• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cbruiz / printhor / 13852050169

14 Mar 2025 07:41AM UTC coverage: 90.892% (+0.2%) from 90.713%
13852050169

Pull #37

github

web-flow
Merge 62a9e2a91 into 44ad727f7
Pull Request #37: Motion review

779 of 821 new or added lines in 19 files covered. (94.88%)

10 existing lines in 3 files now uncovered.

14639 of 16106 relevant lines covered (90.89%)

750530.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.74
/printhor/src/bin/processing/gcode_parser.rs
1
use crate::helpers;
2
use crate::hwa;
3
#[allow(unused)]
4
use crate::processing::EFSXYZ;
5
#[allow(unused)]
6
use crate::processing::EXYZ;
7
#[allow(unused)]
8
use crate::processing::FXYZ;
9
#[allow(unused)]
10
use crate::processing::GCodeCmd;
11
#[allow(unused)]
12
use crate::processing::GCodeValue;
13
#[allow(unused)]
14
use crate::processing::N;
15
#[allow(unused)]
16
use crate::processing::S;
17
use hwa::CommChannel;
18

19
// dyn trait could reduce code size a lot with the penalty of the indirection
20
pub struct GCodeLineParser<STREAM>
21
where
22
    STREAM: async_gcode::ByteStream<Item = Result<u8, async_gcode::Error>>,
23
{
24
    /// The parser
25
    raw_parser: async_gcode::Parser<STREAM, async_gcode::Error>,
26
    /// The current line in the parser state
27
    current_line: u32,
28
    /// The tagged line (NXX) from gcode commands, when provided
29
    tagged_line_num: Option<u32>,
30
}
31

32
impl<STREAM> GCodeLineParser<STREAM>
33
where
34
    STREAM: async_gcode::ByteStream<Item = Result<u8, async_gcode::Error>>,
35
{
36
    pub fn new(stream: STREAM) -> Self {
251✔
37
        Self {
251✔
38
            raw_parser: async_gcode::Parser::new(stream),
251✔
39
            current_line: 0,
251✔
40
            tagged_line_num: None,
251✔
41
        }
251✔
42
    }
251✔
43

44
    pub fn gcode_line(&self) -> Option<u32> {
150✔
45
        self.tagged_line_num
150✔
46
    }
150✔
47

48
    pub async fn next_gcode(
452,764✔
49
        &mut self,
452,764✔
50
        _channel: CommChannel,
452,764✔
51
    ) -> Result<GCodeCmd, GCodeLineParserError> {
452,764✔
52
        // The GcodeCmd being constructed.
452,764✔
53
        // * Initially set to none
452,764✔
54
        // * Reset back to None when built, updated and returned
452,764✔
55
        let mut current_gcode: Option<GCodeCmd> = None;
452,764✔
56
        // Same as previous but unparsed
452,764✔
57
        let mut raw_gcode_spec: Option<RawGCodeSpec> = None;
452,764✔
58

452,764✔
59
        let mut tagged_line_num = None;
452,764✔
60
        let mut skip_gcode = false;
452,764✔
61

62
        loop {
63
            self.current_line = self.raw_parser.get_current_line();
2,281,889✔
64
            match self.raw_parser.next().await {
2,281,889✔
65
                None => {
66
                    hwa::trace!("EOF reading from stream");
45✔
67
                    self.tagged_line_num = tagged_line_num;
45✔
68
                    match current_gcode.take() {
45✔
69
                        None => {
70
                            match raw_gcode_spec.take() {
21✔
71
                                None => {
72
                                    return Err(GCodeLineParserError::EOF);
21✔
73
                                } // Empty line. Just ignore
74
                                Some(rgs) => {
×
75
                                    return Err(GCodeLineParserError::GCodeNotImplemented(
×
76
                                        self.raw_parser.get_current_line(),
×
77
                                        alloc::format!("{:?}", rgs),
×
78
                                    ));
×
79
                                }
80
                            }
81
                        }
82
                        Some(mut cgv) => {
24✔
83
                            cgv.order_num = self.raw_parser.get_current_line();
24✔
84
                            return Ok(cgv);
24✔
85
                        }
86
                    }
87
                }
88
                Some(parser_gcode) => {
2,281,808✔
89
                    match parser_gcode {
2,281,808✔
90
                        Ok(g) => {
2,281,787✔
91
                            #[cfg(feature = "debug-gcode")]
2,281,787✔
92
                            hwa::info!(
2,281,787✔
93
                                "[debug-gcode] channel: {:?} gcode: {:?}",
2,281,787✔
94
                                _channel,
2,281,787✔
95
                                FormatableGCode(&g)
2,281,787✔
96
                            );
2,281,787✔
97
                            match g {
2,281,787✔
98
                                // FIXME Undo this hacky temporary solution in async-gcode.
99
                                // A sub-protocol approach is preferred
100
                                #[cfg(feature = "grbl-compat")]
101
                                async_gcode::GCode::StatusCommand => {
102
                                    return Ok(GCodeCmd::new(
103
                                        self.raw_parser.get_current_line(),
104
                                        tagged_line_num,
105
                                        GCodeValue::Status,
106
                                    ));
107
                                }
108
                                async_gcode::GCode::BlockDelete => {
6✔
109
                                    skip_gcode = true;
6✔
110
                                    //crate::debug!("BlockDelete");
6✔
111
                                }
6✔
112
                                async_gcode::GCode::LineNumber(n) => {
18✔
113
                                    tagged_line_num = n;
18✔
114
                                }
18✔
115
                                async_gcode::GCode::Word(ch, fv) => {
1,739,461✔
116
                                    if skip_gcode {
1,739,461✔
117
                                        continue;
333✔
118
                                    }
1,739,128✔
119
                                    let frx = match &fv {
1,739,128✔
120
                                        async_gcode::RealValue::Literal(
121
                                            async_gcode::Literal::RealNumber(f),
1,739,098✔
122
                                        ) => Some((f.integer_part(), f.scale())),
1,739,098✔
123
                                        _ => None,
30✔
124
                                    };
125
                                    if let Some(current_gcode) = &mut current_gcode {
1,739,128✔
126
                                        // We already are building a GCodeCmd, so we update its fields
127
                                        update_current(current_gcode, ch, frx, fv)
1,286,433✔
128
                                    } else {
129
                                        raw_gcode_spec.replace(RawGCodeSpec::from(ch, frx));
452,695✔
130

131
                                        let has_text_argument = match (ch, frx) {
452,695✔
132
                                            ('m', Some((23, 0)))
133
                                            | ('m', Some((117, 0)))
134
                                            | ('m', Some((118, 0))) => true,
30✔
135
                                            _ => false,
452,665✔
136
                                        };
137
                                        // We need to start building the current GCodeCmd that is being parsed
138
                                        match init_current(ch, frx) {
452,695✔
139
                                            None => {
351✔
140
                                                // Unable to init: The GCodeValue is unexpected
351✔
141
                                                // Hence, we will skip every [async_gcode::GCode::Word] until [async_gcode::GCode::Execute]
351✔
142
                                                skip_gcode = true;
351✔
143
                                            }
351✔
144
                                            Some(gcode_value) => {
452,344✔
145
                                                current_gcode.replace(GCodeCmd::new(
452,344✔
146
                                                    self.current_line,
452,344✔
147
                                                    tagged_line_num,
452,344✔
148
                                                    gcode_value,
452,344✔
149
                                                ));
452,344✔
150
                                                if has_text_argument {
452,344✔
151
                                                    self.raw_parser.switch_to_text();
30✔
152
                                                }
452,314✔
153
                                            }
154
                                        }
155
                                    }
156
                                }
157
                                async_gcode::GCode::Text(value) => {
30✔
158
                                    if let Some(current_gcode) = &mut current_gcode {
30✔
159
                                        // We already are building a GCodeCmd, so we update its fields
160
                                        update_current(current_gcode, 'T', None, value)
30✔
161
                                    }
×
162
                                }
163
                                async_gcode::GCode::Execute => {
164
                                    // Reset skip_gcode status
165
                                    skip_gcode = false;
542,272✔
166
                                    self.tagged_line_num = tagged_line_num;
542,272✔
167
                                    match current_gcode.take() {
542,272✔
168
                                        None => {
169
                                            match raw_gcode_spec.take() {
89,961✔
170
                                                None => {
171
                                                    #[cfg(feature = "trace-commands")]
172
                                                    hwa::warn!("Ignoring empty line");
173
                                                    continue;
89,610✔
174
                                                } // Empty line. Just ignore
175
                                                Some(rgs) => {
351✔
176
                                                    return Err(
351✔
177
                                                        GCodeLineParserError::GCodeNotImplemented(
351✔
178
                                                            self.current_line,
351✔
179
                                                            alloc::format!("{:?}", rgs),
351✔
180
                                                        ),
351✔
181
                                                    );
351✔
182
                                                }
183
                                            }
184
                                        }
185
                                        Some(mut cgv) => {
452,311✔
186
                                            cgv.order_num = self.current_line;
452,311✔
187
                                            return Ok(cgv);
452,311✔
188
                                        }
189
                                    }
190
                                }
191
                                _ => {
192
                                    return Err(GCodeLineParserError::GCodeNotImplemented(
×
NEW
193
                                        self.current_line,
×
NEW
194
                                        alloc::string::String::from("N/A"),
×
UNCOV
195
                                    ));
×
196
                                }
197
                            }
198
                        }
199
                        Err(error) => {
21✔
200
                            match error {
21✔
201
                                async_gcode::Error::UnexpectedByte(_b) => {
15✔
202
                                    hwa::warn!("Unexpected byte: {} ({})", _b, char::from(_b));
15✔
203
                                }
204
                                async_gcode::Error::NumberOverflow => {
205
                                    hwa::warn!("Number overflow");
6✔
206
                                }
207
                                async_gcode::Error::BadNumberFormat => {
208
                                    hwa::warn!("Bad number format");
×
209
                                }
210
                                _e => {
×
211
                                    #[cfg(feature = "native")]
×
212
                                    hwa::error!("Parse error {:?}", _e);
×
213
                                    hwa::error!("Parse error");
×
214
                                }
215
                            }
216
                            return Err(GCodeLineParserError::ParseError(self.current_line));
21✔
217
                        }
218
                    }
219
                }
220
            }
221
        }
222
    }
452,728✔
223

224
    pub async fn reset(&mut self) {
30✔
225
        hwa::debug!("AsyncGcodeParser reset");
30✔
226
        self.raw_parser.reset().await;
30✔
227
    }
30✔
228

229
    pub fn reset_current_line(&mut self) {
12✔
230
        hwa::warn!("AsyncGcodeParser reset_current_line");
12✔
231
        self.raw_parser.update_current_line(0);
12✔
232
    }
12✔
233

234
    pub fn get_state(&self) -> async_gcode::AsyncParserState {
36✔
235
        self.raw_parser.get_state()
36✔
236
    }
36✔
237

238
    pub fn get_line(&self) -> u32 {
452,359✔
239
        self.raw_parser.get_current_line()
452,359✔
240
    }
452,359✔
241

242
    pub async fn close(&mut self) {
16✔
243
        self.raw_parser.reset().await;
16✔
244
    }
16✔
245
}
246

247
/// Initialize and EMPTY GCodeValue variant from ch, frx spec coming from parser
248
fn init_current(ch: char, frx: Option<(i32, u8)>) -> Option<GCodeValue> {
452,695✔
249
    match (ch, frx) {
452,695✔
250
        #[cfg(feature = "grbl-compat")]
251
        ('$', None) => Some(GCodeValue::GRBLCmd),
252
        ('g', None) => Some(GCodeValue::G),
6✔
253
        #[cfg(feature = "with-motion")]
254
        ('g', Some((0, 0))) => Some(GCodeValue::G0(FXYZ::new())),
58✔
255
        #[cfg(feature = "with-motion")]
256
        ('g', Some((1, 0))) => Some(GCodeValue::G1(EFSXYZ::new())),
440,005✔
257
        #[cfg(feature = "with-motion")]
258
        ('g', Some((4, 0))) => Some(GCodeValue::G4(S::new())),
25✔
259
        #[cfg(feature = "with-motion")]
260
        ('g', Some((10, 0))) => Some(GCodeValue::G10),
6✔
261
        #[cfg(feature = "with-motion")]
262
        ('g', Some((17, 0))) => Some(GCodeValue::G17),
6✔
263
        #[cfg(feature = "with-motion")]
264
        ('g', Some((21, 0))) => Some(GCodeValue::G21),
16✔
265
        #[cfg(feature = "with-motion")]
266
        ('g', Some((28, 0))) => Some(GCodeValue::G28(EXYZ::new())),
25✔
267
        #[cfg(feature = "with-motion")]
268
        ('g', Some((29, 0))) => Some(GCodeValue::G29),
12✔
269
        #[cfg(feature = "with-motion")]
270
        ('g', Some((291, 1))) => Some(GCodeValue::G29_1),
12✔
271
        #[cfg(feature = "with-motion")]
272
        ('g', Some((292, 1))) => Some(GCodeValue::G29_2),
12✔
273
        #[cfg(feature = "with-probe")]
274
        ('g', Some((31, 0))) => Some(GCodeValue::G31),
12✔
275
        #[cfg(feature = "with-probe")]
276
        ('g', Some((32, 0))) => Some(GCodeValue::G32),
6✔
277
        ('g', Some((80, 0))) => Some(GCodeValue::G80),
9✔
278
        #[cfg(feature = "with-motion")]
279
        ('g', Some((90, 0))) => Some(GCodeValue::G90),
19✔
280
        #[cfg(feature = "with-motion")]
281
        ('g', Some((91, 0))) => Some(GCodeValue::G91),
6✔
282
        #[cfg(feature = "with-motion")]
283
        ('g', Some((92, 0))) => Some(GCodeValue::G92(EXYZ::new())),
739✔
284
        #[cfg(feature = "with-motion")]
285
        ('g', Some((94, 0))) => Some(GCodeValue::G94),
6✔
286
        ('m', None) => Some(GCodeValue::M),
6✔
287
        #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
288
        ('m', Some((2, 0))) => Some(GCodeValue::M2),
13✔
289
        ('m', Some((3, 0))) => Some(GCodeValue::M3),
×
290
        ('m', Some((4, 0))) => Some(GCodeValue::M4),
4✔
291
        ('m', Some((5, 0))) => Some(GCodeValue::M5),
4✔
292
        #[cfg(feature = "with-sd-card")]
293
        ('m', Some((20, 0))) => Some(GCodeValue::M20(None)),
6✔
294
        #[cfg(feature = "with-sd-card")]
295
        ('m', Some((21, 0))) => Some(GCodeValue::M21),
×
296
        #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
297
        ('m', Some((23, 0))) => Some(GCodeValue::M23(None)),
18✔
298
        #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
299
        ('m', Some((24, 0))) => Some(GCodeValue::M24),
×
300
        #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
301
        ('m', Some((25, 0))) => Some(GCodeValue::M25),
×
302
        ('m', Some((37, 0))) => Some(GCodeValue::M37(S::new())),
12✔
303
        ('m', Some((73, 0))) => Some(GCodeValue::M73),
1,176✔
304
        ('m', Some((79, 0))) => Some(GCodeValue::M79),
×
305
        #[cfg(feature = "with-ps-on")]
306
        ('m', Some((80, 0))) => Some(GCodeValue::M80),
3✔
307
        #[cfg(feature = "with-ps-on")]
308
        ('m', Some((81, 0))) => Some(GCodeValue::M81),
3✔
309
        ('m', Some((83, 0))) => Some(GCodeValue::M83),
6✔
310
        #[cfg(feature = "with-motion")]
311
        ('m', Some((84, 0))) => Some(GCodeValue::M84),
3✔
312
        ('m', Some((100, 0))) => Some(GCodeValue::M100),
×
313
        #[cfg(feature = "with-hot-end")]
314
        ('m', Some((104, 0))) => Some(GCodeValue::M104(S::new())),
8✔
315
        ('m', Some((105, 0))) => Some(GCodeValue::M105),
×
316
        #[cfg(feature = "with-fan-layer")]
317
        ('m', Some((106, 0))) => Some(GCodeValue::M106),
640✔
318
        #[cfg(feature = "with-fan-layer")]
319
        ('m', Some((107, 0))) => Some(GCodeValue::M107),
10✔
320
        #[cfg(feature = "with-hot-end")]
321
        ('m', Some((109, 0))) => Some(GCodeValue::M109(S::new())),
4✔
322
        ('m', Some((110, 0))) => Some(GCodeValue::M110(N::new())),
12✔
323
        #[cfg(feature = "with-motion")]
324
        ('m', Some((114, 0))) => Some(GCodeValue::M114),
10✔
325
        ('m', Some((115, 0))) => Some(GCodeValue::M115),
3✔
326
        ('m', Some((117, 0))) => Some(GCodeValue::M117(None)),
6✔
327
        ('m', Some((118, 0))) => Some(GCodeValue::M118(None)),
6✔
328
        ('m', Some((119, 0))) => Some(GCodeValue::M119),
×
329
        #[cfg(feature = "with-hot-bed")]
330
        ('m', Some((140, 0))) => Some(GCodeValue::M140(S::new())),
6✔
331
        #[cfg(feature = "with-hot-bed")]
332
        ('m', Some((190, 0))) => Some(GCodeValue::M190),
2✔
333
        #[cfg(feature = "with-motion")]
334
        ('m', Some((201, 0))) => Some(GCodeValue::M201),
3✔
335
        #[cfg(feature = "with-motion")]
336
        ('m', Some((203, 0))) => Some(GCodeValue::M203),
3✔
337
        ('m', Some((204, 0))) => Some(GCodeValue::M204),
9,315✔
338
        ('m', Some((205, 0))) => Some(GCodeValue::M205),
12✔
339
        #[cfg(feature = "with-motion")]
340
        ('m', Some((206, 0))) => Some(GCodeValue::M206),
10✔
341
        ('m', Some((220, 0))) => Some(GCodeValue::M220(S::new())),
12✔
342
        ('m', Some((221, 0))) => Some(GCodeValue::M221(S::new())),
6✔
343
        ('m', Some((502, 0))) => Some(GCodeValue::M502),
×
344
        ('m', Some((503, 0))) => Some(GCodeValue::M503(S::new())),
×
345
        #[cfg(feature = "with-motion")]
346
        ('m', Some((8621, 1))) => Some(GCodeValue::M862_1),
9✔
347
        #[cfg(feature = "with-motion")]
348
        ('m', Some((8623, 1))) => Some(GCodeValue::M862_3),
9✔
349
        #[cfg(feature = "with-motion")]
350
        ('m', Some((900, 0))) => Some(GCodeValue::M900),
12✔
351
        #[cfg(feature = "with-motion")]
352
        ('m', Some((907, 0))) => Some(GCodeValue::M907),
12✔
353
        _ => None,
351✔
354
    }
355
}
452,695✔
356

357
fn update_current(
1,286,463✔
358
    gcode_cmd: &mut GCodeCmd,
1,286,463✔
359
    ch: char,
1,286,463✔
360
    frx: Option<(i32, u8)>,
1,286,463✔
361
    fv: async_gcode::RealValue,
1,286,463✔
362
) {
1,286,463✔
363
    match &mut gcode_cmd.value {
1,286,463✔
364
        #[cfg(feature = "grbl-compat")]
365
        GCodeValue::Status => match (ch, frx) {
366
            ('I', Some(_val)) => {
367
                hwa::warn!("TODO!!");
368
            }
369
            _ => {}
370
        },
371
        #[cfg(feature = "with-motion")]
372
        GCodeValue::G0(coord) => match (ch, frx) {
170✔
373
            ('f', Some(val)) => {
33✔
374
                coord.f.replace(helpers::to_fixed(val));
33✔
375
            }
33✔
376
            //
377
            #[cfg(feature = "with-x-axis")]
378
            ('x', Some(val)) => {
22✔
379
                coord.x.replace(helpers::to_fixed(val));
22✔
380
            }
22✔
381
            #[cfg(feature = "with-y-axis")]
382
            ('y', Some(val)) => {
22✔
383
                coord.y.replace(helpers::to_fixed(val));
22✔
384
            }
22✔
385
            #[cfg(feature = "with-z-axis")]
386
            ('z', Some(val)) => {
33✔
387
                coord.z.replace(helpers::to_fixed(val));
33✔
388
            }
33✔
389
            //
390
            #[cfg(feature = "with-a-axis")]
391
            ('a', Some(val)) => {
1✔
392
                coord.a.replace(helpers::to_fixed(val));
1✔
393
            }
1✔
394
            #[cfg(feature = "with-b-axis")]
395
            ('b', Some(val)) => {
1✔
396
                coord.b.replace(helpers::to_fixed(val));
1✔
397
            }
1✔
398
            #[cfg(feature = "with-c-axis")]
399
            ('c', Some(val)) => {
1✔
400
                coord.c.replace(helpers::to_fixed(val));
1✔
401
            }
1✔
402
            //
403
            #[cfg(feature = "with-i-axis")]
404
            ('i', Some(val)) => {
1✔
405
                coord.i.replace(helpers::to_fixed(val));
1✔
406
            }
1✔
407
            #[cfg(feature = "with-j-axis")]
408
            ('j', Some(val)) => {
1✔
409
                coord.j.replace(helpers::to_fixed(val));
1✔
410
            }
1✔
411
            #[cfg(feature = "with-k-axis")]
412
            ('k', Some(val)) => {
1✔
413
                coord.k.replace(helpers::to_fixed(val));
1✔
414
            }
1✔
415
            //
416
            #[cfg(feature = "with-u-axis")]
417
            ('u', Some(val)) => {
1✔
418
                coord.u.replace(helpers::to_fixed(val));
1✔
419
            }
1✔
420
            #[cfg(feature = "with-v-axis")]
421
            ('v', Some(val)) => {
1✔
422
                coord.v.replace(helpers::to_fixed(val));
1✔
423
            }
1✔
424
            #[cfg(feature = "with-w-axis")]
425
            ('w', Some(val)) => {
1✔
426
                coord.w.replace(helpers::to_fixed(val));
1✔
427
            }
1✔
428
            _ => {}
51✔
429
        },
430

431
        #[cfg(feature = "with-motion")]
432
        GCodeValue::G1(coord) => match (ch, frx) {
1,272,868✔
433
            #[cfg(feature = "with-e-axis")]
434
            ('e', Some(val)) => {
267,540✔
435
                coord.e.replace(helpers::to_fixed(val));
267,540✔
436
            }
267,540✔
437
            ('f', Some(val)) => {
49,603✔
438
                coord.f.replace(helpers::to_fixed(val));
49,603✔
439
            }
49,603✔
440
            ('s', Some(val)) => {
3,413✔
441
                coord.s.replace(helpers::to_fixed(val));
3,413✔
442
            }
3,413✔
443
            //
444
            #[cfg(feature = "with-x-axis")]
445
            ('x', Some(val)) => {
403,668✔
446
                coord.x.replace(helpers::to_fixed(val));
403,668✔
447
            }
403,668✔
448
            #[cfg(feature = "with-y-axis")]
449
            ('y', Some(val)) => {
403,665✔
450
                coord.y.replace(helpers::to_fixed(val));
403,665✔
451
            }
403,665✔
452
            #[cfg(feature = "with-z-axis")]
453
            ('z', Some(val)) => {
11,152✔
454
                coord.z.replace(helpers::to_fixed(val));
11,152✔
455
            }
11,152✔
456
            //
457
            #[cfg(feature = "with-a-axis")]
458
            ('a', Some(val)) => {
1✔
459
                coord.a.replace(helpers::to_fixed(val));
1✔
460
            }
1✔
461
            #[cfg(feature = "with-b-axis")]
462
            ('b', Some(val)) => {
1✔
463
                coord.b.replace(helpers::to_fixed(val));
1✔
464
            }
1✔
465
            #[cfg(feature = "with-c-axis")]
466
            ('c', Some(val)) => {
1✔
467
                coord.c.replace(helpers::to_fixed(val));
1✔
468
            }
1✔
469
            //
470
            #[cfg(feature = "with-i-axis")]
471
            ('i', Some(val)) => {
1✔
472
                coord.i.replace(helpers::to_fixed(val));
1✔
473
            }
1✔
474
            #[cfg(feature = "with-j-axis")]
475
            ('j', Some(val)) => {
1✔
476
                coord.j.replace(helpers::to_fixed(val));
1✔
477
            }
1✔
478
            #[cfg(feature = "with-k-axis")]
479
            ('k', Some(val)) => {
1✔
480
                coord.k.replace(helpers::to_fixed(val));
1✔
481
            }
1✔
482
            //
483
            #[cfg(feature = "with-u-axis")]
484
            ('u', Some(val)) => {
1✔
485
                coord.u.replace(helpers::to_fixed(val));
1✔
486
            }
1✔
487
            #[cfg(feature = "with-v-axis")]
488
            ('v', Some(val)) => {
1✔
489
                coord.v.replace(helpers::to_fixed(val));
1✔
490
            }
1✔
491
            #[cfg(feature = "with-w-axis")]
492
            ('w', Some(val)) => {
1✔
493
                coord.w.replace(helpers::to_fixed(val));
1✔
494
            }
1✔
495
            _ => {}
133,818✔
496
        },
497
        #[cfg(feature = "with-motion")]
498
        GCodeValue::G4(param) => match (ch, frx) {
6✔
499
            ('s', Some(val)) => {
6✔
500
                param.s.replace(helpers::to_fixed(val).abs());
6✔
501
            }
6✔
502
            _ => {}
×
503
        },
504
        #[cfg(feature = "with-motion")]
505
        GCodeValue::G28(coord) => match (ch, frx) {
39✔
506
            #[cfg(feature = "with-e-axis")]
507
            ('e', Some(val)) => {
2✔
508
                coord.e.replace(helpers::to_fixed(val));
2✔
509
            }
2✔
510
            //
511
            #[cfg(feature = "with-x-axis")]
512
            ('x', Some(val)) => {
6✔
513
                coord.x.replace(helpers::to_fixed(val));
6✔
514
            }
6✔
515
            #[cfg(feature = "with-y-axis")]
516
            ('y', Some(val)) => {
6✔
517
                coord.y.replace(helpers::to_fixed(val));
6✔
518
            }
6✔
519
            #[cfg(feature = "with-z-axis")]
520
            ('z', Some(val)) => {
6✔
521
                coord.z.replace(helpers::to_fixed(val));
6✔
522
            }
6✔
523
            //
524
            #[cfg(feature = "with-a-axis")]
525
            ('a', Some(val)) => {
×
526
                coord.a.replace(helpers::to_fixed(val));
×
527
            }
×
528
            #[cfg(feature = "with-b-axis")]
529
            ('b', Some(val)) => {
×
530
                coord.b.replace(helpers::to_fixed(val));
×
531
            }
×
532
            #[cfg(feature = "with-c-axis")]
533
            ('c', Some(val)) => {
×
534
                coord.c.replace(helpers::to_fixed(val));
×
535
            }
×
536
            #[cfg(feature = "with-i-axis")]
537
            ('i', Some(val)) => {
×
538
                coord.i.replace(helpers::to_fixed(val));
×
539
            }
×
540
            #[cfg(feature = "with-j-axis")]
541
            ('j', Some(val)) => {
×
542
                coord.j.replace(helpers::to_fixed(val));
×
543
            }
×
544
            #[cfg(feature = "with-k-axis")]
545
            ('k', Some(val)) => {
×
546
                coord.k.replace(helpers::to_fixed(val));
×
547
            }
×
548
            #[cfg(feature = "with-u-axis")]
549
            ('u', Some(val)) => {
×
550
                coord.u.replace(helpers::to_fixed(val));
×
551
            }
×
552
            #[cfg(feature = "with-v-axis")]
553
            ('v', Some(val)) => {
×
554
                coord.v.replace(helpers::to_fixed(val));
×
555
            }
×
556
            #[cfg(feature = "with-w-axis")]
557
            ('w', Some(val)) => {
×
558
                coord.w.replace(helpers::to_fixed(val));
×
559
            }
×
560
            _ => {}
19✔
561
        },
562
        #[cfg(feature = "with-motion")]
563
        GCodeValue::G92(coord) => match (ch, frx) {
812✔
564
            #[cfg(feature = "with-e-axis")]
565
            ('e', Some(val)) => {
486✔
566
                coord.e.replace(helpers::to_fixed(val));
486✔
567
            }
486✔
568
            //
569
            #[cfg(feature = "with-x-axis")]
570
            ('x', Some(val)) => {
7✔
571
                coord.x.replace(helpers::to_fixed(val));
7✔
572
            }
7✔
573
            #[cfg(feature = "with-y-axis")]
574
            ('y', Some(val)) => {
7✔
575
                coord.y.replace(helpers::to_fixed(val));
7✔
576
            }
7✔
577
            #[cfg(feature = "with-z-axis")]
578
            ('z', Some(val)) => {
6✔
579
                coord.z.replace(helpers::to_fixed(val));
6✔
580
            }
6✔
581
            //
582
            #[cfg(feature = "with-a-axis")]
583
            ('a', Some(val)) => {
1✔
584
                coord.a.replace(helpers::to_fixed(val));
1✔
585
            }
1✔
586
            #[cfg(feature = "with-b-axis")]
587
            ('b', Some(val)) => {
1✔
588
                coord.b.replace(helpers::to_fixed(val));
1✔
589
            }
1✔
590
            #[cfg(feature = "with-c-axis")]
591
            ('c', Some(val)) => {
1✔
592
                coord.c.replace(helpers::to_fixed(val));
1✔
593
            }
1✔
594
            //
595
            #[cfg(feature = "with-i-axis")]
596
            ('i', Some(val)) => {
1✔
597
                coord.i.replace(helpers::to_fixed(val));
1✔
598
            }
1✔
599
            #[cfg(feature = "with-j-axis")]
600
            ('j', Some(val)) => {
1✔
601
                coord.j.replace(helpers::to_fixed(val));
1✔
602
            }
1✔
603
            #[cfg(feature = "with-k-axis")]
604
            ('k', Some(val)) => {
1✔
605
                coord.k.replace(helpers::to_fixed(val));
1✔
606
            }
1✔
607
            //
608
            #[cfg(feature = "with-u-axis")]
609
            ('u', Some(val)) => {
1✔
610
                coord.u.replace(helpers::to_fixed(val));
1✔
611
            }
1✔
612
            #[cfg(feature = "with-v-axis")]
613
            ('v', Some(val)) => {
1✔
614
                coord.v.replace(helpers::to_fixed(val));
1✔
615
            }
1✔
616
            #[cfg(feature = "with-w-axis")]
617
            ('w', Some(val)) => {
1✔
618
                coord.w.replace(helpers::to_fixed(val));
1✔
619
            }
1✔
620
            _ => {}
297✔
621
        },
622
        #[cfg(feature = "with-sd-card")]
623
        GCodeValue::M20(path) => {
6✔
624
            if ch == 'f' {
6✔
625
                if let async_gcode::RealValue::Literal(async_gcode::Literal::String(mstr)) = fv {
6✔
626
                    path.replace(mstr);
6✔
627
                }
6✔
628
            }
×
629
        }
630
        #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
631
        GCodeValue::M23(file) => {
18✔
632
            if let async_gcode::RealValue::Literal(async_gcode::Literal::String(mstr)) = fv {
18✔
633
                file.replace(mstr);
18✔
634
            }
18✔
635
        }
636
        GCodeValue::M37(coord) => match (ch, frx) {
6✔
637
            ('s', Some(val)) => {
6✔
638
                coord.s.replace(helpers::to_fixed(val));
6✔
639
            }
6✔
640
            _ => {}
×
641
        },
642
        #[cfg(feature = "with-hot-end")]
643
        GCodeValue::M104(coord) => match (ch, frx) {
6✔
644
            ('s', Some(val)) => {
6✔
645
                coord.s.replace(helpers::to_fixed(val));
6✔
646
            }
6✔
647
            _ => {}
×
648
        },
649
        #[cfg(feature = "with-hot-end")]
650
        GCodeValue::M109(coord) => match (ch, frx) {
2✔
651
            ('s', Some(val)) => {
2✔
652
                coord.s.replace(helpers::to_fixed(val));
2✔
653
            }
2✔
654
            _ => {}
×
655
        },
656
        GCodeValue::M117(msg) | GCodeValue::M118(msg) => {
6✔
657
            if let async_gcode::RealValue::Literal(async_gcode::Literal::String(text)) = fv {
12✔
658
                msg.replace(text);
12✔
659
            }
12✔
660
        }
661
        #[cfg(feature = "with-hot-bed")]
662
        GCodeValue::M140(coord) => match (ch, frx) {
4✔
663
            ('s', Some(val)) => {
4✔
664
                coord.s.replace(helpers::to_fixed(val));
4✔
665
            }
4✔
666
            _ => {}
×
667
        },
668
        GCodeValue::M220(coord) => match (ch, frx) {
6✔
669
            ('s', Some(val)) => {
6✔
670
                coord.s.replace(helpers::to_fixed(val));
6✔
671
            }
6✔
672
            _ => {}
×
673
        },
674
        GCodeValue::M110(coord) => match (ch, frx) {
6✔
675
            ('n', Some(val)) => {
6✔
676
                coord.n.replace(helpers::to_fixed(val));
6✔
677
            }
6✔
678
            _ => {}
×
679
        },
680
        GCodeValue::M503(param) => match (ch, frx) {
×
681
            ('s', Some(val)) => {
×
682
                param.s.replace(helpers::to_fixed(val));
×
683
            }
×
684
            _ => {}
×
685
        },
686
        _ => {}
12,502✔
687
    }
688
}
1,286,463✔
689

690
#[derive(Debug)]
691
#[cfg_attr(test, derive(PartialEq))]
692
pub enum GCodeLineParserError {
693
    /// There was an error parsing
694
    ParseError(u32),
695
    /// The Gcode was not implemented
696
    GCodeNotImplemented(u32, alloc::string::String),
697
    /// EOF Reading from parser
698
    EOF,
699
    /// Unexpected fatal error
700
    #[allow(unused)]
701
    FatalError,
702
}
703

704
#[cfg(feature = "debug-gcode")]
705
struct FormatableGCode<'a>(&'a async_gcode::GCode);
706

707
#[cfg(feature = "debug-gcode")]
708
impl core::fmt::Debug for FormatableGCode<'_> {
709
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
710
        match self.0 {
711
            async_gcode::GCode::StatusCommand => {
712
                f.write_str("StatusCommand")?;
713
            }
714
            async_gcode::GCode::BlockDelete => {
715
                f.write_str("BlockDelete")?;
716
            }
717
            async_gcode::GCode::LineNumber(_ln) => {
718
                core::write!(f, "LineNumber<{:?}>", _ln)?;
719
            }
720
            async_gcode::GCode::Word(_w, _v) => {
721
                core::write!(f, "Word[{}", _w.to_uppercase())?;
722
                match _v {
723
                    async_gcode::RealValue::Literal(_l) => {
724
                        match _l {
725
                            async_gcode::Literal::RealNumber(_r) => {
726
                                let div = 10_i32.pow(_r.scale().into());
727
                                let w0 = _r.integer_part() / div;
728
                                let w1 = _r.integer_part() % div;
729
                                core::write!(
730
                                    f,
731
                                    "{}.{:0width$}",
732
                                    w0,
733
                                    w1,
734
                                    width = _r.scale() as usize
735
                                )?;
736
                            }
737
                            async_gcode::Literal::String(_s) => {
738
                                core::write!(f, " {}", _s)?;
739
                            }
740
                        }
741
                        core::write!(f, "]")?;
742
                    }
743
                    _ => {}
744
                }
745
            }
746
            async_gcode::GCode::Text(_t) => match _t {
747
                async_gcode::RealValue::Literal(async_gcode::Literal::String(_s)) => {
748
                    core::write!(f, "String[{}]", _s)?;
749
                }
750
                _ => {}
751
            },
752
            async_gcode::GCode::Execute => {
753
                core::write!(f, "Execute")?;
754
            }
755
            #[allow(unreachable_patterns)]
756
            _ => {}
757
        }
758
        Ok(())
759
    }
760
}
761
#[cfg(all(feature = "with-defmt", feature = "debug-gcode"))]
762
impl defmt::Format for FormatableGCode<'_> {
763
    fn format(&self, f: defmt::Formatter) {
764
        match self.0 {
765
            async_gcode::GCode::StatusCommand => {
766
                defmt::write!(f, "StatusCommand");
767
            }
768
            async_gcode::GCode::BlockDelete => {
769
                defmt::write!(f, "BlockDelete");
770
            }
771
            async_gcode::GCode::LineNumber(_ln) => {
772
                defmt::write!(f, "LineNumber<{:?}>", _ln);
773
            }
774
            async_gcode::GCode::Word(_w, _v) => {
775
                use alloc::string::ToString;
776
                defmt::write!(f, "Word[{:?}", _w.to_uppercase().to_string().as_str());
777
                match _v {
778
                    async_gcode::RealValue::Literal(_l) => {
779
                        match _l {
780
                            async_gcode::Literal::RealNumber(_r) => {
781
                                defmt::write!(
782
                                    f,
783
                                    " val: {}, scale: {}",
784
                                    _r.integer_part(),
785
                                    _r.scale()
786
                                );
787
                            }
788
                            async_gcode::Literal::String(_s) => {
789
                                defmt::write!(f, " {}", _s.as_str());
790
                            }
791
                        }
792
                        defmt::write!(f, "]");
793
                    }
794
                    _ => {}
795
                }
796
            }
797
            async_gcode::GCode::Text(_t) => match _t {
798
                async_gcode::RealValue::Literal(async_gcode::Literal::String(_s)) => {
799
                    defmt::write!(f, "String[{}]", _s.as_str());
800
                }
801
                _ => {}
802
            },
803
            async_gcode::GCode::Execute => {
804
                defmt::write!(f, "Execute");
805
            }
806
            #[allow(unreachable_patterns)]
807
            _ => {}
808
        }
809
    }
810
}
811

812
#[cfg(feature = "with-defmt")]
813
impl defmt::Format for GCodeLineParserError {
814
    fn format(&self, fmt: defmt::Formatter) {
815
        match self {
816
            GCodeLineParserError::ParseError(ln) => {
817
                defmt::write!(fmt, "ParseError(line={})", ln);
818
            }
819
            GCodeLineParserError::GCodeNotImplemented(ln, string) => {
820
                defmt::write!(
821
                    fmt,
822
                    "GCodeNotImplemented(line={}, gcode={})",
823
                    ln,
824
                    string.as_str()
825
                );
826
            }
827
            GCodeLineParserError::FatalError => {
828
                defmt::write!(fmt, "FatalError");
829
            }
830
            GCodeLineParserError::EOF => {
831
                defmt::write!(fmt, "EOF");
832
            }
833
        }
834
    }
835
}
836

837
/// Represents the raw specification of a G-code command.
838
///
839
/// G-code is a language used to control CNC machines, 3D printers, and other similar equipment.
840
/// A `RawGCodeSpec` captures the initial character of the command (e.g., 'G', 'M') and any numerical
841
/// specifics associated with it, potentially including a sub-value used for more granular control.
842
///
843
/// # Fields
844
/// - `code`: The main character of the G-code command.
845
/// - `spec`: An optional numerical part that follows the main character. For example, 'G1' would have
846
///   '1' as its spec.
847
/// - `sub`: An optional sub-value that provides additional specificity. For example, 'G1.1' would have
848
///   '1' as its spec and '1' as its sub.
849
///
850
/// # Example
851
///
852
/// ```
853
/// let gcode = RawTGCodeSpec::from('G', Some((1, 0)));
854
/// assert_eq!(format!("{:?}", gcode), "G1");
855
/// ```
856
pub struct RawGCodeSpec {
857
    code: char,
858
    spec: Option<i32>,
859
    sub: Option<i32>,
860
}
861

862
impl RawGCodeSpec {
863
    pub fn from(code: char, spec: Option<(i32, u8)>) -> Self {
452,701✔
864
        match spec {
452,701✔
865
            None => Self {
18✔
866
                code,
18✔
867
                spec: None,
18✔
868
                sub: None,
18✔
869
            },
18✔
870
            Some((_num, _scale)) => {
452,683✔
871
                if _scale == 0 {
452,683✔
872
                    Self {
452,641✔
873
                        code,
452,641✔
874
                        spec: Some(_num),
452,641✔
875
                        sub: None,
452,641✔
876
                    }
452,641✔
877
                } else {
878
                    let sc = 10_i32.pow(_scale as u32);
42✔
879
                    let sp = _num / sc;
42✔
880
                    let ss = _num % sc;
42✔
881
                    Self {
42✔
882
                        code,
42✔
883
                        spec: Some(sp),
42✔
884
                        sub: Some(ss),
42✔
885
                    }
42✔
886
                }
887
            }
888
        }
889
    }
452,701✔
890
}
891
impl core::fmt::Debug for RawGCodeSpec {
892
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
357✔
893
        core::write!(f, "{}", self.code.to_uppercase())?;
357✔
894
        match &self.spec {
357✔
895
            Some(_spec) => {
351✔
896
                core::write!(f, "{}", _spec)?;
351✔
897
                match &self.sub {
351✔
NEW
898
                    Some(_sub) => {
×
NEW
899
                        core::write!(f, ".{}", _sub)
×
900
                    }
901
                    _ => Ok(()),
351✔
902
                }
903
            }
904
            _ => Ok(()),
6✔
905
        }
906
    }
357✔
907
}
908

909
/// This trait is just a hack to give backward compatibility with unpatched async-gcode 0.3.0
910
/// Normally, won't be used as it is patched in cargo.toml
911
/// Purpose of this trait and impl is just to be able to publish printhor in crates.io
912
#[allow(unused)]
913
trait FixedAdaptor {
914
    fn integer_part(&self) -> i32;
915
    fn scale(&self) -> u8;
916
}
917

918
impl FixedAdaptor for f64 {
919
    #[inline(always)]
NEW
920
    fn integer_part(&self) -> i32 {
×
NEW
921
        panic!("Please, use patched async-gcode instead")
×
922
    }
923

924
    #[inline(always)]
NEW
925
    fn scale(&self) -> u8 {
×
NEW
926
        panic!("Please, use patched async-gcode instead")
×
927
    }
928
}
929

930
#[cfg(feature = "native")]
931
#[cfg(test)]
932
mod tests {
933
    use super::*;
934
    use async_gcode::{AsyncParserState, ByteStream};
935
    use std::collections::VecDeque;
936

937
    struct BufferStream {
938
        buff: VecDeque<u8>,
939
    }
940

941
    impl BufferStream {
942
        const fn new(buff: VecDeque<u8>) -> Self {
234✔
943
            Self { buff }
234✔
944
        }
234✔
945
    }
946

947
    impl async_gcode::ByteStream for BufferStream {
948
        type Item = Result<u8, async_gcode::Error>;
949

950
        async fn next(&mut self) -> Option<Self::Item> {
3,108✔
951
            match self.buff.pop_front() {
3,108✔
952
                None => None,
78✔
953
                Some(_b) => Some(Ok(_b)),
3,030✔
954
            }
955
        }
3,108✔
956

957
        async fn recovery_check(&mut self) {}
18✔
958
    }
959

960
    #[futures_test::test]
961
    async fn test_m23_with_newline() {
6✔
962
        let stream = BufferStream::new("M23 BENCHY.G\n".as_bytes().to_vec().into());
6✔
963
        let mut parser = GCodeLineParser::new(stream);
6✔
964
        assert_eq!(
6✔
965
            parser.next_gcode(CommChannel::Internal).await,
6✔
966
            Ok(GCodeCmd::new(
6✔
967
                0,
6✔
968
                None,
6✔
969
                GCodeValue::M23(Some("BENCHY.G".into()))
6✔
970
            ))
6✔
971
        )
972
    }
12✔
973

974
    #[futures_test::test]
975
    async fn test_m23_with_eof_condition() {
6✔
976
        let stream = BufferStream::new("M23 BENCHY.G".as_bytes().to_vec().into());
6✔
977
        let mut parser = GCodeLineParser::new(stream);
6✔
978
        assert_eq!(
6✔
979
            parser.next_gcode(CommChannel::Internal).await,
6✔
980
            Ok(GCodeCmd::new(
6✔
981
                0,
6✔
982
                None,
6✔
983
                GCodeValue::M23(Some("BENCHY.G".into()))
6✔
984
            ))
6✔
985
        )
986
    }
12✔
987

988
    #[futures_test::test]
989
    async fn test_m23_with_trailin_comments() {
6✔
990
        let stream = BufferStream::new("M23 BENCHY.G".as_bytes().to_vec().into());
6✔
991
        let mut parser = GCodeLineParser::new(stream);
6✔
992
        assert_eq!(
6✔
993
            parser.next_gcode(CommChannel::Internal).await,
6✔
994
            Ok(GCodeCmd::new(
6✔
995
                0,
6✔
996
                None,
6✔
997
                GCodeValue::M23(Some("BENCHY.G".into()))
6✔
998
            ))
6✔
999
        )
1000
    }
12✔
1001

1002
    #[futures_test::test]
1003
    async fn test_m117() {
6✔
1004
        let stream = BufferStream::new("M117 Hello World!".as_bytes().to_vec().into());
6✔
1005
        let mut parser = GCodeLineParser::new(stream);
6✔
1006
        assert_eq!(
6✔
1007
            parser.next_gcode(CommChannel::Internal).await,
6✔
1008
            Ok(GCodeCmd::new(
6✔
1009
                0,
6✔
1010
                None,
6✔
1011
                GCodeValue::M117(Some("Hello World!".into()))
6✔
1012
            ))
6✔
1013
        )
1014
    }
12✔
1015

1016
    #[futures_test::test]
1017
    async fn test_m118() {
6✔
1018
        let stream = BufferStream::new("M118 Hello World!".as_bytes().to_vec().into());
6✔
1019
        let mut parser = GCodeLineParser::new(stream);
6✔
1020
        assert_eq!(
6✔
1021
            parser.next_gcode(CommChannel::Internal).await,
6✔
1022
            Ok(GCodeCmd::new(
6✔
1023
                0,
6✔
1024
                None,
6✔
1025
                GCodeValue::M118(Some("Hello World!".into()))
6✔
1026
            ))
6✔
1027
        )
1028
    }
12✔
1029

1030
    #[futures_test::test]
1031
    async fn test_gs() {
6✔
1032
        // TODO: Check EOF condition
6✔
1033
        let stream = BufferStream::new(
6✔
1034
            "G0 F0 E0 X0 Y0 Z0 A0 B0 C0 I0 J0 K0 U0 V0 W0\n"
6✔
1035
                .as_bytes()
6✔
1036
                .to_vec()
6✔
1037
                .into(),
6✔
1038
        );
6✔
1039
        let mut parser = GCodeLineParser::new(stream);
6✔
1040
        let _ = parser.gcode_line();
6✔
1041
        parser
6✔
1042
            .next_gcode(CommChannel::Internal)
6✔
1043
            .await
6✔
1044
            .expect("G0 OK");
6✔
1045

6✔
1046
        let stream = BufferStream::new(
6✔
1047
            "G1 F0 E0 X0 Y0 Z0 A0 B0 C0 I0 J0 K0 U0 V0 W0\n"
6✔
1048
                .as_bytes()
6✔
1049
                .to_vec()
6✔
1050
                .into(),
6✔
1051
        );
6✔
1052
        let mut parser = GCodeLineParser::new(stream);
6✔
1053
        let _ = parser.gcode_line();
6✔
1054
        parser
6✔
1055
            .next_gcode(CommChannel::Internal)
6✔
1056
            .await
6✔
1057
            .expect("G1 OK");
6✔
1058

6✔
1059
        let stream = BufferStream::new(
6✔
1060
            "G92 F0 E0 X0 Y0 Z0 A0 B0 C0 I0 J0 K0 U0 V0 W0\n"
6✔
1061
                .as_bytes()
6✔
1062
                .to_vec()
6✔
1063
                .into(),
6✔
1064
        );
6✔
1065
        let mut parser = GCodeLineParser::new(stream);
6✔
1066
        let _ = parser.gcode_line();
6✔
1067
        parser
6✔
1068
            .next_gcode(CommChannel::Internal)
6✔
1069
            .await
6✔
1070
            .expect("G1 OK");
6✔
1071

6✔
1072
        let stream = BufferStream::new("G4\n".as_bytes().to_vec().into());
6✔
1073
        let mut parser = GCodeLineParser::new(stream);
6✔
1074
        let _ = parser.gcode_line();
6✔
1075
        parser
6✔
1076
            .next_gcode(CommChannel::Internal)
6✔
1077
            .await
6✔
1078
            .expect("G4 OK");
6✔
1079

6✔
1080
        let stream = BufferStream::new("G4 S10\n".as_bytes().to_vec().into());
6✔
1081
        let mut parser = GCodeLineParser::new(stream);
6✔
1082
        let _ = parser.gcode_line();
6✔
1083
        parser
6✔
1084
            .next_gcode(CommChannel::Internal)
6✔
1085
            .await
6✔
1086
            .expect("G4 S10 OK");
6✔
1087

6✔
1088
        let stream = BufferStream::new("N10 G28 F0 E0 X0 Y0 Z0\n".as_bytes().to_vec().into());
6✔
1089
        let mut parser = GCodeLineParser::new(stream);
6✔
1090
        let _ = parser.gcode_line();
6✔
1091
        parser
6✔
1092
            .next_gcode(CommChannel::Internal)
6✔
1093
            .await
6✔
1094
            .expect("G28 OK");
6✔
1095

6✔
1096
        parser.reset_current_line();
6✔
1097

6✔
1098
        let stream = BufferStream::new("N10 G29 F0 E0 X0 Y0 Z0\n".as_bytes().to_vec().into());
6✔
1099
        let mut parser = GCodeLineParser::new(stream);
6✔
1100
        let _ = parser.gcode_line();
6✔
1101
        parser
6✔
1102
            .next_gcode(CommChannel::Internal)
6✔
1103
            .await
6✔
1104
            .expect("G29 OK");
6✔
1105

6✔
1106
        let stream = BufferStream::new("N11 G29.1 F0 E0 X0 Y0 Z0\n".as_bytes().to_vec().into());
6✔
1107
        let mut parser = GCodeLineParser::new(stream);
6✔
1108
        let _ = parser.gcode_line();
6✔
1109
        parser
6✔
1110
            .next_gcode(CommChannel::Internal)
6✔
1111
            .await
6✔
1112
            .expect("G29.1 OK");
6✔
1113

6✔
1114
        let stream = BufferStream::new("G29.2 F0 E0 X0 Y0 Z0\n".as_bytes().to_vec().into());
6✔
1115
        let mut parser = GCodeLineParser::new(stream);
6✔
1116
        let _ = parser.gcode_line();
6✔
1117
        parser
6✔
1118
            .next_gcode(CommChannel::Internal)
6✔
1119
            .await
6✔
1120
            .expect("G29.2 OK");
6✔
1121

6✔
1122
        let stream = BufferStream::new("G31\n".as_bytes().to_vec().into());
6✔
1123
        let mut parser = GCodeLineParser::new(stream);
6✔
1124
        let _ = parser.gcode_line();
6✔
1125
        parser
6✔
1126
            .next_gcode(CommChannel::Internal)
6✔
1127
            .await
6✔
1128
            .expect("G31 OK");
6✔
1129
    }
6✔
1130

1131
    #[futures_test::test]
1132
    async fn test_machinery() {
6✔
1133
        let mut stream = BufferStream::new("".as_bytes().to_vec().into());
6✔
1134
        assert_eq!(stream.next().await, None);
6✔
1135
        stream.recovery_check().await;
6✔
1136
        assert_eq!(stream.next().await, None);
6✔
1137

1138
        let spec = RawGCodeSpec::from('Z', None);
6✔
1139

6✔
1140
        let _str = format!("{:?}", spec);
6✔
1141

6✔
1142
        let stream = BufferStream::new("".as_bytes().to_vec().into());
6✔
1143
        let mut parser = GCodeLineParser::new(stream);
6✔
1144
        parser
6✔
1145
            .next_gcode(CommChannel::Internal)
6✔
1146
            .await
6✔
1147
            .expect_err("None");
6✔
1148
        assert_eq!(parser.get_state(), AsyncParserState::Start(true));
6✔
1149
        parser
6✔
1150
            .next_gcode(CommChannel::Internal)
6✔
1151
            .await
6✔
1152
            .expect_err("EOF");
6✔
1153
        assert_eq!(parser.get_state(), AsyncParserState::Start(true));
6✔
1154
        let _ = parser.close().await;
6✔
1155

1156
        let stream = BufferStream::new("/X0\n".as_bytes().to_vec().into());
6✔
1157
        let mut parser = GCodeLineParser::new(stream);
6✔
1158
        parser
6✔
1159
            .next_gcode(CommChannel::Internal)
6✔
1160
            .await
6✔
1161
            .expect_err("None");
6✔
1162

6✔
1163
        let stream = BufferStream::new("&0\n".as_bytes().to_vec().into());
6✔
1164
        let mut parser = GCodeLineParser::new(stream);
6✔
1165
        parser
6✔
1166
            .next_gcode(CommChannel::Internal)
6✔
1167
            .await
6✔
1168
            .expect_err("None");
6✔
1169

6✔
1170
        let stream = BufferStream::new("V0\n".as_bytes().to_vec().into());
6✔
1171
        let mut parser = GCodeLineParser::new(stream);
6✔
1172
        parser
6✔
1173
            .next_gcode(CommChannel::Internal)
6✔
1174
            .await
6✔
1175
            .expect_err("None");
6✔
1176
    }
6✔
1177

1178
    #[futures_test::test]
1179
    async fn test_ms() {
6✔
1180
        let stream = BufferStream::new("M20 F\"X.g\"\n".as_bytes().to_vec().into());
6✔
1181
        let mut parser = GCodeLineParser::new(stream);
6✔
1182
        let _ = parser.gcode_line();
6✔
1183
        parser
6✔
1184
            .next_gcode(CommChannel::Internal)
6✔
1185
            .await
6✔
1186
            .expect("M20 OK");
6✔
1187

6✔
1188
        let stream = BufferStream::new("M37\n".as_bytes().to_vec().into());
6✔
1189
        let mut parser = GCodeLineParser::new(stream);
6✔
1190
        let _ = parser.gcode_line();
6✔
1191
        parser
6✔
1192
            .next_gcode(CommChannel::Internal)
6✔
1193
            .await
6✔
1194
            .expect("M37 OK");
6✔
1195

6✔
1196
        let stream = BufferStream::new("M37 S1\n".as_bytes().to_vec().into());
6✔
1197
        let mut parser = GCodeLineParser::new(stream);
6✔
1198
        let _ = parser.gcode_line();
6✔
1199
        parser
6✔
1200
            .next_gcode(CommChannel::Internal)
6✔
1201
            .await
6✔
1202
            .expect("M37 S1 OK");
6✔
1203

6✔
1204
        #[cfg(feature = "with-hot-bed")]
6✔
1205
        {
6✔
1206
            let stream = BufferStream::new("M140\n".as_bytes().to_vec().into());
6✔
1207
            let mut parser = GCodeLineParser::new(stream);
6✔
1208
            let _ = parser.gcode_line();
6✔
1209
            parser
6✔
1210
                .next_gcode(CommChannel::Internal)
6✔
1211
                .await
6✔
1212
                .expect("M140 OK");
6✔
1213
        }
6✔
1214

6✔
1215
        #[cfg(feature = "with-hot-end")]
6✔
1216
        {
6✔
1217
            let stream = BufferStream::new("M104\n".as_bytes().to_vec().into());
6✔
1218
            let mut parser = GCodeLineParser::new(stream);
6✔
1219
            let _ = parser.gcode_line();
6✔
1220
            parser
6✔
1221
                .next_gcode(CommChannel::Internal)
6✔
1222
                .await
6✔
1223
                .expect("M104 OK");
6✔
1224

6✔
1225
            let stream = BufferStream::new("M109\n".as_bytes().to_vec().into());
6✔
1226
            let mut parser = GCodeLineParser::new(stream);
6✔
1227
            let _ = parser.gcode_line();
6✔
1228
            parser
6✔
1229
                .next_gcode(CommChannel::Internal)
6✔
1230
                .await
6✔
1231
                .expect("M109 OK");
6✔
1232
        }
6✔
1233

6✔
1234
        let stream = BufferStream::new("M220\n".as_bytes().to_vec().into());
6✔
1235
        let mut parser = GCodeLineParser::new(stream);
6✔
1236
        let _ = parser.gcode_line();
6✔
1237
        parser
6✔
1238
            .next_gcode(CommChannel::Internal)
6✔
1239
            .await
6✔
1240
            .expect("M220 OK");
6✔
1241

6✔
1242
        let stream = BufferStream::new("M220 S1\n".as_bytes().to_vec().into());
6✔
1243
        let mut parser = GCodeLineParser::new(stream);
6✔
1244
        let _ = parser.gcode_line();
6✔
1245
        parser
6✔
1246
            .next_gcode(CommChannel::Internal)
6✔
1247
            .await
6✔
1248
            .expect("M220 S1 OK");
6✔
1249

6✔
1250
        let stream = BufferStream::new("M110\n".as_bytes().to_vec().into());
6✔
1251
        let mut parser = GCodeLineParser::new(stream);
6✔
1252
        let _ = parser.gcode_line();
6✔
1253
        parser
6✔
1254
            .next_gcode(CommChannel::Internal)
6✔
1255
            .await
6✔
1256
            .expect("M110 OK");
6✔
1257

6✔
1258
        parser.reset_current_line();
6✔
1259
        parser.reset().await;
6✔
1260

1261
        let stream = BufferStream::new("M110 N1\n".as_bytes().to_vec().into());
6✔
1262
        let mut parser = GCodeLineParser::new(stream);
6✔
1263
        let _ = parser.gcode_line();
6✔
1264
        parser
6✔
1265
            .next_gcode(CommChannel::Internal)
6✔
1266
            .await
6✔
1267
            .expect("M110 N1 OK");
6✔
1268

6✔
1269
        let stream = BufferStream::new("M204\n".as_bytes().to_vec().into());
6✔
1270
        let mut parser = GCodeLineParser::new(stream);
6✔
1271
        let _ = parser.gcode_line();
6✔
1272
        parser
6✔
1273
            .next_gcode(CommChannel::Internal)
6✔
1274
            .await
6✔
1275
            .expect("M204 OK");
6✔
1276

6✔
1277
        let stream = BufferStream::new("M205\n".as_bytes().to_vec().into());
6✔
1278
        let mut parser = GCodeLineParser::new(stream);
6✔
1279
        let _ = parser.gcode_line();
6✔
1280
        parser
6✔
1281
            .next_gcode(CommChannel::Internal)
6✔
1282
            .await
6✔
1283
            .expect("M205 OK");
6✔
1284

6✔
1285
        let stream = BufferStream::new("M862.1\n".as_bytes().to_vec().into());
6✔
1286
        let mut parser = GCodeLineParser::new(stream);
6✔
1287
        let _ = parser.gcode_line();
6✔
1288
        parser
6✔
1289
            .next_gcode(CommChannel::Internal)
6✔
1290
            .await
6✔
1291
            .expect("M862.1 OK");
6✔
1292

6✔
1293
        let stream = BufferStream::new("M862.3\n".as_bytes().to_vec().into());
6✔
1294
        let mut parser = GCodeLineParser::new(stream);
6✔
1295
        let _ = parser.gcode_line();
6✔
1296
        parser
6✔
1297
            .next_gcode(CommChannel::Internal)
6✔
1298
            .await
6✔
1299
            .expect("M862.3 OK");
6✔
1300

6✔
1301
        let stream = BufferStream::new("M900\n".as_bytes().to_vec().into());
6✔
1302
        let mut parser = GCodeLineParser::new(stream);
6✔
1303
        let _ = parser.gcode_line();
6✔
1304
        parser
6✔
1305
            .next_gcode(CommChannel::Internal)
6✔
1306
            .await
6✔
1307
            .expect("M900 OK");
6✔
1308

6✔
1309
        let stream = BufferStream::new("M907\n".as_bytes().to_vec().into());
6✔
1310
        let mut parser = GCodeLineParser::new(stream);
6✔
1311
        let _ = parser.gcode_line();
6✔
1312
        parser
6✔
1313
            .next_gcode(CommChannel::Internal)
6✔
1314
            .await
6✔
1315
            .expect("M907 OK");
6✔
1316
    }
6✔
1317

1318
    #[futures_test::test]
1319
    async fn test_g_variants() {
6✔
1320
        let stream = BufferStream::new("G\nG0\nG1\nG2\nG4\nG10\nG17\nG21\nG28\nG29\nG29.1\nG29.2\nG31\nG32\nG80\nG90\nG91\nG92\nG94\n".as_bytes().to_vec().into());
6✔
1321

6✔
1322
        /*
6✔
1323
            #[cfg(feature = "with-motion")]
6✔
1324
            ('g', Some((94, 0))) => Some(GCodeValue::G94),
6✔
1325
            ('m', None) => Some(GCodeValue::M),
6✔
1326
            #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
6✔
1327
            ('m', Some((2, 0))) => Some(GCodeValue::M2),
6✔
1328
            ('m', Some((3, 0))) => Some(GCodeValue::M3),
6✔
1329
            ('m', Some((4, 0))) => Some(GCodeValue::M4),
6✔
1330
            ('m', Some((5, 0))) => Some(GCodeValue::M5),
6✔
1331
            #[cfg(feature = "with-sd-card")]
6✔
1332
            ('m', Some((20, 0))) => Some(GCodeValue::M20(None)),
6✔
1333
            #[cfg(feature = "with-sd-card")]
6✔
1334
            ('m', Some((21, 0))) => Some(GCodeValue::M21),
6✔
1335
            #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
6✔
1336
            ('m', Some((23, 0))) => Some(GCodeValue::M23(None)),
6✔
1337
            #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
6✔
1338
            ('m', Some((24, 0))) => Some(GCodeValue::M24),
6✔
1339
            #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
6✔
1340
            ('m', Some((25, 0))) => Some(GCodeValue::M25),
6✔
1341
            ('m', Some((37, 0))) => Some(GCodeValue::M37(S::new())),
6✔
1342
            ('m', Some((73, 0))) => Some(GCodeValue::M73),
6✔
1343
            ('m', Some((79, 0))) => Some(GCodeValue::M79),
6✔
1344
            #[cfg(feature = "with-ps-on")]
6✔
1345
            ('m', Some((80, 0))) => Some(GCodeValue::M80),
6✔
1346
            #[cfg(feature = "with-ps-on")]
6✔
1347
            ('m', Some((81, 0))) => Some(GCodeValue::M81),
6✔
1348
            ('m', Some((83, 0))) => Some(GCodeValue::M83),
6✔
1349
            #[cfg(feature = "with-motion")]
6✔
1350
            ('m', Some((84, 0))) => Some(GCodeValue::M84),
6✔
1351
            ('m', Some((100, 0))) => Some(GCodeValue::M100),
6✔
1352
            #[cfg(feature = "with-hot-end")]
6✔
1353
            ('m', Some((104, 0))) => Some(GCodeValue::M104(S::new())),
6✔
1354
            ('m', Some((105, 0))) => Some(GCodeValue::M105),
6✔
1355
            #[cfg(feature = "with-fan-layer")]
6✔
1356
            ('m', Some((106, 0))) => Some(GCodeValue::M106),
6✔
1357
            #[cfg(feature = "with-fan-layer")]
6✔
1358
            ('m', Some((107, 0))) => Some(GCodeValue::M107),
6✔
1359
            #[cfg(feature = "with-hot-end")]
6✔
1360
            ('m', Some((109, 0))) => Some(GCodeValue::M109(S::new())),
6✔
1361
            ('m', Some((110, 0))) => Some(GCodeValue::M110(N::new())),
6✔
1362
            #[cfg(feature = "with-motion")]
6✔
1363
            ('m', Some((114, 0))) => Some(GCodeValue::M114),
6✔
1364
            ('m', Some((115, 0))) => Some(GCodeValue::M115),
6✔
1365
            ('m', Some((117, 0))) => Some(GCodeValue::M117(None)),
6✔
1366
            ('m', Some((118, 0))) => Some(GCodeValue::M118(None)),
6✔
1367
            ('m', Some((119, 0))) => Some(GCodeValue::M119),
6✔
1368
            #[cfg(feature = "with-hot-bed")]
6✔
1369
            ('m', Some((140, 0))) => Some(GCodeValue::M140(S::new())),
6✔
1370
            #[cfg(feature = "with-hot-bed")]
6✔
1371
            ('m', Some((190, 0))) => Some(GCodeValue::M190),
6✔
1372
            #[cfg(feature = "with-motion")]
6✔
1373
            ('m', Some((201, 0))) => Some(GCodeValue::M201),
6✔
1374
            #[cfg(feature = "with-motion")]
6✔
1375
            ('m', Some((203, 0))) => Some(GCodeValue::M203),
6✔
1376
            ('m', Some((204, 0))) => Some(GCodeValue::M204),
6✔
1377
            ('m', Some((205, 0))) => Some(GCodeValue::M205),
6✔
1378
            #[cfg(feature = "with-motion")]
6✔
1379
            ('m', Some((206, 0))) => Some(GCodeValue::M206),
6✔
1380
            ('m', Some((220, 0))) => Some(GCodeValue::M220(S::new())),
6✔
1381
            ('m', Some((221, 0))) => Some(GCodeValue::M221(S::new())),
6✔
1382
            ('m', Some((502, 0))) => Some(GCodeValue::M502),
6✔
1383
            ('m', Some((503, 0))) => Some(GCodeValue::M503(S::new())),
6✔
1384
            #[cfg(feature = "with-motion")]
6✔
1385
            ('m', Some((8621, 1))) => Some(GCodeValue::M862_1),
6✔
1386
            #[cfg(feature = "with-motion")]
6✔
1387
            ('m', Some((8623, 1))) => Some(GCodeValue::M862_3),
6✔
1388
            #[cfg(feature = "with-motion")]
6✔
1389
            ('m', Some((900, 0))) => Some(GCodeValue::M900),
6✔
1390
            #[cfg(feature = "with-motion")]
6✔
1391
            ('m', Some((907, 0))) => Some(GCodeValue::M907),
6✔
1392
            _ => None,
6✔
1393
        */
6✔
1394

6✔
1395
        let mut parser = GCodeLineParser::new(stream);
6✔
1396

1397
        assert_eq!(
6✔
1398
            parser.next_gcode(CommChannel::Internal).await,
6✔
1399
            Ok(GCodeCmd::new(0, None, GCodeValue::G))
6✔
1400
        );
1401
        assert_eq!(
6✔
1402
            parser.next_gcode(CommChannel::Internal).await,
6✔
1403
            Ok(GCodeCmd::new(1, None, GCodeValue::G0(FXYZ::new())))
6✔
1404
        );
1405
        assert_eq!(
6✔
1406
            parser.next_gcode(CommChannel::Internal).await,
6✔
1407
            Ok(GCodeCmd::new(2, None, GCodeValue::G1(EFSXYZ::new())))
6✔
1408
        );
1409
        assert_eq!(
6✔
1410
            parser.next_gcode(CommChannel::Internal).await,
6✔
1411
            Err(GCodeLineParserError::GCodeNotImplemented(3, "G2".into()))
6✔
1412
        );
1413
        assert_eq!(
6✔
1414
            parser.next_gcode(CommChannel::Internal).await,
6✔
1415
            Ok(GCodeCmd::new(4, None, GCodeValue::G4(S::new())))
6✔
1416
        );
1417
        assert_eq!(
6✔
1418
            parser.next_gcode(CommChannel::Internal).await,
6✔
1419
            Ok(GCodeCmd::new(5, None, GCodeValue::G10))
6✔
1420
        );
1421
        assert_eq!(
6✔
1422
            parser.next_gcode(CommChannel::Internal).await,
6✔
1423
            Ok(GCodeCmd::new(6, None, GCodeValue::G17))
6✔
1424
        );
1425
        assert_eq!(
6✔
1426
            parser.next_gcode(CommChannel::Internal).await,
6✔
1427
            Ok(GCodeCmd::new(7, None, GCodeValue::G21))
6✔
1428
        );
1429
        assert_eq!(
6✔
1430
            parser.next_gcode(CommChannel::Internal).await,
6✔
1431
            Ok(GCodeCmd::new(8, None, GCodeValue::G28(EXYZ::new())))
6✔
1432
        );
1433
        assert_eq!(
6✔
1434
            parser.next_gcode(CommChannel::Internal).await,
6✔
1435
            Ok(GCodeCmd::new(9, None, GCodeValue::G29))
6✔
1436
        );
1437
        assert_eq!(
6✔
1438
            parser.next_gcode(CommChannel::Internal).await,
6✔
1439
            Ok(GCodeCmd::new(10, None, GCodeValue::G29_1))
6✔
1440
        );
1441
        assert_eq!(
6✔
1442
            parser.next_gcode(CommChannel::Internal).await,
6✔
1443
            Ok(GCodeCmd::new(11, None, GCodeValue::G29_2))
6✔
1444
        );
1445
        assert_eq!(
6✔
1446
            parser.next_gcode(CommChannel::Internal).await,
6✔
1447
            Ok(GCodeCmd::new(12, None, GCodeValue::G31))
6✔
1448
        );
1449
        assert_eq!(
6✔
1450
            parser.next_gcode(CommChannel::Internal).await,
6✔
1451
            Ok(GCodeCmd::new(13, None, GCodeValue::G32))
6✔
1452
        );
1453
        assert_eq!(
6✔
1454
            parser.next_gcode(CommChannel::Internal).await,
6✔
1455
            Ok(GCodeCmd::new(14, None, GCodeValue::G80))
6✔
1456
        );
1457

1458
        assert_eq!(
6✔
1459
            parser.next_gcode(CommChannel::Internal).await,
6✔
1460
            Ok(GCodeCmd::new(15, None, GCodeValue::G90))
6✔
1461
        );
1462
        assert_eq!(
6✔
1463
            parser.next_gcode(CommChannel::Internal).await,
6✔
1464
            Ok(GCodeCmd::new(16, None, GCodeValue::G91))
6✔
1465
        );
1466
        assert_eq!(
6✔
1467
            parser.next_gcode(CommChannel::Internal).await,
6✔
1468
            Ok(GCodeCmd::new(17, None, GCodeValue::G92(EXYZ::new())))
6✔
1469
        );
1470
        assert_eq!(
6✔
1471
            parser.next_gcode(CommChannel::Internal).await,
6✔
1472
            Ok(GCodeCmd::new(18, None, GCodeValue::G94))
6✔
1473
        );
1474
    }
12✔
1475

1476
    #[futures_test::test]
1477
    async fn test_m_variants() {
6✔
1478
        let stream = BufferStream::new("M\nM2\nM3\nM4\nM5\nM20\nM21\nM23\nM23\nM24\nM25\nM37\nM73\nM79\nM80\nM81\nM83\nM84\nM100\nM104\nM105\nM106\nM107\nM109\nM110\nM114\nM115\nM117\nM118\nM119\nM140\nM190\nM201\nM203\nM204\nM205\nM206\nM220\nM221\nM502\nM503\nM862.1\nM862.3\nM900\nM907\n".as_bytes().to_vec().into());
6✔
1479

6✔
1480
        let mut parser = GCodeLineParser::new(stream);
6✔
1481

1482
        assert_eq!(
6✔
1483
            parser.next_gcode(CommChannel::Internal).await,
6✔
1484
            Ok(GCodeCmd::new(0, None, GCodeValue::M))
6✔
1485
        );
1486
        assert_eq!(
6✔
1487
            parser.next_gcode(CommChannel::Internal).await,
6✔
1488
            Ok(GCodeCmd::new(1, None, GCodeValue::M2))
6✔
1489
        );
1490

1491
        // WIP
1492
    }
12✔
1493

1494
    #[futures_test::test]
1495
    async fn test_boundary_cases() {
6✔
1496
        let stream = BufferStream::new("X00\n".as_bytes().to_vec().into());
6✔
1497
        let mut parser = GCodeLineParser::new(stream);
6✔
1498
        let _ = parser.gcode_line();
6✔
1499
        assert_eq!(
6✔
1500
            parser.next_gcode(CommChannel::Internal).await,
6✔
1501
            Err(GCodeLineParserError::GCodeNotImplemented(0, "X0".into()))
6✔
1502
        );
1503

1504
        let stream = BufferStream::new("G.-1\n".as_bytes().to_vec().into());
6✔
1505
        let mut parser = GCodeLineParser::new(stream);
6✔
1506
        assert_eq!(
6✔
1507
            parser.next_gcode(CommChannel::Internal).await,
6✔
1508
            Err(GCodeLineParserError::ParseError(0))
1509
        );
1510

1511
        let stream = BufferStream::new("G999999999999999999999999999\n".as_bytes().to_vec().into());
6✔
1512
        let mut parser = GCodeLineParser::new(stream);
6✔
1513
        assert_eq!(
6✔
1514
            parser.next_gcode(CommChannel::Internal).await,
6✔
1515
            Err(GCodeLineParserError::ParseError(0))
1516
        );
1517
    }
12✔
1518
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc