diff --git a/examples/climb.rs b/examples/climb.rs new file mode 100644 index 0000000000000000000000000000000000000000..49ecb09f2ed83266a7ec46cd92cde3e2ee1226d0 --- /dev/null +++ b/examples/climb.rs @@ -0,0 +1,205 @@ +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); + 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!(), + } +}