calc_rational

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

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:
MCargo.toml | 31+++++++++++++++++++++++--------
Msrc/cache.rs | 135++-----------------------------------------------------------------------------
Asrc/cache/tests.rs | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lending_iterator.rs | 3+++
Asrc/lending_iterator/tests.rs | 5+++++
Msrc/lib.rs | 1933+------------------------------------------------------------------------------
Asrc/tests.rs | 1925+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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"")); +}