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