Files
calc/src/main.rs
2024-04-15 10:15:59 +02:00

773 lines
24 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 std::{io, io::Write};
#[derive(Debug, PartialEq)]
enum TokenizeError {
NumberParseError(String),
}
#[derive(Debug, PartialEq)]
enum InfixToPostfixError {
ExpectedOperator,
ExpectedLeftParenthesis,
}
#[derive(Debug, PartialEq)]
enum CalculateError {
PostfixExpectedNumbers,
EmptyStack,
}
#[derive(PartialEq)]
enum Associativity {
Right,
Left,
}
#[derive(Debug, PartialEq, Clone)]
enum Token {
LeftParenthesis,
RightParenthesis,
Multiply,
Divide,
Add,
Subtract,
Modulus,
Power,
Separator,
Number(f64),
Function(FunctionType),
}
#[derive(Clone, PartialEq, Debug)]
enum FunctionType {
Sine,
Cosine,
NaturalLog,
Log10,
Max,
Min,
SquareRoot,
}
fn main() {
if std::env::args().len() == 1 {
loop {
print!("> ");
io::stdout().flush().expect("failed to flush stdout");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
let result = compute(&input);
print_result(result);
}
} else {
let arg = match std::env::args().nth(1) {
Some(v) => v,
None => {
eprintln!("missing argument, Usage: expr, Example: 1 + 1 * 3 (6 - 4)^2");
std::process::exit(1);
}
};
let result = compute(&arg);
print_result(result);
}
}
impl Token {
fn is_operator(&self) -> bool {
use Token::*;
match self {
Multiply | Divide | Add | Subtract | Modulus | Power => true,
LeftParenthesis | RightParenthesis | Separator | Number(_) | Function(_) => false,
}
}
}
fn print_result(result: f64) {
if result > 10_000. || (result < 0.0001 && result > -0.0001) && result != 0. {
println!("{:e}", result);
} else {
println!("{result}");
}
}
fn compute(input: &str) -> f64 {
let input = &input
.trim()
.replace(' ', "")
.replace("pi", "π")
.replace("tau", "τ");
let tokens = match tokenize(input) {
Ok(v) => v,
Err(e) => {
eprintln!("failed to parse tokens: {:?}", e);
std::process::exit(1);
}
};
let tokens = match implicit_operations(tokens) {
Some(v) => v,
None => {
eprintln!("expected at least two tokens");
std::process::exit(1);
}
};
let tokens = match infix_to_postfix(tokens) {
Ok(v) => v,
Err(e) => {
eprintln!("failed to convert infix to postfix: {:?}", e);
std::process::exit(1);
}
};
match calculate(tokens) {
Ok(v) => v,
Err(e) => {
eprintln!("failed to calculate result: {:?}", e);
std::process::exit(1);
}
}
}
impl std::fmt::Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Token::Number(n) => write!(f, "{n}"),
Token::Add => write!(f, "+"),
Token::Subtract => write!(f, "-"),
Token::Multiply => write!(f, "*"),
Token::Divide => write!(f, "/"),
Token::Power => write!(f, "^"),
Token::Modulus => write!(f, "%"),
Token::LeftParenthesis => write!(f, "("),
Token::RightParenthesis => write!(f, ")"),
Token::Separator => write!(f, ","),
Token::Function(function) => match function {
FunctionType::Sine => write!(f, "sin"),
FunctionType::Cosine => write!(f, "cos"),
FunctionType::NaturalLog => write!(f, "ln"),
FunctionType::Max => write!(f, "max"),
FunctionType::Min => write!(f, "min"),
FunctionType::SquareRoot => write!(f, "sqrt"),
FunctionType::Log10 => write!(f, "log"),
},
}
}
}
fn tokenize(input: &str) -> Result<Vec<Token>, TokenizeError> {
let mut num_buf = String::new();
let mut fun_buf = String::new();
let mut tokens = Vec::new();
let mut prev = None;
for c in input.chars() {
let token = match c {
'(' => Token::LeftParenthesis,
')' => Token::RightParenthesis,
'*' | '×' => Token::Multiply,
'/' | '÷' => Token::Divide,
'+' => Token::Add,
'-' | '' => {
// exception for negative exponent for scientific notation
if let Some(p) = prev {
if p == 'E' {
num_buf.push(c);
continue;
}
}
Token::Subtract
}
'^' => Token::Power,
'%' => Token::Modulus,
',' => Token::Separator,
'π' => Token::Number(std::f64::consts::PI),
'e' => Token::Number(std::f64::consts::E),
'τ' => Token::Number(std::f64::consts::TAU),
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'E' | '.' => {
push_buf(&mut tokens, &mut fun_buf)?;
num_buf.push(c);
prev = Some(c);
continue;
}
_ => {
push_buf(&mut tokens, &mut num_buf)?;
fun_buf.push(c);
prev = Some(c);
continue;
}
};
push_buf(&mut tokens, &mut num_buf)?;
push_buf(&mut tokens, &mut fun_buf)?;
tokens.push(token);
prev = Some(c);
}
push_buf(&mut tokens, &mut num_buf)?;
push_buf(&mut tokens, &mut fun_buf)?;
Ok(tokens)
}
fn push_buf(tokens: &mut Vec<Token>, buf: &mut String) -> Result<(), TokenizeError> {
if !buf.is_empty() {
tokens.push(parse_buffer(buf)?);
buf.clear();
}
Ok(())
}
fn parse_buffer(buf: &str) -> Result<Token, TokenizeError> {
match buf {
"sin" => Ok(Token::Function(FunctionType::Sine)),
"cos" => Ok(Token::Function(FunctionType::Cosine)),
"ln" => Ok(Token::Function(FunctionType::NaturalLog)),
"log" => Ok(Token::Function(FunctionType::Log10)),
"max" => Ok(Token::Function(FunctionType::Max)),
"min" => Ok(Token::Function(FunctionType::Min)),
"sqrt" => Ok(Token::Function(FunctionType::SquareRoot)),
_ => {
if let Ok(number) = buf.parse() {
Ok(Token::Number(number))
} else {
Err(TokenizeError::NumberParseError(buf.to_string()))
}
}
}
}
fn implicit_operations(tokens: Vec<Token>) -> Option<Vec<Token>> {
if tokens.len() < 2 {
return Some(tokens);
}
let mut new_tokens = vec![tokens[0].clone()];
let mut prev = tokens[0].clone();
for token in tokens.iter().skip(1) {
let mut token = token.clone();
match prev {
Token::Subtract => match token {
Token::Add => {
new_tokens.pop();
token = Token::Subtract
}
Token::Subtract => {
new_tokens.pop();
token = Token::Add;
}
_ => (),
},
Token::Number(_) => match token {
Token::LeftParenthesis | Token::Number(_) | Token::Function(_) => {
new_tokens.push(Token::Multiply)
}
_ => (),
},
Token::RightParenthesis => match token {
Token::LeftParenthesis | Token::Number(_) => new_tokens.push(Token::Multiply),
_ => (),
},
_ => (),
}
new_tokens.push(token.clone());
prev = token.clone();
}
let mut tokens = new_tokens.iter();
let mut new_tokens = Vec::new();
let (mut a, mut b) = (tokens.next()?, tokens.next()?);
new_tokens.push(a.clone());
new_tokens.push(b.clone());
if new_tokens[0] == Token::Subtract {
if let Token::Number(n) = new_tokens[1] {
new_tokens.pop();
new_tokens.pop();
new_tokens.push(Token::Number(-n));
} else if new_tokens[1] == Token::LeftParenthesis {
new_tokens.pop();
new_tokens.pop();
new_tokens.push(Token::Number(-1.));
new_tokens.push(Token::Multiply);
new_tokens.push(Token::LeftParenthesis);
}
}
for t in tokens {
let c = t;
match a {
Token::Number(_) => new_tokens.push(c.clone()),
_ => {
if *b == Token::Subtract {
if let Token::Number(n) = c {
if a.is_operator() {
new_tokens.pop();
new_tokens.push(Token::Number(-n));
} else {
new_tokens.push(c.clone());
}
} else if *c == Token::LeftParenthesis {
if *a == Token::LeftParenthesis {
new_tokens.pop();
new_tokens.push(Token::Number(-1.));
new_tokens.push(Token::Multiply);
new_tokens.push(Token::LeftParenthesis);
} else {
new_tokens.push(c.clone());
}
}
} else {
new_tokens.push(c.clone());
}
}
}
a = b;
b = c;
}
Some(new_tokens)
}
fn associativity(token: &Token) -> Option<Associativity> {
match token {
Token::Power => Some(Associativity::Right),
Token::Number(_) => None,
_ => Some(Associativity::Left),
}
}
fn infix_to_postfix(tokens: Vec<Token>) -> Result<Vec<Token>, InfixToPostfixError> {
let mut output = Vec::new();
let mut op_stack: Vec<Token> = Vec::new();
for token in tokens.iter() {
match token {
Token::Number(n) => output.push(Token::Number(*n)),
Token::LeftParenthesis => op_stack.push(Token::LeftParenthesis),
Token::RightParenthesis => {
while op_stack.last() != Some(&Token::LeftParenthesis) {
output.push(
op_stack
.pop()
.ok_or(InfixToPostfixError::ExpectedLeftParenthesis)?,
);
}
assert!(op_stack.last() == Some(&Token::LeftParenthesis));
op_stack.pop();
}
Token::Function(_) => op_stack.push(token.clone()),
Token::Separator => {
while op_stack.last() != Some(&Token::LeftParenthesis) {
output.push(
op_stack
.pop()
.ok_or(InfixToPostfixError::ExpectedLeftParenthesis)?,
);
}
}
op => {
while let Some(op2) = op_stack.last() {
if op2 != &Token::LeftParenthesis
&& (precedence(op2) > precedence(op)
|| (precedence(op) == precedence(op2)
&& associativity(op)
.ok_or(InfixToPostfixError::ExpectedOperator)?
== Associativity::Left))
{
output.push(
op_stack
.pop()
.ok_or(InfixToPostfixError::ExpectedLeftParenthesis)?,
);
} else {
break;
}
}
op_stack.push(op.clone());
}
}
}
while let Some(item) = op_stack.pop() {
output.push(item);
}
Ok(output)
}
fn precedence(token: &Token) -> u8 {
match token {
Token::Add | Token::Subtract => 0,
Token::Multiply | Token::Divide | Token::Modulus => 1,
Token::Power => 2,
Token::LeftParenthesis | Token::RightParenthesis | Token::Function(_) => 3,
Token::Number(_) => unreachable!(),
Token::Separator => unreachable!(),
}
}
fn calculate(tokens: Vec<Token>) -> Result<f64, CalculateError> {
let mut stack = Vec::new();
let mut tokens = tokens.iter();
if tokens.len() == 1 {
match tokens.next() {
Some(Token::Number(n)) => return Ok(*n),
Some(_) => return Err(CalculateError::PostfixExpectedNumbers),
None => return Err(CalculateError::EmptyStack),
}
}
while let Some(mut token) = tokens.next() {
while let Token::Number(_) = token {
stack.push(token.clone());
if let Some(t) = tokens.next() {
token = t;
} else {
break;
}
}
let a = stack.pop().ok_or(CalculateError::PostfixExpectedNumbers)?;
if let Token::Function(f) = token {
if let Token::Number(n) = a {
let n = match f {
FunctionType::Sine => Some(n.sin()),
FunctionType::Cosine => Some(n.cos()),
FunctionType::NaturalLog => Some(n.ln()),
FunctionType::Log10 => Some(n.log10()),
FunctionType::SquareRoot => Some(n.sqrt()),
_ => None,
};
if let Some(n) = n {
stack.push(Token::Number(n));
continue;
}
} else {
return Err(CalculateError::PostfixExpectedNumbers);
}
}
let b = stack.pop().ok_or(CalculateError::PostfixExpectedNumbers)?;
let (a, b) = match (a, b) {
(Token::Number(a), Token::Number(b)) => (b, a),
_ => unreachable!(),
};
let n = match token {
Token::Add => a + b,
Token::Subtract => a - b,
Token::Multiply => a * b,
Token::Divide => a / b,
Token::Power => a.powf(b),
Token::Modulus => a % b,
Token::Function(FunctionType::Max) => a.max(b),
Token::Function(FunctionType::Min) => a.min(b),
_ => unreachable!(),
};
stack.push(Token::Number(n));
}
if stack.is_empty() {
return Err(CalculateError::EmptyStack);
}
if let Token::Number(n) = stack[0] {
Ok(n)
} else {
Err(CalculateError::PostfixExpectedNumbers)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn token_test() {
assert_eq!(tokenize("("), Ok(vec![Token::LeftParenthesis]));
assert_eq!(tokenize(")"), Ok(vec![Token::RightParenthesis]));
assert_eq!(tokenize("*"), Ok(vec![Token::Multiply]));
assert_eq!(tokenize("%"), Ok(vec![Token::Modulus]));
assert_eq!(tokenize("/"), Ok(vec![Token::Divide]));
assert_eq!(tokenize("+"), Ok(vec![Token::Add]));
assert_eq!(tokenize("-"), Ok(vec![Token::Subtract]));
assert_eq!(tokenize("3"), Ok(vec![Token::Number(3.)]));
assert_eq!(tokenize("e"), Ok(vec![Token::Number(std::f64::consts::E)]));
assert_eq!(tokenize("π"), Ok(vec![Token::Number(std::f64::consts::PI)]));
assert_eq!(
tokenize("max"),
Ok(vec![Token::Function(FunctionType::Max)])
);
assert_eq!(
tokenize("sin"),
Ok(vec![Token::Function(FunctionType::Sine)])
);
assert_eq!(
tokenize("cos"),
Ok(vec![Token::Function(FunctionType::Cosine)])
);
assert_eq!(
tokenize("sqrt"),
Ok(vec![Token::Function(FunctionType::SquareRoot)])
);
assert_eq!(
tokenize("ln"),
Ok(vec![Token::Function(FunctionType::Log10)])
);
assert_eq!(
tokenize("log"),
Ok(vec![Token::Function(FunctionType::Log10)])
);
assert_eq!(
tokenize("3.14159265358979323"),
Ok(vec![Token::Number(3.14159265358979323)])
);
assert_eq!(tokenize("0."), Ok(vec![Token::Number(0.)]));
assert_eq!(
tokenize("."),
Err(TokenizeError::NumberParseError(".".to_string()))
);
assert_eq!(
tokenize(".."),
Err(TokenizeError::NumberParseError("..".to_string()))
);
assert_eq!(
tokenize(".1."),
Err(TokenizeError::NumberParseError(".1.".to_string()))
);
assert_eq!(tokenize("1E3"), Ok(vec![Token::Number(1000.)]));
assert_eq!(
tokenize("E3"),
Err(TokenizeError::NumberParseError("E3".to_string()))
);
assert_eq!(
tokenize("1+1"),
Ok(vec![Token::Number(1.), Token::Add, Token::Number(1.)])
);
assert_eq!(
tokenize("1*1"),
Ok(vec![Token::Number(1.), Token::Multiply, Token::Number(1.)])
);
assert_eq!(
tokenize("(1*1)"),
Ok(vec![
Token::LeftParenthesis,
Token::Number(1.),
Token::Multiply,
Token::Number(1.),
Token::RightParenthesis,
])
);
}
#[test]
fn implicit_test() {
// (3)2 == (3)*2
assert_eq!(
implicit_operations(vec![
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::Number(2.),
]),
Some(vec![
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::Multiply,
Token::Number(2.),
])
);
// 3(2) == 3*(2)
assert_eq!(
implicit_operations(vec![
Token::Number(3.),
Token::LeftParenthesis,
Token::Number(2.),
Token::RightParenthesis,
]),
Some(vec![
Token::Number(3.),
Token::Multiply,
Token::LeftParenthesis,
Token::Number(2.),
Token::RightParenthesis,
])
);
// (3)(2) = (3)*(2)
assert_eq!(
implicit_operations(vec![
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::LeftParenthesis,
Token::Number(2.),
Token::RightParenthesis
]),
Some(vec![
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::Multiply,
Token::LeftParenthesis,
Token::Number(2.),
Token::RightParenthesis
])
);
// 3 -- 2 == 3 + 2
assert_eq!(
implicit_operations(vec![
Token::Number(3.),
Token::Subtract,
Token::Subtract,
Token::Number(2.),
]),
Some(vec![Token::Number(3.), Token::Add, Token::Number(2.),])
);
assert_eq!(
implicit_operations(vec![Token::Subtract, Token::Number(3.),]),
Some(vec![Token::Number(-3.)])
);
assert_eq!(
implicit_operations(vec![
Token::Subtract,
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
]),
Some(vec![
Token::Number(-1.),
Token::Multiply,
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
])
);
assert_eq!(
implicit_operations(vec![
Token::LeftParenthesis,
Token::Subtract,
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::RightParenthesis,
]),
Some(vec![
Token::LeftParenthesis,
Token::Number(-1.),
Token::Multiply,
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::RightParenthesis,
])
);
assert_eq!(
implicit_operations(vec![
Token::Subtract,
Token::LeftParenthesis,
Token::Subtract,
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::RightParenthesis,
]),
Some(vec![
Token::Number(-1.),
Token::Multiply,
Token::LeftParenthesis,
Token::Number(-1.),
Token::Multiply,
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::RightParenthesis,
])
);
}
#[test]
fn infix_to_postfix_test() {
assert_eq!(
infix_to_postfix(vec![Token::Number(3.), Token::Add, Token::Number(2.)]),
Ok(vec![Token::Number(3.), Token::Number(2.), Token::Add,])
);
assert_eq!(
infix_to_postfix(vec![Token::Number(3.), Token::Add, Token::Number(2.)]),
Ok(vec![Token::Number(3.), Token::Number(2.), Token::Add,])
);
assert_eq!(
// 3 + 4 × 2 ÷ ( 1 5 ) ^ 2 ^ 3
infix_to_postfix(vec![
Token::Number(3.),
Token::Add,
Token::Number(4.),
Token::Multiply,
Token::Number(2.),
Token::Divide,
Token::LeftParenthesis,
Token::Number(1.),
Token::Subtract,
Token::Number(5.),
Token::RightParenthesis,
Token::Power,
Token::Number(2.),
Token::Power,
Token::Number(3.),
]),
// 3 4 2 × 1 5 2 3 ^ ^ ÷ +
Ok(vec![
Token::Number(3.),
Token::Number(4.),
Token::Number(2.),
Token::Multiply,
Token::Number(1.),
Token::Number(5.),
Token::Subtract,
Token::Number(2.),
Token::Number(3.),
Token::Power,
Token::Power,
Token::Divide,
Token::Add,
])
);
}
#[test]
fn test_calculate() {
assert_eq!(
calculate(vec![Token::Number(3.), Token::Number(2.), Token::Multiply]),
Ok(6.)
);
}
#[test]
fn test_compute() {
assert_eq!(compute("(6 - 2)"), 4.);
assert_eq!(compute("3E2"), 300.);
assert_eq!(compute("4(3 - 1)^2"), 16.);
assert_eq!(compute("(6 - -2)"), 8.);
assert_eq!(compute("sin(3)"), 3_f64.sin());
assert_eq!(compute("sin(3 - 1)"), 2_f64.sin());
assert_eq!(compute("cos(3 - 1)"), 2_f64.cos());
assert_eq!(
compute("3 + 4 × 2 ÷ ( 1 5 ) ^ 2 ^ 3"),
3. + (4. * 2.) / (1_f64 - 5.).powf((2_f64).powf(3.))
);
assert_eq!(
compute("sin ( max ( 2, 3 ) ÷ 3 × π )"),
std::f64::consts::PI.sin()
);
assert_eq!(compute("min(2, 3)"), 2.);
assert_eq!(compute("max(2, 3)"), 3.);
assert_eq!(compute("3 +- 2"), 1.);
assert_eq!(compute("3 -+ 2"), 1.);
assert_eq!(compute("1E-3"), 0.001);
assert_eq!(compute("3sin(3)"), 3. * 3_f64.sin());
assert_eq!(compute("(1 + 2) - (1)"), 2.);
assert_eq!(compute("(1 + 2) - 1"), 2.);
assert_eq!(compute("sqrt(4)"), 2.);
}
}