parse_display_derive/
format_syntax.rs

1use std::fmt::{Display, Formatter};
2use std::str::FromStr;
3
4use proc_macro2::Span;
5
6#[derive(Debug, PartialEq, Eq)]
7pub enum Sign {
8    Plus,
9    Minus,
10}
11
12#[derive(Debug, PartialEq, Eq)]
13pub enum Align {
14    Left,
15    Right,
16    Center,
17}
18
19#[derive(Debug, PartialEq, Eq, Default)]
20pub struct FormatSpec<'a> {
21    pub fill: Option<char>,
22    pub align: Option<Align>,
23    pub sign: Option<Sign>,
24    pub is_alternate: bool,
25    pub is_zero: bool,
26    pub width: Option<SubArg<'a, usize>>,
27    pub precision: Option<SubArg<'a, usize>>,
28    pub format_type: FormatType,
29}
30
31#[derive(Debug, PartialEq, Eq)]
32pub enum SubArg<'a, T> {
33    Value(T),
34    Index(usize),
35    Name(&'a str),
36    Input,
37}
38
39#[derive(Debug, PartialEq, Eq, Default)]
40pub enum FormatType {
41    #[default]
42    Display,
43    Debug,
44    DebugUpperHex,
45    DebugLowerHex,
46    Octal,
47    LowerHex,
48    UpperHex,
49    Pointer,
50    Binary,
51    LowerExp,
52    UpperExp,
53}
54impl FormatType {
55    pub fn trait_name(&self) -> &str {
56        match self {
57            FormatType::Display => "Display",
58            FormatType::Debug | FormatType::DebugUpperHex | FormatType::DebugLowerHex => "Debug",
59            FormatType::Octal => "Octal",
60            FormatType::LowerHex => "LowerHex",
61            FormatType::UpperHex => "UpperHex",
62            FormatType::Pointer => "Pointer",
63            FormatType::Binary => "Binary",
64            FormatType::LowerExp => "LowerExp",
65            FormatType::UpperExp => "UpperExp",
66        }
67    }
68}
69
70#[derive(Debug, Eq, PartialEq)]
71pub struct FormatParseError;
72
73impl FormatParseError {
74    const ERROR_MESSAGE: &'static str = "FormatSpec parse failed.";
75}
76
77impl std::error::Error for FormatParseError {
78    fn description(&self) -> &str {
79        FormatParseError::ERROR_MESSAGE
80    }
81}
82impl Display for FormatParseError {
83    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
84        write!(f, "{}", FormatParseError::ERROR_MESSAGE)
85    }
86}
87
88impl<'a> FormatSpec<'a> {
89    pub fn parse_with_span(s: &'a str, span: Span) -> syn::Result<Self> {
90        match Self::parse(s) {
91            Ok(ps) => Ok(ps),
92            Err(_) => bail!(span, "unknown format trait `{s}`"), // This message is the same as the one output by the compiler
93        }
94    }
95
96    pub fn parse(s: &'a str) -> std::result::Result<Self, FormatParseError> {
97        let re = regex!(
98            "^\
99             ((?<fill>.)?\
100             (?<align>[<>^]))??\
101             (?<sign>[+-])?\
102             (?<is_alternate>#)?\
103             (?<is_zero>0)?\
104             (\
105             (?<width_integer>[0-9]+)|\
106             ((?<width_arg>[a-zA-Z0-9_]+)\\$)\
107             )?\
108             (\\.(\
109             (?<precision_input>\\*)|\
110             (?<precision_integer>[0-9]+)|\
111             ((?<precision_arg>[a-zA-Z0-9_]+)\\$)\
112             ))?\
113             (?<format_type>[a-zA-Z0-9_]*\\??)\
114             $"
115        );
116
117        let c = re.captures(s).ok_or(FormatParseError)?;
118        let fill = c.name("fill").map(|m| m.as_str().chars().next().unwrap());
119        let align = c.name("align").map(|m| m.as_str().parse().unwrap());
120        let sign = c.name("sign").map(|m| match m.as_str() {
121            "+" => Sign::Plus,
122            "-" => Sign::Minus,
123            _ => unreachable!(),
124        });
125        let is_alternate = c.name("is_alternate").is_some();
126        let is_zero = c.name("is_zero").is_some();
127        let width = if let Some(m) = c.name("width_integer") {
128            let value = m.as_str().parse().map_err(|_| FormatParseError)?;
129            Some(SubArg::Value(value))
130        } else if let Some(m) = c.name("width_arg") {
131            let s = m.as_str();
132            Some(if let Ok(idx) = s.parse() {
133                SubArg::Index(idx)
134            } else {
135                SubArg::Name(s)
136            })
137        } else {
138            None
139        };
140
141        let precision = if let Some(m) = c.name("precision_integer") {
142            let value = m.as_str().parse().map_err(|_| FormatParseError)?;
143            Some(SubArg::Value(value))
144        } else if let Some(m) = c.name("precision_arg") {
145            let s = m.as_str();
146            Some(if let Ok(idx) = s.parse() {
147                SubArg::Index(idx)
148            } else {
149                SubArg::Name(s)
150            })
151        } else if c.name("precision_input").is_some() {
152            Some(SubArg::Input)
153        } else {
154            None
155        };
156        let format_type = c.name("format_type").unwrap().as_str().parse()?;
157
158        Ok(FormatSpec {
159            fill,
160            align,
161            sign,
162            is_alternate,
163            is_zero,
164            width,
165            precision,
166            format_type,
167        })
168    }
169}
170
171impl FromStr for Align {
172    type Err = FormatParseError;
173    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
174        Ok(match s {
175            "<" => Align::Left,
176            ">" => Align::Right,
177            "^" => Align::Center,
178            _ => return Err(FormatParseError),
179        })
180    }
181}
182
183impl FromStr for FormatType {
184    type Err = FormatParseError;
185    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
186        Ok(match s {
187            "" => FormatType::Display,
188            "?" => FormatType::Debug,
189            "x?" => FormatType::DebugLowerHex,
190            "X?" => FormatType::DebugUpperHex,
191            "o" => FormatType::Octal,
192            "x" => FormatType::LowerHex,
193            "X" => FormatType::UpperHex,
194            "p" => FormatType::Pointer,
195            "b" => FormatType::Binary,
196            "e" => FormatType::LowerExp,
197            "E" => FormatType::UpperExp,
198            _ => return Err(FormatParseError),
199        })
200    }
201}
202impl Display for FormatType {
203    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
204        match self {
205            FormatType::Display => write!(f, ""),
206            FormatType::Debug => write!(f, "?"),
207            FormatType::DebugLowerHex => write!(f, "x?"),
208            FormatType::DebugUpperHex => write!(f, "X?"),
209            FormatType::Octal => write!(f, "o"),
210            FormatType::LowerHex => write!(f, "x"),
211            FormatType::UpperHex => write!(f, "X"),
212            FormatType::Pointer => write!(f, "p"),
213            FormatType::Binary => write!(f, "b"),
214            FormatType::LowerExp => write!(f, "e"),
215            FormatType::UpperExp => write!(f, "E"),
216        }
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223
224    #[test]
225    fn align() {
226        assert_ps(
227            "<",
228            FormatSpec {
229                align: Some(Align::Left),
230                ..Default::default()
231            },
232        );
233        assert_ps(
234            "^",
235            FormatSpec {
236                align: Some(Align::Center),
237                ..Default::default()
238            },
239        );
240        assert_ps(
241            ">",
242            FormatSpec {
243                align: Some(Align::Right),
244                ..Default::default()
245            },
246        );
247    }
248
249    #[test]
250    fn fill_align() {
251        assert_ps(
252            "x<",
253            FormatSpec {
254                fill: Some('x'),
255                align: Some(Align::Left),
256                ..Default::default()
257            },
258        );
259        assert_ps(
260            "0>",
261            FormatSpec {
262                fill: Some('0'),
263                align: Some(Align::Right),
264                ..Default::default()
265            },
266        );
267    }
268
269    #[test]
270    fn sign() {
271        assert_ps(
272            "+",
273            FormatSpec {
274                sign: Some(Sign::Plus),
275                ..Default::default()
276            },
277        );
278        assert_ps(
279            "-",
280            FormatSpec {
281                sign: Some(Sign::Minus),
282                ..Default::default()
283            },
284        );
285    }
286    #[test]
287    fn alternate() {
288        assert_ps(
289            "#",
290            FormatSpec {
291                is_alternate: true,
292                ..Default::default()
293            },
294        );
295    }
296
297    #[test]
298    fn zero() {
299        assert_ps(
300            "0",
301            FormatSpec {
302                is_zero: true,
303                ..Default::default()
304            },
305        );
306    }
307
308    #[test]
309    fn width_value() {
310        assert_ps(
311            "5",
312            FormatSpec {
313                width: Some(SubArg::Value(5)),
314                ..Default::default()
315            },
316        );
317    }
318
319    #[test]
320    fn width_arg_index() {
321        assert_ps(
322            "5$",
323            FormatSpec {
324                width: Some(SubArg::Index(5)),
325                ..Default::default()
326            },
327        );
328    }
329
330    #[test]
331    fn width_arg_name() {
332        assert_ps(
333            "field$",
334            FormatSpec {
335                width: Some(SubArg::Name("field")),
336                ..Default::default()
337            },
338        );
339    }
340
341    #[test]
342    fn zero_width() {
343        assert_ps(
344            "05",
345            FormatSpec {
346                is_zero: true,
347                width: Some(SubArg::Value(5)),
348                ..Default::default()
349            },
350        );
351    }
352
353    #[test]
354    fn precision_value() {
355        assert_ps(
356            ".5",
357            FormatSpec {
358                precision: Some(SubArg::Value(5)),
359                ..Default::default()
360            },
361        );
362    }
363
364    #[test]
365    fn precision_arg_index() {
366        assert_ps(
367            ".5$",
368            FormatSpec {
369                precision: Some(SubArg::Index(5)),
370                ..Default::default()
371            },
372        );
373    }
374
375    #[test]
376    fn precision_arg_name() {
377        assert_ps(
378            ".field$",
379            FormatSpec {
380                precision: Some(SubArg::Name("field")),
381                ..Default::default()
382            },
383        );
384    }
385
386    #[test]
387    fn precision_arg_input() {
388        assert_ps(
389            ".*",
390            FormatSpec {
391                precision: Some(SubArg::Input),
392                ..Default::default()
393            },
394        );
395    }
396
397    #[test]
398    fn format_type() {
399        assert_ps(
400            "?",
401            FormatSpec {
402                format_type: FormatType::Debug,
403                ..Default::default()
404            },
405        );
406        assert_ps(
407            "x?",
408            FormatSpec {
409                format_type: FormatType::DebugLowerHex,
410                ..Default::default()
411            },
412        );
413        assert_ps(
414            "x",
415            FormatSpec {
416                format_type: FormatType::LowerHex,
417                ..Default::default()
418            },
419        );
420        assert_ps(
421            "X",
422            FormatSpec {
423                format_type: FormatType::UpperHex,
424                ..Default::default()
425            },
426        );
427        assert_ps(
428            "b",
429            FormatSpec {
430                format_type: FormatType::Binary,
431                ..Default::default()
432            },
433        );
434    }
435
436    #[test]
437    fn all() {
438        assert_ps(
439            "_>+#05$.name$x?",
440            FormatSpec {
441                fill: Some('_'),
442                align: Some(Align::Right),
443                sign: Some(Sign::Plus),
444                is_alternate: true,
445                is_zero: true,
446                width: Some(SubArg::Index(5)),
447                precision: Some(SubArg::Name("name")),
448                format_type: FormatType::DebugLowerHex,
449            },
450        );
451    }
452
453    fn assert_ps<'a>(s: &'a str, ps: FormatSpec<'a>) {
454        assert_eq!(FormatSpec::parse(s), Ok(ps), "input : {s}");
455    }
456}