diff --git a/lib/enum_macro/src/enum_with_eq.rs b/lib/enum_macro/src/enum_with_eq.rs new file mode 100644 index 00000000..f42db784 --- /dev/null +++ b/lib/enum_macro/src/enum_with_eq.rs @@ -0,0 +1,67 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::Data::Enum; +use syn::{DeriveInput, parse_macro_input}; +use syn::__private::TokenStream2; +use crate::helper::{get_number_value, is_numeric}; + +pub fn with_eq(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let enum_name = &input.ident; + + if let Enum(enum_data) = &input.data { + let arms = enum_data.variants.iter().map(|variant| { + let variant_name = variant.ident.clone(); + match &variant.fields { + syn::Fields::Unnamed(fields) => { + let variant_offset = if let Some(_) = variant.attrs.iter().find(|attr| { + attr.path().is_ident("value_comparison_offset") + }) { + get_number_value(variant, "value_comparison_offset").unwrap_or(0) + } else { + 0 + }; + let mut args1 = fields.unnamed.iter().map(|_| quote! {_}).collect::>(); + let mut args2 = fields.unnamed.iter().map(|_| quote! {_}).collect::>(); + let mut should_deref = false; + if args1.len() > 1 { + args1[variant_offset] = quote! {variant1}; + args2[variant_offset] = quote! {variant2}; + should_deref = is_numeric(&fields.unnamed[variant_offset]); + } + // let field_types = fields.unnamed.iter().map(|field| &field.ty); + if fields.unnamed.len() > 1 { + if should_deref { + quote! {(#enum_name::#variant_name(#(#args1,)*), #enum_name::#variant_name(#(#args2,)*)) => *variant1 == *variant2, } + } else { + quote! {(#enum_name::#variant_name(#(#args1,)*), #enum_name::#variant_name(#(#args2,)*)) => variant1 == variant2, } + } + } else { + quote! {(#enum_name::#variant_name(#(#args1)*), #enum_name::#variant_name(#(#args2)*)) => true, } + } + } + syn::Fields::Unit => { + quote! { + (#enum_name::#variant_name, #enum_name::#variant_name) => true, + } + } + syn::Fields::Named(_) => { + // Handling named fields if necessary + panic!("Named fields are not supported in this macro") + } + } + }); + TokenStream::from(quote! { + impl PartialEq for #enum_name { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + #(#arms)* + _ => false + } + } + } + }) + } else { + TokenStream::from(quote! {}) + } +} \ No newline at end of file diff --git a/lib/enum_macro/src/enum_with_mask.rs b/lib/enum_macro/src/enum_with_mask.rs new file mode 100644 index 00000000..1acabf76 --- /dev/null +++ b/lib/enum_macro/src/enum_with_mask.rs @@ -0,0 +1,86 @@ + +macro_rules! with_mask { + ($function:ident, $trait:ident, $macro:ident, $type:ty, $max_bits:expr) => { + + #[proc_macro_derive($macro, attributes(mask_value, mask_all))] + pub fn $function(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let enum_name = &input.ident; + fn count_number_of_1_bits(value: $type) -> usize { + let mut k: usize = 0; + // count number of 1 bits in value + loop { + if k > $max_bits { + break; + } + if value & (1 << k) != 0 { + return k; + } + k += 1; + } + 0 + } + let res = if let Enum(enum_data) = &input.data { + let mut i: usize = 0; + let from_value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { + let variant_name = variant.ident.clone(); + let maybe_value = crate::helper::get_number_value::<$type>(variant, "mask_value"); + let is_all_value = crate::helper::is_all_value(variant, "mask_all"); + let j = if let Some(value) = maybe_value { + i = count_number_of_1_bits(value); + value + } else if is_all_value { + <$type>::MAX + } else { + 1 << i + }; + + let res = quote! { + #j => #enum_name::#variant_name, + }; + i += 1; + res + }); + let mut i: usize = 0; + let value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { + let variant_name = variant.ident.clone(); + let maybe_value = crate::helper::get_number_value::<$type>(variant, "mask_value"); + let is_all_value = crate::helper::is_all_value(variant, "mask_all"); + let j = if let Some(value) = maybe_value { + i = count_number_of_1_bits(value); + value + } else if is_all_value { + <$type>::MAX + } else { + 1 << i + }; + let res = quote! { + #enum_name::#variant_name => #j, + }; + i += 1; + res + }); + quote! { + impl $trait for #enum_name { + fn from_flag(value: $type) -> Self { + match value { + #(#from_value_match_arms)* + _ => panic!("Can't create enum_macro #enum_name for value {}", value) + } + } + + fn as_flag(&self) -> $type { + match self { + #(#value_match_arms)* + _ => panic!("Value can't be found for enum_macro #enum_name") + } + } + } + } + } else { + quote! {} + }; + TokenStream::from(res) + } + } +} \ No newline at end of file diff --git a/lib/enum_macro/src/enum_with_number_value.rs b/lib/enum_macro/src/enum_with_number_value.rs new file mode 100644 index 00000000..568f7bea --- /dev/null +++ b/lib/enum_macro/src/enum_with_number_value.rs @@ -0,0 +1,79 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::Data::Enum; +use syn::{DeriveInput, parse_macro_input}; +use crate::helper::get_number_value; + +pub fn with_number_value(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let enum_name = &input.ident; + + let res = if let Enum(enum_data) = &input.data { + let mut j: usize = 1; + let from_value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { + let variant_name = variant.ident.clone(); + let maybe_value = get_number_value::(variant, "value"); + if let Some(value) = maybe_value { + j = value; + } + let res = quote! { + #j => #enum_name::#variant_name, + }; + j += 1; + res + }); + let mut j: usize = 1; + let try_from_value_match_arms = + enum_data.variants.iter().enumerate().map(|(_, variant)| { + let variant_name = variant.ident.clone(); + let maybe_value = get_number_value::(variant, "value"); + if let Some(value) = maybe_value { + j = value; + } + let res = quote! { + #j => Ok(#enum_name::#variant_name), + }; + j += 1; + res + }); + let mut j: usize = 1; + let value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { + let variant_name = variant.ident.clone(); + let maybe_value = get_number_value::(variant, "value"); + if let Some(value) = maybe_value { + j = value; + } + let res = quote! { + #enum_name::#variant_name => #j, + }; + j += 1; + res + }); + quote! { + impl EnumWithNumberValue for #enum_name { + fn from_value(value: usize) -> Self { + match value { + #(#from_value_match_arms)* + _ => panic!("Can't create enum_macro #enum_name for value {}", value) + } + } + fn try_from_value(value: usize) -> Result { + match value { + #(#try_from_value_match_arms)* + _ => panic!("Can't create enum_macro #enum_name for value {}", value) + } + } + + fn value(&self) -> usize { + match self { + #(#value_match_arms)* + _ => panic!("Value can't be found for enum_macro #enum_name") + } + } + } + } + } else { + quote! {} + }; + TokenStream::from(res) +} \ No newline at end of file diff --git a/lib/enum_macro/src/enum_with_stackable.rs b/lib/enum_macro/src/enum_with_stackable.rs new file mode 100644 index 00000000..5a32c60b --- /dev/null +++ b/lib/enum_macro/src/enum_with_stackable.rs @@ -0,0 +1,131 @@ +use proc_macro::TokenStream; +use proc_macro2::Ident; +use quote::quote; +use syn::__private::{Span, TokenStream2}; +use syn::Data::Enum; +use syn::{DeriveInput, parse_macro_input}; +use crate::helper::{field_type, get_number_value, is_numeric}; + +pub fn stackable_enum(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let enum_name = &input.ident; + + if let Enum(enum_data) = &input.data { + let get_value_sum_arms = enum_data.variants.iter().filter(|variant| matches!(&variant.fields, syn::Fields::Unnamed(..))).map(|variant| { + let variant_name = variant.ident.clone(); + if let syn::Fields::Unnamed(fields) = &variant.fields { + let value_offset = if let Some(_) = variant.attrs.iter().find(|attr| { + attr.path().is_ident("value_offset") + }) { + get_number_value(variant, "value_offset").unwrap_or(1) + } else { + if fields.unnamed.len() == 1 { + 0 + } else { + 1 + } + }; + if is_numeric(&fields.unnamed[value_offset]) { + let mut args1 = fields.unnamed.iter().map(|_| quote! {_}).collect::>(); + args1[value_offset] = quote! {val}; + quote! {#enum_name::#variant_name(#(#args1,)*) => Some(*val as f32), } + } else { + quote! {} + } + } else { + panic!("patterns `Fields::Named(_)` and `Fields::Unit` not covered") + } + }); + let get_value_sum_return_arms = enum_data.variants.iter().map(|variant| { + let variant_name = variant.ident.clone(); + if let syn::Fields::Unnamed(fields) = &variant.fields { + let value_offset = if let Some(_) = variant.attrs.iter().find(|attr| { + attr.path().is_ident("value_offset") + }) { + get_number_value(variant, "value_offset").unwrap_or(1) + } else { + if fields.unnamed.len() == 1 { + 0 + } else { + 1 + } + }; + if is_numeric(&fields.unnamed[value_offset]) { + let mut args1 = fields.unnamed.iter().enumerate().map(|(i, _)| { + let v = Ident::new(format!("value{}", i).as_str(), Span::call_site()); + quote! {#v} + }).collect::>(); + let mut args2 = args1.clone(); + let val_type = Ident::new(field_type(&fields.unnamed[value_offset]).unwrap().as_str(), Span::call_site()); + args1[value_offset] = quote! {_}; + args2[value_offset] = quote! {&val as #val_type}; + quote! {#enum_name::#variant_name(#(#args1,)*) => #enum_name::#variant_name(#(*#args2,)*), } + } else { + let args1 = fields.unnamed.iter().enumerate().map(|(i, _)| { + let v = Ident::new(format!("value{}", i).as_str(), Span::call_site()); + quote! {#v} + }).collect::>(); + quote! {#enum_name::#variant_name(#(#args1,)*) => #enum_name::#variant_name(#(*#args1,)*), } + } + + } else { + quote! {#enum_name::#variant_name => #enum_name::#variant_name, } + } + }); + let get_enum_value = enum_data.variants.iter().filter(|variant| matches!(&variant.fields, syn::Fields::Unnamed(..))).map(|variant| { + let variant_name = variant.ident.clone(); + if let syn::Fields::Unnamed(fields) = &variant.fields { + let value_offset = if let Some(_) = variant.attrs.iter().find(|attr| { + attr.path().is_ident("value_offset") + }) { + get_number_value(variant, "value_offset").unwrap_or(1) + } else { + if fields.unnamed.len() == 1 { + 0 + } else { + 1 + } + }; + if is_numeric(&fields.unnamed[value_offset]) { + let mut args1 = fields.unnamed.iter().map(|_| { + quote! {_} + }).collect::>(); + args1[value_offset] = quote! {val}; + quote! {#enum_name::#variant_name(#(#args1,)*) => *val as f32, } + } else { + quote!{} + } + + } else { + panic!("patterns `Fields::Named(_)` and `Fields::Unit` not covered") + } + }); + TokenStream::from(quote! { + impl EnumStackable<#enum_name> for #enum_name { + fn get_value_sum(single_enum: &#enum_name, enums: &Vec<#enum_name>) -> #enum_name { + let val: f32 = enums.into_iter().filter_map(|e| + if e == single_enum { + match e { + #(#get_value_sum_arms)* + _ => None + } + } else { + None + } + ).sum(); + match single_enum { + #(#get_value_sum_return_arms)* + } + } + fn get_enum_value<'a>(single_enum: &#enum_name, enums: &'a Vec<&#enum_name>) -> Option { + Self::get_enum(single_enum, enums).map(|b| match b { + #(#get_enum_value)* + _ => 0.0 + }) + } + } + }) + } else { + TokenStream::from(quote! {}) + } +} \ No newline at end of file diff --git a/lib/enum_macro/src/enum_with_string_value.rs b/lib/enum_macro/src/enum_with_string_value.rs new file mode 100644 index 00000000..717dea11 --- /dev/null +++ b/lib/enum_macro/src/enum_with_string_value.rs @@ -0,0 +1,108 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::Data::Enum; +use syn::{DeriveInput, parse_macro_input}; +use crate::helper::{get_enum_string_value}; + +pub fn with_string_value(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let enum_name = &input.ident; + let res = if let Enum(enum_data) = &input.data { + let from_value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { + let variant_name = variant.ident.clone(); + let string_value = get_enum_string_value(variant, "value_string", false); + + let res = if string_value.len() > 1 { + quote! { + #(#string_value )|* => #enum_name::#variant_name, + } + } else { + quote! { + #(#string_value)* => #enum_name::#variant_name, + } + }; + res + }); + let try_from_value_match_arms = + enum_data.variants.iter().enumerate().map(|(_, variant)| { + let variant_name = variant.ident.clone(); + let string_value = get_enum_string_value(variant, "value_string", false); + + let res = if string_value.len() > 1 { + quote! { + #(#string_value )|* => Ok(#enum_name::#variant_name), + } + } else { + quote! { + #(#string_value)* => Ok(#enum_name::#variant_name), + } + }; + res + }); + let from_value_ignore_case_match_arms = + enum_data.variants.iter().enumerate().map(|(_, variant)| { + let variant_name = variant.ident.clone(); + let string_value = get_enum_string_value(variant, "value_string", true); + + let res = if string_value.len() > 1 { + quote! { + #(#string_value )|* => #enum_name::#variant_name, + } + } else { + quote! { + #(#string_value)* => #enum_name::#variant_name, + } + }; + + res + }); + let value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { + let variant_name = variant.ident.clone(); + let string_value = get_enum_string_value(variant, "value_string", false); + let res = if string_value.len() > 1 { + let string_value = string_value.iter().map(|v| v.to_string().replace("\"", "")).collect::>().join("|"); + quote! { + #enum_name::#variant_name => #string_value, + } + } else { + quote! { + #enum_name::#variant_name => #(#string_value)*, + } + }; + + res + }); + quote! { + impl EnumWithStringValue for #enum_name { + fn from_string(value: &str) -> Self { + match value { + #(#from_value_match_arms)* + _ => panic!("Can't create enum_macro #enum_name for value {}", value) + } + } + fn try_from_string(value: &str) -> Result { + match value { + #(#try_from_value_match_arms)* + _ => Err(format!("Can't create enum_macro #enum_name for value {}", value)) + } + } + fn from_string_ignore_case(value: &str) -> Self { + match value.to_string().to_lowercase().as_str() { + #(#from_value_ignore_case_match_arms)* + _ => panic!("Can't create enum_macro #enum_name for value {}", value) + } + } + + fn as_str(&self) -> &str { + match self { + #(#value_match_arms)* + _ => panic!("Value can't be found for enum_macro #enum_name") + } + } + } + } + } else { + quote! {} + }; + TokenStream::from(res) +} \ No newline at end of file diff --git a/lib/enum_macro/src/helper.rs b/lib/enum_macro/src/helper.rs new file mode 100644 index 00000000..d460b9ae --- /dev/null +++ b/lib/enum_macro/src/helper.rs @@ -0,0 +1,95 @@ +use quote::quote; +use syn::{Field, Type, Variant}; + +use proc_macro2::{TokenStream as TokenStream2}; + +pub(crate) fn get_number_value(variant: &Variant, ident: &str) -> Option + where + T: std::str::FromStr, + ::Err: std::fmt::Display, +{ + variant.attrs.iter() + .find(|attr| attr.path().is_ident(ident)) + .and_then(|attr| { + if let syn::Meta::NameValue(syn::MetaNameValue { + eq_token: _, + value: + syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(s), .. }), + .. + }) = &attr.meta + { + return s.base10_parse::().ok(); + } + + None + }) +} + +pub(crate) fn is_all_value(variant: &Variant, ident: &str) -> bool { + variant.attrs.iter().any(|attr| attr.path().is_ident(ident)) +} + + +pub(crate) fn get_enum_string_value(variant: &Variant, ident: &str, force_lowercase: bool) -> Vec { + let variant_name = variant.ident.clone(); + let string_value; + let aliases = get_string_value(variant, ident).iter() + .filter(|maybe_value| maybe_value.is_some()) + .map(|maybe_value| { + let mut val = maybe_value.clone().unwrap(); + if force_lowercase { + val = val.to_lowercase() + } + quote! { #val } + }).collect::>(); + if aliases.len() > 0 { + return aliases; + } else { + if force_lowercase { + let string_value = format!("{variant_name}").to_lowercase(); + return vec![quote! { #string_value }]; + } + string_value = format!("{variant_name}"); + } + vec![quote! { #string_value }] +} + +pub(crate) fn get_string_value(variant: &Variant, ident: &str) -> Vec> { + variant.attrs.iter() + .filter_map(|attr| { + if let syn::Meta::NameValue(syn::MetaNameValue { + path, + eq_token: _, + value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }), + }) = &attr.meta { + if path.is_ident(ident) { + return Some(Some(s.value())); + } + } + None + }) + .collect() +} + + +pub(crate) fn field_type(field: &Field) -> Option { + match &field.ty { + Type::Path(p) => { + Some(p.path.get_ident().unwrap().to_string()) + } + _ => None + } +} + +pub(crate) fn is_numeric(field: &Field) -> bool { + match &field.ty { + Type::Path(_) => { + let is_numeric = match field_type(field).unwrap().as_str() { + "u8" | "i8" | "u16" | "i16" | "i32" | "u32" | "i64" | "u64" | "i128" | "u128" => true, + _ => false + }; + return is_numeric; + } + _ => false + } +} \ No newline at end of file diff --git a/lib/enum_macro/src/lib.rs b/lib/enum_macro/src/lib.rs index c7301ffa..22cc794f 100644 --- a/lib/enum_macro/src/lib.rs +++ b/lib/enum_macro/src/lib.rs @@ -1,172 +1,24 @@ +mod enum_with_number_value; +mod helper; +mod enum_with_string_value; +mod enum_with_eq; +mod enum_with_stackable; + extern crate proc_macro; +#[macro_use] +mod enum_with_mask; + use proc_macro::TokenStream; -use proc_macro2::{Ident as Ident2, TokenStream as TokenStream2}; use quote::quote; use syn::Data::Enum; -use syn::{parse_macro_input, DeriveInput, Variant, Ident, Meta, Type, Field}; -use syn::__private::Span; +use syn::{parse_macro_input, DeriveInput}; #[proc_macro_derive(WithNumberValue, attributes(value))] pub fn with_number_value(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let enum_name = &input.ident; - - let res = if let Enum(enum_data) = &input.data { - let mut j: usize = 1; - let from_value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { - let variant_name = variant.ident.clone(); - let maybe_value = get_number_value::(variant, "value"); - if let Some(value) = maybe_value { - j = value; - } - let res = quote! { - #j => #enum_name::#variant_name, - }; - j += 1; - res - }); - let mut j: usize = 1; - let try_from_value_match_arms = - enum_data.variants.iter().enumerate().map(|(_, variant)| { - let variant_name = variant.ident.clone(); - let maybe_value = get_number_value::(variant, "value"); - if let Some(value) = maybe_value { - j = value; - } - let res = quote! { - #j => Ok(#enum_name::#variant_name), - }; - j += 1; - res - }); - let mut j: usize = 1; - let value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { - let variant_name = variant.ident.clone(); - let maybe_value = get_number_value::(variant, "value"); - if let Some(value) = maybe_value { - j = value; - } - let res = quote! { - #enum_name::#variant_name => #j, - }; - j += 1; - res - }); - quote! { - impl EnumWithNumberValue for #enum_name { - fn from_value(value: usize) -> Self { - match value { - #(#from_value_match_arms)* - _ => panic!("Can't create enum_macro #enum_name for value {}", value) - } - } - fn try_from_value(value: usize) -> Result { - match value { - #(#try_from_value_match_arms)* - _ => panic!("Can't create enum_macro #enum_name for value {}", value) - } - } - - fn value(&self) -> usize { - match self { - #(#value_match_arms)* - _ => panic!("Value can't be found for enum_macro #enum_name") - } - } - } - } - } else { - quote! {} - }; - TokenStream::from(res) + crate::enum_with_number_value::with_number_value(input) } -macro_rules! with_mask { - ($function:ident, $trait:ident, $macro:ident, $type:ty, $max_bits:expr) => { - - #[proc_macro_derive($macro, attributes(mask_value, mask_all))] - pub fn $function(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let enum_name = &input.ident; - fn count_number_of_1_bits(value: $type) -> usize { - let mut k: usize = 0; - // count number of 1 bits in value - loop { - if k > $max_bits { - break; - } - if value & (1 << k) != 0 { - return k; - } - k += 1; - } - 0 - } - let res = if let Enum(enum_data) = &input.data { - let mut i: usize = 0; - let from_value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { - let variant_name = variant.ident.clone(); - let maybe_value = get_number_value::<$type>(variant, "mask_value"); - let is_all_value = is_all_value(variant, "mask_all"); - let j = if let Some(value) = maybe_value { - i = count_number_of_1_bits(value); - value - } else if is_all_value { - <$type>::MAX - } else { - 1 << i - }; - - let res = quote! { - #j => #enum_name::#variant_name, - }; - i += 1; - res - }); - let mut i: usize = 0; - let value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { - let variant_name = variant.ident.clone(); - let maybe_value = get_number_value::<$type>(variant, "mask_value"); - let is_all_value = is_all_value(variant, "mask_all"); - let j = if let Some(value) = maybe_value { - i = count_number_of_1_bits(value); - value - } else if is_all_value { - <$type>::MAX - } else { - 1 << i - }; - let res = quote! { - #enum_name::#variant_name => #j, - }; - i += 1; - res - }); - quote! { - impl $trait for #enum_name { - fn from_flag(value: $type) -> Self { - match value { - #(#from_value_match_arms)* - _ => panic!("Can't create enum_macro #enum_name for value {}", value) - } - } - - fn as_flag(&self) -> $type { - match self { - #(#value_match_arms)* - _ => panic!("Value can't be found for enum_macro #enum_name") - } - } - } - } - } else { - quote! {} - }; - TokenStream::from(res) - } - } -} with_mask!( with_mask_value_u64, @@ -197,424 +49,21 @@ with_mask!( 7 ); -fn get_number_value(variant: &Variant, ident: &str) -> Option - where - T: std::str::FromStr, - ::Err: std::fmt::Display, -{ - let maybe_value = variant - .attrs - .iter() - .find(|attr| attr.path().is_ident(ident)) - .and_then(|attr| { - let meta = &attr.meta; - if let syn::Meta::NameValue(syn::MetaNameValue { - path: _, - eq_token: _, - value, - }) = meta - { - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Int(s), - .. - }) = value - { - s.base10_parse::().ok() - } else { - None - } - } else { - None - } - }); - maybe_value -} - -fn is_all_value(variant: &Variant, ident: &str) -> bool { - variant.attrs.iter().any(|attr| attr.path().is_ident(ident)) -} - #[proc_macro_derive( WithStringValue, -attributes(with_string_value_uppercase, with_string_value_lowercase, value_string) +attributes(value_string) )] pub fn with_string_value(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let enum_name = &input.ident; - let mut uppercase = false; - let mut lowercase = false; - input.attrs.iter().for_each(|attr| { - if attr.path().is_ident("with_string_value_uppercase") { - uppercase = true; - } - if attr.path().is_ident("with_string_value_lowercase") { - lowercase = true; - } - }); - let res = if let Enum(enum_data) = &input.data { - let from_value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { - let variant_name = variant.ident.clone(); - let string_value = get_enum_string_value(uppercase, lowercase, variant, false); - - let res = if string_value.len() > 1 { - quote! { - #(#string_value )|* => #enum_name::#variant_name, - } - } else { - quote! { - #(#string_value)* => #enum_name::#variant_name, - } - }; - res - }); - let try_from_value_match_arms = - enum_data.variants.iter().enumerate().map(|(_, variant)| { - let variant_name = variant.ident.clone(); - let string_value = get_enum_string_value(uppercase, lowercase, variant, false); - - let res = if string_value.len() > 1 { - quote! { - #(#string_value )|* => Ok(#enum_name::#variant_name), - } - } else { - quote! { - #(#string_value)* => Ok(#enum_name::#variant_name), - } - }; - res - }); - let from_value_ignore_case_match_arms = - enum_data.variants.iter().enumerate().map(|(_, variant)| { - let variant_name = variant.ident.clone(); - let string_value = get_enum_string_value(uppercase, lowercase, variant, true); - - let res = if string_value.len() > 1 { - quote! { - #(#string_value )|* => #enum_name::#variant_name, - } - } else { - quote! { - #(#string_value)* => #enum_name::#variant_name, - } - }; - - res - }); - let value_match_arms = enum_data.variants.iter().enumerate().map(|(_, variant)| { - let variant_name = variant.ident.clone(); - let maybe_value = get_string_value(variant); - let string_value = get_enum_string_value(uppercase, lowercase, variant, false); - let res = if string_value.len() > 1 { - let string_value = string_value.iter().map(|v| v.to_string().replace("\"", "")).collect::>().join("|"); - quote! { - #enum_name::#variant_name => #string_value, - } - } else { - quote! { - #enum_name::#variant_name => #(#string_value)*, - } - }; - - res - }); - quote! { - impl EnumWithStringValue for #enum_name { - fn from_string(value: &str) -> Self { - match value { - #(#from_value_match_arms)* - _ => panic!("Can't create enum_macro #enum_name for value {}", value) - } - } - fn try_from_string(value: &str) -> Result { - match value { - #(#try_from_value_match_arms)* - _ => Err(format!("Can't create enum_macro #enum_name for value {}", value)) - } - } - fn from_string_ignore_case(value: &str) -> Self { - match value.to_string().to_lowercase().as_str() { - #(#from_value_ignore_case_match_arms)* - _ => panic!("Can't create enum_macro #enum_name for value {}", value) - } - } - - fn as_str(&self) -> &str { - match self { - #(#value_match_arms)* - _ => panic!("Value can't be found for enum_macro #enum_name") - } - } - } - } - } else { - quote! {} - }; - TokenStream::from(res) -} - -fn get_enum_string_value(mut uppercase: bool, mut lowercase: bool, variant: &Variant, force_lowercase: bool) -> Vec { - let variant_name = variant.ident.clone(); - let mut string_value; - let aliases = get_string_value(variant).iter() - .filter(|maybe_value| maybe_value.is_some()) - .map(|maybe_value| { - let mut val = maybe_value.clone().unwrap(); - if force_lowercase { - val = val.to_lowercase() - } - quote! { #val } - }).collect::>(); - if aliases.len() > 0 { - return aliases; - } else { - if force_lowercase { - let string_value = format!("{variant_name}").to_lowercase(); - return vec![quote! { #string_value }]; - } - string_value = if uppercase { - format!("{variant_name}").to_uppercase() - } else if lowercase { - format!("{variant_name}").to_lowercase() - } else { - format!("{variant_name}") - }; - } - vec![quote! { #string_value }] -} - -fn get_string_value(variant: &Variant) -> Vec> { - variant - .attrs - .iter() - .filter(|attr| attr.path().is_ident("value_string")) - .map(|attr| { - let meta = &attr.meta; - if let syn::Meta::NameValue(syn::MetaNameValue { - path: _, - eq_token: _, - value, - }) = meta - { - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(s), - .. - }) = value - { - Some(s.value()) - } else { - None - } - } else { - None - } - }).collect() + crate::enum_with_string_value::with_string_value(input) } #[proc_macro_derive(WithEq, attributes(value_comparison_offset))] -pub fn with_wq(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let enum_name = &input.ident; - - if let Enum(enum_data) = &input.data { - let arms = enum_data.variants.iter().map(|variant| { - let variant_name = variant.ident.clone(); - match &variant.fields { - syn::Fields::Unnamed(fields) => { - let variant_offset = if let Some(attribute) = variant.attrs.iter().find(|attr| { - attr.path().is_ident("value_comparison_offset") - }) { - get_number_value(variant, "value_comparison_offset").unwrap_or(0) - } else { - 0 - }; - let mut args1 = fields.unnamed.iter().map(|_| quote! {_}).collect::>(); - let mut args2 = fields.unnamed.iter().map(|_| quote! {_}).collect::>(); - let mut should_deref = false; - if args1.len() > 1 { - args1[variant_offset] = quote! {variant1}; - args2[variant_offset] = quote! {variant2}; - should_deref = is_numeric(&fields.unnamed[variant_offset]); - } - // let field_types = fields.unnamed.iter().map(|field| &field.ty); - if fields.unnamed.len() > 1 { - if should_deref { - quote! {(#enum_name::#variant_name(#(#args1,)*), #enum_name::#variant_name(#(#args2,)*)) => *variant1 == *variant2, } - } else { - quote! {(#enum_name::#variant_name(#(#args1,)*), #enum_name::#variant_name(#(#args2,)*)) => variant1 == variant2, } - } - } else { - quote! {(#enum_name::#variant_name(#(#args1)*), #enum_name::#variant_name(#(#args2)*)) => true, } - } - } - syn::Fields::Unit => { - quote! { - (#enum_name::#variant_name, #enum_name::#variant_name) => true, - } - } - syn::Fields::Named(_) => { - // Handling named fields if necessary - panic!("Named fields are not supported in this macro") - } - } - }); - TokenStream::from(quote! { - impl PartialEq for #enum_name { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - #(#arms)* - _ => false - } - } - } - }) - } else { - TokenStream::from(quote! {}) - } -} - -fn field_type(field: &Field) -> Option { - match &field.ty { - Type::Path(p) => { - Some(p.path.get_ident().unwrap().to_string()) - } - _ => None - } +pub fn with_eq(input: TokenStream) -> TokenStream { + crate::enum_with_eq::with_eq(input) } -fn is_numeric(field: &Field) -> bool { - match &field.ty { - Type::Path(p) => { - let is_numeric = match field_type(field).unwrap().as_str() { - "u8" | "i8" | "u16" | "i16" | "i32" | "u32" | "i64" | "u64" | "i128" | "u128" => true, - _ => false - }; - return is_numeric; - } - _ => false - } -} #[proc_macro_derive(WithStackable, attributes(skip_sum, value_offset))] pub fn stackable_enum(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let enum_name = &input.ident; - - if let Enum(enum_data) = &input.data { - let get_value_sum_arms = enum_data.variants.iter().filter(|variant| matches!(&variant.fields, syn::Fields::Unnamed(..))).map(|variant| { - let variant_name = variant.ident.clone(); - if let syn::Fields::Unnamed(fields) = &variant.fields { - let value_offset = if let Some(_) = variant.attrs.iter().find(|attr| { - attr.path().is_ident("value_offset") - }) { - get_number_value(variant, "value_offset").unwrap_or(1) - } else { - if fields.unnamed.len() == 1 { - 0 - } else { - 1 - } - }; - if is_numeric(&fields.unnamed[value_offset]) { - let mut args1 = fields.unnamed.iter().map(|_| quote! {_}).collect::>(); - args1[value_offset] = quote! {val}; - quote! {#enum_name::#variant_name(#(#args1,)*) => Some(*val as f32), } - } else { - quote! {} - } - } else { - panic!("patterns `Fields::Named(_)` and `Fields::Unit` not covered") - } - }); - let get_value_sum_return_arms = enum_data.variants.iter().map(|variant| { - let variant_name = variant.ident.clone(); - if let syn::Fields::Unnamed(fields) = &variant.fields { - let value_offset = if let Some(_) = variant.attrs.iter().find(|attr| { - attr.path().is_ident("value_offset") - }) { - get_number_value(variant, "value_offset").unwrap_or(1) - } else { - if fields.unnamed.len() == 1 { - 0 - } else { - 1 - } - }; - if is_numeric(&fields.unnamed[value_offset]) { - let mut args1 = fields.unnamed.iter().enumerate().map(|(i, _)| { - let v = Ident::new(format!("value{}", i).as_str(), Span::call_site()); - quote! {#v} - }).collect::>(); - let mut args2 = args1.clone(); - let val_type = Ident::new(field_type(&fields.unnamed[value_offset]).unwrap().as_str(), Span::call_site()); - args1[value_offset] = quote! {_}; - args2[value_offset] = quote! {&val as #val_type}; - quote! {#enum_name::#variant_name(#(#args1,)*) => #enum_name::#variant_name(#(*#args2,)*), } - } else { - let mut args1 = fields.unnamed.iter().enumerate().map(|(i, _)| { - let v = Ident::new(format!("value{}", i).as_str(), Span::call_site()); - quote! {#v} - }).collect::>(); - quote! {#enum_name::#variant_name(#(#args1,)*) => #enum_name::#variant_name(#(*#args1,)*), } - } - - } else { - quote! {#enum_name::#variant_name => #enum_name::#variant_name, } - } - }); - let get_enum_value = enum_data.variants.iter().filter(|variant| matches!(&variant.fields, syn::Fields::Unnamed(..))).map(|variant| { - let variant_name = variant.ident.clone(); - if let syn::Fields::Unnamed(fields) = &variant.fields { - let value_offset = if let Some(_) = variant.attrs.iter().find(|attr| { - attr.path().is_ident("value_offset") - }) { - get_number_value(variant, "value_offset").unwrap_or(1) - } else { - if fields.unnamed.len() == 1 { - 0 - } else { - 1 - } - }; - if is_numeric(&fields.unnamed[value_offset]) { - let mut args1 = fields.unnamed.iter().enumerate().map(|(i, _)| { - quote! {_} - }).collect::>(); - args1[value_offset] = quote! {val}; - quote! {#enum_name::#variant_name(#(#args1,)*) => *val as f32, } - } else { - quote!{} - } - - } else { - panic!("patterns `Fields::Named(_)` and `Fields::Unit` not covered") - } - }); - TokenStream::from(quote! { - impl EnumStackable<#enum_name> for #enum_name { - fn get_value_sum(single_enum: &#enum_name, enums: &Vec<#enum_name>) -> #enum_name { - let val: f32 = enums.into_iter().filter_map(|e| - if e == single_enum { - match e { - #(#get_value_sum_arms)* - _ => None - } - } else { - None - } - ).sum(); - match single_enum { - #(#get_value_sum_return_arms)* - } - } - fn get_enum_value<'a>(single_enum: &#enum_name, enums: &'a Vec<&#enum_name>) -> Option { - Self::get_enum(single_enum, enums).map(|b| match b { - #(#get_enum_value)* - _ => 0.0 - }) - } - } - }) - } else { - TokenStream::from(quote! {}) - } + crate::enum_with_stackable::stackable_enum(input) } \ No newline at end of file diff --git a/lib/models/src/enums/bonus.rs b/lib/models/src/enums/bonus.rs index 0a08d73d..4d1a9d07 100644 --- a/lib/models/src/enums/bonus.rs +++ b/lib/models/src/enums/bonus.rs @@ -5,7 +5,7 @@ use crate::enums::mob::{MobClass, MobGroup, MobRace}; use crate::enums::size::Size; use crate::enums::status::StatusEffect; use crate::enums::weapon::WeaponType; -use crate::status::{StatusBonus, StatusSnapshot}; +use crate::status::{StatusSnapshot}; #[derive(Debug, Clone, Copy)] #[derive(WithEq, WithStackable)] diff --git a/lib/models/src/enums/mod.rs b/lib/models/src/enums/mod.rs index 86b80e97..270bb142 100644 --- a/lib/models/src/enums/mod.rs +++ b/lib/models/src/enums/mod.rs @@ -1,6 +1,5 @@ pub use enum_macro::*; -use crate::enums::bonus::BonusType; pub mod action; pub mod cell;