Skip to content

Commit 35c84d2

Browse files
committed
yeast: Remove explicit ctx from tree builders
We never used the ability to specify a different context from `ctx` in these builders anyway, so this commit makes it so that we always use the ambient `ctx` that is provided inside of rule bodies. Note that if we end up needing a particular context anyway, then we can simply reassign `ctx` before calling the tree macros. This has the same effect as passing in a custom context would have now.
1 parent d92e57c commit 35c84d2

6 files changed

Lines changed: 125 additions & 96 deletions

File tree

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

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,10 @@ pub fn query(input: TokenStream) -> TokenStream {
7575
/// error, so the choice between "unset the field" and "unwrap it" stays
7676
/// explicit.
7777
///
78-
/// Can be called with an explicit context or using the implicit context
79-
/// from an enclosing `rule!`:
78+
/// Uses the `BuildCtx` binding named `ctx` from the surrounding scope:
8079
///
8180
/// ```text
82-
/// tree!(ctx, (kind ...)) // explicit BuildCtx
83-
/// tree!((kind ...)) // implicit context from rule!
81+
/// tree!((kind ...))
8482
/// ```
8583
#[proc_macro]
8684
pub fn tree(input: TokenStream) -> TokenStream {
@@ -96,12 +94,10 @@ pub fn tree(input: TokenStream) -> TokenStream {
9694
/// Like `tree!` but returns `Vec<Id>` and supports multiple top-level
9795
/// elements. All syntax from `tree!` is available.
9896
///
99-
/// Can be called with an explicit context or using the implicit context
100-
/// from an enclosing `rule!`:
97+
/// Uses the `BuildCtx` binding named `ctx` from the surrounding scope:
10198
///
10299
/// ```text
103-
/// trees!(ctx, (node1 ...) (node2 ...)) // explicit BuildCtx
104-
/// trees!((node1 ...) (node2 ...)) // implicit context from rule!
100+
/// trees!((node1 ...) (node2 ...))
105101
/// ```
106102
#[proc_macro]
107103
pub fn trees(input: TokenStream) -> TokenStream {
@@ -112,6 +108,38 @@ pub fn trees(input: TokenStream) -> TokenStream {
112108
}
113109
}
114110

111+
/// Build one AST node whose root uses another node's source range.
112+
///
113+
/// Uses the `BuildCtx` binding named `ctx` from the surrounding scope:
114+
///
115+
/// ```text
116+
/// tree_at!(source, (kind ...))
117+
/// ```
118+
#[proc_macro]
119+
pub fn tree_at(input: TokenStream) -> TokenStream {
120+
let input2: TokenStream2 = input.into();
121+
match parse::parse_tree_at_top(input2) {
122+
Ok(output) => output.into(),
123+
Err(err) => err.to_compile_error().into(),
124+
}
125+
}
126+
127+
/// Build one AST node whose root spans a collection of nodes.
128+
///
129+
/// Uses the `BuildCtx` binding named `ctx` from the surrounding scope:
130+
///
131+
/// ```text
132+
/// tree_spanning!(sources, (kind ...))
133+
/// ```
134+
#[proc_macro]
135+
pub fn tree_spanning(input: TokenStream) -> TokenStream {
136+
let input2: TokenStream2 = input.into();
137+
match parse::parse_tree_spanning_top(input2) {
138+
Ok(output) => output.into(),
139+
Err(err) => err.to_compile_error().into(),
140+
}
141+
}
142+
115143
/// Define a desugaring rule with query and transform in one declaration.
116144
///
117145
/// ```text
@@ -146,7 +174,7 @@ pub fn trees(input: TokenStream) -> TokenStream {
146174
/// Mutations to `ctx` are visible to the transform when the guard succeeds.
147175
/// Omitting the guard is equivalent to writing `where true`.
148176
///
149-
/// `tree!` and `trees!` can be used without explicit context inside `{...}`.
177+
/// `tree!` and `trees!` use the rule transform's `ctx` binding inside `{...}`.
150178
#[proc_macro]
151179
pub fn rule(input: TokenStream) -> TokenStream {
152180
let input2: TokenStream2 = input.into();

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

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -332,28 +332,10 @@ fn parse_query_list(tokens: &mut Tokens) -> Result<Vec<TokenStream>> {
332332

333333
const IMPLICIT_CTX: &str = "ctx";
334334

335-
/// Determine the context identifier: either explicit `ctx,` or the implicit
336-
/// `ctx` from an enclosing `rule!`.
337-
fn parse_ctx_or_implicit(tokens: &mut Tokens) -> Ident {
338-
// Check if first token is an ident followed by a comma
339-
let mut lookahead = tokens.clone();
340-
let is_explicit = matches!(lookahead.next(), Some(TokenTree::Ident(_)))
341-
&& matches!(lookahead.next(), Some(TokenTree::Punct(p)) if p.as_char() == ',');
342-
343-
if is_explicit {
344-
let ctx = expect_ident(tokens, "unreachable: ident was just peeked")
345-
.expect("unreachable: ident was just peeked");
346-
let _ = tokens.next(); // consume comma
347-
ctx
348-
} else {
349-
Ident::new(IMPLICIT_CTX, Span::call_site())
350-
}
351-
}
352-
353-
/// Parse `tree!(ctx, (template))` or `tree!((template))` — returns single `Id`.
335+
/// Parse `tree!((template))` — returns single `Id`.
354336
pub fn parse_tree_top(input: TokenStream) -> Result<TokenStream> {
355337
let mut tokens = input.into_iter().peekable();
356-
let ctx = parse_ctx_or_implicit(&mut tokens);
338+
let ctx = Ident::new(IMPLICIT_CTX, Span::call_site());
357339

358340
let first = parse_direct_node(&mut tokens, &ctx, None)?;
359341

@@ -368,10 +350,10 @@ pub fn parse_tree_top(input: TokenStream) -> Result<TokenStream> {
368350
Ok(quote! { { #first } })
369351
}
370352

371-
/// Parse `trees!(ctx, ...)` or `trees!(...)` — returns `Vec<Id>`.
353+
/// Parse `trees!(...)` — returns `Vec<Id>`.
372354
pub fn parse_trees_top(input: TokenStream) -> Result<TokenStream> {
373355
let mut tokens = input.into_iter().peekable();
374-
let ctx = parse_ctx_or_implicit(&mut tokens);
356+
let ctx = Ident::new(IMPLICIT_CTX, Span::call_site());
375357
let items = parse_direct_list(&mut tokens, &ctx)?;
376358
if let Some(tok) = tokens.next() {
377359
return Err(syn::Error::new_spanned(
@@ -388,6 +370,70 @@ pub fn parse_trees_top(input: TokenStream) -> Result<TokenStream> {
388370
})
389371
}
390372

373+
pub fn parse_tree_at_top(input: TokenStream) -> Result<TokenStream> {
374+
let mut tokens = input.into_iter().peekable();
375+
let source = parse_argument(&mut tokens, "expected `,` after source node")?;
376+
let ctx = Ident::new(IMPLICIT_CTX, Span::call_site());
377+
let node = parse_direct_node(&mut tokens, &ctx, None)?;
378+
if let Some(tok) = tokens.next() {
379+
return Err(syn::Error::new_spanned(
380+
tok,
381+
"unexpected token after tree_at! template",
382+
));
383+
}
384+
385+
Ok(quote! {
386+
{
387+
let __yeast_source: yeast::Id = { #source };
388+
let __yeast_source_range = #ctx
389+
.ast
390+
.get_node(__yeast_source)
391+
.and_then(|node| node.source_range());
392+
let __yeast_node: yeast::Id = #node;
393+
#ctx.set_node_source_range(__yeast_node, __yeast_source_range)
394+
}
395+
})
396+
}
397+
398+
pub fn parse_tree_spanning_top(input: TokenStream) -> Result<TokenStream> {
399+
let mut tokens = input.into_iter().peekable();
400+
let sources = parse_argument(&mut tokens, "expected `,` after source nodes")?;
401+
let ctx = Ident::new(IMPLICIT_CTX, Span::call_site());
402+
let node = parse_direct_node(&mut tokens, &ctx, None)?;
403+
if let Some(tok) = tokens.next() {
404+
return Err(syn::Error::new_spanned(
405+
tok,
406+
"unexpected token after tree_spanning! template",
407+
));
408+
}
409+
410+
Ok(quote! {
411+
{
412+
let __yeast_source_range = ::std::iter::IntoIterator::into_iter({ #sources })
413+
.filter_map(|source: yeast::Id| {
414+
#ctx.ast.get_node(source).and_then(|node| node.source_range())
415+
})
416+
.reduce(yeast::Range::union);
417+
let __yeast_node: yeast::Id = #node;
418+
#ctx.set_node_source_range(__yeast_node, __yeast_source_range)
419+
}
420+
})
421+
}
422+
423+
fn parse_argument(tokens: &mut Tokens, missing_comma: &str) -> Result<TokenStream> {
424+
let mut argument = TokenStream::new();
425+
while let Some(token) = tokens.next() {
426+
if matches!(&token, TokenTree::Punct(p) if p.as_char() == ',') {
427+
if argument.is_empty() {
428+
return Err(syn::Error::new_spanned(token, "expected expression"));
429+
}
430+
return Ok(argument);
431+
}
432+
argument.extend([token]);
433+
}
434+
Err(syn::Error::new(Span::call_site(), missing_comma))
435+
}
436+
391437
/// Parse a single node template and generate code that returns an `Id`.
392438
/// Handles: `(kind fields... children...)` and `{expr}`.
393439
///

‎shared/yeast/doc/yeast.md‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,22 +160,21 @@ Templates construct new AST nodes using the `tree!` and `trees!` macros.
160160
All children in a template must be in named fields — output AST nodes are
161161
always fully fielded.
162162

163-
When used inside a `rule!` macro, the context is implicit — no explicit
164-
`BuildCtx` argument is needed. When used standalone, they take a `BuildCtx`
165-
as the first argument:
163+
The macros use a `BuildCtx` binding named `ctx` from the surrounding scope.
164+
`rule!` provides this binding automatically; standalone uses must create it:
166165

167166
```rust
168-
// Inside rule! — implicit context, captures are Rust variables
167+
// Inside rule! — ctx is provided automatically, captures are Rust variables
169168
yeast::rule!(
170169
(assignment left: (_) @left right: (_) @right)
171170
=>
172171
(assignment left: {right} right: {left})
173172
);
174173

175-
// Standalone — explicit context
174+
// Standalone — create a binding named ctx
176175
let mut user_ctx = ();
177176
let mut ctx = BuildCtx::new(ast, &captures, &mut user_ctx);
178-
let id = yeast::tree!(ctx,
177+
let id = yeast::tree!(
179178
(assignment
180179
left: {ctx.capture("lhs")}
181180
right: {ctx.capture("rhs")}
@@ -188,7 +187,7 @@ let id = yeast::tree!(ctx,
188187
`tree!(...)` returns a single node `Id`:
189188

190189
```rust
191-
yeast::tree!(ctx,
190+
yeast::tree!(
192191
(assignment
193192
left: {ctx.capture("lhs")}
194193
right: {ctx.capture("rhs")}
@@ -201,7 +200,7 @@ yeast::tree!(ctx,
201200
`trees!(...)` returns `Vec<Id>`:
202201

203202
```rust
204-
yeast::trees!(ctx,
203+
yeast::trees!(
205204
(assignment left: {tmp} right: {right})
206205
{body}
207206
)
@@ -262,7 +261,6 @@ rule!(
262261
=>
263262
synthetic_node {
264263
tree_at!(
265-
ctx,
266264
source_node,
267265
(synthetic_node child: (nested value: {child}))
268266
)
@@ -282,7 +280,6 @@ rule!(
282280
=>
283281
synthetic_node {
284282
tree_spanning!(
285-
ctx,
286283
[first, second],
287284
(synthetic_node child: {child})
288285
)
@@ -382,7 +379,7 @@ options uniformly:
382379
right: {rhs} // a captured value (inside rule!)
383380
)
384381

385-
yeast::trees!(ctx,
382+
yeast::trees!(
386383
(assignment left: {tmp} right: {right})
387384
{extra_nodes} // splices a Vec<Id>
388385
)

‎shared/yeast/src/lib.rs‎

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,7 @@ pub mod schema;
1515
mod visitor;
1616

1717
pub use range::{Point, Range};
18-
pub use yeast_macros::{query, rule, rules, tree, trees};
19-
20-
/// Build a single AST node whose root uses another node's source range.
21-
///
22-
/// Nested nodes in the template are built normally and derive their locations
23-
/// from their own children.
24-
#[macro_export]
25-
macro_rules! tree_at {
26-
($ctx:ident, $source:expr, ($($tree:tt)*)) => {{
27-
let __yeast_source: $crate::Id = $source;
28-
let __yeast_source_range = $ctx
29-
.ast
30-
.get_node(__yeast_source)
31-
.and_then(|node| node.source_range());
32-
let __yeast_node: $crate::Id = $crate::tree!($ctx, ($($tree)*));
33-
$ctx.set_node_source_range(__yeast_node, __yeast_source_range)
34-
}};
35-
}
36-
37-
/// Build a single AST node whose root spans a collection of nodes.
38-
///
39-
/// Nested nodes in the template are built normally and derive their locations
40-
/// from their own children.
41-
#[macro_export]
42-
macro_rules! tree_spanning {
43-
($ctx:ident, $sources:expr, ($($tree:tt)*)) => {{
44-
let __yeast_source_range = ::std::iter::IntoIterator::into_iter($sources)
45-
.filter_map(|source: $crate::Id| {
46-
$ctx.ast.get_node(source).and_then(|node| node.source_range())
47-
})
48-
.reduce($crate::Range::union);
49-
let __yeast_node: $crate::Id = $crate::tree!($ctx, ($($tree)*));
50-
$ctx.set_node_source_range(__yeast_node, __yeast_source_range)
51-
}};
52-
}
18+
pub use yeast_macros::{query, rule, rules, tree, tree_at, tree_spanning, trees};
5319

5420
use captures::Captures;
5521
use query::QueryNode;

‎shared/yeast/tests/test.rs‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ fn test_tree_builder() {
695695
// Swap left and right
696696
let mut user_ctx = ();
697697
let mut ctx = yeast::build::BuildCtx::new(&mut ast, &captures, &mut user_ctx);
698-
let new_id = yeast::tree!(ctx,
698+
let new_id = yeast::tree!(
699699
(program
700700
child: (assignment
701701
left: {ctx.capture("right")}
@@ -723,8 +723,8 @@ fn build_optional_right(ast: &mut Ast, value: Option<yeast::Id>) -> (yeast::Id,
723723
let captures = yeast::captures::Captures::new();
724724
let mut user_ctx = ();
725725
let mut ctx = yeast::build::BuildCtx::new(ast, &captures, &mut user_ctx);
726-
let left = yeast::tree!(ctx, (identifier "x"));
727-
let root = yeast::tree!(ctx,
726+
let left = yeast::tree!((identifier "x"));
727+
let root = yeast::tree!(
728728
(assignment
729729
left: {left}
730730
right: (integer #{value})?
@@ -781,8 +781,8 @@ fn test_optional_field_propagates_through_nested_nodes() {
781781
// The absent value sits two levels below the `?`, so the whole
782782
// `left_assignment_list` subtree is abandoned along with it.
783783
let absent: Option<yeast::Id> = None;
784-
let right = yeast::tree!(ctx, (integer "1"));
785-
let root = yeast::tree!(ctx,
784+
let right = yeast::tree!((integer "1"));
785+
let root = yeast::tree!(
786786
(assignment
787787
left: (left_assignment_list child: (identifier #{absent}))?
788788
right: {right}
@@ -809,7 +809,7 @@ fn test_innermost_optional_field_catches_first() {
809809

810810
// The inner `?` catches, so only `child` is dropped; `left` survives.
811811
let absent: Option<yeast::Id> = None;
812-
let root = yeast::tree!(ctx,
812+
let root = yeast::tree!(
813813
(assignment
814814
left: (left_assignment_list child: (identifier #{absent})?)?
815815
)
@@ -1782,7 +1782,7 @@ fn test_tree_at_assigns_capture_range_to_root_only() {
17821782
) @@source
17831783
=>
17841784
call {
1785-
let arguments = tree_at!(ctx, source, (argument_list argument: (integer "0")));
1785+
let arguments = tree_at!(source, (argument_list argument: (integer "0")));
17861786
tree!((call method: {name} receiver: {recv} arguments: {arguments}))
17871787
}
17881788
);
@@ -1817,7 +1817,6 @@ fn test_tree_spanning_assigns_union_to_root_only() {
18171817
=>
18181818
call {
18191819
let arguments = tree_spanning!(
1820-
ctx,
18211820
[recv, name],
18221821
(argument_list argument: (integer "0"))
18231822
);

0 commit comments

Comments
 (0)