1use std::fmt::{self, Debug, Display};
4
5use crate::Config;
6#[cfg(feature = "unicode-casefold")]
7use crate::chars::case_fold::CASE_FOLDING_SIMPLE;
8
9#[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(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 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#[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#[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
194pub 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}