1use super::*;
2use Token::*;
3
4pub fn parse_formatter(s: &str) -> std::result::Result<Formatter, ParseError> {
5 if s.is_empty() {
11 return Ok(Formatter::default());
12 }
13
14 let tokens = {
15 let mut tokens: Vec<(Token, Option<Token>)> =
16 s.chars().map(Token::recognise).map(|t| (t, None)).collect();
17 for i in 1..tokens.len() {
18 tokens[i - 1].1 = Some(tokens[i].0);
19 }
20 tokens
21 };
22
23 let mut iter = tokens.into_iter();
24
25 let prefix = {
27 let mut prefix = String::new();
28 while let Some((token, next)) = iter.next() {
29 match token {
30 OpenBracket if next == Some(OpenBracket) => {
31 prefix.push('['); iter.next(); }
34 OpenBracket => break, CloseBracket if next == Some(CloseBracket) => {
36 prefix.push(']'); iter.next(); }
39 CloseBracket => return Err(ParseError::OutOfPlace(']')),
40 tt => prefix.push(tt.as_char()),
41 }
42 }
43 prefix
44 };
45
46 let mut scaler = None;
48 let mut no_precision = false;
49 let mut use_dec = false;
50 let mut prec = None;
51 let mut digits = false;
52 let mut sep = Some(',');
53 let mut comma = false;
54
55 while let Some((token, next)) = iter.next() {
56 let next_is_digit = next.map(|t| matches!(t, Digit(_))).unwrap_or_default();
57
58 match token {
59 Digit(d) if digits => {
60 prec = if let Some(p) = prec {
61 if p >= 26 || (p == 25 && d >= 6) {
62 return Err(ParseError::PrecisionTooLarge);
63 } else {
64 Some(p * 10 + d)
65 }
66 } else {
67 Some(d)
68 };
69 digits = next_is_digit;
70 }
71 Period | Comma if next == Some(Char('*')) => {
72 no_precision = true;
73 comma = token == Comma;
74 iter.next();
75 }
76 Tilde | Period | Comma if !next_is_digit => {
77 return Err(ParseError::Unexp(
78 "digit",
79 next.map(|t| t.as_char()).unwrap_or(' '),
80 ))
81 }
82 Tilde | Period | Comma if no_precision => {
83 return Err(ParseError::DupPrec(no_precision, 0))
84 }
85 Tilde | Period | Comma if prec.is_some() => {
86 return Err(ParseError::DupPrec(no_precision, prec.unwrap()))
87 }
88 Period => {
89 digits = true;
90 use_dec = true;
91 }
92 Comma => {
93 digits = true;
94 use_dec = true;
95 comma = true;
96 }
97 Tilde => digits = true,
98 Char('%') if scaler.is_none() => scaler = Some(Scaler::Percent),
100 Char('s') if scaler.is_none() => scaler = Some(Scaler::Short),
101 Char('m') if scaler.is_none() => scaler = Some(Scaler::Metric),
102 Char('b') if scaler.is_none() => scaler = Some(Scaler::Binary),
103 Char('n') if scaler.is_none() => scaler = Some(Scaler::No),
104 Char('%') | Char('s') | Char('b') | Char('n') if scaler.is_some() => {
105 return Err(ParseError::DupScaler(token.as_char()))
106 }
107 Slash if next == Some(CloseBracket) => sep = None,
108 Slash => {
109 sep = next.map(|t| t.as_char());
110 iter.next();
111 }
112 CloseBracket => break,
113 tt => {
114 return Err(ParseError::Unexp(
115 "., ,, ~, %, s, m, b, n, /, ]",
116 tt.as_char(),
117 ))
118 }
119 }
120 }
121
122 let mut suffix: String = iter
124 .filter_map(|x| match x {
125 (OpenBracket, Some(OpenBracket)) => None,
126 (CloseBracket, Some(CloseBracket)) => None,
127 (tt, _) => Some(tt.as_char()),
128 })
129 .collect();
130
131 let mut f = match scaler {
133 Some(Scaler::Percent) => {
134 suffix.insert(0, '%');
135 Formatter::percentage()
136 }
137 Some(Scaler::Short) => Formatter::default().scales(Scales::short()),
138 Some(Scaler::Metric) => Formatter::default().scales(Scales::metric()),
139 Some(Scaler::Binary) => Formatter::default().scales(Scales::binary()),
140 Some(Scaler::No) => Formatter::default().scales(Scales::none()),
141 None => Formatter::default(),
142 };
143
144 f = f.separator(sep)?.prefix(&prefix)?.suffix(&suffix)?;
145 comma |= f.comma;
146 f = f.comma(comma);
147
148 if no_precision {
149 f = f.precision(Unspecified);
150 } else if let Some(prec) = prec {
151 f = f.precision(if use_dec {
152 Decimals(prec)
153 } else {
154 Significance(prec)
155 });
156 }
157
158 Ok(f)
159}
160
161#[derive(Debug, PartialEq, Copy, Clone)]
162enum Token {
163 OpenBracket,
164 CloseBracket,
165 Period,
166 Comma,
167 Tilde,
168 Slash,
169 Digit(u8),
170 Char(char),
171}
172
173impl Token {
174 fn recognise(ch: char) -> Self {
175 match ch {
176 '[' => OpenBracket,
177 ']' => CloseBracket,
178 '.' => Period,
179 ',' => Comma,
180 '~' => Tilde,
181 '/' => Slash,
182 x if x.is_ascii_digit() => Digit(x.to_digit(10).unwrap() as u8),
183 x => Char(x),
184 }
185 }
186
187 fn as_char(&self) -> char {
188 match self {
189 OpenBracket => '[',
190 CloseBracket => ']',
191 Period => '.',
192 Comma => ',',
193 Tilde => '~',
194 Slash => '/',
195 Digit(d) => (d + 48).into(), Char(ch) => *ch,
197 }
198 }
199}
200
201enum Scaler {
202 Percent,
203 Short,
204 Metric,
205 Binary,
206 No,
207}
208
209#[derive(Debug, PartialEq)]
211#[allow(missing_docs)]
212pub enum ParseError {
213 FormatterError(Error),
214 OutOfPlace(char),
215 Unexp(&'static str, char),
216 PrecisionTooLarge,
217 DupScaler(char),
218 DupPrec(bool, u8),
219}
220
221impl From<Error> for ParseError {
222 fn from(err: Error) -> Self {
223 ParseError::FormatterError(err)
224 }
225}
226
227impl error::Error for ParseError {}
228impl fmt::Display for ParseError {
229 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
230 match self {
231 ParseError::FormatterError(e) => write!(f, "formatter error: {e}"),
232 ParseError::OutOfPlace(ch) => write!(
233 f,
234 "the character `{ch}` was found when parser was in prefix state"
235 ),
236 ParseError::Unexp(exp, found) => write!(
237 f,
238 "unexpected character. expected a {exp} but found '{found}'"
239 ),
240 ParseError::PrecisionTooLarge => write!(f, "precision is larger than 255"),
241 ParseError::DupScaler(ch) => write!(
242 f,
243 "a scaler has already been set and '{ch}' can not override"
244 ),
245 ParseError::DupPrec(no, n) => {
246 if *no {
247 write!(f, "unspecified precision has already been set")
248 } else {
249 write!(f, "precision of {n} has already been set")
250 }
251 }
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn parsing_empty_num_formatter() {
262 let p = parse_formatter;
263 let r = p("");
264 assert_eq!(r, Ok(Formatter::default()));
265
266 let r = p("a prefix");
267 assert_eq!(r, Ok(Formatter::default().prefix("a prefix").unwrap()));
268
269 let r = p("[]a suffix");
270 assert_eq!(r, Ok(Formatter::default().suffix("a suffix").unwrap()));
271
272 let r = p("a prefix[]a suffix");
273 assert_eq!(
274 r,
275 Ok(Formatter::default()
276 .prefix("a prefix")
277 .unwrap()
278 .suffix("a suffix")
279 .unwrap())
280 );
281
282 let r = p("a[[p]]refix[]a[[s]]uffix");
283 assert_eq!(
284 r,
285 Ok(Formatter::default()
286 .prefix("a[p]refix")
287 .unwrap()
288 .suffix("a[s]uffix")
289 .unwrap())
290 );
291 }
292
293 #[test]
294 fn parsing_percentage() {
295 let p = parse_formatter;
296 let r = p("[%]");
297 assert_eq!(r, Ok(Formatter::percentage().separator(',').unwrap()));
298
299 let r = p("prefix [%] suffix");
300 assert_eq!(
301 r,
302 Ok(Formatter::percentage()
303 .separator(',')
304 .unwrap()
305 .prefix("prefix ")
306 .unwrap()
307 .suffix("% suffix")
308 .unwrap())
309 );
310 }
311
312 #[test]
313 fn parsing_precision() {
314 let p = parse_formatter;
315 let r = p("[.*]");
316 assert_eq!(r, Ok(Formatter::default().precision(Unspecified)));
317
318 let r = p("[,*]");
319 assert_eq!(
320 r,
321 Ok(Formatter::default()
322 .separator('.')
323 .unwrap()
324 .comma(true)
325 .precision(Unspecified))
326 );
327
328 let r = p("[.0]");
329 assert_eq!(r, Ok(Formatter::default().precision(Decimals(0))));
330 let r = p("[.5]");
331 assert_eq!(r, Ok(Formatter::default().precision(Decimals(5))));
332 let r = p("[.15]");
333 assert_eq!(r, Ok(Formatter::default().precision(Decimals(15))));
334
335 let r = p("[,15]");
336 assert_eq!(
337 r,
338 Ok(Formatter::default()
339 .separator('.')
340 .unwrap()
341 .precision(Decimals(15)))
342 );
343
344 let r = p("[~0]");
345 assert_eq!(r, Ok(Formatter::default().precision(Significance(0))));
346 let r = p("[~5]");
347 assert_eq!(r, Ok(Formatter::default().precision(Significance(5))));
348 let r = p("[~15]");
349 assert_eq!(r, Ok(Formatter::default().precision(Significance(15))));
350
351 let r = p("[~15/.]");
352 assert_eq!(
353 r,
354 Ok(Formatter::default()
355 .separator('.')
356 .unwrap()
357 .precision(Significance(15)))
358 );
359 }
360
361 #[test]
362 fn parsing_test_digit_token_as_char() {
363 let t = |x| Token::Digit(x).as_char();
364 assert_eq!(t(0), '0');
365 assert_eq!(t(1), '1');
366 assert_eq!(t(2), '2');
367 assert_eq!(t(3), '3');
368 assert_eq!(t(4), '4');
369 assert_eq!(t(5), '5');
370 assert_eq!(t(6), '6');
371 assert_eq!(t(7), '7');
372 assert_eq!(t(8), '8');
373 assert_eq!(t(9), '9');
374 }
375
376 #[test]
377 fn parsing_separator() {
378 let p = parse_formatter;
379 let r = p("[/]");
380 assert_eq!(r, Ok(Formatter::default().separator(None).unwrap()));
381
382 let r = p("[//]");
383 assert_eq!(r, Ok(Formatter::default().separator('/').unwrap()));
384
385 let r = p("[/%]");
386 assert_eq!(r, Ok(Formatter::default().separator('%').unwrap()));
387
388 let r = p("[/.]");
389 assert_eq!(
390 r,
391 Ok(Formatter::default().separator('.').unwrap().comma(true))
392 );
393 }
394
395 #[test]
396 fn scalers() {
397 let p = parse_formatter;
398 let r = p("[s]");
399 assert_eq!(r, Ok(Formatter::default()));
400
401 let r = p("[m]");
402 assert_eq!(r, Ok(Formatter::default().scales(Scales::metric())));
403
404 let r = p("[b]");
405 assert_eq!(r, Ok(Formatter::default().scales(Scales::binary())));
406
407 let r = p("[n]");
408 assert_eq!(r, Ok(Formatter::default().scales(Scales::none())));
409 }
410
411 #[test]
412 fn error_testing() {
413 let p = parse_formatter;
414 let r = p("prefix]");
415 assert!(r.is_err());
416 assert_eq!(
417 &r.unwrap_err().to_string(),
418 "the character `]` was found when parser was in prefix state"
419 );
420
421 let r = p("pre[fix]");
422 assert!(r.is_err());
423 assert_eq!(
424 &r.unwrap_err().to_string(),
425 "unexpected character. expected a ., ,, ~, %, s, m, b, n, /, ] but found 'f'"
426 );
427
428 let r = p("[.3~1]");
429 assert!(r.is_err());
430 assert_eq!(
431 &r.unwrap_err().to_string(),
432 "precision of 3 has already been set"
433 );
434
435 let r = p("[.*~1]");
436 assert!(r.is_err());
437 assert_eq!(
438 &r.unwrap_err().to_string(),
439 "unspecified precision has already been set"
440 );
441
442 let r = p("prefix[./.]suffix");
443 assert!(r.is_err());
444 assert_eq!(
445 &r.unwrap_err().to_string(),
446 "unexpected character. expected a digit but found '/'"
447 );
448
449 let r = p("prefix[.3/.%n]suffix");
450 assert!(r.is_err());
451 assert_eq!(
452 &r.unwrap_err().to_string(),
453 "a scaler has already been set and 'n' can not override"
454 );
455
456 let r = p("pre~fix[.256]suffix");
457 assert!(r.is_err());
458 assert_eq!(&r.unwrap_err().to_string(), "precision is larger than 255");
459
460 let r = p("pre~fix[~300]suffix");
461 assert!(r.is_err());
462 assert_eq!(&r.unwrap_err().to_string(), "precision is larger than 255");
463
464 let r = p("prefix that is long[~3]suffix");
465 assert!(r.is_err());
466 assert_eq!(
467 &r.unwrap_err().to_string(),
468 "formatter error: Invalid prefix `prefix that is long`. Prefix is longer than the supported 12 bytes"
469 );
470 }
471}