// 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. #[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 for CalculatorParser { fn parse(rule: Rule, input: &str) -> Result, Error> { fn expression(state: Box>) -> ParseResult>> { 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>) -> ParseResult>> { 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>) -> ParseResult>> { 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>) -> ParseResult>> { state.rule(Rule::plus, |s| s.match_string("+")) } fn minus(state: Box>) -> ParseResult>> { state.rule(Rule::minus, |s| s.match_string("-")) } fn times(state: Box>) -> ParseResult>> { state.rule(Rule::times, |s| s.match_string("*")) } fn divide(state: Box>) -> ParseResult>> { state.rule(Rule::divide, |s| s.match_string("/")) } fn modulus(state: Box>) -> ParseResult>> { state.rule(Rule::modulus, |s| s.match_string("%")) } fn power(state: Box>) -> ParseResult>> { 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) -> i32 { let primary = |pair| consume(pair, climber); let infix = |lhs: i32, op: Pair, 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)); }