├── .gitignore ├── cg.png ├── .github ├── bors.toml └── workflows │ └── ci.yml ├── nopanic ├── .gitignore ├── memory.x ├── README.md ├── examples │ ├── ptr.rs │ ├── slice.rs │ ├── char.rs │ ├── str.rs │ ├── buffered.rs │ ├── ixx.rs │ ├── uxx.rs │ ├── tuple.rs │ ├── recursion.rs │ ├── enum.rs │ └── struct.rs ├── Cargo.toml └── src │ └── lib.rs ├── src ├── impls.rs ├── impls │ ├── array.rs │ ├── tuple.rs │ ├── nz.rs │ ├── ptr.rs │ ├── hex.rs │ ├── std.rs │ ├── core.rs │ ├── uxx.rs │ └── ixx.rs ├── helpers.rs └── lib.rs ├── macros ├── Cargo.toml ├── CHANGELOG.md └── src │ └── lib.rs ├── write ├── Cargo.toml └── src │ └── lib.rs ├── utils ├── Cargo.toml ├── CHANGELOG.md └── src │ └── lib.rs ├── tests ├── gh36.rs └── vs-std-write.rs ├── Cargo.toml ├── LICENSE-MIT ├── CHANGELOG.md ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | Cargo.lock 4 | target 5 | -------------------------------------------------------------------------------- /cg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japaric/ufmt/HEAD/cg.png -------------------------------------------------------------------------------- /.github/bors.toml: -------------------------------------------------------------------------------- 1 | delete_merged_branches = true 2 | status = ["ci"] -------------------------------------------------------------------------------- /nopanic/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | .gdb_history 4 | Cargo.lock 5 | target/ 6 | -------------------------------------------------------------------------------- /nopanic/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 4 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 5 | } 6 | -------------------------------------------------------------------------------- /nopanic/README.md: -------------------------------------------------------------------------------- 1 | # `nopanic` 2 | 3 | One of the design goals of `μfmt` is "No panicking branches when optimized" so 4 | here we test that! 5 | -------------------------------------------------------------------------------- /src/impls.rs: -------------------------------------------------------------------------------- 1 | mod array; 2 | mod core; 3 | mod hex; 4 | mod ixx; 5 | mod nz; 6 | mod ptr; 7 | #[cfg(feature = "std")] 8 | mod std; 9 | mod tuple; 10 | mod uxx; 11 | -------------------------------------------------------------------------------- /nopanic/examples/ptr.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::uwrite; 5 | 6 | use common::W; 7 | 8 | #[no_mangle] 9 | fn _start(p: *const u8) { 10 | uwrite!(&mut W, "{:?}", p).unwrap(); 11 | } 12 | -------------------------------------------------------------------------------- /nopanic/examples/slice.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::uwrite; 5 | 6 | use common::W; 7 | 8 | #[no_mangle] 9 | fn _start(a: &[i8]) { 10 | uwrite!(&mut W, "{:?}", a).unwrap(); 11 | uwrite!(&mut W, "{:#?}", a).unwrap(); 12 | } 13 | -------------------------------------------------------------------------------- /nopanic/examples/char.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::uwrite; 5 | 6 | use common::W; 7 | 8 | #[no_mangle] 9 | fn _start(c: char) { 10 | uwrite!(&mut W, "{}", c).unwrap(); 11 | // TODO 12 | // uwrite!(&mut W, "{:?}", c).unwrap(); 13 | } 14 | -------------------------------------------------------------------------------- /nopanic/examples/str.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::uwrite; 5 | 6 | use common::W; 7 | 8 | #[no_mangle] 9 | fn _start(s: &str) { 10 | uwrite!(&mut W, "{}", s).unwrap(); 11 | // TODO 12 | // uwrite!(&mut W, "{:?}", s).unwrap(); 13 | } 14 | -------------------------------------------------------------------------------- /nopanic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | name = "common" 5 | publish = false 6 | version = "0.0.0" 7 | 8 | [dependencies] 9 | panic-never = "0.1.0" 10 | ufmt = { path = ".." } 11 | ufmt-utils = { path = "../utils" } 12 | 13 | [profile.release] 14 | codegen-units = 1 15 | lto = true 16 | opt-level = "z" 17 | 18 | [workspace] -------------------------------------------------------------------------------- /nopanic/examples/buffered.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::uwriteln; 5 | use ufmt_utils::LineBuffered; 6 | 7 | use common::W; 8 | 9 | #[no_mangle] 10 | fn _start(a: i8, b: i16) { 11 | let mut w = LineBuffered::<_, 64>::new(W); 12 | 13 | uwriteln!(&mut w, "{}", a).unwrap(); 14 | uwriteln!(&mut w, "{}", b).unwrap(); 15 | uwriteln!(&mut w, "{:?}", (a, b)).unwrap(); 16 | } 17 | -------------------------------------------------------------------------------- /nopanic/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::{convert::Infallible, ptr}; 4 | 5 | use panic_never as _; 6 | use ufmt::uWrite; 7 | 8 | pub struct W; 9 | 10 | impl uWrite for W { 11 | type Error = Infallible; 12 | 13 | fn write_str(&mut self, s: &str) -> Result<(), Infallible> { 14 | s.as_bytes().iter().for_each(|b| unsafe { 15 | let _ = ptr::read_volatile(b); 16 | }); 17 | 18 | Ok(()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /nopanic/examples/ixx.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::uwrite; 5 | 6 | use common::W; 7 | 8 | #[no_mangle] 9 | fn _start(a: i8, b: i16, c: i32, d: isize, e: i64, f: i128) { 10 | uwrite!(&mut W, "{}", a).unwrap(); 11 | uwrite!(&mut W, "{}", b).unwrap(); 12 | uwrite!(&mut W, "{}", c).unwrap(); 13 | uwrite!(&mut W, "{}", d).unwrap(); 14 | uwrite!(&mut W, "{}", e).unwrap(); 15 | uwrite!(&mut W, "{}", f).unwrap(); 16 | } 17 | -------------------------------------------------------------------------------- /nopanic/examples/uxx.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::uwrite; 5 | 6 | use common::W; 7 | 8 | #[no_mangle] 9 | fn _start(a: u8, b: u16, c: u32, d: usize, e: u64, f: u128) { 10 | uwrite!(&mut W, "{}", a).unwrap(); 11 | uwrite!(&mut W, "{}", b).unwrap(); 12 | uwrite!(&mut W, "{}", c).unwrap(); 13 | uwrite!(&mut W, "{}", d).unwrap(); 14 | uwrite!(&mut W, "{}", e).unwrap(); 15 | uwrite!(&mut W, "{}", f).unwrap(); 16 | } 17 | -------------------------------------------------------------------------------- /nopanic/examples/tuple.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::uwrite; 5 | 6 | use common::W; 7 | 8 | #[no_mangle] 9 | fn _start(a: u8, b: i8) { 10 | uwrite!(&mut W, "{:?}", ()).unwrap(); 11 | uwrite!(&mut W, "{:#?}", ()).unwrap(); 12 | 13 | uwrite!(&mut W, "{:?}", (a,)).unwrap(); 14 | uwrite!(&mut W, "{:#?}", (a,)).unwrap(); 15 | 16 | uwrite!(&mut W, "{:?}", (a, b)).unwrap(); 17 | uwrite!(&mut W, "{:#?}", (a, b)).unwrap(); 18 | } 19 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | categories = ["embedded", "no-std"] 4 | description = "`μfmt` macros" 5 | edition = "2021" 6 | keywords = ["Debug", "Display", "Write", "format"] 7 | license = "MIT OR Apache-2.0" 8 | name = "ufmt-macros" 9 | repository = "https://github.com/japaric/ufmt" 10 | version = "0.3.0" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | proc-macro2 = "1" 17 | quote = "1" 18 | 19 | [dependencies.syn] 20 | features = ["full"] 21 | version = "1" -------------------------------------------------------------------------------- /nopanic/examples/recursion.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::{derive::uDebug, uwrite}; 5 | 6 | use common::W; 7 | 8 | #[derive(uDebug)] 9 | struct Node { 10 | value: u32, 11 | next: Option<&'static Node>, 12 | } 13 | 14 | #[no_mangle] 15 | fn _start(x: u32) { 16 | static TAIL: Node = Node { 17 | value: 0, 18 | next: None, 19 | }; 20 | 21 | uwrite!( 22 | &mut W, 23 | "{:#?}", 24 | Node { 25 | value: x, 26 | next: Some(&TAIL), 27 | } 28 | ) 29 | .unwrap(); 30 | } 31 | -------------------------------------------------------------------------------- /write/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | categories = ["embedded", "no-std"] 4 | description = "`μfmt`'s `uWrite` trait" 5 | edition = "2018" 6 | keywords = ["Debug", "Display", "Write", "format"] 7 | license = "MIT OR Apache-2.0" 8 | name = "ufmt-write" 9 | repository = "https://github.com/japaric/ufmt" 10 | version = "0.1.0" 11 | 12 | # NOTE do NOT add an `alloc` feature before the alloc crate can be used in 13 | # no-std BINARIES 14 | [features] 15 | # NOTE do NOT turn `std` into a default feature; this is a no-std first crate 16 | std = [] 17 | -------------------------------------------------------------------------------- /utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | categories = ["embedded", "no-std"] 4 | description = "`μfmt` utilities" 5 | documentation = "https://docs.rs/ufmt-utils" 6 | edition = "2021" 7 | keywords = ["Debug", "Display", "Write", "format"] 8 | license = "MIT OR Apache-2.0" 9 | name = "ufmt-utils" 10 | repository = "https://github.com/japaric/ufmt" 11 | version = "0.2.0" 12 | 13 | [dependencies] 14 | heapless = "0.7.16" 15 | ufmt-write = { version = "0.1.0", path = "../write" } 16 | 17 | [dev-dependencies] 18 | ufmt = { version = "0.2.0", path = ".." } 19 | -------------------------------------------------------------------------------- /nopanic/examples/enum.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::{derive::uDebug, uwrite}; 5 | 6 | use common::W; 7 | 8 | #[derive(uDebug)] 9 | enum X { 10 | A, 11 | B(u8, u16), 12 | C { x: u8, y: u16 }, 13 | } 14 | 15 | #[no_mangle] 16 | fn _start(x: u8, y: u16) { 17 | uwrite!(&mut W, "{:?}", X::A).unwrap(); 18 | uwrite!(&mut W, "{:#?}", X::A).unwrap(); 19 | 20 | uwrite!(&mut W, "{:?}", X::B(x, y)).unwrap(); 21 | uwrite!(&mut W, "{:#?}", X::B(x, y)).unwrap(); 22 | 23 | uwrite!(&mut W, "{:?}", X::C { x, y }).unwrap(); 24 | uwrite!(&mut W, "{:#?}", X::C { x, y }).unwrap(); 25 | } 26 | -------------------------------------------------------------------------------- /src/impls/array.rs: -------------------------------------------------------------------------------- 1 | use crate::{uDebug, uWrite, Formatter}; 2 | 3 | macro_rules! array { 4 | ($($N:expr),+) => { 5 | $( 6 | impl uDebug for [T; $N] 7 | where 8 | T: uDebug, 9 | { 10 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 11 | where 12 | W: uWrite + ?Sized, 13 | { 14 | <[T] as uDebug>::fmt(self, f) 15 | } 16 | } 17 | )+ 18 | } 19 | } 20 | 21 | array!( 22 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 23 | 26, 27, 28, 29, 30, 31, 32 24 | ); 25 | -------------------------------------------------------------------------------- /tests/gh36.rs: -------------------------------------------------------------------------------- 1 | //! `uwrite!` works in presence of a third-party `Ok` constructor 2 | 3 | mod third_party { 4 | #[allow(dead_code)] 5 | pub enum Result { 6 | Ok(A), 7 | Err(B), 8 | } 9 | } 10 | 11 | use ufmt::{derive::uDebug, uwrite, uwriteln}; 12 | 13 | #[allow(unused_imports)] 14 | use crate::third_party::Result::{self, Err, Ok}; 15 | 16 | #[derive(uDebug)] 17 | struct Pair { 18 | x: u32, 19 | y: u32, 20 | } 21 | 22 | #[test] 23 | fn uwrite() { 24 | let mut s = String::new(); 25 | let pair = Pair { x: 1, y: 2 }; 26 | 27 | uwrite!(s, "{:?}", pair).unwrap(); 28 | assert_eq!(s, "Pair { x: 1, y: 2 }"); 29 | } 30 | 31 | #[test] 32 | fn uwriteln() { 33 | let mut s = String::new(); 34 | let pair = Pair { x: 1, y: 2 }; 35 | 36 | uwriteln!(s, "{:?}", pair).unwrap(); 37 | assert_eq!(s, "Pair { x: 1, y: 2 }\n"); 38 | } 39 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | categories = ["embedded", "no-std"] 4 | description = "A (6-40x) smaller, (2-9x) faster and panic-free alternative to `core::fmt`" 5 | documentation = "https://docs.rs/ufmt" 6 | edition = "2021" 7 | keywords = ["Debug", "Display", "Write", "format"] 8 | license = "MIT OR Apache-2.0" 9 | name = "ufmt" 10 | readme = "README.md" 11 | repository = "https://github.com/japaric/ufmt" 12 | version = "0.2.0" 13 | 14 | [dependencies] 15 | ufmt-macros = { path = "macros", version = "0.3.0" } 16 | ufmt-write = { path = "write", version = "0.1.0" } 17 | 18 | # NOTE do NOT add an `alloc` feature before the alloc crate can be used in 19 | # no-std BINARIES 20 | [features] 21 | # NOTE do NOT turn `std` into a default feature; this is a no-std first crate 22 | std = ["ufmt-write/std"] 23 | 24 | [[test]] 25 | name = "vs-std-write" 26 | required-features = ["std"] 27 | 28 | [workspace] 29 | members = [ 30 | "macros", 31 | "utils", 32 | "write", 33 | ] 34 | -------------------------------------------------------------------------------- /utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [Unreleased] 9 | 10 | ## [v0.2.0] - 2022-08-10 11 | 12 | ### Changed 13 | 14 | - [breaking-change] Minimum Supported Rust Version (MSRV) guarantee has been removed 15 | - [breaking-change] the capacity type parameter `N` of `LineBuffered` is now a constant, rather than a type 16 | 17 | ### Removed 18 | 19 | - [breaking-change] the `consts` module has been removed 20 | 21 | ## [v0.1.1] - 2020-02-11 22 | 23 | ### Added 24 | 25 | - a `WriteAdapter` that lets one `uwrite!` types that implement `core::fmt::Write` 26 | 27 | ## v0.1.0 - 2019-11-17 28 | 29 | Initial release 30 | 31 | [Unreleased]: https://github.com/japaric/ufmt/compare/ufmt-utils-v0.2.0...HEAD 32 | [v0.2.0]: https://github.com/japaric/ufmt/compare/ufmt-utils-v0.1.1...ufmt-utils-v0.2.0 33 | [v0.1.1]: https://github.com/japaric/ufmt/compare/ufmt-utils-v0.1.0...ufmt-utils-v0.1.1 34 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Jorge Aparicio 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [Unreleased] 9 | 10 | ## [v0.2.0] - 2022-08-10 11 | 12 | ## Changed 13 | 14 | - [breaking-change] Minimum Supported Rust Version (MSRV) guarantee has been removed 15 | 16 | ## Fixed 17 | 18 | - fixed miri errors around use of `mem::uninitialized`; updated code to use `MaybeUninit` 19 | 20 | ## [v0.1.2] - 2022-08-10 21 | 22 | ## Added 23 | 24 | - added support for hexadecimal formatting of integers using the `{:x}` format string 25 | 26 | ## [v0.1.1] - 2022-08-09 27 | 28 | ### Fixed 29 | 30 | - fixed link in crate level documentation 31 | - fixed `uDebug` implementation for `i32` / `u32` on 16-bit platforms (like AVR and MSP430) 32 | 33 | ## v0.1.0 - 2019-11-17 34 | 35 | Initial release 36 | 37 | [Unreleased]: https://github.com/japaric/ufmt/compare/ufmt-v0.2.0...HEAD 38 | [v0.2.0]: https://github.com/japaric/ufmt/compare/ufmt-v0.1.2...ufmt-v0.2.0 39 | [v0.1.2]: https://github.com/japaric/ufmt/compare/ufmt-v0.1.1...ufmt-v0.1.2 40 | [v0.1.1]: https://github.com/japaric/ufmt/compare/ufmt-v0.1.0...ufmt-v0.1.1 41 | -------------------------------------------------------------------------------- /src/impls/tuple.rs: -------------------------------------------------------------------------------- 1 | use crate::{uDebug, uWrite, Formatter}; 2 | 3 | macro_rules! tuple { 4 | ($($T:ident),*; $($i:tt),*) => { 5 | impl<$($T,)*> uDebug for ($($T,)*) 6 | where 7 | $($T: uDebug,)* 8 | { 9 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 10 | where 11 | W: uWrite + ?Sized, 12 | { 13 | f.debug_tuple("")?$(.field(&self.$i)?)*.finish() 14 | } 15 | } 16 | 17 | } 18 | } 19 | 20 | impl uDebug for () { 21 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 22 | where 23 | W: uWrite + ?Sized, 24 | { 25 | f.write_str("()") 26 | } 27 | } 28 | 29 | tuple!(A; 0); 30 | tuple!(A, B; 0, 1); 31 | tuple!(A, B, C; 0, 1, 2); 32 | tuple!(A, B, C, D; 0, 1, 2, 3); 33 | tuple!(A, B, C, D, E; 0, 1, 2, 3, 4); 34 | tuple!(A, B, C, D, E, F; 0, 1, 2, 3, 4, 5); 35 | tuple!(A, B, C, D, E, F, G; 0, 1, 2, 3, 4, 5, 6); 36 | tuple!(A, B, C, D, E, F, G, H; 0, 1, 2, 3, 4, 5, 6, 7); 37 | tuple!(A, B, C, D, E, F, G, H, I; 0, 1, 2, 3, 4, 5, 6, 7, 8); 38 | tuple!(A, B, C, D, E, F, G, H, I, J; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); 39 | tuple!(A, B, C, D, E, F, G, H, I, J, K; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 40 | tuple!(A, B, C, D, E, F, G, H, I, J, K, L; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); 41 | -------------------------------------------------------------------------------- /nopanic/examples/struct.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use ufmt::{derive::uDebug, uwrite}; 5 | 6 | use common::W; 7 | 8 | #[derive(uDebug)] 9 | struct Braces {} 10 | 11 | #[derive(uDebug)] 12 | struct Parens(); 13 | 14 | #[derive(uDebug)] 15 | struct I32(i32); 16 | 17 | #[derive(uDebug)] 18 | struct Tuple(i32, i32); 19 | 20 | #[derive(uDebug)] 21 | struct Nested { 22 | first: Pair, 23 | second: Pair, 24 | } 25 | 26 | #[derive(Clone, Copy, uDebug)] 27 | struct Pair { 28 | x: i32, 29 | y: i32, 30 | } 31 | 32 | #[no_mangle] 33 | fn _start(x: i32, y: i32) { 34 | uwrite!(&mut W, "{:?}", Braces {}).unwrap(); 35 | uwrite!(&mut W, "{:#?}", Braces {}).unwrap(); 36 | 37 | uwrite!(&mut W, "{:?}", Parens()).unwrap(); 38 | uwrite!(&mut W, "{:#?}", Parens()).unwrap(); 39 | 40 | uwrite!(&mut W, "{:?}", I32(x)).unwrap(); 41 | uwrite!(&mut W, "{:#?}", I32(x)).unwrap(); 42 | 43 | uwrite!(&mut W, "{:?}", Tuple(x, y)).unwrap(); 44 | uwrite!(&mut W, "{:#?}", Tuple(x, y)).unwrap(); 45 | 46 | let pair = Pair { x, y }; 47 | uwrite!(&mut W, "{:?}", pair).unwrap(); 48 | uwrite!(&mut W, "{:#?}", pair).unwrap(); 49 | 50 | let first = pair; 51 | let second = pair; 52 | uwrite!(&mut W, "{:?}", Nested { first, second }).unwrap(); 53 | uwrite!(&mut W, "{:#?}", Nested { first, second }).unwrap(); 54 | } 55 | -------------------------------------------------------------------------------- /src/impls/nz.rs: -------------------------------------------------------------------------------- 1 | use core::num::{ 2 | NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU16, NonZeroU32, 3 | NonZeroU64, NonZeroU8, NonZeroUsize, 4 | }; 5 | 6 | use crate::{uDebug, uDisplay, uWrite, Formatter}; 7 | 8 | macro_rules! nz { 9 | ($($NZ:ident : $inner:ident,)*) => { 10 | $( 11 | impl uDebug for $NZ { 12 | #[inline(always)] 13 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 14 | where 15 | W: uWrite + ?Sized, 16 | { 17 | <$inner as uDebug>::fmt(&self.get(), f) 18 | } 19 | } 20 | 21 | impl uDisplay for $NZ { 22 | #[inline(always)] 23 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 24 | where 25 | W: uWrite + ?Sized, 26 | { 27 | <$inner as uDisplay>::fmt(&self.get(), f) 28 | } 29 | } 30 | )* 31 | } 32 | } 33 | 34 | nz!( 35 | NonZeroI16: i16, 36 | NonZeroI32: i32, 37 | NonZeroI64: i64, 38 | NonZeroI8: i8, 39 | NonZeroIsize: isize, 40 | NonZeroU16: u16, 41 | NonZeroU32: u32, 42 | NonZeroU64: u64, 43 | NonZeroU8: u8, 44 | NonZeroUsize: usize, 45 | ); 46 | -------------------------------------------------------------------------------- /macros/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [Unreleased] 9 | 10 | ## [v0.3.0] - 2022-08-10 11 | 12 | ## Changed 13 | 14 | - [breaking-change] Minimum Supported Rust Version (MSRV) guarantee has been removed 15 | 16 | ## Fixed 17 | 18 | - fixed `uwrite!` and `uwriteln!` in presence of a third-party `Ok` constructor 19 | 20 | ## [v0.2.0] - 2022-08-10 21 | 22 | ### Added 23 | 24 | - added support for `{:x}`-style formatting arguments. must be used with `ufmt` 0.1.2+ 25 | 26 | ## [v0.1.2] - 2022-08-09 27 | 28 | ### Fixed 29 | 30 | - `derive(uDebug)` on enums that have no variants 31 | 32 | ## [v0.1.1] - 2020-02-11 33 | 34 | ### Fixed 35 | 36 | - fully qualify internal uses of `core::result::Result` to avoid problems when derive in presence of an imported `Result` type that's not libcore's 37 | 38 | ## v0.1.0 - 2019-11-17 39 | 40 | Initial release 41 | 42 | [Unreleased]: https://github.com/japaric/ufmt/compare/ufmt-macros-v0.3.0...HEAD 43 | [v0.3.0]: https://github.com/japaric/ufmt/compare/ufmt-macros-v0.2.0...ufmt-macros-v0.3.0 44 | [v0.2.0]: https://github.com/japaric/ufmt/compare/ufmt-macros-v0.1.2...ufmt-macros-v0.2.0 45 | [v0.1.2]: https://github.com/japaric/ufmt/compare/ufmt-macros-v0.1.1...ufmt-macros-v0.1.2 46 | [v0.1.1]: https://github.com/japaric/ufmt/compare/ufmt-macros-v0.1.0...ufmt-macros-v0.1.1 47 | -------------------------------------------------------------------------------- /write/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `μfmt`'s `uWrite` trait 2 | 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | #![deny(missing_docs)] 5 | #![deny(rust_2018_compatibility)] 6 | #![deny(rust_2018_idioms)] 7 | #![deny(warnings)] 8 | 9 | #[cfg(feature = "std")] 10 | use core::convert::Infallible; 11 | 12 | #[allow(deprecated)] 13 | unsafe fn uninitialized() -> T { 14 | core::mem::uninitialized() 15 | } 16 | 17 | /// A collection of methods that are required / used to format a message into a stream. 18 | #[allow(non_camel_case_types)] 19 | pub trait uWrite { 20 | /// The error associated to this writer 21 | type Error; 22 | 23 | /// Writes a string slice into this writer, returning whether the write succeeded. 24 | /// 25 | /// This method can only succeed if the entire string slice was successfully written, and this 26 | /// method will not return until all data has been written or an error occurs. 27 | fn write_str(&mut self, s: &str) -> Result<(), Self::Error>; 28 | 29 | /// Writes a [`char`] into this writer, returning whether the write succeeded. 30 | /// 31 | /// A single [`char`] may be encoded as more than one byte. This method can only succeed if the 32 | /// entire byte sequence was successfully written, and this method will not return until all 33 | /// data has been written or an error occurs. 34 | fn write_char(&mut self, c: char) -> Result<(), Self::Error> { 35 | let mut buf: [u8; 4] = unsafe { uninitialized() }; 36 | self.write_str(c.encode_utf8(&mut buf)) 37 | } 38 | } 39 | 40 | #[cfg(feature = "std")] 41 | impl uWrite for String { 42 | type Error = Infallible; 43 | 44 | fn write_str(&mut self, s: &str) -> Result<(), Infallible> { 45 | self.push_str(s); 46 | Ok(()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/impls/ptr.rs: -------------------------------------------------------------------------------- 1 | use core::{mem::MaybeUninit, slice, str}; 2 | 3 | use crate::{uDebug, uWrite, Formatter}; 4 | 5 | macro_rules! hex { 6 | ($self:expr, $f:expr, $N:expr) => {{ 7 | let mut buf = [MaybeUninit::::uninit(); $N]; 8 | 9 | let i = hex(*$self as usize, &mut buf); 10 | 11 | unsafe { 12 | $f.write_str(str::from_utf8_unchecked(slice::from_raw_parts( 13 | buf.as_mut_ptr().add(i).cast(), 14 | $N - i, 15 | ))) 16 | } 17 | }}; 18 | } 19 | 20 | fn hex(mut n: usize, buf: &mut [MaybeUninit]) -> usize { 21 | let ptr = buf.as_mut_ptr().cast::(); 22 | let len = buf.len(); 23 | let mut i = len - 1; 24 | 25 | loop { 26 | let d = (n % 16) as u8; 27 | unsafe { 28 | ptr.add(i) 29 | .write(if d < 10 { d + b'0' } else { (d - 10) + b'a' }); 30 | } 31 | n /= 16; 32 | 33 | i -= 1; 34 | if n == 0 { 35 | break; 36 | } 37 | } 38 | 39 | unsafe { ptr.add(i).write(b'x') } 40 | i -= 1; 41 | 42 | unsafe { ptr.add(i).write(b'0') } 43 | 44 | i 45 | } 46 | 47 | impl uDebug for *const T { 48 | #[cfg(target_pointer_width = "16")] 49 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 50 | where 51 | W: uWrite + ?Sized, 52 | { 53 | hex!(self, f, 6) 54 | } 55 | 56 | #[cfg(target_pointer_width = "32")] 57 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 58 | where 59 | W: uWrite + ?Sized, 60 | { 61 | hex!(self, f, 10) 62 | } 63 | 64 | #[cfg(target_pointer_width = "64")] 65 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 66 | where 67 | W: uWrite + ?Sized, 68 | { 69 | hex!(self, f, 18) 70 | } 71 | } 72 | 73 | impl uDebug for *mut T { 74 | #[inline(always)] 75 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 76 | where 77 | W: uWrite + ?Sized, 78 | { 79 | (*self as *const T).fmt(f) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/impls/hex.rs: -------------------------------------------------------------------------------- 1 | use crate::{uDisplayHex, uWrite, Formatter, HexOptions}; 2 | 3 | macro_rules! hex_format { 4 | ($buf:expr, $val:expr, $options:expr) => {{ 5 | let mut cursor = $buf.len(); 6 | let mut val = $val; 7 | if val <= 0 { 8 | cursor -= 1; 9 | $buf[cursor] = b'0'; 10 | } else { 11 | while val != 0 && cursor > 0 { 12 | let rem = val & 0xf; 13 | cursor -= 1; 14 | $buf[cursor] = hex_digit(rem as u8, $options.upper_case); 15 | val >>= 4; 16 | } 17 | } 18 | unsafe { core::str::from_utf8_unchecked(&$buf[cursor..]) } 19 | }}; 20 | } 21 | 22 | macro_rules! hex_pattern { 23 | ($itype: ty, $utype:ty) => { 24 | impl uDisplayHex for $itype { 25 | fn fmt_hex( 26 | &self, 27 | fmt: &mut Formatter<'_, W>, 28 | options: HexOptions, 29 | ) -> Result<(), W::Error> 30 | where 31 | W: uWrite + ?Sized, 32 | { 33 | let positive = if false && // the standard rust library doesn't format negative numbers with a minus sign 34 | *self < 0 { 35 | fmt.write_char('-')?; 36 | ((!*self) as $utype).wrapping_add(1) 37 | } else { 38 | *self as $utype 39 | }; 40 | <$utype as uDisplayHex>::fmt_hex(&positive, fmt, options) 41 | } 42 | } 43 | 44 | impl uDisplayHex for $utype { 45 | fn fmt_hex( 46 | &self, 47 | fmt: &mut Formatter<'_, W>, 48 | options: HexOptions, 49 | ) -> Result<(), W::Error> 50 | where 51 | W: uWrite + ?Sized, 52 | { 53 | let mut buffer = [b'0'; 2 * core::mem::size_of::<$utype>()]; 54 | let hex_string = hex_format!(buffer, *self, options); 55 | options.with_stuff(fmt, hex_string) 56 | } 57 | } 58 | }; 59 | } 60 | 61 | hex_pattern! {i8, u8} 62 | hex_pattern! {i16, u16} 63 | hex_pattern! {i32, u32} 64 | hex_pattern! {i64, u64} 65 | hex_pattern! {i128, u128} 66 | hex_pattern! {isize, usize} 67 | 68 | fn hex_digit(val: u8, upper_case: bool) -> u8 { 69 | if val < 10 { 70 | b'0' + val 71 | } else { 72 | (if upper_case { b'A' } else { b'a' }) + (val - 10) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/impls/std.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; 2 | 3 | use crate::{uDebug, uDisplay, uWrite, Formatter}; 4 | 5 | impl uDebug for Box 6 | where 7 | T: uDebug, 8 | { 9 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 10 | where 11 | W: uWrite + ?Sized, 12 | { 13 | ::fmt(self, f) 14 | } 15 | } 16 | 17 | impl uDisplay for Box 18 | where 19 | T: uDisplay, 20 | { 21 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 22 | where 23 | W: uWrite + ?Sized, 24 | { 25 | ::fmt(self, f) 26 | } 27 | } 28 | 29 | impl uDebug for BTreeMap 30 | where 31 | K: uDebug, 32 | V: uDebug, 33 | { 34 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 35 | where 36 | W: uWrite + ?Sized, 37 | { 38 | f.debug_map()?.entries(self)?.finish() 39 | } 40 | } 41 | 42 | impl uDebug for BTreeSet 43 | where 44 | T: uDebug, 45 | { 46 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 47 | where 48 | W: uWrite + ?Sized, 49 | { 50 | f.debug_set()?.entries(self)?.finish() 51 | } 52 | } 53 | 54 | impl uDebug for HashMap 55 | where 56 | K: uDebug, 57 | V: uDebug, 58 | { 59 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 60 | where 61 | W: uWrite + ?Sized, 62 | { 63 | f.debug_map()?.entries(self)?.finish() 64 | } 65 | } 66 | 67 | impl uDebug for HashSet 68 | where 69 | T: uDebug, 70 | { 71 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 72 | where 73 | W: uWrite + ?Sized, 74 | { 75 | f.debug_set()?.entries(self)?.finish() 76 | } 77 | } 78 | 79 | // TODO 80 | // impl uDebug for String { 81 | // fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 82 | // where 83 | // W: uWrite + ?Sized, 84 | // { 85 | // ::fmt(self, f) 86 | // } 87 | // } 88 | 89 | impl uDisplay for String { 90 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 91 | where 92 | W: uWrite + ?Sized, 93 | { 94 | ::fmt(self, f) 95 | } 96 | } 97 | 98 | impl uDebug for Vec 99 | where 100 | T: uDebug, 101 | { 102 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 103 | where 104 | W: uWrite + ?Sized, 105 | { 106 | <[T] as uDebug>::fmt(self, f) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: [main] 4 | push: 5 | branches: [main, staging, trying] 6 | 7 | name: CI 8 | 9 | jobs: 10 | test: 11 | name: test 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | include: 16 | - target: x86_64-unknown-linux-gnu 17 | toolchain: stable 18 | test: true 19 | macros_test: true 20 | 21 | - target: i686-unknown-linux-musl 22 | toolchain: stable 23 | test: true 24 | 25 | # no panics 26 | - target: thumbv7m-none-eabi 27 | toolchain: stable 28 | no_panics: true 29 | 30 | # miri 31 | - target: x86_64-unknown-linux-gnu 32 | toolchain: nightly 33 | miri: true 34 | 35 | steps: 36 | - uses: actions/checkout@v3 37 | 38 | - name: Set up toolchain 39 | run: | 40 | rustup default ${{ matrix.toolchain }} 41 | rustup target add ${{ matrix.target }} 42 | 43 | - name: Build ufmt 44 | run: | 45 | cargo check -p ufmt --target ${{ matrix.target }} 46 | 47 | - name: Build ufmt-utils 48 | if: ${{ matrix.only_build_ufmt }} 49 | run: | 50 | cargo check -p ufmt-utils --target ${{ matrix.target }} 51 | 52 | - name: Run tests 53 | if: ${{ matrix.test }} 54 | run: | 55 | cargo test --target ${{ matrix.target }} --features std 56 | 57 | - name: Run tests in macros crate 58 | if: ${{ matrix.macros_test }} 59 | run: | 60 | cd macros 61 | cargo test 62 | 63 | - name: Check absence of panicking branches 64 | if: ${{ matrix.no_panics }} 65 | run: | 66 | cd nopanic 67 | cargo build --target ${{ matrix.target }} --examples --release 68 | size $(find target/${{ matrix.target }}/release/examples -executable -type f ! -name '*-*' | sort) 69 | 70 | - name: Run tests within miri 71 | if: ${{ matrix.miri }} 72 | run: | 73 | rustup component add miri 74 | cargo miri test --features std 75 | 76 | clippy: 77 | name: clippy 78 | runs-on: ubuntu-latest 79 | steps: 80 | - uses: actions/checkout@v3 81 | 82 | - name: Run clippy (workspace) 83 | run: | 84 | cargo clippy 85 | 86 | - name: Run clippy (nopanic) 87 | run: | 88 | cd nopanic 89 | cargo clippy 90 | 91 | ci-success: 92 | name: ci 93 | if: github.event_name == 'push' && success() 94 | needs: 95 | - test 96 | - clippy 97 | runs-on: ubuntu-latest 98 | steps: 99 | - name: Mark the job as a success 100 | run: exit 0 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `μfmt` 2 | 3 | > A (6-40x) smaller, (2-9x) faster and panic-free alternative to `core::fmt` 4 | 5 | ![Call graph of formatting structs](cg.png) 6 | 7 | Call graph of a program that formats some structs (generated using 8 | [`cargo-call-stack`]). Source code can be found at the bottom of this file. The 9 | program was compiled with `-C opt-level=z`. 10 | 11 | [`cargo-call-stack`]: https://crates.io/crates/cargo-call-stack 12 | 13 | ## [API docs](https://docs.rs/ufmt) 14 | 15 | ## Design goals 16 | 17 | From highest priority to lowest priority 18 | 19 | - Optimized for binary size and speed (rather than for compilation time) 20 | 21 | - No dynamic dispatch in generated code 22 | 23 | - No panicking branches in generated code, when optimized 24 | 25 | - No recursion where possible 26 | 27 | ## Features 28 | 29 | - `Debug` and `Display`-like traits 30 | 31 | - `core::write!`-like macro 32 | 33 | - A generic `Formatter<'_, impl uWrite>` instead of a single `core::Formatter`; 34 | the `uWrite` trait has an associated error type so each writer can choose its 35 | error type. For example, the implementation for `std::String` uses 36 | `Infallible` as its error type. 37 | 38 | - `core::fmt::Formatter::debug_struct`-like API 39 | 40 | - `#[derive(uDebug)]` 41 | 42 | - Pretty formatting (`{:#?}`) for `uDebug` 43 | 44 | - Hexadecimal formatting (`{:x}`) of integer primitives (e.g. `i32`) 45 | 46 | # Minimum Supported Rust Version (MSRV) 47 | 48 | This crate does *not* have a Minimum Supported Rust Version (MSRV) and may make use of language 49 | features and API in the standard library available in the latest stable Rust version. 50 | 51 | In other words, changes in the Rust version requirement of this crate are not considered semver 52 | breaking change and may occur in patch version release. 53 | 54 | ## License 55 | 56 | All source code (including code snippets) is licensed under either of 57 | 58 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 59 | [https://www.apache.org/licenses/LICENSE-2.0][L1]) 60 | 61 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or 62 | [https://opensource.org/licenses/MIT][L2]) 63 | 64 | [L1]: https://www.apache.org/licenses/LICENSE-2.0 65 | [L2]: https://opensource.org/licenses/MIT 66 | 67 | at your option. 68 | 69 | ### Contribution 70 | 71 | Unless you explicitly state otherwise, any contribution intentionally submitted 72 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 73 | licensed as above, without any additional terms or conditions. 74 | 75 | ## Appendix 76 | 77 | ### Formatting structs (snippet) 78 | 79 | Full source code in [nopanic/examples/struct.rs](nopanic/examples/struct.rs). 80 | 81 | ``` rust 82 | // .. 83 | 84 | #[derive(Clone, Copy, uDebug)] 85 | struct Pair { 86 | x: i32, 87 | y: i32, 88 | } 89 | 90 | static X: AtomicI32 = AtomicI32::new(0); 91 | static Y: AtomicI32 = AtomicI32::new(0); 92 | 93 | #[exception] 94 | fn PendSV() { 95 | let x = X.load(Ordering::Relaxed); 96 | let y = Y.load(Ordering::Relaxed); 97 | 98 | uwrite!(&mut W, "{:?}", Braces {}).unwrap(); 99 | uwrite!(&mut W, "{:#?}", Braces {}).unwrap(); 100 | 101 | uwrite!(&mut W, "{:?}", Parens()).unwrap(); 102 | uwrite!(&mut W, "{:#?}", Parens()).unwrap(); 103 | 104 | uwrite!(&mut W, "{:?}", I32(x)).unwrap(); 105 | uwrite!(&mut W, "{:#?}", I32(x)).unwrap(); 106 | 107 | uwrite!(&mut W, "{:?}", Tuple(x, y)).unwrap(); 108 | uwrite!(&mut W, "{:#?}", Tuple(x, y)).unwrap(); 109 | 110 | let pair = Pair { x, y }; 111 | uwrite!(&mut W, "{:?}", pair).unwrap(); 112 | uwrite!(&mut W, "{:#?}", pair).unwrap(); 113 | 114 | let first = pair; 115 | let second = pair; 116 | uwrite!(&mut W, "{:?}", Nested { first, second }).unwrap(); 117 | uwrite!(&mut W, "{:#?}", Nested { first, second }).unwrap(); 118 | } 119 | 120 | // .. 121 | ``` 122 | -------------------------------------------------------------------------------- /utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `μfmt` utilities 2 | //! 3 | //! # Minimum Supported Rust Version (MSRV) 4 | //! 5 | //! This crate is guaranteed to compile on stable Rust 1.36 and up. It *might* compile on older 6 | //! versions but that may change in any new patch release. 7 | 8 | #![deny(missing_docs)] 9 | #![deny(warnings)] 10 | #![no_std] 11 | 12 | use core::{convert::Infallible, fmt, str}; 13 | 14 | use heapless::String; 15 | use ufmt_write::uWrite; 16 | 17 | macro_rules! assume_unreachable { 18 | () => { 19 | if cfg!(debug_assertions) { 20 | panic!() 21 | } else { 22 | core::hint::unreachable_unchecked() 23 | } 24 | }; 25 | } 26 | 27 | /// A write adapter that ignores all errors 28 | pub struct Ignore 29 | where 30 | W: uWrite, 31 | { 32 | writer: W, 33 | } 34 | 35 | impl Ignore 36 | where 37 | W: uWrite, 38 | { 39 | /// Creates a new `Ignore` adapter 40 | pub fn new(writer: W) -> Self { 41 | Self { writer } 42 | } 43 | 44 | /// Destroys the adapter and returns the underlying writer 45 | pub fn free(self) -> W { 46 | self.writer 47 | } 48 | } 49 | 50 | impl uWrite for Ignore 51 | where 52 | W: uWrite, 53 | { 54 | type Error = Infallible; 55 | 56 | fn write_str(&mut self, s: &str) -> Result<(), Infallible> { 57 | let _ = self.writer.write_str(s); 58 | Ok(()) 59 | } 60 | } 61 | 62 | /// A write adapter that buffers writes and automatically flushes on newlines 63 | pub struct LineBuffered 64 | where 65 | W: uWrite, 66 | { 67 | buffer: String, 68 | writer: W, 69 | } 70 | 71 | impl LineBuffered 72 | where 73 | W: uWrite, 74 | { 75 | /// Creates a new `LineBuffered` adapter 76 | pub fn new(writer: W) -> Self { 77 | Self { 78 | buffer: String::new(), 79 | writer, 80 | } 81 | } 82 | 83 | /// Flushes the contents of the buffer 84 | pub fn flush(&mut self) -> Result<(), W::Error> { 85 | let ret = self.writer.write_str(&self.buffer); 86 | self.buffer.clear(); 87 | ret 88 | } 89 | 90 | /// Destroys the adapter and returns the underlying writer 91 | pub fn free(self) -> W { 92 | self.writer 93 | } 94 | 95 | fn push_str(&mut self, s: &str) -> Result<(), W::Error> { 96 | let len = s.as_bytes().len(); 97 | if self.buffer.len() + len > self.buffer.capacity() { 98 | self.flush()?; 99 | } 100 | 101 | if len > self.buffer.capacity() { 102 | self.writer.write_str(s)?; 103 | } else { 104 | self.buffer 105 | .push_str(s) 106 | .unwrap_or_else(|_| unsafe { assume_unreachable!() }) 107 | } 108 | 109 | Ok(()) 110 | } 111 | } 112 | 113 | impl uWrite for LineBuffered 114 | where 115 | W: uWrite, 116 | { 117 | type Error = W::Error; 118 | 119 | fn write_str(&mut self, mut s: &str) -> Result<(), W::Error> { 120 | while let Some(pos) = s.as_bytes().iter().position(|b| *b == b'\n') { 121 | let line = s 122 | .get(..pos + 1) 123 | .unwrap_or_else(|| unsafe { assume_unreachable!() }); 124 | 125 | self.push_str(line)?; 126 | self.flush()?; 127 | 128 | s = s 129 | .get(pos + 1..) 130 | .unwrap_or_else(|| unsafe { assume_unreachable!() }); 131 | } 132 | 133 | self.push_str(s) 134 | } 135 | } 136 | 137 | /// An adapter struct allowing to use `ufmt` on types which implement `core::fmt::Write` 138 | /// 139 | /// For example: 140 | /// 141 | /// ``` 142 | /// use ufmt::uwrite; 143 | /// use ufmt_write::uWrite; 144 | /// use ufmt_utils::WriteAdapter; 145 | /// 146 | /// let fancy_number: u8 = 42; 147 | /// 148 | /// let mut s = String::new(); 149 | /// uwrite!(WriteAdapter(&mut s), "{:?}", fancy_number); 150 | /// ``` 151 | pub struct WriteAdapter(pub W) 152 | where 153 | W: fmt::Write; 154 | 155 | impl uWrite for WriteAdapter 156 | where 157 | W: fmt::Write, 158 | { 159 | type Error = fmt::Error; 160 | 161 | fn write_char(&mut self, c: char) -> Result<(), Self::Error> { 162 | self.0.write_char(c) 163 | } 164 | 165 | fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { 166 | self.0.write_str(s) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/impls/core.rs: -------------------------------------------------------------------------------- 1 | use crate::{uDebug, uDisplay, uWrite, Formatter}; 2 | 3 | impl uDebug for bool { 4 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 5 | where 6 | W: uWrite + ?Sized, 7 | { 8 | if *self { 9 | f.write_str("true") 10 | } else { 11 | f.write_str("false") 12 | } 13 | } 14 | } 15 | 16 | impl uDisplay for bool { 17 | #[inline(always)] 18 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 19 | where 20 | W: uWrite + ?Sized, 21 | { 22 | ::fmt(self, f) 23 | } 24 | } 25 | 26 | // FIXME this (`escape_debug`) contains a panicking branch 27 | // impl uDebug for char { 28 | // fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 29 | // where 30 | // W: uWrite + ?Sized, 31 | // { 32 | // f.write_str("'")?; 33 | // for c in self.escape_debug() { 34 | // f.write_char(c)? 35 | // } 36 | // f.write_str("'") 37 | // } 38 | // } 39 | 40 | impl uDisplay for char { 41 | #[inline(always)] 42 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 43 | where 44 | W: uWrite + ?Sized, 45 | { 46 | f.write_char(*self) 47 | } 48 | } 49 | 50 | impl uDebug for [T] 51 | where 52 | T: uDebug, 53 | { 54 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 55 | where 56 | W: uWrite + ?Sized, 57 | { 58 | f.debug_list()?.entries(self)?.finish() 59 | } 60 | } 61 | 62 | // FIXME this (`escape_debug`) contains a panicking branch 63 | // impl uDebug for str { 64 | // fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 65 | // where 66 | // W: uWrite + ?Sized, 67 | // { 68 | // f.write_str("\"")?; 69 | 70 | // let mut from = 0; 71 | // for (i, c) in self.char_indices() { 72 | // let esc = c.escape_debug(); 73 | 74 | // // If char needs escaping, flush backlog so far and write, else skip 75 | // if esc.len() != 1 { 76 | // f.write_str( 77 | // self.get(from..i) 78 | // .unwrap_or_else(|| unsafe { assume_unreachable!() }), 79 | // )?; 80 | // for c in esc { 81 | // f.write_char(c)?; 82 | // } 83 | // from = i + c.len_utf8(); 84 | // } 85 | // } 86 | 87 | // f.write_str( 88 | // self.get(from..) 89 | // .unwrap_or_else(|| unsafe { assume_unreachable!() }), 90 | // )?; 91 | // f.write_str("\"") 92 | // } 93 | // } 94 | 95 | impl uDisplay for str { 96 | #[inline(always)] 97 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 98 | where 99 | W: uWrite + ?Sized, 100 | { 101 | f.write_str(self) 102 | } 103 | } 104 | 105 | impl uDebug for &'_ T 106 | where 107 | T: uDebug + ?Sized, 108 | { 109 | #[inline(always)] 110 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 111 | where 112 | W: uWrite + ?Sized, 113 | { 114 | ::fmt(self, f) 115 | } 116 | } 117 | 118 | impl uDisplay for &'_ T 119 | where 120 | T: uDisplay + ?Sized, 121 | { 122 | #[inline(always)] 123 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 124 | where 125 | W: uWrite + ?Sized, 126 | { 127 | ::fmt(self, f) 128 | } 129 | } 130 | 131 | impl uDebug for &'_ mut T 132 | where 133 | T: uDebug + ?Sized, 134 | { 135 | #[inline(always)] 136 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 137 | where 138 | W: uWrite + ?Sized, 139 | { 140 | ::fmt(self, f) 141 | } 142 | } 143 | 144 | impl uDisplay for &'_ mut T 145 | where 146 | T: uDisplay + ?Sized, 147 | { 148 | #[inline(always)] 149 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 150 | where 151 | W: uWrite + ?Sized, 152 | { 153 | ::fmt(self, f) 154 | } 155 | } 156 | 157 | impl uDebug for Option 158 | where 159 | T: uDebug, 160 | { 161 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 162 | where 163 | W: uWrite + ?Sized, 164 | { 165 | match self { 166 | None => f.write_str("None"), 167 | Some(x) => f.debug_tuple("Some")?.field(x)?.finish(), 168 | } 169 | } 170 | } 171 | 172 | impl uDebug for Result 173 | where 174 | T: uDebug, 175 | E: uDebug, 176 | { 177 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 178 | where 179 | W: uWrite + ?Sized, 180 | { 181 | match self { 182 | Err(e) => f.debug_tuple("Err")?.field(e)?.finish(), 183 | Ok(x) => f.debug_tuple("Ok")?.field(x)?.finish(), 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/impls/uxx.rs: -------------------------------------------------------------------------------- 1 | use core::{mem::MaybeUninit, slice, str}; 2 | 3 | use crate::{uDebug, uDisplay, uWrite, Formatter}; 4 | 5 | macro_rules! uxx { 6 | ($n:expr, $buf:expr) => {{ 7 | let ptr = $buf.as_mut_ptr().cast::(); 8 | let len = $buf.len(); 9 | let mut n = $n; 10 | let mut i = len - 1; 11 | loop { 12 | unsafe { ptr.add(i).write((n % 10) as u8 + b'0') } 13 | n /= 10; 14 | 15 | if n == 0 { 16 | break; 17 | } else { 18 | i -= 1; 19 | } 20 | } 21 | 22 | unsafe { str::from_utf8_unchecked(slice::from_raw_parts(ptr.add(i), len - i)) } 23 | }}; 24 | } 25 | 26 | fn usize(n: usize, buf: &mut [MaybeUninit]) -> &str { 27 | uxx!(n, buf) 28 | } 29 | 30 | impl uDebug for u8 { 31 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 32 | where 33 | W: uWrite + ?Sized, 34 | { 35 | let mut buf = [MaybeUninit::uninit(); 3]; 36 | 37 | f.write_str(usize(usize::from(*self), &mut buf)) 38 | } 39 | } 40 | 41 | impl uDisplay for u8 { 42 | #[inline(always)] 43 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 44 | where 45 | W: uWrite + ?Sized, 46 | { 47 | ::fmt(self, f) 48 | } 49 | } 50 | 51 | impl uDebug for u16 { 52 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 53 | where 54 | W: uWrite + ?Sized, 55 | { 56 | let mut buf = [MaybeUninit::uninit(); 5]; 57 | 58 | f.write_str(usize(usize::from(*self), &mut buf)) 59 | } 60 | } 61 | 62 | impl uDisplay for u16 { 63 | #[inline(always)] 64 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 65 | where 66 | W: uWrite + ?Sized, 67 | { 68 | ::fmt(self, f) 69 | } 70 | } 71 | 72 | impl uDebug for u32 { 73 | #[cfg(not(target_pointer_width = "16"))] 74 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 75 | where 76 | W: uWrite + ?Sized, 77 | { 78 | let mut buf = [MaybeUninit::uninit(); 10]; 79 | 80 | f.write_str(usize(*self as usize, &mut buf)) 81 | } 82 | 83 | #[cfg(target_pointer_width = "16")] 84 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 85 | where 86 | W: uWrite + ?Sized, 87 | { 88 | let mut buf = [MaybeUninit::::uninit(); 10]; 89 | 90 | let s = uxx!(*self, buf); 91 | f.write_str(s) 92 | } 93 | } 94 | 95 | impl uDisplay for u32 { 96 | #[inline(always)] 97 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 98 | where 99 | W: uWrite + ?Sized, 100 | { 101 | ::fmt(self, f) 102 | } 103 | } 104 | 105 | impl uDebug for u64 { 106 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))] 107 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 108 | where 109 | W: uWrite + ?Sized, 110 | { 111 | let mut buf = [MaybeUninit::::uninit(); 20]; 112 | 113 | let s = uxx!(*self, buf); 114 | f.write_str(s) 115 | } 116 | 117 | #[cfg(target_pointer_width = "64")] 118 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 119 | where 120 | W: uWrite + ?Sized, 121 | { 122 | let mut buf = [MaybeUninit::uninit(); 20]; 123 | 124 | f.write_str(usize(*self as usize, &mut buf)) 125 | } 126 | } 127 | 128 | impl uDisplay for u64 { 129 | #[inline(always)] 130 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 131 | where 132 | W: uWrite + ?Sized, 133 | { 134 | ::fmt(self, f) 135 | } 136 | } 137 | 138 | impl uDebug for u128 { 139 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 140 | where 141 | W: uWrite + ?Sized, 142 | { 143 | let mut buf = [MaybeUninit::::uninit(); 39]; 144 | 145 | let s = uxx!(*self, buf); 146 | f.write_str(s) 147 | } 148 | } 149 | 150 | impl uDisplay for u128 { 151 | #[inline(always)] 152 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 153 | where 154 | W: uWrite + ?Sized, 155 | { 156 | ::fmt(self, f) 157 | } 158 | } 159 | 160 | impl uDebug for usize { 161 | #[cfg(target_pointer_width = "16")] 162 | #[inline(always)] 163 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 164 | where 165 | W: uWrite + ?Sized, 166 | { 167 | ::fmt(&(*self as u16), f) 168 | } 169 | 170 | #[cfg(target_pointer_width = "32")] 171 | #[inline(always)] 172 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 173 | where 174 | W: uWrite + ?Sized, 175 | { 176 | ::fmt(&(*self as u32), f) 177 | } 178 | 179 | #[cfg(target_pointer_width = "64")] 180 | #[inline(always)] 181 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 182 | where 183 | W: uWrite + ?Sized, 184 | { 185 | ::fmt(&(*self as u64), f) 186 | } 187 | } 188 | 189 | impl uDisplay for usize { 190 | #[cfg(target_pointer_width = "16")] 191 | #[inline(always)] 192 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 193 | where 194 | W: uWrite + ?Sized, 195 | { 196 | ::fmt(&(*self as u16), f) 197 | } 198 | 199 | #[cfg(target_pointer_width = "32")] 200 | #[inline(always)] 201 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 202 | where 203 | W: uWrite + ?Sized, 204 | { 205 | ::fmt(&(*self as u32), f) 206 | } 207 | 208 | #[cfg(target_pointer_width = "64")] 209 | #[inline(always)] 210 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 211 | where 212 | W: uWrite + ?Sized, 213 | { 214 | ::fmt(&(*self as u64), f) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/impls/ixx.rs: -------------------------------------------------------------------------------- 1 | use core::{mem::MaybeUninit, slice, str}; 2 | 3 | use crate::{uDebug, uDisplay, uWrite, Formatter}; 4 | 5 | macro_rules! ixx { 6 | ($uxx:ty, $n:expr, $buf:expr) => {{ 7 | let ptr = $buf.as_mut_ptr().cast::(); 8 | let len = $buf.len(); 9 | let n = $n; 10 | let negative = n.is_negative(); 11 | let mut n = if negative { 12 | match n.checked_abs() { 13 | Some(n) => n as $uxx, 14 | None => <$uxx>::max_value() / 2 + 1, 15 | } 16 | } else { 17 | n as $uxx 18 | }; 19 | let mut i = len - 1; 20 | loop { 21 | unsafe { ptr.add(i).write((n % 10) as u8 + b'0') } 22 | n /= 10; 23 | 24 | if n == 0 { 25 | break; 26 | } else { 27 | i -= 1; 28 | } 29 | } 30 | 31 | if negative { 32 | i -= 1; 33 | unsafe { ptr.add(i).write(b'-') } 34 | } 35 | 36 | unsafe { str::from_utf8_unchecked(slice::from_raw_parts(ptr.add(i), len - i)) } 37 | }}; 38 | } 39 | 40 | fn isize(n: isize, buf: &mut [MaybeUninit]) -> &str { 41 | ixx!(usize, n, buf) 42 | } 43 | 44 | impl uDebug for i8 { 45 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 46 | where 47 | W: uWrite + ?Sized, 48 | { 49 | let mut buf = [MaybeUninit::uninit(); 4]; 50 | 51 | f.write_str(isize(isize::from(*self), &mut buf)) 52 | } 53 | } 54 | 55 | impl uDisplay for i8 { 56 | #[inline(always)] 57 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 58 | where 59 | W: uWrite + ?Sized, 60 | { 61 | ::fmt(self, f) 62 | } 63 | } 64 | 65 | impl uDebug for i16 { 66 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 67 | where 68 | W: uWrite + ?Sized, 69 | { 70 | let mut buf = [MaybeUninit::uninit(); 6]; 71 | 72 | f.write_str(isize(isize::from(*self), &mut buf)) 73 | } 74 | } 75 | 76 | impl uDisplay for i16 { 77 | #[inline(always)] 78 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 79 | where 80 | W: uWrite + ?Sized, 81 | { 82 | ::fmt(self, f) 83 | } 84 | } 85 | 86 | impl uDebug for i32 { 87 | #[cfg(not(target_pointer_width = "16"))] 88 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 89 | where 90 | W: uWrite + ?Sized, 91 | { 92 | let mut buf = [MaybeUninit::uninit(); 11]; 93 | 94 | f.write_str(isize(*self as isize, &mut buf)) 95 | } 96 | 97 | #[cfg(target_pointer_width = "16")] 98 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 99 | where 100 | W: uWrite + ?Sized, 101 | { 102 | let mut buf = [MaybeUninit::::uninit(); 11]; 103 | 104 | let s = ixx!(u32, *self, buf); 105 | f.write_str(s) 106 | } 107 | } 108 | 109 | impl uDisplay for i32 { 110 | #[inline(always)] 111 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 112 | where 113 | W: uWrite + ?Sized, 114 | { 115 | ::fmt(self, f) 116 | } 117 | } 118 | 119 | impl uDebug for i64 { 120 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))] 121 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 122 | where 123 | W: uWrite + ?Sized, 124 | { 125 | let mut buf = [MaybeUninit::::uninit(); 20]; 126 | 127 | let s = ixx!(u64, *self, buf); 128 | f.write_str(s) 129 | } 130 | 131 | #[cfg(target_pointer_width = "64")] 132 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 133 | where 134 | W: uWrite + ?Sized, 135 | { 136 | let mut buf = [MaybeUninit::uninit(); 20]; 137 | 138 | f.write_str(isize(*self as isize, &mut buf)) 139 | } 140 | } 141 | 142 | impl uDisplay for i64 { 143 | #[inline(always)] 144 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 145 | where 146 | W: uWrite + ?Sized, 147 | { 148 | ::fmt(self, f) 149 | } 150 | } 151 | 152 | impl uDebug for i128 { 153 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 154 | where 155 | W: uWrite + ?Sized, 156 | { 157 | let mut buf = [MaybeUninit::::uninit(); 40]; 158 | 159 | let s = ixx!(u128, *self, buf); 160 | f.write_str(s) 161 | } 162 | } 163 | 164 | impl uDisplay for i128 { 165 | #[inline(always)] 166 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 167 | where 168 | W: uWrite + ?Sized, 169 | { 170 | ::fmt(self, f) 171 | } 172 | } 173 | 174 | impl uDebug for isize { 175 | #[cfg(target_pointer_width = "16")] 176 | #[inline(always)] 177 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 178 | where 179 | W: uWrite + ?Sized, 180 | { 181 | ::fmt(&(*self as i16), f) 182 | } 183 | 184 | #[cfg(target_pointer_width = "32")] 185 | #[inline(always)] 186 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 187 | where 188 | W: uWrite + ?Sized, 189 | { 190 | ::fmt(&(*self as i32), f) 191 | } 192 | 193 | #[cfg(target_pointer_width = "64")] 194 | #[inline(always)] 195 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 196 | where 197 | W: uWrite + ?Sized, 198 | { 199 | ::fmt(&(*self as i64), f) 200 | } 201 | } 202 | 203 | impl uDisplay for isize { 204 | #[cfg(target_pointer_width = "16")] 205 | #[inline(always)] 206 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 207 | where 208 | W: uWrite + ?Sized, 209 | { 210 | ::fmt(&(*self as i16), f) 211 | } 212 | 213 | #[cfg(target_pointer_width = "32")] 214 | #[inline(always)] 215 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 216 | where 217 | W: uWrite + ?Sized, 218 | { 219 | ::fmt(&(*self as i32), f) 220 | } 221 | 222 | #[cfg(target_pointer_width = "64")] 223 | #[inline(always)] 224 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 225 | where 226 | W: uWrite + ?Sized, 227 | { 228 | ::fmt(&(*self as i64), f) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /tests/vs-std-write.rs: -------------------------------------------------------------------------------- 1 | use core::convert::Infallible; 2 | use std::collections::{BTreeMap, BTreeSet}; 3 | 4 | use ufmt::{derive::uDebug, uDebug, uWrite, uwrite, uwriteln, Formatter}; 5 | 6 | macro_rules! uformat { 7 | ($($tt:tt)*) => {{ 8 | let mut s = String::new(); 9 | #[allow(unreachable_code)] 10 | match ufmt::uwrite!(&mut s, $($tt)*) { 11 | Ok(_) => Ok(s), 12 | Err(e) => Err(e), 13 | } 14 | }}; 15 | } 16 | 17 | macro_rules! cmp { 18 | ($($tt:tt)*) => { 19 | assert_eq!( 20 | uformat!($($tt)*), 21 | Ok(format!($($tt)*)), 22 | ) 23 | } 24 | } 25 | 26 | #[test] 27 | fn core() { 28 | cmp!("{:?}", None::); 29 | cmp!("{:#?}", None::); 30 | 31 | cmp!("{:?}", Some(0)); 32 | cmp!("{:#?}", Some(0)); 33 | 34 | cmp!("{:?}", Ok::<_, ()>(1)); 35 | cmp!("{:#?}", Ok::<_, ()>(1)); 36 | 37 | cmp!("{:?}", Err::<(), _>(2)); 38 | cmp!("{:#?}", Err::<(), _>(2)); 39 | } 40 | 41 | #[test] 42 | fn recursion() { 43 | #[derive(uDebug, Debug)] 44 | struct Node { 45 | value: i32, 46 | next: Option>, 47 | } 48 | 49 | fn x() -> Node { 50 | let tail = Node { 51 | value: 0, 52 | next: None, 53 | }; 54 | Node { 55 | value: 1, 56 | next: Some(Box::new(tail)), 57 | } 58 | } 59 | 60 | cmp!("{:?}", x()); 61 | cmp!("{:#?}", x()); 62 | } 63 | 64 | #[test] 65 | fn uxx() { 66 | cmp!("{}", 0u8); 67 | cmp!("{}", 10u8); 68 | cmp!("{}", 100u8); 69 | 70 | // extreme values 71 | cmp!("{}", u8::max_value()); 72 | cmp!("{}", u16::max_value()); 73 | cmp!("{}", u32::max_value()); 74 | cmp!("{}", u64::max_value()); 75 | cmp!("{}", u128::max_value()); 76 | cmp!("{}", usize::max_value()); 77 | } 78 | 79 | #[test] 80 | fn ixx() { 81 | // sanity check 82 | cmp!("{}", 0i8); 83 | cmp!("{}", 10i8); 84 | cmp!("{}", 100i8); 85 | 86 | // extreme values 87 | cmp!("{}", i8::min_value()); 88 | cmp!("{}", i8::max_value()); 89 | cmp!("{}", i16::min_value()); 90 | cmp!("{}", i16::max_value()); 91 | cmp!("{}", i32::min_value()); 92 | cmp!("{}", i32::max_value()); 93 | cmp!("{}", i64::min_value()); 94 | cmp!("{}", i64::max_value()); 95 | cmp!("{}", i128::min_value()); 96 | cmp!("{}", i128::max_value()); 97 | cmp!("{}", isize::min_value()); 98 | cmp!("{}", isize::max_value()); 99 | } 100 | 101 | #[test] 102 | fn fmt() { 103 | cmp!("Hello, world!"); 104 | cmp!("The answer is {}", 42); 105 | } 106 | 107 | #[test] 108 | fn map() { 109 | fn x() -> BTreeMap { 110 | let mut m = BTreeMap::new(); 111 | m.insert(1, 2); 112 | m.insert(3, 4); 113 | m 114 | } 115 | 116 | cmp!("{:?}", BTreeMap::<(), ()>::new()); 117 | cmp!("{:?}", x()); 118 | 119 | cmp!("{:#?}", BTreeMap::<(), ()>::new()); 120 | cmp!("{:#?}", x()); 121 | } 122 | 123 | #[test] 124 | fn set() { 125 | fn x() -> BTreeSet { 126 | let mut m = BTreeSet::new(); 127 | m.insert(1); 128 | m.insert(3); 129 | m 130 | } 131 | 132 | cmp!("{:?}", BTreeSet::<()>::new()); 133 | cmp!("{:?}", x()); 134 | 135 | cmp!("{:#?}", BTreeSet::<()>::new()); 136 | cmp!("{:#?}", x()); 137 | } 138 | 139 | #[test] 140 | fn struct_() { 141 | #[derive(Debug, uDebug)] 142 | struct Braces {} 143 | 144 | #[derive(Debug, uDebug)] 145 | struct Parens(); 146 | 147 | #[derive(Debug, Default, uDebug)] 148 | struct I32(i32); 149 | 150 | #[derive(Debug, Default, uDebug)] 151 | struct Tuple(i32, i32); 152 | 153 | #[derive(Debug, Default, uDebug)] 154 | struct Pair { 155 | x: i32, 156 | y: i32, 157 | } 158 | 159 | #[derive(Debug, Default, uDebug)] 160 | struct Nested { 161 | first: Pair, 162 | second: Pair, 163 | } 164 | 165 | cmp!("{:?}", Braces {}); 166 | cmp!("{:?}", Parens()); 167 | cmp!("{:?}", I32::default()); 168 | cmp!("{:?}", Tuple::default()); 169 | cmp!("{:?}", Pair::default()); 170 | cmp!("{:?}", Nested::default()); 171 | 172 | cmp!("{:#?}", Braces {}); 173 | cmp!("{:#?}", Parens()); 174 | cmp!("{:#?}", I32::default()); 175 | cmp!("{:#?}", Tuple::default()); 176 | cmp!("{:#?}", Pair::default()); 177 | cmp!("{:#?}", Nested::default()); 178 | } 179 | 180 | #[test] 181 | fn enum_() { 182 | #[derive(Debug, uDebug)] 183 | enum X { 184 | A, 185 | B(u8, u16), 186 | C { x: u8, y: u16 }, 187 | } 188 | 189 | cmp!("{:?}", X::A); 190 | cmp!("{:?}", X::B(0, 1)); 191 | cmp!("{:?}", X::C { x: 0, y: 1 }); 192 | 193 | cmp!("{:#?}", X::A); 194 | cmp!("{:#?}", X::B(0, 1)); 195 | cmp!("{:#?}", X::C { x: 0, y: 1 }); 196 | } 197 | 198 | #[test] 199 | fn ptr() { 200 | cmp!("{:?}", 1 as *const u8); 201 | cmp!("{:?}", 0xf as *const u8); 202 | cmp!("{:?}", 0xff as *const u8); 203 | cmp!("{:?}", 0xfff as *const u8); 204 | cmp!("{:?}", 0xffff as *const u8); 205 | cmp!("{:?}", 0xfffff as *const u8); 206 | cmp!("{:?}", 0xffffff as *const u8); 207 | cmp!("{:?}", 0xfffffff as *const u8); 208 | cmp!("{:?}", 0xffffffff as *const u8); 209 | 210 | #[cfg(target_pointer_width = "64")] 211 | cmp!("{:?}", 0xfffffffff as *const u8); 212 | } 213 | 214 | #[test] 215 | fn tuples() { 216 | cmp!("{:?}", ()); 217 | cmp!("{:?}", (1,)); 218 | cmp!("{:?}", (1, 2)); 219 | cmp!("{:?}", (1, 2, 3)); 220 | cmp!("{:?}", (1, 2, 3, 4)); 221 | cmp!("{:?}", (1, 2, 3, 4, 5)); 222 | cmp!("{:?}", (1, 2, 3, 4, 5, 6)); 223 | cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7)); 224 | cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7, 8)); 225 | cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7, 8, 9)); 226 | cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); 227 | cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); 228 | cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); 229 | 230 | cmp!("{:#?}", ()); 231 | cmp!("{:#?}", (1,)); 232 | cmp!("{:#?}", (1, 2)); 233 | } 234 | 235 | #[test] 236 | fn slice() { 237 | cmp!("{:?}", [0; 0]); 238 | cmp!("{:?}", [0]); 239 | cmp!("{:?}", [0, 1]); 240 | 241 | cmp!("{:#?}", [0; 0]); 242 | cmp!("{:#?}", [0]); 243 | cmp!("{:#?}", [0, 1]); 244 | } 245 | 246 | #[test] 247 | fn uwriteln() { 248 | let mut s = String::new(); 249 | uwriteln!(&mut s, "Hello").unwrap(); 250 | uwriteln!(&mut s, "World",).unwrap(); 251 | assert_eq!(s, "Hello\nWorld\n"); 252 | } 253 | 254 | #[test] 255 | fn formatter_uwrite() { 256 | #[derive(uDebug)] 257 | struct X; 258 | 259 | struct Y; 260 | 261 | impl uDebug for Y { 262 | fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> 263 | where 264 | W: uWrite + ?Sized, 265 | { 266 | uwrite!(f, "{:?}", X) 267 | } 268 | } 269 | 270 | assert_eq!(uformat!("{:?}", Y).unwrap(), "X") 271 | } 272 | 273 | #[test] 274 | fn generic() { 275 | #[derive(uDebug, Debug)] 276 | struct X(T); 277 | 278 | cmp!("{:?}", X(0)); 279 | 280 | #[derive(uDebug, Debug)] 281 | enum Y { 282 | Z(T), 283 | } 284 | 285 | cmp!("{:?}", Y::Z(0)); 286 | } 287 | 288 | // compile-pass test 289 | #[allow(dead_code)] 290 | fn static_lifetime(x: &'static mut u32) { 291 | fn foo(x: &'static mut u32) -> *mut u32 { 292 | x as *mut u32 293 | } 294 | 295 | uwrite!(&mut String::new(), "{:?}", foo(x)).ok(); 296 | } 297 | 298 | // test dynamically sized writer 299 | #[test] 300 | fn dst() { 301 | struct Cursor 302 | where 303 | B: ?Sized, 304 | { 305 | pos: usize, 306 | buffer: B, 307 | } 308 | 309 | impl Cursor { 310 | fn new(buffer: B) -> Self { 311 | Cursor { pos: 0, buffer } 312 | } 313 | } 314 | 315 | impl uWrite for Cursor<[u8]> { 316 | type Error = Infallible; 317 | 318 | fn write_str(&mut self, s: &str) -> Result<(), Infallible> { 319 | let bytes = s.as_bytes(); 320 | let len = bytes.len(); 321 | let start = self.pos; 322 | if let Some(buffer) = self.buffer.get_mut(start..start + len) { 323 | buffer.copy_from_slice(bytes); 324 | self.pos += len; 325 | } 326 | 327 | Ok(()) 328 | } 329 | } 330 | 331 | let mut cursor = Cursor::new([0; 256]); 332 | let cursor: &mut Cursor<[u8]> = &mut cursor; 333 | 334 | uwrite!(cursor, "The answer is {}", 42).ok(); 335 | 336 | let msg = b"The answer is 42"; 337 | assert_eq!(&cursor.buffer[..msg.len()], msg); 338 | } 339 | 340 | #[test] 341 | fn hex() { 342 | cmp!("{:x}", 771u32); 343 | cmp!("{:x}", -10000); 344 | cmp!("{:4x}", 33); 345 | cmp!("{:4x}", 89001); 346 | cmp!("{:04x}", 33); 347 | cmp!("{:#03x}", 33); 348 | cmp!("{:#09x}", 33); 349 | cmp!("{:#x}", 71); 350 | 351 | // extreme values 352 | cmp!("{:x}", i8::min_value()); 353 | cmp!("{:x}", i8::max_value()); 354 | cmp!("{:x}", i16::min_value()); 355 | cmp!("{:x}", i16::max_value()); 356 | cmp!("{:x}", i32::min_value()); 357 | cmp!("{:x}", i32::max_value()); 358 | cmp!("{:x}", i64::min_value()); 359 | cmp!("{:x}", i64::max_value()); 360 | cmp!("{:x}", i128::min_value()); 361 | cmp!("{:x}", i128::max_value()); 362 | cmp!("{:x}", isize::min_value()); 363 | cmp!("{:x}", isize::max_value()); 364 | 365 | // ::fmt(-128) 366 | } 367 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/helpers.rs: -------------------------------------------------------------------------------- 1 | use crate::{uDebug, uWrite, Formatter}; 2 | 3 | impl<'w, W> Formatter<'w, W> 4 | where 5 | W: uWrite + ?Sized, 6 | { 7 | /// Creates a `DebugList` builder designed to assist with creation of `uDebug` implementations 8 | /// for list-like structures. 9 | pub fn debug_list(&mut self) -> Result, W::Error> { 10 | self.write_str("[")?; 11 | 12 | if self.pretty { 13 | self.indentation += 1; 14 | } 15 | 16 | Ok(DebugList { 17 | first: true, 18 | formatter: self, 19 | }) 20 | } 21 | 22 | /// Creates a `DebugMap` builder designed to assist with creation of `uDebug` implementations 23 | /// for map-like structures. 24 | pub fn debug_map(&mut self) -> Result, W::Error> { 25 | self.write_str("{")?; 26 | 27 | if self.pretty { 28 | self.indentation += 1; 29 | } 30 | 31 | Ok(DebugMap { 32 | first: true, 33 | formatter: self, 34 | }) 35 | } 36 | 37 | /// Creates a `DebugSet` builder designed to assist with creation of `uDebug` implementations 38 | /// for set-like structures. 39 | pub fn debug_set(&mut self) -> Result, W::Error> { 40 | self.write_str("{")?; 41 | 42 | if self.pretty { 43 | self.indentation += 1; 44 | } 45 | 46 | Ok(DebugSet { 47 | first: true, 48 | formatter: self, 49 | }) 50 | } 51 | 52 | /// Creates a `DebugStruct` builder designed to assist with creation of `uDebug` implementations 53 | /// for structs. 54 | pub fn debug_struct(&mut self, name: &str) -> Result, W::Error> { 55 | self.write_str(name)?; 56 | 57 | if self.pretty { 58 | self.indentation += 1; 59 | } 60 | 61 | Ok(DebugStruct { 62 | first: true, 63 | formatter: self, 64 | }) 65 | } 66 | 67 | /// Creates a `DebugTuple` builder designed to assist with creation of `uDebug` implementations 68 | /// for tuple structs. 69 | pub fn debug_tuple(&mut self, name: &str) -> Result, W::Error> { 70 | self.write_str(name)?; 71 | 72 | if self.pretty { 73 | self.indentation += 1; 74 | } 75 | 76 | Ok(DebugTuple { 77 | fields: 0, 78 | first: true, 79 | formatter: self, 80 | unnamed: name.is_empty(), 81 | }) 82 | } 83 | } 84 | 85 | /// A struct to help with [`uDebug`] implementations. 86 | /// 87 | /// This is useful when you wish to output a formatted list of items as a part of your 88 | /// [`uDebug::fmt`] implementation. 89 | /// 90 | /// This can be constructed by the [`Formatter::debug_list`] method. 91 | pub struct DebugList<'f, 'w, W> 92 | where 93 | W: uWrite + ?Sized, 94 | { 95 | first: bool, 96 | formatter: &'f mut Formatter<'w, W>, 97 | } 98 | 99 | impl DebugList<'_, '_, W> 100 | where 101 | W: uWrite + ?Sized, 102 | { 103 | /// Adds a new entry to the list output. 104 | pub fn entry(&mut self, entry: &impl uDebug) -> Result<&mut Self, W::Error> { 105 | if self.first { 106 | self.first = false; 107 | 108 | if self.formatter.pretty { 109 | self.formatter.write_str("\n")?; 110 | } 111 | } else if !self.formatter.pretty { 112 | self.formatter.write_str(", ")?; 113 | } 114 | 115 | if self.formatter.pretty { 116 | self.formatter.indent()?; 117 | } 118 | 119 | entry.fmt(self.formatter)?; 120 | 121 | if self.formatter.pretty { 122 | self.formatter.write_str(",\n")?; 123 | } 124 | 125 | Ok(self) 126 | } 127 | 128 | /// Adds the contents of an iterator of entries to the list output. 129 | pub fn entries( 130 | &mut self, 131 | entries: impl IntoIterator, 132 | ) -> Result<&mut Self, W::Error> { 133 | for entry in entries { 134 | self.entry(&entry)?; 135 | } 136 | 137 | Ok(self) 138 | } 139 | 140 | /// Finishes output 141 | pub fn finish(&mut self) -> Result<(), W::Error> { 142 | if self.formatter.pretty { 143 | self.formatter.indentation -= 1; 144 | self.formatter.indent()?; 145 | } 146 | 147 | self.formatter.write_str("]") 148 | } 149 | } 150 | 151 | /// A struct to help with [`uDebug`] implementations. 152 | /// 153 | /// This is useful when you wish to output a formatted map as a part of your [`uDebug::fmt`] 154 | /// implementation. 155 | /// 156 | /// This can be constructed by the [`Formatter::debug_map`] method. 157 | pub struct DebugMap<'f, 'w, W> 158 | where 159 | W: uWrite + ?Sized, 160 | { 161 | first: bool, 162 | formatter: &'f mut Formatter<'w, W>, 163 | } 164 | 165 | impl DebugMap<'_, '_, W> 166 | where 167 | W: uWrite + ?Sized, 168 | { 169 | /// Adds a new entry to the map output. 170 | pub fn entry(&mut self, key: &impl uDebug, value: &impl uDebug) -> Result<&mut Self, W::Error> { 171 | if self.first { 172 | self.first = false; 173 | 174 | if self.formatter.pretty { 175 | self.formatter.write_str("\n")?; 176 | } 177 | } else if !self.formatter.pretty { 178 | self.formatter.write_str(", ")?; 179 | } 180 | 181 | if self.formatter.pretty { 182 | self.formatter.indent()?; 183 | } 184 | 185 | key.fmt(self.formatter)?; 186 | self.formatter.write_str(": ")?; 187 | value.fmt(self.formatter)?; 188 | 189 | if self.formatter.pretty { 190 | self.formatter.write_str(",\n")?; 191 | } 192 | 193 | Ok(self) 194 | } 195 | 196 | /// Adds the contents of an iterator of entries to the map output. 197 | pub fn entries( 198 | &mut self, 199 | entries: impl IntoIterator, 200 | ) -> Result<&mut Self, W::Error> { 201 | for (k, v) in entries.into_iter() { 202 | self.entry(&k, &v)?; 203 | } 204 | 205 | Ok(self) 206 | } 207 | 208 | /// Finishes output 209 | pub fn finish(&mut self) -> Result<(), W::Error> { 210 | self.formatter.write_str("}") 211 | } 212 | } 213 | 214 | /// A struct to help with [`uDebug`] implementations. 215 | /// 216 | /// This is useful when you wish to output a formatted set of items as a part of your 217 | /// [`uDebug::fmt`] implementation. 218 | /// 219 | /// This can be constructed by the [`Formatter::debug_set`] method. 220 | pub struct DebugSet<'f, 'w, W> 221 | where 222 | W: uWrite + ?Sized, 223 | { 224 | first: bool, 225 | formatter: &'f mut Formatter<'w, W>, 226 | } 227 | 228 | impl DebugSet<'_, '_, W> 229 | where 230 | W: uWrite + ?Sized, 231 | { 232 | /// Adds a new entry to the set output. 233 | pub fn entry(&mut self, entry: &impl uDebug) -> Result<&mut Self, W::Error> { 234 | if self.first { 235 | self.first = false; 236 | 237 | if self.formatter.pretty { 238 | self.formatter.write_str("\n")?; 239 | } 240 | } else if !self.formatter.pretty { 241 | self.formatter.write_str(", ")?; 242 | } 243 | 244 | if self.formatter.pretty { 245 | self.formatter.indent()?; 246 | } 247 | 248 | entry.fmt(self.formatter)?; 249 | 250 | if self.formatter.pretty { 251 | self.formatter.write_str(",\n")?; 252 | } 253 | 254 | Ok(self) 255 | } 256 | 257 | /// Adds the contents of an iterator of entries to the set output. 258 | pub fn entries( 259 | &mut self, 260 | entries: impl IntoIterator, 261 | ) -> Result<&mut Self, W::Error> { 262 | for entry in entries { 263 | self.entry(&entry)?; 264 | } 265 | 266 | Ok(self) 267 | } 268 | 269 | /// Finishes output 270 | pub fn finish(&mut self) -> Result<(), W::Error> { 271 | self.formatter.write_str("}") 272 | } 273 | } 274 | 275 | /// A struct to help with [`uDebug`] implementations. 276 | /// 277 | /// This is useful when you wish to output a formatted struct as a part of your [`uDebug::fmt`] 278 | /// implementation. 279 | /// 280 | /// This can be constructed by the [`Formatter::debug_struct`] method. 281 | pub struct DebugStruct<'f, 'w, W> 282 | where 283 | W: uWrite + ?Sized, 284 | { 285 | first: bool, 286 | formatter: &'f mut Formatter<'w, W>, 287 | } 288 | 289 | impl DebugStruct<'_, '_, W> 290 | where 291 | W: uWrite + ?Sized, 292 | { 293 | /// Adds a new field to the generated struct output. 294 | pub fn field(&mut self, name: &str, value: &impl uDebug) -> Result<&mut Self, W::Error> { 295 | if self.first { 296 | self.first = false; 297 | 298 | self.formatter.write_str(" {")?; 299 | 300 | if self.formatter.pretty { 301 | self.formatter.write_str("\n")?; 302 | } else { 303 | self.formatter.write_str(" ")?; 304 | } 305 | } else if !self.formatter.pretty { 306 | self.formatter.write_str(", ")?; 307 | } 308 | 309 | if self.formatter.pretty { 310 | self.formatter.indent()?; 311 | } 312 | 313 | self.formatter.write_str(name)?; 314 | self.formatter.write_str(": ")?; 315 | value.fmt(self.formatter)?; 316 | 317 | if self.formatter.pretty { 318 | self.formatter.write_str(",\n")?; 319 | } 320 | 321 | Ok(self) 322 | } 323 | 324 | /// Finishes output 325 | pub fn finish(&mut self) -> Result<(), W::Error> { 326 | if self.formatter.pretty { 327 | self.formatter.indentation -= 1; 328 | } 329 | 330 | if !self.first { 331 | if self.formatter.pretty { 332 | self.formatter.indent()?; 333 | } else { 334 | self.formatter.write_str(" ")?; 335 | } 336 | 337 | self.formatter.write_str("}")?; 338 | } 339 | 340 | Ok(()) 341 | } 342 | } 343 | 344 | /// A struct to help with [`uDebug`] implementations. 345 | /// 346 | /// This is useful when you wish to output a formatted tuple as a part of your [`uDebug::fmt`] 347 | /// implementation. 348 | /// 349 | /// This can be constructed by the [`Formatter::debug_tuple`] method. 350 | pub struct DebugTuple<'f, 'w, W> 351 | where 352 | W: uWrite + ?Sized, 353 | { 354 | fields: u8, 355 | first: bool, 356 | formatter: &'f mut Formatter<'w, W>, 357 | unnamed: bool, 358 | } 359 | 360 | impl DebugTuple<'_, '_, W> 361 | where 362 | W: uWrite + ?Sized, 363 | { 364 | /// Adds a new field to the generated tuple struct output. 365 | pub fn field(&mut self, value: &impl uDebug) -> Result<&mut Self, W::Error> { 366 | self.fields += 1; 367 | 368 | if self.first { 369 | self.first = false; 370 | 371 | self.formatter.write_str("(")?; 372 | 373 | if self.formatter.pretty { 374 | self.formatter.write_str("\n")?; 375 | } 376 | } else if !self.formatter.pretty { 377 | self.formatter.write_str(", ")?; 378 | } 379 | 380 | if self.formatter.pretty { 381 | self.formatter.indent()?; 382 | } 383 | 384 | value.fmt(self.formatter)?; 385 | 386 | if self.formatter.pretty { 387 | self.formatter.write_str(",\n")?; 388 | } 389 | 390 | Ok(self) 391 | } 392 | 393 | /// Finishes output 394 | pub fn finish(&mut self) -> Result<(), W::Error> { 395 | if self.formatter.pretty { 396 | self.formatter.indentation -= 1; 397 | } 398 | 399 | if !self.first { 400 | if self.formatter.pretty { 401 | self.formatter.indent()?; 402 | } else if self.unnamed && self.fields == 1 { 403 | // this is a one-element tuple so we need a trailing comma 404 | self.formatter.write_str(",")?; 405 | } 406 | 407 | self.formatter.write_str(")")?; 408 | } 409 | 410 | Ok(()) 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `μfmt`, a (6-40x) smaller, (2-9x) faster and panic-free alternative to `core::fmt` 2 | //! 3 | //! # Design goals 4 | //! 5 | //! From highest priority to lowest priority 6 | //! 7 | //! - Optimized for binary size and speed (rather than for compilation time) 8 | //! - No dynamic dispatch in generated code 9 | //! - No panicking branches in generated code, when optimized 10 | //! - No recursion where possible 11 | //! 12 | //! # Features 13 | //! 14 | //! - [`Debug`] and [`Display`]-like traits 15 | //! - [`core::write!`][uwrite]-like macro 16 | //! - A generic [`Formatter<'_, impl uWrite>`][formatter] instead of a single `core::Formatter`; the 17 | //! [`uWrite`] trait has an associated error type so each writer can choose its error type. For 18 | //! example, the implementation for `std::String` uses [`Infallible`] as its error type. 19 | //! - [`core::fmt::Formatter::debug_struct`][debug_struct]-like API 20 | //! - [`#[derive(uDebug)]`][derive] 21 | //! - Pretty formatting (`{:#?}`) for `uDebug` 22 | //! - Hexadecimal formatting (`{:x}`) of integer primitives (e.g. `i32`) -- currently cannot be extended to other types 23 | //! 24 | //! [`Debug`]: trait.uDebug.html 25 | //! [`Display`]: trait.uDisplay.html 26 | //! [uwrite]: index.html#reexports 27 | //! [formatter]: struct.Formatter.html 28 | //! [`uWrite`]: trait.uWrite.html 29 | //! [`Infallible`]: https://doc.rust-lang.org/core/convert/enum.Infallible.html 30 | //! [debug_struct]: struct.Formatter.html#method.debug_struct 31 | //! [derive]: derive/index.html 32 | //! 33 | //! # Non-features 34 | //! 35 | //! These are out of scope 36 | //! 37 | //! - Padding, alignment and other formatting options 38 | //! - Formatting floating point numbers 39 | //! 40 | //! # Examples 41 | //! 42 | //! - `uwrite!` / `uwriteln!` 43 | //! 44 | //! ``` 45 | //! use ufmt::{derive::uDebug, uwrite}; 46 | //! 47 | //! #[derive(uDebug)] 48 | //! struct Pair { x: u32, y: u32 } 49 | //! 50 | //! let mut s = String::new(); 51 | //! let pair = Pair { x: 1, y: 2 }; 52 | //! uwrite!(s, "{:?}", pair).unwrap(); 53 | //! assert_eq!(s, "Pair { x: 1, y: 2 }"); 54 | //! ``` 55 | //! 56 | //! - Hexadecimal formatting 57 | //! 58 | //! Lowercase (`{:x}`), uppercase (`{:X}`), `0x`-prefix (`{:#x}`) and padding (`{:02x}`) are 59 | //! supported on primitive integer types. 60 | //! 61 | //! ``` 62 | //! use ufmt::uwrite; 63 | //! 64 | //! let mut s = String::new(); 65 | //! uwrite!(s, "{:#06x}", 0x42); 66 | //! assert_eq!(s, "0x0042"); 67 | //! ``` 68 | //! 69 | //! - implementing `uWrite` 70 | //! 71 | //! When implementing the `uWrite` trait you should prefer the `ufmt_write::uWrite` crate over the 72 | //! `ufmt::uWrite` crate for better forward compatibility. 73 | //! 74 | //! ``` 75 | //! use core::convert::Infallible; 76 | //! 77 | //! use ufmt_write::uWrite; 78 | //! 79 | //! struct MyWriter; 80 | //! 81 | //! impl uWrite for MyWriter { 82 | //! type Error = Infallible; 83 | //! 84 | //! fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { 85 | //! // .. 86 | //! Ok(()) 87 | //! } 88 | //! } 89 | //! ``` 90 | //! 91 | //! - writing a `macro_rules!` macro that uses `uwrite!` (or `uwriteln!`). 92 | //! 93 | //! ``` 94 | //! // like `std::format!` it returns a `std::String` but uses `uwrite!` instead of `write!` 95 | //! macro_rules! uformat { 96 | //! // IMPORTANT use `tt` fragments instead of `expr` fragments (i.e. `$($exprs:expr),*`) 97 | //! ($($tt:tt)*) => {{ 98 | //! let mut s = String::new(); 99 | //! match ufmt::uwrite!(&mut s, $($tt)*) { 100 | //! Ok(_) => Ok(s), 101 | //! Err(e) => Err(e), 102 | //! } 103 | //! }} 104 | //! } 105 | //! ``` 106 | //! 107 | //! # Benchmarks 108 | //! 109 | //! The benchmarks ran on a ARM Cortex-M3 chip (`thumbv7m-none-eabi`). 110 | //! 111 | //! The benchmarks were compiled with `nightly-2019-05-01`, `-C opt-level=3`, `lto = true`, 112 | //! `codegen-units = 1`. 113 | //! 114 | //! In all benchmarks `x = i32::MIN` and `y = i32::MIN` plus some obfuscation was applied to 115 | //! prevent const-propagation of the `*write!` arguments. 116 | //! 117 | //! The unit of time is one core clock cycle: 125 ns (8 MHz) 118 | //! 119 | //! The `.text` and `.rodata` columns indicate the delta (in bytes) when commenting out the 120 | //! `*write!` statement. 121 | //! 122 | //! |Code |Time|% |`.text`|% |`.rodata`|% | 123 | //! |------------------------------------------|----|---------|-------|---------|---------|--------| 124 | //! |`write!("Hello, world!")` |154 |~ |1906 |~ |248 |~ | 125 | //! |`uwrite!("Hello, world!")` |20 |**13.0%**|34 |**1.8%** |16 |**6.5%**| 126 | //! |`write!(w, "{}", 0i32)` |256 |~ |1958 |~ |232 |~ | 127 | //! |`uwrite!(w, "{}", 0i32)` |37 |**14.5%**|288 |**14.7%**|0 |**0%** | 128 | //! |`write!(w, "{}", x)` |381 |~ | 129 | //! |`uwrite!(w, "{}", x)` |295 |77.4% | 130 | //! |`write!(w, "{:?}", Pair { x: 0, y: 0 })` |996 |~ |4704 |~ |312 |~ | 131 | //! |`uwrite!(w, "{:?}", Pair { x: 0, y: 0 })` |254 |**25.5%**|752 |**16.0%**|24 |**7.7%**| 132 | //! |`write!(w, "{:?}", Pair { x, y })` |1264|~ | 133 | //! |`uwrite!(w, "{:?}", Pair { x, y })` |776 |61.4% | 134 | //! |`write!(w, "{:#?}", Pair { x: 0, y: 0 })` |2853|~ |4710 |~ |348 |~ | 135 | //! |`uwrite!(w, "{:#?}", Pair { x: 0, y: 0 })`|301 |**10.6%**|754 |**16.0%**|24 |**6.9%**| 136 | //! |`write!(w, "{:#?}", Pair { x, y })` |3693|~ | 137 | //! |`uwrite!(w, "{:#?}", Pair { x, y })` |823 |**22.3%**| 138 | //! 139 | //! 140 | //! Benchmark program: 141 | //! 142 | //! ``` ignore 143 | //! static X: AtomicI32 = AtomicI32::new(i32::MIN); // or `0` 144 | //! static Y: AtomicI32 = AtomicI32::new(i32::MIN); // or `0` 145 | //! 146 | //! #[exception] 147 | //! fn PendSV() { 148 | //! // read DWT.CYCCNT here 149 | //! 150 | //! let x = X.load(Ordering::Relaxed); 151 | //! let y = Y.load(Ordering::Relaxed); 152 | //! 153 | //! let p = Pair { x, y }; 154 | //! 155 | //! uwrite!(&mut W, "{:#?}", p).ok(); 156 | //! 157 | //! // write!(&mut W, "{:#?}", p).ok(); 158 | //! 159 | //! asm::bkpt(); // read DWT.CYCCNT here 160 | //! } 161 | //! ``` 162 | //! 163 | //! Writer used in the benchmarks: 164 | //! 165 | //! ``` 166 | //! use core::{convert::Infallible, fmt, ptr}; 167 | //! 168 | //! use ufmt::uWrite; 169 | //! 170 | //! struct W; 171 | //! 172 | //! impl uWrite for W { 173 | //! type Error = Infallible; 174 | //! 175 | //! fn write_str(&mut self, s: &str) -> Result<(), Infallible> { 176 | //! s.as_bytes() 177 | //! .iter() 178 | //! .for_each(|b| unsafe { drop(ptr::read_volatile(b)) }); 179 | //! 180 | //! Ok(()) 181 | //! } 182 | //! } 183 | //! 184 | //! impl fmt::Write for W { 185 | //! fn write_str(&mut self, s: &str) -> fmt::Result { 186 | //! s.as_bytes() 187 | //! .iter() 188 | //! .for_each(|b| unsafe { drop(ptr::read_volatile(b)) }); 189 | //! 190 | //! Ok(()) 191 | //! } 192 | //! } 193 | //! ``` 194 | //! 195 | //! # Minimum Supported Rust Version (MSRV) 196 | //! 197 | //! This crate does *not* have a Minimum Supported Rust Version (MSRV) and may make use of language 198 | //! features and API in the standard library available in the latest stable Rust version. 199 | //! 200 | //! In other words, changes in the Rust version requirement of this crate are not considered semver 201 | //! breaking change and may occur in patch version release. 202 | 203 | #![cfg_attr(not(feature = "std"), no_std)] 204 | #![deny(missing_docs)] 205 | #![deny(warnings)] 206 | 207 | // this lets us use `uwrite!` in the test suite 208 | #[allow(unused_extern_crates)] 209 | #[cfg(test)] 210 | extern crate self as ufmt; 211 | 212 | use core::str; 213 | 214 | pub use ufmt_write::uWrite; 215 | 216 | /// Write formatted data into a buffer 217 | /// 218 | /// This macro accepts a format string, a list of arguments, and a 'writer'. Arguments will be 219 | /// formatted according to the specified format string and the result will be passed to the writer. 220 | /// The writer must have type `[&mut] impl uWrite` or `[&mut] ufmt::Formatter<'_, impl uWrite>`. The 221 | /// macro returns the associated `Error` type of the `uWrite`-r. 222 | /// 223 | /// The syntax is similar to [`core::write!`] but only a handful of argument types are accepted: 224 | /// 225 | /// [`core::write!`]: https://doc.rust-lang.org/core/macro.write.html 226 | /// 227 | /// - `{}` - `uDisplay` 228 | /// - `{:?}` - `uDebug` 229 | /// - `{:#?}` - "pretty" `uDebug` 230 | /// 231 | /// Named parameters and "specified" positional parameters (`{0}`) are not supported. 232 | /// 233 | /// `{{` and `}}` can be used to escape braces. 234 | pub use ufmt_macros::uwrite; 235 | 236 | /// Write formatted data into a buffer, with a newline appended 237 | /// 238 | /// See [`uwrite!`](macro.uwrite.html) for more details 239 | pub use ufmt_macros::uwriteln; 240 | 241 | pub use crate::helpers::{DebugList, DebugMap, DebugStruct, DebugTuple}; 242 | 243 | mod helpers; 244 | mod impls; 245 | /// Derive macros 246 | pub mod derive { 247 | pub use ufmt_macros::uDebug; 248 | } 249 | 250 | /// Just like `core::fmt::Debug` 251 | #[allow(non_camel_case_types)] 252 | pub trait uDebug { 253 | /// Formats the value using the given formatter 254 | fn fmt(&self, _: &mut Formatter<'_, W>) -> Result<(), W::Error> 255 | where 256 | W: uWrite + ?Sized; 257 | } 258 | 259 | /// Just like `core::fmt::Display` 260 | #[allow(non_camel_case_types)] 261 | pub trait uDisplay { 262 | /// Formats the value using the given formatter 263 | fn fmt(&self, _: &mut Formatter<'_, W>) -> Result<(), W::Error> 264 | where 265 | W: uWrite + ?Sized; 266 | } 267 | 268 | /// HEADS UP this is currently an implementation detail and not subject to semver guarantees. 269 | /// do NOT use this outside the `ufmt` crate 270 | // options for formatting hexadecimal numbers 271 | #[doc(hidden)] 272 | pub struct HexOptions { 273 | /// when we need to use digits a-f, should they be upper case instead? 274 | pub upper_case: bool, 275 | /// when we are padding to a target length, what character should we pad using? 276 | pub pad_char: u8, 277 | /// when we are padding to a target length, how long should our string be? 278 | pub pad_length: usize, 279 | /// should we include a 0x prefix? (also controlled by upper_case) 280 | pub ox_prefix: bool, 281 | } 282 | 283 | impl HexOptions { 284 | /// applies the various padding/prefix options while writing the `payload` string 285 | pub fn with_stuff( 286 | &self, 287 | fmt: &mut Formatter<'_, W>, 288 | payload: &str, 289 | ) -> Result<(), ::Error> { 290 | let pad_before = self.ox_prefix && self.pad_char == b' '; 291 | 292 | let pad = self.pad_length as isize 293 | - (if self.ox_prefix { 2 } else { 0 } + payload.len()) as isize; 294 | 295 | let do_pad = |fmt: &mut Formatter<'_, W>, pad: isize| -> Result<(), ::Error> { 296 | if pad > 0 { 297 | for _ in 0..pad { 298 | // miri considers the `write_char` defined in `ufmt-write` v0.1.0 unsound 299 | // to workaround the issue we use `write_str` instead of `write_char` 300 | fmt.write_str(unsafe { str::from_utf8_unchecked(&[self.pad_char]) })?; 301 | } 302 | } 303 | Ok(()) 304 | }; 305 | 306 | let do_prefix = |fmt: &mut Formatter<'_, W>, 307 | go: bool, 308 | upper_case: bool| 309 | -> Result<(), ::Error> { 310 | if go { 311 | fmt.write_str(if upper_case { "0X" } else { "0x" }) 312 | } else { 313 | Ok(()) 314 | } 315 | }; 316 | if pad_before { 317 | do_pad(fmt, pad)?; 318 | do_prefix(fmt, self.ox_prefix, self.upper_case)?; 319 | } else { 320 | do_prefix(fmt, self.ox_prefix, self.upper_case)?; 321 | do_pad(fmt, pad)?; 322 | } 323 | 324 | fmt.write_str(payload) 325 | } 326 | } 327 | 328 | /// HEADS UP this is currently an implementation detail and not subject to semver guarantees. 329 | /// do NOT use this outside the `ufmt` crate 330 | // just like std::fmt::LowerHex 331 | #[doc(hidden)] 332 | #[allow(non_camel_case_types)] 333 | pub trait uDisplayHex { 334 | /// Formats the value using the given formatter 335 | fn fmt_hex(&self, _: &mut Formatter<'_, W>, options: HexOptions) -> Result<(), W::Error> 336 | where 337 | W: uWrite + ?Sized; 338 | } 339 | 340 | /// Configuration for formatting 341 | #[allow(non_camel_case_types)] 342 | pub struct Formatter<'w, W> 343 | where 344 | W: uWrite + ?Sized, 345 | { 346 | indentation: u8, 347 | pretty: bool, 348 | writer: &'w mut W, 349 | } 350 | 351 | impl<'w, W> Formatter<'w, W> 352 | where 353 | W: uWrite + ?Sized, 354 | { 355 | /// Creates a formatter from the given writer 356 | pub fn new(writer: &'w mut W) -> Self { 357 | Self { 358 | indentation: 0, 359 | pretty: false, 360 | writer, 361 | } 362 | } 363 | 364 | /// Execute the closure with pretty-printing enabled 365 | pub fn pretty( 366 | &mut self, 367 | f: impl FnOnce(&mut Self) -> Result<(), W::Error>, 368 | ) -> Result<(), W::Error> { 369 | let pretty = self.pretty; 370 | self.pretty = true; 371 | f(self)?; 372 | self.pretty = pretty; 373 | Ok(()) 374 | } 375 | 376 | /// Writes a character to the underlying buffer contained within this formatter. 377 | pub fn write_char(&mut self, c: char) -> Result<(), W::Error> { 378 | self.writer.write_char(c) 379 | } 380 | 381 | /// Writes a string slice to the underlying buffer contained within this formatter. 382 | pub fn write_str(&mut self, s: &str) -> Result<(), W::Error> { 383 | self.writer.write_str(s) 384 | } 385 | 386 | /// Write whitespace according to the current `self.indentation` 387 | fn indent(&mut self) -> Result<(), W::Error> { 388 | for _ in 0..self.indentation { 389 | self.write_str(" ")?; 390 | } 391 | 392 | Ok(()) 393 | } 394 | } 395 | 396 | // Implementation detail of the `uwrite*!` macros 397 | #[doc(hidden)] 398 | pub trait UnstableDoAsFormatter { 399 | type Writer: uWrite + ?Sized; 400 | 401 | fn do_as_formatter( 402 | &mut self, 403 | f: impl FnOnce(&mut Formatter<'_, Self::Writer>) -> Result<(), ::Error>, 404 | ) -> Result<(), ::Error>; 405 | } 406 | 407 | impl UnstableDoAsFormatter for W 408 | where 409 | W: uWrite + ?Sized, 410 | { 411 | type Writer = W; 412 | 413 | fn do_as_formatter( 414 | &mut self, 415 | f: impl FnOnce(&mut Formatter<'_, W>) -> Result<(), W::Error>, 416 | ) -> Result<(), W::Error> { 417 | f(&mut Formatter::new(self)) 418 | } 419 | } 420 | 421 | impl UnstableDoAsFormatter for Formatter<'_, W> 422 | where 423 | W: uWrite + ?Sized, 424 | { 425 | type Writer = W; 426 | 427 | fn do_as_formatter( 428 | &mut self, 429 | f: impl FnOnce(&mut Formatter<'_, W>) -> Result<(), W::Error>, 430 | ) -> Result<(), W::Error> { 431 | f(self) 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `μfmt` macros 2 | 3 | #![deny(warnings)] 4 | 5 | extern crate proc_macro; 6 | 7 | use core::mem; 8 | use proc_macro::TokenStream; 9 | use std::borrow::Cow; 10 | use std::cmp::Ordering; 11 | 12 | use proc_macro2::{Literal, Span}; 13 | use quote::quote; 14 | use syn::{ 15 | parse::{self, Parse, ParseStream}, 16 | parse_macro_input, parse_quote, 17 | punctuated::Punctuated, 18 | spanned::Spanned, 19 | Data, DeriveInput, Expr, Fields, GenericParam, Ident, LitStr, Token, 20 | }; 21 | 22 | /// Automatically derive the `uDebug` trait for a `struct` or `enum` 23 | /// 24 | /// Supported items 25 | /// 26 | /// - all kind of `struct`-s 27 | /// - all kind of `enum`-s 28 | /// 29 | /// `union`-s are not supported 30 | #[proc_macro_derive(uDebug)] 31 | pub fn debug(input: TokenStream) -> TokenStream { 32 | let input = parse_macro_input!(input as DeriveInput); 33 | 34 | let mut generics = input.generics; 35 | 36 | for param in &mut generics.params { 37 | if let GenericParam::Type(type_param) = param { 38 | type_param.bounds.push(parse_quote!(ufmt::uDebug)); 39 | } 40 | } 41 | 42 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 43 | 44 | let ident = &input.ident; 45 | let ts = match input.data { 46 | Data::Struct(data) => { 47 | let ident_s = ident.to_string(); 48 | 49 | let body = match data.fields { 50 | Fields::Named(fields) => { 51 | let fields = fields 52 | .named 53 | .iter() 54 | .map(|field| { 55 | let ident = field.ident.as_ref().expect("UNREACHABLE"); 56 | let name = ident.to_string(); 57 | 58 | quote!(field(#name, &self.#ident)?) 59 | }) 60 | .collect::>(); 61 | 62 | quote!(f.debug_struct(#ident_s)?#(.#fields)*.finish()) 63 | } 64 | 65 | Fields::Unnamed(fields) => { 66 | let fields = (0..fields.unnamed.len()) 67 | .map(|i| { 68 | let i = Literal::u64_unsuffixed(i as u64); 69 | 70 | quote!(field(&self.#i)?) 71 | }) 72 | .collect::>(); 73 | 74 | quote!(f.debug_tuple(#ident_s)?#(.#fields)*.finish()) 75 | } 76 | 77 | Fields::Unit => quote!(f.write_str(#ident_s)), 78 | }; 79 | 80 | quote!( 81 | impl #impl_generics ufmt::uDebug for #ident #ty_generics #where_clause { 82 | fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> core::result::Result<(), W::Error> 83 | where 84 | W: ufmt::uWrite + ?Sized, 85 | { 86 | #body 87 | } 88 | } 89 | 90 | ) 91 | } 92 | 93 | Data::Enum(data) => { 94 | let arms = data 95 | .variants 96 | .iter() 97 | .map(|var| { 98 | let variant = &var.ident; 99 | let variant_s = variant.to_string(); 100 | 101 | match &var.fields { 102 | Fields::Named(fields) => { 103 | let mut pats = Vec::with_capacity(fields.named.len()); 104 | let mut methods = Vec::with_capacity(fields.named.len()); 105 | for field in &fields.named { 106 | let ident = field.ident.as_ref().unwrap(); 107 | let ident_s = ident.to_string(); 108 | 109 | pats.push(quote!(#ident)); 110 | methods.push(quote!(field(#ident_s, #ident)?)); 111 | } 112 | 113 | quote!( 114 | #ident::#variant { #(#pats),* } => { 115 | f.debug_struct(#variant_s)?#(.#methods)*.finish() 116 | } 117 | ) 118 | } 119 | 120 | Fields::Unnamed(fields) => { 121 | let pats = &(0..fields.unnamed.len()) 122 | .map(|i| Ident::new(&format!("_{}", i), Span::call_site())) 123 | .collect::>(); 124 | 125 | quote!( 126 | #ident::#variant(#(#pats),*) => { 127 | f.debug_tuple(#variant_s)?#(.field(#pats)?)*.finish() 128 | } 129 | ) 130 | } 131 | 132 | Fields::Unit => quote!( 133 | #ident::#variant => { 134 | f.write_str(#variant_s) 135 | } 136 | ), 137 | } 138 | }) 139 | .collect::>(); 140 | 141 | let body = if arms.is_empty() { 142 | // Debug's implementation uses `::core::intrinsics::unreachable()` 143 | quote!(unsafe { core::unreachable!() }) 144 | } else { 145 | quote!( 146 | match self { 147 | #(#arms),* 148 | } 149 | ) 150 | }; 151 | 152 | quote!( 153 | impl #impl_generics ufmt::uDebug for #ident #ty_generics #where_clause { 154 | fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> core::result::Result<(), W::Error> 155 | where 156 | W: ufmt::uWrite + ?Sized, 157 | { 158 | #body 159 | } 160 | } 161 | ) 162 | } 163 | 164 | Data::Union(..) => { 165 | return parse::Error::new(Span::call_site(), "this trait cannot be derived for unions") 166 | .to_compile_error() 167 | .into(); 168 | } 169 | }; 170 | 171 | ts.into() 172 | } 173 | 174 | #[proc_macro] 175 | pub fn uwrite(input: TokenStream) -> TokenStream { 176 | write(input, false) 177 | } 178 | 179 | #[proc_macro] 180 | pub fn uwriteln(input: TokenStream) -> TokenStream { 181 | write(input, true) 182 | } 183 | 184 | fn write(input: TokenStream, newline: bool) -> TokenStream { 185 | let input = parse_macro_input!(input as Input); 186 | 187 | let formatter = &input.formatter; 188 | let literal = input.literal; 189 | 190 | let mut format = literal.value(); 191 | if newline { 192 | format.push('\n'); 193 | } 194 | let pieces = match parse(&format, literal.span()) { 195 | Err(e) => return e.to_compile_error().into(), 196 | Ok(pieces) => pieces, 197 | }; 198 | 199 | let required_args = pieces.iter().filter(|piece| !piece.is_str()).count(); 200 | let supplied_args = input.args.len(); 201 | match supplied_args.cmp(&required_args) { 202 | Ordering::Less => { 203 | return parse::Error::new( 204 | literal.span(), 205 | &format!( 206 | "format string requires {} arguments but {} {} supplied", 207 | required_args, 208 | supplied_args, 209 | if supplied_args == 1 { "was" } else { "were" } 210 | ), 211 | ) 212 | .to_compile_error() 213 | .into(); 214 | } 215 | Ordering::Greater => { 216 | return parse::Error::new( 217 | input.args[required_args].span(), 218 | "argument never used".to_string(), 219 | ) 220 | .to_compile_error() 221 | .into(); 222 | } 223 | Ordering::Equal => {} 224 | } 225 | 226 | let mut args = vec![]; 227 | let mut pats = vec![]; 228 | let mut exprs = vec![]; 229 | let mut i = 0; 230 | for piece in pieces { 231 | if let Piece::Str(s) = piece { 232 | exprs.push(quote!(f.write_str(#s)?;)) 233 | } else { 234 | let pat = mk_ident(i); 235 | let arg = &input.args[i]; 236 | i += 1; 237 | 238 | args.push(quote!(&(#arg))); 239 | pats.push(quote!(#pat)); 240 | 241 | match piece { 242 | Piece::Display => { 243 | exprs.push(quote!(ufmt::uDisplay::fmt(#pat, f)?;)); 244 | } 245 | 246 | Piece::Debug { pretty } => { 247 | exprs.push(if pretty { 248 | quote!(f.pretty(|f| ufmt::uDebug::fmt(#pat, f))?;) 249 | } else { 250 | quote!(ufmt::uDebug::fmt(#pat, f)?;) 251 | }); 252 | } 253 | Piece::Hex { 254 | upper_case, 255 | pad_char, 256 | pad_length, 257 | prefix, 258 | } => { 259 | exprs.push(quote!(ufmt::uDisplayHex::fmt_hex(#pat, f, ufmt::HexOptions{ 260 | upper_case:#upper_case, 261 | pad_char: #pad_char, 262 | pad_length: #pad_length, 263 | ox_prefix: #prefix})?;)); 264 | } 265 | Piece::Str(_) => unreachable!(), 266 | } 267 | } 268 | } 269 | 270 | quote!(match (#(#args),*) { 271 | (#(#pats),*) => { 272 | use ufmt::UnstableDoAsFormatter as _; 273 | 274 | (#formatter).do_as_formatter(|f| { 275 | #(#exprs)* 276 | core::result::Result::Ok(()) 277 | }) 278 | } 279 | }) 280 | .into() 281 | } 282 | 283 | struct Input { 284 | formatter: Expr, 285 | _comma: Token![,], 286 | literal: LitStr, 287 | _comma2: Option, 288 | args: Punctuated, 289 | } 290 | 291 | impl Parse for Input { 292 | fn parse(input: ParseStream) -> parse::Result { 293 | let formatter = input.parse()?; 294 | let _comma = input.parse()?; 295 | let literal = input.parse()?; 296 | 297 | if input.is_empty() { 298 | Ok(Input { 299 | formatter, 300 | _comma, 301 | literal, 302 | _comma2: None, 303 | args: Punctuated::new(), 304 | }) 305 | } else { 306 | Ok(Input { 307 | formatter, 308 | _comma, 309 | literal, 310 | _comma2: input.parse()?, 311 | args: Punctuated::parse_terminated(input)?, 312 | }) 313 | } 314 | } 315 | } 316 | 317 | #[derive(Debug, PartialEq)] 318 | enum Piece<'a> { 319 | Debug { 320 | pretty: bool, 321 | }, 322 | Display, 323 | Str(Cow<'a, str>), 324 | Hex { 325 | upper_case: bool, 326 | pad_char: u8, 327 | pad_length: usize, 328 | prefix: bool, 329 | }, 330 | } 331 | 332 | impl Piece<'_> { 333 | fn is_str(&self) -> bool { 334 | matches!(self, Piece::Str(_)) 335 | } 336 | } 337 | 338 | fn mk_ident(i: usize) -> Ident { 339 | Ident::new(&format!("__{}", i), Span::call_site()) 340 | } 341 | 342 | // `}}` -> `}` 343 | fn unescape(mut literal: &str, span: Span) -> parse::Result> { 344 | if literal.contains('}') { 345 | let mut buf = String::new(); 346 | 347 | while literal.contains('}') { 348 | const ERR: &str = "format string contains an unmatched right brace"; 349 | let mut parts = literal.splitn(2, '}'); 350 | 351 | match (parts.next(), parts.next()) { 352 | (Some(left), Some(right)) => { 353 | const ESCAPED_BRACE: &str = "}"; 354 | 355 | if let Some(tail) = right.strip_prefix(ESCAPED_BRACE) { 356 | buf.push_str(left); 357 | buf.push('}'); 358 | 359 | literal = tail; 360 | } else { 361 | return Err(parse::Error::new(span, ERR)); 362 | } 363 | } 364 | 365 | _ => unreachable!(), 366 | } 367 | } 368 | 369 | buf.push_str(literal); 370 | 371 | Ok(buf.into()) 372 | } else { 373 | Ok(Cow::Borrowed(literal)) 374 | } 375 | } 376 | 377 | fn parse(mut literal: &str, span: Span) -> parse::Result> { 378 | let mut pieces = vec![]; 379 | 380 | let mut buf = String::new(); 381 | loop { 382 | let mut parts = literal.splitn(2, '{'); 383 | match (parts.next(), parts.next()) { 384 | // empty string literal 385 | (None, None) => break, 386 | 387 | // end of the string literal 388 | (Some(s), None) => { 389 | if buf.is_empty() { 390 | if !s.is_empty() { 391 | pieces.push(Piece::Str(unescape(s, span)?)); 392 | } 393 | } else { 394 | buf.push_str(&unescape(s, span)?); 395 | 396 | pieces.push(Piece::Str(Cow::Owned(buf))); 397 | } 398 | 399 | break; 400 | } 401 | 402 | (head, Some(tail)) => { 403 | const DEBUG: &str = ":?}"; 404 | const DEBUG_PRETTY: &str = ":#?}"; 405 | const DISPLAY: &str = "}"; 406 | const ESCAPED_BRACE: &str = "{"; 407 | 408 | let head = head.unwrap_or(""); 409 | if tail.starts_with(DEBUG) 410 | || tail.starts_with(DEBUG_PRETTY) 411 | || tail.starts_with(DISPLAY) 412 | || tail.starts_with(':') 413 | { 414 | if buf.is_empty() { 415 | if !head.is_empty() { 416 | pieces.push(Piece::Str(unescape(head, span)?)); 417 | } 418 | } else { 419 | buf.push_str(&unescape(head, span)?); 420 | 421 | pieces.push(Piece::Str(Cow::Owned(mem::take(&mut buf)))); 422 | } 423 | 424 | if let Some(tail_tail) = tail.strip_prefix(DEBUG) { 425 | pieces.push(Piece::Debug { pretty: false }); 426 | 427 | literal = tail_tail; 428 | } else if let Some(tail_tail) = tail.strip_prefix(DEBUG_PRETTY) { 429 | pieces.push(Piece::Debug { pretty: true }); 430 | 431 | literal = tail_tail; 432 | } else if let Some(tail2) = tail.strip_prefix(':') { 433 | let (piece, remainder) = parse_colon(tail2, span)?; 434 | pieces.push(piece); 435 | literal = remainder; 436 | } else { 437 | pieces.push(Piece::Display); 438 | 439 | literal = &tail[DISPLAY.len()..]; 440 | } 441 | } else if let Some(tail_tail) = tail.strip_prefix(ESCAPED_BRACE) { 442 | buf.push_str(&unescape(head, span)?); 443 | buf.push('{'); 444 | 445 | literal = tail_tail; 446 | } else { 447 | return Err(parse::Error::new( 448 | span, 449 | "invalid format string: expected `{{`, `{}`, `{:?}` or `{:#?}`", 450 | )); 451 | } 452 | } 453 | } 454 | } 455 | 456 | Ok(pieces) 457 | } 458 | 459 | /// given a string src that begins with a text decimal number, return the tail (characters after the number) and the value of the decimal number 460 | fn split_number(src: &str) -> (&str, usize) { 461 | let mut rval = 0; 462 | let mut cursor = 0; 463 | 464 | let chars = src.chars(); 465 | for (i, ch) in chars.enumerate() { 466 | match ch.to_digit(10) { 467 | Some(val) => { 468 | rval = rval * 10 + val as usize; 469 | cursor = i + 1; 470 | } 471 | None => break, 472 | } 473 | } 474 | 475 | (&src[cursor..], rval) 476 | } 477 | 478 | /// parses the stuff after a `{:` into a [Piece] and the trailing `&str` (what comes after the `}`) 479 | fn parse_colon(format: &str, span: Span) -> parse::Result<(Piece, &str)> { 480 | let (format, prefix) = if let Some(tail) = format.strip_prefix('#') { 481 | (tail, true) 482 | } else { 483 | (format, false) 484 | }; 485 | let (format, pad_char) = if let Some(tail) = format.strip_prefix('0') { 486 | (tail, b'0') 487 | } else { 488 | (format, b' ') 489 | }; 490 | let (format, pad_length) = if !format.is_empty() 491 | && if let Some(ch) = format.chars().next() { 492 | ch.is_ascii_digit() 493 | } else { 494 | false 495 | } { 496 | split_number(format) 497 | } else { 498 | (format, 0) 499 | }; 500 | if let Some(tail) = format.strip_prefix("x}") { 501 | Ok(( 502 | Piece::Hex { 503 | upper_case: false, 504 | pad_char, 505 | pad_length, 506 | prefix, 507 | }, 508 | tail, 509 | )) 510 | } else if let Some(tail) = format.strip_prefix("X}") { 511 | Ok(( 512 | Piece::Hex { 513 | upper_case: true, 514 | pad_char, 515 | pad_length, 516 | prefix, 517 | }, 518 | tail, 519 | )) 520 | } else { 521 | Err(parse::Error::new( 522 | span, 523 | "invalid format string: expected `{{`, `{}`, `{:?}`, `{:#?}` or '{:x}'", 524 | )) 525 | } 526 | } 527 | 528 | #[cfg(test)] 529 | mod tests { 530 | use std::borrow::Cow; 531 | 532 | use proc_macro2::Span; 533 | 534 | use crate::Piece; 535 | 536 | #[test] 537 | fn pieces() { 538 | let span = Span::call_site(); 539 | 540 | // string interpolation 541 | assert_eq!( 542 | super::parse("The answer is {}", span).ok(), 543 | Some(vec![ 544 | Piece::Str(Cow::Borrowed("The answer is ")), 545 | Piece::Display 546 | ]), 547 | ); 548 | 549 | assert_eq!( 550 | super::parse("{:?}", span).ok(), 551 | Some(vec![Piece::Debug { pretty: false }]), 552 | ); 553 | 554 | assert_eq!( 555 | super::parse("{:#?}", span).ok(), 556 | Some(vec![Piece::Debug { pretty: true }]), 557 | ); 558 | 559 | assert_eq!( 560 | super::parse("{:x}", span).ok(), 561 | Some(vec![Piece::Hex { 562 | upper_case: false, 563 | pad_char: b' ', 564 | pad_length: 0, 565 | prefix: false 566 | }]), 567 | ); 568 | 569 | assert_eq!( 570 | super::parse("{:9x}", span).ok(), 571 | Some(vec![Piece::Hex { 572 | upper_case: false, 573 | pad_char: b' ', 574 | pad_length: 9, 575 | prefix: false 576 | }]), 577 | ); 578 | 579 | assert_eq!( 580 | super::parse("{:9X}", span).ok(), 581 | Some(vec![Piece::Hex { 582 | upper_case: true, 583 | pad_char: b' ', 584 | pad_length: 9, 585 | prefix: false 586 | }]), 587 | ); 588 | 589 | assert_eq!( 590 | super::parse("{:#X}", span).ok(), 591 | Some(vec![Piece::Hex { 592 | upper_case: true, 593 | pad_char: b' ', 594 | pad_length: 0, 595 | prefix: true 596 | }]), 597 | ); 598 | 599 | // escaped braces 600 | assert_eq!( 601 | super::parse("{{}} is not an argument", span).ok(), 602 | Some(vec![Piece::Str(Cow::Borrowed("{} is not an argument"))]), 603 | ); 604 | 605 | // left brace & junk 606 | assert!(super::parse("{", span).is_err()); 607 | assert!(super::parse(" {", span).is_err()); 608 | assert!(super::parse("{ ", span).is_err()); 609 | assert!(super::parse("{ {", span).is_err()); 610 | assert!(super::parse("{:q}", span).is_err()); 611 | } 612 | 613 | #[test] 614 | fn unescape() { 615 | let span = Span::call_site(); 616 | 617 | // no right brace 618 | assert_eq!(super::unescape("", span).ok(), Some(Cow::Borrowed(""))); 619 | assert_eq!( 620 | super::unescape("Hello", span).ok(), 621 | Some(Cow::Borrowed("Hello")) 622 | ); 623 | 624 | // unmatched right brace 625 | assert!(super::unescape(" }", span).is_err()); 626 | assert!(super::unescape("} ", span).is_err()); 627 | assert!(super::unescape("}", span).is_err()); 628 | 629 | // escaped right brace 630 | assert_eq!(super::unescape("}}", span).ok(), Some(Cow::Borrowed("}"))); 631 | assert_eq!(super::unescape("}} ", span).ok(), Some(Cow::Borrowed("} "))); 632 | } 633 | 634 | #[test] 635 | fn split_number() { 636 | let (a, b) = crate::split_number("42 card pickup"); 637 | assert_eq!(" card pickup", a); 638 | assert_eq!(42, b); 639 | } 640 | } 641 | --------------------------------------------------------------------------------