printf_compat/
output.rs

1//! Various ways to output formatting data.
2
3use core::cell::Cell;
4use core::ffi::*;
5use core::fmt;
6use core::str::from_utf8;
7
8#[cfg(feature = "std")]
9pub use yes_std::*;
10
11use crate::{Argument, DoubleFormat, Flags, Specifier};
12
13struct DummyWriter(usize);
14
15impl fmt::Write for DummyWriter {
16    fn write_str(&mut self, s: &str) -> fmt::Result {
17        self.0 += s.len();
18        Ok(())
19    }
20}
21
22struct WriteCounter<'a, T: fmt::Write>(&'a mut T, usize);
23
24impl<'a, T: fmt::Write> fmt::Write for WriteCounter<'a, T> {
25    fn write_str(&mut self, s: &str) -> fmt::Result {
26        self.1 += s.len();
27        self.0.write_str(s)
28    }
29}
30
31fn write_str(
32    w: &mut impl fmt::Write,
33    flags: Flags,
34    width: c_int,
35    precision: Option<c_int>,
36    b: &[u8],
37) -> fmt::Result {
38    let string = from_utf8(b).map_err(|_| fmt::Error)?;
39    let precision = precision.unwrap_or(string.len() as c_int);
40    if flags.contains(Flags::LEFT_ALIGN) {
41        write!(
42            w,
43            "{:1$.prec$}",
44            string,
45            width as usize,
46            prec = precision as usize
47        )
48    } else {
49        write!(
50            w,
51            "{:>1$.prec$}",
52            string,
53            width as usize,
54            prec = precision as usize
55        )
56    }
57}
58
59macro_rules! define_numeric {
60    ($w: expr, $data: expr, $flags: expr, $width: expr, $precision: expr) => {
61        define_numeric!($w, $data, $flags, $width, $precision, "")
62    };
63    ($w: expr, $data: expr, $flags: expr, $width: expr, $precision: expr, $ty:expr) => {{
64        use fmt::Write;
65        if $flags.contains(Flags::LEFT_ALIGN) {
66            if $flags.contains(Flags::PREPEND_PLUS) {
67                write!(
68                    $w,
69                    concat!("{:<+width$.prec$", $ty, "}"),
70                    $data,
71                    width = $width as usize,
72                    prec = $precision as usize
73                )
74            } else if $flags.contains(Flags::PREPEND_SPACE) && !$data.is_sign_negative() {
75                write!(
76                    $w,
77                    concat!(" {:<width$.prec$", $ty, "}"),
78                    $data,
79                    width = ($width as usize).wrapping_sub(1),
80                    prec = $precision as usize
81                )
82            } else {
83                write!(
84                    $w,
85                    concat!("{:<width$.prec$", $ty, "}"),
86                    $data,
87                    width = $width as usize,
88                    prec = $precision as usize
89                )
90            }
91        } else if $flags.contains(Flags::PREPEND_PLUS) {
92            if $flags.contains(Flags::PREPEND_ZERO) {
93                write!(
94                    $w,
95                    concat!("{:+0width$.prec$", $ty, "}"),
96                    $data,
97                    width = $width as usize,
98                    prec = $precision as usize
99                )
100            } else {
101                write!(
102                    $w,
103                    concat!("{:+width$.prec$", $ty, "}"),
104                    $data,
105                    width = $width as usize,
106                    prec = $precision as usize
107                )
108            }
109        } else if $flags.contains(Flags::PREPEND_ZERO) {
110            if $flags.contains(Flags::PREPEND_SPACE) && !$data.is_sign_negative() {
111                let mut d = DummyWriter(0);
112                let _ = write!(
113                    d,
114                    concat!("{:.prec$", $ty, "}"),
115                    $data,
116                    prec = $precision as usize
117                );
118                if d.0 + 1 > $width as usize {
119                    $width += 1;
120                }
121                write!(
122                    $w,
123                    concat!(" {:0width$.prec$", $ty, "}"),
124                    $data,
125                    width = ($width as usize).wrapping_sub(1),
126                    prec = $precision as usize
127                )
128            } else {
129                write!(
130                    $w,
131                    concat!("{:0width$.prec$", $ty, "}"),
132                    $data,
133                    width = $width as usize,
134                    prec = $precision as usize
135                )
136            }
137        } else {
138            if $flags.contains(Flags::PREPEND_SPACE) && !$data.is_sign_negative() {
139                let mut d = DummyWriter(0);
140                let _ = write!(
141                    d,
142                    concat!("{:.prec$", $ty, "}"),
143                    $data,
144                    prec = $precision as usize
145                );
146                if d.0 + 1 > $width as usize {
147                    $width = d.0 as i32 + 1;
148                }
149            }
150            write!(
151                $w,
152                concat!("{:width$.prec$", $ty, "}"),
153                $data,
154                width = $width as usize,
155                prec = $precision as usize
156            )
157        }
158    }};
159}
160
161macro_rules! define_unumeric {
162    ($w: expr, $data: expr, $flags: expr, $width: expr, $precision: expr) => {
163        define_unumeric!($w, $data, $flags, $width, $precision, "")
164    };
165    ($w: expr, $data: expr, $flags: expr, $width: expr, $precision: expr, $ty:expr) => {{
166        if $flags.contains(Flags::LEFT_ALIGN) {
167            if $flags.contains(Flags::ALTERNATE_FORM) {
168                write!(
169                    $w,
170                    concat!("{:<#width$", $ty, "}"),
171                    $data,
172                    width = $width as usize
173                )
174            } else {
175                write!(
176                    $w,
177                    concat!("{:<width$", $ty, "}"),
178                    $data,
179                    width = $width as usize
180                )
181            }
182        } else if $flags.contains(Flags::ALTERNATE_FORM) {
183            if $flags.contains(Flags::PREPEND_ZERO) {
184                write!(
185                    $w,
186                    concat!("{:#0width$", $ty, "}"),
187                    $data,
188                    width = $width as usize
189                )
190            } else {
191                write!(
192                    $w,
193                    concat!("{:#width$", $ty, "}"),
194                    $data,
195                    width = $width as usize
196                )
197            }
198        } else if $flags.contains(Flags::PREPEND_ZERO) {
199            write!(
200                $w,
201                concat!("{:0width$", $ty, "}"),
202                $data,
203                width = $width as usize
204            )
205        } else {
206            write!(
207                $w,
208                concat!("{:width$", $ty, "}"),
209                $data,
210                width = $width as usize
211            )
212        }
213    }};
214}
215
216/// Write to a struct that implements [`fmt::Write`].
217///
218/// # Differences
219///
220/// There are a few differences from standard printf format:
221///
222/// - only valid UTF-8 data can be printed.
223/// - an `X` format specifier with a `#` flag prints the hex data in uppercase,
224///   but the leading `0x` is still lowercase
225/// - an `o` format specifier with a `#` flag precedes the number with an `o`
226///   instead of `0`
227/// - `g`/`G` (shorted floating point) is aliased to `f`/`F`` (decimal floating
228///   point)
229/// - same for `a`/`A` (hex floating point)
230/// - the `n` format specifier, [`Specifier::WriteBytesWritten`], is not
231///   implemented and will cause an error if encountered.
232pub fn fmt_write(w: &mut impl fmt::Write) -> impl FnMut(Argument) -> c_int + '_ {
233    use fmt::Write;
234    move |Argument {
235              flags,
236              mut width,
237              precision,
238              specifier,
239          }| {
240        let mut w = WriteCounter(w, 0);
241        let w = &mut w;
242        let res = match specifier {
243            Specifier::Percent => w.write_char('%'),
244            Specifier::Bytes(data) => write_str(w, flags, width, precision, data),
245            Specifier::String(data) => write_str(w, flags, width, precision, data.to_bytes()),
246            Specifier::Hex(data) => {
247                define_unumeric!(w, data, flags, width, precision.unwrap_or(0), "x")
248            }
249            Specifier::UpperHex(data) => {
250                define_unumeric!(w, data, flags, width, precision.unwrap_or(0), "X")
251            }
252            Specifier::Octal(data) => {
253                define_unumeric!(w, data, flags, width, precision.unwrap_or(0), "o")
254            }
255            Specifier::Uint(data) => {
256                define_unumeric!(w, data, flags, width, precision.unwrap_or(0))
257            }
258            Specifier::Int(data) => define_numeric!(w, data, flags, width, precision.unwrap_or(0)),
259            Specifier::Double { value, format } => match format {
260                DoubleFormat::Normal
261                | DoubleFormat::UpperNormal
262                | DoubleFormat::Auto
263                | DoubleFormat::UpperAuto
264                | DoubleFormat::Hex
265                | DoubleFormat::UpperHex => {
266                    define_numeric!(w, value, flags, width, precision.unwrap_or(6))
267                }
268                DoubleFormat::Scientific => {
269                    define_numeric!(w, value, flags, width, precision.unwrap_or(6), "e")
270                }
271                DoubleFormat::UpperScientific => {
272                    define_numeric!(w, value, flags, width, precision.unwrap_or(6), "E")
273                }
274            },
275            Specifier::Char(data) => {
276                if flags.contains(Flags::LEFT_ALIGN) {
277                    write!(w, "{:width$}", data as u8 as char, width = width as usize)
278                } else {
279                    write!(w, "{:>width$}", data as u8 as char, width = width as usize)
280                }
281            }
282            Specifier::Pointer(data) => {
283                if flags.contains(Flags::LEFT_ALIGN) {
284                    write!(w, "{:<width$p}", data, width = width as usize)
285                } else if flags.contains(Flags::PREPEND_ZERO) {
286                    write!(w, "{:0width$p}", data, width = width as usize)
287                } else {
288                    write!(w, "{:width$p}", data, width = width as usize)
289                }
290            }
291            Specifier::WriteBytesWritten(_, _) => Err(Default::default()),
292        };
293        match res {
294            Ok(_) => w.1 as c_int,
295            Err(_) => -1,
296        }
297    }
298}
299
300/// Returns an object that implements [`Display`][fmt::Display] for safely
301/// printing formatting data. This is slightly less performant than using
302/// [`fmt_write`], but may be the only option.
303///
304/// This shares the same caveats as [`fmt_write`].
305///
306/// # Safety
307///
308/// [`VaList`]s are *very* unsafe. The passed `format` and `args` parameter must be a valid [`printf` format string](http://www.cplusplus.com/reference/cstdio/printf/).
309pub unsafe fn display<'a>(format: *const c_char, va_list: VaList<'a>) -> VaListDisplay<'a> {
310    VaListDisplay {
311        format,
312        va_list,
313        written: Cell::new(0),
314    }
315}
316
317/// Helper struct created by [`display`] for safely printing `printf`-style
318/// formatting with [`format!`] and `{}`. This can be used with anything that
319/// uses [`format_args!`], such as [`println!`] or the `log` crate.
320///
321/// ```rust
322/// #![feature(c_variadic)]
323///
324/// use core::ffi::{c_char, c_int};
325///
326/// #[unsafe(no_mangle)]
327/// unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int {
328///     let format = unsafe { printf_compat::output::display(str, args) };
329///     println!("{}", format);
330///     format.bytes_written()
331/// }
332/// ```
333pub struct VaListDisplay<'a> {
334    format: *const c_char,
335    va_list: VaList<'a>,
336    written: Cell<c_int>,
337}
338
339impl VaListDisplay<'_> {
340    /// Get the number of bytes written, or 0 if there was an error.
341    pub fn bytes_written(&self) -> c_int {
342        self.written.get()
343    }
344}
345
346impl<'a> fmt::Display for VaListDisplay<'a> {
347    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348        unsafe {
349            let bytes = crate::format(self.format, self.va_list.clone(), fmt_write(f));
350            self.written.set(bytes);
351            if bytes < 0 { Err(fmt::Error) } else { Ok(()) }
352        }
353    }
354}
355
356#[cfg(feature = "std")]
357mod yes_std {
358    use std::io;
359
360    use super::*;
361
362    struct FmtWriter<T: io::Write>(T, io::Result<()>);
363
364    impl<T: io::Write> fmt::Write for FmtWriter<T> {
365        fn write_str(&mut self, s: &str) -> fmt::Result {
366            match self.0.write_all(s.as_bytes()) {
367                Ok(()) => Ok(()),
368                Err(e) => {
369                    self.1 = Err(e);
370                    Err(fmt::Error)
371                }
372            }
373        }
374    }
375
376    struct IoWriteCounter<'a, T: io::Write>(&'a mut T, usize);
377
378    impl<'a, T: io::Write> io::Write for IoWriteCounter<'a, T> {
379        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
380            self.0.write_all(buf)?;
381            self.1 += buf.len();
382            Ok(buf.len())
383        }
384
385        fn flush(&mut self) -> io::Result<()> {
386            self.0.flush()
387        }
388    }
389
390    fn write_bytes(
391        w: &mut impl io::Write,
392        flags: Flags,
393        width: c_int,
394        precision: Option<c_int>,
395        b: &[u8],
396    ) -> io::Result<()> {
397        let precision = precision.unwrap_or(b.len() as c_int);
398        let b = b.get(..(b.len().min(precision as usize))).unwrap_or(&[]);
399
400        if flags.contains(Flags::LEFT_ALIGN) {
401            w.write_all(b)?;
402            for _ in 0..((width as usize).saturating_sub(b.len())) {
403                w.write_all(b" ")?;
404            }
405            Ok(())
406        } else {
407            for _ in 0..((width as usize).saturating_sub(b.len())) {
408                w.write_all(b" ")?;
409            }
410            w.write_all(b)
411        }
412    }
413
414    /// Write to a struct that implements [`io::Write`].
415    ///
416    /// This shares the same caveats as [`fmt_write`], except that non-UTF-8
417    /// data is supported.
418    pub fn io_write(w: &mut impl io::Write) -> impl FnMut(Argument) -> c_int + '_ {
419        use io::Write;
420        move |Argument {
421                  flags,
422                  width,
423                  precision,
424                  specifier,
425              }| {
426            let mut w = IoWriteCounter(w, 0);
427            let mut w = &mut w;
428            let res = match specifier {
429                Specifier::Percent => w.write_all(b"%"),
430                Specifier::Bytes(data) => write_bytes(w, flags, width, precision, data),
431                Specifier::String(data) => write_bytes(w, flags, width, precision, data.to_bytes()),
432                _ => {
433                    let mut writer = FmtWriter(&mut w, Ok(()));
434                    fmt_write(&mut writer)(Argument {
435                        flags,
436                        width,
437                        precision,
438                        specifier,
439                    });
440                    writer.1
441                }
442            };
443            match res {
444                Ok(_) => w.1 as c_int,
445                Err(_) => -1,
446            }
447        }
448    }
449}