#[macro_export]
macro_rules! bitfield {
(
$(#[$($meta:meta)+])*
$vis:vis struct $Name:ident<$T:ident> {
$(
$(#[$field_meta:meta])*
$field_vis:vis const $Field:ident $(: $F:ty)? $( = $val:tt)?;
)+
}
) => {
$(#[$($meta)+])*
#[derive(Copy, Clone)]
#[repr(transparent)]
$vis struct $Name($T);
#[automatically_derived]
impl core::fmt::Debug for $Name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut dbg = f.debug_struct(stringify!($Name));
$(
{
// skip reserved fields (names starting with `_`).
//
// NOTE(eliza): i hope this `if` gets const-folded...we
// could probably do this in a macro and guarantee that
// it happens at compile-time, but this is fine for now.
if !stringify!($Field).starts_with('_') {
dbg.field(stringify!($Field), &self.get(Self::$Field));
}
}
)+
dbg.finish()
}
}
#[allow(dead_code)]
#[automatically_derived]
impl $Name {
$crate::bitfield! { @field<$T>:
$(
$(#[$field_meta])*
$field_vis const $Field $(: $F)? $( = $val)?;
)+
}
const FIELDS: &'static [(&'static str, $crate::bitfield! { @t $T, $T, Self })] = &[$(
(stringify!($Field), Self::$Field.typed())
),+];
#[inline]
#[must_use]
$vis const fn from_bits(bits: $T) -> Self {
Self(bits)
}
#[inline]
#[must_use]
$vis const fn new() -> Self {
Self(0)
}
#[inline]
$vis const fn bits(self) -> $T {
self.0
}
$vis fn with<T>(self, field: $crate::bitfield! { @t $T, T, Self }, value: T) -> Self
where
T: $crate::FromBits<$T>,
{
Self(field.pack(value, self.0))
}
$vis fn set<T>(&mut self, field: $crate::bitfield! { @t $T, T, Self }, value: T) -> &mut Self
where
T: $crate::FromBits<$T>,
{
field.pack_into(value, &mut self.0);
self
}
$vis fn get<T>(self, field: $crate::bitfield! { @t $T, T, Self }) -> T
where
T: $crate::FromBits<$T>,
{
field.unpack(self.0)
}
$vis fn try_get<T>(self, field: $crate::bitfield! { @t $T, T, Self }) -> Result<T, T::Error>
where
T: $crate::FromBits<$T>,
{
field.try_unpack(self.0)
}
$vis fn assert_valid() {
<$crate::bitfield! { @t $T, $T, Self }>::assert_all_valid(&Self::FIELDS);
}
$vis fn display_ascii(&self) -> impl core::fmt::Display {
struct DisplayAscii($Name);
impl core::fmt::Display for DisplayAscii {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt_ascii(f)
}
}
DisplayAscii(*self)
}
$vis fn display_unicode(&self) -> impl core::fmt::Display {
struct DisplayUnicode($Name);
impl core::fmt::Display for DisplayUnicode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt_unicode(f)
}
}
DisplayUnicode(*self)
}
fn fmt_ascii(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.pad("")?;
writeln!(f, "{:0width$b}", self.0, width = $T::BITS as usize)?;
f.pad("")?;
$({
let field = Self::$Field;
const NAME: &str = stringify!($Field);
if !NAME.starts_with("_") {
f.pad("")?;
let mut cur_pos = $T::BITS;
while cur_pos > field.most_significant_index() {
f.write_str(".")?;
cur_pos -= 1;
}
write!(f, "{:0width$b}", field.unpack_bits(self.0), width = field.bits() as usize)?;
cur_pos -= field.bits();
while cur_pos > 0 {
f.write_str(".")?;
cur_pos -= 1;
}
writeln!(f, " {NAME}: {:?}", field.unpack(self.0))?
}
})+
Ok(())
}
fn fmt_unicode(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.pad("")?;
writeln!(f, "{:0width$b}", self.0, width = $T::BITS as usize)?;
f.pad("")?;
let mut cur_pos = $T::BITS;
let mut max_len = 0;
let mut rem = 0;
let mut fields = Self::FIELDS.iter().rev().peekable();
while let Some((name, field)) = fields.next() {
while cur_pos > field.most_significant_index() {
f.write_str(" ")?;
cur_pos -= 1;
}
let bits = field.bits();
match (name, bits) {
(name, bits) if name.starts_with("_") => {
for _ in 0..bits {
f.write_str(" ")?;
}
cur_pos -= bits;
continue;
}
(_, 1) => f.write_str("│")?,
(_, 2) => f.write_str("└┤")?,
(_, bits) => {
f.write_str("└┬")?;
for _ in 0..(bits - 3) {
f.write_str("─")?;
}
f.write_str("┘")?;
}
}
if fields.peek().is_none() {
rem = cur_pos - (bits - 1);
}
max_len = core::cmp::max(max_len, name.len());
cur_pos -= field.bits()
}
f.write_str("\n")?;
$(
let field = Self::$Field;
let name = stringify!($Field);
if !name.starts_with("_") {
f.pad("")?;
cur_pos = $T::BITS;
for (cur_name, cur_field) in Self::FIELDS.iter().rev() {
while cur_pos > cur_field.most_significant_index() {
f.write_str(" ")?;
cur_pos -= 1;
}
if field == cur_field {
break;
}
let bits = cur_field.bits();
match (cur_name, bits) {
(name, bits) if name.starts_with("_") => {
for _ in 0..bits {
f.write_str(" ")?;
}
}
(_, 1) => f.write_str("│")?,
(_, bits) => {
f.write_str(" │")?;
for _ in 0..(bits - 2) {
f.write_str(" ")?;
}
}
}
cur_pos -= bits;
}
let field_bits = field.bits();
if field_bits == 1 {
f.write_str("â””")?;
cur_pos -= 1;
} else {
f.write_str(" â””")?;
cur_pos -= 2;
}
let len = cur_pos as usize + (max_len - name.len());
for _ in rem as usize..len {
f.write_str("─")?;
}
writeln!(f, " {}: {:?} ({:0width$b})", name, field.unpack(self.0), field.unpack_bits(self.0), width = field_bits as usize)?
}
)+
Ok(())
}
}
#[automatically_derived]
impl core::fmt::Display for $Name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if f.alternate() {
self.fmt_unicode(f)
} else {
self.fmt_ascii(f)
}
}
}
#[automatically_derived]
impl core::fmt::Binary for $Name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if f.alternate() {
write!(f, concat!(stringify!($Name), "({:#b})"), self.0)
} else {
write!(f, concat!(stringify!($Name), "({:b})"), self.0)
}
}
}
#[automatically_derived]
impl core::fmt::UpperHex for $Name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if f.alternate() {
write!(f, concat!(stringify!($Name), "({:#X})"), self.0)
} else {
write!(f, concat!(stringify!($Name), "({:X})"), self.0)
}
}
}
#[automatically_derived]
impl core::fmt::LowerHex for $Name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if f.alternate() {
write!(f, concat!(stringify!($Name), "({:#x})"), self.0)
} else {
write!(f, concat!(stringify!($Name), "({:x})"), self.0)
}
}
}
#[automatically_derived]
impl From<$T> for $Name {
#[inline]
fn from(val: $T) -> Self {
Self::from_bits(val)
}
}
#[automatically_derived]
impl From<$Name> for $T {
#[inline]
fn from($Name(bits): $Name) -> Self {
bits
}
}
};
(@field<$T:ident>, prev: $Prev:ident:
$(#[$meta:meta])*
$vis:vis const $Field:ident = ..;
) => {
$(#[$meta])*
$vis const $Field: $crate::bitfield!{ @t $T, $T, Self } = Self::$Prev.remaining();
};
(@field<$T:ident>, prev: $Prev:ident:
$(#[$meta:meta])*
$vis:vis const $Field:ident = $value:literal;
$($rest:tt)*
) => {
$(#[$meta])*
$vis const $Field: $crate::bitfield!{ @t $T, $T, Self } = Self::$Prev.next($value);
$crate::bitfield!{ @field<$T>, prev: $Field: $($rest)* }
};
(@field<$T:ident>, prev: $Prev:ident:
$(#[$meta:meta])*
$vis:vis const $Field:ident: $Val:ty;
$($rest:tt)*
) => {
$(#[$meta])*
$vis const $Field: $crate::bitfield!{ @t $T, $Val, Self } = Self::$Prev.then::<$Val>();
$crate::bitfield!{ @field<$T>, prev: $Field: $($rest)* }
};
(@field<$T:ident>, prev: $Prev:ident: ) => { };
(@field<$T:ident>:
$(#[$meta:meta])*
$vis:vis const $Field:ident = $value:literal;
$($rest:tt)*
) => {
$(#[$meta])*
$vis const $Field: $crate::bitfield!{ @t $T, $T, Self } = <$crate::bitfield!{ @t $T, $T, () }>::least_significant($value).typed();
$crate::bitfield!{ @field<$T>, prev: $Field: $($rest)* }
};
(@field<$T:ident>:
$(#[$meta:meta])*
$vis:vis const $Field:ident: $Val:ty;
$($rest:tt)*
) => {
$(#[$meta])*
$vis const $Field: $crate::bitfield!{ @t $T, $Val, Self } = <$crate::bitfield!{ @t $T, $Val, Self } >::first();
$crate::bitfield!{ @field<$T>, prev: $Field: $($rest)* }
};
(@t usize, $V:ty, $F:ty) => { $crate::PackUsize<$V, $F> };
(@t u128, $V:ty, $F:ty) => { $crate::Pack128<$V, $F> };
(@t u64, $V:ty, $F:ty) => { $crate::Pack64<$V, $F> };
(@t u32, $V:ty, $F:ty) => { $crate::Pack32<$V, $F> };
(@t u16, $V:ty, $F:ty) => { $crate::Pack16<$V, $F> };
(@t u8, $V:ty, $F:ty) => { $crate::Pack8<$V, $F> };
(@t $T:ty, $V:ty, $F:ty) => { compile_error!(concat!("unsupported bitfield type `", stringify!($T), "`; expected one of `usize`, `u128`, `u64`, `u32`, `u16`, or `u8`")) }
}
#[cfg(test)]
mod tests {
use crate::FromBits;
bitfield! {
#[allow(dead_code)]
struct TestBitfield<u32> {
const HELLO = 4;
const _RESERVED_1 = 3;
const WORLD: bool;
const HAVE: TestEnum;
const LOTS = 5;
const OF = 1;
const FUN = 6;
}
}
bitfield! {
#[allow(dead_code)]
struct TestBitfieldHuge<u128> {
const HELLO = 4;
const _RESERVED_1 = 3;
const WORLD: bool;
const HAVE: TestEnum;
const LOTS = 5;
const OF = 1;
const FUN = 6;
const REST = ..;
}
}
#[repr(u8)]
#[derive(Debug)]
enum TestEnum {
Foo = 0b00,
Bar = 0b01,
Baz = 0b10,
Qux = 0b11,
}
impl FromBits<u32> for TestEnum {
const BITS: u32 = 2;
type Error = core::convert::Infallible;
fn try_from_bits(bits: u32) -> Result<Self, Self::Error> {
Ok(match bits as u8 {
bits if bits == Self::Foo as u8 => Self::Foo,
bits if bits == Self::Bar as u8 => Self::Bar,
bits if bits == Self::Baz as u8 => Self::Baz,
bits if bits == Self::Qux as u8 => Self::Qux,
bits => unreachable!("all patterns are covered: {:#b}", bits),
})
}
fn into_bits(self) -> u32 {
self as u8 as u32
}
}
impl FromBits<u128> for TestEnum {
const BITS: u32 = 2;
type Error = core::convert::Infallible;
fn try_from_bits(bits: u128) -> Result<Self, Self::Error> {
FromBits::<u32>::try_from_bits(bits as u32)
}
fn into_bits(self) -> u128 {
self as u8 as u128
}
}
#[derive(Debug)]
#[allow(dead_code)]
struct TestDebug {
value: usize,
bits: TestBitfield,
}
#[test]
fn test_bitfield_format() {
let test_bitfield = TestBitfield::new()
.with(TestBitfield::HELLO, 0b1001)
.with(TestBitfield::WORLD, true)
.with(TestBitfield::HAVE, TestEnum::Bar)
.with(TestBitfield::LOTS, 0b11010)
.with(TestBitfield::OF, 0)
.with(TestBitfield::FUN, 9);
let ascii = test_bitfield.to_string();
assert_eq!(ascii, test_bitfield.display_ascii().to_string());
println!("test ASCII display:\n{ascii}");
println!("test unicode display:\n{test_bitfield:#}\n");
let test_debug = TestDebug {
value: 42,
bits: test_bitfield,
};
println!("test_debug(alt): {test_debug:#?}\n");
println!("test_debug: {test_debug:?}\n");
}
#[test]
fn macro_bitfield_valid() {
TestBitfield::assert_valid();
}
}