use std::{cell::RefCell, rc::Rc};
use nom::{
Compare, IResult, Input, Parser,
bytes::complete::tag,
combinator::{cond, flat_map, iterator},
error::{ErrorKind, FromExternalError},
number::complete::le_u8,
};
pub(crate) const MP_BOTH_UNPACKABLE_HEADER: [u8; 1] = [0xFF];
pub(crate) const MP_SINGLE_UNPACKABLE_MASK: u8 = 0xF;
pub(crate) const MP_COMMAND_HEADER: [u8; 2] = [0xFF, 0xFF];
pub(crate) const MP_COMMAND_ENABLE_PACKING: u8 = 251;
pub(crate) const MP_COMMAND_DISABLE_PACKING: u8 = 250;
pub(crate) const MP_COMMAND_RESET_ALL: u8 = 249;
pub(crate) const MP_COMMAND_QUERY_CONFIG: u8 = 248;
pub(crate) const MP_COMMAND_ENABLE_NO_SPACES: u8 = 247;
pub(crate) const MP_COMMAND_DISABLE_NO_SPACES: u8 = 246;
pub fn meatpacked_to_string<I>(input: I) -> IResult<I, String, nom::error::Error<I>>
where
I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
{
let state = Rc::new(RefCell::new(MeatpackState::default()));
let mut parser = iterator(input, decode_next(state.clone()));
let it = &mut parser;
let bytes = it.fold(vec![], |mut acc, yielded| {
match yielded {
YieldedChars::None => {}
YieldedChars::One(one) => acc.push(one),
YieldedChars::Two(two) => acc.extend_from_slice(&two),
};
acc
});
let (remaining, ()) = parser.finish()?;
match String::from_utf8(bytes) {
Ok(string) => Ok((remaining, string)),
Err(err) => Err(nom::Err::Error(nom::error::Error::from_external_error(
remaining,
ErrorKind::Char,
err,
))),
}
}
#[derive(Debug, Default)]
struct MeatpackState {
packing: bool,
no_spaces: bool,
}
enum YieldedChars {
None,
One(u8),
Two([u8; 2]),
}
fn decode_next<I>(
state: Rc<RefCell<MeatpackState>>,
) -> impl Parser<I, Output = YieldedChars, Error = nom::error::Error<I>>
where
I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
{
let state_clone = state.clone();
flat_map(tag(MP_COMMAND_HEADER.as_slice()), |_tag| le_u8)
.map(move |command| {
let mut state = state.borrow_mut();
match command {
MP_COMMAND_ENABLE_PACKING => state.packing = true,
MP_COMMAND_DISABLE_PACKING => state.packing = false,
MP_COMMAND_ENABLE_NO_SPACES => state.no_spaces = true,
MP_COMMAND_DISABLE_NO_SPACES => state.no_spaces = false,
MP_COMMAND_RESET_ALL => state.packing = false,
MP_COMMAND_QUERY_CONFIG => {}
_other => {}
}
YieldedChars::None
})
.or(decode_character_pair(state_clone))
}
fn decode_character_pair<I>(
state: Rc<RefCell<MeatpackState>>,
) -> impl Parser<I, Output = YieldedChars, Error = nom::error::Error<I>>
where
I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
{
let both_unpacked_parser = tag(MP_BOTH_UNPACKABLE_HEADER.as_slice())
.and(le_u8)
.and(le_u8)
.map(|((_tag, first), second)| YieldedChars::Two([first, second]));
let packed_parser = flat_map(le_u8, move |byte: u8| {
let state = state.borrow();
let first_unpacked = if state.packing {
unpack_character(byte & MP_SINGLE_UNPACKABLE_MASK, state.no_spaces)
} else {
None
};
let second_unpacked = if state.packing {
unpack_character((byte >> 4) & MP_SINGLE_UNPACKABLE_MASK, state.no_spaces)
} else {
None
};
cond(
state.packing && (first_unpacked.is_none() || second_unpacked.is_none()),
le_u8,
)
.map(move |next_byte| match (first_unpacked, second_unpacked) {
(None, None) => YieldedChars::One(byte),
(None, Some(second)) => YieldedChars::Two([next_byte.unwrap(), second]),
(Some(first), None) => YieldedChars::Two([first, next_byte.unwrap()]),
(Some(first), Some(second)) => YieldedChars::Two([first, second]),
})
});
both_unpacked_parser.or(packed_parser)
}
const fn unpack_character(x: u8, no_spaces: bool) -> Option<u8> {
Some(match x {
0 => b'0',
1 => b'1',
2 => b'2',
3 => b'3',
4 => b'4',
5 => b'5',
6 => b'6',
7 => b'7',
8 => b'8',
9 => b'9',
10 => b'.',
11 if !no_spaces => b' ',
11 if no_spaces => b'E',
12 => b'\n',
13 => b'G',
14 => b'X',
_other => return None,
})
}