ceph-csi/vendor/github.com/google/cel-go/interpreter/interpretable.go

1231 lines
33 KiB
Go
Raw Normal View History

// Copyright 2019 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 (
"math"
"github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/overloads"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
"github.com/google/cel-go/interpreter/functions"
)
// Interpretable can accept a given Activation and produce a value along with
// an accompanying EvalState which can be used to inspect whether additional
// data might be necessary to complete the evaluation.
type Interpretable interface {
// ID value corresponding to the expression node.
ID() int64
// Eval an Activation to produce an output.
Eval(activation Activation) ref.Val
}
// InterpretableConst interface for tracking whether the Interpretable is a constant value.
type InterpretableConst interface {
Interpretable
// Value returns the constant value of the instruction.
Value() ref.Val
}
// InterpretableAttribute interface for tracking whether the Interpretable is an attribute.
type InterpretableAttribute interface {
Interpretable
// Attr returns the Attribute value.
Attr() Attribute
// Adapter returns the type adapter to be used for adapting resolved Attribute values.
Adapter() ref.TypeAdapter
// AddQualifier proxies the Attribute.AddQualifier method.
//
// Note, this method may mutate the current attribute state. If the desire is to clone the
// Attribute, the Attribute should first be copied before adding the qualifier. Attributes
// are not copyable by default, so this is a capable that would need to be added to the
// AttributeFactory or specifically to the underlying Attribute implementation.
AddQualifier(Qualifier) (Attribute, error)
// Qualify replicates the Attribute.Qualify method to permit extension and interception
// of object qualification.
Qualify(vars Activation, obj interface{}) (interface{}, error)
// Resolve returns the value of the Attribute given the current Activation.
Resolve(Activation) (interface{}, error)
}
// InterpretableCall interface for inspecting Interpretable instructions related to function calls.
type InterpretableCall interface {
Interpretable
// Function returns the function name as it appears in text or mangled operator name as it
// appears in the operators.go file.
Function() string
// OverloadID returns the overload id associated with the function specialization.
// Overload ids are stable across language boundaries and can be treated as synonymous with a
// unique function signature.
OverloadID() string
// Args returns the normalized arguments to the function overload.
// For receiver-style functions, the receiver target is arg 0.
Args() []Interpretable
}
// InterpretableConstructor interface for inspecting Interpretable instructions that initialize a list, map
// or struct.
type InterpretableConstructor interface {
Interpretable
// InitVals returns all the list elements, map key and values or struct field values.
InitVals() []Interpretable
// Type returns the type constructed.
Type() ref.Type
}
// Core Interpretable implementations used during the program planning phase.
type evalTestOnly struct {
id int64
op Interpretable
field types.String
fieldType *ref.FieldType
}
// ID implements the Interpretable interface method.
func (test *evalTestOnly) ID() int64 {
return test.id
}
// Eval implements the Interpretable interface method.
func (test *evalTestOnly) Eval(ctx Activation) ref.Val {
// Handle field selection on a proto in the most efficient way possible.
if test.fieldType != nil {
opAttr, ok := test.op.(InterpretableAttribute)
if ok {
opVal, err := opAttr.Resolve(ctx)
if err != nil {
return types.NewErr(err.Error())
}
refVal, ok := opVal.(ref.Val)
if ok {
opVal = refVal.Value()
}
if test.fieldType.IsSet(opVal) {
return types.True
}
return types.False
}
}
obj := test.op.Eval(ctx)
tester, ok := obj.(traits.FieldTester)
if ok {
return tester.IsSet(test.field)
}
container, ok := obj.(traits.Container)
if ok {
return container.Contains(test.field)
}
return types.ValOrErr(obj, "invalid type for field selection.")
}
// Cost provides the heuristic cost of a `has(field)` macro. The cost has at least 1 for determining
// if the field exists, apart from the cost of accessing the field.
func (test *evalTestOnly) Cost() (min, max int64) {
min, max = estimateCost(test.op)
min++
max++
return
}
// NewConstValue creates a new constant valued Interpretable.
func NewConstValue(id int64, val ref.Val) InterpretableConst {
return &evalConst{
id: id,
val: val,
}
}
type evalConst struct {
id int64
val ref.Val
}
// ID implements the Interpretable interface method.
func (cons *evalConst) ID() int64 {
return cons.id
}
// Eval implements the Interpretable interface method.
func (cons *evalConst) Eval(ctx Activation) ref.Val {
return cons.val
}
// Cost returns zero for a constant valued Interpretable.
func (cons *evalConst) Cost() (min, max int64) {
return 0, 0
}
// Value implements the InterpretableConst interface method.
func (cons *evalConst) Value() ref.Val {
return cons.val
}
type evalOr struct {
id int64
lhs Interpretable
rhs Interpretable
}
// ID implements the Interpretable interface method.
func (or *evalOr) ID() int64 {
return or.id
}
// Eval implements the Interpretable interface method.
func (or *evalOr) Eval(ctx Activation) ref.Val {
// short-circuit lhs.
lVal := or.lhs.Eval(ctx)
lBool, lok := lVal.(types.Bool)
if lok && lBool == types.True {
return types.True
}
// short-circuit on rhs.
rVal := or.rhs.Eval(ctx)
rBool, rok := rVal.(types.Bool)
if rok && rBool == types.True {
return types.True
}
// return if both sides are bool false.
if lok && rok {
return types.False
}
// TODO: return both values as a set if both are unknown or error.
// prefer left unknown to right unknown.
if types.IsUnknown(lVal) {
return lVal
}
if types.IsUnknown(rVal) {
return rVal
}
// If the left-hand side is non-boolean return it as the error.
if types.IsError(lVal) {
return lVal
}
return types.ValOrErr(rVal, "no such overload")
}
// Cost implements the Coster interface method. The minimum possible cost incurs when the left-hand
// side expr is sufficient in determining the evaluation result.
func (or *evalOr) Cost() (min, max int64) {
return calShortCircuitBinaryOpsCost(or.lhs, or.rhs)
}
type evalAnd struct {
id int64
lhs Interpretable
rhs Interpretable
}
// ID implements the Interpretable interface method.
func (and *evalAnd) ID() int64 {
return and.id
}
// Eval implements the Interpretable interface method.
func (and *evalAnd) Eval(ctx Activation) ref.Val {
// short-circuit lhs.
lVal := and.lhs.Eval(ctx)
lBool, lok := lVal.(types.Bool)
if lok && lBool == types.False {
return types.False
}
// short-circuit on rhs.
rVal := and.rhs.Eval(ctx)
rBool, rok := rVal.(types.Bool)
if rok && rBool == types.False {
return types.False
}
// return if both sides are bool true.
if lok && rok {
return types.True
}
// TODO: return both values as a set if both are unknown or error.
// prefer left unknown to right unknown.
if types.IsUnknown(lVal) {
return lVal
}
if types.IsUnknown(rVal) {
return rVal
}
// If the left-hand side is non-boolean return it as the error.
if types.IsError(lVal) {
return lVal
}
return types.ValOrErr(rVal, "no such overload")
}
// Cost implements the Coster interface method. The minimum possible cost incurs when the left-hand
// side expr is sufficient in determining the evaluation result.
func (and *evalAnd) Cost() (min, max int64) {
return calShortCircuitBinaryOpsCost(and.lhs, and.rhs)
}
func calShortCircuitBinaryOpsCost(lhs, rhs Interpretable) (min, max int64) {
lMin, lMax := estimateCost(lhs)
_, rMax := estimateCost(rhs)
return lMin, lMax + rMax + 1
}
type evalEq struct {
id int64
lhs Interpretable
rhs Interpretable
}
// ID implements the Interpretable interface method.
func (eq *evalEq) ID() int64 {
return eq.id
}
// Eval implements the Interpretable interface method.
func (eq *evalEq) Eval(ctx Activation) ref.Val {
lVal := eq.lhs.Eval(ctx)
rVal := eq.rhs.Eval(ctx)
if types.IsUnknownOrError(lVal) {
return lVal
}
if types.IsUnknownOrError(rVal) {
return rVal
}
return types.Equal(lVal, rVal)
}
// Cost implements the Coster interface method.
func (eq *evalEq) Cost() (min, max int64) {
return calExhaustiveBinaryOpsCost(eq.lhs, eq.rhs)
}
// Function implements the InterpretableCall interface method.
func (*evalEq) Function() string {
return operators.Equals
}
// OverloadID implements the InterpretableCall interface method.
func (*evalEq) OverloadID() string {
return overloads.Equals
}
// Args implements the InterpretableCall interface method.
func (eq *evalEq) Args() []Interpretable {
return []Interpretable{eq.lhs, eq.rhs}
}
type evalNe struct {
id int64
lhs Interpretable
rhs Interpretable
}
// ID implements the Interpretable interface method.
func (ne *evalNe) ID() int64 {
return ne.id
}
// Eval implements the Interpretable interface method.
func (ne *evalNe) Eval(ctx Activation) ref.Val {
lVal := ne.lhs.Eval(ctx)
rVal := ne.rhs.Eval(ctx)
if types.IsUnknownOrError(lVal) {
return lVal
}
if types.IsUnknownOrError(rVal) {
return rVal
}
return types.Bool(types.Equal(lVal, rVal) != types.True)
}
// Cost implements the Coster interface method.
func (ne *evalNe) Cost() (min, max int64) {
return calExhaustiveBinaryOpsCost(ne.lhs, ne.rhs)
}
// Function implements the InterpretableCall interface method.
func (*evalNe) Function() string {
return operators.NotEquals
}
// OverloadID implements the InterpretableCall interface method.
func (*evalNe) OverloadID() string {
return overloads.NotEquals
}
// Args implements the InterpretableCall interface method.
func (ne *evalNe) Args() []Interpretable {
return []Interpretable{ne.lhs, ne.rhs}
}
type evalZeroArity struct {
id int64
function string
overload string
impl functions.FunctionOp
}
// ID implements the Interpretable interface method.
func (zero *evalZeroArity) ID() int64 {
return zero.id
}
// Eval implements the Interpretable interface method.
func (zero *evalZeroArity) Eval(ctx Activation) ref.Val {
return zero.impl()
}
// Cost returns 1 representing the heuristic cost of the function.
func (zero *evalZeroArity) Cost() (min, max int64) {
return 1, 1
}
// Function implements the InterpretableCall interface method.
func (zero *evalZeroArity) Function() string {
return zero.function
}
// OverloadID implements the InterpretableCall interface method.
func (zero *evalZeroArity) OverloadID() string {
return zero.overload
}
// Args returns the argument to the unary function.
func (zero *evalZeroArity) Args() []Interpretable {
return []Interpretable{}
}
type evalUnary struct {
id int64
function string
overload string
arg Interpretable
trait int
impl functions.UnaryOp
nonStrict bool
}
// ID implements the Interpretable interface method.
func (un *evalUnary) ID() int64 {
return un.id
}
// Eval implements the Interpretable interface method.
func (un *evalUnary) Eval(ctx Activation) ref.Val {
argVal := un.arg.Eval(ctx)
// Early return if the argument to the function is unknown or error.
strict := !un.nonStrict
if strict && types.IsUnknownOrError(argVal) {
return argVal
}
// If the implementation is bound and the argument value has the right traits required to
// invoke it, then call the implementation.
if un.impl != nil && (un.trait == 0 || (!strict && types.IsUnknownOrError(argVal)) || argVal.Type().HasTrait(un.trait)) {
return un.impl(argVal)
}
// Otherwise, if the argument is a ReceiverType attempt to invoke the receiver method on the
// operand (arg0).
if argVal.Type().HasTrait(traits.ReceiverType) {
return argVal.(traits.Receiver).Receive(un.function, un.overload, []ref.Val{})
}
return types.NewErr("no such overload: %s", un.function)
}
// Cost implements the Coster interface method.
func (un *evalUnary) Cost() (min, max int64) {
min, max = estimateCost(un.arg)
min++ // add cost for function
max++
return
}
// Function implements the InterpretableCall interface method.
func (un *evalUnary) Function() string {
return un.function
}
// OverloadID implements the InterpretableCall interface method.
func (un *evalUnary) OverloadID() string {
return un.overload
}
// Args returns the argument to the unary function.
func (un *evalUnary) Args() []Interpretable {
return []Interpretable{un.arg}
}
type evalBinary struct {
id int64
function string
overload string
lhs Interpretable
rhs Interpretable
trait int
impl functions.BinaryOp
nonStrict bool
}
// ID implements the Interpretable interface method.
func (bin *evalBinary) ID() int64 {
return bin.id
}
// Eval implements the Interpretable interface method.
func (bin *evalBinary) Eval(ctx Activation) ref.Val {
lVal := bin.lhs.Eval(ctx)
rVal := bin.rhs.Eval(ctx)
// Early return if any argument to the function is unknown or error.
strict := !bin.nonStrict
if strict {
if types.IsUnknownOrError(lVal) {
return lVal
}
if types.IsUnknownOrError(rVal) {
return rVal
}
}
// If the implementation is bound and the argument value has the right traits required to
// invoke it, then call the implementation.
if bin.impl != nil && (bin.trait == 0 || (!strict && types.IsUnknownOrError(lVal)) || lVal.Type().HasTrait(bin.trait)) {
return bin.impl(lVal, rVal)
}
// Otherwise, if the argument is a ReceiverType attempt to invoke the receiver method on the
// operand (arg0).
if lVal.Type().HasTrait(traits.ReceiverType) {
return lVal.(traits.Receiver).Receive(bin.function, bin.overload, []ref.Val{rVal})
}
return types.NewErr("no such overload: %s", bin.function)
}
// Cost implements the Coster interface method.
func (bin *evalBinary) Cost() (min, max int64) {
return calExhaustiveBinaryOpsCost(bin.lhs, bin.rhs)
}
// Function implements the InterpretableCall interface method.
func (bin *evalBinary) Function() string {
return bin.function
}
// OverloadID implements the InterpretableCall interface method.
func (bin *evalBinary) OverloadID() string {
return bin.overload
}
// Args returns the argument to the unary function.
func (bin *evalBinary) Args() []Interpretable {
return []Interpretable{bin.lhs, bin.rhs}
}
type evalVarArgs struct {
id int64
function string
overload string
args []Interpretable
trait int
impl functions.FunctionOp
nonStrict bool
}
// NewCall creates a new call Interpretable.
func NewCall(id int64, function, overload string, args []Interpretable, impl functions.FunctionOp) InterpretableCall {
return &evalVarArgs{
id: id,
function: function,
overload: overload,
args: args,
impl: impl,
}
}
// ID implements the Interpretable interface method.
func (fn *evalVarArgs) ID() int64 {
return fn.id
}
// Eval implements the Interpretable interface method.
func (fn *evalVarArgs) Eval(ctx Activation) ref.Val {
argVals := make([]ref.Val, len(fn.args))
// Early return if any argument to the function is unknown or error.
strict := !fn.nonStrict
for i, arg := range fn.args {
argVals[i] = arg.Eval(ctx)
if strict && types.IsUnknownOrError(argVals[i]) {
return argVals[i]
}
}
// If the implementation is bound and the argument value has the right traits required to
// invoke it, then call the implementation.
arg0 := argVals[0]
if fn.impl != nil && (fn.trait == 0 || (!strict && types.IsUnknownOrError(arg0)) || arg0.Type().HasTrait(fn.trait)) {
return fn.impl(argVals...)
}
// Otherwise, if the argument is a ReceiverType attempt to invoke the receiver method on the
// operand (arg0).
if arg0.Type().HasTrait(traits.ReceiverType) {
return arg0.(traits.Receiver).Receive(fn.function, fn.overload, argVals[1:])
}
return types.NewErr("no such overload: %s", fn.function)
}
// Cost implements the Coster interface method.
func (fn *evalVarArgs) Cost() (min, max int64) {
min, max = sumOfCost(fn.args)
min++ // add cost for function
max++
return
}
// Function implements the InterpretableCall interface method.
func (fn *evalVarArgs) Function() string {
return fn.function
}
// OverloadID implements the InterpretableCall interface method.
func (fn *evalVarArgs) OverloadID() string {
return fn.overload
}
// Args returns the argument to the unary function.
func (fn *evalVarArgs) Args() []Interpretable {
return fn.args
}
type evalList struct {
id int64
elems []Interpretable
adapter ref.TypeAdapter
}
// ID implements the Interpretable interface method.
func (l *evalList) ID() int64 {
return l.id
}
// Eval implements the Interpretable interface method.
func (l *evalList) Eval(ctx Activation) ref.Val {
elemVals := make([]ref.Val, len(l.elems))
// If any argument is unknown or error early terminate.
for i, elem := range l.elems {
elemVal := elem.Eval(ctx)
if types.IsUnknownOrError(elemVal) {
return elemVal
}
elemVals[i] = elemVal
}
return l.adapter.NativeToValue(elemVals)
}
func (l *evalList) InitVals() []Interpretable {
return l.elems
}
func (l *evalList) Type() ref.Type {
return types.ListType
}
// Cost implements the Coster interface method.
func (l *evalList) Cost() (min, max int64) {
return sumOfCost(l.elems)
}
type evalMap struct {
id int64
keys []Interpretable
vals []Interpretable
adapter ref.TypeAdapter
}
// ID implements the Interpretable interface method.
func (m *evalMap) ID() int64 {
return m.id
}
// Eval implements the Interpretable interface method.
func (m *evalMap) Eval(ctx Activation) ref.Val {
entries := make(map[ref.Val]ref.Val)
// If any argument is unknown or error early terminate.
for i, key := range m.keys {
keyVal := key.Eval(ctx)
if types.IsUnknownOrError(keyVal) {
return keyVal
}
valVal := m.vals[i].Eval(ctx)
if types.IsUnknownOrError(valVal) {
return valVal
}
entries[keyVal] = valVal
}
return m.adapter.NativeToValue(entries)
}
func (m *evalMap) InitVals() []Interpretable {
if len(m.keys) != len(m.vals) {
return nil
}
result := make([]Interpretable, len(m.keys)+len(m.vals))
idx := 0
for i, k := range m.keys {
v := m.vals[i]
result[idx] = k
idx++
result[idx] = v
idx++
}
return result
}
func (m *evalMap) Type() ref.Type {
return types.MapType
}
// Cost implements the Coster interface method.
func (m *evalMap) Cost() (min, max int64) {
kMin, kMax := sumOfCost(m.keys)
vMin, vMax := sumOfCost(m.vals)
return kMin + vMin, kMax + vMax
}
type evalObj struct {
id int64
typeName string
fields []string
vals []Interpretable
provider ref.TypeProvider
}
// ID implements the Interpretable interface method.
func (o *evalObj) ID() int64 {
return o.id
}
// Eval implements the Interpretable interface method.
func (o *evalObj) Eval(ctx Activation) ref.Val {
fieldVals := make(map[string]ref.Val)
// If any argument is unknown or error early terminate.
for i, field := range o.fields {
val := o.vals[i].Eval(ctx)
if types.IsUnknownOrError(val) {
return val
}
fieldVals[field] = val
}
return o.provider.NewValue(o.typeName, fieldVals)
}
func (o *evalObj) InitVals() []Interpretable {
return o.vals
}
func (o *evalObj) Type() ref.Type {
return types.NewObjectTypeValue(o.typeName)
}
// Cost implements the Coster interface method.
func (o *evalObj) Cost() (min, max int64) {
return sumOfCost(o.vals)
}
func sumOfCost(interps []Interpretable) (min, max int64) {
min, max = 0, 0
for _, in := range interps {
minT, maxT := estimateCost(in)
min += minT
max += maxT
}
return
}
type evalFold struct {
id int64
accuVar string
iterVar string
iterRange Interpretable
accu Interpretable
cond Interpretable
step Interpretable
result Interpretable
adapter ref.TypeAdapter
exhaustive bool
interruptable bool
}
// ID implements the Interpretable interface method.
func (fold *evalFold) ID() int64 {
return fold.id
}
// Eval implements the Interpretable interface method.
func (fold *evalFold) Eval(ctx Activation) ref.Val {
foldRange := fold.iterRange.Eval(ctx)
if !foldRange.Type().HasTrait(traits.IterableType) {
return types.ValOrErr(foldRange, "got '%T', expected iterable type", foldRange)
}
// Configure the fold activation with the accumulator initial value.
accuCtx := varActivationPool.Get().(*varActivation)
accuCtx.parent = ctx
accuCtx.name = fold.accuVar
accuCtx.val = fold.accu.Eval(ctx)
// If the accumulator starts as an empty list, then the comprehension will build a list
// so create a mutable list to optimize the cost of the inner loop.
l, ok := accuCtx.val.(traits.Lister)
buildingList := false
if !fold.exhaustive && ok && l.Size() == types.IntZero {
buildingList = true
accuCtx.val = types.NewMutableList(fold.adapter)
}
iterCtx := varActivationPool.Get().(*varActivation)
iterCtx.parent = accuCtx
iterCtx.name = fold.iterVar
interrupted := false
it := foldRange.(traits.Iterable).Iterator()
for it.HasNext() == types.True {
// Modify the iter var in the fold activation.
iterCtx.val = it.Next()
// Evaluate the condition, terminate the loop if false.
cond := fold.cond.Eval(iterCtx)
condBool, ok := cond.(types.Bool)
if !fold.exhaustive && ok && condBool != types.True {
break
}
// Evaluate the evaluation step into accu var.
accuCtx.val = fold.step.Eval(iterCtx)
if fold.interruptable {
if stop, found := ctx.ResolveName("#interrupted"); found && stop == true {
interrupted = true
break
}
}
}
varActivationPool.Put(iterCtx)
if interrupted {
varActivationPool.Put(accuCtx)
return types.NewErr("operation interrupted")
}
// Compute the result.
res := fold.result.Eval(accuCtx)
varActivationPool.Put(accuCtx)
// Convert a mutable list to an immutable one, if the comprehension has generated a list as a result.
if !types.IsUnknownOrError(res) && buildingList {
if _, ok := res.(traits.MutableLister); ok {
res = res.(traits.MutableLister).ToImmutableList()
}
}
return res
}
// Cost implements the Coster interface method.
func (fold *evalFold) Cost() (min, max int64) {
// Compute the cost for evaluating iterRange.
iMin, iMax := estimateCost(fold.iterRange)
// Compute the size of iterRange. If the size depends on the input, return the maximum possible
// cost range.
foldRange := fold.iterRange.Eval(EmptyActivation())
if !foldRange.Type().HasTrait(traits.IterableType) {
return 0, math.MaxInt64
}
var rangeCnt int64
it := foldRange.(traits.Iterable).Iterator()
for it.HasNext() == types.True {
it.Next()
rangeCnt++
}
aMin, aMax := estimateCost(fold.accu)
cMin, cMax := estimateCost(fold.cond)
sMin, sMax := estimateCost(fold.step)
rMin, rMax := estimateCost(fold.result)
if fold.exhaustive {
cMin = cMin * rangeCnt
sMin = sMin * rangeCnt
}
// The cond and step costs are multiplied by size(iterRange). The minimum possible cost incurs
// when the evaluation result can be determined by the first iteration.
return iMin + aMin + cMin + sMin + rMin,
iMax + aMax + cMax*rangeCnt + sMax*rangeCnt + rMax
}
// Optional Interpretable implementations that specialize, subsume, or extend the core evaluation
// plan via decorators.
// evalSetMembership is an Interpretable implementation which tests whether an input value
// exists within the set of map keys used to model a set.
type evalSetMembership struct {
inst Interpretable
arg Interpretable
valueSet map[ref.Val]ref.Val
}
// ID implements the Interpretable interface method.
func (e *evalSetMembership) ID() int64 {
return e.inst.ID()
}
// Eval implements the Interpretable interface method.
func (e *evalSetMembership) Eval(ctx Activation) ref.Val {
val := e.arg.Eval(ctx)
if ret, found := e.valueSet[val]; found {
return ret
}
return types.False
}
// Cost implements the Coster interface method.
func (e *evalSetMembership) Cost() (min, max int64) {
return estimateCost(e.arg)
}
// evalWatch is an Interpretable implementation that wraps the execution of a given
// expression so that it may observe the computed value and send it to an observer.
type evalWatch struct {
Interpretable
observer EvalObserver
}
// Eval implements the Interpretable interface method.
func (e *evalWatch) Eval(ctx Activation) ref.Val {
val := e.Interpretable.Eval(ctx)
e.observer(e.ID(), e.Interpretable, val)
return val
}
// Cost implements the Coster interface method.
func (e *evalWatch) Cost() (min, max int64) {
return estimateCost(e.Interpretable)
}
// evalWatchAttr describes a watcher of an instAttr Interpretable.
//
// Since the watcher may be selected against at a later stage in program planning, the watcher
// must implement the instAttr interface by proxy.
type evalWatchAttr struct {
InterpretableAttribute
observer EvalObserver
}
// AddQualifier creates a wrapper over the incoming qualifier which observes the qualification
// result.
func (e *evalWatchAttr) AddQualifier(q Qualifier) (Attribute, error) {
cq, isConst := q.(ConstantQualifier)
if isConst {
q = &evalWatchConstQual{
ConstantQualifier: cq,
observer: e.observer,
adapter: e.InterpretableAttribute.Adapter(),
}
} else {
q = &evalWatchQual{
Qualifier: q,
observer: e.observer,
adapter: e.InterpretableAttribute.Adapter(),
}
}
_, err := e.InterpretableAttribute.AddQualifier(q)
return e, err
}
// Cost implements the Coster interface method.
func (e *evalWatchAttr) Cost() (min, max int64) {
return estimateCost(e.InterpretableAttribute)
}
// Eval implements the Interpretable interface method.
func (e *evalWatchAttr) Eval(vars Activation) ref.Val {
val := e.InterpretableAttribute.Eval(vars)
e.observer(e.ID(), e.InterpretableAttribute, val)
return val
}
// evalWatchConstQual observes the qualification of an object using a constant boolean, int,
// string, or uint.
type evalWatchConstQual struct {
ConstantQualifier
observer EvalObserver
adapter ref.TypeAdapter
}
// Cost implements the Coster interface method.
func (e *evalWatchConstQual) Cost() (min, max int64) {
return estimateCost(e.ConstantQualifier)
}
// Qualify observes the qualification of a object via a constant boolean, int, string, or uint.
func (e *evalWatchConstQual) Qualify(vars Activation, obj interface{}) (interface{}, error) {
out, err := e.ConstantQualifier.Qualify(vars, obj)
var val ref.Val
if err != nil {
val = types.NewErr(err.Error())
} else {
val = e.adapter.NativeToValue(out)
}
e.observer(e.ID(), e.ConstantQualifier, val)
return out, err
}
// QualifierValueEquals tests whether the incoming value is equal to the qualifying constant.
func (e *evalWatchConstQual) QualifierValueEquals(value interface{}) bool {
qve, ok := e.ConstantQualifier.(qualifierValueEquator)
return ok && qve.QualifierValueEquals(value)
}
// evalWatchQual observes the qualification of an object by a value computed at runtime.
type evalWatchQual struct {
Qualifier
observer EvalObserver
adapter ref.TypeAdapter
}
// Cost implements the Coster interface method.
func (e *evalWatchQual) Cost() (min, max int64) {
return estimateCost(e.Qualifier)
}
// Qualify observes the qualification of a object via a value computed at runtime.
func (e *evalWatchQual) Qualify(vars Activation, obj interface{}) (interface{}, error) {
out, err := e.Qualifier.Qualify(vars, obj)
var val ref.Val
if err != nil {
val = types.NewErr(err.Error())
} else {
val = e.adapter.NativeToValue(out)
}
e.observer(e.ID(), e.Qualifier, val)
return out, err
}
// evalWatchConst describes a watcher of an instConst Interpretable.
type evalWatchConst struct {
InterpretableConst
observer EvalObserver
}
// Eval implements the Interpretable interface method.
func (e *evalWatchConst) Eval(vars Activation) ref.Val {
val := e.Value()
e.observer(e.ID(), e.InterpretableConst, val)
return val
}
// Cost implements the Coster interface method.
func (e *evalWatchConst) Cost() (min, max int64) {
return estimateCost(e.InterpretableConst)
}
// evalExhaustiveOr is just like evalOr, but does not short-circuit argument evaluation.
type evalExhaustiveOr struct {
id int64
lhs Interpretable
rhs Interpretable
}
// ID implements the Interpretable interface method.
func (or *evalExhaustiveOr) ID() int64 {
return or.id
}
// Eval implements the Interpretable interface method.
func (or *evalExhaustiveOr) Eval(ctx Activation) ref.Val {
lVal := or.lhs.Eval(ctx)
rVal := or.rhs.Eval(ctx)
lBool, lok := lVal.(types.Bool)
if lok && lBool == types.True {
return types.True
}
rBool, rok := rVal.(types.Bool)
if rok && rBool == types.True {
return types.True
}
if lok && rok {
return types.False
}
if types.IsUnknown(lVal) {
return lVal
}
if types.IsUnknown(rVal) {
return rVal
}
// TODO: Combine the errors into a set in the future.
// If the left-hand side is non-boolean return it as the error.
if types.IsError(lVal) {
return lVal
}
return types.ValOrErr(rVal, "no such overload")
}
// Cost implements the Coster interface method.
func (or *evalExhaustiveOr) Cost() (min, max int64) {
return calExhaustiveBinaryOpsCost(or.lhs, or.rhs)
}
// evalExhaustiveAnd is just like evalAnd, but does not short-circuit argument evaluation.
type evalExhaustiveAnd struct {
id int64
lhs Interpretable
rhs Interpretable
}
// ID implements the Interpretable interface method.
func (and *evalExhaustiveAnd) ID() int64 {
return and.id
}
// Eval implements the Interpretable interface method.
func (and *evalExhaustiveAnd) Eval(ctx Activation) ref.Val {
lVal := and.lhs.Eval(ctx)
rVal := and.rhs.Eval(ctx)
lBool, lok := lVal.(types.Bool)
if lok && lBool == types.False {
return types.False
}
rBool, rok := rVal.(types.Bool)
if rok && rBool == types.False {
return types.False
}
if lok && rok {
return types.True
}
if types.IsUnknown(lVal) {
return lVal
}
if types.IsUnknown(rVal) {
return rVal
}
// TODO: Combine the errors into a set in the future.
// If the left-hand side is non-boolean return it as the error.
if types.IsError(lVal) {
return lVal
}
return types.ValOrErr(rVal, "no such overload")
}
// Cost implements the Coster interface method.
func (and *evalExhaustiveAnd) Cost() (min, max int64) {
return calExhaustiveBinaryOpsCost(and.lhs, and.rhs)
}
func calExhaustiveBinaryOpsCost(lhs, rhs Interpretable) (min, max int64) {
lMin, lMax := estimateCost(lhs)
rMin, rMax := estimateCost(rhs)
return lMin + rMin + 1, lMax + rMax + 1
}
// evalExhaustiveConditional is like evalConditional, but does not short-circuit argument
// evaluation.
type evalExhaustiveConditional struct {
id int64
adapter ref.TypeAdapter
attr *conditionalAttribute
}
// ID implements the Interpretable interface method.
func (cond *evalExhaustiveConditional) ID() int64 {
return cond.id
}
// Eval implements the Interpretable interface method.
func (cond *evalExhaustiveConditional) Eval(ctx Activation) ref.Val {
cVal := cond.attr.expr.Eval(ctx)
tVal, err := cond.attr.truthy.Resolve(ctx)
if err != nil {
return types.NewErr(err.Error())
}
fVal, err := cond.attr.falsy.Resolve(ctx)
if err != nil {
return types.NewErr(err.Error())
}
cBool, ok := cVal.(types.Bool)
if !ok {
return types.ValOrErr(cVal, "no such overload")
}
if cBool {
return cond.adapter.NativeToValue(tVal)
}
return cond.adapter.NativeToValue(fVal)
}
// Cost implements the Coster interface method.
func (cond *evalExhaustiveConditional) Cost() (min, max int64) {
return cond.attr.Cost()
}
// evalAttr evaluates an Attribute value.
type evalAttr struct {
adapter ref.TypeAdapter
attr Attribute
}
// ID of the attribute instruction.
func (a *evalAttr) ID() int64 {
return a.attr.ID()
}
// AddQualifier implements the instAttr interface method.
func (a *evalAttr) AddQualifier(qual Qualifier) (Attribute, error) {
attr, err := a.attr.AddQualifier(qual)
a.attr = attr
return attr, err
}
// Attr implements the instAttr interface method.
func (a *evalAttr) Attr() Attribute {
return a.attr
}
// Adapter implements the instAttr interface method.
func (a *evalAttr) Adapter() ref.TypeAdapter {
return a.adapter
}
// Cost implements the Coster interface method.
func (a *evalAttr) Cost() (min, max int64) {
return estimateCost(a.attr)
}
// Eval implements the Interpretable interface method.
func (a *evalAttr) Eval(ctx Activation) ref.Val {
v, err := a.attr.Resolve(ctx)
if err != nil {
return types.NewErr(err.Error())
}
return a.adapter.NativeToValue(v)
}
// Qualify proxies to the Attribute's Qualify method.
func (a *evalAttr) Qualify(ctx Activation, obj interface{}) (interface{}, error) {
return a.attr.Qualify(ctx, obj)
}
// Resolve proxies to the Attribute's Resolve method.
func (a *evalAttr) Resolve(ctx Activation) (interface{}, error) {
return a.attr.Resolve(ctx)
}