icu_datetime/provider/
packed_pattern.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//! Data structures for packing of datetime patterns.
6
7use super::pattern::{
8    runtime::{Pattern, PatternBorrowed, PatternMetadata},
9    PatternItem,
10};
11use crate::{options::Length, size_test_macro::size_test};
12use alloc::vec::Vec;
13use icu_plurals::{
14    provider::{FourBitMetadata, PluralElementsPackedULE},
15    PluralElements,
16};
17use icu_provider::prelude::*;
18use zerovec::{VarZeroVec, ZeroSlice};
19
20/// A field of [`PackedPatternsBuilder`].
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct LengthPluralElements<T> {
23    /// The "long" length pattern plural elements.
24    pub long: PluralElements<T>,
25    /// The "medium" length pattern plural elements.
26    pub medium: PluralElements<T>,
27    /// The "short" length pattern plural elements.
28    pub short: PluralElements<T>,
29}
30
31/// A builder for a [`PackedPatterns`].
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct PackedPatternsBuilder<'a> {
34    /// Patterns always available.
35    pub standard: LengthPluralElements<Pattern<'a>>,
36    /// Patterns for variant 0. If `None`, falls back to standard.
37    pub variant0: Option<LengthPluralElements<Pattern<'a>>>,
38    /// Patterns for variant 1. If `None`, falls back to standard.
39    pub variant1: Option<LengthPluralElements<Pattern<'a>>>,
40}
41
42size_test!(PackedPatterns, packed_skeleton_data_size, 32);
43
44icu_provider::data_marker!(
45    /// `DatetimePatternsDateBuddhistV1`
46    DatetimePatternsDateBuddhistV1,
47    PackedPatterns<'static>
48);
49icu_provider::data_marker!(
50    /// `DatetimePatternsDateChineseV1`
51    DatetimePatternsDateChineseV1,
52    PackedPatterns<'static>
53);
54icu_provider::data_marker!(
55    /// `DatetimePatternsDateCopticV1`
56    DatetimePatternsDateCopticV1,
57    PackedPatterns<'static>
58);
59icu_provider::data_marker!(
60    /// `DatetimePatternsDateDangiV1`
61    DatetimePatternsDateDangiV1,
62    PackedPatterns<'static>
63);
64icu_provider::data_marker!(
65    /// `DatetimePatternsDateEthiopianV1`
66    DatetimePatternsDateEthiopianV1,
67    PackedPatterns<'static>
68);
69icu_provider::data_marker!(
70    /// `DatetimePatternsDateGregorianV1`
71    DatetimePatternsDateGregorianV1,
72    PackedPatterns<'static>
73);
74icu_provider::data_marker!(
75    /// `DatetimePatternsDateHebrewV1`
76    DatetimePatternsDateHebrewV1,
77    PackedPatterns<'static>
78);
79icu_provider::data_marker!(
80    /// `DatetimePatternsDateIndianV1`
81    DatetimePatternsDateIndianV1,
82    PackedPatterns<'static>
83);
84icu_provider::data_marker!(
85    /// `DatetimePatternsDateHijriV1`
86    DatetimePatternsDateHijriV1,
87    PackedPatterns<'static>
88);
89icu_provider::data_marker!(
90    /// `DatetimePatternsDateJapaneseV1`
91    DatetimePatternsDateJapaneseV1,
92    PackedPatterns<'static>
93);
94icu_provider::data_marker!(
95    /// `DatetimePatternsDateJapanextV1`
96    DatetimePatternsDateJapanextV1,
97    PackedPatterns<'static>
98);
99icu_provider::data_marker!(
100    /// `DatetimePatternsDatePersianV1`
101    DatetimePatternsDatePersianV1,
102    PackedPatterns<'static>
103);
104icu_provider::data_marker!(
105    /// `DatetimePatternsDateRocV1`
106    DatetimePatternsDateRocV1,
107    PackedPatterns<'static>
108);
109icu_provider::data_marker!(
110    /// `DatetimePatternsTimeV1`
111    DatetimePatternsTimeV1,
112    PackedPatterns<'static>
113);
114
115// Main data struct for packed datetime patterns.
116#[doc = packed_skeleton_data_size!()]
117///
118/// ## Variants
119///
120/// This supports a set of "standard" patterns plus up to two "variants".
121/// The variants are currently used by year formatting:
122///
123/// - Standard: Year, which could be partial precision (2-digit Gregorian)
124/// - Variant 0: Full Year, which is always full precision
125/// - Variant 1: Year With Era
126///
127/// And by time formatting:
128///
129/// - Standard: Hour only
130/// - Variant 0: Hour and minute
131/// - Variant 1: Hour, minute, and second
132///
133/// Variants should be used when the pattern could depend on the value being
134/// formatted. For example, with [`YearStyle::Auto`], any of these three
135/// patterns could be selected based on the year value.
136///
137/// ## Representation
138///
139/// Currently, there are at most 9 patterns that need to be stored together,
140/// named according to this table:
141///
142/// |        | Standard | Variant 0 | Variant 1 |
143/// |--------|----------|-----------|-----------|
144/// | Long   | La       | Lb        | Lc        |
145/// | Medium | Ma       | Mb        | Mc        |
146/// | Short  | Sa       | Sb        | Sc        |
147///
148/// The header byte encodes which pattern in the patterns array corresponds to
149/// a particular cell in the table. It contains the following information:
150///
151/// - Bits 0-1: "LMS" value of the standard column
152/// - Bit 2: "Q" value: 1 for directly-indexed variants; 0 for per-cell offsets
153/// - Bits 3-20: Packed offset into patterns table for each variant cell
154/// - Bits 21-31: unused/reserved
155///
156/// The LMS value determines which pattern index is used for the first column:
157///
158/// | LMS Value   | Long Index | Medium Index | Short Index |
159/// |-------------|------------|--------------|-------------|
160/// | 0 (L=M=S)   | 0          | 0            | 0           |
161/// | 1 (L, M=S)  | 0          | 1            | 1           |
162/// | 2 (L=M, S)  | 0          | 0            | 1           |
163/// | 3 (L, M, S) | 0          | 1            | 2           |
164///
165/// If bit 2 is 1 (Q=1), it means there is one pattern per table cell,
166/// with the index offset by the short index `S` from the table above.
167/// However, this requires storing multiple, possibly duplicate, patterns in
168/// the packed structure. The more common case is Q=0 and then to store
169/// per-cell offsets in chunks of 3 bits per cell:
170///
171/// - Chunk = 0: Inherit according to the table below
172/// - Chunk = 1-7: Use pattern index Chunk - 1
173///
174/// This is summarized below:
175///
176/// | Cell in Table | Q=1 Pattern Index | Q=0 Header Bits | Inheritance |
177/// |---------------|-------------------|-----------------|-------------|
178/// | Lb            | S + 1             | 3-5             | La          |
179/// | Mb            | S + 2             | 6-8             | Ma          |
180/// | Sb            | S + 3             | 9-11            | Sa          |
181/// | Lc            | S + 4             | 12-14           | La          |
182/// | Mc            | S + 5             | 15-17           | Ma          |
183/// | Sc            | S + 6             | 18-20           | Sa          |
184///
185/// As a result, if there are no variants, bits 2 and higher will be all zero,
186/// making the header int suitable for varint packing, such as that used by
187/// postcard and other size-optimized serialization formats.
188///
189/// [`YearStyle::Auto`]: crate::options::YearStyle::Auto
190#[derive(Debug, PartialEq, Eq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
191#[cfg_attr(feature = "datagen", derive(databake::Bake))]
192#[cfg_attr(feature = "datagen", databake(path = icu_datetime::provider))]
193pub struct PackedPatterns<'data> {
194    /// An encoding of which standard/variant cell corresponds to which entry
195    /// in the patterns table. See class docs.
196    pub header: u32,
197    /// The list of patterns. Length should be between 1 and 9,
198    /// depending on the header.
199    pub elements: VarZeroVec<'data, PluralElementsPackedULE<ZeroSlice<PatternItem>>>,
200}
201
202icu_provider::data_struct!(
203    PackedPatterns<'_>,
204    #[cfg(feature = "datagen")]
205);
206
207mod constants {
208    /// Value when standard long, medium, and short are all the same
209    pub(super) const LMS: u32 = 0;
210    /// Value when standard medium is the same as short but not long
211    pub(super) const L_MS: u32 = 1;
212    /// Value when standard medium is the same as long but not short
213    pub(super) const LM_S: u32 = 2;
214    /// Bit that indicates that standard medium differs from standard long
215    pub(super) const M_DIFFERS: u32 = 0x1;
216    /// Bit that indicates that standard short differs from standard medium
217    pub(super) const S_DIFFERS: u32 = 0x2;
218    /// Bitmask over all LMS values
219    pub(super) const LMS_MASK: u32 = 0x3;
220    /// Bit that indicates whether there are per-cell chunks
221    pub(super) const Q_BIT: u32 = 0x4;
222    /// A mask applied to individual chunks (the largest possible chunk)
223    pub(super) const CHUNK_MASK: u32 = 0x7;
224}
225
226struct UnpackedPatterns<'a> {
227    pub(super) has_explicit_medium: bool,
228    pub(super) has_explicit_short: bool,
229    pub(super) variant_indices: VariantIndices,
230    pub(super) elements: Vec<PluralElements<Pattern<'a>>>,
231}
232
233#[repr(u8)]
234#[derive(Copy, Clone)]
235enum VariantPatternIndex {
236    Inherit = 0,
237    I0 = 1,
238    I1 = 2,
239    I2 = 3,
240    I3 = 4,
241    I4 = 5,
242    I5 = 6,
243    I6 = 7,
244}
245
246impl VariantPatternIndex {
247    #[cfg(feature = "datagen")]
248    pub(super) fn from_header_with_shift(header: u32, shift: u32) -> Self {
249        match Self::try_from_u32((header >> shift) & constants::CHUNK_MASK) {
250            Some(x) => x,
251            None => {
252                debug_assert!(false, "unreachable");
253                Self::Inherit
254            }
255        }
256    }
257
258    fn try_from_u32(u: u32) -> Option<Self> {
259        match u {
260            0 => Some(Self::Inherit),
261            1 => Some(Self::I0),
262            2 => Some(Self::I1),
263            3 => Some(Self::I2),
264            4 => Some(Self::I3),
265            5 => Some(Self::I4),
266            6 => Some(Self::I5),
267            7 => Some(Self::I6),
268            _ => None,
269        }
270    }
271
272    pub(super) fn try_from_chunks_u32(chunks: [u32; 6]) -> Option<[Self; 6]> {
273        let [c0, c1, c2, c3, c4, c5] = chunks;
274        Some([
275            Self::try_from_u32(c0)?,
276            Self::try_from_u32(c1)?,
277            Self::try_from_u32(c2)?,
278            Self::try_from_u32(c3)?,
279            Self::try_from_u32(c4)?,
280            Self::try_from_u32(c5)?,
281        ])
282    }
283
284    pub(super) fn to_chunks_u32(chunks: [Self; 6]) -> [u32; 6] {
285        let [c0, c1, c2, c3, c4, c5] = chunks;
286        [
287            c0 as u32, c1 as u32, c2 as u32, c3 as u32, c4 as u32, c5 as u32,
288        ]
289    }
290}
291
292enum VariantIndices {
293    OnePatternPerVariant,
294    IndicesPerVariant([VariantPatternIndex; 6]),
295}
296
297impl<'a> UnpackedPatterns<'a> {
298    pub(super) fn build(&self) -> PackedPatterns<'static> {
299        let mut header = 0u32;
300        if self.has_explicit_medium {
301            header |= constants::M_DIFFERS;
302        }
303        if self.has_explicit_short {
304            header |= constants::S_DIFFERS;
305        }
306        match self.variant_indices {
307            VariantIndices::OnePatternPerVariant => {
308                header |= constants::Q_BIT;
309            }
310            VariantIndices::IndicesPerVariant(chunks) => {
311                let mut shift = 3;
312                for chunk_u32 in VariantPatternIndex::to_chunks_u32(chunks).iter() {
313                    debug_assert!(*chunk_u32 <= constants::CHUNK_MASK);
314                    header |= *chunk_u32 << shift;
315                    shift += 3;
316                }
317            }
318        }
319        let elements: Vec<PluralElements<(FourBitMetadata, &ZeroSlice<PatternItem>)>> = self
320            .elements
321            .iter()
322            .map(|plural_elements| {
323                plural_elements.as_ref().map(|pattern| {
324                    (
325                        pattern.metadata.to_four_bit_metadata(),
326                        pattern.items.as_slice(),
327                    )
328                })
329            })
330            .collect();
331        PackedPatterns {
332            header,
333            elements: elements.as_slice().into(),
334        }
335    }
336
337    #[cfg(feature = "datagen")]
338    pub(super) fn from_packed(packed: &'a PackedPatterns<'_>) -> Self {
339        let variant_indices = if (packed.header & constants::Q_BIT) != 0 {
340            VariantIndices::OnePatternPerVariant
341        } else {
342            VariantIndices::IndicesPerVariant([
343                VariantPatternIndex::from_header_with_shift(packed.header, 3),
344                VariantPatternIndex::from_header_with_shift(packed.header, 6),
345                VariantPatternIndex::from_header_with_shift(packed.header, 9),
346                VariantPatternIndex::from_header_with_shift(packed.header, 12),
347                VariantPatternIndex::from_header_with_shift(packed.header, 15),
348                VariantPatternIndex::from_header_with_shift(packed.header, 18),
349            ])
350        };
351        let elements = packed
352            .elements
353            .iter()
354            .map(|plural_elements| {
355                plural_elements.decode().map(|(metadata, items)| {
356                    PatternBorrowed {
357                        metadata: PatternMetadata::from_u8(metadata.get()),
358                        items,
359                    }
360                    .as_pattern()
361                })
362            })
363            .collect();
364        Self {
365            has_explicit_medium: (packed.header & constants::M_DIFFERS) != 0,
366            has_explicit_short: (packed.header & constants::S_DIFFERS) != 0,
367            variant_indices,
368            elements,
369        }
370    }
371}
372
373impl PackedPatternsBuilder<'_> {
374    /// Builds a packed pattern representation from the builder.
375    pub fn build(mut self) -> PackedPatterns<'static> {
376        self.simplify();
377
378        // Initialize the elements vector with the standard patterns.
379        let mut elements = Vec::new();
380        let mut has_explicit_medium = false;
381        let mut has_explicit_short = false;
382        elements.push(self.standard.long.as_ref().map(Pattern::as_ref));
383        let mut s_offset = 0;
384        if self.standard.medium != self.standard.long {
385            elements.push(self.standard.medium.as_ref().map(Pattern::as_ref));
386            has_explicit_medium = true;
387            s_offset += 1;
388        }
389        if self.standard.short != self.standard.medium {
390            elements.push(self.standard.short.as_ref().map(Pattern::as_ref));
391            has_explicit_short = true;
392            s_offset += 1;
393        }
394
395        // Fill in the variant patterns
396        let variant_patterns = [
397            self.variant0.as_ref().map(|v| &v.long),
398            self.variant0.as_ref().map(|v| &v.medium),
399            self.variant0.as_ref().map(|v| &v.short),
400            self.variant1.as_ref().map(|v| &v.long),
401            self.variant1.as_ref().map(|v| &v.medium),
402            self.variant1.as_ref().map(|v| &v.short),
403        ];
404        let fallbacks = [
405            &self.standard.long,
406            &self.standard.medium,
407            &self.standard.short,
408            &self.standard.long,
409            &self.standard.medium,
410            &self.standard.short,
411        ];
412        let mut chunks = [0u32; 6]; // per-cell chunk values
413        for ((pattern, fallback), chunk) in variant_patterns
414            .iter()
415            .zip(fallbacks.iter())
416            .zip(chunks.iter_mut())
417        {
418            if let Some(pattern) = pattern {
419                if pattern != fallback {
420                    *chunk = match elements.iter().position(|p| p == *pattern) {
421                        Some(i) => i as u32 + 1,
422                        None => {
423                            elements.push(pattern.as_ref().map(Pattern::as_ref));
424                            elements.len() as u32
425                        }
426                    }
427                }
428            }
429        }
430
431        // Check to see if we need to switch to Q=1 mode. We need to do this
432        // if any of the calculated chunk values is too big (larger than 7).
433        let variant_indices = if let Some(chunks) = VariantPatternIndex::try_from_chunks_u32(chunks)
434        {
435            // per-cell offsets
436            VariantIndices::IndicesPerVariant(chunks)
437        } else {
438            // one pattern per table cell
439            elements.truncate(s_offset + 1);
440            elements.extend(variant_patterns.into_iter().zip(fallbacks.iter()).map(
441                |(pattern, fallback)| pattern.unwrap_or(fallback).as_ref().map(Pattern::as_ref),
442            ));
443            VariantIndices::OnePatternPerVariant
444        };
445
446        // Now we can build the data representation
447        let unpacked = UnpackedPatterns {
448            has_explicit_medium,
449            has_explicit_short,
450            variant_indices,
451            elements,
452        };
453        unpacked.build()
454    }
455
456    fn simplify(&mut self) {
457        if self.variant0.as_ref() == Some(&self.standard) {
458            self.variant0 = None;
459        }
460        if self.variant1.as_ref() == Some(&self.standard) {
461            self.variant1 = None;
462        }
463    }
464}
465
466/// Which pattern to select. For details, see [`PackedPatterns`].
467pub(crate) enum PackedSkeletonVariant {
468    /// Default-precision year OR hours only
469    Standard,
470    /// Full-precision year OR hours and minutes
471    Variant0,
472    /// Year with era OR hours, minutes, and seconds
473    Variant1,
474}
475
476impl PackedPatterns<'_> {
477    pub(crate) fn get(
478        &self,
479        length: Length,
480        variant: PackedSkeletonVariant,
481    ) -> PatternBorrowed<'_> {
482        use Length::*;
483        use PackedSkeletonVariant::*;
484        let lms = self.header & constants::LMS_MASK;
485        let pattern_index = if matches!(variant, Standard) {
486            // Standard pattern (first column)
487            match (length, lms) {
488                (Long, _) => 0,
489                (Medium, constants::LMS | constants::LM_S) => 0,
490                (Medium, _) => 1,
491                (Short, constants::LMS) => 0,
492                (Short, constants::L_MS | constants::LM_S) => 1,
493                (Short, _) => 2,
494            }
495        } else {
496            let s_offset = match lms {
497                constants::LMS => 0,
498                constants::L_MS | constants::LM_S => 1,
499                _ => 2,
500            };
501            let q = self.header & constants::Q_BIT;
502            if q == 0 {
503                // per-cell offsets
504                let chunk_in_low_bits = match (length, variant) {
505                    (Long, Variant0) => self.header >> 3,
506                    (Medium, Variant0) => self.header >> 6,
507                    (Short, Variant0) => self.header >> 9,
508                    (Long, Variant1) => self.header >> 12,
509                    (Medium, Variant1) => self.header >> 15,
510                    (Short, Variant1) => self.header >> 18,
511                    (_, Standard) => {
512                        debug_assert!(false, "unreachable");
513                        return PatternBorrowed::DEFAULT;
514                    }
515                };
516                let chunk = chunk_in_low_bits & constants::CHUNK_MASK;
517                if chunk == 0 {
518                    // Fall back to standard with the same length
519                    return self.get(length, Standard);
520                }
521                chunk - 1
522            } else {
523                // one pattern per table cell
524                let additional_offset = match (length, variant) {
525                    (Long, Variant0) => 1,
526                    (Medium, Variant0) => 2,
527                    (Short, Variant0) => 3,
528                    (Long, Variant1) => 4,
529                    (Medium, Variant1) => 5,
530                    (Short, Variant1) => 6,
531                    (_, Standard) => {
532                        debug_assert!(false, "unreachable");
533                        return PatternBorrowed::DEFAULT;
534                    }
535                };
536                s_offset + additional_offset
537            }
538        };
539        let Some(plural_elements) = self.elements.get(pattern_index as usize) else {
540            debug_assert!(false, "unreachable");
541            return PatternBorrowed::DEFAULT;
542        };
543        let (metadata, items) = plural_elements.get_default();
544        PatternBorrowed {
545            metadata: PatternMetadata::from_u8(metadata.get()),
546            items,
547        }
548    }
549
550    fn get_as_plural_elements(
551        &self,
552        length: Length,
553        variant: PackedSkeletonVariant,
554    ) -> PluralElements<Pattern<'_>> {
555        PluralElements::new(self.get(length, variant).as_pattern())
556    }
557
558    /// Converts this packed data to a builder that can be mutated.
559    pub fn to_builder(&self) -> PackedPatternsBuilder<'_> {
560        use Length::*;
561        use PackedSkeletonVariant::*;
562        let mut builder = PackedPatternsBuilder {
563            standard: LengthPluralElements {
564                long: self.get_as_plural_elements(Long, Standard),
565                medium: self.get_as_plural_elements(Medium, Standard),
566                short: self.get_as_plural_elements(Short, Standard),
567            },
568            variant0: Some(LengthPluralElements {
569                long: self.get_as_plural_elements(Long, Variant0),
570                medium: self.get_as_plural_elements(Medium, Variant0),
571                short: self.get_as_plural_elements(Short, Variant0),
572            }),
573            variant1: Some(LengthPluralElements {
574                long: self.get_as_plural_elements(Long, Variant1),
575                medium: self.get_as_plural_elements(Medium, Variant1),
576                short: self.get_as_plural_elements(Short, Variant1),
577            }),
578        };
579        builder.simplify();
580        builder
581    }
582}
583
584#[cfg(feature = "serde")]
585mod _serde {
586    use super::*;
587    use crate::provider::pattern::reference;
588    use zerovec::VarZeroSlice;
589
590    #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
591    #[cfg_attr(feature = "datagen", derive(serde::Serialize))]
592    struct PackedPatternsMachine<'data> {
593        pub header: u32,
594        #[serde(borrow)]
595        pub elements: &'data VarZeroSlice<PluralElementsPackedULE<ZeroSlice<PatternItem>>>,
596    }
597
598    #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
599    #[cfg_attr(feature = "datagen", derive(serde::Serialize))]
600    #[derive(Default)]
601    struct PackedPatternsHuman {
602        #[cfg_attr(
603            feature = "serde",
604            serde(default, skip_serializing_if = "core::ops::Not::not")
605        )]
606        pub(super) has_explicit_medium: bool,
607        #[cfg_attr(
608            feature = "serde",
609            serde(default, skip_serializing_if = "core::ops::Not::not")
610        )]
611        pub(super) has_explicit_short: bool,
612        #[cfg_attr(
613            feature = "serde",
614            serde(default, skip_serializing_if = "core::ops::Not::not")
615        )]
616        pub(super) has_one_pattern_per_variant: bool,
617        #[cfg_attr(
618            feature = "serde",
619            serde(default, skip_serializing_if = "Option::is_none")
620        )]
621        pub(super) variant_pattern_indices: Option<[u32; 6]>,
622        pub(super) elements: Vec<reference::Pattern>,
623    }
624
625    impl<'de, 'data> serde::Deserialize<'de> for PackedPatterns<'data>
626    where
627        'de: 'data,
628    {
629        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
630        where
631            D: serde::Deserializer<'de>,
632        {
633            use serde::de::Error as _;
634            if deserializer.is_human_readable() {
635                let human = <PackedPatternsHuman>::deserialize(deserializer)?;
636                let variant_indices = match (
637                    human.has_one_pattern_per_variant,
638                    human.variant_pattern_indices,
639                ) {
640                    (true, None) => VariantIndices::OnePatternPerVariant,
641                    (false, Some(chunks)) => VariantIndices::IndicesPerVariant(
642                        VariantPatternIndex::try_from_chunks_u32(chunks).ok_or_else(|| {
643                            D::Error::custom("variant pattern index out of range")
644                        })?,
645                    ),
646                    _ => {
647                        return Err(D::Error::custom(
648                            "must have either one pattern per variant or indices",
649                        ))
650                    }
651                };
652                let elements = human
653                    .elements
654                    .iter()
655                    .map(|pattern| PluralElements::new(pattern.to_runtime_pattern()))
656                    .collect();
657                let unpacked = UnpackedPatterns {
658                    has_explicit_medium: human.has_explicit_medium,
659                    has_explicit_short: human.has_explicit_short,
660                    variant_indices,
661                    elements,
662                };
663                Ok(unpacked.build())
664            } else {
665                let machine = <PackedPatternsMachine>::deserialize(deserializer)?;
666                Ok(Self {
667                    header: machine.header,
668                    elements: machine.elements.as_varzerovec(),
669                })
670            }
671        }
672    }
673
674    #[cfg(feature = "datagen")]
675    impl serde::Serialize for PackedPatterns<'_> {
676        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
677        where
678            S: serde::Serializer,
679        {
680            use serde::ser::Error as _;
681            if serializer.is_human_readable() {
682                let unpacked = UnpackedPatterns::from_packed(self);
683                let mut human = PackedPatternsHuman {
684                    has_explicit_medium: unpacked.has_explicit_medium,
685                    has_explicit_short: unpacked.has_explicit_short,
686                    ..Default::default()
687                };
688                match unpacked.variant_indices {
689                    VariantIndices::OnePatternPerVariant => {
690                        human.has_one_pattern_per_variant = true;
691                    }
692                    VariantIndices::IndicesPerVariant(chunks) => {
693                        let chunks = VariantPatternIndex::to_chunks_u32(chunks);
694                        human.variant_pattern_indices = Some(chunks);
695                    }
696                }
697                human.elements = Vec::with_capacity(unpacked.elements.len());
698                for pattern_elements in unpacked.elements.into_iter() {
699                    let pattern = pattern_elements
700                        .try_into_other()
701                        .ok_or_else(|| S::Error::custom("cannot yet serialize plural patterns"))?;
702                    human.elements.push(reference::Pattern::from(&pattern));
703                }
704                human.serialize(serializer)
705            } else {
706                let machine = PackedPatternsMachine {
707                    header: self.header,
708                    elements: &self.elements,
709                };
710                machine.serialize(serializer)
711            }
712        }
713    }
714}
715
716#[cfg(test)]
717mod tests {
718    use super::*;
719    use crate::provider::pattern::reference;
720
721    const PATTERN_STRS: &[&str] = &[
722        "M/d/y",
723        "HH:mm",
724        "MMM d y G",
725        "E",
726        "E MMM d",
727        "dd.MM.yy",
728        "h a",
729        "hh:mm:ss B",
730        "y MMMM",
731    ];
732
733    fn get_patterns() -> Vec<Pattern<'static>> {
734        PATTERN_STRS
735            .iter()
736            .map(|s| {
737                s.parse::<reference::Pattern>()
738                    .unwrap()
739                    .to_runtime_pattern()
740            })
741            .collect::<Vec<_>>()
742    }
743
744    #[test]
745    fn test_basic() {
746        let patterns = get_patterns();
747        let mut it = patterns.iter().cloned();
748        let lms0 = LengthPluralElements {
749            long: PluralElements::new(it.next().unwrap()),
750            medium: PluralElements::new(it.next().unwrap()),
751            short: PluralElements::new(it.next().unwrap()),
752        };
753        let lms1 = LengthPluralElements {
754            long: PluralElements::new(it.next().unwrap()),
755            medium: PluralElements::new(it.next().unwrap()),
756            short: PluralElements::new(it.next().unwrap()),
757        };
758        let lms2 = LengthPluralElements {
759            long: PluralElements::new(it.next().unwrap()),
760            medium: PluralElements::new(it.next().unwrap()),
761            short: PluralElements::new(it.next().unwrap()),
762        };
763        let lms0a = LengthPluralElements {
764            long: PluralElements::new(patterns[0].clone()),
765            medium: PluralElements::new(patterns[0].clone()),
766            short: PluralElements::new(patterns[1].clone()),
767        };
768        let lms1a = LengthPluralElements {
769            long: PluralElements::new(patterns[3].clone()),
770            medium: PluralElements::new(patterns[4].clone()),
771            short: PluralElements::new(patterns[4].clone()),
772        };
773
774        {
775            // Q = 1
776            let builder = PackedPatternsBuilder {
777                standard: lms0.clone(),
778                variant0: Some(lms1.clone()),
779                variant1: Some(lms2.clone()),
780            };
781            let packed = builder.clone().build();
782            assert_eq!(packed.header, 7);
783            assert_eq!(packed.elements.len(), 9);
784            for (pattern_elements, expected) in packed.elements.iter().zip(patterns.iter()) {
785                assert_eq!(pattern_elements.get_default().1, &expected.items);
786            }
787            let recovered_builder = packed.to_builder();
788            assert_eq!(builder, recovered_builder);
789        }
790        {
791            // Q = 0
792            let builder = PackedPatternsBuilder {
793                standard: lms0.clone(),
794                variant0: Some(lms0.clone()),
795                variant1: Some(lms2.clone()),
796            };
797            let packed = builder.clone().build();
798            assert_eq!(packed.header, 0x1AC003);
799            assert_eq!(packed.elements.len(), 6);
800            let recovered_builder = packed.to_builder();
801            assert_ne!(builder, recovered_builder);
802            let mut builder = builder;
803            builder.simplify();
804            assert_eq!(builder, recovered_builder);
805        }
806        {
807            // No variants
808            let builder = PackedPatternsBuilder {
809                standard: lms0.clone(),
810                variant0: None,
811                variant1: None,
812            };
813            let packed = builder.clone().build();
814            assert_eq!(packed.header, 3);
815            assert_eq!(packed.elements.len(), 3);
816            let recovered_builder = packed.to_builder();
817            assert_eq!(builder, recovered_builder);
818        }
819        {
820            // Some duplicate patterns and inheritance
821            let builder = PackedPatternsBuilder {
822                standard: lms0a.clone(),
823                variant0: Some(lms0.clone()),
824                variant1: Some(lms1.clone()),
825            };
826            let packed = builder.clone().build();
827            assert_eq!(packed.header, 0x1AC682);
828            assert_eq!(packed.elements.len(), 6);
829            let recovered_builder = packed.to_builder();
830            assert_eq!(builder, recovered_builder);
831        }
832        {
833            // Q = 1 with 8 patterns (min for Q = 1)
834            let builder = PackedPatternsBuilder {
835                standard: lms0a.clone(),
836                variant0: Some(lms1.clone()),
837                variant1: Some(lms2.clone()),
838            };
839            let packed = builder.clone().build();
840            assert_eq!(packed.header, 6);
841            assert_eq!(packed.elements.len(), 8);
842            let recovered_builder = packed.to_builder();
843            assert_eq!(builder, recovered_builder);
844        }
845        {
846            // Q = 0 with 7 patterns (max for Q = 0)
847            let builder = PackedPatternsBuilder {
848                standard: lms1a.clone(),
849                variant0: Some(lms0a.clone()),
850                variant1: Some(lms2.clone()),
851            };
852            let packed = builder.clone().build();
853            assert_eq!(packed.header, 0x1F58D9);
854            assert_eq!(packed.elements.len(), 7);
855            let recovered_builder = packed.to_builder();
856            assert_eq!(builder, recovered_builder);
857        }
858    }
859
860    #[cfg(feature = "datagen")]
861    #[test]
862    fn test_serde() {
863        let patterns = get_patterns();
864        let lms0a = LengthPluralElements {
865            long: PluralElements::new(patterns[0].clone()),
866            medium: PluralElements::new(patterns[0].clone()),
867            short: PluralElements::new(patterns[1].clone()),
868        };
869        let lms1 = LengthPluralElements {
870            long: PluralElements::new(patterns[3].clone()),
871            medium: PluralElements::new(patterns[4].clone()),
872            short: PluralElements::new(patterns[5].clone()),
873        };
874
875        let builder = PackedPatternsBuilder {
876            standard: lms0a,
877            variant0: Some(lms1),
878            variant1: None,
879        };
880        let packed = builder.clone().build();
881
882        let bincode_bytes = bincode::serialize(&packed).unwrap();
883        assert_eq!(
884            bincode_bytes.as_slice(),
885            &[
886                26, 11, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 5, 0, 16, 0, 26, 0, 30, 0, 46, 0, 0, 128,
887                32, 1, 0, 0, 47, 128, 64, 1, 0, 0, 47, 128, 16, 1, 2, 128, 114, 2, 0, 0, 58, 128,
888                128, 2, 0, 128, 80, 1, 0, 128, 80, 1, 0, 0, 32, 128, 32, 3, 0, 0, 32, 128, 64, 1,
889                0, 128, 64, 2, 0, 0, 46, 128, 32, 2, 0, 0, 46, 128, 16, 2
890            ][..]
891        );
892        let bincode_recovered = bincode::deserialize::<PackedPatterns>(&bincode_bytes).unwrap();
893        assert_eq!(builder, bincode_recovered.to_builder());
894
895        let json_str = serde_json::to_string(&packed).unwrap();
896        assert_eq!(json_str, "{\"has_explicit_short\":true,\"variant_pattern_indices\":[3,4,5,0,0,0],\"elements\":[\"M/d/y\",\"HH:mm\",\"E\",\"E MMM d\",\"dd.MM.yy\"]}");
897        let json_recovered = serde_json::from_str::<PackedPatterns>(&json_str).unwrap();
898        assert_eq!(builder, json_recovered.to_builder());
899    }
900}