overgrown/src/rpn.rs

230 lines
6.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* 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, Floor, 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,
Floor,
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)
}
}
// Im 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<CalcType> {
let as_int = string.parse::<f64>();
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(Floor),
"%" => Some(Modulo),
_ => None,
}
}
fn eval(
input: &str,
initial_stack: VecDeque<f64>,
) -> Result<VecDeque<f64>, 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::<Vec<&str>>();
let mut ops: VecDeque<CalcType> = 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, y) = (
stack.pop_back().unwrap(),
stack.pop_back().unwrap(),
);
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)),
Floor => stack.push_back((y / x).floor()),
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::<Vec<String>>();
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::<Vec<String>>()
.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)
}