mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:01:08 +01:00
Implement the current set of rules for precedence partial ordering (#1096)
In the process, switch to an unambiguous grammar, using the precedence-climbing method for operator precedence (suitably modified to handle a partial precedence order) rather than Bison %precedence / %prec.
This commit is contained in:
@@ -15,3 +15,134 @@ techniques can be applied to other kinds of AST nodes as needed. See the
|
||||
handling of the `UNIMPL_EXAMPLE` token for an example of how this is done, and
|
||||
see [`unimplemented_example_test.cpp`](unimplemented_example_test.cpp) for an
|
||||
example of how to test it.
|
||||
|
||||
## Precedence and associativity
|
||||
|
||||
The [Bison expression grammar](parser.ypp) uses the
|
||||
[precedence climbing method](https://en.wikipedia.org/wiki/Operator-precedence_parser#Precedence_climbing_method)
|
||||
to model precedence and associativity, suitably modified to handle Carbon's
|
||||
partial precedence order without grammar ambiguities.
|
||||
|
||||
Consider this example
|
||||
[precedence diagram](/docs/design/expressions/README.md#precedence):
|
||||
|
||||
```mermaid
|
||||
graph BT
|
||||
%%{init: {'themeVariables': {'fontFamily': 'monospace'}}}%%
|
||||
minus["minus<br>-x"]
|
||||
mul>"mul<br>x * y"]
|
||||
add>"add<br>x + y"]
|
||||
mod["mod<br>x % y"]
|
||||
eq["eq<br>x = y"]
|
||||
|
||||
eq --> add & mod
|
||||
add --> mul
|
||||
mul & mod --> minus
|
||||
```
|
||||
|
||||
For each precedence level, we have up to three grammar productions:
|
||||
|
||||
- `foo_expression` represents an expression at that precedence level or
|
||||
higher, and includes as productions all of the expression kinds that are
|
||||
immediately higher in the precedence graph:
|
||||
```bison
|
||||
add_expression:
|
||||
mul_expression | add_lhs '+' add_operand ;
|
||||
```
|
||||
- `foo_operand` represents an operand of a `foo_expression` that is not itself
|
||||
a `foo_expression`.
|
||||
```bison
|
||||
eq_operand:
|
||||
add_expression | mod_expression ;
|
||||
```
|
||||
- For left-associative operators, `foo_lhs` represents either a `foo_operand`
|
||||
or a `foo_expression`.
|
||||
```
|
||||
add_lhs:
|
||||
add_operand | add_expression ;
|
||||
```
|
||||
|
||||
The above approach leads to (benign) reduce-reduce conflicts. In our example
|
||||
precedence diagram, the expression `-x == y` has two different parses:
|
||||
|
||||
- _eq_expression_
|
||||
- _eq_operand_
|
||||
- _add_expression_
|
||||
- _mul_expression_
|
||||
- _minus_expression_
|
||||
- `-`
|
||||
- `x`
|
||||
- `==`
|
||||
- _eq_operand_
|
||||
- ...
|
||||
- `y`
|
||||
|
||||
and
|
||||
|
||||
- _eq_expression_
|
||||
- _eq_operand_
|
||||
- _mod_expression_
|
||||
- _minus_expression_
|
||||
- `-`
|
||||
- `x`
|
||||
- `==`
|
||||
- _eq_operand_
|
||||
- ...
|
||||
- `y`
|
||||
|
||||
These would invoke the same parsing actions, so the states can be combined, but
|
||||
Bison isn't smart enough to see that.
|
||||
|
||||
In order to eliminate these conflicts, if there are multiple paths through the
|
||||
precedence graph between a higher-precedence level `foo` and some lower
|
||||
precedence level `bar` -- that is, if there's a diamond in the precedence graph
|
||||
with `foo` at the top and `bar` at the bottom -- `foo_expression`s are excluded
|
||||
from all intermediate `_expression` productions on the diamond between `foo` and
|
||||
`bar`, and are added back in the downstream `_operand` productions in the
|
||||
diamond instead:
|
||||
|
||||
```bison
|
||||
minus_expression:
|
||||
identifier | '-' identifier ;
|
||||
|
||||
// In the real grammar, trivial productions like this are inlined.
|
||||
mul_operand:
|
||||
minus_expression ;
|
||||
mul_lhs:
|
||||
mul_operand | mul_expression ;
|
||||
// A minus_expression is not a mul_expression, even though it's a
|
||||
// higher-precedence expression, because there are multiple paths from
|
||||
// eq_expression to minus_expression, and this production is on such a path.
|
||||
mul_expression:
|
||||
mul_lhs '*' mul_operand
|
||||
|
||||
// minus_expression is listed here because it is excluded from mul_expression.
|
||||
add_operand:
|
||||
minus_expression | mul_expression ;
|
||||
// This is notionally
|
||||
// add_operand | add_expression
|
||||
// but that introduces another kind of reduce-reduce conflict, because there
|
||||
// would be two ways to interpret a mul_expression as an add_lhs.
|
||||
add_lhs:
|
||||
minus_expression | add_expression ;
|
||||
// A mul_expression is an add_expression, because multiplication is
|
||||
// higher-precedence, and mul is not at the top of a diamond in the precedence
|
||||
// graph. minus_expression is excluded because we are within a diamond with it
|
||||
// at the top.
|
||||
add_expression:
|
||||
mul_expression | add_lhs '+' add_operand ;
|
||||
|
||||
mod_operand:
|
||||
minus_expression ;
|
||||
mod_expression:
|
||||
mod_operand '%' mod_operand ;
|
||||
|
||||
// We add back minus_expression here because it was excluded from add_expression
|
||||
// and mod_expression.
|
||||
eq_operand:
|
||||
minus_expression | add_expression | mod_expression ;
|
||||
// We also include minus_expression here because this is the bottom of the
|
||||
// precedence diamond.
|
||||
eq_expression:
|
||||
minus_expression | add_expression | mod_expression | eq_operand '=' eq_operand ;
|
||||
```
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
%parse-param {std::optional<AST>* ast}
|
||||
|
||||
// No shift-reduce conflicts are expected.
|
||||
// See README.md#precedence-and-associativity for a description of how
|
||||
// operator precedence is expressed.
|
||||
%expect 0
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -109,6 +111,29 @@
|
||||
%type <Nonnull<Block*>> nonempty_block
|
||||
%type <Nonnull<Block*>> block
|
||||
%type <std::vector<Nonnull<Statement*>>> statement_list
|
||||
%type <Nonnull<Expression*>> primary_expression
|
||||
%type <Nonnull<Expression*>> postfix_expression
|
||||
%type <Nonnull<Expression*>> ref_deref_expression
|
||||
%type <Nonnull<Expression*>> type_expression
|
||||
%type <Nonnull<Expression*>> fn_type_expression
|
||||
%type <Nonnull<Expression*>> minus_expression
|
||||
%type <Nonnull<Expression*>> multiplicative_operand
|
||||
%type <Nonnull<Expression*>> multiplicative_lhs
|
||||
%type <Nonnull<Expression*>> multiplicative_expression
|
||||
%type <Nonnull<Expression*>> additive_operand
|
||||
%type <Nonnull<Expression*>> additive_lhs
|
||||
%type <Nonnull<Expression*>> additive_expression
|
||||
%type <Nonnull<Expression*>> unimpl_expression
|
||||
%type <Nonnull<Expression*>> value_expression
|
||||
%type <Nonnull<Expression*>> comparison_operand
|
||||
%type <Nonnull<Expression*>> comparison_expression
|
||||
%type <Nonnull<Expression*>> not_expression
|
||||
%type <Nonnull<Expression*>> predicate_expression
|
||||
%type <Nonnull<Expression*>> and_or_operand
|
||||
%type <Nonnull<Expression*>> and_lhs
|
||||
%type <Nonnull<Expression*>> and_expression
|
||||
%type <Nonnull<Expression*>> or_lhs
|
||||
%type <Nonnull<Expression*>> or_expression
|
||||
%type <Nonnull<Expression*>> expression
|
||||
%type <Nonnull<GenericBinding*>> generic_binding
|
||||
%type <std::vector<Nonnull<AstNode*>>> deduced_params
|
||||
@@ -211,31 +236,6 @@
|
||||
BINARY_STAR "binary *"
|
||||
;
|
||||
|
||||
%precedence FNARROW
|
||||
%precedence LEFT_CURLY_BRACE RIGHT_CURLY_BRACE
|
||||
%precedence COLON_BANG COLON COMMA DOUBLE_ARROW
|
||||
%left OR AND
|
||||
%nonassoc EQUAL_EQUAL
|
||||
%left PLUS MINUS
|
||||
%left BINARY_STAR
|
||||
%precedence NOT UNARY_MINUS PREFIX_STAR AMPERSAND
|
||||
// We need to give the `UNARY_STAR` token a precedence, rather than overriding
|
||||
// the precedence of the `expression UNARY_STAR` rule below, because bison
|
||||
// compares the precedence of the final token (for a shift) to the precedence
|
||||
// of the other rule (for a reduce) when attempting to resolve a shift-reduce
|
||||
// conflict. See https://stackoverflow.com/a/26188429/1041090. When UNARY_STAR
|
||||
// is the final token of a rule, it must be a postfix usage, so we give it the
|
||||
// same precedence as POSTFIX_STAR.
|
||||
%precedence POSTFIX_STAR UNARY_STAR
|
||||
%left PERIOD ARROW
|
||||
%nonassoc UNIMPL_EXAMPLE
|
||||
%precedence
|
||||
LEFT_PARENTHESIS
|
||||
RIGHT_PARENTHESIS
|
||||
LEFT_SQUARE_BRACKET
|
||||
RIGHT_SQUARE_BRACKET
|
||||
;
|
||||
|
||||
%start input
|
||||
%%
|
||||
input: package_directive import_directives declaration_list
|
||||
@@ -275,13 +275,9 @@ api_or_impl:
|
||||
| IMPL
|
||||
{ $$ = false; }
|
||||
;
|
||||
expression:
|
||||
primary_expression:
|
||||
identifier
|
||||
{ $$ = arena->New<IdentifierExpression>(context.source_loc(), $1); }
|
||||
| expression designator
|
||||
{ $$ = arena->New<FieldAccessExpression>(context.source_loc(), $1, $2); }
|
||||
| expression LEFT_SQUARE_BRACKET expression RIGHT_SQUARE_BRACKET
|
||||
{ $$ = arena->New<IndexExpression>(context.source_loc(), $1, $3); }
|
||||
| integer_literal
|
||||
{ $$ = arena->New<IntLiteral>(context.source_loc(), $1); }
|
||||
| string_literal
|
||||
@@ -309,96 +305,184 @@ expression:
|
||||
| paren_expression { $$ = $1; }
|
||||
| struct_literal { $$ = $1; }
|
||||
| struct_type_literal { $$ = $1; }
|
||||
;
|
||||
postfix_expression:
|
||||
primary_expression
|
||||
| postfix_expression designator
|
||||
{ $$ = arena->New<FieldAccessExpression>(context.source_loc(), $1, $2); }
|
||||
| postfix_expression LEFT_SQUARE_BRACKET expression RIGHT_SQUARE_BRACKET
|
||||
{ $$ = arena->New<IndexExpression>(context.source_loc(), $1, $3); }
|
||||
| intrinsic_identifier tuple
|
||||
{ $$ = arena->New<IntrinsicExpression>($1, $2, context.source_loc()); }
|
||||
| expression EQUAL_EQUAL expression
|
||||
| postfix_expression tuple
|
||||
{ $$ = arena->New<CallExpression>(context.source_loc(), $1, $2); }
|
||||
| postfix_expression POSTFIX_STAR
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Eq,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
context.source_loc(), Operator::Ptr,
|
||||
std::vector<Nonnull<Expression*>>({$1}));
|
||||
}
|
||||
| expression PLUS expression
|
||||
| postfix_expression UNARY_STAR
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Add,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
context.source_loc(), Operator::Ptr,
|
||||
std::vector<Nonnull<Expression*>>({$1}));
|
||||
}
|
||||
| expression MINUS expression
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Sub,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
}
|
||||
| expression BINARY_STAR expression
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Mul,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
}
|
||||
| expression AND expression
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::And,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
}
|
||||
| expression OR expression
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Or,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
}
|
||||
| NOT expression
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Not,
|
||||
std::vector<Nonnull<Expression*>>({$2}));
|
||||
}
|
||||
| MINUS expression %prec UNARY_MINUS
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Neg,
|
||||
std::vector<Nonnull<Expression*>>({$2}));
|
||||
}
|
||||
| PREFIX_STAR expression
|
||||
;
|
||||
ref_deref_expression:
|
||||
postfix_expression
|
||||
| PREFIX_STAR ref_deref_expression
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Deref,
|
||||
std::vector<Nonnull<Expression*>>({$2}));
|
||||
}
|
||||
| UNARY_STAR expression %prec PREFIX_STAR
|
||||
| UNARY_STAR ref_deref_expression
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Deref,
|
||||
std::vector<Nonnull<Expression*>>({$2}));
|
||||
}
|
||||
| AMPERSAND expression
|
||||
| AMPERSAND ref_deref_expression
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::AddressOf,
|
||||
std::vector<Nonnull<Expression*>>({$2}));
|
||||
}
|
||||
| expression tuple
|
||||
{ $$ = arena->New<CallExpression>(context.source_loc(), $1, $2); }
|
||||
| expression POSTFIX_STAR
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Ptr,
|
||||
std::vector<Nonnull<Expression*>>({$1}));
|
||||
}
|
||||
| expression UNARY_STAR
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Ptr,
|
||||
std::vector<Nonnull<Expression*>>({$1}));
|
||||
}
|
||||
| FN_TYPE tuple ARROW expression
|
||||
;
|
||||
fn_type_expression:
|
||||
FN_TYPE tuple ARROW type_expression
|
||||
{ $$ = arena->New<FunctionTypeLiteral>(context.source_loc(), $2, $4); }
|
||||
| expression UNIMPL_EXAMPLE expression
|
||||
;
|
||||
type_expression:
|
||||
ref_deref_expression
|
||||
| fn_type_expression
|
||||
;
|
||||
minus_expression:
|
||||
// ref_deref_expression excluded due to precedence diamond.
|
||||
MINUS ref_deref_expression
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Neg,
|
||||
std::vector<Nonnull<Expression*>>({$2}));
|
||||
}
|
||||
;
|
||||
multiplicative_operand:
|
||||
ref_deref_expression
|
||||
| minus_expression
|
||||
;
|
||||
multiplicative_lhs:
|
||||
ref_deref_expression
|
||||
| multiplicative_expression
|
||||
;
|
||||
multiplicative_expression:
|
||||
minus_expression
|
||||
| multiplicative_lhs BINARY_STAR multiplicative_operand
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Mul,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
}
|
||||
;
|
||||
additive_operand:
|
||||
ref_deref_expression
|
||||
| multiplicative_expression
|
||||
;
|
||||
additive_lhs:
|
||||
ref_deref_expression
|
||||
| additive_expression
|
||||
;
|
||||
additive_expression:
|
||||
multiplicative_expression
|
||||
| additive_lhs PLUS additive_operand
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Add,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
}
|
||||
| additive_lhs MINUS additive_operand
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Sub,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
}
|
||||
;
|
||||
unimpl_expression:
|
||||
// ref_deref_expression excluded due to precedence diamond.
|
||||
ref_deref_expression UNIMPL_EXAMPLE ref_deref_expression
|
||||
{
|
||||
$$ = arena->New<UnimplementedExpression>(context.source_loc(),
|
||||
"ExampleInfix", $1, $3);
|
||||
}
|
||||
;
|
||||
value_expression:
|
||||
// ref_deref_expression excluded due to precedence diamond.
|
||||
additive_expression
|
||||
| fn_type_expression
|
||||
| unimpl_expression
|
||||
;
|
||||
comparison_operand:
|
||||
ref_deref_expression
|
||||
| value_expression
|
||||
;
|
||||
comparison_expression:
|
||||
value_expression
|
||||
| comparison_operand EQUAL_EQUAL comparison_operand
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Eq,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
}
|
||||
;
|
||||
not_expression:
|
||||
NOT ref_deref_expression
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Not,
|
||||
std::vector<Nonnull<Expression*>>({$2}));
|
||||
}
|
||||
;
|
||||
predicate_expression:
|
||||
// ref_deref_expression excluded due to precedence diamond.
|
||||
not_expression
|
||||
| comparison_expression
|
||||
;
|
||||
and_or_operand:
|
||||
ref_deref_expression
|
||||
| predicate_expression
|
||||
;
|
||||
and_lhs:
|
||||
and_or_operand
|
||||
| and_expression
|
||||
;
|
||||
and_expression:
|
||||
// predicate_expression excluded due to precedence diamond.
|
||||
and_lhs AND and_or_operand
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::And,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
}
|
||||
;
|
||||
or_lhs:
|
||||
and_or_operand
|
||||
| or_expression
|
||||
;
|
||||
or_expression:
|
||||
// predicate_expression excluded due to precedence diamond.
|
||||
or_lhs OR and_or_operand
|
||||
{
|
||||
$$ = arena->New<PrimitiveOperatorExpression>(
|
||||
context.source_loc(), Operator::Or,
|
||||
std::vector<Nonnull<Expression*>>({$1, $3}));
|
||||
}
|
||||
;
|
||||
expression:
|
||||
ref_deref_expression
|
||||
| predicate_expression
|
||||
| and_expression
|
||||
| or_expression
|
||||
;
|
||||
designator: PERIOD identifier { $$ = $2; }
|
||||
;
|
||||
paren_expression: paren_expression_base
|
||||
@@ -481,7 +565,7 @@ non_expression_pattern:
|
||||
{ $$ = arena->New<BindingPattern>(context.source_loc(), $1, $3); }
|
||||
| paren_pattern
|
||||
{ $$ = $1; }
|
||||
| expression tuple_pattern
|
||||
| postfix_expression tuple_pattern
|
||||
{ $$ = arena->New<AlternativePattern>(context.source_loc(), $1, $2); }
|
||||
;
|
||||
binding_lhs:
|
||||
@@ -643,9 +727,9 @@ nonempty_block:
|
||||
return_term:
|
||||
// Empty
|
||||
{ $$ = ReturnTerm::Omitted(context.source_loc()); }
|
||||
| ARROW AUTO %prec FNARROW
|
||||
| ARROW AUTO
|
||||
{ $$ = ReturnTerm::Auto(context.source_loc()); }
|
||||
| ARROW expression %prec FNARROW
|
||||
| ARROW expression
|
||||
{ $$ = ReturnTerm::Explicit($2); }
|
||||
;
|
||||
generic_binding:
|
||||
|
||||
@@ -18,14 +18,14 @@ TEST(UnimplementedExampleTest, VerifyPrecedence) {
|
||||
static constexpr std::string_view Program = R"(
|
||||
package ExecutableSemanticsTest api;
|
||||
fn Main() -> i32 {
|
||||
return 1 __unimplemented_example_infix 2 + 3;
|
||||
return 1 __unimplemented_example_infix 2 == 3;
|
||||
}
|
||||
)";
|
||||
Arena arena;
|
||||
EXPECT_THAT(ParseFromString(&arena, "dummy.carbon", Program, false),
|
||||
ParsedAs(ASTDeclarations(
|
||||
ElementsAre(MatchesFunctionDeclaration().WithBody(
|
||||
BlockContentsAre(ElementsAre(MatchesReturn(MatchesAdd(
|
||||
BlockContentsAre(ElementsAre(MatchesReturn(MatchesEq(
|
||||
MatchesUnimplementedExpression(
|
||||
"ExampleInfix", ElementsAre(MatchesLiteral(1),
|
||||
MatchesLiteral(2))),
|
||||
|
||||
Reference in New Issue
Block a user