aboutsummaryrefslogtreecommitdiff
path: root/pw_format/rust/pw_format/macros.rs
diff options
context:
space:
mode:
Diffstat (limited to 'pw_format/rust/pw_format/macros.rs')
-rw-r--r--pw_format/rust/pw_format/macros.rs487
1 files changed, 487 insertions, 0 deletions
diff --git a/pw_format/rust/pw_format/macros.rs b/pw_format/rust/pw_format/macros.rs
new file mode 100644
index 000000000..1c049471d
--- /dev/null
+++ b/pw_format/rust/pw_format/macros.rs
@@ -0,0 +1,487 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+//! The `macro` module provides helpers that simplify writing proc macros
+//! that take format strings and arguments. This is accomplish with three
+//! main constructs:
+//! * [`FormatAndArgs`]: A struct that implements [syn::parse::Parse] to
+//! parse a format string and its following arguments.
+//! * [`FormatMacroGenerator`]: A trait used to implement the macro specific
+//! logic to generate code.
+//! * [`generate`]: A function to handle the execution of the proc macro by
+//! calling into a [FormatMacroGenerator].
+//!
+//! Additionally [`PrintfFormatMacroGenerator`] trait and [`generate_printf`]
+//! function are provided to help when implementing generators that need to
+//! produce `printf` style format strings as part of their code generation.
+//!
+//! ## Example
+//!
+//! An example of implementing a proc macro is provided in the
+//! [pw_format_example_macro crate](https://pigweed.googlesource.com/pigweed/pigweed/+/refs/heads/main/pw_format/rust/pw_format_example_macro.rs)
+//!
+//!
+
+use std::collections::VecDeque;
+
+use proc_macro2::Ident;
+use quote::{format_ident, quote, ToTokens};
+use syn::{
+ parse::{Parse, ParseStream},
+ punctuated::Punctuated,
+ Expr, LitStr, Token,
+};
+
+use crate::{
+ ConversionSpec, FormatFragment, FormatString, Length, MinFieldWidth, Precision, Specifier,
+};
+
+type TokenStream2 = proc_macro2::TokenStream;
+
+/// An error occurring during proc macro evaluation.
+///
+/// In order to stay as flexible as possible to implementors of
+/// [`FormatMacroGenerator`], the error is simply represent by a
+/// string.
+#[derive(Debug)]
+pub struct Error {
+ text: String,
+}
+
+impl Error {
+ /// Create a new proc macro evaluation error.
+ pub fn new(text: &str) -> Self {
+ Self {
+ text: text.to_string(),
+ }
+ }
+}
+
+/// An alias for a Result with an ``Error``
+pub type Result<T> = core::result::Result<T, Error>;
+
+/// Style in which to display the an integer.
+///
+/// In order to maintain compatibility with `printf` style systems, sign
+/// and base are combined.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum IntegerDisplayType {
+ /// Signed integer
+ Signed,
+ /// Unsigned integer
+ Unsigned,
+ /// Unsigned octal
+ Octal,
+ /// Unsigned hex with lower case letters
+ Hex,
+ /// Unsigned hex with upper case letters
+ UpperHex,
+}
+
+impl TryFrom<crate::Specifier> for IntegerDisplayType {
+ type Error = Error;
+
+ fn try_from(value: Specifier) -> Result<Self> {
+ match value {
+ Specifier::Decimal | Specifier::Integer => Ok(Self::Signed),
+ Specifier::Unsigned => Ok(Self::Unsigned),
+ Specifier::Octal => Ok(Self::Octal),
+ Specifier::Hex => Ok(Self::Hex),
+ Specifier::UpperHex => Ok(Self::UpperHex),
+ _ => Err(Error::new("No valid IntegerDisplayType for {value:?}.")),
+ }
+ }
+}
+
+/// Implemented for testing through the pw_format_test_macros crate.
+impl ToTokens for IntegerDisplayType {
+ fn to_tokens(&self, tokens: &mut TokenStream2) {
+ let new_tokens = match self {
+ IntegerDisplayType::Signed => quote!(pw_format::macros::IntegerDisplayType::Signed),
+ IntegerDisplayType::Unsigned => {
+ quote!(pw_format::macros::IntegerDisplayType::Unsigned)
+ }
+ IntegerDisplayType::Octal => quote!(pw_format::macros::IntegerDisplayType::Octal),
+ IntegerDisplayType::Hex => quote!(pw_format::macros::IntegerDisplayType::Hex),
+ IntegerDisplayType::UpperHex => {
+ quote!(pw_format::macros::IntegerDisplayType::UpperHex)
+ }
+ };
+ new_tokens.to_tokens(tokens);
+ }
+}
+
+/// A code generator for implementing a `pw_format` style macro.
+///
+/// This trait serves as the primary interface between `pw_format` and a
+/// proc macro using it to implement format string and argument parsing. When
+/// evaluating the proc macro and generating code, [`generate`] will make
+/// repeated calls to [`string_fragment`](FormatMacroGenerator::string_fragment)
+/// and the conversion functions. These calls will be made in the order they
+/// appear in the format string. After all fragments and conversions are
+/// processed, [`generate`] will call
+/// [`finalize`](FormatMacroGenerator::finalize).
+///
+/// For an example of implementing a `FormatMacroGenerator` see the
+/// [pw_format_example_macro crate](https://pigweed.googlesource.com/pigweed/pigweed/+/refs/heads/main/pw_format/rust/pw_format_example_macro.rs).
+pub trait FormatMacroGenerator {
+ /// Called by [`generate`] at the end of code generation.
+ ///
+ /// Consumes `self` and returns the code to be emitted by the proc macro of
+ /// and [`Error`].
+ fn finalize(self) -> Result<TokenStream2>;
+
+ /// Process a string fragment.
+ ///
+ /// A string fragment is a string of characters that appear in a format
+ /// string. This is different than a
+ /// [`string_conversion`](FormatMacroGenerator::string_conversion) which is
+ /// a string provided through a conversion specifier (i.e. `"%s"`).
+ fn string_fragment(&mut self, string: &str) -> Result<()>;
+
+ /// Process an integer conversion.
+ fn integer_conversion(
+ &mut self,
+ display: IntegerDisplayType,
+ type_width: u8, // This should probably be an enum
+ expression: Expr,
+ ) -> Result<()>;
+
+ /// Process a string conversion.
+ ///
+ /// See [`string_fragment`](FormatMacroGenerator::string_fragment) for a
+ /// disambiguation between that function and this one.
+ fn string_conversion(&mut self, expression: Expr) -> Result<()>;
+
+ /// Process a character conversion.
+ fn char_conversion(&mut self, expression: Expr) -> Result<()>;
+}
+
+/// A parsed format string and it's arguments.
+///
+/// `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),*)`
+#[derive(Debug)]
+pub struct FormatAndArgs {
+ format_string: LitStr,
+ parsed: FormatString,
+ args: VecDeque<Expr>,
+}
+
+impl Parse for FormatAndArgs {
+ fn parse(input: ParseStream) -> syn::parse::Result<Self> {
+ let format_string = input.parse::<LitStr>()?;
+
+ let args = if input.is_empty() {
+ // If there are no more tokens, no arguments were specified.
+ VecDeque::new()
+ } else {
+ // Eat the `,` following the format string.
+ input.parse::<Token![,]>()?;
+
+ let punctuated = Punctuated::<Expr, Token![,]>::parse_terminated(input)?;
+ punctuated.into_iter().collect()
+ };
+
+ let parsed = FormatString::parse(&format_string.value()).map_err(|e| {
+ syn::Error::new_spanned(
+ format_string.to_token_stream(),
+ format!("Error parsing format string {e}"),
+ )
+ })?;
+
+ Ok(FormatAndArgs {
+ format_string,
+ parsed,
+ args,
+ })
+ }
+}
+
+// Grab the next argument returning a descriptive error if no more args are left.
+fn next_arg(spec: &ConversionSpec, args: &mut VecDeque<Expr>) -> Result<Expr> {
+ args.pop_front()
+ .ok_or_else(|| Error::new(&format!("No argument given for {spec:?}")))
+}
+
+// Handle a single format conversion specifier (i.e. `%08x`). Grabs the
+// necessary arguments for the specifier from `args` and generates code
+// to marshal the arguments into the buffer declared in `_tokenize_to_buffer`.
+// Returns an error if args is too short of if a format specifier is unsupported.
+fn handle_conversion(
+ generator: &mut dyn FormatMacroGenerator,
+ spec: &ConversionSpec,
+ args: &mut VecDeque<Expr>,
+) -> Result<()> {
+ match spec.specifier {
+ Specifier::Decimal
+ | Specifier::Integer
+ | Specifier::Octal
+ | Specifier::Unsigned
+ | Specifier::Hex
+ | Specifier::UpperHex => {
+ // TODO: b/281862660 - Support Width::Variable and Precision::Variable.
+ if spec.min_field_width == MinFieldWidth::Variable {
+ return Err(Error::new(
+ "Variable width '*' integer formats are not supported.",
+ ));
+ }
+
+ if spec.precision == Precision::Variable {
+ return Err(Error::new(
+ "Variable precision '*' integer formats are not supported.",
+ ));
+ }
+
+ let arg = next_arg(spec, args)?;
+ let bits = match spec.length.unwrap_or(Length::Long) {
+ Length::Char => 8,
+ Length::Short => 16,
+ Length::Long => 32,
+ Length::LongLong => 64,
+ Length::IntMax => 64,
+ Length::Size => 32,
+ Length::PointerDiff => 32,
+ Length::LongDouble => {
+ return Err(Error::new(
+ "Long double length parameter invalid for integer formats",
+ ))
+ }
+ };
+
+ let display: IntegerDisplayType =
+ spec.specifier.clone().try_into().expect(
+ "Specifier is guaranteed to convert display type but enclosing match arm.",
+ );
+ generator.integer_conversion(display, bits, arg)
+ }
+ Specifier::String => {
+ // TODO: b/281862660 - Support Width::Variable and Precision::Variable.
+ if spec.min_field_width == MinFieldWidth::Variable {
+ return Err(Error::new(
+ "Variable width '*' string formats are not supported.",
+ ));
+ }
+
+ if spec.precision == Precision::Variable {
+ return Err(Error::new(
+ "Variable precision '*' string formats are not supported.",
+ ));
+ }
+
+ let arg = next_arg(spec, args)?;
+ generator.string_conversion(arg)
+ }
+ Specifier::Char => {
+ let arg = next_arg(spec, args)?;
+ generator.char_conversion(arg)
+ }
+
+ Specifier::Double
+ | Specifier::UpperDouble
+ | Specifier::Exponential
+ | Specifier::UpperExponential
+ | Specifier::SmallDouble
+ | Specifier::UpperSmallDouble => {
+ // TODO: b/281862328 - Support floating point numbers.
+ Err(Error::new("Floating point numbers are not supported."))
+ }
+
+ // TODO: b/281862333 - Support pointers.
+ Specifier::Pointer => Err(Error::new("Pointer types are not supported.")),
+ }
+}
+
+/// Generate code for a `pw_format` style proc macro.
+///
+/// `generate` takes a [`FormatMacroGenerator`] and a [`FormatAndArgs`] struct
+/// and uses them to produce the code output for a proc macro.
+pub fn generate(
+ mut generator: impl FormatMacroGenerator,
+ format_and_args: FormatAndArgs,
+) -> core::result::Result<TokenStream2, syn::Error> {
+ let mut args = format_and_args.args;
+ let mut errors = Vec::new();
+
+ for fragment in format_and_args.parsed.fragments {
+ let result = match fragment {
+ FormatFragment::Conversion(spec) => handle_conversion(&mut generator, &spec, &mut args),
+ FormatFragment::Literal(string) => generator.string_fragment(&string),
+ FormatFragment::Percent => generator.string_fragment("%"),
+ };
+ if let Err(e) = result {
+ errors.push(syn::Error::new_spanned(
+ format_and_args.format_string.to_token_stream(),
+ e.text,
+ ));
+ }
+ }
+
+ if !errors.is_empty() {
+ return Err(errors
+ .into_iter()
+ .reduce(|mut accumulated_errors, error| {
+ accumulated_errors.combine(error);
+ accumulated_errors
+ })
+ .expect("errors should not be empty"));
+ }
+
+ generator.finalize().map_err(|e| {
+ syn::Error::new_spanned(format_and_args.format_string.to_token_stream(), e.text)
+ })
+}
+
+/// A specialized generator for proc macros that produce `printf` style format strings.
+///
+/// For proc macros that need to translate a `pw_format` invocation into a
+/// `printf` style format string, `PrintfFormatMacroGenerator` offer a
+/// specialized form of [`FormatMacroGenerator`] that builds the format string
+/// and provides it as an argument to
+/// [`finalize`](PrintfFormatMacroGenerator::finalize).
+///
+/// In cases where a generator needs to override the conversion specifier it
+/// can return it from its appropriate conversion method. An example of using
+/// this would be wanting to pass a Rust string directly to a `printf` call
+/// over FFI. In that case,
+/// [`string_conversion`](PrintfFormatMacroGenerator::string_conversion) could
+/// return `Ok(Some("%.*s".to_string()))` to allow both the length and string
+/// pointer to be passed to `printf`.
+pub trait PrintfFormatMacroGenerator {
+ /// Called by [`generate_printf`] at the end of code generation.
+ ///
+ /// Works like [`FormatMacroGenerator::finalize`] with the addition of
+ /// being provided a `printf_style` format string.
+ fn finalize(self, format_string: String) -> Result<TokenStream2>;
+
+ /// Process a string fragment.
+ ///
+ /// **NOTE**: This string may contain unescaped `%` characters.
+ ///
+ /// 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.
+ ///
+ /// May optionally return a printf format string (i.e. "%d") to override the
+ /// default.
+ fn integer_conversion(&mut self, ty: Ident, expression: Expr) -> Result<Option<String>>;
+
+ /// Process a string conversion.
+ ///
+ /// May optionally return a printf format string (i.e. "%s") to override the
+ /// default.
+ ///
+ /// 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.
+ ///
+ /// May optionally return a printf format string (i.e. "%c") to override the
+ /// default.
+ fn char_conversion(&mut self, expression: Expr) -> Result<Option<String>>;
+}
+
+// Wraps a `PrintfFormatMacroGenerator` in a `FormatMacroGenerator` that
+// generates the format string as it goes.
+struct PrintfGenerator<GENERATOR: PrintfFormatMacroGenerator> {
+ inner: GENERATOR,
+ format_string: String,
+}
+
+impl<GENERATOR: PrintfFormatMacroGenerator> FormatMacroGenerator for PrintfGenerator<GENERATOR> {
+ fn finalize(self) -> Result<TokenStream2> {
+ self.inner.finalize(self.format_string)
+ }
+
+ fn string_fragment(&mut self, string: &str) -> Result<()> {
+ // Escape '%' characters.
+ let format_string = string.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 length_modifer = match type_width {
+ 8 => "hh",
+ 16 => "h",
+ 32 => "",
+ 64 => "ll",
+ _ => {
+ return Err(Error::new(&format!(
+ "printf backend does not support {} bit field width",
+ type_width
+ )))
+ }
+ };
+
+ let (conversion, ty) = match display {
+ IntegerDisplayType::Signed => ("d", format_ident!("i{type_width}")),
+ IntegerDisplayType::Unsigned => ("u", 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(&format!("%{}{}", length_modifer, 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("%s"),
+ }
+ 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("%c"),
+ }
+ Ok(())
+ }
+}
+
+/// Generate code for a `pw_format` style proc macro that needs a `printf` format string.
+///
+/// `generate_printf` is a specialized version of [`generate`] which works with
+/// [`PrintfFormatMacroGenerator`]
+pub fn generate_printf(
+ generator: impl PrintfFormatMacroGenerator,
+ format_and_args: FormatAndArgs,
+) -> core::result::Result<TokenStream2, syn::Error> {
+ let generator = PrintfGenerator {
+ inner: generator,
+ format_string: "".into(),
+ };
+ generate(generator, format_and_args)
+}