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, };