Skip to main content

printf_compat/
lib.rs

1//! `printf` reimplemented in Rust
2//!
3//! This is a complete reimplementation of `printf` in Rust. Example use cases:
4//!
5//! - [Many C][sigrok-log] [libraries][libusb-log] provide a way to provide a
6//!   custom log callback. With this crate, you can provide a pure Rust option,
7//!   and do whatever you want with it. Log it to the console, store it in a
8//!   string, or do anything else.
9//! - If you're writing a Rust-first program for a microcontroller and need to
10//!   interface with a C library, you might not *have* a libc and have to
11//!   reimplement it yourself. If it uses `printf`, use this crate to easily add
12//!   your own output. [`core::fmt`] too big? No problem! Write your own
13//!   formatting code, or use a minimal formatting library like [`ufmt`] or
14//!   [`defmt`]. Don't need *every* single option given by `printf` format
15//!   strings? No problem! Just don't implement it.
16//!
17//! # Benefits
18//!
19//! ## ⚒ Modular
20//!
21//! printf-compat lets you pick how you want to output a message. Use
22//! pre-written adapters for [`fmt::Write`][output::fmt_write] (like a
23//! [`String`]) or [`io::Write`][output::io_write] (like
24//! [`io::stdout()`][std::io::stdout]), or implement your own.
25//!
26//! ## 🔬 Small
27//!
28//! This crate is `no_std` compatible (with `default-features = false`).
29//! The main machinery doesn't require the use of [`core::fmt`], and it can't panic.
30//!
31//! ## 🔒 Safe (as can be)
32//!
33//! Of course, `printf` is *completely* unsafe, as it requires the use of
34//! `va_list`. However, outside of that, all of the actual string parsing is
35//! written in completely safe Rust. No buffer overflow attacks!
36//!
37//! The `n` format specifier, which writes to a user-provided pointer, is
38//! considered a serious security vulnerability if a user-provided string is
39//! ever passed to `printf`. It *is* supported by this crate; however, it
40//! doesn't do anything by default, and you'll have to explicitly do the writing
41//! yourself.
42//!
43//! ## 🧹 Tested
44//!
45//! A wide [test suite] is used to ensure that many different possibilities are
46//! identical to glibc's `printf`. [Differences are
47//! documented][output::fmt_write#differences].
48//!
49//! # Getting Started
50//!
51//! Start by adding your function signature:
52//!
53//! ```rust
54//! use core::ffi::{c_char, c_int};
55//!
56//! #[unsafe(no_mangle)]
57//! unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int {
58//!     todo!()
59//! }
60//! ```
61//!
62//! Think about what you're doing:
63//!
64//! - If you're implementing `printf` *because you don't have one*, you'll want to
65//!   call it `printf` and add `#[unsafe(no_mangle)]`.
66//! - Likewise, if you're creating a custom log function for a C library and it
67//!   expects to call a globally-defined function, keep `#[unsafe(no_mangle)]` and
68//!   rename the function to what it expects.
69//! - On the other hand, if your C library expects you to call a function to
70//!   register a callback ([example 1][sigrok-log], [example 2][libusb-log]),
71//!   remove `#[unsafe(no_mangle)]`.
72//!
73//! Now, add your logic:
74//!
75//! ```rust
76//! # use core::ffi::{c_char, c_int};
77//! # #[unsafe(no_mangle)]
78//! # unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int {
79//! use printf_compat::{format, output};
80//! let mut s = String::new();
81//! let bytes_written = format(str, args, output::fmt_write(&mut s));
82//! println!("{}", s);
83//! bytes_written
84//! # }
85//! ```
86//!
87//! Of course, replace [`output::fmt_write`] with whatever you like—some are
88//! provided for you in [`output`]. If you'd like to write your own, follow
89//! their function signature: you need to provide a function to [`format()`]
90//! that takes an [`Argument`] and returns the number of bytes written (although
91//! you don't *need* to if your C library doesn't use it) or -1 if there was an
92//! error.
93//!
94//! [sigrok-log]: https://sigrok.org/api/libsigrok/unstable/a00063.html#ga4240b8fe79be72ef758f40f9acbd4316
95//! [libusb-log]: http://libusb.sourceforge.net/api-1.0/group__libusb__lib.html#ga2efb66b8f16ffb0851f3907794c06e20
96//! [test suite]: https://github.com/lights0123/printf-compat/blob/master/tests/tests.rs
97//! [`ufmt`]: https://docs.rs/ufmt/
98//! [`defmt`]: https://defmt.ferrous-systems.com/
99
100#![cfg_attr(not(any(test, feature = "std")), no_std)]
101
102use core::{ffi::*, fmt};
103
104pub mod output;
105mod parser;
106use argument::*;
107pub use parser::format;
108pub mod argument {
109    use super::*;
110
111    bitflags::bitflags! {
112        /// Flags field.
113        ///
114        /// Definitions from
115        /// [Wikipedia](https://en.wikipedia.org/wiki/Printf_format_string#Flags_field).
116        #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
117        pub struct Flags: u8 {
118            /// Left-align the output of this placeholder. (The default is to
119            /// right-align the output.)
120            const LEFT_ALIGN = 0b00000001;
121            /// Prepends a plus for positive signed-numeric types. positive =
122            /// `+`, negative = `-`.
123            ///
124            /// (The default doesn't prepend anything in front of positive
125            /// numbers.)
126            const PREPEND_PLUS = 0b00000010;
127            /// Prepends a space for positive signed-numeric types. positive = `
128            /// `, negative = `-`. This flag is ignored if the
129            /// [`PREPEND_PLUS`][Flags::PREPEND_PLUS] flag exists.
130            ///
131            /// (The default doesn't prepend anything in front of positive
132            /// numbers.)
133            const PREPEND_SPACE = 0b00000100;
134            /// When the 'width' option is specified, prepends zeros for numeric
135            /// types. (The default prepends spaces.)
136            ///
137            /// For example, `printf("%4X",3)` produces `   3`, while
138            /// `printf("%04X",3)` produces `0003`.
139            const PREPEND_ZERO = 0b00001000;
140            /// The integer or exponent of a decimal has the thousands grouping
141            /// separator applied.
142            const THOUSANDS_GROUPING = 0b00010000;
143            /// Alternate form:
144            ///
145            /// For `g` and `G` types, trailing zeros are not removed. \
146            /// For `f`, `F`, `e`, `E`, `g`, `G` types, the output always
147            /// contains a decimal point. \ For `o`, `x`, `X` types,
148            /// the text `0`, `0x`, `0X`, respectively, is prepended
149            /// to non-zero numbers.
150            const ALTERNATE_FORM = 0b00100000;
151        }
152    }
153
154    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
155    pub enum DoubleFormat {
156        /// `f`
157        Normal,
158        /// `F`
159        UpperNormal,
160        /// `e`
161        Scientific,
162        /// `E`
163        UpperScientific,
164        /// `g`
165        Auto,
166        /// `G`
167        UpperAuto,
168        /// `a`
169        Hex,
170        /// `A`
171        UpperHex,
172    }
173
174    impl DoubleFormat {
175        /// If the format is uppercase.
176        pub fn is_upper(self) -> bool {
177            use DoubleFormat::*;
178            matches!(self, UpperNormal | UpperScientific | UpperAuto | UpperHex)
179        }
180
181        pub fn set_upper(self, upper: bool) -> Self {
182            use DoubleFormat::*;
183            match self {
184                Normal | UpperNormal => {
185                    if upper {
186                        UpperNormal
187                    } else {
188                        Normal
189                    }
190                }
191                Scientific | UpperScientific => {
192                    if upper {
193                        UpperScientific
194                    } else {
195                        Scientific
196                    }
197                }
198                Auto | UpperAuto => {
199                    if upper {
200                        UpperAuto
201                    } else {
202                        Auto
203                    }
204                }
205                Hex | UpperHex => {
206                    if upper {
207                        UpperHex
208                    } else {
209                        Hex
210                    }
211                }
212            }
213        }
214    }
215
216    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
217    #[non_exhaustive]
218    pub enum SignedInt {
219        Int(c_int),
220        Char(c_schar),
221        Short(c_short),
222        Long(c_long),
223        LongLong(c_longlong),
224        Isize(isize),
225    }
226
227    impl From<SignedInt> for i64 {
228        fn from(num: SignedInt) -> Self {
229            // Some casts are only needed on some platforms.
230            #[allow(clippy::unnecessary_cast)]
231            match num {
232                SignedInt::Int(x) => x as i64,
233                SignedInt::Char(x) => x as i64,
234                SignedInt::Short(x) => x as i64,
235                SignedInt::Long(x) => x as i64,
236                SignedInt::LongLong(x) => x as i64,
237                SignedInt::Isize(x) => x as i64,
238            }
239        }
240    }
241
242    impl SignedInt {
243        pub fn is_sign_negative(self) -> bool {
244            match self {
245                SignedInt::Int(x) => x < 0,
246                SignedInt::Char(x) => x < 0,
247                SignedInt::Short(x) => x < 0,
248                SignedInt::Long(x) => x < 0,
249                SignedInt::LongLong(x) => x < 0,
250                SignedInt::Isize(x) => x < 0,
251            }
252        }
253    }
254
255    impl fmt::Display for SignedInt {
256        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257            match self {
258                SignedInt::Int(x) => fmt::Display::fmt(x, f),
259                SignedInt::Char(x) => fmt::Display::fmt(x, f),
260                SignedInt::Short(x) => fmt::Display::fmt(x, f),
261                SignedInt::Long(x) => fmt::Display::fmt(x, f),
262                SignedInt::LongLong(x) => fmt::Display::fmt(x, f),
263                SignedInt::Isize(x) => fmt::Display::fmt(x, f),
264            }
265        }
266    }
267
268    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
269    #[non_exhaustive]
270    pub enum UnsignedInt {
271        Int(c_uint),
272        Char(c_uchar),
273        Short(c_ushort),
274        Long(c_ulong),
275        LongLong(c_ulonglong),
276        Isize(usize),
277    }
278
279    impl From<UnsignedInt> for u64 {
280        fn from(num: UnsignedInt) -> Self {
281            // Some casts are only needed on some platforms.
282            #[allow(clippy::unnecessary_cast)]
283            match num {
284                UnsignedInt::Int(x) => x as u64,
285                UnsignedInt::Char(x) => x as u64,
286                UnsignedInt::Short(x) => x as u64,
287                UnsignedInt::Long(x) => x as u64,
288                UnsignedInt::LongLong(x) => x as u64,
289                UnsignedInt::Isize(x) => x as u64,
290            }
291        }
292    }
293
294    impl fmt::Display for UnsignedInt {
295        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296            match self {
297                UnsignedInt::Int(x) => fmt::Display::fmt(x, f),
298                UnsignedInt::Char(x) => fmt::Display::fmt(x, f),
299                UnsignedInt::Short(x) => fmt::Display::fmt(x, f),
300                UnsignedInt::Long(x) => fmt::Display::fmt(x, f),
301                UnsignedInt::LongLong(x) => fmt::Display::fmt(x, f),
302                UnsignedInt::Isize(x) => fmt::Display::fmt(x, f),
303            }
304        }
305    }
306
307    impl fmt::LowerHex for UnsignedInt {
308        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309            match self {
310                UnsignedInt::Int(x) => fmt::LowerHex::fmt(x, f),
311                UnsignedInt::Char(x) => fmt::LowerHex::fmt(x, f),
312                UnsignedInt::Short(x) => fmt::LowerHex::fmt(x, f),
313                UnsignedInt::Long(x) => fmt::LowerHex::fmt(x, f),
314                UnsignedInt::LongLong(x) => fmt::LowerHex::fmt(x, f),
315                UnsignedInt::Isize(x) => fmt::LowerHex::fmt(x, f),
316            }
317        }
318    }
319
320    impl fmt::UpperHex for UnsignedInt {
321        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322            match self {
323                UnsignedInt::Int(x) => fmt::UpperHex::fmt(x, f),
324                UnsignedInt::Char(x) => fmt::UpperHex::fmt(x, f),
325                UnsignedInt::Short(x) => fmt::UpperHex::fmt(x, f),
326                UnsignedInt::Long(x) => fmt::UpperHex::fmt(x, f),
327                UnsignedInt::LongLong(x) => fmt::UpperHex::fmt(x, f),
328                UnsignedInt::Isize(x) => fmt::UpperHex::fmt(x, f),
329            }
330        }
331    }
332
333    impl fmt::Octal for UnsignedInt {
334        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335            match self {
336                UnsignedInt::Int(x) => fmt::Octal::fmt(x, f),
337                UnsignedInt::Char(x) => fmt::Octal::fmt(x, f),
338                UnsignedInt::Short(x) => fmt::Octal::fmt(x, f),
339                UnsignedInt::Long(x) => fmt::Octal::fmt(x, f),
340                UnsignedInt::LongLong(x) => fmt::Octal::fmt(x, f),
341                UnsignedInt::Isize(x) => fmt::Octal::fmt(x, f),
342            }
343        }
344    }
345
346    /// An argument as passed to [`format()`].
347    #[derive(Debug, Copy, Clone, PartialEq)]
348    pub struct Argument<'a> {
349        pub flags: Flags,
350        pub width: c_int,
351        pub precision: Option<c_int>,
352        pub specifier: Specifier<'a>,
353    }
354
355    impl<'a> From<Specifier<'a>> for Argument<'a> {
356        fn from(specifier: Specifier<'a>) -> Self {
357            Self {
358                flags: Flags::empty(),
359                width: 0,
360                precision: None,
361                specifier,
362            }
363        }
364    }
365
366    /// A [format specifier](https://en.wikipedia.org/wiki/Printf_format_string#Type_field).
367    #[derive(Debug, Copy, Clone, PartialEq)]
368    #[non_exhaustive]
369    pub enum Specifier<'a> {
370        /// `%`
371        Percent,
372        /// `d`, `i`
373        Int(SignedInt),
374        /// `u`
375        Uint(UnsignedInt),
376        /// `o`
377        Octal(UnsignedInt),
378        /// `f`, `F`, `e`, `E`, `g`, `G`, `a`, `A`
379        Double { value: f64, format: DoubleFormat },
380        /// string outside of formatting
381        Bytes(&'a [u8]),
382        /// `s`
383        ///
384        /// The same as [`Bytes`][Specifier::Bytes] but guaranteed to be
385        /// null-terminated. This can be used for optimizations, where if you
386        /// need to null terminate a string to print it, you can skip that step.
387        String(&'a CStr),
388        /// `c`
389        Char(c_char),
390        /// `x`
391        Hex(UnsignedInt),
392        /// `X`
393        UpperHex(UnsignedInt),
394        /// `p`
395        Pointer(*const ()),
396        /// `n`
397        ///
398        /// # Safety
399        ///
400        /// This can be a serious security vulnerability if the format specifier
401        /// of `printf` is allowed to be user-specified. This shouldn't ever
402        /// happen, but poorly-written software may do so.
403        WriteBytesWritten(c_int, *const c_int),
404    }
405}