Skip to content

Commit 61477ea

Browse files
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
1 parent 74f9b6f commit 61477ea

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed

macros/src/lib.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use proc_macro::TokenStream;
22
use syn::parse_macro_input;
3+
use syn::DeriveInput;
4+
use quote::quote;
5+
use quote::ToTokens;
36

47
mod generator;
58
mod parser;
@@ -116,6 +119,7 @@ mod parser;
116119
/// assert_eq!(core::mem::size_of::<Bits>(), 8);
117120
/// ```
118121
///
122+
119123
#[proc_macro]
120124
pub fn bitmap(input: TokenStream) -> TokenStream {
121125
let parsed = parse_macro_input!(input as parser::BitmapInput);
@@ -124,3 +128,59 @@ pub fn bitmap(input: TokenStream) -> TokenStream {
124128
Err(err) => err.to_compile_error().into(),
125129
}
126130
}
131+
132+
#[proc_macro_attribute]
133+
pub fn bitmap_attr(_args: TokenStream, input: TokenStream) -> TokenStream {
134+
let input = parse_macro_input!(input as DeriveInput);
135+
136+
// Convert the attribute macro input to the existing BitmapInput format
137+
let bitmap_input = convert_derive_to_bitmap_input(input);
138+
139+
// Use the EXACT SAME expansion logic as the bitmap! macro
140+
match generator::expand_bitmap(bitmap_input) {
141+
Ok(tokens) => tokens.into(),
142+
Err(err) => err.to_compile_error().into(),
143+
}
144+
}
145+
146+
fn convert_derive_to_bitmap_input(input: DeriveInput) -> parser::BitmapInput {
147+
let name = input.ident; // Use 'name' not 'struct_name'
148+
149+
// Extract struct fields
150+
let syn::Data::Struct(data_struct) = input.data else {
151+
panic!("#[bitmap_attr] can only be used on structs");
152+
};
153+
154+
let syn::Fields::Named(fields_named) = data_struct.fields else {
155+
panic!("#[bitmap_attr] struct must have named fields");
156+
};
157+
158+
// Convert each field to the format expected by the existing parser
159+
let fields = fields_named.named.into_iter().map(|field| {
160+
let field_name = field.ident.expect("Field must have a name");
161+
let field_type = field.ty;
162+
163+
// Extract the size from the type (e.g., u1 -> 1, u7 -> 7)
164+
let type_str = field_type.to_token_stream().to_string();
165+
166+
if !type_str.starts_with("u") {
167+
panic!("Field type must be unsigned integer like u1, u2, etc.");
168+
}
169+
170+
let size: u8 = type_str[1..].parse().expect("Invalid bit width");
171+
172+
if size == 0 || size > 128 {
173+
panic!("Invalid size for {}, expected u{{1..128}}", type_str);
174+
}
175+
176+
parser::FieldDef {
177+
name: field_name,
178+
size,
179+
}
180+
}).collect();
181+
182+
parser::BitmapInput {
183+
name, // Use 'name' not 'struct_name'
184+
fields,
185+
}
186+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#![no_std]
22

33
pub use macros::bitmap;
4+
pub use macros::bitmap_attr;
45
pub use traits::*;
56

7+
68
pub mod traits;
79

810
#[test]

tests/compare_macros.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// tests/compare_macros.rs
2+
use bitmap::{bitmap, bitmap_attr};
3+
4+
// Function-like macro (original)
5+
bitmap!(
6+
struct OldWay {
7+
flag: u1,
8+
counter: u7,
9+
}
10+
);
11+
12+
// Attribute macro (new)
13+
#[bitmap_attr]
14+
struct NewWay {
15+
flag: u1,
16+
counter: u7,
17+
}
18+
19+
#[test]
20+
fn test_both_macros_produce_same_api() {
21+
let mut old = OldWay(0);
22+
let mut new = NewWay(0);
23+
24+
// Both should have the same methods
25+
old.set_flag(1).set_counter(42);
26+
new.set_flag(1).set_counter(42);
27+
28+
// Both should produce the same results
29+
assert_eq!(old.flag(), new.flag());
30+
assert_eq!(old.counter(), new.counter());
31+
assert_eq!(*old, *new);
32+
33+
println!("Both macros produce identical results!");
34+
}

tests/test_attr.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// tests/test_attr.rs
2+
use bitmap::bitmap_attr;
3+
4+
#[bitmap_attr]
5+
struct TestBits {
6+
flag: u1,
7+
counter: u7,
8+
}
9+
10+
#[test]
11+
fn test_attribute_macro_creates_correct_api() {
12+
let mut bits = TestBits(0);
13+
14+
// Test that the macro generated the correct methods
15+
bits.set_flag(1);
16+
bits.set_counter(42);
17+
18+
assert_eq!(bits.flag(), 1);
19+
assert_eq!(bits.counter(), 42);
20+
21+
// Test the raw value
22+
assert_eq!(*bits, 0b10101010); // flag=1 (MSB), counter=42
23+
24+
println!("Attribute macro creates correct bitmap API!");
25+
}
26+
27+
#[test]
28+
fn test_attribute_macro_deref_and_into() {
29+
let bits = TestBits(0xFF);
30+
let raw_value: u8 = bits.into();
31+
assert_eq!(raw_value, 0xFF);
32+
assert_eq!(*bits, 0xFF);
33+
}

0 commit comments

Comments
 (0)