Select Git revision
Forked from
Per Lindgren / D7050E
5 commits behind the upstream repository.
-
Per Lindgren authoredPer Lindgren authored
climb.rs 5.13 KiB
extern crate nom;
use std::iter::Peekable;
use std::slice::Iter;
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::char,
character::complete::{digit1, multispace0},
combinator::{cut, map},
error::ParseError,
multi::many1,
sequence::{delimited, preceded},
IResult,
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Op {
Eq,
Neq,
And,
Or,
Add,
Sub,
Mul,
Div,
Pow,
Not,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Num(i32),
Par(Box<Expr>),
// Identifier
// Function application
BinOp(Op, Box<Expr>, Box<Expr>),
UnaryOp(Op, Box<Expr>),
}
pub fn parse_i32(i: &str) -> IResult<&str, i32> {
map(digit1, |digit_str: &str| digit_str.parse::<i32>().unwrap())(i)
}
fn parse_op(i: &str) -> IResult<&str, Op> {
alt((
map(tag("=="), |_| Op::Eq),
map(tag("!="), |_| Op::Neq),
map(tag("**"), |_| Op::Pow),
map(tag("&&"), |_| Op::And),
map(tag("||"), |_| Op::Or),
map(tag("+"), |_| Op::Add),
map(tag("-"), |_| Op::Sub),
map(tag("*"), |_| Op::Mul),
map(tag("/"), |_| Op::Div),
map(tag("!"), |_| Op::Not),
))(i)
}
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Num(i32),
Par(Vec<Token>),
Op(Op),
}
fn parse_terminal(i: &str) -> IResult<&str, Token> {
alt((
map(parse_i32, |v| Token::Num(v)),
map(parse_par(parse_tokens), |tokens| Token::Par(tokens)),
))(i)
}
fn parse_token(i: &str) -> IResult<&str, Token> {
preceded(
multispace0,
alt((map(parse_op, |op| Token::Op(op)), parse_terminal)),
)(i)
}
fn parse_tokens(i: &str) -> IResult<&str, Vec<Token>> {
many1(parse_token)(i)
}
fn compute_atom(t: &mut Peekable<Iter<Token>>) -> Expr {
match t.next() {
Some(Token::Num(i)) => Expr::Num(*i),
Some(Token::Par(v)) => climb(&mut v.iter().peekable(), 0),
Some(Token::Op(op)) => Expr::UnaryOp(*op, Box::new(climb(t, 4))), // assume highest precedence
_ => panic!("error in compute atom"),
}
}
fn climb(t: &mut Peekable<Iter<Token>>, min_prec: u8) -> Expr {
let mut result = compute_atom(t);
loop {
match t.peek() {
Some(Token::Op(op)) => {
let (prec, ass) = get_prec(op);
if prec < min_prec {
break;
};
let next_prec = prec
+ match ass {
Ass::Left => 1,
_ => 0,
};
t.next();
let rhs = climb(t, next_prec);
result = Expr::BinOp(*op, Box::new(result), Box::new(rhs))
}
_ => {
break;
}
}
}
result
}
fn test(s: &str, v: i32) {
match parse_tokens(s) {
Ok(("", t)) => {
let mut t = t.iter().peekable();
println!("{:?}", &t);
let e = climb(&mut t, 0);
println!("{:?}", &e);
println!("eval {} {}", math_eval(&e), v);
assert_eq!(math_eval(&e), v);
}
Ok((s, t)) => println!(
"parse incomplete, \n parsed tokens \t{:?}, \n remaining \t{:?}",
t, s
),
Err(err) => println!("{:?}", err),
}
}
fn main() {
test("- -1 + + 1", - -1 + 1); // rust does not allow + as a unary op (I do ;)
test("(-1-1)+(-1+3)", (-1 - 1) + (-1) + 3);
// just to check that right associative works (you don't need to implement pow)
test("2+3**2**3*5+1", 2 + 3i32.pow(2u32.pow(3)) * 5 + 1);
test("(12*2)/3-4", (12 * 2) / 3 - 4);
test("1*2+3", 1 * 2 + 3);
// just to check that we get a parse error
test("1*2+3+3*21-a12+2", 1 * 2 + 3 + 3 * 21 - 12 + 2);
}
// helpers
fn parse_par<'a, O, F, E>(
inner: F,
) -> impl Fn(&'a str) -> IResult<&'a str, O, E>
where
F: Fn(&'a str) -> IResult<&'a str, O, E>,
E: ParseError<&'a str>,
{
// delimited allows us to split up the input
// cut allwos us to consume the input (and prevent backtracking)
delimited(char('('), preceded(multispace0, inner), cut(char(')')))
}
fn math_eval(e: &Expr) -> i32 {
match e {
Expr::Num(i) => *i,
Expr::BinOp(op, l, r) => {
let lv = math_eval(l);
let rv = math_eval(r);
match op {
Op::Add => lv + rv,
Op::Sub => lv - rv,
Op::Mul => lv * rv,
Op::Div => lv / rv,
Op::Pow => lv.pow(rv as u32),
_ => unimplemented!(),
}
}
Expr::UnaryOp(op, e) => {
let e = math_eval(e);
match op {
Op::Add => e,
Op::Sub => -e,
_ => unimplemented!(),
}
}
_ => unimplemented!(),
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum Ass {
Left,
Right,
}
fn get_prec(op: &Op) -> (u8, Ass) {
match op {
Op::Add => (1, Ass::Left),
Op::Sub => (1, Ass::Left),
Op::Mul => (2, Ass::Left),
Op::Div => (2, Ass::Left),
Op::Pow => (3, Ass::Right),
_ => unimplemented!(),
}
}