bitbash 0.5.1

Macros for working with bitfields
Documentation
#![cfg_attr(
    all(test, feature = "const"),
    feature(const_fn, const_panic, const_if_match, const_mut_refs)
)]
#![cfg_attr(not(test), no_std)]

//! Macros for working with bitfields.
//!
//! This crate contains the `bitfield!` macro and `BitEnum` derive.
//!
//! `bitfield!` is used to generate (`const fn`) getter and setter methods on bitfields.
//! `BitEnum` allows an enum to be used as a field value.
//!
//! # Example
//! ```
//! use bitbash::{bitfield, BitEnum};
//!
//! bitfield! {
//!     #[derive(Copy, Clone, PartialEq, Eq)]
//!     pub struct Foo(u16);
//!
//!     pub new(bar);
//!     derive_debug;
//!
//!     pub field bar: Bar = [0..3];
//!     pub field baz: usize = [3..7] ~ [8..12];
//!     pub field quux: bool = [7];
//! }
//!
//! #[derive(BitEnum, Copy, Clone, PartialEq, Eq, Debug)]
//! pub enum Bar {
//!     A = 0b101,
//!     B = 0b010,
//! }
//!
//! fn main() {
//!     let mut foo = Foo::new(Bar::A).with_baz(0xcd);
//!     foo.set_quux(true);
//!     assert_eq!(foo.bar(), Bar::A);
//!     assert_eq!(foo.baz(), 0xcd);
//!     assert!(foo.quux());
//!     assert_eq!(foo.0, ((Bar::A as u16) << 0) | (0xd << 3) | (0xc << 8) | (1 << 7));
//! }
//! ```
//!
//! # `bitfield!` macro
//!
//! ## Supported structs
//! The `bitfield!` macro supports three kinds of structs: a tuple struct of an unsigned integer, a tuple struct of an array of unsigned integers, and a regular struct.
//! ```
//! # use bitbash::bitfield;
//! bitfield! {
//!     struct Foo(u8);
//!
//!     field a: bool = [7];
//! }
//!
//! bitfield! {
//!     struct Bar([u32; 2]);
//!
//!     field a: bool = 0[7];
//!     field b: bool = 1[7];
//! }
//!
//! bitfield! {
//!     struct Baz {
//!         _padding: [u32; 1],
//!         apple: u32,
//!         banana: u32,
//!     }
//!
//!     field a: bool = apple[7];
//! }
//! # fn main() {}
//! ```
//! Regular structs may contain types that are not unsigned integers, however they cannot be referred to by `field`s.
//!
//! ## (`pub`) `field`
//! A `field` statement defines in which bits a value is stored.
//! Fields can refer to a single bit, a range of bits, a concatenation of bits, or a mapping of bits in the value to bits in the bitfield:
//! ```
//! # use bitbash::bitfield;
//! bitfield! {
//!     struct Foo([u32; 2]);
//!
//!     field single: bool = 0[0];
//!     field range: u8 = 0[0..8];
//!     field concatenation: u16 = 0[0..8] ~ 1[8..16];
//!     field mapping: u32 {
//!         [8..16] => 1[8..16],
//!         [31] => 0[31],
//!     }
//! }
//! # fn main() {}
//! ```
//! By default, three methods are generated for each field: a getter, a setter, and a builder:
//! ```
//! # type Value = ();
//! # trait Example {
//! fn field_name(&self) -> Value;
//! fn set_field_name(&mut self, value: Value);
//! fn with_field_name(self, value: Value) -> Self;
//! # }
//! ```
//! To only generate the getter, use the `#[ro]` attribute.
//! To make the setter and builder of a `pub field` private, use the `#[private_write]` attribute.
//!
//! When a field is read, its bits must be a valid representation of the value.
//! When a field is written, only the bits in the value that are referred to by the field may be set.
//! If these requirements are not met a panic occurs.
//!
//! Referring to bits that do not exist causes either a compile-time error or a runtime error, depending on whether `const fn` code was generated.
//!
//! Construction of values is done entirely in safe code.
//! Moreover, no unsafe code is generated by the macros in this crate.
//!
//! ## (`pub`) `new()`
//! When the `new()` statement is specified, an `fn new()` method that zero-initializes the bitfield is generated.
//! Fields for which zero is not a valid representation can be passed as parameters to the `new()` statement to generate a method which takes their initial values as parameters:
//! ```
//! # use bitbash::{bitfield, BitEnum};
//! bitfield! {
//!     struct Foo(u32);
//!
//!     new(a);
//!
//!     field a: Bar = [0..3];
//! }
//!
//! #[derive(BitEnum)]
//! enum Bar {
//!     A = 0b101,
//!     B = 0b010,
//! }
//!
//! fn main() {
//!     let _ = Foo::new(Bar::A);
//! }
//! ```
//!
//! When the bitfield has been constructed, the `new` method reads all fields to ensure that no invalid representations exist in the bitfield.
//! This behaviour can be disabled by using the `#[disable_check]` attribute.
//!
//! `new()` does not support structs that contain types that are not (arrays of) unsigned integers.
//!
//! ## `derive_debug`
//! The `derive_debug` statement implements `core::fmt::Debug` for the bitfield.
//! The underlying representation is not printed.
//!
//! ## `const fn`
//! To generate `const fn` methods, build the bitbash crate with the `"const"` feature.
//! Alternatively, use the `bitflags_const!` macro.
//!
//! A nightly compiler is required and the `#![feature(const_fn, const_panic, const_if_match, const_mut_refs)]` features must be enabled.
//!
//! ## `ConvertRepr` trait
//! Bitfield values must implement the `ConvertRepr` trait.
//! It is implemented for all unsigned integer types and types that derive `BitEnum`.
//!
//! The `try_from_repr` and `into_repr` methods are used in non-`const fn` bitfields.
//! Implement `const_try_from_repr` and `const_into_repr` on your types to also use them in `const fn` bitfields.
//!
//! # `BitEnum` derive
//! The `BitEnum` derive implements `ConvertRepr` for C-style enums.
//!
//! Enums that derive `BitEnum` can be used in both `const fn` and non-`const fn` bitfields.

pub use bitbash_macros::{bitfield_const, bitfield_nonconst, BitEnumConst, BitEnumNonConst};

#[cfg(feature = "const")]
pub use self::{bitfield_const as bitfield, BitEnumConst as BitEnum};
#[cfg(not(feature = "const"))]
pub use self::{bitfield_nonconst as bitfield, BitEnumNonConst as BitEnum};

pub trait ConvertRepr: Sized {
    type Repr;

    fn try_from_repr(repr: Self::Repr) -> Option<Self>;
    fn into_repr(self) -> Self::Repr;
}

macro_rules! impl_convert_repr {
    ($($t:ty),*) => {$(
        impl ConvertRepr for $t {
            type Repr = $t;

            fn try_from_repr(repr: $t) -> Option<$t> {
                Some(repr)
            }

            fn into_repr(self) -> $t {
                self
            }
        }
    )*}
}
impl_convert_repr!(usize, u8, u16, u32, u64, u128);

impl ConvertRepr for bool {
    type Repr = u8;

    fn try_from_repr(repr: u8) -> Option<bool> {
        match repr {
            0 => Some(false),
            1 => Some(true),
            _ => None,
        }
    }

    fn into_repr(self) -> u8 {
        self as u8
    }
}

#[cfg(test)]
mod tests {
    macro_rules! tests {
        ($bitfield:tt, $BitEnum:tt) => {
            #[allow(unused_imports)]
            use crate as bitbash;
            crate::$bitfield! {
                struct Foo(u32);

                new(e);
                derive_debug;

                field a: bool = [31];
                field b: u8 = [0..8];
                field c: u8 = [8..12] ~ [16..20];
                field d: u32 {
                    [12..16] => [12..16],
                    [20..24] => [20..24],
                }
                field e: FooE = [24..28];
            }

            #[derive(crate::$BitEnum, Copy, Clone, PartialEq, Eq, Debug)]
            #[repr(u8)]
            enum FooE {
                A = 0b1010,
                B = 0b0101,
            }

            #[test]
            fn foo() {
                let mut foo = Foo::new(FooE::A);
                assert_eq!(foo.a(), false);
                assert_eq!(foo.b(), 0u8);
                assert_eq!(foo.c(), 0u8);
                assert_eq!(foo.d(), 0u32);
                assert_eq!(foo.e(), FooE::A);
                assert_eq!(foo.0, (FooE::A as u32) << 24);
                foo.set_a(true);
                foo.set_b(0x12u8);
                foo.set_c(0x34u8);
                foo.set_d((0x5u32 << 12) | (0x6u32 << 20));
                foo.set_e(FooE::B);
                assert_eq!(foo.a(), true);
                assert_eq!(foo.b(), 0x12u8);
                assert_eq!(foo.c(), 0x34u8);
                assert_eq!(foo.d(), (0x5u32 << 12) | (0x6u32 << 20));
                assert_eq!(foo.e(), FooE::B);
                assert_eq!(
                    foo.0,
                    (1 << 31)
                        | (0x12 << 0)
                        | ((0x4 << 8) | (0x3 << 16))
                        | ((0x5 << 12) | (0x6 << 20))
                        | ((FooE::B as u32) << 24)
                );

                match <FooE as crate::ConvertRepr>::try_from_repr(FooE::A as u8) {
                    Some(FooE::A) => (),
                    _ => unreachable!(),
                }
            }

            const BAR_LEN: usize = 2;

            crate::$bitfield! {
                struct Bar([u32; BAR_LEN]);

                new();

                field a: u32 = 0;
                field b: u8 = (BAR_LEN - 1)[16..24];
            }

            #[test]
            fn bar() {
                let mut bar = Bar::new();
                assert_eq!(bar.a(), 0);
                assert_eq!(bar.b(), 0);
                assert_eq!(bar.0, [0, 0]);
                bar.set_a(1);
                bar.set_b(2);
                assert_eq!(bar.a(), 1);
                assert_eq!(bar.b(), 2);
                assert_eq!(bar.0, [1, 2 << 16]);
            }

            crate::$bitfield! {
                struct Baz {
                    _padding0: [u32; 1],
                    a0: u32,
                    a1: u32,
                    _padding1: [u32; 2],
                    pub b: u32,
                }

                new();

                field a: u64 = a0 ~ a1;
            }

            #[test]
            fn baz() {
                let mut baz = Baz::new();
                assert_eq!(baz.a(), 0);
                assert_eq!(baz._padding0, [0]);
                assert_eq!(baz.a0, 0);
                assert_eq!(baz.a1, 0);
                assert_eq!(baz._padding1, [0, 0]);
                assert_eq!(baz.b, 0);
                baz.set_a(!0);
                assert_eq!(baz.a(), !0);
                assert_eq!(baz._padding0, [0]);
                assert_eq!(baz.a0, !0);
                assert_eq!(baz.a1, !0);
                assert_eq!(baz._padding1, [0, 0]);
                assert_eq!(baz.b, 0);
                baz.b = 0x10101010;
                assert_eq!(baz.a(), !0);
            }

            const QUUX_START: isize = 0x5;

            #[derive(crate::$BitEnum)]
            enum Quux {
                Asdf = QUUX_START,
                Zxcv = QUUX_START + 1,
            }
        };
    }
    #[cfg(feature = "const")]
    mod konst {
        tests!(bitfield_const, BitEnumConst);
    }
    mod nonconst {
        tests!(bitfield_nonconst, BitEnumNonConst);
    }
}