Skip to content

Commit 9deb0d7

Browse files
committed
yeast: Add internal AST for rules
Extends the previous work. We now explicitly split apart parsing of rules from lowering them into Rust code. There is some redundancy here -- a Pattern is basically the same as the Query that we eventually execute, but cleaning that up would introduce a lot of churn, so I've tried to leave it as-is for now.
1 parent a46fb66 commit 9deb0d7

6 files changed

Lines changed: 1261 additions & 1236 deletions

File tree

‎shared/yeast-macros/src/ast.rs‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,90 @@
11
use proc_macro2::{Ident, Literal};
22

3+
/// A complete rewrite rule such as `(input) where guard => (output)`.
4+
pub(crate) struct Rule {
5+
/// Input pattern before `where` or `=>`.
6+
pub(crate) pattern: Pattern,
7+
/// Optional Rust guard expression after `where`.
8+
pub(crate) guard: Option<syn::Expr>,
9+
/// Template or annotated Rust replacement after `=>`.
10+
pub(crate) replacement: Replacement,
11+
}
12+
13+
pub(crate) enum Replacement {
14+
/// One or more output templates such as `(call ...) {extra_nodes}`.
15+
Templates(Vec<Template>),
16+
/// Annotated Rust body such as `expr? { ... }`.
17+
Rust {
18+
/// Retained in the rule AST for validation and analysis; runtime
19+
/// lowering only needs the Rust body.
20+
#[allow(dead_code)]
21+
annotation: ReturnAnnotation,
22+
body: syn::Block,
23+
},
24+
}
25+
26+
#[allow(dead_code)]
27+
pub(crate) struct ReturnAnnotation {
28+
/// Declared output kind before the Rust body.
29+
pub(crate) kind: Ident,
30+
/// Declared output cardinality (`kind`, `kind?`, or `kind*`).
31+
pub(crate) cardinality: Cardinality,
32+
}
33+
34+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35+
pub(crate) struct Cardinality {
36+
pub(crate) multiple: bool,
37+
pub(crate) required: bool,
38+
}
39+
40+
impl Cardinality {
41+
pub(crate) const SINGLE: Self = Self {
42+
multiple: false,
43+
required: true,
44+
};
45+
pub(crate) const OPTIONAL: Self = Self {
46+
multiple: false,
47+
required: false,
48+
};
49+
pub(crate) const ZERO_OR_MORE: Self = Self {
50+
multiple: true,
51+
required: false,
52+
};
53+
pub(crate) const ONE_OR_MORE: Self = Self {
54+
multiple: true,
55+
required: true,
56+
};
57+
}
58+
59+
/// An input pattern such as `(call method: (identifier) @name)`.
60+
pub(crate) enum Pattern {
61+
/// `_` or `(_)`, distinguished by whether unnamed nodes may match.
62+
Any { match_unnamed: bool },
63+
/// A named node and the pattern matched against each field.
64+
Node {
65+
kind: String,
66+
fields: Vec<(String, Pattern)>,
67+
},
68+
/// An unnamed token pattern such as `"+"`.
69+
Unnamed(String),
70+
/// A captured pattern such as `(identifier) @name` or `_ @@raw`.
71+
Capture {
72+
/// Capture identifier after `@` or `@@`.
73+
name: Ident,
74+
/// Whether `@@` keeps the captured input node untranslated.
75+
raw: bool,
76+
pattern: Box<Pattern>,
77+
cardinality: Cardinality,
78+
},
79+
/// An ordered sequence of patterns within a field.
80+
Sequence(Vec<Pattern>),
81+
/// A repeated or optional pattern.
82+
Repeated {
83+
pattern: Box<Pattern>,
84+
cardinality: Cardinality,
85+
},
86+
}
87+
388
pub(crate) enum Template {
489
/// A parenthesized node template such as `(call method: {name})`.
590
Node(Node),

‎shared/yeast-macros/src/lib.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ use proc_macro2::TokenStream as TokenStream2;
44
mod ast;
55
mod lower;
66
mod parse;
7+
mod rule_parse;
8+
mod template_parse;
79

8-
/// Proc macro for constructing a `QueryNode` from a tree-sitter-inspired pattern.
10+
/// Proc macro for constructing a tree-sitter-inspired query `Pattern`.
911
///
1012
/// # Syntax
1113
///

‎shared/yeast-macros/src/lower.rs‎

Lines changed: 251 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use quote::quote;
33
use std::sync::atomic::{AtomicUsize, Ordering};
44
use syn::Lifetime;
55

6-
use crate::ast::{Content, Field, Node, Template};
6+
use crate::ast::{Cardinality, Content, Field, Node, Pattern, Replacement, Rule, Template};
77

88
struct LoweringContext<'a> {
99
/// Enclosing optional-field label to exit when a computed value is absent.
@@ -16,6 +16,256 @@ fn fresh_fallible_label() -> Lifetime {
1616
Lifetime::new(&format!("'__yeast_field_{n}"), Span::call_site())
1717
}
1818

19+
#[derive(Clone)]
20+
struct CaptureBinding {
21+
name: Ident,
22+
raw: bool,
23+
cardinality: Cardinality,
24+
}
25+
26+
impl Rule {
27+
pub(crate) fn lower(&self) -> TokenStream {
28+
let query = self.pattern.lower();
29+
let captures = self.pattern.captures();
30+
let (raw_captures, translated_captures): (Vec<_>, Vec<_>) =
31+
captures.iter().partition(|capture| capture.raw);
32+
let raw_capture_names = raw_captures
33+
.iter()
34+
.map(|capture| capture.name.to_string())
35+
.collect::<Vec<_>>();
36+
let raw_bindings = capture_bindings(raw_captures.into_iter());
37+
let translated_bindings = capture_bindings(translated_captures.into_iter());
38+
let replacement = self.replacement.lower();
39+
let guard = self
40+
.guard
41+
.clone()
42+
.unwrap_or_else(|| syn::parse_quote!(true));
43+
let ctx = Ident::new("ctx", Span::call_site());
44+
45+
quote! {
46+
{
47+
let __query = #query;
48+
yeast::Rule::guarded(
49+
__query,
50+
Box::new(|__ast: &yeast::Ast, __captures: &yeast::captures::Captures, __user_ctx: &mut _| {
51+
#(#raw_bindings)*
52+
#(#translated_bindings)*
53+
let ast = __ast;
54+
let #ctx = __user_ctx;
55+
Ok(#guard)
56+
}),
57+
Box::new(|__ast: &mut yeast::Ast, mut __captures: yeast::captures::Captures, __source_range: Option<yeast::Range>, __user_ctx: &mut _, __translator: yeast::TranslatorHandle<'_, _>| {
58+
let __skip: &[&str] = &[#(#raw_capture_names),*];
59+
__translator.auto_translate_captures(&mut __captures, __ast, __user_ctx, __skip)?;
60+
#(#raw_bindings)*
61+
#(#translated_bindings)*
62+
let mut #ctx = yeast::build::BuildCtx::with_translator(__ast, &__captures, __source_range, __user_ctx, __translator);
63+
let __result: Vec<yeast::Id> = { #replacement };
64+
let __result = #ctx.finish_rule(__result);
65+
Ok(__result)
66+
}),
67+
)
68+
}
69+
}
70+
}
71+
}
72+
73+
impl Replacement {
74+
fn lower(&self) -> TokenStream {
75+
match self {
76+
Replacement::Templates(templates) => Template::lower_list(templates),
77+
Replacement::Rust { body, .. } => {
78+
let body = rust_block(body);
79+
quote! {
80+
let __value = #body;
81+
let mut __ids: Vec<yeast::Id> = Vec::new();
82+
yeast::IntoFieldIds::extend_into(__value, &mut __ids);
83+
__ids
84+
}
85+
}
86+
}
87+
}
88+
}
89+
90+
impl Pattern {
91+
pub(crate) fn lower(&self) -> TokenStream {
92+
match self {
93+
Pattern::Any { match_unnamed } => {
94+
quote! { yeast::query::QueryNode::Any { match_unnamed: #match_unnamed } }
95+
}
96+
Pattern::Node { kind, fields } => {
97+
let fields = fields.iter().map(|(name, pattern)| {
98+
let elements = pattern.lower_list();
99+
quote! { (#name, vec![#(#elements),*]) }
100+
});
101+
quote! {
102+
yeast::query::QueryNode::Node {
103+
kind: #kind,
104+
children: vec![#(#fields),*],
105+
}
106+
}
107+
}
108+
Pattern::Unnamed(kind) => {
109+
quote! { yeast::query::QueryNode::UnnamedNode { kind: #kind } }
110+
}
111+
Pattern::Capture {
112+
name,
113+
pattern,
114+
cardinality,
115+
..
116+
} => {
117+
debug_assert_eq!(*cardinality, Cardinality::SINGLE);
118+
let name = name.to_string();
119+
let pattern = pattern.lower();
120+
quote! {
121+
yeast::query::QueryNode::Capture {
122+
capture: #name,
123+
node: Box::new(#pattern),
124+
}
125+
}
126+
}
127+
Pattern::Sequence(_) | Pattern::Repeated { .. } => {
128+
unreachable!("sequence and repeated patterns cannot match a single node")
129+
}
130+
}
131+
}
132+
133+
fn lower_list(&self) -> Vec<TokenStream> {
134+
match self {
135+
Pattern::Sequence(patterns) => patterns.iter().flat_map(Pattern::lower_list).collect(),
136+
Pattern::Capture {
137+
name,
138+
pattern,
139+
cardinality,
140+
..
141+
} => {
142+
let capture = name.to_string();
143+
let pattern = pattern.lower();
144+
let captured = quote! {
145+
yeast::query::QueryListElem::SingleNode(
146+
yeast::query::QueryNode::Capture {
147+
capture: #capture,
148+
node: Box::new(#pattern),
149+
}
150+
)
151+
};
152+
if *cardinality == Cardinality::SINGLE {
153+
vec![captured]
154+
} else {
155+
let repetition = match (cardinality.multiple, cardinality.required) {
156+
(true, false) => quote! { yeast::query::Rep::ZeroOrMore },
157+
(true, true) => quote! { yeast::query::Rep::OneOrMore },
158+
(false, false) => quote! { yeast::query::Rep::ZeroOrOne },
159+
(false, true) => unreachable!("single cardinality handled above"),
160+
};
161+
vec![quote! {
162+
yeast::query::QueryListElem::Repeated {
163+
children: vec![#captured],
164+
rep: #repetition,
165+
}
166+
}]
167+
}
168+
}
169+
Pattern::Repeated {
170+
pattern,
171+
cardinality,
172+
} => {
173+
let children = pattern.lower_list();
174+
let repetition = match (cardinality.multiple, cardinality.required) {
175+
(true, false) => quote! { yeast::query::Rep::ZeroOrMore },
176+
(true, true) => quote! { yeast::query::Rep::OneOrMore },
177+
(false, false) => quote! { yeast::query::Rep::ZeroOrOne },
178+
(false, true) => unreachable!("single patterns are not wrapped as repeated"),
179+
};
180+
vec![quote! {
181+
yeast::query::QueryListElem::Repeated {
182+
children: vec![#(#children),*],
183+
rep: #repetition,
184+
}
185+
}]
186+
}
187+
pattern => {
188+
let pattern = pattern.lower();
189+
vec![quote! { yeast::query::QueryListElem::SingleNode(#pattern) }]
190+
}
191+
}
192+
}
193+
fn captures(&self) -> Vec<CaptureBinding> {
194+
let mut captures = Vec::new();
195+
self.collect_captures(Cardinality::SINGLE, &mut captures);
196+
captures
197+
}
198+
199+
fn collect_captures(&self, cardinality: Cardinality, captures: &mut Vec<CaptureBinding>) {
200+
match self {
201+
Pattern::Any { .. } | Pattern::Unnamed(_) => {}
202+
Pattern::Node { fields, .. } => {
203+
for (_, pattern) in fields {
204+
pattern.collect_captures(cardinality, captures);
205+
}
206+
}
207+
Pattern::Capture {
208+
name,
209+
raw,
210+
pattern,
211+
cardinality: captured,
212+
} => {
213+
let cardinality = combine_cardinality(cardinality, *captured);
214+
pattern.collect_captures(cardinality, captures);
215+
captures.push(CaptureBinding {
216+
name: name.clone(),
217+
raw: *raw,
218+
cardinality,
219+
});
220+
}
221+
Pattern::Sequence(patterns) => {
222+
for pattern in patterns {
223+
pattern.collect_captures(cardinality, captures);
224+
}
225+
}
226+
Pattern::Repeated {
227+
pattern,
228+
cardinality: repeated,
229+
} => {
230+
pattern.collect_captures(combine_cardinality(cardinality, *repeated), captures);
231+
}
232+
}
233+
}
234+
}
235+
236+
fn combine_cardinality(parent: Cardinality, child: Cardinality) -> Cardinality {
237+
Cardinality {
238+
multiple: parent.multiple || child.multiple,
239+
required: parent.required && child.required,
240+
}
241+
}
242+
243+
fn capture_bindings<'a>(captures: impl Iterator<Item = &'a CaptureBinding>) -> Vec<TokenStream> {
244+
captures
245+
.map(|capture| {
246+
let name_string = capture.name.to_string();
247+
let name = Ident::new(&name_string, Span::call_site());
248+
match (capture.cardinality.multiple, capture.cardinality.required) {
249+
(true, _) => {
250+
quote! {
251+
let #name: Vec<yeast::Id> = __captures.get_all(#name_string);
252+
}
253+
}
254+
(false, false) => {
255+
quote! {
256+
let #name: Option<yeast::Id> = __captures.get_opt(#name_string);
257+
}
258+
}
259+
(false, true) => {
260+
quote! {
261+
let #name: yeast::Id = __captures.get_var(#name_string).unwrap();
262+
}
263+
}
264+
}
265+
})
266+
.collect()
267+
}
268+
19269
impl Template {
20270
pub(crate) fn lower_root(&self) -> TokenStream {
21271
self.lower(&LoweringContext {

0 commit comments

Comments
 (0)