diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | modules/morgana_proc/Cargo.toml | 1 | ||||
-rw-r--r-- | modules/morgana_proc/src/lib.rs | 141 | ||||
-rw-r--r-- | modules/site_test/src/main.rs | 28 | ||||
-rw-r--r-- | src/render.rs | 10 |
5 files changed, 150 insertions, 31 deletions
diff --git a/Cargo.lock b/Cargo.lock index 5ebe58f..6243213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,7 @@ dependencies = [ name = "morgana_proc" version = "0.0.0" dependencies = [ + "quote", "unsynn", ] 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, - }, - ] } - ] + } } } diff --git a/src/render.rs b/src/render.rs index 3bb6e41..94b193b 100644 --- a/src/render.rs +++ b/src/render.rs @@ -18,6 +18,9 @@ pub enum RenderNode { attributes: HashMap<String, String>, children: Vec<RenderNode> }, + Fragment { + children: Vec<RenderNode> + }, TextNode { content: String, }, @@ -58,6 +61,13 @@ impl RenderNode { })()) }, + RenderNode::Fragment { children } => { + Box::pin((async move || { + join_all(children.into_iter() + .map(|child| child.render_to_string())).await + .join("") + })()) + } RenderNode::TextNode { content } => Box::pin((async move || content)()), RenderNode::Null => Box::pin((async move || "".to_string())()), |