#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Extended<T> {
NegInf,
Finite(T),
PosInf,
}
impl<T> Extended<T> {
#[inline]
pub fn fold<U>(self, neg_inf: U, pos_inf: U, f: impl FnOnce(T) -> U) -> U {
match self {
Extended::NegInf => neg_inf,
Extended::Finite(t) => f(t),
Extended::PosInf => pos_inf,
}
}
#[inline]
pub fn fold_with<U>(
self,
neg_inf: impl FnOnce() -> U,
pos_inf: impl FnOnce() -> U,
f: impl FnOnce(T) -> U,
) -> U {
match self {
Extended::NegInf => neg_inf(),
Extended::Finite(t) => f(t),
Extended::PosInf => pos_inf(),
}
}
#[inline]
#[must_use]
pub const fn finite(self) -> Option<T>
where
T: Copy,
{
match self {
Extended::Finite(t) => Some(t),
Extended::NegInf | Extended::PosInf => None,
}
}
}
#[cfg(feature = "try_trait")]
impl<T> core::ops::Try for Extended<T> {
type Output = T;
type Residual = Extended<core::convert::Infallible>;
#[inline]
fn from_output(output: Self::Output) -> Self {
Extended::Finite(output)
}
#[inline]
fn branch(self) -> core::ops::ControlFlow<Self::Residual, Self::Output> {
match self {
Extended::Finite(t) => core::ops::ControlFlow::Continue(t),
Extended::NegInf => core::ops::ControlFlow::Break(Extended::NegInf),
Extended::PosInf => core::ops::ControlFlow::Break(Extended::PosInf),
}
}
}
#[cfg(feature = "try_trait")]
impl<T> core::ops::FromResidual for Extended<T> {
#[inline]
fn from_residual(residual: Extended<core::convert::Infallible>) -> Self {
match residual {
Extended::NegInf => Extended::NegInf,
Extended::PosInf => Extended::PosInf,
Extended::Finite(never) => match never {},
}
}
}
#[cfg(feature = "try_trait")]
impl<O> core::ops::Residual<O> for Extended<core::convert::Infallible> {
type TryType = Extended<O>;
}
impl<T: PartialOrd> PartialOrd for Extended<T> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use Extended::*;
use std::cmp::Ordering::*;
match (self, other) {
(NegInf, NegInf) => Some(Equal),
(PosInf, PosInf) => Some(Equal),
(NegInf, _) => Some(Less),
(_, NegInf) => Some(Greater),
(_, PosInf) => Some(Less),
(PosInf, _) => Some(Greater),
(Finite(a), Finite(b)) => a.partial_cmp(b),
}
}
}
impl<T: Ord> Ord for Extended<T> {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
#[macro_export]
macro_rules! lift_l {
($parent:expr) => {
$crate::conn::Conn::new_l(
|a| match a {
$crate::extended::Extended::NegInf => $crate::extended::Extended::NegInf,
$crate::extended::Extended::Finite(x) => {
$crate::extended::Extended::Finite(($parent).ceil(x))
}
$crate::extended::Extended::PosInf => $crate::extended::Extended::PosInf,
},
|b| match b {
$crate::extended::Extended::NegInf => $crate::extended::Extended::NegInf,
$crate::extended::Extended::Finite(y) => {
$crate::extended::Extended::Finite(($parent).upper(y))
}
$crate::extended::Extended::PosInf => $crate::extended::Extended::PosInf,
},
)
};
}
#[macro_export]
macro_rules! lift_r {
($parent:expr) => {
$crate::conn::Conn::new_r(
|b| match b {
$crate::extended::Extended::NegInf => $crate::extended::Extended::NegInf,
$crate::extended::Extended::Finite(y) => {
$crate::extended::Extended::Finite(($parent).lower(y))
}
$crate::extended::Extended::PosInf => $crate::extended::Extended::PosInf,
},
|a| match a {
$crate::extended::Extended::NegInf => $crate::extended::Extended::NegInf,
$crate::extended::Extended::Finite(x) => {
$crate::extended::Extended::Finite(($parent).floor(x))
}
$crate::extended::Extended::PosInf => $crate::extended::Extended::PosInf,
},
)
};
}
#[macro_export]
macro_rules! lift_k {
($name:ident : $A:ty => $B:ty = $parent:path $(,)?) => {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct $name;
impl $name {
#[allow(dead_code)]
#[inline]
#[must_use]
pub const fn view_l(
self,
) -> $crate::conn::Conn<
$crate::extended::Extended<$A>,
$crate::extended::Extended<$B>,
$crate::conn::L,
> {
const LIFTED: $crate::conn::Conn<
$crate::extended::Extended<$A>,
$crate::extended::Extended<$B>,
$crate::conn::L,
> = $crate::lift_l!($parent.swap_l().swap_r());
LIFTED
}
#[allow(dead_code)]
#[inline]
#[must_use]
pub const fn view_r(
self,
) -> $crate::conn::Conn<
$crate::extended::Extended<$A>,
$crate::extended::Extended<$B>,
$crate::conn::R,
> {
const LIFTED: $crate::conn::Conn<
$crate::extended::Extended<$A>,
$crate::extended::Extended<$B>,
$crate::conn::R,
> = $crate::lift_r!($parent.swap_r().swap_l());
LIFTED
}
#[allow(dead_code)]
#[inline]
#[must_use]
pub const fn swap_l(
self,
) -> $crate::conn::Conn<
$crate::extended::Extended<$B>,
$crate::extended::Extended<$A>,
$crate::conn::R,
> {
self.view_l().swap_l()
}
#[allow(dead_code)]
#[inline]
#[must_use]
pub const fn swap_r(
self,
) -> $crate::conn::Conn<
$crate::extended::Extended<$B>,
$crate::extended::Extended<$A>,
$crate::conn::L,
> {
self.view_r().swap_r()
}
}
impl $crate::conn::ConnL for $name {
type A = $crate::extended::Extended<$A>;
type B = $crate::extended::Extended<$B>;
#[inline]
fn swap_l(
&self,
) -> $crate::conn::Conn<
$crate::extended::Extended<$B>,
$crate::extended::Extended<$A>,
$crate::conn::R,
> {
$name::swap_l(*self)
}
}
impl $crate::conn::ConnR for $name {
type A = $crate::extended::Extended<$A>;
type B = $crate::extended::Extended<$B>;
#[inline]
fn swap_r(
&self,
) -> $crate::conn::Conn<
$crate::extended::Extended<$B>,
$crate::extended::Extended<$A>,
$crate::conn::L,
> {
$name::swap_r(*self)
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prop::arb::arb_extended_i64;
use proptest::prelude::*;
use std::cell::Cell;
#[test]
fn extended_fold_neg_inf() {
assert_eq!(Extended::<i64>::NegInf.fold(0, 99, |t| t + 1), 0);
}
#[test]
fn extended_fold_finite() {
assert_eq!(Extended::Finite(3i64).fold(0, 99, |t| t + 1), 4);
}
#[test]
fn extended_fold_pos_inf() {
assert_eq!(Extended::<i64>::PosInf.fold(0, 99, |t| t + 1), 99);
}
#[test]
fn extended_fold_with_skips_unselected_arms() {
let variants: [Extended<i64>; 3] =
[Extended::NegInf, Extended::Finite(7), Extended::PosInf];
for x in variants {
let neg_called = Cell::new(false);
let pos_called = Cell::new(false);
let fin_called = Cell::new(false);
let _ = x.fold_with(
|| {
neg_called.set(true);
0
},
|| {
pos_called.set(true);
0
},
|_| {
fin_called.set(true);
0
},
);
match x {
Extended::NegInf => {
assert!(neg_called.get());
assert!(!pos_called.get() && !fin_called.get());
}
Extended::Finite(_) => {
assert!(fin_called.get());
assert!(!neg_called.get() && !pos_called.get());
}
Extended::PosInf => {
assert!(pos_called.get());
assert!(!neg_called.get() && !fin_called.get());
}
}
}
}
proptest! {
#[test]
fn extended_fold_with_const_agrees_with_fold(
x in arb_extended_i64(),
gx in any::<i64>(),
gy in any::<i64>(),
) {
let f = |t: i64| t.wrapping_mul(3).wrapping_add(1);
let lazy = x.fold_with(|| gx, || gy, f);
let strict = x.fold(gx, gy, f);
prop_assert_eq!(lazy, strict);
}
}
#[test]
fn standard_order_on_finite() {
let a: Extended<i64> = Extended::Finite(1);
let b: Extended<i64> = Extended::Finite(2);
assert!(a < b);
assert!(b > a);
}
#[test]
fn neginf_is_bottom() {
let m: Extended<i64> = Extended::NegInf;
let f: Extended<i64> = Extended::Finite(-9_999_999);
assert!(m < f);
assert!(f > m);
}
#[test]
fn posinf_is_top() {
let p: Extended<i64> = Extended::PosInf;
let f: Extended<i64> = Extended::Finite(9_999_999);
assert!(f < p);
assert!(p > f);
}
#[test]
fn neginf_below_posinf() {
let m: Extended<i64> = Extended::NegInf;
let p: Extended<i64> = Extended::PosInf;
assert!(m < p);
}
#[test]
fn extended_eq_reflexive_on_infinities() {
let m: Extended<i64> = Extended::NegInf;
let p: Extended<i64> = Extended::PosInf;
assert_eq!(m, Extended::NegInf);
assert_eq!(p, Extended::PosInf);
}
}
#[cfg(all(test, feature = "fixed"))]
mod lift_tests {
use super::*;
use crate::conn::{Conn, L, R};
use crate::fixed::i016::Q000I016;
use ::fixed::FixedI16;
use ::fixed::types::extra::U0;
use proptest::prelude::*;
fn arb_extended<T, S>(inner: S) -> impl Strategy<Value = Extended<T>>
where
T: core::fmt::Debug + Clone + 'static,
S: Strategy<Value = T> + 'static,
{
prop_oneof![
1 => Just(Extended::NegInf),
1 => Just(Extended::PosInf),
3 => inner.prop_map(Extended::Finite),
]
}
fn arb_ext_i64() -> impl Strategy<Value = Extended<i64>> {
arb_extended(any::<i64>())
}
fn arb_ext_q000() -> impl Strategy<Value = Extended<FixedI16<U0>>> {
arb_extended(any::<i16>().prop_map(FixedI16::<U0>::from_bits))
}
fn arb_ext_i16() -> impl Strategy<Value = Extended<i16>> {
arb_extended(any::<i16>())
}
#[test]
fn lift_l_identity_synthetic_arms() {
const ID: Conn<i64, i64, L> = Conn::identity();
const LIFTED: Conn<Extended<i64>, Extended<i64>, L> = lift_l!(ID);
assert_eq!(LIFTED.ceil(Extended::NegInf), Extended::NegInf);
assert_eq!(LIFTED.ceil(Extended::PosInf), Extended::PosInf);
assert_eq!(LIFTED.upper(Extended::NegInf), Extended::NegInf);
assert_eq!(LIFTED.upper(Extended::PosInf), Extended::PosInf);
}
#[test]
fn lift_r_identity_synthetic_arms() {
const ID_R: Conn<i64, i64, R> = Conn::identity();
const LIFTED: Conn<Extended<i64>, Extended<i64>, R> = lift_r!(ID_R);
assert_eq!(LIFTED.floor(Extended::NegInf), Extended::NegInf);
assert_eq!(LIFTED.floor(Extended::PosInf), Extended::PosInf);
assert_eq!(LIFTED.lower(Extended::NegInf), Extended::NegInf);
assert_eq!(LIFTED.lower(Extended::PosInf), Extended::PosInf);
}
#[test]
fn lift_l_identity_finite_passthrough() {
const ID: Conn<i64, i64, L> = Conn::identity();
const LIFTED: Conn<Extended<i64>, Extended<i64>, L> = lift_l!(ID);
assert_eq!(LIFTED.ceil(Extended::Finite(7)), Extended::Finite(7));
assert_eq!(LIFTED.upper(Extended::Finite(-3)), Extended::Finite(-3));
}
lift_k!(EXTQ000I016 : FixedI16<U0> => i16 = Q000I016);
#[test]
fn lift_k_q000i016_synthetic_arms() {
assert_eq!(
EXTQ000I016.view_l().ceil(Extended::NegInf),
Extended::NegInf
);
assert_eq!(
EXTQ000I016.view_l().ceil(Extended::PosInf),
Extended::PosInf
);
assert_eq!(
EXTQ000I016.view_r().floor(Extended::NegInf),
Extended::NegInf
);
assert_eq!(
EXTQ000I016.view_r().floor(Extended::PosInf),
Extended::PosInf
);
assert_eq!(
EXTQ000I016.view_l().upper(Extended::NegInf),
Extended::NegInf
);
assert_eq!(
EXTQ000I016.view_r().lower(Extended::PosInf),
Extended::PosInf
);
}
#[test]
fn lift_k_q000i016_finite_dispatches_through_parent() {
let q = FixedI16::<U0>::from_bits(42);
assert_eq!(
EXTQ000I016.view_l().ceil(Extended::Finite(q)),
Extended::Finite(42_i16)
);
assert_eq!(
EXTQ000I016.view_l().upper(Extended::Finite(42_i16)),
Extended::Finite(q)
);
assert_eq!(
EXTQ000I016.view_r().floor(Extended::Finite(q)),
Extended::Finite(42_i16)
);
assert_eq!(
EXTQ000I016.view_r().lower(Extended::Finite(42_i16)),
Extended::Finite(q)
);
}
#[test]
fn lift_l_const_init_smoke() {
const ID: Conn<i64, i64, L> = Conn::identity();
const LIFTED: Conn<Extended<i64>, Extended<i64>, L> = lift_l!(ID);
assert_eq!(LIFTED.ceil(Extended::Finite(0)), Extended::Finite(0));
}
const ID_I64: Conn<i64, i64, L> = Conn::identity();
const LIFTED_ID_I64: Conn<Extended<i64>, Extended<i64>, L> = lift_l!(ID_I64);
crate::law_battery! {
mod lifted_id_i64,
conn: LIFTED_ID_I64,
fine: arb_ext_i64(),
coarse: arb_ext_i64(),
subset: l_only,
}
crate::law_battery! {
mod lifted_q000i016,
conn: EXTQ000I016,
fine: arb_ext_q000(),
coarse: arb_ext_i16(),
}
#[test]
fn lift_l_compose_chain_const_smoke() {
const ID_I64: Conn<i64, i64, L> = Conn::identity();
const LIFTED: Conn<Extended<i64>, Extended<i64>, L> = lift_l!(ID_I64);
const ID_EXT: Conn<Extended<i64>, Extended<i64>, L> = Conn::identity();
const CHAIN: Conn<Extended<i64>, Extended<i64>, L> = crate::compose_l!(LIFTED, ID_EXT);
assert_eq!(CHAIN.ceil(Extended::Finite(7)), Extended::Finite(7));
assert_eq!(CHAIN.upper(Extended::PosInf), Extended::PosInf);
}
#[test]
fn lift_k_compose_chain_runtime_smoke() {
const ID_EXT_I16: Conn<Extended<i16>, Extended<i16>, L> = Conn::identity();
let chain: Conn<Extended<FixedI16<U0>>, Extended<i16>, L> =
crate::compose_l!(EXTQ000I016.view_l(), ID_EXT_I16);
let q = FixedI16::<U0>::from_bits(99);
assert_eq!(chain.ceil(Extended::Finite(q)), Extended::Finite(99_i16));
assert_eq!(chain.ceil(Extended::NegInf), Extended::NegInf);
}
}