Skip to content

Commit 23007f4

Browse files
committed
Allow using generics with %parse-param
This commit adds a new directive `%type-params`, which allows passing arbitrary generics (types and lifetimes) to the generated parser implementation. The generics are propagated to action functions and `__GtActionsKind`, so they're available in return types as well. This change only affects generated code: generics are not propagated to parser backend. I've also added an example with my use case: passing an arena allocator, and returning references to AST nodes.
1 parent f7397a7 commit 23007f4

File tree

15 files changed

+328
-33
lines changed

15 files changed

+328
-33
lines changed

.buildbot.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ touch src/main.rs && CACHE_EXPECTED=y cargo build
6161
cd $root/lrpar/examples/calc_ast
6262
echo "2 + 3 * 4" | cargo run --package nimbleparse -- src/calc.l src/calc.y -
6363
echo "2 + 3 * 4" | cargo run | grep "Result: 14"
64+
cd $root/lrpar/examples/calc_ast_arena
65+
echo "2 + 3 * 4" | cargo run --package nimbleparse -- src/calc.l src/calc.y -
66+
echo "2 + 3 * 4" | cargo run | grep "Result: 14"
6467
touch src/main.rs && CACHE_EXPECTED=y cargo build
6568
cd $root/lrpar/examples/calc_parsetree
6669
echo "2 + 3 * 4" | cargo run --package nimbleparse -- src/calc.l src/calc.y -

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ members=[
1010
"lrpar/examples/calc_actions",
1111
"lrpar/examples/calc_ast",
1212
"lrpar/examples/calc_parsetree",
13+
"lrpar/examples/calc_ast_arena",
1314
"lrpar/examples/start_states",
1415
"lrpar/examples/clone_param",
1516
"lrtable",

cfgrammar/src/lib/yacc/ast.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ pub struct GrammarAST {
170170
pub expect: Option<(usize, Span)>,
171171
pub expectrr: Option<(usize, Span)>,
172172
pub parse_param: Option<(String, String)>,
173+
pub parse_generics: Option<String>,
173174
pub programs: Option<String>,
174175
// The set of symbol names that, if unused in a
175176
// grammar, will not cause a warning or error.
@@ -248,6 +249,7 @@ impl GrammarAST {
248249
expect: None,
249250
expectrr: None,
250251
parse_param: None,
252+
parse_generics: None,
251253
programs: None,
252254
expect_unused: Vec::new(),
253255
}

cfgrammar/src/lib/yacc/grammar.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ pub struct YaccGrammar<StorageT = u32> {
8383
actions: Box<[Option<String>]>,
8484
/// A `(name, type)` pair defining an extra parameter to pass to action functions.
8585
parse_param: Option<(String, String)>,
86+
/// Generic parameters (types and lifetimes) to pass to action functions.
87+
parse_generics: Option<String>,
8688
/// Lifetimes for `param_args`
8789
programs: Option<String>,
8890
/// The actiontypes of rules (one per rule).
@@ -130,6 +132,7 @@ where
130132
implicit_rule: Decode::decode(decoder)?,
131133
actions: Decode::decode(decoder)?,
132134
parse_param: Decode::decode(decoder)?,
135+
parse_generics: Decode::decode(decoder)?,
133136
programs: Decode::decode(decoder)?,
134137
actiontypes: Decode::decode(decoder)?,
135138
avoid_insert: Decode::decode(decoder)?,
@@ -168,6 +171,7 @@ where
168171
implicit_rule: ::bincode::BorrowDecode::<'_, __Context>::borrow_decode(decoder)?,
169172
actions: ::bincode::BorrowDecode::<'_, __Context>::borrow_decode(decoder)?,
170173
parse_param: ::bincode::BorrowDecode::<'_, __Context>::borrow_decode(decoder)?,
174+
parse_generics: ::bincode::BorrowDecode::<'_, __Context>::borrow_decode(decoder)?,
171175
programs: ::bincode::BorrowDecode::<'_, __Context>::borrow_decode(decoder)?,
172176
actiontypes: ::bincode::BorrowDecode::<'_, __Context>::borrow_decode(decoder)?,
173177
avoid_insert: ::bincode::BorrowDecode::<'_, __Context>::borrow_decode(decoder)?,
@@ -456,6 +460,7 @@ where
456460
implicit_rule: implicit_rule.map(|x| rule_map[&x]),
457461
actions: actions.into_boxed_slice(),
458462
parse_param: ast.parse_param.clone(),
463+
parse_generics: ast.parse_generics.clone(),
459464
programs: ast.programs.clone(),
460465
avoid_insert,
461466
actiontypes: actiontypes.into_boxed_slice(),
@@ -628,6 +633,10 @@ where
628633
&self.parse_param
629634
}
630635

636+
pub fn parse_generics(&self) -> &Option<String> {
637+
&self.parse_generics
638+
}
639+
631640
/// Get the programs part of the grammar
632641
pub fn programs(&self) -> &Option<String> {
633642
&self.programs

cfgrammar/src/lib/yacc/parser.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,13 @@ impl YaccParser<'_> {
549549
i = self.parse_ws(j, true)?;
550550
continue;
551551
}
552+
if let Some(j) = self.lookahead_is("%parse-generics", i) {
553+
i = self.parse_ws(j, false)?;
554+
let (j, ty) = self.parse_to_eol(i)?;
555+
self.ast.parse_generics = Some(ty);
556+
i = self.parse_ws(j, true)?;
557+
continue;
558+
}
552559
if let YaccKind::Eco = self.yacc_kind {
553560
if let Some(j) = self.lookahead_is("%implicit_tokens", i) {
554561
i = self.parse_ws(j, false)?;
@@ -2454,6 +2461,18 @@ x"
24542461
);
24552462
}
24562463

2464+
#[test]
2465+
fn test_parse_generics() {
2466+
let src = "
2467+
%parse-generics 'a, K, V
2468+
%%
2469+
A: 'a';
2470+
";
2471+
let grm = parse(YaccKind::Original(YaccOriginalActionKind::UserAction), src).unwrap();
2472+
2473+
assert_eq!(grm.parse_generics, Some("'a, K, V".to_owned()));
2474+
}
2475+
24572476
#[test]
24582477
fn test_duplicate_rule() {
24592478
let ast = parse(

doc/src/actioncode.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,19 @@ R -> ...:
6969
'ID' { format!("{}{}", p, ...) }
7070
;
7171
```
72+
73+
# Generic parse parameter
74+
75+
If `%parse-param` needs to be generic, additional type variables and lifetimes
76+
can be specified in the `%parse-generics T1, T2, ...` declaration.
77+
78+
For example, if a grammar has following declarations:
79+
80+
```
81+
%parse-generics T: FromStr
82+
%parse-param p: T
83+
```
84+
85+
then the `parse` function will take an additional parameter of type `T`.
86+
87+
This can be used, for example, [to allocate AST nodes in a memory arena.](https://github.com/softdevteam/grmtools/tree/master/lrpar/examples/calc_ast_arena).

lrpar/cttests/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ lrpar_mod!("parseparam.y");
4747
lrlex_mod!("parseparam_copy.l");
4848
lrpar_mod!("parseparam_copy.y");
4949

50+
lrlex_mod!("typeparams.l");
51+
lrpar_mod!("typeparams.y");
52+
5053
lrlex_mod!("passthrough.l");
5154
lrpar_mod!("passthrough.y");
5255

@@ -294,6 +297,16 @@ fn test_parseparam_copy() {
294297
}
295298
}
296299

300+
#[test]
301+
fn test_typeparams() {
302+
let lexerdef = typeparams_l::lexerdef();
303+
let lexer = lexerdef.lexer("101");
304+
match typeparams_y::parse(&lexer, &3u64) {
305+
(Some(104u64), _) => (),
306+
_ => unreachable!(),
307+
}
308+
}
309+
297310
#[test]
298311
fn test_passthrough() {
299312
let lexerdef = passthrough_l::lexerdef();

lrpar/cttests/src/typeparams.test

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Test %parse-param
2+
yacckind: Grmtools
3+
grammar: |
4+
%start S
5+
%parse-generics 'a, T: Into<u64> + Copy, R: From<u64>
6+
%parse-param p: &'a T
7+
%%
8+
S -> R:
9+
'INT' { From::from((*p).into() + $lexer.span_str($1.unwrap().span()).parse::<u64>().unwrap()) }
10+
;
11+
%%
12+
lexer: |
13+
%%
14+
[0-9]+ 'INT'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "calc_ast_arena"
3+
version = "0.1.0"
4+
authors = ["Laurence Tratt <http://tratt.net/laurie/>"]
5+
edition = "2024"
6+
license = "Apache-2.0/MIT"
7+
8+
[[bin]]
9+
doc = false
10+
name = "calc_ast_arena"
11+
12+
[build-dependencies]
13+
cfgrammar = { path="../../../cfgrammar" }
14+
lrlex = { path="../../../lrlex" }
15+
lrpar = { path="../.." }
16+
17+
[dependencies]
18+
cfgrammar = { path="../../../cfgrammar" }
19+
lrlex = { path="../../../lrlex" }
20+
lrpar = { path="../.." }
21+
bumpalo = "3"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![deny(rust_2018_idioms)]
2+
use lrlex::CTLexerBuilder;
3+
4+
fn main() {
5+
// Since we're using both lrlex and lrpar, we use lrlex's `lrpar_config` convenience function
6+
// that makes it easy to a) create a lexer and parser and b) link them together.
7+
CTLexerBuilder::new()
8+
.rust_edition(lrlex::RustEdition::Rust2021)
9+
.lrpar_config(|ctp| {
10+
ctp.rust_edition(lrpar::RustEdition::Rust2021)
11+
.grammar_in_src_dir("calc.y")
12+
.unwrap()
13+
})
14+
.lexer_in_src_dir("calc.l")
15+
.unwrap()
16+
.build()
17+
.unwrap();
18+
}

0 commit comments

Comments
 (0)