Yeast: Unify tree! and trees! into a single tree! macro

tree! now returns Id for a single element or Vec<Id> for multiple,
determined by how many top-level elements appear in the template.
trees! is kept as an alias for backward compatibility.

- tree!(ctx, (single_node ...))        → Id
- tree!(ctx, (node1 ...) (node2 ...))  → Vec<Id>
- tree!(ctx, (node) {..splice})        → Vec<Id>

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Taus
2026-04-30 21:38:04 +00:00
parent e54d84c29f
commit b0e2e5eb0d
2 changed files with 24 additions and 18 deletions

View File

@@ -404,29 +404,30 @@ fn parse_builder_child_list(tokens: &mut Tokens) -> Result<Vec<TokenStream>> {
// tree! / trees! parsing — direct code generation against BuildCtx
// ---------------------------------------------------------------------------
/// Parse `tree!(ctx, (template))` — first arg is context ident, then a comma,
/// then a single node template.
/// Parse `tree!(ctx, template)` — unified macro that returns `Id` for a single
/// top-level element or `Vec<Id>` for multiple elements.
pub fn parse_tree_top(input: TokenStream) -> Result<TokenStream> {
let mut tokens = input.into_iter().peekable();
let ctx = expect_ident(&mut tokens, "expected build context identifier")?;
expect_punct(&mut tokens, ',', "expected `,` after context")?;
let body = parse_direct_node(&mut tokens, &ctx)?;
// Parse the first element
let first = parse_direct_node(&mut tokens, &ctx)?;
// If nothing follows, return a single Id
if tokens.peek().is_none() {
return Ok(quote! { { #first } });
}
// Multiple elements — collect into Vec<Id>
let mut items = vec![quote! { __nodes.push(#first); }];
let rest = parse_direct_list(&mut tokens, &ctx)?;
items.extend(rest);
if let Some(tok) = tokens.next() {
return Err(syn::Error::new_spanned(tok, "unexpected token after tree! template"));
}
Ok(quote! { { #body } })
}
/// Parse `trees!(ctx, (node1) (node2) {expr} ...)` — context ident, comma,
/// then a list of node templates / embedded expressions.
pub fn parse_trees_top(input: TokenStream) -> Result<TokenStream> {
let mut tokens = input.into_iter().peekable();
let ctx = expect_ident(&mut tokens, "expected build context identifier")?;
expect_punct(&mut tokens, ',', "expected `,` after context")?;
let items = parse_direct_list(&mut tokens, &ctx)?;
if let Some(tok) = tokens.next() {
return Err(syn::Error::new_spanned(tok, "unexpected token after trees! template"));
}
Ok(quote! {
{
let mut __nodes: Vec<usize> = Vec::new();
@@ -436,6 +437,11 @@ pub fn parse_trees_top(input: TokenStream) -> Result<TokenStream> {
})
}
/// Kept for backward compatibility — identical to `parse_tree_top`.
pub fn parse_trees_top(input: TokenStream) -> Result<TokenStream> {
parse_tree_top(input)
}
/// Parse a single node template and generate code that returns an `Id`.
/// Handles: `(kind fields... children...)`, `@capture`, `{expr}`.
fn parse_direct_node(tokens: &mut Tokens, ctx: &Ident) -> Result<TokenStream> {

View File

@@ -13,7 +13,7 @@ pub fn rules() -> Vec<Rule> {
let left_ids = match_.get_all("left");
let mut ctx = BuildCtx::new(ast, &match_);
yeast::trees!(ctx,
yeast::tree!(ctx,
(assignment
left: (identifier $tmp)
right: @right
@@ -43,7 +43,7 @@ pub fn rules() -> Vec<Rule> {
);
let for_transform = |ast: &mut Ast, match_: Captures| {
let mut ctx = BuildCtx::new(ast, &match_);
yeast::trees!(ctx,
vec![yeast::tree!(ctx,
(call
receiver: @val
method: (identifier "each")
@@ -60,7 +60,7 @@ pub fn rules() -> Vec<Rule> {
)
)
)
)
)]
};
let for_rule = Rule::new(for_query, Box::new(for_transform));