202 lines
6.2 KiB
Rust
202 lines
6.2 KiB
Rust
// pest. The Elegant Parser
|
|
// Copyright (c) 2018 Dragoș Tiselice
|
|
//
|
|
// Licensed under the Apache License, Version 2.0
|
|
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
|
|
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
// option. All files in the project carrying such notice may not be copied,
|
|
// modified, or distributed except according to those terms.
|
|
|
|
#[macro_use]
|
|
extern crate pest;
|
|
|
|
use pest::error::Error;
|
|
use pest::iterators::{Pair, Pairs};
|
|
use pest::prec_climber::{Assoc, Operator, PrecClimber};
|
|
use pest::{state, ParseResult, Parser, ParserState};
|
|
|
|
#[allow(dead_code, non_camel_case_types)]
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
enum Rule {
|
|
expression,
|
|
primary,
|
|
number,
|
|
plus,
|
|
minus,
|
|
times,
|
|
divide,
|
|
modulus,
|
|
power,
|
|
}
|
|
|
|
struct CalculatorParser;
|
|
|
|
impl Parser<Rule> for CalculatorParser {
|
|
fn parse(rule: Rule, input: &str) -> Result<Pairs<Rule>, Error<Rule>> {
|
|
fn expression(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
|
|
state.rule(Rule::expression, |s| {
|
|
s.sequence(|s| {
|
|
primary(s).and_then(|s| {
|
|
s.repeat(|s| {
|
|
s.sequence(|s| {
|
|
plus(s)
|
|
.or_else(|s| minus(s))
|
|
.or_else(|s| times(s))
|
|
.or_else(|s| divide(s))
|
|
.or_else(|s| modulus(s))
|
|
.or_else(|s| power(s))
|
|
.and_then(|s| primary(s))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
fn primary(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
|
|
state
|
|
.sequence(|s| {
|
|
s.match_string("(")
|
|
.and_then(|s| expression(s))
|
|
.and_then(|s| s.match_string(")"))
|
|
})
|
|
.or_else(|s| number(s))
|
|
}
|
|
|
|
fn number(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
|
|
state.rule(Rule::number, |s| {
|
|
s.sequence(|s| {
|
|
s.optional(|s| s.match_string("-")).and_then(|s| {
|
|
s.match_string("0").or_else(|s| {
|
|
s.sequence(|s| {
|
|
s.match_range('1'..'9')
|
|
.and_then(|s| s.repeat(|s| s.match_range('0'..'9')))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
fn plus(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
|
|
state.rule(Rule::plus, |s| s.match_string("+"))
|
|
}
|
|
|
|
fn minus(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
|
|
state.rule(Rule::minus, |s| s.match_string("-"))
|
|
}
|
|
|
|
fn times(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
|
|
state.rule(Rule::times, |s| s.match_string("*"))
|
|
}
|
|
|
|
fn divide(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
|
|
state.rule(Rule::divide, |s| s.match_string("/"))
|
|
}
|
|
|
|
fn modulus(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
|
|
state.rule(Rule::modulus, |s| s.match_string("%"))
|
|
}
|
|
|
|
fn power(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
|
|
state.rule(Rule::power, |s| s.match_string("^"))
|
|
}
|
|
|
|
state(input, |state| match rule {
|
|
Rule::expression => expression(state),
|
|
_ => unreachable!(),
|
|
})
|
|
}
|
|
}
|
|
|
|
fn consume<'i>(pair: Pair<'i, Rule>, climber: &PrecClimber<Rule>) -> i32 {
|
|
let primary = |pair| consume(pair, climber);
|
|
let infix = |lhs: i32, op: Pair<Rule>, rhs: i32| match op.as_rule() {
|
|
Rule::plus => lhs + rhs,
|
|
Rule::minus => lhs - rhs,
|
|
Rule::times => lhs * rhs,
|
|
Rule::divide => lhs / rhs,
|
|
Rule::modulus => lhs % rhs,
|
|
Rule::power => lhs.pow(rhs as u32),
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
match pair.as_rule() {
|
|
Rule::expression => climber.climb(pair.into_inner(), primary, infix),
|
|
Rule::number => pair.as_str().parse().unwrap(),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn number() {
|
|
parses_to! {
|
|
parser: CalculatorParser,
|
|
input: "-12",
|
|
rule: Rule::expression,
|
|
tokens: [
|
|
expression(0, 3, [
|
|
number(0, 3)
|
|
])
|
|
]
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn parens() {
|
|
parses_to! {
|
|
parser: CalculatorParser,
|
|
input: "((-12))",
|
|
rule: Rule::expression,
|
|
tokens: [
|
|
expression(0, 7, [
|
|
expression(1, 6, [
|
|
expression(2, 5, [
|
|
number(2, 5)
|
|
])
|
|
])
|
|
])
|
|
]
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn expression() {
|
|
parses_to! {
|
|
parser: CalculatorParser,
|
|
input: "-12+3*(4-9)^7^2",
|
|
rule: Rule::expression,
|
|
tokens: [
|
|
expression(0, 15, [
|
|
number(0, 3),
|
|
plus(3, 4),
|
|
number(4, 5),
|
|
times(5, 6),
|
|
expression(7, 10, [
|
|
number(7, 8),
|
|
minus(8, 9),
|
|
number(9, 10)
|
|
]),
|
|
power(11, 12),
|
|
number(12, 13),
|
|
power(13, 14),
|
|
number(14, 15)
|
|
])
|
|
]
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn prec_climb() {
|
|
let climber = PrecClimber::new(vec![
|
|
Operator::new(Rule::plus, Assoc::Left) | Operator::new(Rule::minus, Assoc::Left),
|
|
Operator::new(Rule::times, Assoc::Left)
|
|
| Operator::new(Rule::divide, Assoc::Left)
|
|
| Operator::new(Rule::modulus, Assoc::Left),
|
|
Operator::new(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(), &climber));
|
|
}
|