commit 46b75ca3ce6d3740209b2a0b320969a5fdb0b03c
parent c8a0f3bd1daeaa1f8c36eda2ee75ad76d306a91d
Author: Zack Newman <zack@philomathiclife.com>
Date: Mon, 13 Jul 2026 12:44:22 -0600
bump msrv. new lints. move unit tests to separate files
Diffstat:
7 files changed, 2093 insertions(+), 2068 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -9,8 +9,8 @@ license = "MIT OR Apache-2.0"
name = "calc_rational"
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/calc_rational/"
-rust-version = "1.93.1"
-version = "3.0.0"
+rust-version = "1.97.0"
+version = "3.1.0"
[lib]
name = "calc_lib"
@@ -37,12 +37,14 @@ unused = { level = "deny", priority = -1 }
warnings = { level = "deny", priority = -1 }
ambiguous-negative-literals = { level = "deny", priority = -1 }
closure-returning-async-block = { level = "deny", priority = -1 }
+dead-code-pub-in-binary = { level = "deny", priority = -1 }
deprecated-in-future = { level = "deny", priority = -1 }
+#deprecated-llvm-intrinsic = { level = "deny", priority = -1 }
deref-into-dyn-supertrait = { level = "deny", priority = -1 }
ffi-unwind-calls = { level = "deny", priority = -1 }
#fuzzy-provenance-casts = { level = "deny", priority = -1 }
impl-trait-redundant-captures = { level = "deny", priority = -1 }
-linker-messages = { level = "deny", priority = -1 }
+linker-info = { level = "deny", priority = -1 }
#lossy-provenance-casts = { level = "deny", priority = -1 }
macro-use-extern-crate = { level = "deny", priority = -1 }
meta-variable-misuse = { level = "deny", priority = -1 }
@@ -75,6 +77,7 @@ variant-size-differences = { level = "deny", priority = -1 }
# Before publishing to crates.io, comment above and uncomment below.
#warnings = { level = "allow", priority = -1 }
#ambiguous-associated-items = { level = "allow", priority = -1 }
+#ambiguous-glob-imports = { level = "allow", priority = -1 }
#arithmetic-overflow = { level = "allow", priority = -1 }
#binary-asm-labels = { level = "allow", priority = -1 }
#bindings-with-variant-name = { level = "allow", priority = -1 }
@@ -83,6 +86,7 @@ variant-size-differences = { level = "deny", priority = -1 }
#default-overrides-default-fields = { level = "allow", priority = -1 }
#dependency-on-unit-never-type-fallback = { level = "allow", priority = -1 }
#deref-nullptr = { level = "allow", priority = -1 }
+#duplicate-features = { level = "allow", priority = -1 }
#elided-lifetimes-in-associated-constant = { level = "allow", priority = -1 }
#enum-intrinsics-non-enums = { level = "allow", priority = -1 }
#explicit-builtin-cfgs-in-flags = { level = "allow", priority = -1 }
@@ -110,16 +114,16 @@ variant-size-differences = { level = "deny", priority = -1 }
#pub-use-of-private-extern-crate = { level = "allow", priority = -1 }
#repr-transparent-non-zst-fields = { level = "allow", priority = -1 }
#semicolon-in-expressions-from-macros = { level = "allow", priority = -1 }
-#soft-unstable = { level = "allow", priority = -1 }
#test-unstable-lint = { level = "allow", priority = -1 }
#text-direction-codepoint-in-comment = { level = "allow", priority = -1 }
#text-direction-codepoint-in-literal = { level = "allow", priority = -1 }
#unconditional-panic = { level = "allow", priority = -1 }
#undropped-manually-drops = { level = "allow", priority = -1 }
+#uninhabited-static = { level = "allow", priority = -1 }
#unknown-crate-types = { level = "allow", priority = -1 }
#useless-deprecated = { level = "allow", priority = -1 }
+#varargs-without-pattern = { level = "allow", priority = -1 }
-# Before publishing to crates.io, comment below.
[lints.clippy]
cargo = { level = "deny", priority = -1 }
complexity = { level = "deny", priority = -1 }
@@ -142,9 +146,20 @@ question_mark_used = "allow"
ref_patterns = "allow"
return_and_then = "allow"
semicolon_outside_block = "allow"
+self_named_module_files = "allow"
single_call_fn = "allow"
single_char_lifetime_names = "allow"
unseparated_literal_suffix = "allow"
+# Before publishing to crates.io, comment above and uncomment below.
+#cargo = "allow"
+#complexity = "allow"
+#correctness = "allow"
+#nursery = "allow"
+#pedantic = "allow"
+#perf = "allow"
+#restriction = "allow"
+#style = "allow"
+#suspicious = "allow"
[lints.rustdoc]
# Before publishing to crates.io, comment below and uncomment below that.
@@ -168,14 +183,14 @@ targets = [
]
[dependencies]
-num-bigint = { version = "0.4.6", default-features = false }
+num-bigint = { version = "0.4.8", default-features = false }
num-integer = { version = "0.1.46", default-features = false }
num-rational = { version = "0.4.2", default-features = false, features = ["num-bigint"] }
num-traits = { version = "0.2.19", default-features = false }
-rand = { version = "0.10.0", default-features = false, features = ["thread_rng"], optional = true }
+rand = { version = "0.10.2", default-features = false, features = ["thread_rng"], optional = true }
[target.'cfg(target_os = "openbsd")'.dependencies]
-priv_sep = { version = "3.0.0-alpha.5.0", default-features = false }
+priv_sep = { version = "3.0.0-alpha.5.1", default-features = false }
### FEATURES #################################################################
diff --git a/src/cache.rs b/src/cache.rs
@@ -1,3 +1,6 @@
+/// Unit tests.
+#[cfg(test)]
+mod tests;
use core::ops::Index;
/// A cache of `N` `T`s. When the cache is filled up,
/// a new entry overwrites the oldest entry. `Cache<T, N>`
@@ -174,135 +177,3 @@ where
}
}
}
-#[cfg(test)]
-mod tests {
- use super::Cache;
- #[test]
- fn len() {
- let mut c = Cache::<bool, 1>::new();
- assert_eq!(0, c.len());
- c.push(false);
- assert_eq!(1, c.len());
- c.push(false);
- assert_eq!(1, c.len());
- }
- #[test]
- fn is_empty() {
- let mut c = Cache::<bool, 1>::new();
- assert!(c.is_empty());
- c.push(false);
- assert!(!c.is_empty());
- }
- #[expect(clippy::cognitive_complexity, reason = "a lot to test")]
- #[test]
- fn get() {
- let mut c = Cache::<bool, 4>::new();
- assert_eq!(c.get(0), None);
- assert_eq!(c.get(1), None);
- assert_eq!(c.get(2), None);
- assert_eq!(c.get(3), None);
- assert_eq!(c.get(4), None);
- assert_eq!(c.get(5), None);
- assert_eq!(c.get(usize::MAX), None);
- c.push(true);
- assert_eq!(c.get(0), Some(&true));
- assert_eq!(c.get(1), None);
- assert_eq!(c.get(2), None);
- assert_eq!(c.get(3), None);
- assert_eq!(c.get(4), None);
- assert_eq!(c.get(5), None);
- assert_eq!(c.get(usize::MAX), None);
- c.push(false);
- assert_eq!(c.get(0), Some(&false));
- assert_eq!(c.get(1), Some(&true));
- assert_eq!(c.get(2), None);
- assert_eq!(c.get(3), None);
- assert_eq!(c.get(4), None);
- assert_eq!(c.get(5), None);
- assert_eq!(c.get(usize::MAX), None);
- c.push(false);
- assert_eq!(c.get(0), Some(&false));
- assert_eq!(c.get(1), Some(&false));
- assert_eq!(c.get(2), Some(&true));
- assert_eq!(c.get(3), None);
- assert_eq!(c.get(4), None);
- assert_eq!(c.get(5), None);
- assert_eq!(c.get(usize::MAX), None);
- c.push(true);
- assert_eq!(c.get(0), Some(&true));
- assert_eq!(c.get(1), Some(&false));
- assert_eq!(c.get(2), Some(&false));
- assert_eq!(c.get(3), Some(&true));
- assert_eq!(c.get(4), None);
- assert_eq!(c.get(5), None);
- assert_eq!(c.get(usize::MAX), None);
- c.push(true);
- assert_eq!(c.get(0), Some(&true));
- assert_eq!(c.get(1), Some(&true));
- assert_eq!(c.get(2), Some(&false));
- assert_eq!(c.get(3), Some(&false));
- assert_eq!(c.get(4), None);
- assert_eq!(c.get(5), None);
- assert_eq!(c.get(usize::MAX), None);
- }
- #[test]
- fn get_unsafe() {
- let mut c = Cache::<bool, 4>::new();
- assert!(!c.get_unchecked(0));
- assert!(!c.get_unchecked(1));
- assert!(!c.get_unchecked(2));
- assert!(!c.get_unchecked(3));
- assert!(!c.get_unchecked(4));
- c.push(true);
- assert!(c.get_unchecked(0));
- assert!(!c.get_unchecked(1));
- assert!(!c.get_unchecked(2));
- assert!(!c.get_unchecked(3));
- assert!(c.get_unchecked(4));
- }
- #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
- #[test]
- fn index() {
- let mut c = Cache::<bool, 4>::new();
- c.push(true);
- // `c.len() > 0`.
- assert!(c[0]);
- }
- #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
- #[test]
- #[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
- fn index_panic() {
- let c = Cache::<bool, 4>::new();
- // `c.len() > 0`.
- assert!(c[0]);
- }
- #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
- #[test]
- fn push() {
- let mut c = Cache::<bool, 4>::new();
- c.push(true);
- // `c.len() > 0`.
- assert!(c[0]);
- c.push(true);
- // `c.len() > 0`.
- assert!(c[0]);
- c.push(false);
- // `c.len() > 0`.
- assert!(!c[0]);
- c.push(true);
- // `c.len() > 0`.
- assert!(c[0]);
- c.push(false);
- // `c.len() > 0`.
- assert!(!c[0]);
- c.push(false);
- // `c.len() > 0`.
- assert!(!c[0]);
- }
- #[test]
- fn new() {
- _ = Cache::<bool, 0>::new();
- _ = Cache::<bool, 32>::new();
- _ = Cache::<bool, 31>::new();
- }
-}
diff --git a/src/cache/tests.rs b/src/cache/tests.rs
@@ -0,0 +1,129 @@
+use super::Cache;
+#[test]
+fn len() {
+ let mut c = Cache::<bool, 1>::new();
+ assert_eq!(0, c.len());
+ c.push(false);
+ assert_eq!(1, c.len());
+ c.push(false);
+ assert_eq!(1, c.len());
+}
+#[test]
+fn is_empty() {
+ let mut c = Cache::<bool, 1>::new();
+ assert!(c.is_empty());
+ c.push(false);
+ assert!(!c.is_empty());
+}
+#[expect(clippy::cognitive_complexity, reason = "a lot to test")]
+#[test]
+fn get() {
+ let mut c = Cache::<bool, 4>::new();
+ assert_eq!(c.get(0), None);
+ assert_eq!(c.get(1), None);
+ assert_eq!(c.get(2), None);
+ assert_eq!(c.get(3), None);
+ assert_eq!(c.get(4), None);
+ assert_eq!(c.get(5), None);
+ assert_eq!(c.get(usize::MAX), None);
+ c.push(true);
+ assert_eq!(c.get(0), Some(&true));
+ assert_eq!(c.get(1), None);
+ assert_eq!(c.get(2), None);
+ assert_eq!(c.get(3), None);
+ assert_eq!(c.get(4), None);
+ assert_eq!(c.get(5), None);
+ assert_eq!(c.get(usize::MAX), None);
+ c.push(false);
+ assert_eq!(c.get(0), Some(&false));
+ assert_eq!(c.get(1), Some(&true));
+ assert_eq!(c.get(2), None);
+ assert_eq!(c.get(3), None);
+ assert_eq!(c.get(4), None);
+ assert_eq!(c.get(5), None);
+ assert_eq!(c.get(usize::MAX), None);
+ c.push(false);
+ assert_eq!(c.get(0), Some(&false));
+ assert_eq!(c.get(1), Some(&false));
+ assert_eq!(c.get(2), Some(&true));
+ assert_eq!(c.get(3), None);
+ assert_eq!(c.get(4), None);
+ assert_eq!(c.get(5), None);
+ assert_eq!(c.get(usize::MAX), None);
+ c.push(true);
+ assert_eq!(c.get(0), Some(&true));
+ assert_eq!(c.get(1), Some(&false));
+ assert_eq!(c.get(2), Some(&false));
+ assert_eq!(c.get(3), Some(&true));
+ assert_eq!(c.get(4), None);
+ assert_eq!(c.get(5), None);
+ assert_eq!(c.get(usize::MAX), None);
+ c.push(true);
+ assert_eq!(c.get(0), Some(&true));
+ assert_eq!(c.get(1), Some(&true));
+ assert_eq!(c.get(2), Some(&false));
+ assert_eq!(c.get(3), Some(&false));
+ assert_eq!(c.get(4), None);
+ assert_eq!(c.get(5), None);
+ assert_eq!(c.get(usize::MAX), None);
+}
+#[test]
+fn get_unsafe() {
+ let mut c = Cache::<bool, 4>::new();
+ assert!(!c.get_unchecked(0));
+ assert!(!c.get_unchecked(1));
+ assert!(!c.get_unchecked(2));
+ assert!(!c.get_unchecked(3));
+ assert!(!c.get_unchecked(4));
+ c.push(true);
+ assert!(c.get_unchecked(0));
+ assert!(!c.get_unchecked(1));
+ assert!(!c.get_unchecked(2));
+ assert!(!c.get_unchecked(3));
+ assert!(c.get_unchecked(4));
+}
+#[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
+#[test]
+fn index() {
+ let mut c = Cache::<bool, 4>::new();
+ c.push(true);
+ // `c.len() > 0`.
+ assert!(c[0]);
+}
+#[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
+#[test]
+#[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
+fn index_panic() {
+ let c = Cache::<bool, 4>::new();
+ // `c.len() > 0`.
+ assert!(c[0]);
+}
+#[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
+#[test]
+fn push() {
+ let mut c = Cache::<bool, 4>::new();
+ c.push(true);
+ // `c.len() > 0`.
+ assert!(c[0]);
+ c.push(true);
+ // `c.len() > 0`.
+ assert!(c[0]);
+ c.push(false);
+ // `c.len() > 0`.
+ assert!(!c[0]);
+ c.push(true);
+ // `c.len() > 0`.
+ assert!(c[0]);
+ c.push(false);
+ // `c.len() > 0`.
+ assert!(!c[0]);
+ c.push(false);
+ // `c.len() > 0`.
+ assert!(!c[0]);
+}
+#[test]
+fn new() {
+ _ = Cache::<bool, 0>::new();
+ _ = Cache::<bool, 32>::new();
+ _ = Cache::<bool, 31>::new();
+}
diff --git a/src/lending_iterator.rs b/src/lending_iterator.rs
@@ -1,3 +1,6 @@
+/// Unit tests.
+#[cfg(test)]
+mod tests;
/// Generalizes [`Iterator`] by allowing one to yield references.
pub trait LendingIterator {
/// Read [`Iterator::Item`].
diff --git a/src/lending_iterator/tests.rs b/src/lending_iterator/tests.rs
@@ -0,0 +1,5 @@
+use super::LendingIterator as _;
+#[test]
+fn lending_iterator() {
+ assert_eq!([0u32, 1u32].into_iter().lend_next(), Some(0));
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -121,6 +121,9 @@
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate alloc;
+/// Unit tests.
+#[cfg(test)]
+mod tests;
use LangErr::{
DivByZero, ExpDivByZero, ExpIsNotIntOrOneHalf, InvalidAbs, InvalidDec, InvalidPar, InvalidQuit,
InvalidRound, InvalidStore, MissingTerm, ModIsNotInt, ModZero, NotEnoughPrevResults,
@@ -518,7 +521,7 @@ impl<'input, 'cache, 'prev, 'scratch, 'rand> Evaluator<'input, 'cache, 'prev, 's
b' ' | b'\t' => Ok(val + 1),
_ => Err(val),
})
- .map_or_else(convert::identity, convert::identity);
+ .unwrap_or_else(convert::identity);
}
/// Evaluates addition expressions as defined in the calc language.
/// This function is used for both addition and subtraction operations which
@@ -1101,7 +1104,7 @@ impl<'input, 'cache, 'prev, 'scratch, 'rand> Evaluator<'input, 'cache, 'prev, 's
Err(prev)
}
})
- .map_or_else(convert::identity, convert::identity)
+ .unwrap_or_else(convert::identity)
}
let (int, len) = to_biguint(&self.utf8[self.i..]);
if len == 0 {
@@ -1256,1929 +1259,3 @@ where
)
}
}
-#[cfg(test)]
-mod tests {
- use super::{
- BigInt, BigUint, Cache, Evaluator,
- LangErr::MissingTerm,
- O::{Empty, Eval, Store},
- Sign, Vec, vec,
- };
- #[cfg(feature = "rand")]
- use super::{BufRead, E, EvalIter, LangErr, LendingIterator as _};
- #[cfg(not(feature = "rand"))]
- use super::{
- LangErr::{
- DivByZero, ExpDivByZero, ExpIsNotIntOrOneHalf, InvalidAbs, InvalidDec, InvalidPar,
- InvalidQuit, InvalidRound, InvalidStore, ModIsNotInt, ModZero, NotEnoughPrevResults,
- NotNonNegIntFact, SqrtDoesNotExist, TrailingSyms,
- },
- O::Exit,
- Ratio,
- };
- #[cfg(not(feature = "rand"))]
- use alloc::{borrow::ToOwned as _, string::ToString as _};
- #[cfg(not(feature = "rand"))]
- use num_traits::Pow as _;
- #[cfg(feature = "rand")]
- use num_traits::ToPrimitive as _;
- #[cfg(feature = "rand")]
- use std::io::{self, Error, Read};
- #[expect(clippy::too_many_lines, reason = "a lot to test")]
- #[cfg(not(feature = "rand"))]
- #[test]
- fn empty() {
- // Empty expressions without a previous result return nothing.
- assert_eq!(
- Evaluator::new(b"\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
- Ok(Empty(&None))
- );
- assert_eq!(
- 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()
- .map(|a| a.to_string()),
- Ok("> -12".to_owned())
- );
- assert_eq!(
- 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()
- .map(|a| a.to_string()),
- Ok("> -0.666666667".to_owned())
- );
- assert_eq!(
- 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![4_230_196_224, 6]))
- )),
- &mut Vec::new()
- )
- .evaluate()
- .map(|a| a.to_string()),
- Ok("> 0.000000000".to_owned())
- );
- assert_eq!(
- 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![4_230_196_224, 6]))
- )),
- &mut Vec::new()
- )
- .evaluate()
- .map(|a| a.to_string()),
- Ok("> 0.000000001".to_owned())
- );
- assert_eq!(
- 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()
- .map(|a| a.to_string()),
- Ok("> 0.3".to_owned())
- );
- assert_eq!(
- 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()
- .map(|a| a.to_string()),
- Ok("> -20.3".to_owned())
- );
- assert_eq!(
- Evaluator::new(
- &[0u8; 0],
- &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()
- .map(|a| a.to_string()),
- Ok("> -20.3".to_owned())
- );
- }
- #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
- #[cfg(not(feature = "rand"))]
- #[test]
- fn number_literal() {
- // Normal 0 is fine.
- assert_eq!(
- Evaluator::new(b"0", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::NoSign,
- BigUint::new(Vec::new())
- ))))
- );
- // Leading 0s and trailing 0s are fine.
- assert_eq!(
- Evaluator::new(b"0000.00000", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_rational(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::NoSign,
- BigUint::new(Vec::new())
- ))))
- );
- // Parsing stops at first non-(digit/period).
- assert_eq!(
- Evaluator::new(b"1 0", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
- Ok(Some(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_or_else(|| unreachable!("bug in BigInt::parse_bytes")),
- );
- let right = Ratio::new(
- BigInt::parse_bytes(frac, 10)
- .unwrap_or_else(|| unreachable!("bug in BigInt::parse_bytes")),
- 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_eq!(
- Evaluator::new(
- vec.as_slice(),
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_rational(),
- Ok(Some(left + right))
- );
- // Leading 0s and trailing 0s for a non-zero value are fine.
- assert_eq!(
- Evaluator::new(
- b"000000014.0000000000000",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_rational(),
- Ok(Some(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_eq!(
- Evaluator::new(b"1.", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
- Err(InvalidDec(2))
- );
- // 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_eq!(
- Evaluator::new(b"1. 2", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
- Err(InvalidDec(2))
- );
- // 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_eq!(
- Evaluator::new(b"a1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
- Ok(None)
- );
- // A space starting the input is valid but produces no value.
- assert_eq!(
- Evaluator::new(b" 1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
- Ok(None)
- );
- // A tab starting the input is valid but produces no value.
- assert_eq!(
- Evaluator::new(b"\t1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
- Ok(None)
- );
- // Negative literals don't exist, so this should succeed but produce nothing.
- assert_eq!(
- Evaluator::new(b"-1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
- Ok(None)
- );
- // '/' is division and not part of a number literal so only the "numerator" is parsed.
- assert_eq!(
- Evaluator::new(b"1/2", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
- Ok(Some(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_eq!(
- Evaluator::new(b"130alj", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![130])
- ))))
- );
- }
- #[cfg(not(feature = "rand"))]
- #[test]
- fn par() {
- // Missing closing ')'
- assert_eq!(
- Evaluator::new(b"(1", &mut Cache::new(), &mut None, &mut Vec::new()).get_par(),
- Err(InvalidPar(2))
- );
- assert_eq!(
- Evaluator::new(b"((1\t + 2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_par(),
- Err(InvalidPar(9))
- );
- assert_eq!(
- Evaluator::new(b"( 0 \t )", &mut Cache::new(), &mut None, &mut Vec::new()).get_par(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::NoSign,
- BigUint::new(Vec::new())
- ))))
- );
- assert_eq!(
- Evaluator::new(b"( - \t 5 )", &mut Cache::new(), &mut None, &mut Vec::new()).get_par(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![5])
- ))))
- );
- assert_eq!(
- Evaluator::new(
- b"( ( 2 -\t 5) * 9 )",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_par(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![27])
- ))))
- );
- }
- #[expect(clippy::too_many_lines, reason = "a lot to test")]
- #[expect(
- clippy::indexing_slicing,
- clippy::unwrap_used,
- reason = "comments justify correctness"
- )]
- #[cfg(not(feature = "rand"))]
- #[test]
- fn recall_expression() {
- // If the input does not start with '@', then it's valid but produces nothing.
- assert_eq!(
- Evaluator::new(b"1", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
- Ok(None)
- );
- assert_eq!(
- Evaluator::new(b"a", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
- Ok(None)
- );
- assert_eq!(
- Evaluator::new(b" @", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
- Ok(None)
- );
- assert_eq!(
- Evaluator::new(b"\t@", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
- Ok(None)
- );
- // Invalid recall expression since there are no previous results.
- assert_eq!(
- Evaluator::new(b"@", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
- Err(NotEnoughPrevResults(0))
- );
- // Invalid recall expression since there are no previous results.
- assert_eq!(
- Evaluator::new(b"@4", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
- Err(NotEnoughPrevResults(0))
- );
- // Invalid recall expression since there are no previous results.
- // The input violates our grammar, but this error happens before that.
- assert_eq!(
- Evaluator::new(b"@0", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
- Err(NotEnoughPrevResults(0))
- );
- // Successfully extract previous expression.
- let mut prev = None;
- let mut cache = Cache::new();
- // Quick check that `Ok` is returned.
- _ = Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new())
- .evaluate()
- .unwrap();
- // Quick check that `Ok` is returned.
- _ = Evaluator::new(b" s \r\n", &mut cache, &mut prev, &mut Vec::new())
- .evaluate()
- .unwrap();
- assert_eq!(
- Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- ))))
- );
- // Invalid characters are ignored at this stage.
- assert_eq!(
- Evaluator::new(b"@&", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- ))))
- );
- // 0 is not a valid stored value only 1-8 are.
- assert_eq!(
- Evaluator::new(b"@0", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- ))))
- );
- // 9 is not a valid stored value only 1-8 are.
- assert_eq!(
- Evaluator::new(b"@9", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(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_eq!(
- Evaluator::new(b"@ 2", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(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_eq!(
- Evaluator::new(b"@\t2", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- ))))
- );
- // One digits are looked at so this is not @<ten>.
- assert_eq!(
- Evaluator::new(b"@10", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- ))))
- );
- // Invalid recall expression since there is only one stored result.
- assert_eq!(
- Evaluator::new(b"@2", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Err(NotEnoughPrevResults(1))
- );
- // Quick check that `Ok` is returned.
- _ = Evaluator::new(b"2\r\n", &mut cache, &mut prev, &mut Vec::new())
- .evaluate()
- .unwrap();
- // Quick check that `Ok` is returned.
- _ = Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
- .evaluate()
- .unwrap();
- // Stored values correct.
- assert_eq!(
- Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![2])
- ))))
- );
- assert_eq!(
- Evaluator::new(b"@2", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- ))))
- );
- // Invalid recall expression since there are only three stored results.
- assert_eq!(
- Evaluator::new(b"@3", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Err(NotEnoughPrevResults(2))
- );
- let mut v = vec![0, b'\n'];
- for i in b'3'..=b'8' {
- // `v.len() > 0`.
- v[0] = i;
- _ = 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.len() > 1`.
- v[1] = i;
- // Cache is filled up correctly storing all previous values.
- assert_eq!(
- Evaluator::new(v.as_slice(), &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![u32::from(b'9' - i)])
- ))))
- );
- }
- // Only parses the first @ since the second @ is not a digit between 1 and 8.
- assert_eq!(
- Evaluator::new(b"@@", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![8])
- ))))
- );
- // Quick check that `Ok` is returned.
- _ = Evaluator::new(b"9\r\n", &mut cache, &mut prev, &mut Vec::new())
- .evaluate()
- .unwrap();
- // Quick check that `Ok` is returned.
- _ = 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.len() > 1`.
- v[1] = i;
- assert_eq!(
- Evaluator::new(v.as_slice(), &mut cache, &mut prev, &mut Vec::new()).get_recall(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![u32::from((b'9' + 1) - i)])
- ))))
- );
- }
- }
- #[cfg(not(feature = "rand"))]
- #[test]
- fn abs() {
- // Missing closing '|'
- assert_eq!(
- Evaluator::new(b"|1", &mut Cache::new(), &mut None, &mut Vec::new()).get_abs(),
- Err(InvalidAbs(2))
- );
- assert_eq!(
- Evaluator::new(b"||1 + 2|", &mut Cache::new(), &mut None, &mut Vec::new()).get_abs(),
- Err(InvalidAbs(8))
- );
- assert_eq!(
- Evaluator::new(
- b"| 0\t \t |",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_abs(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::NoSign,
- BigUint::new(Vec::new())
- ))))
- );
- assert_eq!(
- Evaluator::new(b"| - 5 |", &mut Cache::new(), &mut None, &mut Vec::new()).get_abs(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![5])
- ))))
- );
- assert_eq!(
- Evaluator::new(
- b"| \t| 2 - 5| * 9 |",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_abs(),
- Ok(Some(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_eq!(
- Evaluator::new(b" \t|9|", &mut Cache::new(), &mut None, &mut Vec::new()).get_abs(),
- Ok(None)
- );
- }
- #[cfg(not(feature = "rand"))]
- #[test]
- fn round() {
- // Missing ',<digit>)'
- assert_eq!(
- Evaluator::new(b"round(1", &mut Cache::new(), &mut None, &mut Vec::new()).get_round(),
- Err(InvalidRound(7))
- );
- assert_eq!(
- Evaluator::new(b"round(1,", &mut Cache::new(), &mut None, &mut Vec::new()).get_round(),
- Err(InvalidRound(8))
- );
- assert_eq!(
- Evaluator::new(b"round(1,2", &mut Cache::new(), &mut None, &mut Vec::new()).get_round(),
- Err(InvalidRound(9))
- );
- assert_eq!(
- Evaluator::new(
- b"round(1,10)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_round(),
- Err(InvalidRound(9))
- );
- assert_eq!(
- Evaluator::new(b"round(1,a)", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_round(),
- Err(InvalidRound(8))
- );
- assert_eq!(
- Evaluator::new(
- b"round(2, 7)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_round(),
- Ok(Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![2])
- ))))
- );
- assert_eq!(
- Evaluator::new(
- b"round(2.677, 1)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_round(),
- Ok(Some(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10]))
- )))
- );
- }
- #[expect(clippy::too_many_lines, reason = "a lot to test")]
- #[cfg(feature = "rand")]
- #[test]
- fn rand() {
- assert_eq!(
- Evaluator::new(
- b"rand(1",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng()
- )
- .get_rand(),
- Err(LangErr::InvalidRand(6))
- );
- assert_eq!(
- Evaluator::new(
- b"rand(1,2",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng()
- )
- .get_rand(),
- Err(LangErr::InvalidRand(8))
- );
- assert_eq!(
- Evaluator::new(
- b"rand(1/2,3/4)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng(),
- )
- .get_rand(),
- Err(LangErr::RandNoInts(13))
- );
- assert_eq!(
- Evaluator::new(
- b"rand(-100000000000000000000000,-1000000000000000000000)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng(),
- )
- .get_rand(),
- Err(LangErr::RandNoInts(55))
- );
- assert_eq!(
- Evaluator::new(
- b"rand(2/3,1/3)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng(),
- )
- .get_rand(),
- Err(LangErr::RandInvalidArgs(13))
- );
- // If the input does not start with 'rand(', then it's invalid since get_rand must only be called as the last terminal expression which means whitespace must be consumed already.
- assert_eq!(
- Evaluator::new(
- b" rand(2/3,2)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng(),
- )
- .get_rand(),
- Err(MissingTerm(0))
- );
- assert!(
- Evaluator::new(
- b"rand(2, 7)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng()
- )
- .get_rand()
- .is_ok_and(|r| {
- let int = r.numer();
- int >= &BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))
- && *int <= BigInt::from_biguint(Sign::Plus, BigUint::new(vec![7]))
- })
- );
- assert!(
- Evaluator::new(
- b"rand()",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng()
- )
- .get_rand()
- .is_ok_and(|r| {
- let int = r.numer();
- int >= &BigInt::from(i64::MIN) && *int <= BigInt::from(i64::MAX)
- })
- );
- for _ in 0..100u8 {
- assert!(
- Evaluator::new(
- b"rand(2, 2)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng()
- )
- .get_rand()
- .is_ok_and(
- |r| *r.numer() == BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))
- )
- );
- }
- }
- #[expect(
- clippy::indexing_slicing,
- clippy::unwrap_used,
- reason = "comment justifies correctness"
- )]
- #[cfg(feature = "rand")]
- #[test]
- #[ignore = "slow"]
- fn rand_uni() {
- const COUNT: u32 = 999_999;
- #[expect(
- clippy::integer_division,
- clippy::integer_division_remainder_used,
- reason = "correct"
- )]
- const LOWER: u32 = COUNT * 33 / 100;
- #[expect(
- clippy::integer_division,
- clippy::integer_division_remainder_used,
- reason = "correct"
- )]
- const UPPER: u32 = COUNT * 101 / 300;
- // Test rand on an interval that is not a power of 2 in size.
- // This causes rand to adjust the interval to enforce uniformity.
- let mut vals = [0u32; 3];
- let mut vec = Vec::new();
- let mut cache = Cache::new();
- let mut none = None;
- for _ in 1..COUNT {
- // We want to `panic` if `rand` does not work correctly.
- vals[usize::try_from(
- Evaluator::new(
- b"rand(-1, 1)",
- &mut cache,
- &mut none,
- &mut vec,
- &mut rand::rng(),
- )
- .get_rand()
- .unwrap()
- .numer()
- .to_i32()
- .unwrap()
- + 1i32,
- )
- .unwrap()] += 1;
- }
- // Test that the distribution is within 1% of what is expected.
- assert_eq!(
- vals.into_iter().try_fold(false, |_, r| {
- if (LOWER..=UPPER).contains(&r) {
- Ok(true)
- } else {
- Err(false)
- }
- }),
- Ok(true)
- );
- }
- #[allow(
- clippy::allow_attributes,
- reason = "unwrap_used only fires when rand is not enabled"
- )]
- #[allow(clippy::unwrap_used, reason = "comments justify correctness")]
- #[test]
- fn term() {
- #[cfg(not(feature = "rand"))]
- assert_eq!(
- Evaluator::new(b"0000.00000", &mut Cache::new(), &mut None, &mut Vec::new()).get_term(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::NoSign,
- BigUint::new(Vec::new())
- )))
- );
- #[cfg(not(feature = "rand"))]
- assert_eq!(
- Evaluator::new(b"(4)", &mut Cache::new(), &mut None, &mut Vec::new()).get_term(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![4])
- )))
- );
- #[cfg(not(feature = "rand"))]
- assert_eq!(
- Evaluator::new(
- b"round(-2/3,2)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_term(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Minus, BigUint::new(vec![67])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![100]))
- ))
- );
- #[cfg(feature = "rand")]
- drop(
- Evaluator::new(
- b"rand()",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng(),
- )
- .get_term()
- .unwrap(),
- );
- #[cfg(feature = "rand")]
- drop(
- Evaluator::new(
- b"rand(-13/93, 833)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::rng(),
- )
- .get_term()
- .unwrap(),
- );
- #[cfg(not(feature = "rand"))]
- assert_eq!(
- Evaluator::new(b"rand()", &mut Cache::new(), &mut None, &mut Vec::new()).get_term(),
- Err(MissingTerm(0))
- );
- #[cfg(not(feature = "rand"))]
- assert_eq!(
- Evaluator::new(b"|4|", &mut Cache::new(), &mut None, &mut Vec::new()).get_term(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![4])
- )))
- );
- // Terminal expressions do no clean up before or after.
- #[cfg(not(feature = "rand"))]
- assert_eq!(
- Evaluator::new(b" 2", &mut Cache::new(), &mut None, &mut Vec::new()).get_term(),
- Err(MissingTerm(0))
- );
- #[cfg(not(feature = "rand"))]
- let mut prev = None;
- #[cfg(not(feature = "rand"))]
- let mut cache = Cache::new();
- #[cfg(not(feature = "rand"))]
- {
- // Quick check that `Ok` is returned.
- _ = Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new())
- .evaluate()
- .unwrap();
- // Quick check that `Ok` is returned.
- _ = Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
- .evaluate()
- .unwrap();
- }
- #[cfg(not(feature = "rand"))]
- assert_eq!(
- Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()).get_term(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- }
- #[expect(clippy::unwrap_used, reason = "comments justify correctness")]
- #[cfg(not(feature = "rand"))]
- #[test]
- fn factorial() {
- // Negative integer is not allowed.
- assert_eq!(
- Evaluator::new(b"(-1)!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Err(NotNonNegIntFact(5))
- );
- // Non-integer is not allowed.
- assert_eq!(
- Evaluator::new(b"2.5!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Err(NotNonNegIntFact(4))
- );
- // factorials always become terminal expressions eventually.
- assert_eq!(
- Evaluator::new(b"7", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![7])
- )))
- );
- assert_eq!(
- Evaluator::new(b"(7)", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![7])
- )))
- );
- assert_eq!(
- Evaluator::new(b"|7|", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![7])
- )))
- );
- let mut prev = None;
- let mut cache = Cache::new();
- // Quick check that `Ok` is returned.
- _ = Evaluator::new(b"3\n", &mut cache, &mut prev, &mut Vec::new())
- .evaluate()
- .unwrap();
- // Quick check that `Ok` is returned.
- _ = Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
- .evaluate()
- .unwrap();
- assert_eq!(
- Evaluator::new(b"@!", &mut cache, &mut prev, &mut Vec::new()).get_fact(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![6])
- )))
- );
- assert_eq!(
- Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()).get_fact(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![3])
- )))
- );
- // 0! = 1.
- assert_eq!(
- Evaluator::new(b"0.0!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- // 1! = 1.
- assert_eq!(
- Evaluator::new(b"1!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- // 4! = 24, and whitespace is not consumed.
- assert_eq!(
- Evaluator::new(b"4! \t", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![24])
- )))
- );
- // Factorials can be chained.
- assert_eq!(
- Evaluator::new(b"3!! ", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![720])
- )))
- );
- // only factorial is consumed.
- assert_eq!(
- Evaluator::new(b"2!+3", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Ok(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_eq!(
- Evaluator::new(b" 2!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Err(MissingTerm(0))
- );
- assert_eq!(
- Evaluator::new(b"\t2!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Err(MissingTerm(0))
- );
- // Error since negation is not consumed by factorial or higher precedence expressions.
- assert_eq!(
- Evaluator::new(b"-2!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
- Err(MissingTerm(0))
- );
- }
- #[expect(
- clippy::cognitive_complexity,
- clippy::too_many_lines,
- reason = "a lot to test"
- )]
- #[cfg(not(feature = "rand"))]
- #[test]
- fn exp() {
- // 1 can be raised to anything and return 1.
- // Also white space is ignored between operator.
- assert_eq!(
- Evaluator::new(b"1 ^\t 0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"1^0.5", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"1^(-1/2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"1.0^(-2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(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_eq!(
- Evaluator::new(b"0^0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"0^1", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::NoSign,
- BigUint::new(vec![0])
- )))
- );
- assert_eq!(
- Evaluator::new(b"0^0.5", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::NoSign,
- BigUint::new(vec![0])
- )))
- );
- // Anything else can only be raised to integers.
- assert_eq!(
- Evaluator::new(b"4^0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"4^1", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![4])
- )))
- );
- assert_eq!(
- Evaluator::new(b"4^(-2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16]))
- ))
- );
- assert_eq!(
- Evaluator::new(b"(-4)^0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"(-4)^1", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![4])
- )))
- );
- assert_eq!(
- Evaluator::new(b"(-4)^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![16])
- )))
- );
- assert_eq!(
- Evaluator::new(b"(-4)^(-2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16]))
- ))
- );
- assert_eq!(
- Evaluator::new(b"(-4)^(-3)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Minus, BigUint::new(vec![1])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64]))
- ))
- );
- assert_eq!(
- Evaluator::new(b"(2/3)^0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"(2/3)^(2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9]))
- ))
- );
- assert_eq!(
- Evaluator::new(b"(2/3)^(-3)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8]))
- ))
- );
- assert_eq!(
- Evaluator::new(b"(-2/3)^0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"(-2/3)^(2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9]))
- ))
- );
- assert_eq!(
- Evaluator::new(b"(-2/3)^(3)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Minus, BigUint::new(vec![8])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27]))
- ))
- );
- assert_eq!(
- Evaluator::new(
- b"(-2/3)^(-2)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_exps(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4]))
- ))
- );
- assert_eq!(
- Evaluator::new(
- b"(-2/3)^(-3)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_exps(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Minus, BigUint::new(vec![27])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8]))
- ))
- );
- assert_eq!(
- Evaluator::new(
- b"(4/9)^(-1/2)",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_exps(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))
- ))
- );
- // Error since 0 cannot be raised to a negative power.
- assert_eq!(
- Evaluator::new(b"0^(-1)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Err(ExpDivByZero(6))
- );
- // Error since anything other than 0 or 1 cannot be raised to a non-integer power or (+/-) 1/2.
- assert_eq!(
- Evaluator::new(b"2^(1/3)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Err(ExpIsNotIntOrOneHalf(7))
- );
- // When exponent is (+/-) 1/2, base has to be the square of a rational number.
- assert_eq!(
- Evaluator::new(b"2^(1/2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Err(SqrtDoesNotExist(7))
- );
- // exps always become factorials eventually.
- assert_eq!(
- Evaluator::new(b"3!", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![6])
- )))
- );
- // exponentiation has lower precedence than factorials.
- assert_eq!(
- Evaluator::new(b"2^3!", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![64])
- )))
- );
- assert_eq!(
- Evaluator::new(b"3!^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Ok(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_eq!(
- Evaluator::new(b" 2^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Err(MissingTerm(0))
- );
- assert_eq!(
- Evaluator::new(b"\t2^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
- Err(MissingTerm(0))
- );
- }
- #[cfg(not(feature = "rand"))]
- #[test]
- fn neg() {
- assert_eq!(
- Evaluator::new(b"-1", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"- \t - 1", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"-0", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::NoSign,
- BigUint::new(Vec::new())
- )))
- );
- // negation has lower precedence than exponentiation.
- assert_eq!(
- Evaluator::new(b"-2^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![4])
- )))
- );
- // negation always becomes exponentiation eventually.
- assert_eq!(
- Evaluator::new(b"2.0^2.0", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
- Ok(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_eq!(
- Evaluator::new(b" -2", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
- Err(MissingTerm(0))
- );
- assert_eq!(
- Evaluator::new(b"\t-2", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
- Err(MissingTerm(0))
- );
- }
- #[expect(
- clippy::cognitive_complexity,
- clippy::too_many_lines,
- reason = "a lot to test"
- )]
- #[cfg(not(feature = "rand"))]
- #[test]
- fn mult() {
- assert_eq!(
- Evaluator::new(b"2 * 3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![6])
- )))
- );
- assert_eq!(
- Evaluator::new(
- b"-2 * \t 3",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![6])
- )))
- );
- assert_eq!(
- Evaluator::new(
- b"2\t * -3.0",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![6])
- )))
- );
- assert_eq!(
- Evaluator::new(b"-2.5*-3.5", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![35])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4]))
- ))
- );
- assert_eq!(
- Evaluator::new(b"4.0\t / 6", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_mults(),
- Ok(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3]))
- ))
- );
- assert_eq!(
- Evaluator::new(b"6/3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![2])
- )))
- );
- assert_eq!(
- Evaluator::new(b"-6/3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![2])
- )))
- );
- assert_eq!(
- Evaluator::new(b"6/-3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![2])
- )))
- );
- assert_eq!(
- Evaluator::new(
- b"- 6 /\t - 3",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_mults(),
- Ok(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 None, &mut Vec::new())
- .get_mults()
- .is_ok_and(|r| {
- Evaluator::new(b"1/3/2", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_mults()
- .is_ok_and(|r2| r != r2)
- })
- );
- assert!(
- Evaluator::new(b"1/1.5", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_mults()
- .is_ok_and(|r| {
- Evaluator::new(b"1/(3/2)", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_mults()
- .is_ok_and(|r2| r == r2)
- })
- );
- // multiplication always becomes negation eventually.
- assert_eq!(
- Evaluator::new(b"-2.0", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(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_eq!(
- Evaluator::new(b" 2*2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Err(MissingTerm(0))
- );
- assert_eq!(
- Evaluator::new(b"\t2/2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Err(MissingTerm(0))
- );
- assert_eq!(
- Evaluator::new(
- b"4.0\t mod 6",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![4])
- ),))
- );
- assert_eq!(
- Evaluator::new(b"5 mod 3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![2])
- )))
- );
- assert_eq!(
- Evaluator::new(b"-5 mod 3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"5 mod -3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![2])
- )))
- );
- assert_eq!(
- Evaluator::new(
- b"-5 mod\t -3",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- // Cannot divide by 0.
- assert_eq!(
- Evaluator::new(b"2/0", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Err(DivByZero(3))
- );
- assert_eq!(
- Evaluator::new(b"2 mod 0", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Err(ModZero(7))
- );
- // Right and left operands of mod must be integers.
- assert_eq!(
- Evaluator::new(b"3.2 mod 1", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Err(ModIsNotInt(4))
- );
- assert_eq!(
- Evaluator::new(b"3 mod 3.2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Err(ModIsNotInt(9))
- );
- // multiplication has lower precedence than exponentiation.
- assert_eq!(
- Evaluator::new(b"2*2^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![8])
- )))
- );
- assert_eq!(
- Evaluator::new(b"8/2^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![2])
- )))
- );
- assert_eq!(
- Evaluator::new(b"8 mod 3^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![8])
- )))
- );
- }
- #[expect(clippy::too_many_lines, reason = "a lot to test")]
- #[cfg(not(feature = "rand"))]
- #[test]
- fn add() {
- assert_eq!(
- Evaluator::new(b"2 + 3", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![5])
- )))
- );
- assert_eq!(
- Evaluator::new(b"-2 + 3", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(
- b"2 + \t -3.0",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![1])
- )))
- );
- assert_eq!(
- Evaluator::new(b"-2.5+-3.5", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![6])
- )))
- );
- assert_eq!(
- Evaluator::new(b"4.0\t - 6", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![2])
- )))
- );
- assert_eq!(
- Evaluator::new(b"6-3", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![3])
- )))
- );
- assert_eq!(
- Evaluator::new(b"-6-3", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![9])
- )))
- );
- assert_eq!(
- Evaluator::new(b"6--3", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![9])
- )))
- );
- assert_eq!(
- Evaluator::new(
- b"- 6 -\t - 3",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![3])
- )))
- );
- // addition always becomes multiplication eventually.
- assert_eq!(
- Evaluator::new(b"2 * 8", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![16])
- )))
- );
- assert_eq!(
- Evaluator::new(b"8 /\t 2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(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_eq!(
- Evaluator::new(b" 2+2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Err(MissingTerm(0))
- );
- assert_eq!(
- Evaluator::new(b" 2-2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Err(MissingTerm(0))
- );
- // addition has lower precedence than multiplication.
- assert_eq!(
- Evaluator::new(b"2+2*2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![6])
- )))
- );
- assert_eq!(
- Evaluator::new(b"2+2/2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![3])
- )))
- );
- assert_eq!(
- Evaluator::new(b"2-2*2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Minus,
- BigUint::new(vec![2])
- )))
- );
- assert_eq!(
- Evaluator::new(b"2-2/2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
- Ok(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))
- );
- }
- #[cfg(not(feature = "rand"))]
- #[test]
- fn exit() {
- assert_eq!(
- Evaluator::new(b" q \n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
- Ok(Exit)
- );
- assert_eq!(
- Evaluator::new(
- b" q \r\n",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new()
- )
- .evaluate(),
- Ok(Exit)
- );
- assert_eq!(
- Evaluator::new(b"q\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
- Ok(Exit)
- );
- assert_eq!(
- Evaluator::new(b"q\r\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
- Ok(Exit)
- );
- assert_eq!(
- Evaluator::new(b"\rq\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
- Err(MissingTerm(0))
- );
- assert_eq!(
- Evaluator::new(b"\tq\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
- Ok(Exit)
- );
- assert_eq!(
- Evaluator::new(b"q\n\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
- Err(InvalidQuit)
- );
- assert_eq!(
- Evaluator::new(b"\nq\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
- Err(MissingTerm(0))
- );
- assert_eq!(
- Evaluator::new(b"q", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
- Ok(Exit)
- );
- }
- #[expect(clippy::unwrap_used, reason = "comment justifies correctness")]
- #[cfg(not(feature = "rand"))]
- #[test]
- fn store() {
- let mut prev = None;
- let mut cache = Cache::new();
- // Quick check that `Ok` is returned.
- _ = Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new())
- .evaluate()
- .unwrap();
- assert!(cache.is_empty());
- assert_eq!(
- Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new()).evaluate(),
- Ok(Store(&Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![1])
- )))))
- );
- assert_eq!(cache.len(), 1);
- assert_eq!(
- Evaluator::new(b"s2\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
- Err(InvalidStore)
- );
- }
- #[expect(clippy::too_many_lines, reason = "a lot to test")]
- #[cfg(not(feature = "rand"))]
- #[test]
- fn eval() {
- use core::str::FromStr as _;
- let mut prev = None;
- let mut cache = Cache::new();
- let mut exp = Vec::new();
- assert_eq!(
- Evaluator::new(b"1+2\n", &mut cache, &mut prev, &mut exp).evaluate(),
- Ok(Eval(&Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![3])
- ))))
- );
- assert_eq!(
- Evaluator::new(b"\t s \n", &mut cache, &mut prev, &mut exp).evaluate(),
- Ok(Store(&Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![3])
- )))))
- );
- assert_eq!(
- Evaluator::new(b"-1/2+2*@\n", &mut cache, &mut prev, &mut exp).evaluate(),
- Ok(Eval(&Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![11])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))
- )))
- );
- assert_eq!(
- Evaluator::new(b"s\n", &mut cache, &mut prev, &mut exp).evaluate(),
- Ok(Store(&Some(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![11])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))
- ))))
- );
- assert_eq!(
- Evaluator::new(b"@^@2!\r\n", &mut cache, &mut prev, &mut exp).evaluate(),
- Ok(Eval(&Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1_771_561])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64]))
- )))
- );
- assert_eq!(
- Evaluator::new(b"s\n", &mut cache, &mut prev, &mut exp).evaluate(),
- Ok(Store(&Some(Ratio::new(
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1_771_561])),
- BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64]))
- ))))
- );
- // Verified with Wolfram Alpha.
- assert!(
- Evaluator::new(
- b" \t 1 + (2 * |(7.98\t - 12/7)|) / 4!^@3!^|1-3|\t \n",
- &mut cache,
- &mut prev,
- &mut exp
- )
- .evaluate().is_ok_and(|r| {
- Ratio::from_str("2841328814244153299237884950647090899374680152474331/2841328814244153299237884950647090899374680152473600").is_ok_and(|r2| {
- r == Eval(&r2)
- })
- })
- );
- assert_eq!(
- Evaluator::new(
- b" \t round(19/6,0)!\t \r\n",
- &mut cache,
- &mut prev,
- &mut exp
- )
- .evaluate(),
- Ok(Eval(&Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![6])
- ))))
- );
- assert_eq!(
- Evaluator::new(
- b" \t 2^round(19/6,0)!\t \r\n",
- &mut cache,
- &mut prev,
- &mut exp
- )
- .evaluate(),
- Ok(Eval(&Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![64])
- ))))
- );
- assert_eq!(
- Evaluator::new(b"round(19/6,0)^2\t\n", &mut cache, &mut prev, &mut exp).evaluate(),
- Ok(Eval(&Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![9])
- ))))
- );
- // Invalid UTF-8 does not cause a panic!.
- assert_eq!(
- Evaluator::new(&[255, 255, 255, b'\n'], &mut cache, &mut prev, &mut exp).evaluate(),
- Err(MissingTerm(0))
- );
- assert_eq!(
- Evaluator::new(&[b'2', 255, b'\n'], &mut cache, &mut prev, &mut exp).evaluate(),
- Err(TrailingSyms(1))
- );
- // Exactly one newline is required.
- assert_eq!(
- Evaluator::new(b"2\n\n", &mut cache, &mut prev, &mut exp).evaluate(),
- Err(TrailingSyms(1))
- );
- assert_eq!(
- prev,
- Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![9])
- )))
- );
- assert_eq!(
- Evaluator::new(b"\n", &mut cache, &mut prev.clone(), &mut exp).evaluate(),
- Ok(Empty(&Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![9])
- )))))
- );
- assert_eq!(
- prev,
- Some(Ratio::from_integer(BigInt::from_biguint(
- Sign::Plus,
- BigUint::new(vec![9])
- )))
- );
- assert_eq!(
- Evaluator::new(b"\r\n", &mut cache, &mut prev.clone(), &mut exp).evaluate(),
- Ok(Empty(&prev))
- );
- assert_eq!(
- Evaluator::new(&[0u8; 0], &mut cache, &mut prev.clone(), &mut exp).evaluate(),
- Ok(Empty(&prev))
- );
- }
- #[cfg(feature = "rand")]
- #[test]
- fn eval_iter() {
- struct Reader<'a> {
- data: &'a [u8],
- err: bool,
- }
- impl<'a> Reader<'a> {
- fn new(data: &'a [u8]) -> Self {
- Self { data, err: true }
- }
- }
- impl Read for Reader<'_> {
- #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
- fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
- if self.err {
- self.err = false;
- Err(Error::other(""))
- } else {
- let len = usize::min(buf.len(), self.data.len());
- // `len <= buf.len()` and `len <= `self.data.len()`.
- buf[..len].copy_from_slice(&self.data[..len]);
- Ok(len)
- }
- }
- }
- impl BufRead for Reader<'_> {
- fn fill_buf(&mut self) -> io::Result<&[u8]> {
- if self.err {
- self.err = false;
- Err(Error::other(""))
- } else {
- Ok(self.data)
- }
- }
- #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
- fn consume(&mut self, amount: usize) {
- // This is just a test, so calling code passing in an invalid `amount` is fine.
- self.data = &self.data[amount..];
- }
- }
- let mut iter = EvalIter::new(Reader::new(
- b"1+2\n4\n\nq\n5\ns\nrand() + rand(-139/@, 2984/134)\nab",
- ));
- assert!(
- iter.lend_next()
- .is_some_and(|res| res.map_or_else(|e| matches!(e, E::Error(_)), |_| false))
- );
- assert!(iter.lend_next().is_some_and(|res| {
- res.is_ok_and(|e| matches!(e, Eval(r) if r.numer().to_i32() == Some(3i32)))
- }));
- assert!(iter.lend_next().is_some_and(|res| {
- res.is_ok_and(|e| matches!(e, Eval(r) if r.numer().to_i32() == Some(4i32)))
- }));
- assert!(iter.lend_next().is_some_and(|res| {
- res.is_ok_and(
- |e| matches!(e, Empty(r) if r.as_ref().is_some_and(|val| val.numer().to_i32() == Some(4i32))),
- )
- }));
- assert!(iter.lend_next().is_none());
- assert!(iter.lend_next().is_some_and(|res| {
- res.is_ok_and(|e| matches!(e, Eval(r) if r.numer().to_i32() == Some(5i32)))
- }));
- assert!(iter.lend_next().is_some_and(|res| {
- res.is_ok_and(
- |e| matches!(e, Store(r) if r.as_ref().is_some_and(|val| val.numer().to_i32() == Some(5i32))),
- )
- }));
- assert!(
- iter.lend_next()
- .is_some_and(|res| res.is_ok_and(|e| matches!(e, Eval(r) if r.is_integer())))
- );
- assert!(iter.lend_next().is_some_and(|res| {
- res.is_err_and(|err| matches!(err, E::LangErr(ref e) if matches!(*e, MissingTerm(_))))
- }));
- assert!(iter.lend_next().is_none());
- assert!(iter.lend_next().is_none());
- assert!(iter.lend_next().is_none());
- }
-}
diff --git a/src/tests.rs b/src/tests.rs
@@ -0,0 +1,1925 @@
+use super::{
+ BigInt, BigUint, Cache, Evaluator,
+ LangErr::MissingTerm,
+ O::{Empty, Eval, Store},
+ Sign, Vec, vec,
+};
+#[cfg(feature = "rand")]
+use super::{BufRead, LangErr, LendingIterator as _};
+#[cfg(feature = "std")]
+use super::{E, EvalIter};
+#[cfg(not(feature = "rand"))]
+use super::{
+ LangErr::{
+ DivByZero, ExpDivByZero, ExpIsNotIntOrOneHalf, InvalidAbs, InvalidDec, InvalidPar,
+ InvalidQuit, InvalidRound, InvalidStore, ModIsNotInt, ModZero, NotEnoughPrevResults,
+ NotNonNegIntFact, SqrtDoesNotExist, TrailingSyms,
+ },
+ O::Exit,
+ Ratio,
+};
+#[cfg(not(feature = "rand"))]
+use alloc::{borrow::ToOwned as _, string::ToString as _};
+#[cfg(not(feature = "rand"))]
+use num_traits::Pow as _;
+#[cfg(feature = "rand")]
+use num_traits::ToPrimitive as _;
+#[cfg(feature = "rand")]
+use std::io::{self, Error, Read};
+#[expect(clippy::too_many_lines, reason = "a lot to test")]
+#[cfg(not(feature = "rand"))]
+#[test]
+fn empty() {
+ // Empty expressions without a previous result return nothing.
+ assert_eq!(
+ Evaluator::new(b"\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
+ Ok(Empty(&None))
+ );
+ assert_eq!(
+ 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()
+ .map(|a| a.to_string()),
+ Ok("> -12".to_owned())
+ );
+ assert_eq!(
+ 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()
+ .map(|a| a.to_string()),
+ Ok("> -0.666666667".to_owned())
+ );
+ assert_eq!(
+ 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![4_230_196_224, 6]))
+ )),
+ &mut Vec::new()
+ )
+ .evaluate()
+ .map(|a| a.to_string()),
+ Ok("> 0.000000000".to_owned())
+ );
+ assert_eq!(
+ 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![4_230_196_224, 6]))
+ )),
+ &mut Vec::new()
+ )
+ .evaluate()
+ .map(|a| a.to_string()),
+ Ok("> 0.000000001".to_owned())
+ );
+ assert_eq!(
+ 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()
+ .map(|a| a.to_string()),
+ Ok("> 0.3".to_owned())
+ );
+ assert_eq!(
+ 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()
+ .map(|a| a.to_string()),
+ Ok("> -20.3".to_owned())
+ );
+ assert_eq!(
+ Evaluator::new(
+ &[0u8; 0],
+ &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()
+ .map(|a| a.to_string()),
+ Ok("> -20.3".to_owned())
+ );
+}
+#[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
+#[cfg(not(feature = "rand"))]
+#[test]
+fn number_literal() {
+ // Normal 0 is fine.
+ assert_eq!(
+ Evaluator::new(b"0", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::NoSign,
+ BigUint::new(Vec::new())
+ ))))
+ );
+ // Leading 0s and trailing 0s are fine.
+ assert_eq!(
+ Evaluator::new(b"0000.00000", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::NoSign,
+ BigUint::new(Vec::new())
+ ))))
+ );
+ // Parsing stops at first non-(digit/period).
+ assert_eq!(
+ Evaluator::new(b"1 0", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Ok(Some(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_or_else(|| unreachable!("bug in BigInt::parse_bytes")),
+ );
+ let right = Ratio::new(
+ BigInt::parse_bytes(frac, 10).unwrap_or_else(|| unreachable!("bug in BigInt::parse_bytes")),
+ 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_eq!(
+ Evaluator::new(
+ vec.as_slice(),
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_rational(),
+ Ok(Some(left + right))
+ );
+ // Leading 0s and trailing 0s for a non-zero value are fine.
+ assert_eq!(
+ Evaluator::new(
+ b"000000014.0000000000000",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_rational(),
+ Ok(Some(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_eq!(
+ Evaluator::new(b"1.", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Err(InvalidDec(2))
+ );
+ // 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_eq!(
+ Evaluator::new(b"1. 2", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Err(InvalidDec(2))
+ );
+ // 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_eq!(
+ Evaluator::new(b"a1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Ok(None)
+ );
+ // A space starting the input is valid but produces no value.
+ assert_eq!(
+ Evaluator::new(b" 1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Ok(None)
+ );
+ // A tab starting the input is valid but produces no value.
+ assert_eq!(
+ Evaluator::new(b"\t1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Ok(None)
+ );
+ // Negative literals don't exist, so this should succeed but produce nothing.
+ assert_eq!(
+ Evaluator::new(b"-1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Ok(None)
+ );
+ // '/' is division and not part of a number literal so only the "numerator" is parsed.
+ assert_eq!(
+ Evaluator::new(b"1/2", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Ok(Some(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_eq!(
+ Evaluator::new(b"130alj", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![130])
+ ))))
+ );
+}
+#[cfg(not(feature = "rand"))]
+#[test]
+fn par() {
+ // Missing closing ')'
+ assert_eq!(
+ Evaluator::new(b"(1", &mut Cache::new(), &mut None, &mut Vec::new()).get_par(),
+ Err(InvalidPar(2))
+ );
+ assert_eq!(
+ Evaluator::new(b"((1\t + 2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_par(),
+ Err(InvalidPar(9))
+ );
+ assert_eq!(
+ Evaluator::new(b"( 0 \t )", &mut Cache::new(), &mut None, &mut Vec::new()).get_par(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::NoSign,
+ BigUint::new(Vec::new())
+ ))))
+ );
+ assert_eq!(
+ Evaluator::new(b"( - \t 5 )", &mut Cache::new(), &mut None, &mut Vec::new()).get_par(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![5])
+ ))))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"( ( 2 -\t 5) * 9 )",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_par(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![27])
+ ))))
+ );
+}
+#[expect(clippy::too_many_lines, reason = "a lot to test")]
+#[expect(
+ clippy::indexing_slicing,
+ clippy::unwrap_used,
+ reason = "comments justify correctness"
+)]
+#[cfg(not(feature = "rand"))]
+#[test]
+fn recall_expression() {
+ // If the input does not start with '@', then it's valid but produces nothing.
+ assert_eq!(
+ Evaluator::new(b"1", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
+ Ok(None)
+ );
+ assert_eq!(
+ Evaluator::new(b"a", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
+ Ok(None)
+ );
+ assert_eq!(
+ Evaluator::new(b" @", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
+ Ok(None)
+ );
+ assert_eq!(
+ Evaluator::new(b"\t@", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
+ Ok(None)
+ );
+ // Invalid recall expression since there are no previous results.
+ assert_eq!(
+ Evaluator::new(b"@", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
+ Err(NotEnoughPrevResults(0))
+ );
+ // Invalid recall expression since there are no previous results.
+ assert_eq!(
+ Evaluator::new(b"@4", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
+ Err(NotEnoughPrevResults(0))
+ );
+ // Invalid recall expression since there are no previous results.
+ // The input violates our grammar, but this error happens before that.
+ assert_eq!(
+ Evaluator::new(b"@0", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
+ Err(NotEnoughPrevResults(0))
+ );
+ // Successfully extract previous expression.
+ let mut prev = None;
+ let mut cache = Cache::new();
+ // Quick check that `Ok` is returned.
+ _ = Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new())
+ .evaluate()
+ .unwrap();
+ // Quick check that `Ok` is returned.
+ _ = Evaluator::new(b" s \r\n", &mut cache, &mut prev, &mut Vec::new())
+ .evaluate()
+ .unwrap();
+ assert_eq!(
+ Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ ))))
+ );
+ // Invalid characters are ignored at this stage.
+ assert_eq!(
+ Evaluator::new(b"@&", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ ))))
+ );
+ // 0 is not a valid stored value only 1-8 are.
+ assert_eq!(
+ Evaluator::new(b"@0", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ ))))
+ );
+ // 9 is not a valid stored value only 1-8 are.
+ assert_eq!(
+ Evaluator::new(b"@9", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(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_eq!(
+ Evaluator::new(b"@ 2", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(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_eq!(
+ Evaluator::new(b"@\t2", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ ))))
+ );
+ // One digits are looked at so this is not @<ten>.
+ assert_eq!(
+ Evaluator::new(b"@10", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ ))))
+ );
+ // Invalid recall expression since there is only one stored result.
+ assert_eq!(
+ Evaluator::new(b"@2", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Err(NotEnoughPrevResults(1))
+ );
+ // Quick check that `Ok` is returned.
+ _ = Evaluator::new(b"2\r\n", &mut cache, &mut prev, &mut Vec::new())
+ .evaluate()
+ .unwrap();
+ // Quick check that `Ok` is returned.
+ _ = Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
+ .evaluate()
+ .unwrap();
+ // Stored values correct.
+ assert_eq!(
+ Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![2])
+ ))))
+ );
+ assert_eq!(
+ Evaluator::new(b"@2", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ ))))
+ );
+ // Invalid recall expression since there are only three stored results.
+ assert_eq!(
+ Evaluator::new(b"@3", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Err(NotEnoughPrevResults(2))
+ );
+ let mut v = vec![0, b'\n'];
+ for i in b'3'..=b'8' {
+ // `v.len() > 0`.
+ v[0] = i;
+ _ = 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.len() > 1`.
+ v[1] = i;
+ // Cache is filled up correctly storing all previous values.
+ assert_eq!(
+ Evaluator::new(v.as_slice(), &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![u32::from(b'9' - i)])
+ ))))
+ );
+ }
+ // Only parses the first @ since the second @ is not a digit between 1 and 8.
+ assert_eq!(
+ Evaluator::new(b"@@", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![8])
+ ))))
+ );
+ // Quick check that `Ok` is returned.
+ _ = Evaluator::new(b"9\r\n", &mut cache, &mut prev, &mut Vec::new())
+ .evaluate()
+ .unwrap();
+ // Quick check that `Ok` is returned.
+ _ = 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.len() > 1`.
+ v[1] = i;
+ assert_eq!(
+ Evaluator::new(v.as_slice(), &mut cache, &mut prev, &mut Vec::new()).get_recall(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![u32::from((b'9' + 1) - i)])
+ ))))
+ );
+ }
+}
+#[cfg(not(feature = "rand"))]
+#[test]
+fn abs() {
+ // Missing closing '|'
+ assert_eq!(
+ Evaluator::new(b"|1", &mut Cache::new(), &mut None, &mut Vec::new()).get_abs(),
+ Err(InvalidAbs(2))
+ );
+ assert_eq!(
+ Evaluator::new(b"||1 + 2|", &mut Cache::new(), &mut None, &mut Vec::new()).get_abs(),
+ Err(InvalidAbs(8))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"| 0\t \t |",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_abs(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::NoSign,
+ BigUint::new(Vec::new())
+ ))))
+ );
+ assert_eq!(
+ Evaluator::new(b"| - 5 |", &mut Cache::new(), &mut None, &mut Vec::new()).get_abs(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![5])
+ ))))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"| \t| 2 - 5| * 9 |",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_abs(),
+ Ok(Some(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_eq!(
+ Evaluator::new(b" \t|9|", &mut Cache::new(), &mut None, &mut Vec::new()).get_abs(),
+ Ok(None)
+ );
+}
+#[cfg(not(feature = "rand"))]
+#[test]
+fn round() {
+ // Missing ',<digit>)'
+ assert_eq!(
+ Evaluator::new(b"round(1", &mut Cache::new(), &mut None, &mut Vec::new()).get_round(),
+ Err(InvalidRound(7))
+ );
+ assert_eq!(
+ Evaluator::new(b"round(1,", &mut Cache::new(), &mut None, &mut Vec::new()).get_round(),
+ Err(InvalidRound(8))
+ );
+ assert_eq!(
+ Evaluator::new(b"round(1,2", &mut Cache::new(), &mut None, &mut Vec::new()).get_round(),
+ Err(InvalidRound(9))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"round(1,10)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_round(),
+ Err(InvalidRound(9))
+ );
+ assert_eq!(
+ Evaluator::new(b"round(1,a)", &mut Cache::new(), &mut None, &mut Vec::new()).get_round(),
+ Err(InvalidRound(8))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"round(2, 7)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_round(),
+ Ok(Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![2])
+ ))))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"round(2.677, 1)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_round(),
+ Ok(Some(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10]))
+ )))
+ );
+}
+#[expect(clippy::too_many_lines, reason = "a lot to test")]
+#[cfg(feature = "rand")]
+#[test]
+fn rand() {
+ assert_eq!(
+ Evaluator::new(
+ b"rand(1",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng()
+ )
+ .get_rand(),
+ Err(LangErr::InvalidRand(6))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"rand(1,2",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng()
+ )
+ .get_rand(),
+ Err(LangErr::InvalidRand(8))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"rand(1/2,3/4)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng(),
+ )
+ .get_rand(),
+ Err(LangErr::RandNoInts(13))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"rand(-100000000000000000000000,-1000000000000000000000)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng(),
+ )
+ .get_rand(),
+ Err(LangErr::RandNoInts(55))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"rand(2/3,1/3)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng(),
+ )
+ .get_rand(),
+ Err(LangErr::RandInvalidArgs(13))
+ );
+ // If the input does not start with 'rand(', then it's invalid since get_rand must only be called as the last terminal expression which means whitespace must be consumed already.
+ assert_eq!(
+ Evaluator::new(
+ b" rand(2/3,2)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng(),
+ )
+ .get_rand(),
+ Err(MissingTerm(0))
+ );
+ assert!(
+ Evaluator::new(
+ b"rand(2, 7)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng()
+ )
+ .get_rand()
+ .is_ok_and(|r| {
+ let int = r.numer();
+ int >= &BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))
+ && *int <= BigInt::from_biguint(Sign::Plus, BigUint::new(vec![7]))
+ })
+ );
+ assert!(
+ Evaluator::new(
+ b"rand()",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng()
+ )
+ .get_rand()
+ .is_ok_and(|r| {
+ let int = r.numer();
+ int >= &BigInt::from(i64::MIN) && *int <= BigInt::from(i64::MAX)
+ })
+ );
+ for _ in 0..100u8 {
+ assert!(
+ Evaluator::new(
+ b"rand(2, 2)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng()
+ )
+ .get_rand()
+ .is_ok_and(|r| *r.numer() == BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))
+ );
+ }
+}
+#[expect(
+ clippy::indexing_slicing,
+ clippy::unwrap_used,
+ reason = "comment justifies correctness"
+)]
+#[cfg(feature = "rand")]
+#[test]
+#[ignore = "slow"]
+fn rand_uni() {
+ const COUNT: u32 = 999_999;
+ #[expect(
+ clippy::integer_division,
+ clippy::integer_division_remainder_used,
+ reason = "correct"
+ )]
+ const LOWER: u32 = COUNT * 33 / 100;
+ #[expect(
+ clippy::integer_division,
+ clippy::integer_division_remainder_used,
+ reason = "correct"
+ )]
+ const UPPER: u32 = COUNT * 101 / 300;
+ // Test rand on an interval that is not a power of 2 in size.
+ // This causes rand to adjust the interval to enforce uniformity.
+ let mut vals = [0u32; 3];
+ let mut vec = Vec::new();
+ let mut cache = Cache::new();
+ let mut none = None;
+ for _ in 1..COUNT {
+ // We want to `panic` if `rand` does not work correctly.
+ vals[usize::try_from(
+ Evaluator::new(
+ b"rand(-1, 1)",
+ &mut cache,
+ &mut none,
+ &mut vec,
+ &mut rand::rng(),
+ )
+ .get_rand()
+ .unwrap()
+ .numer()
+ .to_i32()
+ .unwrap()
+ + 1i32,
+ )
+ .unwrap()] += 1;
+ }
+ // Test that the distribution is within 1% of what is expected.
+ assert_eq!(
+ vals.into_iter().try_fold(false, |_, r| {
+ if (LOWER..=UPPER).contains(&r) {
+ Ok(true)
+ } else {
+ Err(false)
+ }
+ }),
+ Ok(true)
+ );
+}
+#[allow(
+ clippy::allow_attributes,
+ reason = "unwrap_used only fires when rand is not enabled"
+)]
+#[allow(clippy::unwrap_used, reason = "comments justify correctness")]
+#[test]
+fn term() {
+ #[cfg(not(feature = "rand"))]
+ assert_eq!(
+ Evaluator::new(b"0000.00000", &mut Cache::new(), &mut None, &mut Vec::new()).get_term(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::NoSign,
+ BigUint::new(Vec::new())
+ )))
+ );
+ #[cfg(not(feature = "rand"))]
+ assert_eq!(
+ Evaluator::new(b"(4)", &mut Cache::new(), &mut None, &mut Vec::new()).get_term(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![4])
+ )))
+ );
+ #[cfg(not(feature = "rand"))]
+ assert_eq!(
+ Evaluator::new(
+ b"round(-2/3,2)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_term(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Minus, BigUint::new(vec![67])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![100]))
+ ))
+ );
+ #[cfg(feature = "rand")]
+ drop(
+ Evaluator::new(
+ b"rand()",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng(),
+ )
+ .get_term()
+ .unwrap(),
+ );
+ #[cfg(feature = "rand")]
+ drop(
+ Evaluator::new(
+ b"rand(-13/93, 833)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng(),
+ )
+ .get_term()
+ .unwrap(),
+ );
+ #[cfg(not(feature = "rand"))]
+ assert_eq!(
+ Evaluator::new(b"rand()", &mut Cache::new(), &mut None, &mut Vec::new()).get_term(),
+ Err(MissingTerm(0))
+ );
+ #[cfg(not(feature = "rand"))]
+ assert_eq!(
+ Evaluator::new(b"|4|", &mut Cache::new(), &mut None, &mut Vec::new()).get_term(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![4])
+ )))
+ );
+ // Terminal expressions do no clean up before or after.
+ #[cfg(not(feature = "rand"))]
+ assert_eq!(
+ Evaluator::new(b" 2", &mut Cache::new(), &mut None, &mut Vec::new()).get_term(),
+ Err(MissingTerm(0))
+ );
+ #[cfg(not(feature = "rand"))]
+ let mut prev = None;
+ #[cfg(not(feature = "rand"))]
+ let mut cache = Cache::new();
+ #[cfg(not(feature = "rand"))]
+ {
+ // Quick check that `Ok` is returned.
+ _ = Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new())
+ .evaluate()
+ .unwrap();
+ // Quick check that `Ok` is returned.
+ _ = Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
+ .evaluate()
+ .unwrap();
+ }
+ #[cfg(not(feature = "rand"))]
+ assert_eq!(
+ Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()).get_term(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+}
+#[expect(clippy::unwrap_used, reason = "comments justify correctness")]
+#[cfg(not(feature = "rand"))]
+#[test]
+fn factorial() {
+ // Negative integer is not allowed.
+ assert_eq!(
+ Evaluator::new(b"(-1)!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Err(NotNonNegIntFact(5))
+ );
+ // Non-integer is not allowed.
+ assert_eq!(
+ Evaluator::new(b"2.5!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Err(NotNonNegIntFact(4))
+ );
+ // factorials always become terminal expressions eventually.
+ assert_eq!(
+ Evaluator::new(b"7", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![7])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"(7)", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![7])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"|7|", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![7])
+ )))
+ );
+ let mut prev = None;
+ let mut cache = Cache::new();
+ // Quick check that `Ok` is returned.
+ _ = Evaluator::new(b"3\n", &mut cache, &mut prev, &mut Vec::new())
+ .evaluate()
+ .unwrap();
+ // Quick check that `Ok` is returned.
+ _ = Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
+ .evaluate()
+ .unwrap();
+ assert_eq!(
+ Evaluator::new(b"@!", &mut cache, &mut prev, &mut Vec::new()).get_fact(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![6])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new()).get_fact(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![3])
+ )))
+ );
+ // 0! = 1.
+ assert_eq!(
+ Evaluator::new(b"0.0!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ // 1! = 1.
+ assert_eq!(
+ Evaluator::new(b"1!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ // 4! = 24, and whitespace is not consumed.
+ assert_eq!(
+ Evaluator::new(b"4! \t", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![24])
+ )))
+ );
+ // Factorials can be chained.
+ assert_eq!(
+ Evaluator::new(b"3!! ", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![720])
+ )))
+ );
+ // only factorial is consumed.
+ assert_eq!(
+ Evaluator::new(b"2!+3", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Ok(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_eq!(
+ Evaluator::new(b" 2!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Err(MissingTerm(0))
+ );
+ assert_eq!(
+ Evaluator::new(b"\t2!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Err(MissingTerm(0))
+ );
+ // Error since negation is not consumed by factorial or higher precedence expressions.
+ assert_eq!(
+ Evaluator::new(b"-2!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
+ Err(MissingTerm(0))
+ );
+}
+#[expect(
+ clippy::cognitive_complexity,
+ clippy::too_many_lines,
+ reason = "a lot to test"
+)]
+#[cfg(not(feature = "rand"))]
+#[test]
+fn exp() {
+ // 1 can be raised to anything and return 1.
+ // Also white space is ignored between operator.
+ assert_eq!(
+ Evaluator::new(b"1 ^\t 0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"1^0.5", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"1^(-1/2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"1.0^(-2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(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_eq!(
+ Evaluator::new(b"0^0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"0^1", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::NoSign,
+ BigUint::new(vec![0])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"0^0.5", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::NoSign,
+ BigUint::new(vec![0])
+ )))
+ );
+ // Anything else can only be raised to integers.
+ assert_eq!(
+ Evaluator::new(b"4^0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"4^1", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![4])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"4^(-2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(b"(-4)^0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"(-4)^1", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![4])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"(-4)^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![16])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"(-4)^(-2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![16]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(b"(-4)^(-3)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Minus, BigUint::new(vec![1])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(b"(2/3)^0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"(2/3)^(2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(b"(2/3)^(-3)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(b"(-2/3)^0", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"(-2/3)^(2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(b"(-2/3)^(3)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Minus, BigUint::new(vec![8])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![27]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"(-2/3)^(-2)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_exps(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![9])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"(-2/3)^(-3)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_exps(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Minus, BigUint::new(vec![27])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"(4/9)^(-1/2)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_exps(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))
+ ))
+ );
+ // Error since 0 cannot be raised to a negative power.
+ assert_eq!(
+ Evaluator::new(b"0^(-1)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Err(ExpDivByZero(6))
+ );
+ // Error since anything other than 0 or 1 cannot be raised to a non-integer power or (+/-) 1/2.
+ assert_eq!(
+ Evaluator::new(b"2^(1/3)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Err(ExpIsNotIntOrOneHalf(7))
+ );
+ // When exponent is (+/-) 1/2, base has to be the square of a rational number.
+ assert_eq!(
+ Evaluator::new(b"2^(1/2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Err(SqrtDoesNotExist(7))
+ );
+ // exps always become factorials eventually.
+ assert_eq!(
+ Evaluator::new(b"3!", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![6])
+ )))
+ );
+ // exponentiation has lower precedence than factorials.
+ assert_eq!(
+ Evaluator::new(b"2^3!", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![64])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"3!^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Ok(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_eq!(
+ Evaluator::new(b" 2^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Err(MissingTerm(0))
+ );
+ assert_eq!(
+ Evaluator::new(b"\t2^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
+ Err(MissingTerm(0))
+ );
+}
+#[cfg(not(feature = "rand"))]
+#[test]
+fn neg() {
+ assert_eq!(
+ Evaluator::new(b"-1", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"- \t - 1", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"-0", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::NoSign,
+ BigUint::new(Vec::new())
+ )))
+ );
+ // negation has lower precedence than exponentiation.
+ assert_eq!(
+ Evaluator::new(b"-2^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![4])
+ )))
+ );
+ // negation always becomes exponentiation eventually.
+ assert_eq!(
+ Evaluator::new(b"2.0^2.0", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
+ Ok(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_eq!(
+ Evaluator::new(b" -2", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
+ Err(MissingTerm(0))
+ );
+ assert_eq!(
+ Evaluator::new(b"\t-2", &mut Cache::new(), &mut None, &mut Vec::new()).get_neg(),
+ Err(MissingTerm(0))
+ );
+}
+#[expect(
+ clippy::cognitive_complexity,
+ clippy::too_many_lines,
+ reason = "a lot to test"
+)]
+#[cfg(not(feature = "rand"))]
+#[test]
+fn mult() {
+ assert_eq!(
+ Evaluator::new(b"2 * 3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![6])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"-2 * \t 3",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![6])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"2\t * -3.0",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![6])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"-2.5*-3.5", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![35])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(b"4.0\t / 6", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3]))
+ ))
+ );
+ assert_eq!(
+ Evaluator::new(b"6/3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![2])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"-6/3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![2])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"6/-3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![2])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"- 6 /\t - 3",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_mults(),
+ Ok(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 None, &mut Vec::new())
+ .get_mults()
+ .is_ok_and(|r| {
+ Evaluator::new(b"1/3/2", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_mults()
+ .is_ok_and(|r2| r != r2)
+ })
+ );
+ assert!(
+ Evaluator::new(b"1/1.5", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_mults()
+ .is_ok_and(|r| {
+ Evaluator::new(b"1/(3/2)", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_mults()
+ .is_ok_and(|r2| r == r2)
+ })
+ );
+ // multiplication always becomes negation eventually.
+ assert_eq!(
+ Evaluator::new(b"-2.0", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(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_eq!(
+ Evaluator::new(b" 2*2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Err(MissingTerm(0))
+ );
+ assert_eq!(
+ Evaluator::new(b"\t2/2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Err(MissingTerm(0))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"4.0\t mod 6",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![4])
+ ),))
+ );
+ assert_eq!(
+ Evaluator::new(b"5 mod 3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![2])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"-5 mod 3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"5 mod -3", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![2])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"-5 mod\t -3",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ // Cannot divide by 0.
+ assert_eq!(
+ Evaluator::new(b"2/0", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Err(DivByZero(3))
+ );
+ assert_eq!(
+ Evaluator::new(b"2 mod 0", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Err(ModZero(7))
+ );
+ // Right and left operands of mod must be integers.
+ assert_eq!(
+ Evaluator::new(b"3.2 mod 1", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Err(ModIsNotInt(4))
+ );
+ assert_eq!(
+ Evaluator::new(b"3 mod 3.2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Err(ModIsNotInt(9))
+ );
+ // multiplication has lower precedence than exponentiation.
+ assert_eq!(
+ Evaluator::new(b"2*2^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![8])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"8/2^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![2])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"8 mod 3^2", &mut Cache::new(), &mut None, &mut Vec::new()).get_mults(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![8])
+ )))
+ );
+}
+#[expect(clippy::too_many_lines, reason = "a lot to test")]
+#[cfg(not(feature = "rand"))]
+#[test]
+fn add() {
+ assert_eq!(
+ Evaluator::new(b"2 + 3", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![5])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"-2 + 3", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"2 + \t -3.0",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![1])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"-2.5+-3.5", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![6])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"4.0\t - 6", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![2])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"6-3", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![3])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"-6-3", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![9])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"6--3", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![9])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b"- 6 -\t - 3",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![3])
+ )))
+ );
+ // addition always becomes multiplication eventually.
+ assert_eq!(
+ Evaluator::new(b"2 * 8", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![16])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"8 /\t 2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(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_eq!(
+ Evaluator::new(b" 2+2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Err(MissingTerm(0))
+ );
+ assert_eq!(
+ Evaluator::new(b" 2-2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Err(MissingTerm(0))
+ );
+ // addition has lower precedence than multiplication.
+ assert_eq!(
+ Evaluator::new(b"2+2*2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![6])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"2+2/2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![3])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"2-2*2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Minus,
+ BigUint::new(vec![2])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"2-2/2", &mut Cache::new(), &mut None, &mut Vec::new()).get_adds(),
+ Ok(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))
+ );
+}
+#[cfg(not(feature = "rand"))]
+#[test]
+fn exit() {
+ assert_eq!(
+ Evaluator::new(b" q \n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
+ Ok(Exit)
+ );
+ assert_eq!(
+ Evaluator::new(
+ b" q \r\n",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .evaluate(),
+ Ok(Exit)
+ );
+ assert_eq!(
+ Evaluator::new(b"q\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
+ Ok(Exit)
+ );
+ assert_eq!(
+ Evaluator::new(b"q\r\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
+ Ok(Exit)
+ );
+ assert_eq!(
+ Evaluator::new(b"\rq\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
+ Err(MissingTerm(0))
+ );
+ assert_eq!(
+ Evaluator::new(b"\tq\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
+ Ok(Exit)
+ );
+ assert_eq!(
+ Evaluator::new(b"q\n\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
+ Err(InvalidQuit)
+ );
+ assert_eq!(
+ Evaluator::new(b"\nq\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
+ Err(MissingTerm(0))
+ );
+ assert_eq!(
+ Evaluator::new(b"q", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
+ Ok(Exit)
+ );
+}
+#[expect(clippy::unwrap_used, reason = "comment justifies correctness")]
+#[cfg(not(feature = "rand"))]
+#[test]
+fn store() {
+ let mut prev = None;
+ let mut cache = Cache::new();
+ // Quick check that `Ok` is returned.
+ _ = Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new())
+ .evaluate()
+ .unwrap();
+ assert!(cache.is_empty());
+ assert_eq!(
+ Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new()).evaluate(),
+ Ok(Store(&Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![1])
+ )))))
+ );
+ assert_eq!(cache.len(), 1);
+ assert_eq!(
+ Evaluator::new(b"s2\n", &mut Cache::new(), &mut None, &mut Vec::new()).evaluate(),
+ Err(InvalidStore)
+ );
+}
+#[expect(clippy::too_many_lines, reason = "a lot to test")]
+#[cfg(not(feature = "rand"))]
+#[test]
+fn eval() {
+ use core::str::FromStr as _;
+ let mut prev = None;
+ let mut cache = Cache::new();
+ let mut exp = Vec::new();
+ assert_eq!(
+ Evaluator::new(b"1+2\n", &mut cache, &mut prev, &mut exp).evaluate(),
+ Ok(Eval(&Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![3])
+ ))))
+ );
+ assert_eq!(
+ Evaluator::new(b"\t s \n", &mut cache, &mut prev, &mut exp).evaluate(),
+ Ok(Store(&Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![3])
+ )))))
+ );
+ assert_eq!(
+ Evaluator::new(b"-1/2+2*@\n", &mut cache, &mut prev, &mut exp).evaluate(),
+ Ok(Eval(&Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![11])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"s\n", &mut cache, &mut prev, &mut exp).evaluate(),
+ Ok(Store(&Some(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![11])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))
+ ))))
+ );
+ assert_eq!(
+ Evaluator::new(b"@^@2!\r\n", &mut cache, &mut prev, &mut exp).evaluate(),
+ Ok(Eval(&Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1_771_561])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64]))
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"s\n", &mut cache, &mut prev, &mut exp).evaluate(),
+ Ok(Store(&Some(Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1_771_561])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![64]))
+ ))))
+ );
+ // Verified with Wolfram Alpha.
+ assert!(
+ Evaluator::new(
+ b" \t 1 + (2 * |(7.98\t - 12/7)|) / 4!^@3!^|1-3|\t \n",
+ &mut cache,
+ &mut prev,
+ &mut exp
+ )
+ .evaluate().is_ok_and(|r| {
+ Ratio::from_str("2841328814244153299237884950647090899374680152474331/2841328814244153299237884950647090899374680152473600").is_ok_and(|r2| {
+ r == Eval(&r2)
+ })
+ })
+ );
+ assert_eq!(
+ Evaluator::new(
+ b" \t round(19/6,0)!\t \r\n",
+ &mut cache,
+ &mut prev,
+ &mut exp
+ )
+ .evaluate(),
+ Ok(Eval(&Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![6])
+ ))))
+ );
+ assert_eq!(
+ Evaluator::new(
+ b" \t 2^round(19/6,0)!\t \r\n",
+ &mut cache,
+ &mut prev,
+ &mut exp
+ )
+ .evaluate(),
+ Ok(Eval(&Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![64])
+ ))))
+ );
+ assert_eq!(
+ Evaluator::new(b"round(19/6,0)^2\t\n", &mut cache, &mut prev, &mut exp).evaluate(),
+ Ok(Eval(&Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![9])
+ ))))
+ );
+ // Invalid UTF-8 does not cause a panic!.
+ assert_eq!(
+ Evaluator::new(&[255, 255, 255, b'\n'], &mut cache, &mut prev, &mut exp).evaluate(),
+ Err(MissingTerm(0))
+ );
+ assert_eq!(
+ Evaluator::new(&[b'2', 255, b'\n'], &mut cache, &mut prev, &mut exp).evaluate(),
+ Err(TrailingSyms(1))
+ );
+ // Exactly one newline is required.
+ assert_eq!(
+ Evaluator::new(b"2\n\n", &mut cache, &mut prev, &mut exp).evaluate(),
+ Err(TrailingSyms(1))
+ );
+ assert_eq!(
+ prev,
+ Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![9])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"\n", &mut cache, &mut prev.clone(), &mut exp).evaluate(),
+ Ok(Empty(&Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![9])
+ )))))
+ );
+ assert_eq!(
+ prev,
+ Some(Ratio::from_integer(BigInt::from_biguint(
+ Sign::Plus,
+ BigUint::new(vec![9])
+ )))
+ );
+ assert_eq!(
+ Evaluator::new(b"\r\n", &mut cache, &mut prev.clone(), &mut exp).evaluate(),
+ Ok(Empty(&prev))
+ );
+ assert_eq!(
+ Evaluator::new(&[0u8; 0], &mut cache, &mut prev.clone(), &mut exp).evaluate(),
+ Ok(Empty(&prev))
+ );
+}
+#[cfg(feature = "rand")]
+#[test]
+fn eval_iter() {
+ struct Reader<'a> {
+ data: &'a [u8],
+ err: bool,
+ }
+ impl<'a> Reader<'a> {
+ fn new(data: &'a [u8]) -> Self {
+ Self { data, err: true }
+ }
+ }
+ impl Read for Reader<'_> {
+ #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ if self.err {
+ self.err = false;
+ Err(Error::other(""))
+ } else {
+ let len = usize::min(buf.len(), self.data.len());
+ // `len <= buf.len()` and `len <= `self.data.len()`.
+ buf[..len].copy_from_slice(&self.data[..len]);
+ Ok(len)
+ }
+ }
+ }
+ impl BufRead for Reader<'_> {
+ fn fill_buf(&mut self) -> io::Result<&[u8]> {
+ if self.err {
+ self.err = false;
+ Err(Error::other(""))
+ } else {
+ Ok(self.data)
+ }
+ }
+ #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
+ fn consume(&mut self, amount: usize) {
+ // This is just a test, so calling code passing in an invalid `amount` is fine.
+ self.data = &self.data[amount..];
+ }
+ }
+ let mut iter = EvalIter::new(Reader::new(
+ b"1+2\n4\n\nq\n5\ns\nrand() + rand(-139/@, 2984/134)\nab",
+ ));
+ assert!(
+ iter.lend_next()
+ .is_some_and(|res| res.map_or_else(|e| matches!(e, E::Error(_)), |_| false))
+ );
+ assert!(iter.lend_next().is_some_and(|res| {
+ res.is_ok_and(|e| matches!(e, Eval(r) if r.numer().to_i32() == Some(3i32)))
+ }));
+ assert!(iter.lend_next().is_some_and(|res| {
+ res.is_ok_and(|e| matches!(e, Eval(r) if r.numer().to_i32() == Some(4i32)))
+ }));
+ assert!(iter.lend_next().is_some_and(|res| {
+ res.is_ok_and(
+ |e| matches!(e, Empty(r) if r.as_ref().is_some_and(|val| val.numer().to_i32() == Some(4i32))),
+ )
+ }));
+ assert!(iter.lend_next().is_none());
+ assert!(iter.lend_next().is_some_and(|res| {
+ res.is_ok_and(|e| matches!(e, Eval(r) if r.numer().to_i32() == Some(5i32)))
+ }));
+ assert!(iter.lend_next().is_some_and(|res| {
+ res.is_ok_and(
+ |e| matches!(e, Store(r) if r.as_ref().is_some_and(|val| val.numer().to_i32() == Some(5i32))),
+ )
+ }));
+ assert!(
+ iter.lend_next()
+ .is_some_and(|res| res.is_ok_and(|e| matches!(e, Eval(r) if r.is_integer())))
+ );
+ assert!(iter.lend_next().is_some_and(|res| {
+ res.is_err_and(|err| matches!(err, E::LangErr(ref e) if matches!(*e, MissingTerm(_))))
+ }));
+ assert!(iter.lend_next().is_none());
+ assert!(iter.lend_next().is_none());
+ assert!(iter.lend_next().is_none());
+}
+#[ignore = "only to silence lint"]
+#[cfg(feature = "std")]
+#[test]
+fn silence_dead_pub_in_binary_when_only_std() {
+ drop(E::LangErr(MissingTerm(0)));
+ drop(EvalIter::new(b""));
+}