Skip to content

Commit

Permalink
Parts formatting for icu_decimal (#5897)
Browse files Browse the repository at this point in the history
  • Loading branch information
sffc authored Dec 17, 2024
1 parent c2af1f0 commit c26c10c
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 26 deletions.
80 changes: 58 additions & 22 deletions components/decimal/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@

//! Lower-level types for decimal formatting.
use core::fmt::Write;

use crate::grouper;
use crate::options::*;
use crate::parts;
use crate::provider::*;
use fixed_decimal::Sign;
use fixed_decimal::SignedFixedDecimal;
use writeable::Part;
use writeable::PartsWrite;
use writeable::Writeable;

/// An intermediate structure returned by [`FixedDecimalFormatter`](crate::FixedDecimalFormatter).
Expand All @@ -23,43 +28,74 @@ pub struct FormattedFixedDecimal<'l> {

impl FormattedFixedDecimal<'_> {
/// Returns the affixes needed for the current sign, as (prefix, suffix)
fn get_affixes(&self) -> Option<(&str, &str)> {
fn get_affixes(&self) -> Option<(Part, (&str, &str))> {
match self.value.sign() {
Sign::None => None,
Sign::Negative => Some(self.symbols.minus_sign_affixes()),
Sign::Positive => Some(self.symbols.plus_sign_affixes()),
Sign::Negative => Some((parts::MINUS_SIGN, self.symbols.minus_sign_affixes())),
Sign::Positive => Some((parts::PLUS_SIGN, self.symbols.plus_sign_affixes())),
}
}
}

impl Writeable for FormattedFixedDecimal<'_> {
fn write_to<W>(&self, sink: &mut W) -> core::result::Result<(), core::fmt::Error>
fn write_to_parts<W>(&self, w: &mut W) -> core::result::Result<(), core::fmt::Error>
where
W: core::fmt::Write + ?Sized,
W: writeable::PartsWrite + ?Sized,
{
let affixes = self.get_affixes();
if let Some(affixes) = affixes {
sink.write_str(affixes.0)?;
if let Some((part, affixes)) = affixes {
w.with_part(part, |w| w.write_str(affixes.0))?;
}
let range = self.value.absolute.magnitude_range();
let upper_magnitude = *range.end();
for m in range.rev() {
if m == -1 {
sink.write_str(self.symbols.decimal_separator())?;
}
#[allow(clippy::indexing_slicing)] // digit_at in 0..=9
sink.write_char(self.digits.digits[self.value.digit_at(m) as usize])?;
if grouper::check(
upper_magnitude,
m,
self.options.grouping_strategy,
&self.symbols.grouping_sizes,
) {
sink.write_str(self.symbols.grouping_separator())?;
let mut range = range.rev();
let mut has_fraction = false;
w.with_part(parts::INTEGER, |w| {
loop {
let m = match range.next() {
Some(m) if m < 0 => {
has_fraction = true;
break Ok(());
}
Some(m) => m,
None => {
break Ok(());
}
};
#[allow(clippy::indexing_slicing)] // digit_at in 0..=9
w.write_char(self.digits.digits[self.value.digit_at(m) as usize])?;
if grouper::check(
upper_magnitude,
m,
self.options.grouping_strategy,
&self.symbols.grouping_sizes,
) {
w.with_part(parts::GROUP, |w| {
w.write_str(self.symbols.grouping_separator())
})?;
}
}
})?;
if has_fraction {
w.with_part(parts::DECIMAL, |w| {
w.write_str(self.symbols.decimal_separator())
})?;
w.with_part(parts::FRACTION, |w| {
let mut m = -1; // read in the previous loop
loop {
#[allow(clippy::indexing_slicing)] // digit_at in 0..=9
w.write_char(self.digits.digits[self.value.digit_at(m) as usize])?;
m = match range.next() {
Some(m) => m,
None => {
break Ok(());
}
};
}
})?;
}
if let Some(affixes) = affixes {
sink.write_str(affixes.1)?;
if let Some((part, affixes)) = affixes {
w.with_part(part, |w| w.write_str(affixes.1))?;
}
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions components/decimal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ extern crate alloc;
mod format;
mod grouper;
pub mod options;
pub mod parts;
pub mod provider;
pub(crate) mod size_test_macro;

Expand Down
75 changes: 75 additions & 0 deletions components/decimal/src/parts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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 ).

//! Parts of a formatted decimal.
//!
//! # Examples
//!
//! ```
//! use icu::calendar::Gregorian;
//! use icu::calendar::{Date, Time};
//! use icu::decimal::parts;
//! use icu::decimal::FixedDecimalFormatter;
//! use icu::locale::locale;
//! use writeable::assert_writeable_parts_eq;
//!
//! let dtf = FixedDecimalFormatter::try_new(
//! locale!("en").into(),
//! Default::default(),
//! )
//! .unwrap();
//!
//! let fixed_decimal = "-987654.321".parse().unwrap();
//!
//! // Missing data is filled in on a best-effort basis, and an error is signaled.
//! assert_writeable_parts_eq!(
//! dtf.format(&fixed_decimal),
//! "-987,654.321",
//! [
//! (0, 1, parts::MINUS_SIGN),
//! (1, 8, parts::INTEGER),
//! (4, 5, parts::GROUP),
//! (8, 9, parts::DECIMAL),
//! (9, 12, parts::FRACTION),
//! ]
//! );
//! ```
use writeable::Part;

/// A [`Part`] used by [`FormattedFixedDecimal`](super::FormattedFixedDecimal).
pub const PLUS_SIGN: Part = Part {
category: "decimal",
value: "plusSign",
};

/// A [`Part`] used by [`FormattedFixedDecimal`](super::FormattedFixedDecimal).
pub const MINUS_SIGN: Part = Part {
category: "decimal",
value: "minusSign",
};

/// A [`Part`] used by [`FormattedFixedDecimal`](super::FormattedFixedDecimal).
pub const INTEGER: Part = Part {
category: "decimal",
value: "integer",
};

/// A [`Part`] used by [`FormattedFixedDecimal`](super::FormattedFixedDecimal).
pub const FRACTION: Part = Part {
category: "decimal",
value: "fraction",
};

/// A [`Part`] used by [`FormattedFixedDecimal`](super::FormattedFixedDecimal).
pub const GROUP: Part = Part {
category: "decimal",
value: "group",
};

/// A [`Part`] used by [`FormattedFixedDecimal`](super::FormattedFixedDecimal).
pub const DECIMAL: Part = Part {
category: "decimal",
value: "decimal",
};
6 changes: 3 additions & 3 deletions components/experimental/src/dimension/units/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ pub struct FormattedUnit<'l> {
}

impl Writeable for FormattedUnit<'_> {
fn write_to<W>(&self, sink: &mut W) -> core::result::Result<(), core::fmt::Error>
fn write_to_parts<W>(&self, sink: &mut W) -> core::result::Result<(), core::fmt::Error>
where
W: core::fmt::Write + ?Sized,
W: writeable::PartsWrite + ?Sized,
{
self.display_name
.patterns
.get(self.value.into(), self.plural_rules)
.interpolate((self.fixed_decimal_formatter.format(self.value),))
.write_to(sink)
.write_to_parts(sink)
}
}

Expand Down
11 changes: 10 additions & 1 deletion components/experimental/src/duration/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,19 +710,28 @@ mod tests {
[
(0, 6, parts::YEAR),
(0, 6, icu_list::parts::ELEMENT),
(0, 1, icu_decimal::parts::MINUS_SIGN),
(1, 2, icu_decimal::parts::INTEGER),
(6, 8, icu_list::parts::LITERAL),
(8, 14, parts::MONTH),
(8, 14, icu_list::parts::ELEMENT),
(8, 9, icu_decimal::parts::INTEGER),
(14, 16, icu_list::parts::LITERAL),
(16, 21, parts::WEEK),
(16, 21, icu_list::parts::ELEMENT),
(16, 17, icu_decimal::parts::INTEGER),
(21, 23, icu_list::parts::LITERAL),
(23, 37, icu_list::parts::ELEMENT),
(23, 25, icu_decimal::parts::INTEGER),
(23, 25, parts::HOUR),
(25, 26, parts::LITERAL),
(26, 28, icu_decimal::parts::INTEGER),
(26, 28, parts::MINUTE),
(28, 29, parts::LITERAL),
(29, 37, parts::SECOND)
(29, 37, parts::SECOND),
(29, 31, icu_decimal::parts::INTEGER),
(31, 32, icu_decimal::parts::DECIMAL),
(32, 37, icu_decimal::parts::FRACTION),
]
);
}
Expand Down
1 change: 1 addition & 0 deletions tools/make/diplomat-coverage/src/allowlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ lazy_static::lazy_static! {
// Don't want parts for 2.0, would need to introduce diplomat writeable with parts
"icu::list::parts",
"icu::datetime::parts",
"icu::decimal::parts",

// Not planned until someone needs them
"icu::locale::extensions",
Expand Down

0 comments on commit c26c10c

Please sign in to comment.