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:
M | Cargo.toml | | | 2 | +- |
M | README.md | | | 194 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
M | lang.tex | | | 80 | +++++++++++++++++++++++++++++++++++++++---------------------------------------- |
M | src/cache.rs | | | 25 | ++++++++++++++----------- |
M | src/lib.rs | | | 2453 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
M | src/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`, `.`, `+`, `-`,
-`*`, `/`, `^`, `!`, `|`, `(`, `)`, `@`, `=`, <space>, <tab>, <line feed>, <carriage return>,
-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`, <space>, <tab>, <line feed>, <carriage return>,
+`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`, `.`, `+`, `-`,
-//! `*`, `/`, `^`, `!`, `|`, `(`, `)`, `@`, `=`, <space>, <line feed>, 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`, <space>, <tab>, <line feed>, <carriage return>,
+//! `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();
}