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 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| ¶m.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