// 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, 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(format: &mut NulStr) -> Option { 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(format: &mut NulStr) -> Option { // 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(format: &mut NulStr) -> Option { 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(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: &mut W, current_side: bool, pad_char: u8, range: Range, ) -> 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: &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: &mut W, trim: bool, alternate: bool, precision: usize, float: c_double, left: bool, pad_space: usize, pad_zero: usize, ) -> io::Result { 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: &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, 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, 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, ()>; fn next(&mut self) -> Option { // 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( w: impl Write, format: NulStr, mut ap: VaList, ) -> io::Result { 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::(); 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::(); 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::(), 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] /// ``` /// ///
/// ※ : This symbol means it is not implemented yet, but it is defined in the C standard ///
/// /// ## 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::(w, format, ap).unwrap_or(-1) } }