pw_format: Add core::fmt macro helper

Change-Id: I5d3d1108a5674f6e1d2ca8b9b69a44353c1e9cfd
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/178537
Reviewed-by: Taylor Cramer <cramertj@google.com>
Commit-Queue: Erik Gilling <konkers@google.com>
diff --git a/pw_format/rust/pw_format/macros.rs b/pw_format/rust/pw_format/macros.rs
index 1c04947..5f96c6f 100644
--- a/pw_format/rust/pw_format/macros.rs
+++ b/pw_format/rust/pw_format/macros.rs
@@ -369,6 +369,10 @@
     /// Process a string fragment.
     ///
     /// **NOTE**: This string may contain unescaped `%` characters.
+    /// However, most implementations of this train can simply ignore string
+    /// fragments as they will be included (with properly escaped `%`
+    /// characters) as part of the format string passed to
+    /// [`PrintfFormatMacroGenerator::finalize`].
     ///
     /// See [`FormatMacroGenerator::string_fragment`] for a disambiguation
     /// between a string fragment and string conversion.
@@ -485,3 +489,117 @@
     };
     generate(generator, format_and_args)
 }
+
+/// A specialized generator for proc macros that produce [`core::fmt`] style format strings.
+///
+/// For proc macros that need to translate a `pw_format` invocation into a
+/// [`core::fmt`] style format string, `CoreFmtFormatMacroGenerator` offer a
+/// specialized form of [`FormatMacroGenerator`] that builds the format string
+/// and provides it as an argument to
+/// [`finalize`](CoreFmtFormatMacroGenerator::finalize).
+///
+/// In cases where a generator needs to override the conversion specifier (i.e.
+/// `{}`, it can return it from its appropriate conversion method.
+pub trait CoreFmtFormatMacroGenerator {
+    /// Called by [`generate_core_fmt`] at the end of code generation.
+    ///
+    /// Works like [`FormatMacroGenerator::finalize`] with the addition of
+    /// being provided a [`core::fmt`] format string.
+    fn finalize(self, format_string: String) -> Result<TokenStream2>;
+
+    /// Process a string fragment.
+    ///
+    /// **NOTE**: This string may contain unescaped `{` and `}` characters.
+    /// However, most implementations of this train can simply ignore string
+    /// fragments as they will be included (with properly escaped `{` and `}`
+    /// characters) as part of the format string passed to
+    /// [`CoreFmtFormatMacroGenerator::finalize`].
+    ///
+    ///
+    /// See [`FormatMacroGenerator::string_fragment`] for a disambiguation
+    /// between a string fragment and string conversion.
+    fn string_fragment(&mut self, string: &str) -> Result<()>;
+
+    /// Process an integer conversion.
+    fn integer_conversion(&mut self, ty: Ident, expression: Expr) -> Result<Option<String>>;
+
+    /// Process a string conversion.
+    fn string_conversion(&mut self, expression: Expr) -> Result<Option<String>>;
+
+    /// Process a character conversion.
+    fn char_conversion(&mut self, expression: Expr) -> Result<Option<String>>;
+}
+
+// Wraps a `CoreFmtFormatMacroGenerator` in a `FormatMacroGenerator` that
+// generates the format string as it goes.
+struct CoreFmtGenerator<GENERATOR: CoreFmtFormatMacroGenerator> {
+    inner: GENERATOR,
+    format_string: String,
+}
+
+impl<GENERATOR: CoreFmtFormatMacroGenerator> FormatMacroGenerator for CoreFmtGenerator<GENERATOR> {
+    fn finalize(self) -> Result<TokenStream2> {
+        self.inner.finalize(self.format_string)
+    }
+
+    fn string_fragment(&mut self, string: &str) -> Result<()> {
+        // Escape '{' and '} characters.
+        let format_string = string.replace("{", "{{").replace("}", "}}");
+
+        self.format_string.push_str(&format_string);
+        self.inner.string_fragment(string)
+    }
+
+    fn integer_conversion(
+        &mut self,
+        display: IntegerDisplayType,
+        type_width: u8, // in bits
+        expression: Expr,
+    ) -> Result<()> {
+        let (conversion, ty) = match display {
+            IntegerDisplayType::Signed => ("{}", format_ident!("i{type_width}")),
+            IntegerDisplayType::Unsigned => ("{}", format_ident!("u{type_width}")),
+            IntegerDisplayType::Octal => ("{:o}", format_ident!("u{type_width}")),
+            IntegerDisplayType::Hex => ("{:x}", format_ident!("u{type_width}")),
+            IntegerDisplayType::UpperHex => ("{:X}", format_ident!("u{type_width}")),
+        };
+
+        match self.inner.integer_conversion(ty, expression)? {
+            Some(s) => self.format_string.push_str(&s),
+            None => self.format_string.push_str(conversion),
+        }
+
+        Ok(())
+    }
+
+    fn string_conversion(&mut self, expression: Expr) -> Result<()> {
+        match self.inner.string_conversion(expression)? {
+            Some(s) => self.format_string.push_str(&s),
+            None => self.format_string.push_str("{}"),
+        }
+        Ok(())
+    }
+
+    fn char_conversion(&mut self, expression: Expr) -> Result<()> {
+        match self.inner.char_conversion(expression)? {
+            Some(s) => self.format_string.push_str(&s),
+            None => self.format_string.push_str("{}"),
+        }
+        Ok(())
+    }
+}
+
+/// Generate code for a `pw_format` style proc macro that needs a [`core::fmt`] format string.
+///
+/// `generate_core_fmt` is a specialized version of [`generate`] which works with
+/// [`CoreFmtFormatMacroGenerator`]
+pub fn generate_core_fmt(
+    generator: impl CoreFmtFormatMacroGenerator,
+    format_and_args: FormatAndArgs,
+) -> core::result::Result<TokenStream2, syn::Error> {
+    let generator = CoreFmtGenerator {
+        inner: generator,
+        format_string: "".into(),
+    };
+    generate(generator, format_and_args)
+}
diff --git a/pw_format/rust/pw_format_test_macros.rs b/pw_format/rust/pw_format_test_macros.rs
index 0ccb9fa..5740dca 100644
--- a/pw_format/rust/pw_format_test_macros.rs
+++ b/pw_format/rust/pw_format_test_macros.rs
@@ -15,8 +15,8 @@
 use proc_macro::TokenStream;
 use proc_macro2::Ident;
 use pw_format::macros::{
-    generate, generate_printf, FormatAndArgs, FormatMacroGenerator, IntegerDisplayType,
-    PrintfFormatMacroGenerator, Result,
+    generate, generate_core_fmt, generate_printf, CoreFmtFormatMacroGenerator, FormatAndArgs,
+    FormatMacroGenerator, IntegerDisplayType, PrintfFormatMacroGenerator, Result,
 };
 use quote::quote;
 use syn::{parse_macro_input, Expr};
@@ -220,3 +220,126 @@
         Err(e) => e.to_compile_error().into(),
     }
 }
+
+// Generator for testing `generate_core_fmt()`.  Allows control over the return
+// value of the conversion functions.
+struct CoreFmtTestGenerator {
+    code_fragments: Vec<TokenStream2>,
+    integer_specifier_override: Option<String>,
+    string_specifier_override: Option<String>,
+    char_specifier_override: Option<String>,
+}
+
+impl CoreFmtTestGenerator {
+    pub fn new() -> Self {
+        Self {
+            code_fragments: Vec::new(),
+            integer_specifier_override: None,
+            string_specifier_override: None,
+            char_specifier_override: None,
+        }
+    }
+
+    pub fn with_integer_specifier(mut self, specifier: &str) -> Self {
+        self.integer_specifier_override = Some(specifier.to_string());
+        self
+    }
+
+    pub fn with_string_specifier(mut self, specifier: &str) -> Self {
+        self.string_specifier_override = Some(specifier.to_string());
+        self
+    }
+
+    pub fn with_char_specifier(mut self, specifier: &str) -> Self {
+        self.char_specifier_override = Some(specifier.to_string());
+        self
+    }
+}
+
+// Until they diverge, we reuse `PrintfTestGeneratorOps` here.
+impl CoreFmtFormatMacroGenerator for CoreFmtTestGenerator {
+    fn finalize(self, format_string: String) -> Result<TokenStream2> {
+        // Create locally scoped alias so we can refer to them in `quote!()`.
+        let code_fragments = self.code_fragments;
+
+        Ok(quote! {
+          {
+            use pw_format_test_macros_test::PrintfTestGeneratorOps;
+            let mut ops = Vec::new();
+            #(#code_fragments);*;
+            ops.push(PrintfTestGeneratorOps::Finalize);
+            (#format_string, ops)
+          }
+        })
+    }
+    fn string_fragment(&mut self, string: &str) -> Result<()> {
+        self.code_fragments.push(quote! {
+            ops.push(PrintfTestGeneratorOps::StringFragment(#string.to_string()));
+        });
+        Ok(())
+    }
+    fn integer_conversion(&mut self, ty: Ident, _expression: Expr) -> Result<Option<String>> {
+        let ty_str = format!("{ty}");
+        self.code_fragments.push(quote! {
+            ops.push(PrintfTestGeneratorOps::IntegerConversion{ ty: #ty_str.to_string() });
+        });
+        Ok(self.integer_specifier_override.clone())
+    }
+
+    fn string_conversion(&mut self, _expression: Expr) -> Result<Option<String>> {
+        self.code_fragments.push(quote! {
+            ops.push(PrintfTestGeneratorOps::StringConversion);
+        });
+        Ok(self.string_specifier_override.clone())
+    }
+
+    fn char_conversion(&mut self, _expression: Expr) -> Result<Option<String>> {
+        self.code_fragments.push(quote! {
+            ops.push(PrintfTestGeneratorOps::CharConversion);
+        });
+        Ok(self.char_specifier_override.clone())
+    }
+}
+
+#[proc_macro]
+pub fn core_fmt_generator_test_macro(tokens: TokenStream) -> TokenStream {
+    let format_and_args = parse_macro_input!(tokens as FormatAndArgs);
+    let generator = CoreFmtTestGenerator::new();
+    match generate_core_fmt(generator, format_and_args) {
+        Ok(token_stream) => token_stream.into(),
+        Err(e) => e.to_compile_error().into(),
+    }
+}
+
+// Causes the generator to substitute {} with {:?}.
+#[proc_macro]
+pub fn integer_sub_core_fmt_generator_test_macro(tokens: TokenStream) -> TokenStream {
+    let format_and_args = parse_macro_input!(tokens as FormatAndArgs);
+    let generator = CoreFmtTestGenerator::new().with_integer_specifier("{:?}");
+    match generate_core_fmt(generator, format_and_args) {
+        Ok(token_stream) => token_stream.into(),
+        Err(e) => e.to_compile_error().into(),
+    }
+}
+
+// Causes the generator to substitute {} with {:?}.
+#[proc_macro]
+pub fn string_sub_core_fmt_generator_test_macro(tokens: TokenStream) -> TokenStream {
+    let format_and_args = parse_macro_input!(tokens as FormatAndArgs);
+    let generator = CoreFmtTestGenerator::new().with_string_specifier("{:?}");
+    match generate_core_fmt(generator, format_and_args) {
+        Ok(token_stream) => token_stream.into(),
+        Err(e) => e.to_compile_error().into(),
+    }
+}
+
+// Causes the generator to substitute {} with {:?}.
+#[proc_macro]
+pub fn char_sub_core_fmt_generator_test_macro(tokens: TokenStream) -> TokenStream {
+    let format_and_args = parse_macro_input!(tokens as FormatAndArgs);
+    let generator = CoreFmtTestGenerator::new().with_char_specifier("{:?}");
+    match generate_core_fmt(generator, format_and_args) {
+        Ok(token_stream) => token_stream.into(),
+        Err(e) => e.to_compile_error().into(),
+    }
+}
diff --git a/pw_format/rust/pw_format_test_macros_test.rs b/pw_format/rust/pw_format_test_macros_test.rs
index 3ae1f54..3220262 100644
--- a/pw_format/rust/pw_format_test_macros_test.rs
+++ b/pw_format/rust/pw_format_test_macros_test.rs
@@ -41,8 +41,10 @@
 mod tests {
     use pw_format::macros::IntegerDisplayType;
     use pw_format_test_macros::{
-        char_sub_printf_generator_test_macro, generator_test_macro,
-        integer_sub_printf_generator_test_macro, printf_generator_test_macro,
+        char_sub_core_fmt_generator_test_macro, char_sub_printf_generator_test_macro,
+        core_fmt_generator_test_macro, generator_test_macro,
+        integer_sub_core_fmt_generator_test_macro, integer_sub_printf_generator_test_macro,
+        printf_generator_test_macro, string_sub_core_fmt_generator_test_macro,
         string_sub_printf_generator_test_macro,
     };
 
@@ -165,4 +167,94 @@
             )
         );
     }
+
+    #[test]
+    fn generate_core_fmt_calls_generator_correctly() {
+        assert_eq!(
+            core_fmt_generator_test_macro!("test %ld %s %c", 5, "test", 'c'),
+            (
+                "test {} {} {}",
+                vec![
+                    PrintfTestGeneratorOps::StringFragment("test ".to_string()),
+                    PrintfTestGeneratorOps::IntegerConversion {
+                        ty: "i32".to_string(),
+                    },
+                    PrintfTestGeneratorOps::StringFragment(" ".to_string()),
+                    PrintfTestGeneratorOps::StringConversion,
+                    PrintfTestGeneratorOps::StringFragment(" ".to_string()),
+                    PrintfTestGeneratorOps::CharConversion,
+                    PrintfTestGeneratorOps::Finalize
+                ]
+            )
+        );
+    }
+
+    // Test that a generator returning an overridden integer conversion specifier
+    // changes that and only that conversion specifier in the format string.
+    #[test]
+    fn generate_core_fmt_substitutes_integer_conversion() {
+        assert_eq!(
+            integer_sub_core_fmt_generator_test_macro!("test %ld %s %c", 5, "test", 'c'),
+            (
+                "test {:?} {} {}",
+                vec![
+                    PrintfTestGeneratorOps::StringFragment("test ".to_string()),
+                    PrintfTestGeneratorOps::IntegerConversion {
+                        ty: "i32".to_string(),
+                    },
+                    PrintfTestGeneratorOps::StringFragment(" ".to_string()),
+                    PrintfTestGeneratorOps::StringConversion,
+                    PrintfTestGeneratorOps::StringFragment(" ".to_string()),
+                    PrintfTestGeneratorOps::CharConversion,
+                    PrintfTestGeneratorOps::Finalize
+                ]
+            )
+        );
+    }
+
+    // Test that a generator returning an overridden string conversion specifier
+    // changes that and only that conversion specifier in the format string.
+    #[test]
+    fn generate_core_fmt_substitutes_string_conversion() {
+        assert_eq!(
+            string_sub_core_fmt_generator_test_macro!("test %ld %s %c", 5, "test", 'c'),
+            (
+                "test {} {:?} {}",
+                vec![
+                    PrintfTestGeneratorOps::StringFragment("test ".to_string()),
+                    PrintfTestGeneratorOps::IntegerConversion {
+                        ty: "i32".to_string(),
+                    },
+                    PrintfTestGeneratorOps::StringFragment(" ".to_string()),
+                    PrintfTestGeneratorOps::StringConversion,
+                    PrintfTestGeneratorOps::StringFragment(" ".to_string()),
+                    PrintfTestGeneratorOps::CharConversion,
+                    PrintfTestGeneratorOps::Finalize
+                ]
+            )
+        );
+    }
+
+    // Test that a generator returning an overridden character conversion specifier
+    // changes that and only that conversion specifier in the format string.
+    #[test]
+    fn generate_core_fmt_substitutes_char_conversion() {
+        assert_eq!(
+            char_sub_core_fmt_generator_test_macro!("test %ld %s %c", 5, "test", 'c'),
+            (
+                "test {} {} {:?}",
+                vec![
+                    PrintfTestGeneratorOps::StringFragment("test ".to_string()),
+                    PrintfTestGeneratorOps::IntegerConversion {
+                        ty: "i32".to_string(),
+                    },
+                    PrintfTestGeneratorOps::StringFragment(" ".to_string()),
+                    PrintfTestGeneratorOps::StringConversion,
+                    PrintfTestGeneratorOps::StringFragment(" ".to_string()),
+                    PrintfTestGeneratorOps::CharConversion,
+                    PrintfTestGeneratorOps::Finalize
+                ]
+            )
+        );
+    }
 }