calc_rational

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

commit 4f1ec116c052c314994c95323776d0e67622a773
parent 722ebcd9db3b4c65b1309b5d1bd54c2d64dbda7a
Author: Zack Newman <zack@philomathiclife.com>
Date:   Sat, 15 Apr 2023 20:07:46 -0600

ignore tabs and allow CRLF for newlines

Diffstat:
MREADME.md | 37++++++++++++++++++++++---------------
Mlang.tex | 47+++++++++++++++++++++++++++--------------------
Msrc/cache.rs | 4+++-
Msrc/lib.rs | 138++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/main.rs | 7+++++--
5 files changed, 148 insertions(+), 85 deletions(-)

diff --git a/README.md b/README.md @@ -11,6 +11,7 @@ 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 @^0 @@ -23,8 +24,6 @@ and [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html). > 0.000 =6@ > 0.000031 -=@7 -There are only 4 previous results. @6 There are only 4 previous results. =2 @2 @@ -32,14 +31,11 @@ Invalid recall statement. A recall statement must be of the extended regex form: q a Invalid quit statement. A quit statement must be of the extended regex form: ^ *q *$. 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, or addition expression enclosed in vertical bars or parentheses. |1 -Invalid absolute value expression ending at position 2. An absolute value expression is an -addition expression enclosed in '||'. +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 '()'. +Invalid parenthetical expression ending at position 2. A parenthetical expression is an addition expression enclosed in '()'. 1/(2-2) Division by zero ending at position 7. 2^(1/2) @@ -48,14 +44,12 @@ Non-integer exponent with a base that was not 0 or 1 ending at position 7. 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. -(1/2)! -Factorial of a rational number that was not a non-negative integer ending at position 6. 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 @@ -122,8 +116,8 @@ not influence previous results as can be recalled by `@`. ## Character encoding All inputs must only contain the UTF-8 encoding of the following Unicode scalar values: `0`-`9`, `.`, `+`, `-`, -`*`, `/`, `^`, `!`, `|`, `(`, `)`, `@`, `=`, &lt;space&gt;, &lt;line feed&gt;, and `q`. Any other byte -sequences are grammatically incorrect and will lead to an error message. +`*`, `/`, `^`, `!`, `|`, `(`, `)`, `@`, `=`, &lt;space&gt;, &lt;tab&gt;, &lt;line feed&gt;, &lt;carriage return&gt;, +and `q`. Any other byte sequences are grammatically incorrect and will lead to an error message. ## Errors @@ -135,11 +129,24 @@ variant, and [`fmt::Error`](https://doc.rust-lang.org/beta/core/fmt/struct.Error `q` with any number of spaces before and after will cause the program to terminate. +## 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 +rational numbers. + +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. + ## Installing `cargo install calc_rational`. The executable is called `calc`. ### 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). diff --git a/lang.tex b/lang.tex @@ -5,19 +5,26 @@ We will define the language, \(L\), of our rational number calculator program. Define the set of non-terminal symbols to be -\[ N = \{expr, add, mult, neg, exp, fact, term, s, dec, int, at, digit, prev\}. \] +\begin{align*} +N = \{&expr, empty, quit, disp, add, mult, neg, exp, fact, term,\\ +&ws, l, dec, int, at, digit, prev\}. +\end{align*} Define the set of terminal symbols to be -\[ \Sigma = \{0,1,2,3,4,5,6,7,8,9,.,+,-,*,/,\string^,!,|(,),@,=,space,lf,q\}. \] +\[ \Sigma = \{0,1,2,3,4,5,6,7,8,9,.,+,-,*,/,\string^,!,|(,),@,=,space,tab,lf,cr,q\}. \] Define the production rules, \(P\), as the following: \begin{enumerate} - \item \(expr \rightarrow s\ add\ s\ lf\ |\ s\ = \ s\ at \ s\ lf\ |\ s\ = \ s\ digit\ at \ s\ lf\ |\ s\ lf\ |\ s\ q\ s\ lf\) - \item \(add \rightarrow add\ s\ +\ s\ mult\ |\ add\ s\ -\ s\ mult\ |\ mult\) - \item \(mult \rightarrow mult\ s\ *\ s\ neg\ |\ mult\ s\ /\ s\ neg\ |\ neg\) - \item \(neg \rightarrow -\ s\ neg\ |\ exp\) - \item \(exp \rightarrow fact\ s\ \string^\ s\ neg\ |\ fact\) + \item \(expr \rightarrow empty\ |\ quit\ |\ disp\ |\ ws\ add\ ws\ l\) + \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 \(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\ |\ |s\ add\ s|\ |\ (s\ add\ s)\) - \item \(s \rightarrow space\ s\ |\ \epsilon\) + \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 \(dec \rightarrow int\ |\ int.int\) \item \(int \rightarrow digit\ |\ digit\ int\) \item \(at \rightarrow @\ |\ @prev\) @@ -29,17 +36,17 @@ does not actually have a space). Define the start symbol to be \(expr\). Define 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, \(space\) is the Unicode scalar value -U+0020, 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: +\(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: \begin{enumerate} \item \(()\), \(||\) \item \(!\) diff --git a/src/cache.rs b/src/cache.rs @@ -5,10 +5,12 @@ clippy::all, clippy::cargo, clippy::nursery, - clippy::pedantic + clippy::pedantic, + clippy::restriction )] #![allow( clippy::arithmetic_side_effects, + clippy::blanket_clippy_restriction_lints, clippy::implicit_return, clippy::integer_arithmetic )] diff --git a/src/lib.rs b/src/lib.rs @@ -9,10 +9,12 @@ clippy::all, clippy::cargo, clippy::nursery, - clippy::pedantic + clippy::pedantic, + clippy::restriction )] #![allow( clippy::arithmetic_side_effects, + clippy::blanket_clippy_restriction_lints, clippy::exhaustive_enums, clippy::implicit_return, clippy::integer_arithmetic, @@ -237,7 +239,7 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { self.utf8 = if self.utf8.last().map_or(true, |b| *b != b'\n') { return Err(MissingLF) } else { - &self.utf8[..(self.utf8.len() - 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 { @@ -274,7 +276,7 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { }) } } - /// Reads from the input until the next non-space byte value. + /// Reads from the input until the next non-{space/tab} byte value. #[inline] #[allow(clippy::indexing_slicing)] fn consume_ws(&mut self) { @@ -284,12 +286,9 @@ impl<'input, 'cache, 'exp> Evaluator<'input, 'cache, 'exp> { self.i += self.utf8[self.i..] .into_iter() .try_fold(0, |val, b| { - if *b == b' ' { - Ok(val + 1) - } else { - // Only an "error" in the sense - // we want try_fold to terminate. - Err(val) + match *b { + b' ' | b'\t' => Ok(val + 1), + _ => Err(val), } }) .map_or_else(convert::identity, convert::identity); @@ -711,17 +710,19 @@ mod tests { E::InvalidDec(i) => i == 2, _ => false, }); - // A sequence of digits followed immediately by a decimal point and space but no digits after is invalid. + // 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, }); - // A non-space starting the input is valid but produces no value. + // 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()); // 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()); + // 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()); // 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()); // '/' is division and not part of a number literal so only the "numerator" is parsed. @@ -735,6 +736,7 @@ mod tests { 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()); // 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, @@ -763,6 +765,8 @@ mod tests { 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])))); // 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. @@ -770,7 +774,7 @@ mod tests { E::NotEnoughPrevResults(count) => count == 1, _ => false, }); - Evaluator::new(b"2\n", &mut cache, &mut Vec::new()).evaluate().unwrap(); + 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. @@ -793,7 +797,7 @@ mod tests { } // 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\n", &mut cache, &mut Vec::new()).evaluate().unwrap(); + Evaluator::new(b"9\r\n", &mut cache, &mut Vec::new()).evaluate().unwrap(); // Oldest value is overwritten; all others remain. for i in b'1'..=b'8' { v[1] = i; @@ -811,11 +815,11 @@ mod tests { E::InvalidAbs(i) => i == 8, _ => false, }); - assert!(Evaluator::new(b"| 0 |", &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"| 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"| | 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!(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])))); // If the input does not start with '|', then it's valid but produces nothing. - assert!(Evaluator::new(b" |9|", &mut Cache::new(), &mut Vec::new()).get_abs().unwrap().is_none()); + assert!(Evaluator::new(b" \t|9|", &mut Cache::new(), &mut Vec::new()).get_abs().unwrap().is_none()); } #[test] fn par() { @@ -824,13 +828,13 @@ mod tests { E::InvalidPar(i) => i == 2, _ => false, }); - assert!(match Evaluator::new(b"((1 + 2)", &mut Cache::new(), &mut Vec::new()).get_par().unwrap_err() { - E::InvalidPar(i) => i == 8, + 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 )", &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"( - 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 - 5) * 9 )", &mut Cache::new(), &mut Vec::new()).get_par().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![27])))); + 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() { E::MissingTerm(i) => i == 0, @@ -875,8 +879,8 @@ mod tests { 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])))); // 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])))); - // 4! = 24, and spaces are not consumed. - assert!(Evaluator::new(b"4! ", &mut Cache::new(), &mut Vec::new()).get_fact().unwrap() == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![24])))); + // 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])))); // 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])))); // only factorial is consumed. @@ -886,7 +890,11 @@ mod tests { E::MissingTerm(i) => i == 0, _ => false, }); - // Error since leading/trailing whitespace is not consumed by factorial or higher precedence expressions. + assert!(match Evaluator::new(b"\t2!", &mut Cache::new(), &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, @@ -896,7 +904,7 @@ mod tests { fn exp() { // 1 can be raised to anything and return 1. // Also white space is ignored between operator. - assert!(Evaluator::new(b"1 ^ 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 ^\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])))); @@ -941,11 +949,15 @@ mod tests { 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, + }); } #[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"- - 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"- \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())))); // 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])))); @@ -956,18 +968,22 @@ mod tests { 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, + }); } #[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 * 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 * -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 * \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 / 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"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 / - 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 /\t - 3", &mut Cache::new(), &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()); @@ -978,7 +994,7 @@ mod tests { E::MissingTerm(i) => i == 0, _ => false, }); - assert!(match Evaluator::new(b" 2/2", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap_err() { + assert!(match Evaluator::new(b"\t2/2", &mut Cache::new(), &mut Vec::new()).get_mults().unwrap_err() { E::MissingTerm(i) => i == 0, _ => false, }); @@ -995,16 +1011,16 @@ mod tests { 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 + -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 + \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 - 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"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 - - 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"- 6 -\t - 3", &mut Cache::new(), &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 / 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"8 /\t 2", &mut Cache::new(), &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, @@ -1026,26 +1042,42 @@ mod tests { 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_err() { - E::MissingTerm(i) => i == 0, + 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_err() { - E::MissingTerm(i) => i == 0, + 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, + }); } #[test] fn exit() { @@ -1053,16 +1085,24 @@ mod tests { 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_err() { - E::MissingTerm(i) => i == 0, + 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() { @@ -1082,11 +1122,11 @@ mod tests { fn recall_statement() { let mut cache = Cache::new(); Evaluator::new(b"1\n", &mut cache, &mut Vec::new()).evaluate().unwrap(); - assert!(match Evaluator::new(b" = @ \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" = 2@ \n", &mut cache, &mut Vec::new()).evaluate().unwrap() { + 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, }); @@ -1098,7 +1138,7 @@ mod tests { 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\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { + assert!(match Evaluator::new(b"=@2\r\n", &mut Cache::new(), &mut Vec::new()).evaluate().unwrap_err() { E::NotEnoughPrevResults(count)=> count == 0, _ => false, }); @@ -1128,12 +1168,12 @@ mod tests { 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!\n", &mut cache, &mut exp).evaluate().unwrap() { + 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, }); // Verified with Wolfram Alpha. - assert!(match Evaluator::new(b" 1 + (2 * |(7.98 - 12/7)|) / 4!^@3!^|1-3| \n", &mut cache, &mut exp).evaluate().unwrap() { + 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(), _ => false, }); @@ -1146,7 +1186,7 @@ mod tests { E::TrailingSyms(i) => i == 1, _ => false, }); - // Exactly one line feed is required. + // 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, @@ -1155,6 +1195,10 @@ mod tests { O::Empty => true, _ => false, }); + assert!(match Evaluator::new(&[b'\r', b'\n'], &mut cache, &mut exp).evaluate().unwrap() { + O::Empty => true, + _ => false, + }); // Empty input does not cause a panic!. assert!(match Evaluator::new(&[], &mut cache, &mut exp).evaluate().unwrap_err() { E::MissingLF => true, diff --git a/src/main.rs b/src/main.rs @@ -3,6 +3,7 @@ //! display previous results in decimal form. //! //! ```bash +//! [zack@laptop ~]$ calc //! 2.71828^0^3.14159 + -1! //! > 0 //! @^0 @@ -105,9 +106,10 @@ clippy::all, clippy::cargo, clippy::nursery, - clippy::pedantic + clippy::pedantic, + clippy::restriction )] -#![allow(clippy::implicit_return)] +#![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}; @@ -161,6 +163,7 @@ fn main() -> Result<(), GenErr> { }, Err(e) => out_lock.write_all(&e.to_string().into_bytes())?, } + out_lock.flush()?; buffer.clear(); exp_cache.clear(); }