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