Skip to main content

ncp_matcher/
chars.rs

1//! Utilities for working with (Unicode) characters and codepoints.
2
3use std::fmt::{self, Debug, Display};
4
5use crate::Config;
6#[cfg(feature = "unicode-casefold")]
7use crate::chars::case_fold::CASE_FOLDING_SIMPLE;
8
9// autogenerated by generate-ucd
10#[allow(warnings)]
11#[rustfmt::skip]
12#[cfg(feature = "unicode-casefold")]
13mod case_fold;
14#[cfg(feature = "unicode-normalization")]
15mod canonicalize;
16#[cfg(feature = "unicode-normalization")]
17mod normalize;
18
19pub(crate) trait Char: Copy + Eq + Ord + fmt::Display {
20    const ASCII: bool;
21    fn char_class(self, config: &Config) -> CharClass;
22    fn char_class_and_normalize(self, config: &Config) -> (Self, CharClass);
23    fn normalize(self, config: &Config) -> Self;
24}
25
26/// repr tansparent wrapper around u8 with better formatting and `PartialEq<char>` implementation
27#[repr(transparent)]
28#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
29pub(crate) struct AsciiChar(pub u8);
30
31impl AsciiChar {
32    pub fn cast(bytes: &[u8]) -> &[AsciiChar] {
33        unsafe { &*(bytes as *const [u8] as *const [AsciiChar]) }
34    }
35}
36
37impl fmt::Display for AsciiChar {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        Display::fmt(&(self.0 as char), f)
40    }
41}
42
43impl PartialEq<AsciiChar> for char {
44    fn eq(&self, other: &AsciiChar) -> bool {
45        other.0 as char == *self
46    }
47}
48
49impl Char for AsciiChar {
50    const ASCII: bool = true;
51    #[inline]
52    fn char_class(self, config: &Config) -> CharClass {
53        let c = self.0;
54        // using manual if conditions instead optimizes better
55        if c >= b'a' && c <= b'z' {
56            CharClass::Lower
57        } else if c >= b'A' && c <= b'Z' {
58            CharClass::Upper
59        } else if c >= b'0' && c <= b'9' {
60            CharClass::Number
61        } else if c.is_ascii_whitespace() {
62            CharClass::Whitespace
63        } else if config.delimiter_chars.contains(&c) {
64            CharClass::Delimiter
65        } else {
66            CharClass::NonWord
67        }
68    }
69
70    #[inline(always)]
71    fn char_class_and_normalize(mut self, config: &Config) -> (Self, CharClass) {
72        let char_class = self.char_class(config);
73        if config.ignore_case && char_class == CharClass::Upper {
74            self.0 += 32
75        }
76        (self, char_class)
77    }
78
79    #[inline(always)]
80    fn normalize(mut self, config: &Config) -> Self {
81        if config.ignore_case && self.0 >= b'A' && self.0 <= b'Z' {
82            self.0 += 32
83        }
84        self
85    }
86}
87
88fn char_class_non_ascii(c: char) -> CharClass {
89    if c.is_lowercase() {
90        CharClass::Lower
91    } else if is_upper_case(c) {
92        CharClass::Upper
93    } else if c.is_numeric() {
94        CharClass::Number
95    } else if c.is_alphabetic() {
96        CharClass::Letter
97    } else if c.is_whitespace() {
98        CharClass::Whitespace
99    } else {
100        CharClass::NonWord
101    }
102}
103
104impl Char for char {
105    const ASCII: bool = false;
106    #[inline(always)]
107    fn char_class(self, config: &Config) -> CharClass {
108        if self.is_ascii() {
109            return AsciiChar(self as u8).char_class(config);
110        }
111        char_class_non_ascii(self)
112    }
113
114    #[inline(always)]
115    fn char_class_and_normalize(mut self, config: &Config) -> (Self, CharClass) {
116        if self.is_ascii() {
117            let (c, class) = AsciiChar(self as u8).char_class_and_normalize(config);
118            return (c.0 as char, class);
119        }
120        let char_class = char_class_non_ascii(self);
121        #[cfg(feature = "unicode-casefold")]
122        let mut case_fold = char_class == CharClass::Upper;
123        #[cfg(feature = "unicode-normalization")]
124        if config.normalize {
125            self = normalize::normalize_latin(self);
126            case_fold = true
127        }
128        #[cfg(feature = "unicode-casefold")]
129        if case_fold && config.ignore_case {
130            self = CASE_FOLDING_SIMPLE
131                .binary_search_by_key(&self, |(upper, _)| *upper)
132                .map_or(self, |idx| CASE_FOLDING_SIMPLE[idx].1)
133        }
134        (self, char_class)
135    }
136
137    #[inline(always)]
138    fn normalize(mut self, config: &Config) -> Self {
139        #[cfg(feature = "unicode-normalization")]
140        if config.normalize {
141            self = normalize::normalize_latin(self);
142        }
143        #[cfg(feature = "unicode-casefold")]
144        if config.ignore_case {
145            self = to_lower_case(self)
146        }
147        self
148    }
149}
150
151#[cfg(feature = "unicode-normalization")]
152pub use normalize::normalize_latin;
153
154#[cfg(feature = "unicode-normalization")]
155pub use canonicalize::canonicalize_latin;
156
157#[cfg(feature = "unicode-segmentation")]
158use unicode_segmentation::UnicodeSegmentation;
159
160/// Converts a character to lower case using simple Unicode case folding.
161#[cfg(feature = "unicode-casefold")]
162#[inline(always)]
163pub fn to_lower_case(c: char) -> char {
164    CASE_FOLDING_SIMPLE
165        .binary_search_by_key(&c, |(upper, _)| *upper)
166        .map_or(c, |idx| CASE_FOLDING_SIMPLE[idx].1)
167}
168
169/// Checks if a character is upper case according to simple Unicode case folding.
170///
171/// If the `unicode-casefold` feature is disabled, the equivalent std function is used instead.
172#[inline(always)]
173pub fn is_upper_case(c: char) -> bool {
174    #[cfg(feature = "unicode-casefold")]
175    let val = CASE_FOLDING_SIMPLE
176        .binary_search_by_key(&c, |(upper, _)| *upper)
177        .is_ok();
178    #[cfg(not(feature = "unicode-casefold"))]
179    let val = c.is_uppercase();
180    val
181}
182
183#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Copy, Clone, Hash)]
184pub(crate) enum CharClass {
185    Whitespace,
186    NonWord,
187    Delimiter,
188    Lower,
189    Upper,
190    Letter,
191    Number,
192}
193
194/// Returns an iterator over single-codepoint representations of each grapheme in the provided
195/// text.
196///
197/// This iterates over graphemes and applies the [`canonicalize_latin`] function to each grapheme.
198/// Read its docs for more detail on what this function does.
199///
200/// We must perform this canonicalization since Nucleo cannot match graphemes as single units.
201/// Therefore, we internally map each grapheme to a simpler in-memory representation. This
202/// method is used when constructing `Utf32Str(ing)`.
203pub fn graphemes(text: &str) -> impl Iterator<Item = char> + '_ {
204    #[cfg(feature = "unicode-segmentation")]
205    let res = text.graphemes(true).map(canonicalize_latin);
206    #[cfg(not(feature = "unicode-segmentation"))]
207    let res = text.chars();
208    res
209}