use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{
braced, parenthesized,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
token::{Brace, Comma, Paren},
Expr, FieldValue, Result, Token, Type,
};
enum ParsedChild {
Element(ParsedElement),
Expr(Expr),
}
struct ParsedElement {
ty: Type,
props: Punctuated<FieldValue, Comma>,
children: Vec<ParsedChild>,
}
impl Parse for ParsedElement {
fn parse(input: ParseStream) -> Result<Self> {
let ty: Type = input.parse()?;
let props = if input.peek(Paren) {
let props_input;
parenthesized!(props_input in input);
Punctuated::parse_terminated(&props_input)?
} else {
Punctuated::new()
};
let mut children = Vec::new();
if input.peek(Brace) {
let children_input;
braced!(children_input in input);
while !children_input.is_empty() {
if children_input.peek(Token![#]) {
children_input.parse::<Token![#]>()?;
let expr_input;
parenthesized!(expr_input in children_input);
children.push(ParsedChild::Expr(expr_input.parse()?));
} else {
children.push(ParsedChild::Element(children_input.parse()?));
}
}
}
Ok(Self {
ty,
props,
children,
})
}
}
impl ToTokens for ParsedElement {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let ty = &self.ty;
let prop_assignments = self.props.iter().map(|fv| {
let member = &fv.member;
let expr = &fv.expr;
quote! { _quill_props.#member = (#expr).into(); }
});
let has_children = !self.children.is_empty();
let children_code = if has_children {
let child_elements = self.children.iter().map(|child| match child {
ParsedChild::Element(elem) => {
quote! { #elem }
}
ParsedChild::Expr(expr) => {
quote! { #expr }
}
});
Some(quote! {
let _quill_children: ::std::vec::Vec<Element> = ::std::vec![#(#child_elements),*];
})
} else {
None
};
let children_vec = if has_children {
quote! { _quill_children }
} else {
quote! { ::std::vec![] }
};
tokens.extend(quote! {
{
type Props = <#ty as Component>::Props;
let mut _quill_props: Props = ::std::default::Default::default();
#(#prop_assignments)*
#children_code
Element::node::<#ty>(_quill_props, #children_vec)
}
});
}
}
#[proc_macro]
pub fn element(input: TokenStream) -> TokenStream {
let element = parse_macro_input!(input as ParsedElement);
quote!(#element).into()
}
#[cfg(test)]
mod tests {
}