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 @@
+
Expr
+
+[](https://github.com/expr-lang/expr/actions/workflows/test.yml)
+[](https://goreportcard.com/report/github.com/expr-lang/expr)
+[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:expr)
+[](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