#![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;
#[cfg(test)]
mod tests;
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),
})
.unwrap_or_else(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)
}
})
.unwrap_or_else(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,
},
)
}
},
)
}
}