Skip to content

Commit 0818a79

Browse files
committed
Add docs about multiple start rules.
1 parent 0fd68c8 commit 0818a79

2 files changed

Lines changed: 149 additions & 0 deletions

File tree

doc/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- [grmtools parsing idioms](parsing_idioms.md)
1616
- [Error recovery](errorrecovery.md)
1717
- [An AST evaluator](ast_example.md)
18+
- [Multiple start rules](parsing_startrules.md)
1819
- [Rust Editions](editions.md)
1920
- [The individual libraries and tools](libsandtools.md)
2021
- [lrpar](lrpar.md)

doc/src/parsing_startrules.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Multiple start rules
2+
3+
lrpar parsers start with a single start rule, this chapter discusses
4+
the case where you want _multiple_ start rules.
5+
6+
## Simulating multiple start rules
7+
8+
An approach to simulating multiple start rules from a single start rule
9+
is to synthesize tokens not produced by the lexer as initial start tokens.
10+
Then produce those as the initial token passed to the parser to select between
11+
parser entry points.
12+
13+
### Initial project
14+
First create a new project, add the dependencies and build dependencies.
15+
16+
```console
17+
cargo new multistart
18+
cd multistart
19+
cargo add lrlex lrpar --build
20+
cargo add lrlex lrpar cfgrammar
21+
```
22+
23+
### Parser
24+
Here is our grammar `src/multistart.y` the tokens `START_A` and `START_B` are
25+
our synthetic tokens. While `ARule` and `BRule` are our different start rules
26+
we want to select between.
27+
28+
```
29+
%grmtools{yacckind: Grmtools}
30+
%token START_A START_B
31+
%start meta_start
32+
%%
33+
meta_start -> Result<String, DefaultLexeme> :
34+
START_A ARule { $2 }
35+
| START_B BRule { $2 }
36+
;
37+
38+
ARule -> Result<String, DefaultLexeme>
39+
: 'A' { Ok($lexer.span_str($1?.span()).to_string()) }
40+
| BRule { $1 }
41+
;
42+
43+
BRule -> Result<String, DefaultLexeme>
44+
: 'B' { Ok($lexer.span_str($1?.span()).to_string()) }
45+
;
46+
%%
47+
use lrlex::DefaultLexeme;
48+
```
49+
50+
### Lexer
51+
52+
In our lexer we'll need to add a directive to advise that the checker that we expect the missing tokens.
53+
54+
```
55+
%expect-missing "START_A" "START_B"
56+
%%
57+
A "A"
58+
B "B"
59+
[\ \n\t] ;
60+
```
61+
62+
Following this we'll need to prepend one of the start tokens to the lexical analysis phase.
63+
To indicate which parse rule we expect to use as the start rule. For that we'll need a `token_map`.
64+
65+
### Build.rs
66+
67+
```rust
68+
use lrlex::{CTLexerBuilder, CTTokenMapBuilder, DefaultLexerTypes};
69+
use lrpar::CTParserBuilder;
70+
71+
fn main() {
72+
// In addition to the usual parser, and lexer
73+
// We'll need a token map for our synthetic tokens.
74+
let ctp = CTParserBuilder::<DefaultLexerTypes>::new()
75+
.grammar_in_src_dir("multistart.y")
76+
.unwrap()
77+
.build()
78+
.unwrap();
79+
CTLexerBuilder::new()
80+
.rule_ids_map(ctp.token_map())
81+
.lexer_in_src_dir("multistart.l")
82+
.unwrap()
83+
.build()
84+
.unwrap();
85+
CTTokenMapBuilder::new("token_map", ctp.token_map())
86+
.allow_dead_code(true)
87+
.build()
88+
.unwrap();
89+
}
90+
```
91+
92+
### Wrapping the lexer
93+
94+
Finally we'll need to:
95+
96+
1. Run our input text through the parser.
97+
2. Prepend the selected start token.
98+
3. Run the parser with the combined tokens.
99+
100+
```rust
101+
use cfgrammar::NewlineCache;
102+
use lrlex::LRLexError;
103+
use lrlex::{DefaultLexeme, DefaultLexerTypes, LRNonStreamingLexer, lrlex_mod};
104+
use lrpar::Lexer as _;
105+
use lrpar::{Lexeme, lrpar_mod};
106+
107+
lrlex_mod!("multistart.l");
108+
lrpar_mod!("multistart.y");
109+
110+
lrlex_mod!("token_map");
111+
use token_map::{T_START_A, T_START_B};
112+
113+
fn lrlex_wrapper(s: &str, start_token: u32) -> LRNonStreamingLexer<'_, '_, DefaultLexerTypes> {
114+
let nl_cache = NewlineCache::new();
115+
let lexerdef = multistart_l::lexerdef();
116+
let mut tokens: Vec<Result<DefaultLexeme, LRLexError>> =
117+
vec![Ok(DefaultLexeme::new(start_token, 0, 0))];
118+
let lexer = lexerdef.lexer(s);
119+
tokens.extend(lexer.iter());
120+
LRNonStreamingLexer::new(s, tokens, nl_cache)
121+
}
122+
123+
fn main() {
124+
assert!(
125+
multistart_y::parse(&lrlex_wrapper("A", T_START_A))
126+
.0
127+
.is_some()
128+
);
129+
assert!(
130+
multistart_y::parse(&lrlex_wrapper("B", T_START_B))
131+
.0
132+
.is_some()
133+
);
134+
// This should parse okay, since `START_A` takes either "A", or "B" as input.
135+
assert!(
136+
multistart_y::parse(&lrlex_wrapper("B", T_START_A))
137+
.0
138+
.is_some()
139+
);
140+
141+
// However the following should error since `START_B` only takes "B" as input.
142+
assert!(
143+
!multistart_y::parse(&lrlex_wrapper("A", T_START_B))
144+
.1
145+
.is_empty()
146+
);
147+
}
148+
```

0 commit comments

Comments
 (0)