numfmt/
parse.rs

1use super::*;
2use Token::*;
3
4pub fn parse_formatter(s: &str) -> std::result::Result<Formatter, ParseError> {
5    // follows grammar prefix[[.#|~#][%|s|b|n][/<char>]]suffix
6    // has 3 iteration phases
7    // 1. Prefix
8    // 2. Number
9    // 3. Suffix
10    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    // Prefix Phase
26    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('['); // part of prefix
32                    iter.next(); // skip next
33                }
34                OpenBracket => break, // start num phase
35                CloseBracket if next == Some(CloseBracket) => {
36                    prefix.push(']'); // part of prefix
37                    iter.next(); // skip next
38                }
39                CloseBracket => return Err(ParseError::OutOfPlace(']')),
40                tt => prefix.push(tt.as_char()),
41            }
42        }
43        prefix
44    };
45
46    // Number Phase
47    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            // Single Char Matches
99            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    // Suffix Phase
123    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    // Create Formatter
132    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(), // checked in tests
196            Char(ch) => *ch,
197        }
198    }
199}
200
201enum Scaler {
202    Percent,
203    Short,
204    Metric,
205    Binary,
206    No,
207}
208
209/// Errors that occur when parsing a formatting string.
210#[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}