1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// 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 ).

//! 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).
/// Use [`Writeable`][Writeable] to render the formatted decimal to a string or buffer.
#[derive(Debug, PartialEq, Clone)]
pub struct FormattedFixedDecimal<'l> {
    pub(crate) value: &'l SignedFixedDecimal,
    pub(crate) options: &'l FixedDecimalFormatterOptions,
    pub(crate) symbols: &'l DecimalSymbolsV2<'l>,
    pub(crate) digits: &'l DecimalDigitsV1,
}

impl FormattedFixedDecimal<'_> {
    /// Returns the affixes needed for the current sign, as (prefix, suffix)
    fn get_affixes(&self) -> Option<(Part, (&str, &str))> {
        match self.value.sign() {
            Sign::None => None,
            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_parts<W>(&self, w: &mut W) -> core::result::Result<(), core::fmt::Error>
    where
        W: writeable::PartsWrite + ?Sized,
    {
        let affixes = self.get_affixes();
        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();
        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((part, affixes)) = affixes {
            w.with_part(part, |w| w.write_str(affixes.1))?;
        }
        Ok(())
    }
}

writeable::impl_display_with_writeable!(FormattedFixedDecimal<'_>);

#[cfg(test)]
mod tests {
    use icu_locale_core::locale;
    use writeable::assert_writeable_eq;

    use crate::FixedDecimalFormatter;

    #[test]
    pub fn test_es_mx() {
        let locale = locale!("es-MX").into();
        let fmt = FixedDecimalFormatter::try_new(locale, Default::default()).unwrap();
        let fd = "12345.67".parse().unwrap();
        assert_writeable_eq!(fmt.format(&fd), "12,345.67");
    }
}