Skip to main content

pw_format/
format_string.rs

1// Copyright 2023 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
17#[cfg(feature = "proc_macro")]
18use quote::{quote, ToTokens};
19
20use crate::{core_fmt, printf};
21
22/// Primitive type of a conversion (integer, float, string, etc.)
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum Primitive {
25    /// Signed integer primitive.
26    Integer,
27
28    /// Unsigned integer primitive.
29    Unsigned,
30
31    /// Floating point primitive.
32    Float,
33
34    /// String primitive.
35    String,
36
37    /// Character primitive.
38    Character,
39
40    /// Pointer primitive.
41    Pointer,
42
43    /// Untyped primitive.
44    Untyped,
45}
46
47/// The abstract formatting style for a conversion.
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum Style {
50    /// No style specified, use defaults.
51    None,
52
53    /// Octal rendering (i.e. "%o" or "{:o}").
54    Octal,
55
56    /// Hexadecimal rendering (i.e. "%x" or "{:x}").
57    Hex,
58
59    /// Upper case hexadecimal rendering (i.e. "%X" or "{:X}").
60    UpperHex,
61
62    /// Exponential rendering (i.e. "%e" or "{:e}".
63    Exponential,
64
65    /// Upper case exponential rendering (i.e. "%E" or "{:E}".
66    UpperExponential,
67
68    /// Pointer type rendering (i.e. "%p" or "{:p}").
69    Pointer,
70
71    /// `core::fmt`'s `{:?}`
72    Debug,
73
74    /// `core::fmt`'s `{:x?}`
75    HexDebug,
76
77    /// `core::fmt`'s `{:X?}`
78    UpperHexDebug,
79
80    /// Unsupported binary rendering
81    ///
82    /// This variant exists so that the proc macros can give useful error
83    /// messages.
84    Binary,
85}
86
87/// Implemented for testing through the pw_format_test_macros crate.
88#[cfg(feature = "proc_macro")]
89impl ToTokens for Style {
90    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
91        let new_tokens = match self {
92            Style::None => quote!(pw_format::Style::None),
93            Style::Octal => quote!(pw_format::Style::Octal),
94            Style::Hex => quote!(pw_format::Style::Hex),
95            Style::UpperHex => quote!(pw_format::Style::UpperHex),
96            Style::Exponential => quote!(pw_format::Style::Exponential),
97            Style::UpperExponential => quote!(pw_format::Style::UpperExponential),
98            Style::Debug => quote!(pw_format::Style::Debug),
99            Style::HexDebug => quote!(pw_format::Style::HexDebug),
100            Style::UpperHexDebug => quote!(pw_format::Style::UpperHexDebug),
101            Style::Pointer => quote!(pw_format::Style::Pointer),
102            Style::Binary => quote!(pw_format::Style::Binary),
103        };
104        new_tokens.to_tokens(tokens);
105    }
106}
107
108/// A printf flag (the '+' in %+d).
109#[derive(Clone, Debug, Hash, PartialEq, Eq)]
110pub enum Flag {
111    /// `-`
112    LeftJustify,
113
114    /// `+`
115    ForceSign,
116
117    /// ` `
118    SpaceSign,
119
120    /// `#`
121    AlternateSyntax,
122
123    /// `0`
124    LeadingZeros,
125}
126
127/// A printf minimum field width (the 5 in %5d).
128#[derive(Clone, Debug, PartialEq, Eq)]
129pub enum MinFieldWidth {
130    /// No field width specified.
131    None,
132
133    /// Fixed field with.
134    Fixed(u32),
135
136    /// Variable field width passed as an argument (i.e. %*d).
137    Variable,
138}
139
140/// A printf precision (the .5 in %.5d).
141///
142/// For string conversions (%s) this is treated as the maximum number of
143/// bytes of the string to output.
144#[derive(Clone, Debug, PartialEq, Eq)]
145pub enum Precision {
146    /// No precision specified.
147    None,
148
149    /// Fixed precision.
150    Fixed(u32),
151
152    /// Variable precision passed as an argument (i.e. %.*f).
153    Variable,
154}
155
156/// A printf length (the l in %ld).
157#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158pub enum Length {
159    /// `hh`
160    Char,
161
162    /// `h`
163    Short,
164
165    /// `l`
166    Long,
167
168    /// `ll`
169    LongLong,
170
171    /// `L`
172    LongDouble,
173
174    /// `j`
175    IntMax,
176
177    /// `z`
178    Size,
179
180    /// `t`
181    PointerDiff,
182}
183
184/// A core::fmt alignment spec.
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
186pub enum Alignment {
187    /// No alignment
188    None,
189
190    /// Left alignment (`<`)
191    Left,
192
193    /// Center alignment (`^`)
194    Center,
195
196    /// Right alignment (`>`)
197    Right,
198}
199
200/// An argument in a core::fmt style alignment spec.
201///
202/// i.e. the var_name in `{var_name:#0x}`
203#[derive(Clone, Debug, PartialEq, Eq)]
204pub enum Argument {
205    /// No argument
206    None,
207
208    /// A positional argument (i.e. `{0}`).
209    Positional(usize),
210
211    /// A named argument (i.e. `{var_name}`).
212    Named(String),
213}
214
215/// A printf conversion specification aka a % clause.
216#[derive(Clone, Debug, PartialEq, Eq)]
217pub struct ConversionSpec {
218    /// ConversionSpec's argument.
219    pub argument: Argument,
220    /// ConversionSpec's fill character.
221    pub fill: char,
222    /// ConversionSpec's field alignment.
223    pub alignment: Alignment,
224    /// ConversionSpec's set of [Flag]s.
225    pub flags: HashSet<Flag>,
226    /// ConversionSpec's minimum field width argument.
227    pub min_field_width: MinFieldWidth,
228    /// ConversionSpec's [Precision] argument.
229    pub precision: Precision,
230    /// ConversionSpec's [Length] argument.
231    pub length: Option<Length>,
232    /// ConversionSpec's [Primitive].
233    pub primitive: Primitive,
234    /// ConversionSpec's [Style].
235    pub style: Style,
236}
237
238impl ConversionSpec {
239    /// Reconstructs the conversion specifier back to its printf format string representation (e.g., `%+05.2ld`).
240    #[must_use]
241    pub fn to_printf(&self) -> String {
242        let mut s = String::from("%");
243        if self.flags.contains(&Flag::LeftJustify) {
244            s.push('-');
245        }
246        if self.flags.contains(&Flag::ForceSign) {
247            s.push('+');
248        }
249        if self.flags.contains(&Flag::SpaceSign) {
250            s.push(' ');
251        }
252        if self.flags.contains(&Flag::AlternateSyntax) {
253            s.push('#');
254        }
255        if self.flags.contains(&Flag::LeadingZeros) {
256            s.push('0');
257        }
258
259        match self.min_field_width {
260            MinFieldWidth::None => {}
261            MinFieldWidth::Fixed(w) => s.push_str(&w.to_string()),
262            MinFieldWidth::Variable => s.push('*'),
263        }
264
265        match self.precision {
266            Precision::None => {}
267            Precision::Fixed(p) => s.push_str(&format!(".{p}")),
268            Precision::Variable => s.push_str(".*"),
269        }
270
271        if let Some(length) = self.length {
272            s.push_str(match length {
273                Length::Char => "hh",
274                Length::Short => "h",
275                Length::Long => "l",
276                Length::LongLong => "ll",
277                Length::LongDouble => "L",
278                Length::IntMax => "j",
279                Length::Size => "z",
280                Length::PointerDiff => "t",
281            });
282        }
283
284        let type_char = match (self.primitive, self.style) {
285            (Primitive::Integer, _) => 'd',
286            (Primitive::Unsigned, Style::Octal) => 'o',
287            (Primitive::Unsigned, Style::Hex) => 'x',
288            (Primitive::Unsigned, Style::UpperHex) => 'X',
289            (Primitive::Unsigned, _) => 'u',
290            (Primitive::Float, Style::Exponential) => 'e',
291            (Primitive::Float, Style::UpperExponential) => 'E',
292            (Primitive::Float, _) => 'f',
293            (Primitive::Character, _) => 'c',
294            (Primitive::String, _) => 's',
295            (Primitive::Pointer, _) => 'p',
296            (Primitive::Untyped, _) => 'v',
297        };
298        s.push(type_char);
299        s
300    }
301}
302
303/// A fragment of a printf format string.
304#[derive(Clone, Debug, PartialEq, Eq)]
305pub enum FormatFragment {
306    /// A literal string value.
307    Literal(String),
308
309    /// A conversion specification (i.e. %d).
310    Conversion(ConversionSpec),
311}
312
313impl FormatFragment {
314    /// Try to append `fragment` to `self`.
315    ///
316    /// Returns `None` if the appending succeeds and `Some<fragment>` if it fails.
317    fn try_append<'a>(&mut self, fragment: &'a FormatFragment) -> Option<&'a FormatFragment> {
318        let Self::Literal(literal_fragment) = &fragment else {
319            return Some(fragment);
320        };
321
322        let Self::Literal(literal_self) = self else {
323            return Some(fragment);
324        };
325
326        literal_self.push_str(literal_fragment);
327
328        None
329    }
330}
331
332/// Representation of a decoded argument.
333#[derive(Debug, Clone, PartialEq)]
334pub enum Arg {
335    /// Signed integer.
336    Int(i64),
337    /// Unsigned integer.
338    Uint(u64),
339    /// Floating point number.
340    Float(f64),
341    /// String.
342    Str(String),
343    /// Character.
344    Char(char),
345    /// Pointer.
346    Ptr(usize),
347}
348
349/// The style of formatting to apply (influences defaults).
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
351pub enum FormatStyle {
352    /// Printf style defaults (e.g. %f defaults to precision 6).
353    Printf,
354    /// Core::fmt style defaults.
355    CoreFmt,
356}
357
358/// A trait for formatting conversion specifiers that failed, were skipped, or were missing.
359pub trait FormatError {
360    /// The domain-specific error type.
361    type Error;
362
363    /// Renders a conversion specifier that failed with a domain-specific error.
364    fn format_error(&self, spec: &ConversionSpec, error: &Self::Error) -> String;
365
366    /// Renders a conversion specifier that was missing from the supplied arguments.
367    fn format_missing(&self, spec: &ConversionSpec) -> String;
368
369    /// Renders a conversion specifier that decoded successfully but failed to format (type mismatch).
370    fn format_type_error(&self, spec: &ConversionSpec, arg: &Arg) -> String;
371}
372
373/// Formatter that retains the original conversion specifier when formatting fails,
374/// parameterizing over `std::convert::Infallible` (which has no error state).
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376struct DefaultFormatter;
377
378impl FormatError for DefaultFormatter {
379    type Error = core::convert::Infallible;
380
381    fn format_error(&self, spec: &ConversionSpec, _error: &core::convert::Infallible) -> String {
382        spec.to_printf()
383    }
384    fn format_missing(&self, spec: &ConversionSpec) -> String {
385        spec.to_printf()
386    }
387    fn format_type_error(&self, spec: &ConversionSpec, _arg: &Arg) -> String {
388        spec.to_printf()
389    }
390}
391
392/// A parsed format string.
393#[derive(Clone, Debug, PartialEq, Eq)]
394pub struct FormatString {
395    /// The [FormatFragment]s that comprise the [FormatString].
396    pub fragments: Vec<FormatFragment>,
397}
398
399impl FormatString {
400    /// Formats a parsed format string with provided arguments.
401    #[must_use]
402    pub fn format(&self, args: &[Arg], style: FormatStyle) -> String {
403        let result_args: Vec<Result<Arg, core::convert::Infallible>> =
404            args.iter().map(|arg| Ok(arg.clone())).collect();
405
406        self.format_with_errors(&result_args, style, &DefaultFormatter)
407    }
408
409    /// Formats a parsed format string with the provided argument states (Result<Arg, FE::Error>),
410    /// delegating formatting of any failures, missing arguments, or type mismatches
411    /// to the provided `FormatError` implementation.
412    pub fn format_with_errors<FE: FormatError>(
413        &self,
414        args: &[Result<Arg, FE::Error>],
415        style: FormatStyle,
416        error_formatter: &FE,
417    ) -> String {
418        let mut output = String::new();
419        let mut args_iter = args.iter();
420
421        for fragment in &self.fragments {
422            self.format_fragment(
423                fragment,
424                &mut args_iter,
425                style,
426                error_formatter,
427                &mut output,
428            );
429        }
430
431        output
432    }
433
434    fn format_fragment<'a, FE: FormatError>(
435        &self,
436        fragment: &FormatFragment,
437        args: &mut impl Iterator<Item = &'a Result<Arg, FE::Error>>,
438        style: FormatStyle,
439        error_formatter: &FE,
440        output: &mut String,
441    ) where
442        FE::Error: 'a,
443    {
444        let spec = match fragment {
445            FormatFragment::Conversion(spec) => spec,
446            FormatFragment::Literal(s) => {
447                output.push_str(s);
448                return;
449            }
450        };
451
452        let Some(decoded) = args.next() else {
453            output.push_str(&error_formatter.format_missing(spec));
454            return;
455        };
456
457        let arg = match decoded {
458            Ok(arg) => arg,
459            Err(err) => {
460                output.push_str(&error_formatter.format_error(spec, err));
461                return;
462            }
463        };
464
465        let mut formatted = String::new();
466        match self.format_value(spec, arg, style, &mut formatted) {
467            Ok(()) => output.push_str(&formatted),
468            Err(_) => {
469                output.push_str(&error_formatter.format_type_error(spec, arg));
470            }
471        }
472    }
473
474    /// Parses a printf style format string.
475    pub fn parse_printf(s: &str) -> Result<Self, String> {
476        // TODO: b/281858500 - Add better errors to failed parses.
477        let (rest, result) = printf::format_string(s)
478            .map_err(|e| format!("Failed to parse format string \"{s}\": {e}"))?;
479
480        // If the parser did not consume all the input, return an error.
481        if !rest.is_empty() {
482            return Err(format!(
483                "Failed to parse format string fragment: \"{rest}\""
484            ));
485        }
486
487        Ok(result)
488    }
489
490    /// Parses a core::fmt style format string.
491    pub fn parse_core_fmt(s: &str) -> Result<Self, String> {
492        // TODO: b/281858500 - Add better errors to failed parses.
493        let (rest, result) = core_fmt::format_string(s)
494            .map_err(|e| format!("Failed to parse format string \"{s}\": {e}"))?;
495
496        // If the parser did not consume all the input, return an error.
497        if !rest.is_empty() {
498            return Err(format!("Failed to parse format string: \"{rest}\""));
499        }
500
501        Ok(result)
502    }
503
504    /// Creates a `FormatString` from a slice of fragments.
505    ///
506    /// This primary responsibility of this function is to merge literal
507    /// fragments.  Adjacent literal fragments occur when a parser parses
508    /// escape sequences.  Merging them here allows a
509    /// [`macros::FormatMacroGenerator`] to not worry about the escape codes.
510    pub(crate) fn from_fragments(fragments: &[FormatFragment]) -> Self {
511        Self {
512            fragments: fragments
513                .iter()
514                .fold(Vec::<_>::new(), |mut fragments, fragment| {
515                    // Collapse adjacent literal fragments.
516                    let Some(last) = fragments.last_mut() else {
517                        // If there are no accumulated fragments, add this one and return.
518                        fragments.push((*fragment).clone());
519                        return fragments;
520                    };
521                    if let Some(fragment) = last.try_append(fragment) {
522                        // If the fragments were able to append, no more work to do
523                        fragments.push((*fragment).clone());
524                    };
525                    fragments
526                }),
527        }
528    }
529
530    fn format_value(
531        &self,
532        spec: &ConversionSpec,
533        arg: &Arg,
534        style: FormatStyle,
535        output: &mut String,
536    ) -> Result<(), String> {
537        match (spec.primitive, arg) {
538            (Primitive::Integer, Arg::Int(v)) => self.format_int(*v, spec, style, output),
539            (Primitive::Unsigned, Arg::Uint(v)) => self.format_uint(*v, spec, style, output),
540            (Primitive::Float, Arg::Float(v)) => self.format_float(*v, spec, style, output),
541            (Primitive::String, Arg::Str(v)) => self.format_str(v, spec, style, output),
542            (Primitive::Character, Arg::Char(v)) => self.format_char(*v, spec, style, output),
543            (Primitive::Pointer, Arg::Ptr(v)) => self.format_ptr(*v, spec, style, output),
544            (Primitive::Untyped, _) => self.format_untyped(spec, arg, style, output),
545            _ => Err(format!(
546                "Mismatched type: expected {:?}, got {:?}",
547                spec.primitive, arg
548            )),
549        }
550    }
551
552    fn format_untyped(
553        &self,
554        spec: &ConversionSpec,
555        arg: &Arg,
556        style: FormatStyle,
557        output: &mut String,
558    ) -> Result<(), String> {
559        match arg {
560            Arg::Int(v) => self.format_int(*v, spec, style, output),
561            Arg::Uint(v) => self.format_uint(*v, spec, style, output),
562            Arg::Float(v) => self.format_float(*v, spec, style, output),
563            Arg::Str(v) => self.format_str(v, spec, style, output),
564            Arg::Char(v) => self.format_char(*v, spec, style, output),
565            Arg::Ptr(v) => self.format_ptr(*v, spec, style, output),
566        }
567    }
568
569    fn format_int_common(
570        &self,
571        v: u64,
572        sign: &str,
573        spec: &ConversionSpec,
574        output: &mut String,
575    ) -> Result<(), String> {
576        let (base_prefix, mut value) = match spec.style {
577            Style::Hex | Style::Pointer => ("0x", format!("{:x}", v)),
578            Style::UpperHex => ("0X", format!("{:X}", v)),
579            Style::Octal => ("0", format!("{:o}", v)),
580            _ => ("", format!("{}", v)),
581        };
582
583        if let Precision::Fixed(p) = spec.precision {
584            while value.len() < p as usize {
585                value.insert(0, '0');
586            }
587        }
588
589        let mut prefix = sign.to_string();
590        if spec.flags.contains(&Flag::AlternateSyntax) || spec.style == Style::Pointer {
591            // For octal, it's possible that the value string already starts with the prefix.
592            if !value.starts_with(base_prefix) {
593                prefix.push_str(base_prefix);
594            }
595        }
596
597        let s = self.apply_width_and_alignment(&prefix, &value, spec)?;
598        output.push_str(&s);
599        Ok(())
600    }
601
602    fn sign_prefix(&self, is_negative: bool, spec: &ConversionSpec) -> &'static str {
603        if is_negative {
604            "-"
605        } else if spec.flags.contains(&Flag::ForceSign) {
606            "+"
607        } else if spec.flags.contains(&Flag::SpaceSign) {
608            " "
609        } else {
610            ""
611        }
612    }
613
614    fn format_int(
615        &self,
616        v: i64,
617        spec: &ConversionSpec,
618        _style: FormatStyle,
619        output: &mut String,
620    ) -> Result<(), String> {
621        let sign = self.sign_prefix(v < 0, spec);
622        self.format_int_common(v.unsigned_abs(), sign, spec, output)
623    }
624
625    fn format_uint(
626        &self,
627        v: u64,
628        spec: &ConversionSpec,
629        _style: FormatStyle,
630        output: &mut String,
631    ) -> Result<(), String> {
632        self.format_int_common(v, "", spec, output)
633    }
634
635    fn format_float(
636        &self,
637        v: f64,
638        spec: &ConversionSpec,
639        style: FormatStyle,
640        output: &mut String,
641    ) -> Result<(), String> {
642        let abs_v = v.abs();
643        let value = match spec.precision {
644            Precision::Fixed(p) => format!("{:.1$}", abs_v, p as usize),
645            _ => match style {
646                FormatStyle::Printf => format!("{:.6}", abs_v),
647                FormatStyle::CoreFmt => format!("{}", abs_v),
648            },
649        };
650        let prefix = self.sign_prefix(v < 0.0 || v.is_sign_negative(), spec);
651
652        let s = self.apply_width_and_alignment(prefix, &value, spec)?;
653        output.push_str(&s);
654        Ok(())
655    }
656
657    fn format_str(
658        &self,
659        v: &str,
660        spec: &ConversionSpec,
661        _style: FormatStyle,
662        output: &mut String,
663    ) -> Result<(), String> {
664        let mut value = v.to_string();
665        if let Precision::Fixed(p) = spec.precision {
666            value.truncate(p as usize);
667        }
668        let s = self.apply_width_and_alignment("", &value, spec)?;
669        output.push_str(&s);
670        Ok(())
671    }
672
673    fn format_char(
674        &self,
675        v: char,
676        spec: &ConversionSpec,
677        _style: FormatStyle,
678        output: &mut String,
679    ) -> Result<(), String> {
680        let value = v.to_string();
681        let s = self.apply_width_and_alignment("", &value, spec)?;
682        output.push_str(&s);
683        Ok(())
684    }
685
686    fn format_ptr(
687        &self,
688        v: usize,
689        spec: &ConversionSpec,
690        _style: FormatStyle,
691        output: &mut String,
692    ) -> Result<(), String> {
693        self.format_int_common(v as u64, "", spec, output)
694    }
695
696    fn apply_width_and_alignment(
697        &self,
698        prefix: &str,
699        value: &str,
700        spec: &ConversionSpec,
701    ) -> Result<String, String> {
702        // If there is no fixed field width, format w/o padding.
703        // Variable field width is unsupported for now.
704        let MinFieldWidth::Fixed(w) = spec.min_field_width else {
705            return Ok(format!("{}{}", prefix, value));
706        };
707
708        let w = w as usize;
709        let total_len = prefix.len() + value.len();
710
711        // If the value overflows the minimum field width, format w/o padding.
712        if total_len >= w {
713            return Ok(format!("{}{}", prefix, value));
714        }
715
716        let pad_len = w - total_len;
717        let ignore_zero = spec.flags.contains(&Flag::LeftJustify)
718            || (matches!(spec.precision, Precision::Fixed(_))
719                && matches!(spec.primitive, Primitive::Integer | Primitive::Unsigned));
720        let do_zero_fill = spec.flags.contains(&Flag::LeadingZeros) && !ignore_zero;
721        let is_left_aligned =
722            spec.alignment == Alignment::Left || spec.flags.contains(&Flag::LeftJustify);
723
724        let mut s = String::with_capacity(w);
725        if is_left_aligned {
726            // Left justified values are never zero filled.
727            s.push_str(prefix);
728            s.push_str(value);
729            for _ in 0..pad_len {
730                s.push(spec.fill);
731            }
732        } else {
733            if do_zero_fill {
734                // Zero fill happens after the prefix like '0x001' or '-0001'.
735                s.push_str(prefix);
736                for _ in 0..pad_len {
737                    s.push('0');
738                }
739            } else {
740                // Normal fill happens after the prefix like '  0x1' or '  -01'.
741                for _ in 0..pad_len {
742                    s.push(spec.fill);
743                }
744                s.push_str(prefix);
745            }
746            s.push_str(value);
747        }
748        Ok(s)
749    }
750}