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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! A crate for parsing g-code programs, designed with embedded environments in
//! mind.
//!
//! Some explicit design goals of this crate are:
//!
//! - **embedded-friendly:** users should be able to use this crate without
//!   requiring access to an operating system (e.g. `#[no_std]` environments or
//!   WebAssembly)
//! - **deterministic memory usage:** the library can be tweaked to use no
//!   dynamic allocation (see [`buffers::Buffers`])
//! - **error-resistant:** erroneous input won't abort parsing, instead
//!   notifying the caller and continuing on (see [`Callbacks`])
//! - **performance:** parsing should be reasonably fast, guaranteeing `O(n)`
//!   time complexity with no backtracking
//!
//! # Getting Started
//!
//! The typical entry point to this crate is via the [`parse()`] function. This
//! gives you an iterator over the [`GCode`]s in a string of text, ignoring any
//! errors or comments that may appear along the way.
//!
//! ```rust
//! use gcode::Mnemonic;
//!
//! let src = r#"
//!     G90              (absolute coordinates)
//!     G00 X50.0 Y-10   (move somewhere)
//! "#;
//!
//! let got: Vec<_> = gcode::parse(src).collect();
//!
//! assert_eq!(got.len(), 2);
//!
//! let g90 = &got[0];
//! assert_eq!(g90.mnemonic(), Mnemonic::General);
//! assert_eq!(g90.major_number(), 90);
//! assert_eq!(g90.minor_number(), 0);
//!
//! let rapid_move = &got[1];
//! assert_eq!(rapid_move.mnemonic(), Mnemonic::General);
//! assert_eq!(rapid_move.major_number(), 0);
//! assert_eq!(rapid_move.value_for('X'), Some(50.0));
//! assert_eq!(rapid_move.value_for('y'), Some(-10.0));
//! ```
//!
//! The [`full_parse_with_callbacks()`] function can be used if you want access
//! to [`Line`] information and to be notified on any parse errors.
//!
//! ```rust
//! use gcode::{Callbacks, Span};
//!
//! /// A custom set of [`Callbacks`] we'll use to keep track of errors.
//! #[derive(Debug, Default)]
//! struct Errors {
//!     unexpected_line_number : usize,
//!     letter_without_number: usize,
//!     garbage: Vec<String>,
//! }
//!
//! impl Callbacks for Errors {
//!     fn unknown_content(&mut self, text: &str, _span: Span) {
//!         self.garbage.push(text.to_string());
//!     }
//!
//!     fn unexpected_line_number(&mut self, _line_number: f32, _span: Span) {
//!         self.unexpected_line_number += 1;
//!     }
//!
//!     fn letter_without_a_number(&mut self, _value: &str, _span: Span) {
//!         self.letter_without_number += 1;
//!     }
//! }
//!
//! let src = r"
//!     G90 N1           ; Line numbers (N) should be at the start of a line
//!     G                ; there was a G, but no number
//!     G01 X50 $$%# Y20 ; invalid characters are ignored
//! ";
//!
//! let mut errors = Errors::default();
//!
//! {
//!     let lines: Vec<_> = gcode::full_parse_with_callbacks(src, &mut errors)
//!         .collect();
//!     
//!     assert_eq!(lines.len(), 3);
//!     let total_gcodes: usize = lines.iter()
//!         .map(|line| line.gcodes().len())
//!         .sum();
//!     assert_eq!(total_gcodes, 2);
//! }
//!
//! assert_eq!(errors.unexpected_line_number, 1);
//! assert_eq!(errors.letter_without_number, 1);
//! assert_eq!(errors.garbage.len(), 1);
//! assert_eq!(errors.garbage[0], "$$%# ");
//! ```
//!
//! # Customising Memory Usage
//!
//! You'll need to manually create a [`Parser`] if you want control over buffer
//! sizes instead of relying on [`buffers::DefaultBuffers`].
//!
//! You shouldn't normally need to do this unless you are on an embedded device
//! and know your expected input will be bigger than
//! [`buffers::SmallFixedBuffers`] will allow.
//!
//! ```rust
//! use gcode::{Word, Comment, GCode, Nop, Parser, buffers::Buffers};
//! use arrayvec::ArrayVec;
//!
//! /// A type-level variable which contains definitions for each of our buffer
//! /// types.
//! enum MyBuffers {}
//!
//! impl<'input> Buffers<'input> for MyBuffers {
//!     type Arguments = ArrayVec<[Word; 10]>;
//!     type Commands = ArrayVec<[GCode<Self::Arguments>; 2]>;
//!     type Comments = ArrayVec<[Comment<'input>; 1]>;
//! }
//!
//! let src = "G90 G01 X5.1";
//!
//! let parser: Parser<Nop, MyBuffers> = Parser::new(src, Nop);
//!
//! let lines = parser.count();
//! assert_eq!(lines, 1);
//! ```
//!
//! # Spans
//!
//! Something that distinguishes this crate from a lot of other g-code parsers
//! is that each element's original location, its [`Span`], is retained and
//! passed to the caller.
//!
//! This is important for applications such as:
//!
//! - Indicating where in the source text a parsing error or semantic error has
//!   occurred
//! - Visually highlighting the command currently being executed when stepping
//!   through a program in a simulator
//! - Reporting what point a CNC machine is up to when executing a job
//!
//! It's pretty easy to check whether something contains its [`Span`], just look
//! for a `span()` method (e.g. [`GCode::span()`]) or a `span` field (e.g.
//! [`Comment::span`]).
//!
//! # Cargo Features
//!
//! Additional functionality can be enabled by adding feature flags to your
//! `Cargo.toml` file:
//!
//! - **std:** adds `std::error::Error` impls to any errors and switches to
//!   `Vec` for the default backing buffers
//! - **serde-1:** allows serializing and deserializing most types with `serde`
#![deny(
    bare_trait_objects,
    elided_lifetimes_in_paths,
    missing_copy_implementations,
    missing_debug_implementations,
    rust_2018_idioms,
    unreachable_pub,
    unsafe_code,
    unused_qualifications,
    unused_results,
    variant_size_differences,
    intra_doc_link_resolution_failure,
    missing_docs
)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]

#[cfg(all(test, not(feature = "std")))]
#[macro_use]
extern crate std;

#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;

#[macro_use]
mod macros;

pub mod buffers;
mod callbacks;
mod comment;
mod gcode;
mod lexer;
mod line;
mod parser;
mod span;
mod words;

pub use crate::{
    callbacks::{Callbacks, Nop},
    comment::Comment,
    gcode::{GCode, Mnemonic},
    line::Line,
    parser::{full_parse_with_callbacks, parse, Parser},
    span::Span,
    words::Word,
};