How to write a macro that splits a byte into a tuple of bits of user-specified count?

145 Views Asked by At

I would like to have macro splitting one byte into tuple with 2-8 u8 parts using bitreader crate.

I managed to achieve that by following code:

use bitreader::BitReader;

trait Tupleprepend<T> {
    type ResultType;
    fn prepend(self, t: T) -> Self::ResultType;
}

macro_rules! impl_tuple_prepend {
    ( () ) => {};
    ( ( $t0:ident $(, $types:ident)* ) ) => {
        impl<$t0, $($types,)* T> Tupleprepend<T> for ($t0, $($types,)*) {
            type ResultType = (T, $t0, $($types,)*);

            fn prepend(self, t: T) -> Self::ResultType {
                let ($t0, $($types,)*) = self;
                (t, $t0, $($types,)*)
            }
        }

        impl_tuple_prepend! { ($($types),*) }
    };
}

impl_tuple_prepend! {
    (_1, _2, _3, _4, _5, _6, _7, _8)
}

macro_rules! split_byte (
    ($reader:ident, $bytes:expr, $count:expr) => {{
        ($reader.read_u8($count).unwrap(),)
    }};
    ($reader:ident, $bytes:expr, $count:expr, $($next_counts:expr),+) => {{
        let head = split_byte!($reader, $bytes, $count);
        let tail = split_byte!($reader, $bytes, $($next_counts),+);
        tail.prepend(head.0)
    }};
    ($bytes:expr $(, $count:expr)* ) => {{
        let mut reader = BitReader::new($bytes);
        split_byte!(reader, $bytes $(, $count)+)
    }};
);

Now I can use this code as I would like to:

let buf: &[u8] = &[0x72];
let (bit1, bit2, bits3to8) = split_byte!(&buf, 1, 1, 6);

Is there a way to avoid using Tupleprepend trait and create only 1 tuple instead of 8 in the worst scenario?

1

There are 1 best solutions below

0
On

Because the number of bit widths directly corresponds to the number of returned values, I'd solve the problem using generics and arrays instead. The macro only exists to remove the typing of the [], which I don't really think is worth it.

fn split_byte<A>(b: u8, bit_widths: A) -> A
where
    A: Default + std::ops::IndexMut<usize, Output = u8>,
    for<'a> &'a A: IntoIterator<Item = &'a u8>,
{
    let mut result = A::default();
    let mut start = 0;
    for (idx, &width) in bit_widths.into_iter().enumerate() {
        let shifted = b >> (8 - width - start);
        let mask = (0..width).fold(0, |a, _| (a << 1) | 1);
        result[idx] = shifted & mask;
        start += width;
    }
    result
}

macro_rules! split_byte {
    ($b:expr, $($w:expr),+) => (split_byte($b, [$($w),+]));
}

fn main() {
    let [bit1, bit2, bits3_to_8] = split_byte!(0b1010_1010, 1, 1, 6);
    assert_eq!(bit1, 0b1);
    assert_eq!(bit2, 0b0);
    assert_eq!(bits3_to_8, 0b10_1010);
}

See also:

If it's ok to target nightly Rust, I'd use the unstable min_const_generics feature:

#![feature(min_const_generics)]

fn split_byte<const N: usize>(b: u8, bit_widths: [u8; N]) -> [u8; N] {
    let mut result = [0; N];
    let mut start = 0;
    for (idx, &width) in bit_widths.iter().enumerate() {
        let shifted = b >> (8 - width - start);
        let mask = (0..width).fold(0, |a, _| (a << 1) | 1);
        result[idx] = shifted & mask;
        start += width;
    }
    result
}

macro_rules! split_byte {
    ($b:expr, $($w:expr),+) => (split_byte($b, [$($w),+]));
}

fn main() {
    let [bit1, bit2, bits3_to_8] = split_byte!(0b1010_1010, 1, 1, 6);
    assert_eq!(bit1, 0b1);
    assert_eq!(bit2, 0b0);
    assert_eq!(bits3_to_8, 0b10_1010);
}

See also: