diff --git a/Cargo.toml b/Cargo.toml index 5cde847..43b53b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ publish = ["foresight-mining-software-corporation"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -once_cell = "1" -pest = "2" -pest_derive = "2" +once_cell = "1.19.0" +pest = "2.7.5" +pest_derive = "2.7.5" rayon = { version = "1", optional = true } diff --git a/src/parse.rs b/src/parse.rs index 4c1f659..1357e39 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,18 +1,18 @@ -use std::collections::HashSet; - use crate::expression::{BindingId, BoolExpression, Expression, RealExpression}; use crate::StringExpression; - use once_cell::sync::Lazy; -use pest::iterators::{Pair, Pairs}; -use pest::{prec_climber::*, Parser}; +use pest::iterators::Pairs; +use pest::pratt_parser::{Assoc, Op, PrattParser}; +use pest::Parser; use pest_derive::Parser; +use std::collections::HashSet; #[derive(Parser)] #[grammar = "grammar.pest"] // relative to project `src` struct ExpressionParser; -pub type ParseError = pest::error::Error; +// Boxed because error is much larger than Ok variant in most results. +pub type ParseError = Box>; impl Expression { /// Assume this expression is real-valued. @@ -42,7 +42,6 @@ impl Expression { pub fn parse_real_variable_names(input: &str) -> Result, ParseError> { Ok(ExpressionParser::parse(Rule::calculation, input)? .flatten() - .into_iter() .filter(|p| (p.as_rule() == Rule::real_variable)) .map(|p| p.as_str().to_string()) .collect()) @@ -51,7 +50,6 @@ impl Expression { pub fn parse_string_variable_names(input: &str) -> Result, ParseError> { Ok(ExpressionParser::parse(Rule::calculation, input)? .flatten() - .into_iter() .filter(|p| (p.as_rule() == Rule::str_variable)) .map(|p| p.as_str().to_string()) .collect()) @@ -65,38 +63,38 @@ impl Expression { /// [`Expression`] to be efficiently reused with many different data /// bindings. pub fn parse(input: &str, binding_map: impl Fn(&str) -> BindingId) -> Result { - let pairs = ExpressionParser::parse(Rule::calculation, input)?; - Ok(climb_recursive(pairs, &binding_map)) + let mut pairs = ExpressionParser::parse(Rule::calculation, input)?; + // HACK: Working around https://github.com/pest-parser/pest/issues/943 + let inner_expr = pairs.next().unwrap().into_inner(); + Ok(parse_recursive(inner_expr, &binding_map)) } } -static PRECEDENCE_CLIMBER: Lazy> = Lazy::new(|| { +static PRATT_PARSER: Lazy> = Lazy::new(|| { use Assoc::*; use Rule::*; - PrecClimber::new(vec![ - Operator::new(and, Left) | Operator::new(or, Left), - Operator::new(str_eq, Left) - | Operator::new(str_neq, Left) - | Operator::new(real_eq, Left) - | Operator::new(real_neq, Left) - | Operator::new(less, Left) - | Operator::new(le, Left) - | Operator::new(greater, Left) - | Operator::new(ge, Left), - Operator::new(add, Left) | Operator::new(subtract, Left), - Operator::new(multiply, Left) | Operator::new(divide, Left), - Operator::new(power, Right), - ]) + PrattParser::new() + .op(Op::infix(and, Left) | Op::infix(or, Left)) + .op(Op::infix(str_eq, Left) + | Op::infix(str_neq, Left) + | Op::infix(real_eq, Left) + | Op::infix(real_neq, Left) + | Op::infix(less, Left) + | Op::infix(le, Left) + | Op::infix(greater, Left) + | Op::infix(ge, Left)) + .op(Op::infix(add, Left) | Op::infix(subtract, Left)) + .op(Op::infix(multiply, Left) | Op::infix(divide, Left)) + .op(Op::infix(power, Right)) }); -fn climb_recursive(input: Pairs, binding_map: &impl Fn(&str) -> BindingId) -> Expression { - PRECEDENCE_CLIMBER.climb( - input, - |pair: Pair| match pair.as_rule() { - Rule::bool_expr => climb_recursive(pair.into_inner(), binding_map), - Rule::real_expr => climb_recursive(pair.into_inner(), binding_map), - Rule::string_expr => climb_recursive(pair.into_inner(), binding_map), +fn parse_recursive(pairs: Pairs, binding_map: &impl Fn(&str) -> BindingId) -> Expression { + PRATT_PARSER + .map_primary(|pair| match pair.as_rule() { + Rule::bool_expr => parse_recursive(pair.into_inner(), binding_map), + Rule::real_expr => parse_recursive(pair.into_inner(), binding_map), + Rule::string_expr => parse_recursive(pair.into_inner(), binding_map), Rule::real_literal => { let literal_str = pair.as_str(); if let Ok(value) = literal_str.parse::() { @@ -104,7 +102,7 @@ fn climb_recursive(input: Pairs, binding_map: &impl Fn(&str) -> BindingId) } panic!("Unexpected literal: {}", literal_str) } - Rule::string_literal => climb_recursive(pair.into_inner(), binding_map), + Rule::string_literal => parse_recursive(pair.into_inner(), binding_map), Rule::string_literal_value => { let literal_str = pair.as_str(); if let Ok(value) = literal_str.parse::() { @@ -117,7 +115,7 @@ fn climb_recursive(input: Pairs, binding_map: &impl Fn(&str) -> BindingId) let unary = inner.next().unwrap(); match unary.as_rule() { Rule::neg => Expression::Real(RealExpression::Neg(Box::new( - climb_recursive(inner, binding_map).unwrap_real(), + parse_recursive(inner, binding_map).unwrap_real(), ))), x => panic!("Unexpected unary logic operator: {x:?}"), } @@ -127,7 +125,7 @@ fn climb_recursive(input: Pairs, binding_map: &impl Fn(&str) -> BindingId) let unary = inner.next().unwrap(); match unary.as_rule() { Rule::not => Expression::Boolean(BoolExpression::Not(Box::new( - climb_recursive(inner, binding_map).unwrap_bool(), + parse_recursive(inner, binding_map).unwrap_bool(), ))), x => panic!("Unexpected unary logic operator: {x:?}"), } @@ -139,8 +137,8 @@ fn climb_recursive(input: Pairs, binding_map: &impl Fn(&str) -> BindingId) Expression::String(StringExpression::Binding(binding_map(pair.as_str()))) } x => panic!("Unexpected primary rule {x:?}"), - }, - |lhs: Expression, op: Pair, rhs: Expression| match op.as_rule() { + }) + .map_infix(|lhs, op, rhs| match op.as_rule() { Rule::add => Expression::Real(RealExpression::Add( Box::new(lhs.unwrap_real()), Box::new(rhs.unwrap_real()), @@ -202,8 +200,8 @@ fn climb_recursive(input: Pairs, binding_map: &impl Fn(&str) -> BindingId) Box::new(rhs.unwrap_bool()), )), x => panic!("Unexpected operator {x:?}"), - }, - ) + }) + .parse(pairs) } #[cfg(test)] @@ -230,11 +228,11 @@ mod tests { _ => unreachable!(), } } - Expression::parse("x == y", &binding_map).unwrap(); - Expression::parse("x != y", &binding_map).unwrap(); - Expression::parse("x > y", &binding_map).unwrap(); - Expression::parse("x < y", &binding_map).unwrap(); - Expression::parse("x <= y", &binding_map).unwrap(); - Expression::parse("x >= y", &binding_map).unwrap(); + Expression::parse("x == y", binding_map).unwrap(); + Expression::parse("x != y", binding_map).unwrap(); + Expression::parse("x > y", binding_map).unwrap(); + Expression::parse("x < y", binding_map).unwrap(); + Expression::parse("x <= y", binding_map).unwrap(); + Expression::parse("x >= y", binding_map).unwrap(); } }