use alloc::string::String;
use alloc::vec::Vec;
use core::marker::PhantomData;
use super::Praedicatum;
macro_rules! praedicatum_impls {
($pred:ty, $desc:literal, [$($ty:ty),+ $(,)?], |$value:ident| $body:expr) => {
$(impl Praedicatum<$ty> for $pred {
#[inline]
fn check($value: &$ty) -> bool {
$body
}
#[inline]
fn description() -> &'static str {
$desc
}
})+
};
}
macro_rules! intra_fines_impls {
([$($ty:ty),+ $(,)?]) => {
$(impl<const MIN: i64, const MAX: i64> Praedicatum<$ty> for IntraFines<MIN, MAX> {
#[inline]
fn check(value: &$ty) -> bool {
let v = i64::from(*value);
v >= MIN && v <= MAX
}
#[inline]
fn description() -> &'static str {
"value must be in range"
}
})+
};
}
macro_rules! magnitudo_impls {
($pred:ident, $op:tt, $string_desc:literal, $vec_desc:literal) => {
impl<const N: usize> Praedicatum<String> for $pred<N> {
#[inline]
fn check(value: &String) -> bool {
value.len() $op N
}
#[inline]
fn description() -> &'static str {
$string_desc
}
}
impl<T, const N: usize> Praedicatum<Vec<T>> for $pred<N> {
#[inline]
fn check(value: &Vec<T>) -> bool {
value.len() $op N
}
#[inline]
fn description() -> &'static str {
$vec_desc
}
}
};
}
pub struct Positivus;
praedicatum_impls!(
Positivus,
"value must be positive (> 0)",
[i8, i16, i32, i64, i128, isize],
|value| *value > 0
);
praedicatum_impls!(
Positivus,
"value must be positive (> 0)",
[f32, f64],
|value| *value > 0.0
);
pub type Positive = Positivus;
pub struct NonNegativus;
praedicatum_impls!(
NonNegativus,
"value must be non-negative (>= 0)",
[i8, i16, i32, i64, i128, isize],
|value| *value >= 0
);
praedicatum_impls!(
NonNegativus,
"value must be non-negative (>= 0)",
[f32, f64],
|value| *value >= 0.0
);
pub type NonNegative = NonNegativus;
pub struct Negativus;
praedicatum_impls!(
Negativus,
"value must be negative (< 0)",
[i8, i16, i32, i64, i128, isize],
|value| *value < 0
);
praedicatum_impls!(
Negativus,
"value must be negative (< 0)",
[f32, f64],
|value| *value < 0.0
);
pub type Negative = Negativus;
pub struct NonNullus;
praedicatum_impls!(
NonNullus,
"value must be non-zero",
[
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
],
|value| *value != 0
);
praedicatum_impls!(
NonNullus,
"value must be non-zero",
[f32, f64],
|value| *value != 0.0
);
pub type NonZero = NonNullus;
pub struct NonVacuus;
impl Praedicatum<String> for NonVacuus {
#[inline]
fn check(value: &String) -> bool {
!value.is_empty()
}
#[inline]
fn description() -> &'static str {
"string must be non-empty"
}
}
impl<T> Praedicatum<Vec<T>> for NonVacuus {
#[inline]
fn check(value: &Vec<T>) -> bool {
!value.is_empty()
}
#[inline]
fn description() -> &'static str {
"collection must be non-empty"
}
}
impl<T, const N: usize> Praedicatum<[T; N]> for NonVacuus {
#[inline]
fn check(_value: &[T; N]) -> bool {
N > 0
}
#[inline]
fn description() -> &'static str {
"array must be non-empty"
}
}
impl<T> Praedicatum<&[T]> for NonVacuus {
#[inline]
fn check(value: &&[T]) -> bool {
!value.is_empty()
}
#[inline]
fn description() -> &'static str {
"slice must be non-empty"
}
}
pub type NonEmpty = NonVacuus;
pub struct Verum;
impl<T> Praedicatum<T> for Verum {
#[inline]
fn check(_: &T) -> bool {
true
}
#[inline]
fn description() -> &'static str {
"always true"
}
}
pub type True = Verum;
pub struct Falsum;
impl<T> Praedicatum<T> for Falsum {
#[inline]
fn check(_: &T) -> bool {
false
}
#[inline]
fn description() -> &'static str {
"always false"
}
}
pub type False = Falsum;
pub struct IntraFines<const MIN: i64, const MAX: i64>;
intra_fines_impls!([i8, i16, i32, u8, u16, u32]);
impl<const MIN: i64, const MAX: i64> Praedicatum<i64> for IntraFines<MIN, MAX> {
#[inline]
fn check(value: &i64) -> bool {
*value >= MIN && *value <= MAX
}
#[inline]
fn description() -> &'static str {
"value must be in range"
}
}
impl<const MIN: i64, const MAX: i64> Praedicatum<usize> for IntraFines<MIN, MAX> {
#[inline]
fn check(value: &usize) -> bool {
if MIN < 0 {
return false; }
let v = *value as i64;
v >= MIN && v <= MAX
}
#[inline]
fn description() -> &'static str {
"value must be in range"
}
}
pub type InRange<const MIN: i64, const MAX: i64> = IntraFines<MIN, MAX>;
pub struct MaiorQuam<const THRESHOLD: i64>;
impl<const THRESHOLD: i64> Praedicatum<i32> for MaiorQuam<THRESHOLD> {
#[inline]
fn check(value: &i32) -> bool {
i64::from(*value) > THRESHOLD
}
#[inline]
fn description() -> &'static str {
"value must be greater than threshold"
}
}
impl<const THRESHOLD: i64> Praedicatum<i64> for MaiorQuam<THRESHOLD> {
#[inline]
fn check(value: &i64) -> bool {
*value > THRESHOLD
}
#[inline]
fn description() -> &'static str {
"value must be greater than threshold"
}
}
impl<const THRESHOLD: i64> Praedicatum<u32> for MaiorQuam<THRESHOLD> {
#[inline]
fn check(value: &u32) -> bool {
i64::from(*value) > THRESHOLD
}
#[inline]
fn description() -> &'static str {
"value must be greater than threshold"
}
}
impl<const THRESHOLD: i64> Praedicatum<u64> for MaiorQuam<THRESHOLD> {
#[inline]
fn check(value: &u64) -> bool {
if THRESHOLD < 0 {
return true; }
*value > THRESHOLD as u64
}
#[inline]
fn description() -> &'static str {
"value must be greater than threshold"
}
}
impl<const THRESHOLD: i64> Praedicatum<usize> for MaiorQuam<THRESHOLD> {
#[inline]
fn check(value: &usize) -> bool {
if THRESHOLD < 0 {
return true;
}
(*value as u64) > THRESHOLD as u64
}
#[inline]
fn description() -> &'static str {
"value must be greater than threshold"
}
}
pub type GreaterThan<const N: i64> = MaiorQuam<N>;
pub struct MinorQuam<const THRESHOLD: i64>;
impl<const THRESHOLD: i64> Praedicatum<i32> for MinorQuam<THRESHOLD> {
#[inline]
fn check(value: &i32) -> bool {
i64::from(*value) < THRESHOLD
}
#[inline]
fn description() -> &'static str {
"value must be less than threshold"
}
}
impl<const THRESHOLD: i64> Praedicatum<i64> for MinorQuam<THRESHOLD> {
#[inline]
fn check(value: &i64) -> bool {
*value < THRESHOLD
}
#[inline]
fn description() -> &'static str {
"value must be less than threshold"
}
}
impl<const THRESHOLD: i64> Praedicatum<u32> for MinorQuam<THRESHOLD> {
#[inline]
fn check(value: &u32) -> bool {
if THRESHOLD < 0 {
return false; }
i64::from(*value) < THRESHOLD
}
#[inline]
fn description() -> &'static str {
"value must be less than threshold"
}
}
impl<const THRESHOLD: i64> Praedicatum<usize> for MinorQuam<THRESHOLD> {
#[inline]
fn check(value: &usize) -> bool {
if THRESHOLD < 0 {
return false;
}
(*value as u64) < THRESHOLD as u64
}
#[inline]
fn description() -> &'static str {
"value must be less than threshold"
}
}
pub type LessThan<const N: i64> = MinorQuam<N>;
pub struct MagnitudoExacta<const N: usize>;
magnitudo_impls!(MagnitudoExacta, ==,
"string must have exact length", "collection must have exact size");
pub type ExactSize<const N: usize> = MagnitudoExacta<N>;
pub struct MagnitudoMinima<const N: usize>;
magnitudo_impls!(MagnitudoMinima, >=,
"string must have minimum length", "collection must have minimum size");
pub type MinSize<const N: usize> = MagnitudoMinima<N>;
pub struct MagnitudoMaxima<const N: usize>;
magnitudo_impls!(MagnitudoMaxima, <=,
"string must have maximum length", "collection must have maximum size");
/// Alias for `MaxSize` predicate.
pub type MaxSize<const N: usize> = MagnitudoMaxima<N>;
// =============================================================================
// Even/Odd Predicates
// =============================================================================
/// Predicate for even numbers.
///
/// # Latin Etymology
/// *Par* = even.
pub struct Par;
praedicatum_impls!(
Par,
"value must be even",
[i8, i16, i32, i64],
|value| value % 2 == 0
);
praedicatum_impls!(
Par,
"value must be even",
[u8, u16, u32, u64, usize],
|value| value.is_multiple_of(2)
);
/// Alias for Even predicate.
pub type Even = Par;
/// Predicate for odd numbers.
///
/// # Latin Etymology
/// *Impar* = odd.
pub struct Impar;
praedicatum_impls!(
Impar,
"value must be odd",
[i8, i16, i32, i64],
|value| value % 2 != 0
);
praedicatum_impls!(
Impar,
"value must be odd",
[u8, u16, u32, u64, usize],
|value| !value.is_multiple_of(2)
);
/// Alias for Odd predicate.
pub type Odd = Impar;
// =============================================================================
// Custom Predicate Wrapper
// =============================================================================
/// A wrapper for custom predicate functions.
///
/// # Latin Etymology
/// *Praedicatum proprium* = custom predicate.
pub struct PraedicatumProprium<T, F>
where
F: Fn(&T) -> bool,
{
_marker: PhantomData<(T, F)>,
}
// =============================================================================
// Tests
// =============================================================================
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
#[test]
fn test_positive() {
assert!(Positive::check(&42i32));
assert!(!Positive::check(&0i32));
assert!(!Positive::check(&-1i32));
}
#[test]
fn test_non_negative() {
assert!(NonNegative::check(&42i32));
assert!(NonNegative::check(&0i32));
assert!(!NonNegative::check(&-1i32));
}
#[test]
fn test_negative() {
assert!(!Negative::check(&42i32));
assert!(!Negative::check(&0i32));
assert!(Negative::check(&-1i32));
}
#[test]
fn test_non_zero() {
assert!(NonZero::check(&42i32));
assert!(!NonZero::check(&0i32));
assert!(NonZero::check(&-1i32));
assert!(NonZero::check(&42u32));
assert!(!NonZero::check(&0u32));
}
#[test]
fn test_non_empty_string() {
assert!(NonEmpty::check(&"hello".to_string()));
assert!(!NonEmpty::check(&String::new()));
}
#[test]
fn test_non_empty_vec() {
assert!(NonEmpty::check(&vec![1, 2, 3]));
assert!(!NonEmpty::check(&Vec::<i32>::new()));
}
#[test]
fn test_in_range() {
assert!(InRange::<0, 100>::check(&50i32));
assert!(InRange::<0, 100>::check(&0i32));
assert!(InRange::<0, 100>::check(&100i32));
assert!(!InRange::<0, 100>::check(&-1i32));
assert!(!InRange::<0, 100>::check(&101i32));
}
#[test]
fn test_greater_than() {
assert!(GreaterThan::<10>::check(&11i32));
assert!(!GreaterThan::<10>::check(&10i32));
assert!(!GreaterThan::<10>::check(&5i32));
}
#[test]
fn test_less_than() {
assert!(LessThan::<10>::check(&5i32));
assert!(!LessThan::<10>::check(&10i32));
assert!(!LessThan::<10>::check(&15i32));
}
#[test]
fn test_exact_size() {
assert!(ExactSize::<5>::check(&"hello".to_string()));
assert!(!ExactSize::<5>::check(&"hi".to_string()));
}
#[test]
fn test_min_size() {
assert!(MinSize::<3>::check(&"hello".to_string()));
assert!(MinSize::<3>::check(&"abc".to_string()));
assert!(!MinSize::<3>::check(&"ab".to_string()));
}
#[test]
fn test_max_size() {
assert!(MaxSize::<5>::check(&"hello".to_string()));
assert!(MaxSize::<5>::check(&"hi".to_string()));
assert!(!MaxSize::<5>::check(&"hello world".to_string()));
}
#[test]
fn test_even() {
assert!(Even::check(&42i32));
assert!(Even::check(&0i32));
assert!(!Even::check(&41i32));
}
#[test]
fn test_odd() {
assert!(Odd::check(&41i32));
assert!(Odd::check(&1i32));
assert!(!Odd::check(&42i32));
}
#[test]
fn test_true_false() {
assert!(True::check(&42i32));
assert!(!False::check(&42i32));
}
#[test]
fn test_predicates_with_f64_special_values() {
// NaN comparisons always return false, so NaN satisfies none of the
// ordering predicates but does satisfy NonZero (NaN != 0.0 is true).
assert!(!Positive::check(&f64::NAN));
assert!(!Negative::check(&f64::NAN));
assert!(!NonNegative::check(&f64::NAN));
assert!(NonZero::check(&f64::NAN));
// Infinity behaves as expected under standard ordering.
assert!(Positive::check(&f64::INFINITY));
assert!(NonNegative::check(&f64::INFINITY));
assert!(!Negative::check(&f64::INFINITY));
assert!(Negative::check(&f64::NEG_INFINITY));
assert!(!Positive::check(&f64::NEG_INFINITY));
assert!(!NonNegative::check(&f64::NEG_INFINITY));
}
#[test]
fn usize_thresholds_compare_in_u64() {
assert!(!<MaiorQuam<{ i64::MAX }> as Praedicatum<usize>>::check(&5));
assert!(<MinorQuam<{ i64::MAX }> as Praedicatum<usize>>::check(&5));
assert!(<MaiorQuam<-1> as Praedicatum<usize>>::check(&0));
assert!(!<MinorQuam<-1> as Praedicatum<usize>>::check(&0));
}
}