summary refs log tree commit diff
path: root/modules/morgana_proc/src
diff options
context:
space:
mode:
authorAshelyn Rose <git@ashen.earth>2025-04-25 01:28:20 -0600
committerAshelyn Rose <git@ashen.earth>2025-04-25 01:28:20 -0600
commit9f29a187b2395c5d1d4039600917e642948ad26b (patch)
tree88852805dc6c0642bf9e3ebf3a25a0ba4f597642 /modules/morgana_proc/src
parentb115605055e72c5a261f9f024d7db8f508517fc9 (diff)
Working proc macro proc_macro
Diffstat (limited to 'modules/morgana_proc/src')
-rw-r--r--modules/morgana_proc/src/lib.rs141
1 files changed, 131 insertions, 10 deletions
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) }
+    }
 }