Skip to main content

name_index_derive/
lib.rs

1extern crate proc_macro;
2extern crate proc_macro2;
3
4use proc_macro2::{TokenStream, TokenTree};
5use quote::quote;
6use syn::{parse_macro_input, DeriveInput, Ident, Type, Meta};
7
8struct AliasMapping {
9    alias: Ident,
10    resolve: Ident,
11}
12
13#[proc_macro_derive(NameIndex, attributes(index, alias))]
14pub fn derive_name_index(input: proc_macro::TokenStream)
15    -> proc_macro::TokenStream {
16    let input = parse_macro_input!(input as DeriveInput);
17    let struct_ident = input.ident;
18
19    macro_rules! err {
20        ($msg:expr) => {
21            return syn::Error::new_spanned(&struct_ident, $msg)
22                .into_compile_error()
23                .into()
24        };
25    }
26
27    let fields = match input.data {
28        syn::Data::Struct(s) => s.fields,
29        _ => err!("NameIndex can only be derived for structs!"),
30    };
31
32    let fields = match fields {
33        syn::Fields::Named(f) => f.named,
34        _ => err!("Only named fields are supported for NameIndex!"),
35    };
36
37    if fields.len() < 1 {
38        err!("NameIndex derive requires at least one field!");
39    }
40
41    let mut field_idents: Vec<Ident> = vec![];
42
43    let mut aliases: Vec<AliasMapping> = vec![];
44    let mut aliases_field: Vec<(Ident, Vec<Ident>)> = vec![];
45
46    let mut ty: &Type = &fields[0].ty;
47    let mut found_index = false;
48    let mut index_present = false;
49
50    for i in 0..fields.len() {
51        let f = &fields[i];
52        for attr in f.attrs.iter() {
53            if attr.path().get_ident().unwrap().to_string() == "index" {
54                index_present = true;
55                break;
56            }
57        }
58        if index_present {
59            break;
60        }
61    }
62
63    for i in 0..fields.len() {
64        let f = &fields[i];
65        let ident = f.ident.clone().unwrap();
66
67        if index_present {
68            for attr in f.attrs.iter() {
69                if attr.path().get_ident().unwrap().to_string() == "index" {
70                    // Already found an index attribute, i.e. multiple attrs
71                    if found_index {
72                        err!("Only one field can hold the index attribute!");
73                    }
74
75                    found_index = true;
76                    ty = &f.ty;
77                    break;
78                }
79            }
80            // Ignore first fields until the index attribute if there is one
81            if !found_index {
82                continue;
83            }
84        }
85
86        if f.ty != *ty {
87            continue;
88        }
89
90        field_idents.push(ident.clone());
91
92        for attr in f.attrs.iter() {
93            if attr.path().get_ident().unwrap().to_string() == "alias" {
94                let list = match &attr.meta {
95                    Meta::List(l) => l,
96                    _ => {
97                        err!("Invalid alias attribute! Expected MetaList");
98                    },
99                };
100                let tokens: Vec<TokenTree> = list.tokens
101                    .clone()
102                    .into_iter()
103                    .collect();
104                let mut field_aliases: Vec<Ident> = vec![];
105                let mut found_delim = true;
106                for tt in tokens {
107                    if found_delim {
108                        match tt {
109                            TokenTree::Ident(id) => {
110                                field_aliases.push(id);
111                            },
112                            _ => {
113                                err!("Invalid alias attribute!");
114                            },
115                        }
116                        found_delim = false;
117                    }
118                    else {
119                        match tt {
120                            TokenTree::Punct(p) => {
121                                if p.as_char() != ',' {
122                                    err!("Invalid alias attribute!");
123                                }
124                                found_delim = true;
125                            }
126                            _ => {
127                                err!("Invalid alias attribute!");
128                            },
129                        }
130                    }
131                }
132                for alias in &field_aliases {
133                    aliases.push(AliasMapping {
134                        alias: alias.clone(),
135                        resolve: ident.clone(),
136                    });
137                }
138                aliases_field.push((ident.clone(), field_aliases));
139                break;
140            }
141        }
142    }
143
144    let mut aliases_ref = TokenStream::new();
145    for m in &aliases {
146        let alias = &m.alias;
147        let resolve = &m.resolve;
148        let tok: TokenStream = quote! {
149            ::std::stringify!(#alias) =>
150                ::std::option::Option::Some(&self.#resolve),
151        }.into();
152        aliases_ref.extend(tok);
153    }
154
155    let mut aliases_ref_mut = TokenStream::new();
156    for m in &aliases {
157        let alias = &m.alias;
158        let resolve = &m.resolve;
159        let tok: TokenStream = quote! {
160            ::std::stringify!(#alias) =>
161                ::std::option::Option::Some(&mut self.#resolve),
162        }.into();
163        aliases_ref_mut.extend(tok);
164    }
165
166    let mut field_aliases = TokenStream::new();
167    for (ident, aliases) in aliases_field {
168        let v: TokenStream = quote! {
169            ::std::vec![#(::std::stringify!(#aliases)),*]
170        }.into();
171        let tok: TokenStream = quote! {
172            ::std::stringify!(#ident) => #v,
173        }.into();
174        field_aliases.extend(tok);
175    }
176
177    let mut resolve_aliases = TokenStream::new();
178    for m in &aliases {
179        let alias = &m.alias;
180        let resolve = &m.resolve;
181        let tok: TokenStream = quote! {
182            ::std::stringify!(#alias) =>
183                ::std::option::Option::Some(::std::stringify!(#resolve)),
184        }.into();
185        resolve_aliases.extend(tok);
186    }
187
188    let tok = quote! {
189        impl ::name_index::NameIndex<#ty> for #struct_ident {
190            fn get_ref(&self, name: &str) -> ::std::option::Option<&#ty> {
191                match name {
192                    #(::std::stringify!(#field_idents) =>
193                        ::std::option::Option::Some(&self.#field_idents),)*
194                    #aliases_ref
195                    _ => ::std::option::Option::None,
196                }
197            }
198
199            fn get_ref_mut(&mut self, name: &str) ->
200                ::std::option::Option<&mut #ty> {
201                match name {
202                    #(::std::stringify!(#field_idents) =>
203                        ::std::option::Option::Some(&mut self.#field_idents),)*
204                    #aliases_ref_mut
205                    _ => ::std::option::Option::None,
206                }
207            }
208
209            fn fields(&self) ->
210                ::std::vec::Vec<::name_index::Field::<#ty>> {
211                ::std::vec![#((::std::stringify!(#field_idents),
212                &self.#field_idents)),*]
213            }
214
215            fn fields_mut(&mut self) ->
216                ::std::vec::Vec<::name_index::FieldMut::<#ty>> {
217                ::std::vec![#((::std::stringify!(#field_idents),
218                &mut self.#field_idents)),*]
219            }
220
221            fn field_aliases(&self, name: &str) ->
222                ::std::vec::Vec<&'static str> {
223                match name {
224                    #field_aliases
225                    _ => ::std::vec![],
226                }
227            }
228
229            fn resolve_alias(&self, name: &str) ->
230                ::std::option::Option<&'static str> {
231                match name {
232                    #(::std::stringify!(#field_idents) =>
233                        ::std::option::Option::Some(
234                            ::std::stringify!(#field_idents)
235                        ),)*
236                    #resolve_aliases
237                    _ => ::std::option::Option::None,
238                }
239            }
240        }
241    };
242
243    //println!("{}", tok.to_string());
244
245    tok.into()
246}