gcode/
lib.rs

1//! A crate for parsing g-code programs, designed with embedded environments in
2//! mind.
3//!
4//! Some explicit design goals of this crate are:
5//!
6//! - **embedded-friendly:** users should be able to use this crate without
7//!   requiring access to an operating system (e.g. `#[no_std]` environments or
8//!   WebAssembly)
9//! - **deterministic memory usage:** the library can be tweaked to use no
10//!   dynamic allocation (see [`buffers::Buffers`])
11//! - **error-resistant:** erroneous input won't abort parsing, instead
12//!   notifying the caller and continuing on (see [`Callbacks`])
13//! - **performance:** parsing should be reasonably fast, guaranteeing `O(n)`
14//!   time complexity with no backtracking
15//!
16//! # Getting Started
17//!
18//! The typical entry point to this crate is via the [`parse()`] function. This
19//! gives you an iterator over the [`GCode`]s in a string of text, ignoring any
20//! errors or comments that may appear along the way.
21//!
22//! ```rust
23//! use gcode::Mnemonic;
24//!
25//! let src = r#"
26//!     G90              (absolute coordinates)
27//!     G00 X50.0 Y-10   (move somewhere)
28//! "#;
29//!
30//! let got: Vec<_> = gcode::parse(src).collect();
31//!
32//! assert_eq!(got.len(), 2);
33//!
34//! let g90 = &got[0];
35//! assert_eq!(g90.mnemonic(), Mnemonic::General);
36//! assert_eq!(g90.major_number(), 90);
37//! assert_eq!(g90.minor_number(), 0);
38//!
39//! let rapid_move = &got[1];
40//! assert_eq!(rapid_move.mnemonic(), Mnemonic::General);
41//! assert_eq!(rapid_move.major_number(), 0);
42//! assert_eq!(rapid_move.value_for('X'), Some(50.0));
43//! assert_eq!(rapid_move.value_for('y'), Some(-10.0));
44//! ```
45//!
46//! The [`full_parse_with_callbacks()`] function can be used if you want access
47//! to [`Line`] information and to be notified on any parse errors.
48//!
49//! ```rust
50//! use gcode::{Callbacks, Span};
51//!
52//! /// A custom set of [`Callbacks`] we'll use to keep track of errors.
53//! #[derive(Debug, Default)]
54//! struct Errors {
55//!     unexpected_line_number : usize,
56//!     letter_without_number: usize,
57//!     garbage: Vec<String>,
58//! }
59//!
60//! impl Callbacks for Errors {
61//!     fn unknown_content(&mut self, text: &str, _span: Span) {
62//!         self.garbage.push(text.to_string());
63//!     }
64//!
65//!     fn unexpected_line_number(&mut self, _line_number: f32, _span: Span) {
66//!         self.unexpected_line_number += 1;
67//!     }
68//!
69//!     fn letter_without_a_number(&mut self, _value: &str, _span: Span) {
70//!         self.letter_without_number += 1;
71//!     }
72//! }
73//!
74//! let src = r"
75//!     G90 N1           ; Line numbers (N) should be at the start of a line
76//!     G                ; there was a G, but no number
77//!     G01 X50 $$%# Y20 ; invalid characters are ignored
78//! ";
79//!
80//! let mut errors = Errors::default();
81//!
82//! {
83//!     let lines: Vec<_> = gcode::full_parse_with_callbacks(src, &mut errors)
84//!         .collect();
85//!     
86//!     assert_eq!(lines.len(), 3);
87//!     let total_gcodes: usize = lines.iter()
88//!         .map(|line| line.gcodes().len())
89//!         .sum();
90//!     assert_eq!(total_gcodes, 2);
91//! }
92//!
93//! assert_eq!(errors.unexpected_line_number, 1);
94//! assert_eq!(errors.letter_without_number, 1);
95//! assert_eq!(errors.garbage.len(), 1);
96//! assert_eq!(errors.garbage[0], "$$%# ");
97//! ```
98//!
99//! # Customising Memory Usage
100//!
101//! You'll need to manually create a [`Parser`] if you want control over buffer
102//! sizes instead of relying on [`buffers::DefaultBuffers`].
103//!
104//! You shouldn't normally need to do this unless you are on an embedded device
105//! and know your expected input will be bigger than
106//! [`buffers::SmallFixedBuffers`] will allow.
107//!
108//! ```rust
109//! use gcode::{Word, Comment, GCode, Nop, Parser, buffers::Buffers};
110//! use arrayvec::ArrayVec;
111//!
112//! /// A type-level variable which contains definitions for each of our buffer
113//! /// types.
114//! enum MyBuffers {}
115//!
116//! impl<'input> Buffers<'input> for MyBuffers {
117//!     type Arguments = ArrayVec<[Word; 10]>;
118//!     type Commands = ArrayVec<[GCode<Self::Arguments>; 2]>;
119//!     type Comments = ArrayVec<[Comment<'input>; 1]>;
120//! }
121//!
122//! let src = "G90 G01 X5.1";
123//!
124//! let parser: Parser<Nop, MyBuffers> = Parser::new(src, Nop);
125//!
126//! let lines = parser.count();
127//! assert_eq!(lines, 1);
128//! ```
129//!
130//! # Spans
131//!
132//! Something that distinguishes this crate from a lot of other g-code parsers
133//! is that each element's original location, its [`Span`], is retained and
134//! passed to the caller.
135//!
136//! This is important for applications such as:
137//!
138//! - Indicating where in the source text a parsing error or semantic error has
139//!   occurred
140//! - Visually highlighting the command currently being executed when stepping
141//!   through a program in a simulator
142//! - Reporting what point a CNC machine is up to when executing a job
143//!
144//! It's pretty easy to check whether something contains its [`Span`], just look
145//! for a `span()` method (e.g. [`GCode::span()`]) or a `span` field (e.g.
146//! [`Comment::span`]).
147//!
148//! # Cargo Features
149//!
150//! Additional functionality can be enabled by adding feature flags to your
151//! `Cargo.toml` file:
152//!
153//! - **std:** adds `std::error::Error` impls to any errors and switches to
154//!   `Vec` for the default backing buffers
155//! - **serde-1:** allows serializing and deserializing most types with `serde`
156#![deny(
157    bare_trait_objects,
158    elided_lifetimes_in_paths,
159    missing_copy_implementations,
160    missing_debug_implementations,
161    rust_2018_idioms,
162    unreachable_pub,
163    unsafe_code,
164    unused_qualifications,
165    unused_results,
166    variant_size_differences,
167    intra_doc_link_resolution_failure,
168    missing_docs
169)]
170#![cfg_attr(not(feature = "std"), no_std)]
171#![cfg_attr(docsrs, feature(doc_cfg))]
172
173#[cfg(all(test, not(feature = "std")))]
174#[macro_use]
175extern crate std;
176
177#[cfg(test)]
178#[macro_use]
179extern crate pretty_assertions;
180
181#[macro_use]
182mod macros;
183
184pub mod buffers;
185mod callbacks;
186mod comment;
187mod gcode;
188mod lexer;
189mod line;
190mod parser;
191mod span;
192mod words;
193
194pub use crate::{
195    callbacks::{Callbacks, Nop},
196    comment::Comment,
197    gcode::{GCode, Mnemonic},
198    line::Line,
199    parser::{full_parse_with_callbacks, parse, Parser},
200    span::Span,
201    words::Word,
202};