commit 6cb9cce1c37157d0f9ea032c86f14e3bac44c933
parent f689ae6e6b031a1ef0048a9b18ac6afc7b5a6dcd
Author: Zack Newman <zack@philomathiclife.com>
Date: Tue, 25 Feb 2025 08:36:47 -0700
rust 2024
Diffstat:
6 files changed, 175 insertions(+), 129 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -3,14 +3,14 @@ authors = ["Zack Newman <zack@philomathiclife.com>"]
categories = ["algorithms", "mathematics", "science", "command-line-utilities"]
description = "CLI calculator for rational numbers."
documentation = "https://crates.io/crates/calc_rational"
-edition = "2021"
+edition = "2024"
keywords = ["calculator", "mathematics", "numerics"]
license = "MIT OR Apache-2.0"
name = "calc_rational"
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/calc_rational/"
-rust-version = "1.81.0"
-version = "2.0.0"
+rust-version = "1.85.0"
+version = "2.1.0"
[lib]
name = "calc_lib"
@@ -20,33 +20,28 @@ path = "src/lib.rs"
name = "calc"
path = "src/main.rs"
-[badges]
-maintenance = { status = "actively-developed" }
-
[package.metadata.docs.rs]
all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
num-bigint = { version = "0.4.6", default-features = false }
num-integer = { version = "0.1.46", default-features = false }
num-rational = { version = "0.4.2", default-features = false, features = ["num-bigint"] }
num-traits = { version = "0.2.19", default-features = false }
-rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"], optional = true }
+rand = { version = "0.9.0", default-features = false, features = ["thread_rng"], optional = true }
[target.'cfg(target_os = "openbsd")'.dependencies]
-priv_sep = { version = "2.1.0", default-features = false, features = ["openbsd"], optional = true }
-
-[build-dependencies]
-rustc_version = { version = "0.4.1", default-features = false }
+priv_sep = { version = "2.2.0", default-features = false, features = ["openbsd"], optional = true }
### FEATURES #################################################################
[features]
-default = ["priv_sep", "std"]
+default = ["priv_sep"]
# Provide pledge and unveil for OpenBSD.
-priv_sep = ["dep:priv_sep"]
+priv_sep = ["dep:priv_sep", "std"]
# Provide random functions.
rand = ["dep:rand", "std"]
diff --git a/README.md b/README.md
@@ -1,5 +1,9 @@
# `calc_rational`
+[<img alt="git" src="https://git.philomathiclife.com/badges/calc_rational.svg" height="20">](https://git.philomathiclife.com/calc_rational/log.html)
+[<img alt="crates.io" src="https://img.shields.io/crates/v/calc_rational.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/calc_rational)
+[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-calc_rational-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/calc_rational/latest/calc_lib/)
+
`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
@@ -157,19 +161,38 @@ it will error if [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) errors wi
`q` with any number of spaces and tabs before and after or sending `EOF` will cause the program to terminate.
+## Minimum Supported Rust Version (MSRV)
+
+This will frequently be updated to be the same as stable. Specifically, any time stable is updated and that
+update has "useful" features or compilation no longer succeeds (e.g., due to new compiler lints), then MSRV
+will be updated.
+
+MSRV changes will correspond to a SemVer minor version bump.
+
+## SemVer Policy
+
+* All on-by-default features of this library are covered by SemVer
+* MSRV is considered exempt from SemVer as noted above
+
## 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).
+* Apache License, Version 2.0 ([LICENSE-APACHE](https://www.apache.org/licenses/LICENSE-2.0))
+* MIT license ([LICENSE-MIT](https://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.
+Unless you explicitly state otherwise, any contribution intentionally submitted for 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.
+
+Before any PR is sent, `cargo clippy` and `cargo t` should be run _for each possible combination of "features"_
+using stable Rust. One easy way to achieve this is by building `ci` and invoking it with no commands in the
+`calc_rational` directory or sub-directories. You can fetch `ci` via `git clone https://git.philomathiclife.com/repos/ci`,
+and it can be built with `cargo build --release`. Additionally,
+`RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` should be run to ensure documentation can be built.
### Status
diff --git a/build.rs b/build.rs
@@ -1,13 +0,0 @@
-use rustc_version::{version_meta, Channel};
-fn main() {
- 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/lending_iterator.rs b/src/lending_iterator.rs
@@ -24,7 +24,7 @@ pub trait LendingIterator {
self.lend_fold(0, |count, _| count + 1)
}
/// Read [`Iterator::advance_by`].
-
+ ///
/// # Errors
///
/// Read [`Iterator::advance_by`].
@@ -77,7 +77,10 @@ impl<T> LendingIterator for T
where
T: Iterator,
{
- type Item<'a> = T::Item where Self: 'a;
+ type Item<'a>
+ = T::Item
+ where
+ Self: 'a;
#[inline]
fn lend_next(&mut self) -> Option<Self::Item<'_>> {
self.next()
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,5 +1,9 @@
-//! # `calc_lib`
-//!
+//! [![git]](https://git.philomathiclife.com/calc_rational/log.html) [![crates-io]](https://crates.io/crates/calc_rational) [![docs-rs]](crate)
+//!
+//! [git]: https://git.philomathiclife.com/git_badge.svg
+//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
+//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
+//!
//! `calc_lib` is a library for performing basic rational number arithmetic using standard operator precedence
//! and associativity. Internally, it is based on
//! [`Ratio<T>`] and [`BigInt`].
@@ -107,7 +111,7 @@
//! 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(docsrs, feature(doc_cfg))]
#![deny(
unknown_lints,
future_incompatible,
@@ -121,7 +125,6 @@
rust_2024_compatibility,
unsafe_code,
unused,
- unused_crate_dependencies,
warnings,
clippy::all,
clippy::cargo,
@@ -139,12 +142,14 @@
reason = "this is a calculator, so this is unavoidable"
)]
#![expect(
+ clippy::arbitrary_source_item_ordering,
clippy::blanket_clippy_restriction_lints,
clippy::indexing_slicing,
clippy::exhaustive_enums,
clippy::implicit_return,
clippy::min_ident_chars,
clippy::missing_trait_methods,
+ clippy::pub_use,
clippy::question_mark_used,
clippy::ref_patterns,
clippy::single_char_lifetime_names,
@@ -152,8 +157,14 @@
reason = "noisy, opinionated, and likely doesn't prevent bugs or improve APIs"
)]
extern crate alloc;
+use LangErr::{
+ DivByZero, ExpDivByZero, ExpIsNotIntOrOneHalf, InvalidAbs, InvalidDec, InvalidPar, InvalidQuit,
+ InvalidRound, InvalidStore, MissingTerm, ModIsNotInt, ModZero, NotEnoughPrevResults,
+ NotNonNegIntFact, SqrtDoesNotExist, TrailingSyms,
+};
+use O::{Empty, Eval, Exit, Store};
use alloc::{
- string::{String, ToString},
+ string::{String, ToString as _},
vec,
vec::Vec,
};
@@ -163,28 +174,25 @@ use core::marker::PhantomData;
use core::{
convert,
fmt::{self, Display, Formatter},
- ops::Index,
+ ops::Index as _,
};
pub use num_bigint;
use num_bigint::{BigInt, BigUint, Sign};
-use num_integer::Integer;
+use num_integer::Integer as _;
pub use num_rational;
use num_rational::Ratio;
#[cfg(feature = "rand")]
-use num_traits::ToPrimitive;
-use num_traits::{Inv, Pow};
+use num_traits::ToPrimitive as _;
+use num_traits::{Inv as _, Pow as _};
+#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
#[cfg(feature = "rand")]
pub use rand;
#[cfg(feature = "rand")]
-use rand::{rngs::ThreadRng, RngCore};
-use LangErr::{
- DivByZero, ExpDivByZero, ExpIsNotIntOrOneHalf, InvalidAbs, InvalidDec, InvalidPar, InvalidQuit,
- InvalidRound, InvalidStore, MissingTerm, ModIsNotInt, ModZero, NotEnoughPrevResults,
- NotNonNegIntFact, SqrtDoesNotExist, TrailingSyms,
-};
-use O::{Empty, Eval, Exit, Store};
+use rand::{RngCore as _, rngs::ThreadRng};
/// Fixed-sized cache that automatically overwrites the oldest data
-/// when a new item is added and the cache is full. One can think of
+/// when a new item is added and the cache is full.
+///
+/// One can think of
/// [`Cache`] as a very limited but more performant [`VecDeque`][alloc::collections::VecDeque] that only
/// adds new data or reads old data.
pub mod cache;
@@ -247,12 +255,15 @@ pub enum LangErr {
/// by symbols that could not be chained with the preceding expression.
TrailingSyms(usize),
/// The input contained an invalid random expression.
+ #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
#[cfg(feature = "rand")]
InvalidRand(usize),
/// Error when the second argument is less than first in the rand function.
+ #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
#[cfg(feature = "rand")]
RandInvalidArgs(usize),
/// Error when there are no 64-bit integers in the interval passed to the random function.
+ #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
#[cfg(feature = "rand")]
RandNoInts(usize),
}
@@ -304,7 +315,7 @@ pub enum O<'a> {
/// It is `None` iff there have been no previous `Eval` results.
Store(&'a Option<Ratio<BigInt>>),
}
-impl<'a> Display for O<'a> {
+impl Display for O<'_> {
#[expect(
unsafe_code,
reason = "manually construct guaranteed UTF-8; thus avoid the needless check"
@@ -429,6 +440,14 @@ pub struct Evaluator<'input, 'cache, 'prev, 'scratch, 'rand> {
#[cfg(not(feature = "rand"))]
_rng: PhantomData<fn() -> &'rand ()>,
}
+#[expect(
+ clippy::allow_attributes,
+ reason = "can't use expect since it isn't triggered when rand is enabled"
+)]
+#[allow(
+ clippy::needless_lifetimes,
+ reason = "when rand is not enable, 'rand is not needed."
+)]
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"))]
@@ -468,15 +487,16 @@ impl<'input, 'cache, 'prev, 'scratch, 'rand> Evaluator<'input, 'cache, 'prev, 's
}
}
/// Evaluates the input consuming the `Evaluator<'input, 'cache, 'exp>`.
+ ///
/// Requires the input to contain one expression (i.e., if there are
/// multiple newlines, it will error).
-
+ ///
/// # Errors
///
/// Returns a [`LangErr`] iff the input violates the calc language.
#[inline]
pub fn evaluate(mut self) -> Result<O<'prev>, LangErr> {
- self.utf8 = if self.utf8.last().map_or(true, |b| *b != b'\n') {
+ self.utf8 = if self.utf8.last().is_none_or(|b| *b != b'\n') {
self.utf8
} else {
&self.utf8[..self.utf8.len()
@@ -1151,6 +1171,7 @@ impl<'input, 'cache, 'prev, 'scratch, 'rand> Evaluator<'input, 'cache, 'prev, 's
}
}
/// Reads data from `R` passing each line to an [`Evaluator`] to be evaluated.
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[cfg(feature = "std")]
pub struct EvalIter<R> {
/// Reader that contains input data.
@@ -1169,11 +1190,13 @@ pub struct EvalIter<R> {
#[cfg(feature = "rand")]
rng: ThreadRng,
}
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[cfg(feature = "std")]
impl<R> EvalIter<R> {
/// Creates a new `EvalIter`.
- #[inline]
+ #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
#[cfg(feature = "rand")]
+ #[inline]
pub fn new(reader: R) -> Self {
Self {
reader,
@@ -1181,12 +1204,12 @@ impl<R> EvalIter<R> {
cache: Cache::new(),
prev: None,
exp_buffer: Vec::new(),
- rng: rand::thread_rng(),
+ rng: rand::rng(),
}
}
/// Creates a new `EvalIter`.
- #[inline]
#[cfg(any(doc, not(feature = "rand")))]
+ #[inline]
pub fn new(reader: R) -> Self {
Self {
reader,
@@ -1201,8 +1224,9 @@ impl<R> EvalIter<R> {
extern crate std;
#[cfg(feature = "std")]
use std::io::{BufRead, Error};
-#[cfg(feature = "std")]
/// Error returned from [`EvalIter`] when an expression has an error.
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+#[cfg(feature = "std")]
#[derive(Debug)]
pub enum E {
/// Error containing [`Error`] which is returned
@@ -1225,12 +1249,16 @@ impl Display for E {
}
#[cfg(feature = "std")]
use crate::lending_iterator::LendingIterator;
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[cfg(feature = "std")]
impl<R> LendingIterator for EvalIter<R>
where
R: BufRead,
{
- type Item<'a> = Result<O<'a>, E> where Self: 'a;
+ type Item<'a>
+ = Result<O<'a>, E>
+ where
+ Self: 'a;
#[inline]
fn lend_next(&mut self) -> Option<Result<O<'_>, E>> {
self.input_buffer.clear();
@@ -1947,7 +1975,7 @@ mod tests {
&mut Cache::new(),
&mut None,
&mut Vec::new(),
- &mut rand::thread_rng()
+ &mut rand::rng()
)
.get_rand()
.unwrap_err()
@@ -1960,7 +1988,7 @@ mod tests {
&mut Cache::new(),
&mut None,
&mut Vec::new(),
- &mut rand::thread_rng()
+ &mut rand::rng()
)
.get_rand()
.unwrap_err()
@@ -1973,7 +2001,7 @@ mod tests {
&mut Cache::new(),
&mut None,
&mut Vec::new(),
- &mut rand::thread_rng(),
+ &mut rand::rng(),
)
.get_rand()
.unwrap_err()
@@ -1986,7 +2014,7 @@ mod tests {
&mut Cache::new(),
&mut None,
&mut Vec::new(),
- &mut rand::thread_rng(),
+ &mut rand::rng(),
)
.get_rand()
.unwrap_err()
@@ -1999,7 +2027,7 @@ mod tests {
&mut Cache::new(),
&mut None,
&mut Vec::new(),
- &mut rand::thread_rng(),
+ &mut rand::rng(),
)
.get_rand()
.unwrap_err()
@@ -2013,7 +2041,7 @@ mod tests {
&mut Cache::new(),
&mut None,
&mut Vec::new(),
- &mut rand::thread_rng(),
+ &mut rand::rng(),
)
.get_rand()
.unwrap_err()
@@ -2021,44 +2049,50 @@ mod tests {
MissingTerm(i) => i == 0,
_ => false,
});
- 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)",
+ assert!(
+ Evaluator::new(
+ b"rand(2, 7)",
&mut Cache::new(),
&mut None,
&mut Vec::new(),
- &mut rand::thread_rng()
+ &mut rand::rng()
)
.get_rand()
- .map(|r| *r.numer() == BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))
- .unwrap());
+ .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::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(),
+ &mut rand::rng()
+ )
+ .get_rand()
+ .map(|r| *r.numer() == BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))
+ .unwrap()
+ );
}
}
#[cfg(feature = "rand")]
@@ -2078,7 +2112,7 @@ mod tests {
&mut cache,
&mut none,
&mut vec,
- &mut rand::thread_rng(),
+ &mut rand::rng(),
)
.get_rand()
.unwrap()
@@ -2088,16 +2122,17 @@ mod tests {
+ 1) as usize] += 1;
}
// Test that the distribution is within 1% of what is expected.
- assert!(vals
- .into_iter()
- .try_fold(false, |_, r| {
- if r < COUNT * 33 / 100 || r > COUNT * 101 / 300 {
- Err(false)
- } else {
- Ok(true)
- }
- })
- .unwrap());
+ assert!(
+ vals.into_iter()
+ .try_fold(false, |_, r| {
+ if r < COUNT * 33 / 100 || r > COUNT * 101 / 300 {
+ Err(false)
+ } else {
+ Ok(true)
+ }
+ })
+ .unwrap()
+ );
}
#[test]
fn term() {
@@ -2134,25 +2169,29 @@ mod tests {
)
);
#[cfg(feature = "rand")]
- assert!(Evaluator::new(
- b"rand()",
- &mut Cache::new(),
- &mut None,
- &mut Vec::new(),
- &mut rand::thread_rng()
- )
- .get_term()
- .is_ok());
+ assert!(
+ Evaluator::new(
+ b"rand()",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::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 rand::thread_rng(),
- )
- .get_term()
- .is_ok());
+ assert!(
+ Evaluator::new(
+ b"rand(-13/93, 833)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new(),
+ &mut rand::rng(),
+ )
+ .get_term()
+ .is_ok()
+ );
#[cfg(not(feature = "rand"))]
assert!(
match Evaluator::new(b"rand()", &mut Cache::new(), &mut None, &mut Vec::new())
diff --git a/src/main.rs b/src/main.rs
@@ -1,7 +1,6 @@
//! # `calc`
//!
//! Consult [`README.md`](https://crates.io/crates/calc_rational).
-#![cfg_attr(all(doc, channel_nightly), feature(doc_auto_cfg))]
#![deny(
unknown_lints,
future_incompatible,
@@ -37,7 +36,7 @@
reason = "noisy, opinionated, and likely doesn't prevent bugs or improve APIs"
)]
#[cfg(feature = "std")]
-use calc_lib::{lending_iterator::LendingIterator, EvalIter};
+use calc_lib::{EvalIter, lending_iterator::LendingIterator as _};
#[cfg(feature = "std")]
use core::fmt::{self, Display, Formatter};
#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
@@ -45,7 +44,7 @@ use priv_sep::{Promise, Promises};
#[cfg(feature = "std")]
use std::{
error,
- io::{self, Error, Write},
+ io::{self, Error, Write as _},
};
/// Error returned by [`main`].
#[cfg(feature = "std")]
@@ -54,7 +53,7 @@ enum Err {
/// Error returned from [`writeln`].
Io(Error),
#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
- /// Error returned from [`pledge`].
+ /// Error returned from `pledge`.
Pledge(Error),
}
#[cfg(feature = "std")]