1#[cfg(all(not(feature = "std"), feature = "parse-comments"))]
16use alloc::string::String;
17#[cfg(all(not(feature = "std"), any(feature = "parse-comments")))]
18use alloc::vec::Vec;
19
20mod values;
21
22#[cfg(feature = "parse-expressions")]
23mod expressions;
24
25#[cfg(test)]
26mod test;
27
28use futures::{Stream, StreamExt};
29
30use crate::{
31 stream::{MyTryStreamExt, PushBackable},
32 types::{Comment, ParseResult},
33 utils::skip_whitespaces,
34 Error, GCode,
35};
36
37use values::parse_number;
38
39#[cfg(not(feature = "parse-expressions"))]
40use values::parse_real_value;
41
42#[cfg(feature = "parse-expressions")]
43use expressions::parse_real_value;
44
45#[derive(PartialEq, Debug, Clone, Copy)]
46enum AsyncParserState {
47 Start(bool),
48 LineNumberOrSegment,
49 Segment,
50 ErrorRecovery,
51 #[cfg(all(feature = "parse-trailing-comment", feature = "parse-checksum"))]
52 EoLOrTrailingComment,
53 #[cfg(any(feature = "parse-trailing-comment", feature = "parse-checksum"))]
54 EndOfLine,
55}
56
57#[cfg(all(feature = "parse-trailing-comment", not(feature = "parse-comments")))]
58async fn parse_eol_comment<S, E>(input: &mut S) -> Option<Result<Comment, E>>
59where
60 S: Stream<Item = Result<u8, E>> + Unpin + PushBackable<Item = u8>,
61{
62 loop {
63 let b = match input.next().await? {
64 Ok(o) => o,
65 Err(e) => return Some(Err(e)),
66 };
67 match b {
68 b'\r' | b'\n' => {
69 input.push_back(b);
70 break Some(Ok(()));
71 }
72 _ => {}
73 }
74 }
75}
76
77#[cfg(all(feature = "parse-trailing-comment", feature = "parse-comments"))]
78async fn parse_eol_comment<S, E>(input: &mut S) -> Option<ParseResult<Comment, E>>
79where
80 S: Stream<Item = Result<u8, E>> + Unpin + PushBackable<Item = u8>,
81{
82 let mut v = Vec::new();
83 loop {
84 let b = try_result!(input.next());
85 match b {
86 b'\r' | b'\n' => {
87 input.push_back(b);
88 break Some(match String::from_utf8(v) {
89 Ok(s) => ParseResult::Ok(s),
90 Err(_) => Error::InvalidUTF8String.into(),
91 });
92 }
93 b => v.push(b),
94 }
95 }
96}
97
98#[cfg(not(feature = "parse-comments"))]
99async fn parse_inline_comment<S, E>(input: &mut S) -> Option<ParseResult<Comment, E>>
100where
101 S: Stream<Item = Result<u8, E>> + Unpin + PushBackable<Item = u8>,
102{
103 loop {
104 match try_result!(input.next()) {
105 b'\\' => {
106 try_result!(input.next());
107 }
108 b'(' => break Some(Error::UnexpectedByte(b'(').into()),
109 b')' => break Some(ParseResult::Ok(())),
110 _ => {}
111 }
112 }
113}
114
115#[cfg(feature = "parse-comments")]
116async fn parse_inline_comment<S, E>(input: &mut S) -> Option<ParseResult<Comment, E>>
117where
118 S: Stream<Item = Result<u8, E>> + Unpin + PushBackable<Item = u8>,
119{
120 let mut v = Vec::new();
121 loop {
122 let b = try_result!(input.next());
123 match b {
124 b'\\' => {
125 v.push(try_result!(input.next()));
126 }
127 b'(' => break Some(Error::UnexpectedByte(b'(').into()),
128 b')' => {
129 break Some(match String::from_utf8(v) {
130 Ok(s) => ParseResult::Ok(s),
131 Err(_) => Error::InvalidUTF8String.into(),
132 })
133 }
134 b => v.push(b),
135 }
136 }
137}
138
139#[cfg(not(feature = "parse-checksum"))]
141use crate::stream::pushback::PushBack;
142#[cfg(feature = "parse-checksum")]
143type PushBack<T> = crate::stream::xorsum_pushback::XorSumPushBack<T>;
144
145async fn parse_eol<S, E>(
146 state: &mut AsyncParserState,
147 input: &mut PushBack<S>,
148) -> Option<ParseResult<GCode, E>>
149where
150 S: Stream<Item = Result<u8, E>> + Unpin,
151{
152 Some(loop {
153 let b = try_result!(input.next());
154 match b {
155 b'\r' | b'\n' => {
156 *state = AsyncParserState::Start(true);
157 #[cfg(feature = "parse-checksum")]
158 {
159 input.reset_sum(0);
160 }
161 break ParseResult::Ok(GCode::Execute);
162 }
163 b' ' => {}
164 b => break Error::UnexpectedByte(b).into(),
165 }
166 })
167}
168
169macro_rules! try_await {
170 ($input:expr) => {
171 match $input.await? {
172 ParseResult::Ok(ok) => ok,
173 ParseResult::Parsing(err) => break Err(err.into()),
174 ParseResult::Input(err) => break Err(err.into()),
175 }
176 };
177}
178
179macro_rules! try_await_result {
180 ($input:expr) => {
181 match $input.await? {
182 Ok(ok) => ok,
183 Err(err) => break Err(err.into()),
184 }
185 };
186}
187
188pub struct Parser<S, E>
189where
190 S: Stream<Item = Result<u8, E>> + Unpin,
191{
192 input: PushBack<S>,
193 state: AsyncParserState,
194}
195
196impl<S, E> Parser<S, E>
197where
198 S: Stream<Item = Result<u8, E>> + Unpin,
199 E: From<Error>,
200{
201 pub fn new(input: S) -> Self {
202 Self {
203 #[cfg(feature = "parse-checksum")]
204 input: input.xor_summed_push_backable(0),
205 #[cfg(not(feature = "parse-checksum"))]
206 input: input.push_backable(),
207 state: AsyncParserState::Start(true),
208 }
209 }
210 pub async fn next(&mut self) -> Option<Result<GCode, E>> {
211 let res = loop {
212 let b = match self.input.next().await? {
213 Ok(b) => b,
214 Err(err) => return Some(Err(err)),
215 };
216
217 match self.state {
219 AsyncParserState::Start(ref mut first_byte) => match b {
220 b'\n' => {
221 self.input.push_back(b);
222 break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
223 }
224 b'/' if *first_byte => {
225 self.state = AsyncParserState::LineNumberOrSegment;
226 break Ok(GCode::BlockDelete);
227 }
228 b' ' => {
229 *first_byte = false;
230 }
231 _ => {
232 self.input.push_back(b);
233 self.state = AsyncParserState::LineNumberOrSegment
234 }
235 },
236 AsyncParserState::LineNumberOrSegment => match b.to_ascii_lowercase() {
237 b'n' => {
238 try_await_result!(skip_whitespaces(&mut self.input));
239 let (n, ord) = try_await_result!(parse_number(&mut self.input));
240 break if ord == 1 {
241 let b = try_await_result!(self.input.next());
242 Err(Error::UnexpectedByte(b).into())
243 } else if ord > 10000 {
244 Err(Error::NumberOverflow.into())
245 } else {
246 self.state = AsyncParserState::Segment;
247 Ok(GCode::LineNumber(n))
248 };
249 }
250 _ => {
251 self.input.push_back(b);
252 self.state = AsyncParserState::Segment;
253 }
254 },
255 AsyncParserState::Segment => match b.to_ascii_lowercase() {
256 b' ' => {}
257 letter @ b'a'..=b'z' => {
258 try_await_result!(skip_whitespaces(&mut self.input));
259 let rv = try_await!(parse_real_value(&mut self.input));
260 break Ok(GCode::Word(letter.into(), rv));
262 }
263 b'\r' | b'\n' => {
264 self.input.push_back(b);
265 break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
266 }
267 #[cfg(feature = "parse-parameters")]
269 b'#' => {
270 try_await_result!(skip_whitespaces(&mut self.input));
271 #[allow(clippy::match_single_binding)]
272 let param_id = match try_await!(parse_real_value(&mut self.input)) {
273 #[cfg(feature = "optional-value")]
274 crate::RealValue::None => {
275 let b = try_await_result!(self.input.next());
276 break Err(Error::UnexpectedByte(b).into());
277 }
278 id => id,
279 };
280 try_await_result!(skip_whitespaces(&mut self.input));
282 let b = try_await_result!(self.input.next());
283 if b'=' != b {
284 break Err(Error::UnexpectedByte(b).into());
285 }
286
287 try_await_result!(skip_whitespaces(&mut self.input));
288 let value = try_await!(parse_real_value(&mut self.input));
289 break Ok(GCode::ParameterSet(param_id, value));
292 }
293 #[cfg(feature = "parse-checksum")]
295 b'*' => {
296 let sum = self.input.sum() ^ b'*';
297 try_await_result!(skip_whitespaces(&mut self.input));
298 let (n, _) = try_await_result!(parse_number(&mut self.input));
299 if n >= 256 {
301 break Err(Error::NumberOverflow.into());
302 } else if (n as u8) != sum {
303 break Err(Error::BadChecksum(sum).into());
304 } else {
305 try_await_result!(skip_whitespaces(&mut self.input));
306 #[cfg(not(feature = "parse-trailing-comment"))]
307 {
308 self.state = AsyncParserState::EndOfLine;
309 }
310 #[cfg(feature = "parse-trailing-comment")]
311 {
312 self.state = AsyncParserState::EoLOrTrailingComment;
313 }
314 }
315 }
316 #[cfg(not(feature = "parse-comments"))]
318 b'(' => {
319 try_await!(parse_inline_comment(&mut self.input));
320 }
321 #[cfg(feature = "parse-comments")]
322 b'(' => {
323 break Ok(GCode::Comment(try_await!(parse_inline_comment(
324 &mut self.input
325 ))));
326 }
327 #[cfg(all(
328 feature = "parse-trailing-comment",
329 not(feature = "parse-comments")
330 ))]
331 b';' => {
332 try_await_result!(parse_eol_comment(&mut self.input));
333 self.state = AsyncParserState::EndOfLine;
334 }
335 #[cfg(all(feature = "parse-trailing-comment", feature = "parse-comments"))]
336 b';' => {
337 let s = try_await!(parse_eol_comment(&mut self.input));
338 self.state = AsyncParserState::EndOfLine;
339 break Ok(GCode::Comment(s));
340 }
341 _ => break Err(Error::UnexpectedByte(b).into()),
342 },
343 #[cfg(all(
344 feature = "parse-trailing-comment",
345 not(feature = "parse-comments"),
346 feature = "parse-checksum"
347 ))]
348 AsyncParserState::EoLOrTrailingComment => match b {
349 b';' => try_await_result!(parse_eol_comment(&mut self.input)),
350 _ => {
351 self.input.push_back(b);
352 break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
353 }
354 },
355 #[cfg(all(
356 feature = "parse-trailing-comment",
357 feature = "parse-comments",
358 feature = "parse-checksum"
359 ))]
360 AsyncParserState::EoLOrTrailingComment => match b {
361 b';' => {
362 let s = try_await!(parse_eol_comment(&mut self.input));
363 self.state = AsyncParserState::EndOfLine;
364 break Ok(GCode::Comment(s));
365 }
366 _ => {
367 self.input.push_back(b);
368 break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
369 }
370 },
371 #[cfg(any(feature = "parse-trailing-comment", feature = "parse-checksum"))]
372 AsyncParserState::EndOfLine => {
373 self.input.push_back(b);
374 break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
375 }
376 AsyncParserState::ErrorRecovery => match b {
377 b'\r' | b'\n' => {
378 self.input.push_back(b);
379 break Ok(try_await!(parse_eol(&mut self.state, &mut self.input)));
380 }
381 _ => {}
382 },
383 }
384 };
385 if res.is_err() {
387 self.state = AsyncParserState::ErrorRecovery;
388 }
389 Some(res)
390 }
391}