use syn::braced; use syn::parse::discouraged::Speculative; use syn::{ Expr, Ident, LitStr, Pat, Path, Result, Token, parse::{Parse, ParseStream}, token::Brace, }; pub struct RsxRoot { pub nodes: Vec, } impl Parse for RsxRoot { fn parse(input: ParseStream) -> Result { let mut nodes = Vec::new(); while !input.is_empty() { nodes.push(input.parse()?); } Ok(Self { nodes }) } } /// A single prop: `ident: expr`. pub struct RsxProp { pub name: Ident, pub value: Expr, } pub enum RsxNode { Text(LitStr), Expr(Expr), Component { path: Path, props: Vec, children: Vec, }, Mount(Ident), If { deps: Vec, cond: Expr, children: Vec, }, For { deps: Vec, pat: Pat, expr: Expr, children: Vec, }, } pub struct Dep { pub ident: Ident, pub pat: Option, } fn parse_deps(input: ParseStream) -> Result> { let mut deps = Vec::new(); if input.peek(Token![%]) { input.parse::()?; loop { let ident: Ident = input.parse()?; let pat = if input.peek(Token![as]) { input.parse::()?; // Patterns in syn 2.0 are parsed via `Pat::parse_multi` // instead of implementing the `Parse` trait directly. Some(Pat::parse_multi(input)?) } else { None }; deps.push(Dep { ident, pat }); if !input.peek(Token![,]) { break; } input.parse::()?; } } Ok(deps) } impl Parse for RsxNode { fn parse(input: ParseStream) -> Result { // @{ $expr } if input.peek(Token![@]) { input.parse::()?; let content; braced!(content in input); let expr: Expr = content.parse()?; return Ok(RsxNode::Expr(expr)); } // !$ident if input.peek(Token![!]) { input.parse::()?; let mount: Ident = input.parse()?; return Ok(RsxNode::Mount(mount)); } let deps = parse_deps(input)?; // %$dep if $expr { $rsx } if input.peek(Token![if]) { input.parse::()?; let cond: Expr = input.parse()?; let content; braced!(content in input); let children = parse_children(&content)?; return Ok(RsxNode::If { deps, cond, children, }); } // %$dep for $pat in $expr { $rsx } if input.peek(Token![for]) { input.parse::()?; let pat: Pat = Pat::parse_multi(input)?; input.parse::()?; let expr: Expr = input.parse()?; let content; braced!(content in input); let children = parse_children(&content)?; return Ok(RsxNode::For { deps, pat, expr, children, }); } // "%$dep for $pat in $expr { $rsx }" if input.peek(LitStr) { return Ok(RsxNode::Text(input.parse()?)); } parse_component_invocation(input) } } /// Parses Path or Path { ... } into RsxNode::Component. fn parse_component_invocation(input: ParseStream) -> Result { let path: Path = input.parse()?; if !input.peek(Brace) { return Ok(RsxNode::Component { path, props: Vec::new(), children: Vec::new(), }); } let content; braced!(content in input); let (props, children) = parse_props_and_children(&content)?; Ok(RsxNode::Component { path, props, children, }) } fn parse_props_and_children(input: ParseStream) -> Result<(Vec, Vec)> { Ok((parse_props(input)?, RsxRoot::parse(input)?.nodes)) } fn parse_props(input: ParseStream) -> Result> { let mut props = Vec::new(); while !input.is_empty() { let fork = input.fork(); let path: Path = match fork.parse() { Ok(p) => p, Err(_) => break, }; if fork.peek(Token![:]) { let name = path .get_ident() .cloned() .ok_or_else(|| fork.error("prop name must be a single identifier"))?; fork.parse::()?; let value: Expr = fork.parse()?; input.advance_to(&fork); props.push(RsxProp { name, value }); if input.peek(Token![,]) { input.parse::()?; } } else { // ❌ do not commit → nothing consumed break; } } Ok(props) } fn parse_children(input: ParseStream) -> Result> { let mut nodes = Vec::new(); while !input.is_empty() { nodes.push(input.parse()?); } Ok(nodes) }