battler_wamp_values_proc_macro/
lib.rs

1extern crate proc_macro;
2
3use itertools::Itertools;
4use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::quote;
7use syn::{
8    Error,
9    Field,
10    Ident,
11    Index,
12    ItemStruct,
13    Meta,
14    Path,
15    Type,
16    parse::{
17        Parse,
18        ParseStream,
19    },
20    parse_macro_input,
21};
22
23#[derive(Default)]
24enum DefaultAttr {
25    #[default]
26    False,
27    True,
28    Path(Path),
29}
30
31impl DefaultAttr {
32    pub fn can_be_default(&self) -> bool {
33        match self {
34            Self::False => false,
35            _ => true,
36        }
37    }
38}
39
40#[derive(Default)]
41struct InputFieldAttrs {
42    default: DefaultAttr,
43    skip_serializing_if: Option<Path>,
44}
45
46fn parse_input_field_attrs(field: &Field) -> syn::Result<InputFieldAttrs> {
47    let attr = field.attrs.iter().find(|attr| {
48        if let Meta::List(list) = &attr.meta {
49            if list.path.is_ident("battler_wamp_values") {
50                return true;
51            }
52        }
53        false
54    });
55    let attr = match attr {
56        Some(attr) => attr,
57        None => return Ok(InputFieldAttrs::default()),
58    };
59
60    let mut default = DefaultAttr::False;
61    let mut skip_serializing_if = None;
62    attr.parse_nested_meta(|meta| {
63        if meta.path.is_ident("default") {
64            default = match meta.value() {
65                Ok(value) => DefaultAttr::Path(value.parse::<Path>()?),
66                Err(_) => DefaultAttr::True,
67            };
68        }
69        if meta.path.is_ident("skip_serializing_if") {
70            let value = meta.value()?;
71            skip_serializing_if = Some(value.parse::<Path>()?);
72        }
73        Ok(())
74    })?;
75    Ok(InputFieldAttrs {
76        default,
77        skip_serializing_if,
78    })
79}
80
81struct InputField {
82    ident: Option<Ident>,
83    ty: Type,
84    attrs: InputFieldAttrs,
85}
86
87struct ListInput {
88    ident: Ident,
89    fields: Vec<InputField>,
90}
91
92impl Parse for ListInput {
93    fn parse(input: ParseStream) -> syn::Result<Self> {
94        let call_site = Span::call_site();
95        let input = match ItemStruct::parse(input) {
96            Ok(item) => item,
97            Err(_) => return Err(Error::new(call_site, "input must be a struct")),
98        };
99        let ident = input.ident;
100        let mut defaulted = false;
101        let fields = input
102            .fields
103            .into_iter()
104            .map(|field| {
105                let attrs = parse_input_field_attrs(&field)?;
106                if attrs.default.can_be_default() {
107                    defaulted = true;
108                } else if defaulted {
109                    return Err(Error::new(
110                        call_site,
111                        "fields after a default field must also have a default",
112                    ));
113                }
114                Ok(InputField {
115                    ident: field.ident,
116                    ty: field.ty,
117                    attrs,
118                })
119            })
120            .collect::<Result<Vec<_>, Error>>()?;
121        Ok(Self { ident, fields })
122    }
123}
124
125/// Procedural macro for deriving `battler_wamp_values::WampSerialize` and
126/// `battler_wamp_values::WampDeserialize` for a struct that converts to a
127/// `battler_wamp_values::List`.
128#[proc_macro_derive(WampList, attributes(battler_wamp_values))]
129pub fn derive_wamp_list(input: TokenStream) -> TokenStream {
130    let input = parse_macro_input!(input as ListInput);
131    let call_site = Span::call_site();
132
133    let ident = input.ident;
134
135    let (field_serializers, field_deserializers, field_identifiers): (Vec<_>, Vec<_>, Vec<_>) = input.fields.iter().enumerate().map(|(i, field)| {
136        let accessor = match &field.ident {
137            Some(ident) => quote!(self.#ident),
138            None => { let i = Index::from(i); quote!(self.#i) },
139        };
140        let ty = &field.ty;
141        let field_name = field.ident.clone().unwrap_or(Ident::new(&format!("field_{i}"), call_site));
142        let serialize_check = match &field.attrs.skip_serializing_if {
143            Some(skip_serializing_if) => Some(quote! {
144                if #skip_serializing_if(&#accessor) {
145                    return Ok(battler_wamp_values::Value::List(list));
146                }
147            }),
148            None => None,
149        };
150        let if_empty = match &field.attrs.default {
151            DefaultAttr::False => quote!(return Err(battler_wamp_values::WampDeserializeError::new(std::fmt::format(format_args!("list member {} of {} is missing", std::stringify!(#field_name), std::stringify!(#ident)))))),
152            DefaultAttr::True => quote!(<#ty as Default>::default()),
153            DefaultAttr::Path(path) => quote!(#path()),
154        };
155        (
156            quote! {
157                #serialize_check
158                match battler_wamp_values::WampSerialize::wamp_serialize(#accessor) {
159                    Ok(val) => list.push(val),
160                    Err(err) => return Err(err.annotate(std::fmt::format(format_args!("failed to serialize list member {} of {}", std::stringify!(#field_name), std::stringify!(#ident))))),
161                }
162            },
163            quote! {
164                let #field_name = match list.get_mut(#i) {
165                    Some(val) => {
166                        let mut out = battler_wamp_values::Value::Bool(false);
167                        std::mem::swap(val, &mut out);
168                        Some(out)
169                    }
170                    None => None,
171                };
172                let #field_name = match #field_name {
173                    Some(val) => match battler_wamp_values::WampDeserialize::wamp_deserialize(val) {
174                        Ok(val) => val,
175                        Err(err) => return Err(err.annotate(std::fmt::format(format_args!("failed to deserialize list member {} of {}", std::stringify!(#field_name), std::stringify!(#ident)))))
176                    },
177                    None => #if_empty,
178                };
179            },
180            quote!(#field_name)
181        )
182    }).multiunzip();
183
184    let serialize = quote! {
185        impl battler_wamp_values::WampSerialize for #ident {
186            fn wamp_serialize(self) -> core::result::Result<battler_wamp_values::Value, battler_wamp_values::WampSerializeError> {
187                let mut list = battler_wamp_values::List::default();
188                #(#field_serializers)*
189                Ok(battler_wamp_values::Value::List(list))
190            }
191        }
192    };
193
194    let named = input.fields.is_empty() || input.fields.iter().any(|field| field.ident.is_some());
195    let struct_constructor = if named {
196        quote!(#ident { #(#field_identifiers,)* })
197    } else {
198        quote!(#ident(#(#field_identifiers,)*))
199    };
200    let deserialize = quote! {
201        impl battler_wamp_values::WampDeserialize for #ident {
202            fn wamp_deserialize(value: battler_wamp_values::Value) -> core::result::Result<Self, battler_wamp_values::WampDeserializeError> {
203                let mut list = match value {
204                    battler_wamp_values::Value::List(list) => list,
205                    _ => return Err(battler_wamp_values::WampDeserializeError::new("value must be a list")),
206                };
207                #(#field_deserializers)*
208                Ok(#struct_constructor)
209            }
210        }
211    };
212
213    quote! {
214        #serialize
215        #deserialize
216    }
217    .into()
218}
219
220struct DictionaryInput {
221    ident: Ident,
222    fields: Vec<InputField>,
223}
224
225impl Parse for DictionaryInput {
226    fn parse(input: ParseStream) -> syn::Result<Self> {
227        let call_site = Span::call_site();
228        let input = match ItemStruct::parse(input) {
229            Ok(item) => item,
230            Err(_) => return Err(Error::new(call_site, "input must be a struct")),
231        };
232        let ident = input.ident;
233        let fields = input
234            .fields
235            .into_iter()
236            .map(|field| {
237                let attrs = parse_input_field_attrs(&field)?;
238                Ok(InputField {
239                    ident: field.ident,
240                    ty: field.ty,
241                    attrs,
242                })
243            })
244            .collect::<Result<Vec<_>, Error>>()?;
245        Ok(Self { ident, fields })
246    }
247}
248
249/// Procedural macro for deriving `battler_wamp_values::WampSerialize` and
250/// `battler_wamp_values::WampDeserialize` for a struct that converts to a
251/// `battler_wamp_values::Dictionary`.
252#[proc_macro_derive(WampDictionary, attributes(battler_wamp_values))]
253pub fn derive_wamp_dictionary(input: TokenStream) -> TokenStream {
254    let input = parse_macro_input!(input as DictionaryInput);
255    let call_site = Span::call_site();
256
257    let ident = input.ident;
258
259    let (field_serializers, field_deserializers, field_identifiers): (Vec<_>, Vec<_>, Vec<_>) = input.fields.iter().enumerate().map(|(i, field)| {
260        let accessor = match &field.ident {
261            Some(ident) => quote!(self.#ident),
262            None => { let i = Index::from(i); quote!(self.#i) },
263        };
264        let ty = &field.ty;
265        let field_name = field.ident.clone().unwrap_or(Ident::new(&format!("field_{i}"), call_site));
266        let serialize_check = match &field.attrs.skip_serializing_if {
267            Some(skip_serializing_if) => quote!(!#skip_serializing_if(&#accessor)),
268            None => quote!(true),
269        };
270        let if_empty = match &field.attrs.default {
271            DefaultAttr::False => quote!(return Err(battler_wamp_values::WampDeserializeError::new(std::fmt::format(format_args!("dictionary member {} of {} is missing", std::stringify!(#field_name), std::stringify!(#ident)))))),
272            DefaultAttr::True => quote!(<#ty as Default>::default()),
273            DefaultAttr::Path(path) => quote!(#path()),
274        };
275        (
276            quote! {
277                if #serialize_check {
278                    match battler_wamp_values::WampSerialize::wamp_serialize(#accessor) {
279                        Ok(val) => dict.insert(stringify!(#field_name).to_owned(), val),
280                        Err(err) => return Err(err.annotate(std::fmt::format(format_args!("failed to serialize dictionary member {} of {}", std::stringify!(#field_name), std::stringify!(#ident))))),
281                    };
282                }
283            },
284            quote! {
285                let #field_name = match dict.get_mut(stringify!(#field_name)) {
286                    Some(val) => {
287                        let mut out = battler_wamp_values::Value::Bool(false);
288                        std::mem::swap(val, &mut out);
289                        Some(out)
290                    }
291                    None => None,
292                };
293                let #field_name = match #field_name {
294                    Some(val) => match battler_wamp_values::WampDeserialize::wamp_deserialize(val) {
295                        Ok(val) => val,
296                        Err(err) => return Err(err.annotate(std::fmt::format(format_args!("failed to deserialize dictionary member {} of {}", std::stringify!(#field_name), std::stringify!(#ident)))))
297                    },
298                    None => #if_empty,
299                };
300            },
301            quote!(#field_name)
302        )
303    }).multiunzip();
304
305    let serialize = quote! {
306        impl battler_wamp_values::WampSerialize for #ident {
307            fn wamp_serialize(self) -> core::result::Result<battler_wamp_values::Value, battler_wamp_values::WampSerializeError> {
308                let mut dict = battler_wamp_values::Dictionary::default();
309                #(#field_serializers)*
310                Ok(battler_wamp_values::Value::Dictionary(dict))
311            }
312        }
313    };
314
315    let named = input.fields.is_empty() || input.fields.iter().any(|field| field.ident.is_some());
316    let struct_constructor = if named {
317        quote!(#ident { #(#field_identifiers,)* })
318    } else {
319        quote!(#ident(#(#field_identifiers,)*))
320    };
321    let deserialize = quote! {
322        impl battler_wamp_values::WampDeserialize for #ident {
323            fn wamp_deserialize(value: battler_wamp_values::Value) -> core::result::Result<Self, battler_wamp_values::WampDeserializeError> {
324                let mut dict = match value {
325                    battler_wamp_values::Value::Dictionary(dict) => dict,
326                    _ => return Err(battler_wamp_values::WampDeserializeError::new("value must be a list")),
327                };
328                #(#field_deserializers)*
329                Ok(#struct_constructor)
330            }
331        }
332    };
333
334    quote! {
335        #serialize
336        #deserialize
337    }
338    .into()
339}