Skip to content

Commit

Permalink
Cleaned derive macro.
Browse files Browse the repository at this point in the history
Added methods to get constant references to values on the Valued trait.
  • Loading branch information
JorgeRicoVivas committed Mar 4, 2024
1 parent eae8719 commit f34e979
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 77 deletions.
2 changes: 2 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 25 additions & 2 deletions indexed_valued_enums/src/macros.rs
Expand Up @@ -155,19 +155,42 @@ macro_rules! create_indexed_valued_enum {
to this [", stringify!($enum_name),"] 's variant, this operation is O(1) as it just \
gets the discriminant as a copy from \
[indexed_valued_enums::valued_enum::Valued::VALUES] \
If you just need a reference to the value, use \
[",stringify!($enum_name),"::value_ref])] instead, as it doesn't require a read copy)
<br><br>This always returns [Option::Some], so it's recommended to call\
[",stringify!($enum_name),"::value] instead")]
[",stringify!($enum_name),"::value] instead") <br>]
pub const fn value_opt(&self) -> Option<$value_type> {
indexed_valued_enums::valued_enum::value_opt_internal(self)
}

#[doc = concat!("Gives the value of type [",stringify!($value_type),"] corresponding \
to this [", stringify!($enum_name),"] 's variant, this operation is O(1) as it just \
gets the discriminant as a copy from \
[indexed_valued_enums::valued_enum::Valued::VALUES]")]
[indexed_valued_enums::valued_enum::Valued::VALUES]. <br>\
If you just need a reference to the value, use \
[",stringify!($enum_name),"::value_ref])] instead, as it doesn't require a read copy")]
pub const fn value(&self) -> $value_type {
indexed_valued_enums::valued_enum::value_internal(self)
}

#[doc = concat!("Gives the value of type [",stringify!($value_type),"] corresponding \
to this [", stringify!($enum_name),"] 's variant, if you need a copy of the value \
but the value doesn't implement Clone, use [",stringify!($enum_name),"::value_opt]\
instead, as it performs a read copy \
<br><br>This always returns [Option::Some], so it's recommended to call\
[",stringify!($enum_name),"::value] instead")]
pub const fn value_opt(&self) -> Option<&'static $value_type> {
indexed_valued_enums::valued_enum::value_ref_opt_internal(self)
}

#[doc = concat!("Gives the value of type [",stringify!($value_type),"] corresponding \
to this [", stringify!($enum_name),"] 's variant, if you need a copy of the value\
but the value doesn't implement Clone, use [",stringify!($value_type),"::value]"
instead as it performs a read copy)]
pub const fn value_ref(&self) -> &'static $value_type {
indexed_valued_enums::valued_enum::value_ref_internal(self)
}

}
};
(process feature $enum_name:ident, $value_type:ty; ValueToVariantDelegators)
Expand Down
63 changes: 63 additions & 0 deletions indexed_valued_enums/src/valued_enum/mod.rs
Expand Up @@ -44,6 +44,9 @@ pub trait Valued: Indexed {
/// treated as a raw pointer whose value is read without cloning through
/// [core::ptr::read]
///
/// If you just need a reference to the value, use [Valued::value_ref_opt] instead, as it doesn't
/// do a read copy.
///
/// Note that if implemented correctly (ensured by using [crate::create_indexed_valued_enum]),
/// calling this method will always produce [Option::Some(Value)]
fn value_opt(&self) -> Option<Self::Value> {
Expand All @@ -52,10 +55,39 @@ pub trait Valued: Indexed {

/// Gives the value corresponding to this variant, this is an O(1) operation as it just gets the
/// value as a copy from [Valued::VALUES]
/// If you just need a reference to the value, use [Valued::value_opt] instead, as it doesn't
/// do a read copy.
fn value(&self) -> Self::Value {
self.value_opt().unwrap()
}

/// Gives the value corresponding for a variant of an enum marked with #[repr(usize)] and
/// implementing the [Valued] trait, this is an O(1) operation as it just gets a reference to the
/// value as a copy.
///
/// If you need the value as structure but it doesn't implement clone, use [value_opt_internal]
/// instead, as it performs a read copy
///
/// Note that if implemented correctly (ensured by the declarative macro
/// [crate::create_indexed_valued_enum]), calling this method will always produce
/// [Option::Some(&Value)]
fn value_ref_opt(&self) -> Option<&'static Self::Value> {
value_ref_opt_internal(self)
}

/// Gives the value corresponding for a variant of an enum marked with #[repr(usize)] and
/// implementing the [Valued] trait, this is an O(1) operation as it just gets a reference to the
/// value as a copy.
///
/// If you need the value as structure but it doesn't implement clone, use [value_internal]
/// instead, as it performs a read copy
///
/// Note that if implemented correctly (ensured by the declarative macro
/// [crate::create_indexed_valued_enum]), calling this method will never panic
fn value_ref(&self) -> &'static Self::Value {
value_ref_internal(self)
}

/// Gives variant corresponding to a value, this is an O(n) operation as it does so by comparing
/// every single value contained in [Valued::VALUES]
fn value_to_variant_opt(value: &Self::Value) -> Option<Self> where Self::Value: PartialEq {
Expand Down Expand Up @@ -106,3 +138,34 @@ pub const fn value_internal<ValuedType: Valued>(variant: &ValuedType) -> ValuedT
unsafe { ValuedType::VALUES.as_ptr().offset(first_offset).offset(second_offset).offset(third_offset).read() }
}

/// Gives the value corresponding for a variant of an enum marked with #[repr(usize)] and
/// implementing the [Valued] trait, this is an O(1) operation as it just gets a reference to the
/// value as a copy.
///
/// If you need the value as structure but it doesn't implement clone, use [value_opt_internal]
/// instead, as it performs a read copy
///
/// Note that if implemented correctly (ensured by the declarative macro
/// [crate::create_indexed_valued_enum]), calling this method will always produce
/// [Option::Some(&Value)]
pub const fn value_ref_opt_internal<ValuedType: Valued>(variant: &ValuedType) -> Option<&'static ValuedType::Value> {
let discriminant = discriminant_internal(variant);
if discriminant >= ValuedType::VARIANTS.len() { return None; }
Some(&ValuedType::VALUES[discriminant])
}

/// Gives the value corresponding for a variant of an enum marked with #[repr(usize)] and
/// implementing the [Valued] trait, this is an O(1) operation as it just gets a reference to the
/// value as a copy.
///
/// If you need the value as structure but it doesn't implement clone, use [value_internal]
/// instead, as it performs a read copy
///
/// Note that if implemented correctly (ensured by the declarative macro
/// [crate::create_indexed_valued_enum]), calling this method will never panic
pub const fn value_ref_internal<ValuedType: Valued>(variant: &ValuedType) -> &'static ValuedType::Value {
let discriminant = discriminant_internal(variant);
if discriminant >= ValuedType::VARIANTS.len() { panic!("Tried to get a variant's value whose index is larger than the amount of Variants") }
&ValuedType::VALUES[discriminant]
}

124 changes: 49 additions & 75 deletions indexed_valued_enums_derive/src/lib.rs
Expand Up @@ -10,14 +10,11 @@ use alloc::vec::Vec;
use proc_macro::TokenStream;

use proc_macro2::{Ident, Punct};
use quote::{quote, ToTokens};
use syn::{Attribute, DataEnum, DeriveInput, parse_macro_input, Type};
use quote::quote;
use syn::{Attribute, DataEnum, DeriveInput, Error, parse_macro_input, Type, Variant};
use syn::Data;
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;

const INCORRECT_VALUED_AS_FORMAT_ERROR_MESSAGE: &'static str = "Wrong syntax of attribute '#[valued_as(*type*)]', it must have one and just one type as content, like:\n\n\
#[derive(Valued)]\n#[enum_valued_as(*your type*)]\nenum your_enums_name {{\n\t...\n}} ";
use syn::parse::ParseStream;
use utils::{ExpectElseOption, ExpectElseResult};

/// Implements the 'Indexed' and 'Valued' traits for an enum, allowing to get a discriminant / index
/// and a value for each variant through the functions 'discriminant' and 'value', and get this
Expand Down Expand Up @@ -183,19 +180,19 @@ pub fn derive_macro_describe(input: TokenStream) -> TokenStream {

fn derive_enum(attrs: &Vec<Attribute>, enum_name: &Ident, my_enum: DataEnum) -> TokenStream {
let valued_as_attribute = find_attribute_last_in_path(&attrs, "enum_valued_as")
.expect(&*format!("Could not find attribute 'valued_as(*type*)'\nRemember '#[derive(Valued)]' must appear before before #[valued_as(*your type*)], like:\n\n\
.expect_else(|| format!("Could not find attribute 'valued_as(*type*)'\nRemember '#[derive(Valued)]' must appear before before #[valued_as(*your type*)], like:\n\n\
#[derive(Valued)]\n#[enum_valued_as(*your type*)]\nenum {enum_name} {{\n\t...\n}} "));
let valued_as = valued_as_attribute.parse_args::<ValuedAsAttribute>()
.expect(INCORRECT_VALUED_AS_FORMAT_ERROR_MESSAGE)
.type_of_value;
let valued_as = valued_as_attribute.parse_args::<Type>()
.expect_else(|_| format!("Wrong syntax of attribute '#[valued_as(*type*)]', it must have one and just one type as content, like:\n\n\
#[derive(Valued)]\n#[enum_valued_as(*your type*)]\nenum {enum_name} {{\n\t...\n}} "));
let unvalued_default = find_attribute(&attrs, "unvalued_default")
.map(|unvalued_default| { &unvalued_default.tokens });

let features = find_attribute(&attrs, "enum_valued_features")
.map(|features_attr| features_attr.parse_args::<Features>().expect(&format!("Wrong syntax of attribute '#[enum_valued_features(*desired features*)]', it must contain just a set of your desired features, which can be consulted on the indexed_valued_enums::create_indexed_valued_enum macro\n\
.map(|features_attr| features_attr.parse_args_with(parse_separated_idents)
.expect_else(|_| format!("Wrong syntax of attribute '#[enum_valued_features(*desired features*)]', it must contain just a set of your desired features, which can be consulted on the indexed_valued_enums::create_indexed_valued_enum macro\n\
Your enum's should look like this, like:\n\n\
#[derive(Valued)]\n#[enum_valued_as({valued_as:?})]\n#[value(...)] <------- Your features here, like 'Delegators, ValueToVariantDelegators...' \nenum {enum_name} {{\n\t...\n}} "))
.idents)
#[derive(Valued)]\n#[enum_valued_as({valued_as:?})]\n#[value(...)] <------- Your features here, like 'Delegators, ValueToVariantDelegators...' \nenum {enum_name} {{\n\t...\n}} ")))
.unwrap_or(Vec::new());

let mut variants = Vec::with_capacity(my_enum.variants.len());
Expand All @@ -208,30 +205,24 @@ fn derive_enum(attrs: &Vec<Attribute>, enum_name: &Ident, my_enum: DataEnum) ->
let variant_value = find_attribute(&variant.attrs, "value")
.map(|variants_value_attr| { &variants_value_attr.tokens })
.or_else(|| unvalued_default.clone())
.expect(&format!("Could not find value for variant {variant_name}\n\n Consider adding a value like:\n\n\
.expect_else(|| format!("Could not find value for variant {variant_name}\n\n Consider adding a value like:\n\n\
#[value(...)] <------- Your value of type {valued_as:?}\n{variant_name}\n\n\n Or add a default value for variants without values, like\n\n\
#[derive(Valued)]\n#[enum_valued_as(*your type*)]\n#[unvalued_default(...)] <------- Your value of type\nenum {{\n\t...\n}} ", ));
let variant_initialize_uses = find_attribute(&variant.attrs, "variant_initialize_uses")
.map(|variants_value_attr| extract_token_stream_of_attribute(variants_value_attr));

print_info(&format!("variant_initialize_uses of variant {enum_name}::{variant_name}"), &format!("{:#?}", variant_initialize_uses));

let first_field_is_named = variant.fields.iter().next().map(|first_field| first_field.ident.is_some()).unwrap_or(false);
utils::print_info(|| format!("variant_initialize_uses of variant {enum_name}::{variant_name}"), || format!("{:#?}", variant_initialize_uses));

let internal_fields_as_default = variant.fields
let first_field_is_named = variant.fields
.iter()
.map(|field| {
field.ident.as_ref()
.map(|field_name| quote!(#field_name (const_default::ConstDefault::DEFAULT)))
.unwrap_or_else(|| quote!((const_default::ConstDefault::DEFAULT)))
})
.reduce(|prev_token, next_token| quote!(#prev_token, #next_token));

.next()
.map(|first_field| first_field.ident.is_some())
.unwrap_or(false);

variants.push(&variant.ident);
variants_values.push(variant_value);
variants_fields_initializer.push(
variant_initialize_uses.map(From::from).or(internal_fields_as_default)
variant_initialize_uses.map(From::from).or_else(|| fields_as_const_defaults_tokens(variant))
.map(|initializers| if first_field_is_named {
quote!(; named_field_initializers #initializers ;)
} else {
Expand All @@ -245,19 +236,46 @@ fn derive_enum(attrs: &Vec<Attribute>, enum_name: &Ident, my_enum: DataEnum) ->
indexed_valued_enums::create_indexed_valued_enum !(impl traits #enum_name #valued_as; #(#variants, #variants_values #variants_fields_initializer),*);
indexed_valued_enums::create_indexed_valued_enum !(process features #enum_name, #valued_as; #(#features);*);
};
print_info("output_str", &format!("{:#?}", output.to_string()));
utils::print_info(|| "output_str", || format!("{:#?}", output.to_string()));
output.into()
}

fn extract_token_stream_of_attribute(variants_value_attr: &Attribute) -> TokenStream {
let mut token_stream = Option::None;
variants_value_attr.parse_args_with(|input: ParseStream| {
let mut token_stream = None;
let _ = variants_value_attr.parse_args_with(|input: ParseStream| {
token_stream = Some(TokenStream::from(input.cursor().token_stream()));
Ok(())
});
token_stream.unwrap()
}

fn fields_as_const_defaults_tokens(variant: &Variant) -> Option<proc_macro2::TokenStream> {
let internal_fields_as_default = variant.fields
.iter()
.map(|field| {
field.ident.as_ref()
.map(|field_name| quote!(#field_name (const_default::ConstDefault::DEFAULT)))
.unwrap_or_else(|| quote!((const_default::ConstDefault::DEFAULT)))
})
.reduce(|prev_token, next_token| quote!(#prev_token, #next_token));
internal_fields_as_default
}

fn parse_separated_idents(input: ParseStream) -> Result<Vec<Ident>, Error> {
let mut idents = Vec::new();
while !input.is_empty() {
match input.parse::<Ident>() {
Ok(ident) => idents.push(ident),
Err(_) => {
if input.parse::<Punct>().is_err() {
return Err(Error::new(input.span(), "Not a feature or a punctuation sign"));
}
}
}
}
Ok(idents)
}

fn find_attribute_last_in_path<'attr>(attrs: &'attr Vec<Attribute>, attribute_ident: &str) -> Option<&'attr Attribute> {
attrs.iter()
.filter(|attribute| attribute.path.segments.iter().last().is_some_and(|segment| segment.ident.to_string().eq(attribute_ident)))
Expand All @@ -270,42 +288,6 @@ fn find_attribute<'attr>(attrs: &'attr Vec<Attribute>, attribute_ident: &str) ->
.next()
}

#[derive(Debug)]
struct ValuedAsAttribute {
type_of_value: Type,
}

impl Parse for ValuedAsAttribute {
fn parse(input: ParseStream) -> syn::Result<Self> {
print_info("Trying to parse valued as attribute", &format!("{input:#?}"));
print_info("Trying to parse valued as attribute", &format!("{input:?}"));
input.parse::<Type>().map(|parsed_type| {
ValuedAsAttribute { type_of_value: parsed_type }
})
}
}

struct Features {
idents: Vec<Ident>,
}

impl Parse for Features {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut idents = Vec::new();
while !input.is_empty() {
match input.parse::<Ident>() {
Ok(ident) => idents.push(ident),
Err(_) => {
if input.parse::<Punct>().is_err() {
return Err(syn::Error::new(input.span(), "Not a feature or a punctuation sign"));
}
}
}
}
Ok(Features { idents })
}
}

/// Attribute macro used by the 'Valued' derive macro to indicate the type of your variant's values,
/// it poses as a simple derive macro, but it is used to modify your enum and prepare it for the
/// Indexed and Valued traits, currently, this only means adding '#[repr(usize)]' to your enum, and
Expand All @@ -320,12 +302,4 @@ pub fn enum_valued_as(_attr: TokenStream, item: TokenStream) -> TokenStream {
res.into()
}


const DEBUG: bool = false;

fn print_info(name: &str, info: &str) {
if !DEBUG { return; }
//eprintln!("--------------------- {} ---------------------\n", name);
//eprintln!("{info}\n", );
//eprintln!("-------------------------------------------------------------\n");
}
mod utils;
40 changes: 40 additions & 0 deletions indexed_valued_enums_derive/src/utils.rs
@@ -0,0 +1,40 @@
use alloc::string::ToString;

pub(crate) fn print_info<TNameRet: ToString, TInfoRet: ToString, TName: FnOnce() -> TNameRet, TInfo: FnOnce() -> TInfoRet>(_name: TName, _info: TInfo) {
//eprintln!("--------------------- {} ---------------------\n", (_name()).to_string());
//eprintln!("{}\n", (_info()).to_string());
//eprintln!("-------------------------------------------------------------\n");
}

pub(crate) trait ExpectElseResult<T, E> {
fn expect_else<TInfoRet: ToString, TInfo: FnOnce(&E) -> TInfoRet>(self, info: TInfo) -> T;
}

impl<T, E: core::fmt::Debug> ExpectElseResult<T, E> for Result<T, E> {
fn expect_else<TInfoRet: ToString, TInfo: FnOnce(&E) -> TInfoRet>(self, info: TInfo) -> T {
if self.is_ok() {
self.expect("")
} else {
let error_info = match &self {
Err(error) => info(error).to_string(),
_ => { panic!("Unreachable point"); }
};
self.expect(&error_info)
}
}
}


pub(crate) trait ExpectElseOption<T> {
fn expect_else<TInfoRet: ToString, TInfo: FnOnce() -> TInfoRet>(self, info: TInfo) -> T;
}

impl<T> ExpectElseOption<T> for Option<T> {
fn expect_else<TInfoRet: ToString, TInfo: FnOnce() -> TInfoRet>(self, info: TInfo) -> T {
if self.is_some() {
self.expect("")
} else {
self.expect(&info().to_string())
}
}
}

0 comments on commit f34e979

Please sign in to comment.