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,
|
|
|
|
|
fmt,
|
|
|
|
|
io::stdin,
|
|
|
|
|
process::ExitCode,
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-01 23:34:07 -07:00
|
|
|
|
use CalcType::*;
|
2024-02-01 00:24:38 -07:00
|
|
|
|
|
2024-02-01 00:41:27 -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 00:57:51 -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),
|
2024-02-01 23:32:37 -07:00
|
|
|
|
Empty,
|
2024-02-01 00:24:38 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2024-02-01 10:46:10 -07:00
|
|
|
|
struct EvaluationError { pub message: String }
|
2024-02-01 00:24:38 -07:00
|
|
|
|
|
|
|
|
|
impl fmt::Display for EvaluationError {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
write!(f, "{}", self.message)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-01 01:18:13 -07:00
|
|
|
|
// 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;
|
|
|
|
|
|
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(_) => Empty,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
2024-02-01 00:24:38 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn eval(
|
|
|
|
|
input: &str,
|
|
|
|
|
initial_stack: VecDeque<f64>,
|
|
|
|
|
) -> Result<VecDeque<f64>, EvaluationError> {
|
|
|
|
|
let mut stack = initial_stack;
|
2024-02-01 00:57:51 -07:00
|
|
|
|
|
|
|
|
|
if input.is_empty() {
|
|
|
|
|
stack.clear();
|
|
|
|
|
return Ok(stack);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-01 00:24:38 -07:00
|
|
|
|
// Split the input into tokens.
|
|
|
|
|
let toks = input.split(' ').collect::<Vec<&str>>();
|
|
|
|
|
let mut ops: VecDeque<CalcType> = VecDeque::new();
|
|
|
|
|
|
2024-02-01 23:32:37 -07:00
|
|
|
|
for tok in toks {
|
|
|
|
|
let x: CalcType = match CalcType::from(tok) {
|
|
|
|
|
Empty => {
|
2024-02-01 00:24:38 -07:00
|
|
|
|
return Err(EvaluationError {
|
|
|
|
|
message: format!("Invalid token: {}", tok),
|
|
|
|
|
})
|
2024-02-01 23:32:37 -07:00
|
|
|
|
},
|
|
|
|
|
x => x,
|
2024-02-01 00:24:38 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match x {
|
|
|
|
|
Val(x_) => stack.push_back(x_),
|
2024-02-01 10:46:10 -07:00
|
|
|
|
_ => ops.push_back(x),
|
2024-02-01 00:24:38 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for op in &ops {
|
|
|
|
|
match op {
|
2024-02-01 10:46:10 -07:00
|
|
|
|
Val(_) => {
|
|
|
|
|
return Err(EvaluationError {
|
|
|
|
|
message: "Unexpected value in the operator stack."
|
|
|
|
|
.to_string()
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
_ => {
|
2024-02-01 10:54:28 -07:00
|
|
|
|
let (x, y) = (
|
2024-02-01 13:06:46 -07:00
|
|
|
|
stack.pop_back().unwrap(),
|
|
|
|
|
stack.pop_back().unwrap(),
|
2024-02-01 10:54:28 -07:00
|
|
|
|
);
|
2024-02-01 00:24:38 -07:00
|
|
|
|
|
|
|
|
|
match op {
|
2024-02-01 13:06:46 -07:00
|
|
|
|
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),
|
|
|
|
|
_ => {},
|
2024-02-01 00:24:38 -07:00
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(stack)
|
|
|
|
|
}
|
|
|
|
|
|
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>>();
|
2024-02-01 00:41:27 -07:00
|
|
|
|
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) {
|
2024-02-01 00:41:27 -07:00
|
|
|
|
match eval(&buf.trim(), stack) {
|
2024-02-01 00:57:51 -07:00
|
|
|
|
Ok(s) => {
|
|
|
|
|
stack = s.clone();
|
|
|
|
|
|
|
|
|
|
let val = match stack.iter().last() {
|
|
|
|
|
Some(v) => v,
|
|
|
|
|
None => break,
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-01 10:46:10 -07:00
|
|
|
|
println!("{}", round_precise(val, precision).to_string());
|
2024-02-01 00:24:38 -07:00
|
|
|
|
buf.clear();
|
|
|
|
|
},
|
|
|
|
|
Err(err) => {
|
|
|
|
|
eprintln!("{}: {}", argv[0], err.message);
|
2024-02-01 00:41:27 -07:00
|
|
|
|
return ExitCode::from(EX_DATAERR as u8);
|
2024-02-01 00:24:38 -07:00
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let input = argv
|
|
|
|
|
.iter()
|
|
|
|
|
.skip(1)
|
|
|
|
|
.map(|x| x.to_owned())
|
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
.join(" ");
|
|
|
|
|
|
|
|
|
|
match eval(&input, stack) {
|
2024-02-01 10:46:10 -07:00
|
|
|
|
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())
|
|
|
|
|
},
|
2024-02-01 00:24:38 -07:00
|
|
|
|
Err(err) => {
|
|
|
|
|
eprintln!("{}: {}", argv[0], err.message);
|
2024-02-01 00:41:27 -07:00
|
|
|
|
return ExitCode::from(EX_DATAERR as u8);
|
2024-02-01 00:24:38 -07:00
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
ExitCode::from(0)
|
|
|
|
|
}
|