/* * 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::{ self, Display, Formatter }, io::stdin, process::ExitCode, }; use CalcType::*; extern crate sysexits; use sysexits::EX_DATAERR; #[derive(Clone, PartialEq, PartialOrd, Debug)] // enum CalcType is a type containing operations used in the calculator enum CalcType { Add, Subtract, Multiply, Divide, Power, Floor, Modulo, Val(f64), Invalid(String), } impl From<&str> for CalcType { fn from(value: &str) -> Self { match value { "+" => Add, "-" | "−" => Subtract, "*" | "×" => Multiply, "/" | "÷" => Divide, "^" | "**" => Power, "//" => Floor, "%" => Modulo, _ => { match value.parse::() { Ok(x) => Val(x), Err(_) => Invalid(value.to_owned()), } }, } } } impl Display for CalcType { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let y: String; write!(f, "{}", match self { Add => "addition", Subtract => "subtraction", Multiply => "multiplication", Divide => "division", Power => "exponentiation", Floor => "floor division", Modulo => "modulus", Val(x) => { y = x.to_string(); &y }, Invalid(i) => { y = i.to_string(); &y }, }) } } #[derive(Debug, Clone)] struct EvaluationError { message: String, code: i32, } // 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; fn eval( input: &str, initial_stack: VecDeque, ) -> Result<(VecDeque, bool), EvaluationError> { let mut stack = initial_stack; let mut oper = false; if input.is_empty() { stack.clear(); return Ok((stack, oper)); } // Split the input into tokens. let mut toks: VecDeque = input .split_whitespace() .rev() .map(|t| CalcType::from(t)) .collect(); let mut ops: VecDeque = VecDeque::new(); while let Some(n) = toks.pop_back() { match n { Val(v) => stack.push_back(v), Invalid(i) => { return Err(EvaluationError { message: format!("{}: Invalid token", i), code: EX_DATAERR, }) }, op => { ops.push_back(op.clone()); oper = true; let vals = ( stack.pop_back(), stack.pop_back(), ); if let (Some(x), Some(y)) = vals { 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(y.powf(x)), Floor => stack.push_back((y / x).floor()), Modulo => stack.push_back(y % x), _ => {}, }; } else { return Err(EvaluationError { message: format!("{}: Unexpected operation", op), code: EX_DATAERR, }) } }, }; } Ok((stack, oper)) } // 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) => { buf.clear(); stack = s.0.clone(); let val = match stack.iter().last() { Some(v) => v, None => break, }; if s.1 == false { continue; } println!("{}", round_precise(val, precision).to_string()); }, Err(err) => { eprintln!("{}: {}", argv[0], err.message); return ExitCode::from(err.code as u8); }, }; } } else { let input = argv .iter() .skip(1) .map(|x| x.to_owned()) .collect::>() .join(" "); match eval(&input, stack) { Ok(s) => { stack = s.0.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(err.code as u8); }, }; } ExitCode::from(0) }