extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, punctuated::Punctuated, token::Comma, Ident, Type};
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);
let args = parse_macro_input!(attrs as syn::AttributeArgs);
parse_deltamod(input, parse_derives(args))
}
fn parse_derives(args: syn::AttributeArgs) -> Vec<syn::Path> {
if args.len() == 0 {
return vec![];
}
let mut res: Option<Vec<_>> = None;
for att in args {
res = match att {
syn::NestedMeta::Meta(syn::Meta::List(p)) if p.path.is_ident("derive") => p
.nested
.iter()
.map(|x| {
if let syn::NestedMeta::Meta(syn::Meta::Path(y)) = x {
Some(y.clone())
} else {
None
}
})
.collect::<Option<Vec<syn::Path>>>(),
_ => None,
};
}
if let Some(res) = res {
return res;
} else {
panic!("Invalid attributes detected")
}
}
fn parse_deltamod(modblock: syn::ItemMod, derives: Vec<syn::Path>) -> 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))]
#[derive(#(#derives),*)]
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()
}