gcode/core/mod.rs
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
//! A zero-allocation, push-based parser for g-code.
//!
//! `gcode::core` is the low-level engine of this crate. Instead of building an
//! abstract syntax tree (AST) for you, it drives your own visitor
//! implementations and never allocates on your behalf. Use this module when
//! you need deterministic memory usage (e.g. embedded or `no_std`), when you
//! want to stream g-code and act on it as it arrives, or when you want full
//! control over how commands, numbers, and diagnostics are represented. If you
//! prefer a ready-made AST and default diagnostics, use [`crate::parse`]
//! and the [`crate::Program`] type instead; they are thin layers built on top
//! of [`crate::core`].
//!
//! ## How the API works
//!
//! The parser is built around a **visitor pattern** that mirrors the g-code
//! grammar: the parser drives the visitor, and the visitor trait hierarchy
//! matches the structure of the language. At the top level you implement
//! [`ProgramVisitor`], which creates a [`BlockVisitor`] for each block (line);
//! each block can then create a [`CommandVisitor`] for each command on that
//! line.
//!
//! ### Terminals vs non-terminals
//!
//! The grammar is reflected in the visitor API:
//!
//! - **Terminals** (leaf tokens) are reported by a single method call with the
//! value and its [`Span`]. No new visitor is created. Examples:
//! [`BlockVisitor::line_number`], [`BlockVisitor::comment`],
//! [`BlockVisitor::program_number`], [`CommandVisitor::argument`].
//!
//! - **Non-terminals** (sub-structures) are entered by a method that returns a
//! **child visitor** inside [`ControlFlow::Continue`]. The parser then drives
//! that visitor until the sub-structure ends, at which point it calls a
//! consuming method (`end_line` or `end_command`) and returns to the parent.
//! Examples: [`ProgramVisitor::start_block`] returns a [`BlockVisitor`] for
//! that line; [`BlockVisitor::start_general_code`] (and the other
//! `start_*_code` methods) return a [`CommandVisitor`] for that command.
//!
//! In other words, the call flow is:
//!
//! - parser calls `start_block()` and gets a [`BlockVisitor`]
//! - block visitor receives `line_number`, `comment`, and `start_*_code(…)`
//! - each `start_*_code(…)` returns a [`CommandVisitor`]
//! - command visitor receives one or more `argument(…)` calls and then
//! `end_command`
//! - control returns to the block visitor for more calls or `end_line`
//! - control returns to the program visitor
//!
//! The type of each level is fixed by the trait; the parser never allocates
//! intermediate nodes.
//!
//! ## Control flow and allocation
//!
//! Because each non-terminal is “enter by returning a visitor, exit by
//! consuming it”, the **caller** can implement visitors as values that only
//! borrow from their parent (for example a struct holding `&mut Vec<Block>`,
//! `&mut Diagnostics`, or other state). The parser only stores and invokes
//! whatever visitor type you supply; it does not build its own trees or
//! buffers. The entire parse can therefore be zero-allocation: no boxes, no
//! `Vec`s, no strings owned by the parser. Allocation happens only if your
//! visitor implementation chooses to allocate (for example to build an AST).
//!
//! If a visitor returns [`ControlFlow::Break`], the parser stops at a
//! well-defined pause point (for example when an output buffer is full). You
//! can resume later by calling [`resume`] with the returned [`ParserState`]
//! and the same visitor, and parsing will continue from where it left off.
//!
//! ## Diagnostics
//!
//! Recoverable diagnostics are reported via [`HasDiagnostics`]: the visitor
//! supplies a [`Diagnostics`] implementation, and the parser calls into it
//! (for example `emit_unknown_content`, `emit_unexpected`) when it encounters
//! bad input. The parser does not abort on these conditions; it reports and
//! continues, so callers can decide how to surface or aggregate errors.
//!
//! ## Relationship to the rest of the crate
//!
//! The higher-level [`crate`] module and the [`crate::parse`] convenience
//! function are implemented in terms of `gcode::core`: they provide visitors
//! that build an owned AST and collect diagnostics into a single value. As a
//! result, the behaviour of the entire crate is defined here; understanding
//! `gcode::core` gives you a precise mental model for how parsing, spans, and
//! diagnostics behave at every layer.
//!
//! Examples
//!
//! Implement [`ProgramVisitor`] to receive blocks as they are parsed. Each
//! block is handled by a [`BlockVisitor`], which in turn creates a
//! [`CommandVisitor`] for each G/M/T command.
//!
//! We don't care about errors in this example, so we use [`Noop`] as the
//! diagnostics implementation.
//!
//! ```rust
//! # #[allow(refining_impl_trait)]
//! use gcode::core::{
//! BlockVisitor, CommandVisitor, ControlFlow, HasDiagnostics, Noop,
//! Number, ProgramVisitor,
//! };
//!
//! #[derive(Default)]
//! struct Counter {
//! blocks: usize,
//! commands: usize,
//! diag: Noop,
//! }
//!
//! impl HasDiagnostics for Counter {
//! fn diagnostics(&mut self) -> &mut dyn gcode::core::Diagnostics {
//! &mut self.diag
//! }
//! }
//!
//! struct BlockCounter<'a>(&'a mut Counter);
//!
//! impl ProgramVisitor for Counter {
//! fn start_block(&mut self) -> ControlFlow<BlockCounter<'_>> {
//! self.blocks += 1;
//! ControlFlow::Continue(BlockCounter(&mut *self))
//! }
//! }
//!
//! impl HasDiagnostics for BlockCounter<'_> {
//! fn diagnostics(&mut self) -> &mut dyn gcode::core::Diagnostics {
//! &mut self.0.diag
//! }
//! }
//!
//! struct CommandCounter<'a>(&'a mut Counter);
//!
//! impl BlockVisitor for BlockCounter<'_> {
//! fn start_general_code(&mut self, _number: Number) -> ControlFlow<CommandCounter<'_>> {
//! self.0.commands += 1;
//! ControlFlow::Continue(CommandCounter(&mut *self.0))
//! }
//! fn start_miscellaneous_code(&mut self, _number: Number) -> ControlFlow<CommandCounter<'_>> {
//! self.0.commands += 1;
//! ControlFlow::Continue(CommandCounter(&mut *self.0))
//! }
//! fn start_tool_change_code(&mut self, _number: Number) -> ControlFlow<CommandCounter<'_>> {
//! self.0.commands += 1;
//! ControlFlow::Continue(CommandCounter(&mut *self.0))
//! }
//! }
//!
//! impl HasDiagnostics for CommandCounter<'_> {
//! fn diagnostics(&mut self) -> &mut dyn gcode::core::Diagnostics {
//! &mut self.0.diag
//! }
//! }
//!
//! impl CommandVisitor for CommandCounter<'_> {}
//!
//! let src = "G90\nG01 X5\nM3";
//! let mut counter = Counter::default();
//! gcode::core::parse(src, &mut counter);
//! assert_eq!(counter.blocks, 3);
//! assert_eq!(counter.commands, 3);
//! ```
mod lexer;
mod parser;
mod types;
pub use self::parser::{ParserState, resume};
pub use self::types::{
BlockVisitor, CommandVisitor, ControlFlow, Diagnostics, HasDiagnostics,
Noop, Number, ProgramVisitor, Span, TokenType, Value,
};
/// Parses `src` from start to finish, driving `visitor` for each block.
///
/// For incremental or resumable parsing, use [`ParserState::empty`] and
/// [`resume`] instead; return [`ControlFlow::Break`] from your visitor when you
/// need to pause, then call [`resume`] with the returned state.
pub fn parse(src: &str, visitor: &mut impl ProgramVisitor) {
let parser = ParserState::empty();
let _ = resume(parser, src, visitor);
}