1use proc_macro::{Span, TokenStream};
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, Ident, LitStr};
4
5fn match_path(path: &syn::Path, ident: &str) -> bool {
6 path.is_ident(&Ident::new(ident, Span::call_site().into()))
7}
8
9fn to_pascal_case(ident: &Ident) -> LitStr {
10 let ident = ident
11 .to_string()
12 .split('_')
13 .into_iter()
14 .map(|segment| {
15 let mut v = segment.chars().collect::<Vec<_>>();
16 v[0] = v[0].to_uppercase().nth(0).unwrap();
17 v.into_iter().collect::<String>()
18 })
19 .collect::<String>();
20 LitStr::new(&ident, Span::call_site().into())
21}
22
23fn has_attr(attrs: &Vec<syn::Attribute>, name: &str) -> bool {
24 attrs.iter().any(|attr| match_path(&attr.path, name))
25}
26
27#[proc_macro_derive(Inherits)]
28pub fn inherits(item: TokenStream) -> TokenStream {
29 let item = parse_macro_input!(item as DeriveInput);
30 let item_name = &item.ident;
31
32 let fields = match &item.data {
33 syn::Data::Struct(data) => &data.fields,
34 _ => panic!("Inherits not supported on non-structs"),
35 };
36 let named_fields = match fields {
37 syn::Fields::Named(fields) => fields,
38 _ => panic!("Inherits requires named fields"),
39 };
40
41 let field = named_fields.named.first().unwrap();
42
43 let (target_ty, target_name) = (&field.ty, field.ident.as_ref().unwrap());
44
45 let expanded = quote!(
46 impl core::ops::Deref for #item_name {
47 type Target = #target_ty;
48
49 fn deref(&self) -> &Self::Target {
50 &self.#target_name
51 }
52 }
53
54 impl core::ops::DerefMut for #item_name {
55 fn deref_mut(&mut self) -> &mut Self::Target {
56 &mut self.#target_name
57 }
58 }
59 );
60
61 TokenStream::from(expanded)
62}
63
64#[proc_macro_derive(PropertyConvert, attributes(shared, propname))]
65pub fn property_convert(item: TokenStream) -> TokenStream {
66 let item = parse_macro_input!(item as DeriveInput);
67 let item_name = &item.ident;
68
69 let fields = match &item.data {
70 syn::Data::Struct(data) => &data.fields,
71 _ => panic!("PropertyConvert not supported on non-structs"),
72 };
73 let named_fields = match fields {
74 syn::Fields::Named(fields) => fields,
75 _ => panic!("PropertyConvert requires named fields"),
76 };
77
78 let (constructor, destructor): (Vec<_>, Vec<_>) = named_fields
79 .named
80 .iter()
81 .map(|field| {
82 let field_name = &field.ident;
83 let shared = has_attr(&field.attrs, "shared");
84 let prop_name = field.attrs.iter().find(|attr| match_path(&attr.path, "propname")).map(|attr| {
85 let meta = if let syn::Meta::NameValue(value) = attr.parse_meta().unwrap() {
86 value
87 } else {
88 panic!()
89 };
90 if let syn::Lit::Str(lit) = meta.lit {
91 lit
92 } else {
93 panic!()
94 }
95 }).unwrap_or(to_pascal_case(&field.ident.as_ref().unwrap()));
96
97 let (getter, setter) = (
98 quote!(
99 crate::serde::internal::FieldFromProperties::from_properties(
100 crate::serde::internal::FieldAttrs { field_name: stringify!(#field_name), prop_name: #prop_name, shared: #shared },
101 properties,
102 )?
103 ),
104 quote!(
105 crate::serde::internal::FieldToProperties::to_properties(
106 self.#field_name.clone(),
107 crate::serde::internal::FieldAttrs { field_name: stringify!(#field_name), prop_name: #prop_name, shared: #shared },
108 properties,
109 );
110 ),
111 );
112
113 (quote!(#field_name: #getter), quote!(#setter))
114 })
115 .unzip();
116
117 let expanded = quote! {
118 impl FromProperties for #item_name {
119 fn from_properties(properties: &mut alloc::collections::BTreeMap<String, Property>) -> core::result::Result<Self, crate::SerdeError> {
120 Ok(Self {
121 #(#constructor),*
122 })
123 }
124 }
125
126 impl ToProperties for #item_name {
127 fn to_properties(&self, properties: &mut alloc::collections::BTreeMap<String, Property>) {
128 #(#destructor;)*
129 }
130 }
131 };
132
133 TokenStream::from(expanded)
134}
135
136#[proc_macro_derive(EnumConvert)]
137pub fn enum_convert(item: TokenStream) -> TokenStream {
138 let item = parse_macro_input!(item as DeriveInput);
139 let item_name = &item.ident;
140
141 let variants = match &item.data {
142 syn::Data::Enum(data) => &data.variants,
143 _ => panic!("TryFrom not supported on non-enums"),
144 };
145
146 let mut last_discrim = 0;
147
148 let variant_match = variants
149 .iter()
150 .map(|var| {
151 let var_name = &var.ident;
152 let discrim: i32 = var
153 .discriminant
154 .as_ref()
155 .map(|(_, expr)| match expr {
156 syn::Expr::Lit(expr_lit) => {
157 if let syn::Lit::Int(val) = &expr_lit.lit {
158 val.base10_parse().unwrap()
159 } else {
160 panic!("Discriminant wasn't an integer literal")
161 }
162 }
163 _ => panic!("Discriminant wasn't a literal value"),
164 })
165 .unwrap_or_else(|| last_discrim + 1);
166
167 last_discrim = discrim;
168
169 quote!( #discrim => Ok(Self::#var_name) )
170 })
171 .collect::<Vec<_>>();
172
173 let expanded = quote! {
174 impl core::convert::TryFrom<i32> for #item_name {
175 type Error = ();
176
177 fn try_from(val: i32) -> core::result::Result<Self, ()> {
178 match val {
179 #(#variant_match,)*
180 _ => Err(()),
181 }
182 }
183 }
184
185 impl core::convert::From<#item_name> for i32 {
186 fn from(i: #item_name) -> Self {
187 i as i32
188 }
189 }
190
191 impl crate::serde::internal::FieldFromProperties for #item_name {
192 fn from_properties(
193 attrs: crate::serde::internal::FieldAttrs,
194 properties: &mut alloc::collections::BTreeMap<alloc::string::String, crate::model::Property>
195 ) -> crate::serde::error::Result<Self> {
196 match properties.remove(attrs.prop_name) {
197 Some(crate::model::Property::Enum(val)) => Self::try_from(val)
198 .map_err(|_| crate::SerdeError::unknown_variant(val)),
199 Some(prop) => Err(crate::SerdeError::wrong_property_type(
200 alloc::string::String::from(attrs.prop_name),
201 Some((crate::model::property::PropertyType::Enum, prop.kind())),
202 )),
203 None => Err(crate::SerdeError::missing_property(alloc::string::String::from(attrs.prop_name))),
204 }
205 }
206 }
207
208 impl crate::serde::internal::FieldToProperties for #item_name {
209 fn to_properties(
210 self,
211 attrs: crate::serde::internal::FieldAttrs,
212 properties: &mut alloc::collections::BTreeMap<alloc::string::String, crate::model::Property>
213 ) {
214 properties.insert(alloc::string::String::from(attrs.prop_name), crate::model::Property::Enum(self.into()));
215 }
216 }
217 };
218
219 TokenStream::from(expanded)
220}
221
222struct InstanceResult {
223 class_name: proc_macro2::TokenStream,
224 name: proc_macro2::TokenStream,
225 from_props: proc_macro2::TokenStream,
226 to_props: proc_macro2::TokenStream,
227}
228
229impl InstanceResult {
230 fn unzip(
231 results: Vec<InstanceResult>,
232 ) -> (
233 Vec<proc_macro2::TokenStream>,
234 Vec<proc_macro2::TokenStream>,
235 Vec<proc_macro2::TokenStream>,
236 Vec<proc_macro2::TokenStream>,
237 ) {
238 results.into_iter().fold(
239 (Vec::new(), Vec::new(), Vec::new(), Vec::new()),
240 |(mut classes, mut names, mut from_props, mut to_props), this| {
241 classes.push(this.class_name);
242 names.push(this.name);
243 from_props.push(this.from_props);
244 to_props.push(this.to_props);
245 (classes, names, from_props, to_props)
246 },
247 )
248 }
249}
250
251#[proc_macro_derive(InstanceExtra)]
252pub fn instance_extra(item: TokenStream) -> TokenStream {
253 let item = parse_macro_input!(item as DeriveInput);
254 let item_name = &item.ident;
255
256 let variants = match &item.data {
257 syn::Data::Enum(data) => &data.variants,
258 _ => panic!("InstanceExtra not supported on non-enums"),
259 };
260
261 let results = variants
262 .iter()
263 .map(|variant| {
264 let variant_name = &variant.ident;
265
266 if &variant_name.to_string() == "Other" {
268 return InstanceResult {
269 class_name: quote!(#item_name::Other(class_name, _) => return class_name.clone()),
270 name: quote!(#item_name::Other(_, attrs) => {
271 if let Property::TextString(name) = attrs.get("Name").expect("Instance didn't have a name") {
272 name
273 } else {
274 panic!("Instance didn't have a Name")
275 }
276 }),
277 from_props: quote!(_ => {
278 let kind = Instance::Other(
281 String::from(kind),
282 properties
283 .iter()
284 .map(|(key, val)| (key.clone(), val.clone()))
285 .collect(),
286 );
287 properties.clear();
288 kind
289 }),
290 to_props: quote!(#item_name::Other(_, attrs) => properties.extend(attrs.clone())),
291 };
292 }
293
294 let fields = if let syn::Fields::Unnamed(fields) = &variant.fields {
295 fields
296 } else {
297 panic!("Expected unnamed enum fields");
298 };
299
300 let class_name_str = variant_name.to_string();
301 let is_boxed = match &fields.unnamed[0].ty {
302 syn::Type::Path(path) => path.path.segments[0].ident.to_string() == "Box",
303 _ => panic!("Unexpected type in Variant"),
304 };
305
306 let class_name = quote!(#item_name::#variant_name(..) => #class_name_str);
307 let name = quote!(#item_name::#variant_name(data) => &data.name);
308 let from_props = if is_boxed {
309 quote!(#class_name_str => #item_name::#variant_name(alloc::boxed::Box::new(#variant_name::from_properties(&mut properties)?)))
310 } else {
311 quote!(#class_name_str => #item_name::#variant_name(#variant_name::from_properties(&mut properties)?))
312 };
313 let to_props = quote!(#item_name::#variant_name(data) => data.to_properties(&mut properties));
314
315 InstanceResult {
316 class_name,
317 name,
318 from_props,
319 to_props
320 }
321 })
322 .collect();
323
324 let (class_names, names, from_props, to_props) = InstanceResult::unzip(results);
325
326 let expanded = quote! {
327 impl #item_name {
328 #[must_use]
330 pub fn class_name(&self) -> String {
331 String::from(match self {
332 #(#class_names),*
333 })
334 }
335
336 #[must_use]
342 pub fn name(&self) -> &str {
343 match self {
344 #(#names),*
345 }
346 }
347
348 pub(crate) fn make_instance(kind: &str, mut properties: BTreeMap<String, Property>) -> Result<Instance, crate::SerdeError> {
349 let out = match kind {
350 #(#from_props),*
351 };
352
353 if properties.is_empty() {
354 Ok(out)
355 } else {
356 Err(crate::SerdeError::unconsumed_properties(
357 out.class_name(),
358 properties.into_iter().map(|(keys, _)| keys).collect(),
359 ))
360 }
361 }
362
363 pub(crate) fn break_instance(&self) -> BTreeMap<String, Property> {
364 let mut properties = BTreeMap::new();
365 match self {
366 #(#to_props),*
367 }
368 properties
369 }
370 }
371 };
372
373 TokenStream::from(expanded)
374}