use std::iter::Peekable; use thiserror::Error; use crate::math::{BinMathOp, MathExpr, MathToken, UnMathOp}; // New thing on stack -> [what to consume, other thing to consume] // Done -> BinOp // Grammer for LR parser // <> is a terminal // // := '-' // := '+' | '-' // := '/' | '*' // := '**' | '^' // := 'sqrt' | 'sin' | 'cos' | etc // // // F := // | // | // | '(' binOp1 ')' // | F // | '(' binOp1 ')' // // // binOp3 := // | binOp3 F // | F // // binOp2 := // | binOp2 binOp3 // | binOp2 '(' expr ')' [Implicit multiplication] // TODO: // | binOp3 // // binOp1 := // | binOp1 binOp2 // | binOp2 // // goal := binOp1 pub fn parse_expr_from_tokens(tokens: Vec) -> Result { let mut tokens = tokens.into_iter().peekable(); parse_expr(&mut tokens) } fn parse_primary<'a>( tokens: &mut Peekable>>, ) -> Result, MathParserError<'a>> { match tokens.next() { Some(MathToken::Number(n)) => Ok(MathExpr::Number(n)), Some(MathToken::Sym(s)) => Ok(MathExpr::Sym(s)), Some(MathToken::LParen) => { let expr = parse_expr(tokens)?; match tokens.next() { Some(MathToken::RParen) => Ok(expr), Some(t) => Err(MathParserError::UnexpectedToken(t)), None => Err(MathParserError::EndOfExpression), } } Some(MathToken::Minus) => Ok(MathExpr::UnOp( UnMathOp::Minus, Box::new(parse_expr(tokens)?), )), // TODO: Function calls such as sqrt, sin, etc Some(t) => Err(MathParserError::UnexpectedToken(t)), None => Err(MathParserError::EndOfExpression), } } fn parse_expr<'a>( tokens: &mut Peekable>>, ) -> Result, MathParserError<'a>> { parse_expr_prime(parse_primary(tokens)?, tokens, 0) } fn parse_expr_prime<'a>( mut lhs: MathExpr<'a>, tokens: &mut Peekable>>, min_precedence: u8, ) -> Result, MathParserError<'a>> { loop { match tokens.peek() { // If the token has zero precedence it is not an operator and should stop expression // parsing. Some(t) if t.precedence() == 0 => return Ok(lhs), // If the token has a precedence lower then the minimum we don't parse it right now. Some(t) if t.precedence() < min_precedence => return Ok(lhs), // If the token has a precedence of at least min_precedence we parse it. Some(token) => { let op_precedence = token.precedence(); let op = parse_bin_op_from_token(*token)?; // Advance iterator let _ = tokens.next(); let mut rhs = parse_primary(tokens)?; loop { match tokens.peek() { Some(t) if t.precedence() > op_precedence => { rhs = parse_expr_prime(rhs, tokens, op_precedence + 1)?; } _ => break, } } lhs = MathExpr::BinOp(op, Box::new(lhs), Box::new(rhs)); } None => return Ok(lhs), } } } fn parse_bin_op_from_token(token: MathToken) -> Result { match token { MathToken::Plus => Ok(BinMathOp::Add), MathToken::Minus => Ok(BinMathOp::Sub), MathToken::Slash => Ok(BinMathOp::Div), MathToken::Star => Ok(BinMathOp::Mult), MathToken::DoubleStar => Ok(BinMathOp::Pow), MathToken::Caret => Ok(BinMathOp::Pow), t => Err(MathParserError::UnexpectedToken(t)), } } #[derive(Debug, Error, PartialEq)] pub enum MathParserError<'a> { #[error("Unexpected token {0}")] UnexpectedToken(MathToken<'a>), #[error("Reached the end of the expression")] EndOfExpression, } #[cfg(test)] mod test { use crate::math::parser::{MathParserError, parse_expr_from_tokens}; use crate::math::tokenizer::tokenize; use crate::math::{BinMathOp, MathExpr, MathToken::*}; #[test] fn basic_examples() { assert_eq!( parse_expr_from_tokens(vec![]).err().unwrap(), MathParserError::EndOfExpression ); assert_eq!( parse_expr_from_tokens(vec![Number(1.)]).unwrap(), MathExpr::Number(1.) ); assert_eq!( parse_expr_from_tokens(vec![Number(1.), Plus, Number(1.)]).unwrap(), MathExpr::BinOp( BinMathOp::Add, Box::new(MathExpr::Number(1.)), Box::new(MathExpr::Number(1.)) ) ); assert_eq!( parse_expr_from_tokens(vec![LParen, Number(1.), Plus, Number(1.), RParen]).unwrap(), MathExpr::BinOp( BinMathOp::Add, Box::new(MathExpr::Number(1.)), Box::new(MathExpr::Number(1.)) ) ); } #[test] fn advanced_examples() { assert_eq!( parse_expr_from_tokens(tokenize("1+2*3-5**10").unwrap()).unwrap(), MathExpr::BinOp( BinMathOp::Sub, Box::new(MathExpr::BinOp( BinMathOp::Add, Box::new(MathExpr::Number(1.)), Box::new(MathExpr::BinOp( BinMathOp::Mult, Box::new(MathExpr::Number(2.)), Box::new(MathExpr::Number(3.)) )) )), Box::new(MathExpr::BinOp( BinMathOp::Pow, Box::new(MathExpr::Number(5.)), Box::new(MathExpr::Number(10.)) )) ) ); assert_eq!( parse_expr_from_tokens(tokenize("(1+(2*3))-(5**10)").unwrap()).unwrap(), MathExpr::BinOp( BinMathOp::Sub, Box::new(MathExpr::BinOp( BinMathOp::Add, Box::new(MathExpr::Number(1.)), Box::new(MathExpr::BinOp( BinMathOp::Mult, Box::new(MathExpr::Number(2.)), Box::new(MathExpr::Number(3.)) )) )), Box::new(MathExpr::BinOp( BinMathOp::Pow, Box::new(MathExpr::Number(5.)), Box::new(MathExpr::Number(10.)) )) ) ); } }