ngc/
parse.rs

1// Copyright (c) 2019-2021 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//! Parser for G-code.
8
9use std::str::FromStr;
10use itertools::Itertools;
11use pest::{Parser, Span, error::{Error, ErrorVariant}, iterators::{Pair, Pairs}};
12
13use crate::ast::*;
14use crate::util::num_to_int;
15
16// Since pest makes Rule "pub", we do this in order to not show it to crate users.
17mod parser {
18    use pest_derive::Parser;
19
20    #[derive(Parser)]
21    #[grammar = "gcode.pest"]
22    pub struct GcodeParser;
23}
24
25use self::parser::{GcodeParser, Rule};
26
27/// Result of a parsing operation.
28pub type ParseResult<T> = Result<T, Error<Rule>>;
29
30fn err(span: Span, msg: impl Into<String>) -> Error<Rule> {
31    Error::new_from_span(ErrorVariant::CustomError { message: msg.into() }, span)
32}
33
34fn parse_filtered<T: FromStr>(pair: Pair<Rule>) -> T
35    where T::Err: std::fmt::Debug
36{
37    pair.as_str().chars()
38                 .filter(|&ch| ch != ' ' && ch != '\t')
39                 .collect::<String>()
40                 .parse().expect("valid parse")
41}
42
43fn make_par_ref(pair: Pair<Rule>) -> ParseResult<ParId> {
44    let (pair,) = pair.into_inner().collect_tuple().expect("one child");
45    if pair.as_rule() == Rule::par_name {
46        Ok(ParId::Named(parse_filtered(pair)))
47    } else {
48        let expr_span = pair.as_span();
49        let expr = make_expr(pair)?;
50        if let Expr::Num(f) = expr {
51            let n = num_to_int(f, u16::max_value(),
52                               |f| err(expr_span, format!("Invalid parameter number {}", f)))?;
53            Ok(ParId::Numeric(n as u16))
54        } else {
55            Ok(ParId::Indirect(Box::new(expr)))
56        }
57    }
58}
59
60fn make_signed(sign: f64, expr: Expr) -> Expr {
61    if sign < 0. {
62        Expr::UnOp(UnOp::Minus, Box::new(expr))
63    } else {
64        expr
65    }
66}
67
68fn make_expr(expr_pair: Pair<Rule>) -> ParseResult<Expr> {
69    let mut lhs = None;
70    let mut op = None;
71    let mut sign = 1.;
72    for pair in expr_pair.into_inner() {
73        match pair.as_rule() {
74            // prefix unary op
75            Rule::op_un => if pair.as_str() == "-" { sign = -sign; },
76            // singleton inside "expr_atom" or "value"
77            Rule::expr => return Ok(make_signed(sign, make_expr(pair)?)),
78            Rule::num => return Ok(Expr::Num(sign * parse_filtered::<f64>(pair))),
79            Rule::expr_call => {
80                let (func, arg) = pair.into_inner().collect_tuple().expect("children");
81                let arg = Box::new(make_expr(arg)?);
82                let func = match func.as_str() {
83                    x if x.eq_ignore_ascii_case("ABS")    => Call::Abs(arg),
84                    x if x.eq_ignore_ascii_case("ACOS")   => Call::Acos(arg),
85                    x if x.eq_ignore_ascii_case("ASIN")   => Call::Asin(arg),
86                    x if x.eq_ignore_ascii_case("COS")    => Call::Cos(arg),
87                    x if x.eq_ignore_ascii_case("EXP")    => Call::Exp(arg),
88                    x if x.eq_ignore_ascii_case("FIX")    => Call::Fix(arg),
89                    x if x.eq_ignore_ascii_case("FUP")    => Call::Fup(arg),
90                    x if x.eq_ignore_ascii_case("ROUND")  => Call::Round(arg),
91                    x if x.eq_ignore_ascii_case("LN")     => Call::Ln(arg),
92                    x if x.eq_ignore_ascii_case("SIN")    => Call::Sin(arg),
93                    x if x.eq_ignore_ascii_case("SQRT")   => Call::Sqrt(arg),
94                    _                                     => Call::Tan(arg),
95                };
96                return Ok(make_signed(sign, Expr::Call(func)));
97            }
98            Rule::expr_exist => {
99                let (par_ref,) = pair.into_inner().collect_tuple().expect("one child");
100                return Ok(make_signed(sign, Expr::Call(Call::Exists(
101                    make_par_ref(par_ref)?
102                ))));
103            }
104            Rule::expr_atan => {
105                let (argy, argx) = pair.into_inner().collect_tuple().expect("children");
106                return Ok(make_signed(
107                    sign, Expr::Call(Call::Atan(Box::new(make_expr(argy)?),
108                                                Box::new(make_expr(argx)?)))));
109            }
110            Rule::par_ref => return Ok(make_signed(sign, Expr::Par(make_par_ref(pair)?))),
111            // rules inside (left-associative) binops
112            Rule::expr_atom |
113            Rule::expr_pow |
114            Rule::expr_mul |
115            Rule::expr_add |
116            Rule::expr_cmp => {
117                if let Some(op) = op.take() {
118                    let lhs_expr = lhs.take().expect("LHS expected before op");
119                    lhs = Some(Expr::BinOp(op, Box::new(lhs_expr), Box::new(make_expr(pair)?)));
120                } else {
121                    lhs = Some(make_expr(pair)?);
122                }
123            }
124            Rule::expr_unop => {
125                let mut inner = pair.into_inner().collect::<Vec<_>>();
126                let expr = make_expr(inner.pop().expect("children"))?;
127                let full = if inner.is_empty() || inner[0].as_str() == "+" {
128                    expr
129                } else {
130                    Expr::UnOp(UnOp::Minus, Box::new(expr))
131                };
132                if let Some(op) = op.take() {
133                    let lhs_expr = lhs.take().expect("LHS expected before op");
134                    lhs = Some(Expr::BinOp(op, Box::new(lhs_expr), Box::new(full)));
135                } else {
136                    lhs = Some(full);
137                }
138            }
139            // operators inside binops
140            Rule::op_pow => op = Some(Op::Exp),
141            Rule::op_mul => op = Some(match pair.as_str() {
142                "*" => Op::Mul, "/" => Op::Div, _ => Op::Mod,
143            }),
144            Rule::op_add => op = Some(match pair.as_str() {
145                "+" => Op::Add, _ => Op::Sub,
146            }),
147            Rule::op_cmp => op = Some(match pair.as_str() {
148                x if x.eq_ignore_ascii_case("EQ") => Op::Eq,
149                x if x.eq_ignore_ascii_case("NE") => Op::Ne,
150                x if x.eq_ignore_ascii_case("GT") => Op::Gt,
151                x if x.eq_ignore_ascii_case("GE") => Op::Ge,
152                x if x.eq_ignore_ascii_case("LT") => Op::Lt,
153                _                                 => Op::Le
154            }),
155            Rule::op_log => op = Some(match pair.as_str() {
156                x if x.eq_ignore_ascii_case("AND") => Op::And,
157                x if x.eq_ignore_ascii_case("OR")  => Op::Or,
158                _                                  => Op::Xor,
159            }),
160            _ => unreachable!()
161        }
162    }
163    Ok(lhs.expect("no children in expr?"))
164}
165
166fn make_word(pairs: Pairs<Rule>) -> ParseResult<Option<Word>> {
167    let (letter, value) = pairs.collect_tuple().expect("children");
168    let value = make_expr(value)?;
169    match letter.as_str() {
170        "o" | "O" => Err(err(letter.as_span(), "O-word control flow is not supported")),
171        "n" | "N" => Ok(None),  // line numbers are accepted but ignoredxs
172        "g" | "G" => Ok(Some(Word::Gcode(value))),
173        "m" | "M" => Ok(Some(Word::Mcode(value))),
174        "f" | "F" => Ok(Some(Word::Feed(value))),
175        "s" | "S" => Ok(Some(Word::Spindle(value))),
176        "t" | "T" => Ok(Some(Word::Tool(value))),
177        "a" | "A" => Ok(Some(Word::Arg(Arg::AxisA, value))),
178        "b" | "B" => Ok(Some(Word::Arg(Arg::AxisB, value))),
179        "c" | "C" => Ok(Some(Word::Arg(Arg::AxisC, value))),
180        "u" | "U" => Ok(Some(Word::Arg(Arg::AxisU, value))),
181        "v" | "V" => Ok(Some(Word::Arg(Arg::AxisV, value))),
182        "w" | "W" => Ok(Some(Word::Arg(Arg::AxisW, value))),
183        "x" | "X" => Ok(Some(Word::Arg(Arg::AxisX, value))),
184        "y" | "Y" => Ok(Some(Word::Arg(Arg::AxisY, value))),
185        "z" | "Z" => Ok(Some(Word::Arg(Arg::AxisZ, value))),
186        "i" | "I" => Ok(Some(Word::Arg(Arg::OffsetI, value))),
187        "j" | "J" => Ok(Some(Word::Arg(Arg::OffsetJ, value))),
188        "k" | "K" => Ok(Some(Word::Arg(Arg::OffsetK, value))),
189        "d" | "D" => Ok(Some(Word::Arg(Arg::ParamD, value))),
190        "e" | "E" => Ok(Some(Word::Arg(Arg::ParamE, value))),
191        "h" | "H" => Ok(Some(Word::Arg(Arg::ParamH, value))),
192        "l" | "L" => Ok(Some(Word::Arg(Arg::ParamL, value))),
193        "p" | "P" => Ok(Some(Word::Arg(Arg::ParamP, value))),
194        "q" | "Q" => Ok(Some(Word::Arg(Arg::ParamQ, value))),
195        "r" | "R" => Ok(Some(Word::Arg(Arg::ParamR, value))),
196        _ => unreachable!()
197    }
198}
199
200enum PercentState {
201    NotOnFirstLine,
202    OnFirstLine,
203    SeenTwice,
204}
205
206fn make_block(lineno: usize, pairs: Pairs<Rule>, pct: &mut PercentState)
207              -> ParseResult<Option<Block>> {
208    let mut block = Block { lineno, ..Block::default() };
209    for pair in pairs {
210        match pair.as_rule() {
211            Rule::word => if let Some(word) = make_word(pair.into_inner())? {
212                block.words.push(word);
213            }
214            Rule::par_assign => {
215                let (id, value) = pair.into_inner().collect_tuple().expect("children");
216                block.assignments.push(ParAssign {
217                    id: make_par_ref(id)?,
218                    value: make_expr(value)?
219                });
220            }
221            Rule::blockdel => block.blockdel = true,
222            Rule::percent => {
223                if lineno == 1 {
224                    *pct = PercentState::OnFirstLine;
225                } else if let PercentState::OnFirstLine = pct  {
226                    *pct = PercentState::SeenTwice;
227                } else {
228                    return Err(err(pair.as_span(), "percent sign missing on first line"));
229                }
230            }
231            _ => unreachable!()
232        }
233    }
234    Ok(if block.words.len() + block.assignments.len() > 0 { Some(block) } else { None })
235}
236
237/// Parse a program, coming from *filename*, consisting of the source *text*.
238///
239/// On parse error, a standard parse error from [pest] is returned.
240///
241/// [pest]: https://docs.rs/pest
242pub fn parse(filename: &str, input: &str) -> ParseResult<Program> {
243    let lines = GcodeParser::parse(Rule::file, input).map_err(|e| e.with_path(filename))?;
244    let mut prog = Program { filename: filename.into(), blocks: vec![] };
245    let mut pct = PercentState::NotOnFirstLine;
246    let mut span = None;
247    for (lineno, line) in lines.into_iter().enumerate() {
248        span = Some(line.as_span());
249        if let Some(block) = make_block(lineno + 1, line.into_inner(), &mut pct)? {
250            prog.blocks.push(block);
251        }
252        // After the second percent sign, stop program
253        if let PercentState::SeenTwice = pct {
254            // if percent signs are used, M2 is optional
255            prog.blocks.push(Block { lineno: lineno + 1,
256                                     blockdel: false,
257                                     words: vec![Word::Mcode(Expr::Num(2.0))],
258                                     assignments: vec![] });
259            break;
260        }
261    }
262    if let PercentState::OnFirstLine = pct {
263        return Err(err(span.unwrap(), "percent sign missing on last line"));
264    }
265    Ok(prog)
266}