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 (
|
||||
ArithmeticOpInvalid ArithmeticOp = 0
|
||||
|
||||
ArithmeticOpAdd ArithmeticOp = iota
|
||||
ArithmeticOpSub
|
||||
ArithmeticOpMul
|
||||
ArithmeticOpDiv
|
||||
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 (
|
||||
"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)
|
||||
}
|
||||
|
|
|
@ -30,6 +30,11 @@ func registerBuiltins(scope *ast.BasicScope) *ast.BasicScope {
|
|||
// 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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
return ast.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeFloat},
|
||||
|
|
|
@ -67,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)
|
||||
|
@ -113,6 +116,18 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
|||
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
|
||||
// every expression until we find one with a type we recognize.
|
||||
// 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
|
||||
}
|
||||
|
||||
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 {
|
||||
n *ast.Call
|
||||
}
|
||||
|
@ -240,6 +365,79 @@ 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.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 {
|
||||
n *ast.Output
|
||||
}
|
||||
|
|
|
@ -232,6 +232,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:
|
||||
|
@ -274,6 +276,23 @@ 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.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) {
|
||||
|
|
|
@ -16,6 +16,22 @@ func init() {
|
|||
// 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,
|
||||
|
|
|
@ -191,7 +191,56 @@ func (p *parser) ParseInterpolation() (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
|
||||
|
@ -348,6 +397,30 @@ func (p *parser) ParseExpressionTerm() (ast.Node, error) {
|
|||
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()
|
||||
|
||||
|
|
|
@ -168,7 +168,7 @@ func scanInterpolationToken(s string, startPos ast.Pos) (*Token, int, ast.Pos) {
|
|||
var token *Token
|
||||
|
||||
switch next {
|
||||
case '(', ')', '[', ']', ',', '.', '+', '-', '*', '/', '%':
|
||||
case '(', ')', '[', ']', ',', '.', '+', '-', '*', '/', '%', '?', ':':
|
||||
// Easy punctuation symbols that don't have any special meaning
|
||||
// during scanning, and that stand for themselves in the
|
||||
// TokenType enumeration.
|
||||
|
@ -189,6 +189,91 @@ func scanInterpolationToken(s string, startPos ast.Pos) (*Token, int, ast.Pos) {
|
|||
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)
|
||||
|
|
|
@ -48,6 +48,20 @@ const (
|
|||
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
|
||||
|
@ -73,6 +87,16 @@ func (t *Token) String() string {
|
|||
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.
|
||||
|
|
|
@ -4,32 +4,43 @@ package scanner
|
|||
|
||||
import "fmt"
|
||||
|
||||
const _TokenType_name = "BEGINPERCENTOPARENCPARENSTARPLUSCOMMAMINUSPERIODSLASHBOOLFLOATINTEGERSTRINGOBRACKETCBRACKETIDENTIFIERLITERALENDOQUOTECQUOTEEOFINVALID"
|
||||
const _TokenType_name = "BANGBEGINPERCENTOPARENCPARENSTARPLUSCOMMAMINUSPERIODSLASHCOLONLTEQUALGTQUESTIONBOOLFLOATINTEGERSTRINGOBRACKETCBRACKETIDENTIFIERLITERALENDOQUOTECQUOTEANDORNOTEQUALLTEGTEEOFINVALID"
|
||||
|
||||
var _TokenType_map = map[TokenType]string{
|
||||
36: _TokenType_name[0:5],
|
||||
37: _TokenType_name[5:12],
|
||||
40: _TokenType_name[12:18],
|
||||
41: _TokenType_name[18:24],
|
||||
42: _TokenType_name[24:28],
|
||||
43: _TokenType_name[28:32],
|
||||
44: _TokenType_name[32:37],
|
||||
45: _TokenType_name[37:42],
|
||||
46: _TokenType_name[42:48],
|
||||
47: _TokenType_name[48:53],
|
||||
66: _TokenType_name[53:57],
|
||||
70: _TokenType_name[57:62],
|
||||
73: _TokenType_name[62:69],
|
||||
83: _TokenType_name[69:75],
|
||||
91: _TokenType_name[75:83],
|
||||
93: _TokenType_name[83:91],
|
||||
105: _TokenType_name[91:101],
|
||||
111: _TokenType_name[101:108],
|
||||
125: _TokenType_name[108:111],
|
||||
8220: _TokenType_name[111:117],
|
||||
8221: _TokenType_name[117:123],
|
||||
9220: _TokenType_name[123:126],
|
||||
65533: _TokenType_name[126:133],
|
||||
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 {
|
||||
|
|
|
@ -1586,28 +1586,28 @@
|
|||
"revisionTime": "2016-11-30T20:58:18Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "/TJCBetWCMVsOpehJzVk3S/xtWM=",
|
||||
"checksumSHA1": "xONRNgLDc5OqCUmyDN3iBRKmKB0=",
|
||||
"path": "github.com/hashicorp/hil",
|
||||
"revision": "76d6c606283f17780baaad00df4b6ce8e9fca941",
|
||||
"revisionTime": "2016-11-20T06:09:40Z"
|
||||
"revision": "bf046eec69cc383b7f32c47990336409601c8116",
|
||||
"revisionTime": "2016-12-04T02:32:26Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "YPJwewz3dAqEWOGP2qIIWeCufF0=",
|
||||
"checksumSHA1": "oZ2a2x9qyHqvqJdv/Du3LGeaFdA=",
|
||||
"path": "github.com/hashicorp/hil/ast",
|
||||
"revision": "76d6c606283f17780baaad00df4b6ce8e9fca941",
|
||||
"revisionTime": "2016-11-20T06:09:40Z"
|
||||
"revision": "bf046eec69cc383b7f32c47990336409601c8116",
|
||||
"revisionTime": "2016-12-04T02:32:26Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "+mEEIN7pTcda+Vu837rQBkHYzao=",
|
||||
"checksumSHA1": "p/zZysJW/AaPXXPCI20nM2arPnw=",
|
||||
"path": "github.com/hashicorp/hil/parser",
|
||||
"revision": "76d6c606283f17780baaad00df4b6ce8e9fca941",
|
||||
"revisionTime": "2016-11-20T06:09:40Z"
|
||||
"revision": "bf046eec69cc383b7f32c47990336409601c8116",
|
||||
"revisionTime": "2016-12-04T02:32:26Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "2aIAMbqPrE0qyYoJSL6qjVm+Tt0=",
|
||||
"checksumSHA1": "FlzgVCYqnODad4pKujXhSaGcrIo=",
|
||||
"path": "github.com/hashicorp/hil/scanner",
|
||||
"revision": "76d6c606283f17780baaad00df4b6ce8e9fca941",
|
||||
"revisionTime": "2016-11-20T06:09:40Z"
|
||||
"revision": "bf046eec69cc383b7f32c47990336409601c8116",
|
||||
"revisionTime": "2016-12-04T02:32:26Z"
|
||||
},
|
||||
{
|
||||
"path": "github.com/hashicorp/logutils",
|
||||
|
|
Loading…
Reference in New Issue