diff --git a/immediate_stats_macros/src/derive_enum.rs b/immediate_stats_macros/src/derive_enum.rs new file mode 100644 index 0000000..4deeeac --- /dev/null +++ b/immediate_stats_macros/src/derive_enum.rs @@ -0,0 +1,73 @@ +use crate::FieldOptions; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{DataEnum, Ident, Variant}; + +/// Returns a match statement that can be used to reset an enum. +pub fn reset_enum(body: &DataEnum) -> TokenStream { + body.variants + .iter() + .map(|variant| { + let cases = reset_variant(&variant); + quote! { + match self { + #cases + _ => {} + } + } + }) + .flatten() + .collect() +} + +/// Returns a case that can be used to reset the variant. +/// If there are no stat fields, the result will be empty. +fn reset_variant(variant: &Variant) -> TokenStream { + // List of all identifiers that need to be reset. + let names: Vec = variant + .fields + .iter() + .enumerate() + .filter_map(|(index, field)| { + let options = FieldOptions::from_field(field); + + if options.is_stat() { + return Some(match options.ident { + Some(ident) => ident, + None => get_ident_from_index(index), + }); + } + + None + }) + .collect(); + + if names.is_empty() { + return TokenStream::new(); + } + + let is_named = variant.fields.iter().next().unwrap().ident.is_some(); + let ident = &variant.ident; + + if is_named { + quote! { + Self::#ident { #(#names,)* .. } => { + #(#names.reset_modifiers();)* + }, + } + } else { + quote! { + Self::#ident ( #(#names,)* .. ) => { + #(#names.reset_modifiers();)* + }, + } + } +} + +/// Generates an alphabetic identifier from an index. +fn get_ident_from_index(index: usize) -> Ident { + Ident::new( + format!("{}", ('a' as u8 + index as u8) as char).as_str(), + Span::call_site(), + ) +} diff --git a/immediate_stats_macros/src/derive_struct.rs b/immediate_stats_macros/src/derive_struct.rs new file mode 100644 index 0000000..4f17f63 --- /dev/null +++ b/immediate_stats_macros/src/derive_struct.rs @@ -0,0 +1,35 @@ +use crate::FieldOptions; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{DataStruct, Field, Index}; + +/// Returns the code that can be used to reset a struct's stat fields. +pub fn reset_struct(body: &DataStruct) -> TokenStream { + body.fields + .iter() + .enumerate() + .map(|(index, field)| reset_struct_field(field, index)) + .flatten() + .collect() +} + +/// Returns the method call that can be used to reset a stat field. +/// If the field is not a stat, the result will be empty. +/// The `index` is used for tuple/unnamed fields. +fn reset_struct_field(field: &Field, index: usize) -> TokenStream { + let options = FieldOptions::from_field(&field); + + if options.is_stat() { + return match options.ident { + Some(ident) => { + quote! { self.#ident.reset_modifiers(); } + } + None => { + let index = Index::from(index); + quote! { self.#index.reset_modifiers(); } + } + }; + } + + TokenStream::new() +} diff --git a/immediate_stats_macros/src/lib.rs b/immediate_stats_macros/src/lib.rs index 556dbc1..1b2a3a4 100644 --- a/immediate_stats_macros/src/lib.rs +++ b/immediate_stats_macros/src/lib.rs @@ -1,12 +1,13 @@ #[cfg(feature = "bevy_butler")] mod bevy_butler; +mod derive_enum; +mod derive_struct; -use darling::{Error, FromField}; +use darling::Error; use proc_macro_error::{emit_call_site_error, emit_warning, proc_macro_error}; -use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, quote}; use syn::spanned::Spanned; -use syn::{Data, DeriveInput, Field, Fields, Ident, Index, Variant, parse_macro_input}; +use syn::{Data, DeriveInput, Field, Ident, parse_macro_input}; #[proc_macro_derive(StatContainer, attributes(stat, stat_ignore, add_component))] #[proc_macro_error] @@ -15,8 +16,8 @@ pub fn stat_container_derive(item: proc_macro::TokenStream) -> proc_macro::Token let ident = &tree.ident; let method_contents = match tree.data.clone() { - Data::Struct(s) => reset_struct(&s.fields), - Data::Enum(e) => reset_variants(e.variants.iter()), + Data::Struct(s) => derive_struct::reset_struct(&s), + Data::Enum(e) => derive_enum::reset_enum(&e), Data::Union(_) => { emit_call_site_error!("This trait cannot be derived from unions."); return proc_macro::TokenStream::new(); @@ -41,11 +42,15 @@ pub fn stat_container_derive(item: proc_macro::TokenStream) -> proc_macro::Token method.into() } +/// Represents the options that a field could have. #[derive(Default)] struct FieldOptions { ident: Option, + /// True if the field's type contains the word "Stat". stat_type: bool, + /// True if the field has the `#[stat]` attribute. include: bool, + /// True if the field has the `#[stat_ignore]` attribute. exclude: bool, } @@ -62,10 +67,8 @@ impl FieldOptions { (self.include || self.stat_type) && !self.exclude } -} -impl FromField for FieldOptions { - fn from_field(field: &Field) -> darling::Result { + fn from_field(field: &Field) -> Self { let mut options = FieldOptions { ident: field.ident.clone(), ..Self::default() @@ -84,107 +87,6 @@ impl FromField for FieldOptions { } } - Ok(options) + options } } - -/// Returns the code that can be used to reset a struct's stat fields. -fn reset_struct(fields: &Fields) -> TokenStream { - fields - .iter() - .enumerate() - .map(|(index, field)| reset_struct_field(field, index)) - .flatten() - .collect() -} - -/// Returns the method call that can be used to reset a stat field. -/// If the field is not a stat, the result will be empty. -/// The `index` is used for tuple/unnamed fields. -fn reset_struct_field(field: &Field, index: usize) -> TokenStream { - let options = FieldOptions::from_field(&field).unwrap(); - - if options.is_stat() { - return match options.ident { - Some(ident) => { - quote! { self.#ident.reset_modifiers(); } - } - None => { - let index = Index::from(index); - quote! { self.#index.reset_modifiers(); } - } - }; - } - - TokenStream::new() -} - -/// Returns a match statement that can be used to reset an enum. -fn reset_variants<'a, T>(variants: T) -> TokenStream -where - T: Iterator, -{ - variants - .map(|variant| { - let cases = reset_variant(&variant); - quote! { - match self { - #cases - _ => {} - } - } - }) - .flatten() - .collect() -} - -/// Returns a case that can be used to reset the variant. -/// If there are no stat fields, the result will be empty. -fn reset_variant(variant: &Variant) -> TokenStream { - let names: Vec = variant - .fields - .iter() - .enumerate() - .filter_map(|(index, field)| { - let options = FieldOptions::from_field(field).unwrap(); - - if options.is_stat() { - return Some(match options.ident { - Some(ident) => ident, - None => get_ident_from_index(index), - }); - } - - None - }) - .collect(); - - if names.is_empty() { - return TokenStream::new(); - } - - let is_named = variant.fields.iter().next().unwrap().ident.is_some(); - let ident = &variant.ident; - - if is_named { - quote! { - Self::#ident { #(#names,)* .. } => { - #(#names.reset_modifiers();)* - }, - } - } else { - quote! { - Self::#ident ( #(#names,)* .. ) => { - #(#names.reset_modifiers();)* - }, - } - } -} - -/// Generates an alphabetic identifier from an index. -fn get_ident_from_index(index: usize) -> Ident { - Ident::new( - format!("{}", ('a' as u8 + index as u8) as char).as_str(), - Span::call_site(), - ) -}