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"
)
type interpretablePlanner interface {
Plan(expr ast.Expr) (Interpretable, error)
}
func newPlanner(disp Dispatcher,
provider types.Provider,
adapter types.Adapter,
attrFactory AttributeFactory,
cont *containers.Container,
exprAST *ast.AST,
decorators ...InterpretableDecorator) interpretablePlanner {
return &planner{
disp: disp,
provider: provider,
adapter: adapter,
attrFactory: attrFactory,
container: cont,
refMap: exprAST.ReferenceMap(),
typeMap: exprAST.TypeMap(),
decorators: decorators,
}
}
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
}
func (p *planner) Plan(expr ast.Expr) (Interpretable, error) {
switch expr.Kind() {
case ast.CallKind:
return p.decorate(p.planCall(expr))
case ast.IdentKind:
return p.decorate(p.planIdent(expr))
case ast.LiteralKind:
return p.decorate(p.planConst(expr))
case ast.SelectKind:
return p.decorate(p.planSelect(expr))
case ast.ListKind:
return p.decorate(p.planCreateList(expr))
case ast.MapKind:
return p.decorate(p.planCreateMap(expr))
case ast.StructKind:
return p.decorate(p.planCreateStruct(expr))
case ast.ComprehensionKind:
return p.decorate(p.planComprehension(expr))
}
return nil, fmt.Errorf("unsupported expr: %v", expr)
}
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
}
func (p *planner) planIdent(expr ast.Expr) (Interpretable, error) {
if identRef, found := p.refMap[expr.ID()]; found {
return p.planCheckedIdent(expr.ID(), identRef)
}
ident := expr.AsIdent()
return &evalAttr{
adapter: p.adapter,
attr: p.attrFactory.MaybeAttribute(expr.ID(), ident),
}, nil
}
func (p *planner) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (Interpretable, error) {
if identRef.Value != nil {
return NewConstValue(id, identRef.Value), nil
}
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
}
return &evalAttr{
adapter: p.adapter,
attr: p.attrFactory.AbsoluteAttribute(id, identRef.Name),
}, nil
}
func (p *planner) planSelect(expr ast.Expr) (Interpretable, error) {
if identRef, found := p.refMap[expr.ID()]; found {
return p.planCheckedIdent(expr.ID(), identRef)
}
sel := expr.AsSelect()
op, err := p.Plan(sel.Operand())
if err != nil {
return nil, err
}
opType := p.typeMap[sel.Operand().ID()]
attr, isAttr := op.(InterpretableAttribute)
if !isAttr {
attr, err = p.relativeAttr(op.ID(), op, false)
if err != nil {
return nil, err
}
}
qual, err := p.attrFactory.NewQualifier(opType, expr.ID(), sel.FieldName(), false)
if err != nil {
return nil, err
}
if sel.IsTestOnly() {
attr = &evalTestOnly{
id: expr.ID(),
InterpretableAttribute: attr,
}
}
_, err = attr.AddQualifier(qual)
return attr, err
}
func (p *planner) planCall(expr ast.Expr) (Interpretable, error) {
call := expr.AsCall()
target, fnName, oName := p.resolveFunction(expr)
argCount := len(call.Args())
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.Args() {
arg, err := p.Plan(argExpr)
if err != nil {
return nil, err
}
args[i+offset] = arg
}
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)
}
var fnDef *functions.Overload
if oName != "" {
fnDef, _ = p.disp.FindOverload(oName)
}
if fnDef == nil {
fnDef, _ = p.disp.FindOverload(fnName)
}
switch argCount {
case 0:
return p.planCallZero(expr, fnName, oName, fnDef)
case 1:
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 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)
}
}
func (p *planner) planCallZero(expr ast.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.ID(),
function: function,
overload: overload,
impl: impl.Function,
}, nil
}
func (p *planner) planCallUnary(expr ast.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.ID(),
function: function,
overload: overload,
arg: args[0],
trait: trait,
impl: fn,
nonStrict: nonStrict,
}, nil
}
func (p *planner) planCallBinary(expr ast.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.ID(),
function: function,
overload: overload,
lhs: args[0],
rhs: args[1],
trait: trait,
impl: fn,
nonStrict: nonStrict,
}, nil
}
func (p *planner) planCallVarArgs(expr ast.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.ID(),
function: function,
overload: overload,
args: args,
trait: trait,
impl: fn,
nonStrict: nonStrict,
}, nil
}
func (p *planner) planCallEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) {
return &evalEq{
id: expr.ID(),
lhs: args[0],
rhs: args[1],
}, nil
}
func (p *planner) planCallNotEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) {
return &evalNe{
id: expr.ID(),
lhs: args[0],
rhs: args[1],
}, nil
}
func (p *planner) planCallLogicalAnd(expr ast.Expr, args []Interpretable) (Interpretable, error) {
return &evalAnd{
id: expr.ID(),
terms: args,
}, nil
}
func (p *planner) planCallLogicalOr(expr ast.Expr, args []Interpretable) (Interpretable, error) {
return &evalOr{
id: expr.ID(),
terms: args,
}, nil
}
func (p *planner) planCallConditional(expr ast.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.ID(), cond, tAttr, fAttr),
}, nil
}
func (p *planner) planCallIndex(expr ast.Expr, args []Interpretable, optional bool) (Interpretable, error) {
op := args[0]
ind := args[1]
opType := p.typeMap[op.ID()]
var err error
attr, isAttr := op.(InterpretableAttribute)
if !isAttr {
attr, err = p.relativeAttr(op.ID(), op, false)
if err != nil {
return nil, err
}
}
var qual Qualifier
switch ind := ind.(type) {
case InterpretableConst:
qual, err = p.attrFactory.NewQualifier(opType, expr.ID(), ind.Value(), optional)
case InterpretableAttribute:
qual, err = p.attrFactory.NewQualifier(opType, expr.ID(), ind, optional)
default:
qual, err = p.relativeAttr(expr.ID(), ind, optional)
}
if err != nil {
return nil, err
}
_, err = attr.AddQualifier(qual)
return attr, err
}
func (p *planner) planCreateList(expr ast.Expr) (Interpretable, error) {
list := expr.AsList()
optionalIndices := list.OptionalIndices()
elements := list.Elements()
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.ID(),
elems: elems,
optionals: optionals,
hasOptionals: len(optionals) != 0,
adapter: p.adapter,
}, nil
}
func (p *planner) planCreateMap(expr ast.Expr) (Interpretable, error) {
m := expr.AsMap()
entries := m.Entries()
optionals := make([]bool, len(entries))
keys := make([]Interpretable, len(entries))
vals := make([]Interpretable, len(entries))
for i, e := range entries {
entry := e.AsMapEntry()
keyVal, err := p.Plan(entry.Key())
if err != nil {
return nil, err
}
keys[i] = keyVal
valVal, err := p.Plan(entry.Value())
if err != nil {
return nil, err
}
vals[i] = valVal
optionals[i] = entry.IsOptional()
}
return &evalMap{
id: expr.ID(),
keys: keys,
vals: vals,
optionals: optionals,
hasOptionals: len(optionals) != 0,
adapter: p.adapter,
}, nil
}
func (p *planner) planCreateStruct(expr ast.Expr) (Interpretable, error) {
obj := expr.AsStruct()
typeName, defined := p.resolveTypeName(obj.TypeName())
if !defined {
return nil, fmt.Errorf("unknown type: %s", obj.TypeName())
}
objFields := obj.Fields()
optionals := make([]bool, len(objFields))
fields := make([]string, len(objFields))
vals := make([]Interpretable, len(objFields))
for i, f := range objFields {
field := f.AsStructField()
fields[i] = field.Name()
val, err := p.Plan(field.Value())
if err != nil {
return nil, err
}
vals[i] = val
optionals[i] = field.IsOptional()
}
return &evalObj{
id: expr.ID(),
typeName: typeName,
fields: fields,
vals: vals,
optionals: optionals,
hasOptionals: len(optionals) != 0,
provider: p.provider,
}, nil
}
func (p *planner) planComprehension(expr ast.Expr) (Interpretable, error) {
fold := expr.AsComprehension()
accu, err := p.Plan(fold.AccuInit())
if err != nil {
return nil, err
}
iterRange, err := p.Plan(fold.IterRange())
if err != nil {
return nil, err
}
cond, err := p.Plan(fold.LoopCondition())
if err != nil {
return nil, err
}
step, err := p.Plan(fold.LoopStep())
if err != nil {
return nil, err
}
result, err := p.Plan(fold.Result())
if err != nil {
return nil, err
}
return &evalFold{
id: expr.ID(),
accuVar: fold.AccuVar(),
accu: accu,
iterVar: fold.IterVar(),
iterRange: iterRange,
cond: cond,
step: step,
result: result,
adapter: p.adapter,
}, nil
}
func (p *planner) planConst(expr ast.Expr) (Interpretable, error) {
return NewConstValue(expr.ID(), expr.AsLiteral()), nil
}
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
}
func (p *planner) resolveFunction(expr ast.Expr) (ast.Expr, string, string) {
call := expr.AsCall()
var target ast.Expr = nil
if call.IsMemberFunction() {
target = call.Target()
}
fnName := call.FunctionName()
oRef, hasOverload := p.refMap[expr.ID()]
if hasOverload {
if len(oRef.OverloadIDs) == 1 {
return target, fnName, oRef.OverloadIDs[0]
}
return target, fnName, ""
}
if target == nil {
for _, qualifiedName := range p.container.ResolveCandidateNames(fnName) {
_, found := p.disp.FindOverload(qualifiedName)
if found {
return nil, qualifiedName, ""
}
}
return target, stripLeadingDot(fnName), ""
}
qualifiedPrefix, maybeQualified := p.toQualifiedName(target)
if maybeQualified {
maybeQualifiedName := qualifiedPrefix + "." + fnName
for _, qualifiedName := range p.container.ResolveCandidateNames(maybeQualifiedName) {
_, found := p.disp.FindOverload(qualifiedName)
if found {
return nil, qualifiedName, ""
}
}
}
return target, fnName, ""
}
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,
}
}
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
}
func (p *planner) toQualifiedName(operand ast.Expr) (string, bool) {
_, isAttr := p.refMap[operand.ID()]
if isAttr {
return "", false
}
switch operand.Kind() {
case ast.IdentKind:
id := operand.AsIdent()
return id, true
case ast.SelectKind:
sel := operand.AsSelect()
if sel.IsTestOnly() {
return "", false
}
if qual, found := p.toQualifiedName(sel.Operand()); found {
return qual + "." + sel.FieldName(), true
}
}
return "", false
}
func stripLeadingDot(name string) string {
if strings.HasPrefix(name, ".") {
return name[1:]
}
return name
}