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}`"), }
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}