overgrown/src/rpn.rs

212 lines
5.4 KiB
Rust

/*
* 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, Multiply, Power, Subtract, Val };
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
/// enum CalcType is a type containing operations used in the calculator.
enum CalcType {
Add,
Subtract,
Multiply,
Divide,
Power,
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)
}
}
/// 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),
_ => None,
}
}
fn eval(
input: &str,
initial_stack: VecDeque<f64>,
) -> Result<VecDeque<f64>, EvaluationError> {
let mut stack = initial_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 {
Add | Divide | Multiply | Power | Subtract => ops.push_back(x),
Val(x_) => stack.push_back(x_),
}
}
for op in &ops {
match op {
Add | Subtract | Multiply | Divide | Power => {
let x = &stack.pop_back().ok_or(EvaluationError {
message: "Stack is empty.".to_string(),
})?;
let y = &stack.pop_back().ok_or(EvaluationError {
message: "Stack is empty.".to_string(),
})?;
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 => {
let result = x.powf(*y);
&stack.push_back(result)
},
_ => &{},
};
},
Val(_) => {
return Err(
EvaluationError {
message: "Unexpected value in the operator stack.".to_string()
}
)
},
};
}
Ok(stack)
}
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 stack = VecDeque::new();
let mut buf = String::new();
let precision = (-f64::EPSILON.log10() * 0.999).ceil() as usize;
if argv.get(1).is_none() {
while let Ok(_) = stdin().read_line(&mut buf) {
match eval(&buf.trim(), stack.clone()) {
Ok(val) => {
let precise = round_precise(val.iter().last().unwrap(), precision);
println!("{}", precise.to_string());
buf.clear();
},
Err(err) => {
eprintln!("{}: {}", argv[0], err.message);
return ExitCode::from(1);
},
};
}
} else {
let input = argv
.iter()
.skip(1)
.map(|x| x.to_owned())
.collect::<Vec<String>>()
.join(" ");
match eval(&input, stack) {
Ok(val) => println!("{}", val.iter().last().unwrap().to_string()),
Err(err) => {
eprintln!("{}: {}", argv[0], err.message);
return ExitCode::from(1);
},
};
}
ExitCode::from(0)
}