asklyphe/asklyphe-frontend/src/math.rs

356 lines
9.5 KiB
Rust
Raw Normal View History

2025-06-22 12:09:21 +12:00
use tracing::{debug, error};
use once_cell::sync::Lazy;
#[derive(Debug)]
pub struct Calculation {
pub equation: String,
pub result: String,
}
pub fn calculate(query: &str) -> Option<Calculation> {
debug!("Got query {}", query);
let mut parser = Parser::new(Lexer::new(query));
2025-09-05 20:58:21 +12:00
let tree = parser.parse()?;
let res = tree.eval();
debug!("Calculation: {}", query);
2025-09-05 20:58:21 +12:00
debug!("Tree: {:?}", tree);
debug!("Result: {}", res);
Some(Calculation {equation: query.to_string(), result: res.to_string()})
2025-06-22 12:09:21 +12:00
}
// TODO: put into own crate with dependency astro-float = "0.9.2" so I can use more than f64
2025-09-05 19:35:18 +12:00
#[derive(Debug, Copy, Clone)]
2025-06-22 12:09:21 +12:00
enum Token {
2025-09-04 08:54:21 +12:00
Op(Op),
2025-09-05 16:11:11 +12:00
Atom(Atom),
2025-09-04 08:54:21 +12:00
/* Number(f64),
Func(Func),*/
}
2025-09-05 19:35:18 +12:00
#[derive(Debug, Copy, Clone)]
2025-09-04 08:54:21 +12:00
enum Op {
2025-09-05 21:14:04 +12:00
Add,
Subtract,
Multiply,
Divide,
Exponent,
LParen,
RParen,
2025-09-05 16:11:11 +12:00
Func(Func), // A function is an Op that takes whatever the next thing is and binds it, either the next number or whatever is in parens
}
2025-09-05 16:45:57 +12:00
impl Op {
fn bp_infix(&self) -> Option<(f64, f64)> {
2025-09-05 19:35:18 +12:00
match self {
// Op::LParen => Some(0.0),
// Op::RParen => Some(0.0),
Op::Add => Some((1.0, 1.1)),
Op::Subtract => Some((1.0, 1.1)),
Op::Multiply => Some((2.0, 2.1)),
Op::Divide => Some((2.0, 2.1)),
Op::Exponent => Some((3.1, 3.0)),
_ => None,
// Op::Func(_) => 0.0, // TODO: decide if this is a good LBP
2025-09-05 19:35:18 +12:00
}
2025-09-05 16:45:57 +12:00
}
fn bp_prefix(&self) -> Option<f64> {
2025-09-05 19:35:18 +12:00
match self {
Op::Func(_) => Some(0.1),
Op::Subtract => Some(5.0),
Op::Add => Some(5.0),
_ => None,
2025-09-05 19:35:18 +12:00
}
2025-09-05 16:45:57 +12:00
}
fn apply_to(&self, args: &Vec<Expr>) -> f64 {
match args.len() {
1 => match self {
Op::Subtract => Some(0.0 - args[0].eval()),
Op::Add => Some(args[0].eval().abs()),
Op::Func(f) => match f {
Func::Sine => Some(args[0].eval().sin()),
Func::Cosine => Some(args[0].eval().cos()),
Func::Tangent => Some(args[0].eval().tan()),
Func::ArcSine => Some(args[0].eval().asin()),
Func::ArcCosine => Some(args[0].eval().acos()),
Func::ArcTangent => Some(args[0].eval().atan()),
Func::Log2 => Some(args[0].eval().log2()),
Func::Log10 => Some(args[0].eval().log10()),
Func::Square => Some(args[0].eval().powf(2.0)),
Func::SquareRoot => Some(args[0].eval().sqrt()),
2025-09-05 21:56:46 +12:00
Func::LogN => None,
_ => todo!("{:?}", self)
}
_ => None,
}
2 => match self {
Op::LParen => Some(args[0].eval()),
Op::RParen => Some(args[0].eval()),
Op::Add => Some(args[0].eval() + args[1].eval()),
Op::Subtract => Some(args[0].eval() - args[1].eval()),
Op::Multiply => Some(args[0].eval() * args[1].eval()),
Op::Divide => Some(args[0].eval() / args[1].eval()),
Op::Exponent => Some(args[0].eval().powf(args[1].eval())),
2025-09-05 21:56:46 +12:00
Op::Func(Func::LogN) => Some(args[0].eval().log(args[1].eval())),
_ => None,
}
_ => None,
}.unwrap_or_else(|| {
error!("ERROR when evaluating maths expression, got invalid number of args ({}) for {:?}", args.len(), self);
// None
f64::NAN
})
}
2025-09-05 16:45:57 +12:00
}
2025-09-05 19:35:18 +12:00
#[derive(Debug, Copy, Clone)]
2025-09-04 08:54:21 +12:00
enum Atom {
2025-09-05 19:35:18 +12:00
Number(f64), // TODO: use the high precision floats library instead
2025-09-05 16:11:11 +12:00
Const(Const),
2025-06-22 12:09:21 +12:00
}
2025-09-05 19:39:48 +12:00
impl Atom {
fn get_val(&self) -> f64 {
match self {
Atom::Number(val) => *val,
Atom::Const(c) => match c {
Const::Pi => 3.141592653589793238462643383279,
Const::E => 2.718281828459045235360287471352,
}
}
}
}
2025-09-05 19:35:18 +12:00
#[derive(Debug, Copy, Clone)]
2025-06-22 12:09:21 +12:00
enum Func {
Sine,
Cosine,
Tangent,
// sin-1, cos-1, tan-1
ArcSine,
ArcCosine,
ArcTangent,
Log2,
Log10,
LogN,
Square,
SquareRoot,
}
2025-09-05 19:35:18 +12:00
#[derive(Debug, Copy, Clone)]
2025-09-05 16:11:11 +12:00
enum Const {
Pi,
E,
}
2025-09-05 19:35:18 +12:00
#[derive(Debug, Copy, Clone)]
2025-09-05 16:45:57 +12:00
enum ParseErr {
2025-06-22 12:09:21 +12:00
Eof,
Invalid,
}
// this can probably be swapped out with a lexer generator like Logos if needed
struct Lexer<'a> {
data: &'a str,
data_ptr: &'a str,
idx: usize,
2025-09-05 19:35:18 +12:00
next_tok: Result<Token, ParseErr>,
2025-06-22 12:09:21 +12:00
}
// TODO: refactor with iterator that returns Option(Token) where one token option is Eof (or a enum of Token(Token) and Eof, or just Option(Option(Token)))
impl Lexer<'_> {
2025-09-05 19:35:18 +12:00
fn new(data: &str) -> Lexer<'_> {
let mut n: Lexer = Lexer {data, data_ptr: data, idx: 0, next_tok: Err(ParseErr::Eof)};
n.next();
debug!("New finished!");
n
}
2025-06-22 12:09:21 +12:00
2025-09-05 19:35:18 +12:00
fn _next(&mut self) -> Result<Token, ParseErr> {
2025-06-22 12:09:21 +12:00
match self.data.chars().nth(self.idx) {
Some(val) => {
debug!("lexing char '{}' at idx {}", val, self.idx);
// debug!("current char '{}'", self.data.chars().nth(0).unwrap());
self.idx += 1;
// TODO: make more efficient
self.data_ptr = &self.data[self.idx..];
match val {
2025-09-05 21:14:04 +12:00
'+' => Ok(Token::Op(Op::Add)),
'-' => Ok(Token::Op(Op::Subtract)),
'×' | '*' => Ok(Token::Op(Op::Multiply)),
'÷' | '/' => Ok(Token::Op(Op::Divide)),
'^' => Ok(Token::Op(Op::Exponent)),
'(' => Ok(Token::Op(Op::LParen)),
')' => Ok(Token::Op(Op::RParen)),
2025-09-05 19:35:18 +12:00
_ if val.is_whitespace() => self._next(),
2025-09-05 16:11:11 +12:00
// TODO: maybe parse '-' as part of number so I can do '1 + -1' and similar
2025-06-22 12:09:21 +12:00
_ if val.is_digit(10) => {
let start = self.idx - 1;
self.data_ptr.chars().take_while(|c| c.is_digit(10)).for_each(|_| self.idx += 1);//.next().unwrap_or(' ').is_digit(10) {self.idx += 1;}
match self.data[start..self.idx].parse() {
2025-09-05 16:11:11 +12:00
Ok(val) => Ok(Token::Atom(Atom::Number(val))),
2025-09-05 16:45:57 +12:00
Err(e) => Err(ParseErr::Invalid),
2025-06-22 12:09:21 +12:00
}
},
_ => {
2025-09-05 19:35:18 +12:00
let mut l: usize;
l = matches(&self.data[self.idx - 1..], "sin");
if l != 0 {
self.idx += l;
return Ok(Token::Op(Op::Func(Func::Sine)));
}
l = matches(&self.data[self.idx - 1..], "cos");
if l != 0 {
self.idx += l;
return Ok(Token::Op(Op::Func(Func::Cosine)));
}
l = matches(&self.data[self.idx - 1..], "tan");
if l != 0 {
self.idx += l;
return Ok(Token::Op(Op::Func(Func::Tangent)));
}
l = matches(&self.data[self.idx - 1..], "arcsin");
if l != 0 {
self.idx += l;
return Ok(Token::Op(Op::Func(Func::ArcSine)));
}
l = matches(&self.data[self.idx - 1..], "arccos");
if l != 0 {
self.idx += l;
return Ok(Token::Op(Op::Func(Func::ArcCosine)));
}
l = matches(&self.data[self.idx - 1..], "arctan");
if l != 0 {
self.idx += l;
return Ok(Token::Op(Op::Func(Func::ArcTangent)));
}
l = matches(&self.data[self.idx - 1..], "sqrt");
if l != 0 {
self.idx += l;
return Ok(Token::Op(Op::Func(Func::SquareRoot)));
}
2025-06-22 12:09:21 +12:00
debug!("got invalid char '{}'", val);
2025-09-05 16:45:57 +12:00
Err(ParseErr::Invalid)
2025-06-22 12:09:21 +12:00
}
}
}
2025-09-05 16:45:57 +12:00
None => Err(ParseErr::Eof),
2025-06-22 12:09:21 +12:00
}
}
2025-09-05 19:35:18 +12:00
fn next(&mut self) -> Result<Token, ParseErr> {
let val = self.next_tok;
self.next_tok = self._next();
val
}
fn peek(&mut self) -> Result<Token, ParseErr> {
self.next_tok
}
2025-06-22 12:09:21 +12:00
// TODO: replace with iterator so I can do parser.parse(lexer.iter()) and parse does lex_iter.next() & such
fn lex_all(&mut self) -> Option<Vec<Token>> {
let mut tokens: Vec<Token> = vec![];
loop {
match self.next() {
2025-09-05 16:45:57 +12:00
Err(ParseErr::Eof) => return Some(tokens),
Err(ParseErr::Invalid) => return None,
2025-06-22 12:09:21 +12:00
Ok(tok) => tokens.push(tok),
}
// debug!("tokens: {:?}", tokens);
}
}
}
2025-09-05 19:35:18 +12:00
fn matches(s: &str, check: &str) -> usize {
// debug!("s: \"{}\", check: \"{}\"c_len: {}, s_len: {}, s[c_len]: {:?}, s[c_len + 1]: {:?}", s, check, check.chars().count(), s.chars().count(), s.chars().nth(check.chars().count()), s.chars().nth(check.chars().count() + 1));
match (s.chars().count(), check.chars().count()) {
(s_len, c_len) if s_len < c_len => 0,
(s_len, c_len) if s_len == c_len && s == check => c_len - 1,
(s_len, c_len) if s_len > c_len && s.starts_with(check) && s.chars().nth(c_len).unwrap().is_whitespace() => c_len,
(_, _) => 0,
}
}
2025-06-22 12:09:21 +12:00
struct Parser<'a> {
lex: Lexer<'a>,
}
impl Parser<'_> {
fn new(lex: Lexer) -> Parser { Parser {lex} }
2025-09-04 08:54:21 +12:00
fn parse(&mut self) -> Option<Expr> {
self.parse_expr(0.0).ok()
}
fn parse_expr(&mut self, min_bp: f64) -> Result<Expr, ParseErr> {
2025-09-05 16:45:57 +12:00
/*while let Ok(val) = self.lex.next() {debug!("token: {:?}", val)}
2025-09-05 16:11:11 +12:00
match self.lex.next().err() {
2025-09-04 08:54:21 +12:00
_ => return Err(ParseErr::Invalid),
2025-09-05 16:45:57 +12:00
}*/
let mut lhs: Expr = match self.lex.next() {
Ok(val) => match val {
Token::Atom(val) => Ok(Expr::Atom(val)),
Token::Op(op) => match op {
Op::LParen => self.parse_expr(0.0),
// Op::Func(f) => Ok(Expr::Node(Op::Func(f), vec![self.parse_expr(op.get_lbp())?])),
_ => match op.bp_prefix() {
Some(bp) => Ok(Expr::Node(op, vec![self.parse_expr(bp)?])),
None => {debug!("Got unexpected {:?} as prefix", op); Err(ParseErr::Invalid)}
}
2025-09-05 16:45:57 +12:00
},
},
Err(err) => Err(err),
}.map_err(|err| { debug!("Unexpected error at start of expr: {:?}", err); err })?;
2025-09-05 19:35:18 +12:00
debug!("lhs of expression is {:?}", lhs);
2025-09-05 16:45:57 +12:00
loop {
2025-09-05 19:35:18 +12:00
let op: Op = match self.lex.peek() {
2025-09-05 16:45:57 +12:00
Err(ParseErr::Eof) => break,
2025-09-05 19:35:18 +12:00
Err(e) => { debug!("In expr got err {:?}", e); Err(e) },
2025-09-05 16:45:57 +12:00
Ok(tok) => match tok {
Token::Op(op) => match op {
2025-09-05 21:14:04 +12:00
Op::RParen => break,
_ => Ok(op),
2025-09-05 16:45:57 +12:00
}
2025-09-05 19:35:18 +12:00
v => { debug!("Got unexpected token {:?}", v); Err(ParseErr::Invalid) },
2025-09-05 16:45:57 +12:00
}
2025-09-05 19:35:18 +12:00
}.map_err(|err| { debug!("Unexpected error inside expr at {:?}", err); err })?;
if let Some((lbp, rbp)) = op.bp_infix() {
if (lbp < min_bp) { break; }
self.lex.next();
let rhs: Expr = self.parse_expr(rbp)?;
lhs = Expr::Node(op, vec![lhs, rhs]);
} else {
debug!("Got unexpected non-infix operator in expression: {:?}", op);
return Err(ParseErr::Invalid);
}
2025-09-04 08:54:21 +12:00
}
2025-09-05 16:45:57 +12:00
Ok(lhs)
2025-09-04 08:54:21 +12:00
}
2025-06-22 12:09:21 +12:00
}
2025-09-04 08:54:21 +12:00
#[derive(Debug)]
enum Expr {
2025-09-05 16:45:57 +12:00
Atom(Atom),
2025-09-04 08:54:21 +12:00
Node(Op, Vec<Expr>),
2025-06-22 12:09:21 +12:00
}
impl Expr {
fn eval(&self) -> f64 {
let res = match (self) {
Expr::Atom(at) => at.get_val(),
Expr::Node(op, exprs) => op.apply_to(exprs),
};
// debug!("{:?} evaluated to {}", self, res);
res
}
}