caretta_framework_macros/
lib.rs

1mod derive;
2
3use derive::*;
4use proc_macro::{self, TokenStream};
5use quote::quote;
6use syn::{Data, DeriveInput, Expr, Fields, FieldsNamed, Ident, parse_macro_input};
7
8fn extract_unique_field_ident<'a>(
9    fields: &'a FieldsNamed,
10    attribute_arg: &'static str,
11) -> &'a Ident {
12    let mut fields = extract_field_idents(fields, attribute_arg);
13    if fields.len() == 1 {
14        return fields.pop().unwrap();
15    } else {
16        panic!("Model must need one {} field attribute", attribute_arg);
17    };
18}
19
20fn extract_field_idents<'a>(
21    fields: &'a FieldsNamed,
22    attribute_arg: &'static str,
23) -> Vec<&'a Ident> {
24    fields
25        .named
26        .iter()
27        .filter_map(|field| {
28            field.attrs.iter().find_map(|attr| {
29                if attr.path().is_ident("syncable") {
30                    let args: Expr = attr.parse_args().unwrap();
31
32                    match args {
33                        Expr::Tuple(arg_tupple) => arg_tupple.elems.iter().find_map(|arg| {
34                            if let Expr::Path(arg_path) = arg {
35                                if arg_path.path.is_ident(attribute_arg) {
36                                    Some(field.ident.as_ref().unwrap())
37                                } else {
38                                    None
39                                }
40                            } else {
41                                None
42                            }
43                        }),
44                        Expr::Path(arg_path) => {
45                            if arg_path.path.is_ident(attribute_arg) {
46                                Some(field.ident.as_ref().unwrap())
47                            } else {
48                                None
49                            }
50                        }
51                        _ => None,
52                    }
53                } else {
54                    None
55                }
56            })
57        })
58        .collect()
59}
60
61fn extract_fields(data: &Data) -> &FieldsNamed {
62    match *data {
63        Data::Struct(ref data) => match data.fields {
64            Fields::Named(ref fields) => fields,
65            _ => panic!("all fields must be named."),
66        },
67        _ => panic!("struct expected, but got other item."),
68    }
69}
70
71#[proc_macro_derive(Emptiable)]
72pub fn emptiable(input: TokenStream) -> TokenStream {
73    let input = parse_macro_input!(input as DeriveInput);
74    let type_ident = input.ident;
75    match input.data {
76        Data::Struct(ref data) => {
77            let field_idents = extract_idents_and_types_from_data_struct(data);
78            let is_empty_iter = field_idents.iter().map(|(ident, type_name)| {
79                quote! {
80                    <#type_name as Emptiable>::is_empty(&self.#ident)
81                }
82            });
83            let empty_iter = field_idents.iter().map(|(ident, type_name)| {
84                quote! {
85                    #ident: <#type_name as Emptiable>::empty(),
86                }
87            });
88            quote! {
89                impl Emptiable for #type_ident {
90                    fn empty() -> Self {
91                        Self {
92                            #(#empty_iter)*
93                        }
94                    }
95                    fn is_empty(&self) -> bool {
96                        #(#is_empty_iter)&&*
97                    }
98                }
99            }
100            .into()
101        }
102        _ => panic!("struct or expected, but got other type."),
103    }
104}
105
106#[proc_macro_derive(Mergeable)]
107pub fn mergeable(input: TokenStream) -> TokenStream {
108    let input = parse_macro_input!(input as DeriveInput);
109    let type_ident = input.ident;
110    match input.data {
111        Data::Struct(ref data) => {
112            let field_idents = extract_idents_and_types_from_data_struct(data);
113            let merge_iter = field_idents.iter().map(|(ident, type_name)| {
114                quote! {
115                    <#type_name as Mergeable>::merge(&mut self.#ident, other.#ident);
116                }
117            });
118            quote! {
119                impl Mergeable for #type_ident {
120                    fn merge(&mut self, mut other: Self){
121                        #(#merge_iter)*
122                    }
123                }
124            }
125            .into()
126        }
127        _ => panic!("struct expected, but got other type."),
128    }
129}
130
131#[proc_macro_derive(RunnableCommand, attributes(runnable_command))]
132pub fn runnable_command(input: TokenStream) -> TokenStream {
133    let input = parse_macro_input!(input as DeriveInput);
134    let type_ident = input.ident;
135    match input.data {
136        Data::Struct(ref data) => {
137            let idents =
138                extract_idents_and_types_from_data_struct_with_attribute(data, "runnable_command");
139            let (field_ident, field_type) = unwrap_vec_or_panic(
140                idents,
141                "RunnableCommand struct must have one field with runnable attribute",
142            );
143
144            quote! {
145                impl RunnableCommand for #type_ident {
146                    fn run(self, app_name: &'static str) {
147                        <#field_type as RunnableCommand>::run(self.#field_ident, app_name)
148                    }
149                }
150            }
151            .into()
152        }
153        Data::Enum(ref variants) => {
154            let quote_vec = extract_idents_and_types_from_enum_struct(&variants);
155            let quote_iter = quote_vec.iter().map(|(variant_ident, variant_type)| {
156                quote! {
157                    Self::#variant_ident(x) => <#variant_type as RunnableCommand>::run(x, app_name),
158                }
159            });
160            quote! {
161                impl RunnableCommand for #type_ident {
162                    fn run(self, app_name: &'static str) {
163                        match self {
164                            #(#quote_iter)*
165                        }
166                    }
167                }
168            }
169            .into()
170        }
171        _ => panic!("struct or enum expected, but got union."),
172    }
173}