diff --git a/common/policydsl/policyparser.go b/common/policydsl/policyparser.go index aa6f514370d..653a02442c5 100644 --- a/common/policydsl/policyparser.go +++ b/common/policydsl/policyparser.go @@ -13,7 +13,7 @@ import ( "strconv" "strings" - "github.com/Knetic/govaluate" + "github.com/expr-lang/expr" cb "github.com/hyperledger/fabric-protos-go-apiv2/common" mb "github.com/hyperledger/fabric-protos-go-apiv2/msp" "google.golang.org/protobuf/proto" @@ -111,6 +111,8 @@ func firstPass(args ...any) (any, error) { case float32: case float64: toret.WriteString(strconv.Itoa(int(t))) + case int: + toret.WriteString(strconv.Itoa(t)) default: return nil, fmt.Errorf("unexpected type %s", reflect.TypeOf(arg)) } @@ -143,6 +145,8 @@ func secondPass(args ...any) (any, error) { switch arg := args[1].(type) { case float64: t = int(arg) + case int: + t = arg default: return nil, fmt.Errorf("unrecognized type, expected a number, got %s", reflect.TypeOf(args[1])) } @@ -253,24 +257,23 @@ func newContext() *context { // the required role func FromString(policy string) (*cb.SignaturePolicyEnvelope, error) { // first we translate the and/or business into outof gates - intermediate, err := govaluate.NewEvaluableExpressionWithFunctions( - policy, map[string]govaluate.ExpressionFunction{ - GateAnd: and, - strings.ToLower(GateAnd): and, - strings.ToUpper(GateAnd): and, - GateOr: or, - strings.ToLower(GateOr): or, - strings.ToUpper(GateOr): or, - GateOutOf: outof, - strings.ToLower(GateOutOf): outof, - strings.ToUpper(GateOutOf): outof, - }, - ) + env := map[string]interface{}{ + GateAnd: and, + strings.ToLower(GateAnd): and, + strings.ToUpper(GateAnd): and, + GateOr: or, + strings.ToLower(GateOr): or, + strings.ToUpper(GateOr): or, + GateOutOf: outof, + strings.ToLower(GateOutOf): outof, + strings.ToUpper(GateOutOf): outof, + } + intermediate, err := expr.Compile(policy, expr.Env(env)) if err != nil { return nil, err } - intermediateRes, err := intermediate.Evaluate(map[string]any{}) + intermediateRes, err := expr.Run(intermediate, env) if err != nil { // attempt to produce a meaningful error if regexErr.MatchString(err.Error()) { @@ -282,7 +285,6 @@ func FromString(policy string) (*cb.SignaturePolicyEnvelope, error) { return nil, err } - resStr, ok := intermediateRes.(string) if !ok { return nil, fmt.Errorf("invalid policy string '%s'", policy) @@ -294,15 +296,14 @@ func FromString(policy string) (*cb.SignaturePolicyEnvelope, error) { // to user-implemented functions other than via arguments. // We need this argument because we need a global place where // we put the identities that the policy requires - exp, err := govaluate.NewEvaluableExpressionWithFunctions( - resStr, - map[string]govaluate.ExpressionFunction{"outof": firstPass}, - ) + env = map[string]interface{}{ + "outof": firstPass, + } + exp, err := expr.Compile(resStr, expr.Env(env)) if err != nil { return nil, err } - - res, err := exp.Evaluate(map[string]any{}) + res, err := expr.Run(exp, env) if err != nil { // attempt to produce a meaningful error if regexErr.MatchString(err.Error()) { @@ -321,18 +322,16 @@ func FromString(policy string) (*cb.SignaturePolicyEnvelope, error) { } ctx := newContext() - parameters := make(map[string]any, 1) - parameters["ID"] = ctx - - exp, err = govaluate.NewEvaluableExpressionWithFunctions( - resStr, - map[string]govaluate.ExpressionFunction{"outof": secondPass}, - ) + env = map[string]interface{}{ + "outof": secondPass, + "ID": ctx, + } + exp, err = expr.Compile(resStr, expr.Env(env)) if err != nil { return nil, err } - res, err = exp.Evaluate(parameters) + res, err = expr.Run(exp, env) if err != nil { // attempt to produce a meaningful error if regexErr.MatchString(err.Error()) { diff --git a/common/policydsl/policyparser_test.go b/common/policydsl/policyparser_test.go index d4da2e9a6c6..1e4ef36d5de 100644 --- a/common/policydsl/policyparser_test.go +++ b/common/policydsl/policyparser_test.go @@ -238,13 +238,13 @@ func TestMSPIDWIthSpecialChars(t *testing.T) { func TestBadStringsNoPanic(t *testing.T) { _, err := FromString("OR('A.member', Bmember)") // error after 1st Evaluate() - require.EqualError(t, err, "unrecognized token 'Bmember' in policy string") + require.ErrorContains(t, err, "unknown name Bmember") _, err = FromString("OR('A.member', 'Bmember')") // error after 2nd Evalute() - require.EqualError(t, err, "unrecognized token 'Bmember' in policy string") + require.ErrorContains(t, err, "unknown name Bmember") _, err = FromString(`OR('A.member', '\'Bmember\'')`) // error after 3rd Evalute() - require.EqualError(t, err, "unrecognized token 'Bmember' in policy string") + require.ErrorContains(t, err, "unknown name Bmember") } func TestNodeOUs(t *testing.T) { @@ -310,39 +310,39 @@ func TestOutOfNumIsString(t *testing.T) { func TestOutOfErrorCase(t *testing.T) { p1, err1 := FromString("") // 1st NewEvaluableExpressionWithFunctions() returns an error require.Nil(t, p1) - require.EqualError(t, err1, "Unexpected end of expression") + require.EqualError(t, err1, "unexpected token EOF") p2, err2 := FromString("OutOf(1)") // outof() if len(args)<2 require.Nil(t, p2) - require.EqualError(t, err2, "expected at least two arguments to NOutOf. Given 1") + require.ErrorContains(t, err2, "expected at least two arguments to NOutOf. Given 1") p3, err3 := FromString("OutOf(true, 'A.member')") // outof() }else{. 1st arg is non of float, int, string require.Nil(t, p3) - require.EqualError(t, err3, "unexpected type bool") + require.ErrorContains(t, err3, "unexpected type bool") p4, err4 := FromString("OutOf(1, 2)") // oufof() switch default. 2nd arg is not string. require.Nil(t, p4) - require.EqualError(t, err4, "unexpected type float64") + require.ErrorContains(t, err4, "unexpected type int") p5, err5 := FromString("OutOf(1, 'true')") // firstPass() switch default require.Nil(t, p5) - require.EqualError(t, err5, "unexpected type bool") + require.ErrorContains(t, err5, "unexpected type bool") p6, err6 := FromString(`OutOf('\'\\\'A\\\'\'', 'B.member')`) // secondPass() switch args[1].(type) default require.Nil(t, p6) - require.EqualError(t, err6, "unrecognized type, expected a number, got string") + require.ErrorContains(t, err6, "unrecognized type, expected a number, got string") p7, err7 := FromString(`OutOf(1, '\'1\'')`) // secondPass() switch args[1].(type) default require.Nil(t, p7) - require.EqualError(t, err7, "unrecognized type, expected a principal or a policy, got float64") + require.ErrorContains(t, err7, "unrecognized type, expected a principal or a policy, got int") p8, err8 := FromString(`''`) // 2nd NewEvaluateExpressionWithFunction() returns an error require.Nil(t, p8) - require.EqualError(t, err8, "Unexpected end of expression") + require.EqualError(t, err8, "unexpected token EOF") p9, err9 := FromString(`'\'\''`) // 3rd NewEvaluateExpressionWithFunction() returns an error require.Nil(t, p9) - require.EqualError(t, err9, "Unexpected end of expression") + require.EqualError(t, err9, "unexpected token EOF") } func TestBadStringBeforeFAB11404_ThisCanDeleteAfterFAB11404HasMerged(t *testing.T) { @@ -367,7 +367,7 @@ func TestSecondPassBoundaryCheck(t *testing.T) { // Prohibit t<0 p0, err0 := FromString("OutOf(-1, 'A.member', 'B.member')") require.Nil(t, p0) - require.EqualError(t, err0, "invalid t-out-of-n predicate, t -1, n 2") + require.ErrorContains(t, err0, "invalid t-out-of-n predicate, t -1, n 2") // Permit t==0 : always satisfied policy // There is no clear usecase of t=0, but somebody may already use it, so we don't treat as an error. @@ -404,5 +404,5 @@ func TestSecondPassBoundaryCheck(t *testing.T) { // Prohibit t>n + 1 p3, err3 := FromString("OutOf(4, 'A.member', 'B.member')") require.Nil(t, p3) - require.EqualError(t, err3, "invalid t-out-of-n predicate, t 4, n 2") + require.ErrorContains(t, err3, "invalid t-out-of-n predicate, t 4, n 2") } diff --git a/go.mod b/go.mod index c8aed570ce6..fe2c4384f9b 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.26.1 require ( code.cloudfoundry.org/clock v1.15.0 github.com/IBM/idemix v0.0.2-0.20240913182345-72941a5f41cd - github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible github.com/VictoriaMetrics/fastcache v1.12.2 github.com/bits-and-blooms/bitset v1.20.0 github.com/cheggaaa/pb/v3 v3.1.5 github.com/davecgh/go-spew v1.1.1 + github.com/expr-lang/expr v1.17.8 github.com/go-kit/kit v0.13.0 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/gorilla/handlers v1.5.2 @@ -48,6 +48,7 @@ require ( github.com/IBM/idemix/bccsp/schemes/weak-bb v0.0.0-20240913182345-72941a5f41cd // indirect github.com/IBM/idemix/bccsp/types v0.0.0-20240913182345-72941a5f41cd // indirect github.com/IBM/mathlib v0.0.3-0.20250709075152-a138079496c3 // indirect + github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/VividCortex/ewma v1.2.0 // indirect diff --git a/go.sum b/go.sum index b23674bf6b9..96a6bf1e269 100644 --- a/go.sum +++ b/go.sum @@ -715,6 +715,8 @@ github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= +github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= diff --git a/internal/configtxgen/encoder/encoder_test.go b/internal/configtxgen/encoder/encoder_test.go index 6bf62d3db4d..24e9d40a527 100644 --- a/internal/configtxgen/encoder/encoder_test.go +++ b/internal/configtxgen/encoder/encoder_test.go @@ -10,10 +10,6 @@ import ( "fmt" "time" - . "github.com/hyperledger/fabric/internal/test" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - cb "github.com/hyperledger/fabric-protos-go-apiv2/common" ab "github.com/hyperledger/fabric-protos-go-apiv2/orderer" "github.com/hyperledger/fabric-protos-go-apiv2/orderer/etcdraft" @@ -23,7 +19,10 @@ import ( "github.com/hyperledger/fabric/internal/configtxgen/encoder/fakes" "github.com/hyperledger/fabric/internal/configtxgen/genesisconfig" "github.com/hyperledger/fabric/internal/pkg/identity" + . "github.com/hyperledger/fabric/internal/test" "github.com/hyperledger/fabric/protoutil" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "google.golang.org/protobuf/proto" ) @@ -202,7 +201,7 @@ var _ = Describe("Encoder", func() { It("wraps and returns the error", func() { err := encoder.AddPolicies(cg, policies, "Readers") - Expect(err).To(MatchError("invalid signature policy rule 'garbage': unrecognized token 'garbage' in policy string")) + Expect(err).To(MatchError("invalid signature policy rule 'garbage': unknown name garbage (1:1)\n | garbage\n | ^")) }) }) diff --git a/internal/peer/chaincode/common_test.go b/internal/peer/chaincode/common_test.go index ee9000d0bc9..56704bfc6f2 100644 --- a/internal/peer/chaincode/common_test.go +++ b/internal/peer/chaincode/common_test.go @@ -294,7 +294,7 @@ func TestCollectionParsing(t *testing.T) { { name: "Invalid member orgs policy", collectionConfig: sampleCollectionConfigBad, - expectedErr: "invalid policy barf: unrecognized token 'barf' in policy string", + expectedErr: "invalid policy barf: unknown name barf (1:1)\n | barf\n | ^", }, { name: "Invalid collection config", diff --git a/vendor/github.com/expr-lang/expr/.gitattributes b/vendor/github.com/expr-lang/expr/.gitattributes new file mode 100644 index 00000000000..efd30300a1e --- /dev/null +++ b/vendor/github.com/expr-lang/expr/.gitattributes @@ -0,0 +1 @@ +*\[generated\].go linguist-language=txt diff --git a/vendor/github.com/expr-lang/expr/.gitignore b/vendor/github.com/expr-lang/expr/.gitignore new file mode 100644 index 00000000000..9d3410bdfb5 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/.gitignore @@ -0,0 +1,11 @@ +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +*.html +custom_tests.json +pro/ +test/avs/ diff --git a/vendor/github.com/expr-lang/expr/LICENSE b/vendor/github.com/expr-lang/expr/LICENSE new file mode 100644 index 00000000000..ec46ce324eb --- /dev/null +++ b/vendor/github.com/expr-lang/expr/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Anton Medvedev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/expr-lang/expr/README.md b/vendor/github.com/expr-lang/expr/README.md new file mode 100644 index 00000000000..fed955d7b42 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/README.md @@ -0,0 +1,181 @@ +

Zx logo Expr

+ +[![test](https://github.com/expr-lang/expr/actions/workflows/test.yml/badge.svg)](https://github.com/expr-lang/expr/actions/workflows/test.yml) +[![Go Report Card](https://goreportcard.com/badge/github.com/expr-lang/expr)](https://goreportcard.com/report/github.com/expr-lang/expr) +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/expr.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:expr) +[![GoDoc](https://godoc.org/github.com/expr-lang/expr?status.svg)](https://godoc.org/github.com/expr-lang/expr) + +**Expr** is a Go-centric expression language designed to deliver dynamic configurations with unparalleled accuracy, safety, and speed. +**Expr** combines simple [syntax](https://expr-lang.org/docs/language-definition) with powerful features for ease of use: + +```js +// Allow only admins and moderators to moderate comments. +user.Group in ["admin", "moderator"] || user.Id == comment.UserId +``` + +```js +// Determine whether the request is in the permitted time window. +request.Time - resource.Age < duration("24h") +``` + +```js +// Ensure all tweets are less than 240 characters. +all(tweets, len(.Content) <= 240) +``` + +## Features + +**Expr** is a safe, fast, and intuitive expression evaluator optimized for the Go language. +Here are its standout features: + +### Safety and Isolation +* **Memory-Safe**: Expr is designed with a focus on safety, ensuring that programs do not access unrelated memory or introduce memory vulnerabilities. +* **Side-Effect-Free**: Expressions evaluated in Expr only compute outputs from their inputs, ensuring no side-effects that can change state or produce unintended results. +* **Always Terminating**: Expr is designed to prevent infinite loops, ensuring that every program will conclude in a reasonable amount of time. + +### Go Integration +* **Seamless with Go**: Integrate Expr into your Go projects without the need to redefine types. + +### Static Typing +* Ensures type correctness and prevents runtime type errors. + ```go + out, err := expr.Compile(`name + age`) + // err: invalid operation + (mismatched types string and int) + // | name + age + // | .....^ + ``` + +### User-Friendly +* Provides user-friendly error messages to assist with debugging and development. + +### Flexibility and Utility +* **Rich Operators**: Offers a reasonable set of basic operators for a variety of applications. +* **Built-in Functions**: Functions like `all`, `none`, `any`, `one`, `filter`, and `map` are provided out-of-the-box. + +### Performance +* **Optimized for Speed**: Expr stands out in its performance, utilizing an optimizing compiler and a bytecode virtual machine. Check out these [benchmarks](https://github.com/antonmedv/golang-expression-evaluation-comparison#readme) for more details. + +## Install + +``` +go get github.com/expr-lang/expr +``` + +## Documentation + +* See [Getting Started](https://expr-lang.org/docs/Getting-Started) page for developer documentation. +* See [Language Definition](https://expr-lang.org/docs/language-definition) page to learn the syntax. + +## Examples + +[Play Online](https://go.dev/play/p/XCoNXEjm3TS) + +```go +package main + +import ( + "fmt" + "github.com/expr-lang/expr" +) + +func main() { + env := map[string]interface{}{ + "greet": "Hello, %v!", + "names": []string{"world", "you"}, + "sprintf": fmt.Sprintf, + } + + code := `sprintf(greet, names[0])` + + program, err := expr.Compile(code, expr.Env(env)) + if err != nil { + panic(err) + } + + output, err := expr.Run(program, env) + if err != nil { + panic(err) + } + + fmt.Println(output) +} +``` + +[Play Online](https://go.dev/play/p/tz-ZneBfSuw) + +```go +package main + +import ( + "fmt" + "github.com/expr-lang/expr" +) + +type Tweet struct { + Len int +} + +type Env struct { + Tweets []Tweet +} + +func main() { + code := `all(Tweets, {.Len <= 240})` + + program, err := expr.Compile(code, expr.Env(Env{})) + if err != nil { + panic(err) + } + + env := Env{ + Tweets: []Tweet{{42}, {98}, {69}}, + } + output, err := expr.Run(program, env) + if err != nil { + panic(err) + } + + fmt.Println(output) +} +``` + +## Who uses Expr? + +* [Google](https://google.com) uses Expr as one of its expression languages on the [Google Cloud Platform](https://cloud.google.com). +* [Uber](https://uber.com) uses Expr to allow customization of its Uber Eats marketplace. +* [GoDaddy](https://godaddy.com) employs Expr for the customization of its GoDaddy Pro product. +* [ByteDance](https://bytedance.com) incorporates Expr into its internal business rule engine. +* [Aviasales](https://aviasales.ru) utilizes Expr as a business rule engine for its flight search engine. +* [Alibaba](https://alibaba.com) uses Expr in a web framework for building recommendation services. +* [Argo](https://argoproj.github.io) integrates Expr into Argo Rollouts and Argo Workflows for Kubernetes. +* [Wish.com](https://www.wish.com) employs Expr in its decision-making rule engine for the Wish Assistant. +* [OpenTelemetry](https://opentelemetry.io) integrates Expr into the OpenTelemetry Collector. +* [Philips Labs](https://github.com/philips-labs/tabia) employs Expr in Tabia, a tool designed to collect insights on their code bases. +* [CrowdSec](https://crowdsec.net) incorporates Expr into its security automation tool. +* [CoreDNS](https://coredns.io) uses Expr in CoreDNS, which is a DNS server. +* [qiniu](https://www.qiniu.com) implements Expr in its trade systems. +* [Junglee Games](https://www.jungleegames.com/) uses Expr for its in-house marketing retention tool, Project Audience. +* [Faceit](https://www.faceit.com) uses Expr to enhance customization of its eSports matchmaking algorithm. +* [Chaos Mesh](https://chaos-mesh.org) incorporates Expr into Chaos Mesh, a cloud-native Chaos Engineering platform. +* [Visually.io](https://visually.io) employs Expr as a business rule engine for its personalization targeting algorithm. +* [Akvorado](https://github.com/akvorado/akvorado) utilizes Expr to classify exporters and interfaces in network flows. +* [keda.sh](https://keda.sh) uses Expr to allow customization of its Kubernetes-based event-driven autoscaling. +* [Span Digital](https://spandigital.com/) uses Expr in its Knowledge Management products. +* [Xiaohongshu](https://www.xiaohongshu.com/) combining yaml with Expr for dynamically policies delivery. +* [Melrōse](https://melrōse.org) uses Expr to implement its music programming language. +* [Tork](https://www.tork.run/) integrates Expr into its workflow execution. +* [Critical Moments](https://criticalmoments.io) uses Expr for its mobile realtime conditional targeting system. +* [WoodpeckerCI](https://woodpecker-ci.org) uses Expr for [filtering workflows/steps](https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate). +* [FastSchema](https://github.com/fastschema/fastschema) - A BaaS leveraging Expr for its customizable and dynamic Access Control system. +* [WunderGraph Cosmo](https://github.com/wundergraph/cosmo) - GraphQL Federeration Router uses Expr to customize Middleware behaviour +* [SOLO](https://solo.one) uses Expr interally to allow dynamic code execution with custom defined functions. +* [Naoma.AI](https://www.naoma.ai) uses Expr as a part of its call scoring engine. +* [GlassFlow.dev](https://github.com/glassflow/clickhouse-etl) uses Expr to do realtime data transformation in ETL pipelines + +[Add your company too](https://github.com/expr-lang/expr/edit/master/README.md) + +## License + +[MIT](https://github.com/expr-lang/expr/blob/master/LICENSE) + +

diff --git a/vendor/github.com/expr-lang/expr/SECURITY.md b/vendor/github.com/expr-lang/expr/SECURITY.md new file mode 100644 index 00000000000..e18771f5d25 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/SECURITY.md @@ -0,0 +1,22 @@ +# Security Policy + +## Supported Versions + +Expr is generally backwards compatible with very few exceptions, so we +recommend users to always use the latest version to experience stability, +performance and security. + +We generally backport security issues to a single previous minor version, +unless this is not possible or feasible with a reasonable effort. + +| Version | Supported | +|---------|--------------------| +| 1.x | :white_check_mark: | +| 0.x | :x: | + +## Reporting a Vulnerability + +If you believe you've discovered a serious vulnerability, please contact the +Expr core team at anton+security@medv.io. We will evaluate your report and if +necessary issue a fix and an advisory. If the issue was previously undisclosed, +we'll also mention your name in the credits. diff --git a/vendor/github.com/expr-lang/expr/ast/dump.go b/vendor/github.com/expr-lang/expr/ast/dump.go new file mode 100644 index 00000000000..56bc7dbe2e3 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/ast/dump.go @@ -0,0 +1,59 @@ +package ast + +import ( + "fmt" + "reflect" + "regexp" +) + +func Dump(node Node) string { + return dump(reflect.ValueOf(node), "") +} + +func dump(v reflect.Value, ident string) string { + if !v.IsValid() { + return "nil" + } + t := v.Type() + switch t.Kind() { + case reflect.Struct: + out := t.Name() + "{\n" + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if isPrivate(f.Name) { + continue + } + s := v.Field(i) + out += fmt.Sprintf("%v%v: %v,\n", ident+"\t", f.Name, dump(s, ident+"\t")) + } + return out + ident + "}" + case reflect.Slice: + if v.Len() == 0 { + return t.String() + "{}" + } + out := t.String() + "{\n" + for i := 0; i < v.Len(); i++ { + s := v.Index(i) + out += fmt.Sprintf("%v%v,", ident+"\t", dump(s, ident+"\t")) + if i+1 < v.Len() { + out += "\n" + } + } + return out + "\n" + ident + "}" + case reflect.Ptr: + return dump(v.Elem(), ident) + case reflect.Interface: + return dump(reflect.ValueOf(v.Interface()), ident) + + case reflect.String: + return fmt.Sprintf("%q", v) + default: + return fmt.Sprintf("%v", v) + } +} + +var isCapital = regexp.MustCompile("^[A-Z]") + +func isPrivate(s string) bool { + return !isCapital.Match([]byte(s)) +} diff --git a/vendor/github.com/expr-lang/expr/ast/find.go b/vendor/github.com/expr-lang/expr/ast/find.go new file mode 100644 index 00000000000..247ff6c00d9 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/ast/find.go @@ -0,0 +1,18 @@ +package ast + +func Find(node Node, fn func(node Node) bool) Node { + v := &finder{fn: fn} + Walk(&node, v) + return v.node +} + +type finder struct { + node Node + fn func(node Node) bool +} + +func (f *finder) Visit(node *Node) { + if f.fn(*node) { + f.node = *node + } +} diff --git a/vendor/github.com/expr-lang/expr/ast/node.go b/vendor/github.com/expr-lang/expr/ast/node.go new file mode 100644 index 00000000000..fbb9ae8259b --- /dev/null +++ b/vendor/github.com/expr-lang/expr/ast/node.go @@ -0,0 +1,251 @@ +package ast + +import ( + "reflect" + + "github.com/expr-lang/expr/checker/nature" + "github.com/expr-lang/expr/file" +) + +var ( + anyType = reflect.TypeOf(new(any)).Elem() +) + +// Node represents items of abstract syntax tree. +type Node interface { + Location() file.Location + SetLocation(file.Location) + Nature() *nature.Nature + SetNature(nature.Nature) + Type() reflect.Type + SetType(reflect.Type) + String() string +} + +// Patch replaces the node with a new one. +// Location information is preserved. +// Type information is lost. +func Patch(node *Node, newNode Node) { + newNode.SetLocation((*node).Location()) + *node = newNode +} + +// base is a base struct for all nodes. +type base struct { + loc file.Location + nature nature.Nature +} + +// Location returns the location of the node in the source code. +func (n *base) Location() file.Location { + return n.loc +} + +// SetLocation sets the location of the node in the source code. +func (n *base) SetLocation(loc file.Location) { + n.loc = loc +} + +// Nature returns the nature of the node. +func (n *base) Nature() *nature.Nature { + return &n.nature +} + +// SetNature sets the nature of the node. +func (n *base) SetNature(nature nature.Nature) { + n.nature = nature +} + +// Type returns the type of the node. +func (n *base) Type() reflect.Type { + if n.nature.Type == nil { + return anyType + } + return n.nature.Type +} + +// SetType sets the type of the node. +func (n *base) SetType(t reflect.Type) { + n.nature = nature.FromType(t) +} + +// NilNode represents nil. +type NilNode struct { + base +} + +// IdentifierNode represents an identifier. +type IdentifierNode struct { + base + Value string // Name of the identifier. Like "foo" in "foo.bar". +} + +// IntegerNode represents an integer. +type IntegerNode struct { + base + Value int // Value of the integer. +} + +// FloatNode represents a float. +type FloatNode struct { + base + Value float64 // Value of the float. +} + +// BoolNode represents a boolean. +type BoolNode struct { + base + Value bool // Value of the boolean. +} + +// StringNode represents a string. +type StringNode struct { + base + Value string // Value of the string. +} + +// BytesNode represents a byte slice. +type BytesNode struct { + base + Value []byte // Value of the byte slice. +} + +// ConstantNode represents a constant. +// Constants are predefined values like nil, true, false, array, map, etc. +// The parser.Parse will never generate ConstantNode, it is only generated +// by the optimizer. +type ConstantNode struct { + base + Value any // Value of the constant. +} + +// UnaryNode represents a unary operator. +type UnaryNode struct { + base + Operator string // Operator of the unary operator. Like "!" in "!foo" or "not" in "not foo". + Node Node // Node of the unary operator. Like "foo" in "!foo". +} + +// BinaryNode represents a binary operator. +type BinaryNode struct { + base + Operator string // Operator of the binary operator. Like "+" in "foo + bar" or "matches" in "foo matches bar". + Left Node // Left node of the binary operator. + Right Node // Right node of the binary operator. +} + +// ChainNode represents an optional chaining group. +// A few MemberNode nodes can be chained together, +// and will be wrapped in a ChainNode. Example: +// +// foo.bar?.baz?.qux +// +// The whole chain will be wrapped in a ChainNode. +type ChainNode struct { + base + Node Node // Node of the chain. +} + +// MemberNode represents a member access. +// It can be a field access, a method call, +// or an array element access. +// Example: +// +// foo.bar or foo["bar"] +// foo.bar() +// array[0] +type MemberNode struct { + base + Node Node // Node of the member access. Like "foo" in "foo.bar". + Property Node // Property of the member access. For property access it is a StringNode. + Optional bool // If true then the member access is optional. Like "foo?.bar". + Method bool +} + +// SliceNode represents access to a slice of an array. +// Example: +// +// array[1:4] +type SliceNode struct { + base + Node Node // Node of the slice. Like "array" in "array[1:4]". + From Node // From an index of the array. Like "1" in "array[1:4]". + To Node // To an index of the array. Like "4" in "array[1:4]". +} + +// CallNode represents a function or a method call. +type CallNode struct { + base + Callee Node // Node of the call. Like "foo" in "foo()". + Arguments []Node // Arguments of the call. +} + +// BuiltinNode represents a builtin function call. +type BuiltinNode struct { + base + Name string // Name of the builtin function. Like "len" in "len(foo)". + Arguments []Node // Arguments of the builtin function. + Throws bool // If true then accessing a field or array index can throw an error. Used by optimizer. + Map Node // Used by optimizer to fold filter() and map() builtins. + Threshold *int // Used by optimizer for count() early termination. +} + +// PredicateNode represents a predicate. +// Example: +// +// filter(foo, .bar == 1) +// +// The predicate is ".bar == 1". +type PredicateNode struct { + base + Node Node // Node of the predicate body. +} + +// PointerNode represents a pointer to a current value in predicate. +type PointerNode struct { + base + Name string // Name of the pointer. Like "index" in "#index". +} + +// ConditionalNode represents a ternary operator or if/else operator. +type ConditionalNode struct { + base + Ternary bool // Is it ternary or if/else operator? + Cond Node // Condition + Exp1 Node // Expression 1 + Exp2 Node // Expression 2 +} + +// VariableDeclaratorNode represents a variable declaration. +type VariableDeclaratorNode struct { + base + Name string // Name of the variable. Like "foo" in "let foo = 1; foo + 1". + Value Node // Value of the variable. Like "1" in "let foo = 1; foo + 1". + Expr Node // Expression of the variable. Like "foo + 1" in "let foo = 1; foo + 1". +} + +// SequenceNode represents a sequence of nodes separated by semicolons. +// All nodes are executed, only the last node will be returned. +type SequenceNode struct { + base + Nodes []Node +} + +// ArrayNode represents an array. +type ArrayNode struct { + base + Nodes []Node // Nodes of the array. +} + +// MapNode represents a map. +type MapNode struct { + base + Pairs []Node // PairNode nodes. +} + +// PairNode represents a key-value pair of a map. +type PairNode struct { + base + Key Node // Key of the pair. + Value Node // Value of the pair. +} diff --git a/vendor/github.com/expr-lang/expr/ast/print.go b/vendor/github.com/expr-lang/expr/ast/print.go new file mode 100644 index 00000000000..1c197445ed9 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/ast/print.go @@ -0,0 +1,267 @@ +package ast + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/expr-lang/expr/parser/operator" + "github.com/expr-lang/expr/parser/utils" +) + +func (n *NilNode) String() string { + return "nil" +} + +func (n *IdentifierNode) String() string { + return n.Value +} + +func (n *IntegerNode) String() string { + return fmt.Sprintf("%d", n.Value) +} + +func (n *FloatNode) String() string { + return fmt.Sprintf("%v", n.Value) +} + +func (n *BoolNode) String() string { + return fmt.Sprintf("%t", n.Value) +} + +func (n *StringNode) String() string { + return fmt.Sprintf("%q", n.Value) +} + +func (n *BytesNode) String() string { + return fmt.Sprintf("b%q", n.Value) +} + +func (n *ConstantNode) String() string { + if n.Value == nil { + return "nil" + } + b, err := json.Marshal(n.Value) + if err != nil { + panic(err) + } + return string(b) +} + +func (n *UnaryNode) String() string { + op := n.Operator + if n.Operator == "not" { + op = fmt.Sprintf("%s ", n.Operator) + } + wrap := false + switch b := n.Node.(type) { + case *BinaryNode: + if operator.Binary[b.Operator].Precedence < + operator.Unary[n.Operator].Precedence { + wrap = true + } + case *ConditionalNode: + wrap = true + } + if wrap { + return fmt.Sprintf("%s(%s)", op, n.Node.String()) + } + return fmt.Sprintf("%s%s", op, n.Node.String()) +} + +func (n *BinaryNode) String() string { + if n.Operator == ".." { + return fmt.Sprintf("%s..%s", n.Left, n.Right) + } + + var lhs, rhs string + var lwrap, rwrap bool + + if l, ok := n.Left.(*UnaryNode); ok { + if operator.Unary[l.Operator].Precedence < + operator.Binary[n.Operator].Precedence { + lwrap = true + } + } + if lb, ok := n.Left.(*BinaryNode); ok { + if operator.Less(lb.Operator, n.Operator) { + lwrap = true + } + if operator.Binary[lb.Operator].Precedence == + operator.Binary[n.Operator].Precedence && + operator.Binary[n.Operator].Associativity == operator.Right { + lwrap = true + } + if lb.Operator == "??" { + lwrap = true + } + if operator.IsBoolean(lb.Operator) && n.Operator != lb.Operator { + lwrap = true + } + } + if rb, ok := n.Right.(*BinaryNode); ok { + if operator.Less(rb.Operator, n.Operator) { + rwrap = true + } + if operator.Binary[rb.Operator].Precedence == + operator.Binary[n.Operator].Precedence && + operator.Binary[n.Operator].Associativity == operator.Left { + rwrap = true + } + if operator.IsBoolean(rb.Operator) && n.Operator != rb.Operator { + rwrap = true + } + } + + if _, ok := n.Left.(*ConditionalNode); ok { + lwrap = true + } + if _, ok := n.Right.(*ConditionalNode); ok { + rwrap = true + } + + if lwrap { + lhs = fmt.Sprintf("(%s)", n.Left.String()) + } else { + lhs = n.Left.String() + } + + if rwrap { + rhs = fmt.Sprintf("(%s)", n.Right.String()) + } else { + rhs = n.Right.String() + } + + return fmt.Sprintf("%s %s %s", lhs, n.Operator, rhs) +} + +func (n *ChainNode) String() string { + return n.Node.String() +} + +func (n *MemberNode) String() string { + node := n.Node.String() + if _, ok := n.Node.(*BinaryNode); ok { + node = fmt.Sprintf("(%s)", node) + } + + if n.Optional { + if str, ok := n.Property.(*StringNode); ok && utils.IsValidIdentifier(str.Value) { + return fmt.Sprintf("%s?.%s", node, str.Value) + } else { + return fmt.Sprintf("%s?.[%s]", node, n.Property.String()) + } + } + if str, ok := n.Property.(*StringNode); ok && utils.IsValidIdentifier(str.Value) { + if _, ok := n.Node.(*PointerNode); ok { + return fmt.Sprintf(".%s", str.Value) + } + return fmt.Sprintf("%s.%s", node, str.Value) + } + return fmt.Sprintf("%s[%s]", node, n.Property.String()) +} + +func (n *SliceNode) String() string { + if n.From == nil && n.To == nil { + return fmt.Sprintf("%s[:]", n.Node.String()) + } + if n.From == nil { + return fmt.Sprintf("%s[:%s]", n.Node.String(), n.To.String()) + } + if n.To == nil { + return fmt.Sprintf("%s[%s:]", n.Node.String(), n.From.String()) + } + return fmt.Sprintf("%s[%s:%s]", n.Node.String(), n.From.String(), n.To.String()) +} + +func (n *CallNode) String() string { + arguments := make([]string, len(n.Arguments)) + for i, arg := range n.Arguments { + arguments[i] = arg.String() + } + return fmt.Sprintf("%s(%s)", n.Callee.String(), strings.Join(arguments, ", ")) +} + +func (n *BuiltinNode) String() string { + arguments := make([]string, len(n.Arguments)) + for i, arg := range n.Arguments { + arguments[i] = arg.String() + } + return fmt.Sprintf("%s(%s)", n.Name, strings.Join(arguments, ", ")) +} + +func (n *PredicateNode) String() string { + return n.Node.String() +} + +func (n *PointerNode) String() string { + return fmt.Sprintf("#%s", n.Name) +} + +func (n *VariableDeclaratorNode) String() string { + return fmt.Sprintf("let %s = %s; %s", n.Name, n.Value.String(), n.Expr.String()) +} + +func (n *SequenceNode) String() string { + nodes := make([]string, len(n.Nodes)) + for i, node := range n.Nodes { + nodes[i] = node.String() + } + return strings.Join(nodes, "; ") +} + +func (n *ConditionalNode) String() string { + if !n.Ternary { + cond := n.Cond.String() + exp1 := n.Exp1.String() + if c2, ok := n.Exp2.(*ConditionalNode); ok && !c2.Ternary { + return fmt.Sprintf("if %s { %s } else %s", cond, exp1, c2.String()) + } + exp2 := n.Exp2.String() + return fmt.Sprintf("if %s { %s } else { %s }", cond, exp1, exp2) + } + + var cond, exp1, exp2 string + if _, ok := n.Cond.(*ConditionalNode); ok { + cond = fmt.Sprintf("(%s)", n.Cond.String()) + } else { + cond = n.Cond.String() + } + if _, ok := n.Exp1.(*ConditionalNode); ok { + exp1 = fmt.Sprintf("(%s)", n.Exp1.String()) + } else { + exp1 = n.Exp1.String() + } + if _, ok := n.Exp2.(*ConditionalNode); ok { + exp2 = fmt.Sprintf("(%s)", n.Exp2.String()) + } else { + exp2 = n.Exp2.String() + } + return fmt.Sprintf("%s ? %s : %s", cond, exp1, exp2) +} + +func (n *ArrayNode) String() string { + nodes := make([]string, len(n.Nodes)) + for i, node := range n.Nodes { + nodes[i] = node.String() + } + return fmt.Sprintf("[%s]", strings.Join(nodes, ", ")) +} + +func (n *MapNode) String() string { + pairs := make([]string, len(n.Pairs)) + for i, pair := range n.Pairs { + pairs[i] = pair.String() + } + return fmt.Sprintf("{%s}", strings.Join(pairs, ", ")) +} + +func (n *PairNode) String() string { + if str, ok := n.Key.(*StringNode); ok { + if utils.IsValidIdentifier(str.Value) { + return fmt.Sprintf("%s: %s", str.Value, n.Value.String()) + } + return fmt.Sprintf("%s: %s", str.String(), n.Value.String()) + } + return fmt.Sprintf("(%s): %s", n.Key.String(), n.Value.String()) +} diff --git a/vendor/github.com/expr-lang/expr/ast/visitor.go b/vendor/github.com/expr-lang/expr/ast/visitor.go new file mode 100644 index 00000000000..ef23758e140 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/ast/visitor.go @@ -0,0 +1,79 @@ +package ast + +import "fmt" + +type Visitor interface { + Visit(node *Node) +} + +func Walk(node *Node, v Visitor) { + if *node == nil { + return + } + switch n := (*node).(type) { + case *NilNode: + case *IdentifierNode: + case *IntegerNode: + case *FloatNode: + case *BoolNode: + case *StringNode: + case *BytesNode: + case *ConstantNode: + case *UnaryNode: + Walk(&n.Node, v) + case *BinaryNode: + Walk(&n.Left, v) + Walk(&n.Right, v) + case *ChainNode: + Walk(&n.Node, v) + case *MemberNode: + Walk(&n.Node, v) + Walk(&n.Property, v) + case *SliceNode: + Walk(&n.Node, v) + if n.From != nil { + Walk(&n.From, v) + } + if n.To != nil { + Walk(&n.To, v) + } + case *CallNode: + Walk(&n.Callee, v) + for i := range n.Arguments { + Walk(&n.Arguments[i], v) + } + case *BuiltinNode: + for i := range n.Arguments { + Walk(&n.Arguments[i], v) + } + case *PredicateNode: + Walk(&n.Node, v) + case *PointerNode: + case *VariableDeclaratorNode: + Walk(&n.Value, v) + Walk(&n.Expr, v) + case *SequenceNode: + for i := range n.Nodes { + Walk(&n.Nodes[i], v) + } + case *ConditionalNode: + Walk(&n.Cond, v) + Walk(&n.Exp1, v) + Walk(&n.Exp2, v) + case *ArrayNode: + for i := range n.Nodes { + Walk(&n.Nodes[i], v) + } + case *MapNode: + for i := range n.Pairs { + Walk(&n.Pairs[i], v) + } + case *PairNode: + Walk(&n.Key, v) + Walk(&n.Value, v) + default: + panic(fmt.Sprintf("undefined node type (%T)", node)) + } + + v.Visit(node) +} diff --git a/vendor/github.com/expr-lang/expr/builtin/builtin.go b/vendor/github.com/expr-lang/expr/builtin/builtin.go new file mode 100644 index 00000000000..78b55be6de8 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/builtin/builtin.go @@ -0,0 +1,1081 @@ +package builtin + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "reflect" + "sort" + "strings" + "time" + + "github.com/expr-lang/expr/internal/deref" + "github.com/expr-lang/expr/vm/runtime" +) + +var ( + Index map[string]int + Names []string + + // MaxDepth limits the recursion depth for nested structures. + MaxDepth = 10000 + ErrorMaxDepth = errors.New("recursion depth exceeded") +) + +func init() { + Index = make(map[string]int) + Names = make([]string, len(Builtins)) + for i, fn := range Builtins { + Index[fn.Name] = i + Names[i] = fn.Name + } +} + +var Builtins = []*Function{ + { + Name: "all", + Predicate: true, + Types: types(new(func([]any, func(any) bool) bool)), + }, + { + Name: "none", + Predicate: true, + Types: types(new(func([]any, func(any) bool) bool)), + }, + { + Name: "any", + Predicate: true, + Types: types(new(func([]any, func(any) bool) bool)), + }, + { + Name: "one", + Predicate: true, + Types: types(new(func([]any, func(any) bool) bool)), + }, + { + Name: "filter", + Predicate: true, + Types: types(new(func([]any, func(any) bool) []any)), + }, + { + Name: "map", + Predicate: true, + Types: types(new(func([]any, func(any) any) []any)), + }, + { + Name: "find", + Predicate: true, + Types: types(new(func([]any, func(any) bool) any)), + }, + { + Name: "findIndex", + Predicate: true, + Types: types(new(func([]any, func(any) bool) int)), + }, + { + Name: "findLast", + Predicate: true, + Types: types(new(func([]any, func(any) bool) any)), + }, + { + Name: "findLastIndex", + Predicate: true, + Types: types(new(func([]any, func(any) bool) int)), + }, + { + Name: "count", + Predicate: true, + Types: types(new(func([]any, func(any) bool) int)), + }, + { + Name: "sum", + Predicate: true, + Types: types(new(func([]any, func(any) bool) int)), + }, + { + Name: "groupBy", + Predicate: true, + Types: types(new(func([]any, func(any) any) map[any][]any)), + }, + { + Name: "sortBy", + Predicate: true, + Types: types(new(func([]any, func(any) bool, string) []any)), + }, + { + Name: "reduce", + Predicate: true, + Types: types(new(func([]any, func(any, any) any, any) any)), + }, + { + Name: "len", + Fast: Len, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String, reflect.Interface: + return integerType, nil + } + return anyType, fmt.Errorf("invalid argument for len (type %s)", args[0]) + }, + }, + { + Name: "type", + Fast: Type, + Types: types(new(func(any) string)), + }, + { + Name: "abs", + Fast: Abs, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Interface: + return args[0], nil + } + return anyType, fmt.Errorf("invalid argument for abs (type %s)", args[0]) + }, + }, + { + Name: "ceil", + Fast: Ceil, + Validate: func(args []reflect.Type) (reflect.Type, error) { + return validateRoundFunc("ceil", args) + }, + }, + { + Name: "floor", + Fast: Floor, + Validate: func(args []reflect.Type) (reflect.Type, error) { + return validateRoundFunc("floor", args) + }, + }, + { + Name: "round", + Fast: Round, + Validate: func(args []reflect.Type) (reflect.Type, error) { + return validateRoundFunc("round", args) + }, + }, + { + Name: "int", + Fast: Int, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Interface: + return integerType, nil + case reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return integerType, nil + case reflect.String: + return integerType, nil + } + return anyType, fmt.Errorf("invalid argument for int (type %s)", args[0]) + }, + }, + { + Name: "float", + Fast: Float, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Interface: + return floatType, nil + case reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return floatType, nil + case reflect.String: + return floatType, nil + } + return anyType, fmt.Errorf("invalid argument for float (type %s)", args[0]) + }, + }, + { + Name: "string", + Fast: String, + Types: types(new(func(any any) string)), + }, + { + Name: "trim", + Func: func(args ...any) (any, error) { + if len(args) == 1 { + return strings.TrimSpace(args[0].(string)), nil + } else if len(args) == 2 { + return strings.Trim(args[0].(string), args[1].(string)), nil + } else { + return nil, fmt.Errorf("invalid number of arguments for trim (expected 1 or 2, got %d)", len(args)) + } + }, + Types: types( + strings.TrimSpace, + strings.Trim, + ), + }, + { + Name: "trimPrefix", + Func: func(args ...any) (any, error) { + s := " " + if len(args) == 2 { + s = args[1].(string) + } + return strings.TrimPrefix(args[0].(string), s), nil + }, + Types: types( + strings.TrimPrefix, + new(func(string) string), + ), + }, + { + Name: "trimSuffix", + Func: func(args ...any) (any, error) { + s := " " + if len(args) == 2 { + s = args[1].(string) + } + return strings.TrimSuffix(args[0].(string), s), nil + }, + Types: types( + strings.TrimSuffix, + new(func(string) string), + ), + }, + { + Name: "upper", + Fast: func(arg any) any { + return strings.ToUpper(arg.(string)) + }, + Types: types(strings.ToUpper), + }, + { + Name: "lower", + Fast: func(arg any) any { + return strings.ToLower(arg.(string)) + }, + Types: types(strings.ToLower), + }, + { + Name: "split", + Func: func(args ...any) (any, error) { + if len(args) == 2 { + return strings.Split(args[0].(string), args[1].(string)), nil + } else if len(args) == 3 { + return strings.SplitN(args[0].(string), args[1].(string), runtime.ToInt(args[2])), nil + } else { + return nil, fmt.Errorf("invalid number of arguments for split (expected 2 or 3, got %d)", len(args)) + } + }, + Types: types( + strings.Split, + strings.SplitN, + ), + }, + { + Name: "splitAfter", + Func: func(args ...any) (any, error) { + if len(args) == 2 { + return strings.SplitAfter(args[0].(string), args[1].(string)), nil + } else if len(args) == 3 { + return strings.SplitAfterN(args[0].(string), args[1].(string), runtime.ToInt(args[2])), nil + } else { + return nil, fmt.Errorf("invalid number of arguments for splitAfter (expected 2 or 3, got %d)", len(args)) + } + }, + Types: types( + strings.SplitAfter, + strings.SplitAfterN, + ), + }, + { + Name: "replace", + Func: func(args ...any) (any, error) { + if len(args) == 4 { + return strings.Replace(args[0].(string), args[1].(string), args[2].(string), runtime.ToInt(args[3])), nil + } else if len(args) == 3 { + return strings.ReplaceAll(args[0].(string), args[1].(string), args[2].(string)), nil + } else { + return nil, fmt.Errorf("invalid number of arguments for replace (expected 3 or 4, got %d)", len(args)) + } + }, + Types: types( + strings.Replace, + strings.ReplaceAll, + ), + }, + { + Name: "repeat", + Safe: func(args ...any) (any, uint, error) { + s := args[0].(string) + n := runtime.ToInt(args[1]) + if n < 0 { + return nil, 0, fmt.Errorf("invalid argument for repeat (expected positive integer, got %d)", n) + } + if n > 1e6 { + return nil, 0, fmt.Errorf("memory budget exceeded") + } + return strings.Repeat(s, n), uint(len(s) * n), nil + }, + Types: types(strings.Repeat), + }, + { + Name: "join", + Func: func(args ...any) (any, error) { + glue := "" + if len(args) == 2 { + glue = args[1].(string) + } + switch args[0].(type) { + case []string: + return strings.Join(args[0].([]string), glue), nil + case []any: + var s []string + for _, arg := range args[0].([]any) { + s = append(s, arg.(string)) + } + return strings.Join(s, glue), nil + } + return nil, fmt.Errorf("invalid argument for join (type %s)", reflect.TypeOf(args[0])) + }, + Types: types( + strings.Join, + new(func([]any, string) string), + new(func([]any) string), + new(func([]string, string) string), + new(func([]string) string), + ), + }, + { + Name: "indexOf", + Func: func(args ...any) (any, error) { + return strings.Index(args[0].(string), args[1].(string)), nil + }, + Types: types(strings.Index), + }, + { + Name: "lastIndexOf", + Func: func(args ...any) (any, error) { + return strings.LastIndex(args[0].(string), args[1].(string)), nil + }, + Types: types(strings.LastIndex), + }, + { + Name: "hasPrefix", + Func: func(args ...any) (any, error) { + return strings.HasPrefix(args[0].(string), args[1].(string)), nil + }, + Types: types(strings.HasPrefix), + }, + { + Name: "hasSuffix", + Func: func(args ...any) (any, error) { + return strings.HasSuffix(args[0].(string), args[1].(string)), nil + }, + Types: types(strings.HasSuffix), + }, + { + Name: "max", + Func: func(args ...any) (any, error) { + return minMax("max", runtime.Less, 0, args...) + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + return validateAggregateFunc("max", args) + }, + }, + { + Name: "min", + Func: func(args ...any) (any, error) { + return minMax("min", runtime.More, 0, args...) + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + return validateAggregateFunc("min", args) + }, + }, + { + Name: "mean", + Func: func(args ...any) (any, error) { + count, sum, err := mean(0, args...) + if err != nil { + return nil, err + } + if count == 0 { + return 0.0, nil + } + return sum / float64(count), nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + return validateAggregateFunc("mean", args) + }, + }, + { + Name: "median", + Func: func(args ...any) (any, error) { + values, err := median(0, args...) + if err != nil { + return nil, err + } + if n := len(values); n > 0 { + sort.Float64s(values) + if n%2 == 1 { + return values[n/2], nil + } + return (values[n/2-1] + values[n/2]) / 2, nil + } + return 0.0, nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + return validateAggregateFunc("median", args) + }, + }, + { + Name: "toJSON", + Func: func(args ...any) (any, error) { + b, err := json.MarshalIndent(args[0], "", " ") + if err != nil { + return nil, err + } + return string(b), nil + }, + Types: types(new(func(any) string)), + }, + { + Name: "fromJSON", + Func: func(args ...any) (any, error) { + var v any + err := json.Unmarshal([]byte(args[0].(string)), &v) + if err != nil { + return nil, err + } + return v, nil + }, + Types: types(new(func(string) any)), + }, + { + Name: "toBase64", + Func: func(args ...any) (any, error) { + return base64.StdEncoding.EncodeToString([]byte(args[0].(string))), nil + }, + Types: types(new(func(string) string)), + }, + { + Name: "fromBase64", + Func: func(args ...any) (any, error) { + b, err := base64.StdEncoding.DecodeString(args[0].(string)) + if err != nil { + return nil, err + } + return string(b), nil + }, + Types: types(new(func(string) string)), + }, + { + Name: "now", + Func: func(args ...any) (any, error) { + if len(args) == 0 { + return time.Now(), nil + } + if len(args) == 1 { + if tz, ok := args[0].(*time.Location); ok { + return time.Now().In(tz), nil + } + } + return nil, fmt.Errorf("invalid number of arguments (expected 0, got %d)", len(args)) + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) == 0 { + return timeType, nil + } + if len(args) == 1 { + if args[0] != nil && args[0].AssignableTo(locationType) { + return timeType, nil + } + } + return anyType, fmt.Errorf("invalid number of arguments (expected 0, got %d)", len(args)) + }, + Deref: func(i int, arg reflect.Type) bool { + return false + }, + }, + { + Name: "duration", + Func: func(args ...any) (any, error) { + return time.ParseDuration(args[0].(string)) + }, + Types: types(time.ParseDuration), + }, + { + Name: "date", + Func: func(args ...any) (any, error) { + tz, ok := args[0].(*time.Location) + if ok { + args = args[1:] + } + + date := args[0].(string) + if len(args) == 2 { + layout := args[1].(string) + if tz != nil { + return time.ParseInLocation(layout, date, tz) + } + return time.Parse(layout, date) + } + if len(args) == 3 { + layout := args[1].(string) + timeZone := args[2].(string) + tz, err := time.LoadLocation(timeZone) + if err != nil { + return nil, err + } + t, err := time.ParseInLocation(layout, date, tz) + if err != nil { + return nil, err + } + return t, nil + } + + layouts := []string{ + "2006-01-02", + "15:04:05", + "2006-01-02 15:04:05", + time.RFC3339, + time.RFC822, + time.RFC850, + time.RFC1123, + } + for _, layout := range layouts { + if tz == nil { + t, err := time.Parse(layout, date) + if err == nil { + return t, nil + } + } else { + t, err := time.ParseInLocation(layout, date, tz) + if err == nil { + return t, nil + } + } + } + return nil, fmt.Errorf("invalid date %s", date) + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) < 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected at least 1, got %d)", len(args)) + } + if args[0] != nil && args[0].AssignableTo(locationType) { + args = args[1:] + } + if len(args) > 3 { + return anyType, fmt.Errorf("invalid number of arguments (expected at most 3, got %d)", len(args)) + } + return timeType, nil + }, + Deref: func(i int, arg reflect.Type) bool { + if arg.AssignableTo(locationType) { + return false + } + return true + }, + }, + { + Name: "timezone", + Func: func(args ...any) (any, error) { + tz, err := time.LoadLocation(args[0].(string)) + if err != nil { + return nil, err + } + return tz, nil + }, + Types: types(time.LoadLocation), + }, + { + Name: "first", + Func: func(args ...any) (any, error) { + defer func() { + if r := recover(); r != nil { + return + } + }() + return runtime.Fetch(args[0], 0), nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Interface: + return anyType, nil + case reflect.Slice, reflect.Array: + return args[0].Elem(), nil + } + return anyType, fmt.Errorf("cannot get first element from %s", args[0]) + }, + }, + { + Name: "last", + Func: func(args ...any) (any, error) { + defer func() { + if r := recover(); r != nil { + return + } + }() + return runtime.Fetch(args[0], -1), nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Interface: + return anyType, nil + case reflect.Slice, reflect.Array: + return args[0].Elem(), nil + } + return anyType, fmt.Errorf("cannot get last element from %s", args[0]) + }, + }, + { + Name: "get", + Func: get, + }, + { + Name: "take", + Func: func(args ...any) (any, error) { + if len(args) != 2 { + return nil, fmt.Errorf("invalid number of arguments (expected 2, got %d)", len(args)) + } + v := reflect.ValueOf(args[0]) + if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { + return nil, fmt.Errorf("cannot take from %s", v.Kind()) + } + n := reflect.ValueOf(args[1]) + if !n.CanInt() { + return nil, fmt.Errorf("cannot take %s elements", n.Kind()) + } + to := 0 + if n.Int() > int64(v.Len()) { + to = v.Len() + } else { + to = int(n.Int()) + } + return v.Slice(0, to).Interface(), nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 2 { + return anyType, fmt.Errorf("invalid number of arguments (expected 2, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Interface, reflect.Slice, reflect.Array: + default: + return anyType, fmt.Errorf("cannot take from %s", args[0]) + } + switch kind(args[1]) { + case reflect.Interface, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + default: + return anyType, fmt.Errorf("cannot take %s elements", args[1]) + } + return args[0], nil + }, + }, + { + Name: "keys", + Func: func(args ...any) (any, error) { + if len(args) != 1 { + return nil, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + v := reflect.ValueOf(args[0]) + if v.Kind() != reflect.Map { + return nil, fmt.Errorf("cannot get keys from %s", v.Kind()) + } + keys := v.MapKeys() + out := make([]any, len(keys)) + for i, key := range keys { + out[i] = key.Interface() + } + return out, nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Interface: + return arrayType, nil + case reflect.Map: + return arrayType, nil + } + return anyType, fmt.Errorf("cannot get keys from %s", args[0]) + }, + }, + { + Name: "values", + Func: func(args ...any) (any, error) { + if len(args) != 1 { + return nil, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + v := reflect.ValueOf(args[0]) + if v.Kind() != reflect.Map { + return nil, fmt.Errorf("cannot get values from %s", v.Kind()) + } + keys := v.MapKeys() + out := make([]any, len(keys)) + for i, key := range keys { + out[i] = v.MapIndex(key).Interface() + } + return out, nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Interface: + return arrayType, nil + case reflect.Map: + return arrayType, nil + } + return anyType, fmt.Errorf("cannot get values from %s", args[0]) + }, + }, + { + Name: "toPairs", + Func: func(args ...any) (any, error) { + if len(args) != 1 { + return nil, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + v := reflect.ValueOf(args[0]) + if v.Kind() != reflect.Map { + return nil, fmt.Errorf("cannot transform %s to pairs", v.Kind()) + } + keys := v.MapKeys() + out := make([][2]any, len(keys)) + for i, key := range keys { + out[i] = [2]any{key.Interface(), v.MapIndex(key).Interface()} + } + return out, nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Interface, reflect.Map: + return arrayType, nil + } + return anyType, fmt.Errorf("cannot transform %s to pairs", args[0]) + }, + }, + { + Name: "fromPairs", + Func: func(args ...any) (any, error) { + if len(args) != 1 { + return nil, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + v := reflect.ValueOf(args[0]) + if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { + return nil, fmt.Errorf("cannot transform %s from pairs", v) + } + out := reflect.MakeMap(mapType) + for i := 0; i < v.Len(); i++ { + pair := deref.Value(v.Index(i)) + if pair.Kind() != reflect.Array && pair.Kind() != reflect.Slice { + return nil, fmt.Errorf("invalid pair %v", pair) + } + if pair.Len() != 2 { + return nil, fmt.Errorf("invalid pair length %v", pair) + } + key := pair.Index(0) + value := pair.Index(1) + out.SetMapIndex(key, value) + } + return out.Interface(), nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Interface, reflect.Slice, reflect.Array: + return mapType, nil + } + return anyType, fmt.Errorf("cannot transform %s from pairs", args[0]) + }, + }, + { + Name: "reverse", + Safe: func(args ...any) (any, uint, error) { + if len(args) != 1 { + return nil, 0, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + + v := reflect.ValueOf(args[0]) + if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { + return nil, 0, fmt.Errorf("cannot reverse %s", v.Kind()) + } + + size := v.Len() + arr := make([]any, size) + + for i := 0; i < size; i++ { + arr[i] = v.Index(size - i - 1).Interface() + } + + return arr, uint(size), nil + + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Interface, reflect.Slice, reflect.Array: + return arrayType, nil + default: + return anyType, fmt.Errorf("cannot reverse %s", args[0]) + } + }, + }, + + { + Name: "uniq", + Func: func(args ...any) (any, error) { + if len(args) != 1 { + return nil, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + + v := reflect.ValueOf(args[0]) + if v.Kind() != reflect.Array && v.Kind() != reflect.Slice { + return nil, fmt.Errorf("cannot uniq %s", v.Kind()) + } + + size := v.Len() + ret := []any{} + + eq := func(i int) bool { + for _, r := range ret { + if runtime.Equal(v.Index(i).Interface(), r) { + return true + } + } + + return false + } + + for i := 0; i < size; i += 1 { + if eq(i) { + continue + } + + ret = append(ret, v.Index(i).Interface()) + } + + return ret, nil + }, + + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + + switch kind(args[0]) { + case reflect.Interface, reflect.Slice, reflect.Array: + return arrayType, nil + default: + return anyType, fmt.Errorf("cannot uniq %s", args[0]) + } + }, + }, + + { + Name: "concat", + Safe: func(args ...any) (any, uint, error) { + if len(args) == 0 { + return nil, 0, fmt.Errorf("invalid number of arguments (expected at least 1, got 0)") + } + + var size uint + var arr []any + + for _, arg := range args { + v := reflect.ValueOf(arg) + + if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { + return nil, 0, fmt.Errorf("cannot concat %s", v.Kind()) + } + + size += uint(v.Len()) + + for i := 0; i < v.Len(); i++ { + item := v.Index(i) + arr = append(arr, item.Interface()) + } + } + + return arr, size, nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) == 0 { + return anyType, fmt.Errorf("invalid number of arguments (expected at least 1, got 0)") + } + + for _, arg := range args { + switch kind(arg) { + case reflect.Interface, reflect.Slice, reflect.Array: + default: + return anyType, fmt.Errorf("cannot concat %s", arg) + } + } + + return arrayType, nil + }, + }, + { + Name: "flatten", + Safe: func(args ...any) (any, uint, error) { + var size uint + if len(args) != 1 { + return nil, 0, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + v := reflect.ValueOf(args[0]) + if v.Kind() != reflect.Array && v.Kind() != reflect.Slice { + return nil, size, fmt.Errorf("cannot flatten %s", v.Kind()) + } + ret, err := flatten(v, 0) + if err != nil { + return nil, 0, err + } + size = uint(len(ret)) + return ret, size, nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + + for _, arg := range args { + switch kind(arg) { + case reflect.Interface, reflect.Slice, reflect.Array: + default: + return anyType, fmt.Errorf("cannot flatten %s", arg) + } + } + + return arrayType, nil + }, + }, + { + Name: "sort", + Safe: func(args ...any) (any, uint, error) { + if len(args) != 1 && len(args) != 2 { + return nil, 0, fmt.Errorf("invalid number of arguments (expected 1 or 2, got %d)", len(args)) + } + + var array []any + + switch in := args[0].(type) { + case []any: + array = make([]any, len(in)) + copy(array, in) + case []int: + array = make([]any, len(in)) + for i, v := range in { + array[i] = v + } + case []float64: + array = make([]any, len(in)) + for i, v := range in { + array[i] = v + } + case []string: + array = make([]any, len(in)) + for i, v := range in { + array[i] = v + } + } + + var desc bool + if len(args) == 2 { + order, ok := args[1].(string) + if !ok { + return nil, 0, fmt.Errorf("sort order argument must be a string (got %T)", args[1]) + } + switch order { + case "asc": + desc = false + case "desc": + desc = true + default: + return nil, 0, fmt.Errorf("invalid order %s, expected asc or desc", order) + } + } + + sortable := &runtime.Sort{ + Desc: desc, + Array: array, + } + sort.Sort(sortable) + + return sortable.Array, uint(len(array)), nil + }, + Types: types( + new(func([]any, string) []any), + new(func([]int, string) []any), + new(func([]float64, string) []any), + new(func([]string, string) []any), + + new(func([]any) []any), + new(func([]float64) []any), + new(func([]string) []any), + new(func([]int) []any), + ), + }, + bitFunc("bitand", func(x, y int) (any, error) { + return x & y, nil + }), + bitFunc("bitor", func(x, y int) (any, error) { + return x | y, nil + }), + bitFunc("bitxor", func(x, y int) (any, error) { + return x ^ y, nil + }), + bitFunc("bitnand", func(x, y int) (any, error) { + return x &^ y, nil + }), + bitFunc("bitshl", func(x, y int) (any, error) { + if y < 0 { + return nil, fmt.Errorf("invalid operation: negative shift count %d (type int)", y) + } + return x << y, nil + }), + bitFunc("bitshr", func(x, y int) (any, error) { + if y < 0 { + return nil, fmt.Errorf("invalid operation: negative shift count %d (type int)", y) + } + return x >> y, nil + }), + bitFunc("bitushr", func(x, y int) (any, error) { + if y < 0 { + return nil, fmt.Errorf("invalid operation: negative shift count %d (type int)", y) + } + return int(uint(x) >> y), nil + }), + { + Name: "bitnot", + Func: func(args ...any) (any, error) { + if len(args) != 1 { + return nil, fmt.Errorf("invalid number of arguments for bitnot (expected 1, got %d)", len(args)) + } + x, err := toInt(args[0]) + if err != nil { + return nil, fmt.Errorf("%v to call bitnot", err) + } + return ^x, nil + }, + Types: types(new(func(int) int)), + }, +} diff --git a/vendor/github.com/expr-lang/expr/builtin/function.go b/vendor/github.com/expr-lang/expr/builtin/function.go new file mode 100644 index 00000000000..6634ac3f89d --- /dev/null +++ b/vendor/github.com/expr-lang/expr/builtin/function.go @@ -0,0 +1,23 @@ +package builtin + +import ( + "reflect" +) + +type Function struct { + Name string + Fast func(arg any) any + Func func(args ...any) (any, error) + Safe func(args ...any) (any, uint, error) + Types []reflect.Type + Validate func(args []reflect.Type) (reflect.Type, error) + Deref func(i int, arg reflect.Type) bool + Predicate bool +} + +func (f *Function) Type() reflect.Type { + if len(f.Types) > 0 { + return f.Types[0] + } + return reflect.TypeOf(f.Func) +} diff --git a/vendor/github.com/expr-lang/expr/builtin/lib.go b/vendor/github.com/expr-lang/expr/builtin/lib.go new file mode 100644 index 00000000000..d626f944bce --- /dev/null +++ b/vendor/github.com/expr-lang/expr/builtin/lib.go @@ -0,0 +1,618 @@ +package builtin + +import ( + "fmt" + "math" + "reflect" + "strconv" + "unicode/utf8" + + "github.com/expr-lang/expr/internal/deref" + "github.com/expr-lang/expr/vm/runtime" +) + +func Len(x any) any { + v := reflect.ValueOf(x) + switch v.Kind() { + case reflect.Array, reflect.Slice, reflect.Map: + return v.Len() + case reflect.String: + return utf8.RuneCountInString(v.String()) + default: + panic(fmt.Sprintf("invalid argument for len (type %T)", x)) + } +} + +func Type(arg any) any { + if arg == nil { + return "nil" + } + v := reflect.ValueOf(arg) + if v.Type().Name() != "" && v.Type().PkgPath() != "" { + return fmt.Sprintf("%s.%s", v.Type().PkgPath(), v.Type().Name()) + } + switch v.Type().Kind() { + case reflect.Invalid: + return "invalid" + case reflect.Bool: + return "bool" + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return "int" + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return "uint" + case reflect.Float32, reflect.Float64: + return "float" + case reflect.String: + return "string" + case reflect.Array, reflect.Slice: + return "array" + case reflect.Map: + return "map" + case reflect.Func: + return "func" + case reflect.Struct: + return "struct" + default: + return "unknown" + } +} + +func Abs(x any) any { + switch x := x.(type) { + case float32: + if x < 0 { + return -x + } else { + return x + } + case float64: + if x < 0 { + return -x + } else { + return x + } + case int: + if x < 0 { + return -x + } else { + return x + } + case int8: + if x < 0 { + return -x + } else { + return x + } + case int16: + if x < 0 { + return -x + } else { + return x + } + case int32: + if x < 0 { + return -x + } else { + return x + } + case int64: + if x < 0 { + return -x + } else { + return x + } + case uint: + return x + case uint8: + return x + case uint16: + return x + case uint32: + return x + case uint64: + return x + } + panic(fmt.Sprintf("invalid argument for abs (type %T)", x)) +} + +func Ceil(x any) any { + switch x := x.(type) { + case float32: + return math.Ceil(float64(x)) + case float64: + return math.Ceil(x) + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + return Float(x) + } + panic(fmt.Sprintf("invalid argument for ceil (type %T)", x)) +} + +func Floor(x any) any { + switch x := x.(type) { + case float32: + return math.Floor(float64(x)) + case float64: + return math.Floor(x) + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + return Float(x) + } + panic(fmt.Sprintf("invalid argument for floor (type %T)", x)) +} + +func Round(x any) any { + switch x := x.(type) { + case float32: + return math.Round(float64(x)) + case float64: + return math.Round(x) + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + return Float(x) + } + panic(fmt.Sprintf("invalid argument for round (type %T)", x)) +} + +func Int(x any) any { + switch x := x.(type) { + case float32: + return int(x) + case float64: + return int(x) + case int: + return x + case int8: + return int(x) + case int16: + return int(x) + case int32: + return int(x) + case int64: + return int(x) + case uint: + return int(x) + case uint8: + return int(x) + case uint16: + return int(x) + case uint32: + return int(x) + case uint64: + return int(x) + case string: + i, err := strconv.Atoi(x) + if err != nil { + panic(fmt.Sprintf("invalid operation: int(%s)", x)) + } + return i + default: + val := reflect.ValueOf(x) + if val.CanConvert(integerType) { + return val.Convert(integerType).Interface() + } + panic(fmt.Sprintf("invalid operation: int(%T)", x)) + } +} + +func Float(x any) any { + switch x := x.(type) { + case float32: + return float64(x) + case float64: + return x + case int: + return float64(x) + case int8: + return float64(x) + case int16: + return float64(x) + case int32: + return float64(x) + case int64: + return float64(x) + case uint: + return float64(x) + case uint8: + return float64(x) + case uint16: + return float64(x) + case uint32: + return float64(x) + case uint64: + return float64(x) + case string: + f, err := strconv.ParseFloat(x, 64) + if err != nil { + panic(fmt.Sprintf("invalid operation: float(%s)", x)) + } + return f + default: + panic(fmt.Sprintf("invalid operation: float(%T)", x)) + } +} + +func String(arg any) any { + return fmt.Sprintf("%v", arg) +} + +func minMax(name string, fn func(any, any) bool, depth int, args ...any) (any, error) { + if depth > MaxDepth { + return nil, ErrorMaxDepth + } + var val any + for _, arg := range args { + // Fast paths for common typed slices - avoid reflection and allocations + switch arr := arg.(type) { + case []int: + if len(arr) == 0 { + continue + } + m := arr[0] + for i := 1; i < len(arr); i++ { + if fn(m, arr[i]) { + m = arr[i] + } + } + if val == nil || fn(val, m) { + val = m + } + continue + case []float64: + if len(arr) == 0 { + continue + } + m := arr[0] + for i := 1; i < len(arr); i++ { + if fn(m, arr[i]) { + m = arr[i] + } + } + if val == nil || fn(val, m) { + val = m + } + continue + case []any: + // Fast path for []any with simple numeric types + for _, elem := range arr { + switch e := elem.(type) { + case int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, + float32, float64: + if val == nil || fn(val, e) { + val = e + } + case []int, []float64, []any: + // Nested array - recurse + nested, err := minMax(name, fn, depth+1, e) + if err != nil { + return nil, err + } + if nested != nil && (val == nil || fn(val, nested)) { + val = nested + } + default: + // Could be another slice type, use reflection + rv := reflect.ValueOf(e) + if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array { + nested, err := minMax(name, fn, depth+1, e) + if err != nil { + return nil, err + } + if nested != nil && (val == nil || fn(val, nested)) { + val = nested + } + } else { + return nil, fmt.Errorf("invalid argument for %s (type %T)", name, e) + } + } + } + continue + } + + // Slow path: use reflection for other types + rv := reflect.ValueOf(arg) + switch rv.Kind() { + case reflect.Array, reflect.Slice: + size := rv.Len() + for i := 0; i < size; i++ { + elemVal, err := minMax(name, fn, depth+1, rv.Index(i).Interface()) + if err != nil { + return nil, err + } + switch elemVal.(type) { + case int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, + float32, float64: + if elemVal != nil && (val == nil || fn(val, elemVal)) { + val = elemVal + } + default: + return nil, fmt.Errorf("invalid argument for %s (type %T)", name, elemVal) + } + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + elemVal := rv.Interface() + if val == nil || fn(val, elemVal) { + val = elemVal + } + default: + if len(args) == 1 { + return args[0], nil + } + return nil, fmt.Errorf("invalid argument for %s (type %T)", name, arg) + } + } + return val, nil +} + +func mean(depth int, args ...any) (int, float64, error) { + if depth > MaxDepth { + return 0, 0, ErrorMaxDepth + } + var total float64 + var count int + + for _, arg := range args { + // Fast paths for common typed slices - avoid reflection and allocations + switch arr := arg.(type) { + case []int: + for _, v := range arr { + total += float64(v) + } + count += len(arr) + continue + case []float64: + for _, v := range arr { + total += v + } + count += len(arr) + continue + case []any: + // Fast path for []any - single pass without recursive calls for flat arrays + for _, elem := range arr { + switch e := elem.(type) { + case int: + total += float64(e) + count++ + case float64: + total += e + count++ + case []int, []float64, []any: + // Nested array - recurse + nestedCount, nestedSum, err := mean(depth+1, e) + if err != nil { + return 0, 0, err + } + total += nestedSum + count += nestedCount + default: + // Other numeric types or slices - use reflection + rv := reflect.ValueOf(e) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + total += float64(rv.Int()) + count++ + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + total += float64(rv.Uint()) + count++ + case reflect.Float32, reflect.Float64: + total += rv.Float() + count++ + case reflect.Slice, reflect.Array: + nestedCount, nestedSum, err := mean(depth+1, e) + if err != nil { + return 0, 0, err + } + total += nestedSum + count += nestedCount + default: + return 0, 0, fmt.Errorf("invalid argument for mean (type %T)", e) + } + } + } + continue + } + + // Slow path: use reflection for other types + rv := reflect.ValueOf(arg) + switch rv.Kind() { + case reflect.Array, reflect.Slice: + size := rv.Len() + for i := 0; i < size; i++ { + elemCount, elemSum, err := mean(depth+1, rv.Index(i).Interface()) + if err != nil { + return 0, 0, err + } + total += elemSum + count += elemCount + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + total += float64(rv.Int()) + count++ + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + total += float64(rv.Uint()) + count++ + case reflect.Float32, reflect.Float64: + total += rv.Float() + count++ + default: + return 0, 0, fmt.Errorf("invalid argument for mean (type %T)", arg) + } + } + return count, total, nil +} + +func median(depth int, args ...any) ([]float64, error) { + if depth > MaxDepth { + return nil, ErrorMaxDepth + } + var values []float64 + + for _, arg := range args { + // Fast paths for common typed slices - avoid reflection and allocations + switch arr := arg.(type) { + case []int: + for _, v := range arr { + values = append(values, float64(v)) + } + continue + case []float64: + values = append(values, arr...) + continue + case []any: + // Fast path for []any - single pass without recursive calls for flat arrays + for _, elem := range arr { + switch e := elem.(type) { + case int: + values = append(values, float64(e)) + case float64: + values = append(values, e) + case []int, []float64, []any: + // Nested array - recurse + elems, err := median(depth+1, e) + if err != nil { + return nil, err + } + values = append(values, elems...) + default: + // Other numeric types or slices - use reflection + rv := reflect.ValueOf(e) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + values = append(values, float64(rv.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + values = append(values, float64(rv.Uint())) + case reflect.Float32, reflect.Float64: + values = append(values, rv.Float()) + case reflect.Slice, reflect.Array: + elems, err := median(depth+1, e) + if err != nil { + return nil, err + } + values = append(values, elems...) + default: + return nil, fmt.Errorf("invalid argument for median (type %T)", e) + } + } + } + continue + } + + // Slow path: use reflection for other types + rv := reflect.ValueOf(arg) + switch rv.Kind() { + case reflect.Array, reflect.Slice: + size := rv.Len() + for i := 0; i < size; i++ { + elems, err := median(depth+1, rv.Index(i).Interface()) + if err != nil { + return nil, err + } + values = append(values, elems...) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + values = append(values, float64(rv.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + values = append(values, float64(rv.Uint())) + case reflect.Float32, reflect.Float64: + values = append(values, rv.Float()) + default: + return nil, fmt.Errorf("invalid argument for median (type %T)", arg) + } + } + return values, nil +} + +func flatten(arg reflect.Value, depth int) ([]any, error) { + if depth > MaxDepth { + return nil, ErrorMaxDepth + } + ret := []any{} + for i := 0; i < arg.Len(); i++ { + v := deref.Value(arg.Index(i)) + if v.Kind() == reflect.Array || v.Kind() == reflect.Slice { + x, err := flatten(v, depth+1) + if err != nil { + return nil, err + } + ret = append(ret, x...) + } else { + ret = append(ret, v.Interface()) + } + } + return ret, nil +} + +func get(params ...any) (out any, err error) { + if len(params) < 2 { + return nil, fmt.Errorf("invalid number of arguments (expected 2, got %d)", len(params)) + } + from := params[0] + i := params[1] + v := reflect.ValueOf(from) + + if from == nil { + return nil, nil + } + + if v.Kind() == reflect.Invalid { + panic(fmt.Sprintf("cannot fetch %v from %T", i, from)) + } + + // Methods can be defined on any type. + if v.NumMethod() > 0 { + if methodName, ok := i.(string); ok { + method := v.MethodByName(methodName) + if method.IsValid() { + return method.Interface(), nil + } + } + } + + switch v.Kind() { + case reflect.Array, reflect.Slice, reflect.String: + index := runtime.ToInt(i) + l := v.Len() + if index < 0 { + index = l + index + } + if 0 <= index && index < l { + value := v.Index(index) + if value.IsValid() { + return value.Interface(), nil + } + } + + case reflect.Map: + var value reflect.Value + if i == nil { + value = v.MapIndex(reflect.Zero(v.Type().Key())) + } else { + value = v.MapIndex(reflect.ValueOf(i)) + } + if value.IsValid() { + return value.Interface(), nil + } + + case reflect.Struct: + fieldName := i.(string) + value := v.FieldByNameFunc(func(name string) bool { + field, _ := v.Type().FieldByName(name) + switch field.Tag.Get("expr") { + case "-": + return false + case fieldName: + return true + default: + return name == fieldName + } + }) + if value.IsValid() { + return value.Interface(), nil + } + } + + // Main difference from runtime.Fetch + // is that we return `nil` instead of panic. + return nil, nil +} diff --git a/vendor/github.com/expr-lang/expr/builtin/utils.go b/vendor/github.com/expr-lang/expr/builtin/utils.go new file mode 100644 index 00000000000..262bb379d94 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/builtin/utils.go @@ -0,0 +1,90 @@ +package builtin + +import ( + "fmt" + "reflect" + "time" + + "github.com/expr-lang/expr/internal/deref" +) + +var ( + anyType = reflect.TypeOf(new(any)).Elem() + integerType = reflect.TypeOf(0) + floatType = reflect.TypeOf(float64(0)) + arrayType = reflect.TypeOf([]any{}) + mapType = reflect.TypeOf(map[any]any{}) + timeType = reflect.TypeOf(new(time.Time)).Elem() + locationType = reflect.TypeOf(new(time.Location)) +) + +func kind(t reflect.Type) reflect.Kind { + if t == nil { + return reflect.Invalid + } + t = deref.Type(t) + return t.Kind() +} + +func types(types ...any) []reflect.Type { + ts := make([]reflect.Type, len(types)) + for i, t := range types { + t := reflect.TypeOf(t) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() != reflect.Func { + panic("not a function") + } + ts[i] = t + } + return ts +} + +func toInt(val any) (int, error) { + switch v := val.(type) { + case int: + return v, nil + case int8: + return int(v), nil + case int16: + return int(v), nil + case int32: + return int(v), nil + case int64: + return int(v), nil + case uint: + return int(v), nil + case uint8: + return int(v), nil + case uint16: + return int(v), nil + case uint32: + return int(v), nil + case uint64: + return int(v), nil + default: + return 0, fmt.Errorf("cannot use %T as argument (type int)", val) + } +} + +func bitFunc(name string, fn func(x, y int) (any, error)) *Function { + return &Function{ + Name: name, + Func: func(args ...any) (any, error) { + if len(args) != 2 { + return nil, fmt.Errorf("invalid number of arguments for %s (expected 2, got %d)", name, len(args)) + } + x, err := toInt(args[0]) + if err != nil { + return nil, fmt.Errorf("%v to call %s", err, name) + } + y, err := toInt(args[1]) + if err != nil { + return nil, fmt.Errorf("%v to call %s", err, name) + } + return fn(x, y) + }, + Types: types(new(func(int, int) int)), + } +} diff --git a/vendor/github.com/expr-lang/expr/builtin/validation.go b/vendor/github.com/expr-lang/expr/builtin/validation.go new file mode 100644 index 00000000000..057f247e914 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/builtin/validation.go @@ -0,0 +1,38 @@ +package builtin + +import ( + "fmt" + "reflect" + + "github.com/expr-lang/expr/internal/deref" +) + +func validateAggregateFunc(name string, args []reflect.Type) (reflect.Type, error) { + switch len(args) { + case 0: + return anyType, fmt.Errorf("not enough arguments to call %s", name) + default: + for _, arg := range args { + switch kind(deref.Type(arg)) { + case reflect.Interface, reflect.Array, reflect.Slice: + return anyType, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: + default: + return anyType, fmt.Errorf("invalid argument for %s (type %s)", name, arg) + } + } + return args[0], nil + } +} + +func validateRoundFunc(name string, args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + switch kind(args[0]) { + case reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Interface: + return floatType, nil + default: + return anyType, fmt.Errorf("invalid argument for %s (type %s)", name, args[0]) + } +} diff --git a/vendor/github.com/expr-lang/expr/checker/checker.go b/vendor/github.com/expr-lang/expr/checker/checker.go new file mode 100644 index 00000000000..3620f20751e --- /dev/null +++ b/vendor/github.com/expr-lang/expr/checker/checker.go @@ -0,0 +1,1344 @@ +package checker + +import ( + "fmt" + "reflect" + "regexp" + "time" + + "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/builtin" + . "github.com/expr-lang/expr/checker/nature" + "github.com/expr-lang/expr/conf" + "github.com/expr-lang/expr/file" + "github.com/expr-lang/expr/parser" +) + +var ( + anyType = reflect.TypeOf(new(any)).Elem() + boolType = reflect.TypeOf(true) + intType = reflect.TypeOf(0) + floatType = reflect.TypeOf(float64(0)) + stringType = reflect.TypeOf("") + arrayType = reflect.TypeOf([]any{}) + mapType = reflect.TypeOf(map[string]any{}) + timeType = reflect.TypeOf(time.Time{}) + durationType = reflect.TypeOf(time.Duration(0)) + byteSliceType = reflect.TypeOf([]byte(nil)) + + anyTypeSlice = []reflect.Type{anyType} +) + +// ParseCheck parses input expression and checks its types. Also, it applies +// all provided patchers. In case of error, it returns error with a tree. +func ParseCheck(input string, config *conf.Config) (*parser.Tree, error) { + tree, err := parser.ParseWithConfig(input, config) + if err != nil { + return tree, err + } + + _, err = new(Checker).PatchAndCheck(tree, config) + if err != nil { + return tree, err + } + + return tree, nil +} + +// Check calls Check on a disposable Checker. +func Check(tree *parser.Tree, config *conf.Config) (reflect.Type, error) { + return new(Checker).Check(tree, config) +} + +type Checker struct { + config *conf.Config + predicateScopes []predicateScope + varScopes []varScope + err *file.Error + needsReset bool +} + +type predicateScope struct { + collection Nature + vars []varScope +} + +type varScope struct { + name string + nature Nature +} + +// PatchAndCheck applies all patchers and checks the tree. +func (v *Checker) PatchAndCheck(tree *parser.Tree, config *conf.Config) (reflect.Type, error) { + v.reset(config) + if len(config.Visitors) > 0 { + // Run all patchers that dont support being run repeatedly first + v.runVisitors(tree, false) + + // Run patchers that require multiple passes next (currently only Operator patching) + v.runVisitors(tree, true) + } + return v.Check(tree, config) +} + +// Check checks types of the expression tree. It returns type of the expression +// and error if any. If config is nil, then default configuration will be used. +func (v *Checker) Check(tree *parser.Tree, config *conf.Config) (reflect.Type, error) { + v.reset(config) + return v.check(tree) +} + +// Run visitors in a given config over the given tree +// runRepeatable controls whether to filter for only vistors that require multiple passes or not +func (v *Checker) runVisitors(tree *parser.Tree, runRepeatable bool) { + for { + more := false + for _, visitor := range v.config.Visitors { + // We need to perform types check, because some visitors may rely on + // types information available in the tree. + _, _ = v.Check(tree, v.config) + + r, repeatable := visitor.(interface { + Reset() + ShouldRepeat() bool + }) + + if repeatable { + if runRepeatable { + r.Reset() + ast.Walk(&tree.Node, visitor) + more = more || r.ShouldRepeat() + } + } else { + if !runRepeatable { + ast.Walk(&tree.Node, visitor) + } + } + } + + if !more { + break + } + } +} + +func (v *Checker) check(tree *parser.Tree) (reflect.Type, error) { + nt := v.visit(tree.Node) + + // To keep compatibility with previous versions, we should return any, if nature is unknown. + t := nt.Type + if t == nil { + t = anyType + } + + if v.err != nil { + return t, v.err.Bind(tree.Source) + } + + if v.config.Expect != reflect.Invalid { + if v.config.ExpectAny { + if nt.IsUnknown(&v.config.NtCache) { + return t, nil + } + } + + switch v.config.Expect { + case reflect.Int, reflect.Int64, reflect.Float64: + if !nt.IsNumber() { + return nil, fmt.Errorf("expected %v, but got %s", v.config.Expect, nt.String()) + } + default: + if nt.Kind != v.config.Expect { + return nil, fmt.Errorf("expected %v, but got %s", v.config.Expect, nt.String()) + } + } + } + + return t, nil +} + +func (v *Checker) reset(config *conf.Config) { + if v.needsReset { + clearSlice(v.predicateScopes) + clearSlice(v.varScopes) + v.predicateScopes = v.predicateScopes[:0] + v.varScopes = v.varScopes[:0] + v.err = nil + } + v.needsReset = true + + if config == nil { + config = conf.New(nil) + } + v.config = config +} + +func clearSlice[S ~[]E, E any](s S) { + var zero E + for i := range s { + s[i] = zero + } +} + +func (v *Checker) visit(node ast.Node) Nature { + var nt Nature + switch n := node.(type) { + case *ast.NilNode: + nt = v.config.NtCache.NatureOf(nil) + case *ast.IdentifierNode: + nt = v.identifierNode(n) + case *ast.IntegerNode: + nt = v.config.NtCache.FromType(intType) + case *ast.FloatNode: + nt = v.config.NtCache.FromType(floatType) + case *ast.BoolNode: + nt = v.config.NtCache.FromType(boolType) + case *ast.StringNode: + nt = v.config.NtCache.FromType(stringType) + case *ast.BytesNode: + nt = v.config.NtCache.FromType(byteSliceType) + case *ast.ConstantNode: + nt = v.config.NtCache.FromType(reflect.TypeOf(n.Value)) + case *ast.UnaryNode: + nt = v.unaryNode(n) + case *ast.BinaryNode: + nt = v.binaryNode(n) + case *ast.ChainNode: + nt = v.chainNode(n) + case *ast.MemberNode: + nt = v.memberNode(n) + case *ast.SliceNode: + nt = v.sliceNode(n) + case *ast.CallNode: + nt = v.callNode(n) + case *ast.BuiltinNode: + nt = v.builtinNode(n) + case *ast.PredicateNode: + nt = v.predicateNode(n) + case *ast.PointerNode: + nt = v.pointerNode(n) + case *ast.VariableDeclaratorNode: + nt = v.variableDeclaratorNode(n) + case *ast.SequenceNode: + nt = v.sequenceNode(n) + case *ast.ConditionalNode: + nt = v.conditionalNode(n) + case *ast.ArrayNode: + nt = v.arrayNode(n) + case *ast.MapNode: + nt = v.mapNode(n) + case *ast.PairNode: + nt = v.pairNode(n) + default: + panic(fmt.Sprintf("undefined node type (%T)", node)) + } + node.SetNature(nt) + return nt +} + +func (v *Checker) error(node ast.Node, format string, args ...any) Nature { + if v.err == nil { // show first error + v.err = &file.Error{ + Location: node.Location(), + Message: fmt.Sprintf(format, args...), + } + } + return Nature{} +} + +func (v *Checker) identifierNode(node *ast.IdentifierNode) Nature { + for i := len(v.varScopes) - 1; i >= 0; i-- { + if v.varScopes[i].name == node.Value { + return v.varScopes[i].nature + } + } + if node.Value == "$env" { + return Nature{} + } + + return v.ident(node, node.Value, v.config.Strict, true) +} + +// ident method returns type of environment variable, builtin or function. +func (v *Checker) ident(node ast.Node, name string, strict, builtins bool) Nature { + if nt, ok := v.config.Env.Get(&v.config.NtCache, name); ok { + return nt + } + if builtins { + if fn, ok := v.config.Functions[name]; ok { + nt := v.config.NtCache.FromType(fn.Type()) + if nt.TypeData == nil { + nt.TypeData = new(TypeData) + } + nt.TypeData.Func = fn + return nt + } + if fn, ok := v.config.Builtins[name]; ok { + nt := v.config.NtCache.FromType(fn.Type()) + if nt.TypeData == nil { + nt.TypeData = new(TypeData) + } + nt.TypeData.Func = fn + return nt + } + } + if v.config.Strict && strict { + return v.error(node, "unknown name %s", name) + } + return Nature{} +} + +func (v *Checker) unaryNode(node *ast.UnaryNode) Nature { + nt := v.visit(node.Node) + nt = nt.Deref(&v.config.NtCache) + + switch node.Operator { + + case "!", "not": + if nt.IsBool() { + return v.config.NtCache.FromType(boolType) + } + if nt.IsUnknown(&v.config.NtCache) { + return v.config.NtCache.FromType(boolType) + } + + case "+", "-": + if nt.IsNumber() { + return nt + } + if nt.IsUnknown(&v.config.NtCache) { + return Nature{} + } + + default: + return v.error(node, "unknown operator (%s)", node.Operator) + } + + return v.error(node, `invalid operation: %s (mismatched type %s)`, node.Operator, nt.String()) +} + +func (v *Checker) binaryNode(node *ast.BinaryNode) Nature { + l := v.visit(node.Left) + r := v.visit(node.Right) + + l = l.Deref(&v.config.NtCache) + r = r.Deref(&v.config.NtCache) + + switch node.Operator { + case "==", "!=": + if l.ComparableTo(&v.config.NtCache, r) { + return v.config.NtCache.FromType(boolType) + } + + case "or", "||", "and", "&&": + if l.IsBool() && r.IsBool() { + return v.config.NtCache.FromType(boolType) + } + if l.MaybeCompatible(&v.config.NtCache, r, BoolCheck) { + return v.config.NtCache.FromType(boolType) + } + + case "<", ">", ">=", "<=": + if l.IsNumber() && r.IsNumber() { + return v.config.NtCache.FromType(boolType) + } + if l.IsString() && r.IsString() { + return v.config.NtCache.FromType(boolType) + } + if l.IsTime() && r.IsTime() { + return v.config.NtCache.FromType(boolType) + } + if l.IsDuration() && r.IsDuration() { + return v.config.NtCache.FromType(boolType) + } + if l.MaybeCompatible(&v.config.NtCache, r, NumberCheck, StringCheck, TimeCheck, DurationCheck) { + return v.config.NtCache.FromType(boolType) + } + + case "-": + if l.IsNumber() && r.IsNumber() { + return l.PromoteNumericNature(&v.config.NtCache, r) + } + if l.IsTime() && r.IsTime() { + return v.config.NtCache.FromType(durationType) + } + if l.IsTime() && r.IsDuration() { + return v.config.NtCache.FromType(timeType) + } + if l.IsDuration() && r.IsDuration() { + return v.config.NtCache.FromType(durationType) + } + if l.MaybeCompatible(&v.config.NtCache, r, NumberCheck, TimeCheck, DurationCheck) { + return Nature{} + } + + case "*": + if l.IsNumber() && r.IsNumber() { + return l.PromoteNumericNature(&v.config.NtCache, r) + } + if l.IsNumber() && r.IsDuration() { + return v.config.NtCache.FromType(durationType) + } + if l.IsDuration() && r.IsNumber() { + return v.config.NtCache.FromType(durationType) + } + if l.IsDuration() && r.IsDuration() { + return v.config.NtCache.FromType(durationType) + } + if l.MaybeCompatible(&v.config.NtCache, r, NumberCheck, DurationCheck) { + return Nature{} + } + + case "/": + if l.IsNumber() && r.IsNumber() { + return v.config.NtCache.FromType(floatType) + } + if l.MaybeCompatible(&v.config.NtCache, r, NumberCheck) { + return v.config.NtCache.FromType(floatType) + } + + case "**", "^": + if l.IsNumber() && r.IsNumber() { + return v.config.NtCache.FromType(floatType) + } + if l.MaybeCompatible(&v.config.NtCache, r, NumberCheck) { + return v.config.NtCache.FromType(floatType) + } + + case "%": + if l.IsInteger && r.IsInteger { + return v.config.NtCache.FromType(intType) + } + if l.MaybeCompatible(&v.config.NtCache, r, IntegerCheck) { + return v.config.NtCache.FromType(intType) + } + + case "+": + if l.IsNumber() && r.IsNumber() { + return l.PromoteNumericNature(&v.config.NtCache, r) + } + if l.IsString() && r.IsString() { + return v.config.NtCache.FromType(stringType) + } + if l.IsTime() && r.IsDuration() { + return v.config.NtCache.FromType(timeType) + } + if l.IsDuration() && r.IsTime() { + return v.config.NtCache.FromType(timeType) + } + if l.IsDuration() && r.IsDuration() { + return v.config.NtCache.FromType(durationType) + } + if l.MaybeCompatible(&v.config.NtCache, r, NumberCheck, StringCheck, TimeCheck, DurationCheck) { + return Nature{} + } + + case "in": + if (l.IsString() || l.IsUnknown(&v.config.NtCache)) && r.IsStruct() { + return v.config.NtCache.FromType(boolType) + } + if r.IsMap() { + rKey := r.Key(&v.config.NtCache) + if !l.IsUnknown(&v.config.NtCache) && !l.AssignableTo(rKey) { + return v.error(node, "cannot use %s as type %s in map key", l.String(), rKey.String()) + } + return v.config.NtCache.FromType(boolType) + } + if r.IsArray() { + rElem := r.Elem(&v.config.NtCache) + if !l.ComparableTo(&v.config.NtCache, rElem) { + return v.error(node, "cannot use %s as type %s in array", l.String(), rElem.String()) + } + return v.config.NtCache.FromType(boolType) + } + if l.IsUnknown(&v.config.NtCache) && r.IsAnyOf(StringCheck, ArrayCheck, MapCheck) { + return v.config.NtCache.FromType(boolType) + } + if r.IsUnknown(&v.config.NtCache) { + return v.config.NtCache.FromType(boolType) + } + + case "matches": + if s, ok := node.Right.(*ast.StringNode); ok { + _, err := regexp.Compile(s.Value) + if err != nil { + return v.error(node, err.Error()) + } + } + if (l.IsString() || l.IsByteSlice()) && r.IsString() { + return v.config.NtCache.FromType(boolType) + } + if l.MaybeCompatible(&v.config.NtCache, r, StringCheck) { + return v.config.NtCache.FromType(boolType) + } + + case "contains", "startsWith", "endsWith": + if l.IsString() && r.IsString() { + return v.config.NtCache.FromType(boolType) + } + if l.MaybeCompatible(&v.config.NtCache, r, StringCheck) { + return v.config.NtCache.FromType(boolType) + } + + case "..": + if l.IsInteger && r.IsInteger || l.MaybeCompatible(&v.config.NtCache, r, IntegerCheck) { + return ArrayFromType(&v.config.NtCache, intType) + } + + case "??": + if l.Nil && !r.Nil { + return r + } + if !l.Nil && r.Nil { + return l + } + if l.Nil && r.Nil { + return v.config.NtCache.NatureOf(nil) + } + if r.AssignableTo(l) { + return l + } + return Nature{} + + default: + return v.error(node, "unknown operator (%s)", node.Operator) + + } + + return v.error(node, `invalid operation: %s (mismatched types %s and %s)`, node.Operator, l.String(), r.String()) +} + +func (v *Checker) chainNode(node *ast.ChainNode) Nature { + return v.visit(node.Node) +} + +func (v *Checker) memberNode(node *ast.MemberNode) Nature { + // $env variable + if an, ok := node.Node.(*ast.IdentifierNode); ok && an.Value == "$env" { + if name, ok := node.Property.(*ast.StringNode); ok { + strict := v.config.Strict + if node.Optional { + // If user explicitly set optional flag, then we should not + // throw error if field is not found (as user trying to handle + // this case). But if user did not set optional flag, then we + // should throw error if field is not found & v.config.Strict. + strict = false + } + return v.ident(node, name.Value, strict, false /* no builtins and no functions */) + } + return Nature{} + } + + base := v.visit(node.Node) + prop := v.visit(node.Property) + + if base.IsUnknown(&v.config.NtCache) { + return Nature{} + } + + if name, ok := node.Property.(*ast.StringNode); ok { + if base.Nil { + return v.error(node, "type nil has no field %s", name.Value) + } + + // First, check methods defined on base type itself, + // independent of which type it is. Without dereferencing. + if m, ok := base.MethodByName(&v.config.NtCache, name.Value); ok { + return m + } + } + + base = base.Deref(&v.config.NtCache) + + switch base.Kind { + case reflect.Map: + // If the map key is a pointer, we should not dereference the property. + if !prop.AssignableTo(base.Key(&v.config.NtCache)) { + propDeref := prop.Deref(&v.config.NtCache) + if propDeref.AssignableTo(base.Key(&v.config.NtCache)) { + prop = propDeref + } + } + if !prop.AssignableTo(base.Key(&v.config.NtCache)) && !prop.IsUnknown(&v.config.NtCache) { + return v.error(node.Property, "cannot use %s to get an element from %s", prop.String(), base.String()) + } + if prop, ok := node.Property.(*ast.StringNode); ok && base.TypeData != nil { + if field, ok := base.Fields[prop.Value]; ok { + return field + } else if base.Strict { + return v.error(node.Property, "unknown field %s", prop.Value) + } + } + return base.Elem(&v.config.NtCache) + + case reflect.Array, reflect.Slice: + prop = prop.Deref(&v.config.NtCache) + if !prop.IsInteger && !prop.IsUnknown(&v.config.NtCache) { + return v.error(node.Property, "array elements can only be selected using an integer (got %s)", prop.String()) + } + return base.Elem(&v.config.NtCache) + + case reflect.Struct: + if name, ok := node.Property.(*ast.StringNode); ok { + propertyName := name.Value + if field, ok := base.FieldByName(&v.config.NtCache, propertyName); ok { + return v.config.NtCache.FromType(field.Type) + } + if node.Method { + return v.error(node, "type %v has no method %v", base.String(), propertyName) + } + return v.error(node, "type %v has no field %v", base.String(), propertyName) + } + } + + // Not found. + + if name, ok := node.Property.(*ast.StringNode); ok { + if node.Method { + return v.error(node, "type %v has no method %v", base.String(), name.Value) + } + return v.error(node, "type %v has no field %v", base.String(), name.Value) + } + return v.error(node, "type %v[%v] is undefined", base.String(), prop.String()) +} + +func (v *Checker) sliceNode(node *ast.SliceNode) Nature { + nt := v.visit(node.Node) + + if nt.IsUnknown(&v.config.NtCache) { + return Nature{} + } + + switch nt.Kind { + case reflect.String, reflect.Array, reflect.Slice: + // ok + default: + return v.error(node, "cannot slice %s", nt.String()) + } + + if node.From != nil { + from := v.visit(node.From) + from = from.Deref(&v.config.NtCache) + if !from.IsInteger && !from.IsUnknown(&v.config.NtCache) { + return v.error(node.From, "non-integer slice index %v", from.String()) + } + } + + if node.To != nil { + to := v.visit(node.To) + to = to.Deref(&v.config.NtCache) + if !to.IsInteger && !to.IsUnknown(&v.config.NtCache) { + return v.error(node.To, "non-integer slice index %v", to.String()) + } + } + + return nt +} + +func (v *Checker) callNode(node *ast.CallNode) Nature { + // Check if type was set on node (for example, by patcher) + // and use node type instead of function return type. + // + // If node type is anyType, then we should use function + // return type. For example, on error we return anyType + // for a call `errCall().Method()` and method will be + // evaluated on `anyType.Method()`, so return type will + // be anyType `anyType.Method(): anyType`. Patcher can + // fix `errCall()` to return proper type, so on second + // checker pass we should replace anyType on method node + // with new correct function return type. + if typ := node.Type(); typ != nil && typ != anyType { + return *node.Nature() + } + + // $env is not callable. + if id, ok := node.Callee.(*ast.IdentifierNode); ok && id.Value == "$env" { + return v.error(node, "%s is not callable", v.config.Env.String()) + } + + nt := v.visit(node.Callee) + if nt.IsUnknown(&v.config.NtCache) { + return Nature{} + } + + if nt.TypeData != nil && nt.TypeData.Func != nil { + return v.checkFunction(nt.TypeData.Func, node, node.Arguments) + } + + fnName := "function" + if identifier, ok := node.Callee.(*ast.IdentifierNode); ok { + fnName = identifier.Value + } + if member, ok := node.Callee.(*ast.MemberNode); ok { + if name, ok := member.Property.(*ast.StringNode); ok { + fnName = name.Value + } + } + + if nt.Nil { + return v.error(node, "%v is nil; cannot call nil as function", fnName) + } + + if nt.Kind == reflect.Func { + outType, err := v.checkArguments(fnName, nt, node.Arguments, node) + if err != nil { + if v.err == nil { + v.err = err + } + return Nature{} + } + return outType + } + return v.error(node, "%s is not callable", nt.String()) +} + +func (v *Checker) builtinNode(node *ast.BuiltinNode) Nature { + switch node.Name { + case "all", "none", "any", "one": + collection := v.visit(node.Arguments[0]) + collection = collection.Deref(&v.config.NtCache) + if !collection.IsArray() && !collection.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection.String()) + } + + v.begin(collection) + predicate := v.visit(node.Arguments[1]) + v.end() + + if predicate.IsFunc() && + predicate.NumOut() == 1 && + predicate.NumIn() == 1 && predicate.IsFirstArgUnknown(&v.config.NtCache) { + + predicateOut := predicate.Out(&v.config.NtCache, 0) + if !predicateOut.IsBool() && !predicateOut.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[1], "predicate should return boolean (got %s)", predicateOut.String()) + } + return v.config.NtCache.FromType(boolType) + } + return v.error(node.Arguments[1], "predicate should has one input and one output param") + + case "filter": + collection := v.visit(node.Arguments[0]) + collection = collection.Deref(&v.config.NtCache) + if !collection.IsArray() && !collection.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection.String()) + } + + v.begin(collection) + predicate := v.visit(node.Arguments[1]) + v.end() + + if predicate.IsFunc() && + predicate.NumOut() == 1 && + predicate.NumIn() == 1 && predicate.IsFirstArgUnknown(&v.config.NtCache) { + + predicateOut := predicate.Out(&v.config.NtCache, 0) + if !predicateOut.IsBool() && !predicateOut.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[1], "predicate should return boolean (got %s)", predicateOut.String()) + } + if collection.IsUnknown(&v.config.NtCache) { + return v.config.NtCache.FromType(arrayType) + } + collection = collection.Elem(&v.config.NtCache) + return collection.MakeArrayOf(&v.config.NtCache) + } + return v.error(node.Arguments[1], "predicate should has one input and one output param") + + case "map": + collection := v.visit(node.Arguments[0]) + collection = collection.Deref(&v.config.NtCache) + if !collection.IsArray() && !collection.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection.String()) + } + + v.begin(collection, varScope{"index", v.config.NtCache.FromType(intType)}) + predicate := v.visit(node.Arguments[1]) + v.end() + + if predicate.IsFunc() && + predicate.NumOut() == 1 && + predicate.NumIn() == 1 && predicate.IsFirstArgUnknown(&v.config.NtCache) { + + return predicate.Ref.MakeArrayOf(&v.config.NtCache) + } + return v.error(node.Arguments[1], "predicate should has one input and one output param") + + case "count": + collection := v.visit(node.Arguments[0]) + collection = collection.Deref(&v.config.NtCache) + if !collection.IsArray() && !collection.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection.String()) + } + + if len(node.Arguments) == 1 { + return v.config.NtCache.FromType(intType) + } + + v.begin(collection) + predicate := v.visit(node.Arguments[1]) + v.end() + + if predicate.IsFunc() && + predicate.NumOut() == 1 && + predicate.NumIn() == 1 && predicate.IsFirstArgUnknown(&v.config.NtCache) { + predicateOut := predicate.Out(&v.config.NtCache, 0) + if !predicateOut.IsBool() && !predicateOut.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[1], "predicate should return boolean (got %s)", predicateOut.String()) + } + + return v.config.NtCache.FromType(intType) + } + return v.error(node.Arguments[1], "predicate should has one input and one output param") + + case "sum": + collection := v.visit(node.Arguments[0]) + collection = collection.Deref(&v.config.NtCache) + if !collection.IsArray() && !collection.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection.String()) + } + + if len(node.Arguments) == 2 { + v.begin(collection) + predicate := v.visit(node.Arguments[1]) + v.end() + + if predicate.IsFunc() && + predicate.NumOut() == 1 && + predicate.NumIn() == 1 && predicate.IsFirstArgUnknown(&v.config.NtCache) { + return predicate.Out(&v.config.NtCache, 0) + } + } else { + if collection.IsUnknown(&v.config.NtCache) { + return Nature{} + } + return collection.Elem(&v.config.NtCache) + } + + case "find", "findLast": + collection := v.visit(node.Arguments[0]) + collection = collection.Deref(&v.config.NtCache) + if !collection.IsArray() && !collection.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection.String()) + } + + v.begin(collection) + predicate := v.visit(node.Arguments[1]) + v.end() + + if predicate.IsFunc() && + predicate.NumOut() == 1 && + predicate.NumIn() == 1 && predicate.IsFirstArgUnknown(&v.config.NtCache) { + + predicateOut := predicate.Out(&v.config.NtCache, 0) + if !predicateOut.IsBool() && !predicateOut.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[1], "predicate should return boolean (got %s)", predicateOut.String()) + } + if collection.IsUnknown(&v.config.NtCache) { + return Nature{} + } + return collection.Elem(&v.config.NtCache) + } + return v.error(node.Arguments[1], "predicate should has one input and one output param") + + case "findIndex", "findLastIndex": + collection := v.visit(node.Arguments[0]) + collection = collection.Deref(&v.config.NtCache) + if !collection.IsArray() && !collection.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection.String()) + } + + v.begin(collection) + predicate := v.visit(node.Arguments[1]) + v.end() + + if predicate.IsFunc() && + predicate.NumOut() == 1 && + predicate.NumIn() == 1 && predicate.IsFirstArgUnknown(&v.config.NtCache) { + + predicateOut := predicate.Out(&v.config.NtCache, 0) + if !predicateOut.IsBool() && !predicateOut.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[1], "predicate should return boolean (got %s)", predicateOut.String()) + } + return v.config.NtCache.FromType(intType) + } + return v.error(node.Arguments[1], "predicate should has one input and one output param") + + case "groupBy": + collection := v.visit(node.Arguments[0]) + collection = collection.Deref(&v.config.NtCache) + if !collection.IsArray() && !collection.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection.String()) + } + + v.begin(collection) + predicate := v.visit(node.Arguments[1]) + v.end() + + if predicate.IsFunc() && + predicate.NumOut() == 1 && + predicate.NumIn() == 1 && predicate.IsFirstArgUnknown(&v.config.NtCache) { + + collection = collection.Elem(&v.config.NtCache) + collection = collection.MakeArrayOf(&v.config.NtCache) + nt := v.config.NtCache.NatureOf(map[any][]any{}) + nt.Ref = &collection + return nt + } + return v.error(node.Arguments[1], "predicate should has one input and one output param") + + case "sortBy": + collection := v.visit(node.Arguments[0]) + collection = collection.Deref(&v.config.NtCache) + if !collection.IsArray() && !collection.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection.String()) + } + + v.begin(collection) + predicate := v.visit(node.Arguments[1]) + v.end() + + if len(node.Arguments) == 3 { + order := v.visit(node.Arguments[2]) + if !order.IsString() && !order.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[2], "sortBy order argument must be a string (got %v)", order.String()) + } + } + + if predicate.IsFunc() && + predicate.NumOut() == 1 && + predicate.NumIn() == 1 && predicate.IsFirstArgUnknown(&v.config.NtCache) { + + return collection + } + return v.error(node.Arguments[1], "predicate should has one input and one output param") + + case "reduce": + collection := v.visit(node.Arguments[0]) + collection = collection.Deref(&v.config.NtCache) + if !collection.IsArray() && !collection.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection.String()) + } + + v.begin(collection, varScope{"index", v.config.NtCache.FromType(intType)}, varScope{"acc", Nature{}}) + predicate := v.visit(node.Arguments[1]) + v.end() + + if len(node.Arguments) == 3 { + _ = v.visit(node.Arguments[2]) + } + + if predicate.IsFunc() && predicate.NumOut() == 1 { + return *predicate.Ref + } + return v.error(node.Arguments[1], "predicate should has two input and one output param") + + } + + if id, ok := builtin.Index[node.Name]; ok { + switch node.Name { + case "get": + return v.checkBuiltinGet(node) + } + return v.checkFunction(builtin.Builtins[id], node, node.Arguments) + } + + return v.error(node, "unknown builtin %v", node.Name) +} + +func (v *Checker) begin(collectionNature Nature, vars ...varScope) { + v.predicateScopes = append(v.predicateScopes, predicateScope{ + collection: collectionNature, + vars: vars, + }) +} + +func (v *Checker) end() { + v.predicateScopes = v.predicateScopes[:len(v.predicateScopes)-1] +} + +func (v *Checker) checkBuiltinGet(node *ast.BuiltinNode) Nature { + if len(node.Arguments) != 2 { + return v.error(node, "invalid number of arguments (expected 2, got %d)", len(node.Arguments)) + } + + base := v.visit(node.Arguments[0]) + prop := v.visit(node.Arguments[1]) + prop = prop.Deref(&v.config.NtCache) + + if id, ok := node.Arguments[0].(*ast.IdentifierNode); ok && id.Value == "$env" { + if s, ok := node.Arguments[1].(*ast.StringNode); ok { + if nt, ok := v.config.Env.Get(&v.config.NtCache, s.Value); ok { + return nt + } + } + return Nature{} + } + + if base.IsUnknown(&v.config.NtCache) { + return Nature{} + } + + switch base.Kind { + case reflect.Slice, reflect.Array: + if !prop.IsInteger && !prop.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[1], "non-integer slice index %s", prop.String()) + } + return base.Elem(&v.config.NtCache) + case reflect.Map: + if !prop.AssignableTo(base.Key(&v.config.NtCache)) && !prop.IsUnknown(&v.config.NtCache) { + return v.error(node.Arguments[1], "cannot use %s to get an element from %s", prop.String(), base.String()) + } + return base.Elem(&v.config.NtCache) + } + return v.error(node.Arguments[0], "type %v does not support indexing", base.String()) +} + +func (v *Checker) checkFunction(f *builtin.Function, node ast.Node, arguments []ast.Node) Nature { + if f.Validate != nil { + args := make([]reflect.Type, len(arguments)) + for i, arg := range arguments { + argNature := v.visit(arg) + if argNature.IsUnknown(&v.config.NtCache) { + args[i] = anyType + } else { + args[i] = argNature.Type + } + } + t, err := f.Validate(args) + if err != nil { + return v.error(node, "%v", err) + } + return v.config.NtCache.FromType(t) + } else if len(f.Types) == 0 { + nt, err := v.checkArguments(f.Name, v.config.NtCache.FromType(f.Type()), arguments, node) + if err != nil { + if v.err == nil { + v.err = err + } + return Nature{} + } + // No type was specified, so we assume the function returns any. + return nt + } + var lastErr *file.Error + for _, t := range f.Types { + outNature, err := v.checkArguments(f.Name, v.config.NtCache.FromType(t), arguments, node) + if err != nil { + lastErr = err + continue + } + + // As we found the correct function overload, we can stop the loop. + // Also, we need to set the correct nature of the callee so compiler, + // can correctly handle OpDeref opcode. + if callNode, ok := node.(*ast.CallNode); ok { + callNode.Callee.SetType(t) + } + + return outNature + } + if lastErr != nil { + if v.err == nil { + v.err = lastErr + } + return Nature{} + } + + return v.error(node, "no matching overload for %v", f.Name) +} + +func (v *Checker) checkArguments( + name string, + fn Nature, + arguments []ast.Node, + node ast.Node, +) (Nature, *file.Error) { + if fn.IsUnknown(&v.config.NtCache) { + return Nature{}, nil + } + + numOut := fn.NumOut() + if numOut == 0 { + return Nature{}, &file.Error{ + Location: node.Location(), + Message: fmt.Sprintf("func %v doesn't return value", name), + } + } + if numOut > 2 { + return Nature{}, &file.Error{ + Location: node.Location(), + Message: fmt.Sprintf("func %v returns more then two values", name), + } + } + + // If func is method on an env, first argument should be a receiver, + // and actual arguments less than fnNumIn by one. + fnNumIn := fn.NumIn() + if fn.Method { // TODO: Move subtraction to the Nature.NumIn() and Nature.In() methods. + fnNumIn-- + } + // Skip first argument in case of the receiver. + fnInOffset := 0 + if fn.Method { + fnInOffset = 1 + } + + var err *file.Error + isVariadic := fn.IsVariadic() + if isVariadic { + if len(arguments) < fnNumIn-1 { + err = &file.Error{ + Location: node.Location(), + Message: fmt.Sprintf("not enough arguments to call %v", name), + } + } + } else { + if len(arguments) > fnNumIn { + err = &file.Error{ + Location: node.Location(), + Message: fmt.Sprintf("too many arguments to call %v", name), + } + } + if len(arguments) < fnNumIn { + err = &file.Error{ + Location: node.Location(), + Message: fmt.Sprintf("not enough arguments to call %v", name), + } + } + } + + if err != nil { + // If we have an error, we should still visit all arguments to + // type check them, as a patch can fix the error later. + for _, arg := range arguments { + _ = v.visit(arg) + } + return fn.Out(&v.config.NtCache, 0), err + } + + for i, arg := range arguments { + argNature := v.visit(arg) + + var in Nature + if isVariadic && i >= fnNumIn-1 { + // For variadic arguments fn(xs ...int), go replaces type of xs (int) with ([]int). + // As we compare arguments one by one, we need underling type. + in = fn.InElem(&v.config.NtCache, fnNumIn-1+fnInOffset) + } else { + in = fn.In(&v.config.NtCache, i+fnInOffset) + } + + if in.IsFloat && argNature.IsInteger { + traverseAndReplaceIntegerNodesWithFloatNodes(&arguments[i], in) + continue + } + + if in.IsInteger && argNature.IsInteger && argNature.Kind != in.Kind { + traverseAndReplaceIntegerNodesWithIntegerNodes(&arguments[i], in) + continue + } + + if argNature.Nil { + if in.Kind == reflect.Ptr || in.Kind == reflect.Interface { + continue + } + return Nature{}, &file.Error{ + Location: arg.Location(), + Message: fmt.Sprintf("cannot use nil as argument (type %s) to call %v", in.String(), name), + } + } + + // Check if argument is assignable to the function input type. + // We check original type (like *time.Time), not dereferenced type, + // as function input type can be pointer to a struct. + assignable := argNature.AssignableTo(in) + + // We also need to check if dereference arg type is assignable to the function input type. + // For example, func(int) and argument *int. In this case we will add OpDeref to the argument, + // so we can call the function with *int argument. + if !assignable && argNature.IsPointer() { + nt := argNature.Deref(&v.config.NtCache) + assignable = nt.AssignableTo(in) + } + + if !assignable && !argNature.IsUnknown(&v.config.NtCache) { + return Nature{}, &file.Error{ + Location: arg.Location(), + Message: fmt.Sprintf("cannot use %s as argument (type %s) to call %v ", argNature.String(), in.String(), name), + } + } + } + + return fn.Out(&v.config.NtCache, 0), nil +} + +func traverseAndReplaceIntegerNodesWithFloatNodes(node *ast.Node, newNature Nature) { + switch (*node).(type) { + case *ast.IntegerNode: + *node = &ast.FloatNode{Value: float64((*node).(*ast.IntegerNode).Value)} + (*node).SetType(newNature.Type) + case *ast.UnaryNode: + unaryNode := (*node).(*ast.UnaryNode) + traverseAndReplaceIntegerNodesWithFloatNodes(&unaryNode.Node, newNature) + case *ast.BinaryNode: + binaryNode := (*node).(*ast.BinaryNode) + switch binaryNode.Operator { + case "+", "-", "*": + traverseAndReplaceIntegerNodesWithFloatNodes(&binaryNode.Left, newNature) + traverseAndReplaceIntegerNodesWithFloatNodes(&binaryNode.Right, newNature) + } + } +} + +func traverseAndReplaceIntegerNodesWithIntegerNodes(node *ast.Node, newNature Nature) { + switch (*node).(type) { + case *ast.IntegerNode: + (*node).SetType(newNature.Type) + case *ast.UnaryNode: + (*node).SetType(newNature.Type) + unaryNode := (*node).(*ast.UnaryNode) + traverseAndReplaceIntegerNodesWithIntegerNodes(&unaryNode.Node, newNature) + case *ast.BinaryNode: + // TODO: Binary node return type is dependent on the type of the operands. We can't just change the type of the node. + binaryNode := (*node).(*ast.BinaryNode) + switch binaryNode.Operator { + case "+", "-", "*": + traverseAndReplaceIntegerNodesWithIntegerNodes(&binaryNode.Left, newNature) + traverseAndReplaceIntegerNodesWithIntegerNodes(&binaryNode.Right, newNature) + } + } +} + +func (v *Checker) predicateNode(node *ast.PredicateNode) Nature { + nt := v.visit(node.Node) + var out []reflect.Type + if nt.IsUnknown(&v.config.NtCache) { + out = append(out, anyType) + } else if !nt.Nil { + out = append(out, nt.Type) + } + n := v.config.NtCache.FromType(reflect.FuncOf(anyTypeSlice, out, false)) + n.Ref = &nt + return n +} + +func (v *Checker) pointerNode(node *ast.PointerNode) Nature { + if len(v.predicateScopes) == 0 { + return v.error(node, "cannot use pointer accessor outside predicate") + } + scope := v.predicateScopes[len(v.predicateScopes)-1] + if node.Name == "" { + if scope.collection.IsUnknown(&v.config.NtCache) { + return Nature{} + } + switch scope.collection.Kind { + case reflect.Array, reflect.Slice: + return scope.collection.Elem(&v.config.NtCache) + } + return v.error(node, "cannot use %v as array", scope) + } + if scope.vars != nil { + for i := range scope.vars { + if node.Name == scope.vars[i].name { + return scope.vars[i].nature + } + } + } + return v.error(node, "unknown pointer #%v", node.Name) +} + +func (v *Checker) variableDeclaratorNode(node *ast.VariableDeclaratorNode) Nature { + if _, ok := v.config.Env.Get(&v.config.NtCache, node.Name); ok { + return v.error(node, "cannot redeclare %v", node.Name) + } + if _, ok := v.config.Functions[node.Name]; ok { + return v.error(node, "cannot redeclare function %v", node.Name) + } + if _, ok := v.config.Builtins[node.Name]; ok { + return v.error(node, "cannot redeclare builtin %v", node.Name) + } + for i := len(v.varScopes) - 1; i >= 0; i-- { + if v.varScopes[i].name == node.Name { + return v.error(node, "cannot redeclare variable %v", node.Name) + } + } + varNature := v.visit(node.Value) + v.varScopes = append(v.varScopes, varScope{node.Name, varNature}) + exprNature := v.visit(node.Expr) + v.varScopes = v.varScopes[:len(v.varScopes)-1] + return exprNature +} + +func (v *Checker) sequenceNode(node *ast.SequenceNode) Nature { + if len(node.Nodes) == 0 { + return v.error(node, "empty sequence expression") + } + var last Nature + for _, node := range node.Nodes { + last = v.visit(node) + } + return last +} + +func (v *Checker) conditionalNode(node *ast.ConditionalNode) Nature { + c := v.visit(node.Cond) + c = c.Deref(&v.config.NtCache) + if !c.IsBool() && !c.IsUnknown(&v.config.NtCache) { + return v.error(node.Cond, "non-bool expression (type %v) used as condition", c.String()) + } + + t1 := v.visit(node.Exp1) + t2 := v.visit(node.Exp2) + + if t1.Nil && !t2.Nil { + return t2 + } + if !t1.Nil && t2.Nil { + return t1 + } + if t1.Nil && t2.Nil { + return v.config.NtCache.NatureOf(nil) + } + if t1.AssignableTo(t2) { + if t1.IsArray() && t2.IsArray() { + e1 := t1.Elem(&v.config.NtCache) + e2 := t2.Elem(&v.config.NtCache) + if !e1.AssignableTo(e2) || !e2.AssignableTo(e1) { + return v.config.NtCache.FromType(arrayType) + } + } + return t1 + } + return Nature{} +} + +func (v *Checker) arrayNode(node *ast.ArrayNode) Nature { + var prev Nature + allElementsAreSameType := true + for i, node := range node.Nodes { + curr := v.visit(node) + if i > 0 { + if curr.Kind != prev.Kind { + allElementsAreSameType = false + } + } + prev = curr + } + if allElementsAreSameType { + return prev.MakeArrayOf(&v.config.NtCache) + } + return v.config.NtCache.FromType(arrayType) +} + +func (v *Checker) mapNode(node *ast.MapNode) Nature { + for _, pair := range node.Pairs { + v.visit(pair) + } + return v.config.NtCache.FromType(mapType) +} + +func (v *Checker) pairNode(node *ast.PairNode) Nature { + v.visit(node.Key) + v.visit(node.Value) + return v.config.NtCache.NatureOf(nil) +} diff --git a/vendor/github.com/expr-lang/expr/checker/info.go b/vendor/github.com/expr-lang/expr/checker/info.go new file mode 100644 index 00000000000..57202e95837 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/checker/info.go @@ -0,0 +1,125 @@ +package checker + +import ( + "reflect" + + "github.com/expr-lang/expr/ast" + . "github.com/expr-lang/expr/checker/nature" + "github.com/expr-lang/expr/vm" +) + +func FieldIndex(c *Cache, env Nature, node ast.Node) (bool, []int, string) { + switch n := node.(type) { + case *ast.IdentifierNode: + if idx, ok := env.FieldIndex(c, n.Value); ok { + return true, idx, n.Value + } + case *ast.MemberNode: + base := n.Node.Nature().Deref(c) + if base.Kind == reflect.Struct { + if prop, ok := n.Property.(*ast.StringNode); ok { + if idx, ok := base.FieldIndex(c, prop.Value); ok { + return true, idx, prop.Value + } + } + } + } + return false, nil, "" +} + +func MethodIndex(c *Cache, env Nature, node ast.Node) (bool, int, string) { + switch n := node.(type) { + case *ast.IdentifierNode: + if env.Kind == reflect.Struct { + if m, ok := env.Get(c, n.Value); ok && m.TypeData != nil { + return m.Method, m.MethodIndex, n.Value + } + } + case *ast.MemberNode: + if name, ok := n.Property.(*ast.StringNode); ok { + base := n.Node.Type() + if base != nil && base.Kind() != reflect.Interface { + if m, ok := base.MethodByName(name.Value); ok { + return true, m.Index, name.Value + } + } + } + } + return false, 0, "" +} + +func TypedFuncIndex(fn reflect.Type, method bool) (int, bool) { + if fn == nil { + return 0, false + } + if fn.Kind() != reflect.Func { + return 0, false + } + // OnCallTyped doesn't work for functions with variadic arguments. + if fn.IsVariadic() { + return 0, false + } + // OnCallTyped doesn't work named function, like `type MyFunc func() int`. + if fn.PkgPath() != "" { // If PkgPath() is not empty, it means that function is named. + return 0, false + } + + fnNumIn := fn.NumIn() + fnInOffset := 0 + if method { + fnNumIn-- + fnInOffset = 1 + } + +funcTypes: + for i := range vm.FuncTypes { + if i == 0 { + continue + } + typed := reflect.ValueOf(vm.FuncTypes[i]).Elem().Type() + if typed.Kind() != reflect.Func { + continue + } + if typed.NumOut() != fn.NumOut() { + continue + } + for j := 0; j < typed.NumOut(); j++ { + if typed.Out(j) != fn.Out(j) { + continue funcTypes + } + } + if typed.NumIn() != fnNumIn { + continue + } + for j := 0; j < typed.NumIn(); j++ { + if typed.In(j) != fn.In(j+fnInOffset) { + continue funcTypes + } + } + return i, true + } + return 0, false +} + +func IsFastFunc(fn reflect.Type, method bool) bool { + if fn == nil { + return false + } + if fn.Kind() != reflect.Func { + return false + } + numIn := 1 + if method { + numIn = 2 + } + if fn.IsVariadic() && + fn.NumIn() == numIn && + fn.NumOut() == 1 && + fn.Out(0).Kind() == reflect.Interface { + rest := fn.In(fn.NumIn() - 1) // function has only one param for functions and two for methods + if rest != nil && rest.Kind() == reflect.Slice && rest.Elem().Kind() == reflect.Interface { + return true + } + } + return false +} diff --git a/vendor/github.com/expr-lang/expr/checker/nature/nature.go b/vendor/github.com/expr-lang/expr/checker/nature/nature.go new file mode 100644 index 00000000000..c96f28c4334 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/checker/nature/nature.go @@ -0,0 +1,579 @@ +package nature + +import ( + "fmt" + "reflect" + "time" + + "github.com/expr-lang/expr/builtin" + "github.com/expr-lang/expr/internal/deref" +) + +var ( + intType = reflect.TypeOf(0) + floatType = reflect.TypeOf(float64(0)) + arrayType = reflect.TypeOf([]any{}) + byteSliceType = reflect.TypeOf([]byte{}) + timeType = reflect.TypeOf(time.Time{}) + durationType = reflect.TypeOf(time.Duration(0)) + + builtinInt = map[reflect.Type]struct{}{ + reflect.TypeOf(int(0)): {}, + reflect.TypeOf(int8(0)): {}, + reflect.TypeOf(int16(0)): {}, + reflect.TypeOf(int32(0)): {}, + reflect.TypeOf(int64(0)): {}, + reflect.TypeOf(uintptr(0)): {}, + reflect.TypeOf(uint(0)): {}, + reflect.TypeOf(uint8(0)): {}, + reflect.TypeOf(uint16(0)): {}, + reflect.TypeOf(uint32(0)): {}, + reflect.TypeOf(uint64(0)): {}, + } + builtinFloat = map[reflect.Type]struct{}{ + reflect.TypeOf(float32(0)): {}, + reflect.TypeOf(float64(0)): {}, + } +) + +type NatureCheck int + +const ( + _ NatureCheck = iota + BoolCheck + StringCheck + IntegerCheck + NumberCheck + MapCheck + ArrayCheck + TimeCheck + DurationCheck +) + +type Nature struct { + // The order of the fields matter, check alignment before making changes. + + Type reflect.Type // Type of the value. If nil, then value is unknown. + Kind reflect.Kind // Kind of the value. + + *TypeData + + // Ref is a reference used for multiple, disjoint purposes. When the Nature + // is for a: + // - Predicate: then Ref is the nature of the Out of the predicate. + // - Array-like types: then Ref is the Elem nature of array type (usually Type is []any, but ArrayOf can be any nature). + Ref *Nature + + Nil bool // If value is nil. + Strict bool // If map is types.StrictMap. + Method bool // If value retrieved from method. Usually used to determine amount of in arguments. + IsInteger bool // If it's a builtin integer or unsigned integer type. + IsFloat bool // If it's a builtin float type. +} + +type TypeData struct { + methodset *methodset // optional to avoid the map in *Cache + + *structData + + // map-only data + Fields map[string]Nature // Fields of map type. + DefaultMapValue *Nature // Default value of map type. + + // callable-only data + Func *builtin.Function // Used to pass function type from callee to CallNode. + MethodIndex int // Index of method in type. + inElem, outZero *Nature + numIn, numOut int + + isVariadic bool + isVariadicSet bool + numInSet bool + numOutSet bool +} + +// Cache is a shared cache of type information. It is only used in the stages +// where type information becomes relevant, so packages like ast, parser, types, +// and lexer do not need to use the cache because they don't need any service +// from the Nature type, they only describe. However, when receiving a Nature +// from one of those packages, the cache must be set immediately. +type Cache struct { + methods map[reflect.Type]*methodset + structs map[reflect.Type]Nature +} + +// NatureOf returns a Nature describing "i". If "i" is nil then it returns a +// Nature describing the value "nil". +func (c *Cache) NatureOf(i any) Nature { + // reflect.TypeOf(nil) returns nil, but in FromType we want to differentiate + // what nil means for us + if i == nil { + return Nature{Nil: true} + } + return c.FromType(reflect.TypeOf(i)) +} + +// FromType returns a Nature describing a value of type "t". If "t" is nil then +// it returns a Nature describing an unknown value. +func (c *Cache) FromType(t reflect.Type) Nature { + if t == nil { + return Nature{} + } + var td *TypeData + var isInteger, isFloat bool + k := t.Kind() + switch k { + case reflect.Struct: + // c can be nil when we call the package function FromType, which uses a + // nil *Cache to call this method. + if c != nil { + return c.getStruct(t) + } + fallthrough + case reflect.Func: + td = new(TypeData) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + _, isInteger = builtinInt[t] + case reflect.Float32, reflect.Float64: + _, isFloat = builtinFloat[t] + } + return Nature{ + Type: t, + Kind: k, + TypeData: td, + IsInteger: isInteger, + IsFloat: isFloat, + } +} + +func (c *Cache) getStruct(t reflect.Type) Nature { + if c.structs == nil { + c.structs = map[reflect.Type]Nature{} + } else if nt, ok := c.structs[t]; ok { + return nt + } + nt := Nature{ + Type: t, + Kind: reflect.Struct, + TypeData: &TypeData{ + structData: &structData{ + rType: t, + numField: t.NumField(), + anonIdx: -1, // do not lookup embedded fields yet + }, + }, + } + c.structs[t] = nt + return nt +} + +func (c *Cache) getMethodset(t reflect.Type, k reflect.Kind) *methodset { + if t == nil || c == nil { + return nil + } + if c.methods == nil { + c.methods = map[reflect.Type]*methodset{ + t: nil, + } + } else if s, ok := c.methods[t]; ok { + return s + } + numMethod := t.NumMethod() + if numMethod < 1 { + c.methods[t] = nil // negative cache + return nil + } + s := &methodset{ + rType: t, + kind: k, + numMethod: numMethod, + } + c.methods[t] = s + return s +} + +// NatureOf calls NatureOf on a nil *Cache. See the comment on Cache. +func NatureOf(i any) Nature { + var c *Cache + return c.NatureOf(i) +} + +// FromType calls FromType on a nil *Cache. See the comment on Cache. +func FromType(t reflect.Type) Nature { + var c *Cache + return c.FromType(t) +} + +func ArrayFromType(c *Cache, t reflect.Type) Nature { + elem := c.FromType(t) + nt := c.FromType(arrayType) + nt.Ref = &elem + return nt +} + +func (n *Nature) IsAny(c *Cache) bool { + return n.Type != nil && n.Kind == reflect.Interface && n.NumMethods(c) == 0 +} + +func (n *Nature) IsUnknown(c *Cache) bool { + return n.Type == nil && !n.Nil || n.IsAny(c) +} + +func (n *Nature) String() string { + if n.Type != nil { + return n.Type.String() + } + return "unknown" +} + +func (n *Nature) Deref(c *Cache) Nature { + t, _, changed := deref.TypeKind(n.Type, n.Kind) + if !changed { + return *n + } + return c.FromType(t) +} + +func (n *Nature) Key(c *Cache) Nature { + if n.Kind == reflect.Map { + return c.FromType(n.Type.Key()) + } + return Nature{} +} + +func (n *Nature) Elem(c *Cache) Nature { + switch n.Kind { + case reflect.Ptr: + return c.FromType(n.Type.Elem()) + case reflect.Map: + if n.TypeData != nil && n.DefaultMapValue != nil { + return *n.DefaultMapValue + } + return c.FromType(n.Type.Elem()) + case reflect.Slice, reflect.Array: + if n.Ref != nil { + return *n.Ref + } + return c.FromType(n.Type.Elem()) + } + return Nature{} +} + +func (n *Nature) AssignableTo(nt Nature) bool { + if n.Nil { + switch nt.Kind { + case reflect.Pointer, reflect.Interface, reflect.Chan, reflect.Func, + reflect.Map, reflect.Slice: + // nil can be assigned to these kinds + return true + } + } + if n.Type == nil || nt.Type == nil || + n.Kind != nt.Kind && nt.Kind != reflect.Interface { + return false + } + return n.Type.AssignableTo(nt.Type) +} + +func (n *Nature) getMethodset(c *Cache) *methodset { + if n.TypeData != nil && n.TypeData.methodset != nil { + return n.TypeData.methodset + } + s := c.getMethodset(n.Type, n.Kind) + if n.TypeData != nil { + n.TypeData.methodset = s // cache locally if possible + } + return s +} + +func (n *Nature) NumMethods(c *Cache) int { + if s := n.getMethodset(c); s != nil { + return s.numMethod + } + return 0 +} + +func (n *Nature) MethodByName(c *Cache, name string) (Nature, bool) { + if s := n.getMethodset(c); s != nil { + if m := s.method(c, name); m != nil { + return m.nature, true + } + } + return Nature{}, false +} + +func (n *Nature) NumIn() int { + if n.numInSet { + return n.numIn + } + n.numInSet = true + n.numIn = n.Type.NumIn() + return n.numIn +} + +func (n *Nature) InElem(c *Cache, i int) Nature { + if n.inElem == nil { + n2 := c.FromType(n.Type.In(i)) + n2 = n2.Elem(c) + n.inElem = &n2 + } + return *n.inElem +} + +func (n *Nature) In(c *Cache, i int) Nature { + return c.FromType(n.Type.In(i)) +} + +func (n *Nature) IsFirstArgUnknown(c *Cache) bool { + if n.Type != nil { + n2 := c.FromType(n.Type.In(0)) + return n2.IsUnknown(c) + } + return false +} + +func (n *Nature) NumOut() int { + if n.numOutSet { + return n.numOut + } + n.numOutSet = true + n.numOut = n.Type.NumOut() + return n.numOut +} + +func (n *Nature) Out(c *Cache, i int) Nature { + if i != 0 { + return n.out(c, i) + } + if n.outZero != nil { + return *n.outZero + } + nt := n.out(c, 0) + n.outZero = &nt + return nt +} + +func (n *Nature) out(c *Cache, i int) Nature { + if n.Type == nil { + return Nature{} + } + return c.FromType(n.Type.Out(i)) +} + +func (n *Nature) IsVariadic() bool { + if n.isVariadicSet { + return n.isVariadic + } + n.isVariadicSet = true + n.isVariadic = n.Type.IsVariadic() + return n.isVariadic +} + +func (n *Nature) FieldByName(c *Cache, name string) (Nature, bool) { + if n.Kind != reflect.Struct { + return Nature{}, false + } + var sd *structData + if n.TypeData != nil && n.structData != nil { + sd = n.structData + } else { + sd = c.getStruct(n.Type).structData + } + if sf := sd.structField(c, nil, name); sf != nil { + return sf.Nature, true + } + return Nature{}, false +} + +func (n *Nature) IsFastMap() bool { + return n.Type != nil && + n.Type.Kind() == reflect.Map && + n.Type.Key().Kind() == reflect.String && + n.Type.Elem().Kind() == reflect.Interface +} + +func (n *Nature) Get(c *Cache, name string) (Nature, bool) { + if n.Kind == reflect.Map && n.TypeData != nil { + f, ok := n.Fields[name] + return f, ok + } + return n.getSlow(c, name) +} + +func (n *Nature) getSlow(c *Cache, name string) (Nature, bool) { + if nt, ok := n.MethodByName(c, name); ok { + return nt, true + } + t, k, changed := deref.TypeKind(n.Type, n.Kind) + if k == reflect.Struct { + var sd *structData + if changed { + sd = c.getStruct(t).structData + } else { + sd = n.structData + } + if sf := sd.structField(c, nil, name); sf != nil { + return sf.Nature, true + } + } + return Nature{}, false +} + +func (n *Nature) FieldIndex(c *Cache, name string) ([]int, bool) { + if n.Kind != reflect.Struct { + return nil, false + } + if sf := n.structField(c, nil, name); sf != nil { + return sf.Index, true + } + return nil, false +} + +func (n *Nature) All(c *Cache) map[string]Nature { + table := make(map[string]Nature) + + if n.Type == nil { + return table + } + + for i := 0; i < n.NumMethods(c); i++ { + method := n.Type.Method(i) + nt := c.FromType(method.Type) + if nt.TypeData == nil { + nt.TypeData = new(TypeData) + } + nt.Method = true + nt.MethodIndex = method.Index + table[method.Name] = nt + } + + t := deref.Type(n.Type) + + switch t.Kind() { + case reflect.Struct: + for name, nt := range StructFields(c, t) { + if _, ok := table[name]; ok { + continue + } + table[name] = nt + } + + case reflect.Map: + if n.TypeData != nil { + for key, nt := range n.Fields { + if _, ok := table[key]; ok { + continue + } + table[key] = nt + } + } + } + + return table +} + +func (n *Nature) IsNumber() bool { + return n.IsInteger || n.IsFloat +} + +func (n *Nature) PromoteNumericNature(c *Cache, rhs Nature) Nature { + if n.IsUnknown(c) || rhs.IsUnknown(c) { + return Nature{} + } + if n.IsFloat || rhs.IsFloat { + return c.FromType(floatType) + } + return c.FromType(intType) +} + +func (n *Nature) IsTime() bool { + return n.Type == timeType +} + +func (n *Nature) IsDuration() bool { + return n.Type == durationType +} + +func (n *Nature) IsBool() bool { + return n.Kind == reflect.Bool +} + +func (n *Nature) IsString() bool { + return n.Kind == reflect.String +} + +func (n *Nature) IsByteSlice() bool { + return n.Type == byteSliceType +} + +func (n *Nature) IsArray() bool { + return n.Kind == reflect.Slice || n.Kind == reflect.Array +} + +func (n *Nature) IsMap() bool { + return n.Kind == reflect.Map +} + +func (n *Nature) IsStruct() bool { + return n.Kind == reflect.Struct +} + +func (n *Nature) IsFunc() bool { + return n.Kind == reflect.Func +} + +func (n *Nature) IsPointer() bool { + return n.Kind == reflect.Ptr +} + +func (n *Nature) IsAnyOf(cs ...NatureCheck) bool { + var result bool + for i := 0; i < len(cs) && !result; i++ { + switch cs[i] { + case BoolCheck: + result = n.IsBool() + case StringCheck: + result = n.IsString() + case IntegerCheck: + result = n.IsInteger + case NumberCheck: + result = n.IsNumber() + case MapCheck: + result = n.IsMap() + case ArrayCheck: + result = n.IsArray() + case TimeCheck: + result = n.IsTime() + case DurationCheck: + result = n.IsDuration() + default: + panic(fmt.Sprintf("unknown check value %d", cs[i])) + } + } + return result +} + +func (n *Nature) ComparableTo(c *Cache, rhs Nature) bool { + return n.IsUnknown(c) || rhs.IsUnknown(c) || + n.Nil || rhs.Nil || + n.IsNumber() && rhs.IsNumber() || + n.IsDuration() && rhs.IsDuration() || + n.IsTime() && rhs.IsTime() || + n.IsArray() && rhs.IsArray() || + n.AssignableTo(rhs) +} + +func (n *Nature) MaybeCompatible(c *Cache, rhs Nature, cs ...NatureCheck) bool { + nIsUnknown := n.IsUnknown(c) + rshIsUnknown := rhs.IsUnknown(c) + return nIsUnknown && rshIsUnknown || + nIsUnknown && rhs.IsAnyOf(cs...) || + rshIsUnknown && n.IsAnyOf(cs...) +} + +func (n *Nature) MakeArrayOf(c *Cache) Nature { + nt := c.FromType(arrayType) + nt.Ref = n + return nt +} diff --git a/vendor/github.com/expr-lang/expr/checker/nature/utils.go b/vendor/github.com/expr-lang/expr/checker/nature/utils.go new file mode 100644 index 00000000000..2af94600220 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/checker/nature/utils.go @@ -0,0 +1,233 @@ +package nature + +import ( + "reflect" + + "github.com/expr-lang/expr/internal/deref" +) + +func fieldName(fieldName string, tag reflect.StructTag) (string, bool) { + switch taggedName := tag.Get("expr"); taggedName { + case "-": + return "", false + case "": + return fieldName, true + default: + return taggedName, true + } +} + +type structData struct { + rType reflect.Type + fields map[string]*structField + numField, ownIdx, anonIdx int + + curParent, curChild *structData + curChildIndex []int +} + +type structField struct { + Nature + Index []int +} + +func (s *structData) finished() bool { + return s.ownIdx >= s.numField && // no own fields left to visit + s.anonIdx >= s.numField && // no embedded fields to visit + s.curChild == nil // no child in process of visiting +} + +func (s *structData) structField(c *Cache, parentEmbed *structData, name string) *structField { + if s.fields == nil { + if s.numField > 0 { + s.fields = make(map[string]*structField, s.numField) + } + } else if f := s.fields[name]; f != nil { + return f + } + if s.finished() { + return nil + } + + // Lookup own fields first. + for ; s.ownIdx < s.numField; s.ownIdx++ { + field := s.rType.Field(s.ownIdx) + if field.Anonymous && s.anonIdx < 0 { + // start iterating anon fields on the first instead of zero + s.anonIdx = s.ownIdx + } + if !field.IsExported() { + continue + } + fName, ok := fieldName(field.Name, field.Tag) + if !ok || fName == "" { + // name can still be empty for a type created at runtime with + // reflect + continue + } + nt := c.FromType(field.Type) + sf := &structField{ + Nature: nt, + Index: field.Index, + } + s.fields[fName] = sf + if parentEmbed != nil { + parentEmbed.trySet(fName, sf) + } + if fName == name { + return sf + } + } + + if s.curChild != nil { + sf := s.findInEmbedded(c, parentEmbed, s.curChild, s.curChildIndex, name) + if sf != nil { + return sf + } + } + + // Lookup embedded fields through anon own fields + for ; s.anonIdx >= 0 && s.anonIdx < s.numField; s.anonIdx++ { + field := s.rType.Field(s.anonIdx) + // we do enter embedded non-exported types because they could contain + // exported fields + if !field.Anonymous { + continue + } + t, k, _ := deref.TypeKind(field.Type, field.Type.Kind()) + if k != reflect.Struct { + continue + } + + childEmbed := c.getStruct(t).structData + sf := s.findInEmbedded(c, parentEmbed, childEmbed, field.Index, name) + if sf != nil { + return sf + } + } + + return nil +} + +func (s *structData) findInEmbedded( + c *Cache, + parentEmbed, childEmbed *structData, + childIndex []int, + name string, +) *structField { + // Set current parent/child data. This allows trySet to handle child fields + // and add them to our struct and to the parent as well if needed + s.curParent = parentEmbed + s.curChild = childEmbed + s.curChildIndex = childIndex + defer func() { + // Ensure to cleanup references + s.curParent = nil + if childEmbed.finished() { + // If the child can still have more fields to explore then keep it + // referened to look it up again if we need to + s.curChild = nil + s.curChildIndex = nil + } + }() + + // See if the child has already cached its fields. This is still important + // to check even if it's the s.unfinishedEmbedded because it may have + // explored new fields since the last time we visited it + for name, sf := range childEmbed.fields { + s.trySet(name, sf) + } + + // Recheck if we have what we needed from the above sync + if sf := s.fields[name]; sf != nil { + return sf + } + + // Try finding in the child again in case it hasn't finished + if !childEmbed.finished() { + if childEmbed.structField(c, s, name) != nil { + return s.fields[name] + } + } + + return nil +} + +func (s *structData) trySet(name string, sf *structField) { + if _, ok := s.fields[name]; ok { + return + } + sf = &structField{ + Nature: sf.Nature, + Index: append(s.curChildIndex, sf.Index...), + } + s.fields[name] = sf + if s.curParent != nil { + s.curParent.trySet(name, sf) + } +} + +func StructFields(c *Cache, t reflect.Type) map[string]Nature { + table := make(map[string]Nature) + if t == nil { + return table + } + t, k, _ := deref.TypeKind(t, t.Kind()) + if k == reflect.Struct { + // lookup for a field with an empty name, which will cause to never find a + // match, meaning everything will have been cached. + sd := c.getStruct(t).structData + sd.structField(c, nil, "") + for name, sf := range sd.fields { + table[name] = sf.Nature + } + } + return table +} + +type methodset struct { + rType reflect.Type + kind reflect.Kind + methods map[string]*method + numMethod, idx int +} + +type method struct { + reflect.Method + nature Nature +} + +func (s *methodset) method(c *Cache, name string) *method { + if s.methods == nil { + s.methods = make(map[string]*method, s.numMethod) + } else if m := s.methods[name]; m != nil { + return m + } + for ; s.idx < s.numMethod; s.idx++ { + rm := s.rType.Method(s.idx) + if !rm.IsExported() { + continue + } + nt := c.FromType(rm.Type) + if s.rType.Kind() != reflect.Interface { + nt.Method = true + nt.MethodIndex = rm.Index + // In case of interface type method will not have a receiver, + // and to prevent checker decreasing numbers of in arguments + // return method type as not method (second argument is false). + + // Also, we can not use m.Index here, because it will be + // different indexes for different types which implement + // the same interface. + } + m := &method{ + Method: rm, + nature: nt, + } + s.methods[rm.Name] = m + if rm.Name == name { + return m + } + } + return nil +} diff --git a/vendor/github.com/expr-lang/expr/compiler/compiler.go b/vendor/github.com/expr-lang/expr/compiler/compiler.go new file mode 100644 index 00000000000..f66cf9ed838 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/compiler/compiler.go @@ -0,0 +1,1355 @@ +package compiler + +import ( + "fmt" + "math" + "reflect" + "regexp" + "runtime/debug" + + "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/builtin" + "github.com/expr-lang/expr/checker" + . "github.com/expr-lang/expr/checker/nature" + "github.com/expr-lang/expr/conf" + "github.com/expr-lang/expr/file" + "github.com/expr-lang/expr/parser" + . "github.com/expr-lang/expr/vm" + "github.com/expr-lang/expr/vm/runtime" +) + +const ( + placeholder = 12345 +) + +func Compile(tree *parser.Tree, config *conf.Config) (program *Program, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("%v\n%s", r, debug.Stack()) + } + }() + + c := &compiler{ + config: config, + locations: make([]file.Location, 0), + constantsIndex: make(map[any]int), + functionsIndex: make(map[string]int), + debugInfo: make(map[string]string), + } + + if config != nil { + c.ntCache = &c.config.NtCache + } else { + c.ntCache = new(Cache) + } + + c.compile(tree.Node) + + if c.config != nil { + switch c.config.Expect { + case reflect.Int: + c.emit(OpCast, 0) + case reflect.Int64: + c.emit(OpCast, 1) + case reflect.Float64: + c.emit(OpCast, 2) + case reflect.Bool: + c.emit(OpCast, 3) + } + if c.config.Optimize { + c.optimize() + } + } + + var span *Span + if len(c.spans) > 0 { + span = c.spans[0] + } + + program = NewProgram( + tree.Source, + tree.Node, + c.locations, + c.variables, + c.constants, + c.bytecode, + c.arguments, + c.functions, + c.debugInfo, + span, + ) + return +} + +type compiler struct { + config *conf.Config + ntCache *Cache + locations []file.Location + bytecode []Opcode + variables int + scopes []scope + constants []any + constantsIndex map[any]int + functions []Function + functionsIndex map[string]int + debugInfo map[string]string + nodes []ast.Node + spans []*Span + chains [][]int + arguments []int +} + +type scope struct { + variableName string + index int +} + +func (c *compiler) nodeParent() ast.Node { + if len(c.nodes) > 1 { + return c.nodes[len(c.nodes)-2] + } + return nil +} + +func (c *compiler) emitLocation(loc file.Location, op Opcode, arg int) int { + c.bytecode = append(c.bytecode, op) + current := len(c.bytecode) + c.arguments = append(c.arguments, arg) + c.locations = append(c.locations, loc) + return current +} + +func (c *compiler) emit(op Opcode, args ...int) int { + arg := 0 + if len(args) > 1 { + panic("too many arguments") + } + if len(args) == 1 { + arg = args[0] + } + var loc file.Location + if len(c.nodes) > 0 { + loc = c.nodes[len(c.nodes)-1].Location() + } + return c.emitLocation(loc, op, arg) +} + +func (c *compiler) emitPush(value any) int { + return c.emit(OpPush, c.addConstant(value)) +} + +func (c *compiler) addConstant(constant any) int { + indexable := true + hash := constant + switch reflect.TypeOf(constant).Kind() { + case reflect.Slice, reflect.Map, reflect.Struct, reflect.Func: + indexable = false + } + if field, ok := constant.(*runtime.Field); ok { + indexable = true + hash = fmt.Sprintf("%v", field) + } + if method, ok := constant.(*runtime.Method); ok { + indexable = true + hash = fmt.Sprintf("%v", method) + } + if indexable { + if p, ok := c.constantsIndex[hash]; ok { + return p + } + } + c.constants = append(c.constants, constant) + p := len(c.constants) - 1 + if indexable { + c.constantsIndex[hash] = p + } + return p +} + +func (c *compiler) addVariable(name string) int { + c.variables++ + c.debugInfo[fmt.Sprintf("var_%d", c.variables-1)] = name + return c.variables - 1 +} + +// emitFunction adds builtin.Function.Func to the program.functions and emits call opcode. +func (c *compiler) emitFunction(fn *builtin.Function, argsLen int) { + switch argsLen { + case 0: + c.emit(OpCall0, c.addFunction(fn.Name, fn.Func)) + case 1: + c.emit(OpCall1, c.addFunction(fn.Name, fn.Func)) + case 2: + c.emit(OpCall2, c.addFunction(fn.Name, fn.Func)) + case 3: + c.emit(OpCall3, c.addFunction(fn.Name, fn.Func)) + default: + c.emit(OpLoadFunc, c.addFunction(fn.Name, fn.Func)) + c.emit(OpCallN, argsLen) + } +} + +// addFunction adds builtin.Function.Func to the program.functions and returns its index. +func (c *compiler) addFunction(name string, fn Function) int { + if fn == nil { + panic("function is nil") + } + if p, ok := c.functionsIndex[name]; ok { + return p + } + p := len(c.functions) + c.functions = append(c.functions, fn) + c.functionsIndex[name] = p + c.debugInfo[fmt.Sprintf("func_%d", p)] = name + return p +} + +func (c *compiler) patchJump(placeholder int) { + offset := len(c.bytecode) - placeholder + c.arguments[placeholder-1] = offset +} + +func (c *compiler) calcBackwardJump(to int) int { + return len(c.bytecode) + 1 - to +} + +func (c *compiler) compile(node ast.Node) { + c.nodes = append(c.nodes, node) + defer func() { + c.nodes = c.nodes[:len(c.nodes)-1] + }() + + if c.config != nil && c.config.Profile { + span := &Span{ + Name: reflect.TypeOf(node).String(), + Expression: node.String(), + } + if len(c.spans) > 0 { + prev := c.spans[len(c.spans)-1] + prev.Children = append(prev.Children, span) + } + c.spans = append(c.spans, span) + defer func() { + if len(c.spans) > 1 { + c.spans = c.spans[:len(c.spans)-1] + } + }() + + c.emit(OpProfileStart, c.addConstant(span)) + defer func() { + c.emit(OpProfileEnd, c.addConstant(span)) + }() + } + + switch n := node.(type) { + case *ast.NilNode: + c.NilNode(n) + case *ast.IdentifierNode: + c.IdentifierNode(n) + case *ast.IntegerNode: + c.IntegerNode(n) + case *ast.FloatNode: + c.FloatNode(n) + case *ast.BoolNode: + c.BoolNode(n) + case *ast.StringNode: + c.StringNode(n) + case *ast.BytesNode: + c.BytesNode(n) + case *ast.ConstantNode: + c.ConstantNode(n) + case *ast.UnaryNode: + c.UnaryNode(n) + case *ast.BinaryNode: + c.BinaryNode(n) + case *ast.ChainNode: + c.ChainNode(n) + case *ast.MemberNode: + c.MemberNode(n) + case *ast.SliceNode: + c.SliceNode(n) + case *ast.CallNode: + c.CallNode(n) + case *ast.BuiltinNode: + c.BuiltinNode(n) + case *ast.PredicateNode: + c.PredicateNode(n) + case *ast.PointerNode: + c.PointerNode(n) + case *ast.VariableDeclaratorNode: + c.VariableDeclaratorNode(n) + case *ast.SequenceNode: + c.SequenceNode(n) + case *ast.ConditionalNode: + c.ConditionalNode(n) + case *ast.ArrayNode: + c.ArrayNode(n) + case *ast.MapNode: + c.MapNode(n) + case *ast.PairNode: + c.PairNode(n) + default: + panic(fmt.Sprintf("undefined node type (%T)", node)) + } +} + +func (c *compiler) NilNode(_ *ast.NilNode) { + c.emit(OpNil) +} + +func (c *compiler) IdentifierNode(node *ast.IdentifierNode) { + if index, ok := c.lookupVariable(node.Value); ok { + c.emit(OpLoadVar, index) + return + } + if node.Value == "$env" { + c.emit(OpLoadEnv) + return + } + + var env Nature + if c.config != nil { + env = c.config.Env + } + + if env.IsFastMap() { + c.emit(OpLoadFast, c.addConstant(node.Value)) + } else if ok, index, name := checker.FieldIndex(c.ntCache, env, node); ok { + c.emit(OpLoadField, c.addConstant(&runtime.Field{ + Index: index, + Path: []string{name}, + })) + } else if ok, index, name := checker.MethodIndex(c.ntCache, env, node); ok { + c.emit(OpLoadMethod, c.addConstant(&runtime.Method{ + Name: name, + Index: index, + })) + } else { + c.emit(OpLoadConst, c.addConstant(node.Value)) + } +} + +func (c *compiler) IntegerNode(node *ast.IntegerNode) { + t := node.Type() + if t == nil { + c.emitPush(node.Value) + return + } + switch t.Kind() { + case reflect.Float32: + c.emitPush(float32(node.Value)) + case reflect.Float64: + c.emitPush(float64(node.Value)) + case reflect.Int: + c.emitPush(node.Value) + case reflect.Int8: + if node.Value > math.MaxInt8 || node.Value < math.MinInt8 { + panic(fmt.Sprintf("constant %d overflows int8", node.Value)) + } + c.emitPush(int8(node.Value)) + case reflect.Int16: + if node.Value > math.MaxInt16 || node.Value < math.MinInt16 { + panic(fmt.Sprintf("constant %d overflows int16", node.Value)) + } + c.emitPush(int16(node.Value)) + case reflect.Int32: + if node.Value > math.MaxInt32 || node.Value < math.MinInt32 { + panic(fmt.Sprintf("constant %d overflows int32", node.Value)) + } + c.emitPush(int32(node.Value)) + case reflect.Int64: + c.emitPush(int64(node.Value)) + case reflect.Uint: + if node.Value < 0 { + panic(fmt.Sprintf("constant %d overflows uint", node.Value)) + } + c.emitPush(uint(node.Value)) + case reflect.Uint8: + if node.Value > math.MaxUint8 || node.Value < 0 { + panic(fmt.Sprintf("constant %d overflows uint8", node.Value)) + } + c.emitPush(uint8(node.Value)) + case reflect.Uint16: + if node.Value > math.MaxUint16 || node.Value < 0 { + panic(fmt.Sprintf("constant %d overflows uint16", node.Value)) + } + c.emitPush(uint16(node.Value)) + case reflect.Uint32: + if node.Value < 0 { + panic(fmt.Sprintf("constant %d overflows uint32", node.Value)) + } + c.emitPush(uint32(node.Value)) + case reflect.Uint64: + if node.Value < 0 { + panic(fmt.Sprintf("constant %d overflows uint64", node.Value)) + } + c.emitPush(uint64(node.Value)) + default: + c.emitPush(node.Value) + } +} + +func (c *compiler) FloatNode(node *ast.FloatNode) { + switch node.Type().Kind() { + case reflect.Float32: + c.emitPush(float32(node.Value)) + case reflect.Float64: + c.emitPush(node.Value) + default: + c.emitPush(node.Value) + } +} + +func (c *compiler) BoolNode(node *ast.BoolNode) { + if node.Value { + c.emit(OpTrue) + } else { + c.emit(OpFalse) + } +} + +func (c *compiler) StringNode(node *ast.StringNode) { + c.emitPush(node.Value) +} + +func (c *compiler) BytesNode(node *ast.BytesNode) { + c.emitPush(node.Value) +} + +func (c *compiler) ConstantNode(node *ast.ConstantNode) { + if node.Value == nil { + c.emit(OpNil) + return + } + c.emitPush(node.Value) +} + +func (c *compiler) UnaryNode(node *ast.UnaryNode) { + c.compile(node.Node) + c.derefInNeeded(node.Node) + + switch node.Operator { + + case "!", "not": + c.emit(OpNot) + + case "+": + // Do nothing + + case "-": + c.emit(OpNegate) + + default: + panic(fmt.Sprintf("unknown operator (%v)", node.Operator)) + } +} + +func (c *compiler) BinaryNode(node *ast.BinaryNode) { + switch node.Operator { + case "==": + c.equalBinaryNode(node) + + case "!=": + c.equalBinaryNode(node) + c.emit(OpNot) + + case "or", "||": + if c.config != nil && !c.config.ShortCircuit { + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpOr) + break + } + c.compile(node.Left) + c.derefInNeeded(node.Left) + end := c.emit(OpJumpIfTrue, placeholder) + c.emit(OpPop) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.patchJump(end) + + case "and", "&&": + if c.config != nil && !c.config.ShortCircuit { + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpAnd) + break + } + c.compile(node.Left) + c.derefInNeeded(node.Left) + end := c.emit(OpJumpIfFalse, placeholder) + c.emit(OpPop) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.patchJump(end) + + case "<": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpLess) + + case ">": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpMore) + + case "<=": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpLessOrEqual) + + case ">=": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpMoreOrEqual) + + case "+": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpAdd) + + case "-": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpSubtract) + + case "*": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpMultiply) + + case "/": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpDivide) + + case "%": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpModulo) + + case "**", "^": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpExponent) + + case "in": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpIn) + + case "matches": + if str, ok := node.Right.(*ast.StringNode); ok { + re, err := regexp.Compile(str.Value) + if err != nil { + panic(err) + } + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.emit(OpMatchesConst, c.addConstant(re)) + } else { + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpMatches) + } + + case "contains": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpContains) + + case "startsWith": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpStartsWith) + + case "endsWith": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpEndsWith) + + case "..": + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.emit(OpRange) + + case "??": + c.compile(node.Left) + c.derefInNeeded(node.Left) + end := c.emit(OpJumpIfNotNil, placeholder) + c.emit(OpPop) + c.compile(node.Right) + c.derefInNeeded(node.Right) + c.patchJump(end) + + default: + panic(fmt.Sprintf("unknown operator (%v)", node.Operator)) + + } +} + +func (c *compiler) equalBinaryNode(node *ast.BinaryNode) { + l := kind(node.Left.Type()) + r := kind(node.Right.Type()) + + leftIsSimple := isSimpleType(node.Left) + rightIsSimple := isSimpleType(node.Right) + leftAndRightAreSimple := leftIsSimple && rightIsSimple + + c.compile(node.Left) + c.derefInNeeded(node.Left) + c.compile(node.Right) + c.derefInNeeded(node.Right) + + if l == r && l == reflect.Int && leftAndRightAreSimple { + c.emit(OpEqualInt) + } else if l == r && l == reflect.String && leftAndRightAreSimple { + c.emit(OpEqualString) + } else { + c.emit(OpEqual) + } +} + +func isSimpleType(node ast.Node) bool { + if node == nil { + return false + } + t := node.Type() + if t == nil { + return false + } + return t.PkgPath() == "" +} + +func (c *compiler) ChainNode(node *ast.ChainNode) { + c.chains = append(c.chains, []int{}) + c.compile(node.Node) + for _, ph := range c.chains[len(c.chains)-1] { + c.patchJump(ph) // If chain activated jump here (got nit somewhere). + } + parent := c.nodeParent() + if binary, ok := parent.(*ast.BinaryNode); ok && binary.Operator == "??" { + // If chain is used in nil coalescing operator, we can omit + // nil push at the end of the chain. The ?? operator will + // handle it. + } else { + // We need to put the nil on the stack, otherwise "typed" + // nil will be used as a result of the chain. + j := c.emit(OpJumpIfNotNil, placeholder) + c.emit(OpPop) + c.emit(OpNil) + c.patchJump(j) + } + c.chains = c.chains[:len(c.chains)-1] +} + +func (c *compiler) MemberNode(node *ast.MemberNode) { + var env Nature + if c.config != nil { + env = c.config.Env + } + + if ok, index, name := checker.MethodIndex(c.ntCache, env, node); ok { + c.compile(node.Node) + c.emit(OpMethod, c.addConstant(&runtime.Method{ + Name: name, + Index: index, + })) + return + } + op := OpFetch + base := node.Node + + ok, index, nodeName := checker.FieldIndex(c.ntCache, env, node) + path := []string{nodeName} + + if ok { + op = OpFetchField + for !node.Optional { + if ident, isIdent := base.(*ast.IdentifierNode); isIdent { + if ok, identIndex, name := checker.FieldIndex(c.ntCache, env, ident); ok { + index = append(identIndex, index...) + path = append([]string{name}, path...) + c.emitLocation(ident.Location(), OpLoadField, c.addConstant( + &runtime.Field{Index: index, Path: path}, + )) + return + } + } + + if member, isMember := base.(*ast.MemberNode); isMember { + if ok, memberIndex, name := checker.FieldIndex(c.ntCache, env, member); ok { + index = append(memberIndex, index...) + path = append([]string{name}, path...) + node = member + base = member.Node + } else { + break + } + } else { + break + } + } + } + + c.compile(base) + // If the field is optional, we need to jump over the fetch operation. + // If no ChainNode (none c.chains) is used, do not compile the optional fetch. + if node.Optional && len(c.chains) > 0 { + ph := c.emit(OpJumpIfNil, placeholder) + c.chains[len(c.chains)-1] = append(c.chains[len(c.chains)-1], ph) + } + + if op == OpFetch { + c.compile(node.Property) + deref := true + // If the map key is a pointer, we should not dereference the property. + if node.Node.Type() != nil && node.Node.Type().Kind() == reflect.Map { + keyType := node.Node.Type().Key() + propType := node.Property.Type() + if propType != nil && propType.AssignableTo(keyType) { + deref = false + } + } + if deref { + c.derefInNeeded(node.Property) + } + c.emit(OpFetch) + } else { + c.emitLocation(node.Location(), op, c.addConstant( + &runtime.Field{Index: index, Path: path}, + )) + } +} + +func (c *compiler) SliceNode(node *ast.SliceNode) { + c.compile(node.Node) + if node.To != nil { + c.compile(node.To) + c.derefInNeeded(node.To) + } else { + c.emit(OpLen) + } + if node.From != nil { + c.compile(node.From) + c.derefInNeeded(node.From) + } else { + c.emitPush(0) + } + c.emit(OpSlice) +} + +func (c *compiler) CallNode(node *ast.CallNode) { + fn := node.Callee.Type() + if fn.Kind() == reflect.Func { + fnInOffset := 0 + fnNumIn := fn.NumIn() + switch callee := node.Callee.(type) { + case *ast.MemberNode: + if prop, ok := callee.Property.(*ast.StringNode); ok { + if _, ok = callee.Node.Type().MethodByName(prop.Value); ok && callee.Node.Type().Kind() != reflect.Interface { + fnInOffset = 1 + fnNumIn-- + } + } + case *ast.IdentifierNode: + if t, ok := c.config.Env.MethodByName(c.ntCache, callee.Value); ok && t.Method { + fnInOffset = 1 + fnNumIn-- + } + } + for i, arg := range node.Arguments { + c.compile(arg) + + var in reflect.Type + if fn.IsVariadic() && i >= fnNumIn-1 { + in = fn.In(fn.NumIn() - 1).Elem() + } else { + in = fn.In(i + fnInOffset) + } + + c.derefParam(in, arg) + } + } else { + for _, arg := range node.Arguments { + c.compile(arg) + } + } + + if ident, ok := node.Callee.(*ast.IdentifierNode); ok { + if c.config != nil { + if fn, ok := c.config.Functions[ident.Value]; ok { + c.emitFunction(fn, len(node.Arguments)) + return + } + } + } + c.compile(node.Callee) + + if c.config != nil { + isMethod, _, _ := checker.MethodIndex(c.ntCache, c.config.Env, node.Callee) + if index, ok := checker.TypedFuncIndex(node.Callee.Type(), isMethod); ok { + c.emit(OpCallTyped, index) + return + } else if checker.IsFastFunc(node.Callee.Type(), isMethod) { + c.emit(OpCallFast, len(node.Arguments)) + } else { + c.emit(OpCall, len(node.Arguments)) + } + } else { + c.emit(OpCall, len(node.Arguments)) + } +} + +func (c *compiler) BuiltinNode(node *ast.BuiltinNode) { + switch node.Name { + case "all": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + var loopBreak int + c.emitLoop(func() { + c.compile(node.Arguments[1]) + loopBreak = c.emit(OpJumpIfFalse, placeholder) + c.emit(OpPop) + }) + c.emit(OpTrue) + c.patchJump(loopBreak) + c.emit(OpEnd) + return + + case "none": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + var loopBreak int + c.emitLoop(func() { + c.compile(node.Arguments[1]) + c.emit(OpNot) + loopBreak = c.emit(OpJumpIfFalse, placeholder) + c.emit(OpPop) + }) + c.emit(OpTrue) + c.patchJump(loopBreak) + c.emit(OpEnd) + return + + case "any": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + var loopBreak int + c.emitLoop(func() { + c.compile(node.Arguments[1]) + loopBreak = c.emit(OpJumpIfTrue, placeholder) + c.emit(OpPop) + }) + c.emit(OpFalse) + c.patchJump(loopBreak) + c.emit(OpEnd) + return + + case "one": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + c.emitLoop(func() { + c.compile(node.Arguments[1]) + c.emitCond(func() { + c.emit(OpIncrementCount) + }) + }) + c.emit(OpGetCount) + c.emitPush(1) + c.emit(OpEqual) + c.emit(OpEnd) + return + + case "filter": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + c.emitLoop(func() { + c.compile(node.Arguments[1]) + c.emitCond(func() { + c.emit(OpIncrementCount) + if node.Map != nil { + c.compile(node.Map) + } else { + c.emit(OpPointer) + } + }) + }) + c.emit(OpGetCount) + c.emit(OpEnd) + c.emit(OpArray) + return + + case "map": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + c.emitLoop(func() { + c.compile(node.Arguments[1]) + }) + c.emit(OpGetLen) + c.emit(OpEnd) + c.emit(OpArray) + return + + case "count": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + var loopBreak int + c.emitLoop(func() { + if len(node.Arguments) == 2 { + c.compile(node.Arguments[1]) + } else { + c.emit(OpPointer) + } + c.emitCond(func() { + c.emit(OpIncrementCount) + // Early termination if threshold is set + if node.Threshold != nil { + c.emit(OpGetCount) + c.emit(OpInt, *node.Threshold) + c.emit(OpMoreOrEqual) + loopBreak = c.emit(OpJumpIfTrue, placeholder) + c.emit(OpPop) + } + }) + }) + c.emit(OpGetCount) + if node.Threshold != nil { + end := c.emit(OpJump, placeholder) + c.patchJump(loopBreak) + // Early exit path: pop the bool comparison result, push count + c.emit(OpPop) + c.emit(OpGetCount) + c.patchJump(end) + } + c.emit(OpEnd) + return + + case "sum": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + c.emit(OpInt, 0) + c.emit(OpSetAcc) + c.emitLoop(func() { + if len(node.Arguments) == 2 { + c.compile(node.Arguments[1]) + } else { + c.emit(OpPointer) + } + c.emit(OpGetAcc) + c.emit(OpAdd) + c.emit(OpSetAcc) + }) + c.emit(OpGetAcc) + c.emit(OpEnd) + return + + case "find": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + var loopBreak int + c.emitLoop(func() { + c.compile(node.Arguments[1]) + noop := c.emit(OpJumpIfFalse, placeholder) + c.emit(OpPop) + if node.Map != nil { + c.compile(node.Map) + } else { + c.emit(OpPointer) + } + loopBreak = c.emit(OpJump, placeholder) + c.patchJump(noop) + c.emit(OpPop) + }) + if node.Throws { + c.emit(OpPush, c.addConstant(fmt.Errorf("reflect: slice index out of range"))) + c.emit(OpThrow) + } else { + c.emit(OpNil) + } + c.patchJump(loopBreak) + c.emit(OpEnd) + return + + case "findIndex": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + var loopBreak int + c.emitLoop(func() { + c.compile(node.Arguments[1]) + noop := c.emit(OpJumpIfFalse, placeholder) + c.emit(OpPop) + c.emit(OpGetIndex) + loopBreak = c.emit(OpJump, placeholder) + c.patchJump(noop) + c.emit(OpPop) + }) + c.emit(OpNil) + c.patchJump(loopBreak) + c.emit(OpEnd) + return + + case "findLast": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + var loopBreak int + c.emitLoopBackwards(func() { + c.compile(node.Arguments[1]) + noop := c.emit(OpJumpIfFalse, placeholder) + c.emit(OpPop) + if node.Map != nil { + c.compile(node.Map) + } else { + c.emit(OpPointer) + } + loopBreak = c.emit(OpJump, placeholder) + c.patchJump(noop) + c.emit(OpPop) + }) + if node.Throws { + c.emit(OpPush, c.addConstant(fmt.Errorf("reflect: slice index out of range"))) + c.emit(OpThrow) + } else { + c.emit(OpNil) + } + c.patchJump(loopBreak) + c.emit(OpEnd) + return + + case "findLastIndex": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + var loopBreak int + c.emitLoopBackwards(func() { + c.compile(node.Arguments[1]) + noop := c.emit(OpJumpIfFalse, placeholder) + c.emit(OpPop) + c.emit(OpGetIndex) + loopBreak = c.emit(OpJump, placeholder) + c.patchJump(noop) + c.emit(OpPop) + }) + c.emit(OpNil) + c.patchJump(loopBreak) + c.emit(OpEnd) + return + + case "groupBy": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + c.emit(OpCreate, 1) + c.emit(OpSetAcc) + c.emitLoop(func() { + c.compile(node.Arguments[1]) + c.emit(OpGroupBy) + }) + c.emit(OpGetAcc) + c.emit(OpEnd) + return + + case "sortBy": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + if len(node.Arguments) == 3 { + c.compile(node.Arguments[2]) + } else { + c.emit(OpPush, c.addConstant("asc")) + } + c.emit(OpCreate, 2) + c.emit(OpSetAcc) + c.emitLoop(func() { + c.compile(node.Arguments[1]) + c.emit(OpSortBy) + }) + c.emit(OpSort) + c.emit(OpEnd) + return + + case "reduce": + c.compile(node.Arguments[0]) + c.derefInNeeded(node.Arguments[0]) + c.emit(OpBegin) + if len(node.Arguments) == 3 { + c.compile(node.Arguments[2]) + c.derefInNeeded(node.Arguments[2]) + c.emit(OpSetAcc) + } else { + // When no initial value is provided, we use the first element as the + // accumulator. But first we must check if the array is empty to avoid + // an index out of range panic. + empty := c.emit(OpJumpIfEnd, placeholder) + c.emit(OpPointer) + c.emit(OpIncrementIndex) + c.emit(OpSetAcc) + jumpPastError := c.emit(OpJump, placeholder) + c.patchJump(empty) + c.emit(OpPush, c.addConstant(fmt.Errorf("reduce of empty array with no initial value"))) + c.emit(OpThrow) + c.patchJump(jumpPastError) + } + c.emitLoop(func() { + c.compile(node.Arguments[1]) + c.emit(OpSetAcc) + }) + c.emit(OpGetAcc) + c.emit(OpEnd) + return + + } + + if id, ok := builtin.Index[node.Name]; ok { + f := builtin.Builtins[id] + for i, arg := range node.Arguments { + c.compile(arg) + argType := arg.Type() + if argType.Kind() == reflect.Ptr || arg.Nature().IsUnknown(c.ntCache) { + if f.Deref == nil { + // By default, builtins expect arguments to be dereferenced. + c.emit(OpDeref) + } else { + if f.Deref(i, argType) { + c.emit(OpDeref) + } + } + } + } + + if f.Fast != nil { + c.emit(OpCallBuiltin1, id) + } else if f.Safe != nil { + id := c.addConstant(f.Safe) + c.emit(OpPush, id) + c.debugInfo[fmt.Sprintf("const_%d", id)] = node.Name + c.emit(OpCallSafe, len(node.Arguments)) + } else if f.Func != nil { + c.emitFunction(f, len(node.Arguments)) + } + return + } + + panic(fmt.Sprintf("unknown builtin %v", node.Name)) +} + +func (c *compiler) emitCond(body func()) { + noop := c.emit(OpJumpIfFalse, placeholder) + c.emit(OpPop) + + body() + + jmp := c.emit(OpJump, placeholder) + c.patchJump(noop) + c.emit(OpPop) + c.patchJump(jmp) +} + +func (c *compiler) emitLoop(body func()) { + begin := len(c.bytecode) + end := c.emit(OpJumpIfEnd, placeholder) + + body() + + c.emit(OpIncrementIndex) + c.emit(OpJumpBackward, c.calcBackwardJump(begin)) + c.patchJump(end) +} + +func (c *compiler) emitLoopBackwards(body func()) { + c.emit(OpGetLen) + c.emit(OpInt, 1) + c.emit(OpSubtract) + c.emit(OpSetIndex) + begin := len(c.bytecode) + c.emit(OpGetIndex) + c.emit(OpInt, 0) + c.emit(OpMoreOrEqual) + end := c.emit(OpJumpIfFalse, placeholder) + + body() + + c.emit(OpDecrementIndex) + c.emit(OpJumpBackward, c.calcBackwardJump(begin)) + c.patchJump(end) +} + +func (c *compiler) PredicateNode(node *ast.PredicateNode) { + c.compile(node.Node) +} + +func (c *compiler) PointerNode(node *ast.PointerNode) { + switch node.Name { + case "index": + c.emit(OpGetIndex) + case "acc": + c.emit(OpGetAcc) + case "": + c.emit(OpPointer) + default: + panic(fmt.Sprintf("unknown pointer %v", node.Name)) + } +} + +func (c *compiler) VariableDeclaratorNode(node *ast.VariableDeclaratorNode) { + c.compile(node.Value) + index := c.addVariable(node.Name) + c.emit(OpStore, index) + c.beginScope(node.Name, index) + c.compile(node.Expr) + c.endScope() +} + +func (c *compiler) SequenceNode(node *ast.SequenceNode) { + for i, n := range node.Nodes { + c.compile(n) + if i < len(node.Nodes)-1 { + c.emit(OpPop) + } + } +} + +func (c *compiler) beginScope(name string, index int) { + c.scopes = append(c.scopes, scope{name, index}) +} + +func (c *compiler) endScope() { + c.scopes = c.scopes[:len(c.scopes)-1] +} + +func (c *compiler) lookupVariable(name string) (int, bool) { + for i := len(c.scopes) - 1; i >= 0; i-- { + if c.scopes[i].variableName == name { + return c.scopes[i].index, true + } + } + return 0, false +} + +func (c *compiler) ConditionalNode(node *ast.ConditionalNode) { + c.compile(node.Cond) + c.derefInNeeded(node.Cond) + otherwise := c.emit(OpJumpIfFalse, placeholder) + + c.emit(OpPop) + c.compile(node.Exp1) + end := c.emit(OpJump, placeholder) + + c.patchJump(otherwise) + c.emit(OpPop) + c.compile(node.Exp2) + + c.patchJump(end) +} + +func (c *compiler) ArrayNode(node *ast.ArrayNode) { + for _, node := range node.Nodes { + c.compile(node) + } + + c.emitPush(len(node.Nodes)) + c.emit(OpArray) +} + +func (c *compiler) MapNode(node *ast.MapNode) { + for _, pair := range node.Pairs { + c.compile(pair) + } + + c.emitPush(len(node.Pairs)) + c.emit(OpMap) +} + +func (c *compiler) PairNode(node *ast.PairNode) { + c.compile(node.Key) + c.compile(node.Value) +} + +func (c *compiler) derefInNeeded(node ast.Node) { + if node.Nature().Nil { + return + } + switch node.Type().Kind() { + case reflect.Ptr, reflect.Interface: + c.emit(OpDeref) + } +} + +func (c *compiler) derefParam(in reflect.Type, param ast.Node) { + if param.Nature().Nil { + return + } + if param.Type().AssignableTo(in) { + return + } + if in.Kind() != reflect.Ptr && param.Type().Kind() == reflect.Ptr { + c.emit(OpDeref) + } +} + +func (c *compiler) optimize() { + for i, op := range c.bytecode { + switch op { + case OpJumpIfTrue, OpJumpIfFalse, OpJumpIfNil, OpJumpIfNotNil: + target := i + c.arguments[i] + 1 + for target < len(c.bytecode) && c.bytecode[target] == op { + target += c.arguments[target] + 1 + } + c.arguments[i] = target - i - 1 + } + } +} + +func kind(t reflect.Type) reflect.Kind { + if t == nil { + return reflect.Invalid + } + return t.Kind() +} diff --git a/vendor/github.com/expr-lang/expr/conf/config.go b/vendor/github.com/expr-lang/expr/conf/config.go new file mode 100644 index 00000000000..f7c95d203da --- /dev/null +++ b/vendor/github.com/expr-lang/expr/conf/config.go @@ -0,0 +1,107 @@ +package conf + +import ( + "fmt" + "reflect" + + "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/builtin" + "github.com/expr-lang/expr/checker/nature" + "github.com/expr-lang/expr/vm/runtime" +) + +var ( + // DefaultMemoryBudget represents default maximum allowed memory usage by the vm.VM. + DefaultMemoryBudget uint = 1e6 + + // DefaultMaxNodes represents default maximum allowed AST nodes by the compiler. + DefaultMaxNodes uint = 1e4 +) + +type FunctionsTable map[string]*builtin.Function + +type Config struct { + EnvObject any + Env nature.Nature + Expect reflect.Kind + ExpectAny bool + Optimize bool + Strict bool + ShortCircuit bool + Profile bool + MaxNodes uint + ConstFns map[string]reflect.Value + Visitors []ast.Visitor + Functions FunctionsTable + Builtins FunctionsTable + Disabled map[string]bool // disabled builtins + NtCache nature.Cache + // DisableIfOperator disables the built-in `if ... { } else { }` operator syntax + // so that users can use a custom function named `if(...)` without conflicts. + // When enabled, the lexer treats `if`/`else` as identifiers and the parser + // will not parse `if` statements. + DisableIfOperator bool +} + +// CreateNew creates new config with default values. +func CreateNew() *Config { + c := &Config{ + Optimize: true, + ShortCircuit: true, + MaxNodes: DefaultMaxNodes, + ConstFns: make(map[string]reflect.Value), + Functions: make(map[string]*builtin.Function), + Builtins: make(map[string]*builtin.Function), + Disabled: make(map[string]bool), + } + for _, f := range builtin.Builtins { + c.Builtins[f.Name] = f + } + return c +} + +// New creates new config with environment. +func New(env any) *Config { + c := CreateNew() + c.WithEnv(env) + return c +} + +func (c *Config) WithEnv(env any) { + c.EnvObject = env + c.Env = EnvWithCache(&c.NtCache, env) + c.Strict = c.Env.Strict +} + +func (c *Config) ConstExpr(name string) { + if c.EnvObject == nil { + panic("no environment is specified for ConstExpr()") + } + fn := reflect.ValueOf(runtime.Fetch(c.EnvObject, name)) + if fn.Kind() != reflect.Func { + panic(fmt.Errorf("const expression %q must be a function", name)) + } + c.ConstFns[name] = fn +} + +type Checker interface { + Check() +} + +func (c *Config) Check() { + for _, v := range c.Visitors { + if c, ok := v.(Checker); ok { + c.Check() + } + } +} + +func (c *Config) IsOverridden(name string) bool { + if _, ok := c.Functions[name]; ok { + return true + } + if _, ok := c.Env.Get(&c.NtCache, name); ok { + return true + } + return false +} diff --git a/vendor/github.com/expr-lang/expr/conf/env.go b/vendor/github.com/expr-lang/expr/conf/env.go new file mode 100644 index 00000000000..0acd4457091 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/conf/env.go @@ -0,0 +1,76 @@ +package conf + +import ( + "fmt" + "reflect" + + . "github.com/expr-lang/expr/checker/nature" + "github.com/expr-lang/expr/internal/deref" + "github.com/expr-lang/expr/types" +) + +// Env returns the Nature of the given environment. +// +// Deprecated: use EnvWithCache instead. +func Env(env any) Nature { + return EnvWithCache(new(Cache), env) +} + +func EnvWithCache(c *Cache, env any) Nature { + if env == nil { + n := c.NatureOf(map[string]any{}) + n.Strict = true + return n + } + + switch env := env.(type) { + case types.Map: + nt := env.Nature() + return nt + } + + v := reflect.ValueOf(env) + t := v.Type() + + switch deref.Value(v).Kind() { + case reflect.Struct: + n := c.FromType(t) + n.Strict = true + return n + + case reflect.Map: + n := c.FromType(v.Type()) + if n.TypeData == nil { + n.TypeData = new(TypeData) + } + n.Strict = true + n.Fields = make(map[string]Nature, v.Len()) + + for _, key := range v.MapKeys() { + elem := v.MapIndex(key) + if !elem.IsValid() || !elem.CanInterface() { + panic(fmt.Sprintf("invalid map value: %s", key)) + } + + face := elem.Interface() + + switch face := face.(type) { + case types.Map: + nt := face.Nature() + n.Fields[key.String()] = nt + + default: + if face == nil { + n.Fields[key.String()] = c.NatureOf(nil) + continue + } + n.Fields[key.String()] = c.NatureOf(face) + } + + } + + return n + } + + panic(fmt.Sprintf("unknown type %T", env)) +} diff --git a/vendor/github.com/expr-lang/expr/expr.go b/vendor/github.com/expr-lang/expr/expr.go new file mode 100644 index 00000000000..76fbd426f62 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/expr.go @@ -0,0 +1,290 @@ +package expr + +import ( + "errors" + "fmt" + "reflect" + "time" + + "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/builtin" + "github.com/expr-lang/expr/checker" + "github.com/expr-lang/expr/compiler" + "github.com/expr-lang/expr/conf" + "github.com/expr-lang/expr/file" + "github.com/expr-lang/expr/optimizer" + "github.com/expr-lang/expr/parser" + "github.com/expr-lang/expr/patcher" + "github.com/expr-lang/expr/vm" +) + +// Option for configuring config. +type Option func(c *conf.Config) + +// Env specifies expected input of env for type checks. +// If struct is passed, all fields will be treated as variables, +// as well as all fields of embedded structs and struct itself. +// If map is passed, all items will be treated as variables. +// Methods defined on this type will be available as functions. +func Env(env any) Option { + return func(c *conf.Config) { + c.WithEnv(env) + } +} + +// AllowUndefinedVariables allows to use undefined variables inside expressions. +// This can be used with expr.Env option to partially define a few variables. +func AllowUndefinedVariables() Option { + return func(c *conf.Config) { + c.Strict = false + } +} + +// Operator allows to replace a binary operator with a function. +func Operator(operator string, fn ...string) Option { + return func(c *conf.Config) { + p := &patcher.OperatorOverloading{ + Operator: operator, + Overloads: fn, + Env: &c.Env, + Functions: c.Functions, + NtCache: &c.NtCache, + } + c.Visitors = append(c.Visitors, p) + } +} + +// ConstExpr defines func expression as constant. If all argument to this function is constants, +// then it can be replaced by result of this func call on compile step. +func ConstExpr(fn string) Option { + return func(c *conf.Config) { + c.ConstExpr(fn) + } +} + +// AsAny tells the compiler to expect any result. +func AsAny() Option { + return func(c *conf.Config) { + c.ExpectAny = true + } +} + +// AsKind tells the compiler to expect kind of the result. +func AsKind(kind reflect.Kind) Option { + return func(c *conf.Config) { + c.Expect = kind + c.ExpectAny = true + } +} + +// AsBool tells the compiler to expect a boolean result. +func AsBool() Option { + return func(c *conf.Config) { + c.Expect = reflect.Bool + c.ExpectAny = true + } +} + +// AsInt tells the compiler to expect an int result. +func AsInt() Option { + return func(c *conf.Config) { + c.Expect = reflect.Int + c.ExpectAny = true + } +} + +// AsInt64 tells the compiler to expect an int64 result. +func AsInt64() Option { + return func(c *conf.Config) { + c.Expect = reflect.Int64 + c.ExpectAny = true + } +} + +// AsFloat64 tells the compiler to expect a float64 result. +func AsFloat64() Option { + return func(c *conf.Config) { + c.Expect = reflect.Float64 + c.ExpectAny = true + } +} + +// DisableIfOperator disables the `if ... else ...` operator syntax so a custom +// function named `if(...)` can be used without conflicts. +func DisableIfOperator() Option { + return func(c *conf.Config) { + c.DisableIfOperator = true + } +} + +// WarnOnAny tells the compiler to warn if expression return any type. +func WarnOnAny() Option { + return func(c *conf.Config) { + if c.Expect == reflect.Invalid { + panic("WarnOnAny() works only with combination with AsInt(), AsBool(), etc. options") + } + c.ExpectAny = false + } +} + +// Optimize turns optimizations on or off. +func Optimize(b bool) Option { + return func(c *conf.Config) { + c.Optimize = b + } +} + +// DisableShortCircuit turns short circuit off. +func DisableShortCircuit() Option { + return func(c *conf.Config) { + c.ShortCircuit = false + } +} + +// Patch adds visitor to list of visitors what will be applied before compiling AST to bytecode. +func Patch(visitor ast.Visitor) Option { + return func(c *conf.Config) { + c.Visitors = append(c.Visitors, visitor) + } +} + +// Function adds function to list of functions what will be available in expressions. +func Function(name string, fn func(params ...any) (any, error), types ...any) Option { + return func(c *conf.Config) { + ts := make([]reflect.Type, len(types)) + for i, t := range types { + t := reflect.TypeOf(t) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() != reflect.Func { + panic(fmt.Sprintf("expr: type of %s is not a function", name)) + } + ts[i] = t + } + c.Functions[name] = &builtin.Function{ + Name: name, + Func: fn, + Types: ts, + } + } +} + +// DisableAllBuiltins disables all builtins. +func DisableAllBuiltins() Option { + return func(c *conf.Config) { + for name := range c.Builtins { + c.Disabled[name] = true + } + } +} + +// DisableBuiltin disables builtin function. +func DisableBuiltin(name string) Option { + return func(c *conf.Config) { + c.Disabled[name] = true + } +} + +// EnableBuiltin enables builtin function. +func EnableBuiltin(name string) Option { + return func(c *conf.Config) { + delete(c.Disabled, name) + } +} + +// WithContext passes context to all functions calls with a context.Context argument. +func WithContext(name string) Option { + return func(c *conf.Config) { + c.Visitors = append(c.Visitors, patcher.WithContext{ + Name: name, + Functions: c.Functions, + Env: &c.Env, + NtCache: &c.NtCache, + }) + } +} + +// Timezone sets default timezone for date() and now() builtin functions. +func Timezone(name string) Option { + tz, err := time.LoadLocation(name) + if err != nil { + panic(err) + } + return Patch(patcher.WithTimezone{ + Location: tz, + }) +} + +// MaxNodes sets the maximum number of nodes allowed in the expression. +// By default, the maximum number of nodes is conf.DefaultMaxNodes. +// If MaxNodes is set to 0, the node budget check is disabled. +func MaxNodes(n uint) Option { + return func(c *conf.Config) { + c.MaxNodes = n + } +} + +// Compile parses and compiles given input expression to bytecode program. +func Compile(input string, ops ...Option) (*vm.Program, error) { + config := conf.CreateNew() + for _, op := range ops { + op(config) + } + for name := range config.Disabled { + delete(config.Builtins, name) + } + config.Check() + + tree, err := checker.ParseCheck(input, config) + if err != nil { + return nil, err + } + + if config.Optimize { + err = optimizer.Optimize(&tree.Node, config) + if err != nil { + var fileError *file.Error + if errors.As(err, &fileError) { + return nil, fileError.Bind(tree.Source) + } + return nil, err + } + } + + program, err := compiler.Compile(tree, config) + if err != nil { + return nil, err + } + + return program, nil +} + +// Run evaluates given bytecode program. +func Run(program *vm.Program, env any) (any, error) { + return vm.Run(program, env) +} + +// Eval parses, compiles and runs given input. +func Eval(input string, env any) (any, error) { + if _, ok := env.(Option); ok { + return nil, fmt.Errorf("misused expr.Eval: second argument (env) should be passed without expr.Env") + } + + tree, err := parser.Parse(input) + if err != nil { + return nil, err + } + + program, err := compiler.Compile(tree, nil) + if err != nil { + return nil, err + } + + output, err := Run(program, env) + if err != nil { + return nil, err + } + + return output, nil +} diff --git a/vendor/github.com/expr-lang/expr/file/error.go b/vendor/github.com/expr-lang/expr/file/error.go new file mode 100644 index 00000000000..c398ed59cbe --- /dev/null +++ b/vendor/github.com/expr-lang/expr/file/error.go @@ -0,0 +1,85 @@ +package file + +import ( + "fmt" + "strings" +) + +type Error struct { + Location + Line int `json:"line"` + Column int `json:"column"` + Message string `json:"message"` + Snippet string `json:"snippet"` + Prev error `json:"prev"` +} + +func (e *Error) Error() string { + return e.format() +} + +var tabReplacer = strings.NewReplacer("\t", " ") + +func (e *Error) Bind(source Source) *Error { + src := source.String() + + var runeCount, lineStart int + e.Line = 1 + e.Column = 0 + for i, r := range src { + if runeCount == e.From { + break + } + if r == '\n' { + lineStart = i + 1 + e.Line++ + e.Column = 0 + } else { + e.Column++ + } + runeCount++ + } + + lineEnd := lineStart + strings.IndexByte(src[lineStart:], '\n') + if lineEnd < lineStart { + lineEnd = len(src) + } + if lineStart == lineEnd { + return e + } + + const prefix = "\n | " + line := src[lineStart:lineEnd] + snippet := new(strings.Builder) + snippet.Grow(2*len(prefix) + len(line) + e.Column + 1) + snippet.WriteString(prefix) + tabReplacer.WriteString(snippet, line) + snippet.WriteString(prefix) + for i := 0; i < e.Column; i++ { + snippet.WriteByte('.') + } + snippet.WriteByte('^') + e.Snippet = snippet.String() + return e +} + +func (e *Error) Unwrap() error { + return e.Prev +} + +func (e *Error) Wrap(err error) { + e.Prev = err +} + +func (e *Error) format() string { + if e.Snippet == "" { + return e.Message + } + return fmt.Sprintf( + "%s (%d:%d)%s", + e.Message, + e.Line, + e.Column+1, // add one to the 0-based column for display + e.Snippet, + ) +} diff --git a/vendor/github.com/expr-lang/expr/file/location.go b/vendor/github.com/expr-lang/expr/file/location.go new file mode 100644 index 00000000000..6c6bc2427ec --- /dev/null +++ b/vendor/github.com/expr-lang/expr/file/location.go @@ -0,0 +1,6 @@ +package file + +type Location struct { + From int `json:"from"` + To int `json:"to"` +} diff --git a/vendor/github.com/expr-lang/expr/file/source.go b/vendor/github.com/expr-lang/expr/file/source.go new file mode 100644 index 00000000000..b11bb5f9d60 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/file/source.go @@ -0,0 +1,36 @@ +package file + +import "strings" + +type Source struct { + raw string +} + +func NewSource(contents string) Source { + return Source{ + raw: contents, + } +} + +func (s Source) String() string { + return s.raw +} + +func (s Source) Snippet(line int) (string, bool) { + if s.raw == "" { + return "", false + } + var start int + for i := 1; i < line; i++ { + pos := strings.IndexByte(s.raw[start:], '\n') + if pos < 0 { + return "", false + } + start += pos + 1 + } + end := start + strings.IndexByte(s.raw[start:], '\n') + if end < start { + end = len(s.raw) + } + return s.raw[start:end], true +} diff --git a/vendor/github.com/expr-lang/expr/internal/deref/deref.go b/vendor/github.com/expr-lang/expr/internal/deref/deref.go new file mode 100644 index 00000000000..4ad7877f8d8 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/internal/deref/deref.go @@ -0,0 +1,56 @@ +package deref + +import ( + "fmt" + "reflect" +) + +func Interface(p any) any { + if p == nil { + return nil + } + + v := reflect.ValueOf(p) + + for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { + if v.IsNil() { + return nil + } + v = v.Elem() + } + + if v.IsValid() { + return v.Interface() + } + + panic(fmt.Sprintf("cannot dereference %v", p)) +} + +func Type(t reflect.Type) reflect.Type { + if t == nil { + return nil + } + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t +} + +func Value(v reflect.Value) reflect.Value { + for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { + if v.IsNil() { + return v + } + v = v.Elem() + } + return v +} + +func TypeKind(t reflect.Type, k reflect.Kind) (_ reflect.Type, _ reflect.Kind, changed bool) { + for k == reflect.Pointer { + changed = true + t = t.Elem() + k = t.Kind() + } + return t, k, changed +} diff --git a/vendor/github.com/expr-lang/expr/internal/ring/ring.go b/vendor/github.com/expr-lang/expr/internal/ring/ring.go new file mode 100644 index 00000000000..cc9e727b00e --- /dev/null +++ b/vendor/github.com/expr-lang/expr/internal/ring/ring.go @@ -0,0 +1,85 @@ +package ring + +// Ring is a very simple ring buffer implementation that uses a slice. The +// internal slice will only grow, never shrink. When it grows, it grows in +// chunks of "chunkSize" (given as argument in the [New] function). Pointer and +// reference types can be safely used because memory is cleared. +type Ring[T any] struct { + data []T + back, len, chunkSize int +} + +func New[T any](chunkSize int) *Ring[T] { + if chunkSize < 1 { + panic("chunkSize must be greater than zero") + } + return &Ring[T]{ + chunkSize: chunkSize, + } +} + +func (r *Ring[T]) Len() int { + return r.len +} + +func (r *Ring[T]) Cap() int { + return len(r.data) +} + +func (r *Ring[T]) Reset() { + var zero T + for i := range r.data { + r.data[i] = zero // clear mem, optimized by the compiler, in Go 1.21 the "clear" builtin can be used + } + r.back = 0 + r.len = 0 +} + +// Nth returns the n-th oldest value (zero-based) in the ring without making +// any change. +func (r *Ring[T]) Nth(n int) (v T, ok bool) { + if n < 0 || n >= r.len || len(r.data) == 0 { + return v, false + } + n = (n + r.back) % len(r.data) + return r.data[n], true +} + +// Dequeue returns the oldest value. +func (r *Ring[T]) Dequeue() (v T, ok bool) { + if r.len == 0 { + return v, false + } + v, r.data[r.back] = r.data[r.back], v // retrieve and clear mem + r.len-- + r.back = (r.back + 1) % len(r.data) + return v, true +} + +// Enqueue adds an item to the ring. +func (r *Ring[T]) Enqueue(v T) { + if r.len == len(r.data) { + r.grow() + } + writePos := (r.back + r.len) % len(r.data) + r.data[writePos] = v + r.len++ +} + +func (r *Ring[T]) grow() { + s := make([]T, len(r.data)+r.chunkSize) + if r.len > 0 { + chunk1 := r.back + r.len + if chunk1 > len(r.data) { + chunk1 = len(r.data) + } + copied := copy(s, r.data[r.back:chunk1]) + + if copied < r.len { // wrapped slice + chunk2 := r.len - copied + copy(s[copied:], r.data[:chunk2]) + } + } + r.back = 0 + r.data = s +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/const_expr.go b/vendor/github.com/expr-lang/expr/optimizer/const_expr.go new file mode 100644 index 00000000000..1b45385f678 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/const_expr.go @@ -0,0 +1,81 @@ +package optimizer + +import ( + "fmt" + "reflect" + "strings" + + . "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/file" +) + +var errorType = reflect.TypeOf((*error)(nil)).Elem() + +type constExpr struct { + applied bool + err error + fns map[string]reflect.Value +} + +func (c *constExpr) Visit(node *Node) { + defer func() { + if r := recover(); r != nil { + msg := fmt.Sprintf("%v", r) + // Make message more actual, it's a runtime error, but at compile step. + msg = strings.Replace(msg, "runtime error:", "compile error:", 1) + c.err = &file.Error{ + Location: (*node).Location(), + Message: msg, + } + } + }() + + if call, ok := (*node).(*CallNode); ok { + if name, ok := call.Callee.(*IdentifierNode); ok { + fn, ok := c.fns[name.Value] + if ok { + in := make([]reflect.Value, len(call.Arguments)) + for i := 0; i < len(call.Arguments); i++ { + arg := call.Arguments[i] + var param any + + switch a := arg.(type) { + case *NilNode: + param = nil + case *IntegerNode: + param = a.Value + case *FloatNode: + param = a.Value + case *BoolNode: + param = a.Value + case *StringNode: + param = a.Value + case *ConstantNode: + param = a.Value + + default: + return // Const expr optimization not applicable. + } + + if param == nil && reflect.TypeOf(param) == nil { + // In case of nil value and nil type use this hack, + // otherwise reflect.Call will panic on zero value. + in[i] = reflect.ValueOf(¶m).Elem() + } else { + in[i] = reflect.ValueOf(param) + } + } + + out := fn.Call(in) + value := out[0].Interface() + if len(out) == 2 && out[1].Type() == errorType && !out[1].IsNil() { + c.err = out[1].Interface().(error) + return + } + constNode := &ConstantNode{Value: value} + patchWithType(node, constNode) + c.applied = true + } + } + } +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/count_any.go b/vendor/github.com/expr-lang/expr/optimizer/count_any.go new file mode 100644 index 00000000000..e0c50d21d81 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/count_any.go @@ -0,0 +1,36 @@ +package optimizer + +import ( + . "github.com/expr-lang/expr/ast" +) + +// countAny optimizes count comparisons to use any for early termination. +// Patterns: +// - count(arr, pred) > 0 → any(arr, pred) +// - count(arr, pred) >= 1 → any(arr, pred) +type countAny struct{} + +func (*countAny) Visit(node *Node) { + binary, ok := (*node).(*BinaryNode) + if !ok { + return + } + + count, ok := binary.Left.(*BuiltinNode) + if !ok || count.Name != "count" || len(count.Arguments) != 2 { + return + } + + integer, ok := binary.Right.(*IntegerNode) + if !ok { + return + } + + if (binary.Operator == ">" && integer.Value == 0) || + (binary.Operator == ">=" && integer.Value == 1) { + patchCopyType(node, &BuiltinNode{ + Name: "any", + Arguments: count.Arguments, + }) + } +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/count_threshold.go b/vendor/github.com/expr-lang/expr/optimizer/count_threshold.go new file mode 100644 index 00000000000..d045760b055 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/count_threshold.go @@ -0,0 +1,54 @@ +package optimizer + +import ( + . "github.com/expr-lang/expr/ast" +) + +// countThreshold optimizes count comparisons by setting a threshold for early termination. +// The threshold allows the count loop to exit early once enough matches are found. +// Patterns: +// - count(arr, pred) > N → threshold = N + 1 (exit proves > N is true) +// - count(arr, pred) >= N → threshold = N (exit proves >= N is true) +// - count(arr, pred) < N → threshold = N (exit proves < N is false) +// - count(arr, pred) <= N → threshold = N + 1 (exit proves <= N is false) +type countThreshold struct{} + +func (*countThreshold) Visit(node *Node) { + binary, ok := (*node).(*BinaryNode) + if !ok { + return + } + + count, ok := binary.Left.(*BuiltinNode) + if !ok || count.Name != "count" || len(count.Arguments) != 2 { + return + } + + integer, ok := binary.Right.(*IntegerNode) + if !ok || integer.Value < 0 { + return + } + + var threshold int + switch binary.Operator { + case ">": + threshold = integer.Value + 1 + case ">=": + threshold = integer.Value + case "<": + threshold = integer.Value + case "<=": + threshold = integer.Value + 1 + default: + return + } + + // Skip if threshold is 0 or 1 (handled by count_any optimizer) + if threshold <= 1 { + return + } + + // Set threshold on the count node for early termination + // The original comparison remains unchanged + count.Threshold = &threshold +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/filter_first.go b/vendor/github.com/expr-lang/expr/optimizer/filter_first.go new file mode 100644 index 00000000000..b04a5cb3433 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/filter_first.go @@ -0,0 +1,38 @@ +package optimizer + +import ( + . "github.com/expr-lang/expr/ast" +) + +type filterFirst struct{} + +func (*filterFirst) Visit(node *Node) { + if member, ok := (*node).(*MemberNode); ok && member.Property != nil && !member.Optional { + if prop, ok := member.Property.(*IntegerNode); ok && prop.Value == 0 { + if filter, ok := member.Node.(*BuiltinNode); ok && + filter.Name == "filter" && + len(filter.Arguments) == 2 { + patchCopyType(node, &BuiltinNode{ + Name: "find", + Arguments: filter.Arguments, + Throws: true, // to match the behavior of filter()[0] + Map: filter.Map, + }) + } + } + } + if first, ok := (*node).(*BuiltinNode); ok && + first.Name == "first" && + len(first.Arguments) == 1 { + if filter, ok := first.Arguments[0].(*BuiltinNode); ok && + filter.Name == "filter" && + len(filter.Arguments) == 2 { + patchCopyType(node, &BuiltinNode{ + Name: "find", + Arguments: filter.Arguments, + Throws: false, // as first() will return nil if not found + Map: filter.Map, + }) + } + } +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/filter_last.go b/vendor/github.com/expr-lang/expr/optimizer/filter_last.go new file mode 100644 index 00000000000..8c046bf886a --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/filter_last.go @@ -0,0 +1,38 @@ +package optimizer + +import ( + . "github.com/expr-lang/expr/ast" +) + +type filterLast struct{} + +func (*filterLast) Visit(node *Node) { + if member, ok := (*node).(*MemberNode); ok && member.Property != nil && !member.Optional { + if prop, ok := member.Property.(*IntegerNode); ok && prop.Value == -1 { + if filter, ok := member.Node.(*BuiltinNode); ok && + filter.Name == "filter" && + len(filter.Arguments) == 2 { + patchCopyType(node, &BuiltinNode{ + Name: "findLast", + Arguments: filter.Arguments, + Throws: true, // to match the behavior of filter()[-1] + Map: filter.Map, + }) + } + } + } + if first, ok := (*node).(*BuiltinNode); ok && + first.Name == "last" && + len(first.Arguments) == 1 { + if filter, ok := first.Arguments[0].(*BuiltinNode); ok && + filter.Name == "filter" && + len(filter.Arguments) == 2 { + patchCopyType(node, &BuiltinNode{ + Name: "findLast", + Arguments: filter.Arguments, + Throws: false, // as last() will return nil if not found + Map: filter.Map, + }) + } + } +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/filter_len.go b/vendor/github.com/expr-lang/expr/optimizer/filter_len.go new file mode 100644 index 00000000000..c66fde961d2 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/filter_len.go @@ -0,0 +1,22 @@ +package optimizer + +import ( + . "github.com/expr-lang/expr/ast" +) + +type filterLen struct{} + +func (*filterLen) Visit(node *Node) { + if ln, ok := (*node).(*BuiltinNode); ok && + ln.Name == "len" && + len(ln.Arguments) == 1 { + if filter, ok := ln.Arguments[0].(*BuiltinNode); ok && + filter.Name == "filter" && + len(filter.Arguments) == 2 { + patchCopyType(node, &BuiltinNode{ + Name: "count", + Arguments: filter.Arguments, + }) + } + } +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/filter_map.go b/vendor/github.com/expr-lang/expr/optimizer/filter_map.go new file mode 100644 index 00000000000..17659a91478 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/filter_map.go @@ -0,0 +1,33 @@ +package optimizer + +import ( + . "github.com/expr-lang/expr/ast" +) + +type filterMap struct{} + +func (*filterMap) Visit(node *Node) { + if mapBuiltin, ok := (*node).(*BuiltinNode); ok && + mapBuiltin.Name == "map" && + len(mapBuiltin.Arguments) == 2 && + Find(mapBuiltin.Arguments[1], isIndexPointer) == nil { + if predicate, ok := mapBuiltin.Arguments[1].(*PredicateNode); ok { + if filter, ok := mapBuiltin.Arguments[0].(*BuiltinNode); ok && + filter.Name == "filter" && + filter.Map == nil /* not already optimized */ { + patchCopyType(node, &BuiltinNode{ + Name: "filter", + Arguments: filter.Arguments, + Map: predicate.Node, + }) + } + } + } +} + +func isIndexPointer(node Node) bool { + if pointer, ok := node.(*PointerNode); ok && pointer.Name == "index" { + return true + } + return false +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/fold.go b/vendor/github.com/expr-lang/expr/optimizer/fold.go new file mode 100644 index 00000000000..2e5498fa507 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/fold.go @@ -0,0 +1,343 @@ +package optimizer + +import ( + "math" + + . "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/file" +) + +type fold struct { + applied bool + err *file.Error +} + +func (fold *fold) Visit(node *Node) { + patch := func(newNode Node) { + fold.applied = true + patchWithType(node, newNode) + } + patchCopy := func(newNode Node) { + fold.applied = true + patchCopyType(node, newNode) + } + + switch n := (*node).(type) { + case *UnaryNode: + switch n.Operator { + case "-": + if i, ok := n.Node.(*IntegerNode); ok { + patch(&IntegerNode{Value: -i.Value}) + } + if i, ok := n.Node.(*FloatNode); ok { + patch(&FloatNode{Value: -i.Value}) + } + case "+": + if i, ok := n.Node.(*IntegerNode); ok { + patch(&IntegerNode{Value: i.Value}) + } + if i, ok := n.Node.(*FloatNode); ok { + patch(&FloatNode{Value: i.Value}) + } + case "!", "not": + if a := toBool(n.Node); a != nil { + patch(&BoolNode{Value: !a.Value}) + } + } + + case *BinaryNode: + switch n.Operator { + case "+": + { + a := toInteger(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&IntegerNode{Value: a.Value + b.Value}) + } + } + { + a := toInteger(n.Left) + b := toFloat(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: float64(a.Value) + b.Value}) + } + } + { + a := toFloat(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: a.Value + float64(b.Value)}) + } + } + { + a := toFloat(n.Left) + b := toFloat(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: a.Value + b.Value}) + } + } + { + a := toString(n.Left) + b := toString(n.Right) + if a != nil && b != nil { + patch(&StringNode{Value: a.Value + b.Value}) + } + } + case "-": + { + a := toInteger(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&IntegerNode{Value: a.Value - b.Value}) + } + } + { + a := toInteger(n.Left) + b := toFloat(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: float64(a.Value) - b.Value}) + } + } + { + a := toFloat(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: a.Value - float64(b.Value)}) + } + } + { + a := toFloat(n.Left) + b := toFloat(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: a.Value - b.Value}) + } + } + case "*": + { + a := toInteger(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&IntegerNode{Value: a.Value * b.Value}) + } + } + { + a := toInteger(n.Left) + b := toFloat(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: float64(a.Value) * b.Value}) + } + } + { + a := toFloat(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: a.Value * float64(b.Value)}) + } + } + { + a := toFloat(n.Left) + b := toFloat(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: a.Value * b.Value}) + } + } + case "/": + { + a := toInteger(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: float64(a.Value) / float64(b.Value)}) + } + } + { + a := toInteger(n.Left) + b := toFloat(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: float64(a.Value) / b.Value}) + } + } + { + a := toFloat(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: a.Value / float64(b.Value)}) + } + } + { + a := toFloat(n.Left) + b := toFloat(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: a.Value / b.Value}) + } + } + case "%": + if a, ok := n.Left.(*IntegerNode); ok { + if b, ok := n.Right.(*IntegerNode); ok { + if b.Value == 0 { + fold.err = &file.Error{ + Location: (*node).Location(), + Message: "integer divide by zero", + } + return + } + patch(&IntegerNode{Value: a.Value % b.Value}) + } + } + case "**", "^": + { + a := toInteger(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: math.Pow(float64(a.Value), float64(b.Value))}) + } + } + { + a := toInteger(n.Left) + b := toFloat(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: math.Pow(float64(a.Value), b.Value)}) + } + } + { + a := toFloat(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: math.Pow(a.Value, float64(b.Value))}) + } + } + { + a := toFloat(n.Left) + b := toFloat(n.Right) + if a != nil && b != nil { + patch(&FloatNode{Value: math.Pow(a.Value, b.Value)}) + } + } + case "and", "&&": + a := toBool(n.Left) + b := toBool(n.Right) + + if a != nil && a.Value { // true and x + patchCopy(n.Right) + } else if b != nil && b.Value { // x and true + patchCopy(n.Left) + } else if (a != nil && !a.Value) || (b != nil && !b.Value) { // "x and false" or "false and x" + patch(&BoolNode{Value: false}) + } + case "or", "||": + a := toBool(n.Left) + b := toBool(n.Right) + + if a != nil && !a.Value { // false or x + patchCopy(n.Right) + } else if b != nil && !b.Value { // x or false + patchCopy(n.Left) + } else if (a != nil && a.Value) || (b != nil && b.Value) { // "x or true" or "true or x" + patch(&BoolNode{Value: true}) + } + case "==": + { + a := toInteger(n.Left) + b := toInteger(n.Right) + if a != nil && b != nil { + patch(&BoolNode{Value: a.Value == b.Value}) + } + } + { + a := toString(n.Left) + b := toString(n.Right) + if a != nil && b != nil { + patch(&BoolNode{Value: a.Value == b.Value}) + } + } + { + a := toBool(n.Left) + b := toBool(n.Right) + if a != nil && b != nil { + patch(&BoolNode{Value: a.Value == b.Value}) + } + } + } + + case *ArrayNode: + if len(n.Nodes) > 0 { + for _, a := range n.Nodes { + switch a.(type) { + case *IntegerNode, *FloatNode, *StringNode, *BoolNode: + continue + default: + return + } + } + value := make([]any, len(n.Nodes)) + for i, a := range n.Nodes { + switch b := a.(type) { + case *IntegerNode: + value[i] = b.Value + case *FloatNode: + value[i] = b.Value + case *StringNode: + value[i] = b.Value + case *BoolNode: + value[i] = b.Value + } + } + patch(&ConstantNode{Value: value}) + } + + case *BuiltinNode: + // TODO: Move this to a separate visitor filter_filter.go + switch n.Name { + case "filter": + if len(n.Arguments) != 2 { + return + } + if base, ok := n.Arguments[0].(*BuiltinNode); ok && base.Name == "filter" { + patchCopy(&BuiltinNode{ + Name: "filter", + Arguments: []Node{ + base.Arguments[0], + &PredicateNode{ + Node: &BinaryNode{ + Operator: "&&", + Left: base.Arguments[1].(*PredicateNode).Node, + Right: n.Arguments[1].(*PredicateNode).Node, + }, + }, + }, + }) + } + } + } +} + +func toString(n Node) *StringNode { + switch a := n.(type) { + case *StringNode: + return a + } + return nil +} + +func toInteger(n Node) *IntegerNode { + switch a := n.(type) { + case *IntegerNode: + return a + } + return nil +} + +func toFloat(n Node) *FloatNode { + switch a := n.(type) { + case *FloatNode: + return a + } + return nil +} + +func toBool(n Node) *BoolNode { + switch a := n.(type) { + case *BoolNode: + return a + } + return nil +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/in_array.go b/vendor/github.com/expr-lang/expr/optimizer/in_array.go new file mode 100644 index 00000000000..e91320c0f94 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/in_array.go @@ -0,0 +1,68 @@ +package optimizer + +import ( + "reflect" + + . "github.com/expr-lang/expr/ast" +) + +type inArray struct{} + +func (*inArray) Visit(node *Node) { + switch n := (*node).(type) { + case *BinaryNode: + if n.Operator == "in" { + if array, ok := n.Right.(*ArrayNode); ok { + if len(array.Nodes) > 0 { + t := n.Left.Type() + if t == nil || t.Kind() != reflect.Int { + // This optimization can be only performed if left side is int type, + // as runtime.in func uses reflect.Map.MapIndex and keys of map must, + // be same as checked value type. + goto string + } + + for _, a := range array.Nodes { + if _, ok := a.(*IntegerNode); !ok { + goto string + } + } + { + value := make(map[int]struct{}) + for _, a := range array.Nodes { + value[a.(*IntegerNode).Value] = struct{}{} + } + m := &ConstantNode{Value: value} + m.SetType(reflect.TypeOf(value)) + patchCopyType(node, &BinaryNode{ + Operator: n.Operator, + Left: n.Left, + Right: m, + }) + } + + string: + for _, a := range array.Nodes { + if _, ok := a.(*StringNode); !ok { + return + } + } + { + value := make(map[string]struct{}) + for _, a := range array.Nodes { + value[a.(*StringNode).Value] = struct{}{} + } + m := &ConstantNode{Value: value} + m.SetType(reflect.TypeOf(value)) + patchCopyType(node, &BinaryNode{ + Operator: n.Operator, + Left: n.Left, + Right: m, + }) + } + + } + } + } + } +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/in_range.go b/vendor/github.com/expr-lang/expr/optimizer/in_range.go new file mode 100644 index 00000000000..ed2f557ea50 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/in_range.go @@ -0,0 +1,43 @@ +package optimizer + +import ( + "reflect" + + . "github.com/expr-lang/expr/ast" +) + +type inRange struct{} + +func (*inRange) Visit(node *Node) { + switch n := (*node).(type) { + case *BinaryNode: + if n.Operator == "in" { + t := n.Left.Type() + if t == nil { + return + } + if t.Kind() != reflect.Int { + return + } + if rangeOp, ok := n.Right.(*BinaryNode); ok && rangeOp.Operator == ".." { + if from, ok := rangeOp.Left.(*IntegerNode); ok { + if to, ok := rangeOp.Right.(*IntegerNode); ok { + patchCopyType(node, &BinaryNode{ + Operator: "and", + Left: &BinaryNode{ + Operator: ">=", + Left: n.Left, + Right: from, + }, + Right: &BinaryNode{ + Operator: "<=", + Left: n.Left, + Right: to, + }, + }) + } + } + } + } + } +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/optimizer.go b/vendor/github.com/expr-lang/expr/optimizer/optimizer.go new file mode 100644 index 00000000000..9e4c75d3b3b --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/optimizer.go @@ -0,0 +1,82 @@ +package optimizer + +import ( + "fmt" + "reflect" + + . "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/conf" +) + +func Optimize(node *Node, config *conf.Config) error { + Walk(node, &inArray{}) + for limit := 1000; limit >= 0; limit-- { + fold := &fold{} + Walk(node, fold) + if fold.err != nil { + return fold.err + } + if !fold.applied { + break + } + } + if config != nil && len(config.ConstFns) > 0 { + for limit := 100; limit >= 0; limit-- { + constExpr := &constExpr{ + fns: config.ConstFns, + } + Walk(node, constExpr) + if constExpr.err != nil { + return constExpr.err + } + if !constExpr.applied { + break + } + } + } + Walk(node, &inRange{}) + Walk(node, &filterMap{}) + Walk(node, &filterLen{}) + Walk(node, &filterLast{}) + Walk(node, &filterFirst{}) + Walk(node, &predicateCombination{}) + Walk(node, &sumRange{}) + Walk(node, &sumArray{}) + Walk(node, &sumMap{}) + Walk(node, &countAny{}) + Walk(node, &countThreshold{}) + return nil +} + +var ( + boolType = reflect.TypeOf(true) + integerType = reflect.TypeOf(0) + floatType = reflect.TypeOf(float64(0)) + stringType = reflect.TypeOf("") +) + +func patchWithType(node *Node, newNode Node) { + switch n := newNode.(type) { + case *BoolNode: + newNode.SetType(boolType) + case *IntegerNode: + newNode.SetType(integerType) + case *FloatNode: + newNode.SetType(floatType) + case *StringNode: + newNode.SetType(stringType) + case *ConstantNode: + newNode.SetType(reflect.TypeOf(n.Value)) + case *BinaryNode: + newNode.SetType(n.Type()) + default: + panic(fmt.Sprintf("unknown type %T", newNode)) + } + Patch(node, newNode) +} + +func patchCopyType(node *Node, newNode Node) { + t := (*node).Type() + newNode.SetType(t) + Patch(node, newNode) +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/predicate_combination.go b/vendor/github.com/expr-lang/expr/optimizer/predicate_combination.go new file mode 100644 index 00000000000..65f88e34859 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/predicate_combination.go @@ -0,0 +1,61 @@ +package optimizer + +import ( + . "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/parser/operator" +) + +/* +predicateCombination is a visitor that combines multiple predicate calls into a single call. +For example, the following expression: + + all(x, x > 1) && all(x, x < 10) -> all(x, x > 1 && x < 10) + any(x, x > 1) || any(x, x < 10) -> any(x, x > 1 || x < 10) + none(x, x > 1) && none(x, x < 10) -> none(x, x > 1 || x < 10) +*/ +type predicateCombination struct{} + +func (v *predicateCombination) Visit(node *Node) { + if op, ok := (*node).(*BinaryNode); ok && operator.IsBoolean(op.Operator) { + if left, ok := op.Left.(*BuiltinNode); ok { + if combinedOp, ok := combinedOperator(left.Name, op.Operator); ok { + if right, ok := op.Right.(*BuiltinNode); ok && right.Name == left.Name { + if left.Arguments[0].Type() == right.Arguments[0].Type() && left.Arguments[0].String() == right.Arguments[0].String() { + predicate := &PredicateNode{ + Node: &BinaryNode{ + Operator: combinedOp, + Left: left.Arguments[1].(*PredicateNode).Node, + Right: right.Arguments[1].(*PredicateNode).Node, + }, + } + v.Visit(&predicate.Node) + patchCopyType(node, &BuiltinNode{ + Name: left.Name, + Arguments: []Node{ + left.Arguments[0], + predicate, + }, + }) + } + } + } + } + } +} + +func combinedOperator(fn, op string) (string, bool) { + switch { + case fn == "all" && (op == "and" || op == "&&"): + return op, true + case fn == "any" && (op == "or" || op == "||"): + return op, true + case fn == "none" && (op == "and" || op == "&&"): + switch op { + case "and": + return "or", true + case "&&": + return "||", true + } + } + return "", false +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/sum_array.go b/vendor/github.com/expr-lang/expr/optimizer/sum_array.go new file mode 100644 index 00000000000..3c96795efc4 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/sum_array.go @@ -0,0 +1,37 @@ +package optimizer + +import ( + "fmt" + + . "github.com/expr-lang/expr/ast" +) + +type sumArray struct{} + +func (*sumArray) Visit(node *Node) { + if sumBuiltin, ok := (*node).(*BuiltinNode); ok && + sumBuiltin.Name == "sum" && + len(sumBuiltin.Arguments) == 1 { + if array, ok := sumBuiltin.Arguments[0].(*ArrayNode); ok && + len(array.Nodes) >= 2 { + patchCopyType(node, sumArrayFold(array)) + } + } +} + +func sumArrayFold(array *ArrayNode) *BinaryNode { + if len(array.Nodes) > 2 { + return &BinaryNode{ + Operator: "+", + Left: array.Nodes[0], + Right: sumArrayFold(&ArrayNode{Nodes: array.Nodes[1:]}), + } + } else if len(array.Nodes) == 2 { + return &BinaryNode{ + Operator: "+", + Left: array.Nodes[0], + Right: array.Nodes[1], + } + } + panic(fmt.Errorf("sumArrayFold: invalid array length %d", len(array.Nodes))) +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/sum_map.go b/vendor/github.com/expr-lang/expr/optimizer/sum_map.go new file mode 100644 index 00000000000..6de97d373dd --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/sum_map.go @@ -0,0 +1,25 @@ +package optimizer + +import ( + . "github.com/expr-lang/expr/ast" +) + +type sumMap struct{} + +func (*sumMap) Visit(node *Node) { + if sumBuiltin, ok := (*node).(*BuiltinNode); ok && + sumBuiltin.Name == "sum" && + len(sumBuiltin.Arguments) == 1 { + if mapBuiltin, ok := sumBuiltin.Arguments[0].(*BuiltinNode); ok && + mapBuiltin.Name == "map" && + len(mapBuiltin.Arguments) == 2 { + patchCopyType(node, &BuiltinNode{ + Name: "sum", + Arguments: []Node{ + mapBuiltin.Arguments[0], + mapBuiltin.Arguments[1], + }, + }) + } + } +} diff --git a/vendor/github.com/expr-lang/expr/optimizer/sum_range.go b/vendor/github.com/expr-lang/expr/optimizer/sum_range.go new file mode 100644 index 00000000000..92f6e1daccf --- /dev/null +++ b/vendor/github.com/expr-lang/expr/optimizer/sum_range.go @@ -0,0 +1,172 @@ +package optimizer + +import ( + . "github.com/expr-lang/expr/ast" +) + +type sumRange struct{} + +func (*sumRange) Visit(node *Node) { + // Pattern 1: sum(m..n) or sum(m..n, predicate) where m and n are constant integers + if sumBuiltin, ok := (*node).(*BuiltinNode); ok && + sumBuiltin.Name == "sum" && + (len(sumBuiltin.Arguments) == 1 || len(sumBuiltin.Arguments) == 2) { + if rangeOp, ok := sumBuiltin.Arguments[0].(*BinaryNode); ok && rangeOp.Operator == ".." { + if from, ok := rangeOp.Left.(*IntegerNode); ok { + if to, ok := rangeOp.Right.(*IntegerNode); ok { + m := from.Value + n := to.Value + if n >= m { + count := n - m + 1 + // Use the arithmetic series formula: (n - m + 1) * (m + n) / 2 + sum := count * (m + n) / 2 + + if len(sumBuiltin.Arguments) == 1 { + // sum(m..n) + patchWithType(node, &IntegerNode{Value: sum}) + } else if len(sumBuiltin.Arguments) == 2 { + // sum(m..n, predicate) + if result, ok := applySumPredicate(sum, count, sumBuiltin.Arguments[1]); ok { + patchWithType(node, &IntegerNode{Value: result}) + } + } + } + } + } + } + } + + // Pattern 2: reduce(m..n, # + #acc) where m and n are constant integers + if reduceBuiltin, ok := (*node).(*BuiltinNode); ok && + reduceBuiltin.Name == "reduce" && + (len(reduceBuiltin.Arguments) == 2 || len(reduceBuiltin.Arguments) == 3) { + if rangeOp, ok := reduceBuiltin.Arguments[0].(*BinaryNode); ok && rangeOp.Operator == ".." { + if from, ok := rangeOp.Left.(*IntegerNode); ok { + if to, ok := rangeOp.Right.(*IntegerNode); ok { + if isPointerPlusAcc(reduceBuiltin.Arguments[1]) { + m := from.Value + n := to.Value + if n >= m { + // Use the arithmetic series formula: (n - m + 1) * (m + n) / 2 + sum := (n - m + 1) * (m + n) / 2 + + // Check for optional initialValue (3rd argument) + if len(reduceBuiltin.Arguments) == 3 { + if initialValue, ok := reduceBuiltin.Arguments[2].(*IntegerNode); ok { + result := initialValue.Value + sum + patchWithType(node, &IntegerNode{Value: result}) + } + } else { + patchWithType(node, &IntegerNode{Value: sum}) + } + } + } + } + } + } + } +} + +// isPointerPlusAcc checks if the node represents `# + #acc` pattern +func isPointerPlusAcc(node Node) bool { + predicate, ok := node.(*PredicateNode) + if !ok { + return false + } + + binary, ok := predicate.Node.(*BinaryNode) + if !ok { + return false + } + + if binary.Operator != "+" { + return false + } + + // Check for # + #acc (pointer + accumulator) + leftPointer, leftIsPointer := binary.Left.(*PointerNode) + rightPointer, rightIsPointer := binary.Right.(*PointerNode) + + if leftIsPointer && rightIsPointer { + // # + #acc: Left is pointer (Name=""), Right is acc (Name="acc") + if leftPointer.Name == "" && rightPointer.Name == "acc" { + return true + } + // #acc + #: Left is acc (Name="acc"), Right is pointer (Name="") + if leftPointer.Name == "acc" && rightPointer.Name == "" { + return true + } + } + + return false +} + +// applySumPredicate tries to compute the result of sum(m..n, predicate) at compile time. +// Returns (result, true) if optimization is possible, (0, false) otherwise. +// Supported predicates: +// - # (identity): result = sum +// - # * k (multiply by constant): result = k * sum +// - k * # (multiply by constant): result = k * sum +// - # + k (add constant): result = sum + count * k +// - k + # (add constant): result = sum + count * k +// - # - k (subtract constant): result = sum - count * k +func applySumPredicate(sum, count int, predicateArg Node) (int, bool) { + predicate, ok := predicateArg.(*PredicateNode) + if !ok { + return 0, false + } + + // Case 1: # (identity) - just return the sum + if pointer, ok := predicate.Node.(*PointerNode); ok && pointer.Name == "" { + return sum, true + } + + // Case 2: Binary operations with pointer and constant + binary, ok := predicate.Node.(*BinaryNode) + if !ok { + return 0, false + } + + pointer, constant, pointerOnLeft := extractPointerAndConstantWithPosition(binary) + if pointer == nil || constant == nil { + return 0, false + } + + switch binary.Operator { + case "*": + // # * k or k * # => k * sum + return constant.Value * sum, true + case "+": + // # + k or k + # => sum + count * k + return sum + count*constant.Value, true + case "-": + if pointerOnLeft { + // # - k => sum - count * k + return sum - count*constant.Value, true + } + // k - # => count * k - sum + return count*constant.Value - sum, true + } + + return 0, false +} + +// extractPointerAndConstantWithPosition extracts pointer (#) and integer constant from a binary node. +// Returns (pointer, constant, pointerOnLeft) or (nil, nil, false) if not matching the expected pattern. +func extractPointerAndConstantWithPosition(binary *BinaryNode) (*PointerNode, *IntegerNode, bool) { + // Try left=pointer, right=constant + if pointer, ok := binary.Left.(*PointerNode); ok && pointer.Name == "" { + if constant, ok := binary.Right.(*IntegerNode); ok { + return pointer, constant, true + } + } + + // Try left=constant, right=pointer + if constant, ok := binary.Left.(*IntegerNode); ok { + if pointer, ok := binary.Right.(*PointerNode); ok && pointer.Name == "" { + return pointer, constant, false + } + } + + return nil, nil, false +} diff --git a/vendor/github.com/expr-lang/expr/parser/lexer/lexer.go b/vendor/github.com/expr-lang/expr/parser/lexer/lexer.go new file mode 100644 index 00000000000..0e75942d0d5 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/parser/lexer/lexer.go @@ -0,0 +1,315 @@ +package lexer + +import ( + "fmt" + "io" + "strings" + "unicode/utf8" + + "github.com/expr-lang/expr/file" + "github.com/expr-lang/expr/internal/ring" +) + +const ringChunkSize = 10 + +// Lex will buffer and return the tokens of a disposable *[Lexer]. +func Lex(source file.Source) ([]Token, error) { + tokens := make([]Token, 0, ringChunkSize) + l := New() + l.Reset(source) + for { + t, err := l.Next() + switch err { + case nil: + tokens = append(tokens, t) + case io.EOF: + return tokens, nil + default: + return nil, err + } + } +} + +// New returns a reusable lexer. +func New() *Lexer { + return &Lexer{ + tokens: ring.New[Token](ringChunkSize), + } +} + +type Lexer struct { + state stateFn + source file.Source + tokens *ring.Ring[Token] + err *file.Error + start, end struct { + byte, rune int + } + eof bool + // When true, keywords `if`/`else` are not treated as operators and + // will be emitted as identifiers instead (for compatibility with custom if()). + DisableIfOperator bool +} + +func (l *Lexer) Reset(source file.Source) { + l.source = source + l.tokens.Reset() + l.state = root +} + +func (l *Lexer) Next() (Token, error) { + for l.state != nil && l.err == nil && l.tokens.Len() == 0 { + l.state = l.state(l) + } + if l.err != nil { + return Token{}, l.err.Bind(l.source) + } + if t, ok := l.tokens.Dequeue(); ok { + return t, nil + } + return Token{}, io.EOF +} + +const eof rune = -1 + +func (l *Lexer) commit() { + l.start = l.end +} + +func (l *Lexer) next() rune { + if l.end.byte >= len(l.source.String()) { + l.eof = true + return eof + } + r, sz := utf8.DecodeRuneInString(l.source.String()[l.end.byte:]) + l.end.rune++ + l.end.byte += sz + return r +} + +func (l *Lexer) peek() rune { + if l.end.byte < len(l.source.String()) { + r, _ := utf8.DecodeRuneInString(l.source.String()[l.end.byte:]) + return r + } + return eof +} + +func (l *Lexer) backup() { + if l.eof { + l.eof = false + } else if l.end.rune > 0 { + _, sz := utf8.DecodeLastRuneInString(l.source.String()[:l.end.byte]) + l.end.byte -= sz + l.end.rune-- + } +} + +func (l *Lexer) emit(t Kind) { + l.emitValue(t, l.word()) +} + +func (l *Lexer) emitValue(t Kind, value string) { + l.tokens.Enqueue(Token{ + Location: file.Location{From: l.start.rune, To: l.end.rune}, + Kind: t, + Value: value, + }) + l.commit() +} + +func (l *Lexer) emitEOF() { + from := l.end.rune - 1 + if from < 0 { + from = 0 + } + to := l.end.rune - 0 + if to < 0 { + to = 0 + } + l.tokens.Enqueue(Token{ + Location: file.Location{From: from, To: to}, + Kind: EOF, + }) + l.commit() +} + +func (l *Lexer) skip() { + l.commit() +} + +func (l *Lexer) word() string { + return l.source.String()[l.start.byte:l.end.byte] +} + +func (l *Lexer) accept(valid string) bool { + if strings.ContainsRune(valid, l.peek()) { + l.next() + return true + } + return false +} + +func (l *Lexer) acceptRun(valid string) { + for l.accept(valid) { + } +} + +func (l *Lexer) skipSpaces() { + l.acceptRun(" ") + l.skip() +} + +func (l *Lexer) error(format string, args ...any) stateFn { + if l.err == nil { // show first error + end := l.end.rune + if l.eof { + end++ + } + l.err = &file.Error{ + Location: file.Location{ + From: end - 1, + To: end, + }, + Message: fmt.Sprintf(format, args...), + } + } + return nil +} + +func digitVal(ch rune) int { + switch { + case '0' <= ch && ch <= '9': + return int(ch - '0') + case 'a' <= lower(ch) && lower(ch) <= 'f': + return int(lower(ch) - 'a' + 10) + } + return 16 // larger than any legal digit val +} + +func lower(ch rune) rune { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter + +func (l *Lexer) scanDigits(ch rune, base, n int) rune { + for n > 0 && digitVal(ch) < base { + ch = l.next() + n-- + } + if n > 0 { + l.error("invalid char escape") + } + return ch +} + +func (l *Lexer) scanEscape(quote rune) rune { + ch := l.next() // read character after '/' + switch ch { + case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote: + // nothing to do + ch = l.next() + case '0', '1', '2', '3', '4', '5', '6', '7': + ch = l.scanDigits(ch, 8, 3) + case 'x': + ch = l.scanDigits(l.next(), 16, 2) + case 'u': + // Support variable-length form: \u{XXXXXX} + if l.peek() == '{' { + // consume '{' + l.next() + // read 1-6 hex digits + digits := 0 + for { + p := l.peek() + if p == '}' { + break + } + if digitVal(p) >= 16 { + l.error("invalid char escape") + return eof + } + if digits >= 6 { + l.error("invalid char escape") + return eof + } + l.next() + digits++ + } + if l.peek() != '}' || digits == 0 { + l.error("invalid char escape") + return eof + } + // consume '}' and continue + l.next() + ch = l.next() + break + } + ch = l.scanDigits(l.next(), 16, 4) + case 'U': + ch = l.scanDigits(l.next(), 16, 8) + default: + l.error("invalid char escape") + } + return ch +} + +func (l *Lexer) scanString(quote rune) (n int) { + ch := l.next() // read character after quote + for ch != quote { + if ch == '\n' || ch == eof { + l.error("literal not terminated") + return + } + if ch == '\\' { + ch = l.scanEscape(quote) + } else { + ch = l.next() + } + n++ + } + return +} + +func (l *Lexer) scanRawString(quote rune) (n int) { + var escapedQuotes int +loop: + for { + ch := l.next() + for ch == quote && l.peek() == quote { + // skip current and next char which are the quote escape sequence + l.next() + ch = l.next() + escapedQuotes++ + } + switch ch { + case quote: + break loop + case eof: + l.error("literal not terminated") + return + } + n++ + } + str := l.source.String()[l.start.byte+1 : l.end.byte-1] + + // handle simple case where no quoted backtick was found, then no allocation + // is needed for the new string + if escapedQuotes == 0 { + l.emitValue(String, str) + return + } + + var b strings.Builder + var skipped bool + b.Grow(len(str) - escapedQuotes) + for _, r := range str { + if r == quote { + if !skipped { + skipped = true + continue + } + skipped = false + } + b.WriteRune(r) + } + l.emitValue(String, b.String()) + return +} diff --git a/vendor/github.com/expr-lang/expr/parser/lexer/state.go b/vendor/github.com/expr-lang/expr/parser/lexer/state.go new file mode 100644 index 00000000000..d606258b245 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/parser/lexer/state.go @@ -0,0 +1,240 @@ +package lexer + +import ( + "strings" + + "github.com/expr-lang/expr/parser/utils" +) + +type stateFn func(*Lexer) stateFn + +func root(l *Lexer) stateFn { + switch r := l.next(); { + case r == eof: + l.emitEOF() + return nil + case utils.IsSpace(r): + l.skip() + return root + case r == '\'' || r == '"': + l.scanString(r) + str, err := unescape(l.word()) + if err != nil { + l.error("%v", err) + } + l.emitValue(String, str) + case r == '`': + l.scanRawString(r) + case (r == 'b' || r == 'B') && (l.peek() == '\'' || l.peek() == '"'): + quote := l.next() + l.scanString(quote) + str, err := unescapeBytes(l.word()[1:]) // skip 'b' + if err != nil { + l.error("%v", err) + } + l.emitValue(Bytes, str) + case '0' <= r && r <= '9': + l.backup() + return number + case r == '?': + return questionMark + case r == '/': + return slash + case r == '#': + return pointer + case r == '|': + l.accept("|") + l.emit(Operator) + case r == ':': + l.accept(":") + l.emit(Operator) + case strings.ContainsRune("([{", r): + l.emit(Bracket) + case strings.ContainsRune(")]}", r): + l.emit(Bracket) + case strings.ContainsRune(",;%+-^", r): // single rune operator + l.emit(Operator) + case strings.ContainsRune("&!=*<>", r): // possible double rune operator + l.accept("&=*") + l.emit(Operator) + case r == '.': + l.backup() + return dot + case utils.IsAlphaNumeric(r): + l.backup() + return identifier + default: + return l.error("unrecognized character: %#U", r) + } + return root +} + +func number(l *Lexer) stateFn { + if !l.scanNumber() { + return l.error("bad number syntax: %q", l.word()) + } + l.emit(Number) + return root +} + +func (l *Lexer) scanNumber() bool { + digits := "0123456789_" + // Is it hex? + if l.accept("0") { + // Note: Leading 0 does not mean octal in floats. + if l.accept("xX") { + digits = "0123456789abcdefABCDEF_" + } else if l.accept("oO") { + digits = "01234567_" + } else if l.accept("bB") { + digits = "01_" + } + } + l.acceptRun(digits) + end := l.end + if l.accept(".") { + // Lookup for .. operator: if after dot there is another dot (1..2), it maybe a range operator. + if l.peek() == '.' { + // We can't backup() here, as it would require two backups, + // and backup() func supports only one for now. So, save and + // restore it here. + l.end = end + return true + } + l.acceptRun(digits) + } + if l.accept("eE") { + l.accept("+-") + l.acceptRun(digits) + } + // Next thing mustn't be alphanumeric. + if utils.IsAlphaNumeric(l.peek()) { + l.next() + return false + } + return true +} + +func dot(l *Lexer) stateFn { + l.next() + if l.accept("0123456789") { + l.backup() + return number + } + l.accept(".") + l.emit(Operator) + return root +} + +func identifier(l *Lexer) stateFn { +loop: + for { + switch r := l.next(); { + case utils.IsAlphaNumeric(r): + // absorb + default: + l.backup() + switch l.word() { + case "not": + return not + case "in", "or", "and", "matches", "contains", "startsWith", "endsWith", "let": + l.emit(Operator) + case "if", "else": + if !l.DisableIfOperator { + l.emit(Operator) + } else { + l.emit(Identifier) + } + default: + l.emit(Identifier) + } + break loop + } + } + return root +} + +func not(l *Lexer) stateFn { + l.emit(Operator) + + l.skipSpaces() + + end := l.end + + // Get the next word. + for { + r := l.next() + if utils.IsAlphaNumeric(r) { + // absorb + } else { + l.backup() + break + } + } + + switch l.word() { + case "in", "matches", "contains", "startsWith", "endsWith": + l.emit(Operator) + default: + l.end = end + } + return root +} + +func questionMark(l *Lexer) stateFn { + l.accept(".?") + l.emit(Operator) + return root +} + +func slash(l *Lexer) stateFn { + if l.accept("/") { + return singleLineComment + } + if l.accept("*") { + return multiLineComment + } + l.emit(Operator) + return root +} + +func singleLineComment(l *Lexer) stateFn { + for { + r := l.next() + if r == eof || r == '\n' { + break + } + } + l.skip() + return root +} + +func multiLineComment(l *Lexer) stateFn { + for { + r := l.next() + if r == eof { + return l.error("unclosed comment") + } + if r == '*' && l.accept("/") { + break + } + } + l.skip() + return root +} + +func pointer(l *Lexer) stateFn { + l.accept("#") + l.emit(Operator) + for { + switch r := l.next(); { + case utils.IsAlphaNumeric(r): // absorb + default: + l.backup() + if l.word() != "" { + l.emit(Identifier) + } + return root + } + } +} diff --git a/vendor/github.com/expr-lang/expr/parser/lexer/token.go b/vendor/github.com/expr-lang/expr/parser/lexer/token.go new file mode 100644 index 00000000000..1041784e746 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/parser/lexer/token.go @@ -0,0 +1,44 @@ +package lexer + +import ( + "fmt" + + "github.com/expr-lang/expr/file" +) + +type Kind string + +const ( + Identifier Kind = "Identifier" + Number Kind = "Number" + String Kind = "String" + Bytes Kind = "Bytes" + Operator Kind = "Operator" + Bracket Kind = "Bracket" + EOF Kind = "EOF" +) + +type Token struct { + file.Location + Kind Kind + Value string +} + +func (t Token) String() string { + if t.Value == "" { + return string(t.Kind) + } + return fmt.Sprintf("%s(%#v)", t.Kind, t.Value) +} + +func (t Token) Is(kind Kind, values ...string) bool { + if kind != t.Kind { + return false + } + for _, v := range values { + if v == t.Value { + return true + } + } + return len(values) == 0 +} diff --git a/vendor/github.com/expr-lang/expr/parser/lexer/utils.go b/vendor/github.com/expr-lang/expr/parser/lexer/utils.go new file mode 100644 index 00000000000..13cb5f314df --- /dev/null +++ b/vendor/github.com/expr-lang/expr/parser/lexer/utils.go @@ -0,0 +1,350 @@ +package lexer + +import ( + "fmt" + "math" + "strings" + "unicode/utf8" +) + +var ( + newlineNormalizer = strings.NewReplacer("\r\n", "\n", "\r", "\n") +) + +// Unescape takes a quoted string, unquotes, and unescapes it. +func unescape(value string) (string, error) { + // All strings normalize newlines to the \n representation. + value = newlineNormalizer.Replace(value) + n := len(value) + + // Nothing to unescape / decode. + if n < 2 { + return value, fmt.Errorf("unable to unescape string") + } + + // Quoted string of some form, must have same first and last char. + if value[0] != value[n-1] || (value[0] != '"' && value[0] != '\'') { + return value, fmt.Errorf("unable to unescape string") + } + + value = value[1 : n-1] + + // The string contains escape characters. + // The following logic is adapted from `strconv/quote.go` + var runeTmp [utf8.UTFMax]byte + size := 3 * uint64(n) / 2 + if size >= math.MaxInt { + return "", fmt.Errorf("too large string") + } + buf := new(strings.Builder) + buf.Grow(int(size)) + for len(value) > 0 { + c, multibyte, rest, err := unescapeChar(value) + if err != nil { + return "", err + } + value = rest + if c < utf8.RuneSelf || !multibyte { + buf.WriteByte(byte(c)) + } else { + n := utf8.EncodeRune(runeTmp[:], c) + buf.Write(runeTmp[:n]) + } + } + return buf.String(), nil +} + +// unescapeBytes takes a quoted string, unquotes, and unescapes it as bytes. +func unescapeBytes(value string) (string, error) { + // All strings normalize newlines to the \n representation. + value = newlineNormalizer.Replace(value) + n := len(value) + + // Nothing to unescape / decode. + if n < 2 { + return value, fmt.Errorf("unable to unescape string") + } + + // Quoted string of some form, must have same first and last char. + if value[0] != value[n-1] || (value[0] != '"' && value[0] != '\'') { + return value, fmt.Errorf("unable to unescape string") + } + + value = value[1 : n-1] + + // The string contains escape characters. + // The following logic is adapted from `strconv/quote.go` + var runeTmp [utf8.UTFMax]byte + size := 3 * uint64(n) / 2 + if size >= math.MaxInt { + return "", fmt.Errorf("too large string") + } + buf := new(strings.Builder) + buf.Grow(int(size)) + for len(value) > 0 { + c, multibyte, rest, err := unescapeByteChar(value) + if err != nil { + return "", err + } + value = rest + if c < utf8.RuneSelf || !multibyte { + buf.WriteByte(byte(c)) + } else { + n := utf8.EncodeRune(runeTmp[:], c) + buf.Write(runeTmp[:n]) + } + } + return buf.String(), nil +} + +// unescapeChar takes a string input and returns the following info: +// +// value - the escaped unicode rune at the front of the string. +// multibyte - whether the rune value might require multiple bytes to represent. +// tail - the remainder of the input string. +// err - error value, if the character could not be unescaped. +// +// When multibyte is true the return value may still fit within a single byte, +// but a multibyte conversion is attempted which is more expensive than when the +// value is known to fit within one byte. +func unescapeChar(s string) (value rune, multibyte bool, tail string, err error) { + // 1. Character is not an escape sequence. + switch c := s[0]; { + case c >= utf8.RuneSelf: + r, size := utf8.DecodeRuneInString(s) + return r, true, s[size:], nil + case c != '\\': + return rune(s[0]), false, s[1:], nil + } + + // 2. Last character is the start of an escape sequence. + if len(s) <= 1 { + err = fmt.Errorf("unable to unescape string, found '\\' as last character") + return + } + + c := s[1] + s = s[2:] + // 3. Common escape sequences shared with Google SQL + switch c { + case 'a': + value = '\a' + case 'b': + value = '\b' + case 'f': + value = '\f' + case 'n': + value = '\n' + case 'r': + value = '\r' + case 't': + value = '\t' + case 'v': + value = '\v' + case '\\': + value = '\\' + case '\'': + value = '\'' + case '"': + value = '"' + case '`': + value = '`' + case '?': + value = '?' + + // 4. Unicode escape sequences, reproduced from `strconv/quote.go` + case 'x', 'X', 'u', 'U': + // Support Go/Rust-style variable-length form: \u{XXXXXX} + if c == 'u' && len(s) > 0 && s[0] == '{' { + // consume '{' + s = s[1:] + var v rune + digits := 0 + for len(s) > 0 && s[0] != '}' { + x, ok := unhex(s[0]) + if !ok { + err = fmt.Errorf("unable to unescape string") + return + } + if digits >= 6 { // at most 6 hex digits + err = fmt.Errorf("unable to unescape string") + return + } + v = v<<4 | x + s = s[1:] + digits++ + } + // require closing '}' and at least 1 digit + if len(s) == 0 || s[0] != '}' || digits == 0 { + err = fmt.Errorf("unable to unescape string") + return + } + // consume '}' + s = s[1:] + if v > utf8.MaxRune { + err = fmt.Errorf("unable to unescape string") + return + } + value = v + multibyte = true + break + } + n := 0 + switch c { + case 'x', 'X': + n = 2 + case 'u': + n = 4 + case 'U': + n = 8 + } + var v rune + if len(s) < n { + err = fmt.Errorf("unable to unescape string") + return + } + for j := 0; j < n; j++ { + x, ok := unhex(s[j]) + if !ok { + err = fmt.Errorf("unable to unescape string") + return + } + v = v<<4 | x + } + s = s[n:] + if v > utf8.MaxRune { + err = fmt.Errorf("unable to unescape string") + return + } + value = v + multibyte = true + + // 5. Octal escape sequences, must be three digits \[0-3][0-7][0-7] + case '0', '1', '2', '3': + if len(s) < 2 { + err = fmt.Errorf("unable to unescape octal sequence in string") + return + } + v := rune(c - '0') + for j := 0; j < 2; j++ { + x := s[j] + if x < '0' || x > '7' { + err = fmt.Errorf("unable to unescape octal sequence in string") + return + } + v = v*8 + rune(x-'0') + } + if v > utf8.MaxRune { + err = fmt.Errorf("unable to unescape string") + return + } + value = v + s = s[2:] + multibyte = true + + // Unknown escape sequence. + default: + err = fmt.Errorf("unable to unescape string") + } + + tail = s + return +} + +// unescapeByteChar unescapes a single character or escape sequence from a bytes literal. +// Unlike unescapeChar, this only supports byte-level escapes (\x, octal) and rejects +// Unicode escapes (\u, \U) since bytes literals represent raw byte sequences. +// +// Note: We cannot use strconv.UnquoteChar here because it interprets \x and octal +// escapes as Unicode codepoints (e.g., \xff → codepoint 255 → 2 UTF-8 bytes), +// whereas bytes literals require them as raw byte values (\xff → single byte 255). +func unescapeByteChar(s string) (value rune, multibyte bool, tail string, err error) { + // Non-escape: return the character as-is. + // For bytes literals, we accept UTF-8 sequences but they get encoded back to bytes. + c := s[0] + if c != '\\' { + if c >= utf8.RuneSelf { + r, size := utf8.DecodeRuneInString(s) + return r, true, s[size:], nil + } + return rune(c), false, s[1:], nil + } + + // Escape sequence: need at least one more character. + if len(s) <= 1 { + return 0, false, "", fmt.Errorf("unable to unescape string, found '\\' as last character") + } + + c = s[1] + s = s[2:] + + switch c { + // Simple escape sequences + case 'a': + return '\a', false, s, nil + case 'b': + return '\b', false, s, nil + case 'f': + return '\f', false, s, nil + case 'n': + return '\n', false, s, nil + case 'r': + return '\r', false, s, nil + case 't': + return '\t', false, s, nil + case 'v': + return '\v', false, s, nil + case '\\': + return '\\', false, s, nil + case '\'': + return '\'', false, s, nil + case '"': + return '"', false, s, nil + case '`': + return '`', false, s, nil + case '?': + return '?', false, s, nil + + // Hex escape: \xNN (exactly 2 hex digits, value 0-255) + case 'x', 'X': + if len(s) < 2 { + return 0, false, "", fmt.Errorf("unable to unescape string") + } + hi, ok1 := unhex(s[0]) + lo, ok2 := unhex(s[1]) + if !ok1 || !ok2 { + return 0, false, "", fmt.Errorf("unable to unescape string") + } + return hi<<4 | lo, false, s[2:], nil + + // Octal escape: \NNN (3 octal digits, value 0-255) + case '0', '1', '2', '3': + if len(s) < 2 { + return 0, false, "", fmt.Errorf("unable to unescape octal sequence in string") + } + if s[0] < '0' || s[0] > '7' || s[1] < '0' || s[1] > '7' { + return 0, false, "", fmt.Errorf("unable to unescape octal sequence in string") + } + v := rune(c-'0')*64 + rune(s[0]-'0')*8 + rune(s[1]-'0') + if v > 255 { + return 0, false, "", fmt.Errorf("unable to unescape string") + } + return v, false, s[2:], nil + + default: + return 0, false, "", fmt.Errorf("unable to unescape string") + } +} + +func unhex(b byte) (rune, bool) { + c := rune(b) + switch { + case '0' <= c && c <= '9': + return c - '0', true + case 'a' <= c && c <= 'f': + return c - 'a' + 10, true + case 'A' <= c && c <= 'F': + return c - 'A' + 10, true + } + return 0, false +} diff --git a/vendor/github.com/expr-lang/expr/parser/operator/operator.go b/vendor/github.com/expr-lang/expr/parser/operator/operator.go new file mode 100644 index 00000000000..4eeaf80ed82 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/parser/operator/operator.go @@ -0,0 +1,69 @@ +package operator + +type Associativity int + +const ( + Left Associativity = iota + 1 + Right +) + +type Operator struct { + Precedence int + Associativity Associativity +} + +func Less(a, b string) bool { + return Binary[a].Precedence < Binary[b].Precedence +} + +func IsBoolean(op string) bool { + return op == "and" || op == "or" || op == "&&" || op == "||" +} + +func AllowedNegateSuffix(op string) bool { + switch op { + case "contains", "matches", "startsWith", "endsWith", "in": + return true + default: + return false + } +} + +var Unary = map[string]Operator{ + "not": {50, Left}, + "!": {50, Left}, + "-": {90, Left}, + "+": {90, Left}, +} + +var Binary = map[string]Operator{ + "|": {0, Left}, + "or": {10, Left}, + "||": {10, Left}, + "and": {15, Left}, + "&&": {15, Left}, + "==": {20, Left}, + "!=": {20, Left}, + "<": {20, Left}, + ">": {20, Left}, + ">=": {20, Left}, + "<=": {20, Left}, + "in": {20, Left}, + "matches": {20, Left}, + "contains": {20, Left}, + "startsWith": {20, Left}, + "endsWith": {20, Left}, + "..": {25, Left}, + "+": {30, Left}, + "-": {30, Left}, + "*": {60, Left}, + "/": {60, Left}, + "%": {60, Left}, + "**": {100, Right}, + "^": {100, Right}, + "??": {500, Left}, +} + +func IsComparison(op string) bool { + return op == "<" || op == ">" || op == ">=" || op == "<=" +} diff --git a/vendor/github.com/expr-lang/expr/parser/parser.go b/vendor/github.com/expr-lang/expr/parser/parser.go new file mode 100644 index 00000000000..9e24a71e482 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/parser/parser.go @@ -0,0 +1,940 @@ +package parser + +import ( + "errors" + "fmt" + "io" + "math" + "strconv" + "strings" + + . "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/builtin" + "github.com/expr-lang/expr/conf" + "github.com/expr-lang/expr/file" + . "github.com/expr-lang/expr/parser/lexer" + "github.com/expr-lang/expr/parser/operator" + "github.com/expr-lang/expr/parser/utils" +) + +type arg byte + +const ( + expr arg = 1 << iota + predicate +) + +const optional arg = 1 << 7 + +var predicates = map[string]struct { + args []arg +}{ + "all": {[]arg{expr, predicate}}, + "none": {[]arg{expr, predicate}}, + "any": {[]arg{expr, predicate}}, + "one": {[]arg{expr, predicate}}, + "filter": {[]arg{expr, predicate}}, + "map": {[]arg{expr, predicate}}, + "count": {[]arg{expr, predicate | optional}}, + "sum": {[]arg{expr, predicate | optional}}, + "find": {[]arg{expr, predicate}}, + "findIndex": {[]arg{expr, predicate}}, + "findLast": {[]arg{expr, predicate}}, + "findLastIndex": {[]arg{expr, predicate}}, + "groupBy": {[]arg{expr, predicate}}, + "sortBy": {[]arg{expr, predicate, expr | optional}}, + "reduce": {[]arg{expr, predicate, expr | optional}}, +} + +// Parser is a reusable parser. The zero value is ready for use. +type Parser struct { + lexer *Lexer + current, stashed Token + hasStash bool + err *file.Error + config *conf.Config + depth int // predicate call depth + nodeCount uint // tracks number of AST nodes created +} + +func (p *Parser) Parse(input string, config *conf.Config) (*Tree, error) { + if p.lexer == nil { + p.lexer = New() + } + p.config = config + // propagate config flags to lexer + if p.lexer != nil { + if config != nil { + p.lexer.DisableIfOperator = config.DisableIfOperator + } else { + p.lexer.DisableIfOperator = false + } + } + source := file.NewSource(input) + p.lexer.Reset(source) + p.next() + node := p.parseSequenceExpression() + + if !p.current.Is(EOF) { + p.error("unexpected token %v", p.current) + } + + tree := &Tree{ + Node: node, + Source: source, + } + err := p.err + + // cleanup non-reusable pointer values and reset state + p.err = nil + p.config = nil + p.lexer.Reset(file.Source{}) + + if err != nil { + return tree, err.Bind(source) + } + + return tree, nil +} + +func (p *Parser) checkNodeLimit() error { + p.nodeCount++ + if p.config == nil { + if p.nodeCount > conf.DefaultMaxNodes { + p.error("compilation failed: expression exceeds maximum allowed nodes") + return nil + } + return nil + } + if p.config.MaxNodes > 0 && p.nodeCount > p.config.MaxNodes { + p.error("compilation failed: expression exceeds maximum allowed nodes") + return nil + } + return nil +} + +func (p *Parser) createNode(n Node, loc file.Location) Node { + if err := p.checkNodeLimit(); err != nil { + return nil + } + if n == nil || p.err != nil { + return nil + } + n.SetLocation(loc) + return n +} + +func (p *Parser) createMemberNode(n *MemberNode, loc file.Location) *MemberNode { + if err := p.checkNodeLimit(); err != nil { + return nil + } + if n == nil || p.err != nil { + return nil + } + n.SetLocation(loc) + return n +} + +type Tree struct { + Node Node + Source file.Source +} + +func Parse(input string) (*Tree, error) { + return ParseWithConfig(input, nil) +} + +func ParseWithConfig(input string, config *conf.Config) (*Tree, error) { + return new(Parser).Parse(input, config) +} + +func (p *Parser) error(format string, args ...any) { + p.errorAt(p.current, format, args...) +} + +func (p *Parser) errorAt(token Token, format string, args ...any) { + if p.err == nil { // show first error + p.err = &file.Error{ + Location: token.Location, + Message: fmt.Sprintf(format, args...), + } + } +} + +func (p *Parser) next() { + if p.hasStash { + p.current = p.stashed + p.hasStash = false + return + } + + token, err := p.lexer.Next() + var e *file.Error + switch { + case err == nil: + p.current = token + case errors.Is(err, io.EOF): + p.error("unexpected end of expression") + case errors.As(err, &e): + p.err = e + default: + p.err = &file.Error{ + Location: p.current.Location, + Message: "unknown lexing error", + Prev: err, + } + } +} + +func (p *Parser) expect(kind Kind, values ...string) { + if p.current.Is(kind, values...) { + p.next() + return + } + p.error("unexpected token %v", p.current) +} + +// parse functions + +func (p *Parser) parseSequenceExpression() Node { + nodes := []Node{p.parseExpression(0)} + + for p.current.Is(Operator, ";") && p.err == nil { + p.next() + // If a trailing semicolon is present, break out. + if p.current.Is(EOF) { + break + } + nodes = append(nodes, p.parseExpression(0)) + } + + if len(nodes) == 1 { + return nodes[0] + } + + return p.createNode(&SequenceNode{ + Nodes: nodes, + }, nodes[0].Location()) +} + +func (p *Parser) parseExpression(precedence int) Node { + if p.err != nil { + return nil + } + + if precedence == 0 && p.current.Is(Operator, "let") { + return p.parseVariableDeclaration() + } + + if precedence == 0 && (p.config == nil || !p.config.DisableIfOperator) && p.current.Is(Operator, "if") { + return p.parseConditionalIf() + } + + nodeLeft := p.parsePrimary() + + prevOperator := "" + opToken := p.current + for opToken.Is(Operator) && p.err == nil { + negate := opToken.Is(Operator, "not") + var notToken Token + + // Handle "not *" operator, like "not in" or "not contains". + if negate { + tokenBackup := p.current + p.next() + if operator.AllowedNegateSuffix(p.current.Value) { + if op, ok := operator.Binary[p.current.Value]; ok && op.Precedence >= precedence { + notToken = p.current + opToken = p.current + } else { + p.hasStash = true + p.stashed = p.current + p.current = tokenBackup + break + } + } else { + p.error("unexpected token %v", p.current) + break + } + } + + if op, ok := operator.Binary[opToken.Value]; ok && op.Precedence >= precedence { + p.next() + + if opToken.Value == "|" { + identToken := p.current + p.expect(Identifier) + nodeLeft = p.parseCall(identToken, []Node{nodeLeft}, true) + goto next + } + + if prevOperator == "??" && opToken.Value != "??" && !opToken.Is(Bracket, "(") { + p.errorAt(opToken, "Operator (%v) and coalesce expressions (??) cannot be mixed. Wrap either by parentheses.", opToken.Value) + break + } + + if operator.IsComparison(opToken.Value) { + nodeLeft = p.parseComparison(nodeLeft, opToken, op.Precedence) + goto next + } + + var nodeRight Node + if op.Associativity == operator.Left { + nodeRight = p.parseExpression(op.Precedence + 1) + } else { + nodeRight = p.parseExpression(op.Precedence) + } + + nodeLeft = p.createNode(&BinaryNode{ + Operator: opToken.Value, + Left: nodeLeft, + Right: nodeRight, + }, opToken.Location) + if nodeLeft == nil { + return nil + } + + if negate { + nodeLeft = p.createNode(&UnaryNode{ + Operator: "not", + Node: nodeLeft, + }, notToken.Location) + if nodeLeft == nil { + return nil + } + } + + goto next + } + break + + next: + prevOperator = opToken.Value + opToken = p.current + } + + if precedence == 0 { + nodeLeft = p.parseConditional(nodeLeft) + } + + return nodeLeft +} + +func (p *Parser) parseVariableDeclaration() Node { + p.expect(Operator, "let") + variableName := p.current + p.expect(Identifier) + p.expect(Operator, "=") + value := p.parseExpression(0) + p.expect(Operator, ";") + node := p.parseSequenceExpression() + return p.createNode(&VariableDeclaratorNode{ + Name: variableName.Value, + Value: value, + Expr: node, + }, variableName.Location) +} + +func (p *Parser) parseConditionalIf() Node { + p.next() + if p.err != nil { + return nil + } + nodeCondition := p.parseExpression(0) + p.expect(Bracket, "{") + expr1 := p.parseSequenceExpression() + p.expect(Bracket, "}") + p.expect(Operator, "else") + + var expr2 Node + if p.current.Is(Operator, "if") { + expr2 = p.parseConditionalIf() + } else { + p.expect(Bracket, "{") + expr2 = p.parseSequenceExpression() + p.expect(Bracket, "}") + } + + return &ConditionalNode{ + Cond: nodeCondition, + Exp1: expr1, + Exp2: expr2, + } + +} + +func (p *Parser) parseConditional(node Node) Node { + var expr1, expr2 Node + for p.current.Is(Operator, "?") && p.err == nil { + p.next() + + if !p.current.Is(Operator, ":") { + expr1 = p.parseExpression(0) + p.expect(Operator, ":") + expr2 = p.parseExpression(0) + } else { + p.next() + expr1 = node + expr2 = p.parseExpression(0) + } + + node = p.createNode(&ConditionalNode{ + Ternary: true, + Cond: node, + Exp1: expr1, + Exp2: expr2, + }, p.current.Location) + if node == nil { + return nil + } + } + return node +} + +func (p *Parser) parsePrimary() Node { + token := p.current + + if token.Is(Operator) { + if op, ok := operator.Unary[token.Value]; ok { + p.next() + expr := p.parseExpression(op.Precedence) + node := p.createNode(&UnaryNode{ + Operator: token.Value, + Node: expr, + }, token.Location) + if node == nil { + return nil + } + return p.parsePostfixExpression(node) + } + } + + if token.Is(Bracket, "(") { + p.next() + expr := p.parseSequenceExpression() + p.expect(Bracket, ")") // "an opened parenthesis is not properly closed" + return p.parsePostfixExpression(expr) + } + + if p.depth > 0 { + if token.Is(Operator, "#") || token.Is(Operator, ".") { + name := "" + if token.Is(Operator, "#") { + p.next() + if p.current.Is(Identifier) { + name = p.current.Value + p.next() + } + } + node := p.createNode(&PointerNode{Name: name}, token.Location) + if node == nil { + return nil + } + return p.parsePostfixExpression(node) + } + } + + if token.Is(Operator, "::") { + p.next() + token = p.current + p.expect(Identifier) + return p.parsePostfixExpression(p.parseCall(token, []Node{}, false)) + } + + return p.parseSecondary() +} + +func (p *Parser) parseSecondary() Node { + var node Node + token := p.current + + switch token.Kind { + + case Identifier: + p.next() + switch token.Value { + case "true": + node = p.createNode(&BoolNode{Value: true}, token.Location) + if node == nil { + return nil + } + return node + case "false": + node = p.createNode(&BoolNode{Value: false}, token.Location) + if node == nil { + return nil + } + return node + case "nil": + node = p.createNode(&NilNode{}, token.Location) + if node == nil { + return nil + } + return node + default: + if p.current.Is(Bracket, "(") { + node = p.parseCall(token, []Node{}, true) + } else { + node = p.createNode(&IdentifierNode{Value: token.Value}, token.Location) + if node == nil { + return nil + } + } + } + + case Number: + p.next() + value := strings.Replace(token.Value, "_", "", -1) + var node Node + valueLower := strings.ToLower(value) + switch { + case strings.HasPrefix(valueLower, "0x"): + number, err := strconv.ParseInt(value, 0, 64) + if err != nil { + p.error("invalid hex literal: %v", err) + } + node = p.toIntegerNode(number) + case strings.ContainsAny(valueLower, ".e"): + number, err := strconv.ParseFloat(value, 64) + if err != nil { + p.error("invalid float literal: %v", err) + } + node = p.toFloatNode(number) + case strings.HasPrefix(valueLower, "0b"): + number, err := strconv.ParseInt(value, 0, 64) + if err != nil { + p.error("invalid binary literal: %v", err) + } + node = p.toIntegerNode(number) + case strings.HasPrefix(valueLower, "0o"): + number, err := strconv.ParseInt(value, 0, 64) + if err != nil { + p.error("invalid octal literal: %v", err) + } + node = p.toIntegerNode(number) + default: + number, err := strconv.ParseInt(value, 10, 64) + if err != nil { + p.error("invalid integer literal: %v", err) + } + node = p.toIntegerNode(number) + } + if node != nil { + node.SetLocation(token.Location) + } + return node + case String: + p.next() + node = p.createNode(&StringNode{Value: token.Value}, token.Location) + if node == nil { + return nil + } + + case Bytes: + p.next() + node = p.createNode(&BytesNode{Value: []byte(token.Value)}, token.Location) + if node == nil { + return nil + } + + default: + if token.Is(Bracket, "[") { + node = p.parseArrayExpression(token) + } else if token.Is(Bracket, "{") { + node = p.parseMapExpression(token) + } else { + p.error("unexpected token %v", token) + } + } + + return p.parsePostfixExpression(node) +} + +func (p *Parser) toIntegerNode(number int64) Node { + if number > math.MaxInt { + p.error("integer literal is too large") + return nil + } + return p.createNode(&IntegerNode{Value: int(number)}, p.current.Location) +} + +func (p *Parser) toFloatNode(number float64) Node { + if number > math.MaxFloat64 { + p.error("float literal is too large") + return nil + } + return p.createNode(&FloatNode{Value: number}, p.current.Location) +} + +func (p *Parser) parseCall(token Token, arguments []Node, checkOverrides bool) Node { + var node Node + + isOverridden := false + if p.config != nil { + isOverridden = p.config.IsOverridden(token.Value) + } + isOverridden = isOverridden && checkOverrides + + if b, ok := predicates[token.Value]; ok && !isOverridden { + p.expect(Bracket, "(") + + // In case of the pipe operator, the first argument is the left-hand side + // of the operator, so we do not parse it as an argument inside brackets. + args := b.args[len(arguments):] + + for i, arg := range args { + if arg&optional == optional { + if p.current.Is(Bracket, ")") { + break + } + } else { + if p.current.Is(Bracket, ")") { + p.error("expected at least %d arguments", len(args)) + } + } + + if i > 0 { + p.expect(Operator, ",") + } + var node Node + switch { + case arg&expr == expr: + node = p.parseExpression(0) + case arg&predicate == predicate: + node = p.parsePredicate() + } + arguments = append(arguments, node) + } + + // skip last comma + if p.current.Is(Operator, ",") { + p.next() + } + p.expect(Bracket, ")") + + node = p.createNode(&BuiltinNode{ + Name: token.Value, + Arguments: arguments, + }, token.Location) + if node == nil { + return nil + } + } else if _, ok := builtin.Index[token.Value]; ok && (p.config == nil || !p.config.Disabled[token.Value]) && !isOverridden { + node = p.createNode(&BuiltinNode{ + Name: token.Value, + Arguments: p.parseArguments(arguments), + }, token.Location) + if node == nil { + return nil + } + + } else { + callee := p.createNode(&IdentifierNode{Value: token.Value}, token.Location) + if callee == nil { + return nil + } + node = p.createNode(&CallNode{ + Callee: callee, + Arguments: p.parseArguments(arguments), + }, token.Location) + if node == nil { + return nil + } + } + return node +} + +func (p *Parser) parseArguments(arguments []Node) []Node { + // If pipe operator is used, the first argument is the left-hand side + // of the operator, so we do not parse it as an argument inside brackets. + offset := len(arguments) + + p.expect(Bracket, "(") + for !p.current.Is(Bracket, ")") && p.err == nil { + if len(arguments) > offset { + p.expect(Operator, ",") + } + if p.current.Is(Bracket, ")") { + break + } + node := p.parseExpression(0) + arguments = append(arguments, node) + } + p.expect(Bracket, ")") + + return arguments +} + +func (p *Parser) parsePredicate() Node { + startToken := p.current + withBrackets := false + if p.current.Is(Bracket, "{") { + p.next() + withBrackets = true + } + + p.depth++ + var node Node + if withBrackets { + node = p.parseSequenceExpression() + } else { + node = p.parseExpression(0) + if p.current.Is(Operator, ";") { + p.error("wrap predicate with brackets { and }") + } + } + p.depth-- + + if withBrackets { + p.expect(Bracket, "}") + } + predicateNode := p.createNode(&PredicateNode{ + Node: node, + }, startToken.Location) + if predicateNode == nil { + return nil + } + return predicateNode +} + +func (p *Parser) parseArrayExpression(token Token) Node { + nodes := make([]Node, 0) + + p.expect(Bracket, "[") + for !p.current.Is(Bracket, "]") && p.err == nil { + if len(nodes) > 0 { + p.expect(Operator, ",") + if p.current.Is(Bracket, "]") { + goto end + } + } + node := p.parseExpression(0) + nodes = append(nodes, node) + } +end: + p.expect(Bracket, "]") + + node := p.createNode(&ArrayNode{Nodes: nodes}, token.Location) + if node == nil { + return nil + } + return node +} + +func (p *Parser) parseMapExpression(token Token) Node { + p.expect(Bracket, "{") + + nodes := make([]Node, 0) + for !p.current.Is(Bracket, "}") && p.err == nil { + if len(nodes) > 0 { + p.expect(Operator, ",") + if p.current.Is(Bracket, "}") { + goto end + } + if p.current.Is(Operator, ",") { + p.error("unexpected token %v", p.current) + } + } + + var key Node + // Map key can be one of: + // * number + // * string + // * identifier, which is equivalent to a string + // * expression, which must be enclosed in parentheses -- (1 + 2) + if p.current.Is(Number) || p.current.Is(String) || p.current.Is(Identifier) { + key = p.createNode(&StringNode{Value: p.current.Value}, p.current.Location) + if key == nil { + return nil + } + p.next() + } else if p.current.Is(Bracket, "(") { + key = p.parseExpression(0) + } else { + p.error("a map key must be a quoted string, a number, a identifier, or an expression enclosed in parentheses (unexpected token %v)", p.current) + } + + p.expect(Operator, ":") + + node := p.parseExpression(0) + pair := p.createNode(&PairNode{Key: key, Value: node}, token.Location) + if pair == nil { + return nil + } + nodes = append(nodes, pair) + } + +end: + p.expect(Bracket, "}") + + node := p.createNode(&MapNode{Pairs: nodes}, token.Location) + if node == nil { + return nil + } + return node +} + +func (p *Parser) parsePostfixExpression(node Node) Node { + postfixToken := p.current + for (postfixToken.Is(Operator) || postfixToken.Is(Bracket)) && p.err == nil { + optional := postfixToken.Value == "?." + parseToken: + if postfixToken.Value == "." || postfixToken.Value == "?." { + p.next() + + propertyToken := p.current + if optional && propertyToken.Is(Bracket, "[") { + postfixToken = propertyToken + goto parseToken + } + p.next() + + if propertyToken.Kind != Identifier && + // Operators like "not" and "matches" are valid methods or property names. + (propertyToken.Kind != Operator || !utils.IsValidIdentifier(propertyToken.Value)) { + p.error("expected name") + } + + property := p.createNode(&StringNode{Value: propertyToken.Value}, propertyToken.Location) + if property == nil { + return nil + } + + chainNode, isChain := node.(*ChainNode) + optional := postfixToken.Value == "?." + + if isChain { + node = chainNode.Node + } + + memberNode := p.createMemberNode(&MemberNode{ + Node: node, + Property: property, + Optional: optional, + }, propertyToken.Location) + if memberNode == nil { + return nil + } + + if p.current.Is(Bracket, "(") { + memberNode.Method = true + node = p.createNode(&CallNode{ + Callee: memberNode, + Arguments: p.parseArguments([]Node{}), + }, propertyToken.Location) + if node == nil { + return nil + } + } else { + node = memberNode + } + + if isChain || optional { + node = p.createNode(&ChainNode{Node: node}, propertyToken.Location) + if node == nil { + return nil + } + } + + } else if postfixToken.Value == "[" { + p.next() + var from, to Node + + if p.current.Is(Operator, ":") { // slice without from [:1] + p.next() + + if !p.current.Is(Bracket, "]") { // slice without from and to [:] + to = p.parseExpression(0) + } + + node = p.createNode(&SliceNode{ + Node: node, + To: to, + }, postfixToken.Location) + if node == nil { + return nil + } + p.expect(Bracket, "]") + + } else { + + from = p.parseExpression(0) + + if p.current.Is(Operator, ":") { + p.next() + + if !p.current.Is(Bracket, "]") { // slice without to [1:] + to = p.parseExpression(0) + } + + node = p.createNode(&SliceNode{ + Node: node, + From: from, + To: to, + }, postfixToken.Location) + if node == nil { + return nil + } + p.expect(Bracket, "]") + + } else { + // Slice operator [:] was not found, + // it should be just an index node. + node = p.createNode(&MemberNode{ + Node: node, + Property: from, + Optional: optional, + }, postfixToken.Location) + if node == nil { + return nil + } + if optional { + node = p.createNode(&ChainNode{Node: node}, postfixToken.Location) + if node == nil { + return nil + } + } + p.expect(Bracket, "]") + } + } + } else { + break + } + postfixToken = p.current + } + return node +} +func (p *Parser) parseComparison(left Node, token Token, precedence int) Node { + var rootNode Node + for { + comparator := p.parseExpression(precedence + 1) + cmpNode := p.createNode(&BinaryNode{ + Operator: token.Value, + Left: left, + Right: comparator, + }, token.Location) + if cmpNode == nil { + return nil + } + if rootNode == nil { + rootNode = cmpNode + } else { + rootNode = p.createNode(&BinaryNode{ + Operator: "&&", + Left: rootNode, + Right: cmpNode, + }, token.Location) + if rootNode == nil { + return nil + } + } + + left = comparator + token = p.current + if !(token.Is(Operator) && operator.IsComparison(token.Value) && p.err == nil) { + break + } + p.next() + } + return rootNode +} diff --git a/vendor/github.com/expr-lang/expr/parser/utils/utils.go b/vendor/github.com/expr-lang/expr/parser/utils/utils.go new file mode 100644 index 00000000000..947f9a4008d --- /dev/null +++ b/vendor/github.com/expr-lang/expr/parser/utils/utils.go @@ -0,0 +1,34 @@ +package utils + +import ( + "unicode" + "unicode/utf8" +) + +func IsValidIdentifier(str string) bool { + if len(str) == 0 { + return false + } + h, w := utf8.DecodeRuneInString(str) + if !IsAlphabetic(h) { + return false + } + for _, r := range str[w:] { + if !IsAlphaNumeric(r) { + return false + } + } + return true +} + +func IsSpace(r rune) bool { + return unicode.IsSpace(r) +} + +func IsAlphaNumeric(r rune) bool { + return IsAlphabetic(r) || unicode.IsDigit(r) +} + +func IsAlphabetic(r rune) bool { + return r == '_' || r == '$' || unicode.IsLetter(r) +} diff --git a/vendor/github.com/expr-lang/expr/patcher/operator_override.go b/vendor/github.com/expr-lang/expr/patcher/operator_override.go new file mode 100644 index 00000000000..cf4287c2444 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/patcher/operator_override.go @@ -0,0 +1,148 @@ +package patcher + +import ( + "fmt" + "reflect" + + "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/builtin" + "github.com/expr-lang/expr/checker/nature" + "github.com/expr-lang/expr/conf" +) + +type OperatorOverloading struct { + Operator string // Operator token to overload. + Overloads []string // List of function names to replace operator with. + Env *nature.Nature // Env type. + Functions conf.FunctionsTable // Env functions. + applied bool // Flag to indicate if any changes were made to the tree. + NtCache *nature.Cache +} + +func (p *OperatorOverloading) Visit(node *ast.Node) { + binaryNode, ok := (*node).(*ast.BinaryNode) + if !ok { + return + } + + if binaryNode.Operator != p.Operator { + return + } + + leftType := binaryNode.Left.Type() + rightType := binaryNode.Right.Type() + + ret, fn, ok := p.FindSuitableOperatorOverload(leftType, rightType) + if ok { + newNode := &ast.CallNode{ + Callee: &ast.IdentifierNode{Value: fn}, + Arguments: []ast.Node{binaryNode.Left, binaryNode.Right}, + } + newNode.SetType(ret) + ast.Patch(node, newNode) + p.applied = true + } +} + +// Tracking must be reset before every walk over the AST tree +func (p *OperatorOverloading) Reset() { + p.applied = false +} + +func (p *OperatorOverloading) ShouldRepeat() bool { + return p.applied +} + +func (p *OperatorOverloading) FindSuitableOperatorOverload(l, r reflect.Type) (reflect.Type, string, bool) { + t, fn, ok := p.findSuitableOperatorOverloadInFunctions(l, r) + if !ok { + t, fn, ok = p.findSuitableOperatorOverloadInTypes(l, r) + } + return t, fn, ok +} + +func (p *OperatorOverloading) findSuitableOperatorOverloadInTypes(l, r reflect.Type) (reflect.Type, string, bool) { + for _, fn := range p.Overloads { + fnType, ok := p.Env.Get(p.NtCache, fn) + if !ok { + continue + } + firstInIndex := 0 + if fnType.Method { + firstInIndex = 1 // As first argument to method is receiver. + } + ret, done := checkTypeSuits(fnType.Type, l, r, firstInIndex) + if done { + return ret, fn, true + } + } + return nil, "", false +} + +func (p *OperatorOverloading) findSuitableOperatorOverloadInFunctions(l, r reflect.Type) (reflect.Type, string, bool) { + for _, fn := range p.Overloads { + fnType, ok := p.Functions[fn] + if !ok { + continue + } + firstInIndex := 0 + for _, overload := range fnType.Types { + ret, done := checkTypeSuits(overload, l, r, firstInIndex) + if done { + return ret, fn, true + } + } + } + return nil, "", false +} + +func checkTypeSuits(t reflect.Type, l reflect.Type, r reflect.Type, firstInIndex int) (reflect.Type, bool) { + firstArgType := t.In(firstInIndex) + secondArgType := t.In(firstInIndex + 1) + + firstArgumentFit := l == firstArgType || (firstArgType.Kind() == reflect.Interface && (l == nil || l.Implements(firstArgType))) + secondArgumentFit := r == secondArgType || (secondArgType.Kind() == reflect.Interface && (r == nil || r.Implements(secondArgType))) + if firstArgumentFit && secondArgumentFit { + return t.Out(0), true + } + return nil, false +} + +func (p *OperatorOverloading) Check() { + for _, fn := range p.Overloads { + fnType, foundType := p.Env.Get(p.NtCache, fn) + fnFunc, foundFunc := p.Functions[fn] + if !foundFunc && (!foundType || fnType.Type.Kind() != reflect.Func) { + panic(fmt.Errorf("function %s for %s operator does not exist in the environment", fn, p.Operator)) + } + + if foundType { + checkType(fnType, fn, p.Operator) + } + + if foundFunc { + checkFunc(fnFunc, fn, p.Operator) + } + } +} + +func checkType(fnType nature.Nature, fn string, operator string) { + requiredNumIn := 2 + if fnType.Method { + requiredNumIn = 3 // As first argument of method is receiver. + } + if fnType.Type.NumIn() != requiredNumIn || fnType.Type.NumOut() != 1 { + panic(fmt.Errorf("function %s for %s operator does not have a correct signature", fn, operator)) + } +} + +func checkFunc(fn *builtin.Function, name string, operator string) { + if len(fn.Types) == 0 { + panic(fmt.Errorf("function %q for %q operator misses types", name, operator)) + } + for _, t := range fn.Types { + if t.NumIn() != 2 || t.NumOut() != 1 { + panic(fmt.Errorf("function %q for %q operator does not have a correct signature", name, operator)) + } + } +} diff --git a/vendor/github.com/expr-lang/expr/patcher/with_context.go b/vendor/github.com/expr-lang/expr/patcher/with_context.go new file mode 100644 index 00000000000..2043041ba11 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/patcher/with_context.go @@ -0,0 +1,68 @@ +package patcher + +import ( + "reflect" + + "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/checker/nature" + "github.com/expr-lang/expr/conf" +) + +// WithContext adds WithContext.Name argument to all functions calls with a context.Context argument. +type WithContext struct { + Name string + Functions conf.FunctionsTable // Optional: used to look up function types when callee type is unknown. + Env *nature.Nature // Optional: used to look up method types when callee type is unknown. + NtCache *nature.Cache // Optional: cache for nature lookups. +} + +// Visit adds WithContext.Name argument to all functions calls with a context.Context argument. +func (w WithContext) Visit(node *ast.Node) { + switch call := (*node).(type) { + case *ast.CallNode: + fn := call.Callee.Type() + if fn == nil { + return + } + // If callee type is interface{} (unknown), look up the function type from + // the Functions table or Env. This handles cases where the checker returns early + // without visiting nested call arguments (e.g., Date2() in Now2().After(Date2())) + // because the outer call's type is unknown due to missing context arguments. + if fn.Kind() == reflect.Interface { + if ident, ok := call.Callee.(*ast.IdentifierNode); ok { + if w.Functions != nil { + if f, ok := w.Functions[ident.Value]; ok { + fn = f.Type() + } + } + if fn.Kind() == reflect.Interface && w.Env != nil { + if m, ok := w.Env.MethodByName(w.NtCache, ident.Value); ok { + fn = m.Type + } + } + } + } + if fn.Kind() != reflect.Func { + return + } + switch fn.NumIn() { + case 0: + return + case 1: + if fn.In(0).String() != "context.Context" { + return + } + default: + if fn.In(0).String() != "context.Context" && + fn.In(1).String() != "context.Context" { + return + } + } + ast.Patch(node, &ast.CallNode{ + Callee: call.Callee, + Arguments: append([]ast.Node{ + &ast.IdentifierNode{Value: w.Name}, + }, call.Arguments...), + }) + } +} diff --git a/vendor/github.com/expr-lang/expr/patcher/with_timezone.go b/vendor/github.com/expr-lang/expr/patcher/with_timezone.go new file mode 100644 index 00000000000..83eb28e95ac --- /dev/null +++ b/vendor/github.com/expr-lang/expr/patcher/with_timezone.go @@ -0,0 +1,25 @@ +package patcher + +import ( + "time" + + "github.com/expr-lang/expr/ast" +) + +// WithTimezone passes Location to date() and now() functions. +type WithTimezone struct { + Location *time.Location +} + +func (t WithTimezone) Visit(node *ast.Node) { + if btin, ok := (*node).(*ast.BuiltinNode); ok { + switch btin.Name { + case "date", "now": + loc := &ast.ConstantNode{Value: t.Location} + ast.Patch(node, &ast.BuiltinNode{ + Name: btin.Name, + Arguments: append([]ast.Node{loc}, btin.Arguments...), + }) + } + } +} diff --git a/vendor/github.com/expr-lang/expr/types/types.go b/vendor/github.com/expr-lang/expr/types/types.go new file mode 100644 index 00000000000..33257c500f1 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/types/types.go @@ -0,0 +1,184 @@ +package types + +import ( + "fmt" + "reflect" + "strings" + + . "github.com/expr-lang/expr/checker/nature" +) + +// Type is a type that can be used to represent a value. +type Type interface { + Nature() Nature + Equal(Type) bool + String() string +} + +var ( + Int = TypeOf(0) + Int8 = TypeOf(int8(0)) + Int16 = TypeOf(int16(0)) + Int32 = TypeOf(int32(0)) + Int64 = TypeOf(int64(0)) + Uint = TypeOf(uint(0)) + Uint8 = TypeOf(uint8(0)) + Uint16 = TypeOf(uint16(0)) + Uint32 = TypeOf(uint32(0)) + Uint64 = TypeOf(uint64(0)) + Float = TypeOf(float32(0)) + Float64 = TypeOf(float64(0)) + String = TypeOf("") + Bool = TypeOf(true) + Nil = nilType{} + Any = anyType{} +) + +func TypeOf(v any) Type { + if v == nil { + return Nil + } + return rtype{t: reflect.TypeOf(v)} +} + +type anyType struct{} + +func (anyType) Nature() Nature { + return FromType(nil) +} + +func (anyType) Equal(t Type) bool { + return true +} + +func (anyType) String() string { + return "any" +} + +type nilType struct{} + +func (nilType) Nature() Nature { + return NatureOf(nil) +} + +func (nilType) Equal(t Type) bool { + if t == Any { + return true + } + return t == Nil +} + +func (nilType) String() string { + return "nil" +} + +type rtype struct { + t reflect.Type +} + +func (r rtype) Nature() Nature { + return FromType(r.t) +} + +func (r rtype) Equal(t Type) bool { + if t == Any { + return true + } + if rt, ok := t.(rtype); ok { + return r.t.String() == rt.t.String() + } + return false +} + +func (r rtype) String() string { + return r.t.String() +} + +// Map represents a map[string]any type with defined keys. +type Map map[string]Type + +const Extra = "[[__extra_keys__]]" + +func (m Map) Nature() Nature { + nt := NatureOf(map[string]any{}) + if nt.TypeData == nil { + nt.TypeData = new(TypeData) + } + nt.Fields = make(map[string]Nature, len(m)) + nt.Strict = true + for k, v := range m { + if k == Extra { + nt.Strict = false + natureOfDefaultValue := v.Nature() + nt.DefaultMapValue = &natureOfDefaultValue + continue + } + nt.Fields[k] = v.Nature() + } + return nt +} + +func (m Map) Equal(t Type) bool { + if t == Any { + return true + } + mt, ok := t.(Map) + if !ok { + return false + } + if len(m) != len(mt) { + return false + } + for k, v := range m { + if !v.Equal(mt[k]) { + return false + } + } + return true +} + +func (m Map) String() string { + pairs := make([]string, 0, len(m)) + for k, v := range m { + pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) + } + return fmt.Sprintf("Map{%s}", strings.Join(pairs, ", ")) +} + +// Array returns a type that represents an array of the given type. +func Array(of Type) Type { + return array{of} +} + +type array struct { + of Type +} + +func (a array) Nature() Nature { + of := a.of.Nature() + nt := NatureOf([]any{}) + if nt.TypeData == nil { + nt.TypeData = new(TypeData) + } + nt.Fields = make(map[string]Nature, 1) + nt.Ref = &of + return nt +} + +func (a array) Equal(t Type) bool { + if t == Any { + return true + } + at, ok := t.(array) + if !ok { + return false + } + if a.of.Equal(at.of) { + return true + } + return false +} + +func (a array) String() string { + return fmt.Sprintf("Array{%s}", a.of.String()) +} diff --git a/vendor/github.com/expr-lang/expr/vm/debug.go b/vendor/github.com/expr-lang/expr/vm/debug.go new file mode 100644 index 00000000000..470bf90e26d --- /dev/null +++ b/vendor/github.com/expr-lang/expr/vm/debug.go @@ -0,0 +1,6 @@ +//go:build expr_debug +// +build expr_debug + +package vm + +const debug = true diff --git a/vendor/github.com/expr-lang/expr/vm/debug_off.go b/vendor/github.com/expr-lang/expr/vm/debug_off.go new file mode 100644 index 00000000000..8a9e965e2a6 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/vm/debug_off.go @@ -0,0 +1,6 @@ +//go:build !expr_debug +// +build !expr_debug + +package vm + +const debug = false diff --git a/vendor/github.com/expr-lang/expr/vm/func_types[generated].go b/vendor/github.com/expr-lang/expr/vm/func_types[generated].go new file mode 100644 index 00000000000..610b4152021 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/vm/func_types[generated].go @@ -0,0 +1,370 @@ +// Code generated by vm/func_types/main.go. DO NOT EDIT. + +package vm + +import ( + "fmt" + "time" +) + +var FuncTypes = []any{ + 1: new(func() time.Duration), + 2: new(func() time.Month), + 3: new(func() time.Time), + 4: new(func() time.Weekday), + 5: new(func() []interface{}), + 6: new(func() []uint8), + 7: new(func() interface{}), + 8: new(func() bool), + 9: new(func() uint8), + 10: new(func() float32), + 11: new(func() float64), + 12: new(func() int), + 13: new(func() int16), + 14: new(func() int32), + 15: new(func() int64), + 16: new(func() int8), + 17: new(func() map[string]interface{}), + 18: new(func() int32), + 19: new(func() string), + 20: new(func() uint), + 21: new(func() uint16), + 22: new(func() uint32), + 23: new(func() uint64), + 24: new(func() uint8), + 25: new(func(time.Duration) time.Duration), + 26: new(func(time.Duration) time.Time), + 27: new(func(time.Time) time.Duration), + 28: new(func(time.Time) bool), + 29: new(func([]interface{}) []interface{}), + 30: new(func([]interface{}) interface{}), + 31: new(func([]interface{}) map[string]interface{}), + 32: new(func([]interface{}, string) string), + 33: new(func([]uint8) string), + 34: new(func([]string, string) string), + 35: new(func(interface{}) []interface{}), + 36: new(func(interface{}) interface{}), + 37: new(func(interface{}) bool), + 38: new(func(interface{}) float64), + 39: new(func(interface{}) int), + 40: new(func(interface{}) map[string]interface{}), + 41: new(func(interface{}) string), + 42: new(func(interface{}, interface{}) []interface{}), + 43: new(func(interface{}, interface{}) interface{}), + 44: new(func(interface{}, interface{}) bool), + 45: new(func(interface{}, interface{}) string), + 46: new(func(bool) bool), + 47: new(func(bool) float64), + 48: new(func(bool) int), + 49: new(func(bool) string), + 50: new(func(bool, bool) bool), + 51: new(func(float32) float64), + 52: new(func(float64) bool), + 53: new(func(float64) float32), + 54: new(func(float64) float64), + 55: new(func(float64) int), + 56: new(func(float64) string), + 57: new(func(float64, float64) bool), + 58: new(func(int) bool), + 59: new(func(int) float64), + 60: new(func(int) int), + 61: new(func(int) string), + 62: new(func(int, int) bool), + 63: new(func(int, int) int), + 64: new(func(int, int) string), + 65: new(func(int16) int32), + 66: new(func(int32) float64), + 67: new(func(int32) int), + 68: new(func(int32) int64), + 69: new(func(int64) time.Time), + 70: new(func(int8) int), + 71: new(func(int8) int16), + 72: new(func(string) []uint8), + 73: new(func(string) []string), + 74: new(func(string) bool), + 75: new(func(string) float64), + 76: new(func(string) int), + 77: new(func(string) string), + 78: new(func(string, uint8) int), + 79: new(func(string, int) int), + 80: new(func(string, int32) int), + 81: new(func(string, string) bool), + 82: new(func(string, string) string), + 83: new(func(uint) float64), + 84: new(func(uint) int), + 85: new(func(uint) uint), + 86: new(func(uint16) uint), + 87: new(func(uint32) uint64), + 88: new(func(uint64) float64), + 89: new(func(uint64) int64), + 90: new(func(uint8) uint8), +} + +func (vm *VM) call(fn any, kind int) any { + switch kind { + case 1: + return fn.(func() time.Duration)() + case 2: + return fn.(func() time.Month)() + case 3: + return fn.(func() time.Time)() + case 4: + return fn.(func() time.Weekday)() + case 5: + return fn.(func() []interface{})() + case 6: + return fn.(func() []uint8)() + case 7: + return fn.(func() interface{})() + case 8: + return fn.(func() bool)() + case 9: + return fn.(func() uint8)() + case 10: + return fn.(func() float32)() + case 11: + return fn.(func() float64)() + case 12: + return fn.(func() int)() + case 13: + return fn.(func() int16)() + case 14: + return fn.(func() int32)() + case 15: + return fn.(func() int64)() + case 16: + return fn.(func() int8)() + case 17: + return fn.(func() map[string]interface{})() + case 18: + return fn.(func() int32)() + case 19: + return fn.(func() string)() + case 20: + return fn.(func() uint)() + case 21: + return fn.(func() uint16)() + case 22: + return fn.(func() uint32)() + case 23: + return fn.(func() uint64)() + case 24: + return fn.(func() uint8)() + case 25: + arg1 := vm.pop().(time.Duration) + return fn.(func(time.Duration) time.Duration)(arg1) + case 26: + arg1 := vm.pop().(time.Duration) + return fn.(func(time.Duration) time.Time)(arg1) + case 27: + arg1 := vm.pop().(time.Time) + return fn.(func(time.Time) time.Duration)(arg1) + case 28: + arg1 := vm.pop().(time.Time) + return fn.(func(time.Time) bool)(arg1) + case 29: + arg1 := vm.pop().([]interface{}) + return fn.(func([]interface{}) []interface{})(arg1) + case 30: + arg1 := vm.pop().([]interface{}) + return fn.(func([]interface{}) interface{})(arg1) + case 31: + arg1 := vm.pop().([]interface{}) + return fn.(func([]interface{}) map[string]interface{})(arg1) + case 32: + arg2 := vm.pop().(string) + arg1 := vm.pop().([]interface{}) + return fn.(func([]interface{}, string) string)(arg1, arg2) + case 33: + arg1 := vm.pop().([]uint8) + return fn.(func([]uint8) string)(arg1) + case 34: + arg2 := vm.pop().(string) + arg1 := vm.pop().([]string) + return fn.(func([]string, string) string)(arg1, arg2) + case 35: + arg1 := vm.pop() + return fn.(func(interface{}) []interface{})(arg1) + case 36: + arg1 := vm.pop() + return fn.(func(interface{}) interface{})(arg1) + case 37: + arg1 := vm.pop() + return fn.(func(interface{}) bool)(arg1) + case 38: + arg1 := vm.pop() + return fn.(func(interface{}) float64)(arg1) + case 39: + arg1 := vm.pop() + return fn.(func(interface{}) int)(arg1) + case 40: + arg1 := vm.pop() + return fn.(func(interface{}) map[string]interface{})(arg1) + case 41: + arg1 := vm.pop() + return fn.(func(interface{}) string)(arg1) + case 42: + arg2 := vm.pop() + arg1 := vm.pop() + return fn.(func(interface{}, interface{}) []interface{})(arg1, arg2) + case 43: + arg2 := vm.pop() + arg1 := vm.pop() + return fn.(func(interface{}, interface{}) interface{})(arg1, arg2) + case 44: + arg2 := vm.pop() + arg1 := vm.pop() + return fn.(func(interface{}, interface{}) bool)(arg1, arg2) + case 45: + arg2 := vm.pop() + arg1 := vm.pop() + return fn.(func(interface{}, interface{}) string)(arg1, arg2) + case 46: + arg1 := vm.pop().(bool) + return fn.(func(bool) bool)(arg1) + case 47: + arg1 := vm.pop().(bool) + return fn.(func(bool) float64)(arg1) + case 48: + arg1 := vm.pop().(bool) + return fn.(func(bool) int)(arg1) + case 49: + arg1 := vm.pop().(bool) + return fn.(func(bool) string)(arg1) + case 50: + arg2 := vm.pop().(bool) + arg1 := vm.pop().(bool) + return fn.(func(bool, bool) bool)(arg1, arg2) + case 51: + arg1 := vm.pop().(float32) + return fn.(func(float32) float64)(arg1) + case 52: + arg1 := vm.pop().(float64) + return fn.(func(float64) bool)(arg1) + case 53: + arg1 := vm.pop().(float64) + return fn.(func(float64) float32)(arg1) + case 54: + arg1 := vm.pop().(float64) + return fn.(func(float64) float64)(arg1) + case 55: + arg1 := vm.pop().(float64) + return fn.(func(float64) int)(arg1) + case 56: + arg1 := vm.pop().(float64) + return fn.(func(float64) string)(arg1) + case 57: + arg2 := vm.pop().(float64) + arg1 := vm.pop().(float64) + return fn.(func(float64, float64) bool)(arg1, arg2) + case 58: + arg1 := vm.pop().(int) + return fn.(func(int) bool)(arg1) + case 59: + arg1 := vm.pop().(int) + return fn.(func(int) float64)(arg1) + case 60: + arg1 := vm.pop().(int) + return fn.(func(int) int)(arg1) + case 61: + arg1 := vm.pop().(int) + return fn.(func(int) string)(arg1) + case 62: + arg2 := vm.pop().(int) + arg1 := vm.pop().(int) + return fn.(func(int, int) bool)(arg1, arg2) + case 63: + arg2 := vm.pop().(int) + arg1 := vm.pop().(int) + return fn.(func(int, int) int)(arg1, arg2) + case 64: + arg2 := vm.pop().(int) + arg1 := vm.pop().(int) + return fn.(func(int, int) string)(arg1, arg2) + case 65: + arg1 := vm.pop().(int16) + return fn.(func(int16) int32)(arg1) + case 66: + arg1 := vm.pop().(int32) + return fn.(func(int32) float64)(arg1) + case 67: + arg1 := vm.pop().(int32) + return fn.(func(int32) int)(arg1) + case 68: + arg1 := vm.pop().(int32) + return fn.(func(int32) int64)(arg1) + case 69: + arg1 := vm.pop().(int64) + return fn.(func(int64) time.Time)(arg1) + case 70: + arg1 := vm.pop().(int8) + return fn.(func(int8) int)(arg1) + case 71: + arg1 := vm.pop().(int8) + return fn.(func(int8) int16)(arg1) + case 72: + arg1 := vm.pop().(string) + return fn.(func(string) []uint8)(arg1) + case 73: + arg1 := vm.pop().(string) + return fn.(func(string) []string)(arg1) + case 74: + arg1 := vm.pop().(string) + return fn.(func(string) bool)(arg1) + case 75: + arg1 := vm.pop().(string) + return fn.(func(string) float64)(arg1) + case 76: + arg1 := vm.pop().(string) + return fn.(func(string) int)(arg1) + case 77: + arg1 := vm.pop().(string) + return fn.(func(string) string)(arg1) + case 78: + arg2 := vm.pop().(uint8) + arg1 := vm.pop().(string) + return fn.(func(string, uint8) int)(arg1, arg2) + case 79: + arg2 := vm.pop().(int) + arg1 := vm.pop().(string) + return fn.(func(string, int) int)(arg1, arg2) + case 80: + arg2 := vm.pop().(int32) + arg1 := vm.pop().(string) + return fn.(func(string, int32) int)(arg1, arg2) + case 81: + arg2 := vm.pop().(string) + arg1 := vm.pop().(string) + return fn.(func(string, string) bool)(arg1, arg2) + case 82: + arg2 := vm.pop().(string) + arg1 := vm.pop().(string) + return fn.(func(string, string) string)(arg1, arg2) + case 83: + arg1 := vm.pop().(uint) + return fn.(func(uint) float64)(arg1) + case 84: + arg1 := vm.pop().(uint) + return fn.(func(uint) int)(arg1) + case 85: + arg1 := vm.pop().(uint) + return fn.(func(uint) uint)(arg1) + case 86: + arg1 := vm.pop().(uint16) + return fn.(func(uint16) uint)(arg1) + case 87: + arg1 := vm.pop().(uint32) + return fn.(func(uint32) uint64)(arg1) + case 88: + arg1 := vm.pop().(uint64) + return fn.(func(uint64) float64)(arg1) + case 89: + arg1 := vm.pop().(uint64) + return fn.(func(uint64) int64)(arg1) + case 90: + arg1 := vm.pop().(uint8) + return fn.(func(uint8) uint8)(arg1) + + } + panic(fmt.Sprintf("unknown function kind (%v)", kind)) +} diff --git a/vendor/github.com/expr-lang/expr/vm/opcodes.go b/vendor/github.com/expr-lang/expr/vm/opcodes.go new file mode 100644 index 00000000000..5fca0fa29a7 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/vm/opcodes.go @@ -0,0 +1,90 @@ +package vm + +type Opcode byte + +const ( + OpInvalid Opcode = iota + OpPush + OpInt + OpPop + OpStore + OpLoadVar + OpLoadConst + OpLoadField + OpLoadFast + OpLoadMethod + OpLoadFunc + OpLoadEnv + OpFetch + OpFetchField + OpMethod + OpTrue + OpFalse + OpNil + OpNegate + OpNot + OpEqual + OpEqualInt + OpEqualString + OpJump + OpJumpIfTrue + OpJumpIfFalse + OpJumpIfNil + OpJumpIfNotNil + OpJumpIfEnd + OpJumpBackward + OpIn + OpLess + OpMore + OpLessOrEqual + OpMoreOrEqual + OpAdd + OpSubtract + OpMultiply + OpDivide + OpModulo + OpExponent + OpRange + OpMatches + OpMatchesConst + OpContains + OpStartsWith + OpEndsWith + OpSlice + OpCall + OpCall0 + OpCall1 + OpCall2 + OpCall3 + OpCallN + OpCallFast + OpCallSafe + OpCallTyped + OpCallBuiltin1 + OpArray + OpMap + OpLen + OpCast + OpDeref + OpIncrementIndex + OpDecrementIndex + OpIncrementCount + OpGetIndex + OpGetCount + OpGetLen + OpGetAcc + OpSetAcc + OpSetIndex + OpPointer + OpThrow + OpCreate + OpGroupBy + OpSortBy + OpSort + OpProfileStart + OpProfileEnd + OpBegin + OpAnd + OpOr + OpEnd // This opcode must be at the end of this list. +) diff --git a/vendor/github.com/expr-lang/expr/vm/program.go b/vendor/github.com/expr-lang/expr/vm/program.go new file mode 100644 index 00000000000..7eb96bd3d72 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/vm/program.go @@ -0,0 +1,391 @@ +package vm + +import ( + "bytes" + "fmt" + "io" + "reflect" + "regexp" + "strings" + "text/tabwriter" + + "github.com/expr-lang/expr/ast" + "github.com/expr-lang/expr/builtin" + "github.com/expr-lang/expr/file" + "github.com/expr-lang/expr/vm/runtime" +) + +// Program represents a compiled expression. +type Program struct { + Bytecode []Opcode + Arguments []int + Constants []any + + source file.Source + node ast.Node + locations []file.Location + variables int + functions []Function + debugInfo map[string]string + span *Span +} + +// NewProgram returns a new Program. It's used by the compiler. +func NewProgram( + source file.Source, + node ast.Node, + locations []file.Location, + variables int, + constants []any, + bytecode []Opcode, + arguments []int, + functions []Function, + debugInfo map[string]string, + span *Span, +) *Program { + return &Program{ + source: source, + node: node, + locations: locations, + variables: variables, + Constants: constants, + Bytecode: bytecode, + Arguments: arguments, + functions: functions, + debugInfo: debugInfo, + span: span, + } +} + +// Source returns origin file.Source. +func (program *Program) Source() file.Source { + return program.source +} + +// Node returns origin ast.Node. +func (program *Program) Node() ast.Node { + return program.node +} + +// Locations returns a slice of bytecode's locations. +func (program *Program) Locations() []file.Location { + return program.locations +} + +// Disassemble returns opcodes as a string. +func (program *Program) Disassemble() string { + var buf bytes.Buffer + w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0) + program.DisassembleWriter(w) + _ = w.Flush() + return buf.String() +} + +// DisassembleWriter takes a writer and writes opcodes to it. +func (program *Program) DisassembleWriter(w io.Writer) { + ip := 0 + for ip < len(program.Bytecode) { + pp := ip + op := program.Bytecode[ip] + arg := program.Arguments[ip] + ip += 1 + + code := func(label string) { + _, _ = fmt.Fprintf(w, "%v\t%v\n", pp, label) + } + jump := func(label string) { + _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t(%v)\n", pp, label, arg, ip+arg) + } + jumpBack := func(label string) { + _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t(%v)\n", pp, label, arg, ip-arg) + } + argument := func(label string) { + _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\n", pp, label, arg) + } + argumentWithInfo := func(label string, prefix string) { + _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, program.debugInfo[fmt.Sprintf("%s_%d", prefix, arg)]) + } + constant := func(label string) { + var c any + if arg < len(program.Constants) { + c = program.Constants[arg] + } else { + c = "out of range" + } + if name, ok := program.debugInfo[fmt.Sprintf("const_%d", arg)]; ok { + c = name + } + if r, ok := c.(*regexp.Regexp); ok { + c = r.String() + } + if field, ok := c.(*runtime.Field); ok { + c = fmt.Sprintf("{%v %v}", strings.Join(field.Path, "."), field.Index) + } + if method, ok := c.(*runtime.Method); ok { + c = fmt.Sprintf("{%v %v}", method.Name, method.Index) + } + _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, c) + } + builtinArg := func(label string) { + _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, builtin.Builtins[arg].Name) + } + + switch op { + case OpInvalid: + code("OpInvalid") + + case OpPush: + constant("OpPush") + + case OpInt: + argument("OpInt") + + case OpPop: + code("OpPop") + + case OpStore: + argumentWithInfo("OpStore", "var") + + case OpLoadVar: + argumentWithInfo("OpLoadVar", "var") + + case OpLoadConst: + constant("OpLoadConst") + + case OpLoadField: + constant("OpLoadField") + + case OpLoadFast: + constant("OpLoadFast") + + case OpLoadMethod: + constant("OpLoadMethod") + + case OpLoadFunc: + argumentWithInfo("OpLoadFunc", "func") + + case OpLoadEnv: + code("OpLoadEnv") + + case OpFetch: + code("OpFetch") + + case OpFetchField: + constant("OpFetchField") + + case OpMethod: + constant("OpMethod") + + case OpTrue: + code("OpTrue") + + case OpFalse: + code("OpFalse") + + case OpNil: + code("OpNil") + + case OpNegate: + code("OpNegate") + + case OpNot: + code("OpNot") + + case OpEqual: + code("OpEqual") + + case OpEqualInt: + code("OpEqualInt") + + case OpEqualString: + code("OpEqualString") + + case OpJump: + jump("OpJump") + + case OpJumpIfTrue: + jump("OpJumpIfTrue") + + case OpJumpIfFalse: + jump("OpJumpIfFalse") + + case OpJumpIfNil: + jump("OpJumpIfNil") + + case OpJumpIfNotNil: + jump("OpJumpIfNotNil") + + case OpJumpIfEnd: + jump("OpJumpIfEnd") + + case OpJumpBackward: + jumpBack("OpJumpBackward") + + case OpIn: + code("OpIn") + + case OpLess: + code("OpLess") + + case OpMore: + code("OpMore") + + case OpLessOrEqual: + code("OpLessOrEqual") + + case OpMoreOrEqual: + code("OpMoreOrEqual") + + case OpAdd: + code("OpAdd") + + case OpSubtract: + code("OpSubtract") + + case OpMultiply: + code("OpMultiply") + + case OpDivide: + code("OpDivide") + + case OpModulo: + code("OpModulo") + + case OpExponent: + code("OpExponent") + + case OpRange: + code("OpRange") + + case OpMatches: + code("OpMatches") + + case OpMatchesConst: + constant("OpMatchesConst") + + case OpContains: + code("OpContains") + + case OpStartsWith: + code("OpStartsWith") + + case OpEndsWith: + code("OpEndsWith") + + case OpSlice: + code("OpSlice") + + case OpCall: + argument("OpCall") + + case OpCall0: + argumentWithInfo("OpCall0", "func") + + case OpCall1: + argumentWithInfo("OpCall1", "func") + + case OpCall2: + argumentWithInfo("OpCall2", "func") + + case OpCall3: + argumentWithInfo("OpCall3", "func") + + case OpCallN: + argument("OpCallN") + + case OpCallFast: + argument("OpCallFast") + + case OpCallSafe: + argument("OpCallSafe") + + case OpCallTyped: + signature := reflect.TypeOf(FuncTypes[arg]).Elem().String() + _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, "OpCallTyped", arg, signature) + + case OpCallBuiltin1: + builtinArg("OpCallBuiltin1") + + case OpArray: + code("OpArray") + + case OpMap: + code("OpMap") + + case OpLen: + code("OpLen") + + case OpCast: + argument("OpCast") + + case OpDeref: + code("OpDeref") + + case OpIncrementIndex: + code("OpIncrementIndex") + + case OpDecrementIndex: + code("OpDecrementIndex") + + case OpIncrementCount: + code("OpIncrementCount") + + case OpGetIndex: + code("OpGetIndex") + + case OpGetCount: + code("OpGetCount") + + case OpGetLen: + code("OpGetLen") + + case OpGetAcc: + code("OpGetAcc") + + case OpSetAcc: + code("OpSetAcc") + + case OpSetIndex: + code("OpSetIndex") + + case OpPointer: + code("OpPointer") + + case OpThrow: + code("OpThrow") + + case OpCreate: + argument("OpCreate") + + case OpGroupBy: + code("OpGroupBy") + + case OpSortBy: + code("OpSortBy") + + case OpSort: + code("OpSort") + + case OpProfileStart: + code("OpProfileStart") + + case OpProfileEnd: + code("OpProfileEnd") + + case OpBegin: + code("OpBegin") + + case OpAnd: + code("OpAnd") + + case OpOr: + code("OpOr") + + case OpEnd: + code("OpEnd") + + default: + _, _ = fmt.Fprintf(w, "%v\t%#x (unknown)\n", ip, op) + } + } +} diff --git a/vendor/github.com/expr-lang/expr/vm/runtime/helpers[generated].go b/vendor/github.com/expr-lang/expr/vm/runtime/helpers[generated].go new file mode 100644 index 00000000000..d950f111147 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/vm/runtime/helpers[generated].go @@ -0,0 +1,3718 @@ +// Code generated by vm/runtime/helpers/main.go. DO NOT EDIT. + +package runtime + +import ( + "fmt" + "reflect" + "time" +) + +func Equal(a, b interface{}) bool { + switch x := a.(type) { + case uint: + switch y := b.(type) { + case uint: + return int(x) == int(y) + case uint8: + return int(x) == int(y) + case uint16: + return int(x) == int(y) + case uint32: + return int(x) == int(y) + case uint64: + return int(x) == int(y) + case int: + return int(x) == int(y) + case int8: + return int(x) == int(y) + case int16: + return int(x) == int(y) + case int32: + return int(x) == int(y) + case int64: + return int(x) == int(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case uint8: + switch y := b.(type) { + case uint: + return int(x) == int(y) + case uint8: + return int(x) == int(y) + case uint16: + return int(x) == int(y) + case uint32: + return int(x) == int(y) + case uint64: + return int(x) == int(y) + case int: + return int(x) == int(y) + case int8: + return int(x) == int(y) + case int16: + return int(x) == int(y) + case int32: + return int(x) == int(y) + case int64: + return int(x) == int(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case uint16: + switch y := b.(type) { + case uint: + return int(x) == int(y) + case uint8: + return int(x) == int(y) + case uint16: + return int(x) == int(y) + case uint32: + return int(x) == int(y) + case uint64: + return int(x) == int(y) + case int: + return int(x) == int(y) + case int8: + return int(x) == int(y) + case int16: + return int(x) == int(y) + case int32: + return int(x) == int(y) + case int64: + return int(x) == int(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case uint32: + switch y := b.(type) { + case uint: + return int(x) == int(y) + case uint8: + return int(x) == int(y) + case uint16: + return int(x) == int(y) + case uint32: + return int(x) == int(y) + case uint64: + return int(x) == int(y) + case int: + return int(x) == int(y) + case int8: + return int(x) == int(y) + case int16: + return int(x) == int(y) + case int32: + return int(x) == int(y) + case int64: + return int(x) == int(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case uint64: + switch y := b.(type) { + case uint: + return int(x) == int(y) + case uint8: + return int(x) == int(y) + case uint16: + return int(x) == int(y) + case uint32: + return int(x) == int(y) + case uint64: + return int(x) == int(y) + case int: + return int(x) == int(y) + case int8: + return int(x) == int(y) + case int16: + return int(x) == int(y) + case int32: + return int(x) == int(y) + case int64: + return int(x) == int(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case int: + switch y := b.(type) { + case uint: + return int(x) == int(y) + case uint8: + return int(x) == int(y) + case uint16: + return int(x) == int(y) + case uint32: + return int(x) == int(y) + case uint64: + return int(x) == int(y) + case int: + return int(x) == int(y) + case int8: + return int(x) == int(y) + case int16: + return int(x) == int(y) + case int32: + return int(x) == int(y) + case int64: + return int(x) == int(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case int8: + switch y := b.(type) { + case uint: + return int(x) == int(y) + case uint8: + return int(x) == int(y) + case uint16: + return int(x) == int(y) + case uint32: + return int(x) == int(y) + case uint64: + return int(x) == int(y) + case int: + return int(x) == int(y) + case int8: + return int(x) == int(y) + case int16: + return int(x) == int(y) + case int32: + return int(x) == int(y) + case int64: + return int(x) == int(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case int16: + switch y := b.(type) { + case uint: + return int(x) == int(y) + case uint8: + return int(x) == int(y) + case uint16: + return int(x) == int(y) + case uint32: + return int(x) == int(y) + case uint64: + return int(x) == int(y) + case int: + return int(x) == int(y) + case int8: + return int(x) == int(y) + case int16: + return int(x) == int(y) + case int32: + return int(x) == int(y) + case int64: + return int(x) == int(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case int32: + switch y := b.(type) { + case uint: + return int(x) == int(y) + case uint8: + return int(x) == int(y) + case uint16: + return int(x) == int(y) + case uint32: + return int(x) == int(y) + case uint64: + return int(x) == int(y) + case int: + return int(x) == int(y) + case int8: + return int(x) == int(y) + case int16: + return int(x) == int(y) + case int32: + return int(x) == int(y) + case int64: + return int(x) == int(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case int64: + switch y := b.(type) { + case uint: + return int(x) == int(y) + case uint8: + return int(x) == int(y) + case uint16: + return int(x) == int(y) + case uint32: + return int(x) == int(y) + case uint64: + return int(x) == int(y) + case int: + return int(x) == int(y) + case int8: + return int(x) == int(y) + case int16: + return int(x) == int(y) + case int32: + return int(x) == int(y) + case int64: + return int(x) == int(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case float32: + switch y := b.(type) { + case uint: + return float64(x) == float64(y) + case uint8: + return float64(x) == float64(y) + case uint16: + return float64(x) == float64(y) + case uint32: + return float64(x) == float64(y) + case uint64: + return float64(x) == float64(y) + case int: + return float64(x) == float64(y) + case int8: + return float64(x) == float64(y) + case int16: + return float64(x) == float64(y) + case int32: + return float64(x) == float64(y) + case int64: + return float64(x) == float64(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case float64: + switch y := b.(type) { + case uint: + return float64(x) == float64(y) + case uint8: + return float64(x) == float64(y) + case uint16: + return float64(x) == float64(y) + case uint32: + return float64(x) == float64(y) + case uint64: + return float64(x) == float64(y) + case int: + return float64(x) == float64(y) + case int8: + return float64(x) == float64(y) + case int16: + return float64(x) == float64(y) + case int32: + return float64(x) == float64(y) + case int64: + return float64(x) == float64(y) + case float32: + return float64(x) == float64(y) + case float64: + return float64(x) == float64(y) + } + case []any: + switch y := b.(type) { + case []string: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []uint: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []uint8: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []uint16: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []uint32: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []uint64: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []int: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []int8: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []int16: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []int32: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []int64: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []float32: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []float64: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + case []any: + if len(x) != len(y) { + return false + } + for i := range x { + if !Equal(x[i], y[i]) { + return false + } + } + return true + } + case []string: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []string: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []uint: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []uint: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []uint8: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []uint8: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []uint16: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []uint16: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []uint32: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []uint32: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []uint64: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []uint64: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []int: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []int: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []int8: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []int8: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []int16: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []int16: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []int32: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []int32: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []int64: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []int64: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []float32: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []float32: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case []float64: + switch y := b.(type) { + case []any: + return Equal(y, x) + case []float64: + if len(x) != len(y) { + return false + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true + } + case string: + switch y := b.(type) { + case string: + return x == y + } + case time.Time: + switch y := b.(type) { + case time.Time: + return x.Equal(y) + } + case time.Duration: + switch y := b.(type) { + case time.Duration: + return x == y + } + case bool: + switch y := b.(type) { + case bool: + return x == y + } + } + if IsNil(a) && IsNil(b) { + return true + } + return reflect.DeepEqual(a, b) +} + +func Less(a, b interface{}) bool { + switch x := a.(type) { + case uint: + switch y := b.(type) { + case uint: + return int(x) < int(y) + case uint8: + return int(x) < int(y) + case uint16: + return int(x) < int(y) + case uint32: + return int(x) < int(y) + case uint64: + return int(x) < int(y) + case int: + return int(x) < int(y) + case int8: + return int(x) < int(y) + case int16: + return int(x) < int(y) + case int32: + return int(x) < int(y) + case int64: + return int(x) < int(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case uint8: + switch y := b.(type) { + case uint: + return int(x) < int(y) + case uint8: + return int(x) < int(y) + case uint16: + return int(x) < int(y) + case uint32: + return int(x) < int(y) + case uint64: + return int(x) < int(y) + case int: + return int(x) < int(y) + case int8: + return int(x) < int(y) + case int16: + return int(x) < int(y) + case int32: + return int(x) < int(y) + case int64: + return int(x) < int(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case uint16: + switch y := b.(type) { + case uint: + return int(x) < int(y) + case uint8: + return int(x) < int(y) + case uint16: + return int(x) < int(y) + case uint32: + return int(x) < int(y) + case uint64: + return int(x) < int(y) + case int: + return int(x) < int(y) + case int8: + return int(x) < int(y) + case int16: + return int(x) < int(y) + case int32: + return int(x) < int(y) + case int64: + return int(x) < int(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case uint32: + switch y := b.(type) { + case uint: + return int(x) < int(y) + case uint8: + return int(x) < int(y) + case uint16: + return int(x) < int(y) + case uint32: + return int(x) < int(y) + case uint64: + return int(x) < int(y) + case int: + return int(x) < int(y) + case int8: + return int(x) < int(y) + case int16: + return int(x) < int(y) + case int32: + return int(x) < int(y) + case int64: + return int(x) < int(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case uint64: + switch y := b.(type) { + case uint: + return int(x) < int(y) + case uint8: + return int(x) < int(y) + case uint16: + return int(x) < int(y) + case uint32: + return int(x) < int(y) + case uint64: + return int(x) < int(y) + case int: + return int(x) < int(y) + case int8: + return int(x) < int(y) + case int16: + return int(x) < int(y) + case int32: + return int(x) < int(y) + case int64: + return int(x) < int(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case int: + switch y := b.(type) { + case uint: + return int(x) < int(y) + case uint8: + return int(x) < int(y) + case uint16: + return int(x) < int(y) + case uint32: + return int(x) < int(y) + case uint64: + return int(x) < int(y) + case int: + return int(x) < int(y) + case int8: + return int(x) < int(y) + case int16: + return int(x) < int(y) + case int32: + return int(x) < int(y) + case int64: + return int(x) < int(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case int8: + switch y := b.(type) { + case uint: + return int(x) < int(y) + case uint8: + return int(x) < int(y) + case uint16: + return int(x) < int(y) + case uint32: + return int(x) < int(y) + case uint64: + return int(x) < int(y) + case int: + return int(x) < int(y) + case int8: + return int(x) < int(y) + case int16: + return int(x) < int(y) + case int32: + return int(x) < int(y) + case int64: + return int(x) < int(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case int16: + switch y := b.(type) { + case uint: + return int(x) < int(y) + case uint8: + return int(x) < int(y) + case uint16: + return int(x) < int(y) + case uint32: + return int(x) < int(y) + case uint64: + return int(x) < int(y) + case int: + return int(x) < int(y) + case int8: + return int(x) < int(y) + case int16: + return int(x) < int(y) + case int32: + return int(x) < int(y) + case int64: + return int(x) < int(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case int32: + switch y := b.(type) { + case uint: + return int(x) < int(y) + case uint8: + return int(x) < int(y) + case uint16: + return int(x) < int(y) + case uint32: + return int(x) < int(y) + case uint64: + return int(x) < int(y) + case int: + return int(x) < int(y) + case int8: + return int(x) < int(y) + case int16: + return int(x) < int(y) + case int32: + return int(x) < int(y) + case int64: + return int(x) < int(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case int64: + switch y := b.(type) { + case uint: + return int(x) < int(y) + case uint8: + return int(x) < int(y) + case uint16: + return int(x) < int(y) + case uint32: + return int(x) < int(y) + case uint64: + return int(x) < int(y) + case int: + return int(x) < int(y) + case int8: + return int(x) < int(y) + case int16: + return int(x) < int(y) + case int32: + return int(x) < int(y) + case int64: + return int(x) < int(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case float32: + switch y := b.(type) { + case uint: + return float64(x) < float64(y) + case uint8: + return float64(x) < float64(y) + case uint16: + return float64(x) < float64(y) + case uint32: + return float64(x) < float64(y) + case uint64: + return float64(x) < float64(y) + case int: + return float64(x) < float64(y) + case int8: + return float64(x) < float64(y) + case int16: + return float64(x) < float64(y) + case int32: + return float64(x) < float64(y) + case int64: + return float64(x) < float64(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case float64: + switch y := b.(type) { + case uint: + return float64(x) < float64(y) + case uint8: + return float64(x) < float64(y) + case uint16: + return float64(x) < float64(y) + case uint32: + return float64(x) < float64(y) + case uint64: + return float64(x) < float64(y) + case int: + return float64(x) < float64(y) + case int8: + return float64(x) < float64(y) + case int16: + return float64(x) < float64(y) + case int32: + return float64(x) < float64(y) + case int64: + return float64(x) < float64(y) + case float32: + return float64(x) < float64(y) + case float64: + return float64(x) < float64(y) + } + case string: + switch y := b.(type) { + case string: + return x < y + } + case time.Time: + switch y := b.(type) { + case time.Time: + return x.Before(y) + } + case time.Duration: + switch y := b.(type) { + case time.Duration: + return x < y + } + } + panic(fmt.Sprintf("invalid operation: %T < %T", a, b)) +} + +func More(a, b interface{}) bool { + switch x := a.(type) { + case uint: + switch y := b.(type) { + case uint: + return int(x) > int(y) + case uint8: + return int(x) > int(y) + case uint16: + return int(x) > int(y) + case uint32: + return int(x) > int(y) + case uint64: + return int(x) > int(y) + case int: + return int(x) > int(y) + case int8: + return int(x) > int(y) + case int16: + return int(x) > int(y) + case int32: + return int(x) > int(y) + case int64: + return int(x) > int(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case uint8: + switch y := b.(type) { + case uint: + return int(x) > int(y) + case uint8: + return int(x) > int(y) + case uint16: + return int(x) > int(y) + case uint32: + return int(x) > int(y) + case uint64: + return int(x) > int(y) + case int: + return int(x) > int(y) + case int8: + return int(x) > int(y) + case int16: + return int(x) > int(y) + case int32: + return int(x) > int(y) + case int64: + return int(x) > int(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case uint16: + switch y := b.(type) { + case uint: + return int(x) > int(y) + case uint8: + return int(x) > int(y) + case uint16: + return int(x) > int(y) + case uint32: + return int(x) > int(y) + case uint64: + return int(x) > int(y) + case int: + return int(x) > int(y) + case int8: + return int(x) > int(y) + case int16: + return int(x) > int(y) + case int32: + return int(x) > int(y) + case int64: + return int(x) > int(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case uint32: + switch y := b.(type) { + case uint: + return int(x) > int(y) + case uint8: + return int(x) > int(y) + case uint16: + return int(x) > int(y) + case uint32: + return int(x) > int(y) + case uint64: + return int(x) > int(y) + case int: + return int(x) > int(y) + case int8: + return int(x) > int(y) + case int16: + return int(x) > int(y) + case int32: + return int(x) > int(y) + case int64: + return int(x) > int(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case uint64: + switch y := b.(type) { + case uint: + return int(x) > int(y) + case uint8: + return int(x) > int(y) + case uint16: + return int(x) > int(y) + case uint32: + return int(x) > int(y) + case uint64: + return int(x) > int(y) + case int: + return int(x) > int(y) + case int8: + return int(x) > int(y) + case int16: + return int(x) > int(y) + case int32: + return int(x) > int(y) + case int64: + return int(x) > int(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case int: + switch y := b.(type) { + case uint: + return int(x) > int(y) + case uint8: + return int(x) > int(y) + case uint16: + return int(x) > int(y) + case uint32: + return int(x) > int(y) + case uint64: + return int(x) > int(y) + case int: + return int(x) > int(y) + case int8: + return int(x) > int(y) + case int16: + return int(x) > int(y) + case int32: + return int(x) > int(y) + case int64: + return int(x) > int(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case int8: + switch y := b.(type) { + case uint: + return int(x) > int(y) + case uint8: + return int(x) > int(y) + case uint16: + return int(x) > int(y) + case uint32: + return int(x) > int(y) + case uint64: + return int(x) > int(y) + case int: + return int(x) > int(y) + case int8: + return int(x) > int(y) + case int16: + return int(x) > int(y) + case int32: + return int(x) > int(y) + case int64: + return int(x) > int(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case int16: + switch y := b.(type) { + case uint: + return int(x) > int(y) + case uint8: + return int(x) > int(y) + case uint16: + return int(x) > int(y) + case uint32: + return int(x) > int(y) + case uint64: + return int(x) > int(y) + case int: + return int(x) > int(y) + case int8: + return int(x) > int(y) + case int16: + return int(x) > int(y) + case int32: + return int(x) > int(y) + case int64: + return int(x) > int(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case int32: + switch y := b.(type) { + case uint: + return int(x) > int(y) + case uint8: + return int(x) > int(y) + case uint16: + return int(x) > int(y) + case uint32: + return int(x) > int(y) + case uint64: + return int(x) > int(y) + case int: + return int(x) > int(y) + case int8: + return int(x) > int(y) + case int16: + return int(x) > int(y) + case int32: + return int(x) > int(y) + case int64: + return int(x) > int(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case int64: + switch y := b.(type) { + case uint: + return int(x) > int(y) + case uint8: + return int(x) > int(y) + case uint16: + return int(x) > int(y) + case uint32: + return int(x) > int(y) + case uint64: + return int(x) > int(y) + case int: + return int(x) > int(y) + case int8: + return int(x) > int(y) + case int16: + return int(x) > int(y) + case int32: + return int(x) > int(y) + case int64: + return int(x) > int(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case float32: + switch y := b.(type) { + case uint: + return float64(x) > float64(y) + case uint8: + return float64(x) > float64(y) + case uint16: + return float64(x) > float64(y) + case uint32: + return float64(x) > float64(y) + case uint64: + return float64(x) > float64(y) + case int: + return float64(x) > float64(y) + case int8: + return float64(x) > float64(y) + case int16: + return float64(x) > float64(y) + case int32: + return float64(x) > float64(y) + case int64: + return float64(x) > float64(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case float64: + switch y := b.(type) { + case uint: + return float64(x) > float64(y) + case uint8: + return float64(x) > float64(y) + case uint16: + return float64(x) > float64(y) + case uint32: + return float64(x) > float64(y) + case uint64: + return float64(x) > float64(y) + case int: + return float64(x) > float64(y) + case int8: + return float64(x) > float64(y) + case int16: + return float64(x) > float64(y) + case int32: + return float64(x) > float64(y) + case int64: + return float64(x) > float64(y) + case float32: + return float64(x) > float64(y) + case float64: + return float64(x) > float64(y) + } + case string: + switch y := b.(type) { + case string: + return x > y + } + case time.Time: + switch y := b.(type) { + case time.Time: + return x.After(y) + } + case time.Duration: + switch y := b.(type) { + case time.Duration: + return x > y + } + } + panic(fmt.Sprintf("invalid operation: %T > %T", a, b)) +} + +func LessOrEqual(a, b interface{}) bool { + switch x := a.(type) { + case uint: + switch y := b.(type) { + case uint: + return int(x) <= int(y) + case uint8: + return int(x) <= int(y) + case uint16: + return int(x) <= int(y) + case uint32: + return int(x) <= int(y) + case uint64: + return int(x) <= int(y) + case int: + return int(x) <= int(y) + case int8: + return int(x) <= int(y) + case int16: + return int(x) <= int(y) + case int32: + return int(x) <= int(y) + case int64: + return int(x) <= int(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case uint8: + switch y := b.(type) { + case uint: + return int(x) <= int(y) + case uint8: + return int(x) <= int(y) + case uint16: + return int(x) <= int(y) + case uint32: + return int(x) <= int(y) + case uint64: + return int(x) <= int(y) + case int: + return int(x) <= int(y) + case int8: + return int(x) <= int(y) + case int16: + return int(x) <= int(y) + case int32: + return int(x) <= int(y) + case int64: + return int(x) <= int(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case uint16: + switch y := b.(type) { + case uint: + return int(x) <= int(y) + case uint8: + return int(x) <= int(y) + case uint16: + return int(x) <= int(y) + case uint32: + return int(x) <= int(y) + case uint64: + return int(x) <= int(y) + case int: + return int(x) <= int(y) + case int8: + return int(x) <= int(y) + case int16: + return int(x) <= int(y) + case int32: + return int(x) <= int(y) + case int64: + return int(x) <= int(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case uint32: + switch y := b.(type) { + case uint: + return int(x) <= int(y) + case uint8: + return int(x) <= int(y) + case uint16: + return int(x) <= int(y) + case uint32: + return int(x) <= int(y) + case uint64: + return int(x) <= int(y) + case int: + return int(x) <= int(y) + case int8: + return int(x) <= int(y) + case int16: + return int(x) <= int(y) + case int32: + return int(x) <= int(y) + case int64: + return int(x) <= int(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case uint64: + switch y := b.(type) { + case uint: + return int(x) <= int(y) + case uint8: + return int(x) <= int(y) + case uint16: + return int(x) <= int(y) + case uint32: + return int(x) <= int(y) + case uint64: + return int(x) <= int(y) + case int: + return int(x) <= int(y) + case int8: + return int(x) <= int(y) + case int16: + return int(x) <= int(y) + case int32: + return int(x) <= int(y) + case int64: + return int(x) <= int(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case int: + switch y := b.(type) { + case uint: + return int(x) <= int(y) + case uint8: + return int(x) <= int(y) + case uint16: + return int(x) <= int(y) + case uint32: + return int(x) <= int(y) + case uint64: + return int(x) <= int(y) + case int: + return int(x) <= int(y) + case int8: + return int(x) <= int(y) + case int16: + return int(x) <= int(y) + case int32: + return int(x) <= int(y) + case int64: + return int(x) <= int(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case int8: + switch y := b.(type) { + case uint: + return int(x) <= int(y) + case uint8: + return int(x) <= int(y) + case uint16: + return int(x) <= int(y) + case uint32: + return int(x) <= int(y) + case uint64: + return int(x) <= int(y) + case int: + return int(x) <= int(y) + case int8: + return int(x) <= int(y) + case int16: + return int(x) <= int(y) + case int32: + return int(x) <= int(y) + case int64: + return int(x) <= int(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case int16: + switch y := b.(type) { + case uint: + return int(x) <= int(y) + case uint8: + return int(x) <= int(y) + case uint16: + return int(x) <= int(y) + case uint32: + return int(x) <= int(y) + case uint64: + return int(x) <= int(y) + case int: + return int(x) <= int(y) + case int8: + return int(x) <= int(y) + case int16: + return int(x) <= int(y) + case int32: + return int(x) <= int(y) + case int64: + return int(x) <= int(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case int32: + switch y := b.(type) { + case uint: + return int(x) <= int(y) + case uint8: + return int(x) <= int(y) + case uint16: + return int(x) <= int(y) + case uint32: + return int(x) <= int(y) + case uint64: + return int(x) <= int(y) + case int: + return int(x) <= int(y) + case int8: + return int(x) <= int(y) + case int16: + return int(x) <= int(y) + case int32: + return int(x) <= int(y) + case int64: + return int(x) <= int(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case int64: + switch y := b.(type) { + case uint: + return int(x) <= int(y) + case uint8: + return int(x) <= int(y) + case uint16: + return int(x) <= int(y) + case uint32: + return int(x) <= int(y) + case uint64: + return int(x) <= int(y) + case int: + return int(x) <= int(y) + case int8: + return int(x) <= int(y) + case int16: + return int(x) <= int(y) + case int32: + return int(x) <= int(y) + case int64: + return int(x) <= int(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case float32: + switch y := b.(type) { + case uint: + return float64(x) <= float64(y) + case uint8: + return float64(x) <= float64(y) + case uint16: + return float64(x) <= float64(y) + case uint32: + return float64(x) <= float64(y) + case uint64: + return float64(x) <= float64(y) + case int: + return float64(x) <= float64(y) + case int8: + return float64(x) <= float64(y) + case int16: + return float64(x) <= float64(y) + case int32: + return float64(x) <= float64(y) + case int64: + return float64(x) <= float64(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case float64: + switch y := b.(type) { + case uint: + return float64(x) <= float64(y) + case uint8: + return float64(x) <= float64(y) + case uint16: + return float64(x) <= float64(y) + case uint32: + return float64(x) <= float64(y) + case uint64: + return float64(x) <= float64(y) + case int: + return float64(x) <= float64(y) + case int8: + return float64(x) <= float64(y) + case int16: + return float64(x) <= float64(y) + case int32: + return float64(x) <= float64(y) + case int64: + return float64(x) <= float64(y) + case float32: + return float64(x) <= float64(y) + case float64: + return float64(x) <= float64(y) + } + case string: + switch y := b.(type) { + case string: + return x <= y + } + case time.Time: + switch y := b.(type) { + case time.Time: + return x.Before(y) || x.Equal(y) + } + case time.Duration: + switch y := b.(type) { + case time.Duration: + return x <= y + } + } + panic(fmt.Sprintf("invalid operation: %T <= %T", a, b)) +} + +func MoreOrEqual(a, b interface{}) bool { + switch x := a.(type) { + case uint: + switch y := b.(type) { + case uint: + return int(x) >= int(y) + case uint8: + return int(x) >= int(y) + case uint16: + return int(x) >= int(y) + case uint32: + return int(x) >= int(y) + case uint64: + return int(x) >= int(y) + case int: + return int(x) >= int(y) + case int8: + return int(x) >= int(y) + case int16: + return int(x) >= int(y) + case int32: + return int(x) >= int(y) + case int64: + return int(x) >= int(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case uint8: + switch y := b.(type) { + case uint: + return int(x) >= int(y) + case uint8: + return int(x) >= int(y) + case uint16: + return int(x) >= int(y) + case uint32: + return int(x) >= int(y) + case uint64: + return int(x) >= int(y) + case int: + return int(x) >= int(y) + case int8: + return int(x) >= int(y) + case int16: + return int(x) >= int(y) + case int32: + return int(x) >= int(y) + case int64: + return int(x) >= int(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case uint16: + switch y := b.(type) { + case uint: + return int(x) >= int(y) + case uint8: + return int(x) >= int(y) + case uint16: + return int(x) >= int(y) + case uint32: + return int(x) >= int(y) + case uint64: + return int(x) >= int(y) + case int: + return int(x) >= int(y) + case int8: + return int(x) >= int(y) + case int16: + return int(x) >= int(y) + case int32: + return int(x) >= int(y) + case int64: + return int(x) >= int(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case uint32: + switch y := b.(type) { + case uint: + return int(x) >= int(y) + case uint8: + return int(x) >= int(y) + case uint16: + return int(x) >= int(y) + case uint32: + return int(x) >= int(y) + case uint64: + return int(x) >= int(y) + case int: + return int(x) >= int(y) + case int8: + return int(x) >= int(y) + case int16: + return int(x) >= int(y) + case int32: + return int(x) >= int(y) + case int64: + return int(x) >= int(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case uint64: + switch y := b.(type) { + case uint: + return int(x) >= int(y) + case uint8: + return int(x) >= int(y) + case uint16: + return int(x) >= int(y) + case uint32: + return int(x) >= int(y) + case uint64: + return int(x) >= int(y) + case int: + return int(x) >= int(y) + case int8: + return int(x) >= int(y) + case int16: + return int(x) >= int(y) + case int32: + return int(x) >= int(y) + case int64: + return int(x) >= int(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case int: + switch y := b.(type) { + case uint: + return int(x) >= int(y) + case uint8: + return int(x) >= int(y) + case uint16: + return int(x) >= int(y) + case uint32: + return int(x) >= int(y) + case uint64: + return int(x) >= int(y) + case int: + return int(x) >= int(y) + case int8: + return int(x) >= int(y) + case int16: + return int(x) >= int(y) + case int32: + return int(x) >= int(y) + case int64: + return int(x) >= int(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case int8: + switch y := b.(type) { + case uint: + return int(x) >= int(y) + case uint8: + return int(x) >= int(y) + case uint16: + return int(x) >= int(y) + case uint32: + return int(x) >= int(y) + case uint64: + return int(x) >= int(y) + case int: + return int(x) >= int(y) + case int8: + return int(x) >= int(y) + case int16: + return int(x) >= int(y) + case int32: + return int(x) >= int(y) + case int64: + return int(x) >= int(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case int16: + switch y := b.(type) { + case uint: + return int(x) >= int(y) + case uint8: + return int(x) >= int(y) + case uint16: + return int(x) >= int(y) + case uint32: + return int(x) >= int(y) + case uint64: + return int(x) >= int(y) + case int: + return int(x) >= int(y) + case int8: + return int(x) >= int(y) + case int16: + return int(x) >= int(y) + case int32: + return int(x) >= int(y) + case int64: + return int(x) >= int(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case int32: + switch y := b.(type) { + case uint: + return int(x) >= int(y) + case uint8: + return int(x) >= int(y) + case uint16: + return int(x) >= int(y) + case uint32: + return int(x) >= int(y) + case uint64: + return int(x) >= int(y) + case int: + return int(x) >= int(y) + case int8: + return int(x) >= int(y) + case int16: + return int(x) >= int(y) + case int32: + return int(x) >= int(y) + case int64: + return int(x) >= int(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case int64: + switch y := b.(type) { + case uint: + return int(x) >= int(y) + case uint8: + return int(x) >= int(y) + case uint16: + return int(x) >= int(y) + case uint32: + return int(x) >= int(y) + case uint64: + return int(x) >= int(y) + case int: + return int(x) >= int(y) + case int8: + return int(x) >= int(y) + case int16: + return int(x) >= int(y) + case int32: + return int(x) >= int(y) + case int64: + return int(x) >= int(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case float32: + switch y := b.(type) { + case uint: + return float64(x) >= float64(y) + case uint8: + return float64(x) >= float64(y) + case uint16: + return float64(x) >= float64(y) + case uint32: + return float64(x) >= float64(y) + case uint64: + return float64(x) >= float64(y) + case int: + return float64(x) >= float64(y) + case int8: + return float64(x) >= float64(y) + case int16: + return float64(x) >= float64(y) + case int32: + return float64(x) >= float64(y) + case int64: + return float64(x) >= float64(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case float64: + switch y := b.(type) { + case uint: + return float64(x) >= float64(y) + case uint8: + return float64(x) >= float64(y) + case uint16: + return float64(x) >= float64(y) + case uint32: + return float64(x) >= float64(y) + case uint64: + return float64(x) >= float64(y) + case int: + return float64(x) >= float64(y) + case int8: + return float64(x) >= float64(y) + case int16: + return float64(x) >= float64(y) + case int32: + return float64(x) >= float64(y) + case int64: + return float64(x) >= float64(y) + case float32: + return float64(x) >= float64(y) + case float64: + return float64(x) >= float64(y) + } + case string: + switch y := b.(type) { + case string: + return x >= y + } + case time.Time: + switch y := b.(type) { + case time.Time: + return x.After(y) || x.Equal(y) + } + case time.Duration: + switch y := b.(type) { + case time.Duration: + return x >= y + } + } + panic(fmt.Sprintf("invalid operation: %T >= %T", a, b)) +} + +func Add(a, b interface{}) interface{} { + switch x := a.(type) { + case uint: + switch y := b.(type) { + case uint: + return int(x) + int(y) + case uint8: + return int(x) + int(y) + case uint16: + return int(x) + int(y) + case uint32: + return int(x) + int(y) + case uint64: + return int(x) + int(y) + case int: + return int(x) + int(y) + case int8: + return int(x) + int(y) + case int16: + return int(x) + int(y) + case int32: + return int(x) + int(y) + case int64: + return int(x) + int(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case uint8: + switch y := b.(type) { + case uint: + return int(x) + int(y) + case uint8: + return int(x) + int(y) + case uint16: + return int(x) + int(y) + case uint32: + return int(x) + int(y) + case uint64: + return int(x) + int(y) + case int: + return int(x) + int(y) + case int8: + return int(x) + int(y) + case int16: + return int(x) + int(y) + case int32: + return int(x) + int(y) + case int64: + return int(x) + int(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case uint16: + switch y := b.(type) { + case uint: + return int(x) + int(y) + case uint8: + return int(x) + int(y) + case uint16: + return int(x) + int(y) + case uint32: + return int(x) + int(y) + case uint64: + return int(x) + int(y) + case int: + return int(x) + int(y) + case int8: + return int(x) + int(y) + case int16: + return int(x) + int(y) + case int32: + return int(x) + int(y) + case int64: + return int(x) + int(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case uint32: + switch y := b.(type) { + case uint: + return int(x) + int(y) + case uint8: + return int(x) + int(y) + case uint16: + return int(x) + int(y) + case uint32: + return int(x) + int(y) + case uint64: + return int(x) + int(y) + case int: + return int(x) + int(y) + case int8: + return int(x) + int(y) + case int16: + return int(x) + int(y) + case int32: + return int(x) + int(y) + case int64: + return int(x) + int(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case uint64: + switch y := b.(type) { + case uint: + return int(x) + int(y) + case uint8: + return int(x) + int(y) + case uint16: + return int(x) + int(y) + case uint32: + return int(x) + int(y) + case uint64: + return int(x) + int(y) + case int: + return int(x) + int(y) + case int8: + return int(x) + int(y) + case int16: + return int(x) + int(y) + case int32: + return int(x) + int(y) + case int64: + return int(x) + int(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case int: + switch y := b.(type) { + case uint: + return int(x) + int(y) + case uint8: + return int(x) + int(y) + case uint16: + return int(x) + int(y) + case uint32: + return int(x) + int(y) + case uint64: + return int(x) + int(y) + case int: + return int(x) + int(y) + case int8: + return int(x) + int(y) + case int16: + return int(x) + int(y) + case int32: + return int(x) + int(y) + case int64: + return int(x) + int(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case int8: + switch y := b.(type) { + case uint: + return int(x) + int(y) + case uint8: + return int(x) + int(y) + case uint16: + return int(x) + int(y) + case uint32: + return int(x) + int(y) + case uint64: + return int(x) + int(y) + case int: + return int(x) + int(y) + case int8: + return int(x) + int(y) + case int16: + return int(x) + int(y) + case int32: + return int(x) + int(y) + case int64: + return int(x) + int(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case int16: + switch y := b.(type) { + case uint: + return int(x) + int(y) + case uint8: + return int(x) + int(y) + case uint16: + return int(x) + int(y) + case uint32: + return int(x) + int(y) + case uint64: + return int(x) + int(y) + case int: + return int(x) + int(y) + case int8: + return int(x) + int(y) + case int16: + return int(x) + int(y) + case int32: + return int(x) + int(y) + case int64: + return int(x) + int(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case int32: + switch y := b.(type) { + case uint: + return int(x) + int(y) + case uint8: + return int(x) + int(y) + case uint16: + return int(x) + int(y) + case uint32: + return int(x) + int(y) + case uint64: + return int(x) + int(y) + case int: + return int(x) + int(y) + case int8: + return int(x) + int(y) + case int16: + return int(x) + int(y) + case int32: + return int(x) + int(y) + case int64: + return int(x) + int(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case int64: + switch y := b.(type) { + case uint: + return int(x) + int(y) + case uint8: + return int(x) + int(y) + case uint16: + return int(x) + int(y) + case uint32: + return int(x) + int(y) + case uint64: + return int(x) + int(y) + case int: + return int(x) + int(y) + case int8: + return int(x) + int(y) + case int16: + return int(x) + int(y) + case int32: + return int(x) + int(y) + case int64: + return int(x) + int(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case float32: + switch y := b.(type) { + case uint: + return float64(x) + float64(y) + case uint8: + return float64(x) + float64(y) + case uint16: + return float64(x) + float64(y) + case uint32: + return float64(x) + float64(y) + case uint64: + return float64(x) + float64(y) + case int: + return float64(x) + float64(y) + case int8: + return float64(x) + float64(y) + case int16: + return float64(x) + float64(y) + case int32: + return float64(x) + float64(y) + case int64: + return float64(x) + float64(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case float64: + switch y := b.(type) { + case uint: + return float64(x) + float64(y) + case uint8: + return float64(x) + float64(y) + case uint16: + return float64(x) + float64(y) + case uint32: + return float64(x) + float64(y) + case uint64: + return float64(x) + float64(y) + case int: + return float64(x) + float64(y) + case int8: + return float64(x) + float64(y) + case int16: + return float64(x) + float64(y) + case int32: + return float64(x) + float64(y) + case int64: + return float64(x) + float64(y) + case float32: + return float64(x) + float64(y) + case float64: + return float64(x) + float64(y) + } + case string: + switch y := b.(type) { + case string: + return x + y + } + case time.Time: + switch y := b.(type) { + case time.Duration: + return x.Add(y) + } + case time.Duration: + switch y := b.(type) { + case time.Time: + return y.Add(x) + case time.Duration: + return x + y + } + } + panic(fmt.Sprintf("invalid operation: %T + %T", a, b)) +} + +func Subtract(a, b interface{}) interface{} { + switch x := a.(type) { + case uint: + switch y := b.(type) { + case uint: + return int(x) - int(y) + case uint8: + return int(x) - int(y) + case uint16: + return int(x) - int(y) + case uint32: + return int(x) - int(y) + case uint64: + return int(x) - int(y) + case int: + return int(x) - int(y) + case int8: + return int(x) - int(y) + case int16: + return int(x) - int(y) + case int32: + return int(x) - int(y) + case int64: + return int(x) - int(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case uint8: + switch y := b.(type) { + case uint: + return int(x) - int(y) + case uint8: + return int(x) - int(y) + case uint16: + return int(x) - int(y) + case uint32: + return int(x) - int(y) + case uint64: + return int(x) - int(y) + case int: + return int(x) - int(y) + case int8: + return int(x) - int(y) + case int16: + return int(x) - int(y) + case int32: + return int(x) - int(y) + case int64: + return int(x) - int(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case uint16: + switch y := b.(type) { + case uint: + return int(x) - int(y) + case uint8: + return int(x) - int(y) + case uint16: + return int(x) - int(y) + case uint32: + return int(x) - int(y) + case uint64: + return int(x) - int(y) + case int: + return int(x) - int(y) + case int8: + return int(x) - int(y) + case int16: + return int(x) - int(y) + case int32: + return int(x) - int(y) + case int64: + return int(x) - int(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case uint32: + switch y := b.(type) { + case uint: + return int(x) - int(y) + case uint8: + return int(x) - int(y) + case uint16: + return int(x) - int(y) + case uint32: + return int(x) - int(y) + case uint64: + return int(x) - int(y) + case int: + return int(x) - int(y) + case int8: + return int(x) - int(y) + case int16: + return int(x) - int(y) + case int32: + return int(x) - int(y) + case int64: + return int(x) - int(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case uint64: + switch y := b.(type) { + case uint: + return int(x) - int(y) + case uint8: + return int(x) - int(y) + case uint16: + return int(x) - int(y) + case uint32: + return int(x) - int(y) + case uint64: + return int(x) - int(y) + case int: + return int(x) - int(y) + case int8: + return int(x) - int(y) + case int16: + return int(x) - int(y) + case int32: + return int(x) - int(y) + case int64: + return int(x) - int(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case int: + switch y := b.(type) { + case uint: + return int(x) - int(y) + case uint8: + return int(x) - int(y) + case uint16: + return int(x) - int(y) + case uint32: + return int(x) - int(y) + case uint64: + return int(x) - int(y) + case int: + return int(x) - int(y) + case int8: + return int(x) - int(y) + case int16: + return int(x) - int(y) + case int32: + return int(x) - int(y) + case int64: + return int(x) - int(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case int8: + switch y := b.(type) { + case uint: + return int(x) - int(y) + case uint8: + return int(x) - int(y) + case uint16: + return int(x) - int(y) + case uint32: + return int(x) - int(y) + case uint64: + return int(x) - int(y) + case int: + return int(x) - int(y) + case int8: + return int(x) - int(y) + case int16: + return int(x) - int(y) + case int32: + return int(x) - int(y) + case int64: + return int(x) - int(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case int16: + switch y := b.(type) { + case uint: + return int(x) - int(y) + case uint8: + return int(x) - int(y) + case uint16: + return int(x) - int(y) + case uint32: + return int(x) - int(y) + case uint64: + return int(x) - int(y) + case int: + return int(x) - int(y) + case int8: + return int(x) - int(y) + case int16: + return int(x) - int(y) + case int32: + return int(x) - int(y) + case int64: + return int(x) - int(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case int32: + switch y := b.(type) { + case uint: + return int(x) - int(y) + case uint8: + return int(x) - int(y) + case uint16: + return int(x) - int(y) + case uint32: + return int(x) - int(y) + case uint64: + return int(x) - int(y) + case int: + return int(x) - int(y) + case int8: + return int(x) - int(y) + case int16: + return int(x) - int(y) + case int32: + return int(x) - int(y) + case int64: + return int(x) - int(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case int64: + switch y := b.(type) { + case uint: + return int(x) - int(y) + case uint8: + return int(x) - int(y) + case uint16: + return int(x) - int(y) + case uint32: + return int(x) - int(y) + case uint64: + return int(x) - int(y) + case int: + return int(x) - int(y) + case int8: + return int(x) - int(y) + case int16: + return int(x) - int(y) + case int32: + return int(x) - int(y) + case int64: + return int(x) - int(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case float32: + switch y := b.(type) { + case uint: + return float64(x) - float64(y) + case uint8: + return float64(x) - float64(y) + case uint16: + return float64(x) - float64(y) + case uint32: + return float64(x) - float64(y) + case uint64: + return float64(x) - float64(y) + case int: + return float64(x) - float64(y) + case int8: + return float64(x) - float64(y) + case int16: + return float64(x) - float64(y) + case int32: + return float64(x) - float64(y) + case int64: + return float64(x) - float64(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case float64: + switch y := b.(type) { + case uint: + return float64(x) - float64(y) + case uint8: + return float64(x) - float64(y) + case uint16: + return float64(x) - float64(y) + case uint32: + return float64(x) - float64(y) + case uint64: + return float64(x) - float64(y) + case int: + return float64(x) - float64(y) + case int8: + return float64(x) - float64(y) + case int16: + return float64(x) - float64(y) + case int32: + return float64(x) - float64(y) + case int64: + return float64(x) - float64(y) + case float32: + return float64(x) - float64(y) + case float64: + return float64(x) - float64(y) + } + case time.Time: + switch y := b.(type) { + case time.Time: + return x.Sub(y) + case time.Duration: + return x.Add(-y) + } + case time.Duration: + switch y := b.(type) { + case time.Duration: + return x - y + } + } + panic(fmt.Sprintf("invalid operation: %T - %T", a, b)) +} + +func Multiply(a, b interface{}) interface{} { + switch x := a.(type) { + case uint: + switch y := b.(type) { + case uint: + return int(x) * int(y) + case uint8: + return int(x) * int(y) + case uint16: + return int(x) * int(y) + case uint32: + return int(x) * int(y) + case uint64: + return int(x) * int(y) + case int: + return int(x) * int(y) + case int8: + return int(x) * int(y) + case int16: + return int(x) * int(y) + case int32: + return int(x) * int(y) + case int64: + return int(x) * int(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + case uint8: + switch y := b.(type) { + case uint: + return int(x) * int(y) + case uint8: + return int(x) * int(y) + case uint16: + return int(x) * int(y) + case uint32: + return int(x) * int(y) + case uint64: + return int(x) * int(y) + case int: + return int(x) * int(y) + case int8: + return int(x) * int(y) + case int16: + return int(x) * int(y) + case int32: + return int(x) * int(y) + case int64: + return int(x) * int(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + case uint16: + switch y := b.(type) { + case uint: + return int(x) * int(y) + case uint8: + return int(x) * int(y) + case uint16: + return int(x) * int(y) + case uint32: + return int(x) * int(y) + case uint64: + return int(x) * int(y) + case int: + return int(x) * int(y) + case int8: + return int(x) * int(y) + case int16: + return int(x) * int(y) + case int32: + return int(x) * int(y) + case int64: + return int(x) * int(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + case uint32: + switch y := b.(type) { + case uint: + return int(x) * int(y) + case uint8: + return int(x) * int(y) + case uint16: + return int(x) * int(y) + case uint32: + return int(x) * int(y) + case uint64: + return int(x) * int(y) + case int: + return int(x) * int(y) + case int8: + return int(x) * int(y) + case int16: + return int(x) * int(y) + case int32: + return int(x) * int(y) + case int64: + return int(x) * int(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + case uint64: + switch y := b.(type) { + case uint: + return int(x) * int(y) + case uint8: + return int(x) * int(y) + case uint16: + return int(x) * int(y) + case uint32: + return int(x) * int(y) + case uint64: + return int(x) * int(y) + case int: + return int(x) * int(y) + case int8: + return int(x) * int(y) + case int16: + return int(x) * int(y) + case int32: + return int(x) * int(y) + case int64: + return int(x) * int(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + case int: + switch y := b.(type) { + case uint: + return int(x) * int(y) + case uint8: + return int(x) * int(y) + case uint16: + return int(x) * int(y) + case uint32: + return int(x) * int(y) + case uint64: + return int(x) * int(y) + case int: + return int(x) * int(y) + case int8: + return int(x) * int(y) + case int16: + return int(x) * int(y) + case int32: + return int(x) * int(y) + case int64: + return int(x) * int(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + case int8: + switch y := b.(type) { + case uint: + return int(x) * int(y) + case uint8: + return int(x) * int(y) + case uint16: + return int(x) * int(y) + case uint32: + return int(x) * int(y) + case uint64: + return int(x) * int(y) + case int: + return int(x) * int(y) + case int8: + return int(x) * int(y) + case int16: + return int(x) * int(y) + case int32: + return int(x) * int(y) + case int64: + return int(x) * int(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + case int16: + switch y := b.(type) { + case uint: + return int(x) * int(y) + case uint8: + return int(x) * int(y) + case uint16: + return int(x) * int(y) + case uint32: + return int(x) * int(y) + case uint64: + return int(x) * int(y) + case int: + return int(x) * int(y) + case int8: + return int(x) * int(y) + case int16: + return int(x) * int(y) + case int32: + return int(x) * int(y) + case int64: + return int(x) * int(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + case int32: + switch y := b.(type) { + case uint: + return int(x) * int(y) + case uint8: + return int(x) * int(y) + case uint16: + return int(x) * int(y) + case uint32: + return int(x) * int(y) + case uint64: + return int(x) * int(y) + case int: + return int(x) * int(y) + case int8: + return int(x) * int(y) + case int16: + return int(x) * int(y) + case int32: + return int(x) * int(y) + case int64: + return int(x) * int(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + case int64: + switch y := b.(type) { + case uint: + return int(x) * int(y) + case uint8: + return int(x) * int(y) + case uint16: + return int(x) * int(y) + case uint32: + return int(x) * int(y) + case uint64: + return int(x) * int(y) + case int: + return int(x) * int(y) + case int8: + return int(x) * int(y) + case int16: + return int(x) * int(y) + case int32: + return int(x) * int(y) + case int64: + return int(x) * int(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + case float32: + switch y := b.(type) { + case uint: + return float64(x) * float64(y) + case uint8: + return float64(x) * float64(y) + case uint16: + return float64(x) * float64(y) + case uint32: + return float64(x) * float64(y) + case uint64: + return float64(x) * float64(y) + case int: + return float64(x) * float64(y) + case int8: + return float64(x) * float64(y) + case int16: + return float64(x) * float64(y) + case int32: + return float64(x) * float64(y) + case int64: + return float64(x) * float64(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return float64(x) * float64(y) + } + case float64: + switch y := b.(type) { + case uint: + return float64(x) * float64(y) + case uint8: + return float64(x) * float64(y) + case uint16: + return float64(x) * float64(y) + case uint32: + return float64(x) * float64(y) + case uint64: + return float64(x) * float64(y) + case int: + return float64(x) * float64(y) + case int8: + return float64(x) * float64(y) + case int16: + return float64(x) * float64(y) + case int32: + return float64(x) * float64(y) + case int64: + return float64(x) * float64(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return float64(x) * float64(y) + } + case time.Duration: + switch y := b.(type) { + case uint: + return time.Duration(x) * time.Duration(y) + case uint8: + return time.Duration(x) * time.Duration(y) + case uint16: + return time.Duration(x) * time.Duration(y) + case uint32: + return time.Duration(x) * time.Duration(y) + case uint64: + return time.Duration(x) * time.Duration(y) + case int: + return time.Duration(x) * time.Duration(y) + case int8: + return time.Duration(x) * time.Duration(y) + case int16: + return time.Duration(x) * time.Duration(y) + case int32: + return time.Duration(x) * time.Duration(y) + case int64: + return time.Duration(x) * time.Duration(y) + case float32: + return float64(x) * float64(y) + case float64: + return float64(x) * float64(y) + case time.Duration: + return time.Duration(x) * time.Duration(y) + } + } + panic(fmt.Sprintf("invalid operation: %T * %T", a, b)) +} + +func Divide(a, b interface{}) float64 { + switch x := a.(type) { + case uint: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case uint8: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case uint16: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case uint32: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case uint64: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case int: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case int8: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case int16: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case int32: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case int64: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case float32: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + case float64: + switch y := b.(type) { + case uint: + return float64(x) / float64(y) + case uint8: + return float64(x) / float64(y) + case uint16: + return float64(x) / float64(y) + case uint32: + return float64(x) / float64(y) + case uint64: + return float64(x) / float64(y) + case int: + return float64(x) / float64(y) + case int8: + return float64(x) / float64(y) + case int16: + return float64(x) / float64(y) + case int32: + return float64(x) / float64(y) + case int64: + return float64(x) / float64(y) + case float32: + return float64(x) / float64(y) + case float64: + return float64(x) / float64(y) + } + } + panic(fmt.Sprintf("invalid operation: %T / %T", a, b)) +} + +func Modulo(a, b interface{}) int { + switch x := a.(type) { + case uint: + switch y := b.(type) { + case uint: + return int(x) % int(y) + case uint8: + return int(x) % int(y) + case uint16: + return int(x) % int(y) + case uint32: + return int(x) % int(y) + case uint64: + return int(x) % int(y) + case int: + return int(x) % int(y) + case int8: + return int(x) % int(y) + case int16: + return int(x) % int(y) + case int32: + return int(x) % int(y) + case int64: + return int(x) % int(y) + } + case uint8: + switch y := b.(type) { + case uint: + return int(x) % int(y) + case uint8: + return int(x) % int(y) + case uint16: + return int(x) % int(y) + case uint32: + return int(x) % int(y) + case uint64: + return int(x) % int(y) + case int: + return int(x) % int(y) + case int8: + return int(x) % int(y) + case int16: + return int(x) % int(y) + case int32: + return int(x) % int(y) + case int64: + return int(x) % int(y) + } + case uint16: + switch y := b.(type) { + case uint: + return int(x) % int(y) + case uint8: + return int(x) % int(y) + case uint16: + return int(x) % int(y) + case uint32: + return int(x) % int(y) + case uint64: + return int(x) % int(y) + case int: + return int(x) % int(y) + case int8: + return int(x) % int(y) + case int16: + return int(x) % int(y) + case int32: + return int(x) % int(y) + case int64: + return int(x) % int(y) + } + case uint32: + switch y := b.(type) { + case uint: + return int(x) % int(y) + case uint8: + return int(x) % int(y) + case uint16: + return int(x) % int(y) + case uint32: + return int(x) % int(y) + case uint64: + return int(x) % int(y) + case int: + return int(x) % int(y) + case int8: + return int(x) % int(y) + case int16: + return int(x) % int(y) + case int32: + return int(x) % int(y) + case int64: + return int(x) % int(y) + } + case uint64: + switch y := b.(type) { + case uint: + return int(x) % int(y) + case uint8: + return int(x) % int(y) + case uint16: + return int(x) % int(y) + case uint32: + return int(x) % int(y) + case uint64: + return int(x) % int(y) + case int: + return int(x) % int(y) + case int8: + return int(x) % int(y) + case int16: + return int(x) % int(y) + case int32: + return int(x) % int(y) + case int64: + return int(x) % int(y) + } + case int: + switch y := b.(type) { + case uint: + return int(x) % int(y) + case uint8: + return int(x) % int(y) + case uint16: + return int(x) % int(y) + case uint32: + return int(x) % int(y) + case uint64: + return int(x) % int(y) + case int: + return int(x) % int(y) + case int8: + return int(x) % int(y) + case int16: + return int(x) % int(y) + case int32: + return int(x) % int(y) + case int64: + return int(x) % int(y) + } + case int8: + switch y := b.(type) { + case uint: + return int(x) % int(y) + case uint8: + return int(x) % int(y) + case uint16: + return int(x) % int(y) + case uint32: + return int(x) % int(y) + case uint64: + return int(x) % int(y) + case int: + return int(x) % int(y) + case int8: + return int(x) % int(y) + case int16: + return int(x) % int(y) + case int32: + return int(x) % int(y) + case int64: + return int(x) % int(y) + } + case int16: + switch y := b.(type) { + case uint: + return int(x) % int(y) + case uint8: + return int(x) % int(y) + case uint16: + return int(x) % int(y) + case uint32: + return int(x) % int(y) + case uint64: + return int(x) % int(y) + case int: + return int(x) % int(y) + case int8: + return int(x) % int(y) + case int16: + return int(x) % int(y) + case int32: + return int(x) % int(y) + case int64: + return int(x) % int(y) + } + case int32: + switch y := b.(type) { + case uint: + return int(x) % int(y) + case uint8: + return int(x) % int(y) + case uint16: + return int(x) % int(y) + case uint32: + return int(x) % int(y) + case uint64: + return int(x) % int(y) + case int: + return int(x) % int(y) + case int8: + return int(x) % int(y) + case int16: + return int(x) % int(y) + case int32: + return int(x) % int(y) + case int64: + return int(x) % int(y) + } + case int64: + switch y := b.(type) { + case uint: + return int(x) % int(y) + case uint8: + return int(x) % int(y) + case uint16: + return int(x) % int(y) + case uint32: + return int(x) % int(y) + case uint64: + return int(x) % int(y) + case int: + return int(x) % int(y) + case int8: + return int(x) % int(y) + case int16: + return int(x) % int(y) + case int32: + return int(x) % int(y) + case int64: + return int(x) % int(y) + } + } + panic(fmt.Sprintf("invalid operation: %T %% %T", a, b)) +} diff --git a/vendor/github.com/expr-lang/expr/vm/runtime/runtime.go b/vendor/github.com/expr-lang/expr/vm/runtime/runtime.go new file mode 100644 index 00000000000..d24c6af030d --- /dev/null +++ b/vendor/github.com/expr-lang/expr/vm/runtime/runtime.go @@ -0,0 +1,439 @@ +package runtime + +//go:generate sh -c "go run ./helpers > ./helpers[generated].go" + +import ( + "fmt" + "math" + "reflect" + "sync" + + "github.com/expr-lang/expr/internal/deref" +) + +var fieldCache sync.Map + +type fieldCacheKey struct { + t reflect.Type + f string +} + +func Fetch(from, i any) any { + v := reflect.ValueOf(from) + if v.Kind() == reflect.Invalid { + panic(fmt.Sprintf("cannot fetch %v from %T", i, from)) + } + + // Methods can be defined on any type. + if v.NumMethod() > 0 { + if methodName, ok := i.(string); ok { + method := v.MethodByName(methodName) + if method.IsValid() { + return method.Interface() + } + } + } + + // Structs, maps, and slices can be access through a pointer or through + // a value, when they are accessed through a pointer we don't want to + // copy them to a value. + // De-reference everything if necessary (interface and pointers) + v = deref.Value(v) + + switch v.Kind() { + case reflect.Array, reflect.Slice, reflect.String: + index := ToInt(i) + l := v.Len() + if index < 0 { + index = l + index + } + if index < 0 || index >= l { + panic(fmt.Sprintf("index out of range: %v (array length is %v)", index, l)) + } + value := v.Index(index) + if value.IsValid() { + return value.Interface() + } + + case reflect.Map: + var value reflect.Value + if i == nil { + value = v.MapIndex(reflect.Zero(v.Type().Key())) + } else { + value = v.MapIndex(reflect.ValueOf(i)) + } + if value.IsValid() { + return value.Interface() + } else { + elem := reflect.TypeOf(from).Elem() + return reflect.Zero(elem).Interface() + } + + case reflect.Struct: + fieldName := i.(string) + t := v.Type() + key := fieldCacheKey{ + t: t, + f: fieldName, + } + if cv, ok := fieldCache.Load(key); ok { + return v.FieldByIndex(cv.([]int)).Interface() + } + field, ok := t.FieldByNameFunc(func(name string) bool { + field, _ := t.FieldByName(name) + switch field.Tag.Get("expr") { + case "-": + return false + case fieldName: + return true + default: + return name == fieldName + } + }) + if ok { + value := v.FieldByIndex(field.Index) + if value.IsValid() { + fieldCache.Store(key, field.Index) + return value.Interface() + } + } + } + panic(fmt.Sprintf("cannot fetch %v from %T", i, from)) +} + +type Field struct { + Index []int + Path []string +} + +func FetchField(from any, field *Field) any { + v := reflect.ValueOf(from) + if v.Kind() != reflect.Invalid { + v = reflect.Indirect(v) + + // We can use v.FieldByIndex here, but it will panic if the field + // is not exists. And we need to recover() to generate a more + // user-friendly error message. + // Also, our fieldByIndex() function is slightly faster than the + // v.FieldByIndex() function as we don't need to verify what a field + // is a struct as we already did it on compilation step. + value := fieldByIndex(v, field) + if value.IsValid() { + return value.Interface() + } + } + panic(fmt.Sprintf("cannot get %v from %T", field.Path[0], from)) +} + +func fieldByIndex(v reflect.Value, field *Field) reflect.Value { + if len(field.Index) == 1 { + return v.Field(field.Index[0]) + } + for i, x := range field.Index { + if i > 0 { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + panic(fmt.Sprintf("cannot get %v from %v", field.Path[i], field.Path[i-1])) + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} + +type Method struct { + Index int + Name string +} + +func FetchMethod(from any, method *Method) any { + v := reflect.ValueOf(from) + kind := v.Kind() + if kind != reflect.Invalid { + // Methods can be defined on any type, no need to dereference. + method := v.Method(method.Index) + if method.IsValid() { + return method.Interface() + } + } + panic(fmt.Sprintf("cannot fetch %v from %T", method.Name, from)) +} + +func Slice(array, from, to any) any { + v := reflect.ValueOf(array) + + switch v.Kind() { + case reflect.Array, reflect.Slice, reflect.String: + length := v.Len() + a, b := ToInt(from), ToInt(to) + if a < 0 { + a = length + a + } + if a < 0 { + a = 0 + } + if b < 0 { + b = length + b + } + if b < 0 { + b = 0 + } + if b > length { + b = length + } + if a > b { + a = b + } + if v.Kind() == reflect.Array && !v.CanAddr() { + newValue := reflect.New(v.Type()).Elem() + newValue.Set(v) + v = newValue + } + value := v.Slice(a, b) + if value.IsValid() { + return value.Interface() + } + + case reflect.Ptr: + value := v.Elem() + if value.IsValid() { + return Slice(value.Interface(), from, to) + } + + } + panic(fmt.Sprintf("cannot slice %v", from)) +} + +func In(needle any, array any) bool { + if array == nil { + return false + } + v := reflect.ValueOf(array) + + switch v.Kind() { + + case reflect.Array, reflect.Slice: + for i := 0; i < v.Len(); i++ { + value := v.Index(i) + if value.IsValid() { + if Equal(value.Interface(), needle) { + return true + } + } + } + return false + + case reflect.Map: + var value reflect.Value + if needle == nil { + value = v.MapIndex(reflect.Zero(v.Type().Key())) + } else { + value = v.MapIndex(reflect.ValueOf(needle)) + } + if value.IsValid() { + return true + } + return false + + case reflect.Struct: + n := reflect.ValueOf(needle) + if !n.IsValid() || n.Kind() != reflect.String { + panic(fmt.Sprintf("cannot use %T as field name of %T", needle, array)) + } + field, ok := v.Type().FieldByName(n.String()) + if !ok || !field.IsExported() || field.Tag.Get("expr") == "-" { + return false + } + value := v.FieldByIndex(field.Index) + if value.IsValid() { + return true + } + return false + + case reflect.Ptr: + value := v.Elem() + if value.IsValid() { + return In(needle, value.Interface()) + } + return false + } + + panic(fmt.Sprintf(`operator "in" not defined on %T`, array)) +} + +func Len(a any) int { + v := reflect.ValueOf(a) + switch v.Kind() { + case reflect.Array, reflect.Slice, reflect.Map, reflect.String: + return v.Len() + default: + panic(fmt.Sprintf("invalid argument for len (type %T)", a)) + } +} + +func Negate(i any) any { + switch v := i.(type) { + case float32: + return -v + case float64: + return -v + case int: + return -v + case int8: + return -v + case int16: + return -v + case int32: + return -v + case int64: + return -v + case uint: + return -v + case uint8: + return -v + case uint16: + return -v + case uint32: + return -v + case uint64: + return -v + default: + panic(fmt.Sprintf("invalid operation: - %T", v)) + } +} + +func Exponent(a, b any) float64 { + return math.Pow(ToFloat64(a), ToFloat64(b)) +} + +func MakeRange(min, max int) []int { + size := max - min + 1 + if size <= 0 { + return []int{} + } + rng := make([]int, size) + for i := range rng { + rng[i] = min + i + } + return rng +} + +func ToInt(a any) int { + switch x := a.(type) { + case float32: + return int(x) + case float64: + return int(x) + case int: + return x + case int8: + return int(x) + case int16: + return int(x) + case int32: + return int(x) + case int64: + return int(x) + case uint: + return int(x) + case uint8: + return int(x) + case uint16: + return int(x) + case uint32: + return int(x) + case uint64: + return int(x) + default: + panic(fmt.Sprintf("invalid operation: int(%T)", x)) + } +} + +func ToInt64(a any) int64 { + switch x := a.(type) { + case float32: + return int64(x) + case float64: + return int64(x) + case int: + return int64(x) + case int8: + return int64(x) + case int16: + return int64(x) + case int32: + return int64(x) + case int64: + return x + case uint: + return int64(x) + case uint8: + return int64(x) + case uint16: + return int64(x) + case uint32: + return int64(x) + case uint64: + return int64(x) + default: + panic(fmt.Sprintf("invalid operation: int64(%T)", x)) + } +} + +func ToFloat64(a any) float64 { + switch x := a.(type) { + case float32: + return float64(x) + case float64: + return x + case int: + return float64(x) + case int8: + return float64(x) + case int16: + return float64(x) + case int32: + return float64(x) + case int64: + return float64(x) + case uint: + return float64(x) + case uint8: + return float64(x) + case uint16: + return float64(x) + case uint32: + return float64(x) + case uint64: + return float64(x) + default: + panic(fmt.Sprintf("invalid operation: float(%T)", x)) + } +} + +func ToBool(a any) bool { + if a == nil { + return false + } + switch x := a.(type) { + case bool: + return x + default: + panic(fmt.Sprintf("invalid operation: bool(%T)", x)) + } +} + +func IsNil(v any) bool { + if v == nil { + return true + } + r := reflect.ValueOf(v) + switch r.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice: + return r.IsNil() + default: + return false + } +} diff --git a/vendor/github.com/expr-lang/expr/vm/runtime/sort.go b/vendor/github.com/expr-lang/expr/vm/runtime/sort.go new file mode 100644 index 00000000000..fb1f340d79c --- /dev/null +++ b/vendor/github.com/expr-lang/expr/vm/runtime/sort.go @@ -0,0 +1,45 @@ +package runtime + +type SortBy struct { + Desc bool + Array []any + Values []any +} + +func (s *SortBy) Len() int { + return len(s.Array) +} + +func (s *SortBy) Swap(i, j int) { + s.Array[i], s.Array[j] = s.Array[j], s.Array[i] + s.Values[i], s.Values[j] = s.Values[j], s.Values[i] +} + +func (s *SortBy) Less(i, j int) bool { + a, b := s.Values[i], s.Values[j] + if s.Desc { + return Less(b, a) + } + return Less(a, b) +} + +type Sort struct { + Desc bool + Array []any +} + +func (s *Sort) Len() int { + return len(s.Array) +} + +func (s *Sort) Swap(i, j int) { + s.Array[i], s.Array[j] = s.Array[j], s.Array[i] +} + +func (s *Sort) Less(i, j int) bool { + a, b := s.Array[i], s.Array[j] + if s.Desc { + return Less(b, a) + } + return Less(a, b) +} diff --git a/vendor/github.com/expr-lang/expr/vm/utils.go b/vendor/github.com/expr-lang/expr/vm/utils.go new file mode 100644 index 00000000000..7f1ca1e89d7 --- /dev/null +++ b/vendor/github.com/expr-lang/expr/vm/utils.go @@ -0,0 +1,59 @@ +package vm + +import ( + "reflect" + "time" +) + +type ( + Function = func(params ...any) (any, error) + SafeFunction = func(params ...any) (any, uint, error) +) + +var ( + errorType = reflect.TypeOf((*error)(nil)).Elem() +) + +type Scope struct { + Array reflect.Value + Index int + Len int + Count int + Acc any + // Fast paths + Ints []int + Floats []float64 + Strings []string + Anys []any +} + +// Item returns the current element from the scope using fast paths when available. +func (s *Scope) Item() any { + if s.Ints != nil { + return s.Ints[s.Index] + } + if s.Floats != nil { + return s.Floats[s.Index] + } + if s.Strings != nil { + return s.Strings[s.Index] + } + if s.Anys != nil { + return s.Anys[s.Index] + } + return s.Array.Index(s.Index).Interface() +} + +type groupBy = map[any][]any + +type Span struct { + Name string `json:"name"` + Expression string `json:"expression"` + Duration int64 `json:"duration"` + Children []*Span `json:"children"` + start time.Time +} + +func GetSpan(program *Program) *Span { + return program.span +} diff --git a/vendor/github.com/expr-lang/expr/vm/vm.go b/vendor/github.com/expr-lang/expr/vm/vm.go new file mode 100644 index 00000000000..29ceb51f74f --- /dev/null +++ b/vendor/github.com/expr-lang/expr/vm/vm.go @@ -0,0 +1,818 @@ +package vm + +//go:generate sh -c "go run ./func_types > ./func_types[generated].go" + +import ( + "fmt" + "reflect" + "regexp" + "sort" + "strings" + "time" + + "github.com/expr-lang/expr/builtin" + "github.com/expr-lang/expr/conf" + "github.com/expr-lang/expr/file" + "github.com/expr-lang/expr/internal/deref" + "github.com/expr-lang/expr/vm/runtime" +) + +const maxFnArgsBuf = 256 + +func Run(program *Program, env any) (any, error) { + if program == nil { + return nil, fmt.Errorf("program is nil") + } + vm := VM{} + return vm.Run(program, env) +} + +func Debug() *VM { + vm := &VM{ + debug: true, + step: make(chan struct{}, 0), + curr: make(chan int, 0), + } + return vm +} + +type VM struct { + Stack []any + Scopes []*Scope + Variables []any + MemoryBudget uint + ip int + memory uint + debug bool + step chan struct{} + curr chan int + scopePool []Scope // Pre-allocated pool of Scope values; grows as needed but never shrinks + scopePoolIdx int // Current index into scopePool for allocation + currScope *Scope // Cached pointer to the current scope (optimization) +} + +func (vm *VM) Run(program *Program, env any) (_ any, err error) { + defer func() { + if r := recover(); r != nil { + var location file.Location + if vm.ip-1 < len(program.locations) { + location = program.locations[vm.ip-1] + } + f := &file.Error{ + Location: location, + Message: fmt.Sprintf("%v", r), + } + if err, ok := r.(error); ok { + f.Wrap(err) + } + err = f.Bind(program.source) + } + }() + + if vm.Stack == nil { + vm.Stack = make([]any, 0, 2) + } else { + clearSlice(vm.Stack) + vm.Stack = vm.Stack[0:0] + } + if vm.Scopes != nil { + clearSlice(vm.Scopes) + vm.Scopes = vm.Scopes[0:0] + } + vm.scopePoolIdx = 0 // Reset pool index for reuse + vm.currScope = nil + if len(vm.Variables) < program.variables { + vm.Variables = make([]any, program.variables) + } + if vm.MemoryBudget == 0 { + vm.MemoryBudget = conf.DefaultMemoryBudget + } + vm.memory = 0 + vm.ip = 0 + + var fnArgsBuf []any + + for vm.ip < len(program.Bytecode) { + if debug && vm.debug { + <-vm.step + } + + op := program.Bytecode[vm.ip] + arg := program.Arguments[vm.ip] + vm.ip += 1 + + switch op { + + case OpInvalid: + panic("invalid opcode") + + case OpPush: + vm.push(program.Constants[arg]) + + case OpInt: + vm.push(arg) + + case OpPop: + vm.pop() + + case OpStore: + vm.Variables[arg] = vm.pop() + + case OpLoadVar: + vm.push(vm.Variables[arg]) + + case OpLoadConst: + vm.push(runtime.Fetch(env, program.Constants[arg])) + + case OpLoadField: + vm.push(runtime.FetchField(env, program.Constants[arg].(*runtime.Field))) + + case OpLoadFast: + vm.push(env.(map[string]any)[program.Constants[arg].(string)]) + + case OpLoadMethod: + vm.push(runtime.FetchMethod(env, program.Constants[arg].(*runtime.Method))) + + case OpLoadFunc: + vm.push(program.functions[arg]) + + case OpFetch: + b := vm.pop() + a := vm.pop() + vm.push(runtime.Fetch(a, b)) + + case OpFetchField: + a := vm.pop() + vm.push(runtime.FetchField(a, program.Constants[arg].(*runtime.Field))) + + case OpLoadEnv: + vm.push(env) + + case OpMethod: + a := vm.pop() + vm.push(runtime.FetchMethod(a, program.Constants[arg].(*runtime.Method))) + + case OpTrue: + vm.push(true) + + case OpFalse: + vm.push(false) + + case OpNil: + vm.push(nil) + + case OpNegate: + v := runtime.Negate(vm.pop()) + vm.push(v) + + case OpNot: + v := vm.pop().(bool) + vm.push(!v) + + case OpEqual: + b := vm.pop() + a := vm.pop() + vm.push(runtime.Equal(a, b)) + + case OpEqualInt: + b := vm.pop() + a := vm.pop() + vm.push(a.(int) == b.(int)) + + case OpEqualString: + b := vm.pop() + a := vm.pop() + vm.push(a.(string) == b.(string)) + + case OpJump: + if arg < 0 { + panic("negative jump offset is invalid") + } + vm.ip += arg + + case OpJumpIfTrue: + if arg < 0 { + panic("negative jump offset is invalid") + } + if vm.current().(bool) { + vm.ip += arg + } + + case OpJumpIfFalse: + if arg < 0 { + panic("negative jump offset is invalid") + } + if !vm.current().(bool) { + vm.ip += arg + } + + case OpJumpIfNil: + if arg < 0 { + panic("negative jump offset is invalid") + } + if runtime.IsNil(vm.current()) { + vm.ip += arg + } + + case OpJumpIfNotNil: + if arg < 0 { + panic("negative jump offset is invalid") + } + if !runtime.IsNil(vm.current()) { + vm.ip += arg + } + + case OpJumpIfEnd: + if arg < 0 { + panic("negative jump offset is invalid") + } + if vm.currScope.Index >= vm.currScope.Len { + vm.ip += arg + } + + case OpJumpBackward: + vm.ip -= arg + + case OpIn: + b := vm.pop() + a := vm.pop() + vm.push(runtime.In(a, b)) + + case OpLess: + b := vm.pop() + a := vm.pop() + vm.push(runtime.Less(a, b)) + + case OpMore: + b := vm.pop() + a := vm.pop() + vm.push(runtime.More(a, b)) + + case OpLessOrEqual: + b := vm.pop() + a := vm.pop() + vm.push(runtime.LessOrEqual(a, b)) + + case OpMoreOrEqual: + b := vm.pop() + a := vm.pop() + vm.push(runtime.MoreOrEqual(a, b)) + + case OpAdd: + b := vm.pop() + a := vm.pop() + vm.push(runtime.Add(a, b)) + + case OpSubtract: + b := vm.pop() + a := vm.pop() + vm.push(runtime.Subtract(a, b)) + + case OpMultiply: + b := vm.pop() + a := vm.pop() + vm.push(runtime.Multiply(a, b)) + + case OpDivide: + b := vm.pop() + a := vm.pop() + vm.push(runtime.Divide(a, b)) + + case OpModulo: + b := vm.pop() + a := vm.pop() + vm.push(runtime.Modulo(a, b)) + + case OpExponent: + b := vm.pop() + a := vm.pop() + vm.push(runtime.Exponent(a, b)) + + case OpRange: + b := vm.pop() + a := vm.pop() + min := runtime.ToInt(a) + max := runtime.ToInt(b) + size := max - min + 1 + if size <= 0 { + size = 0 + } + vm.memGrow(uint(size)) + vm.push(runtime.MakeRange(min, max)) + + case OpMatches: + b := vm.pop() + a := vm.pop() + if runtime.IsNil(a) || runtime.IsNil(b) { + vm.push(false) + break + } + var match bool + var err error + if s, ok := a.(string); ok { + match, err = regexp.MatchString(b.(string), s) + } else { + match, err = regexp.Match(b.(string), a.([]byte)) + } + if err != nil { + panic(err) + } + vm.push(match) + + case OpMatchesConst: + a := vm.pop() + if runtime.IsNil(a) { + vm.push(false) + break + } + r := program.Constants[arg].(*regexp.Regexp) + if s, ok := a.(string); ok { + vm.push(r.MatchString(s)) + } else { + vm.push(r.Match(a.([]byte))) + } + + case OpContains: + b := vm.pop() + a := vm.pop() + if runtime.IsNil(a) || runtime.IsNil(b) { + vm.push(false) + break + } + vm.push(strings.Contains(a.(string), b.(string))) + + case OpStartsWith: + b := vm.pop() + a := vm.pop() + if runtime.IsNil(a) || runtime.IsNil(b) { + vm.push(false) + break + } + vm.push(strings.HasPrefix(a.(string), b.(string))) + + case OpEndsWith: + b := vm.pop() + a := vm.pop() + if runtime.IsNil(a) || runtime.IsNil(b) { + vm.push(false) + break + } + vm.push(strings.HasSuffix(a.(string), b.(string))) + + case OpSlice: + from := vm.pop() + to := vm.pop() + node := vm.pop() + vm.push(runtime.Slice(node, from, to)) + + case OpCall: + v := vm.pop() + if v == nil { + panic("invalid operation: cannot call nil") + } + fn := reflect.ValueOf(v) + if fn.Kind() != reflect.Func { + panic(fmt.Sprintf("invalid operation: cannot call non-function of type %T", v)) + } + fnType := fn.Type() + size := arg + isVariadic := fnType.IsVariadic() + numIn := fnType.NumIn() + if isVariadic { + if size < numIn-1 { + panic(fmt.Sprintf("invalid number of arguments: expected at least %d, got %d", numIn-1, size)) + } + } else { + if size != numIn { + panic(fmt.Sprintf("invalid number of arguments: expected %d, got %d", numIn, size)) + } + } + in := make([]reflect.Value, size) + for i := int(size) - 1; i >= 0; i-- { + param := vm.pop() + if param == nil { + var inType reflect.Type + if isVariadic && i >= numIn-1 { + inType = fnType.In(numIn - 1).Elem() + } else { + inType = fnType.In(i) + } + in[i] = reflect.Zero(inType) + } else { + in[i] = reflect.ValueOf(param) + } + } + out := fn.Call(in) + if len(out) == 2 && out[1].Type() == errorType && !out[1].IsNil() { + panic(out[1].Interface().(error)) + } + vm.push(out[0].Interface()) + + case OpCall0: + out, err := program.functions[arg]() + if err != nil { + panic(err) + } + vm.push(out) + + case OpCall1: + var args []any + args, fnArgsBuf = vm.getArgsForFunc(fnArgsBuf, program, 1) + out, err := program.functions[arg](args...) + if err != nil { + panic(err) + } + vm.push(out) + + case OpCall2: + var args []any + args, fnArgsBuf = vm.getArgsForFunc(fnArgsBuf, program, 2) + out, err := program.functions[arg](args...) + if err != nil { + panic(err) + } + vm.push(out) + + case OpCall3: + var args []any + args, fnArgsBuf = vm.getArgsForFunc(fnArgsBuf, program, 3) + out, err := program.functions[arg](args...) + if err != nil { + panic(err) + } + vm.push(out) + + case OpCallN: + fn := vm.pop().(Function) + var args []any + args, fnArgsBuf = vm.getArgsForFunc(fnArgsBuf, program, arg) + out, err := fn(args...) + if err != nil { + panic(err) + } + vm.push(out) + + case OpCallFast: + fn := vm.pop().(func(...any) any) + var args []any + args, fnArgsBuf = vm.getArgsForFunc(fnArgsBuf, program, arg) + vm.push(fn(args...)) + + case OpCallSafe: + fn := vm.pop().(SafeFunction) + var args []any + args, fnArgsBuf = vm.getArgsForFunc(fnArgsBuf, program, arg) + out, mem, err := fn(args...) + if err != nil { + panic(err) + } + vm.memGrow(mem) + vm.push(out) + + case OpCallTyped: + vm.push(vm.call(vm.pop(), arg)) + + case OpCallBuiltin1: + vm.push(builtin.Builtins[arg].Fast(vm.pop())) + + case OpArray: + size := vm.pop().(int) + vm.memGrow(uint(size)) + array := make([]any, size) + for i := size - 1; i >= 0; i-- { + array[i] = vm.pop() + } + vm.push(array) + + case OpMap: + size := vm.pop().(int) + vm.memGrow(uint(size)) + m := make(map[string]any) + for i := size - 1; i >= 0; i-- { + value := vm.pop() + key := vm.pop() + m[key.(string)] = value + } + vm.push(m) + + case OpLen: + vm.push(runtime.Len(vm.current())) + + case OpCast: + switch arg { + case 0: + vm.push(runtime.ToInt(vm.pop())) + case 1: + vm.push(runtime.ToInt64(vm.pop())) + case 2: + vm.push(runtime.ToFloat64(vm.pop())) + case 3: + vm.push(runtime.ToBool(vm.pop())) + } + + case OpDeref: + a := vm.pop() + vm.push(deref.Interface(a)) + + case OpIncrementIndex: + vm.currScope.Index++ + + case OpDecrementIndex: + vm.currScope.Index-- + + case OpIncrementCount: + vm.currScope.Count++ + + case OpGetIndex: + vm.push(vm.currScope.Index) + + case OpGetCount: + vm.push(vm.currScope.Count) + + case OpGetLen: + vm.push(vm.currScope.Len) + + case OpGetAcc: + vm.push(vm.currScope.Acc) + + case OpSetAcc: + vm.currScope.Acc = vm.pop() + + case OpSetIndex: + vm.currScope.Index = vm.pop().(int) + + case OpPointer: + vm.push(vm.currScope.Item()) + + case OpThrow: + panic(vm.pop().(error)) + + case OpCreate: + switch arg { + case 1: + vm.push(make(groupBy)) + case 2: + scope := vm.currScope + var desc bool + order, ok := vm.pop().(string) + if !ok { + panic("sortBy order argument must be a string") + } + switch order { + case "asc": + desc = false + case "desc": + desc = true + default: + panic("unknown order, use asc or desc") + } + vm.push(&runtime.SortBy{ + Desc: desc, + Array: make([]any, 0, scope.Len), + Values: make([]any, 0, scope.Len), + }) + default: + panic(fmt.Sprintf("unknown OpCreate argument %v", arg)) + } + + case OpGroupBy: + scope := vm.currScope + key := vm.pop() + scope.Acc.(groupBy)[key] = append(scope.Acc.(groupBy)[key], scope.Item()) + + case OpSortBy: + scope := vm.currScope + value := vm.pop() + sortable := scope.Acc.(*runtime.SortBy) + sortable.Array = append(sortable.Array, scope.Item()) + sortable.Values = append(sortable.Values, value) + + case OpSort: + scope := vm.currScope + sortable := scope.Acc.(*runtime.SortBy) + sort.Sort(sortable) + vm.memGrow(uint(scope.Len)) + vm.push(sortable.Array) + + case OpProfileStart: + span := program.Constants[arg].(*Span) + span.start = time.Now() + + case OpProfileEnd: + span := program.Constants[arg].(*Span) + span.Duration += time.Since(span.start).Nanoseconds() + + case OpBegin: + a := vm.pop() + s := vm.allocScope() + switch v := a.(type) { + case []int: + s.Ints = v + s.Len = len(v) + case []float64: + s.Floats = v + s.Len = len(v) + case []string: + s.Strings = v + s.Len = len(v) + case []any: + s.Anys = v + s.Len = len(v) + default: + s.Array = reflect.ValueOf(a) + s.Len = s.Array.Len() + } + vm.Scopes = append(vm.Scopes, s) + vm.currScope = s + + case OpAnd: + a := vm.pop() + b := vm.pop() + vm.push(a.(bool) && b.(bool)) + + case OpOr: + a := vm.pop() + b := vm.pop() + vm.push(a.(bool) || b.(bool)) + + case OpEnd: + vm.Scopes = vm.Scopes[:len(vm.Scopes)-1] + if len(vm.Scopes) > 0 { + vm.currScope = vm.Scopes[len(vm.Scopes)-1] + } else { + vm.currScope = nil + } + + default: + panic(fmt.Sprintf("unknown bytecode %#x", op)) + } + + if debug && vm.debug { + vm.curr <- vm.ip + } + } + + if debug && vm.debug { + close(vm.curr) + close(vm.step) + } + + if len(vm.Stack) > 0 { + return vm.pop(), nil + } + + return nil, nil +} + +func (vm *VM) push(value any) { + vm.Stack = append(vm.Stack, value) +} + +func (vm *VM) current() any { + if len(vm.Stack) == 0 { + panic("stack underflow") + } + return vm.Stack[len(vm.Stack)-1] +} + +func (vm *VM) pop() any { + if len(vm.Stack) == 0 { + panic("stack underflow") + } + value := vm.Stack[len(vm.Stack)-1] + vm.Stack = vm.Stack[:len(vm.Stack)-1] + return value +} + +func (vm *VM) memGrow(size uint) { + vm.memory += size + if vm.memory >= vm.MemoryBudget { + panic("memory budget exceeded") + } +} + +func (vm *VM) scope() *Scope { + return vm.Scopes[len(vm.Scopes)-1] +} + +// allocScope returns a pointer to a Scope from the pool, growing the pool if needed. +// Callers must set Len and exactly one of: Ints, Floats, Strings, Anys, or Array. +func (vm *VM) allocScope() *Scope { + if vm.scopePoolIdx >= len(vm.scopePool) { + vm.scopePool = append(vm.scopePool, Scope{}) + } + s := &vm.scopePool[vm.scopePoolIdx] + vm.scopePoolIdx++ + // Reset iteration state + s.Index = 0 + s.Count = 0 + s.Acc = nil + // Clear typed slice pointers to avoid stale fast-path matches + s.Ints = nil + s.Floats = nil + s.Strings = nil + s.Anys = nil + // Clear Array to release reference for GC (only matters for fallback path) + s.Array = reflect.Value{} + return s +} + +// getArgsForFunc lazily initializes the buffer the first time it is called for +// a given program (thus, it also needs "program" to run). It will +// take "needed" elements from the buffer and populate them with vm.pop() in +// reverse order. Because the estimation can fall short, this function can +// occasionally make a new allocation. +func (vm *VM) getArgsForFunc(argsBuf []any, program *Program, needed int) (args []any, argsBufOut []any) { + if needed == 0 || program == nil { + return nil, argsBuf + } + + // Step 1: fix estimations and preallocate + if argsBuf == nil { + estimatedFnArgsCount := estimateFnArgsCount(program) + if estimatedFnArgsCount > maxFnArgsBuf { + // put a practical limit to avoid excessive preallocation + estimatedFnArgsCount = maxFnArgsBuf + } + if estimatedFnArgsCount < needed { + // in the case that the first call is for example OpCallN with a large + // number of arguments, then make sure we will be able to serve them at + // least. + estimatedFnArgsCount = needed + } + + // in the case that we are preparing the arguments for the first + // function call of the program, then argsBuf will be nil, so we + // initialize it. We delay this initial allocation here because a + // program could have many function calls but exit earlier than the + // first call, so in that case we avoid allocating unnecessarily + argsBuf = make([]any, estimatedFnArgsCount) + } + + // Step 2: get the final slice that will be returned + var buf []any + if len(argsBuf) >= needed { + // in this case, we are successfully using the single preallocation. We + // use the full slice expression [low : high : max] because in that way + // a function that receives this slice as variadic arguments will not be + // able to make modifications to contiguous elements with append(). If + // they call append on their variadic arguments they will make a new + // allocation. + buf = (argsBuf)[:needed:needed] + argsBuf = (argsBuf)[needed:] // advance the buffer + } else { + // if we have been making calls to something like OpCallN with many more + // arguments than what we estimated, then we will need to allocate + // separately + buf = make([]any, needed) + } + + // Step 3: populate the final slice bulk copying from the stack. This is the + // exact order and copy() is a highly optimized operation + copy(buf, vm.Stack[len(vm.Stack)-needed:]) + vm.Stack = vm.Stack[:len(vm.Stack)-needed] + + return buf, argsBuf +} + +func (vm *VM) Step() { + vm.step <- struct{}{} +} + +func (vm *VM) Position() chan int { + return vm.curr +} + +func clearSlice[S ~[]E, E any](s S) { + var zero E + for i := range s { + s[i] = zero // clear mem, optimized by the compiler, in Go 1.21 the "clear" builtin can be used + } +} + +// estimateFnArgsCount inspects a *Program and estimates how many function +// arguments will be required to run it. +func estimateFnArgsCount(program *Program) int { + // Implementation note: a program will not necessarily go through all + // operations, but this is just an estimation + var count int + for _, op := range program.Bytecode { + if int(op) < len(opArgLenEstimation) { + count += opArgLenEstimation[op] + } + } + return count +} + +var opArgLenEstimation = [...]int{ + OpCall1: 1, + OpCall2: 2, + OpCall3: 3, + // we don't know exactly but we know at least 4, so be conservative as this + // is only an optimization and we also want to avoid excessive preallocation + OpCallN: 4, + // here we don't know either, but we can guess it could be common to receive + // up to 3 arguments in a function + OpCallFast: 3, + OpCallSafe: 3, +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 2241de9d8f8..0d94521f7e8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -120,6 +120,27 @@ github.com/docker/go-units # github.com/dustin/go-humanize v1.0.1 ## explicit; go 1.16 github.com/dustin/go-humanize +# github.com/expr-lang/expr v1.17.8 +## explicit; go 1.18 +github.com/expr-lang/expr +github.com/expr-lang/expr/ast +github.com/expr-lang/expr/builtin +github.com/expr-lang/expr/checker +github.com/expr-lang/expr/checker/nature +github.com/expr-lang/expr/compiler +github.com/expr-lang/expr/conf +github.com/expr-lang/expr/file +github.com/expr-lang/expr/internal/deref +github.com/expr-lang/expr/internal/ring +github.com/expr-lang/expr/optimizer +github.com/expr-lang/expr/parser +github.com/expr-lang/expr/parser/lexer +github.com/expr-lang/expr/parser/operator +github.com/expr-lang/expr/parser/utils +github.com/expr-lang/expr/patcher +github.com/expr-lang/expr/types +github.com/expr-lang/expr/vm +github.com/expr-lang/expr/vm/runtime # github.com/fatih/color v1.15.0 ## explicit; go 1.17 github.com/fatih/color