Skip to content

Commit 2853b34

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 2853b34

6 files changed

Lines changed: 1206 additions & 1236 deletions

File tree

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

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,94 @@
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: Capture,
73+
pattern: Box<Pattern>,
74+
},
75+
/// An ordered sequence of patterns within a field.
76+
Sequence(Vec<Pattern>),
77+
/// A repeated or optional pattern.
78+
Repeated {
79+
pattern: Box<Pattern>,
80+
cardinality: Cardinality,
81+
},
82+
}
83+
84+
#[derive(Clone)]
85+
pub(crate) struct Capture {
86+
/// Capture identifier after `@` or `@@`.
87+
pub(crate) name: Ident,
88+
/// Whether `@@` keeps the captured input node untranslated.
89+
pub(crate) raw: bool,
90+
}
91+
392
pub(crate) enum Template {
493
/// A parenthesized node template such as `(call method: {name})`.
594
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: 206 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ 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::{
7+
Capture, Cardinality, Content, Field, Node, Pattern, Replacement, Rule, Template,
8+
};
79

810
struct LoweringContext<'a> {
911
/// Enclosing optional-field label to exit when a computed value is absent.
@@ -16,6 +18,209 @@ fn fresh_fallible_label() -> Lifetime {
1618
Lifetime::new(&format!("'__yeast_field_{n}"), Span::call_site())
1719
}
1820

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

0 commit comments

Comments
 (0)