odsek 0.1.0

Lazy, pull-based composition of mathematical interval sets with open/closed endpoints, no_std-compatible.
Documentation
use crate::interval::LeftT::*;
use crate::interval::RightT::*;
use crate::interval::*;
use core::cmp::Ordering;

/// An endpoint that is explicitly tagged as either `Open` (exclusive) or `Closed` (inclusive).
///
/// Use this when you need full control over open/closed boundaries on either
/// side of an interval. For ranges that are always `[a, b)` (closed-left,
/// open-right), prefer the lighter [`EndpointSymmetric`](crate::EndpointSymmetric).
#[derive(Debug, Clone, PartialEq)]
pub enum EndpointOC<T> {
    /// Open endpoint — the boundary value is *not* part of the interval.
    Open(T),
    /// Closed endpoint — the boundary value *is* part of the interval.
    Closed(T),
}

impl<T> EndpointToggle for EndpointOC<T> {
    fn toggle(self) -> Self {
        match self {
            EndpointOC::Open(a) => EndpointOC::Closed(a),
            EndpointOC::Closed(a) => EndpointOC::Open(a),
        }
    }
}

impl<E> EndpointMap for EndpointOC<E> {
    type EndpointValue = E;
    fn map_endpoint(self, f: impl Fn(Self::EndpointValue) -> Self::EndpointValue) -> Self {
        match self {
            EndpointOC::Open(a) => EndpointOC::Open(f(a)),
            EndpointOC::Closed(a) => EndpointOC::Closed(f(a)),
        }
    }
}

impl<T, U> EndpointMapInto<U> for EndpointOC<T> {
    type EndpointValue = T;
    type Output = EndpointOC<U>;
    fn map_endpoint_into(self, f: impl Fn(T) -> U) -> EndpointOC<U> {
        match self {
            EndpointOC::Open(a) => EndpointOC::Open(f(a)),
            EndpointOC::Closed(a) => EndpointOC::Closed(f(a)),
        }
    }
}

impl<T> EndpointOC<T> {
    /// If this is an `Open` endpoint, return a clone of the underlying value.
    pub fn open(&self) -> Option<T>
    where
        T: Clone,
    {
        match self {
            EndpointOC::Open(a) => Some(a.clone()),
            EndpointOC::Closed(_) => None,
        }
    }
    /// If this is a `Closed` endpoint, return a clone of the underlying value.
    pub fn closed(&self) -> Option<T>
    where
        T: Clone,
    {
        match self {
            EndpointOC::Open(_) => None,
            EndpointOC::Closed(a) => Some(a.clone()),
        }
    }
}

// Left Left
impl<T> PartialOrd<LeftT<EndpointOC<T>>> for LeftT<EndpointOC<T>>
where
    T: Ord,
{
    fn partial_cmp(&self, other: &LeftT<EndpointOC<T>>) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<T> PartialOrd<RightT<EndpointOC<T>>> for RightT<EndpointOC<T>>
where
    T: Ord,
{
    fn partial_cmp(&self, other: &RightT<EndpointOC<T>>) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<T> Eq for LeftT<EndpointOC<T>> where T: Eq {}
impl<T> Eq for RightT<EndpointOC<T>> where T: Eq {}

impl<T> Ord for LeftT<EndpointOC<T>>
where
    T: Ord,
{
    fn cmp(&self, other: &Self) -> Ordering {
        let Left(ref a) = self;
        let Left(ref b) = other;
        use EndpointOC::*;
        use Ordering::*;
        match a {
            Open(x) => match b {
                Open(y) => x.cmp(y),
                Closed(y) => match x.cmp(y) {
                    Less => Less,
                    Equal => Greater,
                    Greater => Greater,
                },
            },
            Closed(x) => match b {
                Open(y) => match x.cmp(y) {
                    Less => Less,
                    Equal => Less,
                    Greater => Greater,
                },
                Closed(y) => x.cmp(y),
            },
        }
    }
}

impl<T> Ord for RightT<EndpointOC<T>>
where
    T: Ord,
{
    fn cmp(&self, other: &Self) -> Ordering {
        let Right(ref a) = self;
        let Right(ref b) = other;
        use EndpointOC::*;
        use Ordering::*;
        match a {
            Open(x) => match b {
                Open(y) => x.cmp(y),
                Closed(y) => match x.cmp(y) {
                    Less => Less,
                    Equal => Less,
                    Greater => Greater,
                },
            },
            Closed(x) => match b {
                Open(y) => match x.cmp(y) {
                    Less => Less,
                    Equal => Greater,
                    Greater => Greater,
                },
                Closed(y) => x.cmp(y),
            },
        }
    }
}

impl<E> EndpointOC<E> {
    /// Returns `true` if this endpoint is `Open`.
    pub fn is_open(&self) -> bool {
        match self {
            EndpointOC::Open(_) => true,
            EndpointOC::Closed(_) => false,
        }
    }

    /// Returns `true` if this endpoint is `Closed`.
    pub fn is_closed(&self) -> bool {
        !self.is_open()
    }

    /// Swap `Open` ↔ `Closed`, preserving the underlying value.
    ///
    /// Equivalent to [`EndpointToggle::toggle`].
    pub fn toggle(self) -> Self {
        match self {
            EndpointOC::Open(a) => EndpointOC::Closed(a),
            EndpointOC::Closed(a) => EndpointOC::Open(a),
        }
    }

    /// Like [`toggle`](Self::toggle), but works through a borrow by cloning
    /// the underlying value.
    pub fn toggle_ref(&self) -> Self
    where
        E: Clone,
    {
        match self {
            EndpointOC::Open(a) => EndpointOC::Closed(a.clone()),
            EndpointOC::Closed(a) => EndpointOC::Open(a.clone()),
        }
    }
}

#[cfg(test)]
mod tests {
    use core::cmp::Ordering;

    use crate::EndpointOC;
    use crate::LeftT;
    use crate::RightT;

    // generic comparison macro used for test
    macro_rules! cmp_m {
        ($alr:ident, $aoc:ident, $blr:ident, $boc:ident,  $e:expr) => {
            // enpoints are equal
            let va = $alr(EndpointOC::$aoc(2));
            let vb = $blr(EndpointOC::$boc(2));

            let cc = va.partial_cmp(&vb);
            let cx = vb.partial_cmp(&va);

            assert_eq!(cc, Some($e));
            match ($e) {
                Ordering::Equal => assert_eq!(Some(Ordering::Equal), cx),
                Ordering::Less => assert_eq!(Some(Ordering::Greater), cx),
                Ordering::Greater => assert_eq!(Some(Ordering::Less), cx),
            }

            // va < vb
            let va = $alr(EndpointOC::$aoc(2));
            let vb = $blr(EndpointOC::$boc(3));

            assert_eq!(va.partial_cmp(&vb), Some(Ordering::Less));
            assert_eq!(vb.partial_cmp(&va), Some(Ordering::Greater));
        };
    }
    #[test]
    fn cmp_test() {
        let left = LeftT::<EndpointOC<i32>>::Left;
        let right = RightT::<EndpointOC<i32>>::Right;
        //Closed, Closed
        cmp_m!(left, Closed, left, Closed, Ordering::Equal);
        //cmp_m!(left, Closed, right, Closed, Ordering::Equal);
        cmp_m!(right, Closed, right, Closed, Ordering::Equal);
        // Closed, Open
        cmp_m!(left, Closed, left, Open, Ordering::Less);
        //cmp_m!(right, Open, left, Closed, Ordering::Less);
        //cmp_m!(right, Closed, left, Open, Ordering::Less);
        cmp_m!(right, Open, right, Closed, Ordering::Less);
        // Open, Open
        cmp_m!(left, Open, left, Open, Ordering::Equal);
        //cmp_m!(right, Open, left, Open, Ordering::Less);
        cmp_m!(right, Open, right, Open, Ordering::Equal);
    }
}