From 9daa9942b3fe4eae82b011e1b0789251ea54fc51 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 15 Nov 2016 15:24:48 -0800 Subject: [PATCH 1/2] vendor: update HIL --- .../hashicorp/hil/parser/binary_op.go | 29 +++++++++++ .../github.com/hashicorp/hil/parser/parser.go | 50 ++++++++++--------- vendor/vendor.json | 18 +++---- 3 files changed, 64 insertions(+), 33 deletions(-) create mode 100644 vendor/github.com/hashicorp/hil/parser/binary_op.go 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..c43c5eb02 --- /dev/null +++ b/vendor/github.com/hashicorp/hil/parser/binary_op.go @@ -0,0 +1,29 @@ +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.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/parser.go b/vendor/github.com/hashicorp/hil/parser/parser.go index 88d60471b..af1a90e75 100644 --- a/vendor/github.com/hashicorp/hil/parser/parser.go +++ b/vendor/github.com/hashicorp/hil/parser/parser.go @@ -188,6 +188,21 @@ func (p *parser) ParseInterpolation() (ast.Node, error) { } func (p *parser) ParseExpression() (ast.Node, error) { + return p.parseBinaryOps(binaryOps) +} + +// 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 @@ -198,37 +213,24 @@ func (p *parser) ParseExpression() (ast.Node, error) { // 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.ParseExpressionTerm() + lhs, err = p.parseBinaryOps(remaining) if err != nil { return nil, err } // We'll keep eating up arithmetic operators until we run - // out, so that binary expressions will combine in a manner - // that is compatible with the old yacc-based parser: - // a+b*c => (a+b)*c, *not* a+(b*c) + // 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) // - // (perhaps later we'll implement some more intuitive precendence - // rules here, but for now being compatible with the old parser - // is the goal.) + // 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() - newOperator := ast.ArithmeticOpInvalid - - switch next.Type { - case scanner.PLUS: - newOperator = ast.ArithmeticOpAdd - case scanner.MINUS: - newOperator = ast.ArithmeticOpSub - case scanner.STAR: - newOperator = ast.ArithmeticOpMul - case scanner.SLASH: - newOperator = ast.ArithmeticOpDiv - case scanner.PERCENT: - newOperator = ast.ArithmeticOpMod - } - - if newOperator == ast.ArithmeticOpInvalid { + var newOperator ast.ArithmeticOp + var ok bool + if newOperator, ok = thisLevel[next.Type]; !ok { break } @@ -244,7 +246,7 @@ func (p *parser) ParseExpression() (ast.Node, error) { operator = newOperator p.peeker.Read() // eat operator token - rhs, err = p.ParseExpressionTerm() + rhs, err = p.parseBinaryOps(remaining) if err != nil { return nil, err } diff --git a/vendor/vendor.json b/vendor/vendor.json index b0bb726d2..ce47ca9f9 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1426,26 +1426,26 @@ { "checksumSHA1": "/TJCBetWCMVsOpehJzVk3S/xtWM=", "path": "github.com/hashicorp/hil", - "revision": "fceef4715de114b559e042e19fd2cec06003628a", - "revisionTime": "2016-11-15T03:45:18Z" + "revision": "6861984a28be5acf4e47b5769bc841ec653bf3ba", + "revisionTime": "2016-11-15T22:45:22Z" }, { "checksumSHA1": "YPJwewz3dAqEWOGP2qIIWeCufF0=", "path": "github.com/hashicorp/hil/ast", - "revision": "fceef4715de114b559e042e19fd2cec06003628a", - "revisionTime": "2016-11-15T03:45:18Z" + "revision": "6861984a28be5acf4e47b5769bc841ec653bf3ba", + "revisionTime": "2016-11-15T22:45:22Z" }, { - "checksumSHA1": "BeqAygYXJlCHpU0HVWg4mxuROks=", + "checksumSHA1": "FlyG9T7bd3jNK6R9XwXSobecVEQ=", "path": "github.com/hashicorp/hil/parser", - "revision": "fceef4715de114b559e042e19fd2cec06003628a", - "revisionTime": "2016-11-15T03:45:18Z" + "revision": "6861984a28be5acf4e47b5769bc841ec653bf3ba", + "revisionTime": "2016-11-15T22:45:22Z" }, { "checksumSHA1": "2aIAMbqPrE0qyYoJSL6qjVm+Tt0=", "path": "github.com/hashicorp/hil/scanner", - "revision": "fceef4715de114b559e042e19fd2cec06003628a", - "revisionTime": "2016-11-15T03:45:18Z" + "revision": "6861984a28be5acf4e47b5769bc841ec653bf3ba", + "revisionTime": "2016-11-15T22:45:22Z" }, { "path": "github.com/hashicorp/logutils", From 8fab86a9d482fcaf6369218a3b7990cce9940242 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 15 Nov 2016 15:29:38 -0800 Subject: [PATCH 2/2] website: update docs for precedence rules --- .../docs/configuration/interpolation.html.md | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index 5d791edf3..fa8cdbf59 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -381,19 +381,21 @@ The supported operations are: - *Add* (`+`), *Subtract* (`-`), *Multiply* (`*`), and *Divide* (`/`) for **float** types - *Add* (`+`), *Subtract* (`-`), *Multiply* (`*`), *Divide* (`/`), and *Modulo* (`%`) for **integer** types +Operator precedences is the standard mathematical order of operations: +*Multiply* (`*`), *Divide* (`/`), and *Modulo* (`%`) have precedence over +*Add* (`+`) and *Subtract* (`-`). Parenthesis can be used to force ordering. + +``` +"${2 * 4 + 3 * 3}" # computes to 17 +"${3 * 3 + 2 * 4}" # computes to 17 +"${2 * (4 + 3) * 3}" # computes to 42 +``` + +You can use the [terraform console](/docs/commands/console.html) command to +try the math operations. + -> **Note:** Since Terraform allows hyphens in resource and variable names, it's best to use spaces between math operators to prevent confusion or unexpected behavior. For example, `${var.instance-count - 1}` will subtract **1** from the `instance-count` variable value, while `${var.instance-count-1}` will interpolate the `instance-count-1` variable value. - - --> **Note:** Operator precedence is not the usual one where *Multiply* (`*`), -*Divide* (`/`), and *Modulo* (`%`) have precedence over *Add* (`+`) and *Subtract* (`-`). -The operations are made in the order they appear. Parenthesis can be used to force ordering : -``` -"${2 * 4 + 3 * 3}" # computes to 33 -"${3 * 3 + 2 * 4}" # computes to 44 -"${(2 * 4) + (3 * 3)}" # computes to 17 -"${(3 * 3) + (2 * 4)}" # computes to 17 -```