Skip to content

Commit

Permalink
Add PluralElements type (#5475)
Browse files Browse the repository at this point in the history
As discussed in #5426
  • Loading branch information
robertbastian authored Sep 4, 2024
1 parent 5e89d20 commit aa15ec5
Show file tree
Hide file tree
Showing 876 changed files with 53,838 additions and 51,039 deletions.
6 changes: 3 additions & 3 deletions components/experimental/src/dimension/currency/long_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ impl<'l> Writeable for LongFormattedCurrency<'l> {
where
W: core::fmt::Write + ?Sized,
{
let plural_category = self.plural_rules.category_for(self.value);
let display_name = self.extended.display_names.get_str(plural_category);
let pattern = self.patterns.patterns.get_pattern(plural_category);
let operands = self.value.into();
let display_name = self.extended.display_names.get(operands, self.plural_rules);
let pattern = self.patterns.patterns.get(operands, self.plural_rules);
let formatted_value = self.fixed_decimal_formatter.format(self.value);
let interpolated = pattern.interpolate((formatted_value, display_name));
interpolated.write_to(sink)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
//!
//! Read more about data providers: [`icu_provider`]

use crate::relativetime::provider::PluralElements;
use icu_pattern::DoublePlaceholderPattern;
use crate::relativetime::provider::PluralPatterns;
use icu_pattern::DoublePlaceholder;
use icu_provider::prelude::*;

/// Currency Extended V1 data struct.
Expand All @@ -23,5 +23,5 @@ use icu_provider::prelude::*;
pub struct CurrencyPatternsDataV1<'data> {
/// Contains the unit patterns for a currency based on plural rules.
#[cfg_attr(feature = "serde", serde(borrow))]
pub patterns: PluralElements<'data, DoublePlaceholderPattern<str>>,
pub patterns: PluralPatterns<'data, DoublePlaceholder>,
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//!
//! Read more about data providers: [`icu_provider`]

use crate::relativetime::provider::PluralElements;
use icu_plurals::provider::PluralElementsV1;
use icu_provider::prelude::*;

#[cfg(feature = "compiled_data")]
Expand Down Expand Up @@ -43,5 +43,5 @@ pub struct CurrencyExtendedDataV1<'data> {
/// Regards to the [Unicode Report TR35](https://unicode.org/reports/tr35/tr35-numbers.html#Currencies),
/// If no matching for specific count, the `other` count will be used.
#[cfg_attr(feature = "serde", serde(borrow))]
pub display_names: PluralElements<'data, str>,
pub display_names: PluralElementsV1<'data>,
}
7 changes: 3 additions & 4 deletions components/experimental/src/dimension/provider/units.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
//!
//! Read more about data providers: [`icu_provider`]

use crate::relativetime::provider::PluralElements;
use icu_pattern::SinglePlaceholderPattern;
use crate::relativetime::provider::PluralPatterns;
use icu_pattern::SinglePlaceholder;
use icu_provider::prelude::*;

#[icu_provider::data_struct(marker(
Expand All @@ -24,9 +24,8 @@ use icu_provider::prelude::*;
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[yoke(prove_covariance_manually)]
pub struct UnitsDisplayNameV1<'data> {
// TODO: store the pattern in a SinglePattern.
// TODO: use `MeasureUnit` for the units key instead of strings.
/// Contains the long width patterns for the units.
#[cfg_attr(feature = "serde", serde(borrow))]
pub patterns: PluralElements<'data, SinglePlaceholderPattern<str>>,
pub patterns: PluralPatterns<'data, SinglePlaceholder>,
}
2 changes: 1 addition & 1 deletion components/experimental/src/dimension/units/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl<'l> Writeable for FormattedUnit<'l> {
{
self.display_name
.patterns
.get_pattern(self.plural_rules.category_for(self.value))
.get(self.value.into(), self.plural_rules)
.interpolate((self.fixed_decimal_formatter.format(self.value),))
.write_to(sink)
}
Expand Down
1 change: 1 addition & 0 deletions components/experimental/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub mod provider {
pub mod icu {
pub use crate as experimental;
pub use icu_experimental_data::icu_locale as locale;
pub use icu_plurals as plurals;
}
make_provider!(Baked);

Expand Down
2 changes: 1 addition & 1 deletion components/experimental/src/relativetime/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl<'a> Writeable for FormattedRelativeTime<'a> {
} else {
&self.formatter.rt.get().future
}
.get_pattern(self.formatter.plural_rules.category_for(&self.value))
.get((&self.value).into(), &self.formatter.plural_rules)
.interpolate((self.formatter.fixed_decimal_format.format(&self.value),))
.write_to(sink)
}
Expand Down
218 changes: 85 additions & 133 deletions components/experimental/src/relativetime/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ use alloc::borrow::Cow;
use core::marker::PhantomData;
#[cfg(feature = "datagen")]
use core::{fmt::Debug, str::FromStr};
use icu_pattern::{Pattern, PatternBackend, SinglePlaceholderPattern};
use icu_plurals::PluralCategory;
use icu_pattern::{Pattern, PatternBackend, SinglePlaceholder};
#[cfg(feature = "datagen")]
use icu_plurals::PluralElements;
use icu_plurals::{PluralCategory, PluralOperands, PluralRules};
use icu_provider::prelude::*;
use zerovec::{ule::AsULE, VarZeroVec, ZeroMap};
use zerovec::ZeroMap;

#[cfg(feature = "compiled_data")]
/// Baked data
Expand Down Expand Up @@ -69,10 +71,10 @@ pub struct RelativeTimePatternDataV1<'data> {
pub relatives: ZeroMap<'data, i8, str>,
/// How to display times in the past.
#[cfg_attr(feature = "serde", serde(borrow))]
pub past: PluralElements<'data, SinglePlaceholderPattern<str>>,
pub past: PluralPatterns<'data, SinglePlaceholder>,
/// How to display times in the future.
#[cfg_attr(feature = "serde", serde(borrow))]
pub future: PluralElements<'data, SinglePlaceholderPattern<str>>,
pub future: PluralPatterns<'data, SinglePlaceholder>,
}

#[derive(Debug)]
Expand All @@ -98,152 +100,102 @@ pub struct PluralCategoryStr<'data>(pub PluralCategory, pub Cow<'data, str>);
#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
#[cfg_attr(feature = "datagen", databake(path = icu_experimental::relativetime::provider))]
#[yoke(prove_covariance_manually)]
pub struct PluralElements<'data, T: ?Sized> {
/// Optional entries for categories other than PluralCategory::Other
#[cfg_attr(feature = "serde", serde(borrow))]
pub specials: VarZeroVec<'data, PluralCategoryStrULE>,
/// The entry for PluralCategory::Other
pub struct PluralPatterns<'data, B> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub other: Cow<'data, str>,
/// Keeps track of T
#[doc(hidden)] // databake only
pub strings: icu_plurals::provider::PluralElementsV1<'data>,
#[cfg_attr(feature = "serde", serde(skip))]
pub _phantom: PhantomData<T>,
#[doc(hidden)] // databake only
pub _phantom: PhantomData<B>,
}

impl<'data, T: ?Sized> Clone for PluralElements<'data, T> {
impl<'data, B> Clone for PluralPatterns<'data, B> {
fn clone(&self) -> Self {
Self {
specials: self.specials.clone(),
other: self.other.clone(),
_phantom: self._phantom,
strings: self.strings.clone(),
_phantom: PhantomData,
}
}
}

impl<'data, B: PatternBackend<Store = str>> PluralElements<'data, Pattern<B, str>> {
/// Creates a [`PluralElements`] from the given patterns.
#[cfg(feature = "datagen")]
pub fn try_new_pattern(
other: &str,
zero: Option<&str>,
one: Option<&str>,
two: Option<&str>,
few: Option<&str>,
many: Option<&str>,
) -> Result<Self, icu_pattern::PatternError>
where
B::PlaceholderKeyCow<'data>: FromStr,
<B::PlaceholderKeyCow<'data> as FromStr>::Err: Debug,
{
let optional_convert = |category, pattern: Option<&str>| {
pattern
.filter(|p| *p != other)
.map(|s| {
Ok(PluralCategoryStr(
category,
// TODO: Make pattern support apostrophes
Pattern::<B, String>::from_str(&s.replace('\'', "''"))
.map(|p| Pattern::<B, _>::from_store_unchecked(p.take_store().into()))?
.take_store(),
))
})
.transpose()
};

Ok(Self {
specials: (&[
(optional_convert(PluralCategory::Zero, zero)?),
(optional_convert(PluralCategory::One, one)?),
(optional_convert(PluralCategory::Two, two)?),
(optional_convert(PluralCategory::Few, few)?),
(optional_convert(PluralCategory::Many, many)?),
]
.into_iter()
.flatten()
.collect::<Vec<_>>())
.into(),
// TODO: Make pattern support apostrophes
other: Pattern::<B, String>::from_str(&other.replace('\'', "''"))?
.take_store()
.into(),
_phantom: PhantomData::<Pattern<B, str>>,
})
}

impl<'data, B: PatternBackend<Store = str>> PluralPatterns<'data, B> {
/// Returns the pattern for the given [`PluralCategory`].
pub fn get_pattern(&'data self, c: PluralCategory) -> &'data Pattern<B, str> {
Pattern::from_ref_store_unchecked(if c == PluralCategory::Other {
&*self.other
} else {
self.specials
.iter()
.filter_map(|ule| (ule.0 == c.to_unaligned()).then_some(&ule.1))
.next()
.unwrap_or(&*self.other)
})
pub fn get(&'data self, op: PluralOperands, rules: &PluralRules) -> &'data Pattern<B, str> {
Pattern::from_ref_store_unchecked(self.strings.get(op, rules))
}
}

impl<'data> PluralElements<'data, str> {
/// Creates a [`PluralElements`] from the given strings.
#[cfg(feature = "datagen")]
pub fn try_new(
other: &str,
zero: Option<&str>,
one: Option<&str>,
two: Option<&str>,
few: Option<&str>,
many: Option<&str>,
) -> Self
where {
Self {
specials: (&[
zero.filter(|p| *p != other)
.map(|s| PluralCategoryStr(PluralCategory::Zero, s.into())),
one.filter(|p| *p != other)
.map(|s| PluralCategoryStr(PluralCategory::One, s.into())),
two.filter(|p| *p != other)
.map(|s| PluralCategoryStr(PluralCategory::Two, s.into())),
few.filter(|p| *p != other)
.map(|s| PluralCategoryStr(PluralCategory::Few, s.into())),
many.filter(|p| *p != other)
.map(|s| PluralCategoryStr(PluralCategory::Many, s.into())),
]
.into_iter()
.flatten()
.collect::<Vec<_>>())
.into(),
other: other.to_owned().into(),
_phantom: PhantomData::<str>,
}
}
#[cfg(feature = "datagen")]
impl<'data, B: PatternBackend<Store = str>> TryFrom<PluralElements<'data, str>>
for PluralPatterns<'static, B>
where
B::PlaceholderKeyCow<'data>: FromStr,
<B::PlaceholderKeyCow<'data> as FromStr>::Err: Debug,
{
type Error = icu_pattern::PatternError;

fn try_from(elements: PluralElements<str>) -> Result<Self, Self::Error> {
let make_pattern = |s: &str|
// TODO: Make pattern support apostrophes
Pattern::<B, String>::from_str(&s.replace('\'', "''")).map(|p| p.take_store());

/// Returns the string for the given [`PluralCategory`].
pub fn get_str(&'data self, c: PluralCategory) -> &'data str {
if c == PluralCategory::Other {
&self.other
} else {
self.specials
.iter()
.filter_map(|ule| (ule.0 == c.to_unaligned()).then_some(&ule.1))
.next()
.unwrap_or(&*self.other)
}
Ok(Self {
strings: PluralElements::new(make_pattern(elements.other())?.as_str())
.with_zero_value(
Some(elements.zero())
.filter(|&e| e != elements.other())
.map(make_pattern)
.transpose()?
.as_deref(),
)
.with_one_value(
Some(elements.one())
.filter(|&e| e != elements.other())
.map(make_pattern)
.transpose()?
.as_deref(),
)
.with_two_value(
Some(elements.two())
.filter(|&e| e != elements.other())
.map(make_pattern)
.transpose()?
.as_deref(),
)
.with_few_value(
Some(elements.few())
.filter(|&e| e != elements.other())
.map(make_pattern)
.transpose()?
.as_deref(),
)
.with_many_value(
Some(elements.many())
.filter(|&e| e != elements.other())
.map(make_pattern)
.transpose()?
.as_deref(),
)
.with_explicit_zero_value(
elements
.explicit_zero()
.map(make_pattern)
.transpose()?
.as_deref(),
)
.with_explicit_one_value(
elements
.explicit_one()
.map(make_pattern)
.transpose()?
.as_deref(),
)
.into(),
_phantom: PhantomData,
})
}
}

#[test]
fn plural_patterns_niche() {
assert_eq!(
core::mem::size_of::<PluralElements<SinglePlaceholderPattern<str>>>(),
48
);
assert_eq!(
core::mem::size_of::<Option<PluralElements<SinglePlaceholderPattern<str>>>>(),
48
);
}

pub(crate) struct ErasedRelativeTimeFormatV1Marker;

impl DynamicDataMarker for ErasedRelativeTimeFormatV1Marker {
Expand Down
2 changes: 1 addition & 1 deletion components/plurals/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ criterion = { workspace = true }
default = ["compiled_data"]
std = ["fixed_decimal/std", "icu_locale_core/std", "icu_provider/std"]
serde = ["dep:serde", "zerovec/serde", "icu_locale_core/serde", "icu_provider/serde"]
datagen = ["serde", "zerovec/databake", "dep:databake"]
datagen = ["std", "serde", "zerovec/databake", "dep:databake"]
experimental = []
bench = ["serde", "datagen"]
compiled_data = ["dep:icu_plurals_data"]
Expand Down
Loading

0 comments on commit aa15ec5

Please sign in to comment.