diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hil/eval.go')
-rw-r--r-- | vendor/github.com/hashicorp/hil/eval.go | 472 |
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 0000000..2782076 --- /dev/null +++ b/vendor/github.com/hashicorp/hil/eval.go | |||
@@ -0,0 +1,472 @@ | |||
1 | package hil | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "errors" | ||
6 | "fmt" | ||
7 | "sync" | ||
8 | |||
9 | "github.com/hashicorp/hil/ast" | ||
10 | ) | ||
11 | |||
12 | // EvalConfig is the configuration for evaluating. | ||
13 | type EvalConfig struct { | ||
14 | // GlobalScope is the global scope of execution for evaluation. | ||
15 | GlobalScope *ast.BasicScope | ||
16 | |||
17 | // SemanticChecks is a list of additional semantic checks that will be run | ||
18 | // on the tree prior to evaluating it. The type checker, identifier checker, | ||
19 | // etc. will be run before these automatically. | ||
20 | SemanticChecks []SemanticChecker | ||
21 | } | ||
22 | |||
23 | // SemanticChecker is the type that must be implemented to do a | ||
24 | // semantic check on an AST tree. This will be called with the root node. | ||
25 | type SemanticChecker func(ast.Node) error | ||
26 | |||
27 | // EvaluationResult is a struct returned from the hil.Eval function, | ||
28 | // representing the result of an interpolation. Results are returned in their | ||
29 | // "natural" Go structure rather than in terms of the HIL AST. For the types | ||
30 | // currently implemented, this means that the Value field can be interpreted as | ||
31 | // the following Go types: | ||
32 | // TypeInvalid: undefined | ||
33 | // TypeString: string | ||
34 | // TypeList: []interface{} | ||
35 | // TypeMap: map[string]interface{} | ||
36 | // TypBool: bool | ||
37 | type EvaluationResult struct { | ||
38 | Type EvalType | ||
39 | Value interface{} | ||
40 | } | ||
41 | |||
42 | // InvalidResult is a structure representing the result of a HIL interpolation | ||
43 | // which has invalid syntax, missing variables, or some other type of error. | ||
44 | // The error is described out of band in the accompanying error return value. | ||
45 | var InvalidResult = EvaluationResult{Type: TypeInvalid, Value: nil} | ||
46 | |||
47 | // errExitUnknown is an internal error that when returned means the result | ||
48 | // is an unknown value. We use this for early exit. | ||
49 | var errExitUnknown = errors.New("unknown value") | ||
50 | |||
51 | func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) { | ||
52 | output, outputType, err := internalEval(root, config) | ||
53 | if err != nil { | ||
54 | return InvalidResult, err | ||
55 | } | ||
56 | |||
57 | // If the result contains any nested unknowns then the result as a whole | ||
58 | // is unknown, so that callers only have to deal with "entirely known" | ||
59 | // or "entirely unknown" as outcomes. | ||
60 | if ast.IsUnknown(ast.Variable{Type: outputType, Value: output}) { | ||
61 | outputType = ast.TypeUnknown | ||
62 | output = UnknownValue | ||
63 | } | ||
64 | |||
65 | switch outputType { | ||
66 | case ast.TypeList: | ||
67 | val, err := VariableToInterface(ast.Variable{ | ||
68 | Type: ast.TypeList, | ||
69 | Value: output, | ||
70 | }) | ||
71 | return EvaluationResult{ | ||
72 | Type: TypeList, | ||
73 | Value: val, | ||
74 | }, err | ||
75 | case ast.TypeMap: | ||
76 | val, err := VariableToInterface(ast.Variable{ | ||
77 | Type: ast.TypeMap, | ||
78 | Value: output, | ||
79 | }) | ||
80 | return EvaluationResult{ | ||
81 | Type: TypeMap, | ||
82 | Value: val, | ||
83 | }, err | ||
84 | case ast.TypeString: | ||
85 | return EvaluationResult{ | ||
86 | Type: TypeString, | ||
87 | Value: output, | ||
88 | }, nil | ||
89 | case ast.TypeBool: | ||
90 | return EvaluationResult{ | ||
91 | Type: TypeBool, | ||
92 | Value: output, | ||
93 | }, nil | ||
94 | case ast.TypeUnknown: | ||
95 | return EvaluationResult{ | ||
96 | Type: TypeUnknown, | ||
97 | Value: UnknownValue, | ||
98 | }, nil | ||
99 | default: | ||
100 | return InvalidResult, fmt.Errorf("unknown type %s as interpolation output", outputType) | ||
101 | } | ||
102 | } | ||
103 | |||
104 | // Eval evaluates the given AST tree and returns its output value, the type | ||
105 | // of the output, and any error that occurred. | ||
106 | func internalEval(root ast.Node, config *EvalConfig) (interface{}, ast.Type, error) { | ||
107 | // Copy the scope so we can add our builtins | ||
108 | if config == nil { | ||
109 | config = new(EvalConfig) | ||
110 | } | ||
111 | scope := registerBuiltins(config.GlobalScope) | ||
112 | implicitMap := map[ast.Type]map[ast.Type]string{ | ||
113 | ast.TypeFloat: { | ||
114 | ast.TypeInt: "__builtin_FloatToInt", | ||
115 | ast.TypeString: "__builtin_FloatToString", | ||
116 | }, | ||
117 | ast.TypeInt: { | ||
118 | ast.TypeFloat: "__builtin_IntToFloat", | ||
119 | ast.TypeString: "__builtin_IntToString", | ||
120 | }, | ||
121 | ast.TypeString: { | ||
122 | ast.TypeInt: "__builtin_StringToInt", | ||
123 | ast.TypeFloat: "__builtin_StringToFloat", | ||
124 | ast.TypeBool: "__builtin_StringToBool", | ||
125 | }, | ||
126 | ast.TypeBool: { | ||
127 | ast.TypeString: "__builtin_BoolToString", | ||
128 | }, | ||
129 | } | ||
130 | |||
131 | // Build our own semantic checks that we always run | ||
132 | tv := &TypeCheck{Scope: scope, Implicit: implicitMap} | ||
133 | ic := &IdentifierCheck{Scope: scope} | ||
134 | |||
135 | // Build up the semantic checks for execution | ||
136 | checks := make( | ||
137 | []SemanticChecker, | ||
138 | len(config.SemanticChecks), | ||
139 | len(config.SemanticChecks)+2) | ||
140 | copy(checks, config.SemanticChecks) | ||
141 | checks = append(checks, ic.Visit) | ||
142 | checks = append(checks, tv.Visit) | ||
143 | |||
144 | // Run the semantic checks | ||
145 | for _, check := range checks { | ||
146 | if err := check(root); err != nil { | ||
147 | return nil, ast.TypeInvalid, err | ||
148 | } | ||
149 | } | ||
150 | |||
151 | // Execute | ||
152 | v := &evalVisitor{Scope: scope} | ||
153 | return v.Visit(root) | ||
154 | } | ||
155 | |||
156 | // EvalNode is the interface that must be implemented by any ast.Node | ||
157 | // to support evaluation. This will be called in visitor pattern order. | ||
158 | // The result of each call to Eval is automatically pushed onto the | ||
159 | // stack as a LiteralNode. Pop elements off the stack to get child | ||
160 | // values. | ||
161 | type EvalNode interface { | ||
162 | Eval(ast.Scope, *ast.Stack) (interface{}, ast.Type, error) | ||
163 | } | ||
164 | |||
165 | type evalVisitor struct { | ||
166 | Scope ast.Scope | ||
167 | Stack ast.Stack | ||
168 | |||
169 | err error | ||
170 | lock sync.Mutex | ||
171 | } | ||
172 | |||
173 | func (v *evalVisitor) Visit(root ast.Node) (interface{}, ast.Type, error) { | ||
174 | // Run the actual visitor pattern | ||
175 | root.Accept(v.visit) | ||
176 | |||
177 | // Get our result and clear out everything else | ||
178 | var result *ast.LiteralNode | ||
179 | if v.Stack.Len() > 0 { | ||
180 | result = v.Stack.Pop().(*ast.LiteralNode) | ||
181 | } else { | ||
182 | result = new(ast.LiteralNode) | ||
183 | } | ||
184 | resultErr := v.err | ||
185 | if resultErr == errExitUnknown { | ||
186 | // This means the return value is unknown and we used the error | ||
187 | // as an early exit mechanism. Reset since the value on the stack | ||
188 | // should be the unknown value. | ||
189 | resultErr = nil | ||
190 | } | ||
191 | |||
192 | // Clear everything else so we aren't just dangling | ||
193 | v.Stack.Reset() | ||
194 | v.err = nil | ||
195 | |||
196 | t, err := result.Type(v.Scope) | ||
197 | if err != nil { | ||
198 | return nil, ast.TypeInvalid, err | ||
199 | } | ||
200 | |||
201 | return result.Value, t, resultErr | ||
202 | } | ||
203 | |||
204 | func (v *evalVisitor) visit(raw ast.Node) ast.Node { | ||
205 | if v.err != nil { | ||
206 | return raw | ||
207 | } | ||
208 | |||
209 | en, err := evalNode(raw) | ||
210 | if err != nil { | ||
211 | v.err = err | ||
212 | return raw | ||
213 | } | ||
214 | |||
215 | out, outType, err := en.Eval(v.Scope, &v.Stack) | ||
216 | if err != nil { | ||
217 | v.err = err | ||
218 | return raw | ||
219 | } | ||
220 | |||
221 | v.Stack.Push(&ast.LiteralNode{ | ||
222 | Value: out, | ||
223 | Typex: outType, | ||
224 | }) | ||
225 | |||
226 | if outType == ast.TypeUnknown { | ||
227 | // Halt immediately | ||
228 | v.err = errExitUnknown | ||
229 | return raw | ||
230 | } | ||
231 | |||
232 | return raw | ||
233 | } | ||
234 | |||
235 | // evalNode is a private function that returns an EvalNode for built-in | ||
236 | // types as well as any other EvalNode implementations. | ||
237 | func evalNode(raw ast.Node) (EvalNode, error) { | ||
238 | switch n := raw.(type) { | ||
239 | case *ast.Index: | ||
240 | return &evalIndex{n}, nil | ||
241 | case *ast.Call: | ||
242 | return &evalCall{n}, nil | ||
243 | case *ast.Conditional: | ||
244 | return &evalConditional{n}, nil | ||
245 | case *ast.Output: | ||
246 | return &evalOutput{n}, nil | ||
247 | case *ast.LiteralNode: | ||
248 | return &evalLiteralNode{n}, nil | ||
249 | case *ast.VariableAccess: | ||
250 | return &evalVariableAccess{n}, nil | ||
251 | default: | ||
252 | en, ok := n.(EvalNode) | ||
253 | if !ok { | ||
254 | return nil, fmt.Errorf("node doesn't support evaluation: %#v", raw) | ||
255 | } | ||
256 | |||
257 | return en, nil | ||
258 | } | ||
259 | } | ||
260 | |||
261 | type evalCall struct{ *ast.Call } | ||
262 | |||
263 | func (v *evalCall) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { | ||
264 | // Look up the function in the map | ||
265 | function, ok := s.LookupFunc(v.Func) | ||
266 | if !ok { | ||
267 | return nil, ast.TypeInvalid, fmt.Errorf( | ||
268 | "unknown function called: %s", v.Func) | ||
269 | } | ||
270 | |||
271 | // The arguments are on the stack in reverse order, so pop them off. | ||
272 | args := make([]interface{}, len(v.Args)) | ||
273 | for i, _ := range v.Args { | ||
274 | node := stack.Pop().(*ast.LiteralNode) | ||
275 | if node.IsUnknown() { | ||
276 | // If any arguments are unknown then the result is automatically unknown | ||
277 | return UnknownValue, ast.TypeUnknown, nil | ||
278 | } | ||
279 | args[len(v.Args)-1-i] = node.Value | ||
280 | } | ||
281 | |||
282 | // Call the function | ||
283 | result, err := function.Callback(args) | ||
284 | if err != nil { | ||
285 | return nil, ast.TypeInvalid, fmt.Errorf("%s: %s", v.Func, err) | ||
286 | } | ||
287 | |||
288 | return result, function.ReturnType, nil | ||
289 | } | ||
290 | |||
291 | type evalConditional struct{ *ast.Conditional } | ||
292 | |||
293 | func (v *evalConditional) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { | ||
294 | // On the stack we have literal nodes representing the resulting values | ||
295 | // of the condition, true and false expressions, but they are in reverse | ||
296 | // order. | ||
297 | falseLit := stack.Pop().(*ast.LiteralNode) | ||
298 | trueLit := stack.Pop().(*ast.LiteralNode) | ||
299 | condLit := stack.Pop().(*ast.LiteralNode) | ||
300 | |||
301 | if condLit.IsUnknown() { | ||
302 | // If our conditional is unknown then our result is also unknown | ||
303 | return UnknownValue, ast.TypeUnknown, nil | ||
304 | } | ||
305 | |||
306 | if condLit.Value.(bool) { | ||
307 | return trueLit.Value, trueLit.Typex, nil | ||
308 | } else { | ||
309 | return falseLit.Value, trueLit.Typex, nil | ||
310 | } | ||
311 | } | ||
312 | |||
313 | type evalIndex struct{ *ast.Index } | ||
314 | |||
315 | func (v *evalIndex) Eval(scope ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { | ||
316 | key := stack.Pop().(*ast.LiteralNode) | ||
317 | target := stack.Pop().(*ast.LiteralNode) | ||
318 | |||
319 | variableName := v.Index.Target.(*ast.VariableAccess).Name | ||
320 | |||
321 | if key.IsUnknown() { | ||
322 | // If our key is unknown then our result is also unknown | ||
323 | return UnknownValue, ast.TypeUnknown, nil | ||
324 | } | ||
325 | |||
326 | // For target, we'll accept collections containing unknown values but | ||
327 | // we still need to catch when the collection itself is unknown, shallowly. | ||
328 | if target.Typex == ast.TypeUnknown { | ||
329 | return UnknownValue, ast.TypeUnknown, nil | ||
330 | } | ||
331 | |||
332 | switch target.Typex { | ||
333 | case ast.TypeList: | ||
334 | return v.evalListIndex(variableName, target.Value, key.Value) | ||
335 | case ast.TypeMap: | ||
336 | return v.evalMapIndex(variableName, target.Value, key.Value) | ||
337 | default: | ||
338 | return nil, ast.TypeInvalid, fmt.Errorf( | ||
339 | "target %q for indexing must be ast.TypeList or ast.TypeMap, is %s", | ||
340 | variableName, target.Typex) | ||
341 | } | ||
342 | } | ||
343 | |||
344 | func (v *evalIndex) evalListIndex(variableName string, target interface{}, key interface{}) (interface{}, ast.Type, error) { | ||
345 | // We assume type checking was already done and we can assume that target | ||
346 | // is a list and key is an int | ||
347 | list, ok := target.([]ast.Variable) | ||
348 | if !ok { | ||
349 | return nil, ast.TypeInvalid, fmt.Errorf( | ||
350 | "cannot cast target to []Variable, is: %T", target) | ||
351 | } | ||
352 | |||
353 | keyInt, ok := key.(int) | ||
354 | if !ok { | ||
355 | return nil, ast.TypeInvalid, fmt.Errorf( | ||
356 | "cannot cast key to int, is: %T", key) | ||
357 | } | ||
358 | |||
359 | if len(list) == 0 { | ||
360 | return nil, ast.TypeInvalid, fmt.Errorf("list is empty") | ||
361 | } | ||
362 | |||
363 | if keyInt < 0 || len(list) < keyInt+1 { | ||
364 | return nil, ast.TypeInvalid, fmt.Errorf( | ||
365 | "index %d out of range for list %s (max %d)", | ||
366 | keyInt, variableName, len(list)) | ||
367 | } | ||
368 | |||
369 | returnVal := list[keyInt].Value | ||
370 | returnType := list[keyInt].Type | ||
371 | return returnVal, returnType, nil | ||
372 | } | ||
373 | |||
374 | func (v *evalIndex) evalMapIndex(variableName string, target interface{}, key interface{}) (interface{}, ast.Type, error) { | ||
375 | // We assume type checking was already done and we can assume that target | ||
376 | // is a map and key is a string | ||
377 | vmap, ok := target.(map[string]ast.Variable) | ||
378 | if !ok { | ||
379 | return nil, ast.TypeInvalid, fmt.Errorf( | ||
380 | "cannot cast target to map[string]Variable, is: %T", target) | ||
381 | } | ||
382 | |||
383 | keyString, ok := key.(string) | ||
384 | if !ok { | ||
385 | return nil, ast.TypeInvalid, fmt.Errorf( | ||
386 | "cannot cast key to string, is: %T", key) | ||
387 | } | ||
388 | |||
389 | if len(vmap) == 0 { | ||
390 | return nil, ast.TypeInvalid, fmt.Errorf("map is empty") | ||
391 | } | ||
392 | |||
393 | value, ok := vmap[keyString] | ||
394 | if !ok { | ||
395 | return nil, ast.TypeInvalid, fmt.Errorf( | ||
396 | "key %q does not exist in map %s", keyString, variableName) | ||
397 | } | ||
398 | |||
399 | return value.Value, value.Type, nil | ||
400 | } | ||
401 | |||
402 | type evalOutput struct{ *ast.Output } | ||
403 | |||
404 | func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { | ||
405 | // The expressions should all be on the stack in reverse | ||
406 | // order. So pop them off, reverse their order, and concatenate. | ||
407 | nodes := make([]*ast.LiteralNode, 0, len(v.Exprs)) | ||
408 | haveUnknown := false | ||
409 | for range v.Exprs { | ||
410 | n := stack.Pop().(*ast.LiteralNode) | ||
411 | nodes = append(nodes, n) | ||
412 | |||
413 | // If we have any unknowns then the whole result is unknown | ||
414 | // (we must deal with this first, because the type checker can | ||
415 | // skip type conversions in the presence of unknowns, and thus | ||
416 | // any of our other nodes may be incorrectly typed.) | ||
417 | if n.IsUnknown() { | ||
418 | haveUnknown = true | ||
419 | } | ||
420 | } | ||
421 | |||
422 | if haveUnknown { | ||
423 | return UnknownValue, ast.TypeUnknown, nil | ||
424 | } | ||
425 | |||
426 | // Special case the single list and map | ||
427 | if len(nodes) == 1 { | ||
428 | switch t := nodes[0].Typex; t { | ||
429 | case ast.TypeList: | ||
430 | fallthrough | ||
431 | case ast.TypeMap: | ||
432 | fallthrough | ||
433 | case ast.TypeUnknown: | ||
434 | return nodes[0].Value, t, nil | ||
435 | } | ||
436 | } | ||
437 | |||
438 | // Otherwise concatenate the strings | ||
439 | var buf bytes.Buffer | ||
440 | for i := len(nodes) - 1; i >= 0; i-- { | ||
441 | if nodes[i].Typex != ast.TypeString { | ||
442 | return nil, ast.TypeInvalid, fmt.Errorf( | ||
443 | "invalid output with %s value at index %d: %#v", | ||
444 | nodes[i].Typex, | ||
445 | i, | ||
446 | nodes[i].Value, | ||
447 | ) | ||
448 | } | ||
449 | buf.WriteString(nodes[i].Value.(string)) | ||
450 | } | ||
451 | |||
452 | return buf.String(), ast.TypeString, nil | ||
453 | } | ||
454 | |||
455 | type evalLiteralNode struct{ *ast.LiteralNode } | ||
456 | |||
457 | func (v *evalLiteralNode) Eval(ast.Scope, *ast.Stack) (interface{}, ast.Type, error) { | ||
458 | return v.Value, v.Typex, nil | ||
459 | } | ||
460 | |||
461 | type evalVariableAccess struct{ *ast.VariableAccess } | ||
462 | |||
463 | func (v *evalVariableAccess) Eval(scope ast.Scope, _ *ast.Stack) (interface{}, ast.Type, error) { | ||
464 | // Look up the variable in the map | ||
465 | variable, ok := scope.LookupVar(v.Name) | ||
466 | if !ok { | ||
467 | return nil, ast.TypeInvalid, fmt.Errorf( | ||
468 | "unknown variable accessed: %s", v.Name) | ||
469 | } | ||
470 | |||
471 | return variable.Value, variable.Type, nil | ||
472 | } | ||