calc_rational

CLI calculator for rational numbers.
git clone https://git.philomathiclife.com/repos/calc_rational
Log | Files | Refs | README

commit 4db36eb57b6fe303a334d126e682c7f7bcb6e546
parent 5ee61f6ac45dbb8e0598e83badffae679c2de79f
Author: Zack Newman <zack@philomathiclife.com>
Date:   Sun, 22 Oct 2023 16:39:07 -0600

minor doc fixes. deps update. minor clippy fixes.

Diffstat:
MCargo.toml | 6+++---
MREADME.md | 123++-----------------------------------------------------------------------------
Msrc/main.rs | 180+++++--------------------------------------------------------------------------
3 files changed, 15 insertions(+), 294 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" name = "calc_rational" readme = "README.md" repository = "https://git.philomathiclife.com/repos/calc_rational/" -version = "0.7.0" +version = "0.8.0" [lib] name = "calc_lib" @@ -23,8 +23,8 @@ path = "src/main.rs" num-bigint = { version = "0.4.4", default-features = false } num-integer = { version = "0.1.45", default-features = false } num-rational = { version = "0.4.1", default-features = false, features = ["num-bigint"] } -num-traits = { version = "0.2.16", default-features = false } -priv_sep = { version = "0.6.1", default-features = false, features = ["openbsd"], optional = true } +num-traits = { version = "0.2.17", default-features = false } +priv_sep = { version = "0.7.0", default-features = false, features = ["openbsd"], optional = true } rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"], optional = true } [build-dependencies] diff --git a/README.md b/README.md @@ -155,7 +155,7 @@ it will error if [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) errors wi ## Exiting -`q` with any number of spaces and tabs before and after will cause the program to terminate. +`q` with any number of spaces and tabs before and after or sending `EOF` will cause the program to terminate. ### Status @@ -170,126 +170,7 @@ The crates are only tested on the `x86_64-unknown-linux-gnu` and `x86_64-unknown they should work on any [Tier 1 with Host Tools](https://doc.rust-lang.org/beta/rustc/platform-support.html) target. Note one must be aware of the ASCII encoding requirement. In particular there are platforms (e.g., Windows) where the default text encoding is not a superset of ASCII. - -### Installing - -```bash -[zack@laptop ~]$ cargo install --all-features calc_rational - Updating crates.io index - Downloaded calc_rational v0.5.1 - Downloaded 1 crate (35.3 KB) in 0.60s - Installing calc_rational v0.5.1 - Updating crates.io index - Compiling autocfg v1.1.0 - Compiling semver v1.0.18 - Compiling libc v0.2.147 - Compiling cfg-if v1.0.0 - Compiling ppv-lite86 v0.2.17 - Compiling num-traits v0.2.16 - Compiling num-integer v0.1.45 - Compiling num-bigint v0.4.3 - Compiling num-rational v0.4.1 - Compiling rustc_version v0.4.0 - Compiling priv_sep v0.3.0 - Compiling calc_rational v0.5.1 - Compiling getrandom v0.2.10 - Compiling rand_core v0.6.4 - Compiling rand_chacha v0.3.1 - Compiling rand v0.8.5 - Finished release [optimized] target(s) in 6.92s - Installing /home/zack/.cargo/bin/calc - Installed package `calc_rational v0.5.1` (executable `calc`) -``` - -### Building and testing - -```bash -[zack@laptop ~]$ git clone https://git.philomathiclife.com/repos/calc_rational -Cloning into 'calc_rational'... -[zack@laptop ~]$ cd calc_rational/ -[zack@laptop calc_rational]$ cargo build --release --all-features - Updating crates.io index - Compiling autocfg v1.1.0 - Compiling semver v1.0.18 - Compiling libc v0.2.147 - Compiling cfg-if v1.0.0 - Compiling ppv-lite86 v0.2.17 - Compiling num-traits v0.2.16 - Compiling num-integer v0.1.45 - Compiling num-bigint v0.4.3 - Compiling num-rational v0.4.1 - Compiling rustc_version v0.4.0 - Compiling priv_sep v0.3.0 - Compiling calc_rational v0.5.1 (/home/zack/calc_rational) - Compiling getrandom v0.2.10 - Compiling rand_core v0.6.4 - Compiling rand_chacha v0.3.1 - Compiling rand v0.8.5 - Finished release [optimized] target(s) in 6.10s -[zack@laptop calc_rational]$ cargo t --all-features - Compiling autocfg v1.1.0 - Compiling semver v1.0.18 - Compiling libc v0.2.147 - Compiling cfg-if v1.0.0 - Compiling ppv-lite86 v0.2.17 - Compiling num-traits v0.2.16 - Compiling num-integer v0.1.45 - Compiling num-bigint v0.4.3 - Compiling num-rational v0.4.1 - Compiling rustc_version v0.4.0 - Compiling priv_sep v0.3.0 - Compiling calc_rational v0.5.1 (/home/zack/calc_rational) - Compiling getrandom v0.2.10 - Compiling rand_core v0.6.4 - Compiling rand_chacha v0.3.1 - Compiling rand v0.8.5 - Finished test [unoptimized + debuginfo] target(s) in 2.38s - Running unittests src/lib.rs (target/debug/deps/calc_lib-cd811ad9fff78426) - -running 26 tests -test cache::tests::test_get ... ok -test cache::tests::test_index ... ok -test cache::tests::test_is_empty ... ok -test cache::tests::test_new ... ok -test cache::tests::test_get_unsafe ... ok -test cache::tests::test_push ... ok -test tests::abs ... ok -test tests::add ... ok -test cache::tests::test_len ... ok -test cache::tests::test_index_panic - should panic ... ok -test tests::exit ... ok -test tests::rand_uni ... ignored -test tests::eval_iter ... ok -test tests::factorial ... ok -test tests::neg ... ok -test tests::empty ... ok -test tests::recall_expression ... ok -test tests::round ... ok -test tests::store ... ok -test tests::par ... ok -test tests::eval ... ok -test tests::number_literal ... ok -test tests::term ... ok -test tests::exp ... ok -test tests::mult ... ok -test tests::rand ... ok - -test result: ok. 25 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.01s - - Running unittests src/main.rs (target/debug/deps/calc-36458944d29cbbaf) - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - - Doc-tests calc_lib - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - -``` - + #### Formal language specification For a more precise specification of the “calc language”, one can read the diff --git a/src/main.rs b/src/main.rs @@ -1,166 +1,6 @@ -//! # `calc` -//! -//! `calc` is a CLI calculator for basic -//! rational number arithmetic using standard operator precedence and associativity. Internally, it is -//! based on [`Ratio<T>`](https://docs.rs/num/latest/num/rational/struct.Ratio.html) -//! and [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html). -//! -//! ## Calc in action -//! -//! ```bash -//! [zack@laptop ~]$ calc -//! 2.71828^0^3.14159 + -1! -//! > 0 -//! s -//! > 0 -//! @^0 -//! > 1 -//! s -//! > 1 -//! @/3 * 3 -//! > 1 -//! s -//! > 1 -//! |@2 - 9|^(1 - 2*3) -//! > 1/32768 -//! s -//! > 1/32768 +//! # `calc` //! -//! > 0.000030517578125 -//! round(@, 3) -//! > 0 -//! round(@, 6) -//! > 31/1000000 -//! -//! > 0.000031 -//! 2/3 -//! > 2/3 -//! -//! > 0.666666667 -//! rand() -//! > 939435294927814822 -//! rand(1+9,10!) -//! > 2660936 -//! 1+4 mod 2 + 1 -//! > 2 -//! -5 mod 2 -//! > 1 -//! -5 mod -2 -//! > 1 -//! 5 mod -2 -//! > 1 -//! 9^0.5 -//! > 3 -//! (4/9)^(-1/2) -//! > 3/2 -//! q -//! [zack@laptop ~]$ -//! ``` -//! -//! ## Expressions -//! -//! The following are the list of expressions in descending order of precedence: -//! 1. number literals, `@`, `()`, `||`, `round()`, `rand()` -//! 2. `!` -//! 3. `^` -//! 4. `-` (unary negation operator) -//! 5. `*`, `/`, `mod` -//! 6. `+`, `-` -//! -//! All binary operators are left-associative sans `^` which is right-associative. -//! -//! Any expression is allowed to be enclosed in `()`. Note that parentheses are purely for grouping expressions; -//! in particular, you cannot use them to represent multiplication (e.g., `4(2)` is grammatically incorrect and -//! will result in an error message). -//! -//! Any expression is allowed to be enclosed in `||`. This unary operator represents absolute value. -//! -//! `!` is the factorial operator. Due to its high precedence, something like *-i!^j!* for *i, j ∈ ℕ* is -//! the same thing as *-((i!)^(j!))*. If the expression preceding it does not evaluate to a non-negative integer, -//! then an error will be displayed. Spaces and tabs are *not* ignored; so `1 !` is grammatically incorrect and -//! will result in an error message. -//! -//! `^` is the exponentiation operator. The expression left of the operator can evaluate to any rational number; -//! however the expression right of the operator must evaluate to an integer or ±1/2 unless the expression on -//! the left evaluates to `0` or `1`. In the event of the former, the expression right of the operator must evaluate -//! to a non-negative rational number. In the event of the latter, the expression right of the operator can evaluate to -//! any rational number. Note that `0^0` is defined to be 1. When the operand right of `^` evaluates to ±1/2, then -//! the left operand must be the square of a rational number. -//! -//! The unary operator `-` represents negation. -//! -//! The operators `*` and `/` represent multiplication and division respectively. Expressions right of `/` -//! must evaluate to any non-zero rational number; otherwise an error will be displayed. -//! -//! The binary operator `mod` represents modulo such that *n mod m = r = n - m\*q* for *n,q ∈ ℤ, m ∈ ℤ\\{0}, and r ∈ ℕ* -//! where *r* is the minimum non-negative solution. -//! -//! The binary operators `+` and `-` represent addition and subtraction respectively. -//! -//! With the aforementioned exception of `!`, all spaces and tabs before and after operators are ignored. -//! -//! ## Round expression -//! -//! `round(expression, digit)` rounds `expression` to `digit`-number of fractional digits. An error will -//! be displayed if called incorrectly. -//! -//! ## Rand expression -//! -//! `rand(expression, expression)` generates a random 64-bit integer inclusively between the passed expressions. -//! An error will be displayed if called incorrectly. `rand()` generates a random 64-bit integer. -//! -//! ## Numbers -//! -//! A number literal is a non-empty sequence of digits or a non-empty sequence of digits immediately followed by `.` -//! which is immediately followed by a non-empty sequence of digits (e.g., `134.901`). This means that number -//! literals represent precisely all rational numbers that are equivalent to a ratio of a non-negative integer -//! to a positive integer whose sole prime factors are 2 or 5. To represent all other rational numbers, the unary -//! operator `-` and binary operator `/` must be used. -//! -//! ## Empty expression -//! -//! The empty expression (i.e., expression that at most only consists of spaces and tabs) will return -//! the result from the previous non-(empty/store) expression in *decimal* form using the minimum number of digits. -//! In the event an infinite number of digits is required, it will be rounded to 9 fractional digits using normal rounding -//! rules first. -//! -//! ## Store expression -//! -//! To store the result of the previous non-(empty/store) expression, one simply passes `s`. In addition to storing the -//! result which will subsequently be available via `@`, it displays the result. At most 8 results can be stored at once; -//! at which point, results that are stored overwrite the oldest result. -//! -//! ## Recall expression -//! -//! `@` is used to recall previously stored results. It can be followed by any *digit* from `1` to `8`. -//! If such a digit does not immediately follow it, then it will be interpreted as if there were a `1`. -//! `@i` returns the *i*-th most-previous stored result where *i ∈ {1, 2, 3, 4, 5, 6, 7, 8}*. -//! Note that spaces and tabs are *not* ignored so `@ 2` is grammatically incorrect and will result in an error message. -//! As emphasized, it does not work on expressions; so both `@@` and `@(1)` are grammatically incorrect. -//! -//! ## Character encoding -//! -//! All inputs must only contain the ASCII encoding of the following Unicode scalar values: `0`-`9`, `.`, `+`, `-`, -//! `*`, `/`, `^`, `!`, `mod`, `|`, `(`, `)`, `round`, `rand`, `,`, `@`, `s`, &lt;space&gt;, &lt;tab&gt;, -//! &lt;line feed&gt;, &lt;carriage return&gt;, and `q`. Any other byte sequences are grammatically incorrect and will -//! lead to an error message. -//! -//! ## Errors -//! -//! Errors due to a language violation (e.g., dividing by `0`) manifest into an error message. `panic!`s -//! and [`io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html)s caused by writing to the global -//! standard output stream lead to program abortion. On OpenBSD-stable when compiled with the `priv_sep` feature, -//! it will error if [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) errors with the promise of -//! `"stdio"`. -//! -//! ## Exiting -//! -//! `q` with any number of spaces and tabs before and after will cause the program to terminate. -//! -//! ### Formal language specification -//! -//! For a more precise specification of the “calc language”, one can read the -//! [calc language specification](https://git.philomathiclife.com/calc_rational/lang.pdf). +//! Consult [`README.md`](https://crates.io/crates/calc_rational). #![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))] #![deny( unsafe_code, @@ -195,11 +35,11 @@ use std::io::{self, Error, Write}; /// Error returned by [`main`]. #[allow(clippy::exhaustive_enums)] #[derive(Debug)] -pub enum Err { - // Error returned from [`writeln`]. - Error(Error), +enum Err { + /// Error returned from [`writeln`]. + Io(Error), #[cfg(all(feature = "priv_sep", target_os = "openbsd"))] - // Error returned from [`pledge`]. + /// Error returned from [`pledge`]. Pledge(Error), } impl Display for Err { @@ -207,7 +47,7 @@ impl Display for Err { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { - Self::Error(ref e) => e.fmt(f), + Self::Io(ref e) => e.fmt(f), #[cfg(all(feature = "priv_sep", target_os = "openbsd"))] Self::Pledge(ref e) => write!(f, "pledge(2)ing 'stdio' failed with {e}"), } @@ -237,14 +77,14 @@ fn main() -> Result<(), Err> { const fn privsep() -> Result<(), Err> { Ok(()) } - privsep().and_then(|_| { + privsep().and_then(|()| { let mut out = io::stdout().lock(); - EvalIter::new(io::stdin().lock()).lend_try_fold((), |_, res| { + EvalIter::new(io::stdin().lock()).lend_try_fold((), |(), res| { match res { Ok(o) => writeln!(&mut out, "{o}"), Err(e) => writeln!(&mut out, "{e}"), } - .map_err(Err::Error) + .map_err(Err::Io) }) }) }