extern crate proc_macro; use unsynn::*; use quote::quote; unsynn! { keyword MorxDocKey = "doctype"; struct MorxBlock(Vec>); struct MorxDoctype(Cons, MorxDocKey, Vec>, NodeSeparator>); 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"); let child_vec_expr = generate_block(&ast); quote! { ::morgana::RenderNode::Fragment { children: #child_vec_expr } }.into() } fn generate_block(ast: &MorxBlock) -> unsynn::TokenStream { let children: Vec = ast.0.iter().map(|child| { match child { Either::First(doctype) => generate_doctype(doctype), Either::Second(node) => generate_node(node), Either::Third(child_expression) => generate_expr_node(&child_expression.0.second), Either::Fourth(literal) => generate_literal(literal), } }).collect(); quote! { vec! [ #(#children),* ] }.into() } fn generate_doctype(ast: &MorxDoctype) -> unsynn::TokenStream { let elem_expressions: Vec = ast.0.third.iter().map(|elem| { match elem { Either::First(ident) => { let ident_string = Literal::string(&ident.to_string()).into_token_stream(); quote! {::morgana::DoctypeElem::Word( #ident_string )} }, Either::Second(literal) => { let literal = literal.clone().into_token_stream(); quote! {::morgana::DoctypeElem::String( #literal )} }, _ => unreachable!("Invalid doctype AST"), } }).collect(); quote! { ::morgana::RenderNode::Doctype(vec![ #(#elem_expressions),* ]) } } fn generate_node(ast: &MorxNode) -> unsynn::TokenStream { let elem_name = &ast.0.first; 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)) => { let inner = bracegroup.0.stream(); quote!{ {#inner}.into() } }, 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!{ {String::from(#literal).into()} }, Some(Either::Second(ident)) => quote!{ {String::from(#ident).into()} }, Some(Either::Third(bracegroup)) => { let inner = bracegroup.0.stream(); quote!{ {#inner}.into() } }, 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.to_string().chars().next().map_or(false, |char| char.is_uppercase()); if is_component_node { quote! { ::morgana::RenderNode::Component( Box::new({ #elem_name { children: #children, #(#elem_props),* } }) ) }.into() } else { let elem_name = Literal::string(&elem_name.to_string()).into_token_stream(); quote! { ::morgana::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! { ::morgana::RenderNode::Fragment { children: ::morgana::IntoRender::into_render(#children) } } }, _ => unreachable!("Invalid expr node"), } } fn generate_literal(ast: &Literal) -> unsynn::TokenStream { quote! { ::morgana::RenderNode::TextNode { content: format!("{}", #ast) } } }