Skip to content

Commit ae6c33c

Browse files
committed
document ambiguity and precedence a bit more
1 parent 13fe33c commit ae6c33c

File tree

3 files changed

+22
-13
lines changed

3 files changed

+22
-13
lines changed

book/src/formality_core/parse.md

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,33 @@ struct MyEnum {
1919
}
2020
```
2121

22-
### Ambiguity and precedence
22+
### Ambiguity and greedy parsing
2323

24-
When parsing an enum there will be multiple possibilities. We will attempt to parse them all. If more than one succeeds, the parser will attempt to resolve the ambiguity. Ambiguity can be resolved in two ways:
24+
When parsing an enum there will be multiple possibilities. We will attempt to parse them all. If more than one succeeds, the parser will attempt to resolve the ambiguity by looking for the **longest match**. However, we don't just consider the number of characters, we look for a **reduction prefix**:
2525

26-
* Explicit precedence: By default, every variant has precedence 0, but you can override this by annotating variants with `#[precedence(N)]` (where `N` is some integer). This will override the precedence for that variant. Variants with higher precedences are preferred.
27-
* Reduction prefix: When parsing, we track the list of things we had to parse. If there are two variants at the same precedence level, but one of them had to parse strictly more things than the other and in the same way, we'll prefer the longer one. So for example if one variant parsed a `Ty` and the other parsed a `Ty Ty`, we'd take the `Ty Ty`.
26+
* When parsing, we track the list of things we had to parse. If there are two variants at the same precedence level, but one of them had to parse strictly more things than the other and in the same way, we'll prefer the longer one. So for example if one variant parsed a `Ty` and the other parsed a `Ty Ty`, we'd take the `Ty Ty`.
2827
* When considering whether a reduction is "significant", we take casts into account. See `ActiveVariant::mark_as_cast_variant` for a more detailed explanation and set of examples.
2928

30-
Otherwise, the parser will panic and report ambiguity. The parser panics rather than returning an error because ambiguity doesn't mean that there is no way to parse the given text as the nonterminal -- rather that there are multiple ways. Errors mean that the text does not match the grammar for that nonterminal.
29+
### Precedence and left-recursive grammars
3130

32-
### Left-recursive grammars
33-
34-
We permit left recursive grammars like:
31+
We support left-recursive grammars like this one from the `parse-torture-tests`:
3532

33+
```rust
34+
{{#include ../../../tests/parser-torture-tests/src/path.rs:path}}
3635
```
37-
Expr = Expr + Expr
38-
| integer
36+
37+
We also support ambiguous grammars. For example, you can code up arithmetic expressions like this:
38+
39+
40+
```rust
41+
{{#include ../../../tests/parser-torture-tests/src/left_associative.rs:Expr}}
3942
```
4043

41-
We *always* bias towards greedy parses, so `a + b + c` parses as `(a + b) + c`.
42-
This might occasionally not be what you want.
43-
Sorry.
44+
When specifying the `#[precedence]` of a variant, the default is left-associativity, which can be written more explicitly as `#[precedence(L, left)]`. If you prefer, you can specify right-associativity (`#[precedence(L, right)]`) or non-associativity `#[precedence(L, none)]`. This affects how things of the same level are parsed:
45+
46+
* `1 + 1 + 1` when left-associative is `(1 + 1) + 1`
47+
* `1 + 1 + 1` when right-associative is `1 + (1 + 1)`
48+
* `1 + 1 + 1` when none-associative is an error.
4449

4550
### Symbols
4651

tests/parser-torture-tests/left_associative.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use formality_core::{term, test};
22
use std::sync::Arc;
33

4+
// ANCHOR: Expr
45
#[term]
56
pub enum Expr {
67
#[cast]
@@ -16,6 +17,7 @@ pub enum Expr {
1617
}
1718

1819
formality_core::id!(Id);
20+
// ANCHOR_END: Expr
1921

2022
#[test]
2123
fn add_mul() {

tests/parser-torture-tests/path.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use formality_core::{term, test};
22
use std::sync::Arc;
33

4+
// ANCHOR: path
45
#[term]
56
pub enum Path {
67
#[cast]
@@ -14,6 +15,7 @@ pub enum Path {
1415
}
1516

1617
formality_core::id!(Id);
18+
// ANCHOR_END: path
1719

1820
#[test]
1921
fn path() {

0 commit comments

Comments
 (0)