calc_rational

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

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:
MCargo.toml | 37+++++++++++++++++++++++--------------
MREADME.md | 182++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mbuild.rs | 19++++++++++---------
Msrc/cache.rs | 37+++++++++++++++----------------------
Msrc/lending_iterator.rs | 5++++-
Msrc/lib.rs | 430+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/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`, &lt;space&gt;, &lt;tab&gt;, &lt;line feed&gt;, &lt;carriage return&gt;, 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(()) }