commit
20e3ae3aa7
|
@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -188,6 +188,21 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
startPos := p.peeker.Peek().Pos
|
||||||
|
|
||||||
var lhs, rhs ast.Node
|
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
|
// expression or it might just be a standalone term, but
|
||||||
// we won't know until we've parsed it and can look ahead
|
// we won't know until we've parsed it and can look ahead
|
||||||
// to see if there's an operator token.
|
// to see if there's an operator token.
|
||||||
lhs, err = p.ParseExpressionTerm()
|
lhs, err = p.parseBinaryOps(remaining)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll keep eating up arithmetic operators until we run
|
// We'll keep eating up arithmetic operators until we run
|
||||||
// out, so that binary expressions will combine in a manner
|
// out, so that operators with the same precedence will combine in a
|
||||||
// that is compatible with the old yacc-based parser:
|
// left-associative manner:
|
||||||
// a+b*c => (a+b)*c, *not* a+(b*c)
|
// a+b+c => (a+b)+c, not a+(b+c)
|
||||||
//
|
//
|
||||||
// (perhaps later we'll implement some more intuitive precendence
|
// Should we later want to have right-associative operators, a way
|
||||||
// rules here, but for now being compatible with the old parser
|
// to achieve that would be to call back up to ParseExpression here
|
||||||
// is the goal.)
|
// instead of iteratively parsing only the remaining operators.
|
||||||
for {
|
for {
|
||||||
next := p.peeker.Peek()
|
next := p.peeker.Peek()
|
||||||
newOperator := ast.ArithmeticOpInvalid
|
var newOperator ast.ArithmeticOp
|
||||||
|
var ok bool
|
||||||
switch next.Type {
|
if newOperator, ok = thisLevel[next.Type]; !ok {
|
||||||
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 {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +246,7 @@ func (p *parser) ParseExpression() (ast.Node, error) {
|
||||||
|
|
||||||
operator = newOperator
|
operator = newOperator
|
||||||
p.peeker.Read() // eat operator token
|
p.peeker.Read() // eat operator token
|
||||||
rhs, err = p.ParseExpressionTerm()
|
rhs, err = p.parseBinaryOps(remaining)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1426,26 +1426,26 @@
|
||||||
{
|
{
|
||||||
"checksumSHA1": "/TJCBetWCMVsOpehJzVk3S/xtWM=",
|
"checksumSHA1": "/TJCBetWCMVsOpehJzVk3S/xtWM=",
|
||||||
"path": "github.com/hashicorp/hil",
|
"path": "github.com/hashicorp/hil",
|
||||||
"revision": "fceef4715de114b559e042e19fd2cec06003628a",
|
"revision": "6861984a28be5acf4e47b5769bc841ec653bf3ba",
|
||||||
"revisionTime": "2016-11-15T03:45:18Z"
|
"revisionTime": "2016-11-15T22:45:22Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "YPJwewz3dAqEWOGP2qIIWeCufF0=",
|
"checksumSHA1": "YPJwewz3dAqEWOGP2qIIWeCufF0=",
|
||||||
"path": "github.com/hashicorp/hil/ast",
|
"path": "github.com/hashicorp/hil/ast",
|
||||||
"revision": "fceef4715de114b559e042e19fd2cec06003628a",
|
"revision": "6861984a28be5acf4e47b5769bc841ec653bf3ba",
|
||||||
"revisionTime": "2016-11-15T03:45:18Z"
|
"revisionTime": "2016-11-15T22:45:22Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "BeqAygYXJlCHpU0HVWg4mxuROks=",
|
"checksumSHA1": "FlyG9T7bd3jNK6R9XwXSobecVEQ=",
|
||||||
"path": "github.com/hashicorp/hil/parser",
|
"path": "github.com/hashicorp/hil/parser",
|
||||||
"revision": "fceef4715de114b559e042e19fd2cec06003628a",
|
"revision": "6861984a28be5acf4e47b5769bc841ec653bf3ba",
|
||||||
"revisionTime": "2016-11-15T03:45:18Z"
|
"revisionTime": "2016-11-15T22:45:22Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "2aIAMbqPrE0qyYoJSL6qjVm+Tt0=",
|
"checksumSHA1": "2aIAMbqPrE0qyYoJSL6qjVm+Tt0=",
|
||||||
"path": "github.com/hashicorp/hil/scanner",
|
"path": "github.com/hashicorp/hil/scanner",
|
||||||
"revision": "fceef4715de114b559e042e19fd2cec06003628a",
|
"revision": "6861984a28be5acf4e47b5769bc841ec653bf3ba",
|
||||||
"revisionTime": "2016-11-15T03:45:18Z"
|
"revisionTime": "2016-11-15T22:45:22Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "github.com/hashicorp/logutils",
|
"path": "github.com/hashicorp/logutils",
|
||||||
|
|
|
@ -381,19 +381,21 @@ The supported operations are:
|
||||||
- *Add* (`+`), *Subtract* (`-`), *Multiply* (`*`), and *Divide* (`/`) for **float** types
|
- *Add* (`+`), *Subtract* (`-`), *Multiply* (`*`), and *Divide* (`/`) for **float** types
|
||||||
- *Add* (`+`), *Subtract* (`-`), *Multiply* (`*`), *Divide* (`/`), and *Modulo* (`%`) for **integer** 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,
|
-> **Note:** Since Terraform allows hyphens in resource and variable names,
|
||||||
it's best to use spaces between math operators to prevent confusion or unexpected
|
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
|
behavior. For example, `${var.instance-count - 1}` will subtract **1** from the
|
||||||
`instance-count` variable value, while `${var.instance-count-1}` will interpolate
|
`instance-count` variable value, while `${var.instance-count-1}` will interpolate
|
||||||
the `instance-count-1` variable value.
|
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
|
|
||||||
```
|
|
||||||
|
|
Loading…
Reference in New Issue