ranges 0.4.0

This crate provides a generic alternative to core/std ranges, set-operations to work with them and a range set that can efficiently store them with the least amount of memory possible.
Documentation
use core::{char::from_u32, ops::Bound};

use super::{Domain, Iterable};

/// This implementation recognizes invalid characters and skips them,
/// reserved characters, however, are not.
impl Domain for char {
    const DISCRETE: bool = true;

    /// If the invalid character boundary from the right side is hit, meaning `self` is `\u{0xe000}`,
    /// the returned value will be `\u{0xd7ff}`.
    #[inline]
    #[allow(clippy::as_conversions, clippy::arithmetic_side_effects, clippy::use_self)]
    fn predecessor(&self) -> Option<Self> {
        match Self::minimum() {
            Bound::Included(min) => {
                if *self == min {
                    None
                } else {
                    // we're not at the minimum, but we may hit 0xdfff
                    let pre = *self as u32 - 1;
                    if pre == 0xdfff {
                        Some('\u{d7ff}')
                    } else {
                        from_u32(pre)
                    }
                }
            }
            Bound::Excluded(_) | Bound::Unbounded => unreachable!(),
        }
    }

    /// If the invalid character boundary from the left side is hit, meaning `self` is `\u{0xd7ff}`,
    /// the returned value will be `\u{0xe000}`.
    #[inline]
    #[allow(clippy::as_conversions, clippy::arithmetic_side_effects, clippy::use_self)]
    fn successor(&self) -> Option<Self> {
        match Self::maximum() {
            Bound::Included(max) => {
                if *self == max {
                    None
                } else {
                    // we're not at the maximum, but we may hit 0xd800
                    let succ = *self as u32 + 1;
                    if succ == 0xd800 {
                        Some('\u{e000}')
                    } else {
                        from_u32(succ)
                    }
                }
            }
            Bound::Excluded(_) | Bound::Unbounded => {
                unreachable!()
            }
        }
    }

    /// Returns `\u{0x0}`.
    #[inline]
    fn minimum() -> Bound<Self> {
        Bound::Included('\u{0}')
    }

    /// Returns `\u{10ffff}`.
    #[inline]
    fn maximum() -> Bound<Self> {
        Bound::Included('\u{10ffff}')
    }

    /// This method does _not_ ignore invalid code points.
    ///
    /// # Example
    /// ```
    /// use ranges::Domain;
    ///
    /// assert!(!'\u{df777}'.shares_neighbour_with(&'\u{e001}'));
    /// ```
    #[must_use]
    #[allow(clippy::as_conversions)]
    fn shares_neighbour_with(&self, other: &Self) -> bool {
        (*self as u32).shares_neighbour_with(&(*other as u32))
    }
}

/// Iterates over valid UTF-8 codepoint range. Invalid values are skipped over.
/// ```
/// use ranges::GenericRange;
///
/// let test_range = GenericRange::from('\u{d7ff}'..);
/// let mut iter = test_range.into_iter();
/// assert_eq!(iter.next().unwrap(), '\u{d7ff}');
/// assert_eq!(iter.next().unwrap(), '\u{e000}');
/// assert_eq!(iter.last().unwrap(), '\u{10ffff}');
/// ```
impl Iterable for char {
    type Output = Self;

    #[inline]
    fn next(&self) -> Option<Self::Output> {
        self.successor()
    }
}

#[cfg(test)]
mod tests {
    use crate::domain::Domain;

    #[test]
    fn neighbour_chars() {
        assert!('a'.is_next_to(&'b'));
        assert!(!'\u{0}'.is_next_to(&'\u{10ffff}'));
        assert!('\u{d7ff}'.is_next_to(&'\u{e000}'));
    }
    #[test]
    fn distance_between() {
        assert!(!'a'.shares_neighbour_with(&'b'));
        assert!('a'.shares_neighbour_with(&'c'));
        assert!('c'.shares_neighbour_with(&'a'));
        assert!(!'a'.shares_neighbour_with(&'z'));
        assert!(!'z'.shares_neighbour_with(&'a'));
        assert!(!'A'.shares_neighbour_with(&'Z'));
        assert!(!'Z'.shares_neighbour_with(&'A'));
    }
}