oxygengine_ignite_derive/
lib.rs

1extern crate proc_macro;
2
3use lazy_static::lazy_static;
4use oxygengine_ignite_types::*;
5use proc_macro::TokenStream;
6use quote::ToTokens;
7use std::{
8    collections::HashMap,
9    fs::{create_dir_all, write},
10    path::PathBuf,
11    sync::{Arc, RwLock},
12};
13use syn::{
14    parse_macro_input, Attribute, Data, DeriveInput, Expr, Fields, GenericArgument, GenericParam,
15    Lit, Meta, NestedMeta, PathArguments, Type,
16};
17
18lazy_static! {
19    static ref TYPES_DIR: Arc<RwLock<PathBuf>> = {
20        let path = std::env::current_dir()
21            .unwrap()
22            .join("target")
23            .join("ignite")
24            .join("types");
25        if create_dir_all(&path).is_err() {
26            println!(
27                "Could not create Ignite type definitions directory: {:?}",
28                path
29            );
30        }
31        Arc::new(RwLock::new(path))
32    };
33}
34
35fn store_type(item: &IgniteTypeDefinition) {
36    #[cfg(feature = "target-yaml")]
37    store_type_yaml(item);
38    #[cfg(feature = "target-json")]
39    store_type_json(item);
40    #[cfg(feature = "target-ron")]
41    store_type_ron(item);
42    #[cfg(feature = "target-binary")]
43    store_type_binary(item);
44}
45
46#[cfg(feature = "target-yaml")]
47fn store_type_yaml(item: &IgniteTypeDefinition) {
48    let name = match &item.variant {
49        IgniteTypeVariant::StructUnit(item) => item,
50        IgniteTypeVariant::StructNamed(item) => &item.name,
51        IgniteTypeVariant::StructUnnamed(item) => &item.name,
52        IgniteTypeVariant::Enum(item) => &item.name,
53    };
54    let path = TYPES_DIR
55        .read()
56        .unwrap()
57        .join(format!("{}.{}.ignite-type.yaml", item.namespace, name));
58    if let Ok(content) = serde_yaml::to_string(&item) {
59        if write(&path, content).is_err() {
60            println!("Could not save Ignite type definition to file: {:?}", path);
61        }
62    } else {
63        println!("Could not serialize Ignite type definition: {:?}", path);
64    }
65}
66
67#[cfg(feature = "target-json")]
68fn store_type_json(item: &IgniteTypeDefinition) {
69    let name = match &item.variant {
70        IgniteTypeVariant::StructUnit(item) => &item,
71        IgniteTypeVariant::StructNamed(item) => &item.name,
72        IgniteTypeVariant::StructUnnamed(item) => &item.name,
73        IgniteTypeVariant::Enum(item) => &item.name,
74    };
75    let path = TYPES_DIR
76        .read()
77        .unwrap()
78        .join(format!("{}.{}.ignite-type.json", item.namespace, name));
79    #[cfg(feature = "pretty")]
80    let result = serde_json::to_string_pretty(&item);
81    #[cfg(not(feature = "pretty"))]
82    let result = serde_json::to_string(&item);
83    if let Ok(content) = result {
84        if write(&path, content).is_err() {
85            println!("Could not save Ignite type definition to file: {:?}", path);
86        }
87    } else {
88        println!("Could not serialize Ignite type definition: {:?}", path);
89    }
90}
91
92#[cfg(feature = "target-ron")]
93fn store_type_ron(item: &IgniteTypeDefinition) {
94    let name = match &item.variant {
95        IgniteTypeVariant::StructUnit(item) => &item,
96        IgniteTypeVariant::StructNamed(item) => &item.name,
97        IgniteTypeVariant::StructUnnamed(item) => &item.name,
98        IgniteTypeVariant::Enum(item) => &item.name,
99    };
100    let path = TYPES_DIR
101        .read()
102        .unwrap()
103        .join(format!("{}.{}.ignite-type.ron", item.namespace, name));
104    #[cfg(feature = "pretty")]
105    let result = ron::ser::to_string_pretty(&item, Default::default());
106    #[cfg(not(feature = "pretty"))]
107    let result = ron::ser::to_string(&item);
108    if let Ok(content) = result {
109        if write(&path, content).is_err() {
110            println!("Could not save Ignite type definition to file: {:?}", path);
111        }
112    } else {
113        println!("Could not serialize Ignite type definition: {:?}", path);
114    }
115}
116
117#[cfg(feature = "target-binary")]
118fn store_type_binary(item: &IgniteTypeDefinition) {
119    let name = match &item.variant {
120        IgniteTypeVariant::StructUnit(item) => &item,
121        IgniteTypeVariant::StructNamed(item) => &item.name,
122        IgniteTypeVariant::StructUnnamed(item) => &item.name,
123        IgniteTypeVariant::Enum(item) => &item.name,
124    };
125    let path = TYPES_DIR
126        .read()
127        .unwrap()
128        .join(format!("{}.{}.ignite-type.bin", item.namespace, name));
129    if let Ok(content) = bincode::serialize(&item) {
130        if write(&path, content).is_err() {
131            println!("Could not save Ignite type definition to file: {:?}", path);
132        }
133    } else {
134        println!("Could not serialize Ignite type definition: {:?}", path);
135    }
136}
137
138#[derive(Debug, Default)]
139struct IgniteFieldAttribs {
140    pub ignore: bool,
141    pub mapping: Option<String>,
142    pub meta: HashMap<String, IgniteAttribMeta>,
143}
144
145#[derive(Debug, Default)]
146struct IgniteTypeAttribs {
147    pub namespace: Option<String>,
148    pub meta: HashMap<String, IgniteAttribMeta>,
149}
150
151#[proc_macro]
152pub fn ignite_proxy(input: TokenStream) -> TokenStream {
153    derive_ignite_inner(input, true)
154}
155
156#[proc_macro]
157pub fn ignite_alias(input: TokenStream) -> TokenStream {
158    input
159}
160
161#[proc_macro_derive(Ignite, attributes(ignite))]
162pub fn derive_ignite(input: TokenStream) -> TokenStream {
163    derive_ignite_inner(input, false)
164}
165
166fn derive_ignite_inner(input: TokenStream, is_proxy: bool) -> TokenStream {
167    let ast = parse_macro_input!(input as DeriveInput);
168    let attribs = parse_type_attribs(&ast.attrs);
169    let generic_args = ast
170        .generics
171        .params
172        .iter()
173        .filter_map(|param| {
174            if let GenericParam::Type(param) = param {
175                Some(param.ident.to_string())
176            } else {
177                None
178            }
179        })
180        .collect::<Vec<_>>();
181    let name = ast.ident.to_string();
182    let variant = match ast.data {
183        Data::Struct(data) => match data.fields {
184            Fields::Named(fields) => {
185                let fields = fields
186                    .named
187                    .iter()
188                    .filter_map(|field| {
189                        let attribs = parse_field_attribs(&field.attrs);
190                        if attribs.ignore {
191                            return None;
192                        }
193                        let name = field.ident.as_ref().unwrap().to_string();
194                        let type_ = parse_type(&field.ty);
195                        Some(IgniteNamedField {
196                            name,
197                            typename: type_,
198                            mapping: attribs.mapping,
199                            meta: attribs.meta,
200                        })
201                    })
202                    .collect::<Vec<_>>();
203                IgniteTypeVariant::StructNamed(IgniteNamed { name, fields })
204            }
205            Fields::Unnamed(fields) => {
206                let fields = fields
207                    .unnamed
208                    .iter()
209                    .filter_map(|field| {
210                        let attribs = parse_field_attribs(&field.attrs);
211                        if attribs.ignore {
212                            return None;
213                        }
214                        let type_ = parse_type(&field.ty);
215                        Some(IgniteUnnamedField {
216                            typename: type_,
217                            mapping: attribs.mapping,
218                            meta: attribs.meta,
219                        })
220                    })
221                    .collect::<Vec<_>>();
222                IgniteTypeVariant::StructUnnamed(IgniteUnnamed { name, fields })
223            }
224            Fields::Unit => IgniteTypeVariant::StructUnit(name),
225        },
226        Data::Enum(data) => {
227            let variants = data
228                .variants
229                .iter()
230                .map(|variant| {
231                    let name = variant.ident.to_string();
232                    match &variant.fields {
233                        Fields::Named(fields) => {
234                            let fields = fields
235                                .named
236                                .iter()
237                                .filter_map(|field| {
238                                    let attribs = parse_field_attribs(&field.attrs);
239                                    if attribs.ignore {
240                                        return None;
241                                    }
242                                    let name = field.ident.as_ref().unwrap().to_string();
243                                    let type_ = parse_type(&field.ty);
244                                    Some(IgniteNamedField {
245                                        name,
246                                        typename: type_,
247                                        mapping: attribs.mapping,
248                                        meta: attribs.meta,
249                                    })
250                                })
251                                .collect::<Vec<_>>();
252                            IgniteVariant::Named(IgniteNamed { name, fields })
253                        }
254                        Fields::Unnamed(fields) => {
255                            let fields = fields
256                                .unnamed
257                                .iter()
258                                .filter_map(|field| {
259                                    let attribs = parse_field_attribs(&field.attrs);
260                                    if attribs.ignore {
261                                        return None;
262                                    }
263                                    let type_ = parse_type(&field.ty);
264                                    Some(IgniteUnnamedField {
265                                        typename: type_,
266                                        mapping: attribs.mapping,
267                                        meta: attribs.meta,
268                                    })
269                                })
270                                .collect::<Vec<_>>();
271                            IgniteVariant::Unnamed(IgniteUnnamed { name, fields })
272                        }
273                        Fields::Unit => IgniteVariant::Unit(name),
274                    }
275                })
276                .collect::<Vec<_>>();
277            IgniteTypeVariant::Enum(IgniteEnum { name, variants })
278        }
279        _ => panic!("Ignite can be derived only for structs and enums"),
280    };
281    let definition = IgniteTypeDefinition {
282        namespace: if let Some(namespace) = attribs.namespace {
283            namespace
284        } else {
285            std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| env!("CARGO_PKG_NAME").to_owned())
286        },
287        generic_args,
288        variant,
289        meta: attribs.meta,
290        is_proxy,
291    };
292    store_type(&definition);
293    TokenStream::new()
294}
295
296fn parse_type_attribs(attrs: &[Attribute]) -> IgniteTypeAttribs {
297    let mut result = IgniteTypeAttribs::default();
298    for attrib in attrs {
299        match attrib.parse_meta() {
300            Err(error) => panic!(
301                "Could not parse ignite attribute `{}`: {:?}",
302                attrib.to_token_stream(),
303                error
304            ),
305            Ok(Meta::List(meta)) => {
306                if meta.path.is_ident("ignite") {
307                    for meta in meta.nested {
308                        if let NestedMeta::Meta(Meta::NameValue(meta)) = &meta {
309                            if meta.path.is_ident("namespace") {
310                                if let Lit::Str(value) = &meta.lit {
311                                    result.namespace = Some(value.value());
312                                }
313                            } else if let Some(ident) = meta.path.get_ident() {
314                                let value = match &meta.lit {
315                                    Lit::Str(value) => IgniteAttribMeta::String(value.value()),
316                                    Lit::Int(value) => IgniteAttribMeta::Integer(
317                                        value.base10_parse::<i64>().unwrap(),
318                                    ),
319                                    Lit::Float(value) => IgniteAttribMeta::Float(
320                                        value.base10_parse::<f64>().unwrap(),
321                                    ),
322                                    Lit::Bool(value) => IgniteAttribMeta::Bool(value.value),
323                                    _ => IgniteAttribMeta::None,
324                                };
325                                result.meta.insert(ident.to_string(), value);
326                            }
327                        }
328                    }
329                }
330            }
331            _ => {}
332        }
333    }
334    result
335}
336
337fn parse_field_attribs(attrs: &[Attribute]) -> IgniteFieldAttribs {
338    let mut result = IgniteFieldAttribs::default();
339    for attrib in attrs {
340        match attrib.parse_meta() {
341            Err(error) => panic!(
342                "Could not parse ignite attribute `{}`: {:?}",
343                attrib.to_token_stream(),
344                error
345            ),
346            Ok(Meta::List(meta)) => {
347                if meta.path.is_ident("ignite") {
348                    for meta in meta.nested {
349                        if let NestedMeta::Meta(meta) = &meta {
350                            if let Meta::Path(path) = meta {
351                                if path.is_ident("ignore") {
352                                    result.ignore = true;
353                                } else if let Some(ident) = path.get_ident() {
354                                    result
355                                        .meta
356                                        .insert(ident.to_string(), IgniteAttribMeta::None);
357                                }
358                            } else if let Meta::NameValue(meta) = meta {
359                                if meta.path.is_ident("mapping") {
360                                    if let Lit::Str(value) = &meta.lit {
361                                        result.mapping = Some(value.value());
362                                    }
363                                } else if let Some(ident) = meta.path.get_ident() {
364                                    let value = match &meta.lit {
365                                        Lit::Str(value) => IgniteAttribMeta::String(value.value()),
366                                        Lit::Int(value) => IgniteAttribMeta::Integer(
367                                            value.base10_parse::<i64>().unwrap(),
368                                        ),
369                                        Lit::Float(value) => IgniteAttribMeta::Float(
370                                            value.base10_parse::<f64>().unwrap(),
371                                        ),
372                                        Lit::Bool(value) => IgniteAttribMeta::Bool(value.value),
373                                        _ => IgniteAttribMeta::None,
374                                    };
375                                    result.meta.insert(ident.to_string(), value);
376                                }
377                            }
378                        }
379                    }
380                }
381            }
382            _ => {}
383        }
384    }
385    result
386}
387
388fn parse_type(type_: &Type) -> IgniteType {
389    match type_ {
390        Type::Path(path) => {
391            let segment = path.path.segments.last().unwrap();
392            let name = segment.ident.to_string();
393            match &segment.arguments {
394                PathArguments::None => IgniteType::Atom(name),
395                PathArguments::AngleBracketed(arguments) => {
396                    let arguments = arguments.args.iter().filter_map(|arg| {
397                        match arg {
398                            GenericArgument::Type(ty) => Some(parse_type(ty)),
399                            _ => None,
400                        }
401                    }).collect::<Vec<_>>();
402                    IgniteType::Generic(IgniteTypeGeneric{name, arguments})
403                }
404                _ => panic!(
405                    "Ignite requires owned types of either unit, atom, tuple, array or generic. This type does not parse: {}",
406                    type_.to_token_stream(),
407                ),
408            }
409        }
410        Type::Tuple(tuple) => {
411            if tuple.elems.is_empty() {
412                IgniteType::Unit
413            } else {
414                let elems = tuple.elems.iter().map(parse_type).collect::<Vec<_>>();
415                IgniteType::Tuple(elems)
416            }
417        }
418        Type::Array(array) => {
419            let typename = Box::new(parse_type(&array.elem));
420            let size = match &array.len {
421                Expr::Lit(lit) => {
422                    match &lit.lit {
423                        Lit::Int(lit) => lit.base10_parse::<usize>().unwrap(),
424                        _ => panic!(
425                            "Ignite requires owned types of either unit, atom, tuple, array or generic. This type does not parse: {}",
426                            type_.to_token_stream(),
427                        ),
428                    }
429                }
430                _ => panic!(
431                    "Ignite requires owned types of either unit, atom, tuple, array or generic. This type does not parse: {}",
432                    type_.to_token_stream(),
433                ),
434            };
435            IgniteType::Array(IgniteTypeArray{typename,size})
436        }
437        _ => panic!(
438            "Ignite requires owned types of either unit, atom, tuple, array or generic. This type does not parse: {}",
439            type_.to_token_stream(),
440        ),
441    }
442}