use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use syn::{parse::Parse, punctuated::Punctuated, Token};
struct GenerateGeneratorSyn {
macroname: syn::Ident,
component: Option<proc_macro2::TokenTree>,
structblk: Option<proc_macro2::TokenStream>,
initblk: Option<proc_macro2::TokenStream>,
updateblk: Option<proc_macro2::TokenStream>,
updateout: Option<Punctuated<proc_macro2::TokenStream, Token![,]>>,
view_first: Option<proc_macro2::TokenTree>,
views: Option<proc_macro2::TokenStream>,
}
impl syn::parse::Parse for GenerateGeneratorSyn {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let macroname = input.parse()?;
input.parse::<syn::Token![=>]>()?;
let mut out = Self {
macroname,
component: None,
structblk: None,
initblk: None,
updateblk: None,
updateout: None,
view_first: None,
views: None,
};
let mut next = loop {
match input.parse::<TokenTree>()? {
TokenTree::Group(g) if g.delimiter() == proc_macro2::Delimiter::Bracket => {
if out.component.is_some() {
return Err(syn::Error::new(
g.span(),
"kurage: you may specify [< paste >] once only.",
));
}
out.component = Some(g.into());
continue;
}
TokenTree::Group(g) if g.delimiter() == proc_macro2::Delimiter::Brace => {
out.structblk = Some(g.stream());
input.parse::<syn::Token![:]>()?;
break input.parse()?;
}
TokenTree::Group(g) => {
return Err(syn::Error::new(g.span(), "kurage: unexpected token. Pass in [<$name Page>] for a custom naming scheme (look at the paste crate), or provide a { field1: Type, field2: Type2 } block."));
}
TokenTree::Punct(p) if p.as_char() == ':' && out.component.is_some() => {
break input.parse()?;
}
TokenTree::Punct(p) if p.as_char() == ':' => {
return Err(syn::Error::new(
p.span(),
"kurage: missing [< paste Component >] before `:`",
))
}
x => break x,
}
};
match &next {
proc_macro2::TokenTree::Ident(i) if i == "init" => {
input.parse::<syn::Token![:]>()?;
let t = input.parse::<Group>()?;
if t.delimiter() != Delimiter::Brace {
return Err(syn::Error::new(t.span(), "kurage: expected { ... }"));
}
out.initblk = Some(t.stream());
next = input.parse()?;
}
_ => (),
}
match &next {
proc_macro2::TokenTree::Ident(i) if i == "update" => {
struct Idk(Punctuated<TokenStream, Token![,]>);
impl Parse for Idk {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input
.parse_terminated(TokenStream::parse, Token![,])
.map(Self)
}
}
input.parse::<syn::Token![:]>()?;
let t = input.parse::<Group>()?;
if t.delimiter() != Delimiter::Brace {
return Err(syn::Error::new(t.span(), "kurage: expected { ... }"));
}
out.updateblk = Some(t.stream());
input.parse::<syn::Token![=>]>()?;
let t = input.parse::<Group>()?;
if t.delimiter() != Delimiter::Brace {
return Err(syn::Error::new(t.span(), "kurage: expected { ... }"));
}
out.updateout = Some(syn::parse2::<Idk>(t.stream())?.0);
next = input.parse()?;
}
_ => (),
}
out.view_first = Some(next);
out.views = Some(input.parse()?);
Ok(out)
}
}
fn recurse_replace<'a, T: FnMut(proc_macro2::Ident) -> Box<dyn Iterator<Item = TokenTree>>>(
ts: TokenStream,
ident: &'a str,
f: &'a mut T,
) -> impl Iterator<Item = TokenTree> + use<'a, T> {
ts.into_iter().flat_map(move |tt| match tt {
TokenTree::Ident(i) if i == ident => f(i),
TokenTree::Group(group) => {
Box::new(std::iter::once(TokenTree::Group(proc_macro2::Group::new(
group.delimiter(),
recurse_replace(group.stream(), ident, f).collect(),
))))
}
other => Box::new(std::iter::once(other)),
})
}
#[allow(clippy::missing_panics_doc)]
#[proc_macro]
pub fn generate_generator(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let GenerateGeneratorSyn {
macroname,
component,
structblk,
initblk,
updateblk,
updateout,
view_first,
views,
} = syn::parse_macro_input!(input as GenerateGeneratorSyn);
let views: TokenStream = recurse_replace(views.unwrap(), "KURAGE_INNER", &mut |_| {
Box::new(quote::quote! { $($viewtt)* }.into_iter())
})
.collect();
let component =
component.unwrap_or_else(|| quote::quote! { [<$name>] }.into_iter().next().unwrap());
let structblk = structblk.iter();
let inputblk = initblk.iter();
let updateblk = updateblk.iter();
let updateout = updateout.iter();
quote::quote! {
macro_rules! #macroname {
($name:ident $({$($model:tt)+})? $(as $modelname:ident)?:
$(
$(preinit $preinit:block)?
init$([$($local_ref:tt)+])?($root:ident, $initsender:ident, $initmodel:ident, $initwidgets:ident) $initblock:block
)?
update($self:ident, $message:ident, $sender:ident) {
$( $msg:ident$(($($param:ident: $paramtype:ty),+$(,)?))? => $msghdl:expr ),*$(,)?
}
=> {$( $out:pat ),*}
$($viewtt:tt)*
) => { ::kurage::paste::paste! {
::kurage::generate_component!(
#component$({#(#structblk)* $($model)+})? $(as $modelname)?:
$(init$([$($local_ref)+])?($root, $initsender, $initmodel, $initwidgets) {
#(#inputblk)*
$initblock
})?
update($self, $message, $sender) {
#(#updateblk)*
$( $msg$(($($param: $paramtype),+))? => $msghdl),*
} => {#(#updateout)* $($out),*}
#view_first #views
);
}};
}
}.into()
}
#[proc_macro_attribute]
pub fn mangle_ident(
attr: proc_macro::TokenStream,
body: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let ident = syn::parse_macro_input!(attr as syn::Ident);
let mut ii = None;
let body: TokenStream = recurse_replace(body.into(), &ident.to_string(), &mut |i| {
if ii.is_none() {
ii = Some(i);
}
Box::new(std::iter::once(TokenTree::Ident(ii.clone().unwrap())))
})
.collect();
quote::quote! { #body }.into()
}