Skip to content

Commit

Permalink
fix(parser): multiplicative operators are above additive operators in…
Browse files Browse the repository at this point in the history
… precedence (#907)

The multiplicative operators are a higher precedence, but were
accidentally put in the EBNF at a lower precedence. This adds a
precedence table to the docs and fixes the EBNF so
multiplication/division are higher precedence than addition/subtraction.
  • Loading branch information
jsternberg authored Jan 22, 2019
1 parent f0f4df9 commit 0b10ca9
Show file tree
Hide file tree
Showing 4 changed files with 437 additions and 108 deletions.
29 changes: 23 additions & 6 deletions docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,23 @@ It is not possible to access an object's property using an arbitrary expression.
#### Operators

Operators combine operands into expressions.
Operator precedence is encoded directly into the grammar.
The precedence of the operators is given in the table below. Operators with a lower number have higher precedence.

|Precedence| Operator | Description |
|----------|----------|---------------------------|
| 1 | `a()` | Function call |
| | `a[]` | Member or index access |
| | `.` | Member access |
| 2 | `*` `/` |Multiplication and division|
| 3 | `+` `-` | Addition and subtraction |
| 4 |`==` `!=` | Comparison operators |
| | `<` `<=` | |
| | `>` `>=` | |
| |`=~` `!~` | |
| 5 | `not` | Unary logical expression |
| 6 |`and` `or`| Logical AND and OR |

The operator precedence is encoded directly into the grammar as the following.

Expression = LogicalExpression .
LogicalExpression = UnaryLogicalExpression
Expand All @@ -734,12 +750,12 @@ Operator precedence is encoded directly into the grammar.
ComparisonExpression = MultiplicativeExpression
| ComparisonExpression ComparisonOperator MultiplicativeExpression .
ComparisonOperator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "=~" | "!~" .
MultiplicativeExpression = AdditiveExpression
| MultiplicativeExpression MultiplicativeOperator AdditiveExpression .
MultiplicativeOperator = "*" | "/" .
AdditiveExpression = PipeExpression
| AdditiveExpression AdditiveOperator PipeExpression .
AdditiveExpression = MultiplicativeExpression
| AdditiveExpression AdditiveOperator MultiplicativeExpression .
AdditiveOperator = "+" | "-" .
MultiplicativeExpression = PipeExpression
| MultiplicativeExpression MultiplicativeOperator PipeExpression .
MultiplicativeOperator = "*" | "/" .
PipeExpression = PostfixExpression
| PipeExpression PipeOperator UnaryExpression .
PipeOperator = "|>" .
Expand All @@ -751,6 +767,7 @@ Operator precedence is encoded directly into the grammar.
PostfixOperator = MemberExpression
| CallExpression
| IndexExpression .
### Packages

Flux source is organized into packages.
Expand Down
179 changes: 100 additions & 79 deletions internal/parser/grammar.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,85 +6,86 @@ For the parser, the SPEC grammar undergoes a process to have the left-recursion

The parser directly implements the following grammar.

File = [ PackageClause ] [ ImportList ] StatementList .
PackageClause = "package" identifier .
ImportList = { ImportDeclaration } .
ImportDeclaration = "import" [identifier] string_lit
StatementList = { Statement } .
Statement = OptionAssignment
| BuiltinStatement
| IdentStatement
| ReturnStatement
| ExpressionStatement .
IdentStatement = identifer ( AssignStatement | ExpressionSuffix ) .
OptionAssignment = "option" identifier OptionAssignmentSuffix .
OptionAssignmentSuffix = AssignStatement
| "." identifier AssignStatement .
BuiltinStatement = "builtin" identifier .
AssignStatement = "=" Expression .
ReturnStatement = "return" Expression .
ExpressionStatement = Expression .
Expression = LogicalExpression .
ExpressionSuffix = { PostfixOperator } { PipeExpressionSuffix } { AdditiveExpressionSuffix } { MultiplicativeExpressionSuffix } { ComparisonExpressionSuffix } { LogicalExpressionSuffix } .
LogicalExpression = UnaryLogicalExpression { LogicalExpressionSuffix } .
LogicalExpressionSuffix = LogicalOperator UnaryLogicalExpression .
LogicalOperator = "and" | "or" .
UnaryLogicalExpression = ComparisonExpression
| UnaryLogicalOperator UnaryLogicalExpression .
UnaryLogicalOperator = "not" .
ComparisonExpression = MultiplicativeExpression { ComparisonExpressionSuffix } .
ComparisonExpressionSuffix = ComparisonOperator MultiplicativeExpr .
ComparisonOperator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "=~" | "!~" .
MultiplicativeExpression = AdditiveExpression { MultiplicativeOperator AdditiveExpression } .
MultiplicativeOperator = "*"| "/".
AdditiveExpression = PipeExpression { AdditiveExpressionSuffix } .
AdditiveExpressionSuffix = AdditiveOperator PipeExpression .
AdditiveOperator = "+" | "-" .
PipeExpression = UnaryExpression { PipeExpressionSuffix } .
PipeExpressionSuffix = PipeOperator UnaryExpression .
PipeOperator = pipe_forward .
UnaryExpression = PostfixExpression
| PrefixOperator UnaryExpression .
PrefixOperator = "+" | "-" .
PostfixExpression = PrimaryExpression { PostfixOperator } .
PostfixOperator = MemberExpression
| CallExpression
| IndexExpression .
MemberExpression = DotExpression | MemberBracketExpression
DotExpression = "." identifer
MemberBracketExpression = "[" string "]" .
CallExpression = "(" ParameterList ")" .
IndexExpression = "[" Expression "]" .
PrimaryExpression = identifer
| int_lit
| float_lit
| string_lit
| regex_lit
| duration_lit
| pipe_receive_lit
| ObjectLiteral
| ArrayLiteral
| ParenExpression .
ObjectLiteral = "{" PropertyList "}" .
ArrayLiteral = "[" ExpressionList "]" .
ParenExpression = "(" ParenExpressionBody .
ParenExpressionBody = ")" FunctionExpressionSuffix
| identifer ParenIdentExpression
| Expression ")" .
ParenIdentExpression = ")" [ FunctionExpressionSuffix ]
| "=" Expression [ "," ParameterList ] ")" FunctionExpressionSuffix .
| "," ParameterList ")" FunctionExpressionSuffix
| ExpressionSuffix ")" .
ParenExpression = "(" Expression ")" .
FunctionExpressionSuffix = "=>" FunctionBodyExpression .
FunctionBodyExpression = Block | Expression .
Block = "{" StatementList "}" .
ExpressionList = [ Expression { "," Expression } ] .
PropertyList = [ Property { "," Property } ] .
Property = identifier [ ":" Expression ]
| string_lit ":" Expression .
ParameterList = [ Parameter { "," Parameter } ] .
Parameter = identifer [ "=" Expression ] .
File = [ PackageClause ] [ ImportList ] StatementList .
PackageClause = "package" identifier .
ImportList = { ImportDeclaration } .
ImportDeclaration = "import" [identifier] string_lit
StatementList = { Statement } .
Statement = OptionAssignment
| BuiltinStatement
| IdentStatement
| ReturnStatement
| ExpressionStatement .
IdentStatement = identifer ( AssignStatement | ExpressionSuffix ) .
OptionAssignment = "option" identifier OptionAssignmentSuffix .
OptionAssignmentSuffix = AssignStatement
| "." identifier AssignStatement .
BuiltinStatement = "builtin" identifier .
AssignStatement = "=" Expression .
ReturnStatement = "return" Expression .
ExpressionStatement = Expression .
Expression = LogicalExpression .
ExpressionSuffix = { PostfixOperator } { PipeExpressionSuffix } { MultiplicativeExpressionSuffix } { AdditiveExpressionSuffix } { ComparisonExpressionSuffix } { LogicalExpressionSuffix } .
LogicalExpression = UnaryLogicalExpression { LogicalExpressionSuffix } .
LogicalExpressionSuffix = LogicalOperator UnaryLogicalExpression .
LogicalOperator = "and" | "or" .
UnaryLogicalExpression = ComparisonExpression
| UnaryLogicalOperator UnaryLogicalExpression .
UnaryLogicalOperator = "not" .
ComparisonExpression = AdditiveExpression { ComparisonExpressionSuffix } .
ComparisonExpressionSuffix = ComparisonOperator AdditiveExpression .
ComparisonOperator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "=~" | "!~" .
AdditiveExpression = MultiplicativeExpression { AdditiveExpressionSuffix } .
AdditiveExpressionSuffix = AdditiveOperator MultiplicativeExpression .
AdditiveOperator = "+" | "-" .
MultiplicativeExpression = PipeExpression { MultiplicativeExpressionSuffix } .
MultiplicativeExpressionSuffix = MultiplicativeOperator PipeExpression .
MultiplicativeOperator = "*"| "/".
PipeExpression = UnaryExpression { PipeExpressionSuffix } .
PipeExpressionSuffix = PipeOperator UnaryExpression .
PipeOperator = pipe_forward .
UnaryExpression = PostfixExpression
| PrefixOperator UnaryExpression .
PrefixOperator = "+" | "-" .
PostfixExpression = PrimaryExpression { PostfixOperator } .
PostfixOperator = MemberExpression
| CallExpression
| IndexExpression .
MemberExpression = DotExpression | MemberBracketExpression
DotExpression = "." identifer
MemberBracketExpression = "[" string "]" .
CallExpression = "(" ParameterList ")" .
IndexExpression = "[" Expression "]" .
PrimaryExpression = identifer
| int_lit
| float_lit
| string_lit
| regex_lit
| duration_lit
| pipe_receive_lit
| ObjectLiteral
| ArrayLiteral
| ParenExpression .
ObjectLiteral = "{" PropertyList "}" .
ArrayLiteral = "[" ExpressionList "]" .
ParenExpression = "(" ParenExpressionBody .
ParenExpressionBody = ")" FunctionExpressionSuffix
| identifer ParenIdentExpression
| Expression ")" .
ParenIdentExpression = ")" [ FunctionExpressionSuffix ]
| "=" Expression [ "," ParameterList ] ")" FunctionExpressionSuffix .
| "," ParameterList ")" FunctionExpressionSuffix
| ExpressionSuffix ")" .
ParenExpression = "(" Expression ")" .
FunctionExpressionSuffix = "=>" FunctionBodyExpression .
FunctionBodyExpression = Block | Expression .
Block = "{" StatementList "}" .
ExpressionList = [ Expression { "," Expression } ] .
PropertyList = [ Property { "," Property } ] .
Property = identifier [ ":" Expression ]
| string_lit ":" Expression .
ParameterList = [ Parameter { "," Parameter } ] .
Parameter = identifer [ "=" Expression ] .

When processing the grammar, the parser follows a few simple rules.

Expand All @@ -99,3 +100,23 @@ To determine which tokens a production accepts, compute `FIRST(X)` for each prod
1. For a terminal, `FIRST(X) = {X}`.
2. For a alternation, calculate `FIRST(X)` for each production and form the union.
3. For concatentation, calculate `FIRST(X)` for the first production. If this set contains the empty set, calculate `FIRST(X)` for the next production. Continue until you hit a production that does not contain the empty set or, if all productions have been evaluated, then the empty set is accepted for this production.

## Operator Precedence

Operator precedence is defined within the grammar itself so a precedence table is not needed. While the table itself is not needed and not used in the parser itself, a table is provided below for human readability with verifying the grammar.

|Precedence| Operator | Description |
|----------|----------|---------------------------|
| 1 | `a()` | Function call |
| | `a[]` | Member or index access |
| | `.` | Member access |
| 2 | `*` `/` |Multiplication and division|
| 3 | `+` `-` | Addition and subtraction |
| 4 |`==` `!=` | Comparison operators |
| | `<` `<=` | |
| | `>` `>=` | |
| |`=~` `!~` | |
| 5 | `not` | Unary logical expression |
| 6 |`and` `or`| Logical AND and OR |

Within the grammar itself, precedence is reversed so lower precedence operators appear above higher precedence operators. This ensures that the higher precedence values are nested within lower precedence operators.
46 changes: 23 additions & 23 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,8 @@ func (p *parser) parseExpression() ast.Expression {
func (p *parser) parseExpressionSuffix(expr ast.Expression) ast.Expression {
p.repeat(p.parsePostfixOperatorSuffix(&expr))
p.repeat(p.parsePipeExpressionSuffix(&expr))
p.repeat(p.parseAdditiveExpressionSuffix(&expr))
p.repeat(p.parseMultiplicativeExpressionSuffix(&expr))
p.repeat(p.parseAdditiveExpressionSuffix(&expr))
p.repeat(p.parseComparisonExpressionSuffix(&expr))
p.repeat(p.parseLogicalExpressionSuffix(&expr))
return expr
Expand Down Expand Up @@ -415,7 +415,7 @@ func (p *parser) parseUnaryLogicalOperator() (token.Pos, ast.OperatorKind, bool)
}

func (p *parser) parseComparisonExpression() ast.Expression {
expr := p.parseMultiplicativeExpression()
expr := p.parseAdditiveExpression()
p.repeat(p.parseComparisonExpressionSuffix(&expr))
return expr
}
Expand All @@ -426,7 +426,7 @@ func (p *parser) parseComparisonExpressionSuffix(expr *ast.Expression) func() bo
if !ok {
return false
}
rhs := p.parseMultiplicativeExpression()
rhs := p.parseAdditiveExpression()
*expr = &ast.BinaryExpression{
Operator: op,
Left: *expr,
Expand Down Expand Up @@ -471,19 +471,19 @@ func (p *parser) parseComparisonOperator() (ast.OperatorKind, bool) {
}
}

func (p *parser) parseMultiplicativeExpression() ast.Expression {
expr := p.parseAdditiveExpression()
p.repeat(p.parseMultiplicativeExpressionSuffix(&expr))
func (p *parser) parseAdditiveExpression() ast.Expression {
expr := p.parseMultiplicativeExpression()
p.repeat(p.parseAdditiveExpressionSuffix(&expr))
return expr
}

func (p *parser) parseMultiplicativeExpressionSuffix(expr *ast.Expression) func() bool {
func (p *parser) parseAdditiveExpressionSuffix(expr *ast.Expression) func() bool {
return func() bool {
op, ok := p.parseMultiplicativeOperator()
op, ok := p.parseAdditiveOperator()
if !ok {
return false
}
rhs := p.parseAdditiveExpression()
rhs := p.parseMultiplicativeExpression()
*expr = &ast.BinaryExpression{
Operator: op,
Left: *expr,
Expand All @@ -497,28 +497,28 @@ func (p *parser) parseMultiplicativeExpressionSuffix(expr *ast.Expression) func(
}
}

func (p *parser) parseMultiplicativeOperator() (ast.OperatorKind, bool) {
func (p *parser) parseAdditiveOperator() (ast.OperatorKind, bool) {
switch _, tok, _ := p.peek(); tok {
case token.MUL:
case token.ADD:
p.consume()
return ast.MultiplicationOperator, true
case token.DIV:
return ast.AdditionOperator, true
case token.SUB:
p.consume()
return ast.DivisionOperator, true
return ast.SubtractionOperator, true
default:
return 0, false
}
}

func (p *parser) parseAdditiveExpression() ast.Expression {
func (p *parser) parseMultiplicativeExpression() ast.Expression {
expr := p.parsePipeExpression()
p.repeat(p.parseAdditiveExpressionSuffix(&expr))
p.repeat(p.parseMultiplicativeExpressionSuffix(&expr))
return expr
}

func (p *parser) parseAdditiveExpressionSuffix(expr *ast.Expression) func() bool {
func (p *parser) parseMultiplicativeExpressionSuffix(expr *ast.Expression) func() bool {
return func() bool {
op, ok := p.parseAdditiveOperator()
op, ok := p.parseMultiplicativeOperator()
if !ok {
return false
}
Expand All @@ -536,14 +536,14 @@ func (p *parser) parseAdditiveExpressionSuffix(expr *ast.Expression) func() bool
}
}

func (p *parser) parseAdditiveOperator() (ast.OperatorKind, bool) {
func (p *parser) parseMultiplicativeOperator() (ast.OperatorKind, bool) {
switch _, tok, _ := p.peek(); tok {
case token.ADD:
case token.MUL:
p.consume()
return ast.AdditionOperator, true
case token.SUB:
return ast.MultiplicationOperator, true
case token.DIV:
p.consume()
return ast.SubtractionOperator, true
return ast.DivisionOperator, true
default:
return 0, false
}
Expand Down
Loading

0 comments on commit 0b10ca9

Please sign in to comment.