bmx 0.0.2

Binary modeling expressions
Documentation
use std::io;

#[derive(Debug)]
pub struct HexReader<R> {
    reader: R,
}

impl<R> HexReader<R> {
    pub fn new(reader: R) -> Self {
        HexReader { reader }
    }
}

#[derive(Debug, Copy, Clone)]
enum HexState {
    Comment(Nibble),
    Data(Nibble),
}

#[derive(Debug, Copy, Clone)]
enum Nibble {
    Low,
    High,
}

fn as_nibble(octet: u8) -> Option<u8> {
    match octet {
        b'0'..=b'9' => Some(octet - b'0'),
        b'a'..=b'f' => Some(10 + (octet - b'a')),
        b'A'..=b'F' => Some(10 + (octet - b'A')),
        _ => None,
    }
}

impl<R: io::Read> io::Read for HexReader<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let mut n_decoded = 0;
        for loc in buf.iter_mut() {
            use HexState::*;
            use Nibble::*;
            let mut state = Data(High);
            let mut acc = 0;
            loop {
                let mut b = [0u8; 1];
                let n_read = self.reader.read(&mut b)?;
                if n_read == 0 {
                    match state {
                        Data(High) | Comment(High) => {
                            return Ok(n_decoded);
                        }
                        _ => {
                            return Err(io::Error::new(
                                io::ErrorKind::InvalidData,
                                "end of input when expecting low nibble",
                            ));
                        }
                    }
                }
                let octet = b[0];
                match state {
                    Comment(n) => {
                        if octet == b'\n' {
                            state = Data(n);
                        }
                    }
                    Data(n) => match as_nibble(octet) {
                        Some(nibble) => match n {
                            High => {
                                acc = nibble << 4;
                                state = Data(Low);
                            }
                            Low => {
                                *loc = acc | nibble;
                                n_decoded += 1;
                                break;
                            }
                        },
                        None => match octet {
                            b'#' => {
                                state = Comment(n);
                            }
                            b'\n' | b' ' | b'\t' | 12 => {}
                            _ => {
                                return Err(io::Error::new(
                                    io::ErrorKind::InvalidData,
                                    "octet not hexidecimal ASCII",
                                ));
                            }
                        },
                    },
                }
            }
        }
        Ok(n_decoded)
    }
}