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