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 char, width = width as usize)
278                } else {
279                    write!(w, "{:>width$}", data 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, 'b>(
310    format: *const c_char,
311    va_list: VaList<'a, 'b>,
312) -> VaListDisplay<'a, 'b> {
313    VaListDisplay {
314        format,
315        va_list,
316        written: Cell::new(0),
317    }
318}
319
320/// Helper struct created by [`display`] for safely printing `printf`-style
321/// formatting with [`format!`] and `{}`. This can be used with anything that
322/// uses [`format_args!`], such as [`println!`] or the `log` crate.
323///
324/// ```rust
325/// #![feature(c_variadic)]
326///
327/// use core::ffi::{c_char, c_int};
328///
329/// #[no_mangle]
330/// unsafe extern "C" fn c_library_print(str: *const c_char, mut args: ...) -> c_int {
331///     let format = printf_compat::output::display(str, args.as_va_list());
332///     println!("{}", format);
333///     format.bytes_written()
334/// }
335/// ```
336pub struct VaListDisplay<'a, 'b> {
337    format: *const c_char,
338    va_list: VaList<'a, 'b>,
339    written: Cell<c_int>,
340}
341
342impl VaListDisplay<'_, '_> {
343    /// Get the number of bytes written, or 0 if there was an error.
344    pub fn bytes_written(&self) -> c_int {
345        self.written.get()
346    }
347}
348
349impl<'a, 'b> fmt::Display for VaListDisplay<'a, 'b> {
350    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351        unsafe {
352            let bytes = crate::format(self.format, self.va_list.clone().as_va_list(), fmt_write(f));
353            self.written.set(bytes);
354            if bytes < 0 {
355                Err(fmt::Error)
356            } else {
357                Ok(())
358            }
359        }
360    }
361}
362
363#[cfg(feature = "std")]
364mod yes_std {
365    use std::io;
366
367    use super::*;
368
369    struct FmtWriter<T: io::Write>(T, io::Result<()>);
370
371    impl<T: io::Write> fmt::Write for FmtWriter<T> {
372        fn write_str(&mut self, s: &str) -> fmt::Result {
373            match self.0.write_all(s.as_bytes()) {
374                Ok(()) => Ok(()),
375                Err(e) => {
376                    self.1 = Err(e);
377                    Err(fmt::Error)
378                }
379            }
380        }
381    }
382
383    struct IoWriteCounter<'a, T: io::Write>(&'a mut T, usize);
384
385    impl<'a, T: io::Write> io::Write for IoWriteCounter<'a, T> {
386        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
387            self.0.write_all(buf)?;
388            self.1 += buf.len();
389            Ok(buf.len())
390        }
391
392        fn flush(&mut self) -> io::Result<()> {
393            self.0.flush()
394        }
395    }
396
397    fn write_bytes(
398        w: &mut impl io::Write,
399        flags: Flags,
400        width: c_int,
401        precision: Option<c_int>,
402        b: &[u8],
403    ) -> io::Result<()> {
404        let precision = precision.unwrap_or(b.len() as c_int);
405        let b = b.get(..(b.len().min(precision as usize))).unwrap_or(&[]);
406
407        if flags.contains(Flags::LEFT_ALIGN) {
408            w.write_all(b)?;
409            for _ in 0..((width as usize).saturating_sub(b.len())) {
410                w.write_all(b" ")?;
411            }
412            Ok(())
413        } else {
414            for _ in 0..((width as usize).saturating_sub(b.len())) {
415                w.write_all(b" ")?;
416            }
417            w.write_all(b)
418        }
419    }
420
421    /// Write to a struct that implements [`io::Write`].
422    ///
423    /// This shares the same caveats as [`fmt_write`], except that non-UTF-8
424    /// data is supported.
425    pub fn io_write(w: &mut impl io::Write) -> impl FnMut(Argument) -> c_int + '_ {
426        use io::Write;
427        move |Argument {
428                  flags,
429                  width,
430                  precision,
431                  specifier,
432              }| {
433            let mut w = IoWriteCounter(w, 0);
434            let mut w = &mut w;
435            let res = match specifier {
436                Specifier::Percent => w.write_all(b"%"),
437                Specifier::Bytes(data) => write_bytes(w, flags, width, precision, data),
438                Specifier::String(data) => write_bytes(w, flags, width, precision, data.to_bytes()),
439                _ => {
440                    let mut writer = FmtWriter(&mut w, Ok(()));
441                    fmt_write(&mut writer)(Argument {
442                        flags,
443                        width,
444                        precision,
445                        specifier,
446                    });
447                    writer.1
448                }
449            };
450            match res {
451                Ok(_) => w.1 as c_int,
452                Err(_) => -1,
453            }
454        }
455    }
456}