1use rust_decimal::prelude::ToPrimitive;
2
3use std::borrow::Borrow;
4use std::fmt::{self, Write as FmtWrite};
5use std::io::Write as IoWrite;
6
7use super::token::Flag;
8use super::{Field, Token, Value};
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13struct XorAndPipe<W> {
14 acc: u8,
15 downstream: W,
16}
17
18impl<W> IoWrite for XorAndPipe<W>
19where
20 W: IoWrite,
21{
22 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
23 self.acc = buf.iter().fold(self.acc, |acc, b| acc ^ b);
24 self.downstream.write(buf)
25 }
26
27 fn flush(&mut self) -> std::io::Result<()> {
28 self.downstream.flush()
29 }
30}
31
32impl<W> FmtWrite for XorAndPipe<W>
33where
34 W: FmtWrite,
35{
36 fn write_str(&mut self, s: &str) -> fmt::Result {
37 self.acc = s.bytes().fold(self.acc, |acc, b| acc ^ b);
38 self.downstream.write_str(s)
39 }
40}
41
42impl<W> XorAndPipe<W> {
43 pub fn new(downstream: W) -> Self {
44 Self { acc: 0, downstream }
45 }
46
47 pub fn reset(&mut self) {
48 self.acc = 0;
49 }
50
51 pub fn checksum(&self) -> u8 {
52 self.acc
53 }
54}
55
56#[derive(Debug, Clone, Default)]
57#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
58pub struct FormatOptions {
59 pub checksums: bool,
61 pub line_numbers: bool,
63 pub delimit_with_percent: bool,
65 #[cfg_attr(feature = "serde", serde(default))]
70 pub newline_before_comment: bool,
71}
72
73macro_rules! formatter_core {
74 ($program: expr, $opts: ident, $downstream: ident) => {
75 use Token::*;
76 let mut preceded_by_newline = true;
77 let mut line_number = 0usize;
78
79 let mut w = XorAndPipe::new($downstream);
80 if $opts.delimit_with_percent {
81 writeln!(w, "%")?;
82 w.reset();
83 }
84
85 for token in $program {
86 let token = token.borrow();
87 if let Token::Field(ref f) = token {
88 if preceded_by_newline && f.letters == "N" {
90 continue;
91 }
92 }
93
94 if $opts.line_numbers && preceded_by_newline {
95 write!(w, "N{line_number} ")?;
96 }
97
98 match token {
99 Field(f) => {
100 if !preceded_by_newline {
101 if matches!(f.letters.as_ref(), "G" | "g" | "M" | "m" | "D" | "d") {
102 if $opts.checksums {
103 write!(w, "*{}", w.checksum())?;
104 }
105 line_number += 1;
106 writeln!(w)?;
107 w.reset();
108 if $opts.line_numbers {
109 write!(w, "N{line_number} ")?;
110 }
111 } else {
112 write!(w, " ")?;
113 }
114 }
115 write!(w, "{f}")?;
116 preceded_by_newline = false;
117 }
118 Flag(f) => {
119 if !preceded_by_newline {
120 write!(w, " ")?;
121 }
122 write!(w, "{f}")?;
123 }
124 Comment {
125 is_inline: true,
126 inner,
127 } => {
128 write!(w, "({inner})")?;
129 preceded_by_newline = false;
130 }
131 Comment {
132 is_inline: false,
133 inner,
134 } => {
135 if $opts.checksums {
136 write!(w, "*{}", w.checksum())?;
137 }
138 if !preceded_by_newline && $opts.newline_before_comment {
139 line_number += 1;
140 writeln!(w)?;
141 w.reset();
142 if $opts.line_numbers {
143 write!(w, "N{line_number} ")?;
144 }
145 if $opts.checksums {
146 write!(w, "*{}", w.checksum())?;
147 }
148 }
149 line_number += 1;
150 writeln!(w, ";{inner}")?;
151 w.reset();
152 preceded_by_newline = true;
153 }
154 }
155 }
156 if !preceded_by_newline {
158 if $opts.checksums {
159 write!(w, "*{}", w.checksum())?;
160 w.reset();
161 }
162 writeln!(w)?;
163 }
164 if $opts.delimit_with_percent {
165 write!(w, "%")?;
166 }
167 };
168}
169
170pub fn format_gcode_io<'a: 'b, 'b, W, I, T>(
172 program: I,
173 opts: FormatOptions,
174 w: W,
175) -> std::io::Result<()>
176where
177 W: IoWrite,
178 I: IntoIterator<Item = T>,
179 T: Borrow<Token<'a>> + 'b,
180{
181 formatter_core!(program.into_iter(), opts, w);
182 Ok(())
183}
184
185pub fn format_gcode_fmt<'a: 'b, 'b, W, I, T>(program: I, opts: FormatOptions, w: W) -> fmt::Result
187where
188 W: FmtWrite,
189 I: IntoIterator<Item = T>,
190 T: Borrow<Token<'a>> + 'b,
191{
192 formatter_core!(program.into_iter(), opts, w);
193 Ok(())
194}
195
196impl fmt::Display for Token<'_> {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 use Token::*;
199 match self {
200 Field(field) => write!(f, "{field}"),
201 Flag(flag) => write!(f, "{flag}"),
202 Comment { is_inline, inner } => match is_inline {
203 true => write!(f, "({inner})"),
204 false => write!(f, ";{inner}"),
205 },
206 }
207 }
208}
209
210impl<'a> fmt::Display for Field<'a> {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 write!(f, "{}{}", self.letters, self.value)
213 }
214}
215
216impl<'a> fmt::Display for Flag<'a> {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 f.write_str(&self.letter)
219 }
220}
221
222impl fmt::Display for Value<'_> {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
224 match self {
225 Self::Rational(r) => {
226 if r.fract().is_zero() {
230 if let Some(unsigned_rep) = r.to_u128() {
231 return write!(f, "{unsigned_rep}.");
232 } else if let Some(signed_rep) = r.to_i128() {
233 return write!(f, "{signed_rep}.");
234 }
235 }
236 write!(f, "{r}")
237 }
238 Self::Float(float) => write!(f, "{float}"),
239 Self::Integer(i) => write!(f, "{i}"),
240 Self::String(s) => write!(f, "\"{s}\""),
241 }
242 }
243}