#![expect(
clippy::doc_paragraphs_missing_punctuation,
reason = "false positive for crate documentation having image links"
)]
#![expect(
clippy::arithmetic_side_effects,
reason = "calculator can't realistically avoid this"
)]
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate alloc;
use LangErr::{
DivByZero, ExpDivByZero, ExpIsNotIntOrOneHalf, InvalidAbs, InvalidDec, InvalidPar, InvalidQuit,
InvalidRound, InvalidStore, MissingTerm, ModIsNotInt, ModZero, NotEnoughPrevResults,
NotNonNegIntFact, SqrtDoesNotExist, TrailingSyms,
};
use O::{Empty, Eval, Exit, Store};
use alloc::{
string::{String, ToString as _},
vec,
vec::Vec,
};
use cache::Cache;
#[cfg(not(feature = "rand"))]
use core::marker::PhantomData;
use core::{
convert,
fmt::{self, Display, Formatter},
ops::Index as _,
};
pub use num_bigint;
use num_bigint::{BigInt, BigUint, Sign};
use num_integer::Integer as _;
pub use num_rational;
use num_rational::Ratio;
#[cfg(feature = "rand")]
use num_traits::ToPrimitive as _;
use num_traits::{Inv as _, Pow as _};
#[cfg(target_os = "openbsd")]
use priv_sep as _;
#[cfg(feature = "rand")]
pub use rand;
#[cfg(feature = "rand")]
use rand::{Rng as _, rngs::ThreadRng};
pub mod cache;
pub mod lending_iterator;
#[non_exhaustive]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[derive(Debug)]
pub enum LangErr {
InvalidQuit,
InvalidStore,
DivByZero(usize),
ExpIsNotIntOrOneHalf(usize),
ExpDivByZero(usize),
ModZero(usize),
ModIsNotInt(usize),
NotNonNegIntFact(usize),
InvalidDec(usize),
NotEnoughPrevResults(usize),
InvalidAbs(usize),
InvalidPar(usize),
InvalidRound(usize),
MissingTerm(usize),
SqrtDoesNotExist(usize),
TrailingSyms(usize),
#[cfg(feature = "rand")]
InvalidRand(usize),
#[cfg(feature = "rand")]
RandInvalidArgs(usize),
#[cfg(feature = "rand")]
RandNoInts(usize),
}
impl Display for LangErr {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
InvalidStore => f.write_str("Invalid store expression. A store expression must be of the extended regex form: ^[ \\t]*s[ \\t]*$."),
InvalidQuit => f.write_str("Invalid quit expression. A quit expression must be of the extended regex form: ^[ \\t]*q[ \\t]*$."),
DivByZero(u) => write!(f, "Division by zero ending at position {u}."),
ExpIsNotIntOrOneHalf(u) => write!(f, "Non-integer exponent that is not (+/-) 1/2 with a base that was not 0 or 1 ending at position {u}."),
ExpDivByZero(u) => write!(f, "Non-negative exponent with a base of 0 ending at position {u}."),
ModZero(u) => write!(f, "A number modulo 0 ending at position {u}."),
ModIsNotInt(u) => write!(f, "The modulo expression was applied to at least one non-integer ending at position {u}."),
NotNonNegIntFact(u) => write!(f, "Factorial of a rational number that was not a non-negative integer ending at position {u}."),
InvalidDec(u) => write!(f, "Invalid decimal literal expression ending at position {u}. A decimal literal expression must be of the extended regex form: [0-9]+(\\.[0-9]+)?."),
NotEnoughPrevResults(len) => write!(f, "There are only {len} previous results."),
InvalidAbs(u) => write!(f, "Invalid absolute value expression ending at position {u}. An absolute value expression is an addition expression enclosed in '||'."),
InvalidPar(u) => write!(f, "Invalid parenthetical expression ending at position {u}. A parenthetical expression is an addition expression enclosed in '()'."),
InvalidRound(u) => write!(f, "Invalid round expression ending at position {u}. A round expression is of the form 'round(<mod expression>, digit)'"),
SqrtDoesNotExist(u) => write!(f, "The square root of the passed expression does not have a solution in the field of rational numbers ending at position {u}."),
#[cfg(not(feature = "rand"))]
MissingTerm(u) => write!(f, "Missing terminal expression at position {u}. A terminal expression is a decimal literal expression, recall expression, absolute value expression, parenthetical expression, or round expression."),
#[cfg(feature = "rand")]
MissingTerm(u) => write!(f, "Missing terminal expression at position {u}. A terminal expression is a decimal literal expression, recall expression, absolute value expression, parenthetical expression, round expression, or rand expression."),
TrailingSyms(u) => write!(f, "Trailing symbols starting at position {u}."),
#[cfg(feature = "rand")]
Self::InvalidRand(u) => write!(f, "Invalid rand expression ending at position {u}. A rand expression is of the form 'rand()' or 'rand(<mod expression>, <mod expression>)'."),
#[cfg(feature = "rand")]
Self::RandInvalidArgs(u) => write!(f, "The second expression passed to the random function evaluated to rational number less than the first ending at position {u}."),
#[cfg(feature = "rand")]
Self::RandNoInts(u) => write!(f, "There are no 64-bit integers within the interval passed to the random function ending at position {u}."),
}
}
}
#[cfg_attr(test, derive(Eq, PartialEq))]
#[derive(Debug)]
pub enum O<'a> {
Empty(&'a Option<Ratio<BigInt>>),
Exit,
Eval(&'a Ratio<BigInt>),
Store(&'a Option<Ratio<BigInt>>),
}
impl Display for O<'_> {
#[expect(
unsafe_code,
reason = "manually construct guaranteed UTF-8; thus avoid the needless check"
)]
#[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Empty(o) => {
o.as_ref().map_or(Ok(()), |val| {
if val.is_integer() {
write!(f, "> {val}")
} else {
let mut twos = 0;
let mut fives = 0;
let zero = BigInt::from_biguint(Sign::NoSign, BigUint::new(Vec::new()));
let one = BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1]));
let two = BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]));
let five = BigInt::from_biguint(Sign::Plus, BigUint::new(vec![5]));
let mut denom = val.denom().clone();
let mut div_rem;
while denom > one {
div_rem = denom.div_rem(&two);
if div_rem.1 == zero {
twos += 1;
denom = div_rem.0;
} else {
break;
}
}
while denom > one {
div_rem = denom.div_rem(&five);
if div_rem.1 == zero {
fives += 1;
denom = div_rem.0;
} else {
break;
}
}
let (int, frac, digits) = if denom == one {
let (int, mut frac) = val.numer().div_rem(val.denom());
while twos > fives {
frac *= &five;
fives += 1;
}
while fives > twos {
frac *= &two;
twos += 1;
}
(int, frac, twos)
} else {
let mult =
BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10])).pow(9u8);
let (int, frac) = (val * &mult).round().numer().div_rem(&mult);
(int, frac, 9)
};
let int_str = int.to_string().into_bytes();
let (mut v, frac_str) = if val.numer().sign() == Sign::Minus {
if int_str[0] == b'-' {
(
Vec::with_capacity(int_str.len() + 1 + digits),
(-frac).to_string().into_bytes(),
)
} else {
let mut tmp = Vec::with_capacity(int_str.len() + 2 + digits);
tmp.push(b'-');
(tmp, (-frac).to_string().into_bytes())
}
} else {
(
Vec::with_capacity(int_str.len() + 1 + digits),
frac.to_string().into_bytes(),
)
};
v.extend_from_slice(int_str.as_slice());
v.push(b'.');
v.resize(v.len() + (digits - frac_str.len()), b'0');
v.extend_from_slice(frac_str.as_slice());
write!(f, "> {}", unsafe { String::from_utf8_unchecked(v) })
}
})
}
Eval(r) => write!(f, "> {r}"),
Exit => Ok(()),
Store(o) => o.as_ref().map_or(Ok(()), |val| write!(f, "> {val}")),
}
}
}
const CACHE_SIZE: usize = 8;
#[derive(Debug)]
pub struct Evaluator<'input, 'cache, 'prev, 'scratch, 'rand> {
utf8: &'input [u8],
i: usize,
cache: &'cache mut Cache<Ratio<BigInt>, CACHE_SIZE>,
prev: &'prev mut Option<Ratio<BigInt>>,
scratch: &'scratch mut Vec<Ratio<BigInt>>,
#[cfg(feature = "rand")]
rng: &'rand mut ThreadRng,
#[cfg(not(feature = "rand"))]
_rng: PhantomData<fn() -> &'rand ()>,
}
#[allow(
single_use_lifetimes,
clippy::allow_attributes,
clippy::elidable_lifetime_names,
reason = "unify rand and not rand"
)]
impl<'input, 'cache, 'prev, 'scratch, 'rand> Evaluator<'input, 'cache, 'prev, 'scratch, 'rand> {
#[cfg(not(feature = "rand"))]
#[inline]
pub fn new(
utf8: &'input [u8],
cache: &'cache mut Cache<Ratio<BigInt>, 8>,
prev: &'prev mut Option<Ratio<BigInt>>,
scratch: &'scratch mut Vec<Ratio<BigInt>>,
) -> Self {
Self {
utf8,
i: 0,
cache,
prev,
scratch,
_rng: PhantomData,
}
}
#[cfg(feature = "rand")]
#[inline]
pub const fn new(
utf8: &'input [u8],
cache: &'cache mut Cache<Ratio<BigInt>, 8>,
prev: &'prev mut Option<Ratio<BigInt>>,
scratch: &'scratch mut Vec<Ratio<BigInt>>,
rng: &'rand mut ThreadRng,
) -> Self {
Self {
utf8,
i: 0,
cache,
prev,
scratch,
rng,
}
}
#[expect(clippy::indexing_slicing, reason = "correct")]
#[inline]
pub fn evaluate(mut self) -> Result<O<'prev>, LangErr> {
self.utf8 = if self.utf8.last().is_none_or(|b| *b != b'\n') {
self.utf8
} else {
&self.utf8[..self.utf8.len()
- self
.utf8
.get(self.utf8.len().wrapping_sub(2))
.map_or(1, |b| if *b == b'\r' { 2 } else { 1 })]
};
self.consume_ws();
let Some(b) = self.utf8.get(self.i) else {
return Ok(Empty(self.prev));
};
if *b == b'q' {
self.i += 1;
self.consume_ws();
if self.i == self.utf8.len() {
Ok(Exit)
} else {
Err(InvalidQuit)
}
} else if *b == b's' {
self.i += 1;
self.consume_ws();
if self.i == self.utf8.len() {
if let Some(ref val) = *self.prev {
self.cache.push(val.clone());
}
Ok(Store(self.prev))
} else {
Err(InvalidStore)
}
} else {
self.get_adds().and_then(move |val| {
self.consume_ws();
if self.i == self.utf8.len() {
Ok(Eval(self.prev.insert(val)))
} else {
Err(TrailingSyms(self.i))
}
})
}
}
#[expect(clippy::indexing_slicing, reason = "correct")]
fn consume_ws(&mut self) {
self.i += self.utf8[self.i..]
.iter()
.try_fold(0, |val, b| match *b {
b' ' | b'\t' => Ok(val + 1),
_ => Err(val),
})
.map_or_else(convert::identity, convert::identity);
}
fn get_adds(&mut self) -> Result<Ratio<BigInt>, LangErr> {
let mut left = self.get_mults()?;
let mut j;
self.consume_ws();
while let Some(i) = self.utf8.get(self.i) {
j = *i;
self.consume_ws();
if j == b'+' {
self.i += 1;
self.consume_ws();
left += self.get_mults()?;
} else if j == b'-' {
self.i += 1;
self.consume_ws();
left -= self.get_mults()?;
} else {
break;
}
}
Ok(left)
}
fn get_mults(&mut self) -> Result<Ratio<BigInt>, LangErr> {
let mut left = self.get_neg()?;
let mut right;
let mut j;
let mut mod_val;
let mut numer;
self.consume_ws();
while let Some(i) = self.utf8.get(self.i) {
j = *i;
self.consume_ws();
if j == b'*' {
self.i += 1;
self.consume_ws();
left *= self.get_neg()?;
} else if j == b'/' {
self.i += 1;
self.consume_ws();
right = self.get_neg()?;
if right.numer().sign() == Sign::NoSign {
return Err(DivByZero(self.i));
}
left /= right;
} else if let Some(k) = self.utf8.get(self.i..self.i.saturating_add(3)) {
if k == b"mod" {
if !left.is_integer() {
return Err(ModIsNotInt(self.i));
}
self.i += 3;
self.consume_ws();
right = self.get_neg()?;
if !right.is_integer() {
return Err(ModIsNotInt(self.i));
}
numer = right.numer();
if numer.sign() == Sign::NoSign {
return Err(ModZero(self.i));
}
mod_val = left.numer() % numer;
left = Ratio::from_integer(if mod_val.sign() == Sign::Minus {
if numer.sign() == Sign::Minus {
mod_val - numer
} else {
mod_val + numer
}
} else {
mod_val
});
} else {
break;
}
} else {
break;
}
}
Ok(left)
}
fn get_neg(&mut self) -> Result<Ratio<BigInt>, LangErr> {
let mut count = 0usize;
while let Some(b) = self.utf8.get(self.i) {
if *b == b'-' {
self.i += 1;
self.consume_ws();
count += 1;
} else {
break;
}
}
self.get_exps()
.map(|val| if count & 1 == 0 { val } else { -val })
}
#[expect(
clippy::unreachable,
reason = "code that shouldn't happen did, so we want to crash"
)]
fn sqrt(val: Ratio<BigInt>) -> Option<Ratio<BigInt>> {
#[expect(clippy::suspicious_operation_groupings, reason = "false positive")]
fn calc(n: &BigUint) -> Option<BigUint> {
let mut shift = n.bits();
shift += shift & 1;
let mut result = BigUint::new(Vec::new());
let one = BigUint::new(vec![1]);
let zero = BigUint::new(Vec::new());
loop {
shift -= 2;
result <<= 1u32;
result |= &one;
result ^= if &result * &result > (n >> shift) {
&one
} else {
&zero
};
if shift == 0 {
break (&result * &result == *n).then_some(result);
}
}
}
let numer = val.numer();
if numer.sign() == Sign::NoSign {
Some(val)
} else {
numer.try_into().map_or_else(
|_| None,
|num| {
calc(&num).and_then(|n| {
calc(&val.denom().try_into().unwrap_or_else(|_| {
unreachable!("Ratio must never have a negative denominator")
}))
.map(|d| Ratio::new(n.into(), d.into()))
})
},
)
}
}
fn get_exps(&mut self) -> Result<Ratio<BigInt>, LangErr> {
let mut t = self.get_fact()?;
let ix = self.scratch.len();
let mut prev;
let mut numer;
self.scratch.push(t);
self.consume_ws();
let mut j;
let one = BigInt::new(Sign::Plus, vec![1]);
let min_one = BigInt::new(Sign::Minus, vec![1]);
let two = BigInt::new(Sign::Plus, vec![2]);
while let Some(i) = self.utf8.get(self.i) {
j = *i;
self.consume_ws();
if j == b'^' {
self.i += 1;
self.consume_ws();
t = self.get_neg()?;
prev = self.scratch.index(self.scratch.len() - 1);
numer = prev.numer();
if numer.sign() == Sign::NoSign {
if t.numer().sign() == Sign::Minus {
self.scratch.clear();
return Err(ExpDivByZero(self.i));
}
self.scratch.push(t);
} else if prev.is_integer() {
let t_numer = t.numer();
if *numer == one {
} else if t.is_integer()
|| ((*t_numer == one || *t_numer == min_one) && *t.denom() == two)
{
self.scratch.push(t);
} else {
self.scratch.clear();
return Err(ExpIsNotIntOrOneHalf(self.i));
}
} else if t.is_integer()
|| ((*t.numer() == one || *t.numer() == min_one) && *t.denom() == two)
{
self.scratch.push(t);
} else {
self.scratch.clear();
return Err(ExpIsNotIntOrOneHalf(self.i));
}
} else {
break;
}
}
self.scratch
.drain(ix..)
.try_rfold(Ratio::from_integer(one.clone()), |exp, base| {
if exp.is_integer() {
Ok(base.pow(exp.numer()))
} else if base.numer().sign() == Sign::NoSign {
Ok(base)
} else if *exp.denom() == two {
if *exp.numer() == one {
Self::sqrt(base).map_or_else(|| Err(SqrtDoesNotExist(self.i)), Ok)
} else if *exp.numer() == min_one {
Self::sqrt(base)
.map_or_else(|| Err(SqrtDoesNotExist(self.i)), |v| Ok(v.inv()))
} else {
Err(ExpIsNotIntOrOneHalf(self.i))
}
} else {
Err(ExpIsNotIntOrOneHalf(self.i))
}
})
}
fn get_fact(&mut self) -> Result<Ratio<BigInt>, LangErr> {
fn fact(mut val: BigUint) -> BigUint {
let zero = BigUint::new(Vec::new());
let one = BigUint::new(vec![1]);
let mut calc = BigUint::new(vec![1]);
while val > zero {
calc *= &val;
val -= &one;
}
calc
}
let t = self.get_term()?;
let Some(b) = self.utf8.get(self.i) else {
return Ok(t);
};
if *b == b'!' {
self.i += 1;
if t.is_integer() {
let i = self.i;
t.numer().try_into().map_or_else(
|_| Err(NotNonNegIntFact(i)),
|val| {
let mut factorial = fact(val);
while let Some(b2) = self.utf8.get(self.i) {
if *b2 == b'!' {
self.i += 1;
factorial = fact(factorial);
} else {
break;
}
}
Ok(Ratio::from_integer(BigInt::from_biguint(
Sign::Plus,
factorial,
)))
},
)
} else {
Err(NotNonNegIntFact(self.i))
}
} else {
Ok(t)
}
}
fn get_term(&mut self) -> Result<Ratio<BigInt>, LangErr> {
self.get_rational().map_or_else(Err, |o| {
o.map_or_else(
|| {
self.get_par().map_or_else(Err, |o2| {
o2.map_or_else(
|| {
self.get_recall().map_or_else(Err, |o3| {
o3.map_or_else(
|| {
self.get_abs().map_or_else(Err, |o4| {
o4.map_or_else(
|| {
self.get_round().and_then(|o5| {
o5.map_or_else(
#[cfg(not(feature = "rand"))]
|| Err(MissingTerm(self.i)),
#[cfg(feature = "rand")]
|| self.get_rand(),
Ok,
)
})
},
Ok,
)
})
},
Ok,
)
})
},
Ok,
)
})
},
Ok,
)
})
}
#[cfg(feature = "rand")]
fn get_rand(&mut self) -> Result<Ratio<BigInt>, LangErr> {
#[expect(clippy::host_endian_bytes, reason = "must keep platform endianness")]
fn rand(rng: &mut ThreadRng) -> i64 {
let mut bytes = [0; 8];
rng.fill_bytes(&mut bytes);
i64::from_ne_bytes(bytes)
}
#[expect(
clippy::integer_division_remainder_used,
reason = "need for uniform randomness"
)]
#[expect(
clippy::as_conversions,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
reason = "lossless conversions between signed integers"
)]
fn rand_range(
rng: &mut ThreadRng,
lower: &Ratio<BigInt>,
upper: &Ratio<BigInt>,
i: usize,
) -> Result<i64, LangErr> {
if lower > upper {
return Err(LangErr::RandInvalidArgs(i));
}
let lo = lower.ceil();
let up = upper.floor();
let lo_int = lo.numer();
let up_int = up.numer();
if lo_int > &BigInt::from(i64::MAX) || up_int < &BigInt::from(i64::MIN) {
return Err(LangErr::RandNoInts(i));
}
let lo_min = lo_int.to_i64().unwrap_or(i64::MIN);
let up_max = up_int.to_i64().unwrap_or(i64::MAX);
if up_max > lo_min || upper.is_integer() || lower.is_integer() {
let low = i128::from(lo_min);
let modulus = (i128::from(up_max) - low + 1) as u128;
let rem = (0x0001_0000_0000_0000_0000 % modulus) as u64;
let mut low_adj;
loop {
low_adj = rand(rng) as u64;
if low_adj >= rem {
return Ok(
((u128::from(low_adj) % modulus) as i128 + low) as i64,
);
}
}
} else {
Err(LangErr::RandNoInts(i))
}
}
let Some(b) = self.utf8.get(self.i..self.i.saturating_add(5)) else {
return Err(MissingTerm(self.i));
};
if b == b"rand(" {
self.i += 5;
self.consume_ws();
let i = self.i;
self.utf8.get(self.i).map_or_else(
|| Err(LangErr::InvalidRand(i)),
|p| {
if *p == b')' {
self.i += 1;
Ok(Ratio::from_integer(BigInt::from(rand(self.rng))))
} else {
let add = self.get_adds()?;
let Some(b2) = self.utf8.get(self.i) else {
return Err(LangErr::InvalidRand(self.i));
};
if *b2 == b',' {
self.i += 1;
self.consume_ws();
let add2 = self.get_adds()?;
self.consume_ws();
let Some(b3) = self.utf8.get(self.i) else {
return Err(LangErr::InvalidRand(self.i));
};
if *b3 == b')' {
self.i += 1;
rand_range(self.rng, &add, &add2, self.i)
.map(|v| Ratio::from_integer(BigInt::from(v)))
} else {
Err(LangErr::InvalidRand(self.i))
}
} else {
Err(LangErr::InvalidRand(self.i))
}
}
},
)
} else {
Err(MissingTerm(self.i))
}
}
fn get_round(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i..self.i.saturating_add(6)) else {
return Ok(None);
};
if b == b"round(" {
self.i += 6;
self.consume_ws();
let val = self.get_adds()?;
self.consume_ws();
let Some(b2) = self.utf8.get(self.i) else {
return Err(InvalidRound(self.i));
};
let b3 = *b2;
if b3 == b',' {
self.i += 1;
self.consume_ws();
let Some(b4) = self.utf8.get(self.i) else {
return Err(InvalidRound(self.i));
};
let r = if b4.is_ascii_digit() {
self.i += 1;
*b4 - b'0'
} else {
return Err(InvalidRound(self.i));
};
self.consume_ws();
let i = self.i;
self.utf8.get(self.i).map_or_else(
|| Err(InvalidRound(i)),
|p| {
if *p == b')' {
self.i += 1;
let mult =
BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10])).pow(r);
Ok(Some((val * &mult).round() / &mult))
} else {
Err(InvalidRound(self.i))
}
},
)
} else {
Err(InvalidRound(self.i))
}
} else {
Ok(None)
}
}
fn get_abs(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i) else {
return Ok(None);
};
if *b == b'|' {
self.i += 1;
self.consume_ws();
let r = self.get_adds()?;
self.consume_ws();
let Some(b2) = self.utf8.get(self.i) else {
return Err(InvalidAbs(self.i));
};
let b3 = *b2;
if b3 == b'|' {
self.i += 1;
Ok(Some(if r.numer().sign() == Sign::Minus {
-r
} else {
r
}))
} else {
Err(InvalidAbs(self.i))
}
} else {
Ok(None)
}
}
fn get_recall(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i) else {
return Ok(None);
};
if *b == b'@' {
self.i += 1;
self.cache
.get(self.utf8.get(self.i).map_or(0, |b2| {
if (b'1'..b'9').contains(b2) {
self.i += 1;
usize::from(*b2 - b'1')
} else {
0
}
}))
.map_or_else(
|| Err(NotEnoughPrevResults(self.cache.len())),
|p| Ok(Some(p.clone())),
)
} else {
Ok(None)
}
}
fn get_par(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i) else {
return Ok(None);
};
if *b == b'(' {
self.i += 1;
self.consume_ws();
let r = self.get_adds()?;
self.consume_ws();
let Some(b2) = self.utf8.get(self.i) else {
return Err(InvalidPar(self.i));
};
let b3 = *b2;
if b3 == b')' {
self.i += 1;
Ok(Some(r))
} else {
Err(InvalidPar(self.i))
}
} else {
Ok(None)
}
}
#[expect(clippy::indexing_slicing, reason = "correct")]
fn get_rational(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
fn to_biguint(v: &[u8]) -> (BigUint, usize) {
v.iter()
.try_fold((BigUint::new(Vec::new()), 0), |mut prev, d| {
if d.is_ascii_digit() {
prev.1 += 1;
prev.0 = prev.0 * 10u8 + (*d - b'0');
Ok(prev)
} else {
Err(prev)
}
})
.map_or_else(convert::identity, convert::identity)
}
let (int, len) = to_biguint(&self.utf8[self.i..]);
if len == 0 {
return Ok(None);
}
self.i += len;
if let Some(b) = self.utf8.get(self.i) {
if *b == b'.' {
self.i += 1;
let (numer, len2) = to_biguint(&self.utf8[self.i..]);
if len2 == 0 {
Err(InvalidDec(self.i))
} else {
self.i += len2;
Ok(Some(
Ratio::from_integer(BigInt::from_biguint(Sign::Plus, int))
+ Ratio::new(
BigInt::from_biguint(Sign::Plus, numer),
BigInt::from_biguint(Sign::Plus, BigUint::new(vec![10]).pow(len2)),
),
))
}
} else {
Ok(Some(Ratio::from_integer(BigInt::from_biguint(
Sign::Plus,
int,
))))
}
} else {
Ok(Some(Ratio::from_integer(BigInt::from_biguint(
Sign::Plus,
int,
))))
}
}
}
#[cfg(feature = "std")]
#[derive(Debug)]
pub struct EvalIter<R> {
reader: R,
input_buffer: Vec<u8>,
cache: Cache<Ratio<BigInt>, 8>,
prev: Option<Ratio<BigInt>>,
exp_buffer: Vec<Ratio<BigInt>>,
#[cfg(feature = "rand")]
rng: ThreadRng,
}
#[cfg(feature = "std")]
impl<R> EvalIter<R> {
#[cfg(feature = "rand")]
#[inline]
pub fn new(reader: R) -> Self {
Self {
reader,
input_buffer: Vec::new(),
cache: Cache::new(),
prev: None,
exp_buffer: Vec::new(),
rng: rand::rng(),
}
}
#[cfg(any(doc, not(feature = "rand")))]
#[inline]
pub fn new(reader: R) -> Self {
Self {
reader,
input_buffer: Vec::new(),
cache: Cache::new(),
prev: None,
exp_buffer: Vec::new(),
}
}
}
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
use std::io::{BufRead, Error};
#[cfg(feature = "std")]
#[derive(Debug)]
pub enum E {
Error(Error),
LangErr(LangErr),
}
#[cfg(feature = "std")]
impl Display for E {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Self::Error(ref e) => e.fmt(f),
Self::LangErr(ref e) => e.fmt(f),
}
}
}
#[cfg(feature = "std")]
use crate::lending_iterator::LendingIterator;
#[cfg(feature = "std")]
impl<R> LendingIterator for EvalIter<R>
where
R: BufRead,
{
type Item<'a>
= Result<O<'a>, E>
where
Self: 'a;
#[inline]
fn lend_next(&mut self) -> Option<Result<O<'_>, E>> {
self.input_buffer.clear();
self.exp_buffer.clear();
self.reader
.read_until(b'\n', &mut self.input_buffer)
.map_or_else(
|e| Some(Err(E::Error(e))),
|c| {
if c == 0 {
None
} else {
Evaluator::new(
self.input_buffer.as_slice(),
&mut self.cache,
&mut self.prev,
&mut self.exp_buffer,
#[cfg(feature = "rand")]
&mut self.rng,
)
.evaluate()
.map_or_else(
|e| Some(Err(E::LangErr(e))),
|o| match o {
Empty(_) | Eval(_) | Store(_) => Some(Ok(o)),
Exit => None,
},
)
}
},
)
}
}
#[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() {
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() {
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())
))))
);
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())
))))
);
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);
assert_eq!(
Evaluator::new(
vec.as_slice(),
&mut Cache::new(),
&mut None,
&mut Vec::new()
)
.get_rational(),
Ok(Some(left + right))
);
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])
))))
);
assert_eq!(
Evaluator::new(b"1.", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
Err(InvalidDec(2))
);
assert_eq!(
Evaluator::new(b"1. 2", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
Err(InvalidDec(2))
);
assert_eq!(
Evaluator::new(b"a1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
Ok(None)
);
assert_eq!(
Evaluator::new(b" 1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
Ok(None)
);
assert_eq!(
Evaluator::new(b"\t1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
Ok(None)
);
assert_eq!(
Evaluator::new(b"-1", &mut Cache::new(), &mut None, &mut Vec::new()).get_rational(),
Ok(None)
);
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])
))))
);
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() {
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() {
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)
);
assert_eq!(
Evaluator::new(b"@", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
Err(NotEnoughPrevResults(0))
);
assert_eq!(
Evaluator::new(b"@4", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
Err(NotEnoughPrevResults(0))
);
assert_eq!(
Evaluator::new(b"@0", &mut Cache::new(), &mut None, &mut Vec::new()).get_recall(),
Err(NotEnoughPrevResults(0))
);
let mut prev = None;
let mut cache = Cache::new();
_ = Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
_ = Evaluator::new(b" s \r\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
assert_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])
))))
);
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])
))))
);
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])
))))
);
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])
))))
);
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])
))))
);
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])
))))
);
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])
))))
);
assert_eq!(
Evaluator::new(b"@2", &mut cache, &mut prev, &mut Vec::new()).get_recall(),
Err(NotEnoughPrevResults(1))
);
_ = Evaluator::new(b"2\r\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
_ = Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
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])
))))
);
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[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[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' - i)])
))))
);
}
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])
))))
);
_ = Evaluator::new(b"9\r\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
_ = Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
for i in b'1'..=b'8' {
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() {
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])
))))
);
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() {
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))
);
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;
let mut vals = [0u32; 3];
let mut vec = Vec::new();
let mut cache = Cache::new();
let mut none = None;
for _ in 1..COUNT {
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;
}
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])
)))
);
#[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"))]
{
_ = Evaluator::new(b"1\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
_ = Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
}
#[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() {
assert_eq!(
Evaluator::new(b"(-1)!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
Err(NotNonNegIntFact(5))
);
assert_eq!(
Evaluator::new(b"2.5!", &mut Cache::new(), &mut None, &mut Vec::new()).get_fact(),
Err(NotNonNegIntFact(4))
);
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();
_ = Evaluator::new(b"3\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
_ = Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
assert_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])
)))
);
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])
)))
);
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])
)))
);
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])
)))
);
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])
)))
);
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])
)))
);
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))
);
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() {
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])
)))
);
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])
)))
);
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]))
))
);
assert_eq!(
Evaluator::new(b"0^(-1)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
Err(ExpDivByZero(6))
);
assert_eq!(
Evaluator::new(b"2^(1/3)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
Err(ExpIsNotIntOrOneHalf(7))
);
assert_eq!(
Evaluator::new(b"2^(1/2)", &mut Cache::new(), &mut None, &mut Vec::new()).get_exps(),
Err(SqrtDoesNotExist(7))
);
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])
)))
);
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])
)))
);
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())
)))
);
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])
)))
);
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])
)))
);
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])
)))
);
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)
})
);
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])
)))
);
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])
)))
);
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))
);
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))
);
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])
)))
);
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])
)))
);
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))
);
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();
_ = 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]))
))))
);
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])
))))
);
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))
);
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());
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) {
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());
}
}