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 let Some(first) = self.upcoming.first().cloned() else {
1162 return;
1163 };
1164 if !first.decomposition_starts_with_non_starter() {
1165 return;
1166 }
1167 if self.upcoming.len() == 1 {
1168 // We now have a single character that decomposes to start with
1169 // a non-starter. Decompose it and assign the real canonical combining class.
1170 self.upcoming.clear();
1171 // We just cleared `upcoming`, so we can no longer be in the exceptional
1172 // state tracked by `self.upcoming_normalized` being `false`.
1173 self.upcoming_normalized = true;
1174 self.push_decomposed_combining(first);
1175 } else {
1176 // We end up sorting more times than minimally necessary.
1177 self.ensure_upcoming_normalized();
1178 #[cfg(debug_assertions)]
1179 {
1180 if !self.iter_exhausted {
1181 // `unwrap` OK, because len() must be > 1.
1182 debug_assert!(!self
1183 .upcoming
1184 .last()
1185 .unwrap()
1186 .decomposition_starts_with_non_starter());
1187 }
1188 }
1189 return;
1190 }
1191 // Not using `while let` to be able to set `iter_exhausted`
1192 loop {
1193 if let Some(ch) = self.iter_next() {
1194 if ch.decomposition_starts_with_non_starter() {
1195 self.push_decomposed_combining(ch);
1196 } else {
1197 // Got a new starter
1198 self.upcoming.push(ch);
1199 break;
1200 }
1201 } else {
1202 #[cfg(debug_assertions)]
1203 {
1204 self.iter_exhausted = true;
1205 }
1206 break;
1207 }
1208 }
1209 }
1210
1211 /// Ensures that `upcoming` is normalized to NFD, except:
1212 /// 1. When the last item is a starter, it isn't necessarily normalized.
1213 /// 2. Hangul syllable are unnormalized.
1214 fn ensure_upcoming_normalized(&mut self) {
1215 if self.upcoming_normalized {
1216 return;
1217 }
1218 self.upcoming_normalized = true;
1219 let without_trailing_starter = if let Some((last, head)) = self.upcoming.split_last() {
1220 if !last.decomposition_starts_with_non_starter() {
1221 if head.is_empty() {
1222 // There is a single starter, which isn't required
1223 // to be normalized.
1224 return;
1225 } else {
1226 head
1227 }
1228 } else {
1229 &self.upcoming[..]
1230 }
1231 } else {
1232 // Make the assertion conditional to make CI happy.
1233 #[cfg(debug_assertions)]
1234 debug_assert!(self.iter_exhausted);
1235 return;
1236 };
1237
1238 // It would be better to attempt to normalize in place, but let's do at
1239 // least this.
1240 if without_trailing_starter.iter().all(|c| {
1241 (c.trie_val
1242 & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER | HANGUL_SYLLABLE_MARKER))
1243 == 0
1244 }) {
1245 return;
1246 }
1247
1248 let mut unnormalized = core::mem::take(&mut self.upcoming);
1249 let last_index = unnormalized.len() - 1;
1250 let mut start_combining = 0;
1251 for (i, c) in unnormalized.drain(..).enumerate() {
1252 if c.decomposition_starts_with_non_starter() {
1253 self.push_decomposed_combining(c);
1254 } else if i == last_index {
1255 // Indices are in range by construction, so indexing is OK.
1256 #[expect(clippy::indexing_slicing)]
1257 self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1258 self.upcoming.push(c);
1259 return;
1260 } else {
1261 // Indices are in range by construction, so indexing is OK.
1262 #[expect(clippy::indexing_slicing)]
1263 self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1264 start_combining = self.push_decomposed_starter(c);
1265 }
1266 }
1267 // `init()` is responsible for collecting non-starters up to a
1268 // starter or iterator exhausting into `upcoming`, so if we
1269 // didn't take the early return in the `i == last_index` branch
1270 // above, we are in the iterator exhausted case.
1271
1272 // Make the assertion conditional to make CI happy.
1273 #[cfg(debug_assertions)]
1274 debug_assert!(self.iter_exhausted);
1275 // Indices are in range by construction, so indexing is OK.
1276 #[expect(clippy::indexing_slicing)]
1277 self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1278 }
1279
1280 fn push_decomposed_combining(&mut self, c: CharacterAndClassAndTrieValue) {
1281 if !trie_value_indicates_special_non_starter_decomposition(c.trie_val) {
1282 debug_assert!(trie_value_has_ccc(c.trie_val));
1283 self.upcoming.push(c);
1284 return;
1285 }
1286
1287 // The Tibetan special cases are starters that decompose into non-starters.
1288 match c.character() {
1289 '\u{0340}' => {
1290 // COMBINING GRAVE TONE MARK
1291 self.upcoming
1292 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1293 '\u{0300}',
1294 CanonicalCombiningClass::Above,
1295 ));
1296 }
1297 '\u{0341}' => {
1298 // COMBINING ACUTE TONE MARK
1299 self.upcoming
1300 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1301 '\u{0301}',
1302 CanonicalCombiningClass::Above,
1303 ));
1304 }
1305 '\u{0343}' => {
1306 // COMBINING GREEK KORONIS
1307 self.upcoming
1308 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1309 '\u{0313}',
1310 CanonicalCombiningClass::Above,
1311 ));
1312 }
1313 '\u{0344}' => {
1314 // COMBINING GREEK DIALYTIKA TONOS
1315 self.upcoming
1316 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1317 '\u{0308}',
1318 CanonicalCombiningClass::Above,
1319 ));
1320 self.upcoming
1321 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1322 '\u{0301}',
1323 CanonicalCombiningClass::Above,
1324 ));
1325 }
1326 '\u{0F73}' => {
1327 // TIBETAN VOWEL SIGN II
1328 self.upcoming
1329 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1330 '\u{0F71}',
1331 CanonicalCombiningClass::CCC129,
1332 ));
1333 self.upcoming
1334 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1335 '\u{0F72}',
1336 CanonicalCombiningClass::CCC130,
1337 ));
1338 }
1339 '\u{0F75}' => {
1340 // TIBETAN VOWEL SIGN UU
1341 self.upcoming
1342 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1343 '\u{0F71}',
1344 CanonicalCombiningClass::CCC129,
1345 ));
1346 self.upcoming
1347 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1348 '\u{0F74}',
1349 CanonicalCombiningClass::CCC132,
1350 ));
1351 }
1352 '\u{0F81}' => {
1353 // TIBETAN VOWEL SIGN REVERSED II
1354 self.upcoming
1355 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1356 '\u{0F71}',
1357 CanonicalCombiningClass::CCC129,
1358 ));
1359 self.upcoming
1360 .push(CharacterAndClassAndTrieValue::new_with_non_zero_ccc(
1361 '\u{0F80}',
1362 CanonicalCombiningClass::CCC130,
1363 ));
1364 }
1365 _ => {
1366 // GIGO case
1367 debug_assert!(false);
1368 }
1369 }
1370 }
1371
1372 fn push_decomposed_starter(&mut self, c: CharacterAndClassAndTrieValue) -> usize {
1373 let mut search_start_combining = false;
1374 let old_len = self.upcoming.len();
1375 // Not inserting early returns below to keep the same structure
1376 // as in the ce32 mapping code.
1377
1378 // Hangul syllable check omitted, because it's fine not to decompose
1379 // Hangul syllables in lookahead, because Hangul isn't allowed to
1380 // participate in contractions, and the trie default is that a character
1381 // is its own decomposition.
1382
1383 // See components/normalizer/trie-value-format.md
1384 let decomposition = c.trie_val;
1385 if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER))
1386 <= HANGUL_SYLLABLE_MARKER
1387 {
1388 // The character is its own decomposition (or Hangul syllable)
1389 // Set the Canonical Combining Class to zero
1390 self.upcoming.push(
1391 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(c.character()),
1392 );
1393 } else {
1394 let high_zeros = (decomposition & HIGH_ZEROS_MASK) == 0;
1395 let low_zeros = (decomposition & LOW_ZEROS_MASK) == 0;
1396 if !high_zeros && !low_zeros {
1397 // Decomposition into two BMP characters: starter and non-starter
1398 let starter = char_from_u32(decomposition & 0x7FFF);
1399 let low_c = char_from_u32((decomposition >> 15) & 0x7FFF);
1400 self.upcoming
1401 .push(CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(starter));
1402 let trie_value = self.iter.trie().bmp(low_c as u16);
1403 self.upcoming.push(
1404 CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(
1405 low_c, trie_value,
1406 ),
1407 );
1408 } else if high_zeros {
1409 let singleton = decomposition as u16;
1410 debug_assert_ne!(
1411 singleton, FDFA_MARKER,
1412 "How come U+FDFA NFKD marker seen in NFD?"
1413 );
1414 if (singleton & 0xFF00) == 0xD800 {
1415 // We're at the end of the stream, so we aren't dealing with the
1416 // next undecomposed starter but are dealing with an
1417 // already-decomposed non-starter. Just put it back.
1418 self.upcoming.push(c);
1419 // Make the assertion conditional to make CI happy.
1420 #[cfg(debug_assertions)]
1421 debug_assert!(self.iter_exhausted);
1422 } else {
1423 // Decomposition into one BMP character
1424 self.upcoming.push(
1425 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1426 char_from_u16(singleton),
1427 ),
1428 );
1429 }
1430 } else {
1431 debug_assert!(low_zeros);
1432 // Only 12 of 14 bits used as of Unicode 16.
1433 let offset = (((decomposition & !(0b11 << 30)) >> 16) as usize) - 1;
1434 // Only 3 of 4 bits used as of Unicode 16.
1435 let len_bits = decomposition & 0b1111;
1436 let only_non_starters_in_trail = (decomposition & 0b10000) != 0;
1437 if offset < self.scalars16.len() {
1438 let len = (len_bits + 2) as usize;
1439 for u in unwrap_or_gigo(
1440 self.scalars16.get_subslice(offset..offset + len),
1441 SINGLE_REPLACEMENT_CHARACTER_U16, // single instead of empty for consistency with the other code path
1442 )
1443 .iter()
1444 {
1445 let ch = char_from_u16(u);
1446 let trie_value = self.iter.trie().bmp(u);
1447 self.upcoming
1448 .push(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(ch, trie_value));
1449 }
1450 } else {
1451 let len = (len_bits + 1) as usize;
1452 let offset32 = offset - self.scalars16.len();
1453 for ch in unwrap_or_gigo(
1454 self.scalars32.get_subslice(offset32..offset32 + len),
1455 SINGLE_REPLACEMENT_CHARACTER_CHAR, // single instead of empty for consistency with the other code path
1456 )
1457 .iter()
1458 {
1459 let trie_value = self.iter.trie().scalar(ch);
1460 self.upcoming
1461 .push(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(ch, trie_value));
1462 }
1463 }
1464 search_start_combining = !only_non_starters_in_trail;
1465 }
1466 }
1467 if search_start_combining {
1468 // The decomposition contains starters. As of Unicode 14,
1469 // There are two possible patterns:
1470 // BMP: starter, starter, non-starter
1471 // Plane 1: starter, starter.
1472 // However, for forward compatibility, support any combination
1473 // and search for the last starter.
1474 let mut i = self.upcoming.len() - 1;
1475 loop {
1476 if let Some(ch) = self.upcoming.get(i) {
1477 if ch.decomposition_starts_with_non_starter() {
1478 i -= 1;
1479 continue;
1480 }
1481 break;
1482 }
1483 // GIGO case
1484 debug_assert!(false);
1485 // This will wrap to zero below
1486 i = usize::MAX;
1487 break;
1488 }
1489 i + 1
1490 } else {
1491 old_len + 1
1492 }
1493 }
1494
1495 // Decomposes `c`, pushes it to `self.upcoming` (unless the character is
1496 // a Hangul syllable; Hangul isn't allowed to participate in contractions),
1497 // gathers the following combining characters from `self.iter` and the following starter.
1498 // Sorts the combining characters and leaves the starter at the end
1499 // unnormalized. The trailing unnormalized starter doesn't get appended if
1500 // `self.iter` is exhausted.
1501 fn push_decomposed_and_gather_combining(&mut self, c: CharacterAndClassAndTrieValue) {
1502 let start_combining = self.push_decomposed_starter(c);
1503 // Not using `while let` to be able to set `iter_exhausted`
1504 loop {
1505 if let Some(ch) = self.iter_next() {
1506 if ch.decomposition_starts_with_non_starter() {
1507 self.push_decomposed_combining(ch);
1508 } else {
1509 // Got a new starter
1510 // Indices are in range by construction, so indexing is OK.
1511 #[expect(clippy::indexing_slicing)]
1512 self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1513 self.upcoming.push(ch);
1514 return;
1515 }
1516 } else {
1517 #[cfg(debug_assertions)]
1518 {
1519 self.iter_exhausted = true;
1520 }
1521 // Indices are in range by construction, so indexing is OK.
1522 #[expect(clippy::indexing_slicing)]
1523 self.upcoming[start_combining..].sort_by_key(|c| c.ccc());
1524 return;
1525 }
1526 }
1527 }
1528
1529 // Assumption: `pos` starts from zero and increases one by one.
1530 // Indexing is OK, because we check against `len()` and the `pos`
1531 // increases one by one by construction.
1532 #[expect(clippy::indexing_slicing)]
1533 fn look_ahead(&mut self, pos: usize) -> Option<CharacterAndClassAndTrieValue> {
1534 debug_assert!(self.upcoming_normalized);
1535 if pos + 1 == self.upcoming.len() {
1536 let c = self.upcoming.remove(pos);
1537 self.push_decomposed_and_gather_combining(c);
1538 Some(self.upcoming[pos].clone())
1539 } else if pos == self.upcoming.len() {
1540 if let Some(c) = self.iter_next() {
1541 debug_assert!(
1542 false,
1543 "The `upcoming` queue should be empty when iteration `pos` at the end"
1544 );
1545 self.push_decomposed_and_gather_combining(c);
1546 Some(self.upcoming[pos].clone())
1547 } else {
1548 #[cfg(debug_assertions)]
1549 {
1550 self.iter_exhausted = true;
1551 }
1552 None
1553 }
1554 } else {
1555 Some(self.upcoming[pos].clone())
1556 }
1557 }
1558
1559 fn is_next_decomposition_starts_with_starter(&self) -> bool {
1560 if let Some(c_c_tv) = self.upcoming.first() {
1561 !c_c_tv.decomposition_starts_with_non_starter()
1562 } else {
1563 true
1564 }
1565 }
1566
1567 fn prepend_and_sort_non_starter_prefix_of_suffix(&mut self, c: CharacterAndClassAndTrieValue) {
1568 // Add one for the insertion afterwards.
1569 let end = 1 + {
1570 let mut iter = self.upcoming.iter().enumerate();
1571 loop {
1572 if let Some((i, ch)) = iter.next() {
1573 if !ch.decomposition_starts_with_non_starter() {
1574 break i;
1575 }
1576 } else {
1577 #[cfg(debug_assertions)]
1578 {
1579 self.iter_exhausted = true;
1580 }
1581 break self.upcoming.len();
1582 }
1583 }
1584 };
1585 let start = c.decomposition_starts_with_non_starter() as usize;
1586 self.upcoming.insert(0, c);
1587 // Indices in range by construction
1588 #[expect(clippy::indexing_slicing)]
1589 {
1590 let slice: &mut [CharacterAndClassAndTrieValue] = &mut self.upcoming[start..end];
1591 slice.sort_by_key(|cc| cc.ccc());
1592 };
1593 }
1594
1595 fn prefix_push(&mut self, c: char) {
1596 self.prefix[1] = self.prefix[0];
1597 self.prefix[0] = c;
1598 }
1599
1600 /// Micro optimization for doing a simpler write when
1601 /// we know the most recent character was a non-starter
1602 /// that is not a kana voicing mark.
1603 fn mark_prefix_unmatchable(&mut self) {
1604 self.prefix[0] = '\u{FFFF}';
1605 }
1606
1607 pub fn next(&mut self) -> CollationElement {
1608 #[cfg(debug_assertions)]
1609 debug_assert!(self.initialized);
1610 debug_assert!(self.is_next_decomposition_starts_with_starter());
1611 if let Some(&ret) = self.pending.get(self.pending_pos) {
1612 self.pending_pos += 1;
1613 if self.pending_pos == self.pending.len() {
1614 self.pending.clear();
1615 self.pending_pos = 0;
1616 }
1617 return ret;
1618 }
1619 debug_assert_eq!(self.pending_pos, 0);
1620 if let Some(c_c_tv) = self.next_internal() {
1621 let mut c = c_c_tv.character();
1622 let mut ce32;
1623 let mut data: &CollationData = self.tailoring;
1624 // TODO: Should this be a reusable buffer on the struct instead of
1625 // getting re-created on the stack every time?
1626 let mut combining_characters: SmallVec<
1627 [CharacterAndClass; COMBINING_CHARACTER_BUFFER_SIZE],
1628 > = SmallVec::new(); // TODO(#2005): Figure out good length
1629
1630 // Betting that fusing the NFD algorithm into this one at the
1631 // expense of the repetitiveness below, the common cases become
1632 // fast in a way that offsets the lack of the canonical closure.
1633 // The wall of code before the "Slow path" is an attempt to
1634 // optimize based on that bet.
1635
1636 // See components/normalizer/trie-value-format.md
1637 let decomposition = c_c_tv.trie_val;
1638 if (decomposition & !(BACKWARD_COMBINING_MARKER | NON_ROUND_TRIP_MARKER)) == 0 {
1639 // The character is its own decomposition
1640
1641 // TODO: This is a bad idea. Make sure the jamo are in the root trie and then
1642 // remove this special case.
1643 let jamo_index = (c as usize).wrapping_sub(HANGUL_L_BASE as usize);
1644 // Attribute belongs on an inner expression, but
1645 // https://github.com/rust-lang/rust/issues/15701
1646 #[expect(clippy::indexing_slicing)]
1647 if jamo_index >= self.jamo.len() {
1648 // Note: It might seem like a good idea to reuse the CE32s
1649 // from the identical prefix check here, but the logistics
1650 // actually make everything slower.
1651 ce32 = data.ce32_for_char(c);
1652 if ce32 == FALLBACK_CE32 {
1653 data = self.root;
1654 ce32 = data.ce32_for_char(c);
1655 }
1656 } else {
1657 // The purpose of reading the CE32 from the jamo table instead
1658 // of the trie even in this case is to make it unnecessary
1659 // for all search collation tries to carry a copy of the Hangul
1660 // part of the search root. Instead, all non-Korean tailorings
1661 // can use a shared copy of the non-Korean search jamo table.
1662 //
1663 // TODO(#1941): This isn't actually true with the current jamo
1664 // search expansions!
1665
1666 // TODO(#1941): Instead of having different jamo CE32 table for
1667 // "search" collations, we could instead decompose the archaic
1668 // jamo to the modern approximation sequences here and then map
1669 // those by looking up the modern jamo from the normal root.
1670
1671 // We need to set data to root, because archaic jamo refer to
1672 // the root.
1673 data = self.root;
1674 // Index in range by construction above. Not using `get` with
1675 // `if let` in order to put the likely branch first.
1676 ce32 = CollationElement32::new_from_ule(self.jamo[jamo_index]);
1677 }
1678 if self.is_next_decomposition_starts_with_starter() {
1679 if let Some(ce) = ce32.to_ce_simple_or_long_primary() {
1680 self.prefix_push(c);
1681 return ce;
1682 } else if ce32.tag() == Tag::Contraction
1683 && ce32.every_suffix_starts_with_combining()
1684 {
1685 // Avoid falling onto the slow path e.g. that letters that
1686 // may contract with a diacritic when we know that it won't
1687 // contract with the next character.
1688 let default = data.get_default(ce32.index());
1689 if let Some(ce) = default.to_ce_simple_or_long_primary() {
1690 self.prefix_push(c);
1691 return ce;
1692 }
1693 }
1694 // TODO(2003): Figure out if it would be an optimization to
1695 // handle `Implicit` and `Offset` tags here.
1696 }
1697 } else {
1698 let high_zeros = (decomposition & HIGH_ZEROS_MASK) == 0;
1699 let low_zeros = (decomposition & LOW_ZEROS_MASK) == 0;
1700 if !high_zeros && !low_zeros {
1701 // Decomposition into two BMP characters: starter and non-starter
1702 c = char_from_u32(decomposition & 0x7FFF);
1703 ce32 = data.ce32_for_char(c);
1704 if ce32 == FALLBACK_CE32 {
1705 data = self.root;
1706 ce32 = data.ce32_for_char(c);
1707 }
1708 let combining = char_from_u32((decomposition >> 15) & 0x7FFF);
1709 if self.is_next_decomposition_starts_with_starter() {
1710 let diacritic_index =
1711 (combining as usize).wrapping_sub(COMBINING_DIACRITICS_BASE);
1712 if let Some(secondary) = self.diacritics.get(diacritic_index) {
1713 debug_assert_ne!(combining, '\u{0344}', "Should never have COMBINING GREEK DIALYTIKA TONOS here, since it should have decomposed further.");
1714 if let Some(ce) = ce32.to_ce_simple_or_long_primary() {
1715 let ce_for_combining =
1716 CollationElement::new_from_secondary(secondary);
1717 self.pending.push(ce_for_combining);
1718 self.mark_prefix_unmatchable();
1719 return ce;
1720 }
1721 if ce32.tag() == Tag::Contraction
1722 && ce32.every_suffix_starts_with_combining()
1723 {
1724 let (default, mut trie) = data.get_default_and_trie(ce32.index());
1725 match trie.next(combining) {
1726 TrieResult::NoMatch | TrieResult::NoValue => {
1727 if let Some(ce) = default.to_ce_simple_or_long_primary() {
1728 let ce_for_combining =
1729 CollationElement::new_from_secondary(secondary);
1730 self.pending.push(ce_for_combining);
1731 self.mark_prefix_unmatchable();
1732 return ce;
1733 }
1734 }
1735 TrieResult::Intermediate(trie_ce32) => {
1736 if !ce32.at_least_one_suffix_contains_starter() {
1737 if let Some(ce) =
1738 CollationElement32::new(trie_ce32 as u32)
1739 .to_ce_simple_or_long_primary()
1740 {
1741 self.mark_prefix_unmatchable();
1742 return ce;
1743 }
1744 }
1745 }
1746 TrieResult::FinalValue(trie_ce32) => {
1747 if let Some(ce) = CollationElement32::new(trie_ce32 as u32)
1748 .to_ce_simple_or_long_primary()
1749 {
1750 self.mark_prefix_unmatchable();
1751 return ce;
1752 }
1753 }
1754 }
1755 }
1756 }
1757 }
1758 combining_characters.push(CharacterAndClass::new_with_placeholder(combining));
1759 } else if high_zeros {
1760 // Do the Hangul check on the character instead of trusting
1761 // the trie value in order not to let GIGO cause unsafety.
1762 let hangul_offset = u32::from(c).wrapping_sub(HANGUL_S_BASE); // SIndex in the spec
1763 if hangul_offset < HANGUL_S_COUNT {
1764 // Hangul syllable
1765 // The math here comes from page 144 of Unicode 14.0
1766 let l = hangul_offset / HANGUL_N_COUNT;
1767 let v = (hangul_offset % HANGUL_N_COUNT) / HANGUL_T_COUNT;
1768 let t = hangul_offset % HANGUL_T_COUNT;
1769
1770 // No prefix matches on Hangul
1771 self.mark_prefix_unmatchable();
1772 // Indexing OK, because indices in range by construction
1773 #[expect(clippy::indexing_slicing)]
1774 if self.is_next_decomposition_starts_with_starter() {
1775 // TODO(#1941): Assuming self-contained CE32s is OK for the root,
1776 // but not currently OK for search collation, which at this time
1777 // do not support tailored Hangul.
1778 self.pending.push(
1779 CollationElement32::new_from_ule(
1780 self.jamo[(HANGUL_V_BASE - HANGUL_L_BASE + v) as usize],
1781 )
1782 .to_ce_self_contained_or_gigo(),
1783 );
1784 if t != 0 {
1785 self.pending.push(
1786 CollationElement32::new_from_ule(
1787 self.jamo[(HANGUL_T_BASE - HANGUL_L_BASE + t) as usize],
1788 )
1789 .to_ce_self_contained_or_gigo(),
1790 );
1791 }
1792 return CollationElement32::new_from_ule(self.jamo[l as usize])
1793 .to_ce_self_contained_or_gigo();
1794 }
1795
1796 // Uphold the invariant that the upcoming character is a starter (or end of stream)
1797 // at the start of the next `next()` call. We uphold this invariant by leaving the
1798 // last jamo unmapped to `CollationElement` in `pending` and instead prepend it to
1799 // `upcoming`.
1800 //
1801 // Indexing OK, because indices in range by construction
1802 #[expect(clippy::indexing_slicing)]
1803 if t != 0 {
1804 self.pending.push(
1805 CollationElement32::new_from_ule(
1806 self.jamo[(HANGUL_V_BASE - HANGUL_L_BASE + v) as usize],
1807 )
1808 .to_ce_self_contained_or_gigo(),
1809 );
1810 self.upcoming.insert(
1811 0,
1812 // Safety: HANGUL_T_BASE is 0x11A7, t is < HANGUL_T_COUNT = 28, so this is definitely
1813 // in range for a char (≤ 0xD800)
1814 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1815 unsafe { core::char::from_u32_unchecked(HANGUL_T_BASE + t) },
1816 ),
1817 );
1818 } else {
1819 self.upcoming.insert(
1820 0,
1821 // Safety: HANGUL_V_BASE is 0x1161, v is < HANGUL_N_COUNT = 588, so this is definitely
1822 // in range for a char (≤ 0xD800)
1823 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1824 unsafe { core::char::from_u32_unchecked(HANGUL_V_BASE + v) },
1825 ),
1826 );
1827 }
1828
1829 // Indexing OK, because indices in range by construction
1830 #[expect(clippy::indexing_slicing)]
1831 return CollationElement32::new_from_ule(self.jamo[l as usize])
1832 .to_ce_self_contained_or_gigo();
1833 }
1834
1835 let singleton = decomposition as u16;
1836 debug_assert_ne!(
1837 singleton, FDFA_MARKER,
1838 "How come U+FDFA NFKD marker seen in NFD?"
1839 );
1840 // Decomposition into one BMP character
1841 c = char_from_u16(singleton);
1842 ce32 = data.ce32_for_char(c);
1843 if ce32 == FALLBACK_CE32 {
1844 data = self.root;
1845 ce32 = data.ce32_for_char(c);
1846 }
1847 if self.is_next_decomposition_starts_with_starter() {
1848 if let Some(ce) = ce32.to_ce_simple_or_long_primary() {
1849 self.prefix_push(c);
1850 return ce;
1851 }
1852 }
1853 } else {
1854 debug_assert!(low_zeros);
1855 // Only 12 of 14 bits used as of Unicode 16.
1856 let offset = (((decomposition & !(0b11 << 30)) >> 16) as usize) - 1;
1857 // Only 3 of 4 bits used as of Unicode 16.
1858 let len_bits = decomposition & 0b1111;
1859 let only_non_starters_in_trail = (decomposition & 0b10000) != 0;
1860 if offset < self.scalars16.len() {
1861 let len = (len_bits + 2) as usize;
1862 let (starter, tail) = self
1863 .scalars16
1864 .get_subslice(offset..offset + len)
1865 .and_then(ZeroSlice::split_first)
1866 .map_or_else(
1867 || {
1868 // GIGO case
1869 debug_assert!(false);
1870 (REPLACEMENT_CHARACTER, EMPTY_U16)
1871 },
1872 |(first, tail)| (char_from_u16(first), tail),
1873 );
1874 c = starter;
1875 if only_non_starters_in_trail {
1876 for u in tail.iter() {
1877 let char_from_u = char_from_u16(u);
1878 let trie_value = self.iter.trie().bmp(u);
1879 let ccc = ccc_from_trie_value(trie_value);
1880 combining_characters.push(CharacterAndClass::new(char_from_u, ccc));
1881 }
1882 } else {
1883 let mut it = tail.iter();
1884 while let Some(u) = it.next() {
1885 let ch = char_from_u16(u);
1886 let ccc = ccc_from_trie_value(self.iter.trie().bmp(u));
1887 if ccc != CanonicalCombiningClass::NotReordered {
1888 // As of Unicode 14, this branch is never taken.
1889 // It exist for forward compatibility.
1890 combining_characters.push(CharacterAndClass::new(ch, ccc));
1891 continue;
1892 }
1893
1894 // At this point, we might have a single newly-read
1895 // combining character in self.upcoming. In that case, we
1896 // need to buffer up the upcoming combining characters, too,
1897 // in order to make `prepend_and_sort_non_starter_prefix_of_suffix`
1898 // sort the right characters.
1899 self.maybe_gather_combining();
1900
1901 while let Some(u) = it.next_back() {
1902 let tail_char = char_from_u16(u);
1903 let trie_value = self.iter.trie().bmp(u);
1904 self.prepend_and_sort_non_starter_prefix_of_suffix(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(tail_char, trie_value));
1905 }
1906 self.prepend_and_sort_non_starter_prefix_of_suffix(
1907 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1908 ch,
1909 ),
1910 );
1911 break;
1912 }
1913 }
1914 } else {
1915 let len = (len_bits + 1) as usize;
1916 let offset32 = offset - self.scalars16.len();
1917 let (starter, tail) = self
1918 .scalars32
1919 .get_subslice(offset32..offset32 + len)
1920 .and_then(|slice| slice.split_first())
1921 .unwrap_or_else(|| {
1922 // GIGO case
1923 debug_assert!(false);
1924 (REPLACEMENT_CHARACTER, EMPTY_CHAR)
1925 });
1926
1927 c = starter;
1928 if only_non_starters_in_trail {
1929 for ch in tail.iter() {
1930 let trie_value = self.iter.trie().scalar(ch);
1931 let ccc = ccc_from_trie_value(trie_value);
1932 combining_characters.push(CharacterAndClass::new(ch, ccc));
1933 }
1934 } else {
1935 let mut it = tail.iter();
1936 while let Some(ch) = it.next() {
1937 let ccc = ccc_from_trie_value(self.iter.trie().scalar(ch));
1938 if ccc != CanonicalCombiningClass::NotReordered {
1939 // As of Unicode 14, this branch is never taken.
1940 // It exist for forward compatibility.
1941 combining_characters.push(CharacterAndClass::new(ch, ccc));
1942 continue;
1943 }
1944 // At this point, we might have a single newly-read
1945 // combining character in self.upcoming. In that case, we
1946 // need to buffer up the upcoming combining characters, too,
1947 // in order to make `prepend_and_sort_non_starter_prefix_of_suffix`
1948 // sort the right characters.
1949 self.maybe_gather_combining();
1950
1951 while let Some(tail_char) = it.next_back() {
1952 let trie_value = self.iter.trie().scalar(tail_char);
1953 self.prepend_and_sort_non_starter_prefix_of_suffix(CharacterAndClassAndTrieValue::new_with_non_special_decomposition_trie_val(tail_char, trie_value));
1954 }
1955 self.prepend_and_sort_non_starter_prefix_of_suffix(
1956 CharacterAndClassAndTrieValue::new_with_non_decomposing_starter(
1957 ch,
1958 ),
1959 );
1960 break;
1961 }
1962 }
1963 }
1964 ce32 = data.ce32_for_char(c);
1965 if ce32 == FALLBACK_CE32 {
1966 data = self.root;
1967 ce32 = data.ce32_for_char(c);
1968 }
1969 }
1970 }
1971 let mut may_have_contracted_starter = false;
1972 // Slow path
1973 self.collect_combining(&mut combining_characters);
1974 // Now:
1975 // c is the starter character
1976 // ce32 is the CollationElement32 for the starter
1977 // combining_characters contains all the combining characters before
1978 // the next starter sorted by combining class.
1979 let mut looked_ahead = 0;
1980 let mut drain_from_upcoming = 0;
1981 'outer: loop {
1982 'ce32loop: loop {
1983 // TODO(#2002): Ensure that the CE32 flavors in this loop are checked in the optimal
1984 // order given their frequency in real workloads.
1985 if let Some(ce) = ce32.to_ce_self_contained() {
1986 self.pending.push(ce);
1987 break 'ce32loop;
1988 } else {
1989 match ce32.tag() {
1990 Tag::Expansion32 => {
1991 let ce32s = data.get_ce32s(ce32.index(), ce32.len());
1992 for u in ce32s.iter() {
1993 self.pending.push(
1994 CollationElement32::new(u).to_ce_self_contained_or_gigo(),
1995 );
1996 }
1997 break 'ce32loop;
1998 }
1999 Tag::Expansion => {
2000 let ces = data.get_ces(ce32.index(), ce32.len());
2001 for u in ces.iter() {
2002 self.pending.push(CollationElement::new(u));
2003 }
2004 break 'ce32loop;
2005 }
2006 Tag::Prefix => {
2007 let (default, mut trie) = data.get_default_and_trie(ce32.index());
2008 ce32 = default;
2009 for &ch in self.prefix.iter() {
2010 match trie.next(ch) {
2011 TrieResult::NoValue => {}
2012 TrieResult::NoMatch => {
2013 continue 'ce32loop;
2014 }
2015 TrieResult::Intermediate(ce32_i) => {
2016 ce32 = CollationElement32::new(ce32_i as u32);
2017 }
2018 TrieResult::FinalValue(ce32_i) => {
2019 ce32 = CollationElement32::new(ce32_i as u32);
2020 continue 'ce32loop;
2021 }
2022 }
2023 }
2024 continue 'ce32loop;
2025 }
2026 Tag::Contraction => {
2027 let every_suffix_starts_with_combining =
2028 ce32.every_suffix_starts_with_combining();
2029 let at_least_one_suffix_contains_starter =
2030 ce32.at_least_one_suffix_contains_starter();
2031 let at_least_one_suffix_ends_with_non_starter =
2032 ce32.at_least_one_suffix_ends_with_non_starter();
2033 let (default, mut trie) = data.get_default_and_trie(ce32.index());
2034 ce32 = default;
2035 if every_suffix_starts_with_combining
2036 && combining_characters.is_empty()
2037 {
2038 continue 'ce32loop;
2039 }
2040 let mut longest_matching_state = trie.clone();
2041 let mut longest_matching_index = 0;
2042 let mut attempt = 0;
2043 let mut i = 0;
2044 let mut most_recent_skipped_ccc =
2045 CanonicalCombiningClass::NotReordered;
2046 // TODO(#2001): Pending removals will in practice be small numbers.
2047 // What if we made the item smaller than usize?
2048 let mut pending_removals: SmallVec<[usize; PENDING_REMOVALS_SIZE]> =
2049 SmallVec::new();
2050 while let Some((character, ccc)) =
2051 combining_characters.get(i).map(|c| c.character_and_ccc())
2052 {
2053 match (most_recent_skipped_ccc < ccc, trie.next(character)) {
2054 (true, TrieResult::Intermediate(ce32_i)) => {
2055 let _ = combining_characters.remove(i);
2056 while let Some(idx) = pending_removals.pop() {
2057 combining_characters.remove(idx);
2058 i -= 1; // Adjust for the shortening
2059 }
2060 attempt = 0;
2061 longest_matching_index = i;
2062 longest_matching_state = trie.clone();
2063 ce32 = CollationElement32::new(ce32_i as u32);
2064 }
2065 (true, TrieResult::FinalValue(ce32_i)) => {
2066 let _ = combining_characters.remove(i);
2067 while let Some(idx) = pending_removals.pop() {
2068 combining_characters.remove(idx);
2069 }
2070 ce32 = CollationElement32::new(ce32_i as u32);
2071 continue 'ce32loop;
2072 }
2073 (_, TrieResult::NoValue) => {
2074 pending_removals.push(i);
2075 i += 1;
2076 }
2077 _ => {
2078 pending_removals.clear();
2079 most_recent_skipped_ccc = ccc;
2080 attempt += 1;
2081 i = longest_matching_index + attempt;
2082 trie = longest_matching_state.clone();
2083 }
2084 }
2085 }
2086 if !(at_least_one_suffix_contains_starter
2087 && combining_characters.is_empty())
2088 {
2089 continue 'ce32loop;
2090 }
2091 // Let's just set this flag here instead of trying to make
2092 // it more granular and, therefore, more error-prone.
2093 // After all, this flag is just about optimizing away one
2094 // `CodePointInversionList` check in the common case.
2095 may_have_contracted_starter = true;
2096 debug_assert!(pending_removals.is_empty());
2097 self.ensure_upcoming_normalized();
2098 loop {
2099 let ahead = self.look_ahead(looked_ahead);
2100 looked_ahead += 1;
2101 if let Some(ch) = ahead {
2102 match trie.next(ch.character()) {
2103 TrieResult::NoValue => {}
2104 TrieResult::NoMatch => {
2105 if !at_least_one_suffix_ends_with_non_starter {
2106 continue 'ce32loop;
2107 }
2108 if !ch.decomposition_starts_with_non_starter() {
2109 continue 'ce32loop;
2110 }
2111 // The last-checked character is non-starter
2112 // and at least one contraction suffix ends
2113 // with a non-starter. Try a discontiguous
2114 // match.
2115 trie = longest_matching_state.clone();
2116 // For clarity, mint a new set of variables that
2117 // behave consistently with the
2118 // `combining_characters` case
2119 let mut longest_matching_index = 0;
2120 let mut attempt = 0;
2121 let mut i = 0;
2122 most_recent_skipped_ccc = ch.ccc();
2123 self.ensure_upcoming_normalized();
2124 loop {
2125 let ahead = self.look_ahead(looked_ahead + i);
2126 if let Some(ch) = ahead {
2127 let ccc = ch.ccc();
2128 if ccc
2129 == CanonicalCombiningClass::NotReordered
2130 {
2131 // If we came here, we had an intervening non-matching
2132 // non-starter, after which we cannot contract another
2133 // starter anymore.
2134 continue 'ce32loop;
2135 }
2136 match (
2137 most_recent_skipped_ccc < ccc,
2138 trie.next(ch.character()),
2139 ) {
2140 (
2141 true,
2142 TrieResult::Intermediate(ce32_i),
2143 ) => {
2144 let _ = self
2145 .upcoming
2146 .remove(looked_ahead + i);
2147 while let Some(idx) =
2148 pending_removals.pop()
2149 {
2150 self.upcoming
2151 .remove(looked_ahead + idx);
2152 i -= 1; // Adjust for the shortening
2153 }
2154 attempt = 0;
2155 longest_matching_index = i;
2156 longest_matching_state =
2157 trie.clone();
2158 ce32 = CollationElement32::new(
2159 ce32_i as u32,
2160 );
2161 }
2162 (
2163 true,
2164 TrieResult::FinalValue(ce32_i),
2165 ) => {
2166 let _ = self
2167 .upcoming
2168 .remove(looked_ahead + i);
2169 while let Some(idx) =
2170 pending_removals.pop()
2171 {
2172 self.upcoming
2173 .remove(looked_ahead + idx);
2174 }
2175 ce32 = CollationElement32::new(
2176 ce32_i as u32,
2177 );
2178 continue 'ce32loop;
2179 }
2180 (_, TrieResult::NoValue) => {
2181 pending_removals.push(i);
2182 i += 1;
2183 }
2184 _ => {
2185 pending_removals.clear();
2186 most_recent_skipped_ccc = ccc;
2187 attempt += 1;
2188 i = longest_matching_index
2189 + attempt;
2190 trie =
2191 longest_matching_state.clone();
2192 }
2193 }
2194 } else {
2195 continue 'ce32loop;
2196 }
2197 }
2198 }
2199 TrieResult::Intermediate(ce32_i) => {
2200 longest_matching_state = trie.clone();
2201 drain_from_upcoming = looked_ahead;
2202 ce32 = CollationElement32::new(ce32_i as u32);
2203 }
2204 TrieResult::FinalValue(ce32_i) => {
2205 drain_from_upcoming = looked_ahead;
2206 ce32 = CollationElement32::new(ce32_i as u32);
2207 continue 'ce32loop;
2208 }
2209 }
2210 } else {
2211 continue 'ce32loop;
2212 }
2213 }
2214 // Unreachable
2215 }
2216 Tag::Digit => {
2217 if let Some(high_bits) = self.numeric_primary {
2218 let mut digits: SmallVec<[u8; DIGIT_BUFFER_SIZE]> =
2219 SmallVec::new(); // TODO(#2005): Figure out good length
2220 digits.push(ce32.digit());
2221 let numeric_primary = u32::from(high_bits) << 24;
2222 if combining_characters.is_empty() {
2223 // Numeric collation doesn't work with combining
2224 // characters applied to the digits.
2225 // It's unclear if reading from the tailoring first
2226 // is needed for practical purposes, since it doesn't
2227 // make much sense to tailor the numeric value of digits.
2228 // Performing the usual fallback pattern anyway just in
2229 // case.
2230 may_have_contracted_starter = true;
2231 self.ensure_upcoming_normalized();
2232 while let Some(upcoming) = self.look_ahead(looked_ahead) {
2233 looked_ahead += 1;
2234 ce32 =
2235 self.tailoring.ce32_for_char(upcoming.character());
2236 if ce32 == FALLBACK_CE32 {
2237 ce32 =
2238 self.root.ce32_for_char(upcoming.character());
2239 }
2240 if ce32.tag_checked() != Some(Tag::Digit) {
2241 break;
2242 }
2243 drain_from_upcoming = looked_ahead;
2244 digits.push(ce32.digit());
2245 }
2246 }
2247 let mut remaining = digits.as_slice();
2248 while !remaining.is_empty() {
2249 // Skip leading zeros
2250
2251 // If this isn't our initial loop round and we've truncated
2252 // a chunk to 254 digits on a previous round, the eventual
2253 // comparison result can be wrong, but that replicates an
2254 // ICU4C bug. Let's fix both as a follow-up.
2255 loop {
2256 let Some((first, tail)) = remaining.split_first()
2257 else {
2258 // Keep one zero
2259 // If we get here, we must have skipped a zero, since
2260 // 1) the while loop condition above meant that we started
2261 // with a non-empty slice AND
2262 // 2) this loop only skips zeros
2263 // Instead of trying to recover the same zero that we already
2264 // skipped, let's just fill in a static slice.
2265 remaining = &[0];
2266 break;
2267 };
2268 if *first != 0 {
2269 break;
2270 }
2271 remaining = tail;
2272 }
2273 // Numeric CEs are generated for segments of
2274 // up to 254 digits.
2275 let (head, tail) = remaining
2276 .split_at_checked(254)
2277 .unwrap_or((remaining, b""));
2278 remaining = tail;
2279 // From ICU4C CollationIterator::appendNumericSegmentCEs
2280 if head.len() <= 7 {
2281 let mut digit_iter = head.iter();
2282 // `unwrap` succeeds, because we always have at least one
2283 // digit to even start numeric processing.
2284 #[expect(clippy::unwrap_used)]
2285 let mut value = u32::from(*digit_iter.next().unwrap());
2286 for &digit in digit_iter {
2287 value *= 10;
2288 value += u32::from(digit);
2289 }
2290 // Primary weight second byte values:
2291 // 74 byte values 2.. 75 for small numbers in two-byte primary weights.
2292 // 40 byte values 76..115 for medium numbers in three-byte primary weights.
2293 // 16 byte values 116..131 for large numbers in four-byte primary weights.
2294 // 124 byte values 132..255 for very large numbers with 4..127 digit pairs.
2295 let mut first_byte = 2u32;
2296 let mut num_bytes = 74u32;
2297 if value < num_bytes {
2298 self.pending.push(
2299 CollationElement::new_from_primary(
2300 numeric_primary
2301 | ((first_byte + value) << 16),
2302 ),
2303 );
2304 continue;
2305 }
2306 value -= num_bytes;
2307 first_byte += num_bytes;
2308 num_bytes = 40;
2309 if value < num_bytes * 254 {
2310 // Three-byte primary for 74..10233=74+40*254-1, good for year numbers and more.
2311 self.pending.push(
2312 CollationElement::new_from_primary(
2313 numeric_primary
2314 | ((first_byte + value / 254) << 16)
2315 | ((2 + value % 254) << 8),
2316 ),
2317 );
2318 continue;
2319 }
2320 value -= num_bytes * 254;
2321 first_byte += num_bytes;
2322 num_bytes = 16;
2323 if value < num_bytes * 254 * 254 {
2324 // Four-byte primary for 10234..1042489=10234+16*254*254-1.
2325 let mut primary =
2326 numeric_primary | (2 + value % 254);
2327 value /= 254;
2328 primary |= (2 + value % 254) << 8;
2329 value /= 254;
2330 primary |= (first_byte + value % 254) << 16;
2331 self.pending.push(
2332 CollationElement::new_from_primary(primary),
2333 );
2334 continue;
2335 }
2336 // original value > 1042489
2337 }
2338 debug_assert!(head.len() >= 7);
2339 // The second primary byte value 132..255 indicates the number of digit pairs (4..127),
2340 // then we generate primary bytes with those pairs.
2341 // Omit trailing 00 pairs.
2342 // Decrement the value for the last pair.
2343
2344 // Set the exponent. 4 pairs->132, 5 pairs->133, ..., 127 pairs->255.
2345 let mut len = head.len();
2346 let num_pairs = (len as u32).div_ceil(2); // as u32 OK, because capped to 254
2347 let mut primary =
2348 numeric_primary | ((132 - 4 + num_pairs) << 16);
2349 // Find the length without trailing 00 pairs.
2350 //
2351 // The indexing below is within bounds due to the following:
2352 //
2353 // * We skipped leading zeros.
2354 // * If `len == 2`: The loop condition is false, because
2355 // `head[len - 2]` isn't a leading zero.
2356 // * If `len == 1`: The loop condition is false, because
2357 // `head[len - 1]` isn't a leading zero, and `&&`
2358 // short-circuits, so the `head[len - 2]` access doesn't
2359 // occur.
2360 #[expect(clippy::indexing_slicing)]
2361 while head[len - 1] == 0 && head[len - 2] == 0 {
2362 len -= 2;
2363 }
2364 // Read the first pair
2365 // Index in bounds by construction above.
2366 #[expect(clippy::indexing_slicing)]
2367 let mut digit_iter = head[..len].iter();
2368 // `unwrap` succeeds by construction
2369 #[expect(clippy::unwrap_used)]
2370 let mut pair = if len & 1 == 1 {
2371 // Only "half a pair" if we have an odd number of digits.
2372 u32::from(*digit_iter.next().unwrap())
2373 } else {
2374 u32::from(*digit_iter.next().unwrap()) * 10
2375 + u32::from(*digit_iter.next().unwrap())
2376 };
2377 pair = 11 + 2 * pair;
2378 let mut shift = 8u32;
2379 while let (Some(&left), Some(&right)) =
2380 (digit_iter.next(), digit_iter.next())
2381 {
2382 if shift == 0 {
2383 primary |= pair;
2384 self.pending.push(
2385 CollationElement::new_from_primary(primary),
2386 );
2387 primary = numeric_primary;
2388 shift = 16;
2389 } else {
2390 primary |= pair << shift;
2391 shift -= 8;
2392 }
2393 pair =
2394 11 + 2 * (u32::from(left) * 10 + u32::from(right));
2395 }
2396 primary |= (pair - 1) << shift;
2397 self.pending
2398 .push(CollationElement::new_from_primary(primary));
2399 }
2400 break 'ce32loop;
2401 }
2402 ce32 = data.get_ce32(ce32.index());
2403 continue 'ce32loop;
2404 }
2405 Tag::Offset => {
2406 self.pending.push(data.ce_from_offset_ce32(c, ce32));
2407 break 'ce32loop;
2408 }
2409 Tag::Implicit => {
2410 self.pending
2411 .push(CollationElement::new_implicit_from_char(c));
2412 break 'ce32loop;
2413 }
2414 Tag::Fallback
2415 | Tag::Reserved3
2416 | Tag::LongPrimary
2417 | Tag::LongSecondary
2418 | Tag::BuilderData
2419 | Tag::LeadSurrogate
2420 | Tag::LatinExpansion
2421 | Tag::U0000
2422 | Tag::Hangul => {
2423 debug_assert!(false);
2424 // GIGO case
2425 self.pending.push(FFFD_CE);
2426 break 'ce32loop;
2427 }
2428 }
2429 }
2430 }
2431 self.prefix_push(c);
2432 'combining_outer: loop {
2433 debug_assert!(drain_from_upcoming == 0 || combining_characters.is_empty());
2434 let mut i = 0;
2435 'combining: while let Some(ch) =
2436 combining_characters.get(i).map(|c| c.character())
2437 {
2438 c = ch;
2439 let diacritic_index = (c as usize).wrapping_sub(COMBINING_DIACRITICS_BASE);
2440 if let Some(secondary) = self.diacritics.get(diacritic_index) {
2441 // TODO(#2006): unlikely annotation
2442 if c == '\u{0307}' && self.lithuanian_dot_above {
2443 if let Some(next_c) =
2444 combining_characters.get(i + 1).map(|c| c.character())
2445 {
2446 if next_c == '\u{0300}'
2447 || next_c == '\u{0301}'
2448 || next_c == '\u{0303}'
2449 {
2450 // Lithuanian contracts COMBINING DOT ABOVE with three other diacritics of the
2451 // same combining class such that the COMBINING DOT ABOVE is ignored for
2452 // collation. Since the combining class is the same, it's valid to simply
2453 // look at the next character in `combining_characters`.
2454 i += 1;
2455 continue 'combining;
2456 }
2457 }
2458 }
2459 self.pending
2460 .push(CollationElement::new_from_secondary(secondary));
2461 self.mark_prefix_unmatchable();
2462 i += 1;
2463 continue 'combining;
2464 }
2465 // `c` is not a table-optimized diacritic.
2466 // Not bothering to micro optimize away the move of the remaining
2467 // part of `combining_characters`.
2468 let _ = combining_characters.drain(..=i);
2469 data = self.tailoring;
2470 ce32 = data.ce32_for_char(c);
2471 if ce32 == FALLBACK_CE32 {
2472 data = self.root;
2473 ce32 = data.ce32_for_char(c);
2474 }
2475 continue 'outer;
2476 }
2477 // Note: The borrow checker didn't like the iterator formulation
2478 // for the loop below, because the `Drain` would have kept `self`
2479 // mutable borrowed when trying to call `prefix_push`. To change
2480 // this, `prefix` and `prefix_push` would need to be refactored
2481 // into a struct.
2482 i = 0;
2483 while i < drain_from_upcoming {
2484 // By construction, `drain_from_upcoming` doesn't exceed `upcoming.len()`
2485 #[expect(clippy::indexing_slicing)]
2486 let ch = self.upcoming[i].character();
2487 self.prefix_push(ch);
2488 i += 1;
2489 }
2490 // TODO(#2004): The above makes prefix out of sync when starter-contracting
2491 // contractions use `pending_removals` instead of `drain_from_upcoming`.
2492 // Do there exist prefixes that overlap contraction suffixes?
2493 // At least as of CLDR 40, the two possible non-starters in prefixes,
2494 // kana voicing marks, shouldn't be participating in Brahmic contractions.
2495 let _ = self.upcoming.drain(..drain_from_upcoming);
2496 if self.upcoming.is_empty() {
2497 // Make the assertion conditional to make CI happy.
2498 #[cfg(debug_assertions)]
2499 debug_assert!(self.iter_exhausted || may_have_contracted_starter);
2500 if let Some(c_c_tv) = self.iter_next() {
2501 self.upcoming.push(c_c_tv);
2502 } else {
2503 #[cfg(debug_assertions)]
2504 {
2505 self.iter_exhausted = true;
2506 }
2507 }
2508 }
2509 if may_have_contracted_starter {
2510 may_have_contracted_starter = false;
2511 if !self.is_next_decomposition_starts_with_starter() {
2512 // We need to loop back and process another round of
2513 // non-starters in order to maintain the invariant of
2514 // `upcoming` on the next call to `next()`.
2515 drain_from_upcoming = 0;
2516 looked_ahead = 0;
2517 self.collect_combining(&mut combining_characters);
2518 continue 'combining_outer;
2519 }
2520 }
2521 // By construction, we have at least on pending CE by now.
2522 #[expect(clippy::indexing_slicing)]
2523 let ret = self.pending[0];
2524 debug_assert_eq!(self.pending_pos, 0);
2525 if self.pending.len() == 1 {
2526 self.pending.clear();
2527 } else {
2528 self.pending_pos = 1;
2529 }
2530 return ret;
2531 }
2532 }
2533 } else {
2534 NO_CE
2535 }
2536 }
2537
2538 #[inline(always)]
2539 fn collect_combining(
2540 &mut self,
2541 combining_characters: &mut SmallVec<[CharacterAndClass; COMBINING_CHARACTER_BUFFER_SIZE]>,
2542 ) {
2543 while !self.is_next_decomposition_starts_with_starter() {
2544 // `unwrap` is OK, because `!self.is_next_decomposition_starts_with_starter()`
2545 // means the `unwrap()` must succeed.
2546 #[expect(clippy::unwrap_used)]
2547 let combining = self.next_internal().unwrap().c_and_c;
2548 let combining_c = combining.character();
2549 if !in_inclusive_range(combining_c, '\u{0340}', '\u{0F81}') {
2550 combining_characters.push(combining);
2551 } else {
2552 // The Tibetan special cases are starters that decompose into non-starters.
2553 match combining_c {
2554 '\u{0340}' => {
2555 // COMBINING GRAVE TONE MARK
2556 combining_characters.push(CharacterAndClass::new(
2557 '\u{0300}',
2558 CanonicalCombiningClass::Above,
2559 ));
2560 }
2561 '\u{0341}' => {
2562 // COMBINING ACUTE TONE MARK
2563 combining_characters.push(CharacterAndClass::new(
2564 '\u{0301}',
2565 CanonicalCombiningClass::Above,
2566 ));
2567 }
2568 '\u{0343}' => {
2569 // COMBINING GREEK KORONIS
2570 combining_characters.push(CharacterAndClass::new(
2571 '\u{0313}',
2572 CanonicalCombiningClass::Above,
2573 ));
2574 }
2575 '\u{0344}' => {
2576 // COMBINING GREEK DIALYTIKA TONOS
2577 combining_characters.push(CharacterAndClass::new(
2578 '\u{0308}',
2579 CanonicalCombiningClass::Above,
2580 ));
2581 combining_characters.push(CharacterAndClass::new(
2582 '\u{0301}',
2583 CanonicalCombiningClass::Above,
2584 ));
2585 }
2586 '\u{0F73}' => {
2587 // TIBETAN VOWEL SIGN II
2588 combining_characters.push(CharacterAndClass::new(
2589 '\u{0F71}',
2590 CanonicalCombiningClass::CCC129,
2591 ));
2592 combining_characters.push(CharacterAndClass::new(
2593 '\u{0F72}',
2594 CanonicalCombiningClass::CCC130,
2595 ));
2596 }
2597 '\u{0F75}' => {
2598 // TIBETAN VOWEL SIGN UU
2599 combining_characters.push(CharacterAndClass::new(
2600 '\u{0F71}',
2601 CanonicalCombiningClass::CCC129,
2602 ));
2603 combining_characters.push(CharacterAndClass::new(
2604 '\u{0F74}',
2605 CanonicalCombiningClass::CCC132,
2606 ));
2607 }
2608 '\u{0F81}' => {
2609 // TIBETAN VOWEL SIGN REVERSED II
2610 combining_characters.push(CharacterAndClass::new(
2611 '\u{0F71}',
2612 CanonicalCombiningClass::CCC129,
2613 ));
2614 combining_characters.push(CharacterAndClass::new(
2615 '\u{0F80}',
2616 CanonicalCombiningClass::CCC130,
2617 ));
2618 }
2619 _ => {
2620 combining_characters.push(combining);
2621 }
2622 };
2623 }
2624 }
2625 if combining_characters.len() > 1 {
2626 // This optimizes away the class lookup when len() == 1.
2627 // Unclear if this micro optimization is worthwhile.
2628 // In any case, we store the CanonicalCombiningClass in order to
2629 // avoid having to look it up again when deciding whether to proceed
2630 // with a discontiguous match. As a side effect, it also means that
2631 // duplicate lookups aren't needed if the sort below happens to compare
2632 // an item more than once.
2633 combining_characters
2634 .iter_mut()
2635 .for_each(|cc| cc.set_ccc_from_trie_if_not_already_set(self.iter.trie()));
2636 combining_characters.sort_by_key(|cc| cc.ccc());
2637 }
2638 }
2639}