1use super::decimal::{Increment, RoundingMode};
7use crate::error::FormatError;
8use crate::sink::ErrorSink;
9use crate::value::Value;
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
13pub(crate) enum Select {
14 Plural,
15 Ordinal,
16 Exact,
17}
18
19#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
21#[non_exhaustive]
22pub enum SignDisplay {
23 Auto,
25 Always,
27 ExceptZero,
29 Negative,
31 Never,
33}
34
35#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
37#[non_exhaustive]
38pub enum Grouping {
39 Auto,
42 Always,
44 Never,
46 Min2,
48}
49
50#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
52#[non_exhaustive]
53pub enum RoundingPriority {
54 Auto,
56 MorePrecision,
58 LessPrecision,
60}
61
62#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
65pub(crate) struct NumOpts {
66 pub(crate) select: Option<Select>,
67 pub(crate) sign_display: Option<SignDisplay>,
68 pub(crate) use_grouping: Option<Grouping>,
69 pub(crate) min_int: Option<u8>,
70 pub(crate) min_frac: Option<u8>,
71 pub(crate) max_frac: Option<u8>,
72 pub(crate) min_sig: Option<u8>,
73 pub(crate) max_sig: Option<u8>,
74 pub(crate) strip_if_integer: Option<bool>,
75 pub(crate) rounding_priority: Option<RoundingPriority>,
76 pub(crate) rounding_increment: Option<u16>,
78 pub(crate) rounding_mode: Option<RoundingMode>,
79}
80
81#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
84pub(crate) struct FracDefaults {
85 pub(crate) min: u8,
86 pub(crate) max: u8,
87}
88
89#[derive(Clone, Copy, PartialEq, Eq)]
91pub(crate) enum Kind {
92 Select,
93 Sign,
94 Grouping,
95 MinInt,
96 MinFrac,
97 MaxFrac,
98 MinSig,
99 MaxSig,
100 TrailingZero,
101 Priority,
102 Increment,
103 Mode,
104}
105
106pub(crate) const NUM: u8 = 1;
108pub(crate) const INT: u8 = 2;
109pub(crate) const PCT: u8 = 4;
110pub(crate) const CUR: u8 = 8;
111pub(crate) const UNIT: u8 = 16;
112
113pub(crate) const OPTIONS: [(&str, u8, Kind); 12] = [
115 ("select", NUM | INT, Kind::Select),
116 ("signDisplay", NUM | INT | PCT | UNIT, Kind::Sign),
117 ("useGrouping", NUM | INT | PCT | CUR | UNIT, Kind::Grouping),
118 ("minimumIntegerDigits", NUM | INT | CUR | UNIT, Kind::MinInt),
119 ("minimumFractionDigits", NUM | PCT | UNIT, Kind::MinFrac),
120 ("maximumFractionDigits", NUM | PCT | UNIT, Kind::MaxFrac),
121 (
122 "minimumSignificantDigits",
123 NUM | PCT | CUR | UNIT,
124 Kind::MinSig,
125 ),
126 (
127 "maximumSignificantDigits",
128 NUM | INT | PCT | CUR | UNIT,
129 Kind::MaxSig,
130 ),
131 ("trailingZeroDisplay", NUM | PCT | CUR, Kind::TrailingZero),
132 ("roundingPriority", NUM | PCT | CUR | UNIT, Kind::Priority),
133 ("roundingIncrement", NUM | CUR | UNIT, Kind::Increment),
134 ("roundingMode", NUM | PCT | CUR | UNIT, Kind::Mode),
135];
136
137const SELECTS: [(&str, Select); 3] = [
138 ("plural", Select::Plural),
139 ("ordinal", Select::Ordinal),
140 ("exact", Select::Exact),
141];
142const SIGNS: [(&str, SignDisplay); 5] = [
143 ("auto", SignDisplay::Auto),
144 ("always", SignDisplay::Always),
145 ("exceptZero", SignDisplay::ExceptZero),
146 ("negative", SignDisplay::Negative),
147 ("never", SignDisplay::Never),
148];
149const GROUPINGS: [(&str, Grouping); 4] = [
150 ("auto", Grouping::Auto),
151 ("always", Grouping::Always),
152 ("never", Grouping::Never),
153 ("min2", Grouping::Min2),
154];
155const TRAILING: [(&str, bool); 2] = [("auto", false), ("stripIfInteger", true)];
156const PRIORITIES: [(&str, RoundingPriority); 3] = [
157 ("auto", RoundingPriority::Auto),
158 ("morePrecision", RoundingPriority::MorePrecision),
159 ("lessPrecision", RoundingPriority::LessPrecision),
160];
161const MODES: [(&str, RoundingMode); 9] = [
162 ("ceil", RoundingMode::Ceil),
163 ("floor", RoundingMode::Floor),
164 ("expand", RoundingMode::Expand),
165 ("trunc", RoundingMode::Trunc),
166 ("halfCeil", RoundingMode::HalfCeil),
167 ("halfFloor", RoundingMode::HalfFloor),
168 ("halfExpand", RoundingMode::HalfExpand),
169 ("halfTrunc", RoundingMode::HalfTrunc),
170 ("halfEven", RoundingMode::HalfEven),
171];
172const INCREMENTS: [u16; 15] = [
173 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000,
174];
175
176fn find<T: Copy>(table: &[(&str, T)], v: &str) -> Option<T> {
177 table.iter().find(|(k, _)| *k == v).map(|&(_, t)| t)
178}
179
180pub(crate) fn select_named(v: &str) -> Option<Select> {
182 find(&SELECTS, v)
183}
184
185pub(crate) fn digit_size(v: &Value<'_>) -> Option<u8> {
188 match v {
189 Value::Int(n) => u8::try_from(*n).ok().filter(|n| *n <= 99),
190 Value::Number(n) => n
191 .value
192 .to_i64()
193 .and_then(|n| u8::try_from(n).ok())
194 .filter(|n| *n <= 99),
195 Value::Float(x) => {
196 if !(0.0..=99.0).contains(x) {
197 return None;
198 }
199 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
203 let n = *x as u8;
204 #[allow(clippy::float_cmp)]
205 (f64::from(n) == *x).then_some(n)
206 }
207 _ => match *v.as_str()?.as_bytes() {
208 [d @ b'0'..=b'9'] => Some(d - b'0'),
209 [a @ b'1'..=b'9', b @ b'0'..=b'9'] => Some((a - b'0') * 10 + (b - b'0')),
210 _ => None,
211 },
212 }
213}
214
215fn increment_of(v: &Value<'_>) -> Option<u16> {
216 let n = match v {
217 Value::Int(n) => u16::try_from(*n).ok()?,
218 Value::Number(n) => u16::try_from(n.value.to_i64()?).ok()?,
219 _ => {
220 let b = v.as_str()?.as_bytes();
221 if b.is_empty() || b.len() > 4 || b.first() == Some(&b'0') {
222 return None;
223 }
224 b.iter().try_fold(0u16, |acc, &d| {
225 d.is_ascii_digit().then(|| acc * 10 + u16::from(d - b'0'))
226 })?
227 }
228 };
229 INCREMENTS.contains(&n).then_some(n)
230}
231
232pub(crate) fn apply(o: &mut NumOpts, kind: Kind, v: &Value<'_>) -> bool {
235 let t = v.as_str();
236 let ok = match kind {
237 Kind::Sign => t
238 .and_then(|t| find(&SIGNS, t))
239 .map(|x| o.sign_display = Some(x)),
240 Kind::Grouping => t
241 .and_then(|t| find(&GROUPINGS, t))
242 .map(|x| o.use_grouping = Some(x)),
243 Kind::MinInt => digit_size(v).map(|x| o.min_int = Some(x)),
244 Kind::MinFrac => digit_size(v).map(|x| o.min_frac = Some(x)),
245 Kind::MaxFrac => digit_size(v).map(|x| o.max_frac = Some(x)),
246 Kind::MinSig => digit_size(v)
248 .filter(|x| *x >= 1)
249 .map(|x| o.min_sig = Some(x)),
250 Kind::MaxSig => digit_size(v)
251 .filter(|x| *x >= 1)
252 .map(|x| o.max_sig = Some(x)),
253 Kind::TrailingZero => t
254 .and_then(|t| find(&TRAILING, t))
255 .map(|x| o.strip_if_integer = Some(x)),
256 Kind::Priority => t
257 .and_then(|t| find(&PRIORITIES, t))
258 .map(|x| o.rounding_priority = Some(x)),
259 Kind::Increment => increment_of(v).map(|x| o.rounding_increment = Some(x)),
260 Kind::Mode => t
261 .and_then(|t| find(&MODES, t))
262 .map(|x| o.rounding_mode = Some(x)),
263 Kind::Select => None,
264 };
265 ok.is_some()
266}
267
268#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
270pub(crate) enum RoundingType {
271 Fraction,
272 Significant,
273 More,
274 Less,
275}
276
277#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
279pub(crate) struct DigitPlan {
280 pub(crate) min_int: u8,
281 pub(crate) min_frac: u8,
282 pub(crate) max_frac: u8,
283 pub(crate) min_sig: u8,
284 pub(crate) max_sig: u8,
285 pub(crate) ty: RoundingType,
286 pub(crate) increment: (Increment, i16),
288 pub(crate) mode: RoundingMode,
289 pub(crate) strip_if_integer: bool,
290}
291
292fn split_increment(n: u16) -> (Increment, i16) {
294 let mut n = n;
295 let mut k = 0;
296 while n >= 10 && n.is_multiple_of(10) && n != 25 {
297 n /= 10;
298 k += 1;
299 }
300 let inc = match n {
301 2 => Increment::Two,
302 5 => Increment::Five,
303 25 => Increment::TwentyFive,
304 _ => Increment::One,
305 };
306 (inc, k)
307}
308
309#[allow(clippy::inline_always)]
316#[inline(always)]
317pub(crate) fn digit_plan(o: &NumOpts, frac: FracDefaults, errs: &mut dyn ErrorSink) -> DigitPlan {
318 let increment = o.rounding_increment.unwrap_or(1);
319 let mnfd_default = frac.min;
320 let mxfd_default = if increment == 1 { frac.max } else { frac.min };
322 let priority = o.rounding_priority.unwrap_or(RoundingPriority::Auto);
323 let has_sd = o.min_sig.is_some() || o.max_sig.is_some();
324 let has_fd = o.min_frac.is_some() || o.max_frac.is_some();
325 let need_sd = priority != RoundingPriority::Auto || has_sd;
326 let need_fd = priority != RoundingPriority::Auto || !has_sd;
327 let (mut min_sig, mut max_sig) = (1, 21);
328 if need_sd && has_sd {
329 min_sig = o.min_sig.unwrap_or(1);
330 max_sig = o.max_sig.unwrap_or(21.max(min_sig));
331 if min_sig > max_sig {
332 errs.error(FormatError::BadOption);
333 min_sig = max_sig;
334 }
335 }
336 let (mut min_frac, mut max_frac) = (mnfd_default, mxfd_default);
337 if need_fd && has_fd {
338 match (o.min_frac, o.max_frac) {
339 (None, Some(mx)) => {
340 max_frac = mx;
341 min_frac = mnfd_default.min(mx);
342 }
343 (Some(mn), None) => {
344 min_frac = mn;
345 max_frac = mxfd_default.max(mn);
346 }
347 (Some(mn), Some(mx)) => {
348 if mn > mx {
349 errs.error(FormatError::BadOption);
350 min_frac = mx;
351 } else {
352 min_frac = mn;
353 }
354 max_frac = mx;
355 }
356 (None, None) => {}
357 }
358 }
359 let ty = match priority {
360 RoundingPriority::Auto if need_sd => RoundingType::Significant,
361 RoundingPriority::Auto => RoundingType::Fraction,
362 RoundingPriority::MorePrecision => RoundingType::More,
363 RoundingPriority::LessPrecision => RoundingType::Less,
364 };
365 let increment = if increment != 1 && (ty != RoundingType::Fraction || min_frac != max_frac) {
366 errs.error(FormatError::BadOption);
367 1
368 } else {
369 increment
370 };
371 DigitPlan {
372 min_int: o.min_int.unwrap_or(1),
373 min_frac,
374 max_frac,
375 min_sig,
376 max_sig,
377 ty,
378 increment: split_increment(increment),
379 mode: o.rounding_mode.unwrap_or(RoundingMode::HalfExpand),
380 strip_if_integer: o.strip_if_integer.unwrap_or(false),
381 }
382}