mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-11-23 06:40:23 +00:00
e5d9b68d36
Bumps the golang-dependencies group with 1 update: [golang.org/x/crypto](https://github.com/golang/crypto). Updates `golang.org/x/crypto` from 0.16.0 to 0.17.0 - [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor dependency-group: golang-dependencies ... Signed-off-by: dependabot[bot] <support@github.com>
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
|
|
}
|