diff options
| author | tyropro <[email protected]> | 2025-10-28 19:55:10 +0000 |
|---|---|---|
| committer | tyropro <[email protected]> | 2025-10-28 19:55:10 +0000 |
| commit | b3616a15aac1c8fe16700068d51287a36ba68565 (patch) | |
| tree | d45ac9a4349007fe1b524926124867dd42182407 | |
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 7 | ||||
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | LICENSE | 21 | ||||
| -rw-r--r-- | README.md | 11 | ||||
| -rw-r--r-- | src/main.rs | 238 |
6 files changed, 284 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d386bdf --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "simple-calculator" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7318f45 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "simple-calculator" +version = "0.1.0" +edition = "2024" + +[dependencies] @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 tyropro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a61fa1 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Calculator + +This is a very simple calculator programmed in Rust. + +It takes in a string infix input and converts it to a postfix expression before evaluating it. + +# TODO + +- [x] Get it working +- [x] Unary and binary subtraction +- [ ] Handle user input error diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a605a18 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,238 @@ +#[derive(PartialEq, Clone, Copy, Debug)] +enum Token { + Number(f64), + Addition, + // UnarySubtraction, + Subtraction, + Multiplication, + Division, + LeftParenthesis, + RightParenthesis, + Index, +} + +impl Token { + fn into_float(self) -> Option<f64> { + match self { + Self::Number(s) => Some(s), + _ => None, + } + } +} + +fn tokenisation(input: &str) -> Result<Vec<Token>, ()> { + let chars = input.chars(); + + let mut tokens = Vec::new(); + let mut token_buffer = Vec::new(); + + let mut unary_sub = true; + + for token in chars { + if token.is_numeric() { + token_buffer.push(token); + unary_sub = false; + continue; + } + + if token.is_ascii_punctuation() && token_buffer.len() > 0 { + let stringified_token = token_buffer.iter().collect::<String>(); + let numerical_token = stringified_token.as_str().parse::<f64>().unwrap(); + tokens.push(Token::Number(numerical_token)); + token_buffer.clear(); + } + + if token == '+' { + tokens.push(Token::Addition); + } else if token == '-' { + if unary_sub { + token_buffer.push(token); + } else { + tokens.push(Token::Subtraction); + } + } else if token == '*' { + tokens.push(Token::Multiplication); + } else if token == '/' { + tokens.push(Token::Division); + } else if token == '(' { + tokens.push(Token::LeftParenthesis); + } else if token == ')' { + tokens.push(Token::RightParenthesis); + } else if token == '^' { + tokens.push(Token::Index); + } else if token == ' ' { + continue; + } else { + println!( + "WARN: cannot use '{}' in expressions. Character has been ignored.", + token + ); + continue; + } + + unary_sub = true; + } + + if token_buffer.len() > 0 { + let stringified_token = token_buffer.iter().collect::<String>(); + let numerical_token = stringified_token.as_str().parse::<f64>().unwrap(); + tokens.push(Token::Number(numerical_token)); + token_buffer.clear(); + } + + if tokens.len() < 1 { + return Err(()); + } + + Ok(tokens) +} + +fn shunting_yard(infix: Vec<Token>) -> Result<Vec<Token>, ()> { + let mut output: Vec<Token> = Vec::new(); + let mut stack: Vec<Token> = Vec::new(); + + for token in infix { + // dbg!(&token); + match token { + Token::Number(_) => output.push(token), + Token::Addition | Token::Subtraction => { + if stack.len() > 0 { + if stack[0] == Token::Index + || stack[0] == Token::Multiplication + || stack[0] == Token::Division + { + while stack[0] == Token::Index + || stack[0] == Token::Multiplication + || stack[0] == Token::Division + || stack[0] == Token::Addition + || stack[0] == Token::Subtraction + { + output.push(stack[0]); + stack.remove(0); + + if stack.len() == 0 { + break; + } + } + } + } + stack.insert(0, token) + } + Token::Multiplication | Token::Division => { + if stack.len() > 0 { + if stack[0] == Token::Index { + while stack[0] == Token::Index + || stack[0] == Token::Multiplication + || stack[0] == Token::Division + { + output.push(stack[0]); + stack.remove(0); + + if stack.len() == 0 { + break; + } + } + } + } + stack.insert(0, token); + } + Token::RightParenthesis => { + if stack.len() > 0 { + while stack[0] != Token::LeftParenthesis { + output.push(stack[0]); + stack.remove(0); + } + stack.remove(0); + } + } + _ => stack.insert(0, token), + } + + // dbg!(&output); + } + + for token in stack { + if token == Token::LeftParenthesis { + return Err(()); + } + output.push(token); + } + + Ok(output) +} + +fn calculate(mut input: Vec<Token>) -> f64 { + // let mut input = postfix; + let mut stack = Vec::new(); + + while input.len() > 0 { + match input[0] { + Token::Number(_) => { + stack.insert(0, input[0]); + } + Token::Addition => { + let a = stack[1].into_float().unwrap(); + let b = stack[0].into_float().unwrap(); + stack[0] = Token::Number(a + b); + stack.remove(1); + } + Token::Subtraction => { + let a = stack[1].into_float().unwrap(); + let b = stack[0].into_float().unwrap(); + stack[0] = Token::Number(a - b); + stack.remove(1); + } + Token::Multiplication => { + let a = stack[1].into_float().unwrap(); + let b = stack[0].into_float().unwrap(); + stack[0] = Token::Number(a * b); + stack.remove(1); + } + Token::Division => { + let a = stack[1].into_float().unwrap(); + let b = stack[0].into_float().unwrap(); + stack[0] = Token::Number(a / b); + stack.remove(1); + } + Token::Index => { + let a = stack[1].into_float().unwrap(); + let b = stack[0].into_float().unwrap(); + stack[0] = Token::Number(a.powf(b)); + stack.remove(1); + } + _ => {} + } + + input.remove(0); + } + + stack[0].into_float().unwrap() +} + +fn main() { + loop { + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + let calculation = input.trim(); + + if calculation.to_lowercase() == "quit" || calculation.to_lowercase() == "exit" { + break; + } + + let infix_vector = tokenisation(calculation); + if infix_vector.is_err() { + println!("ERR: Invalid expression.\n"); + continue; + } + + let postfix_vector = shunting_yard(infix_vector.unwrap()); + if postfix_vector.is_err() { + println!("ERR: Invalid expression.\n"); + continue; + } + + let answer = calculate(postfix_vector.unwrap()); + + println!("{}\n", answer); + } +} |
