extern crate proc_macro;
use proc_macro::{TokenStream};
use quote::{format_ident, quote};
use syn::{
parse_macro_input, Ident, Type,
punctuated::{Punctuated},
token::{Comma}
};
use convert_case::{Case, Casing};
#[proc_macro_attribute]
pub fn deltastruct(_attrs : TokenStream, input : TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::ItemMod);
parse_deltamod(input)
}
fn parse_deltamod(modblock : syn::ItemMod) -> TokenStream {
let content = modblock.content.expect("Deltastruct requires content defined in a module definition").1;
let ident_struct = modblock.ident.clone();
let ident_delta = format_ident!("{}Delta", modblock.ident);
let mut fields : Option<Punctuated<syn::Field, _>> = None;
let mut methods : Option<Vec<syn::ImplItemMethod>> = None;
let mut user_defined_items = Vec::<syn::Item>::new();
for item in content {
match item {
syn::Item::Struct(s) => {
if s.ident != modblock.ident {
continue;
}
user_defined_items.push(syn::Item::Struct(s.clone()));
fields = match s.fields {
syn::Fields::Named(nf) => Some(nf.named),
_ => panic!("Expected named fields as part of defined struct")
}
},
syn::Item::Impl(i) => {
user_defined_items.push(syn::Item::Impl(i.clone()));
methods = Some(i.items.iter().filter_map(|x| match x {
syn::ImplItem::Method(m) => Some((*m).clone()),
_ => None
}).collect());
},
_ => continue
}
}
let fields = fields.expect("No data field definition found.");
let methods = methods.expect("No impl definition found.");
let field_names = fields.iter().enumerate().map(|(i, field)| {
field.ident.clone().unwrap_or(
format_ident!("f{}", i).clone()
)
}).collect::<Vec<_>>();
let field_names_camel = field_names.iter().map(|n| {
format_ident!("{}", n.to_string().from_case(Case::Snake).to_case(Case::UpperCamel))
}).collect::<Vec<_>>();
let field_types = fields.iter().map(|f| f.ty.clone()).collect::<Vec<_>>();
let field_types_fn : Vec<_> = field_types.iter().map(|ty| {
match ty {
syn::Type::Path(p) => {
let mut segs = p.path.segments.clone();
let seg = segs.pop().expect("Empty type path on field.");
let seg = seg.value();
let nseg = syn::PathSegment{
ident: format_ident!("{}Delta", seg.ident),
..seg.clone()
};
segs.push(nseg);
segs
},
_ => panic!("Expected type path for field.")
}
}).collect();
let fns_names : Vec<Ident> = methods.iter().map(|x|
x.sig.ident.clone()
).collect();
let fns_names_camel : Vec<Ident> = methods.iter().map(|x|
format_ident!("{}", x.sig.ident.to_string().from_case(Case::Snake).to_case(Case::UpperCamel))
).collect();
let fns_args_types : Vec<Punctuated<Box<Type>, Comma>> = methods.iter().map(|m|
m.sig.inputs.clone().into_iter().skip(1)
.filter_map(|arg| match arg {
syn::FnArg::Typed(targ) => Some(targ.ty),
_ => None
}).collect()
).collect();
let fns_args_names : Vec<Punctuated<syn::Ident, Comma>> = fns_args_types.iter().map(|ma| {
(0..ma.len()).map(|i| format_ident!("f{}", i)).collect()
}).collect::<Vec<_>>();
let fns_self_match_names = fns_names_camel.iter().map(|x| {
let mut res = Punctuated::<syn::PathSegment, syn::token::Colon2>::new();
res.push(syn::PathSegment{
ident: ident_delta.clone(),
arguments: syn::PathArguments::None
});
res.push(syn::PathSegment{
ident: x.clone(),
arguments: syn::PathArguments::None
});
res
});
let fns_field_match_names = field_names_camel.iter().map(|x| {
let mut res = Punctuated::<syn::PathSegment, syn::token::Colon2>::new();
res.push(syn::PathSegment{
ident: ident_delta.clone(),
arguments: syn::PathArguments::None
});
res.push(syn::PathSegment{
ident: x.clone(),
arguments: syn::PathArguments::None
});
res
});
let token_delta_struct = quote!(
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum #ident_delta {
#(#fns_names_camel(#fns_args_types)),*,
#(#field_names_camel(#field_types_fn)),*
}
);
let token_impl_match = quote!(
#(#fns_self_match_names(#fns_args_names) => self.#fns_names(#fns_args_names)),*,
#(#fns_field_match_names(ref x) => self.#field_names.apply(x)),*
);
let token_impl = quote!(
impl DeltaStruct<#ident_delta> for #ident_struct {
fn apply(&mut self, delta : &#ident_delta) {
match *delta {
#token_impl_match
};
}
}
);
quote!(
#(#user_defined_items)*
#token_delta_struct
#token_impl
).into()
}