summaryrefslogtreecommitdiff
path: root/vendor/github.com/hashicorp/hil/eval.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/hil/eval.go')
-rw-r--r--vendor/github.com/hashicorp/hil/eval.go472
1 files changed, 472 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/hil/eval.go b/vendor/github.com/hashicorp/hil/eval.go
new file mode 100644
index 00000000..27820769
--- /dev/null
+++ b/vendor/github.com/hashicorp/hil/eval.go
@@ -0,0 +1,472 @@
+package hil
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "sync"
+
+ "github.com/hashicorp/hil/ast"
+)
+
+// EvalConfig is the configuration for evaluating.
+type EvalConfig struct {
+ // GlobalScope is the global scope of execution for evaluation.
+ GlobalScope *ast.BasicScope
+
+ // SemanticChecks is a list of additional semantic checks that will be run
+ // on the tree prior to evaluating it. The type checker, identifier checker,
+ // etc. will be run before these automatically.
+ SemanticChecks []SemanticChecker
+}
+
+// SemanticChecker is the type that must be implemented to do a
+// semantic check on an AST tree. This will be called with the root node.
+type SemanticChecker func(ast.Node) error
+
+// EvaluationResult is a struct returned from the hil.Eval function,
+// representing the result of an interpolation. Results are returned in their
+// "natural" Go structure rather than in terms of the HIL AST. For the types
+// currently implemented, this means that the Value field can be interpreted as
+// the following Go types:
+// TypeInvalid: undefined
+// TypeString: string
+// TypeList: []interface{}
+// TypeMap: map[string]interface{}
+// TypBool: bool
+type EvaluationResult struct {
+ Type EvalType
+ Value interface{}
+}
+
+// InvalidResult is a structure representing the result of a HIL interpolation
+// which has invalid syntax, missing variables, or some other type of error.
+// The error is described out of band in the accompanying error return value.
+var InvalidResult = EvaluationResult{Type: TypeInvalid, Value: nil}
+
+// errExitUnknown is an internal error that when returned means the result
+// is an unknown value. We use this for early exit.
+var errExitUnknown = errors.New("unknown value")
+
+func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) {
+ output, outputType, err := internalEval(root, config)
+ if err != nil {
+ return InvalidResult, err
+ }
+
+ // If the result contains any nested unknowns then the result as a whole
+ // is unknown, so that callers only have to deal with "entirely known"
+ // or "entirely unknown" as outcomes.
+ if ast.IsUnknown(ast.Variable{Type: outputType, Value: output}) {
+ outputType = ast.TypeUnknown
+ output = UnknownValue
+ }
+
+ switch outputType {
+ case ast.TypeList:
+ val, err := VariableToInterface(ast.Variable{
+ Type: ast.TypeList,
+ Value: output,
+ })
+ return EvaluationResult{
+ Type: TypeList,
+ Value: val,
+ }, err
+ case ast.TypeMap:
+ val, err := VariableToInterface(ast.Variable{
+ Type: ast.TypeMap,
+ Value: output,
+ })
+ return EvaluationResult{
+ Type: TypeMap,
+ Value: val,
+ }, err
+ case ast.TypeString:
+ return EvaluationResult{
+ Type: TypeString,
+ Value: output,
+ }, nil
+ case ast.TypeBool:
+ return EvaluationResult{
+ Type: TypeBool,
+ Value: output,
+ }, nil
+ case ast.TypeUnknown:
+ return EvaluationResult{
+ Type: TypeUnknown,
+ Value: UnknownValue,
+ }, nil
+ default:
+ return InvalidResult, fmt.Errorf("unknown type %s as interpolation output", outputType)
+ }
+}
+
+// Eval evaluates the given AST tree and returns its output value, the type
+// of the output, and any error that occurred.
+func internalEval(root ast.Node, config *EvalConfig) (interface{}, ast.Type, error) {
+ // Copy the scope so we can add our builtins
+ if config == nil {
+ config = new(EvalConfig)
+ }
+ scope := registerBuiltins(config.GlobalScope)
+ implicitMap := map[ast.Type]map[ast.Type]string{
+ ast.TypeFloat: {
+ ast.TypeInt: "__builtin_FloatToInt",
+ ast.TypeString: "__builtin_FloatToString",
+ },
+ ast.TypeInt: {
+ ast.TypeFloat: "__builtin_IntToFloat",
+ ast.TypeString: "__builtin_IntToString",
+ },
+ ast.TypeString: {
+ ast.TypeInt: "__builtin_StringToInt",
+ ast.TypeFloat: "__builtin_StringToFloat",
+ ast.TypeBool: "__builtin_StringToBool",
+ },
+ ast.TypeBool: {
+ ast.TypeString: "__builtin_BoolToString",
+ },
+ }
+
+ // Build our own semantic checks that we always run
+ tv := &TypeCheck{Scope: scope, Implicit: implicitMap}
+ ic := &IdentifierCheck{Scope: scope}
+
+ // Build up the semantic checks for execution
+ checks := make(
+ []SemanticChecker,
+ len(config.SemanticChecks),
+ len(config.SemanticChecks)+2)
+ copy(checks, config.SemanticChecks)
+ checks = append(checks, ic.Visit)
+ checks = append(checks, tv.Visit)
+
+ // Run the semantic checks
+ for _, check := range checks {
+ if err := check(root); err != nil {
+ return nil, ast.TypeInvalid, err
+ }
+ }
+
+ // Execute
+ v := &evalVisitor{Scope: scope}
+ return v.Visit(root)
+}
+
+// EvalNode is the interface that must be implemented by any ast.Node
+// to support evaluation. This will be called in visitor pattern order.
+// The result of each call to Eval is automatically pushed onto the
+// stack as a LiteralNode. Pop elements off the stack to get child
+// values.
+type EvalNode interface {
+ Eval(ast.Scope, *ast.Stack) (interface{}, ast.Type, error)
+}
+
+type evalVisitor struct {
+ Scope ast.Scope
+ Stack ast.Stack
+
+ err error
+ lock sync.Mutex
+}
+
+func (v *evalVisitor) Visit(root ast.Node) (interface{}, ast.Type, error) {
+ // Run the actual visitor pattern
+ root.Accept(v.visit)
+
+ // Get our result and clear out everything else
+ var result *ast.LiteralNode
+ if v.Stack.Len() > 0 {
+ result = v.Stack.Pop().(*ast.LiteralNode)
+ } else {
+ result = new(ast.LiteralNode)
+ }
+ resultErr := v.err
+ if resultErr == errExitUnknown {
+ // This means the return value is unknown and we used the error
+ // as an early exit mechanism. Reset since the value on the stack
+ // should be the unknown value.
+ resultErr = nil
+ }
+
+ // Clear everything else so we aren't just dangling
+ v.Stack.Reset()
+ v.err = nil
+
+ t, err := result.Type(v.Scope)
+ if err != nil {
+ return nil, ast.TypeInvalid, err
+ }
+
+ return result.Value, t, resultErr
+}
+
+func (v *evalVisitor) visit(raw ast.Node) ast.Node {
+ if v.err != nil {
+ return raw
+ }
+
+ en, err := evalNode(raw)
+ if err != nil {
+ v.err = err
+ return raw
+ }
+
+ out, outType, err := en.Eval(v.Scope, &v.Stack)
+ if err != nil {
+ v.err = err
+ return raw
+ }
+
+ v.Stack.Push(&ast.LiteralNode{
+ Value: out,
+ Typex: outType,
+ })
+
+ if outType == ast.TypeUnknown {
+ // Halt immediately
+ v.err = errExitUnknown
+ return raw
+ }
+
+ return raw
+}
+
+// evalNode is a private function that returns an EvalNode for built-in
+// types as well as any other EvalNode implementations.
+func evalNode(raw ast.Node) (EvalNode, error) {
+ switch n := raw.(type) {
+ case *ast.Index:
+ return &evalIndex{n}, nil
+ case *ast.Call:
+ return &evalCall{n}, nil
+ case *ast.Conditional:
+ return &evalConditional{n}, nil
+ case *ast.Output:
+ return &evalOutput{n}, nil
+ case *ast.LiteralNode:
+ return &evalLiteralNode{n}, nil
+ case *ast.VariableAccess:
+ return &evalVariableAccess{n}, nil
+ default:
+ en, ok := n.(EvalNode)
+ if !ok {
+ return nil, fmt.Errorf("node doesn't support evaluation: %#v", raw)
+ }
+
+ return en, nil
+ }
+}
+
+type evalCall struct{ *ast.Call }
+
+func (v *evalCall) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) {
+ // Look up the function in the map
+ function, ok := s.LookupFunc(v.Func)
+ if !ok {
+ return nil, ast.TypeInvalid, fmt.Errorf(
+ "unknown function called: %s", v.Func)
+ }
+
+ // The arguments are on the stack in reverse order, so pop them off.
+ args := make([]interface{}, len(v.Args))
+ for i, _ := range v.Args {
+ node := stack.Pop().(*ast.LiteralNode)
+ if node.IsUnknown() {
+ // If any arguments are unknown then the result is automatically unknown
+ return UnknownValue, ast.TypeUnknown, nil
+ }
+ args[len(v.Args)-1-i] = node.Value
+ }
+
+ // Call the function
+ result, err := function.Callback(args)
+ if err != nil {
+ return nil, ast.TypeInvalid, fmt.Errorf("%s: %s", v.Func, err)
+ }
+
+ return result, function.ReturnType, nil
+}
+
+type evalConditional struct{ *ast.Conditional }
+
+func (v *evalConditional) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) {
+ // On the stack we have literal nodes representing the resulting values
+ // of the condition, true and false expressions, but they are in reverse
+ // order.
+ falseLit := stack.Pop().(*ast.LiteralNode)
+ trueLit := stack.Pop().(*ast.LiteralNode)
+ condLit := stack.Pop().(*ast.LiteralNode)
+
+ if condLit.IsUnknown() {
+ // If our conditional is unknown then our result is also unknown
+ return UnknownValue, ast.TypeUnknown, nil
+ }
+
+ if condLit.Value.(bool) {
+ return trueLit.Value, trueLit.Typex, nil
+ } else {
+ return falseLit.Value, trueLit.Typex, nil
+ }
+}
+
+type evalIndex struct{ *ast.Index }
+
+func (v *evalIndex) Eval(scope ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) {
+ key := stack.Pop().(*ast.LiteralNode)
+ target := stack.Pop().(*ast.LiteralNode)
+
+ variableName := v.Index.Target.(*ast.VariableAccess).Name
+
+ if key.IsUnknown() {
+ // If our key is unknown then our result is also unknown
+ return UnknownValue, ast.TypeUnknown, nil
+ }
+
+ // For target, we'll accept collections containing unknown values but
+ // we still need to catch when the collection itself is unknown, shallowly.
+ if target.Typex == ast.TypeUnknown {
+ return UnknownValue, ast.TypeUnknown, nil
+ }
+
+ switch target.Typex {
+ case ast.TypeList:
+ return v.evalListIndex(variableName, target.Value, key.Value)
+ case ast.TypeMap:
+ return v.evalMapIndex(variableName, target.Value, key.Value)
+ default:
+ return nil, ast.TypeInvalid, fmt.Errorf(
+ "target %q for indexing must be ast.TypeList or ast.TypeMap, is %s",
+ variableName, target.Typex)
+ }
+}
+
+func (v *evalIndex) evalListIndex(variableName string, target interface{}, key interface{}) (interface{}, ast.Type, error) {
+ // We assume type checking was already done and we can assume that target
+ // is a list and key is an int
+ list, ok := target.([]ast.Variable)
+ if !ok {
+ return nil, ast.TypeInvalid, fmt.Errorf(
+ "cannot cast target to []Variable, is: %T", target)
+ }
+
+ keyInt, ok := key.(int)
+ if !ok {
+ return nil, ast.TypeInvalid, fmt.Errorf(
+ "cannot cast key to int, is: %T", key)
+ }
+
+ if len(list) == 0 {
+ return nil, ast.TypeInvalid, fmt.Errorf("list is empty")
+ }
+
+ if keyInt < 0 || len(list) < keyInt+1 {
+ return nil, ast.TypeInvalid, fmt.Errorf(
+ "index %d out of range for list %s (max %d)",
+ keyInt, variableName, len(list))
+ }
+
+ returnVal := list[keyInt].Value
+ returnType := list[keyInt].Type
+ return returnVal, returnType, nil
+}
+
+func (v *evalIndex) evalMapIndex(variableName string, target interface{}, key interface{}) (interface{}, ast.Type, error) {
+ // We assume type checking was already done and we can assume that target
+ // is a map and key is a string
+ vmap, ok := target.(map[string]ast.Variable)
+ if !ok {
+ return nil, ast.TypeInvalid, fmt.Errorf(
+ "cannot cast target to map[string]Variable, is: %T", target)
+ }
+
+ keyString, ok := key.(string)
+ if !ok {
+ return nil, ast.TypeInvalid, fmt.Errorf(
+ "cannot cast key to string, is: %T", key)
+ }
+
+ if len(vmap) == 0 {
+ return nil, ast.TypeInvalid, fmt.Errorf("map is empty")
+ }
+
+ value, ok := vmap[keyString]
+ if !ok {
+ return nil, ast.TypeInvalid, fmt.Errorf(
+ "key %q does not exist in map %s", keyString, variableName)
+ }
+
+ return value.Value, value.Type, nil
+}
+
+type evalOutput struct{ *ast.Output }
+
+func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) {
+ // The expressions should all be on the stack in reverse
+ // order. So pop them off, reverse their order, and concatenate.
+ nodes := make([]*ast.LiteralNode, 0, len(v.Exprs))
+ haveUnknown := false
+ for range v.Exprs {
+ n := stack.Pop().(*ast.LiteralNode)
+ nodes = append(nodes, n)
+
+ // If we have any unknowns then the whole result is unknown
+ // (we must deal with this first, because the type checker can
+ // skip type conversions in the presence of unknowns, and thus
+ // any of our other nodes may be incorrectly typed.)
+ if n.IsUnknown() {
+ haveUnknown = true
+ }
+ }
+
+ if haveUnknown {
+ return UnknownValue, ast.TypeUnknown, nil
+ }
+
+ // Special case the single list and map
+ if len(nodes) == 1 {
+ switch t := nodes[0].Typex; t {
+ case ast.TypeList:
+ fallthrough
+ case ast.TypeMap:
+ fallthrough
+ case ast.TypeUnknown:
+ return nodes[0].Value, t, nil
+ }
+ }
+
+ // Otherwise concatenate the strings
+ var buf bytes.Buffer
+ for i := len(nodes) - 1; i >= 0; i-- {
+ if nodes[i].Typex != ast.TypeString {
+ return nil, ast.TypeInvalid, fmt.Errorf(
+ "invalid output with %s value at index %d: %#v",
+ nodes[i].Typex,
+ i,
+ nodes[i].Value,
+ )
+ }
+ buf.WriteString(nodes[i].Value.(string))
+ }
+
+ return buf.String(), ast.TypeString, nil
+}
+
+type evalLiteralNode struct{ *ast.LiteralNode }
+
+func (v *evalLiteralNode) Eval(ast.Scope, *ast.Stack) (interface{}, ast.Type, error) {
+ return v.Value, v.Typex, nil
+}
+
+type evalVariableAccess struct{ *ast.VariableAccess }
+
+func (v *evalVariableAccess) Eval(scope ast.Scope, _ *ast.Stack) (interface{}, ast.Type, error) {
+ // Look up the variable in the map
+ variable, ok := scope.LookupVar(v.Name)
+ if !ok {
+ return nil, ast.TypeInvalid, fmt.Errorf(
+ "unknown variable accessed: %s", v.Name)
+ }
+
+ return variable.Value, variable.Type, nil
+}