use std::str::FromStr;
use itertools::Itertools;
use pest::{Parser, Span, error::{Error, ErrorVariant}, iterators::{Pair, Pairs}};
use crate::ast::*;
use crate::util::num_to_int;
mod parser {
use pest_derive::Parser;
#[derive(Parser)]
#[grammar = "gcode.pest"]
pub struct GcodeParser;
}
use self::parser::{GcodeParser, Rule};
pub type ParseResult<T> = Result<T, Error<Rule>>;
fn err(span: Span, msg: impl Into<String>) -> Error<Rule> {
Error::new_from_span(ErrorVariant::CustomError { message: msg.into() }, span)
}
fn parse_filtered<T: FromStr>(pair: Pair<Rule>) -> T
where T::Err: std::fmt::Debug
{
pair.as_str().chars()
.filter(|&ch| ch != ' ' && ch != '\t')
.collect::<String>()
.parse().expect("valid parse")
}
fn make_par_ref(pair: Pair<Rule>) -> ParseResult<ParId> {
let (pair,) = pair.into_inner().collect_tuple().expect("one child");
if pair.as_rule() == Rule::par_name {
Ok(ParId::Named(parse_filtered(pair)))
} else {
let expr_span = pair.as_span();
let expr = make_expr(pair)?;
if let Expr::Num(f) = expr {
let n = num_to_int(f, u16::max_value(),
|f| err(expr_span, format!("Invalid parameter number {}", f)))?;
Ok(ParId::Numeric(n as u16))
} else {
Ok(ParId::Indirect(Box::new(expr)))
}
}
}
fn make_signed(sign: f64, expr: Expr) -> Expr {
if sign < 0. {
Expr::UnOp(UnOp::Minus, Box::new(expr))
} else {
expr
}
}
fn make_expr(expr_pair: Pair<Rule>) -> ParseResult<Expr> {
let mut lhs = None;
let mut op = None;
let mut sign = 1.;
for pair in expr_pair.into_inner() {
match pair.as_rule() {
Rule::op_un => if pair.as_str() == "-" { sign = -sign; },
Rule::expr => return Ok(make_signed(sign, make_expr(pair)?)),
Rule::num => return Ok(Expr::Num(sign * parse_filtered::<f64>(pair))),
Rule::expr_call => {
let (func, arg) = pair.into_inner().collect_tuple().expect("children");
let arg = Box::new(make_expr(arg)?);
let func = match func.as_str() {
x if x.eq_ignore_ascii_case("ABS") => Call::Abs(arg),
x if x.eq_ignore_ascii_case("ACOS") => Call::Acos(arg),
x if x.eq_ignore_ascii_case("ASIN") => Call::Asin(arg),
x if x.eq_ignore_ascii_case("COS") => Call::Cos(arg),
x if x.eq_ignore_ascii_case("EXP") => Call::Exp(arg),
x if x.eq_ignore_ascii_case("FIX") => Call::Fix(arg),
x if x.eq_ignore_ascii_case("FUP") => Call::Fup(arg),
x if x.eq_ignore_ascii_case("ROUND") => Call::Round(arg),
x if x.eq_ignore_ascii_case("LN") => Call::Ln(arg),
x if x.eq_ignore_ascii_case("SIN") => Call::Sin(arg),
x if x.eq_ignore_ascii_case("SQRT") => Call::Sqrt(arg),
_ => Call::Tan(arg),
};
return Ok(make_signed(sign, Expr::Call(func)));
}
Rule::expr_exist => {
let (par_ref,) = pair.into_inner().collect_tuple().expect("one child");
return Ok(make_signed(sign, Expr::Call(Call::Exists(
make_par_ref(par_ref)?
))));
}
Rule::expr_atan => {
let (argy, argx) = pair.into_inner().collect_tuple().expect("children");
return Ok(make_signed(
sign, Expr::Call(Call::Atan(Box::new(make_expr(argy)?),
Box::new(make_expr(argx)?)))));
}
Rule::par_ref => return Ok(make_signed(sign, Expr::Par(make_par_ref(pair)?))),
Rule::expr_atom |
Rule::expr_pow |
Rule::expr_mul |
Rule::expr_add |
Rule::expr_cmp => {
if let Some(op) = op.take() {
let lhs_expr = lhs.take().expect("LHS expected before op");
lhs = Some(Expr::BinOp(op, Box::new(lhs_expr), Box::new(make_expr(pair)?)));
} else {
lhs = Some(make_expr(pair)?);
}
}
Rule::expr_unop => {
let mut inner = pair.into_inner().collect::<Vec<_>>();
let expr = make_expr(inner.pop().expect("children"))?;
let full = if inner.is_empty() || inner[0].as_str() == "+" {
expr
} else {
Expr::UnOp(UnOp::Minus, Box::new(expr))
};
if let Some(op) = op.take() {
let lhs_expr = lhs.take().expect("LHS expected before op");
lhs = Some(Expr::BinOp(op, Box::new(lhs_expr), Box::new(full)));
} else {
lhs = Some(full);
}
}
Rule::op_pow => op = Some(Op::Exp),
Rule::op_mul => op = Some(match pair.as_str() {
"*" => Op::Mul, "/" => Op::Div, _ => Op::Mod,
}),
Rule::op_add => op = Some(match pair.as_str() {
"+" => Op::Add, _ => Op::Sub,
}),
Rule::op_cmp => op = Some(match pair.as_str() {
x if x.eq_ignore_ascii_case("EQ") => Op::Eq,
x if x.eq_ignore_ascii_case("NE") => Op::Ne,
x if x.eq_ignore_ascii_case("GT") => Op::Gt,
x if x.eq_ignore_ascii_case("GE") => Op::Ge,
x if x.eq_ignore_ascii_case("LT") => Op::Lt,
_ => Op::Le
}),
Rule::op_log => op = Some(match pair.as_str() {
x if x.eq_ignore_ascii_case("AND") => Op::And,
x if x.eq_ignore_ascii_case("OR") => Op::Or,
_ => Op::Xor,
}),
_ => unreachable!()
}
}
Ok(lhs.expect("no children in expr?"))
}
fn make_word(pairs: Pairs<Rule>) -> ParseResult<Option<Word>> {
let (letter, value) = pairs.collect_tuple().expect("children");
let value = make_expr(value)?;
match letter.as_str() {
"o" | "O" => Err(err(letter.as_span(), "O-word control flow is not supported")),
"n" | "N" => Ok(None), "g" | "G" => Ok(Some(Word::Gcode(value))),
"m" | "M" => Ok(Some(Word::Mcode(value))),
"f" | "F" => Ok(Some(Word::Feed(value))),
"s" | "S" => Ok(Some(Word::Spindle(value))),
"t" | "T" => Ok(Some(Word::Tool(value))),
"a" | "A" => Ok(Some(Word::Arg(Arg::AxisA, value))),
"b" | "B" => Ok(Some(Word::Arg(Arg::AxisB, value))),
"c" | "C" => Ok(Some(Word::Arg(Arg::AxisC, value))),
"u" | "U" => Ok(Some(Word::Arg(Arg::AxisU, value))),
"v" | "V" => Ok(Some(Word::Arg(Arg::AxisV, value))),
"w" | "W" => Ok(Some(Word::Arg(Arg::AxisW, value))),
"x" | "X" => Ok(Some(Word::Arg(Arg::AxisX, value))),
"y" | "Y" => Ok(Some(Word::Arg(Arg::AxisY, value))),
"z" | "Z" => Ok(Some(Word::Arg(Arg::AxisZ, value))),
"i" | "I" => Ok(Some(Word::Arg(Arg::OffsetI, value))),
"j" | "J" => Ok(Some(Word::Arg(Arg::OffsetJ, value))),
"k" | "K" => Ok(Some(Word::Arg(Arg::OffsetK, value))),
"d" | "D" => Ok(Some(Word::Arg(Arg::ParamD, value))),
"e" | "E" => Ok(Some(Word::Arg(Arg::ParamE, value))),
"h" | "H" => Ok(Some(Word::Arg(Arg::ParamH, value))),
"l" | "L" => Ok(Some(Word::Arg(Arg::ParamL, value))),
"p" | "P" => Ok(Some(Word::Arg(Arg::ParamP, value))),
"q" | "Q" => Ok(Some(Word::Arg(Arg::ParamQ, value))),
"r" | "R" => Ok(Some(Word::Arg(Arg::ParamR, value))),
_ => unreachable!()
}
}
enum PercentState {
NotOnFirstLine,
OnFirstLine,
SeenTwice,
}
fn make_block(lineno: usize, pairs: Pairs<Rule>, pct: &mut PercentState)
-> ParseResult<Option<Block>> {
let mut block = Block { lineno, ..Block::default() };
for pair in pairs {
match pair.as_rule() {
Rule::word => if let Some(word) = make_word(pair.into_inner())? {
block.words.push(word);
}
Rule::par_assign => {
let (id, value) = pair.into_inner().collect_tuple().expect("children");
block.assignments.push(ParAssign {
id: make_par_ref(id)?,
value: make_expr(value)?
});
}
Rule::blockdel => block.blockdel = true,
Rule::percent => {
if lineno == 1 {
*pct = PercentState::OnFirstLine;
} else if let PercentState::OnFirstLine = pct {
*pct = PercentState::SeenTwice;
} else {
return Err(err(pair.as_span(), "percent sign missing on first line"));
}
}
_ => unreachable!()
}
}
Ok(if block.words.len() + block.assignments.len() > 0 { Some(block) } else { None })
}
pub fn parse(filename: &str, input: &str) -> ParseResult<Program> {
let lines = GcodeParser::parse(Rule::file, input).map_err(|e| e.with_path(filename))?;
let mut prog = Program { filename: filename.into(), blocks: vec![] };
let mut pct = PercentState::NotOnFirstLine;
let mut span = None;
for (lineno, line) in lines.into_iter().enumerate() {
span = Some(line.as_span());
if let Some(block) = make_block(lineno + 1, line.into_inner(), &mut pct)? {
prog.blocks.push(block);
}
if let PercentState::SeenTwice = pct {
prog.blocks.push(Block { lineno: lineno + 1,
blockdel: false,
words: vec![Word::Mcode(Expr::Num(2.0))],
assignments: vec![] });
break;
}
}
if let PercentState::OnFirstLine = pct {
return Err(err(span.unwrap(), "percent sign missing on last line"));
}
Ok(prog)
}