From fc430efb51e959cf9a7254f2138360f4f0052f50 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Sun, 28 Sep 2025 17:30:00 +0200 Subject: [PATCH 1/6] Add BitMap trait and implement for u{8,16,32,64,128} --- Cargo.lock | 7 ++++ Cargo.toml | 1 + src/lib.rs | 3 ++ src/traits.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 src/traits.rs diff --git a/Cargo.lock b/Cargo.lock index 4fb5422..330aafb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ name = "bitmap" version = "0.1.0" dependencies = [ "macros", + "paste", "trybuild", ] @@ -59,6 +60,12 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "proc-macro2" version = "1.0.101" diff --git a/Cargo.toml b/Cargo.toml index 78f7811..14dc79e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ macros = { path = "./macros" } [dev-dependencies] trybuild = "1.0.111" +paste = "1.0.15" diff --git a/src/lib.rs b/src/lib.rs index 29f2605..421e4f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,7 @@ pub use macros::bitmap; +pub use traits::*; + +mod traits; #[test] fn one_bit() { diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..9c74a3b --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,104 @@ +use std::ops::Range; + +pub trait BitMap { + fn get_bit(&self, index: u8) -> T; + fn set_bit(&mut self, index: u8, value: T); + fn get_bits(&self, indices: Range) -> T; + fn set_bits(&mut self, indices: Range, value: T); +} + +macro_rules! impl_bitmap { + ($ty:ident) => { + impl BitMap<$ty> for $ty { + fn get_bit(&self, index: u8) -> $ty { + *self >> index & 0b1 + } + + fn set_bit(&mut self, index: u8, value: $ty) { + *self = (*self & !(1 << index)) | ((value & 1) << index); + } + + fn set_bits(&mut self, indices: Range, value: $ty) { + let width = indices.end - indices.start; + let bit_count = ::core::mem::size_of::<$ty>() * 8; + + let mask = if width as usize >= bit_count { $ty::MAX } else { (1 << width) - 1 }; + + *self = (*self & !(mask << indices.start)) | ((value & mask) << indices.start); + } + + fn get_bits(&self, indices: Range) -> $ty { + let width = indices.end - indices.start; + let bit_count = ::core::mem::size_of::<$ty>() * 8; + + let mask = if width as usize >= bit_count { $ty::MAX } else { (1 << width) - 1 }; + + (*self >> indices.start) & mask + } + } + }; +} + +impl_bitmap!(u8); +impl_bitmap!(u16); +impl_bitmap!(u32); +impl_bitmap!(u64); +impl_bitmap!(u128); + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! generate_tests { + ($ty:ident) => { + paste::paste! { + #[test] + fn []() { + let mut x: $ty = 0; + let bit_width = ::core::mem::size_of::<$ty>() * 8; + + for bit in 0..bit_width { + x.set_bit(bit as u8, 1); + assert_eq!(x.get_bit(bit as u8), 1); + assert_eq!(x, 1 << bit); + + x.set_bit(bit as u8, 0); + assert_eq!(x.get_bit(bit as u8), 0); + assert_eq!(x, 0); + } + } + + #[test] + fn []() { + let mut x: $ty = 0; + let bit_width = ::core::mem::size_of::<$ty>() * 8; + + for start in (0..bit_width).step_by(8) { + for width in 1..=8.min(bit_width - start) { + let end = start + width; + if end > bit_width { + break; + } + let max_val = if width >= bit_width { + $ty::MAX + } else { + (1 << width) - 1 + }; + + x.set_bits(start as u8..end as u8, max_val); + assert_eq!(x.get_bits(start as u8..end as u8), max_val, "Failed range test: {start}..{end} with value {max_val}"); + + x = 0; + } + } + } + } + }; + } + + generate_tests!(u8); + generate_tests!(u16); + generate_tests!(u32); + generate_tests!(u64); + generate_tests!(u128); +} From e0eb19bf4e7f98de16556fbc77e30b949fcaca10 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Sun, 28 Sep 2025 17:35:46 +0200 Subject: [PATCH 2/6] Add trybuild test for BitMap trait --- README.md | 8 +++++--- src/lib.rs | 2 +- tests/trybuild_tests.rs | 6 ++++++ tests/ui/traits.rs | 7 +++++++ 4 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 tests/ui/traits.rs diff --git a/README.md b/README.md index 6d9ec18..255f753 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ -# `bitmap!` +# bitmap + +## `BitMap` trait + +## `bitmap!` API Generates a packed bitmap newtype struct with field-level bit access. The macro expands to a newtype struct around a `u8` to `u128`, depending on the total bit width of the definition, with automatically generated getters and setters for each field. -## API - ### Usage Example ```rust diff --git a/src/lib.rs b/src/lib.rs index 421e4f2..e36bb58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ pub use macros::bitmap; pub use traits::*; -mod traits; +pub mod traits; #[test] fn one_bit() { diff --git a/tests/trybuild_tests.rs b/tests/trybuild_tests.rs index ec43226..a07c9c1 100644 --- a/tests/trybuild_tests.rs +++ b/tests/trybuild_tests.rs @@ -15,3 +15,9 @@ fn invalid_type_zero() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/invalid_type_zero.rs"); } + +#[test] +fn traits() { + let t = trybuild::TestCases::new(); + t.pass("tests/ui/traits.rs"); +} diff --git a/tests/ui/traits.rs b/tests/ui/traits.rs new file mode 100644 index 0000000..5c32a20 --- /dev/null +++ b/tests/ui/traits.rs @@ -0,0 +1,7 @@ +use bitmap::traits::*; + +fn main() { + let mut x: u128 = 0; + x.set_bits(0..2, 0b11); + assert_eq!(x, 0b11); +} From dd507e79260e99bfc8c3817357719b0c75274779 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Sun, 28 Sep 2025 17:55:10 +0200 Subject: [PATCH 3/6] Update README.md and add doc comments to BitMap trait methods --- README.md | 39 +++++++++++++++++++++++++++++++++++---- src/traits.rs | 4 ++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 255f753..0c8bb00 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,38 @@ ## `BitMap` trait -## `bitmap!` API +This trait defines the following API: + +``` + +pub trait BitMap { + /// Gets the bit at position `index` from `&self`. + fn get_bit(&self, index: u8) -> T; + /// Sets the bit at position `index` in `&self`. + fn set_bit(&mut self, index: u8, value: T); + /// Gets the bits at positions `indices.start..indices.end` from `&self`. + fn get_bits(&self, indices: Range) -> T; + /// Sets the bits at positions `indices.start..indices.end` in `&self`. + fn set_bits(&mut self, indices: Range, value: T); +} +``` + +By using the crate's `traits` prelude, the `BitMap` trait is implemented for `u8`, `u16`, `u32`, `u64`, and `u128`. + +```rust +use bitmap::traits::*; + +fn main() { + let mut x: u64 = 0; + + x.set_bit(1, 1); + assert_eq!(x, 2); + x.set_bit(1, 0); + assert_eq!(x, 0); +} +``` + +## `bitmap!` Procedural Macro Generates a packed bitmap newtype struct with field-level bit access. @@ -25,9 +56,9 @@ bitmap!( 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); +player.set_imposter(1) + .set_finished_tasks(5) + .set_kills(3); assert_eq!(player.imposter(), 1); assert_eq!(player.finished_tasks(), 5); diff --git a/src/traits.rs b/src/traits.rs index 9c74a3b..afa1f20 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,9 +1,13 @@ use std::ops::Range; pub trait BitMap { + /// Gets the bit at position `index` from `&self`. fn get_bit(&self, index: u8) -> T; + /// Sets the bit at position `index` in `&self`. fn set_bit(&mut self, index: u8, value: T); + /// Gets the bits at positions `indices.start..indices.end` from `&self`. fn get_bits(&self, indices: Range) -> T; + /// Sets the bits at positions `indices.start..indices.end` in `&self`. fn set_bits(&mut self, indices: Range, value: T); } From b7ec70b842dc3fe7c6a913b99d75a0b064c5d50a Mon Sep 17 00:00:00 2001 From: winstonallo Date: Sun, 28 Sep 2025 17:55:39 +0200 Subject: [PATCH 4/6] Add runtime assertion to panic when value overflows the bit width in bitmap setters --- macros/src/generator.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/macros/src/generator.rs b/macros/src/generator.rs index 98692c9..f17680f 100644 --- a/macros/src/generator.rs +++ b/macros/src/generator.rs @@ -66,8 +66,9 @@ pub fn expand_bitmap(input: BitmapInput) -> syn::Result { } #[inline] - pub fn #setter_name(&mut self, val: #this_storage_ty) -> &mut Self { - self.0 = ((self.0 & !((#mask) << #index)) | (((val as #storage_ty) & #mask) << #index)); + pub fn #setter_name(&mut self, value: #this_storage_ty) -> &mut Self { + assert!(value <= #mask as #this_storage_ty); + self.0 = ((self.0 & !((#mask) << #index)) | (((value as #storage_ty) & #mask) << #index)); self } } From 51bddf14e0a0313199cb2a811293426182f878d1 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Sun, 28 Sep 2025 17:56:15 +0200 Subject: [PATCH 5/6] Add rust identifier to code block in README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 0c8bb00..983c232 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ This trait defines the following API: -``` - +```rust pub trait BitMap { /// Gets the bit at position `index` from `&self`. fn get_bit(&self, index: u8) -> T; From 47ec3e43925af3ba1ad1d123be9ea461e13a4734 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Sun, 28 Sep 2025 17:59:10 +0200 Subject: [PATCH 6/6] Add fully qualified URI to Range type in BitMap trait --- src/traits.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index afa1f20..b580445 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,14 +1,12 @@ -use std::ops::Range; - pub trait BitMap { /// Gets the bit at position `index` from `&self`. fn get_bit(&self, index: u8) -> T; /// Sets the bit at position `index` in `&self`. fn set_bit(&mut self, index: u8, value: T); /// Gets the bits at positions `indices.start..indices.end` from `&self`. - fn get_bits(&self, indices: Range) -> T; + fn get_bits(&self, indices: ::core::ops::Range) -> T; /// Sets the bits at positions `indices.start..indices.end` in `&self`. - fn set_bits(&mut self, indices: Range, value: T); + fn set_bits(&mut self, indices: ::core::ops::Range, value: T); } macro_rules! impl_bitmap { @@ -22,7 +20,7 @@ macro_rules! impl_bitmap { *self = (*self & !(1 << index)) | ((value & 1) << index); } - fn set_bits(&mut self, indices: Range, value: $ty) { + fn set_bits(&mut self, indices: ::core::ops::Range, value: $ty) { let width = indices.end - indices.start; let bit_count = ::core::mem::size_of::<$ty>() * 8; @@ -31,7 +29,7 @@ macro_rules! impl_bitmap { *self = (*self & !(mask << indices.start)) | ((value & mask) << indices.start); } - fn get_bits(&self, indices: Range) -> $ty { + fn get_bits(&self, indices: ::core::ops::Range) -> $ty { let width = indices.end - indices.start; let bit_count = ::core::mem::size_of::<$ty>() * 8;