Skip to main content

taproot_c_scape/printf_impl/
output.rs

1//! Various ways to output formatting data.
2//!
3//! Vendored from printf-compat 0.4.0 (lights0123, MIT OR Apache-2.0); see
4//! the module docs in mod.rs for the port's deltas. Only the
5//! [`fmt::Write`] adapter survives the port: it is the one c-scape
6//! formats through, and it takes no `va_list` of any kind.
7
8use core::ffi::*;
9use core::fmt;
10use core::str::from_utf8;
11
12use super::{Argument, DoubleFormat, Flags, Specifier};
13
14struct DummyWriter(usize);
15
16impl fmt::Write for DummyWriter {
17    fn write_str(&mut self, s: &str) -> fmt::Result {
18        self.0 += s.len();
19        Ok(())
20    }
21}
22
23struct WriteCounter<'a, T: fmt::Write>(&'a mut T, usize);
24
25impl<'a, T: fmt::Write> fmt::Write for WriteCounter<'a, T> {
26    fn write_str(&mut self, s: &str) -> fmt::Result {
27        self.1 += s.len();
28        self.0.write_str(s)
29    }
30}
31
32fn write_str(
33    w: &mut impl fmt::Write,
34    flags: Flags,
35    width: c_int,
36    precision: Option<c_int>,
37    b: &[u8],
38) -> fmt::Result {
39    let string = from_utf8(b).map_err(|_| fmt::Error)?;
40    let precision = precision.unwrap_or(string.len() as c_int);
41    if flags.contains(Flags::LEFT_ALIGN) {
42        write!(
43            w,
44            "{:1$.prec$}",
45            string,
46            width as usize,
47            prec = precision as usize
48        )
49    } else {
50        write!(
51            w,
52            "{:>1$.prec$}",
53            string,
54            width as usize,
55            prec = precision as usize
56        )
57    }
58}
59
60macro_rules! define_numeric {
61    ($w: expr, $data: expr, $flags: expr, $width: expr, $precision: expr) => {
62        define_numeric!($w, $data, $flags, $width, $precision, "")
63    };
64    ($w: expr, $data: expr, $flags: expr, $width: expr, $precision: expr, $ty:expr) => {{
65        use fmt::Write;
66        if $flags.contains(Flags::LEFT_ALIGN) {
67            if $flags.contains(Flags::PREPEND_PLUS) {
68                write!(
69                    $w,
70                    concat!("{:<+width$.prec$", $ty, "}"),
71                    $data,
72                    width = $width as usize,
73                    prec = $precision as usize
74                )
75            } else if $flags.contains(Flags::PREPEND_SPACE) && !$data.is_sign_negative() {
76                write!(
77                    $w,
78                    concat!(" {:<width$.prec$", $ty, "}"),
79                    $data,
80                    width = ($width as usize).wrapping_sub(1),
81                    prec = $precision as usize
82                )
83            } else {
84                write!(
85                    $w,
86                    concat!("{:<width$.prec$", $ty, "}"),
87                    $data,
88                    width = $width as usize,
89                    prec = $precision as usize
90                )
91            }
92        } else if $flags.contains(Flags::PREPEND_PLUS) {
93            if $flags.contains(Flags::PREPEND_ZERO) {
94                write!(
95                    $w,
96                    concat!("{:+0width$.prec$", $ty, "}"),
97                    $data,
98                    width = $width as usize,
99                    prec = $precision as usize
100                )
101            } else {
102                write!(
103                    $w,
104                    concat!("{:+width$.prec$", $ty, "}"),
105                    $data,
106                    width = $width as usize,
107                    prec = $precision as usize
108                )
109            }
110        } else if $flags.contains(Flags::PREPEND_ZERO) {
111            if $flags.contains(Flags::PREPEND_SPACE) && !$data.is_sign_negative() {
112                let mut d = DummyWriter(0);
113                let _ = write!(
114                    d,
115                    concat!("{:.prec$", $ty, "}"),
116                    $data,
117                    prec = $precision as usize
118                );
119                if d.0 + 1 > $width as usize {
120                    $width += 1;
121                }
122                write!(
123                    $w,
124                    concat!(" {:0width$.prec$", $ty, "}"),
125                    $data,
126                    width = ($width as usize).wrapping_sub(1),
127                    prec = $precision as usize
128                )
129            } else {
130                write!(
131                    $w,
132                    concat!("{:0width$.prec$", $ty, "}"),
133                    $data,
134                    width = $width as usize,
135                    prec = $precision as usize
136                )
137            }
138        } else {
139            if $flags.contains(Flags::PREPEND_SPACE) && !$data.is_sign_negative() {
140                let mut d = DummyWriter(0);
141                let _ = write!(
142                    d,
143                    concat!("{:.prec$", $ty, "}"),
144                    $data,
145                    prec = $precision as usize
146                );
147                if d.0 + 1 > $width as usize {
148                    $width = d.0 as i32 + 1;
149                }
150            }
151            write!(
152                $w,
153                concat!("{:width$.prec$", $ty, "}"),
154                $data,
155                width = $width as usize,
156                prec = $precision as usize
157            )
158        }
159    }};
160}
161
162macro_rules! define_unumeric {
163    ($w: expr, $data: expr, $flags: expr, $width: expr, $precision: expr) => {
164        define_unumeric!($w, $data, $flags, $width, $precision, "")
165    };
166    ($w: expr, $data: expr, $flags: expr, $width: expr, $precision: expr, $ty:expr) => {{
167        if $flags.contains(Flags::LEFT_ALIGN) {
168            if $flags.contains(Flags::ALTERNATE_FORM) {
169                write!(
170                    $w,
171                    concat!("{:<#width$", $ty, "}"),
172                    $data,
173                    width = $width as usize
174                )
175            } else {
176                write!(
177                    $w,
178                    concat!("{:<width$", $ty, "}"),
179                    $data,
180                    width = $width as usize
181                )
182            }
183        } else if $flags.contains(Flags::ALTERNATE_FORM) {
184            if $flags.contains(Flags::PREPEND_ZERO) {
185                write!(
186                    $w,
187                    concat!("{:#0width$", $ty, "}"),
188                    $data,
189                    width = $width as usize
190                )
191            } else {
192                write!(
193                    $w,
194                    concat!("{:#width$", $ty, "}"),
195                    $data,
196                    width = $width as usize
197                )
198            }
199        } else if $flags.contains(Flags::PREPEND_ZERO) {
200            write!(
201                $w,
202                concat!("{:0width$", $ty, "}"),
203                $data,
204                width = $width as usize
205            )
206        } else {
207            write!(
208                $w,
209                concat!("{:width$", $ty, "}"),
210                $data,
211                width = $width as usize
212            )
213        }
214    }};
215}
216
217/// Write to a struct that implements [`fmt::Write`].
218///
219/// # Differences
220///
221/// There are a few differences from standard printf format:
222///
223/// - only valid UTF-8 data can be printed.
224/// - an `X` format specifier with a `#` flag prints the hex data in uppercase,
225///   but the leading `0x` is still lowercase.
226/// - an `o` format specifier with a `#` flag precedes the number with an `o`
227///   instead of `0`.
228/// - `g`/`G` (shorted floating point) is aliased to `f`/`F`` (decimal floating
229///   point).
230/// - same for `a`/`A` (hex floating point).
231/// - the `n` format specifier, [`Specifier::WriteBytesWritten`], is not
232///   implemented and will cause an error if encountered.
233/// - precision is ignored for integral types, instead of specifying the
234///   minimum number of digits.
235pub fn fmt_write(w: &mut impl fmt::Write) -> impl FnMut(Argument) -> c_int + '_ {
236    use fmt::Write;
237    move |Argument {
238              flags,
239              mut width,
240              precision,
241              specifier,
242          }| {
243        let mut w = WriteCounter(w, 0);
244        let w = &mut w;
245        let res = match specifier {
246            Specifier::Percent => w.write_char('%'),
247            Specifier::Bytes(data) => write_str(w, flags, width, precision, data),
248            Specifier::String(data) => write_str(w, flags, width, precision, data.to_bytes()),
249            Specifier::Hex(data) => {
250                define_unumeric!(w, data, flags, width, precision.unwrap_or(0), "x")
251            }
252            Specifier::UpperHex(data) => {
253                define_unumeric!(w, data, flags, width, precision.unwrap_or(0), "X")
254            }
255            Specifier::Octal(data) => {
256                define_unumeric!(w, data, flags, width, precision.unwrap_or(0), "o")
257            }
258            Specifier::Uint(data) => {
259                define_unumeric!(w, data, flags, width, precision.unwrap_or(0))
260            }
261            Specifier::Int(data) => define_numeric!(w, data, flags, width, precision.unwrap_or(0)),
262            Specifier::Double { value, format } => match format {
263                DoubleFormat::Normal
264                | DoubleFormat::UpperNormal
265                | DoubleFormat::Auto
266                | DoubleFormat::UpperAuto
267                | DoubleFormat::Hex
268                | DoubleFormat::UpperHex => {
269                    define_numeric!(w, value, flags, width, precision.unwrap_or(6))
270                }
271                DoubleFormat::Scientific => {
272                    define_numeric!(w, value, flags, width, precision.unwrap_or(6), "e")
273                }
274                DoubleFormat::UpperScientific => {
275                    define_numeric!(w, value, flags, width, precision.unwrap_or(6), "E")
276                }
277            },
278            Specifier::Char(data) => {
279                if flags.contains(Flags::LEFT_ALIGN) {
280                    write!(w, "{:width$}", data as u8 as char, width = width as usize)
281                } else {
282                    write!(w, "{:>width$}", data as u8 as char, width = width as usize)
283                }
284            }
285            Specifier::Pointer(data) => {
286                if flags.contains(Flags::LEFT_ALIGN) {
287                    write!(w, "{:<width$p}", data, width = width as usize)
288                } else if flags.contains(Flags::PREPEND_ZERO) {
289                    write!(w, "{:0width$p}", data, width = width as usize)
290                } else {
291                    write!(w, "{:width$p}", data, width = width as usize)
292                }
293            }
294            Specifier::WriteBytesWritten(_, _) => Err(Default::default()),
295        };
296        match res {
297            Ok(_) => w.1 as c_int,
298            Err(_) => -1,
299        }
300    }
301}