1 // pest. The Elegant Parser
2 // Copyright (c) 2018 Dragoș Tiselice
3 //
4 // Licensed under the Apache License, Version 2.0
5 // <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
6 // license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. All files in the project carrying such notice may not be copied,
8 // modified, or distributed except according to those terms.
9 
10 #[macro_use]
11 extern crate pest;
12 
13 use pest::error::Error;
14 use pest::iterators::{Pair, Pairs};
15 use pest::pratt_parser::{Assoc, Op, PrattParser};
16 use pest::{state, ParseResult, Parser, ParserState};
17 
18 #[allow(dead_code, non_camel_case_types)]
19 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
20 enum Rule {
21     expression,
22     primary,
23     number,
24     plus,
25     minus,
26     times,
27     divide,
28     modulus,
29     power,
30 }
31 
32 struct CalculatorParser;
33 
34 impl Parser<Rule> for CalculatorParser {
35     // false positive: pest uses `..` as a complete range (historically)
36     #[allow(clippy::almost_complete_range)]
parse(rule: Rule, input: &str) -> Result<Pairs<Rule>, Error<Rule>>37     fn parse(rule: Rule, input: &str) -> Result<Pairs<Rule>, Error<Rule>> {
38         fn expression(
39             state: Box<ParserState<'_, Rule>>,
40         ) -> ParseResult<Box<ParserState<'_, Rule>>> {
41             state.rule(Rule::expression, |s| {
42                 s.sequence(|s| {
43                     primary(s).and_then(|s| {
44                         s.repeat(|s| {
45                             s.sequence(|s| {
46                                 plus(s)
47                                     .or_else(minus)
48                                     .or_else(times)
49                                     .or_else(divide)
50                                     .or_else(modulus)
51                                     .or_else(power)
52                                     .and_then(primary)
53                             })
54                         })
55                     })
56                 })
57             })
58         }
59 
60         fn primary(state: Box<ParserState<'_, Rule>>) -> ParseResult<Box<ParserState<'_, Rule>>> {
61             state
62                 .sequence(|s| {
63                     s.match_string("(")
64                         .and_then(expression)
65                         .and_then(|s| s.match_string(")"))
66                 })
67                 .or_else(number)
68         }
69 
70         fn number(state: Box<ParserState<'_, Rule>>) -> ParseResult<Box<ParserState<'_, Rule>>> {
71             state.rule(Rule::number, |s| {
72                 s.sequence(|s| {
73                     s.optional(|s| s.match_string("-")).and_then(|s| {
74                         s.match_string("0").or_else(|s| {
75                             s.sequence(|s| {
76                                 s.match_range('1'..'9')
77                                     .and_then(|s| s.repeat(|s| s.match_range('0'..'9')))
78                             })
79                         })
80                     })
81                 })
82             })
83         }
84 
85         fn plus(state: Box<ParserState<'_, Rule>>) -> ParseResult<Box<ParserState<'_, Rule>>> {
86             state.rule(Rule::plus, |s| s.match_string("+"))
87         }
88 
89         fn minus(state: Box<ParserState<'_, Rule>>) -> ParseResult<Box<ParserState<'_, Rule>>> {
90             state.rule(Rule::minus, |s| s.match_string("-"))
91         }
92 
93         fn times(state: Box<ParserState<'_, Rule>>) -> ParseResult<Box<ParserState<'_, Rule>>> {
94             state.rule(Rule::times, |s| s.match_string("*"))
95         }
96 
97         fn divide(state: Box<ParserState<'_, Rule>>) -> ParseResult<Box<ParserState<'_, Rule>>> {
98             state.rule(Rule::divide, |s| s.match_string("/"))
99         }
100 
101         fn modulus(state: Box<ParserState<'_, Rule>>) -> ParseResult<Box<ParserState<'_, Rule>>> {
102             state.rule(Rule::modulus, |s| s.match_string("%"))
103         }
104 
105         fn power(state: Box<ParserState<'_, Rule>>) -> ParseResult<Box<ParserState<'_, Rule>>> {
106             state.rule(Rule::power, |s| s.match_string("^"))
107         }
108 
109         state(input, |state| match rule {
110             Rule::expression => expression(state),
111             _ => unreachable!(),
112         })
113     }
114 }
115 
116 #[allow(deprecated)]
117 enum PrattOrPrecClimber<'a> {
118     Pratt(&'a PrattParser<Rule>),
119     PrecClimber(&'a pest::prec_climber::PrecClimber<Rule>),
120 }
121 
consume(pair: Pair<Rule>, pratt_or_climber: &PrattOrPrecClimber) -> i32122 fn consume(pair: Pair<Rule>, pratt_or_climber: &PrattOrPrecClimber) -> i32 {
123     let primary = |pair| consume(pair, pratt_or_climber);
124     let infix = |lhs: i32, op: Pair<Rule>, rhs: i32| match op.as_rule() {
125         Rule::plus => lhs + rhs,
126         Rule::minus => lhs - rhs,
127         Rule::times => lhs * rhs,
128         Rule::divide => lhs / rhs,
129         Rule::modulus => lhs % rhs,
130         Rule::power => lhs.pow(rhs as u32),
131         _ => unreachable!(),
132     };
133 
134     #[allow(deprecated)]
135     match (pair.as_rule(), pratt_or_climber) {
136         (Rule::expression, PrattOrPrecClimber::Pratt(pratt)) => pratt
137             .map_primary(primary)
138             .map_infix(infix)
139             .parse(pair.into_inner()),
140         (Rule::expression, PrattOrPrecClimber::PrecClimber(climber)) => {
141             climber.climb(pair.into_inner(), primary, infix)
142         }
143         (Rule::number, _) => pair.as_str().parse().unwrap(),
144         _ => unreachable!(),
145     }
146 }
147 
148 #[test]
number()149 fn number() {
150     parses_to! {
151         parser: CalculatorParser,
152         input: "-12",
153         rule: Rule::expression,
154         tokens: [
155             expression(0, 3, [
156                 number(0, 3)
157             ])
158         ]
159     };
160 }
161 
162 #[test]
parens()163 fn parens() {
164     parses_to! {
165         parser: CalculatorParser,
166         input: "((-12))",
167         rule: Rule::expression,
168         tokens: [
169             expression(0, 7, [
170                 expression(1, 6, [
171                     expression(2, 5, [
172                         number(2, 5)
173                     ])
174                 ])
175             ])
176         ]
177     };
178 }
179 
180 #[test]
expression()181 fn expression() {
182     parses_to! {
183         parser: CalculatorParser,
184         input: "-12+3*(4-9)^7^2",
185         rule: Rule::expression,
186         tokens: [
187             expression(0, 15, [
188                 number(0, 3),
189                 plus(3, 4),
190                 number(4, 5),
191                 times(5, 6),
192                 expression(7, 10, [
193                     number(7, 8),
194                     minus(8, 9),
195                     number(9, 10)
196                 ]),
197                 power(11, 12),
198                 number(12, 13),
199                 power(13, 14),
200                 number(14, 15)
201             ])
202         ]
203     };
204 }
205 
206 #[test]
207 #[allow(deprecated)]
prec_climb()208 fn prec_climb() {
209     use pest::prec_climber::{Assoc, Operator, PrecClimber};
210     let climber = PrecClimber::new(vec![
211         Operator::new(Rule::plus, Assoc::Left) | Operator::new(Rule::minus, Assoc::Left),
212         Operator::new(Rule::times, Assoc::Left)
213             | Operator::new(Rule::divide, Assoc::Left)
214             | Operator::new(Rule::modulus, Assoc::Left),
215         Operator::new(Rule::power, Assoc::Right),
216     ]);
217 
218     let pairs = CalculatorParser::parse(Rule::expression, "-12+3*(4-9)^3^2/9%7381");
219     assert_eq!(
220         -1_525,
221         consume(
222             pairs.unwrap().next().unwrap(),
223             &PrattOrPrecClimber::PrecClimber(&climber)
224         )
225     );
226 }
227 
228 #[test]
pratt_parse()229 fn pratt_parse() {
230     let pratt = PrattParser::new()
231         .op(Op::infix(Rule::plus, Assoc::Left) | Op::infix(Rule::minus, Assoc::Left))
232         .op(Op::infix(Rule::times, Assoc::Left)
233             | Op::infix(Rule::divide, Assoc::Left)
234             | Op::infix(Rule::modulus, Assoc::Left))
235         .op(Op::infix(Rule::power, Assoc::Right));
236 
237     let pairs = CalculatorParser::parse(Rule::expression, "-12+3*(4-9)^3^2/9%7381");
238     assert_eq!(
239         -1_525,
240         consume(
241             pairs.unwrap().next().unwrap(),
242             &PrattOrPrecClimber::Pratt(&pratt)
243         )
244     );
245 }
246