diff options
Diffstat (limited to 'modules/morgana_proc/src')
-rw-r--r-- | modules/morgana_proc/src/lib.rs | 141 |
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) } + } } |