name-index-derive 0.2.1

Proc-macro crate for name-index
Documentation
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" {
                    // Already found an index attribute, i.e. multiple attrs
                    if found_index {
                        err!("Only one field can hold the index attribute!");
                    }

                    found_index = true;
                    ty = &f.ty;
                    break;
                }
            }
            // Ignore first fields until the index attribute if there is one
            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,
                }
            }
        }
    };

    //println!("{}", tok.to_string());

    tok.into()
}