Skip to content

Commit

Permalink
📝 Extend and simplify docs
Browse files Browse the repository at this point in the history
  • Loading branch information
wrenger committed Jan 24, 2023
1 parent 0a9129c commit 3f2f0af
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 67 deletions.
58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,63 @@ Add this to your `Cargo.toml`:
bitfield-struct = "0.3"
```

## Example
## Basics

Let's begin with a simple example.
Suppose we want to store multiple data inside a single Byte, as shown below:

<table>
<tr>
<td>7</td>
<td>6</td>
<td>5</td>
<td>4</td>
<td>3</td>
<td>3</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>P</td>
<td colspan="2">Level</td>
<td>S</td>
<td colspan="4">Kind</td>
</tr>
</table>

This crate generates a nice wrapper type that makes it easy to do this:

The example below shows the main features of the macro and how to use them.
```rust
/// Define your type like this with the bitfield attribute
#[bitfield(u8)]
struct MyByte {
/// The first field occupies the least significant bits
#[bits(4)]
kind: usize,
/// Booleans are 1 bit large
system: bool,
/// The bits attribute specifies the bit size of this field
#[bits(2)]
level: usize,
/// The last field spans over the most significant bits
present: bool
}
// The macro creates three accessor functions for each field:
// <name>, with_<name> and set_<name>
let my_byte = MyByte::new()
.with_kind(15)
.with_system(false)
.with_level(3)
.with_present(true);

assert!(my_byte.present());
```

## Features

Additionally, this crate has a few useful features, which are shown here in more detail.

The example below shows how attributes are carried over and how signed integers, padding, and custom types are handled.

```rust
/// A test bitfield with documentation
Expand Down
69 changes: 66 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,72 @@
//! Procedural macro for bitfields that allows specifying bitfields as structs.
//! As this library provides a procedural-macro it has no runtime dependencies and works for `no-std`.
//!
//! ## Example
//! - Supports bool flags, raw integers, and every custom type convertible into integers (structs/enums)
//! - Ideal for driver/OS/embedded development (defining HW registers/structures)
//! - Generates minimalistic, pure, safe rust functions
//! - Compile-time checks for type and field sizes
//! - Rust-analyzer friendly (carries over documentation to accessor functions)
//! - Exports field offsets and sizes as constants (useful for const asserts)
//! - Generation of `fmt::Debug`
//!
//! The example below shows the main features of the macro and how to use them.
//! ## Basics
//!
//! Let's begin with a simple example.</br>
//! Suppose we want to store multiple data inside a single Byte, as shown below:
//!
//! <table>
//! <tr>
//! <td>7</td>
//! <td>6</td>
//! <td>5</td>
//! <td>4</td>
//! <td>3</td>
//! <td>3</td>
//! <td>1</td>
//! <td>0</td>
//! </tr>
//! <tr>
//! <td>P</td>
//! <td colspan="2">Level</td>
//! <td>S</td>
//! <td colspan="4">Kind</td>
//! </tr>
//! </table>
//!
//! This crate is able to generate a nice wrapper type that makes it easy to do this:
//!
//! ```
//! # use bitfield_struct::bitfield;
//! /// Define your type like this with the bitfield attribute
//! #[bitfield(u8)]
//! struct MyByte {
//! /// The first field occupies the least significant bits
//! #[bits(4)]
//! kind: usize,
//! /// Booleans are 1 bit large
//! system: bool,
//! /// The bits attribute specifies the bit size of this field
//! #[bits(2)]
//! level: usize,
//! /// The last field spans over the most significant bits
//! present: bool
//! }
//! // The macro creates three accessor functions for each field:
//! // <name>, with_<name> and set_<name>
//! let my_byte = MyByte::new()
//! .with_kind(15)
//! .with_system(false)
//! .with_level(3)
//! .with_present(true);
//!
//! assert!(my_byte.present());
//! ```
//!
//! ## Features
//!
//! Additionally, this crate has a few useful features, which are shown here in more detail.
//!
//! The example below shows how attributes are carried over and how signed integers, padding, and custom types are handled.
//!
//! ```
//! # use bitfield_struct::bitfield;
Expand Down Expand Up @@ -439,7 +502,7 @@ fn bits(attrs: &[syn::Attribute], ty: &syn::Type) -> syn::Result<(TypeClass, usi
if bits <= size {
Ok((class, bits))
} else {
Err(syn::Error::new(tokens.span(), "overflowing member type"))
Err(syn::Error::new(tokens.span(), "overflowing field type"))
}
} else {
Ok((TypeClass::Other, bits))
Expand Down
130 changes: 68 additions & 62 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,58 @@ use std::fmt;

use bitfield_struct::bitfield;

/// A test bitfield with documentation
#[bitfield(u64)]
struct MyBitfield {
/// defaults to 16 bits for u16
int: u16,
/// interpreted as 1 bit flag
flag: bool,
/// custom bit size
#[bits(1)]
tiny: u8,
/// sign extend for signed integers
#[bits(13)]
negative: i16,
/// supports any type that implements `From<u64>` and `Into<u64>`
#[bits(16)]
custom: CustomEnum,
/// public field -> public accessor functions
#[bits(12)]
pub public: usize,
/// padding
#[bits(5)]
_p: u8,
/// zero-sized members are ignored
#[bits(0)]
_completely_ignored: String,
}
#[test]
fn members() {
/// A test bitfield with documentation
#[bitfield(u64)]
struct MyBitfield {
/// defaults to 16 bits for u16
int: u16,
/// interpreted as 1 bit flag
flag: bool,
/// custom bit size
#[bits(1)]
tiny: u8,
/// sign extend for signed integers
#[bits(13)]
negative: i16,
/// supports any type that implements `From<u64>` and `Into<u64>`
#[bits(16)]
custom: CustomEnum,
/// public field -> public accessor functions
#[bits(12)]
pub public: usize,
/// padding
#[bits(5)]
_p: u8,
/// zero-sized members are ignored
#[bits(0)]
_completely_ignored: String,
}

/// A custom enum
#[derive(Debug, PartialEq, Eq)]
#[repr(u64)]
enum CustomEnum {
A = 0,
B = 1,
C = 2,
}
impl From<u64> for CustomEnum {
fn from(value: u64) -> Self {
match value {
0 => Self::A,
1 => Self::B,
_ => Self::C,
}
/// A custom enum
#[derive(Debug, PartialEq, Eq)]
#[repr(u64)]
enum CustomEnum {
A = 0,
B = 1,
C = 2,
}
}
impl From<CustomEnum> for u64 {
fn from(value: CustomEnum) -> Self {
value as _
impl From<u64> for CustomEnum {
fn from(value: u64) -> Self {
match value {
0 => Self::A,
1 => Self::B,
_ => Self::C,
}
}
}
}

/// We have a custom debug implementation -> opt out
#[bitfield(u64, debug = false)]
#[derive(PartialEq, Eq, Default)]
struct Full {
data: u64,
}

impl fmt::Debug for Full {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{:x}", self.data())
impl From<CustomEnum> for u64 {
fn from(value: CustomEnum) -> Self {
value as _
}
}
}

#[test]
fn members() {
let mut val = MyBitfield::new()
.with_int(3 << 15)
.with_flag(true)
Expand Down Expand Up @@ -100,8 +87,15 @@ fn members() {

#[test]
fn attrs() {
/// We have a custom debug implementation -> opt out
#[bitfield(u64)]
#[derive(PartialEq, Eq, Default)]
struct Full {
data: u64,
}

let full = Full::default();
assert_eq!(full, Full::new());
assert_eq!(full.0, Full::new().0);

let full = Full::new().with_data(u64::MAX);
assert_eq!(full.data(), u64::MAX);
Expand All @@ -110,6 +104,18 @@ fn attrs() {

#[test]
fn debug() {
let full = Full::default();
/// We have a custom debug implementation -> opt out
#[bitfield(u64, debug = false)]
struct Full {
data: u64,
}

impl fmt::Debug for Full {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{:x}", self.data())
}
}

let full = Full::new().with_data(123);
println!("{full:?}");
}

0 comments on commit 3f2f0af

Please sign in to comment.