Skip to content

Commit

Permalink
The actual formatting part of compact decimal formatting (#2898)
Browse files Browse the repository at this point in the history
  • Loading branch information
eggrobin authored Dec 20, 2022
1 parent 4319510 commit a61a16a
Show file tree
Hide file tree
Showing 11 changed files with 566 additions and 3 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion experimental/compactdecimal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fixed_decimal = { version = "0.5", path = "../../utils/fixed_decimal" }
databake = { version = "0.1.0", path = "../../utils/databake", optional = true, features = ["derive"]}

[dev-dependencies]
icu_testdata = { path = "../../provider/testdata", default-features = false, features = ["icu_plurals", "icu_decimal"] }
icu_testdata = { path = "../../provider/testdata", default-features = false, features = ["icu_plurals", "icu_decimal", "icu_compactdecimal"] }
icu_locid = { version = "1.0.0", path = "../../components/locid" }

[features]
Expand Down
414 changes: 414 additions & 0 deletions experimental/compactdecimal/src/compactdecimal.rs

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions experimental/compactdecimal/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ pub enum CompactDecimalError {
/// An error originating from [`FixedDecimalFormatter`](icu_decimal::FixedDecimalFormatter).
#[displaydoc("Error loading FixedDecimalFormatter: {0}")]
Decimal(DecimalError),
/// An error due to a [`CompactDecimal`](fixed_decimal::CompactDecimal) with an
/// exponent inconsistent with the compact decimal data for the locale, e.g.,
/// when formatting 1c5 in English (US).
#[displaydoc("Expected compact exponent {expected} for 10^{log10_type}, got {actual}")]
Exponent {
/// The compact decimal exponent passed to the formatter.
actual: i16,
/// The appropriate compact decimal exponent for a number of the given magnitude.
expected: i16,
/// The magnitude of the number being formatted.
log10_type: i16,
},
}

impl From<PluralsError> for CompactDecimalError {
Expand Down
74 changes: 74 additions & 0 deletions experimental/compactdecimal/src/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use alloc::borrow::Cow;
use fixed_decimal::{CompactDecimal, FixedDecimal};
use writeable::Writeable;
use zerovec::maps::ZeroMap2dCursor;

use crate::compactdecimal::CompactDecimalFormatter;
use crate::provider::{Count, PatternULE};

/// An intermediate structure returned by [`CompactDecimalFormatter`](crate::CompactDecimalFormatter).
/// Use [`Writeable`][Writeable] to render the formatted decimal to a string or buffer.
pub struct FormattedCompactDecimal<'l> {
pub(crate) formatter: &'l CompactDecimalFormatter,
pub(crate) value: Cow<'l, CompactDecimal>,
pub(crate) plural_map: Option<ZeroMap2dCursor<'l, 'l, i8, Count, PatternULE>>,
}

impl<'l> Writeable for FormattedCompactDecimal<'l> {
fn write_to<W>(&self, sink: &mut W) -> core::result::Result<(), core::fmt::Error>
where
W: core::fmt::Write + ?Sized,
{
if self.value.exponent() == 0 {
self.formatter
.fixed_decimal_format
.format(self.value.significand())
.write_to(sink)
} else {
let plural_map = self.plural_map.as_ref().ok_or(core::fmt::Error)?;
let chosen_pattern = (|| {
if self.value.significand() == &FixedDecimal::from(1) {
if let Some(pattern) = plural_map.get1(&Count::Explicit1) {
return Some(pattern);
}
}
let plural_category = self
.formatter
.plural_rules
.category_for(self.value.significand());
plural_map
.get1(&plural_category.into())
.or_else(|| plural_map.get1(&Count::Other))
})()
.ok_or(core::fmt::Error)?;
match chosen_pattern.index {
u8::MAX => sink.write_str(&chosen_pattern.literal_text),
_ => {
let i = usize::from(chosen_pattern.index);
sink.write_str(
chosen_pattern
.literal_text
.get(..i)
.ok_or(core::fmt::Error)?,
)?;
self.formatter
.fixed_decimal_format
.format(self.value.significand())
.write_to(sink)?;
sink.write_str(
chosen_pattern
.literal_text
.get(i..)
.ok_or(core::fmt::Error)?,
)
}
}
}
}
}

writeable::impl_display_with_writeable!(FormattedCompactDecimal<'_>);
5 changes: 4 additions & 1 deletion experimental/compactdecimal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@
clippy::panic,
clippy::exhaustive_structs,
clippy::exhaustive_enums,
missing_debug_implementations,
// TODO(#2266): enable missing_debug_implementations,
)
)]
#![warn(missing_docs)]

extern crate alloc;

mod compactdecimal;
mod error;
mod format;
pub mod provider;

pub use compactdecimal::CompactDecimalFormatter;
pub use error::CompactDecimalError;
22 changes: 21 additions & 1 deletion experimental/compactdecimal/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
//! Read more about data providers: [`icu_provider`]
use alloc::borrow::Cow;
use icu_provider::{yoke, zerofrom};
use icu_plurals::PluralCategory;
use icu_provider::{yoke, zerofrom, DataMarker};
use zerovec::ZeroMap2d;

/// Relative time format V1 data struct.
Expand Down Expand Up @@ -89,6 +90,20 @@ pub enum Count {
// algorithm does not allow such a thing to arise.
}

impl From<PluralCategory> for Count {
fn from(other: PluralCategory) -> Self {
use PluralCategory::*;
match other {
Zero => Count::Zero,
One => Count::One,
Two => Count::Two,
Few => Count::Few,
Many => Count::Many,
Other => Count::Other,
}
}
}

/// A compact decimal pattern, representing some literal text with an optional
/// placeholder, and the power of 10 expressed by the text.
#[derive(
Expand Down Expand Up @@ -125,3 +140,8 @@ pub struct Pattern<'data> {
/// " M" for the pattern "000 M"
pub literal_text: Cow<'data, str>,
}
pub(crate) struct ErasedCompactDecimalFormatDataV1Marker;

impl DataMarker for ErasedCompactDecimalFormatDataV1Marker {
type Yokeable = CompactDecimalPatternDataV1<'static>;
}
2 changes: 2 additions & 0 deletions ffi/diplomat/ffi_coverage/tests/missing_apis.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
fixed_decimal::CompactDecimal#Struct
fixed_decimal::CompactDecimal::exponent#FnInStruct
fixed_decimal::CompactDecimal::from_significand_and_exponent#FnInStruct
fixed_decimal::CompactDecimal::from_str#FnInStruct
fixed_decimal::CompactDecimal::into_significand#FnInStruct
fixed_decimal::CompactDecimal::significand#FnInStruct
fixed_decimal::CompactDecimal::write_to#FnInStruct
fixed_decimal::FixedInteger#Struct
fixed_decimal::FixedInteger::from_str#FnInStruct
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,22 @@ impl TryFrom<&DecimalFormat> for CompactDecimalPatternDataV1<'static> {
);
}
}
if !patterns
.iter()
.tuple_windows()
.all(|((_, low), (_, high))| {
low.get(&Count::Other).map(|p| p.exponent)
<= high.get(&Count::Other).map(|p| p.exponent)
})
{
Err(format!(
"Compact decimal exponents should be nondecreasing: {:?}",
patterns
.iter()
.map(|(_, plural_map)| plural_map.get(&Count::Other).map(|p| p.exponent))
.collect::<Vec<_>>(),
))?;
}
// Deduplicate sequences of types that have the same plural map (up to =1), keeping the lowest type.
// The pattern 0 for type 1 is implicit.
let deduplicated_patterns = patterns
Expand Down
1 change: 1 addition & 0 deletions provider/testdata/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ zerovec = { version = "0.9", path = "../../utils/zerovec" }
icu_calendar = { version = "1.0.0", path = "../../components/calendar", default-features = false, optional = true }
icu_casemapping = { version = "0.7.0", path = "../../experimental/casemapping", default-features = false, optional = true }
icu_collator = { version = "1.0.0", path = "../../components/collator", default-features = false, optional = true }
icu_compactdecimal = { version = "0.1.0", path = "../../experimental/compactdecimal", default-features = false, optional = true }
icu_datetime = { version = "1.0.0", path = "../../components/datetime", default-features = false, optional = true }
icu_decimal = { version = "1.0.0", path = "../../components/decimal", default-features = false, optional = true }
icu_displaynames = { version = "0.7.0", path = "../../experimental/displaynames", default-features = false, optional = true }
Expand Down
20 changes: 20 additions & 0 deletions utils/fixed_decimal/src/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@ pub struct CompactDecimal {
}

impl CompactDecimal {
/// Constructs a [`CompactDecimal`] from its significand and exponent.
pub fn from_significand_and_exponent(significand: FixedDecimal, exponent: u8) -> Self {
Self {
significand,
exponent: exponent.into(),
}
}

/// Returns a reference to the significand of `self`.
/// ```
/// # use fixed_decimal::CompactDecimal;
/// # use fixed_decimal::FixedDecimal;
/// # use std::str::FromStr;
/// #
/// assert_eq!(CompactDecimal::from_str("+1.20c6").unwrap().significand(), &FixedDecimal::from_str("+1.20").unwrap());
/// ```
pub fn significand(&self) -> &FixedDecimal {
&self.significand
}

/// Returns the significand of `self`.
/// ```
/// # use fixed_decimal::CompactDecimal;
Expand Down

0 comments on commit a61a16a

Please sign in to comment.