diff --git a/go.mod b/go.mod index 2e9959b..b35abf1 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.24 require ( github.com/jacobbrewer1/uhttp v0.0.12 github.com/jmoiron/sqlx v1.4.0 - github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 + github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 github.com/oapi-codegen/runtime v1.1.2 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/vektra/mockery/v2 v2.53.4 ) @@ -18,7 +18,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/getkin/kin-openapi v0.131.0 // indirect + github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect @@ -49,7 +49,8 @@ require ( github.com/rs/zerolog v1.33.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect + github.com/speakeasy-api/jsonpath v0.6.0 // indirect + github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/spf13/cobra v1.8.1 // indirect @@ -58,6 +59,7 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.23.0 // indirect golang.org/x/sync v0.11.0 // indirect diff --git a/go.sum b/go.sum index 1c59cc9..b36eb24 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= -github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= @@ -109,8 +109,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q= -github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8= +github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 h1:5vHNY1uuPBRBWqB2Dp0G7YB03phxLQZupZTIZaeorjc= +github.com/oapi-codegen/oapi-codegen/v2 v2.5.1/go.mod h1:ro0npU1BWkcGpCgGD9QwPp44l5OIZ94tB3eabnT7DjQ= github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= @@ -127,9 +127,8 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= @@ -158,8 +157,10 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg= -github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU= +github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= @@ -176,8 +177,8 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= @@ -186,6 +187,8 @@ github.com/vektra/mockery/v2 v2.53.4 h1:abBWJLUQppM7T/VsLasBwgl7XXQRWH6lC3bnbJpO github.com/vektra/mockery/v2 v2.53.4/go.mod h1:hIFFb3CvzPdDJJiU7J4zLRblUMv7OuezWsHPmswriwo= github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/errors.go b/vendor/github.com/getkin/kin-openapi/openapi3/errors.go index 010dc88..384af59 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/errors.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/errors.go @@ -57,3 +57,13 @@ func (meo multiErrorForOneOf) Error() string { func (meo multiErrorForOneOf) Unwrap() error { return MultiError(meo) } + +type multiErrorForAllOf MultiError + +func (mea multiErrorForAllOf) Error() string { + return spliceErr(" And ", mea) +} + +func (mea multiErrorForAllOf) Unwrap() error { + return MultiError(mea) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go b/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go index d50b3d8..b36b0b7 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go @@ -31,22 +31,35 @@ func ValidateIdentifier(value string) error { return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (charset: [%q])", value, identifierChars) } +// Ptr is a helper for defining OpenAPI schemas. +func Ptr[T any](value T) *T { + return &value +} + // Float64Ptr is a helper for defining OpenAPI schemas. +// +// Deprecated: Use Ptr instead. func Float64Ptr(value float64) *float64 { return &value } // BoolPtr is a helper for defining OpenAPI schemas. +// +// Deprecated: Use Ptr instead. func BoolPtr(value bool) *bool { return &value } // Int64Ptr is a helper for defining OpenAPI schemas. +// +// Deprecated: Use Ptr instead. func Int64Ptr(value int64) *int64 { return &value } // Uint64Ptr is a helper for defining OpenAPI schemas. +// +// Deprecated: Use Ptr instead. func Uint64Ptr(value uint64) *uint64 { return &value } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go b/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go index 01f5dad..b725baf 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go @@ -70,18 +70,11 @@ func DefaultRefNameResolver(doc *T, ref ComponentRef) string { // Trim the common prefix with the root doc path. if doc.url != nil { - commonDir := path.Dir(doc.url.Path) - for { - if commonDir == "." { // no common prefix - break - } - + for commonDir := path.Dir(doc.url.Path); /*no common prefix*/ commonDir != "."; commonDir = path.Dir(commonDir) { if p, found := cutDirectories(filePath, commonDir); found { filePath = p break } - - commonDir = path.Dir(commonDir) } } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/loader.go b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go index 436a1b3..67674fc 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/loader.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go @@ -657,6 +657,11 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat return err } } + for _, example := range value.Examples { + if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { + return err + } + } return nil } @@ -716,12 +721,22 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum return err } } + for _, example := range contentType.Examples { + if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { + return err + } + } } if schema := value.Schema; schema != nil { if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { return err } } + for _, example := range value.Examples { + if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { + return err + } + } return nil } @@ -1214,5 +1229,5 @@ func (loader *Loader) resolvePathItemRef(doc *T, pathItem *PathItem, documentPat } func unescapeRefString(ref string) string { - return strings.Replace(strings.Replace(ref, "~1", "/", -1), "~0", "~", -1) + return strings.ReplaceAll(strings.ReplaceAll(ref, "~1", "/"), "~0", "~") } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go b/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go index b023dfb..5dc4a9b 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go @@ -7,6 +7,7 @@ import ( "net/http" "net/url" "os" + "path" "path/filepath" "sync" ) @@ -79,7 +80,7 @@ func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error) { if !is_file(location) { return nil, ErrURINotSupported } - return os.ReadFile(filepath.FromSlash(location.Path)) + return os.ReadFile(path.Clean(filepath.FromSlash(location.Path))) } // URIMapCache returns a ReadFromURIFunc that caches the contents read from URI diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go b/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go index 35b3365..36aa244 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go @@ -71,8 +71,7 @@ func (responses Responses) JSONLookup(token string) (any, error) { } else if ref := v.Ref; ref != "" { return &Ref{Ref: ref}, nil } else { - var vv *Response = v.Value - return vv, nil + return v.Value, nil } } @@ -213,8 +212,7 @@ func (callback Callback) JSONLookup(token string) (any, error) { } else if ref := v.Ref; ref != "" { return &Ref{Ref: ref}, nil } else { - var vv *PathItem = v - return vv, nil + return v, nil } } @@ -355,8 +353,7 @@ func (paths Paths) JSONLookup(token string) (any, error) { } else if ref := v.Ref; ref != "" { return &Ref{Ref: ref}, nil } else { - var vv *PathItem = v - return vv, nil + return v, nil } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go index 75bcf12..d76bef4 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/schema.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go @@ -17,6 +17,7 @@ import ( "github.com/go-openapi/jsonpointer" "github.com/mohae/deepcopy" + "github.com/woodsbury/decimal128" ) const ( @@ -245,7 +246,7 @@ func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error { addProps.Schema = &SchemaRef{Value: &Schema{}} } else { buf := new(bytes.Buffer) - json.NewEncoder(buf).Encode(y) + _ = json.NewEncoder(buf).Encode(y) if err := json.NewDecoder(buf).Decode(&addProps.Schema); err != nil { return err } @@ -837,12 +838,12 @@ func (schema *Schema) WithMaxProperties(i int64) *Schema { } func (schema *Schema) WithAnyAdditionalProperties() *Schema { - schema.AdditionalProperties = AdditionalProperties{Has: BoolPtr(true)} + schema.AdditionalProperties = AdditionalProperties{Has: Ptr(true)} return schema } func (schema *Schema) WithoutAdditionalProperties() *Schema { - schema.AdditionalProperties = AdditionalProperties{Has: BoolPtr(false)} + schema.AdditionalProperties = AdditionalProperties{Has: Ptr(false)} return schema } @@ -1428,6 +1429,7 @@ func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, val visitedAnyOf = true } + validationErrors := multiErrorForAllOf{} for _, item := range schema.AllOf { v := item.Value if v == nil { @@ -1437,17 +1439,20 @@ func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, val if settings.failfast { return errSchema, false } - return &SchemaError{ - Value: value, - Schema: schema, - SchemaField: "allOf", - Reason: `doesn't match all schemas from "allOf"`, - Origin: err, - customizeMessageError: settings.customizeMessageError, - }, false + validationErrors = append(validationErrors, err) } visitedAllOf = true } + if len(validationErrors) > 0 { + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "allOf", + Reason: `doesn't match all schemas from "allOf"`, + Origin: fmt.Errorf("doesn't match schema due to: %w", validationErrors), + customizeMessageError: settings.customizeMessageError, + }, false + } run = !((visitedOneOf || visitedAnyOf || visitedAllOf) && value == nil) return @@ -1643,7 +1648,10 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value if v := schema.MultipleOf; v != nil { // "A numeric instance is valid only if division by this keyword's // value results in an integer." - if bigFloat := big.NewFloat(value / *v); !bigFloat.IsInt() { + numParsed, _ := decimal128.Parse(fmt.Sprintf("%.10f", value)) + denParsed, _ := decimal128.Parse(fmt.Sprintf("%.10f", *v)) + _, remainder := numParsed.QuoRem(denParsed) + if !remainder.IsZero() { if settings.failfast { return errSchema } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go index 5819713..2010d57 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go @@ -30,6 +30,6 @@ func (schema *Schema) compilePattern(c RegexCompilerFunc) (cp RegexMatcher, err return } - var _ bool = compiledPatterns.CompareAndSwap(pattern, nil, cp) + compiledPatterns.Store(pattern, cp) return } diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen/oapi-codegen.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen/oapi-codegen.go index 2d05cff..78bea5b 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen/oapi-codegen.go +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen/oapi-codegen.go @@ -111,7 +111,7 @@ func main() { flag.StringVar(&flagImportMapping, "import-mapping", "", "A dict from the external reference to golang package path.") flag.StringVar(&flagExcludeSchemas, "exclude-schemas", "", "A comma separated list of schemas which must be excluded from generation.") flag.StringVar(&flagResponseTypeSuffix, "response-type-suffix", "", "The suffix used for responses types.") - flag.BoolVar(&flagAliasTypes, "alias-types", false, "Alias type declarations of possible.") + flag.BoolVar(&flagAliasTypes, "alias-types", false, "Alias type declarations if possible.") flag.BoolVar(&flagInitialismOverrides, "initialism-overrides", false, "Use initialism overrides.") flag.Parse() @@ -271,6 +271,15 @@ func main() { errExit("configuration error: %v\n", err) } + if warnings := opts.Generate.Warnings(); len(warnings) > 0 { + out := "WARNING: A number of warning(s) were returned when validating the GenerateOptions:" + for k, v := range warnings { + out += "\n- " + k + ": " + v + } + + _, _ = fmt.Fprint(os.Stderr, out) + } + // If the user asked to output configuration, output it to stdout and exit if flagOutputConfig { buf, err := yaml.Marshal(opts) @@ -297,11 +306,11 @@ func main() { } if strings.HasPrefix(swagger.OpenAPI, "3.1.") { - fmt.Println("WARNING: You are using an OpenAPI 3.1.x specification, which is not yet supported by oapi-codegen (https://github.com/oapi-codegen/oapi-codegen/issues/373) and so some functionality may not be available. Until oapi-codegen supports OpenAPI 3.1, it is recommended to downgrade your spec to 3.0.x") + fmt.Fprintln(os.Stderr, "WARNING: You are using an OpenAPI 3.1.x specification, which is not yet supported by oapi-codegen (https://github.com/oapi-codegen/oapi-codegen/issues/373) and so some functionality may not be available. Until oapi-codegen supports OpenAPI 3.1, it is recommended to downgrade your spec to 3.0.x") } if len(noVCSVersionOverride) > 0 { - opts.Configuration.NoVCSVersionOverride = &noVCSVersionOverride + opts.NoVCSVersionOverride = &noVCSVersionOverride } code, err := codegen.Generate(swagger, opts.Configuration) @@ -310,6 +319,9 @@ func main() { } if opts.OutputFile != "" { + if err := os.MkdirAll(filepath.Dir(opts.OutputFile), 0o755); err != nil { + errExit("error unable to create directory: %s\n", err) + } err = os.WriteFile(opts.OutputFile, []byte(code), 0o644) if err != nil { errExit("error writing generated code to file: %s\n", err) diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/codegen.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/codegen.go index 632f649..04ac96c 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/codegen.go +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/codegen.go @@ -19,7 +19,9 @@ import ( "bytes" "context" "embed" + "errors" "fmt" + "go/scanner" "io" "io/fs" "net/http" @@ -47,6 +49,9 @@ var globalState struct { options Configuration spec *openapi3.T importMapping importMap + // initialismsMap stores initialisms as "lower(initialism) -> initialism" map. + // List of initialisms was taken from https://staticcheck.io/docs/configuration/options/#initialisms. + initialismsMap map[string]string } // goImport represents a go package to be imported in the generated code @@ -139,6 +144,12 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { opts.OutputOptions.NameNormalizer, NameNormalizers.Options()) } + if nameNormalizerFunction != NameNormalizerFunctionToCamelCaseWithInitialisms && len(opts.OutputOptions.AdditionalInitialisms) > 0 { + return "", fmt.Errorf("you have specified `additional-initialisms`, but the `name-normalizer` is not set to `ToCamelCaseWithInitialisms`. Please specify `name-normalizer: ToCamelCaseWithInitialisms` or remove the `additional-initialisms` configuration") + } + + globalState.initialismsMap = makeInitialismsMap(opts.OutputOptions.AdditionalInitialisms) + // This creates the golang templates text package TemplateFunctions["opts"] = func() Configuration { return globalState.options } t := template.New("oapi-codegen").Funcs(TemplateFunctions) @@ -193,6 +204,14 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { MergeImports(xGoTypeImports, imprts) } + var serverURLsDefinitions string + if opts.Generate.ServerURLs { + serverURLsDefinitions, err = GenerateServerURLs(t, spec) + if err != nil { + return "", fmt.Errorf("error generating Server URLs: %w", err) + } + } + var irisServerOut string if opts.Generate.IrisServer { irisServerOut, err = GenerateIrisServer(t, ops) @@ -317,6 +336,11 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { return "", fmt.Errorf("error writing constants: %w", err) } + _, err = w.WriteString(serverURLsDefinitions) + if err != nil { + return "", fmt.Errorf("error writing Server URLs: %w", err) + } + _, err = w.WriteString(typeDefinitions) if err != nil { return "", fmt.Errorf("error writing type definitions: %w", err) @@ -413,11 +437,36 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { outBytes, err := imports.Process(opts.PackageName+".go", []byte(goCode), nil) if err != nil { - return "", fmt.Errorf("error formatting Go code %s: %w", goCode, err) + // if we don't get a line number + errLine := -1 + var scanErr scanner.ErrorList + if errors.As(err, &scanErr) && scanErr.Len() > 0 { + // for now, only return the first error's information + errLine = scanErr[0].Pos.Line + } + return "", fmt.Errorf("error formatting Go code:\n%s\nerror was: %w", addLineNumbers(goCode, errLine), err) } return string(outBytes), nil } +func addLineNumbers(goCode string, lineWithError int) string { + var out []string + lines := strings.Split(goCode, "\n") + for i, line := range lines { + // lines for humans start at 1 + lineNumber := i + 1 + + errLine := " " + if lineNumber == lineWithError { + errLine = "❗" + } + + out = append(out, fmt.Sprintf("%s%5d: %s", errLine, lineNumber, line)) + } + + return strings.Join(out, "\n") +} + func GenerateTypeDefinitions(t *template.Template, swagger *openapi3.T, ops []OperationDefinition, excludeSchemas []string) (string, error) { var allTypes []TypeDefinition if swagger.Components != nil { @@ -955,7 +1004,9 @@ func GetUserTemplateText(inputData string) (template string, err error) { return "", fmt.Errorf("failed to execute GET request data from %s: %w", inputData, err) } if resp != nil { - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return "", fmt.Errorf("got non %d status code on GET %s", resp.StatusCode, inputData) diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/configuration.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/configuration.go index c2d36f9..1d9ff3e 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/configuration.go +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/configuration.go @@ -126,12 +126,43 @@ type GenerateOptions struct { Models bool `yaml:"models,omitempty"` // EmbeddedSpec indicates whether to embed the swagger spec in the generated code EmbeddedSpec bool `yaml:"embedded-spec,omitempty"` + // ServerURLs generates types for the `Server` definitions' URLs, instead of needing to provide your own values + ServerURLs bool `yaml:"server-urls,omitempty"` } func (oo GenerateOptions) Validate() map[string]string { return nil } +func (oo GenerateOptions) Warnings() map[string]string { + warnings := make(map[string]string) + + if oo.StdHTTPServer { + if warning := oo.warningForStdHTTP(); warning != "" { + warnings["std-http-server"] = warning + } + } + + return warnings +} + +func (oo GenerateOptions) warningForStdHTTP() string { + pathToGoMod, mod, err := findAndParseGoModuleForDepth(".", maximumDepthToSearchForGoMod) + if err != nil { + return fmt.Sprintf("Encountered an error while trying to find a `go.mod` or a `tools.mod` in this directory, or %d levels above it: %v", maximumDepthToSearchForGoMod, err) + } + + if mod == nil { + return fmt.Sprintf("Failed to find a `go.mod` or a `tools.mod` in this directory, or %d levels above it, so unable to validate that you're using Go 1.22+. If you start seeing API interactions resulting in a `404 page not found`, the Go directive (implying source compatibility for this module) needs to be bumped. See also: https://www.jvt.me/posts/2024/03/04/go-net-http-why-404/", maximumDepthToSearchForGoMod) + } + + if !hasMinimalMinorGoDirective(minimumGoVersionForGenerateStdHTTPServer, mod) { + return fmt.Sprintf("Found a `go.mod` or a `tools.mod` at path %v, but it only had a version of %v, whereas the minimum required is 1.%d. It's very likely API interactions will result in a `404 page not found`. The Go directive (implying source compatibility for this module) needs to be bumped. See also: https://www.jvt.me/posts/2024/03/04/go-net-http-why-404/", pathToGoMod, mod.Go.Version, minimumGoVersionForGenerateStdHTTPServer) + } + + return "" +} + // CompatibilityOptions specifies backward compatibility settings for the // code generator. type CompatibilityOptions struct { @@ -195,6 +226,14 @@ type CompatibilityOptions struct { // // NOTE that this can be confusing to users of your OpenAPI specification, who may see a field present and therefore be expecting to see/use it in the request/response, without understanding the nuance of how `oapi-codegen` generates the code. AllowUnexportedStructFieldNames bool `yaml:"allow-unexported-struct-field-names"` + + // PreserveOriginalOperationIdCasingInEmbeddedSpec ensures that the `operationId` from the source spec is kept intact in case when embedding it into the Embedded Spec output. + // When `oapi-codegen` parses the original OpenAPI specification, it will apply the configured `output-options.name-normalizer` to each operation's `operationId` before that is used to generate code from. + // However, this is also applied to the copy of the `operationId`s in the `embedded-spec` generation, which means that the embedded OpenAPI specification is then out-of-sync with the input specificiation. + // To ensure that the `operationId` in the embedded spec is preserved as-is from the input specification, set this. + // NOTE that this will not impact generated code. + // NOTE that if you're using `include-operation-ids` or `exclude-operation-ids` you may want to ensure that the `operationId`s used are correct. + PreserveOriginalOperationIdCasingInEmbeddedSpec bool `yaml:"preserve-original-operation-id-casing-in-embedded-spec"` } func (co CompatibilityOptions) Validate() map[string]string { @@ -226,6 +265,9 @@ type OutputOptions struct { ClientTypeName string `yaml:"client-type-name,omitempty"` // Whether to use the initialism overrides InitialismOverrides bool `yaml:"initialism-overrides,omitempty"` + // AdditionalInitialisms is a list of additional initialisms to use when generating names. + // NOTE that this has no effect unless the `name-normalizer` is set to `ToCamelCaseWithInitialisms` + AdditionalInitialisms []string `yaml:"additional-initialisms,omitempty"` // Whether to generate nullable type for nullable fields NullableType bool `yaml:"nullable-type,omitempty"` @@ -239,9 +281,34 @@ type OutputOptions struct { // Overlay defines configuration for the OpenAPI Overlay (https://github.com/OAI/Overlay-Specification) to manipulate the OpenAPI specification before generation. This allows modifying the specification without needing to apply changes directly to it, making it easier to keep it up-to-date. Overlay OutputOptionsOverlay `yaml:"overlay"` + + // EnableYamlTags adds YAML tags to generated structs, in addition to default JSON ones + EnableYamlTags bool `yaml:"yaml-tags,omitempty"` + + // ClientResponseBytesFunction decides whether to enable the generation of a `Bytes()` method on response objects for `ClientWithResponses` + ClientResponseBytesFunction bool `yaml:"client-response-bytes-function,omitempty"` + + // PreferSkipOptionalPointer allows defining at a global level whether to omit the pointer for a type to indicate that the field/type is optional. + // This is the same as adding `x-go-type-skip-optional-pointer` to each field (manually, or using an OpenAPI Overlay) + PreferSkipOptionalPointer bool `yaml:"prefer-skip-optional-pointer,omitempty"` + + // PreferSkipOptionalPointerWithOmitzero allows generating the `omitzero` JSON tag types that would have had an optional pointer. + // This is the same as adding `x-omitzero` to each field (manually, or using an OpenAPI Overlay). + // A field can set `x-omitzero: false` to disable the `omitzero` JSON tag. + // NOTE that this must be used alongside `prefer-skip-optional-pointer`, otherwise makes no difference. + PreferSkipOptionalPointerWithOmitzero bool `yaml:"prefer-skip-optional-pointer-with-omitzero,omitempty"` + + // PreferSkipOptionalPointerOnContainerTypes allows disabling the generation of an "optional pointer" for an optional field that is a container type (such as a slice or a map), which ends up requiring an additional, unnecessary, `... != nil` check + PreferSkipOptionalPointerOnContainerTypes bool `yaml:"prefer-skip-optional-pointer-on-container-types,omitempty"` } func (oo OutputOptions) Validate() map[string]string { + if NameNormalizerFunction(oo.NameNormalizer) != NameNormalizerFunctionToCamelCaseWithInitialisms && len(oo.AdditionalInitialisms) > 0 { + return map[string]string{ + "additional-initialisms": "You have specified `additional-initialisms`, but the `name-normalizer` is not set to `ToCamelCaseWithInitialisms`. Please specify `name-normalizer: ToCamelCaseWithInitialisms` or remove the `additional-initialisms` configuration", + } + } + return nil } diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/extension.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/extension.go index f5ef5ef..579d8a1 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/extension.go +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/extension.go @@ -18,6 +18,7 @@ const ( extGoTypeName = "x-go-type-name" extPropGoJsonIgnore = "x-go-json-ignore" extPropOmitEmpty = "x-omitempty" + extPropOmitZero = "x-omitzero" extPropExtraTags = "x-oapi-codegen-extra-tags" extEnumVarNames = "x-enum-varnames" extEnumNames = "x-enumNames" @@ -60,6 +61,14 @@ func extParseOmitEmpty(extPropValue interface{}) (bool, error) { return omitEmpty, nil } +func extParseOmitZero(extPropValue interface{}) (bool, error) { + omitZero, ok := extPropValue.(bool) + if !ok { + return false, fmt.Errorf("failed to convert type: %T", extPropValue) + } + return omitZero, nil +} + func extExtraTags(extPropValue interface{}) (map[string]string, error) { tagsI, ok := extPropValue.(map[string]interface{}) if !ok { diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/minimum_go_version.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/minimum_go_version.go new file mode 100644 index 0000000..4f4f70f --- /dev/null +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/minimum_go_version.go @@ -0,0 +1,91 @@ +package codegen + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "golang.org/x/mod/modfile" +) + +const maximumDepthToSearchForGoMod = 5 + +// minimumGoVersionForGenerateStdHTTPServer indicates the Go 1.x minor version that the module the std-http-server is being generated into needs. +// If the version is lower, a warning should be logged. +const minimumGoVersionForGenerateStdHTTPServer = 22 + +func findAndParseGoModuleForDepth(dir string, maxDepth int) (string, *modfile.File, error) { + absDir, err := filepath.Abs(dir) + if err != nil { + return "", nil, fmt.Errorf("failed to determine absolute path for %v: %w", dir, err) + } + currentDir := absDir + + for i := 0; i <= maxDepth; i++ { + goModPath := filepath.Join(currentDir, "go.mod") + if _, err := os.Stat(goModPath); err == nil { + goModContent, err := os.ReadFile(goModPath) + if err != nil { + return "", nil, fmt.Errorf("failed to read `go.mod`: %w", err) + } + + mod, err := modfile.ParseLax("go.mod", goModContent, nil) + if err != nil { + return "", nil, fmt.Errorf("failed to parse `go.mod`: %w", err) + } + + return goModPath, mod, nil + } + + goModPath = filepath.Join(currentDir, "tools.mod") + if _, err := os.Stat(goModPath); err == nil { + goModContent, err := os.ReadFile(goModPath) + if err != nil { + return "", nil, fmt.Errorf("failed to read `tools.mod`: %w", err) + } + + parsedModFile, err := modfile.ParseLax("tools.mod", goModContent, nil) + if err != nil { + return "", nil, fmt.Errorf("failed to parse `tools.mod`: %w", err) + } + + return goModPath, parsedModFile, nil + } + + parentDir := filepath.Dir(currentDir) + // NOTE that this may not work particularly well on Windows + if parentDir == "/" { + break + } + + currentDir = parentDir + } + + return "", nil, fmt.Errorf("no `go.mod` or `tools.mod` file found within %d levels upwards from %s", maxDepth, absDir) +} + +// hasMinimalMinorGoDirective indicates that the Go module (`mod`) has a minor version greater than or equal to the `expected`'s +// This only applies to the `go` directive: +// +// go 1.23 +// go 1.22.1 +func hasMinimalMinorGoDirective(expected int, mod *modfile.File) bool { + parts := strings.Split(mod.Go.Version, ".") + + if len(parts) < 2 { + return false + } + + actual, err := strconv.Atoi(parts[1]) + if err != nil { + return false + } + + if actual < expected { + return false + } + + return true +} diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/operations.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/operations.go index 3b0f130..e4d9784 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/operations.go +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/operations.go @@ -135,10 +135,17 @@ func (pd ParameterDefinition) GoName() string { return SchemaNameToTypeName(goName) } +// Deprecated: Use HasOptionalPointer, as it is clearer what the intent is. func (pd ParameterDefinition) IndirectOptional() bool { return !pd.Required && !pd.Schema.SkipOptionalPointer } +// HasOptionalPointer indicates whether the generated property has an optional pointer associated with it. +// This takes into account the `x-go-type-skip-optional-pointer` extension, allowing a parameter definition to control whether the pointer should be skipped. +func (pd ParameterDefinition) HasOptionalPointer() bool { + return pd.Required == false && pd.Schema.SkipOptionalPointer == false //nolint:staticcheck +} + type ParameterDefinitions []ParameterDefinition func (p ParameterDefinitions) FindByName(name string) *ParameterDefinition { @@ -208,7 +215,8 @@ func DescribeSecurityDefinition(securityRequirements openapi3.SecurityRequiremen // OperationDefinition describes an Operation type OperationDefinition struct { - OperationId string // The operation_id description from Swagger, used to generate function names + // OperationId is the `operationId` field from the OpenAPI Specification, after going through a `nameNormalizer`, and will be used to generate function names + OperationId string PathParams []ParameterDefinition // Parameters in the path, eg, /path/:param HeaderParams []ParameterDefinition // Parameters in HTTP headers @@ -299,7 +307,7 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefini if contentType.Schema != nil { responseSchema, err := GenerateGoSchema(contentType.Schema, []string{o.OperationId, responseName}) if err != nil { - return nil, fmt.Errorf("Unable to determine Go type for %s.%s: %w", o.OperationId, contentTypeName, err) + return nil, fmt.Errorf("unable to determine Go type for %s.%s: %w", o.OperationId, contentTypeName, err) } var typeName string @@ -308,7 +316,7 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefini // HAL+JSON: case StringInArray(contentTypeName, contentTypesHalJSON): typeName = fmt.Sprintf("HALJSON%s", nameNormalizer(responseName)) - case "application/json" == contentTypeName: + case contentTypeName == "application/json": // if it's the standard application/json typeName = fmt.Sprintf("JSON%s", nameNormalizer(responseName)) // Vendored JSON @@ -558,25 +566,34 @@ func OperationDefinitions(swagger *openapi3.T, initialismOverrides bool) ([]Oper // Each path can have a number of operations, POST, GET, OPTIONS, etc. pathOps := pathItem.Operations() for _, opName := range SortedMapKeys(pathOps) { + // NOTE that this is a reference to the existing copy of the Operation, so any modifications will modify our shared copy of the spec op := pathOps[opName] + if pathItem.Servers != nil { op.Servers = &pathItem.Servers } + // take a copy of operationId, so we don't modify the underlying spec + operationId := op.OperationID // We rely on OperationID to generate function names, it's required - if op.OperationID == "" { - op.OperationID, err = generateDefaultOperationID(opName, requestPath, toCamelCaseFunc) + if operationId == "" { + operationId, err = generateDefaultOperationID(opName, requestPath, toCamelCaseFunc) if err != nil { return nil, fmt.Errorf("error generating default OperationID for %s/%s: %s", opName, requestPath, err) } } else { - op.OperationID = nameNormalizer(op.OperationID) + operationId = nameNormalizer(operationId) + } + operationId = typeNamePrefix(operationId) + operationId + + if !globalState.options.Compatibility.PreserveOriginalOperationIdCasingInEmbeddedSpec { + // update the existing, shared, copy of the spec if we're not wanting to preserve it + op.OperationID = operationId } - op.OperationID = typeNamePrefix(op.OperationID) + op.OperationID // These are parameters defined for the specific path method that // we're iterating over. - localParams, err := DescribeParameters(op.Parameters, []string{op.OperationID + "Params"}) + localParams, err := DescribeParameters(op.Parameters, []string{operationId + "Params"}) if err != nil { return nil, fmt.Errorf("error describing global parameters for %s/%s: %s", opName, requestPath, err) @@ -599,14 +616,14 @@ func OperationDefinitions(swagger *openapi3.T, initialismOverrides bool) ([]Oper return nil, err } - bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(op.OperationID, op.RequestBody) + bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(operationId, op.RequestBody) if err != nil { return nil, fmt.Errorf("error generating body definitions: %w", err) } ensureExternalRefsInRequestBodyDefinitions(&bodyDefinitions, pathItem.Ref) - responseDefinitions, err := GenerateResponseDefinitions(op.OperationID, op.Responses.Map()) + responseDefinitions, err := GenerateResponseDefinitions(operationId, op.Responses.Map()) if err != nil { return nil, fmt.Errorf("error generating response definitions: %w", err) } @@ -618,7 +635,7 @@ func OperationDefinitions(swagger *openapi3.T, initialismOverrides bool) ([]Oper HeaderParams: FilterParameterDefinitionByType(allParams, "header"), QueryParams: FilterParameterDefinitionByType(allParams, "query"), CookieParams: FilterParameterDefinitionByType(allParams, "cookie"), - OperationId: nameNormalizer(op.OperationID), + OperationId: nameNormalizer(operationId), // Replace newlines in summary. Summary: op.Summary, Method: opName, diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/schema.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/schema.go index 2cef002..c099752 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/schema.go +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/schema.go @@ -128,6 +128,29 @@ func (p Property) GoTypeDef() string { return typeDef } +// RequiresNilCheck indicates whether the generated property should have a nil check performed on it before other checks. +// This should be used in templates when performing `nil` checks, but NOT when i.e. determining if there should be an optional pointer given to the type - in that case, use `HasOptionalPointer` +func (p Property) RequiresNilCheck() bool { + return p.ZeroValueIsNil() || p.HasOptionalPointer() +} + +// HasOptionalPointer indicates whether the generated property has an optional pointer associated with it. +// This takes into account the `x-go-type-skip-optional-pointer` extension, allowing a parameter definition to control whether the pointer should be skipped. +func (p Property) HasOptionalPointer() bool { + return p.Required == false && p.Schema.SkipOptionalPointer == false //nolint:staticcheck +} + +// ZeroValueIsNil is a helper function to determine if the given Go type used for this property +// Will return true if the OpenAPI `type` is: +// - `array` +func (p Property) ZeroValueIsNil() bool { + if p.Schema.OAPISchema == nil { + return false + } + + return p.Schema.OAPISchema.Type.Is("array") +} + // EnumDefinition holds type information for enum type EnumDefinition struct { // Schema is the scheme of a type which has a list of enum values, eg, the @@ -254,6 +277,18 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { schema := sref.Value + // Check x-go-type-skip-optional-pointer, which will override if the type + // should be a pointer or not when the field is optional. + // NOTE skipOptionalPointer will be defaulted to the global value, but can be overridden on a per-type/-field basis + skipOptionalPointer := globalState.options.OutputOptions.PreferSkipOptionalPointer + if extension, ok := schema.Extensions[extPropGoTypeSkipOptionalPointer]; ok { + var err error + skipOptionalPointer, err = extParsePropGoTypeSkipOptionalPointer(extension) + if err != nil { + return Schema{}, fmt.Errorf("invalid value for %q: %w", extPropGoTypeSkipOptionalPointer, err) + } + } + // If Ref is set on the SchemaRef, it means that this type is actually a reference to // another type. We're not de-referencing, so simply use the referenced type. if IsGoTypeReference(sref.Ref) { @@ -264,16 +299,18 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { sref.Ref, err) } return Schema{ - GoType: refType, - Description: schema.Description, - DefineViaAlias: true, - OAPISchema: schema, + GoType: refType, + Description: schema.Description, + DefineViaAlias: true, + OAPISchema: schema, + SkipOptionalPointer: skipOptionalPointer, }, nil } outSchema := Schema{ - Description: schema.Description, - OAPISchema: schema, + Description: schema.Description, + OAPISchema: schema, + SkipOptionalPointer: skipOptionalPointer, } // AllOf is interesting, and useful. It's the union of a number of other @@ -302,16 +339,6 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { return outSchema, nil } - // Check x-go-type-skip-optional-pointer, which will override if the type - // should be a pointer or not when the field is optional. - if extension, ok := schema.Extensions[extPropGoTypeSkipOptionalPointer]; ok { - skipOptionalPointer, err := extParsePropGoTypeSkipOptionalPointer(extension) - if err != nil { - return outSchema, fmt.Errorf("invalid value for %q: %w", extPropGoTypeSkipOptionalPointer, err) - } - outSchema.SkipOptionalPointer = skipOptionalPointer - } - // Schema type and format, eg. string / binary t := schema.Type // Handle objects and empty schemas first as a special case @@ -325,10 +352,13 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { // We have an object with no properties. This is a generic object // expressed as a map. outType = "map[string]interface{}" + setSkipOptionalPointerForContainerType(&outSchema) } else { // t == "" // If we don't even have the object designator, we're a completely // generic type. outType = "interface{}" + // this should never have an "optional pointer", as it doesn't make sense to be a `*interface{}` + outSchema.SkipOptionalPointer = true } outSchema.GoType = outType outSchema.DefineViaAlias = true @@ -385,6 +415,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { // since we don't need them for a simple map. outSchema.HasAdditionalProperties = false outSchema.GoType = fmt.Sprintf("map[string]%s", additionalPropertiesType(outSchema)) + setSkipOptionalPointerForContainerType(&outSchema) return outSchema, nil } @@ -580,40 +611,34 @@ func oapiSchemaToGoType(schema *openapi3.Schema, path []string, outSchema *Schem if sliceContains(globalState.options.OutputOptions.DisableTypeAliasesForType, "array") { outSchema.DefineViaAlias = false } + setSkipOptionalPointerForContainerType(outSchema) } else if t.Is("integer") { // We default to int if format doesn't ask for something else. - if f == "int64" { - outSchema.GoType = "int64" - } else if f == "int32" { - outSchema.GoType = "int32" - } else if f == "int16" { - outSchema.GoType = "int16" - } else if f == "int8" { - outSchema.GoType = "int8" - } else if f == "int" { - outSchema.GoType = "int" - } else if f == "uint64" { - outSchema.GoType = "uint64" - } else if f == "uint32" { - outSchema.GoType = "uint32" - } else if f == "uint16" { - outSchema.GoType = "uint16" - } else if f == "uint8" { - outSchema.GoType = "uint8" - } else if f == "uint" { - outSchema.GoType = "uint" - } else { + switch f { + case "int64", + "int32", + "int16", + "int8", + "int", + "uint64", + "uint32", + "uint16", + "uint8", + "uint": + outSchema.GoType = f + default: outSchema.GoType = "int" } outSchema.DefineViaAlias = true } else if t.Is("number") { // We default to float for "number" - if f == "double" { + switch f { + case "double": outSchema.GoType = "float64" - } else if f == "float" || f == "" { + case "float", "": outSchema.GoType = "float32" - } else { + default: return fmt.Errorf("invalid number format: %s", f) } outSchema.DefineViaAlias = true @@ -628,6 +653,7 @@ func oapiSchemaToGoType(schema *openapi3.Schema, path []string, outSchema *Schem switch f { case "byte": outSchema.GoType = "[]byte" + setSkipOptionalPointerForContainerType(outSchema) case "email": outSchema.GoType = "openapi_types.Email" case "date": @@ -667,6 +693,13 @@ type FieldDescriptor struct { IsRef bool // Is this schema a reference to predefined object? } +func stringOrEmpty(b bool, s string) string { + if b { + return s + } + return "" +} + // GenFieldsFromProperties produce corresponding field names with JSON annotations, // given a list of schema descriptors func GenFieldsFromProperties(props []Property) []string { @@ -690,8 +723,8 @@ func GenFieldsFromProperties(props []Property) []string { // This comment has to be on its own line for godoc & IDEs to pick up var deprecationReason string if extension, ok := p.Extensions[extDeprecationReason]; ok { - if extOmitEmpty, err := extParseDeprecationReason(extension); err == nil { - deprecationReason = extOmitEmpty + if extDeprecationReason, err := extParseDeprecationReason(extension); err == nil { + deprecationReason = extDeprecationReason } } @@ -717,25 +750,37 @@ func GenFieldsFromProperties(props []Property) []string { omitEmpty = shouldOmitEmpty } - // Support x-omitempty + omitZero := false + + // default, but allow turning of + if shouldOmitEmpty && p.Schema.SkipOptionalPointer && globalState.options.OutputOptions.PreferSkipOptionalPointerWithOmitzero { + omitZero = true + } + + // Support x-omitempty and x-omitzero if extOmitEmptyValue, ok := p.Extensions[extPropOmitEmpty]; ok { - if extOmitEmpty, err := extParseOmitEmpty(extOmitEmptyValue); err == nil { - omitEmpty = extOmitEmpty + if xValue, err := extParseOmitEmpty(extOmitEmptyValue); err == nil { + omitEmpty = xValue + } + } + + if extOmitEmptyValue, ok := p.Extensions[extPropOmitZero]; ok { + if xValue, err := extParseOmitZero(extOmitEmptyValue); err == nil { + omitZero = xValue } } fieldTags := make(map[string]string) - if !omitEmpty { - fieldTags["json"] = p.JsonFieldName - if p.NeedsFormTag { - fieldTags["form"] = p.JsonFieldName - } - } else { - fieldTags["json"] = p.JsonFieldName + ",omitempty" - if p.NeedsFormTag { - fieldTags["form"] = p.JsonFieldName + ",omitempty" - } + fieldTags["json"] = p.JsonFieldName + + stringOrEmpty(omitEmpty, ",omitempty") + + stringOrEmpty(omitZero, ",omitzero") + + if globalState.options.OutputOptions.EnableYamlTags { + fieldTags["yaml"] = p.JsonFieldName + stringOrEmpty(omitEmpty, ",omitempty") + } + if p.NeedsFormTag { + fieldTags["form"] = p.JsonFieldName + stringOrEmpty(omitEmpty, ",omitempty") } // Support x-go-json-ignore @@ -889,3 +934,14 @@ func generateUnion(outSchema *Schema, elements openapi3.SchemaRefs, discriminato return nil } + +// setSkipOptionalPointerForContainerType ensures that the "optional pointer" is skipped on container types (such as a slice or a map). +// This is controlled using the `prefer-skip-optional-pointer-on-container-types` Output Option +// NOTE that it is still possible to override this on a per-field basis with `x-go-type-skip-optional-pointer` +func setSkipOptionalPointerForContainerType(outSchema *Schema) { + if !globalState.options.OutputOptions.PreferSkipOptionalPointerOnContainerTypes { + return + } + + outSchema.SkipOptionalPointer = true +} diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/server_urls.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/server_urls.go new file mode 100644 index 0000000..d10c2d9 --- /dev/null +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/server_urls.go @@ -0,0 +1,81 @@ +package codegen + +import ( + "fmt" + "strconv" + "text/template" + + "github.com/getkin/kin-openapi/openapi3" +) + +const serverURLPrefix = "ServerUrl" +const serverURLSuffixIterations = 10 + +// ServerObjectDefinition defines the definition of an OpenAPI Server object (https://spec.openapis.org/oas/v3.0.3#server-object) as it is provided to code generation in `oapi-codegen` +type ServerObjectDefinition struct { + // GoName is the name of the variable for this Server URL + GoName string + + // OAPISchema is the underlying OpenAPI representation of the Server + OAPISchema *openapi3.Server +} + +func GenerateServerURLs(t *template.Template, spec *openapi3.T) (string, error) { + names := make(map[string]*openapi3.Server) + + for _, server := range spec.Servers { + suffix := server.Description + if suffix == "" { + suffix = nameNormalizer(server.URL) + } + name := serverURLPrefix + UppercaseFirstCharacter(suffix) + name = nameNormalizer(name) + + // if this is the only type with this name, store it + if _, conflict := names[name]; !conflict { + names[name] = server + continue + } + + // otherwise, try appending a number to the name + saved := false + // NOTE that we start at 1 on purpose, as + // + // ... ServerURLDevelopmentServer + // ... ServerURLDevelopmentServer1` + // + // reads better than: + // + // ... ServerURLDevelopmentServer + // ... ServerURLDevelopmentServer0 + for i := 1; i < 1+serverURLSuffixIterations; i++ { + suffixed := name + strconv.Itoa(i) + // and then store it if there's no conflict + if _, suffixConflict := names[suffixed]; !suffixConflict { + names[suffixed] = server + saved = true + break + } + } + + if saved { + continue + } + + // otherwise, error + return "", fmt.Errorf("failed to create a unique name for the Server URL (%#v) with description (%#v) after %d iterations", server.URL, server.Description, serverURLSuffixIterations) + } + + keys := SortedMapKeys(names) + servers := make([]ServerObjectDefinition, len(keys)) + i := 0 + for _, k := range keys { + servers[i] = ServerObjectDefinition{ + GoName: k, + OAPISchema: names[k], + } + i++ + } + + return GenerateTemplates([]string{"server-urls.tmpl"}, t, servers) +} diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/template_helpers.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/template_helpers.go index b9efe2c..49ee3ab 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/template_helpers.go +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/template_helpers.go @@ -23,6 +23,7 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" + "github.com/getkin/kin-openapi/openapi3" "github.com/oapi-codegen/oapi-codegen/v2/pkg/util" ) @@ -295,6 +296,26 @@ func stripNewLines(s string) string { return r.Replace(s) } +// genServerURLWithVariablesFunctionParams is a template helper method to generate the function parameters for the generated function for a Server object that contains `variables` (https://spec.openapis.org/oas/v3.0.3#server-object) +// +// goTypePrefix is the prefix being used to create underlying types in the template (likely the `ServerObjectDefinition.GoName`) +// variables are this `ServerObjectDefinition`'s variables for the Server object (likely the `ServerObjectDefinition.OAPISchema`) +func genServerURLWithVariablesFunctionParams(goTypePrefix string, variables map[string]*openapi3.ServerVariable) string { + keys := SortedMapKeys(variables) + + if len(variables) == 0 { + return "" + } + parts := make([]string, len(variables)) + + for i := range keys { + k := keys[i] + variableDefinitionPrefix := goTypePrefix + UppercaseFirstCharacter(k) + "Variable" + parts[i] = k + " " + variableDefinitionPrefix + } + return strings.Join(parts, ", ") +} + // TemplateFunctions is passed to the template engine, and we can call each // function here by keyName from the template code. var TemplateFunctions = template.FuncMap{ @@ -323,4 +344,6 @@ var TemplateFunctions = template.FuncMap{ "stripNewLines": stripNewLines, "sanitizeGoIdentity": SanitizeGoIdentity, "toGoComment": StringWithTypeNameToGoComment, + + "genServerURLWithVariablesFunctionParams": genServerURLWithVariablesFunctionParams, } diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/additional-properties.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/additional-properties.tmpl index 7b7c0ac..a103216 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/additional-properties.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/additional-properties.tmpl @@ -53,12 +53,12 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) { var err error object := make(map[string]json.RawMessage) {{range .Schema.Properties}} -{{if not .Required}}if a.{{.GoFieldName}} != nil { {{end}} +{{if .RequiresNilCheck}}if a.{{.GoFieldName}} != nil { {{end}} object["{{.JsonFieldName}}"], err = json.Marshal(a.{{.GoFieldName}}) if err != nil { return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err) } -{{if not .Required}} }{{end}} +{{if .RequiresNilCheck}} }{{end}} {{end}} for fieldName, field := range a.AdditionalProperties { object[fieldName], err = json.Marshal(field) @@ -69,4 +69,4 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) { return json.Marshal(object) } {{end}} -{{end}} \ No newline at end of file +{{end}} diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/chi/chi-middleware.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/chi/chi-middleware.tmpl index 9423a67..97866fc 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/chi/chi-middleware.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/chi/chi-middleware.tmpl @@ -58,7 +58,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} @@ -69,7 +69,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) @@ -98,7 +98,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if .HasOptionalPointer }}&{{end}}valueList[0] {{end}} {{if .IsJson}} @@ -117,7 +117,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found") @@ -135,7 +135,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ if cookie, err = r.Cookie("{{.ParamName}}"); err == nil { {{- if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value {{end}} {{- if .IsJson}} @@ -154,7 +154,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{- if .IsStyled}} @@ -164,7 +164,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} } diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/client-with-responses.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/client-with-responses.tmpl index 908f214..3b85500 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/client-with-responses.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/client-with-responses.tmpl @@ -74,6 +74,13 @@ func (r {{genResponseTypeName $opid | ucFirst}}) StatusCode() int { } return 0 } + +{{ if opts.OutputOptions.ClientResponseBytesFunction }} +// Bytes is a convenience method to retrieve the raw bytes from the HTTP response +func (r {{genResponseTypeName $opid | ucFirst}}) Bytes() []byte { + return r.Body +} +{{end}} {{end}} diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/client.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/client.tmpl index 10ee564..822e110 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/client.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/client.tmpl @@ -197,12 +197,12 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr if params != nil { queryValues := queryURL.Query() {{range $paramIdx, $param := .QueryParams}} - {{if not .Required}} if params.{{.GoName}} != nil { {{end}} + {{if .HasOptionalPointer}} if params.{{.GoName}} != nil { {{end}} {{if .IsPassThrough}} - queryValues.Add("{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}) + queryValues.Add("{{.ParamName}}", {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}) {{end}} {{if .IsJson}} - if queryParamBuf, err := json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}); err != nil { + if queryParamBuf, err := json.Marshal({{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}); err != nil { return nil, err } else { queryValues.Add("{{.ParamName}}", string(queryParamBuf)) @@ -210,7 +210,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr {{end}} {{if .IsStyled}} - if queryFrag, err := runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationQuery, {{if not .Required}}*{{end}}params.{{.GoName}}); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationQuery, {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -222,7 +222,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr } } {{end}} - {{if not .Required}}}{{end}} + {{if .HasOptionalPointer}}}{{end}} {{end}} queryURL.RawQuery = queryValues.Encode() } @@ -236,27 +236,27 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr {{ if .HeaderParams }} if params != nil { {{range $paramIdx, $param := .HeaderParams}} - {{if not .Required}} if params.{{.GoName}} != nil { {{end}} + {{if .HasOptionalPointer}} if params.{{.GoName}} != nil { {{end}} var headerParam{{$paramIdx}} string {{if .IsPassThrough}} - headerParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}} + headerParam{{$paramIdx}} = {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}} {{end}} {{if .IsJson}} var headerParamBuf{{$paramIdx}} []byte - headerParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}) + headerParamBuf{{$paramIdx}}, err = json.Marshal({{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}) if err != nil { return nil, err } headerParam{{$paramIdx}} = string(headerParamBuf{{$paramIdx}}) {{end}} {{if .IsStyled}} - headerParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, {{if not .Required}}*{{end}}params.{{.GoName}}) + headerParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}) if err != nil { return nil, err } {{end}} req.Header.Set("{{.ParamName}}", headerParam{{$paramIdx}}) - {{if not .Required}}}{{end}} + {{if .HasOptionalPointer}}}{{end}} {{end}} } {{- end }}{{/* if .HeaderParams */}} @@ -264,21 +264,21 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr {{ if .CookieParams }} if params != nil { {{range $paramIdx, $param := .CookieParams}} - {{if not .Required}} if params.{{.GoName}} != nil { {{end}} + {{if .HasOptionalPointer}} if params.{{.GoName}} != nil { {{end}} var cookieParam{{$paramIdx}} string {{if .IsPassThrough}} - cookieParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}} + cookieParam{{$paramIdx}} = {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}} {{end}} {{if .IsJson}} var cookieParamBuf{{$paramIdx}} []byte - cookieParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}) + cookieParamBuf{{$paramIdx}}, err = json.Marshal({{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}) if err != nil { return nil, err } cookieParam{{$paramIdx}} = url.QueryEscape(string(cookieParamBuf{{$paramIdx}})) {{end}} {{if .IsStyled}} - cookieParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("simple", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationCookie, {{if not .Required}}*{{end}}params.{{.GoName}}) + cookieParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("simple", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationCookie, {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}) if err != nil { return nil, err } @@ -288,7 +288,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr Value:cookieParam{{$paramIdx}}, } req.AddCookie(cookie{{$paramIdx}}) - {{if not .Required}}}{{end}} + {{if .HasOptionalPointer}}}{{end}} {{ end -}} } {{- end }}{{/* if .CookieParams */}} diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/echo/echo-wrappers.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/echo/echo-wrappers.tmpl index 1603c3d..ea7b75e 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/echo/echo-wrappers.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/echo/echo-wrappers.tmpl @@ -44,7 +44,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { {{else}} if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} var value {{.TypeDef}} @@ -52,7 +52,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) @@ -70,7 +70,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n)) } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] {{end}} {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) @@ -84,7 +84,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found")) }{{end}} @@ -94,7 +94,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { {{range .CookieParams}} if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value {{end}} {{if .IsJson}} var value {{.TypeDef}} @@ -107,7 +107,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{if .IsStyled}} var value {{.TypeDef}} @@ -115,7 +115,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/fiber/fiber-middleware.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/fiber/fiber-middleware.tmpl index 0904c78..e44f483 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/fiber/fiber-middleware.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/fiber/fiber-middleware.tmpl @@ -59,7 +59,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error { if paramValue := c.Query("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} @@ -69,7 +69,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error()) } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { err = fmt.Errorf("Query argument {{.ParamName}} is required, but not found") @@ -93,7 +93,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error { var {{.GoName}} {{.TypeDef}} {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{if .IsJson}} @@ -110,7 +110,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error { } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { err = fmt.Errorf("Header parameter {{.ParamName}} is required, but not found: %w", err) @@ -126,7 +126,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error { if cookie = c.Cookies("{{.ParamName}}"); cookie == "" { {{- if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie + params.{{.GoName}} = {{if .HasOptionalPointer}}}&{{end}}cookie {{end}} {{- if .IsJson}} @@ -142,7 +142,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error()) } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{- if .IsStyled}} @@ -151,7 +151,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error { if err != nil { return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error()) } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} } diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gin/gin-wrappers.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gin/gin-wrappers.tmpl index 8f1a6b2..3bc02e5 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gin/gin-wrappers.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gin/gin-wrappers.tmpl @@ -55,7 +55,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { if paramValue := c.Query("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} @@ -66,7 +66,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { siw.ErrorHandler(c, fmt.Errorf("Query argument {{.ParamName}} is required, but not found"), http.StatusBadRequest) @@ -96,7 +96,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] {{end}} {{if .IsJson}} @@ -115,7 +115,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { siw.ErrorHandler(c, fmt.Errorf("Header parameter {{.ParamName}} is required, but not found"), http.StatusBadRequest) @@ -132,7 +132,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { if cookie, err = c.Cookie("{{.ParamName}}"); err == nil { {{- if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie {{end}} {{- if .IsJson}} @@ -150,7 +150,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{- if .IsStyled}} @@ -160,7 +160,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err), http.StatusBadRequest) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} } diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl index 85b04c8..e8aa979 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl @@ -58,7 +58,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} @@ -69,7 +69,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) @@ -98,7 +98,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] {{end}} {{if .IsJson}} @@ -117,7 +117,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { err = fmt.Errorf("Header parameter {{.ParamName}} is required, but not found") @@ -135,7 +135,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ if cookie, err = r.Cookie("{{.ParamName}}"); err == nil { {{- if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value {{end}} {{- if .IsJson}} @@ -154,7 +154,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{- if .IsStyled}} @@ -164,7 +164,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} } diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/iris/iris-middleware.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/iris/iris-middleware.tmpl index ae40439..814e6bc 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/iris/iris-middleware.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/iris/iris-middleware.tmpl @@ -55,7 +55,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) { {{else}} if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} var value {{.TypeDef}} @@ -65,7 +65,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) { ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON") return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { ctx.StatusCode(http.StatusBadRequest) @@ -87,7 +87,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) { return } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] {{end}} {{if .IsJson}} err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) @@ -105,7 +105,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) { return } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { ctx.StatusCode(http.StatusBadRequest) ctx.WriteString("Header {{.ParamName}} is required, but not found") @@ -117,7 +117,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) { {{range .CookieParams}} if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value {{end}} {{if .IsJson}} var value {{.TypeDef}} @@ -134,7 +134,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) { ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON") return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{if .IsStyled}} var value {{.TypeDef}} @@ -144,7 +144,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) { ctx.Writef("Invalid format for parameter {{.ParamName}}: %s", err) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { ctx.StatusCode(http.StatusBadRequest) diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/server-urls.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/server-urls.tmpl new file mode 100644 index 0000000..f3599e5 --- /dev/null +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/server-urls.tmpl @@ -0,0 +1,61 @@ +{{ range . }} +{{ if eq 0 (len .OAPISchema.Variables) }} +{{/* URLs without variables are straightforward, so we'll create them a constant */}} +// {{ .GoName }} defines the Server URL for {{ .OAPISchema.Description }} +const {{ .GoName}} = "{{ .OAPISchema.URL }}" +{{ else }} +{{/* URLs with variables are not straightforward, as we may need multiple types, and so will model them as a function */}} + +{{/* first, we'll start by generating requisite types */}} + +{{ $goName := .GoName }} +{{ range $k, $v := .OAPISchema.Variables }} + {{ $prefix := printf "%s%sVariable" $goName ($k | ucFirst) }} + // {{ $prefix }} is the `{{ $k }}` variable for {{ $goName }} + type {{ $prefix }} string + {{ range $v.Enum }} + {{/* TODO this may result in broken generated code if any of the `enum` values are the literal value `default` https://github.com/oapi-codegen/oapi-codegen/issues/2003 */}} + // {{ $prefix }}{{ . | ucFirst }} is one of the accepted values for the `{{ $k }}` variable for {{ $goName }} + const {{ $prefix }}{{ . | ucFirst }} {{ $prefix }} = "{{ . }}" + {{ end }} + + {{/* TODO we should introduce a `Valid() error` method to enums https://github.com/oapi-codegen/oapi-codegen/issues/2006 */}} + + {{ if $v.Default }} + {{ if gt (len $v.Enum) 0 }} + {{/* if we have an enum, we should use the type defined for it for its default value + and reference the constant we've already defined for the value */}} + {{/* TODO this may result in broken generated code if any of the `enum` values are the literal value `default` https://github.com/oapi-codegen/oapi-codegen/issues/2003 */}} + {{/* TODO this may result in broken generated code if the `default` isn't found in `enum` (which is an issue with the spec) https://github.com/oapi-codegen/oapi-codegen/issues/2007 */}} + // {{ $prefix }}Default is the default choice, for the accepted values for the `{{ $k }}` variable for {{ $goName }} + const {{ $prefix }}Default {{ $prefix }} = {{ $prefix }}{{ $v.Default | ucFirst }} + {{ else }} + // {{ $prefix }}Default is the default value for the `{{ $k }}` variable for {{ $goName }} + const {{ $prefix }}Default = "{{ $v.Default }}" + {{ end }} + {{ end }} +{{ end }} + + +// New{{ .GoName }} constructs the Server URL for {{ .OAPISchema.Description }}, with the provided variables. +func New{{ .GoName }}({{ genServerURLWithVariablesFunctionParams .GoName .OAPISchema.Variables }}) (string, error) { + u := "{{ .OAPISchema.URL }}" + + {{ range $k, $v := .OAPISchema.Variables }} + {{- $placeholder := printf "{%s}" $k -}} + {{- if gt (len $v.Enum) 0 -}} + {{/* TODO https://github.com/oapi-codegen/oapi-codegen/issues/2006 */}} + // TODO in the future, this will validate that the value is part of the {{ printf "%s%sVariable" $goName ($k | ucFirst) }} enum + {{ end -}} + u = strings.ReplaceAll(u, "{{ $placeholder }}", string({{ $k }})) + {{ end }} + + if strings.Contains(u, "{") || strings.Contains(u, "}") { + return "", fmt.Errorf("after mapping variables, there were still `{` or `}` characters in the string: %#v", u) + } + + return u, nil +} + +{{ end }} +{{ end }} diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl index 19f1fe2..0997735 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl @@ -58,7 +58,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue {{end}} {{if .IsJson}} @@ -69,7 +69,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} }{{if .Required}} else { siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"}) @@ -98,7 +98,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ } {{if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0] {{end}} {{if .IsJson}} @@ -117,7 +117,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ } {{end}} - params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}} } {{if .Required}}else { err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found") @@ -135,7 +135,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ if cookie, err = r.Cookie("{{.ParamName}}"); err == nil { {{- if .IsPassThrough}} - params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value {{end}} {{- if .IsJson}} @@ -154,7 +154,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} {{- if .IsStyled}} @@ -164,7 +164,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err}) return } - params.{{.GoName}} = {{if not .Required}}&{{end}}value + params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value {{end}} } diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/union-and-additional-properties.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/union-and-additional-properties.tmpl index 79b4c67..6ec69f5 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/union-and-additional-properties.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/union-and-additional-properties.tmpl @@ -54,12 +54,12 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) { } } {{range .Schema.Properties}} -{{if not .Required}}if a.{{.GoFieldName}} != nil { {{end}} +{{if .RequiresNilCheck}}if a.{{.GoFieldName}} != nil { {{end}} object["{{.JsonFieldName}}"], err = json.Marshal(a.{{.GoFieldName}}) if err != nil { return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err) } -{{if not .Required}} }{{end}} +{{if .RequiresNilCheck}} }{{end}} {{end}} for fieldName, field := range a.AdditionalProperties { object[fieldName], err = json.Marshal(field) diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/union.tmpl b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/union.tmpl index 464fb11..b27a2ac 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/union.tmpl +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/union.tmpl @@ -102,12 +102,12 @@ } } {{range .Schema.Properties}} - {{if not .Required}}if t.{{.GoFieldName}} != nil { {{end}} + {{if .RequiresNilCheck}}if t.{{.GoFieldName}} != nil { {{end}} object["{{.JsonFieldName}}"], err = json.Marshal(t.{{.GoFieldName}}) if err != nil { return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err) } - {{if not .Required}} }{{end}} + {{if .RequiresNilCheck}} }{{end}} {{end -}} b, err = json.Marshal(object) {{end -}} diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/utils.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/utils.go index e82d5e3..5326e67 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/utils.go +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/utils.go @@ -283,7 +283,7 @@ func ToCamelCaseWithDigits(s string) string { func ToCamelCaseWithInitialisms(s string) string { parts := camelCaseMatchParts.FindAllString(ToCamelCaseWithDigits(s), -1) for i := range parts { - if v, ok := initialismsMap[strings.ToLower(parts[i])]; ok { + if v, ok := globalState.initialismsMap[strings.ToLower(parts[i])]; ok { parts[i] = v } } @@ -292,19 +292,26 @@ func ToCamelCaseWithInitialisms(s string) string { var camelCaseMatchParts = regexp.MustCompile(`[\p{Lu}\d]+([\p{Ll}\d]+|$)`) -// initialismsMap stores initialisms as "lower(initialism) -> initialism" map. -// List of initialisms was taken from https://staticcheck.io/docs/configuration/options/#initialisms. -var initialismsMap = makeInitialismsMap([]string{ +var initialismsList = []string{ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS", -}) +} + +// targetWordRegex is a regex that matches all initialisms. +var targetWordRegex *regexp.Regexp + +func makeInitialismsMap(additionalInitialisms []string) map[string]string { + l := append(initialismsList, additionalInitialisms...) -func makeInitialismsMap(l []string) map[string]string { m := make(map[string]string, len(l)) for i := range l { m[strings.ToLower(l[i])] = l[i] } + + // Create a regex to match the initialisms + targetWordRegex = regexp.MustCompile(`(?i)(` + strings.Join(l, "|") + `)`) + return m } @@ -315,8 +322,6 @@ func ToCamelCaseWithInitialism(str string) string { func replaceInitialism(s string) string { // These strings do not apply CamelCase // Do not do CamelCase when these characters match when the preceding character is lowercase - // ["Acl", "Api", "Ascii", "Cpu", "Css", "Dns", "Eof", "Guid", "Html", "Http", "Https", "Id", "Ip", "Json", "Qps", "Ram", "Rpc", "Sla", "Smtp", "Sql", "Ssh", "Tcp", "Tls", "Ttl", "Udp", "Ui", "Gid", "Uid", "Uuid", "Uri", "Url", "Utf8", "Vm", "Xml", "Xmpp", "Xsrf", "Xss", "Sip", "Rtp", "Amqp", "Db", "Ts"] - targetWordRegex := regexp.MustCompile(`(?i)(Acl|Api|Ascii|Cpu|Css|Dns|Eof|Guid|Html|Http|Https|Id|Ip|Json|Qps|Ram|Rpc|Sla|Smtp|Sql|Ssh|Tcp|Tls|Ttl|Udp|Ui|Gid|Uid|Uuid|Uri|Url|Utf8|Vm|Xml|Xmpp|Xsrf|Xss|Sip|Rtp|Amqp|Db|Ts)`) return targetWordRegex.ReplaceAllStringFunc(s, func(s string) string { // If the preceding character is lowercase, do not do CamelCase if unicode.IsLower(rune(s[0])) { @@ -623,6 +628,12 @@ func SwaggerUriToGorillaUri(uri string) string { // {?param} // {?param*} func SwaggerUriToStdHttpUri(uri string) string { + // https://pkg.go.dev/net/http#hdr-Patterns-ServeMux + // The special wildcard {$} matches only the end of the URL. For example, the pattern "/{$}" matches only the path "/", whereas the pattern "/" matches every path. + if uri == "/" { + return "/{$}" + } + return pathParamRE.ReplaceAllString(uri, "{$1}") } @@ -805,6 +816,8 @@ func typeNamePrefix(name string) (prefix string) { prefix += "Caret" case '%': prefix += "Percent" + case '_': + prefix += "Underscore" default: // Prepend "N" to schemas starting with a number if prefix == "" && unicode.IsDigit(r) { @@ -868,6 +881,8 @@ func DeprecationComment(reason string) string { content := "Deprecated:" // The colon is required at the end even without reason if reason != "" { content += fmt.Sprintf(" %s", reason) + } else { + content += " this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set" } return stringToGoCommentWithPrefix(content, "") @@ -1086,7 +1101,7 @@ func isAdditionalPropertiesExplicitFalse(s *openapi3.Schema) bool { return false } - return *s.AdditionalProperties.Has == false //nolint:gosimple + return *s.AdditionalProperties.Has == false //nolint:staticcheck } func sliceContains[E comparable](s []E, v E) bool { diff --git a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/util/loader.go b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/util/loader.go index b10e594..89830a8 100644 --- a/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/util/loader.go +++ b/vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/util/loader.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "net/url" + "path/filepath" "strings" "github.com/getkin/kin-openapi/openapi3" @@ -65,29 +66,34 @@ func LoadSwaggerWithOverlay(filePath string, opts LoadSwaggerWithOverlayOpts) (s err = overlay.Validate() if err != nil { - return nil, fmt.Errorf("The Overlay in %#v was not valid: %v", opts.Path, err) + return nil, fmt.Errorf("the Overlay in %#v was not valid: %v", opts.Path, err) } if opts.Strict { err, vs := overlay.ApplyToStrict(&node) if err != nil { - return nil, fmt.Errorf("Failed to apply Overlay %#v to specification %#v: %v\nAdditionally, the following validation errors were found:\n- %s", opts.Path, filePath, err, strings.Join(vs, "\n- ")) + return nil, fmt.Errorf("failed to apply Overlay %#v to specification %#v: %v\nAdditionally, the following validation errors were found:\n- %s", opts.Path, filePath, err, strings.Join(vs, "\n- ")) } } else { err = overlay.ApplyTo(&node) if err != nil { - return nil, fmt.Errorf("Failed to apply Overlay %#v to specification %#v: %v", opts.Path, filePath, err) + return nil, fmt.Errorf("failed to apply Overlay %#v to specification %#v: %v", opts.Path, filePath, err) } } b, err := yaml.Marshal(&node) if err != nil { - return nil, fmt.Errorf("Failed to serialize Overlay'd specification %#v: %v", opts.Path, err) + return nil, fmt.Errorf("failed to serialize Overlay'd specification %#v: %v", opts.Path, err) } - swagger, err = openapi3.NewLoader().LoadFromData(b) + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + + swagger, err = loader.LoadFromDataWithPath(b, &url.URL{ + Path: filepath.ToSlash(filePath), + }) if err != nil { - return nil, fmt.Errorf("Failed to serialize Overlay'd specification %#v: %v", opts.Path, err) + return nil, fmt.Errorf("failed to serialize Overlay'd specification %#v: %v", opts.Path, err) } return swagger, nil diff --git a/vendor/github.com/speakeasy-api/jsonpath/LICENSE b/vendor/github.com/speakeasy-api/jsonpath/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/vendor/github.com/speakeasy-api/jsonpath/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/config/config.go b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/config/config.go new file mode 100644 index 0000000..bd8286c --- /dev/null +++ b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/config/config.go @@ -0,0 +1,31 @@ +package config + +type Option func(*config) + +// WithPropertyNameExtension enables the use of the "~" character to access a property key. +// It is not enabled by default as this is outside of RFC 9535, but is important for several use-cases +func WithPropertyNameExtension() Option { + return func(cfg *config) { + cfg.propertyNameExtension = true + } +} + +type Config interface { + PropertyNameEnabled() bool +} + +type config struct { + propertyNameExtension bool +} + +func (c *config) PropertyNameEnabled() bool { + return c.propertyNameExtension +} + +func New(opts ...Option) Config { + cfg := &config{} + for _, opt := range opts { + opt(cfg) + } + return cfg +} diff --git a/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/filter.go b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/filter.go new file mode 100644 index 0000000..0f5b77f --- /dev/null +++ b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/filter.go @@ -0,0 +1,456 @@ +package jsonpath + +import ( + "gopkg.in/yaml.v3" + "strconv" + "strings" +) + +// filter-selector = "?" S logical-expr +type filterSelector struct { + // logical-expr = logical-or-expr + expression *logicalOrExpr +} + +func (s filterSelector) ToString() string { + return s.expression.ToString() +} + +// logical-or-expr = logical-and-expr *(S "||" S logical-and-expr) +type logicalOrExpr struct { + expressions []*logicalAndExpr +} + +func (e logicalOrExpr) ToString() string { + builder := strings.Builder{} + for i, expr := range e.expressions { + if i > 0 { + builder.WriteString(" || ") + } + builder.WriteString(expr.ToString()) + } + return builder.String() +} + +// logical-and-expr = basic-expr *(S "&&" S basic-expr) +type logicalAndExpr struct { + expressions []*basicExpr +} + +func (e logicalAndExpr) ToString() string { + builder := strings.Builder{} + for i, expr := range e.expressions { + if i > 0 { + builder.WriteString(" && ") + } + builder.WriteString(expr.ToString()) + } + return builder.String() +} + +// relQuery rel-query = current-node-identifier segments +// current-node-identifier = "@" +type relQuery struct { + segments []*segment +} + +func (q relQuery) ToString() string { + builder := strings.Builder{} + builder.WriteString("@") + for _, segment := range q.segments { + builder.WriteString(segment.ToString()) + } + return builder.String() +} + +// filterQuery filter-query = rel-query / jsonpath-query +type filterQuery struct { + relQuery *relQuery + jsonPathQuery *jsonPathAST +} + +func (q filterQuery) ToString() string { + if q.relQuery != nil { + return q.relQuery.ToString() + } else if q.jsonPathQuery != nil { + return q.jsonPathQuery.ToString() + } + return "" +} + +// functionArgument function-argument = literal / +// +// filter-query / ; (includes singular-query) +// logical-expr / +// function-expr +type functionArgument struct { + literal *literal + filterQuery *filterQuery + logicalExpr *logicalOrExpr + functionExpr *functionExpr +} + +type functionArgType int + +const ( + functionArgTypeLiteral functionArgType = iota + functionArgTypeNodes +) + +type resolvedArgument struct { + kind functionArgType + literal *literal + nodes []*literal +} + +func (a functionArgument) Eval(idx index, node *yaml.Node, root *yaml.Node) resolvedArgument { + if a.literal != nil { + return resolvedArgument{kind: functionArgTypeLiteral, literal: a.literal} + } else if a.filterQuery != nil { + result := a.filterQuery.Query(idx, node, root) + lits := make([]*literal, len(result)) + for i, node := range result { + lit := nodeToLiteral(node) + lits[i] = &lit + } + if len(result) != 1 { + return resolvedArgument{kind: functionArgTypeNodes, nodes: lits} + } else { + return resolvedArgument{kind: functionArgTypeLiteral, literal: lits[0]} + } + } else if a.logicalExpr != nil { + res := a.logicalExpr.Matches(idx, node, root) + return resolvedArgument{kind: functionArgTypeLiteral, literal: &literal{bool: &res}} + } else if a.functionExpr != nil { + res := a.functionExpr.Evaluate(idx, node, root) + return resolvedArgument{kind: functionArgTypeLiteral, literal: &res} + } + return resolvedArgument{} +} + +func (a functionArgument) ToString() string { + builder := strings.Builder{} + if a.literal != nil { + builder.WriteString(a.literal.ToString()) + } else if a.filterQuery != nil { + builder.WriteString(a.filterQuery.ToString()) + } else if a.logicalExpr != nil { + builder.WriteString(a.logicalExpr.ToString()) + } else if a.functionExpr != nil { + builder.WriteString(a.functionExpr.ToString()) + } + return builder.String() +} + +//function-name = function-name-first *function-name-char +//function-name-first = LCALPHA +//function-name-char = function-name-first / "_" / DIGIT +//LCALPHA = %x61-7A ; "a".."z" +// + +type functionType int + +const ( + functionTypeLength functionType = iota + functionTypeCount + functionTypeMatch + functionTypeSearch + functionTypeValue +) + +var functionTypeMap = map[string]functionType{ + "length": functionTypeLength, + "count": functionTypeCount, + "match": functionTypeMatch, + "search": functionTypeSearch, + "value": functionTypeValue, +} + +func (f functionType) String() string { + for k, v := range functionTypeMap { + if v == f { + return k + } + } + return "unknown" +} + +// functionExpr function-expr = function-name "(" S [function-argument +// *(S "," S function-argument)] S ")" +type functionExpr struct { + funcType functionType + args []*functionArgument +} + +func (e functionExpr) ToString() string { + builder := strings.Builder{} + builder.WriteString(e.funcType.String()) + builder.WriteString("(") + for i, arg := range e.args { + if i > 0 { + builder.WriteString(", ") + } + builder.WriteString(arg.ToString()) + } + builder.WriteString(")") + return builder.String() +} + +// testExpr test-expr = [logical-not-op S] +// +// (filter-query / ; existence/non-existence +// function-expr) ; LogicalType or NodesType +type testExpr struct { + not bool + filterQuery *filterQuery + functionExpr *functionExpr +} + +func (e testExpr) ToString() string { + builder := strings.Builder{} + if e.not { + builder.WriteString("!") + } + if e.filterQuery != nil { + builder.WriteString(e.filterQuery.ToString()) + } else if e.functionExpr != nil { + builder.WriteString(e.functionExpr.ToString()) + } + return builder.String() +} + +// basicExpr basic-expr = +// +// paren-expr / +// comparison-expr / +// test-expr +type basicExpr struct { + parenExpr *parenExpr + comparisonExpr *comparisonExpr + testExpr *testExpr +} + +func (e basicExpr) ToString() string { + if e.parenExpr != nil { + return e.parenExpr.ToString() + } else if e.comparisonExpr != nil { + return e.comparisonExpr.ToString() + } else if e.testExpr != nil { + return e.testExpr.ToString() + } + return "" +} + +// literal literal = number / +// . string-literal / +// . true / false / null +type literal struct { + // we generally decompose these into their component parts for easier evaluation + integer *int + float64 *float64 + string *string + bool *bool + null *bool + node *yaml.Node +} + +func (l literal) ToString() string { + if l.integer != nil { + return strconv.Itoa(*l.integer) + } else if l.float64 != nil { + return strconv.FormatFloat(*l.float64, 'f', -1, 64) + } else if l.string != nil { + builder := strings.Builder{} + builder.WriteString("'") + builder.WriteString(escapeString(*l.string)) + builder.WriteString("'") + return builder.String() + } else if l.bool != nil { + if *l.bool { + return "true" + } else { + return "false" + } + } else if l.null != nil { + if *l.null { + return "null" + } else { + return "null" + } + } else if l.node != nil { + switch l.node.Kind { + case yaml.ScalarNode: + return l.node.Value + case yaml.SequenceNode: + builder := strings.Builder{} + builder.WriteString("[") + for i, child := range l.node.Content { + if i > 0 { + builder.WriteString(",") + } + builder.WriteString(literal{node: child}.ToString()) + } + builder.WriteString("]") + return builder.String() + case yaml.MappingNode: + builder := strings.Builder{} + builder.WriteString("{") + for i, child := range l.node.Content { + if i > 0 { + builder.WriteString(",") + } + builder.WriteString(literal{node: child}.ToString()) + } + builder.WriteString("}") + return builder.String() + } + } + return "" +} + +func escapeString(value string) string { + b := strings.Builder{} + for i := 0; i < len(value); i++ { + if value[i] == '\n' { + b.WriteString("\\\\n") + } else if value[i] == '\\' { + b.WriteString("\\\\") + } else if value[i] == '\'' { + b.WriteString("\\'") + } else { + b.WriteByte(value[i]) + } + } + return b.String() +} + +type absQuery jsonPathAST + +func (q absQuery) ToString() string { + builder := strings.Builder{} + builder.WriteString("$") + for _, segment := range q.segments { + builder.WriteString(segment.ToString()) + } + return builder.String() +} + +// singularQuery singular-query = rel-singular-query / abs-singular-query +type singularQuery struct { + relQuery *relQuery + absQuery *absQuery +} + +func (q singularQuery) ToString() string { + if q.relQuery != nil { + return q.relQuery.ToString() + } else if q.absQuery != nil { + return q.absQuery.ToString() + } + return "" +} + +// comparable +// +// comparable = literal / +// singular-query / ; singular query value +// function-expr ; ValueType +type comparable struct { + literal *literal + singularQuery *singularQuery + functionExpr *functionExpr +} + +func (c comparable) ToString() string { + if c.literal != nil { + return c.literal.ToString() + } else if c.singularQuery != nil { + return c.singularQuery.ToString() + } else if c.functionExpr != nil { + return c.functionExpr.ToString() + } + return "" +} + +// comparisonExpr represents a comparison expression +// +// comparison-expr = comparable S comparison-op S comparable +// literal = number / string-literal / +// true / false / null +// comparable = literal / +// singular-query / ; singular query value +// function-expr ; ValueType +// comparison-op = "==" / "!=" / +// "<=" / ">=" / +// "<" / ">" +type comparisonExpr struct { + left *comparable + op comparisonOperator + right *comparable +} + +func (e comparisonExpr) ToString() string { + builder := strings.Builder{} + builder.WriteString(e.left.ToString()) + builder.WriteString(" ") + builder.WriteString(e.op.ToString()) + builder.WriteString(" ") + builder.WriteString(e.right.ToString()) + return builder.String() +} + +// existExpr represents an existence expression +type existExpr struct { + query string +} + +// parenExpr represents a parenthesized expression +// +// paren-expr = [logical-not-op S] "(" S logical-expr S ")" +type parenExpr struct { + // "!" + not bool + // "(" logicalOrExpr ")" + expr *logicalOrExpr +} + +func (e parenExpr) ToString() string { + builder := strings.Builder{} + if e.not { + builder.WriteString("!") + } + builder.WriteString("(") + builder.WriteString(e.expr.ToString()) + builder.WriteString(")") + return builder.String() +} + +// comparisonOperator represents a comparison operator +type comparisonOperator int + +const ( + equalTo comparisonOperator = iota + notEqualTo + lessThan + lessThanEqualTo + greaterThan + greaterThanEqualTo +) + +func (o comparisonOperator) ToString() string { + switch o { + case equalTo: + return "==" + case notEqualTo: + return "!=" + case lessThan: + return "<" + case lessThanEqualTo: + return "<=" + case greaterThan: + return ">" + case greaterThanEqualTo: + return ">=" + } + return "" +} diff --git a/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/jsonpath.go b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/jsonpath.go new file mode 100644 index 0000000..d5c8a5b --- /dev/null +++ b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/jsonpath.go @@ -0,0 +1,35 @@ +package jsonpath + +import ( + "fmt" + "github.com/speakeasy-api/jsonpath/pkg/jsonpath/config" + "github.com/speakeasy-api/jsonpath/pkg/jsonpath/token" + "gopkg.in/yaml.v3" +) + +func NewPath(input string, opts ...config.Option) (*JSONPath, error) { + tokenizer := token.NewTokenizer(input, opts...) + tokens := tokenizer.Tokenize() + for i := 0; i < len(tokens); i++ { + if tokens[i].Token == token.ILLEGAL { + return nil, fmt.Errorf(tokenizer.ErrorString(&tokens[i], "unexpected token")) + } + } + parser := newParserPrivate(tokenizer, tokens, opts...) + err := parser.parse() + if err != nil { + return nil, err + } + return parser, nil +} + +func (p *JSONPath) Query(root *yaml.Node) []*yaml.Node { + return p.ast.Query(root, root) +} + +func (p *JSONPath) String() string { + if p == nil { + return "" + } + return p.ast.ToString() +} diff --git a/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/parser.go b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/parser.go new file mode 100644 index 0000000..ae6e7f1 --- /dev/null +++ b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/parser.go @@ -0,0 +1,729 @@ +package jsonpath + +import ( + "errors" + "fmt" + "github.com/speakeasy-api/jsonpath/pkg/jsonpath/config" + "github.com/speakeasy-api/jsonpath/pkg/jsonpath/token" + "strconv" + "strings" +) + +const MaxSafeFloat int64 = 9007199254740991 + +type mode int + +const ( + modeNormal mode = iota + modeSingular +) + +// JSONPath represents a JSONPath parser. +type JSONPath struct { + tokenizer *token.Tokenizer + tokens []token.TokenInfo + ast jsonPathAST + current int + mode []mode + config config.Config +} + +// newParserPrivate creates a new JSONPath with the given tokens. +func newParserPrivate(tokenizer *token.Tokenizer, tokens []token.TokenInfo, opts ...config.Option) *JSONPath { + return &JSONPath{tokenizer, tokens, jsonPathAST{}, 0, []mode{modeNormal}, config.New(opts...)} +} + +// parse parses the JSONPath tokens and returns the root node of the AST. +// +// jsonpath-query = root-identifier segments +func (p *JSONPath) parse() error { + if len(p.tokens) == 0 { + return fmt.Errorf("empty JSONPath expression") + } + + if p.tokens[p.current].Token != token.ROOT { + return p.parseFailure(&p.tokens[p.current], "expected '$'") + } + p.current++ + + for p.current < len(p.tokens) { + segment, err := p.parseSegment() + if err != nil { + return err + } + p.ast.segments = append(p.ast.segments, segment) + } + return nil +} + +func (p *JSONPath) parseFailure(target *token.TokenInfo, msg string) error { + return errors.New(p.tokenizer.ErrorString(target, msg)) +} + +// peek returns true if the upcoming token matches the given token type. +func (p *JSONPath) peek(token token.Token) bool { + return p.current+1 < len(p.tokens) && p.tokens[p.current+1].Token == token +} + +// peek returns true if the upcoming token matches the given token type. +func (p *JSONPath) next(token token.Token) bool { + return p.current < len(p.tokens) && p.tokens[p.current].Token == token +} + +// expect consumes the current token if it matches the given token type. +func (p *JSONPath) expect(token token.Token) bool { + if p.peek(token) { + p.current++ + return true + } + return false +} + +// isComparisonOperator returns true if the given token is a comparison operator. +func (p *JSONPath) isComparisonOperator(tok token.Token) bool { + return tok == token.EQ || tok == token.NE || tok == token.GT || tok == token.GE || tok == token.LT || tok == token.LE +} + +func (p *JSONPath) parseSegment() (*segment, error) { + currentToken := p.tokens[p.current] + if currentToken.Token == token.RECURSIVE { + if p.mode[len(p.mode)-1] == modeSingular { + return nil, p.parseFailure(&p.tokens[p.current], "unexpected recursive descent in singular query") + } + p.current++ + child, err := p.parseInnerSegment() + if err != nil { + return nil, err + } + return &segment{kind: segmentKindDescendant, descendant: child}, nil + } else if currentToken.Token == token.CHILD || currentToken.Token == token.BRACKET_LEFT { + if currentToken.Token == token.CHILD { + p.current++ + } + child, err := p.parseInnerSegment() + if err != nil { + return nil, err + } + return &segment{kind: segmentKindChild, child: child}, nil + } else if p.config.PropertyNameEnabled() && currentToken.Token == token.PROPERTY_NAME { + p.current++ + return &segment{kind: segmentKindProperyName}, nil + } + return nil, p.parseFailure(¤tToken, "unexpected token when parsing segment") +} + +func (p *JSONPath) parseInnerSegment() (retValue *innerSegment, err error) { + defer func() { + if p.mode[len(p.mode)-1] == modeSingular && retValue != nil { + if len(retValue.selectors) > 1 { + retValue = nil + err = p.parseFailure(&p.tokens[p.current], "unexpected multiple selectors in singular query") + return + } else if retValue.kind == segmentDotWildcard { + retValue = nil + err = p.parseFailure(&p.tokens[p.current], "unexpected wildcard in singular query") + return + } + } + }() + // .* + // .STRING + // [] + if p.current >= len(p.tokens) { + return nil, p.parseFailure(nil, "unexpected end of input") + } + firstToken := p.tokens[p.current] + if firstToken.Token == token.WILDCARD { + p.current += 1 + return &innerSegment{segmentDotWildcard, "", nil}, nil + } else if firstToken.Token == token.STRING { + dotName := p.tokens[p.current].Literal + p.current += 1 + return &innerSegment{segmentDotMemberName, dotName, nil}, nil + } else if firstToken.Token == token.BRACKET_LEFT { + prior := p.current + p.current += 1 + selectors := []*selector{} + for p.current < len(p.tokens) { + innerSelector, err := p.parseSelector() + if err != nil { + p.current = prior + return nil, err + } + selectors = append(selectors, innerSelector) + if len(p.tokens) <= p.current { + return nil, p.parseFailure(&p.tokens[p.current-1], "unexpected end of input") + } + if p.tokens[p.current].Token == token.BRACKET_RIGHT { + break + } else if p.tokens[p.current].Token == token.COMMA { + p.current++ + } + } + if p.tokens[p.current].Token != token.BRACKET_RIGHT { + prior = p.current + return nil, p.parseFailure(&p.tokens[p.current], "expected ']'") + } + p.current += 1 + return &innerSegment{kind: segmentLongHand, dotName: "", selectors: selectors}, nil + } + return nil, p.parseFailure(&firstToken, "unexpected token when parsing inner segment") +} + +func (p *JSONPath) parseSelector() (retSelector *selector, err error) { + //selector = name-selector / + // wildcard-selector / + // slice-selector / + // index-selector / + // filter-selector + initial := p.current + defer func() { + if p.mode[len(p.mode)-1] == modeSingular && retSelector != nil { + if retSelector.kind == selectorSubKindWildcard { + err = p.parseFailure(&p.tokens[initial], "unexpected wildcard in singular query") + retSelector = nil + } else if retSelector.kind == selectorSubKindArraySlice { + err = p.parseFailure(&p.tokens[initial], "unexpected slice in singular query") + retSelector = nil + } + } + }() + + // name-selector = string-literal + if p.tokens[p.current].Token == token.STRING_LITERAL { + name := p.tokens[p.current].Literal + p.current++ + return &selector{kind: selectorSubKindName, name: name}, nil + // wildcard-selector = "*" + } else if p.tokens[p.current].Token == token.WILDCARD { + p.current++ + return &selector{kind: selectorSubKindWildcard}, nil + } else if p.tokens[p.current].Token == token.INTEGER { + // peek ahead to see if it's a slice + if p.peek(token.ARRAY_SLICE) { + slice, err := p.parseSliceSelector() + if err != nil { + return nil, err + } + return &selector{kind: selectorSubKindArraySlice, slice: slice}, nil + } + // peek ahead to see if we close the array index properly + if !p.peek(token.BRACKET_RIGHT) && !p.peek(token.COMMA) { + return nil, p.parseFailure(&p.tokens[p.current], "expected ']' or ','") + } + // else it's an index + lit := p.tokens[p.current].Literal + // make sure it's not -0 + if lit == "-0" { + return nil, p.parseFailure(&p.tokens[p.current], "-0 unexpected") + } + // make sure lit is an integer + i, err := strconv.ParseInt(lit, 10, 64) + if err != nil { + return nil, p.parseFailure(&p.tokens[p.current], "expected an integer") + } + err = p.checkSafeInteger(i, lit) + if err != nil { + return nil, err + } + + p.current++ + + return &selector{kind: selectorSubKindArrayIndex, index: i}, nil + } else if p.tokens[p.current].Token == token.ARRAY_SLICE { + slice, err := p.parseSliceSelector() + if err != nil { + return nil, err + } + return &selector{kind: selectorSubKindArraySlice, slice: slice}, nil + } else if p.tokens[p.current].Token == token.FILTER { + return p.parseFilterSelector() + } + + return nil, p.parseFailure(&p.tokens[p.current], "unexpected token when parsing selector") +} + +func (p *JSONPath) parseSliceSelector() (*slice, error) { + // slice-selector = [start S] ":" S [end S] [":" [S step]] + var start, end, step *int64 + + // parse the start index + if p.tokens[p.current].Token == token.INTEGER { + literal := p.tokens[p.current].Literal + i, err := strconv.ParseInt(literal, 10, 64) + if err != nil { + return nil, p.parseFailure(&p.tokens[p.current], "expected an integer") + } + err = p.checkSafeInteger(i, literal) + if err != nil { + return nil, err + } + + start = &i + p.current += 1 + } + + // Expect a colon + if p.tokens[p.current].Token != token.ARRAY_SLICE { + return nil, p.parseFailure(&p.tokens[p.current], "expected ':'") + } + p.current++ + + // parse the end index + if p.tokens[p.current].Token == token.INTEGER { + literal := p.tokens[p.current].Literal + i, err := strconv.ParseInt(literal, 10, 64) + if err != nil { + return nil, p.parseFailure(&p.tokens[p.current], "expected an integer") + } + err = p.checkSafeInteger(i, literal) + if err != nil { + return nil, err + } + + end = &i + p.current++ + } + + // Check for an optional second colon and step value + if p.tokens[p.current].Token == token.ARRAY_SLICE { + p.current++ + if p.tokens[p.current].Token == token.INTEGER { + literal := p.tokens[p.current].Literal + i, err := strconv.ParseInt(literal, 10, 64) + if err != nil { + return nil, p.parseFailure(&p.tokens[p.current], "expected an integer") + } + err = p.checkSafeInteger(i, literal) + if err != nil { + return nil, err + } + + step = &i + p.current++ + } + } + if p.tokens[p.current].Token != token.BRACKET_RIGHT { + return nil, p.parseFailure(&p.tokens[p.current], "expected ']'") + } + + return &slice{start: start, end: end, step: step}, nil +} + +func (p *JSONPath) checkSafeInteger(i int64, literal string) error { + if i > MaxSafeFloat || i < -MaxSafeFloat { + return p.parseFailure(&p.tokens[p.current], "outside bounds for safe integers") + } + if literal == "-0" { + return p.parseFailure(&p.tokens[p.current], "-0 unexpected") + } + return nil +} + +func (p *JSONPath) parseFilterSelector() (*selector, error) { + + if p.tokens[p.current].Token != token.FILTER { + return nil, p.parseFailure(&p.tokens[p.current], "expected '?'") + } + p.current++ + + expr, err := p.parseLogicalOrExpr() + if err != nil { + return nil, err + } + + return &selector{kind: selectorSubKindFilter, filter: &filterSelector{expr}}, nil +} + +func (p *JSONPath) parseLogicalOrExpr() (*logicalOrExpr, error) { + var expr logicalOrExpr + + for { + andExpr, err := p.parseLogicalAndExpr() + if err != nil { + return nil, err + } + expr.expressions = append(expr.expressions, andExpr) + + if !p.next(token.OR) { + break + } + p.current++ + } + + return &expr, nil +} + +func (p *JSONPath) parseLogicalAndExpr() (*logicalAndExpr, error) { + var expr logicalAndExpr + + for { + basicExpr, err := p.parseBasicExpr() + if err != nil { + return nil, err + } + expr.expressions = append(expr.expressions, basicExpr) + + if !p.next(token.AND) { + break + } + p.current++ + } + + return &expr, nil +} + +func (p *JSONPath) parseBasicExpr() (*basicExpr, error) { + //basic-expr = paren-expr / + // comparison-expr / + // test-expr + + switch p.tokens[p.current].Token { + case token.NOT: + p.current++ + expr, err := p.parseLogicalOrExpr() + if err != nil { + return nil, err + } + // Inspect if the expr is topped by a parenExpr -- if so we can simplify + if len(expr.expressions) == 1 && len(expr.expressions[0].expressions) == 1 && expr.expressions[0].expressions[0].parenExpr != nil { + child := expr.expressions[0].expressions[0].parenExpr + child.not = !child.not + return &basicExpr{parenExpr: child}, nil + } + return &basicExpr{parenExpr: &parenExpr{not: true, expr: expr}}, nil + case token.PAREN_LEFT: + p.current++ + expr, err := p.parseLogicalOrExpr() + if err != nil { + return nil, err + } + if p.tokens[p.current].Token != token.PAREN_RIGHT { + return nil, p.parseFailure(&p.tokens[p.current], "expected ')'") + } + p.current++ + return &basicExpr{parenExpr: &parenExpr{not: false, expr: expr}}, nil + } + prevCurrent := p.current + comparisonExpr, comparisonErr := p.parseComparisonExpr() + if comparisonErr == nil { + return &basicExpr{comparisonExpr: comparisonExpr}, nil + } + p.current = prevCurrent + testExpr, testErr := p.parseTestExpr() + if testErr == nil { + return &basicExpr{testExpr: testExpr}, nil + } + p.current = prevCurrent + return nil, p.parseFailure(&p.tokens[p.current], fmt.Sprintf("could not parse query: expected either testExpr [err: %s] or comparisonExpr: [err: %s]", testErr.Error(), comparisonErr.Error())) +} + +func (p *JSONPath) parseComparisonExpr() (*comparisonExpr, error) { + left, err := p.parseComparable() + if err != nil { + return nil, err + } + + if !p.isComparisonOperator(p.tokens[p.current].Token) { + return nil, p.parseFailure(&p.tokens[p.current], "expected comparison operator") + } + operator := p.tokens[p.current].Token + var op comparisonOperator + switch operator { + case token.EQ: + op = equalTo + case token.NE: + op = notEqualTo + case token.LT: + op = lessThan + case token.LE: + op = lessThanEqualTo + case token.GT: + op = greaterThan + case token.GE: + op = greaterThanEqualTo + default: + return nil, p.parseFailure(&p.tokens[p.current], "expected comparison operator") + } + p.current++ + + right, err := p.parseComparable() + if err != nil { + return nil, err + } + + return &comparisonExpr{left: left, op: op, right: right}, nil +} + +func (p *JSONPath) parseComparable() (*comparable, error) { + // comparable = literal / + // singular-query / ; singular query value + // function-expr ; ValueType + if literal, err := p.parseLiteral(); err == nil { + return &comparable{literal: literal}, nil + } + if funcExpr, err := p.parseFunctionExpr(); err == nil { + if funcExpr.funcType == functionTypeMatch { + return nil, p.parseFailure(&p.tokens[p.current], "match result cannot be compared") + } else if funcExpr.funcType == functionTypeSearch { + return nil, p.parseFailure(&p.tokens[p.current], "search result cannot be compared") + } + return &comparable{functionExpr: funcExpr}, nil + } + switch p.tokens[p.current].Token { + case token.ROOT: + p.current++ + query, err := p.parseSingleQuery() + if err != nil { + return nil, err + } + return &comparable{singularQuery: &singularQuery{absQuery: &absQuery{segments: query.segments}}}, nil + case token.CURRENT: + p.current++ + query, err := p.parseSingleQuery() + if err != nil { + return nil, err + } + return &comparable{singularQuery: &singularQuery{relQuery: &relQuery{segments: query.segments}}}, nil + default: + return nil, p.parseFailure(&p.tokens[p.current], "expected literal or query") + } +} + +func (p *JSONPath) parseQuery() (*jsonPathAST, error) { + var query jsonPathAST + p.mode = append(p.mode, modeNormal) + + for p.current < len(p.tokens) { + prior := p.current + segment, err := p.parseSegment() + if err != nil { + p.current = prior + break + } + query.segments = append(query.segments, segment) + } + p.mode = p.mode[:len(p.mode)-1] + return &query, nil +} + +func (p *JSONPath) parseTestExpr() (*testExpr, error) { + //test-expr = [logical-not-op S] + // (filter-query / ; existence/non-existence + // function-expr) ; LogicalType or NodesType + //filter-query = rel-query / jsonpath-query + //rel-query = current-node-identifier segments + //current-node-identifier = "@" + not := false + if p.tokens[p.current].Token == token.NOT { + not = true + p.current++ + } + switch p.tokens[p.current].Token { + case token.CURRENT: + p.current++ + query, err := p.parseQuery() + if err != nil { + return nil, err + } + return &testExpr{filterQuery: &filterQuery{relQuery: &relQuery{segments: query.segments}}, not: not}, nil + case token.ROOT: + p.current++ + query, err := p.parseQuery() + if err != nil { + return nil, err + } + return &testExpr{filterQuery: &filterQuery{jsonPathQuery: &jsonPathAST{segments: query.segments}}, not: not}, nil + default: + funcExpr, err := p.parseFunctionExpr() + if err != nil { + return nil, err + } + if funcExpr.funcType == functionTypeCount { + return nil, p.parseFailure(&p.tokens[p.current], "count function must be compared") + } + if funcExpr.funcType == functionTypeLength { + return nil, p.parseFailure(&p.tokens[p.current], "length function must be compared") + } + if funcExpr.funcType == functionTypeValue { + return nil, p.parseFailure(&p.tokens[p.current], "length function must be compared") + } + return &testExpr{functionExpr: funcExpr, not: not}, nil + } + + return nil, p.parseFailure(&p.tokens[p.current], "unexpected token when parsing test expression") +} + +func (p *JSONPath) parseFunctionExpr() (*functionExpr, error) { + functionName := p.tokens[p.current].Literal + if p.current+1 >= len(p.tokens) || p.tokens[p.current+1].Token != token.PAREN_LEFT { + return nil, p.parseFailure(&p.tokens[p.current], "expected '(' after function") + } + p.current += 2 + args := []*functionArgument{} + switch functionTypeMap[functionName] { + case functionTypeLength: + arg, err := p.parseFunctionArgument(true) + if err != nil { + return nil, err + } + args = append(args, arg) + case functionTypeCount: + arg, err := p.parseFunctionArgument(false) + if err != nil { + return nil, err + } + if arg.literal != nil && arg.literal.node == nil { + return nil, p.parseFailure(&p.tokens[p.current], "count function only supports containers") + } + args = append(args, arg) + case functionTypeValue: + arg, err := p.parseFunctionArgument(false) + if err != nil { + return nil, err + } + args = append(args, arg) + case functionTypeMatch: + fallthrough + case functionTypeSearch: + arg, err := p.parseFunctionArgument(false) + if err != nil { + return nil, err + } + args = append(args, arg) + if p.tokens[p.current].Token != token.COMMA { + return nil, p.parseFailure(&p.tokens[p.current], "expected ','") + } + p.current++ + arg, err = p.parseFunctionArgument(false) + if err != nil { + return nil, err + } + args = append(args, arg) + } + if p.tokens[p.current].Token != token.PAREN_RIGHT { + return nil, p.parseFailure(&p.tokens[p.current], "expected ')'") + } + p.current++ + return &functionExpr{funcType: functionTypeMap[functionName], args: args}, nil +} + +func (p *JSONPath) parseSingleQuery() (*jsonPathAST, error) { + var query jsonPathAST + for p.current < len(p.tokens) { + try := p.current + p.mode = append(p.mode, modeSingular) + segment, err := p.parseSegment() + if err != nil { + // rollback + p.mode = p.mode[:len(p.mode)-1] + p.current = try + break + } + p.mode = p.mode[:len(p.mode)-1] + query.segments = append(query.segments, segment) + } + //if len(query.segments) == 0 { + // return nil, p.parseFailure(p.tokens[p.current], "expected at least one segment") + //} + return &query, nil +} + +func (p *JSONPath) parseFunctionArgument(single bool) (*functionArgument, error) { + //function-argument = literal / + // filter-query / ; (includes singular-query) + // logical-expr / + // function-expr + + if lit, err := p.parseLiteral(); err == nil { + return &functionArgument{literal: lit}, nil + } + switch p.tokens[p.current].Token { + case token.CURRENT: + p.current++ + var query *jsonPathAST + var err error + if single { + query, err = p.parseSingleQuery() + } else { + query, err = p.parseQuery() + } + if err != nil { + return nil, err + } + return &functionArgument{filterQuery: &filterQuery{relQuery: &relQuery{segments: query.segments}}}, nil + case token.ROOT: + p.current++ + var query *jsonPathAST + var err error + if single { + query, err = p.parseSingleQuery() + } else { + query, err = p.parseQuery() + } + if err != nil { + return nil, err + } + return &functionArgument{filterQuery: &filterQuery{jsonPathQuery: &jsonPathAST{segments: query.segments}}}, nil + } + if expr, err := p.parseLogicalOrExpr(); err == nil { + return &functionArgument{logicalExpr: expr}, nil + } + if funcExpr, err := p.parseFunctionExpr(); err == nil { + return &functionArgument{functionExpr: funcExpr}, nil + } + + return nil, p.parseFailure(&p.tokens[p.current], "unexpected token for function argument") +} + +func (p *JSONPath) parseLiteral() (*literal, error) { + switch p.tokens[p.current].Token { + case token.STRING_LITERAL: + lit := p.tokens[p.current].Literal + p.current++ + return &literal{string: &lit}, nil + case token.INTEGER: + lit := p.tokens[p.current].Literal + p.current++ + i, err := strconv.Atoi(lit) + if err != nil { + return nil, p.parseFailure(&p.tokens[p.current], "expected integer") + } + return &literal{integer: &i}, nil + case token.FLOAT: + lit := p.tokens[p.current].Literal + p.current++ + f, err := strconv.ParseFloat(lit, 64) + if err != nil { + return nil, p.parseFailure(&p.tokens[p.current], "expected float") + } + return &literal{float64: &f}, nil + case token.TRUE: + p.current++ + res := true + return &literal{bool: &res}, nil + case token.FALSE: + p.current++ + res := false + return &literal{bool: &res}, nil + case token.NULL: + p.current++ + res := true + return &literal{null: &res}, nil + } + return nil, p.parseFailure(&p.tokens[p.current], "expected literal") +} + +type jsonPathAST struct { + // "$" + segments []*segment +} + +func (q jsonPathAST) ToString() string { + b := strings.Builder{} + b.WriteString("$") + for _, seg := range q.segments { + b.WriteString(seg.ToString()) + } + return b.String() +} diff --git a/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/segment.go b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/segment.go new file mode 100644 index 0000000..cb6233f --- /dev/null +++ b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/segment.go @@ -0,0 +1,83 @@ +package jsonpath + +import ( + "gopkg.in/yaml.v3" + "strings" +) + +type segmentKind int + +const ( + segmentKindChild segmentKind = iota // . + segmentKindDescendant // .. + segmentKindProperyName // ~ (extension only) +) + +type segment struct { + kind segmentKind + child *innerSegment + descendant *innerSegment +} + +type segmentSubKind int + +const ( + segmentDotWildcard segmentSubKind = iota // .* + segmentDotMemberName // .property + segmentLongHand // [ selector[] ] +) + +func (s segment) ToString() string { + switch s.kind { + case segmentKindChild: + if s.child.kind != segmentLongHand { + return "." + s.child.ToString() + } else { + return s.child.ToString() + } + case segmentKindDescendant: + return ".." + s.descendant.ToString() + case segmentKindProperyName: + return "~" + } + panic("unknown segment kind") +} + +type innerSegment struct { + kind segmentSubKind + dotName string + selectors []*selector +} + +func (s innerSegment) ToString() string { + builder := strings.Builder{} + switch s.kind { + case segmentDotWildcard: + builder.WriteString("*") + break + case segmentDotMemberName: + builder.WriteString(s.dotName) + break + case segmentLongHand: + builder.WriteString("[") + for i, selector := range s.selectors { + builder.WriteString(selector.ToString()) + if i < len(s.selectors)-1 { + builder.WriteString(", ") + } + } + builder.WriteString("]") + break + default: + panic("unknown child segment kind") + } + return builder.String() +} + +func descend(value *yaml.Node, root *yaml.Node) []*yaml.Node { + result := []*yaml.Node{value} + for _, child := range value.Content { + result = append(result, descend(child, root)...) + } + return result +} diff --git a/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/selector.go b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/selector.go new file mode 100644 index 0000000..f71cb47 --- /dev/null +++ b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/selector.go @@ -0,0 +1,63 @@ +package jsonpath + +import ( + "fmt" + "strconv" + "strings" +) + +type selectorSubKind int + +const ( + selectorSubKindWildcard selectorSubKind = iota + selectorSubKindName + selectorSubKindArraySlice + selectorSubKindArrayIndex + selectorSubKindFilter +) + +type slice struct { + start *int64 + end *int64 + step *int64 +} + +type selector struct { + kind selectorSubKind + name string + index int64 + slice *slice + filter *filterSelector +} + +func (s selector) ToString() string { + switch s.kind { + case selectorSubKindName: + return "'" + escapeString(s.name) + "'" + case selectorSubKindArrayIndex: + // int to string + return strconv.FormatInt(s.index, 10) + case selectorSubKindFilter: + return "?" + s.filter.ToString() + case selectorSubKindWildcard: + return "*" + case selectorSubKindArraySlice: + builder := strings.Builder{} + if s.slice.start != nil { + builder.WriteString(strconv.FormatInt(*s.slice.start, 10)) + } + builder.WriteString(":") + if s.slice.end != nil { + builder.WriteString(strconv.FormatInt(*s.slice.end, 10)) + } + + if s.slice.step != nil { + builder.WriteString(":") + builder.WriteString(strconv.FormatInt(*s.slice.step, 10)) + } + return builder.String() + default: + panic(fmt.Sprintf("unimplemented selector kind: %v", s.kind)) + } + return "" +} diff --git a/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/token/token.go b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/token/token.go new file mode 100644 index 0000000..6cf1654 --- /dev/null +++ b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/token/token.go @@ -0,0 +1,776 @@ +package token + +import ( + "fmt" + "github.com/speakeasy-api/jsonpath/pkg/jsonpath/config" + "strconv" + "strings" +) + +// ***************************************************************************** +// The Tokenizer is responsible for tokenizing the jsonpath expression. This means +// * removing whitespace +// * scanning strings +// * and detecting illegal characters +// ***************************************************************************** + +// Token represents a lexical token in a JSONPath expression. +type Token int + +// We are allowed the following tokens + +//jsonpath-query = root-identifier segments +//segments = *(segment) +//root-identifier = "$" +//selector = name-selector / +// wildcard-selector / +// slice-selector / +// index-selector / +// filter-selector +//name-selector = string-literal +//wildcard-selector = "*" +//index-selector = int ; decimal integer +// +//int = "0" / +// (["-"] DIGIT1 *DIGIT) ; - optional +//DIGIT1 = %x31-39 ; 1-9 non-zero digit +//slice-selector = [start] ":" [end] [":" [step]] +// +//start = int ; included in selection +//end = int ; not included in selection +//step = int ; default: 1 +//filter-selector = "?" logical-expr +//logical-expr = logical-or-expr +//logical-or-expr = logical-and-expr *("||" logical-and-expr) +// ; disjunction +// ; binds less tightly than conjunction +//logical-and-expr = basic-expr *("&&" basic-expr) +// ; conjunction +// ; binds more tightly than disjunction +// +//basic-expr = paren-expr / +// comparison-expr / +// test-expr +// +//paren-expr = [logical-not-op] "(" logical-expr ")" +// ; parenthesized expression +//logical-not-op = "!" ; logical NOT operator +//test-expr = [logical-not-op S] +// (filter-query / ; existence/non-existence +// function-expr) ; LogicalType or NodesType +//filter-query = rel-query / jsonpath-query +//rel-query = current-node-identifier segments +//current-node-identifier = "@" +//comparison-expr = comparable comparison-op comparable +//literal = number / string-literal / +// true / false / null +//comparable = literal / +// singular-query / ; singular query value +// function-expr ; ValueType +//comparison-op = "==" / "!=" / +// "<=" / ">=" / +// "<" / ">" +// +//singular-query = rel-singular-query / abs-singular-query +//rel-singular-query = current-node-identifier singular-query-segments +//abs-singular-query = root-identifier singular-query-segments +//singular-query-segments = *(S (name-segment / index-segment)) +//name-segment = ("[" name-selector "]") / +// ("." member-name-shorthand) +//index-segment = "[" index-selector "]" +//number = (int / "-0") [ frac ] [ exp ] ; decimal number +//frac = "." 1*DIGIT ; decimal fraction +//exp = "e" [ "-" / "+" ] 1*DIGIT ; decimal exponent +//true = %x74.72.75.65 ; true +//false = %x66.61.6c.73.65 ; false +//null = %x6e.75.6c.6c ; null +//function-name = function-name-first *function-name-char +//function-name-first = LCALPHA +//function-name-char = function-name-first / "_" / DIGIT +//LCALPHA = %x61-7A ; "a".."z" +// +//function-expr = function-name "(" [function-argument +// *(S "," function-argument)] ")" +//function-argument = literal / +// filter-query / ; (includes singular-query) +// logical-expr / +// function-expr +//segment = child-segment / descendant-segment +//child-segment = bracketed-selection / +// ("." +// (wildcard-selector / +// member-name-shorthand)) +// +//bracketed-selection = "[" selector *(S "," selector) "]" +// +//member-name-shorthand = name-first *name-char +//name-first = ALPHA / +// "_" / +// %x80-D7FF / +// ; skip surrogate code points +// %xE000-10FFFF +//name-char = name-first / DIGIT +// +//DIGIT = %x30-39 ; 0-9 +//ALPHA = %x41-5A / %x61-7A ; A-Z / a-z +//descendant-segment = ".." (bracketed-selection / +// wildcard-selector / +// member-name-shorthand) +// +// Figure 2: Collected ABNF of JSONPath Queries +// +//Figure 3 contains the collected ABNF grammar that defines the syntax +//of a JSONPath Normalized Path while also using the rules root- +//identifier, ESC, DIGIT, and DIGIT1 from Figure 2. +// +//normalized-path = root-identifier *(normal-index-segment) +//normal-index-segment = "[" normal-selector "]" +//normal-selector = normal-name-selector / normal-index-selector +//normal-name-selector = %x27 *normal-single-quoted %x27 ; 'string' +//normal-single-quoted = normal-unescaped / +// ESC normal-escapable +//normal-unescaped = ; omit %x0-1F control codes +// %x20-26 / +// ; omit 0x27 ' +// %x28-5B / +// ; omit 0x5C \ +// %x5D-D7FF / +// ; skip surrogate code points +// %xE000-10FFFF +// +//normal-escapable = %x62 / ; b BS backspace U+0008 +// %x66 / ; f FF form feed U+000C +// %x6E / ; n LF line feed U+000A +// %x72 / ; r CR carriage return U+000D +// %x74 / ; t HT horizontal tab U+0009 +// "'" / ; ' apostrophe U+0027 +// "\" / ; \ backslash (reverse solidus) U+005C +// (%x75 normal-hexchar) +// ; certain values u00xx U+00XX +//normal-hexchar = "0" "0" +// ( +// ("0" %x30-37) / ; "00"-"07" +// ; omit U+0008-U+000A BS HT LF +// ("0" %x62) / ; "0b" +// ; omit U+000C-U+000D FF CR +// ("0" %x65-66) / ; "0e"-"0f" +// ("1" normal-HEXDIG) +// ) +//normal-HEXDIG = DIGIT / %x61-66 ; "0"-"9", "a"-"f" +//normal-index-selector = "0" / (DIGIT1 *DIGIT) +// ; non-negative decimal integer + +// The list of tokens. +const ( + ILLEGAL Token = iota + STRING + INTEGER + FLOAT + STRING_LITERAL + TRUE + FALSE + NULL + ROOT + CURRENT + WILDCARD + PROPERTY_NAME + RECURSIVE + CHILD + ARRAY_SLICE + FILTER + PAREN_LEFT + PAREN_RIGHT + BRACKET_LEFT + BRACKET_RIGHT + COMMA + TILDE + AND + OR + NOT + EQ + NE + GT + GE + LT + LE + MATCHES + FUNCTION +) + +var SimpleTokens = [...]Token{ + STRING, + INTEGER, + STRING_LITERAL, + CHILD, + BRACKET_LEFT, + BRACKET_RIGHT, + ROOT, +} + +var tokens = [...]string{ + ILLEGAL: "ILLEGAL", + STRING: "STRING", + INTEGER: "INTEGER", + FLOAT: "FLOAT", + STRING_LITERAL: "STRING_LITERAL", + TRUE: "TRUE", + FALSE: "FALSE", + NULL: "NULL", + // root node identifier (Section 2.2) + ROOT: "$", + // current node identifier (Section 2.3.5) + // (valid only within filter selectors) + CURRENT: "@", + WILDCARD: "*", + RECURSIVE: "..", + CHILD: ".", + // start:end:step array slice operator (Section 2.3.4) + ARRAY_SLICE: ":", + // filter selector (Section 2.3.5): selects + // particular children using a logical + // expression + FILTER: "?", + PAREN_LEFT: "(", + PAREN_RIGHT: ")", + BRACKET_LEFT: "[", + BRACKET_RIGHT: "]", + COMMA: ",", + TILDE: "~", + AND: "&&", + OR: "||", + NOT: "!", + EQ: "==", + NE: "!=", + GT: ">", + GE: ">=", + LT: "<", + LE: "<=", + MATCHES: "=~", + FUNCTION: "FUNCTION", +} + +// String returns the string representation of the token. +func (tok Token) String() string { + if tok >= 0 && tok < Token(len(tokens)) { + return tokens[tok] + } + return "token(" + strconv.Itoa(int(tok)) + ")" +} + +func (tok Tokens) IsSimple() bool { + if len(tok) == 0 { + return false + } + if tok[0].Token != ROOT { + return false + } + for _, token := range tok { + isSimple := false + for _, simpleToken := range SimpleTokens { + if token.Token == simpleToken { + isSimple = true + } + } + if !isSimple { + return false + } + } + return true +} + +// When there's an error in the tokenizer, this helps represent it. +func (t Tokenizer) ErrorString(target *TokenInfo, msg string) string { + var errorBuilder strings.Builder + + var token TokenInfo + if target == nil { + // grab last token (as value) + token = t.tokens[len(t.tokens)-1] + // set column to +1 + token.Column++ + target = &token + } + + // Write the error message with line and column information + errorBuilder.WriteString(fmt.Sprintf("Error at line %d, column %d: %s\n", target.Line, target.Column, msg)) + + // Find the start and end positions of the line containing the target token + lineStart := 0 + lineEnd := len(t.input) + for i := target.Line - 1; i > 0; i-- { + if pos := strings.LastIndexByte(t.input[:lineStart], '\n'); pos != -1 { + lineStart = pos + 1 + break + } + } + if pos := strings.IndexByte(t.input[lineStart:], '\n'); pos != -1 { + lineEnd = lineStart + pos + } + + // Extract the line containing the target token + line := t.input[lineStart:lineEnd] + errorBuilder.WriteString(line) + errorBuilder.WriteString("\n") + + // Calculate the number of spaces before the target token + spaces := strings.Repeat(" ", target.Column) + + // Write the caret symbol pointing to the target token + errorBuilder.WriteString(spaces) + dots := "" + if target.Len > 0 { + dots = strings.Repeat(".", target.Len-1) + } + errorBuilder.WriteString("^" + dots + "\n") + + return errorBuilder.String() +} + +// When there's an error +func (t Tokenizer) ErrorTokenString(target *TokenInfo, msg string) string { + var errorBuilder strings.Builder + var token TokenInfo + if target == nil { + // grab last token (as value) + token = t.tokens[len(t.tokens)-1] + // set column to +1 + token.Column++ + target = &token + } + // Write the error message with line and column information + errorBuilder.WriteString(t.ErrorString(target, msg)) + + // Find the start and end positions of the line containing the target token + lineStart := 0 + lineEnd := len(t.input) + for i := target.Line - 1; i > 0; i-- { + if pos := strings.LastIndexByte(t.input[:lineStart], '\n'); pos != -1 { + lineStart = pos + 1 + break + } + } + if pos := strings.IndexByte(t.input[lineStart:], '\n'); pos != -1 { + lineEnd = lineStart + pos + } + + // Extract the line containing the target token + line := t.input[lineStart:lineEnd] + + // Calculate the number of spaces before the target token + for _, token := range t.tokens { + errorBuilder.WriteString(line) + errorBuilder.WriteString("\n") + spaces := strings.Repeat(" ", token.Column) + dots := "" + if token.Len > 0 { + dots = strings.Repeat(".", token.Len-1) + } + errorBuilder.WriteString(spaces) + errorBuilder.WriteString(fmt.Sprintf("^%s %s\n", dots, tokens[token.Token])) + } + + return errorBuilder.String() +} + +// TokenInfo represents a token and its associated information. +type TokenInfo struct { + Token Token + Line int + Column int + Literal string + Len int +} + +// Tokens represents the list of tokens +type Tokens []TokenInfo + +// Tokenizer represents a JSONPath tokenizer. +type Tokenizer struct { + input string + pos int + line int + column int + tokens []TokenInfo + stack []Token + illegalWhitespace bool + config config.Config +} + +// NewTokenizer creates a new JSONPath tokenizer for the given input string. +func NewTokenizer(input string, opts ...config.Option) *Tokenizer { + cfg := config.New(opts...) + return &Tokenizer{ + input: input, + config: cfg, + line: 1, + stack: make([]Token, 0), + } +} + +// Tokenize tokenizes the input string and returns a slice of TokenInfo. +func (t *Tokenizer) Tokenize() Tokens { + for t.pos < len(t.input) { + if !t.illegalWhitespace { + t.skipWhitespace() + } + if t.pos >= len(t.input) { + break + } + + switch ch := t.input[t.pos]; { + case ch == '$': + t.addToken(ROOT, 1, "") + case ch == '@': + t.addToken(CURRENT, 1, "") + case ch == '*': + t.addToken(WILDCARD, 1, "") + case ch == '~': + if t.config.PropertyNameEnabled() { + t.addToken(PROPERTY_NAME, 1, "") + } else { + t.addToken(ILLEGAL, 1, "invalid property name token without config.PropertyNameExtension set to true") + } + case ch == '.': + if t.peek() == '.' { + t.addToken(RECURSIVE, 2, "") + t.pos++ + t.column++ + t.illegalWhitespace = true + } else { + t.addToken(CHILD, 1, "") + t.illegalWhitespace = true + } + case ch == ',': + t.addToken(COMMA, 1, "") + case ch == ':': + t.addToken(ARRAY_SLICE, 1, "") + case ch == '?': + t.addToken(FILTER, 1, "") + case ch == '(': + t.addToken(PAREN_LEFT, 1, "") + t.stack = append(t.stack, PAREN_LEFT) + case ch == ')': + t.addToken(PAREN_RIGHT, 1, "") + if len(t.stack) > 0 && t.stack[len(t.stack)-1] == PAREN_LEFT { + t.stack = t.stack[:len(t.stack)-1] + } else { + t.addToken(ILLEGAL, 1, "unmatched closing parenthesis") + } + case ch == '[': + t.addToken(BRACKET_LEFT, 1, "") + t.stack = append(t.stack, BRACKET_LEFT) + case ch == ']': + if len(t.stack) > 0 && t.stack[len(t.stack)-1] == BRACKET_LEFT { + t.addToken(BRACKET_RIGHT, 1, "") + t.stack = t.stack[:len(t.stack)-1] + } else { + t.addToken(ILLEGAL, 1, "unmatched closing bracket") + } + case ch == '&': + if t.peek() == '&' { + t.addToken(AND, 2, "") + t.pos++ + t.column++ + } else { + t.addToken(ILLEGAL, 1, "invalid token") + } + case ch == '|': + if t.peek() == '|' { + t.addToken(OR, 2, "") + t.pos++ + t.column++ + } else { + t.addToken(ILLEGAL, 1, "invalid token") + } + case ch == '!': + if t.peek() == '=' { + t.addToken(NE, 2, "") + t.pos++ + t.column++ + } else { + t.addToken(NOT, 1, "") + } + case ch == '=': + if t.peek() == '=' { + t.addToken(EQ, 2, "") + t.pos++ + t.column++ + } else if t.peek() == '~' { + t.addToken(MATCHES, 2, "") + t.pos++ + t.column++ + } else { + t.addToken(ILLEGAL, 1, "invalid token") + } + case ch == '>': + if t.peek() == '=' { + t.addToken(GE, 2, "") + t.pos++ + t.column++ + } else { + t.addToken(GT, 1, "") + } + case ch == '<': + if t.peek() == '=' { + t.addToken(LE, 2, "") + t.pos++ + t.column++ + } else { + t.addToken(LT, 1, "") + } + case ch == '"' || ch == '\'': + t.scanString(rune(ch)) + case ch == '-' && isDigit(t.peek()): + fallthrough + case isDigit(ch): + t.scanNumber() + case isLiteralChar(ch): + t.scanLiteral() + default: + t.addToken(ILLEGAL, 1, string(ch)) + } + t.pos++ + t.column++ + } + + if len(t.stack) > 0 { + t.addToken(ILLEGAL, 1, fmt.Sprintf("unmatched %s", t.stack[len(t.stack)-1].String())) + } + return t.tokens +} + +func (t *Tokenizer) addToken(token Token, len int, literal string) { + t.tokens = append(t.tokens, TokenInfo{ + Token: token, + Line: t.line, + Column: t.column, + Len: len, + Literal: literal, + }) + t.illegalWhitespace = false +} + +func (t *Tokenizer) scanString(quote rune) { + start := t.pos + 1 + var literal strings.Builder +illegal: + for i := start; i < len(t.input); i++ { + b := literal.String() + _ = b + if t.input[i] == byte(quote) { + t.addToken(STRING_LITERAL, len(t.input[start:i])+2, literal.String()) + t.pos = i + t.column += i - start + 1 + return + } + if t.input[i] == '\\' { + i++ + if i >= len(t.input) { + t.addToken(ILLEGAL, len(t.input[start:]), literal.String()) + t.pos = len(t.input) - 1 + t.column = len(t.input) - 1 + return + } + switch t.input[i] { + case 'b': + literal.WriteByte('\b') + case 'f': + literal.WriteByte('\f') + case 'n': + literal.WriteByte('\n') + case 'r': + literal.WriteByte('\n') + case 't': + literal.WriteByte('\t') + case '\'': + if quote != '\'' { + // don't escape it, when we're not in a single quoted string + break illegal + } else { + literal.WriteByte(t.input[i]) + } + case '"': + if quote != '"' { + // don't escape it, when we're not in a single quoted string + break illegal + } else { + literal.WriteByte(t.input[i]) + } + case '\\', '/': + literal.WriteByte(t.input[i]) + default: + break illegal + } + } else { + literal.WriteByte(t.input[i]) + } + } + t.addToken(ILLEGAL, len(t.input[start:]), literal.String()) + t.pos = len(t.input) - 1 + t.column = len(t.input) - 1 +} + +func (t *Tokenizer) scanNumber() { + start := t.pos + tokenType := INTEGER + dotSeen := false + exponentSeen := false + + for i := start; i < len(t.input); i++ { + if i == start && t.input[i] == '-' { + continue + } + + if t.input[i] == '.' { + if dotSeen || exponentSeen { + t.addToken(ILLEGAL, len(t.input[start:i]), t.input[start:i]) + t.pos = i + t.column += i - start + return + } + tokenType = FLOAT + dotSeen = true + continue + } + + if t.input[i] == 'e' || t.input[i] == 'E' { + if exponentSeen || (len(t.input) > 0 && t.input[i-1] == '.') { + t.addToken(ILLEGAL, len(t.input[start:i]), t.input[start:i]) + t.pos = i + t.column += i - start + return + } + tokenType = FLOAT + exponentSeen = true + if i+1 < len(t.input) && (t.input[i+1] == '+' || t.input[i+1] == '-') { + i++ + } + continue + } + + if !isDigit(t.input[i]) { + literal := t.input[start:i] + // check for legal numbers + _, err := strconv.ParseFloat(literal, 64) + if err != nil { + tokenType = ILLEGAL + } + // conformance spec + if len(literal) > 1 && literal[0] == '0' && !dotSeen { + // no leading zero + tokenType = ILLEGAL + } else if len(literal) > 2 && literal[0] == '-' && literal[1] == '0' && !dotSeen { + // no trailing dot + tokenType = ILLEGAL + } else if len(literal) > 0 && literal[len(literal)-1] == '.' { + // no trailing dot + tokenType = ILLEGAL + } else if literal[len(literal)-1] == 'e' || literal[len(literal)-1] == 'E' { + // no exponent + tokenType = ILLEGAL + } + + t.addToken(tokenType, len(literal), literal) + t.pos = i - 1 + t.column += i - start - 1 + return + } + } + + if exponentSeen && !isDigit(t.input[len(t.input)-1]) { + t.addToken(ILLEGAL, len(t.input[start:]), t.input[start:]) + t.pos = len(t.input) - 1 + t.column = len(t.input) - 1 + return + } + + literal := t.input[start:] + t.addToken(tokenType, len(literal), literal) + t.pos = len(t.input) - 1 + t.column = len(t.input) - 1 +} + +func (t *Tokenizer) scanLiteral() { + start := t.pos + for i := start; i < len(t.input); i++ { + if !isLiteralChar(t.input[i]) && !isDigit(t.input[i]) { + literal := t.input[start:i] + switch literal { + case "true": + t.addToken(TRUE, len(literal), literal) + case "false": + t.addToken(FALSE, len(literal), literal) + case "null": + t.addToken(NULL, len(literal), literal) + default: + if isFunctionName(literal) { + t.addToken(FUNCTION, len(literal), literal) + t.illegalWhitespace = true + } else { + t.addToken(STRING, len(literal), literal) + } + } + t.pos = i - 1 + t.column += i - start - 1 + return + } + } + literal := t.input[start:] + switch literal { + case "true": + t.addToken(TRUE, len(literal), literal) + case "false": + t.addToken(FALSE, len(literal), literal) + case "null": + t.addToken(NULL, len(literal), literal) + default: + t.addToken(STRING, len(literal), literal) + } + t.pos = len(t.input) - 1 + t.column = len(t.input) - 1 +} + +func isFunctionName(literal string) bool { + return literal == "length" || literal == "count" || literal == "match" || literal == "search" || literal == "value" +} + +func (t *Tokenizer) skipWhitespace() { + // S = *B ; optional blank space + // B = %x20 / ; Space + // %x09 / ; Horizontal tab + // %x0A / ; Line feed or New line + // %x0D ; Carriage return + for len(t.tokens) > 0 && t.pos+1 < len(t.input) { + ch := t.input[t.pos] + if ch == '\n' { + t.line++ + t.pos++ + t.column = 0 + } else if !isSpace(ch) { + break + } else { + t.pos++ + t.column++ + } + } +} + +func (t *Tokenizer) peek() byte { + if t.pos+1 < len(t.input) { + return t.input[t.pos+1] + } + return 0 +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func isLiteralChar(ch byte) bool { + // allow unicode characters + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 +} + +func isSpace(ch byte) bool { + return ch == ' ' || ch == '\t' || ch == '\r' +} diff --git a/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/yaml_eval.go b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/yaml_eval.go new file mode 100644 index 0000000..7291ea9 --- /dev/null +++ b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/yaml_eval.go @@ -0,0 +1,278 @@ +package jsonpath + +import ( + "fmt" + "gopkg.in/yaml.v3" + "reflect" + "regexp" + "strconv" + "unicode/utf8" +) + +func (l literal) Equals(value literal) bool { + if l.integer != nil && value.integer != nil { + return *l.integer == *value.integer + } + if l.float64 != nil && value.float64 != nil { + return *l.float64 == *value.float64 + } + if l.integer != nil && value.float64 != nil { + return float64(*l.integer) == *value.float64 + } + if l.float64 != nil && value.integer != nil { + return *l.float64 == float64(*value.integer) + } + if l.string != nil && value.string != nil { + return *l.string == *value.string + } + if l.bool != nil && value.bool != nil { + return *l.bool == *value.bool + } + if l.null != nil && value.null != nil { + return *l.null == *value.null + } + if l.node != nil && value.node != nil { + return equalsNode(l.node, value.node) + } + if reflect.ValueOf(l).IsZero() && reflect.ValueOf(value).IsZero() { + return true + } + return false +} + +func equalsNode(a *yaml.Node, b *yaml.Node) bool { + // decode into interfaces, then compare + if a.Tag != b.Tag { + return false + } + switch a.Tag { + case "!!str": + return a.Value == b.Value + case "!!int": + return a.Value == b.Value + case "!!float": + return a.Value == b.Value + case "!!bool": + return a.Value == b.Value + case "!!null": + return a.Value == b.Value + case "!!seq": + if len(a.Content) != len(b.Content) { + return false + } + for i := 0; i < len(a.Content); i++ { + if !equalsNode(a.Content[i], b.Content[i]) { + return false + } + } + case "!!map": + if len(a.Content) != len(b.Content) { + return false + } + for i := 0; i < len(a.Content); i += 2 { + if !equalsNode(a.Content[i], b.Content[i]) { + return false + } + if !equalsNode(a.Content[i+1], b.Content[i+1]) { + return false + } + } + } + return true +} + +func (l literal) LessThan(value literal) bool { + if l.integer != nil && value.integer != nil { + return *l.integer < *value.integer + } + if l.float64 != nil && value.float64 != nil { + return *l.float64 < *value.float64 + } + if l.integer != nil && value.float64 != nil { + return float64(*l.integer) < *value.float64 + } + if l.float64 != nil && value.integer != nil { + return *l.float64 < float64(*value.integer) + } + if l.string != nil && value.string != nil { + return *l.string < *value.string + } + return false +} + +func (l literal) LessThanOrEqual(value literal) bool { + return l.LessThan(value) || l.Equals(value) +} + +func (c comparable) Evaluate(idx index, node *yaml.Node, root *yaml.Node) literal { + if c.literal != nil { + return *c.literal + } + if c.singularQuery != nil { + return c.singularQuery.Evaluate(idx, node, root) + } + if c.functionExpr != nil { + return c.functionExpr.Evaluate(idx, node, root) + } + return literal{} +} + +func (e functionExpr) length(idx index, node *yaml.Node, root *yaml.Node) literal { + args := e.args[0].Eval(idx, node, root) + if args.kind != functionArgTypeLiteral { + return literal{} + } + //* If the argument value is a string, the result is the number of + //Unicode scalar values in the string. + if args.literal != nil && args.literal.string != nil { + res := utf8.RuneCountInString(*args.literal.string) + return literal{integer: &res} + } + //* If the argument value is an array, the result is the number of + //elements in the array. + // + //* If the argument value is an object, the result is the number of + //members in the object. + // + //* For any other argument value, the result is the special result + //Nothing. + + if args.literal.node != nil { + switch args.literal.node.Kind { + case yaml.SequenceNode: + res := len(args.literal.node.Content) + return literal{integer: &res} + case yaml.MappingNode: + res := len(args.literal.node.Content) / 2 + return literal{integer: &res} + } + } + return literal{} +} + +func (e functionExpr) count(idx index, node *yaml.Node, root *yaml.Node) literal { + args := e.args[0].Eval(idx, node, root) + if args.kind == functionArgTypeNodes { + res := len(args.nodes) + return literal{integer: &res} + } + + res := 1 + return literal{integer: &res} +} + +func (e functionExpr) match(idx index, node *yaml.Node, root *yaml.Node) literal { + arg1 := e.args[0].Eval(idx, node, root) + arg2 := e.args[1].Eval(idx, node, root) + if arg1.kind != functionArgTypeLiteral || arg2.kind != functionArgTypeLiteral { + return literal{} + } + if arg1.literal.string == nil || arg2.literal.string == nil { + return literal{bool: &[]bool{false}[0]} + } + matched, _ := regexp.MatchString(fmt.Sprintf("^(%s)$", *arg2.literal.string), *arg1.literal.string) + return literal{bool: &matched} +} + +func (e functionExpr) search(idx index, node *yaml.Node, root *yaml.Node) literal { + arg1 := e.args[0].Eval(idx, node, root) + arg2 := e.args[1].Eval(idx, node, root) + if arg1.kind != functionArgTypeLiteral || arg2.kind != functionArgTypeLiteral { + return literal{} + } + if arg1.literal.string == nil || arg2.literal.string == nil { + return literal{bool: &[]bool{false}[0]} + } + matched, _ := regexp.MatchString(*arg2.literal.string, *arg1.literal.string) + return literal{bool: &matched} +} + +func (e functionExpr) value(idx index, node *yaml.Node, root *yaml.Node) literal { + // 2.4.8. value() Function Extension + // + //Parameters: + // 1. NodesType + // + //Result: ValueType + //Its only argument is an instance of NodesType (possibly taken from a + //filter-query, as in the example above). The result is an instance of + //ValueType. + // + //* If the argument contains a single node, the result is the value of + //the node. + // + //* If the argument is the empty nodelist or contains multiple nodes, + // the result is Nothing. + + nodesType := e.args[0].Eval(idx, node, root) + if nodesType.kind == functionArgTypeLiteral { + return *nodesType.literal + } else if nodesType.kind == functionArgTypeNodes && len(nodesType.nodes) == 1 { + return *nodesType.nodes[0] + } + return literal{} +} + +func nodeToLiteral(node *yaml.Node) literal { + switch node.Tag { + case "!!str": + return literal{string: &node.Value} + case "!!int": + i, _ := strconv.Atoi(node.Value) + return literal{integer: &i} + case "!!float": + f, _ := strconv.ParseFloat(node.Value, 64) + return literal{float64: &f} + case "!!bool": + b, _ := strconv.ParseBool(node.Value) + return literal{bool: &b} + case "!!null": + b := true + return literal{null: &b} + default: + return literal{node: node} + } +} + +func (e functionExpr) Evaluate(idx index, node *yaml.Node, root *yaml.Node) literal { + switch e.funcType { + case functionTypeLength: + return e.length(idx, node, root) + case functionTypeCount: + return e.count(idx, node, root) + case functionTypeMatch: + return e.match(idx, node, root) + case functionTypeSearch: + return e.search(idx, node, root) + case functionTypeValue: + return e.value(idx, node, root) + } + return literal{} +} + +func (q singularQuery) Evaluate(idx index, node *yaml.Node, root *yaml.Node) literal { + if q.relQuery != nil { + return q.relQuery.Evaluate(idx, node, root) + } + if q.absQuery != nil { + return q.absQuery.Evaluate(idx, node, root) + } + return literal{} +} + +func (q relQuery) Evaluate(idx index, node *yaml.Node, root *yaml.Node) literal { + result := q.Query(idx, node, root) + if len(result) == 1 { + return nodeToLiteral(result[0]) + } + return literal{} + +} + +func (q absQuery) Evaluate(idx index, node *yaml.Node, root *yaml.Node) literal { + result := q.Query(idx, root, root) + if len(result) == 1 { + return nodeToLiteral(result[0]) + } + return literal{} +} diff --git a/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/yaml_query.go b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/yaml_query.go new file mode 100644 index 0000000..44b55a3 --- /dev/null +++ b/vendor/github.com/speakeasy-api/jsonpath/pkg/jsonpath/yaml_query.go @@ -0,0 +1,393 @@ +package jsonpath + +import ( + "gopkg.in/yaml.v3" +) + +type Evaluator interface { + Query(current *yaml.Node, root *yaml.Node) []*yaml.Node +} + +type index interface { + setPropertyKey(key *yaml.Node, value *yaml.Node) + getPropertyKey(key *yaml.Node) *yaml.Node +} + +type _index struct { + propertyKeys map[*yaml.Node]*yaml.Node +} + +func (i *_index) setPropertyKey(key *yaml.Node, value *yaml.Node) { + if i != nil && i.propertyKeys != nil { + i.propertyKeys[key] = value + } +} + +func (i *_index) getPropertyKey(key *yaml.Node) *yaml.Node { + if i != nil { + return i.propertyKeys[key] + } + return nil +} + +// jsonPathAST can be Evaluated +var _ Evaluator = jsonPathAST{} + +func (q jsonPathAST) Query(current *yaml.Node, root *yaml.Node) []*yaml.Node { + idx := _index{ + propertyKeys: map[*yaml.Node]*yaml.Node{}, + } + result := make([]*yaml.Node, 0) + // If the top level node is a documentnode, unwrap it + if root.Kind == yaml.DocumentNode && len(root.Content) == 1 { + root = root.Content[0] + } + result = append(result, root) + + for _, segment := range q.segments { + newValue := []*yaml.Node{} + for _, value := range result { + newValue = append(newValue, segment.Query(&idx, value, root)...) + } + result = newValue + } + return result +} + +func (s segment) Query(idx index, value *yaml.Node, root *yaml.Node) []*yaml.Node { + switch s.kind { + case segmentKindChild: + return s.child.Query(idx, value, root) + case segmentKindDescendant: + // run the inner segment against this node + var result = []*yaml.Node{} + children := descend(value, root) + for _, child := range children { + result = append(result, s.descendant.Query(idx, child, root)...) + } + // make children unique by pointer value + result = unique(result) + return result + case segmentKindProperyName: + found := idx.getPropertyKey(value) + if found != nil { + return []*yaml.Node{found} + } + return []*yaml.Node{} + } + panic("no segment type") +} + +func unique(nodes []*yaml.Node) []*yaml.Node { + // stably returns a new slice containing only the unique elements from nodes + res := make([]*yaml.Node, 0) + seen := make(map[*yaml.Node]bool) + for _, node := range nodes { + if _, ok := seen[node]; !ok { + res = append(res, node) + seen[node] = true + } + } + return res +} + +func (s innerSegment) Query(idx index, value *yaml.Node, root *yaml.Node) []*yaml.Node { + result := []*yaml.Node{} + + switch s.kind { + case segmentDotWildcard: + // Handle wildcard - get all children + switch value.Kind { + case yaml.MappingNode: + // in a mapping node, keys and values alternate + // we just want to return the values + for i, child := range value.Content { + if i%2 == 1 { + idx.setPropertyKey(value.Content[i-1], value) + idx.setPropertyKey(child, value.Content[i-1]) + result = append(result, child) + } + } + case yaml.SequenceNode: + for _, child := range value.Content { + result = append(result, child) + } + } + return result + case segmentDotMemberName: + // Handle member access + if value.Kind == yaml.MappingNode { + // In YAML mapping nodes, keys and values alternate + + for i := 0; i < len(value.Content); i += 2 { + key := value.Content[i] + val := value.Content[i+1] + + if key.Value == s.dotName { + idx.setPropertyKey(key, value) + idx.setPropertyKey(val, key) + result = append(result, val) + break + } + } + } + + case segmentLongHand: + // Handle long hand selectors + for _, selector := range s.selectors { + result = append(result, selector.Query(idx, value, root)...) + } + default: + panic("unknown child segment kind") + } + + return result + +} + +func (s selector) Query(idx index, value *yaml.Node, root *yaml.Node) []*yaml.Node { + switch s.kind { + case selectorSubKindName: + if value.Kind != yaml.MappingNode { + return nil + } + // MappingNode children is a list of alternating keys and values + var key string + for i, child := range value.Content { + if i%2 == 0 { + key = child.Value + continue + } + if key == s.name && i%2 == 1 { + idx.setPropertyKey(value.Content[i], value.Content[i-1]) + idx.setPropertyKey(value.Content[i-1], value) + return []*yaml.Node{child} + } + } + case selectorSubKindArrayIndex: + if value.Kind != yaml.SequenceNode { + return nil + } + // if out of bounds, return nothing + if s.index >= int64(len(value.Content)) || s.index < -int64(len(value.Content)) { + return nil + } + // if index is negative, go backwards + if s.index < 0 { + return []*yaml.Node{value.Content[int64(len(value.Content))+s.index]} + } + return []*yaml.Node{value.Content[s.index]} + case selectorSubKindWildcard: + if value.Kind == yaml.SequenceNode { + return value.Content + } else if value.Kind == yaml.MappingNode { + var result []*yaml.Node + for i, child := range value.Content { + if i%2 == 1 { + idx.setPropertyKey(value.Content[i-1], value) + idx.setPropertyKey(child, value.Content[i-1]) + result = append(result, child) + } + } + return result + } + return nil + case selectorSubKindArraySlice: + if value.Kind != yaml.SequenceNode { + return nil + } + if len(value.Content) == 0 { + return nil + } + step := int64(1) + if s.slice.step != nil { + step = *s.slice.step + } + if step == 0 { + return nil + } + + start, end := s.slice.start, s.slice.end + lower, upper := bounds(start, end, step, int64(len(value.Content))) + + var result []*yaml.Node + if step > 0 { + for i := lower; i < upper; i += step { + result = append(result, value.Content[i]) + } + } else { + for i := upper; i > lower; i += step { + result = append(result, value.Content[i]) + } + } + + return result + case selectorSubKindFilter: + var result []*yaml.Node + switch value.Kind { + case yaml.MappingNode: + for i := 1; i < len(value.Content); i += 2 { + idx.setPropertyKey(value.Content[i-1], value) + idx.setPropertyKey(value.Content[i], value.Content[i-1]) + if s.filter.Matches(idx, value.Content[i], root) { + result = append(result, value.Content[i]) + } + } + case yaml.SequenceNode: + for _, child := range value.Content { + if s.filter.Matches(idx, child, root) { + result = append(result, child) + } + } + } + return result + } + return nil +} + +func normalize(i, length int64) int64 { + if i >= 0 { + return i + } + return length + i +} + +func bounds(start, end *int64, step, length int64) (int64, int64) { + var nStart, nEnd int64 + if start != nil { + nStart = normalize(*start, length) + } else if step > 0 { + nStart = 0 + } else { + nStart = length - 1 + } + if end != nil { + nEnd = normalize(*end, length) + } else if step > 0 { + nEnd = length + } else { + nEnd = -1 + } + + var lower, upper int64 + if step >= 0 { + lower = max(min(nStart, length), 0) + upper = min(max(nEnd, 0), length) + } else { + upper = min(max(nStart, -1), length-1) + lower = min(max(nEnd, -1), length-1) + } + + return lower, upper +} + +func (s filterSelector) Matches(idx index, node *yaml.Node, root *yaml.Node) bool { + return s.expression.Matches(idx, node, root) +} + +func (e logicalOrExpr) Matches(idx index, node *yaml.Node, root *yaml.Node) bool { + for _, expr := range e.expressions { + if expr.Matches(idx, node, root) { + return true + } + } + return false +} + +func (e logicalAndExpr) Matches(idx index, node *yaml.Node, root *yaml.Node) bool { + for _, expr := range e.expressions { + if !expr.Matches(idx, node, root) { + return false + } + } + return true +} + +func (e basicExpr) Matches(idx index, node *yaml.Node, root *yaml.Node) bool { + if e.parenExpr != nil { + result := e.parenExpr.expr.Matches(idx, node, root) + if e.parenExpr.not { + return !result + } + return result + } else if e.comparisonExpr != nil { + return e.comparisonExpr.Matches(idx, node, root) + } else if e.testExpr != nil { + return e.testExpr.Matches(idx, node, root) + } + return false +} + +func (e comparisonExpr) Matches(idx index, node *yaml.Node, root *yaml.Node) bool { + leftValue := e.left.Evaluate(idx, node, root) + rightValue := e.right.Evaluate(idx, node, root) + + switch e.op { + case equalTo: + return leftValue.Equals(rightValue) + case notEqualTo: + return !leftValue.Equals(rightValue) + case lessThan: + return leftValue.LessThan(rightValue) + case lessThanEqualTo: + return leftValue.LessThanOrEqual(rightValue) + case greaterThan: + return rightValue.LessThan(leftValue) + case greaterThanEqualTo: + return rightValue.LessThanOrEqual(leftValue) + default: + return false + } +} + +func (e testExpr) Matches(idx index, node *yaml.Node, root *yaml.Node) bool { + var result bool + if e.filterQuery != nil { + result = len(e.filterQuery.Query(idx, node, root)) > 0 + } else if e.functionExpr != nil { + funcResult := e.functionExpr.Evaluate(idx, node, root) + if funcResult.bool != nil { + result = *funcResult.bool + } else if funcResult.null == nil { + result = true + } + } + if e.not { + return !result + } + return result +} + +func (q filterQuery) Query(idx index, node *yaml.Node, root *yaml.Node) []*yaml.Node { + if q.relQuery != nil { + return q.relQuery.Query(idx, node, root) + } + if q.jsonPathQuery != nil { + return q.jsonPathQuery.Query(node, root) + } + return nil +} + +func (q relQuery) Query(idx index, node *yaml.Node, root *yaml.Node) []*yaml.Node { + result := []*yaml.Node{node} + for _, seg := range q.segments { + var newResult []*yaml.Node + for _, value := range result { + newResult = append(newResult, seg.Query(idx, value, root)...) + } + result = newResult + } + return result +} + +func (q absQuery) Query(idx index, node *yaml.Node, root *yaml.Node) []*yaml.Node { + result := []*yaml.Node{root} + for _, seg := range q.segments { + var newResult []*yaml.Node + for _, value := range result { + newResult = append(newResult, seg.Query(idx, value, root)...) + } + result = newResult + } + return result +} diff --git a/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/apply.go b/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/apply.go index 8cd9aae..faaaf92 100644 --- a/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/apply.go +++ b/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/apply.go @@ -2,7 +2,8 @@ package overlay import ( "fmt" - "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" + "github.com/speakeasy-api/jsonpath/pkg/jsonpath/config" + "github.com/speakeasy-api/jsonpath/pkg/jsonpath/token" "gopkg.in/yaml.v3" "strings" ) @@ -13,9 +14,9 @@ func (o *Overlay) ApplyTo(root *yaml.Node) error { for _, action := range o.Actions { var err error if action.Remove { - err = applyRemoveAction(root, action) + err = o.applyRemoveAction(root, action, nil) } else { - err = applyUpdateAction(root, action, &[]string{}) + err = o.applyUpdateAction(root, action, &[]string{}) } if err != nil { @@ -29,41 +30,51 @@ func (o *Overlay) ApplyTo(root *yaml.Node) error { func (o *Overlay) ApplyToStrict(root *yaml.Node) (error, []string) { multiError := []string{} warnings := []string{} + hasFilterExpression := false for i, action := range o.Actions { - err := validateSelectorHasAtLeastOneTarget(root, action) + tokens := token.NewTokenizer(action.Target, config.WithPropertyNameExtension()).Tokenize() + for _, tok := range tokens { + if tok.Token == token.FILTER { + hasFilterExpression = true + } + } + + actionWarnings := []string{} + err := o.validateSelectorHasAtLeastOneTarget(root, action) if err != nil { multiError = append(multiError, err.Error()) } if action.Remove { - err = applyRemoveAction(root, action) + err = o.applyRemoveAction(root, action, &actionWarnings) } else { - actionWarnings := []string{} - err = applyUpdateAction(root, action, &actionWarnings) - for _, warning := range actionWarnings { - warnings = append(warnings, fmt.Sprintf("update action (%v / %v) target=%s: %s", i+1, len(o.Actions), action.Target, warning)) - } + err = o.applyUpdateAction(root, action, &actionWarnings) + } + for _, warning := range actionWarnings { + warnings = append(warnings, fmt.Sprintf("update action (%v / %v) target=%s: %s", i+1, len(o.Actions), action.Target, warning)) } } + + if hasFilterExpression && !o.UsesRFC9535() { + warnings = append(warnings, "overlay has a filter expression but lacks `x-speakeasy-jsonpath: rfc9535` extension. Deprecated jsonpath behaviour in use. See overlay.speakeasy.com for the implementation playground.") + } + if len(multiError) > 0 { return fmt.Errorf("error applying overlay (strict): %v", strings.Join(multiError, ",")), warnings } return nil, warnings } -func validateSelectorHasAtLeastOneTarget(root *yaml.Node, action Action) error { +func (o *Overlay) validateSelectorHasAtLeastOneTarget(root *yaml.Node, action Action) error { if action.Target == "" { return nil } - p, err := yamlpath.NewPath(action.Target) + p, err := o.NewPath(action.Target, nil) if err != nil { return err } - nodes, err := p.Find(root) - if err != nil { - return err - } + nodes := p.Query(root) if len(nodes) == 0 { return fmt.Errorf("selector %q did not match any targets", action.Target) @@ -72,19 +83,19 @@ func validateSelectorHasAtLeastOneTarget(root *yaml.Node, action Action) error { return nil } -func applyRemoveAction(root *yaml.Node, action Action) error { +func (o *Overlay) applyRemoveAction(root *yaml.Node, action Action, warnings *[]string) error { if action.Target == "" { return nil } idx := newParentIndex(root) - p, err := yamlpath.NewPath(action.Target) + p, err := o.NewPath(action.Target, warnings) if err != nil { return err } - nodes, err := p.Find(root) + nodes := p.Query(root) if err != nil { return err } @@ -106,8 +117,13 @@ func removeNode(idx parentIndex, node *yaml.Node) { if child == node { switch parent.Kind { case yaml.MappingNode: - // we have to delete the key too - parent.Content = append(parent.Content[:i-1], parent.Content[i+1:]...) + if i%2 == 1 { + // if we select a value, we should delete the key too + parent.Content = append(parent.Content[:i-1], parent.Content[i+1:]...) + } else { + // if we select a key, we should delete the value + parent.Content = append(parent.Content[:i], parent.Content[i+2:]...) + } return case yaml.SequenceNode: parent.Content = append(parent.Content[:i], parent.Content[i+1:]...) @@ -117,7 +133,7 @@ func removeNode(idx parentIndex, node *yaml.Node) { } } -func applyUpdateAction(root *yaml.Node, action Action, warnings *[]string) error { +func (o *Overlay) applyUpdateAction(root *yaml.Node, action Action, warnings *[]string) error { if action.Target == "" { return nil } @@ -126,22 +142,19 @@ func applyUpdateAction(root *yaml.Node, action Action, warnings *[]string) error return nil } - p, err := yamlpath.NewPath(action.Target) - if err != nil { - return err - } - - nodes, err := p.Find(root) + p, err := o.NewPath(action.Target, warnings) if err != nil { return err } + nodes := p.Query(root) prior, err := yaml.Marshal(root) if err != nil { return err } + for _, node := range nodes { - if err := updateNode(node, action.Update); err != nil { + if err := updateNode(node, &action.Update); err != nil { return err } } @@ -156,14 +169,14 @@ func applyUpdateAction(root *yaml.Node, action Action, warnings *[]string) error return nil } -func updateNode(node *yaml.Node, updateNode yaml.Node) error { +func updateNode(node *yaml.Node, updateNode *yaml.Node) error { mergeNode(node, updateNode) return nil } -func mergeNode(node *yaml.Node, merge yaml.Node) { +func mergeNode(node *yaml.Node, merge *yaml.Node) { if node.Kind != merge.Kind { - *node = merge + *node = *clone(merge) return } switch node.Kind { @@ -178,7 +191,7 @@ func mergeNode(node *yaml.Node, merge yaml.Node) { // mergeMappingNode will perform a shallow merge of the merge node into the main // node. -func mergeMappingNode(node *yaml.Node, merge yaml.Node) { +func mergeMappingNode(node *yaml.Node, merge *yaml.Node) { NextKey: for i := 0; i < len(merge.Content); i += 2 { mergeKey := merge.Content[i].Value @@ -187,16 +200,39 @@ NextKey: for j := 0; j < len(node.Content); j += 2 { nodeKey := node.Content[j].Value if nodeKey == mergeKey { - mergeNode(node.Content[j+1], *mergeValue) + mergeNode(node.Content[j+1], mergeValue) continue NextKey } } - node.Content = append(node.Content, merge.Content[i], mergeValue) + node.Content = append(node.Content, merge.Content[i], clone(mergeValue)) } } // mergeSequenceNode will append the merge node's content to the original node. -func mergeSequenceNode(node *yaml.Node, merge yaml.Node) { - node.Content = append(node.Content, merge.Content...) +func mergeSequenceNode(node *yaml.Node, merge *yaml.Node) { + node.Content = append(node.Content, clone(merge).Content...) +} + +func clone(node *yaml.Node) *yaml.Node { + newNode := &yaml.Node{ + Kind: node.Kind, + Style: node.Style, + Tag: node.Tag, + Value: node.Value, + Anchor: node.Anchor, + HeadComment: node.HeadComment, + LineComment: node.LineComment, + FootComment: node.FootComment, + } + if node.Alias != nil { + newNode.Alias = clone(node.Alias) + } + if node.Content != nil { + newNode.Content = make([]*yaml.Node, len(node.Content)) + for i, child := range node.Content { + newNode.Content[i] = clone(child) + } + } + return newNode } diff --git a/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/compare.go b/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/compare.go index 1aa8d2f..33dff6a 100644 --- a/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/compare.go +++ b/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/compare.go @@ -18,7 +18,8 @@ func Compare(title string, y1 *yaml.Node, y2 yaml.Node) (*Overlay, error) { } return &Overlay{ - Version: "1.0.0", + Version: "1.0.0", + JSONPathVersion: "rfc9535", Info: Info{ Title: title, Version: "0.0.0", diff --git a/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/jsonpath.go b/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/jsonpath.go new file mode 100644 index 0000000..d43617c --- /dev/null +++ b/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/jsonpath.go @@ -0,0 +1,47 @@ +package overlay + +import ( + "fmt" + "github.com/speakeasy-api/jsonpath/pkg/jsonpath" + "github.com/speakeasy-api/jsonpath/pkg/jsonpath/config" + "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" + "gopkg.in/yaml.v3" +) + +type Queryable interface { + Query(root *yaml.Node) []*yaml.Node +} + +type yamlPathQueryable struct { + path *yamlpath.Path +} + +func (y yamlPathQueryable) Query(root *yaml.Node) []*yaml.Node { + if y.path == nil { + return []*yaml.Node{} + } + // errors aren't actually possible from yamlpath. + result, _ := y.path.Find(root) + return result +} + +func (o *Overlay) NewPath(target string, warnings *[]string) (Queryable, error) { + rfcJSONPath, rfcJSONPathErr := jsonpath.NewPath(target, config.WithPropertyNameExtension()) + if o.UsesRFC9535() { + return rfcJSONPath, rfcJSONPathErr + } + if rfcJSONPathErr != nil && warnings != nil { + *warnings = append(*warnings, fmt.Sprintf("invalid rfc9535 jsonpath %s: %s\nThis will be treated as an error in the future. Please fix and opt into the new implementation with `\"x-speakeasy-jsonpath\": rfc9535` in the root of your overlay. See overlay.speakeasy.com for an implementation playground.", target, rfcJSONPathErr.Error())) + } + + path, err := yamlpath.NewPath(target) + return mustExecute(path), err +} + +func (o *Overlay) UsesRFC9535() bool { + return o.JSONPathVersion == "rfc9535" +} + +func mustExecute(path *yamlpath.Path) yamlPathQueryable { + return yamlPathQueryable{path} +} diff --git a/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/schema.go b/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/schema.go index db38422..f4408f0 100644 --- a/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/schema.go +++ b/vendor/github.com/speakeasy-api/openapi-overlay/pkg/overlay/schema.go @@ -13,10 +13,12 @@ type Extensions map[string]any type Overlay struct { Extensions `yaml:"-,inline"` - // Version is the version of the overlay configuration. As the RFC was never - // really ratifies, this value does not mean much. + // Version is the version of the overlay configuration. This is only ever expected to be 1.0.0 Version string `yaml:"overlay"` + // JSONPathVersion should be set to rfc9535, and is used for backwards compatability purposes + JSONPathVersion string `yaml:"x-speakeasy-jsonpath,omitempty"` + // Info describes the metadata for the overlay. Info Info `yaml:"info"` diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/vendor/github.com/stretchr/testify/assert/assertion_compare.go index 7e19eba..ffb24e8 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_compare.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_compare.go @@ -390,7 +390,8 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) + failMessage := fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2) + return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, failMessage, msgAndArgs...) } // GreaterOrEqual asserts that the first element is greater than or equal to the second @@ -403,7 +404,8 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) + failMessage := fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2) + return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, failMessage, msgAndArgs...) } // Less asserts that the first element is less than the second @@ -415,7 +417,8 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) + failMessage := fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2) + return compareTwoValues(t, e1, e2, []compareResult{compareLess}, failMessage, msgAndArgs...) } // LessOrEqual asserts that the first element is less than or equal to the second @@ -428,7 +431,8 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) + failMessage := fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2) + return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, failMessage, msgAndArgs...) } // Positive asserts that the specified element is positive @@ -440,7 +444,8 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { h.Helper() } zero := reflect.Zero(reflect.TypeOf(e)) - return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, "\"%v\" is not positive", msgAndArgs...) + failMessage := fmt.Sprintf("\"%v\" is not positive", e) + return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, failMessage, msgAndArgs...) } // Negative asserts that the specified element is negative @@ -452,7 +457,8 @@ func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { h.Helper() } zero := reflect.Zero(reflect.TypeOf(e)) - return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, "\"%v\" is not negative", msgAndArgs...) + failMessage := fmt.Sprintf("\"%v\" is not negative", e) + return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, failMessage, msgAndArgs...) } func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool { @@ -468,11 +474,11 @@ func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedCompare compareResult, isComparable := compare(e1, e2, e1Kind) if !isComparable { - return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...) + return Fail(t, fmt.Sprintf(`Can not compare type "%T"`, e1), msgAndArgs...) } if !containsValue(allowedComparesResults, compareResult) { - return Fail(t, fmt.Sprintf(failMessage, e1, e2), msgAndArgs...) + return Fail(t, failMessage, msgAndArgs...) } return true diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go index 1906341..c592f6a 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_format.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -50,10 +50,19 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) } -// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. +// Emptyf asserts that the given value is "empty". +// +// [Zero values] are "empty". +// +// Arrays are "empty" if every element is the zero value of the type (stricter than "empty"). +// +// Slices, maps and channels with zero length are "empty". +// +// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty". // // assert.Emptyf(t, obj, "error message %s", "formatted") +// +// [Zero values]: https://go.dev/ref/spec#The_zero_value func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -117,10 +126,8 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri // Errorf asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if assert.Errorf(t, err, "error message %s", "formatted") { -// assert.Equal(t, expectedErrorf, err) -// } +// actualObj, err := SomeFunction() +// assert.Errorf(t, err, "error message %s", "formatted") func Errorf(t TestingT, err error, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -438,7 +445,19 @@ func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interf return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...) } +// IsNotTypef asserts that the specified objects are not of the same type. +// +// assert.IsNotTypef(t, &NotMyStruct{}, &MyStruct{}, "error message %s", "formatted") +func IsNotTypef(t TestingT, theType interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return IsNotType(t, theType, object, append([]interface{}{msg}, args...)...) +} + // IsTypef asserts that the specified objects are of the same type. +// +// assert.IsTypef(t, &MyStruct{}, &MyStruct{}, "error message %s", "formatted") func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -585,8 +604,7 @@ func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg str return NotElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) } -// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. +// NotEmptyf asserts that the specified object is NOT [Empty]. // // if assert.NotEmptyf(t, obj, "error message %s", "formatted") { // assert.Equal(t, "two", obj[1]) @@ -693,12 +711,15 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...) } -// NotSubsetf asserts that the specified list(array, slice...) or map does NOT -// contain all elements given in the specified subset list(array, slice...) or -// map. +// NotSubsetf asserts that the list (array, slice, or map) does NOT contain all +// elements given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted") // assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +// assert.NotSubsetf(t, [1, 3, 4], {1: "one", 2: "two"}, "error message %s", "formatted") +// assert.NotSubsetf(t, {"x": 1, "y": 2}, ["z"], "error message %s", "formatted") func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -782,11 +803,15 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg return Same(t, expected, actual, append([]interface{}{msg}, args...)...) } -// Subsetf asserts that the specified list(array, slice...) or map contains all -// elements given in the specified subset list(array, slice...) or map. +// Subsetf asserts that the list (array, slice, or map) contains all elements +// given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted") // assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +// assert.Subsetf(t, [1, 2, 3], {1: "one", 2: "two"}, "error message %s", "formatted") +// assert.Subsetf(t, {"x": 1, "y": 2}, ["x"], "error message %s", "formatted") func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index 2162908..58db928 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -92,10 +92,19 @@ func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg st return ElementsMatchf(a.t, listA, listB, msg, args...) } -// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. +// Empty asserts that the given value is "empty". +// +// [Zero values] are "empty". +// +// Arrays are "empty" if every element is the zero value of the type (stricter than "empty"). +// +// Slices, maps and channels with zero length are "empty". +// +// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty". // // a.Empty(obj) +// +// [Zero values]: https://go.dev/ref/spec#The_zero_value func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -103,10 +112,19 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { return Empty(a.t, object, msgAndArgs...) } -// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. +// Emptyf asserts that the given value is "empty". +// +// [Zero values] are "empty". +// +// Arrays are "empty" if every element is the zero value of the type (stricter than "empty"). +// +// Slices, maps and channels with zero length are "empty". +// +// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty". // // a.Emptyf(obj, "error message %s", "formatted") +// +// [Zero values]: https://go.dev/ref/spec#The_zero_value func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -224,10 +242,8 @@ func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string // Error asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if a.Error(err) { -// assert.Equal(t, expectedError, err) -// } +// actualObj, err := SomeFunction() +// a.Error(err) func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -297,10 +313,8 @@ func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...inter // Errorf asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if a.Errorf(err, "error message %s", "formatted") { -// assert.Equal(t, expectedErrorf, err) -// } +// actualObj, err := SomeFunction() +// a.Errorf(err, "error message %s", "formatted") func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -868,7 +882,29 @@ func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...in return IsNonIncreasingf(a.t, object, msg, args...) } +// IsNotType asserts that the specified objects are not of the same type. +// +// a.IsNotType(&NotMyStruct{}, &MyStruct{}) +func (a *Assertions) IsNotType(theType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsNotType(a.t, theType, object, msgAndArgs...) +} + +// IsNotTypef asserts that the specified objects are not of the same type. +// +// a.IsNotTypef(&NotMyStruct{}, &MyStruct{}, "error message %s", "formatted") +func (a *Assertions) IsNotTypef(theType interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsNotTypef(a.t, theType, object, msg, args...) +} + // IsType asserts that the specified objects are of the same type. +// +// a.IsType(&MyStruct{}, &MyStruct{}) func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -877,6 +913,8 @@ func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAnd } // IsTypef asserts that the specified objects are of the same type. +// +// a.IsTypef(&MyStruct{}, &MyStruct{}, "error message %s", "formatted") func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1162,8 +1200,7 @@ func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg return NotElementsMatchf(a.t, listA, listB, msg, args...) } -// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. +// NotEmpty asserts that the specified object is NOT [Empty]. // // if a.NotEmpty(obj) { // assert.Equal(t, "two", obj[1]) @@ -1175,8 +1212,7 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) boo return NotEmpty(a.t, object, msgAndArgs...) } -// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. +// NotEmptyf asserts that the specified object is NOT [Empty]. // // if a.NotEmptyf(obj, "error message %s", "formatted") { // assert.Equal(t, "two", obj[1]) @@ -1378,12 +1414,15 @@ func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg stri return NotSamef(a.t, expected, actual, msg, args...) } -// NotSubset asserts that the specified list(array, slice...) or map does NOT -// contain all elements given in the specified subset list(array, slice...) or -// map. +// NotSubset asserts that the list (array, slice, or map) does NOT contain all +// elements given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // a.NotSubset([1, 3, 4], [1, 2]) // a.NotSubset({"x": 1, "y": 2}, {"z": 3}) +// a.NotSubset([1, 3, 4], {1: "one", 2: "two"}) +// a.NotSubset({"x": 1, "y": 2}, ["z"]) func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1391,12 +1430,15 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs return NotSubset(a.t, list, subset, msgAndArgs...) } -// NotSubsetf asserts that the specified list(array, slice...) or map does NOT -// contain all elements given in the specified subset list(array, slice...) or -// map. +// NotSubsetf asserts that the list (array, slice, or map) does NOT contain all +// elements given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted") // a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +// a.NotSubsetf([1, 3, 4], {1: "one", 2: "two"}, "error message %s", "formatted") +// a.NotSubsetf({"x": 1, "y": 2}, ["z"], "error message %s", "formatted") func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1556,11 +1598,15 @@ func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, return Samef(a.t, expected, actual, msg, args...) } -// Subset asserts that the specified list(array, slice...) or map contains all -// elements given in the specified subset list(array, slice...) or map. +// Subset asserts that the list (array, slice, or map) contains all elements +// given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // a.Subset([1, 2, 3], [1, 2]) // a.Subset({"x": 1, "y": 2}, {"x": 1}) +// a.Subset([1, 2, 3], {1: "one", 2: "two"}) +// a.Subset({"x": 1, "y": 2}, ["x"]) func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1568,11 +1614,15 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ... return Subset(a.t, list, subset, msgAndArgs...) } -// Subsetf asserts that the specified list(array, slice...) or map contains all -// elements given in the specified subset list(array, slice...) or map. +// Subsetf asserts that the list (array, slice, or map) contains all elements +// given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted") // a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +// a.Subsetf([1, 2, 3], {1: "one", 2: "two"}, "error message %s", "formatted") +// a.Subsetf({"x": 1, "y": 2}, ["x"], "error message %s", "formatted") func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/assert/assertion_order.go b/vendor/github.com/stretchr/testify/assert/assertion_order.go index 1d2f718..2fdf80f 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_order.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_order.go @@ -33,7 +33,7 @@ func isOrdered(t TestingT, object interface{}, allowedComparesResults []compareR compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind) if !isComparable { - return Fail(t, fmt.Sprintf("Can not compare type \"%s\" and \"%s\"", reflect.TypeOf(value), reflect.TypeOf(prevValue)), msgAndArgs...) + return Fail(t, fmt.Sprintf(`Can not compare type "%T" and "%T"`, value, prevValue), msgAndArgs...) } if !containsValue(allowedComparesResults, compareResult) { diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index 4e91332..de8de0c 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -210,59 +210,77 @@ the problem actually occurred in calling code.*/ // of each stack frame leading from the current test to the assert call that // failed. func CallerInfo() []string { - var pc uintptr - var ok bool var file string var line int var name string + const stackFrameBufferSize = 10 + pcs := make([]uintptr, stackFrameBufferSize) + callers := []string{} - for i := 0; ; i++ { - pc, file, line, ok = runtime.Caller(i) - if !ok { - // The breaks below failed to terminate the loop, and we ran off the - // end of the call stack. - break - } + offset := 1 - // This is a huge edge case, but it will panic if this is the case, see #180 - if file == "" { - break - } + for { + n := runtime.Callers(offset, pcs) - f := runtime.FuncForPC(pc) - if f == nil { - break - } - name = f.Name() - - // testing.tRunner is the standard library function that calls - // tests. Subtests are called directly by tRunner, without going through - // the Test/Benchmark/Example function that contains the t.Run calls, so - // with subtests we should break when we hit tRunner, without adding it - // to the list of callers. - if name == "testing.tRunner" { + if n == 0 { break } - parts := strings.Split(file, "/") - if len(parts) > 1 { - filename := parts[len(parts)-1] - dir := parts[len(parts)-2] - if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" { - callers = append(callers, fmt.Sprintf("%s:%d", file, line)) + frames := runtime.CallersFrames(pcs[:n]) + + for { + frame, more := frames.Next() + pc = frame.PC + file = frame.File + line = frame.Line + + // This is a huge edge case, but it will panic if this is the case, see #180 + if file == "" { + break } - } - // Drop the package - segments := strings.Split(name, ".") - name = segments[len(segments)-1] - if isTest(name, "Test") || - isTest(name, "Benchmark") || - isTest(name, "Example") { - break + f := runtime.FuncForPC(pc) + if f == nil { + break + } + name = f.Name() + + // testing.tRunner is the standard library function that calls + // tests. Subtests are called directly by tRunner, without going through + // the Test/Benchmark/Example function that contains the t.Run calls, so + // with subtests we should break when we hit tRunner, without adding it + // to the list of callers. + if name == "testing.tRunner" { + break + } + + parts := strings.Split(file, "/") + if len(parts) > 1 { + filename := parts[len(parts)-1] + dir := parts[len(parts)-2] + if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" { + callers = append(callers, fmt.Sprintf("%s:%d", file, line)) + } + } + + // Drop the package + dotPos := strings.LastIndexByte(name, '.') + name = name[dotPos+1:] + if isTest(name, "Test") || + isTest(name, "Benchmark") || + isTest(name, "Example") { + break + } + + if !more { + break + } } + + // Next batch + offset += cap(pcs) } return callers @@ -437,17 +455,34 @@ func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, return true } +func isType(expectedType, object interface{}) bool { + return ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) +} + // IsType asserts that the specified objects are of the same type. -func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { +// +// assert.IsType(t, &MyStruct{}, &MyStruct{}) +func IsType(t TestingT, expectedType, object interface{}, msgAndArgs ...interface{}) bool { + if isType(expectedType, object) { + return true + } if h, ok := t.(tHelper); ok { h.Helper() } + return Fail(t, fmt.Sprintf("Object expected to be of type %T, but was %T", expectedType, object), msgAndArgs...) +} - if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) { - return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...) +// IsNotType asserts that the specified objects are not of the same type. +// +// assert.IsNotType(t, &NotMyStruct{}, &MyStruct{}) +func IsNotType(t TestingT, theType, object interface{}, msgAndArgs ...interface{}) bool { + if !isType(theType, object) { + return true } - - return true + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Fail(t, fmt.Sprintf("Object type expected to be different than %T", theType), msgAndArgs...) } // Equal asserts that two objects are equal. @@ -475,7 +510,6 @@ func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) } return true - } // validateEqualArgs checks whether provided arguments can be safely used in the @@ -510,8 +544,9 @@ func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) b if !same { // both are pointers but not the same type & pointing to the same address return Fail(t, fmt.Sprintf("Not same: \n"+ - "expected: %p %#v\n"+ - "actual : %p %#v", expected, expected, actual, actual), msgAndArgs...) + "expected: %p %#[1]v\n"+ + "actual : %p %#[2]v", + expected, actual), msgAndArgs...) } return true @@ -530,14 +565,14 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} same, ok := samePointers(expected, actual) if !ok { - //fails when the arguments are not pointers + // fails when the arguments are not pointers return !(Fail(t, "Both arguments must be pointers", msgAndArgs...)) } if same { return Fail(t, fmt.Sprintf( - "Expected and actual point to the same object: %p %#v", - expected, expected), msgAndArgs...) + "Expected and actual point to the same object: %p %#[1]v", + expected), msgAndArgs...) } return true } @@ -549,7 +584,7 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} func samePointers(first, second interface{}) (same bool, ok bool) { firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second) if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr { - return false, false //not both are pointers + return false, false // not both are pointers } firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second) @@ -610,7 +645,6 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa } return true - } // EqualExportedValues asserts that the types of two objects are equal and their public @@ -665,7 +699,6 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} } return Equal(t, expected, actual, msgAndArgs...) - } // NotNil asserts that the specified object is not nil. @@ -715,37 +748,45 @@ func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { // isEmpty gets whether the specified object is considered empty or not. func isEmpty(object interface{}) bool { - // get nil case out of the way if object == nil { return true } - objValue := reflect.ValueOf(object) + return isEmptyValue(reflect.ValueOf(object)) +} +// isEmptyValue gets whether the specified reflect.Value is considered empty or not. +func isEmptyValue(objValue reflect.Value) bool { + if objValue.IsZero() { + return true + } + // Special cases of non-zero values that we consider empty switch objValue.Kind() { // collection types are empty when they have no element + // Note: array types are empty when they match their zero-initialized state. case reflect.Chan, reflect.Map, reflect.Slice: return objValue.Len() == 0 - // pointers are empty if nil or if the value they point to is empty + // non-nil pointers are empty if the value they point to is empty case reflect.Ptr: - if objValue.IsNil() { - return true - } - deref := objValue.Elem().Interface() - return isEmpty(deref) - // for all other types, compare against the zero value - // array types are empty when they match their zero-initialized state - default: - zero := reflect.Zero(objValue.Type()) - return reflect.DeepEqual(object, zero.Interface()) + return isEmptyValue(objValue.Elem()) } + return false } -// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. +// Empty asserts that the given value is "empty". +// +// [Zero values] are "empty". +// +// Arrays are "empty" if every element is the zero value of the type (stricter than "empty"). +// +// Slices, maps and channels with zero length are "empty". +// +// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty". // // assert.Empty(t, obj) +// +// [Zero values]: https://go.dev/ref/spec#The_zero_value func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { pass := isEmpty(object) if !pass { @@ -756,11 +797,9 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { } return pass - } -// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. +// NotEmpty asserts that the specified object is NOT [Empty]. // // if assert.NotEmpty(t, obj) { // assert.Equal(t, "two", obj[1]) @@ -775,7 +814,6 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { } return pass - } // getLen tries to get the length of an object. @@ -819,7 +857,6 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { } return true - } // False asserts that the specified value is false. @@ -834,7 +871,6 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { } return true - } // NotEqual asserts that the specified values are NOT equal. @@ -857,7 +893,6 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{ } return true - } // NotEqualValues asserts that two objects are not equal even when converted to the same type @@ -880,7 +915,6 @@ func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...inte // return (true, false) if element was not found. // return (true, true) if element was found. func containsElement(list interface{}, element interface{}) (ok, found bool) { - listValue := reflect.ValueOf(list) listType := reflect.TypeOf(list) if listType == nil { @@ -915,7 +949,6 @@ func containsElement(list interface{}, element interface{}) (ok, found bool) { } } return true, false - } // Contains asserts that the specified string, list(array, slice...) or map contains the @@ -938,7 +971,6 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo } return true - } // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the @@ -961,14 +993,17 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) } return true - } -// Subset asserts that the specified list(array, slice...) or map contains all -// elements given in the specified subset list(array, slice...) or map. +// Subset asserts that the list (array, slice, or map) contains all elements +// given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // assert.Subset(t, [1, 2, 3], [1, 2]) // assert.Subset(t, {"x": 1, "y": 2}, {"x": 1}) +// assert.Subset(t, [1, 2, 3], {1: "one", 2: "two"}) +// assert.Subset(t, {"x": 1, "y": 2}, ["x"]) func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { if h, ok := t.(tHelper); ok { h.Helper() @@ -983,7 +1018,7 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok } subsetKind := reflect.TypeOf(subset).Kind() - if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map { + if subsetKind != reflect.Array && subsetKind != reflect.Slice && subsetKind != reflect.Map { return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) } @@ -1007,6 +1042,13 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok } subsetList := reflect.ValueOf(subset) + if subsetKind == reflect.Map { + keys := make([]interface{}, subsetList.Len()) + for idx, key := range subsetList.MapKeys() { + keys[idx] = key.Interface() + } + subsetList = reflect.ValueOf(keys) + } for i := 0; i < subsetList.Len(); i++ { element := subsetList.Index(i).Interface() ok, found := containsElement(list, element) @@ -1021,12 +1063,15 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok return true } -// NotSubset asserts that the specified list(array, slice...) or map does NOT -// contain all elements given in the specified subset list(array, slice...) or -// map. +// NotSubset asserts that the list (array, slice, or map) does NOT contain all +// elements given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // assert.NotSubset(t, [1, 3, 4], [1, 2]) // assert.NotSubset(t, {"x": 1, "y": 2}, {"z": 3}) +// assert.NotSubset(t, [1, 3, 4], {1: "one", 2: "two"}) +// assert.NotSubset(t, {"x": 1, "y": 2}, ["z"]) func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1041,7 +1086,7 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) } subsetKind := reflect.TypeOf(subset).Kind() - if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map { + if subsetKind != reflect.Array && subsetKind != reflect.Slice && subsetKind != reflect.Map { return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) } @@ -1065,11 +1110,18 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) } subsetList := reflect.ValueOf(subset) + if subsetKind == reflect.Map { + keys := make([]interface{}, subsetList.Len()) + for idx, key := range subsetList.MapKeys() { + keys[idx] = key.Interface() + } + subsetList = reflect.ValueOf(keys) + } for i := 0; i < subsetList.Len(); i++ { element := subsetList.Index(i).Interface() ok, found := containsElement(list, element) if !ok { - return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) + return Fail(t, fmt.Sprintf("%q could not be applied builtin len()", list), msgAndArgs...) } if !found { return true @@ -1591,10 +1643,8 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { // Error asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if assert.Error(t, err) { -// assert.Equal(t, expectedError, err) -// } +// actualObj, err := SomeFunction() +// assert.Error(t, err) func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { if err == nil { if h, ok := t.(tHelper); ok { @@ -1667,7 +1717,6 @@ func matchRegexp(rx interface{}, str interface{}) bool { default: return r.MatchString(fmt.Sprint(v)) } - } // Regexp asserts that a specified regexp matches a string. @@ -1703,7 +1752,6 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf } return !match - } // Zero asserts that i is the zero value for its type. @@ -1814,6 +1862,11 @@ func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{ return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...) } + // Shortcut if same bytes + if actual == expected { + return true + } + if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil { return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...) } @@ -1832,6 +1885,11 @@ func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{ return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid yaml.\nYAML parsing error: '%s'", expected, err.Error()), msgAndArgs...) } + // Shortcut if same bytes + if actual == expected { + return true + } + if err := yaml.Unmarshal([]byte(actual), &actualYAMLAsInterface); err != nil { return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid yaml.\nYAML error: '%s'", actual, err.Error()), msgAndArgs...) } @@ -1933,6 +1991,7 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t } ch := make(chan bool, 1) + checkCond := func() { ch <- condition() } timer := time.NewTimer(waitFor) defer timer.Stop() @@ -1940,18 +1999,23 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t ticker := time.NewTicker(tick) defer ticker.Stop() - for tick := ticker.C; ; { + var tickC <-chan time.Time + + // Check the condition once first on the initial call. + go checkCond() + + for { select { case <-timer.C: return Fail(t, "Condition never satisfied", msgAndArgs...) - case <-tick: - tick = nil - go func() { ch <- condition() }() + case <-tickC: + tickC = nil + go checkCond() case v := <-ch: if v { return true } - tick = ticker.C + tickC = ticker.C } } } @@ -1964,6 +2028,9 @@ type CollectT struct { errors []error } +// Helper is like [testing.T.Helper] but does nothing. +func (CollectT) Helper() {} + // Errorf collects the error. func (c *CollectT) Errorf(format string, args ...interface{}) { c.errors = append(c.errors, fmt.Errorf(format, args...)) @@ -2021,35 +2088,42 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time var lastFinishedTickErrs []error ch := make(chan *CollectT, 1) + checkCond := func() { + collect := new(CollectT) + defer func() { + ch <- collect + }() + condition(collect) + } + timer := time.NewTimer(waitFor) defer timer.Stop() ticker := time.NewTicker(tick) defer ticker.Stop() - for tick := ticker.C; ; { + var tickC <-chan time.Time + + // Check the condition once first on the initial call. + go checkCond() + + for { select { case <-timer.C: for _, err := range lastFinishedTickErrs { t.Errorf("%v", err) } return Fail(t, "Condition never satisfied", msgAndArgs...) - case <-tick: - tick = nil - go func() { - collect := new(CollectT) - defer func() { - ch <- collect - }() - condition(collect) - }() + case <-tickC: + tickC = nil + go checkCond() case collect := <-ch: if !collect.failed() { return true } // Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached. lastFinishedTickErrs = collect.errors - tick = ticker.C + tickC = ticker.C } } } @@ -2064,6 +2138,7 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D } ch := make(chan bool, 1) + checkCond := func() { ch <- condition() } timer := time.NewTimer(waitFor) defer timer.Stop() @@ -2071,18 +2146,23 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D ticker := time.NewTicker(tick) defer ticker.Stop() - for tick := ticker.C; ; { + var tickC <-chan time.Time + + // Check the condition once first on the initial call. + go checkCond() + + for { select { case <-timer.C: return true - case <-tick: - tick = nil - go func() { ch <- condition() }() + case <-tickC: + tickC = nil + go checkCond() case v := <-ch: if v { return Fail(t, "Condition satisfied", msgAndArgs...) } - tick = ticker.C + tickC = ticker.C } } } @@ -2100,9 +2180,12 @@ func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { var expectedText string if target != nil { expectedText = target.Error() + if err == nil { + return Fail(t, fmt.Sprintf("Expected error with %q in chain but got nil.", expectedText), msgAndArgs...) + } } - chain := buildErrorChainString(err) + chain := buildErrorChainString(err, false) return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+ "expected: %q\n"+ @@ -2125,7 +2208,7 @@ func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { expectedText = target.Error() } - chain := buildErrorChainString(err) + chain := buildErrorChainString(err, false) return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ "found: %q\n"+ @@ -2143,11 +2226,17 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{ return true } - chain := buildErrorChainString(err) + expectedType := reflect.TypeOf(target).Elem().String() + if err == nil { + return Fail(t, fmt.Sprintf("An error is expected but got nil.\n"+ + "expected: %s", expectedType), msgAndArgs...) + } + + chain := buildErrorChainString(err, true) return Fail(t, fmt.Sprintf("Should be in error chain:\n"+ - "expected: %q\n"+ - "in chain: %s", target, chain, + "expected: %s\n"+ + "in chain: %s", expectedType, chain, ), msgAndArgs...) } @@ -2161,24 +2250,46 @@ func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interfa return true } - chain := buildErrorChainString(err) + chain := buildErrorChainString(err, true) return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ - "found: %q\n"+ - "in chain: %s", target, chain, + "found: %s\n"+ + "in chain: %s", reflect.TypeOf(target).Elem().String(), chain, ), msgAndArgs...) } -func buildErrorChainString(err error) string { +func unwrapAll(err error) (errs []error) { + errs = append(errs, err) + switch x := err.(type) { + case interface{ Unwrap() error }: + err = x.Unwrap() + if err == nil { + return + } + errs = append(errs, unwrapAll(err)...) + case interface{ Unwrap() []error }: + for _, err := range x.Unwrap() { + errs = append(errs, unwrapAll(err)...) + } + } + return +} + +func buildErrorChainString(err error, withType bool) string { if err == nil { return "" } - e := errors.Unwrap(err) - chain := fmt.Sprintf("%q", err.Error()) - for e != nil { - chain += fmt.Sprintf("\n\t%q", e.Error()) - e = errors.Unwrap(e) + var chain string + errs := unwrapAll(err) + for i := range errs { + if i != 0 { + chain += "\n\t" + } + chain += fmt.Sprintf("%q", errs[i].Error()) + if withType { + chain += fmt.Sprintf(" (%T)", errs[i]) + } } return chain } diff --git a/vendor/github.com/stretchr/testify/assert/doc.go b/vendor/github.com/stretchr/testify/assert/doc.go index 4953981..a0b953a 100644 --- a/vendor/github.com/stretchr/testify/assert/doc.go +++ b/vendor/github.com/stretchr/testify/assert/doc.go @@ -1,5 +1,9 @@ // Package assert provides a set of comprehensive testing tools for use with the normal Go testing system. // +// # Note +// +// All functions in this package return a bool value indicating whether the assertion has passed. +// // # Example Usage // // The following is a complete example using assert in a standard test function: diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go index 861ed4b..5a6bb75 100644 --- a/vendor/github.com/stretchr/testify/assert/http_assertions.go +++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go @@ -138,7 +138,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, contains := strings.Contains(body, fmt.Sprint(str)) if !contains { - Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body), msgAndArgs...) + Fail(t, fmt.Sprintf("Expected response body for %q to contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...) } return contains @@ -158,7 +158,7 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url strin contains := strings.Contains(body, fmt.Sprint(str)) if contains { - Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body), msgAndArgs...) + Fail(t, fmt.Sprintf("Expected response body for %q to NOT contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...) } return !contains diff --git a/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go b/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go index baa0cc7..5a74c4f 100644 --- a/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go +++ b/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go @@ -1,5 +1,4 @@ //go:build testify_yaml_custom && !testify_yaml_fail && !testify_yaml_default -// +build testify_yaml_custom,!testify_yaml_fail,!testify_yaml_default // Package yaml is an implementation of YAML functions that calls a pluggable implementation. // diff --git a/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go b/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go index b83c6cf..0bae80e 100644 --- a/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go +++ b/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go @@ -1,5 +1,4 @@ //go:build !testify_yaml_fail && !testify_yaml_custom -// +build !testify_yaml_fail,!testify_yaml_custom // Package yaml is just an indirection to handle YAML deserialization. // diff --git a/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go b/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go index e78f7df..8041803 100644 --- a/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go +++ b/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go @@ -1,5 +1,4 @@ //go:build testify_yaml_fail && !testify_yaml_custom && !testify_yaml_default -// +build testify_yaml_fail,!testify_yaml_custom,!testify_yaml_default // Package yaml is an implementation of YAML functions that always fail. // diff --git a/vendor/github.com/stretchr/testify/mock/mock.go b/vendor/github.com/stretchr/testify/mock/mock.go index eb5682d..efc89de 100644 --- a/vendor/github.com/stretchr/testify/mock/mock.go +++ b/vendor/github.com/stretchr/testify/mock/mock.go @@ -208,9 +208,16 @@ func (c *Call) On(methodName string, arguments ...interface{}) *Call { return c.Parent.On(methodName, arguments...) } -// Unset removes a mock handler from being called. +// Unset removes all mock handlers that satisfy the call instance arguments from being +// called. Only supported on call instances with static input arguments. // -// test.On("func", mock.Anything).Unset() +// For example, the only handler remaining after the following would be "MyMethod(2, 2)": +// +// Mock. +// On("MyMethod", 2, 2).Return(0). +// On("MyMethod", 3, 3).Return(0). +// On("MyMethod", Anything, Anything).Return(0) +// Mock.On("MyMethod", 3, 3).Unset() func (c *Call) Unset() *Call { var unlockOnce sync.Once @@ -331,7 +338,10 @@ func (m *Mock) TestData() objx.Map { Setting expectations */ -// Test sets the test struct variable of the mock object +// Test sets the [TestingT] on which errors will be reported, otherwise errors +// will cause a panic. +// Test should not be called on an object that is going to be used in a +// goroutine other than the one running the test function. func (m *Mock) Test(t TestingT) { m.mutex.Lock() defer m.mutex.Unlock() @@ -494,7 +504,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen // expected call found, but it has already been called with repeatable times if call != nil { m.mutex.Unlock() - m.fail("\nassert: mock: The method has been called over %d times.\n\tEither do one more Mock.On(\"%s\").Return(...), or remove extra call.\n\tThis call was unexpected:\n\t\t%s\n\tat: %s", call.totalCalls, methodName, callString(methodName, arguments, true), assert.CallerInfo()) + m.fail("\nassert: mock: The method has been called over %d times.\n\tEither do one more Mock.On(%#v).Return(...), or remove extra call.\n\tThis call was unexpected:\n\t\t%s\n\tat: %s", call.totalCalls, methodName, callString(methodName, arguments, true), assert.CallerInfo()) } // we have to fail here - because we don't know what to do // as the return arguments. This is because: @@ -514,7 +524,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen assert.CallerInfo(), ) } else { - m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo()) + m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(%#v).Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo()) } } @@ -661,7 +671,7 @@ func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls actualCalls++ } } - return assert.Equal(t, expectedCalls, actualCalls, fmt.Sprintf("Expected number of calls (%d) does not match the actual number of calls (%d).", expectedCalls, actualCalls)) + return assert.Equal(t, expectedCalls, actualCalls, fmt.Sprintf("Expected number of calls (%d) of method %s does not match the actual number of calls (%d).", expectedCalls, methodName, actualCalls)) } // AssertCalled asserts that the method was called. diff --git a/vendor/github.com/stretchr/testify/require/doc.go b/vendor/github.com/stretchr/testify/require/doc.go index 9684347..c8e3f94 100644 --- a/vendor/github.com/stretchr/testify/require/doc.go +++ b/vendor/github.com/stretchr/testify/require/doc.go @@ -23,6 +23,8 @@ // // The `require` package have same global functions as in the `assert` package, // but instead of returning a boolean result they call `t.FailNow()`. +// A consequence of this is that it must be called from the goroutine running +// the test function, not from other goroutines created during the test. // // Every assertion function also takes an optional string message as the final argument, // allowing custom error messages to be appended to the message the assertion method outputs. diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go index d892195..2d02f9b 100644 --- a/vendor/github.com/stretchr/testify/require/require.go +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -117,10 +117,19 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string t.FailNow() } -// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. +// Empty asserts that the given value is "empty". +// +// [Zero values] are "empty". +// +// Arrays are "empty" if every element is the zero value of the type (stricter than "empty"). +// +// Slices, maps and channels with zero length are "empty". +// +// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty". // // require.Empty(t, obj) +// +// [Zero values]: https://go.dev/ref/spec#The_zero_value func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -131,10 +140,19 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { t.FailNow() } -// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. +// Emptyf asserts that the given value is "empty". +// +// [Zero values] are "empty". +// +// Arrays are "empty" if every element is the zero value of the type (stricter than "empty"). +// +// Slices, maps and channels with zero length are "empty". +// +// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty". // // require.Emptyf(t, obj, "error message %s", "formatted") +// +// [Zero values]: https://go.dev/ref/spec#The_zero_value func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -279,10 +297,8 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar // Error asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if require.Error(t, err) { -// require.Equal(t, expectedError, err) -// } +// actualObj, err := SomeFunction() +// require.Error(t, err) func Error(t TestingT, err error, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -373,10 +389,8 @@ func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface // Errorf asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if require.Errorf(t, err, "error message %s", "formatted") { -// require.Equal(t, expectedErrorf, err) -// } +// actualObj, err := SomeFunction() +// require.Errorf(t, err, "error message %s", "formatted") func Errorf(t TestingT, err error, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1097,7 +1111,35 @@ func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interf t.FailNow() } +// IsNotType asserts that the specified objects are not of the same type. +// +// require.IsNotType(t, &NotMyStruct{}, &MyStruct{}) +func IsNotType(t TestingT, theType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNotType(t, theType, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsNotTypef asserts that the specified objects are not of the same type. +// +// require.IsNotTypef(t, &NotMyStruct{}, &MyStruct{}, "error message %s", "formatted") +func IsNotTypef(t TestingT, theType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNotTypef(t, theType, object, msg, args...) { + return + } + t.FailNow() +} + // IsType asserts that the specified objects are of the same type. +// +// require.IsType(t, &MyStruct{}, &MyStruct{}) func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1109,6 +1151,8 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs } // IsTypef asserts that the specified objects are of the same type. +// +// require.IsTypef(t, &MyStruct{}, &MyStruct{}, "error message %s", "formatted") func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1469,8 +1513,7 @@ func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg str t.FailNow() } -// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. +// NotEmpty asserts that the specified object is NOT [Empty]. // // if require.NotEmpty(t, obj) { // require.Equal(t, "two", obj[1]) @@ -1485,8 +1528,7 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { t.FailNow() } -// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. +// NotEmptyf asserts that the specified object is NOT [Empty]. // // if require.NotEmptyf(t, obj, "error message %s", "formatted") { // require.Equal(t, "two", obj[1]) @@ -1745,12 +1787,15 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, t.FailNow() } -// NotSubset asserts that the specified list(array, slice...) or map does NOT -// contain all elements given in the specified subset list(array, slice...) or -// map. +// NotSubset asserts that the list (array, slice, or map) does NOT contain all +// elements given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // require.NotSubset(t, [1, 3, 4], [1, 2]) // require.NotSubset(t, {"x": 1, "y": 2}, {"z": 3}) +// require.NotSubset(t, [1, 3, 4], {1: "one", 2: "two"}) +// require.NotSubset(t, {"x": 1, "y": 2}, ["z"]) func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1761,12 +1806,15 @@ func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...i t.FailNow() } -// NotSubsetf asserts that the specified list(array, slice...) or map does NOT -// contain all elements given in the specified subset list(array, slice...) or -// map. +// NotSubsetf asserts that the list (array, slice, or map) does NOT contain all +// elements given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // require.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted") // require.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +// require.NotSubsetf(t, [1, 3, 4], {1: "one", 2: "two"}, "error message %s", "formatted") +// require.NotSubsetf(t, {"x": 1, "y": 2}, ["z"], "error message %s", "formatted") func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1971,11 +2019,15 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg t.FailNow() } -// Subset asserts that the specified list(array, slice...) or map contains all -// elements given in the specified subset list(array, slice...) or map. +// Subset asserts that the list (array, slice, or map) contains all elements +// given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // require.Subset(t, [1, 2, 3], [1, 2]) // require.Subset(t, {"x": 1, "y": 2}, {"x": 1}) +// require.Subset(t, [1, 2, 3], {1: "one", 2: "two"}) +// require.Subset(t, {"x": 1, "y": 2}, ["x"]) func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1986,11 +2038,15 @@ func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...inte t.FailNow() } -// Subsetf asserts that the specified list(array, slice...) or map contains all -// elements given in the specified subset list(array, slice...) or map. +// Subsetf asserts that the list (array, slice, or map) contains all elements +// given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // require.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted") // require.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +// require.Subsetf(t, [1, 2, 3], {1: "one", 2: "two"}, "error message %s", "formatted") +// require.Subsetf(t, {"x": 1, "y": 2}, ["x"], "error message %s", "formatted") func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go index 1bd8730..e6f7e94 100644 --- a/vendor/github.com/stretchr/testify/require/require_forward.go +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -93,10 +93,19 @@ func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg st ElementsMatchf(a.t, listA, listB, msg, args...) } -// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. +// Empty asserts that the given value is "empty". +// +// [Zero values] are "empty". +// +// Arrays are "empty" if every element is the zero value of the type (stricter than "empty"). +// +// Slices, maps and channels with zero length are "empty". +// +// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty". // // a.Empty(obj) +// +// [Zero values]: https://go.dev/ref/spec#The_zero_value func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -104,10 +113,19 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { Empty(a.t, object, msgAndArgs...) } -// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either -// a slice or a channel with len == 0. +// Emptyf asserts that the given value is "empty". +// +// [Zero values] are "empty". +// +// Arrays are "empty" if every element is the zero value of the type (stricter than "empty"). +// +// Slices, maps and channels with zero length are "empty". +// +// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty". // // a.Emptyf(obj, "error message %s", "formatted") +// +// [Zero values]: https://go.dev/ref/spec#The_zero_value func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -225,10 +243,8 @@ func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string // Error asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if a.Error(err) { -// assert.Equal(t, expectedError, err) -// } +// actualObj, err := SomeFunction() +// a.Error(err) func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -298,10 +314,8 @@ func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...inter // Errorf asserts that a function returned an error (i.e. not `nil`). // -// actualObj, err := SomeFunction() -// if a.Errorf(err, "error message %s", "formatted") { -// assert.Equal(t, expectedErrorf, err) -// } +// actualObj, err := SomeFunction() +// a.Errorf(err, "error message %s", "formatted") func (a *Assertions) Errorf(err error, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -869,7 +883,29 @@ func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...in IsNonIncreasingf(a.t, object, msg, args...) } +// IsNotType asserts that the specified objects are not of the same type. +// +// a.IsNotType(&NotMyStruct{}, &MyStruct{}) +func (a *Assertions) IsNotType(theType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNotType(a.t, theType, object, msgAndArgs...) +} + +// IsNotTypef asserts that the specified objects are not of the same type. +// +// a.IsNotTypef(&NotMyStruct{}, &MyStruct{}, "error message %s", "formatted") +func (a *Assertions) IsNotTypef(theType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNotTypef(a.t, theType, object, msg, args...) +} + // IsType asserts that the specified objects are of the same type. +// +// a.IsType(&MyStruct{}, &MyStruct{}) func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -878,6 +914,8 @@ func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAnd } // IsTypef asserts that the specified objects are of the same type. +// +// a.IsTypef(&MyStruct{}, &MyStruct{}, "error message %s", "formatted") func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1163,8 +1201,7 @@ func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg NotElementsMatchf(a.t, listA, listB, msg, args...) } -// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. +// NotEmpty asserts that the specified object is NOT [Empty]. // // if a.NotEmpty(obj) { // assert.Equal(t, "two", obj[1]) @@ -1176,8 +1213,7 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { NotEmpty(a.t, object, msgAndArgs...) } -// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either -// a slice or a channel with len == 0. +// NotEmptyf asserts that the specified object is NOT [Empty]. // // if a.NotEmptyf(obj, "error message %s", "formatted") { // assert.Equal(t, "two", obj[1]) @@ -1379,12 +1415,15 @@ func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg stri NotSamef(a.t, expected, actual, msg, args...) } -// NotSubset asserts that the specified list(array, slice...) or map does NOT -// contain all elements given in the specified subset list(array, slice...) or -// map. +// NotSubset asserts that the list (array, slice, or map) does NOT contain all +// elements given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // a.NotSubset([1, 3, 4], [1, 2]) // a.NotSubset({"x": 1, "y": 2}, {"z": 3}) +// a.NotSubset([1, 3, 4], {1: "one", 2: "two"}) +// a.NotSubset({"x": 1, "y": 2}, ["z"]) func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1392,12 +1431,15 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs NotSubset(a.t, list, subset, msgAndArgs...) } -// NotSubsetf asserts that the specified list(array, slice...) or map does NOT -// contain all elements given in the specified subset list(array, slice...) or -// map. +// NotSubsetf asserts that the list (array, slice, or map) does NOT contain all +// elements given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted") // a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +// a.NotSubsetf([1, 3, 4], {1: "one", 2: "two"}, "error message %s", "formatted") +// a.NotSubsetf({"x": 1, "y": 2}, ["z"], "error message %s", "formatted") func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1557,11 +1599,15 @@ func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, Samef(a.t, expected, actual, msg, args...) } -// Subset asserts that the specified list(array, slice...) or map contains all -// elements given in the specified subset list(array, slice...) or map. +// Subset asserts that the list (array, slice, or map) contains all elements +// given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // a.Subset([1, 2, 3], [1, 2]) // a.Subset({"x": 1, "y": 2}, {"x": 1}) +// a.Subset([1, 2, 3], {1: "one", 2: "two"}) +// a.Subset({"x": 1, "y": 2}, ["x"]) func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1569,11 +1615,15 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ... Subset(a.t, list, subset, msgAndArgs...) } -// Subsetf asserts that the specified list(array, slice...) or map contains all -// elements given in the specified subset list(array, slice...) or map. +// Subsetf asserts that the list (array, slice, or map) contains all elements +// given in the subset (array, slice, or map). +// Map elements are key-value pairs unless compared with an array or slice where +// only the map key is evaluated. // // a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted") // a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +// a.Subsetf([1, 2, 3], {1: "one", 2: "two"}, "error message %s", "formatted") +// a.Subsetf({"x": 1, "y": 2}, ["x"], "error message %s", "formatted") func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/suite/stats.go b/vendor/github.com/stretchr/testify/suite/stats.go index 261da37..be4ccd6 100644 --- a/vendor/github.com/stretchr/testify/suite/stats.go +++ b/vendor/github.com/stretchr/testify/suite/stats.go @@ -16,26 +16,30 @@ type TestInformation struct { } func newSuiteInformation() *SuiteInformation { - testStats := make(map[string]*TestInformation) - return &SuiteInformation{ - TestStats: testStats, + TestStats: make(map[string]*TestInformation), } } -func (s SuiteInformation) start(testName string) { +func (s *SuiteInformation) start(testName string) { + if s == nil { + return + } s.TestStats[testName] = &TestInformation{ TestName: testName, Start: time.Now(), } } -func (s SuiteInformation) end(testName string, passed bool) { +func (s *SuiteInformation) end(testName string, passed bool) { + if s == nil { + return + } s.TestStats[testName].End = time.Now() s.TestStats[testName].Passed = passed } -func (s SuiteInformation) Passed() bool { +func (s *SuiteInformation) Passed() bool { for _, stats := range s.TestStats { if !stats.Passed { return false diff --git a/vendor/github.com/stretchr/testify/suite/suite.go b/vendor/github.com/stretchr/testify/suite/suite.go index 18443a9..1b19be3 100644 --- a/vendor/github.com/stretchr/testify/suite/suite.go +++ b/vendor/github.com/stretchr/testify/suite/suite.go @@ -7,6 +7,7 @@ import ( "reflect" "regexp" "runtime/debug" + "strings" "sync" "testing" "time" @@ -15,7 +16,6 @@ import ( "github.com/stretchr/testify/require" ) -var allTestsFilter = func(_, _ string) (bool, error) { return true, nil } var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run") // Suite is a basic testing suite with methods for storing and @@ -116,6 +116,11 @@ func (suite *Suite) Run(name string, subtest func()) bool { }) } +type test = struct { + name string + run func(t *testing.T) +} + // Run takes a testing suite and runs all of the tests attached // to it. func Run(t *testing.T, suite TestingSuite) { @@ -124,45 +129,39 @@ func Run(t *testing.T, suite TestingSuite) { suite.SetT(t) suite.SetS(suite) - var suiteSetupDone bool - var stats *SuiteInformation if _, ok := suite.(WithStats); ok { stats = newSuiteInformation() } - tests := []testing.InternalTest{} + var tests []test methodFinder := reflect.TypeOf(suite) suiteName := methodFinder.Elem().Name() - for i := 0; i < methodFinder.NumMethod(); i++ { - method := methodFinder.Method(i) - - ok, err := methodFilter(method.Name) + var matchMethodRE *regexp.Regexp + if *matchMethod != "" { + var err error + matchMethodRE, err = regexp.Compile(*matchMethod) if err != nil { fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err) os.Exit(1) } + } - if !ok { + for i := 0; i < methodFinder.NumMethod(); i++ { + method := methodFinder.Method(i) + + if !strings.HasPrefix(method.Name, "Test") { continue } - - if !suiteSetupDone { - if stats != nil { - stats.Start = time.Now() - } - - if setupAllSuite, ok := suite.(SetupAllSuite); ok { - setupAllSuite.SetupSuite() - } - - suiteSetupDone = true + // Apply -testify.m filter + if matchMethodRE != nil && !matchMethodRE.MatchString(method.Name) { + continue } - test := testing.InternalTest{ - Name: method.Name, - F: func(t *testing.T) { + test := test{ + name: method.Name, + run: func(t *testing.T) { parentT := suite.T() suite.SetT(t) defer recoverAndFailOnPanic(t) @@ -171,10 +170,7 @@ func Run(t *testing.T, suite TestingSuite) { r := recover() - if stats != nil { - passed := !t.Failed() && r == nil - stats.end(method.Name, passed) - } + stats.end(method.Name, !t.Failed() && r == nil) if afterTestSuite, ok := suite.(AfterTest); ok { afterTestSuite.AfterTest(suiteName, method.Name) @@ -195,59 +191,47 @@ func Run(t *testing.T, suite TestingSuite) { beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name) } - if stats != nil { - stats.start(method.Name) - } + stats.start(method.Name) method.Func.Call([]reflect.Value{reflect.ValueOf(suite)}) }, } tests = append(tests, test) } - if suiteSetupDone { - defer func() { - if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok { - tearDownAllSuite.TearDownSuite() - } - - if suiteWithStats, measureStats := suite.(WithStats); measureStats { - stats.End = time.Now() - suiteWithStats.HandleStats(suiteName, stats) - } - }() + + if len(tests) == 0 { + return } - runTests(t, tests) -} + if stats != nil { + stats.Start = time.Now() + } -// Filtering method according to set regular expression -// specified command-line argument -m -func methodFilter(name string) (bool, error) { - if ok, _ := regexp.MatchString("^Test", name); !ok { - return false, nil + if setupAllSuite, ok := suite.(SetupAllSuite); ok { + setupAllSuite.SetupSuite() } - return regexp.MatchString(*matchMethod, name) + + defer func() { + if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok { + tearDownAllSuite.TearDownSuite() + } + + if suiteWithStats, measureStats := suite.(WithStats); measureStats { + stats.End = time.Now() + suiteWithStats.HandleStats(suiteName, stats) + } + }() + + runTests(t, tests) } -func runTests(t testing.TB, tests []testing.InternalTest) { +func runTests(t *testing.T, tests []test) { if len(tests) == 0 { t.Log("warning: no tests to run") return } - r, ok := t.(runner) - if !ok { // backwards compatibility with Go 1.6 and below - if !testing.RunTests(allTestsFilter, tests) { - t.Fail() - } - return - } - for _, test := range tests { - r.Run(test.Name, test.F) + t.Run(test.name, test.run) } } - -type runner interface { - Run(name string, f func(t *testing.T)) bool -} diff --git a/vendor/github.com/woodsbury/decimal128/LICENCE b/vendor/github.com/woodsbury/decimal128/LICENCE new file mode 100644 index 0000000..fe64715 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/LICENCE @@ -0,0 +1,14 @@ +BSD Zero Clause License + +Copyright (c) 2022 Wade Smith + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/woodsbury/decimal128/arith.go b/vendor/github.com/woodsbury/decimal128/arith.go new file mode 100644 index 0000000..e87be63 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/arith.go @@ -0,0 +1,1178 @@ +package decimal128 + +import "math/bits" + +// Add adds d and o, rounded using the [DefaultRoundingMode], and returns the +// result. +func (d Decimal) Add(o Decimal) Decimal { + return d.AddWithMode(o, DefaultRoundingMode) +} + +// AddWithMode adds d and o, rounding using the provided rounding mode, and +// returns the result. +func (d Decimal) AddWithMode(o Decimal, mode RoundingMode) Decimal { + if d.isSpecial() || o.isSpecial() { + if d.IsNaN() { + return d + } + + if o.IsNaN() { + return o + } + + if d.isInf() { + neg := d.Signbit() + + if o.isInf() && neg != o.Signbit() { + lhs := payloadValPosInfinite + rhs := payloadValNegInfinite + if neg { + lhs = payloadValNegInfinite + rhs = payloadValPosInfinite + } + + return nan(payloadOpAdd, lhs, rhs) + } + + return inf(neg) + } + + return inf(o.Signbit()) + } + + return d.add(o, mode, false) +} + +// Mul multiplies d and o, rounding using the [DefaultRoundingMode], and +// returns the result. +func (d Decimal) Mul(o Decimal) Decimal { + return d.MulWithMode(o, DefaultRoundingMode) +} + +// MulWithMode multiplies d and o, rounding using the provided rounding mode, +// and returns the result. +func (d Decimal) MulWithMode(o Decimal, mode RoundingMode) Decimal { + if d.isSpecial() || o.isSpecial() { + if d.IsNaN() { + return d + } + + if o.IsNaN() { + return o + } + + if !d.isSpecial() { + sig, _ := d.decompose() + if sig[0]|sig[1] == 0 { + lhs := payloadValPosZero + if d.Signbit() { + lhs = payloadValNegZero + } + + rhs := payloadValPosInfinite + if o.Signbit() { + rhs = payloadValNegInfinite + } + + return nan(payloadOpMul, lhs, rhs) + } + } else if !o.isSpecial() { + sig, _ := o.decompose() + if sig[0]|sig[1] == 0 { + lhs := payloadValPosInfinite + if d.Signbit() { + lhs = payloadValNegInfinite + } + + rhs := payloadValPosZero + if o.Signbit() { + rhs = payloadValNegZero + } + + return nan(payloadOpMul, lhs, rhs) + } + } + + return inf(d.Signbit() != o.Signbit()) + } + + dSig, dExp := d.decompose() + oSig, oExp := o.decompose() + + exp := (dExp - exponentBias) + (oExp - exponentBias) + exponentBias + neg := d.Signbit() != o.Signbit() + + var sig uint128 + if dSig[1]|oSig[1] == 0 { + sig1, sig0 := bits.Mul64(dSig[0], oSig[0]) + + if sig0|sig1 == 0 { + return zero(neg) + } + + sig, exp = mode.reduce128(neg, uint128{sig0, sig1}, exp, 0) + } else { + sig256 := dSig.mul(oSig) + + if sig256[0]|sig256[1]|sig256[2]|sig256[3] == 0 { + return zero(neg) + } + + sig, exp = mode.reduce256(neg, sig256, exp, 0) + } + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// Pow raises d to the power of o, rounding using the [DefaultRoundingMode], +// and returns the result. +func (d Decimal) Pow(o Decimal) Decimal { + return d.PowWithMode(o, DefaultRoundingMode) +} + +// PowWithMode raises d to the power of o, rounding using the provided rounding +// mode, and returns the result. +func (d Decimal) PowWithMode(o Decimal, mode RoundingMode) Decimal { + if o.IsZero() { + return one(false) + } + + if d.isOne() { + if !d.Signbit() || o.isInf() { + return one(false) + } + } + + if o.isOne() { + if o.Signbit() { + return one(false).QuoWithMode(d, mode) + } + + return d + } + + if d.IsNaN() { + return d + } + + if o.IsNaN() { + return o + } + + dNeg := d.Signbit() + oNeg := o.Signbit() + + if o.isInf() { + if d.IsZero() { + if oNeg { + return inf(false) + } + + return zero(false) + } + + if d.isInf() { + if oNeg { + return zero(false) + } + + return inf(false) + } + + dSig, dExp := d.decompose() + dExp -= exponentBias + + if dExp > 0 { + if oNeg { + return zero(false) + } + + return inf(false) + } + + if dExp > -maxDigits { + l10 := int16(dSig.log10()) + if l10 >= -dExp { + if oNeg { + return zero(false) + } + + return inf(false) + } + } + + if oNeg { + return inf(false) + } + + return zero(false) + } + + oSig, oExp := o.decompose() + + for { + sig, rem := oSig.div10() + if rem != 0 { + break + } + + oSig = sig + oExp++ + } + + if d.IsZero() { + neg := false + + if d.Signbit() && oExp == exponentBias { + _, digit := oSig.div10() + if digit&1 != 0 { + neg = true + } + } + + if oNeg { + return inf(neg) + } + + return zero(neg) + } + + if d.isInf() { + if dNeg { + neg := false + + if oExp == exponentBias { + _, digit := oSig.div10() + if digit&1 != 0 { + neg = true + } + } + + if oNeg { + return zero(neg) + } + + return inf(neg) + } + + if oNeg { + return zero(false) + } + + return inf(false) + } + + dSig, dExp := d.decompose() + + for { + sig, rem := dSig.div10() + if rem != 0 { + break + } + + dSig = sig + dExp++ + } + + neg := false + + if dNeg { + if oExp < exponentBias { + rhs := payloadValPosFinite + if oNeg { + rhs = payloadValNegFinite + } + + return nan(payloadOpPow, payloadValNegFinite, rhs) + } + + if oExp == exponentBias { + _, digit := oSig.div10() + if digit&1 != 0 { + neg = true + } + } + } + + if !oNeg && oExp >= exponentBias && dSig == (uint128{1, 0}) { + if oSig[1] != 0 || oSig[0] > maxUnbiasedExponent { + if dExp == exponentBias { + return one(neg) + } + + if dExp < exponentBias { + return zero(neg) + } + + return inf(neg) + } + + var p10 int64 + switch oExp { + case exponentBias: + p10 = 1 + case 1 + exponentBias: + p10 = 10 + case 2 + exponentBias: + p10 = 100 + case 3 + exponentBias: + p10 = 1_000 + case 4 + exponentBias: + p10 = 10_000 + case 5 + exponentBias: + p10 = 100_000 + case 6 + exponentBias: + p10 = 1_000_000 + case 7 + exponentBias: + p10 = 10_000_000 + default: + if dExp == exponentBias { + return one(neg) + } + + if dExp < exponentBias { + return zero(neg) + } + + return inf(neg) + } + + exp64 := int64(dExp-exponentBias)*p10*int64(oSig[0]) + exponentBias + + if exp64 < minBiasedExponent-maxDigits { + return zero(neg) + } + + if exp64 > maxBiasedExponent+maxDigits { + return inf(neg) + } + + sig, exp := mode.reduce128(dNeg, dSig, int16(exp64), 0) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) + } + + if dExp&1 == 0 && oExp == exponentBias-1 && dSig == (uint128{1, 0}) && oSig == (uint128{5, 0}) { + exp := (dExp - exponentBias) / 2 + + if oNeg { + exp *= -1 + } + + return compose(neg, dSig, exp+exponentBias) + } + + inv, res, trunc := decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp - exponentBias, + }.log() + + if res.sig[0]|res.sig[1]|res.sig[2] == 0 { + return one(neg) + } + + if int64(res.exp)+int64(oExp) > maxBiasedExponent+maxDigits { + if oNeg != inv { + return zero(neg) + } + + return inf(neg) + } + + res, trunc = res.mul(decomposed192{ + sig: uint192{oSig[0], oSig[1], 0}, + exp: oExp - exponentBias, + }, trunc) + + if res.sig[0]|res.sig[1]|res.sig[2] == 0 { + return one(neg) + } + + l10 := res.sig.log10() + + if int(res.exp) > 5-l10 { + if oNeg != inv { + return zero(neg) + } + + return inf(neg) + } + + if res.sig[0]|res.sig[1]|res.sig[2] == 0 { + return one(neg) + } + + res, trunc = res.epow(int16(l10), trunc) + + if res.exp > maxUnbiasedExponent+58 { + if oNeg != inv { + return zero(neg) + } + + return inf(neg) + } + + if oNeg != inv { + res, trunc = res.rcp(trunc) + trunc *= -1 + } + + sig, exp := mode.reduce192(neg, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// Quo divides d by o, rounding using the [DefaultRoundingMode], and returns +// the result. +func (d Decimal) Quo(o Decimal) Decimal { + return d.QuoWithMode(o, DefaultRoundingMode) +} + +// QuoWithMode divides d by o, rounding using the provided rounding mode, and +// returns the result. +func (d Decimal) QuoWithMode(o Decimal, mode RoundingMode) Decimal { + if d.isSpecial() || o.isSpecial() { + if d.IsNaN() { + return d + } + + if o.IsNaN() { + return o + } + + if d.isInf() { + if o.isInf() { + lhs := payloadValPosInfinite + if d.Signbit() { + lhs = payloadValNegInfinite + } + + rhs := payloadValPosInfinite + if o.Signbit() { + rhs = payloadValNegInfinite + } + + return nan(payloadOpQuo, lhs, rhs) + } + + return inf(d.Signbit() != o.Signbit()) + } + + if o.isInf() { + return zero(d.Signbit() != o.Signbit()) + } + } + + dSig, dExp := d.decompose() + oSig, oExp := o.decompose() + + if oSig[0]|oSig[1] == 0 { + if dSig[0]|dSig[1] == 0 { + lhs := payloadValPosZero + if d.Signbit() { + lhs = payloadValNegZero + } + + rhs := payloadValPosZero + if o.Signbit() { + rhs = payloadValNegZero + } + + return nan(payloadOpQuo, lhs, rhs) + } + + return inf(d.Signbit() != o.Signbit()) + } + + if dSig[0]|dSig[1] == 0 { + return zero(d.Signbit() != o.Signbit()) + } + + exp := (dExp - exponentBias) - (oExp - exponentBias) + exponentBias + + var sig uint128 + var rem uint128 + if dSig[1]|oSig[1] == 0 { + dSig64 := dSig[0] + + for dSig64 <= 0x0002_7fff_ffff_ffff { + dSig64 *= 10_000 + exp -= 4 + } + + for dSig64 <= 0x18ff_ffff_ffff_ffff { + dSig64 *= 10 + exp-- + } + + sig64, rem64 := bits.Div64(0, dSig64, oSig[0]) + + var carry uint64 + for rem64 != 0 && sig64 <= 0x18ff_ffff_ffff_ffff { + for rem64 <= 0x0002_7fff_ffff_ffff && sig64 <= 0x0002_7fff_ffff_ffff { + rem64 *= 10_000 + sig64 *= 10_000 + exp -= 4 + } + + for rem64 <= 0x18ff_ffff_ffff_ffff && sig64 <= 0x18ff_ffff_ffff_ffff { + rem64 *= 10 + sig64 *= 10 + exp-- + } + + if rem64 < oSig[0] { + break + } + + var tmp uint64 + tmp, rem64 = bits.Div64(0, rem64, oSig[0]) + sig64, carry = bits.Add64(sig64, tmp, 0) + + if carry != 0 { + break + } + } + + sig = uint128{sig64, carry} + rem = uint128{rem64, 0} + } else { + if dSig[1] == 0 { + dSig = dSig.mul64(10_000_000_000_000_000_000) + exp -= 19 + } + + for dSig[1] <= 0x0002_7fff_ffff_ffff { + dSig = dSig.mul64(10_000) + exp -= 4 + } + + for dSig[1] <= 0x18ff_ffff_ffff_ffff { + dSig = dSig.mul64(10) + exp-- + } + + sig, rem = dSig.div(oSig) + } + + trunc := int8(0) + + for rem[0]|rem[1] != 0 && sig[1] <= 0x0002_7fff_ffff_ffff { + for rem[1] <= 0x0002_7fff_ffff_ffff && sig[1] <= 0x0002_7fff_ffff_ffff { + rem = rem.mul64(10_000) + sig = sig.mul64(10_000) + exp -= 4 + } + + for rem[1] <= 0x18ff_ffff_ffff_ffff && sig[1] <= 0x18ff_ffff_ffff_ffff { + rem = rem.mul64(10) + sig = sig.mul64(10) + exp-- + } + + var tmp uint128 + tmp, rem = rem.div(oSig) + sig192 := sig.add(tmp) + + for sig192[2] != 0 { + var rem192 uint64 + sig192, rem192 = sig192.div10() + exp++ + + if rem192 != 0 { + trunc = 1 + } + } + + sig = uint128{sig192[0], sig192[1]} + } + + if rem[0]|rem[1] != 0 { + trunc = 1 + } + + neg := d.Signbit() != o.Signbit() + sig, exp = mode.reduce128(neg, sig, exp, trunc) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// QuoRem divides d by o, rounding using the [DefaultRoundingMode], and returns +// the result as an integer quotient and a remainder. +func (d Decimal) QuoRem(o Decimal) (Decimal, Decimal) { + return d.QuoRemWithMode(o, DefaultRoundingMode) +} + +// QuoRem divides d by o, rounding using the provided rounding mode, and +// returns the result as an integer quotient and a remainder. +func (d Decimal) QuoRemWithMode(o Decimal, mode RoundingMode) (Decimal, Decimal) { + if d.isSpecial() || o.isSpecial() { + if d.IsNaN() { + return d, d + } + + if o.IsNaN() { + return o, o + } + + if d.isInf() { + lhs := payloadValPosInfinite + if d.Signbit() { + lhs = payloadValNegInfinite + } + + if o.isInf() { + rhs := payloadValPosInfinite + if o.Signbit() { + rhs = payloadValNegInfinite + } + + res := nan(payloadOpQuoRem, lhs, rhs) + return res, res + } + + rhs := payloadValPosFinite + if o.IsZero() { + if o.Signbit() { + rhs = payloadValNegZero + } else { + rhs = payloadValPosZero + } + } else if o.Signbit() { + rhs = payloadValNegFinite + } + + return inf(d.Signbit() != o.Signbit()), nan(payloadOpQuoRem, lhs, rhs) + } + + if o.isInf() { + return zero(d.Signbit() != o.Signbit()), d + } + } + + dSig, dExp := d.decompose() + oSig, oExp := o.decompose() + + if oSig[0]|oSig[1] == 0 { + rhs := payloadValPosZero + if o.Signbit() { + rhs = payloadValNegZero + } + + if dSig[0]|dSig[1] == 0 { + lhs := payloadValPosZero + if d.Signbit() { + lhs = payloadValNegZero + } + + res := nan(payloadOpQuoRem, lhs, rhs) + return res, res + } + + lhs := payloadValPosFinite + if d.Signbit() { + lhs = payloadValNegFinite + } + + return inf(d.Signbit() != o.Signbit()), nan(payloadOpQuoRem, lhs, rhs) + } + + if dSig[0]|dSig[1] == 0 { + return zero(d.Signbit() != o.Signbit()), zero(d.Signbit()) + } + + exp := (dExp - exponentBias) - (oExp - exponentBias) + + if exp < 0 { + if exp <= -19 && oSig[1] == 0 { + oSig = oSig.mul64(10_000_000_000_000_000_000) + exp += 19 + } + + for exp <= -4 && oSig[1] <= 0x0002_7fff_ffff_ffff { + oSig = oSig.mul64(10_000) + exp += 4 + } + + for exp < 0 && oSig[1] <= 0x18ff_ffff_ffff_ffff { + oSig = oSig.mul64(10) + exp++ + } + + if exp < 0 || oSig.cmp(dSig) > 0 { + return zero(d.Signbit() != o.Signbit()), d + } + } else if exp > 0 { + if exp >= 19 && dSig[1] == 0 { + dSig = dSig.mul64(10_000_000_000_000_000_000) + dExp -= 19 + exp -= 19 + } + + for exp >= 4 && dSig[1] <= 0x0002_7fff_ffff_ffff { + dSig = dSig.mul64(10_000) + dExp -= 4 + exp -= 4 + } + + for exp > 0 && dSig[1] <= 0x18ff_ffff_ffff_ffff { + dSig = dSig.mul64(10) + dExp-- + exp-- + } + } + + qexp := exp + exponentBias + rexp := dExp + + var sig uint128 + var rem uint128 + if dSig[1]|oSig[1] == 0 { + sig64, rem64 := bits.Div64(0, dSig[0], oSig[0]) + + var carry uint64 + for exp > 0 && rem64 != 0 && sig64 <= 0x18ff_ffff_ffff_ffff { + for exp >= 4 && rem64 <= 0x0002_7fff_ffff_ffff && sig64 <= 0x0002_7fff_ffff_ffff { + rem64 *= 10_000 + sig64 *= 10_000 + exp -= 4 + qexp -= 4 + rexp -= 4 + } + + for exp > 0 && rem64 <= 0x18ff_ffff_ffff_ffff && sig64 <= 0x18ff_ffff_ffff_ffff { + rem64 *= 10 + sig64 *= 10 + exp-- + qexp-- + rexp-- + } + + if rem64 < oSig[0] { + break + } + + var tmp uint64 + tmp, rem64 = bits.Div64(0, rem64, oSig[0]) + sig64, carry = bits.Add64(sig64, tmp, 0) + + if carry != 0 { + break + } + } + + sig = uint128{sig64, carry} + rem = uint128{rem64, 0} + } else { + sig, rem = dSig.div(oSig) + } + + trunc := int8(0) + + for exp > 0 && rem[0]|rem[1] != 0 && sig[1] <= 0x0002_7fff_ffff_ffff { + for exp >= 4 && rem[1] <= 0x0002_7fff_ffff_ffff && sig[1] <= 0x0002_7fff_ffff_ffff { + rem = rem.mul64(10_000) + sig = sig.mul64(10_000) + exp -= 4 + qexp -= 4 + rexp -= 4 + } + + for exp > 0 && rem[1] <= 0x18ff_ffff_ffff_ffff && sig[1] <= 0x18ff_ffff_ffff_ffff { + rem = rem.mul64(10) + sig = sig.mul64(10) + exp-- + qexp-- + rexp-- + } + + var tmp uint128 + tmp, rem = rem.div(oSig) + sig192 := sig.add(tmp) + + for sig192[2] != 0 { + var rem192 uint64 + sig192, rem192 = sig192.div10() + qexp++ + + if rem192 != 0 { + trunc = 1 + } + } + + sig = uint128{sig192[0], sig192[1]} + } + + for exp > 0 && rem[0]|rem[1] != 0 { + for exp >= 4 && rem[1] <= 0x0002_7fff_ffff_ffff { + rem = rem.mul64(10_000) + exp -= 4 + rexp -= 4 + } + + for exp > 0 && rem[1] <= 0x18ff_ffff_ffff_ffff { + rem = rem.mul64(10) + exp-- + rexp-- + } + + var tmp uint128 + tmp, rem = rem.div(oSig) + + if tmp[0]|tmp[1] != 0 { + trunc = 1 + } + } + + qneg := d.Signbit() != o.Signbit() + qsig, qexp := mode.reduce128(qneg, sig, qexp, trunc) + + rneg := d.Signbit() + rsig, rexp := mode.reduce128(rneg, rem, rexp, 0) + + quo := compose(qneg, qsig, qexp) + + if qexp > maxBiasedExponent { + quo = inf(qneg) + } + + if rexp > maxBiasedExponent { + return quo, inf(rneg) + } + + return quo, compose(rneg, rsig, rexp) +} + +// Sub subtracts o from d, rounding using the [DefaultRoundingMode], and +// returns the result. +func (d Decimal) Sub(o Decimal) Decimal { + return d.SubWithMode(o, DefaultRoundingMode) +} + +// SubWithMode subtracts o from d, rounding using the provided rounding mode, +// and returns the result. +func (d Decimal) SubWithMode(o Decimal, mode RoundingMode) Decimal { + if d.isSpecial() || o.isSpecial() { + if d.IsNaN() { + return d + } + + if o.IsNaN() { + return o + } + + if d.isInf() { + neg := d.Signbit() + + if o.isInf() && neg == o.Signbit() { + lhs := payloadValPosInfinite + rhs := payloadValPosInfinite + if neg { + lhs = payloadValNegInfinite + rhs = payloadValNegInfinite + } + + return nan(payloadOpSub, lhs, rhs) + } + + return inf(neg) + } + + return inf(!o.Signbit()) + } + + return d.add(o, mode, true) +} + +func (d Decimal) add(o Decimal, mode RoundingMode, subtract bool) Decimal { + dSig, dExp := d.decompose() + oSig, oExp := o.decompose() + + if dSig[0]|dSig[1] == 0 { + if oSig[0]|oSig[1] == 0 { + if subtract { + return zero(d.Signbit() && !o.Signbit()) + } else { + return zero(d.Signbit() && o.Signbit()) + } + } + + if subtract { + return compose(!o.Signbit(), oSig, oExp) + } + + return o + } + + if oSig[0]|oSig[1] == 0 { + return d + } + + exp := dExp - oExp + trunc := int8(0) + + if exp < 0 { + if exp <= -19 && oSig[1] == 0 { + oSig = oSig.mul64(10_000_000_000_000_000_000) + oExp -= 19 + exp += 19 + } + + for exp <= -4 && oSig[1] <= 0x0002_7fff_ffff_ffff { + oSig = oSig.mul64(10_000) + oExp -= 4 + exp += 4 + } + + for exp < 0 && oSig[1] <= 0x18ff_ffff_ffff_ffff { + oSig = oSig.mul64(10) + oExp-- + exp++ + } + + if exp < -maxDigits { + if dSig[0]|dSig[1] != 0 { + dSig = uint128{} + trunc = 1 + } + + dExp = oExp + exp = 0 + } + + if exp <= -8 { + var rem uint64 + dSig, rem = dSig.div1e8() + if rem != 0 { + trunc = 1 + } + + if dSig[0]|dSig[1] == 0 { + dExp = oExp + exp = 0 + } else { + dExp += 8 + exp += 8 + } + } + + if exp <= -4 { + var rem uint64 + dSig, rem = dSig.div10000() + if rem != 0 { + trunc = 1 + } + + if dSig[0]|dSig[1] == 0 { + dExp = oExp + exp = 0 + } else { + dExp += 4 + exp += 4 + } + } + + if exp <= -3 { + var rem uint64 + dSig, rem = dSig.div1000() + if rem != 0 { + trunc = 1 + } + + if dSig[0]|dSig[1] == 0 { + dExp = oExp + exp = 0 + } else { + dExp += 3 + exp += 3 + } + } + + if exp <= -2 { + var rem uint64 + dSig, rem = dSig.div100() + if rem != 0 { + trunc = 1 + } + + if dSig[0]|dSig[1] == 0 { + dExp = oExp + exp = 0 + } else { + dExp += 2 + exp += 2 + } + } + + for exp < 0 { + var rem uint64 + dSig, rem = dSig.div10() + if rem != 0 { + trunc = 1 + } + + if dSig[0]|dSig[1] == 0 { + dExp = oExp + break + } + + dExp++ + exp++ + } + } else if exp > 0 { + if exp >= 19 && dSig[1] == 0 { + dSig = dSig.mul64(10_000_000_000_000_000_000) + dExp -= 19 + exp -= 19 + } + + for exp >= 4 && dSig[1] <= 0x0002_7fff_ffff_ffff { + dSig = dSig.mul64(10_000) + dExp -= 4 + exp -= 4 + } + + for exp > 0 && dSig[1] <= 0x18ff_ffff_ffff_ffff { + dSig = dSig.mul64(10) + dExp-- + exp-- + } + + if exp > maxDigits { + if oSig[0]|oSig[1] != 0 { + oSig = uint128{} + trunc = -1 + } + + exp = 0 + } + + if exp >= 8 { + var rem uint64 + oSig, rem = oSig.div1e8() + if rem != 0 { + trunc = -1 + } + + if oSig[0]|oSig[1] == 0 { + exp = 0 + } else { + exp -= 8 + } + } + + if exp >= 4 { + var rem uint64 + oSig, rem = oSig.div10000() + if rem != 0 { + trunc = -1 + } + + if oSig[0]|oSig[1] == 0 { + exp = 0 + } else { + exp -= 4 + } + } + + if exp >= 3 { + var rem uint64 + oSig, rem = oSig.div1000() + if rem != 0 { + trunc = -1 + } + + if oSig[0]|oSig[1] == 0 { + exp = 0 + } else { + exp -= 3 + } + } + + if exp >= 2 { + var rem uint64 + oSig, rem = oSig.div100() + if rem != 0 { + trunc = -1 + } + + if oSig[0]|oSig[1] == 0 { + exp = 0 + } else { + exp -= 2 + } + } + + for exp > 0 { + var rem uint64 + oSig, rem = oSig.div10() + if rem != 0 { + trunc = -1 + } + + if oSig[0]|oSig[1] == 0 { + break + } + + exp-- + } + } + + dNeg := d.Signbit() + oNeg := o.Signbit() + if subtract { + oNeg = !oNeg + } + + neg := dNeg + + var sig uint128 + if dNeg == oNeg { + sig192 := dSig.add(oSig) + + if sig192[0]|sig192[1]|sig192[2] == 0 { + return zero(mode == ToNegativeInf) + } + + if trunc == -1 { + trunc = 1 + } + + sig, exp = mode.reduce192(neg, sig192, dExp, trunc) + } else { + var brw uint + sig, brw = dSig.sub(oSig) + + if brw != 0 { + sig = sig.twos() + neg = !neg + trunc *= -1 + } else if sig[0]|sig[1] == 0 { + return zero(mode == ToNegativeInf) + } + + sig, exp = mode.reduce128(neg, sig, dExp, trunc) + } + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} diff --git a/vendor/github.com/woodsbury/decimal128/binary.go b/vendor/github.com/woodsbury/decimal128/binary.go new file mode 100644 index 0000000..be681a2 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/binary.go @@ -0,0 +1,59 @@ +package decimal128 + +import "errors" + +// MarshalBinary implements the [encoding.BinaryMarshaler] interface. It +// marshals the Decimal into IEEE 754 format. +func (d Decimal) MarshalBinary() ([]byte, error) { + data := make([]byte, 16) + + data[0] = byte(d.hi >> 56) + data[1] = byte(d.hi >> 48) + data[2] = byte(d.hi >> 40) + data[3] = byte(d.hi >> 32) + data[4] = byte(d.hi >> 24) + data[5] = byte(d.hi >> 16) + data[6] = byte(d.hi >> 8) + data[7] = byte(d.hi) + + data[8] = byte(d.lo >> 56) + data[9] = byte(d.lo >> 48) + data[10] = byte(d.lo >> 40) + data[11] = byte(d.lo >> 32) + data[12] = byte(d.lo >> 24) + data[13] = byte(d.lo >> 16) + data[14] = byte(d.lo >> 8) + data[15] = byte(d.lo) + + return data, nil +} + +// UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface. It +// unmarshals a Decimal in IEEE 754 format. +func (d *Decimal) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return errors.New("Decimal.UnmarshalBinary: invalid length") + } + + lo := uint64(data[15]) + lo |= uint64(data[14]) << 8 + lo |= uint64(data[13]) << 16 + lo |= uint64(data[12]) << 24 + lo |= uint64(data[11]) << 32 + lo |= uint64(data[10]) << 40 + lo |= uint64(data[9]) << 48 + lo |= uint64(data[8]) << 56 + + hi := uint64(data[7]) + hi |= uint64(data[6]) << 8 + hi |= uint64(data[5]) << 16 + hi |= uint64(data[4]) << 24 + hi |= uint64(data[3]) << 32 + hi |= uint64(data[2]) << 40 + hi |= uint64(data[1]) << 48 + hi |= uint64(data[0]) << 56 + + *d = Decimal{lo, hi} + + return nil +} diff --git a/vendor/github.com/woodsbury/decimal128/compare.go b/vendor/github.com/woodsbury/decimal128/compare.go new file mode 100644 index 0000000..f720f90 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/compare.go @@ -0,0 +1,1054 @@ +package decimal128 + +// Compare returns: +// +// -1 if d < o +// 0 if d == o +// +1 if d > o +// +// Unlike [Decimal.Cmp], Compare considers NaN values to be less than any other +// values. +func Compare(d, o Decimal) int { + if d.IsNaN() { + if o.IsNaN() { + return 0 + } + + return -1 + } + + if o.IsNaN() { + return 1 + } + + return int(d.Cmp(o)) +} + +// Max returns the larger of d or o. If either value is NaN the result is NaN. +func Max(d, o Decimal) Decimal { + if d.IsNaN() { + return d + } + + if o.IsNaN() { + return o + } + + if d.IsZero() && o.IsZero() { + if !d.Signbit() || !o.Signbit() { + return zero(false) + } + + return zero(true) + } + + if o.Cmp(d).Greater() { + return o + } + + return d +} + +// Min returns the smaller of d or o. If either value is NaN the result is NaN. +func Min(d, o Decimal) Decimal { + if d.IsNaN() { + return d + } + + if o.IsNaN() { + return o + } + + if d.IsZero() && o.IsZero() { + if d.Signbit() || o.Signbit() { + return zero(true) + } + + return zero(false) + } + + if o.Cmp(d).Less() { + return o + } + + return d +} + +// CmpResult represents the result from comparing two Decimals. When the values +// being compared aren't NaNs, the integer value of the CmpResult will be: +// +// -1 if lhs < rhs +// 0 if lhs == rhs +// +1 if lhs > rhs +// +// The Equal, Greater, GreaterOrEqual, Less, and LessOrEqual methods can also +// be used to determine the result. If either value is a NaN, then these +// methods will still behave correctly. +type CmpResult int8 + +const ( + cmpNaN CmpResult = -2 + cmpLess CmpResult = -1 + cmpEqual CmpResult = 0 + cmpGreater CmpResult = 1 +) + +// Equal returns whether this CmpResult represents that the two Decimals were +// equal to each other. This method will handle when one of the values being +// compared was a NaN. +func (cr CmpResult) Equal() bool { + return cr == cmpEqual +} + +// Greater returns whether this CmpResult represents that the value on the +// left-hand side of the comparison was greater than the value on the +// right-hand side. This method will handle when one of the values being +// compared was a NaN. +func (cr CmpResult) Greater() bool { + return cr == cmpGreater +} + +// GreaterOrEqual returns whether this CmpResult represents that the value on +// the left-hand side of the comparison was greater than or equal to the value +// on the right-hand side. This method will handle when one of the values being +// compared was a NaN. +func (cr CmpResult) GreaterOrEqual() bool { + return cr == cmpGreater || cr == cmpEqual +} + +// Less returns whether this CmpResult represents that the value on the +// left-hand side of the comparison was less than the value on the right-hand +// side. This method will handle when one of the values being compared was a +// NaN. +func (cr CmpResult) Less() bool { + return cr == cmpLess +} + +// LessOrEqual returns whether this CmpResult represents that the value on the +// left-hand side of the comparison was less than or equal to the value on the +// right-hand side. This method will handle when one of the values being +// compared was a NaN. +func (cr CmpResult) LessOrEqual() bool { + return cr == cmpLess || cr == cmpEqual +} + +// Cmp compares two Decimals and returns a CmpResult representing whether the +// two values were equal, the left-hand side was greater than the right-hand +// side, or the left-hand side was less than the right-hand side. +func (d Decimal) Cmp(o Decimal) CmpResult { + if d.isSpecial() || o.isSpecial() { + if d.IsNaN() || o.IsNaN() { + return cmpNaN + } + + if d.isInf() { + neg := d.Signbit() + + if o.isInf() && neg == o.Signbit() { + return cmpEqual + } + + if neg { + return cmpLess + } + + return cmpGreater + } + + if o.isInf() { + if o.Signbit() { + return cmpGreater + } + + return cmpLess + } + } + + if d == o { + return cmpEqual + } + + dSig, dExp := d.decompose() + + if dSig[0]|dSig[1] == 0 { + if o.IsZero() { + return cmpEqual + } + + if o.Signbit() { + return cmpGreater + } + + return cmpLess + } + + oSig, oExp := o.decompose() + + if oSig[0]|oSig[1] == 0 { + if d.Signbit() { + return cmpLess + } + + return cmpGreater + } + + neg := d.Signbit() + + if neg != o.Signbit() { + if neg { + return cmpLess + } + + return cmpGreater + } + + exp := dExp - oExp + trunc := false + + var res CmpResult + if neg { + res = cmpLess + } else { + res = cmpGreater + } + + if exp < 0 { + if oSig.cmp(dSig) >= 0 { + return res * -1 + } + + if exp <= -19 { + if exp < -maxDigits { + return res * -1 + } + + var rem uint64 + dSig, rem = dSig.div1e19() + if dSig[0]|dSig[1] == 0 { + return res * -1 + } + + if rem != 0 { + trunc = true + } + + exp += 19 + } + + exp *= -1 + dSig, oSig = oSig, dSig + res *= -1 + } else if exp > 0 { + if dSig.cmp(oSig) >= 0 { + return res + } + + if exp >= 19 { + if exp > maxDigits { + return res + } + + var rem uint64 + oSig, rem = oSig.div1e19() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + exp -= 19 + } + } + + if exp >= 8 { + var rem uint64 + oSig, rem = oSig.div1e8() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + exp -= 8 + } + + if oSig[1] == 0 { + if dSig[1] != 0 { + return res + } + + oSig64 := oSig[0] + + if exp >= 8 { + if oSig64%100_000_000 != 0 { + trunc = true + } + + oSig64 /= 100_000_000 + if oSig64 == 0 { + return res + } + + exp -= 8 + } + + switch exp { + case 7: + if oSig64%10_000_000 != 0 { + trunc = true + } + + oSig64 /= 10_000_000 + if oSig64 == 0 { + return res + } + case 6: + if oSig64%1_000_000 != 0 { + trunc = true + } + + oSig64 /= 1_000_000 + if oSig64 == 0 { + return res + } + case 5: + if oSig64%100_000 != 0 { + trunc = true + } + + oSig64 /= 100_000 + if oSig64 == 0 { + return res + } + case 4: + if oSig64%10_000 != 0 { + trunc = true + } + + oSig64 /= 10_000 + if oSig64 == 0 { + return res + } + case 3: + if oSig64%1000 != 0 { + trunc = true + } + + oSig64 /= 1000 + if oSig64 == 0 { + return res + } + case 2: + if oSig64%100 != 0 { + trunc = true + } + + oSig64 /= 100 + if oSig64 == 0 { + return res + } + case 1: + if oSig64%10 != 0 { + trunc = true + } + + oSig64 /= 10 + if oSig64 == 0 { + return res + } + } + + if dSig[0] == oSig64 { + if trunc { + return res * -1 + } + + return cmpEqual + } + + if dSig[0] < oSig64 { + return res * -1 + } + + return res + } + + if exp >= 8 { + var rem uint64 + oSig, rem = oSig.div1e8() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + exp -= 8 + } + + var rem uint64 + switch exp { + case 7: + oSig, rem = oSig.div10() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + fallthrough + case 6: + oSig, rem = oSig.div1000() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + oSig, rem = oSig.div1000() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + case 5: + oSig, rem = oSig.div10() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + fallthrough + case 4: + oSig, rem = oSig.div10000() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + case 3: + oSig, rem = oSig.div1000() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + case 2: + oSig, rem = oSig.div10() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + fallthrough + case 1: + oSig, rem = oSig.div10() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + } + + sres := dSig.cmp(oSig) + if sres == 0 { + if trunc { + return res * -1 + } + + return cmpEqual + } + + if res == cmpLess { + return CmpResult(sres * -1) + } + + return CmpResult(sres) +} + +// CmpAbs compares the absolute value of two Decimals and returns a CmpResult +// representing whether the two values were equal, the left-hand side was +// greater than the right-hand side, or the left-hand side was less than the +// right-hand side. +func (d Decimal) CmpAbs(o Decimal) CmpResult { + if d.isSpecial() || o.isSpecial() { + if d.IsNaN() || o.IsNaN() { + return cmpNaN + } + + if d.isInf() { + if o.isInf() { + return cmpEqual + } + + return cmpGreater + } + + if o.isInf() { + return cmpLess + } + } + + if d == o { + return cmpEqual + } + + dSig, dExp := d.decompose() + + if dSig[0]|dSig[1] == 0 { + if o.IsZero() { + return cmpEqual + } + + return cmpLess + } + + oSig, oExp := o.decompose() + + if oSig[0]|oSig[1] == 0 { + return cmpGreater + } + + exp := dExp - oExp + trunc := false + res := cmpGreater + + if exp < 0 { + if oSig.cmp(dSig) >= 0 { + return cmpLess + } + + if exp <= -19 { + if exp < -maxDigits { + return cmpLess + } + + var rem uint64 + dSig, rem = dSig.div1e19() + if dSig[0]|dSig[1] == 0 { + return cmpLess + } + + if rem != 0 { + trunc = true + } + + exp += 19 + } + + exp *= -1 + dSig, oSig = oSig, dSig + res = cmpLess + } else if exp > 0 { + if dSig.cmp(oSig) >= 0 { + return cmpGreater + } + + if exp >= 19 { + if exp > maxDigits { + return cmpGreater + } + + var rem uint64 + oSig, rem = oSig.div1e19() + if oSig[0]|oSig[1] == 0 { + return cmpGreater + } + + if rem != 0 { + trunc = true + } + + exp -= 19 + } + } + + if exp >= 8 { + var rem uint64 + oSig, rem = oSig.div1e8() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + exp -= 8 + } + + if oSig[1] == 0 { + if dSig[1] != 0 { + return res + } + + oSig64 := oSig[0] + + if exp >= 8 { + if oSig64%100_000_000 != 0 { + trunc = true + } + + oSig64 /= 100_000_000 + if oSig64 == 0 { + return res + } + + exp -= 8 + } + + switch exp { + case 7: + if oSig64%10_000_000 != 0 { + trunc = true + } + + oSig64 /= 10_000_000 + if oSig64 == 0 { + return res + } + case 6: + if oSig64%1_000_000 != 0 { + trunc = true + } + + oSig64 /= 1_000_000 + if oSig64 == 0 { + return res + } + case 5: + if oSig64%100_000 != 0 { + trunc = true + } + + oSig64 /= 100_000 + if oSig64 == 0 { + return res + } + case 4: + if oSig64%10_000 != 0 { + trunc = true + } + + oSig64 /= 10_000 + if oSig64 == 0 { + return res + } + case 3: + if oSig64%1000 != 0 { + trunc = true + } + + oSig64 /= 1000 + if oSig64 == 0 { + return res + } + case 2: + if oSig64%100 != 0 { + trunc = true + } + + oSig64 /= 100 + if oSig64 == 0 { + return res + } + case 1: + if oSig64%10 != 0 { + trunc = true + } + + oSig64 /= 10 + if oSig64 == 0 { + return res + } + } + + if dSig[0] == oSig64 { + if trunc { + return res * -1 + } + + return cmpEqual + } + + if dSig[0] < oSig64 { + return res * -1 + } + + return res + } + + if exp >= 8 { + var rem uint64 + oSig, rem = oSig.div1e8() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + exp -= 8 + } + + var rem uint64 + switch exp { + case 7: + oSig, rem = oSig.div10() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + fallthrough + case 6: + oSig, rem = oSig.div1000() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + oSig, rem = oSig.div1000() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + case 5: + oSig, rem = oSig.div10() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + fallthrough + case 4: + oSig, rem = oSig.div10000() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + case 3: + oSig, rem = oSig.div1000() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + case 2: + oSig, rem = oSig.div10() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + + fallthrough + case 1: + oSig, rem = oSig.div10() + if oSig[0]|oSig[1] == 0 { + return res + } + + if rem != 0 { + trunc = true + } + } + + sres := dSig.cmp(oSig) + if sres == 0 { + if trunc { + return res * -1 + } + + return cmpEqual + } + + if res == cmpLess { + return CmpResult(sres * -1) + } + + return CmpResult(sres) +} + +// Equal compares two Decimals and reports whether they are equal. +func (d Decimal) Equal(o Decimal) bool { + if d.isSpecial() || o.isSpecial() { + if d.IsNaN() || o.IsNaN() { + return false + } + + if d.isInf() { + return o.isInf() && d.Signbit() == o.Signbit() + } + + if o.isInf() { + return false + } + } + + if d == o { + return true + } + + dSig, dExp := d.decompose() + + if dSig[0]|dSig[1] == 0 { + return o.IsZero() + } + + oSig, oExp := o.decompose() + + if oSig[0]|oSig[1] == 0 { + return false + } + + if d.Signbit() != o.Signbit() { + return false + } + + exp := dExp - oExp + + if exp < 0 { + if oSig.cmp(dSig) >= 0 { + return false + } + + if exp <= -19 { + if exp < -maxDigits { + return false + } + + var rem uint64 + dSig, rem = dSig.div1e19() + if rem != 0 { + return false + } + + exp += 19 + } + + exp *= -1 + dSig, oSig = oSig, dSig + } else if exp > 0 { + if dSig.cmp(oSig) >= 0 { + return false + } + + if exp >= 19 { + if exp > maxDigits { + return false + } + + var rem uint64 + oSig, rem = oSig.div1e19() + if rem != 0 { + return false + } + + exp -= 19 + } + } + + if exp >= 8 { + var rem uint64 + oSig, rem = oSig.div1e8() + if rem != 0 { + return false + } + + exp -= 8 + } + + if oSig[1] == 0 { + if dSig[1] != 0 { + return false + } + + oSig64 := oSig[0] + + if exp >= 8 { + if oSig64%100_000_000 != 0 { + return false + } + + oSig64 /= 100_000_000 + exp -= 8 + } + + switch exp { + case 7: + if oSig64%10_000_000 != 0 { + return false + } + + oSig64 /= 10_000_000 + case 6: + if oSig64%1_000_000 != 0 { + return false + } + + oSig64 /= 1_000_000 + case 5: + if oSig64%100_000 != 0 { + return false + } + + oSig64 /= 100_000 + case 4: + if oSig64%10_000 != 0 { + return false + } + + oSig64 /= 10_000 + case 3: + if oSig64%1000 != 0 { + return false + } + + oSig64 /= 1000 + case 2: + if oSig64%100 != 0 { + return false + } + + oSig64 /= 100 + case 1: + if oSig64%10 != 0 { + return false + } + + oSig64 /= 10 + } + + return dSig[0] == oSig64 + } + + if exp >= 8 { + var rem uint64 + oSig, rem = oSig.div1e8() + if rem != 0 { + return false + } + + exp -= 8 + } + + var rem uint64 + switch exp { + case 7: + oSig, rem = oSig.div10() + if rem != 0 { + return false + } + + fallthrough + case 6: + oSig, rem = oSig.div1000() + if rem != 0 { + return false + } + + oSig, rem = oSig.div1000() + if rem != 0 { + return false + } + case 5: + oSig, rem = oSig.div10() + if rem != 0 { + return false + } + + fallthrough + case 4: + oSig, rem = oSig.div10000() + if rem != 0 { + return false + } + case 3: + oSig, rem = oSig.div1000() + if rem != 0 { + return false + } + case 2: + oSig, rem = oSig.div10() + if rem != 0 { + return false + } + + fallthrough + case 1: + oSig, rem = oSig.div10() + if rem != 0 { + return false + } + } + + return dSig == oSig +} + +// IsZero reports whether the Decimal is equal to zero. This method will return +// true for both positive and negative zero. +func (d Decimal) IsZero() bool { + if d.hi&0x6000_0000_0000_0000 == 0x6000_0000_0000_0000 { + return false + } else { + return d.lo == 0 && d.hi&0x0001_ffff_ffff_ffff == 0 + } +} + +func (d Decimal) isOne() bool { + if d.isSpecial() { + return false + } + + sig, exp := d.decompose() + + if exp <= int16(-len(uint128PowersOf10)+exponentBias) || exp > exponentBias { + return false + } + + return sig == uint128PowersOf10[-(exp-exponentBias)] +} diff --git a/vendor/github.com/woodsbury/decimal128/compose.go b/vendor/github.com/woodsbury/decimal128/compose.go new file mode 100644 index 0000000..d21f117 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/compose.go @@ -0,0 +1,252 @@ +package decimal128 + +import ( + "math/big" + "strconv" +) + +// Compose sets d to the value represented by the parts provided as arguments. +// The arguments consist of: +// - a byte form value that should be set to 0 for finite values, 1 for +// infinite values, or 2 for values which are NaN +// - a bool value that should be set to true when the value is negative, +// false otherwise +// - a byte slice that should be set to the significand of the value as a big +// endian integer +// - an int32 exponent +// +// If the value represented by the parts in the arguments are outside the range +// of a Decimal an error is returned. Compose implements the composer interface +// used by the [database/sql] package to read and write decimal values. +func (d *Decimal) Compose(form byte, neg bool, sig []byte, exp int32) error { + switch form { + case 0: // finite + i := 0 + l := len(sig) + for ; i < l; i++ { + if sig[i] != 0 { + break + } + } + + if i == l { + *d = zero(neg) + return nil + } + + sig = sig[i:] + + if len(sig) > 32 { + if exp > maxUnbiasedExponent { + return &composeRangeError{} + } + + bigsig := new(big.Int) + bigsig.SetBytes(sig) + + den := new(big.Int).SetUint64(10_000_000_000_000_000_000) + rem := new(big.Int) + + for bigsig.BitLen() > 32*8 { + bigsig.QuoRem(bigsig, den, rem) + + if rem.BitLen() != 0 { + return &composeRangeError{} + } + + exp += 19 + + if exp > maxUnbiasedExponent { + return &composeRangeError{} + } + } + + sig = bigsig.Bytes() + } + + var sig128 uint128 + if l := len(sig); l > 16 { + if exp > maxUnbiasedExponent { + return &composeRangeError{} + } + + var sig256 uint256 + sig256[0] = uint64(sig[0]) + + for i := 1; i < l; i++ { + sig256 = sig256.lsh(8) + sig256[0] |= uint64(sig[i]) + } + + for sig256[3] > 0 { + var rem uint64 + sig256, rem = sig256.div1e19() + + if rem != 0 { + return &composeRangeError{} + } + + exp += 19 + + if exp > maxUnbiasedExponent { + return &composeRangeError{} + } + } + + sig192 := uint192{sig256[0], sig256[1], sig256[2]} + + for sig192[2] > 0 { + var rem uint64 + sig192, rem = sig192.div10000() + + if rem != 0 { + return &composeRangeError{} + } + + exp += 4 + + if exp > maxUnbiasedExponent { + return &composeRangeError{} + } + } + + sig128 = uint128{sig192[0], sig192[1]} + } else { + sig128[0] = uint64(sig[0]) + + for i := 1; i < len(sig); i++ { + sig128 = sig128.lsh(8) + sig128[0] |= uint64(sig[i]) + } + } + + for sig128[1] > 0x0002_7fff_ffff_ffff { + var rem uint64 + sig128, rem = sig128.div10() + + if rem != 0 { + return &composeRangeError{} + } + + exp++ + + if exp > maxUnbiasedExponent { + return &composeRangeError{} + } + } + + if exp < minUnbiasedExponent-maxDigits { + return &composeRangeError{} + } + + for exp < minUnbiasedExponent { + var rem uint64 + sig128, rem = sig128.div10() + + if rem != 0 { + return &composeRangeError{} + } + + exp++ + } + + for exp > maxUnbiasedExponent { + sig128 = sig128.mul64(10) + + if sig128[1] > 0x0002_7fff_ffff_ffff { + return &composeRangeError{} + } + + exp-- + } + + *d = compose(neg, sig128, int16(exp+exponentBias)) + return nil + case 1: // infinite + *d = inf(neg) + return nil + case 2: // NaN + *d = nan(payloadOpCompose, 0, 0) + return nil + } + + return &composeFormError{form} +} + +// Decompose returns the state of d in parts. The returned values consist of: +// - a byte form value set to 0 when the value is finite, 1 when the value is +// infinite, or 2 when the value is NaN +// - a bool value set to true if the value is negative, false otherwise +// - a byte slice containing the significand of the value as a big endian +// integer +// - an int32 exponent +// +// If the provided buf has sufficient capacity, it may be returned as the +// significand with the correct value and length set. Decompose implements the +// decomposer interface used by the [database/sql] package to read and write +// decimal values. +func (d Decimal) Decompose(buf []byte) (byte, bool, []byte, int32) { + if d.IsNaN() { + return 2, d.Signbit(), nil, 0 + } + + if d.isInf() { + return 1, d.Signbit(), nil, 0 + } + + sig128, exp := d.decompose() + + if sig128[0]|sig128[1] == 0 { + return 0, d.Signbit(), nil, 0 + } + + var sig []byte + if cap(buf) >= 16 { + sig = buf[:16] + } else { + sig = make([]byte, 16) + } + + sig[0] = byte(sig128[1] >> 56) + sig[1] = byte(sig128[1] >> 48) + sig[2] = byte(sig128[1] >> 40) + sig[3] = byte(sig128[1] >> 32) + sig[4] = byte(sig128[1] >> 24) + sig[5] = byte(sig128[1] >> 16) + sig[6] = byte(sig128[1] >> 8) + sig[7] = byte(sig128[1]) + + sig[8] = byte(sig128[0] >> 56) + sig[9] = byte(sig128[0] >> 48) + sig[10] = byte(sig128[0] >> 40) + sig[11] = byte(sig128[0] >> 32) + sig[12] = byte(sig128[0] >> 24) + sig[13] = byte(sig128[0] >> 16) + sig[14] = byte(sig128[0] >> 8) + sig[15] = byte(sig128[0]) + + i := 0 + for ; i < len(sig); i++ { + if sig[i] != 0 { + break + } + } + + sig = sig[i:] + + return 0, d.Signbit(), sig, int32(exp) - exponentBias +} + +type composeFormError struct { + form byte +} + +func (err *composeFormError) Error() string { + return "unknown form " + strconv.FormatUint(uint64(err.form), 10) +} + +type composeRangeError struct{} + +func (err *composeRangeError) Error() string { + return "value out of range" +} diff --git a/vendor/github.com/woodsbury/decimal128/constants.go b/vendor/github.com/woodsbury/decimal128/constants.go new file mode 100644 index 0000000..037662e --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/constants.go @@ -0,0 +1,22 @@ +package decimal128 + +var e = Decimal{0x4e90_6acc_b26a_bb56, 0x2ffe_8605_8a4b_f4de} + +// E returns the mathematical constant e. +func E() Decimal { + return e +} + +var phi = Decimal{0x1c9c_005e_9476_8cc6, 0x2ffe_4fc6_75c9_f21d} + +// Phi returns the golden ratio. +func Phi() Decimal { + return phi +} + +var pi = Decimal{0xbabe_5564_e6f3_9f8f, 0x2ffe_9ae4_7957_96a7} + +// Pi returns the mathematical constant π. +func Pi() Decimal { + return pi +} diff --git a/vendor/github.com/woodsbury/decimal128/convert.go b/vendor/github.com/woodsbury/decimal128/convert.go new file mode 100644 index 0000000..d1e16c6 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/convert.go @@ -0,0 +1,799 @@ +package decimal128 + +import ( + "math" + "math/big" + "math/bits" +) + +// FromFloat converts f into a Decimal. +func FromFloat(f *big.Float) Decimal { + if f.IsInf() { + return inf(f.Signbit()) + } + + if f.Sign() == 0 { + return zero(f.Signbit()) + } + + r, _ := f.Rat(nil) + return FromRat(r) +} + +// FromFloat32 converts f into a Decimal. +func FromFloat32(f float32) Decimal { + if math.IsNaN(float64(f)) { + return nan(payloadOpFromFloat32, 0, 0) + } + + return FromFloat64(float64(f)) +} + +// FromFloat64 converts f into a Decimal. +func FromFloat64(f float64) Decimal { + if math.IsNaN(f) { + return nan(payloadOpFromFloat64, 0, 0) + } + + if math.IsInf(f, 0) { + return inf(math.Signbit(f)) + } + + if f == 0.0 { + return zero(math.Signbit(f)) + } + + fbits := math.Float64bits(f) + mant := fbits & 0x000f_ffff_ffff_ffff + exp := int16(fbits >> 52 & 0x07ff) + neg := fbits&0x8000_0000_0000_0000 != 0 + + if exp == 0 { + exp = -1022 + } else { + mant |= 0x0010_0000_0000_0000 + exp -= 1023 + } + + shift := int(52 - exp) + + if shift == 0 { + return compose(neg, uint128{mant, 0}, exponentBias) + } + + var sig256 uint256 + exp = exponentBias + trunc := int8(0) + + if shift < 0 { + shift *= -1 + zeros := bits.LeadingZeros64(mant) + + if zeros > shift { + zeros = shift + } + + mant <<= zeros + shift -= zeros + + sig256 = uint256{mant, 0, 0, 0} + + if shift <= 192 { + sig256 = sig256.lsh(uint(shift)) + } else { + sig256 = sig256.lsh(192) + shift -= 192 + + for shift != 0 { + var rem uint64 + sig256, rem = sig256.div10() + exp++ + + if rem != 0 { + trunc = 1 + } + + zeros = bits.LeadingZeros64(sig256[3]) + if shift > zeros { + sig256 = sig256.lsh(uint(zeros)) + shift -= zeros + } else { + sig256 = sig256.lsh(uint(shift)) + break + } + } + } + } else { + zeros := bits.TrailingZeros64(mant) + + if zeros > shift { + zeros = shift + } + + mant >>= zeros + shift -= zeros + + if shift == 0 { + sig256 = uint256{mant, 0, 0, 0} + } else { + sig := uint128{mant, 0} + sig256 = sig.mul1e38() + exp -= 38 + + for shift != 0 { + zeros = bits.LeadingZeros64(sig256[3]) + for zeros >= 4 { + sig256 = sig256.mul64(10) + exp-- + zeros = bits.LeadingZeros64(sig256[3]) + } + + max := 4 - zeros + if shift < max { + max = shift + } + + zeros = bits.TrailingZeros64(sig256[0]) + + if zeros < max { + trunc = 1 + } + + sig256 = sig256.rsh(uint(max)) + shift -= max + } + } + } + + sig, exp := DefaultRoundingMode.reduce256(neg, sig256, exp, trunc) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// FromInt converts i into a Decimal. +func FromInt(i *big.Int) Decimal { + neg := false + if sgn := i.Sign(); sgn == 0 { + return zero(false) + } else if sgn < 0 { + neg = true + } + + exp := int16(exponentBias) + trunc := int8(0) + + if bl := i.BitLen(); bl > 128 { + i = new(big.Int).Set(i) + r := new(big.Int) + + if bl > 256 { + e18 := big.NewInt(1_000_000_000_000_000_000) + + for bl > 256 { + i.QuoRem(i, e18, r) + exp += 18 + + if exp > maxBiasedExponent { + return inf(neg) + } + + bl = i.BitLen() + + if r.Sign() != 0 { + trunc = 1 + } + } + } + + ten := big.NewInt(10) + + for bl > 128 { + i.QuoRem(i, ten, r) + exp++ + + if exp > maxBiasedExponent { + return inf(neg) + } + + bl = i.BitLen() + + if r.Sign() != 0 { + trunc = 1 + } + } + } + + var sig uint128 + + b := i.Bits() + for i := len(b) - 1; i >= 0; i-- { + sig = sig.lsh(bits.UintSize) + sig = sig.or64(uint64(b[i])) + } + + sig, exp = DefaultRoundingMode.reduce128(neg, sig, exp, trunc) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// FromInt32 converts i into a Decimal. +func FromInt32(i int32) Decimal { + return FromInt64(int64(i)) +} + +// FromInt64 converts i into a Decimal. +func FromInt64(i int64) Decimal { + if i == 0 { + return zero(false) + } + + neg := false + if i < 0 { + neg = true + i *= -1 + } + + return compose(neg, uint128{uint64(i), 0}, exponentBias) +} + +// FromRat converts r into a Decimal. +func FromRat(r *big.Rat) Decimal { + num := r.Num() + + if num.Sign() == 0 { + return zero(false) + } + + denom := r.Denom() + + return FromInt(num).Quo(FromInt(denom)) +} + +// FromUint32 converts i into a Decimal. +func FromUint32(i uint32) Decimal { + return FromUint64(uint64(i)) +} + +// FromUint64 converts i into a Decimal. +func FromUint64(i uint64) Decimal { + if i == 0 { + return zero(false) + } + + return compose(false, uint128{i, 0}, exponentBias) +} + +// Float converts d into a big.Float. If a non-nil argument f is provided, +// Float stores the result in f instead of allocating a new big.Float. It +// panics if d is NaN. +func (d Decimal) Float(f *big.Float) *big.Float { + if d.isSpecial() { + if d.IsNaN() { + panic("Decimal(NaN).Float()") + } + + if f == nil { + f = new(big.Float) + } else if f.Prec() == 0 { + f.SetPrec(128) + } + + return f.SetInf(d.Signbit()) + } + + sig, exp := d.decompose() + exp -= exponentBias + + if f == nil { + f = new(big.Float).SetPrec(128) + } else if f.Prec() == 0 { + f.SetPrec(128) + } + + if sig[1] == 0 { + f.SetUint64(sig[0]) + } else { + bigsig := new(big.Int).SetUint64(sig[1]) + bigsig.Lsh(bigsig, 64).Or(bigsig, new(big.Int).SetUint64(sig[0])) + + f.SetInt(bigsig) + } + + if d.Signbit() { + f.Neg(f) + } + + if exp == 0 { + return f + } + + var bigexp *big.Int + if exp > 0 { + bigexp = big.NewInt(int64(exp)) + } else { + bigexp = big.NewInt(int64(exp * -1)) + } + + bigexp.Exp(big.NewInt(10), bigexp, nil) + + if exp > 0 { + f.Mul(f, new(big.Float).SetInt(bigexp)) + } else { + f.Quo(f, new(big.Float).SetInt(bigexp)) + } + + return f +} + +// Float32 converts d into a float32. +func (d Decimal) Float32() float32 { + return float32(d.Float64()) +} + +// Float64 converts d into a float64. +func (d Decimal) Float64() float64 { + if d.isSpecial() { + if d.IsNaN() { + return math.NaN() + } + + if d.Signbit() { + return math.Inf(-1) + } + + return math.Inf(1) + } + + sig, exp := d.decompose() + + if sig[0]|sig[1] == 0 { + f := 0.0 + if d.Signbit() { + f = math.Copysign(f, -1.0) + } + + return f + } + + exp -= exponentBias + + if exp < -358 { + f := 0.0 + if d.Signbit() { + f = math.Copysign(f, -1.0) + } + + return f + } + + if exp > 308 { + if d.Signbit() { + return math.Inf(-1) + } + + return math.Inf(1) + } + + var sig256 uint256 + shift := int(exp) + exp = 0 + + if shift < 0 { + shift *= -1 + sig256 = uint256{0, 0, sig[0], sig[1]} + exp = -128 + + for shift != 0 { + zeros := bits.LeadingZeros64(sig256[3]) + sig256 = sig256.lsh(uint(zeros)) + exp -= int16(zeros) + + sig256, _ = sig256.div10() + shift-- + } + } else { + sig256 = uint256{sig[0], sig[1], 0, 0} + + for shift > 19 && sig256[3] == 0 { + sig256 = sig256.mul64(10_000_000_000_000_000_000) + shift -= 19 + } + + for shift != 0 && sig256[3] <= 0x18ff_ffff_ffff_ffff { + sig256 = sig256.mul64(10) + shift-- + } + + for shift != 0 { + sig256 = sig256.rsh(4) + exp += 4 + + for shift != 0 && sig256[3] <= 0x18ff_ffff_ffff_ffff { + sig256 = sig256.mul64(10) + shift-- + } + } + } + + zeros := bits.LeadingZeros64(sig256[3]) + for zeros != 0 { + sig256 = sig256.lsh(uint(zeros)) + exp -= int16(zeros) + zeros = bits.LeadingZeros64(sig256[3]) + } + + exp += 192 + f := float64(sig256[3]) + f = math.Ldexp(f, int(exp)) + + if d.Signbit() { + f = math.Copysign(f, -1.0) + } + + return f +} + +// Int converts d into a big.Int, truncating towards zero. If a non-nil +// argument i is provided, Int stores the result in i instead of allocating a +// new big.Int. It panics if d is NaN or infinite. +func (d Decimal) Int(i *big.Int) *big.Int { + if d.isSpecial() { + if d.IsNaN() { + panic("Decimal(NaN).Int()") + } + + if d.Signbit() { + panic("Decimal(-Inf).Int()") + } + + panic("Decimal(+Inf).Int()") + } + + sig, exp := d.decompose() + exp -= exponentBias + + if i == nil { + i = new(big.Int) + } + + if exp < -maxDigits { + return i + } + + if sig[1] == 0 { + i.SetUint64(sig[0]) + } else { + i.SetUint64(sig[1]) + i.Lsh(i, 64).Or(i, new(big.Int).SetUint64(sig[0])) + } + + if d.Signbit() { + i.Neg(i) + } + + if exp == 0 { + return i + } + + var bigexp *big.Int + if exp > 0 { + bigexp = big.NewInt(int64(exp)) + } else { + bigexp = big.NewInt(int64(exp * -1)) + } + + bigexp.Exp(big.NewInt(10), bigexp, nil) + + if exp > 0 { + i.Mul(i, bigexp) + } else { + i.Quo(i, bigexp) + } + + return i +} + +// Int32 converts d into an int32, truncating towards zero. If the result is +// outside the range of an int32 the returned value will be either +// [math.MinInt32] or [math.MaxInt32] depending on the sign of the result and +// the boolean value will be false. Otherwise the boolean value will be true. +// It panics if d is NaN. +func (d Decimal) Int32() (int32, bool) { + if d.isSpecial() { + if d.IsNaN() { + panic("Decimal(NaN).Int32()") + } + + if d.Signbit() { + return math.MinInt32, false + } + + return math.MaxInt32, false + } + + sig, exp := d.decompose() + exp -= exponentBias + + if exp < -maxDigits { + return 0, true + } + + for exp < 0 { + sig, _ = sig.div10() + exp++ + + if sig[0]|sig[1] == 0 { + exp = 0 + break + } + } + + for sig[1] == 0 && exp > 0 { + sig = sig.mul64(10) + exp-- + } + + if sig[1] != 0 || exp != 0 { + if d.Signbit() { + return math.MinInt32, false + } + + return math.MaxInt32, false + } + + neg := d.Signbit() + + if neg { + if sig[0] > math.MinInt32*-1 { + return math.MinInt32, false + } + } else { + if sig[0] > math.MaxInt32 { + return math.MaxInt32, false + } + } + + i := int32(sig[0]) + + if neg { + i *= -1 + } + + return i, true +} + +// Int64 converts d into an int64, truncating towards zero. If the result is +// outside the range of an int64 the returned value will be either +// [math.MinInt64] or [math.MaxInt64] depending on the sign of the result and +// the boolean value will be false. Otherwise the boolean value will be true. +// It panics if d is NaN. +func (d Decimal) Int64() (int64, bool) { + if d.isSpecial() { + if d.IsNaN() { + panic("Decimal(NaN).Int64()") + } + + if d.Signbit() { + return math.MinInt64, false + } + + return math.MaxInt64, false + } + + sig, exp := d.decompose() + exp -= exponentBias + + if exp < -maxDigits { + return 0, true + } + + for exp < 0 { + sig, _ = sig.div10() + exp++ + + if sig[0]|sig[1] == 0 { + exp = 0 + break + } + } + + for sig[1] == 0 && exp > 0 { + sig = sig.mul64(10) + exp-- + } + + if sig[1] != 0 || exp != 0 { + if d.Signbit() { + return math.MinInt64, false + } + + return math.MaxInt64, false + } + + neg := d.Signbit() + + if neg { + if sig[0] > math.MinInt64*-1 { + return math.MinInt64, false + } + } else { + if sig[0] > math.MaxInt64 { + return math.MaxInt64, false + } + } + + i := int64(sig[0]) + + if neg { + i *= -1 + } + + return i, true +} + +// Rat converts d into a big.Rat. If a non-nil argument r is provided, Rat +// stores the result in r instead of allocating a new big.Rat. It panics if d +// is NaN or infinite. +func (d Decimal) Rat(r *big.Rat) *big.Rat { + if d.isSpecial() { + if d.IsNaN() { + panic("Decimal(NaN).Rat()") + } + + if d.Signbit() { + panic("Decimal(-Inf).Rat()") + } + + panic("Decimal(+Inf).Rat()") + } + + sig, exp := d.decompose() + exp -= exponentBias + + if r == nil { + r = new(big.Rat) + } + + if exp == 0 && sig[1] == 0 { + r.SetUint64(sig[0]) + } else { + bigsig := new(big.Int).SetUint64(sig[1]) + bigsig.Lsh(bigsig, 64).Or(bigsig, new(big.Int).SetUint64(sig[0])) + + var bigexp *big.Int + if exp > 0 { + bigexp = big.NewInt(int64(exp)) + } else { + bigexp = big.NewInt(int64(exp * -1)) + } + + bigexp.Exp(big.NewInt(10), bigexp, nil) + + if exp > 0 { + bigsig.Mul(bigsig, bigexp) + r.SetInt(bigsig) + } else { + r.SetFrac(bigsig, bigexp) + } + } + + if d.Signbit() { + r.Neg(r) + } + + return r +} + +// Uint32 converts d into a uint32, truncating towards zero. If the result is +// outside the range of a uint32 the returned value will be either 0 or +// [math.MaxUint32] depending on the sign of the result and the boolean value +// will be false. Otherwise the boolean value will be true. It panics if d is +// NaN. +func (d Decimal) Uint32() (uint32, bool) { + if d.isSpecial() { + if d.IsNaN() { + panic("Decimal(NaN).Uint32()") + } + + if d.Signbit() { + return 0, false + } + + return math.MaxUint32, false + } + + if d.Signbit() { + return 0, false + } + + sig, exp := d.decompose() + exp -= exponentBias + + if exp < -maxDigits { + return 0, true + } + + for exp < 0 { + sig, _ = sig.div10() + exp++ + + if sig[0]|sig[1] == 0 { + exp = 0 + break + } + } + + for sig[1] == 0 && exp > 0 { + sig = sig.mul64(10) + exp-- + } + + if sig[1] != 0 || exp != 0 { + return math.MaxUint32, false + } + + if sig[0] > math.MaxUint32 { + return math.MaxUint32, false + } + + return uint32(sig[0]), true +} + +// Uint64 converts d into a uint64, truncating towards zero. If the result is +// outside the range of a uint64 the returned value will be either 0 or +// [math.MaxUint64] depending on the sign of the result and the boolean value +// will be false. Otherwise the boolean value will be true. It panics if d is +// NaN. +func (d Decimal) Uint64() (uint64, bool) { + if d.isSpecial() { + if d.IsNaN() { + panic("Decimal(NaN).Uint64()") + } + + if d.Signbit() { + return 0, false + } + + return math.MaxUint64, false + } + + if d.Signbit() { + return 0, false + } + + sig, exp := d.decompose() + exp -= exponentBias + + if exp < -maxDigits { + return 0, true + } + + for exp < 0 { + sig, _ = sig.div10() + exp++ + + if sig[0]|sig[1] == 0 { + exp = 0 + break + } + } + + for sig[1] == 0 && exp > 0 { + sig = sig.mul64(10) + exp-- + } + + if sig[1] != 0 || exp != 0 { + return math.MaxUint64, false + } + + return sig[0], true +} diff --git a/vendor/github.com/woodsbury/decimal128/decimal.go b/vendor/github.com/woodsbury/decimal128/decimal.go new file mode 100644 index 0000000..7cf9175 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/decimal.go @@ -0,0 +1,284 @@ +// Package decimal128 provides a 128-bit decimal floating point type. +package decimal128 + +const ( + exponentBias = 6176 + maxBiasedExponent = 12287 + maxUnbiasedExponent = maxBiasedExponent - exponentBias + minBiasedExponent = 0 + minUnbiasedExponent = minBiasedExponent - exponentBias + maxDigits = 35 +) + +// Decimal represents a 128-bit decimal floating point value. The zero value +// for Decimal is the number +0.0. +type Decimal struct { + lo, hi uint64 +} + +// Abs returns a new Decimal set to the absolute value of d. +func Abs(d Decimal) Decimal { + return Decimal{d.lo, d.hi & 0x7fff_ffff_ffff_ffff} +} + +// Frexp breaks a finite, non-zero d into a fraction and an integral power of +// ten. The absolute value of the fraction will be in the interval [0.1, 1). +// +// If d is ±Inf, NaN, or zero the value is returned unchanged and the returned +// power of ten is zero. +func Frexp(d Decimal) (Decimal, int) { + if d.isSpecial() || d.IsZero() { + return d, 0 + } + + sig, exp := d.decompose() + rexp := int(exp) - exponentBias + sig.log10() + 1 + exp -= int16(rexp) + + return compose(d.Signbit(), sig, exp), rexp +} + +// Inf returns a new Decimal set to positive infinity if sign >= 0, or negative +// infinity if sign < 0. +func Inf(sign int) Decimal { + return inf(sign < 0) +} + +// Ldexp is the inverse of [Frexp], returning frac × 10**exp. +func Ldexp(frac Decimal, exp int) Decimal { + if frac.isSpecial() || frac.IsZero() { + return frac + } + + if exp < minUnbiasedExponent { + return zero(frac.Signbit()) + } + + if exp > maxUnbiasedExponent+39 { + return inf(frac.Signbit()) + } + + neg := frac.Signbit() + fsig, fexp := frac.decompose() + fexp += int16(exp) + + sig, exp16 := DefaultRoundingMode.reduce128(neg, fsig, fexp, 0) + + if exp16 > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp16) +} + +// NaN returns a new Decimal set to the "not-a-number" value. +func NaN() Decimal { + return nan(payloadOpNaN, 0, 0) +} + +// New returns a new Decimal with the provided significand and exponent. +func New(sig int64, exp int) Decimal { + if sig == 0 { + return zero(false) + } + + neg := false + if sig < 0 { + neg = true + sig *= -1 + } + + if exp < minUnbiasedExponent+19 { + return zero(neg) + } + + if exp > maxUnbiasedExponent+39 { + return inf(neg) + } + + sig128, exp16 := DefaultRoundingMode.reduce64(neg, uint64(sig), int16(exp+exponentBias)) + + if exp16 > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig128, exp16) +} + +func compose(neg bool, sig uint128, exp int16) Decimal { + var hi uint64 + if sig[1] > 0x0001_ffff_ffff_ffff { + hi = 0x6000_0000_0000_0000 | uint64(exp)<<47 | sig[1]&0x7fff_ffff_ffff + } else { + hi = uint64(exp)<<49 | sig[1] + } + + if neg { + hi |= 0x8000_0000_0000_0000 + } + + return Decimal{sig[0], hi} +} + +func inf(neg bool) Decimal { + if neg { + return Decimal{0, 0xf800_0000_0000_0000} + } + + return Decimal{0, 0x7800_0000_0000_0000} +} + +func nan(op, lhs, rhs Payload) Decimal { + return Decimal{uint64(op | lhs<<8 | rhs<<16), 0x7c00_0000_0000_0000} +} + +func one(neg bool) Decimal { + if neg { + return Decimal{1, 0xb040_0000_0000_0000} + } + + return Decimal{1, 0x3040_0000_0000_0000} +} + +func zero(neg bool) Decimal { + if neg { + return Decimal{0, 0x8000_0000_0000_0000} + } + + return Decimal{} +} + +// Canonical returns the result of converting d into its canonical +// representation. Many values have multiple possible ways of being represented +// as a Decimal. Canonical converts each of these into a single representation. +// +// If d is ±Inf or NaN, the canonical representation consists of only the bits +// required to represent the respective special floating point value with all +// other bits set to 0. For NaN values this also removes any payload it may +// have had. +// +// If d is ±0, the canonical representation consists of only the sign bit set +// based on the sign of the value with all other bits set to 0. +// +// If d is finite and non-zero, the canonical representation is calculated as +// the representation with an exponent closest to zero that still accurately +// stores all non-zero digits the value has. +func (d Decimal) Canonical() Decimal { + if d.isSpecial() { + if d.IsNaN() { + return nan(0, 0, 0) + } + + return inf(d.Signbit()) + } + + sig, exp := d.decompose() + + if sig[0]|sig[1] == 0 { + return zero(d.Signbit()) + } + + for exp > exponentBias { + tmp := sig.mul64(10) + + if tmp[1] > 0x0002_7fff_ffff_ffff { + break + } + + sig = tmp + exp-- + } + + for exp < exponentBias { + tmp, rem := sig.div10() + + if rem != 0 { + break + } + + sig = tmp + exp++ + } + + return compose(d.Signbit(), sig, exp) +} + +// IsInf reports whether d is an infinity. If sign > 0, IsInf reports whether +// d is positive infinity. If sign < 0, IsInf reports whether d is negative +// infinity. If sign == 0, IsInf reports whether d is either infinity. +func (d Decimal) IsInf(sign int) bool { + if !d.isInf() { + return false + } + + if sign == 0 { + return true + } + + if sign > 0 { + return !d.Signbit() + } + + return d.Signbit() +} + +// IsNaN reports whether d is a "not-a-number" value. +func (d Decimal) IsNaN() bool { + return d.hi&0x7c00_0000_0000_0000 == 0x7c00_0000_0000_0000 +} + +// Neg returns d with its sign negated. +func (d Decimal) Neg() Decimal { + return Decimal{d.lo, d.hi ^ 0x8000_0000_0000_0000} +} + +// Sign returns: +// +// -1 if d < 0 +// 0 if d is ±0 +// +1 if d > 0 +// +// It panics if d is NaN. +func (d Decimal) Sign() int { + if d.IsNaN() { + panic("Decimal(NaN).Sign()") + } + + if d.IsZero() { + return 0 + } + + if d.Signbit() { + return -1 + } + + return 1 +} + +// Signbit reports whether d is negative or negative zero. +func (d Decimal) Signbit() bool { + return d.hi&0x8000_0000_0000_0000 == 0x8000_0000_0000_0000 +} + +func (d Decimal) decompose() (uint128, int16) { + var sig uint128 + var exp int16 + + if d.hi&0x6000_0000_0000_0000 == 0x6000_0000_0000_0000 { + sig = uint128{d.lo, d.hi&0x7fff_ffff_ffff | 0x0002_0000_0000_0000} + exp = int16(d.hi & 0x1fff_8000_0000_0000 >> 47) + } else { + sig = uint128{d.lo, d.hi & 0x0001_ffff_ffff_ffff} + exp = int16(d.hi & 0x7ffe_0000_0000_0000 >> 49) + } + + return sig, exp +} + +func (d Decimal) isInf() bool { + return d.hi&0x7c00_0000_0000_0000 == 0x7800_0000_0000_0000 +} + +func (d Decimal) isSpecial() bool { + return d.hi&0x7800_0000_0000_0000 == 0x7800_0000_0000_0000 +} diff --git a/vendor/github.com/woodsbury/decimal128/decomposed.go b/vendor/github.com/woodsbury/decimal128/decomposed.go new file mode 100644 index 0000000..ef74588 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/decomposed.go @@ -0,0 +1,1199 @@ +package decimal128 + +import ( + "math" + "strconv" +) + +const expln = -57 + +var ( + dinf = decomposed192{ + sig: uint192{math.MaxUint64, math.MaxUint64, math.MaxUint64}, + exp: math.MaxInt16, + } + + ln10 = decomposed192{ + sig: uint192{0x0193_5df2_0be5_35a1, 0x05cd_f4ae_1bee_93d0, 0x5de8_1c2d_b4b8_b6e0}, + exp: -57, + } + + ln2 = decomposed192{ + sig: uint192{0x858a_b5e3_0047_4e69, 0x37c6_e47a_55a3_5374, 0x1c44_cb52_b6b7_4c42}, + exp: -57, + } + + invLn10 = decomposed192{ + sig: uint192{0x225c_04d6_a014_36d9, 0xff32_111e_97d1_0db8, 0xb11e_75a6_72b6_07b7}, + exp: -58, + } + + invLn2 = decomposed192{ + sig: uint192{0xf291_3320_09f2_3937, 0x9975_90b2_7010_b870, 0x3ad6_7064_88d7_799b}, + exp: -57, + } + + ln = [...]uint192{ + {0xce06_052e_ed85_0b11, 0xf432_4af7_5d64_cfcb, 0x03e3_15af_624a_52e7}, // ln(1.1) + {0xb352_8e25_962a_8d07, 0xa21f_990f_44a0_1c4d, 0x076f_869f_7595_b691}, // ln(1.2) + {0x1a25_cea3_d4a1_52e2, 0xe179_b27c_1d59_58e7, 0x0ab3_35f2_7f80_2c14}, // ln(1.3) + {0xc29b_26c7_4da3_cc33, 0x447d_92a8_ae23_ba16, 0x0db8_ef1e_b627_bfbd}, // ln(1.4) + {0x2445_ca6e_a139_d76e, 0x0098_e04e_5fa4_b5c0, 0x1089_40d5_0628_88ab}, // ln(1.5) + {0x1497_7999_f538_0401, 0xd94d_9d3b_3a9e_ba02, 0x132b_111d_2624_7a28}, // ln(1.6) + {0xbe13_49be_566b_31c0, 0x53f1_7e04_1b66_c1da, 0x15a4_0476_af99_0d2e}, // ln(1.7) + {0xd798_5894_3764_6475, 0xa2b8_795d_a444_d20d, 0x17f8_c774_7bbe_3f3c}, // ln(1.8) + {0x3ebb_a665_59ef_25c9, 0x3825_0832_751f_5f0a, 0x1a2d_446a_7ec9_5e1c}, // ln(1.9) + {0x858a_b5e3_0047_4e69, 0x37c6_e47a_55a3_5374, 0x1c44_cb52_b6b7_4c42}, // ln(2.0) + {0xe6e0_f135_eedd_a3a1, 0x4516_72f7_0dc8_6fd6, 0x1e42_2ff3_bc50_4868}, // ln(2.1) + {0x5390_bb11_edcc_597a, 0x2bf9_2f71_b308_2340, 0x2027_e102_1901_9f2a}, // ln(2.2) + {0xaef4_f891_bf7e_1f8d, 0x4361_9f46_37f4_3cb8, 0x21f7_fa1c_4962_edb7}, // ln(2.3) + {0x38dd_4408_9671_db6f, 0xd9e6_7d89_9a43_6fc2, 0x23b4_51f2_2c4d_02d3}, // ln(2.4) + {0xf67d_f22c_0b56_98d0, 0x9640_2bb9_70a7_ece6, 0x255e_8588_474a_1e5b}, // ln(2.5) + {0x9fb0_8486_d4e8_a14a, 0x1940_96f6_72fc_ac5b, 0x26f8_0145_3637_7857}, // ln(2.6) + {0xfbde_2302_d89e_3be3, 0xa351_59ac_03e9_87cd, 0x2882_0849_81e6_c7e7}, // ln(2.7) + {0x4825_dcaa_4deb_1a9b, 0x7c44_7723_03c7_0d8b, 0x29fd_ba71_6cdf_0bff}, // ln(2.8) + {0xb3e9_e533_f3b6_1ca8, 0xffd0_5b4b_98de_4ef8, 0x2b6c_1948_c734_4a0d}, // ln(2.9) + {0xa9d0_8051_a181_25d7, 0x385f_c4c8_b548_0934, 0x2cce_0c27_bcdf_d4ed}, // ln(3.0) + {0xc27d_4089_0337_700b, 0x7146_7807_969e_299d, 0x2e24_63a2_f2ca_b5c1}, // ln(3.1) + {0x9a22_2f7c_f57f_526a, 0x1114_81b5_9042_0d76, 0x2f6f_dc6f_dcdb_c66b}, // ln(3.2) + {0x77d6_8580_8f06_30e8, 0x2c92_0fc0_12ac_d900, 0x30b1_21d7_1f2a_27d5}, // ln(3.3) + {0x439d_ffa1_56b2_8029, 0x8bb8_627e_710a_154f, 0x31e8_cfc9_6650_5970}, // ln(3.4) + {0xb919_18f3_58fa_6503, 0xdabd_be62_1ecb_a6fd, 0x3317_74a6_fd71_de18}, // ln(3.5) + {0x5d23_0e77_37ab_b2dd, 0xda7f_5dd7_f9e8_2582, 0x343d_92c7_3275_8b7e}, // ln(3.6) + {0x69d0_a4c1_549f_d69c, 0xdd0f_8e01_8b1e_e9c9, 0x355b_a1ca_1731_b507}, // ln(3.7) + {0xc446_5c48_5a36_7431, 0x6feb_ecac_cac2_b27e, 0x3672_0fbd_3580_aa5e}, // ln(3.8) + {0xc3f6_4ef5_7622_78b8, 0x19d9_7744_d2a1_621b, 0x3781_421a_3c60_0102}, // ln(3.9) + {0x0b15_6bc6_008e_9cd1, 0x6f8d_c8f4_ab46_a6e9, 0x3889_96a5_6d6e_9884}, // ln(4.0) + {0xe28e_8b7b_5605_80b2, 0x397b_4431_992e_98ee, 0x398b_6430_9250_5cf1}, // ln(4.1) + {0x6c6b_a718_ef24_f209, 0x7cdd_5771_636b_c34b, 0x3a86_fb46_7307_94aa}, // ln(4.2) + {0xd8b1_0e6e_1f3a_6af6, 0xa701_96a9_626a_a2b9, 0x3b7c_a6c2_1ebe_1cab}, // ln(4.3) + {0xd91b_70f4_ee13_a7e3, 0x63c0_13ec_08ab_76b4, 0x3c6c_ac54_cfb8_eb6c}, // ln(4.4) + {0xce16_4ac0_42ba_fd45, 0x38f8_a517_14ec_bef4, 0x3d57_4cfc_c308_5d98}, // ln(4.5) + {0x347f_ae74_bfc5_6df6, 0x7b28_83c0_8d97_902d, 0x3e3c_c56f_001a_39f9}, // ln(4.6) + {0x43e8_ca2c_117f_6c0f, 0x398b_8628_b08d_cde8, 0x3f1d_4e75_bfcd_2079}, // ln(4.7) + {0xbe67_f9eb_96b9_29d8, 0x11ad_6203_efe6_c336, 0x3ff9_1d44_e304_4f16}, // ln(4.8) + {0x7bb4_3fba_a69e_3135, 0x1f3b_510a_ccef_6114, 0x40d0_63c5_b399_9dd6}, // ln(4.9) + {0x7c08_a80f_0b9d_e739, 0xce07_1033_c64b_405b, 0x41a3_50da_fe01_6a9d}, // ln(5.0) + {0x67e3_ca0f_f7ec_5797, 0x8c51_42cc_d0ae_cb0f, 0x4272_109e_6c78_e21b}, // ln(5.1) + {0x253b_3a69_d52f_efb3, 0x5107_7b70_c89f_ffd0, 0x433c_cc97_ecee_c499}, // ln(5.2) + {0xab7a_5b8e_d6d1_7c47, 0x1b42_3605_7910_936c, 0x4403_abef_d001_c5a5}, // ln(5.3) + {0x8168_d8e5_d8e5_8a4b, 0xdb18_3e26_598c_db42, 0x44c6_d39c_389e_1429}, // ln(5.4) + {0x4a0e_ad3d_f922_f24a, 0xc239_5b2b_23b0_1027, 0x4586_668a_604b_bd85}, // ln(5.5) + {0xcdb0_928d_4e32_6904, 0xb40b_5b9d_596a_60ff, 0x4642_85c4_2396_5841}, // ln(5.6) + {0xe88c_26b6_fb70_4b9f, 0x7084_ccfb_2a67_683e, 0x46fb_5092_3ba9_3309}, // ln(5.7) + {0x3974_9b16_f3fd_6b11, 0x3797_3fc5_ee81_a26d, 0x47b0_e49b_7deb_9650}, // ln(5.8) + {0xd2c2_8970_025a_1047, 0xaf22_6b1b_a088_567d, 0x4863_5e01_6fc8_129d}, // ln(5.9) + {0x2f5b_3634_a1c8_743f, 0x7026_a943_0aeb_5ca9, 0x4912_d77a_7397_212f}, // ln(6.0) + {0x9b3c_dbdf_230c_2578, 0x9616_fdb5_2530_20ba, 0x49bf_6a69_cbaa_177b}, // ln(6.1) + {0x4807_f66c_037e_be74, 0xa90d_5c81_ec41_7d12, 0x4a69_2ef5_a982_0203}, // ln(6.2) + {0x90b1_7187_905e_c977, 0x7d76_37bf_c310_790b, 0x4b10_3c1b_7930_1d55}, // ln(6.3) + {0x1fac_e55f_f5c6_a0d3, 0x48db_662f_e5e5_60eb, 0x4bb4_a7c2_9393_12ad}, // ln(6.4) + {0x962e_76b2_e03f_3a1a, 0xaf80_c2af_e3a4_9942, 0x4c56_86cd_7d81_96b2}, // ln(6.5) + {0xfd61_3b63_8f4d_7f51, 0x6458_f43a_6850_2c74, 0x4cf5_ed29_d5e1_7417}, // ln(6.6) + {0x4d02_0691_ecd6_5f10, 0xdef9_cc6c_3618_5eca, 0x4d92_eddf_1115_ca85}, // ln(6.7) + {0xc928_b584_56f9_ce91, 0xc37f_46f8_c6ad_68c3, 0x4e2d_9b1c_1d07_a5b2}, // ln(6.8) + {0x58c5_78e3_60ff_4564, 0x7bc1_640e_ed3c_45ed, 0x4ec6_0644_0642_c2a4}, // ln(6.9) + {0x3ea3_ced6_5941_b36b, 0x1284_a2dc_746e_fa72, 0x4f5c_3ff9_b429_2a5b}, // ln(7.0) + {0xf07c_9f19_1e68_0638, 0x3b1b_d0da_b4d3_d782, 0x4ff0_582a_d010_bcaf}, // ln(7.1) + {0xe2ad_c45a_37f3_0146, 0x1246_4252_4f8b_78f6, 0x5082_5e19_e92c_d7c1}, // ln(7.2) + {0x7083_fc6a_d208_73b2, 0x5445_67d2_67ad_ca0f, 0x5112_6067_e56c_3166}, // ln(7.3) + {0xef5b_5aa4_54e7_2504, 0x14d6_727b_e0c2_3d3d, 0x51a0_6d1c_cde9_014a}, // ln(7.4) + {0xa04e_727d_acd7_bea7, 0xce9f_f082_25ef_f61b, 0x522c_91b0_0429_f348}, // ln(7.5) + {0x49d1_122b_5a7d_c29a, 0xa7b2_d127_2066_05f3, 0x52b6_db0f_ec37_f6a0}, // ln(7.6) + {0x0ca9_d405_46c6_be7d, 0x06b6_edd3_d1d3_ca3e, 0x533f_55a9_1673_7d43}, // ln(7.7) + {0x4981_04d8_7669_c721, 0x51a0_5bbf_2844_b590, 0x53c6_0d6c_f317_4d44}, // ln(7.8) + {0x5e09_218f_8e58_6ba3, 0xcf83_91e4_317a_22d1, 0x544b_0dd8_1874_4ff3}, // ln(7.9) + {0x90a0_21a9_00d5_eb3a, 0xa754_ad6f_00e9_fa5d, 0x54ce_61f8_2425_e4c6}, // ln(8.0) + {0xa5ae_a354_7a1f_61b9, 0xdbb1_1e74_b931_9102, 0x5550_1471_3ec6_9cd4}, // ln(8.1) + {0x6819_415e_564c_cf1b, 0x7142_28ab_eed1_ec63, 0x55d0_2f83_4907_a933}, // ln(8.2) + {0x8db3_4b47_6c54_a96b, 0x62b0_75c1_2fb5_6287, 0x564e_bd0e_b877_bb77}, // ln(8.3) + {0xf1f6_5cfb_ef6c_4072, 0xb4a4_3beb_b90f_16bf, 0x56cb_c699_29be_e0ec}, // ln(8.4) + {0x3a1b_f1cd_6209_18f9, 0x21f8_8e37_e1b2_0236, 0x5747_5551_ad9a_77cc}, // ln(8.5) + {0x5e3b_c451_1f81_b95f, 0xdec8_7b23_b80d_f62e, 0x57c1_7214_d575_68ed}, // ln(8.6) + {0x5dba_6585_9537_427f, 0x3830_2014_4e26_582d, 0x583a_2570_8414_1efb}, // ln(8.7) + {0x5ea6_26d7_ee5a_f64b, 0x9b86_f866_5e4e_ca29, 0x58b1_77a7_8670_37ae}, // ln(8.8) + {0x0068_a073_0412_de69, 0xf885_50ee_b461_f78d, 0x5927_70b4_f88c_b48f}, // ln(8.9) + {0x53a1_00a3_4302_4bad, 0x70bf_8991_6a90_1269, 0x599c_184f_79bf_a9da}, // ln(9.0) + {0x58c9_9d7a_2de3_064d, 0xf3fe_5558_91c8_5359, 0x5a0f_75ec_33a9_566f}, // ln(9.1) + {0xba0a_6457_c00c_bc5f, 0xb2ef_683a_e33a_e3a1, 0x5a81_90c1_b6d1_863b}, // ln(9.2) + {0x6c4d_c0da_a4b8_95e2, 0xa9a6_3cd0_4be6_32d2, 0x5af2_6fca_afaa_8aae}, // ln(9.3) + {0xc973_800f_11c6_ba78, 0x7152_6aa3_0631_215c, 0x5b62_19c8_7684_6cbb}, // ln(9.4) + {0xbac4_4e74_658d_0d01, 0x062c_1866_3b6a_9f65, 0x5bd0_9545_7cca_c8ba}, // ln(9.5) + {0x43f2_afce_9700_7841, 0x4974_467e_458a_16ab, 0x5c3d_e897_99bb_9b58}, // ln(9.6) + {0x933f_0a3f_f913_ea27, 0xaada_8272_21d0_0c36, 0x5caa_19e2_389c_c6c5}, // ln(9.7) + {0x013e_f59d_a6e5_7f9e, 0x5702_3585_2292_b489, 0x5d15_2f18_6a50_ea18}, // ln(9.8) + {0x21a7_05d2_3087_56bf, 0x64f1_d488_c7f4_e235, 0x5d7f_2dfe_dc09_fcc2}, // ln(9.9) + } +) + +type decomposed192 struct { + sig uint192 + exp int16 +} + +func (d decomposed192) String() string { + return d.sig.String() + "e" + strconv.FormatInt(int64(d.exp), 10) +} + +func (d decomposed192) add(o decomposed192, trunc int8) (decomposed192, int8) { + exp := d.exp - o.exp + + if exp < 0 { + for exp <= -19 && o.sig[2] == 0 { + o.sig = o.sig.mul64(10_000_000_000_000_000_000) + o.exp -= 19 + exp += 19 + } + + for exp <= -4 && o.sig[2] <= 0x0002_7fff_ffff_ffff { + o.sig = o.sig.mul64(10_000) + o.exp -= 4 + exp += 4 + } + + for exp < 0 && o.sig[2] <= 0x18ff_ffff_ffff_ffff { + o.sig = o.sig.mul64(10) + o.exp-- + exp++ + } + + if exp < -57 { + if d.sig[0]|d.sig[1]|d.sig[2] != 0 { + d.sig = uint192{} + trunc = 1 + } + + d.exp = o.exp + exp = 0 + } + + for exp <= -4 { + var rem uint64 + d.sig, rem = d.sig.div10000() + if rem != 0 { + trunc = 1 + } + + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + d.exp = o.exp + exp = 0 + } else { + d.exp += 4 + exp += 4 + } + } + + for exp < 0 { + var rem uint64 + d.sig, rem = d.sig.div10() + d.exp++ + exp++ + if rem != 0 { + trunc = 1 + } + } + } else if exp > 0 { + for exp >= 19 && d.sig[2] == 0 { + d.sig = d.sig.mul64(10_000_000_000_000_000_000) + d.exp -= 19 + exp -= 19 + } + + for exp >= 4 && d.sig[2] <= 0x0002_7fff_ffff_ffff { + d.sig = d.sig.mul64(10_000) + d.exp -= 4 + exp -= 4 + } + + for exp > 0 && d.sig[2] <= 0x18ff_ffff_ffff_ffff { + d.sig = d.sig.mul64(10) + d.exp-- + exp-- + } + + if exp > 57 { + if o.sig[0]|o.sig[1]|o.sig[2] != 0 { + o.sig = uint192{} + trunc = -1 + } + + exp = 0 + } + + for exp >= 4 { + var rem uint64 + o.sig, rem = o.sig.div10000() + if rem != 0 { + trunc = -1 + } + + if o.sig[0]|o.sig[1]|o.sig[2] == 0 { + exp = 0 + } else { + exp -= 4 + } + } + + for exp > 0 { + var rem uint64 + o.sig, rem = o.sig.div10() + exp-- + if rem != 0 { + trunc = 1 + } + } + } + + sig256 := d.sig.add(o.sig) + exp = d.exp + + for sig256[3] >= 0x0000_0000_0000_ffff { + var rem uint64 + sig256, rem = sig256.div10000() + exp += 4 + + if rem != 0 { + trunc = 1 + } + } + + for sig256[3] > 0 { + var rem uint64 + sig256, rem = sig256.div10() + exp++ + if rem != 0 { + trunc = 1 + } + } + + return decomposed192{ + sig: uint192{sig256[0], sig256[1], sig256[2]}, + exp: exp, + }, trunc +} + +func (d decomposed192) add1(trunc int8) (decomposed192, int8) { + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + return decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, trunc + } + + if d.exp < -116 { + return decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, 1 + } + + if d.exp > 58 { + return d, 1 + } + + var sig256 uint256 + + if d.exp <= 0 { + for d.exp < -62 { + var rem uint64 + d.sig, rem = d.sig.div10000() + if rem != 0 { + trunc = 1 + } + + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + return decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, trunc + } + + d.exp += 4 + } + + for d.exp < -57 { + var rem uint64 + d.sig, rem = d.sig.div10() + d.exp++ + if rem != 0 { + trunc = 1 + } + + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + return decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, trunc + } + } + + sig256 = d.sig.add(uint192PowersOf10[-d.exp]) + } else { + for d.exp > 4 && d.sig[2] <= 0x0002_7fff_ffff_ffff { + d.sig = d.sig.mul64(10_000) + d.exp -= 4 + } + + for d.exp > 0 && d.sig[2] <= 0x18ff_ffff_ffff_ffff { + d.sig = d.sig.mul64(10) + d.exp-- + } + + if d.exp != 0 { + return d, 1 + } + + sig256 = d.sig.add(uint192{1, 0, 0}) + } + + if sig256[3] != 0 { + var rem uint64 + sig256, rem = sig256.div10() + d.exp++ + + if rem != 0 { + trunc = 1 + } + } + + return decomposed192{ + sig: uint192{sig256[0], sig256[1], sig256[2]}, + exp: d.exp, + }, trunc +} + +func (d decomposed192) add1neg(trunc int8) (bool, decomposed192, int8) { + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + return false, decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, trunc + } + + if d.exp < -116 { + return false, decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, trunc + } + + if d.exp > 58 { + return true, d, 1 + } + + var sig uint192 + var brw uint + + if d.exp <= 0 { + for d.exp < -62 { + var rem uint64 + d.sig, rem = d.sig.div10000() + if rem != 0 { + trunc = 1 + } + + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + return false, decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, trunc + } + + d.exp += 4 + } + + for d.exp < -57 { + var rem uint64 + d.sig, rem = d.sig.div10() + d.exp++ + if rem != 0 { + trunc = 1 + } + + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + return false, decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, trunc + } + } + + sig, brw = uint192PowersOf10[-d.exp].sub(d.sig) + } else { + for d.exp > 4 && d.sig[2] <= 0x0002_7fff_ffff_ffff { + d.sig = d.sig.mul64(10_000) + d.exp -= 4 + } + + for d.exp > 0 && d.sig[2] <= 0x18ff_ffff_ffff_ffff { + d.sig = d.sig.mul64(10) + d.exp-- + } + + if d.exp != 0 { + return true, d, 1 + } + + sig, brw = uint192{1, 0, 0}.sub(d.sig) + } + + neg := false + + if brw != 0 { + sig = sig.twos() + neg = true + trunc *= -1 + } + + return neg, decomposed192{ + sig: sig, + exp: d.exp, + }, trunc +} + +func (d decomposed192) epow(l10 int16, trunc int8) (decomposed192, int8) { + exp := d.exp + l10 + 1 + if exp < 0 { + exp = 0 + } else { + d.exp = -l10 - 1 + } + + for d.sig[2] <= 0x0002_7fff_ffff_ffff { + d.sig = d.sig.mul64(10_000) + d.exp -= 4 + } + + for d.sig[2] <= 0x18ff_ffff_ffff_ffff { + d.sig = d.sig.mul64(10) + d.exp-- + } + + res, trunc := d.quo(decomposed192{ + sig: uint192{40, 0, 0}, + exp: 0, + }, trunc) + + for i := uint64(39); i > 1; i-- { + tmp, _ := d.quo(decomposed192{ + sig: uint192{i, 0, 0}, + exp: 0, + }, int8(0)) + + res, trunc = res.mul(tmp, trunc) + res, trunc = res.add1(trunc) + } + + res, trunc = res.mul(d, trunc) + res, trunc = res.add1(trunc) + + return res.powexp10(exp, trunc) +} + +func (d decomposed192) epowm1(neg bool, l10 int16, trunc int8) (bool, decomposed192, int8) { + exp := d.exp + l10 + 1 + if exp < 0 { + exp = 0 + } else { + d.exp = -l10 - 1 + } + + for d.sig[2] <= 0x0002_7fff_ffff_ffff { + d.sig = d.sig.mul64(10_000) + d.exp -= 4 + } + + for d.sig[2] <= 0x18ff_ffff_ffff_ffff { + d.sig = d.sig.mul64(10) + d.exp-- + } + + res, trunc := d.quo(decomposed192{ + sig: uint192{40, 0, 0}, + exp: 0, + }, trunc) + + for i := uint64(39); i > 1; i-- { + tmp, _ := d.quo(decomposed192{ + sig: uint192{i, 0, 0}, + exp: 0, + }, int8(0)) + + res, trunc = res.mul(tmp, trunc) + res, trunc = res.add1(trunc) + } + + res, trunc = res.mul(d, trunc) + + if res.exp > maxUnbiasedExponent+58 { + if neg { + return true, decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, 0 + } + + return false, dinf, 0 + } + + if exp == 0 { + if neg { + res, trunc = res.add1(trunc) + res, trunc = res.rcp(trunc) + return res.sub1(trunc) + } + + return false, res, trunc + } + + res, trunc = res.add1(trunc) + res, trunc = res.powexp10(exp, trunc) + + if neg { + res, trunc = res.rcp(trunc) + } + + return res.sub1(trunc) +} + +func (d decomposed192) log() (bool, decomposed192, int8) { + l10 := int16(d.sig.log10()) + exp := d.exp + l10 + d.exp = -l10 + + msd := d.sig.msd2() + + for d.sig[2] == 0 { + d.sig = d.sig.mul64(10_000_000_000_000_000_000) + d.exp -= 19 + } + + for d.sig[2] <= 0x0002_7fff_ffff_ffff { + d.sig = d.sig.mul64(10_000) + d.exp -= 4 + } + + for d.sig[2] <= 0x18ff_ffff_ffff_ffff { + d.sig = d.sig.mul64(10) + d.exp-- + } + + if msd < 10 { + msd *= 10 + } + + var trunc int8 + if msd > 10 { + d, trunc = d.quo(decomposed192{ + sig: uint192{uint64(msd), 0, 0}, + exp: -1, + }, 0) + } + + _, num, _ := d.sub1(int8(0)) + den, _ := d.add1(int8(0)) + frc, trunc := num.quo(den, trunc) + sqr, _ := frc.pow2(int8(0)) + + res := frc + + for i := uint64(3); i <= 25; i += 2 { + // res += frc^i / i + frc, _ = frc.mul(sqr, int8(0)) + tmp, _ := frc.quo(decomposed192{ + sig: uint192{i, 0, 0}, + exp: 0, + }, int8(0)) + + res, trunc = res.add(tmp, trunc) + } + + expNeg := false + if exp < 0 { + exp *= -1 + expNeg = true + } + + lnExp, _ := ln10.mul(decomposed192{ + sig: uint192{uint64(exp), 0, 0}, + exp: 0, + }, int8(0)) + + res, trunc = res.mul(decomposed192{ + sig: uint192{2, 0, 0}, + exp: 0, + }, trunc) + + neg := false + if expNeg { + neg, res, trunc = res.sub(lnExp, trunc) + } else { + res, trunc = res.add(lnExp, trunc) + } + + if msd > 10 { + lnMSD := decomposed192{ + sig: ln[msd-11], + exp: expln, + } + + if expNeg { + _, res, trunc = res.sub(lnMSD, trunc) + } else { + res, trunc = res.add(lnMSD, trunc) + } + } + + return neg, res, trunc +} + +func (d decomposed192) log1p(neg bool) (bool, decomposed192, int8) { + num := d + res := d + + var trunc int8 + for i := uint64(2); i <= 10; i++ { + num, trunc = num.mul(d, trunc) + tmp, _ := num.quo(decomposed192{ + sig: uint192{i, 0, 0}, + exp: 0, + }, int8(0)) + + if i%2 == 0 { + if neg { + res, trunc = res.add(tmp, trunc) + } else { + _, res, trunc = res.sub(tmp, trunc) + } + } else { + res, trunc = res.add(tmp, trunc) + } + } + + return neg, res, trunc +} + +func (d decomposed192) mul(o decomposed192, trunc int8) (decomposed192, int8) { + sig384 := d.sig.mul(o.sig) + exp := d.exp + o.exp + + for sig384[5] > 0 { + var rem uint64 + sig384, rem = sig384.div1e19() + exp += 19 + + if rem != 0 { + trunc = 1 + } + } + + for sig384[4] > 0 { + var rem uint64 + sig384, rem = sig384.div1e19() + exp += 19 + + if rem != 0 { + trunc = 1 + } + } + + sig256 := uint256{sig384[0], sig384[1], sig384[2], sig384[3]} + + for sig256[3] >= 0x0000_0000_0fff_ffff { + var rem uint64 + sig256, rem = sig256.div1e8() + exp += 8 + + if rem != 0 { + trunc = 1 + } + } + + for sig256[3] >= 0x0000_0000_0000_ffff { + var rem uint64 + sig256, rem = sig256.div10000() + exp += 4 + + if rem != 0 { + trunc = 1 + } + } + + for sig256[3] > 0 { + var rem uint64 + sig256, rem = sig256.div10() + exp++ + + if rem != 0 { + trunc = 1 + } + } + + return decomposed192{ + sig: uint192{sig256[0], sig256[1], sig256[2]}, + exp: exp, + }, trunc +} + +func (d decomposed192) pow2(trunc int8) (decomposed192, int8) { + sig384 := d.sig.pow2() + exp := d.exp * 2 + + for sig384[5] > 0 { + var rem uint64 + sig384, rem = sig384.div1e19() + exp += 19 + + if rem != 0 { + trunc = 1 + } + } + + for sig384[4] > 0 { + var rem uint64 + sig384, rem = sig384.div1e19() + exp += 19 + + if rem != 0 { + trunc = 1 + } + } + + sig256 := uint256{sig384[0], sig384[1], sig384[2], sig384[3]} + + for sig256[3] >= 0x0000_0000_0000_ffff { + var rem uint64 + sig256, rem = sig256.div10000() + exp += 4 + + if rem != 0 { + trunc = 1 + } + } + + for sig256[3] > 0 { + var rem uint64 + sig256, rem = sig256.div10() + exp++ + + if rem != 0 { + trunc = 1 + } + } + + return decomposed192{ + sig: uint192{sig256[0], sig256[1], sig256[2]}, + exp: exp, + }, trunc +} + +func (d decomposed192) powexp10(o int16, trunc int8) (decomposed192, int8) { + var p10 int64 + switch o { + case 0: + return d, trunc + case 1: + p10 = 10 + case 2: + p10 = 100 + case 3: + p10 = 1_000 + case 4: + p10 = 10_000 + case 5: + p10 = 100_000 + case 6: + p10 = 1_000_000 + case 7: + p10 = 10_000_000 + case 8: + return dinf, trunc + } + + rtrunc := trunc + r := decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + } + + for p10 > 1 { + if int64(d.exp)*2 > math.MaxInt16-58*2 { + return dinf, trunc + } + + if p10&1 != 0 { + r, rtrunc = d.mul(r, rtrunc) + p10-- + } + + d, trunc = d.mul(d, trunc) + p10 /= 2 + } + + if int64(d.exp)+int64(r.exp) > math.MaxInt16 { + return dinf, trunc + } + + if rtrunc != 0 { + trunc = 1 + } + + return d.mul(r, trunc) +} + +func (d decomposed192) quo(o decomposed192, trunc int8) (decomposed192, int8) { + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + return decomposed192{ + sig: uint192{}, + exp: 0, + }, trunc + } + + for d.sig[2] == 0 { + d.sig = d.sig.mul64(10_000_000_000_000_000_000) + d.exp -= 19 + } + + for d.sig[2] <= 0x0002_7fff_ffff_ffff { + d.sig = d.sig.mul64(10_000) + d.exp -= 4 + } + + for d.sig[2] <= 0x18ff_ffff_ffff_ffff { + d.sig = d.sig.mul64(10) + d.exp-- + } + + for o.sig[2] >= 0x18ff_ffff_ffff_ffff { + var rem uint64 + o.sig, rem = o.sig.div10() + o.exp++ + + if rem != 0 { + trunc = 1 + } + } + + sig, rem := d.sig.div(o.sig) + exp := d.exp - o.exp + + for rem[0]|rem[1]|rem[2] != 0 && sig[2] <= 0x18ff_ffff_ffff_ffff { + for rem[2] <= 0x0002_7fff_ffff_ffff && sig[2] <= 0x0002_7fff_ffff_ffff { + rem = rem.mul64(10_000) + sig = sig.mul64(10_000) + exp -= 4 + } + + for rem[2] <= 0x18ff_ffff_ffff_ffff && sig[2] <= 0x18ff_ffff_ffff_ffff { + rem = rem.mul64(10) + sig = sig.mul64(10) + exp-- + } + + var tmp uint192 + tmp, rem = rem.div(o.sig) + sig256 := sig.add(tmp) + + for sig256[3] != 0 { + var rem uint64 + sig256, rem = sig256.div10() + exp++ + + if rem != 0 { + trunc = 1 + } + } + + sig = uint192{sig256[0], sig256[1], sig256[2]} + } + + if rem[0]|rem[1]|rem[2] != 0 { + trunc = 1 + } + + return decomposed192{ + sig: sig, + exp: exp, + }, trunc +} + +func (d decomposed192) rcp(trunc int8) (decomposed192, int8) { + oneSig := uint192{0x4a00_0000_0000_0000, 0xebfd_cb54_864a_da83, 0x28c8_7cb5_c89a_2571} + + for d.sig[2] >= 0x18ff_ffff_ffff_ffff { + var rem uint64 + d.sig, rem = d.sig.div10() + d.exp++ + + if rem != 0 { + trunc = 1 + } + } + + sig, rem := oneSig.div(d.sig) + exp := -57 - d.exp + + for rem[0]|rem[1]|rem[2] != 0 && sig[2] <= 0x18ff_ffff_ffff_ffff { + for rem[2] <= 0x0002_7fff_ffff_ffff && sig[2] <= 0x0002_7fff_ffff_ffff { + rem = rem.mul64(10_000) + sig = sig.mul64(10_000) + exp -= 4 + } + + for rem[2] <= 0x18ff_ffff_ffff_ffff && sig[2] <= 0x18ff_ffff_ffff_ffff { + rem = rem.mul64(10) + sig = sig.mul64(10) + exp-- + } + + var tmp uint192 + tmp, rem = rem.div(d.sig) + sig256 := sig.add(tmp) + + for sig256[3] != 0 { + var rem uint64 + sig256, rem = sig256.div10() + exp++ + + if rem != 0 { + trunc = 1 + } + } + + sig = uint192{sig256[0], sig256[1], sig256[2]} + } + + if rem[0]|rem[1]|rem[2] != 0 { + trunc = 1 + } + + return decomposed192{ + sig: sig, + exp: exp, + }, trunc +} + +func (d decomposed192) sub(o decomposed192, trunc int8) (bool, decomposed192, int8) { + exp := d.exp - o.exp + + if exp < 0 { + for exp <= -19 && o.sig[2] == 0 { + o.sig = o.sig.mul64(10_000_000_000_000_000_000) + o.exp -= 19 + exp += 19 + } + + for exp <= -4 && o.sig[2] <= 0x0002_7fff_ffff_ffff { + o.sig = o.sig.mul64(10_000) + o.exp -= 4 + exp += 4 + } + + for exp < 0 && o.sig[2] <= 0x18ff_ffff_ffff_ffff { + o.sig = o.sig.mul64(10) + o.exp-- + exp++ + } + + if exp < -57 { + if d.sig[0]|d.sig[1]|d.sig[2] != 0 { + d.sig = uint192{} + trunc = 1 + } + + d.exp = o.exp + exp = 0 + } + + for exp <= -4 { + var rem uint64 + d.sig, rem = d.sig.div10000() + if rem != 0 { + trunc = 1 + } + + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + d.exp = o.exp + exp = 0 + } else { + d.exp += 4 + exp += 4 + } + } + + for exp < 0 { + var rem uint64 + d.sig, rem = d.sig.div10() + if rem != 0 { + trunc = 1 + } + + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + d.exp = o.exp + break + } + + d.exp++ + exp++ + } + } else if exp > 0 { + for exp >= 19 && d.sig[2] == 0 { + d.sig = d.sig.mul64(10_000_000_000_000_000_000) + d.exp -= 19 + exp -= 19 + } + + for exp >= 4 && d.sig[2] <= 0x0002_7fff_ffff_ffff { + d.sig = d.sig.mul64(10_000) + d.exp -= 4 + exp -= 4 + } + + for exp > 0 && d.sig[2] <= 0x18ff_ffff_ffff_ffff { + d.sig = d.sig.mul64(10) + d.exp-- + exp-- + } + + if exp > 57 { + if o.sig[0]|o.sig[1]|o.sig[2] != 0 { + o.sig = uint192{} + trunc = -1 + } + + exp = 0 + } + + for exp >= 4 { + var rem uint64 + o.sig, rem = o.sig.div10000() + if rem != 0 { + trunc = -1 + } + + if o.sig[0]|o.sig[1]|o.sig[2] == 0 { + exp = 0 + } else { + exp -= 4 + } + } + + for exp > 0 { + var rem uint64 + o.sig, rem = o.sig.div10() + if rem != 0 { + trunc = -1 + } + + if o.sig[0]|o.sig[1]|o.sig[2] == 0 { + break + } + + exp-- + } + } + + neg := false + sig, brw := d.sig.sub(o.sig) + exp = d.exp + + if brw != 0 { + sig = sig.twos() + neg = true + trunc *= -1 + } + + return neg, decomposed192{ + sig: sig, + exp: exp, + }, trunc +} + +func (d decomposed192) sub1(trunc int8) (bool, decomposed192, int8) { + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + return true, decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, trunc + } + + if d.exp < -116 { + return true, decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, 1 + } + + if d.exp > 58 { + return false, d, 1 + } + + var sig uint192 + var brw uint + + if d.exp <= 0 { + for d.exp < -62 { + var rem uint64 + d.sig, rem = d.sig.div10000() + if rem != 0 { + trunc = 1 + } + + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + return true, decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, trunc + } + + d.exp += 4 + } + + for d.exp < -57 { + var rem uint64 + d.sig, rem = d.sig.div10() + d.exp++ + if rem != 0 { + trunc = 1 + } + + if d.sig[0]|d.sig[1]|d.sig[2] == 0 { + return true, decomposed192{ + sig: uint192{1, 0, 0}, + exp: 0, + }, trunc + } + } + + sig, brw = d.sig.sub(uint192PowersOf10[-d.exp]) + } else { + for d.exp > 4 && d.sig[2] <= 0x0002_7fff_ffff_ffff { + d.sig = d.sig.mul64(10_000) + d.exp -= 4 + } + + for d.exp > 0 && d.sig[2] <= 0x18ff_ffff_ffff_ffff { + d.sig = d.sig.mul64(10) + d.exp-- + } + + if d.exp != 0 { + return false, d, 1 + } + + sig, brw = d.sig.sub(uint192{1, 0, 0}) + } + + neg := false + + if brw != 0 { + sig = sig.twos() + neg = true + trunc *= -1 + } + + return neg, decomposed192{ + sig: sig, + exp: d.exp, + }, trunc +} diff --git a/vendor/github.com/woodsbury/decimal128/exp.go b/vendor/github.com/woodsbury/decimal128/exp.go new file mode 100644 index 0000000..3ee67c1 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/exp.go @@ -0,0 +1,783 @@ +package decimal128 + +import "math/bits" + +// Cbrt returns the cube root of d. +func Cbrt(d Decimal) Decimal { + if d.isSpecial() || d.IsZero() { + return d + } + + dSig, dExp := d.decompose() + dExp -= exponentBias + l10 := int16(dSig.log10()) + + d192 := decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp, + } + + d192x2 := decomposed192{ + sig: d192.sig.lsh(1), + exp: dExp, + } + + exp := dExp + l10 + if exp < 0 { + exp++ + } + + dExp -= exp - exp/3 + + res := decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp, + } + + var trunc int8 + for i := 0; i < 7; i++ { + cub, _ := res.mul(res, int8(0)) + cub, _ = cub.mul(res, int8(0)) + + num, _ := cub.add(d192x2, int8(0)) + den, _ := cub.add(cub, int8(0)) + den, _ = den.add(d192, int8(0)) + frc, _ := num.quo(den, int8(0)) + res, trunc = res.mul(frc, trunc) + } + + neg := d.Signbit() + sig, exp := DefaultRoundingMode.reduce192(neg, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// Exp returns e**d, the base-e exponential of d. +func Exp(d Decimal) Decimal { + if d.isSpecial() { + if d.IsNaN() { + return d + } + + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + if d.IsZero() { + return one(false) + } + + dSig, dExp := d.decompose() + dExp -= exponentBias + l10 := dSig.log10() + + if int(dExp) > 5-l10 { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + res, trunc := decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp, + }.epow(int16(l10), int8(0)) + + if res.exp > maxUnbiasedExponent+58 { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + if d.Signbit() { + res, trunc = res.rcp(trunc) + } + + sig, exp := DefaultRoundingMode.reduce192(false, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + return compose(false, sig, exp) +} + +// Exp10 returns 10**d, the base-10 exponential of d. +func Exp10(d Decimal) Decimal { + if d.isSpecial() { + if d.IsNaN() { + return d + } + + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + if d.IsZero() { + return one(false) + } + + dSig, dExp := d.decompose() + dExp -= exponentBias + l10 := dSig.log10() + + if int(dExp) > 4-l10 { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + var dSigInt uint + if l10+int(dExp) >= 0 { + sig := dSig + exp := dExp + dSig = uint128{} + + for exp < 0 { + var rem uint64 + sig, rem = sig.div10() + + dSig = dSig.mul64(10) + dSig = dSig.add64(rem) + exp++ + } + + dSigInt = uint(sig[0]) + + for exp > 0 { + dSigInt *= 10 + exp-- + } + + if dSigInt > maxUnbiasedExponent+58 { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + sig = dSig + dSig = uint128{} + dExp = 0 + + for sig[0]|sig[1] != 0 { + var rem uint64 + sig, rem = sig.div10() + + dSig = dSig.mul64(10) + dSig = dSig.add64(rem) + dExp-- + } + } + + var res decomposed192 + var trunc int8 + + var sigInt uint128 + var expInt int16 + + if dSigInt != 0 { + sigInt = uint128{1, 0} + + for dSigInt > maxUnbiasedExponent { + sigInt = sigInt.mul64(10) + dSigInt-- + } + + expInt = int16(dSigInt) + } + + if dSig[0]|dSig[1] != 0 { + res, trunc = decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp, + }.mul(ln10, int8(0)) + + res, trunc = res.epow(int16(res.sig.log10()), trunc) + + if res.exp > maxUnbiasedExponent+58 { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + if expInt != 0 { + res.exp += expInt + } + } else { + res = decomposed192{ + sig: uint192{1, 0, 0}, + exp: expInt, + } + } + + if res.exp > maxUnbiasedExponent+58 { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + if d.Signbit() { + res, trunc = res.rcp(trunc) + } + + sig, exp := DefaultRoundingMode.reduce192(false, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + return compose(false, sig, exp) +} + +// Exp2 returns 2**d, the base-2 exponential of d. +func Exp2(d Decimal) Decimal { + if d.isSpecial() { + if d.IsNaN() { + return d + } + + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + if d.IsZero() { + return one(false) + } + + dSig, dExp := d.decompose() + dExp -= exponentBias + l10 := dSig.log10() + + if int(dExp) > 5-l10 { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + var dSigInt uint + if l10+int(dExp) >= 0 { + sig := dSig + exp := dExp + dSig = uint128{} + + for exp < 0 { + var rem uint64 + sig, rem = sig.div10() + + dSig = dSig.mul64(10) + dSig = dSig.add64(rem) + exp++ + } + + dSigInt = uint(sig[0]) + + for exp > 0 { + dSigInt *= 10 + exp-- + } + + sig = dSig + dSig = uint128{} + dExp = 0 + + for sig[0]|sig[1] != 0 { + var rem uint64 + sig, rem = sig.div10() + + dSig = dSig.mul64(10) + dSig = dSig.add64(rem) + dExp-- + } + } + + var res decomposed192 + var trunc int8 + + var sigInt uint192 + var expInt int16 + + if dSigInt != 0 { + if dSigInt > exponentBias+maxDigits { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + shift := dSigInt + + if shift < 64 { + sigInt[0] = 1 << shift + } else if shift < 128 { + sigInt[1] = 1 << (shift - 64) + } else { + var sigInt256 uint256 + if shift < 192 { + sigInt256[2] = 1 << (shift - 128) + } else if shift < 256 { + sigInt256[3] = 1 << (shift - 192) + } else { + sigInt256[3] = 0x8000_0000_0000_0000 + shift -= 255 + + for shift > 0 { + var rem uint64 + sigInt256, rem = sigInt256.div10() + expInt++ + + if rem != 0 { + trunc = 1 + } + + zeros := uint(bits.LeadingZeros64(sigInt256[3])) + if shift > zeros { + sigInt256 = sigInt256.lsh(zeros) + shift -= zeros + } else { + sigInt256 = sigInt256.lsh(shift) + break + } + } + } + + for sigInt256[3] > 0 { + var rem uint64 + sigInt256, rem = sigInt256.div1e19() + expInt += 19 + + if rem != 0 { + trunc = 1 + } + } + + sigInt = uint192{sigInt256[0], sigInt256[1], sigInt256[2]} + } + } + + if dSig[0]|dSig[1] != 0 { + res, trunc = decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp, + }.mul(ln2, int8(0)) + + res, trunc = res.epow(int16(res.sig.log10()), trunc) + + if res.exp > maxUnbiasedExponent+58 { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + if dSigInt != 0 { + res, trunc = decomposed192{ + sig: sigInt, + exp: expInt, + }.mul(res, trunc) + } + } else { + res = decomposed192{ + sig: sigInt, + exp: expInt, + } + } + + if res.exp > maxUnbiasedExponent+maxDigits { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + if d.Signbit() { + res, trunc = res.rcp(trunc) + } + + sig, exp := DefaultRoundingMode.reduce192(false, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + if d.Signbit() { + return zero(false) + } + + return inf(false) + } + + return compose(false, sig, exp) +} + +// Expm1 returns e**d - 1, the base-e exponential of d minus 1. It is more +// accurate than Exp(d) - 1 when d is near zero. +func Expm1(d Decimal) Decimal { + if d.isSpecial() { + if d.IsNaN() { + return d + } + + if d.Signbit() { + return one(true) + } + + return inf(false) + } + + if d.IsZero() { + return zero(false) + } + + dSig, dExp := d.decompose() + dExp -= exponentBias + l10 := dSig.log10() + + if int(dExp) > 5-l10 { + if d.Signbit() { + return one(true) + } + + return inf(false) + } + + neg, res, trunc := decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp, + }.epowm1(d.Signbit(), int16(l10), int8(0)) + + if res.exp > maxUnbiasedExponent+58 { + if d.Signbit() { + return one(true) + } + + return inf(false) + } + + sig, exp := DefaultRoundingMode.reduce192(neg, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + if d.Signbit() { + return one(true) + } + + return inf(false) + } + + return compose(neg, sig, exp) +} + +// Log returns the natural logarithm of d. +func Log(d Decimal) Decimal { + if d.isSpecial() { + if d.IsNaN() { + return d + } + + if d.Signbit() { + return nan(payloadOpLog, payloadValNegInfinite, 0) + } + + return inf(false) + } + + if d.IsZero() { + return inf(true) + } + + if d.Signbit() { + return nan(payloadOpLog, payloadValNegFinite, 0) + } + + dSig, dExp := d.decompose() + + neg, res, trunc := decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp - exponentBias, + }.log() + + sig, exp := DefaultRoundingMode.reduce192(neg, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// Log10 returns the decimal logarithm of d. +func Log10(d Decimal) Decimal { + if d.isSpecial() { + if d.IsNaN() { + return d + } + + if d.Signbit() { + return nan(payloadOpLog10, payloadValNegInfinite, 0) + } + + return inf(false) + } + + if d.IsZero() { + return inf(true) + } + + if d.Signbit() { + return nan(payloadOpLog10, payloadValNegFinite, 0) + } + + dSig, dExp := d.decompose() + + neg, res, trunc := decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp - exponentBias, + }.log() + + res, trunc = res.mul(invLn10, trunc) + + sig, exp := DefaultRoundingMode.reduce192(neg, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// Log1p returns the natural logarithm 1 plus d. It is more accurate than +// Log(1 + d) when d is near zero. +func Log1p(d Decimal) Decimal { + if d.isSpecial() { + if d.IsNaN() { + return d + } + + if d.Signbit() { + return nan(payloadOpLog1p, payloadValNegInfinite, 0) + } + + return inf(false) + } + + if d.IsZero() { + return zero(d.Signbit()) + } + + dSig, dExp := d.decompose() + dExp -= exponentBias + dNeg := d.Signbit() + + if dNeg { + if dExp > 0 { + return nan(payloadOpLog1p, payloadValNegFinite, 0) + } + + if dExp > int16(-len(uint128PowersOf10)) { + if cmp := dSig.cmp(uint128PowersOf10[-dExp]); cmp == 0 { + return inf(true) + } else if cmp > 0 { + return nan(payloadOpLog1p, payloadValNegFinite, 0) + } + } + } + + l10 := int16(dSig.log10()) + dExp + + if l10 > -10 { + res := decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp, + } + + if dNeg { + _, res, _ = res.add1neg(0) + } else { + res, _ = res.add1(0) + } + + neg, res, trunc := res.log() + + sig, exp := DefaultRoundingMode.reduce192(neg, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) + } + + neg, res, trunc := decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp, + }.log1p(d.Signbit()) + + sig, exp := DefaultRoundingMode.reduce192(neg, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// Log2 returns the binary logarithm of d. +func Log2(d Decimal) Decimal { + if d.isSpecial() { + if d.IsNaN() { + return d + } + + if d.Signbit() { + return nan(payloadOpLog2, payloadValNegInfinite, 0) + } + + return inf(false) + } + + if d.IsZero() { + return inf(true) + } + + if d.Signbit() { + return nan(payloadOpLog2, payloadValNegFinite, 0) + } + + dSig, dExp := d.decompose() + + neg, res, trunc := decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: dExp - exponentBias, + }.log() + + res, trunc = res.mul(invLn2, trunc) + + sig, exp := DefaultRoundingMode.reduce192(neg, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// Sqrt returns the square root of d. +func Sqrt(d Decimal) Decimal { + if d.isSpecial() { + if d.IsNaN() { + return d + } + + if d.Signbit() { + return nan(payloadOpSqrt, payloadValNegInfinite, 0) + } + + return d + } + + if d.IsZero() { + return d + } + + if d.Signbit() { + return nan(payloadOpSqrt, payloadValNegFinite, 0) + } + + dSig, dExp := d.decompose() + l10 := int16(dSig.log10()) + dExp = (dExp - exponentBias) + l10 + + var add decomposed192 + var mul decomposed192 + var nrm decomposed192 + if dExp&1 == 0 { + add = decomposed192{ + sig: uint192{259, 0, 0}, + exp: -3, + } + + mul = decomposed192{ + sig: uint192{819, 0, 0}, + exp: -3, + } + + nrm = decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: -l10, + } + } else { + add = decomposed192{ + sig: uint192{819, 0, 0}, + exp: -4, + } + + mul = decomposed192{ + sig: uint192{259, 0, 0}, + exp: -2, + } + + nrm = decomposed192{ + sig: uint192{dSig[0], dSig[1], 0}, + exp: -l10 - 1, + } + + dExp++ + } + + res, trunc := nrm.mul(mul, int8(0)) + res, trunc = res.add(add, trunc) + + var tmp decomposed192 + half := decomposed192{ + sig: uint192{5, 0, 0}, + exp: -1, + } + + for i := 0; i < 8; i++ { + tmp, trunc = nrm.quo(res, trunc) + res, trunc = res.add(tmp, trunc) + res, trunc = half.mul(res, trunc) + } + + res.exp += dExp / 2 + sig, exp := DefaultRoundingMode.reduce192(false, res.sig, res.exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + return inf(false) + } + + return compose(false, sig, exp) +} diff --git a/vendor/github.com/woodsbury/decimal128/format.go b/vendor/github.com/woodsbury/decimal128/format.go new file mode 100644 index 0000000..ed48ed5 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/format.go @@ -0,0 +1,929 @@ +package decimal128 + +import ( + "fmt" + "unsafe" +) + +var ( + nanText = []byte("NaN") + padNaNText = []byte(" NaN") + posNaNText = []byte("+NaN") + negInfText = []byte("-Inf") + padInfText = []byte(" Inf") + posInfText = []byte("+Inf") + spaceText = []byte{' ', ' ', ' '} + + digitPairs = [...][2]byte{ + {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, + {'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, + {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'}, + {'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'}, + {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'}, + {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, + {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, + {'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, + {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'}, + {'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'}, + {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'}, + {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, + {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, + {'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, + {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'}, + {'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'}, + {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'}, + {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, + {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, + {'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}, + } +) + +// Append appends the string representation of the Decimal to the provided byte +// slice, as generated by [Format], and returns the updated byte slice. +func Append(buf []byte, d Decimal, fmt byte, prec int) []byte { + if d.isSpecial() { + return d.appendSpecial(buf, 0, false, false, false) + } + + var digs digits + d.digits(&digs) + + switch fmt { + case 'e', 'E': + if prec < 0 { + if digs.ndig != 0 { + prec = digs.ndig - 1 + } else { + prec = 0 + } + } else { + digs.round(prec + 1) + } + + return digs.fmtE(buf, prec, 0, false, false, false, true, false, false, fmt) + case 'f': + if prec < 0 { + if digs.exp < 0 { + prec = -digs.exp + } else { + prec = 0 + } + } else if digs.exp < 0 { + digs.round(digs.ndig + digs.exp + prec) + } + + return digs.fmtF(buf, prec, 0, false, false, false, false, false) + case 'g', 'G': + var maxprec int + if prec < 0 { + maxprec = 6 + prec = digs.ndig + } else { + if prec == 0 { + prec = 1 + } + + digs.round(prec) + maxprec = prec + prec = digs.ndig + } + + eprec := 0 + if digs.ndig != 0 { + eprec = digs.ndig - 1 + } + + exp := digs.exp + eprec + + if exp < -4 || exp >= maxprec { + e := byte('e') + if fmt == 'G' { + e = byte('E') + } + + return digs.fmtE(buf, prec-1, 0, false, false, false, true, false, false, e) + } else { + prec = 0 + if digs.exp < 0 { + prec -= digs.exp + } + + return digs.fmtF(buf, prec, 0, false, false, false, false, false) + } + default: + return append(buf, '%', fmt) + } +} + +// Format converts the Decimal to a string according to the provided format and +// precision. +// +// The format is one of 'e', 'E', 'f', 'g', or 'G'. +// +// The precision controls the number of digits in the resulting string. For +// 'e', 'E', and 'f' it is the number of digits after the decimal point. For +// 'g' and 'G' it is the maximum number of significant digits. The special +// precision -1 uses the smallest number of digits necessary to represent the +// Decimal. +func Format(d Decimal, fmt byte, prec int) string { + return string(Append(nil, d, fmt, prec)) +} + +// Append formats the Decimal according to the provided format specifier and +// appends the result to the provided byte slice, returning the updated byte +// slice. The format specifier can be any value supported by [Decimal.Format], +// without the leading %. +func (d Decimal) Append(buf []byte, format string) []byte { + var args formatArgs + parseFormat(format, &args) + + if args.verb == 0 { + return append(buf, "%!(NOVERB)"...) + } + + if d.isSpecial() { + width := 0 + padSign := false + printSign := false + if args.verb != 'v' { + printSign = args.printSign + padSign = args.padSign + + width = args.width() + } + + return d.appendSpecial(buf, width, printSign, padSign, args.padRight) + } + + return d.format(buf, &args) +} + +// Format implements the [fmt.Formatter] interface. It supports the verbs 'e', +// 'E', 'f', 'F', 'g', 'G', and 'v', along with the format flags '+', '-', '#', +// ' ', and '0' and custom width and precision values. Decimal values interpret +// the format value the same way float32 and float64 does. +func (d Decimal) Format(f fmt.State, verb rune) { + if d.isSpecial() { + width := 0 + padSign := false + printSign := false + if verb != 'v' { + printSign = f.Flag('+') + padSign = f.Flag(' ') + + w, hasWidth := f.Width() + if hasWidth { + width = w + } + } + + d.writeSpecial(f, width, printSign, padSign, f.Flag('-')) + return + } + + prec, hasPrec := f.Precision() + if !hasPrec { + prec = -1 + } + + width, hasWidth := f.Width() + if !hasWidth { + width = 0 + } + + args := formatArgs{ + forceDP: f.Flag('#'), + printSign: f.Flag('+'), + padSign: f.Flag(' '), + padRight: f.Flag('-'), + padZero: f.Flag('0'), + verb: byte(verb), + prec: prec, + wid: width, + } + + f.Write(d.format(nil, &args)) +} + +// MarshalText implements the [encoding.TextMarshaler] interface. +func (d Decimal) MarshalText() ([]byte, error) { + if d.isSpecial() { + return d.appendSpecial(nil, 0, false, false, false), nil + } + + var digs digits + d.digits(&digs) + + prec := 0 + if digs.ndig != 0 { + prec = digs.ndig - 1 + } + + exp := digs.exp + prec + + if exp < -4 || exp >= 6 { + return digs.fmtE(nil, prec, 0, false, false, false, true, false, false, 'e'), nil + } + + prec = 0 + if digs.exp < 0 { + prec = -digs.exp + } + + return digs.fmtF(nil, prec, 0, false, false, false, false, false), nil +} + +// String returns a string representation of the Decimal value. +func (d Decimal) String() string { + var buf []byte + if d.isSpecial() { + buf = d.appendSpecial(buf, 0, false, false, false) + } else { + var digs digits + d.digits(&digs) + + prec := 0 + if digs.ndig != 0 { + prec = digs.ndig - 1 + } + + exp := digs.exp + prec + + if exp < -4 || exp >= 6 { + buf = digs.fmtE(buf, prec, 0, false, false, false, true, false, false, 'e') + } else { + prec = 0 + if digs.exp < 0 { + prec = -digs.exp + } + + buf = digs.fmtF(buf, prec, 0, false, false, false, false, false) + } + } + + return unsafe.String(unsafe.SliceData(buf), len(buf)) +} + +func (d Decimal) appendSpecial(buf []byte, width int, printSign, padSign, padRight bool) []byte { + var value []byte + if d.IsNaN() { + if printSign { + value = posNaNText + } else if padSign { + value = padNaNText + } else { + value = nanText + } + } else { + if d.Signbit() { + value = negInfText + } else { + if padSign && !printSign { + value = padInfText + } else { + value = posInfText + } + } + } + + if cap(buf) == 0 { + sizeHint := len(value) + if width > sizeHint { + sizeHint = width + } + + buf = make([]byte, 0, sizeHint) + } + + n := len(value) + if p := width - n; p > 0 { + if padRight { + buf = append(buf, value...) + + for i := n; i < width; i++ { + buf = append(buf, ' ') + } + } else { + for i := 0; i < p; i++ { + buf = append(buf, ' ') + } + + buf = append(buf, value...) + } + } else { + buf = append(buf, value...) + } + + return buf +} + +func (d Decimal) digits(digs *digits) { + *digs = digits{} + digs.neg = d.Signbit() + + sig, exp := d.decompose() + + if sig[0]|sig[1] != 0 { + digs.exp = int(exp - exponentBias) + + n := 0 + for sig[1] != 0 { + var rem uint64 + sig, rem = sig.div100() + + if n == 0 && rem == 0 { + digs.exp += 2 + continue + } + + pair := digitPairs[rem] + + if n == 0 && pair[1] == '0' { + digs.exp++ + digs.dig[n] = pair[0] + n++ + continue + } + + if pair[0] == '0' && sig[0]|sig[1] == 0 { + digs.dig[n] = pair[1] + n++ + } else { + digs.dig[n], digs.dig[n+1] = pair[1], pair[0] + n += 2 + } + } + + sig64 := sig[0] + + for sig64 != 0 { + rem := sig64 % 100 + sig64 /= 100 + + if n == 0 && rem == 0 { + digs.exp += 2 + continue + } + + pair := digitPairs[rem] + + if n == 0 && pair[1] == '0' { + digs.exp++ + digs.dig[n] = pair[0] + n++ + continue + } + + if pair[0] == '0' && sig64 == 0 { + digs.dig[n] = pair[1] + n++ + } else { + digs.dig[n], digs.dig[n+1] = pair[1], pair[0] + n += 2 + } + } + + for i, j := 0, n-1; i < j; i, j = i+1, j-1 { + digs.dig[i], digs.dig[j] = digs.dig[j], digs.dig[i] + } + + digs.ndig = n + } +} + +func (d Decimal) format(buf []byte, args *formatArgs) []byte { + var digs digits + d.digits(&digs) + + prec, hasPrec := args.precision() + width := args.width() + + switch args.verb { + case 'e', 'E': + if !hasPrec { + prec = 6 + } + + digs.round(prec + 1) + return digs.fmtE(buf, prec, width, args.forceDP, args.printSign, args.padSign, true, args.padRight, args.padZero, args.verb) + case 'f', 'F': + if !hasPrec { + prec = 6 + } + + if digs.exp < 0 { + digs.round(digs.ndig + digs.exp + prec) + } + + return digs.fmtF(buf, prec, width, args.forceDP, args.printSign, args.padSign, args.padRight, args.padZero) + case 'g', 'G': + var maxprec int + if args.forceDP { + if !hasPrec { + if digs.ndig < 6 { + prec = 6 + } else { + prec = digs.ndig + } + + maxprec = 6 + } else { + if prec == 0 { + prec = 1 + } + + maxprec = prec + } + + digs.round(prec) + } else { + if hasPrec { + if prec == 0 { + prec = 1 + } + + digs.round(prec) + maxprec = prec + prec = digs.ndig + } else if digs.ndig != 0 { + maxprec = 6 + prec = digs.ndig + } else { + maxprec = 6 + } + } + + eprec := 0 + if digs.ndig != 0 { + eprec = digs.ndig - 1 + } + + exp := digs.exp + eprec + + if exp < -4 || exp >= maxprec { + e := byte('e') + if args.verb == 'G' { + e = byte('E') + } + + return digs.fmtE(buf, prec-1, width, args.forceDP, args.printSign, args.padSign, true, args.padRight, args.padZero, e) + } else { + if args.forceDP { + prec -= digs.exp + if digs.ndig == 0 { + prec-- + } else { + prec -= digs.ndig + } + } else { + prec = 0 + if digs.exp < 0 { + prec -= digs.exp + } + } + + return digs.fmtF(buf, prec, width, args.forceDP, args.printSign, args.padSign, args.padRight, args.padZero) + } + case 'v': + prec := 0 + if digs.ndig != 0 { + prec = digs.ndig - 1 + } + + exp := digs.exp + prec + + if exp < -4 || exp >= 6 { + return digs.fmtE(buf, prec, 0, false, false, false, true, false, false, 'e') + } else { + prec = 0 + if digs.exp < 0 { + prec = -digs.exp + } + } + + return digs.fmtF(buf, prec, 0, false, false, false, false, false) + default: + return fmt.Appendf(buf, "%%!%c(decimal128.Decimal=%s)", args.verb, d.String()) + } +} + +func (d Decimal) writeSpecial(f fmt.State, width int, printSign, padSign, padRight bool) { + var value []byte + if d.IsNaN() { + if printSign { + value = posNaNText + } else if padSign { + value = padNaNText + } else { + value = nanText + } + } else { + if d.Signbit() { + value = negInfText + } else { + if padSign && !printSign { + value = padInfText + } else { + value = posInfText + } + } + } + + n := len(value) + if p := width - n; p > 0 { + if padRight { + f.Write(value) + + i := n + for ; i < width-2; i += 3 { + f.Write(spaceText) + } + + if i < width-1 { + f.Write(spaceText[:2]) + } else if i < width { + f.Write(spaceText[:1]) + } + } else { + i := 0 + for ; i < p-2; i += 3 { + f.Write(spaceText) + } + + if i < p-1 { + f.Write(spaceText[:2]) + } else if i < p { + f.Write(spaceText[:1]) + } + + f.Write(value) + } + } else { + f.Write(value) + } +} + +type digits struct { + neg bool + dig [39]byte + exp int + ndig int +} + +func (d *digits) fmtE(buf []byte, prec, width int, forceDP, printSign, padSign, padExp, padRight, padZero bool, e byte) []byte { + if cap(buf) == 0 { + // Attempt to pre-size buffer to avoid multiple allocations. This might + // overshoot the actual needed size. Calculation is: + // sign + decimal point + 'e+/-' + exponent + zero + digits + sizeHint := 1 + 1 + 2 + 4 + 1 + d.ndig + if width > sizeHint { + sizeHint = width + } + + buf = make([]byte, 0, sizeHint) + } + + if d.neg { + buf = append(buf, '-') + } else if printSign { + buf = append(buf, '+') + } else if padSign { + buf = append(buf, ' ') + } + + if d.ndig == 0 { + buf = append(buf, '0') + } else { + buf = append(buf, d.dig[0]) + } + + if prec > 0 { + buf = append(buf, '.') + + i := 0 + if d.ndig > 1 { + buf = append(buf, d.dig[1:d.ndig]...) + i = d.ndig - 1 + } + + for ; i < prec; i++ { + buf = append(buf, '0') + } + } else if forceDP { + buf = append(buf, '.') + } + + buf = append(buf, e) + + exp := d.exp + if d.ndig > 1 { + exp += d.ndig - 1 + } + + if exp < 0 { + exp = -exp + buf = append(buf, '-') + } else { + buf = append(buf, '+') + } + + if exp < 10 { + if padExp { + buf = append(buf, '0', '0'+byte(exp)) + } else { + buf = append(buf, '0'+byte(exp)) + } + } else if exp < 100 { + buf = append(buf, '0'+byte(exp/10), '0'+byte(exp%10)) + } else if exp < 1000 { + buf = append(buf, '0'+byte(exp/100), '0'+byte(exp/10%10), '0'+byte(exp%10)) + } else { + buf = append(buf, '0'+byte(exp/1000), '0'+byte(exp/100%10), '0'+byte(exp/10%10), '0'+byte(exp%10)) + } + + buf = d.pad(buf, width, printSign, padSign, padRight, padZero) + return buf +} + +func (d *digits) fmtF(buf []byte, prec, width int, forceDP, printSign, padSign, padRight, padZero bool) []byte { + if cap(buf) == 0 { + // Attempt to pre-size buffer to avoid multiple allocations. This might + // overshoot the actual needed size. Calculation is: + // sign + decimal point + digits + zeros + sizeHint := 1 + 1 + d.ndig + d.exp + if width > sizeHint { + sizeHint = width + } + + buf = make([]byte, 0, sizeHint) + } + + if d.neg { + buf = append(buf, '-') + } else if printSign { + buf = append(buf, '+') + } else if padSign { + buf = append(buf, ' ') + } + + dp := 0 + if d.ndig == 0 { + buf = append(buf, '0') + } else { + dp = d.ndig + d.exp + + if dp > 0 { + if d.ndig > dp { + buf = append(buf, d.dig[:dp]...) + } else { + buf = append(buf, d.dig[:d.ndig]...) + + i := dp - d.ndig + for ; i > 2; i -= 3 { + buf = append(buf, '0', '0', '0') + } + + for ; i > 0; i-- { + buf = append(buf, '0') + } + } + } else { + buf = append(buf, '0') + } + } + + if prec > 0 { + buf = append(buf, '.') + + for ; dp < 0; dp++ { + prec-- + buf = append(buf, '0') + } + + i := 0 + if d.ndig > dp { + buf = append(buf, d.dig[dp:d.ndig]...) + i = d.ndig - dp + } + + for ; i < prec; i++ { + buf = append(buf, '0') + } + } else if forceDP { + buf = append(buf, '.') + } + + buf = d.pad(buf, width, printSign, padSign, padRight, padZero) + return buf +} + +func (d *digits) pad(buf []byte, width int, printSign, padSign, padRight, padZero bool) []byte { + p := width - len(buf) + if p <= 0 { + // No need for padding. + return buf + } + + padChar := byte(' ') + if padZero { + padChar = byte('0') + } + + if padRight { + for i := 0; i < p; i++ { + buf = append(buf, padChar) + } + } else { + // Determine where to keep the sign. + i := 0 + if padZero && (d.neg || printSign || padSign) { + i = 1 + p++ + } + + // Grow buf until it fits the nb + padding. + if len(buf) < width { + if cap(buf) < width { + tmp := make([]byte, width) + copy(tmp, buf) + buf = tmp + } else { + buf = buf[:width] + } + } + + // Move the existing number to the end of the buffer. + copy(buf[p:], buf[i:]) + + // Fill left-padding chars. + for ; i < p; i++ { + buf[i] = padChar + } + } + + return buf +} + +func (d *digits) round(prec int) { + if d.ndig <= prec { + return + } + + if prec < 0 { + d.exp += d.ndig + d.ndig = 0 + return + } + + up := false + if d.ndig > 1 && d.ndig == prec+1 && d.dig[prec] == '5' { + up = (d.dig[prec-1]-'0')%2 != 0 + } else { + up = d.dig[prec] >= '5' + } + + if up { + i := prec - 1 + for i >= 0 && d.dig[i] == '9' { + i-- + } + + if i == -1 { + d.dig[0] = '1' + d.exp += d.ndig + d.ndig = 1 + } else { + d.dig[i]++ + prec = i + 1 + d.exp += d.ndig - prec + d.ndig = prec + } + } else { + i := prec - 1 + for i >= 0 && d.dig[i] == '0' { + i-- + } + + prec = i + 1 + d.exp += d.ndig - prec + d.ndig = prec + } +} + +type formatArgs struct { + forceDP bool + printSign bool + padSign bool + padRight bool + padZero bool + verb byte + prec int + wid int +} + +func parseFormat(format string, args *formatArgs) { + *args = formatArgs{ + prec: -1, + } + + var c byte + i := 0 + end := len(format) + +parseFlags: + for ; i < end; i++ { + c = format[i] + switch c { + case ' ': + args.padSign = true + case '#': + args.forceDP = true + case '+': + args.printSign = true + case '-': + args.padRight = true + args.padZero = false + case '0': + args.padZero = !args.padRight + default: + break parseFlags + } + } + + if i >= end { + return + } + + if c >= '1' && c <= '9' { + args.wid = int(c - '0') + i++ + + for ; i < end; i++ { + c = format[i] + if c < '0' || c > '9' { + break + } + + if args.wid < 1e5 { + args.wid = args.wid*10 + int(c-'0') + } else { + args.wid = 0 + } + } + + if i >= end { + return + } + } + + if c == '.' { + i++ + if i >= end { + args.prec = 0 + return + } + + c = format[i] + if c < '0' || c > '9' { + args.prec = 0 + } else { + args.prec = int(c - '0') + i++ + + for ; i < end; i++ { + c = format[i] + if c < '0' || c > '9' { + break + } + + if args.prec < 1e5 { + args.prec = args.prec*10 + int(c-'0') + } else { + args.prec = -1 + } + } + + if i >= end { + return + } + } + } + + if i != end-1 { + return + } + + args.verb = c +} + +func (args formatArgs) precision() (int, bool) { + if args.prec < 0 { + return 0, false + } + + return args.prec, true +} + +func (args formatArgs) width() int { + return args.wid +} diff --git a/vendor/github.com/woodsbury/decimal128/int.go b/vendor/github.com/woodsbury/decimal128/int.go new file mode 100644 index 0000000..055449f --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/int.go @@ -0,0 +1,1017 @@ +package decimal128 + +import ( + "math" + "math/bits" +) + +var ( + uint128PowersOf10 = [...]uint128{ + {0x0000_0000_0000_0001, 0x0000_0000_0000_0000}, + {0x0000_0000_0000_000a, 0x0000_0000_0000_0000}, + {0x0000_0000_0000_0064, 0x0000_0000_0000_0000}, + {0x0000_0000_0000_03e8, 0x0000_0000_0000_0000}, + {0x0000_0000_0000_2710, 0x0000_0000_0000_0000}, + {0x0000_0000_0001_86a0, 0x0000_0000_0000_0000}, + {0x0000_0000_000f_4240, 0x0000_0000_0000_0000}, + {0x0000_0000_0098_9680, 0x0000_0000_0000_0000}, + {0x0000_0000_05f5_e100, 0x0000_0000_0000_0000}, + {0x0000_0000_3b9a_ca00, 0x0000_0000_0000_0000}, + {0x0000_0002_540b_e400, 0x0000_0000_0000_0000}, + {0x0000_0017_4876_e800, 0x0000_0000_0000_0000}, + {0x0000_00e8_d4a5_1000, 0x0000_0000_0000_0000}, + {0x0000_0918_4e72_a000, 0x0000_0000_0000_0000}, + {0x0000_5af3_107a_4000, 0x0000_0000_0000_0000}, + {0x0003_8d7e_a4c6_8000, 0x0000_0000_0000_0000}, + {0x0023_86f2_6fc1_0000, 0x0000_0000_0000_0000}, + {0x0163_4578_5d8a_0000, 0x0000_0000_0000_0000}, + {0x0de0_b6b3_a764_0000, 0x0000_0000_0000_0000}, + {0x8ac7_2304_89e8_0000, 0x0000_0000_0000_0000}, + {0x6bc7_5e2d_6310_0000, 0x0000_0000_0000_0005}, + {0x35c9_adc5_dea0_0000, 0x0000_0000_0000_0036}, + {0x19e0_c9ba_b240_0000, 0x0000_0000_0000_021e}, + {0x02c7_e14a_f680_0000, 0x0000_0000_0000_152d}, + {0x1bce_cced_a100_0000, 0x0000_0000_0000_d3c2}, + {0x1614_0148_4a00_0000, 0x0000_0000_0008_4595}, + {0xdcc8_0cd2_e400_0000, 0x0000_0000_0052_b7d2}, + {0x9fd0_803c_e800_0000, 0x0000_0000_033b_2e3c}, + {0x3e25_0261_1000_0000, 0x0000_0000_204f_ce5e}, + {0x6d72_17ca_a000_0000, 0x0000_0001_431e_0fae}, + {0x4674_edea_4000_0000, 0x0000_000c_9f2c_9cd0}, + {0xc091_4b26_8000_0000, 0x0000_007e_37be_2022}, + {0x85ac_ef81_0000_0000, 0x0000_04ee_2d6d_415b}, + {0x38c1_5b0a_0000_0000, 0x0000_314d_c644_8d93}, + {0x378d_8e64_0000_0000, 0x0001_ed09_bead_87c0}, + {0x2b87_8fe8_0000_0000, 0x0013_4261_72c7_4d82}, + {0xb34b_9f10_0000_0000, 0x00c0_97ce_7bc9_0715}, + {0x00f4_36a0_0000_0000, 0x0785_ee10_d5da_46d9}, + {0x098a_2240_0000_0000, 0x4b3b_4ca8_5a86_c47a}, + } + + uint192PowersOf10 = [...]uint192{ + {0x0000_0000_0000_0001, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0000_0000_000a, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0000_0000_0064, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0000_0000_03e8, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0000_0000_2710, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0000_0001_86a0, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0000_000f_4240, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0000_0098_9680, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0000_05f5_e100, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0000_3b9a_ca00, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0002_540b_e400, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0017_4876_e800, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_00e8_d4a5_1000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_0918_4e72_a000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0000_5af3_107a_4000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0003_8d7e_a4c6_8000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0023_86f2_6fc1_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0163_4578_5d8a_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x0de0_b6b3_a764_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x8ac7_2304_89e8_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000}, + {0x6bc7_5e2d_6310_0000, 0x0000_0000_0000_0005, 0x0000_0000_0000_0000}, + {0x35c9_adc5_dea0_0000, 0x0000_0000_0000_0036, 0x0000_0000_0000_0000}, + {0x19e0_c9ba_b240_0000, 0x0000_0000_0000_021e, 0x0000_0000_0000_0000}, + {0x02c7_e14a_f680_0000, 0x0000_0000_0000_152d, 0x0000_0000_0000_0000}, + {0x1bce_cced_a100_0000, 0x0000_0000_0000_d3c2, 0x0000_0000_0000_0000}, + {0x1614_0148_4a00_0000, 0x0000_0000_0008_4595, 0x0000_0000_0000_0000}, + {0xdcc8_0cd2_e400_0000, 0x0000_0000_0052_b7d2, 0x0000_0000_0000_0000}, + {0x9fd0_803c_e800_0000, 0x0000_0000_033b_2e3c, 0x0000_0000_0000_0000}, + {0x3e25_0261_1000_0000, 0x0000_0000_204f_ce5e, 0x0000_0000_0000_0000}, + {0x6d72_17ca_a000_0000, 0x0000_0001_431e_0fae, 0x0000_0000_0000_0000}, + {0x4674_edea_4000_0000, 0x0000_000c_9f2c_9cd0, 0x0000_0000_0000_0000}, + {0xc091_4b26_8000_0000, 0x0000_007e_37be_2022, 0x0000_0000_0000_0000}, + {0x85ac_ef81_0000_0000, 0x0000_04ee_2d6d_415b, 0x0000_0000_0000_0000}, + {0x38c1_5b0a_0000_0000, 0x0000_314d_c644_8d93, 0x0000_0000_0000_0000}, + {0x378d_8e64_0000_0000, 0x0001_ed09_bead_87c0, 0x0000_0000_0000_0000}, + {0x2b87_8fe8_0000_0000, 0x0013_4261_72c7_4d82, 0x0000_0000_0000_0000}, + {0xb34b_9f10_0000_0000, 0x00c0_97ce_7bc9_0715, 0x0000_0000_0000_0000}, + {0x00f4_36a0_0000_0000, 0x0785_ee10_d5da_46d9, 0x0000_0000_0000_0000}, + {0x098a_2240_0000_0000, 0x4b3b_4ca8_5a86_c47a, 0x0000_0000_0000_0000}, + {0x5f65_5680_0000_0000, 0xf050_fe93_8943_acc4, 0x0000_0000_0000_0002}, + {0xb9f5_6100_0000_0000, 0x6329_f1c3_5ca4_bfab, 0x0000_0000_0000_001d}, + {0x4395_ca00_0000_0000, 0xdfa3_71a1_9e6f_7cb5, 0x0000_0000_0000_0125}, + {0xa3d9_e400_0000_0000, 0xbc62_7050_305a_df14, 0x0000_0000_0000_0b7a}, + {0x6682_e800_0000_0000, 0x5bd8_6321_e38c_b6ce, 0x0000_0000_0000_72cb}, + {0x011d_1000_0000_0000, 0x9673_df52_e37f_2410, 0x0000_0000_0004_7bf1}, + {0x0b22_a000_0000_0000, 0xe086_b93c_e2f7_68a0, 0x0000_0000_002c_d76f}, + {0x6f5a_4000_0000_0000, 0xc543_3c60_ddaa_1640, 0x0000_0000_01c0_6a5e}, + {0x5986_8000_0000_0000, 0xb4a0_5bc8_a8a4_de84, 0x0000_0000_1184_27b3}, + {0x7f41_0000_0000_0000, 0x0e43_95d6_9670_b12b, 0x0000_0000_af29_8d05}, + {0xf88a_0000_0000_0000, 0x8ea3_da61_e066_ebb2, 0x0000_0006_d79f_8232}, + {0xb564_0000_0000_0000, 0x9266_87d2_c405_34fd, 0x0000_0044_6c3b_15f9}, + {0x15e8_0000_0000_0000, 0xb801_4e3b_a834_11e9, 0x0000_02ac_3a4e_dbbf}, + {0xdb10_0000_0000_0000, 0x300d_0e54_9208_b31a, 0x0000_1aba_4714_957d}, + {0x8ea0_0000_0000_0000, 0xe082_8f4d_b456_ff0c, 0x0001_0b46_c6cd_d6e3}, + {0x9240_0000_0000_0000, 0xc519_9909_0b65_f67d, 0x000a_70c3_c40a_64e6}, + {0xb680_0000_0000_0000, 0xb2ff_fa5a_71fb_a0e7, 0x0068_67a5_a867_f103}, + {0x2100_0000_0000_0000, 0xfdff_c788_73d4_490d, 0x0414_0c78_940f_6a24}, + {0x4a00_0000_0000_0000, 0xebfd_cb54_864a_da83, 0x28c8_7cb5_c89a_2571}, + } +) + +type uint128 [2]uint64 + +func (n uint128) String() string { + if n[0]|n[1] == 0 { + return "0" + } + + var buf [39]byte + + i := 39 + for n[0]|n[1] != 0 { + var d uint64 + n, d = n.div10() + i-- + buf[i] = '0' + byte(d) + } + + return string(buf[i:]) +} + +func (n uint128) add(o uint128) uint192 { + r0, carry := bits.Add64(n[0], o[0], 0) + r1, r2 := bits.Add64(n[1], o[1], carry) + + return uint192{r0, r1, r2} +} + +func (n uint128) add64(o uint64) uint128 { + r0, carry := bits.Add64(n[0], o, 0) + r1 := n[1] + carry + + return uint128{r0, r1} +} + +func (n uint128) cmp(o uint128) int { + if n[1] == o[1] { + if n[0] == o[0] { + return 0 + } + + if n[0] < o[0] { + return -1 + } + + return 1 + } + + if n[1] < o[1] { + return -1 + } + + return 1 +} + +func (n uint128) div(o uint128) (uint128, uint128) { + if o[1] == 0 { + var r0, r1, rem uint64 + if n[1] < o[0] { + r0, rem = bits.Div64(n[1], n[0], o[0]) + } else { + r1, rem = bits.Div64(0, n[1], o[0]) + r0, rem = bits.Div64(rem, n[0], o[0]) + } + + return uint128{r0, r1}, uint128{rem, 0} + } + + i := uint(bits.LeadingZeros64(o[1])) + u := o.lsh(i) + v := n.rsh(1) + r0, _ := bits.Div64(v[1], v[0], u[1]) + r0 >>= 63 - i + if r0 != 0 { + r0-- + } + + r := uint128{r0, 0} + rem, _ := n.sub(o.mul64(r0)) + + if rem.cmp(o) >= 0 { + r = r.add64(1) + rem, _ = rem.sub(o) + } + + return r, rem +} + +func (n uint128) div10() (uint128, uint64) { + var r0, r1, rem uint64 + if n[1] < 10 { + r0, rem = bits.Div64(n[1], n[0], 10) + } else { + r1, rem = bits.Div64(0, n[1], 10) + r0, rem = bits.Div64(rem, n[0], 10) + } + + return uint128{r0, r1}, rem +} + +func (n uint128) div100() (uint128, uint64) { + var r0, r1, rem uint64 + if n[1] < 100 { + r0, rem = bits.Div64(n[1], n[0], 100) + } else { + r1, rem = bits.Div64(0, n[1], 100) + r0, rem = bits.Div64(rem, n[0], 100) + } + + return uint128{r0, r1}, rem +} + +func (n uint128) div1000() (uint128, uint64) { + var r0, r1, rem uint64 + if n[1] < 1000 { + r0, rem = bits.Div64(n[1], n[0], 1000) + } else { + r1, rem = bits.Div64(0, n[1], 1000) + r0, rem = bits.Div64(rem, n[0], 1000) + } + + return uint128{r0, r1}, rem +} + +func (n uint128) div10000() (uint128, uint64) { + var r0, r1, rem uint64 + if n[1] < 10_000 { + r0, rem = bits.Div64(n[1], n[0], 10_000) + } else { + r1, rem = bits.Div64(0, n[1], 10_000) + r0, rem = bits.Div64(rem, n[0], 10_000) + } + + return uint128{r0, r1}, rem +} + +func (n uint128) div1e8() (uint128, uint64) { + var r0, r1, rem uint64 + if n[1] < 100_000_000 { + r0, rem = bits.Div64(n[1], n[0], 100_000_000) + } else { + r1, rem = bits.Div64(0, n[1], 100_000_000) + r0, rem = bits.Div64(rem, n[0], 100_000_000) + } + + return uint128{r0, r1}, rem +} + +func (n uint128) div1e19() (uint128, uint64) { + var r0, r1, rem uint64 + if n[1] < 10_000_000_000_000_000_000 { + r0, rem = bits.Div64(n[1], n[0], 10_000_000_000_000_000_000) + } else { + r1, rem = bits.Div64(0, n[1], 10_000_000_000_000_000_000) + r0, rem = bits.Div64(rem, n[0], 10_000_000_000_000_000_000) + } + + return uint128{r0, r1}, rem +} + +func (n uint128) log10() int { + var l2 int + if n[1] != 0 { + l2 = bits.Len64(n[1]) + 64 + } else if n[0] != 0 { + l2 = bits.Len64(n[0]) + } else { + return 0 + } + + l10 := l2 * 1233 >> 12 + if n.cmp(uint128PowersOf10[l10]) < 0 { + l10-- + } + + return l10 +} + +func (n uint128) lsh(o uint) uint128 { + var r0, r1 uint64 + if o > 64 { + r1 = n[0] << (o - 64) + } else { + r0 = n[0] << o + r1 = n[1]<>(64-o) + } + + return uint128{r0, r1} +} + +func (n uint128) mul(o uint128) uint256 { + s1, r0 := bits.Mul64(n[0], o[0]) + t2, t1 := bits.Mul64(n[1], o[0]) + u2, u1 := bits.Mul64(n[0], o[1]) + v3, v2 := bits.Mul64(n[1], o[1]) + + r1, carry := bits.Add64(s1, t1, 0) + r2, w3 := bits.Add64(t2, u2, carry) + r1, carry = bits.Add64(r1, u1, 0) + r2, x3 := bits.Add64(r2, v2, carry) + r3, _ := bits.Add64(v3, w3, x3) + + return uint256{r0, r1, r2, r3} +} + +func (n uint128) mul1e38() uint256 { + const o0 = 687399551400673280 + const o1 = 5421010862427522170 + + s1, r0 := bits.Mul64(n[0], o0) + t2, t1 := bits.Mul64(n[1], o0) + u2, u1 := bits.Mul64(n[0], o1) + v3, v2 := bits.Mul64(n[1], o1) + + r1, carry := bits.Add64(s1, t1, 0) + r2, w3 := bits.Add64(t2, u2, carry) + r1, carry = bits.Add64(r1, u1, 0) + r2, x3 := bits.Add64(r2, v2, carry) + r3, _ := bits.Add64(v3, w3, x3) + + return uint256{r0, r1, r2, r3} +} + +func (n uint128) mul64(o uint64) uint128 { + r1, r0 := bits.Mul64(n[0], o) + r1 += n[1] * o + + return uint128{r0, r1} +} + +func (n uint128) or64(o uint64) uint128 { + return uint128{n[0] | o, n[1]} +} + +func (n uint128) rsh(o uint) uint128 { + var r0, r1 uint64 + if o > 64 { + r0 = n[1] >> (o - 64) + } else { + r0 = n[0]>>o | n[1]<<(64-o) + r1 = n[1] >> o + } + + return uint128{r0, r1} +} + +func (n uint128) sub(o uint128) (uint128, uint) { + r0, borrow := bits.Sub64(n[0], o[0], 0) + r1, borrow := bits.Sub64(n[1], o[1], borrow) + + return uint128{r0, r1}, uint(borrow) +} + +func (n uint128) sub64(o uint64) uint128 { + r0, borrow := bits.Sub64(n[0], o, 0) + r1 := n[1] - borrow + + return uint128{r0, r1} +} + +func (n uint128) twos() uint128 { + r0, carry := bits.Add64(^n[0], 1, 0) + r1 := ^n[1] + carry + + return uint128{r0, r1} +} + +type uint192 [3]uint64 + +func (n uint192) String() string { + if n[0]|n[1]|n[2] == 0 { + return "0" + } + + var buf [58]byte + + i := 58 + for n[0]|n[1]|n[2] != 0 { + var d uint64 + n, d = n.div10() + i-- + buf[i] = '0' + byte(d) + } + + return string(buf[i:]) +} + +func (n uint192) add(o uint192) uint256 { + r0, carry := bits.Add64(n[0], o[0], 0) + r1, carry := bits.Add64(n[1], o[1], carry) + r2, r3 := bits.Add64(n[2], o[2], carry) + + return uint256{r0, r1, r2, r3} +} + +func (n uint192) add64(o uint64) uint192 { + r0, carry := bits.Add64(n[0], o, 0) + r1, carry := bits.Add64(n[1], 0, carry) + r2 := n[2] + carry + + return uint192{r0, r1, r2} +} + +func (n uint192) cmp(o uint192) int { + if n[2] == o[2] { + if n[1] == o[1] { + if n[0] == o[0] { + return 0 + } + + if n[0] < o[0] { + return -1 + } + + return 1 + } + + if n[1] < o[1] { + return -1 + } + + return 1 + } + + if n[2] < o[2] { + return -1 + } + + return 1 +} + +func (n uint192) div(o uint192) (uint192, uint192) { + if o[2] == 0 { + if o[1] == 0 { + var r0, r1, r2, rem uint64 + if n[2] < o[0] { + r1, rem = bits.Div64(n[2], n[1], o[0]) + r0, rem = bits.Div64(rem, n[0], o[0]) + } else { + r2, rem = bits.Div64(0, n[2], o[0]) + r1, rem = bits.Div64(rem, n[1], o[0]) + r0, rem = bits.Div64(rem, n[0], o[0]) + } + + return uint192{r0, r1, r2}, uint192{rem, 0, 0} + } + + i := uint(bits.LeadingZeros64(o[1])) + u := o.lsh(i) + + if n[2] == 0 { + v := n.rsh(1) + r0, _ := bits.Div64(v[1], v[0], u[1]) + r0 >>= 63 - i + if r0 != 0 { + r0-- + } + + r := uint192{r0, 0, 0} + rem, _ := n.sub(o.mul64(r0)) + + if rem.cmp(o) >= 0 { + r = r.add64(1) + rem, _ = rem.sub(o) + } + + return r, rem + } + + if n[2] < o[1] { + v := n.lsh(i) + r0, ur := bits.Div64(v[2], v[1], u[1]) + + p1, p0 := bits.Mul64(r0, u[0]) + if p1 > ur || (p1 == ur && p0 > v[0]) { + r0-- + ur, carry := bits.Add64(ur, u[1], 0) + + if carry == 0 { + p1, p0 = bits.Mul64(r0, u[0]) + if p1 > ur || (p1 == ur && p0 > v[0]) { + r0-- + } + } + } + + r := uint192{r0, 0, 0} + + q := o.mul(r) + rem, _ := n.sub(uint192{q[0], q[1], q[2]}) + + if rem.cmp(o) >= 0 { + rem, _ = rem.sub(o) + r = r.add64(1) + } + + return r, rem + } + + v := uint256{n[0], n[1], n[2], 0}.lsh(i) + r1, ur := bits.Div64(v[3], v[2], u[1]) + + p1, p0 := bits.Mul64(r1, u[0]) + if p1 > ur || (p1 == ur && p0 > v[1]) { + ur, carry := bits.Add64(ur, u[1], 0) + r1-- + + if carry == 0 { + p1, p0 = bits.Mul64(r1, u[0]) + if p1 > ur || (p1 == ur && p0 > v[1]) { + r1-- + } + } + } + + q192 := u.mul64(r1) + rem, _ := uint192{v[1], v[2], v[3]}.sub(q192) + + if rem.cmp(u) >= 0 { + rem, _ = rem.sub(u) + r1++ + } + + var r0 uint64 + if rem[1] == u[1] { + r0, ur = math.MaxUint64, rem[0] + } else { + r0, ur = bits.Div64(rem[1], rem[0], u[1]) + } + + p1, p0 = bits.Mul64(r0, u[0]) + if p1 > ur || (p1 == ur && p0 > v[0]) { + var carry uint64 + ur, carry = bits.Add64(ur, u[1], 0) + r0-- + + if carry == 0 { + p1, p0 = bits.Mul64(r0, u[0]) + if p1 > ur || (p1 == ur && p0 > v[0]) { + r0-- + } + } + } + + r := uint192{r0, r1, 0} + + q := o.mul(r) + rem, _ = n.sub(uint192{q[0], q[1], q[2]}) + + if rem.cmp(o) >= 0 { + rem, _ = rem.sub(o) + r = r.add64(1) + } + + return r, rem + } + + i := uint(bits.LeadingZeros64(o[2])) + u := o.lsh(i) + v := n.rsh(1) + r0, ur := bits.Div64(v[2], v[1], u[2]) + + p1, p0 := bits.Mul64(r0, u[1]) + if p1 > ur || (p1 == ur && p0 > v[0]) { + r0-- + } + + r0 >>= 63 - i + if r0 != 0 { + r0-- + } + + r := uint192{r0, 0, 0} + + q := o.mul(r) + rem, _ := n.sub(uint192{q[0], q[1], q[2]}) + + if rem.cmp(o) >= 0 { + rem, _ = rem.sub(o) + r = r.add64(1) + } + + return r, rem +} + +func (n uint192) div10() (uint192, uint64) { + var r1, r2, rem uint64 + if n[2] < 10 { + r1, rem = bits.Div64(n[2], n[1], 10) + } else { + r2, rem = bits.Div64(0, n[2], 10) + r1, rem = bits.Div64(rem, n[1], 10) + } + + r0, rem := bits.Div64(rem, n[0], 10) + + return uint192{r0, r1, r2}, rem +} + +func (n uint192) div10000() (uint192, uint64) { + var r1, r2, rem uint64 + if n[2] < 10000 { + r1, rem = bits.Div64(n[2], n[1], 10000) + } else { + r2, rem = bits.Div64(0, n[2], 10000) + r1, rem = bits.Div64(rem, n[1], 10000) + } + + r0, rem := bits.Div64(rem, n[0], 10000) + + return uint192{r0, r1, r2}, rem +} + +func (n uint192) div1e8() (uint192, uint64) { + var r1, r2, rem uint64 + if n[2] < 100_000_000 { + r1, rem = bits.Div64(n[2], n[1], 100_000_000) + } else { + r2, rem = bits.Div64(0, n[2], 100_000_000) + r1, rem = bits.Div64(rem, n[1], 100_000_000) + } + + r0, rem := bits.Div64(rem, n[0], 100_000_000) + + return uint192{r0, r1, r2}, rem +} + +func (n uint192) div1e19() (uint192, uint64) { + var r1, r2, rem uint64 + if n[2] < 10_000_000_000_000_000_000 { + r1, rem = bits.Div64(n[2], n[1], 10_000_000_000_000_000_000) + } else { + r2, rem = bits.Div64(0, n[2], 10_000_000_000_000_000_000) + r1, rem = bits.Div64(rem, n[1], 10_000_000_000_000_000_000) + } + + r0, rem := bits.Div64(rem, n[0], 10_000_000_000_000_000_000) + + return uint192{r0, r1, r2}, rem +} + +func (n uint192) log10() int { + var l2 int + if n[2] != 0 { + l2 = bits.Len64(n[2]) + 128 + } else if n[1] != 0 { + l2 = bits.Len64(n[1]) + 64 + } else if n[0] != 0 { + l2 = bits.Len64(n[0]) + } else { + return 0 + } + + l10 := l2 * 1233 >> 12 + if n.cmp(uint192PowersOf10[l10]) < 0 { + l10-- + } + + return l10 +} + +func (n uint192) lsh(o uint) uint192 { + var r0, r1, r2 uint64 + if o > 128 { + r2 = n[0] << (o - 128) + } else if o > 64 { + r1 = n[0] << (o - 64) + r2 = n[1]<<(o-64) | n[0]>>(128-o) + } else { + r0 = n[0] << o + r1 = n[1]<>(64-o) + r2 = n[2]<>(64-o) + } + + return uint192{r0, r1, r2} +} + +func (n uint192) mul(o uint192) uint384 { + s1, r0 := bits.Mul64(n[0], o[0]) + t2, t1 := bits.Mul64(n[1], o[0]) + s3, s2 := bits.Mul64(n[2], o[0]) + u2, u1 := bits.Mul64(n[0], o[1]) + v3, v2 := bits.Mul64(n[1], o[1]) + u4, u3 := bits.Mul64(n[2], o[1]) + w3, w2 := bits.Mul64(n[0], o[2]) + x4, x3 := bits.Mul64(n[1], o[2]) + w5, w4 := bits.Mul64(n[2], o[2]) + + r1, carry := bits.Add64(s1, t1, 0) + r2, a3 := bits.Add64(s2, t2, carry) + r1, carry = bits.Add64(r1, u1, 0) + r2, b3 := bits.Add64(r2, u2, carry) + r2, carry = bits.Add64(r2, v2, 0) + r3, a4 := bits.Add64(s3, u3, a3) + r3, b4 := bits.Add64(r3, v3, b3) + r3, c4 := bits.Add64(r3, w3, carry) + r2, carry = bits.Add64(r2, w2, 0) + r3, carry = bits.Add64(r3, x3, carry) + r4, a5 := bits.Add64(u4, w4, a4) + r4, b5 := bits.Add64(r4, x4, b4) + r4, carry = bits.Add64(r4, c4+carry, 0) + r5, _ := bits.Add64(w5, a5+b5+carry, 0) + + return uint384{r0, r1, r2, r3, r4, r5} +} + +func (n uint192) mul64(o uint64) uint192 { + s1, r0 := bits.Mul64(n[0], o) + t2, t1 := bits.Mul64(n[1], o) + u2 := n[2] * o + + r1, carry := bits.Add64(s1, t1, 0) + r2, _ := bits.Add64(t2, u2, carry) + + return uint192{r0, r1, r2} +} + +func (n uint192) msd2() int { + for n[2] >= 10 { + n, _ = n.div1e19() + } + + if n[2] != 0 { + n, _ = n.div10() + } + + n128 := uint128{n[0], n[1]} + + for n128[1] >= 10 { + n128, _ = n128.div1e19() + } + + if n128[1] != 0 { + n128, _ = n128.div10() + } + + n64 := n128[0] + + for n64 >= 10000 { + n64 /= 1000 + } + + for n64 >= 100 { + n64 /= 10 + } + + return int(n64) +} + +func (n uint192) pow2() uint384 { + s1, r0 := bits.Mul64(n[0], n[0]) + t2, t1 := bits.Mul64(n[1], n[0]) + s3, s2 := bits.Mul64(n[2], n[0]) + u2, u1 := bits.Mul64(n[0], n[1]) + v3, v2 := bits.Mul64(n[1], n[1]) + u4, u3 := bits.Mul64(n[2], n[1]) + w3, w2 := bits.Mul64(n[0], n[2]) + x4, x3 := bits.Mul64(n[1], n[2]) + w5, w4 := bits.Mul64(n[2], n[2]) + + r1, carry := bits.Add64(s1, t1, 0) + r2, a3 := bits.Add64(s2, t2, carry) + r1, carry = bits.Add64(r1, u1, 0) + r2, b3 := bits.Add64(r2, u2, carry) + r2, carry = bits.Add64(r2, v2, 0) + r3, a4 := bits.Add64(s3, u3, a3) + r3, b4 := bits.Add64(r3, v3, b3) + r3, c4 := bits.Add64(r3, w3, carry) + r2, carry = bits.Add64(r2, w2, 0) + r3, carry = bits.Add64(r3, x3, carry) + r4, a5 := bits.Add64(u4, w4, a4) + r4, b5 := bits.Add64(r4, x4, b4) + r4, carry = bits.Add64(r4, c4+carry, 0) + r5, _ := bits.Add64(w5, a5+b5+carry, 0) + + return uint384{r0, r1, r2, r3, r4, r5} +} + +func (n uint192) rsh(o uint) uint192 { + var r0, r1, r2 uint64 + if o > 128 { + r0 = n[2] >> (o - 128) + } else if o > 64 { + r0 = n[1]>>(o-64) | n[2]<<(128-o) + r1 = n[2] >> (o - 64) + } else { + r0 = n[0]>>o | n[1]<<(64-o) + r1 = n[1]>>o | n[2]<<(64-o) + r2 = n[2] >> o + } + + return uint192{r0, r1, r2} +} + +func (n uint192) sub(o uint192) (uint192, uint) { + r0, borrow := bits.Sub64(n[0], o[0], 0) + r1, borrow := bits.Sub64(n[1], o[1], borrow) + r2, borrow := bits.Sub64(n[2], o[2], borrow) + + return uint192{r0, r1, r2}, uint(borrow) +} + +func (n uint192) sub64(o uint64) uint192 { + r0, borrow := bits.Sub64(n[0], o, 0) + r1, borrow := bits.Sub64(n[1], 0, borrow) + r2 := n[2] - borrow + + return uint192{r0, r1, r2} +} + +func (n uint192) twos() uint192 { + r0, carry := bits.Add64(^n[0], 1, 0) + r1, carry := bits.Add64(^n[1], 0, carry) + r2 := ^n[2] + carry + + return uint192{r0, r1, r2} +} + +type uint256 [4]uint64 + +func (n uint256) String() string { + if n[0]|n[1]|n[2]|n[3] == 0 { + return "0" + } + + var buf [78]byte + + i := 78 + for n[0]|n[1]|n[2]|n[3] != 0 { + var d uint64 + n, d = n.div10() + i-- + buf[i] = '0' + byte(d) + } + + return string(buf[i:]) +} + +func (n uint256) div10() (uint256, uint64) { + var r2, r3, rem uint64 + if n[3] < 10 { + r2, rem = bits.Div64(n[3], n[2], 10) + } else { + r3, rem = bits.Div64(0, n[3], 10) + r2, rem = bits.Div64(rem, n[2], 10) + } + + r1, rem := bits.Div64(rem, n[1], 10) + r0, rem := bits.Div64(rem, n[0], 10) + + return uint256{r0, r1, r2, r3}, rem +} + +func (n uint256) div10000() (uint256, uint64) { + var r2, r3, rem uint64 + if n[3] < 10_000 { + r2, rem = bits.Div64(n[3], n[2], 10_000) + } else { + r3, rem = bits.Div64(0, n[3], 10_000) + r2, rem = bits.Div64(rem, n[2], 10_000) + } + + r1, rem := bits.Div64(rem, n[1], 10_000) + r0, rem := bits.Div64(rem, n[0], 10_000) + + return uint256{r0, r1, r2, r3}, rem +} + +func (n uint256) div1e8() (uint256, uint64) { + var r2, r3, rem uint64 + if n[3] < 100_000_000 { + r2, rem = bits.Div64(n[3], n[2], 100_000_000) + } else { + r3, rem = bits.Div64(0, n[3], 100_000_000) + r2, rem = bits.Div64(rem, n[2], 100_000_000) + } + + r1, rem := bits.Div64(rem, n[1], 100_000_000) + r0, rem := bits.Div64(rem, n[0], 100_000_000) + + return uint256{r0, r1, r2, r3}, rem +} + +func (n uint256) div1e19() (uint256, uint64) { + var r2, r3, rem uint64 + if n[3] < 10_000_000_000_000_000_000 { + r2, rem = bits.Div64(n[3], n[2], 10_000_000_000_000_000_000) + } else { + r3, rem = bits.Div64(0, n[3], 10_000_000_000_000_000_000) + r2, rem = bits.Div64(rem, n[2], 10_000_000_000_000_000_000) + } + + r1, rem := bits.Div64(rem, n[1], 10_000_000_000_000_000_000) + r0, rem := bits.Div64(rem, n[0], 10_000_000_000_000_000_000) + + return uint256{r0, r1, r2, r3}, rem +} + +func (n uint256) lsh(o uint) uint256 { + var r0, r1, r2, r3 uint64 + if o > 192 { + r3 = n[0] << (o - 192) + } else if o > 128 { + r2 = n[0] << (o - 128) + r3 = n[1]<<(o-128) | n[0]>>(192-o) + } else if o > 64 { + r1 = n[0] << (o - 64) + r2 = n[1]<<(o-64) | n[0]>>(128-o) + r3 = n[2]<<(o-64) | n[1]>>(128-o) + } else { + r0 = n[0] << o + r1 = n[1]<>(64-o) + r2 = n[2]<>(64-o) + r3 = n[3]<>(64-o) + } + + return uint256{r0, r1, r2, r3} +} + +func (n uint256) mul64(o uint64) uint256 { + s1, r0 := bits.Mul64(n[0], o) + t2, t1 := bits.Mul64(n[1], o) + u3, u2 := bits.Mul64(n[2], o) + v3 := n[3] * o + + r1, carry := bits.Add64(s1, t1, 0) + r2, carry := bits.Add64(t2, u2, carry) + r3, _ := bits.Add64(u3, v3, carry) + + return uint256{r0, r1, r2, r3} +} + +func (n uint256) rsh(o uint) uint256 { + var r0, r1, r2, r3 uint64 + if o > 192 { + r0 = n[3] >> (o - 192) + } else if o > 128 { + r0 = n[2]>>(o-128) | n[3]<<(192-o) + r1 = n[3] >> (o - 128) + } else if o > 64 { + r0 = n[1]>>(o-64) | n[2]<<(128-o) + r1 = n[2]>>(o-64) | n[3]<<(128-o) + r2 = n[3] >> (o - 64) + } else { + r0 = n[0]>>o | n[1]<<(64-o) + r1 = n[1]>>o | n[2]<<(64-o) + r2 = n[2]>>o | n[3]<<(64-o) + r3 = n[3] >> o + } + + return uint256{r0, r1, r2, r3} +} + +type uint384 [6]uint64 + +func (n uint384) String() string { + if n[0]|n[1]|n[2]|n[3]|n[4]|n[5] == 0 { + return "0" + } + + var buf [116]byte + + i := 116 + for n[0]|n[1]|n[2]|n[3]|n[4]|n[5] != 0 { + var d uint64 + n, d = n.div10() + i-- + buf[i] = '0' + byte(d) + } + + return string(buf[i:]) +} + +func (n uint384) div10() (uint384, uint64) { + var r5, r4, rem uint64 + if n[5] < 10 { + r4, rem = bits.Div64(n[5], n[4], 10) + } else { + r5, rem = bits.Div64(0, n[5], 10) + r4, rem = bits.Div64(rem, n[4], 10) + } + + r3, rem := bits.Div64(rem, n[3], 10) + r2, rem := bits.Div64(rem, n[2], 10) + r1, rem := bits.Div64(rem, n[1], 10) + r0, rem := bits.Div64(rem, n[0], 10) + + return uint384{r0, r1, r2, r3, r4, r5}, rem +} + +func (n uint384) div1e19() (uint384, uint64) { + var r5, r4, rem uint64 + if n[5] < 10_000_000_000_000_000_000 { + r4, rem = bits.Div64(n[5], n[4], 10_000_000_000_000_000_000) + } else { + r5, rem = bits.Div64(0, n[5], 10_000_000_000_000_000_000) + r4, rem = bits.Div64(rem, n[4], 10_000_000_000_000_000_000) + } + + r3, rem := bits.Div64(rem, n[3], 10_000_000_000_000_000_000) + r2, rem := bits.Div64(rem, n[2], 10_000_000_000_000_000_000) + r1, rem := bits.Div64(rem, n[1], 10_000_000_000_000_000_000) + r0, rem := bits.Div64(rem, n[0], 10_000_000_000_000_000_000) + + return uint384{r0, r1, r2, r3, r4, r5}, rem +} diff --git a/vendor/github.com/woodsbury/decimal128/json.go b/vendor/github.com/woodsbury/decimal128/json.go new file mode 100644 index 0000000..6cb9969 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/json.go @@ -0,0 +1,104 @@ +package decimal128 + +import ( + "encoding/json" + "reflect" +) + +// MarshalJSON implements the [encoding/json.Marshaler] interface. +func (d Decimal) MarshalJSON() ([]byte, error) { + if d.isSpecial() { + return nil, &json.UnsupportedValueError{ + Value: reflect.ValueOf(d), + Str: d.String(), + } + } + + var digs digits + d.digits(&digs) + + prec := 0 + if digs.ndig != 0 { + prec = digs.ndig - 1 + } + + exp := digs.exp + prec + + if exp < -6 || exp >= 20 { + return digs.fmtE(nil, prec, 0, false, false, false, false, false, false, 'e'), nil + } + + prec = 0 + if digs.exp < 0 { + prec = -digs.exp + } + + return digs.fmtF(nil, prec, 0, false, false, false, false, false), nil +} + +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. +func (d *Decimal) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + return nil + } + + l := len(data) + + if l == 0 { + return nil + } + + neg := false + + i := 0 + if data[0] == '+' { + i = 1 + } else if data[0] == '-' { + neg = true + i = 1 + } + + tmp, err := parseNumber(data[i:], neg, false) + if err != nil { + switch err := err.(type) { + case parseNumberRangeError: + return &json.UnmarshalTypeError{ + Value: "number " + string(data), + Type: reflect.TypeOf(Decimal{}), + } + case parseNumberSyntaxError: + switch data[0] { + case '[': + return &json.UnmarshalTypeError{ + Value: "array", + Type: reflect.TypeOf(Decimal{}), + } + case '{': + return &json.UnmarshalTypeError{ + Value: "object", + Type: reflect.TypeOf(Decimal{}), + } + case 'f', 't': + return &json.UnmarshalTypeError{ + Value: "bool", + Type: reflect.TypeOf(Decimal{}), + } + case '"': + return &json.UnmarshalTypeError{ + Value: "string", + Type: reflect.TypeOf(Decimal{}), + } + default: + return &json.UnmarshalTypeError{ + Value: "number " + string(data), + Type: reflect.TypeOf(Decimal{}), + } + } + default: + return err + } + } + + *d = tmp + return nil +} diff --git a/vendor/github.com/woodsbury/decimal128/payload.go b/vendor/github.com/woodsbury/decimal128/payload.go new file mode 100644 index 0000000..8892d2d --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/payload.go @@ -0,0 +1,125 @@ +package decimal128 + +import "fmt" + +const ( + payloadOpCompose Payload = iota + 1 + payloadOpFromFloat32 + payloadOpFromFloat64 + payloadOpMustParse + payloadOpNaN + payloadOpParse + payloadOpScan + payloadOpUnmarshalText + + payloadOpAdd + payloadOpLog + payloadOpLog10 + payloadOpLog1p + payloadOpLog2 + payloadOpMul + payloadOpPow + payloadOpQuo + payloadOpQuoRem + payloadOpSqrt + payloadOpSub +) + +const ( + payloadValPosZero Payload = iota + 1 + payloadValNegZero + payloadValPosFinite + payloadValNegFinite + payloadValPosInfinite + payloadValNegInfinite +) + +// Payload returns the first 64 bits of the payload of a NaN. If the NaN was +// generated by an operation in this package, the contents of the payload +// provides information about what kind of operation was being performed that +// lead to the NaN being generated. Payload panics if d is not a NaN. +func (d Decimal) Payload() Payload { + if !d.IsNaN() { + panic("Decimal(!NaN).Payload()") + } + + return Payload(d.lo) +} + +// Payload represents the payload value of a NaN decimal. This value can +// contain additional information about the operation that caused the value to +// be set to NaN. +type Payload uint64 + +// String returns a string representation of the payload. +func (p Payload) String() string { + if p == 0 { + return "Payload(0)" + } + + if p > 0x00ff_ffff { + return fmt.Sprintf("Payload(%d)", uint64(p)) + } + + switch p & 0xff { + case payloadOpCompose: + return "Compose()" + case payloadOpFromFloat32: + return "FromFloat32()" + case payloadOpFromFloat64: + return "FromFloat64()" + case payloadOpMustParse: + return "MustParse()" + case payloadOpNaN: + return "NaN()" + case payloadOpParse: + return "Parse()" + case payloadOpScan: + return "Scan()" + case payloadOpUnmarshalText: + return "UnmarshalText()" + case payloadOpAdd: + return "Add(" + p.argString(8) + ", " + p.argString(16) + ")" + case payloadOpLog: + return "Log(" + p.argString(8) + ")" + case payloadOpLog10: + return "Log10(" + p.argString(8) + ")" + case payloadOpLog1p: + return "Log1p(" + p.argString(8) + ")" + case payloadOpLog2: + return "Log2(" + p.argString(8) + ")" + case payloadOpMul: + return "Mul(" + p.argString(8) + ", " + p.argString(16) + ")" + case payloadOpPow: + return "Pow(" + p.argString(8) + ", " + p.argString(16) + ")" + case payloadOpQuo: + return "Quo(" + p.argString(8) + ", " + p.argString(16) + ")" + case payloadOpQuoRem: + return "QuoRem(" + p.argString(8) + ", " + p.argString(16) + ")" + case payloadOpSqrt: + return "Sqrt(" + p.argString(8) + ")" + case payloadOpSub: + return "Sub(" + p.argString(8) + ", " + p.argString(16) + ")" + default: + return fmt.Sprintf("Payload(%d)", uint64(p)) + } +} + +func (p Payload) argString(offset int) string { + switch p >> offset & 0xff { + case payloadValPosZero: + return "Zero" + case payloadValNegZero: + return "-Zero" + case payloadValPosFinite: + return "Finite" + case payloadValNegFinite: + return "-Finite" + case payloadValPosInfinite: + return "Infinite" + case payloadValNegInfinite: + return "-Infinite" + default: + return "Unknown" + } +} diff --git a/vendor/github.com/woodsbury/decimal128/rounding.go b/vendor/github.com/woodsbury/decimal128/rounding.go new file mode 100644 index 0000000..ac47f40 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/rounding.go @@ -0,0 +1,747 @@ +package decimal128 + +import "fmt" + +// Ceil returns the least integer value greater than or equal to d. +// +// Ceil is equivalent to: +// +// d.Ceil(0) +func Ceil(d Decimal) Decimal { + return d.Ceil(0) +} + +// Floor returns the greatest integer value less than or equal to d. +// +// Floor is equivalent to: +// +// d.Floor(0) +func Floor(d Decimal) Decimal { + return d.Floor(0) +} + +// Round returns the nearest integer, rounding half away from zero. +// +// Round is equivalent to: +// +// d.Round(0, decimal128.ToNearestAway) +func Round(d Decimal) Decimal { + return d.Round(0, ToNearestAway) +} + +// Trunc returns the integer value of d. +// +// Trunc is equivalent to: +// +// d.Round(0, decimal128.ToZero) +func Trunc(d Decimal) Decimal { + return d.Round(0, ToZero) +} + +// Ceil returns the least Decimal value greater than or equal to d that has no +// digits after the specified number of decimal places. +// +// The value of dp affects how many digits after the decimal point the Decimal +// would have if it were printed in decimal notation (for example, by the '%f' +// verb in Format). It can be zero to return an integer, and can also be +// negative to round off digits before the decimal point. +// +// NaN and infinity values are left untouched. +func (d Decimal) Ceil(dp int) Decimal { + if d.isSpecial() { + return d + } + + sig, exp := d.decompose() + + if sig[0]|sig[1] == 0 { + return zero(d.Signbit()) + } + + dp = dp*-1 + exponentBias + iexp := int(exp) + + if iexp >= dp { + return d + } + + if iexp < dp-maxDigits { + if d.Signbit() { + return zero(d.Signbit()) + } + + return compose(false, uint128{1, 0}, int16(dp)) + } + + var trunc int8 + + for iexp < dp { + var rem uint64 + sig, rem = sig.div10() + + if rem != 0 { + trunc = 1 + } + + if sig[0]|sig[1] == 0 { + iexp = dp + break + } + + iexp++ + } + + neg := d.Signbit() + exp = int16(iexp) + + if !neg { + for trunc != 0 { + sig = sig.add64(1) + trunc = 0 + + if sig[1] > 0x0002_7fff_ffff_ffff { + var rem uint64 + sig, rem = sig.div10() + + if rem != 0 { + trunc = 1 + } + + exp++ + } + } + } + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// Floor returns the greatest Decimal value less than or equal to d that has no +// digits after the specified number of decimal places. +// +// The value of dp affects how many digits after the decimal point the Decimal +// would have if it were printed in decimal notation (for example, by the '%f' +// verb in Format). It can be zero to return an integer, and can also be +// negative to round off digits before the decimal point. +// +// NaN and infinity values are left untouched. +func (d Decimal) Floor(dp int) Decimal { + if d.isSpecial() { + return d + } + + sig, exp := d.decompose() + + if sig[0]|sig[1] == 0 { + return zero(d.Signbit()) + } + + dp = dp*-1 + exponentBias + iexp := int(exp) + + if iexp >= dp { + return d + } + + if iexp < dp-maxDigits { + if !d.Signbit() { + return zero(d.Signbit()) + } + + return compose(true, uint128{1, 0}, int16(dp)) + } + + var trunc int8 + + for iexp < dp { + var rem uint64 + sig, rem = sig.div10() + + if rem != 0 { + trunc = 1 + } + + if sig[0]|sig[1] == 0 { + iexp = dp + break + } + + iexp++ + } + + neg := d.Signbit() + exp = int16(iexp) + + if neg { + for trunc != 0 { + sig = sig.add64(1) + trunc = 0 + + if sig[1] > 0x0002_7fff_ffff_ffff { + var rem uint64 + sig, rem = sig.div10() + + if rem != 0 { + trunc = 1 + } + + exp++ + } + } + } + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// Round rounds (or quantises) a Decimal value to the specified number of +// decimal places using the rounding mode provided. +// +// The value of dp affects how many digits after the decimal point the Decimal +// would have if it were printed in decimal notation (for example, by the '%f' +// verb in Format). It can be zero to round off all digits after the decimal +// point and return an integer, and can also be negative to round off digits +// before the decimal point. +// +// NaN and infinity values are left untouched. +func (d Decimal) Round(dp int, mode RoundingMode) Decimal { + if d.isSpecial() { + return d + } + + sig, exp := d.decompose() + + if sig[0]|sig[1] == 0 { + return zero(d.Signbit()) + } + + dp = dp*-1 + exponentBias + iexp := int(exp) + + if iexp >= dp { + return d + } + + if iexp < dp-maxDigits { + return zero(d.Signbit()) + } + + var trunc int8 + var digit uint64 + + for iexp < dp { + if digit != 0 { + trunc = 1 + } + + sig, digit = sig.div10() + + if sig[0]|sig[1]|digit == 0 { + return zero(d.Signbit()) + } + + iexp++ + } + + neg := d.Signbit() + sig, exp = mode.round(false, neg, sig, int16(iexp), trunc, digit) + + if exp > maxBiasedExponent { + return inf(neg) + } + + return compose(neg, sig, exp) +} + +// RoundingMode determines how a Decimal value is rounded when the result of an +// operation is greater than the format can hold. +type RoundingMode uint8 + +const ( + ToNearestEven RoundingMode = iota // == IEEE 754 roundTiesToEven + ToNearestAway // == IEEE 754 roundTiesToAway + ToZero // == IEEE 754 roundTowardZero + AwayFromZero // no IEEE 754 equivalent + ToNegativeInf // == IEEE 754 roundTowardNegative + ToPositiveInf // == IEEE 754 roundTowardPositive +) + +// String returns a string representation of the rounding mode. +func (rm RoundingMode) String() string { + switch rm { + case ToNearestEven: + return "ToNearestEven" + case ToNearestAway: + return "ToNearestAway" + case ToZero: + return "ToZero" + case AwayFromZero: + return "AwayFromZero" + case ToNegativeInf: + return "ToNegativeInf" + case ToPositiveInf: + return "ToPositiveInf" + default: + return fmt.Sprintf("RoundingMode(%d)", uint8(rm)) + } +} + +func (rm RoundingMode) reduce256(neg bool, sig256 uint256, exp int16, trunc int8) (uint128, int16) { + for sig256[3] > 0 { + var rem uint64 + sig256, rem = sig256.div1e19() + exp += 19 + + if rem != 0 { + trunc = 1 + } + } + + sig192 := uint192{sig256[0], sig256[1], sig256[2]} + + if sig192[2] > 10000 { + var rem uint64 + sig192, rem = sig192.div1e8() + exp += 8 + + if rem != 0 { + trunc = 1 + } + } + + for sig192[2] > 0 { + var rem uint64 + sig192, rem = sig192.div10000() + exp += 4 + + if rem != 0 { + trunc = 1 + } + } + + sig := uint128{sig192[0], sig192[1]} + + var digit uint64 + + if sig[1] > 0x09c4_0000_0000_0000 { + var rem uint64 + sig, rem = sig.div10000() + exp += 4 + + if rem != 0 { + digit = rem / 1000 + + if rem%1000 != 0 { + trunc = 1 + } + } + } else if sig[1] > 0x00fa_0000_0000_0000 { + var rem uint64 + sig, rem = sig.div1000() + exp += 3 + + if rem != 0 { + digit = rem / 100 + + if rem%100 != 0 { + trunc = 1 + } + } + } else if sig[1] > 0x0019_0000_0000_0000 { + var rem uint64 + sig, rem = sig.div100() + exp += 2 + + if rem != 0 { + digit = rem / 10 + + if rem%10 != 0 { + trunc = 1 + } + } + } + + for sig[1] > 0x0002_7fff_ffff_ffff { + if digit != 0 { + trunc = 1 + } + + sig, digit = sig.div10() + exp++ + } + + for exp < minBiasedExponent { + if digit != 0 { + trunc = 1 + } + + sig, digit = sig.div10() + + if sig[0]|sig[1]|digit == 0 { + trunc = 0 + digit = 0 + exp = 0 + break + } + + exp++ + } + + for exp > maxBiasedExponent && sig[1] < 0x0002_7fff_ffff_ffff { + tmp := sig.mul64(10) + + if tmp[1] <= 0x0002_7fff_ffff_ffff { + sig = tmp + exp-- + } else { + break + } + } + + return rm.round(true, neg, sig, exp, trunc, digit) +} + +func (rm RoundingMode) reduce192(neg bool, sig192 uint192, exp int16, trunc int8) (uint128, int16) { + if sig192[2] > 10000 { + var rem uint64 + sig192, rem = sig192.div1e8() + exp += 8 + + if rem != 0 { + trunc = 1 + } + } + + for sig192[2] > 0 { + var rem uint64 + sig192, rem = sig192.div10000() + exp += 4 + + if rem != 0 { + trunc = 1 + } + } + + sig := uint128{sig192[0], sig192[1]} + + var digit uint64 + + if sig[1] > 0x09c4_0000_0000_0000 { + var rem uint64 + sig, rem = sig.div10000() + exp += 4 + + if rem != 0 { + digit = rem / 1000 + + if rem%1000 != 0 { + trunc = 1 + } + } + } else if sig[1] > 0x00fa_0000_0000_0000 { + var rem uint64 + sig, rem = sig.div1000() + exp += 3 + + if rem != 0 { + digit = rem / 100 + + if rem%100 != 0 { + trunc = 1 + } + } + } else if sig[1] > 0x0019_0000_0000_0000 { + var rem uint64 + sig, rem = sig.div100() + exp += 2 + + if rem != 0 { + digit = rem / 10 + + if rem%10 != 0 { + trunc = 1 + } + } + } + + for sig[1] > 0x0002_7fff_ffff_ffff { + if digit != 0 { + trunc = 1 + } + + sig, digit = sig.div10() + exp++ + } + + for exp < minBiasedExponent { + if digit != 0 { + trunc = 1 + } + + sig, digit = sig.div10() + + if sig[0]|sig[1]|digit == 0 { + trunc = 0 + digit = 0 + exp = 0 + break + } + + exp++ + } + + for exp > maxBiasedExponent && sig[1] < 0x0002_7fff_ffff_ffff { + tmp := sig.mul64(10) + + if tmp[1] <= 0x0002_7fff_ffff_ffff { + sig = tmp + exp-- + } else { + break + } + } + + return rm.round(true, neg, sig, exp, trunc, digit) +} + +func (rm RoundingMode) reduce128(neg bool, sig uint128, exp int16, trunc int8) (uint128, int16) { + var digit uint64 + + if sig[1] > 0x09c4_0000_0000_0000 { + var rem uint64 + sig, rem = sig.div10000() + exp += 4 + + if rem != 0 { + digit = rem / 1000 + + if rem%1000 != 0 { + trunc = 1 + } + } + } else if sig[1] > 0x00fa_0000_0000_0000 { + var rem uint64 + sig, rem = sig.div1000() + exp += 3 + + if rem != 0 { + digit = rem / 100 + + if rem%100 != 0 { + trunc = 1 + } + } + } else if sig[1] > 0x0019_0000_0000_0000 { + var rem uint64 + sig, rem = sig.div100() + exp += 2 + + if rem != 0 { + digit = rem / 10 + + if rem%10 != 0 { + trunc = 1 + } + } + } + + for sig[1] > 0x0002_7fff_ffff_ffff { + if digit != 0 { + trunc = 1 + } + + sig, digit = sig.div10() + exp++ + } + + for exp < minBiasedExponent { + if digit != 0 { + trunc = 1 + } + + sig, digit = sig.div10() + + if sig[0]|sig[1]|digit == 0 { + trunc = 0 + digit = 0 + exp = 0 + break + } + + exp++ + } + + for exp > maxBiasedExponent && sig[1] < 0x0002_7fff_ffff_ffff { + tmp := sig.mul64(10) + + if tmp[1] <= 0x0002_7fff_ffff_ffff { + sig = tmp + exp-- + } else { + break + } + } + + return rm.round(true, neg, sig, exp, trunc, digit) +} + +func (rm RoundingMode) reduce64(neg bool, sig64 uint64, exp int16) (uint128, int16) { + var trunc int8 + var digit uint64 + + for exp < minBiasedExponent { + if digit != 0 { + trunc = 1 + } + + digit = sig64 % 10 + sig64 = sig64 / 10 + + if sig64|digit == 0 { + trunc = 0 + digit = 0 + exp = 0 + break + } + + exp++ + } + + sig := uint128{sig64, 0} + + for exp > maxBiasedExponent && sig[1] < 0x0002_7fff_ffff_ffff { + tmp := sig.mul64(10) + + if tmp[1] <= 0x0002_7fff_ffff_ffff { + sig = tmp + exp-- + } else { + break + } + } + + return rm.round(true, neg, sig, exp, trunc, digit) +} + +func (rm RoundingMode) round(shift, neg bool, sig uint128, exp int16, trunc int8, digit uint64) (uint128, int16) { + for { + var adjust int + switch rm { + case ToNearestEven: + if trunc == 1 { + if digit >= 5 { + adjust = 1 + } + } else if trunc == -1 { + if digit > 5 { + adjust = 1 + } + } else { + if digit > 5 { + adjust = 1 + } else if digit == 5 { + if sig[0]%2 != 0 { + adjust = 1 + } + } + } + case ToNearestAway: + if digit >= 5 { + adjust = 1 + } + case ToZero: + if trunc == -1 && digit == 0 { + adjust = -1 + } + case AwayFromZero: + if trunc == 1 || digit != 0 { + adjust = 1 + } + case ToPositiveInf: + if neg { + if trunc == -1 && digit == 0 { + adjust = -1 + } + } else if trunc == 1 || digit != 0 { + adjust = 1 + } + case ToNegativeInf: + if neg { + if trunc == 1 || digit != 0 { + adjust = 1 + } + } else if trunc == -1 && digit == 0 { + adjust = -1 + } + } + + if adjust != 0 { + var tsig uint128 + if adjust == 1 { + if shift { + if sig[0]|sig[1] != 0 { + if exp >= minBiasedExponent+19 && sig[1] == 0 { + sig = sig.mul64(10_000_000_000_000_000_000) + exp -= 19 + } + + for exp > minBiasedExponent && sig[1] < 0x0002_7fff_ffff_ffff/10 { + sig = sig.mul64(10) + exp-- + } + } else { + exp = minBiasedExponent + } + + shift = false + } + + tsig = sig.add64(1) + } else { + if shift { + if sig[0]|sig[1] != 0 { + if exp >= minBiasedExponent+19 && sig[1] == 0 { + sig = sig.mul64(10_000_000_000_000_000_000) + exp -= 19 + } + + for exp > minBiasedExponent && sig[1] <= 0x0002_7fff_ffff_ffff/10 { + sig = sig.mul64(10) + exp-- + } + } else { + exp = minBiasedExponent + } + + shift = false + } + + tsig = sig.sub64(1) + } + + if tsig[1] > 0x0002_7fff_ffff_ffff { + if digit != 0 { + trunc = 1 + } + + sig, digit = sig.div10() + exp++ + continue + } + + sig = tsig + } + + return sig, exp + } +} + +// DefaultRoundingMode is the rounding mode used by any methods where an +// alternate rounding mode isn't provided. +var DefaultRoundingMode RoundingMode = ToNearestEven diff --git a/vendor/github.com/woodsbury/decimal128/scan.go b/vendor/github.com/woodsbury/decimal128/scan.go new file mode 100644 index 0000000..8fb7910 --- /dev/null +++ b/vendor/github.com/woodsbury/decimal128/scan.go @@ -0,0 +1,475 @@ +package decimal128 + +import ( + "errors" + "fmt" + "io" + "strconv" +) + +// MustParse is like [Parse] but panics if the provided string cannot be parsed, +// instead of returning an error. +func MustParse(s string) Decimal { + d, err := parse(s, payloadOpMustParse) + if err != nil { + panic("decimal128.MustParse(" + strconv.Quote(s) + "): invalid syntax") + } + + return d +} + +// Parse parses a Decimal value from the string provided. Parse accepts decimal +// floating point syntax. An underscore character '_' may appear between digits +// as a separator. Parse also recognises the string "NaN", and the (possibly +// signed) strings "Inf" and "Infinity", as their respective special floating +// point values. It ignores case when matching. +// +// If s is not syntactically well-formed, Parse returns an error that can be +// compared to [strconv.ErrSyntax] via [errors.Is]. +// +// If the value is too precise to fit in a Decimal the result is rounded using +// the [DefaultRoundingMode]. If the value is greater than the largest possible +// Decimal value, Parse returns ±Inf and an error that can be compared to +// [strconv.ErrRange] via [errors.Is]. +func Parse(s string) (Decimal, error) { + return parse(s, payloadOpParse) +} + +// Scan implements the [fmt.Scanner] interface. It supports the verbs 'e', 'E', +// 'f', 'F', 'g', 'G', and 'v'. +func (d *Decimal) Scan(f fmt.ScanState, verb rune) error { + switch verb { + case 'e', 'E', 'f', 'F', 'g', 'G', 'v': + default: + return errors.New("bad verb '%" + string(verb) + "' for Decimal") + } + + f.SkipSpace() + r, _, err := f.ReadRune() + if err != nil { + if errors.Is(err, io.EOF) { + return io.ErrUnexpectedEOF + } + + return err + } + + neg := false + + if r == '-' { + neg = true + } else if r != '+' { + f.UnreadRune() + } + + r, _, err = f.ReadRune() + if err != nil { + if errors.Is(err, io.EOF) { + return io.ErrUnexpectedEOF + } + + return err + } + + if r == 'I' || r == 'i' { + r2, _, err := f.ReadRune() + if err != nil { + if errors.Is(err, io.EOF) { + return io.ErrUnexpectedEOF + } + + return err + } + + if r2 != 'N' && r2 != 'n' { + return &parseSyntaxError{s: string([]rune{r, r2})} + } + + r3, _, err := f.ReadRune() + if err != nil { + if errors.Is(err, io.EOF) { + return io.ErrUnexpectedEOF + } + + return err + } + + if r3 != 'F' && r3 != 'f' { + return &parseSyntaxError{s: string([]rune{r, r2, r3})} + } + + *d = inf(neg) + return nil + } + + if r == 'N' || r == 'n' { + r2, _, err := f.ReadRune() + if err != nil { + if errors.Is(err, io.EOF) { + return io.ErrUnexpectedEOF + } + + return err + } + + if r2 != 'A' && r2 != 'a' { + return &parseSyntaxError{s: string([]rune{r, r2})} + } + + r3, _, err := f.ReadRune() + if err != nil { + if errors.Is(err, io.EOF) { + return io.ErrUnexpectedEOF + } + + return err + } + + if r3 != 'N' && r3 != 'n' { + return &parseSyntaxError{s: string([]rune{r, r2, r3})} + } + + *d = nan(payloadOpScan, 0, 0) + return nil + } + + f.UnreadRune() + + tok, err := f.Token(false, func(r rune) bool { + switch { + case r >= '0' && r <= '9': + return true + case r == '.': + return true + case r == 'E' || r == 'e': + return true + case r == '-': + return true + case r == '_': + return true + case r == '+': + return true + default: + return false + } + }) + + if err != nil { + return err + } + + tmp, err := parseNumber(tok, neg, true) + if err != nil { + switch err := err.(type) { + case parseNumberRangeError: + return &parseRangeError{string(tok)} + case parseNumberSyntaxError: + return &parseSyntaxError{string(tok)} + default: + return err + } + } + + *d = tmp + return nil +} + +// UnmarshalText implements the [encoding.TextUnmarshaler] interface. +func (d *Decimal) UnmarshalText(data []byte) error { + tmp, err := parse(data, payloadOpUnmarshalText) + if err != nil { + return err + } + + *d = tmp + return nil +} + +func parse[D []byte | string](d D, op Payload) (Decimal, error) { + if len(d) == 0 { + return Decimal{}, &parseSyntaxError{} + } + + s := d + neg := false + + if d[0] == '+' { + d = d[1:] + } else if d[0] == '-' { + neg = true + d = d[1:] + } + + l := len(d) + + if l == 0 { + return Decimal{}, &parseSyntaxError{string(s)} + } else if l == 3 { + if (d[0] == 'I' || d[0] == 'i') && (d[1] == 'N' || d[1] == 'n') && (d[2] == 'F' || d[2] == 'f') { + return inf(neg), nil + } + + if (d[0] == 'N' || d[0] == 'n') && (d[1] == 'A' || d[1] == 'a') && (d[2] == 'N' || d[2] == 'n') { + return nan(op, 0, 0), nil + } + } else if l == 8 { + if (d[0] == 'I' || d[0] == 'i') && (d[1] == 'N' || d[1] == 'n') && (d[2] == 'F' || d[2] == 'f') && (d[3] == 'I' || d[3] == 'i') && (d[4] == 'N' || d[4] == 'n') && (d[5] == 'I' || d[5] == 'i') && (d[6] == 'T' || d[6] == 't') && (d[7] == 'Y' || d[7] == 'y') { + return inf(neg), nil + } + } + + v, err := parseNumber(d, neg, true) + if err != nil { + switch err := err.(type) { + case parseNumberRangeError: + return v, &parseRangeError{string(s)} + case parseNumberSyntaxError: + return v, &parseSyntaxError{string(s)} + default: + return v, err + } + } + + return v, nil +} + +func parseNumber[D []byte | string](d D, neg, sepallowed bool) (Decimal, error) { + var sig64 uint64 + var nfrac int16 + var trunc int8 + caneof := false + cansep := false + cansgn := false + eneg := false + sawdig := false + sawdot := false + sawexp := false + + l := len(d) + i := 0 + for ; !sawexp && sig64 <= 0x18ff_ffff_ffff_ffff && i < l; i++ { + switch c := d[i]; true { + case c >= '0' && c <= '9': + caneof = true + cansep = true + cansgn = false + sawdig = true + + sig64 = sig64*10 + uint64(c-'0') + + if sawdot { + nfrac++ + } + case c == '.': + if sawdot { + return Decimal{}, parseNumberSyntaxError{} + } + + caneof = true + cansep = false + cansgn = false + sawdot = true + case c == 'E' || c == 'e': + if !sawdig { + return Decimal{}, parseNumberSyntaxError{} + } + + caneof = false + cansep = false + cansgn = true + sawexp = true + case c == '_': + if !sepallowed || !cansep { + return Decimal{}, parseNumberSyntaxError{} + } + + caneof = false + cansep = false + cansgn = false + default: + return Decimal{}, parseNumberSyntaxError{} + } + } + + sig := uint128{sig64, 0} + var exp int16 + maxexp := false + + for ; i < l; i++ { + switch c := d[i]; true { + case c >= '0' && c <= '9': + caneof = true + cansep = true + cansgn = false + sawdig = true + + if sawexp { + if exp > exponentBias/10+1 { + maxexp = true + } + + exp *= 10 + exp += int16(c - '0') + } else { + if sig[1] <= 0x18ff_ffff_ffff_ffff { + if sig[1] <= 0x027f_ffff_ffff_ffff && i < l-1 { + c2 := d[i+1] + if c2 >= '0' && c2 <= '9' { + sig = sig.mul64(100) + sig = sig.add64(uint64(c-'0')*10 + uint64(c2-'0')) + + if sawdot { + nfrac += 2 + } + + i++ + continue + } + } + + sig = sig.mul64(10) + sig = sig.add64(uint64(c - '0')) + + if sawdot { + nfrac++ + } + } else { + if c != '0' { + trunc = 1 + } + + if !sawdot { + if exp < exponentBias+39 { + nfrac-- + } + } + } + } + case c == '.': + if sawdot || sawexp { + return Decimal{}, parseNumberSyntaxError{} + } + + caneof = true + cansep = false + cansgn = false + sawdot = true + case c == 'E' || c == 'e': + if !sawdig || sawexp { + return Decimal{}, parseNumberSyntaxError{} + } + + caneof = false + cansep = false + cansgn = true + sawexp = true + case c == '-': + if !cansgn { + return Decimal{}, parseNumberSyntaxError{} + } + + caneof = false + cansep = false + cansgn = false + eneg = true + case c == '_': + if !cansep { + return Decimal{}, parseNumberSyntaxError{} + } + + caneof = false + cansep = false + cansgn = false + case c == '+': + if !cansgn { + return Decimal{}, parseNumberSyntaxError{} + } + + caneof = false + cansep = false + cansgn = false + default: + return Decimal{}, parseNumberSyntaxError{} + } + } + + if !caneof { + return Decimal{}, parseNumberSyntaxError{} + } + + if sig[0]|sig[1] == 0 { + return zero(neg), nil + } + + // If the exponent value is larger than the maximum supported exponent, + // there are two cases where the value is still valid: + // - the exponent is negative, where the logical value rounds to 0 + // - the significand is zero, where the logical value is 0 + // + // Otherwise, return a range error. + if maxexp { + if eneg { + return zero(neg), nil + } + + return inf(neg), parseNumberRangeError{} + } + + if eneg { + exp *= -1 + } + + exp -= nfrac + + if exp > maxUnbiasedExponent+39 { + return inf(neg), parseNumberRangeError{} + } + + if exp < minUnbiasedExponent-39 { + return zero(neg), nil + } + + sig, exp = DefaultRoundingMode.reduce128(neg, sig, exp+exponentBias, trunc) + + if exp > maxBiasedExponent { + return inf(neg), parseNumberRangeError{} + } + + return compose(neg, sig, exp), nil +} + +type parseNumberRangeError struct{} + +func (err parseNumberRangeError) Error() string { + return "value out of range" +} + +type parseNumberSyntaxError struct{} + +func (err parseNumberSyntaxError) Error() string { + return "invalid syntax" +} + +type parseRangeError struct { + s string +} + +func (err *parseRangeError) Error() string { + return "parsing " + strconv.Quote(err.s) + ": value out of range" +} + +func (err *parseRangeError) Is(target error) bool { + return target == strconv.ErrRange +} + +type parseSyntaxError struct { + s string +} + +func (err *parseSyntaxError) Error() string { + return "parsing " + strconv.Quote(err.s) + ": invalid syntax" +} + +func (err *parseSyntaxError) Is(target error) bool { + return target == strconv.ErrSyntax +} diff --git a/vendor/golang.org/x/mod/modfile/print.go b/vendor/golang.org/x/mod/modfile/print.go new file mode 100644 index 0000000..2a0123d --- /dev/null +++ b/vendor/golang.org/x/mod/modfile/print.go @@ -0,0 +1,184 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Module file printer. + +package modfile + +import ( + "bytes" + "fmt" + "strings" +) + +// Format returns a go.mod file as a byte slice, formatted in standard style. +func Format(f *FileSyntax) []byte { + pr := &printer{} + pr.file(f) + + // remove trailing blank lines + b := pr.Bytes() + for len(b) > 0 && b[len(b)-1] == '\n' && (len(b) == 1 || b[len(b)-2] == '\n') { + b = b[:len(b)-1] + } + return b +} + +// A printer collects the state during printing of a file or expression. +type printer struct { + bytes.Buffer // output buffer + comment []Comment // pending end-of-line comments + margin int // left margin (indent), a number of tabs +} + +// printf prints to the buffer. +func (p *printer) printf(format string, args ...interface{}) { + fmt.Fprintf(p, format, args...) +} + +// indent returns the position on the current line, in bytes, 0-indexed. +func (p *printer) indent() int { + b := p.Bytes() + n := 0 + for n < len(b) && b[len(b)-1-n] != '\n' { + n++ + } + return n +} + +// newline ends the current line, flushing end-of-line comments. +func (p *printer) newline() { + if len(p.comment) > 0 { + p.printf(" ") + for i, com := range p.comment { + if i > 0 { + p.trim() + p.printf("\n") + for i := 0; i < p.margin; i++ { + p.printf("\t") + } + } + p.printf("%s", strings.TrimSpace(com.Token)) + } + p.comment = p.comment[:0] + } + + p.trim() + if b := p.Bytes(); len(b) == 0 || (len(b) >= 2 && b[len(b)-1] == '\n' && b[len(b)-2] == '\n') { + // skip the blank line at top of file or after a blank line + } else { + p.printf("\n") + } + for i := 0; i < p.margin; i++ { + p.printf("\t") + } +} + +// trim removes trailing spaces and tabs from the current line. +func (p *printer) trim() { + // Remove trailing spaces and tabs from line we're about to end. + b := p.Bytes() + n := len(b) + for n > 0 && (b[n-1] == '\t' || b[n-1] == ' ') { + n-- + } + p.Truncate(n) +} + +// file formats the given file into the print buffer. +func (p *printer) file(f *FileSyntax) { + for _, com := range f.Before { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + + for i, stmt := range f.Stmt { + switch x := stmt.(type) { + case *CommentBlock: + // comments already handled + p.expr(x) + + default: + p.expr(x) + p.newline() + } + + for _, com := range stmt.Comment().After { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + + if i+1 < len(f.Stmt) { + p.newline() + } + } +} + +func (p *printer) expr(x Expr) { + // Emit line-comments preceding this expression. + if before := x.Comment().Before; len(before) > 0 { + // Want to print a line comment. + // Line comments must be at the current margin. + p.trim() + if p.indent() > 0 { + // There's other text on the line. Start a new line. + p.printf("\n") + } + // Re-indent to margin. + for i := 0; i < p.margin; i++ { + p.printf("\t") + } + for _, com := range before { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + } + + switch x := x.(type) { + default: + panic(fmt.Errorf("printer: unexpected type %T", x)) + + case *CommentBlock: + // done + + case *LParen: + p.printf("(") + case *RParen: + p.printf(")") + + case *Line: + p.tokens(x.Token) + + case *LineBlock: + p.tokens(x.Token) + p.printf(" ") + p.expr(&x.LParen) + p.margin++ + for _, l := range x.Line { + p.newline() + p.expr(l) + } + p.margin-- + p.newline() + p.expr(&x.RParen) + } + + // Queue end-of-line comments for printing when we + // reach the end of the line. + p.comment = append(p.comment, x.Comment().Suffix...) +} + +func (p *printer) tokens(tokens []string) { + sep := "" + for _, t := range tokens { + if t == "," || t == ")" || t == "]" || t == "}" { + sep = "" + } + p.printf("%s%s", sep, t) + sep = " " + if t == "(" || t == "[" || t == "{" { + sep = "" + } + } +} diff --git a/vendor/golang.org/x/mod/modfile/read.go b/vendor/golang.org/x/mod/modfile/read.go new file mode 100644 index 0000000..2d74868 --- /dev/null +++ b/vendor/golang.org/x/mod/modfile/read.go @@ -0,0 +1,964 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfile + +import ( + "bytes" + "errors" + "fmt" + "os" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// A Position describes an arbitrary source position in a file, including the +// file, line, column, and byte offset. +type Position struct { + Line int // line in input (starting at 1) + LineRune int // rune in line (starting at 1) + Byte int // byte in input (starting at 0) +} + +// add returns the position at the end of s, assuming it starts at p. +func (p Position) add(s string) Position { + p.Byte += len(s) + if n := strings.Count(s, "\n"); n > 0 { + p.Line += n + s = s[strings.LastIndex(s, "\n")+1:] + p.LineRune = 1 + } + p.LineRune += utf8.RuneCountInString(s) + return p +} + +// An Expr represents an input element. +type Expr interface { + // Span returns the start and end position of the expression, + // excluding leading or trailing comments. + Span() (start, end Position) + + // Comment returns the comments attached to the expression. + // This method would normally be named 'Comments' but that + // would interfere with embedding a type of the same name. + Comment() *Comments +} + +// A Comment represents a single // comment. +type Comment struct { + Start Position + Token string // without trailing newline + Suffix bool // an end of line (not whole line) comment +} + +// Comments collects the comments associated with an expression. +type Comments struct { + Before []Comment // whole-line comments before this expression + Suffix []Comment // end-of-line comments after this expression + + // For top-level expressions only, After lists whole-line + // comments following the expression. + After []Comment +} + +// Comment returns the receiver. This isn't useful by itself, but +// a [Comments] struct is embedded into all the expression +// implementation types, and this gives each of those a Comment +// method to satisfy the Expr interface. +func (c *Comments) Comment() *Comments { + return c +} + +// A FileSyntax represents an entire go.mod file. +type FileSyntax struct { + Name string // file path + Comments + Stmt []Expr +} + +func (x *FileSyntax) Span() (start, end Position) { + if len(x.Stmt) == 0 { + return + } + start, _ = x.Stmt[0].Span() + _, end = x.Stmt[len(x.Stmt)-1].Span() + return start, end +} + +// addLine adds a line containing the given tokens to the file. +// +// If the first token of the hint matches the first token of the +// line, the new line is added at the end of the block containing hint, +// extracting hint into a new block if it is not yet in one. +// +// If the hint is non-nil buts its first token does not match, +// the new line is added after the block containing hint +// (or hint itself, if not in a block). +// +// If no hint is provided, addLine appends the line to the end of +// the last block with a matching first token, +// or to the end of the file if no such block exists. +func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line { + if hint == nil { + // If no hint given, add to the last statement of the given type. + Loop: + for i := len(x.Stmt) - 1; i >= 0; i-- { + stmt := x.Stmt[i] + switch stmt := stmt.(type) { + case *Line: + if stmt.Token != nil && stmt.Token[0] == tokens[0] { + hint = stmt + break Loop + } + case *LineBlock: + if stmt.Token[0] == tokens[0] { + hint = stmt + break Loop + } + } + } + } + + newLineAfter := func(i int) *Line { + new := &Line{Token: tokens} + if i == len(x.Stmt) { + x.Stmt = append(x.Stmt, new) + } else { + x.Stmt = append(x.Stmt, nil) + copy(x.Stmt[i+2:], x.Stmt[i+1:]) + x.Stmt[i+1] = new + } + return new + } + + if hint != nil { + for i, stmt := range x.Stmt { + switch stmt := stmt.(type) { + case *Line: + if stmt == hint { + if stmt.Token == nil || stmt.Token[0] != tokens[0] { + return newLineAfter(i) + } + + // Convert line to line block. + stmt.InBlock = true + block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}} + stmt.Token = stmt.Token[1:] + x.Stmt[i] = block + new := &Line{Token: tokens[1:], InBlock: true} + block.Line = append(block.Line, new) + return new + } + + case *LineBlock: + if stmt == hint { + if stmt.Token[0] != tokens[0] { + return newLineAfter(i) + } + + new := &Line{Token: tokens[1:], InBlock: true} + stmt.Line = append(stmt.Line, new) + return new + } + + for j, line := range stmt.Line { + if line == hint { + if stmt.Token[0] != tokens[0] { + return newLineAfter(i) + } + + // Add new line after hint within the block. + stmt.Line = append(stmt.Line, nil) + copy(stmt.Line[j+2:], stmt.Line[j+1:]) + new := &Line{Token: tokens[1:], InBlock: true} + stmt.Line[j+1] = new + return new + } + } + } + } + } + + new := &Line{Token: tokens} + x.Stmt = append(x.Stmt, new) + return new +} + +func (x *FileSyntax) updateLine(line *Line, tokens ...string) { + if line.InBlock { + tokens = tokens[1:] + } + line.Token = tokens +} + +// markRemoved modifies line so that it (and its end-of-line comment, if any) +// will be dropped by (*FileSyntax).Cleanup. +func (line *Line) markRemoved() { + line.Token = nil + line.Comments.Suffix = nil +} + +// Cleanup cleans up the file syntax x after any edit operations. +// To avoid quadratic behavior, (*Line).markRemoved marks the line as dead +// by setting line.Token = nil but does not remove it from the slice +// in which it appears. After edits have all been indicated, +// calling Cleanup cleans out the dead lines. +func (x *FileSyntax) Cleanup() { + w := 0 + for _, stmt := range x.Stmt { + switch stmt := stmt.(type) { + case *Line: + if stmt.Token == nil { + continue + } + case *LineBlock: + ww := 0 + for _, line := range stmt.Line { + if line.Token != nil { + stmt.Line[ww] = line + ww++ + } + } + if ww == 0 { + continue + } + if ww == 1 && len(stmt.RParen.Comments.Before) == 0 { + // Collapse block into single line but keep the Line reference used by the + // parsed File structure. + *stmt.Line[0] = Line{ + Comments: Comments{ + Before: commentsAdd(stmt.Before, stmt.Line[0].Before), + Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix), + After: commentsAdd(stmt.Line[0].After, stmt.After), + }, + Token: stringsAdd(stmt.Token, stmt.Line[0].Token), + } + x.Stmt[w] = stmt.Line[0] + w++ + continue + } + stmt.Line = stmt.Line[:ww] + } + x.Stmt[w] = stmt + w++ + } + x.Stmt = x.Stmt[:w] +} + +func commentsAdd(x, y []Comment) []Comment { + return append(x[:len(x):len(x)], y...) +} + +func stringsAdd(x, y []string) []string { + return append(x[:len(x):len(x)], y...) +} + +// A CommentBlock represents a top-level block of comments separate +// from any rule. +type CommentBlock struct { + Comments + Start Position +} + +func (x *CommentBlock) Span() (start, end Position) { + return x.Start, x.Start +} + +// A Line is a single line of tokens. +type Line struct { + Comments + Start Position + Token []string + InBlock bool + End Position +} + +func (x *Line) Span() (start, end Position) { + return x.Start, x.End +} + +// A LineBlock is a factored block of lines, like +// +// require ( +// "x" +// "y" +// ) +type LineBlock struct { + Comments + Start Position + LParen LParen + Token []string + Line []*Line + RParen RParen +} + +func (x *LineBlock) Span() (start, end Position) { + return x.Start, x.RParen.Pos.add(")") +} + +// An LParen represents the beginning of a parenthesized line block. +// It is a place to store suffix comments. +type LParen struct { + Comments + Pos Position +} + +func (x *LParen) Span() (start, end Position) { + return x.Pos, x.Pos.add(")") +} + +// An RParen represents the end of a parenthesized line block. +// It is a place to store whole-line (before) comments. +type RParen struct { + Comments + Pos Position +} + +func (x *RParen) Span() (start, end Position) { + return x.Pos, x.Pos.add(")") +} + +// An input represents a single input file being parsed. +type input struct { + // Lexing state. + filename string // name of input file, for errors + complete []byte // entire input + remaining []byte // remaining input + tokenStart []byte // token being scanned to end of input + token token // next token to be returned by lex, peek + pos Position // current input position + comments []Comment // accumulated comments + + // Parser state. + file *FileSyntax // returned top-level syntax tree + parseErrors ErrorList // errors encountered during parsing + + // Comment assignment state. + pre []Expr // all expressions, in preorder traversal + post []Expr // all expressions, in postorder traversal +} + +func newInput(filename string, data []byte) *input { + return &input{ + filename: filename, + complete: data, + remaining: data, + pos: Position{Line: 1, LineRune: 1, Byte: 0}, + } +} + +// parse parses the input file. +func parse(file string, data []byte) (f *FileSyntax, err error) { + // The parser panics for both routine errors like syntax errors + // and for programmer bugs like array index errors. + // Turn both into error returns. Catching bug panics is + // especially important when processing many files. + in := newInput(file, data) + defer func() { + if e := recover(); e != nil && e != &in.parseErrors { + in.parseErrors = append(in.parseErrors, Error{ + Filename: in.filename, + Pos: in.pos, + Err: fmt.Errorf("internal error: %v", e), + }) + } + if err == nil && len(in.parseErrors) > 0 { + err = in.parseErrors + } + }() + + // Prime the lexer by reading in the first token. It will be available + // in the next peek() or lex() call. + in.readToken() + + // Invoke the parser. + in.parseFile() + if len(in.parseErrors) > 0 { + return nil, in.parseErrors + } + in.file.Name = in.filename + + // Assign comments to nearby syntax. + in.assignComments() + + return in.file, nil +} + +// Error is called to report an error. +// Error does not return: it panics. +func (in *input) Error(s string) { + in.parseErrors = append(in.parseErrors, Error{ + Filename: in.filename, + Pos: in.pos, + Err: errors.New(s), + }) + panic(&in.parseErrors) +} + +// eof reports whether the input has reached end of file. +func (in *input) eof() bool { + return len(in.remaining) == 0 +} + +// peekRune returns the next rune in the input without consuming it. +func (in *input) peekRune() int { + if len(in.remaining) == 0 { + return 0 + } + r, _ := utf8.DecodeRune(in.remaining) + return int(r) +} + +// peekPrefix reports whether the remaining input begins with the given prefix. +func (in *input) peekPrefix(prefix string) bool { + // This is like bytes.HasPrefix(in.remaining, []byte(prefix)) + // but without the allocation of the []byte copy of prefix. + for i := 0; i < len(prefix); i++ { + if i >= len(in.remaining) || in.remaining[i] != prefix[i] { + return false + } + } + return true +} + +// readRune consumes and returns the next rune in the input. +func (in *input) readRune() int { + if len(in.remaining) == 0 { + in.Error("internal lexer error: readRune at EOF") + } + r, size := utf8.DecodeRune(in.remaining) + in.remaining = in.remaining[size:] + if r == '\n' { + in.pos.Line++ + in.pos.LineRune = 1 + } else { + in.pos.LineRune++ + } + in.pos.Byte += size + return int(r) +} + +type token struct { + kind tokenKind + pos Position + endPos Position + text string +} + +type tokenKind int + +const ( + _EOF tokenKind = -(iota + 1) + _EOLCOMMENT + _IDENT + _STRING + _COMMENT + + // newlines and punctuation tokens are allowed as ASCII codes. +) + +func (k tokenKind) isComment() bool { + return k == _COMMENT || k == _EOLCOMMENT +} + +// isEOL returns whether a token terminates a line. +func (k tokenKind) isEOL() bool { + return k == _EOF || k == _EOLCOMMENT || k == '\n' +} + +// startToken marks the beginning of the next input token. +// It must be followed by a call to endToken, once the token's text has +// been consumed using readRune. +func (in *input) startToken() { + in.tokenStart = in.remaining + in.token.text = "" + in.token.pos = in.pos +} + +// endToken marks the end of an input token. +// It records the actual token string in tok.text. +// A single trailing newline (LF or CRLF) will be removed from comment tokens. +func (in *input) endToken(kind tokenKind) { + in.token.kind = kind + text := string(in.tokenStart[:len(in.tokenStart)-len(in.remaining)]) + if kind.isComment() { + if strings.HasSuffix(text, "\r\n") { + text = text[:len(text)-2] + } else { + text = strings.TrimSuffix(text, "\n") + } + } + in.token.text = text + in.token.endPos = in.pos +} + +// peek returns the kind of the next token returned by lex. +func (in *input) peek() tokenKind { + return in.token.kind +} + +// lex is called from the parser to obtain the next input token. +func (in *input) lex() token { + tok := in.token + in.readToken() + return tok +} + +// readToken lexes the next token from the text and stores it in in.token. +func (in *input) readToken() { + // Skip past spaces, stopping at non-space or EOF. + for !in.eof() { + c := in.peekRune() + if c == ' ' || c == '\t' || c == '\r' { + in.readRune() + continue + } + + // Comment runs to end of line. + if in.peekPrefix("//") { + in.startToken() + + // Is this comment the only thing on its line? + // Find the last \n before this // and see if it's all + // spaces from there to here. + i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n")) + suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0 + in.readRune() + in.readRune() + + // Consume comment. + for len(in.remaining) > 0 && in.readRune() != '\n' { + } + + // If we are at top level (not in a statement), hand the comment to + // the parser as a _COMMENT token. The grammar is written + // to handle top-level comments itself. + if !suffix { + in.endToken(_COMMENT) + return + } + + // Otherwise, save comment for later attachment to syntax tree. + in.endToken(_EOLCOMMENT) + in.comments = append(in.comments, Comment{in.token.pos, in.token.text, suffix}) + return + } + + if in.peekPrefix("/*") { + in.Error("mod files must use // comments (not /* */ comments)") + } + + // Found non-space non-comment. + break + } + + // Found the beginning of the next token. + in.startToken() + + // End of file. + if in.eof() { + in.endToken(_EOF) + return + } + + // Punctuation tokens. + switch c := in.peekRune(); c { + case '\n', '(', ')', '[', ']', '{', '}', ',': + in.readRune() + in.endToken(tokenKind(c)) + return + + case '"', '`': // quoted string + quote := c + in.readRune() + for { + if in.eof() { + in.pos = in.token.pos + in.Error("unexpected EOF in string") + } + if in.peekRune() == '\n' { + in.Error("unexpected newline in string") + } + c := in.readRune() + if c == quote { + break + } + if c == '\\' && quote != '`' { + if in.eof() { + in.pos = in.token.pos + in.Error("unexpected EOF in string") + } + in.readRune() + } + } + in.endToken(_STRING) + return + } + + // Checked all punctuation. Must be identifier token. + if c := in.peekRune(); !isIdent(c) { + in.Error(fmt.Sprintf("unexpected input character %#q", c)) + } + + // Scan over identifier. + for isIdent(in.peekRune()) { + if in.peekPrefix("//") { + break + } + if in.peekPrefix("/*") { + in.Error("mod files must use // comments (not /* */ comments)") + } + in.readRune() + } + in.endToken(_IDENT) +} + +// isIdent reports whether c is an identifier rune. +// We treat most printable runes as identifier runes, except for a handful of +// ASCII punctuation characters. +func isIdent(c int) bool { + switch r := rune(c); r { + case ' ', '(', ')', '[', ']', '{', '}', ',': + return false + default: + return !unicode.IsSpace(r) && unicode.IsPrint(r) + } +} + +// Comment assignment. +// We build two lists of all subexpressions, preorder and postorder. +// The preorder list is ordered by start location, with outer expressions first. +// The postorder list is ordered by end location, with outer expressions last. +// We use the preorder list to assign each whole-line comment to the syntax +// immediately following it, and we use the postorder list to assign each +// end-of-line comment to the syntax immediately preceding it. + +// order walks the expression adding it and its subexpressions to the +// preorder and postorder lists. +func (in *input) order(x Expr) { + if x != nil { + in.pre = append(in.pre, x) + } + switch x := x.(type) { + default: + panic(fmt.Errorf("order: unexpected type %T", x)) + case nil: + // nothing + case *LParen, *RParen: + // nothing + case *CommentBlock: + // nothing + case *Line: + // nothing + case *FileSyntax: + for _, stmt := range x.Stmt { + in.order(stmt) + } + case *LineBlock: + in.order(&x.LParen) + for _, l := range x.Line { + in.order(l) + } + in.order(&x.RParen) + } + if x != nil { + in.post = append(in.post, x) + } +} + +// assignComments attaches comments to nearby syntax. +func (in *input) assignComments() { + const debug = false + + // Generate preorder and postorder lists. + in.order(in.file) + + // Split into whole-line comments and suffix comments. + var line, suffix []Comment + for _, com := range in.comments { + if com.Suffix { + suffix = append(suffix, com) + } else { + line = append(line, com) + } + } + + if debug { + for _, c := range line { + fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte) + } + } + + // Assign line comments to syntax immediately following. + for _, x := range in.pre { + start, _ := x.Span() + if debug { + fmt.Fprintf(os.Stderr, "pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte) + } + xcom := x.Comment() + for len(line) > 0 && start.Byte >= line[0].Start.Byte { + if debug { + fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte) + } + xcom.Before = append(xcom.Before, line[0]) + line = line[1:] + } + } + + // Remaining line comments go at end of file. + in.file.After = append(in.file.After, line...) + + if debug { + for _, c := range suffix { + fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte) + } + } + + // Assign suffix comments to syntax immediately before. + for i := len(in.post) - 1; i >= 0; i-- { + x := in.post[i] + + start, end := x.Span() + if debug { + fmt.Fprintf(os.Stderr, "post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte) + } + + // Do not assign suffix comments to end of line block or whole file. + // Instead assign them to the last element inside. + switch x.(type) { + case *FileSyntax: + continue + } + + // Do not assign suffix comments to something that starts + // on an earlier line, so that in + // + // x ( y + // z ) // comment + // + // we assign the comment to z and not to x ( ... ). + if start.Line != end.Line { + continue + } + xcom := x.Comment() + for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte { + if debug { + fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte) + } + xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1]) + suffix = suffix[:len(suffix)-1] + } + } + + // We assigned suffix comments in reverse. + // If multiple suffix comments were appended to the same + // expression node, they are now in reverse. Fix that. + for _, x := range in.post { + reverseComments(x.Comment().Suffix) + } + + // Remaining suffix comments go at beginning of file. + in.file.Before = append(in.file.Before, suffix...) +} + +// reverseComments reverses the []Comment list. +func reverseComments(list []Comment) { + for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { + list[i], list[j] = list[j], list[i] + } +} + +func (in *input) parseFile() { + in.file = new(FileSyntax) + var cb *CommentBlock + for { + switch in.peek() { + case '\n': + in.lex() + if cb != nil { + in.file.Stmt = append(in.file.Stmt, cb) + cb = nil + } + case _COMMENT: + tok := in.lex() + if cb == nil { + cb = &CommentBlock{Start: tok.pos} + } + com := cb.Comment() + com.Before = append(com.Before, Comment{Start: tok.pos, Token: tok.text}) + case _EOF: + if cb != nil { + in.file.Stmt = append(in.file.Stmt, cb) + } + return + default: + in.parseStmt() + if cb != nil { + in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before + cb = nil + } + } + } +} + +func (in *input) parseStmt() { + tok := in.lex() + start := tok.pos + end := tok.endPos + tokens := []string{tok.text} + for { + tok := in.lex() + switch { + case tok.kind.isEOL(): + in.file.Stmt = append(in.file.Stmt, &Line{ + Start: start, + Token: tokens, + End: end, + }) + return + + case tok.kind == '(': + if next := in.peek(); next.isEOL() { + // Start of block: no more tokens on this line. + in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, tokens, tok)) + return + } else if next == ')' { + rparen := in.lex() + if in.peek().isEOL() { + // Empty block. + in.lex() + in.file.Stmt = append(in.file.Stmt, &LineBlock{ + Start: start, + Token: tokens, + LParen: LParen{Pos: tok.pos}, + RParen: RParen{Pos: rparen.pos}, + }) + return + } + // '( )' in the middle of the line, not a block. + tokens = append(tokens, tok.text, rparen.text) + } else { + // '(' in the middle of the line, not a block. + tokens = append(tokens, tok.text) + } + + default: + tokens = append(tokens, tok.text) + end = tok.endPos + } + } +} + +func (in *input) parseLineBlock(start Position, token []string, lparen token) *LineBlock { + x := &LineBlock{ + Start: start, + Token: token, + LParen: LParen{Pos: lparen.pos}, + } + var comments []Comment + for { + switch in.peek() { + case _EOLCOMMENT: + // Suffix comment, will be attached later by assignComments. + in.lex() + case '\n': + // Blank line. Add an empty comment to preserve it. + in.lex() + if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" { + comments = append(comments, Comment{}) + } + case _COMMENT: + tok := in.lex() + comments = append(comments, Comment{Start: tok.pos, Token: tok.text}) + case _EOF: + in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune)) + case ')': + rparen := in.lex() + // Don't preserve blank lines (denoted by a single empty comment, added above) + // at the end of the block. + if len(comments) == 1 && comments[0] == (Comment{}) { + comments = nil + } + x.RParen.Before = comments + x.RParen.Pos = rparen.pos + if !in.peek().isEOL() { + in.Error("syntax error (expected newline after closing paren)") + } + in.lex() + return x + default: + l := in.parseLine() + x.Line = append(x.Line, l) + l.Comment().Before = comments + comments = nil + } + } +} + +func (in *input) parseLine() *Line { + tok := in.lex() + if tok.kind.isEOL() { + in.Error("internal parse error: parseLine at end of line") + } + start := tok.pos + end := tok.endPos + tokens := []string{tok.text} + for { + tok := in.lex() + if tok.kind.isEOL() { + return &Line{ + Start: start, + Token: tokens, + End: end, + InBlock: true, + } + } + tokens = append(tokens, tok.text) + end = tok.endPos + } +} + +var ( + slashSlash = []byte("//") + moduleStr = []byte("module") +) + +// ModulePath returns the module path from the gomod file text. +// If it cannot find a module path, it returns an empty string. +// It is tolerant of unrelated problems in the go.mod file. +func ModulePath(mod []byte) string { + for len(mod) > 0 { + line := mod + mod = nil + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, mod = line[:i], line[i+1:] + } + if i := bytes.Index(line, slashSlash); i >= 0 { + line = line[:i] + } + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, moduleStr) { + continue + } + line = line[len(moduleStr):] + n := len(line) + line = bytes.TrimSpace(line) + if len(line) == n || len(line) == 0 { + continue + } + + if line[0] == '"' || line[0] == '`' { + p, err := strconv.Unquote(string(line)) + if err != nil { + return "" // malformed quoted string or multiline module path + } + return p + } + + return string(line) + } + return "" // missing module path +} diff --git a/vendor/golang.org/x/mod/modfile/rule.go b/vendor/golang.org/x/mod/modfile/rule.go new file mode 100644 index 0000000..3e4a1d0 --- /dev/null +++ b/vendor/golang.org/x/mod/modfile/rule.go @@ -0,0 +1,1836 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package modfile implements a parser and formatter for go.mod files. +// +// The go.mod syntax is described in +// https://pkg.go.dev/cmd/go/#hdr-The_go_mod_file. +// +// The [Parse] and [ParseLax] functions both parse a go.mod file and return an +// abstract syntax tree. ParseLax ignores unknown statements and may be used to +// parse go.mod files that may have been developed with newer versions of Go. +// +// The [File] struct returned by Parse and ParseLax represent an abstract +// go.mod file. File has several methods like [File.AddNewRequire] and +// [File.DropReplace] that can be used to programmatically edit a file. +// +// The [Format] function formats a File back to a byte slice which can be +// written to a file. +package modfile + +import ( + "errors" + "fmt" + "path/filepath" + "sort" + "strconv" + "strings" + "unicode" + + "golang.org/x/mod/internal/lazyregexp" + "golang.org/x/mod/module" + "golang.org/x/mod/semver" +) + +// A File is the parsed, interpreted form of a go.mod file. +type File struct { + Module *Module + Go *Go + Toolchain *Toolchain + Godebug []*Godebug + Require []*Require + Exclude []*Exclude + Replace []*Replace + Retract []*Retract + Tool []*Tool + + Syntax *FileSyntax +} + +// A Module is the module statement. +type Module struct { + Mod module.Version + Deprecated string + Syntax *Line +} + +// A Go is the go statement. +type Go struct { + Version string // "1.23" + Syntax *Line +} + +// A Toolchain is the toolchain statement. +type Toolchain struct { + Name string // "go1.21rc1" + Syntax *Line +} + +// A Godebug is a single godebug key=value statement. +type Godebug struct { + Key string + Value string + Syntax *Line +} + +// An Exclude is a single exclude statement. +type Exclude struct { + Mod module.Version + Syntax *Line +} + +// A Replace is a single replace statement. +type Replace struct { + Old module.Version + New module.Version + Syntax *Line +} + +// A Retract is a single retract statement. +type Retract struct { + VersionInterval + Rationale string + Syntax *Line +} + +// A Tool is a single tool statement. +type Tool struct { + Path string + Syntax *Line +} + +// A VersionInterval represents a range of versions with upper and lower bounds. +// Intervals are closed: both bounds are included. When Low is equal to High, +// the interval may refer to a single version ('v1.2.3') or an interval +// ('[v1.2.3, v1.2.3]'); both have the same representation. +type VersionInterval struct { + Low, High string +} + +// A Require is a single require statement. +type Require struct { + Mod module.Version + Indirect bool // has "// indirect" comment + Syntax *Line +} + +func (r *Require) markRemoved() { + r.Syntax.markRemoved() + *r = Require{} +} + +func (r *Require) setVersion(v string) { + r.Mod.Version = v + + if line := r.Syntax; len(line.Token) > 0 { + if line.InBlock { + // If the line is preceded by an empty line, remove it; see + // https://golang.org/issue/33779. + if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 { + line.Comments.Before = line.Comments.Before[:0] + } + if len(line.Token) >= 2 { // example.com v1.2.3 + line.Token[1] = v + } + } else { + if len(line.Token) >= 3 { // require example.com v1.2.3 + line.Token[2] = v + } + } + } +} + +// setIndirect sets line to have (or not have) a "// indirect" comment. +func (r *Require) setIndirect(indirect bool) { + r.Indirect = indirect + line := r.Syntax + if isIndirect(line) == indirect { + return + } + if indirect { + // Adding comment. + if len(line.Suffix) == 0 { + // New comment. + line.Suffix = []Comment{{Token: "// indirect", Suffix: true}} + return + } + + com := &line.Suffix[0] + text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash))) + if text == "" { + // Empty comment. + com.Token = "// indirect" + return + } + + // Insert at beginning of existing comment. + com.Token = "// indirect; " + text + return + } + + // Removing comment. + f := strings.TrimSpace(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash))) + if f == "indirect" { + // Remove whole comment. + line.Suffix = nil + return + } + + // Remove comment prefix. + com := &line.Suffix[0] + i := strings.Index(com.Token, "indirect;") + com.Token = "//" + com.Token[i+len("indirect;"):] +} + +// isIndirect reports whether line has a "// indirect" comment, +// meaning it is in go.mod only for its effect on indirect dependencies, +// so that it can be dropped entirely once the effective version of the +// indirect dependency reaches the given minimum version. +func isIndirect(line *Line) bool { + if len(line.Suffix) == 0 { + return false + } + f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash))) + return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;") +} + +func (f *File) AddModuleStmt(path string) error { + if f.Syntax == nil { + f.Syntax = new(FileSyntax) + } + if f.Module == nil { + f.Module = &Module{ + Mod: module.Version{Path: path}, + Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)), + } + } else { + f.Module.Mod.Path = path + f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path)) + } + return nil +} + +func (f *File) AddComment(text string) { + if f.Syntax == nil { + f.Syntax = new(FileSyntax) + } + f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{ + Comments: Comments{ + Before: []Comment{ + { + Token: text, + }, + }, + }, + }) +} + +type VersionFixer func(path, version string) (string, error) + +// errDontFix is returned by a VersionFixer to indicate the version should be +// left alone, even if it's not canonical. +var dontFixRetract VersionFixer = func(_, vers string) (string, error) { + return vers, nil +} + +// Parse parses and returns a go.mod file. +// +// file is the name of the file, used in positions and errors. +// +// data is the content of the file. +// +// fix is an optional function that canonicalizes module versions. +// If fix is nil, all module versions must be canonical ([module.CanonicalVersion] +// must return the same string). +func Parse(file string, data []byte, fix VersionFixer) (*File, error) { + return parseToFile(file, data, fix, true) +} + +// ParseLax is like Parse but ignores unknown statements. +// It is used when parsing go.mod files other than the main module, +// under the theory that most statement types we add in the future will +// only apply in the main module, like exclude and replace, +// and so we get better gradual deployments if old go commands +// simply ignore those statements when found in go.mod files +// in dependencies. +func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) { + return parseToFile(file, data, fix, false) +} + +func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) { + fs, err := parse(file, data) + if err != nil { + return nil, err + } + f := &File{ + Syntax: fs, + } + var errs ErrorList + + // fix versions in retract directives after the file is parsed. + // We need the module path to fix versions, and it might be at the end. + defer func() { + oldLen := len(errs) + f.fixRetract(fix, &errs) + if len(errs) > oldLen { + parsed, err = nil, errs + } + }() + + for _, x := range fs.Stmt { + switch x := x.(type) { + case *Line: + f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict) + + case *LineBlock: + if len(x.Token) > 1 { + if strict { + errs = append(errs, Error{ + Filename: file, + Pos: x.Start, + Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), + }) + } + continue + } + switch x.Token[0] { + default: + if strict { + errs = append(errs, Error{ + Filename: file, + Pos: x.Start, + Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), + }) + } + continue + case "module", "godebug", "require", "exclude", "replace", "retract", "tool": + for _, l := range x.Line { + f.add(&errs, x, l, x.Token[0], l.Token, fix, strict) + } + } + } + } + + if len(errs) > 0 { + return nil, errs + } + return f, nil +} + +var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`) +var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`) + +// Toolchains must be named beginning with `go1`, +// like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted. +// Note that this regexp is a much looser condition than go/version.IsValid, +// for forward compatibility. +// (This code has to be work to identify new toolchains even if we tweak the syntax in the future.) +var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`) + +func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) { + // If strict is false, this module is a dependency. + // We ignore all unknown directives as well as main-module-only + // directives like replace and exclude. It will work better for + // forward compatibility if we can depend on modules that have unknown + // statements (presumed relevant only when acting as the main module) + // and simply ignore those statements. + if !strict { + switch verb { + case "go", "module", "retract", "require": + // want these even for dependency go.mods + default: + return + } + } + + wrapModPathError := func(modPath string, err error) { + *errs = append(*errs, Error{ + Filename: f.Syntax.Name, + Pos: line.Start, + ModPath: modPath, + Verb: verb, + Err: err, + }) + } + wrapError := func(err error) { + *errs = append(*errs, Error{ + Filename: f.Syntax.Name, + Pos: line.Start, + Err: err, + }) + } + errorf := func(format string, args ...interface{}) { + wrapError(fmt.Errorf(format, args...)) + } + + switch verb { + default: + errorf("unknown directive: %s", verb) + + case "go": + if f.Go != nil { + errorf("repeated go statement") + return + } + if len(args) != 1 { + errorf("go directive expects exactly one argument") + return + } else if !GoVersionRE.MatchString(args[0]) { + fixed := false + if !strict { + if m := laxGoVersionRE.FindStringSubmatch(args[0]); m != nil { + args[0] = m[1] + fixed = true + } + } + if !fixed { + errorf("invalid go version '%s': must match format 1.23.0", args[0]) + return + } + } + + f.Go = &Go{Syntax: line} + f.Go.Version = args[0] + + case "toolchain": + if f.Toolchain != nil { + errorf("repeated toolchain statement") + return + } + if len(args) != 1 { + errorf("toolchain directive expects exactly one argument") + return + } else if !ToolchainRE.MatchString(args[0]) { + errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0]) + return + } + f.Toolchain = &Toolchain{Syntax: line} + f.Toolchain.Name = args[0] + + case "module": + if f.Module != nil { + errorf("repeated module statement") + return + } + deprecated := parseDeprecation(block, line) + f.Module = &Module{ + Syntax: line, + Deprecated: deprecated, + } + if len(args) != 1 { + errorf("usage: module module/path") + return + } + s, err := parseString(&args[0]) + if err != nil { + errorf("invalid quoted string: %v", err) + return + } + f.Module.Mod = module.Version{Path: s} + + case "godebug": + if len(args) != 1 || strings.ContainsAny(args[0], "\"`',") { + errorf("usage: godebug key=value") + return + } + key, value, ok := strings.Cut(args[0], "=") + if !ok { + errorf("usage: godebug key=value") + return + } + f.Godebug = append(f.Godebug, &Godebug{ + Key: key, + Value: value, + Syntax: line, + }) + + case "require", "exclude": + if len(args) != 2 { + errorf("usage: %s module/path v1.2.3", verb) + return + } + s, err := parseString(&args[0]) + if err != nil { + errorf("invalid quoted string: %v", err) + return + } + v, err := parseVersion(verb, s, &args[1], fix) + if err != nil { + wrapError(err) + return + } + pathMajor, err := modulePathMajor(s) + if err != nil { + wrapError(err) + return + } + if err := module.CheckPathMajor(v, pathMajor); err != nil { + wrapModPathError(s, err) + return + } + if verb == "require" { + f.Require = append(f.Require, &Require{ + Mod: module.Version{Path: s, Version: v}, + Syntax: line, + Indirect: isIndirect(line), + }) + } else { + f.Exclude = append(f.Exclude, &Exclude{ + Mod: module.Version{Path: s, Version: v}, + Syntax: line, + }) + } + + case "replace": + replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix) + if wrappederr != nil { + *errs = append(*errs, *wrappederr) + return + } + f.Replace = append(f.Replace, replace) + + case "retract": + rationale := parseDirectiveComment(block, line) + vi, err := parseVersionInterval(verb, "", &args, dontFixRetract) + if err != nil { + if strict { + wrapError(err) + return + } else { + // Only report errors parsing intervals in the main module. We may + // support additional syntax in the future, such as open and half-open + // intervals. Those can't be supported now, because they break the + // go.mod parser, even in lax mode. + return + } + } + if len(args) > 0 && strict { + // In the future, there may be additional information after the version. + errorf("unexpected token after version: %q", args[0]) + return + } + retract := &Retract{ + VersionInterval: vi, + Rationale: rationale, + Syntax: line, + } + f.Retract = append(f.Retract, retract) + + case "tool": + if len(args) != 1 { + errorf("tool directive expects exactly one argument") + return + } + s, err := parseString(&args[0]) + if err != nil { + errorf("invalid quoted string: %v", err) + return + } + f.Tool = append(f.Tool, &Tool{ + Path: s, + Syntax: line, + }) + } +} + +func parseReplace(filename string, line *Line, verb string, args []string, fix VersionFixer) (*Replace, *Error) { + wrapModPathError := func(modPath string, err error) *Error { + return &Error{ + Filename: filename, + Pos: line.Start, + ModPath: modPath, + Verb: verb, + Err: err, + } + } + wrapError := func(err error) *Error { + return &Error{ + Filename: filename, + Pos: line.Start, + Err: err, + } + } + errorf := func(format string, args ...interface{}) *Error { + return wrapError(fmt.Errorf(format, args...)) + } + + arrow := 2 + if len(args) >= 2 && args[1] == "=>" { + arrow = 1 + } + if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" { + return nil, errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb) + } + s, err := parseString(&args[0]) + if err != nil { + return nil, errorf("invalid quoted string: %v", err) + } + pathMajor, err := modulePathMajor(s) + if err != nil { + return nil, wrapModPathError(s, err) + + } + var v string + if arrow == 2 { + v, err = parseVersion(verb, s, &args[1], fix) + if err != nil { + return nil, wrapError(err) + } + if err := module.CheckPathMajor(v, pathMajor); err != nil { + return nil, wrapModPathError(s, err) + } + } + ns, err := parseString(&args[arrow+1]) + if err != nil { + return nil, errorf("invalid quoted string: %v", err) + } + nv := "" + if len(args) == arrow+2 { + if !IsDirectoryPath(ns) { + if strings.Contains(ns, "@") { + return nil, errorf("replacement module must match format 'path version', not 'path@version'") + } + return nil, errorf("replacement module without version must be directory path (rooted or starting with . or ..)") + } + if filepath.Separator == '/' && strings.Contains(ns, `\`) { + return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)") + } + } + if len(args) == arrow+3 { + nv, err = parseVersion(verb, ns, &args[arrow+2], fix) + if err != nil { + return nil, wrapError(err) + } + if IsDirectoryPath(ns) { + return nil, errorf("replacement module directory path %q cannot have version", ns) + } + } + return &Replace{ + Old: module.Version{Path: s, Version: v}, + New: module.Version{Path: ns, Version: nv}, + Syntax: line, + }, nil +} + +// fixRetract applies fix to each retract directive in f, appending any errors +// to errs. +// +// Most versions are fixed as we parse the file, but for retract directives, +// the relevant module path is the one specified with the module directive, +// and that might appear at the end of the file (or not at all). +func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) { + if fix == nil { + return + } + path := "" + if f.Module != nil { + path = f.Module.Mod.Path + } + var r *Retract + wrapError := func(err error) { + *errs = append(*errs, Error{ + Filename: f.Syntax.Name, + Pos: r.Syntax.Start, + Err: err, + }) + } + + for _, r = range f.Retract { + if path == "" { + wrapError(errors.New("no module directive found, so retract cannot be used")) + return // only print the first one of these + } + + args := r.Syntax.Token + if args[0] == "retract" { + args = args[1:] + } + vi, err := parseVersionInterval("retract", path, &args, fix) + if err != nil { + wrapError(err) + } + r.VersionInterval = vi + } +} + +func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) { + wrapError := func(err error) { + *errs = append(*errs, Error{ + Filename: f.Syntax.Name, + Pos: line.Start, + Err: err, + }) + } + errorf := func(format string, args ...interface{}) { + wrapError(fmt.Errorf(format, args...)) + } + + switch verb { + default: + errorf("unknown directive: %s", verb) + + case "go": + if f.Go != nil { + errorf("repeated go statement") + return + } + if len(args) != 1 { + errorf("go directive expects exactly one argument") + return + } else if !GoVersionRE.MatchString(args[0]) { + errorf("invalid go version '%s': must match format 1.23.0", args[0]) + return + } + + f.Go = &Go{Syntax: line} + f.Go.Version = args[0] + + case "toolchain": + if f.Toolchain != nil { + errorf("repeated toolchain statement") + return + } + if len(args) != 1 { + errorf("toolchain directive expects exactly one argument") + return + } else if !ToolchainRE.MatchString(args[0]) { + errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0]) + return + } + + f.Toolchain = &Toolchain{Syntax: line} + f.Toolchain.Name = args[0] + + case "godebug": + if len(args) != 1 || strings.ContainsAny(args[0], "\"`',") { + errorf("usage: godebug key=value") + return + } + key, value, ok := strings.Cut(args[0], "=") + if !ok { + errorf("usage: godebug key=value") + return + } + f.Godebug = append(f.Godebug, &Godebug{ + Key: key, + Value: value, + Syntax: line, + }) + + case "use": + if len(args) != 1 { + errorf("usage: %s local/dir", verb) + return + } + s, err := parseString(&args[0]) + if err != nil { + errorf("invalid quoted string: %v", err) + return + } + f.Use = append(f.Use, &Use{ + Path: s, + Syntax: line, + }) + + case "replace": + replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix) + if wrappederr != nil { + *errs = append(*errs, *wrappederr) + return + } + f.Replace = append(f.Replace, replace) + } +} + +// IsDirectoryPath reports whether the given path should be interpreted as a directory path. +// Just like on the go command line, relative paths starting with a '.' or '..' path component +// and rooted paths are directory paths; the rest are module paths. +func IsDirectoryPath(ns string) bool { + // Because go.mod files can move from one system to another, + // we check all known path syntaxes, both Unix and Windows. + return ns == "." || strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, `.\`) || + ns == ".." || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, `..\`) || + strings.HasPrefix(ns, "/") || strings.HasPrefix(ns, `\`) || + len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':' +} + +// MustQuote reports whether s must be quoted in order to appear as +// a single token in a go.mod line. +func MustQuote(s string) bool { + for _, r := range s { + switch r { + case ' ', '"', '\'', '`': + return true + + case '(', ')', '[', ']', '{', '}', ',': + if len(s) > 1 { + return true + } + + default: + if !unicode.IsPrint(r) { + return true + } + } + } + return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*") +} + +// AutoQuote returns s or, if quoting is required for s to appear in a go.mod, +// the quotation of s. +func AutoQuote(s string) string { + if MustQuote(s) { + return strconv.Quote(s) + } + return s +} + +func parseVersionInterval(verb string, path string, args *[]string, fix VersionFixer) (VersionInterval, error) { + toks := *args + if len(toks) == 0 || toks[0] == "(" { + return VersionInterval{}, fmt.Errorf("expected '[' or version") + } + if toks[0] != "[" { + v, err := parseVersion(verb, path, &toks[0], fix) + if err != nil { + return VersionInterval{}, err + } + *args = toks[1:] + return VersionInterval{Low: v, High: v}, nil + } + toks = toks[1:] + + if len(toks) == 0 { + return VersionInterval{}, fmt.Errorf("expected version after '['") + } + low, err := parseVersion(verb, path, &toks[0], fix) + if err != nil { + return VersionInterval{}, err + } + toks = toks[1:] + + if len(toks) == 0 || toks[0] != "," { + return VersionInterval{}, fmt.Errorf("expected ',' after version") + } + toks = toks[1:] + + if len(toks) == 0 { + return VersionInterval{}, fmt.Errorf("expected version after ','") + } + high, err := parseVersion(verb, path, &toks[0], fix) + if err != nil { + return VersionInterval{}, err + } + toks = toks[1:] + + if len(toks) == 0 || toks[0] != "]" { + return VersionInterval{}, fmt.Errorf("expected ']' after version") + } + toks = toks[1:] + + *args = toks + return VersionInterval{Low: low, High: high}, nil +} + +func parseString(s *string) (string, error) { + t := *s + if strings.HasPrefix(t, `"`) { + var err error + if t, err = strconv.Unquote(t); err != nil { + return "", err + } + } else if strings.ContainsAny(t, "\"'`") { + // Other quotes are reserved both for possible future expansion + // and to avoid confusion. For example if someone types 'x' + // we want that to be a syntax error and not a literal x in literal quotation marks. + return "", fmt.Errorf("unquoted string cannot contain quote") + } + *s = AutoQuote(t) + return t, nil +} + +var deprecatedRE = lazyregexp.New(`(?s)(?:^|\n\n)Deprecated: *(.*?)(?:$|\n\n)`) + +// parseDeprecation extracts the text of comments on a "module" directive and +// extracts a deprecation message from that. +// +// A deprecation message is contained in a paragraph within a block of comments +// that starts with "Deprecated:" (case sensitive). The message runs until the +// end of the paragraph and does not include the "Deprecated:" prefix. If the +// comment block has multiple paragraphs that start with "Deprecated:", +// parseDeprecation returns the message from the first. +func parseDeprecation(block *LineBlock, line *Line) string { + text := parseDirectiveComment(block, line) + m := deprecatedRE.FindStringSubmatch(text) + if m == nil { + return "" + } + return m[1] +} + +// parseDirectiveComment extracts the text of comments on a directive. +// If the directive's line does not have comments and is part of a block that +// does have comments, the block's comments are used. +func parseDirectiveComment(block *LineBlock, line *Line) string { + comments := line.Comment() + if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 { + comments = block.Comment() + } + groups := [][]Comment{comments.Before, comments.Suffix} + var lines []string + for _, g := range groups { + for _, c := range g { + if !strings.HasPrefix(c.Token, "//") { + continue // blank line + } + lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//"))) + } + } + return strings.Join(lines, "\n") +} + +type ErrorList []Error + +func (e ErrorList) Error() string { + errStrs := make([]string, len(e)) + for i, err := range e { + errStrs[i] = err.Error() + } + return strings.Join(errStrs, "\n") +} + +type Error struct { + Filename string + Pos Position + Verb string + ModPath string + Err error +} + +func (e *Error) Error() string { + var pos string + if e.Pos.LineRune > 1 { + // Don't print LineRune if it's 1 (beginning of line). + // It's always 1 except in scanner errors, which are rare. + pos = fmt.Sprintf("%s:%d:%d: ", e.Filename, e.Pos.Line, e.Pos.LineRune) + } else if e.Pos.Line > 0 { + pos = fmt.Sprintf("%s:%d: ", e.Filename, e.Pos.Line) + } else if e.Filename != "" { + pos = fmt.Sprintf("%s: ", e.Filename) + } + + var directive string + if e.ModPath != "" { + directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath) + } else if e.Verb != "" { + directive = fmt.Sprintf("%s: ", e.Verb) + } + + return pos + directive + e.Err.Error() +} + +func (e *Error) Unwrap() error { return e.Err } + +func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) { + t, err := parseString(s) + if err != nil { + return "", &Error{ + Verb: verb, + ModPath: path, + Err: &module.InvalidVersionError{ + Version: *s, + Err: err, + }, + } + } + if fix != nil { + fixed, err := fix(path, t) + if err != nil { + if err, ok := err.(*module.ModuleError); ok { + return "", &Error{ + Verb: verb, + ModPath: path, + Err: err.Err, + } + } + return "", err + } + t = fixed + } else { + cv := module.CanonicalVersion(t) + if cv == "" { + return "", &Error{ + Verb: verb, + ModPath: path, + Err: &module.InvalidVersionError{ + Version: t, + Err: errors.New("must be of the form v1.2.3"), + }, + } + } + t = cv + } + *s = t + return *s, nil +} + +func modulePathMajor(path string) (string, error) { + _, major, ok := module.SplitPathVersion(path) + if !ok { + return "", fmt.Errorf("invalid module path") + } + return major, nil +} + +func (f *File) Format() ([]byte, error) { + return Format(f.Syntax), nil +} + +// Cleanup cleans up the file f after any edit operations. +// To avoid quadratic behavior, modifications like [File.DropRequire] +// clear the entry but do not remove it from the slice. +// Cleanup cleans out all the cleared entries. +func (f *File) Cleanup() { + w := 0 + for _, g := range f.Godebug { + if g.Key != "" { + f.Godebug[w] = g + w++ + } + } + f.Godebug = f.Godebug[:w] + + w = 0 + for _, r := range f.Require { + if r.Mod.Path != "" { + f.Require[w] = r + w++ + } + } + f.Require = f.Require[:w] + + w = 0 + for _, x := range f.Exclude { + if x.Mod.Path != "" { + f.Exclude[w] = x + w++ + } + } + f.Exclude = f.Exclude[:w] + + w = 0 + for _, r := range f.Replace { + if r.Old.Path != "" { + f.Replace[w] = r + w++ + } + } + f.Replace = f.Replace[:w] + + w = 0 + for _, r := range f.Retract { + if r.Low != "" || r.High != "" { + f.Retract[w] = r + w++ + } + } + f.Retract = f.Retract[:w] + + f.Syntax.Cleanup() +} + +func (f *File) AddGoStmt(version string) error { + if !GoVersionRE.MatchString(version) { + return fmt.Errorf("invalid language version %q", version) + } + if f.Go == nil { + var hint Expr + if f.Module != nil && f.Module.Syntax != nil { + hint = f.Module.Syntax + } else if f.Syntax == nil { + f.Syntax = new(FileSyntax) + } + f.Go = &Go{ + Version: version, + Syntax: f.Syntax.addLine(hint, "go", version), + } + } else { + f.Go.Version = version + f.Syntax.updateLine(f.Go.Syntax, "go", version) + } + return nil +} + +// DropGoStmt deletes the go statement from the file. +func (f *File) DropGoStmt() { + if f.Go != nil { + f.Go.Syntax.markRemoved() + f.Go = nil + } +} + +// DropToolchainStmt deletes the toolchain statement from the file. +func (f *File) DropToolchainStmt() { + if f.Toolchain != nil { + f.Toolchain.Syntax.markRemoved() + f.Toolchain = nil + } +} + +func (f *File) AddToolchainStmt(name string) error { + if !ToolchainRE.MatchString(name) { + return fmt.Errorf("invalid toolchain name %q", name) + } + if f.Toolchain == nil { + var hint Expr + if f.Go != nil && f.Go.Syntax != nil { + hint = f.Go.Syntax + } else if f.Module != nil && f.Module.Syntax != nil { + hint = f.Module.Syntax + } + f.Toolchain = &Toolchain{ + Name: name, + Syntax: f.Syntax.addLine(hint, "toolchain", name), + } + } else { + f.Toolchain.Name = name + f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name) + } + return nil +} + +// AddGodebug sets the first godebug line for key to value, +// preserving any existing comments for that line and removing all +// other godebug lines for key. +// +// If no line currently exists for key, AddGodebug adds a new line +// at the end of the last godebug block. +func (f *File) AddGodebug(key, value string) error { + need := true + for _, g := range f.Godebug { + if g.Key == key { + if need { + g.Value = value + f.Syntax.updateLine(g.Syntax, "godebug", key+"="+value) + need = false + } else { + g.Syntax.markRemoved() + *g = Godebug{} + } + } + } + + if need { + f.addNewGodebug(key, value) + } + return nil +} + +// addNewGodebug adds a new godebug key=value line at the end +// of the last godebug block, regardless of any existing godebug lines for key. +func (f *File) addNewGodebug(key, value string) { + line := f.Syntax.addLine(nil, "godebug", key+"="+value) + g := &Godebug{ + Key: key, + Value: value, + Syntax: line, + } + f.Godebug = append(f.Godebug, g) +} + +// AddRequire sets the first require line for path to version vers, +// preserving any existing comments for that line and removing all +// other lines for path. +// +// If no line currently exists for path, AddRequire adds a new line +// at the end of the last require block. +func (f *File) AddRequire(path, vers string) error { + need := true + for _, r := range f.Require { + if r.Mod.Path == path { + if need { + r.Mod.Version = vers + f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers) + need = false + } else { + r.Syntax.markRemoved() + *r = Require{} + } + } + } + + if need { + f.AddNewRequire(path, vers, false) + } + return nil +} + +// AddNewRequire adds a new require line for path at version vers at the end of +// the last require block, regardless of any existing require lines for path. +func (f *File) AddNewRequire(path, vers string, indirect bool) { + line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers) + r := &Require{ + Mod: module.Version{Path: path, Version: vers}, + Syntax: line, + } + r.setIndirect(indirect) + f.Require = append(f.Require, r) +} + +// SetRequire updates the requirements of f to contain exactly req, preserving +// the existing block structure and line comment contents (except for 'indirect' +// markings) for the first requirement on each named module path. +// +// The Syntax field is ignored for the requirements in req. +// +// Any requirements not already present in the file are added to the block +// containing the last require line. +// +// The requirements in req must specify at most one distinct version for each +// module path. +// +// If any existing requirements may be removed, the caller should call +// [File.Cleanup] after all edits are complete. +func (f *File) SetRequire(req []*Require) { + type elem struct { + version string + indirect bool + } + need := make(map[string]elem) + for _, r := range req { + if prev, dup := need[r.Mod.Path]; dup && prev.version != r.Mod.Version { + panic(fmt.Errorf("SetRequire called with conflicting versions for path %s (%s and %s)", r.Mod.Path, prev.version, r.Mod.Version)) + } + need[r.Mod.Path] = elem{r.Mod.Version, r.Indirect} + } + + // Update or delete the existing Require entries to preserve + // only the first for each module path in req. + for _, r := range f.Require { + e, ok := need[r.Mod.Path] + if ok { + r.setVersion(e.version) + r.setIndirect(e.indirect) + } else { + r.markRemoved() + } + delete(need, r.Mod.Path) + } + + // Add new entries in the last block of the file for any paths that weren't + // already present. + // + // This step is nondeterministic, but the final result will be deterministic + // because we will sort the block. + for path, e := range need { + f.AddNewRequire(path, e.version, e.indirect) + } + + f.SortBlocks() +} + +// SetRequireSeparateIndirect updates the requirements of f to contain the given +// requirements. Comment contents (except for 'indirect' markings) are retained +// from the first existing requirement for each module path. Like SetRequire, +// SetRequireSeparateIndirect adds requirements for new paths in req, +// updates the version and "// indirect" comment on existing requirements, +// and deletes requirements on paths not in req. Existing duplicate requirements +// are deleted. +// +// As its name suggests, SetRequireSeparateIndirect puts direct and indirect +// requirements into two separate blocks, one containing only direct +// requirements, and the other containing only indirect requirements. +// SetRequireSeparateIndirect may move requirements between these two blocks +// when their indirect markings change. However, SetRequireSeparateIndirect +// won't move requirements from other blocks, especially blocks with comments. +// +// If the file initially has one uncommented block of requirements, +// SetRequireSeparateIndirect will split it into a direct-only and indirect-only +// block. This aids in the transition to separate blocks. +func (f *File) SetRequireSeparateIndirect(req []*Require) { + // hasComments returns whether a line or block has comments + // other than "indirect". + hasComments := func(c Comments) bool { + return len(c.Before) > 0 || len(c.After) > 0 || len(c.Suffix) > 1 || + (len(c.Suffix) == 1 && + strings.TrimSpace(strings.TrimPrefix(c.Suffix[0].Token, string(slashSlash))) != "indirect") + } + + // moveReq adds r to block. If r was in another block, moveReq deletes + // it from that block and transfers its comments. + moveReq := func(r *Require, block *LineBlock) { + var line *Line + if r.Syntax == nil { + line = &Line{Token: []string{AutoQuote(r.Mod.Path), r.Mod.Version}} + r.Syntax = line + if r.Indirect { + r.setIndirect(true) + } + } else { + line = new(Line) + *line = *r.Syntax + if !line.InBlock && len(line.Token) > 0 && line.Token[0] == "require" { + line.Token = line.Token[1:] + } + r.Syntax.Token = nil // Cleanup will delete the old line. + r.Syntax = line + } + line.InBlock = true + block.Line = append(block.Line, line) + } + + // Examine existing require lines and blocks. + var ( + // We may insert new requirements into the last uncommented + // direct-only and indirect-only blocks. We may also move requirements + // to the opposite block if their indirect markings change. + lastDirectIndex = -1 + lastIndirectIndex = -1 + + // If there are no direct-only or indirect-only blocks, a new block may + // be inserted after the last require line or block. + lastRequireIndex = -1 + + // If there's only one require line or block, and it's uncommented, + // we'll move its requirements to the direct-only or indirect-only blocks. + requireLineOrBlockCount = 0 + + // Track the block each requirement belongs to (if any) so we can + // move them later. + lineToBlock = make(map[*Line]*LineBlock) + ) + for i, stmt := range f.Syntax.Stmt { + switch stmt := stmt.(type) { + case *Line: + if len(stmt.Token) == 0 || stmt.Token[0] != "require" { + continue + } + lastRequireIndex = i + requireLineOrBlockCount++ + if !hasComments(stmt.Comments) { + if isIndirect(stmt) { + lastIndirectIndex = i + } else { + lastDirectIndex = i + } + } + + case *LineBlock: + if len(stmt.Token) == 0 || stmt.Token[0] != "require" { + continue + } + lastRequireIndex = i + requireLineOrBlockCount++ + allDirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments) + allIndirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments) + for _, line := range stmt.Line { + lineToBlock[line] = stmt + if hasComments(line.Comments) { + allDirect = false + allIndirect = false + } else if isIndirect(line) { + allDirect = false + } else { + allIndirect = false + } + } + if allDirect { + lastDirectIndex = i + } + if allIndirect { + lastIndirectIndex = i + } + } + } + + oneFlatUncommentedBlock := requireLineOrBlockCount == 1 && + !hasComments(*f.Syntax.Stmt[lastRequireIndex].Comment()) + + // Create direct and indirect blocks if needed. Convert lines into blocks + // if needed. If we end up with an empty block or a one-line block, + // Cleanup will delete it or convert it to a line later. + insertBlock := func(i int) *LineBlock { + block := &LineBlock{Token: []string{"require"}} + f.Syntax.Stmt = append(f.Syntax.Stmt, nil) + copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:]) + f.Syntax.Stmt[i] = block + return block + } + + ensureBlock := func(i int) *LineBlock { + switch stmt := f.Syntax.Stmt[i].(type) { + case *LineBlock: + return stmt + case *Line: + block := &LineBlock{ + Token: []string{"require"}, + Line: []*Line{stmt}, + } + stmt.Token = stmt.Token[1:] // remove "require" + stmt.InBlock = true + f.Syntax.Stmt[i] = block + return block + default: + panic(fmt.Sprintf("unexpected statement: %v", stmt)) + } + } + + var lastDirectBlock *LineBlock + if lastDirectIndex < 0 { + if lastIndirectIndex >= 0 { + lastDirectIndex = lastIndirectIndex + lastIndirectIndex++ + } else if lastRequireIndex >= 0 { + lastDirectIndex = lastRequireIndex + 1 + } else { + lastDirectIndex = len(f.Syntax.Stmt) + } + lastDirectBlock = insertBlock(lastDirectIndex) + } else { + lastDirectBlock = ensureBlock(lastDirectIndex) + } + + var lastIndirectBlock *LineBlock + if lastIndirectIndex < 0 { + lastIndirectIndex = lastDirectIndex + 1 + lastIndirectBlock = insertBlock(lastIndirectIndex) + } else { + lastIndirectBlock = ensureBlock(lastIndirectIndex) + } + + // Delete requirements we don't want anymore. + // Update versions and indirect comments on requirements we want to keep. + // If a requirement is in last{Direct,Indirect}Block with the wrong + // indirect marking after this, or if the requirement is in an single + // uncommented mixed block (oneFlatUncommentedBlock), move it to the + // correct block. + // + // Some blocks may be empty after this. Cleanup will remove them. + need := make(map[string]*Require) + for _, r := range req { + need[r.Mod.Path] = r + } + have := make(map[string]*Require) + for _, r := range f.Require { + path := r.Mod.Path + if need[path] == nil || have[path] != nil { + // Requirement not needed, or duplicate requirement. Delete. + r.markRemoved() + continue + } + have[r.Mod.Path] = r + r.setVersion(need[path].Mod.Version) + r.setIndirect(need[path].Indirect) + if need[path].Indirect && + (oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) { + moveReq(r, lastIndirectBlock) + } else if !need[path].Indirect && + (oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) { + moveReq(r, lastDirectBlock) + } + } + + // Add new requirements. + for path, r := range need { + if have[path] == nil { + if r.Indirect { + moveReq(r, lastIndirectBlock) + } else { + moveReq(r, lastDirectBlock) + } + f.Require = append(f.Require, r) + } + } + + f.SortBlocks() +} + +func (f *File) DropGodebug(key string) error { + for _, g := range f.Godebug { + if g.Key == key { + g.Syntax.markRemoved() + *g = Godebug{} + } + } + return nil +} + +func (f *File) DropRequire(path string) error { + for _, r := range f.Require { + if r.Mod.Path == path { + r.Syntax.markRemoved() + *r = Require{} + } + } + return nil +} + +// AddExclude adds a exclude statement to the mod file. Errors if the provided +// version is not a canonical version string +func (f *File) AddExclude(path, vers string) error { + if err := checkCanonicalVersion(path, vers); err != nil { + return err + } + + var hint *Line + for _, x := range f.Exclude { + if x.Mod.Path == path && x.Mod.Version == vers { + return nil + } + if x.Mod.Path == path { + hint = x.Syntax + } + } + + f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)}) + return nil +} + +func (f *File) DropExclude(path, vers string) error { + for _, x := range f.Exclude { + if x.Mod.Path == path && x.Mod.Version == vers { + x.Syntax.markRemoved() + *x = Exclude{} + } + } + return nil +} + +func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error { + return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers) +} + +func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error { + need := true + old := module.Version{Path: oldPath, Version: oldVers} + new := module.Version{Path: newPath, Version: newVers} + tokens := []string{"replace", AutoQuote(oldPath)} + if oldVers != "" { + tokens = append(tokens, oldVers) + } + tokens = append(tokens, "=>", AutoQuote(newPath)) + if newVers != "" { + tokens = append(tokens, newVers) + } + + var hint *Line + for _, r := range *replace { + if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) { + if need { + // Found replacement for old; update to use new. + r.New = new + syntax.updateLine(r.Syntax, tokens...) + need = false + continue + } + // Already added; delete other replacements for same. + r.Syntax.markRemoved() + *r = Replace{} + } + if r.Old.Path == oldPath { + hint = r.Syntax + } + } + if need { + *replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)}) + } + return nil +} + +func (f *File) DropReplace(oldPath, oldVers string) error { + for _, r := range f.Replace { + if r.Old.Path == oldPath && r.Old.Version == oldVers { + r.Syntax.markRemoved() + *r = Replace{} + } + } + return nil +} + +// AddRetract adds a retract statement to the mod file. Errors if the provided +// version interval does not consist of canonical version strings +func (f *File) AddRetract(vi VersionInterval, rationale string) error { + var path string + if f.Module != nil { + path = f.Module.Mod.Path + } + if err := checkCanonicalVersion(path, vi.High); err != nil { + return err + } + if err := checkCanonicalVersion(path, vi.Low); err != nil { + return err + } + + r := &Retract{ + VersionInterval: vi, + } + if vi.Low == vi.High { + r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low)) + } else { + r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]") + } + if rationale != "" { + for _, line := range strings.Split(rationale, "\n") { + com := Comment{Token: "// " + line} + r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com) + } + } + return nil +} + +func (f *File) DropRetract(vi VersionInterval) error { + for _, r := range f.Retract { + if r.VersionInterval == vi { + r.Syntax.markRemoved() + *r = Retract{} + } + } + return nil +} + +// AddTool adds a new tool directive with the given path. +// It does nothing if the tool line already exists. +func (f *File) AddTool(path string) error { + for _, t := range f.Tool { + if t.Path == path { + return nil + } + } + + f.Tool = append(f.Tool, &Tool{ + Path: path, + Syntax: f.Syntax.addLine(nil, "tool", path), + }) + + f.SortBlocks() + return nil +} + +// RemoveTool removes a tool directive with the given path. +// It does nothing if no such tool directive exists. +func (f *File) DropTool(path string) error { + for _, t := range f.Tool { + if t.Path == path { + t.Syntax.markRemoved() + *t = Tool{} + } + } + return nil +} + +func (f *File) SortBlocks() { + f.removeDups() // otherwise sorting is unsafe + + // semanticSortForExcludeVersionV is the Go version (plus leading "v") at which + // lines in exclude blocks start to use semantic sort instead of lexicographic sort. + // See go.dev/issue/60028. + const semanticSortForExcludeVersionV = "v1.21" + useSemanticSortForExclude := f.Go != nil && semver.Compare("v"+f.Go.Version, semanticSortForExcludeVersionV) >= 0 + + for _, stmt := range f.Syntax.Stmt { + block, ok := stmt.(*LineBlock) + if !ok { + continue + } + less := lineLess + if block.Token[0] == "exclude" && useSemanticSortForExclude { + less = lineExcludeLess + } else if block.Token[0] == "retract" { + less = lineRetractLess + } + sort.SliceStable(block.Line, func(i, j int) bool { + return less(block.Line[i], block.Line[j]) + }) + } +} + +// removeDups removes duplicate exclude, replace and tool directives. +// +// Earlier exclude and tool directives take priority. +// +// Later replace directives take priority. +// +// require directives are not de-duplicated. That's left up to higher-level +// logic (MVS). +// +// retract directives are not de-duplicated since comments are +// meaningful, and versions may be retracted multiple times. +func (f *File) removeDups() { + removeDups(f.Syntax, &f.Exclude, &f.Replace, &f.Tool) +} + +func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace, tool *[]*Tool) { + kill := make(map[*Line]bool) + + // Remove duplicate excludes. + if exclude != nil { + haveExclude := make(map[module.Version]bool) + for _, x := range *exclude { + if haveExclude[x.Mod] { + kill[x.Syntax] = true + continue + } + haveExclude[x.Mod] = true + } + var excl []*Exclude + for _, x := range *exclude { + if !kill[x.Syntax] { + excl = append(excl, x) + } + } + *exclude = excl + } + + // Remove duplicate replacements. + // Later replacements take priority over earlier ones. + haveReplace := make(map[module.Version]bool) + for i := len(*replace) - 1; i >= 0; i-- { + x := (*replace)[i] + if haveReplace[x.Old] { + kill[x.Syntax] = true + continue + } + haveReplace[x.Old] = true + } + var repl []*Replace + for _, x := range *replace { + if !kill[x.Syntax] { + repl = append(repl, x) + } + } + *replace = repl + + if tool != nil { + haveTool := make(map[string]bool) + for _, t := range *tool { + if haveTool[t.Path] { + kill[t.Syntax] = true + continue + } + haveTool[t.Path] = true + } + var newTool []*Tool + for _, t := range *tool { + if !kill[t.Syntax] { + newTool = append(newTool, t) + } + } + *tool = newTool + } + + // Duplicate require and retract directives are not removed. + + // Drop killed statements from the syntax tree. + var stmts []Expr + for _, stmt := range syntax.Stmt { + switch stmt := stmt.(type) { + case *Line: + if kill[stmt] { + continue + } + case *LineBlock: + var lines []*Line + for _, line := range stmt.Line { + if !kill[line] { + lines = append(lines, line) + } + } + stmt.Line = lines + if len(lines) == 0 { + continue + } + } + stmts = append(stmts, stmt) + } + syntax.Stmt = stmts +} + +// lineLess returns whether li should be sorted before lj. It sorts +// lexicographically without assigning any special meaning to tokens. +func lineLess(li, lj *Line) bool { + for k := 0; k < len(li.Token) && k < len(lj.Token); k++ { + if li.Token[k] != lj.Token[k] { + return li.Token[k] < lj.Token[k] + } + } + return len(li.Token) < len(lj.Token) +} + +// lineExcludeLess reports whether li should be sorted before lj for lines in +// an "exclude" block. +func lineExcludeLess(li, lj *Line) bool { + if len(li.Token) != 2 || len(lj.Token) != 2 { + // Not a known exclude specification. + // Fall back to sorting lexicographically. + return lineLess(li, lj) + } + // An exclude specification has two tokens: ModulePath and Version. + // Compare module path by string order and version by semver rules. + if pi, pj := li.Token[0], lj.Token[0]; pi != pj { + return pi < pj + } + return semver.Compare(li.Token[1], lj.Token[1]) < 0 +} + +// lineRetractLess returns whether li should be sorted before lj for lines in +// a "retract" block. It treats each line as a version interval. Single versions +// are compared as if they were intervals with the same low and high version. +// Intervals are sorted in descending order, first by low version, then by +// high version, using semver.Compare. +func lineRetractLess(li, lj *Line) bool { + interval := func(l *Line) VersionInterval { + if len(l.Token) == 1 { + return VersionInterval{Low: l.Token[0], High: l.Token[0]} + } else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" { + return VersionInterval{Low: l.Token[1], High: l.Token[3]} + } else { + // Line in unknown format. Treat as an invalid version. + return VersionInterval{} + } + } + vii := interval(li) + vij := interval(lj) + if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 { + return cmp > 0 + } + return semver.Compare(vii.High, vij.High) > 0 +} + +// checkCanonicalVersion returns a non-nil error if vers is not a canonical +// version string or does not match the major version of path. +// +// If path is non-empty, the error text suggests a format with a major version +// corresponding to the path. +func checkCanonicalVersion(path, vers string) error { + _, pathMajor, pathMajorOk := module.SplitPathVersion(path) + + if vers == "" || vers != module.CanonicalVersion(vers) { + if pathMajor == "" { + return &module.InvalidVersionError{ + Version: vers, + Err: fmt.Errorf("must be of the form v1.2.3"), + } + } + return &module.InvalidVersionError{ + Version: vers, + Err: fmt.Errorf("must be of the form %s.2.3", module.PathMajorPrefix(pathMajor)), + } + } + + if pathMajorOk { + if err := module.CheckPathMajor(vers, pathMajor); err != nil { + if pathMajor == "" { + // In this context, the user probably wrote "v2.3.4" when they meant + // "v2.3.4+incompatible". Suggest that instead of "v0 or v1". + return &module.InvalidVersionError{ + Version: vers, + Err: fmt.Errorf("should be %s+incompatible (or module %s/%v)", vers, path, semver.Major(vers)), + } + } + return err + } + } + + return nil +} diff --git a/vendor/golang.org/x/mod/modfile/work.go b/vendor/golang.org/x/mod/modfile/work.go new file mode 100644 index 0000000..5387d0c --- /dev/null +++ b/vendor/golang.org/x/mod/modfile/work.go @@ -0,0 +1,335 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfile + +import ( + "fmt" + "sort" + "strings" +) + +// A WorkFile is the parsed, interpreted form of a go.work file. +type WorkFile struct { + Go *Go + Toolchain *Toolchain + Godebug []*Godebug + Use []*Use + Replace []*Replace + + Syntax *FileSyntax +} + +// A Use is a single directory statement. +type Use struct { + Path string // Use path of module. + ModulePath string // Module path in the comment. + Syntax *Line +} + +// ParseWork parses and returns a go.work file. +// +// file is the name of the file, used in positions and errors. +// +// data is the content of the file. +// +// fix is an optional function that canonicalizes module versions. +// If fix is nil, all module versions must be canonical ([module.CanonicalVersion] +// must return the same string). +func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) { + fs, err := parse(file, data) + if err != nil { + return nil, err + } + f := &WorkFile{ + Syntax: fs, + } + var errs ErrorList + + for _, x := range fs.Stmt { + switch x := x.(type) { + case *Line: + f.add(&errs, x, x.Token[0], x.Token[1:], fix) + + case *LineBlock: + if len(x.Token) > 1 { + errs = append(errs, Error{ + Filename: file, + Pos: x.Start, + Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), + }) + continue + } + switch x.Token[0] { + default: + errs = append(errs, Error{ + Filename: file, + Pos: x.Start, + Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), + }) + continue + case "godebug", "use", "replace": + for _, l := range x.Line { + f.add(&errs, l, x.Token[0], l.Token, fix) + } + } + } + } + + if len(errs) > 0 { + return nil, errs + } + return f, nil +} + +// Cleanup cleans up the file f after any edit operations. +// To avoid quadratic behavior, modifications like [WorkFile.DropRequire] +// clear the entry but do not remove it from the slice. +// Cleanup cleans out all the cleared entries. +func (f *WorkFile) Cleanup() { + w := 0 + for _, r := range f.Use { + if r.Path != "" { + f.Use[w] = r + w++ + } + } + f.Use = f.Use[:w] + + w = 0 + for _, r := range f.Replace { + if r.Old.Path != "" { + f.Replace[w] = r + w++ + } + } + f.Replace = f.Replace[:w] + + f.Syntax.Cleanup() +} + +func (f *WorkFile) AddGoStmt(version string) error { + if !GoVersionRE.MatchString(version) { + return fmt.Errorf("invalid language version %q", version) + } + if f.Go == nil { + stmt := &Line{Token: []string{"go", version}} + f.Go = &Go{ + Version: version, + Syntax: stmt, + } + // Find the first non-comment-only block and add + // the go statement before it. That will keep file comments at the top. + i := 0 + for i = 0; i < len(f.Syntax.Stmt); i++ { + if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok { + break + } + } + f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...) + } else { + f.Go.Version = version + f.Syntax.updateLine(f.Go.Syntax, "go", version) + } + return nil +} + +func (f *WorkFile) AddToolchainStmt(name string) error { + if !ToolchainRE.MatchString(name) { + return fmt.Errorf("invalid toolchain name %q", name) + } + if f.Toolchain == nil { + stmt := &Line{Token: []string{"toolchain", name}} + f.Toolchain = &Toolchain{ + Name: name, + Syntax: stmt, + } + // Find the go line and add the toolchain line after it. + // Or else find the first non-comment-only block and add + // the toolchain line before it. That will keep file comments at the top. + i := 0 + for i = 0; i < len(f.Syntax.Stmt); i++ { + if line, ok := f.Syntax.Stmt[i].(*Line); ok && len(line.Token) > 0 && line.Token[0] == "go" { + i++ + goto Found + } + } + for i = 0; i < len(f.Syntax.Stmt); i++ { + if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok { + break + } + } + Found: + f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...) + } else { + f.Toolchain.Name = name + f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name) + } + return nil +} + +// DropGoStmt deletes the go statement from the file. +func (f *WorkFile) DropGoStmt() { + if f.Go != nil { + f.Go.Syntax.markRemoved() + f.Go = nil + } +} + +// DropToolchainStmt deletes the toolchain statement from the file. +func (f *WorkFile) DropToolchainStmt() { + if f.Toolchain != nil { + f.Toolchain.Syntax.markRemoved() + f.Toolchain = nil + } +} + +// AddGodebug sets the first godebug line for key to value, +// preserving any existing comments for that line and removing all +// other godebug lines for key. +// +// If no line currently exists for key, AddGodebug adds a new line +// at the end of the last godebug block. +func (f *WorkFile) AddGodebug(key, value string) error { + need := true + for _, g := range f.Godebug { + if g.Key == key { + if need { + g.Value = value + f.Syntax.updateLine(g.Syntax, "godebug", key+"="+value) + need = false + } else { + g.Syntax.markRemoved() + *g = Godebug{} + } + } + } + + if need { + f.addNewGodebug(key, value) + } + return nil +} + +// addNewGodebug adds a new godebug key=value line at the end +// of the last godebug block, regardless of any existing godebug lines for key. +func (f *WorkFile) addNewGodebug(key, value string) { + line := f.Syntax.addLine(nil, "godebug", key+"="+value) + g := &Godebug{ + Key: key, + Value: value, + Syntax: line, + } + f.Godebug = append(f.Godebug, g) +} + +func (f *WorkFile) DropGodebug(key string) error { + for _, g := range f.Godebug { + if g.Key == key { + g.Syntax.markRemoved() + *g = Godebug{} + } + } + return nil +} + +func (f *WorkFile) AddUse(diskPath, modulePath string) error { + need := true + for _, d := range f.Use { + if d.Path == diskPath { + if need { + d.ModulePath = modulePath + f.Syntax.updateLine(d.Syntax, "use", AutoQuote(diskPath)) + need = false + } else { + d.Syntax.markRemoved() + *d = Use{} + } + } + } + + if need { + f.AddNewUse(diskPath, modulePath) + } + return nil +} + +func (f *WorkFile) AddNewUse(diskPath, modulePath string) { + line := f.Syntax.addLine(nil, "use", AutoQuote(diskPath)) + f.Use = append(f.Use, &Use{Path: diskPath, ModulePath: modulePath, Syntax: line}) +} + +func (f *WorkFile) SetUse(dirs []*Use) { + need := make(map[string]string) + for _, d := range dirs { + need[d.Path] = d.ModulePath + } + + for _, d := range f.Use { + if modulePath, ok := need[d.Path]; ok { + d.ModulePath = modulePath + } else { + d.Syntax.markRemoved() + *d = Use{} + } + } + + // TODO(#45713): Add module path to comment. + + for diskPath, modulePath := range need { + f.AddNewUse(diskPath, modulePath) + } + f.SortBlocks() +} + +func (f *WorkFile) DropUse(path string) error { + for _, d := range f.Use { + if d.Path == path { + d.Syntax.markRemoved() + *d = Use{} + } + } + return nil +} + +func (f *WorkFile) AddReplace(oldPath, oldVers, newPath, newVers string) error { + return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers) +} + +func (f *WorkFile) DropReplace(oldPath, oldVers string) error { + for _, r := range f.Replace { + if r.Old.Path == oldPath && r.Old.Version == oldVers { + r.Syntax.markRemoved() + *r = Replace{} + } + } + return nil +} + +func (f *WorkFile) SortBlocks() { + f.removeDups() // otherwise sorting is unsafe + + for _, stmt := range f.Syntax.Stmt { + block, ok := stmt.(*LineBlock) + if !ok { + continue + } + sort.SliceStable(block.Line, func(i, j int) bool { + return lineLess(block.Line[i], block.Line[j]) + }) + } +} + +// removeDups removes duplicate replace directives. +// +// Later replace directives take priority. +// +// require directives are not de-duplicated. That's left up to higher-level +// logic (MVS). +// +// retract directives are not de-duplicated since comments are +// meaningful, and versions may be retracted multiple times. +func (f *WorkFile) removeDups() { + removeDups(f.Syntax, nil, &f.Replace, nil) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 59b9b88..31c140a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -17,7 +17,7 @@ github.com/dprotaso/go-yit ## explicit; go 1.17 github.com/fsnotify/fsnotify github.com/fsnotify/fsnotify/internal -# github.com/getkin/kin-openapi v0.131.0 +# github.com/getkin/kin-openapi v0.133.0 ## explicit; go 1.22.5 github.com/getkin/kin-openapi/openapi3 # github.com/go-openapi/jsonpointer v0.21.0 @@ -91,8 +91,8 @@ github.com/mohae/deepcopy # github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 ## explicit github.com/munnerz/goautoneg -# github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 -## explicit; go 1.21.0 +# github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 +## explicit; go 1.22.5 github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen github.com/oapi-codegen/oapi-codegen/v2/pkg/util @@ -152,8 +152,13 @@ github.com/sourcegraph/conc github.com/sourcegraph/conc/internal/multierror github.com/sourcegraph/conc/iter github.com/sourcegraph/conc/panics -# github.com/speakeasy-api/openapi-overlay v0.9.0 -## explicit; go 1.21.0 +# github.com/speakeasy-api/jsonpath v0.6.0 +## explicit; go 1.22 +github.com/speakeasy-api/jsonpath/pkg/jsonpath +github.com/speakeasy-api/jsonpath/pkg/jsonpath/config +github.com/speakeasy-api/jsonpath/pkg/jsonpath/token +# github.com/speakeasy-api/openapi-overlay v0.10.2 +## explicit; go 1.22 github.com/speakeasy-api/openapi-overlay/pkg/loader github.com/speakeasy-api/openapi-overlay/pkg/overlay # github.com/spf13/afero v1.12.0 @@ -181,7 +186,7 @@ github.com/spf13/viper/internal/features # github.com/stretchr/objx v0.5.2 ## explicit; go 1.20 github.com/stretchr/objx -# github.com/stretchr/testify v1.10.0 +# github.com/stretchr/testify v1.11.1 ## explicit; go 1.17 github.com/stretchr/testify/assert github.com/stretchr/testify/assert/yaml @@ -202,12 +207,16 @@ github.com/vektra/mockery/v2/pkg/stackerr # github.com/vmware-labs/yaml-jsonpath v0.3.2 ## explicit; go 1.13 github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath +# github.com/woodsbury/decimal128 v1.3.0 +## explicit; go 1.21 +github.com/woodsbury/decimal128 # go.uber.org/multierr v1.11.0 ## explicit; go 1.19 go.uber.org/multierr # golang.org/x/mod v0.23.0 ## explicit; go 1.22.0 golang.org/x/mod/internal/lazyregexp +golang.org/x/mod/modfile golang.org/x/mod/module golang.org/x/mod/semver # golang.org/x/sync v0.11.0