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: | .POSIX: | ||||||
| .PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1)
 | .PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1)
 | ||||||
|  | .PRAGMA: command_comment # breaks without this?
 | ||||||
| 
 | 
 | ||||||
| PREFIX=/usr/local | PREFIX=/usr/local | ||||||
| 
 | 
 | ||||||
| @ -51,7 +52,7 @@ build/o/libsysexits.rlib: build | |||||||
| 		"$$(printf '#include <sysexits.h>\n' \
 | 		"$$(printf '#include <sysexits.h>\n' \
 | ||||||
| 			| cpp -M -idirafter "build/include" - \
 | 			| cpp -M -idirafter "build/include" - \
 | ||||||
| 			| sed 's/ /\n/g' | grep sysexits.h)" \
 | 			| 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 | build/o/libgetopt.rlib: src/getopt-rs/lib.rs | ||||||
| 	$(RUSTC) $(RUSTCFLAGS) --crate-type=lib --crate-name=getopt \
 | 	$(RUSTC) $(RUSTCFLAGS) --crate-type=lib --crate-name=getopt \
 | ||||||
| @ -79,6 +80,13 @@ intcmp: build/bin/intcmp | |||||||
| build/bin/intcmp: src/intcmp.c build | build/bin/intcmp: src/intcmp.c build | ||||||
| 	$(CC) $(CFLAGS) -o $@ src/intcmp.c | 	$(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 | .PHONY: scrut | ||||||
| scrut: build/bin/scrut | scrut: build/bin/scrut | ||||||
| build/bin/scrut: src/scrut.c build | 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