pw_tokenizer/lib.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//! `pw_tokenizer` - Efficient string handling and printf style encoding.
16//!
17//! Logging is critical, but developers are often forced to choose between
18//! additional logging or saving crucial flash space. The `pw_tokenizer` crate
19//! helps address this by replacing printf-style strings with binary tokens
20//! during compilation. This enables extensive logging with substantially less
21//! memory usage.
22//!
23//! For a more in depth explanation of the systems design and motivations,
24//! see [Pigweed's pw_tokenizer module documentation](https://pigweed.dev/pw_tokenizer/).
25//!
26//! # Examples
27//!
28//! Pigweed's tokenization database uses `printf` style strings internally so
29//! those are supported directly.
30//!
31//! ```
32//! use pw_tokenizer::tokenize_printf_to_buffer;
33//!
34//! let mut buffer = [0u8; 1024];
35//! let len = tokenize_printf_to_buffer!(&mut buffer, "The answer is %d", 42)?;
36//!
37//! // 4 bytes used to encode the token and one to encode the value 42. This
38//! // is a **3.5x** reduction in size compared to the raw string!
39//! assert_eq!(len, 5);
40//! # Ok::<(), pw_status::Error>(())
41//! ```
42//!
43//! We also support Rust's `core::fmt` style syntax. These format strings are
44//! converted to `printf` style at compile time to maintain compatibly with the
45//! rest of the Pigweed tokenizer ecosystem. The below example produces the
46//! same token and output as the above one.
47//!
48//! ```
49//! use pw_tokenizer::tokenize_core_fmt_to_buffer;
50//!
51//! let mut buffer = [0u8; 1024];
52//! let len = tokenize_core_fmt_to_buffer!(&mut buffer, "The answer is {}", 42 as i32)?;
53//! assert_eq!(len, 5);
54//! # Ok::<(), pw_status::Error>(())
55//! ```
56#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
57#![cfg_attr(not(feature = "std"), no_std)]
58#![deny(missing_docs)]
59
60use pw_status::Result;
61
62#[doc(hidden)]
63pub mod internal;
64
65#[doc(hidden)]
66// Creating a __private namespace allows us a way to get to the modules
67// we need from macros by doing:
68// use $crate::__private as __pw_tokenizer_crate;
69//
70// This is how proc macro generated code can reliably reference back to
71// `pw_tokenizer` while still allowing a user to import it under a different
72// name.
73pub mod __private {
74 pub use crate::*;
75 pub use pw_bytes::concat_static_strs;
76 pub use pw_format_core::{PrintfFormatter, PrintfHexFormatter, PrintfUpperHexFormatter};
77 pub use pw_status::Result;
78 pub use pw_stream::{Cursor, Seek, WriteInteger, WriteVarint};
79 pub use pw_tokenizer_core::hash_string;
80 pub use pw_tokenizer_macro::{
81 _token, _tokenize_core_fmt_to_buffer, _tokenize_core_fmt_to_writer,
82 _tokenize_printf_to_buffer, _tokenize_printf_to_writer,
83 };
84}
85
86/// Return the [`u32`] token for the specified string and add it to the token
87/// database.
88///
89/// This is where the magic happens in `pw_tokenizer`! ... and by magic
90/// we mean hiding information in a special linker section that ends up in the
91/// final elf binary but does not get flashed to the device.
92///
93/// Two things are accomplished here:
94/// 1) The string is hashed into its stable `u32` token. This is the value that
95/// is returned from the macro.
96/// 2) A [token database entry](https://pigweed.dev/pw_tokenizer/design.html#binary-database-format)
97/// is generated, assigned to a unique static symbol, placed in a linker
98/// section named `pw_tokenizer.entries.<TOKEN_HASH>`. A
99/// [linker script](https://pigweed.googlesource.com/pigweed/pigweed/+/refs/heads/main/pw_tokenizer/pw_tokenizer_linker_sections.ld)
100/// is responsible for picking these symbols up and aggregating them into a
101/// single `.pw_tokenizer.entries` section in the final binary.
102///
103/// # Example
104/// ```
105/// use pw_tokenizer::token;
106///
107/// let token = token!("hello, \"world\"");
108/// assert_eq!(token, 3537412730);
109/// ```
110///
111/// Currently there is no support for encoding tokens to specific domains
112/// or with "fixed lengths" per [`pw_tokenizer_core::hash_bytes_fixed`].
113#[macro_export]
114macro_rules! token {
115 ($string:literal) => {{
116 use $crate::__private as __pw_tokenizer_crate;
117 $crate::__private::_token!($string)
118 }};
119}
120
121/// Tokenize a `core::fmt` style format string and arguments to an [`AsMut<u8>`]
122/// buffer. The format string is converted in to a `printf` and added token to
123/// the token database.
124///
125/// See [`token`] for an explanation on how strings are tokenized and entries
126/// are added to the token database. The token's domain is set to `""`.
127///
128/// Returns a [`pw_status::Result<usize>`] the number of bytes written to the buffer.
129///
130/// `tokenize_to_buffer!` supports concatenation of format strings as described
131/// in [`pw_format::macros::FormatAndArgs`].
132///
133/// # Errors
134/// - [`pw_status::Error::OutOfRange`] - Buffer is not large enough to fit
135/// tokenized data.
136/// - [`pw_status::Error::InvalidArgument`] - Invalid buffer was provided.
137///
138/// # Example
139///
140/// ```
141/// use pw_tokenizer::tokenize_core_fmt_to_buffer;
142///
143/// // Tokenize a format string and argument into a buffer.
144/// let mut buffer = [0u8; 1024];
145/// let len = tokenize_core_fmt_to_buffer!(&mut buffer, "The answer is {}", 42 as i32)?;
146///
147/// // 4 bytes used to encode the token and one to encode the value 42.
148/// assert_eq!(len, 5);
149///
150/// // The format string can be composed of multiple strings literals using
151/// // the custom`PW_FMT_CONCAT` operator.
152/// let len = tokenize_core_fmt_to_buffer!(&mut buffer, "Hello " PW_FMT_CONCAT "Pigweed")?;
153///
154/// // Only a single 4 byte token is emitted after concatenation of the string
155/// // literals above.
156/// assert_eq!(len, 4);
157/// # Ok::<(), pw_status::Error>(())
158/// ```
159#[macro_export]
160macro_rules! tokenize_core_fmt_to_buffer {
161 ($buffer:expr, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
162 use $crate::__private as __pw_tokenizer_crate;
163 __pw_tokenizer_crate::_tokenize_core_fmt_to_buffer!($buffer, $($format_string)PW_FMT_CONCAT+, $($args),*)
164 }};
165}
166
167/// Tokenize a printf format string and arguments to an [`AsMut<u8>`] buffer
168/// and add the format string's token to the token database.
169///
170/// See [`token`] for an explanation on how strings are tokenized and entries
171/// are added to the token database. The token's domain is set to `""`.
172///
173/// Returns a [`pw_status::Result<usize>`] the number of bytes written to the buffer.
174///
175/// `tokenize_to_buffer!` supports concatenation of format strings as described
176/// in [`pw_format::macros::FormatAndArgs`].
177///
178/// # Errors
179/// - [`pw_status::Error::OutOfRange`] - Buffer is not large enough to fit
180/// tokenized data.
181/// - [`pw_status::Error::InvalidArgument`] - Invalid buffer was provided.
182///
183/// # Example
184///
185/// ```
186/// use pw_tokenizer::tokenize_printf_to_buffer;
187///
188/// // Tokenize a format string and argument into a buffer.
189/// let mut buffer = [0u8; 1024];
190/// let len = tokenize_printf_to_buffer!(&mut buffer, "The answer is %d", 42)?;
191///
192/// // 4 bytes used to encode the token and one to encode the value 42.
193/// assert_eq!(len, 5);
194///
195/// // The format string can be composed of multiple strings literals using
196/// // the custom`PW_FMT_CONCAT` operator.
197/// let len = tokenize_printf_to_buffer!(&mut buffer, "Hello " PW_FMT_CONCAT "Pigweed")?;
198///
199/// // Only a single 4 byte token is emitted after concatenation of the string
200/// // literals above.
201/// assert_eq!(len, 4);
202/// # Ok::<(), pw_status::Error>(())
203/// ```
204#[macro_export]
205macro_rules! tokenize_printf_to_buffer {
206 ($buffer:expr, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
207 use $crate::__private as __pw_tokenizer_crate;
208 __pw_tokenizer_crate::_tokenize_printf_to_buffer!($buffer, $($format_string)PW_FMT_CONCAT+, $($args),*)
209 }};
210}
211
212/// Deprecated alias for [`tokenize_printf_to_buffer!`].
213#[macro_export]
214macro_rules! tokenize_to_buffer {
215 ($buffer:expr, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
216 $crate::tokenize_printf_to_buffer!($buffer, $($format_string)PW_FMT_CONCAT+, $($args),*)
217 }};
218}
219
220/// Tokenize a `core::fmt` format string and arguments to a [`MessageWriter`].
221/// The format string is converted in to a `printf` and added token to the token
222/// database.
223///
224/// `tokenize_core_fmt_to_writer!` and the accompanying [`MessageWriter`] trait
225/// provide an optimized API for use cases like logging where the output of the
226/// tokenization will be written to a shared/ambient resource like stdio, a
227/// UART, or a shared buffer.
228///
229/// See [`token`] for an explanation on how strings are tokenized and entries
230/// are added to the token database. The token's domain is set to `""`.
231///
232/// Returns a [`pw_status::Result<()>`].
233///
234/// `tokenize_core_fmt_to_writer!` supports concatenation of format strings as
235/// described in [`pw_format::macros::FormatAndArgs`].
236///
237/// # Errors
238/// - [`pw_status::Error::OutOfRange`] - [`MessageWriter`] does not have enough
239/// space to fit tokenized data.
240/// - others - `tokenize_core_fmt_to_writer!` will pass on any errors returned
241/// by the [`MessageWriter`].
242///
243/// # Code Size
244///
245/// This data was collected by examining the disassembly of a test program
246/// built for a Cortex M0.
247///
248/// | Tokenized Message | Per Call-site Cost (bytes) |
249/// | --------------------| -------------------------- |
250/// | no arguments | 10 |
251/// | one `i32` argument | 18 |
252///
253/// # Example
254///
255/// ```
256/// use pw_status::Result;
257/// use pw_stream::{Cursor, Write};
258/// use pw_tokenizer::{MessageWriter, tokenize_core_fmt_to_writer};
259///
260/// const BUFFER_LEN: usize = 32;
261///
262/// // Declare a simple MessageWriter that uses a [`pw_status::Cursor`] to
263/// // maintain an internal buffer.
264/// struct TestMessageWriter {
265/// cursor: Cursor<[u8; BUFFER_LEN]>,
266/// }
267///
268/// impl MessageWriter for TestMessageWriter {
269/// fn new() -> Self {
270/// Self {
271/// cursor: Cursor::new([0u8; BUFFER_LEN]),
272/// }
273/// }
274///
275/// fn write(&mut self, data: &[u8]) -> Result<()> {
276/// self.cursor.write_all(data)
277/// }
278///
279/// fn remaining(&self) -> usize {
280/// self.cursor.remaining()
281/// }
282///
283/// fn finalize(self) -> Result<()> {
284/// let len = self.cursor.position();
285/// // 4 bytes used to encode the token and one to encode the value 42.
286/// assert_eq!(len, 5);
287/// Ok(())
288/// }
289/// }
290///
291/// // Tokenize a format string and argument into the writer. Note how we
292/// // pass in the message writer's type, not an instance of it.
293/// let len = tokenize_core_fmt_to_writer!(TestMessageWriter, "The answer is {}", 42 as i32)?;
294/// # Ok::<(), pw_status::Error>(())
295/// ```
296#[macro_export]
297macro_rules! tokenize_core_fmt_to_writer {
298 ($ty:ty, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
299 use $crate::__private as __pw_tokenizer_crate;
300 __pw_tokenizer_crate::_tokenize_core_fmt_to_writer!($ty, $($format_string)PW_FMT_CONCAT+, $($args),*)
301 }};
302}
303
304/// Tokenize a `printf` format string and arguments to a [`MessageWriter`] and
305/// add the format string's token to the token database.
306///
307/// `tokenize_printf_fmt_to_writer!` and the accompanying [`MessageWriter`] trait
308/// provide an optimized API for use cases like logging where the output of the
309/// tokenization will be written to a shared/ambient resource like stdio, a
310/// UART, or a shared buffer.
311///
312/// See [`token`] for an explanation on how strings are tokenized and entries
313/// are added to the token database. The token's domain is set to `""`.
314///
315/// Returns a [`pw_status::Result<()>`].
316///
317/// `tokenize_core_fmt_to_writer!` supports concatenation of format strings as
318/// described in [`pw_format::macros::FormatAndArgs`].
319///
320/// # Errors
321/// - [`pw_status::Error::OutOfRange`] - [`MessageWriter`] does not have enough
322/// space to fit tokenized data.
323/// - others - `tokenize_printf_to_writer!` will pass on any errors returned
324/// by the [`MessageWriter`].
325///
326/// # Code Size
327///
328/// This data was collected by examining the disassembly of a test program
329/// built for a Cortex M0.
330///
331/// | Tokenized Message | Per Call-site Cost (bytes) |
332/// | --------------------| -------------------------- |
333/// | no arguments | 10 |
334/// | one `i32` argument | 18 |
335///
336/// # Example
337///
338/// ```
339/// use pw_status::Result;
340/// use pw_stream::{Cursor, Write};
341/// use pw_tokenizer::{MessageWriter, tokenize_printf_to_writer};
342///
343/// const BUFFER_LEN: usize = 32;
344///
345/// // Declare a simple MessageWriter that uses a [`pw_status::Cursor`] to
346/// // maintain an internal buffer.
347/// struct TestMessageWriter {
348/// cursor: Cursor<[u8; BUFFER_LEN]>,
349/// }
350///
351/// impl MessageWriter for TestMessageWriter {
352/// fn new() -> Self {
353/// Self {
354/// cursor: Cursor::new([0u8; BUFFER_LEN]),
355/// }
356/// }
357///
358/// fn write(&mut self, data: &[u8]) -> Result<()> {
359/// self.cursor.write_all(data)
360/// }
361///
362/// fn remaining(&self) -> usize {
363/// self.cursor.remaining()
364/// }
365///
366/// fn finalize(self) -> Result<()> {
367/// let len = self.cursor.position();
368/// // 4 bytes used to encode the token and one to encode the value 42.
369/// assert_eq!(len, 5);
370/// Ok(())
371/// }
372/// }
373///
374/// // Tokenize a format string and argument into the writer. Note how we
375/// // pass in the message writer's type, not an instance of it.
376/// let len = tokenize_printf_to_writer!(TestMessageWriter, "The answer is %d", 42)?;
377/// # Ok::<(), pw_status::Error>(())
378/// ```
379#[macro_export]
380macro_rules! tokenize_printf_to_writer {
381 ($ty:ty, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
382 use $crate::__private as __pw_tokenizer_crate;
383 __pw_tokenizer_crate::_tokenize_printf_to_writer!($ty, $($format_string)PW_FMT_CONCAT+, $($args),*)
384 }};
385}
386
387/// Deprecated alias for [`tokenize_printf_to_writer!`].
388#[macro_export]
389macro_rules! tokenize_to_writer {
390 ($ty:ty, $($format_string:literal)PW_FMT_CONCAT+ $(, $args:expr)* $(,)?) => {{
391 $crate::tokenize_printf_to_writer!($ty, $($format_string)PW_FMT_CONCAT+, $($args),*)
392 }};
393}
394
395/// A trait used by [`tokenize_to_writer!`] to output tokenized messages.
396///
397/// For more details on how this type is used, see the [`tokenize_to_writer!`]
398/// documentation.
399pub trait MessageWriter {
400 /// Returns a new instance of a `MessageWriter`.
401 fn new() -> Self;
402
403 /// Append `data` to the message.
404 fn write(&mut self, data: &[u8]) -> Result<()>;
405
406 /// Return the remaining space in this message instance.
407 ///
408 /// If there are no space constraints, return `usize::MAX`.
409 fn remaining(&self) -> usize;
410
411 /// Finalize message.
412 ///
413 /// `finalize()` is called when the tokenized message is complete.
414 fn finalize(self) -> Result<()>;
415}
416
417#[cfg(test)]
418// Untyped prints code rely on as casts to annotate type information.
419#[allow(clippy::unnecessary_cast)]
420#[allow(clippy::literal_string_with_formatting_args)]
421mod tests {
422 use super::*;
423 extern crate self as pw_tokenizer;
424 use pw_stream::{Cursor, Write};
425 use std::cell::RefCell;
426
427 // This is not meant to be an exhaustive test of tokenization which is
428 // covered by `pw_tokenizer_core`'s unit tests. Rather, this is testing
429 // that the `tokenize!` macro connects to that correctly.
430 #[test]
431 fn test_token() {}
432
433 macro_rules! tokenize_to_buffer_test {
434 ($expected_data:expr, $buffer_len:expr, $printf_fmt:literal, $core_fmt:literal $(, $args:expr)* $(,)?) => {{
435 if $printf_fmt != "" {
436 let mut buffer = [0u8; $buffer_len];
437 let len = tokenize_printf_to_buffer!(&mut buffer, $printf_fmt, $($args),*).unwrap();
438 assert_eq!(
439 &buffer[..len],
440 $expected_data,
441 "printf style input does not produce expected output",
442 );
443 }
444 if $core_fmt != "" {
445 let mut buffer = [0u8; $buffer_len];
446 let len = tokenize_core_fmt_to_buffer!(&mut buffer, $core_fmt, $($args),*).unwrap();
447 assert_eq!(
448 &buffer[..len],
449 $expected_data,
450 "core::fmt style input does not produce expected output",
451 );
452 }
453 }}
454 }
455
456 macro_rules! tokenize_to_writer_test {
457 ($expected_data:expr, $buffer_len:expr, $printf_fmt:literal, $core_fmt:literal $(, $args:expr)* $(,)?) => {{
458 // The `MessageWriter` API is used in places like logging where it
459 // accesses an shared/ambient resource (like stdio or an UART). To test
460 // it in a hermetic way we declare test specific `MessageWriter` that
461 // writes it's output to a scoped static variable that can be checked
462 // after the test is run.
463
464 // Since these tests are not multi-threaded, we can use a thread_local!
465 // instead of a mutex.
466 thread_local!(static TEST_OUTPUT: RefCell<Option<Vec<u8>>> = RefCell::new(None));
467
468 struct TestMessageWriter {
469 cursor: Cursor<[u8; $buffer_len]>,
470 }
471
472 impl MessageWriter for TestMessageWriter {
473 fn new() -> Self {
474 Self {
475 cursor: Cursor::new([0u8; $buffer_len]),
476 }
477 }
478
479 fn write(&mut self, data: &[u8]) -> Result<()> {
480 self.cursor.write_all(data)
481 }
482
483 fn remaining(&self) -> usize {
484 self.cursor.remaining()
485 }
486
487 fn finalize(self) -> Result<()> {
488 let write_len = self.cursor.position();
489 let data = self.cursor.into_inner();
490 TEST_OUTPUT.with(|output| *output.borrow_mut() = Some(data[..write_len].to_vec()));
491
492 Ok(())
493 }
494 }
495
496 if $printf_fmt != "" {
497 TEST_OUTPUT.with(|output| *output.borrow_mut() = None);
498 tokenize_printf_to_writer!(TestMessageWriter, $printf_fmt, $($args),*).unwrap();
499 TEST_OUTPUT.with(|output| {
500 assert_eq!(
501 *output.borrow(),
502 Some($expected_data.to_vec()),
503 )
504 });
505 }
506
507 if $core_fmt != "" {
508 TEST_OUTPUT.with(|output| *output.borrow_mut() = None);
509 tokenize_core_fmt_to_writer!(TestMessageWriter, $core_fmt, $($args),*).unwrap();
510 TEST_OUTPUT.with(|output| {
511 assert_eq!(
512 *output.borrow(),
513 Some($expected_data.to_vec()),
514 )
515 });
516 }
517 }}
518 }
519
520 macro_rules! tokenize_test {
521 ($expected_data:expr, $buffer_len:expr, $printf_fmt:literal, $core_fmt:literal $(, $args:expr)* $(,)?) => {{
522 tokenize_to_buffer_test!($expected_data, $buffer_len, $printf_fmt, $core_fmt, $($args),*);
523 tokenize_to_writer_test!($expected_data, $buffer_len, $printf_fmt, $core_fmt, $($args),*);
524 }};
525 }
526
527 #[test]
528 fn bare_string_encodes_correctly() {
529 tokenize_test!(
530 &[0xe0, 0x92, 0xe0, 0xa], // expected buffer
531 64, // buffer size
532 "Hello Pigweed", // printf style
533 "Hello Pigweed", // core::fmt style
534 );
535 }
536
537 #[test]
538 fn test_decimal_format() {
539 // "as casts" are used for the integer arguments below. They are only
540 // need for the core::fmt style arguments but are added so that we can
541 // check that the printf and core::fmt style equivalents encode the same.
542 tokenize_test!(
543 &[0x52, 0x1c, 0xb0, 0x4c, 0x2], // expected buffer
544 64, // buffer size
545 "The answer is %d!", // printf style
546 "The answer is {}!", // core::fmt style
547 1 as i32
548 );
549
550 tokenize_test!(
551 &[0x36, 0xd0, 0xfb, 0x69, 0x1], // expected buffer
552 64, // buffer size
553 "No! The answer is %d!", // printf style
554 "No! The answer is {}!", // core::fmt style
555 -1 as i32
556 );
557
558 tokenize_test!(
559 &[0xa4, 0xad, 0x50, 0x54, 0x0], // expected buffer
560 64, // buffer size
561 "I think you'll find that the answer is %d!", // printf style
562 "I think you'll find that the answer is {}!", // core::fmt style
563 0 as i32
564 );
565 }
566
567 #[test]
568 fn test_misc_integer_format() {
569 // %d, %i, %o, %u, %x, %X all encode integers the same.
570 tokenize_test!(
571 &[0x52, 0x1c, 0xb0, 0x4c, 0x2], // expected buffer
572 64, // buffer size
573 "The answer is %d!", // printf style
574 "", // no equivalent core::fmt style
575 1
576 );
577
578 // Because %i is an alias for %d, it gets converted to a %d by the
579 // `pw_format` macro infrastructure.
580 tokenize_test!(
581 &[0x52, 0x1c, 0xb0, 0x4c, 0x2], // expected buffer
582 64, // buffer size
583 "The answer is %i!", // printf style
584 "", // no equivalent core::fmt style
585 1
586 );
587
588 tokenize_test!(
589 &[0x5d, 0x70, 0x12, 0xb4, 0x2], // expected buffer
590 64, // buffer size
591 "The answer is %o!", // printf style
592 "", // no equivalent core::fmt style
593 1u32
594 );
595
596 tokenize_test!(
597 &[0x63, 0x58, 0x5f, 0x8f, 0x2], // expected buffer
598 64, // buffer size
599 "The answer is %u!", // printf style
600 "", // no equivalent core::fmt style
601 1u32
602 );
603
604 tokenize_test!(
605 &[0x66, 0xcc, 0x05, 0x7d, 0x2], // expected buffer
606 64, // buffer size
607 "The answer is %x!", // printf style
608 "", // no equivalent core::fmt style
609 1u32
610 );
611
612 tokenize_test!(
613 &[0x46, 0x4c, 0x16, 0x96, 0x2], // expected buffer
614 64, // buffer size
615 "The answer is %X!", // printf style
616 "", // no equivalent core::fmt style
617 1u32
618 );
619 }
620
621 #[test]
622 fn test_string_format() {
623 tokenize_test!(
624 b"\x25\xf6\x2e\x66\x07Pigweed", // expected buffer
625 64, // buffer size
626 "Hello: %s!", // printf style
627 "", // no equivalent core::fmt style
628 "Pigweed"
629 );
630 }
631
632 #[test]
633 fn test_string_format_overflow() {
634 tokenize_test!(
635 b"\x25\xf6\x2e\x66\x83Pig", // expected buffer
636 8, // buffer size
637 "Hello: %s!", // printf style
638 "", // no equivalent core::fmt style
639 "Pigweed"
640 );
641 }
642
643 #[test]
644 fn test_char_format() {
645 tokenize_test!(
646 &[0x2e, 0x52, 0xac, 0xe4, 0xa0, 0x1], // expected buffer
647 64, // buffer size
648 "Hello: %cigweed", // printf style
649 "", // no equivalent core::fmt style
650 "P".as_bytes()[0]
651 );
652 }
653
654 #[test]
655 fn test_untyped_format() {
656 tokenize_test!(
657 &[0x63, 0x58, 0x5f, 0x8f, 0x2], // expected buffer
658 64, // buffer size
659 "The answer is %u!", // printf style
660 "The answer is {}!", // core::fmt style
661 1 as u32
662 );
663
664 tokenize_test!(
665 &[0x36, 0xd0, 0xfb, 0x69, 0x1], // expected buffer
666 64, // buffer size
667 "No! The answer is %v!", // printf style
668 "No! The answer is {}!", // core::fmt style
669 -1 as i32
670 );
671
672 tokenize_test!(
673 b"\x25\xf6\x2e\x66\x07Pigweed", // expected buffer
674 64, // buffer size
675 "Hello: %v!", // printf style
676 "Hello: {}!", // core::fmt style
677 "Pigweed" as &str
678 );
679 }
680
681 #[test]
682 fn test_field_width_and_zero_pad_format() {
683 tokenize_test!(
684 &[0x3a, 0xc2, 0x1a, 0x05, 0xfc, 0xab, 0x06], // expected buffer
685 64, // buffer size
686 "Lets go to the %x", // printf style
687 "Lets go to the {:x}", // core::fmt style
688 0xcafe as u32
689 );
690
691 tokenize_test!(
692 &[0xf3, 0x16, 0x03, 0x99, 0xfc, 0xab, 0x06], // expected buffer
693 64, // buffer size
694 "Lets go to the %8x", // printf style
695 "Lets go to the {:8x}", // core::fmt style
696 0xcafe as u32
697 );
698
699 tokenize_test!(
700 &[0x44, 0xce, 0xa3, 0x7e, 0xfc, 0xab, 0x06], // expected buffer
701 64, // buffer size
702 "Lets go to the %08x", // printf style
703 "Lets go to the {:08x}", // core::fmt style
704 0xcafe as u32
705 );
706 }
707
708 #[test]
709 fn tokenizer_supports_concatenated_printf_format_strings() {
710 // Since the no argument and some arguments cases are handled differently
711 // by `tokenize_to_buffer!` we need to test both.
712 let mut buffer = [0u8; 64];
713 let len =
714 tokenize_printf_to_buffer!(&mut buffer, "Hello" PW_FMT_CONCAT " Pigweed").unwrap();
715 assert_eq!(&buffer[..len], &[0xe0, 0x92, 0xe0, 0xa]);
716
717 let len = tokenize_printf_to_buffer!(&mut buffer, "Hello: " PW_FMT_CONCAT "%cigweed",
718 "P".as_bytes()[0])
719 .unwrap();
720 assert_eq!(&buffer[..len], &[0x2e, 0x52, 0xac, 0xe4, 0xa0, 0x1]);
721 }
722
723 #[test]
724 fn tokenizer_supports_concatenated_core_fmt_format_strings() {
725 // Since the no argument and some arguments cases are handled differently
726 // by `tokenize_to_buffer!` we need to test both.
727 let mut buffer = [0u8; 64];
728 let len =
729 tokenize_core_fmt_to_buffer!(&mut buffer, "Hello" PW_FMT_CONCAT " Pigweed").unwrap();
730 assert_eq!(&buffer[..len], &[0xe0, 0x92, 0xe0, 0xa]);
731
732 let len = tokenize_core_fmt_to_buffer!(&mut buffer, "The answer is " PW_FMT_CONCAT "{}!",
733 1 as i32)
734 .unwrap();
735 assert_eq!(&buffer[..len], &[0x52, 0x1c, 0xb0, 0x4c, 0x2]);
736 }
737}