printf_compat/
output.rs

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