diff --git a/examples/simple_list.rs b/examples/simple_list.rs index d615e2371..d4616c19b 100644 --- a/examples/simple_list.rs +++ b/examples/simple_list.rs @@ -8,12 +8,24 @@ fn app(cx: Scope) -> Element { cx.render(rsx!( // Use Map directly to lazily pull elements (0..10).map(|f| rsx! { "{f}" }), + // Collect into an intermediate collection if necessary ["a", "b", "c"] .into_iter() .map(|f| rsx! { "{f}" }) .collect::>(), + // Use optionals Some(rsx! { "Some" }), + + div { + for name in 0..10 { + rsx! { "{name}" } + } + + if true { + rsx!{ "hello world!" } + } + } )) } diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index 406a24ddc..5572030b6 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -5,7 +5,7 @@ use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned, - token, Expr, LitStr, Result, + token, Block, Expr, ExprIf, LitStr, Pat, Result, }; /* @@ -20,6 +20,8 @@ Parse pub enum BodyNode { Element(Element), Component(Component), + ForLoop(ForLoop), + IfChain(ExprIf), Text(IfmtInput), RawExpr(Expr), } @@ -35,6 +37,8 @@ impl BodyNode { BodyNode::Component(component) => component.name.span(), BodyNode::Text(text) => text.source.span(), BodyNode::RawExpr(exp) => exp.span(), + BodyNode::ForLoop(fl) => fl.for_token.span(), + BodyNode::IfChain(f) => f.if_token.span(), } } } @@ -83,11 +87,32 @@ impl Parse for BodyNode { // crate::Input:: {} if body_stream.peek(token::Brace) { Component::validate_component_path(&path)?; - return Ok(BodyNode::Component(stream.parse()?)); } } + // Transform for loops into into_iter calls + if stream.peek(Token![for]) { + let _f = stream.parse::()?; + let pat = stream.parse::()?; + let _i = stream.parse::()?; + let expr = stream.parse::>()?; + let body = stream.parse::()?; + + return Ok(BodyNode::ForLoop(ForLoop { + for_token: _f, + pat, + in_token: _i, + expr, + body, + })); + } + + // Transform unterminated if statements into terminated optional if statements + if stream.peek(Token![if]) { + return Ok(BodyNode::IfChain(stream.parse()?)); + } + Ok(BodyNode::RawExpr(stream.parse::()?)) } } @@ -103,6 +128,104 @@ impl ToTokens for BodyNode { BodyNode::RawExpr(exp) => tokens.append_all(quote! { __cx.fragment_from_iter(#exp) }), + BodyNode::ForLoop(exp) => { + let ForLoop { + pat, expr, body, .. + } = exp; + + tokens.append_all(quote! { + __cx.fragment_from_iter( + (#expr).into_iter().map(|#pat| { + #body + }) + ) + }) + } + BodyNode::IfChain(chain) => { + if is_if_chain_terminated(chain) { + tokens.append_all(quote! { + __cx.fragment_from_iter(#chain) + }); + } else { + let ExprIf { + cond, + then_branch, + else_branch, + .. + } = chain; + + let mut body = TokenStream2::new(); + + body.append_all(quote! { + if #cond { + Some(#then_branch) + } + }); + + let mut elif = else_branch; + + while let Some((_, ref branch)) = elif { + match branch.as_ref() { + Expr::If(ref eelif) => { + let ExprIf { + cond, + then_branch, + else_branch, + .. + } = eelif; + + body.append_all(quote! { + else if #cond { + Some(#then_branch) + } + }); + + elif = else_branch; + } + _ => { + body.append_all(quote! { + else { + #branch + } + }); + break; + } + } + } + + body.append_all(quote! { + else { None } + }); + + tokens.append_all(quote! { + __cx.fragment_from_iter(#body) + }); + } + } + } + } +} + +#[derive(PartialEq, Eq, Clone, Debug, Hash)] +pub struct ForLoop { + pub for_token: Token![for], + pub pat: Pat, + pub in_token: Token![in], + pub expr: Box, + pub body: Block, +} + +fn is_if_chain_terminated(chain: &ExprIf) -> bool { + let mut current = chain; + loop { + if let Some((_, else_block)) = ¤t.else_branch { + if let Expr::If(else_if) = else_block.as_ref() { + current = else_if; + } else { + return true; + } + } else { + return false; } } } diff --git a/packages/rsx/src/template.rs b/packages/rsx/src/template.rs index 693f29b05..8e1544105 100644 --- a/packages/rsx/src/template.rs +++ b/packages/rsx/src/template.rs @@ -557,6 +557,22 @@ impl TemplateBuilder { fully_static: false, }); } + BodyNode::ForLoop(expr) => { + self.nodes.push(TemplateNodeBuilder { + id, + node_type: TemplateNodeTypeBuilder::DynamicNode( + self.dynamic_context.add_node(BodyNode::ForLoop(expr)), + ), + }); + } + BodyNode::IfChain(expr) => { + self.nodes.push(TemplateNodeBuilder { + id, + node_type: TemplateNodeTypeBuilder::DynamicNode( + self.dynamic_context.add_node(BodyNode::IfChain(expr)), + ), + }); + } } id }