Expand description
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_lineorend_command) and returns to the parent. Examples:ProgramVisitor::start_blockreturns aBlockVisitorfor that line;BlockVisitor::start_general_code(and the otherstart_*_codemethods) return aCommandVisitorfor that command.
In other words, the call flow is:
- parser calls
start_block()and gets aBlockVisitor - block visitor receives
line_number,comment, andstart_*_code(…) - each
start_*_code(…)returns aCommandVisitor - command visitor receives one or more
argument(…)calls and thenend_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
Vecs, 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.
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);Structs§
- Noop
- A no-op visitor that ignores all callbacks.
- Number
- A g-code command number (G, M, T): fixed-point decimal premultiplied by 10.
E.g.
G91.1→Number::new_with_minor(91, 1). Line numbers (N) and program numbers (O) useu32; seeBlockVisitor::line_numberandBlockVisitor::program_number. - Parser
State - Opaque state for pausing and resuming a parse.
- Span
- A half-open range indicating where an element appears in the source text.
Enums§
- Token
Type - Lexer token kind. Used by the parser and in
Diagnostics::emit_unexpectedto report what was expected. - Value
- The value of a g-code argument: a numeric literal or a variable reference.
Traits§
- Block
Visitor - Visitor for a single block (line) of g-code.
- Command
Visitor - Visitor for a single command (one G, M, O, or T code and its arguments).
- Diagnostics
- Callbacks invoked by the parser when it encounters invalid or unexpected input.
- HasDiagnostics
- Allows the parser to obtain a
Diagnosticsimplementation from a visitor. - Program
Visitor - Top-level visitor: one implementation per parse, created by the caller and
passed to
parseorresume.
Functions§
- parse
- Parses
srcfrom start to finish, drivingvisitorfor each block. - resume
- Resumes parsing from a saved
ParserState.
Type Aliases§
- Control
Flow - Return type for visitor methods that may either continue with a child visitor or pause parsing. See the module-level docs for the control-flow model.