Skip to main content

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.
232/// - precision is ignored for integral types, instead of specifying the
233///   minimum number of digits.
234pub fn fmt_write(w: &mut impl fmt::Write) -> impl FnMut(Argument) -> c_int + '_ {
235    use fmt::Write;
236    move |Argument {
237              flags,
238              mut width,
239              precision,
240              specifier,
241          }| {
242        let mut w = WriteCounter(w, 0);
243        let w = &mut w;
244        let res = match specifier {
245            Specifier::Percent => w.write_char('%'),
246            Specifier::Bytes(data) => write_str(w, flags, width, precision, data),
247            Specifier::String(data) => write_str(w, flags, width, precision, data.to_bytes()),
248            Specifier::Hex(data) => {
249                define_unumeric!(w, data, flags, width, precision.unwrap_or(0), "x")
250            }
251            Specifier::UpperHex(data) => {
252                define_unumeric!(w, data, flags, width, precision.unwrap_or(0), "X")
253            }
254            Specifier::Octal(data) => {
255                define_unumeric!(w, data, flags, width, precision.unwrap_or(0), "o")
256            }
257            Specifier::Uint(data) => {
258                define_unumeric!(w, data, flags, width, precision.unwrap_or(0))
259            }
260            Specifier::Int(data) => define_numeric!(w, data, flags, width, precision.unwrap_or(0)),
261            Specifier::Double { value, format } => match format {
262                DoubleFormat::Normal
263                | DoubleFormat::UpperNormal
264                | DoubleFormat::Auto
265                | DoubleFormat::UpperAuto
266                | DoubleFormat::Hex
267                | DoubleFormat::UpperHex => {
268                    define_numeric!(w, value, flags, width, precision.unwrap_or(6))
269                }
270                DoubleFormat::Scientific => {
271                    define_numeric!(w, value, flags, width, precision.unwrap_or(6), "e")
272                }
273                DoubleFormat::UpperScientific => {
274                    define_numeric!(w, value, flags, width, precision.unwrap_or(6), "E")
275                }
276            },
277            Specifier::Char(data) => {
278                if flags.contains(Flags::LEFT_ALIGN) {
279                    write!(w, "{:width$}", data as u8 as char, width = width as usize)
280                } else {
281                    write!(w, "{:>width$}", data as u8 as char, width = width as usize)
282                }
283            }
284            Specifier::Pointer(data) => {
285                if flags.contains(Flags::LEFT_ALIGN) {
286                    write!(w, "{:<width$p}", data, width = width as usize)
287                } else if flags.contains(Flags::PREPEND_ZERO) {
288                    write!(w, "{:0width$p}", data, width = width as usize)
289                } else {
290                    write!(w, "{:width$p}", data, width = width as usize)
291                }
292            }
293            Specifier::WriteBytesWritten(_, _) => Err(Default::default()),
294        };
295        match res {
296            Ok(_) => w.1 as c_int,
297            Err(_) => -1,
298        }
299    }
300}
301
302/// Returns an object that implements [`Display`][fmt::Display] for safely
303/// printing formatting data. This is slightly less performant than using
304/// [`fmt_write`], but may be the only option.
305///
306/// This shares the same caveats as [`fmt_write`].
307///
308/// # Safety
309///
310/// [`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/).
311pub unsafe fn display<'a>(format: *const c_char, va_list: VaList<'a>) -> VaListDisplay<'a> {
312    VaListDisplay {
313        format,
314        va_list,
315        written: Cell::new(0),
316    }
317}
318
319/// Helper struct created by [`display`] for safely printing `printf`-style
320/// formatting with [`format!`] and `{}`. This can be used with anything that
321/// uses [`format_args!`], such as [`println!`] or the `log` crate.
322///
323/// ```rust
324/// #![feature(c_variadic)]
325///
326/// use core::ffi::{c_char, c_int};
327///
328/// #[unsafe(no_mangle)]
329/// unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int {
330///     let format = unsafe { printf_compat::output::display(str, args) };
331///     println!("{}", format);
332///     format.bytes_written()
333/// }
334/// ```
335pub struct VaListDisplay<'a> {
336    format: *const c_char,
337    va_list: VaList<'a>,
338    written: Cell<c_int>,
339}
340
341impl VaListDisplay<'_> {
342    /// Get the number of bytes written, or 0 if there was an error.
343    pub fn bytes_written(&self) -> c_int {
344        self.written.get()
345    }
346}
347
348impl<'a> fmt::Display for VaListDisplay<'a> {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        unsafe {
351            let bytes = crate::format(self.format, self.va_list.clone(), fmt_write(f));
352            self.written.set(bytes);
353            if bytes < 0 { Err(fmt::Error) } else { Ok(()) }
354        }
355    }
356}
357
358#[cfg(feature = "std")]
359mod yes_std {
360    use std::io;
361
362    use super::*;
363
364    struct FmtWriter<T: io::Write>(T, io::Result<()>);
365
366    impl<T: io::Write> fmt::Write for FmtWriter<T> {
367        fn write_str(&mut self, s: &str) -> fmt::Result {
368            match self.0.write_all(s.as_bytes()) {
369                Ok(()) => Ok(()),
370                Err(e) => {
371                    self.1 = Err(e);
372                    Err(fmt::Error)
373                }
374            }
375        }
376    }
377
378    struct IoWriteCounter<'a, T: io::Write>(&'a mut T, usize);
379
380    impl<'a, T: io::Write> io::Write for IoWriteCounter<'a, T> {
381        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
382            self.0.write_all(buf)?;
383            self.1 += buf.len();
384            Ok(buf.len())
385        }
386
387        fn flush(&mut self) -> io::Result<()> {
388            self.0.flush()
389        }
390    }
391
392    fn write_bytes(
393        w: &mut impl io::Write,
394        flags: Flags,
395        width: c_int,
396        precision: Option<c_int>,
397        b: &[u8],
398    ) -> io::Result<()> {
399        let precision = precision.unwrap_or(b.len() as c_int);
400        let b = b.get(..(b.len().min(precision as usize))).unwrap_or(&[]);
401
402        if flags.contains(Flags::LEFT_ALIGN) {
403            w.write_all(b)?;
404            for _ in 0..((width as usize).saturating_sub(b.len())) {
405                w.write_all(b" ")?;
406            }
407            Ok(())
408        } else {
409            for _ in 0..((width as usize).saturating_sub(b.len())) {
410                w.write_all(b" ")?;
411            }
412            w.write_all(b)
413        }
414    }
415
416    /// Write to a struct that implements [`io::Write`].
417    ///
418    /// This shares the same caveats as [`fmt_write`], except that non-UTF-8
419    /// data is supported.
420    pub fn io_write(w: &mut impl io::Write) -> impl FnMut(Argument) -> c_int + '_ {
421        use io::Write;
422        move |Argument {
423                  flags,
424                  width,
425                  precision,
426                  specifier,
427              }| {
428            let mut w = IoWriteCounter(w, 0);
429            let mut w = &mut w;
430            let res = match specifier {
431                Specifier::Percent => w.write_all(b"%"),
432                Specifier::Bytes(data) => write_bytes(w, flags, width, precision, data),
433                Specifier::String(data) => write_bytes(w, flags, width, precision, data.to_bytes()),
434                _ => {
435                    let mut writer = FmtWriter(&mut w, Ok(()));
436                    fmt_write(&mut writer)(Argument {
437                        flags,
438                        width,
439                        precision,
440                        specifier,
441                    });
442                    writer.1
443                }
444            };
445            match res {
446                Ok(_) => w.1 as c_int,
447                Err(_) => -1,
448            }
449        }
450    }
451}