calc_rational

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

commit 39807875e38def044b66ac250be28b7df73d73b1
parent 4f1ec116c052c314994c95323776d0e67622a773
Author: Zack Newman <zack@philomathiclife.com>
Date:   Tue, 18 Apr 2023 17:05:34 -0600

refactor. make empty expression display decimal form. dont store results automatically.

Diffstat:
MCargo.toml | 2+-
MREADME.md | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mlang.tex | 80+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/cache.rs | 25++++++++++++++-----------
Msrc/lib.rs | 2453++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/main.rs | 205+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
6 files changed, 2196 insertions(+), 763 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.1.2" +version = "0.2.0" [lib] name = "calc_lib" diff --git a/README.md b/README.md @@ -1,10 +1,8 @@ # calc_rational calc_rational consists of a binary crate `calc` and a library crate -[`calc_lib`](https://docs.rs/calc_rational/latest/calc_lib). `calc` is a CLI calculator for basic rational number arithmetic -using standard operator precedence and associativity. Additionally, it -stores the previous eight evaluated expressions as well as provides a -way to display previous results in decimal form. Internally, it is +[`calc_lib`](https://docs.rs/calc_rational/latest/calc_lib). `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). @@ -14,28 +12,46 @@ and [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html). [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 -=3@ -> 0.000 -=6@ +s +> 1/32768 + +> 0.000030517578125 +round(@, 3) +> 0 +round(@, 6) +> 31/1000000 + > 0.000031 +2/3 +> 2/3 + +> 0.666666667 @6 There are only 4 previous results. -=2 @2 -Invalid recall statement. A recall statement must be of the extended regex form: ^ *= *[0-9]?@[1-8]? *$. +s 1 +Invalid store expression. A store expression must be of the extended regex form: ^[ \t]*s[ \t]*$. q a -Invalid quit statement. A quit statement must be of the extended regex form: ^ *q *$. +Invalid quit expression. A quit expression must be of the extended regex form: ^[ \t]*q[ \t]*$. a -Missing terminal expression at position 0. A terminal expression is a decimal literal expression, recall expression, or addition expression enclosed in vertical bars or parentheses. +Missing terminal expression at position 0. A terminal expression is a decimal literal expression, recall expression, absolute value expression, parenthetical expression, or a round expression. |1 Invalid absolute value expression ending at position 2. An absolute value expression is an addition expression enclosed in '||'. (2 Invalid parenthetical expression ending at position 2. A parenthetical expression is an addition expression enclosed in '()'. +round(1,10) +Invalid round expression ending at position 9. A round expression is of the form 'round(<add expression>, digit)' 1/(2-2) Division by zero ending at position 7. 2^(1/2) @@ -45,17 +61,17 @@ Non-negative exponent with a base of 0 ending at position 4. (-1)! Factorial of a rational number that was not a non-negative integer ending at position 5. 4. -Invalid decimal literal expression ending at position 2. A decimal literal expression must be of the extended regex form: *[0-9]+(\.[0-9]+)?. +Invalid decimal literal expression ending at position 2. A decimal literal expression must be of the extended regex form: [0-9]+(\.[0-9]+)?. 1+2 a Trailing symbols starting at position 4. q [zack@laptop ~]$ ``` -## Operators +## Expressions -The following are the list of operators in descending order of precedence: - 1. `()`, `||` +The following are the list of expressions in descending order of precedence: + 1. number literals, `@`, `()`, `||`, `round()` 2. `!` 3. `^` 4. `-` (unary negation operator) @@ -72,7 +88,7 @@ Any expression is allowed to be enclosed in `||`. This unary operator represents `!` 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 are *not* ignored; so `1 !` is grammatically incorrect and +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; @@ -88,7 +104,12 @@ must evaluate to any non-zero rational number; otherwise an error will be displa The binary operators `+` and `-` represent addition and subtraction respectively. -With the aforementioned exception of `!`, all spaces before and after operators are ignored. +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. ## Numbers @@ -98,55 +119,140 @@ literals represent precisely all possible ratios of non-negative integers to non prime factors are 2 or 5. To represent all other rational numbers, the unary operator `-` and binary operator `/` must be used. -## Recall expressions +## Empty expression -`@` is used to recall previously evaluated expressions. 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 result where *i ∈ {1, 2, 3, 4, 5, 6, 7, 8}*. -Note that spaces 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. +The empty expression (i.e., expression that at most only consists of spaces and tabs) will return +the result from the previous non-empty and non-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 -## Displaying results in decimal form +To store the result of the previous non-empty and non-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. -All calculations are done using exact rational number arithmetic; however if one wants to display a previous -result in decimal form, one can use `=<digit>@<1-8>`. For example `=2@` will display the previous result -rounded to two decimal places following normal rounding rules. Note that this is not an expression and so does -not influence previous results as can be recalled by `@`. +## 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 UTF-8 encoding of the following Unicode scalar values: `0`-`9`, `.`, `+`, `-`, -`*`, `/`, `^`, `!`, `|`, `(`, `)`, `@`, `=`, &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. +All inputs must only contain the ASCII encoding of the following Unicode scalar values: `0`-`9`, `.`, `+`, `-`, +`*`, `/`, `^`, `!`, `|`, `(`, `)`, `round`, `,`, `@`, `s`, &lt;space&gt;, &lt;tab&gt;, &lt;line feed&gt;, &lt;carriage return&gt;, +`q`, and `d`. 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, -[`io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html)s that are not of the [`ErrorKind::Interrupted`](https://doc.rust-lang.org/std/io/enum.ErrorKind.html) -variant, and [`fmt::Error`](https://doc.rust-lang.org/beta/core/fmt/struct.Error.html)s all lead to program abortion. +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. ## Exiting -`q` with any number of spaces before and after will cause the program to terminate. +`q` with any number of spaces and tabs before and after will cause the program to terminate. -## Status +### Status This package will be actively maintained until it is deemed “feature complete”. There are really only two properties that will always be true. First, the grammar that generates a “reasonable” superset of the language will -be an unambiguous context-free grammar with operator precedence -embedded within. Last, the language will only deal with the field of +be an unambiguous context-free grammar with expression precedence and binary operator +associativity embedded within. Last, the language will only deal with the field of rational numbers. -The crates are only tested on the x86_64-unknown-linux-gnu target, but +The crates are only tested on the `x86_64-unknown-linux-gnu` target, but they should work on any [Tier 1 with Host Tools](https://doc.rust-lang.org/beta/rustc/platform-support.html) -target. +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 +### Installing -`cargo install calc_rational`. The executable is called `calc`. +```bash +[zack@laptop ~]$ cargo install calc_rational + Updating crates.io index + Installing calc_rational v0.2.0 + Compiling autocfg v1.1.0 + Compiling num-traits v0.2.15 + Compiling num-integer v0.1.45 + Compiling num-bigint v0.4.3 + Compiling num-rational v0.4.1 + Compiling calc_rational v0.1.2 + Finished release [optimized] target(s) in 6.09s + Installing /home/zack/.cargo/bin/calc + Installed package `calc_rational v0.2.0` (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 + Updating crates.io index + Compiling autocfg v1.1.0 + Compiling num-traits v0.2.15 + Compiling num-integer v0.1.45 + Compiling num-bigint v0.4.3 + Compiling num-rational v0.4.1 + Compiling calc_rational v0.2.0 (/home/zack/calc_rational) + Finished release [optimized] target(s) in 8.25s +[zack@laptop calc_rational]$ cargo t + Compiling autocfg v1.1.0 + Compiling num-traits v0.2.15 + Compiling num-integer v0.1.45 + Compiling num-bigint v0.4.3 + Compiling num-rational v0.4.1 + Compiling calc_rational v0.2.0 (/home/zack/calc_rational) + Finished test [unoptimized + debuginfo] target(s) in 2.40s + Running unittests src/lib.rs (target/debug/deps/calc_lib-381715bd94efc206) + +running 23 tests +test cache::tests::test_get ... ok +test cache::tests::test_index ... ok +test cache::tests::test_is_empty ... ok +test cache::tests::test_get_unsafe ... ok +test cache::tests::test_len ... ok +test cache::tests::test_new ... ok +test tests::exit ... ok +test cache::tests::test_index_panic - should panic ... ok +test tests::abs ... ok +test cache::tests::test_push ... ok +test tests::factorial ... ok +test tests::neg ... ok +test tests::recall_expression ... ok +test tests::par ... ok +test tests::empty ... ok +test tests::eval ... ok +test tests::store ... ok +test tests::exp ... ok +test tests::add ... ok +test tests::mult ... ok +test tests::round ... ok +test tests::term ... ok +test tests::number_literal ... ok + +test result: ok. 23 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running unittests src/main.rs (target/debug/deps/calc-42b0141dee0be10c) + +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 +#### 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). +[calc language specification](https://git.philomathiclife.com/calc_rational/lang.pdf). diff --git a/lang.tex b/lang.tex @@ -6,49 +6,58 @@ We will define the language, \(L\), of our rational number calculator program. Define the set of non-terminal symbols to be \begin{align*} -N = \{&expr, empty, quit, disp, add, mult, neg, exp, fact, term,\\ -&ws, l, dec, int, at, digit, prev\}. +N = \{&expr, empty, quit, eval, store, add, mult, neg, exp, fact, term,\\ +&r, abs, recall, par, dec, int, ws, digit, prev, l\}. \end{align*} Define the set of terminal symbols to be -\[ \Sigma = \{0,1,2,3,4,5,6,7,8,9,.,+,-,*,/,\string^,!,|(,),@,=,space,tab,lf,cr,q\}. \] +\begin{align*} +\Sigma = \{&0,1,2,3,4,5,6,7,8,9,.,+,-,*,/,\string^,!,|(,),round,,,@,s,space\\ +&tab,lf,cr,q,d\}. +\end{align*} Define the production rules, \(P\), as the following: \begin{enumerate} - \item \(expr \rightarrow empty\ |\ quit\ |\ disp\ |\ ws\ add\ ws\ l\) + \item \(expr \rightarrow empty\ |\ quit\ |\ eval\ |\ store\) \item \(empty \rightarrow ws\ l\) \item \(quit \rightarrow ws\ q\ ws\ l\) - \item \(disp \rightarrow ws\ = \ ws\ at \ ws\ l\ |\ ws\ = \ ws\ digit\ at \ ws\ l\) + \item \(eval\rightarrow ws\ add\ ws\ l\) + \item \(store\rightarrow ws\ s\ ws\ l\) \item \(add \rightarrow add\ ws\ +\ ws\ mult\ |\ add\ ws\ -\ ws\ mult\ |\ mult\) \item \(mult \rightarrow mult\ ws\ *\ ws\ neg\ |\ mult\ ws\ /\ ws\ neg\ |\ neg\) \item \(neg \rightarrow -\ ws\ neg\ |\ exp\) \item \(exp \rightarrow fact\ ws\ \string^\ ws\ neg\ |\ fact\) \item \(fact \rightarrow fact!\ |\ term\) - \item \(term \rightarrow dec\ |\ at\ |\ |ws\ add\ ws|\ |\ (ws\ add\ ws)\) - \item \(l \rightarrow lf\ |\ cr\ lf\) - \item \(ws \rightarrow space\ ws\ |\ tab\ ws\ |\ \epsilon\) + \item \(term \rightarrow dec\ |\ par\ |\ recall\ |\ abs\ |\ r\) + \item \(r \rightarrow round(ws\ add\ ws,ws\ digit\ ws)\) + \item \(abs \rightarrow |ws\ add\ ws|\) + \item \(recall \rightarrow @\ |\ @prev\) + \item \(par \rightarrow (ws\ add\ ws)\) \item \(dec \rightarrow int\ |\ int.int\) \item \(int \rightarrow digit\ |\ digit\ int\) - \item \(at \rightarrow @\ |\ @prev\) + \item \(ws \rightarrow space\ ws\ |\ tab\ ws\ |\ \epsilon\) \item \(digit \rightarrow prev\ |\ 0\ |\ 9\) \item \(prev \rightarrow 1\ |\ 2\ |\ 3\ |\ 4\ |\ 5\ |\ 6\ |\ 7\ |\ 8\) + \item \(l \rightarrow lf\ |\ cr\ lf\) \end{enumerate} Note that the use of spaces above is purely for visualization purposes (e.g., \(digit\ int\) does not actually have a space). Define the start symbol to be \(expr\). Define the unambiguous, context-free grammar to be \[ G = (N, \Sigma, P, expr). \] -Let \(\mathcal{L}(G)\) be the language generated from \(G\). Let \(@ = @1\), and \(@prev\) represent the -\(prev^{th}\) most-recent result. \(lf\) is the Unicode scalar value U+000A, \(cr\) is the Unicode scalar value -U+000D, \(space\) is the Unicode scalar value U+0020, \(tab\) is the Unicode scalar value U+0009, and \(\epsilon\) -is the empty string. We define \(\mathbb{Q} \subset L \subset \mathcal{L}(G)\) with \(\mathbb{Q}\) representing the -field of rational numbers such that \(L\) extends \(\mathbb{Q}\) with the ability to recall the previous one to -eight results as well as adds the unary operators \(||\), \(-\), and \(!\) as well as the binary operator -\(\string^\) to mean absolute value, negation, factorial, and exponentiation respectively. Note that this means -for \(mult / exp\), \(exp\) does not evaluate to 0. Similarly, \(term\string^exp\) is valid iff \(term\) evaluates -to 1, \(term\) evaluates to 0 and \(exp\) evaluates to a non-negative rational number—\(0^{0}\) is defined to be -1—or \(term\) evaluates to any other rational number and \(exp\) evaluates to an integer. \(!\) is only defined for -non-negative integers. \(@prev\) is only defined iff at least \(prev\) number of previous expressions have been -evaluated. From the above grammar, we see the operator precedence in descending order is the following: +Let \(\mathcal{L}(G)\) be the language generated from \(G\). When \(@\) is not immediately followed by a \(prev\), +let it mean \(@1\). \(@prev\) represents the \(prev^{th}\) most-recent result that has been stored from a \(store\) +expression. \(lf\) is the Unicode scalar value U+000A, \(cr\) is the Unicode scalar value U+000D, \(space\) is the +Unicode scalar value U+0020, \(tab\) is the Unicode scalar value U+0009, and \(\epsilon\) is the empty string. We +define \(\mathbb{Q} \subset L \subset \mathcal{L}(G)\) with \(\mathbb{Q}\) representing the field of rational +numbers such that \(L\) extends \(\mathbb{Q}\) with the ability to recall the previous one to eight \(store\) +results as well as adds the unary operators \(||\), \(-\), and \(!\) as well as the binary operator \(\string^\) to +mean absolute value, negation, factorial, and exponentiation respectively. It also adds the function \(round\) which +rounds the passed expression to \(digit\)-number of fractional digits. Note that this means for \(mult / exp\), +\(exp\) does not evaluate to 0. Similarly, \(term\string^exp\) is valid iff \(term\) evaluates to 1, \(term\) +evaluates to 0 and \(exp\) evaluates to a non-negative rational number—\(0^{0}\) is defined to be 1—or \(term\) +evaluates to any other rational number and \(exp\) evaluates to an integer. \(!\) is only defined for non-negative +integers. \(@prev\) is only defined iff at least \(prev\) number of previous \(store\) expressions have been +evaluated. From the above grammar, we see the expression precedence in descending order is the following: \begin{enumerate} - \item \(()\), \(||\) + \item number literals, \(()\), \(@\), \(||\), \(round()\) \item \(!\) \item \(\string^\) \item \(-\) (the unary negation operator) @@ -71,23 +80,12 @@ For \(n\in\mathbb{N}\) we define the factorial operator as \[ n! = n * (n - 1) *\cdots * 1 \] which of course equals 1 when \(n = 0\). -For the empty expression and the exit (i.e., \(q\)) and “recall” statements (i.e., statements that have \(=\)), -the previous results are left in tact; all other expressions push the evaluated result to be the next previous -result. Recall statements are used purely to display a previous value with the option to round to \(digit\) number -of fractional digits using normal rounding rules. For example, -\begin{align*} -&4\\ -&@\\ -&4+@2 -\end{align*} -returns 4, stores 4 as the previous result, returns 4, pushes 4 to be the second-most previous result, pushes -4 to be the previous result, returns 8, pushes 4 to be the third-most previous result, pushes 4 to be the -second-most previous result, and pushes 8 to be the most previous result. In contrast, -\begin{align*} -&4\\ -&=@\\ -&4+@2 -\end{align*} -returns 4, stores 4 as the previous result, returns 4, and fails since the last line is not part of our -language \(L\) since there is no second-previous result. +The \(empty\) expression (i.e., expression consisting of spaces and tabs) returns the result of the previous \(eval\) +expression in decimal form—in the event there is no such previous expression, it returns the empty string. The +minimum number of digits will be used; if the value requires an infinite number of digits, then the value will be +rounded to 9 fractional digits. The \(quit\) expression (i.e., expression consisting of spaces, tabs, and exactly +one \(q\)) causes the program to terminate. The \(store\) expression (i.e., expression consisting of spaces, tabs, +and exactly one \(s\)) stores and returns the result from the previous \(eval\) expression and can be recalled with +\(@\). At most 8 results can be stored; after which the oldest result is overwritten. Stored results cannot be +unstored. \end{document} diff --git a/src/cache.rs b/src/cache.rs @@ -72,20 +72,17 @@ impl<T> Cache<T, $x> { self.vals.index(self.next_head.wrapping_sub(1).wrapping_sub(idx) & ($x - 1)) } /// Adds `val` to the cache. In the event `N` values have already been cached, - /// the oldest entry is overwritten. Returns a shared reference to the - /// value it inserts. + /// the oldest entry is overwritten. #[inline] #[allow(clippy::indexing_slicing)] - pub fn push(&mut self, val: T) -> &T { + pub fn push(&mut self, val: T) { if self.len < $x { self.len += 1; } // next_head is always less than or equal to N since we only implement // this function when N is a power of 2. self.vals[self.next_head] = val; - let ret = self.vals.index(self.next_head); self.next_head = (self.next_head + 1) & ($x - 1); - ret } } impl<T> Index<usize> for Cache<T, $x> { @@ -297,12 +294,18 @@ mod tests { #[test] fn test_push() { let mut c = Cache::<bool, 4>::new(); - assert!(c.push(true)); - assert!(c.push(true)); - assert!(!c.push(false)); - assert!(c.push(true)); - assert!(!c.push(false)); - assert!(!c.push(false)); + c.push(true); + assert!(c[0]); + c.push(true); + assert!(c[0]); + c.push(false); + assert!(!c[0]); + c.push(true); + assert!(c[0]); + c.push(false); + assert!(!c[0]); + c.push(false); + assert!(!c[0]); } #[test] fn test_new() { diff --git a/src/lib.rs b/src/lib.rs @@ -31,92 +31,32 @@ use cache::Cache; use core::convert; use core::fmt::{self, Display, Formatter}; use num_bigint::{BigInt, BigUint, Sign}; -use num_rational::Ratio; use num_integer::Integer; +use num_rational::Ratio; use num_traits::Pow; use E::{ DivByZero, ExpDivByZero, ExpIsNotInt, InvalidAbs, InvalidDec, InvalidPar, InvalidQuit, - InvalidRecall, MissingLF, MissingTerm, NotEnoughPrevResults, NotNonNegIntFact, TrailingSyms, + InvalidRound, InvalidStore, MissingLF, MissingTerm, NotEnoughPrevResults, NotNonNegIntFact, + TrailingSyms, }; -use O::{Empty, Exit, Prev, Val}; +use O::{Empty, Eval, Exit, Store}; /// Fixed-sized cache that automatically overwrites the oldest data /// when a new item is added and the cache is full. One can think of /// [`Cache`] as a very limited but more performant [`VecDeque`][alloc::collections::VecDeque] that only /// adds new data or reads old data. pub mod cache; -/// A value from a previous expression that is to be -/// displayed potentially in decimal form. -#[derive(Debug)] -pub struct PrevVal<'a> { - /// A cached value from a previous expression. - val: &'a Ratio<BigInt>, - /// None iff the previous value is to be displayed - /// in fractional form; otherwise contains the number - /// of fractional digits from 0 to 9 `val` will be rounded - /// to. - round: Option<u8>, -} -impl<'a> Display for PrevVal<'a> { - #[inline] - #[allow(unsafe_code, clippy::as_conversions, clippy::indexing_slicing)] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(rem) = self.round { - let r = rem as usize; - let mult = BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10])).pow(r); - // int < 0 iff val <= -1. frac < 0 iff val is a negative non-integer. - let (int, frac) = (self.val * &mult).round().numer().div_rem(&mult); - let int_str = int.to_string().into_bytes(); - let frac_str = frac.to_string().into_bytes(); - // The max length is the length of int_str plus the number of fractional digits - // plus a possible negative sign plus a possible decimal point. - // At most we will over-allocate by 2 precisely when int is negative - // and r is 0. - let mut v = Vec::with_capacity(int_str.len() + 2 + r); - // In the case val is a negative value greater than -1, - // we have to add the negative sign since 0 is unsigned. - // Technically this can panic! if one of the Strings returned from - // to_string is empty; however that is a bug, or at least undesired - // behavior, in the to_string implementation from BigInt. - let frac_val = match (int_str[0] == b'-', frac_str[0] == b'-') { - (false, true) => { - v.push(b'-'); - &frac_str[1..] - } - (true, true) => &frac_str[1..], - _ => frac_str.as_slice(), - }; - v.extend_from_slice(int_str.as_slice()); - if r > 0 { - v.push(b'.'); - let len = v.len(); - // frac_val.len() is <= r. - while v.len() < len + r - frac_val.len() { - v.push(b'0'); - } - v.extend_from_slice(frac_val); - } - // SAFETY: - // v contains precisely the UTF-8 code units returned from Strings - // returned from the to_string function on the integer and fraction part of - // val plus optionally the single byte encodings of ".", "-", and "0". - writeln!(f, "> {}", unsafe { String::from_utf8_unchecked(v) }) - } else { - writeln!(f, "> {}", self.val) - } - } -} /// The error that is returned when attempting /// to evaluate the input. #[derive(Clone, Copy, Debug)] pub enum E { /// The input was not terminated with a line feed. MissingLF, - /// The input began with a `q` but had other non-spaces + /// The input began with a `q` but had non-whitespace /// that followed it. InvalidQuit, - /// The input began with an `=` but - /// was otherwise syntactically invalid. - InvalidRecall, + /// The input began with an `s` but had non-whitespace + /// that followed it. + InvalidStore, /// A sub-expression in the input would have led /// to a division by zero. DivByZero(usize), @@ -134,18 +74,20 @@ pub enum E { /// The input contained a non-empty sequence of digits followed /// by `.` which was not followed by a non-empty sequence of digits. InvalidDec(usize), - /// A recall expression was used to recall the *i*-th previous result, - /// but there are fewer than *i* previous results where + /// A recall expression was used to recall the *i*-th most-recent stored result, + /// but there are fewer than *i* stored where /// *i ∈ {1, 2, 3, 4, 5, 6, 7, 8}*. NotEnoughPrevResults(u8), /// The input did not contain a closing `|`. InvalidAbs(usize), /// The input did not contain a closing `)`. InvalidPar(usize), + /// The input contained an invalid round expression. + InvalidRound(usize), /// A sub-expression in the input had a missing terminal expression /// where a terminal expression is a decimal literal expression, - /// recall expression, absolute value expression, or a parenthetical - /// expression. + /// recall expression, absolute value expression, parenthetical + /// expression, or round expression. MissingTerm(usize), /// The input started with a valid expression but was immediately followed /// by symbols that could not be chained with the preceding expression. @@ -155,48 +97,140 @@ impl Display for E { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { - MissingLF => f.write_str("The last character is not a line feed.\n"), - InvalidRecall => f.write_str("Invalid recall statement. A recall statement must be of the extended regex form: ^ *= *[0-9]?@[1-8]? *$.\n"), - InvalidQuit => f.write_str("Invalid quit statement. A quit statement must be of the extended regex form: ^ *q *$.\n"), - DivByZero(u) => writeln!(f, "Division by zero ending at position {u}."), - ExpIsNotInt(u) => writeln!(f, "Non-integer exponent with a base that was not 0 or 1 ending at position {u}."), - ExpDivByZero(u) => writeln!(f, "Non-negative exponent with a base of 0 ending at position {u}."), - NotNonNegIntFact(u) => writeln!(f, "Factorial of a rational number that was not a non-negative integer ending at position {u}."), - InvalidDec(u) => writeln!(f, "Invalid decimal literal expression ending at position {u}. A decimal literal expression must be of the extended regex form: *[0-9]+(\\.[0-9]+)?."), - NotEnoughPrevResults(len) => writeln!(f, "There are only {len} previous results."), - InvalidAbs(u) => writeln!(f, "Invalid absolute value expression ending at position {u}. An absolute value expression is an addition expression enclosed in '||'."), - InvalidPar(u) => writeln!(f, "Invalid parenthetical expression ending at position {u}. A parenthetical expression is an addition expression enclosed in '()'."), - MissingTerm(u) => writeln!(f, "Missing terminal expression at position {u}. A terminal expression is a decimal literal expression, recall expression, or addition expression enclosed in vertical bars or parentheses."), - TrailingSyms(u) => writeln!(f, "Trailing symbols starting at position {u}."), + MissingLF => f.write_str("The last character is not a line feed."), + InvalidStore => f.write_str("Invalid store expression. A store expression must be of the extended regex form: ^[ \\t]*s[ \\t]*$."), + InvalidQuit => f.write_str("Invalid quit expression. A quit expression must be of the extended regex form: ^[ \\t]*q[ \\t]*$."), + DivByZero(u) => write!(f, "Division by zero ending at position {u}."), + ExpIsNotInt(u) => write!(f, "Non-integer exponent with a base that was not 0 or 1 ending at position {u}."), + ExpDivByZero(u) => write!(f, "Non-negative exponent with a base of 0 ending at position {u}."), + NotNonNegIntFact(u) => write!(f, "Factorial of a rational number that was not a non-negative integer ending at position {u}."), + InvalidDec(u) => write!(f, "Invalid decimal literal expression ending at position {u}. A decimal literal expression must be of the extended regex form: [0-9]+(\\.[0-9]+)?."), + NotEnoughPrevResults(len) => write!(f, "There are only {len} previous results."), + InvalidAbs(u) => write!(f, "Invalid absolute value expression ending at position {u}. An absolute value expression is an addition expression enclosed in '||'."), + InvalidPar(u) => write!(f, "Invalid parenthetical expression ending at position {u}. A parenthetical expression is an addition expression enclosed in '()'."), + InvalidRound(u) => write!(f, "Invalid round expression ending at position {u}. A round expression is of the form 'round(<add expression>, digit)'"), + MissingTerm(u) => write!(f, "Missing terminal expression at position {u}. A terminal expression is a decimal literal expression, recall expression, absolute value expression, parenthetical expression, or a round expression."), + TrailingSyms(u) => write!(f, "Trailing symbols starting at position {u}."), } } } /// A successful evaluation of an input. #[derive(Debug)] pub enum O<'a> { - /// The result of a passed expression. - Val(&'a Ratio<BigInt>), - /// The previous result of the requested - /// recall statement. - Prev(PrevVal<'a>), - /// The input contained all spaces which is part of our language - /// but does not do anything. - Empty, - /// A quit statement was issued to terminate the program. + /// The input only contained whitespace. + /// This returns the previous `Eval`. + /// It is `None` iff there have been no + /// previous `Eval` results. + Empty(&'a Option<Ratio<BigInt>>), + /// The quit expression was issued to terminate the program. Exit, + /// Result of a "normal" expression. + Eval(&'a Ratio<BigInt>), + /// The store expression stores and returns the previous `Eval`. + /// It is `None` iff there have been no previous `Eval` results. + Store(&'a Option<Ratio<BigInt>>), } impl<'a> Display for O<'a> { #[inline] + #[allow(unsafe_code, clippy::indexing_slicing)] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { - Val(r) => writeln!(f, "> {r}"), - Prev(ref p) => p.fmt(f), - Empty | Exit => Ok(()), + Empty(o) => { + o.as_ref().map_or(Ok(()), |val| { + if val.is_integer() { + write!(f, "> {val}") + } else { + // If the prime factors of the denominator are only 2 and 5, + // then the number requires a finite number of digits and thus + // will be represented perfectly using the fewest number of digits. + // Any other situation will be rounded to 9 fractional digits. + // max{twos, fives} represents the minimum number of fractional + // digits necessary to represent val. + let mut twos = 0; + let mut fives = 0; + let zero = BigInt::from_biguint(Sign::NoSign, BigUint::new(Vec::new())); + let one = BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])); + let two = BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])); + let five = BigInt::from_biguint(Sign::Plus, BigUint::new(vec![5])); + let mut denom = val.denom().clone(); + let mut div_rem; + while denom > one { + div_rem = denom.div_rem(&two); + if div_rem.1 == zero { + twos += 1; + denom = div_rem.0; + } else { + break; + } + } + while denom > one { + div_rem = denom.div_rem(&five); + if div_rem.1 == zero { + fives += 1; + denom = div_rem.0; + } else { + break; + } + } + // int < 0 iff val <= -1. frac < 0 iff val is a negative non-integer. + let (int, frac, digits) = if denom == one { + let (int, mut frac) = val.numer().div_rem(val.denom()); + while twos > fives { + frac *= &five; + fives += 1; + } + while fives > twos { + frac *= &two; + twos += 1; + } + (int, frac, twos) + } else { + // Requires an infinite number of decimal digits to represent, so we display + // 9 digits after rounding. + let mult = + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10])).pow(9u8); + let (int, frac) = (val * &mult).round().numer().div_rem(&mult); + (int, frac, 9) + }; + let int_str = int.to_string().into_bytes(); + let (mut v, frac_str) = if val.numer().sign() == Sign::Minus { + if int_str[0] == b'-' { + ( + Vec::with_capacity(int_str.len() + 1 + digits), + (-frac).to_string().into_bytes(), + ) + } else { + let mut tmp = Vec::with_capacity(int_str.len() + 2 + digits); + tmp.push(b'-'); + (tmp, (-frac).to_string().into_bytes()) + } + } else { + ( + Vec::with_capacity(int_str.len() + 1 + digits), + frac.to_string().into_bytes(), + ) + }; + v.extend_from_slice(int_str.as_slice()); + v.push(b'.'); + // digits >= frac_str.len(). + v.resize(v.len() + (digits - frac_str.len()), b'0'); + v.extend_from_slice(frac_str.as_slice()); + // SAFETY: + // v contains precisely the UTF-8 code units returned from Strings + // returned from the to_string function on the integer and fraction part of + // val plus optionally the single byte encodings of ".", "-", and "0". + write!(f, "> {}", unsafe { String::from_utf8_unchecked(v) }) + } + }) + } + Eval(r) => write!(f, "> {r}"), + Exit => Ok(()), + Store(o) => o.as_ref().map_or(Ok(()), |val| write!(f, "> {val}")), } } } /// Evaluates the supplied input. -pub struct Evaluator<'input, 'cache, 'exp> { +pub struct Evaluator<'input, 'cache, 'prev, 'exp> { /// The input to be evaluated. utf8: &'input [u8], /// The index within `utf8` that evaluation needs to continue. @@ -204,8 +238,10 @@ pub struct Evaluator<'input, 'cache, 'exp> { /// to be able to report the position within the input /// that an error occurs. i: usize, - /// The cache of previously evaluated expressions. - prev: &'cache mut Cache<Ratio<BigInt>, 8>, + /// The cache of previously stored results. + cache: &'cache mut Cache<Ratio<BigInt>, 8>, + /// The last result. + prev: &'prev mut Option<Ratio<BigInt>>, /// Buffer used to evaluate exponentiation sub-expressions. /// We require buffering exponentiation sub-expressions due /// to their being right-associative. Instead of repeatedly @@ -213,17 +249,19 @@ pub struct Evaluator<'input, 'cache, 'exp> { /// buffer that can be re-used across multiple evaluations. exp_cache: &'exp mut Vec<Ratio<BigInt>>, } -impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { - /// Creates an `Evaluator<'input, 'cache, 'exp>` based on the supplied arguments. +impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> { + /// Creates an `Evaluator<'input, 'cache, 'prev, 'exp>` based on the supplied arguments. #[inline] pub fn new( utf8: &'input [u8], - prev: &'cache mut Cache<Ratio<BigInt>, 8>, + cache: &'cache mut Cache<Ratio<BigInt>, 8>, + prev: &'prev mut Option<Ratio<BigInt>>, exp_cache: &'exp mut Vec<Ratio<BigInt>>, - ) -> Evaluator<'input, 'cache, 'exp> { + ) -> Evaluator<'input, 'cache, 'prev, 'exp> { Self { utf8, i: 0, + cache, prev, exp_cache, } @@ -234,30 +272,22 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { /// /// Returns an [`E`] iff the input violates the calc language. #[inline] - #[allow(clippy::indexing_slicing)] - pub fn evaluate(mut self) -> Result<O<'cache>, E> { + #[allow(unsafe_code, clippy::indexing_slicing)] + pub fn evaluate(mut self) -> Result<O<'prev>, E> { self.utf8 = if self.utf8.last().map_or(true, |b| *b != b'\n') { - return Err(MissingLF) + return Err(MissingLF); } else { - &self.utf8[..self.utf8.len() - self.utf8.get(self.utf8.len().wrapping_sub(2)).map_or(1, |b| if *b == b'\r' { 2 } else { 1 })] + &self.utf8[..self.utf8.len() + - self + .utf8 + .get(self.utf8.len().wrapping_sub(2)) + .map_or(1, |b| if *b == b'\r' { 2 } else { 1 })] }; self.consume_ws(); let Some(b) = self.utf8.get(self.i) else { - return Ok(Empty) + return Ok(Empty(self.prev)) }; - if *b == b'=' { - self.i += 1; - self.consume_ws(); - let Some(b2) = self.utf8.get(self.i) else { - return Err(InvalidRecall) - }; - let b3 = *b2; - let round = b3.is_ascii_digit().then(|| { - self.i += 1; - b3 - b'0' - }); - self.get_recall().map(|val| Prev(PrevVal { val, round })) - } else if *b == b'q' { + if *b == b'q' { self.i += 1; self.consume_ws(); if self.i == self.utf8.len() { @@ -265,11 +295,26 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { } else { Err(InvalidQuit) } + } else if *b == b's' { + self.i += 1; + self.consume_ws(); + if self.i == self.utf8.len() { + if let Some(ref val) = *self.prev { + self.cache.push(val.clone()); + } + Ok(Store(self.prev)) + } else { + Err(InvalidStore) + } } else { - self.get_adds().and_then(move |r| { + self.get_adds().and_then(move |val| { self.consume_ws(); if self.i == self.utf8.len() { - Ok(Val(self.prev.push(r))) + *self.prev = Some(val); + // SAFETY: + // We know this is not None since we assign the value + // directly above. + Ok(Eval(unsafe { self.prev.as_ref().unwrap_unchecked() })) } else { Err(TrailingSyms(self.i)) } @@ -285,11 +330,9 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { // Err taking the role of ControlFlow::Break. self.i += self.utf8[self.i..] .into_iter() - .try_fold(0, |val, b| { - match *b { - b' ' | b'\t' => Ok(val + 1), - _ => Err(val), - } + .try_fold(0, |val, b| match *b { + b' ' | b'\t' => Ok(val + 1), + _ => Err(val), }) .map_or_else(convert::identity, convert::identity); } @@ -451,9 +494,9 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { // We can make a copy of self.i here, or call map_or instead // of map_or_else. let i = self.i; - t.numer() - .to_biguint() - .map_or_else(|| Err(NotNonNegIntFact(i)), |val| { + t.numer().to_biguint().map_or_else( + || Err(NotNonNegIntFact(i)), + |val| { let mut factorial = fact(val); while let Some(b2) = self.utf8.get(self.i) { if *b2 == b'!' { @@ -467,7 +510,8 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { Sign::Plus, factorial, ))) - }) + }, + ) } else { Err(NotNonNegIntFact(self.i)) } @@ -476,20 +520,26 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { } } /// Evaluates terminal expressions as defined in the calc language. - /// This function is based on parenthetical expressions, absolute value - /// expressions, recall expressions, and numer literal expressions. + /// This function is based on number literal expressions, parenthetical expressions, + /// recall expressions, absolute value expressions, and round expressions. #[inline] #[allow(clippy::option_if_let_else)] fn get_term(&mut self) -> Result<Ratio<BigInt>, E> { match self.get_rational() { Ok(o) => match o { Some(r) => Ok(r), - None => match self.get_prev() { + None => match self.get_par() { Ok(o2) => match o2 { - Some(r2) => Ok(r2.clone()), - None => self - .get_abs() - .and_then(|o3| o3.map_or_else(|| self.get_par(), Ok)), + Some(r2) => Ok(r2), + None => match self.get_recall() { + Ok(o3) => match o3 { + Some(r3) => Ok(r3.clone()), + None => self + .get_abs() + .and_then(|o4| o4.map_or_else(|| self.get_round(), Ok)), + }, + Err(e3) => Err(e3), + }, }, Err(e2) => Err(e2), }, @@ -497,34 +547,56 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { Err(e) => Err(e), } } - /// Evaluates parenthetical expressions as defined in the calc language. + /// Rounds a value to the specified number of fractional digits. /// This function is based on addition expressions. /// This is the last terminal expression attempted when needing /// a terminal expression; as a result, it is the only terminal expression /// that does not return an `Option`. #[inline] - fn get_par(&mut self) -> Result<Ratio<BigInt>, E> { + fn get_round(&mut self) -> Result<Ratio<BigInt>, E> { // This is the last kind of terminal expression that is attempted. // If there is no more data, then we have a missing terminal expression. - let Some(b) = self.utf8.get(self.i) else { - return Err(MissingTerm(self.i)); + let Some(b) = self.utf8.get(self.i..self.i.saturating_add(6)) else { + return Err(MissingTerm(self.i)) }; - // This is always the last terminal expression we call; so if there is a missing open parenthesis, - // then the sentence is grammatically incorrect. - if *b == b'(' { - self.i += 1; + if b == b"round(" { + self.i += 6; self.consume_ws(); - let r = self.get_adds()?; + let val = self.get_adds()?; self.consume_ws(); let Some(b2) = self.utf8.get(self.i) else { - return Err(InvalidPar(self.i)) + return Err(InvalidRound(self.i)) }; let b3 = *b2; - if b3 == b')' { + if b3 == b',' { self.i += 1; - Ok(r) + self.consume_ws(); + let Some(b4) = self.utf8.get(self.i) else { + return Err(InvalidRound(self.i)) + }; + let r = if b4.is_ascii_digit() { + self.i += 1; + *b4 - b'0' + } else { + return Err(InvalidRound(self.i)); + }; + self.consume_ws(); + let i = self.i; + self.utf8.get(self.i).map_or_else( + || Err(InvalidRound(i)), + |p| { + if *p == b')' { + self.i += 1; + let mult = + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10])).pow(r); + Ok((val * &mult).round() / &mult) + } else { + Err(InvalidRound(self.i)) + } + }, + ) } else { - Err(InvalidPar(self.i)) + Err(InvalidRound(self.i)) } } else { Err(MissingTerm(self.i)) @@ -560,55 +632,57 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { Ok(None) } } - /// Obtains the index within `self.prev` that a recall - /// expression is to return. This is a separate function - /// from [`get_prev`] due to [`get_recall`] also using this - /// logic. + /// Evaluates recall expressions as defined in the calc language. #[inline] - #[allow(clippy::as_conversions)] - fn get_prev_idx(&mut self) -> Option<usize> { + #[allow(clippy::as_conversions, clippy::cast_possible_truncation)] + fn get_recall(&mut self) -> Result<Option<&Ratio<BigInt>>, E> { let Some(b) = self.utf8.get(self.i) else { - return None + return Ok(None) }; - (*b == b'@').then(|| { + if *b == b'@' { self.i += 1; - self.utf8.get(self.i).map_or(0, |b2| { - if (b'1'..b'9').contains(b2) { - self.i += 1; - (*b2 - b'1') as usize - } else { - 0 - } - }) - }) - } - /// Evaluates recall expressions as defined in the calc language. - #[inline] - #[allow(clippy::as_conversions, clippy::cast_possible_truncation)] - fn get_prev(&mut self) -> Result<Option<&Ratio<BigInt>>, E> { - self.get_prev_idx().map_or_else(|| Ok(None), |i| { - self.prev - .get(i) - .map_or_else(|| Err(NotEnoughPrevResults(self.prev.len() as u8)), |r| Ok(Some(r))) - }) + self.cache + .get(self.utf8.get(self.i).map_or(0, |b2| { + if (b'1'..b'9').contains(b2) { + self.i += 1; + (*b2 - b'1') as usize + } else { + 0 + } + })) + .map_or_else( + || Err(NotEnoughPrevResults(self.cache.len() as u8)), + |p| Ok(Some(p)), + ) + } else { + Ok(None) + } } - /// Evaluates recall statements as defined in the calc language. - /// This function has to be separate from [`get_prev`] since it returns - /// a shared reference with 'cache lifetime which requires the function to move self - /// since it has an exclusive reference to the cache. + /// Evaluates parenthetical expressions as defined in the calc language. + /// This function is based on addition expressions. #[inline] - #[allow(clippy::as_conversions, clippy::cast_possible_truncation)] - fn get_recall(mut self) -> Result<&'cache Ratio<BigInt>, E> { - self.get_prev_idx().map_or_else(|| Err(InvalidRecall), move |i| { + fn get_par(&mut self) -> Result<Option<Ratio<BigInt>>, E> { + let Some(b) = self.utf8.get(self.i) else { + return Ok(None) + }; + if *b == b'(' { + self.i += 1; self.consume_ws(); - self.prev.get(i).map_or_else(|| Err(NotEnoughPrevResults(self.prev.len() as u8)), |val| { - if self.i == self.utf8.len() { - Ok(val) - } else { - Err(InvalidRecall) - } - }) - }) + let r = self.get_adds()?; + self.consume_ws(); + let Some(b2) = self.utf8.get(self.i) else { + return Err(InvalidPar(self.i)) + }; + let b3 = *b2; + if b3 == b')' { + self.i += 1; + Ok(Some(r)) + } else { + Err(InvalidPar(self.i)) + } + } else { + Ok(None) + } } /// Evaluates number literal expressions as defined in the calc language. #[inline] @@ -675,532 +749,1735 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { mod tests { use super::*; #[test] - fn prev_string() { - assert!(PrevVal { val: &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![12]))), round: None }.to_string() == "> 12\n"); - assert!(PrevVal { val: &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![12]))), round: Some(0) }.to_string() == "> 12\n"); - assert!(PrevVal { val: &Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![12]))), round: Some(1) }.to_string() == "> -12.0\n"); - assert!(PrevVal { val: &Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3]))), round: None }.to_string() == "> 2/3\n"); - assert!(PrevVal { val: &Ratio::new(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3]))), round: Some(0)}.to_string() == "> -1\n"); - assert!(PrevVal { val: &Ratio::new(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3]))), round: Some(1)}.to_string() == "> -0.7\n"); - assert!(PrevVal { val: &Ratio::new(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![4])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6]))), round: Some(2)}.to_string() == "> -0.67\n"); + fn empty() { + // Empty expressions without a previous result return nothing. + assert!( + match Evaluator::new(b"\n", &mut Cache::new(), &mut None, &mut Vec::new()) + .evaluate() + .unwrap() + { + O::Empty(o) => o.is_none(), + _ => false, + } + ); + assert!( + Evaluator::new( + b" \t \t \n", + &mut Cache::new(), + &mut Some(Ratio::from_integer(BigInt::from_biguint( + Sign::Minus, + BigUint::new(vec![12]) + ))), + &mut Vec::new() + ) + .evaluate() + .unwrap() + .to_string() + == "> -12" + ); + assert!( + Evaluator::new( + b"\t\n", + &mut Cache::new(), + &mut Some(Ratio::new( + BigInt::from_biguint(Sign::Minus, BigUint::new(vec![4])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6])) + )), + &mut Vec::new() + ) + .evaluate() + .unwrap() + .to_string() + == "> -0.666666667" + ); + assert!( + Evaluator::new( + b"\t\n", + &mut Cache::new(), + &mut Some(Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4230196224, 6])) + )), + &mut Vec::new() + ) + .evaluate() + .unwrap() + .to_string() + == "> 0.000000000" + ); + assert!( + Evaluator::new( + b"\t\n", + &mut Cache::new(), + &mut Some(Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![17])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4230196224, 6])) + )), + &mut Vec::new() + ) + .evaluate() + .unwrap() + .to_string() + == "> 0.000000001" + ); + assert!( + Evaluator::new( + b"\t\n", + &mut Cache::new(), + &mut Some(Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10])) + )), + &mut Vec::new() + ) + .evaluate() + .unwrap() + .to_string() + == "> 0.3" + ); + assert!( + Evaluator::new( + b"\t\n", + &mut Cache::new(), + &mut Some(Ratio::new( + BigInt::from_biguint(Sign::Minus, BigUint::new(vec![203])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10])) + )), + &mut Vec::new() + ) + .evaluate() + .unwrap() + .to_string() + == "> -20.3" + ); + assert!( + match Evaluator::new(b" ", &mut Cache::new(), &mut None, &mut Vec::new()) + .evaluate() + .unwrap_err() + { + E::MissingLF => true, + _ => false, + } + ); } #[test] fn number_literal() { // Normal 0 is fine. - assert!(Evaluator::new(b"0", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::NoSign, BigUint::new(Vec::new())))); + assert!( + Evaluator::new(b"0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint( + Sign::NoSign, + BigUint::new(Vec::new()) + )) + ); // Leading 0s and trailing 0s are fine. - assert!(Evaluator::new(b"0000.00000", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::NoSign, BigUint::new(Vec::new())))); + assert!( + Evaluator::new(b"0000.00000", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint( + Sign::NoSign, + BigUint::new(Vec::new()) + )) + ); // Parsing stops at first non-(digit/period). - assert!(Evaluator::new(b"1 0", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); + assert!( + Evaluator::new(b"1 0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); let int = b"3397450981271938475135134759823759835414"; let frac = b"913759810573549872354897210539127530981570"; let left = Ratio::from_integer(BigInt::parse_bytes(int, 10).unwrap()); - let right = Ratio::new(BigInt::parse_bytes(frac, 10).unwrap(), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10]).pow(frac.len() + 1))); + let right = Ratio::new( + BigInt::parse_bytes(frac, 10).unwrap(), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10]).pow(frac.len() + 1)), + ); let mut vec = Vec::new(); vec.extend_from_slice(int); vec.push(b'.'); vec.push(b'0'); vec.extend_from_slice(frac); // Test a number whose integer and fraction portions are larger than u128::MAX. - assert!(Evaluator::new(vec.as_slice(), &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().unwrap() == left + right); + assert!( + Evaluator::new( + vec.as_slice(), + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_rational() + .unwrap() + .unwrap() + == left + right + ); // Leading 0s and trailing 0s for a non-zero value are fine. - assert!(Evaluator::new(b"000000014.0000000000000", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![14])))); + assert!( + Evaluator::new( + b"000000014.0000000000000", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_rational() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![14]))) + ); // A sequence of digits followed immediately by a decimal point but no digits after is invalid. - assert!(match Evaluator::new(b"1.", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap_err() { - E::InvalidDec(i) => i == 2, - _ => false, - }); + assert!( + match Evaluator::new(b"1.", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap_err() + { + E::InvalidDec(i) => i == 2, + _ => false, + } + ); // A sequence of digits followed immediately by a decimal point but no digits after is invalid. // This just shows that spaces are not ignored in number literals. - assert!(match Evaluator::new(b"1. 2", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap_err() { - E::InvalidDec(i) => i == 2, - _ => false, - }); + assert!( + match Evaluator::new(b"1. 2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap_err() + { + E::InvalidDec(i) => i == 2, + _ => false, + } + ); // Non-whitespace starting the input is valid but produces no value. // This also shows that an invalid byte sequence does not produce an error here. - assert!(Evaluator::new(b"a1", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().is_none()); + assert!( + Evaluator::new(b"a1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap() + .is_none() + ); // A space starting the input is valid but produces no value. - assert!(Evaluator::new(b" 1", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().is_none()); + assert!( + Evaluator::new(b" 1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap() + .is_none() + ); // A tab starting the input is valid but produces no value. - assert!(Evaluator::new(b"\t1", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().is_none()); + assert!( + Evaluator::new(b"\t1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap() + .is_none() + ); // Negative literals don't exist, so this should succeed but produce nothing. - assert!(Evaluator::new(b"-1", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().is_none()); + assert!( + Evaluator::new(b"-1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap() + .is_none() + ); // '/' is division and not part of a number literal so only the "numerator" is parsed. - assert!(Evaluator::new(b"1/2", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); + assert!( + Evaluator::new(b"1/2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); // A sequence of digits followed by invalid bytes is valid and produces a number equal to the digits before the invalid bytes. - assert!(Evaluator::new(b"130alj", &mut Cache::new(), &mut Vec::new()).get_rational().unwrap().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![130])))); + assert!( + Evaluator::new(b"130alj", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_rational() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![130]))) + ); + } + #[test] + fn par() { + // Missing closing ')' + assert!( + match Evaluator::new(b"(1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_par() + .unwrap_err() + { + E::InvalidPar(i) => i == 2, + _ => false, + } + ); + assert!( + match Evaluator::new(b"((1\t + 2)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_par() + .unwrap_err() + { + E::InvalidPar(i) => i == 9, + _ => false, + } + ); + assert!( + Evaluator::new(b"( 0 \t )", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_par() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint( + Sign::NoSign, + BigUint::new(Vec::new()) + )) + ); + assert!( + Evaluator::new(b"( - \t 5 )", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_par() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![5]))) + ); + assert!( + Evaluator::new( + b"( ( 2 -\t 5) * 9 )", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_par() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![27]))) + ); } #[test] fn recall_expression() { // If the input does not start with '@', then it's valid but produces nothing. - assert!(Evaluator::new(b"1", &mut Cache::new(), &mut Vec::new()).get_prev().unwrap().is_none()); - assert!(Evaluator::new(b"a", &mut Cache::new(), &mut Vec::new()).get_prev().unwrap().is_none()); - assert!(Evaluator::new(b" @", &mut Cache::new(), &mut Vec::new()).get_prev().unwrap().is_none()); - assert!(Evaluator::new(b"\t@", &mut Cache::new(), &mut Vec::new()).get_prev().unwrap().is_none()); + assert!( + Evaluator::new(b"1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_recall() + .unwrap() + .is_none() + ); + assert!( + Evaluator::new(b"a", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_recall() + .unwrap() + .is_none() + ); + assert!( + Evaluator::new(b" @", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_recall() + .unwrap() + .is_none() + ); + assert!( + Evaluator::new(b"\t@", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_recall() + .unwrap() + .is_none() + ); // Invalid recall expression since there are no previous results. - assert!(match Evaluator::new(b"@", &mut Cache::new(), &mut Vec::new()).get_prev().unwrap_err() { - E::NotEnoughPrevResults(count) => count == 0, - _ => false, - }); + assert!( + match Evaluator::new(b"@", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_recall() + .unwrap_err() + { + E::NotEnoughPrevResults(count) => count == 0, + _ => false, + } + ); // Invalid recall expression since there are no previous results. - assert!(match Evaluator::new(b"@4", &mut Cache::new(), &mut Vec::new()).get_prev().unwrap_err() { - E::NotEnoughPrevResults(count) => count == 0, - _ => false, - }); + assert!( + match Evaluator::new(b"@4", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_recall() + .unwrap_err() + { + E::NotEnoughPrevResults(count) => count == 0, + _ => false, + } + ); // Invalid recall expression since there are no previous results. // The input violates our grammar, but this error happens before that. - assert!(match Evaluator::new(b"@0", &mut Cache::new(), &mut Vec::new()).get_prev().unwrap_err() { - E::NotEnoughPrevResults(count) => count == 0, - _ => false, - }); + assert!( + match Evaluator::new(b"@0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_recall() + .unwrap_err() + { + E::NotEnoughPrevResults(count) => count == 0, + _ => false, + } + ); // Successfully extract previous expression. + let mut prev = None; let mut cache = Cache::new(); - Evaluator::new(b"1\n", &mut cache, &mut Vec::new()).evaluate().unwrap(); - assert!(Evaluator::new(b"@", &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); + Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + Evaluator::new(b" s \r\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + assert!( + Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); // Invalid characters are ignored at this stage. - assert!(Evaluator::new(b"@&", &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - // 0 is not a valid previous value only 1-8 are. - assert!(Evaluator::new(b"@0", &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - // 9 is not a valid previous value only 1-8 are. - assert!(Evaluator::new(b"@9", &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - // Spaces are not cleaned; otherwise this would error since we only have 1 previous value. - assert!(Evaluator::new(b"@ 2", &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - // Tabs are not cleaned; otherwise this would error since we only have 1 previous value. - assert!(Evaluator::new(b"@\t2", &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); + assert!( + Evaluator::new(b"@&", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + // 0 is not a valid stored value only 1-8 are. + assert!( + Evaluator::new(b"@0", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + // 9 is not a valid stored value only 1-8 are. + assert!( + Evaluator::new(b"@9", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + // Spaces are not cleaned; otherwise this would error since we only have 1 stored value. + assert!( + Evaluator::new(b"@ 2", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + // Tabs are not cleaned; otherwise this would error since we only have 1 stored value. + assert!( + Evaluator::new(b"@\t2", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); // One digits are looked at so this is not @<ten>. - assert!(Evaluator::new(b"@10", &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - // Invalid recall expression since there are no previous results. - assert!(match Evaluator::new(b"@2", &mut cache, &mut Vec::new()).get_prev().unwrap_err() { - E::NotEnoughPrevResults(count) => count == 1, - _ => false, - }); - Evaluator::new(b"2\r\n", &mut cache, &mut Vec::new()).evaluate().unwrap(); - // Previous value is correct. - assert!(Evaluator::new(b"@", &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))); - // Second previous value is correct. - assert!(Evaluator::new(b"@2", &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - // Invalid recall expression since there are no previous results. - assert!(match Evaluator::new(b"@3", &mut cache, &mut Vec::new()).get_prev().unwrap_err() { - E::NotEnoughPrevResults(count) => count == 2, - _ => false, - }); + assert!( + Evaluator::new(b"@10", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + // Invalid recall expression since there is only one stored result. + assert!( + match Evaluator::new(b"@2", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap_err() + { + E::NotEnoughPrevResults(count) => count == 1, + _ => false, + } + ); + Evaluator::new(b"2\r\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + // Stored values correct. + assert!( + Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))) + ); + assert!( + Evaluator::new(b"@2", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + // Invalid recall expression since there are only three stored results. + assert!( + match Evaluator::new(b"@3", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap_err() + { + E::NotEnoughPrevResults(count) => count == 2, + _ => false, + } + ); let mut v = vec![0, b'\n']; for i in b'3'..=b'8' { v[0] = i; - Evaluator::new(v.as_slice(), &mut cache, &mut Vec::new()).evaluate().unwrap(); + Evaluator::new(v.as_slice(), &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); } v[0] = b'@'; for i in b'1'..=b'8' { v[1] = i; // Cache is filled up correctly storing all previous values. - assert!(Evaluator::new(v.as_slice(), &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![(b'9' - i) as u32])))); + assert!( + Evaluator::new(v.as_slice(), &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint( + Sign::Plus, + BigUint::new(vec![(b'9' - i) as u32]) + )) + ); } // Only parses the first @ since the second @ is not a digit between 1 and 8. - assert!(Evaluator::new(b"@@", &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8])))); - Evaluator::new(b"9\r\n", &mut cache, &mut Vec::new()).evaluate().unwrap(); + assert!( + Evaluator::new(b"@@", &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8]))) + ); + Evaluator::new(b"9\r\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); // Oldest value is overwritten; all others remain. for i in b'1'..=b'8' { v[1] = i; - assert!(Evaluator::new(v.as_slice(), &mut cache, &mut Vec::new()).get_prev().unwrap().unwrap() == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![((b'9' + 1) - i) as u32])))); + assert!( + Evaluator::new(v.as_slice(), &mut cache, &mut prev, &mut Vec::new()) + .get_recall() + .unwrap() + .unwrap() + == &Ratio::from_integer(BigInt::from_biguint( + Sign::Plus, + BigUint::new(vec![((b'9' + 1) - i) as u32]) + )) + ); } } #[test] fn abs() { // Missing closing '|' - assert!(match Evaluator::new(b"|1", &mut Cache::new(), &mut Vec::new()).get_abs().unwrap_err() { - E::InvalidAbs(i) => i == 2, - _ => false, - }); - assert!(match Evaluator::new(b"||1 + 2|", &mut Cache::new(), &mut Vec::new()).get_abs().unwrap_err() { - E::InvalidAbs(i) => i == 8, - _ => false, - }); - assert!(Evaluator::new(b"| 0\t \t |", &mut Cache::new(), &mut Vec::new()).get_abs().unwrap().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::NoSign, BigUint::new(Vec::new())))); - assert!(Evaluator::new(b"| - 5 |", &mut Cache::new(), &mut Vec::new()).get_abs().unwrap().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![5])))); - assert!(Evaluator::new(b"| \t| 2 - 5| * 9 |", &mut Cache::new(), &mut Vec::new()).get_abs().unwrap().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27])))); + assert!( + match Evaluator::new(b"|1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_abs() + .unwrap_err() + { + E::InvalidAbs(i) => i == 2, + _ => false, + } + ); + assert!( + match Evaluator::new(b"||1 + 2|", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_abs() + .unwrap_err() + { + E::InvalidAbs(i) => i == 8, + _ => false, + } + ); + assert!( + Evaluator::new( + b"| 0\t \t |", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_abs() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint( + Sign::NoSign, + BigUint::new(Vec::new()) + )) + ); + assert!( + Evaluator::new(b"| - 5 |", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_abs() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![5]))) + ); + assert!( + Evaluator::new( + b"| \t| 2 - 5| * 9 |", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_abs() + .unwrap() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27]))) + ); // If the input does not start with '|', then it's valid but produces nothing. - assert!(Evaluator::new(b" \t|9|", &mut Cache::new(), &mut Vec::new()).get_abs().unwrap().is_none()); + assert!( + Evaluator::new(b" \t|9|", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_abs() + .unwrap() + .is_none() + ); } #[test] - fn par() { - // Missing closing ')' - assert!(match Evaluator::new(b"(1", &mut Cache::new(), &mut Vec::new()).get_par().unwrap_err() { - E::InvalidPar(i) => i == 2, - _ => false, - }); - assert!(match Evaluator::new(b"((1\t + 2)", &mut Cache::new(), &mut Vec::new()).get_par().unwrap_err() { - E::InvalidPar(i) => i == 9, - _ => false, - }); - assert!(Evaluator::new(b"( 0 \t )", &mut Cache::new(), &mut Vec::new()).get_par().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::NoSign, BigUint::new(Vec::new())))); - assert!(Evaluator::new(b"( - \t 5 )", &mut Cache::new(), &mut Vec::new()).get_par().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![5])))); - assert!(Evaluator::new(b"( ( 2 -\t 5) * 9 )", &mut Cache::new(), &mut Vec::new()).get_par().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![27])))); - // If the input does not start with '(', then it's invalid since get_par must only be called as the last terminal expression which means whitespace must be consumed already. - assert!(match Evaluator::new(b" (2)", &mut Cache::new(), &mut Vec::new()).get_par().unwrap_err() { + fn round() { + // Missing ',<digit>)' + assert!( + match Evaluator::new(b"round(1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_round() + .unwrap_err() + { + E::InvalidRound(i) => i == 7, + _ => false, + } + ); + assert!( + match Evaluator::new(b"round(1,", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_round() + .unwrap_err() + { + E::InvalidRound(i) => i == 8, + _ => false, + } + ); + assert!( + match Evaluator::new(b"round(1,2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_round() + .unwrap_err() + { + E::InvalidRound(i) => i == 9, + _ => false, + } + ); + assert!(match Evaluator::new( + b"round(1,10)", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_round() + .unwrap_err() + { + E::InvalidRound(i) => i == 9, + _ => false, + }); + assert!( + match Evaluator::new(b"round(1,a)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_round() + .unwrap_err() + { + E::InvalidRound(i) => i == 8, + _ => false, + } + ); + // If the input does not start with 'round(', then it's invalid since get_round must only be called as the last terminal expression which means whitespace must be consumed already. + assert!(match Evaluator::new( + b" round(2/3,2)", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_round() + .unwrap_err() + { E::MissingTerm(i) => i == 0, _ => false, }); + assert!( + Evaluator::new( + b"round(2, 7)", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_round() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))) + ); + assert!( + Evaluator::new( + b"round(2.677, 1)", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_round() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10])) + ) + ); } #[test] fn term() { - assert!(Evaluator::new(b"0000.00000", &mut Cache::new(), &mut Vec::new()).get_term().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::NoSign, BigUint::new(Vec::new())))); - assert!(Evaluator::new(b"|4|", &mut Cache::new(), &mut Vec::new()).get_term().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])))); - assert!(Evaluator::new(b"(4)", &mut Cache::new(), &mut Vec::new()).get_term().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])))); + assert!( + Evaluator::new(b"0000.00000", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_term() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint( + Sign::NoSign, + BigUint::new(Vec::new()) + )) + ); + assert!( + Evaluator::new(b"(4)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_term() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4]))) + ); + assert!( + Evaluator::new( + b"round(-2/3,2)", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_term() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Minus, BigUint::new(vec![67])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![100])) + ) + ); + assert!( + Evaluator::new(b"|4|", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_term() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4]))) + ); // Terminal expressions do no clean up before or after. - assert!(match Evaluator::new(b" 2", &mut Cache::new(), &mut Vec::new()).get_term().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); + assert!( + match Evaluator::new(b" 2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_term() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); + let mut prev = None; let mut cache = Cache::new(); - Evaluator::new(b"1\n", &mut cache, &mut Vec::new()).evaluate().unwrap(); - assert!(Evaluator::new(b"@", &mut cache, &mut Vec::new()).get_term().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); + Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + assert!( + Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()) + .get_term() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); } #[test] fn factorial() { // Negative integer is not allowed. - assert!(match Evaluator::new(b"(-1)!", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap_err() { - E::NotNonNegIntFact(i) => i == 5, - _ => false, - }); + assert!( + match Evaluator::new(b"(-1)!", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap_err() + { + E::NotNonNegIntFact(i) => i == 5, + _ => false, + } + ); // Non-integer is not allowed. - assert!(match Evaluator::new(b"2.5!", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap_err() { - E::NotNonNegIntFact(i) => i == 4, - _ => false, - }); + assert!( + match Evaluator::new(b"2.5!", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap_err() + { + E::NotNonNegIntFact(i) => i == 4, + _ => false, + } + ); // factorials always become terminal expressions eventually. - assert!(Evaluator::new(b"7", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![7])))); - assert!(Evaluator::new(b"(7)", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![7])))); - assert!(Evaluator::new(b"|7|", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![7])))); + assert!( + Evaluator::new(b"7", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![7]))) + ); + assert!( + Evaluator::new(b"(7)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![7]))) + ); + assert!( + Evaluator::new(b"|7|", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![7]))) + ); + let mut prev = None; let mut cache = Cache::new(); - Evaluator::new(b"3\n", &mut cache, &mut Vec::new()).evaluate().unwrap(); - assert!(Evaluator::new(b"@!", &mut cache, &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6])))); - assert!(Evaluator::new(b"@", &mut cache, &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3])))); + Evaluator::new(b"3\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + assert!( + Evaluator::new(b"@!", &mut cache, &mut prev, &mut Vec::new()) + .get_fact() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6]))) + ); + assert!( + Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()) + .get_fact() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3]))) + ); // 0! = 1. - assert!(Evaluator::new(b"0.0!", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); + assert!( + Evaluator::new(b"0.0!", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); // 1! = 1. - assert!(Evaluator::new(b"1!", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); + assert!( + Evaluator::new(b"1!", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); // 4! = 24, and whitespace is not consumed. - assert!(Evaluator::new(b"4! \t", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![24])))); + assert!( + Evaluator::new(b"4! \t", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![24]))) + ); // Factorials can be chained. - assert!(Evaluator::new(b"3!! ", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![720])))); + assert!( + Evaluator::new(b"3!! ", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![720]))) + ); // only factorial is consumed. - assert!(Evaluator::new(b"2!+3", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))); + assert!( + Evaluator::new(b"2!+3", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))) + ); // Error since leading/trailing whitespace is not consumed by factorial or higher precedence expressions. - assert!(match Evaluator::new(b" 2!", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); - assert!(match Evaluator::new(b"\t2!", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); + assert!( + match Evaluator::new(b" 2!", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); + assert!( + match Evaluator::new(b"\t2!", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); // Error since negation is not consumed by factorial or higher precedence expressions. - assert!(match Evaluator::new(b"-2!", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); + assert!( + match Evaluator::new(b"-2!", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_fact() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); } #[test] fn exp() { // 1 can be raised to anything and return 1. // Also white space is ignored between operator. - assert!(Evaluator::new(b"1 ^\t 0", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"1^0.5", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"1^(-1/2)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"1.0^(-2)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); + assert!( + Evaluator::new(b"1 ^\t 0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"1^0.5", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"1^(-1/2)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"1.0^(-2)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); // 0 can be raised to any non-negative value and will always return 0 unless raised to 0 which will return 1. - assert!(Evaluator::new(b"0^0", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"0^1", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::NoSign, BigUint::new(vec![0])))); - assert!(Evaluator::new(b"0^0.5", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::NoSign, BigUint::new(vec![0])))); + assert!( + Evaluator::new(b"0^0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"0^1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::NoSign, BigUint::new(vec![0]))) + ); + assert!( + Evaluator::new(b"0^0.5", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::NoSign, BigUint::new(vec![0]))) + ); // Anything else can only be raised to integers. - assert!(Evaluator::new(b"4^0", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"4^1", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])))); - assert!(Evaluator::new(b"4^(-2)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16])))); - assert!(Evaluator::new(b"(-4)^0", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"(-4)^1", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![4])))); - assert!(Evaluator::new(b"(-4)^2", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16])))); - assert!(Evaluator::new(b"(-4)^(-2)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16])))); - assert!(Evaluator::new(b"(-4)^(-3)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![1])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64])))); - assert!(Evaluator::new(b"(2/3)^0", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"(2/3)^(2)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9])))); - assert!(Evaluator::new(b"(2/3)^(-3)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8])))); - assert!(Evaluator::new(b"(-2/3)^0", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"(-2/3)^(2)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9])))); - assert!(Evaluator::new(b"(-2/3)^(3)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![8])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27])))); - assert!(Evaluator::new(b"(-2/3)^(-2)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])))); - assert!(Evaluator::new(b"(-2/3)^(-3)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![27])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8])))); + assert!( + Evaluator::new(b"4^0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"4^1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4]))) + ); + assert!( + Evaluator::new(b"4^(-2)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16])) + ) + ); + assert!( + Evaluator::new(b"(-4)^0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"(-4)^1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![4]))) + ); + assert!( + Evaluator::new(b"(-4)^2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16]))) + ); + assert!( + Evaluator::new(b"(-4)^(-2)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16])) + ) + ); + assert!( + Evaluator::new(b"(-4)^(-3)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Minus, BigUint::new(vec![1])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64])) + ) + ); + assert!( + Evaluator::new(b"(2/3)^0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"(2/3)^(2)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9])) + ) + ); + assert!( + Evaluator::new(b"(2/3)^(-3)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8])) + ) + ); + assert!( + Evaluator::new(b"(-2/3)^0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"(-2/3)^(2)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9])) + ) + ); + assert!( + Evaluator::new(b"(-2/3)^(3)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Minus, BigUint::new(vec![8])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27])) + ) + ); + assert!( + Evaluator::new( + b"(-2/3)^(-2)", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_exps() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])) + ) + ); + assert!( + Evaluator::new( + b"(-2/3)^(-3)", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_exps() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Minus, BigUint::new(vec![27])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8])) + ) + ); // Error since 0 cannot be raised to a negative power. - assert!(match Evaluator::new(b"0^(-1)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap_err() { - E::ExpDivByZero(i) => i == 6, - _ => false, - }); + assert!( + match Evaluator::new(b"0^(-1)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap_err() + { + E::ExpDivByZero(i) => i == 6, + _ => false, + } + ); // Error anything other than 0 or 1 cannot be raised to a non-integer power. - assert!(match Evaluator::new(b"2^(1/2)", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap_err() { - E::ExpIsNotInt(i) => i == 7, - _ => false, - }); + assert!( + match Evaluator::new(b"2^(1/2)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap_err() + { + E::ExpIsNotInt(i) => i == 7, + _ => false, + } + ); // exps always become factorials eventually. - assert!(Evaluator::new(b"3!", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6])))); + assert!( + Evaluator::new(b"3!", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6]))) + ); // exponentiation has lower precedence than factorials. - assert!(Evaluator::new(b"2^3!", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64])))); - assert!(Evaluator::new(b"3!^2", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![36])))); + assert!( + Evaluator::new(b"2^3!", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64]))) + ); + assert!( + Evaluator::new(b"3!^2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![36]))) + ); // Error since leading/trailing whitespace is not consumed by exponentiation or higher precedence expressions. - assert!(match Evaluator::new(b" 2^2", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); - assert!(match Evaluator::new(b"\t2^2", &mut Cache::new(), &mut Vec::new()).get_exps().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); + assert!( + match Evaluator::new(b" 2^2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); + assert!( + match Evaluator::new(b"\t2^2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_exps() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); } #[test] fn neg() { - assert!(Evaluator::new(b"-1", &mut Cache::new(), &mut Vec::new()).get_neg().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"- \t - 1", &mut Cache::new(), &mut Vec::new()).get_neg().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"-0", &mut Cache::new(), &mut Vec::new()).get_neg().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::NoSign, BigUint::new(Vec::new())))); + assert!( + Evaluator::new(b"-1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_neg() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"- \t - 1", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_neg() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"-0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_neg() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint( + Sign::NoSign, + BigUint::new(Vec::new()) + )) + ); // negation has lower precedence than exponentiation. - assert!(Evaluator::new(b"-2^2", &mut Cache::new(), &mut Vec::new()).get_neg().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![4])))); + assert!( + Evaluator::new(b"-2^2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_neg() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![4]))) + ); // negation always becomes exponentiation eventually. - assert!(Evaluator::new(b"2.0^2.0", &mut Cache::new(), &mut Vec::new()).get_neg().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])))); + assert!( + Evaluator::new(b"2.0^2.0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_neg() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4]))) + ); // Error since leading/trailing whitespace is not consumed by exponentiation or higher precedence expressions. - assert!(match Evaluator::new(b" -2", &mut Cache::new(), &mut Vec::new()).get_neg().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); - assert!(match Evaluator::new(b"\t-2", &mut Cache::new(), &mut Vec::new()).get_neg().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); + assert!( + match Evaluator::new(b" -2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_neg() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); + assert!( + match Evaluator::new(b"\t-2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_neg() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); } #[test] fn mult() { - assert!(Evaluator::new(b"2 * 3", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6])))); - assert!(Evaluator::new(b"-2 * \t 3", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![6])))); - assert!(Evaluator::new(b"2\t * -3.0", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![6])))); - assert!(Evaluator::new(b"-2.5*-3.5", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![35])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])))); - assert!(Evaluator::new(b"4.0\t / 6", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3])))); - assert!(Evaluator::new(b"6/3", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))); - assert!(Evaluator::new(b"-6/3", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2])))); - assert!(Evaluator::new(b"6/-3", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2])))); - assert!(Evaluator::new(b"- 6 /\t - 3", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))); + assert!( + Evaluator::new(b"2 * 3", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6]))) + ); + assert!( + Evaluator::new( + b"-2 * \t 3", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_mults() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![6]))) + ); + assert!( + Evaluator::new( + b"2\t * -3.0", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_mults() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![6]))) + ); + assert!( + Evaluator::new(b"-2.5*-3.5", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![35])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])) + ) + ); + assert!( + Evaluator::new(b"4.0\t / 6", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + == Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3])) + ) + ); + assert!( + Evaluator::new(b"6/3", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))) + ); + assert!( + Evaluator::new(b"-6/3", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2]))) + ); + assert!( + Evaluator::new(b"6/-3", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2]))) + ); + assert!( + Evaluator::new( + b"- 6 /\t - 3", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_mults() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))) + ); // Number literals are not strictly equivalent to "ratios" as "ratios" don't exist (i.e., 2/3 is not the ratio of 2 to 3 but is the rational number two divided by the rational number 3). - assert!(Evaluator::new(b"1/1.5", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() != Evaluator::new(b"1/3/2", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap()); - assert!(Evaluator::new(b"1/1.5", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Evaluator::new(b"1/(3/2)", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap()); + assert!( + Evaluator::new(b"1/1.5", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + != Evaluator::new(b"1/3/2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + ); + assert!( + Evaluator::new(b"1/1.5", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + == Evaluator::new(b"1/(3/2)", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + ); // multiplication always becomes negation eventually. - assert!(Evaluator::new(b"-2.0", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2])))); + assert!( + Evaluator::new(b"-2.0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2]))) + ); // Error since leading/trailing whitespace is not consumed by multiplication or higher precedence expressions. - assert!(match Evaluator::new(b" 2*2", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); - assert!(match Evaluator::new(b"\t2/2", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); + assert!( + match Evaluator::new(b" 2*2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); + assert!( + match Evaluator::new(b"\t2/2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); // Cannot divide by 0. - assert!(match Evaluator::new(b"2/0", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap_err() { - E::DivByZero(i) => i == 3, - _ => false, - }); + assert!( + match Evaluator::new(b"2/0", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap_err() + { + E::DivByZero(i) => i == 3, + _ => false, + } + ); // multiplication has lower precedence than exponentiation. - assert!(Evaluator::new(b"2*2^2", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8])))); - assert!(Evaluator::new(b"8/2^2", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))); + assert!( + Evaluator::new(b"2*2^2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8]))) + ); + assert!( + Evaluator::new(b"8/2^2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_mults() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))) + ); } #[test] fn add() { - assert!(Evaluator::new(b"2 + 3", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![5])))); - assert!(Evaluator::new(b"-2 + 3", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"2 + \t -3.0", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![1])))); - assert!(Evaluator::new(b"-2.5+-3.5", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![6])))); - assert!(Evaluator::new(b"4.0\t - 6", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2])))); - assert!(Evaluator::new(b"6-3", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3])))); - assert!(Evaluator::new(b"-6-3", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![9])))); - assert!(Evaluator::new(b"6--3", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9])))); - assert!(Evaluator::new(b"- 6 -\t - 3", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![3])))); + assert!( + Evaluator::new(b"2 + 3", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![5]))) + ); + assert!( + Evaluator::new(b"-2 + 3", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new( + b"2 + \t -3.0", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![1]))) + ); + assert!( + Evaluator::new(b"-2.5+-3.5", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![6]))) + ); + assert!( + Evaluator::new(b"4.0\t - 6", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2]))) + ); + assert!( + Evaluator::new(b"6-3", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3]))) + ); + assert!( + Evaluator::new(b"-6-3", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![9]))) + ); + assert!( + Evaluator::new(b"6--3", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9]))) + ); + assert!( + Evaluator::new( + b"- 6 -\t - 3", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![3]))) + ); // addition always becomes multiplication eventually. - assert!(Evaluator::new(b"2 * 8", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16])))); - assert!(Evaluator::new(b"8 /\t 2", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])))); + assert!( + Evaluator::new(b"2 * 8", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16]))) + ); + assert!( + Evaluator::new(b"8 /\t 2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4]))) + ); // Error since leading/trailing whitespace is not consumed by addition or higher precedence expressions. - assert!(match Evaluator::new(b" 2+2", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); - assert!(match Evaluator::new(b" 2-2", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); + assert!( + match Evaluator::new(b" 2+2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); + assert!( + match Evaluator::new(b" 2-2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); // addition has lower precedence than multiplication. - assert!(Evaluator::new(b"2+2*2", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6])))); - assert!(Evaluator::new(b"2+2/2", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3])))); - assert!(Evaluator::new(b"2-2*2", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2])))); - assert!(Evaluator::new(b"2-2/2", &mut Cache::new(), &mut Vec::new()).get_adds().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))); - } - #[test] - fn empty() { - assert!(match Evaluator::new(b" \n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap() { - O::Empty => true, - _ => false, - }); - assert!(match Evaluator::new(b"\t \t\r\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap() { - O::Empty => true, - _ => false, - }); - assert!(match Evaluator::new(b"\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap() { - O::Empty => true, - _ => false, - }); - assert!(match Evaluator::new(b"\r\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap() { - O::Empty => true, - _ => false, - }); - assert!(match Evaluator::new(b"\t\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap() { - O::Empty => true, - _ => false, - }); - assert!(match Evaluator::new(b"\n\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); - assert!(match Evaluator::new(b"\r\r\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); - assert!(match Evaluator::new(b"\n ", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { - E::MissingLF => true, - _ => false, - }); - assert!(match Evaluator::new(b"\r", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { - E::MissingLF => true, - _ => false, - }); - assert!(match Evaluator::new(b"\r\n ", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { - E::MissingLF => true, - _ => false, - }); + assert!( + Evaluator::new(b"2+2*2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6]))) + ); + assert!( + Evaluator::new(b"2+2/2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3]))) + ); + assert!( + Evaluator::new(b"2-2*2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![2]))) + ); + assert!( + Evaluator::new(b"2-2/2", &mut Cache::new(), &mut None, &mut Vec::new()) + .get_adds() + .unwrap() + == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) + ); } #[test] fn exit() { - assert!(match Evaluator::new(b" q \n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap() { - O::Exit=> true, - _ => false, - }); - assert!(match Evaluator::new(b" q \r\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap() { - O::Exit=> true, - _ => false, - }); - assert!(match Evaluator::new(b"q\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap() { - O::Exit=> true, - _ => false, - }); - assert!(match Evaluator::new(b"q\r\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap() { - O::Exit=> true, - _ => false, - }); - assert!(match Evaluator::new(b"\rq\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); - assert!(match Evaluator::new(b"\tq\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap() { - O::Exit=> true, - _ => false, - }); - assert!(match Evaluator::new(b"q\n\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { - E::InvalidQuit => true, - _ => false, - }); - assert!(match Evaluator::new(b"\nq\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); - assert!(match Evaluator::new(b"q", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { - E::MissingLF => true, - _ => false, - }); + assert!( + match Evaluator::new(b" q \n", &mut Cache::new(), &mut None, &mut Vec::new()) + .evaluate() + .unwrap() + { + O::Exit => true, + _ => false, + } + ); + assert!(match Evaluator::new( + b" q \r\n", + &mut Cache::new(), + &mut None, + &mut Vec::new() + ) + .evaluate() + .unwrap() + { + O::Exit => true, + _ => false, + }); + assert!( + match Evaluator::new(b"q\n", &mut Cache::new(), &mut None, &mut Vec::new()) + .evaluate() + .unwrap() + { + O::Exit => true, + _ => false, + } + ); + assert!( + match Evaluator::new(b"q\r\n", &mut Cache::new(), &mut None, &mut Vec::new()) + .evaluate() + .unwrap() + { + O::Exit => true, + _ => false, + } + ); + assert!( + match Evaluator::new(b"\rq\n", &mut Cache::new(), &mut None, &mut Vec::new()) + .evaluate() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); + assert!( + match Evaluator::new(b"\tq\n", &mut Cache::new(), &mut None, &mut Vec::new()) + .evaluate() + .unwrap() + { + O::Exit => true, + _ => false, + } + ); + assert!( + match Evaluator::new(b"q\n\n", &mut Cache::new(), &mut None, &mut Vec::new()) + .evaluate() + .unwrap_err() + { + E::InvalidQuit => true, + _ => false, + } + ); + assert!( + match Evaluator::new(b"\nq\n", &mut Cache::new(), &mut None, &mut Vec::new()) + .evaluate() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); + assert!( + match Evaluator::new(b"q", &mut Cache::new(), &mut None, &mut Vec::new()) + .evaluate() + .unwrap_err() + { + E::MissingLF => true, + _ => false, + } + ); } #[test] - fn recall_statement() { + fn store() { + let mut prev = None; let mut cache = Cache::new(); - Evaluator::new(b"1\n", &mut cache, &mut Vec::new()).evaluate().unwrap(); - assert!(match Evaluator::new(b" = \t @ \r\n", &mut cache, &mut Vec::new()).evaluate().unwrap() { - O::Prev(v)=> v.val == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) && v.round.is_none(), - _ => false, - }); - assert!(match Evaluator::new(b"\t = 2@ \n", &mut cache, &mut Vec::new()).evaluate().unwrap() { - O::Prev(v)=> v.val == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) && v.round.unwrap() == 2, - _ => false, - }); - assert!(match Evaluator::new(b"=9@1\n", &mut cache, &mut Vec::new()).evaluate().unwrap() { - O::Prev(v)=> v.val == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) && v.round.unwrap() == 9, - _ => false, - }); - assert!(match Evaluator::new(b"=0@1\n", &mut cache, &mut Vec::new()).evaluate().unwrap() { - O::Prev(v)=> v.val == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]))) && v.round.unwrap() == 0, - _ => false, - }); - assert!(match Evaluator::new(b"=@2\r\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { - E::NotEnoughPrevResults(count)=> count == 0, - _ => false, - }); - assert!(match Evaluator::new(b"=@ 1\n", &mut cache, &mut Vec::new()).evaluate().unwrap_err() { - E::InvalidRecall => true, - _ => false, - }); - assert!(match Evaluator::new(b"=0 @1\n", &mut cache, &mut Vec::new()).evaluate().unwrap_err() { - E::InvalidRecall => true, - _ => false, - }); - assert!(match Evaluator::new(b"=@", &mut cache, &mut Vec::new()).evaluate().unwrap_err() { - E::MissingLF=> true, - _ => false, - }); + Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap(); + assert!(cache.is_empty()); + assert!( + match Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new()) + .evaluate() + .unwrap() + { + O::Store(o) => + o.as_ref().unwrap() + == &Ratio::from_integer(BigInt::from_biguint( + Sign::Plus, + BigUint::new(vec![1]) + )), + _ => false, + } + ); + assert!(cache.len() == 1); } #[test] fn eval() { use core::str::FromStr; + let mut prev = None; let mut cache = Cache::new(); let mut exp = Vec::new(); - assert!(match Evaluator::new(b"1+2\n", &mut cache, &mut exp).evaluate().unwrap() { - O::Val(i) => i == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3]))), - _ => false, - }); - assert!(match Evaluator::new(b"-1/2+2*@\n", &mut cache, &mut exp).evaluate().unwrap() { - O::Val(i) => i == &Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![11])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))), - _ => false, - }); - assert!(match Evaluator::new(b"@^@2!\r\n", &mut cache, &mut exp).evaluate().unwrap() { - O::Val(i) => i == &Ratio::new(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1771561])), BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64]))), - _ => false, - }); + assert!( + match Evaluator::new(b"1+2\n", &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap() + { + O::Eval(i) => + i == &Ratio::from_integer(BigInt::from_biguint( + Sign::Plus, + BigUint::new(vec![3]) + )), + _ => false, + } + ); + assert!( + match Evaluator::new(b"\t s \n", &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap() + { + O::Store(o) => + o.as_ref().unwrap() + == &Ratio::from_integer(BigInt::from_biguint( + Sign::Plus, + BigUint::new(vec![3]) + )), + _ => false, + } + ); + assert!( + match Evaluator::new(b"-1/2+2*@\n", &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap() + { + O::Eval(i) => + i == &Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![11])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])) + ), + _ => false, + } + ); + assert!( + match Evaluator::new(b"s\n", &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap() + { + O::Store(o) => + o == &Some(Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![11])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])) + )), + _ => false, + } + ); + assert!( + match Evaluator::new(b"@^@2!\r\n", &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap() + { + O::Eval(i) => + i == &Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1771561])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64])) + ), + _ => false, + } + ); + assert!( + match Evaluator::new(b"s\n", &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap() + { + O::Store(o) => + o == &Some(Ratio::new( + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1771561])), + BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64])) + )), + _ => false, + } + ); // Verified with Wolfram Alpha. - assert!(match Evaluator::new(b" \t 1 + (2 * |(7.98\t - 12/7)|) / 4!^@3!^|1-3|\t \n", &mut cache, &mut exp).evaluate().unwrap() { - O::Val(i) => i == &Ratio::from_str("2841328814244153299237884950647090899374680152474331/2841328814244153299237884950647090899374680152473600").unwrap(), + assert!(match Evaluator::new(b" \t 1 + (2 * |(7.98\t - 12/7)|) / 4!^@3!^|1-3|\t \n", &mut cache, &mut prev, &mut exp).evaluate().unwrap() { + O::Eval(i) => i == &Ratio::from_str("2841328814244153299237884950647090899374680152474331/2841328814244153299237884950647090899374680152473600").unwrap(), _ => false, }); + assert!(match Evaluator::new( + b" \t round(19/6,0)!\t \r\n", + &mut cache, + &mut prev, + &mut exp + ) + .evaluate() + .unwrap() + { + O::Eval(i) => + i == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![6]))), + _ => false, + }); + assert!(match Evaluator::new( + b" \t 2^round(19/6,0)!\t \r\n", + &mut cache, + &mut prev, + &mut exp + ) + .evaluate() + .unwrap() + { + O::Eval(i) => + i == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64]))), + _ => false, + }); + assert!( + match Evaluator::new(b"round(19/6,0)^2\t\n", &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap() + { + O::Eval(i) => + i == &Ratio::from_integer(BigInt::from_biguint( + Sign::Plus, + BigUint::new(vec![9]) + )), + _ => false, + } + ); // Invalid UTF-8 does not cause a panic!. - assert!(match Evaluator::new(&[255, 255, 255, b'\n'], &mut cache, &mut exp).evaluate().unwrap_err() { - E::MissingTerm(i) => i == 0, - _ => false, - }); - assert!(match Evaluator::new(&[b'2', 255, b'\n'], &mut cache, &mut exp).evaluate().unwrap_err() { - E::TrailingSyms(i) => i == 1, - _ => false, - }); + assert!( + match Evaluator::new(&[255, 255, 255, b'\n'], &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap_err() + { + E::MissingTerm(i) => i == 0, + _ => false, + } + ); + assert!( + match Evaluator::new(&[b'2', 255, b'\n'], &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap_err() + { + E::TrailingSyms(i) => i == 1, + _ => false, + } + ); // Exactly one newline is required. - assert!(match Evaluator::new(&[b'2', b'\n', b'\n'], &mut cache, &mut exp).evaluate().unwrap_err() { - E::TrailingSyms(i) => i == 1, - _ => false, - }); - assert!(match Evaluator::new(&[b'\n'], &mut cache, &mut exp).evaluate().unwrap() { - O::Empty => true, - _ => false, - }); - assert!(match Evaluator::new(&[b'\r', b'\n'], &mut cache, &mut exp).evaluate().unwrap() { - O::Empty => true, + assert!( + match Evaluator::new(b"2\n\n", &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap_err() + { + E::TrailingSyms(i) => i == 1, + _ => false, + } + ); + assert!( + prev == Some(Ratio::from_integer(BigInt::from_biguint( + Sign::Plus, + BigUint::new(vec![9]) + ))) + ); + assert!(match Evaluator::new(b"\n", &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap() + { + O::Empty(o) => + o == &Some(Ratio::from_integer(BigInt::from_biguint( + Sign::Plus, + BigUint::new(vec![9]) + ))), _ => false, }); + assert!( + prev == Some(Ratio::from_integer(BigInt::from_biguint( + Sign::Plus, + BigUint::new(vec![9]) + ))) + ); + assert!( + match Evaluator::new(b"\r\n", &mut cache, &mut prev.clone(), &mut exp) + .evaluate() + .unwrap() + { + O::Empty(o) => *o == prev, + _ => false, + } + ); // Empty input does not cause a panic!. - assert!(match Evaluator::new(&[], &mut cache, &mut exp).evaluate().unwrap_err() { + assert!(match Evaluator::new(&[], &mut cache, &mut prev, &mut exp) + .evaluate() + .unwrap_err() + { E::MissingLF => true, _ => false, }); diff --git a/src/main.rs b/src/main.rs @@ -1,27 +1,77 @@ -//! This crate is a CLI calculator for basic rational number arithmetic using standard operator precedence and -//! associativity. Additionally, it stores the previous eight evaluated expressions as well as provides a way to -//! display previous results in decimal form. +//! # calc_rational +//! +//! calc_rational consists of a binary crate `calc` and a library crate +//! [`calc_lib`](https://docs.rs/calc_rational/latest/calc_lib). `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 -//! =3@ -//! > 0.000 -//! =6@ +//! s +//! > 1/32768 +//! +//! > 0.000030517578125 +//! round(@, 3) +//! > 0 +//! round(@, 6) +//! > 31/1000000 +//! //! > 0.000031 +//! 2/3 +//! > 2/3 +//! +//! > 0.666666667 +//! @6 +//! There are only 4 previous results. +//! s 1 +//! Invalid store expression. A store expression must be of the extended regex form: ^[ \t]*s[ \t]*$. +//! q a +//! Invalid quit expression. A quit expression must be of the extended regex form: ^[ \t]*q[ \t]*$. +//! a +//! Missing terminal expression at position 0. A terminal expression is a decimal literal expression, recall expression, absolute value expression, parenthetical expression, or a round expression. +//! |1 +//! Invalid absolute value expression ending at position 2. An absolute value expression is an addition expression enclosed in '||'. +//! (2 +//! Invalid parenthetical expression ending at position 2. A parenthetical expression is an addition expression enclosed in '()'. +//! round(1,10) +//! Invalid round expression ending at position 9. A round expression is of the form 'round(<add expression>, digit)' +//! 1/(2-2) +//! Division by zero ending at position 7. +//! 2^(1/2) +//! Non-integer exponent with a base that was not 0 or 1 ending at position 7. +//! 0^-1 +//! Non-negative exponent with a base of 0 ending at position 4. +//! (-1)! +//! Factorial of a rational number that was not a non-negative integer ending at position 5. +//! 4. +//! Invalid decimal literal expression ending at position 2. A decimal literal expression must be of the extended regex form: [0-9]+(\.[0-9]+)?. +//! 1+2 a +//! Trailing symbols starting at position 4. +//! q +//! [zack@laptop ~]$ //! ``` //! -//! # Operators +//! ## Expressions //! -//! The following are the list of operators in descending order of precedence: -//! 1. `()`, `||` +//! The following are the list of expressions in descending order of precedence: +//! 1. number literals, `@`, `()`, `||`, `round()` //! 2. `!` //! 3. `^` //! 4. `-` (unary negation operator) @@ -38,7 +88,7 @@ //! //! `!` 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 are *not* ignored; so `1 !` is grammatically incorrect and +//! 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; @@ -54,9 +104,14 @@ //! //! The binary operators `+` and `-` represent addition and subtraction respectively. //! -//! With the aforementioned exception of `!`, all spaces before and after operators are ignored. +//! With the aforementioned exception of `!`, all spaces and tabs before and after operators are ignored. //! -//! # Numbers +//! ## Round expression +//! +//! `round(expression, digit)` rounds `expression` to `digit`-number of fractional digits. An error will +//! be displayed if called incorrectly. +//! +//! ## 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 @@ -64,40 +119,46 @@ //! prime factors are 2 or 5. To represent all other rational numbers, the unary operator `-` and binary //! operator `/` must be used. //! -//! # Recall expressions +//! ## Empty expression //! -//! `@` is used to recall previously evaluated expressions. 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 result where *i ∈ {1, 2, 3, 4, 5, 6, 7, 8}*. -//! Note that spaces 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. +//! The empty expression (i.e., expression that at most only consists of spaces and tabs) will return +//! the result from the previous non-empty and non-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 and non-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. //! -//! # Displaying results in decimal form +//! ## Recall expression //! -//! All calculations are done using exact rational number arithmetic; however if one wants to display a previous -//! result in decimal form, one can use `=<digit>@<1-8>`. For example `=2@` will display the previous result -//! rounded to two decimal places following normal rounding rules. Note that this is not an expression and so does -//! not influence previous results as can be recalled by `@`. +//! `@` 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 +//! ## Character encoding //! -//! All inputs must only contain the UTF-8 encoding of the following Unicode scalar values: `0`-`9`, `.`, `+`, `-`, -//! `*`, `/`, `^`, `!`, `|`, `(`, `)`, `@`, `=`, &lt;space&gt;, &lt;line feed&gt;, and `q`. Any other byte -//! sequences are grammatically incorrect and will lead to an error message. +//! All inputs must only contain the ASCII encoding of the following Unicode scalar values: `0`-`9`, `.`, `+`, `-`, +//! `*`, `/`, `^`, `!`, `|`, `(`, `)`, `round`, `,`, `@`, `s`, &lt;space&gt;, &lt;tab&gt;, &lt;line feed&gt;, &lt;carriage return&gt;, +//! `q`, and `d`. Any other byte sequences are grammatically incorrect and will lead to an error message. //! -//! # Errors +//! ## Errors //! -//! Errors due to a language violation (e.g., dividing by `0`) manifest into an error message. `panic!`s, -//! [`io::Error`][std::io::Error]s that are not of the [`ErrorKind::Interrupted`][std::io::ErrorKind::Interrupted] -//! variant, and [`fmt::Error`][core::fmt::Error]s all lead to program abortion. +//! 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. //! -//! # Exiting +//! ## Exiting //! -//! `q` with any number of spaces before and after will cause the program to terminate. +//! `q` with any number of spaces and tabs before and after will cause the program to terminate. //! -//! # Formal language specification +//! ### Formal language specification //! -//! For a more precise specification of the "calc language", one can read the +//! For a more precise specification of the “calc language”, one can read the //! [calc language specification](https://git.philomathiclife.com/calc_rational/lang.pdf). #![deny( unsafe_code, @@ -112,41 +173,18 @@ #![allow(clippy::blanket_clippy_restriction_lints, clippy::implicit_return)] use calc_lib::cache::Cache; use calc_lib::Evaluator; -use calc_lib::O::{Empty, Exit, Prev, Val}; -use core::fmt::{self, Debug, Formatter}; +use calc_lib::O::{Empty, Eval, Exit, Store}; use std::io::{self, BufRead, Error, Write}; -/// Generic unrecoverable error. -enum GenErr { - /// An [`io::Error`][std::io::Error] that is not of the - /// [`io::ErrorKind::Interrupted`][std::io::ErrorKind] variant. - Error(Error), - /// A [`fmt::Error`][core::fmt::Error]. - Fmt(fmt::Error), -} -impl From<Error> for GenErr { - #[inline] - fn from(value: Error) -> Self { - Self::Error(value) - } -} -impl From<fmt::Error> for GenErr { - #[inline] - fn from(value: fmt::Error) -> Self { - Self::Fmt(value) - } -} -impl Debug for GenErr { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match *self { - Self::Error(ref e) => e.fmt(f), - Self::Fmt(ref e) => e.fmt(f), - } - } -} /// Entry point to the calc program. -fn main() -> Result<(), GenErr> { - let mut prev = Cache::new(); + +/// # Errors +/// +/// Returns [`Error`] iff [`write`] returns one when writing +/// to the global standard output stream. +#[allow(clippy::significant_drop_in_scrutinee)] +fn main() -> Result<(), Error> { + let mut stored = Cache::new(); + let mut prev = None; let input = io::stdin(); let mut in_lock = input.lock(); let mut buffer = Vec::new(); @@ -154,16 +192,27 @@ fn main() -> Result<(), GenErr> { let mut out_lock = out.lock(); let mut exp_cache = Vec::new(); loop { - in_lock.read_until(b'\n', &mut buffer)?; - match Evaluator::new(&buffer, &mut prev, &mut exp_cache).evaluate() { - Ok(o) => match o { - Empty => (), - Exit => return Ok(()), - Prev(_) | Val(_) => out_lock.write_all(&o.to_string().into_bytes())?, - }, - Err(e) => out_lock.write_all(&e.to_string().into_bytes())?, - } - out_lock.flush()?; + writeln!( + &mut out_lock, + "{}", + match in_lock.read_until(b'\n', &mut buffer) { + Ok(_) => match Evaluator::new(&buffer, &mut stored, &mut prev, &mut exp_cache) + .evaluate() + { + Ok(o) => match o { + Empty(_) | Eval(_) | Store(_) => o.to_string(), + Exit => return Ok(()), + }, + Err(e) => e.to_string(), + }, + Err(e) => { + let mut err = String::with_capacity(128); + err.push_str("There was an error when reading the passed expression: "); + err.push_str(e.to_string().as_str()); + err + } + } + )?; buffer.clear(); exp_cache.clear(); }