1use std::collections::HashSet;
16
17use nom::{
18 branch::alt,
19 bytes::complete::tag,
20 bytes::complete::take_till1,
21 character::complete::anychar,
22 combinator::{map, map_res},
23 multi::many0,
24 IResult,
25};
26
27use crate::{
28 precision, width, Alignment, Argument, ConversionSpec, Flag, FormatFragment, FormatString,
29 Length, Primitive, Style,
30};
31
32fn map_specifier(value: char) -> Result<(Primitive, Style), String> {
33 match value {
34 'd' | 'i' => Ok((Primitive::Integer, Style::None)),
35 'o' => Ok((Primitive::Unsigned, Style::Octal)),
36 'u' => Ok((Primitive::Unsigned, Style::None)),
37 'x' => Ok((Primitive::Unsigned, Style::Hex)),
38 'X' => Ok((Primitive::Unsigned, Style::UpperHex)),
39 'f' => Ok((Primitive::Float, Style::None)),
40 'e' => Ok((Primitive::Float, Style::Exponential)),
41 'E' => Ok((Primitive::Float, Style::UpperExponential)),
42 'c' => Ok((Primitive::Character, Style::None)),
43 's' => Ok((Primitive::String, Style::None)),
44 'p' => Ok((Primitive::Pointer, Style::Pointer)),
45 'v' => Ok((Primitive::Untyped, Style::None)),
46 'F' => Err("%F is not supported because it does not have a core::fmt analog".to_string()),
47 'g' => Err("%g is not supported because it does not have a core::fmt analog".to_string()),
48 'G' => Err("%G is not supported because it does not have a core::fmt analog".to_string()),
49 _ => Err(format!("Unsupported format specifier '{}'", value)),
50 }
51}
52
53fn specifier(input: &str) -> IResult<&str, (Primitive, Style)> {
54 map_res(anychar, map_specifier)(input)
55}
56
57fn map_flag(value: char) -> Result<Flag, String> {
58 match value {
59 '-' => Ok(Flag::LeftJustify),
60 '+' => Ok(Flag::ForceSign),
61 ' ' => Ok(Flag::SpaceSign),
62 '#' => Ok(Flag::AlternateSyntax),
63 '0' => Ok(Flag::LeadingZeros),
64 _ => Err(format!("Unsupported flag '{}'", value)),
65 }
66}
67
68fn flags(input: &str) -> IResult<&str, HashSet<Flag>> {
69 let (input, flags) = many0(map_res(anychar, map_flag))(input)?;
70
71 Ok((input, flags.into_iter().collect()))
72}
73
74fn length(input: &str) -> IResult<&str, Option<Length>> {
75 alt((
76 map(tag("hh"), |_| Some(Length::Char)),
77 map(tag("h"), |_| Some(Length::Short)),
78 map(tag("ll"), |_| Some(Length::LongLong)), map(tag("l"), |_| Some(Length::Long)),
80 map(tag("L"), |_| Some(Length::LongDouble)),
81 map(tag("j"), |_| Some(Length::IntMax)),
82 map(tag("z"), |_| Some(Length::Size)),
83 map(tag("t"), |_| Some(Length::PointerDiff)),
84 map(tag(""), |_| None),
85 ))(input)
86}
87
88fn conversion_spec(input: &str) -> IResult<&str, ConversionSpec> {
89 let (input, _) = tag("%")(input)?;
90 let (input, flags) = flags(input)?;
91 let (input, width) = width(input)?;
92 let (input, precision) = precision(input)?;
93 let (input, length) = length(input)?;
94 let (input, (primitive, style)) = specifier(input)?;
95
96 Ok((
97 input,
98 ConversionSpec {
99 argument: Argument::None, fill: ' ',
101 alignment: if flags.contains(&Flag::LeftJustify) {
102 Alignment::Left
103 } else {
104 Alignment::None
105 },
106 flags,
107 min_field_width: width,
108 precision,
109 length,
110 primitive,
111 style,
112 },
113 ))
114}
115
116fn literal_fragment(input: &str) -> IResult<&str, FormatFragment> {
117 map(take_till1(|c| c == '%'), |s: &str| {
118 FormatFragment::Literal(s.to_string())
119 })(input)
120}
121
122fn percent_fragment(input: &str) -> IResult<&str, FormatFragment> {
123 map(tag("%%"), |_| FormatFragment::Literal("%".to_string()))(input)
124}
125
126fn conversion_fragment(input: &str) -> IResult<&str, FormatFragment> {
127 map(conversion_spec, FormatFragment::Conversion)(input)
128}
129
130fn fragment(input: &str) -> IResult<&str, FormatFragment> {
131 alt((percent_fragment, conversion_fragment, literal_fragment))(input)
132}
133
134pub(crate) fn format_string(input: &str) -> IResult<&str, FormatString> {
135 let (input, fragments) = many0(fragment)(input)?;
136
137 Ok((input, FormatString::from_fragments(&fragments)))
138}
139
140#[cfg(test)]
141mod tests {
142 use crate::{MinFieldWidth, Precision};
143
144 use super::*;
145
146 #[test]
147 fn test_specifier() {
148 assert_eq!(specifier("d"), Ok(("", (Primitive::Integer, Style::None))));
149 assert_eq!(specifier("i"), Ok(("", (Primitive::Integer, Style::None))));
150 assert_eq!(
151 specifier("o"),
152 Ok(("", (Primitive::Unsigned, Style::Octal)))
153 );
154 assert_eq!(specifier("u"), Ok(("", (Primitive::Unsigned, Style::None))));
155 assert_eq!(specifier("x"), Ok(("", (Primitive::Unsigned, Style::Hex))));
156 assert_eq!(
157 specifier("X"),
158 Ok(("", (Primitive::Unsigned, Style::UpperHex)))
159 );
160 assert_eq!(specifier("f"), Ok(("", (Primitive::Float, Style::None))));
161 assert_eq!(
162 specifier("e"),
163 Ok(("", (Primitive::Float, Style::Exponential)))
164 );
165 assert_eq!(
166 specifier("E"),
167 Ok(("", (Primitive::Float, Style::UpperExponential)))
168 );
169 assert_eq!(
170 specifier("c"),
171 Ok(("", (Primitive::Character, Style::None)))
172 );
173 assert_eq!(specifier("s"), Ok(("", (Primitive::String, Style::None))));
174 assert_eq!(
175 specifier("p"),
176 Ok(("", (Primitive::Pointer, Style::Pointer)))
177 );
178 assert_eq!(specifier("v"), Ok(("", (Primitive::Untyped, Style::None))));
179
180 assert_eq!(
181 specifier("z"),
182 Err(nom::Err::Error(nom::error::Error {
183 input: "z",
184 code: nom::error::ErrorKind::MapRes
185 }))
186 );
187 }
188
189 #[test]
190 fn test_flags() {
191 assert_eq!(
193 flags("-+ #0"),
194 Ok((
195 "",
196 vec![
197 Flag::LeftJustify,
198 Flag::ForceSign,
199 Flag::SpaceSign,
200 Flag::AlternateSyntax,
201 Flag::LeadingZeros,
202 ]
203 .into_iter()
204 .collect()
205 ))
206 );
207
208 assert_eq!(
210 flags("0# +-"),
211 Ok((
212 "",
213 vec![
214 Flag::LeftJustify,
215 Flag::ForceSign,
216 Flag::SpaceSign,
217 Flag::AlternateSyntax,
218 Flag::LeadingZeros,
219 ]
220 .into_iter()
221 .collect()
222 ))
223 );
224
225 assert_eq!(
227 flags("0d"),
228 Ok(("d", vec![Flag::LeadingZeros,].into_iter().collect()))
229 );
230
231 assert_eq!(flags("d"), Ok(("d", vec![].into_iter().collect())));
233 }
234
235 #[test]
236 fn test_width() {
237 assert_eq!(
238 width("1234567890d"),
239 Ok(("d", MinFieldWidth::Fixed(1234567890)))
240 );
241 assert_eq!(width("*d"), Ok(("d", MinFieldWidth::Variable)));
242 assert_eq!(width("d"), Ok(("d", MinFieldWidth::None)));
243 }
244
245 #[test]
246 fn test_precision() {
247 assert_eq!(
248 precision(".1234567890f"),
249 Ok(("f", Precision::Fixed(1234567890)))
250 );
251 assert_eq!(precision(".*f"), Ok(("f", Precision::Variable)));
252 assert_eq!(precision("f"), Ok(("f", Precision::None)));
253 }
254 #[test]
255 fn test_length() {
256 assert_eq!(length("hhd"), Ok(("d", Some(Length::Char))));
257 assert_eq!(length("hd"), Ok(("d", Some(Length::Short))));
258 assert_eq!(length("ld"), Ok(("d", Some(Length::Long))));
259 assert_eq!(length("lld"), Ok(("d", Some(Length::LongLong))));
260 assert_eq!(length("Lf"), Ok(("f", Some(Length::LongDouble))));
261 assert_eq!(length("jd"), Ok(("d", Some(Length::IntMax))));
262 assert_eq!(length("zd"), Ok(("d", Some(Length::Size))));
263 assert_eq!(length("td"), Ok(("d", Some(Length::PointerDiff))));
264 assert_eq!(length("d"), Ok(("d", None)));
265 }
266
267 #[test]
268 fn test_conversion_spec() {
269 assert_eq!(
270 conversion_spec("%d"),
271 Ok((
272 "",
273 ConversionSpec {
274 argument: Argument::None,
275 fill: ' ',
276 alignment: Alignment::None,
277 flags: [].into_iter().collect(),
278 min_field_width: MinFieldWidth::None,
279 precision: Precision::None,
280 length: None,
281 primitive: Primitive::Integer,
282 style: Style::None,
283 }
284 ))
285 );
286
287 assert_eq!(
288 conversion_spec("%04ld"),
289 Ok((
290 "",
291 ConversionSpec {
292 argument: Argument::None,
293 fill: ' ',
294 alignment: Alignment::None,
295 flags: [Flag::LeadingZeros].into_iter().collect(),
296 min_field_width: MinFieldWidth::Fixed(4),
297 precision: Precision::None,
298 length: Some(Length::Long),
299 primitive: Primitive::Integer,
300 style: Style::None,
301 }
302 ))
303 );
304 }
305
306 #[test]
307 fn test_format_string() {
308 assert_eq!(
309 format_string("long double %+ 4.2Lf is %-03hd%%."),
310 Ok((
311 "",
312 FormatString {
313 fragments: vec![
314 FormatFragment::Literal("long double ".to_string()),
315 FormatFragment::Conversion(ConversionSpec {
316 argument: Argument::None,
317 fill: ' ',
318 alignment: Alignment::None,
319 flags: [Flag::ForceSign, Flag::SpaceSign].into_iter().collect(),
320 min_field_width: MinFieldWidth::Fixed(4),
321 precision: Precision::Fixed(2),
322 length: Some(Length::LongDouble),
323 primitive: Primitive::Float,
324 style: Style::None,
325 }),
326 FormatFragment::Literal(" is ".to_string()),
327 FormatFragment::Conversion(ConversionSpec {
328 argument: Argument::None,
329 fill: ' ',
330 alignment: Alignment::Left,
331 flags: [Flag::LeftJustify, Flag::LeadingZeros]
332 .into_iter()
333 .collect(),
334 min_field_width: MinFieldWidth::Fixed(3),
335 precision: Precision::None,
336 length: Some(Length::Short),
337 primitive: Primitive::Integer,
338 style: Style::None,
339 }),
340 FormatFragment::Literal("%.".to_string()),
341 ]
342 }
343 ))
344 );
345 }
346}