pw_log: Add core::fmt style format string support to Rust API
Change-Id: Ia48951608a931f74882eaa70b5579e72598b2d87
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/207331
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Reviewed-by: Taylor Cramer <cramertj@google.com>
Pigweed-Auto-Submit: Erik Gilling <konkers@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
diff --git a/pw_log/rust/backend_tests.rs b/pw_log/rust/backend_tests.rs
index c7e9cd0..4539666 100644
--- a/pw_log/rust/backend_tests.rs
+++ b/pw_log/rust/backend_tests.rs
@@ -14,12 +14,16 @@
#[cfg(test)]
mod tests {
use crate::run_with_capture;
- use pw_log_backend::pw_logf_backend;
+ use pw_log_backend::{pw_log_backend, pw_logf_backend};
use pw_log_backend_api::LogLevel;
#[test]
fn no_argument_log_line_prints_to_stdout() {
assert_eq!(
+ run_with_capture(|| pw_log_backend!(LogLevel::Info, "test")),
+ "[INF] test\n"
+ );
+ assert_eq!(
run_with_capture(|| pw_logf_backend!(LogLevel::Info, "test")),
"[INF] test\n"
);
@@ -59,6 +63,11 @@
#[test]
fn untyped_i32_argument_prints_to_stdout() {
assert_eq!(
+ run_with_capture(|| pw_log_backend!(LogLevel::Info, "test {}", -1 as i32)),
+ "[INF] test -1\n",
+ );
+
+ assert_eq!(
run_with_capture(|| pw_logf_backend!(LogLevel::Info, "test %v", -1 as i32)),
"[INF] test -1\n",
);
@@ -66,6 +75,11 @@
#[test]
fn untyped_u32_argument_prints_to_stdout() {
assert_eq!(
+ run_with_capture(|| pw_log_backend!(LogLevel::Info, "test {}", 1 as u32)),
+ "[INF] test 1\n",
+ );
+
+ assert_eq!(
run_with_capture(|| pw_logf_backend!(LogLevel::Info, "test %v", 1 as u32)),
"[INF] test 1\n",
);
@@ -74,6 +88,11 @@
#[test]
fn untyped_str_argument_prints_to_stdout() {
assert_eq!(
+ run_with_capture(|| pw_log_backend!(LogLevel::Info, "test {}", "Pigweed" as &str)),
+ "[INF] test Pigweed\n",
+ );
+
+ assert_eq!(
run_with_capture(|| pw_logf_backend!(LogLevel::Info, "test %v", "Pigweed" as &str)),
"[INF] test Pigweed\n",
);
diff --git a/pw_log/rust/pw_log.rs b/pw_log/rust/pw_log.rs
index a1c58a8..924d91b 100644
--- a/pw_log/rust/pw_log.rs
+++ b/pw_log/rust/pw_log.rs
@@ -21,20 +21,24 @@
//! This flexibility is accomplished using Pigweed's
//! [facade pattern](https://pigweed.dev/docs/facades.html),
//! which uses build-system redirection to forward log invocations to the
-//! configured backend implementaiton.
+//! configured backend implementation.
//!
//! ```
-//! use pw_log::{logf, LogLevel};
+//! use pw_log::{log, info, LogLevel};
//!
-//! logf!(LogLevel::Info, "Thank you for signing up for Log Facts!");
-//! logf!(LogLevel::Info, "Log Fact: Logs can be either %s, %s, or %s sawn.",
-//! "flat", "quarter", "rift");
+//! log!(LogLevel::Info, "Thank you for signing up for Log Facts!");
+//! info!("Log Fact: Logs can be either {}, {}, or {} sawn.",
+//! "flat" as &str, "quarter" as &str, "rift" as &str);
//! ```
//!
-//! Today `printf` style format strings are supported with Rust
-//! [`core::fmt`]/`println!()` style strings planned
+//! Today `printf` style format strings are well supported with Rust
+//! [`core::fmt`]/`println!()` style strings partially supported
//! ([b/311232607](https://issues.pigweed.dev/issues/311232607)).
//!
+//! Currently, when using a `stable` toolchain, "untyped" arguments (i.e.
+//! `{}` style) need to be in the form of an as-cast. Users with nightly
+//! toolchains can enable the `nightly_tait` feature to remove this restriction.
+//!
//! TODO: <pwbug.dev/311266298> - Document `pw_log`'s backend API.
//!
//! TODO: <pwbug.dev/311232605> - Document how to configure facade backends.
@@ -47,13 +51,37 @@
#[doc(hidden)]
pub mod __private {
pub use crate::*;
- pub use pw_log_backend;
- pub use pw_log_backend::pw_logf_backend;
+ // pub use pw_log_backend;
+ pub use pw_log_backend::{pw_log_backend, pw_logf_backend};
+}
+
+/// Emit a log message using `core::fmt` format string semantics.
+///
+/// `log` takes a [`LogLevel`], a `core::fmt` style format string, and necessary
+/// arguments to that string and emits a log message to the logging backend.
+///
+/// ```
+/// use pw_log::{log, LogLevel};
+///
+/// log!(LogLevel::Info, "Log fact: A {} log has a Janka hardness of {} lbf.",
+/// "Spruce Pine" as &str, 700 as i32);
+/// ```
+#[macro_export]
+macro_rules! log {
+ ($log_level:expr, $format_string:literal) => {{
+ use $crate::__private as __pw_log_crate;
+ $crate::__private::pw_log_backend!($log_level, $format_string)
+ }};
+
+ ($log_level:expr, $format_string:literal, $($args:expr),*) => {{
+ use $crate::__private as __pw_log_crate;
+ $crate::__private::pw_log_backend!($log_level, $format_string, $($args),*)
+ }};
}
/// Emit a log message using `printf` format string semantics.
///
-/// `logf` takes a [`LogLevel`], a `printf style format string, and necessary
+/// `logf` takes a [`LogLevel`], a `printf` style format string, and necessary
/// arguments to that string and emits a log message to the logging backend.
///
/// ```
@@ -83,10 +111,26 @@
}}
}
+/// Emit a debug level log message using `core:fmt` format string semantics.
+///
+/// ```
+/// use pw_log::debug;
+///
+/// debug!("Log Fact: The American toy Lincoln Logs were inspired by the {} in {}.",
+/// "Imperial Hotel" as &str, "Tokyo" as &str);
+/// ```
+#[macro_export]
+macro_rules! debug {
+ ($($args:expr),*) => {{
+ use $crate::__private as __pw_log_crate;
+ __pw_log_crate::log!(__pw_log_crate::LogLevel::Debug, $($args),*)
+ }};
+}
+
/// Emit a debug level log message using `printf` format string semantics.
///
/// ```
-/// use pw_log::{debugf, LogLevel};
+/// use pw_log::debugf;
///
/// debugf!("Log Fact: The American toy Lincoln Logs were inspired by the %s in %s.",
/// "Imperial Hotel", "Tokyo");
@@ -107,10 +151,27 @@
}}
}
+/// Emit an info level log message using `core:fmt` format string semantics.
+///
+/// ```
+/// use pw_log::info;
+///
+/// info!(
+/// "Log Fact: The American president Abraham Lincoln (born {}) once lived in a log cabin.",
+/// 1809 as u32);
+/// ```
+#[macro_export]
+macro_rules! info {
+ ($($args:expr),*) => {{
+ use $crate::__private as __pw_log_crate;
+ __pw_log_crate::log!(__pw_log_crate::LogLevel::Info, $($args),*)
+ }};
+}
+
/// Emit an info level log message using `printf` format string semantics.
///
/// ```
-/// use pw_log::{infof, LogLevel};
+/// use pw_log::infof;
///
/// infof!(
/// "Log Fact: The American president Abraham Lincoln (born %x) once lived in a log cabin.",
@@ -132,10 +193,27 @@
}}
}
+/// Emit a warn level log message using `core::fmt` format string semantics.
+///
+/// ```
+/// use pw_log::warn;
+///
+/// warn!(
+/// "Log Fact: Made from a log, an {} year old dugout canoe is the oldest discovered boat in {}.",
+/// 8000 as i32, "Africa" as &str);
+/// ```
+#[macro_export]
+macro_rules! warn {
+ ($($args:expr),*) => {{
+ use $crate::__private as __pw_log_crate;
+ __pw_log_crate::log!(__pw_log_crate::LogLevel::Warn, $($args),*)
+ }};
+}
+
/// Emit a warn level log message using `printf` format string semantics.
///
/// ```
-/// use pw_log::{warnf, LogLevel};
+/// use pw_log::warnf;
///
/// warnf!(
/// "Log Fact: Made from a log, an %d year old dugout canoe is the oldest discovered boat in %s.",
@@ -157,10 +235,27 @@
}}
}
+/// Emit an error level log message using `core::fmt` format string semantics.
+///
+/// ```
+/// use pw_log::error;
+///
+/// error!(
+/// "Log Fact: Before saws were invented, the {} was used prepare logs for use.",
+/// "adze" as &str);
+/// ```
+#[macro_export]
+macro_rules! error {
+ ($($args:expr),*) => {{
+ use $crate::__private as __pw_log_crate;
+ __pw_log_crate::log!(__pw_log_crate::LogLevel::Error, $($args),*)
+ }};
+}
+
/// Emit an error level log message using `printf` format string semantics.
///
/// ```
-/// use pw_log::{errorf, LogLevel};
+/// use pw_log::errorf;
///
/// errorf!(
/// "Log Fact: Before saws were invented, the %s was used prepare logs for use.",
@@ -182,6 +277,23 @@
}}
}
+/// Emit a critical level log message using `core::fmt` format string semantics.
+///
+/// ```
+/// use pw_log::critical;
+///
+/// critical!(
+/// "Log Fact: Until the {}th century, all ships' masts were made from a single log.",
+/// 19 as u32);
+/// ```
+#[macro_export]
+macro_rules! critical {
+ ($($args:expr),*) => {{
+ use $crate::__private as __pw_log_crate;
+ __pw_log_crate::log!(__pw_log_crate::LogLevel::Critical, $($args),*)
+ }};
+}
+
/// Emit a critical level log message using `printf` format string semantics.
///
/// ```
@@ -207,6 +319,23 @@
}}
}
+/// Emit a fatal level log message using `core::fmt` format string semantics.
+///
+/// *Note*: `fatal` only emits a log message and does not cause a `panic!()`
+///
+/// ```
+/// use pw_log::fatal;
+///
+/// fatal!("Log Fact: All out of log facts! Timber!");
+/// ```
+#[macro_export]
+macro_rules! fatal {
+ ($($args:expr),*) => {{
+ use $crate::__private as __pw_log_crate;
+ __pw_log_crate::log!(__pw_log_crate::LogLevel::Fatal, $($args),*)
+ }};
+}
+
/// Emit a fatal level log message using `printf` format string semantics.
///
/// *Note*: `fatalf` only emits a log message and does not cause a `panic!()`
diff --git a/pw_log/rust/pw_log_backend_printf/lib.rs b/pw_log/rust/pw_log_backend_printf/lib.rs
index f67abdb..704003e 100644
--- a/pw_log/rust/pw_log_backend_printf/lib.rs
+++ b/pw_log/rust/pw_log_backend_printf/lib.rs
@@ -29,7 +29,6 @@
//! TODO: <pwbug.dev/311232605> - Document how to configure facade backends.
#![deny(missing_docs)]
-pub use pw_log_backend_printf_macro::_pw_logf_backend;
pub mod varargs;
@@ -39,7 +38,7 @@
use core::ffi::{c_int, c_uchar};
pub use pw_bytes::concat_static_strs;
- pub use pw_log_backend_printf_macro::_pw_logf_backend;
+ pub use pw_log_backend_printf_macro::{_pw_log_backend, _pw_logf_backend};
use pw_log_backend_api::LogLevel;
@@ -123,10 +122,19 @@
/// Implements the `pw_log` backend api.
#[macro_export]
+macro_rules! pw_log_backend {
+ ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{
+ use $crate::__private as __pw_log_backend_crate;
+ $crate::__private::_pw_log_backend!($log_level, $format_string, $($args),*)
+ }};
+}
+
+/// Implements the `pw_log` backend api.
+#[macro_export]
macro_rules! pw_logf_backend {
($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{
use $crate::__private as __pw_log_backend_crate;
- $crate::_pw_logf_backend!($log_level, $format_string, $($args),*)
+ $crate::__private::_pw_logf_backend!($log_level, $format_string, $($args),*)
}};
}
diff --git a/pw_log/rust/pw_log_backend_printf_macro.rs b/pw_log/rust/pw_log_backend_printf_macro.rs
index 992bf9d..75f58cc 100644
--- a/pw_log/rust/pw_log_backend_printf_macro.rs
+++ b/pw_log/rust/pw_log_backend_printf_macro.rs
@@ -21,27 +21,27 @@
};
use pw_format::macros::{
- generate_printf, Arg, FormatAndArgsFlavor, PrintfFormatMacroGenerator,
- PrintfFormatStringFragment, PrintfFormatStringParser, Result,
+ generate_printf, Arg, CoreFmtFormatStringParser, FormatAndArgsFlavor, FormatStringParser,
+ PrintfFormatMacroGenerator, PrintfFormatStringFragment, PrintfFormatStringParser, Result,
};
type TokenStream2 = proc_macro2::TokenStream;
-// Arguments to `pw_logf_backend`. A log level followed by a [`pw_format`]
+// Arguments to `pw_log[f]_backend`. A log level followed by a [`pw_format`]
// format string.
#[derive(Debug)]
-struct PwLogfArgs {
+struct PwLogArgs<T: FormatStringParser> {
log_level: Expr,
- format_and_args: FormatAndArgsFlavor<PrintfFormatStringParser>,
+ format_and_args: FormatAndArgsFlavor<T>,
}
-impl Parse for PwLogfArgs {
+impl<T: FormatStringParser> Parse for PwLogArgs<T> {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
let log_level: Expr = input.parse()?;
input.parse::<Token![,]>()?;
let format_and_args: FormatAndArgsFlavor<_> = input.parse()?;
- Ok(PwLogfArgs {
+ Ok(PwLogArgs {
log_level,
format_and_args,
})
@@ -148,8 +148,20 @@
}
#[proc_macro]
+pub fn _pw_log_backend(tokens: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(tokens as PwLogArgs<CoreFmtFormatStringParser>);
+
+ let generator = LogfGenerator::new(&input.log_level);
+
+ match generate_printf(generator, input.format_and_args.into()) {
+ Ok(token_stream) => token_stream.into(),
+ Err(e) => e.to_compile_error().into(),
+ }
+}
+
+#[proc_macro]
pub fn _pw_logf_backend(tokens: TokenStream) -> TokenStream {
- let input = parse_macro_input!(tokens as PwLogfArgs);
+ let input = parse_macro_input!(tokens as PwLogArgs<PrintfFormatStringParser>);
let generator = LogfGenerator::new(&input.log_level);
diff --git a/pw_log/rust/pw_log_backend_println.rs b/pw_log/rust/pw_log_backend_println.rs
index 3f4ecdc..f8a6704 100644
--- a/pw_log/rust/pw_log_backend_println.rs
+++ b/pw_log/rust/pw_log_backend_println.rs
@@ -19,11 +19,13 @@
//! *Note*: This modules requires `std`.
//!
//! TODO: <pwbug.dev/311232605> - Document how to configure facade backends.
-pub use pw_log_backend_println_macro::_pw_logf_backend;
#[doc(hidden)]
pub mod __private {
+ pub use pw_log_backend_println_macro::{_pw_log_backend, _pw_logf_backend};
+
use pw_log_backend_api::LogLevel;
+
pub const fn log_level_tag(level: LogLevel) -> &'static str {
match level {
LogLevel::Debug => "DBG",
@@ -38,9 +40,17 @@
// Implement the `pw_log` backend API.
#[macro_export]
+macro_rules! pw_log_backend {
+ ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{
+ use $crate::__private as __pw_log_backend_crate;
+ $crate::__private::_pw_log_backend!($log_level, $format_string, $($args),*);
+ }};
+}
+
+#[macro_export]
macro_rules! pw_logf_backend {
($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{
use $crate::__private as __pw_log_backend_crate;
- $crate::_pw_logf_backend!($log_level, $format_string, $($args),*);
+ $crate::__private::_pw_logf_backend!($log_level, $format_string, $($args),*);
}};
}
diff --git a/pw_log/rust/pw_log_backend_println_macro.rs b/pw_log/rust/pw_log_backend_println_macro.rs
index 48a7163..35e43a5 100644
--- a/pw_log/rust/pw_log_backend_println_macro.rs
+++ b/pw_log/rust/pw_log_backend_println_macro.rs
@@ -21,27 +21,27 @@
};
use pw_format::macros::{
- generate_core_fmt, Arg, CoreFmtFormatMacroGenerator, FormatAndArgsFlavor,
- PrintfFormatStringParser, Result,
+ generate_core_fmt, Arg, CoreFmtFormatMacroGenerator, CoreFmtFormatStringParser,
+ FormatAndArgsFlavor, FormatStringParser, PrintfFormatStringParser, Result,
};
type TokenStream2 = proc_macro2::TokenStream;
-// Arguments to `pw_logf_backend`. A log level followed by a [`pw_format`]
+// Arguments to `pw_log[f]_backend`. A log level followed by a [`pw_format`]
// format string.
#[derive(Debug)]
-struct PwLogfArgs {
+struct PwLogArgs<T: FormatStringParser> {
log_level: Expr,
- format_and_args: FormatAndArgsFlavor<PrintfFormatStringParser>,
+ format_and_args: FormatAndArgsFlavor<T>,
}
-impl Parse for PwLogfArgs {
+impl<T: FormatStringParser> Parse for PwLogArgs<T> {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
let log_level: Expr = input.parse()?;
input.parse::<Token![,]>()?;
let format_and_args: FormatAndArgsFlavor<_> = input.parse()?;
- Ok(PwLogfArgs {
+ Ok(PwLogArgs {
log_level,
format_and_args,
})
@@ -106,8 +106,20 @@
}
#[proc_macro]
+pub fn _pw_log_backend(tokens: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(tokens as PwLogArgs<CoreFmtFormatStringParser>);
+
+ let generator = LogfGenerator::new(&input.log_level);
+
+ match generate_core_fmt(generator, input.format_and_args.into()) {
+ Ok(token_stream) => token_stream.into(),
+ Err(e) => e.to_compile_error().into(),
+ }
+}
+
+#[proc_macro]
pub fn _pw_logf_backend(tokens: TokenStream) -> TokenStream {
- let input = parse_macro_input!(tokens as PwLogfArgs);
+ let input = parse_macro_input!(tokens as PwLogArgs<PrintfFormatStringParser>);
let generator = LogfGenerator::new(&input.log_level);
diff --git a/pw_rust/examples/tokenized_logging/main.rs b/pw_rust/examples/tokenized_logging/main.rs
index 74f6ea6..70de7b2 100644
--- a/pw_rust/examples/tokenized_logging/main.rs
+++ b/pw_rust/examples/tokenized_logging/main.rs
@@ -30,17 +30,27 @@
// Semihosting support which is well supported for QEMU targets.
use cortex_m_semihosting::{debug, hprintln};
-use pw_log::{infof, warnf};
+use pw_log::{critical, infof, warnf};
#[entry]
fn main() -> ! {
// Plain text printout without `pw_log`
hprintln!("Hello, Pigweed!");
- // `pw_log` messages
+ // printf style `pw_log` messages
infof!("Bare string");
warnf!("Integer value %d", 42);
- infof!("generic arguments %v %v", 42 as u32, -42 as i32);
+
+ // core::fmt style `pw_log` messages
+ //
+ // Note the support for this is in progress with the following notes:
+ // * only `u32`, `i32`, and '&str' arguments are supported right now.
+ // * arguments must be annotated with `as <type>` to work with `stable`
+ // toolchains.
+ //
+ // Both of these will be addressed in the future.
+ critical!("Generic rusty arguments {} {}", 42 as u32, -42 as i32);
+
debug::exit(debug::EXIT_SUCCESS);
loop {}
}
diff --git a/pw_rust/examples/tokenized_logging/pw_log_backend.rs b/pw_rust/examples/tokenized_logging/pw_log_backend.rs
index 96a5c0a..d3c84c0 100644
--- a/pw_rust/examples/tokenized_logging/pw_log_backend.rs
+++ b/pw_rust/examples/tokenized_logging/pw_log_backend.rs
@@ -26,7 +26,7 @@
use pw_tokenizer::MessageWriter;
// Re-export for use by the `pw_logf_backend!` macro.
- pub use pw_tokenizer::tokenize_to_writer;
+ pub use pw_tokenizer::{tokenize_core_fmt_to_writer, tokenize_printf_to_writer};
const ENCODE_BUFFER_SIZE: usize = 32;
@@ -80,16 +80,29 @@
}
// Implement the `pw_log` backend API.
+//
+// Since we're logging to a shared/ambient resource we can use
+// tokenize_*_to_writer!` instead of `tokenize_*_to_buffer!` and avoid the
+// overhead of initializing any intermediate buffers or objects.
+//
+// Uses `pw_format` special `PW_FMT_CONCAT` operator to prepend a place to
+// print the log level.
+#[macro_export]
+macro_rules! pw_log_backend {
+ ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{
+ let _ = $crate::__private::tokenize_core_fmt_to_writer!(
+ $crate::__private::LogMessageWriter,
+ "[{}] " PW_FMT_CONCAT $format_string,
+ $crate::__private::log_level_tag($log_level) as &str,
+ $($args),*);
+ }};
+}
+
#[macro_export]
macro_rules! pw_logf_backend {
($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{
- // Since we're logging to a shared/ambient resource we can use
- // tokenize_to_writer!` instead of `tokenize_to_buffer!` and avoid the
- // overhead of initializing any intermediate buffers or objects.
- let _ = $crate::__private::tokenize_to_writer!(
+ let _ = $crate::__private::tokenize_printf_to_writer!(
$crate::__private::LogMessageWriter,
- // Use `pw_format` special `PW_FMT_CONCAT` operator to prepend a place to
- // print the log level.
"[%s] " PW_FMT_CONCAT $format_string,
$crate::__private::log_level_tag($log_level),
$($args),*);
diff --git a/pw_tokenizer/rust/pw_tokenizer/lib.rs b/pw_tokenizer/rust/pw_tokenizer/lib.rs
index 23c0133..faee9a2 100644
--- a/pw_tokenizer/rust/pw_tokenizer/lib.rs
+++ b/pw_tokenizer/rust/pw_tokenizer/lib.rs
@@ -23,21 +23,35 @@
//! For a more in depth explanation of the systems design and motivations,
//! see [Pigweed's pw_tokenizer module documentation](https://pigweed.dev/pw_tokenizer/).
//!
-//! # Example
+//! # Examples
+//!
+//! Pigweed's tokenization database uses `printf` style strings internally so
+//! those are supported directly.
//!
//! ```
-//! use pw_tokenizer::tokenize_to_buffer;
+//! use pw_tokenizer::tokenize_printf_to_buffer;
//!
-//! # fn doctest() -> pw_status::Result<()> {
//! let mut buffer = [0u8; 1024];
-//! let len = tokenize_to_buffer!(&mut buffer, "The answer is %d", 42)?;
+//! let len = tokenize_printf_to_buffer!(&mut buffer, "The answer is %d", 42)?;
//!
//! // 4 bytes used to encode the token and one to encode the value 42. This
//! // is a **3.5x** reduction in size compared to the raw string!
//! assert_eq!(len, 5);
-//! # Ok(())
-//! # }
-//! # doctest().unwrap();
+//! # Ok::<(), pw_status::Error>(())
+//! ```
+//!
+//! We also support Rust's `core::fmt` style syntax. These format strings are
+//! converted to `printf` style at compile time to maintain compatibly with the
+//! rest of the Pigweed tokenizer ecosystem. The below example produces the
+//! same token and output as the above one.
+//!
+//! ```
+//! use pw_tokenizer::tokenize_core_fmt_to_buffer;
+//!
+//! let mut buffer = [0u8; 1024];
+//! let len = tokenize_core_fmt_to_buffer!(&mut buffer, "The answer is {}", 42 as i32)?;
+//! assert_eq!(len, 5);
+//! # Ok::<(), pw_status::Error>(())
//! ```
#![cfg_attr(not(feature = "std"), no_std)]
@@ -63,7 +77,10 @@
pub use pw_status::Result;
pub use pw_stream::{Cursor, Seek, WriteInteger, WriteVarint};
pub use pw_tokenizer_core::hash_string;
- pub use pw_tokenizer_macro::{_token, _tokenize_to_buffer, _tokenize_to_writer};
+ pub use pw_tokenizer_macro::{
+ _token, _tokenize_core_fmt_to_buffer, _tokenize_core_fmt_to_writer,
+ _tokenize_printf_to_buffer, _tokenize_printf_to_writer,
+ };
}
/// Return the [`u32`] token for the specified string and add it to the token
@@ -101,8 +118,9 @@
}};
}
-/// Tokenize a format string and arguments to an [`AsMut<u8>`] buffer and add
-/// the format string's token to the token database.
+/// Tokenize a `core::fmt` style format string and arguments to an [`AsMut<u8>`]
+/// buffer. The format string is converted in to a `printf` and added token to
+/// the token database.
///
/// See [`token`] for an explanation on how strings are tokenized and entries
/// are added to the token database.
@@ -120,18 +138,18 @@
/// # Example
///
/// ```
-/// use pw_tokenizer::tokenize_to_buffer;
+/// use pw_tokenizer::tokenize_core_fmt_to_buffer;
///
/// // Tokenize a format string and argument into a buffer.
/// let mut buffer = [0u8; 1024];
-/// let len = tokenize_to_buffer!(&mut buffer, "The answer is %d", 42)?;
+/// let len = tokenize_core_fmt_to_buffer!(&mut buffer, "The answer is {}", 42 as i32)?;
///
/// // 4 bytes used to encode the token and one to encode the value 42.
/// assert_eq!(len, 5);
///
/// // The format string can be composed of multiple strings literals using
/// // the custom`PW_FMT_CONCAT` operator.
-/// let len = tokenize_to_buffer!(&mut buffer, "Hello " PW_FMT_CONCAT "Pigweed")?;
+/// let len = tokenize_core_fmt_to_buffer!(&mut buffer, "Hello " PW_FMT_CONCAT "Pigweed")?;
///
/// // Only a single 4 byte token is emitted after concatenation of the string
/// // literals above.
@@ -139,10 +157,63 @@
/// # Ok::<(), pw_status::Error>(())
/// ```
#[macro_export]
-macro_rules! tokenize_to_buffer {
+macro_rules! tokenize_core_fmt_to_buffer {
($buffer:expr, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
use $crate::__private as __pw_tokenizer_crate;
- __pw_tokenizer_crate::_tokenize_to_buffer!($buffer, $($format_string)PW_FMT_CONCAT+, $($args),*)
+ __pw_tokenizer_crate::_tokenize_core_fmt_to_buffer!($buffer, $($format_string)PW_FMT_CONCAT+, $($args),*)
+ }};
+}
+
+/// Tokenize a printf format string and arguments to an [`AsMut<u8>`] buffer
+/// and add the format string's token to the token database.
+///
+/// See [`token`] for an explanation on how strings are tokenized and entries
+/// are added to the token database. The token's domain is set to "".
+///
+/// Returns a [`pw_status::Result<usize>`] the number of bytes written to the buffer.
+///
+/// `tokenize_to_buffer!` supports concatenation of format strings as described
+/// in [`pw_format::macros::FormatAndArgs`].
+///
+/// # Errors
+/// - [`pw_status::Error::OutOfRange`] - Buffer is not large enough to fit
+/// tokenized data.
+/// - [`pw_status::Error::InvalidArgument`] - Invalid buffer was provided.
+///
+/// # Example
+///
+/// ```
+/// use pw_tokenizer::tokenize_printf_to_buffer;
+///
+/// // Tokenize a format string and argument into a buffer.
+/// let mut buffer = [0u8; 1024];
+/// let len = tokenize_printf_to_buffer!(&mut buffer, "The answer is %d", 42)?;
+///
+/// // 4 bytes used to encode the token and one to encode the value 42.
+/// assert_eq!(len, 5);
+///
+/// // The format string can be composed of multiple strings literals using
+/// // the custom`PW_FMT_CONCAT` operator.
+/// let len = tokenize_printf_to_buffer!(&mut buffer, "Hello " PW_FMT_CONCAT "Pigweed")?;
+///
+/// // Only a single 4 byte token is emitted after concatenation of the string
+/// // literals above.
+/// assert_eq!(len, 4);
+/// # Ok::<(), pw_status::Error>(())
+/// ```
+#[macro_export]
+macro_rules! tokenize_printf_to_buffer {
+ ($buffer:expr, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
+ use $crate::__private as __pw_tokenizer_crate;
+ __pw_tokenizer_crate::_tokenize_printf_to_buffer!($buffer, $($format_string)PW_FMT_CONCAT+, $($args),*)
+ }};
+}
+
+/// Deprecated alias for [`tokenize_printf_to_buffer!`].
+#[macro_export]
+macro_rules! tokenize_to_buffer {
+ ($buffer:expr, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
+ $crate::tokenize_printf_to_buffer!($buffer, $($format_string)PW_FMT_CONCAT+, $($args),*)
}};
}
@@ -155,7 +226,7 @@
/// UART, or a shared buffer.
///
/// See [`token`] for an explanation on how strings are tokenized and entries
-/// are added to the token database.
+/// are added to the token database. The token's domain is set to "".
///
/// Returns a [`pw_status::Result<()>`].
///
@@ -222,13 +293,30 @@
/// # Ok::<(), pw_status::Error>(())
/// ```
#[macro_export]
-macro_rules! tokenize_to_writer {
+macro_rules! tokenize_core_fmt_to_writer {
($ty:ty, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
use $crate::__private as __pw_tokenizer_crate;
- __pw_tokenizer_crate::_tokenize_to_writer!($ty, $($format_string)PW_FMT_CONCAT+, $($args),*)
+ __pw_tokenizer_crate::_tokenize_core_fmt_to_writer!($ty, $($format_string)PW_FMT_CONCAT+, $($args),*)
}};
}
+/// DOCME
+#[macro_export]
+macro_rules! tokenize_printf_to_writer {
+ ($ty:ty, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
+ use $crate::__private as __pw_tokenizer_crate;
+ __pw_tokenizer_crate::_tokenize_printf_to_writer!($ty, $($format_string)PW_FMT_CONCAT+, $($args),*)
+ }};
+}
+
+/// Deprecated alias for [`tokenize_printf_to_writer!`].
+#[macro_export]
+macro_rules! tokenize_to_writer {
+ ($ty:ty, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
+ $crate::tokenize_printf_to_writer!($ty, $($format_string)PW_FMT_CONCAT+, $($args),*)
+ }};
+}
+
/// A trait used by [`tokenize_to_writer!`] to output tokenized messages.
///
/// For more details on how this type is used, see the [`tokenize_to_writer!`]
@@ -265,18 +353,30 @@
fn test_token() {}
macro_rules! tokenize_to_buffer_test {
- ($expected_data:expr, $buffer_len:expr, $fmt:expr $(, $args:expr)* $(,)?) => {{
- let mut buffer = [0u8; $buffer_len];
- let len = tokenize_to_buffer!(&mut buffer, $fmt, $($args),*).unwrap();
- assert_eq!(
- &buffer[..len],
- $expected_data,
- );
+ ($expected_data:expr, $buffer_len:expr, $printf_fmt:literal, $core_fmt:literal $(, $args:expr)* $(,)?) => {{
+ if $printf_fmt != "" {
+ let mut buffer = [0u8; $buffer_len];
+ let len = tokenize_printf_to_buffer!(&mut buffer, $printf_fmt, $($args),*).unwrap();
+ assert_eq!(
+ &buffer[..len],
+ $expected_data,
+ "printf style input does not produce expected output",
+ );
+ }
+ if $core_fmt != "" {
+ let mut buffer = [0u8; $buffer_len];
+ let len = tokenize_core_fmt_to_buffer!(&mut buffer, $core_fmt, $($args),*).unwrap();
+ assert_eq!(
+ &buffer[..len],
+ $expected_data,
+ "core::fmt style input does not produce expected output",
+ );
+ }
}}
}
macro_rules! tokenize_to_writer_test {
- ($expected_data:expr, $buffer_len:expr, $fmt:expr $(, $args:expr)* $(,)?) => {{
+ ($expected_data:expr, $buffer_len:expr, $printf_fmt:literal, $core_fmt:literal $(, $args:expr)* $(,)?) => {{
// The `MessageWriter` API is used in places like logging where it
// accesses an shared/ambient resource (like stdio or an UART). To test
// it in a hermetic way we declare test specific `MessageWriter` that
@@ -315,20 +415,34 @@
}
}
- tokenize_to_writer!(TestMessageWriter, $fmt, $($args),*).unwrap();
- TEST_OUTPUT.with(|output| {
- assert_eq!(
- *output.borrow(),
- Some($expected_data.to_vec()),
- )
- });
+ if $printf_fmt != "" {
+ TEST_OUTPUT.with(|output| *output.borrow_mut() = None);
+ tokenize_printf_to_writer!(TestMessageWriter, $printf_fmt, $($args),*).unwrap();
+ TEST_OUTPUT.with(|output| {
+ assert_eq!(
+ *output.borrow(),
+ Some($expected_data.to_vec()),
+ )
+ });
+ }
+
+ if $core_fmt != "" {
+ TEST_OUTPUT.with(|output| *output.borrow_mut() = None);
+ tokenize_core_fmt_to_writer!(TestMessageWriter, $core_fmt, $($args),*).unwrap();
+ TEST_OUTPUT.with(|output| {
+ assert_eq!(
+ *output.borrow(),
+ Some($expected_data.to_vec()),
+ )
+ });
+ }
}}
}
macro_rules! tokenize_test {
- ($expected_data:expr, $buffer_len:expr, $fmt:expr $(, $args:expr)* $(,)?) => {{
- tokenize_to_buffer_test!($expected_data, $buffer_len, $fmt, $($args),*);
- tokenize_to_writer_test!($expected_data, $buffer_len, $fmt, $($args),*);
+ ($expected_data:expr, $buffer_len:expr, $printf_fmt:literal, $core_fmt:literal $(, $args:expr)* $(,)?) => {{
+ tokenize_to_buffer_test!($expected_data, $buffer_len, $printf_fmt, $core_fmt, $($args),*);
+ tokenize_to_writer_test!($expected_data, $buffer_len, $printf_fmt, $core_fmt, $($args),*);
}};
}
@@ -337,30 +451,38 @@
tokenize_test!(
&[0xe0, 0x92, 0xe0, 0xa], // expected buffer
64, // buffer size
- "Hello Pigweed",
+ "Hello Pigweed", // printf style
+ "Hello Pigweed", // core::fmt style
);
}
+
#[test]
fn test_decimal_format() {
+ // "as casts" are used for the integer arguments below. They are only
+ // need for the core::fmt style arguments but are added so that we can
+ // check that the printf and core::fmt style equivalents encode the same.
tokenize_test!(
&[0x52, 0x1c, 0xb0, 0x4c, 0x2], // expected buffer
64, // buffer size
- "The answer is %d!",
- 1
+ "The answer is %d!", // printf style
+ "The answer is {}!", // core::fmt style
+ 1 as i32
);
tokenize_test!(
&[0x36, 0xd0, 0xfb, 0x69, 0x1], // expected buffer
64, // buffer size
- "No! The answer is %d!",
- -1
+ "No! The answer is %d!", // printf style
+ "No! The answer is {}!", // core::fmt style
+ -1 as i32
);
tokenize_test!(
- &[0xa4, 0xad, 0x50, 0x54, 0x0], // expected buffer
- 64, // buffer size
- "I think you'll find that the answer is %d!",
- 0
+ &[0xa4, 0xad, 0x50, 0x54, 0x0], // expected buffer
+ 64, // buffer size
+ "I think you'll find that the answer is %d!", // printf style
+ "I think you'll find that the answer is {}!", // core::fmt style
+ 0 as i32
);
}
@@ -370,7 +492,8 @@
tokenize_test!(
&[0x52, 0x1c, 0xb0, 0x4c, 0x2], // expected buffer
64, // buffer size
- "The answer is %d!",
+ "The answer is %d!", // printf style
+ "", // no equivalent core::fmt style
1
);
@@ -379,35 +502,40 @@
tokenize_test!(
&[0x52, 0x1c, 0xb0, 0x4c, 0x2], // expected buffer
64, // buffer size
- "The answer is %i!",
+ "The answer is %i!", // printf style
+ "", // no equivalent core::fmt style
1
);
tokenize_test!(
&[0x5d, 0x70, 0x12, 0xb4, 0x2], // expected buffer
64, // buffer size
- "The answer is %o!",
+ "The answer is %o!", // printf style
+ "", // no equivalent core::fmt style
1u32
);
tokenize_test!(
&[0x63, 0x58, 0x5f, 0x8f, 0x2], // expected buffer
64, // buffer size
- "The answer is %u!",
+ "The answer is %u!", // printf style
+ "", // no equivalent core::fmt style
1u32
);
tokenize_test!(
&[0x66, 0xcc, 0x05, 0x7d, 0x2], // expected buffer
64, // buffer size
- "The answer is %x!",
+ "The answer is %x!", // printf style
+ "", // no equivalent core::fmt style
1u32
);
tokenize_test!(
&[0x46, 0x4c, 0x16, 0x96, 0x2], // expected buffer
64, // buffer size
- "The answer is %X!",
+ "The answer is %X!", // printf style
+ "", // no equivalent core::fmt style
1u32
);
}
@@ -417,7 +545,8 @@
tokenize_test!(
b"\x25\xf6\x2e\x66\x07Pigweed", // expected buffer
64, // buffer size
- "Hello: %s!",
+ "Hello: %s!", // printf style
+ "", // no equivalent core::fmt style
"Pigweed"
);
}
@@ -427,7 +556,8 @@
tokenize_test!(
b"\x25\xf6\x2e\x66\x83Pig", // expected buffer
8, // buffer size
- "Hello: %s!",
+ "Hello: %s!", // printf style
+ "", // no equivalent core::fmt style
"Pigweed"
);
}
@@ -437,22 +567,66 @@
tokenize_test!(
&[0x2e, 0x52, 0xac, 0xe4, 0x50], // expected buffer
64, // buffer size
- "Hello: %cigweed",
+ "Hello: %cigweed", // printf style
+ "", // no equivalent core::fmt style
"P".as_bytes()[0]
);
}
#[test]
- fn tokenizer_supports_concatenated_format_strings() {
+ fn test_untyped_format() {
+ tokenize_test!(
+ &[0x63, 0x58, 0x5f, 0x8f, 0x2], // expected buffer
+ 64, // buffer size
+ "The answer is %u!", // printf style
+ "The answer is {}!", // core::fmt style
+ 1 as u32
+ );
+
+ tokenize_test!(
+ &[0x36, 0xd0, 0xfb, 0x69, 0x1], // expected buffer
+ 64, // buffer size
+ "No! The answer is %v!", // printf style
+ "No! The answer is {}!", // core::fmt style
+ -1 as i32
+ );
+
+ tokenize_test!(
+ b"\x25\xf6\x2e\x66\x07Pigweed", // expected buffer
+ 64, // buffer size
+ "Hello: %v!", // printf style
+ "Hello: {}!", // core::fmt style
+ "Pigweed" as &str
+ );
+ }
+
+ #[test]
+ fn tokenizer_supports_concatenated_printf_format_strings() {
// Since the no argument and some arguments cases are handled differently
// by `tokenize_to_buffer!` we need to test both.
let mut buffer = [0u8; 64];
- let len = tokenize_to_buffer!(&mut buffer, "Hello" PW_FMT_CONCAT " Pigweed").unwrap();
+ let len =
+ tokenize_printf_to_buffer!(&mut buffer, "Hello" PW_FMT_CONCAT " Pigweed").unwrap();
assert_eq!(&buffer[..len], &[0xe0, 0x92, 0xe0, 0xa]);
- let len = tokenize_to_buffer!(&mut buffer, "Hello: " PW_FMT_CONCAT "%cigweed",
+ let len = tokenize_printf_to_buffer!(&mut buffer, "Hello: " PW_FMT_CONCAT "%cigweed",
"P".as_bytes()[0])
.unwrap();
assert_eq!(&buffer[..len], &[0x2e, 0x52, 0xac, 0xe4, 0x50]);
}
+
+ #[test]
+ fn tokenizer_supports_concatenated_core_fmt_format_strings() {
+ // Since the no argument and some arguments cases are handled differently
+ // by `tokenize_to_buffer!` we need to test both.
+ let mut buffer = [0u8; 64];
+ let len =
+ tokenize_core_fmt_to_buffer!(&mut buffer, "Hello" PW_FMT_CONCAT " Pigweed").unwrap();
+ assert_eq!(&buffer[..len], &[0xe0, 0x92, 0xe0, 0xa]);
+
+ let len = tokenize_core_fmt_to_buffer!(&mut buffer, "The answer is " PW_FMT_CONCAT "{}!",
+ 1 as i32)
+ .unwrap();
+ assert_eq!(&buffer[..len], &[0x52, 0x1c, 0xb0, 0x4c, 0x2]);
+ }
}
diff --git a/pw_tokenizer/rust/pw_tokenizer_macro.rs b/pw_tokenizer/rust/pw_tokenizer_macro.rs
index 9503dda..4b4db40 100644
--- a/pw_tokenizer/rust/pw_tokenizer_macro.rs
+++ b/pw_tokenizer/rust/pw_tokenizer_macro.rs
@@ -26,8 +26,8 @@
};
use pw_format::macros::{
- generate_printf, Arg, FormatAndArgsFlavor, PrintfFormatMacroGenerator,
- PrintfFormatStringFragment, PrintfFormatStringParser, Result,
+ generate_printf, Arg, CoreFmtFormatStringParser, FormatAndArgsFlavor, FormatStringParser,
+ PrintfFormatMacroGenerator, PrintfFormatStringFragment, PrintfFormatStringParser, Result,
};
use pw_tokenizer_core::TOKENIZER_ENTRY_MAGIC;
@@ -102,12 +102,12 @@
// Args to tokenize to buffer that are parsed according to the pattern:
// ($buffer:expr, $format_string:literal, $($args:expr),*)
#[derive(Debug)]
-struct TokenizeToBufferArgs {
+struct TokenizeToBufferArgs<T: FormatStringParser + core::fmt::Debug> {
buffer: Expr,
- format_and_args: FormatAndArgsFlavor<PrintfFormatStringParser>,
+ format_and_args: FormatAndArgsFlavor<T>,
}
-impl Parse for TokenizeToBufferArgs {
+impl<T: FormatStringParser + core::fmt::Debug> Parse for TokenizeToBufferArgs<T> {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
let buffer: Expr = input.parse()?;
input.parse::<Token![,]>()?;
@@ -205,20 +205,40 @@
fn untyped_conversion(&mut self, expression: Arg) -> Result<()> {
self.encoding_fragments.push(quote! {
- __pw_tokenizer_crate::internal::Encoder::encode(#expression, &mut cursor)?;
+ Argument::from(#expression)
});
Ok(())
}
}
-// Generates code to marshal a tokenized string and arguments into a buffer.
-// See [`pw_tokenizer::tokenize_to_buffer`] for details on behavior.
-//
-// Internally the [`AsMut<u8>`] is wrapped in a [`pw_stream::Cursor`] to
-// fill the buffer incrementally.
+/// Generates code to marshal a tokenized core::fmt format string and arguments
+/// into a buffer. See [`pw_tokenizer::tokenize_core_fmt_to_buffer`] for details
+/// on behavior.
+///
+/// Internally the [`AsMut<u8>`] is wrapped in a [`pw_stream::Cursor`] to
+/// fill the buffer incrementally.
#[proc_macro]
-pub fn _tokenize_to_buffer(tokens: TokenStream) -> TokenStream {
- let input = parse_macro_input!(tokens as TokenizeToBufferArgs);
+pub fn _tokenize_core_fmt_to_buffer(tokens: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(tokens as TokenizeToBufferArgs<CoreFmtFormatStringParser>);
+
+ // Hard codes domain to "".
+ let generator = TokenizeToBufferGenerator::new("", &input.buffer);
+
+ match generate_printf(generator, input.format_and_args.into()) {
+ Ok(token_stream) => token_stream.into(),
+ Err(e) => e.to_compile_error().into(),
+ }
+}
+
+/// Generates code to marshal a tokenized printf format string and arguments
+/// into a buffer. See [`pw_tokenizer::tokenize_printf_to_buffer`] for details
+/// on behavior.
+///
+/// Internally the [`AsMut<u8>`] is wrapped in a [`pw_stream::Cursor`] to
+/// fill the buffer incrementally.
+#[proc_macro]
+pub fn _tokenize_printf_to_buffer(tokens: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(tokens as TokenizeToBufferArgs<PrintfFormatStringParser>);
// Hard codes domain to "".
let generator = TokenizeToBufferGenerator::new("", &input.buffer);
@@ -232,12 +252,12 @@
// Args to tokenize to buffer that are parsed according to the pattern:
// ($ty:ty, $format_string:literal, $($args:expr),*)
#[derive(Debug)]
-struct TokenizeToWriterArgs {
+struct TokenizeToWriterArgs<T: FormatStringParser> {
ty: Type,
- format_and_args: FormatAndArgsFlavor<PrintfFormatStringParser>,
+ format_and_args: FormatAndArgsFlavor<T>,
}
-impl Parse for TokenizeToWriterArgs {
+impl<T: FormatStringParser> Parse for TokenizeToWriterArgs<T> {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
let ty: Type = input.parse()?;
input.parse::<Token![,]>()?;
@@ -340,9 +360,28 @@
}
}
+/// Generates code to marshal a tokenized core::fmt format string and arguments
+/// into a [`pw_stream::Write`]. See [`pw_tokenizer::tokenize_core_fmt_to_writer`]
+/// for details on behavior.
#[proc_macro]
-pub fn _tokenize_to_writer(tokens: TokenStream) -> TokenStream {
- let input = parse_macro_input!(tokens as TokenizeToWriterArgs);
+pub fn _tokenize_core_fmt_to_writer(tokens: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(tokens as TokenizeToWriterArgs<CoreFmtFormatStringParser>);
+
+ // Hard codes domain to "".
+ let generator = TokenizeToWriterGenerator::new("", &input.ty);
+
+ match generate_printf(generator, input.format_and_args.into()) {
+ Ok(token_stream) => token_stream.into(),
+ Err(e) => e.to_compile_error().into(),
+ }
+}
+
+/// Generates code to marshal a tokenized printf format string and arguments
+/// into a [`pw_stream::Write`]. See [`pw_tokenizer::tokenize_printf_to_writer`]
+/// for details on behavior.
+#[proc_macro]
+pub fn _tokenize_printf_to_writer(tokens: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(tokens as TokenizeToWriterArgs<PrintfFormatStringParser>);
// Hard codes domain to "".
let generator = TokenizeToWriterGenerator::new("", &input.ty);
diff --git a/pw_toolchain/rust/defs.bzl b/pw_toolchain/rust/defs.bzl
index dff8881..11fdb4e 100644
--- a/pw_toolchain/rust/defs.bzl
+++ b/pw_toolchain/rust/defs.bzl
@@ -65,7 +65,7 @@
CHANNELS = [
{
"name": "nightly",
- "extra_rustc_flags": ["-Dwarnings"],
+ "extra_rustc_flags": ["-Dwarnings", "-Zmacro-backtrace"],
"target_settings": ["@rules_rust//rust/toolchain/channel:nightly"],
},
{