aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortyropro <[email protected]>2025-10-28 19:55:10 +0000
committertyropro <[email protected]>2025-10-28 19:55:10 +0000
commitb3616a15aac1c8fe16700068d51287a36ba68565 (patch)
treed45ac9a4349007fe1b524926124867dd42182407
first commitHEADmain
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml6
-rw-r--r--LICENSE21
-rw-r--r--README.md11
-rw-r--r--src/main.rs238
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]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cb0c875
--- /dev/null
+++ b/LICENSE
@@ -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);
+ }
+}