extern crate proc_macro; use unsynn::*; use quote::quote; unsynn! { struct MorxBlock(Vec>); struct MorxNode(Cons>, >>); struct NodeSeparator(Either); struct MorxAttrs(Vec); struct MorxAttr(Cons, Either, Optional>); } #[proc_macro] pub fn morx(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let tokens = unsynn::TokenStream::from(input); let ast = tokens.to_token_iter().parse_all::().expect("syntax error"); generate_block(&ast).into() } fn generate_block(ast: &MorxBlock) -> unsynn::TokenStream { let children: Vec = 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(); 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) -> 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) } } }