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:
M | README.md | | | 37 | ++++++++++++++++++++++--------------- |
M | lang.tex | | | 47 | +++++++++++++++++++++++++++-------------------- |
M | src/cache.rs | | | 4 | +++- |
M | src/lib.rs | | | 138 | ++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- |
M | src/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`, `.`, `+`, `-`,
-`*`, `/`, `^`, `!`, `|`, `(`, `)`, `@`, `=`, <space>, <line feed>, and `q`. Any other byte
-sequences are grammatically incorrect and will lead to an error message.
+`*`, `/`, `^`, `!`, `|`, `(`, `)`, `@`, `=`, <space>, <tab>, <line feed>, <carriage return>,
+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();
}