/* * 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, Modulo, Multiply, Power, Subtract, Val }; extern crate sysexits; use sysexits::EX_DATAERR; #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] // enum CalcType is a type containing operations used in the calculator. enum CalcType { Add, Subtract, Multiply, Divide, Power, Modulo, 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) } } // I’m no math nerd but I want the highest possible approximation of 0.9 // repeating and it seems this can give it to me const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0; // 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), "%" => Some(Modulo), _ => None, } } fn eval( input: &str, initial_stack: VecDeque, ) -> Result, EvaluationError> { let mut stack = initial_stack; if input.is_empty() { stack.clear(); return Ok(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 { Val(x_) => stack.push_back(x_), _ => ops.push_back(x), } } for op in &ops { match op { Val(_) => { return Err(EvaluationError { message: "Unexpected value in the operator stack." .to_string() }) }, _ => { 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 => &stack.push_back(x.powf(*y)), Modulo => &stack.push_back(y % x), _ => &{}, }; }, }; } Ok(stack) } // Round a float to the given precision level 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 mut stack = VecDeque::new(); let mut buf = String::new(); // Set floating-point precision for correcting rounding errors based on // machine epsilon let precision = (-f64::EPSILON.log10() * PRECISION_MOD).ceil() as usize; if argv.get(1).is_none() { while let Ok(_) = stdin().read_line(&mut buf) { match eval(&buf.trim(), stack) { Ok(s) => { stack = s.clone(); let val = match stack.iter().last() { Some(v) => v, None => break, }; println!("{}", round_precise(val, precision).to_string()); buf.clear(); }, Err(err) => { eprintln!("{}: {}", argv[0], err.message); return ExitCode::from(EX_DATAERR as u8); }, }; } } else { let input = argv .iter() .skip(1) .map(|x| x.to_owned()) .collect::>() .join(" "); match eval(&input, stack) { Ok(s) => { stack = s.clone(); let val = match stack.iter().last() { Some(v) => v, None => return ExitCode::from(0), }; println!("{}", round_precise(val, precision).to_string()) }, Err(err) => { eprintln!("{}: {}", argv[0], err.message); return ExitCode::from(EX_DATAERR as u8); }, }; } ExitCode::from(0) }