From c9516d1355d07cbb195544cb65c530bb377b187c Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sat, 22 Jul 2023 10:38:08 -0700 Subject: [PATCH] feat(bitfield): add `enum_from_bits!` macro This branch adds a macro to the `mycelium-bitfield` crate that allows automatically generating `FromBits` implementations for `enum` types that are `repr(u{N})`. For example, we can now write: ```rust mycelium_bitfield::enum_from_bits! { #[derive(Debug, Eq, PartialEq)] pub enum MyGeneratedEnum { /// Isn't this cool? Wow = 0b1001, /// It sure is! :D Whoa = 0b0110, } } ``` This probably _should_ be a `derive` proc-macro but I'm having so much fun doing `macro_rules!` crimes in the bitfield crate for whatever reason... Closes #443 --- bitfield/README.md | 28 +++- bitfield/src/bitfield.rs | 25 +++- bitfield/src/from_bits.rs | 301 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 348 insertions(+), 6 deletions(-) diff --git a/bitfield/README.md b/bitfield/README.md index 056eec5a..9e847bf1 100644 --- a/bitfield/README.md +++ b/bitfield/README.md @@ -204,11 +204,14 @@ and the code it generates. #### `FromBits` trait The [`FromBits`] trait can be implemented for user-defined types which can be -used as subfields of a [`bitfield!`]-generated structured bitfield type. +used as subfields of a [`bitfield!`]-generated structured bitfield type. This +trait may be manually implemented for any user-defined type that has a defined +bit representation, or generated automatically for `enum` types using the +[`enum_from_bits!`] macro. For example: ```rust -use mycelium_bitfield::{bitfield, FromBits}; +use mycelium_bitfield::{bitfield, enum_from_bits, FromBits}; // An enum type can implement the `FromBits` trait if it has a // `#[repr(uN)]` attribute. @@ -239,6 +242,19 @@ impl FromBits for MyEnum { } } +// Alternatively, the `enum_from_bits!` macro can be used to +// automatically generate a `FromBits` implementation for an +// enum type: +enum_from_bits! { + #[derive(Debug, Eq, PartialEq)] + pub enum MyGeneratedEnum { + /// Isn't this cool? + Wow = 0b1001, + /// It sure is! :D + Whoa = 0b0110, + } +} + bitfield! { pub struct TypedBitfield { /// Use the first two bits to represent a typed `MyEnum` value. @@ -256,16 +272,20 @@ bitfield! { /// `FromBits` is also implemented by (signed and unsigned) integer /// types. This will allow the next 8 bits to be treated as a `u8`. pub const A_BYTE: u8; + + /// We can also use the automatically generated enum: + pub const OTHER_ENUM: MyGeneratedEnum; } } // Unpacking a typed value with `get` will return that value, or panic if // the bit pattern is invalid: -let my_bitfield = TypedBitfield::from_bits(0b0011_0101_1001_1110); +let my_bitfield = TypedBitfield::from_bits(0b0010_0100_0011_0101_1001_1110); assert_eq!(my_bitfield.get(TypedBitfield::ENUM_VALUE), MyEnum::Baz); assert_eq!(my_bitfield.get(TypedBitfield::FLAG_1), true); assert_eq!(my_bitfield.get(TypedBitfield::FLAG_2), false); +assert_eq!(my_bitfield.get(TypedBitfield::OTHER_ENUM), MyGeneratedEnum::Wow); // The `try_get` method will return an error rather than panicking if an // invalid bit pattern is encountered: @@ -289,6 +309,8 @@ implementing [`FromBits`] for user-defined types. [bitflags-macro]: https://docs.rs/bitflags/latest/bitflags/macro.bitflags.html [`FromBits`]: https://docs.rs/mycelium-bitfield/latest/mycelium_bitfield/trait.FromBits.html +[`enum_from_bits!`]: + https://docs.rs/mycelium-bitfield/latest/mycelium_bitfield/macro.enum_from_bits.html [mbf-validation]: https://docs.rs/modular-bitfield/latest/modular_bitfield/#example-extra-safety-guard [`Pack64`]: diff --git a/bitfield/src/bitfield.rs b/bitfield/src/bitfield.rs index 65e806a5..4595a34a 100644 --- a/bitfield/src/bitfield.rs +++ b/bitfield/src/bitfield.rs @@ -139,10 +139,11 @@ /// ``` /// /// Bitfields may also contain typed values, as long as those values implement -/// the [`FromBits`] trait: +/// the [`FromBits`] trait. [`FromBits`] may be manually implemented, or +/// generated automatically for `enum` types using the [`enum_from_bits!] macro: /// /// ``` -/// use mycelium_bitfield::{bitfield, FromBits}; +/// use mycelium_bitfield::{bitfield, enum_from_bits, FromBits}; /// /// // An enum type can implement the `FromBits` trait if it has a /// // `#[repr(uN)]` attribute. @@ -173,6 +174,19 @@ /// } /// } /// +/// Alternatively, the `enum_from_bits!` macro can be used to +/// automatically generate a `FromBits` implementation for an +/// enum type: +/// enum_from_bits! { +/// #[derive(Debug, Eq, PartialEq)] +/// pub enum MyGeneratedEnum { +/// /// Isn't this cool? +/// Wow = 0b1001, +/// /// It sure is! :D +/// Whoa = 0b0110, +/// } +/// } +/// /// bitfield! { /// pub struct TypedBitfield { /// /// Use the first two bits to represent a typed `MyEnum` value. @@ -190,16 +204,20 @@ /// /// `FromBits` is also implemented by (signed and unsigned) integer /// /// types. This will allow the next 8 bits to be treated as a `u8`. /// pub const A_BYTE: u8; +/// +/// /// We can also use the automatically generated enum: +/// pub const OTHER_ENUM: MyGeneratedEnum; /// } /// } /// /// // Unpacking a typed value with `get` will return that value, or panic if /// // the bit pattern is invalid: -/// let my_bitfield = TypedBitfield::from_bits(0b0011_0101_1001_1110); +/// let my_bitfield = TypedBitfield::from_bits(0b0010_0100_0011_0101_1001_1110); /// /// assert_eq!(my_bitfield.get(TypedBitfield::ENUM_VALUE), MyEnum::Baz); /// assert_eq!(my_bitfield.get(TypedBitfield::FLAG_1), true); /// assert_eq!(my_bitfield.get(TypedBitfield::FLAG_2), false); +/// assert_eq!(my_bitfield.get(TypedBitfield::OTHER_ENUM), MyGeneratedEnum::Wow); /// /// // The `try_get` method will return an error rather than panicking if an /// // invalid bit pattern is encountered: @@ -334,6 +352,7 @@ /// [`example`]: crate::example /// [`ExampleBitfield`]: crate::example::ExampleBitfield /// [`FromBits`]: crate::FromBits +/// [`enum_from_bits!`]: crate::enum_from_bits! #[macro_export] macro_rules! bitfield { ( diff --git a/bitfield/src/from_bits.rs b/bitfield/src/from_bits.rs index adcbf4ed..92a80d5d 100644 --- a/bitfield/src/from_bits.rs +++ b/bitfield/src/from_bits.rs @@ -30,6 +30,257 @@ pub trait FromBits: Sized { fn into_bits(self) -> B; } +/// Generates automatic [`FromBits`] and [`core::convert::TryFrom`] +/// implementations for an `enum` type of [`repr(uN)`], where `uN` is one of +/// [`u8`], [`u16`], [`u32`], [`u64`], or [`u128`]. +/// +/// This allows an `enum` type to be used with the +/// [`bitfield!`](crate::bitfield!) macro without requiring a manual [`FromBits`] +/// implementation. Essentially, this macro can be thought of as being analogous +/// to `#[derive(FromBits, TryFrom)]`.[^1] +/// +/// # Generated Implementations +/// +/// This macro will automatically generate a [`FromBits`]`` and a +/// [`core::convert::TryFrom`]`` implementation for the defined `enum` type. +/// In addition, [`FromBits`] and [`core::convert::TryFrom`] implementations for +/// each unsized integer type *larger* than `uN` are also automatically +/// generated. The [`Copy`] and [`Clone`] traits are also derived for the +/// generated `enum`, as they are required by the [`FromBits`] implementation.. +/// +/// Generated `enum` types are [`repr(uN)]`]. +/// +/// Additional traits may be derived for the `enum` type, such as +/// [`PartialEq`], [`Eq`], and [`Default`]. These traits are not automatically +/// derived, as custom implementations may also be desired, depending on the +/// use-case. For example, the `Default` value for am `enum` may _not_ be all +/// zeroes. +/// +/// # Examples +/// +/// Basic usage: +/// +/// ```rust +/// use mycelium_bitfield::FromBits; +/// use core::convert::TryFrom; +/// +/// mycelium_bitfield::enum_from_bits! { +/// /// Doc comments can be added to generated enum types. +/// #[derive(Debug, PartialEq, Eq)] // additional `derive` attributes can be added +/// enum Example { // generate an enum represented by a u8 +/// Foo = 0b0000, +/// Bar = 0b0001, +/// Baz = 0b1000, +/// Qux = 0b0111, +/// } +/// } +/// +/// // the generated enum will implement the `FromBits` trait: +/// assert_eq!(Example::try_from_bits(0b1u8), Ok(Example::Bar)); +/// assert_eq!(FromBits::::into_bits(Example::Foo), 0); +/// +/// // `core::convert::TryFrom` implementations are also generated: +/// assert_eq!(Example::try_from(0b1000u8), Ok(Example::Baz)); +/// assert_eq!(0b0111u32.try_into(), Ok(Example::Qux)); +/// +/// // invalid bit-patterns return an error: +/// assert!(Example::try_from_bits(0b1001u8).is_err()); // invalid bit pattern +/// assert!(Example::try_from_bits(0b1000_0000u8).is_err()); // too many bits +/// ``` +/// +/// Only `u8`, `u16`, `u32`, `u64`, and `u128` may be used as `repr`s for +/// generated enums: +/// +/// ```rust,compile_fail +/// mycelium_bitfield::enum_from_bits! { +/// /// This won't work. Don't do this. +/// enum InvalidRepr { +/// This = 0b01, +/// Wont = 0b10, +/// Work = 0b11, +/// } +/// ``` +/// +/// [^1]: **Why Not `#[derive(FromBits)]`?** Some readers may be curious about why +/// this is a declarative macro, rather than a procedural `#[derive]` macro. +/// The answer is..."because I felt like it lol". This probably *should* be +/// a proc-macro, since it's essentially just deriving a trait +/// implementation. However, one of my goals for `mycelium-bitfield` was to +/// see how far I could go using only `macro_rules!` macros. This isn't +/// because I dislike procedural macros, or that I'm concerned about +/// proc-macro compile times --- I just thought it would be a fun challenge +/// to do everything declaratively, if it was possible. And, if you *do* +/// care about the potential build time impact of proc-macro dependencies, +/// this should help. :) +/// +/// [`repr(uN)`]: +/// https://doc.rust-lang.org/reference/type-layout.html#primitive-representations +#[macro_export] +macro_rules! enum_from_bits { + ( + $(#[$meta:meta])* $vis:vis enum $Type:ident<$uN:ident> { + $(#[$var1_meta:meta])* + $Variant1:ident = $value1:expr, + $( + $(#[$var_meta:meta])* + $Variant:ident = $value:expr + ),* $(,)? + } + ) => { + $(#[$meta])* + #[repr($uN)] + #[derive(Copy, Clone)] + $vis enum $Type { + $(#[$var1_meta])* + $Variant1 = $value1, + $( + $(#[$var_meta])* + $Variant = $value + ),* + } + + impl $Type { + const VARIANTS: &[Self] = &[ + Self::$Variant1, + $( + Self::$Variant, + )* + ]; + + const MAX_VARIANT: Self = { + // crappy while loop because `for` and iterator adapters don't + // work in const-eval... + let mut max = Self::VARIANTS[0]; + let mut i = 0; + while i < Self::VARIANTS.len() { + if Self::VARIANTS[i] as $uN > max as $uN { + max = Self::VARIANTS[i]; + } + i += 1; + } + max + }; + + const MIN_VARIANT: Self = { + let mut min = Self::VARIANTS[0]; + let mut i = 0; + while i < Self::VARIANTS.len() { + if (Self::VARIANTS[i] as $uN) < min as $uN { + min = Self::VARIANTS[i]; + } + i += 1; + } + min + }; + + const NEEDED_BITS: u32 = { + // we need at least (bit position of `MAX_VARIANT`'s MSB) bits + // to represent a value of this type. + let max = Self::MAX_VARIANT as $uN; + <$uN>::BITS - max.leading_zeros() + }; + + const ERROR: &'static str = concat!( + "invalid value for ", + stringify!($Type), + ": expected one of [", + stringify!($value1), + $( + ", ", stringify!($value), + )* + "]" + ); + } + + #[automatically_derived] + impl core::convert::TryFrom<$uN> for $Type { + type Error = &'static str; + + #[inline] + fn try_from(value: $uN) -> Result { + match value { + $value1 => Ok(Self::$Variant1), + $( + $value => Ok(Self::$Variant), + )* + _ => Err(Self::ERROR), + } + } + } + + #[automatically_derived] + impl $crate::FromBits<$uN> for $Type { + type Error = &'static str; + const BITS: u32 = Self::NEEDED_BITS; + + #[inline] + fn try_from_bits(u: $uN) -> Result { + Self::try_from(u) + } + + #[inline] + fn into_bits(self) -> $uN { + self as $uN + } + } + + $crate::enum_from_bits!(@bigger $uN, $Type); + }; + + (@bigger u8, $Type:ty) => { + $crate::enum_from_bits! { @impl u8, $Type, u16, u32, u64, u128, usize } + }; + (@bigger u16, $Type:ty) => { + $crate::enum_from_bits! { @impl u16, $Type, u32, u64, u128, usize } + }; + (@bigger u32, $Type:ty) => { + $crate::enum_from_bits! { @impl u32, $Type, u64, u128, usize } + }; + (@bigger u64, $Type:ty) => { + $crate::enum_from_bits! { @impl u128 } + }; + (@bigger $uN:ty, $Type:ty) => { + compile_error!( + concat!( + "repr for ", + stringify!($Type), + " must be one of u8, u16, u32, u64, or u128 (got ", + stringify!($uN), + ")", + )); + }; + (@impl $uN:ty, $Type:ty, $($bigger:ty),+) => { + $( + + #[automatically_derived] + impl $crate::FromBits<$bigger> for $Type { + type Error = &'static str; + const BITS: u32 = Self::NEEDED_BITS; + + #[inline] + fn try_from_bits(u: $bigger) -> Result { + Self::try_from(u as $uN) + } + + #[inline] + fn into_bits(self) -> $bigger { + self as $bigger + } + } + + #[automatically_derived] + impl core::convert::TryFrom<$bigger> for $Type { + type Error = &'static str; + #[inline] + fn try_from(u: $bigger) -> Result { + Self::try_from(u as $uN) + } + } + )+ + }; + +} + macro_rules! impl_frombits_for_ty { ($(impl FromBits<$($F:ty),+> for $T:ty {})+) => { $( @@ -129,3 +380,53 @@ impl_frombits_for_ty! { impl FromBits for u64 {} impl FromBits for i64 {} } + +#[cfg(test)] +mod tests { + use super::*; + + enum_from_bits! { + #[derive(Debug, PartialEq, Eq)] + enum Test { + Foo = 0b0000, + Bar = 0b0001, + Baz = 0b1000, + Qux = 0b0111, + } + } + + #[test] + fn enum_max_variant() { + assert_eq!(Test::MAX_VARIANT, Test::Baz); + } + + #[test] + fn enum_min_variant() { + assert_eq!(Test::MIN_VARIANT, Test::Foo); + } + + #[test] + fn enum_needed_bits() { + assert_eq!(Test::NEEDED_BITS, 4); + } + + #[test] + fn enum_roundtrips() { + for variant in [Test::Foo, Test::Bar, Test::Baz, Test::Qux] { + let bits = dbg!(variant as u8); + assert_eq!(dbg!(Test::try_from_bits(bits)), Ok(variant)); + assert_eq!(dbg!(Test::try_from_bits(bits as u16)), Ok(variant)); + assert_eq!(dbg!(Test::try_from_bits(bits as u32)), Ok(variant)); + assert_eq!(dbg!(Test::try_from_bits(bits as u64)), Ok(variant)); + assert_eq!(dbg!(Test::try_from_bits(bits as u128)), Ok(variant)); + } + } + + #[test] + fn enum_invalid() { + for value in [0b1001u8, 0b1000_0000u8, 0b1000_0001u8, 0b1111u8] { + dbg!(value); + assert!(dbg!(Test::try_from_bits(value)).is_err()); + } + } +}