diff --git a/README.md b/README.md index 4a0c8f1..3e46f66 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,29 @@ assert_eq!(player.finished_tasks(), 5); assert_eq!(player.kills(), 3); assert_eq!(*player, 0b01101011); ``` +## `#[bitmap_attr]` Attribute Macro + +If you prefer attribute macro syntax, you can use `#[bitmap_attr]` instead of `bitmap!`. Both work exactly the same way. + +```rust +use bitmap::bitmap_attr; + +#[bitmap_attr] +struct Player { + imposter: u1, + finished_tasks: u3, + kills: u3, +} + +let mut player = Player(0); +player.set_imposter(1) + .set_finished_tasks(5) + .set_kills(3); + +assert_eq!(player.imposter(), 1); +assert_eq!(player.finished_tasks(), 5); +assert_eq!(player.kills(), 3); +} ### Accessing fields diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 6f475b0..9a0a033 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,5 +1,6 @@ use proc_macro::TokenStream; use syn::parse_macro_input; +use syn::DeriveInput; mod generator; mod parser; @@ -116,6 +117,7 @@ mod parser; /// assert_eq!(core::mem::size_of::(), 8); /// ``` /// + #[proc_macro] pub fn bitmap(input: TokenStream) -> TokenStream { let parsed = parse_macro_input!(input as parser::BitmapInput); @@ -124,3 +126,76 @@ pub fn bitmap(input: TokenStream) -> TokenStream { Err(err) => err.to_compile_error().into(), } } + +/// Generates a packed bitmap newtype struct with field-level bit access. +/// +/// This attribute macro works exactly like the `bitmap!` macro but uses attribute syntax. +/// +/// ### Usage Example +/// ``` +/// use bitmap::bitmap_attr; +/// +/// #[bitmap_attr] +/// struct Player { +/// imposter: u1, +/// finished_tasks: u3, +/// kills: u3, +/// } +/// +/// let mut player = Player(0); +/// assert_eq!(std::mem::size_of::(), 1); +/// +/// player.set_imposter(1); +/// player.set_finished_tasks(5); +/// player.set_kills(3); +/// +/// assert_eq!(player.imposter(), 1); +/// assert_eq!(player.finished_tasks(), 5); +/// assert_eq!(player.kills(), 3); +/// assert_eq!(*player, 0b01101011); +/// ``` +#[proc_macro_attribute] +pub fn bitmap_attr(_args: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let bitmap_input = convert_derive_to_bitmap_input(input); + + match generator::expand_bitmap(bitmap_input) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + } +} + +fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput { + let name = input.ident; + + let syn::Data::Struct(data_struct) = input.data else { + panic!("#[bitmap_attr] can only be used on structs"); + }; + + let syn::Fields::Named(fields_named) = data_struct.fields else { + panic!("#[bitmap_attr] struct must have named fields"); + }; + + let fields = fields_named.named.into_iter().map(|field| { + let field_name = field.ident.expect("Field must have a name"); + let field_type = field.ty; + + let syn::Type::Path(type_path) = field_type else { + panic!("Field type must be a path like u1, u7, etc."); + }; + + let segment = type_path.path.segments.last().expect("Type path must have a segment"); + let size = parser::parse_bit_width(&segment.ident).expect("Invalid bit width"); + + parser::FieldDef { + name: field_name, + size, + } + }).collect(); + + parser::BitmapInput { + name, + fields, + } +} diff --git a/macros/src/parser.rs b/macros/src/parser.rs index a7f1ca0..547a0dd 100644 --- a/macros/src/parser.rs +++ b/macros/src/parser.rs @@ -33,19 +33,22 @@ impl Parse for FieldDef { let _: Token![:] = input.parse()?; let ty: Ident = input.parse()?; - let ty_str = ty.to_string(); - let ty_str = ty_str.as_str(); - if !ty_str.starts_with("u") { - return Err(syn::Error::new_spanned(ty, format!("Invalid type {ty_str}, expected u{{1..128}}"))); - } - let size = *match &ty_str[1..].parse::() { - Ok(val) => val, - Err(e) => return Err(syn::Error::new_spanned(ty, format!("Could not parse type size: {e}"))), - }; - if size == 0 || size > 128 { - return Err(syn::Error::new_spanned(ty, format!("Invalid size for {ty_str}, expected u{{1..128}}"))); - } + let size = parse_bit_width(&ty)?; Ok(FieldDef { name, size }) } } + +pub fn parse_bit_width(ty: &syn::Ident) -> Result { + let ty_str = ty.to_string(); + if !ty_str.starts_with("u") { + return Err(syn::Error::new_spanned(ty, format!("Invalid type {ty_str}, expected u{{1..128}}"))); + } + let size = ty_str[1..] + .parse::() + .map_err(|e| syn::Error::new_spanned(ty, format!("Could not parse type size: {e}")))?; + if size == 0 || size > 128 { + return Err(syn::Error::new_spanned(ty, format!("Invalid size for {ty_str}, expected u{{1..128}}"))); + } + Ok(size) +} diff --git a/src/lib.rs b/src/lib.rs index c2e1945..fd240d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,10 @@ #![no_std] pub use macros::bitmap; +pub use macros::bitmap_attr; pub use traits::*; + pub mod traits; #[test] diff --git a/tests/compare_macros.rs b/tests/compare_macros.rs new file mode 100644 index 0000000..dae1aa7 --- /dev/null +++ b/tests/compare_macros.rs @@ -0,0 +1,29 @@ +use bitmap::{bitmap, bitmap_attr}; + +bitmap!( + struct OldWay { + flag: u1, + counter: u7, + } +); + +#[bitmap_attr] +struct NewWay { + flag: u1, + counter: u7, +} + +#[test] +fn test_both_macros_produce_same_api() { + let mut old = OldWay(0); + let mut new = NewWay(0); + + old.set_flag(1).set_counter(42); + new.set_flag(1).set_counter(42); + + assert_eq!(old.flag(), new.flag()); + assert_eq!(old.counter(), new.counter()); + assert_eq!(*old, *new); + + println!("Both macros produce identical results!"); +} diff --git a/tests/test_attr.rs b/tests/test_attr.rs new file mode 100644 index 0000000..795c08d --- /dev/null +++ b/tests/test_attr.rs @@ -0,0 +1,30 @@ +use bitmap::bitmap_attr; + +#[bitmap_attr] +struct TestBits { + flag: u1, + counter: u7, +} + +#[test] +fn test_attribute_macro_creates_correct_api() { + let mut bits = TestBits(0); + + bits.set_flag(1); + bits.set_counter(42); + + assert_eq!(bits.flag(), 1); + assert_eq!(bits.counter(), 42); + + assert_eq!(*bits, 0b10101010); + + println!("Attribute macro creates correct bitmap API!"); +} + +#[test] +fn test_attribute_macro_deref_and_into() { + let bits = TestBits(0xFF); + let raw_value: u8 = bits.into(); + assert_eq!(raw_value, 0xFF); + assert_eq!(*bits, 0xFF); +}