pub mod number;
pub mod operations;
use number::Number;
pub trait ExprParser {
fn remove_spaces(&self) -> String;
fn add_spaces(&self) -> String;
fn to_postfix_vec(&self) -> Vec<String>;
fn to_postfix(&self, seperator: Option<&str>) -> String;
fn eval(&self) -> Number;
}
impl ExprParser for String {
fn remove_spaces(&self) -> String {
self.trim().split(' ').collect()
}
fn add_spaces(&self) -> String {
let mut last_ch_is_operator = false;
let mut new_string = String::new();
for c in self.chars() {
if c == ' ' {
continue;
}
if c.is_ascii_digit() || c == '.' {
new_string.push(c);
last_ch_is_operator = false;
} else if c.is_ascii_alphabetic() {
new_string.push(c);
last_ch_is_operator = true;
} else {
if !last_ch_is_operator {
new_string.push(' ');
}
new_string.push(c);
new_string.push(' ');
last_ch_is_operator = true;
}
}
new_string.trim().to_string()
}
fn to_postfix_vec(&self) -> Vec<String> {
let mut op_buff = String::new();
let mut num_buff = String::new();
let mut result: Vec<String> = vec![];
let mut op_stack: Vec<String> = vec![];
let mut prev_is_op: bool = true;
for c in self.chars() {
if c.is_whitespace() {
continue;
}
if c == '.' || c.is_ascii_digit() {
num_buff.push(c);
prev_is_op = false;
continue;
}
try_push_and_clear(&mut result, &mut num_buff);
if c.is_ascii_alphabetic() {
op_buff.push(c);
continue;
}
if c == '(' {
op_stack.push(c.to_string());
try_push_and_clear(&mut op_stack, &mut op_buff);
prev_is_op = true;
} else if c == ')' {
while let Some(op) = op_stack.pop() {
if op == '('.to_string() {
break;
}
result.push(op);
}
prev_is_op = false;
} else {
if (c == '+' || c == '-') && prev_is_op {
num_buff.push(c);
continue;
}
let op_str = c.to_string();
while let Some(op) = op_stack.last() {
if priority(&op_str) > priority(op) {
break;
}
result.push(op_stack.pop().unwrap());
}
op_stack.push(op_str);
prev_is_op = true;
}
}
try_push_and_clear(&mut result, &mut num_buff);
while let Some(op) = op_stack.pop() {
result.push(op);
}
result
}
fn to_postfix(&self, seperator: Option<&str>) -> String {
let sep: &str = match seperator {
Some(c) => c,
None => "",
};
self.to_postfix_vec().join(sep)
}
fn eval(&self) -> Number {
let pf_vec = self.to_postfix_vec();
let mut val_stack: Vec<Number> = vec![];
for pf in pf_vec {
match pf.as_str() {
"+" => do_binary_op(&mut val_stack, operations::add),
"-" => do_binary_op(&mut val_stack, operations::sub),
"*" => do_binary_op(&mut val_stack, operations::mul),
"/" | "\\" => do_binary_op(&mut val_stack, operations::div),
"^" => do_binary_op(&mut val_stack, operations::pow),
"sqrt" => do_unary_op(&mut val_stack, operations::sqrt),
"abs" => do_unary_op(&mut val_stack, operations::abs),
"cos" => do_unary_op(&mut val_stack, operations::cos),
"sin" => do_unary_op(&mut val_stack, operations::sin),
"tan" => do_unary_op(&mut val_stack, operations::tan),
"arccos" => do_unary_op(&mut val_stack, operations::acos),
"arcsin" => do_unary_op(&mut val_stack, operations::asin),
"arctan" => do_unary_op(&mut val_stack, operations::atan),
_ => {
if pf.contains('.') {
let parsed_float = pf.parse::<f64>();
match parsed_float {
Ok(res) => {
val_stack.push(Number::Float(res));
}
Err(_) => {
panic!("Fail to parse {:?} as float, please check your input.", pf);
}
}
} else {
let parsed_int = pf.parse::<i32>();
match parsed_int {
Ok(res) => {
val_stack.push(Number::Integer(res));
}
Err(_) => {
panic!(
"Fail to parse {:?} as integer, please check your input.",
pf
);
}
}
}
}
}
}
val_stack.pop().unwrap()
}
}
fn priority(s: &String) -> u8 {
if s == "+" || s == "-" {
1
} else if s == "*" || s == "\\" || s == "/" {
2
} else if s == "^" {
3
} else {
0
}
}
fn try_push_and_clear(seq: &mut Vec<String>, item: &mut String) {
if !item.is_empty() {
seq.push(item.clone());
item.clear();
}
}
fn do_unary_op(stack: &mut Vec<Number>, op: fn(Option<Number>) -> Number) {
let res: Number = op(stack.pop());
stack.push(res);
}
fn do_binary_op(stack: &mut Vec<Number>, op: fn(Option<Number>, Option<Number>) -> Number) {
let right: Option<Number> = stack.pop();
let left: Option<Number> = stack.pop();
let res: Number = op(left, right);
stack.push(res);
}