Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: unit tests
name: tests
on:
push: &trigger_config
branches: ["*"]
Expand Down
57 changes: 26 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Player>(), 1);
Expand Down Expand Up @@ -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();
Expand All @@ -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`.
Expand All @@ -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)
Expand All @@ -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::<Bits>(), 8);
```
79 changes: 39 additions & 40 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use proc_macro::TokenStream;
use syn::parse_macro_input;
use syn::{DeriveInput, parse_macro_input};

mod generator;
mod parser;
Expand All @@ -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::<Player>(), 1);
Expand All @@ -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`
Expand All @@ -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();
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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::<Bits>(), 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(),
}
}
66 changes: 54 additions & 12 deletions macros/src/parser.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -22,6 +23,31 @@ impl Parse for BitmapInput {
}
}

impl TryFrom<DeriveInput> for BitmapInput {
type Error = syn::Error;

fn try_from(value: DeriveInput) -> std::result::Result<Self, Self::Error> {
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::<Result<Vec<_>>>()?,
_ => 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,
Expand All @@ -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::<u8>() {
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<u8> {
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<u8> {
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::<u8>() {
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)
}
Loading