Skip to content

Commit d1dfd6c

Browse files
committed
Grammar-wide injected variables for things like spans
1 parent 830ba4f commit d1dfd6c

File tree

8 files changed

+482
-65
lines changed

8 files changed

+482
-65
lines changed

peg-macros/ast.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,21 @@ impl Grammar {
2525
#[derive(Debug)]
2626
pub enum Item {
2727
Use(TokenStream),
28+
InjectVar(InjectVar),
2829
Rule(Rule),
2930
}
3031

32+
#[derive(Debug)]
33+
pub struct InjectVar {
34+
pub doc: Option<TokenStream>,
35+
pub name: Ident,
36+
pub input_param: Ident,
37+
pub lpos_param: Ident,
38+
pub rpos_param: Ident,
39+
pub ty: TokenStream,
40+
pub body: Group,
41+
}
42+
3143
#[derive(Debug)]
3244
pub enum Cache {
3345
Simple,

peg-macros/grammar.rs

Lines changed: 251 additions & 38 deletions
Large diffs are not rendered by default.

peg-macros/grammar.rustpeg

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ pub rule peg_grammar() -> Grammar
1515
rule grammar_args() -> Vec<(Ident, TokenStream)>
1616
= "(" args:((i:IDENT() ":" t:$(rust_type()) { (i, t) })**",") ","? ")" { args }
1717

18+
rule item() -> Item
19+
= u:rust_use() { Item::Use(u) }
20+
/ r:peg_rule() { Item::Rule(r) }
21+
/ f:inject_var() { Item::InjectVar(f) }
22+
23+
rule inject_var() -> InjectVar
24+
= doc:rust_doc_comment() "inject" name:IDENT() "(" input_param:IDENT() "," lpos_param:IDENT() "," rpos_param:IDENT() ")" "->" ty:$(rust_type()) body:BRACE_GROUP()
25+
{ InjectVar { doc, name, input_param, lpos_param, rpos_param, ty, body } }
26+
1827
rule peg_rule() -> Rule
1928
= doc:rust_doc_comment() cache:cacheflag() no_eof:no_eof_flag() visibility:rust_visibility()
2029
span:sp() "rule"
@@ -41,10 +50,6 @@ rule peg_rule() -> Rule
4150
rule rule_params() -> Vec<RuleParam>
4251
= "(" params:(x:(name:IDENT() ":" ty:rule_param_ty() { RuleParam { name, ty} }) ++ "," ","? {x})? ")" { params.unwrap_or_default() }
4352

44-
rule item() -> Item
45-
= u:rust_use() { Item::Use(u) }
46-
/ r:peg_rule() { Item::Rule(r) }
47-
4853
rule rust_doc_comment() -> Option<TokenStream> = $(("#" "[" "doc" "=" LITERAL() "]")*)?
4954
rule rust_attribute() = "#" "[" rust_path() (DELIM_GROUP() / "=" LITERAL()) "]"
5055

peg-macros/translate.rs

Lines changed: 98 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,26 @@ struct Context<'a> {
5252
parse_state_ty: TokenStream,
5353
extra_args_call: TokenStream,
5454
extra_args_def: TokenStream,
55+
injected_vars: TokenStream,
5556
}
5657

5758
pub(crate) fn compile_grammar(grammar: &Grammar) -> TokenStream {
5859
let analysis = analysis::check(grammar);
5960

6061
let grammar_lifetime_params = ty_params_slice(&grammar.lifetime_params);
62+
let extra_args_def = extra_args_def(grammar);
63+
let extra_args_call = extra_args_call(grammar);
64+
let injected_vars = invoke_injected_vars(grammar, &extra_args_call);
6165

6266
let context = &Context {
6367
rules: &analysis.rules,
6468
rules_from_args: HashSet::new(),
6569
grammar_lifetime_params,
6670
input_ty: quote!(&'input Input<#(#grammar_lifetime_params),*>),
6771
parse_state_ty: quote!(&mut ParseState<'input #(, #grammar_lifetime_params)*>),
68-
extra_args_call: extra_args_call(grammar),
69-
extra_args_def: extra_args_def(grammar),
72+
extra_args_call,
73+
extra_args_def,
74+
injected_vars,
7075
};
7176

7277
let mut seen_rule_names = HashSet::new();
@@ -75,6 +80,7 @@ pub(crate) fn compile_grammar(grammar: &Grammar) -> TokenStream {
7580
for item in &grammar.items {
7681
match item {
7782
Item::Use(tt) => items.push(tt.clone()),
83+
Item::InjectVar(var) => items.push(compile_inject_func(context, var)),
7884
Item::Rule(rule) => {
7985
if !seen_rule_names.insert(rule.name.to_string()) {
8086
items.push(report_error(
@@ -208,6 +214,61 @@ fn rule_params_list(context: &Context, rule: &Rule) -> Vec<TokenStream> {
208214
}).collect()
209215
}
210216

217+
fn compile_inject_func(context: &Context, var: &InjectVar) -> TokenStream {
218+
let span = var.name.span().resolved_at(Span::mixed_site());
219+
220+
let InjectVar {
221+
doc,
222+
name,
223+
input_param,
224+
lpos_param,
225+
rpos_param,
226+
ty,
227+
body,
228+
} = var;
229+
230+
let name = format_ident!("__inject_{}", name, span = span);
231+
232+
let Context {
233+
input_ty,
234+
grammar_lifetime_params,
235+
extra_args_def,
236+
..
237+
} = context;
238+
239+
quote_spanned! { span =>
240+
#doc
241+
fn #name<'input #(, #grammar_lifetime_params)*>(
242+
#input_param: #input_ty,
243+
#lpos_param: usize,
244+
#rpos_param: usize
245+
#extra_args_def
246+
) -> #ty #body
247+
}
248+
}
249+
250+
fn invoke_injected_vars(grammar: &Grammar, extra_args_call: &TokenStream) -> TokenStream {
251+
let vars = grammar
252+
.items
253+
.iter()
254+
.filter_map(|item| match item {
255+
Item::InjectVar(var) => Some(var),
256+
_ => None,
257+
})
258+
.map(|var| {
259+
let name = &var.name;
260+
let name_fn = format_ident!("__inject_{}", var.name);
261+
let span = var.name.span().resolved_at(Span::mixed_site());
262+
quote_spanned! { span =>
263+
#[allow(unused)]
264+
let #name = #name_fn(__input, __lpos, __pos #extra_args_call);
265+
}
266+
})
267+
.collect::<Vec<_>>();
268+
269+
quote!(#(#vars)*)
270+
}
271+
211272
/// Compile a rule to a function for use internal to the grammar.
212273
/// Returns `RuleResult<T>`.
213274
fn compile_rule(context: &Context, rule: &Rule) -> TokenStream {
@@ -777,28 +838,44 @@ fn compile_expr(context: &Context, e: &SpannedExpr, result_used: bool) -> TokenS
777838
}}
778839
}
779840

780-
Expr::Action(ref exprs, ref code) => labeled_seq(context, exprs, {
781-
if let Some(code) = code {
782-
let code_span = code.span().resolved_at(Span::mixed_site());
783-
784-
// Peek and see if the first token in the block is '?'. If so, it's a conditional block
785-
if let Some(body) = group_check_prefix(code, '?') {
786-
quote_spanned! {code_span =>
787-
match (||{ #body })() {
788-
Ok(res) => ::peg::RuleResult::Matched(__pos, res),
789-
Err(expected) => {
790-
__err_state.mark_failure(__pos, expected);
791-
::peg::RuleResult::Failed
792-
},
841+
Expr::Action(ref exprs, ref code) => {
842+
let seq = labeled_seq(context, exprs, {
843+
if let Some(code) = code {
844+
let injected_vars = &context.injected_vars;
845+
let code_span = code.span().resolved_at(Span::mixed_site());
846+
847+
// Peek and see if the first token in the block is '?'. If so, it's a conditional block
848+
if let Some(body) = group_check_prefix(code, '?') {
849+
quote_spanned! {code_span =>
850+
match (||{ #injected_vars #body })() {
851+
Ok(res) => ::peg::RuleResult::Matched(__pos, res),
852+
Err(expected) => {
853+
__err_state.mark_failure(__pos, expected);
854+
::peg::RuleResult::Failed
855+
},
856+
}
793857
}
858+
} else {
859+
let body = code.stream();
860+
quote_spanned! {code_span => ::peg::RuleResult::Matched(__pos, (|| {
861+
#injected_vars
862+
#body
863+
} )()) }
794864
}
795865
} else {
796-
quote_spanned! {code_span => ::peg::RuleResult::Matched(__pos, (||#code)()) }
866+
quote_spanned! { span => ::peg::RuleResult::Matched(__pos, ()) }
797867
}
868+
});
869+
870+
if context.injected_vars.is_empty() {
871+
seq
798872
} else {
799-
quote_spanned! { span => ::peg::RuleResult::Matched(__pos, ()) }
873+
quote_spanned! { span => {
874+
let __lpos = __pos;
875+
#seq
876+
}}
800877
}
801-
}),
878+
}
802879
Expr::MatchStr(ref expr) => {
803880
let inner = compile_expr(context, expr, false);
804881
quote_spanned! { span => {
@@ -826,6 +903,7 @@ fn compile_expr(context: &Context, e: &SpannedExpr, result_used: bool) -> TokenS
826903
}
827904

828905
Expr::Precedence { ref levels } => {
906+
let injected_vars = &context.injected_vars;
829907
let mut pre_rules = Vec::new();
830908
let mut level_code = Vec::new();
831909
let mut span_capture: Option<(TokenStream, TokenStream, TokenStream, &Group)> = None;
@@ -848,8 +926,8 @@ fn compile_expr(context: &Context, e: &SpannedExpr, result_used: bool) -> TokenS
848926
let right_arg = &op.elements[op.elements.len() - 1];
849927
let r_arg = name_or_ignore(right_arg.name.as_ref());
850928

851-
let action = &op.action;
852-
let action = quote_spanned!(op.action.span()=>(||#action)());
929+
let action = &op.action.stream();
930+
let action = quote_spanned!(op.action.span()=>(||{ #injected_vars #action })());
853931

854932
let action = if let Some((lpos_name, val_name, rpos_name, wrap_action)) =
855933
&span_capture

tests/compile-fail/syntax_error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
extern crate peg;
22

33
peg::parser!(grammar foo() for str {
4-
fn asdf() {} //~ ERROR expected one of "#", "crate", "pub", "rule", "use", "}"
4+
fn asdf() {} //~ ERROR expected one of "#", "inject", "crate", "pub", "rule", "use", "}"
55
});
66

77
fn main() {}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
error: expected one of "#", "pub", "rule", "use", "}"
1+
error: expected one of "#", "inject", "pub", "rule", "use", "}"
22
--> tests/compile-fail/syntax_error.rs:4:5
33
|
4-
4 | fn asdf() {} //~ ERROR expected one of "#", "crate", "pub", "rule", "use", "}"
4+
4 | fn asdf() {} //~ ERROR expected one of "#", "inject", "crate", "pub", "rule", "use", "}"
55
| ^^

tests/pass/inject_span.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use std::ops::Range;
2+
3+
#[derive(Debug, PartialEq)]
4+
struct Node {
5+
span: Range<usize>,
6+
kind: NodeKind,
7+
}
8+
9+
#[derive(Debug, PartialEq)]
10+
enum NodeKind {
11+
Number(i64),
12+
Add(Box<Node>, Box<Node>),
13+
Sub(Box<Node>, Box<Node>),
14+
Mul(Box<Node>, Box<Node>),
15+
Div(Box<Node>, Box<Node>),
16+
Factorial(Box<Node>),
17+
Neg(Box<Node>),
18+
Group(Box<Node>),
19+
Var(String),
20+
}
21+
22+
peg::parser!( grammar lang() for str {
23+
inject span(_input, lpos, rpos) -> Range<usize> { lpos..rpos }
24+
25+
rule number() -> Node
26+
= n:$(['0'..='9']+) {? match n.parse() {
27+
Ok(n) => Ok(Node { span, kind: NodeKind::Number(n) }),
28+
Err(_) => Err("number too large"),
29+
}}
30+
31+
rule var() -> Node
32+
= v:$(['a'..='z']+) { Node { span, kind: NodeKind::Var(v.to_string()) } }
33+
34+
pub rule expr() -> Node = precedence!{
35+
x:(@) "+" y:@ { Node { span, kind: NodeKind::Add(Box::new(x), Box::new(y)) } }
36+
x:(@) "-" y:@ { Node { span, kind: NodeKind::Sub(Box::new(x), Box::new(y)) } }
37+
"-" v:@ { Node { span, kind: NodeKind::Neg(Box::new(v)) } }
38+
--
39+
x:(@) "*" y:@ { Node { span, kind: NodeKind::Mul(Box::new(x), Box::new(y)) } }
40+
x:(@) "/" y:@ { Node { span, kind: NodeKind::Div(Box::new(x), Box::new(y)) } }
41+
--
42+
v:@ "!" { Node { span, kind: NodeKind::Factorial(Box::new(v)) } }
43+
--
44+
"(" v:expr() ")" { Node { span, kind: NodeKind::Group(Box::new(v)) } }
45+
v:var() { v }
46+
n:number() { n }
47+
}
48+
});
49+
50+
#[test]
51+
fn main() {
52+
assert_eq!(
53+
lang::expr("3+3*(-33+v!)"),
54+
Ok(Node {
55+
span: 0..12,
56+
kind: NodeKind::Add(
57+
Box::new(Node {
58+
span: 0..1,
59+
kind: NodeKind::Number(3),
60+
}),
61+
Box::new(Node {
62+
span: 2..12,
63+
kind: NodeKind::Mul(
64+
Box::new(Node {
65+
span: 2..3,
66+
kind: NodeKind::Number(3),
67+
}),
68+
Box::new(Node {
69+
span: 4..12,
70+
kind: NodeKind::Group(Box::new(Node {
71+
span: 5..11,
72+
kind: NodeKind::Add(
73+
Box::new(Node {
74+
span: 5..8,
75+
kind: NodeKind::Neg(Box::new(Node {
76+
span: 6..8,
77+
kind: NodeKind::Number(33),
78+
})),
79+
}),
80+
Box::new(Node {
81+
span: 9..11,
82+
kind: NodeKind::Factorial(Box::new(Node {
83+
span: 9..10,
84+
kind: NodeKind::Var("v".to_string()),
85+
})),
86+
}),
87+
),
88+
})),
89+
}),
90+
)
91+
}),
92+
),
93+
})
94+
);
95+
}
96+
97+
peg::parser!( grammar inject2(offset: usize) for str {
98+
inject span(_input, lpos, rpos) -> Range<usize> { (offset + lpos)..(offset + rpos) }
99+
inject text(input, lpos, rpos) -> &'input str { &input[lpos..rpos] }
100+
101+
pub rule test() -> (Range<usize>, String)
102+
= "abc" { (span, text.to_string()) }
103+
});
104+
105+
#[test]
106+
fn inject2() {
107+
assert_eq!(inject2::test("abc", 10), Ok((10..13, "abc".into())));
108+
}

tests/pass/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod custom_expr;
1313
mod errors;
1414
mod generic_fn_traits;
1515
mod grammar_with_args_and_cache;
16+
mod inject_span;
1617
mod keyval;
1718
mod lifetimes;
1819
mod memoization;

0 commit comments

Comments
 (0)