summaryrefslogtreecommitdiff
path: root/vendor/github.com/google/cel-go/cel/program.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/google/cel-go/cel/program.go')
-rw-r--r--vendor/github.com/google/cel-go/cel/program.go245
1 files changed, 86 insertions, 159 deletions
diff --git a/vendor/github.com/google/cel-go/cel/program.go b/vendor/github.com/google/cel-go/cel/program.go
index 144d1f2..ec3869b 100644
--- a/vendor/github.com/google/cel-go/cel/program.go
+++ b/vendor/github.com/google/cel-go/cel/program.go
@@ -20,6 +20,7 @@ import (
"sync"
"github.com/google/cel-go/common/ast"
+ "github.com/google/cel-go/common/functions"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter"
@@ -75,7 +76,7 @@ func NewActivation(bindings any) (Activation, error) {
return interpreter.NewActivation(bindings)
}
-// PartialActivation extends the Activation interface with a set of UnknownAttributePatterns.
+// PartialActivation extends the Activation interface with a set of unknown AttributePatterns.
type PartialActivation = interpreter.PartialActivation
// NoVars returns an empty Activation.
@@ -91,7 +92,7 @@ func NoVars() Activation {
//
// The `vars` value may either be an Activation or any valid input to the NewActivation call.
func PartialVars(vars any,
- unknowns ...*interpreter.AttributePattern) (PartialActivation, error) {
+ unknowns ...*AttributePatternType) (PartialActivation, error) {
return interpreter.NewPartialActivation(vars, unknowns...)
}
@@ -108,12 +109,15 @@ func PartialVars(vars any,
// fully qualified variable name may be `ns.app.a`, `ns.a`, or `a` per the CEL namespace resolution
// rules. Pick the fully qualified variable name that makes sense within the container as the
// AttributePattern `varName` argument.
+func AttributePattern(varName string) *AttributePatternType {
+ return interpreter.NewAttributePattern(varName)
+}
+
+// AttributePatternType represents a top-level variable with an optional set of qualifier patterns.
//
// See the interpreter.AttributePattern and interpreter.AttributeQualifierPattern for more info
// about how to create and manipulate AttributePattern values.
-func AttributePattern(varName string) *interpreter.AttributePattern {
- return interpreter.NewAttributePattern(varName)
-}
+type AttributePatternType = interpreter.AttributePattern
// EvalDetails holds additional information observed during the Eval() call.
type EvalDetails struct {
@@ -151,30 +155,17 @@ type prog struct {
// Intermediate state used to configure the InterpretableDecorator set provided
// to the initInterpretable call.
- decorators []interpreter.InterpretableDecorator
+ plannerOptions []interpreter.PlannerOption
regexOptimizations []*interpreter.RegexOptimization
// Interpretable configured from an Ast and aggregate decorator set based on program options.
interpretable interpreter.Interpretable
+ observable *interpreter.ObservableInterpretable
callCostEstimator interpreter.ActualCostEstimator
costOptions []interpreter.CostTrackerOption
costLimit *uint64
}
-func (p *prog) clone() *prog {
- costOptsCopy := make([]interpreter.CostTrackerOption, len(p.costOptions))
- copy(costOptsCopy, p.costOptions)
-
- return &prog{
- Env: p.Env,
- evalOpts: p.evalOpts,
- defaultVars: p.defaultVars,
- dispatcher: p.dispatcher,
- interpreter: p.interpreter,
- interruptCheckFrequency: p.interruptCheckFrequency,
- }
-}
-
// newProgram creates a program instance with an environment, an ast, and an optional list of
// ProgramOption values.
//
@@ -186,10 +177,10 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) {
// Ensure the default attribute factory is set after the adapter and provider are
// configured.
p := &prog{
- Env: e,
- decorators: []interpreter.InterpretableDecorator{},
- dispatcher: disp,
- costOptions: []interpreter.CostTrackerOption{},
+ Env: e,
+ plannerOptions: []interpreter.PlannerOption{},
+ dispatcher: disp,
+ costOptions: []interpreter.CostTrackerOption{},
}
// Configure the program via the ProgramOption values.
@@ -201,16 +192,25 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) {
}
}
- // Add the function bindings created via Function() options.
- for _, fn := range e.functions {
- bindings, err := fn.Bindings()
- if err != nil {
- return nil, err
- }
- err = disp.Add(bindings...)
- if err != nil {
- return nil, err
+ e.funcBindOnce.Do(func() {
+ var bindings []*functions.Overload
+ e.functionBindings = []*functions.Overload{}
+ for _, fn := range e.functions {
+ bindings, err = fn.Bindings()
+ if err != nil {
+ return
+ }
+ e.functionBindings = append(e.functionBindings, bindings...)
}
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Add the function bindings created via Function() options.
+ err = disp.Add(e.functionBindings...)
+ if err != nil {
+ return nil, err
}
// Set the attribute factory after the options have been set.
@@ -227,74 +227,71 @@ func newProgram(e *Env, a *ast.AST, opts []ProgramOption) (Program, error) {
p.interpreter = interp
// Translate the EvalOption flags into InterpretableDecorator instances.
- decorators := make([]interpreter.InterpretableDecorator, len(p.decorators))
- copy(decorators, p.decorators)
+ plannerOptions := make([]interpreter.PlannerOption, len(p.plannerOptions))
+ copy(plannerOptions, p.plannerOptions)
// Enable interrupt checking if there's a non-zero check frequency
if p.interruptCheckFrequency > 0 {
- decorators = append(decorators, interpreter.InterruptableEval())
+ plannerOptions = append(plannerOptions, interpreter.InterruptableEval())
}
// Enable constant folding first.
if p.evalOpts&OptOptimize == OptOptimize {
- decorators = append(decorators, interpreter.Optimize())
+ plannerOptions = append(plannerOptions, interpreter.Optimize())
p.regexOptimizations = append(p.regexOptimizations, interpreter.MatchesRegexOptimization)
}
// Enable regex compilation of constants immediately after folding constants.
if len(p.regexOptimizations) > 0 {
- decorators = append(decorators, interpreter.CompileRegexConstants(p.regexOptimizations...))
+ plannerOptions = append(plannerOptions, interpreter.CompileRegexConstants(p.regexOptimizations...))
}
// Enable exhaustive eval, state tracking and cost tracking last since they require a factory.
if p.evalOpts&(OptExhaustiveEval|OptTrackState|OptTrackCost) != 0 {
- factory := func(state interpreter.EvalState, costTracker *interpreter.CostTracker) (Program, error) {
- costTracker.Estimator = p.callCostEstimator
- costTracker.Limit = p.costLimit
- for _, costOpt := range p.costOptions {
- err := costOpt(costTracker)
- if err != nil {
- return nil, err
- }
- }
- // Limit capacity to guarantee a reallocation when calling 'append(decs, ...)' below. This
- // prevents the underlying memory from being shared between factory function calls causing
- // undesired mutations.
- decs := decorators[:len(decorators):len(decorators)]
- var observers []interpreter.EvalObserver
-
- if p.evalOpts&(OptExhaustiveEval|OptTrackState) != 0 {
- // EvalStateObserver is required for OptExhaustiveEval.
- observers = append(observers, interpreter.EvalStateObserver(state))
- }
- if p.evalOpts&OptTrackCost == OptTrackCost {
- observers = append(observers, interpreter.CostObserver(costTracker))
- }
-
- // Enable exhaustive eval over a basic observer since it offers a superset of features.
- if p.evalOpts&OptExhaustiveEval == OptExhaustiveEval {
- decs = append(decs, interpreter.ExhaustiveEval(), interpreter.Observe(observers...))
- } else if len(observers) > 0 {
- decs = append(decs, interpreter.Observe(observers...))
- }
-
- return p.clone().initInterpretable(a, decs)
+ costOptCount := len(p.costOptions)
+ if p.costLimit != nil {
+ costOptCount++
+ }
+ costOpts := make([]interpreter.CostTrackerOption, 0, costOptCount)
+ costOpts = append(costOpts, p.costOptions...)
+ if p.costLimit != nil {
+ costOpts = append(costOpts, interpreter.CostTrackerLimit(*p.costLimit))
+ }
+ trackerFactory := func() (*interpreter.CostTracker, error) {
+ return interpreter.NewCostTracker(p.callCostEstimator, costOpts...)
+ }
+ var observers []interpreter.PlannerOption
+ if p.evalOpts&(OptExhaustiveEval|OptTrackState) != 0 {
+ // EvalStateObserver is required for OptExhaustiveEval.
+ observers = append(observers, interpreter.EvalStateObserver())
+ }
+ if p.evalOpts&OptTrackCost == OptTrackCost {
+ observers = append(observers, interpreter.CostObserver(interpreter.CostTrackerFactory(trackerFactory)))
+ }
+ // Enable exhaustive eval over a basic observer since it offers a superset of features.
+ if p.evalOpts&OptExhaustiveEval == OptExhaustiveEval {
+ plannerOptions = append(plannerOptions,
+ append([]interpreter.PlannerOption{interpreter.ExhaustiveEval()}, observers...)...)
+ } else if len(observers) > 0 {
+ plannerOptions = append(plannerOptions, observers...)
}
- return newProgGen(factory)
}
- return p.initInterpretable(a, decorators)
+ return p.initInterpretable(a, plannerOptions)
}
-func (p *prog) initInterpretable(a *ast.AST, decs []interpreter.InterpretableDecorator) (*prog, error) {
+func (p *prog) initInterpretable(a *ast.AST, plannerOptions []interpreter.PlannerOption) (*prog, error) {
// When the AST has been exprAST it contains metadata that can be used to speed up program execution.
- interpretable, err := p.interpreter.NewInterpretable(a, decs...)
+ interpretable, err := p.interpreter.NewInterpretable(a, plannerOptions...)
if err != nil {
return nil, err
}
p.interpretable = interpretable
+ if oi, ok := interpretable.(*interpreter.ObservableInterpretable); ok {
+ p.observable = oi
+ }
return p, nil
}
// Eval implements the Program interface method.
-func (p *prog) Eval(input any) (v ref.Val, det *EvalDetails, err error) {
+func (p *prog) Eval(input any) (out ref.Val, det *EvalDetails, err error) {
// Configure error recovery for unexpected panics during evaluation. Note, the use of named
// return values makes it possible to modify the error response during the recovery
// function.
@@ -322,12 +319,24 @@ func (p *prog) Eval(input any) (v ref.Val, det *EvalDetails, err error) {
if p.defaultVars != nil {
vars = interpreter.NewHierarchicalActivation(p.defaultVars, vars)
}
- v = p.interpretable.Eval(vars)
+ if p.observable != nil {
+ det = &EvalDetails{}
+ out = p.observable.ObserveEval(vars, func(observed any) {
+ switch o := observed.(type) {
+ case interpreter.EvalState:
+ det.state = o
+ case *interpreter.CostTracker:
+ det.costTracker = o
+ }
+ })
+ } else {
+ out = p.interpretable.Eval(vars)
+ }
// The output of an internal Eval may have a value (`v`) that is a types.Err. This step
// translates the CEL value to a Go error response. This interface does not quite match the
// RPC signature which allows for multiple errors to be returned, but should be sufficient.
- if types.IsError(v) {
- err = v.(*types.Err)
+ if types.IsError(out) {
+ err = out.(*types.Err)
}
return
}
@@ -355,88 +364,6 @@ func (p *prog) ContextEval(ctx context.Context, input any) (ref.Val, *EvalDetail
return p.Eval(vars)
}
-// progFactory is a helper alias for marking a program creation factory function.
-type progFactory func(interpreter.EvalState, *interpreter.CostTracker) (Program, error)
-
-// progGen holds a reference to a progFactory instance and implements the Program interface.
-type progGen struct {
- factory progFactory
-}
-
-// newProgGen tests the factory object by calling it once and returns a factory-based Program if
-// the test is successful.
-func newProgGen(factory progFactory) (Program, error) {
- // Test the factory to make sure that configuration errors are spotted at config
- tracker, err := interpreter.NewCostTracker(nil)
- if err != nil {
- return nil, err
- }
- _, err = factory(interpreter.NewEvalState(), tracker)
- if err != nil {
- return nil, err
- }
- return &progGen{factory: factory}, nil
-}
-
-// Eval implements the Program interface method.
-func (gen *progGen) Eval(input any) (ref.Val, *EvalDetails, error) {
- // The factory based Eval() differs from the standard evaluation model in that it generates a
- // new EvalState instance for each call to ensure that unique evaluations yield unique stateful
- // results.
- state := interpreter.NewEvalState()
- costTracker, err := interpreter.NewCostTracker(nil)
- if err != nil {
- return nil, nil, err
- }
- det := &EvalDetails{state: state, costTracker: costTracker}
-
- // Generate a new instance of the interpretable using the factory configured during the call to
- // newProgram(). It is incredibly unlikely that the factory call will generate an error given
- // the factory test performed within the Program() call.
- p, err := gen.factory(state, costTracker)
- if err != nil {
- return nil, det, err
- }
-
- // Evaluate the input, returning the result and the 'state' within EvalDetails.
- v, _, err := p.Eval(input)
- if err != nil {
- return v, det, err
- }
- return v, det, nil
-}
-
-// ContextEval implements the Program interface method.
-func (gen *progGen) ContextEval(ctx context.Context, input any) (ref.Val, *EvalDetails, error) {
- if ctx == nil {
- return nil, nil, fmt.Errorf("context can not be nil")
- }
- // The factory based Eval() differs from the standard evaluation model in that it generates a
- // new EvalState instance for each call to ensure that unique evaluations yield unique stateful
- // results.
- state := interpreter.NewEvalState()
- costTracker, err := interpreter.NewCostTracker(nil)
- if err != nil {
- return nil, nil, err
- }
- det := &EvalDetails{state: state, costTracker: costTracker}
-
- // Generate a new instance of the interpretable using the factory configured during the call to
- // newProgram(). It is incredibly unlikely that the factory call will generate an error given
- // the factory test performed within the Program() call.
- p, err := gen.factory(state, costTracker)
- if err != nil {
- return nil, det, err
- }
-
- // Evaluate the input, returning the result and the 'state' within EvalDetails.
- v, _, err := p.ContextEval(ctx, input)
- if err != nil {
- return v, det, err
- }
- return v, det, nil
-}
-
type ctxEvalActivation struct {
parent Activation
interrupt <-chan struct{}