diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f5844f8868..c28beacedeb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -53,6 +53,12 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
- **AWS SQS Scaler**: Support for scaling to include delayed messages. ([#4377](https://github.com/kedacore/keda/issues/4377))
- **Governance**: KEDA transitioned to CNCF Graduated project ([#63](https://github.com/kedacore/governance/issues/63))
+#### Experimental
+
+Here is an overview of all new **experimental** features:
+
+- **General**: Add support for formula based evaluation of metric values ([#2440](https://github.com/kedacore/keda/issues/2440))
+
### Improvements
- **General**: Add more events for user checking ([#796](https://github.com/kedacore/keda/issues/3764))
diff --git a/apis/keda/v1alpha1/scaledobject_types.go b/apis/keda/v1alpha1/scaledobject_types.go
index 24af5a2c0ee..d1fb0e89405 100644
--- a/apis/keda/v1alpha1/scaledobject_types.go
+++ b/apis/keda/v1alpha1/scaledobject_types.go
@@ -17,6 +17,8 @@ limitations under the License.
package v1alpha1
import (
+ "reflect"
+
autoscalingv2 "k8s.io/api/autoscaling/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -67,6 +69,9 @@ const (
// HealthStatusFailing means the status of the health object is failing
HealthStatusFailing HealthStatusType = "Failing"
+
+ // Composite metric name used for scalingModifiers composite metric
+ CompositeMetricName string = "composite-metric"
)
// ScaledObjectSpec is the spec for a ScaledObject resource
@@ -102,6 +107,14 @@ type AdvancedConfig struct {
HorizontalPodAutoscalerConfig *HorizontalPodAutoscalerConfig `json:"horizontalPodAutoscalerConfig,omitempty"`
// +optional
RestoreToOriginalReplicaCount bool `json:"restoreToOriginalReplicaCount,omitempty"`
+ // +optional
+ ScalingModifiers ScalingModifiers `json:"scalingModifiers,omitempty"`
+}
+
+// ScalingModifiers describes advanced scaling logic options like formula
+type ScalingModifiers struct {
+ Formula string `json:"formula,omitempty"`
+ Target string `json:"target,omitempty"`
}
// HorizontalPodAutoscalerConfig specifies horizontal scale config
@@ -141,6 +154,8 @@ type ScaledObjectStatus struct {
// +optional
ResourceMetricNames []string `json:"resourceMetricNames,omitempty"`
// +optional
+ CompositeScalerName string `json:"compositeScalerName,omitempty"`
+ // +optional
Conditions Conditions `json:"conditions,omitempty"`
// +optional
Health map[string]HealthStatus `json:"health,omitempty"`
@@ -167,3 +182,8 @@ func init() {
func (so *ScaledObject) GenerateIdentifier() string {
return GenerateIdentifier("ScaledObject", so.Namespace, so.Name)
}
+
+// IsUsingModifiers determines whether scalingModifiers are defined or not
+func (so *ScaledObject) IsUsingModifiers() bool {
+ return so.Spec.Advanced != nil && !reflect.DeepEqual(so.Spec.Advanced.ScalingModifiers, ScalingModifiers{})
+}
diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go
index ce366ebc6bb..d7b9abea6d4 100644
--- a/apis/keda/v1alpha1/scaledobject_webhook.go
+++ b/apis/keda/v1alpha1/scaledobject_webhook.go
@@ -19,8 +19,12 @@ package v1alpha1
import (
"context"
"encoding/json"
+ "errors"
"fmt"
+ "strconv"
+ "github.com/antonmedv/expr"
+ "github.com/antonmedv/expr/vm"
appsv1 "k8s.io/api/apps/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
@@ -213,6 +217,16 @@ func verifyScaledObjects(incomingSo *ScaledObject, action string) error {
}
}
+ // verify ScalingModifiers structure if defined in ScaledObject
+ if incomingSo.IsUsingModifiers() {
+ _, err = ValidateAndCompileScalingModifiers(incomingSo)
+ if err != nil {
+ scaledobjectlog.Error(err, "error validating ScalingModifiers")
+ prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "scaling-modifiers")
+
+ return err
+ }
+ }
return nil
}
@@ -297,3 +311,115 @@ func verifyCPUMemoryScalers(incomingSo *ScaledObject, action string) error {
}
return nil
}
+
+// ValidateAndCompileScalingModifiers validates all combinations of given arguments
+// and their values. Expects the whole structure's path to be defined (like .Advanced).
+// As part of formula validation this function also compiles the formula
+// (with dummy values that determine whether all necessary triggers are defined)
+// and returns it to be stored in cache and reused.
+func ValidateAndCompileScalingModifiers(so *ScaledObject) (*vm.Program, error) {
+ sm := so.Spec.Advanced.ScalingModifiers
+
+ if sm.Formula == "" {
+ return nil, fmt.Errorf("error ScalingModifiers.Formula is mandatory")
+ }
+
+ // validate formula if not empty
+ compiledFormula, err := validateScalingModifiersFormula(so)
+ if err != nil {
+ err := errors.Join(fmt.Errorf("error validating formula in ScalingModifiers"), err)
+ return nil, err
+ }
+ // validate target if not empty
+ err = validateScalingModifiersTarget(so)
+ if err != nil {
+ err := errors.Join(fmt.Errorf("error validating target in ScalingModifiers"), err)
+ return nil, err
+ }
+ return compiledFormula, nil
+}
+
+// validateScalingModifiersFormula helps validate the ScalingModifiers struct,
+// specifically the formula.
+func validateScalingModifiersFormula(so *ScaledObject) (*vm.Program, error) {
+ sm := so.Spec.Advanced.ScalingModifiers
+
+ // if formula is empty, nothing to validate
+ if sm.Formula == "" {
+ return nil, nil
+ }
+ // formula needs target because it's always transformed to composite-scaler
+ if sm.Target == "" {
+ return nil, fmt.Errorf("formula is given but target is empty")
+ }
+
+ // dummy value for compiled map of triggers
+ dummyValue := -1.0
+
+ // Compile & Run with dummy values to determine if all triggers in formula are
+ // defined (have names)
+ triggersMap := make(map[string]float64)
+ for _, trig := range so.Spec.Triggers {
+ // if resource metrics are given, skip
+ if trig.Type == cpuString || trig.Type == memoryString {
+ continue
+ }
+ if trig.Name != "" {
+ triggersMap[trig.Name] = dummyValue
+ }
+ }
+ compiled, err := expr.Compile(sm.Formula, expr.Env(triggersMap), expr.AsFloat64())
+ if err != nil {
+ return nil, err
+ }
+ _, err = expr.Run(compiled, triggersMap)
+ if err != nil {
+ return nil, err
+ }
+ return compiled, nil
+}
+
+func validateScalingModifiersTarget(so *ScaledObject) error {
+ sm := so.Spec.Advanced.ScalingModifiers
+
+ if sm.Target == "" {
+ return nil
+ }
+
+ // convert string to float
+ num, err := strconv.ParseFloat(sm.Target, 64)
+ if err != nil || num <= 0.0 {
+ return fmt.Errorf("error converting target for scalingModifiers (string->float) to valid target: %w", err)
+ }
+
+ // if target is given, composite-scaler will be passed to HPA -> all types
+ // need to be the same - make sure all metrics are of the same metricTargetType
+
+ var trigType autoscalingv2.MetricTargetType
+
+ // gauron99: possible TODO: more sofisticated check for trigger could be used here
+ // as well if solution is found (check just the right triggers that are used)
+ for _, trig := range so.Spec.Triggers {
+ if trig.Type == cpuString || trig.Type == memoryString || trig.Name == "" {
+ continue
+ }
+ var current autoscalingv2.MetricTargetType
+ if trig.MetricType == "" {
+ current = autoscalingv2.AverageValueMetricType // default is AverageValue
+ } else {
+ current = trig.MetricType
+ }
+ if trigType == "" {
+ trigType = current
+ } else if trigType != current {
+ err := fmt.Errorf("error trigger types are not the same for composite scaler: %s & %s", trigType, current)
+ return err
+ }
+ }
+ if trigType == autoscalingv2.UtilizationMetricType {
+ err := fmt.Errorf("error trigger type is Utilization, but it needs to be AverageValue or Value for external metrics")
+ return err
+ }
+
+ return nil
+}
diff --git a/apis/keda/v1alpha1/scaledobject_webhook_test.go b/apis/keda/v1alpha1/scaledobject_webhook_test.go
index 2b1aed78745..c20ad35d644 100644
--- a/apis/keda/v1alpha1/scaledobject_webhook_test.go
+++ b/apis/keda/v1alpha1/scaledobject_webhook_test.go
@@ -419,6 +419,159 @@ var _ = It("shouldn't create so when stabilizationWindowSeconds exceeds 3600", f
}).Should(HaveOccurred())
})
+var _ = It("should validate the so creation with ScalingModifiers.Formula", func() {
+ namespaceName := "scaling-modifiers-formula-good"
+ namespace := createNamespace(namespaceName)
+ workload := createDeployment(namespaceName, false, false)
+
+ sm := ScalingModifiers{Target: "2", Formula: "workload_trig + cron_trig"}
+
+ triggers := []ScaleTriggers{
+ {
+ Type: "cron",
+ Name: "cron_trig",
+ Metadata: map[string]string{
+ "timezone": "UTC",
+ "start": "0 * * * *",
+ "end": "1 * * * *",
+ "desiredReplicas": "1",
+ },
+ },
+ {
+ Type: "kubernetes-workload",
+ Name: "workload_trig",
+ Metadata: map[string]string{
+ "podSelector": "pod=workload-test",
+ "value": "1",
+ },
+ },
+ }
+
+ so := createScaledObjectScalingModifiers(namespaceName, sm, triggers)
+
+ err := k8sClient.Create(context.Background(), namespace)
+ Expect(err).ToNot(HaveOccurred())
+ err = k8sClient.Create(context.Background(), workload)
+ Expect(err).ToNot(HaveOccurred())
+ Eventually(func() error {
+ return k8sClient.Create(context.Background(), so)
+ }).ShouldNot(HaveOccurred())
+})
+
+var _ = It("shouldnt validate the so creation with scalingModifiers.Formula but no target", func() {
+ namespaceName := "scaling-modifiers-formula-no-target-bad"
+ namespace := createNamespace(namespaceName)
+ workload := createDeployment(namespaceName, false, false)
+
+ sm := ScalingModifiers{Formula: "workload_trig + cron_trig"}
+
+ triggers := []ScaleTriggers{
+ {
+ Type: "cron",
+ Name: "cron_trig",
+ Metadata: map[string]string{
+ "timezone": "UTC",
+ "start": "0 * * * *",
+ "end": "1 * * * *",
+ "desiredReplicas": "1",
+ },
+ },
+ {
+ Type: "kubernetes-workload",
+ Name: "workload_trig",
+ Metadata: map[string]string{
+ "podSelector": "pod=workload-test",
+ "value": "1",
+ },
+ },
+ }
+
+ so := createScaledObjectScalingModifiers(namespaceName, sm, triggers)
+
+ err := k8sClient.Create(context.Background(), namespace)
+ Expect(err).ToNot(HaveOccurred())
+ err = k8sClient.Create(context.Background(), workload)
+ Expect(err).ToNot(HaveOccurred())
+ Eventually(func() error {
+ return k8sClient.Create(context.Background(), so)
+ }).Should(HaveOccurred())
+})
+
+var _ = It("shouldnt validate the so creation with ScalingModifiers when triggers dont have names", func() {
+ namespaceName := "scaling-modifiers-triggers-no-names-bad"
+ namespace := createNamespace(namespaceName)
+ workload := createDeployment(namespaceName, true, true)
+
+ sm := ScalingModifiers{Formula: "workload_trig + cron_trig"}
+
+ triggers := []ScaleTriggers{
+ {
+ Type: "cron",
+ Metadata: map[string]string{
+ "timezone": "UTC",
+ "start": "0 * * * *",
+ "end": "1 * * * *",
+ "desiredReplicas": "1",
+ },
+ },
+ {
+ Type: "kubernetes-workload",
+ Metadata: map[string]string{
+ "podSelector": "pod=workload-test",
+ "value": "1",
+ },
+ },
+ }
+
+ so := createScaledObjectScalingModifiers(namespaceName, sm, triggers)
+
+ err := k8sClient.Create(context.Background(), namespace)
+ Expect(err).ToNot(HaveOccurred())
+ err = k8sClient.Create(context.Background(), workload)
+ Expect(err).ToNot(HaveOccurred())
+ Eventually(func() error {
+ return k8sClient.Create(context.Background(), so)
+ }).Should(HaveOccurred())
+})
+
+var _ = It("should validate the so creation with ScalingModifiers when formula triggers do have names but not all triggers", func() {
+ namespaceName := "scaling-modifiers-specific-triggers-good"
+ namespace := createNamespace(namespaceName)
+ workload := createDeployment(namespaceName, true, true)
+
+ sm := ScalingModifiers{Target: "2", Formula: "workload_trig + 1"}
+
+ triggers := []ScaleTriggers{
+ {
+ Type: "cron",
+ Metadata: map[string]string{
+ "timezone": "UTC",
+ "start": "0 * * * *",
+ "end": "1 * * * *",
+ "desiredReplicas": "1",
+ },
+ },
+ {
+ Type: "kubernetes-workload",
+ Name: "workload_trig",
+ Metadata: map[string]string{
+ "podSelector": "pod=workload-test",
+ "value": "1",
+ },
+ },
+ }
+
+ so := createScaledObjectScalingModifiers(namespaceName, sm, triggers)
+
+ err := k8sClient.Create(context.Background(), namespace)
+ Expect(err).ToNot(HaveOccurred())
+ err = k8sClient.Create(context.Background(), workload)
+ Expect(err).ToNot(HaveOccurred())
+ Eventually(func() error {
+ return k8sClient.Create(context.Background(), so)
+ }).ShouldNot(HaveOccurred())
+})
+
var _ = AfterSuite(func() {
cancel()
By("tearing down the test environment")
@@ -666,3 +819,31 @@ func createScaledObjectSTZ(name string, namespace string, targetName string, min
},
}
}
+
+func createScaledObjectScalingModifiers(namespace string, sm ScalingModifiers, triggers []ScaleTriggers) *ScaledObject {
+ name := soName
+ targetName := workloadName
+ return &ScaledObject{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ UID: types.UID(name),
+ },
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ScaledObject",
+ APIVersion: "keda.sh",
+ },
+ Spec: ScaledObjectSpec{
+ ScaleTargetRef: &ScaleTarget{
+ Name: targetName,
+ },
+ MinReplicaCount: ptr.To[int32](0),
+ MaxReplicaCount: ptr.To[int32](10),
+ CooldownPeriod: ptr.To[int32](1),
+ Triggers: triggers,
+ Advanced: &AdvancedConfig{
+ ScalingModifiers: sm,
+ },
+ },
+ }
+}
diff --git a/apis/keda/v1alpha1/zz_generated.deepcopy.go b/apis/keda/v1alpha1/zz_generated.deepcopy.go
index 3f45524c373..02709296643 100755
--- a/apis/keda/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/keda/v1alpha1/zz_generated.deepcopy.go
@@ -1,4 +1,5 @@
//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
/*
Copyright 2023 The KEDA Authors
@@ -34,6 +35,7 @@ func (in *AdvancedConfig) DeepCopyInto(out *AdvancedConfig) {
*out = new(HorizontalPodAutoscalerConfig)
(*in).DeepCopyInto(*out)
}
+ out.ScalingModifiers = in.ScalingModifiers
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdvancedConfig.
@@ -786,6 +788,21 @@ func (in *ScaledObjectStatus) DeepCopy() *ScaledObjectStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ScalingModifiers) DeepCopyInto(out *ScalingModifiers) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScalingModifiers.
+func (in *ScalingModifiers) DeepCopy() *ScalingModifiers {
+ if in == nil {
+ return nil
+ }
+ out := new(ScalingModifiers)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ScalingStrategy) DeepCopyInto(out *ScalingStrategy) {
*out = *in
diff --git a/config/crd/bases/keda.sh_scaledobjects.yaml b/config/crd/bases/keda.sh_scaledobjects.yaml
index aa99d2db234..86ab804d13e 100644
--- a/config/crd/bases/keda.sh_scaledobjects.yaml
+++ b/config/crd/bases/keda.sh_scaledobjects.yaml
@@ -203,6 +203,15 @@ spec:
type: object
restoreToOriginalReplicaCount:
type: boolean
+ scalingModifiers:
+ description: ScalingModifiers describes advanced scaling logic
+ options like formula
+ properties:
+ formula:
+ type: string
+ target:
+ type: string
+ type: object
type: object
cooldownPeriod:
format: int32
@@ -291,6 +300,8 @@ spec:
status:
description: ScaledObjectStatus is the status for a ScaledObject resource
properties:
+ compositeScalerName:
+ type: string
conditions:
description: Conditions an array representation to store multiple
Conditions
diff --git a/controllers/keda/hpa.go b/controllers/keda/hpa.go
index 83c4ad9d0ed..8f0e470782f 100644
--- a/controllers/keda/hpa.go
+++ b/controllers/keda/hpa.go
@@ -20,12 +20,14 @@ import (
"context"
"fmt"
"sort"
+ "strconv"
"strings"
"unicode"
"github.com/go-logr/logr"
autoscalingv2 "k8s.io/api/autoscaling/v2"
"k8s.io/apimachinery/pkg/api/equality"
+ "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -246,7 +248,74 @@ func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context,
updateHealthStatus(scaledObject, externalMetricNames, status)
+ // if ScalingModifiers struct is not empty, expect Formula and Target to be
+ // non-empty (is validated beforehand - in cache). Only if target is > 0.0
+ // create a compositeScaler structure
+ if scaledObject.IsUsingModifiers() {
+ // convert string to float (this is already validated in:
+ // cache, err := r.ScaleHandler.GetScalersCache(ctx, scaledObject.DeepCopy())
+ // at the beginning of this function, where the whole scalingModifiers are validated)
+ validNumTarget, _ := strconv.ParseFloat(scaledObject.Spec.Advanced.ScalingModifiers.Target, 64)
+
+ // check & get metric specs type
+ var validMetricType autoscalingv2.MetricTargetType
+ for _, metric := range metricSpecs {
+ if metric.External == nil {
+ continue
+ }
+ if validMetricType == "" {
+ validMetricType = metric.External.Target.Type
+ } else if metric.External.Target.Type != validMetricType {
+ err := fmt.Errorf("error metric target type is not the same for composite scaler: %s & %s", validMetricType, metric.External.Target.Type)
+ return nil, err
+ }
+ }
+ if validMetricType == autoscalingv2.UtilizationMetricType {
+ err := fmt.Errorf("error metric target type is Utilization, but it needs to be AverageValue or Value for external metrics")
+ return nil, err
+ }
+
+ // if target is valid, use composite scaler. Expect defined formula that returns one metric
+ if validNumTarget > 0.0 {
+ quan := resource.NewMilliQuantity(int64(validNumTarget*1000), resource.DecimalSI)
+
+ correctHpaTarget := autoscalingv2.MetricTarget{
+ Type: validMetricType,
+ }
+ if validMetricType == autoscalingv2.AverageValueMetricType {
+ correctHpaTarget.AverageValue = quan
+ } else if validMetricType == autoscalingv2.ValueMetricType {
+ correctHpaTarget.Value = quan
+ }
+ compMetricName := kedav1alpha1.CompositeMetricName
+ compositeSpec := autoscalingv2.MetricSpec{
+ Type: autoscalingv2.MetricSourceType("External"),
+ External: &autoscalingv2.ExternalMetricSource{
+ Metric: autoscalingv2.MetricIdentifier{
+ Name: compMetricName,
+ Selector: &metav1.LabelSelector{
+ MatchLabels: map[string]string{kedav1alpha1.ScaledObjectOwnerAnnotation: scaledObject.Name},
+ },
+ },
+ Target: correctHpaTarget,
+ },
+ }
+ status.CompositeScalerName = compMetricName
+
+ // overwrite external metrics in returned array with composite metric ONLY (keep resource metrics)
+ finalHpaSpecs := []autoscalingv2.MetricSpec{}
+ // keep resource specs
+ for _, rm := range scaledObjectMetricSpecs {
+ if rm.Resource != nil {
+ finalHpaSpecs = append(finalHpaSpecs, rm)
+ }
+ }
+ finalHpaSpecs = append(finalHpaSpecs, compositeSpec)
+ scaledObjectMetricSpecs = finalHpaSpecs
+ }
+ }
err = kedastatus.UpdateScaledObjectStatus(ctx, r.Client, logger, scaledObject, status)
+
if err != nil {
logger.Error(err, "Error updating scaledObject status with used externalMetricNames")
return nil, err
diff --git a/go.mod b/go.mod
index 214ad60ea9e..71c6ceffb77 100644
--- a/go.mod
+++ b/go.mod
@@ -22,6 +22,7 @@ require (
github.com/DataDog/datadog-api-client-go v1.16.0
github.com/Huawei/gophercloud v1.0.21
github.com/IBM/sarama v1.41.1
+ github.com/antonmedv/expr v1.15.3
github.com/arangodb/go-driver v1.6.0
github.com/aws/aws-sdk-go-v2 v1.21.0
github.com/aws/aws-sdk-go-v2/config v1.18.38
diff --git a/go.sum b/go.sum
index 0be34be7282..48ebbaa9242 100644
--- a/go.sum
+++ b/go.sum
@@ -133,6 +133,8 @@ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
+github.com/antonmedv/expr v1.15.3 h1:q3hOJZNvLvhqE8OHBs1cFRdbXFNKuA+bHmRaI+AmRmI=
+github.com/antonmedv/expr v1.15.3/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/arangodb/go-driver v1.6.0 h1:NFWj/idqXZxhFVueihMSI2R9NotNIsgvNfM/xmpekb4=
diff --git a/pkg/fallback/fallback.go b/pkg/fallback/fallback.go
index 286144692f0..c746ac20295 100644
--- a/pkg/fallback/fallback.go
+++ b/pkg/fallback/fallback.go
@@ -44,7 +44,7 @@ func isFallbackEnabled(scaledObject *kedav1alpha1.ScaledObject, metricSpec v2.Me
return true
}
-func GetMetricsWithFallback(ctx context.Context, client runtimeclient.Client, metrics []external_metrics.ExternalMetricValue, suppressedError error, metricName string, scaledObject *kedav1alpha1.ScaledObject, metricSpec v2.MetricSpec) ([]external_metrics.ExternalMetricValue, error) {
+func GetMetricsWithFallback(ctx context.Context, client runtimeclient.Client, metrics []external_metrics.ExternalMetricValue, suppressedError error, metricName string, scaledObject *kedav1alpha1.ScaledObject, metricSpec v2.MetricSpec) ([]external_metrics.ExternalMetricValue, bool, error) {
status := scaledObject.Status.DeepCopy()
initHealthStatus(status)
@@ -57,7 +57,7 @@ func GetMetricsWithFallback(ctx context.Context, client runtimeclient.Client, me
status.Health[metricName] = *healthStatus
updateStatus(ctx, client, scaledObject, status, metricSpec)
- return metrics, nil
+ return metrics, false, nil
}
healthStatus.Status = kedav1alpha1.HealthStatusFailing
@@ -68,14 +68,14 @@ func GetMetricsWithFallback(ctx context.Context, client runtimeclient.Client, me
switch {
case !isFallbackEnabled(scaledObject, metricSpec):
- return nil, suppressedError
+ return nil, false, suppressedError
case !validateFallback(scaledObject):
log.Info("Failed to validate ScaledObject Spec. Please check that parameters are positive integers", "scaledObject.Namespace", scaledObject.Namespace, "scaledObject.Name", scaledObject.Name)
- return nil, suppressedError
+ return nil, false, suppressedError
case *healthStatus.NumberOfFailures > scaledObject.Spec.Fallback.FailureThreshold:
- return doFallback(scaledObject, metricSpec, metricName, suppressedError), nil
+ return doFallback(scaledObject, metricSpec, metricName, suppressedError), true, nil
default:
- return nil, suppressedError
+ return nil, false, suppressedError
}
}
diff --git a/pkg/fallback/fallback_test.go b/pkg/fallback/fallback_test.go
index 9f10ff8aad2..8a8dd456b27 100644
--- a/pkg/fallback/fallback_test.go
+++ b/pkg/fallback/fallback_test.go
@@ -70,7 +70,7 @@ var _ = Describe("fallback", func() {
expectStatusPatch(ctrl, client)
metrics, _, err := scaler.GetMetricsAndActivity(context.Background(), metricName)
- metrics, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
+ metrics, _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
Expect(err).ToNot(HaveOccurred())
value := metrics[0].Value.AsApproximateFloat64()
@@ -101,7 +101,7 @@ var _ = Describe("fallback", func() {
expectStatusPatch(ctrl, client)
metrics, _, err := scaler.GetMetricsAndActivity(context.Background(), metricName)
- metrics, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
+ metrics, _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
Expect(err).ToNot(HaveOccurred())
value := metrics[0].Value.AsApproximateFloat64()
@@ -117,7 +117,7 @@ var _ = Describe("fallback", func() {
expectStatusPatch(ctrl, client)
metrics, _, err := scaler.GetMetricsAndActivity(context.Background(), metricName)
- _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
+ _, _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
Expect(err).ShouldNot(BeNil())
Expect(err.Error()).Should(Equal("Some error"))
@@ -146,7 +146,7 @@ var _ = Describe("fallback", func() {
expectStatusPatch(ctrl, client)
metrics, _, err := scaler.GetMetricsAndActivity(context.Background(), metricName)
- _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
+ _, _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
Expect(err).ShouldNot(BeNil())
Expect(err.Error()).Should(Equal("Some error"))
@@ -176,7 +176,7 @@ var _ = Describe("fallback", func() {
expectStatusPatch(ctrl, client)
metrics, _, err := scaler.GetMetricsAndActivity(context.Background(), metricName)
- metrics, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
+ metrics, _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
Expect(err).ToNot(HaveOccurred())
value := metrics[0].Value.AsApproximateFloat64()
@@ -232,7 +232,7 @@ var _ = Describe("fallback", func() {
client.EXPECT().Status().Return(statusWriter)
metrics, _, err := scaler.GetMetricsAndActivity(context.Background(), metricName)
- metrics, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
+ metrics, _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
Expect(err).ToNot(HaveOccurred())
value := metrics[0].Value.AsApproximateFloat64()
@@ -262,7 +262,7 @@ var _ = Describe("fallback", func() {
expectStatusPatch(ctrl, client)
metrics, _, err := scaler.GetMetricsAndActivity(context.Background(), metricName)
- _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
+ _, _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
Expect(err).ShouldNot(BeNil())
Expect(err.Error()).Should(Equal("Some error"))
@@ -296,7 +296,7 @@ var _ = Describe("fallback", func() {
expectStatusPatch(ctrl, client)
metrics, _, err := scaler.GetMetricsAndActivity(context.Background(), metricName)
- _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
+ _, _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
Expect(err).ToNot(HaveOccurred())
condition := so.Status.Conditions.GetFallbackCondition()
Expect(condition.IsTrue()).Should(BeTrue())
@@ -330,7 +330,7 @@ var _ = Describe("fallback", func() {
expectStatusPatch(ctrl, client)
metrics, _, err := scaler.GetMetricsAndActivity(context.Background(), metricName)
- _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
+ _, _, err = GetMetricsWithFallback(context.Background(), client, metrics, err, metricName, so, metricSpec)
Expect(err).ShouldNot(BeNil())
Expect(err.Error()).Should(Equal("Some error"))
condition := so.Status.Conditions.GetFallbackCondition()
diff --git a/pkg/scaling/cache/scalers_cache.go b/pkg/scaling/cache/scalers_cache.go
index da3a0d05100..3fbd8133908 100644
--- a/pkg/scaling/cache/scalers_cache.go
+++ b/pkg/scaling/cache/scalers_cache.go
@@ -21,6 +21,7 @@ import (
"fmt"
"time"
+ "github.com/antonmedv/expr/vm"
v2 "k8s.io/api/autoscaling/v2"
"k8s.io/client-go/tools/record"
"k8s.io/metrics/pkg/apis/external_metrics"
@@ -37,6 +38,7 @@ type ScalersCache struct {
Scalers []ScalerBuilder
ScalableObjectGeneration int64
Recorder record.EventRecorder
+ CompiledFormula *vm.Program
}
type ScalerBuilder struct {
diff --git a/pkg/scaling/modifiers/formula.go b/pkg/scaling/modifiers/formula.go
new file mode 100644
index 00000000000..843dc15d206
--- /dev/null
+++ b/pkg/scaling/modifiers/formula.go
@@ -0,0 +1,138 @@
+/*
+Copyright 2021 The KEDA Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// ******************************* DESCRIPTION ****************************** \\
+// modifiers package describes functions that handle scaling modifiers. This
+// file contains main functionality and supporting functions. The parent
+// function is HandleScalingModifiers() that is called from scale_handler.
+// If fallback is active or the struct scalingModifiers in SO is not defined,
+// input metrics are simply returned without change, otherwise apply formula if
+// conditions are met.
+// ************************************************************************** \\
+
+package modifiers
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/antonmedv/expr"
+ "github.com/go-logr/logr"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/metrics/pkg/apis/external_metrics"
+
+ kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
+ "github.com/kedacore/keda/v2/pkg/scaling/cache"
+)
+
+// HandleScalingModifiers is the parent function for scalingModifiers structure.
+// If the structure is defined and conditions are met, apply the formula to
+// manipulate the metrics and return them
+func HandleScalingModifiers(so *kedav1alpha1.ScaledObject, metrics []external_metrics.ExternalMetricValue, metricTriggerList map[string]string, fallbackActive bool, cacheObj *cache.ScalersCache, log logr.Logger) []external_metrics.ExternalMetricValue {
+ var err error
+ // dont manipulate with metrics if fallback is currently active or structure isnt defined
+ if !fallbackActive && so != nil && so.IsUsingModifiers() {
+ sm := so.Spec.Advanced.ScalingModifiers
+
+ // apply formula if defined
+ metrics, err = applyScalingModifiersFormula(sm, metrics, metricTriggerList, cacheObj)
+ if err != nil {
+ log.Error(err, "error applying custom scalingModifiers.Formula")
+ }
+ log.V(1).Info("returned metrics after formula is applied", "metrics", metrics)
+ }
+ return metrics
+}
+
+// ArrayContainsElement determines whether array 'arr' contains element 'el'
+func ArrayContainsElement(el string, arr []string) bool {
+ for _, item := range arr {
+ if strings.EqualFold(item, el) {
+ return true
+ }
+ }
+ return false
+}
+
+// applyScalingModifiersFormula applies formula if formula is defined, otherwise
+// skip
+func applyScalingModifiersFormula(sm kedav1alpha1.ScalingModifiers, metrics []external_metrics.ExternalMetricValue, pairList map[string]string, cacheObj *cache.ScalersCache) ([]external_metrics.ExternalMetricValue, error) {
+ if sm.Formula != "" {
+ metrics, err := calculateScalingModifiersFormula(metrics, cacheObj, pairList)
+ return metrics, err
+ }
+ return metrics, nil
+}
+
+// calculateScalingModifiersFormula creates custom composite metric & calculates
+// custom formula and returns this finalized metric
+func calculateScalingModifiersFormula(list []external_metrics.ExternalMetricValue, cacheObj *cache.ScalersCache, pairList map[string]string) ([]external_metrics.ExternalMetricValue, error) {
+ var ret external_metrics.ExternalMetricValue
+ var out float64
+ ret.MetricName = kedav1alpha1.CompositeMetricName
+ ret.Timestamp = v1.Now()
+
+ // using https://github.com/antonmedv/expr to evaluate formula expression
+ data := make(map[string]float64)
+ for _, v := range list {
+ data[pairList[v.MetricName]] = v.Value.AsApproximateFloat64()
+ }
+
+ if cacheObj.CompiledFormula == nil {
+ return nil, fmt.Errorf("cached compiled formula is nil during its calculation")
+ }
+
+ // run expression with precompiled formula and real data
+ tmp, err := expr.Run(cacheObj.CompiledFormula, data)
+ if err != nil {
+ return nil, fmt.Errorf("error trying to run custom formula: %w", err)
+ }
+
+ // return values to known format for externalMetricValue struct
+ out = tmp.(float64)
+ ret.Value.SetMilli(int64(out * 1000))
+ return []external_metrics.ExternalMetricValue{ret}, nil
+}
+
+// AddPairTriggerAndMetric adds new pair of trigger-metric to the list for
+// scalingModifiers formula list thats needed to map the metric value to
+// trigger name. This is only ran if scalingModifiers.Formula is defined in SO.
+func AddPairTriggerAndMetric(list map[string]string, so *kedav1alpha1.ScaledObject, metric string, trigger string) (map[string]string, error) {
+ if so.Spec.Advanced != nil && so.Spec.Advanced.ScalingModifiers.Formula != "" {
+ if trigger == "" {
+ return list, fmt.Errorf("trigger name not given with compositeScaler for metric %s", metric)
+ }
+
+ triggerHasMetrics := 0
+ // count number of metrics per trigger
+ for _, t := range list {
+ if strings.HasPrefix(t, trigger) {
+ triggerHasMetrics++
+ }
+ }
+
+ // if trigger doesnt have a pair yet
+ if triggerHasMetrics == 0 {
+ list[metric] = trigger
+ } else {
+ // if trigger has a pair add a number
+ list[metric] = fmt.Sprintf("%s%02d", trigger, triggerHasMetrics)
+ }
+
+ return list, nil
+ }
+ return map[string]string{}, nil
+}
diff --git a/pkg/scaling/resolver/scale_resolvers.go b/pkg/scaling/resolver/scale_resolvers.go
index c2a9c39c9a6..f15db5ee5e5 100644
--- a/pkg/scaling/resolver/scale_resolvers.go
+++ b/pkg/scaling/resolver/scale_resolvers.go
@@ -77,6 +77,7 @@ func ResolveScaleTargetPodSpec(ctx context.Context, kubeClient client.Client, sc
// trying to prevent operator crashes, due to some race condition, sometimes obj.Status.ScaleTargetGVKR is nil
// see https://github.com/kedacore/keda/issues/4389
+ // Tracking issue: https://github.com/kedacore/keda/issues/4955
if obj.Status.ScaleTargetGVKR == nil {
scaledObject := &kedav1alpha1.ScaledObject{}
err := kubeClient.Get(ctx, types.NamespacedName{Name: obj.Name, Namespace: obj.Namespace}, scaledObject)
diff --git a/pkg/scaling/scale_handler.go b/pkg/scaling/scale_handler.go
index 4999bd01eb6..db9bc8b2adb 100644
--- a/pkg/scaling/scale_handler.go
+++ b/pkg/scaling/scale_handler.go
@@ -42,6 +42,7 @@ import (
"github.com/kedacore/keda/v2/pkg/scaling/cache"
"github.com/kedacore/keda/v2/pkg/scaling/cache/metricscache"
"github.com/kedacore/keda/v2/pkg/scaling/executor"
+ "github.com/kedacore/keda/v2/pkg/scaling/modifiers"
"github.com/kedacore/keda/v2/pkg/scaling/resolver"
"github.com/kedacore/keda/v2/pkg/scaling/scaledjob"
)
@@ -365,12 +366,20 @@ func (h *scaleHandler) performGetScalersCache(ctx context.Context, key string, s
}
switch obj := scalableObject.(type) {
case *kedav1alpha1.ScaledObject:
+ if obj.Spec.Advanced != nil && obj.Spec.Advanced.ScalingModifiers.Formula != "" {
+ // validate scalingModifiers struct and compile formula
+ program, err := kedav1alpha1.ValidateAndCompileScalingModifiers(obj)
+ if err != nil {
+ log.Error(err, "error validating-compiling scalingModifiers")
+ return nil, err
+ }
+ newCache.CompiledFormula = program
+ }
newCache.ScaledObject = obj
default:
}
h.scalerCaches[key] = newCache
-
return h.scalerCaches[key], nil
}
@@ -402,9 +411,8 @@ func (h *scaleHandler) ClearScalersCache(ctx context.Context, scalableObject int
// GetScaledObjectMetrics returns metrics for specified metric name for a ScaledObject identified by its name and namespace.
// It could either query the metric value directly from the scaler or from a cache, that's being stored for the scaler.
-func (h *scaleHandler) GetScaledObjectMetrics(ctx context.Context, scaledObjectName, scaledObjectNamespace, metricName string) (*external_metrics.ExternalMetricValueList, error) {
+func (h *scaleHandler) GetScaledObjectMetrics(ctx context.Context, scaledObjectName, scaledObjectNamespace, metricsName string) (*external_metrics.ExternalMetricValueList, error) {
logger := log.WithValues("scaledObject.Namespace", scaledObjectNamespace, "scaledObject.Name", scaledObjectName)
-
var matchingMetrics []external_metrics.ExternalMetricValue
cache, err := h.getScalersCacheForScaledObject(ctx, scaledObjectName, scaledObjectNamespace)
@@ -422,10 +430,18 @@ func (h *scaleHandler) GetScaledObjectMetrics(ctx context.Context, scaledObjectN
logger.Error(err, "scaledObject not found in the cache")
return nil, err
}
-
isScalerError := false
scaledObjectIdentifier := scaledObject.GenerateIdentifier()
+ // returns all relevant metrics for current scaler (standard is one metric,
+ // composite scaler gets all external metrics for further computation)
+ metricsArray, err := h.getTrueMetricArray(ctx, metricsName, scaledObject)
+ if err != nil {
+ logger.Error(err, "error getting true metrics array, probably because of invalid cache")
+ }
+ metricTriggerPairList := make(map[string]string)
+ fallbackActive := false
+
// let's check metrics for all scalers in a ScaledObject
scalers, scalerConfigs := cache.GetScalers()
for scalerIndex := 0; scalerIndex < len(scalers); scalerIndex++ {
@@ -441,14 +457,30 @@ func (h *scaleHandler) GetScaledObjectMetrics(ctx context.Context, scaledObjectN
cache.Recorder.Event(scaledObject, corev1.EventTypeWarning, eventreason.KEDAScalerFailed, err.Error())
}
+ if len(metricsArray) == 0 {
+ err = fmt.Errorf("no metrics found getting metricsArray array %s", metricsName)
+ logger.Error(err, "error metricsArray is empty")
+ cache.Recorder.Event(scaledObject, corev1.EventTypeWarning, eventreason.KEDAScalerFailed, err.Error())
+ }
for _, spec := range metricSpecs {
// skip cpu/memory resource scaler
if spec.External == nil {
continue
}
- // Filter only the desired metric
- if strings.EqualFold(spec.External.Metric.Name, metricName) {
+ // Filter only the desired metric or if composite scaler is active,
+ // metricsArray contains all external metrics
+ if modifiers.ArrayContainsElement(spec.External.Metric.Name, metricsArray) {
+ // if compositeScaler is used, override with current metric, otherwise do nothing
+ metricName := spec.External.Metric.Name
+
+ // Pair metric values with its trigger names. This is applied only when
+ // ScalingModifiers.Formula is defined in SO.
+ metricTriggerPairList, err = modifiers.AddPairTriggerAndMetric(metricTriggerPairList, scaledObject, metricName, scalerConfigs[scalerIndex].TriggerName)
+ if err != nil {
+ logger.Error(err, "error pairing triggers & metrics for compositeScaler")
+ }
+
var metrics []external_metrics.ExternalMetricValue
// if cache is defined for this scaler/metric, let's try to hit it first
@@ -472,8 +504,10 @@ func (h *scaleHandler) GetScaledObjectMetrics(ctx context.Context, scaledObjectN
}
// check if we need to set a fallback
- metrics, err = fallback.GetMetricsWithFallback(ctx, h.client, metrics, err, metricName, scaledObject, spec)
-
+ metrics, fallbackApplied, err := fallback.GetMetricsWithFallback(ctx, h.client, metrics, err, metricName, scaledObject, spec)
+ if fallbackApplied {
+ fallbackActive = true
+ }
if err != nil {
isScalerError = true
logger.Error(err, "error getting metric for scaler", "scaler", scalerName)
@@ -500,9 +534,11 @@ func (h *scaleHandler) GetScaledObjectMetrics(ctx context.Context, scaledObjectN
}
if len(matchingMetrics) == 0 {
- return nil, fmt.Errorf("no matching metrics found for " + metricName)
+ return nil, fmt.Errorf("no matching metrics found for " + metricsName)
}
+ // handle scalingModifiers here and simply return the matchingMetrics
+ matchingMetrics = modifiers.HandleScalingModifiers(scaledObject, matchingMetrics, metricTriggerPairList, fallbackActive, cache, logger)
return &external_metrics.ExternalMetricValueList{
Items: matchingMetrics,
}, nil
@@ -681,3 +717,35 @@ func (h *scaleHandler) isScaledJobActive(ctx context.Context, scaledJob *kedav1a
logger.V(1).WithValues("ScaledJob", scaledJob.Name).Info("Checking if ScaleJob Scalers are active", "isActive", isActive, "maxValue", maxFloatValue, "MultipleScalersCalculation", scaledJob.Spec.ScalingStrategy.MultipleScalersCalculation)
return isActive, queueLength, maxValue
}
+
+// getTrueMetricArray is a help function made for composite scaler to determine
+// what metrics should be used. In case of composite scaler (ScalingModifiers struct),
+// all external metrics will be used. Returns all external metrics otherwise it
+// returns the same metric given.
+// TODO: if the bug (mentioned in function below) is fixed this can be moved to
+// 'modifiers/' directory with the rest of the functions
+func (h *scaleHandler) getTrueMetricArray(ctx context.Context, metricName string, so *kedav1alpha1.ScaledObject) ([]string, error) {
+ // if composite scaler is given return all external metrics
+
+ // bug fix for the invalid cache (not loaded properly) and needs to be fetched again
+ // Tracking issue: https://github.com/kedacore/keda/issues/4955
+ if so != nil && so.Spec.Advanced != nil && so.Spec.Advanced.ScalingModifiers.Target != "" {
+ if len(so.Status.ExternalMetricNames) == 0 {
+ scaledObject := &kedav1alpha1.ScaledObject{}
+ err := h.client.Get(ctx, types.NamespacedName{Name: so.Name, Namespace: so.Namespace}, scaledObject)
+ if err != nil {
+ log.Error(err, "failed to get ScaledObject", "name", so.Name, "namespace", so.Namespace)
+ return nil, err
+ }
+ if len(scaledObject.Status.ExternalMetricNames) == 0 {
+ err := fmt.Errorf("failed to get ScaledObject.Status.ExternalMetricNames, probably invalid ScaledObject cache")
+ log.Error(err, "failed to get ScaledObject.Status.ExternalMetricNames, probably invalid ScaledObject cache", "scaledObject.Name", scaledObject.Name, "scaledObject.Namespace", scaledObject.Namespace)
+ return nil, err
+ }
+
+ so = scaledObject
+ }
+ return so.Status.ExternalMetricNames, nil
+ }
+ return []string{metricName}, nil
+}
diff --git a/pkg/scaling/scale_handler_test.go b/pkg/scaling/scale_handler_test.go
index b10a8dab183..eacf593a7cf 100644
--- a/pkg/scaling/scale_handler_test.go
+++ b/pkg/scaling/scale_handler_test.go
@@ -24,6 +24,7 @@ import (
"testing"
"time"
+ "github.com/antonmedv/expr"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
v2 "k8s.io/api/autoscaling/v2"
@@ -43,9 +44,13 @@ import (
"github.com/kedacore/keda/v2/pkg/scaling/cache/metricscache"
)
+const testNamespaceGlobal = "testNamespace"
+const compositeMetricNameGlobal = "composite-metric"
+const testNameGlobal = "testName"
+
func TestGetScaledObjectMetrics_DirectCall(t *testing.T) {
- scaledObjectName := "testName"
- scaledObjectNamespace := "testNamespace"
+ scaledObjectName := testNameGlobal
+ scaledObjectNamespace := testNamespaceGlobal
metricName := "test-metric-name"
longPollingInterval := int32(300)
@@ -616,6 +621,126 @@ func createScaler(ctrl *gomock.Controller, queueLength int64, averageValue int64
return scaler
}
+// -----------------------------------------------------------------------------
+// test for scalingModifiers formula
+// -----------------------------------------------------------------------------
+
+const triggerName1 = "trigger_one"
+const triggerName2 = "trigger_two"
+const metricName1 = "metric_one"
+const metricName2 = "metric_two"
+
+func TestScalingModifiersFormula(t *testing.T) {
+ scaledObjectName := testNameGlobal
+ scaledObjectNamespace := testNamespaceGlobal
+ compositeMetricName := compositeMetricNameGlobal
+
+ ctrl := gomock.NewController(t)
+ recorder := record.NewFakeRecorder(1)
+ mockClient := mock_client.NewMockClient(ctrl)
+ mockExecutor := mock_executor.NewMockScaleExecutor(ctrl)
+ mockStatusWriter := mock_client.NewMockStatusWriter(ctrl)
+
+ metricsSpecs1 := []v2.MetricSpec{createMetricSpec(2, metricName1)}
+ metricsSpecs2 := []v2.MetricSpec{createMetricSpec(5, metricName2)}
+ metricValue1 := scalers.GenerateMetricInMili(metricName1, float64(2))
+ metricValue2 := scalers.GenerateMetricInMili(metricName2, float64(5))
+
+ scaler1 := mock_scalers.NewMockScaler(ctrl)
+ scaler2 := mock_scalers.NewMockScaler(ctrl)
+ // dont use cached metrics
+ scalerConfig1 := scalers.ScalerConfig{TriggerUseCachedMetrics: false, TriggerName: triggerName1, ScalerIndex: 0}
+ scalerConfig2 := scalers.ScalerConfig{TriggerUseCachedMetrics: false, TriggerName: triggerName2, ScalerIndex: 1}
+ factory1 := func() (scalers.Scaler, *scalers.ScalerConfig, error) {
+ return scaler1, &scalerConfig1, nil
+ }
+ factory2 := func() (scalers.Scaler, *scalers.ScalerConfig, error) {
+ return scaler2, &scalerConfig2, nil
+ }
+
+ scaledObject := kedav1alpha1.ScaledObject{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: scaledObjectName,
+ Namespace: scaledObjectNamespace,
+ },
+ Spec: kedav1alpha1.ScaledObjectSpec{
+ ScaleTargetRef: &kedav1alpha1.ScaleTarget{
+ Name: "test",
+ },
+ Advanced: &kedav1alpha1.AdvancedConfig{
+ ScalingModifiers: kedav1alpha1.ScalingModifiers{
+ Target: "2",
+ Formula: fmt.Sprintf("%s + %s", triggerName1, triggerName2),
+ },
+ },
+ Triggers: []kedav1alpha1.ScaleTriggers{
+ {Name: triggerName1, Type: "fake_trig1"},
+ {Name: triggerName2, Type: "fake_trig2"},
+ },
+ },
+ Status: kedav1alpha1.ScaledObjectStatus{
+ ScaleTargetGVKR: &kedav1alpha1.GroupVersionKindResource{
+ Group: "apps",
+ Kind: "Deployment",
+ },
+ ExternalMetricNames: []string{metricName1, metricName2},
+ },
+ }
+
+ // formula is compiled and cached
+ compiledFormula, err := expr.Compile(scaledObject.Spec.Advanced.ScalingModifiers.Formula)
+ assert.Equal(t, err, nil)
+
+ scalerCache := cache.ScalersCache{
+ ScaledObject: &scaledObject,
+ Scalers: []cache.ScalerBuilder{{
+ Scaler: scaler1,
+ ScalerConfig: scalerConfig1,
+ Factory: factory1,
+ },
+ {
+ Scaler: scaler2,
+ ScalerConfig: scalerConfig2,
+ Factory: factory2,
+ },
+ },
+ Recorder: recorder,
+ CompiledFormula: compiledFormula,
+ }
+
+ caches := map[string]*cache.ScalersCache{}
+ caches[scaledObject.GenerateIdentifier()] = &scalerCache
+
+ sh := scaleHandler{
+ client: mockClient,
+ scaleLoopContexts: &sync.Map{},
+ scaleExecutor: mockExecutor,
+ globalHTTPTimeout: time.Duration(1000),
+ recorder: recorder,
+ scalerCaches: caches,
+ scalerCachesLock: &sync.RWMutex{},
+ scaledObjectsMetricCache: metricscache.NewMetricsCache(),
+ }
+
+ mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
+ scaler1.EXPECT().GetMetricSpecForScaling(gomock.Any()).Return(metricsSpecs1)
+ scaler2.EXPECT().GetMetricSpecForScaling(gomock.Any()).Return(metricsSpecs2)
+ scaler1.EXPECT().GetMetricsAndActivity(gomock.Any(), gomock.Any()).Return([]external_metrics.ExternalMetricValue{metricValue1, metricValue2}, true, nil)
+ scaler2.EXPECT().GetMetricsAndActivity(gomock.Any(), gomock.Any()).Return([]external_metrics.ExternalMetricValue{metricValue1, metricValue2}, true, nil)
+ mockExecutor.EXPECT().RequestScale(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
+ sh.checkScalers(context.TODO(), &scaledObject, &sync.RWMutex{})
+
+ mockClient.EXPECT().Status().Return(mockStatusWriter).Times(2)
+ mockStatusWriter.EXPECT().Patch(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2)
+ scaler1.EXPECT().GetMetricSpecForScaling(gomock.Any()).Return(metricsSpecs1)
+ scaler2.EXPECT().GetMetricSpecForScaling(gomock.Any()).Return(metricsSpecs2)
+ scaler1.EXPECT().GetMetricsAndActivity(gomock.Any(), gomock.Any()).Return([]external_metrics.ExternalMetricValue{metricValue1, metricValue2}, true, nil)
+ scaler2.EXPECT().GetMetricsAndActivity(gomock.Any(), gomock.Any()).Return([]external_metrics.ExternalMetricValue{metricValue1, metricValue2}, true, nil)
+ metrics, err := sh.GetScaledObjectMetrics(context.TODO(), scaledObjectName, scaledObjectNamespace, compositeMetricName)
+ assert.Nil(t, err)
+ assert.Equal(t, float64(7), metrics.Items[0].Value.AsApproximateFloat64())
+}
+
// createMetricSpec creates MetricSpec for given metric name and target value.
func createMetricSpec(averageValue int64, metricName string) v2.MetricSpec {
qty := resource.NewQuantity(averageValue, resource.DecimalSI)
diff --git a/tests/internals/scaling_modifiers/scaling_modifiers_test.go b/tests/internals/scaling_modifiers/scaling_modifiers_test.go
new file mode 100644
index 00000000000..ab02f01ea73
--- /dev/null
+++ b/tests/internals/scaling_modifiers/scaling_modifiers_test.go
@@ -0,0 +1,293 @@
+//go:build e2e
+// +build e2e
+
+package scaling_modifiers_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/joho/godotenv"
+ "github.com/stretchr/testify/assert"
+ "k8s.io/client-go/kubernetes"
+
+ . "github.com/kedacore/keda/v2/tests/helper"
+)
+
+const (
+ testName = "scaling-modifiers-test"
+)
+
+// Load environment variables from .env file
+var _ = godotenv.Load("../../.env")
+
+var (
+ namespace = fmt.Sprintf("%s-ns", testName)
+ deploymentName = fmt.Sprintf("%s-deployment", testName)
+ metricsServerDeploymentName = fmt.Sprintf("%s-metrics-server", testName)
+ serviceName = fmt.Sprintf("%s-service", testName)
+ triggerAuthName = fmt.Sprintf("%s-ta", testName)
+ scaledObjectName = fmt.Sprintf("%s-so", testName)
+ secretName = fmt.Sprintf("%s-secret", testName)
+ metricsServerEndpoint = fmt.Sprintf("http://%s.%s.svc.cluster.local:8080/api/value", serviceName, namespace)
+)
+
+type templateData struct {
+ TestNamespace string
+ DeploymentName string
+ ScaledObject string
+ TriggerAuthName string
+ SecretName string
+ ServiceName string
+ MetricsServerDeploymentName string
+ MetricsServerEndpoint string
+ MetricValue int
+}
+
+const (
+ secretTemplate = `apiVersion: v1
+kind: Secret
+metadata:
+ name: {{.SecretName}}
+ namespace: {{.TestNamespace}}
+data:
+ AUTH_PASSWORD: U0VDUkVUCg==
+ AUTH_USERNAME: VVNFUgo=
+`
+
+ triggerAuthenticationTemplate = `apiVersion: keda.sh/v1alpha1
+kind: TriggerAuthentication
+metadata:
+ name: {{.TriggerAuthName}}
+ namespace: {{.TestNamespace}}
+spec:
+ secretTargetRef:
+ - parameter: username
+ name: {{.SecretName}}
+ key: AUTH_USERNAME
+ - parameter: password
+ name: {{.SecretName}}
+ key: AUTH_PASSWORD
+`
+
+ deploymentTemplate = `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ app: {{.DeploymentName}}
+ name: {{.DeploymentName}}
+ namespace: {{.TestNamespace}}
+spec:
+ selector:
+ matchLabels:
+ app: {{.DeploymentName}}
+ replicas: 0
+ template:
+ metadata:
+ labels:
+ app: {{.DeploymentName}}
+ spec:
+ containers:
+ - name: nginx
+ image: nginxinc/nginx-unprivileged
+ ports:
+ - containerPort: 80
+`
+
+ // for metrics-api trigger
+ metricsServerDeploymentTemplate = `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{.MetricsServerDeploymentName}}
+ namespace: {{.TestNamespace}}
+ labels:
+ app: {{.MetricsServerDeploymentName}}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: {{.MetricsServerDeploymentName}}
+ template:
+ metadata:
+ labels:
+ app: {{.MetricsServerDeploymentName}}
+ spec:
+ containers:
+ - name: metrics
+ image: ghcr.io/kedacore/tests-metrics-api
+ ports:
+ - containerPort: 8080
+ envFrom:
+ - secretRef:
+ name: {{.SecretName}}
+ imagePullPolicy: Always
+`
+ soFallbackTemplate = `
+apiVersion: keda.sh/v1alpha1
+kind: ScaledObject
+metadata:
+ name: {{.ScaledObject}}
+ namespace: {{.TestNamespace}}
+ labels:
+ app: {{.DeploymentName}}
+spec:
+ scaleTargetRef:
+ name: {{.DeploymentName}}
+ advanced:
+ horizontalPodAutoscalerConfig:
+ behavior:
+ scaleDown:
+ stabilizationWindowSeconds: 5
+ scalingModifiers:
+ formula: metrics_api + kw_trig
+ target: '2'
+ pollingInterval: 5
+ cooldownPeriod: 5
+ minReplicaCount: 0
+ maxReplicaCount: 10
+ fallback:
+ replicas: 5
+ failureThreshold: 1
+ triggers:
+ - type: metrics-api
+ name: metrics_api
+ metadata:
+ targetValue: "2"
+ url: "{{.MetricsServerEndpoint}}"
+ valueLocation: 'value'
+ method: "query"
+ authenticationRef:
+ name: {{.TriggerAuthName}}
+ - type: kubernetes-workload
+ name: kw_trig
+ metadata:
+ podSelector: pod=workload-test
+ value: '1'
+`
+
+ workloadDeploymentTemplate = `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: depl-workload-base
+ namespace: {{.TestNamespace}}
+ labels:
+ deploy: workload-test
+spec:
+ replicas: 0
+ selector:
+ matchLabels:
+ pod: workload-test
+ template:
+ metadata:
+ labels:
+ pod: workload-test
+ spec:
+ containers:
+ - name: nginx
+ image: 'nginxinc/nginx-unprivileged'`
+
+ updateMetricsTemplate = `
+apiVersion: batch/v1
+kind: Job
+metadata:
+ name: update-ms-value
+ namespace: {{.TestNamespace}}
+spec:
+ ttlSecondsAfterFinished: 0
+ backoffLimit: 4
+ template:
+ spec:
+ containers:
+ - name: job-curl
+ image: curlimages/curl
+ imagePullPolicy: Always
+ command: ["curl", "-X", "POST", "{{.MetricsServerEndpoint}}/{{.MetricValue}}"]
+ restartPolicy: OnFailure
+`
+
+ serviceTemplate = `
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{.ServiceName}}
+ namespace: {{.TestNamespace}}
+spec:
+ selector:
+ app: {{.MetricsServerDeploymentName}}
+ ports:
+ - port: 8080
+ targetPort: 8080
+`
+)
+
+func TestScalingModifiers(t *testing.T) {
+ // setup
+ t.Log("-- setting up ---")
+ kc := GetKubernetesClient(t)
+ data, templates := getTemplateData()
+ CreateKubernetesResources(t, kc, namespace, data, templates)
+
+ testFormula(t, kc, data)
+
+ templates = append(templates, Template{Name: "soFallbackTemplate", Config: soFallbackTemplate})
+ DeleteKubernetesResources(t, namespace, data, templates)
+}
+
+func testFormula(t *testing.T, kc *kubernetes.Clientset, data templateData) {
+ t.Log("--- testFormula ---")
+ // formula simply adds 2 metrics together (3+2=5; target = 2 -> 5/2 replicas should be 3)
+ data.MetricValue = 3
+ KubectlApplyWithTemplate(t, data, "updateMetricsTemplate", updateMetricsTemplate)
+
+ KubectlApplyWithTemplate(t, data, "soFallbackTemplate", soFallbackTemplate)
+ _, err := ExecuteCommand(fmt.Sprintf("kubectl scale deployment/depl-workload-base --replicas=2 -n %s", namespace))
+ assert.NoErrorf(t, err, "cannot scale workload deployment - %s", err)
+
+ assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, "depl-workload-base", namespace, 2, 12, 10),
+ "replica count should be %d after 1 minute", 2)
+ assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, namespace, 3, 12, 10),
+ "replica count should be %d after 2 minutes", 3)
+
+ // apply fallback fallback
+ _, err = ExecuteCommand(fmt.Sprintf("kubectl scale deployment/%s --replicas=0 -n %s", metricsServerDeploymentName, namespace))
+ assert.NoErrorf(t, err, "cannot scale metricsServer deployment - %s", err)
+
+ assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, namespace, 5, 12, 10),
+ "replica count should be %d after 2 minutes", 5)
+
+ // ensure state returns to normal after error resolved and triggers are healthy
+ _, err = ExecuteCommand(fmt.Sprintf("kubectl scale deployment/%s --replicas=1 -n %s", metricsServerDeploymentName, namespace))
+ assert.NoErrorf(t, err, "cannot scale metricsServer deployment - %s", err)
+
+ data.MetricValue = 2
+ KubectlApplyWithTemplate(t, data, "updateMetricsTemplate", updateMetricsTemplate)
+ // 2+2=4; target = 2 -> 4/2 replicas should be 2
+ assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, namespace, 2, 12, 10),
+ "replica count should be %d after 2 minutes", 2)
+}
+
+func getTemplateData() (templateData, []Template) {
+ return templateData{
+ TestNamespace: namespace,
+ DeploymentName: deploymentName,
+ MetricsServerDeploymentName: metricsServerDeploymentName,
+ ServiceName: serviceName,
+ TriggerAuthName: triggerAuthName,
+ ScaledObject: scaledObjectName,
+ SecretName: secretName,
+ MetricsServerEndpoint: metricsServerEndpoint,
+ MetricValue: 0,
+ }, []Template{
+ // basic: scaled deployment, metrics-api trigger server & authentication
+ {Name: "secretTemplate", Config: secretTemplate},
+ {Name: "metricsServerDeploymentTemplate", Config: metricsServerDeploymentTemplate},
+ {Name: "serviceTemplate", Config: serviceTemplate},
+ {Name: "triggerAuthenticationTemplate", Config: triggerAuthenticationTemplate},
+ {Name: "deploymentTemplate", Config: deploymentTemplate},
+ // workload base
+ {Name: "workloadDeploymentTemplate", Config: workloadDeploymentTemplate},
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/.gitignore b/vendor/github.com/antonmedv/expr/.gitignore
new file mode 100644
index 00000000000..b0df3eb4442
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/.gitignore
@@ -0,0 +1,8 @@
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+*.test
+*.out
+*.html
diff --git a/vendor/github.com/antonmedv/expr/LICENSE b/vendor/github.com/antonmedv/expr/LICENSE
new file mode 100644
index 00000000000..7d058f841cb
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 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/antonmedv/expr/README.md b/vendor/github.com/antonmedv/expr/README.md
new file mode 100644
index 00000000000..0e11943d311
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/README.md
@@ -0,0 +1,172 @@
+# Expr
+[](https://github.com/antonmedv/expr/actions/workflows/test.yml)
+[](https://goreportcard.com/report/github.com/antonmedv/expr)
+[](https://godoc.org/github.com/antonmedv/expr)
+[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:expr)
+
+**Expr** is a Go-centric expression language designed to deliver dynamic configurations with unparalleled accuracy, safety, and speed.
+
+
+
+```js
+// Allow only admins and moderators to moderate comments.
+user.Group in ["admin", "moderator"] || user.Id == comment.UserId
+```
+
+```js
+// Ensure all tweets are less than 240 characters.
+all(Tweets, .Size <= 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/antonmedv/expr
+```
+
+## Documentation
+
+* See [Getting Started](https://expr.medv.io/docs/Getting-Started) page for developer documentation.
+* See [Language Definition](https://expr.medv.io/docs/Language-Definition) page to learn the syntax.
+
+## Expr Code Editor
+
+
+
+
+
+Also, I have an embeddable code editor written in JavaScript which allows editing expressions with syntax highlighting and autocomplete based on your types declaration.
+
+[Learn more →](https://antonmedv.github.io/expr/)
+
+## Examples
+
+[Play Online](https://play.golang.org/p/z7T8ytJ1T1d)
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/antonmedv/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://play.golang.org/p/4S4brsIvU4i)
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/antonmedv/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.
+* [Wish.com](https://www.wish.com) employs Expr in its decision-making rule engine for the Wish Assistant.
+* [Argo](https://argoproj.github.io) integrates Expr into Argo Rollouts and Argo Workflows for Kubernetes.
+* [Crowdsec](https://crowdsec.net) incorporates Expr into its security automation tool.
+* [FACEIT](https://www.faceit.com) uses Expr to enhance customization of its eSports matchmaking algorithm.
+* [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.
+* [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.
+* [CoreDNS](https://coredns.io) uses Expr in CoreDNS, which is a DNS server.
+* [Chaos Mesh](https://chaos-mesh.org) incorporates Expr into Chaos Mesh, a cloud-native Chaos Engineering platform.
+* [Milvus](https://milvus.io) integrates Expr into Milvus, an open-source vector database.
+* [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.
+
+[Add your company too](https://github.com/antonmedv/expr/edit/master/README.md)
+
+## License
+
+[MIT](https://github.com/antonmedv/expr/blob/master/LICENSE)
diff --git a/vendor/github.com/antonmedv/expr/SECURITY.md b/vendor/github.com/antonmedv/expr/SECURITY.md
new file mode 100644
index 00000000000..6e343ee3675
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/SECURITY.md
@@ -0,0 +1,25 @@
+# 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.15 | :white_check_mark: |
+| 1.14 | :white_check_mark: |
+| 1.13 | :white_check_mark: |
+| 1.12 | :white_check_mark: |
+| < 1.12 | :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/antonmedv/expr/ast/dump.go b/vendor/github.com/antonmedv/expr/ast/dump.go
new file mode 100644
index 00000000000..56bc7dbe2e3
--- /dev/null
+++ b/vendor/github.com/antonmedv/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/antonmedv/expr/ast/func.go b/vendor/github.com/antonmedv/expr/ast/func.go
new file mode 100644
index 00000000000..a873c35e994
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/ast/func.go
@@ -0,0 +1,12 @@
+package ast
+
+import "reflect"
+
+type Function struct {
+ Name string
+ Func func(args ...any) (any, error)
+ Fast func(arg any) any
+ Types []reflect.Type
+ Validate func(args []reflect.Type) (reflect.Type, error)
+ Predicate bool
+}
diff --git a/vendor/github.com/antonmedv/expr/ast/node.go b/vendor/github.com/antonmedv/expr/ast/node.go
new file mode 100644
index 00000000000..d037926c19e
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/ast/node.go
@@ -0,0 +1,177 @@
+package ast
+
+import (
+ "reflect"
+ "regexp"
+
+ "github.com/antonmedv/expr/file"
+)
+
+// Node represents items of abstract syntax tree.
+type Node interface {
+ Location() file.Location
+ SetLocation(file.Location)
+ Type() reflect.Type
+ SetType(reflect.Type)
+ String() string
+}
+
+func Patch(node *Node, newNode Node) {
+ newNode.SetType((*node).Type())
+ newNode.SetLocation((*node).Location())
+ *node = newNode
+}
+
+type base struct {
+ loc file.Location
+ nodeType reflect.Type
+}
+
+func (n *base) Location() file.Location {
+ return n.loc
+}
+
+func (n *base) SetLocation(loc file.Location) {
+ n.loc = loc
+}
+
+func (n *base) Type() reflect.Type {
+ return n.nodeType
+}
+
+func (n *base) SetType(t reflect.Type) {
+ n.nodeType = t
+}
+
+type NilNode struct {
+ base
+}
+
+type IdentifierNode struct {
+ base
+ Value string
+ FieldIndex []int
+ Method bool // true if method, false if field
+ MethodIndex int // index of method, set only if Method is true
+}
+
+type IntegerNode struct {
+ base
+ Value int
+}
+
+type FloatNode struct {
+ base
+ Value float64
+}
+
+type BoolNode struct {
+ base
+ Value bool
+}
+
+type StringNode struct {
+ base
+ Value string
+}
+
+type ConstantNode struct {
+ base
+ Value any
+}
+
+type UnaryNode struct {
+ base
+ Operator string
+ Node Node
+}
+
+type BinaryNode struct {
+ base
+ Regexp *regexp.Regexp
+ Operator string
+ Left Node
+ Right Node
+}
+
+type ChainNode struct {
+ base
+ Node Node
+}
+
+type MemberNode struct {
+ base
+ Node Node
+ Property Node
+ Name string // Name of the filed or method. Used for error reporting.
+ Optional bool
+ FieldIndex []int
+
+ // TODO: Combine Method and MethodIndex into a single MethodIndex field of &int type.
+ Method bool
+ MethodIndex int
+}
+
+type SliceNode struct {
+ base
+ Node Node
+ From Node
+ To Node
+}
+
+type CallNode struct {
+ base
+ Callee Node
+ Arguments []Node
+ Typed int
+ Fast bool
+ Func *Function
+}
+
+type BuiltinNode struct {
+ base
+ Name string
+ Arguments []Node
+ Throws bool
+ Map Node
+}
+
+type ClosureNode struct {
+ base
+ Node Node
+}
+
+type PointerNode struct {
+ base
+ Name string
+}
+
+type ConditionalNode struct {
+ base
+ Cond Node
+ Exp1 Node
+ Exp2 Node
+}
+
+type VariableDeclaratorNode struct {
+ base
+ Name string
+ Value Node
+ Expr Node
+}
+
+type ArrayNode struct {
+ base
+ Nodes []Node
+}
+
+type MapNode struct {
+ base
+ Pairs []Node
+}
+
+type PairNode struct {
+ base
+ Key Node
+ Value Node
+}
diff --git a/vendor/github.com/antonmedv/expr/ast/print.go b/vendor/github.com/antonmedv/expr/ast/print.go
new file mode 100644
index 00000000000..dd9e0db0f95
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/ast/print.go
@@ -0,0 +1,175 @@
+package ast
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/antonmedv/expr/parser/operator"
+ "github.com/antonmedv/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 *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 := ""
+ if n.Operator == "not" {
+ op = fmt.Sprintf("%s ", n.Operator)
+ } else {
+ op = fmt.Sprintf("%s", n.Operator)
+ }
+ if _, ok := n.Node.(*BinaryNode); ok {
+ return fmt.Sprintf("%s(%s)", op, n.Node.String())
+ }
+ return fmt.Sprintf("%s%s", op, n.Node.String())
+}
+
+func (n *BinaryNode) String() string {
+ var left, right string
+ if b, ok := n.Left.(*BinaryNode); ok && operator.Less(b.Operator, n.Operator) {
+ left = fmt.Sprintf("(%s)", n.Left.String())
+ } else {
+ left = n.Left.String()
+ }
+ if b, ok := n.Right.(*BinaryNode); ok && operator.Less(b.Operator, n.Operator) {
+ right = fmt.Sprintf("(%s)", n.Right.String())
+ } else {
+ right = n.Right.String()
+ }
+ return fmt.Sprintf("%s %s %s", left, n.Operator, right)
+}
+
+func (n *ChainNode) String() string {
+ return n.Node.String()
+}
+
+func (n *MemberNode) String() string {
+ if n.Optional {
+ if str, ok := n.Property.(*StringNode); ok && utils.IsValidIdentifier(str.Value) {
+ return fmt.Sprintf("%s?.%s", n.Node.String(), str.Value)
+ } else {
+ return fmt.Sprintf("get(%s, %s)", n.Node.String(), 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", n.Node.String(), str.Value)
+ }
+ return fmt.Sprintf("%s[%s]", n.Node.String(), 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 *ClosureNode) String() string {
+ return n.Node.String()
+}
+
+func (n *PointerNode) String() string {
+ return "#"
+}
+
+func (n *VariableDeclaratorNode) String() string {
+ return fmt.Sprintf("let %s = %s; %s", n.Name, n.Value.String(), n.Expr.String())
+}
+
+func (n *ConditionalNode) String() string {
+ 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 {
+ return fmt.Sprintf("%s: %s", n.Key.String(), n.Value.String())
+}
diff --git a/vendor/github.com/antonmedv/expr/ast/visitor.go b/vendor/github.com/antonmedv/expr/ast/visitor.go
new file mode 100644
index 00000000000..287a7558967
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/ast/visitor.go
@@ -0,0 +1,71 @@
+package ast
+
+import "fmt"
+
+type Visitor interface {
+ Visit(node *Node)
+}
+
+func Walk(node *Node, v Visitor) {
+ switch n := (*node).(type) {
+ case *NilNode:
+ case *IdentifierNode:
+ case *IntegerNode:
+ case *FloatNode:
+ case *BoolNode:
+ case *StringNode:
+ 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 *ClosureNode:
+ Walk(&n.Node, v)
+ case *PointerNode:
+ case *VariableDeclaratorNode:
+ Walk(&n.Value, v)
+ Walk(&n.Expr, 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/antonmedv/expr/builtin/builtin.go b/vendor/github.com/antonmedv/expr/builtin/builtin.go
new file mode 100644
index 00000000000..d7248a86791
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/builtin/builtin.go
@@ -0,0 +1,933 @@
+package builtin
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/antonmedv/expr/ast"
+ "github.com/antonmedv/expr/vm/runtime"
+)
+
+var (
+ Index map[string]int
+ Names []string
+)
+
+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 = []*ast.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: "groupBy",
+ Predicate: true,
+ Types: types(new(func([]any, func(any) any) map[any][]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: "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",
+ Func: func(args ...any) (any, error) {
+ n := runtime.ToInt(args[1])
+ if n > 1e6 {
+ panic("memory budget exceeded")
+ }
+ return strings.Repeat(args[0].(string), 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: Max,
+ Validate: func(args []reflect.Type) (reflect.Type, error) {
+ if len(args) == 0 {
+ return anyType, fmt.Errorf("not enough arguments to call max")
+ }
+ for _, arg := range args {
+ switch kind(arg) {
+ case reflect.Interface:
+ 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 max (type %s)", arg)
+ }
+ }
+ return args[0], nil
+ },
+ },
+ {
+ Name: "min",
+ Func: Min,
+ Validate: func(args []reflect.Type) (reflect.Type, error) {
+ if len(args) == 0 {
+ return anyType, fmt.Errorf("not enough arguments to call min")
+ }
+ for _, arg := range args {
+ switch kind(arg) {
+ case reflect.Interface:
+ 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 min (type %s)", arg)
+ }
+ }
+ return args[0], nil
+ },
+ },
+ {
+ Name: "sum",
+ 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 sum %s", v.Kind())
+ }
+ sum := int64(0)
+ i := 0
+ for ; i < v.Len(); i++ {
+ it := deref(v.Index(i))
+ if it.CanInt() {
+ sum += it.Int()
+ } else if it.CanFloat() {
+ goto float
+ } else {
+ return nil, fmt.Errorf("cannot sum %s", it.Kind())
+ }
+ }
+ return int(sum), nil
+ float:
+ fSum := float64(sum)
+ for ; i < v.Len(); i++ {
+ it := deref(v.Index(i))
+ if it.CanInt() {
+ fSum += float64(it.Int())
+ } else if it.CanFloat() {
+ fSum += it.Float()
+ } else {
+ return nil, fmt.Errorf("cannot sum %s", it.Kind())
+ }
+ }
+ return fSum, 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:
+ default:
+ return anyType, fmt.Errorf("cannot sum %s", args[0])
+ }
+ return anyType, nil
+ },
+ },
+ {
+ Name: "mean",
+ 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 mean %s", v.Kind())
+ }
+ if v.Len() == 0 {
+ return 0.0, nil
+ }
+ sum := float64(0)
+ i := 0
+ for ; i < v.Len(); i++ {
+ it := deref(v.Index(i))
+ if it.CanInt() {
+ sum += float64(it.Int())
+ } else if it.CanFloat() {
+ sum += it.Float()
+ } else {
+ return nil, fmt.Errorf("cannot mean %s", it.Kind())
+ }
+ }
+ return sum / float64(i), 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:
+ default:
+ return anyType, fmt.Errorf("cannot avg %s", args[0])
+ }
+ return floatType, nil
+ },
+ },
+ {
+ Name: "median",
+ 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 median %s", v.Kind())
+ }
+ if v.Len() == 0 {
+ return 0.0, nil
+ }
+ s := make([]float64, v.Len())
+ for i := 0; i < v.Len(); i++ {
+ it := deref(v.Index(i))
+ if it.CanInt() {
+ s[i] = float64(it.Int())
+ } else if it.CanFloat() {
+ s[i] = it.Float()
+ } else {
+ return nil, fmt.Errorf("cannot median %s", it.Kind())
+ }
+ }
+ sort.Float64s(s)
+ if len(s)%2 == 0 {
+ return (s[len(s)/2-1] + s[len(s)/2]) / 2, nil
+ }
+ return s[len(s)/2], 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:
+ default:
+ return anyType, fmt.Errorf("cannot median %s", args[0])
+ }
+ return floatType, nil
+ },
+ },
+ {
+ 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) {
+ return time.Now(), nil
+ },
+ Types: types(new(func() time.Time)),
+ },
+ {
+ 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) {
+ date := args[0].(string)
+ if len(args) == 2 {
+ layout := args[1].(string)
+ 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 {
+ t, err := time.Parse(layout, date)
+ if err == nil {
+ return t, nil
+ }
+ }
+ return nil, fmt.Errorf("invalid date %s", date)
+ },
+ Types: types(
+ new(func(string) time.Time),
+ new(func(string, string) time.Time),
+ new(func(string, string, string) time.Time),
+ ),
+ },
+ {
+ 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: func(args ...any) (out any, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ return
+ }
+ }()
+ return runtime.Fetch(args[0], args[1]), nil
+ },
+ },
+ {
+ 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())
+ }
+ if n.Int() > int64(v.Len()) {
+ return args[0], nil
+ }
+ return v.Slice(0, int(n.Int())).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(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: "sort",
+ Func: func(args ...any) (any, error) {
+ if len(args) != 1 && len(args) != 2 {
+ return nil, fmt.Errorf("invalid number of arguments (expected 1 or 2, got %d)", len(args))
+ }
+
+ v := reflect.ValueOf(args[0])
+ if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
+ return nil, fmt.Errorf("cannot sort %s", v.Kind())
+ }
+
+ orderBy := OrderBy{}
+ if len(args) == 2 {
+ dir, err := ascOrDesc(args[1])
+ if err != nil {
+ return nil, err
+ }
+ orderBy.Desc = dir
+ }
+
+ sortable, err := copyArray(v, orderBy)
+ if err != nil {
+ return nil, err
+ }
+ sort.Sort(sortable)
+ return sortable.Array, nil
+ },
+ Validate: func(args []reflect.Type) (reflect.Type, error) {
+ if len(args) != 1 && len(args) != 2 {
+ return anyType, fmt.Errorf("invalid number of arguments (expected 1 or 2, got %d)", len(args))
+ }
+ switch kind(args[0]) {
+ case reflect.Interface, reflect.Slice, reflect.Array:
+ default:
+ return anyType, fmt.Errorf("cannot sort %s", args[0])
+ }
+ if len(args) == 2 {
+ switch kind(args[1]) {
+ case reflect.String, reflect.Interface:
+ default:
+ return anyType, fmt.Errorf("invalid argument for sort (expected string, got %s)", args[1])
+ }
+ }
+ return arrayType, nil
+ },
+ },
+ {
+ Name: "sortBy",
+ Func: func(args ...any) (any, error) {
+ if len(args) != 2 && len(args) != 3 {
+ return nil, fmt.Errorf("invalid number of arguments (expected 2 or 3, got %d)", len(args))
+ }
+
+ v := reflect.ValueOf(args[0])
+ if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
+ return nil, fmt.Errorf("cannot sort %s", v.Kind())
+ }
+
+ orderBy := OrderBy{}
+
+ field, ok := args[1].(string)
+ if !ok {
+ return nil, fmt.Errorf("invalid argument for sort (expected string, got %s)", reflect.TypeOf(args[1]))
+ }
+ orderBy.Field = field
+
+ if len(args) == 3 {
+ dir, err := ascOrDesc(args[2])
+ if err != nil {
+ return nil, err
+ }
+ orderBy.Desc = dir
+ }
+
+ sortable, err := copyArray(v, orderBy)
+ if err != nil {
+ return nil, err
+ }
+ sort.Sort(sortable)
+ return sortable.Array, nil
+ },
+ Validate: func(args []reflect.Type) (reflect.Type, error) {
+ if len(args) != 2 && len(args) != 3 {
+ return anyType, fmt.Errorf("invalid number of arguments (expected 2 or 3, got %d)", len(args))
+ }
+ switch kind(args[0]) {
+ case reflect.Interface, reflect.Slice, reflect.Array:
+ default:
+ return anyType, fmt.Errorf("cannot sort %s", args[0])
+ }
+ switch kind(args[1]) {
+ case reflect.String, reflect.Interface:
+ default:
+ return anyType, fmt.Errorf("invalid argument for sort (expected string, got %s)", args[1])
+ }
+ if len(args) == 3 {
+ switch kind(args[2]) {
+ case reflect.String, reflect.Interface:
+ default:
+ return anyType, fmt.Errorf("invalid argument for sort (expected string, got %s)", args[1])
+ }
+ }
+ return arrayType, nil
+ },
+ },
+}
diff --git a/vendor/github.com/antonmedv/expr/builtin/func.go b/vendor/github.com/antonmedv/expr/builtin/func.go
new file mode 100644
index 00000000000..7c042c6d28c
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/builtin/func.go
@@ -0,0 +1,237 @@
+package builtin
+
+import (
+ "fmt"
+ "reflect"
+ "strconv"
+
+ "github.com/antonmedv/expr/vm/runtime"
+)
+
+func Len(x any) any {
+ v := reflect.ValueOf(x)
+ 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)", x))
+ }
+}
+
+func Type(arg any) any {
+ if arg == nil {
+ return "nil"
+ }
+ v := reflect.ValueOf(arg)
+ for {
+ if v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ } else if v.Kind() == reflect.Interface {
+ v = v.Elem()
+ } else {
+ break
+ }
+ }
+ 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"
+ }
+ return "unknown"
+}
+
+func Abs(x any) any {
+ switch x.(type) {
+ case float32:
+ if x.(float32) < 0 {
+ return -x.(float32)
+ } else {
+ return x
+ }
+ case float64:
+ if x.(float64) < 0 {
+ return -x.(float64)
+ } else {
+ return x
+ }
+ case int:
+ if x.(int) < 0 {
+ return -x.(int)
+ } else {
+ return x
+ }
+ case int8:
+ if x.(int8) < 0 {
+ return -x.(int8)
+ } else {
+ return x
+ }
+ case int16:
+ if x.(int16) < 0 {
+ return -x.(int16)
+ } else {
+ return x
+ }
+ case int32:
+ if x.(int32) < 0 {
+ return -x.(int32)
+ } else {
+ return x
+ }
+ case int64:
+ if x.(int64) < 0 {
+ return -x.(int64)
+ } else {
+ return x
+ }
+ case uint:
+ if x.(uint) < 0 {
+ return -x.(uint)
+ } else {
+ return x
+ }
+ case uint8:
+ if x.(uint8) < 0 {
+ return -x.(uint8)
+ } else {
+ return x
+ }
+ case uint16:
+ if x.(uint16) < 0 {
+ return -x.(uint16)
+ } else {
+ return x
+ }
+ case uint32:
+ if x.(uint32) < 0 {
+ return -x.(uint32)
+ } else {
+ return x
+ }
+ case uint64:
+ if x.(uint64) < 0 {
+ return -x.(uint64)
+ } else {
+ return x
+ }
+ }
+ panic(fmt.Sprintf("invalid argument for abs (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:
+ 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 Max(args ...any) (any, error) {
+ var max any
+ for _, arg := range args {
+ if max == nil || runtime.Less(max, arg) {
+ max = arg
+ }
+ }
+ return max, nil
+}
+
+func Min(args ...any) (any, error) {
+ var min any
+ for _, arg := range args {
+ if min == nil || runtime.More(min, arg) {
+ min = arg
+ }
+ }
+ return min, nil
+}
diff --git a/vendor/github.com/antonmedv/expr/builtin/sort.go b/vendor/github.com/antonmedv/expr/builtin/sort.go
new file mode 100644
index 00000000000..31e302c7272
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/builtin/sort.go
@@ -0,0 +1,91 @@
+package builtin
+
+import (
+ "fmt"
+ "reflect"
+)
+
+type Sortable struct {
+ Array []any
+ Values []reflect.Value
+ OrderBy
+}
+
+type OrderBy struct {
+ Field string
+ Desc bool
+}
+
+func (s *Sortable) Len() int {
+ return len(s.Array)
+}
+
+func (s *Sortable) 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 *Sortable) Less(i, j int) bool {
+ a, b := s.Values[i], s.Values[j]
+ switch a.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if s.Desc {
+ return a.Int() > b.Int()
+ }
+ return a.Int() < b.Int()
+ case reflect.String:
+ if s.Desc {
+ return a.String() > b.String()
+ }
+ return a.String() < b.String()
+ default:
+ panic(fmt.Sprintf("sort: unsupported type %s", a.Kind()))
+ }
+}
+
+func copyArray(v reflect.Value, orderBy OrderBy) (*Sortable, error) {
+ s := &Sortable{
+ Array: make([]any, v.Len()),
+ Values: make([]reflect.Value, v.Len()),
+ OrderBy: orderBy,
+ }
+ var prev reflect.Value
+ for i := 0; i < s.Len(); i++ {
+ elem := deref(v.Index(i))
+ var value reflect.Value
+ switch elem.Kind() {
+ case reflect.Struct:
+ value = elem.FieldByName(s.Field)
+ case reflect.Map:
+ value = elem.MapIndex(reflect.ValueOf(s.Field))
+ default:
+ value = elem
+ }
+ value = deref(value)
+
+ s.Array[i] = elem.Interface()
+ s.Values[i] = value
+
+ if i == 0 {
+ prev = value
+ } else if value.Type() != prev.Type() {
+ return nil, fmt.Errorf("cannot sort array of different types (%s and %s)", value.Type(), prev.Type())
+ }
+ }
+ return s, nil
+}
+
+func ascOrDesc(arg any) (bool, error) {
+ dir, ok := arg.(string)
+ if !ok {
+ return false, fmt.Errorf("invalid argument for sort (expected string, got %s)", reflect.TypeOf(arg))
+ }
+ switch dir {
+ case "desc":
+ return true, nil
+ case "asc":
+ return false, nil
+ default:
+ return false, fmt.Errorf(`invalid argument for sort (expected "asc" or "desc", got %q)`, dir)
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/builtin/utils.go b/vendor/github.com/antonmedv/expr/builtin/utils.go
new file mode 100644
index 00000000000..eff8fdcd1d8
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/builtin/utils.go
@@ -0,0 +1,65 @@
+package builtin
+
+import (
+ "fmt"
+ "reflect"
+)
+
+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{})
+)
+
+func kind(t reflect.Type) reflect.Kind {
+ if t == nil {
+ return reflect.Invalid
+ }
+ 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 deref(v reflect.Value) reflect.Value {
+ if v.Kind() == reflect.Interface {
+ if v.IsNil() {
+ return v
+ }
+ v = v.Elem()
+ }
+
+loop:
+ for v.Kind() == reflect.Ptr {
+ if v.IsNil() {
+ return v
+ }
+ indirect := reflect.Indirect(v)
+ switch indirect.Kind() {
+ case reflect.Struct, reflect.Map, reflect.Array, reflect.Slice:
+ break loop
+ default:
+ v = v.Elem()
+ }
+ }
+
+ if v.IsValid() {
+ return v
+ }
+
+ panic(fmt.Sprintf("cannot deref %s", v))
+}
diff --git a/vendor/github.com/antonmedv/expr/checker/checker.go b/vendor/github.com/antonmedv/expr/checker/checker.go
new file mode 100644
index 00000000000..da46b6f9f87
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/checker/checker.go
@@ -0,0 +1,1150 @@
+package checker
+
+import (
+ "fmt"
+ "reflect"
+ "regexp"
+
+ "github.com/antonmedv/expr/ast"
+ "github.com/antonmedv/expr/builtin"
+ "github.com/antonmedv/expr/conf"
+ "github.com/antonmedv/expr/file"
+ "github.com/antonmedv/expr/parser"
+ "github.com/antonmedv/expr/vm"
+)
+
+func Check(tree *parser.Tree, config *conf.Config) (t reflect.Type, err error) {
+ if config == nil {
+ config = conf.New(nil)
+ }
+
+ v := &checker{config: config}
+
+ t, _ = v.visit(tree.Node)
+
+ if v.err != nil {
+ return t, v.err.Bind(tree.Source)
+ }
+
+ if v.config.Expect != reflect.Invalid {
+ if v.config.ExpectAny {
+ if isAny(t) {
+ return t, nil
+ }
+ }
+
+ switch v.config.Expect {
+ case reflect.Int, reflect.Int64, reflect.Float64:
+ if !isNumber(t) {
+ return nil, fmt.Errorf("expected %v, but got %v", v.config.Expect, t)
+ }
+ default:
+ if t != nil {
+ if t.Kind() == v.config.Expect {
+ return t, nil
+ }
+ }
+ return nil, fmt.Errorf("expected %v, but got %v", v.config.Expect, t)
+ }
+ }
+
+ return t, nil
+}
+
+type checker struct {
+ config *conf.Config
+ predicateScopes []predicateScope
+ varScopes []varScope
+ parents []ast.Node
+ err *file.Error
+}
+
+type predicateScope struct {
+ vtype reflect.Type
+ vars map[string]reflect.Type
+}
+
+type varScope struct {
+ name string
+ vtype reflect.Type
+ info info
+}
+
+type info struct {
+ method bool
+ fn *ast.Function
+
+ // elem is element type of array or map.
+ // Arrays created with type []any, but
+ // we would like to detect expressions
+ // like `42 in ["a"]` as invalid.
+ elem reflect.Type
+}
+
+func (v *checker) visit(node ast.Node) (reflect.Type, info) {
+ var t reflect.Type
+ var i info
+ v.parents = append(v.parents, node)
+ switch n := node.(type) {
+ case *ast.NilNode:
+ t, i = v.NilNode(n)
+ case *ast.IdentifierNode:
+ t, i = v.IdentifierNode(n)
+ case *ast.IntegerNode:
+ t, i = v.IntegerNode(n)
+ case *ast.FloatNode:
+ t, i = v.FloatNode(n)
+ case *ast.BoolNode:
+ t, i = v.BoolNode(n)
+ case *ast.StringNode:
+ t, i = v.StringNode(n)
+ case *ast.ConstantNode:
+ t, i = v.ConstantNode(n)
+ case *ast.UnaryNode:
+ t, i = v.UnaryNode(n)
+ case *ast.BinaryNode:
+ t, i = v.BinaryNode(n)
+ case *ast.ChainNode:
+ t, i = v.ChainNode(n)
+ case *ast.MemberNode:
+ t, i = v.MemberNode(n)
+ case *ast.SliceNode:
+ t, i = v.SliceNode(n)
+ case *ast.CallNode:
+ t, i = v.CallNode(n)
+ case *ast.BuiltinNode:
+ t, i = v.BuiltinNode(n)
+ case *ast.ClosureNode:
+ t, i = v.ClosureNode(n)
+ case *ast.PointerNode:
+ t, i = v.PointerNode(n)
+ case *ast.VariableDeclaratorNode:
+ t, i = v.VariableDeclaratorNode(n)
+ case *ast.ConditionalNode:
+ t, i = v.ConditionalNode(n)
+ case *ast.ArrayNode:
+ t, i = v.ArrayNode(n)
+ case *ast.MapNode:
+ t, i = v.MapNode(n)
+ case *ast.PairNode:
+ t, i = v.PairNode(n)
+ default:
+ panic(fmt.Sprintf("undefined node type (%T)", node))
+ }
+ v.parents = v.parents[:len(v.parents)-1]
+ node.SetType(t)
+ return t, i
+}
+
+func (v *checker) error(node ast.Node, format string, args ...any) (reflect.Type, info) {
+ if v.err == nil { // show first error
+ v.err = &file.Error{
+ Location: node.Location(),
+ Message: fmt.Sprintf(format, args...),
+ }
+ }
+ return anyType, info{} // interface represent undefined type
+}
+
+func (v *checker) NilNode(*ast.NilNode) (reflect.Type, info) {
+ return nilType, info{}
+}
+
+func (v *checker) IdentifierNode(node *ast.IdentifierNode) (reflect.Type, info) {
+ if s, ok := v.lookupVariable(node.Value); ok {
+ return s.vtype, s.info
+ }
+ if node.Value == "$env" {
+ return mapType, info{}
+ }
+ if fn, ok := v.config.Builtins[node.Value]; ok {
+ return functionType, info{fn: fn}
+ }
+ if fn, ok := v.config.Functions[node.Value]; ok {
+ return functionType, info{fn: fn}
+ }
+ if t, ok := v.config.Types[node.Value]; ok {
+ if t.Ambiguous {
+ return v.error(node, "ambiguous identifier %v", node.Value)
+ }
+ node.Method = t.Method
+ node.MethodIndex = t.MethodIndex
+ node.FieldIndex = t.FieldIndex
+ return t.Type, info{method: t.Method}
+ }
+ if v.config.Strict {
+ return v.error(node, "unknown name %v", node.Value)
+ }
+ if v.config.DefaultType != nil {
+ return v.config.DefaultType, info{}
+ }
+ return anyType, info{}
+}
+
+func (v *checker) IntegerNode(*ast.IntegerNode) (reflect.Type, info) {
+ return integerType, info{}
+}
+
+func (v *checker) FloatNode(*ast.FloatNode) (reflect.Type, info) {
+ return floatType, info{}
+}
+
+func (v *checker) BoolNode(*ast.BoolNode) (reflect.Type, info) {
+ return boolType, info{}
+}
+
+func (v *checker) StringNode(*ast.StringNode) (reflect.Type, info) {
+ return stringType, info{}
+}
+
+func (v *checker) ConstantNode(node *ast.ConstantNode) (reflect.Type, info) {
+ return reflect.TypeOf(node.Value), info{}
+}
+
+func (v *checker) UnaryNode(node *ast.UnaryNode) (reflect.Type, info) {
+ t, _ := v.visit(node.Node)
+
+ t = deref(t)
+
+ switch node.Operator {
+
+ case "!", "not":
+ if isBool(t) {
+ return boolType, info{}
+ }
+ if isAny(t) {
+ return boolType, info{}
+ }
+
+ case "+", "-":
+ if isNumber(t) {
+ return t, info{}
+ }
+ if isAny(t) {
+ return anyType, info{}
+ }
+
+ default:
+ return v.error(node, "unknown operator (%v)", node.Operator)
+ }
+
+ return v.error(node, `invalid operation: %v (mismatched type %v)`, node.Operator, t)
+}
+
+func (v *checker) BinaryNode(node *ast.BinaryNode) (reflect.Type, info) {
+ l, _ := v.visit(node.Left)
+ r, ri := v.visit(node.Right)
+
+ l = deref(l)
+ r = deref(r)
+
+ // check operator overloading
+ if fns, ok := v.config.Operators[node.Operator]; ok {
+ t, _, ok := conf.FindSuitableOperatorOverload(fns, v.config.Types, l, r)
+ if ok {
+ return t, info{}
+ }
+ }
+
+ switch node.Operator {
+ case "==", "!=":
+ if isComparable(l, r) {
+ return boolType, info{}
+ }
+
+ case "or", "||", "and", "&&":
+ if isBool(l) && isBool(r) {
+ return boolType, info{}
+ }
+ if or(l, r, isBool) {
+ return boolType, info{}
+ }
+
+ case "<", ">", ">=", "<=":
+ if isNumber(l) && isNumber(r) {
+ return boolType, info{}
+ }
+ if isString(l) && isString(r) {
+ return boolType, info{}
+ }
+ if isTime(l) && isTime(r) {
+ return boolType, info{}
+ }
+ if or(l, r, isNumber, isString, isTime) {
+ return boolType, info{}
+ }
+
+ case "-":
+ if isNumber(l) && isNumber(r) {
+ return combined(l, r), info{}
+ }
+ if isTime(l) && isTime(r) {
+ return durationType, info{}
+ }
+ if isTime(l) && isDuration(r) {
+ return timeType, info{}
+ }
+ if or(l, r, isNumber, isTime) {
+ return anyType, info{}
+ }
+
+ case "*":
+ if isNumber(l) && isNumber(r) {
+ return combined(l, r), info{}
+ }
+ if or(l, r, isNumber) {
+ return anyType, info{}
+ }
+
+ case "/":
+ if isNumber(l) && isNumber(r) {
+ return floatType, info{}
+ }
+ if or(l, r, isNumber) {
+ return floatType, info{}
+ }
+
+ case "**", "^":
+ if isNumber(l) && isNumber(r) {
+ return floatType, info{}
+ }
+ if or(l, r, isNumber) {
+ return floatType, info{}
+ }
+
+ case "%":
+ if isInteger(l) && isInteger(r) {
+ return combined(l, r), info{}
+ }
+ if or(l, r, isInteger) {
+ return anyType, info{}
+ }
+
+ case "+":
+ if isNumber(l) && isNumber(r) {
+ return combined(l, r), info{}
+ }
+ if isString(l) && isString(r) {
+ return stringType, info{}
+ }
+ if isTime(l) && isDuration(r) {
+ return timeType, info{}
+ }
+ if isDuration(l) && isTime(r) {
+ return timeType, info{}
+ }
+ if or(l, r, isNumber, isString, isTime, isDuration) {
+ return anyType, info{}
+ }
+
+ case "in":
+ if (isString(l) || isAny(l)) && isStruct(r) {
+ return boolType, info{}
+ }
+ if isMap(r) {
+ if l == nil { // It is possible to compare with nil.
+ return boolType, info{}
+ }
+ if !isAny(l) && !l.AssignableTo(r.Key()) {
+ return v.error(node, "cannot use %v as type %v in map key", l, r.Key())
+ }
+ return boolType, info{}
+ }
+ if isArray(r) {
+ if l == nil { // It is possible to compare with nil.
+ return boolType, info{}
+ }
+ if !isComparable(l, r.Elem()) {
+ return v.error(node, "cannot use %v as type %v in array", l, r.Elem())
+ }
+ if !isComparable(l, ri.elem) {
+ return v.error(node, "cannot use %v as type %v in array", l, ri.elem)
+ }
+ return boolType, info{}
+ }
+ if isAny(l) && anyOf(r, isString, isArray, isMap) {
+ return boolType, info{}
+ }
+ if isAny(r) {
+ return boolType, info{}
+ }
+
+ case "matches":
+ if s, ok := node.Right.(*ast.StringNode); ok {
+ r, err := regexp.Compile(s.Value)
+ if err != nil {
+ return v.error(node, err.Error())
+ }
+ node.Regexp = r
+ }
+ if isString(l) && isString(r) {
+ return boolType, info{}
+ }
+ if or(l, r, isString) {
+ return boolType, info{}
+ }
+
+ case "contains", "startsWith", "endsWith":
+ if isString(l) && isString(r) {
+ return boolType, info{}
+ }
+ if or(l, r, isString) {
+ return boolType, info{}
+ }
+
+ case "..":
+ ret := reflect.SliceOf(integerType)
+ if isInteger(l) && isInteger(r) {
+ return ret, info{}
+ }
+ if or(l, r, isInteger) {
+ return ret, info{}
+ }
+
+ case "??":
+ if l == nil && r != nil {
+ return r, info{}
+ }
+ if l != nil && r == nil {
+ return l, info{}
+ }
+ if l == nil && r == nil {
+ return nilType, info{}
+ }
+ if r.AssignableTo(l) {
+ return l, info{}
+ }
+ return anyType, info{}
+
+ default:
+ return v.error(node, "unknown operator (%v)", node.Operator)
+
+ }
+
+ return v.error(node, `invalid operation: %v (mismatched types %v and %v)`, node.Operator, l, r)
+}
+
+func (v *checker) ChainNode(node *ast.ChainNode) (reflect.Type, info) {
+ return v.visit(node.Node)
+}
+
+func (v *checker) MemberNode(node *ast.MemberNode) (reflect.Type, info) {
+ base, _ := v.visit(node.Node)
+ prop, _ := v.visit(node.Property)
+
+ if an, ok := node.Node.(*ast.IdentifierNode); ok && an.Value == "$env" {
+ // If the index is a constant string, can save some
+ // cycles later by finding the type of its referent
+ if name, ok := node.Property.(*ast.StringNode); ok {
+ if t, ok := v.config.Types[name.Value]; ok {
+ return t.Type, info{method: t.Method}
+ } // No error if no type found; it may be added to env between compile and run
+ }
+ return anyType, info{}
+ }
+
+ if name, ok := node.Property.(*ast.StringNode); ok {
+ if base == nil {
+ return v.error(node, "type %v has no field %v", base, name.Value)
+ }
+ // First, check methods defined on base type itself,
+ // independent of which type it is. Without dereferencing.
+ if m, ok := base.MethodByName(name.Value); ok {
+ if kind(base) == reflect.Interface {
+ // 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.
+ return m.Type, info{}
+ } else {
+ node.Method = true
+ node.MethodIndex = m.Index
+ node.Name = name.Value
+ return m.Type, info{method: true}
+ }
+ }
+ }
+
+ if kind(base) == reflect.Ptr {
+ base = base.Elem()
+ }
+
+ switch kind(base) {
+ case reflect.Interface:
+ return anyType, info{}
+
+ case reflect.Map:
+ if prop != nil && !prop.AssignableTo(base.Key()) && !isAny(prop) {
+ return v.error(node.Property, "cannot use %v to get an element from %v", prop, base)
+ }
+ return base.Elem(), info{}
+
+ case reflect.Array, reflect.Slice:
+ if !isInteger(prop) && !isAny(prop) {
+ return v.error(node.Property, "array elements can only be selected using an integer (got %v)", prop)
+ }
+ return base.Elem(), info{}
+
+ case reflect.Struct:
+ if name, ok := node.Property.(*ast.StringNode); ok {
+ propertyName := name.Value
+ if field, ok := fetchField(base, propertyName); ok {
+ node.FieldIndex = field.Index
+ node.Name = propertyName
+ return field.Type, info{}
+ }
+ if len(v.parents) > 1 {
+ if _, ok := v.parents[len(v.parents)-2].(*ast.CallNode); ok {
+ return v.error(node, "type %v has no method %v", base, propertyName)
+ }
+ }
+ return v.error(node, "type %v has no field %v", base, propertyName)
+ }
+ }
+
+ return v.error(node, "type %v[%v] is undefined", base, prop)
+}
+
+func (v *checker) SliceNode(node *ast.SliceNode) (reflect.Type, info) {
+ t, _ := v.visit(node.Node)
+
+ switch kind(t) {
+ case reflect.Interface:
+ // ok
+ case reflect.String, reflect.Array, reflect.Slice:
+ // ok
+ default:
+ return v.error(node, "cannot slice %v", t)
+ }
+
+ if node.From != nil {
+ from, _ := v.visit(node.From)
+ if !isInteger(from) && !isAny(from) {
+ return v.error(node.From, "non-integer slice index %v", from)
+ }
+ }
+ if node.To != nil {
+ to, _ := v.visit(node.To)
+ if !isInteger(to) && !isAny(to) {
+ return v.error(node.To, "non-integer slice index %v", to)
+ }
+ }
+ return t, info{}
+}
+
+func (v *checker) CallNode(node *ast.CallNode) (reflect.Type, info) {
+ fn, fnInfo := v.visit(node.Callee)
+
+ if fnInfo.fn != nil {
+ node.Func = fnInfo.fn
+ return v.checkFunction(fnInfo.fn, 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
+ }
+ }
+ switch fn.Kind() {
+ case reflect.Interface:
+ return anyType, info{}
+ case reflect.Func:
+ inputParamsCount := 1 // for functions
+ if fnInfo.method {
+ inputParamsCount = 2 // for methods
+ }
+ // TODO: Deprecate OpCallFast and move fn(...any) any to TypedFunc list.
+ // To do this we need add support for variadic arguments in OpCallTyped.
+ if !isAny(fn) &&
+ fn.IsVariadic() &&
+ fn.NumIn() == inputParamsCount &&
+ 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 kind(rest) == reflect.Slice && rest.Elem().Kind() == reflect.Interface {
+ node.Fast = true
+ }
+ }
+
+ outType, err := v.checkArguments(fnName, fn, fnInfo.method, node.Arguments, node)
+ if err != nil {
+ if v.err == nil {
+ v.err = err
+ }
+ return anyType, info{}
+ }
+
+ v.findTypedFunc(node, fn, fnInfo.method)
+
+ return outType, info{}
+ }
+ return v.error(node, "%v is not callable", fn)
+}
+
+func (v *checker) BuiltinNode(node *ast.BuiltinNode) (reflect.Type, info) {
+ switch node.Name {
+ case "all", "none", "any", "one":
+ collection, _ := v.visit(node.Arguments[0])
+ if !isArray(collection) && !isAny(collection) {
+ return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
+ }
+
+ v.begin(collection)
+ closure, _ := v.visit(node.Arguments[1])
+ v.end()
+
+ if isFunc(closure) &&
+ closure.NumOut() == 1 &&
+ closure.NumIn() == 1 && isAny(closure.In(0)) {
+
+ if !isBool(closure.Out(0)) && !isAny(closure.Out(0)) {
+ return v.error(node.Arguments[1], "predicate should return boolean (got %v)", closure.Out(0).String())
+ }
+ return boolType, info{}
+ }
+ return v.error(node.Arguments[1], "predicate should has one input and one output param")
+
+ case "filter":
+ collection, _ := v.visit(node.Arguments[0])
+ if !isArray(collection) && !isAny(collection) {
+ return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
+ }
+
+ v.begin(collection)
+ closure, _ := v.visit(node.Arguments[1])
+ v.end()
+
+ if isFunc(closure) &&
+ closure.NumOut() == 1 &&
+ closure.NumIn() == 1 && isAny(closure.In(0)) {
+
+ if !isBool(closure.Out(0)) && !isAny(closure.Out(0)) {
+ return v.error(node.Arguments[1], "predicate should return boolean (got %v)", closure.Out(0).String())
+ }
+ if isAny(collection) {
+ return arrayType, info{}
+ }
+ return reflect.SliceOf(collection.Elem()), info{}
+ }
+ return v.error(node.Arguments[1], "predicate should has one input and one output param")
+
+ case "map":
+ collection, _ := v.visit(node.Arguments[0])
+ if !isArray(collection) && !isAny(collection) {
+ return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
+ }
+
+ v.begin(collection, scopeVar{"index", integerType})
+ closure, _ := v.visit(node.Arguments[1])
+ v.end()
+
+ if isFunc(closure) &&
+ closure.NumOut() == 1 &&
+ closure.NumIn() == 1 && isAny(closure.In(0)) {
+
+ return reflect.SliceOf(closure.Out(0)), info{}
+ }
+ return v.error(node.Arguments[1], "predicate should has one input and one output param")
+
+ case "count":
+ collection, _ := v.visit(node.Arguments[0])
+ if !isArray(collection) && !isAny(collection) {
+ return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
+ }
+
+ v.begin(collection)
+ closure, _ := v.visit(node.Arguments[1])
+ v.end()
+
+ if isFunc(closure) &&
+ closure.NumOut() == 1 &&
+ closure.NumIn() == 1 && isAny(closure.In(0)) {
+ if !isBool(closure.Out(0)) && !isAny(closure.Out(0)) {
+ return v.error(node.Arguments[1], "predicate should return boolean (got %v)", closure.Out(0).String())
+ }
+
+ return integerType, info{}
+ }
+ return v.error(node.Arguments[1], "predicate should has one input and one output param")
+
+ case "find", "findLast":
+ collection, _ := v.visit(node.Arguments[0])
+ if !isArray(collection) && !isAny(collection) {
+ return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
+ }
+
+ v.begin(collection)
+ closure, _ := v.visit(node.Arguments[1])
+ v.end()
+
+ if isFunc(closure) &&
+ closure.NumOut() == 1 &&
+ closure.NumIn() == 1 && isAny(closure.In(0)) {
+
+ if !isBool(closure.Out(0)) && !isAny(closure.Out(0)) {
+ return v.error(node.Arguments[1], "predicate should return boolean (got %v)", closure.Out(0).String())
+ }
+ if isAny(collection) {
+ return anyType, info{}
+ }
+ return collection.Elem(), info{}
+ }
+ return v.error(node.Arguments[1], "predicate should has one input and one output param")
+
+ case "findIndex", "findLastIndex":
+ collection, _ := v.visit(node.Arguments[0])
+ if !isArray(collection) && !isAny(collection) {
+ return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
+ }
+
+ v.begin(collection)
+ closure, _ := v.visit(node.Arguments[1])
+ v.end()
+
+ if isFunc(closure) &&
+ closure.NumOut() == 1 &&
+ closure.NumIn() == 1 && isAny(closure.In(0)) {
+
+ if !isBool(closure.Out(0)) && !isAny(closure.Out(0)) {
+ return v.error(node.Arguments[1], "predicate should return boolean (got %v)", closure.Out(0).String())
+ }
+ return integerType, info{}
+ }
+ return v.error(node.Arguments[1], "predicate should has one input and one output param")
+
+ case "groupBy":
+ collection, _ := v.visit(node.Arguments[0])
+ if !isArray(collection) && !isAny(collection) {
+ return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
+ }
+
+ v.begin(collection)
+ closure, _ := v.visit(node.Arguments[1])
+ v.end()
+
+ if isFunc(closure) &&
+ closure.NumOut() == 1 &&
+ closure.NumIn() == 1 && isAny(closure.In(0)) {
+
+ return reflect.TypeOf(map[any][]any{}), info{}
+ }
+ return v.error(node.Arguments[1], "predicate should has one input and one output param")
+
+ case "reduce":
+ collection, _ := v.visit(node.Arguments[0])
+ if !isArray(collection) && !isAny(collection) {
+ return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
+ }
+
+ v.begin(collection, scopeVar{"index", integerType}, scopeVar{"acc", anyType})
+ closure, _ := v.visit(node.Arguments[1])
+ v.end()
+
+ if len(node.Arguments) == 3 {
+ _, _ = v.visit(node.Arguments[2])
+ }
+
+ if isFunc(closure) && closure.NumOut() == 1 {
+ return closure.Out(0), info{}
+ }
+ 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)
+}
+
+type scopeVar struct {
+ name string
+ vtype reflect.Type
+}
+
+func (v *checker) begin(vtype reflect.Type, vars ...scopeVar) {
+ scope := predicateScope{vtype: vtype, vars: make(map[string]reflect.Type)}
+ for _, v := range vars {
+ scope.vars[v.name] = v.vtype
+ }
+ v.predicateScopes = append(v.predicateScopes, scope)
+}
+
+func (v *checker) end() {
+ v.predicateScopes = v.predicateScopes[:len(v.predicateScopes)-1]
+}
+
+func (v *checker) checkBuiltinGet(node *ast.BuiltinNode) (reflect.Type, info) {
+ if len(node.Arguments) != 2 {
+ return v.error(node, "invalid number of arguments (expected 2, got %d)", len(node.Arguments))
+ }
+
+ val := node.Arguments[0]
+ prop := node.Arguments[1]
+ if id, ok := val.(*ast.IdentifierNode); ok && id.Value == "$env" {
+ if s, ok := prop.(*ast.StringNode); ok {
+ return v.config.Types[s.Value].Type, info{}
+ }
+ return anyType, info{}
+ }
+
+ t, _ := v.visit(val)
+
+ switch kind(t) {
+ case reflect.Interface:
+ return anyType, info{}
+ case reflect.Slice, reflect.Array:
+ p, _ := v.visit(prop)
+ if p == nil {
+ return v.error(prop, "cannot use nil as slice index")
+ }
+ if !isInteger(p) && !isAny(p) {
+ return v.error(prop, "non-integer slice index %v", p)
+ }
+ return t.Elem(), info{}
+ case reflect.Map:
+ p, _ := v.visit(prop)
+ if p == nil {
+ return v.error(prop, "cannot use nil as map index")
+ }
+ if !p.AssignableTo(t.Key()) && !isAny(p) {
+ return v.error(prop, "cannot use %v to get an element from %v", p, t)
+ }
+ return t.Elem(), info{}
+ }
+ return v.error(val, "type %v does not support indexing", t)
+}
+
+func (v *checker) checkFunction(f *ast.Function, node ast.Node, arguments []ast.Node) (reflect.Type, info) {
+ if f.Validate != nil {
+ args := make([]reflect.Type, len(arguments))
+ for i, arg := range arguments {
+ args[i], _ = v.visit(arg)
+ }
+ t, err := f.Validate(args)
+ if err != nil {
+ return v.error(node, "%v", err)
+ }
+ return t, info{}
+ } else if len(f.Types) == 0 {
+ t, err := v.checkArguments(f.Name, functionType, false, arguments, node)
+ if err != nil {
+ if v.err == nil {
+ v.err = err
+ }
+ return anyType, info{}
+ }
+ // No type was specified, so we assume the function returns any.
+ return t, info{}
+ }
+ var lastErr *file.Error
+ for _, t := range f.Types {
+ outType, err := v.checkArguments(f.Name, t, false, arguments, node)
+ if err != nil {
+ lastErr = err
+ continue
+ }
+ return outType, info{}
+ }
+ if lastErr != nil {
+ if v.err == nil {
+ v.err = lastErr
+ }
+ return anyType, info{}
+ }
+
+ return v.error(node, "no matching overload for %v", f.Name)
+}
+
+func (v *checker) checkArguments(name string, fn reflect.Type, method bool, arguments []ast.Node, node ast.Node) (reflect.Type, *file.Error) {
+ if isAny(fn) {
+ return anyType, nil
+ }
+
+ if fn.NumOut() == 0 {
+ return anyType, &file.Error{
+ Location: node.Location(),
+ Message: fmt.Sprintf("func %v doesn't return value", name),
+ }
+ }
+ if numOut := fn.NumOut(); numOut > 2 {
+ return anyType, &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 method {
+ fnNumIn--
+ }
+ // Skip first argument in case of the receiver.
+ fnInOffset := 0
+ if method {
+ fnInOffset = 1
+ }
+
+ if fn.IsVariadic() {
+ if len(arguments) < fnNumIn-1 {
+ return anyType, &file.Error{
+ Location: node.Location(),
+ Message: fmt.Sprintf("not enough arguments to call %v", name),
+ }
+ }
+ } else {
+ if len(arguments) > fnNumIn {
+ return anyType, &file.Error{
+ Location: node.Location(),
+ Message: fmt.Sprintf("too many arguments to call %v", name),
+ }
+ }
+ if len(arguments) < fnNumIn {
+ return anyType, &file.Error{
+ Location: node.Location(),
+ Message: fmt.Sprintf("not enough arguments to call %v", name),
+ }
+ }
+ }
+
+ for i, arg := range arguments {
+ t, _ := v.visit(arg)
+
+ var in reflect.Type
+ if fn.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.In(fn.NumIn() - 1).Elem()
+ } else {
+ in = fn.In(i + fnInOffset)
+ }
+
+ if isFloat(in) {
+ traverseAndReplaceIntegerNodesWithFloatNodes(&arguments[i], in)
+ continue
+ }
+
+ if isInteger(in) && isInteger(t) && kind(t) != kind(in) {
+ traverseAndReplaceIntegerNodesWithIntegerNodes(&arguments[i], in)
+ continue
+ }
+
+ if t == nil {
+ continue
+ }
+
+ if !t.AssignableTo(in) && kind(t) != reflect.Interface {
+ return anyType, &file.Error{
+ Location: arg.Location(),
+ Message: fmt.Sprintf("cannot use %v as argument (type %v) to call %v ", t, in, name),
+ }
+ }
+ }
+
+ return fn.Out(0), nil
+}
+
+func traverseAndReplaceIntegerNodesWithFloatNodes(node *ast.Node, newType reflect.Type) {
+ switch (*node).(type) {
+ case *ast.IntegerNode:
+ *node = &ast.FloatNode{Value: float64((*node).(*ast.IntegerNode).Value)}
+ (*node).SetType(newType)
+ case *ast.UnaryNode:
+ unaryNode := (*node).(*ast.UnaryNode)
+ traverseAndReplaceIntegerNodesWithFloatNodes(&unaryNode.Node, newType)
+ case *ast.BinaryNode:
+ binaryNode := (*node).(*ast.BinaryNode)
+ switch binaryNode.Operator {
+ case "+", "-", "*":
+ traverseAndReplaceIntegerNodesWithFloatNodes(&binaryNode.Left, newType)
+ traverseAndReplaceIntegerNodesWithFloatNodes(&binaryNode.Right, newType)
+ }
+ }
+}
+
+func traverseAndReplaceIntegerNodesWithIntegerNodes(node *ast.Node, newType reflect.Type) {
+ switch (*node).(type) {
+ case *ast.IntegerNode:
+ (*node).SetType(newType)
+ case *ast.UnaryNode:
+ unaryNode := (*node).(*ast.UnaryNode)
+ traverseAndReplaceIntegerNodesWithIntegerNodes(&unaryNode.Node, newType)
+ case *ast.BinaryNode:
+ binaryNode := (*node).(*ast.BinaryNode)
+ switch binaryNode.Operator {
+ case "+", "-", "*":
+ traverseAndReplaceIntegerNodesWithIntegerNodes(&binaryNode.Left, newType)
+ traverseAndReplaceIntegerNodesWithIntegerNodes(&binaryNode.Right, newType)
+ }
+ }
+}
+
+func (v *checker) ClosureNode(node *ast.ClosureNode) (reflect.Type, info) {
+ t, _ := v.visit(node.Node)
+ if t == nil {
+ return v.error(node.Node, "closure cannot be nil")
+ }
+ return reflect.FuncOf([]reflect.Type{anyType}, []reflect.Type{t}, false), info{}
+}
+
+func (v *checker) PointerNode(node *ast.PointerNode) (reflect.Type, info) {
+ if len(v.predicateScopes) == 0 {
+ return v.error(node, "cannot use pointer accessor outside closure")
+ }
+ scope := v.predicateScopes[len(v.predicateScopes)-1]
+ if node.Name == "" {
+ switch scope.vtype.Kind() {
+ case reflect.Interface:
+ return anyType, info{}
+ case reflect.Array, reflect.Slice:
+ return scope.vtype.Elem(), info{}
+ }
+ return v.error(node, "cannot use %v as array", scope)
+ }
+ if scope.vars != nil {
+ if t, ok := scope.vars[node.Name]; ok {
+ return t, info{}
+ }
+ }
+ return v.error(node, "unknown pointer #%v", node.Name)
+}
+
+func (v *checker) VariableDeclaratorNode(node *ast.VariableDeclaratorNode) (reflect.Type, info) {
+ if _, ok := v.config.Types[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)
+ }
+ if _, ok := v.lookupVariable(node.Name); ok {
+ return v.error(node, "cannot redeclare variable %v", node.Name)
+ }
+ vtype, vinfo := v.visit(node.Value)
+ v.varScopes = append(v.varScopes, varScope{node.Name, vtype, vinfo})
+ t, i := v.visit(node.Expr)
+ v.varScopes = v.varScopes[:len(v.varScopes)-1]
+ return t, i
+}
+
+func (v *checker) lookupVariable(name string) (varScope, bool) {
+ for i := len(v.varScopes) - 1; i >= 0; i-- {
+ if v.varScopes[i].name == name {
+ return v.varScopes[i], true
+ }
+ }
+ return varScope{}, false
+}
+
+func (v *checker) ConditionalNode(node *ast.ConditionalNode) (reflect.Type, info) {
+ c, _ := v.visit(node.Cond)
+ if !isBool(c) && !isAny(c) {
+ return v.error(node.Cond, "non-bool expression (type %v) used as condition", c)
+ }
+
+ t1, _ := v.visit(node.Exp1)
+ t2, _ := v.visit(node.Exp2)
+
+ if t1 == nil && t2 != nil {
+ return t2, info{}
+ }
+ if t1 != nil && t2 == nil {
+ return t1, info{}
+ }
+ if t1 == nil && t2 == nil {
+ return nilType, info{}
+ }
+ if t1.AssignableTo(t2) {
+ return t1, info{}
+ }
+ return anyType, info{}
+}
+
+func (v *checker) ArrayNode(node *ast.ArrayNode) (reflect.Type, info) {
+ var prev reflect.Type
+ allElementsAreSameType := true
+ for i, node := range node.Nodes {
+ curr, _ := v.visit(node)
+ if i > 0 {
+ if curr == nil || prev == nil {
+ allElementsAreSameType = false
+ } else if curr.Kind() != prev.Kind() {
+ allElementsAreSameType = false
+ }
+ }
+ prev = curr
+ }
+ if allElementsAreSameType && prev != nil {
+ return arrayType, info{elem: prev}
+ }
+ return arrayType, info{}
+}
+
+func (v *checker) MapNode(node *ast.MapNode) (reflect.Type, info) {
+ for _, pair := range node.Pairs {
+ v.visit(pair)
+ }
+ return mapType, info{}
+}
+
+func (v *checker) PairNode(node *ast.PairNode) (reflect.Type, info) {
+ v.visit(node.Key)
+ v.visit(node.Value)
+ return nilType, info{}
+}
+
+func (v *checker) findTypedFunc(node *ast.CallNode, fn reflect.Type, method bool) {
+ // OnCallTyped doesn't work for functions with variadic arguments,
+ // and doesn't work named function, like `type MyFunc func() int`.
+ // In PkgPath() is an empty string, it's unnamed function.
+ if !fn.IsVariadic() && fn.PkgPath() == "" {
+ 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
+ }
+ }
+ node.Typed = i
+ }
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/checker/types.go b/vendor/github.com/antonmedv/expr/checker/types.go
new file mode 100644
index 00000000000..890d74a5f2f
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/checker/types.go
@@ -0,0 +1,244 @@
+package checker
+
+import (
+ "reflect"
+ "time"
+
+ "github.com/antonmedv/expr/conf"
+)
+
+var (
+ nilType = reflect.TypeOf(nil)
+ boolType = reflect.TypeOf(true)
+ integerType = reflect.TypeOf(0)
+ floatType = reflect.TypeOf(float64(0))
+ stringType = reflect.TypeOf("")
+ arrayType = reflect.TypeOf([]any{})
+ mapType = reflect.TypeOf(map[string]any{})
+ anyType = reflect.TypeOf(new(any)).Elem()
+ timeType = reflect.TypeOf(time.Time{})
+ durationType = reflect.TypeOf(time.Duration(0))
+ functionType = reflect.TypeOf(new(func(...any) (any, error))).Elem()
+)
+
+func combined(a, b reflect.Type) reflect.Type {
+ if a.Kind() == b.Kind() {
+ return a
+ }
+ if isFloat(a) || isFloat(b) {
+ return floatType
+ }
+ return integerType
+}
+
+func anyOf(t reflect.Type, fns ...func(reflect.Type) bool) bool {
+ for _, fn := range fns {
+ if fn(t) {
+ return true
+ }
+ }
+ return false
+}
+
+func or(l, r reflect.Type, fns ...func(reflect.Type) bool) bool {
+ if isAny(l) && isAny(r) {
+ return true
+ }
+ if isAny(l) && anyOf(r, fns...) {
+ return true
+ }
+ if isAny(r) && anyOf(l, fns...) {
+ return true
+ }
+ return false
+}
+
+func isAny(t reflect.Type) bool {
+ if t != nil {
+ switch t.Kind() {
+ case reflect.Interface:
+ return true
+ }
+ }
+ return false
+}
+
+func isInteger(t reflect.Type) bool {
+ if t != nil {
+ switch t.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ fallthrough
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ return true
+ }
+ }
+ return false
+}
+
+func isFloat(t reflect.Type) bool {
+ if t != nil {
+ switch t.Kind() {
+ case reflect.Float32, reflect.Float64:
+ return true
+ }
+ }
+ return false
+}
+
+func isNumber(t reflect.Type) bool {
+ return isInteger(t) || isFloat(t)
+}
+
+func isTime(t reflect.Type) bool {
+ if t != nil {
+ switch t {
+ case timeType:
+ return true
+ }
+ }
+ return false
+}
+
+func isDuration(t reflect.Type) bool {
+ if t != nil {
+ switch t {
+ case durationType:
+ return true
+ }
+ }
+ return false
+}
+
+func isBool(t reflect.Type) bool {
+ if t != nil {
+ switch t.Kind() {
+ case reflect.Bool:
+ return true
+ }
+ }
+ return false
+}
+
+func isString(t reflect.Type) bool {
+ if t != nil {
+ switch t.Kind() {
+ case reflect.String:
+ return true
+ }
+ }
+ return false
+}
+
+func isArray(t reflect.Type) bool {
+ if t != nil {
+ switch t.Kind() {
+ case reflect.Ptr:
+ return isArray(t.Elem())
+ case reflect.Slice, reflect.Array:
+ return true
+ }
+ }
+ return false
+}
+
+func isMap(t reflect.Type) bool {
+ if t != nil {
+ switch t.Kind() {
+ case reflect.Ptr:
+ return isMap(t.Elem())
+ case reflect.Map:
+ return true
+ }
+ }
+ return false
+}
+
+func isStruct(t reflect.Type) bool {
+ if t != nil {
+ switch t.Kind() {
+ case reflect.Ptr:
+ return isStruct(t.Elem())
+ case reflect.Struct:
+ return true
+ }
+ }
+ return false
+}
+
+func isFunc(t reflect.Type) bool {
+ if t != nil {
+ switch t.Kind() {
+ case reflect.Ptr:
+ return isFunc(t.Elem())
+ case reflect.Func:
+ return true
+ }
+ }
+ return false
+}
+
+func fetchField(t reflect.Type, name string) (reflect.StructField, bool) {
+ if t != nil {
+ // First check all structs fields.
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+ // Search all fields, even embedded structs.
+ if conf.FieldName(field) == name {
+ return field, true
+ }
+ }
+
+ // Second check fields of embedded structs.
+ for i := 0; i < t.NumField(); i++ {
+ anon := t.Field(i)
+ if anon.Anonymous {
+ if field, ok := fetchField(anon.Type, name); ok {
+ field.Index = append(anon.Index, field.Index...)
+ return field, true
+ }
+ }
+ }
+ }
+ return reflect.StructField{}, false
+}
+
+func deref(t reflect.Type) reflect.Type {
+ if t == nil {
+ return nil
+ }
+ if t.Kind() == reflect.Interface {
+ return t
+ }
+ for t != nil && t.Kind() == reflect.Ptr {
+ e := t.Elem()
+ switch e.Kind() {
+ case reflect.Struct, reflect.Map, reflect.Array, reflect.Slice:
+ return t
+ default:
+ t = e
+ }
+ }
+ return t
+}
+
+func kind(t reflect.Type) reflect.Kind {
+ if t == nil {
+ return reflect.Invalid
+ }
+ return t.Kind()
+}
+
+func isComparable(l, r reflect.Type) bool {
+ if l == nil || r == nil {
+ return true
+ }
+ switch {
+ case l.Kind() == r.Kind():
+ return true
+ case isNumber(l) && isNumber(r):
+ return true
+ case isAny(l) || isAny(r):
+ return true
+ }
+ return false
+}
diff --git a/vendor/github.com/antonmedv/expr/compiler/compiler.go b/vendor/github.com/antonmedv/expr/compiler/compiler.go
new file mode 100644
index 00000000000..8e26d8788eb
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/compiler/compiler.go
@@ -0,0 +1,1010 @@
+package compiler
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/antonmedv/expr/ast"
+ "github.com/antonmedv/expr/builtin"
+ "github.com/antonmedv/expr/conf"
+ "github.com/antonmedv/expr/file"
+ "github.com/antonmedv/expr/parser"
+ . "github.com/antonmedv/expr/vm"
+ "github.com/antonmedv/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", r)
+ }
+ }()
+
+ c := &compiler{
+ locations: make([]file.Location, 0),
+ constantsIndex: make(map[any]int),
+ functionsIndex: make(map[string]int),
+ debugInfo: make(map[string]string),
+ }
+
+ if config != nil {
+ c.mapEnv = config.MapEnv
+ c.cast = config.Expect
+ }
+
+ c.compile(tree.Node)
+
+ switch c.cast {
+ case reflect.Int:
+ c.emit(OpCast, 0)
+ case reflect.Int64:
+ c.emit(OpCast, 1)
+ case reflect.Float64:
+ c.emit(OpCast, 2)
+ }
+
+ program = &Program{
+ Node: tree.Node,
+ Source: tree.Source,
+ Locations: c.locations,
+ Variables: c.variables,
+ Constants: c.constants,
+ Bytecode: c.bytecode,
+ Arguments: c.arguments,
+ Functions: c.functions,
+ DebugInfo: c.debugInfo,
+ }
+ return
+}
+
+type compiler struct {
+ locations []file.Location
+ bytecode []Opcode
+ variables []any
+ scopes []scope
+ constants []any
+ constantsIndex map[any]int
+ functions []Function
+ functionsIndex map[string]int
+ debugInfo map[string]string
+ mapEnv bool
+ cast reflect.Kind
+ nodes []ast.Node
+ chains [][]int
+ arguments []int
+}
+
+type scope struct {
+ variableName string
+ index int
+}
+
+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:
+ 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 = append(c.variables, nil)
+ p := len(c.variables) - 1
+ c.debugInfo[fmt.Sprintf("var_%d", p)] = name
+ return p
+}
+
+// emitFunction adds builtin.Function.Func to the program.Functions and emits call opcode.
+func (c *compiler) emitFunction(fn *ast.Function, argsLen int) {
+ switch argsLen {
+ case 0:
+ c.emit(OpCall0, c.addFunction(fn))
+ case 1:
+ c.emit(OpCall1, c.addFunction(fn))
+ case 2:
+ c.emit(OpCall2, c.addFunction(fn))
+ case 3:
+ c.emit(OpCall3, c.addFunction(fn))
+ default:
+ c.emit(OpLoadFunc, c.addFunction(fn))
+ c.emit(OpCallN, argsLen)
+ }
+}
+
+// addFunction adds builtin.Function.Func to the program.Functions and returns its index.
+func (c *compiler) addFunction(fn *ast.Function) int {
+ if fn == nil {
+ panic("function is nil")
+ }
+ if p, ok := c.functionsIndex[fn.Name]; ok {
+ return p
+ }
+ p := len(c.functions)
+ c.functions = append(c.functions, fn.Func)
+ c.functionsIndex[fn.Name] = p
+ c.debugInfo[fmt.Sprintf("func_%d", p)] = fn.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]
+ }()
+
+ 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.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.ClosureNode:
+ c.ClosureNode(n)
+ case *ast.PointerNode:
+ c.PointerNode(n)
+ case *ast.VariableDeclaratorNode:
+ c.VariableDeclaratorNode(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
+ }
+ if c.mapEnv {
+ c.emit(OpLoadFast, c.addConstant(node.Value))
+ } else if len(node.FieldIndex) > 0 {
+ c.emit(OpLoadField, c.addConstant(&runtime.Field{
+ Index: node.FieldIndex,
+ Path: []string{node.Value},
+ }))
+ } else if node.Method {
+ c.emit(OpLoadMethod, c.addConstant(&runtime.Method{
+ Name: node.Value,
+ Index: node.MethodIndex,
+ }))
+ } 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:
+ c.emitPush(int8(node.Value))
+ case reflect.Int16:
+ c.emitPush(int16(node.Value))
+ case reflect.Int32:
+ c.emitPush(int32(node.Value))
+ case reflect.Int64:
+ c.emitPush(int64(node.Value))
+ case reflect.Uint:
+ c.emitPush(uint(node.Value))
+ case reflect.Uint8:
+ c.emitPush(uint8(node.Value))
+ case reflect.Uint16:
+ c.emitPush(uint16(node.Value))
+ case reflect.Uint32:
+ c.emitPush(uint32(node.Value))
+ case reflect.Uint64:
+ c.emitPush(uint64(node.Value))
+ default:
+ c.emitPush(node.Value)
+ }
+}
+
+func (c *compiler) FloatNode(node *ast.FloatNode) {
+ 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(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) ConstantNode(node *ast.ConstantNode) {
+ 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) {
+ l := kind(node.Left)
+ r := kind(node.Right)
+
+ switch node.Operator {
+ case "==":
+ c.compile(node.Left)
+ c.derefInNeeded(node.Left)
+ c.compile(node.Right)
+ c.derefInNeeded(node.Left)
+
+ if l == r && l == reflect.Int {
+ c.emit(OpEqualInt)
+ } else if l == r && l == reflect.String {
+ c.emit(OpEqualString)
+ } else {
+ c.emit(OpEqual)
+ }
+
+ case "!=":
+ c.compile(node.Left)
+ c.derefInNeeded(node.Left)
+ c.compile(node.Right)
+ c.derefInNeeded(node.Left)
+ c.emit(OpEqual)
+ c.emit(OpNot)
+
+ case "or", "||":
+ 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", "&&":
+ 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 node.Regexp != nil {
+ c.compile(node.Left)
+ c.derefInNeeded(node.Left)
+ c.emit(OpMatchesConst, c.addConstant(node.Regexp))
+ } 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) ChainNode(node *ast.ChainNode) {
+ c.chains = append(c.chains, []int{})
+ c.compile(node.Node)
+ // Chain activate (got nit somewhere)
+ for _, ph := range c.chains[len(c.chains)-1] {
+ c.patchJump(ph)
+ }
+ c.chains = c.chains[:len(c.chains)-1]
+}
+
+func (c *compiler) MemberNode(node *ast.MemberNode) {
+ if node.Method {
+ c.compile(node.Node)
+ c.emit(OpMethod, c.addConstant(&runtime.Method{
+ Name: node.Name,
+ Index: node.MethodIndex,
+ }))
+ return
+ }
+ op := OpFetch
+ index := node.FieldIndex
+ path := []string{node.Name}
+ base := node.Node
+ if len(node.FieldIndex) > 0 {
+ op = OpFetchField
+ for !node.Optional {
+ ident, ok := base.(*ast.IdentifierNode)
+ if ok && len(ident.FieldIndex) > 0 {
+ index = append(ident.FieldIndex, index...)
+ path = append([]string{ident.Value}, path...)
+ c.emitLocation(ident.Location(), OpLoadField, c.addConstant(
+ &runtime.Field{Index: index, Path: path},
+ ))
+ return
+ }
+ member, ok := base.(*ast.MemberNode)
+ if ok && len(member.FieldIndex) > 0 {
+ index = append(member.FieldIndex, index...)
+ path = append([]string{member.Name}, path...)
+ node = member
+ base = member.Node
+ } else {
+ break
+ }
+ }
+ }
+
+ c.compile(base)
+ if node.Optional {
+ 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)
+ 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)
+ } else {
+ c.emit(OpLen)
+ }
+ if node.From != nil {
+ c.compile(node.From)
+ } else {
+ c.emitPush(0)
+ }
+ c.emit(OpSlice)
+}
+
+func (c *compiler) CallNode(node *ast.CallNode) {
+ for _, arg := range node.Arguments {
+ c.compile(arg)
+ }
+ if node.Func != nil {
+ c.emitFunction(node.Func, len(node.Arguments))
+ return
+ }
+ c.compile(node.Callee)
+ if node.Typed > 0 {
+ c.emit(OpCallTyped, node.Typed)
+ return
+ } else if node.Fast {
+ c.emit(OpCallFast, 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.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.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.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.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.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.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.emit(OpBegin)
+ c.emitLoop(func() {
+ c.compile(node.Arguments[1])
+ c.emitCond(func() {
+ c.emit(OpIncrementCount)
+ })
+ })
+ c.emit(OpGetCount)
+ c.emit(OpEnd)
+ return
+
+ case "find":
+ c.compile(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.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.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.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.emit(OpBegin)
+ c.emitLoop(func() {
+ c.compile(node.Arguments[1])
+ c.emit(OpGroupBy)
+ })
+ c.emit(OpGetGroupBy)
+ c.emit(OpEnd)
+ return
+
+ case "reduce":
+ c.compile(node.Arguments[0])
+ c.emit(OpBegin)
+ if len(node.Arguments) == 3 {
+ c.compile(node.Arguments[2])
+ c.emit(OpSetAcc)
+ } else {
+ c.emit(OpPointer)
+ c.emit(OpIncrementIndex)
+ c.emit(OpSetAcc)
+ }
+ 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 _, arg := range node.Arguments {
+ c.compile(arg)
+ }
+ if f.Fast != nil {
+ c.emit(OpCallBuiltin1, id)
+ } 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) ClosureNode(node *ast.ClosureNode) {
+ 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) 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)
+ 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) {
+ switch kind(node) {
+ case reflect.Ptr, reflect.Interface:
+ c.emit(OpDeref)
+ }
+}
+
+func kind(node ast.Node) reflect.Kind {
+ t := node.Type()
+ if t == nil {
+ return reflect.Invalid
+ }
+ return t.Kind()
+}
diff --git a/vendor/github.com/antonmedv/expr/conf/config.go b/vendor/github.com/antonmedv/expr/conf/config.go
new file mode 100644
index 00000000000..5fb5e1194b7
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/conf/config.go
@@ -0,0 +1,117 @@
+package conf
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/antonmedv/expr/ast"
+ "github.com/antonmedv/expr/builtin"
+ "github.com/antonmedv/expr/vm/runtime"
+)
+
+type Config struct {
+ Env any
+ Types TypesTable
+ MapEnv bool
+ DefaultType reflect.Type
+ Operators OperatorsTable
+ Expect reflect.Kind
+ ExpectAny bool
+ Optimize bool
+ Strict bool
+ ConstFns map[string]reflect.Value
+ Visitors []ast.Visitor
+ Functions map[string]*ast.Function
+ Builtins map[string]*ast.Function
+ Disabled map[string]bool // disabled builtins
+}
+
+// CreateNew creates new config with default values.
+func CreateNew() *Config {
+ c := &Config{
+ Optimize: true,
+ Operators: make(map[string][]string),
+ ConstFns: make(map[string]reflect.Value),
+ Functions: make(map[string]*ast.Function),
+ Builtins: make(map[string]*ast.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) {
+ var mapEnv bool
+ var mapValueType reflect.Type
+ if _, ok := env.(map[string]any); ok {
+ mapEnv = true
+ } else {
+ if reflect.ValueOf(env).Kind() == reflect.Map {
+ mapValueType = reflect.TypeOf(env).Elem()
+ }
+ }
+
+ c.Env = env
+ c.Types = CreateTypesTable(env)
+ c.MapEnv = mapEnv
+ c.DefaultType = mapValueType
+ c.Strict = true
+}
+
+func (c *Config) Operator(operator string, fns ...string) {
+ c.Operators[operator] = append(c.Operators[operator], fns...)
+}
+
+func (c *Config) ConstExpr(name string) {
+ if c.Env == nil {
+ panic("no environment is specified for ConstExpr()")
+ }
+ fn := reflect.ValueOf(runtime.Fetch(c.Env, name))
+ if fn.Kind() != reflect.Func {
+ panic(fmt.Errorf("const expression %q must be a function", name))
+ }
+ c.ConstFns[name] = fn
+}
+
+func (c *Config) Check() {
+ for operator, fns := range c.Operators {
+ for _, fn := range fns {
+ fnType, ok := c.Types[fn]
+ if !ok || fnType.Type.Kind() != reflect.Func {
+ panic(fmt.Errorf("function %s for %s operator does not exist in the environment", fn, operator))
+ }
+ 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))
+ }
+ }
+ }
+ for fnName, t := range c.Types {
+ if kind(t.Type) == reflect.Func {
+ for _, b := range c.Builtins {
+ if b.Name == fnName {
+ panic(fmt.Errorf(`cannot override builtin %s(): use expr.DisableBuiltin("%s") to override`, b.Name, b.Name))
+ }
+ }
+ }
+ }
+ for _, f := range c.Functions {
+ for _, b := range c.Builtins {
+ if b.Name == f.Name {
+ panic(fmt.Errorf(`cannot override builtin %s(); use expr.DisableBuiltin("%s") to override`, f.Name, f.Name))
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/conf/functions.go b/vendor/github.com/antonmedv/expr/conf/functions.go
new file mode 100644
index 00000000000..8f52a955753
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/conf/functions.go
@@ -0,0 +1 @@
+package conf
diff --git a/vendor/github.com/antonmedv/expr/conf/operators.go b/vendor/github.com/antonmedv/expr/conf/operators.go
new file mode 100644
index 00000000000..13e069d76ca
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/conf/operators.go
@@ -0,0 +1,59 @@
+package conf
+
+import (
+ "reflect"
+
+ "github.com/antonmedv/expr/ast"
+)
+
+// OperatorsTable maps binary operators to corresponding list of functions.
+// Functions should be provided in the environment to allow operator overloading.
+type OperatorsTable map[string][]string
+
+func FindSuitableOperatorOverload(fns []string, types TypesTable, l, r reflect.Type) (reflect.Type, string, bool) {
+ for _, fn := range fns {
+ fnType := types[fn]
+ firstInIndex := 0
+ if fnType.Method {
+ firstInIndex = 1 // As first argument to method is receiver.
+ }
+ firstArgType := fnType.Type.In(firstInIndex)
+ secondArgType := fnType.Type.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 fnType.Type.Out(0), fn, true
+ }
+ }
+ return nil, "", false
+}
+
+type OperatorPatcher struct {
+ Operators OperatorsTable
+ Types TypesTable
+}
+
+func (p *OperatorPatcher) Visit(node *ast.Node) {
+ binaryNode, ok := (*node).(*ast.BinaryNode)
+ if !ok {
+ return
+ }
+
+ fns, ok := p.Operators[binaryNode.Operator]
+ if !ok {
+ return
+ }
+
+ leftType := binaryNode.Left.Type()
+ rightType := binaryNode.Right.Type()
+
+ _, fn, ok := FindSuitableOperatorOverload(fns, p.Types, leftType, rightType)
+ if ok {
+ newNode := &ast.CallNode{
+ Callee: &ast.IdentifierNode{Value: fn},
+ Arguments: []ast.Node{binaryNode.Left, binaryNode.Right},
+ }
+ ast.Patch(node, newNode)
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/conf/types_table.go b/vendor/github.com/antonmedv/expr/conf/types_table.go
new file mode 100644
index 00000000000..8ebb76c3575
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/conf/types_table.go
@@ -0,0 +1,136 @@
+package conf
+
+import (
+ "reflect"
+)
+
+type Tag struct {
+ Type reflect.Type
+ Ambiguous bool
+ FieldIndex []int
+ Method bool
+ MethodIndex int
+}
+
+type TypesTable map[string]Tag
+
+// CreateTypesTable creates types table for type checks during parsing.
+// 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
+// (key as name, value as type).
+func CreateTypesTable(i any) TypesTable {
+ if i == nil {
+ return nil
+ }
+
+ types := make(TypesTable)
+ v := reflect.ValueOf(i)
+ t := reflect.TypeOf(i)
+
+ d := t
+ if t.Kind() == reflect.Ptr {
+ d = t.Elem()
+ }
+
+ switch d.Kind() {
+ case reflect.Struct:
+ types = FieldsFromStruct(d)
+
+ // Methods of struct should be gathered from original struct with pointer,
+ // as methods maybe declared on pointer receiver. Also this method retrieves
+ // all embedded structs methods as well, no need to recursion.
+ for i := 0; i < t.NumMethod(); i++ {
+ m := t.Method(i)
+ types[m.Name] = Tag{
+ Type: m.Type,
+ Method: true,
+ MethodIndex: i,
+ }
+ }
+
+ case reflect.Map:
+ for _, key := range v.MapKeys() {
+ value := v.MapIndex(key)
+ if key.Kind() == reflect.String && value.IsValid() && value.CanInterface() {
+ if key.String() == "$env" { // Could check for all keywords here
+ panic("attempt to misuse env keyword as env map key")
+ }
+ types[key.String()] = Tag{Type: reflect.TypeOf(value.Interface())}
+ }
+ }
+
+ // A map may have method too.
+ for i := 0; i < t.NumMethod(); i++ {
+ m := t.Method(i)
+ types[m.Name] = Tag{
+ Type: m.Type,
+ Method: true,
+ MethodIndex: i,
+ }
+ }
+ }
+
+ return types
+}
+
+func FieldsFromStruct(t reflect.Type) TypesTable {
+ types := make(TypesTable)
+ t = dereference(t)
+ if t == nil {
+ return types
+ }
+
+ switch t.Kind() {
+ case reflect.Struct:
+ for i := 0; i < t.NumField(); i++ {
+ f := t.Field(i)
+
+ if f.Anonymous {
+ for name, typ := range FieldsFromStruct(f.Type) {
+ if _, ok := types[name]; ok {
+ types[name] = Tag{Ambiguous: true}
+ } else {
+ typ.FieldIndex = append(f.Index, typ.FieldIndex...)
+ types[name] = typ
+ }
+ }
+ }
+ if fn := FieldName(f); fn == "$env" { // Could check for all keywords here
+ panic("attempt to misuse env keyword as env struct field tag")
+ } else {
+ types[FieldName(f)] = Tag{
+ Type: f.Type,
+ FieldIndex: f.Index,
+ }
+ }
+ }
+ }
+
+ return types
+}
+
+func dereference(t reflect.Type) reflect.Type {
+ if t == nil {
+ return nil
+ }
+ if t.Kind() == reflect.Ptr {
+ t = dereference(t.Elem())
+ }
+ return t
+}
+
+func kind(t reflect.Type) reflect.Kind {
+ if t == nil {
+ return reflect.Invalid
+ }
+ return t.Kind()
+}
+
+func FieldName(field reflect.StructField) string {
+ if taggedName := field.Tag.Get("expr"); taggedName != "" {
+ return taggedName
+ }
+ return field.Name
+}
diff --git a/vendor/github.com/antonmedv/expr/expr.go b/vendor/github.com/antonmedv/expr/expr.go
new file mode 100644
index 00000000000..eb9eb7683e7
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/expr.go
@@ -0,0 +1,231 @@
+package expr
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/antonmedv/expr/ast"
+ "github.com/antonmedv/expr/checker"
+ "github.com/antonmedv/expr/compiler"
+ "github.com/antonmedv/expr/conf"
+ "github.com/antonmedv/expr/file"
+ "github.com/antonmedv/expr/optimizer"
+ "github.com/antonmedv/expr/parser"
+ "github.com/antonmedv/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) {
+ c.Operator(operator, fn...)
+ }
+}
+
+// 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
+ }
+}
+
+// AsBool tells the compiler to expect a boolean result.
+func AsBool() Option {
+ return func(c *conf.Config) {
+ c.Expect = reflect.Bool
+ }
+}
+
+// AsInt tells the compiler to expect an int result.
+func AsInt() Option {
+ return func(c *conf.Config) {
+ c.Expect = reflect.Int
+ }
+}
+
+// AsInt64 tells the compiler to expect an int64 result.
+func AsInt64() Option {
+ return func(c *conf.Config) {
+ c.Expect = reflect.Int64
+ }
+}
+
+// AsFloat64 tells the compiler to expect a float64 result.
+func AsFloat64() Option {
+ return func(c *conf.Config) {
+ c.Expect = reflect.Float64
+ }
+}
+
+// Optimize turns optimizations on or off.
+func Optimize(b bool) Option {
+ return func(c *conf.Config) {
+ c.Optimize = b
+ }
+}
+
+// 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] = &ast.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)
+ }
+}
+
+// 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()
+
+ if len(config.Operators) > 0 {
+ config.Visitors = append(config.Visitors, &conf.OperatorPatcher{
+ Operators: config.Operators,
+ Types: config.Types,
+ })
+ }
+
+ tree, err := parser.ParseWithConfig(input, config)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(config.Visitors) > 0 {
+ for _, v := range config.Visitors {
+ // We need to perform types check, because some visitors may rely on
+ // types information available in the tree.
+ _, _ = checker.Check(tree, config)
+ ast.Walk(&tree.Node, v)
+ }
+ }
+ _, err = checker.Check(tree, config)
+ if err != nil {
+ return nil, err
+ }
+
+ if config.Optimize {
+ err = optimizer.Optimize(&tree.Node, config)
+ if err != nil {
+ if fileError, ok := err.(*file.Error); ok {
+ 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")
+ }
+
+ program, err := Compile(input)
+ 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/antonmedv/expr/file/error.go b/vendor/github.com/antonmedv/expr/file/error.go
new file mode 100644
index 00000000000..edf202b0456
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/file/error.go
@@ -0,0 +1,67 @@
+package file
+
+import (
+ "fmt"
+ "strings"
+ "unicode/utf8"
+)
+
+type Error struct {
+ Location
+ Message string
+ Snippet string
+ Prev error
+}
+
+func (e *Error) Error() string {
+ return e.format()
+}
+
+func (e *Error) Bind(source *Source) *Error {
+ if snippet, found := source.Snippet(e.Location.Line); found {
+ snippet := strings.Replace(snippet, "\t", " ", -1)
+ srcLine := "\n | " + snippet
+ var bytes = []byte(snippet)
+ var indLine = "\n | "
+ for i := 0; i < e.Location.Column && len(bytes) > 0; i++ {
+ _, sz := utf8.DecodeRune(bytes)
+ bytes = bytes[sz:]
+ if sz > 1 {
+ goto noind
+ } else {
+ indLine += "."
+ }
+ }
+ if _, sz := utf8.DecodeRune(bytes); sz > 1 {
+ goto noind
+ } else {
+ indLine += "^"
+ }
+ srcLine += indLine
+
+ noind:
+ e.Snippet = srcLine
+ }
+ 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.Location.Empty() {
+ 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/antonmedv/expr/file/location.go b/vendor/github.com/antonmedv/expr/file/location.go
new file mode 100644
index 00000000000..a92e27f0b1c
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/file/location.go
@@ -0,0 +1,10 @@
+package file
+
+type Location struct {
+ Line int // The 1-based line of the location.
+ Column int // The 0-based column number of the location.
+}
+
+func (l Location) Empty() bool {
+ return l.Column == 0 && l.Line == 0
+}
diff --git a/vendor/github.com/antonmedv/expr/file/source.go b/vendor/github.com/antonmedv/expr/file/source.go
new file mode 100644
index 00000000000..9ee297b5802
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/file/source.go
@@ -0,0 +1,76 @@
+package file
+
+import (
+ "encoding/json"
+ "strings"
+ "unicode/utf8"
+)
+
+type Source struct {
+ contents []rune
+ lineOffsets []int32
+}
+
+func NewSource(contents string) *Source {
+ s := &Source{
+ contents: []rune(contents),
+ }
+ s.updateOffsets()
+ return s
+}
+
+func (s *Source) MarshalJSON() ([]byte, error) {
+ return json.Marshal(s.contents)
+}
+
+func (s *Source) UnmarshalJSON(b []byte) error {
+ contents := make([]rune, 0)
+ err := json.Unmarshal(b, &contents)
+ if err != nil {
+ return err
+ }
+
+ s.contents = contents
+ s.updateOffsets()
+ return nil
+}
+
+func (s *Source) Content() string {
+ return string(s.contents)
+}
+
+func (s *Source) Snippet(line int) (string, bool) {
+ charStart, found := s.findLineOffset(line)
+ if !found || len(s.contents) == 0 {
+ return "", false
+ }
+ charEnd, found := s.findLineOffset(line + 1)
+ if found {
+ return string(s.contents[charStart : charEnd-1]), true
+ }
+ return string(s.contents[charStart:]), true
+}
+
+// updateOffsets compute line offsets up front as they are referred to frequently.
+func (s *Source) updateOffsets() {
+ lines := strings.Split(string(s.contents), "\n")
+ offsets := make([]int32, len(lines))
+ var offset int32
+ for i, line := range lines {
+ offset = offset + int32(utf8.RuneCountInString(line)) + 1
+ offsets[int32(i)] = offset
+ }
+ s.lineOffsets = offsets
+}
+
+// findLineOffset returns the offset where the (1-indexed) line begins,
+// or false if line doesn't exist.
+func (s *Source) findLineOffset(line int) (int32, bool) {
+ if line == 1 {
+ return 0, true
+ } else if line > 1 && line <= len(s.lineOffsets) {
+ offset := s.lineOffsets[line-2]
+ return offset, true
+ }
+ return -1, false
+}
diff --git a/vendor/github.com/antonmedv/expr/optimizer/const_expr.go b/vendor/github.com/antonmedv/expr/optimizer/const_expr.go
new file mode 100644
index 00000000000..694c88bcf16
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/optimizer/const_expr.go
@@ -0,0 +1,85 @@
+package optimizer
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+
+ . "github.com/antonmedv/expr/ast"
+ "github.com/antonmedv/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,
+ }
+ }
+ }()
+
+ patch := func(newNode Node) {
+ c.applied = true
+ Patch(node, newNode)
+ }
+
+ 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}
+ patch(constNode)
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/optimizer/const_range.go b/vendor/github.com/antonmedv/expr/optimizer/const_range.go
new file mode 100644
index 00000000000..26d6d6f571b
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/optimizer/const_range.go
@@ -0,0 +1,40 @@
+package optimizer
+
+import (
+ . "github.com/antonmedv/expr/ast"
+)
+
+type constRange struct{}
+
+func (*constRange) Visit(node *Node) {
+ switch n := (*node).(type) {
+ case *BinaryNode:
+ if n.Operator == ".." {
+ if min, ok := n.Left.(*IntegerNode); ok {
+ if max, ok := n.Right.(*IntegerNode); ok {
+ size := max.Value - min.Value + 1
+ // In case the max < min, patch empty slice
+ // as max must be greater than equal to min.
+ if size < 1 {
+ Patch(node, &ConstantNode{
+ Value: make([]int, 0),
+ })
+ return
+ }
+ // In this case array is too big. Skip generation,
+ // and wait for memory budget detection on runtime.
+ if size > 1e6 {
+ return
+ }
+ value := make([]int, size)
+ for i := range value {
+ value[i] = min.Value + i
+ }
+ Patch(node, &ConstantNode{
+ Value: value,
+ })
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/optimizer/filter_first.go b/vendor/github.com/antonmedv/expr/optimizer/filter_first.go
new file mode 100644
index 00000000000..852e2c268aa
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/optimizer/filter_first.go
@@ -0,0 +1,38 @@
+package optimizer
+
+import (
+ . "github.com/antonmedv/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 {
+ Patch(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 {
+ Patch(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/antonmedv/expr/optimizer/filter_last.go b/vendor/github.com/antonmedv/expr/optimizer/filter_last.go
new file mode 100644
index 00000000000..0a072004be5
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/optimizer/filter_last.go
@@ -0,0 +1,38 @@
+package optimizer
+
+import (
+ . "github.com/antonmedv/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 {
+ Patch(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 {
+ Patch(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/antonmedv/expr/optimizer/filter_len.go b/vendor/github.com/antonmedv/expr/optimizer/filter_len.go
new file mode 100644
index 00000000000..2293de81d7d
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/optimizer/filter_len.go
@@ -0,0 +1,22 @@
+package optimizer
+
+import (
+ . "github.com/antonmedv/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 {
+ Patch(node, &BuiltinNode{
+ Name: "count",
+ Arguments: filter.Arguments,
+ })
+ }
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/optimizer/filter_map.go b/vendor/github.com/antonmedv/expr/optimizer/filter_map.go
new file mode 100644
index 00000000000..9044ac34d53
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/optimizer/filter_map.go
@@ -0,0 +1,25 @@
+package optimizer
+
+import (
+ . "github.com/antonmedv/expr/ast"
+)
+
+type filterMap struct{}
+
+func (*filterMap) Visit(node *Node) {
+ if mapBuiltin, ok := (*node).(*BuiltinNode); ok &&
+ mapBuiltin.Name == "map" &&
+ len(mapBuiltin.Arguments) == 2 {
+ if closure, ok := mapBuiltin.Arguments[1].(*ClosureNode); ok {
+ if filter, ok := mapBuiltin.Arguments[0].(*BuiltinNode); ok &&
+ filter.Name == "filter" &&
+ filter.Map == nil /* not already optimized */ {
+ Patch(node, &BuiltinNode{
+ Name: "filter",
+ Arguments: filter.Arguments,
+ Map: closure.Node,
+ })
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/optimizer/fold.go b/vendor/github.com/antonmedv/expr/optimizer/fold.go
new file mode 100644
index 00000000000..670ebec094b
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/optimizer/fold.go
@@ -0,0 +1,357 @@
+package optimizer
+
+import (
+ "fmt"
+ "math"
+ "reflect"
+
+ . "github.com/antonmedv/expr/ast"
+ "github.com/antonmedv/expr/file"
+)
+
+var (
+ integerType = reflect.TypeOf(0)
+ floatType = reflect.TypeOf(float64(0))
+ stringType = reflect.TypeOf("")
+)
+
+type fold struct {
+ applied bool
+ err *file.Error
+}
+
+func (fold *fold) Visit(node *Node) {
+ patch := func(newNode Node) {
+ fold.applied = true
+ Patch(node, newNode)
+ }
+ patchWithType := func(newNode Node) {
+ patch(newNode)
+ switch newNode.(type) {
+ case *IntegerNode:
+ newNode.SetType(integerType)
+ case *FloatNode:
+ newNode.SetType(floatType)
+ case *StringNode:
+ newNode.SetType(stringType)
+ default:
+ panic(fmt.Sprintf("unknown type %T", newNode))
+ }
+ }
+
+ switch n := (*node).(type) {
+ case *UnaryNode:
+ switch n.Operator {
+ case "-":
+ if i, ok := n.Node.(*IntegerNode); ok {
+ patchWithType(&IntegerNode{Value: -i.Value})
+ }
+ if i, ok := n.Node.(*FloatNode); ok {
+ patchWithType(&FloatNode{Value: -i.Value})
+ }
+ case "+":
+ if i, ok := n.Node.(*IntegerNode); ok {
+ patchWithType(&IntegerNode{Value: i.Value})
+ }
+ if i, ok := n.Node.(*FloatNode); ok {
+ patchWithType(&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 {
+ patchWithType(&IntegerNode{Value: a.Value + b.Value})
+ }
+ }
+ {
+ a := toInteger(n.Left)
+ b := toFloat(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: float64(a.Value) + b.Value})
+ }
+ }
+ {
+ a := toFloat(n.Left)
+ b := toInteger(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: a.Value + float64(b.Value)})
+ }
+ }
+ {
+ a := toFloat(n.Left)
+ b := toFloat(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&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 {
+ patchWithType(&IntegerNode{Value: a.Value - b.Value})
+ }
+ }
+ {
+ a := toInteger(n.Left)
+ b := toFloat(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: float64(a.Value) - b.Value})
+ }
+ }
+ {
+ a := toFloat(n.Left)
+ b := toInteger(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: a.Value - float64(b.Value)})
+ }
+ }
+ {
+ a := toFloat(n.Left)
+ b := toFloat(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: a.Value - b.Value})
+ }
+ }
+ case "*":
+ {
+ a := toInteger(n.Left)
+ b := toInteger(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&IntegerNode{Value: a.Value * b.Value})
+ }
+ }
+ {
+ a := toInteger(n.Left)
+ b := toFloat(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: float64(a.Value) * b.Value})
+ }
+ }
+ {
+ a := toFloat(n.Left)
+ b := toInteger(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: a.Value * float64(b.Value)})
+ }
+ }
+ {
+ a := toFloat(n.Left)
+ b := toFloat(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: a.Value * b.Value})
+ }
+ }
+ case "/":
+ {
+ a := toInteger(n.Left)
+ b := toInteger(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: float64(a.Value) / float64(b.Value)})
+ }
+ }
+ {
+ a := toInteger(n.Left)
+ b := toFloat(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: float64(a.Value) / b.Value})
+ }
+ }
+ {
+ a := toFloat(n.Left)
+ b := toInteger(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: a.Value / float64(b.Value)})
+ }
+ }
+ {
+ a := toFloat(n.Left)
+ b := toFloat(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&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 {
+ patchWithType(&FloatNode{Value: math.Pow(float64(a.Value), float64(b.Value))})
+ }
+ }
+ {
+ a := toInteger(n.Left)
+ b := toFloat(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: math.Pow(float64(a.Value), b.Value)})
+ }
+ }
+ {
+ a := toFloat(n.Left)
+ b := toInteger(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&FloatNode{Value: math.Pow(a.Value, float64(b.Value))})
+ }
+ }
+ {
+ a := toFloat(n.Left)
+ b := toFloat(n.Right)
+ if a != nil && b != nil {
+ patchWithType(&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
+ patch(n.Right)
+ } else if b != nil && b.Value { // x and true
+ patch(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
+ patch(n.Right)
+ } else if b != nil && !b.Value { // x or false
+ patch(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:
+ switch n.Name {
+ case "filter":
+ if len(n.Arguments) != 2 {
+ return
+ }
+ if base, ok := n.Arguments[0].(*BuiltinNode); ok && base.Name == "filter" {
+ patch(&BuiltinNode{
+ Name: "filter",
+ Arguments: []Node{
+ base.Arguments[0],
+ &BinaryNode{
+ Operator: "&&",
+ Left: base.Arguments[1],
+ Right: n.Arguments[1],
+ },
+ },
+ })
+ }
+ }
+ }
+}
+
+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/antonmedv/expr/optimizer/in_array.go b/vendor/github.com/antonmedv/expr/optimizer/in_array.go
new file mode 100644
index 00000000000..a51957631c0
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/optimizer/in_array.go
@@ -0,0 +1,64 @@
+package optimizer
+
+import (
+ "reflect"
+
+ . "github.com/antonmedv/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{}{}
+ }
+ Patch(node, &BinaryNode{
+ Operator: n.Operator,
+ Left: n.Left,
+ Right: &ConstantNode{Value: value},
+ })
+ }
+
+ 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{}{}
+ }
+ Patch(node, &BinaryNode{
+ Operator: n.Operator,
+ Left: n.Left,
+ Right: &ConstantNode{Value: value},
+ })
+ }
+
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/optimizer/in_range.go b/vendor/github.com/antonmedv/expr/optimizer/in_range.go
new file mode 100644
index 00000000000..b361c6c39b2
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/optimizer/in_range.go
@@ -0,0 +1,43 @@
+package optimizer
+
+import (
+ "reflect"
+
+ . "github.com/antonmedv/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 {
+ Patch(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/antonmedv/expr/optimizer/optimizer.go b/vendor/github.com/antonmedv/expr/optimizer/optimizer.go
new file mode 100644
index 00000000000..15f50fd7363
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/optimizer/optimizer.go
@@ -0,0 +1,41 @@
+package optimizer
+
+import (
+ . "github.com/antonmedv/expr/ast"
+ "github.com/antonmedv/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, &constRange{})
+ Walk(node, &filterMap{})
+ Walk(node, &filterLen{})
+ Walk(node, &filterLast{})
+ Walk(node, &filterFirst{})
+ return nil
+}
diff --git a/vendor/github.com/antonmedv/expr/parser/lexer/lexer.go b/vendor/github.com/antonmedv/expr/parser/lexer/lexer.go
new file mode 100644
index 00000000000..5db2dcbb52c
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/parser/lexer/lexer.go
@@ -0,0 +1,221 @@
+package lexer
+
+import (
+ "fmt"
+ "strings"
+ "unicode/utf8"
+
+ "github.com/antonmedv/expr/file"
+)
+
+func Lex(source *file.Source) ([]Token, error) {
+ l := &lexer{
+ input: source.Content(),
+ tokens: make([]Token, 0),
+ }
+
+ l.loc = file.Location{Line: 1, Column: 0}
+ l.prev = l.loc
+ l.startLoc = l.loc
+
+ for state := root; state != nil; {
+ state = state(l)
+ }
+
+ if l.err != nil {
+ return nil, l.err.Bind(source)
+ }
+
+ return l.tokens, nil
+}
+
+type lexer struct {
+ input string
+ tokens []Token
+ start, end int // current position in input
+ width int // last rune width
+ startLoc file.Location // start location
+ prev, loc file.Location // prev location of end location, end location
+ err *file.Error
+}
+
+const eof rune = -1
+
+func (l *lexer) next() rune {
+ if l.end >= len(l.input) {
+ l.width = 0
+ return eof
+ }
+ r, w := utf8.DecodeRuneInString(l.input[l.end:])
+ l.width = w
+ l.end += w
+
+ l.prev = l.loc
+ if r == '\n' {
+ l.loc.Line++
+ l.loc.Column = 0
+ } else {
+ l.loc.Column++
+ }
+
+ return r
+}
+
+func (l *lexer) peek() rune {
+ r := l.next()
+ l.backup()
+ return r
+}
+
+func (l *lexer) backup() {
+ l.end -= l.width
+ l.loc = l.prev
+}
+
+func (l *lexer) emit(t Kind) {
+ l.emitValue(t, l.word())
+}
+
+func (l *lexer) emitValue(t Kind, value string) {
+ l.tokens = append(l.tokens, Token{
+ Location: l.startLoc,
+ Kind: t,
+ Value: value,
+ })
+ l.start = l.end
+ l.startLoc = l.loc
+}
+
+func (l *lexer) emitEOF() {
+ l.tokens = append(l.tokens, Token{
+ Location: l.prev, // Point to previous position for better error messages.
+ Kind: EOF,
+ })
+ l.start = l.end
+ l.startLoc = l.loc
+}
+
+func (l *lexer) skip() {
+ l.start = l.end
+ l.startLoc = l.loc
+}
+
+func (l *lexer) word() string {
+ return l.input[l.start:l.end]
+}
+
+func (l *lexer) ignore() {
+ l.start = l.end
+ l.startLoc = l.loc
+}
+
+func (l *lexer) accept(valid string) bool {
+ if strings.ContainsRune(valid, l.next()) {
+ return true
+ }
+ l.backup()
+ return false
+}
+
+func (l *lexer) acceptRun(valid string) {
+ for strings.ContainsRune(valid, l.next()) {
+ }
+ l.backup()
+}
+
+func (l *lexer) skipSpaces() {
+ r := l.peek()
+ for ; r == ' '; r = l.peek() {
+ l.next()
+ }
+ l.skip()
+}
+
+func (l *lexer) acceptWord(word string) bool {
+ pos, loc, prev := l.end, l.loc, l.prev
+
+ l.skipSpaces()
+
+ for _, ch := range word {
+ if l.next() != ch {
+ l.end, l.loc, l.prev = pos, loc, prev
+ return false
+ }
+ }
+ if r := l.peek(); r != ' ' && r != eof {
+ l.end, l.loc, l.prev = pos, loc, prev
+ return false
+ }
+
+ return true
+}
+
+func (l *lexer) error(format string, args ...any) stateFn {
+ if l.err == nil { // show first error
+ l.err = &file.Error{
+ Location: l.loc,
+ 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':
+ 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
+}
diff --git a/vendor/github.com/antonmedv/expr/parser/lexer/state.go b/vendor/github.com/antonmedv/expr/parser/lexer/state.go
new file mode 100644
index 00000000000..bb150878a6a
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/parser/lexer/state.go
@@ -0,0 +1,223 @@
+package lexer
+
+import (
+ "strings"
+
+ "github.com/antonmedv/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.ignore()
+ 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 '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 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)
+ loc, prev, end := l.loc, l.prev, 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.loc, l.prev, l.end = loc, prev, 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":
+ l.emit(Operator)
+ case "let":
+ l.emit(Operator)
+ default:
+ l.emit(Identifier)
+ }
+ break loop
+ }
+ }
+ return root
+}
+
+func not(l *lexer) stateFn {
+ l.emit(Operator)
+
+ l.skipSpaces()
+
+ pos, loc, prev := l.end, l.loc, l.prev
+
+ // 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, l.loc, l.prev = pos, loc, prev
+ }
+ 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.ignore()
+ 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.ignore()
+ 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/antonmedv/expr/parser/lexer/token.go b/vendor/github.com/antonmedv/expr/parser/lexer/token.go
new file mode 100644
index 00000000000..8917b26dce6
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/parser/lexer/token.go
@@ -0,0 +1,47 @@
+package lexer
+
+import (
+ "fmt"
+
+ "github.com/antonmedv/expr/file"
+)
+
+type Kind string
+
+const (
+ Identifier Kind = "Identifier"
+ Number Kind = "Number"
+ String Kind = "String"
+ 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 len(values) == 0 {
+ return kind == t.Kind
+ }
+
+ for _, v := range values {
+ if v == t.Value {
+ goto found
+ }
+ }
+ return false
+
+found:
+ return kind == t.Kind
+}
diff --git a/vendor/github.com/antonmedv/expr/parser/lexer/utils.go b/vendor/github.com/antonmedv/expr/parser/lexer/utils.go
new file mode 100644
index 00000000000..5c9e6b59dee
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/parser/lexer/utils.go
@@ -0,0 +1,186 @@
+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 := make([]byte, 0, size)
+ for len(value) > 0 {
+ c, multibyte, rest, err := unescapeChar(value)
+ if err != nil {
+ return "", err
+ }
+ value = rest
+ if c < utf8.RuneSelf || !multibyte {
+ buf = append(buf, byte(c))
+ } else {
+ n := utf8.EncodeRune(runeTmp[:], c)
+ buf = append(buf, runeTmp[:n]...)
+ }
+ }
+ return string(buf), 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':
+ 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
+}
+
+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/antonmedv/expr/parser/operator/operator.go b/vendor/github.com/antonmedv/expr/parser/operator/operator.go
new file mode 100644
index 00000000000..455c06f17ce
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/parser/operator/operator.go
@@ -0,0 +1,52 @@
+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
+}
+
+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},
+}
diff --git a/vendor/github.com/antonmedv/expr/parser/parser.go b/vendor/github.com/antonmedv/expr/parser/parser.go
new file mode 100644
index 00000000000..d4a870f715d
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/parser/parser.go
@@ -0,0 +1,682 @@
+package parser
+
+import (
+ "fmt"
+ "math"
+ "strconv"
+ "strings"
+
+ . "github.com/antonmedv/expr/ast"
+ "github.com/antonmedv/expr/builtin"
+ "github.com/antonmedv/expr/conf"
+ "github.com/antonmedv/expr/file"
+ . "github.com/antonmedv/expr/parser/lexer"
+ "github.com/antonmedv/expr/parser/operator"
+ "github.com/antonmedv/expr/parser/utils"
+)
+
+var predicates = map[string]struct {
+ arity int
+}{
+ "all": {2},
+ "none": {2},
+ "any": {2},
+ "one": {2},
+ "filter": {2},
+ "map": {2},
+ "count": {2},
+ "find": {2},
+ "findIndex": {2},
+ "findLast": {2},
+ "findLastIndex": {2},
+ "groupBy": {2},
+ "reduce": {3},
+}
+
+type parser struct {
+ tokens []Token
+ current Token
+ pos int
+ err *file.Error
+ depth int // closure call depth
+ config *conf.Config
+}
+
+type Tree struct {
+ Node Node
+ Source *file.Source
+}
+
+func Parse(input string) (*Tree, error) {
+ return ParseWithConfig(input, &conf.Config{
+ Disabled: map[string]bool{},
+ })
+}
+
+func ParseWithConfig(input string, config *conf.Config) (*Tree, error) {
+ source := file.NewSource(input)
+
+ tokens, err := Lex(source)
+ if err != nil {
+ return nil, err
+ }
+
+ p := &parser{
+ tokens: tokens,
+ current: tokens[0],
+ config: config,
+ }
+
+ node := p.parseExpression(0)
+
+ if !p.current.Is(EOF) {
+ p.error("unexpected token %v", p.current)
+ }
+
+ if p.err != nil {
+ return nil, p.err.Bind(source)
+ }
+
+ return &Tree{
+ Node: node,
+ Source: source,
+ }, nil
+}
+
+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() {
+ p.pos++
+ if p.pos >= len(p.tokens) {
+ p.error("unexpected end of expression")
+ return
+ }
+ p.current = p.tokens[p.pos]
+}
+
+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) parseExpression(precedence int) Node {
+ if precedence == 0 {
+ if p.current.Is(Operator, "let") {
+ return p.parseVariableDeclaration()
+ }
+ }
+
+ nodeLeft := p.parsePrimary()
+
+ prevOperator := ""
+ opToken := p.current
+ for opToken.Is(Operator) && p.err == nil {
+ negate := false
+ var notToken Token
+
+ // Handle "not *" operator, like "not in" or "not contains".
+ if opToken.Is(Operator, "not") {
+ p.next()
+ notToken = p.current
+ negate = true
+ opToken = p.current
+ }
+
+ if op, ok := operator.Binary[opToken.Value]; ok {
+ if op.Precedence >= precedence {
+ p.next()
+
+ if opToken.Value == "|" {
+ nodeLeft = p.parsePipe(nodeLeft)
+ 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
+ }
+
+ var nodeRight Node
+ if op.Associativity == operator.Left {
+ nodeRight = p.parseExpression(op.Precedence + 1)
+ } else {
+ nodeRight = p.parseExpression(op.Precedence)
+ }
+
+ nodeLeft = &BinaryNode{
+ Operator: opToken.Value,
+ Left: nodeLeft,
+ Right: nodeRight,
+ }
+ nodeLeft.SetLocation(opToken.Location)
+
+ if negate {
+ nodeLeft = &UnaryNode{
+ Operator: "not",
+ Node: nodeLeft,
+ }
+ nodeLeft.SetLocation(notToken.Location)
+ }
+
+ 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.parseExpression(0)
+ let := &VariableDeclaratorNode{
+ Name: variableName.Value,
+ Value: value,
+ Expr: node,
+ }
+ let.SetLocation(variableName.Location)
+ return let
+}
+
+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 = &ConditionalNode{
+ Cond: node,
+ Exp1: expr1,
+ Exp2: expr2,
+ }
+ }
+ 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 := &UnaryNode{
+ Operator: token.Value,
+ Node: expr,
+ }
+ node.SetLocation(token.Location)
+ return p.parsePostfixExpression(node)
+ }
+ }
+
+ if token.Is(Bracket, "(") {
+ p.next()
+ expr := p.parseExpression(0)
+ 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 := &PointerNode{Name: name}
+ node.SetLocation(token.Location)
+ return p.parsePostfixExpression(node)
+ }
+ } else {
+ if token.Is(Operator, "#") || token.Is(Operator, ".") {
+ p.error("cannot use pointer accessor outside closure")
+ }
+ }
+
+ 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 := &BoolNode{Value: true}
+ node.SetLocation(token.Location)
+ return node
+ case "false":
+ node := &BoolNode{Value: false}
+ node.SetLocation(token.Location)
+ return node
+ case "nil":
+ node := &NilNode{}
+ node.SetLocation(token.Location)
+ return node
+ default:
+ node = p.parseCall(token)
+ }
+
+ case Number:
+ p.next()
+ value := strings.Replace(token.Value, "_", "", -1)
+ if strings.Contains(value, "x") {
+ number, err := strconv.ParseInt(value, 0, 64)
+ if err != nil {
+ p.error("invalid hex literal: %v", err)
+ }
+ if number > math.MaxInt {
+ p.error("integer literal is too large")
+ return nil
+ }
+ node := &IntegerNode{Value: int(number)}
+ node.SetLocation(token.Location)
+ return node
+ } else if strings.ContainsAny(value, ".eE") {
+ number, err := strconv.ParseFloat(value, 64)
+ if err != nil {
+ p.error("invalid float literal: %v", err)
+ }
+ node := &FloatNode{Value: number}
+ node.SetLocation(token.Location)
+ return node
+ } else {
+ number, err := strconv.ParseInt(value, 10, 64)
+ if err != nil {
+ p.error("invalid integer literal: %v", err)
+ }
+ if number > math.MaxInt {
+ p.error("integer literal is too large")
+ return nil
+ }
+ node := &IntegerNode{Value: int(number)}
+ node.SetLocation(token.Location)
+ return node
+ }
+
+ case String:
+ p.next()
+ node := &StringNode{Value: token.Value}
+ node.SetLocation(token.Location)
+ return node
+
+ 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) parseCall(token Token) Node {
+ var node Node
+ if p.current.Is(Bracket, "(") {
+ var arguments []Node
+
+ if b, ok := predicates[token.Value]; ok {
+ p.expect(Bracket, "(")
+
+ // TODO: Refactor parser to use builtin.Builtins instead of predicates map.
+
+ if b.arity == 1 {
+ arguments = make([]Node, 1)
+ arguments[0] = p.parseExpression(0)
+ } else if b.arity == 2 {
+ arguments = make([]Node, 2)
+ arguments[0] = p.parseExpression(0)
+ p.expect(Operator, ",")
+ arguments[1] = p.parseClosure()
+ }
+
+ if token.Value == "reduce" {
+ arguments = make([]Node, 2)
+ arguments[0] = p.parseExpression(0)
+ p.expect(Operator, ",")
+ arguments[1] = p.parseClosure()
+ if p.current.Is(Operator, ",") {
+ p.next()
+ arguments = append(arguments, p.parseExpression(0))
+ }
+ }
+
+ p.expect(Bracket, ")")
+
+ node = &BuiltinNode{
+ Name: token.Value,
+ Arguments: arguments,
+ }
+ node.SetLocation(token.Location)
+ } else if _, ok := builtin.Index[token.Value]; ok && !p.config.Disabled[token.Value] {
+ node = &BuiltinNode{
+ Name: token.Value,
+ Arguments: p.parseArguments(),
+ }
+ node.SetLocation(token.Location)
+ } else {
+ callee := &IdentifierNode{Value: token.Value}
+ callee.SetLocation(token.Location)
+ node = &CallNode{
+ Callee: callee,
+ Arguments: p.parseArguments(),
+ }
+ node.SetLocation(token.Location)
+ }
+ } else {
+ node = &IdentifierNode{Value: token.Value}
+ node.SetLocation(token.Location)
+ }
+ return node
+}
+
+func (p *parser) parseClosure() Node {
+ startToken := p.current
+ expectClosingBracket := false
+ if p.current.Is(Bracket, "{") {
+ p.next()
+ expectClosingBracket = true
+ }
+
+ p.depth++
+ node := p.parseExpression(0)
+ p.depth--
+
+ if expectClosingBracket {
+ p.expect(Bracket, "}")
+ }
+ closure := &ClosureNode{
+ Node: node,
+ }
+ closure.SetLocation(startToken.Location)
+ return closure
+}
+
+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 := &ArrayNode{Nodes: nodes}
+ node.SetLocation(token.Location)
+ 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 = &StringNode{Value: p.current.Value}
+ key.SetLocation(token.Location)
+ 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 := &PairNode{Key: key, Value: node}
+ pair.SetLocation(token.Location)
+ nodes = append(nodes, pair)
+ }
+
+end:
+ p.expect(Bracket, "}")
+
+ node := &MapNode{Pairs: nodes}
+ node.SetLocation(token.Location)
+ return node
+}
+
+func (p *parser) parsePostfixExpression(node Node) Node {
+ postfixToken := p.current
+ for (postfixToken.Is(Operator) || postfixToken.Is(Bracket)) && p.err == nil {
+ if postfixToken.Value == "." || postfixToken.Value == "?." {
+ p.next()
+
+ propertyToken := p.current
+ 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 := &StringNode{Value: propertyToken.Value}
+ property.SetLocation(propertyToken.Location)
+
+ chainNode, isChain := node.(*ChainNode)
+ optional := postfixToken.Value == "?."
+
+ if isChain {
+ node = chainNode.Node
+ }
+
+ memberNode := &MemberNode{
+ Node: node,
+ Property: property,
+ Optional: optional,
+ }
+ memberNode.SetLocation(propertyToken.Location)
+
+ if p.current.Is(Bracket, "(") {
+ node = &CallNode{
+ Callee: memberNode,
+ Arguments: p.parseArguments(),
+ }
+ node.SetLocation(propertyToken.Location)
+ } else {
+ node = memberNode
+ }
+
+ if isChain || optional {
+ node = &ChainNode{Node: node}
+ }
+
+ } 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 = &SliceNode{
+ Node: node,
+ To: to,
+ }
+ node.SetLocation(postfixToken.Location)
+ 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 = &SliceNode{
+ Node: node,
+ From: from,
+ To: to,
+ }
+ node.SetLocation(postfixToken.Location)
+ p.expect(Bracket, "]")
+
+ } else {
+ // Slice operator [:] was not found,
+ // it should be just an index node.
+ node = &MemberNode{
+ Node: node,
+ Property: from,
+ }
+ node.SetLocation(postfixToken.Location)
+ p.expect(Bracket, "]")
+ }
+ }
+ } else {
+ break
+ }
+ postfixToken = p.current
+ }
+ return node
+}
+
+func (p *parser) parsePipe(node Node) Node {
+ identifier := p.current
+ p.expect(Identifier)
+
+ arguments := []Node{node}
+
+ if b, ok := predicates[identifier.Value]; ok {
+ p.expect(Bracket, "(")
+
+ // TODO: Refactor parser to use builtin.Builtins instead of predicates map.
+
+ if b.arity == 2 {
+ arguments = append(arguments, p.parseClosure())
+ }
+
+ if identifier.Value == "reduce" {
+ arguments = append(arguments, p.parseClosure())
+ if p.current.Is(Operator, ",") {
+ p.next()
+ arguments = append(arguments, p.parseExpression(0))
+ }
+ }
+
+ p.expect(Bracket, ")")
+
+ node = &BuiltinNode{
+ Name: identifier.Value,
+ Arguments: arguments,
+ }
+ node.SetLocation(identifier.Location)
+ } else if _, ok := builtin.Index[identifier.Value]; ok {
+ arguments = append(arguments, p.parseArguments()...)
+
+ node = &BuiltinNode{
+ Name: identifier.Value,
+ Arguments: arguments,
+ }
+ node.SetLocation(identifier.Location)
+ } else {
+ callee := &IdentifierNode{Value: identifier.Value}
+ callee.SetLocation(identifier.Location)
+
+ arguments = append(arguments, p.parseArguments()...)
+
+ node = &CallNode{
+ Callee: callee,
+ Arguments: arguments,
+ }
+ node.SetLocation(identifier.Location)
+ }
+
+ return node
+}
+
+func (p *parser) parseArguments() []Node {
+ p.expect(Bracket, "(")
+ nodes := make([]Node, 0)
+ for !p.current.Is(Bracket, ")") && p.err == nil {
+ if len(nodes) > 0 {
+ p.expect(Operator, ",")
+ }
+ node := p.parseExpression(0)
+ nodes = append(nodes, node)
+ }
+ p.expect(Bracket, ")")
+
+ return nodes
+}
diff --git a/vendor/github.com/antonmedv/expr/parser/utils/utils.go b/vendor/github.com/antonmedv/expr/parser/utils/utils.go
new file mode 100644
index 00000000000..947f9a4008d
--- /dev/null
+++ b/vendor/github.com/antonmedv/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/antonmedv/expr/vm/generated.go b/vendor/github.com/antonmedv/expr/vm/generated.go
new file mode 100644
index 00000000000..1a0b3657189
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/vm/generated.go
@@ -0,0 +1,262 @@
+// 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() []uint8),
+ 6: new(func() []any),
+ 7: new(func() bool),
+ 8: new(func() uint8),
+ 9: new(func() float64),
+ 10: new(func() int),
+ 11: new(func() int64),
+ 12: new(func() any),
+ 13: new(func() map[string]any),
+ 14: new(func() int32),
+ 15: new(func() string),
+ 16: new(func() uint),
+ 17: new(func() uint64),
+ 18: new(func(time.Duration) time.Duration),
+ 19: new(func(time.Duration) time.Time),
+ 20: new(func(time.Time) time.Duration),
+ 21: new(func(time.Time) bool),
+ 22: new(func([]any, string) string),
+ 23: new(func([]string, string) string),
+ 24: new(func(bool) bool),
+ 25: new(func(bool) float64),
+ 26: new(func(bool) int),
+ 27: new(func(bool) string),
+ 28: new(func(float64) bool),
+ 29: new(func(float64) float64),
+ 30: new(func(float64) int),
+ 31: new(func(float64) string),
+ 32: new(func(int) bool),
+ 33: new(func(int) float64),
+ 34: new(func(int) int),
+ 35: new(func(int) string),
+ 36: new(func(int, int) int),
+ 37: new(func(int, int) string),
+ 38: new(func(int64) time.Time),
+ 39: new(func(string) []string),
+ 40: new(func(string) bool),
+ 41: new(func(string) float64),
+ 42: new(func(string) int),
+ 43: new(func(string) string),
+ 44: new(func(string, uint8) int),
+ 45: new(func(string, int) int),
+ 46: new(func(string, int32) int),
+ 47: new(func(string, string) bool),
+ 48: new(func(string, string) string),
+ 49: new(func(any) bool),
+ 50: new(func(any) float64),
+ 51: new(func(any) int),
+ 52: new(func(any) string),
+ 53: new(func(any) any),
+ 54: new(func(any) []any),
+ 55: new(func(any) map[string]any),
+ 56: new(func([]any) any),
+ 57: new(func([]any) []any),
+ 58: new(func([]any) map[string]any),
+ 59: new(func(any, any) bool),
+ 60: new(func(any, any) string),
+ 61: new(func(any, any) any),
+ 62: new(func(any, any) []any),
+}
+
+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() []uint8)()
+ case 6:
+ return fn.(func() []any)()
+ case 7:
+ return fn.(func() bool)()
+ case 8:
+ return fn.(func() uint8)()
+ case 9:
+ return fn.(func() float64)()
+ case 10:
+ return fn.(func() int)()
+ case 11:
+ return fn.(func() int64)()
+ case 12:
+ return fn.(func() any)()
+ case 13:
+ return fn.(func() map[string]any)()
+ case 14:
+ return fn.(func() int32)()
+ case 15:
+ return fn.(func() string)()
+ case 16:
+ return fn.(func() uint)()
+ case 17:
+ return fn.(func() uint64)()
+ case 18:
+ arg1 := vm.pop().(time.Duration)
+ return fn.(func(time.Duration) time.Duration)(arg1)
+ case 19:
+ arg1 := vm.pop().(time.Duration)
+ return fn.(func(time.Duration) time.Time)(arg1)
+ case 20:
+ arg1 := vm.pop().(time.Time)
+ return fn.(func(time.Time) time.Duration)(arg1)
+ case 21:
+ arg1 := vm.pop().(time.Time)
+ return fn.(func(time.Time) bool)(arg1)
+ case 22:
+ arg2 := vm.pop().(string)
+ arg1 := vm.pop().([]any)
+ return fn.(func([]any, string) string)(arg1, arg2)
+ case 23:
+ arg2 := vm.pop().(string)
+ arg1 := vm.pop().([]string)
+ return fn.(func([]string, string) string)(arg1, arg2)
+ case 24:
+ arg1 := vm.pop().(bool)
+ return fn.(func(bool) bool)(arg1)
+ case 25:
+ arg1 := vm.pop().(bool)
+ return fn.(func(bool) float64)(arg1)
+ case 26:
+ arg1 := vm.pop().(bool)
+ return fn.(func(bool) int)(arg1)
+ case 27:
+ arg1 := vm.pop().(bool)
+ return fn.(func(bool) string)(arg1)
+ case 28:
+ arg1 := vm.pop().(float64)
+ return fn.(func(float64) bool)(arg1)
+ case 29:
+ arg1 := vm.pop().(float64)
+ return fn.(func(float64) float64)(arg1)
+ case 30:
+ arg1 := vm.pop().(float64)
+ return fn.(func(float64) int)(arg1)
+ case 31:
+ arg1 := vm.pop().(float64)
+ return fn.(func(float64) string)(arg1)
+ case 32:
+ arg1 := vm.pop().(int)
+ return fn.(func(int) bool)(arg1)
+ case 33:
+ arg1 := vm.pop().(int)
+ return fn.(func(int) float64)(arg1)
+ case 34:
+ arg1 := vm.pop().(int)
+ return fn.(func(int) int)(arg1)
+ case 35:
+ arg1 := vm.pop().(int)
+ return fn.(func(int) string)(arg1)
+ case 36:
+ arg2 := vm.pop().(int)
+ arg1 := vm.pop().(int)
+ return fn.(func(int, int) int)(arg1, arg2)
+ case 37:
+ arg2 := vm.pop().(int)
+ arg1 := vm.pop().(int)
+ return fn.(func(int, int) string)(arg1, arg2)
+ case 38:
+ arg1 := vm.pop().(int64)
+ return fn.(func(int64) time.Time)(arg1)
+ case 39:
+ arg1 := vm.pop().(string)
+ return fn.(func(string) []string)(arg1)
+ case 40:
+ arg1 := vm.pop().(string)
+ return fn.(func(string) bool)(arg1)
+ case 41:
+ arg1 := vm.pop().(string)
+ return fn.(func(string) float64)(arg1)
+ case 42:
+ arg1 := vm.pop().(string)
+ return fn.(func(string) int)(arg1)
+ case 43:
+ arg1 := vm.pop().(string)
+ return fn.(func(string) string)(arg1)
+ case 44:
+ arg2 := vm.pop().(uint8)
+ arg1 := vm.pop().(string)
+ return fn.(func(string, uint8) int)(arg1, arg2)
+ case 45:
+ arg2 := vm.pop().(int)
+ arg1 := vm.pop().(string)
+ return fn.(func(string, int) int)(arg1, arg2)
+ case 46:
+ arg2 := vm.pop().(int32)
+ arg1 := vm.pop().(string)
+ return fn.(func(string, int32) int)(arg1, arg2)
+ case 47:
+ arg2 := vm.pop().(string)
+ arg1 := vm.pop().(string)
+ return fn.(func(string, string) bool)(arg1, arg2)
+ case 48:
+ arg2 := vm.pop().(string)
+ arg1 := vm.pop().(string)
+ return fn.(func(string, string) string)(arg1, arg2)
+ case 49:
+ arg1 := vm.pop()
+ return fn.(func(any) bool)(arg1)
+ case 50:
+ arg1 := vm.pop()
+ return fn.(func(any) float64)(arg1)
+ case 51:
+ arg1 := vm.pop()
+ return fn.(func(any) int)(arg1)
+ case 52:
+ arg1 := vm.pop()
+ return fn.(func(any) string)(arg1)
+ case 53:
+ arg1 := vm.pop()
+ return fn.(func(any) any)(arg1)
+ case 54:
+ arg1 := vm.pop()
+ return fn.(func(any) []any)(arg1)
+ case 55:
+ arg1 := vm.pop()
+ return fn.(func(any) map[string]any)(arg1)
+ case 56:
+ arg1 := vm.pop().([]any)
+ return fn.(func([]any) any)(arg1)
+ case 57:
+ arg1 := vm.pop().([]any)
+ return fn.(func([]any) []any)(arg1)
+ case 58:
+ arg1 := vm.pop().([]any)
+ return fn.(func([]any) map[string]any)(arg1)
+ case 59:
+ arg2 := vm.pop()
+ arg1 := vm.pop()
+ return fn.(func(any, any) bool)(arg1, arg2)
+ case 60:
+ arg2 := vm.pop()
+ arg1 := vm.pop()
+ return fn.(func(any, any) string)(arg1, arg2)
+ case 61:
+ arg2 := vm.pop()
+ arg1 := vm.pop()
+ return fn.(func(any, any) any)(arg1, arg2)
+ case 62:
+ arg2 := vm.pop()
+ arg1 := vm.pop()
+ return fn.(func(any, any) []any)(arg1, arg2)
+
+ }
+ panic(fmt.Sprintf("unknown function kind (%v)", kind))
+}
diff --git a/vendor/github.com/antonmedv/expr/vm/opcodes.go b/vendor/github.com/antonmedv/expr/vm/opcodes.go
new file mode 100644
index 00000000000..1106cd3f05b
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/vm/opcodes.go
@@ -0,0 +1,83 @@
+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
+ OpCallTyped
+ OpCallBuiltin1
+ OpArray
+ OpMap
+ OpLen
+ OpCast
+ OpDeref
+ OpIncrementIndex
+ OpDecrementIndex
+ OpIncrementCount
+ OpGetIndex
+ OpSetIndex
+ OpGetCount
+ OpGetLen
+ OpGetGroupBy
+ OpGetAcc
+ OpPointer
+ OpThrow
+ OpGroupBy
+ OpSetAcc
+ OpBegin
+ OpEnd // This opcode must be at the end of this list.
+)
diff --git a/vendor/github.com/antonmedv/expr/vm/program.go b/vendor/github.com/antonmedv/expr/vm/program.go
new file mode 100644
index 00000000000..c45a2bff23a
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/vm/program.go
@@ -0,0 +1,320 @@
+package vm
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "reflect"
+ "regexp"
+ "strings"
+ "text/tabwriter"
+
+ "github.com/antonmedv/expr/ast"
+ "github.com/antonmedv/expr/builtin"
+ "github.com/antonmedv/expr/file"
+ "github.com/antonmedv/expr/vm/runtime"
+)
+
+type Program struct {
+ Node ast.Node
+ Source *file.Source
+ Locations []file.Location
+ Variables []any
+ Constants []any
+ Bytecode []Opcode
+ Arguments []int
+ Functions []Function
+ DebugInfo map[string]string
+}
+
+func (program *Program) Disassemble() string {
+ var buf bytes.Buffer
+ w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0)
+ program.Opcodes(w)
+ _ = w.Flush()
+ return buf.String()
+}
+
+func (program *Program) Opcodes(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 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:
+ argument("OpLoadFunc")
+
+ 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 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 OpSetIndex:
+ code("OpSetIndex")
+
+ case OpGetCount:
+ code("OpGetCount")
+
+ case OpGetLen:
+ code("OpGetLen")
+
+ case OpGetGroupBy:
+ code("OpGetGroupBy")
+
+ case OpGetAcc:
+ code("OpGetAcc")
+
+ case OpPointer:
+ code("OpPointer")
+
+ case OpThrow:
+ code("OpThrow")
+
+ case OpGroupBy:
+ code("OpGroupBy")
+
+ case OpSetAcc:
+ code("OpSetAcc")
+
+ case OpBegin:
+ code("OpBegin")
+
+ case OpEnd:
+ code("OpEnd")
+
+ default:
+ _, _ = fmt.Fprintf(w, "%v\t%#x (unknown)\n", ip, op)
+ }
+ }
+}
diff --git a/vendor/github.com/antonmedv/expr/vm/runtime/generated.go b/vendor/github.com/antonmedv/expr/vm/runtime/generated.go
new file mode 100644
index 00000000000..720feb45543
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/vm/runtime/generated.go
@@ -0,0 +1,3375 @@
+// 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 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
+ }
+ }
+ 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/antonmedv/expr/vm/runtime/runtime.go b/vendor/github.com/antonmedv/expr/vm/runtime/runtime.go
new file mode 100644
index 00000000000..98c34f5d8e9
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/vm/runtime/runtime.go
@@ -0,0 +1,430 @@
+package runtime
+
+//go:generate sh -c "go run ./helpers > ./generated.go"
+
+import (
+ "fmt"
+ "math"
+ "reflect"
+)
+
+func Fetch(from, i any) any {
+ v := reflect.ValueOf(from)
+ kind := v.Kind()
+ if 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.
+ if kind == reflect.Ptr {
+ v = reflect.Indirect(v)
+ kind = v.Kind()
+ }
+
+ // TODO: We can create separate opcodes for each of the cases below to make
+ // the little bit faster.
+ switch kind {
+ case reflect.Array, reflect.Slice, reflect.String:
+ index := ToInt(i)
+ if index < 0 {
+ index = v.Len() + index
+ }
+ 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)
+ value := v.FieldByNameFunc(func(name string) bool {
+ field, _ := v.Type().FieldByName(name)
+ if field.Tag.Get("expr") == fieldName {
+ return true
+ }
+ return name == fieldName
+ })
+ if value.IsValid() {
+ 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)
+ kind := v.Kind()
+ if kind != reflect.Invalid {
+ if kind == reflect.Ptr {
+ 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 Deref(i any) any {
+ if i == nil {
+ return nil
+ }
+
+ v := reflect.ValueOf(i)
+
+ if v.Kind() == reflect.Interface {
+ if v.IsNil() {
+ return i
+ }
+ v = v.Elem()
+ }
+
+loop:
+ for v.Kind() == reflect.Ptr {
+ if v.IsNil() {
+ return i
+ }
+ indirect := reflect.Indirect(v)
+ switch indirect.Kind() {
+ case reflect.Struct, reflect.Map, reflect.Array, reflect.Slice:
+ break loop
+ default:
+ v = v.Elem()
+ }
+ }
+
+ if v.IsValid() {
+ return v.Interface()
+ }
+
+ panic(fmt.Sprintf("cannot dereference %v", i))
+}
+
+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
+ }
+ 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))
+ }
+ value := v.FieldByName(n.String())
+ 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 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/antonmedv/expr/vm/vm.go b/vendor/github.com/antonmedv/expr/vm/vm.go
new file mode 100644
index 00000000000..d020a31a845
--- /dev/null
+++ b/vendor/github.com/antonmedv/expr/vm/vm.go
@@ -0,0 +1,554 @@
+package vm
+
+//go:generate sh -c "go run ./func_types > ./generated.go"
+
+import (
+ "fmt"
+ "reflect"
+ "regexp"
+ "strings"
+
+ "github.com/antonmedv/expr/builtin"
+ "github.com/antonmedv/expr/file"
+ "github.com/antonmedv/expr/vm/runtime"
+)
+
+var MemoryBudget uint = 1e6
+var errorType = reflect.TypeOf((*error)(nil)).Elem()
+
+type Function = func(params ...any) (any, error)
+
+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)
+}
+
+type VM struct {
+ stack []any
+ ip int
+ scopes []*Scope
+ debug bool
+ step chan struct{}
+ curr chan int
+ memory uint
+ memoryBudget uint
+}
+
+type Scope struct {
+ Array reflect.Value
+ Index int
+ Len int
+ Count int
+ GroupBy map[any][]any
+ Acc any
+}
+
+func Debug() *VM {
+ vm := &VM{
+ debug: true,
+ step: make(chan struct{}, 0),
+ curr: make(chan int, 0),
+ }
+ return vm
+}
+
+func (vm *VM) Run(program *Program, env any) (_ any, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ f := &file.Error{
+ Location: program.Locations[vm.ip-1],
+ 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 {
+ vm.stack = vm.stack[0:0]
+ }
+
+ if vm.scopes != nil {
+ vm.scopes = vm.scopes[0:0]
+ }
+
+ vm.memoryBudget = MemoryBudget
+ vm.memory = 0
+ vm.ip = 0
+
+ for vm.ip < len(program.Bytecode) {
+ if 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:
+ program.Variables[arg] = vm.pop()
+
+ case OpLoadVar:
+ vm.push(program.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:
+ vm.ip += arg
+
+ case OpJumpIfTrue:
+ if vm.current().(bool) {
+ vm.ip += arg
+ }
+
+ case OpJumpIfFalse:
+ if !vm.current().(bool) {
+ vm.ip += arg
+ }
+
+ case OpJumpIfNil:
+ if runtime.IsNil(vm.current()) {
+ vm.ip += arg
+ }
+
+ case OpJumpIfNotNil:
+ if !runtime.IsNil(vm.current()) {
+ vm.ip += arg
+ }
+
+ case OpJumpIfEnd:
+ scope := vm.Scope()
+ if scope.Index >= scope.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()
+ match, err := regexp.MatchString(b.(string), a.(string))
+ if err != nil {
+ panic(err)
+ }
+
+ vm.push(match)
+
+ case OpMatchesConst:
+ a := vm.pop()
+ r := program.Constants[arg].(*regexp.Regexp)
+ vm.push(r.MatchString(a.(string)))
+
+ case OpContains:
+ b := vm.pop()
+ a := vm.pop()
+ vm.push(strings.Contains(a.(string), b.(string)))
+
+ case OpStartsWith:
+ b := vm.pop()
+ a := vm.pop()
+ vm.push(strings.HasPrefix(a.(string), b.(string)))
+
+ case OpEndsWith:
+ b := vm.pop()
+ a := vm.pop()
+ 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:
+ fn := reflect.ValueOf(vm.pop())
+ size := arg
+ in := make([]reflect.Value, size)
+ for i := int(size) - 1; i >= 0; i-- {
+ param := vm.pop()
+ 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)
+ 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:
+ a := vm.pop()
+ out, err := program.Functions[arg](a)
+ if err != nil {
+ panic(err)
+ }
+ vm.push(out)
+
+ case OpCall2:
+ b := vm.pop()
+ a := vm.pop()
+ out, err := program.Functions[arg](a, b)
+ if err != nil {
+ panic(err)
+ }
+ vm.push(out)
+
+ case OpCall3:
+ c := vm.pop()
+ b := vm.pop()
+ a := vm.pop()
+ out, err := program.Functions[arg](a, b, c)
+ if err != nil {
+ panic(err)
+ }
+ vm.push(out)
+
+ case OpCallN:
+ fn := vm.pop().(Function)
+ size := arg
+ in := make([]any, size)
+ for i := int(size) - 1; i >= 0; i-- {
+ in[i] = vm.pop()
+ }
+ out, err := fn(in...)
+ if err != nil {
+ panic(err)
+ }
+ vm.push(out)
+
+ case OpCallFast:
+ fn := vm.pop().(func(...any) any)
+ size := arg
+ in := make([]any, size)
+ for i := int(size) - 1; i >= 0; i-- {
+ in[i] = vm.pop()
+ }
+ vm.push(fn(in...))
+
+ 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 OpDeref:
+ a := vm.pop()
+ vm.push(runtime.Deref(a))
+
+ case OpIncrementIndex:
+ vm.Scope().Index++
+
+ case OpDecrementIndex:
+ scope := vm.Scope()
+ scope.Index--
+
+ case OpIncrementCount:
+ scope := vm.Scope()
+ scope.Count++
+
+ case OpGetIndex:
+ vm.push(vm.Scope().Index)
+
+ case OpSetIndex:
+ scope := vm.Scope()
+ scope.Index = vm.pop().(int)
+
+ case OpGetCount:
+ scope := vm.Scope()
+ vm.push(scope.Count)
+
+ case OpGetLen:
+ scope := vm.Scope()
+ vm.push(scope.Len)
+
+ case OpGetGroupBy:
+ vm.push(vm.Scope().GroupBy)
+
+ case OpGetAcc:
+ vm.push(vm.Scope().Acc)
+
+ case OpSetAcc:
+ vm.Scope().Acc = vm.pop()
+
+ case OpPointer:
+ scope := vm.Scope()
+ vm.push(scope.Array.Index(scope.Index).Interface())
+
+ case OpThrow:
+ panic(vm.pop().(error))
+
+ case OpGroupBy:
+ scope := vm.Scope()
+ if scope.GroupBy == nil {
+ scope.GroupBy = make(map[any][]any)
+ }
+ it := scope.Array.Index(scope.Index).Interface()
+ key := vm.pop()
+ scope.GroupBy[key] = append(scope.GroupBy[key], it)
+
+ case OpBegin:
+ a := vm.pop()
+ array := reflect.ValueOf(a)
+ vm.scopes = append(vm.scopes, &Scope{
+ Array: array,
+ Len: array.Len(),
+ })
+
+ case OpEnd:
+ vm.scopes = vm.scopes[:len(vm.scopes)-1]
+
+ default:
+ panic(fmt.Sprintf("unknown bytecode %#x", op))
+ }
+
+ if vm.debug {
+ vm.curr <- vm.ip
+ }
+ }
+
+ if 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 {
+ return vm.stack[len(vm.stack)-1]
+}
+
+func (vm *VM) pop() any {
+ 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) Stack() []any {
+ return vm.stack
+}
+
+func (vm *VM) Scope() *Scope {
+ if len(vm.scopes) > 0 {
+ return vm.scopes[len(vm.scopes)-1]
+ }
+ return nil
+}
+
+func (vm *VM) Step() {
+ vm.step <- struct{}{}
+}
+
+func (vm *VM) Position() chan int {
+ return vm.curr
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 76847683b5b..15855baaef8 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -245,6 +245,22 @@ github.com/andybalholm/brotli
# github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df
## explicit; go 1.18
github.com/antlr/antlr4/runtime/Go/antlr/v4
+# github.com/antonmedv/expr v1.15.3
+## explicit; go 1.18
+github.com/antonmedv/expr
+github.com/antonmedv/expr/ast
+github.com/antonmedv/expr/builtin
+github.com/antonmedv/expr/checker
+github.com/antonmedv/expr/compiler
+github.com/antonmedv/expr/conf
+github.com/antonmedv/expr/file
+github.com/antonmedv/expr/optimizer
+github.com/antonmedv/expr/parser
+github.com/antonmedv/expr/parser/lexer
+github.com/antonmedv/expr/parser/operator
+github.com/antonmedv/expr/parser/utils
+github.com/antonmedv/expr/vm
+github.com/antonmedv/expr/vm/runtime
# github.com/apapsch/go-jsonmerge/v2 v2.0.0
## explicit; go 1.12
github.com/apapsch/go-jsonmerge/v2