bonsix/src/rpn.rs
2024-02-07 19:12:31 -07:00

249 lines
6.3 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::{ 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::<f64>() {
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,
}
// 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;
fn eval(
input: &str,
initial_stack: VecDeque<f64>,
) -> Result<(VecDeque<f64>, 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<CalcType> = input
.split_whitespace()
.rev()
.map(|t| CalcType::from(t))
.collect();
let mut ops: VecDeque<CalcType> = 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(x.powf(y)),
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::<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) => {
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::<Vec<String>>()
.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)
}