792 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			792 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2018 Google LLC
 | |
| //
 | |
| // 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.
 | |
| 
 | |
| package interpreter
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/google/cel-go/common/ast"
 | |
| 	"github.com/google/cel-go/common/containers"
 | |
| 	"github.com/google/cel-go/common/functions"
 | |
| 	"github.com/google/cel-go/common/operators"
 | |
| 	"github.com/google/cel-go/common/types"
 | |
| 	"github.com/google/cel-go/common/types/ref"
 | |
| 
 | |
| 	exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
 | |
| )
 | |
| 
 | |
| // interpretablePlanner creates an Interpretable evaluation plan from a proto Expr value.
 | |
| type interpretablePlanner interface {
 | |
| 	// Plan generates an Interpretable value (or error) from the input proto Expr.
 | |
| 	Plan(expr *exprpb.Expr) (Interpretable, error)
 | |
| }
 | |
| 
 | |
| // newPlanner creates an interpretablePlanner which references a Dispatcher, TypeProvider,
 | |
| // TypeAdapter, Container, and CheckedExpr value. These pieces of data are used to resolve
 | |
| // functions, types, and namespaced identifiers at plan time rather than at runtime since
 | |
| // it only needs to be done once and may be semi-expensive to compute.
 | |
| func newPlanner(disp Dispatcher,
 | |
| 	provider types.Provider,
 | |
| 	adapter types.Adapter,
 | |
| 	attrFactory AttributeFactory,
 | |
| 	cont *containers.Container,
 | |
| 	checked *ast.CheckedAST,
 | |
| 	decorators ...InterpretableDecorator) interpretablePlanner {
 | |
| 	return &planner{
 | |
| 		disp:        disp,
 | |
| 		provider:    provider,
 | |
| 		adapter:     adapter,
 | |
| 		attrFactory: attrFactory,
 | |
| 		container:   cont,
 | |
| 		refMap:      checked.ReferenceMap,
 | |
| 		typeMap:     checked.TypeMap,
 | |
| 		decorators:  decorators,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // newUncheckedPlanner creates an interpretablePlanner which references a Dispatcher, TypeProvider,
 | |
| // TypeAdapter, and Container to resolve functions and types at plan time. Namespaces present in
 | |
| // Select expressions are resolved lazily at evaluation time.
 | |
| func newUncheckedPlanner(disp Dispatcher,
 | |
| 	provider types.Provider,
 | |
| 	adapter types.Adapter,
 | |
| 	attrFactory AttributeFactory,
 | |
| 	cont *containers.Container,
 | |
| 	decorators ...InterpretableDecorator) interpretablePlanner {
 | |
| 	return &planner{
 | |
| 		disp:        disp,
 | |
| 		provider:    provider,
 | |
| 		adapter:     adapter,
 | |
| 		attrFactory: attrFactory,
 | |
| 		container:   cont,
 | |
| 		refMap:      make(map[int64]*ast.ReferenceInfo),
 | |
| 		typeMap:     make(map[int64]*types.Type),
 | |
| 		decorators:  decorators,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // planner is an implementation of the interpretablePlanner interface.
 | |
| type planner struct {
 | |
| 	disp        Dispatcher
 | |
| 	provider    types.Provider
 | |
| 	adapter     types.Adapter
 | |
| 	attrFactory AttributeFactory
 | |
| 	container   *containers.Container
 | |
| 	refMap      map[int64]*ast.ReferenceInfo
 | |
| 	typeMap     map[int64]*types.Type
 | |
| 	decorators  []InterpretableDecorator
 | |
| }
 | |
| 
 | |
| // Plan implements the interpretablePlanner interface. This implementation of the Plan method also
 | |
| // applies decorators to each Interpretable generated as part of the overall plan. Decorators are
 | |
| // useful for layering functionality into the evaluation that is not natively understood by CEL,
 | |
| // such as state-tracking, expression re-write, and possibly efficient thread-safe memoization of
 | |
| // repeated expressions.
 | |
| func (p *planner) Plan(expr *exprpb.Expr) (Interpretable, error) {
 | |
| 	switch expr.GetExprKind().(type) {
 | |
| 	case *exprpb.Expr_CallExpr:
 | |
| 		return p.decorate(p.planCall(expr))
 | |
| 	case *exprpb.Expr_IdentExpr:
 | |
| 		return p.decorate(p.planIdent(expr))
 | |
| 	case *exprpb.Expr_SelectExpr:
 | |
| 		return p.decorate(p.planSelect(expr))
 | |
| 	case *exprpb.Expr_ListExpr:
 | |
| 		return p.decorate(p.planCreateList(expr))
 | |
| 	case *exprpb.Expr_StructExpr:
 | |
| 		return p.decorate(p.planCreateStruct(expr))
 | |
| 	case *exprpb.Expr_ComprehensionExpr:
 | |
| 		return p.decorate(p.planComprehension(expr))
 | |
| 	case *exprpb.Expr_ConstExpr:
 | |
| 		return p.decorate(p.planConst(expr))
 | |
| 	}
 | |
| 	return nil, fmt.Errorf("unsupported expr: %v", expr)
 | |
| }
 | |
| 
 | |
| // decorate applies the InterpretableDecorator functions to the given Interpretable.
 | |
| // Both the Interpretable and error generated by a Plan step are accepted as arguments
 | |
| // for convenience.
 | |
| func (p *planner) decorate(i Interpretable, err error) (Interpretable, error) {
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	for _, dec := range p.decorators {
 | |
| 		i, err = dec(i)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	return i, nil
 | |
| }
 | |
| 
 | |
| // planIdent creates an Interpretable that resolves an identifier from an Activation.
 | |
| func (p *planner) planIdent(expr *exprpb.Expr) (Interpretable, error) {
 | |
| 	// Establish whether the identifier is in the reference map.
 | |
| 	if identRef, found := p.refMap[expr.GetId()]; found {
 | |
| 		return p.planCheckedIdent(expr.GetId(), identRef)
 | |
| 	}
 | |
| 	// Create the possible attribute list for the unresolved reference.
 | |
| 	ident := expr.GetIdentExpr()
 | |
| 	return &evalAttr{
 | |
| 		adapter: p.adapter,
 | |
| 		attr:    p.attrFactory.MaybeAttribute(expr.GetId(), ident.Name),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (p *planner) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (Interpretable, error) {
 | |
| 	// Plan a constant reference if this is the case for this simple identifier.
 | |
| 	if identRef.Value != nil {
 | |
| 		return NewConstValue(id, identRef.Value), nil
 | |
| 	}
 | |
| 
 | |
| 	// Check to see whether the type map indicates this is a type name. All types should be
 | |
| 	// registered with the provider.
 | |
| 	cType := p.typeMap[id]
 | |
| 	if cType.Kind() == types.TypeKind {
 | |
| 		cVal, found := p.provider.FindIdent(identRef.Name)
 | |
| 		if !found {
 | |
| 			return nil, fmt.Errorf("reference to undefined type: %s", identRef.Name)
 | |
| 		}
 | |
| 		return NewConstValue(id, cVal), nil
 | |
| 	}
 | |
| 
 | |
| 	// Otherwise, return the attribute for the resolved identifier name.
 | |
| 	return &evalAttr{
 | |
| 		adapter: p.adapter,
 | |
| 		attr:    p.attrFactory.AbsoluteAttribute(id, identRef.Name),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planSelect creates an Interpretable with either:
 | |
| //
 | |
| //	a) selects a field from a map or proto.
 | |
| //	b) creates a field presence test for a select within a has() macro.
 | |
| //	c) resolves the select expression to a namespaced identifier.
 | |
| func (p *planner) planSelect(expr *exprpb.Expr) (Interpretable, error) {
 | |
| 	// If the Select id appears in the reference map from the CheckedExpr proto then it is either
 | |
| 	// a namespaced identifier or enum value.
 | |
| 	if identRef, found := p.refMap[expr.GetId()]; found {
 | |
| 		return p.planCheckedIdent(expr.GetId(), identRef)
 | |
| 	}
 | |
| 
 | |
| 	sel := expr.GetSelectExpr()
 | |
| 	// Plan the operand evaluation.
 | |
| 	op, err := p.Plan(sel.GetOperand())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	opType := p.typeMap[sel.GetOperand().GetId()]
 | |
| 
 | |
| 	// If the Select was marked TestOnly, this is a presence test.
 | |
| 	//
 | |
| 	// Note: presence tests are defined for structured (e.g. proto) and dynamic values (map, json)
 | |
| 	// as follows:
 | |
| 	//  - True if the object field has a non-default value, e.g. obj.str != ""
 | |
| 	//  - True if the dynamic value has the field defined, e.g. key in map
 | |
| 	//
 | |
| 	// However, presence tests are not defined for qualified identifier names with primitive types.
 | |
| 	// If a string named 'a.b.c' is declared in the environment and referenced within `has(a.b.c)`,
 | |
| 	// it is not clear whether has should error or follow the convention defined for structured
 | |
| 	// values.
 | |
| 
 | |
| 	// Establish the attribute reference.
 | |
| 	attr, isAttr := op.(InterpretableAttribute)
 | |
| 	if !isAttr {
 | |
| 		attr, err = p.relativeAttr(op.ID(), op, false)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Build a qualifier for the attribute.
 | |
| 	qual, err := p.attrFactory.NewQualifier(opType, expr.GetId(), sel.GetField(), false)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	// Modify the attribute to be test-only.
 | |
| 	if sel.GetTestOnly() {
 | |
| 		attr = &evalTestOnly{
 | |
| 			id:                     expr.GetId(),
 | |
| 			InterpretableAttribute: attr,
 | |
| 		}
 | |
| 	}
 | |
| 	// Append the qualifier on the attribute.
 | |
| 	_, err = attr.AddQualifier(qual)
 | |
| 	return attr, err
 | |
| }
 | |
| 
 | |
| // planCall creates a callable Interpretable while specializing for common functions and invocation
 | |
| // patterns. Specifically, conditional operators &&, ||, ?:, and (in)equality functions result in
 | |
| // optimized Interpretable values.
 | |
| func (p *planner) planCall(expr *exprpb.Expr) (Interpretable, error) {
 | |
| 	call := expr.GetCallExpr()
 | |
| 	target, fnName, oName := p.resolveFunction(expr)
 | |
| 	argCount := len(call.GetArgs())
 | |
| 	var offset int
 | |
| 	if target != nil {
 | |
| 		argCount++
 | |
| 		offset++
 | |
| 	}
 | |
| 
 | |
| 	args := make([]Interpretable, argCount)
 | |
| 	if target != nil {
 | |
| 		arg, err := p.Plan(target)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		args[0] = arg
 | |
| 	}
 | |
| 	for i, argExpr := range call.GetArgs() {
 | |
| 		arg, err := p.Plan(argExpr)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		args[i+offset] = arg
 | |
| 	}
 | |
| 
 | |
| 	// Generate specialized Interpretable operators by function name if possible.
 | |
| 	switch fnName {
 | |
| 	case operators.LogicalAnd:
 | |
| 		return p.planCallLogicalAnd(expr, args)
 | |
| 	case operators.LogicalOr:
 | |
| 		return p.planCallLogicalOr(expr, args)
 | |
| 	case operators.Conditional:
 | |
| 		return p.planCallConditional(expr, args)
 | |
| 	case operators.Equals:
 | |
| 		return p.planCallEqual(expr, args)
 | |
| 	case operators.NotEquals:
 | |
| 		return p.planCallNotEqual(expr, args)
 | |
| 	case operators.Index:
 | |
| 		return p.planCallIndex(expr, args, false)
 | |
| 	case operators.OptSelect, operators.OptIndex:
 | |
| 		return p.planCallIndex(expr, args, true)
 | |
| 	}
 | |
| 
 | |
| 	// Otherwise, generate Interpretable calls specialized by argument count.
 | |
| 	// Try to find the specific function by overload id.
 | |
| 	var fnDef *functions.Overload
 | |
| 	if oName != "" {
 | |
| 		fnDef, _ = p.disp.FindOverload(oName)
 | |
| 	}
 | |
| 	// If the overload id couldn't resolve the function, try the simple function name.
 | |
| 	if fnDef == nil {
 | |
| 		fnDef, _ = p.disp.FindOverload(fnName)
 | |
| 	}
 | |
| 	switch argCount {
 | |
| 	case 0:
 | |
| 		return p.planCallZero(expr, fnName, oName, fnDef)
 | |
| 	case 1:
 | |
| 		// If the FunctionOp has been used, then use it as it may exist for the purposes
 | |
| 		// of dynamic dispatch within a singleton function implementation.
 | |
| 		if fnDef != nil && fnDef.Unary == nil && fnDef.Function != nil {
 | |
| 			return p.planCallVarArgs(expr, fnName, oName, fnDef, args)
 | |
| 		}
 | |
| 		return p.planCallUnary(expr, fnName, oName, fnDef, args)
 | |
| 	case 2:
 | |
| 		// If the FunctionOp has been used, then use it as it may exist for the purposes
 | |
| 		// of dynamic dispatch within a singleton function implementation.
 | |
| 		if fnDef != nil && fnDef.Binary == nil && fnDef.Function != nil {
 | |
| 			return p.planCallVarArgs(expr, fnName, oName, fnDef, args)
 | |
| 		}
 | |
| 		return p.planCallBinary(expr, fnName, oName, fnDef, args)
 | |
| 	default:
 | |
| 		return p.planCallVarArgs(expr, fnName, oName, fnDef, args)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // planCallZero generates a zero-arity callable Interpretable.
 | |
| func (p *planner) planCallZero(expr *exprpb.Expr,
 | |
| 	function string,
 | |
| 	overload string,
 | |
| 	impl *functions.Overload) (Interpretable, error) {
 | |
| 	if impl == nil || impl.Function == nil {
 | |
| 		return nil, fmt.Errorf("no such overload: %s()", function)
 | |
| 	}
 | |
| 	return &evalZeroArity{
 | |
| 		id:       expr.GetId(),
 | |
| 		function: function,
 | |
| 		overload: overload,
 | |
| 		impl:     impl.Function,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCallUnary generates a unary callable Interpretable.
 | |
| func (p *planner) planCallUnary(expr *exprpb.Expr,
 | |
| 	function string,
 | |
| 	overload string,
 | |
| 	impl *functions.Overload,
 | |
| 	args []Interpretable) (Interpretable, error) {
 | |
| 	var fn functions.UnaryOp
 | |
| 	var trait int
 | |
| 	var nonStrict bool
 | |
| 	if impl != nil {
 | |
| 		if impl.Unary == nil {
 | |
| 			return nil, fmt.Errorf("no such overload: %s(arg)", function)
 | |
| 		}
 | |
| 		fn = impl.Unary
 | |
| 		trait = impl.OperandTrait
 | |
| 		nonStrict = impl.NonStrict
 | |
| 	}
 | |
| 	return &evalUnary{
 | |
| 		id:        expr.GetId(),
 | |
| 		function:  function,
 | |
| 		overload:  overload,
 | |
| 		arg:       args[0],
 | |
| 		trait:     trait,
 | |
| 		impl:      fn,
 | |
| 		nonStrict: nonStrict,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCallBinary generates a binary callable Interpretable.
 | |
| func (p *planner) planCallBinary(expr *exprpb.Expr,
 | |
| 	function string,
 | |
| 	overload string,
 | |
| 	impl *functions.Overload,
 | |
| 	args []Interpretable) (Interpretable, error) {
 | |
| 	var fn functions.BinaryOp
 | |
| 	var trait int
 | |
| 	var nonStrict bool
 | |
| 	if impl != nil {
 | |
| 		if impl.Binary == nil {
 | |
| 			return nil, fmt.Errorf("no such overload: %s(lhs, rhs)", function)
 | |
| 		}
 | |
| 		fn = impl.Binary
 | |
| 		trait = impl.OperandTrait
 | |
| 		nonStrict = impl.NonStrict
 | |
| 	}
 | |
| 	return &evalBinary{
 | |
| 		id:        expr.GetId(),
 | |
| 		function:  function,
 | |
| 		overload:  overload,
 | |
| 		lhs:       args[0],
 | |
| 		rhs:       args[1],
 | |
| 		trait:     trait,
 | |
| 		impl:      fn,
 | |
| 		nonStrict: nonStrict,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCallVarArgs generates a variable argument callable Interpretable.
 | |
| func (p *planner) planCallVarArgs(expr *exprpb.Expr,
 | |
| 	function string,
 | |
| 	overload string,
 | |
| 	impl *functions.Overload,
 | |
| 	args []Interpretable) (Interpretable, error) {
 | |
| 	var fn functions.FunctionOp
 | |
| 	var trait int
 | |
| 	var nonStrict bool
 | |
| 	if impl != nil {
 | |
| 		if impl.Function == nil {
 | |
| 			return nil, fmt.Errorf("no such overload: %s(...)", function)
 | |
| 		}
 | |
| 		fn = impl.Function
 | |
| 		trait = impl.OperandTrait
 | |
| 		nonStrict = impl.NonStrict
 | |
| 	}
 | |
| 	return &evalVarArgs{
 | |
| 		id:        expr.GetId(),
 | |
| 		function:  function,
 | |
| 		overload:  overload,
 | |
| 		args:      args,
 | |
| 		trait:     trait,
 | |
| 		impl:      fn,
 | |
| 		nonStrict: nonStrict,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCallEqual generates an equals (==) Interpretable.
 | |
| func (p *planner) planCallEqual(expr *exprpb.Expr, args []Interpretable) (Interpretable, error) {
 | |
| 	return &evalEq{
 | |
| 		id:  expr.GetId(),
 | |
| 		lhs: args[0],
 | |
| 		rhs: args[1],
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCallNotEqual generates a not equals (!=) Interpretable.
 | |
| func (p *planner) planCallNotEqual(expr *exprpb.Expr, args []Interpretable) (Interpretable, error) {
 | |
| 	return &evalNe{
 | |
| 		id:  expr.GetId(),
 | |
| 		lhs: args[0],
 | |
| 		rhs: args[1],
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCallLogicalAnd generates a logical and (&&) Interpretable.
 | |
| func (p *planner) planCallLogicalAnd(expr *exprpb.Expr, args []Interpretable) (Interpretable, error) {
 | |
| 	return &evalAnd{
 | |
| 		id:    expr.GetId(),
 | |
| 		terms: args,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCallLogicalOr generates a logical or (||) Interpretable.
 | |
| func (p *planner) planCallLogicalOr(expr *exprpb.Expr, args []Interpretable) (Interpretable, error) {
 | |
| 	return &evalOr{
 | |
| 		id:    expr.GetId(),
 | |
| 		terms: args,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCallConditional generates a conditional / ternary (c ? t : f) Interpretable.
 | |
| func (p *planner) planCallConditional(expr *exprpb.Expr, args []Interpretable) (Interpretable, error) {
 | |
| 	cond := args[0]
 | |
| 	t := args[1]
 | |
| 	var tAttr Attribute
 | |
| 	truthyAttr, isTruthyAttr := t.(InterpretableAttribute)
 | |
| 	if isTruthyAttr {
 | |
| 		tAttr = truthyAttr.Attr()
 | |
| 	} else {
 | |
| 		tAttr = p.attrFactory.RelativeAttribute(t.ID(), t)
 | |
| 	}
 | |
| 
 | |
| 	f := args[2]
 | |
| 	var fAttr Attribute
 | |
| 	falsyAttr, isFalsyAttr := f.(InterpretableAttribute)
 | |
| 	if isFalsyAttr {
 | |
| 		fAttr = falsyAttr.Attr()
 | |
| 	} else {
 | |
| 		fAttr = p.attrFactory.RelativeAttribute(f.ID(), f)
 | |
| 	}
 | |
| 
 | |
| 	return &evalAttr{
 | |
| 		adapter: p.adapter,
 | |
| 		attr:    p.attrFactory.ConditionalAttribute(expr.GetId(), cond, tAttr, fAttr),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCallIndex either extends an attribute with the argument to the index operation, or creates
 | |
| // a relative attribute based on the return of a function call or operation.
 | |
| func (p *planner) planCallIndex(expr *exprpb.Expr, args []Interpretable, optional bool) (Interpretable, error) {
 | |
| 	op := args[0]
 | |
| 	ind := args[1]
 | |
| 	opType := p.typeMap[op.ID()]
 | |
| 
 | |
| 	// Establish the attribute reference.
 | |
| 	var err error
 | |
| 	attr, isAttr := op.(InterpretableAttribute)
 | |
| 	if !isAttr {
 | |
| 		attr, err = p.relativeAttr(op.ID(), op, false)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Construct the qualifier type.
 | |
| 	var qual Qualifier
 | |
| 	switch ind := ind.(type) {
 | |
| 	case InterpretableConst:
 | |
| 		qual, err = p.attrFactory.NewQualifier(opType, expr.GetId(), ind.Value(), optional)
 | |
| 	case InterpretableAttribute:
 | |
| 		qual, err = p.attrFactory.NewQualifier(opType, expr.GetId(), ind, optional)
 | |
| 	default:
 | |
| 		qual, err = p.relativeAttr(expr.GetId(), ind, optional)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Add the qualifier to the attribute
 | |
| 	_, err = attr.AddQualifier(qual)
 | |
| 	return attr, err
 | |
| }
 | |
| 
 | |
| // planCreateList generates a list construction Interpretable.
 | |
| func (p *planner) planCreateList(expr *exprpb.Expr) (Interpretable, error) {
 | |
| 	list := expr.GetListExpr()
 | |
| 	optionalIndices := list.GetOptionalIndices()
 | |
| 	elements := list.GetElements()
 | |
| 	optionals := make([]bool, len(elements))
 | |
| 	for _, index := range optionalIndices {
 | |
| 		if index < 0 || index >= int32(len(elements)) {
 | |
| 			return nil, fmt.Errorf("optional index %d out of element bounds [0, %d]", index, len(elements))
 | |
| 		}
 | |
| 		optionals[index] = true
 | |
| 	}
 | |
| 	elems := make([]Interpretable, len(elements))
 | |
| 	for i, elem := range elements {
 | |
| 		elemVal, err := p.Plan(elem)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		elems[i] = elemVal
 | |
| 	}
 | |
| 	return &evalList{
 | |
| 		id:           expr.GetId(),
 | |
| 		elems:        elems,
 | |
| 		optionals:    optionals,
 | |
| 		hasOptionals: len(optionals) != 0,
 | |
| 		adapter:      p.adapter,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCreateStruct generates a map or object construction Interpretable.
 | |
| func (p *planner) planCreateStruct(expr *exprpb.Expr) (Interpretable, error) {
 | |
| 	str := expr.GetStructExpr()
 | |
| 	if len(str.MessageName) != 0 {
 | |
| 		return p.planCreateObj(expr)
 | |
| 	}
 | |
| 	entries := str.GetEntries()
 | |
| 	optionals := make([]bool, len(entries))
 | |
| 	keys := make([]Interpretable, len(entries))
 | |
| 	vals := make([]Interpretable, len(entries))
 | |
| 	for i, entry := range entries {
 | |
| 		keyVal, err := p.Plan(entry.GetMapKey())
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		keys[i] = keyVal
 | |
| 
 | |
| 		valVal, err := p.Plan(entry.GetValue())
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		vals[i] = valVal
 | |
| 		optionals[i] = entry.GetOptionalEntry()
 | |
| 	}
 | |
| 	return &evalMap{
 | |
| 		id:           expr.GetId(),
 | |
| 		keys:         keys,
 | |
| 		vals:         vals,
 | |
| 		optionals:    optionals,
 | |
| 		hasOptionals: len(optionals) != 0,
 | |
| 		adapter:      p.adapter,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planCreateObj generates an object construction Interpretable.
 | |
| func (p *planner) planCreateObj(expr *exprpb.Expr) (Interpretable, error) {
 | |
| 	obj := expr.GetStructExpr()
 | |
| 	typeName, defined := p.resolveTypeName(obj.GetMessageName())
 | |
| 	if !defined {
 | |
| 		return nil, fmt.Errorf("unknown type: %s", obj.GetMessageName())
 | |
| 	}
 | |
| 	entries := obj.GetEntries()
 | |
| 	optionals := make([]bool, len(entries))
 | |
| 	fields := make([]string, len(entries))
 | |
| 	vals := make([]Interpretable, len(entries))
 | |
| 	for i, entry := range entries {
 | |
| 		fields[i] = entry.GetFieldKey()
 | |
| 		val, err := p.Plan(entry.GetValue())
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		vals[i] = val
 | |
| 		optionals[i] = entry.GetOptionalEntry()
 | |
| 	}
 | |
| 	return &evalObj{
 | |
| 		id:           expr.GetId(),
 | |
| 		typeName:     typeName,
 | |
| 		fields:       fields,
 | |
| 		vals:         vals,
 | |
| 		optionals:    optionals,
 | |
| 		hasOptionals: len(optionals) != 0,
 | |
| 		provider:     p.provider,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planComprehension generates an Interpretable fold operation.
 | |
| func (p *planner) planComprehension(expr *exprpb.Expr) (Interpretable, error) {
 | |
| 	fold := expr.GetComprehensionExpr()
 | |
| 	accu, err := p.Plan(fold.GetAccuInit())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	iterRange, err := p.Plan(fold.GetIterRange())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	cond, err := p.Plan(fold.GetLoopCondition())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	step, err := p.Plan(fold.GetLoopStep())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	result, err := p.Plan(fold.GetResult())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &evalFold{
 | |
| 		id:        expr.GetId(),
 | |
| 		accuVar:   fold.AccuVar,
 | |
| 		accu:      accu,
 | |
| 		iterVar:   fold.IterVar,
 | |
| 		iterRange: iterRange,
 | |
| 		cond:      cond,
 | |
| 		step:      step,
 | |
| 		result:    result,
 | |
| 		adapter:   p.adapter,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // planConst generates a constant valued Interpretable.
 | |
| func (p *planner) planConst(expr *exprpb.Expr) (Interpretable, error) {
 | |
| 	val, err := p.constValue(expr.GetConstExpr())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return NewConstValue(expr.GetId(), val), nil
 | |
| }
 | |
| 
 | |
| // constValue converts a proto Constant value to a ref.Val.
 | |
| func (p *planner) constValue(c *exprpb.Constant) (ref.Val, error) {
 | |
| 	switch c.GetConstantKind().(type) {
 | |
| 	case *exprpb.Constant_BoolValue:
 | |
| 		return p.adapter.NativeToValue(c.GetBoolValue()), nil
 | |
| 	case *exprpb.Constant_BytesValue:
 | |
| 		return p.adapter.NativeToValue(c.GetBytesValue()), nil
 | |
| 	case *exprpb.Constant_DoubleValue:
 | |
| 		return p.adapter.NativeToValue(c.GetDoubleValue()), nil
 | |
| 	case *exprpb.Constant_DurationValue:
 | |
| 		return p.adapter.NativeToValue(c.GetDurationValue().AsDuration()), nil
 | |
| 	case *exprpb.Constant_Int64Value:
 | |
| 		return p.adapter.NativeToValue(c.GetInt64Value()), nil
 | |
| 	case *exprpb.Constant_NullValue:
 | |
| 		return p.adapter.NativeToValue(c.GetNullValue()), nil
 | |
| 	case *exprpb.Constant_StringValue:
 | |
| 		return p.adapter.NativeToValue(c.GetStringValue()), nil
 | |
| 	case *exprpb.Constant_TimestampValue:
 | |
| 		return p.adapter.NativeToValue(c.GetTimestampValue().AsTime()), nil
 | |
| 	case *exprpb.Constant_Uint64Value:
 | |
| 		return p.adapter.NativeToValue(c.GetUint64Value()), nil
 | |
| 	}
 | |
| 	return nil, fmt.Errorf("unknown constant type: %v", c)
 | |
| }
 | |
| 
 | |
| // resolveTypeName takes a qualified string constructed at parse time, applies the proto
 | |
| // namespace resolution rules to it in a scan over possible matching types in the TypeProvider.
 | |
| func (p *planner) resolveTypeName(typeName string) (string, bool) {
 | |
| 	for _, qualifiedTypeName := range p.container.ResolveCandidateNames(typeName) {
 | |
| 		if _, found := p.provider.FindStructType(qualifiedTypeName); found {
 | |
| 			return qualifiedTypeName, true
 | |
| 		}
 | |
| 	}
 | |
| 	return "", false
 | |
| }
 | |
| 
 | |
| // resolveFunction determines the call target, function name, and overload name from a given Expr
 | |
| // value.
 | |
| //
 | |
| // The resolveFunction resolves ambiguities where a function may either be a receiver-style
 | |
| // invocation or a qualified global function name.
 | |
| // - The target expression may only consist of ident and select expressions.
 | |
| // - The function is declared in the environment using its fully-qualified name.
 | |
| // - The fully-qualified function name matches the string serialized target value.
 | |
| func (p *planner) resolveFunction(expr *exprpb.Expr) (*exprpb.Expr, string, string) {
 | |
| 	// Note: similar logic exists within the `checker/checker.go`. If making changes here
 | |
| 	// please consider the impact on checker.go and consolidate implementations or mirror code
 | |
| 	// as appropriate.
 | |
| 	call := expr.GetCallExpr()
 | |
| 	target := call.GetTarget()
 | |
| 	fnName := call.GetFunction()
 | |
| 
 | |
| 	// Checked expressions always have a reference map entry, and _should_ have the fully qualified
 | |
| 	// function name as the fnName value.
 | |
| 	oRef, hasOverload := p.refMap[expr.GetId()]
 | |
| 	if hasOverload {
 | |
| 		if len(oRef.OverloadIDs) == 1 {
 | |
| 			return target, fnName, oRef.OverloadIDs[0]
 | |
| 		}
 | |
| 		// Note, this namespaced function name will not appear as a fully qualified name in ASTs
 | |
| 		// built and stored before cel-go v0.5.0; however, this functionality did not work at all
 | |
| 		// before the v0.5.0 release.
 | |
| 		return target, fnName, ""
 | |
| 	}
 | |
| 
 | |
| 	// Parse-only expressions need to handle the same logic as is normally performed at check time,
 | |
| 	// but with potentially much less information. The only reliable source of information about
 | |
| 	// which functions are configured is the dispatcher.
 | |
| 	if target == nil {
 | |
| 		// If the user has a parse-only expression, then it should have been configured as such in
 | |
| 		// the interpreter dispatcher as it may have been omitted from the checker environment.
 | |
| 		for _, qualifiedName := range p.container.ResolveCandidateNames(fnName) {
 | |
| 			_, found := p.disp.FindOverload(qualifiedName)
 | |
| 			if found {
 | |
| 				return nil, qualifiedName, ""
 | |
| 			}
 | |
| 		}
 | |
| 		// It's possible that the overload was not found, but this situation is accounted for in
 | |
| 		// the planCall phase; however, the leading dot used for denoting fully-qualified
 | |
| 		// namespaced identifiers must be stripped, as all declarations already use fully-qualified
 | |
| 		// names. This stripping behavior is handled automatically by the ResolveCandidateNames
 | |
| 		// call.
 | |
| 		return target, stripLeadingDot(fnName), ""
 | |
| 	}
 | |
| 
 | |
| 	// Handle the situation where the function target actually indicates a qualified function name.
 | |
| 	qualifiedPrefix, maybeQualified := p.toQualifiedName(target)
 | |
| 	if maybeQualified {
 | |
| 		maybeQualifiedName := qualifiedPrefix + "." + fnName
 | |
| 		for _, qualifiedName := range p.container.ResolveCandidateNames(maybeQualifiedName) {
 | |
| 			_, found := p.disp.FindOverload(qualifiedName)
 | |
| 			if found {
 | |
| 				// Clear the target to ensure the proper arity is used for finding the
 | |
| 				// implementation.
 | |
| 				return nil, qualifiedName, ""
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	// In the default case, the function is exactly as it was advertised: a receiver call on with
 | |
| 	// an expression-based target with the given simple function name.
 | |
| 	return target, fnName, ""
 | |
| }
 | |
| 
 | |
| // relativeAttr indicates that the attribute in this case acts as a qualifier and as such needs to
 | |
| // be observed to ensure that it's evaluation value is properly recorded for state tracking.
 | |
| func (p *planner) relativeAttr(id int64, eval Interpretable, opt bool) (InterpretableAttribute, error) {
 | |
| 	eAttr, ok := eval.(InterpretableAttribute)
 | |
| 	if !ok {
 | |
| 		eAttr = &evalAttr{
 | |
| 			adapter:  p.adapter,
 | |
| 			attr:     p.attrFactory.RelativeAttribute(id, eval),
 | |
| 			optional: opt,
 | |
| 		}
 | |
| 	}
 | |
| 	// This looks like it should either decorate the new evalAttr node, or early return the InterpretableAttribute
 | |
| 	decAttr, err := p.decorate(eAttr, nil)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	eAttr, ok = decAttr.(InterpretableAttribute)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("invalid attribute decoration: %v(%T)", decAttr, decAttr)
 | |
| 	}
 | |
| 	return eAttr, nil
 | |
| }
 | |
| 
 | |
| // toQualifiedName converts an expression AST into a qualified name if possible, with a boolean
 | |
| // 'found' value that indicates if the conversion is successful.
 | |
| func (p *planner) toQualifiedName(operand *exprpb.Expr) (string, bool) {
 | |
| 	// If the checker identified the expression as an attribute by the type-checker, then it can't
 | |
| 	// possibly be part of qualified name in a namespace.
 | |
| 	_, isAttr := p.refMap[operand.GetId()]
 | |
| 	if isAttr {
 | |
| 		return "", false
 | |
| 	}
 | |
| 	// Since functions cannot be both namespaced and receiver functions, if the operand is not an
 | |
| 	// qualified variable name, return the (possibly) qualified name given the expressions.
 | |
| 	return containers.ToQualifiedName(operand)
 | |
| }
 | |
| 
 | |
| func stripLeadingDot(name string) string {
 | |
| 	if strings.HasPrefix(name, ".") {
 | |
| 		return name[1:]
 | |
| 	}
 | |
| 	return name
 | |
| }
 |