use proc_macro::TokenStream;
use quote::quote;
use syn::{
Expr, Ident, Path, Result, Token,
parse::{Parse, ParseStream},
parse_macro_input, parse_quote,
punctuated::Punctuated,
token,
};
#[proc_macro]
pub fn bx(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as MacroInput);
let bx_root: Path = if cfg!(feature = "core-default") {
parse_quote!(::builderx_core)
} else {
parse_quote!(::builderx)
};
let adapter: Path = input
.adapter
.unwrap_or_else(|| parse_quote!(#bx_root::DefaultAdapter));
input.root.to_builder_tokens(&adapter, &bx_root).into()
}
struct MacroInput {
adapter: Option<Path>,
root: ViewElement,
}
impl Parse for MacroInput {
fn parse(input: ParseStream) -> Result<Self> {
let adapter = if input.peek(Ident) || input.peek(Token![::]) {
let fork = input.fork();
let _path: Path = fork.parse()?;
if fork.peek(Token![=>]) {
let path = input.parse()?;
input.parse::<Token![=>]>()?;
Some(path)
} else {
None
}
} else {
None
};
let root = input.parse()?;
Ok(Self { adapter, root })
}
}
struct ViewElement {
tag: Path,
args: Option<Punctuated<Expr, Token![,]>>,
modifiers: Option<Punctuated<Expr, Token![,]>>,
children: Vec<ViewChild>,
}
enum ViewChild {
Element(ViewElement),
Expr(Expr),
Spread(Expr),
}
impl Parse for ViewElement {
fn parse(input: ParseStream) -> Result<Self> {
let tag: Path = input.parse()?;
let args = if input.peek(token::Paren) {
let content;
syn::parenthesized!(content in input);
Some(Punctuated::parse_terminated(&content)?)
} else {
None
};
let modifiers = if input.peek(token::Bracket) {
let content;
syn::bracketed!(content in input);
Some(Punctuated::parse_terminated(&content)?)
} else {
None
};
let mut children = Vec::new();
if input.peek(token::Brace) {
let content;
syn::braced!(content in input);
while !content.is_empty() {
children.push(content.parse()?);
if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
}
}
}
Ok(ViewElement {
tag,
args,
modifiers,
children,
})
}
}
impl Parse for ViewChild {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(Token![..]) {
input.parse::<Token![..]>()?;
let expr: Expr = input.parse()?;
return Ok(ViewChild::Spread(expr));
}
if input.peek(Ident) || input.peek(Token![::]) {
let fork = input.fork();
if let Ok(_) = fork.parse::<Path>() {
if fork.peek(token::Paren) {
let content;
syn::parenthesized!(content in fork);
let _ = Punctuated::<Expr, Token![,]>::parse_terminated(&content);
}
if fork.peek(token::Bracket) || fork.peek(token::Brace) {
return Ok(ViewChild::Element(input.parse()?));
}
}
}
Ok(ViewChild::Expr(input.parse()?))
}
}
impl ViewElement {
fn to_builder_tokens(&self, adapter: &Path, bx_root: &Path) -> proc_macro2::TokenStream {
let tag = &self.tag;
let args = &self.args;
let mut builder = if let Some(args) = args {
quote! { #tag(#args) }
} else {
quote! { #tag() }
};
builder = quote! { <#adapter as #bx_root::BxAdapter>::start(#builder) };
if let Some(mods) = &self.modifiers {
for modifier in mods {
match modifier {
Expr::Path(_) => {
builder = quote! { #builder.#modifier() };
}
_ => {
builder = quote! { #builder.#modifier };
}
}
}
}
for child in &self.children {
builder = match child {
ViewChild::Element(el) => {
let child_tokens = el.to_builder_tokens(adapter, bx_root);
quote! { <#adapter as #bx_root::BxAdapter>::attach(#builder, #child_tokens) }
}
ViewChild::Expr(expr) => {
quote! { <#adapter as #bx_root::BxAdapter>::attach(#builder, #expr) }
}
ViewChild::Spread(expr) => {
quote! { <#adapter as #bx_root::BxAdapter>::attach_many(#builder, #expr) }
}
};
}
builder
}
}