diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 768a52eb49d..7f06f0c2722 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -404,29 +404,30 @@ fn parse_builder_child_list(tokens: &mut Tokens) -> Result> { // 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` for multiple elements. pub fn parse_tree_top(input: TokenStream) -> Result { 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 + 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 { - 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 = Vec::new(); @@ -436,6 +437,11 @@ pub fn parse_trees_top(input: TokenStream) -> Result { }) } +/// Kept for backward compatibility — identical to `parse_tree_top`. +pub fn parse_trees_top(input: TokenStream) -> Result { + 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 { diff --git a/shared/yeast/src/rules.rs b/shared/yeast/src/rules.rs index f710c92a181..b74d8700475 100644 --- a/shared/yeast/src/rules.rs +++ b/shared/yeast/src/rules.rs @@ -13,7 +13,7 @@ pub fn rules() -> Vec { 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 { ); 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 { ) ) ) - ) + )] }; let for_rule = Rule::new(for_query, Box::new(for_transform));