1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! This crate implements a GCode (RS-274) parser.
//!
//! The default dialect is taken from NIST's [RS274/NGC interpreter version 3].
//! Some expensive part of that dialect such as parameters and expressions are gated behind feature
//! – respectively `parse-parameters` and `parse-expressions` – in order to avoid dependency on
//! dynamic allocation.
//!
//! Some extension to this dialect such as checksum, trailing comments, optional values are also
//! supported and gated behind features.
//!
//! ## 🔩 Example
//!
//! ```
//! use futures::stream;
//! use futures_executor::block_on;
//! use async_gcode::{Parser, Error};
//! let input = r"
//!    G21 H21. I21.098
//!    J-21 K-21. L-21.098
//!    M+21 N+21. P+21.098
//!    Q.098 R-.098 S+.098
//!    t - 21 . 33 "
//!    // mapping to `Result<u8, Error>` to render error conversion transparent.
//!    .bytes().map(Result::<_, Error>::Ok);
//!
//! block_on(async {
//!     let mut parser = Parser::new(stream::iter(input));
//!
//!     loop {
//!         if let Some(res) = parser.next().await {
//!             println!("{:?}", res);
//!         } else {
//!             break;
//!         }
//!     }
//! });
//! ```
//!
//! ## Erorr management
//!
//! On parsing error the `Parser` can no longer trust its input and enters an error recovery state.
//! No `GCode` or `Error` will be emitted until a new line character (`\n`) is received. Then a
//! `GCode::Execute` is emitted and the parser restarts in a reliable known state.
//!
//! ## ⚙ Features
//! - `std` : Enabled by default. Allows for the use of dynamic allocation.
//! - `parse-comments` : enables the parser to return `GCode::Comment(String)`; requires an allocator.
//! - `parse-trailing-comment`: allows line to end with a `; comment`.
//! - `parse-checksum` : Enables the use of xorsum.
//! - `parse-parameters` : Enables the use of `#` parameters ; requires an allocator.
//!   If `string-value` is enabled then parameters may use string index.
//!   If `optional-value` is enabled then parameters value may be omitted but **NOT** the indices.
//! - `parse-expressions` : Enables parsing infix expressions ; requires an allocator.
//! - `optional-value` : Allows to omit in `RealValue` in word and parameter value positions.
//!   Parameter indices cannot be omitted nor can be literals in expressions.
//! - `string-value` : Allows `RealValue` to be a string. Any character preceded with `\` will be
//!   used as is (useful for `"`).
//!
//! ## ⚠ Warning
//!
//! Dev-dependencies currently leak features to dependencies.
//! This crate requires rust-nightly to build with no_std until `-Z features=dev_dep` makes it to
//! stable.
//!
//! ## Note for future development
//! It might be interesting to have a look at ISO 6983 and/or ISO 14649.
//!
//! During development fixed arithmetics was considered to be made available as an alternative to
//! the floating point arithmetics especially for small target. After investigation, it does not
//! seem to be a significant gain for the precision required by the gcode standard.
//!
//! Ideally all arithmetics triggered by a theoritically valid input should be caught and not
//! trigger a panic. For an excessive  number of digit in a number may exceed the capacity of the
//! variable used internally.
//!
//! [RS274/NGC interpreter version 3]: https://www.nist.gov/publications/nist-rs274ngc-interpreter-version-3?pub_id=823374
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(all(
    not(feature = "std"),
    any(
        feature = "parse-expressions",
        feature = "parse-parameters",
        feature = "parse-comments",
        feature = "string-value"
    )
))]
extern crate alloc;

#[cfg(all(not(feature = "std"), feature = "parse-comments"))]
use alloc::string::String;

#[macro_use]
mod utils;

mod stream;
mod types;

mod parser;

pub use parser::Parser;
pub use types::Literal;
pub use types::RealValue;

#[cfg(any(feature = "parse-expressions", feature = "parse-parameters"))]
pub use types::expressions::Expression;

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Error {
    /// Error no the gcode syntax
    UnexpectedByte(u8),

    /// The parsed number excedded the expected range.
    NumberOverflow,

    /// Format error during number parsing. Typically a dot without digits (at least one is
    /// required).
    BadNumberFormat,

    #[cfg(any(feature = "parse-comments", feature = "string-value"))]
    /// The string or comment received contained an invalid UTF-8 character sequence.
    InvalidUTF8String,

    #[cfg(feature = "parse-checksum")]
    /// Checksum verification failed. The error contains the computed value.
    BadChecksum(u8),

    #[cfg(feature = "parse-expressions")]
    /// The expressions received was invalid.
    InvalidExpression,
}

#[derive(Debug, PartialEq, Clone)]
pub enum GCode {
    BlockDelete,
    LineNumber(u32),
    #[cfg(feature = "parse-comments")]
    Comment(String),
    Word(char, RealValue),
    #[cfg(feature = "parse-parameters")]
    /// When `optional-value` is enabled, the index cannot be `RealValue::None`.
    ParameterSet(RealValue, RealValue),
    Execute,
}