This commit is contained in:
2024-03-21 12:36:17 +01:00
commit 53314ddaad
3 changed files with 414 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

8
Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "calc"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

405
src/main.rs Normal file
View File

@@ -0,0 +1,405 @@
#[derive(Debug, PartialEq)]
enum TokenizeError {
NumberParseError,
}
#[derive(Debug)]
enum CalculateError {
PostfixExpectedNumbers,
EmptyStack,
}
#[derive(PartialEq)]
enum Associativity {
Right,
Left,
}
#[derive(Debug, PartialEq, Clone)]
enum Token {
LeftParenthesis,
RightParenthesis,
Multiply,
Divide,
Add,
Subtract,
Number(f64),
Power,
}
fn main() {
let arg = match std::env::args().nth(1) {
Some(v) => v.replace(" ", ""),
None => {
eprintln!("missing argument, Usage: expr, Example: 1 + 1 * 3 (6 - 4)^2");
std::process::exit(1);
}
};
println!("result: {}", compute(&arg));
}
fn compute(input: &str) -> f64 {
let tokens = match tokenize(input) {
Ok(v) => v,
Err(e) => {
eprintln!("failed to parse tokens: {:?}", e);
std::process::exit(1);
}
};
let tokens = implicit_operations(tokens);
let tokens = infix_to_postfix(tokens);
// print!("postfix expression: ");
// tokens.iter().for_each(|t| print!("{t} "));
// println!();
match calculate(tokens) {
Ok(v) => return 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::LeftParenthesis => write!(f, "("),
Token::RightParenthesis => write!(f, ")"),
}
}
}
fn tokenize(input: &str) -> Result<Vec<Token>, TokenizeError> {
let mut buf = String::new();
let mut tokens = Vec::new();
for c in input.chars() {
let token = match c {
'(' => Token::LeftParenthesis,
')' => Token::RightParenthesis,
'*' => Token::Multiply,
'/' => Token::Divide,
'+' => Token::Add,
'-' => Token::Subtract,
'^' => Token::Power,
_ => {
buf.push(c);
continue;
}
};
if !buf.is_empty() {
if let Ok(number) = buf.parse() {
tokens.push(Token::Number(number));
buf.clear();
} else {
return Err(TokenizeError::NumberParseError);
}
}
tokens.push(token);
}
if !buf.is_empty() {
if let Ok(number) = buf.parse() {
tokens.push(Token::Number(number));
buf.clear();
} else {
return Err(TokenizeError::NumberParseError);
}
}
Ok(tokens)
}
fn implicit_operations(tokens: Vec<Token>) -> Vec<Token> {
if tokens.is_empty() {
return tokens;
}
let mut new_tokens = vec![tokens[0].clone()];
let mut prev = &tokens[0];
for token in tokens.iter().skip(1) {
let /* mut */neg = false;
match prev {
// &Token::Number(_) => {
// match token {
// if prev == &Token::Subtract {
// new_tokens.pop();
// neg = true;
// }
// }
// }
&Token::RightParenthesis | &Token::Number(_) => match token {
&Token::LeftParenthesis | &Token::Number(_) => new_tokens.push(Token::Multiply),
_ => (),
},
_ => (),
}
if neg {
match token {
&Token::Number(a) => new_tokens.push(Token::Number(-a)),
_ => unreachable!(),
}
} else {
new_tokens.push(token.clone());
}
prev = token;
}
new_tokens
}
fn associativity(token: &Token) -> Associativity {
match token {
Token::Power => Associativity::Right,
Token::Number(_) => unreachable!(),
_ => Associativity::Left,
}
}
fn infix_to_postfix(tokens: Vec<Token>) -> Vec<Token> {
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().unwrap() != &Token::LeftParenthesis {
output.push(op_stack.pop().unwrap());
}
assert!(op_stack.last().unwrap() == &Token::LeftParenthesis);
op_stack.pop();
}
op => {
while let Some(op2) = op_stack.last() {
if op2 != &Token::LeftParenthesis
&& (precedence(&op2) > precedence(&op)
|| (precedence(&op) == precedence(&op2)
&& associativity(&op) == Associativity::Left))
{
output.push(op_stack.pop().unwrap());
} else {
break;
}
}
op_stack.push(op.clone());
}
}
}
while !op_stack.is_empty() {
assert!(op_stack.last().unwrap() != &Token::LeftParenthesis);
output.push(op_stack.pop().unwrap());
}
output
}
fn precedence(token: &Token) -> u8 {
match token {
Token::Add | Token::Subtract => 0,
Token::Multiply | Token::Divide => 1,
Token::Power => 2,
Token::LeftParenthesis | Token::RightParenthesis => 3,
Token::Number(_) => 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 stack.len() != 1 && tokens.len() != 0 {
let mut token = tokens.next().unwrap();
loop {
match token {
Token::Number(_) => stack.push(token.clone()),
_ => break,
}
token = tokens.next().unwrap();
}
let a = stack.pop().ok_or(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!(),
};
match token {
Token::Add => stack.push(Token::Number(a + b)),
Token::Subtract => stack.push(Token::Number(a - b)),
Token::Multiply => stack.push(Token::Number(a * b)),
Token::Divide => stack.push(Token::Number(a / b)),
Token::Power => stack.push(Token::Number(a.powf(b))),
_ => unreachable!(),
}
}
if let Token::Number(n) = stack[0] {
return Ok(n);
} else {
return Err(CalculateError::EmptyStack);
}
}
#[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::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("3.14159265358979323"),
Ok(vec![Token::Number(3.14159265358979323)])
);
assert_eq!(tokenize("0."), Ok(vec![Token::Number(0.)]));
assert_eq!(tokenize("."), Err(TokenizeError::NumberParseError));
assert_eq!(tokenize(".."), Err(TokenizeError::NumberParseError));
assert_eq!(tokenize(".1."), Err(TokenizeError::NumberParseError));
assert_eq!(tokenize("1e3"), Ok(vec![Token::Number(1000.)]));
assert_eq!(tokenize("e3"), Err(TokenizeError::NumberParseError));
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() {
assert_eq!(
implicit_operations(vec![
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::Number(2.),
]),
vec![
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::Multiply,
Token::Number(2.),
]
);
assert_eq!(
implicit_operations(vec![
Token::Number(3.),
Token::LeftParenthesis,
Token::Number(2.),
Token::RightParenthesis,
]),
vec![
Token::Number(3.),
Token::Multiply,
Token::LeftParenthesis,
Token::Number(2.),
Token::RightParenthesis,
]
);
assert_eq!(
implicit_operations(vec![
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::LeftParenthesis,
Token::Number(2.),
Token::RightParenthesis
]),
vec![
Token::LeftParenthesis,
Token::Number(3.),
Token::RightParenthesis,
Token::Multiply,
Token::LeftParenthesis,
Token::Number(2.),
Token::RightParenthesis
]
);
}
#[test]
fn post_to_infix_test() {
assert_eq!(
infix_to_postfix(vec![Token::Number(3.), Token::Add, Token::Number(2.)]),
vec![Token::Number(3.), Token::Number(2.), Token::Add,]
);
assert_eq!(
infix_to_postfix(vec![Token::Number(3.), Token::Add, Token::Number(2.)]),
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 ^ ^ ÷ +
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]).unwrap(),
6.
);
}
#[test]
fn test_compute() {
assert_eq!(compute(&"(6 - 2)".replace(" ", "")), 4.);
// assert_eq!(compute(&"(6 - -2)".replace(" ", "")), 8.);
}
}