Skip to main content

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 the [`crate::core`] module)
11//! - **error-resistant:** erroneous input won't abort parsing, instead
12//!   notifying the caller and continuing on (see [`crate::core::Diagnostics`])
13//! - **performance:** parsing should be reasonably fast, guaranteeing `O(n)`
14//!   time complexity with no backtracking
15//!
16//! # Getting Started
17//!
18//! ## Simple parsing (with `alloc`)
19//!
20//! With the [`alloc`] feature (enabled by default), use [`parse`] to get a
21//! [`Program`] and any [`Diagnostics`]. You can then walk [`Block`]s and
22//! inspect [`Code`]s (e.g. [`Code::General`]) and their [`Argument`]s.
23//!
24//! ```rust
25//! # #[cfg(feature = "alloc")]
26//! # fn main() -> Result<(), gcode::ast::Diagnostics> {
27//! # use std::collections::HashMap;
28//! use gcode::ast::{Code, Value};
29//!
30//! let src = "G90 (absolute)\nG00 X50.0 Y-10";
31//! let result = gcode::parse(src)?;
32//!
33//! let program = result;
34//! assert!(program.blocks.len() >= 1);
35//!
36//! for block in &program.blocks {
37//!     for code in &block.codes {
38//!         if let Code::General(g) = code {
39//!             let args: HashMap<char, _> = g.args.iter()
40//!                 .map(|a| (a.letter, a.value.clone()))
41//!                 .collect();
42//!             let Some(x) = args.get(&'X') else { continue; };
43//!             let Some(y) = args.get(&'Y') else { continue; };
44//!             assert_eq!(x, &Value::Literal(50.0));
45//!             assert_eq!(y, &Value::Literal(-10.0));
46//!         }
47//!     }
48//! }
49//! # Ok(())
50//! # }
51//! # #[cfg(not(feature = "alloc"))] fn main() {}
52//! ```
53//!
54//! Parse errors are collected as [`Diagnostic`]s. The [`parse`] function fails
55//! if any parse errors were emitted.
56//!
57//! ## Push-based / zero-allocation parsing
58//!
59//! The [`core`] module is designed guaranteed to parse without requiring any
60//! heap allocations.
61//!
62//! Implement
63//! [`ProgramVisitor`](crate::core::ProgramVisitor): the parser calls
64//! [`ProgramVisitor::start_block`](crate::core::ProgramVisitor::start_block) and
65//! you return a [`ControlFlow::Continue`](crate::core::ControlFlow) with a
66//! [`BlockVisitor`](crate::core::BlockVisitor). That visitor receives
67//! [`BlockVisitor::line_number`](crate::core::BlockVisitor::line_number),
68//! [`BlockVisitor::comment`](crate::core::BlockVisitor::comment), and
69//! [`BlockVisitor::start_general_code`](crate::core::BlockVisitor::start_general_code)
70//! (and similar for M/O/T), returning a
71//! [`CommandVisitor`](crate::core::CommandVisitor) for each command. See
72//! [`crate::core`] for the full visitor model and [`resume`](crate::core::resume)
73//! for pause/resume.
74//!
75//! ```rust
76//! use gcode::core::{
77//!     BlockVisitor, CommandVisitor, ControlFlow, Noop, ProgramVisitor,
78//! };
79//!
80//! let src = "G90 G01 X5";
81//! gcode::core::parse(src, &mut Noop);
82//! ```
83//!
84//! # Zero allocation
85//!
86//! To avoid dynamic allocation, do not enable the `alloc` feature and do not
87//! use the [`parse`] function (which builds an AST). Implement the
88//! [`ProgramVisitor`](crate::core::ProgramVisitor),
89//! [`BlockVisitor`](crate::core::BlockVisitor), and
90//! [`CommandVisitor`](crate::core::CommandVisitor) traits and pass your visitor
91//! to [`core::parse`]; the parser drives your visitor and
92//! does not allocate.
93//!
94//! # Spans
95//!
96//! Each element's original location in the source is retained as a
97//! [`Span`](crate::core::Span).
98//!
99//! This supports:
100//!
101//! - Showing where a parsing or semantic error occurred
102//! - Highlighting the current command when stepping through a program
103//! - Reporting progress (e.g. line/column) to the user or machine
104//!
105//! In the core API, visitor methods receive a `Span` (e.g.
106//! [`BlockVisitor::line_number`](crate::core::BlockVisitor::line_number) and
107//! [`BlockVisitor::comment`](crate::core::BlockVisitor::comment)). AST types
108//! (with `alloc`) have a `span` field (e.g. [`Block::span`], [`Comment::span`],
109//! [`GeneralCode::span`], [`Argument::span`]).
110//!
111//! ## Feature Flags
112//!
113#![doc = document_features::document_features!()]
114#![deny(
115    bare_trait_objects,
116    elided_lifetimes_in_paths,
117    missing_copy_implementations,
118    missing_debug_implementations,
119    rust_2018_idioms,
120    unreachable_pub,
121    unsafe_code,
122    unused_qualifications,
123    unused_results,
124    variant_size_differences,
125    // rustdoc::broken_intra_doc_links,
126    missing_docs
127)]
128#![cfg_attr(not(test), no_std)]
129// Make sure docs indicate when something is hidden behind a feature flag
130#![cfg_attr(feature = "unstable-doc-cfg", feature(doc_cfg))]
131#![cfg_attr(feature = "unstable-doc-cfg", doc(auto_cfg))]
132
133#[cfg(feature = "alloc")]
134extern crate alloc;
135
136#[cfg(feature = "alloc")]
137pub mod ast;
138
139pub mod core;
140
141#[cfg(feature = "alloc")]
142pub use crate::ast::parse;
143
144#[cfg(all(doc, feature = "alloc"))]
145use crate::ast::*;
146
147#[cfg(feature = "alloc")]
148#[doc = include_str!("../README.md")]
149#[doc(hidden)]
150pub fn _assert_readme_code_examples_compile() {}