diff --git a/src/combinator/expression.rs b/src/combinator/expression.rs new file mode 100644 index 00000000..c2b3c1fe --- /dev/null +++ b/src/combinator/expression.rs @@ -0,0 +1,349 @@ +use core::marker::PhantomData; + +use crate::{ + combinator::{opt, trace}, + error::{ErrMode, ParserError}, + stream::{Stream, StreamIsPartial}, + PResult, Parser, +}; + +use super::{empty, fail}; + +/// Parses an expression based on operator precedence. +#[doc(alias = "pratt")] +#[doc(alias = "separated")] +#[doc(alias = "shunting_yard")] +#[doc(alias = "precedence_climbing")] +#[inline(always)] +pub fn expression( + parse_operand: ParseOperand, +) -> Expression< + I, + O, + ParseOperand, + impl Parser, E>, + impl Parser, E>, + impl Parser, E>, + E, +> +where + I: Stream + StreamIsPartial, + ParseOperand: Parser, + E: ParserError, +{ + Expression { + precedence_level: 0, + parse_operand, + parse_prefix: fail, + parse_postfix: fail, + parse_infix: fail, + i: Default::default(), + o: Default::default(), + e: Default::default(), + } +} + +pub struct Expression +where + I: Stream + StreamIsPartial, + ParseOperand: Parser, + E: ParserError, +{ + precedence_level: i64, + parse_operand: ParseOperand, + parse_prefix: Pre, + parse_postfix: Post, + parse_infix: Pix, + i: PhantomData, + o: PhantomData, + e: PhantomData, +} + +impl Expression +where + ParseOperand: Parser, + I: Stream + StreamIsPartial, + E: ParserError, +{ + #[inline(always)] + pub fn prefix( + self, + parser: NewParsePrefix, + ) -> Expression + where + NewParsePrefix: Parser, E>, + { + Expression { + precedence_level: self.precedence_level, + parse_operand: self.parse_operand, + parse_prefix: parser, + parse_postfix: self.parse_postfix, + parse_infix: self.parse_infix, + i: Default::default(), + o: Default::default(), + e: Default::default(), + } + } + + #[inline(always)] + pub fn postfix( + self, + parser: NewParsePostfix, + ) -> Expression + where + NewParsePostfix: Parser, E>, + { + Expression { + precedence_level: self.precedence_level, + parse_operand: self.parse_operand, + parse_prefix: self.parse_prefix, + parse_postfix: parser, + parse_infix: self.parse_infix, + i: Default::default(), + o: Default::default(), + e: Default::default(), + } + } + + #[inline(always)] + pub fn infix( + self, + parser: NewParseInfix, + ) -> Expression + where + NewParseInfix: Parser, E>, + { + Expression { + precedence_level: self.precedence_level, + parse_operand: self.parse_operand, + parse_prefix: self.parse_prefix, + parse_postfix: self.parse_postfix, + parse_infix: parser, + i: Default::default(), + o: Default::default(), + e: Default::default(), + } + } + + #[inline(always)] + pub fn current_precedence_level( + mut self, + level: i64, + ) -> Expression { + self.precedence_level = level; + self + } +} + +impl Parser for Expression +where + I: Stream + StreamIsPartial, + Pop: Parser, + Pix: Parser, E>, + Pre: Parser, E>, + Post: Parser, E>, + E: ParserError, +{ + #[inline(always)] + fn parse_next(&mut self, input: &mut I) -> PResult { + trace("expression", move |i: &mut I| { + expression_impl( + i, + &mut self.parse_operand, + &mut self.parse_prefix, + &mut self.parse_postfix, + &mut self.parse_infix, + self.precedence_level, + ) + }) + .parse_next(input) + } +} + +fn expression_impl( + i: &mut I, + parse_operand: &mut Pop, + prefix: &mut Pre, + postfix: &mut Post, + infix: &mut Pix, + min_power: i64, +) -> PResult +where + I: Stream + StreamIsPartial, + Pop: Parser, + Pix: Parser, E>, + Pre: Parser, E>, + Post: Parser, E>, + E: ParserError, +{ + let operand = opt(trace("operand", parse_operand.by_ref())).parse_next(i)?; + let mut operand = if let Some(operand) = operand { + operand + } else { + // Prefix unary operators + let len = i.eof_offset(); + let Prefix(power, fold_prefix) = trace("prefix", prefix.by_ref()).parse_next(i)?; + // infinite loop check: the parser must always consume + if i.eof_offset() == len { + return Err(ErrMode::assert(i, "`prefix` parsers must always consume")); + } + let operand = expression_impl(i, parse_operand, prefix, postfix, infix, power)?; + fold_prefix(i, operand)? + }; + + // A variable to stop the `'parse` loop when `Assoc::Neither` with the same + // precedence is encountered e.g. `a == b == c`. `Assoc::Neither` has similar + // associativity rules as `Assoc::Left`, but we stop parsing when the next operator + // is the same as the current one. + let mut prev_op_is_neither = None; + 'parse: while i.eof_offset() > 0 { + // Postfix unary operators + let start = i.checkpoint(); + if let Some(Postfix(power, fold_postfix)) = + opt(trace("postfix", postfix.by_ref())).parse_next(i)? + { + // control precedence over the prefix e.g.: + // `--(i++)` or `(--i)++` + if power < min_power { + i.reset(&start); + break 'parse; + } + operand = fold_postfix(i, operand)?; + + continue 'parse; + } + + // Infix binary operators + let start = i.checkpoint(); + let parse_result = opt(trace("infix", infix.by_ref())).parse_next(i)?; + if let Some(infix_op) = parse_result { + let mut is_neither = None; + let (lpower, rpower, fold_infix) = match infix_op { + Infix::Right(p, f) => (p, p - 1, f), + Infix::Left(p, f) => (p, p + 1, f), + Infix::Neither(p, f) => { + is_neither = Some(p); + (p, p + 1, f) + } + }; + if lpower < min_power + // MSRV: `is_some_and` + || match prev_op_is_neither { + None => false, + Some(p) => lpower == p, + } + { + i.reset(&start); + break 'parse; + } + prev_op_is_neither = is_neither; + let rhs = expression_impl(i, parse_operand, prefix, postfix, infix, rpower)?; + operand = fold_infix(i, operand, rhs)?; + + continue 'parse; + } + + break 'parse; + } + + Ok(operand) +} + +pub struct Prefix(i64, fn(&mut I, O) -> PResult); + +impl Clone for Prefix { + #[inline(always)] + fn clone(&self) -> Self { + Prefix(self.0, self.1) + } +} + +impl> Parser, E> for Prefix { + #[inline(always)] + fn parse_next(&mut self, input: &mut I) -> PResult, E> { + empty.value(self.clone()).parse_next(input) + } +} + +pub struct Postfix(i64, fn(&mut I, O) -> PResult); + +impl Clone for Postfix { + #[inline(always)] + fn clone(&self) -> Self { + Postfix(self.0, self.1) + } +} + +impl> Parser, E> + for (i64, fn(&mut I, O) -> PResult) +{ + #[inline(always)] + fn parse_next(&mut self, input: &mut I) -> PResult, E> { + empty.value(Postfix(self.0, self.1)).parse_next(input) + } +} + +pub enum Infix { + Left(i64, fn(&mut I, O, O) -> PResult), + Right(i64, fn(&mut I, O, O) -> PResult), + Neither(i64, fn(&mut I, O, O) -> PResult), +} + +impl Clone for Infix { + #[inline(always)] + fn clone(&self) -> Self { + match self { + Infix::Left(p, f) => Infix::Left(*p, *f), + Infix::Right(p, f) => Infix::Right(*p, *f), + Infix::Neither(p, f) => Infix::Neither(*p, *f), + } + } +} + +impl> Parser, E> for Infix { + #[inline(always)] + fn parse_next(&mut self, input: &mut I) -> PResult, E> { + empty.value(self.clone()).parse_next(input) + } +} + +#[cfg(test)] +mod tests { + use crate::ascii::digit1; + use crate::combinator::fail; + use crate::dispatch; + use crate::error::ContextError; + use crate::token::any; + + use super::*; + + fn parser<'i>() -> impl Parser<&'i str, i32, ContextError> { + move |i: &mut &str| { + use Infix::*; + expression(digit1.parse_to::()) + .current_precedence_level(0) + .prefix(dispatch! {any; + '+' => Prefix(12, |_, a| Ok(a)), + '-' => Prefix(12, |_, a: i32| Ok(-a)), + _ => fail + }) + .infix(dispatch! {any; + '+' => Left(5, |_, a, b| Ok(a + b)), + '-' => Left(5, |_, a, b| Ok(a - b)), + '*' => Left(7, |_, a, b| Ok(a * b)), + '/' => Left(7, |_, a, b| Ok(a / b)), + '%' => Left(7, |_, a, b| Ok(a % b)), + '^' => Left(9, |_, a, b| Ok(a ^ b)), + _ => fail + }) + .parse_next(i) + } + } + + #[test] + fn test_expression() { + assert_eq!(parser().parse("-3+-3*4"), Ok(-15)); + assert_eq!(parser().parse("+2+3*4"), Ok(14)); + assert_eq!(parser().parse("2*3+4"), Ok(10)); + } +} diff --git a/src/combinator/mod.rs b/src/combinator/mod.rs index df791ada..3364942d 100644 --- a/src/combinator/mod.rs +++ b/src/combinator/mod.rs @@ -166,6 +166,8 @@ mod multi; mod parser; mod sequence; +pub mod expression; + #[cfg(test)] mod tests;