chapa 0.1.2

Bitfield structs, batteries included!
Documentation
//! Utilities for masking raw integers by bit ranges.

/// Build a u128 bitmask in MSB0 ordering (bit 0 = MSB).
///
/// `width` is the total bit width of the storage type (e.g. 32 for `u32`).
/// Each element of `ranges` is an inclusive `(start, end)` pair.
pub const fn msb0_mask(width: u32, ranges: &[(u8, u8)]) -> u128 {
    let mut mask = 0u128;
    let mut i = 0;
    while i < ranges.len() {
        let (start, end) = ranges[i];
        let mut bit = start;
        while bit <= end {
            mask |= 1u128 << (width - 1 - bit as u32);
            bit += 1;
        }
        i += 1;
    }
    mask
}

/// Build a u128 bitmask in LSB0 ordering (bit 0 = LSB).
///
/// Each element of `ranges` is an inclusive `(start, end)` pair.
pub const fn lsb0_mask(ranges: &[(u8, u8)]) -> u128 {
    let mut mask = 0u128;
    let mut i = 0;
    while i < ranges.len() {
        let (start, end) = ranges[i];
        let mut bit = start;
        while bit <= end {
            mask |= 1u128 << bit;
            bit += 1;
        }
        i += 1;
    }
    mask
}

/// Internal helper: convert a mixed list of single bits and `start..=end` ranges
/// into an array of `(u8, u8)` pairs via tt-munching.
///
/// Single bit `n` becomes `(n, n)`. Not for direct use.
#[doc(hidden)]
#[macro_export]
macro_rules! __bits_pairs {
    // Base: nothing left, emit the collected pairs as an array
    (@acc [$($pairs:tt)*]) => {
        [$($pairs)*]
    };
    // Range followed by comma + rest
    (@acc [$($pairs:tt)*] $s:literal ..= $e:literal, $($rest:tt)*) => {
        $crate::__bits_pairs!(@acc [$($pairs)* ($s as u8, $e as u8),] $($rest)*)
    };
    // Range at end (no trailing comma)
    (@acc [$($pairs:tt)*] $s:literal ..= $e:literal) => {
        $crate::__bits_pairs!(@acc [$($pairs)* ($s as u8, $e as u8),])
    };
    // Single bit followed by comma + rest
    (@acc [$($pairs:tt)*] $bit:literal, $($rest:tt)*) => {
        $crate::__bits_pairs!(@acc [$($pairs)* ($bit as u8, $bit as u8),] $($rest)*)
    };
    // Single bit at end (no trailing comma)
    (@acc [$($pairs:tt)*] $bit:literal) => {
        $crate::__bits_pairs!(@acc [$($pairs)* ($bit as u8, $bit as u8),])
    };
    // Entry point
    ($($tokens:tt)*) => {
        $crate::__bits_pairs!(@acc [] $($tokens)*)
    };
}

/// Compute the mask for a chapa bitfield struct, using ordering and width from its [`BitField`] impl.
///
/// Used by the no-prefix form of [`extract_bits!`].
///
/// [`BitField`]: crate::BitField
#[inline]
pub fn extract_mask<T: crate::BitField>(ranges: &[(u8, u8)]) -> T::Storage {
    let raw = if T::IS_MSB0 {
        msb0_mask(<T::Storage as crate::BitStorage>::BITS, ranges)
    } else {
        lsb0_mask(ranges)
    };
    <T::Storage as crate::BitStorage>::from_u128(raw)
}

/// Apply a bit mask to a chapa bitfield struct, keeping only the specified bits.
///
/// Used by the no-prefix form of [`extract_bits!`]. The ordering and storage
/// width are taken from the struct's [`BitField`] impl (generated by `#[bitfield]`).
///
/// [`BitField`]: crate::BitField
#[inline]
pub fn extract_bits_auto<T>(val: T, ranges: &[(u8, u8)]) -> T
where
    T: crate::BitField,
    T: core::ops::BitAnd<T::Storage, Output = T>,
{
    val & extract_mask::<T>(ranges)
}

/// Keep only the specified bits from a value.
///
/// # Syntax
///
/// ```
/// # use chapa::extract_bits;
/// let val: u32 = 0xFFFF_FFFF;
///
/// // MSB0 ordering (bit 0 = MSB): keep bits 0, 5–9, and 16–31
/// let masked = extract_bits!(msb0 u32; val; 0, 5..=9, 16..=31);
/// assert_eq!(masked, 0x87C0_FFFF);
///
/// // LSB0 ordering (bit 0 = LSB): keep bits 0–3 and 12–15
/// let masked = extract_bits!(lsb0 u16; val as u16; 0..=3, 12..=15);
/// assert_eq!(masked, 0xF00F);
/// ```
///
/// For chapa bitfield structs, the ordering and storage type can be omitted —
/// they are deduced from the struct's [`BitField::IS_MSB0`] constant.
/// The result is the same struct type with the non-selected bits zeroed out.
///
/// **Note on const:** The explicit (`msb0`/`lsb0`) form emits `const MASK: T = ...`,
/// which is computed at compile time. The struct form calls an [`#[inline]`](inline) helper; 
/// LLVM will constant-fold the mask in optimized builds but there is no language-level `const` guarantee.
///
/// ```ignore
/// // Ordering deduced from the chapa struct (MSR is #[bitfield(u32, order = msb0)])
/// let masked_msr: Msr = extract_bits!(msr; 0, 5..=9, 16..=31);
/// let srr1: u32 = masked_msr.raw();
/// ```
///
/// [`BitField::IS_MSB0`]: crate::BitField::IS_MSB0
#[macro_export]
macro_rules! extract_bits {
    // Explicit MSB0 with type: extract_bits!(msb0 u32; val; specs...)
    (msb0 $ty:ty; $val:expr; $($specs:tt)*) => {{
        const MASK: $ty = $crate::mask::msb0_mask(
            <$ty>::BITS,
            &$crate::__bits_pairs!($($specs)*),
        ) as $ty;
        ($val) & MASK
    }};
    // Explicit LSB0 with type: extract_bits!(lsb0 u8; val; specs...)
    (lsb0 $ty:ty; $val:expr; $($specs:tt)*) => {{
        const MASK: $ty = $crate::mask::lsb0_mask(
            &$crate::__bits_pairs!($($specs)*),
        ) as $ty;
        ($val) & MASK
    }};
    // Chapa struct (ordering deduced): extract_bits!(struct_val; specs...)
    ($val:expr; $($specs:tt)*) => {
        $crate::mask::extract_bits_auto($val, &$crate::__bits_pairs!($($specs)*))
    };
}