summary refs log tree commit diff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/morgana_proc/Cargo.toml1
-rw-r--r--modules/morgana_proc/src/lib.rs141
-rw-r--r--modules/site_test/src/main.rs28
3 files changed, 139 insertions, 31 deletions
diff --git a/modules/morgana_proc/Cargo.toml b/modules/morgana_proc/Cargo.toml
index fb1e28a..5294c7d 100644
--- a/modules/morgana_proc/Cargo.toml
+++ b/modules/morgana_proc/Cargo.toml
@@ -7,3 +7,4 @@ proc-macro = true
 
 [dependencies]
 unsynn = "0.0.26"
+quote = "1.0.40"
diff --git a/modules/morgana_proc/src/lib.rs b/modules/morgana_proc/src/lib.rs
index f4932d6..2f0c81b 100644
--- a/modules/morgana_proc/src/lib.rs
+++ b/modules/morgana_proc/src/lib.rs
@@ -1,21 +1,142 @@
 extern crate proc_macro;
 
 use unsynn::*;
+use quote::quote;
+
+unsynn! {
+    struct MorxBlock(Vec<Either<MorxNode, MorxChild, Literal>>);
+    struct MorxNode(Cons<Ident, Either<
+        NodeSeparator,
+        MorxChildren,
+        MorxChild,
+        Cons<MorxAttrs, Either<MorxChildren, NodeSeparator>>,
+    >>);
+    struct NodeSeparator(Either<EndOfStream, Semicolon>);
+    struct MorxAttrs(Vec<MorxAttr>);
+    struct MorxAttr(Cons<Ident, Optional<Cons<PunctAny<'='>, MorxAttrValue>>>);
+    struct MorxAttrValue(Either<Literal, Ident, BraceGroup>);
+    struct MorxChildren(Cons<BraceGroupContaining<MorxBlock>, Optional<NodeSeparator>>);
+    struct MorxChild(Cons<PunctAny<'='>, Either<Literal, BraceGroup, EndOfStream>, Optional<NodeSeparator>>);
+}
 
 #[proc_macro]
 pub fn morx(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    println!("{:?}", input);
-    let ast: MorxBlock = unsynn::TokenStream::from(input).to_token_iter().parse_all().unwrap();
+    let tokens = unsynn::TokenStream::from(input);
 
-    LiteralString::from_str("test").to_token_stream().into()
+    let ast = tokens.to_token_iter().parse_all::<MorxBlock>().expect("syntax error");
+
+    generate_block(&ast).into()
 }
 
+fn generate_block(ast: &MorxBlock) -> unsynn::TokenStream {
+    let children: Vec<unsynn::TokenStream> = ast.0.iter().map(|child| {
+        match child {
+            Either::First(node) => generate_node(node),
+            Either::Second(child_expression) => generate_expr_node(&child_expression.0.second),
+            Either::Third(literal) => generate_literal(literal),
+            _ => unreachable!("Invalid morx block AST"),
+        }
+    }).collect();
 
-unsynn! {
-    struct MorxBlock(Any<Either<Literal, Box<MorxNode>>, Nothing>);
-    struct MorxNode(Ident, MorxChildren);
-    struct MorxAttr(Either<Ident, MorxComplexAttr>);
-    struct MorxComplexAttr(Ident, Assign, AttrValue);
-    struct AttrValue(Either<Ident, Literal, BracketGroup>);
-    struct MorxChildren(Either<EndOfStream, Semicolon, BracketGroupContaining<Box<MorxBlock>>>);
+    quote! {
+        vec! [
+            #(#children),*
+        ]
+    }.into()
+}
+
+fn generate_node(ast: &MorxNode) -> unsynn::TokenStream {
+    let elem_name = ast.0.first.to_string();
+    let attrs_or_props = match &ast.0.second {
+        Either::Fourth(cons) => {
+            let attrs = &cons.first;
+            attrs.0.iter().map(|attr| {
+                let attr_name = attr.0.first.clone();
+                let attr_value = attr.0.second.0.iter().next().map(|val_expr| {
+                    val_expr.value.second.0.clone()
+                });
+                (attr_name, attr_value)
+            }).collect()
+        },
+        _ => Vec::new(),
+    };
+
+    let elem_attrs = attrs_or_props.iter().map(|attr| {
+        let name = Literal::string(&attr.0.to_string()).into_token_stream();
+
+        let value = match &attr.1 {
+            Some(Either::First(literal)) => quote!{ format!("{}", #literal) },
+            Some(Either::Second(ident)) => quote!{ format!("{}", #ident) },
+            Some(Either::Third(bracegroup)) => bracegroup.0.stream(),
+            Some(Either::Fourth(_invalid)) => unreachable!("Invalid element attribute type"),
+            None => quote!{ String::new() },
+        };
+
+        quote! { (#name.to_string(), #value) }
+    });
+
+    let elem_props = attrs_or_props.iter().map(|attr| {
+        let name = &attr.0;
+        let value = match &attr.1 {
+            Some(Either::First(literal)) => quote!{ format!("{}", #literal) },
+            Some(Either::Second(ident)) => quote!{ #ident },
+            Some(Either::Third(bracegroup)) => bracegroup.0.stream(),
+            Some(Either::Fourth(_invalid)) => unreachable!("Invalid element attribute type"),
+            None => quote!{ true },
+        };
+
+        quote! { #name: #value }
+    });
+
+    let children = match &ast.0.second {
+        Either::Second(children) => generate_block(&children.0.first.content),
+        Either::Third(single_child) => {
+            let node = generate_expr_node(&single_child.0.second);
+            quote!{ vec![ #node ] }
+        },
+        Either::Fourth(Cons {first: _, second: Either::First(children), third: _, fourth: _}) => generate_block(&children.0.first.content),
+        _ => quote!{ vec![] }
+    };
+
+    let is_component_node = elem_name.chars().next().map_or(false, |char| char.is_uppercase());
+
+    if is_component_node {
+        quote! {
+            RenderNode::Component(
+                Box::new(#elem_name {
+                    #(#elem_props),*
+                    children: #children
+                })
+            )
+        }.into()
+    } else {
+        let elem_name = Literal::string(&elem_name).into_token_stream();
+        quote! {
+            RenderNode::Element {
+                name: #elem_name.to_string(),
+                children: #children,
+                attributes: std::collections::HashMap::from([#(#elem_attrs),*])
+            }
+        }.into()
+    }
+}
+
+// This is the ={something} expression as a child of a node
+fn generate_expr_node(ast: &Either<Literal, BraceGroup, EndOfStream>) -> unsynn::TokenStream {
+    match ast {
+        Either::First(literal) => generate_literal(&literal),
+        Either::Second(brace_expr) => {
+            let children = brace_expr.0.stream();
+            quote! {
+                RenderNode::Fragment { children: #children }
+            }
+        },
+        _ => unreachable!("Invalid expr node"),
+    }
+}
+
+fn generate_literal(ast: &Literal) -> unsynn::TokenStream {
+    quote! {
+        RenderNode::TextNode { content: format!("{}", #ast) }
+    }
 }
diff --git a/modules/site_test/src/main.rs b/modules/site_test/src/main.rs
index 55432d9..ba40ede 100644
--- a/modules/site_test/src/main.rs
+++ b/modules/site_test/src/main.rs
@@ -25,28 +25,14 @@ struct ParentLayout {
 
 impl Component for ParentLayout {
     fn render(self: Box<Self>) -> Vec<RenderNode> {
-        let test = morx! {
-            html lang="en-US" {
-                head { title { "test thing" } }
-                body { "some document" }
+        morx!{
+            html lang = "en-US" {
+                head {
+                    title = "test thing"
+                }
+                body = {self.children}
             }
-        };
-
-
-        vec![
-            RenderNode::Element { name: "html".to_string(), attributes: HashMap::from([("lang".to_string(), "en-US".to_string())]), children: vec![
-                RenderNode::Element { name: "head".to_string(), attributes: HashMap::new(), children: vec![
-                    RenderNode::Element { name: "title".to_string(), attributes: HashMap::new(), children: vec![
-                        RenderNode::TextNode { content: "test thing".to_string() }
-                    ] }
-                ] },
-                RenderNode::Element {
-                    name: "body".to_string(),
-                    attributes: HashMap::new(),
-                    children: self.children,
-                },
-            ] }
-        ]
+        }
     }
 }