initial
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal 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
405
src/main.rs
Normal 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.);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user