Skip to main content

pretty_print_visitor/
pretty_print_visitor.rs

1//! Example: custom visitor that pretty-prints a G-Code program.
2//!
3//! This implements the core visitor traits (`ProgramVisitor`, `BlockVisitor`,
4//! `CommandVisitor`) and is driven by `gcode::core::parse`. As the parser
5//! encounters blocks, line numbers, comments, and commands, it calls into
6//! this visitor, which formats them into a single string. No AST is built.
7
8use std::fmt::Write;
9
10use gcode::core::{
11    BlockVisitor, CommandVisitor, ControlFlow, Diagnostics, HasDiagnostics,
12    Number, ProgramVisitor, Span, Value,
13};
14
15/// Top-level visitor: owns the output buffer and delegates each block to a block visitor.
16struct PrettyPrinter<'a> {
17    output: &'a mut String,
18    diagnostics: NoopDiagnostics,
19}
20
21struct NoopDiagnostics;
22
23impl Diagnostics for NoopDiagnostics {}
24
25impl HasDiagnostics for PrettyPrinter<'_> {
26    fn diagnostics(&mut self) -> &mut dyn Diagnostics {
27        &mut self.diagnostics
28    }
29}
30
31impl ProgramVisitor for PrettyPrinter<'_> {
32    fn start_block(&mut self) -> ControlFlow<impl BlockVisitor + '_> {
33        ControlFlow::Continue(PrettyPrintBlock {
34            output: self.output,
35            current_line: String::new(),
36            diagnostics: &mut self.diagnostics,
37        })
38    }
39}
40
41/// Block visitor: builds one line of output, then appends it (with newline) in `end_line`.
42struct PrettyPrintBlock<'a> {
43    output: &'a mut String,
44    current_line: String,
45    diagnostics: &'a mut NoopDiagnostics,
46}
47
48impl PrettyPrintBlock<'_> {
49    fn space_if_needed(&mut self) {
50        if !self.current_line.is_empty() {
51            self.current_line.push(' ');
52        }
53    }
54}
55
56impl HasDiagnostics for PrettyPrintBlock<'_> {
57    fn diagnostics(&mut self) -> &mut dyn Diagnostics {
58        self.diagnostics
59    }
60}
61
62impl BlockVisitor for PrettyPrintBlock<'_> {
63    fn line_number(&mut self, n: u32, _span: Span) {
64        self.space_if_needed();
65        write!(self.current_line, "N{}", n).unwrap();
66    }
67
68    fn comment(&mut self, value: &str, _span: Span) {
69        self.space_if_needed();
70        self.current_line.push_str(value);
71    }
72
73    fn program_number(&mut self, number: u32, _span: Span) {
74        self.space_if_needed();
75        write!(self.current_line, "O{}", number).unwrap();
76    }
77
78    fn program_delimiter(&mut self, _span: Span) {
79        self.space_if_needed();
80        self.current_line.push('%');
81    }
82
83    fn word_address(&mut self, letter: char, value: Value<'_>, _span: Span) {
84        self.space_if_needed();
85        self.current_line.push(letter);
86        write!(self.current_line, "{}", value).unwrap();
87    }
88
89    fn start_general_code(
90        &mut self,
91        number: Number,
92    ) -> ControlFlow<impl CommandVisitor + '_> {
93        self.space_if_needed();
94        write!(self.current_line, "G{}", number).unwrap();
95        ControlFlow::Continue(PrettyPrintCommand {
96            line: &mut self.current_line,
97            diagnostics: self.diagnostics,
98        })
99    }
100
101    fn start_miscellaneous_code(
102        &mut self,
103        number: Number,
104    ) -> ControlFlow<impl CommandVisitor + '_> {
105        self.space_if_needed();
106        write!(self.current_line, "M{}", number).unwrap();
107        ControlFlow::Continue(PrettyPrintCommand {
108            line: &mut self.current_line,
109            diagnostics: self.diagnostics,
110        })
111    }
112
113    fn start_tool_change_code(
114        &mut self,
115        number: Number,
116    ) -> ControlFlow<impl CommandVisitor + '_> {
117        self.space_if_needed();
118        write!(self.current_line, "T{}", number).unwrap();
119        ControlFlow::Continue(PrettyPrintCommand {
120            line: &mut self.current_line,
121            diagnostics: self.diagnostics,
122        })
123    }
124
125    fn end_line(self, _span: Span) {
126        self.output.push_str(&self.current_line);
127        self.output.push('\n');
128    }
129}
130
131/// Command visitor: appends each argument (e.g. ` X10.5`, ` Y#1`) to the block's line.
132struct PrettyPrintCommand<'a> {
133    line: &'a mut String,
134    diagnostics: &'a mut NoopDiagnostics,
135}
136
137impl HasDiagnostics for PrettyPrintCommand<'_> {
138    fn diagnostics(&mut self) -> &mut dyn Diagnostics {
139        self.diagnostics
140    }
141}
142
143impl CommandVisitor for PrettyPrintCommand<'_> {
144    fn argument(&mut self, letter: char, value: Value<'_>, _span: Span) {
145        self.line.push(' ');
146        self.line.push(letter);
147        write!(self.line, "{}", value).unwrap();
148    }
149
150    fn end_command(self, _span: Span) {}
151}
152
153fn main() {
154    let src = r"
155N10 G21 G90 (metric, absolute)
156N20 G00 X50.0 Y-10.0
157N30 M03 S12000
158N40 G01 X1.5 Y-0.25 F100
159";
160    let mut output = String::new();
161    let mut visitor = PrettyPrinter {
162        output: &mut output,
163        diagnostics: NoopDiagnostics,
164    };
165    gcode::core::parse(src, &mut visitor);
166    println!("Pretty-printed program:\n{}", output);
167}