Codebase list golang-github-hashicorp-hil / 72cf410
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
18 changed file(s) with 617 addition(s) and 120 deletion(s). Raw diff Collapse all Expand all
+0
-42
ast/concat.go less more
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
-16
ast/concat_test.go less more
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
1010 _Type_name_3 = "TypeInt"
1111 _Type_name_4 = "TypeFloat"
1212 _Type_name_5 = "TypeList"
13 _Type_name_6 = "TypeMap"
1314 )
1415
1516 var (
1920 _Type_index_3 = [...]uint8{0, 7}
2021 _Type_index_4 = [...]uint8{0, 9}
2122 _Type_index_5 = [...]uint8{0, 8}
23 _Type_index_6 = [...]uint8{0, 7}
2224 )
2325
2426 func (i Type) String() string {
3537 return _Type_name_4
3638 case i == 32:
3739 return _Type_name_5
40 case i == 64:
41 return _Type_name_6
3842 default:
3943 return fmt.Sprintf("Type(%d)", i)
4044 }
3434 c.visitCall(n)
3535 case *ast.VariableAccess:
3636 c.visitVariableAccess(n)
37 case *ast.Concat:
37 case *ast.Output:
3838 // Ignore
3939 case *ast.LiteralNode:
4040 // Ignore
6363 case *ast.Index:
6464 tc := &typeCheckIndex{n}
6565 result, err = tc.TypeCheck(v)
66 case *ast.Concat:
67 tc := &typeCheckConcat{n}
66 case *ast.Output:
67 tc := &typeCheckOutput{n}
6868 result, err = tc.TypeCheck(v)
6969 case *ast.LiteralNode:
7070 tc := &typeCheckLiteral{n}
229229 return tc.n, nil
230230 }
231231
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) {
237237 n := tc.n
238238 types := make([]ast.Type, len(n.Exprs))
239239 for i, _ := range n.Exprs {
243243 // If there is only one argument and it is a list, we evaluate to a list
244244 if len(types) == 1 && types[0] == ast.TypeList {
245245 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)
246252 return n, nil
247253 }
248254
2222 // semantic check on an AST tree. This will be called with the root node.
2323 type SemanticChecker func(ast.Node) error
2424
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
2584 // Eval evaluates the given AST tree and returns its output value, the type
2685 // 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) {
2887 // Copy the scope so we can add our builtins
2988 if config == nil {
3089 config = new(EvalConfig)
144203 return &evalIndex{n}, nil
145204 case *ast.Call:
146205 return &evalCall{n}, nil
147 case *ast.Concat:
148 return &evalConcat{n}, nil
206 case *ast.Output:
207 return &evalOutput{n}, nil
149208 case *ast.LiteralNode:
150209 return &evalLiteralNode{n}, nil
151210 case *ast.VariableAccess:
277336 return value.Value, value.Type, nil
278337 }
279338
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) {
283368 // The expressions should all be on the stack in reverse
284369 // order. So pop them off, reverse their order, and concatenate.
285370 nodes := make([]*ast.LiteralNode, 0, len(v.Exprs))
287372 nodes = append(nodes, stack.Pop().(*ast.LiteralNode))
288373 }
289374
290 // Special case the single list
375 // Special case the single list and map
291376 if len(nodes) == 1 && nodes[0].Typex == ast.TypeList {
292377 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
293381 }
294382
295383 // Otherwise concatenate the strings
88 )
99
1010 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) {
11141 cases := []struct {
12142 Input string
13143 Scope *ast.BasicScope
756886 t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
757887 }
758888
759 out, outType, err := Eval(node, &EvalConfig{GlobalScope: tc.Scope})
889 out, outType, err := internalEval(node, &EvalConfig{GlobalScope: tc.Scope})
760890 if err != nil != tc.Error {
761891 t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
762892 }
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 }
4040 },
4141 }
4242
43 value, valueType, err := hil.Eval(tree, config)
43 result, err := hil.Eval(tree, config)
4444 if err != nil {
4545 log.Fatal(err)
4646 }
4747
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)
5050 // Output:
5151 // Type: TypeString
5252 // Value: test string - 8
1414 log.Fatal(err)
1515 }
1616
17 value, valueType, err := hil.Eval(tree, &hil.EvalConfig{})
17 result, err := hil.Eval(tree, &hil.EvalConfig{})
1818 if err != nil {
1919 log.Fatal(err)
2020 }
2121
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)
2424 // Output:
2525 // Type: TypeString
2626 // Value: 8
2626 },
2727 }
2828
29 value, valueType, err := hil.Eval(tree, config)
29 result, err := hil.Eval(tree, config)
3030 if err != nil {
3131 log.Fatal(err)
3232 }
3333
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)
3636 // Output:
3737 // Type: TypeString
3838 // Value: TEST STRING - 8
4343 {
4444 parserResult = $1
4545
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
4848 // interpolation.
4949 //
5050 // The logic for checking for a LiteralNode is a little annoying
5151 // because functionally the AST is the same, but we do that because
5252 // it makes for an easy literal check later (to check if a string
5353 // has any interpolations).
54 if _, ok := $1.(*ast.Concat); !ok {
54 if _, ok := $1.(*ast.Output); !ok {
5555 if n, ok := $1.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString {
56 parserResult = &ast.Concat{
56 parserResult = &ast.Output{
5757 Exprs: []ast.Node{$1},
5858 Posx: $1.Pos(),
5959 }
6969 | literalModeTop literalModeValue
7070 {
7171 var result []ast.Node
72 if c, ok := $1.(*ast.Concat); ok {
72 if c, ok := $1.(*ast.Output); ok {
7373 result = append(c.Exprs, $2)
7474 } else {
7575 result = []ast.Node{$1, $2}
7676 }
7777
78 $$ = &ast.Concat{
78 $$ = &ast.Output{
7979 Exprs: result,
8080 Posx: result[0].Pos(),
8181 }
4545 {
4646 "foo ${var.bar}",
4747 false,
48 &ast.Concat{
48 &ast.Output{
4949 Posx: ast.Pos{Column: 1, Line: 1},
5050 Exprs: []ast.Node{
5151 &ast.LiteralNode{
6464 {
6565 "foo ${var.bar} baz",
6666 false,
67 &ast.Concat{
67 &ast.Output{
6868 Posx: ast.Pos{Column: 1, Line: 1},
6969 Exprs: []ast.Node{
7070 &ast.LiteralNode{
8888 {
8989 "foo ${\"bar\"}",
9090 false,
91 &ast.Concat{
91 &ast.Output{
9292 Posx: ast.Pos{Column: 1, Line: 1},
9393 Exprs: []ast.Node{
9494 &ast.LiteralNode{
114114 {
115115 "foo ${42}",
116116 false,
117 &ast.Concat{
117 &ast.Output{
118118 Posx: ast.Pos{Column: 1, Line: 1},
119119 Exprs: []ast.Node{
120120 &ast.LiteralNode{
134134 {
135135 "foo ${3.14159}",
136136 false,
137 &ast.Concat{
137 &ast.Output{
138138 Posx: ast.Pos{Column: 1, Line: 1},
139139 Exprs: []ast.Node{
140140 &ast.LiteralNode{
154154 {
155155 "foo ${42+1}",
156156 false,
157 &ast.Concat{
157 &ast.Output{
158158 Posx: ast.Pos{Column: 1, Line: 1},
159159 Exprs: []ast.Node{
160160 &ast.LiteralNode{
185185 {
186186 "foo ${var.bar*1} baz",
187187 false,
188 &ast.Concat{
188 &ast.Output{
189189 Posx: ast.Pos{Column: 1, Line: 1},
190190 Exprs: []ast.Node{
191191 &ast.LiteralNode{
220220 {
221221 "${foo()}",
222222 false,
223 &ast.Concat{
223 &ast.Output{
224224 Posx: ast.Pos{Column: 3, Line: 1},
225225 Exprs: []ast.Node{
226226 &ast.Call{
235235 {
236236 "${foo(bar)}",
237237 false,
238 &ast.Concat{
238 &ast.Output{
239239 Posx: ast.Pos{Column: 3, Line: 1},
240240 Exprs: []ast.Node{
241241 &ast.Call{
255255 {
256256 "${foo(bar, baz)}",
257257 false,
258 &ast.Concat{
258 &ast.Output{
259259 Posx: ast.Pos{Column: 3, Line: 1},
260260 Exprs: []ast.Node{
261261 &ast.Call{
279279 {
280280 "${foo(bar(baz))}",
281281 false,
282 &ast.Concat{
282 &ast.Output{
283283 Posx: ast.Pos{Column: 3, Line: 1},
284284 Exprs: []ast.Node{
285285 &ast.Call{
305305 {
306306 `foo ${"bar ${baz}"}`,
307307 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{
317317 Posx: ast.Pos{Column: 7, Line: 1},
318318 Exprs: []ast.Node{
319319 &ast.LiteralNode{
334334 {
335335 "${foo[1]}",
336336 false,
337 &ast.Concat{
337 &ast.Output{
338338 Posx: ast.Pos{Column: 3, Line: 1},
339339 Exprs: []ast.Node{
340340 &ast.Index{
356356 {
357357 "${foo[1]} - ${bar[0]}",
358358 false,
359 &ast.Concat{
359 &ast.Output{
360360 Posx: ast.Pos{Column: 3, Line: 1},
361361 Exprs: []ast.Node{
362362 &ast.Index{
1313 // We visit the nodes in top-down order
1414 result := root
1515 switch n := result.(type) {
16 case *ast.Concat:
16 case *ast.Output:
1717 for i, v := range n.Exprs {
1818 n.Exprs[i] = FixedValueTransform(v, Value)
1919 }
2222 },
2323
2424 {
25 &ast.Concat{
25 &ast.Output{
2626 Exprs: []ast.Node{
2727 &ast.VariableAccess{Name: "bar"},
2828 &ast.LiteralNode{Value: 42},
2929 },
3030 },
31 &ast.Concat{
31 &ast.Output{
3232 Exprs: []ast.Node{
3333 &ast.LiteralNode{Value: "foo"},
3434 &ast.LiteralNode{Value: 42},
483483 {
484484 parserResult = parserDollar[1].node
485485
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
488488 // interpolation.
489489 //
490490 // The logic for checking for a LiteralNode is a little annoying
491491 // because functionally the AST is the same, but we do that because
492492 // it makes for an easy literal check later (to check if a string
493493 // has any interpolations).
494 if _, ok := parserDollar[1].node.(*ast.Concat); !ok {
494 if _, ok := parserDollar[1].node.(*ast.Output); !ok {
495495 if n, ok := parserDollar[1].node.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString {
496 parserResult = &ast.Concat{
496 parserResult = &ast.Output{
497497 Exprs: []ast.Node{parserDollar[1].node},
498498 Posx: parserDollar[1].node.Pos(),
499499 }
511511 //line lang.y:71
512512 {
513513 var result []ast.Node
514 if c, ok := parserDollar[1].node.(*ast.Concat); ok {
514 if c, ok := parserDollar[1].node.(*ast.Output); ok {
515515 result = append(c.Exprs, parserDollar[2].node)
516516 } else {
517517 result = []ast.Node{parserDollar[1].node, parserDollar[2].node}
518518 }
519519
520 parserVAL.node = &ast.Concat{
520 parserVAL.node = &ast.Output{
521521 Exprs: result,
522522 Posx: result[0].Pos(),
523523 }