1use 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
16mod 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
27pub 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 Rule::op_un => if pair.as_str() == "-" { sign = -sign; },
76 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 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 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), "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
237pub 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 if let PercentState::SeenTwice = pct {
254 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}