Skip to main content

icu_collator/
provider.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// The reordering algorithms in this file are adapted from ICU4C and,
6// therefore, are subject to the ICU license as described in LICENSE.
7
8//! 🚧 \[Unstable\] Data provider struct definitions for this ICU4X component.
9//!
10//! <div class="stab unstable">
11//! 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
12//! including in SemVer minor releases. While the serde representation of data structs is guaranteed
13//! to be stable, their Rust representation might not be. Use with caution.
14//! </div>
15//!
16//! Read more about data providers: [`icu_provider`]
17
18// Provider structs must be stable
19#![allow(clippy::exhaustive_structs, clippy::exhaustive_enums)]
20
21use icu_collections::char16trie::Char16TrieIterator;
22use icu_collections::codepointtrie::SmallCodePointTrie;
23use icu_collections::codepointtrie::TypedCodePointTrie;
24use icu_provider::prelude::*;
25use zerovec::ule::AsULE;
26use zerovec::ZeroVec;
27use zerovec::{zeroslice, ZeroSlice};
28
29use crate::elements::CollationElement;
30use crate::elements::CollationElement32;
31use crate::elements::Tag;
32use crate::elements::EMPTY_U16;
33use crate::elements::FFFD_CE;
34use crate::elements::FFFD_CE32;
35use crate::elements::FFFD_CE32_VALUE;
36use crate::elements::FFFD_CE_VALUE;
37use crate::elements::FFFD_PRIMARY;
38use crate::elements::NO_CE_PRIMARY;
39use crate::preferences::CollationCaseFirst;
40
41use crate::options::MaxVariable;
42
43#[cfg(feature = "compiled_data")]
44#[derive(Debug)]
45/// Baked data
46///
47/// <div class="stab unstable">
48/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
49/// including in SemVer minor releases. In particular, the `DataProvider` implementations are only
50/// guaranteed to match with this version's `*_unstable` providers. Use with caution.
51/// </div>
52pub struct Baked;
53
54#[cfg(feature = "compiled_data")]
55#[allow(unused_imports)]
56const _: () = {
57    use icu_collator_data::*;
58    pub mod icu {
59        pub use crate as collator;
60        pub use icu_collections as collections;
61        pub use icu_locale as locale;
62    }
63    make_provider!(Baked);
64    impl_collation_root_v1!(Baked);
65    impl_collation_tailoring_v1!(Baked);
66    impl_collation_diacritics_v1!(Baked);
67    impl_collation_jamo_v1!(Baked);
68    impl_collation_metadata_v1!(Baked);
69    impl_collation_special_primaries_v1!(Baked);
70    impl_collation_reordering_v1!(Baked);
71};
72
73const SCRIPT_FALLBACK: icu_provider::fallback::LocaleFallbackConfig = {
74    let mut c = icu_provider::fallback::LocaleFallbackConfig::default();
75    c.priority = icu_provider::fallback::LocaleFallbackPriority::Script;
76    c
77};
78
79icu_provider::data_marker!(
80    /// Data marker for singleton root collation data.
81    CollationRootV1,
82    "collation/root/v1",
83    CollationData<'static>,
84    is_singleton = true,
85);
86icu_provider::data_marker!(
87    /// Data marker for collation tailorings.
88    CollationTailoringV1,
89    "collation/tailoring/v1",
90    CollationData<'static>,
91    fallback_config = SCRIPT_FALLBACK,
92    #[cfg(feature = "datagen")]
93    attributes_domain = "collator",
94);
95icu_provider::data_marker!(
96    /// Data marker for collation diacritics data.
97    CollationDiacriticsV1,
98    "collation/diacritics/v1",
99    CollationDiacritics<'static>,
100    fallback_config = SCRIPT_FALLBACK,
101    #[cfg(feature = "datagen")]
102    attributes_domain = "collator",
103);
104icu_provider::data_marker!(
105    /// Data marker for collation jamo data.
106    CollationJamoV1,
107    "collation/jamo/v1",
108    CollationJamo<'static>,
109    is_singleton = true,
110);
111icu_provider::data_marker!(
112    /// Data marker for collation reordering data.
113    CollationReorderingV1,
114    "collation/reordering/v1",
115    CollationReordering<'static>,
116    fallback_config = SCRIPT_FALLBACK,
117    #[cfg(feature = "datagen")]
118    attributes_domain = "collator",
119);
120icu_provider::data_marker!(
121    /// Data marker for collation metadata.
122    CollationMetadataV1,
123    "collation/metadata/v1",
124    CollationMetadata,
125    fallback_config = SCRIPT_FALLBACK,
126    #[cfg(feature = "datagen")]
127    attributes_domain = "collator",
128);
129icu_provider::data_marker!(
130    /// Data marker for collcation special primaries data.
131    CollationSpecialPrimariesV1,
132    "collation/special/primaries/v1",
133    CollationSpecialPrimaries<'static>,
134    is_singleton = true,
135);
136
137#[cfg(feature = "datagen")]
138/// The latest minimum set of markers required by this component.
139pub const MARKERS: &[DataMarkerInfo] = &[
140    CollationRootV1::INFO,
141    CollationTailoringV1::INFO,
142    CollationDiacriticsV1::INFO,
143    CollationJamoV1::INFO,
144    CollationMetadataV1::INFO,
145    CollationReorderingV1::INFO,
146    CollationSpecialPrimariesV1::INFO,
147];
148
149const SINGLE_U32: &ZeroSlice<u32> =
150    zeroslice!(u32; <u32 as AsULE>::ULE::from_unsigned; [FFFD_CE32_VALUE]);
151const SINGLE_U64: &ZeroSlice<u64> =
152    zeroslice!(u64; <u64 as AsULE>::ULE::from_unsigned; [FFFD_CE_VALUE]);
153
154fn data_ce_to_primary(data_ce: u64, c: char) -> u32 {
155    // Collation::getThreeBytePrimaryForOffsetData
156    let p = (data_ce >> 32) as u32; // three-byte primary pppppp00
157    let lower32 = data_ce as u32 as i32; // base code point b & step s: bbbbbbss (bit 7: isCompressible)
158    let mut offset = ((u32::from(c) as i32) - (lower32 >> 8)) * (lower32 & 0x7F); // delta * increment
159    let is_compressible = (lower32 & 0x80) != 0;
160    // Collation::incThreeBytePrimaryByOffset
161    offset += (((p >> 8) & 0xFF) as i32) - 2;
162    let mut primary = (((offset % 254) + 2) as u32) << 8;
163    offset /= 254;
164    // Same with the second byte,
165    // but reserve the PRIMARY_COMPRESSION_LOW_BYTE and high byte if necessary.
166    if is_compressible {
167        offset += (((p >> 16) & 0xFF) as i32) - 4;
168        primary |= (((offset % 251) + 4) as u32) << 16;
169        offset /= 251;
170    } else {
171        offset += (((p >> 16) & 0xFF) as i32) - 2;
172        primary |= (((offset % 254) + 2) as u32) << 16;
173        offset /= 254;
174    }
175    primary | ((p & 0xFF000000) + ((offset as u32) << 24))
176}
177
178/// The main collation data either for the root or for a tailoring
179///
180/// <div class="stab unstable">
181/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
182/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
183/// to be stable, their Rust representation might not be. Use with caution.
184/// </div>
185#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
186#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
187#[cfg_attr(feature = "datagen", databake(path = icu_collator::provider))]
188#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
189pub struct CollationData<'data> {
190    /// Mapping from `char` to `CollationElement32` (represented
191    /// as its `u32` bits).
192    #[cfg_attr(feature = "serde", serde(borrow))]
193    pub trie: SmallCodePointTrie<'data, u32>,
194    /// `CollationElement`s used in expansions and offset CE32s
195    /// (represented as their `u64` bits)
196    #[cfg_attr(feature = "serde", serde(borrow))]
197    pub ces: ZeroVec<'data, u64>,
198    /// `CollationElement32`s used in expansions and as defaults
199    /// for digits when the numeric mode is not in use
200    #[cfg_attr(feature = "serde", serde(borrow))]
201    pub ce32s: ZeroVec<'data, u32>,
202    /// Defaults and tries for prefix and contraction matching
203    #[cfg_attr(feature = "serde", serde(borrow))]
204    pub contexts: ZeroVec<'data, u16>,
205}
206
207icu_provider::data_struct!(
208    CollationData<'_>,
209    #[cfg(feature = "datagen")]
210);
211
212impl<'data> CollationData<'data> {
213    pub(crate) fn ce32_for_char(&self, c: char) -> CollationElement32 {
214        CollationElement32::new(self.trie.get(c))
215    }
216    pub(crate) fn get_ce32(&'data self, index: usize) -> CollationElement32 {
217        CollationElement32::new(if let Some(u) = self.ce32s.get(index) {
218            u
219        } else {
220            // GIGO case
221            debug_assert!(false);
222            FFFD_CE32_VALUE
223        })
224    }
225    pub(crate) fn get_ce32s(&'data self, index: usize, len: usize) -> &'data ZeroSlice<u32> {
226        if len > 0 {
227            if let Some(slice) = self.ce32s.get_subslice(index..index + len) {
228                return slice;
229            }
230        }
231        // GIGO case
232        debug_assert!(false);
233        SINGLE_U32
234    }
235    pub(crate) fn get_ces(&'data self, index: usize, len: usize) -> &'data ZeroSlice<u64> {
236        if len > 0 {
237            if let Some(slice) = self.ces.get_subslice(index..index + len) {
238                return slice;
239            }
240        }
241        // GIGO case
242        debug_assert!(false);
243        SINGLE_U64
244    }
245    pub(crate) fn get_primary_from_ces(&'data self, index: usize) -> u32 {
246        if let Some(ce) = self.ces.get(index) {
247            (ce >> 32) as u32
248        } else {
249            // GIGO case
250            debug_assert!(false);
251            FFFD_PRIMARY
252        }
253    }
254    fn get_default_and_trie_impl(
255        &'data self,
256        index: usize,
257    ) -> (CollationElement32, &'data ZeroSlice<u16>) {
258        if let Some(slice) = self.contexts.get_subslice(index..self.contexts.len()) {
259            #[expect(clippy::unwrap_used)]
260            if slice.len() >= 2 {
261                // `unwrap` must succeed due to the length check above.
262                let first = slice.get(0).unwrap();
263                let second = slice.get(1).unwrap();
264                let trie = slice.get_subslice(2..slice.len()).unwrap();
265                return (
266                    CollationElement32::new((u32::from(first) << 16) | u32::from(second)),
267                    trie,
268                );
269            }
270        }
271        // GIGO case
272        debug_assert!(false);
273        (FFFD_CE32, EMPTY_U16)
274    }
275    pub(crate) fn get_default_and_trie(
276        &'data self,
277        index: usize,
278    ) -> (CollationElement32, Char16TrieIterator<'data>) {
279        let (ce32, trie) = self.get_default_and_trie_impl(index);
280        (ce32, Char16TrieIterator::new(trie))
281    }
282    pub(crate) fn get_default(&'data self, index: usize) -> CollationElement32 {
283        let (ce32, _) = self.get_default_and_trie_impl(index);
284        ce32
285    }
286    pub(crate) fn ce_from_offset_ce32(
287        &self,
288        c: char,
289        ce32: CollationElement32,
290    ) -> CollationElement {
291        debug_assert!(ce32.tag() == Tag::Offset);
292        if let Some(data_ce) = self.ces.get(ce32.index()) {
293            CollationElement::new_from_primary(data_ce_to_primary(data_ce, c))
294        } else {
295            // GIGO case
296            debug_assert!(false);
297            FFFD_CE
298        }
299    }
300}
301
302/// Secondary weights for the start of the Combining Diacritics block.
303///
304/// <div class="stab unstable">
305/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
306/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
307/// to be stable, their Rust representation might not be. Use with caution.
308/// </div>
309#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
310#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
311#[cfg_attr(feature = "datagen", databake(path = icu_collator::provider))]
312#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
313pub struct CollationDiacritics<'data> {
314    /// Secondary weights for characters starting from U+0300 up
315    /// to but not including U+034F. May be shorter than that;
316    /// zero-length when a tailoring opts out of using this
317    /// feature altogether.
318    #[cfg_attr(feature = "serde", serde(borrow))]
319    pub secondaries: ZeroVec<'data, u16>,
320}
321
322icu_provider::data_struct!(
323    CollationDiacritics<'_>,
324    #[cfg(feature = "datagen")]
325);
326
327/// `CollationElement32`s for the Hangul Jamo Unicode Block
328///
329/// <div class="stab unstable">
330/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
331/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
332/// to be stable, their Rust representation might not be. Use with caution.
333/// </div>
334#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
335#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
336#[cfg_attr(feature = "datagen", databake(path = icu_collator::provider))]
337#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
338pub struct CollationJamo<'data> {
339    /// `CollationElement32`s (as `u32`s) for the Hangul Jamo Unicode Block.
340    /// The length must be equal to the size of the block (256).
341    #[cfg_attr(feature = "serde", serde(borrow))]
342    pub ce32s: ZeroVec<'data, u32>,
343}
344
345icu_provider::data_struct!(
346    CollationJamo<'_>,
347    #[cfg(feature = "datagen")]
348);
349
350/// Script reordering data
351///
352/// <div class="stab unstable">
353/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
354/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
355/// to be stable, their Rust representation might not be. Use with caution.
356/// </div>
357#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
358#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
359#[cfg_attr(feature = "datagen", databake(path = icu_collator::provider))]
360#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
361pub struct CollationReordering<'data> {
362    /// Limit of last reordered range. 0 if no reordering or no split bytes.
363    ///
364    /// Comment from ICU4C's `collationsettings.h`
365    pub min_high_no_reorder: u32,
366    /// 256-byte table for reordering permutation of primary lead
367    /// bytes; NULL if no reordering. A 0 entry at a non-zero index means
368    /// that the primary lead byte is "split" (there are different offsets
369    /// for primaries that share that lead byte) and the reordering offset
370    /// must be determined via the reorderRanges.
371    ///
372    /// Comment from ICU4C's `collationsettings.h`
373    #[cfg_attr(feature = "serde", serde(borrow))]
374    pub reorder_table: ZeroVec<'data, u8>, // len always 256
375    /// Primary-weight ranges for script reordering, to be used by
376    /// reorder(p) for split-reordered primary lead bytes.
377    ///
378    /// Each entry is a (limit, offset) pair. The upper 16 bits of the
379    /// entry are the upper 16 bits of the exclusive primary limit of
380    /// a range. Primaries between the previous limit and this one have
381    /// their lead bytes modified by the signed offset (-0xff..+0xff)
382    /// stored in the lower 16 bits.
383    ///
384    /// `CollationData::makeReorderRanges()` writes a full list where the
385    /// first range (at least for terminators and separators) has a 0
386    /// offset. The last range has a non-zero offset. minHighNoReorder
387    /// is set to the limit of that last range.
388    ///
389    /// In the settings object, the initial ranges before the first
390    /// split lead byte are omitted for efficiency; they are handled
391    /// by reorder(p) via the reorderTable. If there are no
392    /// split-reordered lead bytes, then no ranges are needed.
393    ///
394    /// Comment from ICU4C's `collationsettings.h`; names refer to
395    /// ICU4C.
396    #[cfg_attr(feature = "serde", serde(borrow))]
397    pub reorder_ranges: ZeroVec<'data, u32>,
398}
399
400icu_provider::data_struct!(
401    CollationReordering<'_>,
402    #[cfg(feature = "datagen")]
403);
404
405impl CollationReordering<'_> {
406    pub(crate) fn reorder(&self, primary: u32) -> u32 {
407        if let Some(b) = self.reorder_table.get((primary >> 24) as usize) {
408            if b != 0 || primary <= NO_CE_PRIMARY {
409                (u32::from(b) << 24) | (primary & 0x00FFFFFF)
410            } else {
411                self.reorder_ex(primary)
412            }
413        } else {
414            // GIGO case
415            debug_assert!(false);
416            primary
417        }
418    }
419
420    fn reorder_ex(&self, primary: u32) -> u32 {
421        if primary >= self.min_high_no_reorder {
422            return primary;
423        }
424        let q = primary | 0xFFFF;
425        for &range in self.reorder_ranges.as_ule_slice().iter() {
426            let r = u32::from_unaligned(range);
427            if q < r {
428                return primary.wrapping_add(r << 24);
429            }
430        }
431        // GIGO case
432        debug_assert!(false);
433        primary
434    }
435}
436
437/// Each non-alias collation that the data provider knows
438/// about explicitly has an data entry at least for this
439/// struct.
440///
441/// <div class="stab unstable">
442/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
443/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
444/// to be stable, their Rust representation might not be. Use with caution.
445/// </div>
446#[derive(Debug, PartialEq, Clone, Copy, yoke::Yokeable, zerofrom::ZeroFrom)]
447#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
448#[cfg_attr(feature = "datagen", databake(path = icu_collator::provider))]
449#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
450pub struct CollationMetadata {
451    /// See the mask constants in the `impl` block for the
452    /// bit layout. The other bits are ignored: They could
453    /// be from the future if their semantics such that
454    /// old code may ignore them.
455    ///
456    /// Note: At present, it's bogus for the bit for "upper
457    /// first" to be set if "case first" isn't also set.
458    /// However, the methods handle this case gracefully,
459    /// so there is no need for invariant validation.
460    pub bits: u32,
461}
462
463icu_provider::data_struct!(
464    CollationMetadata,
465    #[cfg(feature = "datagen")]
466);
467
468impl CollationMetadata {
469    const MAX_VARIABLE_MASK: u32 = 0b11;
470    const TAILORED_MASK: u32 = 1 << 3;
471    const TAILORED_DIACRITICS_MASK: u32 = 1 << 4;
472    const REORDERING_MASK: u32 = 1 << 5;
473    const LITHUANIAN_DOT_ABOVE_MASK: u32 = 1 << 6;
474    const BACWARD_SECOND_LEVEL_MASK: u32 = 1 << 7;
475    const ALTERNATE_SHIFTED_MASK: u32 = 1 << 8;
476    const CASE_FIRST_MASK: u32 = 1 << 9;
477    const UPPER_FIRST_MASK: u32 = 1 << 10;
478
479    #[inline(always)]
480    pub(crate) fn max_variable(self) -> MaxVariable {
481        // Safety: the possible numeric values for `MaxVariable` are from 0 to 3, inclusive,
482        // and it is repr(u8). MAX_VARIABLE_MASK here ensures our values have most 2 bits, which produces
483        // the same range.
484        unsafe { core::mem::transmute((self.bits & CollationMetadata::MAX_VARIABLE_MASK) as u8) }
485    }
486
487    #[inline(always)]
488    pub(crate) fn tailored(self) -> bool {
489        self.bits & CollationMetadata::TAILORED_MASK != 0
490    }
491
492    /// Vietnamese and Ewe
493    #[inline(always)]
494    pub(crate) fn tailored_diacritics(self) -> bool {
495        self.bits & CollationMetadata::TAILORED_DIACRITICS_MASK != 0
496    }
497
498    /// Lithuanian
499    #[inline(always)]
500    pub(crate) fn lithuanian_dot_above(self) -> bool {
501        self.bits & CollationMetadata::LITHUANIAN_DOT_ABOVE_MASK != 0
502    }
503
504    /// Canadian French
505    #[inline(always)]
506    pub(crate) fn backward_second_level(self) -> bool {
507        self.bits & CollationMetadata::BACWARD_SECOND_LEVEL_MASK != 0
508    }
509
510    #[inline(always)]
511    pub(crate) fn reordering(self) -> bool {
512        self.bits & CollationMetadata::REORDERING_MASK != 0
513    }
514
515    /// Thai
516    #[inline(always)]
517    pub(crate) fn alternate_shifted(self) -> bool {
518        self.bits & CollationMetadata::ALTERNATE_SHIFTED_MASK != 0
519    }
520
521    #[inline(always)]
522    pub(crate) fn case_first(self) -> CollationCaseFirst {
523        if self.bits & CollationMetadata::CASE_FIRST_MASK != 0 {
524            if self.bits & CollationMetadata::UPPER_FIRST_MASK != 0 {
525                CollationCaseFirst::Upper
526            } else {
527                CollationCaseFirst::Lower
528            }
529        } else {
530            CollationCaseFirst::False
531        }
532    }
533}
534
535/// Root-associated additional data that doesn't change in tailorings
536///
537/// These are the fields that logically belong to the root data but
538/// don't belong to the tailoring data and that are on this separate
539/// struct, since we have the same struct for a tailoring and the
540/// bulk of the root.
541///
542/// As a practical matter, this struct happens to only carry
543/// information about what concrete numeric values for primary
544/// weights are special in particular ways. In principle, when the
545/// root data is built, the root builder is allowed to assign the
546/// numeric values as it sees fit, which is why these aren't
547/// hard-coded.
548///
549/// Note: In 2.0.0 and prior, this struct was loaded only if
550/// it was known at collator construction time (based on options)
551/// that the data here was going to be needed. With the introduction
552/// of collation keys and the decision not to introduce a collator
553/// key generator object separate from the collator, this struct
554/// is now always loaded.
555///
556/// <div class="stab unstable">
557/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
558/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
559/// to be stable, their Rust representation might not be. Use with caution.
560/// </div>
561#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
562#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
563#[cfg_attr(feature = "datagen", databake(path = icu_collator::provider))]
564#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
565pub struct CollationSpecialPrimaries<'data> {
566    /// The primaries corresponding to `MaxVariable`
567    /// character classes packed so that each fits in
568    /// 16 bits. Length must match the number of enum
569    /// variants in `MaxVariable`, currently 4.
570    ///
571    /// This is potentially followed by 256 bits
572    /// (packed in 16 u16s) to classify every possible
573    /// byte into compressible or non-compressible.
574    #[cfg_attr(feature = "serde", serde(borrow))]
575    pub last_primaries: ZeroVec<'data, u16>,
576    /// The high 8 bits of the numeric primary
577    pub numeric_primary: u8,
578}
579
580#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
581pub(crate) struct CollationSpecialPrimariesValidated<'data> {
582    /// The primaries corresponding to `MaxVariable`
583    /// character classes packed so that each fits in
584    /// 16 bits. Length must match the number of enum
585    /// variants in `MaxVariable`, currently 4.
586    pub last_primaries: ZeroVec<'data, u16>,
587    /// The high 8 bits of the numeric primary
588    pub numeric_primary: u8,
589    /// 256 bits (packed in 16 u16s) to classify every possible
590    /// byte into compressible or non-compressible.
591    pub compressible_bytes: &'data [<u16 as AsULE>::ULE; 16],
592}
593
594impl CollationSpecialPrimariesValidated<'static> {
595    pub(crate) const HARDCODED_COMPRESSIBLE_BYTES_FALLBACK: &'static [<u16 as AsULE>::ULE; 16] = &[
596        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
597        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
598        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
599        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
600        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
601        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
602        <u16 as AsULE>::ULE::from_unsigned(0b1111_1111_1111_1110),
603        <u16 as AsULE>::ULE::from_unsigned(0b1111_1111_1111_1111),
604        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0001),
605        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
606        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
607        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
608        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
609        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
610        <u16 as AsULE>::ULE::from_unsigned(0b0000_0000_0000_0000),
611        <u16 as AsULE>::ULE::from_unsigned(0b0100_0000_0000_0000),
612    ];
613}
614
615icu_provider::data_struct!(
616    CollationSpecialPrimaries<'_>,
617    #[cfg(feature = "datagen")]
618);
619
620impl CollationSpecialPrimariesValidated<'_> {
621    #[expect(clippy::unwrap_used)]
622    pub(crate) fn last_primary_for_group(&self, max_variable: MaxVariable) -> u32 {
623        // `unwrap` is OK, because `Collator::try_new` validates the length.
624        //
625        // Minus one to generate the right lower 16 bits from the high 16 bits.
626        // See parse.cpp in genrb and getLastPrimaryForGroup in ICU4C.
627        (u32::from(self.last_primaries.get(max_variable as usize).unwrap()) << 16) - 1
628    }
629
630    #[allow(dead_code)]
631    pub(crate) fn is_compressible(&self, b: u8) -> bool {
632        // Indexing slicing OK by construction and pasting this
633        // into Compiler Explorer shows that the panic
634        // is optimized away.
635        #[expect(clippy::indexing_slicing)]
636        let field = u16::from_unaligned(self.compressible_bytes[usize::from(b >> 4)]);
637        let mask = 1 << (b & 0b1111);
638        (field & mask) != 0
639    }
640}
641
642/// Lists the locale and collation keyword combinations that the collator knows about.
643/// The `standard` collation is represented as the empty string.
644/// The root collation is represented as `und`.
645/// Chinese collations are listed as `und-Hani` with `und-Hant` and `und-Hans` resolving
646/// to `stroke` and `pinyin` despite not listing the collation keyword.
647///
648/// The iterator may (in practice _will_) yield duplicate items.
649#[cfg(all(feature = "compiled_data", feature = "unstable"))]
650pub fn list_locales() -> impl Iterator<Item = (DataLocale, tinystr::TinyAsciiStr<8>)> {
651    use icu_provider::baked::DataStore;
652    Baked::DATA_COLLATION_METADATA_V1
653        .iter()
654        .chain(Baked::DATA_COLLATION_TAILORING_V1.iter())
655        .chain(Baked::DATA_COLLATION_REORDERING_V1.iter())
656        .chain(Baked::DATA_COLLATION_DIACRITICS_V1.iter())
657        .map(|d| {
658            (
659                d.locale.clone(),
660                tinystr::TinyAsciiStr::<8>::try_from_str(d.marker_attributes.as_str())
661                    .expect("Marker attribute invariants upheld"),
662            )
663        })
664}