alisa_proc_macros/
lib.rs

1
2use quote::{quote, ToTokens};
3use syn::{parse_macro_input, Attribute, DataStruct, DeriveInput, Ident, Generics};
4
5fn has_attr(attrs: &Vec<Attribute>, query: &str) -> bool {
6    attrs.iter().any(|attr| attr.path().is_ident(query))
7}
8
9fn serializable_struct(strct: DataStruct, name: Ident, project_type: Option<syn::Type>, generics: Generics) -> proc_macro2::TokenStream {
10
11    // TODO: ensure struct fields are named
12
13    let serializable_fields = strct.fields.iter().filter(|field| !has_attr(&field.attrs, "no_serialize"));
14    let serializable_field_names = serializable_fields.clone().map(|field| field.ident.as_ref().unwrap());
15    let serializable_field_types = serializable_fields.clone().map(|field| field.ty.to_token_stream()); 
16    let serializable_field_names_2 = serializable_field_names.clone(); 
17    let impl_generic = if project_type.is_none() {
18        quote! { <P: alisa::Project, #generics > }
19    } else {
20        generics.to_token_stream()
21    };
22    let context_generic = if let Some(project_type) = project_type {
23        quote! { <#project_type> }
24    } else {
25        quote! { <P> }
26    };
27    let generics_names = generics.type_params().map(|param| &param.ident);
28
29    quote! {
30        impl #impl_generic alisa::Serializable #context_generic for #name <#(#generics_names, )*> {
31
32            fn deserialize(data: &alisa::rmpv::Value, context: &mut alisa::DeserializationContext #context_generic) -> Option<Self> {
33                let mut result = Self::default();
34                #(
35                    if let Some(value) = alisa::rmpv_get(data, stringify!(#serializable_field_names_2)) {
36                        if let Some(value) = <#serializable_field_types>::deserialize(value, context) { 
37                            result.#serializable_field_names_2 = value;
38                        }
39                    }
40                )*
41                Some(result)
42            }
43
44            fn serialize(&self, context: &alisa::SerializationContext #context_generic) -> alisa::rmpv::Value {
45                alisa::rmpv::Value::Map(vec![
46                    #((stringify!(#serializable_field_names).into(), self.#serializable_field_names.serialize(context)), )*
47                ])
48            }
49
50        }
51    }
52}
53
54#[proc_macro_derive(Serializable, attributes(project, no_serialize))]
55pub fn serializable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
56    let input = parse_macro_input!(input as DeriveInput);
57    
58    let project_attribute = input.attrs.iter().filter(
59        |a| a.path().segments.len() == 1 && a.path().segments[0].ident == "project"
60    ).nth(0);
61    let project_type = project_attribute.map(|attr| attr.parse_args::<syn::Type>().expect("expected project type!"));
62
63    match input.data {
64        syn::Data::Struct(data_struct) => serializable_struct(data_struct, input.ident, project_type, input.generics),
65        syn::Data::Enum(_data_enum) => todo!(),
66        syn::Data::Union(_data_union) => panic!("cannot serialize union."),
67    }.into()
68}
69