210 lines
6.6 KiB
Rust
210 lines
6.6 KiB
Rust
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 <eof>
|
|
|
|
// Grammer for LR parser
|
|
// <> is a terminal
|
|
//
|
|
// <unOp> := '-'
|
|
// <binOp1> := '+' | '-'
|
|
// <binOp2> := '/' | '*'
|
|
// <binOp3> := '**' | '^'
|
|
// <function> := 'sqrt' | 'sin' | 'cos' | etc
|
|
//
|
|
//
|
|
// F :=
|
|
// | <number>
|
|
// | <sym>
|
|
// | '(' binOp1 ')'
|
|
// | <unOp> F
|
|
// | <function> '(' binOp1 ')'
|
|
//
|
|
//
|
|
// binOp3 :=
|
|
// | binOp3 <binOp3> F
|
|
// | F
|
|
//
|
|
// binOp2 :=
|
|
// | binOp2 <binOp2> binOp3
|
|
// | binOp2 '(' expr ')' [Implicit multiplication] // TODO:
|
|
// | binOp3
|
|
//
|
|
// binOp1 :=
|
|
// | binOp1 <binOp1> binOp2
|
|
// | binOp2
|
|
//
|
|
// goal := binOp1 <eof>
|
|
|
|
pub fn parse_expr_from_tokens(tokens: Vec<MathToken>) -> Result<MathExpr, MathParserError> {
|
|
let mut tokens = tokens.into_iter().peekable();
|
|
parse_expr(&mut tokens)
|
|
}
|
|
|
|
fn parse_primary<'a>(
|
|
tokens: &mut Peekable<impl Iterator<Item = MathToken<'a>>>,
|
|
) -> Result<MathExpr<'a>, 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<impl Iterator<Item = MathToken<'a>>>,
|
|
) -> Result<MathExpr<'a>, MathParserError<'a>> {
|
|
parse_expr_prime(parse_primary(tokens)?, tokens, 0)
|
|
}
|
|
|
|
fn parse_expr_prime<'a>(
|
|
mut lhs: MathExpr<'a>,
|
|
tokens: &mut Peekable<impl Iterator<Item = MathToken<'a>>>,
|
|
min_precedence: u8,
|
|
) -> Result<MathExpr<'a>, 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<BinMathOp, MathParserError> {
|
|
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.))
|
|
))
|
|
)
|
|
);
|
|
}
|
|
}
|