From ab4d732aafb27578b5c0da4062a422b67694da95 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 00:24:38 -0700 Subject: [PATCH] rpn: initial working prototype --- src/rpn.rs | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 src/rpn.rs diff --git a/src/rpn.rs b/src/rpn.rs new file mode 100644 index 0000000..6819ca1 --- /dev/null +++ b/src/rpn.rs @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2024 Emma Tebibyte + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and permission + * notice: + * + * MIT License + * + * Copyright (c) 2022 Lilly Cham + * + * 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. + */ + +use std::{ + collections::VecDeque, + env::args, + fmt, + io::stdin, + process::ExitCode, +}; + +use CalcType::{ Add, Divide, Multiply, Power, Subtract, Val }; + +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +/// enum CalcType is a type containing operations used in the calculator. +enum CalcType { + Add, + Subtract, + Multiply, + Divide, + Power, + Val(f64), +} + +#[derive(Debug, Clone)] +struct EvaluationError { + pub message: String, +} + +impl fmt::Display for EvaluationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.message) + } +} + +/// str_to_calc_type converts a string to an optional `CalcType`. +fn str_to_calc_type(string: &str) -> Option { + let as_int = string.parse::(); + let result = match as_int { + Ok(x) => Some(Val(x)), + Err(_) => None, + }; + + if result.is_some() { + return result; + } + + match string { + "+" => Some(Add), + "-" => Some(Subtract), + "*" => Some(Multiply), + "/" => Some(Divide), + "^" => Some(Power), + _ => None, + } +} + +fn eval( + input: &str, + initial_stack: VecDeque, +) -> Result, EvaluationError> { + let mut stack = initial_stack; + // Split the input into tokens. + let toks = input.split(' ').collect::>(); + let mut ops: VecDeque = VecDeque::new(); + + for tok in &toks { + let x: CalcType = match str_to_calc_type(tok) { + Some(x) => x, + None => { + return Err(EvaluationError { + message: format!("Invalid token: {}", tok), + }) + } + }; + + match x { + Add | Divide | Multiply | Power | Subtract => ops.push_back(x), + + Val(x_) => stack.push_back(x_), + } + } + + for op in &ops { + match op { + Add | Subtract | Multiply | Divide | Power => { + let x = &stack.pop_back().ok_or(EvaluationError { + message: "Stack is empty.".to_string(), + })?; + + let y = &stack.pop_back().ok_or(EvaluationError { + message: "Stack is empty.".to_string(), + })?; + + match op { + Add => { + &stack.push_back(y + x) + }, + Subtract => { + &stack.push_back(y - x) + }, + Multiply => { + &stack.push_back(y * x) + }, + Divide => { + &stack.push_back(y / x) + }, + Power => { + let result = x.powf(*y); + &stack.push_back(result) + }, + _ => &{}, + }; + }, + Val(_) => { + return Err( + EvaluationError { + message: "Unexpected value in the operator stack.".to_string() + } + ) + }, + }; + } + + Ok(stack) +} + +fn round_precise(value: &f64, precision: usize) -> f64 { + let multiplier = 10_f64.powi(precision as i32); + (value * multiplier).round() / multiplier +} + +fn main() -> ExitCode { + let argv = args().collect::>(); + let stack = VecDeque::new(); + let mut buf = String::new(); + let precision = (-f64::EPSILON.log10() * 0.999).ceil() as usize; + + if argv.get(1).is_none() { + while let Ok(_) = stdin().read_line(&mut buf) { + match eval(&buf.trim(), stack.clone()) { + Ok(val) => { + let precise = round_precise(val.iter().last().unwrap(), precision); + println!("{}", precise.to_string()); + buf.clear(); + }, + Err(err) => { + eprintln!("{}: {}", argv[0], err.message); + return ExitCode::from(1); + }, + }; + } + } else { + let input = argv + .iter() + .skip(1) + .map(|x| x.to_owned()) + .collect::>() + .join(" "); + + match eval(&input, stack) { + Ok(val) => println!("{}", val.iter().last().unwrap().to_string()), + Err(err) => { + eprintln!("{}: {}", argv[0], err.message); + return ExitCode::from(1); + }, + }; + } + ExitCode::from(0) +}