use regex::{CaptureMatches, Captures, Regex};
use std::collections::VecDeque;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone)]
enum Token {
Number(f64),
Operator(String),
Function(String),
LeftParen,
RightParen,
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Token::Number(n) => write!(f, "{}", n),
Token::Operator(op) => write!(f, "{}", op),
Token::Function(func) => write!(f, "{}", func),
Token::LeftParen => write!(f, "("),
Token::RightParen => write!(f, ")"),
}
}
}
impl Token {
fn precedence(&self) -> i32 {
match self {
Token::Operator(op) => match op.as_str() {
"+" | "-" => 1,
"*" | "/" => 2,
"^" => 3,
_ => 0,
},
Token::Function(_) => 4,
_ => 0,
}
}
fn to_string(&self) -> String {
match self {
Token::Number(num) => num.to_string(),
Token::Operator(op) => op.clone(),
Token::Function(func) => func.clone(),
Token::LeftParen => String::from("("),
Token::RightParen => String::from(")"),
}
}
}
pub fn evaluate_expression(expression: &str) -> Result<f64, String> {
let expression = replace_key_words(&expression.replace(" ", "")); let tokens = tokenize(&expression)?;
let rpn = to_rpn(&tokens)?;
evaluate_rpn(&rpn)
}
fn replace_key_words(input: &str) -> String {
let regex = Regex::new(r"(?i)pow\s*\(\s*(-?\d+(\.\d*)?)\s*,\s*(-?\d+(\.\d*)?)\s*\)").unwrap();
regex
.replace_all(&input, |caps: &Captures| {
let base = &caps[1];
let exponent = &caps[3];
format!("{}^{}", base, exponent)
})
.to_string()
}
fn tokenize(expression: &str) -> Result<Vec<Token>, String> {
let rexpression = Regex::new(r"(?i)(\d+\.?\d*|\+|\-|\*|\/|\^|\(|\)|ln|log2|exp|asin|acos|atan|sin|cos|tan|abs|sqrt|log10|PI|E)")
.unwrap();
let mut tokens = Vec::new();
let mut iter = rexpression.captures_iter(expression);
while let Some(cap) = iter.next() {
let token = &cap[0];
push_tokens(&mut iter, &mut tokens, token)
}
Ok(tokens)
}
fn push_tokens(iter: &mut CaptureMatches<'_, '_>, tokens: &mut Vec<Token>, token: &str) {
if let Ok(number) = f64::from_str(token) {
tokens.push(Token::Number(number));
} else if token == "-" {
if let Some(cap_fwd) = iter.next() {
let token_fwd = &cap_fwd[0];
match tokens.last() {
None => {
if let Ok(number_fwd) = f64::from_str(token_fwd) {
tokens.push(Token::Number(number_fwd * -1.00));
}else if let Ok(number_fwd) = evaluate_const(&token_fwd.to_string()) {
tokens.push(Token::Number(number_fwd * -1.00 ));
} else {
tokens.push(Token::Number(-1.00));
tokens.push(Token::Operator("*".to_owned()));
push_tokens(iter, tokens, token_fwd);
}
}
Some(c) => {
if !f64::from_str(&c.to_string()).is_err() {
if let Ok(number_fwd) = f64::from_str(token_fwd) {
tokens.push(Token::Operator(token.to_string()));
tokens.push(Token::Number(number_fwd));
}else if let Ok(number_fwd) = evaluate_const(&token_fwd.to_string()) {
tokens.push(Token::Number(number_fwd * -1.00 ));
}else{
tokens.push(Token::Operator("-".to_owned()));
push_tokens(iter, tokens, token_fwd);
}
}else if c.to_string() == "(" || is_operator(&c.to_string()) {
if let Ok(number_fwd) = f64::from_str(token_fwd) {
tokens.push(Token::Number(number_fwd * -1.00));
}else{
tokens.push(Token::Number(-1.00));
tokens.push(Token::Operator("*".to_owned()));
push_tokens(iter, tokens, token_fwd);
}
} else {
tokens.push(Token::Operator(token.to_string()));
push_tokens(iter, tokens, token_fwd);
}
}
}
} else {
tokens.push(Token::Operator(token.to_string()));
}
} else if token == "+" || token == "*" || token == "/" || token == "^" {
tokens.push(Token::Operator(token.to_string()));
} else if token == "(" {
tokens.push(Token::LeftParen);
} else if token == ")" {
tokens.push(Token::RightParen);
} else if let Ok(number) = evaluate_const(&token.to_string()) {
tokens.push(Token::Number(number));
} else {
tokens.push(Token::Function(token.to_string()));
}
}
fn is_operator(c: &str) -> bool {
c == "+" || c == "-" || c == "*" || c == "/" || c == "^"
}
fn evaluate_const(p_const: &String) -> Result<f64, &str> {
match p_const.to_uppercase().as_str() {
"PI" => return Ok(std::f64::consts::PI),
"E" => return Ok(std::f64::consts::E),
_ => return Err(p_const),
};
}
fn to_rpn(tokens: &[Token]) -> Result<Vec<Token>, String> {
let mut output = Vec::new();
let mut operators = VecDeque::new();
for token in tokens {
match token {
Token::Number(_) => output.push(token.clone()),
Token::LeftParen => operators.push_back(Token::LeftParen),
Token::RightParen => {
while let Some(op) = operators.pop_back() {
match op {
Token::LeftParen => break,
_ => output.push(op),
}
}
}
Token::Operator(_) | Token::Function(_) => {
while let Some(op) = operators.back() {
let _p_token = Token::Operator("^".to_string());
if matches!(op, Token::Operator(_) | Token::Function(_))
&& (op.precedence() > token.precedence()
|| (op.precedence() == token.precedence() && matches!(token, _p_token)))
{
output.push(operators.pop_back().unwrap());
} else {
break;
}
}
operators.push_back(token.clone());
}
}
}
while let Some(op) = operators.pop_back() {
output.push(op);
}
Ok(output)
}
fn evaluate_rpn(rpn: &[Token]) -> Result<f64, String> {
let mut stack = VecDeque::new();
for token in rpn {
match token {
Token::Number(value) => {
stack.push_back(*value);
}
Token::Operator(op) => {
let b = stack
.pop_back()
.ok_or("Invalid expression: not enough values for operator (b)")?;
let a = stack
.pop_back()
.ok_or("Invalid expression: not enough values for operator (a)")?;
let result = match op.as_str() {
"+" => a + b,
"-" => a - b,
"*" => a * b,
"/" => a / b,
"^" => a.powf(b),
_ => return Err(format!("Unknown operator {:?}", token)), };
stack.push_back(result);
}
Token::Function(func) => {
let arg = stack
.pop_back()
.ok_or("Invalid expression: not enough values for function")?;
let result = match func.to_lowercase().as_str() {
"sin" => arg.sin(),
"cos" => arg.cos(),
"tan" => arg.tan(),
"asin" => arg.asin(),
"acos" => arg.acos(),
"atan" => arg.atan(),
"exp" => arg.exp(),
"ln" => arg.ln(),
"log" => arg.log10(),
"log2" => arg.log2(),
"abs" => arg.abs(),
"sqrt" => arg.sqrt(),
"log10" => arg.log10(),
_ => return Err(format!("Unknown function: {:?}", token)), };
stack.push_back(result);
}
_ => return Err(format!("Invalid token: {:?}", token)),
}
}
stack
.pop_back()
.ok_or("Invalid expression: no result on stack".to_owned())
}