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}