Skip to main content

Module core

Module core 

Source
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:

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 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.1Number::new_with_minor(91, 1). Line numbers (N) and program numbers (O) use u32; see BlockVisitor::line_number and BlockVisitor::program_number.
ParserState
Opaque state for pausing and resuming a parse.
Span
A half-open range indicating where an element appears in the source text.

Enums§

TokenType
Lexer token kind. Used by the parser and in Diagnostics::emit_unexpected to report what was expected.
Value
The value of a g-code argument: a numeric literal or a variable reference.

Traits§

BlockVisitor
Visitor for a single block (line) of g-code.
CommandVisitor
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 Diagnostics implementation from a visitor.
ProgramVisitor
Top-level visitor: one implementation per parse, created by the caller and passed to parse or resume.

Functions§

parse
Parses src from start to finish, driving visitor for each block.
resume
Resumes parsing from a saved ParserState.

Type Aliases§

ControlFlow
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.