extern crate proc_macro;
extern crate proc_macro2;
use proc_macro2::{TokenStream, TokenTree};
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Ident, Type, Meta};
struct AliasMapping {
alias: Ident,
resolve: Ident,
}
#[proc_macro_derive(NameIndex, attributes(index, alias))]
pub fn derive_name_index(input: proc_macro::TokenStream)
-> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_ident = input.ident;
macro_rules! err {
($msg:expr) => {
return syn::Error::new_spanned(&struct_ident, $msg)
.into_compile_error()
.into()
};
}
let fields = match input.data {
syn::Data::Struct(s) => s.fields,
_ => err!("NameIndex can only be derived for structs!"),
};
let fields = match fields {
syn::Fields::Named(f) => f.named,
_ => err!("Only named fields are supported for NameIndex!"),
};
if fields.len() < 1 {
err!("NameIndex derive requires at least one field!");
}
let mut field_idents: Vec<Ident> = vec![];
let mut aliases: Vec<AliasMapping> = vec![];
let mut aliases_field: Vec<(Ident, Vec<Ident>)> = vec![];
let mut ty: &Type = &fields[0].ty;
let mut found_index = false;
let mut index_present = false;
for i in 0..fields.len() {
let f = &fields[i];
for attr in f.attrs.iter() {
if attr.path().get_ident().unwrap().to_string() == "index" {
index_present = true;
break;
}
}
if index_present {
break;
}
}
for i in 0..fields.len() {
let f = &fields[i];
let ident = f.ident.clone().unwrap();
if index_present {
for attr in f.attrs.iter() {
if attr.path().get_ident().unwrap().to_string() == "index" {
if found_index {
err!("Only one field can hold the index attribute!");
}
found_index = true;
ty = &f.ty;
break;
}
}
if !found_index {
continue;
}
}
if f.ty != *ty {
continue;
}
field_idents.push(ident.clone());
for attr in f.attrs.iter() {
if attr.path().get_ident().unwrap().to_string() == "alias" {
let list = match &attr.meta {
Meta::List(l) => l,
_ => {
err!("Invalid alias attribute! Expected MetaList");
},
};
let tokens: Vec<TokenTree> = list.tokens
.clone()
.into_iter()
.collect();
let mut field_aliases: Vec<Ident> = vec![];
let mut found_delim = true;
for tt in tokens {
if found_delim {
match tt {
TokenTree::Ident(id) => {
field_aliases.push(id);
},
_ => {
err!("Invalid alias attribute!");
},
}
found_delim = false;
}
else {
match tt {
TokenTree::Punct(p) => {
if p.as_char() != ',' {
err!("Invalid alias attribute!");
}
found_delim = true;
}
_ => {
err!("Invalid alias attribute!");
},
}
}
}
for alias in &field_aliases {
aliases.push(AliasMapping {
alias: alias.clone(),
resolve: ident.clone(),
});
}
aliases_field.push((ident.clone(), field_aliases));
break;
}
}
}
let mut aliases_ref = TokenStream::new();
for m in &aliases {
let alias = &m.alias;
let resolve = &m.resolve;
let tok: TokenStream = quote! {
::std::stringify!(#alias) =>
::std::option::Option::Some(&self.#resolve),
}.into();
aliases_ref.extend(tok);
}
let mut aliases_ref_mut = TokenStream::new();
for m in &aliases {
let alias = &m.alias;
let resolve = &m.resolve;
let tok: TokenStream = quote! {
::std::stringify!(#alias) =>
::std::option::Option::Some(&mut self.#resolve),
}.into();
aliases_ref_mut.extend(tok);
}
let mut field_aliases = TokenStream::new();
for (ident, aliases) in aliases_field {
let v: TokenStream = quote! {
::std::vec![#(::std::stringify!(#aliases)),*]
}.into();
let tok: TokenStream = quote! {
::std::stringify!(#ident) => #v,
}.into();
field_aliases.extend(tok);
}
let mut resolve_aliases = TokenStream::new();
for m in &aliases {
let alias = &m.alias;
let resolve = &m.resolve;
let tok: TokenStream = quote! {
::std::stringify!(#alias) =>
::std::option::Option::Some(::std::stringify!(#resolve)),
}.into();
resolve_aliases.extend(tok);
}
let tok = quote! {
impl ::name_index::NameIndex<#ty> for #struct_ident {
fn get_ref(&self, name: &str) -> ::std::option::Option<&#ty> {
match name {
#(::std::stringify!(#field_idents) =>
::std::option::Option::Some(&self.#field_idents),)*
#aliases_ref
_ => ::std::option::Option::None,
}
}
fn get_ref_mut(&mut self, name: &str) ->
::std::option::Option<&mut #ty> {
match name {
#(::std::stringify!(#field_idents) =>
::std::option::Option::Some(&mut self.#field_idents),)*
#aliases_ref_mut
_ => ::std::option::Option::None,
}
}
fn fields(&self) ->
::std::vec::Vec<::name_index::Field::<#ty>> {
::std::vec![#((::std::stringify!(#field_idents),
&self.#field_idents)),*]
}
fn fields_mut(&mut self) ->
::std::vec::Vec<::name_index::FieldMut::<#ty>> {
::std::vec![#((::std::stringify!(#field_idents),
&mut self.#field_idents)),*]
}
fn field_aliases(&self, name: &str) ->
::std::vec::Vec<&'static str> {
match name {
#field_aliases
_ => ::std::vec![],
}
}
fn resolve_alias(&self, name: &str) ->
::std::option::Option<&'static str> {
match name {
#(::std::stringify!(#field_idents) =>
::std::option::Option::Some(
::std::stringify!(#field_idents)
),)*
#resolve_aliases
_ => ::std::option::Option::None,
}
}
}
};
tok.into()
}