use core::mem::MaybeUninit;
use core::ops::{Add, Not, Shl, Shr};
use core::slice::SliceIndex;
pub(crate) fn initialize_with<T, F, const N: usize>(mut f: F) -> [T; N]
where
F: FnMut() -> T,
{
let mut uninit = MaybeUninit::<[T; N]>::uninit();
for i in 0..N {
let ptr = uninit.as_mut_ptr();
let ptr = unsafe { ptr.as_mut().unwrap_unchecked() };
ptr[i] = f();
}
unsafe { uninit.assume_init() }
}
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(usize)]
pub enum BitOffset {
#[default]
Bit0 = 0,
Bit1 = 1,
Bit2 = 2,
Bit3 = 3,
Bit4 = 4,
Bit5 = 5,
Bit6 = 6,
Bit7 = 7,
}
impl BitOffset {
pub fn new(offset: u8) -> Option<Self> {
[
BitOffset::Bit0,
BitOffset::Bit1,
BitOffset::Bit2,
BitOffset::Bit3,
BitOffset::Bit4,
BitOffset::Bit5,
BitOffset::Bit6,
BitOffset::Bit7,
]
.get(offset as usize)
.copied()
}
pub fn new_wrapped(offset: usize) -> Self {
*[
BitOffset::Bit0,
BitOffset::Bit1,
BitOffset::Bit2,
BitOffset::Bit3,
BitOffset::Bit4,
BitOffset::Bit5,
BitOffset::Bit6,
BitOffset::Bit7,
]
.get(offset % crate::BITS_PER_BYTE)
.expect("Index must be in range")
}
}
impl From<BitOffset> for u8 {
fn from(offset: BitOffset) -> Self {
offset as u8
}
}
impl From<BitOffset> for usize {
fn from(offset: BitOffset) -> Self {
offset as usize
}
}
impl Add for BitOffset {
type Output = (BitOffset, bool);
fn add(self, rhs: BitOffset) -> Self::Output {
let lhs = u8::from(self);
let rhs = u8::from(rhs);
match lhs + rhs {
offset @ 0..8 => {
let Some(val) = BitOffset::new(offset) else {
unreachable!("Value is normalized and within range of a bit offset");
};
(val, false)
}
offset @ 8..15 => {
let Some(val) = BitOffset::new(offset - 8) else {
unreachable!("Value is normalized and within range of a bit offset");
};
(val, true)
}
_ => unreachable!(),
}
}
}
impl Not for BitOffset {
type Output = BitOffset;
fn not(self) -> Self::Output {
let Some(offset) = BitOffset::new(8 - u8::from(self)) else {
unreachable!()
};
offset
}
}
impl Shr<BitOffset> for u8 {
type Output = u8;
fn shr(self, rhs: BitOffset) -> Self::Output {
self.overflowing_shr(u8::from(rhs) as u32).0
}
}
impl Shl<BitOffset> for u8 {
type Output = u8;
fn shl(self, rhs: BitOffset) -> Self::Output {
self.overflowing_shl(u8::from(rhs) as u32).0
}
}
#[derive(Copy, Clone, Default, Debug, Eq, PartialOrd, Ord, Hash)]
pub struct BitSlice<'data> {
pub(crate) data: &'data [u8],
pub(crate) bit_offset: BitOffset,
}
impl PartialEq for BitSlice<'_> {
fn eq(&self, other: &BitSlice<'_>) -> bool {
if self.bit_offset != other.bit_offset {
return false;
}
let mask = 0xff >> self.bit_offset;
if self.data.first().map(|b| *b & mask) != other.data.first().map(|b| *b & mask) {
return false;
}
self.data.get(1..) == other.data.get(1..)
}
}
impl<'data> BitSlice<'data> {
pub fn new(data: &'data [u8], bit_offset: BitOffset) -> Self {
BitSlice { data, bit_offset }
}
pub fn bits(&self) -> usize {
let data_bits = self.data.len() * 8;
data_bits - usize::from(self.bit_offset)
}
pub fn with_offset(self, bit_offset: BitOffset) -> Self {
BitSlice {
data: self.data,
bit_offset,
}
}
pub fn offset_by_sized<S>(self) -> Option<BitSlice<'data>>
where
S: crate::Sized,
{
let BitSlice { data, bit_offset } = self;
let (bytes, bits) = S::bytes_and_bits();
let (bit_offset, overflow) = bit_offset + bits;
let byte_offset = bytes + overflow as usize;
let data = data.get(byte_offset..)?;
Some(BitSlice { data, bit_offset })
}
pub fn data(&self) -> &'data [u8] {
self.data
}
pub fn bit_offset(&self) -> &BitOffset {
&self.bit_offset
}
}
impl<'data> BitSlice<'data> {
pub fn copy_array<const N: usize>(&self) -> Option<[u8; N]> {
match self.bit_offset {
BitOffset::Bit0 => self.data.first_chunk().copied(),
_ => {
if self.data.len() < N + 1 {
return None;
}
let BitSlice { data, bit_offset } = *self;
let mut index = 0;
Some(initialize_with(|| {
let Some([b1, b2]) = data.get(index..=index + 1) else {
unreachable!("Array bounds checked and constant sized range for indexing.")
};
index += 1;
(*b1 << bit_offset) | (*b2 >> !bit_offset)
}))
}
}
}
pub fn copy_bits<const N: usize>(&self, bits: usize) -> Option<[u8; N]> {
use crate::BITS_PER_BYTE;
if N * BITS_PER_BYTE < bits {
panic!("N ({}) must be larger than amount of bits ({})", N, bits);
}
if bits % BITS_PER_BYTE == 0 {
self.copy_array::<N>().map(|mut array: [u8; N]| -> [u8; N] {
if let Some(last) = array.get_mut((bits / BITS_PER_BYTE)..) {
last.fill(0);
}
array
})
} else {
match self.bit_offset {
BitOffset::Bit0 => {
self.data
.first_chunk()
.copied()
.map(|mut array: [u8; N]| -> [u8; N] {
if let Some(last) = array.get_mut((bits / BITS_PER_BYTE + 1)..) {
last.fill(0);
}
if let Some(edge) = array.get_mut(bits / BITS_PER_BYTE) {
let mask = 0xff >> (bits % BITS_PER_BYTE);
*edge &= !mask;
}
array
})
}
_ => {
if self.bits() < bits {
return None;
}
let BitSlice { data, bit_offset } = *self;
let mut index = 0;
let mut bits_left = bits;
Some(initialize_with(|| {
let Some(b1) = data.get(index) else {
unreachable!("Array bounds checked")
};
index += 1;
let bits = bits_left;
bits_left = bits_left.checked_sub(BITS_PER_BYTE).unwrap_or_default();
let b2 = data.get(index).map(|b2| *b2 >> !bit_offset).unwrap_or(0);
((*b1 << bit_offset) | b2) & !(0xff >> (bits % BITS_PER_BYTE))
}))
}
}
}
}
pub fn get<I>(&self, index: I) -> Option<BitSlice<'data>>
where
I: SliceIndex<[u8], Output = [u8]>,
{
let BitSlice { data, bit_offset } = *self;
let data = data.get(index)?;
Some(BitSlice { data, bit_offset })
}
}
impl<'data> From<&'data [u8]> for BitSlice<'data> {
fn from(data: &'data [u8]) -> Self {
BitSlice {
data,
bit_offset: Default::default(),
}
}
}
impl<'data, const N: usize> From<&'data [u8; N]> for BitSlice<'data> {
fn from(data: &'data [u8; N]) -> Self {
BitSlice {
data,
bit_offset: Default::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn copy_array() {
let data = &[0b00000001, 0b00000010, 0b00000011];
let slice: BitSlice = data.into();
assert_eq!(slice.copy_array::<2>(), Some([0b00000001, 0b00000010]));
let slice = slice.with_offset(BitOffset::Bit1);
assert_eq!(slice.copy_array::<2>(), Some([0b00000010, 0b00000100]));
let slice = slice.with_offset(BitOffset::Bit7);
assert_eq!(slice.copy_array::<2>(), Some([0b10000001, 0b00000001]));
}
}