aboutsummaryrefslogtreecommitdiff
path: root/pw_format/rust/pw_format/macros.rs
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2024-06-13 10:50:00 -0700
committerXin Li <delphij@google.com>2024-06-13 10:50:00 -0700
commit3ccc229314cb5743e7c9494cc38454ce3dd0aeb0 (patch)
tree1ba9b93fda929860a1670e1d8941ba78ca479f8e /pw_format/rust/pw_format/macros.rs
parent646563934a3e2ee26f50171f94d95173a1662e2c (diff)
parent0069dc840059ee077efa7b808807fc580596f40c (diff)
downloadpigweed-master.tar.gz
Merge Android 14 QPR3 to AOSP mainHEADmastermain
Bug: 346855327 Merged-In: I7ce03a557c45113c8e7a15fc56e858dea3333f60 Change-Id: I4343bc6d1345a3cbbf9eb9d74afe8c42ac1eb177
Diffstat (limited to 'pw_format/rust/pw_format/macros.rs')
-rw-r--r--pw_format/rust/pw_format/macros.rs139
1 files changed, 137 insertions, 2 deletions
diff --git a/pw_format/rust/pw_format/macros.rs b/pw_format/rust/pw_format/macros.rs
index 1c049471d..d08b634e9 100644
--- a/pw_format/rust/pw_format/macros.rs
+++ b/pw_format/rust/pw_format/macros.rs
@@ -40,6 +40,7 @@ use quote::{format_ident, quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
+ spanned::Spanned,
Expr, LitStr, Token,
};
@@ -47,6 +48,10 @@ use crate::{
ConversionSpec, FormatFragment, FormatString, Length, MinFieldWidth, Precision, Specifier,
};
+mod keywords {
+ syn::custom_keyword!(PW_FMT_CONCAT);
+}
+
type TokenStream2 = proc_macro2::TokenStream;
/// An error occurring during proc macro evaluation.
@@ -173,6 +178,10 @@ pub trait FormatMacroGenerator {
/// `FormatAndArgs` implements [`syn::parse::Parse`] and can be used to parse
/// arguments to proc maros that take format strings. Arguments are parsed
/// according to the pattern: `($format_string:literal, $($args:expr),*)`
+///
+/// To support uses where format strings need to be built by macros at compile
+/// time, the format string can be specified as a set of string literals
+/// separated by the custom `PW_FMT_CONCAT` keyword.
#[derive(Debug)]
pub struct FormatAndArgs {
format_string: LitStr,
@@ -182,7 +191,16 @@ pub struct FormatAndArgs {
impl Parse for FormatAndArgs {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
- let format_string = input.parse::<LitStr>()?;
+ let punctuated =
+ Punctuated::<LitStr, keywords::PW_FMT_CONCAT>::parse_separated_nonempty(input)?;
+ let span = punctuated.span();
+ let format_string = LitStr::new(
+ &punctuated.into_iter().fold(String::new(), |mut acc, s| {
+ acc.push_str(&s.value());
+ acc
+ }),
+ span,
+ );
let args = if input.is_empty() {
// If there are no more tokens, no arguments were specified.
@@ -369,6 +387,10 @@ pub trait PrintfFormatMacroGenerator {
/// 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.
@@ -387,7 +409,6 @@ pub trait PrintfFormatMacroGenerator {
///
/// See [`FormatMacroGenerator::string_fragment`] for a disambiguation
/// between a string fragment and string conversion.
- /// FIXME: docs
fn string_conversion(&mut self, expression: Expr) -> Result<Option<String>>;
/// Process a character conversion.
@@ -485,3 +506,117 @@ pub fn generate_printf(
};
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)
+}