From ab4d732aafb27578b5c0da4062a422b67694da95 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 00:24:38 -0700 Subject: [PATCH 01/30] rpn: initial working prototype --- src/rpn.rs | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 src/rpn.rs diff --git a/src/rpn.rs b/src/rpn.rs new file mode 100644 index 0000000..6819ca1 --- /dev/null +++ b/src/rpn.rs @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2024 Emma Tebibyte + * 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 { + let as_int = string.parse::(); + 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, +) -> Result, EvaluationError> { + let mut stack = initial_stack; + // Split the input into tokens. + let toks = input.split(' ').collect::>(); + let mut ops: VecDeque = 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::>(); + 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::>() + .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) +} From 32537895cb38ab8b0644e0b517b80fa4aab2e165 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 00:31:41 -0700 Subject: [PATCH 02/30] Makefile: added rpn build recipe --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 61b67de..ef52a78 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,11 @@ 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 + $(RUSTC) $(RUSTFLAGS) -o $@ src/rpn.rs + .PHONY: scrut scrut: build/bin/scrut build/bin/scrut: src/scrut.c build From 4870f4679c47b0b69740cc5e589bb84b1e233d70 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 00:41:27 -0700 Subject: [PATCH 03/30] Makefile, rpn(1): add sysexits dependency --- Makefile | 2 +- src/rpn.rs | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index ef52a78..9d4261d 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ build/bin/intcmp: src/intcmp.c build .PHONY: rpn rpn: build/bin/rpn -build/bin/rpn: src/rpn.rs build +build/bin/rpn: src/rpn.rs build sysexits $(RUSTC) $(RUSTFLAGS) -o $@ src/rpn.rs .PHONY: scrut diff --git a/src/rpn.rs b/src/rpn.rs index 6819ca1..7e7f948 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -52,6 +52,10 @@ use std::{ use CalcType::{ Add, Divide, Multiply, Power, Subtract, Val }; +extern crate sysexits; + +use sysexits::EX_DATAERR; + #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] /// enum CalcType is a type containing operations used in the calculator. enum CalcType { @@ -156,7 +160,8 @@ fn eval( Val(_) => { return Err( EvaluationError { - message: "Unexpected value in the operator stack.".to_string() + message: "Unexpected value in the operator stack." + .to_string() } ) }, @@ -173,21 +178,25 @@ fn round_precise(value: &f64, precision: usize) -> f64 { fn main() -> ExitCode { let argv = args().collect::>(); - let stack = VecDeque::new(); + let mut 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()) { + match eval(&buf.trim(), stack) { Ok(val) => { - let precise = round_precise(val.iter().last().unwrap(), precision); + stack = val.clone(); + let precise = round_precise( + stack.iter().last().unwrap(), + precision, + ); println!("{}", precise.to_string()); buf.clear(); }, Err(err) => { eprintln!("{}: {}", argv[0], err.message); - return ExitCode::from(1); + return ExitCode::from(EX_DATAERR as u8); }, }; } @@ -203,7 +212,7 @@ fn main() -> ExitCode { Ok(val) => println!("{}", val.iter().last().unwrap().to_string()), Err(err) => { eprintln!("{}: {}", argv[0], err.message); - return ExitCode::from(1); + return ExitCode::from(EX_DATAERR as u8); }, }; } From 942e284f9392e6b8ea1d32258081367c757cc157 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 00:57:51 -0700 Subject: [PATCH 04/30] rpn(1): fixed exiting the program on EOF --- src/rpn.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index 7e7f948..737325c 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -57,7 +57,7 @@ extern crate sysexits; use sysexits::EX_DATAERR; #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] -/// enum CalcType is a type containing operations used in the calculator. +// enum CalcType is a type containing operations used in the calculator. enum CalcType { Add, Subtract, @@ -78,7 +78,7 @@ impl fmt::Display for EvaluationError { } } -/// str_to_calc_type converts a string to an optional `CalcType`. +// str_to_calc_type converts a string to an optional `CalcType`. fn str_to_calc_type(string: &str) -> Option { let as_int = string.parse::(); let result = match as_int { @@ -105,6 +105,12 @@ fn eval( initial_stack: VecDeque, ) -> Result, EvaluationError> { let mut stack = initial_stack; + + if input.is_empty() { + stack.clear(); + return Ok(stack); + } + // Split the input into tokens. let toks = input.split(' ').collect::>(); let mut ops: VecDeque = VecDeque::new(); @@ -185,12 +191,15 @@ fn main() -> ExitCode { if argv.get(1).is_none() { while let Ok(_) = stdin().read_line(&mut buf) { match eval(&buf.trim(), stack) { - Ok(val) => { - stack = val.clone(); - let precise = round_precise( - stack.iter().last().unwrap(), - precision, - ); + Ok(s) => { + stack = s.clone(); + + let val = match stack.iter().last() { + Some(v) => v, + None => break, + }; + + let precise = round_precise(val, precision); println!("{}", precise.to_string()); buf.clear(); }, From 5debb9d9549a456712d8147d71e7830f6d6852e6 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 01:18:13 -0700 Subject: [PATCH 05/30] rpn(1): highest possible precision --- src/rpn.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rpn.rs b/src/rpn.rs index 737325c..83af537 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -78,6 +78,10 @@ impl fmt::Display for EvaluationError { } } +// 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; + // str_to_calc_type converts a string to an optional `CalcType`. fn str_to_calc_type(string: &str) -> Option { let as_int = string.parse::(); @@ -186,7 +190,7 @@ fn main() -> ExitCode { let argv = args().collect::>(); let mut stack = VecDeque::new(); let mut buf = String::new(); - let precision = (-f64::EPSILON.log10() * 0.999).ceil() as usize; + let precision = (-f64::EPSILON.log10() * PRECISION_MOD).ceil() as usize; if argv.get(1).is_none() { while let Ok(_) = stdin().read_line(&mut buf) { From 5f8e90b592719a28b558c9d5b5c3aa8c5154d3e0 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 01:26:48 -0700 Subject: [PATCH 06/30] rpn(1): added modulo --- src/rpn.rs | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index 83af537..893266f 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -50,7 +50,7 @@ use std::{ process::ExitCode, }; -use CalcType::{ Add, Divide, Multiply, Power, Subtract, Val }; +use CalcType::{ Add, Divide, Modulo, Multiply, Power, Subtract, Val }; extern crate sysexits; @@ -64,6 +64,7 @@ enum CalcType { Multiply, Divide, Power, + Modulo, Val(f64), } @@ -100,6 +101,7 @@ fn str_to_calc_type(string: &str) -> Option { "*" => Some(Multiply), "/" => Some(Divide), "^" => Some(Power), + "%" => Some(Modulo), _ => None, } } @@ -130,15 +132,16 @@ fn eval( }; match x { - Add | Divide | Multiply | Power | Subtract => ops.push_back(x), - + Add | Divide | Multiply | Power | Subtract | Modulo => { + ops.push_back(x) + }, Val(x_) => stack.push_back(x_), } } for op in &ops { match op { - Add | Subtract | Multiply | Divide | Power => { + Add | Subtract | Multiply | Divide | Power | Modulo => { let x = &stack.pop_back().ok_or(EvaluationError { message: "Stack is empty.".to_string(), })?; @@ -148,22 +151,12 @@ fn eval( })?; 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) - }, + 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)), + Modulo => &stack.push_back(y % x), _ => &{}, }; }, From f966233e195d869f37d2b39a6714ef915936ceff Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 01:33:47 -0700 Subject: [PATCH 07/30] Makefile: RUSTCFLAGS -> RUSTFLAGS --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9d4261d..5900c5c 100644 --- a/Makefile +++ b/Makefile @@ -54,10 +54,10 @@ sysexits: build "$$(printf '#include \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 - libgetopt: src/getopt-rs/lib.rs - $(RUSTC) $(RUSTCFLAGS) --crate-type=lib --crate-name=getopt \ + $(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \ -o build/o/libgetopt.rlib src/getopt-rs/lib.rs .PHONY: dj From 90ca10990fdb127335d780aa3e3f06528627585c Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 01:35:31 -0700 Subject: [PATCH 08/30] Makefile: fixed build --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 5900c5c..b2233c4 100644 --- a/Makefile +++ b/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 From 34b9519e034aceafb7ea7d7e9f6cba2f0367cc07 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 10:46:10 -0700 Subject: [PATCH 09/30] rpn(1): code cleanup --- src/rpn.rs | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index 893266f..0a0dd40 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -69,9 +69,7 @@ enum CalcType { } #[derive(Debug, Clone)] -struct EvaluationError { - pub message: String, -} +struct EvaluationError { pub message: String } impl fmt::Display for EvaluationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -91,9 +89,7 @@ fn str_to_calc_type(string: &str) -> Option { Err(_) => None, }; - if result.is_some() { - return result; - } + if result.is_some() { return result; } match string { "+" => Some(Add), @@ -132,22 +128,26 @@ fn eval( }; match x { - Add | Divide | Multiply | Power | Subtract | Modulo => { - ops.push_back(x) - }, Val(x_) => stack.push_back(x_), + _ => ops.push_back(x), } } for op in &ops { match op { - Add | Subtract | Multiply | Divide | Power | Modulo => { + Val(_) => { + return Err(EvaluationError { + message: "Unexpected value in the operator stack." + .to_string() + }) + }, + _ => { 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(), + message: "Stack is empty.".to_string(), })?; match op { @@ -160,20 +160,13 @@ fn eval( _ => &{}, }; }, - Val(_) => { - return Err( - EvaluationError { - message: "Unexpected value in the operator stack." - .to_string() - } - ) - }, }; } Ok(stack) } +// 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 @@ -183,6 +176,8 @@ fn main() -> ExitCode { let argv = args().collect::>(); 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() { @@ -196,8 +191,7 @@ fn main() -> ExitCode { None => break, }; - let precise = round_precise(val, precision); - println!("{}", precise.to_string()); + println!("{}", round_precise(val, precision).to_string()); buf.clear(); }, Err(err) => { @@ -215,7 +209,16 @@ fn main() -> ExitCode { .join(" "); match eval(&input, stack) { - Ok(val) => println!("{}", val.iter().last().unwrap().to_string()), + 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()) + }, Err(err) => { eprintln!("{}: {}", argv[0], err.message); return ExitCode::from(EX_DATAERR as u8); From f7108245eabcd3b6977a130c6f6ebe18a958ea4b Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 10:54:28 -0700 Subject: [PATCH 10/30] rpn(1): more code cleanup --- src/rpn.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index 0a0dd40..5322ee6 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -142,13 +142,10 @@ fn eval( }) }, _ => { - 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(), - })?; + let (x, y) = ( + &stack.pop_back().unwrap(), + &stack.pop_back().unwrap(), + ); match op { Add => &stack.push_back(y + x), From 42d268319e862076b0f9bba67797d7f47926cdfa Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 11:36:38 -0700 Subject: [PATCH 11/30] docs/rpn.1: added manpage --- docs/rpn.1 | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 docs/rpn.1 diff --git a/docs/rpn.1 b/docs/rpn.1 new file mode 100644 index 0000000..21131ce --- /dev/null +++ b/docs/rpn.1 @@ -0,0 +1,63 @@ +.\" Copyright (c) 2024 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + +.TH rpn 1 + +.SH NAME + +npc \(en reverse polish notation evaluation + +.SH SYNOPSIS + +rpn [integers...] [operations...] + +.SH DESCRIPTION + +Rpn parses reverse polish notation and adds characters to the stack until there +is an operation. See rpn(7) for more details on the syntax of reverse polish +notation. + +.SH STANDARD INPUT + +If rpn is passed arguments, it interprets those arguments as an expression to +be evaluated. Otherwise, it reads characters from standard input to add to the +stack. + +.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 the complexity of integer storage in memory, rpn is only capable of +parsing decimal integers. + +Additionally, due to the laws of physics, floating-point math can only be as +precise as slightly less than the machine epsilon of the hardware on which rpn +is running. + +.SH RATIONALE + +POSIX has its own calculator in the form of bc(1p), which uses standard input +for its calculations. Pair the clunkiness of piping expressions into it and its +use of standard notation, it was clear what rpn should be. + +There are no mathematics in the qi(1) shell because it was decided early on that +math was the job of a specific tool and not the shell itself. Thus, rpn was +born. + +.SH AUTHOR + +Written by Emma Tebibyte . + +.SH COPYRIGHT + +Copyright (c) 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later +. + +.SH SEE ALSO + +bc(1p), dc(1) From b68f13acdc7430fd2aa0cfc235fef246bc0b6b4a Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 12:38:12 -0700 Subject: [PATCH 12/30] rpn(1): added floor division --- src/rpn.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rpn.rs b/src/rpn.rs index 5322ee6..c7a261d 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -50,7 +50,7 @@ use std::{ process::ExitCode, }; -use CalcType::{ Add, Divide, Modulo, Multiply, Power, Subtract, Val }; +use CalcType::{ Add, Divide, Floor, Modulo, Multiply, Power, Subtract, Val }; extern crate sysexits; @@ -64,6 +64,7 @@ enum CalcType { Multiply, Divide, Power, + Floor, Modulo, Val(f64), } @@ -97,6 +98,7 @@ fn str_to_calc_type(string: &str) -> Option { "*" => Some(Multiply), "/" => Some(Divide), "^" => Some(Power), + "//" => Some(Floor), "%" => Some(Modulo), _ => None, } @@ -153,6 +155,7 @@ fn eval( 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), _ => &{}, }; From 0d97f81f49b886038873cf7000f9bf7f75e31ded Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 13:06:46 -0700 Subject: [PATCH 13/30] rpn(1): unicode subtraction, multiplication, division & code cleanup --- src/rpn.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index c7a261d..16ba94f 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -94,9 +94,9 @@ fn str_to_calc_type(string: &str) -> Option { match string { "+" => Some(Add), - "-" => Some(Subtract), - "*" => Some(Multiply), - "/" => Some(Divide), + "-" | "−" => Some(Subtract), + "*" | "×" => Some(Multiply), + "/" | "÷" => Some(Divide), "^" => Some(Power), "//" => Some(Floor), "%" => Some(Modulo), @@ -145,19 +145,19 @@ fn eval( }, _ => { let (x, y) = ( - &stack.pop_back().unwrap(), - &stack.pop_back().unwrap(), + stack.pop_back().unwrap(), + stack.pop_back().unwrap(), ); 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), - _ => &{}, + 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), + _ => {}, }; }, }; From e7ee75b03fb560083587fb484648a3cef4650bb9 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 23:32:37 -0700 Subject: [PATCH 14/30] rpn(1): added From trait to CalcType --- src/rpn.rs | 59 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index 16ba94f..88c04ba 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -50,7 +50,17 @@ use std::{ process::ExitCode, }; -use CalcType::{ Add, Divide, Floor, Modulo, Multiply, Power, Subtract, Val }; +use CalcType::{ + Add, + Divide, + Empty, + Floor, + Modulo, + Multiply, + Power, + Subtract, + Val +}; extern crate sysexits; @@ -67,6 +77,7 @@ enum CalcType { Floor, Modulo, Val(f64), + Empty, } #[derive(Debug, Clone)] @@ -82,25 +93,23 @@ impl fmt::Display for EvaluationError { // repeating and it seems this can give it to me const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0; -// str_to_calc_type converts a string to an optional `CalcType`. -fn str_to_calc_type(string: &str) -> Option { - let as_int = string.parse::(); - 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), - "//" => Some(Floor), - "%" => Some(Modulo), - _ => None, +impl From<&str> for CalcType { + fn from(value: &str) -> Self { + match value { + "+" => Add, + "-" | "−" => Subtract, + "*" | "×" => Multiply, + "/" | "÷" => Divide, + "^" => Power, + "//" => Floor, + "%" => Modulo, + _ => { + match value.parse::() { + Ok(x) => Val(x), + Err(_) => Empty, + } + }, + } } } @@ -119,14 +128,14 @@ fn eval( let toks = input.split(' ').collect::>(); let mut ops: VecDeque = VecDeque::new(); - for tok in &toks { - let x: CalcType = match str_to_calc_type(tok) { - Some(x) => x, - None => { + for tok in toks { + let x: CalcType = match CalcType::from(tok) { + Empty => { return Err(EvaluationError { message: format!("Invalid token: {}", tok), }) - } + }, + x => x, }; match x { From b35f87bd27950262cebc6f9fa76b65f5301d8dc7 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 23:34:07 -0700 Subject: [PATCH 15/30] rpn(1): code cleanup --- src/rpn.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index 88c04ba..cadffa2 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -50,17 +50,7 @@ use std::{ process::ExitCode, }; -use CalcType::{ - Add, - Divide, - Empty, - Floor, - Modulo, - Multiply, - Power, - Subtract, - Val -}; +use CalcType::*; extern crate sysexits; From 258881dbb2ad88c6d676ee71f00deba2b2f50c01 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 1 Feb 2024 23:35:49 -0700 Subject: [PATCH 16/30] rpn(1): minor change --- src/rpn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpn.rs b/src/rpn.rs index cadffa2..eede26b 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -57,7 +57,7 @@ extern crate sysexits; use sysexits::EX_DATAERR; #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] -// enum CalcType is a type containing operations used in the calculator. +// enum CalcType is a type containing operations used in the calculator enum CalcType { Add, Subtract, From 885f167afcd9d85cfa1d7d1ea2f51ff77a973467 Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 2 Feb 2024 13:04:57 -0700 Subject: [PATCH 17/30] rpn(1): changes to error message --- src/rpn.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index eede26b..10c4e20 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -129,18 +129,18 @@ fn eval( }; match x { - Val(x_) => stack.push_back(x_), + Val(v) => stack.push_back(v), _ => ops.push_back(x), } } for op in &ops { match op { - Val(_) => { - return Err(EvaluationError { - message: "Unexpected value in the operator stack." - .to_string() - }) + Val(v) => { + return Err(EvaluationError { message: format!( + "{}: Unexpected value in the operator stack.", + v + )}); }, _ => { let (x, y) = ( From 4cb5e8e2b0e1cbfd44638953b494727f1cb473e6 Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 2 Feb 2024 14:16:49 -0700 Subject: [PATCH 18/30] rpn(1): better error handling --- src/rpn.rs | 83 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index 10c4e20..0f39187 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -45,7 +45,7 @@ use std::{ collections::VecDeque, env::args, - fmt, + fmt::{ self, Display, Formatter }, io::stdin, process::ExitCode, }; @@ -70,19 +70,6 @@ enum CalcType { Empty, } -#[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) - } -} - -// 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; - impl From<&str> for CalcType { fn from(value: &str) -> Self { match value { @@ -103,15 +90,48 @@ impl From<&str> for CalcType { } } +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 + }, + Empty => "", + }) + } +} + +#[derive(Debug, Clone)] +struct EvaluationError { pub message: String } + +impl Display for EvaluationError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.message) + } +} + +// 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, -) -> Result, EvaluationError> { +) -> Result<(VecDeque, bool), EvaluationError> { let mut stack = initial_stack; + let mut oper = false; if input.is_empty() { stack.clear(); - return Ok(stack); + return Ok((stack, oper)); } // Split the input into tokens. @@ -122,7 +142,7 @@ fn eval( let x: CalcType = match CalcType::from(tok) { Empty => { return Err(EvaluationError { - message: format!("Invalid token: {}", tok), + message: format!("{}: Invalid token", tok), }) }, x => x, @@ -136,13 +156,28 @@ fn eval( for op in &ops { match op { - Val(v) => { + Val(_) => { return Err(EvaluationError { message: format!( "{}: Unexpected value in the operator stack.", - v + op, )}); }, _ => { + + if stack.len() < 2 { + return Err(EvaluationError { + message: format!("{}: Unexpected operation.", op) + }) + } + + if stack.len() > ops.len() + 1 { + return Err( + EvaluationError { message: format!("Syntax error.")} + ); + } + + oper = true; + let (x, y) = ( stack.pop_back().unwrap(), stack.pop_back().unwrap(), @@ -162,7 +197,7 @@ fn eval( }; } - Ok(stack) + Ok((stack, oper)) } // Round a float to the given precision level @@ -183,15 +218,17 @@ fn main() -> ExitCode { while let Ok(_) = stdin().read_line(&mut buf) { match eval(&buf.trim(), stack) { Ok(s) => { - stack = s.clone(); + 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()); - buf.clear(); }, Err(err) => { eprintln!("{}: {}", argv[0], err.message); @@ -209,7 +246,7 @@ fn main() -> ExitCode { match eval(&input, stack) { Ok(s) => { - stack = s.clone(); + stack = s.0.clone(); let val = match stack.iter().last() { Some(v) => v, From fb2a1645074e9b65de9561e435d43546dabb1dd6 Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 2 Feb 2024 15:35:36 -0700 Subject: [PATCH 19/30] rpn(1): rewrote parser for the second time --- src/rpn.rs | 83 ++++++++++++++++++++++-------------------------------- 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index 0f39187..7cba297 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -67,7 +67,7 @@ enum CalcType { Floor, Modulo, Val(f64), - Empty, + Invalid, } impl From<&str> for CalcType { @@ -83,7 +83,7 @@ impl From<&str> for CalcType { _ => { match value.parse::() { Ok(x) => Val(x), - Err(_) => Empty, + Err(_) => Invalid, } }, } @@ -104,7 +104,7 @@ impl Display for CalcType { Val(x) => { y = x.to_string(); &y }, - Empty => "", + Invalid => "", }) } } @@ -122,6 +122,7 @@ impl Display for EvaluationError { // 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, @@ -135,64 +136,45 @@ fn eval( } // Split the input into tokens. - let toks = input.split(' ').collect::>(); + let mut toks: VecDeque = input + .split(' ') + .map(|t| CalcType::from(t)) + .collect(); let mut ops: VecDeque = VecDeque::new(); - for tok in toks { - let x: CalcType = match CalcType::from(tok) { - Empty => { + while let Some(n) = toks.pop_back() { + match n { + Val(v) => stack.push_back(v), + Invalid => { return Err(EvaluationError { - message: format!("{}: Invalid token", tok), + message: format!("{}: Invalid token", n) }) }, - x => x, - }; + op => { + ops.push_back(op); + oper = true; - match x { - Val(v) => stack.push_back(v), - _ => ops.push_back(x), - } - } + let vals = ( + stack.pop_back(), + stack.pop_back(), + ); - for op in &ops { - match op { - Val(_) => { - return Err(EvaluationError { message: format!( - "{}: Unexpected value in the operator stack.", - op, - )}); - }, - _ => { - - if stack.len() < 2 { + 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) }) } - - if stack.len() > ops.len() + 1 { - return Err( - EvaluationError { message: format!("Syntax error.")} - ); - } - - oper = true; - - let (x, y) = ( - stack.pop_back().unwrap(), - stack.pop_back().unwrap(), - ); - - 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), - _ => {}, - }; }, }; } @@ -240,6 +222,7 @@ fn main() -> ExitCode { let input = argv .iter() .skip(1) + .rev() .map(|x| x.to_owned()) .collect::>() .join(" "); From 6f98fdd1d44a0d207556b040b0dd1e7980d956a3 Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 2 Feb 2024 19:13:53 -0700 Subject: [PATCH 20/30] rpn(1): fixed expressions on one line --- src/rpn.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpn.rs b/src/rpn.rs index 7cba297..e369c23 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -138,6 +138,7 @@ fn eval( // Split the input into tokens. let mut toks: VecDeque = input .split(' ') + .rev() .map(|t| CalcType::from(t)) .collect(); let mut ops: VecDeque = VecDeque::new(); From 4993c53080c9878b0427c5c93dd6bbcdefebc42a Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 2 Feb 2024 19:17:00 -0700 Subject: [PATCH 21/30] rpn(1): re-fixed arg parsing --- src/rpn.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index e369c23..697a4a9 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -137,7 +137,7 @@ fn eval( // Split the input into tokens. let mut toks: VecDeque = input - .split(' ') + .split_whitespace() .rev() .map(|t| CalcType::from(t)) .collect(); @@ -223,7 +223,6 @@ fn main() -> ExitCode { let input = argv .iter() .skip(1) - .rev() .map(|x| x.to_owned()) .collect::>() .join(" "); From a30152d7834187d7a5a7c1f6b914f63ed0098ce7 Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 2 Feb 2024 20:12:12 -0700 Subject: [PATCH 22/30] rpn.1: made more precise --- docs/rpn.1 | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/rpn.1 b/docs/rpn.1 index 21131ce..ee1afec 100644 --- a/docs/rpn.1 +++ b/docs/rpn.1 @@ -1,4 +1,5 @@ .\" Copyright (c) 2024 Emma Tebibyte +.\" Copyright (c) 2024 DTB .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . @@ -7,47 +8,48 @@ .SH NAME -npc \(en reverse polish notation evaluation +rpn \(en reverse polish notation evaluation .SH SYNOPSIS -rpn [integers...] [operations...] +rpn +.RB [numbers...]\ [operators...] .SH DESCRIPTION -Rpn parses reverse polish notation and adds characters to the stack until there -is an operation. See rpn(7) for more details on the syntax of reverse polish -notation. +Rpn parses and and evaluates reverse polish notation either from the standard +input or by parsing its arguments. See the STANDARD INPUT section. + +For information on for reverse polish notation syntax, see rpn(7). .SH STANDARD INPUT -If rpn is passed arguments, it interprets those arguments as an expression to -be evaluated. Otherwise, it reads characters from standard input to add to the -stack. +If rpn is passed arguments, it interprets them as an expression to be evaluated. +Otherwise, it reads whitespace-delimited numbers and operations from the +standard input. + +.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 +second-highest float that can be represented in line with this standard to round +numbers to before outputting. .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 the complexity of integer storage in memory, rpn is only capable of -parsing decimal integers. - -Additionally, due to the laws of physics, floating-point math can only be as -precise as slightly less than the machine epsilon of the hardware on which rpn -is running. - .SH RATIONALE -POSIX has its own calculator in the form of bc(1p), which uses standard input -for its calculations. Pair the clunkiness of piping expressions into it and its -use of standard notation, it was clear what rpn should be. - -There are no mathematics in the qi(1) shell because it was decided early on that -math was the job of a specific tool and not the shell itself. Thus, rpn was -born. +An infix notation calculation utility, bc(1p), is included in the POSIX +standard, but it doesn’t 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 @@ -60,4 +62,4 @@ Copyright (c) 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later .SH SEE ALSO -bc(1p), dc(1) +rpn(7), bc(1p), dc(1), IEEE 754 From 228a0111c79fc8ba2a5a29845dd04a3ff988a5f5 Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 2 Feb 2024 21:05:34 -0700 Subject: [PATCH 23/30] rpn(1): fixed Invalid error handling --- src/rpn.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index 697a4a9..1068b06 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -56,7 +56,7 @@ extern crate sysexits; use sysexits::EX_DATAERR; -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +#[derive(Clone, PartialEq, PartialOrd, Debug)] // enum CalcType is a type containing operations used in the calculator enum CalcType { Add, @@ -67,7 +67,7 @@ enum CalcType { Floor, Modulo, Val(f64), - Invalid, + Invalid(String), } impl From<&str> for CalcType { @@ -83,7 +83,7 @@ impl From<&str> for CalcType { _ => { match value.parse::() { Ok(x) => Val(x), - Err(_) => Invalid, + Err(_) => Invalid(value.to_owned()), } }, } @@ -104,7 +104,9 @@ impl Display for CalcType { Val(x) => { y = x.to_string(); &y }, - Invalid => "", + Invalid(i) => { + y = i.to_string(); &y + }, }) } } @@ -146,13 +148,13 @@ fn eval( while let Some(n) = toks.pop_back() { match n { Val(v) => stack.push_back(v), - Invalid => { + Invalid(i) => { return Err(EvaluationError { - message: format!("{}: Invalid token", n) + message: format!("{}: Invalid token", i) }) }, op => { - ops.push_back(op); + ops.push_back(op.clone()); oper = true; let vals = ( From 6d7522be011b53f9552271923ef09b7bba99684e Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 2 Feb 2024 23:24:45 -0700 Subject: [PATCH 24/30] rpn.1: made better --- docs/rpn.1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/rpn.1 b/docs/rpn.1 index ee1afec..3067212 100644 --- a/docs/rpn.1 +++ b/docs/rpn.1 @@ -17,8 +17,8 @@ rpn .SH DESCRIPTION -Rpn parses and and evaluates reverse polish notation either from the standard -input or by parsing its arguments. See the STANDARD INPUT section. +Rpn parses and and evaluates reverse polish notation expressions either from the +standard input or by parsing its arguments. See the STANDARD INPUT section. For information on for reverse polish notation syntax, see rpn(7). @@ -62,4 +62,4 @@ Copyright (c) 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later .SH SEE ALSO -rpn(7), bc(1p), dc(1), IEEE 754 +bc(1p), dc(1), rpn(7), IEEE 754 From 8e0eaeab389d7ba0309f0f2b4d158f65167ee9be Mon Sep 17 00:00:00 2001 From: emma Date: Sat, 3 Feb 2024 17:20:31 -0700 Subject: [PATCH 25/30] rpn.1: made more better --- docs/rpn.1 | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/rpn.1 b/docs/rpn.1 index 3067212..74642f8 100644 --- a/docs/rpn.1 +++ b/docs/rpn.1 @@ -20,6 +20,10 @@ rpn Rpn parses and and evaluates reverse polish notation expressions either from the standard input or by parsing its 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 on the stack +following the last outputted number. + For information on for reverse polish notation syntax, see rpn(7). .SH STANDARD INPUT @@ -28,6 +32,11 @@ If rpn is passed arguments, 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 @@ -36,11 +45,6 @@ arithmetic has rounding errors. This is somewhat curbed by using the second-highest float that can be represented in line with this standard to round numbers to before outputting. -.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 RATIONALE An infix notation calculation utility, bc(1p), is included in the POSIX From d768a257a32914d8273f5f90b6c64c72f3774912 Mon Sep 17 00:00:00 2001 From: emma Date: Tue, 6 Feb 2024 23:29:43 -0700 Subject: [PATCH 26/30] rpn(1): made EvaluationError struct contain exit code --- src/rpn.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/rpn.rs b/src/rpn.rs index 1068b06..6cbb55b 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -112,19 +112,15 @@ impl Display for CalcType { } #[derive(Debug, Clone)] -struct EvaluationError { pub message: String } - -impl Display for EvaluationError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.message) - } +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, @@ -150,7 +146,8 @@ fn eval( Val(v) => stack.push_back(v), Invalid(i) => { return Err(EvaluationError { - message: format!("{}: Invalid token", i) + message: format!("{}: Invalid token", i), + code: EX_DATAERR, }) }, op => { @@ -175,7 +172,8 @@ fn eval( }; } else { return Err(EvaluationError { - message: format!("{}: Unexpected operation.", op) + message: format!("{}: Unexpected operation.", op), + code: EX_DATAERR, }) } }, @@ -217,7 +215,7 @@ fn main() -> ExitCode { }, Err(err) => { eprintln!("{}: {}", argv[0], err.message); - return ExitCode::from(EX_DATAERR as u8); + return ExitCode::from(err.code as u8); }, }; } @@ -242,7 +240,7 @@ fn main() -> ExitCode { }, Err(err) => { eprintln!("{}: {}", argv[0], err.message); - return ExitCode::from(EX_DATAERR as u8); + return ExitCode::from(err.code as u8); }, }; } From 4e040e0021c0a0ce96ac71ff9f26824648a57f1a Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 7 Feb 2024 18:51:58 -0700 Subject: [PATCH 27/30] rpn(1): added alternative representation for powers --- src/rpn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpn.rs b/src/rpn.rs index 6cbb55b..048a593 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -77,7 +77,7 @@ impl From<&str> for CalcType { "-" | "−" => Subtract, "*" | "×" => Multiply, "/" | "÷" => Divide, - "^" => Power, + "^" | "**" => Power, "//" => Floor, "%" => Modulo, _ => { From af53375ff28bb11076efd61cab756aa1de46dd5a Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 7 Feb 2024 19:11:53 -0700 Subject: [PATCH 28/30] rpn.1: fixed some clunky stuff --- docs/rpn.1 | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/rpn.1 b/docs/rpn.1 index 74642f8..506b3c9 100644 --- a/docs/rpn.1 +++ b/docs/rpn.1 @@ -21,16 +21,16 @@ Rpn parses and and evaluates reverse polish notation expressions either from the standard input or by parsing its 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 on the stack -following the last outputted number. +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 rpn is passed arguments, it interprets them as an expression to be evaluated. -Otherwise, it reads whitespace-delimited numbers and operations from the -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 @@ -42,13 +42,14 @@ as defined by sysexits.h(3) and print an error message. 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 -second-highest float that can be represented in line with this standard to round -numbers to before outputting. +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 it doesn’t accept expressions as arguments; in scripts, any +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 From b95b198923b9afe0cc64b4a5e767aa3c3ea61a11 Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 7 Feb 2024 19:12:31 -0700 Subject: [PATCH 29/30] rpn(1): typo --- src/rpn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpn.rs b/src/rpn.rs index 048a593..0168972 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -99,7 +99,7 @@ impl Display for CalcType { Multiply => "multiplication", Divide => "division", Power => "exponentiation", - Floor => "floor divion", + Floor => "floor division", Modulo => "modulus", Val(x) => { y = x.to_string(); &y From 7fb68a2c7af68ec6a76c73bd2b66c6b11d2fe47a Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 7 Feb 2024 19:17:59 -0700 Subject: [PATCH 30/30] rpn.1: fixed DESCRIPTION --- docs/rpn.1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rpn.1 b/docs/rpn.1 index 506b3c9..2197fbe 100644 --- a/docs/rpn.1 +++ b/docs/rpn.1 @@ -17,8 +17,8 @@ rpn .SH DESCRIPTION -Rpn parses and and evaluates reverse polish notation expressions either from the -standard input or by parsing its arguments. See the STANDARD INPUT section. +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