bitcraft 1.0.0

A zero-cost, hardware-aligned bitfield and enumeration generator.
Documentation
/// A declarative macro for generating packed arrays backed by a fixed-size byte array.
///
/// Storage is automatically sized as `[u8; (width * count + 7) / 8]` — a const expression
/// evaluated at compile time to a concrete fixed size. No heap allocation.
///
/// # Supported element types
/// - `u N` — unsigned N-bit integer
/// - `i N` — signed N-bit integer (sign-extended on `get`)
/// - `bool` — single bit, returned as `bool`
///
/// # Examples
/// ```rust
/// use bitcraft::bytearray;
///
/// bytearray! { pub struct NibbleBuf(u 4, 64); }  // → [u8; 32]
/// bytearray! { pub struct Flags(bool, 128); }     // → [u8; 16]
/// ```
#[macro_export]
macro_rules! bytearray {
    // --- bool: width is always 1 ---
    ($vis:vis struct $name:ident (bool, $count:literal);) => {
        $crate::__bytearray_core!($vis, $name, bool, 1, $count, unsigned, bool_type);
    };
    // --- unsigned ---
    ($vis:vis struct $name:ident (u $width:literal, $count:literal);) => {
        $crate::__bytearray_core!($vis, $name, u128, $width, $count, unsigned, int_type);
    };
    // --- signed ---
    ($vis:vis struct $name:ident (i $width:literal, $count:literal);) => {
        $crate::__bytearray_core!($vis, $name, i128, $width, $count, signed, int_type);
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __bytearray_core {
    ($vis:vis, $name:ident, $elem_ty:ty, $width:literal, $count:literal, $signed_tag:ident, $type_tag:ident) => {
        #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
                 bytemuck::Pod, bytemuck::Zeroable)]
        #[repr(transparent)]
        $vis struct $name(pub [u8; ($width * $count + 7) / 8]);

        impl Default for $name {
            fn default() -> Self {
                Self([0u8; ($width * $count + 7) / 8])
            }
        }

        impl core::fmt::Debug for $name {
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                let mut list = f.debug_list();
                for i in 0..$count { list.entry(&self.get(i)); }
                list.finish()
            }
        }

        impl $name {
            pub const ELEMENT_WIDTH: usize = $width;
            pub const ELEMENT_COUNT: usize = $count;
            pub const TOTAL_BITS:    usize = $width * $count;
            pub const BYTES:         usize = ($width * $count + 7) / 8;

            const MASK: u128 = if $width == 128 { !0u128 } else { (1u128 << $width) - 1 };

            #[inline]
            pub fn get(&self, index: usize) -> $elem_ty {
                $crate::__bytearray_get!(&self.0, Self::BYTES, index, $count, $elem_ty, $width, Self::MASK, $signed_tag, $type_tag)
            }

            #[inline]
            pub fn set(&mut self, index: usize, value: $elem_ty) {
                $crate::__bytearray_set!(&mut self.0, Self::BYTES, index, $count, value, $elem_ty, $width, Self::MASK, $type_tag);
            }
        }

        $crate::paste::paste! {
            impl $name {
                /// Returns an iterator over the logical elements of this array.
                #[inline]
                pub fn iter(&self) -> [< $name Iter >]<'_> {
                    self.into_iter()
                }
            }
            pub struct [< $name Iter >]<'a> {
                array: &'a $name,
                index: usize,
            }

            impl<'a> Iterator for [< $name Iter >]<'a> {
                type Item = $elem_ty;
                
                #[inline]
                fn next(&mut self) -> Option<Self::Item> {
                    if self.index < $count {
                        let val = self.array.get(self.index);
                        self.index += 1;
                        Some(val)
                    } else {
                        None
                    }
                }
                
                #[inline]
                fn size_hint(&self) -> (usize, Option<usize>) {
                    let remaining = $count - self.index;
                    (remaining, Some(remaining))
                }
            }

            impl<'a> ExactSizeIterator for [< $name Iter >]<'a> {}

            impl<'a> IntoIterator for &'a $name {
                type Item = $elem_ty;
                type IntoIter = [< $name Iter >]<'a>;

                #[inline]
                fn into_iter(self) -> Self::IntoIter {
                    [< $name Iter >] {
                        array: self,
                        index: 0,
                    }
                }
            }
        }
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __bytearray_get {
    ($data:expr, $len:expr, $index:expr, $count:expr, $elem_ty:ty, $width:expr, $mask:expr, $signed_tag:ident, $type_tag:ident) => {{
        debug_assert!($index < $count, "bytearray index out of bounds");
        let bit_offset       = $index * $width;
        let byte_idx         = bit_offset / 8;
        let inner_bit_offset = bit_offset % 8;
        let bytes_to_read    = ($width + inner_bit_offset + 7) / 8;

        let mut raw = 0u128;
        for i in 0..bytes_to_read {
            if byte_idx + i < $len {
                raw |= ($data[byte_idx + i] as u128) << (i * 8);
            }
        }

        let extracted = (raw >> inner_bit_offset) & $mask;
        $crate::__bitarray_cast_get!(extracted, $elem_ty, $width, $signed_tag, $type_tag)
    }};
}

#[macro_export]
#[doc(hidden)]
macro_rules! __bytearray_set {
    ($data:expr, $len:expr, $index:expr, $count:expr, $value:expr, $elem_ty:ty, $width:expr, $mask:expr, $type_tag:ident) => {{
        debug_assert!($index < $count, "bytearray index out of bounds");
        let bit_offset       = $index * $width;
        let byte_idx         = bit_offset / 8;
        let inner_bit_offset = bit_offset % 8;

        let raw_val      = $crate::__bitarray_cast_set!($value, u128, $type_tag) & $mask;
        let val_shifted  = raw_val   << inner_bit_offset;
        let mask_shifted = $mask << inner_bit_offset;
        let bytes_to_modify = ($width + inner_bit_offset + 7) / 8;

        for i in 0..bytes_to_modify {
            if byte_idx + i < $len {
                let byte_mask = ((mask_shifted >> (i * 8)) & 0xFF) as u8;
                let byte_val  = ((val_shifted  >> (i * 8)) & 0xFF) as u8;
                $data[byte_idx + i] &= !byte_mask;
                $data[byte_idx + i] |=  byte_val;
            }
        }
    }};
}