1#![no_std]
18
19#[cfg(feature = "icu_collections")]
37mod cptrie;
38#[cfg(feature = "icu_collections")]
39mod cptrie_indices;
40mod indices;
41mod report;
42
43#[cfg(feature = "icu_collections")]
44pub use crate::cptrie::Utf8CharsWithTrie;
45#[cfg(feature = "icu_collections")]
46pub use crate::cptrie::Utf8CharsWithTrieDefaultForAscii;
47#[cfg(feature = "icu_collections")]
48pub use crate::cptrie::Utf8CharsWithTrieDefaultForAsciiEx;
49#[cfg(feature = "icu_collections")]
50pub use crate::cptrie::Utf8CharsWithTrieEx;
51#[cfg(feature = "icu_collections")]
52pub use crate::cptrie_indices::Utf8CharIndicesWithTrie;
53#[cfg(feature = "icu_collections")]
54pub use crate::cptrie_indices::Utf8CharIndicesWithTrieDefaultForAscii;
55pub use crate::indices::Utf8CharIndices;
56pub use crate::report::ErrorReportingUtf8Chars;
57pub use crate::report::Utf8CharsError;
58use core::iter::FusedIterator;
59
60#[repr(align(64))] struct Utf8Data {
62 pub table: [u8; 384],
63}
64
65pub(crate) static UTF8_DATA: Utf8Data = Utf8Data {
70 table: [
71 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
72 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
73 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
74 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
75 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
76 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
77 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
78 252, 252, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 148, 148, 148,
79 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 164, 164, 164, 164, 164,
80 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
81 164, 164, 164, 164, 164, 164, 164, 164, 164, 252, 252, 252, 252, 252, 252, 252, 252, 252,
82 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
83 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
84 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
85 252, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
86 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
87 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
88 8, 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 8, 8, 64, 8, 8, 8, 128, 4,
89 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
90 ],
91};
92
93#[inline(always)]
96pub(crate) fn in_inclusive_range8(i: u8, start: u8, end: u8) -> bool {
97 i.wrapping_sub(start) <= (end - start)
98}
99
100#[derive(Debug, Clone)]
103pub struct Utf8Chars<'a> {
104 remaining: &'a [u8],
105}
106
107impl<'a> Utf8Chars<'a> {
108 #[inline(always)]
109 pub fn new(bytes: &'a [u8]) -> Self {
111 Utf8Chars::<'a> { remaining: bytes }
112 }
113
114 #[inline(always)]
117 pub fn as_slice(&self) -> &'a [u8] {
118 self.remaining
119 }
120
121 #[inline(never)]
122 fn next_fallback(&mut self) -> Option<char> {
123 if self.remaining.is_empty() {
124 return None;
125 }
126 let first = self.remaining[0];
127 if first < 0x80 {
128 self.remaining = &self.remaining[1..];
129 return Some(char::from(first));
130 }
131 if !in_inclusive_range8(first, 0xC2, 0xF4) || self.remaining.len() == 1 {
132 self.remaining = &self.remaining[1..];
133 return Some('\u{FFFD}');
134 }
135 let second = self.remaining[1];
136 let (lower_bound, upper_bound) = match first {
137 0xE0 => (0xA0, 0xBF),
138 0xED => (0x80, 0x9F),
139 0xF0 => (0x90, 0xBF),
140 0xF4 => (0x80, 0x8F),
141 _ => (0x80, 0xBF),
142 };
143 if !in_inclusive_range8(second, lower_bound, upper_bound) {
144 self.remaining = &self.remaining[1..];
145 return Some('\u{FFFD}');
146 }
147 if first < 0xE0 {
148 self.remaining = &self.remaining[2..];
149 let point = ((u32::from(first) & 0x1F) << 6) | (u32::from(second) & 0x3F);
150 return Some(unsafe { char::from_u32_unchecked(point) });
151 }
152 if self.remaining.len() == 2 {
153 self.remaining = &self.remaining[2..];
154 return Some('\u{FFFD}');
155 }
156 let third = self.remaining[2];
157 if !in_inclusive_range8(third, 0x80, 0xBF) {
158 self.remaining = &self.remaining[2..];
159 return Some('\u{FFFD}');
160 }
161 if first < 0xF0 {
162 self.remaining = &self.remaining[3..];
163 let point = ((u32::from(first) & 0xF) << 12)
164 | ((u32::from(second) & 0x3F) << 6)
165 | (u32::from(third) & 0x3F);
166 return Some(unsafe { char::from_u32_unchecked(point) });
167 }
168 self.remaining = &self.remaining[3..];
172 Some('\u{FFFD}')
173 }
174}
175
176impl<'a> Iterator for Utf8Chars<'a> {
177 type Item = char;
178
179 #[inline]
180 fn next(&mut self) -> Option<char> {
181 #[allow(clippy::never_loop)]
189 loop {
190 if self.remaining.len() < 4 {
191 break;
192 }
193 let first = self.remaining[0];
194 if first < 0x80 {
195 self.remaining = &self.remaining[1..];
196 return Some(char::from(first));
197 }
198 let second = self.remaining[1];
199 if in_inclusive_range8(first, 0xC2, 0xDF) {
200 if !in_inclusive_range8(second, 0x80, 0xBF) {
201 break;
202 }
203 let point = ((u32::from(first) & 0x1F) << 6) | (u32::from(second) & 0x3F);
204 self.remaining = &self.remaining[2..];
205 return Some(unsafe { char::from_u32_unchecked(point) });
206 }
207 let third = self.remaining[2];
210 if first < 0xF0 {
211 if ((UTF8_DATA.table[usize::from(second)]
212 & UTF8_DATA.table[usize::from(first) + 0x80])
213 | (third >> 6))
214 != 2
215 {
216 break;
217 }
218 let point = ((u32::from(first) & 0xF) << 12)
219 | ((u32::from(second) & 0x3F) << 6)
220 | (u32::from(third) & 0x3F);
221 self.remaining = &self.remaining[3..];
222 return Some(unsafe { char::from_u32_unchecked(point) });
223 }
224 let fourth = self.remaining[3];
225 if (u16::from(
226 UTF8_DATA.table[usize::from(second)] & UTF8_DATA.table[usize::from(first) + 0x80],
227 ) | u16::from(third >> 6)
228 | (u16::from(fourth & 0xC0) << 2))
229 != 0x202
230 {
231 break;
232 }
233 let point = ((u32::from(first) & 0x7) << 18)
234 | ((u32::from(second) & 0x3F) << 12)
235 | ((u32::from(third) & 0x3F) << 6)
236 | (u32::from(fourth) & 0x3F);
237 self.remaining = &self.remaining[4..];
238 return Some(unsafe { char::from_u32_unchecked(point) });
239 }
240 self.next_fallback()
241 }
242}
243
244impl<'a> DoubleEndedIterator for Utf8Chars<'a> {
245 #[inline]
246 fn next_back(&mut self) -> Option<char> {
247 if self.remaining.is_empty() {
248 return None;
249 }
250 let mut attempt = 1;
251 for b in self.remaining.iter().rev() {
252 if b & 0xC0 != 0x80 {
253 let (head, tail) = self.remaining.split_at(self.remaining.len() - attempt);
254 let mut inner = Utf8Chars::new(tail);
255 let candidate = inner.next();
256 if inner.as_slice().is_empty() {
257 self.remaining = head;
258 return candidate;
259 }
260 break;
261 }
262 if attempt == 4 {
263 break;
264 }
265 attempt += 1;
266 }
267
268 self.remaining = &self.remaining[..self.remaining.len() - 1];
269 Some('\u{FFFD}')
270 }
271}
272
273impl FusedIterator for Utf8Chars<'_> {}
274
275pub trait Utf8CharsEx {
278 fn chars(&self) -> Utf8Chars<'_>;
279 fn char_indices(&self) -> Utf8CharIndices<'_>;
280}
281
282impl Utf8CharsEx for [u8] {
283 #[inline]
286 fn chars(&self) -> Utf8Chars<'_> {
287 Utf8Chars::new(self)
288 }
289 #[inline]
292 fn char_indices(&self) -> Utf8CharIndices<'_> {
293 Utf8CharIndices::new(self)
294 }
295}
296
297