From 61477eae3eb5bba0e3a1ed85ef99fef461950a3d Mon Sep 17 00:00:00 2001 From: RabbitAlbatross Date: Tue, 30 Sep 2025 10:19:16 +0530 Subject: [PATCH 1/9] Add #[bitmap_attr] attribute macro as alternative syntax - Implements attribute macro that provides the same functionality as bitmap! - Both macros now generate identical code - Added comprehensive tests to ensure compatibility - Maintains all existing functionality while providing cleaner syntax option --- macros/src/lib.rs | 60 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ tests/compare_macros.rs | 34 +++++++++++++++++++++++ tests/test_attr.rs | 33 +++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 tests/compare_macros.rs create mode 100644 tests/test_attr.rs diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 6f475b0..fbbda0c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,5 +1,8 @@ use proc_macro::TokenStream; use syn::parse_macro_input; +use syn::DeriveInput; +use quote::quote; +use quote::ToTokens; mod generator; mod parser; @@ -116,6 +119,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 +128,59 @@ pub fn bitmap(input: TokenStream) -> TokenStream { Err(err) => err.to_compile_error().into(), } } + +#[proc_macro_attribute] +pub fn bitmap_attr(_args: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + // Convert the attribute macro input to the existing BitmapInput format + let bitmap_input = convert_derive_to_bitmap_input(input); + + // Use the EXACT SAME expansion logic as the bitmap! macro + 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; // Use 'name' not 'struct_name' + + // Extract struct fields + 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"); + }; + + // Convert each field to the format expected by the existing parser + 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; + + // Extract the size from the type (e.g., u1 -> 1, u7 -> 7) + let type_str = field_type.to_token_stream().to_string(); + + if !type_str.starts_with("u") { + panic!("Field type must be unsigned integer like u1, u2, etc."); + } + + let size: u8 = type_str[1..].parse().expect("Invalid bit width"); + + if size == 0 || size > 128 { + panic!("Invalid size for {}, expected u{{1..128}}", type_str); + } + + parser::FieldDef { + name: field_name, + size, + } + }).collect(); + + parser::BitmapInput { + name, // Use 'name' not 'struct_name' + fields, + } +} \ No newline at end of file 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..3213564 --- /dev/null +++ b/tests/compare_macros.rs @@ -0,0 +1,34 @@ +// tests/compare_macros.rs +use bitmap::{bitmap, bitmap_attr}; + +// Function-like macro (original) +bitmap!( + struct OldWay { + flag: u1, + counter: u7, + } +); + +// Attribute macro (new) +#[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); + + // Both should have the same methods + old.set_flag(1).set_counter(42); + new.set_flag(1).set_counter(42); + + // Both should produce the same results + assert_eq!(old.flag(), new.flag()); + assert_eq!(old.counter(), new.counter()); + assert_eq!(*old, *new); + + println!("Both macros produce identical results!"); +} \ No newline at end of file diff --git a/tests/test_attr.rs b/tests/test_attr.rs new file mode 100644 index 0000000..10e6738 --- /dev/null +++ b/tests/test_attr.rs @@ -0,0 +1,33 @@ +// tests/test_attr.rs +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); + + // Test that the macro generated the correct methods + bits.set_flag(1); + bits.set_counter(42); + + assert_eq!(bits.flag(), 1); + assert_eq!(bits.counter(), 42); + + // Test the raw value + assert_eq!(*bits, 0b10101010); // flag=1 (MSB), counter=42 + + 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); +} \ No newline at end of file From c6882b28a1425b1d5741e6ec69402974e797fe01 Mon Sep 17 00:00:00 2001 From: RabbitAlbatross Date: Tue, 30 Sep 2025 10:27:49 +0530 Subject: [PATCH 2/9] Add #[bitmap_attr] attribute macro - Implements feature requested in issue #8 - Provides alternative syntax to bitmap! macro - Both macros generate identical code and functionality - Added comprehensive tests to verify compatibility - Updated README with usage examples --- README.md | 23 +++++++++++++++++++++++ macros/src/lib.rs | 14 +++++++------- tests/compare_macros.rs | 1 - tests/test_attr.rs | 7 +++---- 4 files changed, 33 insertions(+), 12 deletions(-) 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 fbbda0c..adbfd62 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; use syn::parse_macro_input; use syn::DeriveInput; -use quote::quote; + use quote::ToTokens; mod generator; @@ -133,7 +133,7 @@ pub fn bitmap(input: TokenStream) -> TokenStream { pub fn bitmap_attr(_args: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - // Convert the attribute macro input to the existing BitmapInput format + // Converting the attribute macro input to the existing BitmapInput format let bitmap_input = convert_derive_to_bitmap_input(input); // Use the EXACT SAME expansion logic as the bitmap! macro @@ -144,9 +144,9 @@ pub fn bitmap_attr(_args: TokenStream, input: TokenStream) -> TokenStream { } fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput { - let name = input.ident; // Use 'name' not 'struct_name' + let name = input.ident; - // Extract struct fields + // Extracting struct fields let syn::Data::Struct(data_struct) = input.data else { panic!("#[bitmap_attr] can only be used on structs"); }; @@ -155,12 +155,12 @@ fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput { panic!("#[bitmap_attr] struct must have named fields"); }; - // Convert each field to the format expected by the existing parser + // Converting each field to the format expected by the existing parser 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; - // Extract the size from the type (e.g., u1 -> 1, u7 -> 7) + // Extracting the size from the type (e.g., u1 -> 1, u7 -> 7) let type_str = field_type.to_token_stream().to_string(); if !type_str.starts_with("u") { @@ -180,7 +180,7 @@ fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput { }).collect(); parser::BitmapInput { - name, // Use 'name' not 'struct_name' + name, fields, } } \ No newline at end of file diff --git a/tests/compare_macros.rs b/tests/compare_macros.rs index 3213564..197203a 100644 --- a/tests/compare_macros.rs +++ b/tests/compare_macros.rs @@ -1,4 +1,3 @@ -// tests/compare_macros.rs use bitmap::{bitmap, bitmap_attr}; // Function-like macro (original) diff --git a/tests/test_attr.rs b/tests/test_attr.rs index 10e6738..461df74 100644 --- a/tests/test_attr.rs +++ b/tests/test_attr.rs @@ -1,4 +1,3 @@ -// tests/test_attr.rs use bitmap::bitmap_attr; #[bitmap_attr] @@ -11,15 +10,15 @@ struct TestBits { fn test_attribute_macro_creates_correct_api() { let mut bits = TestBits(0); - // Test that the macro generated the correct methods + // Testing that the macro generated the correct methods bits.set_flag(1); bits.set_counter(42); assert_eq!(bits.flag(), 1); assert_eq!(bits.counter(), 42); - // Test the raw value - assert_eq!(*bits, 0b10101010); // flag=1 (MSB), counter=42 + // Testing the raw value + assert_eq!(*bits, 0b10101010); println!("Attribute macro creates correct bitmap API!"); } From 46c14d821574e25647f0e4c19714b93eb7f6bf10 Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:40:00 +0530 Subject: [PATCH 3/9] Update lib.rs removing unnecessary llm comments --- macros/src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index adbfd62..7d8b5dc 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -133,10 +133,8 @@ pub fn bitmap(input: TokenStream) -> TokenStream { pub fn bitmap_attr(_args: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - // Converting the attribute macro input to the existing BitmapInput format let bitmap_input = convert_derive_to_bitmap_input(input); - // Use the EXACT SAME expansion logic as the bitmap! macro match generator::expand_bitmap(bitmap_input) { Ok(tokens) => tokens.into(), Err(err) => err.to_compile_error().into(), @@ -146,7 +144,6 @@ pub fn bitmap_attr(_args: TokenStream, input: TokenStream) -> TokenStream { fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput { let name = input.ident; - // Extracting struct fields let syn::Data::Struct(data_struct) = input.data else { panic!("#[bitmap_attr] can only be used on structs"); }; @@ -155,12 +152,10 @@ fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput { panic!("#[bitmap_attr] struct must have named fields"); }; - // Converting each field to the format expected by the existing parser 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; - // Extracting the size from the type (e.g., u1 -> 1, u7 -> 7) let type_str = field_type.to_token_stream().to_string(); if !type_str.starts_with("u") { @@ -183,4 +178,4 @@ fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput { name, fields, } -} \ No newline at end of file +} From f263dba4bf5dde38ab92841e269906dfa3afe9cc Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:41:44 +0530 Subject: [PATCH 4/9] Update parser.rs --- macros/src/parser.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/macros/src/parser.rs b/macros/src/parser.rs index a7f1ca0..ba2d0ca 100644 --- a/macros/src/parser.rs +++ b/macros/src/parser.rs @@ -49,3 +49,16 @@ impl Parse for FieldDef { 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) +} From d3f0a5f7687813a72e69262c01dba06811663c03 Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:46:00 +0530 Subject: [PATCH 5/9] Update lib.rs --- macros/src/lib.rs | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 7d8b5dc..5de6baf 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -129,6 +129,34 @@ pub fn bitmap(input: TokenStream) -> TokenStream { } } +/// 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); @@ -142,7 +170,7 @@ pub fn bitmap_attr(_args: TokenStream, input: TokenStream) -> TokenStream { } fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput { - let name = input.ident; + let name = input.ident; let syn::Data::Struct(data_struct) = input.data else { panic!("#[bitmap_attr] can only be used on structs"); @@ -156,17 +184,12 @@ fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput { let field_name = field.ident.expect("Field must have a name"); let field_type = field.ty; - let type_str = field_type.to_token_stream().to_string(); - - if !type_str.starts_with("u") { - panic!("Field type must be unsigned integer like u1, u2, etc."); - } + let syn::Type::Path(type_path) = field_type else { + panic!("Field type must be a path like u1, u7, etc."); + }; - let size: u8 = type_str[1..].parse().expect("Invalid bit width"); - - if size == 0 || size > 128 { - panic!("Invalid size for {}, expected u{{1..128}}", type_str); - } + 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, @@ -175,7 +198,7 @@ fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput { }).collect(); parser::BitmapInput { - name, + name, fields, } } From ca542537e56c9fd3b6671d863fbb48c4a2d194fa Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:40:02 +0530 Subject: [PATCH 6/9] Update test_attr.rs --- tests/test_attr.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_attr.rs b/tests/test_attr.rs index 461df74..795c08d 100644 --- a/tests/test_attr.rs +++ b/tests/test_attr.rs @@ -10,14 +10,12 @@ struct TestBits { fn test_attribute_macro_creates_correct_api() { let mut bits = TestBits(0); - // Testing that the macro generated the correct methods bits.set_flag(1); bits.set_counter(42); assert_eq!(bits.flag(), 1); assert_eq!(bits.counter(), 42); - // Testing the raw value assert_eq!(*bits, 0b10101010); println!("Attribute macro creates correct bitmap API!"); @@ -29,4 +27,4 @@ fn test_attribute_macro_deref_and_into() { let raw_value: u8 = bits.into(); assert_eq!(raw_value, 0xFF); assert_eq!(*bits, 0xFF); -} \ No newline at end of file +} From 579d257e4bb6121e2104f1d9226ce1f0aaccc8dd Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:40:20 +0530 Subject: [PATCH 7/9] Update compare_macros.rs --- tests/compare_macros.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/compare_macros.rs b/tests/compare_macros.rs index 197203a..dae1aa7 100644 --- a/tests/compare_macros.rs +++ b/tests/compare_macros.rs @@ -1,6 +1,5 @@ use bitmap::{bitmap, bitmap_attr}; -// Function-like macro (original) bitmap!( struct OldWay { flag: u1, @@ -8,7 +7,6 @@ bitmap!( } ); -// Attribute macro (new) #[bitmap_attr] struct NewWay { flag: u1, @@ -20,14 +18,12 @@ fn test_both_macros_produce_same_api() { let mut old = OldWay(0); let mut new = NewWay(0); - // Both should have the same methods old.set_flag(1).set_counter(42); new.set_flag(1).set_counter(42); - // Both should produce the same results assert_eq!(old.flag(), new.flag()); assert_eq!(old.counter(), new.counter()); assert_eq!(*old, *new); println!("Both macros produce identical results!"); -} \ No newline at end of file +} From dba59635a15220f3bbb74a9d7c6ebc3596628fcc Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:47:25 +0530 Subject: [PATCH 8/9] Update lib.rs --- macros/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 5de6baf..9a0a033 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,8 +1,6 @@ use proc_macro::TokenStream; use syn::parse_macro_input; -use syn::DeriveInput; - -use quote::ToTokens; +use syn::DeriveInput; mod generator; mod parser; @@ -156,7 +154,6 @@ pub fn bitmap(input: TokenStream) -> TokenStream { /// 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); From 1a5d9af984f1215667c8e6544096cbdb5a60b1b8 Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:51:45 +0530 Subject: [PATCH 9/9] Update parser.rs --- macros/src/parser.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/macros/src/parser.rs b/macros/src/parser.rs index ba2d0ca..547a0dd 100644 --- a/macros/src/parser.rs +++ b/macros/src/parser.rs @@ -33,18 +33,7 @@ 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 }) } @@ -55,7 +44,8 @@ pub fn parse_bit_width(ty: &syn::Ident) -> Result { 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::() + 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}}")));