forked from bonsai/harakit
		
	merged rpn(1); closes #2
This commit is contained in:
		
						commit
						0f5247e722
					
				
							
								
								
									
										10
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								Makefile
									
									
									
									
									
								
							| @ -9,6 +9,7 @@ | ||||
| 
 | ||||
| .POSIX: | ||||
| .PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1)
 | ||||
| .PRAGMA: command_comment # breaks without this?
 | ||||
| 
 | ||||
| PREFIX=/usr/local | ||||
| 
 | ||||
| @ -51,7 +52,7 @@ build/o/libsysexits.rlib: build | ||||
| 		"$$(printf '#include <sysexits.h>\n' \
 | ||||
| 			| cpp -M -idirafter "build/include" - \
 | ||||
| 			| sed 's/ /\n/g' | grep sysexits.h)" \
 | ||||
| 		| $(RUSTC) $(RUSTCFLAGS) --crate-type lib -o build/o/libsysexits.rlib - | ||||
| 		| $(RUSTC) $(RUSTFLAGS) --crate-type lib -o build/o/libsysexits.rlib - | ||||
| 
 | ||||
| build/o/libgetopt.rlib: src/getopt-rs/lib.rs | ||||
| 	$(RUSTC) $(RUSTCFLAGS) --crate-type=lib --crate-name=getopt \
 | ||||
| @ -79,6 +80,13 @@ intcmp: build/bin/intcmp | ||||
| build/bin/intcmp: src/intcmp.c build | ||||
| 	$(CC) $(CFLAGS) -o $@ src/intcmp.c | ||||
| 
 | ||||
| .PHONY: rpn | ||||
| rpn: build/bin/rpn | ||||
| build/bin/rpn: src/rpn.rs build build/o/libsysexits.rlib | ||||
| 	$(RUSTC) $(RUSTFLAGS) \
 | ||||
| 		--extern sysexits=build/o/libsysexits.rlib \
 | ||||
| 		-o $@ src/rpn.rs | ||||
| 
 | ||||
| .PHONY: scrut | ||||
| scrut: build/bin/scrut | ||||
| build/bin/scrut: src/scrut.c build | ||||
|  | ||||
							
								
								
									
										70
									
								
								docs/rpn.1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/rpn.1
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| .\" Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media> | ||||
| .\" Copyright (c) 2024 DTB <trinity@trinity.moe> | ||||
| .\" | ||||
| .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, | ||||
| .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. | ||||
| 
 | ||||
| .TH rpn 1 | ||||
| 
 | ||||
| .SH NAME | ||||
| 
 | ||||
| rpn \(en reverse polish notation evaluation | ||||
| 
 | ||||
| .SH SYNOPSIS | ||||
| 
 | ||||
| rpn | ||||
| .RB [numbers...]\ [operators...] | ||||
| 
 | ||||
| .SH DESCRIPTION | ||||
| 
 | ||||
| Rpn evaluates reverse polish notation expressions either read from the standard | ||||
| input or parsed from provided arguments. See the STANDARD INPUT section. | ||||
| 
 | ||||
| Upon evaluation, rpn will print the resulting number on the stack to the | ||||
| standard output. Any further specified numbers will be placed at the end of the | ||||
| stack. | ||||
| 
 | ||||
| For information on for reverse polish notation syntax, see rpn(7). | ||||
| 
 | ||||
| .SH STANDARD INPUT | ||||
| 
 | ||||
| If arguments are passed to rpn, it interprets them as an expression to be | ||||
| evaluated. Otherwise, it reads whitespace-delimited numbers and operations from | ||||
| the standard input. | ||||
| 
 | ||||
| .SH DIAGNOSTICS | ||||
| 
 | ||||
| If encountering a syntax error, rpn will exit with the appropriate error code | ||||
| as defined by sysexits.h(3) and print an error message. | ||||
| 
 | ||||
| .SH CAVEATS | ||||
| 
 | ||||
| Due to precision constraints and the way floats are represented in accordance | ||||
| with the IEEE Standard for Floating Point Arithmetic (IEEE 754), floating-point | ||||
| arithmetic has rounding errors. This is somewhat curbed by using the | ||||
| machine epsilon as provided by the Rust standard library to which to round | ||||
| numbers. Because of this, variation is expected in the number of decimal places  | ||||
| rpn can handle based on the platform and hardware of any given machine. | ||||
| 
 | ||||
| .SH RATIONALE | ||||
| 
 | ||||
| An infix notation calculation utility, bc(1p), is included in the POSIX | ||||
| standard, but does not accept expressions as arguments; in scripts, any | ||||
| predefined, non-interactive input must be piped into the program. A dc(1) | ||||
| pre-dates the standardized bc(1p), the latter originally being a preprocessor | ||||
| for the former, and was included in UNIX v2 onward. While it implements reverse | ||||
| polish notation, it still suffers from being unable to accept an expression as | ||||
| an argument. | ||||
| 
 | ||||
| .SH AUTHOR | ||||
| 
 | ||||
| Written by Emma Tebibyte <emma@tebibyte.media>. | ||||
| 
 | ||||
| .SH COPYRIGHT | ||||
| 
 | ||||
| Copyright (c) 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later | ||||
| <https://gnu.org/licenses/agpl.html>. | ||||
| 
 | ||||
| .SH SEE ALSO | ||||
| 
 | ||||
| bc(1p), dc(1), rpn(7), IEEE 754 | ||||
							
								
								
									
										248
									
								
								src/rpn.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								src/rpn.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,248 @@ | ||||
| /* | ||||
|  * 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, | ||||
| } | ||||
| 
 | ||||
| // 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; | ||||
| 
 | ||||
| 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) | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user