1use crate::{
6 emit::{token::Flag, Field, FormatOptions, Token, Value},
7 parse::compact::meatpack::*,
8};
9
10use std::{borrow::Borrow, fmt::Arguments, io::Write as IoWrite};
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, include_pending_in_checksum)) = self.pending {
101 if include_pending_in_checksum {
102 if self.enabled {
103 checksum ^= packable_to_uppercase(pending_byte, self.meatpack_opts.no_spaces);
104 } else {
105 checksum ^= pending_byte;
106 }
107 }
108 }
109 checksum
110 }
111
112 fn reset_checksum(&mut self) {
113 self.checksum_acc = 0;
114 if let Some((_, ref mut include_pending_in_checksum)) = self.pending {
115 *include_pending_in_checksum = false;
116 }
117 }
118
119 fn write_fmt(&mut self, arguments: Arguments<'_>) -> std::io::Result<()> {
120 let input = arguments.to_string();
121 if !self.enabled {
122 self.checksum_acc = input.bytes().fold(self.checksum_acc, |acc, b| acc ^ b);
123 self.downstream.write_all(input.as_bytes())?;
124 } else if !input.is_empty() {
125 assert!(input.is_ascii(), "Meatpack can only encode ASCII");
126 if self.meatpack_opts.no_spaces {
127 for substr in input.split(' ') {
128 self.write_slice(substr.as_bytes())?;
129 }
130 } else {
131 self.write_slice(input.as_bytes())?;
132 }
133 }
134
135 Ok(())
136 }
137
138 fn write_slice(&mut self, mut slice: &[u8]) -> std::io::Result<()> {
139 if slice.is_empty() {
140 return Ok(());
141 }
142 let mut pending_slice_opt = None;
143 let mut _pending_array_opt = None;
144 if let Some((pending, include_pending_in_checksum)) = self.pending.take() {
145 _pending_array_opt = Some([pending, slice[0]]);
146 pending_slice_opt = _pending_array_opt
147 .as_ref()
148 .map(|array| (array.as_slice(), include_pending_in_checksum));
149 slice = &slice[1..];
150 }
151
152 for (chunk, include_first_in_checksum) in pending_slice_opt
153 .into_iter()
154 .chain(slice.chunks(2).map(|c| (c, true)))
155 {
156 match chunk {
157 [first, second] => {
158 let first = packable_to_uppercase(*first, self.meatpack_opts.no_spaces);
159 let second = packable_to_uppercase(*second, self.meatpack_opts.no_spaces);
160 if include_first_in_checksum {
161 self.checksum_acc ^= first;
162 }
163 self.checksum_acc ^= second;
164 write_packed_characters(
165 [first, second],
166 self.meatpack_opts.no_spaces,
167 &mut self.downstream,
168 )?;
169 }
170 [odd] => self.pending = Some((*odd, true)),
171 _ => unreachable!(),
172 }
173 }
174 Ok(())
175 }
176}
177
178pub fn format_gcode_meatpack<'a: 'b, 'b, W, I, T>(
180 program: I,
181 opts: FormatOptions,
182 meatpack_opts: MeatpackOptions,
183 w: W,
184) -> std::io::Result<()>
185where
186 W: IoWrite,
187 I: IntoIterator<Item = T>,
188 T: Borrow<Token<'a>> + 'b,
189{
190 let mut preceded_by_newline = true;
191 let mut line_number = 0usize;
192
193 let mut w = MeatpackEncodingWriter::new(w, meatpack_opts);
194 w.start_meatpack()?;
195
196 if opts.delimit_with_percent {
197 writeln!(w, "%")?;
198 w.reset_checksum();
199 }
200
201 for token in program {
202 let token = token.borrow();
203 if let Token::Field(ref f) = token {
204 if preceded_by_newline && f.letters == "N" {
206 continue;
207 }
208 }
209
210 let disable_meatpack = match token {
213 Token::Field(Field { letters, value }) => {
214 !letters.is_ascii()
215 || match value {
216 Value::String(_) => true,
217 Value::Rational(_) | Value::Float(_) | Value::Integer(_) => false,
218 }
219 }
220 Token::Flag(Flag { letter }) => !letter.is_ascii(),
221 Token::Comment { .. } => true,
222 };
223
224 if disable_meatpack {
225 let will_write_newline = w.will_write_newline();
226 if will_write_newline {
227 if opts.line_numbers && preceded_by_newline {
228 write!(w, "N{line_number} ")?;
229 }
230
231 if opts.checksums {
232 write!(w, "*{}", w.checksum())?;
233 }
234 line_number += 1;
235 }
236 let still_going_to_write_newline = w.will_write_newline();
237 w.disable_packing()?;
238 if will_write_newline {
239 if !still_going_to_write_newline {
241 writeln!(w)?;
242 }
243 w.reset_checksum();
244 }
245 preceded_by_newline = will_write_newline;
246 } else {
247 w.enable_packing()?;
248 }
249
250 if opts.line_numbers && preceded_by_newline {
251 write!(w, "N{line_number} ")?;
252 }
253
254 match token {
255 Token::Field(f) => {
256 if !preceded_by_newline {
257 if matches!(f.letters.as_ref(), "G" | "g" | "M" | "m" | "D" | "d") {
258 if opts.checksums {
259 write!(w, "*{}", w.checksum())?;
260 }
261 line_number += 1;
262 writeln!(w)?;
263 w.reset_checksum();
264 if opts.line_numbers {
265 write!(w, "N{line_number} ")?;
266 }
267 } else {
268 write!(w, " ")?;
269 }
270 }
271
272 write!(w, "{f}")?;
273 preceded_by_newline = false;
274 }
275 Token::Flag(f) => {
276 if !preceded_by_newline {
277 write!(w, " ")?;
278 }
279 write!(w, "{f}")?;
280 }
281 Token::Comment {
282 is_inline: true,
283 inner,
284 } => {
285 write!(w, "({inner})")?;
286 preceded_by_newline = false;
287 }
288 Token::Comment {
289 is_inline: false,
290 inner,
291 } => {
292 if opts.checksums {
293 write!(w, "*{}", w.checksum())?;
294 }
295 if !preceded_by_newline && opts.newline_before_comment {
296 line_number += 1;
297 writeln!(w)?;
298 w.reset_checksum();
299 if opts.line_numbers {
300 write!(w, "N{line_number} ")?;
301 }
302 if opts.checksums {
303 write!(w, "*{}", w.checksum())?;
304 }
305 }
306 line_number += 1;
307 writeln!(w, ";{inner}")?;
308 w.reset_checksum();
309 preceded_by_newline = true;
310 }
311 }
312 }
313 if !preceded_by_newline {
315 if opts.checksums {
316 write!(w, "*{}", w.checksum())?;
317 w.reset_checksum();
318 }
319 writeln!(w)?;
320 }
321
322 w.stop_meatpack()?;
323
324 if opts.delimit_with_percent {
325 write!(w, "%")?;
326 }
327 Ok(())
328}
329
330fn write_packed_characters<W>(
332 [first, second]: [u8; 2],
333 no_spaces: bool,
334 dest: &mut W,
335) -> std::io::Result<()>
336where
337 W: IoWrite,
338{
339 match (pack_char(first, no_spaces), pack_char(second, no_spaces)) {
340 (None, None) => {
341 dest.write_all(&MP_BOTH_UNPACKABLE_HEADER)?;
342 dest.write_all(&[first, second])
343 }
344 (None, Some(second)) => dest.write_all(&[(second << 4) | MP_SINGLE_UNPACKABLE_MASK, first]),
345 (Some(first), None) => dest.write_all(&[first | (MP_SINGLE_UNPACKABLE_MASK << 4), second]),
346 (Some(first), Some(second)) => dest.write_all(&[first | (second << 4)]),
347 }
348}
349
350const fn pack_char(c: u8, no_spaces: bool) -> Option<u8> {
351 Some(match c {
352 b'0' => 0,
353 b'1' => 1,
354 b'2' => 2,
355 b'3' => 3,
356 b'4' => 4,
357 b'5' => 5,
358 b'6' => 6,
359 b'7' => 7,
360 b'8' => 8,
361 b'9' => 9,
362 b'.' => 10,
363 b' ' if !no_spaces => 11,
364 b'E' if no_spaces => 11,
365 b'\n' => 12,
366 b'G' => 13,
367 b'X' => 14,
368 _other => return None,
369 })
370}
371
372const fn packable_to_uppercase(c: u8, no_spaces: bool) -> u8 {
373 match c {
374 b'e' if !no_spaces => b'E',
375 b'g' => b'G',
376 b'x' => b'X',
377 other => other,
378 }
379}