diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0e7fd2d..af34255 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,4 @@ -name: unit tests +name: tests on: push: &trigger_config branches: ["*"] diff --git a/README.md b/README.md index 556ffac..1132d01 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,12 @@ of the definition, with automatically generated getters and setters for each fie ```rust use bitmap::bitmap; -bitmap!( - struct Player { - imposter: u1, - finished_tasks: u3, - kills: u3, - } -); +#[bitmap] +struct Player { + imposter: u1, + finished_tasks: u3, + kills: u3, +} let mut player = Player(0); assert_eq!(std::mem::size_of::(), 1); @@ -84,13 +83,12 @@ the following traits are implemented: ```rust use bitmap::bitmap; -bitmap!( - struct Bits { - a: u32, - b: u16, - c: u16, - } -); +#[bitmap] +struct Bits { + a: u32, + b: u16, + c: u16, +} let bits = Bits(0); let underlying_u64: u64 = bits.into(); @@ -102,12 +100,11 @@ let underlying_u64 = *bits; ```rust use bitmap::bitmap; -bitmap!( - struct Bits { - flag: u1, - counter: u7, - } -); +#[bitmap] +struct Bits { + flag: u1, + counter: u7, +} ``` Each field must be in the form `uN`, where `1 <= N <= 128`. @@ -128,12 +125,11 @@ This means the first declared field is stored in the highest bits of the underly ```rust use bitmap::bitmap; -bitmap!( - struct Bits { - a: u8, - b: u8, - } -); +#[bitmap] +struct Bits { + a: u8, + b: u8, +} let mut bits = Bits(0); bits.set_a(0xaa) @@ -151,11 +147,10 @@ field will take up 64 bits of space. ```rust use bitmap::bitmap; -bitmap!( - struct Bits { - field: u33, - } -); +#[bitmap] +struct Bits { + field: u33, +} assert_eq!(core::mem::size_of::(), 8); ``` diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 6f475b0..9e80701 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,5 +1,5 @@ use proc_macro::TokenStream; -use syn::parse_macro_input; +use syn::{DeriveInput, parse_macro_input}; mod generator; mod parser; @@ -14,13 +14,13 @@ mod parser; /// ``` /// use macros::bitmap; /// -/// bitmap!( -/// struct Player { -/// imposter: u1, -/// finished_tasks: u3, -/// kills: u3, -/// } -/// ); +/// #[bitmap] +/// struct Player { +/// imposter: u1, +/// finished_tasks: u3, +/// kills: u3, +/// } +/// /// /// let mut player = Player(0); /// assert_eq!(std::mem::size_of::(), 1); @@ -36,7 +36,7 @@ mod parser; /// ``` /// #### Accessing fields /// For each field `name: T`, where `T` is the smallest possible integer such that -/// `field_size <= integer.size`, `bitmap!` generates: +/// `field_size <= integer.size`, `bitmap` generates: /// /// - `fn name(&self) -> T` — returns the value for `name` /// - `fn set_name(&mut self, val: T)` — sets the value for `name` @@ -50,13 +50,12 @@ mod parser; /// ``` /// use macros::bitmap; /// -/// bitmap!( -/// struct Bits { -/// a: u32, -/// b: u16, -/// c: u16, -/// } -/// ); +/// #[bitmap] +/// struct Bits { +/// a: u32, +/// b: u16, +/// c: u16, +/// } /// /// let bits = Bits(0); /// let underlying_u64: u64 = bits.into(); @@ -66,16 +65,15 @@ mod parser; /// ``` /// use macros::bitmap; /// -/// bitmap!( -/// struct Bits { -/// flag: u1, -/// counter: u7, -/// } -/// ); +/// #[bitmap] +/// struct Bits { +/// flag: u1, +/// counter: u7, +/// } /// ``` /// Each field must be in the form `uN`, where `1 <= N <= 128`. /// ### Maximum total size -/// `bitmap!` uses the smallest possible integer type such that `total_bit_width <= integer.bit_width`. +/// `bitmap` uses the smallest possible integer type such that `total_bit_width <= integer.bit_width`. /// The total bit width must fit into a `u128`. If you need more than that, consider using a `Vec` /// of `bitmap`s. /// ### Storage order @@ -86,12 +84,11 @@ mod parser; /// ``` /// use macros::bitmap; /// -/// bitmap!( -/// struct Bits { -/// a: u8, -/// b: u8, -/// } -/// ); +/// #[bitmap] +/// struct Bits { +/// a: u8, +/// b: u8, +/// } /// /// let mut bits = Bits(0); /// bits.set_a(0xaa) @@ -101,26 +98,28 @@ mod parser; /// ``` /// /// ### Note -/// `bitmap!` is built with hardware configuration in mind, where most packed bitmaps have a size +/// `bitmap` is built with hardware configuration in mind, where most packed bitmaps have a size /// aligned to integer sizes. It does not use the _smallest possible size_: a bitmap with only one `u33` /// field will take up 64 bits of space. /// ``` /// use macros::bitmap; /// -/// bitmap!( -/// struct Bits { -/// field: u33, -/// } -/// ); +/// #[bitmap] +/// struct Bits { +/// field: u33, +/// } /// /// assert_eq!(core::mem::size_of::(), 8); /// ``` /// -#[proc_macro] -pub fn bitmap(input: TokenStream) -> TokenStream { - let parsed = parse_macro_input!(input as parser::BitmapInput); - match generator::expand_bitmap(parsed) { - Ok(tokens) => tokens.into(), +#[proc_macro_attribute] +pub fn bitmap(_args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = parse_macro_input!(input as DeriveInput); + match parser::BitmapInput::try_from(parsed) { + Ok(bitmap_input) => match generator::expand_bitmap(bitmap_input) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + }, Err(err) => err.to_compile_error().into(), } } diff --git a/macros/src/parser.rs b/macros/src/parser.rs index a7f1ca0..0dc44a2 100644 --- a/macros/src/parser.rs +++ b/macros/src/parser.rs @@ -1,5 +1,6 @@ use syn::punctuated::Punctuated; use syn::token::Struct; +use syn::{Data, DataStruct, DeriveInput, Fields, Type, TypePath}; use syn::{ Ident, Result, Token, braced, parse::{Parse, ParseStream}, @@ -22,6 +23,31 @@ impl Parse for BitmapInput { } } +impl TryFrom for BitmapInput { + type Error = syn::Error; + + fn try_from(value: DeriveInput) -> std::result::Result { + let name = value.ident; + + let fields = match value.data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), .. + }) => fields + .named + .into_iter() + .map(|field| { + let field_name = field.ident.expect("Named field should have an ident"); + let size = extract_size_from_type(&field.ty)?; + Ok(FieldDef { name: field_name, size }) + }) + .collect::>>()?, + _ => return Err(syn::Error::new_spanned(name, "bitmap attribute can only be used on structs with named fields")), + }; + + Ok(BitmapInput { name, fields }) + } +} + pub struct FieldDef { pub name: Ident, pub size: u8, @@ -32,20 +58,36 @@ impl Parse for FieldDef { let name: Ident = input.parse()?; let _: Token![:] = input.parse()?; let ty: Ident = input.parse()?; + Ok(FieldDef { + name, + size: parse_bit_width(&ty.to_string())?, + }) + } +} - 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}}"))); +fn extract_size_from_type(ty: &Type) -> Result { + match ty { + Type::Path(TypePath { path, .. }) => { + let segment = path.segments.last().unwrap(); + parse_bit_width(&segment.ident.to_string()) } + _ => Err(syn::Error::new_spanned(ty, "Expected a simple type like u1, u8, etc.")), + } +} - Ok(FieldDef { name, size }) +fn parse_bit_width(ty: &str) -> Result { + if !ty.starts_with("u") { + return Err(syn::Error::new_spanned(ty, format!("Invalid type {ty}, expected u{{1..128}}"))); } + + let size = match ty[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}, expected u{{1..128}}"))); + } + + Ok(size) } diff --git a/src/lib.rs b/src/lib.rs index c2e1945..4744598 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,11 +7,10 @@ pub mod traits; #[test] fn one_bit() { - bitmap!( - struct Bits { - a: u1, - } - ); + #[bitmap] + struct Bits { + a: u1, + } let mut bits = Bits(0b0); bits.set_a(0b1); assert_eq!(bits.a(), 0b1); @@ -19,12 +18,11 @@ fn one_bit() { #[test] fn two_bits() { - bitmap!( - struct Bits { - a: u1, - b: u1, - } - ); + #[bitmap] + struct Bits { + a: u1, + b: u1, + } let mut bits = Bits(0b10); bits.set_a(0b1); bits.set_b(0b0); @@ -33,20 +31,19 @@ fn two_bits() { #[test] fn sixty_four_bits_funky_layout() { - bitmap!( - struct Bits { - a: u1, - b: u7, - c: u7, - d: u7, - e: u7, - f: u7, - g: u7, - h: u7, - i: u7, - j: u7, - } - ); + #[bitmap] + struct Bits { + a: u1, + b: u7, + c: u7, + d: u7, + e: u7, + f: u7, + g: u7, + h: u7, + i: u7, + j: u7, + } let mut bits = Bits(0xFF00FF00FF00FF00); bits.set_j(0b0000000).set_i(0b1111111).set_a(0b1); assert_eq!(*bits, 0xFF00FF00FF00FF80); @@ -54,12 +51,11 @@ fn sixty_four_bits_funky_layout() { #[test] fn sixty_four_bits_aligned() { - bitmap!( - struct Bits { - a: u32, - b: u32, - } - ); + #[bitmap] + struct Bits { + a: u32, + b: u32, + } let mut bits = Bits(0xFF00FF00FF00FF00); bits.set_a(0xFFFFFFFF).set_b(0b00000000); assert_eq!(*bits, 0xFFFFFFFF00000000); @@ -67,16 +63,15 @@ fn sixty_four_bits_aligned() { #[test] fn hundred_and_twenty_eight_bits_funky_layout() { - bitmap!( - struct Bits { - a: u40, - b: u25, - c: u31, - d: u16, - e: u9, - f: u7, - } - ); + #[bitmap] + struct Bits { + a: u40, + b: u25, + c: u31, + d: u16, + e: u9, + f: u7, + } let mut bits = Bits(0xFF00FF00FF00FF00FF00FF00FF00FF00); bits.set_a(0xAAAAAAAAAA) @@ -91,14 +86,13 @@ fn hundred_and_twenty_eight_bits_funky_layout() { #[test] fn hundred_and_twenty_eight_bits_aligned() { - bitmap!( - struct Bits { - a: u32, - b: u32, - c: u32, - d: u32, - } - ); + #[bitmap] + struct Bits { + a: u32, + b: u32, + c: u32, + d: u32, + } let mut bits = Bits(0xFF00FF00FF00FF00FF00FF00FF00FF00); bits.set_a(0xFFFFFFFF).set_b(0x00000000).set_c(0x42424242).set_d(0x66666666); assert_eq!(*bits, 0xFFFFFFFF000000004242424266666666); @@ -108,11 +102,10 @@ macro_rules! test_width { ($name:ident, $val:literal) => { #[test] fn $name() { - bitmap!( - struct Bits { - field: $name, - } - ); + #[bitmap] + struct Bits { + field: $name, + } let mut bits = Bits(0); bits.set_field($val); assert_eq!(bits.field(), $val); @@ -120,4 +113,19 @@ macro_rules! test_width { }; } +#[test] +fn attribute_macro() { + #[bitmap] + struct Bits { + a: u1, + b: u2, + c: u3, + d: u4, + } + + let mut bits = Bits(0); + bits.set_a(0b1).set_b(0b00).set_c(0b111).set_d(0b0000); + assert_eq!(*bits, 0b1001110000); +} + include!(concat!(env!("OUT_DIR"), "/generated_tests.rs")); diff --git a/tests/ui/bitmap_too_large.rs b/tests/ui/bitmap_too_large.rs index 0b6d340..db6c7a4 100644 --- a/tests/ui/bitmap_too_large.rs +++ b/tests/ui/bitmap_too_large.rs @@ -1,10 +1,9 @@ use macros::bitmap; fn main() { - bitmap!( - struct Bits { - field0: u128, - field1: u1, - } - ); + #[bitmap] + struct Bits { + field0: u128, + field1: u1, + } } diff --git a/tests/ui/bitmap_too_large.stderr b/tests/ui/bitmap_too_large.stderr index fbd5ba4..f98a738 100644 --- a/tests/ui/bitmap_too_large.stderr +++ b/tests/ui/bitmap_too_large.stderr @@ -1,5 +1,5 @@ error: Too many fields: maximum supported size is 128 bits - --> tests/ui/bitmap_too_large.rs:5:16 + --> tests/ui/bitmap_too_large.rs:5:12 | -5 | struct Bits { - | ^^^^ +5 | struct Bits { + | ^^^^ diff --git a/tests/ui/invalid_type.rs b/tests/ui/invalid_type.rs index 699814f..01a1cfe 100644 --- a/tests/ui/invalid_type.rs +++ b/tests/ui/invalid_type.rs @@ -1,9 +1,8 @@ use macros::bitmap; fn main() { - bitmap!( - struct Bits { - field0: u129, - } - ); + #[bitmap] + struct Bits { + field0: u129, + } } diff --git a/tests/ui/invalid_type.stderr b/tests/ui/invalid_type.stderr index 3b2eefb..5ced12e 100644 --- a/tests/ui/invalid_type.stderr +++ b/tests/ui/invalid_type.stderr @@ -1,5 +1,7 @@ error: Invalid size for u129, expected u{1..128} - --> tests/ui/invalid_type.rs:6:21 + --> tests/ui/invalid_type.rs:4:5 | -6 | field0: u129, - | ^^^^ +4 | #[bitmap] + | ^^^^^^^^^ + | + = note: this error originates in the attribute macro `bitmap` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/invalid_type_zero.rs b/tests/ui/invalid_type_zero.rs index c1b38e0..1a9bc44 100644 --- a/tests/ui/invalid_type_zero.rs +++ b/tests/ui/invalid_type_zero.rs @@ -1,9 +1,8 @@ use macros::bitmap; fn main() { - bitmap!( - struct Bits { - field0: u0, - } - ); + #[bitmap] + struct Bits { + field0: u0, + } } diff --git a/tests/ui/invalid_type_zero.stderr b/tests/ui/invalid_type_zero.stderr index 44a7362..ba19f78 100644 --- a/tests/ui/invalid_type_zero.stderr +++ b/tests/ui/invalid_type_zero.stderr @@ -1,5 +1,7 @@ error: Invalid size for u0, expected u{1..128} - --> tests/ui/invalid_type_zero.rs:6:21 + --> tests/ui/invalid_type_zero.rs:4:5 | -6 | field0: u0, - | ^^ +4 | #[bitmap] + | ^^^^^^^^^ + | + = note: this error originates in the attribute macro `bitmap` (in Nightly builds, run with -Z macro-backtrace for more info)