oxygengine_ignite_derive/
lib.rs1extern 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}