1use std::collections::HashSet;
19
20use nom::{
21 branch::alt,
22 bytes::complete::{tag, take_till1, take_while},
23 character::complete::{alpha1, alphanumeric1, anychar, digit1},
24 combinator::{map, map_res, opt, recognize, value},
25 multi::{many0, many0_count},
26 sequence::pair,
27 IResult,
28};
29
30use crate::{
31 fixed_width, precision, Alignment, Argument, ConversionSpec, Flag, FormatFragment,
32 FormatString, MinFieldWidth, Precision, Primitive, Style,
33};
34
35fn named_argument(input: &str) -> IResult<&str, Argument> {
37 let (input, ident) = recognize(pair(
38 alt((alpha1, tag("_"))),
39 many0_count(alt((alphanumeric1, tag("_")))),
40 ))(input)?;
41
42 Ok((input, Argument::Named(ident.to_string())))
43}
44
45fn positional_argument(input: &str) -> IResult<&str, Argument> {
47 let (input, index) = map_res(digit1, |val: &str| val.parse::<usize>())(input)?;
48
49 Ok((input, Argument::Positional(index)))
50}
51
52fn none_argument(input: &str) -> IResult<&str, Argument> {
56 Ok((input, Argument::None))
57}
58
59fn argument(input: &str) -> IResult<&str, Argument> {
63 alt((named_argument, positional_argument, none_argument))(input)
64}
65
66fn explicit_type(input: &str) -> IResult<&str, Style> {
70 alt((
71 value(Style::Debug, tag("?")),
72 value(Style::HexDebug, tag("x?")),
73 value(Style::UpperHexDebug, tag("X?")),
74 value(Style::Octal, tag("o")),
75 value(Style::Hex, tag("x")),
76 value(Style::UpperHex, tag("X")),
77 value(Style::Pointer, tag("p")),
78 value(Style::Binary, tag("b")),
79 value(Style::Exponential, tag("e")),
80 value(Style::UpperExponential, tag("E")),
81 ))(input)
82}
83
84fn style(input: &str) -> IResult<&str, Style> {
88 let (input, spec) = explicit_type(input).unwrap_or((input, Style::None));
89
90 Ok((input, spec))
91}
92
93fn map_flag(value: char) -> Result<Flag, String> {
95 match value {
96 '-' => Ok(Flag::LeftJustify),
97 '+' => Ok(Flag::ForceSign),
98 '#' => Ok(Flag::AlternateSyntax),
99 '0' => Ok(Flag::LeadingZeros),
100 _ => Err(format!("Unsupported flag '{}'", value)),
101 }
102}
103
104fn flags(input: &str) -> IResult<&str, HashSet<Flag>> {
106 let (input, flags) = many0(map_res(anychar, map_flag))(input)?;
107
108 Ok((input, flags.into_iter().collect()))
109}
110
111fn map_alignment(value: char) -> Result<Alignment, String> {
112 match value {
113 '<' => Ok(Alignment::Left),
114 '^' => Ok(Alignment::Center),
115 '>' => Ok(Alignment::Right),
116 _ => Err(format!("Unsupported alignment '{}'", value)),
117 }
118}
119
120fn bare_alignment(input: &str) -> IResult<&str, Alignment> {
122 map_res(anychar, map_alignment)(input)
123}
124
125fn fill_and_alignment(input: &str) -> IResult<&str, (char, Alignment)> {
127 let (input, fill) = anychar(input)?;
128 let (input, alignment) = bare_alignment(input)?;
129
130 Ok((input, (fill, alignment)))
131}
132
133fn alignment(input: &str) -> IResult<&str, (char, Alignment)> {
135 if let Ok((input, (fill, alignment))) = fill_and_alignment(input) {
139 return Ok((input, (fill, alignment)));
140 }
141
142 if let Ok((input, alignment)) = bare_alignment(input) {
145 return Ok((input, (' ', alignment)));
146 }
147
148 Ok((input, (' ', Alignment::None)))
150}
151
152fn format_spec(input: &str) -> IResult<&str, ConversionSpec> {
154 let (input, _) = tag(":")(input)?;
155 let (input, (fill, alignment)) = alignment(input)?;
156 let (input, flags) = flags(input)?;
157 let (input, width) = opt(fixed_width)(input)?;
158 let (input, precision) = precision(input)?;
159 let (input, style) = style(input)?;
160
161 Ok((
162 input,
163 ConversionSpec {
164 argument: Argument::None, fill,
166 alignment,
167 flags,
168 min_field_width: width.unwrap_or(MinFieldWidth::None),
169 precision,
170 length: None,
171 primitive: Primitive::Untyped, style,
173 },
174 ))
175}
176
177fn conversion(input: &str) -> IResult<&str, ConversionSpec> {
179 let (input, _) = tag("{")(input)?;
180 let (input, argument) = argument(input)?;
181 let (input, spec) = opt(format_spec)(input)?;
182 let (input, _) = take_while(|c: char| c.is_whitespace())(input)?;
186 let (input, _) = tag("}")(input)?;
187
188 let mut spec = spec.unwrap_or_else(|| ConversionSpec {
189 argument: Argument::None,
190 fill: ' ',
191 alignment: Alignment::None,
192 flags: HashSet::new(),
193 min_field_width: MinFieldWidth::None,
194 precision: Precision::None,
195 length: None,
196 primitive: Primitive::Untyped,
197 style: Style::None,
198 });
199
200 spec.argument = argument;
201
202 Ok((input, spec))
203}
204
205fn literal_fragment(input: &str) -> IResult<&str, FormatFragment> {
207 map(take_till1(|c| c == '{' || c == '}'), |s: &str| {
208 FormatFragment::Literal(s.to_string())
209 })(input)
210}
211
212fn escape_fragment(input: &str) -> IResult<&str, FormatFragment> {
214 alt((
215 map(tag("{{"), |_| FormatFragment::Literal("{".to_string())),
216 map(tag("}}"), |_| FormatFragment::Literal("}".to_string())),
217 ))(input)
218}
219
220fn conversion_fragment(input: &str) -> IResult<&str, FormatFragment> {
222 map(conversion, FormatFragment::Conversion)(input)
223}
224
225fn fragment(input: &str) -> IResult<&str, FormatFragment> {
227 alt((escape_fragment, conversion_fragment, literal_fragment))(input)
228}
229
230pub(crate) fn format_string(input: &str) -> IResult<&str, FormatString> {
232 let (input, fragments) = many0(fragment)(input)?;
233
234 Ok((input, FormatString::from_fragments(&fragments)))
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn type_parses_correctly() {
243 assert_eq!(style(""), Ok(("", Style::None)));
244 assert_eq!(style("?"), Ok(("", Style::Debug)));
245 assert_eq!(style("x?"), Ok(("", Style::HexDebug)));
246 assert_eq!(style("X?"), Ok(("", Style::UpperHexDebug)));
247 assert_eq!(style("o"), Ok(("", Style::Octal)));
248 }
249
250 #[test]
251 fn flags_prase_correctly() {
252 assert_eq!(
253 flags("0"),
254 Ok(("", vec![Flag::LeadingZeros].into_iter().collect()))
255 );
256 assert_eq!(
257 flags("-"),
258 Ok(("", vec![Flag::LeftJustify].into_iter().collect()))
259 );
260 assert_eq!(
261 flags("+"),
262 Ok(("", vec![Flag::ForceSign].into_iter().collect()))
263 );
264 assert_eq!(
265 flags("#"),
266 Ok(("", vec![Flag::AlternateSyntax].into_iter().collect()))
267 );
268
269 assert_eq!(
270 flags("+#0"),
271 Ok((
272 "",
273 vec![Flag::ForceSign, Flag::AlternateSyntax, Flag::LeadingZeros]
274 .into_iter()
275 .collect()
276 ))
277 );
278
279 assert_eq!(flags(" "), Ok((" ", HashSet::new())));
281 }
282
283 #[test]
284 fn alignment_parses_correctly() {
285 assert_eq!(alignment(""), Ok(("", (' ', Alignment::None))));
287
288 assert_eq!(alignment("<"), Ok(("", (' ', Alignment::Left))));
290 assert_eq!(alignment("^"), Ok(("", (' ', Alignment::Center))));
291 assert_eq!(alignment(">"), Ok(("", (' ', Alignment::Right))));
292
293 assert_eq!(alignment("-<"), Ok(("", ('-', Alignment::Left))));
295 assert_eq!(alignment("-^"), Ok(("", ('-', Alignment::Center))));
296 assert_eq!(alignment("->"), Ok(("", ('-', Alignment::Right))));
297
298 assert_eq!(alignment("><"), Ok(("", ('>', Alignment::Left))));
300 assert_eq!(alignment("^^"), Ok(("", ('^', Alignment::Center))));
301 assert_eq!(alignment("<>"), Ok(("", ('<', Alignment::Right))));
302
303 assert_eq!(alignment("1234"), Ok(("1234", (' ', Alignment::None))));
305 }
306
307 #[test]
308 fn empty_conversion_spec_has_sensible_defaults() {
309 assert_eq!(
310 conversion("{}"),
311 Ok((
312 "",
313 ConversionSpec {
314 argument: Argument::None,
315 fill: ' ',
316 alignment: Alignment::None,
317 flags: HashSet::new(),
318 min_field_width: MinFieldWidth::None,
319 precision: Precision::None,
320 length: None,
321 primitive: Primitive::Untyped,
322 style: Style::None,
323 }
324 ))
325 );
326 }
327}