Skip to main content

taproot_c_scape/printf_impl/
parser.rs

1//! The `printf` format-string parser.
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. Every argument fetch
5//! goes through [`VaListTag::arg`], the stable `va_arg` walk.
6
7use core::ffi::*;
8
9use super::{Argument, DoubleFormat, Flags, SignedInt, Specifier, UnsignedInt};
10use crate::va::VaListTag;
11
12fn next_char(sub: &[u8]) -> &[u8] {
13    sub.get(1..).unwrap_or(&[])
14}
15
16/// Parse the [Flags field](https://en.wikipedia.org/wiki/Printf_format_string#Flags_field).
17fn parse_flags(mut sub: &[u8]) -> (Flags, &[u8]) {
18    let mut flags: Flags = Flags::empty();
19    while let Some(&ch) = sub.first() {
20        flags.insert(match ch {
21            b'-' => Flags::LEFT_ALIGN,
22            b'+' => Flags::PREPEND_PLUS,
23            b' ' => Flags::PREPEND_SPACE,
24            b'0' => Flags::PREPEND_ZERO,
25            b'\'' => Flags::THOUSANDS_GROUPING,
26            b'#' => Flags::ALTERNATE_FORM,
27            _ => break,
28        });
29        sub = next_char(sub)
30    }
31    (flags, sub)
32}
33
34/// Parse the [Width field](https://en.wikipedia.org/wiki/Printf_format_string#Width_field).
35unsafe fn parse_width<'a>(mut sub: &'a [u8], args: &mut VaListTag) -> (c_int, &'a [u8]) {
36    let mut width: c_int = 0;
37    if sub.first() == Some(&b'*') {
38        return (unsafe { args.arg() }, next_char(sub));
39    }
40    while let Some(&ch) = sub.first() {
41        match ch {
42            // https://rust-malaysia.github.io/code/2020/07/11/faster-integer-parsing.html#the-bytes-solution
43            b'0'..=b'9' => width = width * 10 + (ch & 0x0f) as c_int,
44            _ => break,
45        }
46        sub = next_char(sub);
47    }
48    (width, sub)
49}
50
51/// Parse the [Precision field](https://en.wikipedia.org/wiki/Printf_format_string#Precision_field).
52unsafe fn parse_precision<'a>(sub: &'a [u8], args: &mut VaListTag) -> (Option<c_int>, &'a [u8]) {
53    match sub.first() {
54        Some(&b'.') => {
55            let (prec, sub) = unsafe { parse_width(next_char(sub), args) };
56            (Some(prec), sub)
57        }
58        _ => (None, sub),
59    }
60}
61
62#[derive(Debug, Copy, Clone)]
63enum Length {
64    Int,
65    /// `hh`
66    Char,
67    /// `h`
68    Short,
69    /// `l`
70    Long,
71    /// `ll`
72    LongLong,
73    /// `z`
74    Usize,
75    /// `t`
76    Isize,
77}
78
79impl Length {
80    unsafe fn parse_signed(self, args: &mut VaListTag) -> SignedInt {
81        match self {
82            Length::Int => SignedInt::Int(unsafe { args.arg() }),
83            Length::Char => SignedInt::Char(unsafe { args.arg::<c_int>() } as c_schar),
84            Length::Short => SignedInt::Short(unsafe { args.arg::<c_int>() } as c_short),
85            Length::Long => SignedInt::Long(unsafe { args.arg() }),
86            Length::LongLong => SignedInt::LongLong(unsafe { args.arg() }),
87            // for some reason, these exist as different options, yet produce the same output
88            Length::Usize | Length::Isize => SignedInt::Isize(unsafe { args.arg() }),
89        }
90    }
91    unsafe fn parse_unsigned(self, args: &mut VaListTag) -> UnsignedInt {
92        match self {
93            Length::Int => UnsignedInt::Int(unsafe { args.arg() }),
94            Length::Char => UnsignedInt::Char(unsafe { args.arg::<c_uint>() } as c_uchar),
95            Length::Short => UnsignedInt::Short(unsafe { args.arg::<c_uint>() } as c_ushort),
96            Length::Long => UnsignedInt::Long(unsafe { args.arg() }),
97            Length::LongLong => UnsignedInt::LongLong(unsafe { args.arg() }),
98            // for some reason, these exist as different options, yet produce the same output
99            Length::Usize | Length::Isize => UnsignedInt::Isize(unsafe { args.arg() }),
100        }
101    }
102}
103
104/// Parse the [Length field](https://en.wikipedia.org/wiki/Printf_format_string#Length_field).
105fn parse_length(sub: &[u8]) -> (Length, &[u8]) {
106    match sub.first().copied() {
107        Some(b'h') => match sub.get(1).copied() {
108            Some(b'h') => (Length::Char, sub.get(2..).unwrap_or(&[])),
109            _ => (Length::Short, next_char(sub)),
110        },
111        Some(b'l') => match sub.get(1).copied() {
112            Some(b'l') => (Length::LongLong, sub.get(2..).unwrap_or(&[])),
113            _ => (Length::Long, next_char(sub)),
114        },
115        Some(b'z') => (Length::Usize, next_char(sub)),
116        Some(b't') => (Length::Isize, next_char(sub)),
117        _ => (Length::Int, sub),
118    }
119}
120
121/// Parse a format parameter and write it somewhere.
122///
123/// # Safety
124///
125/// The `args` walk is as unsafe as any `va_list`: the arguments the
126/// caller passed must match the passed `format`, which must be a valid
127/// [`printf` format string](http://www.cplusplus.com/reference/cstdio/printf/).
128pub unsafe fn format(
129    format: *const c_char,
130    args: &mut VaListTag,
131    mut handler: impl FnMut(Argument) -> c_int,
132) -> c_int {
133    let str = unsafe { CStr::from_ptr(format).to_bytes() };
134    // Pair each `%`-split chunk with whether another follows it (the
135    // upstream crate used itertools' tuple_windows over an Option chain
136    // for the same walk).
137    let mut iter = str.split(|&c| c == b'%').peekable();
138    let mut written = 0;
139
140    macro_rules! err {
141        ($ex: expr) => {{
142            let res = $ex;
143            if res < 0 {
144                return -1;
145            } else {
146                written += res;
147            }
148        }};
149    }
150    if let Some(begin) = iter.next() {
151        err!(handler(Specifier::Bytes(begin).into()));
152    }
153    let mut last_was_percent = false;
154    while let Some(sub) = iter.next() {
155        let has_next = iter.peek().is_some();
156        if last_was_percent {
157            err!(handler(Specifier::Bytes(sub).into()));
158            last_was_percent = false;
159            continue;
160        }
161        let (flags, sub) = parse_flags(sub);
162        let (width, sub) = unsafe { parse_width(sub, args) };
163        let (precision, sub) = unsafe { parse_precision(sub, args) };
164        let (length, sub) = parse_length(sub);
165        let ch = sub.first().unwrap_or(if has_next { &b'%' } else { &0 });
166        err!(handler(Argument {
167            flags,
168            width,
169            precision,
170            specifier: match ch {
171                b'%' => {
172                    last_was_percent = true;
173                    Specifier::Percent
174                }
175                b'd' | b'i' => Specifier::Int(unsafe { length.parse_signed(args) }),
176                b'x' => Specifier::Hex(unsafe { length.parse_unsigned(args) }),
177                b'X' => Specifier::UpperHex(unsafe { length.parse_unsigned(args) }),
178                b'u' => Specifier::Uint(unsafe { length.parse_unsigned(args) }),
179                b'o' => Specifier::Octal(unsafe { length.parse_unsigned(args) }),
180                b'f' | b'F' => Specifier::Double {
181                    value: unsafe { args.arg() },
182                    format: DoubleFormat::Normal.set_upper(ch.is_ascii_uppercase()),
183                },
184                b'e' | b'E' => Specifier::Double {
185                    value: unsafe { args.arg() },
186                    format: DoubleFormat::Scientific.set_upper(ch.is_ascii_uppercase()),
187                },
188                b'g' | b'G' => Specifier::Double {
189                    value: unsafe { args.arg() },
190                    format: DoubleFormat::Auto.set_upper(ch.is_ascii_uppercase()),
191                },
192                b'a' | b'A' => Specifier::Double {
193                    value: unsafe { args.arg() },
194                    format: DoubleFormat::Hex.set_upper(ch.is_ascii_uppercase()),
195                },
196                b's' => {
197                    let arg: *mut c_char = unsafe { args.arg() };
198                    // As a common extension supported by glibc, musl, and
199                    // others, format a NULL pointer as "(null)".
200                    if arg.is_null() {
201                        Specifier::Bytes(b"(null)")
202                    } else {
203                        Specifier::String(unsafe { CStr::from_ptr(arg) })
204                    }
205                }
206                b'c' => {
207                    trait CharToInt {
208                        type IntType;
209                    }
210
211                    impl CharToInt for c_schar {
212                        type IntType = c_int;
213                    }
214
215                    impl CharToInt for c_uchar {
216                        type IntType = c_uint;
217                    }
218
219                    Specifier::Char(
220                        unsafe { args.arg::<<c_char as CharToInt>::IntType>() } as c_char
221                    )
222                }
223                b'p' => Specifier::Pointer(unsafe { args.arg() }),
224                b'n' => Specifier::WriteBytesWritten(written, unsafe { args.arg() }),
225                _ => return -1,
226            },
227        }));
228        err!(handler(Specifier::Bytes(next_char(sub)).into()));
229    }
230    written
231}