frei0r_macros/
lib.rs

1#![feature(iterator_try_collect)]
2
3extern crate proc_macro;
4
5use proc_macro::TokenStream;
6
7use syn::*;
8use syn::punctuated::*;
9
10use quote::quote;
11
12use std::ffi::CString;
13
14struct FieldInfo {
15    ident : Ident,
16    ty : Type,
17    rename : Option<Expr>,
18    explain : Option<Expr>,
19}
20
21impl FieldInfo {
22    fn new(field : Field) -> Result<Option<Self>> {
23        let mut skip = false;
24        let mut rename = None;
25        let mut explain = None;
26        for attr in field.attrs {
27            if attr.path().is_ident("frei0r") {
28                let metas: Punctuated<Meta, Token![,]> = attr.parse_args_with(Punctuated::parse_terminated)?;
29                for meta in metas {
30                    match meta {
31                        Meta::Path(path) => {
32                            let ident = path.require_ident()?;
33                            match ident {
34                                ident if ident == "skip" => {
35                                    if rename.is_some() || explain.is_some() {
36                                        return Err(Error::new_spanned(path, "skip attribute cannot be specified with other attribute"));
37                                    }
38
39                                    if skip {
40                                        return Err(Error::new_spanned(path, "attempting to set skip attribute more than once"));
41                                    } else {
42                                        skip = true;
43                                    }
44                                },
45                                _ => Err(Error::new_spanned(path, "unknown attribute"))?,
46                            }
47                        },
48                        Meta::NameValue(meta_name_value) => {
49                            let ident = meta_name_value.path.require_ident()?;
50                            match ident {
51                                ident if ident == "rename" => {
52                                    if skip {
53                                        return Err(Error::new_spanned(meta_name_value, "skip attribute cannot be specified with other attribute"));
54                                    }
55
56                                    rename = match rename {
57                                        Some(_) => Err(Error::new_spanned(meta_name_value, "attempting to set rename attribute more than once"))?,
58                                        None => Some(meta_name_value.value),
59                                    };
60                                },
61                                ident if ident == "explain" => {
62                                    if skip {
63                                        return Err(Error::new_spanned(meta_name_value, "skip attribute cannot be specified with other attribute"));
64                                    }
65
66                                    explain = match explain {
67                                        Some(_) => Err(Error::new_spanned(meta_name_value, "attempting to set explain attribute more than once"))?,
68                                        None => Some(meta_name_value.value),
69                                    };
70                                },
71                                _ => Err(Error::new_spanned(meta_name_value, "unknown attribute"))?,
72                            }
73                        },
74                        Meta::List(meta_list) => Err(Error::new_spanned(meta_list, "unknown attribute"))?,
75                    }
76                }
77            }
78        }
79
80        if skip {
81            return Ok(None);
82        }
83
84        Ok(Some(Self {
85            ident : field.ident.unwrap(),
86            ty : field.ty,
87            rename,
88            explain,
89        }))
90    }
91
92    fn param_name(&self) -> Expr {
93        self.rename.clone().unwrap_or_else(|| {
94            let ident = self.ident.to_string();
95            let ident = CString::new(ident).unwrap();
96            let ident = proc_macro2::Literal::c_string(&ident);
97            parse_quote! { #ident }
98        })
99    }
100
101    fn param_explain(&self) -> Expr {
102        self.explain.clone().unwrap_or_else(|| parse_quote! { c"" })
103    }
104}
105
106struct DeriveInputInfo {
107    ident : Ident,
108    generics : Generics,
109    fields : Vec<FieldInfo>,
110}
111
112impl DeriveInputInfo {
113    fn new(derive_input : DeriveInput) -> Result<Self> {
114        match derive_input {
115            DeriveInput { ident, generics, data : Data::Struct(DataStruct { fields : Fields::Named(fields), .. }), .. } => Ok(Self {
116                ident,
117                generics,
118                fields : fields.named.into_iter().flat_map(|f| FieldInfo::new(f).transpose()).try_collect()?,
119            }),
120            _ => Err(Error::new_spanned(derive_input,  "Derive macro PluginBase is only supported on struct with named fields."))
121        }
122    }
123}
124
125/// Derive macro used in the implementation of [PluginBase](../frei0r_rs/trait.PluginBase.html) trait.
126#[proc_macro_derive(PluginBase, attributes(frei0r))]
127pub fn derive_plugin_base(input : TokenStream) -> TokenStream {
128    DeriveInputInfo::new(parse_macro_input!(input as DeriveInput))
129        .map(|info| {
130            let generics = info.generics;
131            let ident = &info.ident;
132
133            let param_count = info.fields.len();
134            let param_indices = (0..param_count).collect::<Vec<_>>();
135
136            let param_idents = info.fields.iter().map(|field| field.ident.clone()).collect::<Vec<_>>();
137            let param_tys    = info.fields.iter().map(|field| field.ty   .clone()).collect::<Vec<_>>();
138
139            let param_names    = info.fields.iter().map(|field| field.param_name())   .collect::<Vec<_>>();
140            let param_explains = info.fields.iter().map(|field| field.param_explain()).collect::<Vec<_>>();
141
142            let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
143            quote! {
144                unsafe impl #impl_generics ::frei0r_rs::PluginBase for #ident #ty_generics #where_clause {
145                    fn param_count() -> usize {
146                        #param_count
147                    }
148
149                    fn param_info(index : usize) -> ::frei0r_rs::ParamInfo {
150                        match index {
151                            #(#param_indices => ::frei0r_rs::ParamInfo {
152                                name : #param_names,
153                                kind : <#param_tys as ::frei0r_rs::Param>::kind(),
154                                explanation : #param_explains,
155                            }),*,
156                            _ => unreachable!()
157                        }
158                    }
159
160                    fn param_ref(&self, index : usize) -> ::frei0r_rs::ParamRef<'_> {
161                        match index {
162                            #(#param_indices =>  <#param_tys as ::frei0r_rs::Param>::as_ref(&self.#param_idents)),*,
163                            _ => unreachable!()
164                        }
165                    }
166
167                    fn param_mut(&mut self, index : usize) -> ::frei0r_rs::ParamMut<'_> {
168                        match index {
169                            #(#param_indices =>  <#param_tys as ::frei0r_rs::Param>::as_mut(&mut self.#param_idents)),*,
170                            _ => unreachable!()
171                        }
172                    }
173                }
174            }
175        })
176        .unwrap_or_else(|err| err.into_compile_error())
177        .into()
178}
179