1use std::{borrow::Borrow, fmt::Arguments, io::Write as IoWrite};
6
7use crate::{
8 emit::{Field, FormatOptions, Token, Value, token::Flag},
9 parse::compact::meatpack::*,
10};
11
12#[derive(Clone)]
13pub struct MeatpackOptions {
14 pub no_spaces: bool,
15}
16
17struct MeatpackEncodingWriter<W> {
18 downstream: W,
19 meatpack_opts: MeatpackOptions,
20 checksum_acc: u8,
21 enabled: bool,
22 pending: Option<(u8, bool)>,
23}
24
25impl<W> MeatpackEncodingWriter<W>
26where
27 W: IoWrite,
28{
29 fn new(downstream: W, meatpack_opts: MeatpackOptions) -> Self {
30 Self {
31 downstream,
32 meatpack_opts,
33 checksum_acc: 0,
34 enabled: false,
35 pending: None,
36 }
37 }
38
39 fn start_meatpack(&mut self) -> std::io::Result<()> {
40 if self.meatpack_opts.no_spaces {
41 self.downstream.write_all(&MP_COMMAND_HEADER)?;
42 self.downstream.write_all(&[MP_COMMAND_ENABLE_NO_SPACES])?;
43 }
44 Ok(())
45 }
46
47 fn stop_meatpack(&mut self) -> std::io::Result<()> {
48 if self.pending.is_some() {
49 self.enable_packing()?;
50 self.write_pending()?;
51 }
52 self.downstream.write_all(&MP_COMMAND_HEADER)?;
53 self.downstream.write_all(&[MP_COMMAND_RESET_ALL])?;
54 self.enabled = false;
55 Ok(())
56 }
57
58 fn enable_packing(&mut self) -> std::io::Result<()> {
59 if !self.enabled {
60 self.downstream.write_all(&MP_COMMAND_HEADER)?;
61 self.downstream.write_all(&[MP_COMMAND_ENABLE_PACKING])?;
62 }
63 self.enabled = true;
64 Ok(())
65 }
66
67 fn disable_packing(&mut self) -> std::io::Result<()> {
68 if self.enabled {
69 self.write_pending()?;
70 self.downstream.write_all(&MP_COMMAND_HEADER)?;
71 self.downstream.write_all(&[MP_COMMAND_DISABLE_PACKING])?;
72 }
73 self.enabled = false;
74 Ok(())
75 }
76
77 fn will_write_newline(&mut self) -> bool {
78 self.enabled && self.meatpack_opts.no_spaces && self.pending.is_some()
79 }
80
81 fn write_pending(&mut self) -> std::io::Result<()> {
82 if let Some((pending_byte, _)) = self.pending.take() {
83 self.write_slice(
84 [
85 pending_byte,
86 if self.meatpack_opts.no_spaces {
87 b'\n'
88 } else {
89 b' '
90 },
91 ]
92 .as_slice(),
93 )?;
94 }
95 Ok(())
96 }
97
98 fn checksum(&self) -> u8 {
99 let mut checksum = self.checksum_acc;
100 if let Some((pending_byte, true)) = self.pending {
101 if self.enabled {
102 checksum ^= packable_to_uppercase(pending_byte, self.meatpack_opts.no_spaces);
103 } else {
104 checksum ^= pending_byte;
105 }
106 }
107 checksum
108 }
109
110 fn reset_checksum(&mut self) {
111 self.checksum_acc = 0;
112 if let Some((_, ref mut include_pending_in_checksum)) = self.pending {
113 *include_pending_in_checksum = false;
114 }
115 }
116
117 fn write_fmt(&mut self, arguments: Arguments<'_>) -> std::io::Result<()> {
118 let input = arguments.to_string();
119 if !self.enabled {
120 self.checksum_acc = input.bytes().fold(self.checksum_acc, |acc, b| acc ^ b);
121 self.downstream.write_all(input.as_bytes())?;
122 } else if !input.is_empty() {
123 assert!(input.is_ascii(), "Meatpack can only encode ASCII");
124 if self.meatpack_opts.no_spaces {
125 for substr in input.split(' ') {
126 self.write_slice(substr.as_bytes())?;
127 }
128 } else {
129 self.write_slice(input.as_bytes())?;
130 }
131 }
132
133 Ok(())
134 }
135
136 fn write_slice(&mut self, mut slice: &[u8]) -> std::io::Result<()> {
137 if slice.is_empty() {
138 return Ok(());
139 }
140 let mut pending_slice_opt = None;
141 let mut _pending_array_opt = None;
142 if let Some((pending, include_pending_in_checksum)) = self.pending.take() {
143 _pending_array_opt = Some([pending, slice[0]]);
144 pending_slice_opt = _pending_array_opt
145 .as_ref()
146 .map(|array| (array.as_slice(), include_pending_in_checksum));
147 slice = &slice[1..];
148 }
149
150 for (chunk, include_first_in_checksum) in pending_slice_opt
151 .into_iter()
152 .chain(slice.chunks(2).map(|c| (c, true)))
153 {
154 match chunk {
155 [first, second] => {
156 let first = packable_to_uppercase(*first, self.meatpack_opts.no_spaces);
157 let second = packable_to_uppercase(*second, self.meatpack_opts.no_spaces);
158 if include_first_in_checksum {
159 self.checksum_acc ^= first;
160 }
161 self.checksum_acc ^= second;
162 write_packed_characters(
163 [first, second],
164 self.meatpack_opts.no_spaces,
165 &mut self.downstream,
166 )?;
167 }
168 [odd] => self.pending = Some((*odd, true)),
169 _ => unreachable!(),
170 }
171 }
172 Ok(())
173 }
174}
175
176pub fn format_gcode_meatpack<'a: 'b, 'b, W, I, T>(
178 program: I,
179 opts: FormatOptions,
180 meatpack_opts: MeatpackOptions,
181 w: W,
182) -> std::io::Result<()>
183where
184 W: IoWrite,
185 I: IntoIterator<Item = T>,
186 T: Borrow<Token<'a>> + 'b,
187{
188 let mut preceded_by_newline = true;
189 let mut line_number = 0usize;
190
191 let mut w = MeatpackEncodingWriter::new(w, meatpack_opts);
192 w.start_meatpack()?;
193
194 if opts.delimit_with_percent {
195 writeln!(w, "%")?;
196 w.reset_checksum();
197 }
198
199 for token in program {
200 let token = token.borrow();
201 if let Token::Field(f) = token {
202 if preceded_by_newline && f.letters == "N" {
204 continue;
205 }
206 }
207
208 let disable_meatpack = match token {
211 Token::Field(Field { letters, value }) => {
212 !letters.is_ascii()
213 || match value {
214 Value::String(_) => true,
215 Value::Rational(_) | Value::Float(_) | Value::Integer(_) => false,
216 }
217 }
218 Token::Flag(Flag { letter }) => !letter.is_ascii(),
219 Token::Comment { .. } => true,
220 };
221
222 if disable_meatpack {
223 let will_write_newline = w.will_write_newline();
224 if will_write_newline {
225 if opts.line_numbers && preceded_by_newline {
226 write!(w, "N{line_number} ")?;
227 }
228
229 if opts.checksums {
230 write!(w, "*{}", w.checksum())?;
231 }
232 line_number += 1;
233 }
234 let still_going_to_write_newline = w.will_write_newline();
235 w.disable_packing()?;
236 if will_write_newline {
237 if !still_going_to_write_newline {
239 writeln!(w)?;
240 }
241 w.reset_checksum();
242 }
243 preceded_by_newline = will_write_newline;
244 } else {
245 w.enable_packing()?;
246 }
247
248 if opts.line_numbers && preceded_by_newline {
249 write!(w, "N{line_number} ")?;
250 }
251
252 match token {
253 Token::Field(f) => {
254 if !preceded_by_newline {
255 if matches!(f.letters.as_ref(), "G" | "g" | "M" | "m" | "D" | "d") {
256 if opts.checksums {
257 write!(w, "*{}", w.checksum())?;
258 }
259 line_number += 1;
260 writeln!(w)?;
261 w.reset_checksum();
262 if opts.line_numbers {
263 write!(w, "N{line_number} ")?;
264 }
265 } else {
266 write!(w, " ")?;
267 }
268 }
269
270 write!(w, "{f}")?;
271 preceded_by_newline = false;
272 }
273 Token::Flag(f) => {
274 if !preceded_by_newline {
275 write!(w, " ")?;
276 }
277 write!(w, "{f}")?;
278 }
279 Token::Comment {
280 is_inline: true,
281 inner,
282 } => {
283 write!(w, "({inner})")?;
284 preceded_by_newline = false;
285 }
286 Token::Comment {
287 is_inline: false,
288 inner,
289 } => {
290 if opts.checksums {
291 write!(w, "*{}", w.checksum())?;
292 }
293 if !preceded_by_newline && opts.newline_before_comment {
294 line_number += 1;
295 writeln!(w)?;
296 w.reset_checksum();
297 if opts.line_numbers {
298 write!(w, "N{line_number} ")?;
299 }
300 if opts.checksums {
301 write!(w, "*{}", w.checksum())?;
302 }
303 }
304 line_number += 1;
305 writeln!(w, ";{inner}")?;
306 w.reset_checksum();
307 preceded_by_newline = true;
308 }
309 }
310 }
311 if !preceded_by_newline {
313 if opts.checksums {
314 write!(w, "*{}", w.checksum())?;
315 w.reset_checksum();
316 }
317 writeln!(w)?;
318 }
319
320 w.stop_meatpack()?;
321
322 if opts.delimit_with_percent {
323 write!(w, "%")?;
324 }
325 Ok(())
326}
327
328fn write_packed_characters<W>(
330 [first, second]: [u8; 2],
331 no_spaces: bool,
332 dest: &mut W,
333) -> std::io::Result<()>
334where
335 W: IoWrite,
336{
337 match (pack_char(first, no_spaces), pack_char(second, no_spaces)) {
338 (None, None) => {
339 dest.write_all(&MP_BOTH_UNPACKABLE_HEADER)?;
340 dest.write_all(&[first, second])
341 }
342 (None, Some(second)) => dest.write_all(&[(second << 4) | MP_SINGLE_UNPACKABLE_MASK, first]),
343 (Some(first), None) => dest.write_all(&[first | (MP_SINGLE_UNPACKABLE_MASK << 4), second]),
344 (Some(first), Some(second)) => dest.write_all(&[first | (second << 4)]),
345 }
346}
347
348const fn pack_char(c: u8, no_spaces: bool) -> Option<u8> {
349 Some(match c {
350 b'0' => 0,
351 b'1' => 1,
352 b'2' => 2,
353 b'3' => 3,
354 b'4' => 4,
355 b'5' => 5,
356 b'6' => 6,
357 b'7' => 7,
358 b'8' => 8,
359 b'9' => 9,
360 b'.' => 10,
361 b' ' if !no_spaces => 11,
362 b'E' if no_spaces => 11,
363 b'\n' => 12,
364 b'G' => 13,
365 b'X' => 14,
366 _other => return None,
367 })
368}
369
370const fn packable_to_uppercase(c: u8, no_spaces: bool) -> u8 {
371 match c {
372 b'e' if !no_spaces => b'E',
373 b'g' => b'G',
374 b'x' => b'X',
375 other => other,
376 }
377}