Merge pull request #10591 from hashicorp/b-update-hil
vendor: update HIL with conditionals
This commit is contained in:
commit
f7abd6eb1c
|
@ -5,9 +5,20 @@ type ArithmeticOp int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ArithmeticOpInvalid ArithmeticOp = 0
|
ArithmeticOpInvalid ArithmeticOp = 0
|
||||||
|
|
||||||
ArithmeticOpAdd ArithmeticOp = iota
|
ArithmeticOpAdd ArithmeticOp = iota
|
||||||
ArithmeticOpSub
|
ArithmeticOpSub
|
||||||
ArithmeticOpMul
|
ArithmeticOpMul
|
||||||
ArithmeticOpDiv
|
ArithmeticOpDiv
|
||||||
ArithmeticOpMod
|
ArithmeticOpMod
|
||||||
|
|
||||||
|
ArithmeticOpLogicalAnd
|
||||||
|
ArithmeticOpLogicalOr
|
||||||
|
|
||||||
|
ArithmeticOpEqual
|
||||||
|
ArithmeticOpNotEqual
|
||||||
|
ArithmeticOpLessThan
|
||||||
|
ArithmeticOpLessThanOrEqual
|
||||||
|
ArithmeticOpGreaterThan
|
||||||
|
ArithmeticOpGreaterThanOrEqual
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LiteralNode represents a single literal value, such as "foo" or
|
// LiteralNode represents a single literal value, such as "foo" or
|
||||||
|
@ -12,6 +13,51 @@ type LiteralNode struct {
|
||||||
Posx Pos
|
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 {
|
func (n *LiteralNode) Accept(v Visitor) Node {
|
||||||
return v(n)
|
return v(n)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,11 @@ func registerBuiltins(scope *ast.BasicScope) *ast.BasicScope {
|
||||||
// Math operations
|
// Math operations
|
||||||
scope.FuncMap["__builtin_IntMath"] = builtinIntMath()
|
scope.FuncMap["__builtin_IntMath"] = builtinIntMath()
|
||||||
scope.FuncMap["__builtin_FloatMath"] = builtinFloatMath()
|
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
|
return scope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,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 {
|
func builtinFloatToInt() ast.Function {
|
||||||
return ast.Function{
|
return ast.Function{
|
||||||
ArgTypes: []ast.Type{ast.TypeFloat},
|
ArgTypes: []ast.Type{ast.TypeFloat},
|
||||||
|
|
|
@ -67,6 +67,9 @@ func (v *TypeCheck) visit(raw ast.Node) ast.Node {
|
||||||
case *ast.Call:
|
case *ast.Call:
|
||||||
tc := &typeCheckCall{n}
|
tc := &typeCheckCall{n}
|
||||||
result, err = tc.TypeCheck(v)
|
result, err = tc.TypeCheck(v)
|
||||||
|
case *ast.Conditional:
|
||||||
|
tc := &typeCheckConditional{n}
|
||||||
|
result, err = tc.TypeCheck(v)
|
||||||
case *ast.Index:
|
case *ast.Index:
|
||||||
tc := &typeCheckIndex{n}
|
tc := &typeCheckIndex{n}
|
||||||
result, err = tc.TypeCheck(v)
|
result, err = tc.TypeCheck(v)
|
||||||
|
@ -113,6 +116,18 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||||||
exprs[len(tc.n.Exprs)-1-i] = v.StackPop()
|
exprs[len(tc.n.Exprs)-1-i] = v.StackPop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Determine the resulting type we want. We do this by going over
|
||||||
// every expression until we find one with a type we recognize.
|
// every expression until we find one with a type we recognize.
|
||||||
// We do this because the first expr might be a string ("var.foo")
|
// We do this because the first expr might be a string ("var.foo")
|
||||||
|
@ -177,6 +192,116 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||||||
}, nil
|
}, 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",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
type typeCheckCall struct {
|
||||||
n *ast.Call
|
n *ast.Call
|
||||||
}
|
}
|
||||||
|
@ -240,6 +365,79 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||||||
return tc.n, nil
|
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.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 {
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
v.StackPush(trueType)
|
||||||
|
|
||||||
|
return tc.n, nil
|
||||||
|
}
|
||||||
|
|
||||||
type typeCheckOutput struct {
|
type typeCheckOutput struct {
|
||||||
n *ast.Output
|
n *ast.Output
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,6 +232,8 @@ func evalNode(raw ast.Node) (EvalNode, error) {
|
||||||
return &evalIndex{n}, nil
|
return &evalIndex{n}, nil
|
||||||
case *ast.Call:
|
case *ast.Call:
|
||||||
return &evalCall{n}, nil
|
return &evalCall{n}, nil
|
||||||
|
case *ast.Conditional:
|
||||||
|
return &evalConditional{n}, nil
|
||||||
case *ast.Output:
|
case *ast.Output:
|
||||||
return &evalOutput{n}, nil
|
return &evalOutput{n}, nil
|
||||||
case *ast.LiteralNode:
|
case *ast.LiteralNode:
|
||||||
|
@ -274,6 +276,23 @@ func (v *evalCall) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, e
|
||||||
return result, function.ReturnType, nil
|
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.Value.(bool) {
|
||||||
|
return trueLit.Value, trueLit.Typex, nil
|
||||||
|
} else {
|
||||||
|
return falseLit.Value, trueLit.Typex, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type evalIndex struct{ *ast.Index }
|
type evalIndex struct{ *ast.Index }
|
||||||
|
|
||||||
func (v *evalIndex) Eval(scope ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) {
|
func (v *evalIndex) Eval(scope ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) {
|
||||||
|
|
|
@ -16,6 +16,22 @@ func init() {
|
||||||
// the *lowest* precedence first. Operators within the same group
|
// the *lowest* precedence first. Operators within the same group
|
||||||
// have left-to-right associativity.
|
// have left-to-right associativity.
|
||||||
binaryOps = []map[scanner.TokenType]ast.ArithmeticOp{
|
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.PLUS: ast.ArithmeticOpAdd,
|
||||||
scanner.MINUS: ast.ArithmeticOpSub,
|
scanner.MINUS: ast.ArithmeticOpSub,
|
||||||
|
|
|
@ -191,7 +191,56 @@ func (p *parser) ParseInterpolation() (ast.Node, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) ParseExpression() (ast.Node, error) {
|
func (p *parser) ParseExpression() (ast.Node, error) {
|
||||||
return p.parseBinaryOps(binaryOps)
|
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
|
// parseBinaryOps calls itself recursively to work through all of the
|
||||||
|
@ -348,6 +397,30 @@ func (p *parser) ParseExpressionTerm() (ast.Node, error) {
|
||||||
Posx: opTok.Pos,
|
Posx: opTok.Pos,
|
||||||
}, nil
|
}, 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:
|
case scanner.IDENTIFIER:
|
||||||
return p.ParseScopeInteraction()
|
return p.ParseScopeInteraction()
|
||||||
|
|
||||||
|
|
|
@ -168,7 +168,7 @@ func scanInterpolationToken(s string, startPos ast.Pos) (*Token, int, ast.Pos) {
|
||||||
var token *Token
|
var token *Token
|
||||||
|
|
||||||
switch next {
|
switch next {
|
||||||
case '(', ')', '[', ']', ',', '.', '+', '-', '*', '/', '%':
|
case '(', ')', '[', ']', ',', '.', '+', '-', '*', '/', '%', '?', ':':
|
||||||
// Easy punctuation symbols that don't have any special meaning
|
// Easy punctuation symbols that don't have any special meaning
|
||||||
// during scanning, and that stand for themselves in the
|
// during scanning, and that stand for themselves in the
|
||||||
// TokenType enumeration.
|
// TokenType enumeration.
|
||||||
|
@ -189,6 +189,91 @@ func scanInterpolationToken(s string, startPos ast.Pos) (*Token, int, ast.Pos) {
|
||||||
Content: s[:1],
|
Content: s[:1],
|
||||||
Pos: pos,
|
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:
|
default:
|
||||||
if next >= '0' && next <= '9' {
|
if next >= '0' && next <= '9' {
|
||||||
num, numType := scanNumber(s)
|
num, numType := scanNumber(s)
|
||||||
|
|
|
@ -48,6 +48,20 @@ const (
|
||||||
SLASH TokenType = '/'
|
SLASH TokenType = '/'
|
||||||
PERCENT 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 = '␄'
|
EOF TokenType = '␄'
|
||||||
|
|
||||||
// Produced for sequences that cannot be understood as valid tokens
|
// Produced for sequences that cannot be understood as valid tokens
|
||||||
|
@ -73,6 +87,16 @@ func (t *Token) String() string {
|
||||||
return fmt.Sprintf("opening quote")
|
return fmt.Sprintf("opening quote")
|
||||||
case CQUOTE:
|
case CQUOTE:
|
||||||
return fmt.Sprintf("closing quote")
|
return fmt.Sprintf("closing quote")
|
||||||
|
case AND:
|
||||||
|
return "&&"
|
||||||
|
case OR:
|
||||||
|
return "||"
|
||||||
|
case NOTEQUAL:
|
||||||
|
return "!="
|
||||||
|
case GTE:
|
||||||
|
return ">="
|
||||||
|
case LTE:
|
||||||
|
return "<="
|
||||||
default:
|
default:
|
||||||
// The remaining token types have content that
|
// The remaining token types have content that
|
||||||
// speaks for itself.
|
// speaks for itself.
|
||||||
|
|
|
@ -4,32 +4,43 @@ package scanner
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
const _TokenType_name = "BEGINPERCENTOPARENCPARENSTARPLUSCOMMAMINUSPERIODSLASHBOOLFLOATINTEGERSTRINGOBRACKETCBRACKETIDENTIFIERLITERALENDOQUOTECQUOTEEOFINVALID"
|
const _TokenType_name = "BANGBEGINPERCENTOPARENCPARENSTARPLUSCOMMAMINUSPERIODSLASHCOLONLTEQUALGTQUESTIONBOOLFLOATINTEGERSTRINGOBRACKETCBRACKETIDENTIFIERLITERALENDOQUOTECQUOTEANDORNOTEQUALLTEGTEEOFINVALID"
|
||||||
|
|
||||||
var _TokenType_map = map[TokenType]string{
|
var _TokenType_map = map[TokenType]string{
|
||||||
36: _TokenType_name[0:5],
|
33: _TokenType_name[0:4],
|
||||||
37: _TokenType_name[5:12],
|
36: _TokenType_name[4:9],
|
||||||
40: _TokenType_name[12:18],
|
37: _TokenType_name[9:16],
|
||||||
41: _TokenType_name[18:24],
|
40: _TokenType_name[16:22],
|
||||||
42: _TokenType_name[24:28],
|
41: _TokenType_name[22:28],
|
||||||
43: _TokenType_name[28:32],
|
42: _TokenType_name[28:32],
|
||||||
44: _TokenType_name[32:37],
|
43: _TokenType_name[32:36],
|
||||||
45: _TokenType_name[37:42],
|
44: _TokenType_name[36:41],
|
||||||
46: _TokenType_name[42:48],
|
45: _TokenType_name[41:46],
|
||||||
47: _TokenType_name[48:53],
|
46: _TokenType_name[46:52],
|
||||||
66: _TokenType_name[53:57],
|
47: _TokenType_name[52:57],
|
||||||
70: _TokenType_name[57:62],
|
58: _TokenType_name[57:62],
|
||||||
73: _TokenType_name[62:69],
|
60: _TokenType_name[62:64],
|
||||||
83: _TokenType_name[69:75],
|
61: _TokenType_name[64:69],
|
||||||
91: _TokenType_name[75:83],
|
62: _TokenType_name[69:71],
|
||||||
93: _TokenType_name[83:91],
|
63: _TokenType_name[71:79],
|
||||||
105: _TokenType_name[91:101],
|
66: _TokenType_name[79:83],
|
||||||
111: _TokenType_name[101:108],
|
70: _TokenType_name[83:88],
|
||||||
125: _TokenType_name[108:111],
|
73: _TokenType_name[88:95],
|
||||||
8220: _TokenType_name[111:117],
|
83: _TokenType_name[95:101],
|
||||||
8221: _TokenType_name[117:123],
|
91: _TokenType_name[101:109],
|
||||||
9220: _TokenType_name[123:126],
|
93: _TokenType_name[109:117],
|
||||||
65533: _TokenType_name[126:133],
|
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 {
|
func (i TokenType) String() string {
|
||||||
|
|
|
@ -1586,28 +1586,28 @@
|
||||||
"revisionTime": "2016-11-30T20:58:18Z"
|
"revisionTime": "2016-11-30T20:58:18Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "/TJCBetWCMVsOpehJzVk3S/xtWM=",
|
"checksumSHA1": "xONRNgLDc5OqCUmyDN3iBRKmKB0=",
|
||||||
"path": "github.com/hashicorp/hil",
|
"path": "github.com/hashicorp/hil",
|
||||||
"revision": "76d6c606283f17780baaad00df4b6ce8e9fca941",
|
"revision": "bf046eec69cc383b7f32c47990336409601c8116",
|
||||||
"revisionTime": "2016-11-20T06:09:40Z"
|
"revisionTime": "2016-12-04T02:32:26Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "YPJwewz3dAqEWOGP2qIIWeCufF0=",
|
"checksumSHA1": "oZ2a2x9qyHqvqJdv/Du3LGeaFdA=",
|
||||||
"path": "github.com/hashicorp/hil/ast",
|
"path": "github.com/hashicorp/hil/ast",
|
||||||
"revision": "76d6c606283f17780baaad00df4b6ce8e9fca941",
|
"revision": "bf046eec69cc383b7f32c47990336409601c8116",
|
||||||
"revisionTime": "2016-11-20T06:09:40Z"
|
"revisionTime": "2016-12-04T02:32:26Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "+mEEIN7pTcda+Vu837rQBkHYzao=",
|
"checksumSHA1": "p/zZysJW/AaPXXPCI20nM2arPnw=",
|
||||||
"path": "github.com/hashicorp/hil/parser",
|
"path": "github.com/hashicorp/hil/parser",
|
||||||
"revision": "76d6c606283f17780baaad00df4b6ce8e9fca941",
|
"revision": "bf046eec69cc383b7f32c47990336409601c8116",
|
||||||
"revisionTime": "2016-11-20T06:09:40Z"
|
"revisionTime": "2016-12-04T02:32:26Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "2aIAMbqPrE0qyYoJSL6qjVm+Tt0=",
|
"checksumSHA1": "FlzgVCYqnODad4pKujXhSaGcrIo=",
|
||||||
"path": "github.com/hashicorp/hil/scanner",
|
"path": "github.com/hashicorp/hil/scanner",
|
||||||
"revision": "76d6c606283f17780baaad00df4b6ce8e9fca941",
|
"revision": "bf046eec69cc383b7f32c47990336409601c8116",
|
||||||
"revisionTime": "2016-11-20T06:09:40Z"
|
"revisionTime": "2016-12-04T02:32:26Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "github.com/hashicorp/logutils",
|
"path": "github.com/hashicorp/logutils",
|
||||||
|
|
Loading…
Reference in New Issue