bonsix/src/rpn.rs

249 lines
6.2 KiB
Rust
Raw Normal View History

2024-02-01 00:24:38 -07:00
/*
* 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,
2024-02-02 14:16:49 -07:00
fmt::{ self, Display, Formatter },
2024-02-01 00:24:38 -07:00
io::stdin,
process::ExitCode,
};
2024-02-01 23:34:07 -07:00
use CalcType::*;
2024-02-01 00:24:38 -07:00
extern crate sysexits;
use sysexits::EX_DATAERR;
2024-02-01 00:24:38 -07:00
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
2024-02-01 23:35:49 -07:00
// enum CalcType is a type containing operations used in the calculator
2024-02-01 00:24:38 -07:00
enum CalcType {
Add,
Subtract,
Multiply,
Divide,
Power,
2024-02-01 12:38:12 -07:00
Floor,
2024-02-01 01:26:48 -07:00
Modulo,
2024-02-01 00:24:38 -07:00
Val(f64),
Invalid,
2024-02-01 00:24:38 -07:00
}
2024-02-01 23:32:37 -07:00
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,
2024-02-01 23:32:37 -07:00
}
},
}
2024-02-01 00:24:38 -07:00
}
}
2024-02-02 14:16:49 -07:00
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 divion",
Modulo => "modulus",
Val(x) => {
y = x.to_string(); &y
},
Invalid => "",
2024-02-02 14:16:49 -07:00
})
}
}
#[derive(Debug, Clone)]
struct EvaluationError { pub message: String }
impl Display for EvaluationError {
fn fmt(&self, f: &mut 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;
2024-02-01 00:24:38 -07:00
fn eval(
input: &str,
initial_stack: VecDeque<f64>,
2024-02-02 14:16:49 -07:00
) -> Result<(VecDeque<f64>, bool), EvaluationError> {
2024-02-01 00:24:38 -07:00
let mut stack = initial_stack;
2024-02-02 14:16:49 -07:00
let mut oper = false;
if input.is_empty() {
stack.clear();
2024-02-02 14:16:49 -07:00
return Ok((stack, oper));
}
2024-02-01 00:24:38 -07:00
// Split the input into tokens.
let mut toks: VecDeque<CalcType> = input
.split(' ')
.map(|t| CalcType::from(t))
.collect();
2024-02-01 00:24:38 -07:00
let mut ops: VecDeque<CalcType> = VecDeque::new();
while let Some(n) = toks.pop_back() {
match n {
Val(v) => stack.push_back(v),
Invalid => {
2024-02-01 00:24:38 -07:00
return Err(EvaluationError {
message: format!("{}: Invalid token", n)
2024-02-01 00:24:38 -07:00
})
2024-02-01 23:32:37 -07:00
},
op => {
ops.push_back(op);
oper = true;
2024-02-01 00:24:38 -07:00
let vals = (
stack.pop_back(),
stack.pop_back(),
);
2024-02-02 14:16:49 -07:00
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 {
2024-02-02 14:16:49 -07:00
return Err(EvaluationError {
message: format!("{}: Unexpected operation.", op)
})
}
2024-02-01 00:24:38 -07:00
},
};
}
2024-02-02 14:16:49 -07:00
Ok((stack, oper))
2024-02-01 00:24:38 -07:00
}
2024-02-01 10:46:10 -07:00
// Round a float to the given precision level
2024-02-01 00:24:38 -07:00
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();
2024-02-01 00:24:38 -07:00
let mut buf = String::new();
2024-02-01 10:46:10 -07:00
// Set floating-point precision for correcting rounding errors based on
// machine epsilon
2024-02-01 01:18:13 -07:00
let precision = (-f64::EPSILON.log10() * PRECISION_MOD).ceil() as usize;
2024-02-01 00:24:38 -07:00
if argv.get(1).is_none() {
while let Ok(_) = stdin().read_line(&mut buf) {
match eval(&buf.trim(), stack) {
Ok(s) => {
2024-02-02 14:16:49 -07:00
buf.clear();
stack = s.0.clone();
let val = match stack.iter().last() {
Some(v) => v,
None => break,
};
2024-02-02 14:16:49 -07:00
if s.1 == false { continue; }
2024-02-01 10:46:10 -07:00
println!("{}", round_precise(val, precision).to_string());
2024-02-01 00:24:38 -07:00
},
Err(err) => {
eprintln!("{}: {}", argv[0], err.message);
return ExitCode::from(EX_DATAERR as u8);
2024-02-01 00:24:38 -07:00
},
};
}
} else {
let input = argv
.iter()
.skip(1)
.rev()
2024-02-01 00:24:38 -07:00
.map(|x| x.to_owned())
.collect::<Vec<String>>()
.join(" ");
match eval(&input, stack) {
2024-02-01 10:46:10 -07:00
Ok(s) => {
2024-02-02 14:16:49 -07:00
stack = s.0.clone();
2024-02-01 10:46:10 -07:00
let val = match stack.iter().last() {
Some(v) => v,
None => return ExitCode::from(0),
};
println!("{}", round_precise(val, precision).to_string())
},
2024-02-01 00:24:38 -07:00
Err(err) => {
eprintln!("{}: {}", argv[0], err.message);
return ExitCode::from(EX_DATAERR as u8);
2024-02-01 00:24:38 -07:00
},
};
}
ExitCode::from(0)
}