diff --git a/go.mod b/go.mod index 71ddc41a5..f4e581dac 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/hashicorp/go-version v1.2.1 github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/hcl v1.0.0 - github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5 + github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038 github.com/hashicorp/memberlist v0.2.2 github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69 github.com/hashicorp/raft v1.2.0 diff --git a/go.sum b/go.sum index 400cb729d..a059732ea 100644 --- a/go.sum +++ b/go.sum @@ -273,8 +273,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5 h1:uk280DXEbQiCOZgCOI3elFSeNxf8YIZiNsbr2pQLYD0= -github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts= +github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038 h1:n9J0rwVWXDpNd5iZnwY7w4WZyq53/rROeI7OVvLW8Ok= +github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.1 h1:XFSOubp8KWB+Jd2PDyaX5xUd5bhSP/+pTDZVDMzZJM8= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= diff --git a/vendor/github.com/hashicorp/hil/.travis.yml b/vendor/github.com/hashicorp/hil/.travis.yml deleted file mode 100644 index 83dc540ef..000000000 --- a/vendor/github.com/hashicorp/hil/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -sudo: false -language: go -go: 1.5 diff --git a/vendor/github.com/hashicorp/hil/README.md b/vendor/github.com/hashicorp/hil/README.md index 186ed2518..ca9e1a499 100644 --- a/vendor/github.com/hashicorp/hil/README.md +++ b/vendor/github.com/hashicorp/hil/README.md @@ -1,6 +1,6 @@ # HIL -[![GoDoc](https://godoc.org/github.com/hashicorp/hil?status.png)](https://godoc.org/github.com/hashicorp/hil) [![Build Status](https://travis-ci.org/hashicorp/hil.svg?branch=master)](https://travis-ci.org/hashicorp/hil) +[![GoDoc](https://godoc.org/github.com/hashicorp/hil?status.png)](https://godoc.org/github.com/hashicorp/hil) [![Build Status](https://circleci.com/gh/hashicorp/hil/tree/master.svg?style=svg)](https://circleci.com/gh/hashicorp/hil/tree/master) HIL (HashiCorp Interpolation Language) is a lightweight embedded language used primarily for configuration interpolation. The goal of HIL is to make a simple @@ -43,7 +43,7 @@ better tested for general purpose use. ## Syntax For a complete grammar, please see the parser itself. A high-level overview -of the syntax and grammer is listed here. +of the syntax and grammar is listed here. Code begins within `${` and `}`. Outside of this, text is treated literally. For example, `foo` is a valid HIL program that is just the diff --git a/vendor/github.com/hashicorp/hil/appveyor.yml b/vendor/github.com/hashicorp/hil/appveyor.yml deleted file mode 100644 index feaf7a34e..000000000 --- a/vendor/github.com/hashicorp/hil/appveyor.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: "build-{branch}-{build}" -image: Visual Studio 2015 -clone_folder: c:\gopath\src\github.com\hashicorp\hil -environment: - GOPATH: c:\gopath -init: - - git config --global core.autocrlf true -install: -- cmd: >- - echo %Path% - - go version - - go env - - go get -d -v -t ./... -build_script: -- cmd: go test -v ./... diff --git a/vendor/github.com/hashicorp/hil/ast/arithmetic_op.go b/vendor/github.com/hashicorp/hil/ast/arithmetic_op.go index e36dd42dc..18880c604 100644 --- a/vendor/github.com/hashicorp/hil/ast/arithmetic_op.go +++ b/vendor/github.com/hashicorp/hil/ast/arithmetic_op.go @@ -5,9 +5,20 @@ type ArithmeticOp int const ( ArithmeticOpInvalid ArithmeticOp = 0 - ArithmeticOpAdd ArithmeticOp = iota + + ArithmeticOpAdd ArithmeticOp = iota ArithmeticOpSub ArithmeticOpMul ArithmeticOpDiv ArithmeticOpMod + + ArithmeticOpLogicalAnd + ArithmeticOpLogicalOr + + ArithmeticOpEqual + ArithmeticOpNotEqual + ArithmeticOpLessThan + ArithmeticOpLessThanOrEqual + ArithmeticOpGreaterThan + ArithmeticOpGreaterThanOrEqual ) diff --git a/vendor/github.com/hashicorp/hil/ast/ast.go b/vendor/github.com/hashicorp/hil/ast/ast.go index 5d8d7555a..c6350f8bb 100644 --- a/vendor/github.com/hashicorp/hil/ast/ast.go +++ b/vendor/github.com/hashicorp/hil/ast/ast.go @@ -19,13 +19,22 @@ type Node interface { // Pos is the starting position of an AST node type Pos struct { - Column, Line int // Column/Line number, starting at 1 + Column, Line int // Column/Line number, starting at 1 + Filename string // Optional source filename, if known } func (p Pos) String() string { - return fmt.Sprintf("%d:%d", p.Line, p.Column) + if p.Filename == "" { + return fmt.Sprintf("%d:%d", p.Line, p.Column) + } else { + return fmt.Sprintf("%s:%d:%d", p.Filename, p.Line, p.Column) + } } +// InitPos is an initiaial position value. This should be used as +// the starting position (presets the column and line to 1). +var InitPos = Pos{Column: 1, Line: 1} + // Visitors are just implementations of this function. // // The function must return the Node to replace this node with. "nil" is @@ -49,11 +58,19 @@ type Type uint32 const ( TypeInvalid Type = 0 TypeAny Type = 1 << iota + TypeBool TypeString TypeInt TypeFloat TypeList TypeMap + + // This is a special type used by Terraform to mark "unknown" values. + // It is impossible for this type to be introduced into your HIL programs + // unless you explicitly set a variable to this value. In that case, + // any operation including the variable will return "TypeUnknown" as the + // type. + TypeUnknown ) func (t Type) Printable() string { @@ -62,6 +79,8 @@ func (t Type) Printable() string { return "invalid type" case TypeAny: return "any type" + case TypeBool: + return "type bool" case TypeString: return "type string" case TypeInt: @@ -72,6 +91,8 @@ func (t Type) Printable() string { return "type list" case TypeMap: return "type map" + case TypeUnknown: + return "type unknown" default: return "unknown type" } diff --git a/vendor/github.com/hashicorp/hil/ast/conditional.go b/vendor/github.com/hashicorp/hil/ast/conditional.go new file mode 100644 index 000000000..be48f89d4 --- /dev/null +++ b/vendor/github.com/hashicorp/hil/ast/conditional.go @@ -0,0 +1,36 @@ +package ast + +import ( + "fmt" +) + +type Conditional struct { + CondExpr Node + TrueExpr Node + FalseExpr Node + Posx Pos +} + +// Accept passes the given visitor to the child nodes in this order: +// CondExpr, TrueExpr, FalseExpr. It then finally passes itself to the visitor. +func (n *Conditional) Accept(v Visitor) Node { + n.CondExpr = n.CondExpr.Accept(v) + n.TrueExpr = n.TrueExpr.Accept(v) + n.FalseExpr = n.FalseExpr.Accept(v) + + return v(n) +} + +func (n *Conditional) Pos() Pos { + return n.Posx +} + +func (n *Conditional) Type(Scope) (Type, error) { + // This is not actually a useful value; the type checker ignores + // this function when analyzing conditionals, just as with Arithmetic. + return TypeInt, nil +} + +func (n *Conditional) GoString() string { + return fmt.Sprintf("*%#v", *n) +} diff --git a/vendor/github.com/hashicorp/hil/ast/index.go b/vendor/github.com/hashicorp/hil/ast/index.go index 49a3b9c30..860c25fd2 100644 --- a/vendor/github.com/hashicorp/hil/ast/index.go +++ b/vendor/github.com/hashicorp/hil/ast/index.go @@ -13,6 +13,8 @@ type Index struct { } func (n *Index) Accept(v Visitor) Node { + n.Target = n.Target.Accept(v) + n.Key = n.Key.Accept(v) return v(n) } diff --git a/vendor/github.com/hashicorp/hil/ast/literal.go b/vendor/github.com/hashicorp/hil/ast/literal.go index 1714ff026..da6014fee 100644 --- a/vendor/github.com/hashicorp/hil/ast/literal.go +++ b/vendor/github.com/hashicorp/hil/ast/literal.go @@ -2,6 +2,7 @@ package ast import ( "fmt" + "reflect" ) // LiteralNode represents a single literal value, such as "foo" or @@ -12,6 +13,51 @@ type LiteralNode struct { Posx Pos } +// NewLiteralNode returns a new literal node representing the given +// literal Go value, which must correspond to one of the primitive types +// supported by HIL. Lists and maps cannot currently be constructed via +// this function. +// +// If an inappropriately-typed value is provided, this function will +// return an error. The main intended use of this function is to produce +// "synthetic" literals from constants in code, where the value type is +// well known at compile time. To easily store these in global variables, +// see also MustNewLiteralNode. +func NewLiteralNode(value interface{}, pos Pos) (*LiteralNode, error) { + goType := reflect.TypeOf(value) + var hilType Type + + switch goType.Kind() { + case reflect.Bool: + hilType = TypeBool + case reflect.Int: + hilType = TypeInt + case reflect.Float64: + hilType = TypeFloat + case reflect.String: + hilType = TypeString + default: + return nil, fmt.Errorf("unsupported literal node type: %T", value) + } + + return &LiteralNode{ + Value: value, + Typex: hilType, + Posx: pos, + }, nil +} + +// MustNewLiteralNode wraps NewLiteralNode and panics if an error is +// returned, thus allowing valid literal nodes to be easily assigned to +// global variables. +func MustNewLiteralNode(value interface{}, pos Pos) *LiteralNode { + node, err := NewLiteralNode(value, pos) + if err != nil { + panic(err) + } + return node +} + func (n *LiteralNode) Accept(v Visitor) Node { return v(n) } @@ -31,3 +77,12 @@ func (n *LiteralNode) String() string { func (n *LiteralNode) Type(Scope) (Type, error) { return n.Typex, nil } + +// IsUnknown returns true either if the node's value is itself unknown +// of if it is a collection containing any unknown elements, deeply. +func (n *LiteralNode) IsUnknown() bool { + return IsUnknown(Variable{ + Type: n.Typex, + Value: n.Value, + }) +} diff --git a/vendor/github.com/hashicorp/hil/ast/type_string.go b/vendor/github.com/hashicorp/hil/ast/type_string.go index 11793ea59..1f51a98dd 100644 --- a/vendor/github.com/hashicorp/hil/ast/type_string.go +++ b/vendor/github.com/hashicorp/hil/ast/type_string.go @@ -7,21 +7,25 @@ import "fmt" const ( _Type_name_0 = "TypeInvalid" _Type_name_1 = "TypeAny" - _Type_name_2 = "TypeString" - _Type_name_3 = "TypeInt" - _Type_name_4 = "TypeFloat" - _Type_name_5 = "TypeList" - _Type_name_6 = "TypeMap" + _Type_name_2 = "TypeBool" + _Type_name_3 = "TypeString" + _Type_name_4 = "TypeInt" + _Type_name_5 = "TypeFloat" + _Type_name_6 = "TypeList" + _Type_name_7 = "TypeMap" + _Type_name_8 = "TypeUnknown" ) var ( _Type_index_0 = [...]uint8{0, 11} _Type_index_1 = [...]uint8{0, 7} - _Type_index_2 = [...]uint8{0, 10} - _Type_index_3 = [...]uint8{0, 7} - _Type_index_4 = [...]uint8{0, 9} - _Type_index_5 = [...]uint8{0, 8} - _Type_index_6 = [...]uint8{0, 7} + _Type_index_2 = [...]uint8{0, 8} + _Type_index_3 = [...]uint8{0, 10} + _Type_index_4 = [...]uint8{0, 7} + _Type_index_5 = [...]uint8{0, 9} + _Type_index_6 = [...]uint8{0, 8} + _Type_index_7 = [...]uint8{0, 7} + _Type_index_8 = [...]uint8{0, 11} ) func (i Type) String() string { @@ -40,6 +44,10 @@ func (i Type) String() string { return _Type_name_5 case i == 64: return _Type_name_6 + case i == 128: + return _Type_name_7 + case i == 256: + return _Type_name_8 default: return fmt.Sprintf("Type(%d)", i) } diff --git a/vendor/github.com/hashicorp/hil/ast/unknown.go b/vendor/github.com/hashicorp/hil/ast/unknown.go new file mode 100644 index 000000000..d6ddaecc7 --- /dev/null +++ b/vendor/github.com/hashicorp/hil/ast/unknown.go @@ -0,0 +1,30 @@ +package ast + +// IsUnknown reports whether a variable is unknown or contains any value +// that is unknown. This will recurse into lists and maps and so on. +func IsUnknown(v Variable) bool { + // If it is unknown itself, return true + if v.Type == TypeUnknown { + return true + } + + // If it is a container type, check the values + switch v.Type { + case TypeList: + for _, el := range v.Value.([]Variable) { + if IsUnknown(el) { + return true + } + } + case TypeMap: + for _, el := range v.Value.(map[string]Variable) { + if IsUnknown(el) { + return true + } + } + default: + } + + // Not a container type or survive the above checks + return false +} diff --git a/vendor/github.com/hashicorp/hil/ast/variables_helper.go b/vendor/github.com/hashicorp/hil/ast/variables_helper.go index 4b3284198..06bd18de2 100644 --- a/vendor/github.com/hashicorp/hil/ast/variables_helper.go +++ b/vendor/github.com/hashicorp/hil/ast/variables_helper.go @@ -3,43 +3,61 @@ package ast import "fmt" func VariableListElementTypesAreHomogenous(variableName string, list []Variable) (Type, error) { - listTypes := make(map[Type]struct{}) + if len(list) == 0 { + return TypeInvalid, fmt.Errorf("list %q does not have any elements so cannot determine type.", variableName) + } + + elemType := TypeUnknown for _, v := range list { - if _, ok := listTypes[v.Type]; ok { + if v.Type == TypeUnknown { continue } - listTypes[v.Type] = struct{}{} + + if elemType == TypeUnknown { + elemType = v.Type + continue + } + + if v.Type != elemType { + return TypeInvalid, fmt.Errorf( + "list %q does not have homogenous types. found %s and then %s", + variableName, + elemType, v.Type, + ) + } + + elemType = v.Type } - if len(listTypes) != 1 && len(list) != 0 { - return TypeInvalid, fmt.Errorf("list %q does not have homogenous types. found %s", variableName, reportTypes(listTypes)) - } - - if len(list) > 0 { - return list[0].Type, nil - } - - return TypeInvalid, fmt.Errorf("list %q does not have any elements so cannot determine type.", variableName) + return elemType, nil } func VariableMapValueTypesAreHomogenous(variableName string, vmap map[string]Variable) (Type, error) { - valueTypes := make(map[Type]struct{}) + if len(vmap) == 0 { + return TypeInvalid, fmt.Errorf("map %q does not have any elements so cannot determine type.", variableName) + } + + elemType := TypeUnknown for _, v := range vmap { - if _, ok := valueTypes[v.Type]; ok { + if v.Type == TypeUnknown { continue } - valueTypes[v.Type] = struct{}{} + + if elemType == TypeUnknown { + elemType = v.Type + continue + } + + if v.Type != elemType { + return TypeInvalid, fmt.Errorf( + "map %q does not have homogenous types. found %s and then %s", + variableName, + elemType, v.Type, + ) + } + + elemType = v.Type } - if len(valueTypes) != 1 && len(vmap) != 0 { - return TypeInvalid, fmt.Errorf("map %q does not have homogenous value types. found %s", variableName, reportTypes(valueTypes)) - } - - // For loop here is an easy way to get a single key, we return immediately. - for _, v := range vmap { - return v.Type, nil - } - - // This means the map is empty - return TypeInvalid, fmt.Errorf("map %q does not have any elements so cannot determine type.", variableName) + return elemType, nil } diff --git a/vendor/github.com/hashicorp/hil/builtins.go b/vendor/github.com/hashicorp/hil/builtins.go index 8c8a34803..909c788a2 100644 --- a/vendor/github.com/hashicorp/hil/builtins.go +++ b/vendor/github.com/hashicorp/hil/builtins.go @@ -1,6 +1,7 @@ package hil import ( + "errors" "strconv" "github.com/hashicorp/hil/ast" @@ -17,16 +18,23 @@ func registerBuiltins(scope *ast.BasicScope) *ast.BasicScope { } // Implicit conversions + scope.FuncMap["__builtin_BoolToString"] = builtinBoolToString() scope.FuncMap["__builtin_FloatToInt"] = builtinFloatToInt() scope.FuncMap["__builtin_FloatToString"] = builtinFloatToString() scope.FuncMap["__builtin_IntToFloat"] = builtinIntToFloat() scope.FuncMap["__builtin_IntToString"] = builtinIntToString() scope.FuncMap["__builtin_StringToInt"] = builtinStringToInt() scope.FuncMap["__builtin_StringToFloat"] = builtinStringToFloat() + scope.FuncMap["__builtin_StringToBool"] = builtinStringToBool() // Math operations scope.FuncMap["__builtin_IntMath"] = builtinIntMath() scope.FuncMap["__builtin_FloatMath"] = builtinFloatMath() + scope.FuncMap["__builtin_BoolCompare"] = builtinBoolCompare() + scope.FuncMap["__builtin_FloatCompare"] = builtinFloatCompare() + scope.FuncMap["__builtin_IntCompare"] = builtinIntCompare() + scope.FuncMap["__builtin_StringCompare"] = builtinStringCompare() + scope.FuncMap["__builtin_Logical"] = builtinLogical() return scope } @@ -77,8 +85,16 @@ func builtinIntMath() ast.Function { case ast.ArithmeticOpMul: result *= arg case ast.ArithmeticOpDiv: + if arg == 0 { + return nil, errors.New("divide by zero") + } + result /= arg case ast.ArithmeticOpMod: + if arg == 0 { + return nil, errors.New("divide by zero") + } + result = result % arg } } @@ -88,6 +104,136 @@ func builtinIntMath() ast.Function { } } +func builtinBoolCompare() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeInt, ast.TypeBool, ast.TypeBool}, + Variadic: false, + ReturnType: ast.TypeBool, + Callback: func(args []interface{}) (interface{}, error) { + op := args[0].(ast.ArithmeticOp) + lhs := args[1].(bool) + rhs := args[2].(bool) + + switch op { + case ast.ArithmeticOpEqual: + return lhs == rhs, nil + case ast.ArithmeticOpNotEqual: + return lhs != rhs, nil + default: + return nil, errors.New("invalid comparison operation") + } + }, + } +} + +func builtinFloatCompare() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeInt, ast.TypeFloat, ast.TypeFloat}, + Variadic: false, + ReturnType: ast.TypeBool, + Callback: func(args []interface{}) (interface{}, error) { + op := args[0].(ast.ArithmeticOp) + lhs := args[1].(float64) + rhs := args[2].(float64) + + switch op { + case ast.ArithmeticOpEqual: + return lhs == rhs, nil + case ast.ArithmeticOpNotEqual: + return lhs != rhs, nil + case ast.ArithmeticOpLessThan: + return lhs < rhs, nil + case ast.ArithmeticOpLessThanOrEqual: + return lhs <= rhs, nil + case ast.ArithmeticOpGreaterThan: + return lhs > rhs, nil + case ast.ArithmeticOpGreaterThanOrEqual: + return lhs >= rhs, nil + default: + return nil, errors.New("invalid comparison operation") + } + }, + } +} + +func builtinIntCompare() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeInt, ast.TypeInt, ast.TypeInt}, + Variadic: false, + ReturnType: ast.TypeBool, + Callback: func(args []interface{}) (interface{}, error) { + op := args[0].(ast.ArithmeticOp) + lhs := args[1].(int) + rhs := args[2].(int) + + switch op { + case ast.ArithmeticOpEqual: + return lhs == rhs, nil + case ast.ArithmeticOpNotEqual: + return lhs != rhs, nil + case ast.ArithmeticOpLessThan: + return lhs < rhs, nil + case ast.ArithmeticOpLessThanOrEqual: + return lhs <= rhs, nil + case ast.ArithmeticOpGreaterThan: + return lhs > rhs, nil + case ast.ArithmeticOpGreaterThanOrEqual: + return lhs >= rhs, nil + default: + return nil, errors.New("invalid comparison operation") + } + }, + } +} + +func builtinStringCompare() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeInt, ast.TypeString, ast.TypeString}, + Variadic: false, + ReturnType: ast.TypeBool, + Callback: func(args []interface{}) (interface{}, error) { + op := args[0].(ast.ArithmeticOp) + lhs := args[1].(string) + rhs := args[2].(string) + + switch op { + case ast.ArithmeticOpEqual: + return lhs == rhs, nil + case ast.ArithmeticOpNotEqual: + return lhs != rhs, nil + default: + return nil, errors.New("invalid comparison operation") + } + }, + } +} + +func builtinLogical() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeInt}, + Variadic: true, + VariadicType: ast.TypeBool, + ReturnType: ast.TypeBool, + Callback: func(args []interface{}) (interface{}, error) { + op := args[0].(ast.ArithmeticOp) + result := args[1].(bool) + for _, raw := range args[2:] { + arg := raw.(bool) + switch op { + case ast.ArithmeticOpLogicalOr: + result = result || arg + case ast.ArithmeticOpLogicalAnd: + result = result && arg + default: + return nil, errors.New("invalid logical operator") + } + } + + return result, nil + }, + } +} + func builtinFloatToInt() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeFloat}, @@ -158,3 +304,28 @@ func builtinStringToFloat() ast.Function { }, } } + +func builtinBoolToString() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeBool}, + ReturnType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + return strconv.FormatBool(args[0].(bool)), nil + }, + } +} + +func builtinStringToBool() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeString}, + ReturnType: ast.TypeBool, + Callback: func(args []interface{}) (interface{}, error) { + v, err := strconv.ParseBool(args[0].(string)) + if err != nil { + return nil, err + } + + return v, nil + }, + } +} diff --git a/vendor/github.com/hashicorp/hil/check_types.go b/vendor/github.com/hashicorp/hil/check_types.go index 4b35d1142..f16da3918 100644 --- a/vendor/github.com/hashicorp/hil/check_types.go +++ b/vendor/github.com/hashicorp/hil/check_types.go @@ -44,6 +44,12 @@ func (v *TypeCheck) Visit(root ast.Node) error { defer v.lock.Unlock() defer v.reset() root.Accept(v.visit) + + // If the resulting type is unknown, then just let the whole thing go. + if v.err == errExitUnknown { + v.err = nil + } + return v.err } @@ -61,6 +67,9 @@ func (v *TypeCheck) visit(raw ast.Node) ast.Node { case *ast.Call: tc := &typeCheckCall{n} result, err = tc.TypeCheck(v) + case *ast.Conditional: + tc := &typeCheckConditional{n} + result, err = tc.TypeCheck(v) case *ast.Index: tc := &typeCheckIndex{n} result, err = tc.TypeCheck(v) @@ -103,6 +112,28 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) { exprs[len(tc.n.Exprs)-1-i] = v.StackPop() } + // If any operand is unknown then our result is automatically unknown + for _, ty := range exprs { + if ty == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + } + + switch tc.n.Op { + case ast.ArithmeticOpLogicalAnd, ast.ArithmeticOpLogicalOr: + return tc.checkLogical(v, exprs) + case ast.ArithmeticOpEqual, ast.ArithmeticOpNotEqual, + ast.ArithmeticOpLessThan, ast.ArithmeticOpGreaterThan, + ast.ArithmeticOpGreaterThanOrEqual, ast.ArithmeticOpLessThanOrEqual: + return tc.checkComparison(v, exprs) + default: + return tc.checkNumeric(v, exprs) + } + +} + +func (tc *typeCheckArithmetic) checkNumeric(v *TypeCheck, exprs []ast.Type) (ast.Node, error) { // Determine the resulting type we want. We do this by going over // every expression until we find one with a type we recognize. // We do this because the first expr might be a string ("var.foo") @@ -110,20 +141,11 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) { mathFunc := "__builtin_IntMath" mathType := ast.TypeInt for _, v := range exprs { - exit := true - switch v { - case ast.TypeInt: - mathFunc = "__builtin_IntMath" - mathType = v - case ast.TypeFloat: + // We assume int math but if we find ANY float, the entire + // expression turns into floating point math. + if v == ast.TypeFloat { mathFunc = "__builtin_FloatMath" mathType = v - default: - exit = false - } - - // We found the type, so leave - if exit { break } } @@ -167,6 +189,131 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) { }, nil } +func (tc *typeCheckArithmetic) checkComparison(v *TypeCheck, exprs []ast.Type) (ast.Node, error) { + if len(exprs) != 2 { + // This should never happen, because the parser never produces + // nodes that violate this. + return nil, fmt.Errorf( + "comparison operators must have exactly two operands", + ) + } + + // The first operand always dictates the type for a comparison. + compareFunc := "" + compareType := exprs[0] + switch compareType { + case ast.TypeBool: + compareFunc = "__builtin_BoolCompare" + case ast.TypeFloat: + compareFunc = "__builtin_FloatCompare" + case ast.TypeInt: + compareFunc = "__builtin_IntCompare" + case ast.TypeString: + compareFunc = "__builtin_StringCompare" + default: + return nil, fmt.Errorf( + "comparison operators apply only to bool, float, int, and string", + ) + } + + // For non-equality comparisons, we will do implicit conversions to + // integer types if possible. In this case, we need to go through and + // determine the type of comparison we're doing to enable the implicit + // conversion. + if tc.n.Op != ast.ArithmeticOpEqual && tc.n.Op != ast.ArithmeticOpNotEqual { + compareFunc = "__builtin_IntCompare" + compareType = ast.TypeInt + for _, expr := range exprs { + if expr == ast.TypeFloat { + compareFunc = "__builtin_FloatCompare" + compareType = ast.TypeFloat + break + } + } + } + + // Verify (and possibly, convert) the args + for i, arg := range exprs { + if arg != compareType { + cn := v.ImplicitConversion(exprs[i], compareType, tc.n.Exprs[i]) + if cn != nil { + tc.n.Exprs[i] = cn + continue + } + + return nil, fmt.Errorf( + "operand %d should be %s, got %s", + i+1, compareType, arg, + ) + } + } + + // Only ints and floats can have the <, >, <= and >= operators applied + switch tc.n.Op { + case ast.ArithmeticOpEqual, ast.ArithmeticOpNotEqual: + // anything goes + default: + switch compareType { + case ast.TypeFloat, ast.TypeInt: + // fine + default: + return nil, fmt.Errorf( + "<, >, <= and >= may apply only to int and float values", + ) + } + } + + // Comparison operators always return bool + v.StackPush(ast.TypeBool) + + // Replace our node with a call to the proper function. This isn't + // type checked but we already verified types. + args := make([]ast.Node, len(tc.n.Exprs)+1) + args[0] = &ast.LiteralNode{ + Value: tc.n.Op, + Typex: ast.TypeInt, + Posx: tc.n.Pos(), + } + copy(args[1:], tc.n.Exprs) + return &ast.Call{ + Func: compareFunc, + Args: args, + Posx: tc.n.Pos(), + }, nil +} + +func (tc *typeCheckArithmetic) checkLogical(v *TypeCheck, exprs []ast.Type) (ast.Node, error) { + for i, t := range exprs { + if t != ast.TypeBool { + cn := v.ImplicitConversion(t, ast.TypeBool, tc.n.Exprs[i]) + if cn == nil { + return nil, fmt.Errorf( + "logical operators require boolean operands, not %s", + t, + ) + } + tc.n.Exprs[i] = cn + } + } + + // Return type is always boolean + v.StackPush(ast.TypeBool) + + // Arithmetic nodes are replaced with a call to a built-in function + args := make([]ast.Node, len(tc.n.Exprs)+1) + args[0] = &ast.LiteralNode{ + Value: tc.n.Op, + Typex: ast.TypeInt, + Posx: tc.n.Pos(), + } + copy(args[1:], tc.n.Exprs) + return &ast.Call{ + Func: "__builtin_Logical", + Args: args, + Posx: tc.n.Pos(), + }, nil +} + type typeCheckCall struct { n *ast.Call } @@ -190,6 +337,11 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) { continue } + if args[i] == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + if args[i] != expected { cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i]) if cn != nil { @@ -207,6 +359,11 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) { if function.Variadic && function.VariadicType != ast.TypeAny { args = args[len(function.ArgTypes):] for i, t := range args { + if t == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + if t != function.VariadicType { realI := i + len(function.ArgTypes) cn := v.ImplicitConversion( @@ -230,6 +387,90 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) { return tc.n, nil } +type typeCheckConditional struct { + n *ast.Conditional +} + +func (tc *typeCheckConditional) TypeCheck(v *TypeCheck) (ast.Node, error) { + // On the stack we have the types of the condition, true and false + // expressions, but they are in reverse order. + falseType := v.StackPop() + trueType := v.StackPop() + condType := v.StackPop() + + if condType == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + + if condType != ast.TypeBool { + cn := v.ImplicitConversion(condType, ast.TypeBool, tc.n.CondExpr) + if cn == nil { + return nil, fmt.Errorf( + "condition must be type bool, not %s", condType.Printable(), + ) + } + tc.n.CondExpr = cn + } + + // The types of the true and false expression must match + if trueType != falseType && trueType != ast.TypeUnknown && falseType != ast.TypeUnknown { + + // Since passing around stringified versions of other types is + // common, we pragmatically allow the false expression to dictate + // the result type when the true expression is a string. + if trueType == ast.TypeString { + cn := v.ImplicitConversion(trueType, falseType, tc.n.TrueExpr) + if cn == nil { + return nil, fmt.Errorf( + "true and false expression types must match; have %s and %s", + trueType.Printable(), falseType.Printable(), + ) + } + tc.n.TrueExpr = cn + trueType = falseType + } else { + cn := v.ImplicitConversion(falseType, trueType, tc.n.FalseExpr) + if cn == nil { + return nil, fmt.Errorf( + "true and false expression types must match; have %s and %s", + trueType.Printable(), falseType.Printable(), + ) + } + tc.n.FalseExpr = cn + falseType = trueType + } + } + + // Currently list and map types cannot be used, because we cannot + // generally assert that their element types are consistent. + // Such support might be added later, either by improving the type + // system or restricting usage to only variable and literal expressions, + // but for now this is simply prohibited because it doesn't seem to + // be a common enough case to be worth the complexity. + switch trueType { + case ast.TypeList: + return nil, fmt.Errorf( + "conditional operator cannot be used with list values", + ) + case ast.TypeMap: + return nil, fmt.Errorf( + "conditional operator cannot be used with map values", + ) + } + + // Result type (guaranteed to also match falseType due to the above) + if trueType == ast.TypeUnknown { + // falseType may also be unknown, but that's okay because two + // unknowns means our result is unknown anyway. + v.StackPush(falseType) + } else { + v.StackPush(trueType) + } + + return tc.n, nil +} + type typeCheckOutput struct { n *ast.Output } @@ -241,20 +482,33 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) { types[len(n.Exprs)-1-i] = v.StackPop() } - // If there is only one argument and it is a list, we evaluate to a list - if len(types) == 1 && types[0] == ast.TypeList { - v.StackPush(ast.TypeList) - return n, nil + for _, ty := range types { + if ty == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.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 + // If there is only one argument and it is a list, we evaluate to a list + if len(types) == 1 { + switch t := types[0]; t { + case ast.TypeList: + fallthrough + case ast.TypeMap: + v.StackPush(t) + return n, nil + } } // Otherwise, all concat args must be strings, so validate that + resultType := ast.TypeString for i, t := range types { + + if t == ast.TypeUnknown { + resultType = ast.TypeUnknown + continue + } + if t != ast.TypeString { cn := v.ImplicitConversion(t, ast.TypeString, n.Exprs[i]) if cn != nil { @@ -267,8 +521,8 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) { } } - // This always results in type string - v.StackPush(ast.TypeString) + // This always results in type string, unless there are unknowns + v.StackPush(resultType) return n, nil } @@ -305,30 +559,40 @@ type typeCheckIndex struct { } func (tc *typeCheckIndex) TypeCheck(v *TypeCheck) (ast.Node, error) { + keyType := v.StackPop() + targetType := v.StackPop() + + if keyType == ast.TypeUnknown || targetType == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + // Ensure we have a VariableAccess as the target varAccessNode, ok := tc.n.Target.(*ast.VariableAccess) if !ok { - return nil, fmt.Errorf("target of an index must be a VariableAccess node, was %T", tc.n.Target) + return nil, fmt.Errorf( + "target of an index must be a VariableAccess node, was %T", tc.n.Target) } // Get the variable variable, ok := v.Scope.LookupVar(varAccessNode.Name) if !ok { - return nil, fmt.Errorf("unknown variable accessed: %s", varAccessNode.Name) + return nil, fmt.Errorf( + "unknown variable accessed: %s", varAccessNode.Name) } - keyType, err := tc.n.Key.Type(v.Scope) - if err != nil { - return nil, err - } - - switch variable.Type { + switch targetType { case ast.TypeList: if keyType != ast.TypeInt { - return nil, fmt.Errorf("key of an index must be an int, was %s", keyType) + tc.n.Key = v.ImplicitConversion(keyType, ast.TypeInt, tc.n.Key) + if tc.n.Key == nil { + return nil, fmt.Errorf( + "key of an index must be an int, was %s", keyType) + } } - valType, err := ast.VariableListElementTypesAreHomogenous(varAccessNode.Name, variable.Value.([]ast.Variable)) + valType, err := ast.VariableListElementTypesAreHomogenous( + varAccessNode.Name, variable.Value.([]ast.Variable)) if err != nil { return tc.n, err } @@ -337,10 +601,15 @@ func (tc *typeCheckIndex) TypeCheck(v *TypeCheck) (ast.Node, error) { return tc.n, nil case ast.TypeMap: if keyType != ast.TypeString { - return nil, fmt.Errorf("key of an index must be a string, was %s", keyType) + tc.n.Key = v.ImplicitConversion(keyType, ast.TypeString, tc.n.Key) + if tc.n.Key == nil { + return nil, fmt.Errorf( + "key of an index must be a string, was %s", keyType) + } } - valType, err := ast.VariableMapValueTypesAreHomogenous(varAccessNode.Name, variable.Value.(map[string]ast.Variable)) + valType, err := ast.VariableMapValueTypesAreHomogenous( + varAccessNode.Name, variable.Value.(map[string]ast.Variable)) if err != nil { return tc.n, err } @@ -389,3 +658,11 @@ func (v *TypeCheck) StackPop() ast.Type { x, v.Stack = v.Stack[len(v.Stack)-1], v.Stack[:len(v.Stack)-1] return x } + +func (v *TypeCheck) StackPeek() ast.Type { + if len(v.Stack) == 0 { + return ast.TypeInvalid + } + + return v.Stack[len(v.Stack)-1] +} diff --git a/vendor/github.com/hashicorp/hil/convert.go b/vendor/github.com/hashicorp/hil/convert.go index 3841d1fb3..184e029b0 100644 --- a/vendor/github.com/hashicorp/hil/convert.go +++ b/vendor/github.com/hashicorp/hil/convert.go @@ -8,6 +8,11 @@ import ( "github.com/mitchellh/mapstructure" ) +// UnknownValue is a sentinel value that can be used to denote +// that a value of a variable (or map element, list element, etc.) +// is unknown. This will always have the type ast.TypeUnknown. +const UnknownValue = "74D93920-ED26-11E3-AC10-0800200C9A66" + var hilMapstructureDecodeHookSlice []interface{} var hilMapstructureDecodeHookStringSlice []string var hilMapstructureDecodeHookMap map[string]interface{} @@ -42,12 +47,33 @@ func hilMapstructureWeakDecode(m interface{}, rawVal interface{}) error { } func InterfaceToVariable(input interface{}) (ast.Variable, error) { - if inputVariable, ok := input.(ast.Variable); ok { - return inputVariable, nil + if iv, ok := input.(ast.Variable); ok { + return iv, nil + } + + // This is just to maintain backward compatibility + // after https://github.com/mitchellh/mapstructure/pull/98 + if v, ok := input.([]ast.Variable); ok { + return ast.Variable{ + Type: ast.TypeList, + Value: v, + }, nil + } + if v, ok := input.(map[string]ast.Variable); ok { + return ast.Variable{ + Type: ast.TypeMap, + Value: v, + }, nil } var stringVal string if err := hilMapstructureWeakDecode(input, &stringVal); err == nil { + // Special case the unknown value to turn into "unknown" + if stringVal == UnknownValue { + return ast.Variable{Value: UnknownValue, Type: ast.TypeUnknown}, nil + } + + // Otherwise return the string value return ast.Variable{ Type: ast.TypeString, Value: stringVal, diff --git a/vendor/github.com/hashicorp/hil/eval.go b/vendor/github.com/hashicorp/hil/eval.go index 173c67f5a..27820769e 100644 --- a/vendor/github.com/hashicorp/hil/eval.go +++ b/vendor/github.com/hashicorp/hil/eval.go @@ -2,6 +2,7 @@ package hil import ( "bytes" + "errors" "fmt" "sync" @@ -23,19 +24,6 @@ type EvalConfig struct { // 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 @@ -45,6 +33,7 @@ const ( // TypeString: string // TypeList: []interface{} // TypeMap: map[string]interface{} +// TypBool: bool type EvaluationResult struct { Type EvalType Value interface{} @@ -55,12 +44,24 @@ type EvaluationResult struct { // The error is described out of band in the accompanying error return value. var InvalidResult = EvaluationResult{Type: TypeInvalid, Value: nil} +// errExitUnknown is an internal error that when returned means the result +// is an unknown value. We use this for early exit. +var errExitUnknown = errors.New("unknown value") + func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) { output, outputType, err := internalEval(root, config) if err != nil { return InvalidResult, err } + // If the result contains any nested unknowns then the result as a whole + // is unknown, so that callers only have to deal with "entirely known" + // or "entirely unknown" as outcomes. + if ast.IsUnknown(ast.Variable{Type: outputType, Value: output}) { + outputType = ast.TypeUnknown + output = UnknownValue + } + switch outputType { case ast.TypeList: val, err := VariableToInterface(ast.Variable{ @@ -77,7 +78,7 @@ func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) { Value: output, }) return EvaluationResult{ - Type: TypeMap, + Type: TypeMap, Value: val, }, err case ast.TypeString: @@ -85,6 +86,16 @@ func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) { Type: TypeString, Value: output, }, nil + case ast.TypeBool: + return EvaluationResult{ + Type: TypeBool, + Value: output, + }, nil + case ast.TypeUnknown: + return EvaluationResult{ + Type: TypeUnknown, + Value: UnknownValue, + }, nil default: return InvalidResult, fmt.Errorf("unknown type %s as interpolation output", outputType) } @@ -110,6 +121,10 @@ func internalEval(root ast.Node, config *EvalConfig) (interface{}, ast.Type, err ast.TypeString: { ast.TypeInt: "__builtin_StringToInt", ast.TypeFloat: "__builtin_StringToFloat", + ast.TypeBool: "__builtin_StringToBool", + }, + ast.TypeBool: { + ast.TypeString: "__builtin_BoolToString", }, } @@ -167,6 +182,12 @@ func (v *evalVisitor) Visit(root ast.Node) (interface{}, ast.Type, error) { result = new(ast.LiteralNode) } resultErr := v.err + if resultErr == errExitUnknown { + // This means the return value is unknown and we used the error + // as an early exit mechanism. Reset since the value on the stack + // should be the unknown value. + resultErr = nil + } // Clear everything else so we aren't just dangling v.Stack.Reset() @@ -201,6 +222,13 @@ func (v *evalVisitor) visit(raw ast.Node) ast.Node { Value: out, Typex: outType, }) + + if outType == ast.TypeUnknown { + // Halt immediately + v.err = errExitUnknown + return raw + } + return raw } @@ -212,6 +240,8 @@ func evalNode(raw ast.Node) (EvalNode, error) { return &evalIndex{n}, nil case *ast.Call: return &evalCall{n}, nil + case *ast.Conditional: + return &evalConditional{n}, nil case *ast.Output: return &evalOutput{n}, nil case *ast.LiteralNode: @@ -242,6 +272,10 @@ func (v *evalCall) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, e args := make([]interface{}, len(v.Args)) for i, _ := range v.Args { node := stack.Pop().(*ast.LiteralNode) + if node.IsUnknown() { + // If any arguments are unknown then the result is automatically unknown + return UnknownValue, ast.TypeUnknown, nil + } args[len(v.Args)-1-i] = node.Value } @@ -254,42 +288,56 @@ func (v *evalCall) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, e return result, function.ReturnType, nil } +type evalConditional struct{ *ast.Conditional } + +func (v *evalConditional) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { + // On the stack we have literal nodes representing the resulting values + // of the condition, true and false expressions, but they are in reverse + // order. + falseLit := stack.Pop().(*ast.LiteralNode) + trueLit := stack.Pop().(*ast.LiteralNode) + condLit := stack.Pop().(*ast.LiteralNode) + + if condLit.IsUnknown() { + // If our conditional is unknown then our result is also unknown + return UnknownValue, ast.TypeUnknown, nil + } + + if condLit.Value.(bool) { + return trueLit.Value, trueLit.Typex, nil + } else { + return falseLit.Value, trueLit.Typex, nil + } +} + type evalIndex struct{ *ast.Index } func (v *evalIndex) Eval(scope ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { - evalVarAccess, err := evalNode(v.Target) - if err != nil { - return nil, ast.TypeInvalid, err - } - target, targetType, err := evalVarAccess.Eval(scope, stack) - - evalKey, err := evalNode(v.Key) - if err != nil { - return nil, ast.TypeInvalid, err - } - - key, keyType, err := evalKey.Eval(scope, stack) - if err != nil { - return nil, ast.TypeInvalid, err - } + key := stack.Pop().(*ast.LiteralNode) + target := stack.Pop().(*ast.LiteralNode) variableName := v.Index.Target.(*ast.VariableAccess).Name - switch targetType { + if key.IsUnknown() { + // If our key is unknown then our result is also unknown + return UnknownValue, ast.TypeUnknown, nil + } + + // For target, we'll accept collections containing unknown values but + // we still need to catch when the collection itself is unknown, shallowly. + if target.Typex == ast.TypeUnknown { + return UnknownValue, ast.TypeUnknown, nil + } + + switch target.Typex { case ast.TypeList: - if keyType != ast.TypeInt { - return nil, ast.TypeInvalid, fmt.Errorf("key for indexing list %q must be an int, is %s", variableName, keyType) - } - - return v.evalListIndex(variableName, target, key) + return v.evalListIndex(variableName, target.Value, key.Value) case ast.TypeMap: - if keyType != ast.TypeString { - return nil, ast.TypeInvalid, fmt.Errorf("key for indexing map %q must be a string, is %s", variableName, keyType) - } - - return v.evalMapIndex(variableName, target, key) + return v.evalMapIndex(variableName, target.Value, key.Value) default: - return nil, ast.TypeInvalid, fmt.Errorf("target %q for indexing must be ast.TypeList or ast.TypeMap, is %s", variableName, targetType) + return nil, ast.TypeInvalid, fmt.Errorf( + "target %q for indexing must be ast.TypeList or ast.TypeMap, is %s", + variableName, target.Typex) } } @@ -298,12 +346,14 @@ func (v *evalIndex) evalListIndex(variableName string, target interface{}, key i // is a list and key is an int list, ok := target.([]ast.Variable) if !ok { - return nil, ast.TypeInvalid, fmt.Errorf("cannot cast target to []Variable") + return nil, ast.TypeInvalid, fmt.Errorf( + "cannot cast target to []Variable, is: %T", target) } keyInt, ok := key.(int) if !ok { - return nil, ast.TypeInvalid, fmt.Errorf("cannot cast key to int") + return nil, ast.TypeInvalid, fmt.Errorf( + "cannot cast key to int, is: %T", key) } if len(list) == 0 { @@ -311,12 +361,13 @@ func (v *evalIndex) evalListIndex(variableName string, target interface{}, key i } if keyInt < 0 || len(list) < keyInt+1 { - return nil, ast.TypeInvalid, fmt.Errorf("index %d out of range for list %s (max %d)", keyInt, variableName, len(list)) + return nil, ast.TypeInvalid, fmt.Errorf( + "index %d out of range for list %s (max %d)", + keyInt, variableName, len(list)) } returnVal := list[keyInt].Value returnType := list[keyInt].Type - return returnVal, returnType, nil } @@ -325,12 +376,14 @@ func (v *evalIndex) evalMapIndex(variableName string, target interface{}, key in // is a map and key is a string vmap, ok := target.(map[string]ast.Variable) if !ok { - return nil, ast.TypeInvalid, fmt.Errorf("cannot cast target to map[string]Variable") + return nil, ast.TypeInvalid, fmt.Errorf( + "cannot cast target to map[string]Variable, is: %T", target) } keyString, ok := key.(string) if !ok { - return nil, ast.TypeInvalid, fmt.Errorf("cannot cast key to string") + return nil, ast.TypeInvalid, fmt.Errorf( + "cannot cast key to string, is: %T", key) } if len(vmap) == 0 { @@ -339,7 +392,8 @@ func (v *evalIndex) evalMapIndex(variableName string, target interface{}, key in value, ok := vmap[keyString] if !ok { - return nil, ast.TypeInvalid, fmt.Errorf("key %q does not exist in map %s", keyString, variableName) + return nil, ast.TypeInvalid, fmt.Errorf( + "key %q does not exist in map %s", keyString, variableName) } return value.Value, value.Type, nil @@ -351,21 +405,47 @@ func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, // The expressions should all be on the stack in reverse // order. So pop them off, reverse their order, and concatenate. nodes := make([]*ast.LiteralNode, 0, len(v.Exprs)) + haveUnknown := false for range v.Exprs { - nodes = append(nodes, stack.Pop().(*ast.LiteralNode)) + n := stack.Pop().(*ast.LiteralNode) + nodes = append(nodes, n) + + // If we have any unknowns then the whole result is unknown + // (we must deal with this first, because the type checker can + // skip type conversions in the presence of unknowns, and thus + // any of our other nodes may be incorrectly typed.) + if n.IsUnknown() { + haveUnknown = true + } + } + + if haveUnknown { + return UnknownValue, ast.TypeUnknown, nil } // Special case the single list and map - if len(nodes) == 1 && 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 + if len(nodes) == 1 { + switch t := nodes[0].Typex; t { + case ast.TypeList: + fallthrough + case ast.TypeMap: + fallthrough + case ast.TypeUnknown: + return nodes[0].Value, t, nil + } } // Otherwise concatenate the strings var buf bytes.Buffer for i := len(nodes) - 1; i >= 0; i-- { + if nodes[i].Typex != ast.TypeString { + return nil, ast.TypeInvalid, fmt.Errorf( + "invalid output with %s value at index %d: %#v", + nodes[i].Typex, + i, + nodes[i].Value, + ) + } buf.WriteString(nodes[i].Value.(string)) } diff --git a/vendor/github.com/hashicorp/hil/eval_type.go b/vendor/github.com/hashicorp/hil/eval_type.go new file mode 100644 index 000000000..6946ecd23 --- /dev/null +++ b/vendor/github.com/hashicorp/hil/eval_type.go @@ -0,0 +1,16 @@ +package hil + +//go:generate stringer -type=EvalType eval_type.go + +// EvalType represents the type of the output returned from a HIL +// evaluation. +type EvalType uint32 + +const ( + TypeInvalid EvalType = 0 + TypeString EvalType = 1 << iota + TypeBool + TypeList + TypeMap + TypeUnknown +) diff --git a/vendor/github.com/hashicorp/hil/evaltype_string.go b/vendor/github.com/hashicorp/hil/evaltype_string.go index 911ff30e1..b107ddd45 100644 --- a/vendor/github.com/hashicorp/hil/evaltype_string.go +++ b/vendor/github.com/hashicorp/hil/evaltype_string.go @@ -1,4 +1,4 @@ -// Code generated by "stringer -type=EvalType"; DO NOT EDIT +// Code generated by "stringer -type=EvalType eval_type.go"; DO NOT EDIT package hil @@ -7,15 +7,19 @@ import "fmt" const ( _EvalType_name_0 = "TypeInvalid" _EvalType_name_1 = "TypeString" - _EvalType_name_2 = "TypeList" - _EvalType_name_3 = "TypeMap" + _EvalType_name_2 = "TypeBool" + _EvalType_name_3 = "TypeList" + _EvalType_name_4 = "TypeMap" + _EvalType_name_5 = "TypeUnknown" ) 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} + _EvalType_index_3 = [...]uint8{0, 8} + _EvalType_index_4 = [...]uint8{0, 7} + _EvalType_index_5 = [...]uint8{0, 11} ) func (i EvalType) String() string { @@ -28,6 +32,10 @@ func (i EvalType) String() string { return _EvalType_name_2 case i == 8: return _EvalType_name_3 + case i == 16: + return _EvalType_name_4 + case i == 32: + return _EvalType_name_5 default: return fmt.Sprintf("EvalType(%d)", i) } diff --git a/vendor/github.com/hashicorp/hil/go.mod b/vendor/github.com/hashicorp/hil/go.mod new file mode 100644 index 000000000..45719a69b --- /dev/null +++ b/vendor/github.com/hashicorp/hil/go.mod @@ -0,0 +1,6 @@ +module github.com/hashicorp/hil + +require ( + github.com/mitchellh/mapstructure v1.1.2 + github.com/mitchellh/reflectwalk v1.0.0 +) diff --git a/vendor/github.com/hashicorp/hil/go.sum b/vendor/github.com/hashicorp/hil/go.sum new file mode 100644 index 000000000..83639b691 --- /dev/null +++ b/vendor/github.com/hashicorp/hil/go.sum @@ -0,0 +1,4 @@ +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= diff --git a/vendor/github.com/hashicorp/hil/lang.y b/vendor/github.com/hashicorp/hil/lang.y deleted file mode 100644 index 67a7dc2aa..000000000 --- a/vendor/github.com/hashicorp/hil/lang.y +++ /dev/null @@ -1,196 +0,0 @@ -// This is the yacc input for creating the parser for interpolation -// expressions in Go. To build it, just run `go generate` on this -// package, as the lexer has the go generate pragma within it. - -%{ -package hil - -import ( - "github.com/hashicorp/hil/ast" -) - -%} - -%union { - node ast.Node - nodeList []ast.Node - str string - token *parserToken -} - -%token PROGRAM_BRACKET_LEFT PROGRAM_BRACKET_RIGHT -%token PROGRAM_STRING_START PROGRAM_STRING_END -%token PAREN_LEFT PAREN_RIGHT COMMA -%token SQUARE_BRACKET_LEFT SQUARE_BRACKET_RIGHT - -%token ARITH_OP IDENTIFIER INTEGER FLOAT STRING - -%type expr interpolation literal literalModeTop literalModeValue -%type args - -%left ARITH_OP - -%% - -top: - { - parserResult = &ast.LiteralNode{ - Value: "", - Typex: ast.TypeString, - Posx: ast.Pos{Column: 1, Line: 1}, - } - } -| literalModeTop - { - parserResult = $1 - - // 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.Output); !ok { - if n, ok := $1.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString { - parserResult = &ast.Output{ - Exprs: []ast.Node{$1}, - Posx: $1.Pos(), - } - } - } - } - -literalModeTop: - literalModeValue - { - $$ = $1 - } -| literalModeTop literalModeValue - { - var result []ast.Node - if c, ok := $1.(*ast.Output); ok { - result = append(c.Exprs, $2) - } else { - result = []ast.Node{$1, $2} - } - - $$ = &ast.Output{ - Exprs: result, - Posx: result[0].Pos(), - } - } - -literalModeValue: - literal - { - $$ = $1 - } -| interpolation - { - $$ = $1 - } - -interpolation: - PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT - { - $$ = $2 - } - -expr: - PAREN_LEFT expr PAREN_RIGHT - { - $$ = $2 - } -| literalModeTop - { - $$ = $1 - } -| INTEGER - { - $$ = &ast.LiteralNode{ - Value: $1.Value.(int), - Typex: ast.TypeInt, - Posx: $1.Pos, - } - } -| FLOAT - { - $$ = &ast.LiteralNode{ - Value: $1.Value.(float64), - Typex: ast.TypeFloat, - Posx: $1.Pos, - } - } -| ARITH_OP expr - { - // This is REALLY jank. We assume that a singular ARITH_OP - // means 0 ARITH_OP expr, which... is weird. We don't want to - // support *, /, etc., only -. We should fix this later with a pure - // Go scanner/parser. - if $1.Value.(ast.ArithmeticOp) != ast.ArithmeticOpSub { - panic("Unary - is only allowed") - } - - $$ = &ast.Arithmetic{ - Op: $1.Value.(ast.ArithmeticOp), - Exprs: []ast.Node{ - &ast.LiteralNode{Value: 0, Typex: ast.TypeInt}, - $2, - }, - Posx: $2.Pos(), - } - } -| expr ARITH_OP expr - { - $$ = &ast.Arithmetic{ - Op: $2.Value.(ast.ArithmeticOp), - Exprs: []ast.Node{$1, $3}, - Posx: $1.Pos(), - } - } -| IDENTIFIER - { - $$ = &ast.VariableAccess{Name: $1.Value.(string), Posx: $1.Pos} - } -| IDENTIFIER PAREN_LEFT args PAREN_RIGHT - { - $$ = &ast.Call{Func: $1.Value.(string), Args: $3, Posx: $1.Pos} - } -| IDENTIFIER SQUARE_BRACKET_LEFT expr SQUARE_BRACKET_RIGHT - { - $$ = &ast.Index{ - Target: &ast.VariableAccess{ - Name: $1.Value.(string), - Posx: $1.Pos, - }, - Key: $3, - Posx: $1.Pos, - } - } - -args: - { - $$ = nil - } -| args COMMA expr - { - $$ = append($1, $3) - } -| expr - { - $$ = append($$, $1) - } - -literal: - STRING - { - $$ = &ast.LiteralNode{ - Value: $1.Value.(string), - Typex: ast.TypeString, - Posx: $1.Pos, - } - } - -%% diff --git a/vendor/github.com/hashicorp/hil/lex.go b/vendor/github.com/hashicorp/hil/lex.go deleted file mode 100644 index a66694641..000000000 --- a/vendor/github.com/hashicorp/hil/lex.go +++ /dev/null @@ -1,407 +0,0 @@ -package hil - -import ( - "bytes" - "fmt" - "strconv" - "unicode" - "unicode/utf8" - - "github.com/hashicorp/hil/ast" -) - -//go:generate go tool yacc -p parser lang.y - -// The parser expects the lexer to return 0 on EOF. -const lexEOF = 0 - -// The parser uses the type Lex as a lexer. It must provide -// the methods Lex(*SymType) int and Error(string). -type parserLex struct { - Err error - Input string - - mode parserMode - interpolationDepth int - pos int - width int - col, line int - lastLine int - astPos *ast.Pos -} - -// parserToken is the token yielded to the parser. The value can be -// determined within the parser type based on the enum value returned -// from Lex. -type parserToken struct { - Value interface{} - Pos ast.Pos -} - -// parserMode keeps track of what mode we're in for the parser. We have -// two modes: literal and interpolation. Literal mode is when strings -// don't have to be quoted, and interpolations are defined as ${foo}. -// Interpolation mode means that strings have to be quoted and unquoted -// things are identifiers, such as foo("bar"). -type parserMode uint8 - -const ( - parserModeInvalid parserMode = 0 - parserModeLiteral = 1 << iota - parserModeInterpolation -) - -// The parser calls this method to get each new token. -func (x *parserLex) Lex(yylval *parserSymType) int { - // We always start in literal mode, since programs don't start - // in an interpolation. ex. "foo ${bar}" vs "bar" (and assuming interp.) - if x.mode == parserModeInvalid { - x.mode = parserModeLiteral - } - - // Defer an update to set the proper column/line we read the next token. - defer func() { - if yylval.token != nil && yylval.token.Pos.Column == 0 { - yylval.token.Pos = *x.astPos - } - }() - - x.astPos = nil - return x.lex(yylval) -} - -func (x *parserLex) lex(yylval *parserSymType) int { - switch x.mode { - case parserModeLiteral: - return x.lexModeLiteral(yylval) - case parserModeInterpolation: - return x.lexModeInterpolation(yylval) - default: - x.Error(fmt.Sprintf("Unknown parse mode: %d", x.mode)) - return lexEOF - } -} - -func (x *parserLex) lexModeLiteral(yylval *parserSymType) int { - for { - c := x.next() - if c == lexEOF { - return lexEOF - } - - // Are we starting an interpolation? - if c == '$' && x.peek() == '{' { - x.next() - x.interpolationDepth++ - x.mode = parserModeInterpolation - return PROGRAM_BRACKET_LEFT - } - - // We're just a normal string that isn't part of any interpolation yet. - x.backup() - result, terminated := x.lexString(yylval, x.interpolationDepth > 0) - - // If the string terminated and we're within an interpolation already - // then that means that we finished a nested string, so pop - // back out to interpolation mode. - if terminated && x.interpolationDepth > 0 { - x.mode = parserModeInterpolation - - // If the string is empty, just skip it. We're still in - // an interpolation so we do this to avoid empty nodes. - if yylval.token.Value.(string) == "" { - return x.lex(yylval) - } - } - - return result - } -} - -func (x *parserLex) lexModeInterpolation(yylval *parserSymType) int { - for { - c := x.next() - if c == lexEOF { - return lexEOF - } - - // Ignore all whitespace - if unicode.IsSpace(c) { - continue - } - - // If we see a double quote then we're lexing a string since - // we're in interpolation mode. - if c == '"' { - result, terminated := x.lexString(yylval, true) - if !terminated { - // The string didn't end, which means that we're in the - // middle of starting another interpolation. - x.mode = parserModeLiteral - - // If the string is empty and we're starting an interpolation, - // then just skip it to avoid empty string AST nodes - if yylval.token.Value.(string) == "" { - return x.lex(yylval) - } - } - - return result - } - - // If we are seeing a number, it is the start of a number. Lex it. - if c >= '0' && c <= '9' { - x.backup() - return x.lexNumber(yylval) - } - - switch c { - case '}': - // '}' means we ended the interpolation. Pop back into - // literal mode and reduce our interpolation depth. - x.interpolationDepth-- - x.mode = parserModeLiteral - return PROGRAM_BRACKET_RIGHT - case '(': - return PAREN_LEFT - case ')': - return PAREN_RIGHT - case '[': - return SQUARE_BRACKET_LEFT - case ']': - return SQUARE_BRACKET_RIGHT - case ',': - return COMMA - case '+': - yylval.token = &parserToken{Value: ast.ArithmeticOpAdd} - return ARITH_OP - case '-': - yylval.token = &parserToken{Value: ast.ArithmeticOpSub} - return ARITH_OP - case '*': - yylval.token = &parserToken{Value: ast.ArithmeticOpMul} - return ARITH_OP - case '/': - yylval.token = &parserToken{Value: ast.ArithmeticOpDiv} - return ARITH_OP - case '%': - yylval.token = &parserToken{Value: ast.ArithmeticOpMod} - return ARITH_OP - default: - x.backup() - return x.lexId(yylval) - } - } -} - -func (x *parserLex) lexId(yylval *parserSymType) int { - var b bytes.Buffer - var last rune - for { - c := x.next() - if c == lexEOF { - break - } - - // We only allow * after a '.' for resource splast: type.name.*.id - // Otherwise, its probably multiplication. - if c == '*' && last != '.' { - x.backup() - break - } - - // If this isn't a character we want in an ID, return out. - // One day we should make this a regexp. - if c != '_' && - c != '-' && - c != '.' && - c != '*' && - !unicode.IsLetter(c) && - !unicode.IsNumber(c) { - x.backup() - break - } - - if _, err := b.WriteRune(c); err != nil { - x.Error(err.Error()) - return lexEOF - } - - last = c - } - - yylval.token = &parserToken{Value: b.String()} - return IDENTIFIER -} - -// lexNumber lexes out a number: an integer or a float. -func (x *parserLex) lexNumber(yylval *parserSymType) int { - var b bytes.Buffer - gotPeriod := false - for { - c := x.next() - if c == lexEOF { - break - } - - // If we see a period, we might be getting a float.. - if c == '.' { - // If we've already seen a period, then ignore it, and - // exit. This will probably result in a syntax error later. - if gotPeriod { - x.backup() - break - } - - gotPeriod = true - } else if c < '0' || c > '9' { - // If we're not seeing a number, then also exit. - x.backup() - break - } - - if _, err := b.WriteRune(c); err != nil { - x.Error(fmt.Sprintf("internal error: %s", err)) - return lexEOF - } - } - - // If we didn't see a period, it is an int - if !gotPeriod { - v, err := strconv.ParseInt(b.String(), 0, 0) - if err != nil { - x.Error(fmt.Sprintf("expected number: %s", err)) - return lexEOF - } - - yylval.token = &parserToken{Value: int(v)} - return INTEGER - } - - // If we did see a period, it is a float - f, err := strconv.ParseFloat(b.String(), 64) - if err != nil { - x.Error(fmt.Sprintf("expected float: %s", err)) - return lexEOF - } - - yylval.token = &parserToken{Value: f} - return FLOAT -} - -func (x *parserLex) lexString(yylval *parserSymType, quoted bool) (int, bool) { - var b bytes.Buffer - terminated := false - for { - c := x.next() - if c == lexEOF { - if quoted { - x.Error("unterminated string") - } - - break - } - - // Behavior is a bit different if we're lexing within a quoted string. - if quoted { - // If its a double quote, we've reached the end of the string - if c == '"' { - terminated = true - break - } - - // Let's check to see if we're escaping anything. - if c == '\\' { - switch n := x.next(); n { - case '\\', '"': - c = n - case 'n': - c = '\n' - default: - x.backup() - } - } - } - - // If we hit a dollar sign, then check if we're starting - // another interpolation. If so, then we're done. - if c == '$' { - n := x.peek() - - // If it is '{', then we're starting another interpolation - if n == '{' { - x.backup() - break - } - - // If it is '$', then we're escaping a dollar sign - if n == '$' { - x.next() - } - } - - if _, err := b.WriteRune(c); err != nil { - x.Error(err.Error()) - return lexEOF, false - } - } - - yylval.token = &parserToken{Value: b.String()} - return STRING, terminated -} - -// Return the next rune for the lexer. -func (x *parserLex) next() rune { - if int(x.pos) >= len(x.Input) { - x.width = 0 - return lexEOF - } - - r, w := utf8.DecodeRuneInString(x.Input[x.pos:]) - x.width = w - x.pos += x.width - - if x.line == 0 { - x.line = 1 - x.col = 1 - } else { - x.col += 1 - } - - if r == '\n' { - x.lastLine = x.col - x.line += 1 - x.col = 1 - } - - if x.astPos == nil { - x.astPos = &ast.Pos{Column: x.col, Line: x.line} - } - - return r -} - -// peek returns but does not consume the next rune in the input -func (x *parserLex) peek() rune { - r := x.next() - x.backup() - return r -} - -// backup steps back one rune. Can only be called once per next. -func (x *parserLex) backup() { - x.pos -= x.width - x.col -= 1 - - // If we are at column 0, we're backing up across a line boundary - // so we need to be careful to get the proper value. - if x.col == 0 { - x.col = x.lastLine - x.line -= 1 - } -} - -// The parser calls this method on a parse error. -func (x *parserLex) Error(s string) { - x.Err = fmt.Errorf("parse error: %s", s) -} diff --git a/vendor/github.com/hashicorp/hil/parse.go b/vendor/github.com/hashicorp/hil/parse.go index dac21df3f..ecbe1fdbf 100644 --- a/vendor/github.com/hashicorp/hil/parse.go +++ b/vendor/github.com/hashicorp/hil/parse.go @@ -1,30 +1,29 @@ package hil import ( - "sync" - "github.com/hashicorp/hil/ast" + "github.com/hashicorp/hil/parser" + "github.com/hashicorp/hil/scanner" ) -var parserLock sync.Mutex -var parserResult ast.Node - // Parse parses the given program and returns an executable AST tree. +// +// Syntax errors are returned with error having the dynamic type +// *parser.ParseError, which gives the caller access to the source position +// where the error was found, which allows (for example) combining it with +// a known source filename to add context to the error message. func Parse(v string) (ast.Node, error) { - // Unfortunately due to the way that goyacc generated parsers are - // formatted, we can only do a single parse at a time without a lot - // of extra work. In the future we can remove this limitation. - parserLock.Lock() - defer parserLock.Unlock() - - // Reset our globals - parserResult = nil - - // Create the lexer - lex := &parserLex{Input: v} - - // Parse! - parserParse(lex) - - return parserResult, lex.Err + return ParseWithPosition(v, ast.Pos{Line: 1, Column: 1}) +} + +// ParseWithPosition is like Parse except that it overrides the source +// row and column position of the first character in the string, which should +// be 1-based. +// +// This can be used when HIL is embedded in another language and the outer +// parser knows the row and column where the HIL expression started within +// the overall source file. +func ParseWithPosition(v string, pos ast.Pos) (ast.Node, error) { + ch := scanner.Scan(v, pos) + return parser.Parse(ch) } diff --git a/vendor/github.com/hashicorp/hil/parser/binary_op.go b/vendor/github.com/hashicorp/hil/parser/binary_op.go new file mode 100644 index 000000000..2e013e01d --- /dev/null +++ b/vendor/github.com/hashicorp/hil/parser/binary_op.go @@ -0,0 +1,45 @@ +package parser + +import ( + "github.com/hashicorp/hil/ast" + "github.com/hashicorp/hil/scanner" +) + +var binaryOps []map[scanner.TokenType]ast.ArithmeticOp + +func init() { + // This operation table maps from the operator's scanner token type + // to the AST arithmetic operation. All expressions produced from + // binary operators are *ast.Arithmetic nodes. + // + // Binary operator groups are listed in order of precedence, with + // the *lowest* precedence first. Operators within the same group + // have left-to-right associativity. + binaryOps = []map[scanner.TokenType]ast.ArithmeticOp{ + { + scanner.OR: ast.ArithmeticOpLogicalOr, + }, + { + scanner.AND: ast.ArithmeticOpLogicalAnd, + }, + { + scanner.EQUAL: ast.ArithmeticOpEqual, + scanner.NOTEQUAL: ast.ArithmeticOpNotEqual, + }, + { + scanner.GT: ast.ArithmeticOpGreaterThan, + scanner.GTE: ast.ArithmeticOpGreaterThanOrEqual, + scanner.LT: ast.ArithmeticOpLessThan, + scanner.LTE: ast.ArithmeticOpLessThanOrEqual, + }, + { + scanner.PLUS: ast.ArithmeticOpAdd, + scanner.MINUS: ast.ArithmeticOpSub, + }, + { + scanner.STAR: ast.ArithmeticOpMul, + scanner.SLASH: ast.ArithmeticOpDiv, + scanner.PERCENT: ast.ArithmeticOpMod, + }, + } +} diff --git a/vendor/github.com/hashicorp/hil/parser/error.go b/vendor/github.com/hashicorp/hil/parser/error.go new file mode 100644 index 000000000..bacd69645 --- /dev/null +++ b/vendor/github.com/hashicorp/hil/parser/error.go @@ -0,0 +1,38 @@ +package parser + +import ( + "fmt" + + "github.com/hashicorp/hil/ast" + "github.com/hashicorp/hil/scanner" +) + +type ParseError struct { + Message string + Pos ast.Pos +} + +func Errorf(pos ast.Pos, format string, args ...interface{}) error { + return &ParseError{ + Message: fmt.Sprintf(format, args...), + Pos: pos, + } +} + +// TokenErrorf is a convenient wrapper around Errorf that uses the +// position of the given token. +func TokenErrorf(token *scanner.Token, format string, args ...interface{}) error { + return Errorf(token.Pos, format, args...) +} + +func ExpectationError(wanted string, got *scanner.Token) error { + return TokenErrorf(got, "expected %s but found %s", wanted, got) +} + +func (e *ParseError) Error() string { + return fmt.Sprintf("parse error at %s: %s", e.Pos, e.Message) +} + +func (e *ParseError) String() string { + return e.Error() +} diff --git a/vendor/github.com/hashicorp/hil/parser/fuzz.go b/vendor/github.com/hashicorp/hil/parser/fuzz.go new file mode 100644 index 000000000..de954f383 --- /dev/null +++ b/vendor/github.com/hashicorp/hil/parser/fuzz.go @@ -0,0 +1,28 @@ +// +build gofuzz + +package parser + +import ( + "github.com/hashicorp/hil/ast" + "github.com/hashicorp/hil/scanner" +) + +// This is a fuzz testing function designed to be used with go-fuzz: +// https://github.com/dvyukov/go-fuzz +// +// It's not included in a normal build due to the gofuzz build tag above. +// +// There are some input files that you can use as a seed corpus for go-fuzz +// in the directory ./fuzz-corpus . + +func Fuzz(data []byte) int { + str := string(data) + + ch := scanner.Scan(str, ast.Pos{Line: 1, Column: 1}) + _, err := Parse(ch) + if err != nil { + return 0 + } + + return 1 +} diff --git a/vendor/github.com/hashicorp/hil/parser/parser.go b/vendor/github.com/hashicorp/hil/parser/parser.go new file mode 100644 index 000000000..376f1c49d --- /dev/null +++ b/vendor/github.com/hashicorp/hil/parser/parser.go @@ -0,0 +1,522 @@ +package parser + +import ( + "strconv" + "unicode/utf8" + + "github.com/hashicorp/hil/ast" + "github.com/hashicorp/hil/scanner" +) + +func Parse(ch <-chan *scanner.Token) (ast.Node, error) { + peeker := scanner.NewPeeker(ch) + parser := &parser{peeker} + output, err := parser.ParseTopLevel() + peeker.Close() + return output, err +} + +type parser struct { + peeker *scanner.Peeker +} + +func (p *parser) ParseTopLevel() (ast.Node, error) { + return p.parseInterpolationSeq(false) +} + +func (p *parser) ParseQuoted() (ast.Node, error) { + return p.parseInterpolationSeq(true) +} + +// parseInterpolationSeq parses either the top-level sequence of literals +// and interpolation expressions or a similar sequence within a quoted +// string inside an interpolation expression. The latter case is requested +// by setting 'quoted' to true. +func (p *parser) parseInterpolationSeq(quoted bool) (ast.Node, error) { + literalType := scanner.LITERAL + endType := scanner.EOF + if quoted { + // exceptions for quoted sequences + literalType = scanner.STRING + endType = scanner.CQUOTE + } + + startPos := p.peeker.Peek().Pos + + if quoted { + tok := p.peeker.Read() + if tok.Type != scanner.OQUOTE { + return nil, ExpectationError("open quote", tok) + } + } + + var exprs []ast.Node + for { + tok := p.peeker.Read() + + if tok.Type == endType { + break + } + + switch tok.Type { + case literalType: + val, err := p.parseStringToken(tok) + if err != nil { + return nil, err + } + exprs = append(exprs, &ast.LiteralNode{ + Value: val, + Typex: ast.TypeString, + Posx: tok.Pos, + }) + case scanner.BEGIN: + expr, err := p.ParseInterpolation() + if err != nil { + return nil, err + } + exprs = append(exprs, expr) + default: + return nil, ExpectationError(`"${"`, tok) + } + } + + if len(exprs) == 0 { + // If we have no parts at all then the input must've + // been an empty string. + exprs = append(exprs, &ast.LiteralNode{ + Value: "", + Typex: ast.TypeString, + Posx: startPos, + }) + } + + // As a special case, if our "Output" contains only one expression + // and it's a literal string then we'll hoist it up to be our + // direct return value, so callers can easily recognize a string + // that has no interpolations at all. + if len(exprs) == 1 { + if lit, ok := exprs[0].(*ast.LiteralNode); ok { + if lit.Typex == ast.TypeString { + return lit, nil + } + } + } + + return &ast.Output{ + Exprs: exprs, + Posx: startPos, + }, nil +} + +// parseStringToken takes a token of either LITERAL or STRING type and +// returns the interpreted string, after processing any relevant +// escape sequences. +func (p *parser) parseStringToken(tok *scanner.Token) (string, error) { + var backslashes bool + switch tok.Type { + case scanner.LITERAL: + backslashes = false + case scanner.STRING: + backslashes = true + default: + panic("unsupported string token type") + } + + raw := []byte(tok.Content) + buf := make([]byte, 0, len(raw)) + + for i := 0; i < len(raw); i++ { + b := raw[i] + more := len(raw) > (i + 1) + + if b == '$' { + if more && raw[i+1] == '$' { + // skip over the second dollar sign + i++ + } + } else if backslashes && b == '\\' { + if !more { + return "", Errorf( + ast.Pos{ + Column: tok.Pos.Column + utf8.RuneCount(raw[:i]), + Line: tok.Pos.Line, + }, + `unfinished backslash escape sequence`, + ) + } + escapeType := raw[i+1] + switch escapeType { + case '\\': + // skip over the second slash + i++ + case 'n': + b = '\n' + i++ + case '"': + b = '"' + i++ + default: + return "", Errorf( + ast.Pos{ + Column: tok.Pos.Column + utf8.RuneCount(raw[:i]), + Line: tok.Pos.Line, + }, + `invalid backslash escape sequence`, + ) + } + } + + buf = append(buf, b) + } + + return string(buf), nil +} + +func (p *parser) ParseInterpolation() (ast.Node, error) { + // By the time we're called, we're already "inside" the ${ sequence + // because the caller consumed the ${ token. + + expr, err := p.ParseExpression() + if err != nil { + return nil, err + } + + err = p.requireTokenType(scanner.END, `"}"`) + if err != nil { + return nil, err + } + + return expr, nil +} + +func (p *parser) ParseExpression() (ast.Node, error) { + return p.parseTernaryCond() +} + +func (p *parser) parseTernaryCond() (ast.Node, error) { + // The ternary condition operator (.. ? .. : ..) behaves somewhat + // like a binary operator except that the "operator" is itself + // an expression enclosed in two punctuation characters. + // The middle expression is parsed as if the ? and : symbols + // were parentheses. The "rhs" (the "false expression") is then + // treated right-associatively so it behaves similarly to the + // middle in terms of precedence. + + startPos := p.peeker.Peek().Pos + + var cond, trueExpr, falseExpr ast.Node + var err error + + cond, err = p.parseBinaryOps(binaryOps) + if err != nil { + return nil, err + } + + next := p.peeker.Peek() + if next.Type != scanner.QUESTION { + return cond, nil + } + + p.peeker.Read() // eat question mark + + trueExpr, err = p.ParseExpression() + if err != nil { + return nil, err + } + + colon := p.peeker.Read() + if colon.Type != scanner.COLON { + return nil, ExpectationError(":", colon) + } + + falseExpr, err = p.ParseExpression() + if err != nil { + return nil, err + } + + return &ast.Conditional{ + CondExpr: cond, + TrueExpr: trueExpr, + FalseExpr: falseExpr, + Posx: startPos, + }, nil +} + +// parseBinaryOps calls itself recursively to work through all of the +// operator precedence groups, and then eventually calls ParseExpressionTerm +// for each operand. +func (p *parser) parseBinaryOps(ops []map[scanner.TokenType]ast.ArithmeticOp) (ast.Node, error) { + if len(ops) == 0 { + // We've run out of operators, so now we'll just try to parse a term. + return p.ParseExpressionTerm() + } + + thisLevel := ops[0] + remaining := ops[1:] + + startPos := p.peeker.Peek().Pos + + var lhs, rhs ast.Node + operator := ast.ArithmeticOpInvalid + var err error + + // parse a term that might be the first operand of a binary + // expression or it might just be a standalone term, but + // we won't know until we've parsed it and can look ahead + // to see if there's an operator token. + lhs, err = p.parseBinaryOps(remaining) + if err != nil { + return nil, err + } + + // We'll keep eating up arithmetic operators until we run + // out, so that operators with the same precedence will combine in a + // left-associative manner: + // a+b+c => (a+b)+c, not a+(b+c) + // + // Should we later want to have right-associative operators, a way + // to achieve that would be to call back up to ParseExpression here + // instead of iteratively parsing only the remaining operators. + for { + next := p.peeker.Peek() + var newOperator ast.ArithmeticOp + var ok bool + if newOperator, ok = thisLevel[next.Type]; !ok { + break + } + + // Are we extending an expression started on + // the previous iteration? + if operator != ast.ArithmeticOpInvalid { + lhs = &ast.Arithmetic{ + Op: operator, + Exprs: []ast.Node{lhs, rhs}, + Posx: startPos, + } + } + + operator = newOperator + p.peeker.Read() // eat operator token + rhs, err = p.parseBinaryOps(remaining) + if err != nil { + return nil, err + } + } + + if operator != ast.ArithmeticOpInvalid { + return &ast.Arithmetic{ + Op: operator, + Exprs: []ast.Node{lhs, rhs}, + Posx: startPos, + }, nil + } else { + return lhs, nil + } +} + +func (p *parser) ParseExpressionTerm() (ast.Node, error) { + + next := p.peeker.Peek() + + switch next.Type { + + case scanner.OPAREN: + p.peeker.Read() + expr, err := p.ParseExpression() + if err != nil { + return nil, err + } + err = p.requireTokenType(scanner.CPAREN, `")"`) + return expr, err + + case scanner.OQUOTE: + return p.ParseQuoted() + + case scanner.INTEGER: + tok := p.peeker.Read() + val, err := strconv.Atoi(tok.Content) + if err != nil { + return nil, TokenErrorf(tok, "invalid integer: %s", err) + } + return &ast.LiteralNode{ + Value: val, + Typex: ast.TypeInt, + Posx: tok.Pos, + }, nil + + case scanner.FLOAT: + tok := p.peeker.Read() + val, err := strconv.ParseFloat(tok.Content, 64) + if err != nil { + return nil, TokenErrorf(tok, "invalid float: %s", err) + } + return &ast.LiteralNode{ + Value: val, + Typex: ast.TypeFloat, + Posx: tok.Pos, + }, nil + + case scanner.BOOL: + tok := p.peeker.Read() + // the scanner guarantees that tok.Content is either "true" or "false" + var val bool + if tok.Content[0] == 't' { + val = true + } else { + val = false + } + return &ast.LiteralNode{ + Value: val, + Typex: ast.TypeBool, + Posx: tok.Pos, + }, nil + + case scanner.MINUS: + opTok := p.peeker.Read() + // important to use ParseExpressionTerm rather than ParseExpression + // here, otherwise we can capture a following binary expression into + // our negation. + // e.g. -46+5 should parse as (0-46)+5, not 0-(46+5) + operand, err := p.ParseExpressionTerm() + if err != nil { + return nil, err + } + // The AST currently represents negative numbers as + // a binary subtraction of the number from zero. + return &ast.Arithmetic{ + Op: ast.ArithmeticOpSub, + Exprs: []ast.Node{ + &ast.LiteralNode{ + Value: 0, + Typex: ast.TypeInt, + Posx: opTok.Pos, + }, + operand, + }, + Posx: opTok.Pos, + }, nil + + case scanner.BANG: + opTok := p.peeker.Read() + // important to use ParseExpressionTerm rather than ParseExpression + // here, otherwise we can capture a following binary expression into + // our negation. + operand, err := p.ParseExpressionTerm() + if err != nil { + return nil, err + } + // The AST currently represents binary negation as an equality + // test with "false". + return &ast.Arithmetic{ + Op: ast.ArithmeticOpEqual, + Exprs: []ast.Node{ + &ast.LiteralNode{ + Value: false, + Typex: ast.TypeBool, + Posx: opTok.Pos, + }, + operand, + }, + Posx: opTok.Pos, + }, nil + + case scanner.IDENTIFIER: + return p.ParseScopeInteraction() + + default: + return nil, ExpectationError("expression", next) + } +} + +// ParseScopeInteraction parses the expression types that interact +// with the evaluation scope: variable access, function calls, and +// indexing. +// +// Indexing should actually be a distinct operator in its own right, +// so that e.g. it can be applied to the result of a function call, +// but for now we're preserving the behavior of the older yacc-based +// parser. +func (p *parser) ParseScopeInteraction() (ast.Node, error) { + first := p.peeker.Read() + startPos := first.Pos + if first.Type != scanner.IDENTIFIER { + return nil, ExpectationError("identifier", first) + } + + next := p.peeker.Peek() + if next.Type == scanner.OPAREN { + // function call + funcName := first.Content + p.peeker.Read() // eat paren + var args []ast.Node + + for { + if p.peeker.Peek().Type == scanner.CPAREN { + break + } + + arg, err := p.ParseExpression() + if err != nil { + return nil, err + } + + args = append(args, arg) + + if p.peeker.Peek().Type == scanner.COMMA { + p.peeker.Read() // eat comma + continue + } else { + break + } + } + + err := p.requireTokenType(scanner.CPAREN, `")"`) + if err != nil { + return nil, err + } + + return &ast.Call{ + Func: funcName, + Args: args, + Posx: startPos, + }, nil + } + + varNode := &ast.VariableAccess{ + Name: first.Content, + Posx: startPos, + } + + if p.peeker.Peek().Type == scanner.OBRACKET { + // index operator + startPos := p.peeker.Read().Pos // eat bracket + indexExpr, err := p.ParseExpression() + if err != nil { + return nil, err + } + err = p.requireTokenType(scanner.CBRACKET, `"]"`) + if err != nil { + return nil, err + } + return &ast.Index{ + Target: varNode, + Key: indexExpr, + Posx: startPos, + }, nil + } + + return varNode, nil +} + +// requireTokenType consumes the next token an returns an error if its +// type does not match the given type. nil is returned if the type matches. +// +// This is a helper around peeker.Read() for situations where the parser just +// wants to assert that a particular token type must be present. +func (p *parser) requireTokenType(wantType scanner.TokenType, wantName string) error { + token := p.peeker.Read() + if token.Type != wantType { + return ExpectationError(wantName, token) + } + return nil +} diff --git a/vendor/github.com/hashicorp/hil/scanner/peeker.go b/vendor/github.com/hashicorp/hil/scanner/peeker.go new file mode 100644 index 000000000..4de372831 --- /dev/null +++ b/vendor/github.com/hashicorp/hil/scanner/peeker.go @@ -0,0 +1,55 @@ +package scanner + +// Peeker is a utility that wraps a token channel returned by Scan and +// provides an interface that allows a caller (e.g. the parser) to +// work with the token stream in a mode that allows one token of lookahead, +// and provides utilities for more convenient processing of the stream. +type Peeker struct { + ch <-chan *Token + peeked *Token +} + +func NewPeeker(ch <-chan *Token) *Peeker { + return &Peeker{ + ch: ch, + } +} + +// Peek returns the next token in the stream without consuming it. A +// subsequent call to Read will return the same token. +func (p *Peeker) Peek() *Token { + if p.peeked == nil { + p.peeked = <-p.ch + } + return p.peeked +} + +// Read consumes the next token in the stream and returns it. +func (p *Peeker) Read() *Token { + token := p.Peek() + + // As a special case, we will produce the EOF token forever once + // it is reached. + if token.Type != EOF { + p.peeked = nil + } + + return token +} + +// Close ensures that the token stream has been exhausted, to prevent +// the goroutine in the underlying scanner from leaking. +// +// It's not necessary to call this if the caller reads the token stream +// to EOF, since that implicitly closes the scanner. +func (p *Peeker) Close() { + for _ = range p.ch { + // discard + } + // Install a synthetic EOF token in 'peeked' in case someone + // erroneously calls Peek() or Read() after we've closed. + p.peeked = &Token{ + Type: EOF, + Content: "", + } +} diff --git a/vendor/github.com/hashicorp/hil/scanner/scanner.go b/vendor/github.com/hashicorp/hil/scanner/scanner.go new file mode 100644 index 000000000..86085de01 --- /dev/null +++ b/vendor/github.com/hashicorp/hil/scanner/scanner.go @@ -0,0 +1,556 @@ +package scanner + +import ( + "unicode" + "unicode/utf8" + + "github.com/hashicorp/hil/ast" +) + +// Scan returns a channel that recieves Tokens from the given input string. +// +// The scanner's job is just to partition the string into meaningful parts. +// It doesn't do any transformation of the raw input string, so the caller +// must deal with any further interpretation required, such as parsing INTEGER +// tokens into real ints, or dealing with escape sequences in LITERAL or +// STRING tokens. +// +// Strings in the returned tokens are slices from the original string. +// +// startPos should be set to ast.InitPos unless the caller knows that +// this interpolation string is part of a larger file and knows the position +// of the first character in that larger file. +func Scan(s string, startPos ast.Pos) <-chan *Token { + ch := make(chan *Token) + go scan(s, ch, startPos) + return ch +} + +func scan(s string, ch chan<- *Token, pos ast.Pos) { + // 'remain' starts off as the whole string but we gradually + // slice of the front of it as we work our way through. + remain := s + + // nesting keeps track of how many ${ .. } sequences we are + // inside, so we can recognize the minor differences in syntax + // between outer string literals (LITERAL tokens) and quoted + // string literals (STRING tokens). + nesting := 0 + + // We're going to flip back and forth between parsing literals/strings + // and parsing interpolation sequences ${ .. } until we reach EOF or + // some INVALID token. +All: + for { + startPos := pos + // Literal string processing first, since the beginning of + // a string is always outside of an interpolation sequence. + literalVal, terminator := scanLiteral(remain, pos, nesting > 0) + + if len(literalVal) > 0 { + litType := LITERAL + if nesting > 0 { + litType = STRING + } + ch <- &Token{ + Type: litType, + Content: literalVal, + Pos: startPos, + } + remain = remain[len(literalVal):] + } + + ch <- terminator + remain = remain[len(terminator.Content):] + pos = terminator.Pos + // Safe to use len() here because none of the terminator tokens + // can contain UTF-8 sequences. + pos.Column = pos.Column + len(terminator.Content) + + switch terminator.Type { + case INVALID: + // Synthetic EOF after invalid token, since further scanning + // is likely to just produce more garbage. + ch <- &Token{ + Type: EOF, + Content: "", + Pos: pos, + } + break All + case EOF: + // All done! + break All + case BEGIN: + nesting++ + case CQUOTE: + // nothing special to do + default: + // Should never happen + panic("invalid string/literal terminator") + } + + // Now we do the processing of the insides of ${ .. } sequences. + // This loop terminates when we encounter either a closing } or + // an opening ", which will cause us to return to literal processing. + Interpolation: + for { + + token, size, newPos := scanInterpolationToken(remain, pos) + ch <- token + remain = remain[size:] + pos = newPos + + switch token.Type { + case INVALID: + // Synthetic EOF after invalid token, since further scanning + // is likely to just produce more garbage. + ch <- &Token{ + Type: EOF, + Content: "", + Pos: pos, + } + break All + case EOF: + // All done + // (though a syntax error that we'll catch in the parser) + break All + case END: + nesting-- + if nesting < 0 { + // Can happen if there are unbalanced ${ and } sequences + // in the input, which we'll catch in the parser. + nesting = 0 + } + break Interpolation + case OQUOTE: + // Beginning of nested quoted string + break Interpolation + } + } + } + + close(ch) +} + +// Returns the token found at the start of the given string, followed by +// the number of bytes that were consumed from the string and the adjusted +// source position. +// +// Note that the number of bytes consumed can be more than the length of +// the returned token contents if the string begins with whitespace, since +// it will be silently consumed before reading the token. +func scanInterpolationToken(s string, startPos ast.Pos) (*Token, int, ast.Pos) { + pos := startPos + size := 0 + + // Consume whitespace, if any + for len(s) > 0 && byteIsSpace(s[0]) { + if s[0] == '\n' { + pos.Column = 1 + pos.Line++ + } else { + pos.Column++ + } + size++ + s = s[1:] + } + + // Unexpected EOF during sequence + if len(s) == 0 { + return &Token{ + Type: EOF, + Content: "", + Pos: pos, + }, size, pos + } + + next := s[0] + var token *Token + + switch next { + case '(', ')', '[', ']', ',', '.', '+', '-', '*', '/', '%', '?', ':': + // Easy punctuation symbols that don't have any special meaning + // during scanning, and that stand for themselves in the + // TokenType enumeration. + token = &Token{ + Type: TokenType(next), + Content: s[:1], + Pos: pos, + } + case '}': + token = &Token{ + Type: END, + Content: s[:1], + Pos: pos, + } + case '"': + token = &Token{ + Type: OQUOTE, + Content: s[:1], + Pos: pos, + } + case '!': + if len(s) >= 2 && s[:2] == "!=" { + token = &Token{ + Type: NOTEQUAL, + Content: s[:2], + Pos: pos, + } + } else { + token = &Token{ + Type: BANG, + Content: s[:1], + Pos: pos, + } + } + case '<': + if len(s) >= 2 && s[:2] == "<=" { + token = &Token{ + Type: LTE, + Content: s[:2], + Pos: pos, + } + } else { + token = &Token{ + Type: LT, + Content: s[:1], + Pos: pos, + } + } + case '>': + if len(s) >= 2 && s[:2] == ">=" { + token = &Token{ + Type: GTE, + Content: s[:2], + Pos: pos, + } + } else { + token = &Token{ + Type: GT, + Content: s[:1], + Pos: pos, + } + } + case '=': + if len(s) >= 2 && s[:2] == "==" { + token = &Token{ + Type: EQUAL, + Content: s[:2], + Pos: pos, + } + } else { + // A single equals is not a valid operator + token = &Token{ + Type: INVALID, + Content: s[:1], + Pos: pos, + } + } + case '&': + if len(s) >= 2 && s[:2] == "&&" { + token = &Token{ + Type: AND, + Content: s[:2], + Pos: pos, + } + } else { + token = &Token{ + Type: INVALID, + Content: s[:1], + Pos: pos, + } + } + case '|': + if len(s) >= 2 && s[:2] == "||" { + token = &Token{ + Type: OR, + Content: s[:2], + Pos: pos, + } + } else { + token = &Token{ + Type: INVALID, + Content: s[:1], + Pos: pos, + } + } + default: + if next >= '0' && next <= '9' { + num, numType := scanNumber(s) + token = &Token{ + Type: numType, + Content: num, + Pos: pos, + } + } else if stringStartsWithIdentifier(s) { + ident, runeLen := scanIdentifier(s) + tokenType := IDENTIFIER + if ident == "true" || ident == "false" { + tokenType = BOOL + } + token = &Token{ + Type: tokenType, + Content: ident, + Pos: pos, + } + // Skip usual token handling because it doesn't + // know how to deal with UTF-8 sequences. + pos.Column = pos.Column + runeLen + return token, size + len(ident), pos + } else { + _, byteLen := utf8.DecodeRuneInString(s) + token = &Token{ + Type: INVALID, + Content: s[:byteLen], + Pos: pos, + } + // Skip usual token handling because it doesn't + // know how to deal with UTF-8 sequences. + pos.Column = pos.Column + 1 + return token, size + byteLen, pos + } + } + + // Here we assume that the token content contains no UTF-8 sequences, + // because we dealt with UTF-8 characters as a special case where + // necessary above. + size = size + len(token.Content) + pos.Column = pos.Column + len(token.Content) + + return token, size, pos +} + +// Returns the (possibly-empty) prefix of the given string that represents +// a literal, followed by the token that marks the end of the literal. +func scanLiteral(s string, startPos ast.Pos, nested bool) (string, *Token) { + litLen := 0 + pos := startPos + var terminator *Token + for { + + if litLen >= len(s) { + if nested { + // We've ended in the middle of a quoted string, + // which means this token is actually invalid. + return "", &Token{ + Type: INVALID, + Content: s, + Pos: startPos, + } + } + terminator = &Token{ + Type: EOF, + Content: "", + Pos: pos, + } + break + } + + next := s[litLen] + + if next == '$' && len(s) > litLen+1 { + follow := s[litLen+1] + + if follow == '{' { + terminator = &Token{ + Type: BEGIN, + Content: s[litLen : litLen+2], + Pos: pos, + } + pos.Column = pos.Column + 2 + break + } else if follow == '$' { + // Double-$ escapes the special processing of $, + // so we will consume both characters here. + pos.Column = pos.Column + 2 + litLen = litLen + 2 + continue + } + } + + // special handling that applies only to quoted strings + if nested { + if next == '"' { + terminator = &Token{ + Type: CQUOTE, + Content: s[litLen : litLen+1], + Pos: pos, + } + pos.Column = pos.Column + 1 + break + } + + // Escaped quote marks do not terminate the string. + // + // All we do here in the scanner is avoid terminating a string + // due to an escaped quote. The parser is responsible for the + // full handling of escape sequences, since it's able to produce + // better error messages than we can produce in here. + if next == '\\' && len(s) > litLen+1 { + follow := s[litLen+1] + + if follow == '"' { + // \" escapes the special processing of ", + // so we will consume both characters here. + pos.Column = pos.Column + 2 + litLen = litLen + 2 + continue + } else if follow == '\\' { + // \\ escapes \ + // so we will consume both characters here. + pos.Column = pos.Column + 2 + litLen = litLen + 2 + continue + } + } + } + + if next == '\n' { + pos.Column = 1 + pos.Line++ + litLen++ + } else { + pos.Column++ + + // "Column" measures runes, so we need to actually consume + // a valid UTF-8 character here. + _, size := utf8.DecodeRuneInString(s[litLen:]) + litLen = litLen + size + } + + } + + return s[:litLen], terminator +} + +// scanNumber returns the extent of the prefix of the string that represents +// a valid number, along with what type of number it represents: INT or FLOAT. +// +// scanNumber does only basic character analysis: numbers consist of digits +// and periods, with at least one period signalling a FLOAT. It's the parser's +// responsibility to validate the form and range of the number, such as ensuring +// that a FLOAT actually contains only one period, etc. +func scanNumber(s string) (string, TokenType) { + period := -1 + byteLen := 0 + numType := INTEGER + for { + if byteLen >= len(s) { + break + } + + next := s[byteLen] + if next != '.' && (next < '0' || next > '9') { + // If our last value was a period, then we're not a float, + // we're just an integer that ends in a period. + if period == byteLen-1 { + byteLen-- + numType = INTEGER + } + + break + } + + if next == '.' { + // If we've already seen a period, break out + if period >= 0 { + break + } + + period = byteLen + numType = FLOAT + } + + byteLen++ + } + + return s[:byteLen], numType +} + +// scanIdentifier returns the extent of the prefix of the string that +// represents a valid identifier, along with the length of that prefix +// in runes. +// +// Identifiers may contain utf8-encoded non-Latin letters, which will +// cause the returned "rune length" to be shorter than the byte length +// of the returned string. +func scanIdentifier(s string) (string, int) { + byteLen := 0 + runeLen := 0 + for { + if byteLen >= len(s) { + break + } + + nextRune, size := utf8.DecodeRuneInString(s[byteLen:]) + if !(nextRune == '_' || + nextRune == '-' || + nextRune == '.' || + nextRune == '*' || + unicode.IsNumber(nextRune) || + unicode.IsLetter(nextRune) || + unicode.IsMark(nextRune)) { + break + } + + // If we reach a star, it must be between periods to be part + // of the same identifier. + if nextRune == '*' && s[byteLen-1] != '.' { + break + } + + // If our previous character was a star, then the current must + // be period. Otherwise, undo that and exit. + if byteLen > 0 && s[byteLen-1] == '*' && nextRune != '.' { + byteLen-- + if s[byteLen-1] == '.' { + byteLen-- + } + + break + } + + byteLen = byteLen + size + runeLen = runeLen + 1 + } + + return s[:byteLen], runeLen +} + +// byteIsSpace implements a restrictive interpretation of spaces that includes +// only what's valid inside interpolation sequences: spaces, tabs, newlines. +func byteIsSpace(b byte) bool { + switch b { + case ' ', '\t', '\r', '\n': + return true + default: + return false + } +} + +// stringStartsWithIdentifier returns true if the given string begins with +// a character that is a legal start of an identifier: an underscore or +// any character that Unicode considers to be a letter. +func stringStartsWithIdentifier(s string) bool { + if len(s) == 0 { + return false + } + + first := s[0] + + // Easy ASCII cases first + if (first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_' { + return true + } + + // If our first byte begins a UTF-8 sequence then the sequence might + // be a unicode letter. + if utf8.RuneStart(first) { + firstRune, _ := utf8.DecodeRuneInString(s) + if unicode.IsLetter(firstRune) { + return true + } + } + + return false +} diff --git a/vendor/github.com/hashicorp/hil/scanner/token.go b/vendor/github.com/hashicorp/hil/scanner/token.go new file mode 100644 index 000000000..b6c82ae9b --- /dev/null +++ b/vendor/github.com/hashicorp/hil/scanner/token.go @@ -0,0 +1,105 @@ +package scanner + +import ( + "fmt" + + "github.com/hashicorp/hil/ast" +) + +type Token struct { + Type TokenType + Content string + Pos ast.Pos +} + +//go:generate stringer -type=TokenType +type TokenType rune + +const ( + // Raw string data outside of ${ .. } sequences + LITERAL TokenType = 'o' + + // STRING is like a LITERAL but it's inside a quoted string + // within a ${ ... } sequence, and so it can contain backslash + // escaping. + STRING TokenType = 'S' + + // Other Literals + INTEGER TokenType = 'I' + FLOAT TokenType = 'F' + BOOL TokenType = 'B' + + BEGIN TokenType = '$' // actually "${" + END TokenType = '}' + OQUOTE TokenType = '“' // Opening quote of a nested quoted sequence + CQUOTE TokenType = '”' // Closing quote of a nested quoted sequence + OPAREN TokenType = '(' + CPAREN TokenType = ')' + OBRACKET TokenType = '[' + CBRACKET TokenType = ']' + COMMA TokenType = ',' + + IDENTIFIER TokenType = 'i' + + PERIOD TokenType = '.' + PLUS TokenType = '+' + MINUS TokenType = '-' + STAR TokenType = '*' + SLASH TokenType = '/' + PERCENT TokenType = '%' + + AND TokenType = '∧' + OR TokenType = '∨' + BANG TokenType = '!' + + EQUAL TokenType = '=' + NOTEQUAL TokenType = '≠' + GT TokenType = '>' + LT TokenType = '<' + GTE TokenType = '≥' + LTE TokenType = '≤' + + QUESTION TokenType = '?' + COLON TokenType = ':' + + EOF TokenType = '␄' + + // Produced for sequences that cannot be understood as valid tokens + // e.g. due to use of unrecognized punctuation. + INVALID TokenType = '�' +) + +func (t *Token) String() string { + switch t.Type { + case EOF: + return "end of string" + case INVALID: + return fmt.Sprintf("invalid sequence %q", t.Content) + case INTEGER: + return fmt.Sprintf("integer %s", t.Content) + case FLOAT: + return fmt.Sprintf("float %s", t.Content) + case STRING: + return fmt.Sprintf("string %q", t.Content) + case LITERAL: + return fmt.Sprintf("literal %q", t.Content) + case OQUOTE: + return fmt.Sprintf("opening quote") + case CQUOTE: + return fmt.Sprintf("closing quote") + case AND: + return "&&" + case OR: + return "||" + case NOTEQUAL: + return "!=" + case GTE: + return ">=" + case LTE: + return "<=" + default: + // The remaining token types have content that + // speaks for itself. + return fmt.Sprintf("%q", t.Content) + } +} diff --git a/vendor/github.com/hashicorp/hil/scanner/tokentype_string.go b/vendor/github.com/hashicorp/hil/scanner/tokentype_string.go new file mode 100644 index 000000000..a602f5fdd --- /dev/null +++ b/vendor/github.com/hashicorp/hil/scanner/tokentype_string.go @@ -0,0 +1,51 @@ +// Code generated by "stringer -type=TokenType"; DO NOT EDIT + +package scanner + +import "fmt" + +const _TokenType_name = "BANGBEGINPERCENTOPARENCPARENSTARPLUSCOMMAMINUSPERIODSLASHCOLONLTEQUALGTQUESTIONBOOLFLOATINTEGERSTRINGOBRACKETCBRACKETIDENTIFIERLITERALENDOQUOTECQUOTEANDORNOTEQUALLTEGTEEOFINVALID" + +var _TokenType_map = map[TokenType]string{ + 33: _TokenType_name[0:4], + 36: _TokenType_name[4:9], + 37: _TokenType_name[9:16], + 40: _TokenType_name[16:22], + 41: _TokenType_name[22:28], + 42: _TokenType_name[28:32], + 43: _TokenType_name[32:36], + 44: _TokenType_name[36:41], + 45: _TokenType_name[41:46], + 46: _TokenType_name[46:52], + 47: _TokenType_name[52:57], + 58: _TokenType_name[57:62], + 60: _TokenType_name[62:64], + 61: _TokenType_name[64:69], + 62: _TokenType_name[69:71], + 63: _TokenType_name[71:79], + 66: _TokenType_name[79:83], + 70: _TokenType_name[83:88], + 73: _TokenType_name[88:95], + 83: _TokenType_name[95:101], + 91: _TokenType_name[101:109], + 93: _TokenType_name[109:117], + 105: _TokenType_name[117:127], + 111: _TokenType_name[127:134], + 125: _TokenType_name[134:137], + 8220: _TokenType_name[137:143], + 8221: _TokenType_name[143:149], + 8743: _TokenType_name[149:152], + 8744: _TokenType_name[152:154], + 8800: _TokenType_name[154:162], + 8804: _TokenType_name[162:165], + 8805: _TokenType_name[165:168], + 9220: _TokenType_name[168:171], + 65533: _TokenType_name[171:178], +} + +func (i TokenType) String() string { + if str, ok := _TokenType_map[i]; ok { + return str + } + return fmt.Sprintf("TokenType(%d)", i) +} diff --git a/vendor/github.com/hashicorp/hil/y.go b/vendor/github.com/hashicorp/hil/y.go deleted file mode 100644 index 30eb86aa7..000000000 --- a/vendor/github.com/hashicorp/hil/y.go +++ /dev/null @@ -1,662 +0,0 @@ -//line lang.y:6 -package hil - -import __yyfmt__ "fmt" - -//line lang.y:6 -import ( - "github.com/hashicorp/hil/ast" -) - -//line lang.y:14 -type parserSymType struct { - yys int - node ast.Node - nodeList []ast.Node - str string - token *parserToken -} - -const PROGRAM_BRACKET_LEFT = 57346 -const PROGRAM_BRACKET_RIGHT = 57347 -const PROGRAM_STRING_START = 57348 -const PROGRAM_STRING_END = 57349 -const PAREN_LEFT = 57350 -const PAREN_RIGHT = 57351 -const COMMA = 57352 -const SQUARE_BRACKET_LEFT = 57353 -const SQUARE_BRACKET_RIGHT = 57354 -const ARITH_OP = 57355 -const IDENTIFIER = 57356 -const INTEGER = 57357 -const FLOAT = 57358 -const STRING = 57359 - -var parserToknames = [...]string{ - "$end", - "error", - "$unk", - "PROGRAM_BRACKET_LEFT", - "PROGRAM_BRACKET_RIGHT", - "PROGRAM_STRING_START", - "PROGRAM_STRING_END", - "PAREN_LEFT", - "PAREN_RIGHT", - "COMMA", - "SQUARE_BRACKET_LEFT", - "SQUARE_BRACKET_RIGHT", - "ARITH_OP", - "IDENTIFIER", - "INTEGER", - "FLOAT", - "STRING", -} -var parserStatenames = [...]string{} - -const parserEofCode = 1 -const parserErrCode = 2 -const parserInitialStackSize = 16 - -//line lang.y:196 - -//line yacctab:1 -var parserExca = [...]int{ - -1, 1, - 1, -1, - -2, 0, -} - -const parserNprod = 21 -const parserPrivate = 57344 - -var parserTokenNames []string -var parserStates []string - -const parserLast = 37 - -var parserAct = [...]int{ - - 9, 7, 29, 17, 23, 16, 17, 3, 17, 20, - 8, 18, 21, 17, 6, 19, 27, 28, 22, 8, - 1, 25, 26, 7, 11, 2, 24, 10, 4, 30, - 5, 0, 14, 15, 12, 13, 6, -} -var parserPact = [...]int{ - - -3, -1000, -3, -1000, -1000, -1000, -1000, 19, -1000, 0, - 19, -3, -1000, -1000, 19, 1, -1000, 19, -5, -1000, - 19, 19, -1000, -1000, 7, -7, -10, -1000, 19, -1000, - -7, -} -var parserPgo = [...]int{ - - 0, 0, 30, 28, 24, 7, 26, 20, -} -var parserR1 = [...]int{ - - 0, 7, 7, 4, 4, 5, 5, 2, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 6, 6, 6, - 3, -} -var parserR2 = [...]int{ - - 0, 0, 1, 1, 2, 1, 1, 3, 3, 1, - 1, 1, 2, 3, 1, 4, 4, 0, 3, 1, - 1, -} -var parserChk = [...]int{ - - -1000, -7, -4, -5, -3, -2, 17, 4, -5, -1, - 8, -4, 15, 16, 13, 14, 5, 13, -1, -1, - 8, 11, -1, 9, -6, -1, -1, 9, 10, 12, - -1, -} -var parserDef = [...]int{ - - 1, -2, 2, 3, 5, 6, 20, 0, 4, 0, - 0, 9, 10, 11, 0, 14, 7, 0, 0, 12, - 17, 0, 13, 8, 0, 19, 0, 15, 0, 16, - 18, -} -var parserTok1 = [...]int{ - - 1, -} -var parserTok2 = [...]int{ - - 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, - 12, 13, 14, 15, 16, 17, -} -var parserTok3 = [...]int{ - 0, -} - -var parserErrorMessages = [...]struct { - state int - token int - msg string -}{} - -//line yaccpar:1 - -/* parser for yacc output */ - -var ( - parserDebug = 0 - parserErrorVerbose = false -) - -type parserLexer interface { - Lex(lval *parserSymType) int - Error(s string) -} - -type parserParser interface { - Parse(parserLexer) int - Lookahead() int -} - -type parserParserImpl struct { - lval parserSymType - stack [parserInitialStackSize]parserSymType - char int -} - -func (p *parserParserImpl) Lookahead() int { - return p.char -} - -func parserNewParser() parserParser { - return &parserParserImpl{} -} - -const parserFlag = -1000 - -func parserTokname(c int) string { - if c >= 1 && c-1 < len(parserToknames) { - if parserToknames[c-1] != "" { - return parserToknames[c-1] - } - } - return __yyfmt__.Sprintf("tok-%v", c) -} - -func parserStatname(s int) string { - if s >= 0 && s < len(parserStatenames) { - if parserStatenames[s] != "" { - return parserStatenames[s] - } - } - return __yyfmt__.Sprintf("state-%v", s) -} - -func parserErrorMessage(state, lookAhead int) string { - const TOKSTART = 4 - - if !parserErrorVerbose { - return "syntax error" - } - - for _, e := range parserErrorMessages { - if e.state == state && e.token == lookAhead { - return "syntax error: " + e.msg - } - } - - res := "syntax error: unexpected " + parserTokname(lookAhead) - - // To match Bison, suggest at most four expected tokens. - expected := make([]int, 0, 4) - - // Look for shiftable tokens. - base := parserPact[state] - for tok := TOKSTART; tok-1 < len(parserToknames); tok++ { - if n := base + tok; n >= 0 && n < parserLast && parserChk[parserAct[n]] == tok { - if len(expected) == cap(expected) { - return res - } - expected = append(expected, tok) - } - } - - if parserDef[state] == -2 { - i := 0 - for parserExca[i] != -1 || parserExca[i+1] != state { - i += 2 - } - - // Look for tokens that we accept or reduce. - for i += 2; parserExca[i] >= 0; i += 2 { - tok := parserExca[i] - if tok < TOKSTART || parserExca[i+1] == 0 { - continue - } - if len(expected) == cap(expected) { - return res - } - expected = append(expected, tok) - } - - // If the default action is to accept or reduce, give up. - if parserExca[i+1] != 0 { - return res - } - } - - for i, tok := range expected { - if i == 0 { - res += ", expecting " - } else { - res += " or " - } - res += parserTokname(tok) - } - return res -} - -func parserlex1(lex parserLexer, lval *parserSymType) (char, token int) { - token = 0 - char = lex.Lex(lval) - if char <= 0 { - token = parserTok1[0] - goto out - } - if char < len(parserTok1) { - token = parserTok1[char] - goto out - } - if char >= parserPrivate { - if char < parserPrivate+len(parserTok2) { - token = parserTok2[char-parserPrivate] - goto out - } - } - for i := 0; i < len(parserTok3); i += 2 { - token = parserTok3[i+0] - if token == char { - token = parserTok3[i+1] - goto out - } - } - -out: - if token == 0 { - token = parserTok2[1] /* unknown char */ - } - if parserDebug >= 3 { - __yyfmt__.Printf("lex %s(%d)\n", parserTokname(token), uint(char)) - } - return char, token -} - -func parserParse(parserlex parserLexer) int { - return parserNewParser().Parse(parserlex) -} - -func (parserrcvr *parserParserImpl) Parse(parserlex parserLexer) int { - var parsern int - var parserVAL parserSymType - var parserDollar []parserSymType - _ = parserDollar // silence set and not used - parserS := parserrcvr.stack[:] - - Nerrs := 0 /* number of errors */ - Errflag := 0 /* error recovery flag */ - parserstate := 0 - parserrcvr.char = -1 - parsertoken := -1 // parserrcvr.char translated into internal numbering - defer func() { - // Make sure we report no lookahead when not parsing. - parserstate = -1 - parserrcvr.char = -1 - parsertoken = -1 - }() - parserp := -1 - goto parserstack - -ret0: - return 0 - -ret1: - return 1 - -parserstack: - /* put a state and value onto the stack */ - if parserDebug >= 4 { - __yyfmt__.Printf("char %v in %v\n", parserTokname(parsertoken), parserStatname(parserstate)) - } - - parserp++ - if parserp >= len(parserS) { - nyys := make([]parserSymType, len(parserS)*2) - copy(nyys, parserS) - parserS = nyys - } - parserS[parserp] = parserVAL - parserS[parserp].yys = parserstate - -parsernewstate: - parsern = parserPact[parserstate] - if parsern <= parserFlag { - goto parserdefault /* simple state */ - } - if parserrcvr.char < 0 { - parserrcvr.char, parsertoken = parserlex1(parserlex, &parserrcvr.lval) - } - parsern += parsertoken - if parsern < 0 || parsern >= parserLast { - goto parserdefault - } - parsern = parserAct[parsern] - if parserChk[parsern] == parsertoken { /* valid shift */ - parserrcvr.char = -1 - parsertoken = -1 - parserVAL = parserrcvr.lval - parserstate = parsern - if Errflag > 0 { - Errflag-- - } - goto parserstack - } - -parserdefault: - /* default state action */ - parsern = parserDef[parserstate] - if parsern == -2 { - if parserrcvr.char < 0 { - parserrcvr.char, parsertoken = parserlex1(parserlex, &parserrcvr.lval) - } - - /* look through exception table */ - xi := 0 - for { - if parserExca[xi+0] == -1 && parserExca[xi+1] == parserstate { - break - } - xi += 2 - } - for xi += 2; ; xi += 2 { - parsern = parserExca[xi+0] - if parsern < 0 || parsern == parsertoken { - break - } - } - parsern = parserExca[xi+1] - if parsern < 0 { - goto ret0 - } - } - if parsern == 0 { - /* error ... attempt to resume parsing */ - switch Errflag { - case 0: /* brand new error */ - parserlex.Error(parserErrorMessage(parserstate, parsertoken)) - Nerrs++ - if parserDebug >= 1 { - __yyfmt__.Printf("%s", parserStatname(parserstate)) - __yyfmt__.Printf(" saw %s\n", parserTokname(parsertoken)) - } - fallthrough - - case 1, 2: /* incompletely recovered error ... try again */ - Errflag = 3 - - /* find a state where "error" is a legal shift action */ - for parserp >= 0 { - parsern = parserPact[parserS[parserp].yys] + parserErrCode - if parsern >= 0 && parsern < parserLast { - parserstate = parserAct[parsern] /* simulate a shift of "error" */ - if parserChk[parserstate] == parserErrCode { - goto parserstack - } - } - - /* the current p has no shift on "error", pop stack */ - if parserDebug >= 2 { - __yyfmt__.Printf("error recovery pops state %d\n", parserS[parserp].yys) - } - parserp-- - } - /* there is no state on the stack with an error shift ... abort */ - goto ret1 - - case 3: /* no shift yet; clobber input char */ - if parserDebug >= 2 { - __yyfmt__.Printf("error recovery discards %s\n", parserTokname(parsertoken)) - } - if parsertoken == parserEofCode { - goto ret1 - } - parserrcvr.char = -1 - parsertoken = -1 - goto parsernewstate /* try again in the same state */ - } - } - - /* reduction by production parsern */ - if parserDebug >= 2 { - __yyfmt__.Printf("reduce %v in:\n\t%v\n", parsern, parserStatname(parserstate)) - } - - parsernt := parsern - parserpt := parserp - _ = parserpt // guard against "declared and not used" - - parserp -= parserR2[parsern] - // parserp is now the index of $0. Perform the default action. Iff the - // reduced production is ε, $1 is possibly out of range. - if parserp+1 >= len(parserS) { - nyys := make([]parserSymType, len(parserS)*2) - copy(nyys, parserS) - parserS = nyys - } - parserVAL = parserS[parserp+1] - - /* consult goto table to find next state */ - parsern = parserR1[parsern] - parserg := parserPgo[parsern] - parserj := parserg + parserS[parserp].yys + 1 - - if parserj >= parserLast { - parserstate = parserAct[parserg] - } else { - parserstate = parserAct[parserj] - if parserChk[parserstate] != -parsern { - parserstate = parserAct[parserg] - } - } - // dummy call; replaced with literal code - switch parsernt { - - case 1: - parserDollar = parserS[parserpt-0 : parserpt+1] - //line lang.y:36 - { - parserResult = &ast.LiteralNode{ - Value: "", - Typex: ast.TypeString, - Posx: ast.Pos{Column: 1, Line: 1}, - } - } - case 2: - parserDollar = parserS[parserpt-1 : parserpt+1] - //line lang.y:44 - { - parserResult = parserDollar[1].node - - // 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.Output); !ok { - if n, ok := parserDollar[1].node.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString { - parserResult = &ast.Output{ - Exprs: []ast.Node{parserDollar[1].node}, - Posx: parserDollar[1].node.Pos(), - } - } - } - } - case 3: - parserDollar = parserS[parserpt-1 : parserpt+1] - //line lang.y:67 - { - parserVAL.node = parserDollar[1].node - } - case 4: - parserDollar = parserS[parserpt-2 : parserpt+1] - //line lang.y:71 - { - var result []ast.Node - 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.Output{ - Exprs: result, - Posx: result[0].Pos(), - } - } - case 5: - parserDollar = parserS[parserpt-1 : parserpt+1] - //line lang.y:87 - { - parserVAL.node = parserDollar[1].node - } - case 6: - parserDollar = parserS[parserpt-1 : parserpt+1] - //line lang.y:91 - { - parserVAL.node = parserDollar[1].node - } - case 7: - parserDollar = parserS[parserpt-3 : parserpt+1] - //line lang.y:97 - { - parserVAL.node = parserDollar[2].node - } - case 8: - parserDollar = parserS[parserpt-3 : parserpt+1] - //line lang.y:103 - { - parserVAL.node = parserDollar[2].node - } - case 9: - parserDollar = parserS[parserpt-1 : parserpt+1] - //line lang.y:107 - { - parserVAL.node = parserDollar[1].node - } - case 10: - parserDollar = parserS[parserpt-1 : parserpt+1] - //line lang.y:111 - { - parserVAL.node = &ast.LiteralNode{ - Value: parserDollar[1].token.Value.(int), - Typex: ast.TypeInt, - Posx: parserDollar[1].token.Pos, - } - } - case 11: - parserDollar = parserS[parserpt-1 : parserpt+1] - //line lang.y:119 - { - parserVAL.node = &ast.LiteralNode{ - Value: parserDollar[1].token.Value.(float64), - Typex: ast.TypeFloat, - Posx: parserDollar[1].token.Pos, - } - } - case 12: - parserDollar = parserS[parserpt-2 : parserpt+1] - //line lang.y:127 - { - // This is REALLY jank. We assume that a singular ARITH_OP - // means 0 ARITH_OP expr, which... is weird. We don't want to - // support *, /, etc., only -. We should fix this later with a pure - // Go scanner/parser. - if parserDollar[1].token.Value.(ast.ArithmeticOp) != ast.ArithmeticOpSub { - panic("Unary - is only allowed") - } - - parserVAL.node = &ast.Arithmetic{ - Op: parserDollar[1].token.Value.(ast.ArithmeticOp), - Exprs: []ast.Node{ - &ast.LiteralNode{Value: 0, Typex: ast.TypeInt}, - parserDollar[2].node, - }, - Posx: parserDollar[2].node.Pos(), - } - } - case 13: - parserDollar = parserS[parserpt-3 : parserpt+1] - //line lang.y:146 - { - parserVAL.node = &ast.Arithmetic{ - Op: parserDollar[2].token.Value.(ast.ArithmeticOp), - Exprs: []ast.Node{parserDollar[1].node, parserDollar[3].node}, - Posx: parserDollar[1].node.Pos(), - } - } - case 14: - parserDollar = parserS[parserpt-1 : parserpt+1] - //line lang.y:154 - { - parserVAL.node = &ast.VariableAccess{Name: parserDollar[1].token.Value.(string), Posx: parserDollar[1].token.Pos} - } - case 15: - parserDollar = parserS[parserpt-4 : parserpt+1] - //line lang.y:158 - { - parserVAL.node = &ast.Call{Func: parserDollar[1].token.Value.(string), Args: parserDollar[3].nodeList, Posx: parserDollar[1].token.Pos} - } - case 16: - parserDollar = parserS[parserpt-4 : parserpt+1] - //line lang.y:162 - { - parserVAL.node = &ast.Index{ - Target: &ast.VariableAccess{ - Name: parserDollar[1].token.Value.(string), - Posx: parserDollar[1].token.Pos, - }, - Key: parserDollar[3].node, - Posx: parserDollar[1].token.Pos, - } - } - case 17: - parserDollar = parserS[parserpt-0 : parserpt+1] - //line lang.y:174 - { - parserVAL.nodeList = nil - } - case 18: - parserDollar = parserS[parserpt-3 : parserpt+1] - //line lang.y:178 - { - parserVAL.nodeList = append(parserDollar[1].nodeList, parserDollar[3].node) - } - case 19: - parserDollar = parserS[parserpt-1 : parserpt+1] - //line lang.y:182 - { - parserVAL.nodeList = append(parserVAL.nodeList, parserDollar[1].node) - } - case 20: - parserDollar = parserS[parserpt-1 : parserpt+1] - //line lang.y:188 - { - parserVAL.node = &ast.LiteralNode{ - Value: parserDollar[1].token.Value.(string), - Typex: ast.TypeString, - Posx: parserDollar[1].token.Pos, - } - } - } - goto parserstack /* stack new state and value */ -} diff --git a/vendor/github.com/hashicorp/hil/y.output b/vendor/github.com/hashicorp/hil/y.output deleted file mode 100644 index 26df788c5..000000000 --- a/vendor/github.com/hashicorp/hil/y.output +++ /dev/null @@ -1,328 +0,0 @@ - -state 0 - $accept: .top $end - top: . (1) - - PROGRAM_BRACKET_LEFT shift 7 - STRING shift 6 - . reduce 1 (src line 35) - - interpolation goto 5 - literal goto 4 - literalModeTop goto 2 - literalModeValue goto 3 - top goto 1 - -state 1 - $accept: top.$end - - $end accept - . error - - -state 2 - top: literalModeTop. (2) - literalModeTop: literalModeTop.literalModeValue - - PROGRAM_BRACKET_LEFT shift 7 - STRING shift 6 - . reduce 2 (src line 43) - - interpolation goto 5 - literal goto 4 - literalModeValue goto 8 - -state 3 - literalModeTop: literalModeValue. (3) - - . reduce 3 (src line 65) - - -state 4 - literalModeValue: literal. (5) - - . reduce 5 (src line 85) - - -state 5 - literalModeValue: interpolation. (6) - - . reduce 6 (src line 90) - - -state 6 - literal: STRING. (20) - - . reduce 20 (src line 186) - - -state 7 - interpolation: PROGRAM_BRACKET_LEFT.expr PROGRAM_BRACKET_RIGHT - - PROGRAM_BRACKET_LEFT shift 7 - PAREN_LEFT shift 10 - ARITH_OP shift 14 - IDENTIFIER shift 15 - INTEGER shift 12 - FLOAT shift 13 - STRING shift 6 - . error - - expr goto 9 - interpolation goto 5 - literal goto 4 - literalModeTop goto 11 - literalModeValue goto 3 - -state 8 - literalModeTop: literalModeTop literalModeValue. (4) - - . reduce 4 (src line 70) - - -state 9 - interpolation: PROGRAM_BRACKET_LEFT expr.PROGRAM_BRACKET_RIGHT - expr: expr.ARITH_OP expr - - PROGRAM_BRACKET_RIGHT shift 16 - ARITH_OP shift 17 - . error - - -state 10 - expr: PAREN_LEFT.expr PAREN_RIGHT - - PROGRAM_BRACKET_LEFT shift 7 - PAREN_LEFT shift 10 - ARITH_OP shift 14 - IDENTIFIER shift 15 - INTEGER shift 12 - FLOAT shift 13 - STRING shift 6 - . error - - expr goto 18 - interpolation goto 5 - literal goto 4 - literalModeTop goto 11 - literalModeValue goto 3 - -state 11 - literalModeTop: literalModeTop.literalModeValue - expr: literalModeTop. (9) - - PROGRAM_BRACKET_LEFT shift 7 - STRING shift 6 - . reduce 9 (src line 106) - - interpolation goto 5 - literal goto 4 - literalModeValue goto 8 - -state 12 - expr: INTEGER. (10) - - . reduce 10 (src line 110) - - -state 13 - expr: FLOAT. (11) - - . reduce 11 (src line 118) - - -state 14 - expr: ARITH_OP.expr - - PROGRAM_BRACKET_LEFT shift 7 - PAREN_LEFT shift 10 - ARITH_OP shift 14 - IDENTIFIER shift 15 - INTEGER shift 12 - FLOAT shift 13 - STRING shift 6 - . error - - expr goto 19 - interpolation goto 5 - literal goto 4 - literalModeTop goto 11 - literalModeValue goto 3 - -state 15 - expr: IDENTIFIER. (14) - expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT - expr: IDENTIFIER.SQUARE_BRACKET_LEFT expr SQUARE_BRACKET_RIGHT - - PAREN_LEFT shift 20 - SQUARE_BRACKET_LEFT shift 21 - . reduce 14 (src line 153) - - -state 16 - interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (7) - - . reduce 7 (src line 95) - - -state 17 - expr: expr ARITH_OP.expr - - PROGRAM_BRACKET_LEFT shift 7 - PAREN_LEFT shift 10 - ARITH_OP shift 14 - IDENTIFIER shift 15 - INTEGER shift 12 - FLOAT shift 13 - STRING shift 6 - . error - - expr goto 22 - interpolation goto 5 - literal goto 4 - literalModeTop goto 11 - literalModeValue goto 3 - -state 18 - expr: PAREN_LEFT expr.PAREN_RIGHT - expr: expr.ARITH_OP expr - - PAREN_RIGHT shift 23 - ARITH_OP shift 17 - . error - - -state 19 - expr: ARITH_OP expr. (12) - expr: expr.ARITH_OP expr - - . reduce 12 (src line 126) - - -state 20 - expr: IDENTIFIER PAREN_LEFT.args PAREN_RIGHT - args: . (17) - - PROGRAM_BRACKET_LEFT shift 7 - PAREN_LEFT shift 10 - ARITH_OP shift 14 - IDENTIFIER shift 15 - INTEGER shift 12 - FLOAT shift 13 - STRING shift 6 - . reduce 17 (src line 173) - - expr goto 25 - interpolation goto 5 - literal goto 4 - literalModeTop goto 11 - literalModeValue goto 3 - args goto 24 - -state 21 - expr: IDENTIFIER SQUARE_BRACKET_LEFT.expr SQUARE_BRACKET_RIGHT - - PROGRAM_BRACKET_LEFT shift 7 - PAREN_LEFT shift 10 - ARITH_OP shift 14 - IDENTIFIER shift 15 - INTEGER shift 12 - FLOAT shift 13 - STRING shift 6 - . error - - expr goto 26 - interpolation goto 5 - literal goto 4 - literalModeTop goto 11 - literalModeValue goto 3 - -state 22 - expr: expr.ARITH_OP expr - expr: expr ARITH_OP expr. (13) - - . reduce 13 (src line 145) - - -state 23 - expr: PAREN_LEFT expr PAREN_RIGHT. (8) - - . reduce 8 (src line 101) - - -state 24 - expr: IDENTIFIER PAREN_LEFT args.PAREN_RIGHT - args: args.COMMA expr - - PAREN_RIGHT shift 27 - COMMA shift 28 - . error - - -state 25 - expr: expr.ARITH_OP expr - args: expr. (19) - - ARITH_OP shift 17 - . reduce 19 (src line 181) - - -state 26 - expr: expr.ARITH_OP expr - expr: IDENTIFIER SQUARE_BRACKET_LEFT expr.SQUARE_BRACKET_RIGHT - - SQUARE_BRACKET_RIGHT shift 29 - ARITH_OP shift 17 - . error - - -state 27 - expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (15) - - . reduce 15 (src line 157) - - -state 28 - args: args COMMA.expr - - PROGRAM_BRACKET_LEFT shift 7 - PAREN_LEFT shift 10 - ARITH_OP shift 14 - IDENTIFIER shift 15 - INTEGER shift 12 - FLOAT shift 13 - STRING shift 6 - . error - - expr goto 30 - interpolation goto 5 - literal goto 4 - literalModeTop goto 11 - literalModeValue goto 3 - -state 29 - expr: IDENTIFIER SQUARE_BRACKET_LEFT expr SQUARE_BRACKET_RIGHT. (16) - - . reduce 16 (src line 161) - - -state 30 - expr: expr.ARITH_OP expr - args: args COMMA expr. (18) - - ARITH_OP shift 17 - . reduce 18 (src line 177) - - -17 terminals, 8 nonterminals -21 grammar rules, 31/2000 states -0 shift/reduce, 0 reduce/reduce conflicts reported -57 working sets used -memory: parser 45/30000 -26 extra closures -67 shift entries, 1 exceptions -16 goto entries -31 entries saved by goto default -Optimizer space used: output 37/30000 -37 table entries, 1 zero -maximum spread: 17, maximum offset: 28 diff --git a/vendor/modules.txt b/vendor/modules.txt index b3061239f..dbcf96254 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -267,9 +267,11 @@ github.com/hashicorp/hcl/hcl/token github.com/hashicorp/hcl/json/parser github.com/hashicorp/hcl/json/scanner github.com/hashicorp/hcl/json/token -# github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5 +# github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038 github.com/hashicorp/hil github.com/hashicorp/hil/ast +github.com/hashicorp/hil/parser +github.com/hashicorp/hil/scanner # github.com/hashicorp/mdns v1.0.1 github.com/hashicorp/mdns # github.com/hashicorp/memberlist v0.2.2