Introduce Output nodes and EvaluationResult type
hil.Eval now returns a structure including the Value and Result of an
interpolation, in terms not associated with the ast. This prevents the
proliferation of ast.TypeX types in consumer code, and provides a
natural boundary between inside and outside hil.
We also change the output types for lists and maps to use the "natural"
Go types for these ([]interface{} and map[string]interface{}
respectively) which again prevents the proliferation of ast.Variable
processing code in consumers of the library.
This changes the signature - the old tests remain for internal code, and
there are some new ones added for the new interface.
James Nugent
8 years ago
0 | package ast | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "fmt" | |
5 | ) | |
6 | ||
7 | // Concat represents a node where the result of two or more expressions are | |
8 | // concatenated. The result of all expressions must be a string. | |
9 | type Concat struct { | |
10 | Exprs []Node | |
11 | Posx Pos | |
12 | } | |
13 | ||
14 | func (n *Concat) Accept(v Visitor) Node { | |
15 | for i, expr := range n.Exprs { | |
16 | n.Exprs[i] = expr.Accept(v) | |
17 | } | |
18 | ||
19 | return v(n) | |
20 | } | |
21 | ||
22 | func (n *Concat) Pos() Pos { | |
23 | return n.Posx | |
24 | } | |
25 | ||
26 | func (n *Concat) GoString() string { | |
27 | return fmt.Sprintf("*%#v", *n) | |
28 | } | |
29 | ||
30 | func (n *Concat) String() string { | |
31 | var b bytes.Buffer | |
32 | for _, expr := range n.Exprs { | |
33 | b.WriteString(fmt.Sprintf("%s", expr)) | |
34 | } | |
35 | ||
36 | return b.String() | |
37 | } | |
38 | ||
39 | func (n *Concat) Type(Scope) (Type, error) { | |
40 | return TypeString, nil | |
41 | } |
0 | package ast | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ) | |
5 | ||
6 | func TestConcatType(t *testing.T) { | |
7 | c := &Concat{} | |
8 | actual, err := c.Type(nil) | |
9 | if err != nil { | |
10 | t.Fatalf("err: %s", err) | |
11 | } | |
12 | if actual != TypeString { | |
13 | t.Fatalf("bad: %s", actual) | |
14 | } | |
15 | } |
0 | package ast | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "fmt" | |
5 | ) | |
6 | ||
7 | // Output represents the root node of all interpolation evaluations. If the | |
8 | // output only has one expression which is either a list or a map, the Output | |
9 | // evaluates as a string or a map respectively. Otherwise the Output evaluates | |
10 | // as a string, and concatenates the evaluation of each expression. | |
11 | type Output struct { | |
12 | Exprs []Node | |
13 | Posx Pos | |
14 | } | |
15 | ||
16 | func (n *Output) Accept(v Visitor) Node { | |
17 | for i, expr := range n.Exprs { | |
18 | n.Exprs[i] = expr.Accept(v) | |
19 | } | |
20 | ||
21 | return v(n) | |
22 | } | |
23 | ||
24 | func (n *Output) Pos() Pos { | |
25 | return n.Posx | |
26 | } | |
27 | ||
28 | func (n *Output) GoString() string { | |
29 | return fmt.Sprintf("*%#v", *n) | |
30 | } | |
31 | ||
32 | func (n *Output) String() string { | |
33 | var b bytes.Buffer | |
34 | for _, expr := range n.Exprs { | |
35 | b.WriteString(fmt.Sprintf("%s", expr)) | |
36 | } | |
37 | ||
38 | return b.String() | |
39 | } | |
40 | ||
41 | func (n *Output) Type(s Scope) (Type, error) { | |
42 | ||
43 | // Special case no expressions for backward compatibility | |
44 | if len(n.Exprs) == 0 { | |
45 | return TypeString, nil | |
46 | } | |
47 | ||
48 | // Special case a single expression of types list or map | |
49 | if len(n.Exprs) == 1 { | |
50 | exprType, err := n.Exprs[0].Type(s) | |
51 | if err != nil { | |
52 | return TypeInvalid, err | |
53 | } | |
54 | switch exprType { | |
55 | case TypeList: | |
56 | return TypeList, nil | |
57 | case TypeMap: | |
58 | return TypeMap, nil | |
59 | } | |
60 | } | |
61 | ||
62 | // Otherwise ensure all our expressions are strings | |
63 | for index, expr := range n.Exprs { | |
64 | exprType, err := expr.Type(s) | |
65 | if err != nil { | |
66 | return TypeInvalid, err | |
67 | } | |
68 | // We only look for things we know we can't coerce with an implicit | |
69 | if exprType == TypeList || exprType == TypeMap { | |
70 | return TypeInvalid, fmt.Errorf( | |
71 | "multi-expression HIL outputs may only have string inputs: %d is type %s", | |
72 | index, exprType) | |
73 | } | |
74 | } | |
75 | ||
76 | return TypeString, nil | |
77 | } |
0 | package ast | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ) | |
5 | ||
6 | func TestOutput_type(t *testing.T) { | |
7 | testCases := []struct{ | |
8 | Name string | |
9 | Output *Output | |
10 | Scope Scope | |
11 | ReturnType Type | |
12 | ShouldError bool | |
13 | }{ | |
14 | { | |
15 | Name: "No expressions, for backward compatibility", | |
16 | Output: &Output{}, | |
17 | Scope: nil, | |
18 | ReturnType: TypeString, | |
19 | }, | |
20 | { | |
21 | Name: "Single string expression", | |
22 | Output: &Output{ | |
23 | Exprs: []Node{ | |
24 | &LiteralNode{ | |
25 | Value: "Whatever", | |
26 | Typex: TypeString, | |
27 | }, | |
28 | }, | |
29 | }, | |
30 | Scope: nil, | |
31 | ReturnType: TypeString, | |
32 | }, | |
33 | { | |
34 | Name: "Single list expression of strings", | |
35 | Output: &Output{ | |
36 | Exprs: []Node{ | |
37 | &VariableAccess{ | |
38 | Name: "testvar", | |
39 | }, | |
40 | }, | |
41 | }, | |
42 | Scope: &BasicScope{ | |
43 | VarMap: map[string]Variable{ | |
44 | "testvar": Variable{ | |
45 | Type: TypeList, | |
46 | Value: []Variable{ | |
47 | Variable{ | |
48 | Type: TypeString, | |
49 | Value: "Hello", | |
50 | }, | |
51 | Variable{ | |
52 | Type: TypeString, | |
53 | Value: "World", | |
54 | }, | |
55 | }, | |
56 | }, | |
57 | }, | |
58 | }, | |
59 | ReturnType: TypeList, | |
60 | }, | |
61 | { | |
62 | Name: "Single map expression", | |
63 | Output: &Output{ | |
64 | Exprs: []Node{ | |
65 | &VariableAccess{ | |
66 | Name: "testvar", | |
67 | }, | |
68 | }, | |
69 | }, | |
70 | Scope: &BasicScope{ | |
71 | VarMap: map[string]Variable{ | |
72 | "testvar": Variable{ | |
73 | Type: TypeMap, | |
74 | Value: map[string]Variable{ | |
75 | "key1": Variable{ | |
76 | Type: TypeString, | |
77 | Value: "Hello", | |
78 | }, | |
79 | "key2": Variable{ | |
80 | Type: TypeString, | |
81 | Value: "World", | |
82 | }, | |
83 | }, | |
84 | }, | |
85 | }, | |
86 | }, | |
87 | ReturnType: TypeMap, | |
88 | }, | |
89 | { | |
90 | Name: "Multiple map expressions", | |
91 | Output: &Output{ | |
92 | Exprs: []Node{ | |
93 | &VariableAccess{ | |
94 | Name: "testvar", | |
95 | }, | |
96 | &VariableAccess{ | |
97 | Name: "testvar", | |
98 | }, | |
99 | }, | |
100 | }, | |
101 | Scope: &BasicScope{ | |
102 | VarMap: map[string]Variable{ | |
103 | "testvar": Variable{ | |
104 | Type: TypeMap, | |
105 | Value: map[string]Variable{ | |
106 | "key1": Variable{ | |
107 | Type: TypeString, | |
108 | Value: "Hello", | |
109 | }, | |
110 | "key2": Variable{ | |
111 | Type: TypeString, | |
112 | Value: "World", | |
113 | }, | |
114 | }, | |
115 | }, | |
116 | }, | |
117 | }, | |
118 | ShouldError: true, | |
119 | ReturnType: TypeInvalid, | |
120 | }, | |
121 | { | |
122 | Name: "Multiple list expressions", | |
123 | Output: &Output{ | |
124 | Exprs: []Node{ | |
125 | &VariableAccess{ | |
126 | Name: "testvar", | |
127 | }, | |
128 | &VariableAccess{ | |
129 | Name: "testvar", | |
130 | }, | |
131 | }, | |
132 | }, | |
133 | Scope: &BasicScope{ | |
134 | VarMap: map[string]Variable{ | |
135 | "testvar": Variable{ | |
136 | Type: TypeList, | |
137 | Value: []Variable{ | |
138 | Variable{ | |
139 | Type: TypeString, | |
140 | Value: "Hello", | |
141 | }, | |
142 | Variable{ | |
143 | Type: TypeString, | |
144 | Value: "World", | |
145 | }, | |
146 | }, | |
147 | }, | |
148 | }, | |
149 | }, | |
150 | ShouldError: true, | |
151 | ReturnType: TypeInvalid, | |
152 | }, | |
153 | { | |
154 | Name: "Multiple string expressions", | |
155 | Output: &Output{ | |
156 | Exprs: []Node{ | |
157 | &VariableAccess{ | |
158 | Name: "testvar", | |
159 | }, | |
160 | &VariableAccess{ | |
161 | Name: "testvar", | |
162 | }, | |
163 | }, | |
164 | }, | |
165 | Scope: &BasicScope{ | |
166 | VarMap: map[string]Variable{ | |
167 | "testvar": Variable{ | |
168 | Type: TypeString, | |
169 | Value: "Hello", | |
170 | }, | |
171 | }, | |
172 | }, | |
173 | ReturnType: TypeString, | |
174 | }, | |
175 | { | |
176 | Name: "Multiple string expressions with coercion", | |
177 | Output: &Output{ | |
178 | Exprs: []Node{ | |
179 | &VariableAccess{ | |
180 | Name: "testvar", | |
181 | }, | |
182 | &VariableAccess{ | |
183 | Name: "testint", | |
184 | }, | |
185 | }, | |
186 | }, | |
187 | Scope: &BasicScope{ | |
188 | VarMap: map[string]Variable{ | |
189 | "testvar": Variable{ | |
190 | Type: TypeString, | |
191 | Value: "Hello", | |
192 | }, | |
193 | "testint": Variable{ | |
194 | Type: TypeInt, | |
195 | Value: 2, | |
196 | }, | |
197 | }, | |
198 | }, | |
199 | ReturnType: TypeString, | |
200 | }, | |
201 | } | |
202 | ||
203 | ||
204 | for _, v := range testCases { | |
205 | actual, err := v.Output.Type(v.Scope) | |
206 | if err != nil && !v.ShouldError { | |
207 | t.Fatalf("case: %s\nerr: %s", v.Name, err) | |
208 | } | |
209 | if actual != v.ReturnType { | |
210 | t.Fatalf("case: %s\n bad: %s\nexpected: %s\n", v.Name, actual, v.ReturnType) | |
211 | } | |
212 | } | |
213 | } | |
214 |
10 | 10 | _Type_name_3 = "TypeInt" |
11 | 11 | _Type_name_4 = "TypeFloat" |
12 | 12 | _Type_name_5 = "TypeList" |
13 | _Type_name_6 = "TypeMap" | |
13 | 14 | ) |
14 | 15 | |
15 | 16 | var ( |
19 | 20 | _Type_index_3 = [...]uint8{0, 7} |
20 | 21 | _Type_index_4 = [...]uint8{0, 9} |
21 | 22 | _Type_index_5 = [...]uint8{0, 8} |
23 | _Type_index_6 = [...]uint8{0, 7} | |
22 | 24 | ) |
23 | 25 | |
24 | 26 | func (i Type) String() string { |
35 | 37 | return _Type_name_4 |
36 | 38 | case i == 32: |
37 | 39 | return _Type_name_5 |
40 | case i == 64: | |
41 | return _Type_name_6 | |
38 | 42 | default: |
39 | 43 | return fmt.Sprintf("Type(%d)", i) |
40 | 44 | } |
34 | 34 | c.visitCall(n) |
35 | 35 | case *ast.VariableAccess: |
36 | 36 | c.visitVariableAccess(n) |
37 | case *ast.Concat: | |
37 | case *ast.Output: | |
38 | 38 | // Ignore |
39 | 39 | case *ast.LiteralNode: |
40 | 40 | // Ignore |
63 | 63 | case *ast.Index: |
64 | 64 | tc := &typeCheckIndex{n} |
65 | 65 | result, err = tc.TypeCheck(v) |
66 | case *ast.Concat: | |
67 | tc := &typeCheckConcat{n} | |
66 | case *ast.Output: | |
67 | tc := &typeCheckOutput{n} | |
68 | 68 | result, err = tc.TypeCheck(v) |
69 | 69 | case *ast.LiteralNode: |
70 | 70 | tc := &typeCheckLiteral{n} |
229 | 229 | return tc.n, nil |
230 | 230 | } |
231 | 231 | |
232 | type typeCheckConcat struct { | |
233 | n *ast.Concat | |
234 | } | |
235 | ||
236 | func (tc *typeCheckConcat) TypeCheck(v *TypeCheck) (ast.Node, error) { | |
232 | type typeCheckOutput struct { | |
233 | n *ast.Output | |
234 | } | |
235 | ||
236 | func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) { | |
237 | 237 | n := tc.n |
238 | 238 | types := make([]ast.Type, len(n.Exprs)) |
239 | 239 | for i, _ := range n.Exprs { |
243 | 243 | // If there is only one argument and it is a list, we evaluate to a list |
244 | 244 | if len(types) == 1 && types[0] == ast.TypeList { |
245 | 245 | v.StackPush(ast.TypeList) |
246 | return n, nil | |
247 | } | |
248 | ||
249 | // If there is only one argument and it is a list, we evaluate to a map | |
250 | if len(types) == 1 && types[0] == ast.TypeMap { | |
251 | v.StackPush(ast.TypeMap) | |
246 | 252 | return n, nil |
247 | 253 | } |
248 | 254 |
22 | 22 | // semantic check on an AST tree. This will be called with the root node. |
23 | 23 | type SemanticChecker func(ast.Node) error |
24 | 24 | |
25 | // EvalType represents the type of the output returned from a HIL | |
26 | // evaluation. | |
27 | type EvalType uint32 | |
28 | ||
29 | const ( | |
30 | TypeInvalid EvalType = 0 | |
31 | TypeString EvalType = 1 << iota | |
32 | TypeList | |
33 | TypeMap | |
34 | ) | |
35 | ||
36 | //go:generate stringer -type=EvalType | |
37 | ||
38 | // EvaluationResult is a struct returned from the hil.Eval function, | |
39 | // representing the result of an interpolation. Results are returned in their | |
40 | // "natural" Go structure rather than in terms of the HIL AST. For the types | |
41 | // currently implemented, this means that the Value field can be interpreted as | |
42 | // the following Go types: | |
43 | // TypeInvalid: undefined | |
44 | // TypeString: string | |
45 | // TypeList: []interface{} | |
46 | // TypeMap: map[string]interface{} | |
47 | type EvaluationResult struct { | |
48 | Type EvalType | |
49 | Value interface{} | |
50 | } | |
51 | ||
52 | // InvalidResult is a structure representing the result of a HIL interpolation | |
53 | // which has invalid syntax, missing variables, or some other type of error. | |
54 | // The error is described out of band in the accompanying error return value. | |
55 | var InvalidResult = EvaluationResult{Type: TypeInvalid, Value: nil} | |
56 | ||
57 | func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) { | |
58 | output, outputType, err := internalEval(root, config) | |
59 | if err != nil { | |
60 | return InvalidResult, err | |
61 | } | |
62 | ||
63 | switch(outputType) { | |
64 | case ast.TypeList: | |
65 | return EvaluationResult{ | |
66 | Type: TypeList, | |
67 | Value: hilListToGoSlice(output.([]ast.Variable)), | |
68 | }, nil | |
69 | case ast.TypeMap: | |
70 | return EvaluationResult{ | |
71 | Type: TypeMap, | |
72 | Value: hilMapToGoMap(output.(map[string]ast.Variable)), | |
73 | }, nil | |
74 | case ast.TypeString: | |
75 | return EvaluationResult{ | |
76 | Type: TypeString, | |
77 | Value: output, | |
78 | }, nil | |
79 | default: | |
80 | return InvalidResult, fmt.Errorf("unknown type %s as interpolation output", outputType) | |
81 | } | |
82 | } | |
83 | ||
25 | 84 | // Eval evaluates the given AST tree and returns its output value, the type |
26 | 85 | // of the output, and any error that occurred. |
27 | func Eval(root ast.Node, config *EvalConfig) (interface{}, ast.Type, error) { | |
86 | func internalEval(root ast.Node, config *EvalConfig) (interface{}, ast.Type, error) { | |
28 | 87 | // Copy the scope so we can add our builtins |
29 | 88 | if config == nil { |
30 | 89 | config = new(EvalConfig) |
144 | 203 | return &evalIndex{n}, nil |
145 | 204 | case *ast.Call: |
146 | 205 | return &evalCall{n}, nil |
147 | case *ast.Concat: | |
148 | return &evalConcat{n}, nil | |
206 | case *ast.Output: | |
207 | return &evalOutput{n}, nil | |
149 | 208 | case *ast.LiteralNode: |
150 | 209 | return &evalLiteralNode{n}, nil |
151 | 210 | case *ast.VariableAccess: |
277 | 336 | return value.Value, value.Type, nil |
278 | 337 | } |
279 | 338 | |
280 | type evalConcat struct{ *ast.Concat } | |
281 | ||
282 | func (v *evalConcat) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { | |
339 | // hilListToGoSlice converts an ast.Variable into a []interface{}. We assume that | |
340 | // the type checking is already done since this is internal and only used in output | |
341 | // evaluation. | |
342 | func hilListToGoSlice(variable []ast.Variable) []interface{} { | |
343 | output := make([]interface{}, len(variable)) | |
344 | ||
345 | for index, element := range variable { | |
346 | output[index] = element.Value | |
347 | } | |
348 | ||
349 | return output | |
350 | } | |
351 | ||
352 | // hilMapToGoMap converts an ast.Variable into a map[string]interface{}. We assume | |
353 | // that the type checking is already done since this is internal and only used in | |
354 | // output evaluation. | |
355 | func hilMapToGoMap(variable map[string]ast.Variable) map[string]interface{} { | |
356 | output := make(map[string]interface{}) | |
357 | ||
358 | for key, element := range variable { | |
359 | output[key] = element.Value | |
360 | } | |
361 | ||
362 | return output | |
363 | } | |
364 | ||
365 | type evalOutput struct{ *ast.Output } | |
366 | ||
367 | func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { | |
283 | 368 | // The expressions should all be on the stack in reverse |
284 | 369 | // order. So pop them off, reverse their order, and concatenate. |
285 | 370 | nodes := make([]*ast.LiteralNode, 0, len(v.Exprs)) |
287 | 372 | nodes = append(nodes, stack.Pop().(*ast.LiteralNode)) |
288 | 373 | } |
289 | 374 | |
290 | // Special case the single list | |
375 | // Special case the single list and map | |
291 | 376 | if len(nodes) == 1 && nodes[0].Typex == ast.TypeList { |
292 | 377 | return nodes[0].Value, ast.TypeList, nil |
378 | } | |
379 | if len(nodes) == 1 && nodes[0].Typex == ast.TypeMap { | |
380 | return nodes[0].Value, ast.TypeMap, nil | |
293 | 381 | } |
294 | 382 | |
295 | 383 | // Otherwise concatenate the strings |
8 | 8 | ) |
9 | 9 | |
10 | 10 | func TestEval(t *testing.T) { |
11 | cases := []struct { | |
12 | Input string | |
13 | Scope *ast.BasicScope | |
14 | Error bool | |
15 | Result interface{} | |
16 | ResultType EvalType | |
17 | }{ | |
18 | { | |
19 | Input: "Hello World", | |
20 | Scope: nil, | |
21 | Result: "Hello World", | |
22 | ResultType: TypeString, | |
23 | }, | |
24 | { | |
25 | "${var.alist}", | |
26 | &ast.BasicScope{ | |
27 | VarMap: map[string]ast.Variable{ | |
28 | "var.alist": ast.Variable{ | |
29 | Type: ast.TypeList, | |
30 | Value: []ast.Variable{ | |
31 | ast.Variable{ | |
32 | Type: ast.TypeString, | |
33 | Value: "Hello", | |
34 | }, | |
35 | ast.Variable{ | |
36 | Type: ast.TypeString, | |
37 | Value: "World", | |
38 | }, | |
39 | }, | |
40 | }, | |
41 | }, | |
42 | }, | |
43 | false, | |
44 | []interface{}{"Hello", "World"}, | |
45 | TypeList, | |
46 | }, | |
47 | { | |
48 | "${var.alist[1]}", | |
49 | &ast.BasicScope{ | |
50 | VarMap: map[string]ast.Variable{ | |
51 | "var.alist": ast.Variable{ | |
52 | Type: ast.TypeList, | |
53 | Value: []ast.Variable{ | |
54 | ast.Variable{ | |
55 | Type: ast.TypeString, | |
56 | Value: "Hello", | |
57 | }, | |
58 | ast.Variable{ | |
59 | Type: ast.TypeString, | |
60 | Value: "World", | |
61 | }, | |
62 | }, | |
63 | }, | |
64 | }, | |
65 | }, | |
66 | false, | |
67 | "World", | |
68 | TypeString, | |
69 | }, | |
70 | { | |
71 | `${foo}`, | |
72 | &ast.BasicScope{ | |
73 | VarMap: map[string]ast.Variable{ | |
74 | "foo": ast.Variable{ | |
75 | Type: ast.TypeMap, | |
76 | Value: map[string]ast.Variable{ | |
77 | "foo": ast.Variable{ | |
78 | Type: ast.TypeString, | |
79 | Value: "hello", | |
80 | }, | |
81 | "bar": ast.Variable{ | |
82 | Type: ast.TypeString, | |
83 | Value: "world", | |
84 | }, | |
85 | }, | |
86 | }, | |
87 | }, | |
88 | }, | |
89 | false, | |
90 | map[string]interface{}{ | |
91 | "foo": "hello", | |
92 | "bar": "world", | |
93 | }, | |
94 | TypeMap, | |
95 | }, | |
96 | { | |
97 | `${foo["bar"]}`, | |
98 | &ast.BasicScope{ | |
99 | VarMap: map[string]ast.Variable{ | |
100 | "foo": ast.Variable{ | |
101 | Type: ast.TypeMap, | |
102 | Value: map[string]ast.Variable{ | |
103 | "foo": ast.Variable{ | |
104 | Type: ast.TypeString, | |
105 | Value: "hello", | |
106 | }, | |
107 | "bar": ast.Variable{ | |
108 | Type: ast.TypeString, | |
109 | Value: "world", | |
110 | }, | |
111 | }, | |
112 | }, | |
113 | }, | |
114 | }, | |
115 | false, | |
116 | "world", | |
117 | TypeString, | |
118 | }, | |
119 | } | |
120 | ||
121 | for _, tc := range cases { | |
122 | node, err := Parse(tc.Input) | |
123 | if err != nil { | |
124 | t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input) | |
125 | } | |
126 | ||
127 | result, err := Eval(node, &EvalConfig{GlobalScope: tc.Scope}) | |
128 | if err != nil != tc.Error { | |
129 | t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input) | |
130 | } | |
131 | if tc.ResultType != TypeInvalid && result.Type != tc.ResultType { | |
132 | t.Fatalf("Bad: %s\n\nInput: %s", result.Type, tc.Input) | |
133 | } | |
134 | if !reflect.DeepEqual(result.Value, tc.Result) { | |
135 | t.Fatalf("Bad: %#v\n\nInput: %s", result.Value, tc.Input) | |
136 | } | |
137 | } | |
138 | } | |
139 | ||
140 | func TestEvalInternal(t *testing.T) { | |
11 | 141 | cases := []struct { |
12 | 142 | Input string |
13 | 143 | Scope *ast.BasicScope |
756 | 886 | t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input) |
757 | 887 | } |
758 | 888 | |
759 | out, outType, err := Eval(node, &EvalConfig{GlobalScope: tc.Scope}) | |
889 | out, outType, err := internalEval(node, &EvalConfig{GlobalScope: tc.Scope}) | |
760 | 890 | if err != nil != tc.Error { |
761 | 891 | t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input) |
762 | 892 | } |
0 | // Code generated by "stringer -type=EvalType"; DO NOT EDIT | |
1 | ||
2 | package hil | |
3 | ||
4 | import "fmt" | |
5 | ||
6 | const ( | |
7 | _EvalType_name_0 = "TypeInvalid" | |
8 | _EvalType_name_1 = "TypeString" | |
9 | _EvalType_name_2 = "TypeList" | |
10 | _EvalType_name_3 = "TypeMap" | |
11 | ) | |
12 | ||
13 | var ( | |
14 | _EvalType_index_0 = [...]uint8{0, 11} | |
15 | _EvalType_index_1 = [...]uint8{0, 10} | |
16 | _EvalType_index_2 = [...]uint8{0, 8} | |
17 | _EvalType_index_3 = [...]uint8{0, 7} | |
18 | ) | |
19 | ||
20 | func (i EvalType) String() string { | |
21 | switch { | |
22 | case i == 0: | |
23 | return _EvalType_name_0 | |
24 | case i == 2: | |
25 | return _EvalType_name_1 | |
26 | case i == 4: | |
27 | return _EvalType_name_2 | |
28 | case i == 8: | |
29 | return _EvalType_name_3 | |
30 | default: | |
31 | return fmt.Sprintf("EvalType(%d)", i) | |
32 | } | |
33 | } |
40 | 40 | }, |
41 | 41 | } |
42 | 42 | |
43 | value, valueType, err := hil.Eval(tree, config) | |
43 | result, err := hil.Eval(tree, config) | |
44 | 44 | if err != nil { |
45 | 45 | log.Fatal(err) |
46 | 46 | } |
47 | 47 | |
48 | fmt.Printf("Type: %s\n", valueType) | |
49 | fmt.Printf("Value: %s\n", value) | |
48 | fmt.Printf("Type: %s\n", result.Type) | |
49 | fmt.Printf("Value: %s\n", result.Value) | |
50 | 50 | // Output: |
51 | 51 | // Type: TypeString |
52 | 52 | // Value: test string - 8 |
14 | 14 | log.Fatal(err) |
15 | 15 | } |
16 | 16 | |
17 | value, valueType, err := hil.Eval(tree, &hil.EvalConfig{}) | |
17 | result, err := hil.Eval(tree, &hil.EvalConfig{}) | |
18 | 18 | if err != nil { |
19 | 19 | log.Fatal(err) |
20 | 20 | } |
21 | 21 | |
22 | fmt.Printf("Type: %s\n", valueType) | |
23 | fmt.Printf("Value: %s\n", value) | |
22 | fmt.Printf("Type: %s\n", result.Type) | |
23 | fmt.Printf("Value: %s\n", result.Value) | |
24 | 24 | // Output: |
25 | 25 | // Type: TypeString |
26 | 26 | // Value: 8 |
26 | 26 | }, |
27 | 27 | } |
28 | 28 | |
29 | value, valueType, err := hil.Eval(tree, config) | |
29 | result, err := hil.Eval(tree, config) | |
30 | 30 | if err != nil { |
31 | 31 | log.Fatal(err) |
32 | 32 | } |
33 | 33 | |
34 | fmt.Printf("Type: %s\n", valueType) | |
35 | fmt.Printf("Value: %s\n", value) | |
34 | fmt.Printf("Type: %s\n", result.Type) | |
35 | fmt.Printf("Value: %s\n", result.Value) | |
36 | 36 | // Output: |
37 | 37 | // Type: TypeString |
38 | 38 | // Value: TEST STRING - 8 |
43 | 43 | { |
44 | 44 | parserResult = $1 |
45 | 45 | |
46 | // We want to make sure that the top value is always a Concat | |
47 | // so that the return value is always a string type from an | |
46 | // We want to make sure that the top value is always an Output | |
47 | // so that the return value is always a string, list of map from an | |
48 | 48 | // interpolation. |
49 | 49 | // |
50 | 50 | // The logic for checking for a LiteralNode is a little annoying |
51 | 51 | // because functionally the AST is the same, but we do that because |
52 | 52 | // it makes for an easy literal check later (to check if a string |
53 | 53 | // has any interpolations). |
54 | if _, ok := $1.(*ast.Concat); !ok { | |
54 | if _, ok := $1.(*ast.Output); !ok { | |
55 | 55 | if n, ok := $1.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString { |
56 | parserResult = &ast.Concat{ | |
56 | parserResult = &ast.Output{ | |
57 | 57 | Exprs: []ast.Node{$1}, |
58 | 58 | Posx: $1.Pos(), |
59 | 59 | } |
69 | 69 | | literalModeTop literalModeValue |
70 | 70 | { |
71 | 71 | var result []ast.Node |
72 | if c, ok := $1.(*ast.Concat); ok { | |
72 | if c, ok := $1.(*ast.Output); ok { | |
73 | 73 | result = append(c.Exprs, $2) |
74 | 74 | } else { |
75 | 75 | result = []ast.Node{$1, $2} |
76 | 76 | } |
77 | 77 | |
78 | $$ = &ast.Concat{ | |
78 | $$ = &ast.Output{ | |
79 | 79 | Exprs: result, |
80 | 80 | Posx: result[0].Pos(), |
81 | 81 | } |
45 | 45 | { |
46 | 46 | "foo ${var.bar}", |
47 | 47 | false, |
48 | &ast.Concat{ | |
48 | &ast.Output{ | |
49 | 49 | Posx: ast.Pos{Column: 1, Line: 1}, |
50 | 50 | Exprs: []ast.Node{ |
51 | 51 | &ast.LiteralNode{ |
64 | 64 | { |
65 | 65 | "foo ${var.bar} baz", |
66 | 66 | false, |
67 | &ast.Concat{ | |
67 | &ast.Output{ | |
68 | 68 | Posx: ast.Pos{Column: 1, Line: 1}, |
69 | 69 | Exprs: []ast.Node{ |
70 | 70 | &ast.LiteralNode{ |
88 | 88 | { |
89 | 89 | "foo ${\"bar\"}", |
90 | 90 | false, |
91 | &ast.Concat{ | |
91 | &ast.Output{ | |
92 | 92 | Posx: ast.Pos{Column: 1, Line: 1}, |
93 | 93 | Exprs: []ast.Node{ |
94 | 94 | &ast.LiteralNode{ |
114 | 114 | { |
115 | 115 | "foo ${42}", |
116 | 116 | false, |
117 | &ast.Concat{ | |
117 | &ast.Output{ | |
118 | 118 | Posx: ast.Pos{Column: 1, Line: 1}, |
119 | 119 | Exprs: []ast.Node{ |
120 | 120 | &ast.LiteralNode{ |
134 | 134 | { |
135 | 135 | "foo ${3.14159}", |
136 | 136 | false, |
137 | &ast.Concat{ | |
137 | &ast.Output{ | |
138 | 138 | Posx: ast.Pos{Column: 1, Line: 1}, |
139 | 139 | Exprs: []ast.Node{ |
140 | 140 | &ast.LiteralNode{ |
154 | 154 | { |
155 | 155 | "foo ${42+1}", |
156 | 156 | false, |
157 | &ast.Concat{ | |
157 | &ast.Output{ | |
158 | 158 | Posx: ast.Pos{Column: 1, Line: 1}, |
159 | 159 | Exprs: []ast.Node{ |
160 | 160 | &ast.LiteralNode{ |
185 | 185 | { |
186 | 186 | "foo ${var.bar*1} baz", |
187 | 187 | false, |
188 | &ast.Concat{ | |
188 | &ast.Output{ | |
189 | 189 | Posx: ast.Pos{Column: 1, Line: 1}, |
190 | 190 | Exprs: []ast.Node{ |
191 | 191 | &ast.LiteralNode{ |
220 | 220 | { |
221 | 221 | "${foo()}", |
222 | 222 | false, |
223 | &ast.Concat{ | |
223 | &ast.Output{ | |
224 | 224 | Posx: ast.Pos{Column: 3, Line: 1}, |
225 | 225 | Exprs: []ast.Node{ |
226 | 226 | &ast.Call{ |
235 | 235 | { |
236 | 236 | "${foo(bar)}", |
237 | 237 | false, |
238 | &ast.Concat{ | |
238 | &ast.Output{ | |
239 | 239 | Posx: ast.Pos{Column: 3, Line: 1}, |
240 | 240 | Exprs: []ast.Node{ |
241 | 241 | &ast.Call{ |
255 | 255 | { |
256 | 256 | "${foo(bar, baz)}", |
257 | 257 | false, |
258 | &ast.Concat{ | |
258 | &ast.Output{ | |
259 | 259 | Posx: ast.Pos{Column: 3, Line: 1}, |
260 | 260 | Exprs: []ast.Node{ |
261 | 261 | &ast.Call{ |
279 | 279 | { |
280 | 280 | "${foo(bar(baz))}", |
281 | 281 | false, |
282 | &ast.Concat{ | |
282 | &ast.Output{ | |
283 | 283 | Posx: ast.Pos{Column: 3, Line: 1}, |
284 | 284 | Exprs: []ast.Node{ |
285 | 285 | &ast.Call{ |
305 | 305 | { |
306 | 306 | `foo ${"bar ${baz}"}`, |
307 | 307 | false, |
308 | &ast.Concat{ | |
309 | Posx: ast.Pos{Column: 1, Line: 1}, | |
310 | Exprs: []ast.Node{ | |
311 | &ast.LiteralNode{ | |
312 | Value: "foo ", | |
313 | Typex: ast.TypeString, | |
314 | Posx: ast.Pos{Column: 1, Line: 1}, | |
315 | }, | |
316 | &ast.Concat{ | |
308 | &ast.Output{ | |
309 | Posx: ast.Pos{Column: 1, Line: 1}, | |
310 | Exprs: []ast.Node{ | |
311 | &ast.LiteralNode{ | |
312 | Value: "foo ", | |
313 | Typex: ast.TypeString, | |
314 | Posx: ast.Pos{Column: 1, Line: 1}, | |
315 | }, | |
316 | &ast.Output{ | |
317 | 317 | Posx: ast.Pos{Column: 7, Line: 1}, |
318 | 318 | Exprs: []ast.Node{ |
319 | 319 | &ast.LiteralNode{ |
334 | 334 | { |
335 | 335 | "${foo[1]}", |
336 | 336 | false, |
337 | &ast.Concat{ | |
337 | &ast.Output{ | |
338 | 338 | Posx: ast.Pos{Column: 3, Line: 1}, |
339 | 339 | Exprs: []ast.Node{ |
340 | 340 | &ast.Index{ |
356 | 356 | { |
357 | 357 | "${foo[1]} - ${bar[0]}", |
358 | 358 | false, |
359 | &ast.Concat{ | |
359 | &ast.Output{ | |
360 | 360 | Posx: ast.Pos{Column: 3, Line: 1}, |
361 | 361 | Exprs: []ast.Node{ |
362 | 362 | &ast.Index{ |
13 | 13 | // We visit the nodes in top-down order |
14 | 14 | result := root |
15 | 15 | switch n := result.(type) { |
16 | case *ast.Concat: | |
16 | case *ast.Output: | |
17 | 17 | for i, v := range n.Exprs { |
18 | 18 | n.Exprs[i] = FixedValueTransform(v, Value) |
19 | 19 | } |
22 | 22 | }, |
23 | 23 | |
24 | 24 | { |
25 | &ast.Concat{ | |
25 | &ast.Output{ | |
26 | 26 | Exprs: []ast.Node{ |
27 | 27 | &ast.VariableAccess{Name: "bar"}, |
28 | 28 | &ast.LiteralNode{Value: 42}, |
29 | 29 | }, |
30 | 30 | }, |
31 | &ast.Concat{ | |
31 | &ast.Output{ | |
32 | 32 | Exprs: []ast.Node{ |
33 | 33 | &ast.LiteralNode{Value: "foo"}, |
34 | 34 | &ast.LiteralNode{Value: 42}, |
483 | 483 | { |
484 | 484 | parserResult = parserDollar[1].node |
485 | 485 | |
486 | // We want to make sure that the top value is always a Concat | |
487 | // so that the return value is always a string type from an | |
486 | // We want to make sure that the top value is always an Output | |
487 | // so that the return value is always a string, list of map from an | |
488 | 488 | // interpolation. |
489 | 489 | // |
490 | 490 | // The logic for checking for a LiteralNode is a little annoying |
491 | 491 | // because functionally the AST is the same, but we do that because |
492 | 492 | // it makes for an easy literal check later (to check if a string |
493 | 493 | // has any interpolations). |
494 | if _, ok := parserDollar[1].node.(*ast.Concat); !ok { | |
494 | if _, ok := parserDollar[1].node.(*ast.Output); !ok { | |
495 | 495 | if n, ok := parserDollar[1].node.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString { |
496 | parserResult = &ast.Concat{ | |
496 | parserResult = &ast.Output{ | |
497 | 497 | Exprs: []ast.Node{parserDollar[1].node}, |
498 | 498 | Posx: parserDollar[1].node.Pos(), |
499 | 499 | } |
511 | 511 | //line lang.y:71 |
512 | 512 | { |
513 | 513 | var result []ast.Node |
514 | if c, ok := parserDollar[1].node.(*ast.Concat); ok { | |
514 | if c, ok := parserDollar[1].node.(*ast.Output); ok { | |
515 | 515 | result = append(c.Exprs, parserDollar[2].node) |
516 | 516 | } else { |
517 | 517 | result = []ast.Node{parserDollar[1].node, parserDollar[2].node} |
518 | 518 | } |
519 | 519 | |
520 | parserVAL.node = &ast.Concat{ | |
520 | parserVAL.node = &ast.Output{ | |
521 | 521 | Exprs: result, |
522 | 522 | Posx: result[0].Pos(), |
523 | 523 | } |