1use std::{cell::RefCell, rc::Rc};
6
7use nom::{
8 Compare, IResult, Input, Parser,
9 bytes::complete::tag,
10 combinator::{cond, flat_map, iterator},
11 error::{ErrorKind, FromExternalError},
12 number::complete::le_u8,
13};
14
15pub(crate) const MP_BOTH_UNPACKABLE_HEADER: [u8; 1] = [0xFF];
17
18pub(crate) const MP_SINGLE_UNPACKABLE_MASK: u8 = 0xF;
20
21pub(crate) const MP_COMMAND_HEADER: [u8; 2] = [0xFF, 0xFF];
23
24pub(crate) const MP_COMMAND_ENABLE_PACKING: u8 = 251;
26
27pub(crate) const MP_COMMAND_DISABLE_PACKING: u8 = 250;
29
30pub(crate) const MP_COMMAND_RESET_ALL: u8 = 249;
34
35pub(crate) const MP_COMMAND_QUERY_CONFIG: u8 = 248;
37
38pub(crate) const MP_COMMAND_ENABLE_NO_SPACES: u8 = 247;
42pub(crate) const MP_COMMAND_DISABLE_NO_SPACES: u8 = 246;
43
44pub fn meatpacked_to_string<I>(input: I) -> IResult<I, String, nom::error::Error<I>>
48where
49 I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
50{
51 let state = Rc::new(RefCell::new(MeatpackState::default()));
52 let mut parser = iterator(input, decode_next(state.clone()));
53 let it = &mut parser;
54 let bytes = it.fold(vec![], |mut acc, yielded| {
55 match yielded {
56 YieldedChars::None => {}
57 YieldedChars::One(one) => acc.push(one),
58 YieldedChars::Two(two) => acc.extend_from_slice(&two),
59 };
60 acc
61 });
62 let (remaining, ()) = parser.finish()?;
63
64 match String::from_utf8(bytes) {
65 Ok(string) => Ok((remaining, string)),
66 Err(err) => Err(nom::Err::Error(nom::error::Error::from_external_error(
67 remaining,
68 ErrorKind::Char,
69 err,
70 ))),
71 }
72}
73
74#[derive(Debug, Default)]
76struct MeatpackState {
77 packing: bool,
78 no_spaces: bool,
79}
80
81enum YieldedChars {
82 None,
83 One(u8),
84 Two([u8; 2]),
85}
86
87fn decode_next<I>(
89 state: Rc<RefCell<MeatpackState>>,
90) -> impl Parser<I, Output = YieldedChars, Error = nom::error::Error<I>>
91where
92 I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
93{
94 let state_clone = state.clone();
95 flat_map(tag(MP_COMMAND_HEADER.as_slice()), |_tag| le_u8)
96 .map(move |command| {
97 let mut state = state.borrow_mut();
98 match command {
99 MP_COMMAND_ENABLE_PACKING => state.packing = true,
100 MP_COMMAND_DISABLE_PACKING => state.packing = false,
101 MP_COMMAND_ENABLE_NO_SPACES => state.no_spaces = true,
102 MP_COMMAND_DISABLE_NO_SPACES => state.no_spaces = false,
103 MP_COMMAND_RESET_ALL => state.packing = false,
104 MP_COMMAND_QUERY_CONFIG => {}
105 _other => {}
107 }
108 YieldedChars::None
109 })
110 .or(decode_character_pair(state_clone))
111}
112
113fn decode_character_pair<I>(
115 state: Rc<RefCell<MeatpackState>>,
116) -> impl Parser<I, Output = YieldedChars, Error = nom::error::Error<I>>
117where
118 I: Input<Item = u8> + for<'a> Compare<&'a [u8]>,
119{
120 let both_unpacked_parser = tag(MP_BOTH_UNPACKABLE_HEADER.as_slice())
121 .and(le_u8)
122 .and(le_u8)
123 .map(|((_tag, first), second)| YieldedChars::Two([first, second]));
124
125 let packed_parser = flat_map(le_u8, move |byte: u8| {
126 let state = state.borrow();
127 let first_unpacked = if state.packing {
128 unpack_character(byte & MP_SINGLE_UNPACKABLE_MASK, state.no_spaces)
129 } else {
130 None
131 };
132 let second_unpacked = if state.packing {
133 unpack_character((byte >> 4) & MP_SINGLE_UNPACKABLE_MASK, state.no_spaces)
134 } else {
135 None
136 };
137 cond(
138 state.packing && (first_unpacked.is_none() || second_unpacked.is_none()),
139 le_u8,
140 )
141 .map(move |next_byte| match (first_unpacked, second_unpacked) {
142 (None, None) => YieldedChars::One(byte),
143 (None, Some(second)) => YieldedChars::Two([next_byte.unwrap(), second]),
144 (Some(first), None) => YieldedChars::Two([first, next_byte.unwrap()]),
145 (Some(first), Some(second)) => YieldedChars::Two([first, second]),
146 })
147 });
148
149 both_unpacked_parser.or(packed_parser)
150}
151
152const fn unpack_character(x: u8, no_spaces: bool) -> Option<u8> {
154 Some(match x {
155 0 => b'0',
156 1 => b'1',
157 2 => b'2',
158 3 => b'3',
159 4 => b'4',
160 5 => b'5',
161 6 => b'6',
162 7 => b'7',
163 8 => b'8',
164 9 => b'9',
165 10 => b'.',
166 11 if !no_spaces => b' ',
167 11 if no_spaces => b'E',
168 12 => b'\n',
169 13 => b'G',
170 14 => b'X',
171 _other => return None,
172 })
173}