diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/ast.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/ast.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/ast.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/ast.go 2016-07-11 23:18:37.000000000 +0000 @@ -55,3 +55,24 @@ TypeList TypeMap ) + +func (t Type) Printable() string { + switch t { + case TypeInvalid: + return "invalid type" + case TypeAny: + return "any type" + case TypeString: + return "type string" + case TypeInt: + return "type int" + case TypeFloat: + return "type float" + case TypeList: + return "type list" + case TypeMap: + return "type map" + default: + return "unknown type" + } +} diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/concat.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/concat.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/concat.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/concat.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -package ast - -import ( - "bytes" - "fmt" -) - -// Concat represents a node where the result of two or more expressions are -// concatenated. The result of all expressions must be a string. -type Concat struct { - Exprs []Node - Posx Pos -} - -func (n *Concat) Accept(v Visitor) Node { - for i, expr := range n.Exprs { - n.Exprs[i] = expr.Accept(v) - } - - return v(n) -} - -func (n *Concat) Pos() Pos { - return n.Posx -} - -func (n *Concat) GoString() string { - return fmt.Sprintf("*%#v", *n) -} - -func (n *Concat) String() string { - var b bytes.Buffer - for _, expr := range n.Exprs { - b.WriteString(fmt.Sprintf("%s", expr)) - } - - return b.String() -} - -func (n *Concat) Type(Scope) (Type, error) { - return TypeString, nil -} diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/concat_test.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/concat_test.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/concat_test.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/concat_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -package ast - -import ( - "testing" -) - -func TestConcatType(t *testing.T) { - c := &Concat{} - actual, err := c.Type(nil) - if err != nil { - t.Fatalf("err: %s", err) - } - if actual != TypeString { - t.Fatalf("bad: %s", actual) - } -} diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/output.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/output.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/output.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/output.go 2016-07-11 23:18:37.000000000 +0000 @@ -0,0 +1,78 @@ +package ast + +import ( + "bytes" + "fmt" +) + +// Output represents the root node of all interpolation evaluations. If the +// output only has one expression which is either a TypeList or TypeMap, the +// Output can be type-asserted to []interface{} or map[string]interface{} +// respectively. Otherwise the Output evaluates as a string, and concatenates +// the evaluation of each expression. +type Output struct { + Exprs []Node + Posx Pos +} + +func (n *Output) Accept(v Visitor) Node { + for i, expr := range n.Exprs { + n.Exprs[i] = expr.Accept(v) + } + + return v(n) +} + +func (n *Output) Pos() Pos { + return n.Posx +} + +func (n *Output) GoString() string { + return fmt.Sprintf("*%#v", *n) +} + +func (n *Output) String() string { + var b bytes.Buffer + for _, expr := range n.Exprs { + b.WriteString(fmt.Sprintf("%s", expr)) + } + + return b.String() +} + +func (n *Output) Type(s Scope) (Type, error) { + // Special case no expressions for backward compatibility + if len(n.Exprs) == 0 { + return TypeString, nil + } + + // Special case a single expression of types list or map + if len(n.Exprs) == 1 { + exprType, err := n.Exprs[0].Type(s) + if err != nil { + return TypeInvalid, err + } + switch exprType { + case TypeList: + return TypeList, nil + case TypeMap: + return TypeMap, nil + } + } + + // Otherwise ensure all our expressions are strings + for index, expr := range n.Exprs { + exprType, err := expr.Type(s) + if err != nil { + return TypeInvalid, err + } + // We only look for things we know we can't coerce with an implicit conversion func + if exprType == TypeList || exprType == TypeMap { + return TypeInvalid, fmt.Errorf( + "multi-expression HIL outputs may only have string inputs: %d is type %s", + index, exprType) + } + } + + return TypeString, nil +} diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/output_test.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/output_test.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/output_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/output_test.go 2016-07-11 23:18:37.000000000 +0000 @@ -0,0 +1,213 @@ +package ast + +import ( + "testing" +) + +func TestOutput_type(t *testing.T) { + testCases := []struct { + Name string + Output *Output + Scope Scope + ReturnType Type + ShouldError bool + }{ + { + Name: "No expressions, for backward compatibility", + Output: &Output{}, + Scope: nil, + ReturnType: TypeString, + }, + { + Name: "Single string expression", + Output: &Output{ + Exprs: []Node{ + &LiteralNode{ + Value: "Whatever", + Typex: TypeString, + }, + }, + }, + Scope: nil, + ReturnType: TypeString, + }, + { + Name: "Single list expression of strings", + Output: &Output{ + Exprs: []Node{ + &VariableAccess{ + Name: "testvar", + }, + }, + }, + Scope: &BasicScope{ + VarMap: map[string]Variable{ + "testvar": Variable{ + Type: TypeList, + Value: []Variable{ + Variable{ + Type: TypeString, + Value: "Hello", + }, + Variable{ + Type: TypeString, + Value: "World", + }, + }, + }, + }, + }, + ReturnType: TypeList, + }, + { + Name: "Single map expression", + Output: &Output{ + Exprs: []Node{ + &VariableAccess{ + Name: "testvar", + }, + }, + }, + Scope: &BasicScope{ + VarMap: map[string]Variable{ + "testvar": Variable{ + Type: TypeMap, + Value: map[string]Variable{ + "key1": Variable{ + Type: TypeString, + Value: "Hello", + }, + "key2": Variable{ + Type: TypeString, + Value: "World", + }, + }, + }, + }, + }, + ReturnType: TypeMap, + }, + { + Name: "Multiple map expressions", + Output: &Output{ + Exprs: []Node{ + &VariableAccess{ + Name: "testvar", + }, + &VariableAccess{ + Name: "testvar", + }, + }, + }, + Scope: &BasicScope{ + VarMap: map[string]Variable{ + "testvar": Variable{ + Type: TypeMap, + Value: map[string]Variable{ + "key1": Variable{ + Type: TypeString, + Value: "Hello", + }, + "key2": Variable{ + Type: TypeString, + Value: "World", + }, + }, + }, + }, + }, + ShouldError: true, + ReturnType: TypeInvalid, + }, + { + Name: "Multiple list expressions", + Output: &Output{ + Exprs: []Node{ + &VariableAccess{ + Name: "testvar", + }, + &VariableAccess{ + Name: "testvar", + }, + }, + }, + Scope: &BasicScope{ + VarMap: map[string]Variable{ + "testvar": Variable{ + Type: TypeList, + Value: []Variable{ + Variable{ + Type: TypeString, + Value: "Hello", + }, + Variable{ + Type: TypeString, + Value: "World", + }, + }, + }, + }, + }, + ShouldError: true, + ReturnType: TypeInvalid, + }, + { + Name: "Multiple string expressions", + Output: &Output{ + Exprs: []Node{ + &VariableAccess{ + Name: "testvar", + }, + &VariableAccess{ + Name: "testvar", + }, + }, + }, + Scope: &BasicScope{ + VarMap: map[string]Variable{ + "testvar": Variable{ + Type: TypeString, + Value: "Hello", + }, + }, + }, + ReturnType: TypeString, + }, + { + Name: "Multiple string expressions with coercion", + Output: &Output{ + Exprs: []Node{ + &VariableAccess{ + Name: "testvar", + }, + &VariableAccess{ + Name: "testint", + }, + }, + }, + Scope: &BasicScope{ + VarMap: map[string]Variable{ + "testvar": Variable{ + Type: TypeString, + Value: "Hello", + }, + "testint": Variable{ + Type: TypeInt, + Value: 2, + }, + }, + }, + ReturnType: TypeString, + }, + } + + for _, v := range testCases { + actual, err := v.Output.Type(v.Scope) + if err != nil && !v.ShouldError { + t.Fatalf("case: %s\nerr: %s", v.Name, err) + } + if actual != v.ReturnType { + t.Fatalf("case: %s\n bad: %s\nexpected: %s\n", v.Name, actual, v.ReturnType) + } + } +} diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/type_string.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/type_string.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/ast/type_string.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/ast/type_string.go 2016-07-11 23:18:37.000000000 +0000 @@ -11,6 +11,7 @@ _Type_name_3 = "TypeInt" _Type_name_4 = "TypeFloat" _Type_name_5 = "TypeList" + _Type_name_6 = "TypeMap" ) var ( @@ -20,6 +21,7 @@ _Type_index_3 = [...]uint8{0, 7} _Type_index_4 = [...]uint8{0, 9} _Type_index_5 = [...]uint8{0, 8} + _Type_index_6 = [...]uint8{0, 7} ) func (i Type) String() string { @@ -36,6 +38,8 @@ return _Type_name_4 case i == 32: return _Type_name_5 + case i == 64: + return _Type_name_6 default: return fmt.Sprintf("Type(%d)", i) } diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/check_identifier.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/check_identifier.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/check_identifier.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/check_identifier.go 2016-07-11 23:18:37.000000000 +0000 @@ -35,7 +35,7 @@ c.visitCall(n) case *ast.VariableAccess: c.visitVariableAccess(n) - case *ast.Concat: + case *ast.Output: // Ignore case *ast.LiteralNode: // Ignore diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/check_types.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/check_types.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/check_types.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/check_types.go 2016-07-11 23:18:37.000000000 +0000 @@ -64,8 +64,8 @@ case *ast.Index: tc := &typeCheckIndex{n} result, err = tc.TypeCheck(v) - case *ast.Concat: - tc := &typeCheckConcat{n} + case *ast.Output: + tc := &typeCheckOutput{n} result, err = tc.TypeCheck(v) case *ast.LiteralNode: tc := &typeCheckLiteral{n} @@ -199,7 +199,7 @@ return nil, fmt.Errorf( "%s: argument %d should be %s, got %s", - tc.n.Func, i+1, expected, args[i]) + tc.n.Func, i+1, expected.Printable(), args[i].Printable()) } } @@ -219,7 +219,7 @@ return nil, fmt.Errorf( "%s: argument %d should be %s, got %s", tc.n.Func, realI, - function.VariadicType, t) + function.VariadicType.Printable(), t.Printable()) } } } @@ -230,11 +230,11 @@ return tc.n, nil } -type typeCheckConcat struct { - n *ast.Concat +type typeCheckOutput struct { + n *ast.Output } -func (tc *typeCheckConcat) TypeCheck(v *TypeCheck) (ast.Node, error) { +func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) { n := tc.n types := make([]ast.Type, len(n.Exprs)) for i, _ := range n.Exprs { @@ -247,6 +247,12 @@ return n, nil } + // If there is only one argument and it is a map, we evaluate to a map + if len(types) == 1 && types[0] == ast.TypeMap { + v.StackPush(ast.TypeMap) + return n, nil + } + // Otherwise, all concat args must be strings, so validate that for i, t := range types { if t != ast.TypeString { diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/convert.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/convert.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/convert.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/convert.go 2016-07-11 23:18:37.000000000 +0000 @@ -0,0 +1,148 @@ +package hil + +import ( + "fmt" + "reflect" + + "github.com/hashicorp/hil/ast" + "github.com/mitchellh/mapstructure" +) + +var hilMapstructureDecodeHookSlice []interface{} +var hilMapstructureDecodeHookStringSlice []string +var hilMapstructureDecodeHookMap map[string]interface{} + +// hilMapstructureWeakDecode behaves in the same way as mapstructure.WeakDecode +// but has a DecodeHook which defeats the backward compatibility mode of mapstructure +// which WeakDecodes []interface{}{} into an empty map[string]interface{}. This +// allows us to use WeakDecode (desirable), but not fail on empty lists. +func hilMapstructureWeakDecode(m interface{}, rawVal interface{}) error { + config := &mapstructure.DecoderConfig{ + DecodeHook: func(source reflect.Type, target reflect.Type, val interface{}) (interface{}, error) { + sliceType := reflect.TypeOf(hilMapstructureDecodeHookSlice) + stringSliceType := reflect.TypeOf(hilMapstructureDecodeHookStringSlice) + mapType := reflect.TypeOf(hilMapstructureDecodeHookMap) + + if (source == sliceType || source == stringSliceType) && target == mapType { + return nil, fmt.Errorf("Cannot convert %s into a %s", source, target) + } + + return val, nil + }, + WeaklyTypedInput: true, + Result: rawVal, + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(m) +} + +func InterfaceToVariable(input interface{}) (ast.Variable, error) { + if inputVariable, ok := input.(ast.Variable); ok { + return inputVariable, nil + } + + var stringVal string + if err := hilMapstructureWeakDecode(input, &stringVal); err == nil { + return ast.Variable{ + Type: ast.TypeString, + Value: stringVal, + }, nil + } + + var mapVal map[string]interface{} + if err := hilMapstructureWeakDecode(input, &mapVal); err == nil { + elements := make(map[string]ast.Variable) + for i, element := range mapVal { + varElement, err := InterfaceToVariable(element) + if err != nil { + return ast.Variable{}, err + } + elements[i] = varElement + } + + return ast.Variable{ + Type: ast.TypeMap, + Value: elements, + }, nil + } + + var sliceVal []interface{} + if err := hilMapstructureWeakDecode(input, &sliceVal); err == nil { + elements := make([]ast.Variable, len(sliceVal)) + for i, element := range sliceVal { + varElement, err := InterfaceToVariable(element) + if err != nil { + return ast.Variable{}, err + } + elements[i] = varElement + } + + return ast.Variable{ + Type: ast.TypeList, + Value: elements, + }, nil + } + + return ast.Variable{}, fmt.Errorf("value for conversion must be a string, interface{} or map[string]interface: got %T", input) +} + +func VariableToInterface(input ast.Variable) (interface{}, error) { + if input.Type == ast.TypeString { + if inputStr, ok := input.Value.(string); ok { + return inputStr, nil + } else { + return nil, fmt.Errorf("ast.Variable with type string has value which is not a string") + } + } + + if input.Type == ast.TypeList { + inputList, ok := input.Value.([]ast.Variable) + if !ok { + return nil, fmt.Errorf("ast.Variable with type list has value which is not a []ast.Variable") + } + + result := make([]interface{}, 0) + if len(inputList) == 0 { + return result, nil + } + + for _, element := range inputList { + if convertedElement, err := VariableToInterface(element); err == nil { + result = append(result, convertedElement) + } else { + return nil, err + } + } + + return result, nil + } + + if input.Type == ast.TypeMap { + inputMap, ok := input.Value.(map[string]ast.Variable) + if !ok { + return nil, fmt.Errorf("ast.Variable with type map has value which is not a map[string]ast.Variable") + } + + result := make(map[string]interface{}, 0) + if len(inputMap) == 0 { + return result, nil + } + + for key, value := range inputMap { + if convertedValue, err := VariableToInterface(value); err == nil { + result[key] = convertedValue + } else { + return nil, err + } + } + + return result, nil + } + + return nil, fmt.Errorf("unknown input type: %s", input.Type) +} diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/convert_test.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/convert_test.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/convert_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/convert_test.go 2016-07-11 23:18:37.000000000 +0000 @@ -0,0 +1,396 @@ +package hil + +import ( + "reflect" + "testing" + + "github.com/hashicorp/hil/ast" +) + +func TestInterfaceToVariable_variableInput(t *testing.T) { + _, err := InterfaceToVariable(ast.Variable{ + Type: ast.TypeString, + Value: "Hello world", + }) + + if err != nil { + t.Fatalf("Bad: %s", err) + } +} + +func TestInterfaceToVariable(t *testing.T) { + testCases := []struct { + name string + input interface{} + expected ast.Variable + }{ + { + name: "string", + input: "Hello world", + expected: ast.Variable{ + Type: ast.TypeString, + Value: "Hello world", + }, + }, + { + name: "empty list", + input: []interface{}{}, + expected: ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{}, + }, + }, + { + name: "empty list of strings", + input: []string{}, + expected: ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{}, + }, + }, + { + name: "int", + input: 1, + expected: ast.Variable{ + Type: ast.TypeString, + Value: "1", + }, + }, + { + name: "list of strings", + input: []string{"Hello", "World"}, + expected: ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "Hello", + }, + { + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + { + name: "list of lists of strings", + input: [][]interface{}{[]interface{}{"Hello", "World"}, []interface{}{"Goodbye", "World"}}, + expected: ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "Hello", + }, + { + Type: ast.TypeString, + Value: "World", + }, + }, + }, + { + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "Goodbye", + }, + { + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + }, + }, + { + name: "map of string->string", + input: map[string]string{"Hello": "World", "Foo": "Bar"}, + expected: ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "Hello": { + Type: ast.TypeString, + Value: "World", + }, + "Foo": { + Type: ast.TypeString, + Value: "Bar", + }, + }, + }, + }, + { + name: "map of lists of strings", + input: map[string][]string{ + "Hello": []string{"Hello", "World"}, + "Goodbye": []string{"Goodbye", "World"}, + }, + expected: ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "Hello": { + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "Hello", + }, + { + Type: ast.TypeString, + Value: "World", + }, + }, + }, + "Goodbye": { + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "Goodbye", + }, + { + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + }, + }, + { + name: "empty map", + input: map[string]string{}, + expected: ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{}, + }, + }, + { + name: "three-element map", + input: map[string]string{ + "us-west-1": "ami-123456", + "us-west-2": "ami-456789", + "eu-west-1": "ami-012345", + }, + expected: ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "us-west-1": { + Type: ast.TypeString, + Value: "ami-123456", + }, + "us-west-2": { + Type: ast.TypeString, + Value: "ami-456789", + }, + "eu-west-1": { + Type: ast.TypeString, + Value: "ami-012345", + }, + }, + }, + }, + } + + for _, tc := range testCases { + output, err := InterfaceToVariable(tc.input) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(output, tc.expected) { + t.Fatalf("%s:\nExpected: %s\n Got: %s\n", tc.name, tc.expected, output) + } + } +} + +func TestVariableToInterface(t *testing.T) { + testCases := []struct { + name string + expected interface{} + input ast.Variable + }{ + { + name: "string", + expected: "Hello world", + input: ast.Variable{ + Type: ast.TypeString, + Value: "Hello world", + }, + }, + { + name: "empty list", + expected: []interface{}{}, + input: ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{}, + }, + }, + { + name: "int", + expected: "1", + input: ast.Variable{ + Type: ast.TypeString, + Value: "1", + }, + }, + { + name: "list of strings", + expected: []interface{}{"Hello", "World"}, + input: ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "Hello", + }, + { + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + { + name: "list of lists of strings", + expected: []interface{}{[]interface{}{"Hello", "World"}, []interface{}{"Goodbye", "World"}}, + input: ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "Hello", + }, + { + Type: ast.TypeString, + Value: "World", + }, + }, + }, + { + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "Goodbye", + }, + { + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + }, + }, + { + name: "map of string->string", + expected: map[string]interface{}{"Hello": "World", "Foo": "Bar"}, + input: ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "Hello": { + Type: ast.TypeString, + Value: "World", + }, + "Foo": { + Type: ast.TypeString, + Value: "Bar", + }, + }, + }, + }, + { + name: "map of lists of strings", + expected: map[string]interface{}{ + "Hello": []interface{}{"Hello", "World"}, + "Goodbye": []interface{}{"Goodbye", "World"}, + }, + input: ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "Hello": { + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "Hello", + }, + { + Type: ast.TypeString, + Value: "World", + }, + }, + }, + "Goodbye": { + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "Goodbye", + }, + { + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + }, + }, + { + name: "empty map", + expected: map[string]interface{}{}, + input: ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{}, + }, + }, + { + name: "three-element map", + expected: map[string]interface{}{ + "us-west-1": "ami-123456", + "us-west-2": "ami-456789", + "eu-west-1": "ami-012345", + }, + input: ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "us-west-1": { + Type: ast.TypeString, + Value: "ami-123456", + }, + "us-west-2": { + Type: ast.TypeString, + Value: "ami-456789", + }, + "eu-west-1": { + Type: ast.TypeString, + Value: "ami-012345", + }, + }, + }, + }, + } + + for _, tc := range testCases { + output, err := VariableToInterface(tc.input) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(output, tc.expected) { + t.Fatalf("%s:\nExpected: %s\n Got: %s\n", tc.name, + tc.expected, output) + } + } +} diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/debian/changelog golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/debian/changelog --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/debian/changelog 2017-08-08 13:26:31.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/debian/changelog 2017-10-23 05:14:44.000000000 +0000 @@ -1,3 +1,16 @@ +golang-github-hashicorp-hil (0.0~git20160711.1e86c6b-1) unstable; urgency=medium + + * Team upload. + * New upstream snapshot. + * Add new dependency golang-github-mitchellh-mapstructure-dev. + * Start following git history; update gbp.conf + * debian/control: Update Standards-Version (no changes). + * debian/control: Mark package as autopkgtest-able. + * debian/copyright: Format uses insecure http protocol instead of + https + + -- Martín Ferrari Mon, 23 Oct 2017 05:14:44 +0000 + golang-github-hashicorp-hil (0.0~git20160326.0.40da60f-2) unstable; urgency=medium [ Paul Tagliamonte ] diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/debian/control golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/debian/control --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/debian/control 2017-08-08 13:23:30.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/debian/control 2017-10-23 05:14:44.000000000 +0000 @@ -1,23 +1,27 @@ Source: golang-github-hashicorp-hil -Section: devel -Priority: extra Maintainer: Debian Go Packaging Team -Uploaders: Dmitry Smirnov , Tim Potter +Uploaders: Dmitry Smirnov , + Tim Potter , +Section: devel +Testsuite: autopkgtest-pkg-go +Priority: optional Build-Depends: debhelper (>= 9), dh-golang, golang-any, - golang-github-mitchellh-reflectwalk-dev -Standards-Version: 3.9.7 -Homepage: https://github.com/hashicorp/hil + golang-github-mitchellh-mapstructure-dev, + golang-github-mitchellh-reflectwalk-dev, +Standards-Version: 4.1.1 Vcs-Browser: https://anonscm.debian.org/cgit/pkg-go/packages/golang-github-hashicorp-hil.git Vcs-Git: https://anonscm.debian.org/git/pkg-go/packages/golang-github-hashicorp-hil.git +Homepage: https://github.com/hashicorp/hil XS-Go-Import-Path: github.com/hashicorp/hil Package: golang-github-hashicorp-hil-dev Architecture: all -Depends: ${shlibs:Depends}, +Depends: golang-github-mitchellh-mapstructure-dev, + golang-github-mitchellh-reflectwalk-dev, ${misc:Depends}, - golang-github-mitchellh-reflectwalk-dev + ${shlibs:Depends}, Description: small embedded language for string interpolations HIL (HashiCorp Interpolation Language) is a lightweight embedded language used primarily for configuration interpolation. The goal of HIL is to make diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/debian/copyright golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/debian/copyright --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/debian/copyright 2017-08-08 10:39:28.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/debian/copyright 2017-10-23 05:14:44.000000000 +0000 @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: hil Source: https://github.com/hashicorp/hil diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/debian/gbp.conf golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/debian/gbp.conf --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/debian/gbp.conf 2017-08-08 10:39:28.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/debian/gbp.conf 2017-10-23 05:14:44.000000000 +0000 @@ -1,2 +1,4 @@ [DEFAULT] -pristine-tar = True +pristine-tar = False +upstream-tag = upstream/%(version)s +upstream-branch = upstream-git diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/eval.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/eval.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/eval.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/eval.go 2016-07-11 23:18:37.000000000 +0000 @@ -23,9 +23,76 @@ // semantic check on an AST tree. This will be called with the root node. type SemanticChecker func(ast.Node) error +// EvalType represents the type of the output returned from a HIL +// evaluation. +type EvalType uint32 + +const ( + TypeInvalid EvalType = 0 + TypeString EvalType = 1 << iota + TypeList + TypeMap +) + +//go:generate stringer -type=EvalType + +// 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{} +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} + +func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) { + output, outputType, err := internalEval(root, config) + if err != nil { + return InvalidResult, err + } + + 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 + 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 Eval(root ast.Node, config *EvalConfig) (interface{}, ast.Type, error) { +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) @@ -145,8 +212,8 @@ return &evalIndex{n}, nil case *ast.Call: return &evalCall{n}, nil - case *ast.Concat: - return &evalConcat{n}, nil + case *ast.Output: + return &evalOutput{n}, nil case *ast.LiteralNode: return &evalLiteralNode{n}, nil case *ast.VariableAccess: @@ -278,9 +345,9 @@ return value.Value, value.Type, nil } -type evalConcat struct{ *ast.Concat } +type evalOutput struct{ *ast.Output } -func (v *evalConcat) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { +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)) @@ -288,10 +355,13 @@ nodes = append(nodes, stack.Pop().(*ast.LiteralNode)) } - // Special case the single list + // Special case the single list and map if len(nodes) == 1 && nodes[0].Typex == ast.TypeList { return nodes[0].Value, ast.TypeList, nil } + if len(nodes) == 1 && nodes[0].Typex == ast.TypeMap { + return nodes[0].Value, ast.TypeMap, nil + } // Otherwise concatenate the strings var buf bytes.Buffer diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/eval_test.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/eval_test.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/eval_test.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/eval_test.go 2016-07-11 23:18:37.000000000 +0000 @@ -14,6 +14,316 @@ Scope *ast.BasicScope Error bool Result interface{} + ResultType EvalType + }{ + { + Input: "Hello World", + Scope: nil, + Result: "Hello World", + ResultType: TypeString, + }, + { + "${var.alist}", + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "var.alist": ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + ast.Variable{ + Type: ast.TypeString, + Value: "Hello", + }, + ast.Variable{ + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + }, + false, + []interface{}{"Hello", "World"}, + TypeList, + }, + { + "${var.alist[1]}", + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "var.alist": ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + ast.Variable{ + Type: ast.TypeString, + Value: "Hello", + }, + ast.Variable{ + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + }, + false, + "World", + TypeString, + }, + { + "${var.alist[1]} ${var.alist[0]}", + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "var.alist": ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + ast.Variable{ + Type: ast.TypeString, + Value: "Hello", + }, + ast.Variable{ + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + }, + false, + "World Hello", + TypeString, + }, + { + "${var.alist} ${var.alist}", + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "var.alist": ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + ast.Variable{ + Type: ast.TypeString, + Value: "Hello", + }, + ast.Variable{ + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + }, + true, + nil, + TypeInvalid, + }, + { + `${foo}`, + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeString, + Value: "hello", + }, + "bar": ast.Variable{ + Type: ast.TypeString, + Value: "world", + }, + }, + }, + }, + }, + false, + map[string]interface{}{ + "foo": "hello", + "bar": "world", + }, + TypeMap, + }, + { + `${foo["bar"]}`, + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeString, + Value: "hello", + }, + "bar": ast.Variable{ + Type: ast.TypeString, + Value: "world", + }, + }, + }, + }, + }, + false, + "world", + TypeString, + }, + { + `${foo["bar"]} ${foo["foo"]}`, + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeString, + Value: "hello", + }, + "bar": ast.Variable{ + Type: ast.TypeString, + Value: "world", + }, + }, + }, + }, + }, + false, + "world hello", + TypeString, + }, + { + `${foo} ${foo}`, + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeString, + Value: "hello", + }, + "bar": ast.Variable{ + Type: ast.TypeString, + Value: "world", + }, + }, + }, + }, + }, + true, + nil, + TypeInvalid, + }, + { + `${foo} ${bar}`, + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeString, + Value: "Hello", + }, + "bar": ast.Variable{ + Type: ast.TypeString, + Value: "World", + }, + }, + }, + false, + "Hello World", + TypeString, + }, + { + `${foo} ${bar}`, + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeString, + Value: "Hello", + }, + "bar": ast.Variable{ + Type: ast.TypeInt, + Value: 4, + }, + }, + }, + false, + "Hello 4", + TypeString, + }, + { + `${foo}`, + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "foo": ast.Variable{ + Type: ast.TypeString, + Value: "hello", + }, + "bar": ast.Variable{ + Type: ast.TypeString, + Value: "world", + }, + }, + }, + }, + }, + false, + map[string]interface{}{ + "foo": "hello", + "bar": "world", + }, + TypeMap, + }, + { + "${var.alist}", + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "var.alist": ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + ast.Variable{ + Type: ast.TypeString, + Value: "Hello", + }, + ast.Variable{ + Type: ast.TypeString, + Value: "World", + }, + }, + }, + }, + }, + false, + []interface{}{ + "Hello", + "World", + }, + TypeList, + }, + } + + for _, tc := range cases { + node, err := Parse(tc.Input) + if err != nil { + t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input) + } + + result, err := Eval(node, &EvalConfig{GlobalScope: tc.Scope}) + if err != nil != tc.Error { + t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input) + } + if tc.ResultType != TypeInvalid && result.Type != tc.ResultType { + t.Fatalf("Bad: %s\n\nInput: %s", result.Type, tc.Input) + } + if !reflect.DeepEqual(result.Value, tc.Result) { + t.Fatalf("\n Bad: %#v\nExpected: %#v\n\nInput: %s", result.Value, tc.Result, tc.Input) + } + } +} + +func TestEvalInternal(t *testing.T) { + cases := []struct { + Input string + Scope *ast.BasicScope + Error bool + Result interface{} ResultType ast.Type }{ { @@ -749,6 +1059,61 @@ "63", ast.TypeString, }, + + // Unary + { + "foo ${-46}", + nil, + false, + "foo -46", + ast.TypeString, + }, + + { + "foo ${-46 + 5}", + nil, + false, + "foo -41", + ast.TypeString, + }, + + { + "foo ${46 + -5}", + nil, + false, + "foo 41", + ast.TypeString, + }, + + { + "foo ${-bar}", + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "bar": ast.Variable{ + Value: 41, + Type: ast.TypeInt, + }, + }, + }, + false, + "foo -41", + ast.TypeString, + }, + + { + "foo ${5 + -bar}", + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "bar": ast.Variable{ + Value: 41, + Type: ast.TypeInt, + }, + }, + }, + false, + "foo -36", + ast.TypeString, + }, } for _, tc := range cases { @@ -757,7 +1122,7 @@ t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input) } - out, outType, err := Eval(node, &EvalConfig{GlobalScope: tc.Scope}) + out, outType, err := internalEval(node, &EvalConfig{GlobalScope: tc.Scope}) if err != nil != tc.Error { t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input) } diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/evaltype_string.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/evaltype_string.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/evaltype_string.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/evaltype_string.go 2016-07-11 23:18:37.000000000 +0000 @@ -0,0 +1,34 @@ +// Code generated by "stringer -type=EvalType"; DO NOT EDIT + +package hil + +import "fmt" + +const ( + _EvalType_name_0 = "TypeInvalid" + _EvalType_name_1 = "TypeString" + _EvalType_name_2 = "TypeList" + _EvalType_name_3 = "TypeMap" +) + +var ( + _EvalType_index_0 = [...]uint8{0, 11} + _EvalType_index_1 = [...]uint8{0, 10} + _EvalType_index_2 = [...]uint8{0, 8} + _EvalType_index_3 = [...]uint8{0, 7} +) + +func (i EvalType) String() string { + switch { + case i == 0: + return _EvalType_name_0 + case i == 2: + return _EvalType_name_1 + case i == 4: + return _EvalType_name_2 + case i == 8: + return _EvalType_name_3 + default: + return fmt.Sprintf("EvalType(%d)", i) + } +} diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/example_func_test.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/example_func_test.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/example_func_test.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/example_func_test.go 2016-07-11 23:18:37.000000000 +0000 @@ -41,13 +41,13 @@ }, } - value, valueType, err := hil.Eval(tree, config) + result, err := hil.Eval(tree, config) if err != nil { log.Fatal(err) } - fmt.Printf("Type: %s\n", valueType) - fmt.Printf("Value: %s\n", value) + fmt.Printf("Type: %s\n", result.Type) + fmt.Printf("Value: %s\n", result.Value) // Output: // Type: TypeString // Value: test string - 8 diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/example_test.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/example_test.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/example_test.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/example_test.go 2016-07-11 23:18:37.000000000 +0000 @@ -15,13 +15,13 @@ log.Fatal(err) } - value, valueType, err := hil.Eval(tree, &hil.EvalConfig{}) + result, err := hil.Eval(tree, &hil.EvalConfig{}) if err != nil { log.Fatal(err) } - fmt.Printf("Type: %s\n", valueType) - fmt.Printf("Value: %s\n", value) + fmt.Printf("Type: %s\n", result.Type) + fmt.Printf("Value: %s\n", result.Value) // Output: // Type: TypeString // Value: 8 diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/example_var_test.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/example_var_test.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/example_var_test.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/example_var_test.go 2016-07-11 23:18:37.000000000 +0000 @@ -27,13 +27,13 @@ }, } - value, valueType, err := hil.Eval(tree, config) + result, err := hil.Eval(tree, config) if err != nil { log.Fatal(err) } - fmt.Printf("Type: %s\n", valueType) - fmt.Printf("Value: %s\n", value) + fmt.Printf("Type: %s\n", result.Type) + fmt.Printf("Value: %s\n", result.Value) // Output: // Type: TypeString // Value: TEST STRING - 8 diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/.gitignore golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/.gitignore --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/.gitignore 2016-07-11 23:18:37.000000000 +0000 @@ -0,0 +1,3 @@ +.DS_Store +.idea +*.iml diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/lang.y golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/lang.y --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/lang.y 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/lang.y 2016-07-11 23:18:37.000000000 +0000 @@ -44,17 +44,17 @@ { parserResult = $1 - // We want to make sure that the top value is always a Concat - // so that the return value is always a string type from an + // We want to make sure that the top value is always an Output + // so that the return value is always a string, list of map from an // interpolation. // // The logic for checking for a LiteralNode is a little annoying // because functionally the AST is the same, but we do that because // it makes for an easy literal check later (to check if a string // has any interpolations). - if _, ok := $1.(*ast.Concat); !ok { + if _, ok := $1.(*ast.Output); !ok { if n, ok := $1.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString { - parserResult = &ast.Concat{ + parserResult = &ast.Output{ Exprs: []ast.Node{$1}, Posx: $1.Pos(), } @@ -70,13 +70,13 @@ | literalModeTop literalModeValue { var result []ast.Node - if c, ok := $1.(*ast.Concat); ok { + if c, ok := $1.(*ast.Output); ok { result = append(c.Exprs, $2) } else { result = []ast.Node{$1, $2} } - $$ = &ast.Concat{ + $$ = &ast.Output{ Exprs: result, Posx: result[0].Pos(), } diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/parse_test.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/parse_test.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/parse_test.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/parse_test.go 2016-07-11 23:18:37.000000000 +0000 @@ -46,7 +46,7 @@ { "foo ${var.bar}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ @@ -65,7 +65,7 @@ { "foo ${var.bar} baz", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ @@ -89,7 +89,7 @@ { "foo ${\"bar\"}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ @@ -115,7 +115,7 @@ { "foo ${42}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ @@ -135,7 +135,7 @@ { "foo ${3.14159}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ @@ -155,7 +155,7 @@ { "foo ${42+1}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ @@ -186,7 +186,7 @@ { "foo ${var.bar*1} baz", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ @@ -221,7 +221,7 @@ { "${foo()}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 3, Line: 1}, Exprs: []ast.Node{ &ast.Call{ @@ -236,7 +236,7 @@ { "${foo(bar)}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 3, Line: 1}, Exprs: []ast.Node{ &ast.Call{ @@ -256,7 +256,7 @@ { "${foo(bar, baz)}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 3, Line: 1}, Exprs: []ast.Node{ &ast.Call{ @@ -280,7 +280,7 @@ { "${foo(bar(baz))}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 3, Line: 1}, Exprs: []ast.Node{ &ast.Call{ @@ -306,7 +306,7 @@ { `foo ${"bar ${baz}"}`, false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 1, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ @@ -314,7 +314,7 @@ Typex: ast.TypeString, Posx: ast.Pos{Column: 1, Line: 1}, }, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 7, Line: 1}, Exprs: []ast.Node{ &ast.LiteralNode{ @@ -335,7 +335,7 @@ { "${foo[1]}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 3, Line: 1}, Exprs: []ast.Node{ &ast.Index{ @@ -357,7 +357,7 @@ { "${foo[1]} - ${bar[0]}", false, - &ast.Concat{ + &ast.Output{ Posx: ast.Pos{Column: 3, Line: 1}, Exprs: []ast.Node{ &ast.Index{ diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/README.md golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/README.md --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/README.md 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/README.md 2016-07-11 23:18:37.000000000 +0000 @@ -72,7 +72,7 @@ `add(1, var.foo)` or even nested function calls: `add(1, get("some value"))`. - * Witin strings, further interpolations can be opened with `${}`. + * Within strings, further interpolations can be opened with `${}`. Example: `"Hello ${nested}"`. A full example including the original `${}` (remember this list assumes were inside of one already) could be: `foo ${func("hello ${var.foo}")}`. diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/transform_fixed.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/transform_fixed.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/transform_fixed.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/transform_fixed.go 2016-07-11 23:18:37.000000000 +0000 @@ -14,7 +14,7 @@ // We visit the nodes in top-down order result := root switch n := result.(type) { - case *ast.Concat: + case *ast.Output: for i, v := range n.Exprs { n.Exprs[i] = FixedValueTransform(v, Value) } diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/transform_fixed_test.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/transform_fixed_test.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/transform_fixed_test.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/transform_fixed_test.go 2016-07-11 23:18:37.000000000 +0000 @@ -23,13 +23,13 @@ }, { - &ast.Concat{ + &ast.Output{ Exprs: []ast.Node{ &ast.VariableAccess{Name: "bar"}, &ast.LiteralNode{Value: 42}, }, }, - &ast.Concat{ + &ast.Output{ Exprs: []ast.Node{ &ast.LiteralNode{Value: "foo"}, &ast.LiteralNode{Value: 42}, diff -Nru golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/y.go golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/y.go --- golang-github-hashicorp-hil-0.0~git20160326.0.40da60f/y.go 2016-03-26 00:44:49.000000000 +0000 +++ golang-github-hashicorp-hil-0.0~git20160711.1e86c6b/y.go 2016-07-11 23:18:37.000000000 +0000 @@ -484,17 +484,17 @@ { parserResult = parserDollar[1].node - // We want to make sure that the top value is always a Concat - // so that the return value is always a string type from an + // We want to make sure that the top value is always an Output + // so that the return value is always a string, list of map from an // interpolation. // // The logic for checking for a LiteralNode is a little annoying // because functionally the AST is the same, but we do that because // it makes for an easy literal check later (to check if a string // has any interpolations). - if _, ok := parserDollar[1].node.(*ast.Concat); !ok { + if _, ok := parserDollar[1].node.(*ast.Output); !ok { if n, ok := parserDollar[1].node.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString { - parserResult = &ast.Concat{ + parserResult = &ast.Output{ Exprs: []ast.Node{parserDollar[1].node}, Posx: parserDollar[1].node.Pos(), } @@ -512,13 +512,13 @@ //line lang.y:71 { var result []ast.Node - if c, ok := parserDollar[1].node.(*ast.Concat); ok { + if c, ok := parserDollar[1].node.(*ast.Output); ok { result = append(c.Exprs, parserDollar[2].node) } else { result = []ast.Node{parserDollar[1].node, parserDollar[2].node} } - parserVAL.node = &ast.Concat{ + parserVAL.node = &ast.Output{ Exprs: result, Posx: result[0].Pos(), }