extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields,
DataStruct, FieldsNamed, punctuated::Punctuated, Field,
PathSegment};
#[proc_macro_derive(MutConfig)]
pub fn derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let sname = &ast.ident;
let data = &ast.data;
if sname.to_string().find("Config").is_none() {
panic!("Please name the struct in a format like <name>Config (ex. MainConfig)");
};
let fields = filter_ignore_out(extract_fields(&data));
let types = extract_types(&data);
let insertions = (0..fields.len()).map(|i| {
let fname = &fields[i].ident;
let oval = into_oval(&fields[i], &types[i]);
quote! {
map.insert(String::from(stringify!(#fname)), #oval);
}
});
let allinserts = quote! {
#(#insertions)*
};
let expandify = quote! {
impl MutConfig for #sname {
fn to_hashmap(&self) -> HashMap<String, MutOptionVal> {
use MutOptionVal::*;
let mut map = HashMap::new();
#allinserts
map
}
}
};
expandify.into()
}
fn extract_fields(data: &Data) -> &Punctuated<Field, syn::token::Comma>{
if let Data::Struct(DataStruct {
fields: Fields::Named(FieldsNamed {
ref named,
..
}), ..
}) = data {
named
} else {
unimplemented!()
}
}
fn not_ignored(field: &Field) -> bool {
for attr in &field.attrs {
let segs = &attr.path.segments;
for seg in segs {
let attrname = seg.ident.to_string();
if attrname == "ignore" {return false;};
}
};
true
}
fn filter_ignore_out(fields: &Punctuated<Field, syn::token::Comma>) -> Vec<&Field> {
fields.iter().filter(|f| not_ignored(*f)).collect()
}
fn extract_types(data: &Data) -> Vec<&syn::PathSegment>{
let fields = filter_ignore_out(extract_fields(data));
fields.iter().map(|field| {
if let syn::Field {
ty: syn::Type::Path(
syn::TypePath {
path: syn::Path {
ref segments,
..
},
..
}
),
..
} = field {
&segments[0]
} else {
eprintln!("References are not supported with #[derive(MutConfig)]. Reference found within type.");
unimplemented!()
}
}).collect::<Vec<&syn::PathSegment>>()
}
fn extract_generic_types(data: &Data) -> Vec<Vec<&syn::PathSegment>> {
let types = extract_types(data);
let something : Vec<Vec<&syn::PathSegment>> = types.iter().map(|ps| {
let args =
if let syn::PathArguments::AngleBracketed(
syn::AngleBracketedGenericArguments {
ref args,
..
}
) = &ps.arguments {
Some(args)
} else {
None
};
let gentype : Vec<&syn::PathSegment> = args.map_or(vec![], |sa| {
sa.iter().map(|a| {
if let syn::GenericArgument::Type(syn::Type::Path(
syn::TypePath {
path : syn::Path {
ref segments,
..
},
..
}
)) = a {
&segments[0]
} else {
eprintln!("References are not supported with #[derive(MutConfig)]. Reference found within generic.");
unimplemented!()
}
}).collect()
});
gentype
}).collect();
something
}
fn incompatible_type_panic(tyname: &String) {
panic!("Can't use \'{0}\' type - not yet supported by derive(MutConfig).\nHint: If you meant to add a struct implementing MutConfig, please name them in the following format: '{0}Config'\nPlease use one of the supported types as shown below:\n {1:#?}",tyname, ["isize", "String", "bool", "f64", "Vec<...>", "Option<...>"]);
}
fn into_oval(field: &Field, ty: &PathSegment) -> proc_macro2::TokenStream {
let fname = &field.ident;
let tname = &ty.ident;
let tstr = &tname.to_string();
if tstr == "isize" {
quote! {OInt(self.#fname.clone())}
} else if tstr == "String" {
quote! {OString(self.#fname.clone())}
} else if tstr == "bool" {
quote! {OBool(self.#fname.clone())}
} else if tstr == "f64" {
quote! {OFloat(self.#fname.clone())}
} else if tstr == "Vec" {
let gen = get_first_generic(&ty);
let v = into_subval(&gen, &String::from("a"));
quote! {OArray(self.#fname.iter().map(|a| #v).collect())}
} else if tstr == "Option" {
let gen = get_first_generic(&ty);
let v = into_subval(&gen, &String::from("a"));
quote! {self.#fname.clone().map_or(ONone(), |a| #v)}
} else if tstr.find("Config").is_some() {
quote! {OMap(self.#fname.to_hashmap())}
} else {
unimplemented!()
}
}
fn get_first_generic(ty: &PathSegment) -> &PathSegment {
let args =
if let syn::PathArguments::AngleBracketed(
syn::AngleBracketedGenericArguments {
ref args,
..
}
) = &ty.arguments {
args
} else {
unimplemented!();
};
let typs : Vec<&PathSegment> = args.iter().map(|a| {
if let syn::GenericArgument::Type(syn::Type::Path(
syn::TypePath {
path : syn::Path {
ref segments,
..
},
..
}
)) = a {
&segments[0]
} else {
unimplemented!()
}
}).collect();
typs[0]
}
fn into_subval(ty: &PathSegment, arg_name: &String) -> proc_macro2::TokenStream {
let tname = &ty.ident;
let tstr = &tname.to_string();
let arg = syn::Ident::new(arg_name, tname.span());
let new_arg = syn::Ident::new((arg_name.clone() + "a").as_str(), tname.span());
if tstr == "isize" {
quote! {OInt(#arg.clone())}
} else if tstr == "String" {
quote! {OString(#arg.clone())}
} else if tstr == "bool" {
quote! {OBool(#arg.clone())}
} else if tstr == "f64" {
quote! {OFloat(#arg.clone())}
} else if tstr == "Vec" {
let gen = get_first_generic(&ty);
let v = into_subval(&gen, &new_arg.to_string());
quote! {OArray(#arg.iter().map(|#new_arg| #v).collect())}
} else if tstr == "Option" {
let gen = get_first_generic(&ty);
let v = into_subval(&gen, &new_arg.to_string());
quote! {#arg.clone().map_or(ONone(), |#new_arg| #v)}
} else if tstr.find("Config").is_some() {
quote! {OMap(#arg.to_hashmap())}
} else {
incompatible_type_panic(&tstr);
unimplemented!();
}
}