From c02d6b1be897079c8893344e22a6d85dcbfe2e57 Mon Sep 17 00:00:00 2001 From: Cryptjar Date: Fri, 14 Jan 2022 04:51:28 +0100 Subject: [PATCH] Add an enum_properties_lazy macro that allows arbitrary values. --- Cargo.toml | 16 +++ examples/basic_usage_lazy.rs | 38 ++++++ examples/non_const_lazy.rs | 45 +++++++ examples/with_all_features_lazy.rs | 84 ++++++++++++ src/lib.rs | 199 +++++++++++++++++++++++++++++ 5 files changed, 382 insertions(+) create mode 100644 examples/basic_usage_lazy.rs create mode 100644 examples/non_const_lazy.rs create mode 100644 examples/with_all_features_lazy.rs diff --git a/Cargo.toml b/Cargo.toml index d362744..c096867 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,19 @@ categories = ["no-std", "rust-patterns"] [badges] maintenance = { status = "actively-developed" } +[dependencies.lazy_static] +version = "1.4" +optional = true + + +[[example]] +name = "basic_usage_lazy" +required-features = ["lazy_static"] + +[[example]] +name = "with_all_features_lazy" +required-features = ["lazy_static"] + +[[example]] +name = "non_const_lazy" +required-features = ["lazy_static"] \ No newline at end of file diff --git a/examples/basic_usage_lazy.rs b/examples/basic_usage_lazy.rs new file mode 100644 index 0000000..c0cc5f2 --- /dev/null +++ b/examples/basic_usage_lazy.rs @@ -0,0 +1,38 @@ +// Requires the "lazy_static" feature + +use enum_properties::enum_properties_lazy; + +pub struct FruitProperties { + pub name: &'static str, + pub description: &'static str, + pub weight: f32, +} + +enum_properties_lazy! { + #[repr(u8)] + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + pub enum Fruit: FruitProperties { + Apple { + name: "apple", + description: "Keeps the doctor away.", + weight: 0.1, + }, + Orange { + name: "orange", + description: "Round and refreshing.", + weight: 0.13, + }, + Banana { + name: "banana", + description: "Elongated and yellow.", + weight: 0.12, + }, + } +} + +fn main() { + println!("An {} weighs about {} kg.", + Fruit::Apple.name, + Fruit::Apple.weight + ); +} diff --git a/examples/non_const_lazy.rs b/examples/non_const_lazy.rs new file mode 100644 index 0000000..4032633 --- /dev/null +++ b/examples/non_const_lazy.rs @@ -0,0 +1,45 @@ + +use enum_properties::enum_properties_lazy; + +/// Custom struct, none-copy, none-clone, not even const-init +pub struct FruitProperties { + pub name: String, + pub features: Vec, +} + +enum_properties_lazy! { + #[repr(u8)] + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + pub enum Fruit: FruitProperties { + Apple { + name: "apple".to_string(), + features: Vec::new(), + }, + Orange { + name: { + // Allows arbitrarily complex runtime initializers, thanks + // to the magic of lazy_static + let mut builder = String::new(); + builder.push_str("orange"); + builder + }, + features: { + let mut builder = Vec::new(); + builder.push(0.13); + builder.push(0.42); + builder + }, + }, + Banana { + name: "banana".into(), + features: vec![0.12, 0.34, 0.32], + }, + } +} + +fn main() { + println!("An {} weighs, {:?}.", + Fruit::Apple.name, + Fruit::Apple.features + ); +} diff --git a/examples/with_all_features_lazy.rs b/examples/with_all_features_lazy.rs new file mode 100644 index 0000000..bea6951 --- /dev/null +++ b/examples/with_all_features_lazy.rs @@ -0,0 +1,84 @@ +// Requires the "lazy_static" feature + +use enum_properties::enum_properties_lazy; + +pub struct FruitProperties { + pub name: String, + pub description: &'static str, + pub weight: f32, +} + +pub const DEFAULT_FRUIT_PROPERTIES: FruitProperties = FruitProperties { + name: String::new(), + description: "Round and refreshing.", + weight: 0.1, +}; + +enum_properties_lazy! { + // Just like a normal `enum`, an enum defined within an invocation of + // `enum_properties` can have attributes: + #[doc = "Fruit"] + #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] + pub enum Fruit: FruitProperties { + // Variants can be unit-like, followed only by their static properties: + #[doc = "Fruit::Apple"] // Variants can also have attributes + Apple { + name: "apple".to_string(), + description: "Keeps the doctor away." + }, + // Variants can also be tuple-like, containing some non-static data. + // The tuple is defined after the static properties. + #[doc = "Fruit::Orange"] + Orange { + name: "orange".to_string(), + weight: 0.13, + } ( + // Members of a tuple-like variant can also have attributes: + #[doc = "Fruit::Orange(segment_count)"] + usize, + ), + // Variants can also be struct-like, containing some non-static data. + // The struct is defined after the static properties. + #[doc = "Fruit::Banana"] + Banana { + name: "banana".to_string(), + description: "Elongated and yellow.", + weight: 0.12, + } { + // Members of a struct-like variant can also have attributes: + #[doc = "Fruit::Banana { length }"] + length: f32, + }, + // This syntax specifies defaults, such that each variant does not have + // to define all of its static properties. Properties left undefined + // are drawn from this value instead. + .. DEFAULT_FRUIT_PROPERTIES + } +} + +fn main() { + let fruits = [ + Fruit::Apple, + Fruit::Orange(10), + Fruit::Banana { length: 18.0 }, + ]; + + for &fruit in &fruits { + println!("Name: {}", fruit.name); + println!("Weight: {} kg", fruit.weight); + print!("Description: {}", fruit.description); + + match fruit { + Fruit::Apple => { + println!(" This one is like all the others."); + } + Fruit::Orange(segment_count) => { + println!(" This one is made of {} segments.", segment_count); + } + Fruit::Banana { length } => { + println!(" This one is {} cm long.", length); + } + } + } + println!(""); +} diff --git a/src/lib.rs b/src/lib.rs index b16d577..d824a19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,6 +60,13 @@ //! ``` //! + +// Lazy static reexport so we can use it from our macro +#[cfg(feature = "lazy_static")] +#[doc(hidden)] +pub use lazy_static; + + /// Defines a new `enum` and implements [`Deref`] for it. /// /// The `enum` will [`Deref`] to a variant-specific [`static` item]. @@ -270,3 +277,195 @@ macro_rules! enum_properties { }; } + + +/// Defines a new `enum` and implements [`Deref`] for it. +/// +/// The `enum` will [`Deref`] to a variant-specific [`lazy_static` item]. +/// Unlike [`enum_properties`], this macro allows properties that require +/// runtime initialization instead of being const-init. +/// Thus [`enum_properties_lazy`] allows using `Vec` and `String` and much more. +/// +/// **Notice** that this macro requires the **`lazy_static`** crate feature. +/// +/// # Examples +/// +/// ```rust +/// use enum_properties::enum_properties_lazy; +/// +/// struct Shape { +/// name: String, +/// verts: Vec<[i32;2]>, +/// } +/// +/// enum_properties_lazy! { +/// #[derive(Clone, Copy, Debug)] +/// enum SimpleShape: Shape { +/// Point { +/// name: "Point".to_string(), +/// verts: Vec::new(), +/// }, +/// Line { +/// name: { +/// // Works with arbitrary initialization code +/// let mut builder = String::new(); +/// builder.push_str("line"); +/// builder +/// }, +/// verts: { +/// let mut vec = Vec::new(); +/// vec.push([0,0]); +/// vec.push([1,1]); +/// vec +/// }, +/// }, +/// Square { +/// name: "square".to_string(), +/// verts: vec![[0,0], [0,1], [1,0], [1,1]], +/// }, +/// } +/// } +/// +/// fn main() { +/// let line = SimpleShape::Line; +/// assert_eq!(&line.verts, &[[0,0], [1,1]]); +/// } +/// ``` +/// +/// [`Deref`]: https://doc.rust-lang.org/std/ops/trait.Deref.html +/// [`lazy_static` item]: https://crates.io/crates/lazy_static +#[cfg(feature = "lazy_static")] +#[macro_export] +macro_rules! enum_properties_lazy { + ( + $(#[$($enum_attribute_token:tt)*])* + $public:vis enum $Enum:ident : $EnumProperties:ident { + $( + $(#[$($variant_attribute_token:tt)*])* + $variant:ident { + $($field:ident : $value:expr),* $(, $(.. $default:expr)?)? + } + $( + $(@$is_struct_variant_marker:tt)? + { + $($struct_variant_content:tt)* + } + )? + $(( + $( + $(#[$($tuple_attribute_token:tt)*])* + $(@$tuple_variant_item_marker:tt)? + $tuple_variant_item:ty + ),* $(,)? + ))? + $(= $discriminant:expr)? + ),* $(,)? + } + ) => { + $(#[$($enum_attribute_token)*])* + $public enum $Enum { + $( + $(#[$($variant_attribute_token)*])* + $variant + $({$($struct_variant_content)*})? + $(( + $( + $(#[$($tuple_attribute_token)*])* + $tuple_variant_item + ),* + ))? + $(= $discriminant)? + ),* + } + + impl core::ops::Deref for $Enum { + type Target = $EnumProperties; + fn deref(&self) -> &Self::Target { + match self { + $( + $Enum::$variant + $({ .. $(@$is_struct_variant_marker)?})? + $(($(_ $(@$tuple_variant_item_marker)?),*))? + => { + $crate::lazy_static::lazy_static!{ + static ref FOO: $EnumProperties = { + $EnumProperties { + $($field: $value),* $(, $(.. $default)?)? + } + }; + } + + &*FOO + } + ),* + } + } + } + }; + + ( + $(#[$($enum_attribute_token:tt)*])* + $public:vis enum $Enum:ident : $EnumProperties:ident { + $( + $(#[$($variant_attribute_token:tt)*])* + $variant:ident { + $($field:ident : $value:expr),* $(,)? + } + $( + $(@$is_struct_variant_marker:tt)? + { + $($struct_variant_content:tt)* + } + )? + $(( + $( + $(@$tuple_variant_item_marker:tt)? + $(#[$($tuple_attribute_token:tt)*])* + $tuple_variant_item:ty + ),* $(,)? + ))? + $(= $discriminant:expr)? + ),* , .. $default:expr + } + ) => { + $(#[$($enum_attribute_token)*])* + $public enum $Enum { + $( + $(#[$($variant_attribute_token)*])* + $variant + $({$($struct_variant_content)*})? + $(( + $( + $(#[$($tuple_attribute_token)*])* + $tuple_variant_item + ),* + ))? + $(= $discriminant)? + ),* + } + + impl core::ops::Deref for $Enum { + type Target = $EnumProperties; + fn deref(&self) -> &Self::Target { + match self { + $( + $Enum::$variant + $({ .. $(@$is_struct_variant_marker)?})? + $(($(_ $(@$tuple_variant_item_marker)?),*))? + => { + $crate::lazy_static::lazy_static!{ + static ref FOO: $EnumProperties = { + $EnumProperties { + $($field: $value),* , .. $default + } + }; + } + + &*FOO + } + ),* + } + } + } + }; +}