pw_format/
printf.rs

1// Copyright 2024 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15use 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)), // ll must precede l
79        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, // Printf style strings do not support arguments.
100            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        // Parse all the flags
192        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        // Parse all the flags but reversed.  Should produce the same set.
209        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        // Non-flag characters after flags are not parsed.
226        assert_eq!(
227            flags("0d"),
228            Ok(("d", vec![Flag::LeadingZeros,].into_iter().collect()))
229        );
230
231        // No flag characters returns empty set.
232        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}