1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use core::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}`.
    #[allow(clippy::as_conversions, clippy::integer_arithmetic)]
    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 {
                        core::char::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}`.
    #[allow(clippy::as_conversions, clippy::integer_arithmetic)]
    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 {
                        core::char::from_u32(succ)
                    }
                }
            }
            Bound::Excluded(_) | Bound::Unbounded => unreachable!(),
        }
    }

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

    /// Returns `\u{10ffff}`.
    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;

    #[allow(clippy::integer_arithmetic, clippy::as_conversions)]
    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_eq!('a'.shares_neighbour_with(&'b'), false);
        assert_eq!('a'.shares_neighbour_with(&'c'), true);
        assert_eq!('c'.shares_neighbour_with(&'a'), true);
        assert_eq!('a'.shares_neighbour_with(&'z'), false);
        assert_eq!('z'.shares_neighbour_with(&'a'), false);
        assert_eq!('A'.shares_neighbour_with(&'Z'), false);
        assert_eq!('Z'.shares_neighbour_with(&'A'), false);
    }
}