icu_collator/elements.rs
1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5// Various collation-related algorithms and constants in this file are
6// adapted from ICU4C and, therefore, are subject to the ICU license as
7// described in LICENSE.
8
9//! This module holds the 64-bit `CollationElement` struct used for
10//! the actual comparison, the 32-bit `CollationElement32` struct
11//! that's used for storage. (Strictly speaking, the storage is
12//! `RawBytesULE<4>`.) And the `CollationElements` iterator adapter
13//! that turns an iterator over `char` into an iterator over
14//! `CollationElement`. (To match the structure of ICU4C, this isn't
15//! a real Rust `Iterator`. Instead of signaling end by returning
16//! `None`, it signals end by returning `NO_CE`.)
17//!
18//! This module also declares various constants that are also used
19//! by the `comparison` module.
20
21use core::char::REPLACEMENT_CHARACTER;
22use core::marker::PhantomData;
23use icu_collections::char16trie::TrieResult;
24use icu_collections::codepointtrie::AbstractCodePointTrie;
25use icu_collections::codepointtrie::WithTrie;
26use icu_normalizer::provider::DecompositionTables;
27use icu_properties::props::CanonicalCombiningClass;
28use smallvec::SmallVec;
29use zerovec::ule::AsULE;
30use zerovec::ule::RawBytesULE;
31use zerovec::{zeroslice, ZeroSlice};
32
33use crate::provider::CollationData;
34
35/// `true` iff `ce32`, when interpreted as `CollationElement32`,
36/// is self-contained.
37#[cfg(feature = "datagen")]
38pub fn is_self_contained(ce32: u32) -> bool {
39 CollationElement32::new(ce32)
40 .to_ce_self_contained()
41 .is_some()
42}
43
44// Start `SmallVec` size constants.
45//
46// These are the on-stack buffer sizes. If the buffers need
47// to grow larger, they are spilled to the heap.
48//
49// TODO(#2005): Figure out good sizes for these.
50
51/// The number of full 64-bit collation units that get buffered
52/// in the primary comparison loop so that they can be examined
53/// by the subsequent comparison stregths.
54///
55/// Note 1: If a primary difference is found, the comparison
56/// returns early, so these buffers end up holding all the
57/// collation elements only if there is no primary difference.
58///
59/// Note 2: Unfortunately for now, a sentinel value signaling
60/// the end of input gets written into the buffer in addition
61/// to the real collation elements.
62///
63/// This should probably either be halved to 4 on the logic
64/// that especially in the presence of the identical prefix
65/// optimization, most comparisons return after a couple of
66/// primary comparisons or increased to 32 on the logic that
67/// such a buffer could better hold a file or human name that
68/// differs on secordary or higher level.
69pub(crate) const CE_BUFFER_SIZE: usize = 8;
70
71/// The number of extra full 64-bit collation units that have
72/// already been computed as part of an operation that yields
73/// multiple collation units at a time.
74const PENDING_CE_BUFFER_SIZE: usize = 6;
75
76/// Either the identical prefix or the lookahead plus the next
77/// upcoming character.
78///
79/// The longest contraction suffix in CLDR 40 is 7 characters long.
80const UPCOMING_CHARACTER_BUFFER_SIZE: usize = 10;
81
82/// The contiguous sequence of combining characters.
83const COMBINING_CHARACTER_BUFFER_SIZE: usize = 7;
84
85/// The sequence of digits in the numeric mode.
86const DIGIT_BUFFER_SIZE: usize = 8;
87
88/// The number of combining characters that a contraction has
89/// matched.
90const PENDING_REMOVALS_SIZE: usize = 1;
91
92// End `SmallVec` constants
93
94/// Marker that the decomposition does not round trip via NFC.
95///
96/// See components/normalizer/trie-value-format.md
97pub(crate) const NON_ROUND_TRIP_MARKER: u32 = 1 << 30;
98
99/// Marker that the first character of the decomposition
100/// can combine backwards.
101///
102/// See components/normalizer/trie-value-format.md
103pub(crate) const BACKWARD_COMBINING_MARKER: u32 = 1 << 31;
104
105/// Mask for the bits have to be zero for this to be a BMP
106/// singleton decomposition, or value baked into the surrogate
107/// range.
108///
109/// See components/normalizer/trie-value-format.md
110pub(crate) const HIGH_ZEROS_MASK: u32 = 0x3FFF0000;
111
112/// Mask for the bits have to be zero for this to be a complex
113/// decomposition.
114///
115/// See components/normalizer/trie-value-format.md
116pub(crate) const LOW_ZEROS_MASK: u32 = 0xFFE0;
117
118/// Marker value for U+FDFA in NFKD. (Unified with
119/// `HANGUL_SYLLABLE_MARKER`, but they differ by
120/// `NON_ROUND_TRIP_MARKER`.)
121///
122/// See components/normalizer/trie-value-format.md
123const FDFA_MARKER: u16 = 1;
124
125/// Marker value for Hangul syllables. (Unified with `FDFA_MARKER`,
126/// but they differ by `NON_ROUND_TRIP_MARKER`.)
127///
128/// See components/normalizer/trie-value-format.md
129pub(crate) const HANGUL_SYLLABLE_MARKER: u32 = 1;
130
131/// Checks if a trie value carries a (non-zero) canonical
132/// combining class.
133///
134/// See components/normalizer/trie-value-format.md
135fn trie_value_has_ccc(trie_value: u32) -> bool {
136 (trie_value & 0x3FFFFE00) == 0xD800
137}
138
139/// Checks if the trie signifies a special non-starter decomposition.
140///
141/// See components/normalizer/trie-value-format.md
142fn trie_value_indicates_special_non_starter_decomposition(trie_value: u32) -> bool {
143 (trie_value & 0x3FFFFF00) == 0xD900
144}
145
146/// Checks if a trie value signifies a character whose decomposition
147/// starts with a non-starter.
148///
149/// See components/normalizer/trie-value-format.md
150fn decomposition_starts_with_non_starter(trie_value: u32) -> bool {
151 trie_value_has_ccc(trie_value)
152}
153
154/// Extracts a canonical combining class (possibly zero) from a trie value.
155///
156/// See components/normalizer/trie-value-format.md
157fn ccc_from_trie_value(trie_value: u32) -> CanonicalCombiningClass {
158 if trie_value_has_ccc(trie_value) {
159 CanonicalCombiningClass::from_icu4c_value(trie_value as u8)
160 } else {
161 CanonicalCombiningClass::NotReordered
162 }
163}
164
165// These constants originate from page 143 of Unicode 14.0
166pub(crate) const HANGUL_S_BASE: u32 = 0xAC00;
167pub(crate) const HANGUL_L_BASE: u32 = 0x1100;
168pub(crate) const HANGUL_V_BASE: u32 = 0x1161;
169pub(crate) const HANGUL_T_BASE: u32 = 0x11A7;
170pub(crate) const HANGUL_T_COUNT: u32 = 28;
171pub(crate) const HANGUL_N_COUNT: u32 = 588;
172pub(crate) const HANGUL_S_COUNT: u32 = 11172;
173
174pub(crate) const JAMO_COUNT: usize = 256; // 0x1200 - 0x1100
175
176const COMBINING_DIACRITICS_BASE: usize = 0x0300;
177const OPTIMIZED_DIACRITICS_LIMIT: usize = 0x034F;
178pub(crate) const OPTIMIZED_DIACRITICS_MAX_COUNT: usize =
179 OPTIMIZED_DIACRITICS_LIMIT - COMBINING_DIACRITICS_BASE;
180
181pub(crate) const CASE_MASK: u16 = 0xC000;
182pub(crate) const TERTIARY_MASK: u16 = 0x3F3F; // ONLY_TERTIARY_MASK in ICU4C
183pub(crate) const QUATERNARY_MASK: u16 = 0xC0;
184
185/// A CE32 is special if its low byte is this or greater.
186/// Impossible case bits 11 mark special CE32s.
187pub const SPECIAL_CE32_LOW_BYTE: u8 = 0xC0;
188/// This value itself is used to indicate a fallback to the root collation.
189pub(crate) const FALLBACK_CE32: CollationElement32 =
190 CollationElement32(SPECIAL_CE32_LOW_BYTE as u32);
191const LONG_PRIMARY_CE32_LOW_BYTE: u8 = 0xC1; // SPECIAL_CE32_LOW_BYTE | LONG_PRIMARY_TAG
192/// Like `LONG_PRIMARY_CE32_LOW_BYTE` but for reorderables.
193pub const LONG_PRIMARY_FOR_REORDERABLE_CE32_LOW_BYTE: u8 = 0xCB; // SPECIAL_CE32_LOW_BYTE | LONG_PRIMARY_FOR_REORDERABLE_TAG
194/// Used only as a placeholder on the indentical prefix path.
195/// The requirement is that this CE32 fails the quick mapping to a primary,
196/// which is does, because the tag byte is higher than
197/// `LONG_PRIMARY_CE32_LOW_BYTE`.
198pub(crate) const IDENTICAL_PREFIX_HANGUL_MARKER_CE32: CollationElement32 = CollationElement32(0xC2);
199const COMMON_SECONDARY_CE: u64 = 0x05000000;
200const COMMON_TERTIARY_CE: u64 = 0x0500;
201const COMMON_SEC_AND_TER_CE: u64 = COMMON_SECONDARY_CE | COMMON_TERTIARY_CE;
202
203/// The ce32 packing of `COMMON_SEC_AND_TER_CE`.
204#[cfg(feature = "datagen")]
205pub const COMMON_SEC_AND_TER_CE32: u16 = 0x0505;
206
207/// Largest index that can be represented in expansion(32)s.
208#[cfg(feature = "datagen")]
209pub const MAX_INDEX: usize = 0x7FFFF;
210
211const UNASSIGNED_IMPLICIT_BYTE: u8 = 0xFE;
212
213// /// Set if there is no match for the single (no-suffix) character itself.
214// /// This is only possible if there is a prefix.
215// /// In this case, discontiguous contraction matching cannot add combining marks
216// /// starting from an empty suffix.
217// /// The default CE32 is used anyway if there is no suffix match.
218// const CONTRACT_SINGLE_CP_NO_MATCH: u32 = 0x100;
219
220/// Set if the first character of every contraction suffix has lccc!=0.
221const CONTRACT_NEXT_CCC: u32 = 0x200;
222/// Set if any contraction suffix ends with lccc!=0.
223const CONTRACT_TRAILING_CCC: u32 = 0x400;
224/// Set if at least one contraction suffix contains a starter
225const CONTRACT_HAS_STARTER: u32 = 0x800;
226
227// const NO_CE32: CollationElement32 = CollationElement32::default();
228// constants named NO_CE* : End of input. Only used in runtime code, not stored in data.
229pub(crate) const NO_CE: CollationElement = CollationElement::default();
230pub(crate) const NO_CE_PRIMARY: u32 = 1; // not a left-adjusted weight
231 // const NO_CE_NON_PRIMARY: NonPrimary = NonPrimary::default();
232pub(crate) const NO_CE_SECONDARY: u16 = 0x0100;
233pub(crate) const NO_CE_TERTIARY: u16 = 0x0100;
234pub(crate) const NO_CE_QUATERNARY: u16 = 0x0100;
235const NO_CE_VALUE: u64 =
236 ((NO_CE_PRIMARY as u64) << 32) | ((NO_CE_SECONDARY as u64) << 16) | (NO_CE_TERTIARY as u64); // 0x101000100
237
238// See ICU4C collation.h and https://www.unicode.org/reports/tr10/#Trailing_Weights
239pub(crate) const FFFD_PRIMARY: u32 = 0xFFFD0000; // U+FFFD
240pub(crate) const FFFD_CE_VALUE: u64 = ((FFFD_PRIMARY as u64) << 32) | COMMON_SEC_AND_TER_CE;
241pub(crate) const FFFD_CE: CollationElement = CollationElement(FFFD_CE_VALUE);
242pub(crate) const FFFD_CE32_VALUE: u32 = 0xFFFD0505;
243pub(crate) const FFFD_CE32: CollationElement32 = CollationElement32(FFFD_CE32_VALUE);
244
245pub(crate) const EMPTY_U16: &ZeroSlice<u16> = zeroslice![];
246const SINGLE_REPLACEMENT_CHARACTER_U16: &ZeroSlice<u16> =
247 zeroslice!(u16; <u16 as AsULE>::ULE::from_unsigned; [REPLACEMENT_CHARACTER as u16]);
248
249pub(crate) const EMPTY_CHAR: &ZeroSlice<char> = zeroslice![];
250const SINGLE_REPLACEMENT_CHARACTER_CHAR: &ZeroSlice<char> =
251 zeroslice!(char; <char as AsULE>::ULE::from_aligned; [REPLACEMENT_CHARACTER]);
252
253/// If `opt` is `Some`, unwrap it. If `None`, panic if debug assertions
254/// are enabled and return `default` if debug assertions are not enabled.
255///
256/// Use this only if the only reason why `opt` could be `None` is bogus
257/// data from the provider.
258#[inline(always)]
259pub(crate) fn unwrap_or_gigo<T>(opt: Option<T>, default: T) -> T {
260 if let Some(val) = opt {
261 val
262 } else {
263 // GIGO case
264 debug_assert!(false);
265 default
266 }
267}
268
269/// Convert a `u32` _obtained from data provider data_ to `char`.
270#[inline(always)]
271pub(crate) fn char_from_u32(u: u32) -> char {
272 unwrap_or_gigo(core::char::from_u32(u), REPLACEMENT_CHARACTER)
273}
274
275/// Convert a `u16` _obtained from data provider data_ to `char`.
276#[inline(always)]
277fn char_from_u16(u: u16) -> char {
278 char_from_u32(u32::from(u))
279}
280
281#[inline(always)]
282fn in_inclusive_range(c: char, start: char, end: char) -> bool {
283 u32::from(c).wrapping_sub(u32::from(start)) <= (u32::from(end) - u32::from(start))
284}
285
286/// Special-CE32 tags, from bits 3..0 of a special 32-bit CE.
287/// Bits 31..8 are available for tag-specific data.
288/// Bits 5..4: Reserved. May be used in the future to indicate lccc!=0 and tccc!=0.
289#[derive(Eq, PartialEq, Debug)]
290#[allow(dead_code)]
291#[repr(u8)] // This repr is necessary for transmute safety
292pub enum Tag {
293 /// Fall back to the base collator.
294 /// This is the tag value in [`SPECIAL_CE32_LOW_BYTE`] and [`FALLBACK_CE32`].
295 /// Bits 31..8: Unused, 0.
296 Fallback = 0,
297 /// Long-primary CE with [`COMMON_SEC_AND_TER_CE`].
298 ///
299 /// When this occurs directly in the trie (not behind an further lookup like
300 /// `Expansion32`) and we know we haven't loaded old-format data, this further
301 /// signals that the canonical combining class of the character is zero.
302 /// See also [`LongPrimaryForReorderable`].
303 ///
304 /// Bits 31..8: Three-byte primary.
305 LongPrimary = 1,
306 /// Long-secondary CE with zero primary.
307 /// Bits 31..16: Secondary weight.
308 /// Bits 15.. 8: Tertiary weight.
309 LongSecondary = 2,
310 /// Unused.
311 /// May be used in the future for single-byte secondary CEs (`SHORT_SECONDARY_TAG`),
312 /// storing the secondary in bits 31..24, the ccc in bits 23..16,
313 /// and the tertiary in bits 15..8.
314 Reserved3 = 3,
315 /// Latin mini expansions of two simple CEs [pp, 05, tt] [00, ss, 05].
316 /// Bits 31..24: Single-byte primary weight pp of the first CE.
317 /// Bits 23..16: Tertiary weight tt of the first CE.
318 /// Bits 15.. 8: Secondary weight ss of the second CE.
319 /// Unused by ICU4X, may get repurposed for jamo expansions is Korean search.
320 LatinExpansion = 4,
321 /// Points to one or more simple/long-primary/long-secondary 32-bit CE32s.
322 /// Bits 31..13: Index into `uint32_t` table.
323 /// Bits 12.. 8: Length=1..31.
324 Expansion32 = 5,
325 /// Points to one or more 64-bit CEs.
326 ///
327 /// When this occurs directly in the trie (not behind an further lookup like
328 /// `Expansion32`) and we know we haven't loaded old-format data, this further
329 /// signals that the canonical combining class of the character is zero.
330 /// See also [`ExpansionForReorderable`].
331 ///
332 /// Bits 31..13: Index into CE table.
333 /// Bits 12.. 8: Length=1..31.
334 Expansion = 6,
335 /// Builder data, used only in the `CollationDataBuilder`, not in runtime data.
336 ///
337 /// If bit 8 is 0: Builder context, points to a list of context-sensitive mappings.
338 /// Bits 31..13: Index to the builder's list of `ConditionalCE32` for this character.
339 /// Bits 12.. 9: Unused, 0.
340 ///
341 /// If bit 8 is 1 (`IS_BUILDER_JAMO_CE32`): Builder-only jamoCE32 value.
342 /// The builder fetches the Jamo CE32 from the trie.
343 /// Bits 31..13: Jamo code point.
344 /// Bits 12.. 9: Unused, 0.
345 BuilderData = 7,
346 /// Points to prefix trie.
347 /// Bits 31..13: Index into prefix/contraction data.
348 /// Bits 12.. 8: Unused, 0.
349 Prefix = 8,
350 /// Points to contraction data.
351 /// Bits 31..13: Index into prefix/contraction data.
352 /// Bits 12..11: Unused, 0.
353 /// Bit 10: `CONTRACT_TRAILING_CCC` flag.
354 /// Bit 9: `CONTRACT_NEXT_CCC` flag.
355 /// Bit 8: `CONTRACT_SINGLE_CP_NO_MATCH` flag.
356 Contraction = 9,
357 /// Decimal digit.
358 /// Bits 31..13: Index into `uint32_t` table for non-numeric-collation CE32.
359 /// Bit 12: Unused, 0.
360 /// Bits 11.. 8: Digit value 0..9.
361 Digit = 10,
362 /// The same as `LongPrimary` but if ccc!=0.
363 ///
364 /// In ICU4C, this is `U0000_TAG`, which is not used by ICU4X:
365 /// Tag for U+0000, for moving the NUL-termination handling
366 /// from the regular fastpath into specials-handling code.
367 /// Bits 31..8: Unused, 0.
368 LongPrimaryForReorderable = 11,
369 /// Tag for a Hangul syllable.
370 /// Bits 31..9: Unused, 0.
371 /// Bit 8: `HANGUL_NO_SPECIAL_JAMO` flag.
372 /// Not used by ICU4X, may get reused for compressing Hanja expansions.
373 Hangul = 12,
374 /// The same as `Expansion` but if ccc!=0.
375 ///
376 /// In ICU4C, this is `LEAD_SURROGATE_TAG`, which is not used by ICU4X:
377 /// Optional optimization for UTF-16 string processing.
378 /// Bits 31..10: Unused, 0.
379 /// 9.. 8: =0: All associated supplementary code points are unassigned-implicit.
380 /// =1: All associated supplementary code points fall back to the base data.
381 /// else: (Normally 2) Look up the data for the supplementary code point.
382 ExpansionForReorderable = 13,
383 /// Tag for CEs with primary weights in code point order.
384 /// Bits 31..13: Index into CE table, for one data "CE".
385 /// Bits 12.. 8: Unused, 0.
386 ///
387 /// This data "CE" has the following bit fields:
388 /// Bits 63..32: Three-byte primary pppppp00.
389 /// 31.. 8: Start/base code point of the in-order range.
390 /// 7: Flag isCompressible primary.
391 /// 6.. 0: Per-code point primary-weight increment.
392 Offset = 14,
393 /// Implicit CE tag. Compute an unassigned-implicit CE.
394 /// All bits are set (`UNASSIGNED_CE32=0xffffffff`).
395 Implicit = 15,
396}
397
398/// A compressed form of a collation element as stored in the collation
399/// data.
400///
401/// A `CollationElement32` can be "normal" or "special".
402/// Bits 7 and 6 are case bits for the "normal" case and setting
403/// both is an impossible case bit combination. Hence, "special"
404/// `CollationElement32`s are marked by setting both case bits
405/// to 1. This is equivalent with the low byte being less than
406/// `SPECIAL_CE32_LOW_BYTE` (0xC0, i.e. 0b11000000) in the "normal"
407/// case and equal to or greater in the "special" case.
408///
409/// For the normal case:
410/// Bits: 31..16: Primary weight
411/// Bits: 15..8: Secondary weight
412/// Bits: 7..6: Case bits (cannot both be 1 simultaneously)
413/// Bits: 5..0: The high part of the discontiguous tertiary weight
414/// (The quaternary weight and the low part of the discontiguous
415/// tertiary weight are zero.)
416///
417/// For the special case:
418/// Bits 31..8: tag-specific; see the documentation for `Tag`.
419/// Bits 7..6: The specialness marker; both bits set to 1
420/// Bits 5..4: Reserved. May be used in the future to indicate lccc!=0 and tccc!=0.
421/// Bits 3..0: the tag (bit-compatible with `Tag`)
422#[derive(Copy, Clone, PartialEq, Debug)]
423pub struct CollationElement32(u32);
424
425impl CollationElement32 {
426 /// Wrap a `u32` in the newtype.
427 #[inline(always)]
428 pub fn new(bits: u32) -> Self {
429 CollationElement32(bits)
430 }
431
432 #[inline(always)]
433 pub(crate) fn new_from_ule(ule: RawBytesULE<4>) -> Self {
434 CollationElement32(u32::from_unaligned(ule))
435 }
436
437 /// Extract the wrapped `u32`.
438 #[cfg(feature = "datagen")]
439 #[inline(always)]
440 pub fn bits(self) -> u32 {
441 self.0
442 }
443
444 #[inline(always)]
445 fn low_byte(self) -> u8 {
446 self.0 as u8
447 }
448
449 /// Return the tag in `Some` if special or `None` if not special.
450 #[inline(always)]
451 pub fn tag_checked(self) -> Option<Tag> {
452 let t = self.low_byte();
453 if t < SPECIAL_CE32_LOW_BYTE {
454 None
455 } else {
456 Some(self.tag())
457 }
458 }
459
460 /// Returns the tag if this element is special.
461 /// Non-specialness should first be checked by seeing if either
462 /// `to_ce_simple_or_long_primary()` or `to_ce_self_contained()`
463 /// returns non-`None`.
464 ///
465 /// # Panics
466 ///
467 /// Panics in debug mode if called on a non-special element.
468 #[inline(always)]
469 pub(crate) fn tag(self) -> Tag {
470 debug_assert!(self.low_byte() >= SPECIAL_CE32_LOW_BYTE);
471 // Safety: Tag has values 0 to 15, which are filtered for with the 0xF mask.
472 unsafe { core::mem::transmute(self.low_byte() & 0xF) }
473 }
474
475 /// Simplest possible check for the Latin1 fast path.
476 ///
477 /// A return value of zero means no primary.
478 #[cfg(feature = "latin1")]
479 #[inline(always)]
480 pub(crate) fn to_primary_simple(self) -> u32 {
481 let t = self.low_byte();
482 if t < SPECIAL_CE32_LOW_BYTE {
483 // Not special
484 self.0 & 0xFFFF0000
485 } else {
486 0
487 }
488 }
489
490 /// Extract only the first primary in the quick check without identical
491 /// prefix.
492 ///
493 /// A return value of zero means no primary.
494 #[inline(always)]
495 pub(crate) fn to_primary_in_quick_check(self, data: &CollationData) -> u32 {
496 let t = self.low_byte();
497 if t < SPECIAL_CE32_LOW_BYTE {
498 // Not special
499 self.0 & 0xFFFF0000
500 } else if t == LONG_PRIMARY_CE32_LOW_BYTE {
501 self.0 - u32::from(t)
502 } else {
503 let tag = self.tag();
504 if tag == Tag::Expansion {
505 // Hiragana in `ja` tailoring
506 data.get_primary_from_ces(self.index())
507 } else {
508 0
509 }
510 // Do not add support for additional tag types here
511 // without ensuring in datagen that reorderable characters
512 // don't end up with CE32s with tags that are handled here.
513 }
514 }
515
516 /// Extract only the first primary in the quick check after the identical
517 /// prefix. Unlike `to_primary_in_quick_check`, this method variant can
518 /// handle `Tag::Digit` if the numeric mode is not enabled. (The numeric
519 /// mode requires looking ahead.)
520 ///
521 /// A return value of zero means no primary.
522 #[inline(always)]
523 pub(crate) fn to_primary_in_quick_check_numeric(
524 self,
525 data: &CollationData,
526 numeric: bool,
527 ) -> u32 {
528 let mut ce32 = self;
529 loop {
530 let t = ce32.low_byte();
531 if t < SPECIAL_CE32_LOW_BYTE {
532 // Not special
533 return ce32.0 & 0xFFFF0000;
534 }
535 if t == LONG_PRIMARY_CE32_LOW_BYTE {
536 return ce32.0 - u32::from(t);
537 }
538 let tag = ce32.tag();
539 if tag == Tag::Expansion {
540 // Hiragana in `ja` tailoring
541 return data.get_primary_from_ces(ce32.index());
542 }
543 // Digit case for JetStream 3; see https://github.com/WebKit/JetStream/issues/294
544 if tag == Tag::Digit && !numeric {
545 ce32 = data.get_ce32(ce32.index());
546 continue;
547 }
548 return 0;
549 // Do not add support for additional tag types here
550 // without ensuring in datagen that reorderable characters
551 // don't end up with CE32s with tags that are handled here.
552 }
553 }
554
555 /// Expands to 64 bits if the expansion is to a single 64-bit collation
556 /// element and is not a long-secondary expansion.
557 #[inline(always)]
558 pub(crate) fn to_ce_simple_or_long_primary(self) -> Option<CollationElement> {
559 let t = self.low_byte();
560 if t < SPECIAL_CE32_LOW_BYTE {
561 // Not special
562 let as64 = u64::from(self.0);
563 Some(CollationElement::new(
564 ((as64 & 0xFFFF0000) << 32) | ((as64 & 0xFF00) << 16) | (u64::from(t) << 8),
565 ))
566 } else if t == LONG_PRIMARY_CE32_LOW_BYTE || t == LONG_PRIMARY_FOR_REORDERABLE_CE32_LOW_BYTE
567 {
568 let as64 = u64::from(self.0);
569 Some(CollationElement::new(
570 ((as64 - u64::from(t)) << 32) | COMMON_SEC_AND_TER_CE,
571 ))
572 } else {
573 // Could still be long secondary (or not self-contained at all).
574 // See `to_ce_self_contained()`.
575 None
576 }
577 }
578
579 /// Expands to 64 bits if the expansion is to a single 64-bit collation
580 /// element.
581 #[inline(always)]
582 pub(crate) fn to_ce_self_contained(self) -> Option<CollationElement> {
583 if let Some(ce) = self.to_ce_simple_or_long_primary() {
584 return Some(ce);
585 }
586 if self.tag() == Tag::LongSecondary {
587 Some(CollationElement::new(u64::from(self.0 & 0xffffff00)))
588 } else {
589 None
590 }
591 }
592
593 /// Expands to 64 bits if the expansion is to a single 64-bit collation
594 /// element or otherwise returns the collation element for U+FFFD.
595 #[inline(always)]
596 pub(crate) fn to_ce_self_contained_or_gigo(self) -> CollationElement {
597 unwrap_or_gigo(self.to_ce_self_contained(), FFFD_CE)
598 }
599
600 /// Gets the length from this element.
601 ///
602 /// # Panics
603 ///
604 /// In debug builds if this element doesn't have a length.
605 #[inline(always)]
606 pub(crate) fn len(self) -> usize {
607 debug_assert!(
608 self.tag() == Tag::Expansion32
609 || self.tag() == Tag::Expansion
610 || self.tag() == Tag::ExpansionForReorderable
611 );
612 ((self.0 >> 8) & 31) as usize
613 }
614
615 /// Gets the index from this element.
616 ///
617 /// # Panics
618 ///
619 /// In debug builds if this element doesn't have an index.
620 #[inline(always)]
621 pub(crate) fn index(self) -> usize {
622 debug_assert!(
623 self.tag() == Tag::Expansion32
624 || self.tag() == Tag::Expansion
625 || self.tag() == Tag::ExpansionForReorderable
626 || self.tag() == Tag::Contraction
627 || self.tag() == Tag::Digit
628 || self.tag() == Tag::Prefix
629 || self.tag() == Tag::Offset
630 );
631 (self.0 >> 13) as usize
632 }
633
634 #[inline(always)]
635 pub(crate) fn digit(self) -> u8 {
636 debug_assert!(self.tag() == Tag::Digit);
637 ((self.0 >> 8) & 0xF) as u8
638 }
639
640 #[inline(always)]
641 pub(crate) fn every_suffix_starts_with_combining(self) -> bool {
642 debug_assert!(self.tag() == Tag::Contraction);
643 (self.0 & CONTRACT_NEXT_CCC) != 0
644 }
645 #[inline(always)]
646 pub(crate) fn at_least_one_suffix_contains_starter(self) -> bool {
647 debug_assert!(self.tag() == Tag::Contraction);
648 (self.0 & CONTRACT_HAS_STARTER) != 0
649 }
650 #[inline(always)]
651 pub(crate) fn at_least_one_suffix_ends_with_non_starter(self) -> bool {
652 debug_assert!(self.tag() == Tag::Contraction);
653 (self.0 & CONTRACT_TRAILING_CCC) != 0
654 }
655}
656
657impl Default for CollationElement32 {
658 fn default() -> Self {
659 CollationElement32(1) // NO_CE32
660 }
661}
662
663/// A collation element is a 64-bit value.
664///
665/// Bits 63..32 are the primary weight.
666/// Bits 31..16 are the secondary weight.
667/// Bits 15..14 are the case bits.
668/// Bits 13..8 and 5..0 are the (bitwise discontiguous) tertiary weight.
669/// Bits 7..6 the quaternary weight.
670#[derive(Copy, Clone, Debug, PartialEq)]
671pub(crate) struct CollationElement(u64);
672
673impl CollationElement {
674 #[inline(always)]
675 pub fn new(bits: u64) -> Self {
676 CollationElement(bits)
677 }
678
679 #[inline(always)]
680 pub fn new_from_primary(primary: u32) -> Self {
681 CollationElement((u64::from(primary) << 32) | COMMON_SEC_AND_TER_CE)
682 }
683
684 #[inline(always)]
685 pub fn new_from_secondary(secondary: u16) -> Self {
686 CollationElement((u64::from(secondary) << 16) | COMMON_TERTIARY_CE)
687 }
688
689 #[inline(always)]
690 pub fn new_implicit_from_char(c: char) -> Self {
691 // Collation::unassignedPrimaryFromCodePoint
692 // Create a gap before U+0000. Use c-1 for [first unassigned].
693 let mut c_with_offset = u32::from(c) + 1;
694 // Fourth byte: 18 values, every 14th byte value (gap of 13).
695 let mut primary: u32 = 2 + (c_with_offset % 18) * 14;
696 c_with_offset /= 18;
697 // Third byte: 254 values
698 primary |= (2 + (c_with_offset % 254)) << 8;
699 c_with_offset /= 254;
700 // Second byte: 251 values 04..FE excluding the primary compression bytes.
701 primary |= (4 + (c_with_offset % 251)) << 16;
702 // One lead byte covers all code points (c < 0x1182B4 = 1*251*254*18).
703 primary |= u32::from(UNASSIGNED_IMPLICIT_BYTE) << 24;
704 CollationElement::new_from_primary(primary)
705 }
706
707 #[inline(always)]
708 pub fn clone_with_non_primary_zeroed(self) -> Self {
709 CollationElement(self.0 & 0xFFFFFFFF00000000)
710 }
711
712 /// Get the primary weight
713 #[inline(always)]
714 pub fn primary(self) -> u32 {
715 (self.0 >> 32) as u32
716 }
717
718 /// Get the non-primary weights
719 #[inline(always)]
720 pub fn non_primary(self) -> NonPrimary {
721 NonPrimary::new(self.0 as u32)
722 }
723
724 /// Get the secondary weight
725 #[inline(always)]
726 pub fn secondary(self) -> u16 {
727 self.non_primary().secondary()
728 }
729 #[inline(always)]
730 pub fn quaternary(self) -> u32 {
731 self.non_primary().quaternary()
732 }
733 #[inline(always)]
734 pub fn tertiary_ignorable(self) -> bool {
735 self.non_primary().tertiary_ignorable()
736 }
737 #[inline(always)]
738 pub fn either_half_zero(self) -> bool {
739 self.primary() == 0 || (self.0 as u32) == 0
740 }
741
742 #[inline(always)]
743 pub const fn default() -> CollationElement {
744 CollationElement(NO_CE_VALUE) // NO_CE
745 }
746}
747
748impl Default for CollationElement {
749 #[inline(always)]
750 fn default() -> Self {
751 CollationElement(NO_CE_VALUE) // NO_CE
752 }
753}
754
755impl Default for &CollationElement {
756 #[inline(always)]
757 fn default() -> Self {
758 &CollationElement(NO_CE_VALUE) // NO_CE
759 }
760}
761
762/// The purpose of grouping the non-primary bits
763/// into a struct is to allow for a future optimization
764/// that specializes code over whether storage for primary
765/// weights is needed or not. (I.e. whether to specialize
766/// on `CollationElement` or `NonPrimary`.)
767#[derive(Copy, Clone, PartialEq, Debug)]
768pub(crate) struct NonPrimary(u32);
769
770impl NonPrimary {
771 /// Constructor
772 pub fn new(bits: u32) -> Self {
773 NonPrimary(bits)
774 }
775 /// Get the bits
776 pub fn bits(self) -> u32 {
777 self.0
778 }
779 /// Get the secondary weight
780 #[inline(always)]
781 pub fn secondary(self) -> u16 {
782 (self.0 >> 16) as u16
783 }
784 /// Get the case bits as the high two bits of a u16
785 #[inline(always)]
786 pub fn case(self) -> u16 {
787 (self.0 as u16) & CASE_MASK
788 }
789 /// Get the tertiary weight as u16 with the high
790 /// two bits of each half zeroed.
791 #[inline(always)]
792 pub fn tertiary(self) -> u16 {
793 (self.0 as u16) & TERTIARY_MASK
794 }
795 #[inline(always)]
796 pub fn tertiary_ignorable(self) -> bool {
797 (self.0 as u16) <= NO_CE_TERTIARY
798 }
799 /// Get the quaternary weight in the original
800 /// storage bit positions with the other bits
801 /// set to one.
802 #[inline(always)]
803 pub fn quaternary(self) -> u32 {
804 self.0 | !(QUATERNARY_MASK as u32)
805 }
806 /// Get any combination of tertiary, case, and quaternary
807 /// by mask.
808 #[inline(always)]
809 pub fn tertiary_case_quarternary(self, mask: u16) -> u16 {
810 debug_assert!((mask & CASE_MASK) == CASE_MASK || (mask & CASE_MASK) == 0);
811 debug_assert!((mask & TERTIARY_MASK) == TERTIARY_MASK || (mask & TERTIARY_MASK) == 0);
812 debug_assert!((mask & QUATERNARY_MASK) == QUATERNARY_MASK || (mask & QUATERNARY_MASK) == 0);
813 (self.0 as u16) & mask
814 }
815
816 #[inline(always)]
817 pub fn case_quaternary(self) -> u16 {
818 (self.0 as u16) & (CASE_MASK | QUATERNARY_MASK)
819 }
820
821 #[inline(always)]
822 pub fn ignorable(self) -> bool {
823 self.0 == 0
824 }
825}
826
827impl Default for NonPrimary {
828 #[inline(always)]
829 fn default() -> Self {
830 NonPrimary(0x01000100) // Low 32 bits of NO_CE
831 }
832}
833
834/// This struct makes the handling of the `upcoming` buffer
835/// easily so that trie lookups are done at most once. However,
836/// when `upcoming[0]` is an undecomposed starter, we don't
837/// need the ccc yet, and when lookahead has already done the
838/// trie lookups, we don't need `trie_value`, as it is implied
839/// by ccc.
840//
841// TODO(#2386): This struct carries redundant information, and
842// `upcoming` should be split into a buffer of `CharacterAndClass`
843// and an `Option<CharacterAndTrieValue>`, but that refactoring
844// isn't 100% necessary, so focusing on data format stability
845// for 1.0.
846//
847// (Deliberately non-`Copy`, because `CharacterAndClass` is
848// non-`Copy`.)
849#[derive(Debug, Clone)]
850pub(crate) struct CharacterAndClassAndTrieValue {
851 c_and_c: CharacterAndClass,
852 pub trie_val: u32,
853}
854
855impl CharacterAndClassAndTrieValue {
856 pub fn new_with_non_decomposing_starter(c: char) -> Self {
857 CharacterAndClassAndTrieValue {
858 c_and_c: CharacterAndClass::new(c, CanonicalCombiningClass::NotReordered),
859 trie_val: 0,
860 }
861 }
862 pub fn new_with_non_zero_ccc(c: char, ccc: CanonicalCombiningClass) -> Self {
863 CharacterAndClassAndTrieValue {
864 c_and_c: CharacterAndClass::new(c, ccc),
865 trie_val: 0xD800 | u32::from(ccc.to_icu4c_value()),
866 }
867 }
868 pub fn new_with_non_special_decomposition_trie_val(c: char, trie_val: u32) -> Self {
869 debug_assert!(!trie_value_indicates_special_non_starter_decomposition(
870 trie_val
871 ));
872 CharacterAndClassAndTrieValue {
873 c_and_c: CharacterAndClass::new_with_trie_value(c, trie_val),
874 trie_val,
875 }
876 }
877 pub fn new_with_trie_val(c: char, trie_val: u32) -> Self {
878 if !trie_value_indicates_special_non_starter_decomposition(trie_val) {
879 CharacterAndClassAndTrieValue {
880 c_and_c: CharacterAndClass::new_with_trie_value(c, trie_val),
881 trie_val,
882 }
883 } else {
884 CharacterAndClassAndTrieValue {
885 c_and_c: CharacterAndClass::new(c, CanonicalCombiningClass::from_icu4c_value(0xFF)),
886 trie_val,
887 }
888 }
889 }
890
891 pub fn decomposition_starts_with_non_starter(&self) -> bool {
892 decomposition_starts_with_non_starter(self.trie_val)
893 }
894
895 pub fn character(&self) -> char {
896 self.c_and_c.character()
897 }
898
899 fn ccc(&self) -> CanonicalCombiningClass {
900 let ret = self.c_and_c.ccc();
901 debug_assert_ne!(ret, CanonicalCombiningClass::from_icu4c_value(0xFF));
902 ret
903 }
904}
905
906/// Pack a `char` and a `CanonicalCombiningClass` in
907/// 32 bits (the former in the lower 24 bits and the
908/// latter in the high 8 bits). The latter can be
909/// initialized to 0xFF upon creation, in which case
910/// it can be actually set later by calling
911/// `set_ccc_from_trie_if_not_already_set`. This is
912/// a micro optimization to avoid the Canonical
913/// Combining Class trie lookup when there is only
914/// one combining character in a sequence. This type
915/// is intentionally non-`Copy` to get compiler help
916/// in making sure that the class is set on the
917/// instance on which it is intended to be set
918/// and not on a temporary copy.
919///
920/// Note that 0xFF is won't be assigned to an actual
921/// canonical combining class per definition D104
922/// in The Unicode Standard.
923//
924// NOTE: The Pernosco debugger has special knowledge
925// of this struct. Please do not change the bit layout
926// or the crate-module-qualified name of this struct
927// without coordination.
928#[derive(Debug, Clone)]
929// Safety invariant: The low 24 bits are a valid char
930struct CharacterAndClass(u32);
931
932impl CharacterAndClass {
933 pub fn new(c: char, ccc: CanonicalCombiningClass) -> Self {
934 // Safety invariant upheld here: the first half is a valid char
935 // and the second half does not affect the low 24 bits
936 CharacterAndClass(u32::from(c) | (u32::from(ccc.to_icu4c_value()) << 24))
937 }
938 pub fn new_with_placeholder(c: char) -> Self {
939 // Safety invariant upheld here: the first half is a valid char
940 // and the second half does not affect the low 24 bits
941 CharacterAndClass(u32::from(c) | ((0xFF) << 24))
942 }
943 pub fn new_with_trie_value(c: char, trie_value: u32) -> Self {
944 Self::new(c, ccc_from_trie_value(trie_value))
945 }
946 pub fn character(&self) -> char {
947 // Safety: from the safety invariant, this extracts the low 24 bits
948 unsafe { char::from_u32_unchecked(self.0 & 0xFF_FFFF) }
949 }
950 pub fn ccc(&self) -> CanonicalCombiningClass {
951 // Safety invariant upheld here: The argument is outside of the low 24 bits,
952 // and \0 is a valid character
953 CanonicalCombiningClass::from_icu4c_value((self.0 >> 24) as u8)
954 }
955 pub fn character_and_ccc(&self) -> (char, CanonicalCombiningClass) {
956 (self.character(), self.ccc())
957 }
958 pub fn set_ccc_from_trie_if_not_already_set<'data, T: AbstractCodePointTrie<'data, u32>>(
959 &mut self,
960 trie: &T,
961 ) {
962 if self.0 >> 24 != 0xFF {
963 return;
964 }
965 let scalar = self.character();
966 // Safety invariant upheld here: The first half doesn't affect the lower 24 bits,
967 // and the second half was taken from the old `self` which had these invariants upheld already.
968 self.0 = ((ccc_from_trie_value(trie.scalar(scalar)).to_icu4c_value() as u32) << 24)
969 | u32::from(scalar);
970 }
971}
972
973/// Iterator that transforms an iterator over `char` into an iterator
974/// over `CollationElement` with a tailoring.
975/// Not a real Rust iterator: Instead of `None` uses `NO_CE` to indicate
976/// end of iteration to optimize comparison.
977///
978/// It is _extremely_ important for performance that `SmallVec`s not be
979/// moved. To facilitate move-avoidance, this struct has the following
980/// life cycle where `new` returns the struct in a state that is not
981/// yet valid for a `next` call until `init` is called:
982///
983/// 1. `new`.
984/// 2. Some number of calls to `iter_next_before_init` and
985/// `prepend_upcoming_before_init`.
986/// 3. `init`.
987/// 4. Some number of calls to `next`.
988pub(crate) struct CollationElements<'data, I, T>
989where
990 I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32>,
991 T: AbstractCodePointTrie<'data, u32>,
992{
993 /// See components/normalizer/trie-value-format.md for the trie wrapped in `iter`.
994 iter: I,
995 /// Already computed but not yet returned `CollationElement`s.
996 pending: SmallVec<[CollationElement; PENDING_CE_BUFFER_SIZE]>, // TODO(#2005): Figure out good length
997 /// The index of the next item to be returned from `pending`. The purpose
998 /// of this index is to avoid moving the rest of the items.
999 pending_pos: usize,
1000 /// The characters most previously seen (or never-matching placeholders)
1001 /// CLDR, as of 40, has two kinds of prefixes:
1002 /// Prefixes that contain a single starter
1003 /// Prefixes that contain a starter followed by either U+3099 or U+309A
1004 /// Last-pushed is at index 0 and previously-pushed at index 1
1005 prefix: [char; 2],
1006 /// `upcoming` holds the characters that have already been read from
1007 /// `iter` but haven't yet been mapped to `CollationElement`s.
1008 ///
1009 /// Typically, `upcoming` holds one character and corresponds semantically
1010 /// to `pending_unnormalized_starter` in `icu::normalizer::Decomposition`.
1011 /// This is why there isn't a move avoidance optimization similar to
1012 /// `pending_pos` above for this buffer. A complex decomposition, a
1013 /// Hangul syllable followed by a non-starter, or lookahead can cause
1014 /// `pending` to hold more than one `char`.
1015 ///
1016 /// Invariant: `upcoming` is allowed to become empty only after `iter`
1017 /// has been exhausted.
1018 ///
1019 /// Invariant: (Checked by `debug_assert!`) At the start of `next()` call,
1020 /// if `upcoming` isn't empty (with `iter` having been exhausted), the
1021 /// first `char` in `upcoming` must have its decomposition start with a
1022 /// starter.
1023 ///
1024 /// TODO: Reverse the order, since now `insert(0, x)` and `remove(0)`
1025 /// are used more often than `push()` and `pop()`.
1026 upcoming: SmallVec<[CharacterAndClassAndTrieValue; UPCOMING_CHARACTER_BUFFER_SIZE]>,
1027 /// The root collation data.
1028 root: &'data CollationData<'data>,
1029 /// Tailoring if applicable.
1030 tailoring: &'data CollationData<'data>,
1031 /// The `CollationElement32` mapping for the Hangul Jamo block.
1032 ///
1033 /// Note: in ICU4C the jamo table contains only modern jamo. Here, the jamo table contains the whole Unicode block.
1034 jamo: &'data [<u32 as AsULE>::ULE; JAMO_COUNT],
1035 /// The `CollationElement32` mapping for the Combining Diacritical Marks block.
1036 diacritics: &'data ZeroSlice<u16>,
1037 /// NFD complex decompositions on the BMP
1038 scalars16: &'data ZeroSlice<u16>,
1039 /// NFD complex decompositions on supplementary planes
1040 scalars32: &'data ZeroSlice<char>,
1041 /// If numeric mode is enabled, the 8 high bits of the numeric primary.
1042 /// `None` if disabled.
1043 numeric_primary: Option<u8>,
1044 /// Whether the Lithuanian combining dot above handling is enabled.
1045 lithuanian_dot_above: bool,
1046 /// Whether `upcoming` (except the last item) has been normalized already
1047 upcoming_normalized: bool,
1048 #[cfg(debug_assertions)]
1049 /// Whether `iter` has been exhausted
1050 iter_exhausted: bool,
1051 #[cfg(debug_assertions)]
1052 /// Whether `init` has been called
1053 initialized: bool,
1054 _phantom: PhantomData<T>,
1055}
1056
1057impl<'data, I, T> CollationElements<'data, I, T>
1058where
1059 I: Iterator<Item = (char, u32)> + WithTrie<'data, T, u32> + 'data,
1060 T: AbstractCodePointTrie<'data, u32> + 'data,
1061{
1062 #[expect(clippy::too_many_arguments)]
1063 pub fn new(
1064 delegate: I,
1065 root: &'data CollationData,
1066 tailoring: &'data CollationData,
1067 jamo: &'data [<u32 as AsULE>::ULE; JAMO_COUNT],
1068 diacritics: &'data ZeroSlice<u16>,
1069 tables: &'data DecompositionTables,
1070 numeric_primary: Option<u8>,
1071 lithuanian_dot_above: bool,
1072 ) -> Self {
1073 CollationElements::<I, T> {
1074 iter: delegate,
1075 pending: SmallVec::new(),
1076 pending_pos: 0,
1077 prefix: ['\u{FFFF}'; 2],
1078 upcoming: SmallVec::new(),
1079 root,
1080 tailoring,
1081 jamo,
1082 diacritics,
1083 scalars16: &tables.scalars16,
1084 scalars32: &tables.scalars24,
1085 numeric_primary,
1086 lithuanian_dot_above,
1087 upcoming_normalized: false,
1088 #[cfg(debug_assertions)]
1089 iter_exhausted: false,
1090 #[cfg(debug_assertions)]
1091 initialized: false,
1092 _phantom: PhantomData,
1093 }
1094 }
1095
1096 pub fn iter_next_before_init(&mut self) -> Option<CharacterAndClassAndTrieValue> {
1097 #[cfg(debug_assertions)]
1098 debug_assert!(!self.initialized);
1099 self.iter_next()
1100 }
1101
1102 pub fn prepend_upcoming_before_init(&mut self, c: CharacterAndClassAndTrieValue) {
1103 #[cfg(debug_assertions)]
1104 debug_assert!(!self.initialized);
1105 self.upcoming.insert(0, c);
1106 }
1107
1108 pub fn init(&mut self) {
1109 // TODO: Consider removing the invariant that this method upholds.
1110 #[cfg(debug_assertions)]
1111 {
1112 debug_assert!(!self.initialized);
1113 self.initialized = true;
1114 }
1115
1116 loop {
1117 // Ensure the last item is a starter (unless)
1118 // iter exhausted.
1119 if let Some(last) = self.upcoming.last() {
1120 if last.decomposition_starts_with_non_starter() {
1121 // Not using `while let` to be able to set `iter_exhausted`
1122 loop {
1123 if let Some(ch) = self.iter_next() {
1124 let starter = !ch.decomposition_starts_with_non_starter();
1125 self.upcoming.push(ch);
1126 if starter {
1127 break;
1128 }
1129 } else {
1130 #[cfg(debug_assertions)]
1131 {
1132 self.iter_exhausted = true;
1133 }
1134 break;
1135 }
1136 }
1137 }
1138 if let Some(first) = self.upcoming.first() {
1139 if !first.decomposition_starts_with_non_starter() {
1140 return;
1141 }
1142 }
1143 } else {
1144 // Ensure that `upcoming` starts with a starter in the case where
1145 // we get here with an empty `upcoming` due to the identical prefix
1146 // code exiting right away, because the very first code units differ.
1147 if let Some(ch) = self.iter_next() {
1148 let starter = !ch.decomposition_starts_with_non_starter();
1149 self.upcoming.push(ch);
1150 if starter {
1151 return;
1152 }
1153 // Loop back to uphoad the invariant that `upcoming` ends with
1154 // a character whose decomposition starts with a starter unless
1155 // the iterator has been exhausted.
1156 continue;
1157 } else {
1158 #[cfg(debug_assertions)]
1159 {
1160 self.iter_exhausted = true;
1161 }
1162 return;
1163 }
1164 }
1165 break;
1166 }
1167
1168 // The case where upcoming does not start with a starter.
1169 // Ideally, we'd have something more specialized here that would extract
1170 // the code path that `self.next()` runs after dealing with the U+0000.
1171 self.upcoming.insert(
1172 0,
1173 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter('\u{0}'),
1174 ); // Make sure the process always begins with a starter
1175 let _ = self.next(); // Remove the placeholder starter
1176 }
1177
1178 fn iter_next(&mut self) -> Option<CharacterAndClassAndTrieValue> {
1179 let (c, trie_val) = self.iter.next()?;
1180 Some(CharacterAndClassAndTrieValue::new_with_trie_val(
1181 c, trie_val,
1182 ))
1183 }
1184
1185 fn next_internal(&mut self) -> Option<CharacterAndClassAndTrieValue> {
1186 if self.upcoming.is_empty() {
1187 return None;
1188 }
1189 // TODO: something more efficient here.
1190 let ret = self.upcoming.remove(0);
1191 if self.upcoming.is_empty() {
1192 if let Some(c) = self.iter_next() {
1193 self.upcoming.push(c);
1194 } else {
1195 #[cfg(debug_assertions)]
1196 {
1197 self.iter_exhausted = true;
1198 }
1199 }
1200 }
1201 Some(ret)
1202 }
1203
1204 fn maybe_gather_combining(&mut self) {
1205 let Some(first) = self.upcoming.first().cloned() else {
1206 return;
1207 };
1208 if !first.decomposition_starts_with_non_starter() {
1209 return;
1210 }
1211 if self.upcoming.len() == 1 {
1212 // We now have a single character that decomposes to start with
1213 // a non-starter. Decompose it and assign the real canonical combining class.
1214 self.upcoming.clear();
1215 // We just cleared `upcoming`, so we can no longer be in the exceptional
1216 // state tracked by `self.upcoming_normalized` being `false`.
1217 self.upcoming_normalized = true;
1218 self.push_decomposed_combining(first);
1219 } else {
1220 // We end up sorting more times than minimally necessary.
1221 self.ensure_upcoming_normalized();
1222 #[cfg(debug_assertions)]
1223 {
1224 if !self.iter_exhausted {
1225 // `unwrap` OK, because len() must be > 1.
1226 debug_assert!(!self
1227 .upcoming
1228 .last()
1229 .unwrap()
1230 .decomposition_starts_with_non_starter());
1231 }
1232 }
1233 return;
1234 }
1235 // Not using `while let` to be able to set `iter_exhausted`
1236 loop {
1237 if let Some(ch) = self.iter_next() {
1238 if ch.decomposition_starts_with_non_starter() {
1239 self.push_decomposed_combining(ch);
1240 } else {
1241 // Got a new starter
1242 self.upcoming.push(ch);
1243 break;
1244 }
1245 } else {
1246 #[cfg(debug_assertions)]
1247 {
1248 self.iter_exhausted = true;
1249 }
1250 break;
1251 }
1252 }
1253 }
1254
1255 /// Ensures that `upcoming` is normalized to NFD, except:
1256 /// 1. When the last item is a starter, it isn't necessarily normalized.
1257 /// 2. Hangul syllable are unnormalized.
1258 fn ensure_upcoming_normalized(&mut self) {
1259 if self.upcoming_normalized {
1260 return;
1261 }
1262 self.upcoming_normalized = true;
1263 let without_trailing_starter = if let Some((last, head)) = self.upcoming.split_last() {
1264 if !last.decomposition_starts_with_non_starter() {
1265 if head.is_empty() {
1266 // There is a single starter, which isn't required
1267 // to be normalized.
1268 return;
1269 } else {
1270 head
1271 }
1272 } else {
1273 &self.upcoming[..]
1274 }
1275 } else {
1276 // Make the assertion conditional to make CI happy.
1277 #[cfg(debug_assertions)]
1278 debug_assert!(self.iter_exhausted);
1279 return;
1280 };
1281
1282 // It would be better to attempt to normalize in place, but let's do at
1283 // least this.
1284 if without_trailing_starter.iter().all(|c| {
1285 (c.trie_val
1286 & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER | HANGUL_SYLLABLE_MARKER))
1287 == 0
1288 }) {
1289 return;
1290 }
1291
1292 let mut unnormalized = core::mem::take(&mut self.upcoming);
1293 let last_index = unnormalized.len() - 1;
1294 let mut start_combining = 0;
1295 for (i, c) in unnormalized.drain(..).enumerate() {
1296 if c.decomposition_starts_with_non_starter() {
1297 self.push_decomposed_combining(c);
1298 } else if i == last_index {
1299 // Indices are in range by construction, so indexing is OK.
1300 #[expect(clippy::indexing_slicing)]
1301 self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1302 self.upcoming.push(c);
1303 return;
1304 } else {
1305 // Indices are in range by construction, so indexing is OK.
1306 #[expect(clippy::indexing_slicing)]
1307 self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1308 start_combining = self.push_decomposed_starter(c);
1309 }
1310 }
1311 // `init()` is responsible for collecting non-starters up to a
1312 // starter or iterator exhausting into `upcoming`, so if we
1313 // didn't take the early return in the `i == last_index` branch
1314 // above, we are in the iterator exhausted case.
1315
1316 // Make the assertion conditional to make CI happy.
1317 #[cfg(debug_assertions)]
1318 debug_assert!(self.iter_exhausted);
1319 // Indices are in range by construction, so indexing is OK.
1320 #[expect(clippy::indexing_slicing)]
1321 self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1322 }
1323
1324 fn push_decomposed_combining(&mut self, c: CharacterAndClassAndTrieValue) {
1325 if !trie_value_indicates_special_non_starter_decomposition(c.trie_val) {
1326 debug_assert!(trie_value_has_ccc(c.trie_val));
1327 self.upcoming.push(c);
1328 return;
1329 }
1330
1331 // The Tibetan special cases are starters that decompose into non-starters.
1332 match c.character() {
1333 '\u{0340}' => {
1334 // COMBINING GRAVE TONE MARK
1335 self.upcoming
1336 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1337 '\u{0300}',
1338 CanonicalCombiningClass::Above,
1339 ));
1340 }
1341 '\u{0341}' => {
1342 // COMBINING ACUTE TONE MARK
1343 self.upcoming
1344 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1345 '\u{0301}',
1346 CanonicalCombiningClass::Above,
1347 ));
1348 }
1349 '\u{0343}' => {
1350 // COMBINING GREEK KORONIS
1351 self.upcoming
1352 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1353 '\u{0313}',
1354 CanonicalCombiningClass::Above,
1355 ));
1356 }
1357 '\u{0344}' => {
1358 // COMBINING GREEK DIALYTIKA TONOS
1359 self.upcoming
1360 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1361 '\u{0308}',
1362 CanonicalCombiningClass::Above,
1363 ));
1364 self.upcoming
1365 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1366 '\u{0301}',
1367 CanonicalCombiningClass::Above,
1368 ));
1369 }
1370 '\u{0F73}' => {
1371 // TIBETAN VOWEL SIGN II
1372 self.upcoming
1373 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1374 '\u{0F71}',
1375 CanonicalCombiningClass::CCC129,
1376 ));
1377 self.upcoming
1378 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1379 '\u{0F72}',
1380 CanonicalCombiningClass::CCC130,
1381 ));
1382 }
1383 '\u{0F75}' => {
1384 // TIBETAN VOWEL SIGN UU
1385 self.upcoming
1386 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1387 '\u{0F71}',
1388 CanonicalCombiningClass::CCC129,
1389 ));
1390 self.upcoming
1391 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1392 '\u{0F74}',
1393 CanonicalCombiningClass::CCC132,
1394 ));
1395 }
1396 '\u{0F81}' => {
1397 // TIBETAN VOWEL SIGN REVERSED II
1398 self.upcoming
1399 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1400 '\u{0F71}',
1401 CanonicalCombiningClass::CCC129,
1402 ));
1403 self.upcoming
1404 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1405 '\u{0F80}',
1406 CanonicalCombiningClass::CCC130,
1407 ));
1408 }
1409 _ => {
1410 // GIGO case
1411 debug_assert!(false);
1412 }
1413 }
1414 }
1415
1416 fn push_decomposed_starter(&mut self, c: CharacterAndClassAndTrieValue) -> usize {
1417 let mut search_start_combining = false;
1418 let old_len = self.upcoming.len();
1419 // Not inserting early returns below to keep the same structure
1420 // as in the ce32 mapping code.
1421
1422 // Hangul syllable check omitted, because it's fine not to decompose
1423 // Hangul syllables in lookahead, because Hangul isn't allowed to
1424 // participate in contractions, and the trie default is that a character
1425 // is its own decomposition.
1426
1427 // See components/normalizer/trie-value-format.md
1428 let decomposition = c.trie_val;
1429 if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER))
1430 <= HANGUL_SYLLABLE_MARKER
1431 {
1432 // The character is its own decomposition (or Hangul syllable, in which case
1433 // we need to retain `trie_val`).
1434 debug_assert_eq!(
1435 ccc_from_trie_value(decomposition),
1436 CanonicalCombiningClass::NotReordered
1437 );
1438 self.upcoming.push(c);
1439 } else {
1440 let high_zeros = (decomposition & HIGH_ZEROS_MASK) == 0;
1441 let low_zeros = (decomposition & LOW_ZEROS_MASK) == 0;
1442 if !high_zeros && !low_zeros {
1443 // Decomposition into two BMP characters: starter and non-starter
1444 let starter = char_from_u32(decomposition & 0x7FFF);
1445 let low_c = char_from_u32((decomposition >> 15) & 0x7FFF);
1446 self.upcoming
1447 .push(CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(starter));
1448 let trie_value = self.iter.trie().bmp(low_c as u16);
1449 self.upcoming.push(
1450 CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(
1451 low_c, trie_value,
1452 ),
1453 );
1454 } else if high_zeros {
1455 let singleton = decomposition as u16;
1456 debug_assert_ne!(
1457 singleton, FDFA_MARKER,
1458 "How come U+FDFA NFKD marker seen in NFD?"
1459 );
1460 if (singleton & 0xFF00) == 0xD800 {
1461 // We're at the end of the stream, so we aren't dealing with the
1462 // next undecomposed starter but are dealing with an
1463 // already-decomposed non-starter. Just put it back.
1464 self.upcoming.push(c);
1465 // Make the assertion conditional to make CI happy.
1466 #[cfg(debug_assertions)]
1467 debug_assert!(self.iter_exhausted);
1468 } else {
1469 // Decomposition into one BMP character
1470 self.upcoming.push(
1471 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1472 char_from_u16(singleton),
1473 ),
1474 );
1475 }
1476 } else {
1477 debug_assert!(low_zeros);
1478 // Only 12 of 14 bits used as of Unicode 16.
1479 let offset = (((decomposition & !(0b11 << 30)) >> 16) as usize) - 1;
1480 // Only 3 of 4 bits used as of Unicode 16.
1481 let len_bits = decomposition & 0b1111;
1482 let only_non_starters_in_trail = (decomposition & 0b10000) != 0;
1483 if offset < self.scalars16.len() {
1484 let len = (len_bits + 2) as usize;
1485 for u in unwrap_or_gigo(
1486 self.scalars16.get_subslice(offset..offset + len),
1487 SINGLE_REPLACEMENT_CHARACTER_U16, // single instead of empty for consistency with the other code path
1488 )
1489 .iter()
1490 {
1491 let ch = char_from_u16(u);
1492 let trie_value = self.iter.trie().bmp(u);
1493 self.upcoming
1494 .push(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(ch, trie_value));
1495 }
1496 } else {
1497 let len = (len_bits + 1) as usize;
1498 let offset32 = offset - self.scalars16.len();
1499 for ch in unwrap_or_gigo(
1500 self.scalars32.get_subslice(offset32..offset32 + len),
1501 SINGLE_REPLACEMENT_CHARACTER_CHAR, // single instead of empty for consistency with the other code path
1502 )
1503 .iter()
1504 {
1505 let trie_value = self.iter.trie().scalar(ch);
1506 self.upcoming
1507 .push(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(ch, trie_value));
1508 }
1509 }
1510 search_start_combining = !only_non_starters_in_trail;
1511 }
1512 }
1513 if search_start_combining {
1514 // The decomposition contains starters. As of Unicode 14,
1515 // There are two possible patterns:
1516 // BMP: starter, starter, non-starter
1517 // Plane 1: starter, starter.
1518 // However, for forward compatibility, support any combination
1519 // and search for the last starter.
1520 let mut i = self.upcoming.len() - 1;
1521 loop {
1522 if let Some(ch) = self.upcoming.get(i) {
1523 if ch.decomposition_starts_with_non_starter() {
1524 i -= 1;
1525 continue;
1526 }
1527 break;
1528 }
1529 // GIGO case
1530 debug_assert!(false);
1531 // This will wrap to zero below
1532 i = usize::MAX;
1533 break;
1534 }
1535 i + 1
1536 } else {
1537 old_len + 1
1538 }
1539 }
1540
1541 // Decomposes `c`, pushes it to `self.upcoming` (unless the character is
1542 // a Hangul syllable; Hangul isn't allowed to participate in contractions),
1543 // gathers the following combining characters from `self.iter` and the following starter.
1544 // Sorts the combining characters and leaves the starter at the end
1545 // unnormalized. The trailing unnormalized starter doesn't get appended if
1546 // `self.iter` is exhausted.
1547 fn push_decomposed_and_gather_combining(&mut self, c: CharacterAndClassAndTrieValue) {
1548 let start_combining = self.push_decomposed_starter(c);
1549 // Not using `while let` to be able to set `iter_exhausted`
1550 loop {
1551 if let Some(ch) = self.iter_next() {
1552 if ch.decomposition_starts_with_non_starter() {
1553 self.push_decomposed_combining(ch);
1554 } else {
1555 // Got a new starter
1556 // Indices are in range by construction, so indexing is OK.
1557 #[expect(clippy::indexing_slicing)]
1558 self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1559 self.upcoming.push(ch);
1560 return;
1561 }
1562 } else {
1563 #[cfg(debug_assertions)]
1564 {
1565 self.iter_exhausted = true;
1566 }
1567 // Indices are in range by construction, so indexing is OK.
1568 #[expect(clippy::indexing_slicing)]
1569 self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1570 return;
1571 }
1572 }
1573 }
1574
1575 // Assumption: `pos` starts from zero and increases one by one.
1576 // Indexing is OK, because we check against `len()` and the `pos`
1577 // increases one by one by construction.
1578 #[expect(clippy::indexing_slicing)]
1579 fn look_ahead(&mut self, pos: usize) -> Option<CharacterAndClassAndTrieValue> {
1580 debug_assert!(self.upcoming_normalized);
1581 if pos + 1 == self.upcoming.len() {
1582 let c = self.upcoming.remove(pos);
1583 self.push_decomposed_and_gather_combining(c);
1584 Some(self.upcoming[pos].clone())
1585 } else if pos == self.upcoming.len() {
1586 if let Some(c) = self.iter_next() {
1587 debug_assert!(
1588 false,
1589 "The `upcoming` queue should be empty when iteration `pos` at the end"
1590 );
1591 self.push_decomposed_and_gather_combining(c);
1592 Some(self.upcoming[pos].clone())
1593 } else {
1594 #[cfg(debug_assertions)]
1595 {
1596 self.iter_exhausted = true;
1597 }
1598 None
1599 }
1600 } else {
1601 Some(self.upcoming[pos].clone())
1602 }
1603 }
1604
1605 fn is_next_decomposition_starts_with_starter(&self) -> bool {
1606 if let Some(c_c_tv) = self.upcoming.first() {
1607 !c_c_tv.decomposition_starts_with_non_starter()
1608 } else {
1609 true
1610 }
1611 }
1612
1613 fn prepend_and_sort_non_starter_prefix_of_suffix(&mut self, c: CharacterAndClassAndTrieValue) {
1614 // Add one for the insertion afterwards.
1615 let end = 1 + {
1616 let mut iter = self.upcoming.iter().enumerate();
1617 loop {
1618 if let Some((i, ch)) = iter.next() {
1619 if !ch.decomposition_starts_with_non_starter() {
1620 break i;
1621 }
1622 } else {
1623 #[cfg(debug_assertions)]
1624 {
1625 self.iter_exhausted = true;
1626 }
1627 break self.upcoming.len();
1628 }
1629 }
1630 };
1631 let start = c.decomposition_starts_with_non_starter() as usize;
1632 self.upcoming.insert(0, c);
1633 // Indices in range by construction
1634 #[expect(clippy::indexing_slicing)]
1635 {
1636 let slice: &mut [CharacterAndClassAndTrieValue] = &mut self.upcoming[start..end];
1637 slice.sort_by_key(|cc| cc.ccc());
1638 };
1639 }
1640
1641 fn prefix_push(&mut self, c: char) {
1642 self.prefix[1] = self.prefix[0];
1643 self.prefix[0] = c;
1644 }
1645
1646 /// Micro optimization for doing a simpler write when
1647 /// we know the most recent character was a non-starter
1648 /// that is not a kana voicing mark.
1649 fn mark_prefix_unmatchable(&mut self) {
1650 self.prefix[0] = '\u{FFFF}';
1651 }
1652
1653 pub fn next(&mut self) -> CollationElement {
1654 #[cfg(debug_assertions)]
1655 debug_assert!(self.initialized);
1656 debug_assert!(self.is_next_decomposition_starts_with_starter());
1657 if let Some(&ret) = self.pending.get(self.pending_pos) {
1658 self.pending_pos += 1;
1659 if self.pending_pos == self.pending.len() {
1660 self.pending.clear();
1661 self.pending_pos = 0;
1662 }
1663 return ret;
1664 }
1665 debug_assert_eq!(self.pending_pos, 0);
1666 if let Some(c_c_tv) = self.next_internal() {
1667 let mut c = c_c_tv.character();
1668 let mut ce32;
1669 let mut data: &CollationData = self.tailoring;
1670 // TODO: Should this be a reusable buffer on the struct instead of
1671 // getting re-created on the stack every time?
1672 let mut combining_characters: SmallVec<
1673 [CharacterAndClass; COMBINING_CHARACTER_BUFFER_SIZE],
1674 > = SmallVec::new(); // TODO(#2005): Figure out good length
1675
1676 // Betting that fusing the NFD algorithm into this one at the
1677 // expense of the repetitiveness below, the common cases become
1678 // fast in a way that offsets the lack of the canonical closure.
1679 // The wall of code before the "Slow path" is an attempt to
1680 // optimize based on that bet.
1681
1682 // See components/normalizer/trie-value-format.md
1683 let decomposition = c_c_tv.trie_val;
1684 if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0 {
1685 // The character is its own decomposition
1686
1687 // TODO: This is a bad idea. Make sure the jamo are in the root trie and then
1688 // remove this special case.
1689 let jamo_index = (c as usize).wrapping_sub(HANGUL_L_BASE as usize);
1690 // Attribute belongs on an inner expression, but
1691 // https://github.com/rust-lang/rust/issues/15701
1692 #[expect(clippy::indexing_slicing)]
1693 if jamo_index >= self.jamo.len() {
1694 // Note: It might seem like a good idea to reuse the CE32s
1695 // from the identical prefix check here, but the logistics
1696 // actually make everything slower.
1697 ce32 = data.ce32_for_char(c);
1698 if ce32 == FALLBACK_CE32 {
1699 data = self.root;
1700 ce32 = data.ce32_for_char(c);
1701 }
1702 } else {
1703 // The purpose of reading the CE32 from the jamo table instead
1704 // of the trie even in this case is to make it unnecessary
1705 // for all search collation tries to carry a copy of the Hangul
1706 // part of the search root. Instead, all non-Korean tailorings
1707 // can use a shared copy of the non-Korean search jamo table.
1708 //
1709 // TODO(#1941): This isn't actually true with the current jamo
1710 // search expansions!
1711
1712 // TODO(#1941): Instead of having different jamo CE32 table for
1713 // "search" collations, we could instead decompose the archaic
1714 // jamo to the modern approximation sequences here and then map
1715 // those by looking up the modern jamo from the normal root.
1716
1717 // We need to set data to root, because archaic jamo refer to
1718 // the root.
1719 data = self.root;
1720 // Index in range by construction above. Not using `get` with
1721 // `if let` in order to put the likely branch first.
1722 ce32 = CollationElement32::new_from_ule(self.jamo[jamo_index]);
1723 }
1724 if self.is_next_decomposition_starts_with_starter() {
1725 if let Some(ce) = ce32.to_ce_simple_or_long_primary() {
1726 self.prefix_push(c);
1727 return ce;
1728 } else if ce32.tag() == Tag::Contraction
1729 && ce32.every_suffix_starts_with_combining()
1730 {
1731 // Avoid falling onto the slow path e.g. that letters that
1732 // may contract with a diacritic when we know that it won't
1733 // contract with the next character.
1734 let default = data.get_default(ce32.index());
1735 if let Some(ce) = default.to_ce_simple_or_long_primary() {
1736 self.prefix_push(c);
1737 return ce;
1738 }
1739 }
1740 // TODO(2003): Figure out if it would be an optimization to
1741 // handle `Implicit` and `Offset` tags here.
1742 }
1743 } else {
1744 let high_zeros = (decomposition & HIGH_ZEROS_MASK) == 0;
1745 let low_zeros = (decomposition & LOW_ZEROS_MASK) == 0;
1746 if !high_zeros && !low_zeros {
1747 // Decomposition into two BMP characters: starter and non-starter
1748 c = char_from_u32(decomposition & 0x7FFF);
1749 ce32 = data.ce32_for_char(c);
1750 if ce32 == FALLBACK_CE32 {
1751 data = self.root;
1752 ce32 = data.ce32_for_char(c);
1753 }
1754 let combining = char_from_u32((decomposition >> 15) & 0x7FFF);
1755 if self.is_next_decomposition_starts_with_starter() {
1756 let diacritic_index =
1757 (combining as usize).wrapping_sub(COMBINING_DIACRITICS_BASE);
1758 if let Some(secondary) = self.diacritics.get(diacritic_index) {
1759 debug_assert_ne!(combining, '\u{0344}', "Should never have COMBINING GREEK DIALYTIKA TONOS here, since it should have decomposed further.");
1760 if let Some(ce) = ce32.to_ce_simple_or_long_primary() {
1761 let ce_for_combining =
1762 CollationElement::new_from_secondary(secondary);
1763 self.pending.push(ce_for_combining);
1764 self.mark_prefix_unmatchable();
1765 return ce;
1766 }
1767 if ce32.tag() == Tag::Contraction
1768 && ce32.every_suffix_starts_with_combining()
1769 {
1770 let (default, mut trie) = data.get_default_and_trie(ce32.index());
1771 match trie.next(combining) {
1772 TrieResult::NoMatch | TrieResult::NoValue => {
1773 if let Some(ce) = default.to_ce_simple_or_long_primary() {
1774 let ce_for_combining =
1775 CollationElement::new_from_secondary(secondary);
1776 self.pending.push(ce_for_combining);
1777 self.mark_prefix_unmatchable();
1778 return ce;
1779 }
1780 }
1781 TrieResult::Intermediate(trie_ce32) => {
1782 if !ce32.at_least_one_suffix_contains_starter() {
1783 if let Some(ce) =
1784 CollationElement32::new(trie_ce32 as u32)
1785 .to_ce_simple_or_long_primary()
1786 {
1787 self.mark_prefix_unmatchable();
1788 return ce;
1789 }
1790 }
1791 }
1792 TrieResult::FinalValue(trie_ce32) => {
1793 if let Some(ce) = CollationElement32::new(trie_ce32 as u32)
1794 .to_ce_simple_or_long_primary()
1795 {
1796 self.mark_prefix_unmatchable();
1797 return ce;
1798 }
1799 }
1800 }
1801 }
1802 }
1803 }
1804 combining_characters.push(CharacterAndClass::new_with_placeholder(combining));
1805 } else if high_zeros {
1806 // Do the Hangul check on the character instead of trusting
1807 // the trie value in order not to let GIGO cause unsafety.
1808 let hangul_offset = u32::from(c).wrapping_sub(HANGUL_S_BASE); // SIndex in the spec
1809 if hangul_offset < HANGUL_S_COUNT {
1810 // Hangul syllable
1811 // The math here comes from page 144 of Unicode 14.0
1812 let l = hangul_offset / HANGUL_N_COUNT;
1813 let v = (hangul_offset % HANGUL_N_COUNT) / HANGUL_T_COUNT;
1814 let t = hangul_offset % HANGUL_T_COUNT;
1815
1816 // No prefix matches on Hangul
1817 self.mark_prefix_unmatchable();
1818 // Indexing OK, because indices in range by construction
1819 #[expect(clippy::indexing_slicing)]
1820 if self.is_next_decomposition_starts_with_starter() {
1821 // TODO(#1941): Assuming self-contained CE32s is OK for the root,
1822 // but not currently OK for search collation, which at this time
1823 // do not support tailored Hangul.
1824 self.pending.push(
1825 CollationElement32::new_from_ule(
1826 self.jamo[(HANGUL_V_BASE - HANGUL_L_BASE + v) as usize],
1827 )
1828 .to_ce_self_contained_or_gigo(),
1829 );
1830 if t != 0 {
1831 self.pending.push(
1832 CollationElement32::new_from_ule(
1833 self.jamo[(HANGUL_T_BASE - HANGUL_L_BASE + t) as usize],
1834 )
1835 .to_ce_self_contained_or_gigo(),
1836 );
1837 }
1838 return CollationElement32::new_from_ule(self.jamo[l as usize])
1839 .to_ce_self_contained_or_gigo();
1840 }
1841
1842 // Uphold the invariant that the upcoming character is a starter (or end of stream)
1843 // at the start of the next `next()` call. We uphold this invariant by leaving the
1844 // last jamo unmapped to `CollationElement` in `pending` and instead prepend it to
1845 // `upcoming`.
1846 //
1847 // Indexing OK, because indices in range by construction
1848 #[expect(clippy::indexing_slicing)]
1849 if t != 0 {
1850 self.pending.push(
1851 CollationElement32::new_from_ule(
1852 self.jamo[(HANGUL_V_BASE - HANGUL_L_BASE + v) as usize],
1853 )
1854 .to_ce_self_contained_or_gigo(),
1855 );
1856 self.upcoming.insert(
1857 0,
1858 // Safety: HANGUL_T_BASE is 0x11A7, t is < HANGUL_T_COUNT = 28, so this is definitely
1859 // in range for a char (≤ 0xD800)
1860 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1861 unsafe { core::char::from_u32_unchecked(HANGUL_T_BASE + t) },
1862 ),
1863 );
1864 } else {
1865 self.upcoming.insert(
1866 0,
1867 // Safety: HANGUL_V_BASE is 0x1161, v is < HANGUL_N_COUNT = 588, so this is definitely
1868 // in range for a char (≤ 0xD800)
1869 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1870 unsafe { core::char::from_u32_unchecked(HANGUL_V_BASE + v) },
1871 ),
1872 );
1873 }
1874
1875 // Indexing OK, because indices in range by construction
1876 #[expect(clippy::indexing_slicing)]
1877 return CollationElement32::new_from_ule(self.jamo[l as usize])
1878 .to_ce_self_contained_or_gigo();
1879 }
1880
1881 let singleton = decomposition as u16;
1882 debug_assert_ne!(
1883 singleton, FDFA_MARKER,
1884 "How come U+FDFA NFKD marker seen in NFD?"
1885 );
1886 // Decomposition into one BMP character
1887 c = char_from_u16(singleton);
1888 ce32 = data.ce32_for_char(c);
1889 if ce32 == FALLBACK_CE32 {
1890 data = self.root;
1891 ce32 = data.ce32_for_char(c);
1892 }
1893 if self.is_next_decomposition_starts_with_starter() {
1894 if let Some(ce) = ce32.to_ce_simple_or_long_primary() {
1895 self.prefix_push(c);
1896 return ce;
1897 }
1898 }
1899 } else {
1900 debug_assert!(low_zeros);
1901 // Only 12 of 14 bits used as of Unicode 16.
1902 let offset = (((decomposition & !(0b11 << 30)) >> 16) as usize) - 1;
1903 // Only 3 of 4 bits used as of Unicode 16.
1904 let len_bits = decomposition & 0b1111;
1905 let only_non_starters_in_trail = (decomposition & 0b10000) != 0;
1906 if offset < self.scalars16.len() {
1907 let len = (len_bits + 2) as usize;
1908 let (starter, tail) = self
1909 .scalars16
1910 .get_subslice(offset..offset + len)
1911 .and_then(ZeroSlice::split_first)
1912 .map_or_else(
1913 || {
1914 // GIGO case
1915 debug_assert!(false);
1916 (REPLACEMENT_CHARACTER, EMPTY_U16)
1917 },
1918 |(first, tail)| (char_from_u16(first), tail),
1919 );
1920 c = starter;
1921 if only_non_starters_in_trail {
1922 for u in tail.iter() {
1923 let char_from_u = char_from_u16(u);
1924 let trie_value = self.iter.trie().bmp(u);
1925 let ccc = ccc_from_trie_value(trie_value);
1926 combining_characters.push(CharacterAndClass::new(char_from_u, ccc));
1927 }
1928 } else {
1929 let mut it = tail.iter();
1930 while let Some(u) = it.next() {
1931 let ch = char_from_u16(u);
1932 let ccc = ccc_from_trie_value(self.iter.trie().bmp(u));
1933 if ccc != CanonicalCombiningClass::NotReordered {
1934 // As of Unicode 14, this branch is never taken.
1935 // It exist for forward compatibility.
1936 combining_characters.push(CharacterAndClass::new(ch, ccc));
1937 continue;
1938 }
1939
1940 // At this point, we might have a single newly-read
1941 // combining character in self.upcoming. In that case, we
1942 // need to buffer up the upcoming combining characters, too,
1943 // in order to make `prepend_and_sort_non_starter_prefix_of_suffix`
1944 // sort the right characters.
1945 self.maybe_gather_combining();
1946
1947 while let Some(u) = it.next_back() {
1948 let tail_char = char_from_u16(u);
1949 let trie_value = self.iter.trie().bmp(u);
1950 self.prepend_and_sort_non_starter_prefix_of_suffix(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(tail_char, trie_value));
1951 }
1952 self.prepend_and_sort_non_starter_prefix_of_suffix(
1953 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1954 ch,
1955 ),
1956 );
1957 break;
1958 }
1959 }
1960 } else {
1961 let len = (len_bits + 1) as usize;
1962 let offset32 = offset - self.scalars16.len();
1963 let (starter, tail) = self
1964 .scalars32
1965 .get_subslice(offset32..offset32 + len)
1966 .and_then(|slice| slice.split_first())
1967 .unwrap_or_else(|| {
1968 // GIGO case
1969 debug_assert!(false);
1970 (REPLACEMENT_CHARACTER, EMPTY_CHAR)
1971 });
1972
1973 c = starter;
1974 if only_non_starters_in_trail {
1975 for ch in tail.iter() {
1976 let trie_value = self.iter.trie().scalar(ch);
1977 let ccc = ccc_from_trie_value(trie_value);
1978 combining_characters.push(CharacterAndClass::new(ch, ccc));
1979 }
1980 } else {
1981 let mut it = tail.iter();
1982 while let Some(ch) = it.next() {
1983 let ccc = ccc_from_trie_value(self.iter.trie().scalar(ch));
1984 if ccc != CanonicalCombiningClass::NotReordered {
1985 // As of Unicode 14, this branch is never taken.
1986 // It exist for forward compatibility.
1987 combining_characters.push(CharacterAndClass::new(ch, ccc));
1988 continue;
1989 }
1990 // At this point, we might have a single newly-read
1991 // combining character in self.upcoming. In that case, we
1992 // need to buffer up the upcoming combining characters, too,
1993 // in order to make `prepend_and_sort_non_starter_prefix_of_suffix`
1994 // sort the right characters.
1995 self.maybe_gather_combining();
1996
1997 while let Some(tail_char) = it.next_back() {
1998 let trie_value = self.iter.trie().scalar(tail_char);
1999 self.prepend_and_sort_non_starter_prefix_of_suffix(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(tail_char, trie_value));
2000 }
2001 self.prepend_and_sort_non_starter_prefix_of_suffix(
2002 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
2003 ch,
2004 ),
2005 );
2006 break;
2007 }
2008 }
2009 }
2010 ce32 = data.ce32_for_char(c);
2011 if ce32 == FALLBACK_CE32 {
2012 data = self.root;
2013 ce32 = data.ce32_for_char(c);
2014 }
2015 }
2016 }
2017 let mut may_have_contracted_starter = false;
2018 // Slow path
2019 self.collect_combining(&mut combining_characters);
2020 // Now:
2021 // c is the starter character
2022 // ce32 is the CollationElement32 for the starter
2023 // combining_characters contains all the combining characters before
2024 // the next starter sorted by combining class.
2025 let mut looked_ahead = 0;
2026 let mut drain_from_upcoming = 0;
2027 'outer: loop {
2028 'ce32loop: loop {
2029 // TODO(#2002): Ensure that the CE32 flavors in this loop are checked in the optimal
2030 // order given their frequency in real workloads.
2031 if let Some(ce) = ce32.to_ce_self_contained() {
2032 self.pending.push(ce);
2033 break 'ce32loop;
2034 } else {
2035 match ce32.tag() {
2036 Tag::Expansion32 => {
2037 let ce32s = data.get_ce32s(ce32.index(), ce32.len());
2038 for u in ce32s.iter() {
2039 self.pending.push(
2040 CollationElement32::new(u).to_ce_self_contained_or_gigo(),
2041 );
2042 }
2043 break 'ce32loop;
2044 }
2045 Tag::Expansion | Tag::ExpansionForReorderable => {
2046 let ces = data.get_ces(ce32.index(), ce32.len());
2047 for u in ces.iter() {
2048 self.pending.push(CollationElement::new(u));
2049 }
2050 break 'ce32loop;
2051 }
2052 Tag::Prefix => {
2053 let (default, mut trie) = data.get_default_and_trie(ce32.index());
2054 ce32 = default;
2055 for &ch in self.prefix.iter() {
2056 match trie.next(ch) {
2057 TrieResult::NoValue => {}
2058 TrieResult::NoMatch => {
2059 continue 'ce32loop;
2060 }
2061 TrieResult::Intermediate(ce32_i) => {
2062 ce32 = CollationElement32::new(ce32_i as u32);
2063 }
2064 TrieResult::FinalValue(ce32_i) => {
2065 ce32 = CollationElement32::new(ce32_i as u32);
2066 continue 'ce32loop;
2067 }
2068 }
2069 }
2070 continue 'ce32loop;
2071 }
2072 Tag::Contraction => {
2073 let every_suffix_starts_with_combining =
2074 ce32.every_suffix_starts_with_combining();
2075 let at_least_one_suffix_contains_starter =
2076 ce32.at_least_one_suffix_contains_starter();
2077 let at_least_one_suffix_ends_with_non_starter =
2078 ce32.at_least_one_suffix_ends_with_non_starter();
2079 let (default, mut trie) = data.get_default_and_trie(ce32.index());
2080 ce32 = default;
2081 if every_suffix_starts_with_combining
2082 && combining_characters.is_empty()
2083 {
2084 continue 'ce32loop;
2085 }
2086 let mut longest_matching_state = trie.clone();
2087 let mut longest_matching_index = 0;
2088 let mut attempt = 0;
2089 let mut i = 0;
2090 let mut most_recent_skipped_ccc =
2091 CanonicalCombiningClass::NotReordered;
2092 // TODO(#2001): Pending removals will in practice be small numbers.
2093 // What if we made the item smaller than usize?
2094 let mut pending_removals: SmallVec<[usize; PENDING_REMOVALS_SIZE]> =
2095 SmallVec::new();
2096 while let Some((character, ccc)) =
2097 combining_characters.get(i).map(|c| c.character_and_ccc())
2098 {
2099 match (most_recent_skipped_ccc < ccc, trie.next(character)) {
2100 (true, TrieResult::Intermediate(ce32_i)) => {
2101 let _ = combining_characters.remove(i);
2102 while let Some(idx) = pending_removals.pop() {
2103 combining_characters.remove(idx);
2104 i -= 1; // Adjust for the shortening
2105 }
2106 attempt = 0;
2107 longest_matching_index = i;
2108 longest_matching_state = trie.clone();
2109 ce32 = CollationElement32::new(ce32_i as u32);
2110 }
2111 (true, TrieResult::FinalValue(ce32_i)) => {
2112 let _ = combining_characters.remove(i);
2113 while let Some(idx) = pending_removals.pop() {
2114 combining_characters.remove(idx);
2115 }
2116 ce32 = CollationElement32::new(ce32_i as u32);
2117 continue 'ce32loop;
2118 }
2119 (_, TrieResult::NoValue) => {
2120 pending_removals.push(i);
2121 i += 1;
2122 }
2123 _ => {
2124 pending_removals.clear();
2125 most_recent_skipped_ccc = ccc;
2126 attempt += 1;
2127 i = longest_matching_index + attempt;
2128 trie = longest_matching_state.clone();
2129 }
2130 }
2131 }
2132 if !(at_least_one_suffix_contains_starter
2133 && combining_characters.is_empty())
2134 {
2135 continue 'ce32loop;
2136 }
2137 // Let's just set this flag here instead of trying to make
2138 // it more granular and, therefore, more error-prone.
2139 // After all, this flag is just about optimizing away one
2140 // `CodePointInversionList` check in the common case.
2141 may_have_contracted_starter = true;
2142 debug_assert!(pending_removals.is_empty());
2143 self.ensure_upcoming_normalized();
2144 loop {
2145 let ahead = self.look_ahead(looked_ahead);
2146 looked_ahead += 1;
2147 if let Some(ch) = ahead {
2148 match trie.next(ch.character()) {
2149 TrieResult::NoValue => {}
2150 TrieResult::NoMatch => {
2151 if !at_least_one_suffix_ends_with_non_starter {
2152 continue 'ce32loop;
2153 }
2154 if !ch.decomposition_starts_with_non_starter() {
2155 continue 'ce32loop;
2156 }
2157 // The last-checked character is non-starter
2158 // and at least one contraction suffix ends
2159 // with a non-starter. Try a discontiguous
2160 // match.
2161 trie = longest_matching_state.clone();
2162 // For clarity, mint a new set of variables that
2163 // behave consistently with the
2164 // `combining_characters` case
2165 let mut longest_matching_index = 0;
2166 let mut attempt = 0;
2167 let mut i = 0;
2168 most_recent_skipped_ccc = ch.ccc();
2169 self.ensure_upcoming_normalized();
2170 loop {
2171 let ahead = self.look_ahead(looked_ahead + i);
2172 if let Some(ch) = ahead {
2173 let ccc = ch.ccc();
2174 if ccc
2175 == CanonicalCombiningClass::NotReordered
2176 {
2177 // If we came here, we had an intervening non-matching
2178 // non-starter, after which we cannot contract another
2179 // starter anymore.
2180 continue 'ce32loop;
2181 }
2182 match (
2183 most_recent_skipped_ccc < ccc,
2184 trie.next(ch.character()),
2185 ) {
2186 (
2187 true,
2188 TrieResult::Intermediate(ce32_i),
2189 ) => {
2190 let _ = self
2191 .upcoming
2192 .remove(looked_ahead + i);
2193 while let Some(idx) =
2194 pending_removals.pop()
2195 {
2196 self.upcoming
2197 .remove(looked_ahead + idx);
2198 i -= 1; // Adjust for the shortening
2199 }
2200 attempt = 0;
2201 longest_matching_index = i;
2202 longest_matching_state =
2203 trie.clone();
2204 ce32 = CollationElement32::new(
2205 ce32_i as u32,
2206 );
2207 }
2208 (
2209 true,
2210 TrieResult::FinalValue(ce32_i),
2211 ) => {
2212 let _ = self
2213 .upcoming
2214 .remove(looked_ahead + i);
2215 while let Some(idx) =
2216 pending_removals.pop()
2217 {
2218 self.upcoming
2219 .remove(looked_ahead + idx);
2220 }
2221 ce32 = CollationElement32::new(
2222 ce32_i as u32,
2223 );
2224 continue 'ce32loop;
2225 }
2226 (_, TrieResult::NoValue) => {
2227 pending_removals.push(i);
2228 i += 1;
2229 }
2230 _ => {
2231 pending_removals.clear();
2232 most_recent_skipped_ccc = ccc;
2233 attempt += 1;
2234 i = longest_matching_index
2235 + attempt;
2236 trie =
2237 longest_matching_state.clone();
2238 }
2239 }
2240 } else {
2241 continue 'ce32loop;
2242 }
2243 }
2244 }
2245 TrieResult::Intermediate(ce32_i) => {
2246 longest_matching_state = trie.clone();
2247 drain_from_upcoming = looked_ahead;
2248 ce32 = CollationElement32::new(ce32_i as u32);
2249 }
2250 TrieResult::FinalValue(ce32_i) => {
2251 drain_from_upcoming = looked_ahead;
2252 ce32 = CollationElement32::new(ce32_i as u32);
2253 continue 'ce32loop;
2254 }
2255 }
2256 } else {
2257 continue 'ce32loop;
2258 }
2259 }
2260 // Unreachable
2261 }
2262 Tag::Digit => {
2263 if let Some(high_bits) = self.numeric_primary {
2264 let mut digits: SmallVec<[u8; DIGIT_BUFFER_SIZE]> =
2265 SmallVec::new(); // TODO(#2005): Figure out good length
2266 digits.push(ce32.digit());
2267 let numeric_primary = u32::from(high_bits) << 24;
2268 if combining_characters.is_empty() {
2269 // Numeric collation doesn't work with combining
2270 // characters applied to the digits.
2271 // It's unclear if reading from the tailoring first
2272 // is needed for practical purposes, since it doesn't
2273 // make much sense to tailor the numeric value of digits.
2274 // Performing the usual fallback pattern anyway just in
2275 // case.
2276 may_have_contracted_starter = true;
2277 self.ensure_upcoming_normalized();
2278 while let Some(upcoming) = self.look_ahead(looked_ahead) {
2279 looked_ahead += 1;
2280 ce32 =
2281 self.tailoring.ce32_for_char(upcoming.character());
2282 if ce32 == FALLBACK_CE32 {
2283 ce32 =
2284 self.root.ce32_for_char(upcoming.character());
2285 }
2286 if ce32.tag_checked() != Some(Tag::Digit) {
2287 break;
2288 }
2289 drain_from_upcoming = looked_ahead;
2290 digits.push(ce32.digit());
2291 }
2292 }
2293 let mut remaining = digits.as_slice();
2294 while !remaining.is_empty() {
2295 // Skip leading zeros
2296
2297 // If this isn't our initial loop round and we've truncated
2298 // a chunk to 254 digits on a previous round, the eventual
2299 // comparison result can be wrong, but that replicates an
2300 // ICU4C bug. Let's fix both as a follow-up.
2301 loop {
2302 let Some((first, tail)) = remaining.split_first()
2303 else {
2304 // Keep one zero
2305 // If we get here, we must have skipped a zero, since
2306 // 1) the while loop condition above meant that we started
2307 // with a non-empty slice AND
2308 // 2) this loop only skips zeros
2309 // Instead of trying to recover the same zero that we already
2310 // skipped, let's just fill in a static slice.
2311 remaining = &[0];
2312 break;
2313 };
2314 if *first != 0 {
2315 break;
2316 }
2317 remaining = tail;
2318 }
2319 // Numeric CEs are generated for segments of
2320 // up to 254 digits.
2321 let (head, tail) = remaining
2322 .split_at_checked(254)
2323 .unwrap_or((remaining, b""));
2324 remaining = tail;
2325 // From ICU4C CollationIterator::appendNumericSegmentCEs
2326 if head.len() <= 7 {
2327 let mut digit_iter = head.iter();
2328 // `unwrap` succeeds, because we always have at least one
2329 // digit to even start numeric processing.
2330 #[expect(clippy::unwrap_used)]
2331 let mut value = u32::from(*digit_iter.next().unwrap());
2332 for &digit in digit_iter {
2333 value *= 10;
2334 value += u32::from(digit);
2335 }
2336 // Primary weight second byte values:
2337 // 74 byte values 2.. 75 for small numbers in two-byte primary weights.
2338 // 40 byte values 76..115 for medium numbers in three-byte primary weights.
2339 // 16 byte values 116..131 for large numbers in four-byte primary weights.
2340 // 124 byte values 132..255 for very large numbers with 4..127 digit pairs.
2341 let mut first_byte = 2u32;
2342 let mut num_bytes = 74u32;
2343 if value < num_bytes {
2344 self.pending.push(
2345 CollationElement::new_from_primary(
2346 numeric_primary
2347 | ((first_byte + value) << 16),
2348 ),
2349 );
2350 continue;
2351 }
2352 value -= num_bytes;
2353 first_byte += num_bytes;
2354 num_bytes = 40;
2355 if value < num_bytes * 254 {
2356 // Three-byte primary for 74..10233=74+40*254-1, good for year numbers and more.
2357 self.pending.push(
2358 CollationElement::new_from_primary(
2359 numeric_primary
2360 | ((first_byte + value / 254) << 16)
2361 | ((2 + value % 254) << 8),
2362 ),
2363 );
2364 continue;
2365 }
2366 value -= num_bytes * 254;
2367 first_byte += num_bytes;
2368 num_bytes = 16;
2369 if value < num_bytes * 254 * 254 {
2370 // Four-byte primary for 10234..1042489=10234+16*254*254-1.
2371 let mut primary =
2372 numeric_primary | (2 + value % 254);
2373 value /= 254;
2374 primary |= (2 + value % 254) << 8;
2375 value /= 254;
2376 primary |= (first_byte + value % 254) << 16;
2377 self.pending.push(
2378 CollationElement::new_from_primary(primary),
2379 );
2380 continue;
2381 }
2382 // original value > 1042489
2383 }
2384 debug_assert!(head.len() >= 7);
2385 // The second primary byte value 132..255 indicates the number of digit pairs (4..127),
2386 // then we generate primary bytes with those pairs.
2387 // Omit trailing 00 pairs.
2388 // Decrement the value for the last pair.
2389
2390 // Set the exponent. 4 pairs->132, 5 pairs->133, ..., 127 pairs->255.
2391 let mut len = head.len();
2392 let num_pairs = (len as u32).div_ceil(2); // as u32 OK, because capped to 254
2393 let mut primary =
2394 numeric_primary | ((132 - 4 + num_pairs) << 16);
2395 // Find the length without trailing 00 pairs.
2396 //
2397 // The indexing below is within bounds due to the following:
2398 //
2399 // * We skipped leading zeros.
2400 // * If `len == 2`: The loop condition is false, because
2401 // `head[len - 2]` isn't a leading zero.
2402 // * If `len == 1`: The loop condition is false, because
2403 // `head[len - 1]` isn't a leading zero, and `&&`
2404 // short-circuits, so the `head[len - 2]` access doesn't
2405 // occur.
2406 #[expect(clippy::indexing_slicing)]
2407 while head[len - 1] == 0 && head[len - 2] == 0 {
2408 len -= 2;
2409 }
2410 // Read the first pair
2411 // Index in bounds by construction above.
2412 #[expect(clippy::indexing_slicing)]
2413 let mut digit_iter = head[..len].iter();
2414 // `unwrap` succeeds by construction
2415 #[expect(clippy::unwrap_used)]
2416 let mut pair = if len & 1 == 1 {
2417 // Only "half a pair" if we have an odd number of digits.
2418 u32::from(*digit_iter.next().unwrap())
2419 } else {
2420 u32::from(*digit_iter.next().unwrap()) * 10
2421 + u32::from(*digit_iter.next().unwrap())
2422 };
2423 pair = 11 + 2 * pair;
2424 let mut shift = 8u32;
2425 while let (Some(&left), Some(&right)) =
2426 (digit_iter.next(), digit_iter.next())
2427 {
2428 if shift == 0 {
2429 primary |= pair;
2430 self.pending.push(
2431 CollationElement::new_from_primary(primary),
2432 );
2433 primary = numeric_primary;
2434 shift = 16;
2435 } else {
2436 primary |= pair << shift;
2437 shift -= 8;
2438 }
2439 pair =
2440 11 + 2 * (u32::from(left) * 10 + u32::from(right));
2441 }
2442 primary |= (pair - 1) << shift;
2443 self.pending
2444 .push(CollationElement::new_from_primary(primary));
2445 }
2446 break 'ce32loop;
2447 }
2448 ce32 = data.get_ce32(ce32.index());
2449 continue 'ce32loop;
2450 }
2451 Tag::Offset => {
2452 self.pending.push(data.ce_from_offset_ce32(c, ce32));
2453 break 'ce32loop;
2454 }
2455 Tag::Implicit => {
2456 self.pending
2457 .push(CollationElement::new_implicit_from_char(c));
2458 break 'ce32loop;
2459 }
2460 Tag::Fallback
2461 | Tag::Reserved3
2462 | Tag::LongPrimary
2463 | Tag::LongPrimaryForReorderable
2464 | Tag::LongSecondary
2465 | Tag::BuilderData
2466 | Tag::LatinExpansion
2467 | Tag::Hangul => {
2468 debug_assert!(false);
2469 // GIGO case
2470 self.pending.push(FFFD_CE);
2471 break 'ce32loop;
2472 }
2473 }
2474 }
2475 }
2476 self.prefix_push(c);
2477 'combining_outer: loop {
2478 debug_assert!(drain_from_upcoming == 0 || combining_characters.is_empty());
2479 let mut i = 0;
2480 'combining: while let Some(ch) =
2481 combining_characters.get(i).map(|c| c.character())
2482 {
2483 c = ch;
2484 let diacritic_index = (c as usize).wrapping_sub(COMBINING_DIACRITICS_BASE);
2485 if let Some(secondary) = self.diacritics.get(diacritic_index) {
2486 // TODO(#2006): unlikely annotation
2487 if c == '\u{0307}' && self.lithuanian_dot_above {
2488 if let Some(next_c) =
2489 combining_characters.get(i + 1).map(|c| c.character())
2490 {
2491 if next_c == '\u{0300}'
2492 || next_c == '\u{0301}'
2493 || next_c == '\u{0303}'
2494 {
2495 // Lithuanian contracts COMBINING DOT ABOVE with three other diacritics of the
2496 // same combining class such that the COMBINING DOT ABOVE is ignored for
2497 // collation. Since the combining class is the same, it's valid to simply
2498 // look at the next character in `combining_characters`.
2499 i += 1;
2500 continue 'combining;
2501 }
2502 }
2503 }
2504 self.pending
2505 .push(CollationElement::new_from_secondary(secondary));
2506 self.mark_prefix_unmatchable();
2507 i += 1;
2508 continue 'combining;
2509 }
2510 // `c` is not a table-optimized diacritic.
2511 // Not bothering to micro optimize away the move of the remaining
2512 // part of `combining_characters`.
2513 let _ = combining_characters.drain(..=i);
2514 data = self.tailoring;
2515 ce32 = data.ce32_for_char(c);
2516 if ce32 == FALLBACK_CE32 {
2517 data = self.root;
2518 ce32 = data.ce32_for_char(c);
2519 }
2520 continue 'outer;
2521 }
2522 // Note: The borrow checker didn't like the iterator formulation
2523 // for the loop below, because the `Drain` would have kept `self`
2524 // mutable borrowed when trying to call `prefix_push`. To change
2525 // this, `prefix` and `prefix_push` would need to be refactored
2526 // into a struct.
2527 i = 0;
2528 while i < drain_from_upcoming {
2529 // By construction, `drain_from_upcoming` doesn't exceed `upcoming.len()`
2530 #[expect(clippy::indexing_slicing)]
2531 let ch = self.upcoming[i].character();
2532 self.prefix_push(ch);
2533 i += 1;
2534 }
2535 // TODO(#2004): The above makes prefix out of sync when starter-contracting
2536 // contractions use `pending_removals` instead of `drain_from_upcoming`.
2537 // Do there exist prefixes that overlap contraction suffixes?
2538 // At least as of CLDR 40, the two possible non-starters in prefixes,
2539 // kana voicing marks, shouldn't be participating in Brahmic contractions.
2540 let _ = self.upcoming.drain(..drain_from_upcoming);
2541 if self.upcoming.is_empty() {
2542 // Make the assertion conditional to make CI happy.
2543 #[cfg(debug_assertions)]
2544 debug_assert!(self.iter_exhausted || may_have_contracted_starter);
2545 if let Some(c_c_tv) = self.iter_next() {
2546 self.upcoming.push(c_c_tv);
2547 } else {
2548 #[cfg(debug_assertions)]
2549 {
2550 self.iter_exhausted = true;
2551 }
2552 }
2553 }
2554 if may_have_contracted_starter {
2555 may_have_contracted_starter = false;
2556 if !self.is_next_decomposition_starts_with_starter() {
2557 // We need to loop back and process another round of
2558 // non-starters in order to maintain the invariant of
2559 // `upcoming` on the next call to `next()`.
2560 drain_from_upcoming = 0;
2561 looked_ahead = 0;
2562 self.collect_combining(&mut combining_characters);
2563 continue 'combining_outer;
2564 }
2565 }
2566 // By construction, we have at least on pending CE by now.
2567 #[expect(clippy::indexing_slicing)]
2568 let ret = self.pending[0];
2569 debug_assert_eq!(self.pending_pos, 0);
2570 if self.pending.len() == 1 {
2571 self.pending.clear();
2572 } else {
2573 self.pending_pos = 1;
2574 }
2575 return ret;
2576 }
2577 }
2578 } else {
2579 NO_CE
2580 }
2581 }
2582
2583 #[inline(always)]
2584 fn collect_combining(
2585 &mut self,
2586 combining_characters: &mut SmallVec<[CharacterAndClass; COMBINING_CHARACTER_BUFFER_SIZE]>,
2587 ) {
2588 while !self.is_next_decomposition_starts_with_starter() {
2589 // `unwrap` is OK, because `!self.is_next_decomposition_starts_with_starter()`
2590 // means the `unwrap()` must succeed.
2591 #[expect(clippy::unwrap_used)]
2592 let combining = self.next_internal().unwrap().c_and_c;
2593 let combining_c = combining.character();
2594 if !in_inclusive_range(combining_c, '\u{0340}', '\u{0F81}') {
2595 combining_characters.push(combining);
2596 } else {
2597 // The Tibetan special cases are starters that decompose into non-starters.
2598 match combining_c {
2599 '\u{0340}' => {
2600 // COMBINING GRAVE TONE MARK
2601 combining_characters.push(CharacterAndClass::new(
2602 '\u{0300}',
2603 CanonicalCombiningClass::Above,
2604 ));
2605 }
2606 '\u{0341}' => {
2607 // COMBINING ACUTE TONE MARK
2608 combining_characters.push(CharacterAndClass::new(
2609 '\u{0301}',
2610 CanonicalCombiningClass::Above,
2611 ));
2612 }
2613 '\u{0343}' => {
2614 // COMBINING GREEK KORONIS
2615 combining_characters.push(CharacterAndClass::new(
2616 '\u{0313}',
2617 CanonicalCombiningClass::Above,
2618 ));
2619 }
2620 '\u{0344}' => {
2621 // COMBINING GREEK DIALYTIKA TONOS
2622 combining_characters.push(CharacterAndClass::new(
2623 '\u{0308}',
2624 CanonicalCombiningClass::Above,
2625 ));
2626 combining_characters.push(CharacterAndClass::new(
2627 '\u{0301}',
2628 CanonicalCombiningClass::Above,
2629 ));
2630 }
2631 '\u{0F73}' => {
2632 // TIBETAN VOWEL SIGN II
2633 combining_characters.push(CharacterAndClass::new(
2634 '\u{0F71}',
2635 CanonicalCombiningClass::CCC129,
2636 ));
2637 combining_characters.push(CharacterAndClass::new(
2638 '\u{0F72}',
2639 CanonicalCombiningClass::CCC130,
2640 ));
2641 }
2642 '\u{0F75}' => {
2643 // TIBETAN VOWEL SIGN UU
2644 combining_characters.push(CharacterAndClass::new(
2645 '\u{0F71}',
2646 CanonicalCombiningClass::CCC129,
2647 ));
2648 combining_characters.push(CharacterAndClass::new(
2649 '\u{0F74}',
2650 CanonicalCombiningClass::CCC132,
2651 ));
2652 }
2653 '\u{0F81}' => {
2654 // TIBETAN VOWEL SIGN REVERSED II
2655 combining_characters.push(CharacterAndClass::new(
2656 '\u{0F71}',
2657 CanonicalCombiningClass::CCC129,
2658 ));
2659 combining_characters.push(CharacterAndClass::new(
2660 '\u{0F80}',
2661 CanonicalCombiningClass::CCC130,
2662 ));
2663 }
2664 _ => {
2665 combining_characters.push(combining);
2666 }
2667 };
2668 }
2669 }
2670 if combining_characters.len() > 1 {
2671 // This optimizes away the class lookup when len() == 1.
2672 // Unclear if this micro optimization is worthwhile.
2673 // In any case, we store the CanonicalCombiningClass in order to
2674 // avoid having to look it up again when deciding whether to proceed
2675 // with a discontiguous match. As a side effect, it also means that
2676 // duplicate lookups aren't needed if the sort below happens to compare
2677 // an item more than once.
2678 combining_characters
2679 .iter_mut()
2680 .for_each(|cc| cc.set_ccc_from_trie_if_not_already_set(self.iter.trie()));
2681 combining_characters.sort_by_key(|cc| cc.ccc());
2682 }
2683 }
2684}