ngc/
ast.rs

1// Copyright (c) 2019 Georg Brandl.  Licensed under the Apache License,
2// Version 2.0 <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
3// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at
4// your option. This file may not be copied, modified, or distributed except
5// according to those terms.
6
7//! Data types to represent a parsed G-code program.
8//!
9//! The syntax and semantics are described on the [G-code reference] page from
10//! LinuxCNC.
11//!
12//! [G-code reference]: http://linuxcnc.org/docs/html/gcode/overview.html
13
14use std::fmt::{self, Display, Formatter};
15
16/// A whole program, consisting of blocks.  Each block corresponds to a line in
17/// the source code.
18#[derive(Debug, Default)]
19pub struct Program {
20    /// Original filename of the program, as given to `parse()`.
21    pub filename: String,
22    /// Blocks of the program.
23    pub blocks: Vec<Block>,
24}
25
26/// A block (source line), which contains a number of words and parameter
27/// assignments.
28#[derive(Debug, Default)]
29pub struct Block {
30    /// Line number in the original file.
31    pub lineno: usize,
32    /// True if the line was "block deleted"; i.e. starts with a slash.
33    ///
34    /// Execution of these lines can be switched with a global flag.
35    pub blockdel: bool,
36    /// Words (e.g. `G0` or `X5.2`) found in the line.  The ordering is
37    /// irrelevant to G-code.
38    pub words: Vec<Word>,
39    /// Assignments (e.g. `#1=4.2`) found in the line.  The ordering, and
40    /// ordering with respect to words, are irrelevant to G-code.
41    pub assignments: Vec<ParAssign>,
42}
43
44/// A parameter assignment.
45#[derive(Debug)]
46pub struct ParAssign {
47    pub id: ParId,
48    pub value: Expr,
49}
50
51/// A reference to a parameter.
52///
53/// This can be a numeric parameter, a named parameter, or an indirect
54/// reference, where the parameter number is determined from an expression.
55#[derive(Debug)]
56pub enum ParId {
57    Named(String),
58    Numeric(u16),
59    Indirect(Box<Expr>),
60}
61
62/// A G-code expression.
63#[derive(Debug)]
64pub enum Expr {
65    /// A simple number.  G-code only knows floating-point numbers; in any place
66    /// that requires integers, a check for an integral value (or something very
67    /// close to it) is performed at execution time.
68    Num(f64),
69    /// A parameter reference.
70    Par(ParId),
71    /// A function call, with argument(s).
72    Call(Call),
73    /// An operator, with lefthand and righthand expression.
74    BinOp(Op, Box<Expr>, Box<Expr>),
75    /// An unary operator.
76    UnOp(UnOp, Box<Expr>),
77}
78
79/// A G-code "word", i.e. indication letter and value.
80///
81/// The value can be a complex expression introduced in brackets, even for `G`
82/// and `M` words.
83#[derive(Debug)]
84pub enum Word {
85    Gcode(Expr),
86    Mcode(Expr),
87    Feed(Expr),
88    Spindle(Expr),
89    Tool(Expr),
90    Arg(Arg, Expr),
91}
92
93/// The binary operators known to G-code.
94///
95/// For Boolean inputs, all nonzero numbers are true.  Boolean results are
96/// represented as 0.0 and 1.0.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum Op {
99    Exp,
100    Mul,
101    Div,
102    Mod,
103    Add,
104    Sub,
105    Eq,
106    Ne,
107    Gt,
108    Ge,
109    Lt,
110    Le,
111    And,
112    Or,
113    Xor,
114}
115
116/// The unary operators known to G-code.
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum UnOp {
119    Plus,
120    Minus,
121}
122
123/// A function call, with all functions known to G-code.
124#[derive(Debug)]
125pub enum Call {
126    Exists(ParId),
127    Atan(Box<Expr>, Box<Expr>),
128    Abs(Box<Expr>),
129    Acos(Box<Expr>),
130    Asin(Box<Expr>),
131    Cos(Box<Expr>),
132    Exp(Box<Expr>),
133    Fix(Box<Expr>),
134    Fup(Box<Expr>),
135    Round(Box<Expr>),
136    Ln(Box<Expr>),
137    Sin(Box<Expr>),
138    Sqrt(Box<Expr>),
139    Tan(Box<Expr>),
140}
141
142/// The possible argument words (i.e. all words except N, G, M, F, S, T).
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum Arg {
145    // axis words
146    AxisA,
147    AxisB,
148    AxisC,
149    AxisU,
150    AxisV,
151    AxisW,
152    AxisX,
153    AxisY,
154    AxisZ,
155    // arc offset parameters
156    OffsetI,
157    OffsetJ,
158    OffsetK,
159    // variable meaning params
160    ParamD,
161    ParamE,
162    ParamH,
163    ParamL,
164    ParamP,
165    ParamQ,
166    ParamR,
167}
168
169fn wrap_op(f: &mut Formatter, ex: &Expr) -> fmt::Result {
170    if let Expr::BinOp(..) = ex {
171        write!(f, "[{}]", ex)
172    } else {
173        Display::fmt(&ex, f)
174    }
175}
176
177
178impl Display for Program {
179    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
180        for block in &self.blocks {
181            write!(f, "{}\n", block)?;
182        }
183        Ok(())
184    }
185}
186
187impl Display for Block {
188    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
189        if self.blockdel {
190            write!(f, "/ ")?;
191        }
192        for ass in &self.assignments {
193            write!(f, "{} ", ass)?;
194        }
195        for word in &self.words {
196            write!(f, "{} ", word)?;
197        }
198        Ok(())
199    }
200}
201
202impl Display for ParAssign {
203    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
204        // LinuxCNC requires function calls to be parenthesized here, although
205        // it doesn't require it for parameter references elsewhere.
206        if let ParId::Indirect(id) = &self.id {
207            if let Expr::Call(..) = **id {
208                write!(f, "#[{}]=", self.id)?;
209                return wrap_op(f, &self.value);
210            }
211        }
212        write!(f, "#{}=", self.id)?;
213        wrap_op(f, &self.value)
214    }
215}
216
217impl Display for ParId {
218    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
219        match self {
220            ParId::Numeric(n) => Display::fmt(n, f),
221            ParId::Named(n) => write!(f, "<{}>", n),
222            ParId::Indirect(ex) => wrap_op(f, ex),
223        }
224    }
225}
226
227impl Display for Expr {
228    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
229        match self {
230            Expr::Num(n) => Display::fmt(n, f),
231            Expr::Par(id) => write!(f, "#{}", id),
232            Expr::Call(func) => Display::fmt(func, f),
233            Expr::BinOp(op, lhs, rhs) => {
234                wrap_op(f, lhs)?;
235                write!(f, " {} ", op)?;
236                wrap_op(f, rhs)
237            }
238            Expr::UnOp(op, rhs) => {
239                Display::fmt(op, f)?;
240                wrap_op(f, rhs)
241            }
242        }
243    }
244}
245
246impl Display for Op {
247    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
248        f.write_str(match self {
249            Op::Exp => "**",
250            Op::Mul => "*",
251            Op::Div => "/",
252            Op::Mod => "MOD",
253            Op::Add => "+",
254            Op::Sub => "-",
255            Op::Eq  => "EQ",
256            Op::Ne  => "NE",
257            Op::Gt  => "GT",
258            Op::Ge  => "GE",
259            Op::Lt  => "LT",
260            Op::Le  => "LE",
261            Op::And => "AND",
262            Op::Or  => "OR",
263            Op::Xor => "XOR",
264        })
265    }
266}
267
268impl Display for UnOp {
269    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
270        f.write_str(match self {
271            UnOp::Plus => "+",
272            UnOp::Minus => "-",
273        })
274    }
275}
276
277impl Display for Word {
278    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
279        match self {
280            Word::Gcode(n)   => { f.write_str("G")?; wrap_op(f, n) },
281            Word::Mcode(n)   => { f.write_str("M")?; wrap_op(f, n) },
282            Word::Feed(n)    => { f.write_str("F")?; wrap_op(f, n) },
283            Word::Spindle(n) => { f.write_str("S")?; wrap_op(f, n) },
284            Word::Tool(n)    => { f.write_str("T")?; wrap_op(f, n) },
285            Word::Arg(a, n)  => { Display::fmt(a, f)?; wrap_op(f, n) },
286        }
287    }
288}
289
290impl Display for Arg {
291    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
292        f.write_str(match self {
293            Arg::AxisA => "A",
294            Arg::AxisB => "B",
295            Arg::AxisC => "C",
296            Arg::AxisU => "U",
297            Arg::AxisV => "V",
298            Arg::AxisW => "W",
299            Arg::AxisX => "X",
300            Arg::AxisY => "Y",
301            Arg::AxisZ => "Z",
302            Arg::OffsetI => "I",
303            Arg::OffsetJ => "J",
304            Arg::OffsetK => "K",
305            Arg::ParamD => "D",
306            Arg::ParamE => "E",
307            Arg::ParamH => "H",
308            Arg::ParamL => "L",
309            Arg::ParamP => "P",
310            Arg::ParamQ => "Q",
311            Arg::ParamR => "R",
312        })
313    }
314}
315
316impl Display for Call {
317    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
318        match self {
319            Call::Atan(arg1, arg2) => write!(f, "ATAN[{}]/[{}]", arg1, arg2),
320            Call::Exists(par)      => write!(f, "EXISTS[#{}]", par),
321            Call::Abs(arg)         => write!(f, "ABS[{}]", arg),
322            Call::Acos(arg)        => write!(f, "ACOS[{}]", arg),
323            Call::Asin(arg)        => write!(f, "ASIN[{}]", arg),
324            Call::Cos(arg)         => write!(f, "COS[{}]", arg),
325            Call::Exp(arg)         => write!(f, "EXP[{}]", arg),
326            Call::Fix(arg)         => write!(f, "FIX[{}]", arg),
327            Call::Fup(arg)         => write!(f, "FUP[{}]", arg),
328            Call::Round(arg)       => write!(f, "ROUND[{}]", arg),
329            Call::Ln(arg)          => write!(f, "LN[{}]", arg),
330            Call::Sin(arg)         => write!(f, "SIN[{}]", arg),
331            Call::Sqrt(arg)        => write!(f, "SQRT[{}]", arg),
332            Call::Tan(arg)         => write!(f, "TAN[{}]", arg),
333        }
334    }
335}