Coverage Report

Created: 2025-12-10 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/icu_plurals-1.5.0/src/lib.rs
Line
Count
Source
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
//! Determine the plural category appropriate for a given number in a given language.
6
//!
7
//! This module is published as its own crate ([`icu_plurals`](https://docs.rs/icu_plurals/latest/icu_plurals/))
8
//! and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project.
9
//!
10
//! For example in English, when constructing a message
11
//! such as `{ num } items`, the user has to prepare
12
//! two variants of the message:
13
//!
14
//! * `1 item`
15
//! * `0 items`, `2 items`, `5 items`, `0.5 items` etc.
16
//!
17
//! The former variant is used when the placeholder variable has value `1`,
18
//! while the latter is used for all other values of the variable.
19
//!
20
//! Unicode defines [Language Plural Rules] as a mechanism to codify those
21
//! variants and provides data and algorithms to calculate
22
//! appropriate [`PluralCategory`].
23
//!
24
//! # Examples
25
//!
26
//! ```
27
//! use icu::locid::locale;
28
//! use icu::plurals::{PluralCategory, PluralRuleType, PluralRules};
29
//!
30
//! let pr =
31
//!     PluralRules::try_new(&locale!("en").into(), PluralRuleType::Cardinal)
32
//!         .expect("locale should be present");
33
//!
34
//! assert_eq!(pr.category_for(5_usize), PluralCategory::Other);
35
//! ```
36
//!
37
//! ## Plural Rules
38
//!
39
//! The crate provides the main struct [`PluralRules`] which handles selection
40
//! of the correct [`PluralCategory`] for a given language and [`PluralRuleType`].
41
//!
42
//! ## Plural Category
43
//!
44
//! Every number in every language belongs to a certain [`PluralCategory`].
45
//! For example, the Polish language uses four:
46
//!
47
//! * [`One`](PluralCategory::One): `1 miesiąc`
48
//! * [`Few`](PluralCategory::Few): `2 miesiące`
49
//! * [`Many`](PluralCategory::Many): `5 miesięcy`
50
//! * [`Other`](PluralCategory::Other): `1.5 miesiąca`
51
//!
52
//! ## `PluralRuleType`
53
//!
54
//! Plural rules depend on the use case. This crate supports two types of plural rules:
55
//!
56
//! * [`Cardinal`](PluralRuleType::Cardinal): `3 doors`, `1 month`, `10 dollars`
57
//! * [`Ordinal`](PluralRuleType::Ordinal): `1st place`, `10th day`, `11th floor`
58
//!
59
//! [Language Plural Rules]: https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules
60
61
// https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations
62
#![cfg_attr(not(any(test, feature = "std")), no_std)]
63
#![cfg_attr(
64
    not(test),
65
    deny(
66
        clippy::indexing_slicing,
67
        clippy::unwrap_used,
68
        clippy::expect_used,
69
        clippy::panic,
70
        clippy::exhaustive_structs,
71
        clippy::exhaustive_enums,
72
        missing_debug_implementations,
73
    )
74
)]
75
#![warn(missing_docs)]
76
77
extern crate alloc;
78
79
mod error;
80
mod operands;
81
pub mod provider;
82
pub mod rules;
83
84
use core::cmp::{Ord, PartialOrd};
85
pub use error::PluralsError;
86
use icu_provider::prelude::*;
87
pub use operands::PluralOperands;
88
use provider::CardinalV1Marker;
89
use provider::ErasedPluralRulesV1Marker;
90
use provider::OrdinalV1Marker;
91
use rules::runtime::test_rule;
92
93
#[cfg(feature = "experimental")]
94
use provider::PluralRangesV1Marker;
95
#[cfg(feature = "experimental")]
96
use provider::UnvalidatedPluralRange;
97
98
#[doc(no_inline)]
99
pub use PluralsError as Error;
100
101
/// A type of a plural rule which can be associated with the [`PluralRules`] struct.
102
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
103
#[non_exhaustive]
104
pub enum PluralRuleType {
105
    /// Cardinal plural forms express quantities of units such as time, currency or distance,
106
    /// used in conjunction with a number expressed in decimal digits (i.e. "2", not "two").
107
    ///
108
    /// For example, English has two forms for cardinals:
109
    ///
110
    /// * [`One`]: `1 day`
111
    /// * [`Other`]: `0 days`, `2 days`, `10 days`, `0.3 days`
112
    ///
113
    /// [`One`]: PluralCategory::One
114
    /// [`Other`]: PluralCategory::Other
115
    Cardinal,
116
    /// Ordinal plural forms denote the order of items in a set and are always integers.
117
    ///
118
    /// For example, English has four forms for ordinals:
119
    ///
120
    /// * [`One`]: `1st floor`, `21st floor`, `101st floor`
121
    /// * [`Two`]: `2nd floor`, `22nd floor`, `102nd floor`
122
    /// * [`Few`]: `3rd floor`, `23rd floor`, `103rd floor`
123
    /// * [`Other`]: `4th floor`, `11th floor`, `96th floor`
124
    ///
125
    /// [`One`]: PluralCategory::One
126
    /// [`Two`]: PluralCategory::Two
127
    /// [`Few`]: PluralCategory::Few
128
    /// [`Other`]: PluralCategory::Other
129
    Ordinal,
130
}
131
132
/// The plural categories are used to format messages with numeric placeholders, expressed as decimal numbers.
133
///
134
/// The fundamental rule for determining plural categories is the existence of minimal pairs: whenever two different
135
/// numbers may require different versions of the same message, then the numbers have different plural categories.
136
///
137
/// All languages supported by `ICU4X` can match any number to one of the categories.
138
///
139
/// # Examples
140
///
141
/// ```
142
/// use icu::locid::locale;
143
/// use icu::plurals::{PluralCategory, PluralRuleType, PluralRules};
144
///
145
/// let pr =
146
///     PluralRules::try_new(&locale!("en").into(), PluralRuleType::Cardinal)
147
///         .expect("locale should be present");
148
///
149
/// assert_eq!(pr.category_for(5_usize), PluralCategory::Other);
150
/// ```
151
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Ord, PartialOrd)]
152
#[cfg_attr(feature = "datagen", derive(serde::Serialize))]
153
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
154
#[repr(u8)]
155
#[zerovec::make_ule(PluralCategoryULE)]
156
#[allow(clippy::exhaustive_enums)] // this type is mostly stable. new categories may potentially be added in the future,
157
                                   // but at a cadence slower than the ICU4X release cycle
158
pub enum PluralCategory {
159
    /// CLDR "zero" plural category. Used in Arabic and Latvian, among others.
160
    ///
161
    /// Examples of numbers having this category:
162
    ///
163
    /// - 0 in Arabic (ar), Latvian (lv)
164
    /// - 10~20, 30, 40, 50, ... in Latvian (lv)
165
    Zero = 0,
166
    /// CLDR "one" plural category. Signifies the singular form in many languages.
167
    ///
168
    /// Examples of numbers having this category:
169
    ///
170
    /// - 0 in French (fr), Portuguese (pt), ...
171
    /// - 1 in English (en) and most other languages
172
    /// - 2.1 in Filipino (fil), Croatian (hr), Latvian (lv), Serbian (sr)
173
    /// - 2, 3, 5, 7, 8, ... in Filipino (fil)
174
    One = 1,
175
    /// CLDR "two" plural category. Used in Arabic, Hebrew, and Slovenian, among others.
176
    ///
177
    /// Examples of numbers having this category:
178
    ///
179
    /// - 2 in Arabic (ar), Hebrew (iw), Slovenian (sl)
180
    /// - 2.0 in Arabic (ar)
181
    Two = 2,
182
    /// CLDR "few" plural category. Used in Romanian, Polish, Russian, and others.
183
    ///
184
    /// Examples of numbers having this category:
185
    ///
186
    /// - 0 in Romanian (ro)
187
    /// - 1.2 in Croatian (hr), Romanian (ro), Slovenian (sl), Serbian (sr)
188
    /// - 2 in Polish (pl), Russian (ru), Czech (cs), ...
189
    /// - 5 in Arabic (ar), Lithuanian (lt), Romanian (ro)
190
    Few = 3,
191
    /// CLDR "many" plural category. Used in Polish, Russian, Ukrainian, and others.
192
    ///
193
    /// Examples of numbers having this category:
194
    ///
195
    /// - 0 in Polish (pl)
196
    /// - 1.0 in Czech (cs), Slovak (sk)
197
    /// - 1.1 in Czech (cs), Lithuanian (lt), Slovak (sk)
198
    /// - 15 in Arabic (ar), Polish (pl), Russian (ru), Ukrainian (uk)
199
    Many = 4,
200
    /// CLDR "other" plural category, used as a catch-all. Each language supports it, and it
201
    /// is also used as a fail safe result for in case no better match can be identified.
202
    ///
203
    /// In some languages, such as Japanese, Chinese, Korean, and Thai, this is the only
204
    /// plural category.
205
    ///
206
    /// Examples of numbers having this category:
207
    ///
208
    /// - 0 in English (en), German (de), Spanish (es), ...
209
    /// - 1 in Japanese (ja), Korean (ko), Chinese (zh), Thai (th), ...
210
    /// - 2 in English (en), German (de), Spanish (es), ...
211
    Other = 5,
212
}
213
214
impl PluralCategory {
215
    /// Returns an ordered iterator over variants of [`Plural Categories`].
216
    ///
217
    /// Categories are returned in alphabetical order.
218
    ///
219
    /// # Examples
220
    ///
221
    /// ```
222
    /// use icu::plurals::PluralCategory;
223
    ///
224
    /// let mut categories = PluralCategory::all();
225
    ///
226
    /// assert_eq!(categories.next(), Some(PluralCategory::Few));
227
    /// assert_eq!(categories.next(), Some(PluralCategory::Many));
228
    /// assert_eq!(categories.next(), Some(PluralCategory::One));
229
    /// assert_eq!(categories.next(), Some(PluralCategory::Other));
230
    /// assert_eq!(categories.next(), Some(PluralCategory::Two));
231
    /// assert_eq!(categories.next(), Some(PluralCategory::Zero));
232
    /// assert_eq!(categories.next(), None);
233
    /// ```
234
    ///
235
    /// [`Plural Categories`]: PluralCategory
236
0
    pub fn all() -> impl ExactSizeIterator<Item = Self> {
237
0
        [
238
0
            Self::Few,
239
0
            Self::Many,
240
0
            Self::One,
241
0
            Self::Other,
242
0
            Self::Two,
243
0
            Self::Zero,
244
0
        ]
245
0
        .iter()
246
0
        .copied()
247
0
    }
248
249
    /// Returns the PluralCategory corresponding to given TR35 string.
250
0
    pub fn get_for_cldr_string(category: &str) -> Option<PluralCategory> {
251
0
        Self::get_for_cldr_bytes(category.as_bytes())
252
0
    }
253
    /// Returns the PluralCategory corresponding to given TR35 string as bytes
254
0
    pub fn get_for_cldr_bytes(category: &[u8]) -> Option<PluralCategory> {
255
0
        match category {
256
0
            b"zero" => Some(PluralCategory::Zero),
257
0
            b"one" => Some(PluralCategory::One),
258
0
            b"two" => Some(PluralCategory::Two),
259
0
            b"few" => Some(PluralCategory::Few),
260
0
            b"many" => Some(PluralCategory::Many),
261
0
            b"other" => Some(PluralCategory::Other),
262
0
            _ => None,
263
        }
264
0
    }
265
}
266
267
/// A struct which provides an ability to retrieve an appropriate
268
/// [`Plural Category`] for a given number.
269
///
270
/// # Examples
271
///
272
/// ```
273
/// use icu::locid::locale;
274
/// use icu::plurals::{PluralCategory, PluralRuleType, PluralRules};
275
///
276
/// let pr =
277
///     PluralRules::try_new(&locale!("en").into(), PluralRuleType::Cardinal)
278
///         .expect("locale should be present");
279
///
280
/// assert_eq!(pr.category_for(5_usize), PluralCategory::Other);
281
/// ```
282
///
283
/// [`ICU4X`]: ../icu/index.html
284
/// [`Plural Type`]: PluralRuleType
285
/// [`Plural Category`]: PluralCategory
286
#[derive(Debug)]
287
pub struct PluralRules(DataPayload<ErasedPluralRulesV1Marker>);
288
289
impl AsRef<PluralRules> for PluralRules {
290
0
    fn as_ref(&self) -> &PluralRules {
291
0
        self
292
0
    }
293
}
294
295
impl PluralRules {
296
    icu_provider::gen_any_buffer_data_constructors!(
297
        locale: include,
298
        rule_type: PluralRuleType,
299
        error: PluralsError,
300
        /// Constructs a new `PluralRules` for a given locale and type using compiled data.
301
        ///
302
        /// ✨ *Enabled with the `compiled_data` Cargo feature.*
303
        ///
304
        /// [📚 Help choosing a constructor](icu_provider::constructors)
305
        ///
306
        /// # Examples
307
        ///
308
        /// ```
309
        /// use icu::locid::locale;
310
        /// use icu::plurals::{PluralRuleType, PluralRules};
311
        ///
312
        /// let _ = PluralRules::try_new(
313
        ///     &locale!("en").into(),
314
        ///     PluralRuleType::Cardinal,
315
        /// ).expect("locale should be present");
316
        /// ```
317
        ///
318
        /// [`type`]: PluralRuleType
319
        /// [`data provider`]: icu_provider
320
    );
321
322
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
323
0
    pub fn try_new_unstable(
324
0
        provider: &(impl DataProvider<CardinalV1Marker> + DataProvider<OrdinalV1Marker> + ?Sized),
325
0
        locale: &DataLocale,
326
0
        rule_type: PluralRuleType,
327
0
    ) -> Result<Self, PluralsError> {
328
0
        match rule_type {
329
0
            PluralRuleType::Cardinal => Self::try_new_cardinal_unstable(provider, locale),
330
0
            PluralRuleType::Ordinal => Self::try_new_ordinal_unstable(provider, locale),
331
        }
332
0
    }
333
334
    icu_provider::gen_any_buffer_data_constructors!(
335
        locale: include,
336
        options: skip,
337
        error: PluralsError,
338
        /// Constructs a new `PluralRules` for a given locale for cardinal numbers using compiled data.
339
        ///
340
        /// Cardinal plural forms express quantities of units such as time, currency or distance,
341
        /// used in conjunction with a number expressed in decimal digits (i.e. "2", not "two").
342
        ///
343
        /// For example, English has two forms for cardinals:
344
        ///
345
        /// * [`One`]: `1 day`
346
        /// * [`Other`]: `0 days`, `2 days`, `10 days`, `0.3 days`
347
        ///
348
        /// ✨ *Enabled with the `compiled_data` Cargo feature.*
349
        ///
350
        /// [📚 Help choosing a constructor](icu_provider::constructors)
351
        ///
352
        /// # Examples
353
        ///
354
        /// ```
355
        /// use icu::locid::locale;
356
        /// use icu::plurals::{PluralCategory, PluralRules};
357
        ///
358
        /// let rules = PluralRules::try_new_cardinal(&locale!("ru").into()).expect("locale should be present");
359
        ///
360
        /// assert_eq!(rules.category_for(2_usize), PluralCategory::Few);
361
        /// ```
362
        ///
363
        /// [`One`]: PluralCategory::One
364
        /// [`Other`]: PluralCategory::Other
365
        functions: [
366
            try_new_cardinal,
367
            try_new_cardinal_with_any_provider,
368
            try_new_cardinal_with_buffer_provider,
369
            try_new_cardinal_unstable,
370
            Self,
371
        ]
372
    );
373
374
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_cardinal)]
375
0
    pub fn try_new_cardinal_unstable(
376
0
        provider: &(impl DataProvider<CardinalV1Marker> + ?Sized),
377
0
        locale: &DataLocale,
378
0
    ) -> Result<Self, PluralsError> {
379
        Ok(Self(
380
0
            provider
381
0
                .load(DataRequest {
382
0
                    locale,
383
0
                    metadata: Default::default(),
384
0
                })?
385
0
                .take_payload()?
386
0
                .cast(),
387
        ))
388
0
    }
Unexecuted instantiation: <icu_plurals::PluralRules>::try_new_cardinal_unstable::<icu_provider::any::DowncastingAnyProvider<icu_provider_adapters::empty::EmptyDataProvider>>
Unexecuted instantiation: <icu_plurals::PluralRules>::try_new_cardinal_unstable::<icu_plurals::provider::Baked>
389
390
    icu_provider::gen_any_buffer_data_constructors!(
391
        locale: include,
392
        options: skip,
393
        error: PluralsError,
394
        /// Constructs a new `PluralRules` for a given locale for ordinal numbers using compiled data.
395
        ///
396
        /// Ordinal plural forms denote the order of items in a set and are always integers.
397
        ///
398
        /// For example, English has four forms for ordinals:
399
        ///
400
        /// * [`One`]: `1st floor`, `21st floor`, `101st floor`
401
        /// * [`Two`]: `2nd floor`, `22nd floor`, `102nd floor`
402
        /// * [`Few`]: `3rd floor`, `23rd floor`, `103rd floor`
403
        /// * [`Other`]: `4th floor`, `11th floor`, `96th floor`
404
        ///
405
        /// ✨ *Enabled with the `compiled_data` Cargo feature.*
406
        ///
407
        /// [📚 Help choosing a constructor](icu_provider::constructors)
408
        ///
409
        /// # Examples
410
        ///
411
        /// ```
412
        /// use icu::locid::locale;
413
        /// use icu::plurals::{PluralCategory, PluralRules};
414
        ///
415
        /// let rules = PluralRules::try_new_ordinal(
416
        ///     &locale!("ru").into(),
417
        /// )
418
        /// .expect("locale should be present");
419
        ///
420
        /// assert_eq!(rules.category_for(2_usize), PluralCategory::Other);
421
        /// ```
422
        ///
423
        /// [`One`]: PluralCategory::One
424
        /// [`Two`]: PluralCategory::Two
425
        /// [`Few`]: PluralCategory::Few
426
        /// [`Other`]: PluralCategory::Other
427
        functions: [
428
            try_new_ordinal,
429
            try_new_ordinal_with_any_provider,
430
            try_new_ordinal_with_buffer_provider,
431
            try_new_ordinal_unstable,
432
            Self,
433
        ]
434
    );
435
436
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_ordinal)]
437
0
    pub fn try_new_ordinal_unstable(
438
0
        provider: &(impl DataProvider<OrdinalV1Marker> + ?Sized),
439
0
        locale: &DataLocale,
440
0
    ) -> Result<Self, PluralsError> {
441
        Ok(Self(
442
0
            provider
443
0
                .load(DataRequest {
444
0
                    locale,
445
0
                    metadata: Default::default(),
446
0
                })?
447
0
                .take_payload()?
448
0
                .cast(),
449
        ))
450
0
    }
Unexecuted instantiation: <icu_plurals::PluralRules>::try_new_ordinal_unstable::<icu_provider::any::DowncastingAnyProvider<icu_provider_adapters::empty::EmptyDataProvider>>
Unexecuted instantiation: <icu_plurals::PluralRules>::try_new_ordinal_unstable::<icu_plurals::provider::Baked>
451
452
    /// Returns the [`Plural Category`] appropriate for the given number.
453
    ///
454
    /// # Examples
455
    ///
456
    /// ```
457
    /// use icu::locid::locale;
458
    /// use icu::plurals::{PluralCategory, PluralRuleType, PluralRules};
459
    ///
460
    /// let pr =
461
    ///     PluralRules::try_new(&locale!("en").into(), PluralRuleType::Cardinal)
462
    ///         .expect("locale should be present");
463
    ///
464
    /// match pr.category_for(1_usize) {
465
    ///     PluralCategory::One => "One item",
466
    ///     PluralCategory::Other => "Many items",
467
    ///     _ => unreachable!(),
468
    /// };
469
    /// ```
470
    ///
471
    /// The method accepts any input that can be calculated into [`Plural Operands`].
472
    /// All unsigned primitive number types can infallibly be converted so they can be
473
    /// used as an input.
474
    ///
475
    /// For signed numbers and strings, [`Plural Operands`] implement [`TryFrom`]
476
    /// and [`FromStr`](std::str::FromStr), which should be used before passing the result to
477
    /// [`category_for()`](PluralRules::category_for()).
478
    ///
479
    /// # Examples
480
    ///
481
    /// ```
482
    /// use icu::locid::locale;
483
    /// use icu::plurals::{PluralCategory, PluralOperands};
484
    /// use icu::plurals::{PluralRuleType, PluralRules};
485
    /// #
486
    /// # let pr = PluralRules::try_new(&locale!("en").into(), PluralRuleType::Cardinal)
487
    /// #     .expect("locale should be present");
488
    ///
489
    /// let operands = PluralOperands::try_from(-5).expect("Failed to parse to operands.");
490
    /// let operands2: PluralOperands = "5.10".parse().expect("Failed to parse to operands.");
491
    ///
492
    /// assert_eq!(pr.category_for(operands), PluralCategory::Other);
493
    /// assert_eq!(pr.category_for(operands2), PluralCategory::Other);
494
    /// ```
495
    ///
496
    /// [`Plural Category`]: PluralCategory
497
    /// [`Plural Operands`]: operands::PluralOperands
498
0
    pub fn category_for<I: Into<PluralOperands>>(&self, input: I) -> PluralCategory {
499
0
        let rules = self.0.get();
500
0
        let input = input.into();
501
502
        macro_rules! test_rule {
503
            ($rule:ident, $cat:ident) => {
504
                rules
505
                    .$rule
506
                    .as_ref()
507
0
                    .and_then(|r| test_rule(r, &input).then(|| PluralCategory::$cat))
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<icu_plurals::operands::PluralOperands>::{closure#4}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<icu_plurals::operands::PluralOperands>::{closure#0}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<icu_plurals::operands::PluralOperands>::{closure#2}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<icu_plurals::operands::PluralOperands>::{closure#3}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<icu_plurals::operands::PluralOperands>::{closure#1}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<u32>::{closure#4}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<u32>::{closure#0}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<u32>::{closure#2}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<u32>::{closure#3}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<u32>::{closure#1}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<_>::{closure#4}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<_>::{closure#0}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<_>::{closure#2}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<_>::{closure#3}::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<_>::{closure#1}::{closure#0}
508
            };
509
        }
510
511
0
        test_rule!(zero, Zero)
512
0
            .or_else(|| test_rule!(one, One))
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<icu_plurals::operands::PluralOperands>::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<u32>::{closure#0}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<_>::{closure#0}
513
0
            .or_else(|| test_rule!(two, Two))
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<icu_plurals::operands::PluralOperands>::{closure#1}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<u32>::{closure#1}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<_>::{closure#1}
514
0
            .or_else(|| test_rule!(few, Few))
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<icu_plurals::operands::PluralOperands>::{closure#2}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<u32>::{closure#2}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<_>::{closure#2}
515
0
            .or_else(|| test_rule!(many, Many))
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<icu_plurals::operands::PluralOperands>::{closure#3}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<u32>::{closure#3}
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<_>::{closure#3}
516
0
            .unwrap_or(PluralCategory::Other)
517
0
    }
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<icu_plurals::operands::PluralOperands>
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<u32>
Unexecuted instantiation: <icu_plurals::PluralRules>::category_for::<_>
518
519
    /// Returns all [`Plural Categories`] appropriate for a [`PluralRules`] object
520
    /// based on the [`LanguageIdentifier`](icu::locid::{LanguageIdentifier}) and [`PluralRuleType`].
521
    ///
522
    /// The [`Plural Categories`] are returned in UTS 35 sorted order.
523
    ///
524
    /// The category [`PluralCategory::Other`] is always included.
525
    ///
526
    /// # Examples
527
    ///
528
    /// ```
529
    /// use icu::locid::locale;
530
    /// use icu::plurals::{PluralCategory, PluralRuleType, PluralRules};
531
    ///
532
    /// let pr =
533
    ///     PluralRules::try_new(&locale!("fr").into(), PluralRuleType::Cardinal)
534
    ///         .expect("locale should be present");
535
    ///
536
    /// let mut categories = pr.categories();
537
    /// assert_eq!(categories.next(), Some(PluralCategory::One));
538
    /// assert_eq!(categories.next(), Some(PluralCategory::Many));
539
    /// assert_eq!(categories.next(), Some(PluralCategory::Other));
540
    /// assert_eq!(categories.next(), None);
541
    /// ```
542
    ///
543
    /// [`Plural Categories`]: PluralCategory
544
0
    pub fn categories(&self) -> impl Iterator<Item = PluralCategory> + '_ {
545
0
        let rules = self.0.get();
546
547
        macro_rules! test_rule {
548
            ($rule:ident, $cat:ident) => {
549
                rules
550
                    .$rule
551
                    .as_ref()
552
                    .map(|_| PluralCategory::$cat)
553
                    .into_iter()
554
            };
555
        }
556
557
0
        test_rule!(zero, Zero)
558
0
            .chain(test_rule!(one, One))
559
0
            .chain(test_rule!(two, Two))
560
0
            .chain(test_rule!(few, Few))
561
0
            .chain(test_rule!(many, Many))
562
0
            .chain(Some(PluralCategory::Other))
563
0
    }
564
}
565
566
/// A [`PluralRules`] that also has the ability to retrieve an appropriate [`Plural Category`] for a
567
/// range.
568
///
569
/// ✨ *Enabled with the `experimental` Cargo feature.*
570
///
571
/// <div class="stab unstable">
572
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
573
/// including in SemVer minor releases. Use with caution.
574
/// <a href="https://github.com/unicode-org/icu4x/issues/4140">#4140</a>
575
/// </div>
576
///
577
/// # Examples
578
///
579
/// ```
580
/// use icu::locid::locale;
581
/// use icu::plurals::{PluralCategory, PluralOperands};
582
/// use icu::plurals::{PluralRuleType, PluralRulesWithRanges};
583
///
584
/// let ranges = PluralRulesWithRanges::try_new(
585
///     &locale!("ar").into(),
586
///     PluralRuleType::Cardinal,
587
/// )
588
/// .expect("locale should be present");
589
///
590
/// let operands = PluralOperands::from(1_usize);
591
/// let operands2: PluralOperands =
592
///     "2.0".parse().expect("parsing to operands should succeed");
593
///
594
/// assert_eq!(
595
///     ranges.category_for_range(operands, operands2),
596
///     PluralCategory::Other
597
/// );
598
/// ```
599
///
600
/// [`Plural Category`]: PluralCategory
601
#[cfg(feature = "experimental")]
602
#[derive(Debug)]
603
pub struct PluralRulesWithRanges<R> {
604
    rules: R,
605
    ranges: DataPayload<PluralRangesV1Marker>,
606
}
607
608
#[cfg(feature = "experimental")]
609
impl PluralRulesWithRanges<PluralRules> {
610
    icu_provider::gen_any_buffer_data_constructors!(
611
        locale: include,
612
        rule_type: PluralRuleType,
613
        error: PluralsError,
614
        /// Constructs a new `PluralRulesWithRanges` for a given locale using compiled data.
615
        ///
616
        /// ✨ *Enabled with the `compiled_data` Cargo feature.*
617
        ///
618
        /// [📚 Help choosing a constructor](icu_provider::constructors)
619
        ///
620
        /// # Examples
621
        ///
622
        /// ```
623
        /// use icu::locid::locale;
624
        /// use icu::plurals::{PluralRuleType, PluralRulesWithRanges};
625
        ///
626
        /// let _ = PluralRulesWithRanges::try_new(
627
        ///     &locale!("en").into(),
628
        ///     PluralRuleType::Cardinal,
629
        /// ).expect("locale should be present");
630
        /// ```
631
    );
632
633
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
634
    pub fn try_new_unstable(
635
        provider: &(impl DataProvider<PluralRangesV1Marker>
636
              + DataProvider<CardinalV1Marker>
637
              + DataProvider<OrdinalV1Marker>
638
              + ?Sized),
639
        locale: &DataLocale,
640
        rule_type: PluralRuleType,
641
    ) -> Result<Self, PluralsError> {
642
        match rule_type {
643
            PluralRuleType::Cardinal => Self::try_new_cardinal_unstable(provider, locale),
644
            PluralRuleType::Ordinal => Self::try_new_ordinal_unstable(provider, locale),
645
        }
646
    }
647
648
    icu_provider::gen_any_buffer_data_constructors!(
649
        locale: include,
650
        options: skip,
651
        error: PluralsError,
652
        /// Constructs a new `PluralRulesWithRanges` for a given locale for cardinal numbers using
653
        /// compiled data.
654
        ///
655
        /// See [`PluralRules::try_new_cardinal`] for more information.
656
        ///
657
        /// ✨ *Enabled with the `compiled_data` Cargo feature.*
658
        ///
659
        /// [📚 Help choosing a constructor](icu_provider::constructors)
660
        ///
661
        /// # Examples
662
        ///
663
        /// ```
664
        /// use icu::locid::locale;
665
        /// use icu::plurals::{PluralCategory, PluralRulesWithRanges};
666
        ///
667
        /// let rules = PluralRulesWithRanges::try_new_cardinal(&locale!("ru").into())
668
        ///     .expect("locale should be present");
669
        ///
670
        /// assert_eq!(rules.category_for_range(0_usize, 2_usize), PluralCategory::Few);
671
        /// ```
672
        functions: [
673
            try_new_cardinal,
674
            try_new_cardinal_with_any_provider,
675
            try_new_cardinal_with_buffer_provider,
676
            try_new_cardinal_unstable,
677
            Self,
678
        ]
679
    );
680
681
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_cardinal)]
682
    pub fn try_new_cardinal_unstable(
683
        provider: &(impl DataProvider<CardinalV1Marker> + DataProvider<PluralRangesV1Marker> + ?Sized),
684
        locale: &DataLocale,
685
    ) -> Result<Self, PluralsError> {
686
        let rules = PluralRules::try_new_cardinal_unstable(provider, locale)?;
687
688
        PluralRulesWithRanges::try_new_with_rules_unstable(provider, locale, rules)
689
    }
690
691
    icu_provider::gen_any_buffer_data_constructors!(
692
        locale: include,
693
        options: skip,
694
        error: PluralsError,
695
        /// Constructs a new `PluralRulesWithRanges` for a given locale for ordinal numbers using
696
        /// compiled data.
697
        ///
698
        /// See [`PluralRules::try_new_ordinal`] for more information.
699
        ///
700
        /// ✨ *Enabled with the `compiled_data` Cargo feature.*
701
        ///
702
        /// [📚 Help choosing a constructor](icu_provider::constructors)
703
        ///
704
        /// # Examples
705
        ///
706
        /// ```
707
        /// use icu::locid::locale;
708
        /// use icu::plurals::{PluralCategory, PluralRulesWithRanges};
709
        ///
710
        /// let rules = PluralRulesWithRanges::try_new_ordinal(
711
        ///     &locale!("ru").into(),
712
        /// )
713
        /// .expect("locale should be present");
714
        ///
715
        /// assert_eq!(rules.category_for_range(0_usize, 2_usize), PluralCategory::Other);
716
        /// ```
717
        functions: [
718
            try_new_ordinal,
719
            try_new_ordinal_with_any_provider,
720
            try_new_ordinal_with_buffer_provider,
721
            try_new_ordinal_unstable,
722
            Self,
723
        ]
724
    );
725
726
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_ordinal)]
727
    pub fn try_new_ordinal_unstable(
728
        provider: &(impl DataProvider<OrdinalV1Marker> + DataProvider<PluralRangesV1Marker> + ?Sized),
729
        locale: &DataLocale,
730
    ) -> Result<Self, PluralsError> {
731
        let rules = PluralRules::try_new_ordinal_unstable(provider, locale)?;
732
733
        PluralRulesWithRanges::try_new_with_rules_unstable(provider, locale, rules)
734
    }
735
}
736
737
#[cfg(feature = "experimental")]
738
impl<R> PluralRulesWithRanges<R>
739
where
740
    R: AsRef<PluralRules>,
741
{
742
    icu_provider::gen_any_buffer_data_constructors!(
743
        locale: include,
744
        rules: R,
745
        error: PluralsError,
746
        /// Constructs a new `PluralRulesWithRanges` for a given locale from an existing
747
        /// `PluralRules` (either owned or as a reference) and compiled data.
748
        ///
749
        /// # ⚠️ Warning
750
        ///
751
        /// The provided `locale` **MUST** be the same as the locale provided to the constructor
752
        /// of `rules`. Otherwise, [`Self::category_for_range`] will return incorrect results.
753
        ///
754
        /// ✨ *Enabled with the `compiled_data` Cargo feature.*
755
        ///
756
        /// [📚 Help choosing a constructor](icu_provider::constructors)
757
        ///
758
        /// # Examples
759
        ///
760
        /// ```
761
        /// use icu::locid::locale;
762
        /// use icu::plurals::{PluralRuleType, PluralRulesWithRanges, PluralRules};
763
        ///
764
        /// let rules = PluralRules::try_new(&locale!("en").into(), PluralRuleType::Cardinal)
765
        ///     .expect("locale should be present");
766
        ///
767
        /// let _ =
768
        ///     PluralRulesWithRanges::try_new_with_rules(&locale!("en").into(), rules)
769
        ///         .expect("locale should be present");
770
        /// ```
771
        functions: [
772
            try_new_with_rules,
773
            try_new_with_rules_with_any_provider,
774
            try_new_with_rules_with_buffer_provider,
775
            try_new_with_rules_unstable,
776
            Self,
777
        ]
778
    );
779
780
    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_with_rules)]
781
    pub fn try_new_with_rules_unstable(
782
        provider: &(impl DataProvider<PluralRangesV1Marker> + ?Sized),
783
        locale: &DataLocale,
784
        rules: R,
785
    ) -> Result<Self, PluralsError> {
786
        let ranges = provider
787
            .load(DataRequest {
788
                locale,
789
                metadata: Default::default(),
790
            })?
791
            .take_payload()?;
792
793
        Ok(Self { rules, ranges })
794
    }
795
796
    /// Gets a reference to the inner `PluralRules`.
797
    ///
798
    /// # Examples
799
    ///
800
    /// ```
801
    /// use icu::locid::locale;
802
    /// use icu::plurals::{PluralCategory, PluralRulesWithRanges};
803
    ///
804
    /// let ranges = PluralRulesWithRanges::try_new_cardinal(&locale!("en").into())
805
    ///     .expect("locale should be present");
806
    ///
807
    /// let rules = ranges.rules();
808
    ///
809
    /// assert_eq!(rules.category_for(1u8), PluralCategory::One);
810
    /// ```
811
    pub fn rules(&self) -> &PluralRules {
812
        self.rules.as_ref()
813
    }
814
815
    /// Returns the [`Plural Category`] appropriate for a range.
816
    ///
817
    /// Note that the returned category is correct only if the range fulfills the following requirements:
818
    /// - The start value is strictly less than the end value.
819
    /// - Both values are positive.
820
    ///
821
    /// # Examples
822
    ///
823
    /// ```
824
    /// use icu::locid::locale;
825
    /// use icu::plurals::{
826
    ///     PluralCategory, PluralOperands, PluralRuleType, PluralRulesWithRanges,
827
    /// };
828
    ///
829
    /// let ranges = PluralRulesWithRanges::try_new(
830
    ///     &locale!("ro").into(),
831
    ///     PluralRuleType::Cardinal,
832
    /// )
833
    /// .expect("locale should be present");
834
    /// let operands: PluralOperands =
835
    ///     "0.5".parse().expect("parsing to operands should succeed");
836
    /// let operands2 = PluralOperands::from(1_usize);
837
    ///
838
    /// assert_eq!(
839
    ///     ranges.category_for_range(operands, operands2),
840
    ///     PluralCategory::Few
841
    /// );
842
    /// ```
843
    ///
844
    /// [`Plural Category`]: PluralCategory
845
    pub fn category_for_range<S: Into<PluralOperands>, E: Into<PluralOperands>>(
846
        &self,
847
        start: S,
848
        end: E,
849
    ) -> PluralCategory {
850
        let rules = self.rules.as_ref();
851
        let start = rules.category_for(start);
852
        let end = rules.category_for(end);
853
854
        self.resolve_range(start, end)
855
    }
856
857
    /// Returns the [`Plural Category`] appropriate for a range from the categories of its endpoints.
858
    ///
859
    /// Note that the returned category is correct only if the original numeric range fulfills the
860
    /// following requirements:
861
    /// - The start value is strictly less than the end value.
862
    /// - Both values are positive.
863
    ///
864
    /// # Examples
865
    ///
866
    /// ```
867
    /// use icu::locid::locale;
868
    /// use icu::plurals::{PluralCategory, PluralRuleType, PluralRulesWithRanges};
869
    ///
870
    /// let ranges = PluralRulesWithRanges::try_new(
871
    ///     &locale!("sl").into(),
872
    ///     PluralRuleType::Ordinal,
873
    /// )
874
    /// .expect("locale should be present");
875
    ///
876
    /// assert_eq!(
877
    ///     ranges.resolve_range(PluralCategory::Other, PluralCategory::One),
878
    ///     PluralCategory::Few
879
    /// );
880
    /// ```
881
    ///
882
    /// [`Plural Category`]: PluralCategory
883
    pub fn resolve_range(&self, start: PluralCategory, end: PluralCategory) -> PluralCategory {
884
        self.ranges
885
            .get()
886
            .ranges
887
            .get_copied(&UnvalidatedPluralRange::from_range(
888
                start.into(),
889
                end.into(),
890
            ))
891
            .map(PluralCategory::from)
892
            .unwrap_or(end)
893
    }
894
}