application-launcher/src/math/parser.rs
Kalle Struik 93131165ee Math module
Yes this is a full math parser and interpreter
2025-06-25 12:08:38 +02:00

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.))
))
)
);
}
}