asklyphe/asklyphe-frontend/src/math.rs

354 lines
9.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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));
let tree = parser.parse()?;
let res = tree.eval();
debug!("Calculation: {}", query);
debug!("Tree: {:?}", tree);
debug!("Result: {}", res);
Some(Calculation {equation: query.to_string(), result: res.to_string()})
}
// TODO: put into own crate with dependency astro-float = "0.9.2" so I can use more than f64
#[derive(Debug, Copy, Clone)]
enum Token {
Op(Op),
Atom(Atom),
/* Number(f64),
Func(Func),*/
}
#[derive(Debug, Copy, Clone)]
enum Op {
Add,
Subtract,
Multiply,
Divide,
Exponent,
LParen,
RParen,
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
}
impl Op {
fn bp_infix(&self) -> Option<(f64, f64)> {
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
}
}
fn bp_prefix(&self) -> Option<f64> {
match self {
Op::Func(_) => Some(0.1),
Op::Subtract => Some(5.0),
Op::Add => Some(5.0),
_ => None,
}
}
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::LogN => Some(),
Func::Square => Some(args[0].eval().powf(2.0)),
Func::SquareRoot => Some(args[0].eval().sqrt()),
_ => 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())),
_ => None,
}
_ => None,
}.unwrap_or_else(|| {
error!("ERROR when evaluating maths expression, got invalid number of args ({}) for {:?}", args.len(), self);
// None
f64::NAN
})
}
}
#[derive(Debug, Copy, Clone)]
enum Atom {
Number(f64), // TODO: use the high precision floats library instead
Const(Const),
}
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,
}
}
}
}
#[derive(Debug, Copy, Clone)]
enum Func {
Sine,
Cosine,
Tangent,
// sin-1, cos-1, tan-1
ArcSine,
ArcCosine,
ArcTangent,
Log2,
Log10,
LogN,
Square,
SquareRoot,
}
#[derive(Debug, Copy, Clone)]
enum Const {
Pi,
E,
}
#[derive(Debug, Copy, Clone)]
enum ParseErr {
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,
next_tok: Result<Token, ParseErr>,
}
// 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<'_> {
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
}
fn _next(&mut self) -> Result<Token, ParseErr> {
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 {
'+' => 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)),
_ if val.is_whitespace() => self._next(),
// TODO: maybe parse '-' as part of number so I can do '1 + -1' and similar
_ 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() {
Ok(val) => Ok(Token::Atom(Atom::Number(val))),
Err(e) => Err(ParseErr::Invalid),
}
},
_ => {
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)));
}
debug!("got invalid char '{}'", val);
Err(ParseErr::Invalid)
}
}
}
None => Err(ParseErr::Eof),
}
}
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
}
// 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() {
Err(ParseErr::Eof) => return Some(tokens),
Err(ParseErr::Invalid) => return None,
Ok(tok) => tokens.push(tok),
}
// debug!("tokens: {:?}", tokens);
}
}
}
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,
}
}
struct Parser<'a> {
lex: Lexer<'a>,
}
impl Parser<'_> {
fn new(lex: Lexer) -> Parser { Parser {lex} }
fn parse(&mut self) -> Option<Expr> {
self.parse_expr(0.0).ok()
}
fn parse_expr(&mut self, min_bp: f64) -> Result<Expr, ParseErr> {
/*while let Ok(val) = self.lex.next() {debug!("token: {:?}", val)}
match self.lex.next().err() {
_ => return Err(ParseErr::Invalid),
}*/
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)}
}
},
},
Err(err) => Err(err),
}.map_err(|err| { debug!("Unexpected error at start of expr: {:?}", err); err })?;
debug!("lhs of expression is {:?}", lhs);
loop {
let op: Op = match self.lex.peek() {
Err(ParseErr::Eof) => break,
Err(e) => { debug!("In expr got err {:?}", e); Err(e) },
Ok(tok) => match tok {
Token::Op(op) => match op {
Op::RParen => break,
_ => Ok(op),
}
v => { debug!("Got unexpected token {:?}", v); Err(ParseErr::Invalid) },
}
}.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);
}
}
Ok(lhs)
}
}
#[derive(Debug)]
enum Expr {
Atom(Atom),
Node(Op, Vec<Expr>),
}
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
}
}