use lazy_static::lazy_static;
use pest::{
error::{ErrorVariant, InputLocation},
iterators::Pairs,
pratt_parser::PrattParser,
Parser,
};
use pest_derive::Parser;
#[derive(Parser)]
#[grammar = "grammar.pest"]
pub struct DiceParser;
#[derive(Debug, Clone)]
pub enum Expr {
Integer(i64),
BinOp {
lhs: Box<Expr>,
op: BinOp,
rhs: Box<Expr>,
},
UnOp {
op: UnOp,
inner: Box<Expr>,
},
}
#[derive(Debug, Clone)]
pub enum BinOp {
Add,
Subtract,
Multiply,
Divide,
Dice,
}
#[derive(Debug, Clone)]
pub enum UnOp {
Minus,
Dice,
}
lazy_static! {
static ref PRATT_PARSER: PrattParser<Rule> = {
use pest::pratt_parser::{Assoc::*, Op};
use Rule::*;
PrattParser::new()
.op(Op::infix(add, Left) | Op::infix(subtract, Left))
.op(Op::infix(multiply, Left) | Op::infix(divide, Left))
.op(Op::prefix(minus))
.op(Op::prefix(undice))
.op(Op::infix(bindice, Left))
};
}
#[derive(Debug, Clone)]
pub enum ParseError {
MissingStartExpr(String, InputLocation),
MissingEndExpr(String, InputLocation),
InvalidInteger(String),
}
fn parse_pairs(pairs: Pairs<Rule>) -> Result<Expr, ParseError> {
PRATT_PARSER
.map_primary(|primary| match primary.as_rule() {
Rule::integer => {
Ok(Expr::Integer(primary.as_str().parse::<i64>().map_err(
|_| ParseError::InvalidInteger(primary.as_str().into()),
)?))
}
_ => unreachable!(),
})
.map_infix(|lhs, op, rhs| {
let op = match op.as_rule() {
Rule::add => BinOp::Add,
Rule::subtract => BinOp::Subtract,
Rule::multiply => BinOp::Multiply,
Rule::divide => BinOp::Divide,
Rule::bindice => BinOp::Dice,
_ => unreachable!(),
};
Ok(Expr::BinOp {
lhs: Box::new(lhs?),
op,
rhs: Box::new(rhs?),
})
})
.map_prefix(|op, inner| {
let op = match op.as_rule() {
Rule::minus => UnOp::Minus,
Rule::undice => UnOp::Dice,
_ => unreachable!(),
};
Ok(Expr::UnOp {
op,
inner: Box::new(inner?),
})
})
.parse(pairs)
}
fn handle_error(error: pest::error::Error<Rule>) -> ParseError {
match error.variant {
ErrorVariant::CustomError { .. } => unreachable!(),
ErrorVariant::ParsingError { ref positives, .. } => {
if positives.contains(&Rule::expr) {
ParseError::MissingStartExpr(error.line().into(), error.location)
} else {
ParseError::MissingEndExpr(error.line().into(), error.location)
}
}
}
}
pub fn load(text: &str) -> Result<Expr, ParseError> {
let mut pairs = DiceParser::parse(Rule::equation, text).map_err(handle_error)?;
let first_item = pairs.next().unwrap().into_inner();
parse_pairs(first_item)
}