commit f689ae6e6b031a1ef0048a9b18ac6afc7b5a6dcd
parent 0ab49221b566b9badb330a2ef601eee0b5e638cc
Author: Zack Newman <zack@philomathiclife.com>
Date: Fri, 6 Sep 2024 18:02:02 -0600
update deps. address lints. fix compilation with certain features
Diffstat:
M | Cargo.toml | | | 37 | +++++++++++++++++++++++-------------- |
M | README.md | | | 182 | ++++++++++++++++++++++++++++++++++++++++++------------------------------------- |
M | build.rs | | | 19 | ++++++++++--------- |
M | src/cache.rs | | | 37 | +++++++++++++++---------------------- |
M | src/lending_iterator.rs | | | 5 | ++++- |
M | src/lib.rs | | | 430 | +++++++++++++++++++++++++++++++++++++++++++------------------------------------ |
M | src/main.rs | | | 28 | +++++++++++++++++++--------- |
7 files changed, 402 insertions(+), 336 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -9,7 +9,8 @@ license = "MIT OR Apache-2.0"
name = "calc_rational"
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/calc_rational/"
-version = "1.0.1"
+rust-version = "1.81.0"
+version = "2.0.0"
[lib]
name = "calc_lib"
@@ -19,31 +20,39 @@ path = "src/lib.rs"
name = "calc"
path = "src/main.rs"
+[badges]
+maintenance = { status = "actively-developed" }
+
+[package.metadata.docs.rs]
+all-features = true
+
[dependencies]
-num-bigint = { version = "0.4.4", default-features = false }
+num-bigint = { version = "0.4.6", default-features = false }
num-integer = { version = "0.1.46", default-features = false }
-num-rational = { version = "0.4.1", default-features = false, features = ["num-bigint"] }
-num-traits = { version = "0.2.18", 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.8.5", default-features = false, features = ["std", "std_rng"], optional = true }
[target.'cfg(target_os = "openbsd")'.dependencies]
-priv_sep = { version = "1.0.1", default-features = false, features = ["openbsd"], optional = true }
+priv_sep = { version = "2.1.0", default-features = false, features = ["openbsd"], optional = true }
[build-dependencies]
-rustc_version = "0.4.0"
+rustc_version = { version = "0.4.1", default-features = false }
+
+
+### FEATURES #################################################################
[features]
-priv_sep = ["dep:priv_sep"]
-rand = ["dep:rand", "std"]
-std = []
default = ["priv_sep", "std"]
-[package.metadata.docs.rs]
-all-features = true
-rustdoc-args = ["--cfg", "docsrs"]
+# Provide pledge and unveil for OpenBSD.
+priv_sep = ["dep:priv_sep"]
-[badges]
-maintenance = { status = "actively-developed" }
+# Provide random functions.
+rand = ["dep:rand", "std"]
+
+# Provide std support. This must be enabled when compiling the binary crate.
+std = []
[profile.release]
lto = true
diff --git a/README.md b/README.md
@@ -1,13 +1,13 @@
-# calc_rational
-
-calc_rational consists of a binary crate `calc` and a library crate
+# `calc_rational`
+
+`calc_rational` consists of a binary crate `calc` and a library crate
[`calc_lib`](https://docs.rs/calc_rational/latest/calc_lib). `calc` is a CLI calculator for basic
rational number arithmetic using standard operator precedence and associativity. Internally, it is
based on [`Ratio<T>`](https://docs.rs/num/latest/num/rational/struct.Ratio.html)
-and [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html).
-
-## Calc in action
-
+and [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html).
+
+## Calc in action
+
```bash
[zack@laptop ~]$ calc
2.71828^0^3.14159 + -1!
@@ -57,121 +57,133 @@ rand(1+9,10!)
q
[zack@laptop ~]$
```
-
-## Expressions
-
-The following are the list of expressions in descending order of precedence:
- 1. number literals, `@`, `()`, `||`, `round()`, `rand()`
- 2. `!`
- 3. `^`
- 4. `-` (unary negation operator)
- 5. `*`, `/`, `mod`
- 6. `+`, `-`
-
-All binary operators are left-associative sans `^` which is right-associative.
-
+
+## Expressions
+
+The following are the list of expressions in descending order of precedence:
+ 1. number literals, `@`, `()`, `||`, `round()`, `rand()`
+ 2. `!`
+ 3. `^`
+ 4. `-` (unary negation operator)
+ 5. `*`, `/`, `mod`
+ 6. `+`, `-`
+
+All binary operators are left-associative sans `^` which is right-associative.
+
Any expression is allowed to be enclosed in `()`. Note that parentheses are purely for grouping expressions;
in particular, you cannot use them to represent multiplication (e.g., `4(2)` is grammatically incorrect and
-will result in an error message).
-
-Any expression is allowed to be enclosed in `||`. This unary operator represents absolute value.
-
+will result in an error message).
+
+Any expression is allowed to be enclosed in `||`. This unary operator represents absolute value.
+
`!` is the factorial operator. Due to its high precedence, something like *-i!^j!* for *i, j ∈ ℕ* is
the same thing as *-((i!)^(j!))*. If the expression preceding it does not evaluate to a non-negative integer,
-then an error will be displayed. Spaces and tabs are *not* ignored; so `1 !` is grammatically incorrect and
-will result in an error message.
-
+then an error will be displayed. Spaces and tabs are *not* ignored; so `1 !` is grammatically incorrect and
+will result in an error message.
+
`^` is the exponentiation operator. The expression left of the operator can evaluate to any rational number;
however the expression right of the operator must evaluate to an integer or ±1/2 unless the expression on
the left evaluates to `0` or `1`. In the event of the former, the expression right of the operator must evaluate
to a non-negative rational number. In the event of the latter, the expression right of the operator can evaluate to
any rational number. Note that `0^0` is defined to be 1. When the operand right of `^` evaluates to ±1/2, then
-the left operand must be the square of a rational number.
-
-The unary operator `-` represents negation.
-
+the left operand must be the square of a rational number.
+
+The unary operator `-` represents negation.
+
The operators `*` and `/` represent multiplication and division respectively. Expressions right of `/`
-must evaluate to any non-zero rational number; otherwise an error will be displayed.
-
+must evaluate to any non-zero rational number; otherwise an error will be displayed.
+
The binary operator `mod` represents modulo such that *n mod m = r = n - m\*q* for *n,q ∈ ℤ, m ∈ ℤ\\{0}, and r ∈ ℕ*
-where *r* is the minimum non-negative solution.
-
-The binary operators `+` and `-` represent addition and subtraction respectively.
-
-With the aforementioned exception of `!`, all spaces and tabs before and after operators are ignored.
-
-## Round expression
-
+where *r* is the minimum non-negative solution.
+
+The binary operators `+` and `-` represent addition and subtraction respectively.
+
+With the aforementioned exception of `!`, all spaces and tabs before and after operators are ignored.
+
+## Round expression
+
`round(expression, digit)` rounds `expression` to `digit`-number of fractional digits. An error will
-be displayed if called incorrectly.
-
-## Rand expression
-
+be displayed if called incorrectly.
+
+## Rand expression
+
`rand(expression, expression)` generates a random 64-bit integer inclusively between the passed expressions.
-An error will be displayed if called incorrectly. `rand()` generates a random 64-bit integer.
-
-## Numbers
-
+An error will be displayed if called incorrectly. `rand()` generates a random 64-bit integer.
+
+## Numbers
+
A number literal is a non-empty sequence of digits or a non-empty sequence of digits immediately followed by `.`
which is immediately followed by a non-empty sequence of digits (e.g., `134.901`). This means that number
literals represent precisely all rational numbers that are equivalent to a ratio of a non-negative integer to
a positive integer whose sole prime factors are 2 or 5. To represent all other rational numbers, the unary
-operator `-` and binary operator `/` must be used.
-
-## Empty expression
-
+operator `-` and binary operator `/` must be used.
+
+## Empty expression
+
The empty expression (i.e., expression that at most only consists of spaces and tabs) will return
the result from the previous non-(empty/store) expression in *decimal* form using the minimum number of digits.
In the event an infinite number of digits is required, it will be rounded to 9 fractional digits using normal rounding
-rules first.
-
-## Store expression
-
+rules first.
+
+## Store expression
+
To store the result of the previous non-(empty/store) expression, one simply passes `s`. In addition to storing the
result which will subsequently be available via `@`, it displays the result. At most 8 results can be stored at once;
-at which point, results that are stored overwrite the oldest result.
-
-## Recall expression
-
+at which point, results that are stored overwrite the oldest result.
+
+## Recall expression
+
`@` is used to recall previously stored results. It can be followed by any *digit* from `1` to `8`.
If such a digit does not immediately follow it, then it will be interpreted as if there were a `1`.
`@i` returns the *i*-th most-previous stored result where *i ∈ {1, 2, 3, 4, 5, 6, 7, 8}*.
Note that spaces and tabs are *not* ignored so `@ 2` is grammatically incorrect and will result in an error message.
-As emphasized, it does not work on expressions; so both `@@` and `@(1)` are grammatically incorrect.
-
-## Character encoding
-
+As emphasized, it does not work on expressions; so both `@@` and `@(1)` are grammatically incorrect.
+
+## Character encoding
+
All inputs must only contain the ASCII encoding of the following Unicode scalar values: `0`-`9`, `.`, `+`, `-`,
`*`, `/`, `^`, `!`, `mod`, `|`, `(`, `)`, `round`, `rand`, `,`, `@`, `s`, <space>, <tab>,
<line feed>, <carriage return>, and `q`. Any other byte sequences are grammatically incorrect and will
-lead to an error message.
-
-## Errors
-
+lead to an error message.
+
+## Errors
+
Errors due to a language violation (e.g., dividing by `0`) manifest into an error message. `panic!`s
and [`io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html)s caused by writing to the global
standard output stream lead to program abortion. On OpenBSD-stable when compiled with the `priv_sep` feature,
it will error if [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) errors with the promise of `"stdio"`.
-
-## Exiting
-
-`q` with any number of spaces and tabs before and after or sending `EOF` will cause the program to terminate.
-
-### Status
-
+
+## Exiting
+
+`q` with any number of spaces and tabs before and after or sending `EOF` will cause the program to terminate.
+
+## License
+
+Licensed under either of
+
+* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0).
+* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT).
+
+at your option.
+
+## Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted inclusion in the work by you, as
+defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
+
+### Status
+
This package will be actively maintained until it is deemed “feature complete”.
There are really only two properties that will always be true. First,
the grammar that generates a “reasonable” superset of the language will
be an unambiguous context-free grammar with expression precedence and binary operator
associativity embedded within. Last, the language will only deal with the field of
-rational numbers.
-
-The crates are only tested on the `x86_64-unknown-linux-gnu` and `x86_64-unknown-openbsd` targets, but
-they should work on any [Tier 1 with Host Tools](https://doc.rust-lang.org/beta/rustc/platform-support.html)
-target. Note one must be aware of the ASCII encoding requirement. In particular there are platforms
-(e.g., Windows) where the default text encoding is not a superset of ASCII.
-
-#### Formal language specification
-
+rational numbers.
+
+The crate is only tested on `x86_64-unknown-linux-gnu` and `x86_64-unknown-openbsd` targets, but it should work
+on most platforms.
+
+#### Formal language specification
+
For a more precise specification of the “calc language”, one can read the
[calc language specification](https://git.philomathiclife.com/calc_rational/lang.pdf).
diff --git a/build.rs b/build.rs
@@ -1,12 +1,13 @@
use rustc_version::{version_meta, Channel};
-
fn main() {
- // Set cfg flags depending on release channel
- let channel = match version_meta().unwrap().channel {
- Channel::Stable => "CHANNEL_STABLE",
- Channel::Beta => "CHANNEL_BETA",
- Channel::Nightly => "CHANNEL_NIGHTLY",
- Channel::Dev => "CHANNEL_DEV",
- };
- println!("cargo:rustc-cfg={}", channel)
+ println!("cargo::rustc-check-cfg=cfg(channel_dev,channel_nightly,channel_beta,channel_stable)");
+ println!(
+ "cargo::rustc-cfg=channel_{}",
+ match version_meta().map_or(Channel::Stable, |meta| meta.channel) {
+ Channel::Dev => "dev",
+ Channel::Nightly => "nightly",
+ Channel::Beta => "beta",
+ Channel::Stable => "stable",
+ }
+ );
}
diff --git a/src/cache.rs b/src/cache.rs
@@ -38,7 +38,6 @@ impl<T> Cache<T, $x> {
/// at index `idx`. Indexing begins at the newest
/// entry (i.e., `self.get(0)` will return the most recent entry
/// iff at least one item has been cached).
- #[allow(clippy::arithmetic_side_effects)]
#[inline]
pub fn get(&self, idx: usize) -> Option<&T> {
(idx < self.len).then(|| self.vals.index(self.next_head.wrapping_sub(1).wrapping_sub(idx) & ($x - 1)))
@@ -47,19 +46,18 @@ impl<T> Cache<T, $x> {
/// `(idx % N) >= self.len()`, then `&T` references the
/// default value of `T` that was inserted at creation but *not*
/// actually inserted via `push`.
-
- /// # Safety
///
- /// `idx < self.len()`.
+ /// # Correctness
+ ///
+ /// `idx < self.len()`; otherwise a value that was not actually inserted will be returned.
#[inline]
- #[allow(unsafe_code, clippy::arithmetic_side_effects)]
- pub unsafe fn get_unsafe(&self, idx: usize) -> &T {
+ pub fn get_unchecked(&self, idx: usize) -> &T {
self.vals.index(self.next_head.wrapping_sub(1).wrapping_sub(idx) & ($x - 1))
}
/// Adds `val` to the cache. In the event `N` values have already been cached,
/// the oldest entry is overwritten.
+ #[expect(clippy::arithmetic_side_effects, reason = "must, and overflow is not possible")]
#[inline]
- #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)]
pub fn push(&mut self, val: T) {
if self.len < $x {
self.len += 1;
@@ -245,24 +243,19 @@ mod tests {
assert!(c.get(usize::MAX).is_none());
}
#[test]
- #[allow(unsafe_code)]
fn test_get_unsafe() {
let mut c = Cache::<bool, 4>::new();
- unsafe {
- assert!(!c.get_unsafe(0));
- assert!(!c.get_unsafe(1));
- assert!(!c.get_unsafe(2));
- assert!(!c.get_unsafe(3));
- assert!(!c.get_unsafe(4));
- }
+ 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);
- unsafe {
- assert!(c.get_unsafe(0));
- assert!(!c.get_unsafe(1));
- assert!(!c.get_unsafe(2));
- assert!(!c.get_unsafe(3));
- assert!(c.get_unsafe(4));
- }
+ 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));
}
#[test]
fn test_index() {
diff --git a/src/lending_iterator.rs b/src/lending_iterator.rs
@@ -12,7 +12,10 @@ pub trait LendingIterator {
(0, None)
}
/// Read [`Iterator::count`].
- #[allow(clippy::arithmetic_side_effects)]
+ #[expect(
+ clippy::arithmetic_side_effects,
+ reason = "must, and overflow is no worry"
+ )]
#[inline]
fn count(self) -> usize
where
diff --git a/src/lib.rs b/src/lib.rs
@@ -2,8 +2,7 @@
//!
//! `calc_lib` is a library for performing basic rational number arithmetic using standard operator precedence
//! and associativity. Internally, it is based on
-//! [`Ratio<T>`](https://docs.rs/num/latest/num/rational/struct.Ratio.html)
-//! and [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html).
+//! [`Ratio<T>`] and [`BigInt`].
//!
//! ## Expressions
//!
@@ -108,18 +107,21 @@
//! For a more precise specification of the “calc language”, one can read the
//! [calc language specification](https://git.philomathiclife.com/calc_rational/lang.pdf).
#![no_std]
-#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
+#![cfg_attr(all(doc, channel_nightly), feature(doc_auto_cfg))]
#![deny(
+ unknown_lints,
future_incompatible,
let_underscore,
missing_docs,
nonstandard_style,
+ refining_impl_trait,
rust_2018_compatibility,
rust_2018_idioms,
rust_2021_compatibility,
rust_2024_compatibility,
unsafe_code,
unused,
+ unused_crate_dependencies,
warnings,
clippy::all,
clippy::cargo,
@@ -132,15 +134,22 @@
clippy::style,
clippy::suspicious
)]
-#![allow(
+#![expect(
+ clippy::arithmetic_side_effects,
+ reason = "this is a calculator, so this is unavoidable"
+)]
+#![expect(
clippy::blanket_clippy_restriction_lints,
+ clippy::indexing_slicing,
+ clippy::exhaustive_enums,
clippy::implicit_return,
clippy::min_ident_chars,
clippy::missing_trait_methods,
clippy::question_mark_used,
- clippy::single_call_fn,
+ clippy::ref_patterns,
clippy::single_char_lifetime_names,
- clippy::unseparated_literal_suffix
+ clippy::unseparated_literal_suffix,
+ reason = "noisy, opinionated, and likely doesn't prevent bugs or improve APIs"
)]
extern crate alloc;
use alloc::{
@@ -149,18 +158,24 @@ use alloc::{
vec::Vec,
};
use cache::Cache;
+#[cfg(not(feature = "rand"))]
+use core::marker::PhantomData;
use core::{
convert,
fmt::{self, Display, Formatter},
ops::Index,
};
+pub use num_bigint;
use num_bigint::{BigInt, BigUint, Sign};
use num_integer::Integer;
+pub use num_rational;
use num_rational::Ratio;
#[cfg(feature = "rand")]
use num_traits::ToPrimitive;
use num_traits::{Inv, Pow};
#[cfg(feature = "rand")]
+pub use rand;
+#[cfg(feature = "rand")]
use rand::{rngs::ThreadRng, RngCore};
use LangErr::{
DivByZero, ExpDivByZero, ExpIsNotIntOrOneHalf, InvalidAbs, InvalidDec, InvalidPar, InvalidQuit,
@@ -213,7 +228,7 @@ pub enum LangErr {
/// A recall expression was used to recall the *i*-th most-recent stored result,
/// but there are fewer than *i* stored where
/// *i ∈ {1, 2, 3, 4, 5, 6, 7, 8}*.
- NotEnoughPrevResults(u8),
+ NotEnoughPrevResults(usize),
/// The input did not contain a closing `|`.
InvalidAbs(usize),
/// The input did not contain a closing `)`.
@@ -234,9 +249,6 @@ pub enum LangErr {
/// The input contained an invalid random expression.
#[cfg(feature = "rand")]
InvalidRand(usize),
- /// Error when attempting to generate a random number.
- #[cfg(feature = "rand")]
- RandErr(rand::Error),
/// Error when the second argument is less than first in the rand function.
#[cfg(feature = "rand")]
RandInvalidArgs(usize),
@@ -245,7 +257,6 @@ pub enum LangErr {
RandNoInts(usize),
}
impl Display for LangErr {
- #[allow(clippy::ref_patterns)]
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
@@ -271,8 +282,6 @@ impl Display for LangErr {
#[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::RandErr(ref e) => e.fmt(f),
- #[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}."),
@@ -280,7 +289,6 @@ impl Display for LangErr {
}
}
/// A successful evaluation of an input.
-#[allow(clippy::exhaustive_enums)]
#[derive(Debug)]
pub enum O<'a> {
/// The input only contained whitespace.
@@ -297,8 +305,11 @@ pub enum O<'a> {
Store(&'a Option<Ratio<BigInt>>),
}
impl<'a> Display for O<'a> {
+ #[expect(
+ unsafe_code,
+ reason = "manually construct guaranteed UTF-8; thus avoid the needless check"
+ )]
#[inline]
- #[allow(unsafe_code, clippy::arithmetic_side_effects, clippy::indexing_slicing)]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Empty(o) => {
@@ -395,8 +406,10 @@ impl<'a> Display for O<'a> {
}
}
}
+/// Size of [`Evaluator::cache`].
+const CACHE_SIZE: usize = 8;
/// Evaluates the supplied input.
-pub struct Evaluator<'input, 'cache, 'prev, 'scratch> {
+pub struct Evaluator<'input, 'cache, 'prev, 'scratch, 'rand> {
/// The input to be evaluated.
utf8: &'input [u8],
/// The index within `utf8` that evaluation needs to continue.
@@ -405,27 +418,53 @@ pub struct Evaluator<'input, 'cache, 'prev, 'scratch> {
/// that an error occurs.
i: usize,
/// The cache of previously stored results.
- cache: &'cache mut Cache<Ratio<BigInt>, 8>,
+ cache: &'cache mut Cache<Ratio<BigInt>, CACHE_SIZE>,
/// The last result.
prev: &'prev mut Option<Ratio<BigInt>>,
/// Buffer used to evaluate right-associative sub-expressions.
scratch: &'scratch mut Vec<Ratio<BigInt>>,
+ /// Random number generator.
+ #[cfg(feature = "rand")]
+ rng: &'rand mut ThreadRng,
+ #[cfg(not(feature = "rand"))]
+ _rng: PhantomData<fn() -> &'rand ()>,
}
-impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch> {
+impl<'input, 'cache, 'prev, 'scratch, 'rand> Evaluator<'input, 'cache, 'prev, 'scratch, 'rand> {
+ /// Creates an `Evaluator<'input, 'cache, 'prev, 'scratch, 'rand>` based on the supplied arguments.
+ #[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,
+ }
+ }
+ /// Creates an `Evaluator<'input, 'cache, 'prev, 'scratch, 'rand>` based on the supplied arguments.
+ #[cfg(feature = "rand")]
#[inline]
- /// Creates an `Evaluator<'input, 'cache, 'prev, 'scratch>` based on the supplied arguments.
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>>,
- ) -> Evaluator<'input, 'cache, 'prev, 'scratch> {
+ rng: &'rand mut ThreadRng,
+ ) -> Self {
Self {
utf8,
i: 0,
cache,
prev,
scratch,
+ rng,
}
}
/// Evaluates the input consuming the `Evaluator<'input, 'cache, 'exp>`.
@@ -436,11 +475,6 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
///
/// Returns a [`LangErr`] iff the input violates the calc language.
#[inline]
- #[allow(
- clippy::arithmetic_side_effects,
- clippy::indexing_slicing,
- clippy::ref_patterns
- )]
pub fn evaluate(mut self) -> Result<O<'prev>, LangErr> {
self.utf8 = if self.utf8.last().map_or(true, |b| *b != b'\n') {
self.utf8
@@ -486,18 +520,12 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
}
}
/// Reads from the input until the next non-{space/tab} byte value.
- #[inline]
- #[allow(
- clippy::arithmetic_side_effects,
- clippy::indexing_slicing,
- clippy::into_iter_on_ref
- )]
fn consume_ws(&mut self) {
// ControlFlow makes more sense to use in try_fold; however due to a lack
// of a map_or_else function, it is easier to simply return a Result with
// Err taking the role of ControlFlow::Break.
self.i += self.utf8[self.i..]
- .into_iter()
+ .iter()
.try_fold(0, |val, b| match *b {
b' ' | b'\t' => Ok(val + 1),
_ => Err(val),
@@ -507,8 +535,6 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
/// Evaluates addition expressions as defined in the calc language.
/// This function is used for both addition and subtraction operations which
/// themselves are based on multiplication expressions.
- #[allow(clippy::arithmetic_side_effects)]
- #[inline]
fn get_adds(&mut self) -> Result<Ratio<BigInt>, LangErr> {
let mut left = self.get_mults()?;
let mut j;
@@ -533,8 +559,6 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
/// Evaluates multiplication expressions as defined in the calc language.
/// This function is used for both multiplication and division operations which
/// themselves are based on negation expressions.
- #[inline]
- #[allow(clippy::arithmetic_side_effects, clippy::else_if_without_else)]
fn get_mults(&mut self) -> Result<Ratio<BigInt>, LangErr> {
let mut left = self.get_neg()?;
let mut right;
@@ -593,8 +617,6 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
}
/// Evaluates negation expressions as defined in the calc language.
/// This function is based on exponentiation expressions.
- #[allow(clippy::arithmetic_side_effects)]
- #[inline]
fn get_neg(&mut self) -> Result<Ratio<BigInt>, LangErr> {
let mut count = 0usize;
while let Some(b) = self.utf8.get(self.i) {
@@ -610,14 +632,15 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
.map(|val| if count & 1 == 0 { val } else { -val })
}
/// Gets the square root of value so long as a solution exists.
- #[inline]
- #[allow(clippy::arithmetic_side_effects, clippy::unwrap_used)]
+ #[expect(
+ clippy::unreachable,
+ reason = "code that shouldn't happen did, so we want to crash"
+ )]
fn sqrt(val: Ratio<BigInt>) -> Option<Ratio<BigInt>> {
/// Returns the square root of `n` if one exists; otherwise
/// returns `None`.
- // MUST NOT pass 0.
- #[inline]
- #[allow(clippy::suspicious_operation_groupings)]
+ /// MUST NOT pass 0.
+ #[expect(clippy::suspicious_operation_groupings, reason = "false positive")]
fn calc(n: &BigUint) -> Option<BigUint> {
let mut shift = n.bits();
shift += shift & 1;
@@ -646,8 +669,10 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
|_| None,
|num| {
calc(&num).and_then(|n| {
- calc(&val.denom().try_into().unwrap())
- .map(|d| Ratio::new(n.into(), d.into()))
+ calc(&val.denom().try_into().unwrap_or_else(|_| {
+ unreachable!("Ratio must never have a negative denominator")
+ }))
+ .map(|d| Ratio::new(n.into(), d.into()))
})
},
)
@@ -655,8 +680,6 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
}
/// Evaluates exponentiation expressions as defined in the calc language.
/// This function is based on negation expressions.
- #[allow(clippy::arithmetic_side_effects)]
- #[inline]
fn get_exps(&mut self) -> Result<Ratio<BigInt>, LangErr> {
let mut t = self.get_fact()?;
let ix = self.scratch.len();
@@ -734,11 +757,8 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
}
/// Evaluates factorial expressions as defined in the calc language.
/// This function is based on terminal expressions.
- #[allow(clippy::arithmetic_side_effects)]
- #[inline]
fn get_fact(&mut self) -> Result<Ratio<BigInt>, LangErr> {
/// Calculates the factorial of `val`.
- #[inline]
fn fact(mut val: BigUint) -> BigUint {
let zero = BigUint::new(Vec::new());
let one = BigUint::new(vec![1]);
@@ -788,7 +808,6 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
/// This function is based on number literal expressions, parenthetical expressions,
/// recall expressions, absolute value expressions, round expressions, and possibly
/// rand expressions if that feature is enabled.
- #[inline]
fn get_term(&mut self) -> Result<Ratio<BigInt>, LangErr> {
self.get_rational().map_or_else(Err, |o| {
o.map_or_else(
@@ -828,41 +847,36 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
)
})
}
- /// Generates a random 64-bit integer.
- /// This function is based on add expressions.
- /// This is the last terminal expression attempted when needing
- /// a terminal expression; as a result, it is the only terminal expression
+ /// Generates a random 64-bit integer. This function is based on add expressions. This is the last terminal
+ /// expression attempted when needing a terminal expression; as a result, it is the only terminal expression
/// that does not return an `Option`.
#[cfg(feature = "rand")]
- #[allow(
- clippy::arithmetic_side_effects,
- clippy::host_endian_bytes,
- clippy::too_many_lines
- )]
- #[inline]
fn get_rand(&mut self) -> Result<Ratio<BigInt>, LangErr> {
/// Generates a random 64-bit integer.
- #[inline]
- fn rand(rng: &mut ThreadRng) -> Result<i64, rand::Error> {
- let mut bytes = [0u8; 8];
- rng.try_fill_bytes(&mut bytes)
- .map(|()| i64::from_ne_bytes(bytes))
+ #[expect(clippy::host_endian_bytes, reason = "must keep platform endianness")]
+ fn rand(rng: &mut ThreadRng) -> i64 {
+ let mut bytes = [0; 8];
+ // `ThreadRng::try_fill_bytes` is infallible, so easier to call `fill_bytes`.
+ rng.fill_bytes(&mut bytes);
+ i64::from_ne_bytes(bytes)
}
/// Generates a random 64-bit integer inclusively between the passed arguments.
- #[allow(
- clippy::arithmetic_side_effects,
+ #[expect(clippy::single_call_fn, reason = "easier to reason about")]
+ #[expect(
+ clippy::integer_division_remainder_used,
+ reason = "need for uniform randomness"
+ )]
+ #[expect(
clippy::as_conversions,
- clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
- clippy::needless_pass_by_value
+ reason = "lossless conversions between signed integers"
)]
- #[inline]
fn rand_range(
rng: &mut ThreadRng,
- lower: Ratio<BigInt>,
- upper: Ratio<BigInt>,
+ lower: &Ratio<BigInt>,
+ upper: &Ratio<BigInt>,
i: usize,
) -> Result<i64, LangErr> {
if lower > upper {
@@ -878,33 +892,27 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
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 = lo_min as i128;
- // range is [1, 2^64].
- let modulus = (up_max as i128 - low + 1) as u128;
- // range is [0, i64::MAX].
+ let low = i128::from(lo_min);
+ // `i64::MAX >= up_max >= low`; so underflow and overflow cannot happen.
+ // range is [1, 2^64] so casting to a u128 is fine.
+ let modulus = (i128::from(up_max) - low + 1) as u128;
+ // range is [0, i64::MAX] so converting to a `u64` is fine.
// rem represents how many values need to be removed
// when generating a random i64 in order for uniformity.
let rem = (0x0001_0000_0000_0000_0000 % modulus) as u64;
let mut low_adj;
loop {
- match rand(rng) {
- Ok(v) => {
- low_adj = v as u64;
- // Since rem is in [0, i64::MAX],
- // this is the same as low_adj < 0
- // || low_adj >= rem.
- if low_adj >= rem {
- return Ok(
- // range is [i64::MIN, i64::MAX]; thus casts are safe.
- // modulus is up_max - low + 1; so as low grows,
- // % shrinks by the same factor. i64::MAX happens
- // when low = up_max = i64::MAX or when low = 0,
- // up_max = i64::MAX and low_adj is i64::MAX.
- ((low_adj as u128 % modulus) as i128 + low) as i64,
- );
- }
- }
- Err(e) => return Err(LangErr::RandErr(e)),
+ low_adj = rand(rng) as u64;
+ // Since rem is in [0, i64::MAX], this is the same as low_adj < 0 || low_adj >= rem.
+ if low_adj >= rem {
+ return Ok(
+ // range is [i64::MIN, i64::MAX]; thus casts are safe.
+ // modulus is up_max - low + 1; so as low grows,
+ // % shrinks by the same factor. i64::MAX happens
+ // when low = up_max = i64::MAX or when low = 0,
+ // up_max = i64::MAX and low_adj is i64::MAX.
+ ((u128::from(low_adj) % modulus) as i128 + low) as i64,
+ );
}
}
} else {
@@ -925,10 +933,7 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
|p| {
if *p == b')' {
self.i += 1;
- rand(&mut rand::thread_rng()).map_or_else(
- |e| Err(LangErr::RandErr(e)),
- |v| Ok(Ratio::from_integer(BigInt::from(v))),
- )
+ Ok(Ratio::from_integer(BigInt::from(rand(self.rng))))
} else {
let add = self.get_adds()?;
let Some(b2) = self.utf8.get(self.i) else {
@@ -944,7 +949,7 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
};
if *b3 == b')' {
self.i += 1;
- rand_range(&mut rand::thread_rng(), add, add2, self.i)
+ rand_range(self.rng, &add, &add2, self.i)
.map(|v| Ratio::from_integer(BigInt::from(v)))
} else {
Err(LangErr::InvalidRand(self.i))
@@ -961,8 +966,6 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
}
/// Rounds a value to the specified number of fractional digits.
/// This function is based on add expressions.
- #[allow(clippy::arithmetic_side_effects)]
- #[inline]
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);
@@ -1012,8 +1015,6 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
}
/// Evaluates absolute value expressions as defined in the calc language.
/// This function is based on add expressions.
- #[allow(clippy::arithmetic_side_effects)]
- #[inline]
fn get_abs(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i) else {
return Ok(None);
@@ -1046,12 +1047,6 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
// since the only place this function is called is in get_term which
// would end up needing to clone the Ratio anyway. By not forcing
// get_term to clone, it can rely on map_or_else over match expressions.
- #[inline]
- #[allow(
- clippy::arithmetic_side_effects,
- clippy::as_conversions,
- clippy::cast_possible_truncation
- )]
fn get_recall(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i) else {
return Ok(None);
@@ -1062,13 +1057,13 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
.get(self.utf8.get(self.i).map_or(0, |b2| {
if (b'1'..b'9').contains(b2) {
self.i += 1;
- (*b2 - b'1') as usize
+ usize::from(*b2 - b'1')
} else {
0
}
}))
.map_or_else(
- || Err(NotEnoughPrevResults(self.cache.len() as u8)),
+ || Err(NotEnoughPrevResults(self.cache.len())),
|p| Ok(Some(p.clone())),
)
} else {
@@ -1077,8 +1072,6 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
}
/// Evaluates parenthetical expressions as defined in the calc language.
/// This function is based on add expressions.
- #[allow(clippy::arithmetic_side_effects)]
- #[inline]
fn get_par(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i) else {
return Ok(None);
@@ -1103,22 +1096,13 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
}
}
/// Evaluates number literal expressions as defined in the calc language.
- #[inline]
- #[allow(
- clippy::arithmetic_side_effects,
- clippy::as_conversions,
- clippy::cast_lossless,
- clippy::indexing_slicing
- )]
fn get_rational(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
// ControlFlow makes more sense to use in try_fold; however due to a lack
// of a map_or_else function, it is easier to simply return a Result with
// Err taking the role of ControlFlow::Break.
/// Used to parse a sequence of digits into an unsigned integer.
- #[allow(clippy::arithmetic_side_effects, clippy::into_iter_on_ref)]
- #[inline]
fn to_biguint(v: &[u8]) -> (BigUint, usize) {
- v.into_iter()
+ v.iter()
.try_fold((BigUint::new(Vec::new()), 0), |mut prev, d| {
if d.is_ascii_digit() {
prev.1 += 1;
@@ -1167,7 +1151,7 @@ impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch>
}
}
/// Reads data from `R` passing each line to an [`Evaluator`] to be evaluated.
-#[allow(unused)]
+#[cfg(feature = "std")]
pub struct EvalIter<R> {
/// Reader that contains input data.
reader: R,
@@ -1185,6 +1169,7 @@ pub struct EvalIter<R> {
#[cfg(feature = "rand")]
rng: ThreadRng,
}
+#[cfg(feature = "std")]
impl<R> EvalIter<R> {
/// Creates a new `EvalIter`.
#[inline]
@@ -1201,7 +1186,7 @@ impl<R> EvalIter<R> {
}
/// Creates a new `EvalIter`.
#[inline]
- #[cfg(not(feature = "rand"))]
+ #[cfg(any(doc, not(feature = "rand")))]
pub fn new(reader: R) -> Self {
Self {
reader,
@@ -1217,9 +1202,7 @@ extern crate std;
#[cfg(feature = "std")]
use std::io::{BufRead, Error};
#[cfg(feature = "std")]
-/// Error returned from [`EvalIter`] when
-/// an expression has an error.
-#[allow(clippy::exhaustive_enums)]
+/// Error returned from [`EvalIter`] when an expression has an error.
#[derive(Debug)]
pub enum E {
/// Error containing [`Error`] which is returned
@@ -1232,7 +1215,6 @@ pub enum E {
}
#[cfg(feature = "std")]
impl Display for E {
- #[allow(clippy::ref_patterns)]
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
@@ -1266,6 +1248,8 @@ where
&mut self.cache,
&mut self.prev,
&mut self.exp_buffer,
+ #[cfg(feature = "rand")]
+ &mut self.rng,
)
.evaluate()
.map_or_else(
@@ -1282,12 +1266,8 @@ where
}
#[cfg(test)]
mod tests {
- use super::LangErr::{
- DivByZero, ExpDivByZero, ExpIsNotIntOrOneHalf, InvalidAbs, InvalidDec, InvalidPar,
- InvalidQuit, InvalidRound, MissingTerm, ModIsNotInt, ModZero, NotEnoughPrevResults,
- NotNonNegIntFact, TrailingSyms,
- };
use super::*;
+ #[cfg(not(feature = "rand"))]
#[test]
fn empty() {
// Empty expressions without a previous result return nothing.
@@ -1406,6 +1386,7 @@ mod tests {
== "> -20.3"
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn number_literal() {
// Normal 0 is fine.
@@ -1543,6 +1524,7 @@ mod tests {
== Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![130])))
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn par() {
// Missing closing ')'
@@ -1594,6 +1576,7 @@ mod tests {
== Ratio::from_integer(BigInt::from_biguint(Sign::Minus, BigUint::new(vec![27])))
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn recall_expression() {
// If the input does not start with '@', then it's valid but produces nothing.
@@ -1811,6 +1794,7 @@ mod tests {
);
}
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn abs() {
// Missing closing '|'
@@ -1874,6 +1858,7 @@ mod tests {
.is_none()
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn round() {
// Missing ',<digit>)'
@@ -1954,32 +1939,41 @@ mod tests {
))
);
}
- #[test]
#[cfg(feature = "rand")]
+ #[test]
fn rand() {
- assert!(
- match Evaluator::new(b"rand(1", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_rand()
- .unwrap_err()
- {
- LangErr::InvalidRand(i) => i == 6,
- _ => false,
- }
- );
- assert!(
- match Evaluator::new(b"rand(1,2", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_rand()
- .unwrap_err()
- {
- LangErr::InvalidRand(i) => i == 8,
- _ => false,
- }
- );
+ assert!(match Evaluator::new(
+ b"rand(1",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::thread_rng()
+ )
+ .get_rand()
+ .unwrap_err()
+ {
+ LangErr::InvalidRand(i) => i == 6,
+ _ => false,
+ });
+ assert!(match Evaluator::new(
+ b"rand(1,2",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::thread_rng()
+ )
+ .get_rand()
+ .unwrap_err()
+ {
+ LangErr::InvalidRand(i) => i == 8,
+ _ => false,
+ });
assert!(match Evaluator::new(
b"rand(1/2,3/4)",
&mut Cache::new(),
&mut None,
- &mut Vec::new()
+ &mut Vec::new(),
+ &mut rand::thread_rng(),
)
.get_rand()
.unwrap_err()
@@ -1991,7 +1985,8 @@ mod tests {
b"rand(-100000000000000000000000,-1000000000000000000000)",
&mut Cache::new(),
&mut None,
- &mut Vec::new()
+ &mut Vec::new(),
+ &mut rand::thread_rng(),
)
.get_rand()
.unwrap_err()
@@ -2003,7 +1998,8 @@ mod tests {
b"rand(2/3,1/3)",
&mut Cache::new(),
&mut None,
- &mut Vec::new()
+ &mut Vec::new(),
+ &mut rand::thread_rng(),
)
.get_rand()
.unwrap_err()
@@ -2016,7 +2012,8 @@ mod tests {
b" rand(2/3,2)",
&mut Cache::new(),
&mut None,
- &mut Vec::new()
+ &mut Vec::new(),
+ &mut rand::thread_rng(),
)
.get_rand()
.unwrap_err()
@@ -2024,32 +2021,44 @@ mod tests {
MissingTerm(i) => i == 0,
_ => false,
});
- assert!(
- Evaluator::new(b"rand(2, 7)", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_rand()
- .map(|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]))
- })
- .unwrap()
- );
- assert!(
- Evaluator::new(b"rand()", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_rand()
- .map(|r| {
- let int = r.numer();
- int >= &BigInt::from(i64::MIN) && *int <= BigInt::from(i64::MAX)
- })
- .unwrap()
- );
+ assert!(Evaluator::new(
+ b"rand(2, 7)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::thread_rng()
+ )
+ .get_rand()
+ .map(|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]))
+ })
+ .unwrap());
+ assert!(Evaluator::new(
+ b"rand()",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::thread_rng()
+ )
+ .get_rand()
+ .map(|r| {
+ let int = r.numer();
+ int >= &BigInt::from(i64::MIN) && *int <= BigInt::from(i64::MAX)
+ })
+ .unwrap());
for _ in 0..100 {
- assert!(
- Evaluator::new(b"rand(2, 2)", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_rand()
- .map(|r| *r.numer() == BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))
- .unwrap()
- );
+ assert!(Evaluator::new(
+ b"rand(2, 2)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::thread_rng()
+ )
+ .get_rand()
+ .map(|r| *r.numer() == BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))
+ .unwrap());
}
}
#[cfg(feature = "rand")]
@@ -2064,12 +2073,18 @@ mod tests {
let mut none = None;
const COUNT: u32 = 999999;
for _ in 1..COUNT {
- vals[(Evaluator::new(b"rand(-1, 1)", &mut cache, &mut none, &mut vec)
- .get_rand()
- .unwrap()
- .numer()
- .to_i32()
- .unwrap()
+ vals[(Evaluator::new(
+ b"rand(-1, 1)",
+ &mut cache,
+ &mut none,
+ &mut vec,
+ &mut rand::thread_rng(),
+ )
+ .get_rand()
+ .unwrap()
+ .numer()
+ .to_i32()
+ .unwrap()
+ 1) as usize] += 1;
}
// Test that the distribution is within 1% of what is expected.
@@ -2086,6 +2101,7 @@ mod tests {
}
#[test]
fn term() {
+ #[cfg(not(feature = "rand"))]
assert!(
Evaluator::new(b"0000.00000", &mut Cache::new(), &mut None, &mut Vec::new())
.get_term()
@@ -2095,12 +2111,14 @@ mod tests {
BigUint::new(Vec::new())
))
);
+ #[cfg(not(feature = "rand"))]
assert!(
Evaluator::new(b"(4)", &mut Cache::new(), &mut None, &mut Vec::new())
.get_term()
.unwrap()
== Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])))
);
+ #[cfg(not(feature = "rand"))]
assert!(
Evaluator::new(
b"round(-2/3,2)",
@@ -2116,17 +2134,22 @@ mod tests {
)
);
#[cfg(feature = "rand")]
- assert!(
- Evaluator::new(b"rand()", &mut Cache::new(), &mut None, &mut Vec::new())
- .get_term()
- .is_ok()
- );
+ assert!(Evaluator::new(
+ b"rand()",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::thread_rng()
+ )
+ .get_term()
+ .is_ok());
#[cfg(feature = "rand")]
assert!(Evaluator::new(
b"rand(-13/93, 833)",
&mut Cache::new(),
&mut None,
- &mut Vec::new()
+ &mut Vec::new(),
+ &mut rand::thread_rng(),
)
.get_term()
.is_ok());
@@ -2140,6 +2163,7 @@ mod tests {
_ => false,
}
);
+ #[cfg(not(feature = "rand"))]
assert!(
Evaluator::new(b"|4|", &mut Cache::new(), &mut None, &mut Vec::new())
.get_term()
@@ -2147,6 +2171,7 @@ mod tests {
== 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!(
match Evaluator::new(b" 2", &mut Cache::new(), &mut None, &mut Vec::new())
.get_term()
@@ -2156,14 +2181,19 @@ mod tests {
_ => false,
}
);
+ #[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();
+ #[cfg(not(feature = "rand"))]
Evaluator::new(b"s\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
.unwrap();
+ #[cfg(not(feature = "rand"))]
assert!(
Evaluator::new(b"@", &mut cache, &mut prev, &mut Vec::new())
.get_term()
@@ -2171,6 +2201,7 @@ mod tests {
== Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn factorial() {
// Negative integer is not allowed.
@@ -2297,6 +2328,7 @@ mod tests {
}
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn exp() {
// 1 can be raised to anything and return 1.
@@ -2562,6 +2594,7 @@ mod tests {
}
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn neg() {
assert!(
@@ -2619,6 +2652,7 @@ mod tests {
}
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn mult() {
assert!(
@@ -2837,6 +2871,7 @@ mod tests {
== Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8])))
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn add() {
assert!(
@@ -2961,6 +2996,7 @@ mod tests {
== Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn exit() {
assert!(
@@ -3048,6 +3084,7 @@ mod tests {
}
);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn store() {
let mut prev = None;
@@ -3072,6 +3109,7 @@ mod tests {
);
assert!(cache.len() == 1);
}
+ #[cfg(not(feature = "rand"))]
#[test]
fn eval() {
use core::str::FromStr;
@@ -3272,8 +3310,8 @@ mod tests {
}
);
}
- #[test]
#[cfg(feature = "rand")]
+ #[test]
fn eval_iter() {
use super::*;
use num_traits::ToPrimitive;
diff --git a/src/main.rs b/src/main.rs
@@ -1,12 +1,14 @@
//! # `calc`
//!
//! Consult [`README.md`](https://crates.io/crates/calc_rational).
-#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
+#![cfg_attr(all(doc, channel_nightly), feature(doc_auto_cfg))]
#![deny(
+ unknown_lints,
future_incompatible,
let_underscore,
missing_docs,
nonstandard_style,
+ refining_impl_trait,
rust_2018_compatibility,
rust_2018_idioms,
rust_2021_compatibility,
@@ -30,19 +32,23 @@
clippy::implicit_return,
clippy::min_ident_chars,
clippy::missing_trait_methods,
- clippy::question_mark_used,
- clippy::single_call_fn
+ clippy::ref_patterns,
+ clippy::single_call_fn,
+ reason = "noisy, opinionated, and likely doesn't prevent bugs or improve APIs"
)]
+#[cfg(feature = "std")]
use calc_lib::{lending_iterator::LendingIterator, EvalIter};
+#[cfg(feature = "std")]
use core::fmt::{self, Display, Formatter};
#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
use priv_sep::{Promise, Promises};
+#[cfg(feature = "std")]
use std::{
error,
io::{self, Error, Write},
};
/// Error returned by [`main`].
-#[allow(clippy::exhaustive_enums)]
+#[cfg(feature = "std")]
#[derive(Debug)]
enum Err {
/// Error returned from [`writeln`].
@@ -51,9 +57,8 @@ enum Err {
/// Error returned from [`pledge`].
Pledge(Error),
}
+#[cfg(feature = "std")]
impl Display for Err {
- #[allow(clippy::ref_patterns)]
- #[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Self::Io(ref e) => e.fmt(f),
@@ -62,7 +67,10 @@ impl Display for Err {
}
}
}
+#[cfg(feature = "std")]
impl error::Error for Err {}
+#[cfg(not(feature = "std"))]
+fn main() {}
/// Entry point to the calc program.
///
/// # Errors
@@ -72,19 +80,21 @@ impl error::Error for Err {}
/// [`pledge`](https://docs.rs/priv_sep/latest/priv_sep/fn.pledge.html)
/// does when compiled with the `priv_sep` feature which
/// currently only works on OpenBSD-stable.
+#[cfg(feature = "std")]
fn main() -> Result<(), Err> {
/// Calls `pledge(2)` with the "stdio" promise.
#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
- #[inline]
fn privsep() -> Result<(), Err> {
Promises::new([Promise::Stdio])
.pledge()
.map_err(Err::Pledge)
}
/// Returns Ok.
- #[allow(clippy::unnecessary_wraps)]
+ #[expect(
+ clippy::unnecessary_wraps,
+ reason = "consistent return type with priv_sep feature"
+ )]
#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
- #[inline]
const fn privsep() -> Result<(), Err> {
Ok(())
}