Files
RedBear-OS/src/header/stdio/printf.rs
T

1448 lines
56 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// TODO: reuse more code with the wide printf impl
use crate::{
c_str::{self, CStr, NulStr},
io::{self, Write},
};
use alloc::{
collections::BTreeMap,
string::{String, ToString},
vec::Vec,
};
use core::{cmp, ffi::VaList, fmt, num::FpCategory, ops::Range, slice};
use crate::{
header::errno::{self, EILSEQ},
platform::{
self,
types::{
c_char, c_double, c_int, c_long, c_longdouble, c_longlong, c_short, c_uchar, c_uint,
c_ulong, c_ulonglong, c_ushort, c_void, intmax_t, ptrdiff_t, size_t, ssize_t,
uintmax_t, wchar_t, wint_t,
},
},
};
#[allow(unused_doc_comments)]
/// cbindgen:ignore
unsafe extern "C" {
pub unsafe fn relibc_ldtod(x: *const c_longdouble) -> c_double;
pub unsafe fn relibc_dtold(x: c_double, out: *mut c_longdouble);
}
// ____ _ _ _ _
// | __ ) ___ (_) | ___ _ __ _ __ | | __ _| |_ ___ _
// | _ \ / _ \| | |/ _ \ '__| '_ \| |/ _` | __/ _ (_)
// | |_) | (_) | | | __/ | | |_) | | (_| | || __/_
// |____/ \___/|_|_|\___|_| | .__/|_|\__,_|\__\___(_)
// |_|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum IntKind {
Byte,
Short,
Int,
Long,
LongLong,
IntMax,
PtrDiff,
Size,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum FmtKind {
Percent,
Signed,
Unsigned,
Scientific,
Decimal,
AnyNotation,
String,
Char,
Pointer,
GetWritten,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum Number {
Static(usize),
Index(usize),
Next,
}
impl Number {
pub(crate) unsafe fn resolve(self, varargs: &mut VaListCache, ap: &mut VaList) -> usize {
let arg = match self {
Number::Static(num) => return num,
Number::Index(i) => unsafe { varargs.get(i - 1, ap, None) },
Number::Next => {
let i = varargs.i;
varargs.i += 1;
unsafe { varargs.get(i, ap, None) }
}
};
match arg {
VaArg::c_char(i) => i as usize,
VaArg::c_double(i) => i as usize,
#[cfg(target_pointer_width = "32")]
VaArg::c_longdouble(_) => 0 as usize,
#[cfg(target_pointer_width = "64")]
VaArg::c_longdouble(i) => i as usize,
VaArg::c_int(i) => i as usize,
VaArg::c_long(i) => i as usize,
VaArg::c_longlong(i) => i as usize,
VaArg::c_short(i) => i as usize,
VaArg::intmax_t(i) => i as usize,
VaArg::pointer(i) => i as usize,
VaArg::ptrdiff_t(i) => i as usize,
VaArg::ssize_t(i) => i as usize,
VaArg::wint_t(i) => i as usize,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum VaArg {
c_char(c_char),
c_double(c_double),
c_longdouble(c_longdouble),
c_int(c_int),
c_long(c_long),
c_longlong(c_longlong),
c_short(c_short),
intmax_t(intmax_t),
pointer(*const c_void),
ptrdiff_t(ptrdiff_t),
ssize_t(ssize_t),
wint_t(wint_t),
}
impl VaArg {
pub(crate) unsafe fn arg_from(fmtkind: FmtKind, intkind: IntKind, ap: &mut VaList) -> VaArg {
// Per the C standard using va_arg with a type with a size
// less than that of an int for integers and double for floats
// is invalid. As a result any arguments smaller than an int or
// double passed to a function will be promoted to the smallest
// possible size. The VaList::arg function will handle this
// automagically.
match (fmtkind, intkind) {
(FmtKind::Percent, _) => panic!("Can't call arg_from on %"),
(FmtKind::Char, IntKind::Long) | (FmtKind::Char, IntKind::LongLong) => {
VaArg::wint_t(unsafe { relibc_va_arg!(ap, wint_t) })
}
(FmtKind::Char, _)
| (FmtKind::Unsigned, IntKind::Byte)
| (FmtKind::Signed, IntKind::Byte) => {
// c_int is passed but truncated to c_char
VaArg::c_char(unsafe { relibc_va_arg!(ap, c_int) } as c_char)
}
(FmtKind::Unsigned, IntKind::Short) | (FmtKind::Signed, IntKind::Short) => {
// c_int is passed but truncated to c_short
VaArg::c_short(unsafe { relibc_va_arg!(ap, c_int) } as c_short)
}
(FmtKind::Unsigned, IntKind::Int) | (FmtKind::Signed, IntKind::Int) => {
VaArg::c_int(unsafe { relibc_va_arg!(ap, c_int) })
}
(FmtKind::Unsigned, IntKind::Long) | (FmtKind::Signed, IntKind::Long) => {
VaArg::c_long(unsafe { relibc_va_arg!(ap, c_long) })
}
(FmtKind::Unsigned, IntKind::LongLong) | (FmtKind::Signed, IntKind::LongLong) => {
VaArg::c_longlong(unsafe { relibc_va_arg!(ap, c_longlong) })
}
(FmtKind::Unsigned, IntKind::IntMax) | (FmtKind::Signed, IntKind::IntMax) => {
VaArg::intmax_t(unsafe { relibc_va_arg!(ap, intmax_t) })
}
(FmtKind::Unsigned, IntKind::PtrDiff) | (FmtKind::Signed, IntKind::PtrDiff) => {
VaArg::ptrdiff_t(unsafe { relibc_va_arg!(ap, ptrdiff_t) })
}
(FmtKind::Unsigned, IntKind::Size) | (FmtKind::Signed, IntKind::Size) => {
VaArg::ssize_t(unsafe { relibc_va_arg!(ap, ssize_t) })
}
(FmtKind::AnyNotation, IntKind::LongLong)
| (FmtKind::Decimal, IntKind::LongLong)
| (FmtKind::Scientific, IntKind::LongLong) => {
VaArg::c_longdouble(unsafe { VaArg::extract_longdouble(ap) })
}
(FmtKind::AnyNotation, _) | (FmtKind::Decimal, _) | (FmtKind::Scientific, _) => {
VaArg::c_double(unsafe { relibc_va_arg!(ap, c_double) })
}
(FmtKind::GetWritten, _) | (FmtKind::Pointer, _) | (FmtKind::String, _) => {
VaArg::pointer(unsafe { relibc_va_arg!(ap, *const c_void) })
}
}
}
#[cfg(target_arch = "x86")]
unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble {
todo_skip!(0, "long double in variadic printf is not supported");
[0, 0, 0]
}
#[cfg(target_arch = "x86_64")]
unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble {
// https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf (long double)
// exactly same as core::ffi::VaListImpl but all variables exposed
#[repr(C)]
struct VaListImpl {
gp_offset: i32,
fp_offset: i32,
overflow_arg_area: *mut u8,
reg_save_area: *mut u8,
}
let ap_impl = unsafe {
// The double deconstruct is intended
let ptr_to_struct = (ap as *mut core::ffi::VaList as *mut VaListImpl);
&mut *ptr_to_struct
};
let ptr = ap_impl.overflow_arg_area as *const c_longdouble;
let val = unsafe { ptr.read() };
ap_impl.overflow_arg_area = unsafe { ap_impl.overflow_arg_area.add(16) };
val
}
#[cfg(target_arch = "aarch64")]
unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble {
// https://c9x.me/compile/bib/abi-arm64.pdf (quad precision)
// exactly same as core::ffi::VaListImpl but all variables exposed
#[repr(C)]
struct VaListImpl {
stack: *mut u8,
gr_top: *mut u8,
vr_top: *mut u8,
gr_offs: i32,
vr_offs: i32,
}
let ap_impl: &mut VaListImpl = unsafe {
// The double deconstruct is intended
let ptr_to_struct = (ap as *mut core::ffi::VaList as *mut VaListImpl);
&mut *ptr_to_struct
};
let ptr = unsafe { ap_impl.vr_top.offset(ap_impl.vr_offs as isize) as *const c_longdouble };
ap_impl.vr_offs += 16;
unsafe { ptr.read() }
}
#[cfg(target_arch = "riscv64")]
unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble {
todo_skip!(0, "long double in variadic printf is not supported");
0u128
}
unsafe fn transmute(&self, fmtkind: FmtKind, intkind: IntKind) -> VaArg {
// At this point, there are conflicting printf arguments. An
// example of this is:
// ```c
// printf("%1$d %1$lf\n", 5, 0.1);
// ```
// We handle it just like glibc: We read it from the VaList
// using the *last* argument type, but we transmute it when we
// try to access the other ones.
union Untyped {
c_char: c_char,
c_double: c_double,
c_longdouble: c_longdouble,
c_int: c_int,
c_long: c_long,
c_longlong: c_longlong,
c_short: c_short,
intmax_t: intmax_t,
pointer: *const c_void,
ptrdiff_t: ptrdiff_t,
ssize_t: ssize_t,
wint_t: wint_t,
}
let untyped = match *self {
VaArg::c_char(i) => Untyped { c_char: i },
VaArg::c_double(i) => Untyped { c_double: i },
VaArg::c_longdouble(i) => Untyped { c_longdouble: i },
VaArg::c_int(i) => Untyped { c_int: i },
VaArg::c_long(i) => Untyped { c_long: i },
VaArg::c_longlong(i) => Untyped { c_longlong: i },
VaArg::c_short(i) => Untyped { c_short: i },
VaArg::intmax_t(i) => Untyped { intmax_t: i },
VaArg::pointer(i) => Untyped { pointer: i },
VaArg::ptrdiff_t(i) => Untyped { ptrdiff_t: i },
VaArg::ssize_t(i) => Untyped { ssize_t: i },
VaArg::wint_t(i) => Untyped { wint_t: i },
};
match (fmtkind, intkind) {
(FmtKind::Percent, _) => panic!("Can't call transmute on %"),
(FmtKind::Char, IntKind::Long) | (FmtKind::Char, IntKind::LongLong) => {
VaArg::wint_t(unsafe { untyped.wint_t })
}
(FmtKind::Char, _)
| (FmtKind::Unsigned, IntKind::Byte)
| (FmtKind::Signed, IntKind::Byte) => VaArg::c_char(unsafe { untyped.c_char }),
(FmtKind::Unsigned, IntKind::Short) | (FmtKind::Signed, IntKind::Short) => {
VaArg::c_short(unsafe { untyped.c_short })
}
(FmtKind::Unsigned, IntKind::Int) | (FmtKind::Signed, IntKind::Int) => {
VaArg::c_int(unsafe { untyped.c_int })
}
(FmtKind::Unsigned, IntKind::Long) | (FmtKind::Signed, IntKind::Long) => {
VaArg::c_long(unsafe { untyped.c_long })
}
(FmtKind::Unsigned, IntKind::LongLong) | (FmtKind::Signed, IntKind::LongLong) => {
VaArg::c_longlong(unsafe { untyped.c_longlong })
}
(FmtKind::Unsigned, IntKind::IntMax) | (FmtKind::Signed, IntKind::IntMax) => {
VaArg::intmax_t(unsafe { untyped.intmax_t })
}
(FmtKind::Unsigned, IntKind::PtrDiff) | (FmtKind::Signed, IntKind::PtrDiff) => {
VaArg::ptrdiff_t(unsafe { untyped.ptrdiff_t })
}
(FmtKind::Unsigned, IntKind::Size) | (FmtKind::Signed, IntKind::Size) => {
VaArg::ssize_t(unsafe { untyped.ssize_t })
}
(FmtKind::AnyNotation, IntKind::LongLong)
| (FmtKind::Decimal, IntKind::LongLong)
| (FmtKind::Scientific, IntKind::LongLong) => {
VaArg::c_longdouble(unsafe { untyped.c_longdouble })
}
(FmtKind::AnyNotation, _) | (FmtKind::Decimal, _) | (FmtKind::Scientific, _) => {
VaArg::c_double(unsafe { untyped.c_double })
}
(FmtKind::GetWritten, _) | (FmtKind::Pointer, _) | (FmtKind::String, _) => {
VaArg::pointer(unsafe { untyped.pointer })
}
}
}
}
#[derive(Default)]
pub(crate) struct VaListCache {
pub(crate) args: Vec<VaArg>,
pub(crate) i: usize,
}
impl VaListCache {
pub(crate) unsafe fn get(
&mut self,
i: usize,
ap: &mut VaList,
default: Option<(FmtKind, IntKind)>,
) -> VaArg {
if let Some(&arg) = self.args.get(i) {
// This value is already cached
let mut arg = arg;
if let Some((fmtkind, intkind)) = default {
// ...but as a different type
arg = unsafe { arg.transmute(fmtkind, intkind) };
}
return arg;
}
// Get all values before this value
while self.args.len() < i {
// We can't POSSIBLY know the type if we reach this
// point. Reaching here means there are unused gaps in the
// arguments. Ultimately we'll have to settle down with
// defaulting to c_int.
self.args.push(VaArg::c_int(unsafe { relibc_va_arg!(ap, c_int) }))
}
// Add the value to the cache
self.args.push(match default {
Some((fmtkind, intkind)) => unsafe { VaArg::arg_from(fmtkind, intkind, ap) },
None => VaArg::c_int(unsafe { relibc_va_arg!(ap, c_int) }),
});
// Return the value
self.args[i]
}
}
// ___ _ _ _ _
// |_ _|_ __ ___ _ __ | | ___ _ __ ___ ___ _ __ | |_ __ _| |_(_) ___ _ __ _
// | || '_ ` _ \| '_ \| |/ _ \ '_ ` _ \ / _ \ '_ \| __/ _` | __| |/ _ \| '_ \(_)
// | || | | | | | |_) | | __/ | | | | | __/ | | | || (_| | |_| | (_) | | | |_
// |___|_| |_| |_| .__/|_|\___|_| |_| |_|\___|_| |_|\__\__,_|\__|_|\___/|_| |_(_)
// |_|
enum FmtCase {
Lower,
Upper,
}
// The spelled-out "infinity"/"INFINITY" is also permitted by the standard
static INF_STR_LOWER: &str = "inf";
static INF_STR_UPPER: &str = "INF";
static NAN_STR_LOWER: &str = "nan";
static NAN_STR_UPPER: &str = "NAN";
fn pop_int_raw<T: c_str::Kind>(format: &mut NulStr<T>) -> Option<usize> {
let mut int = None;
while let Some((digit, rest)) = format
.split_first_char()
.and_then(|(d, r)| Some((d.to_digit(10)?, r)))
{
*format = rest;
if int.is_none() {
int = Some(0);
}
*int.as_mut().unwrap() *= 10;
*int.as_mut().unwrap() += digit as usize;
}
int
}
fn pop_index<T: c_str::Kind>(format: &mut NulStr<T>) -> Option<usize> {
// Peek ahead for a positional argument:
let mut format2 = *format;
if let Some(i) = pop_int_raw(&mut format2)
&& let Some(('$', format2)) = format2.split_first_char()
{
*format = format2;
return Some(i);
}
None
}
fn pop_int<T: c_str::Kind>(format: &mut NulStr<T>) -> Option<Number> {
if let Some(('*', rest)) = format.split_first_char() {
*format = rest;
Some(pop_index(format).map(Number::Index).unwrap_or(Number::Next))
} else {
pop_int_raw(format).map(Number::Static)
}
}
fn fmt_int<I, T: c_str::Kind>(fmt: char, i: I) -> String
where
I: fmt::Display + fmt::Octal + fmt::LowerHex + fmt::UpperHex + fmt::Binary,
{
match fmt {
'o' => format!("{:o}", i),
'u' => i.to_string(),
'x' => format!("{:x}", i),
'X' => format!("{:X}", i),
'b' | 'B' if T::IS_THIN_NOT_WIDE => format!("{:b}", i),
_ => panic!("fmt_int should never be called with the fmt {:?}", fmt,),
}
}
fn pad<W: Write>(
w: &mut W,
current_side: bool,
pad_char: u8,
range: Range<usize>,
) -> io::Result<()> {
if current_side {
for _ in range {
w.write_all(&[pad_char])?;
}
}
Ok(())
}
fn float_string(float: c_double, precision: usize, trim: bool, alternate: bool) -> String {
// The Rust format! macro doesn't keep the dot on precision = 0 and alternate = true,
// so we have to perform a fix-up
//
// POSIX.1-2024 says "... if the precision is zero and no '#' flag is present,
// no radix character shall appear."
//
// This case is covered here.
let mut string = format!("{:.p$}", float, p = precision);
//
// Additionally, it says "For a, A, e, E, f, F, g, and G conversion specifiers,
// the result shall always contain a radix character, even if no digits follow
// the radix character."
//
if alternate && precision == 0 {
string.push('.');
} else if trim && string.contains('.') {
let truncate = {
let slice = string.trim_end_matches('0');
let mut truncate = slice.len();
if slice.ends_with('.') {
truncate -= 1;
}
truncate
};
string.truncate(truncate);
}
string
}
fn float_exp(mut float: c_double) -> (c_double, isize) {
let mut exp: isize = 0;
while float.abs() >= 10.0 {
float /= 10.0;
exp += 1;
}
while f64::EPSILON < float.abs() && float.abs() < 1.0 {
float *= 10.0;
exp -= 1;
}
(float, exp)
}
fn fmt_float_exp<W: Write>(
w: &mut W,
exp_fmt: char,
trim: bool,
alternate: bool,
precision: usize,
float: c_double,
exp: isize,
left: bool,
pad_space: usize,
pad_zero: usize,
) -> io::Result<()> {
let mut exp2 = exp;
let mut exp_len = 1;
while exp2 >= 10 {
exp2 /= 10;
exp_len += 1;
}
let string = float_string(float, precision, trim, alternate);
let len = string.len() + 2 + 2.max(exp_len);
pad(w, !left, b' ', len..pad_space)?;
let bytes = if string.starts_with('-') {
w.write_all(b"-")?;
&string.as_bytes()[1..]
} else {
string.as_bytes()
};
pad(w, !left, b'0', len..pad_zero)?;
w.write_all(bytes)?;
write!(w, "{}{:+03}", exp_fmt, exp)?;
pad(w, left, b' ', len..pad_space)?;
Ok(())
}
fn fmt_float_normal<W: Write>(
w: &mut W,
trim: bool,
alternate: bool,
precision: usize,
float: c_double,
left: bool,
pad_space: usize,
pad_zero: usize,
) -> io::Result<usize> {
let string = float_string(float, precision, trim, alternate);
pad(w, !left, b' ', string.len()..pad_space)?;
let bytes = if string.starts_with('-') {
w.write_all(b"-")?;
&string.as_bytes()[1..]
} else {
string.as_bytes()
};
pad(w, true, b'0', string.len()..pad_zero)?;
w.write_all(bytes)?;
pad(w, left, b' ', string.len()..pad_space)?;
Ok(string.len())
}
/// Write ±infinity or ±NaN representation for any floating-point style
fn fmt_float_nonfinite<W: Write>(
w: &mut W,
float: c_double,
case: FmtCase,
left: bool,
pad_space: usize,
pad_zero: usize,
) -> io::Result<()> {
let string = match float.classify() {
FpCategory::Infinite => match case {
FmtCase::Lower => INF_STR_LOWER,
FmtCase::Upper => INF_STR_UPPER,
},
FpCategory::Nan => match case {
FmtCase::Lower => NAN_STR_LOWER,
FmtCase::Upper => NAN_STR_UPPER,
},
_ => {
// This function should only be called with infinite or NaN value.
panic!("fmt_float_nonfinite called with finite float")
}
};
// Infinity is always padded with spaces, rather than zeroes
pad(w, !left, b' ', string.len()..pad_space + pad_zero)?;
if float.is_sign_negative() {
w.write_all(b"-")?;
}
w.write_all(string.as_bytes())?;
pad(w, left, b' ', string.len()..pad_space + pad_zero)?;
Ok(())
}
#[derive(Clone, Copy)]
pub(crate) struct PrintfIter<'a, T: c_str::Kind> {
pub(crate) format: NulStr<'a, T>,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct PrintfArg {
pub(crate) index: Option<usize>,
pub(crate) alternate: bool,
pub(crate) zero: bool,
pub(crate) left: bool,
pub(crate) sign_reserve: bool,
pub(crate) sign_always: bool,
pub(crate) min_width: Number,
pub(crate) precision: Option<Number>,
pub(crate) intkind: IntKind,
pub(crate) fmt: char,
pub(crate) fmtkind: FmtKind,
}
#[derive(Debug)]
pub(crate) enum PrintfFmt<'a, U> {
Plain(&'a [U]),
Arg(PrintfArg),
}
impl<'a, T: c_str::Kind> Iterator for PrintfIter<'a, T> {
type Item = Result<PrintfFmt<'a, T::Char>, ()>;
fn next(&mut self) -> Option<Self::Item> {
// Send PrintfFmt::Plain until the next %
let first_percent = match self.format.find_get_subslice_or_all(b'%') {
Err(([], _)) => return None,
Ok((chunk @ [_, ..], rest)) | Err((chunk @ [_, ..], rest)) => {
self.format = rest;
return Some(Ok(PrintfFmt::Plain(chunk)));
}
Ok(([], rest)) => rest,
};
// at this point the next char must be %
self.format = first_percent.split_first().expect("must be %").1;
let mut peekahead = self.format;
let index = pop_index(&mut peekahead).inspect(|i| {
self.format = peekahead;
});
// Flags:
let mut alternate = false;
let mut zero = false;
let mut left = false;
let mut sign_reserve = false;
let mut sign_always = false;
while let Some((c, rest)) = self.format.split_first_char() {
match c {
'#' => alternate = true,
'0' => zero = true,
'-' => left = true,
' ' => sign_reserve = true,
'+' => sign_always = true,
_ => break,
}
self.format = rest;
}
// Width and precision:
let min_width = pop_int(&mut self.format).unwrap_or(Number::Static(0));
let precision = if let Some(('.', rest)) = self.format.split_first_char() {
self.format = rest;
match pop_int(&mut self.format) {
int @ Some(_) => int,
None => return Some(Err(())),
}
} else {
None
};
// Integer size:
let mut intkind = IntKind::Int;
while let Some((byte, rest)) = self.format.split_first_char() {
intkind = match byte {
'h' => {
if intkind == IntKind::Short || intkind == IntKind::Byte {
IntKind::Byte
} else {
IntKind::Short
}
}
'j' => IntKind::IntMax,
'l' => {
if intkind == IntKind::Long || intkind == IntKind::LongLong {
IntKind::LongLong
} else {
IntKind::Long
}
}
'q' | 'L' => IntKind::LongLong,
't' => IntKind::PtrDiff,
'z' => IntKind::Size,
_ => break,
};
self.format = rest;
}
let Some((fmt, rest)) = self.format.split_first_char() else {
return Some(Err(()));
};
self.format = rest;
let fmtkind = match fmt {
'%' => FmtKind::Percent,
'd' | 'i' => FmtKind::Signed,
'o' | 'u' | 'x' | 'X' => FmtKind::Unsigned,
'b' | 'B' if T::IS_THIN_NOT_WIDE => FmtKind::Unsigned,
'e' | 'E' => FmtKind::Scientific,
'f' | 'F' | 'L' => FmtKind::Decimal,
'g' | 'G' => FmtKind::AnyNotation,
's' => FmtKind::String,
'c' => FmtKind::Char,
'p' => FmtKind::Pointer,
'n' => FmtKind::GetWritten,
'm' if T::IS_THIN_NOT_WIDE => {
// %m is technically for syslog only, but musl and glibc implement it for
// printf because it is difficult and error prone to implement a format
// specifier for just *one* function.
return Some(Ok(PrintfFmt::Plain(
T::chars_from_bytes(
errno::STR_ERROR
.get(platform::ERRNO.get() as usize)
.map(|e| e.as_bytes())
.unwrap_or(b"unknown error"),
)
.expect("string must be thin"),
)));
}
_ => return Some(Err(())),
};
// "For b, B, d, i, o, u, x, and X conversions,
// if a precision is specified, the 0 flag is ignored."
match fmt {
'b' | 'B' | 'd' | 'i' | 'o' | 'u' | 'x' | 'X' => {
if precision.is_some() {
zero = false;
}
}
_ => (),
}
Some(Ok(PrintfFmt::Arg(PrintfArg {
index,
alternate,
zero,
left,
sign_reserve,
sign_always,
min_width,
precision,
intkind,
fmt,
fmtkind,
})))
}
}
pub(crate) unsafe fn inner_printf<T: c_str::Kind>(
w: impl Write,
format: NulStr<T>,
mut ap: VaList,
) -> io::Result<c_int> {
let w = &mut platform::CountingWriter::new(w);
let iterator = PrintfIter { format };
// Pre-fetch vararg types
let mut varargs = VaListCache::default();
let mut positional = BTreeMap::new();
// ^ NOTE: This depends on the sorted order, do not change to HashMap or whatever
for section in iterator {
let arg = match section {
Ok(PrintfFmt::Plain(text)) => continue,
Ok(PrintfFmt::Arg(arg)) => arg,
Err(()) => return Ok(-1),
};
if arg.fmtkind == FmtKind::Percent {
continue;
}
for num in &[arg.min_width, arg.precision.unwrap_or(Number::Static(0))] {
match num {
Number::Next => varargs
.args
.push(VaArg::c_int(unsafe { relibc_va_arg!(ap, c_int) })),
Number::Index(i) => {
positional.insert(i - 1, (FmtKind::Signed, IntKind::Int));
}
Number::Static(_) => (),
}
}
match arg.index {
Some(i) => {
positional.insert(i - 1, (arg.fmtkind, arg.intkind));
}
None => varargs
.args
.push(unsafe { VaArg::arg_from(arg.fmtkind, arg.intkind, &mut ap) }),
}
}
// Make sure, in order, the positional arguments exist with the specified type
for (i, arg) in positional {
unsafe { varargs.get(i, &mut ap, Some(arg)) };
}
// Main loop
for section in iterator {
let arg = match section {
Ok(PrintfFmt::Plain(text)) => {
if T::IS_THIN_NOT_WIDE {
let bytes = T::chars_to_bytes(text).expect("is thin");
w.write_all(bytes)?;
} else {
// TODO: wcsrtombs wrapper
for c in text.iter().filter_map(|u| char::from_u32((*u).into())) {
if let Ok(()) = write!(w, "{}", c) {}; // TODO handle error
}
}
continue;
}
Ok(PrintfFmt::Arg(arg)) => arg,
Err(()) => return Ok(-1),
};
let alternate = arg.alternate;
let zero = arg.zero;
let mut left = arg.left;
let sign_reserve = arg.sign_reserve;
let sign_always = arg.sign_always;
let min_width = unsafe { arg.min_width.resolve(&mut varargs, &mut ap) };
let precision = arg
.precision
.map(|n| unsafe { n.resolve(&mut varargs, &mut ap) })
.filter(|&n| (n as c_int) >= 0);
let pad_zero = if zero { min_width } else { 0 };
let signed_space = match pad_zero {
0 => min_width as isize,
_ => 0,
};
let pad_space = if signed_space < 0 {
left = true;
-signed_space as usize
} else {
signed_space as usize
};
let intkind = arg.intkind;
let fmt = arg.fmt;
let fmtkind = arg.fmtkind;
let fmtcase = match fmt {
'b' if T::IS_THIN_NOT_WIDE => Some(FmtCase::Lower),
'B' if T::IS_THIN_NOT_WIDE => Some(FmtCase::Upper),
'x' | 'f' | 'e' | 'g' => Some(FmtCase::Lower),
'X' | 'F' | 'E' | 'G' => Some(FmtCase::Upper),
_ => None,
};
let index = arg.index.map(|i| i - 1).unwrap_or_else(|| {
if fmtkind == FmtKind::Percent {
0
} else {
let i = varargs.i;
varargs.i += 1;
i
}
});
match fmtkind {
FmtKind::Percent => w.write_all(b"%")?,
FmtKind::Signed => {
let string = match unsafe {
varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind)))
} {
VaArg::c_char(i) => i.to_string(),
VaArg::c_double(i) => panic!("this should not be possible"),
VaArg::c_longdouble(i) => panic!("this should not be possible"),
VaArg::c_int(i) => i.to_string(),
VaArg::c_long(i) => i.to_string(),
VaArg::c_longlong(i) => i.to_string(),
VaArg::c_short(i) => i.to_string(),
VaArg::intmax_t(i) => i.to_string(),
VaArg::pointer(i) => (i as usize).to_string(),
VaArg::ptrdiff_t(i) => i.to_string(),
VaArg::ssize_t(i) => i.to_string(),
VaArg::wint_t(_) => unreachable!("this should not be possible"),
};
let positive = !string.starts_with('-');
let zero = precision == Some(0) && string == "0";
let mut len = string.len();
let mut final_len = string.len().max(precision.unwrap_or(0));
if positive && (sign_reserve || sign_always) {
final_len += 1;
}
if zero {
len = 0;
final_len = 0;
}
pad(w, !left, b' ', final_len..pad_space)?;
let bytes = if positive {
if sign_reserve {
w.write_all(b" ")?;
} else if sign_always {
w.write_all(b"+")?;
}
string.as_bytes()
} else {
w.write_all(b"-")?;
&string.as_bytes()[1..]
};
pad(w, true, b'0', len..precision.unwrap_or(pad_zero))?;
if !zero {
w.write_all(bytes)?;
}
pad(w, left, b' ', final_len..pad_space)?;
}
FmtKind::Unsigned => {
let string = match unsafe {
varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind)))
} {
VaArg::c_char(i) => fmt_int::<_, T>(fmt, i as c_uchar),
VaArg::c_double(i) => panic!("this should not be possible"),
VaArg::c_longdouble(i) => panic!("this should not be possible"),
VaArg::c_int(i) => fmt_int::<_, T>(fmt, i as c_uint),
VaArg::c_long(i) => fmt_int::<_, T>(fmt, i as c_ulong),
VaArg::c_longlong(i) => fmt_int::<_, T>(fmt, i as c_ulonglong),
VaArg::c_short(i) => fmt_int::<_, T>(fmt, i as c_ushort),
VaArg::intmax_t(i) => fmt_int::<_, T>(fmt, i as uintmax_t),
VaArg::pointer(i) => fmt_int::<_, T>(fmt, i as usize),
VaArg::ptrdiff_t(i) => fmt_int::<_, T>(fmt, i as size_t),
VaArg::ssize_t(i) => fmt_int::<_, T>(fmt, i as size_t),
VaArg::wint_t(_) => unreachable!("this should not be possible"),
};
let zero = precision == Some(0) && string == "0";
// If this int is padded out to be larger than it is, don't
// add an extra zero if octal.
let no_precision = precision.map(|pad| pad < string.len()).unwrap_or(true);
let len;
let final_len = if zero {
len = 0;
0
} else {
len = string.len();
len.max(precision.unwrap_or(0))
+ if alternate && string != "0" {
match fmt {
'o' if no_precision => 1,
'x' | 'X' => 2,
'b' | 'B' if T::IS_THIN_NOT_WIDE => 2,
_ => 0,
}
} else {
0
}
};
pad(w, !left, b' ', final_len..pad_space)?;
if alternate && string != "0" {
match fmt {
'o' if no_precision => w.write_all(b"0")?,
'x' => w.write_all(b"0x")?,
'X' => w.write_all(b"0X")?,
'b' if T::IS_THIN_NOT_WIDE => w.write_all(b"0b")?,
'B' if T::IS_THIN_NOT_WIDE => w.write_all(b"0B")?,
_ => (),
}
}
pad(w, true, b'0', len..precision.unwrap_or(pad_zero))?;
if !zero {
w.write_all(string.as_bytes())?;
}
pad(w, left, b' ', final_len..pad_space)?;
}
FmtKind::Scientific => {
let float = match unsafe {
varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind)))
} {
VaArg::c_double(i) => i,
VaArg::c_longdouble(i) => unsafe { relibc_ldtod(&raw const i) },
_ => panic!("this should not be possible"),
};
if float.is_finite() {
let (float, exp) = float_exp(float);
let precision = precision.unwrap_or(6);
fmt_float_exp(
w, fmt, false, alternate, precision, float, exp, left, pad_space, pad_zero,
)?;
} else {
fmt_float_nonfinite(w, float, fmtcase.unwrap(), left, pad_space, pad_zero)?;
}
}
FmtKind::Decimal => {
let float = match unsafe {
varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind)))
} {
VaArg::c_double(i) => i,
VaArg::c_longdouble(i) => unsafe { relibc_ldtod(&raw const i) },
_ => panic!("this should not be possible"),
};
if float.is_finite() {
let precision = precision.unwrap_or(6);
fmt_float_normal(
w, false, alternate, precision, float, left, pad_space, pad_zero,
)?;
} else {
fmt_float_nonfinite(w, float, fmtcase.unwrap(), left, pad_space, pad_zero)?;
}
}
FmtKind::AnyNotation => {
let float = match unsafe {
varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind)))
} {
VaArg::c_double(i) => i,
VaArg::c_longdouble(i) => unsafe { relibc_ldtod(&raw const i) },
_ => panic!("this should not be possible"),
};
if float.is_finite() {
let (log, exp) = float_exp(float);
// TODO: .is_uppercase()?
let exp_fmt = if fmt as u32 & 32 == 32 { 'e' } else { 'E' };
let precision = precision.unwrap_or(6);
let use_exp_format = exp < -4 || exp >= precision as isize;
if use_exp_format {
// Length of integral part will always be 1 here,
// because that's how x/floor(log10(x)) works
let precision = precision.saturating_sub(1);
fmt_float_exp(
w, exp_fmt, !alternate, alternate, precision, log, exp, left,
pad_space, pad_zero,
)?;
} else {
// Length of integral part will be the exponent of
// the unused logarithm, unless the exponent is
// negative which in case the integral part must
// of course be 0, 1 in length
let len = 1 + cmp::max(0, exp) as usize;
let precision = precision.saturating_sub(len);
fmt_float_normal(
w, !alternate, alternate, precision, float, left, pad_space, pad_zero,
)?;
}
} else {
fmt_float_nonfinite(w, float, fmtcase.unwrap(), left, pad_space, pad_zero)?;
}
}
FmtKind::String => {
let ptr = match unsafe {
varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind)))
} {
VaArg::pointer(p) => p,
_ => panic!("this should not be possible"),
}
.cast::<c_char>();
if ptr.is_null() {
w.write_all(b"(null)")?;
} else {
let max = precision.unwrap_or(usize::MAX);
if intkind == IntKind::Long || intkind == IntKind::LongLong {
// Handle wchar_t
let mut ptr = ptr.cast::<wchar_t>();
let mut string = String::new();
while unsafe { *ptr } != 0 {
let c = match char::from_u32(unsafe { *ptr } as _) {
Some(c) => c,
None => {
platform::ERRNO.set(EILSEQ);
return Err(io::last_os_error());
}
};
if string.len() + c.len_utf8() >= max {
break;
}
string.push(c);
ptr = unsafe { ptr.add(1) };
}
pad(w, !left, b' ', string.len()..pad_space)?;
w.write_all(string.as_bytes())?;
pad(w, left, b' ', string.len()..pad_space)?;
} else {
let mut len = 0;
while unsafe { *ptr.add(len) } != 0 && len < max {
len += 1;
}
pad(w, !left, b' ', len..pad_space)?;
w.write_all(unsafe { slice::from_raw_parts(ptr.cast::<u8>(), len) })?;
pad(w, left, b' ', len..pad_space)?;
}
}
}
FmtKind::Char => {
match unsafe { varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) } {
VaArg::c_char(c) => {
pad(w, !left, b' ', 1..pad_space)?;
w.write_all(&[c as u8])?;
pad(w, left, b' ', 1..pad_space)?;
}
VaArg::wint_t(c) => {
let c = match char::from_u32(c as _) {
Some(c) => c,
None => {
platform::ERRNO.set(EILSEQ);
return Err(io::last_os_error());
}
};
let mut buf = [0; 4];
pad(w, !left, b' ', 1..pad_space)?;
w.write_all(c.encode_utf8(&mut buf).as_bytes())?;
pad(w, left, b' ', 1..pad_space)?;
}
_ => unreachable!("this should not be possible"),
}
}
FmtKind::Pointer => {
let ptr = match unsafe {
varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind)))
} {
VaArg::pointer(p) => p,
_ => panic!("this should not be possible"),
};
let mut len = 1;
if ptr.is_null() {
len = "(nil)".len();
} else {
let mut ptr = ptr as usize;
while ptr >= 10 {
ptr /= 10;
len += 1;
}
}
pad(w, !left, b' ', len..pad_space)?;
if ptr.is_null() {
write!(w, "(nil)")?;
} else {
write!(w, "0x{:x}", ptr as usize)?;
}
pad(w, left, b' ', len..pad_space)?;
}
FmtKind::GetWritten => {
let ptr = match unsafe {
varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind)))
} {
VaArg::pointer(p) => p,
_ => panic!("this should not be possible"),
};
match intkind {
IntKind::Byte => unsafe { *(ptr as *mut c_char) = w.written as c_char },
IntKind::Short => unsafe { *(ptr as *mut c_short) = w.written as c_short },
IntKind::Int => unsafe { *(ptr as *mut c_int) = w.written as c_int },
IntKind::Long => unsafe { *(ptr as *mut c_long) = w.written as c_long },
IntKind::LongLong => unsafe {
*(ptr as *mut c_longlong) = w.written as c_longlong
},
IntKind::IntMax => unsafe { *(ptr as *mut intmax_t) = w.written as intmax_t },
IntKind::PtrDiff => unsafe {
*(ptr as *mut ptrdiff_t) = w.written as ptrdiff_t
},
IntKind::Size => unsafe { *(ptr as *mut size_t) = w.written as size_t },
}
}
}
}
Ok(w.written as c_int)
}
/// Implementation of `printf` formatting function, generic over a `writer`
///
/// This implementation in currently compliant over C17 specification (lacking a few one from C23)
/// and contains extensions as well.
///
/// # The Format Specification
/// ```text
/// %[conversion-flags][field-width][precision][length-modifier]<conversion-format>
/// ```
///
/// <div class="warning">
/// ※ : This symbol means it is not implemented yet, but it is defined in the C standard
/// </div>
///
/// ## Conversion Flags
/// Conversion flags are flags that modify the behavior of the [conversion
/// format]. Each one can happen only once per format specifier. They are:
///
/// - `-`: The result of the conversion is left-justified within the field (by default it is
/// right-justified).
/// - `+`: The sign of signed conversions is always prepended to the result of the conversion (by
/// default the result is preceded by minus **only** when it is negative).
/// - ` `(space): If the result of a signed conversion does not start with a sign character, or is
/// empty, space is prepended to the result.
/// - It is ignored if `+` flag is present.
/// - `#`: Alternative form of the conversion is performed. See the documentation for each
/// [conversion format] for details.
/// - `0`: For integer and floating-point number conversions, leading zeros are used to pad the
/// field instead of space characters.
/// - For integer numbers it is ignored if the precision is explicitly specified.
/// - For other conversions using this flag results in undefined behavior.
/// - It is ignored if `-` flag is present.
///
/// ## Field Width
/// Specifies minimum field width. This makes the result to be padded (with spaces by default, with
/// zeroes if `0` conversion flag is specified) if the converted value has fewer characters than the
/// specified width. It can take three forms:
///
/// - `N` where N is a positive integer: Specifies the field width value of `N`.
/// - `*`: The width is specified by an extra argument of type [`int`], which has to appear before
/// the argument to be converted and the [precision] (if specified with `.*`).
/// - If the value of e extra argument is negative, it is interpreted as with `-` [conversion
/// flag], i.e. left-justified result.
/// - `*P$` where P is a positive integer: The width is specified by an extra argument of type
/// [`int`], which has to appear exactly at the position specified by `P`.
/// - This is a popular extension of the C and POSIX standards.
/// - If the value of e extra argument is negative, it is interpreted as with `-` [conversion
/// flag], i.e. left-justified result.
///
/// ## Precision
/// Specifies the precision of the conversion.
///
/// For integer [conversion formats], this specifies the number of digits to appear in the result.
///
/// For float point [conversion formats], this specifies the number of digits to appear after the
/// decimal-point character.
///
/// It can take three forms:
///
/// - `.N` where N is a positive integer: Specifies the precision value of `N`.
/// - `.*`: The precision is specified by an extra argument of type [`int`], which has to appear
/// before the argument to be converted and after the the [field width] (if specified with `*`).
/// - If the value of the extra argument is negative, it is interpreted as if the precision were
/// omitted.
/// - `.*P$` where P is a positive integer: The precision is specified by an extra argument of type
/// [`int`], which has to appear exactly at the position specified by `P`.
/// - This is an popular extension of the C and POSIX standards.
/// - If the value of e extra argument is negative, it is interpreted as if the precision were
/// omitted.
///
/// ## Length Modifier
/// Specifies the size of the argument. In combination with the [conversion format], it specifies
/// the type of the corresponding argument.
///
/// - `hh`: Byte size
/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`)
/// - Works with written number conversion format (`n`)
/// - `h`: Short size
/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`)
/// - Works with written number conversion format (`n`)
/// - `l`: Long size
/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`)
/// - Works with character conversion format (`c`)
/// - Works with string conversion format (`s`)
/// - Works with written number conversion format (`n`)
/// - Works with float conversion formats (`f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`) (C99)
/// - `ll`: Long long size
/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`)
/// - Works with written number conversion format (`n`)
/// - `j`: Maximum width
/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`)
/// - Works with written number conversion format (`n`)
/// - `z`: Pointer width size
/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`)
/// - Works with written number conversion format (`n`)
/// - `t`: Pointer diff width
/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`)
/// - Works with written number conversion format (`n`)
/// - `wN` (C23 ※): Specifies that the size should be N bits width version of the supported
/// conversion format.
/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`)
/// - The supported values of `N` must be the same as the widths specified in `stdint.h`
/// - `wfN` (C23 ※): Specifies that the size should be the fast N bits width version of the
/// supported conversion format.
/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`)
/// - The supported values of `N` must be the same as the widths specified in `stdint.h`
/// - `L`: Long double size
/// - Works with float conversion formats (`f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`)
/// - `H` (C23 ※): _Decimal32 size
/// - Works with float conversion formats (`f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`)
/// - `D` (C23 ※): _Decimal64 size
/// - Works with float conversion formats (`f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`)
/// - `DD` (C23 ※): _Decimal128 size
/// - Works with float conversion formats (`f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`)
///
/// ## Conversion Format
/// Specifies the conversion format as one of the following:
///
/// - `%`: Writes a percent symbol. The full conversion format must be `%%`.
/// - `c`: Writes as single character
/// - Without length modifier:
/// - The argument is first converted to [`unsigned char`]
/// - With `l` length modifier:
/// - The argument is first converted to a character string as if by `%ls` with a array of 2
/// [`wchar_t`] argument.
/// - `s`: Writes a character string
/// - The argument is a pointer to the first character
/// - The [precision] specifies the maximum number of bytes to be written. If not specified,
/// writes up to the first null character found.
/// - `d` and `i`: Writes a decimal representation of a signed integer
/// - The [precision] specifies the minimal number to appear (defaults to `1`).
/// - If the precision is zero and the value to be written is also zero, the result is no
/// characters written.
/// - `u`: Writes the decimal representation of a unsigned integer
/// - The [precision] specifies the minimal number to appear (defaults to `1`).
/// - If the precision is zero and the value to be written is also zero, the result is no
/// characters written.
/// - `o`: Writes the octal representation of a unsigned integer.
/// - The [precision] specifies the minimal number to appear (defaults to `1`).
/// - If the precision is zero and the value to be written is also zero, the result is no
/// characters written.
/// - The alternative representation includes a leading `0`.
/// - The types are the same as `u`
/// - `x`: Writes the hexadecimal representation of a unsigned integer with lowercase characters.
/// - The [precision] specifies the minimal number to appear (defaults to `1`).
/// - If the precision is zero and the value to be written is also zero, the result is no
/// characters written.
/// - The alternative representation includes a leading `0x`.
/// - The types are the same as `u`
/// - `X`: Writes the hexadecimal representation of a unsigned integer with uppercase characters.
/// - The [precision] specifies the minimal number to appear (defaults to `1`).
/// - If the precision is zero and the value to be written is also zero, the result is no
/// characters written.
/// - The alternative representation includes a leading `0X`.
/// - The types are the same as `u`.
/// - `b` | `B` (C23): Writes the binary representation of a unsigned integer.
/// - The [precision] specifies the minimal number to appear (defaults to `1`).
/// - If the precision is zero and the value to be written is also zero, the result is no
/// characters written.
/// - The alternative representation includes a leading `0b` and `0B`, respectively.
/// - The types are the same as `u`.
/// - `f` | `F`: Writes the decimal representation of a float point number.
/// - The [precision] specifies the exact number of digits to appear after the decimal point
/// character (defaults to `6`).
/// - The alternative representation, the decimal point character is written even if no digits
/// follow it.
/// - `e` | `E`: Writes the float point number with the decimal exponential notation (\[-\]d.ddd
/// **e**±dd | \[-\]d.ddd**E**±dd)
/// - The [precision] specifies the exact number of digits to appear after the decimal point
/// character (defaults to `6`).
/// - The exponent contains at least two digits, more digits are used only if necessary.
/// - If the value is zero, the exponent is also zero.
/// - The alternative representation: decimal point character is written even if no digits follow
/// it.
/// - `a` | `A`: Writes the float point number with the hexadecimal exponential notation (\[-\]
/// **0x**h.hhh**p**±d | \[-\]**0X**h.hhh**P**±d)
/// - The [precision] specifies the exact number of digits to appear after the hexadecimal point
/// character (defaults to `6`).
/// - If the value is zero, the exponent is also zero.
/// - The alternative representation: decimal point character is written even if no digits follow
/// it.
/// - `g` | `G`: Writes the float point number to decimal or decimal exponent notation depending on
/// the value and the [precision].
/// - Let `P` equal the precision if nonzero, `6` if the precision is not specified, or `1` if the
/// precision is `0`. Then, if a conversion with style `E` would have an exponent of `X`:
/// - If `P > X ≥ 4`, the conversion is with the format `f` and precision `P 1 X`.
/// - Otherwise, the conversion is with the format `e` or `E` and precision `P 1`.
/// - Unless alternative representation is requested, the trailing zeros are removed. Also the
/// decimal point character is removed if no fractional part is left.
/// - `n`: Writes the number of characters written in the call into the argument pointer
/// - It can not contain any [conversion flag], [field width], or [precision].
/// - `p`: Writes an implementation defined character sequence defining a pointer.
///
/// ### Types
/// The types expected by the format string can change with the [length modifier].
///
/// For the `c`:
/// - Without length modifier: [`int`]
/// - With `l` length modifier: [`wint_t`]
///
/// For the `s`:
/// - Without length modifier: pointer to [`char`] (`char*`, `const char*`)
/// - With `l` length modifier: pointer to [`wchar_t`] (`wchar_t*`, `const wchar_t*`)
///
/// For the `d` and `i`:
/// - Without length modifier: [`int`]
/// - With `hh` length modifier: [`signed char`]
/// - With `h` length modifier: [`short`]
/// - With `l` length modifier: [`long`]
/// - With `ll` length modifier: [`long long`]
/// - With `j` length modifier: [`intmax_t`]
/// - With `z` length modifier: [`ssize_t`]
/// - With `t` length modifier: [`ptrdiff_t`]
///
/// For the `u`, `o`, `x`, `X`, `b`, `B`:
/// - Without length modifier: [`unsigned int`]
/// - With `hh` length modifier: [`unsigned char`]
/// - With `h` length modifier: [`unsigned short`]
/// - With `l` length modifier: [`unsigned long`]
/// - With `ll` length modifier: [`unsigned long long`]
/// - With `j` length modifier: [`uintmax_t`]
/// - With `z` length modifier: [`size_t`]
/// - With `t` length modifier: [`unsigned ptrdiff_t`]
///
/// For the `f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`:
/// - Without length modifier: [`double`]
/// - With `l` length modifier: [`double`]
/// - With `L` length modifier: `long double`
/// - With `H` length modifier (C23 ※): `_Decimal32`
/// - With `D` length modifier (C23 ※): `_Decimal64`
/// - With `DD` length modifier (C23 ※): `_Decimal128`
///
/// For the `n`
/// - Without length modifier: pointer to [`int`] (`int*`)
/// - With `hh` length modifier: pointer to [`signed char`] (`signed char*`)
/// - With `h` length modifier: pointer to [`short`] (`short*`)
/// - With `l` length modifier: pointer to [`long`] (`long*`)
/// - With `ll` length modifier: pointer to [`long long`] (`long long*`)
/// - With `j` length modifier: pointer to [`intmax_t`] (`intmax_t*`)
/// - With `z` length modifier: pointer to [`ssize_t`] (`ssize_t*`)
/// - With `t` length modifier: pointer to [`ptrdiff_t`] (`ptrdiff_t*`)
///
/// For the `p`, it must always be a pointer to [`void`] (`void*` | `const void*`)
///
/// [precision]: #precision
/// [field width]: #field-width
/// [length modifier]: #length-modifier
/// [conversion format]: #conversion-format
/// [`int`]: c_int
/// [`unsigned char`]: c_uchar
/// [`unsigned short`]: c_ushort
/// [`unsigned int`]: c_uint
/// [`unsigned long`]: c_ulong
/// [`unsigned long long`]: c_ulonglong
/// [`unsigned ptrdiff_t`]: ptrdiff_t
/// [`wchar_t`]: wchar_t
/// [`char`]: c_char
/// [`signed char`]: c_schar
/// [`short`]: c_short
/// [`long`]: c_long
/// [`long long`]: c_longlong
/// [`double`]: c_double
/// [`long double`]: c_longdouble
/// [`void`]: c_void
///
/// # Safety
/// Behavior is undefined if any of the following conditions are violated:
/// - `ap` must follow the safety contract of variable arguments of C.
pub unsafe fn printf(w: impl Write, format: CStr, ap: VaList) -> c_int {
unsafe { inner_printf::<c_str::Thin>(w, format, ap).unwrap_or(-1) }
}