• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 12020603084

23 Nov 2024 08:43PM UTC coverage: 75.71% (+0.2%) from 75.477%
12020603084

push

github

sffc
Touch Cargo.lock

55589 of 73424 relevant lines covered (75.71%)

644270.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.47
/components/datetime/src/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

7
use super::pattern::{
8
    runtime::{Pattern, PatternBorrowed, PatternMetadata},
9
    PatternItem,
10
};
11
use crate::{size_test_macro::size_test, Length};
12
use alloc::vec::Vec;
13
use icu_plurals::{
14
    provider::{FourBitMetadata, PluralElementsPackedULE},
15
    PluralElements,
16
};
17
use icu_provider::prelude::*;
18
use zerovec::{VarZeroVec, ZeroSlice};
19

20
/// A field of [`PackedPatternsBuilder`].
21
#[derive(Debug, Clone, PartialEq, Eq)]
10,690✔
22
pub struct LengthPluralElements<T> {
23
    /// The "long" length pattern plural elements.
24
    pub long: PluralElements<T>,
5,345✔
25
    /// The "medium" length pattern plural elements.
26
    pub medium: PluralElements<T>,
4,632✔
27
    /// The "short" length pattern plural elements.
28
    pub short: PluralElements<T>,
5,345✔
29
}
30

31
/// A builder for a [`PackedPatternsV1`].
32
#[derive(Debug, Clone, PartialEq, Eq)]
32✔
33
pub struct PackedPatternsBuilder<'a> {
34
    /// Patterns always available.
35
    pub standard: LengthPluralElements<Pattern<'a>>,
16✔
36
    /// Patterns for variant 0. If `None`, falls back to standard.
37
    pub variant0: Option<LengthPluralElements<Pattern<'a>>>,
16✔
38
    /// Patterns for variant 1. If `None`, falls back to standard.
39
    pub variant1: Option<LengthPluralElements<Pattern<'a>>>,
16✔
40
}
41

42
size_test!(PackedPatternsV1, packed_skeleton_data_size, 32);
43

44
/// Main data struct for packed datetime patterns.
45
#[doc = packed_skeleton_data_size!()]
46
///
47
/// ## Variants
48
///
49
/// This supports a set of "standard" patterns plus up to two "variants".
50
/// The variants are currently used by year formatting:
51
///
52
/// - Standard: Year, which could be partial precision (2-digit Gregorain)
53
/// - Variant 0: Full Year, which is always full precision
54
/// - Variant 1: Year With Era
55
///
56
/// Variants should be used when the pattern could depend on the value being
57
/// formatted. For example, with [`YearStyle::Auto`], any of these three
58
/// patterns could be selected based on the year value.
59
///
60
/// ## Representation
61
///
62
/// Currently, there are at most 9 patterns that need to be stored together,
63
/// named according to this table:
64
///
65
/// |        | Standard | Variant 0 | Variant 1 |
66
/// |--------|----------|-----------|-----------|
67
/// | Long   | La       | Lb        | Lc        |
68
/// | Medium | Ma       | Mb        | Mc        |
69
/// | Short  | Sa       | Sb        | Sc        |
70
///
71
/// The header byte encodes which pattern in the patterns array corresponds to
72
/// a particular cell in the table. It contains the following information:
73
///
74
/// - Bits 0-1: "LMS" value of the standard column
75
/// - Bit 2: "Q" value: 1 for directly-indexed variants; 0 for per-cell offsets
76
/// - Bits 3-20: Packed offset into patterns table for each variant cell
77
/// - Bits 21-31: unused/reserved
78
///
79
/// The LMS value determines which pattern index is used for the first column:
80
///
81
/// | LMS Value   | Long Index | Medium Index | Short Index |
82
/// |-------------|------------|--------------|-------------|
83
/// | 0 (L=M=S)   | 0          | 0            | 0           |
84
/// | 1 (L, M=S)  | 0          | 1            | 1           |
85
/// | 2 (L=M, S)  | 0          | 0            | 1           |
86
/// | 3 (L, M, S) | 0          | 1            | 2           |
87
///
88
/// If bit 2 is 1 (Q=1), it means there is one pattern per table cell,
89
/// with the index offset by the short index `S` from the table above.
90
/// However, this requires storing multiple, possibly duplicate, patterns in
91
/// the packed structure. The more common case is Q=0 and then to store
92
/// per-cell offsets in chunks of 3 bits per cell:
93
///
94
/// - Chunk = 0: Inherit according to the table below
95
/// - Chunk = 1-7: Use pattern index Chunk - 1
96
///
97
/// This is summarized below:
98
///
99
/// | Cell in Table | Q=1 Pattern Index | Q=0 Header Bits | Inheritance |
100
/// |---------------|-------------------|-----------------|-------------|
101
/// | Lb            | S + 1             | 3-5             | La          |
102
/// | Mb            | S + 2             | 6-8             | Ma          |
103
/// | Sb            | S + 3             | 9-11            | Sa          |
104
/// | Lc            | S + 4             | 12-14           | La          |
105
/// | Mc            | S + 5             | 15-17           | Ma          |
106
/// | Sc            | S + 6             | 18-20           | Sa          |
107
///
108
/// As a result, if there are no variants, bits 2 and higher will be all zero,
109
/// making the header int suitable for varint packing, such as that used by
110
/// postcard and other size-optimized serialization formats.
111
///
112
/// [`YearStyle::Auto`]: crate::options::YearStyle::Auto
113
#[icu_provider::data_struct(
11,669✔
114
    // Date patterns
115
    marker(BuddhistDateNeoSkeletonPatternsV1Marker, "datetime/patterns/buddhist/skeleton@1"),
116
    marker(ChineseDateNeoSkeletonPatternsV1Marker, "datetime/patterns/chinese/skeleton@1"),
117
    marker(CopticDateNeoSkeletonPatternsV1Marker, "datetime/patterns/coptic/skeleton@1"),
118
    marker(DangiDateNeoSkeletonPatternsV1Marker, "datetime/patterns/dangi/skeleton@1"),
119
    marker(EthiopianDateNeoSkeletonPatternsV1Marker, "datetime/patterns/ethiopic/skeleton@1"),
120
    marker(GregorianDateNeoSkeletonPatternsV1Marker, "datetime/patterns/gregory/skeleton@1"),
121
    marker(HebrewDateNeoSkeletonPatternsV1Marker, "datetime/patterns/hebrew/skeleton@1"),
122
    marker(IndianDateNeoSkeletonPatternsV1Marker, "datetime/patterns/indian/skeleton@1"),
123
    marker(IslamicDateNeoSkeletonPatternsV1Marker, "datetime/patterns/islamic/skeleton@1"),
124
    marker(JapaneseDateNeoSkeletonPatternsV1Marker, "datetime/patterns/japanese/skeleton@1"),
125
    marker(JapaneseExtendedDateNeoSkeletonPatternsV1Marker, "datetime/patterns/japanext/skeleton@1"),
126
    marker(PersianDateNeoSkeletonPatternsV1Marker, "datetime/patterns/persian/skeleton@1"),
127
    marker(RocDateNeoSkeletonPatternsV1Marker, "datetime/patterns/roc/skeleton@1"),
128
    // Time patterns
129
    marker(TimeNeoSkeletonPatternsV1Marker, "datetime/patterns/time_skeleton@1")
130
)]
11,669✔
131
#[derive(Debug, PartialEq, Eq, Clone)]
5,256✔
132
#[cfg_attr(feature = "datagen", derive(databake::Bake))]
×
133
#[cfg_attr(feature = "datagen", databake(path = icu_datetime::provider))]
134
pub struct PackedPatternsV1<'data> {
135
    /// An encoding of which standard/variant cell corresponds to which entry
136
    /// in the patterns table. See class docs.
137
    pub header: u32,
2,628✔
138
    /// The list of patterns. Length should be between 1 and 9,
139
    /// depending on the header.
140
    pub elements: VarZeroVec<'data, PluralElementsPackedULE<ZeroSlice<PatternItem>>>,
2,628✔
141
}
142

143
mod constants {
144
    /// Value when standard long, medium, and short are all the same
145
    pub(super) const LMS: u32 = 0;
146
    /// Value when standard medium is the same as short but not long
147
    pub(super) const L_MS: u32 = 1;
148
    /// Value when standard medium is the same as long but not short
149
    pub(super) const LM_S: u32 = 2;
150
    /// Bit that indicates that standard medium differs from standard long
151
    pub(super) const M_DIFFERS: u32 = 0x1;
152
    /// Bit that indicates that standard short differs from standard medium
153
    pub(super) const S_DIFFERS: u32 = 0x2;
154
    /// Bitmask over all LMS values
155
    pub(super) const LMS_MASK: u32 = 0x3;
156
    /// Bit that indicates whether there are per-cell chunks
157
    pub(super) const Q_BIT: u32 = 0x4;
158
    /// A mask applied to individual chunks (the largest possible chunk)
159
    pub(super) const CHUNK_MASK: u32 = 0x7;
160
}
161

162
struct UnpackedPatterns<'a> {
163
    pub(super) has_explicit_medium: bool,
164
    pub(super) has_explicit_short: bool,
165
    pub(super) variant_indices: VariantIndices,
166
    pub(super) elements: Vec<PluralElements<Pattern<'a>>>,
167
}
168

169
#[repr(u8)]
170
#[derive(Copy, Clone)]
×
171
enum VariantPatternIndex {
172
    Inherit = 0,
173
    I0 = 1,
174
    I1 = 2,
175
    I2 = 3,
176
    I3 = 4,
177
    I4 = 5,
178
    I5 = 6,
179
    I6 = 7,
180
}
181

182
impl VariantPatternIndex {
183
    #[cfg(feature = "datagen")]
184
    pub(super) fn from_header_with_shift(header: u32, shift: u32) -> Self {
24✔
185
        match Self::try_from_u32((header >> shift) & constants::CHUNK_MASK) {
24✔
186
            Some(x) => x,
24✔
187
            None => {
188
                debug_assert!(false, "unreachable");
×
189
                Self::Inherit
190
            }
191
        }
192
    }
24✔
193

194
    fn try_from_u32(u: u32) -> Option<Self> {
15,857✔
195
        match u {
15,857✔
196
            0 => Some(Self::Inherit),
13,595✔
197
            1 => Some(Self::I0),
11✔
198
            2 => Some(Self::I1),
317✔
199
            3 => Some(Self::I2),
463✔
200
            4 => Some(Self::I3),
622✔
201
            5 => Some(Self::I4),
306✔
202
            6 => Some(Self::I5),
529✔
203
            7 => Some(Self::I6),
12✔
204
            _ => None,
2✔
205
        }
206
    }
15,857✔
207

208
    pub(super) fn try_from_chunks_u32(chunks: [u32; 6]) -> Option<[Self; 6]> {
2,639✔
209
        let [c0, c1, c2, c3, c4, c5] = chunks;
2,639✔
210
        Some([
2,637✔
211
            Self::try_from_u32(c0)?,
2,639✔
212
            Self::try_from_u32(c1)?,
2,639✔
213
            Self::try_from_u32(c2)?,
2,639✔
214
            Self::try_from_u32(c3)?,
2,639✔
215
            Self::try_from_u32(c4)?,
2,639✔
216
            Self::try_from_u32(c5)?,
2,638✔
217
        ])
218
    }
2,639✔
219

220
    pub(super) fn to_chunks_u32(chunks: [Self; 6]) -> [u32; 6] {
2,641✔
221
        let [c0, c1, c2, c3, c4, c5] = chunks;
2,641✔
222
        [
2,641✔
223
            c0 as u32, c1 as u32, c2 as u32, c3 as u32, c4 as u32, c5 as u32,
2,641✔
224
        ]
225
    }
2,641✔
226
}
227

228
enum VariantIndices {
229
    OnePatternPerVariant,
230
    IndicesPerVariant([VariantPatternIndex; 6]),
231
}
232

233
impl<'a> UnpackedPatterns<'a> {
234
    pub(super) fn build(&self) -> PackedPatternsV1<'static> {
2,639✔
235
        let mut header = 0u32;
2,639✔
236
        if self.has_explicit_medium {
2,639✔
237
            header |= constants::M_DIFFERS;
2,083✔
238
        }
239
        if self.has_explicit_short {
2,639✔
240
            header |= constants::S_DIFFERS;
1,428✔
241
        }
242
        match self.variant_indices {
2,639✔
243
            VariantIndices::OnePatternPerVariant => {
2✔
244
                header |= constants::Q_BIT;
2✔
245
            }
246
            VariantIndices::IndicesPerVariant(chunks) => {
2,637✔
247
                let mut shift = 3;
2,637✔
248
                for chunk_u32 in VariantPatternIndex::to_chunks_u32(chunks).iter() {
18,459✔
249
                    debug_assert!(*chunk_u32 <= constants::CHUNK_MASK);
15,822✔
250
                    header |= *chunk_u32 << shift;
15,822✔
251
                    shift += 3;
15,822✔
252
                }
253
            }
254
        }
255
        let elements: Vec<PluralElements<(FourBitMetadata, &ZeroSlice<PatternItem>)>> = self
2,639✔
256
            .elements
257
            .iter()
258
            .map(|plural_elements| {
7,511✔
259
                plural_elements.as_ref().map(|pattern| {
15,022✔
260
                    (
7,511✔
261
                        pattern.metadata.to_four_bit_metadata(),
7,511✔
262
                        pattern.items.as_slice(),
7,511✔
263
                    )
264
                })
7,511✔
265
            })
7,511✔
266
            .collect();
267
        PackedPatternsV1 {
2,639✔
268
            header,
2,639✔
269
            elements: elements.as_slice().into(),
2,639✔
270
        }
271
    }
2,639✔
272

273
    #[cfg(feature = "datagen")]
274
    pub(super) fn from_packed(packed: &'a PackedPatternsV1<'_>) -> Self {
4✔
275
        let variant_indices = if (packed.header & constants::Q_BIT) != 0 {
4✔
276
            VariantIndices::OnePatternPerVariant
×
277
        } else {
278
            VariantIndices::IndicesPerVariant([
4✔
279
                VariantPatternIndex::from_header_with_shift(packed.header, 3),
4✔
280
                VariantPatternIndex::from_header_with_shift(packed.header, 6),
4✔
281
                VariantPatternIndex::from_header_with_shift(packed.header, 9),
4✔
282
                VariantPatternIndex::from_header_with_shift(packed.header, 12),
4✔
283
                VariantPatternIndex::from_header_with_shift(packed.header, 15),
4✔
284
                VariantPatternIndex::from_header_with_shift(packed.header, 18),
4✔
285
            ])
286
        };
287
        let elements = packed
4✔
288
            .elements
289
            .iter()
290
            .map(|plural_elements| {
21✔
291
                plural_elements.decode().map(|(metadata, items)| {
42✔
292
                    PatternBorrowed {
21✔
293
                        metadata: PatternMetadata::from_u8(metadata.get()),
21✔
294
                        items,
295
                    }
296
                    .as_pattern()
297
                })
21✔
298
            })
21✔
299
            .collect();
300
        Self {
4✔
301
            has_explicit_medium: (packed.header & constants::M_DIFFERS) != 0,
4✔
302
            has_explicit_short: (packed.header & constants::S_DIFFERS) != 0,
4✔
303
            variant_indices,
4✔
304
            elements,
305
        }
306
    }
4✔
307
}
308

309
impl PackedPatternsBuilder<'_> {
310
    /// Builds a packed pattern representation from the builder.
311
    pub fn build(mut self) -> PackedPatternsV1<'static> {
2,638✔
312
        self.simplify();
2,638✔
313

314
        // Initialize the elements vector with the standard patterns.
315
        let mut elements = Vec::new();
2,638✔
316
        let mut has_explicit_medium = false;
2,638✔
317
        let mut has_explicit_short = false;
2,638✔
318
        elements.push(self.standard.long.as_ref().map(Pattern::as_ref));
2,638✔
319
        let mut s_offset = 0;
2,638✔
320
        if self.standard.medium != self.standard.long {
4,721✔
321
            elements.push(self.standard.medium.as_ref().map(Pattern::as_ref));
2,083✔
322
            has_explicit_medium = true;
2,083✔
323
            s_offset += 1;
2,083✔
324
        }
325
        if self.standard.short != self.standard.medium {
4,065✔
326
            elements.push(self.standard.short.as_ref().map(Pattern::as_ref));
1,427✔
327
            has_explicit_short = true;
1,427✔
328
            s_offset += 1;
1,427✔
329
        }
330

331
        // Fill in the variant patterns
332
        let variant_patterns = [
2,638✔
333
            self.variant0.as_ref().map(|v| &v.long),
2,966✔
334
            self.variant0.as_ref().map(|v| &v.medium),
2,966✔
335
            self.variant0.as_ref().map(|v| &v.short),
2,966✔
336
            self.variant1.as_ref().map(|v| &v.long),
3,140✔
337
            self.variant1.as_ref().map(|v| &v.medium),
3,140✔
338
            self.variant1.as_ref().map(|v| &v.short),
3,140✔
339
        ];
340
        let fallbacks = [
2,638✔
341
            &self.standard.long,
342
            &self.standard.medium,
2,638✔
343
            &self.standard.short,
2,638✔
344
            &self.standard.long,
345
            &self.standard.medium,
2,638✔
346
            &self.standard.short,
2,638✔
347
        ];
348
        let mut chunks = [0u32; 6]; // per-cell chunk values
2,638✔
349
        for ((pattern, fallback), chunk) in variant_patterns
21,104✔
350
            .iter()
351
            .zip(fallbacks.iter())
2,638✔
352
            .zip(chunks.iter_mut())
2,638✔
353
        {
354
            if let Some(pattern) = pattern {
15,828✔
355
                if pattern != fallback {
4,731✔
356
                    *chunk = match elements.iter().position(|p| p == *pattern) {
12,370✔
357
                        Some(i) => i as u32 + 1,
883✔
358
                        None => {
359
                            elements.push(pattern.as_ref().map(Pattern::as_ref));
1,358✔
360
                            elements.len() as u32
1,358✔
361
                        }
362
                    }
363
                }
364
            }
365
        }
366

367
        // Check to see if we need to switch to Q=1 mode. We need to do this
368
        // if any of the calculated chunk values is too big (larger than 7).
369
        let variant_indices = if let Some(chunks) = VariantPatternIndex::try_from_chunks_u32(chunks)
2,640✔
370
        {
371
            // per-cell offsets
372
            VariantIndices::IndicesPerVariant(chunks)
2,636✔
373
        } else {
374
            // one pattern per table cell
375
            elements.truncate(s_offset + 1);
2✔
376
            elements.extend(variant_patterns.into_iter().zip(fallbacks.iter()).map(
2✔
377
                |(pattern, fallback)| pattern.unwrap_or(fallback).as_ref().map(Pattern::as_ref),
12✔
378
            ));
379
            VariantIndices::OnePatternPerVariant
2✔
380
        };
381

382
        // Now we can build the data representation
383
        let unpacked = UnpackedPatterns {
2,638✔
384
            has_explicit_medium,
2,638✔
385
            has_explicit_short,
2,638✔
386
            variant_indices,
2,638✔
387
            elements,
2,638✔
388
        };
389
        unpacked.build()
2,638✔
390
    }
2,638✔
391

392
    fn simplify(&mut self) {
2,647✔
393
        if self.variant0.as_ref() == Some(&self.standard) {
4,959✔
394
            self.variant0 = None;
2,312✔
395
        }
396
        if self.variant1.as_ref() == Some(&self.standard) {
4,784✔
397
            self.variant1 = None;
2,137✔
398
        }
399
    }
2,647✔
400
}
401

402
pub(crate) enum PackedSkeletonVariant {
403
    Standard,
404
    Variant0,
405
    Variant1,
406
}
407

408
impl PackedPatternsV1<'_> {
409
    pub(crate) fn get(&self, length: Length, variant: PackedSkeletonVariant) -> PatternBorrowed {
1,574✔
410
        use Length::*;
411
        use PackedSkeletonVariant::*;
412
        let lms = self.header & constants::LMS_MASK;
1,574✔
413
        let pattern_index = if matches!(variant, Standard) {
1,574✔
414
            // Standard pattern (first column)
415
            match (length, lms) {
753✔
416
                (Long, _) => 0,
348✔
417
                (Medium, constants::LMS | constants::LM_S) => 0,
75✔
418
                (Medium, _) => 1,
133✔
419
                (Short, constants::LMS) => 0,
107✔
420
                (Short, constants::L_MS | constants::LM_S) => 1,
14✔
421
                (Short, _) => 2,
76✔
422
            }
423
        } else {
424
            let s_offset = match lms {
821✔
425
                constants::LMS => 0,
264✔
426
                constants::L_MS | constants::LM_S => 1,
156✔
427
                _ => 2,
401✔
428
            };
429
            let q = self.header & constants::Q_BIT;
821✔
430
            if q == 0 {
1,298✔
431
                // per-cell offsets
432
                let chunk_in_low_bits = match (length, variant) {
809✔
433
                    (Long, Variant0) => self.header >> 3,
29✔
434
                    (Medium, Variant0) => self.header >> 6,
32✔
435
                    (Short, Variant0) => self.header >> 9,
91✔
436
                    (Long, Variant1) => self.header >> 12,
368✔
437
                    (Medium, Variant1) => self.header >> 15,
168✔
438
                    (Short, Variant1) => self.header >> 18,
121✔
439
                    (_, Standard) => {
440
                        debug_assert!(false, "unreachable");
×
441
                        return PatternBorrowed::DEFAULT;
442
                    }
443
                };
444
                let chunk = chunk_in_low_bits & constants::CHUNK_MASK;
809✔
445
                if chunk == 0 {
809✔
446
                    // Fall back to standard with the same length
447
                    return self.get(length, Standard);
344✔
448
                }
449
                chunk - 1
465✔
450
            } else {
451
                // one pattern per table cell
452
                let additional_offset = match (length, variant) {
12✔
453
                    (Long, Variant0) => 1,
2✔
454
                    (Medium, Variant0) => 2,
2✔
455
                    (Short, Variant0) => 3,
2✔
456
                    (Long, Variant1) => 4,
2✔
457
                    (Medium, Variant1) => 5,
2✔
458
                    (Short, Variant1) => 6,
2✔
459
                    (_, Standard) => {
460
                        debug_assert!(false, "unreachable");
×
461
                        return PatternBorrowed::DEFAULT;
462
                    }
463
                };
464
                s_offset + additional_offset
12✔
465
            }
466
        };
467
        let Some(plural_elements) = self.elements.get(pattern_index as usize) else {
1,230✔
468
            debug_assert!(false, "unreachable");
×
469
            return PatternBorrowed::DEFAULT;
470
        };
471
        let (metadata, items) = plural_elements.get_default();
1,230✔
472
        PatternBorrowed {
1,230✔
473
            metadata: PatternMetadata::from_u8(metadata.get()),
1,230✔
474
            items,
475
        }
476
    }
1,574✔
477

478
    fn get_as_plural_elements(
72✔
479
        &self,
480
        length: Length,
481
        variant: PackedSkeletonVariant,
482
    ) -> PluralElements<Pattern> {
483
        PluralElements::new(self.get(length, variant).as_pattern())
72✔
484
    }
72✔
485

486
    /// Converts this packed data to a builder that can be mutated.
487
    pub fn to_builder(&self) -> PackedPatternsBuilder {
8✔
488
        use Length::*;
489
        use PackedSkeletonVariant::*;
490
        let mut builder = PackedPatternsBuilder {
8✔
491
            standard: LengthPluralElements {
8✔
492
                long: self.get_as_plural_elements(Long, Standard),
8✔
493
                medium: self.get_as_plural_elements(Medium, Standard),
8✔
494
                short: self.get_as_plural_elements(Short, Standard),
8✔
495
            },
×
496
            variant0: Some(LengthPluralElements {
8✔
497
                long: self.get_as_plural_elements(Long, Variant0),
8✔
498
                medium: self.get_as_plural_elements(Medium, Variant0),
8✔
499
                short: self.get_as_plural_elements(Short, Variant0),
8✔
500
            }),
×
501
            variant1: Some(LengthPluralElements {
8✔
502
                long: self.get_as_plural_elements(Long, Variant1),
8✔
503
                medium: self.get_as_plural_elements(Medium, Variant1),
8✔
504
                short: self.get_as_plural_elements(Short, Variant1),
8✔
505
            }),
×
506
        };
×
507
        builder.simplify();
8✔
508
        builder
8✔
509
    }
8✔
510
}
511

512
#[cfg(feature = "serde")]
513
mod _serde {
514
    use super::*;
515
    use crate::provider::pattern::reference;
516
    use zerovec::VarZeroSlice;
517

518
    #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
5,258✔
519
    #[cfg_attr(feature = "datagen", derive(serde::Serialize))]
2,630✔
520
    struct PackedPatternsMachine<'data> {
521
        pub header: u32,
×
522
        #[serde(borrow)]
523
        pub elements: &'data VarZeroSlice<PluralElementsPackedULE<ZeroSlice<PatternItem>>>,
×
524
    }
525

526
    #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
14✔
527
    #[cfg_attr(feature = "datagen", derive(serde::Serialize))]
4✔
528
    #[derive(Default)]
8✔
529
    struct PackedPatternsHuman {
530
        #[cfg_attr(
×
531
            feature = "serde",
532
            serde(default, skip_serializing_if = "core::ops::Not::not")
533
        )]
534
        pub(super) has_explicit_medium: bool,
7✔
535
        #[cfg_attr(
×
536
            feature = "serde",
537
            serde(default, skip_serializing_if = "core::ops::Not::not")
538
        )]
539
        pub(super) has_explicit_short: bool,
7✔
540
        #[cfg_attr(
×
541
            feature = "serde",
542
            serde(default, skip_serializing_if = "core::ops::Not::not")
543
        )]
544
        pub(super) has_one_pattern_per_variant: bool,
6✔
545
        #[cfg_attr(
×
546
            feature = "serde",
547
            serde(default, skip_serializing_if = "Option::is_none")
548
        )]
549
        pub(super) variant_pattern_indices: Option<[u32; 6]>,
6✔
550
        pub(super) elements: Vec<reference::Pattern>,
5✔
551
    }
552

553
    impl<'de, 'data> serde::Deserialize<'de> for PackedPatternsV1<'data>
554
    where
555
        'de: 'data,
556
    {
557
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2,630✔
558
        where
559
            D: serde::Deserializer<'de>,
560
        {
561
            use serde::de::Error as _;
562
            if deserializer.is_human_readable() {
5,259✔
563
                let human = <PackedPatternsHuman>::deserialize(deserializer)?;
1✔
564
                let variant_indices = match (
1✔
565
                    human.has_one_pattern_per_variant,
1✔
566
                    human.variant_pattern_indices,
1✔
567
                ) {
568
                    (true, None) => VariantIndices::OnePatternPerVariant,
×
569
                    (false, Some(chunks)) => VariantIndices::IndicesPerVariant(
2✔
570
                        VariantPatternIndex::try_from_chunks_u32(chunks).ok_or_else(|| {
1✔
571
                            D::Error::custom("variant pattern index out of range")
×
572
                        })?,
×
573
                    ),
1✔
574
                    _ => {
575
                        return Err(D::Error::custom(
×
576
                            "must have either one pattern per variant or indices",
577
                        ))
578
                    }
579
                };
580
                let elements = human
1✔
581
                    .elements
582
                    .iter()
583
                    .map(|pattern| PluralElements::new(pattern.to_runtime_pattern()))
5✔
584
                    .collect();
585
                let unpacked = UnpackedPatterns {
1✔
586
                    has_explicit_medium: human.has_explicit_medium,
1✔
587
                    has_explicit_short: human.has_explicit_short,
1✔
588
                    variant_indices,
1✔
589
                    elements,
590
                };
591
                Ok(unpacked.build())
1✔
592
            } else {
1✔
593
                let machine = <PackedPatternsMachine>::deserialize(deserializer)?;
2,629✔
594
                Ok(Self {
2,629✔
595
                    header: machine.header,
2,629✔
596
                    elements: machine.elements.as_varzerovec(),
2,629✔
597
                })
598
            }
599
        }
2,630✔
600
    }
601

602
    #[cfg(feature = "datagen")]
603
    impl serde::Serialize for PackedPatternsV1<'_> {
604
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2,676✔
605
        where
606
            S: serde::Serializer,
607
        {
608
            use serde::ser::Error as _;
609
            if serializer.is_human_readable() {
2,680✔
610
                let unpacked = UnpackedPatterns::from_packed(self);
4✔
611
                let mut human = PackedPatternsHuman {
46✔
612
                    has_explicit_medium: unpacked.has_explicit_medium,
46✔
613
                    has_explicit_short: unpacked.has_explicit_short,
46✔
614
                    ..Default::default()
46✔
615
                };
616
                match unpacked.variant_indices {
46✔
617
                    VariantIndices::OnePatternPerVariant => {
×
618
                        human.has_one_pattern_per_variant = true;
×
619
                    }
620
                    VariantIndices::IndicesPerVariant(chunks) => {
46✔
621
                        let chunks = VariantPatternIndex::to_chunks_u32(chunks);
46✔
622
                        human.variant_pattern_indices = Some(chunks);
4✔
623
                    }
4✔
624
                }
625
                human.elements = Vec::with_capacity(unpacked.elements.len());
4✔
626
                for pattern_elements in unpacked.elements.into_iter() {
4✔
627
                    let pattern = pattern_elements
21✔
628
                        .try_into_other()
629
                        .ok_or_else(|| S::Error::custom("cannot yet serialize plural patterns"))?;
×
630
                    human.elements.push(reference::Pattern::from(&pattern));
21✔
631
                }
25✔
632
                human.serialize(serializer)
4✔
633
            } else {
4✔
634
                let machine = PackedPatternsMachine {
2,630✔
635
                    header: self.header,
2,630✔
636
                    elements: &self.elements,
2,630✔
637
                };
638
                machine.serialize(serializer)
2,630✔
639
            }
640
        }
2,634✔
641
    }
642
}
643

644
#[cfg(test)]
645
pub mod tests {
646
    use super::*;
647
    use crate::provider::pattern::reference;
648

649
    const PATTERN_STRS: &[&str] = &[
650
        "M/d/y",
651
        "HH:mm",
652
        "MMM d y G",
653
        "E",
654
        "E MMM d",
655
        "dd.MM.yy",
656
        "h a",
657
        "hh:mm:ss B",
658
        "y MMMM",
659
    ];
660

661
    fn get_patterns() -> Vec<Pattern<'static>> {
2✔
662
        PATTERN_STRS
2✔
663
            .iter()
664
            .map(|s| {
18✔
665
                s.parse::<reference::Pattern>()
18✔
666
                    .unwrap()
667
                    .to_runtime_pattern()
668
            })
18✔
669
            .collect::<Vec<_>>()
670
    }
2✔
671

672
    #[test]
673
    fn test_basic() {
2✔
674
        let patterns = get_patterns();
1✔
675
        let mut it = patterns.iter().cloned();
1✔
676
        let lms0 = LengthPluralElements {
1✔
677
            long: PluralElements::new(it.next().unwrap()),
1✔
678
            medium: PluralElements::new(it.next().unwrap()),
1✔
679
            short: PluralElements::new(it.next().unwrap()),
1✔
680
        };
×
681
        let lms1 = LengthPluralElements {
1✔
682
            long: PluralElements::new(it.next().unwrap()),
1✔
683
            medium: PluralElements::new(it.next().unwrap()),
1✔
684
            short: PluralElements::new(it.next().unwrap()),
1✔
685
        };
×
686
        let lms2 = LengthPluralElements {
1✔
687
            long: PluralElements::new(it.next().unwrap()),
1✔
688
            medium: PluralElements::new(it.next().unwrap()),
1✔
689
            short: PluralElements::new(it.next().unwrap()),
1✔
690
        };
×
691
        let lms0a = LengthPluralElements {
1✔
692
            long: PluralElements::new(patterns[0].clone()),
1✔
693
            medium: PluralElements::new(patterns[0].clone()),
1✔
694
            short: PluralElements::new(patterns[1].clone()),
1✔
695
        };
×
696
        let lms1a = LengthPluralElements {
1✔
697
            long: PluralElements::new(patterns[3].clone()),
1✔
698
            medium: PluralElements::new(patterns[4].clone()),
1✔
699
            short: PluralElements::new(patterns[4].clone()),
1✔
700
        };
×
701

702
        {
703
            // Q = 1
704
            let builder = PackedPatternsBuilder {
1✔
705
                standard: lms0.clone(),
1✔
706
                variant0: Some(lms1.clone()),
1✔
707
                variant1: Some(lms2.clone()),
1✔
708
            };
×
709
            let packed = builder.clone().build();
1✔
710
            assert_eq!(packed.header, 7);
1✔
711
            assert_eq!(packed.elements.len(), 9);
1✔
712
            for (pattern_elements, expected) in packed.elements.iter().zip(patterns.iter()) {
10✔
713
                assert_eq!(pattern_elements.get_default().1, &expected.items);
9✔
714
            }
715
            let recovered_builder = packed.to_builder();
1✔
716
            assert_eq!(builder, recovered_builder);
1✔
717
        }
1✔
718
        {
719
            // Q = 0
720
            let builder = PackedPatternsBuilder {
1✔
721
                standard: lms0.clone(),
1✔
722
                variant0: Some(lms0.clone()),
1✔
723
                variant1: Some(lms2.clone()),
1✔
724
            };
×
725
            let packed = builder.clone().build();
1✔
726
            assert_eq!(packed.header, 0x1AC003);
1✔
727
            assert_eq!(packed.elements.len(), 6);
1✔
728
            let recovered_builder = packed.to_builder();
1✔
729
            assert_ne!(builder, recovered_builder);
1✔
730
            let mut builder = builder;
1✔
731
            builder.simplify();
1✔
732
            assert_eq!(builder, recovered_builder);
1✔
733
        }
1✔
734
        {
735
            // No variants
736
            let builder = PackedPatternsBuilder {
1✔
737
                standard: lms0.clone(),
1✔
738
                variant0: None,
1✔
739
                variant1: None,
1✔
740
            };
741
            let packed = builder.clone().build();
1✔
742
            assert_eq!(packed.header, 3);
1✔
743
            assert_eq!(packed.elements.len(), 3);
1✔
744
            let recovered_builder = packed.to_builder();
1✔
745
            assert_eq!(builder, recovered_builder);
1✔
746
        }
1✔
747
        {
748
            // Some duplicate patterns and inheritance
749
            let builder = PackedPatternsBuilder {
1✔
750
                standard: lms0a.clone(),
1✔
751
                variant0: Some(lms0.clone()),
1✔
752
                variant1: Some(lms1.clone()),
1✔
753
            };
×
754
            let packed = builder.clone().build();
1✔
755
            assert_eq!(packed.header, 0x1AC682);
1✔
756
            assert_eq!(packed.elements.len(), 6);
1✔
757
            let recovered_builder = packed.to_builder();
1✔
758
            assert_eq!(builder, recovered_builder);
1✔
759
        }
1✔
760
        {
761
            // Q = 1 with 8 patterns (min for Q = 1)
762
            let builder = PackedPatternsBuilder {
1✔
763
                standard: lms0a.clone(),
1✔
764
                variant0: Some(lms1.clone()),
1✔
765
                variant1: Some(lms2.clone()),
1✔
766
            };
×
767
            let packed = builder.clone().build();
1✔
768
            assert_eq!(packed.header, 6);
1✔
769
            assert_eq!(packed.elements.len(), 8);
1✔
770
            let recovered_builder = packed.to_builder();
1✔
771
            assert_eq!(builder, recovered_builder);
1✔
772
        }
1✔
773
        {
774
            // Q = 0 with 7 patterns (max for Q = 0)
775
            let builder = PackedPatternsBuilder {
1✔
776
                standard: lms1a.clone(),
1✔
777
                variant0: Some(lms0a.clone()),
1✔
778
                variant1: Some(lms2.clone()),
1✔
779
            };
×
780
            let packed = builder.clone().build();
1✔
781
            assert_eq!(packed.header, 0x1F58D9);
1✔
782
            assert_eq!(packed.elements.len(), 7);
1✔
783
            let recovered_builder = packed.to_builder();
1✔
784
            assert_eq!(builder, recovered_builder);
1✔
785
        }
1✔
786
    }
2✔
787

788
    #[cfg(feature = "datagen")]
789
    #[test]
790
    fn test_serde() {
2✔
791
        let patterns = get_patterns();
1✔
792
        let lms0a = LengthPluralElements {
1✔
793
            long: PluralElements::new(patterns[0].clone()),
1✔
794
            medium: PluralElements::new(patterns[0].clone()),
1✔
795
            short: PluralElements::new(patterns[1].clone()),
1✔
796
        };
×
797
        let lms1 = LengthPluralElements {
1✔
798
            long: PluralElements::new(patterns[3].clone()),
1✔
799
            medium: PluralElements::new(patterns[4].clone()),
1✔
800
            short: PluralElements::new(patterns[5].clone()),
1✔
801
        };
×
802

803
        let builder = PackedPatternsBuilder {
1✔
804
            standard: lms0a,
1✔
805
            variant0: Some(lms1),
1✔
806
            variant1: None,
1✔
807
        };
808
        let packed = builder.clone().build();
1✔
809

810
        let bincode_bytes = bincode::serialize(&packed).unwrap();
1✔
811
        assert_eq!(
1✔
812
            bincode_bytes.as_slice(),
1✔
813
            &[
1✔
814
                26, 11, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 5, 0, 16, 0, 26, 0, 30, 0, 46, 0, 0, 128,
815
                32, 1, 0, 0, 47, 128, 64, 1, 0, 0, 47, 128, 16, 1, 2, 128, 114, 2, 0, 0, 58, 128,
816
                128, 2, 0, 128, 80, 1, 0, 128, 80, 1, 0, 0, 32, 128, 32, 3, 0, 0, 32, 128, 64, 1,
817
                0, 128, 64, 2, 0, 0, 46, 128, 32, 2, 0, 0, 46, 128, 16, 2
818
            ][..]
1✔
819
        );
820
        let bincode_recovered = bincode::deserialize::<PackedPatternsV1>(&bincode_bytes).unwrap();
1✔
821
        assert_eq!(builder, bincode_recovered.to_builder());
2✔
822

823
        let json_str = serde_json::to_string(&packed).unwrap();
1✔
824
        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\"]}");
1✔
825
        let json_recovered = serde_json::from_str::<PackedPatternsV1>(&json_str).unwrap();
1✔
826
        assert_eq!(builder, json_recovered.to_builder());
2✔
827
    }
2✔
828
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc