diff --git a/derive/Cargo.toml b/derive/Cargo.toml index afa4727b..accff558 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pest_derive" description = "pest's derive macro" -version = "2.3.1" +version = "2.4.0" edition = "2018" authors = ["Dragoș Tiselice "] homepage = "https://pest-parser.github.io/" @@ -23,5 +23,5 @@ std = ["pest/std", "pest_generator/std"] [dependencies] # for tests, included transitively anyway -pest = { path = "../pest", version = "2.3.1", default-features = false } -pest_generator = { path = "../generator", version = "2.3.1", default-features = false } +pest = { path = "../pest", version = "2.4.0", default-features = false } +pest_generator = { path = "../generator", version = "2.4.0", default-features = false } diff --git a/derive/examples/calc.pest b/derive/examples/calc.pest new file mode 100644 index 00000000..9f2cc3b7 --- /dev/null +++ b/derive/examples/calc.pest @@ -0,0 +1,16 @@ +WHITESPACE = _{ " " | "\t" | NEWLINE } + + program = { SOI ~ expr ~ EOI } + expr = { prefix* ~ primary ~ postfix* ~ (infix ~ prefix* ~ primary ~ postfix* )* } + infix = _{ add | sub | mul | div | pow } + add = { "+" } // Addition + sub = { "-" } // Subtraction + mul = { "*" } // Multiplication + div = { "/" } // Division + pow = { "^" } // Exponentiation + prefix = _{ neg } + neg = { "-" } // Negation + postfix = _{ fac } + fac = { "!" } // Factorial + primary = _{ int | "(" ~ expr ~ ")" } + int = @{ (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT+ | ASCII_DIGIT) } \ No newline at end of file diff --git a/derive/examples/calc.rs b/derive/examples/calc.rs new file mode 100644 index 00000000..efc6b7b2 --- /dev/null +++ b/derive/examples/calc.rs @@ -0,0 +1,109 @@ +mod parser { + use pest_derive::Parser; + + #[derive(Parser)] + #[grammar = "../examples/calc.pest"] + pub struct Parser; +} + +use parser::Rule; +use pest::{ + iterators::Pairs, + pratt_parser::{Assoc::*, Op, PrattParser}, + Parser, +}; +use std::io::{stdin, stdout, Write}; + +fn parse_to_str(pairs: Pairs, pratt: &PrattParser) -> String { + pratt + .map_primary(|primary| match primary.as_rule() { + Rule::int => primary.as_str().to_owned(), + Rule::expr => parse_to_str(primary.into_inner(), pratt), + _ => unreachable!(), + }) + .map_prefix(|op, rhs| match op.as_rule() { + Rule::neg => format!("(-{})", rhs), + _ => unreachable!(), + }) + .map_postfix(|lhs, op| match op.as_rule() { + Rule::fac => format!("({}!)", lhs), + _ => unreachable!(), + }) + .map_infix(|lhs, op, rhs| match op.as_rule() { + Rule::add => format!("({}+{})", lhs, rhs), + Rule::sub => format!("({}-{})", lhs, rhs), + Rule::mul => format!("({}*{})", lhs, rhs), + Rule::div => format!("({}/{})", lhs, rhs), + Rule::pow => format!("({}^{})", lhs, rhs), + _ => unreachable!(), + }) + .parse(pairs) +} + +fn parse_to_i32(pairs: Pairs, pratt: &PrattParser) -> i128 { + pratt + .map_primary(|primary| match primary.as_rule() { + Rule::int => primary.as_str().parse().unwrap(), + Rule::expr => parse_to_i32(primary.into_inner(), pratt), + _ => unreachable!(), + }) + .map_prefix(|op, rhs| match op.as_rule() { + Rule::neg => -rhs, + _ => unreachable!(), + }) + .map_postfix(|lhs, op| match op.as_rule() { + Rule::fac => (1..lhs + 1).product(), + _ => unreachable!(), + }) + .map_infix(|lhs, op, rhs| match op.as_rule() { + Rule::add => lhs + rhs, + Rule::sub => lhs - rhs, + Rule::mul => lhs * rhs, + Rule::div => lhs / rhs, + Rule::pow => (1..rhs + 1).map(|_| lhs).product(), + _ => unreachable!(), + }) + .parse(pairs) +} + +fn main() { + let pratt = PrattParser::new() + .op(Op::infix(Rule::add, Left) | Op::infix(Rule::sub, Left)) + .op(Op::infix(Rule::mul, Left) | Op::infix(Rule::div, Left)) + .op(Op::infix(Rule::pow, Right)) + .op(Op::postfix(Rule::fac)) + .op(Op::prefix(Rule::neg)); + + let stdin = stdin(); + let mut stdout = stdout(); + + loop { + let source = { + print!("> "); + let _ = stdout.flush(); + let mut input = String::new(); + let _ = stdin.read_line(&mut input); + input.trim().to_string() + }; + + let pairs = match parser::Parser::parse(Rule::program, &source) { + Ok(mut parse_tree) => { + parse_tree + .next() + .unwrap() + .into_inner() // inner of program + .next() + .unwrap() + .into_inner() // inner of expr + } + Err(err) => { + println!("Failed parsing input: {:}", err); + continue; + } + }; + + print!("{} => ", source); + print!("{} => ", parse_to_str(pairs.clone(), &pratt)); + println!("{}", parse_to_i32(pairs.clone(), &pratt)); + } +} diff --git a/generator/Cargo.toml b/generator/Cargo.toml index fded6955..7f0a2a9c 100644 --- a/generator/Cargo.toml +++ b/generator/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pest_generator" description = "pest code generator" -version = "2.3.1" +version = "2.4.0" edition = "2018" authors = ["Dragoș Tiselice "] homepage = "https://pest-parser.github.io/" @@ -18,8 +18,8 @@ default = ["std"] std = ["pest/std"] [dependencies] -pest = { path = "../pest", version = "2.3.1", default-features = false } -pest_meta = { path = "../meta", version = "2.3.1" } +pest = { path = "../pest", version = "2.4.0", default-features = false } +pest_meta = { path = "../meta", version = "2.4.0" } proc-macro2 = "1.0" quote = "1.0" syn = "1.0" diff --git a/grammars/Cargo.toml b/grammars/Cargo.toml index 2c5add13..db1f4645 100644 --- a/grammars/Cargo.toml +++ b/grammars/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pest_grammars" description = "pest popular grammar implementations" -version = "2.3.1" +version = "2.4.0" edition = "2018" authors = ["Dragoș Tiselice "] homepage = "https://pest-parser.github.io/" @@ -14,8 +14,8 @@ readme = "_README.md" rust-version = "1.56" [dependencies] -pest = { path = "../pest", version = "2.3.1" } -pest_derive = { path = "../derive", version = "2.3.1" } +pest = { path = "../pest", version = "2.4.0" } +pest_derive = { path = "../derive", version = "2.4.0" } [dev-dependencies] criterion = "0.3" diff --git a/meta/Cargo.toml b/meta/Cargo.toml index d40aedda..ce174a2d 100644 --- a/meta/Cargo.toml +++ b/meta/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pest_meta" description = "pest meta language parser and validator" -version = "2.3.1" +version = "2.4.0" edition = "2018" authors = ["Dragoș Tiselice "] homepage = "https://pest-parser.github.io/" @@ -16,7 +16,7 @@ include = ["Cargo.toml", "src/**/*", "src/grammar.rs", "_README.md", "LICENSE-*" rust-version = "1.56" [dependencies] -pest = { path = "../pest", version = "2.3.1" } +pest = { path = "../pest", version = "2.4.0" } once_cell = "1.8.0" [build-dependencies] diff --git a/meta/src/parser.rs b/meta/src/parser.rs index 72abd810..23e2565b 100644 --- a/meta/src/parser.rs +++ b/meta/src/parser.rs @@ -12,7 +12,7 @@ use std::iter::Peekable; use pest::error::{Error, ErrorVariant}; use pest::iterators::{Pair, Pairs}; -use pest::prec_climber::{Assoc, Operator, PrecClimber}; +use pest::pratt_parser::{Assoc, Op, PrattParser}; use pest::{Parser, Span}; use crate::ast::{Expr, Rule as AstRule, RuleType}; @@ -175,10 +175,9 @@ pub fn consume_rules(pairs: Pairs) -> Result, Vec } fn consume_rules_with_spans(pairs: Pairs) -> Result, Vec>> { - let climber = PrecClimber::new(vec![ - Operator::new(Rule::choice_operator, Assoc::Left), - Operator::new(Rule::sequence_operator, Assoc::Left), - ]); + let pratt = PrattParser::new() + .op(Op::infix(Rule::choice_operator, Assoc::Left)) + .op(Op::infix(Rule::sequence_operator, Assoc::Left)); pairs .filter(|pair| pair.as_rule() == Rule::grammar_rule) @@ -210,7 +209,7 @@ fn consume_rules_with_spans(pairs: Pairs) -> Result, Vec) -> Result, Vec( pairs: Peekable>, - climber: &PrecClimber, + pratt: &PrattParser, ) -> Result, Vec>> { fn unaries<'i>( mut pairs: Peekable>, - climber: &PrecClimber, + pratt: &PrattParser, ) -> Result, Vec>> { let pair = pairs.next().unwrap(); let node = match pair.as_rule() { Rule::opening_paren => { - let node = unaries(pairs, climber)?; + let node = unaries(pairs, pratt)?; let end = node.span.end_pos(); ParserNode { @@ -243,7 +242,7 @@ fn consume_expr<'i>( } } Rule::positive_predicate_operator => { - let node = unaries(pairs, climber)?; + let node = unaries(pairs, pratt)?; let end = node.span.end_pos(); ParserNode { @@ -252,7 +251,7 @@ fn consume_expr<'i>( } } Rule::negative_predicate_operator => { - let node = unaries(pairs, climber)?; + let node = unaries(pairs, pratt)?; let end = node.span.end_pos(); ParserNode { @@ -262,14 +261,14 @@ fn consume_expr<'i>( } other_rule => { let node = match other_rule { - Rule::expression => consume_expr(pair.into_inner().peekable(), climber)?, + Rule::expression => consume_expr(pair.into_inner().peekable(), pratt)?, Rule::_push => { let start = pair.clone().as_span().start_pos(); let mut pairs = pair.into_inner(); pairs.next().unwrap(); // opening_paren let pair = pairs.next().unwrap(); - let node = consume_expr(pair.into_inner().peekable(), climber)?; + let node = consume_expr(pair.into_inner().peekable(), pratt)?; let end = node.span.end_pos(); ParserNode { @@ -529,7 +528,7 @@ fn consume_expr<'i>( Ok(node) } - let term = |pair: Pair<'i, Rule>| unaries(pair.into_inner().peekable(), climber); + let term = |pair: Pair<'i, Rule>| unaries(pair.into_inner().peekable(), pratt); let infix = |lhs: Result, Vec>>, op: Pair<'i, Rule>, rhs: Result, Vec>>| match op.as_rule() { @@ -560,7 +559,7 @@ fn consume_expr<'i>( _ => unreachable!(), }; - climber.climb(pairs, term, infix) + pratt.map_primary(term).map_infix(infix).parse(pairs) } fn unescape(string: &str) -> Option { diff --git a/meta/src/validator.rs b/meta/src/validator.rs index 5448c3bc..45367795 100644 --- a/meta/src/validator.rs +++ b/meta/src/validator.rs @@ -423,7 +423,7 @@ fn left_recursion<'a, 'i: 'a>(rules: HashMap>) -> Vec return Some(Error::new_from_span( ErrorVariant::CustomError { message: format!( - "rule {} is left-recursive ({}); pest::prec_climber might be useful \ + "rule {} is left-recursive ({}); pest::pratt_parser might be useful \ in this case", node.span.as_str(), chain @@ -677,7 +677,7 @@ mod tests { 1 | a = { a } | ^ | - = rule a is left-recursive (a -> a); pest::prec_climber might be useful in this case")] + = rule a is left-recursive (a -> a); pest::pratt_parser might be useful in this case")] fn simple_left_recursion() { let input = "a = { a }"; unwrap_or_report(consume_rules( @@ -693,14 +693,14 @@ mod tests { 1 | a = { b } b = { a } | ^ | - = rule b is left-recursive (b -> a -> b); pest::prec_climber might be useful in this case + = rule b is left-recursive (b -> a -> b); pest::pratt_parser might be useful in this case --> 1:17 | 1 | a = { b } b = { a } | ^ | - = rule a is left-recursive (a -> b -> a); pest::prec_climber might be useful in this case")] + = rule a is left-recursive (a -> b -> a); pest::pratt_parser might be useful in this case")] fn indirect_left_recursion() { let input = "a = { b } b = { a }"; unwrap_or_report(consume_rules( @@ -716,7 +716,7 @@ mod tests { 1 | a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"\") ~ a } | ^ | - = rule a is left-recursive (a -> a); pest::prec_climber might be useful in this case")] + = rule a is left-recursive (a -> a); pest::pratt_parser might be useful in this case")] fn non_failing_left_recursion() { let input = "a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"\") ~ a }"; unwrap_or_report(consume_rules( @@ -732,7 +732,7 @@ mod tests { 1 | a = { \"a\" | a } | ^ | - = rule a is left-recursive (a -> a); pest::prec_climber might be useful in this case")] + = rule a is left-recursive (a -> a); pest::pratt_parser might be useful in this case")] fn non_primary_choice_left_recursion() { let input = "a = { \"a\" | a }"; unwrap_or_report(consume_rules( diff --git a/pest/Cargo.toml b/pest/Cargo.toml index edd4e3ac..4dbc7dd8 100644 --- a/pest/Cargo.toml +++ b/pest/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pest" description = "The Elegant Parser" -version = "2.3.1" +version = "2.4.0" edition = "2018" authors = ["Dragoș Tiselice "] homepage = "https://pest-parser.github.io/" diff --git a/pest/src/lib.rs b/pest/src/lib.rs index 30f87db6..c2a83a32 100644 --- a/pest/src/lib.rs +++ b/pest/src/lib.rs @@ -90,6 +90,12 @@ mod macros; mod parser; mod parser_state; mod position; +pub mod pratt_parser; +#[deprecated( + since = "2.4.0", + note = "Use `pest::pratt_parser` instead (it is an equivalent which also supports unary prefix/suffix operators). +While prec_climber is going to be kept in 2.x minor and patch releases, it may be removed in a future major release." +)] pub mod prec_climber; mod span; mod stack; diff --git a/pest/src/pratt_parser.rs b/pest/src/pratt_parser.rs new file mode 100644 index 00000000..fb6be851 --- /dev/null +++ b/pest/src/pratt_parser.rs @@ -0,0 +1,379 @@ +// pest. The Elegant Parser +// Copyright (c) 2018 Dragoș Tiselice +// +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , at your +// option. All files in the project carrying such notice may not be copied, +// modified, or distributed except according to those terms. + +//! Constructs useful in prefix, postfix, and infix operator parsing with the +//! Pratt parsing method. + +use core::iter::Peekable; +use core::marker::PhantomData; +use core::ops::BitOr; + +use alloc::boxed::Box; +use alloc::collections::BTreeMap; + +use crate::iterators::Pair; +use crate::RuleType; + +/// Associativity of an infix binary operator, used by [`Op::infix(Assoc)`]. +/// +/// [`Op::infix(Assoc)`]: struct.Op.html +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Assoc { + /// Left operator associativity. Evaluate expressions from left-to-right. + Left, + /// Right operator associativity. Evaluate expressions from right-to-left. + Right, +} + +type Prec = u32; +const PREC_STEP: Prec = 10; + +/// An operator that corresponds to a rule. +pub struct Op { + rule: R, + affix: Affix, + next: Option>>, +} + +enum Affix { + Prefix, + Postfix, + Infix(Assoc), +} + +impl Op { + /// Defines `rule` as a prefix unary operator. + pub fn prefix(rule: R) -> Self { + Self { + rule, + affix: Affix::Prefix, + next: None, + } + } + + /// Defines `rule` as a postfix unary operator. + pub fn postfix(rule: R) -> Self { + Self { + rule, + affix: Affix::Postfix, + next: None, + } + } + + /// Defines `rule` as an infix binary operator with associativity `assoc`. + pub fn infix(rule: R, assoc: Assoc) -> Self { + Self { + rule, + affix: Affix::Infix(assoc), + next: None, + } + } +} + +impl BitOr for Op { + type Output = Self; + + fn bitor(mut self, rhs: Self) -> Self { + fn assign_next(op: &mut Op, next: Op) { + if let Some(ref mut child) = op.next { + assign_next(child, next); + } else { + op.next = Some(Box::new(next)); + } + } + + assign_next(&mut self, rhs); + self + } +} + +/// Struct containing operators and precedences, which can perform [Pratt parsing][1] on +/// primary, prefix, postfix and infix expressions over [`Pairs`]. The tokens in [`Pairs`] +/// should alternate in the order: +/// `prefix* ~ primary ~ postfix* ~ (infix ~ prefix* ~ primary ~ postfix*)*` +/// +/// # Panics +/// +/// Panics will occur when: +/// * `pairs` is empty +/// * The tokens in `pairs` does not alternate in the expected order. +/// * No `map_*` function is specified for a certain kind of operator encountered in `pairs`. +/// +/// # Example +/// +/// The following pest grammar defines a calculator which can be used for Pratt parsing. +/// +/// ```pest +/// WHITESPACE = _{ " " | "\t" | NEWLINE } +/// +/// program = { SOI ~ expr ~ EOI } +/// expr = { prefix* ~ primary ~ postfix* ~ (infix ~ prefix* ~ primary ~ postfix* )* } +/// infix = _{ add | sub | mul | div | pow } +/// add = { "+" } // Addition +/// sub = { "-" } // Subtraction +/// mul = { "*" } // Multiplication +/// div = { "/" } // Division +/// pow = { "^" } // Exponentiation +/// prefix = _{ neg } +/// neg = { "-" } // Negation +/// postfix = _{ fac } +/// fac = { "!" } // Factorial +/// primary = _{ int | "(" ~ expr ~ ")" } +/// int = @{ (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT+ | ASCII_DIGIT) } +/// ``` +/// +/// Below is a [`PrattParser`] that is able to parse an `expr` in the above grammar. The order +/// of precedence corresponds to the order in which [`op`] is called. Thus, `mul` will +/// have higher precedence than `add`. Operators can also be chained with `|` to give them equal +/// precedence. +/// +/// ``` +/// # use pest::pratt_parser::{Assoc, Op, PrattParser}; +/// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +/// # enum Rule { program, expr, int, add, mul, sub, div, pow, fac, neg } +/// let pratt = +/// PrattParser::new() +/// .op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::sub, Assoc::Left)) +/// .op(Op::infix(Rule::mul, Assoc::Left) | Op::infix(Rule::div, Assoc::Left)) +/// .op(Op::infix(Rule::pow, Assoc::Right)) +/// .op(Op::postfix(Rule::fac)) +/// .op(Op::prefix(Rule::neg)); +/// ``` +/// +/// To parse an expression, call the [`map_primary`], [`map_prefix`], [`map_postfix`], +/// [`map_infix`] and [`parse`] methods as follows: +/// +/// ``` +/// # use pest::{iterators::Pairs, pratt_parser::PrattParser}; +/// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +/// # enum Rule { program, expr, int, add, mul, sub, div, pow, fac, neg } +/// fn parse_expr(pairs: Pairs, pratt: &PrattParser) -> i32 { +/// pratt +/// .map_primary(|primary| match primary.as_rule() { +/// Rule::int => primary.as_str().parse().unwrap(), +/// Rule::expr => parse_expr(primary.into_inner(), pratt), // from "(" ~ expr ~ ")" +/// _ => unreachable!(), +/// }) +/// .map_prefix(|op, rhs| match op.as_rule() { +/// Rule::neg => -rhs, +/// _ => unreachable!(), +/// }) +/// .map_postfix(|lhs, op| match op.as_rule() { +/// Rule::fac => (1..lhs+1).product(), +/// _ => unreachable!(), +/// }) +/// .map_infix(|lhs, op, rhs| match op.as_rule() { +/// Rule::add => lhs + rhs, +/// Rule::sub => lhs - rhs, +/// Rule::mul => lhs * rhs, +/// Rule::div => lhs / rhs, +/// Rule::pow => (1..rhs+1).map(|_| lhs).product(), +/// _ => unreachable!(), +/// }) +/// .parse(pairs) +/// } +/// ``` +/// +/// Note that [`map_prefix`], [`map_postfix`] and [`map_infix`] only need to be specified if the +/// grammar contains the corresponding operators. +/// +/// [1]: https://en.wikipedia.org/wiki/Pratt_parser +/// [`Pairs`]: ../iterators/struct.Pairs.html +/// [`PrattParser`]: struct.PrattParser.html +/// [`map_primary`]: struct.PrattParser.html#method.map_primary +/// [`map_prefix`]: struct.PrattParserMap.html#method.map_prefix +/// [`map_postfix`]: struct.PrattParserMap.html#method.map_postfix +/// [`map_infix`]: struct.PrattParserMap.html#method.map_infix +/// [`parse`]: struct.PrattParserMap.html#method.parse +/// [`op`]: struct.PrattParserMap.html#method.op +pub struct PrattParser { + prec: Prec, + ops: BTreeMap, + has_prefix: bool, + has_postfix: bool, + has_infix: bool, +} + +impl Default for PrattParser { + fn default() -> Self { + Self::new() + } +} + +impl PrattParser { + /// Instantiate a new `PrattParser`. + pub fn new() -> Self { + Self { + prec: PREC_STEP, + ops: BTreeMap::new(), + has_prefix: false, + has_postfix: false, + has_infix: false, + } + } + + /// Add `op` to `PrattParser`. + pub fn op(mut self, op: Op) -> Self { + self.prec += PREC_STEP; + let mut iter = Some(op); + while let Some(Op { rule, affix, next }) = iter.take() { + match affix { + Affix::Prefix => self.has_prefix = true, + Affix::Postfix => self.has_postfix = true, + Affix::Infix(_) => self.has_infix = true, + } + self.ops.insert(rule, (affix, self.prec)); + iter = next.map(|op| *op); + } + self + } + + /// Maps primary expressions with a closure `primary`. + pub fn map_primary<'pratt, 'i, X, T>( + &'pratt self, + primary: X, + ) -> PrattParserMap<'pratt, 'i, R, X, T> + where + X: FnMut(Pair<'i, R>) -> T, + R: 'pratt, + { + PrattParserMap { + pratt: self, + primary, + prefix: None, + postfix: None, + infix: None, + phantom: PhantomData, + } + } +} + +type PrefixFn<'i, R, T> = Box, T) -> T + 'i>; +type PostfixFn<'i, R, T> = Box) -> T + 'i>; +type InfixFn<'i, R, T> = Box, T) -> T + 'i>; + +/// Product of calling [`map_primary`] on [`PrattParser`], defines how expressions should +/// be mapped. +/// +/// [`map_primary`]: struct.PrattParser.html#method.map_primary +/// [`PrattParser`]: struct.PrattParser.html +pub struct PrattParserMap<'pratt, 'i, R, F, T> +where + R: RuleType + 'pratt, + F: FnMut(Pair<'i, R>) -> T, +{ + pratt: &'pratt PrattParser, + primary: F, + prefix: Option>, + postfix: Option>, + infix: Option>, + phantom: PhantomData, +} + +impl<'pratt, 'i, R, F, T> PrattParserMap<'pratt, 'i, R, F, T> +where + R: RuleType + 'pratt, + F: FnMut(Pair<'i, R>) -> T, +{ + /// Maps prefix operators with closure `prefix`. + pub fn map_prefix(mut self, prefix: X) -> Self + where + X: FnMut(Pair<'i, R>, T) -> T + 'i, + { + self.prefix = Some(Box::new(prefix)); + self + } + + /// Maps postfix operators with closure `postfix`. + pub fn map_postfix(mut self, postfix: X) -> Self + where + X: FnMut(T, Pair<'i, R>) -> T + 'i, + { + self.postfix = Some(Box::new(postfix)); + self + } + + /// Maps infix operators with a closure `infix`. + pub fn map_infix(mut self, infix: X) -> Self + where + X: FnMut(T, Pair<'i, R>, T) -> T + 'i, + { + self.infix = Some(Box::new(infix)); + self + } + + pub fn parse>>(&mut self, pairs: P) -> T { + self.expr(&mut pairs.peekable(), 0) + } + + fn expr>>(&mut self, pairs: &mut Peekable

, rbp: Prec) -> T { + let mut lhs = self.nud(pairs); + while rbp < self.lbp(pairs) { + lhs = self.led(pairs, lhs); + } + lhs + } + + /// Null-Denotation + /// + /// "the action that should happen when the symbol is encountered + /// as start of an expression (most notably, prefix operators) + fn nud>>(&mut self, pairs: &mut Peekable

) -> T { + let pair = pairs.next().expect("Pratt parsing expects non-empty Pairs"); + match self.pratt.ops.get(&pair.as_rule()) { + Some((Affix::Prefix, prec)) => { + let rhs = self.expr(pairs, *prec - 1); + match self.prefix.as_mut() { + Some(prefix) => prefix(pair, rhs), + None => panic!("Could not map {}, no `.map_prefix(...)` specified", pair), + } + } + None => (self.primary)(pair), + _ => panic!("Expected prefix or primary expression, found {}", pair), + } + } + + /// Left-Denotation + /// + /// "the action that should happen when the symbol is encountered + /// after the start of an expression (most notably, infix and postfix operators)" + fn led>>(&mut self, pairs: &mut Peekable

, lhs: T) -> T { + let pair = pairs.next().unwrap(); + match self.pratt.ops.get(&pair.as_rule()) { + Some((Affix::Infix(assoc), prec)) => { + let rhs = match *assoc { + Assoc::Left => self.expr(pairs, *prec), + Assoc::Right => self.expr(pairs, *prec - 1), + }; + match self.infix.as_mut() { + Some(infix) => infix(lhs, pair, rhs), + None => panic!("Could not map {}, no `.map_infix(...)` specified", pair), + } + } + Some((Affix::Postfix, _)) => match self.postfix.as_mut() { + Some(postfix) => postfix(lhs, pair), + None => panic!("Could not map {}, no `.map_postfix(...)` specified", pair), + }, + _ => panic!("Expected postfix or infix expression, found {}", pair), + } + } + + /// Left-Binding-Power + /// + /// "describes the symbol's precedence in infix form (most notably, operator precedence)" + fn lbp>>(&mut self, pairs: &mut Peekable

) -> Prec { + match pairs.peek() { + Some(pair) => match self.pratt.ops.get(&pair.as_rule()) { + Some((_, prec)) => *prec, + None => panic!("Expected operator, found {}", pair), + }, + None => 0, + } + } +} diff --git a/pest/tests/calculator.rs b/pest/tests/calculator.rs index 54a50cc6..7935877a 100644 --- a/pest/tests/calculator.rs +++ b/pest/tests/calculator.rs @@ -12,7 +12,7 @@ extern crate pest; use pest::error::Error; use pest::iterators::{Pair, Pairs}; -use pest::prec_climber::{Assoc, Operator, PrecClimber}; +use pest::pratt_parser::{Assoc, Op, PrattParser}; use pest::{state, ParseResult, Parser, ParserState}; #[allow(dead_code, non_camel_case_types)] @@ -109,8 +109,14 @@ impl Parser for CalculatorParser { } } -fn consume(pair: Pair, climber: &PrecClimber) -> i32 { - let primary = |pair| consume(pair, climber); +#[allow(deprecated)] +enum PrattOrPrecClimber<'a> { + Pratt(&'a PrattParser), + PrecClimber(&'a pest::prec_climber::PrecClimber), +} + +fn consume(pair: Pair, pratt_or_climber: &PrattOrPrecClimber) -> i32 { + let primary = |pair| consume(pair, pratt_or_climber); let infix = |lhs: i32, op: Pair, rhs: i32| match op.as_rule() { Rule::plus => lhs + rhs, Rule::minus => lhs - rhs, @@ -121,9 +127,16 @@ fn consume(pair: Pair, climber: &PrecClimber) -> i32 { _ => unreachable!(), }; - match pair.as_rule() { - Rule::expression => climber.climb(pair.into_inner(), primary, infix), - Rule::number => pair.as_str().parse().unwrap(), + #[allow(deprecated)] + match (pair.as_rule(), pratt_or_climber) { + (Rule::expression, PrattOrPrecClimber::Pratt(pratt)) => pratt + .map_primary(primary) + .map_infix(infix) + .parse(pair.into_inner()), + (Rule::expression, PrattOrPrecClimber::PrecClimber(climber)) => { + climber.climb(pair.into_inner(), primary, infix) + } + (Rule::number, _) => pair.as_str().parse().unwrap(), _ => unreachable!(), } } @@ -187,7 +200,9 @@ fn expression() { } #[test] +#[allow(deprecated)] fn prec_climb() { + use pest::prec_climber::{Assoc, Operator, PrecClimber}; let climber = PrecClimber::new(vec![ Operator::new(Rule::plus, Assoc::Left) | Operator::new(Rule::minus, Assoc::Left), Operator::new(Rule::times, Assoc::Left) @@ -197,5 +212,30 @@ fn prec_climb() { ]); let pairs = CalculatorParser::parse(Rule::expression, "-12+3*(4-9)^3^2/9%7381"); - assert_eq!(-1_525, consume(pairs.unwrap().next().unwrap(), &climber)); + assert_eq!( + -1_525, + consume( + pairs.unwrap().next().unwrap(), + &PrattOrPrecClimber::PrecClimber(&climber) + ) + ); +} + +#[test] +fn pratt_parse() { + let pratt = PrattParser::new() + .op(Op::infix(Rule::plus, Assoc::Left) | Op::infix(Rule::minus, Assoc::Left)) + .op(Op::infix(Rule::times, Assoc::Left) + | Op::infix(Rule::divide, Assoc::Left) + | Op::infix(Rule::modulus, Assoc::Left)) + .op(Op::infix(Rule::power, Assoc::Right)); + + let pairs = CalculatorParser::parse(Rule::expression, "-12+3*(4-9)^3^2/9%7381"); + assert_eq!( + -1_525, + consume( + pairs.unwrap().next().unwrap(), + &PrattOrPrecClimber::Pratt(&pratt) + ) + ); } diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 64311a3a..122754a0 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pest_vm" description = "pest grammar virtual machine" -version = "2.3.1" +version = "2.4.0" edition = "2018" authors = ["Dragoș Tiselice "] homepage = "https://pest-parser.github.io/" @@ -14,5 +14,5 @@ readme = "_README.md" rust-version = "1.56" [dependencies] -pest = { path = "../pest", version = "2.3.1" } -pest_meta = { path = "../meta", version = "2.3.1" } +pest = { path = "../pest", version = "2.4.0" } +pest_meta = { path = "../meta", version = "2.4.0" }