ソースを参照

feat: simple iterators and conditionals in rsx (#564)

* feat: simple iterators

* fix: into_iter

* feat: add support for unterminated conditionasl

* fix: add tempalte mapping for helpers
Jon Kelley 2 年 前
コミット
8ea61e1b3e
3 ファイル変更153 行追加2 行削除
  1. 12 0
      examples/simple_list.rs
  2. 125 2
      packages/rsx/src/node.rs
  3. 16 0
      packages/rsx/src/template.rs

+ 12 - 0
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::<Vec<_>>(),
+
         // Use optionals
         Some(rsx! { "Some" }),
+
+        div {
+            for name in 0..10 {
+                rsx! { "{name}" }
+            }
+
+            if true {
+                rsx!{ "hello world!" }
+            }
+        }
     ))
 }

+ 125 - 2
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::<InputProps<'_, i32> {}
             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::<Token![for]>()?;
+            let pat = stream.parse::<Pat>()?;
+            let _i = stream.parse::<Token![in]>()?;
+            let expr = stream.parse::<Box<Expr>>()?;
+            let body = stream.parse::<Block>()?;
+
+            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::<Expr>()?))
     }
 }
@@ -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<Expr>,
+    pub body: Block,
+}
+
+fn is_if_chain_terminated(chain: &ExprIf) -> bool {
+    let mut current = chain;
+    loop {
+        if let Some((_, else_block)) = &current.else_branch {
+            if let Expr::If(else_if) = else_block.as_ref() {
+                current = else_if;
+            } else {
+                return true;
+            }
+        } else {
+            return false;
         }
     }
 }

+ 16 - 0
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
     }