Skip to main content

pw_format/
macros.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
15//! The `macro` module provides helpers that simplify writing proc macros
16//! that take format strings and arguments.  This is accomplish with three
17//! main constructs:
18//! * [`FormatAndArgsFlavor`]: A struct that implements [syn::parse::Parse] to
19//!   parse a format string and its following arguments.
20//! * [`FormatMacroGenerator`]: A trait used to implement the macro specific
21//!   logic to generate code.
22//! * [`generate`]: A function to handle the execution of the proc macro by
23//!   calling into a [FormatMacroGenerator].
24//!
25//! Additionally [`PrintfFormatMacroGenerator`] trait and [`generate_printf`]
26//! function are provided to help when implementing generators that need to
27//! produce `printf` style format strings as part of their code generation.
28//!
29//! ## Example
30//!
31//! An example of implementing a proc macro is provided in the
32//! [pw_format_example_macro crate](https://pigweed.googlesource.com/pigweed/pigweed/+/refs/heads/main/pw_format/rust/pw_format_example_macro.rs)
33//!
34//!
35
36use core::marker::PhantomData;
37use std::collections::VecDeque;
38
39use proc_macro2::Ident;
40use quote::{format_ident, quote, ToTokens};
41use syn::parse::{Parse, ParseStream};
42use syn::punctuated::Punctuated;
43use syn::spanned::Spanned;
44use syn::{Expr, ExprCast, LitStr, Token};
45
46use crate::{
47    ConversionSpec, Flag, FormatFragment, FormatString, Length, MinFieldWidth, Precision,
48    Primitive, Style,
49};
50
51mod keywords {
52    syn::custom_keyword!(PW_FMT_CONCAT);
53}
54
55type TokenStream2 = proc_macro2::TokenStream;
56
57/// An error occurring during proc macro evaluation.
58///
59/// In order to stay as flexible as possible to implementors of
60/// [`FormatMacroGenerator`], the error is simply represent by a
61/// string.
62#[derive(Debug)]
63pub struct Error {
64    text: String,
65}
66
67impl Error {
68    /// Create a new proc macro evaluation error.
69    #[must_use]
70    pub fn new(text: &str) -> Self {
71        Self {
72            text: text.to_string(),
73        }
74    }
75}
76
77/// An alias for a Result with an ``Error``
78pub type Result<T> = core::result::Result<T, Error>;
79
80/// Formatting parameters passed to an untyped conversion.
81#[derive(Clone, Debug, PartialEq)]
82pub struct FormatParams {
83    /// Style in which to print the corresponding argument.
84    pub style: Style,
85
86    /// Minimum field width.  (i.e. the `8` in `{:08x}`).
87    pub min_field_width: Option<u32>,
88
89    /// Zero padding.  (i.e. the existence of `0` in `{:08x}`).
90    pub zero_padding: bool,
91
92    /// Alternate syntax.  (i.e. the existence of `#` in `{:#08x}`).
93    pub alternate_syntax: bool,
94}
95
96impl FormatParams {
97    fn printf_format_trait(&self) -> Result<Ident> {
98        match self.style {
99            Style::None => Ok(format_ident!("PrintfFormatter")),
100            Style::Hex => Ok(format_ident!("PrintfHexFormatter")),
101            Style::UpperHex => Ok(format_ident!("PrintfUpperHexFormatter")),
102            _ => Err(Error::new(&format!(
103                "formatting untyped conversions with {:?} style is unsupported",
104                self.style
105            ))),
106        }
107    }
108
109    fn field_params(&self) -> String {
110        let (zero_pad, min_field_width) = match self.min_field_width {
111            None => ("", "".to_string()),
112            Some(min_field_width) => (
113                if self.zero_padding { "0" } else { "" },
114                format!("{min_field_width}"),
115            ),
116        };
117
118        let alternate_syntax = if self.alternate_syntax { "#" } else { "" };
119
120        format!("{alternate_syntax}{zero_pad}{min_field_width}")
121    }
122
123    fn core_fmt_specifier(&self) -> Result<String> {
124        // If no formatting options are needed, omit the `:`.
125        if self.style == Style::None && self.min_field_width.is_none() {
126            return Ok("{}".to_string());
127        }
128
129        let format = match self.style {
130            Style::None => "",
131            Style::Octal => "o",
132            Style::Hex => "x",
133            Style::UpperHex => "X",
134            _ => {
135                return Err(Error::new(&format!(
136                    "formatting untyped conversions with {:?} style is unsupported",
137                    self.style
138                )));
139            }
140        };
141
142        let field_params = self.field_params();
143
144        Ok(format!("{{:{field_params}{format}}}"))
145    }
146}
147
148impl TryFrom<&ConversionSpec> for FormatParams {
149    type Error = Error;
150    fn try_from(spec: &ConversionSpec) -> core::result::Result<Self, Self::Error> {
151        let min_field_width = match spec.min_field_width {
152            MinFieldWidth::None => None,
153            MinFieldWidth::Fixed(len) => Some(len),
154            MinFieldWidth::Variable => {
155                return Err(Error::new(
156                    "Variable width '*' string formats are not supported.",
157                ));
158            }
159        };
160
161        Ok(FormatParams {
162            style: spec.style,
163            min_field_width,
164            zero_padding: spec.flags.contains(&Flag::LeadingZeros),
165            alternate_syntax: spec.flags.contains(&Flag::AlternateSyntax),
166        })
167    }
168}
169
170/// Implemented for testing through the pw_format_test_macros crate.
171impl ToTokens for FormatParams {
172    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
173        let style = self.style;
174        let min_field_width = match self.min_field_width {
175            None => quote! {None},
176            Some(val) => quote! {Some(#val)},
177        };
178        let zero_padding = self.zero_padding;
179        let alternate_syntax = self.alternate_syntax;
180
181        quote! {
182            pw_format::macros::FormatParams {
183                style: #style,
184                min_field_width: #min_field_width,
185                zero_padding: #zero_padding,
186                alternate_syntax: #alternate_syntax,
187            }
188        }
189        .to_tokens(tokens)
190    }
191}
192
193/// A code generator for implementing a `pw_format` style macro.
194///
195/// This trait serves as the primary interface between `pw_format` and a
196/// proc macro using it to implement format string and argument parsing.  When
197/// evaluating the proc macro and generating code, [`generate`] will make
198/// repeated calls to [`string_fragment`](FormatMacroGenerator::string_fragment)
199/// and the conversion functions.  These calls will be made in the order they
200/// appear in the format string.  After all fragments and conversions are
201/// processed, [`generate`] will call
202/// [`finalize`](FormatMacroGenerator::finalize).
203///
204/// For an example of implementing a `FormatMacroGenerator` see the
205/// [pw_format_example_macro crate](https://pigweed.googlesource.com/pigweed/pigweed/+/refs/heads/main/pw_format/rust/pw_format_example_macro.rs).
206pub trait FormatMacroGenerator {
207    /// Called by [`generate`] at the end of code generation.
208    ///
209    /// Consumes `self` and returns the code to be emitted by the proc macro of
210    /// and [`Error`].
211    fn finalize(self) -> Result<TokenStream2>;
212
213    /// Process a string fragment.
214    ///
215    /// A string fragment is a string of characters that appear in a format
216    /// string.  This is different than a
217    /// [`string_conversion`](FormatMacroGenerator::string_conversion) which is
218    /// a string provided through a conversion specifier (i.e. `"%s"`).
219    fn string_fragment(&mut self, string: &str) -> Result<()>;
220
221    /// Process an integer conversion.
222    fn integer_conversion(
223        &mut self,
224        params: &FormatParams,
225        signed: bool,
226        type_width: u8, // This should probably be an enum
227        expression: Arg,
228    ) -> Result<()>;
229
230    /// Process a string conversion.
231    ///
232    /// See [`string_fragment`](FormatMacroGenerator::string_fragment) for a
233    /// disambiguation between that function and this one.
234    fn string_conversion(&mut self, expression: Arg) -> Result<()>;
235
236    /// Process a character conversion.
237    fn char_conversion(&mut self, expression: Arg) -> Result<()>;
238
239    /// Process an untyped conversion.
240    fn untyped_conversion(&mut self, _expression: Arg, _params: &FormatParams) -> Result<()> {
241        Err(Error::new("untyped conversion (%v) not supported"))
242    }
243}
244
245/// An argument to a `pw_format` backed macro.
246///
247/// `pw_format` backed macros have special case recognition of type casts
248/// (`value as ty`) in order to annotate a type for typeless printing w/o
249/// relying on experimental features.  If an argument is given in that form,
250/// it will be represented as an [`Arg::ExprCast`] here.  Otherwise it will
251/// be an [`Arg::Expr`].
252#[derive(Clone, Debug)]
253pub enum Arg {
254    /// An argument that is an type cast expression.
255    ExprCast(ExprCast),
256    /// An argument that is an expression.
257    Expr(Expr),
258}
259
260impl Arg {
261    fn parse_expr(expr: Expr) -> syn::parse::Result<Self> {
262        match expr.clone() {
263            Expr::Cast(cast) => Ok(Self::ExprCast(cast)),
264
265            // Expr::Casts maybe be wrapped in an Expr::Group or in unexplained
266            // cases where macro expansion in the rust-analyzer VSCode plugin
267            // may cause them to be wrapped in an Expr::Paren instead.
268            Expr::Paren(paren) => Self::parse_expr(*paren.expr),
269            Expr::Group(group) => Self::parse_expr(*group.expr),
270
271            _ => Ok(Self::Expr(expr)),
272        }
273    }
274}
275
276impl Parse for Arg {
277    fn parse(input: ParseStream) -> syn::parse::Result<Self> {
278        Self::parse_expr(input.parse::<Expr>()?)
279    }
280}
281
282impl ToTokens for Arg {
283    fn to_tokens(&self, tokens: &mut TokenStream2) {
284        match self {
285            Self::Expr(expr) => expr.to_tokens(tokens),
286            Self::ExprCast(cast) => cast.to_tokens(tokens),
287        }
288    }
289}
290
291/// A trait for parsing a string into a [`FormatString`].
292pub trait FormatStringParser {
293    /// Parse `format_string` and return the results as a `[FormatString]`.
294    fn parse_format_string(format_string: &str) -> core::result::Result<FormatString, String>;
295}
296
297/// An implementation of [`FormatStringParser`] that parsers `printf` style format strings.
298#[derive(Debug)]
299pub struct PrintfFormatStringParser;
300impl FormatStringParser for PrintfFormatStringParser {
301    fn parse_format_string(format_string: &str) -> core::result::Result<FormatString, String> {
302        FormatString::parse_printf(format_string)
303    }
304}
305
306/// An implementation of [`FormatStringParser`] that parsers `core::fmt` style format strings.
307#[derive(Debug)]
308pub struct CoreFmtFormatStringParser;
309impl FormatStringParser for CoreFmtFormatStringParser {
310    fn parse_format_string(format_string: &str) -> core::result::Result<FormatString, String> {
311        FormatString::parse_core_fmt(format_string)
312    }
313}
314
315/// A parsed format string and it's arguments.
316///
317/// To parse a `FormatAndArgs`, use the [`FormatAndArgsFlavor`] variant which
318/// is generic over [`FormatStringParser`] to allow parsing either `printf` or
319/// `core::fmt` style format strings.
320///
321/// Arguments are parsed according to the pattern:
322/// `($format_string:literal, $($args:expr),*)`
323///
324/// To support uses where format strings need to be built by macros at compile
325/// time, the format string can be specified as a set of string literals
326/// separated by the custom `PW_FMT_CONCAT` keyword.
327#[derive(Debug)]
328pub struct FormatAndArgs {
329    format_string: LitStr,
330    parsed: FormatString,
331    args: VecDeque<Arg>,
332}
333
334/// A variant of [`FormatAndArgs`] that is generic over format string flavor.
335///
336/// `FormatAndArgsFlavor` implements [`syn::parse::Parse`] for it's specified
337/// format string flavor.  Instantiate `FormatAndArgsFlavor` with either
338/// [`PrintfFormatStringParser`] or [`CoreFmtFormatStringParser`] to specify
339/// which format string flavor should be used.
340///
341/// `FormatAndArgsFlavor` trivially converts into [`FormatAndArgs`] with the
342/// [`From`] trait.
343#[derive(Debug)]
344pub struct FormatAndArgsFlavor<T: FormatStringParser> {
345    format_and_args: FormatAndArgs,
346    phantom: PhantomData<T>,
347}
348
349impl<T: FormatStringParser> From<FormatAndArgsFlavor<T>> for FormatAndArgs {
350    fn from(val: FormatAndArgsFlavor<T>) -> Self {
351        val.format_and_args
352    }
353}
354
355impl<T: FormatStringParser> Parse for FormatAndArgsFlavor<T> {
356    fn parse(input: ParseStream) -> syn::parse::Result<Self> {
357        let punctuated =
358            Punctuated::<LitStr, keywords::PW_FMT_CONCAT>::parse_separated_nonempty(input)?;
359        let span = punctuated.span();
360        let format_string = LitStr::new(
361            &punctuated.into_iter().fold(String::new(), |mut acc, s| {
362                acc.push_str(&s.value());
363                acc
364            }),
365            span,
366        );
367
368        let args = if input.is_empty() {
369            // If there are no more tokens, no arguments were specified.
370            VecDeque::new()
371        } else {
372            // Eat the `,` following the format string.
373            input.parse::<Token![,]>()?;
374
375            let punctuated = Punctuated::<Arg, Token![,]>::parse_terminated(input)?;
376            punctuated.into_iter().collect()
377        };
378
379        let parsed = T::parse_format_string(&format_string.value()).map_err(|e| {
380            syn::Error::new_spanned(
381                format_string.to_token_stream(),
382                format!("Error parsing format string {e}"),
383            )
384        })?;
385
386        Ok(FormatAndArgsFlavor {
387            format_and_args: FormatAndArgs {
388                format_string,
389                parsed,
390                args,
391            },
392            phantom: PhantomData,
393        })
394    }
395}
396
397// Grab the next argument returning a descriptive error if no more args are left.
398fn next_arg(spec: &ConversionSpec, args: &mut VecDeque<Arg>) -> Result<Arg> {
399    args.pop_front()
400        .ok_or_else(|| Error::new(&format!("No argument given for {spec:?}")))
401}
402
403// Handle a single format conversion specifier (i.e. `%08x`).  Grabs the
404// necessary arguments for the specifier from `args` and generates code
405// to marshal the arguments into the buffer declared in `_tokenize_to_buffer`.
406// Returns an error if args is too short of if a format specifier is unsupported.
407fn handle_conversion(
408    generator: &mut dyn FormatMacroGenerator,
409    spec: &ConversionSpec,
410    args: &mut VecDeque<Arg>,
411) -> Result<()> {
412    match spec.primitive {
413        Primitive::Integer | Primitive::Unsigned => {
414            // TODO: b/281862660 - Support Width::Variable and Precision::Variable.
415            if spec.min_field_width == MinFieldWidth::Variable {
416                return Err(Error::new(
417                    "Variable width '*' integer formats are not supported.",
418                ));
419            }
420
421            if spec.precision == Precision::Variable {
422                return Err(Error::new(
423                    "Variable precision '*' integer formats are not supported.",
424                ));
425            }
426
427            if spec.style == Style::Binary {
428                return Err(Error::new("Binary output style is not supported."));
429            }
430
431            let arg = next_arg(spec, args)?;
432            let bits = match spec.length.unwrap_or(Length::Long) {
433                Length::Char => 8,
434                Length::Short => 16,
435                Length::Long => 32,
436                Length::LongLong => 64,
437                Length::IntMax => 64,
438                Length::Size => 32,
439                Length::PointerDiff => 32,
440                Length::LongDouble => {
441                    return Err(Error::new(
442                        "Long double length parameter invalid for integer formats",
443                    ));
444                }
445            };
446            let params = spec.try_into()?;
447
448            generator.integer_conversion(&params, spec.primitive == Primitive::Integer, bits, arg)
449        }
450        Primitive::String => {
451            // TODO: b/281862660 - Support Width::Variable and Precision::Variable.
452            if spec.min_field_width == MinFieldWidth::Variable {
453                return Err(Error::new(
454                    "Variable width '*' string formats are not supported.",
455                ));
456            }
457
458            if spec.precision == Precision::Variable {
459                return Err(Error::new(
460                    "Variable precision '*' string formats are not supported.",
461                ));
462            }
463
464            let arg = next_arg(spec, args)?;
465            generator.string_conversion(arg)
466        }
467        Primitive::Character => {
468            let arg = next_arg(spec, args)?;
469            generator.char_conversion(arg)
470        }
471
472        Primitive::Untyped => {
473            let arg = next_arg(spec, args)?;
474            let params = spec.try_into()?;
475            generator.untyped_conversion(arg, &params)
476        }
477
478        Primitive::Float => {
479            // TODO: b/281862328 - Support floating point numbers.
480            Err(Error::new("Floating point numbers are not supported."))
481        }
482
483        // TODO: b/281862333 - Support pointers.
484        Primitive::Pointer => Err(Error::new("Pointer types are not supported.")),
485    }
486}
487
488/// Generate code for a `pw_format` style proc macro.
489///
490/// `generate` takes a [`FormatMacroGenerator`] and a [`FormatAndArgs`] struct
491/// and uses them to produce the code output for a proc macro.
492pub fn generate(
493    mut generator: impl FormatMacroGenerator,
494    format_and_args: FormatAndArgs,
495) -> core::result::Result<TokenStream2, syn::Error> {
496    let mut args = format_and_args.args;
497    let mut errors = Vec::new();
498
499    for fragment in format_and_args.parsed.fragments {
500        let result = match fragment {
501            FormatFragment::Conversion(spec) => handle_conversion(&mut generator, &spec, &mut args),
502            FormatFragment::Literal(string) => generator.string_fragment(&string),
503        };
504        if let Err(e) = result {
505            errors.push(syn::Error::new_spanned(
506                format_and_args.format_string.to_token_stream(),
507                e.text,
508            ));
509        }
510    }
511
512    if !errors.is_empty() {
513        return Err(errors
514            .into_iter()
515            .reduce(|mut accumulated_errors, error| {
516                accumulated_errors.combine(error);
517                accumulated_errors
518            })
519            .expect("errors should not be empty"));
520    }
521
522    generator.finalize().map_err(|e| {
523        syn::Error::new_spanned(format_and_args.format_string.to_token_stream(), e.text)
524    })
525}
526
527/// A specialized generator for proc macros that produce `printf` style format strings.
528///
529/// For proc macros that need to translate a `pw_format` invocation into a
530/// `printf` style format string, `PrintfFormatMacroGenerator` offer a
531/// specialized form of [`FormatMacroGenerator`] that builds the format string
532/// and provides it as an argument to
533/// [`finalize`](PrintfFormatMacroGenerator::finalize).
534///
535/// In cases where a generator needs to override the conversion specifier it
536/// can return it from its appropriate conversion method.  An example of using
537/// this would be wanting to pass a Rust string directly to a `printf` call
538/// over FFI.  In that case,
539/// [`string_conversion`](PrintfFormatMacroGenerator::string_conversion) could
540/// return `Ok(Some("%.*s".to_string()))` to allow both the length and string
541/// pointer to be passed to `printf`.
542pub trait PrintfFormatMacroGenerator {
543    /// Called by [`generate_printf`] at the end of code generation.
544    ///
545    /// Works like [`FormatMacroGenerator::finalize`] with the addition of
546    /// being provided a `printf_style` format string.
547    fn finalize(
548        self,
549        format_string_fragments: &[PrintfFormatStringFragment],
550    ) -> Result<TokenStream2>;
551
552    /// Process a string fragment.
553    ///
554    /// **NOTE**: This string may contain unescaped `%` characters.
555    /// However, most implementations of this train can simply ignore string
556    /// fragments as they will be included (with properly escaped `%`
557    /// characters) as part of the format string passed to
558    /// [`PrintfFormatMacroGenerator::finalize`].
559    ///
560    /// See [`FormatMacroGenerator::string_fragment`] for a disambiguation
561    /// between a string fragment and string conversion.
562    fn string_fragment(&mut self, string: &str) -> Result<()>;
563
564    /// Process an integer conversion.
565    ///
566    /// May optionally return a printf format string (i.e. "%d") to override the
567    /// default.
568    fn integer_conversion(&mut self, ty: Ident, expression: Arg) -> Result<Option<String>>;
569
570    /// Process a string conversion.
571    ///
572    /// May optionally return a printf format string (i.e. "%s") to override the
573    /// default.
574    ///
575    /// See [`FormatMacroGenerator::string_fragment`] for a disambiguation
576    /// between a string fragment and string conversion.
577    fn string_conversion(&mut self, expression: Arg) -> Result<Option<String>>;
578
579    /// Process a character conversion.
580    ///
581    /// May optionally return a printf format string (i.e. "%c") to override the
582    /// default.
583    fn char_conversion(&mut self, expression: Arg) -> Result<Option<String>>;
584
585    /// Process and untyped conversion.
586    fn untyped_conversion(&mut self, _expression: Arg) -> Result<()> {
587        Err(Error::new("untyped conversion not supported"))
588    }
589}
590
591/// A fragment of a printf format string.
592///
593/// Printf format strings are built of multiple fragments.  These fragments can
594/// be either a string ([`PrintfFormatStringFragment::String`]) or an expression
595/// that evaluates to a `const &str` ([`PrintfFormatStringFragment::Expr`]).
596/// These fragments can then be used to create a single `const &str` for use by
597/// code generation.
598///
599/// # Example
600/// ```
601/// use pw_bytes::concat_static_strs;
602/// use pw_format::macros::{PrintfFormatStringFragment, Result};
603/// use quote::quote;
604///
605/// fn handle_fragments(format_string_fragments: &[PrintfFormatStringFragment]) -> Result<()> {
606///   let format_string_pieces: Vec<_> = format_string_fragments
607///     .iter()
608///     .map(|fragment| fragment.as_token_stream("__pw_log_backend_crate"))
609///     .collect::<Result<Vec<_>>>()?;
610///
611///   quote! {
612///     let format_string = concat_static_strs!("prefix: ", #(#format_string_pieces),*, "\n");
613///   };
614///   Ok(())
615/// }
616/// ```
617pub enum PrintfFormatStringFragment {
618    /// A fragment that is a string.
619    String(String),
620
621    /// An expressions that can be converted to a `const &str`.
622    Expr {
623        /// Argument to convert.
624        arg: Arg,
625        /// Trait to used for getting the format specifier for the argument.
626        ///
627        /// One of `PrintfFormatter`, `PrintfHexFormatter`, `PrintfUpperHexFormatter
628        format_trait: Ident,
629    },
630}
631
632impl PrintfFormatStringFragment {
633    /// Returns a [`proc_macro2::TokenStream`] representing this fragment.
634    pub fn as_token_stream(&self, printf_formatter_trait_location: &str) -> Result<TokenStream2> {
635        let crate_name = format_ident!("{}", printf_formatter_trait_location);
636        match self {
637            Self::String(s) => Ok(quote! {#s}),
638            #[cfg(not(feature = "nightly_tait"))]
639            Self::Expr { arg, format_trait } => {
640                let Arg::ExprCast(cast) = arg else {
641                    return Err(Error::new(&format!(
642                        "Expected argument to untyped format (%v/{{}}) to be a cast expression (e.g. x as i32), but found {}.",
643                        arg.to_token_stream(),
644                    )));
645                };
646                let ty = &cast.ty;
647                Ok(quote! {
648                  {
649                    use #crate_name::#format_trait;
650                    <#ty as #format_trait>::FORMAT_ARG
651                  }
652                })
653            }
654            #[cfg(feature = "nightly_tait")]
655            Self::Expr { arg, format_trait } => Ok(quote! {
656              {
657                use #crate_name::#format_trait;
658                type T = impl #format_trait;
659                let _: &T = &(#arg);
660                let arg = <T as #format_trait>::FORMAT_ARG;
661                arg
662              }
663            }),
664        }
665    }
666}
667
668// Wraps a `PrintfFormatMacroGenerator` in a `FormatMacroGenerator` that
669// generates the format string as it goes.
670struct PrintfGenerator<GENERATOR: PrintfFormatMacroGenerator> {
671    inner: GENERATOR,
672    format_string_fragments: Vec<PrintfFormatStringFragment>,
673}
674
675impl<GENERATOR: PrintfFormatMacroGenerator> PrintfGenerator<GENERATOR> {
676    // Append `format_string` to the current set of format string fragments.
677    fn append_format_string(&mut self, format_string: &str) {
678        // If the last fragment is a string, append to that.
679        if let PrintfFormatStringFragment::String(s) = self
680            .format_string_fragments
681            .last_mut()
682            .expect("format_string_fragments always has at least one entry")
683        {
684            s.push_str(format_string)
685        } else {
686            // If the last fragment is not a string, add a new string fragment.
687            self.format_string_fragments
688                .push(PrintfFormatStringFragment::String(
689                    format_string.to_string(),
690                ));
691        }
692    }
693}
694
695impl<GENERATOR: PrintfFormatMacroGenerator> FormatMacroGenerator for PrintfGenerator<GENERATOR> {
696    fn finalize(self) -> Result<TokenStream2> {
697        self.inner.finalize(&self.format_string_fragments)
698    }
699
700    fn string_fragment(&mut self, string: &str) -> Result<()> {
701        // Escape '%' characters.
702        let format_string = string.replace('%', "%%");
703
704        self.append_format_string(&format_string);
705        self.inner.string_fragment(string)
706    }
707
708    fn integer_conversion(
709        &mut self,
710        params: &FormatParams,
711        signed: bool,
712        type_width: u8, // in bits
713        expression: Arg,
714    ) -> Result<()> {
715        let length_modifier = match type_width {
716            8 => "hh",
717            16 => "h",
718            32 => "",
719            64 => "ll",
720            _ => {
721                return Err(Error::new(&format!(
722                    "printf backend does not support {type_width} bit field width"
723                )));
724            }
725        };
726
727        let (conversion, ty) = match params.style {
728            Style::None => {
729                if signed {
730                    ("d", format_ident!("i{type_width}"))
731                } else {
732                    ("u", format_ident!("u{type_width}"))
733                }
734            }
735            Style::Octal => ("o", format_ident!("u{type_width}")),
736            Style::Hex => ("x", format_ident!("u{type_width}")),
737            Style::UpperHex => ("X", format_ident!("u{type_width}")),
738            _ => {
739                return Err(Error::new(&format!(
740                    "printf backend does not support formatting integers with {:?} style",
741                    params.style
742                )));
743            }
744        };
745
746        match self.inner.integer_conversion(ty, expression)? {
747            Some(s) => self.append_format_string(&s),
748            None => self.append_format_string(&format!(
749                "%{}{}{}",
750                params.field_params(),
751                length_modifier,
752                conversion
753            )),
754        }
755
756        Ok(())
757    }
758
759    fn string_conversion(&mut self, expression: Arg) -> Result<()> {
760        match self.inner.string_conversion(expression)? {
761            Some(s) => self.append_format_string(&s),
762            None => self.append_format_string("%s"),
763        }
764        Ok(())
765    }
766
767    fn char_conversion(&mut self, expression: Arg) -> Result<()> {
768        match self.inner.char_conversion(expression)? {
769            Some(s) => self.append_format_string(&s),
770            None => self.append_format_string("%c"),
771        }
772        Ok(())
773    }
774
775    fn untyped_conversion(&mut self, expression: Arg, params: &FormatParams) -> Result<()> {
776        self.inner.untyped_conversion(expression.clone())?;
777
778        self.append_format_string(&format!("%{}", params.field_params()));
779        self.format_string_fragments
780            .push(PrintfFormatStringFragment::Expr {
781                arg: expression,
782                format_trait: params.printf_format_trait()?,
783            });
784        Ok(())
785    }
786}
787
788/// Generate code for a `pw_format` style proc macro that needs a `printf` format string.
789///
790/// `generate_printf` is a specialized version of [`generate`] which works with
791/// [`PrintfFormatMacroGenerator`]
792pub fn generate_printf(
793    generator: impl PrintfFormatMacroGenerator,
794    format_and_args: FormatAndArgs,
795) -> core::result::Result<TokenStream2, syn::Error> {
796    let generator = PrintfGenerator {
797        inner: generator,
798        format_string_fragments: vec![PrintfFormatStringFragment::String("".into())],
799    };
800    generate(generator, format_and_args)
801}
802
803/// A specialized generator for proc macros that produce [`core::fmt`] style format strings.
804///
805/// For proc macros that need to translate a `pw_format` invocation into a
806/// [`core::fmt`] style format string, `CoreFmtFormatMacroGenerator` offer a
807/// specialized form of [`FormatMacroGenerator`] that builds the format string
808/// and provides it as an argument to
809/// [`finalize`](CoreFmtFormatMacroGenerator::finalize).
810///
811/// In cases where a generator needs to override the conversion specifier (i.e.
812/// `{}`, it can return it from its appropriate conversion method.
813pub trait CoreFmtFormatMacroGenerator {
814    /// Called by [`generate_core_fmt`] at the end of code generation.
815    ///
816    /// Works like [`FormatMacroGenerator::finalize`] with the addition of
817    /// being provided a [`core::fmt`] format string.
818    fn finalize(self, format_string: String) -> Result<TokenStream2>;
819
820    /// Process a string fragment.
821    ///
822    /// **NOTE**: This string may contain unescaped `{` and `}` characters.
823    /// However, most implementations of this train can simply ignore string
824    /// fragments as they will be included (with properly escaped `{` and `}`
825    /// characters) as part of the format string passed to
826    /// [`CoreFmtFormatMacroGenerator::finalize`].
827    ///
828    ///
829    /// See [`FormatMacroGenerator::string_fragment`] for a disambiguation
830    /// between a string fragment and string conversion.
831    fn string_fragment(&mut self, string: &str) -> Result<()>;
832
833    /// Process an integer conversion.
834    fn integer_conversion(&mut self, ty: Ident, expression: Arg) -> Result<Option<String>>;
835
836    /// Process a string conversion.
837    fn string_conversion(&mut self, expression: Arg) -> Result<Option<String>>;
838
839    /// Process a character conversion.
840    fn char_conversion(&mut self, expression: Arg) -> Result<Option<String>>;
841
842    /// Process an untyped conversion.
843    fn untyped_conversion(&mut self, _expression: Arg) -> Result<()> {
844        Err(Error::new("untyped conversion ({}) not supported"))
845    }
846}
847
848// Wraps a `CoreFmtFormatMacroGenerator` in a `FormatMacroGenerator` that
849// generates the format string as it goes.
850struct CoreFmtGenerator<GENERATOR: CoreFmtFormatMacroGenerator> {
851    inner: GENERATOR,
852    format_string: String,
853}
854
855impl<GENERATOR: CoreFmtFormatMacroGenerator> FormatMacroGenerator for CoreFmtGenerator<GENERATOR> {
856    fn finalize(self) -> Result<TokenStream2> {
857        self.inner.finalize(self.format_string)
858    }
859
860    fn string_fragment(&mut self, string: &str) -> Result<()> {
861        // Escape '{' and '} characters.
862        let format_string = string.replace('{', "{{").replace('}', "}}");
863
864        self.format_string.push_str(&format_string);
865        self.inner.string_fragment(string)
866    }
867
868    fn integer_conversion(
869        &mut self,
870        params: &FormatParams,
871        signed: bool,
872        type_width: u8, // in bits
873        expression: Arg,
874    ) -> Result<()> {
875        let ty = if signed {
876            format_ident!("i{type_width}")
877        } else {
878            format_ident!("u{type_width}")
879        };
880
881        let conversion = params.core_fmt_specifier()?;
882
883        match self.inner.integer_conversion(ty, expression)? {
884            Some(s) => self.format_string.push_str(&s),
885            None => self.format_string.push_str(&conversion),
886        }
887
888        Ok(())
889    }
890
891    fn string_conversion(&mut self, expression: Arg) -> Result<()> {
892        match self.inner.string_conversion(expression)? {
893            Some(s) => self.format_string.push_str(&s),
894            None => self.format_string.push_str("{}"),
895        }
896        Ok(())
897    }
898
899    fn char_conversion(&mut self, expression: Arg) -> Result<()> {
900        match self.inner.char_conversion(expression)? {
901            Some(s) => self.format_string.push_str(&s),
902            None => self.format_string.push_str("{}"),
903        }
904        Ok(())
905    }
906
907    fn untyped_conversion(&mut self, expression: Arg, params: &FormatParams) -> Result<()> {
908        self.inner.untyped_conversion(expression)?;
909        self.format_string.push_str(&params.core_fmt_specifier()?);
910        Ok(())
911    }
912}
913
914/// Generate code for a `pw_format` style proc macro that needs a [`core::fmt`] format string.
915///
916/// `generate_core_fmt` is a specialized version of [`generate`] which works with
917/// [`CoreFmtFormatMacroGenerator`]
918pub fn generate_core_fmt(
919    generator: impl CoreFmtFormatMacroGenerator,
920    format_and_args: FormatAndArgs,
921) -> core::result::Result<TokenStream2, syn::Error> {
922    let generator = CoreFmtGenerator {
923        inner: generator,
924        format_string: "".into(),
925    };
926    generate(generator, format_and_args)
927}