From 6dc2606886ddf58bcd07c85fbdd0e281be658b89 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Mon, 6 Oct 2025 10:25:01 +0200 Subject: [PATCH 1/6] Expand CStr to make PrintfIter fully safe. --- src/c_str.rs | 63 ++++++- src/header/stdio/mod.rs | 18 +- src/header/stdio/printf.rs | 290 ++++++++++++++++---------------- src/header/sys_syslog/logger.rs | 2 +- src/header/wchar/wprintf.rs | 1 + 5 files changed, 217 insertions(+), 157 deletions(-) diff --git a/src/c_str.rs b/src/c_str.rs index 30e69c9452..0dafbc1f51 100644 --- a/src/c_str.rs +++ b/src/c_str.rs @@ -8,11 +8,11 @@ use alloc::{ }; use crate::{ - header::string::{strchr, strlen}, + header::string::{strchr, strchrnul, strlen}, platform::types::c_char, }; -/// C string wrapper, guaranteed to be +/// Safe wrapper for immutable borrowed C strings, guaranteed to be the same layout as `*const u8`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct CStr<'a> { @@ -37,6 +37,50 @@ impl<'a> CStr<'a> { Some(Self::from_ptr(ptr)) } } + /// Look for the closest occurence of `c`, and if found, split the string into a slice up to + /// that byte and a `CStr` starting at that byte. + #[inline] + #[doc(alias = "strchrnul")] + pub fn find_get_subslice_or_all(self, c: u8) -> Result<(&'a [u8], Self), (&'a [u8], Self)> { + // SAFETY: strchrnul expects self.as_ptr() to be valid up to and including its last NUL + // byte + let found = unsafe { strchrnul(self.as_ptr(), c.into()) }; + + // SAFETY: the pointer returned from strchrnul is always a substring of this string, and + // hence always valid as a CStr. + let found = unsafe { Self::from_ptr(found) }; + let until = unsafe { self.slice_until_substr(found) }; + + if found.first() == 0 { + // The character was not found, and we got the end of the string instead. + Err((until, found)) + } else { + Ok((until, found)) + } + } + /// # Safety + /// + /// `substr` must be contained within `self` + #[inline] + pub unsafe fn slice_until_substr(self, substr: CStr<'_>) -> &'a [u8] { + let index = unsafe { + // SAFETY: the sub-pointer as returned by strchr must be derived from the same + // allocation + substr.as_ptr().offset_from(self.as_ptr()) as usize + }; + unsafe { core::slice::from_raw_parts(self.as_ptr().cast::(), index) } + } + /// Look for the closest occurence of `c`, and if found, split the string into a slice up to + /// that byte and a `CStr` starting at that byte. + #[inline] + pub fn find_get_subslice(self, c: u8) -> Option<(&'a [u8], Self)> { + let rest = self.find(c)?; + + // SAFETY: the output of strchr is obviously a substring if it doesn't return NULL + Some((unsafe { self.slice_until_substr(rest) }, rest)) + } + /// Look for the closest occurence of `c`, and return a new string starting at that byte if + /// found. #[doc(alias = "strchr")] #[inline] pub fn find(self, c: u8) -> Option { @@ -49,6 +93,8 @@ impl<'a> CStr<'a> { Self::from_nullable_ptr(ret) } } + // TODO: strrchr, strchrnul wrappers + #[inline] pub fn contains(self, c: u8) -> bool { self.find(c).is_some() @@ -61,6 +107,16 @@ impl<'a> CStr<'a> { self.ptr.read() as u8 } } + /// Split this string into `Some((first_byte, string_after_that))` or `None` if empty. + #[inline] + pub fn split_first(self) -> Option<(u8, CStr<'a>)> { + if self.first() == 0 { + return None; + } + Some((self.first(), unsafe { + CStr::from_ptr(self.as_ptr().add(1)) + })) + } pub fn to_bytes_with_nul(self) -> &'a [u8] { unsafe { // SAFETY: The string must be valid at least until (and including) the NUL byte. @@ -108,8 +164,9 @@ impl<'a> CStr<'a> { pub fn count_bytes(&self) -> usize { self.to_bytes().len() } + #[inline] pub fn is_empty(&self) -> bool { - self.count_bytes() == 0 + self.first() == 0 } } diff --git a/src/header/stdio/mod.rs b/src/header/stdio/mod.rs index 9300379543..510f325ecc 100644 --- a/src/header/stdio/mod.rs +++ b/src/header/stdio/mod.rs @@ -1194,7 +1194,7 @@ pub unsafe extern "C" fn vfprintf(file: *mut FILE, format: *const c_char, ap: va return -1; } - printf::printf(&mut *file, format, ap) + printf::printf(&mut *file, CStr::from_ptr(format), ap) } #[unsafe(no_mangle)] pub unsafe extern "C" fn fprintf( @@ -1213,7 +1213,7 @@ pub unsafe extern "C" fn vdprintf(fd: c_int, format: *const c_char, ap: va_list) // borrowing the file descriptor here f.reference = true; - printf::printf(f, format, ap) + printf::printf(f, CStr::from_ptr(format), ap) } #[unsafe(no_mangle)] pub unsafe extern "C" fn dprintf(fd: c_int, format: *const c_char, mut __valist: ...) -> c_int { @@ -1236,7 +1236,7 @@ pub unsafe extern "C" fn vasprintf( ap: va_list, ) -> c_int { let mut alloc_writer = CVec::new(); - let ret = printf::printf(&mut alloc_writer, format, ap); + let ret = printf::printf(&mut alloc_writer, CStr::from_ptr(format), ap); alloc_writer.push(0).unwrap(); alloc_writer.shrink_to_fit().unwrap(); *strp = alloc_writer.leak() as *mut c_char; @@ -1260,7 +1260,7 @@ pub unsafe extern "C" fn vsnprintf( ) -> c_int { printf::printf( &mut platform::StringWriter(s as *mut u8, n as usize), - format, + CStr::from_ptr(format), ap, ) } @@ -1273,14 +1273,18 @@ pub unsafe extern "C" fn snprintf( ) -> c_int { printf::printf( &mut platform::StringWriter(s as *mut u8, n as usize), - format, + CStr::from_ptr(format), __valist.as_va_list(), ) } #[unsafe(no_mangle)] pub unsafe extern "C" fn vsprintf(s: *mut c_char, format: *const c_char, ap: va_list) -> c_int { - printf::printf(&mut platform::UnsafeStringWriter(s as *mut u8), format, ap) + printf::printf( + &mut platform::UnsafeStringWriter(s as *mut u8), + CStr::from_ptr(format), + ap, + ) } #[unsafe(no_mangle)] pub unsafe extern "C" fn sprintf( @@ -1290,7 +1294,7 @@ pub unsafe extern "C" fn sprintf( ) -> c_int { printf::printf( &mut platform::UnsafeStringWriter(s as *mut u8), - format, + CStr::from_ptr(format), __valist.as_va_list(), ) } diff --git a/src/header/stdio/printf.rs b/src/header/stdio/printf.rs index 56cd071364..367ab25c0f 100644 --- a/src/header/stdio/printf.rs +++ b/src/header/stdio/printf.rs @@ -1,4 +1,8 @@ -use crate::io::{self, Write}; +// TODO: generalize with wprintf impl +use crate::{ + c_str::CStr, + io::{self, Write}, +}; use alloc::{ collections::BTreeMap, string::{String, ToString}, @@ -283,10 +287,13 @@ static INF_STR_UPPER: &str = "INF"; static NAN_STR_LOWER: &str = "nan"; static NAN_STR_UPPER: &str = "NAN"; -unsafe fn pop_int_raw(format: &mut *const u8) -> Option { +fn pop_int_raw(format: &mut CStr) -> Option { let mut int = None; - while let Some(digit) = (**format as char).to_digit(10) { - *format = format.add(1); + while let Some((digit, rest)) = format + .split_first() + .and_then(|(d, r)| Some(((d as char).to_digit(10)?, r))) + { + *format = rest; if int.is_none() { int = Some(0); } @@ -295,27 +302,27 @@ unsafe fn pop_int_raw(format: &mut *const u8) -> Option { } int } -unsafe fn pop_index(format: &mut *const u8) -> Option { +fn pop_index(format: &mut CStr) -> Option { // Peek ahead for a positional argument: let mut format2 = *format; if let Some(i) = pop_int_raw(&mut format2) { - if *format2 == b'$' { - *format = format2.add(1); + if let Some((b'$', format2)) = format2.split_first() { + *format = format2; return Some(i); } } None } -unsafe fn pop_int(format: &mut *const u8) -> Option { - if **format == b'*' { - *format = format.add(1); +fn pop_int(format: &mut CStr) -> Option { + if let Some((b'*', rest)) = format.split_first() { + *format = rest; Some(pop_index(format).map(Number::Index).unwrap_or(Number::Next)) } else { pop_int_raw(format).map(Number::Static) } } -unsafe fn fmt_int(fmt: u8, i: I) -> String +fn fmt_int(fmt: u8, i: I) -> String where I: fmt::Display + fmt::Octal + fmt::LowerHex + fmt::UpperHex + fmt::Binary, { @@ -327,7 +334,7 @@ where b'b' | b'B' => format!("{:b}", i), _ => panic!( "fmt_int should never be called with the fmt {:?}", - fmt as char + fmt as char, ), } } @@ -463,8 +470,8 @@ fn fmt_float_nonfinite(w: &mut W, float: c_double, case: FmtCase) -> i } #[derive(Clone, Copy)] -struct PrintfIter { - format: *const u8, +struct PrintfIter<'a> { + format: CStr<'a>, } #[derive(Clone, Copy, Debug)] struct PrintfArg { @@ -481,147 +488,139 @@ struct PrintfArg { fmtkind: FmtKind, } #[derive(Debug)] -enum PrintfFmt { - Plain(&'static [u8]), +enum PrintfFmt<'a> { + Plain(&'a [u8]), Arg(PrintfArg), } -impl Iterator for PrintfIter { - type Item = Result; +impl<'a> Iterator for PrintfIter<'a> { + type Item = Result, ()>; fn next(&mut self) -> Option { - unsafe { - // Send PrintfFmt::Plain until the next % - let mut len = 0; - while *self.format.add(len) != 0 && *self.format.add(len) != b'%' { - len += 1; + // 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))); } - if len > 0 { - let slice = slice::from_raw_parts(self.format as *const u8, len); - self.format = self.format.add(len); - return Some(Ok(PrintfFmt::Plain(slice))); + 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).map(|i| { + self.format = peekahead; + i + }); + + // 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() { + match c { + b'#' => alternate = true, + b'0' => zero = true, + b'-' => left = true, + b' ' => sign_reserve = true, + b'+' => sign_always = true, + _ => break, } - self.format = self.format.add(len); - if *self.format == 0 { - return None; - } - - // *self.format is guaranteed to be '%' at this point - self.format = self.format.add(1); - - let mut peekahead = self.format; - let index = pop_index(&mut peekahead).map(|i| { - self.format = peekahead; - i - }); - - // Flags: - let mut alternate = false; - let mut zero = false; - let mut left = false; - let mut sign_reserve = false; - let mut sign_always = false; - - loop { - match *self.format { - b'#' => alternate = true, - b'0' => zero = true, - b'-' => left = true, - b' ' => sign_reserve = true, - b'+' => sign_always = true, - _ => break, - } - self.format = self.format.add(1); - } - - // Width and precision: - let min_width = pop_int(&mut self.format).unwrap_or(Number::Static(0)); - let precision = if *self.format == b'.' { - self.format = self.format.add(1); - match pop_int(&mut self.format) { - int @ Some(_) => int, - None => return Some(Err(())), - } - } else { - None - }; - - // Integer size: - let mut intkind = IntKind::Int; - loop { - intkind = match *self.format { - b'h' => { - if intkind == IntKind::Short || intkind == IntKind::Byte { - IntKind::Byte - } else { - IntKind::Short - } - } - b'j' => IntKind::IntMax, - b'l' => { - if intkind == IntKind::Long || intkind == IntKind::LongLong { - IntKind::LongLong - } else { - IntKind::Long - } - } - b'q' | b'L' => IntKind::LongLong, - b't' => IntKind::PtrDiff, - b'z' => IntKind::Size, - _ => break, - }; - - self.format = self.format.add(1); - } - let fmt = *self.format; - let fmtkind = match fmt { - b'%' => FmtKind::Percent, - b'd' | b'i' => FmtKind::Signed, - b'o' | b'u' | b'x' | b'X' | b'b' | b'B' => FmtKind::Unsigned, - b'e' | b'E' => FmtKind::Scientific, - b'f' | b'F' => FmtKind::Decimal, - b'g' | b'G' => FmtKind::AnyNotation, - b's' => FmtKind::String, - b'c' => FmtKind::Char, - b'p' => FmtKind::Pointer, - b'n' => FmtKind::GetWritten, - b'm' => { - // %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. - self.format = self.format.add(1); - return Some(Ok(PrintfFmt::Plain( - errno::STR_ERROR - .get(platform::ERRNO.get() as usize) - .map(|e| e.as_bytes()) - .unwrap_or(b"unknown error"), - ))); - } - _ => return Some(Err(())), - }; - self.format = self.format.add(1); - - Some(Ok(PrintfFmt::Arg(PrintfArg { - index, - alternate, - zero, - left, - sign_reserve, - sign_always, - min_width, - precision, - intkind, - fmt, - fmtkind, - }))) + self.format = rest; } + + // Width and precision: + let min_width = pop_int(&mut self.format).unwrap_or(Number::Static(0)); + let precision = if let Some((b'.', rest)) = self.format.split_first() { + 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() { + intkind = match byte { + b'h' => { + if intkind == IntKind::Short || intkind == IntKind::Byte { + IntKind::Byte + } else { + IntKind::Short + } + } + b'j' => IntKind::IntMax, + b'l' => { + if intkind == IntKind::Long || intkind == IntKind::LongLong { + IntKind::LongLong + } else { + IntKind::Long + } + } + b'q' | b'L' => IntKind::LongLong, + b't' => IntKind::PtrDiff, + b'z' => IntKind::Size, + _ => break, + }; + + self.format = rest; + } + let Some((fmt, rest)) = self.format.split_first() else { + return Some(Err(())); + }; + self.format = rest; + let fmtkind = match fmt { + b'%' => FmtKind::Percent, + b'd' | b'i' => FmtKind::Signed, + b'o' | b'u' | b'x' | b'X' | b'b' | b'B' => FmtKind::Unsigned, + b'e' | b'E' => FmtKind::Scientific, + b'f' | b'F' => FmtKind::Decimal, + b'g' | b'G' => FmtKind::AnyNotation, + b's' => FmtKind::String, + b'c' => FmtKind::Char, + b'p' => FmtKind::Pointer, + b'n' => FmtKind::GetWritten, + b'm' => { + // %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( + errno::STR_ERROR + .get(platform::ERRNO.get() as usize) + .map(|e| e.as_bytes()) + .unwrap_or(b"unknown error"), + ))); + } + _ => return Some(Err(())), + }; + + Some(Ok(PrintfFmt::Arg(PrintfArg { + index, + alternate, + zero, + left, + sign_reserve, + sign_always, + min_width, + precision, + intkind, + fmt, + fmtkind, + }))) } } -unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> io::Result { +unsafe fn inner_printf(w: W, format: CStr, mut ap: VaList) -> io::Result { let w = &mut platform::CountingWriter::new(w); - let iterator = PrintfIter { - format: format as *const u8, - }; + let iterator = PrintfIter { format }; // Pre-fetch vararg types let mut varargs = VaListCache::default(); @@ -1258,8 +1257,7 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> /// /// # Safety /// Behavior is undefined if any of the following conditions are violated: -/// - `format` must point to valid null-terminated string. /// - `ap` must follow the safety contract of variable arguments of C. -pub unsafe fn printf(w: W, format: *const c_char, ap: VaList) -> c_int { +pub unsafe fn printf(w: W, format: CStr, ap: VaList) -> c_int { inner_printf(w, format, ap).unwrap_or(-1) } diff --git a/src/header/sys_syslog/logger.rs b/src/header/sys_syslog/logger.rs index 7b2ae6b684..151a194017 100644 --- a/src/header/sys_syslog/logger.rs +++ b/src/header/sys_syslog/logger.rs @@ -82,7 +82,7 @@ impl LogParams { // SAFETY: // * Assumes caller passed in a valid C string; printf should handle that invariant. // * `buffer` grows to fit the formatted string. - unsafe { printf(&mut buffer, message.as_ptr(), ap) }; + unsafe { printf(&mut buffer, message, ap) }; buffer.extend(b"\n\0"); if self.maybe_open_logger().is_ok() { diff --git a/src/header/wchar/wprintf.rs b/src/header/wchar/wprintf.rs index 0068af6502..a4ffb9d03b 100644 --- a/src/header/wchar/wprintf.rs +++ b/src/header/wchar/wprintf.rs @@ -1,3 +1,4 @@ +// TODO: generalize with printf impl use crate::io::{self, Write}; use alloc::{ collections::BTreeMap, From 9bc464686f4c9ca7d5fdb3499c4b65fee098436f Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Mon, 6 Oct 2025 10:53:00 +0200 Subject: [PATCH 2/6] Generalize CStr into NulStr to allow wide strings. --- src/c_str.rs | 199 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 137 insertions(+), 62 deletions(-) diff --git a/src/c_str.rs b/src/c_str.rs index 0dafbc1f51..c8f1134238 100644 --- a/src/c_str.rs +++ b/src/c_str.rs @@ -7,30 +7,79 @@ use alloc::{ string::String, }; -use crate::{ - header::string::{strchr, strchrnul, strlen}, - platform::types::c_char, -}; +use crate::platform::types::{c_char, c_int, wchar_t}; + +mod private { + pub trait Sealed {} +} +#[derive(Clone, Copy, Debug)] +pub enum Thin {} + +//#[derive(Clone, Copy, Debug)] +//pub enum Wide {} + +impl private::Sealed for Thin {} +//impl private::Sealed for Wide {} + +pub trait Kind: private::Sealed + Copy { + /// c_char or wchar_t + type C: Copy; + // u8 or u32 + type Char: Copy + Into + PartialEq; + + const NUL: Self::Char; + + fn r2c(c: Self::Char) -> Self::C; + fn c2r(c: Self::C) -> Self::Char; + + unsafe fn strlen(s: *const Self::C) -> usize; + unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C; + unsafe fn strchrnul(s: *const Self::C, c: Self::C) -> *const Self::C; +} +impl Kind for Thin { + type C = c_char; + type Char = u8; + + const NUL: Self::Char = 0; + + unsafe fn strlen(s: *const c_char) -> usize { + crate::header::string::strlen(s) + } + unsafe fn strchr(s: *const c_char, c: c_char) -> *const c_char { + crate::header::string::strchr(s, c.into()) + } + unsafe fn strchrnul(s: *const c_char, c: c_char) -> *const c_char { + crate::header::string::strchrnul(s, c.into()) + } + fn r2c(c: u8) -> c_char { + c as _ + } + fn c2r(c: c_char) -> u8 { + c as _ + } +} /// Safe wrapper for immutable borrowed C strings, guaranteed to be the same layout as `*const u8`. #[derive(Clone, Copy)] #[repr(transparent)] -pub struct CStr<'a> { - ptr: NonNull, +pub struct NulStr<'a, T: Kind> { + ptr: NonNull, _marker: PhantomData<&'a [u8]>, } +pub type CStr<'a> = NulStr<'a, Thin>; +//pub type WStr<'a> = NulStr<'a, Wide>; -impl<'a> CStr<'a> { +impl<'a, T: Kind> NulStr<'a, T> { /// Safety /// /// The ptr must be valid up to and including the first NUL byte from the base ptr. - pub const unsafe fn from_ptr(ptr: *const c_char) -> Self { + pub const unsafe fn from_ptr(ptr: *const T::C) -> Self { Self { - ptr: NonNull::new_unchecked(ptr as *mut c_char), + ptr: NonNull::new_unchecked(ptr as *mut T::C), _marker: PhantomData, } } - pub unsafe fn from_nullable_ptr(ptr: *const c_char) -> Option { + pub unsafe fn from_nullable_ptr(ptr: *const T::C) -> Option { if ptr.is_null() { None } else { @@ -41,17 +90,20 @@ impl<'a> CStr<'a> { /// that byte and a `CStr` starting at that byte. #[inline] #[doc(alias = "strchrnul")] - pub fn find_get_subslice_or_all(self, c: u8) -> Result<(&'a [u8], Self), (&'a [u8], Self)> { + pub fn find_get_subslice_or_all( + self, + c: T::Char, + ) -> Result<(&'a [T::Char], Self), (&'a [T::Char], Self)> { // SAFETY: strchrnul expects self.as_ptr() to be valid up to and including its last NUL // byte - let found = unsafe { strchrnul(self.as_ptr(), c.into()) }; + let found = unsafe { T::strchrnul(self.as_ptr(), T::r2c(c)) }; // SAFETY: the pointer returned from strchrnul is always a substring of this string, and // hence always valid as a CStr. let found = unsafe { Self::from_ptr(found) }; let until = unsafe { self.slice_until_substr(found) }; - if found.first() == 0 { + if found.first() == T::NUL { // The character was not found, and we got the end of the string instead. Err((until, found)) } else { @@ -62,18 +114,18 @@ impl<'a> CStr<'a> { /// /// `substr` must be contained within `self` #[inline] - pub unsafe fn slice_until_substr(self, substr: CStr<'_>) -> &'a [u8] { + pub unsafe fn slice_until_substr(self, substr: NulStr<'_, T>) -> &'a [T::Char] { let index = unsafe { // SAFETY: the sub-pointer as returned by strchr must be derived from the same // allocation substr.as_ptr().offset_from(self.as_ptr()) as usize }; - unsafe { core::slice::from_raw_parts(self.as_ptr().cast::(), index) } + unsafe { core::slice::from_raw_parts(self.as_ptr().cast::(), index) } } /// Look for the closest occurence of `c`, and if found, split the string into a slice up to /// that byte and a `CStr` starting at that byte. #[inline] - pub fn find_get_subslice(self, c: u8) -> Option<(&'a [u8], Self)> { + pub fn find_get_subslice(self, c: T::Char) -> Option<(&'a [T::Char], Self)> { let rest = self.find(c)?; // SAFETY: the output of strchr is obviously a substring if it doesn't return NULL @@ -83,11 +135,11 @@ impl<'a> CStr<'a> { /// found. #[doc(alias = "strchr")] #[inline] - pub fn find(self, c: u8) -> Option { + pub fn find(self, c: T::Char) -> Option { unsafe { // SAFETY: the only requirement is for self.as_ptr() to be valid up to and including // the nearest NUL byte, which this type requires - let ret = strchr(self.as_ptr(), c.into()); + let ret = T::strchr(self.as_ptr(), T::r2c(c)); // SAFETY: strchr must either return NULL (not found) or a substring of self, which can // never exceed the nearest NUL byte of self Self::from_nullable_ptr(ret) @@ -96,82 +148,105 @@ impl<'a> CStr<'a> { // TODO: strrchr, strchrnul wrappers #[inline] - pub fn contains(self, c: u8) -> bool { + pub fn contains(self, c: T::Char) -> bool { self.find(c).is_some() } #[inline] - pub fn first(self) -> u8 { + pub fn first(self) -> T::Char { unsafe { // SAFETY: Self must be valid up to and including its nearest NUL byte, which certainly // implies its readable length is nonzero (string is empty if this first byte is 0). - self.ptr.read() as u8 + T::c2r(self.ptr.read()) } } /// Split this string into `Some((first_byte, string_after_that))` or `None` if empty. #[inline] - pub fn split_first(self) -> Option<(u8, CStr<'a>)> { - if self.first() == 0 { + pub fn split_first(self) -> Option<(T::Char, Self)> { + if self.first() == T::NUL { return None; } Some((self.first(), unsafe { - CStr::from_ptr(self.as_ptr().add(1)) + Self::from_ptr(self.as_ptr().add(1)) })) } - pub fn to_bytes_with_nul(self) -> &'a [u8] { + pub fn to_chars_with_nul(self) -> &'a [T::Char] { unsafe { // SAFETY: The string must be valid at least until (and including) the NUL byte. - let len = strlen(self.ptr.as_ptr()); + let len = T::strlen(self.ptr.as_ptr()); core::slice::from_raw_parts(self.ptr.as_ptr().cast(), len + 1) } } - pub fn to_bytes(self) -> &'a [u8] { - let s = self.to_bytes_with_nul(); + pub fn to_chars(self) -> &'a [T::Char] { + let s = self.to_chars_with_nul(); &s[..s.len() - 1] } + pub const fn as_ptr(self) -> *const T::C { + self.ptr.as_ptr() + } + pub const unsafe fn from_chars_with_nul_unchecked(chars: &'a [T::Char]) -> Self { + Self::from_ptr(chars.as_ptr().cast()) + } + pub fn from_chars_with_nul(chars: &'a [T::Char]) -> Result { + if chars.last() != Some(&T::NUL) || chars[..chars.len() - 1].contains(&T::NUL) { + return Err(FromCharsWithNulError); + } + + Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) }) + } + pub fn from_chars_until_nul(chars: &'a [T::Char]) -> Result { + if !chars.contains(&T::NUL) { + return Err(FromCharsUntilNulError); + } + + Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) }) + } + /// Scan the string to get its length. + #[doc(alias = "strlen")] + pub fn len(self) -> usize { + self.to_chars().len() + } + #[inline] + pub fn is_empty(&self) -> bool { + self.first() == T::NUL + } +} +impl<'a> CStr<'a> { + pub fn to_owned_cstring(self) -> CString { + CString::from(unsafe { core::ffi::CStr::from_ptr(self.ptr.as_ptr()) }) + } + pub fn borrow(string: &'a CString) -> Self { + unsafe { Self::from_ptr(string.as_ptr()) } + } + #[inline] + pub fn to_bytes(self) -> &'a [u8] { + self.to_chars() + } + #[inline] + pub fn to_bytes_with_nul(self) -> &'a [u8] { + self.to_chars_with_nul() + } pub fn to_str(self) -> Result<&'a str, Utf8Error> { core::str::from_utf8(self.to_bytes()) } pub fn to_string_lossy(self) -> Cow<'a, str> { String::from_utf8_lossy(self.to_bytes()) } - pub const fn as_ptr(self) -> *const c_char { - self.ptr.as_ptr() - } + #[inline] pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &'a [u8]) -> Self { - Self::from_ptr(bytes.as_ptr().cast()) - } - pub fn from_bytes_with_nul(bytes: &'a [u8]) -> Result { - if bytes.last() != Some(&b'\0') || bytes[..bytes.len() - 1].contains(&b'\0') { - return Err(FromBytesWithNulError); - } - - Ok(unsafe { Self::from_bytes_with_nul_unchecked(bytes) }) - } - pub fn from_bytes_until_nul(bytes: &'a [u8]) -> Result { - if !bytes.contains(&b'\0') { - return Err(FromBytesUntilNulError); - } - - Ok(unsafe { Self::from_bytes_with_nul_unchecked(bytes) }) - } - pub fn to_owned_cstring(self) -> CString { - CString::from(unsafe { core::ffi::CStr::from_ptr(self.ptr.as_ptr()) }) - } - pub fn borrow(string: &'a CString) -> Self { - unsafe { Self::from_ptr(string.as_ptr()) } - } - #[doc(alias = "strlen")] - pub fn count_bytes(&self) -> usize { - self.to_bytes().len() + Self::from_chars_with_nul_unchecked(bytes) } #[inline] - pub fn is_empty(&self) -> bool { - self.first() == 0 + pub fn from_bytes_with_nul(bytes: &'a [u8]) -> Result { + Self::from_chars_with_nul(bytes) + } + #[inline] + pub fn from_bytes_until_nul(bytes: &'a [u8]) -> Result { + Self::from_chars_until_nul(bytes) } } -unsafe impl Send for CStr<'_> {} -unsafe impl Sync for CStr<'_> {} +unsafe impl Send for NulStr<'_, T> {} +unsafe impl Sync for NulStr<'_, T> {} impl From<&core::ffi::CStr> for CStr<'_> { fn from(s: &core::ffi::CStr) -> Self { @@ -183,9 +258,9 @@ impl From<&core::ffi::CStr> for CStr<'_> { } #[derive(Debug)] -pub struct FromBytesWithNulError; +pub struct FromCharsWithNulError; #[derive(Debug)] -pub struct FromBytesUntilNulError; +pub struct FromCharsUntilNulError; pub use alloc::ffi::CString; From 04719d6f77413d25ede690f064a5aa243a3dcb91 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Mon, 6 Oct 2025 10:57:10 +0200 Subject: [PATCH 3/6] Add WStr type. --- src/c_str.rs | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/c_str.rs b/src/c_str.rs index c8f1134238..b39f33a12f 100644 --- a/src/c_str.rs +++ b/src/c_str.rs @@ -15,11 +15,11 @@ mod private { #[derive(Clone, Copy, Debug)] pub enum Thin {} -//#[derive(Clone, Copy, Debug)] -//pub enum Wide {} +#[derive(Clone, Copy, Debug)] +pub enum Wide {} impl private::Sealed for Thin {} -//impl private::Sealed for Wide {} +impl private::Sealed for Wide {} pub trait Kind: private::Sealed + Copy { /// c_char or wchar_t @@ -58,6 +58,32 @@ impl Kind for Thin { c as _ } } +impl Kind for Wide { + type C = wchar_t; + type Char = u32; + + const NUL: Self::Char = 0; + + unsafe fn strlen(s: *const Self::C) -> usize { + crate::header::wchar::wcslen(s) + } + unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C { + crate::header::wchar::wcschr(s, c) + } + unsafe fn strchrnul(mut s: *const Self::C, c: Self::C) -> *const Self::C { + // TODO: optimized function + while s.read() != c && s.read() != 0 { + s = s.add(1); + } + s + } + fn r2c(c: Self::Char) -> Self::C { + c as _ + } + fn c2r(c: Self::C) -> Self::Char { + c as _ + } +} /// Safe wrapper for immutable borrowed C strings, guaranteed to be the same layout as `*const u8`. #[derive(Clone, Copy)] @@ -67,7 +93,7 @@ pub struct NulStr<'a, T: Kind> { _marker: PhantomData<&'a [u8]>, } pub type CStr<'a> = NulStr<'a, Thin>; -//pub type WStr<'a> = NulStr<'a, Wide>; +pub type WStr<'a> = NulStr<'a, Wide>; impl<'a, T: Kind> NulStr<'a, T> { /// Safety @@ -134,6 +160,7 @@ impl<'a, T: Kind> NulStr<'a, T> { /// Look for the closest occurence of `c`, and return a new string starting at that byte if /// found. #[doc(alias = "strchr")] + #[doc(alias = "wcschr")] #[inline] pub fn find(self, c: T::Char) -> Option { unsafe { @@ -202,6 +229,7 @@ impl<'a, T: Kind> NulStr<'a, T> { } /// Scan the string to get its length. #[doc(alias = "strlen")] + #[doc(alias = "wcslen")] pub fn len(self) -> usize { self.to_chars().len() } From 62cc63c351cc4ab4c94c33d5ee9f8a59550de77a Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Mon, 6 Oct 2025 11:15:49 +0200 Subject: [PATCH 4/6] WIP: Reimpl WPrintfIter also in safe code. --- src/c_str.rs | 10 ++++ src/header/wchar/mod.rs | 3 +- src/header/wchar/wprintf.rs | 102 +++++++++++++++++------------------- 3 files changed, 60 insertions(+), 55 deletions(-) diff --git a/src/c_str.rs b/src/c_str.rs index b39f33a12f..a41f2506ef 100644 --- a/src/c_str.rs +++ b/src/c_str.rs @@ -186,6 +186,16 @@ impl<'a, T: Kind> NulStr<'a, T> { T::c2r(self.ptr.read()) } } + #[inline] + pub fn first_char(self) -> Option { + char::from_u32(self.first().into()) + } + /// Same as `split_first` except also requires that the first char be convertible into `char` + #[inline] + pub fn split_first_char(self) -> Option<(char, Self)> { + self.split_first() + .and_then(|(c, r)| Some((char::from_u32(c.into())?, r))) + } /// Split this string into `Some((first_byte, string_after_that))` or `None` if empty. #[inline] pub fn split_first(self) -> Option<(T::Char, Self)> { diff --git a/src/header/wchar/mod.rs b/src/header/wchar/mod.rs index 84ca2be8b1..0f231b1080 100644 --- a/src/header/wchar/mod.rs +++ b/src/header/wchar/mod.rs @@ -3,6 +3,7 @@ use core::{char, ffi::VaList as va_list, mem, ptr, slice, usize}; use crate::{ + c_str::WStr, header::{ ctype::isspace, errno::{EILSEQ, ENOMEM, ERANGE}, @@ -328,7 +329,7 @@ pub unsafe extern "C" fn vfwprintf( return -1; } - wprintf::wprintf(&mut *stream, format, arg) + wprintf::wprintf(&mut *stream, WStr::from_ptr(format), arg) } #[unsafe(no_mangle)] pub unsafe extern "C" fn fwprintf( diff --git a/src/header/wchar/wprintf.rs b/src/header/wchar/wprintf.rs index a4ffb9d03b..c89b7397e0 100644 --- a/src/header/wchar/wprintf.rs +++ b/src/header/wchar/wprintf.rs @@ -1,5 +1,8 @@ // TODO: generalize with printf impl -use crate::io::{self, Write}; +use crate::{ + c_str::WStr, + io::{self, Write}, +}; use alloc::{ collections::BTreeMap, string::{String, ToString}, @@ -284,10 +287,13 @@ static INF_STR_UPPER: &str = "INF"; static NAN_STR_LOWER: &str = "nan"; static NAN_STR_UPPER: &str = "NAN"; -unsafe fn pop_int_raw(format: &mut *const u32) -> Option { +fn pop_int_raw(format: &mut WStr) -> Option { let mut int = None; - while let Some(digit) = char::from_u32(**format).unwrap_or('\0').to_digit(10) { - *format = format.add(1); + while let Some((digit, rest)) = format + .split_first_char() + .and_then(|(c, r)| Some((c.to_digit(10)?, r))) + { + *format = rest; if int.is_none() { int = Some(0); } @@ -296,27 +302,27 @@ unsafe fn pop_int_raw(format: &mut *const u32) -> Option { } int } -unsafe fn pop_index(format: &mut *const u32) -> Option { +fn pop_index(format: &mut WStr) -> Option { // Peek ahead for a positional argument: let mut format2 = *format; if let Some(i) = pop_int_raw(&mut format2) { - if char::from_u32(*format2) == Some('$') { - *format = format2.add(1); + if let Some(('$', rest)) = format2.split_first_char() { + *format = rest; return Some(i); } } None } -unsafe fn pop_int(format: &mut *const u32) -> Option { - if char::from_u32(**format) == Some('*') { - *format = format.add(1); +fn pop_int(format: &mut WStr) -> 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) } } -unsafe fn fmt_int(fmt: u32, i: I) -> String +fn fmt_int(fmt: u32, i: I) -> String where I: fmt::Display + fmt::Octal + fmt::LowerHex + fmt::UpperHex, { @@ -476,8 +482,8 @@ fn fmt_float_nonfinite(w: &mut W, float: c_double, case: FmtCase) -> i } #[derive(Clone, Copy)] -struct WPrintfIter { - format: *const u32, +struct WPrintfIter<'a> { + format: WStr<'a>, } #[derive(Clone, Copy, Debug)] struct WPrintfArg { @@ -494,31 +500,23 @@ struct WPrintfArg { fmtkind: FmtKind, } #[derive(Debug)] -enum WPrintfFmt { - Plain(&'static [u32]), +enum WPrintfFmt<'a> { + Plain(&'a [u32]), Arg(WPrintfArg), } -impl Iterator for WPrintfIter { - type Item = Result; +impl<'a> Iterator for WPrintfIter<'a> { + type Item = Result, ()>; fn next(&mut self) -> Option { unsafe { // Send WPrintfFmt::Plain until the next % - let mut len = 0; - while *self.format.add(len) != 0 && char::from_u32(*self.format.add(len)) != Some('%') { - len += 1; - } - if len > 0 { - let slice = slice::from_raw_parts(self.format as *const u32, len); - self.format = self.format.add(len); - return Some(Ok(WPrintfFmt::Plain(slice))); - } - self.format = self.format.add(len); - if *self.format == 0 { - return None; - } - - // *self.format is guaranteed to be '%' at this point - self.format = self.format.add(1); + let first_percent = match self.format.find_get_subslice_or_all(u32::from(b'%')) { + Err(([], _)) => return None, + Ok((chunk @ [_, ..], rest)) | Err((chunk @ [_, ..], rest)) => { + self.format = rest; + return Some(Ok(WPrintfFmt::Plain(chunk))); + } + Ok(([], rest)) => rest, + }; let mut peekahead = self.format; let index = pop_index(&mut peekahead).map(|i| { @@ -533,8 +531,8 @@ impl Iterator for WPrintfIter { let mut sign_reserve = false; let mut sign_always = false; - loop { - match char::from_u32(*self.format).unwrap_or('\0') { + while let Some((c, rest)) = self.format.split_first_char() { + match c { '#' => alternate = true, '0' => zero = true, '-' => left = true, @@ -542,13 +540,13 @@ impl Iterator for WPrintfIter { '+' => sign_always = true, _ => break, } - self.format = self.format.add(1); + self.format = rest; } // Width and precision: let min_width = pop_int(&mut self.format).unwrap_or(Number::Static(0)); - let precision = if char::from_u32(*self.format) == Some('.') { - self.format = self.format.add(1); + 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(())), @@ -559,8 +557,8 @@ impl Iterator for WPrintfIter { // Integer size: let mut intkind = IntKind::Int; - loop { - intkind = match char::from_u32(*self.format).unwrap_or('\0') { + while let Some((char, rest)) = self.format.split_first_char() { + intkind = match char { 'h' => { if intkind == IntKind::Short || intkind == IntKind::Byte { IntKind::Byte @@ -582,10 +580,12 @@ impl Iterator for WPrintfIter { _ => break, }; - self.format = self.format.add(1); + self.format = rest; } - let fmt = *self.format; - let fmtkind = match char::from_u32(fmt).unwrap_or('\0') { + let Some((fmt, rest)) = self.format.split_first_char() else { + return Some(Err(())); + }; + let fmtkind = match fmt { '%' => FmtKind::Percent, 'd' | 'i' => FmtKind::Signed, 'o' | 'u' | 'x' | 'X' => FmtKind::Unsigned, @@ -598,7 +598,7 @@ impl Iterator for WPrintfIter { 'n' => FmtKind::GetWritten, _ => return Some(Err(())), }; - self.format = self.format.add(1); + self.format = rest; Some(Ok(WPrintfFmt::Arg(WPrintfArg { index, @@ -610,23 +610,17 @@ impl Iterator for WPrintfIter { min_width, precision, intkind, - fmt, + fmt: fmt as u32, fmtkind, }))) } } } -unsafe fn inner_wprintf( - w: W, - format: *const wchar_t, - mut ap: VaList, -) -> io::Result { +unsafe fn inner_wprintf(w: W, format: WStr, mut ap: VaList) -> io::Result { let w = &mut platform::CountingWriter::new(w); - let iterator = WPrintfIter { - format: format as *const u32, - }; + let iterator = WPrintfIter { format }; // Pre-fetch vararg types let mut varargs = VaListCache::default(); @@ -998,6 +992,6 @@ unsafe fn inner_wprintf( Ok(w.written as c_int) } -pub unsafe fn wprintf(w: W, format: *const wchar_t, ap: VaList) -> c_int { +pub unsafe fn wprintf(w: W, format: WStr, ap: VaList) -> c_int { inner_wprintf(w, format, ap).unwrap_or(-1) } From 96917f519e920cb9dca7272d4d741fa2d9c044b3 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Mon, 6 Oct 2025 11:40:41 +0200 Subject: [PATCH 5/6] Reuse PrintfIter for both printf and wprintf. --- src/c_str.rs | 22 +- src/header/stdio/printf.rs | 141 ++++++------ src/header/wchar/wprintf.rs | 444 +----------------------------------- 3 files changed, 102 insertions(+), 505 deletions(-) diff --git a/src/c_str.rs b/src/c_str.rs index a41f2506ef..758fd4ae7f 100644 --- a/src/c_str.rs +++ b/src/c_str.rs @@ -21,17 +21,21 @@ pub enum Wide {} impl private::Sealed for Thin {} impl private::Sealed for Wide {} -pub trait Kind: private::Sealed + Copy { +pub trait Kind: private::Sealed + Copy + 'static { /// c_char or wchar_t - type C: Copy; + type C: Copy + 'static; // u8 or u32 - type Char: Copy + Into + PartialEq; + type Char: Copy + From + Into + PartialEq + 'static; const NUL: Self::Char; + const IS_THIN_NOT_WIDE: bool; + fn r2c(c: Self::Char) -> Self::C; fn c2r(c: Self::C) -> Self::Char; + fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]>; + unsafe fn strlen(s: *const Self::C) -> usize; unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C; unsafe fn strchrnul(s: *const Self::C, c: Self::C) -> *const Self::C; @@ -41,6 +45,7 @@ impl Kind for Thin { type Char = u8; const NUL: Self::Char = 0; + const IS_THIN_NOT_WIDE: bool = true; unsafe fn strlen(s: *const c_char) -> usize { crate::header::string::strlen(s) @@ -57,12 +62,16 @@ impl Kind for Thin { fn c2r(c: c_char) -> u8 { c as _ } + fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> { + Some(b) + } } impl Kind for Wide { type C = wchar_t; type Char = u32; const NUL: Self::Char = 0; + const IS_THIN_NOT_WIDE: bool = false; unsafe fn strlen(s: *const Self::C) -> usize { crate::header::wchar::wcslen(s) @@ -83,6 +92,9 @@ impl Kind for Wide { fn c2r(c: Self::C) -> Self::Char { c as _ } + fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> { + None + } } /// Safe wrapper for immutable borrowed C strings, guaranteed to be the same layout as `*const u8`. @@ -118,8 +130,10 @@ impl<'a, T: Kind> NulStr<'a, T> { #[doc(alias = "strchrnul")] pub fn find_get_subslice_or_all( self, - c: T::Char, + c: impl Into, ) -> Result<(&'a [T::Char], Self), (&'a [T::Char], Self)> { + let c = c.into(); + // SAFETY: strchrnul expects self.as_ptr() to be valid up to and including its last NUL // byte let found = unsafe { T::strchrnul(self.as_ptr(), T::r2c(c)) }; diff --git a/src/header/stdio/printf.rs b/src/header/stdio/printf.rs index 367ab25c0f..b1ab4841e0 100644 --- a/src/header/stdio/printf.rs +++ b/src/header/stdio/printf.rs @@ -1,6 +1,6 @@ -// TODO: generalize with wprintf impl +// TODO: reuse more code with the wide printf impl use crate::{ - c_str::CStr, + c_str::{self, CStr, NulStr}, io::{self, Write}, }; use alloc::{ @@ -23,7 +23,7 @@ use crate::{ // |_| #[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum IntKind { +pub(crate) enum IntKind { Byte, Short, Int, @@ -34,7 +34,7 @@ enum IntKind { Size, } #[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum FmtKind { +pub(crate) enum FmtKind { Percent, Signed, @@ -50,13 +50,13 @@ enum FmtKind { GetWritten, } #[derive(Clone, Copy, Debug)] -enum Number { +pub(crate) enum Number { Static(usize), Index(usize), Next, } impl Number { - unsafe fn resolve(self, varargs: &mut VaListCache, ap: &mut VaList) -> usize { + 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) => varargs.get(i - 1, ap, None), @@ -82,7 +82,7 @@ impl Number { } } #[derive(Clone, Copy, Debug)] -enum VaArg { +pub(crate) enum VaArg { c_char(c_char), c_double(c_double), c_int(c_int), @@ -96,7 +96,7 @@ enum VaArg { wint_t(wint_t), } impl VaArg { - unsafe fn arg_from(fmtkind: FmtKind, intkind: IntKind, ap: &mut VaList) -> 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 @@ -227,12 +227,12 @@ impl VaArg { } } #[derive(Default)] -struct VaListCache { - args: Vec, - i: usize, +pub(crate) struct VaListCache { + pub(crate) args: Vec, + pub(crate) i: usize, } impl VaListCache { - unsafe fn get( + pub(crate) unsafe fn get( &mut self, i: usize, ap: &mut VaList, @@ -287,11 +287,11 @@ static INF_STR_UPPER: &str = "INF"; static NAN_STR_LOWER: &str = "nan"; static NAN_STR_UPPER: &str = "NAN"; -fn pop_int_raw(format: &mut CStr) -> Option { +fn pop_int_raw(format: &mut NulStr) -> Option { let mut int = None; while let Some((digit, rest)) = format - .split_first() - .and_then(|(d, r)| Some(((d as char).to_digit(10)?, r))) + .split_first_char() + .and_then(|(d, r)| Some((d.to_digit(10)?, r))) { *format = rest; if int.is_none() { @@ -302,19 +302,19 @@ fn pop_int_raw(format: &mut CStr) -> Option { } int } -fn pop_index(format: &mut CStr) -> Option { +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) { - if let Some((b'$', format2)) = format2.split_first() { + if let Some(('$', format2)) = format2.split_first_char() { *format = format2; return Some(i); } } None } -fn pop_int(format: &mut CStr) -> Option { - if let Some((b'*', rest)) = format.split_first() { +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 { @@ -470,30 +470,31 @@ fn fmt_float_nonfinite(w: &mut W, float: c_double, case: FmtCase) -> i } #[derive(Clone, Copy)] -struct PrintfIter<'a> { - format: CStr<'a>, +pub(crate) struct PrintfIter<'a, T: c_str::Kind> { + pub(crate) format: NulStr<'a, T>, } #[derive(Clone, Copy, Debug)] -struct PrintfArg { - index: Option, - alternate: bool, - zero: bool, - left: bool, - sign_reserve: bool, - sign_always: bool, - min_width: Number, - precision: Option, - intkind: IntKind, - fmt: u8, - fmtkind: FmtKind, +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)] -enum PrintfFmt<'a> { - Plain(&'a [u8]), +pub(crate) enum PrintfFmt<'a, U> { + Plain(&'a [U]), Arg(PrintfArg), } -impl<'a> Iterator for PrintfIter<'a> { - type Item = Result, ()>; +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'%') { @@ -521,13 +522,13 @@ impl<'a> Iterator for PrintfIter<'a> { let mut sign_reserve = false; let mut sign_always = false; - while let Some((c, rest)) = self.format.split_first() { + while let Some((c, rest)) = self.format.split_first_char() { match c { - b'#' => alternate = true, - b'0' => zero = true, - b'-' => left = true, - b' ' => sign_reserve = true, - b'+' => sign_always = true, + '#' => alternate = true, + '0' => zero = true, + '-' => left = true, + ' ' => sign_reserve = true, + '+' => sign_always = true, _ => break, } self.format = rest; @@ -535,7 +536,7 @@ impl<'a> Iterator for PrintfIter<'a> { // Width and precision: let min_width = pop_int(&mut self.format).unwrap_or(Number::Static(0)); - let precision = if let Some((b'.', rest)) = self.format.split_first() { + let precision = if let Some(('.', rest)) = self.format.split_first_char() { self.format = rest; match pop_int(&mut self.format) { int @ Some(_) => int, @@ -547,55 +548,59 @@ impl<'a> Iterator for PrintfIter<'a> { // Integer size: let mut intkind = IntKind::Int; - while let Some((byte, rest)) = self.format.split_first() { + while let Some((byte, rest)) = self.format.split_first_char() { intkind = match byte { - b'h' => { + 'h' => { if intkind == IntKind::Short || intkind == IntKind::Byte { IntKind::Byte } else { IntKind::Short } } - b'j' => IntKind::IntMax, - b'l' => { + 'j' => IntKind::IntMax, + 'l' => { if intkind == IntKind::Long || intkind == IntKind::LongLong { IntKind::LongLong } else { IntKind::Long } } - b'q' | b'L' => IntKind::LongLong, - b't' => IntKind::PtrDiff, - b'z' => IntKind::Size, + 'q' | 'L' => IntKind::LongLong, + 't' => IntKind::PtrDiff, + 'z' => IntKind::Size, _ => break, }; self.format = rest; } - let Some((fmt, rest)) = self.format.split_first() else { + let Some((fmt, rest)) = self.format.split_first_char() else { return Some(Err(())); }; self.format = rest; let fmtkind = match fmt { - b'%' => FmtKind::Percent, - b'd' | b'i' => FmtKind::Signed, - b'o' | b'u' | b'x' | b'X' | b'b' | b'B' => FmtKind::Unsigned, - b'e' | b'E' => FmtKind::Scientific, - b'f' | b'F' => FmtKind::Decimal, - b'g' | b'G' => FmtKind::AnyNotation, - b's' => FmtKind::String, - b'c' => FmtKind::Char, - b'p' => FmtKind::Pointer, - b'n' => FmtKind::GetWritten, - b'm' => { + '%' => 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' => 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( - errno::STR_ERROR - .get(platform::ERRNO.get() as usize) - .map(|e| e.as_bytes()) - .unwrap_or(b"unknown error"), + 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(())), @@ -689,7 +694,7 @@ unsafe fn inner_printf(w: W, format: CStr, mut ap: VaList) -> io::Resu signed_space as usize }; let intkind = arg.intkind; - let fmt = arg.fmt; + let fmt = arg.fmt as u8; let fmtkind = arg.fmtkind; let fmtcase = match fmt { b'x' | b'b' | b'f' | b'e' | b'g' => Some(FmtCase::Lower), diff --git a/src/header/wchar/wprintf.rs b/src/header/wchar/wprintf.rs index c89b7397e0..67f6d0b516 100644 --- a/src/header/wchar/wprintf.rs +++ b/src/header/wchar/wprintf.rs @@ -1,6 +1,7 @@ -// TODO: generalize with printf impl +// TODO: reuse more code with the thin printf impl use crate::{ - c_str::WStr, + c_str::{self, WStr}, + header::stdio::printf::{FmtKind, IntKind, Number, PrintfFmt, PrintfIter, VaArg, VaListCache}, io::{self, Write}, }; use alloc::{ @@ -15,259 +16,6 @@ use crate::{ platform::{self, types::*}, }; -// ____ _ _ _ _ -// | __ ) ___ (_) | ___ _ __ _ __ | | __ _| |_ ___ _ -// | _ \ / _ \| | |/ _ \ '__| '_ \| |/ _` | __/ _ (_) -// | |_) | (_) | | | __/ | | |_) | | (_| | || __/_ -// |____/ \___/|_|_|\___|_| | .__/|_|\__,_|\__\___(_) -// |_| - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum IntKind { - Byte, - Short, - Int, - Long, - LongLong, - IntMax, - PtrDiff, - Size, -} -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum FmtKind { - Percent, - - Signed, - Unsigned, - - Scientific, - Decimal, - AnyNotation, - - String, - Char, - Pointer, - GetWritten, -} -#[derive(Clone, Copy, Debug)] -enum Number { - Static(usize), - Index(usize), - Next, -} -impl Number { - unsafe fn resolve(self, varargs: &mut VaListCache, ap: &mut VaList) -> usize { - let arg = match self { - Number::Static(num) => return num, - Number::Index(i) => varargs.get(i - 1, ap, None), - Number::Next => { - let i = varargs.i; - varargs.i += 1; - varargs.get(i, ap, None) - } - }; - match arg { - VaArg::c_char(i) => i as usize, - VaArg::c_double(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)] -enum VaArg { - c_char(c_char), - c_double(c_double), - 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 { - 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(ap.arg::()) - } - - (FmtKind::Char, _) - | (FmtKind::Unsigned, IntKind::Byte) - | (FmtKind::Signed, IntKind::Byte) => { - // c_int is passed but truncated to c_char - VaArg::c_char(ap.arg::() as c_char) - } - (FmtKind::Unsigned, IntKind::Short) | (FmtKind::Signed, IntKind::Short) => { - // c_int is passed but truncated to c_short - VaArg::c_short(ap.arg::() as c_short) - } - (FmtKind::Unsigned, IntKind::Int) | (FmtKind::Signed, IntKind::Int) => { - VaArg::c_int(ap.arg::()) - } - (FmtKind::Unsigned, IntKind::Long) | (FmtKind::Signed, IntKind::Long) => { - VaArg::c_long(ap.arg::()) - } - (FmtKind::Unsigned, IntKind::LongLong) | (FmtKind::Signed, IntKind::LongLong) => { - VaArg::c_longlong(ap.arg::()) - } - (FmtKind::Unsigned, IntKind::IntMax) | (FmtKind::Signed, IntKind::IntMax) => { - VaArg::intmax_t(ap.arg::()) - } - (FmtKind::Unsigned, IntKind::PtrDiff) | (FmtKind::Signed, IntKind::PtrDiff) => { - VaArg::ptrdiff_t(ap.arg::()) - } - (FmtKind::Unsigned, IntKind::Size) | (FmtKind::Signed, IntKind::Size) => { - VaArg::ssize_t(ap.arg::()) - } - - (FmtKind::AnyNotation, _) | (FmtKind::Decimal, _) | (FmtKind::Scientific, _) => { - VaArg::c_double(ap.arg::()) - } - - (FmtKind::GetWritten, _) | (FmtKind::Pointer, _) | (FmtKind::String, _) => { - VaArg::pointer(ap.arg::<*const c_void>()) - } - } - } - unsafe fn transmute(&self, fmtkind: FmtKind, intkind: IntKind) -> VaArg { - // At this point, there are conflicting wprintf arguments. An - // example of this is: - // ```c - // wprintf(L"%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_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_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(untyped.wint_t) - } - - (FmtKind::Char, _) - | (FmtKind::Unsigned, IntKind::Byte) - | (FmtKind::Signed, IntKind::Byte) => VaArg::c_char(untyped.c_char), - (FmtKind::Unsigned, IntKind::Short) | (FmtKind::Signed, IntKind::Short) => { - VaArg::c_short(untyped.c_short) - } - (FmtKind::Unsigned, IntKind::Int) | (FmtKind::Signed, IntKind::Int) => { - VaArg::c_int(untyped.c_int) - } - (FmtKind::Unsigned, IntKind::Long) | (FmtKind::Signed, IntKind::Long) => { - VaArg::c_long(untyped.c_long) - } - (FmtKind::Unsigned, IntKind::LongLong) | (FmtKind::Signed, IntKind::LongLong) => { - VaArg::c_longlong(untyped.c_longlong) - } - (FmtKind::Unsigned, IntKind::IntMax) | (FmtKind::Signed, IntKind::IntMax) => { - VaArg::intmax_t(untyped.intmax_t) - } - (FmtKind::Unsigned, IntKind::PtrDiff) | (FmtKind::Signed, IntKind::PtrDiff) => { - VaArg::ptrdiff_t(untyped.ptrdiff_t) - } - (FmtKind::Unsigned, IntKind::Size) | (FmtKind::Signed, IntKind::Size) => { - VaArg::ssize_t(untyped.ssize_t) - } - - (FmtKind::AnyNotation, _) | (FmtKind::Decimal, _) | (FmtKind::Scientific, _) => { - VaArg::c_double(untyped.c_double) - } - - (FmtKind::GetWritten, _) | (FmtKind::Pointer, _) | (FmtKind::String, _) => { - VaArg::pointer(untyped.pointer) - } - } - } -} -#[derive(Default)] -struct VaListCache { - args: Vec, - i: usize, -} -impl VaListCache { - 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 = 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(ap.arg::())) - } - - // Add the value to the cache - self.args.push(match default { - Some((fmtkind, intkind)) => VaArg::arg_from(fmtkind, intkind, ap), - None => VaArg::c_int(ap.arg::()), - }); - - // Return the value - self.args[i] - } -} - // ___ _ _ _ _ // |_ _|_ __ ___ _ __ | | ___ _ __ ___ ___ _ __ | |_ __ _| |_(_) ___ _ __ _ // | || '_ ` _ \| '_ \| |/ _ \ '_ ` _ \ / _ \ '_ \| __/ _` | __| |/ _ \| '_ \(_) @@ -287,41 +35,6 @@ static INF_STR_UPPER: &str = "INF"; static NAN_STR_LOWER: &str = "nan"; static NAN_STR_UPPER: &str = "NAN"; -fn pop_int_raw(format: &mut WStr) -> Option { - let mut int = None; - while let Some((digit, rest)) = format - .split_first_char() - .and_then(|(c, r)| Some((c.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 WStr) -> Option { - // Peek ahead for a positional argument: - let mut format2 = *format; - if let Some(i) = pop_int_raw(&mut format2) { - if let Some(('$', rest)) = format2.split_first_char() { - *format = rest; - return Some(i); - } - } - None -} -fn pop_int(format: &mut WStr) -> 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: u32, i: I) -> String where I: fmt::Display + fmt::Octal + fmt::LowerHex + fmt::UpperHex, @@ -481,146 +194,10 @@ fn fmt_float_nonfinite(w: &mut W, float: c_double, case: FmtCase) -> i Ok(()) } -#[derive(Clone, Copy)] -struct WPrintfIter<'a> { - format: WStr<'a>, -} -#[derive(Clone, Copy, Debug)] -struct WPrintfArg { - index: Option, - alternate: bool, - zero: bool, - left: bool, - sign_reserve: bool, - sign_always: bool, - min_width: Number, - precision: Option, - intkind: IntKind, - fmt: u32, - fmtkind: FmtKind, -} -#[derive(Debug)] -enum WPrintfFmt<'a> { - Plain(&'a [u32]), - Arg(WPrintfArg), -} -impl<'a> Iterator for WPrintfIter<'a> { - type Item = Result, ()>; - fn next(&mut self) -> Option { - unsafe { - // Send WPrintfFmt::Plain until the next % - let first_percent = match self.format.find_get_subslice_or_all(u32::from(b'%')) { - Err(([], _)) => return None, - Ok((chunk @ [_, ..], rest)) | Err((chunk @ [_, ..], rest)) => { - self.format = rest; - return Some(Ok(WPrintfFmt::Plain(chunk))); - } - Ok(([], rest)) => rest, - }; - - let mut peekahead = self.format; - let index = pop_index(&mut peekahead).map(|i| { - self.format = peekahead; - i - }); - - // 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((char, rest)) = self.format.split_first_char() { - intkind = match char { - '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(())); - }; - let fmtkind = match fmt { - '%' => FmtKind::Percent, - 'd' | 'i' => FmtKind::Signed, - 'o' | 'u' | 'x' | 'X' => FmtKind::Unsigned, - 'e' | 'E' => FmtKind::Scientific, - 'f' | 'F' => FmtKind::Decimal, - 'g' | 'G' => FmtKind::AnyNotation, - 's' => FmtKind::String, - 'c' => FmtKind::Char, - 'p' => FmtKind::Pointer, - 'n' => FmtKind::GetWritten, - _ => return Some(Err(())), - }; - self.format = rest; - - Some(Ok(WPrintfFmt::Arg(WPrintfArg { - index, - alternate, - zero, - left, - sign_reserve, - sign_always, - min_width, - precision, - intkind, - fmt: fmt as u32, - fmtkind, - }))) - } - } -} - unsafe fn inner_wprintf(w: W, format: WStr, mut ap: VaList) -> io::Result { let w = &mut platform::CountingWriter::new(w); - let iterator = WPrintfIter { format }; + let iterator = PrintfIter:: { format }; // Pre-fetch vararg types let mut varargs = VaListCache::default(); @@ -629,8 +206,8 @@ unsafe fn inner_wprintf(w: W, format: WStr, mut ap: VaList) -> io::Res for section in iterator { let arg = match section { - Ok(WPrintfFmt::Plain(text)) => continue, - Ok(WPrintfFmt::Arg(arg)) => arg, + Ok(PrintfFmt::Plain(text)) => continue, + Ok(PrintfFmt::Arg(arg)) => arg, Err(()) => return Ok(-1), }; if arg.fmtkind == FmtKind::Percent { @@ -663,7 +240,7 @@ unsafe fn inner_wprintf(w: W, format: WStr, mut ap: VaList) -> io::Res // Main loop for section in iterator { let arg = match section { - Ok(WPrintfFmt::Plain(text)) => { + Ok(PrintfFmt::Plain(text)) => { for &wc in text.iter() { if let Some(c) = char::from_u32(wc) { write!(w, "{}", c)?; @@ -671,7 +248,7 @@ unsafe fn inner_wprintf(w: W, format: WStr, mut ap: VaList) -> io::Res } continue; } - Ok(WPrintfFmt::Arg(arg)) => arg, + Ok(PrintfFmt::Arg(arg)) => arg, Err(()) => return Ok(-1), }; let alternate = arg.alternate; @@ -693,9 +270,10 @@ unsafe fn inner_wprintf(w: W, format: WStr, mut ap: VaList) -> io::Res signed_space as usize }; let intkind = arg.intkind; - let fmt = arg.fmt; + let fmt_char = arg.fmt; + let fmt = fmt_char as u32; let fmtkind = arg.fmtkind; - let fmtcase = match char::from_u32(fmt).unwrap_or('\0') { + let fmtcase = match fmt_char { 'x' | 'f' | 'e' | 'g' => Some(FmtCase::Lower), 'X' | 'F' | 'E' | 'G' => Some(FmtCase::Upper), _ => None, From 1c5732ba36b9e5613600011a04b2a5b1bc34f11b Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 7 Oct 2025 15:58:34 +0200 Subject: [PATCH 6/6] Share almost all code between printf and wprintf. --- src/c_str.rs | 7 + src/header/stdio/printf.rs | 80 +++-- src/header/wchar/wprintf.rs | 574 +----------------------------------- 3 files changed, 61 insertions(+), 600 deletions(-) diff --git a/src/c_str.rs b/src/c_str.rs index 758fd4ae7f..ee98f47dee 100644 --- a/src/c_str.rs +++ b/src/c_str.rs @@ -35,6 +35,7 @@ pub trait Kind: private::Sealed + Copy + 'static { fn c2r(c: Self::C) -> Self::Char; fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]>; + fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]>; unsafe fn strlen(s: *const Self::C) -> usize; unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C; @@ -65,6 +66,9 @@ impl Kind for Thin { fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> { Some(b) } + fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> { + Some(c) + } } impl Kind for Wide { type C = wchar_t; @@ -95,6 +99,9 @@ impl Kind for Wide { fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> { None } + fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> { + None + } } /// Safe wrapper for immutable borrowed C strings, guaranteed to be the same layout as `*const u8`. diff --git a/src/header/stdio/printf.rs b/src/header/stdio/printf.rs index b1ab4841e0..057da66909 100644 --- a/src/header/stdio/printf.rs +++ b/src/header/stdio/printf.rs @@ -322,16 +322,16 @@ fn pop_int(format: &mut NulStr) -> Option { } } -fn fmt_int(fmt: u8, i: I) -> String +fn fmt_int(fmt: char, i: I) -> String where I: fmt::Display + fmt::Octal + fmt::LowerHex + fmt::UpperHex + fmt::Binary, { match fmt { - b'o' => format!("{:o}", i), - b'u' => i.to_string(), - b'x' => format!("{:x}", i), - b'X' => format!("{:X}", i), - b'b' | b'B' => format!("{:b}", i), + '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 as char, @@ -384,7 +384,7 @@ fn float_exp(mut float: c_double) -> (c_double, isize) { fn fmt_float_exp( w: &mut W, - exp_fmt: u8, + exp_fmt: char, trim: bool, precision: usize, float: c_double, @@ -412,7 +412,7 @@ fn fmt_float_exp( }; pad(w, !left, b'0', len..pad_zero)?; w.write_all(bytes)?; - write!(w, "{}{:+03}", exp_fmt as char, exp)?; + write!(w, "{}{:+03}", exp_fmt, exp)?; pad(w, left, b' ', len..pad_space)?; Ok(()) @@ -622,7 +622,11 @@ impl<'a, T: c_str::Kind> Iterator for PrintfIter<'a, T> { } } -unsafe fn inner_printf(w: W, format: CStr, mut ap: VaList) -> io::Result { +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 }; @@ -669,7 +673,15 @@ unsafe fn inner_printf(w: W, format: CStr, mut ap: VaList) -> io::Resu for section in iterator { let arg = match section { Ok(PrintfFmt::Plain(text)) => { - w.write_all(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())) { + write!(w, "{}", c); + } + } continue; } Ok(PrintfFmt::Arg(arg)) => arg, @@ -694,11 +706,13 @@ unsafe fn inner_printf(w: W, format: CStr, mut ap: VaList) -> io::Resu signed_space as usize }; let intkind = arg.intkind; - let fmt = arg.fmt as u8; + let fmt = arg.fmt; let fmtkind = arg.fmtkind; let fmtcase = match fmt { - b'x' | b'b' | b'f' | b'e' | b'g' => Some(FmtCase::Lower), - b'X' | b'B' | b'F' | b'E' | b'G' => Some(FmtCase::Upper), + '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, }; @@ -764,16 +778,16 @@ unsafe fn inner_printf(w: W, format: CStr, mut ap: VaList) -> io::Resu } FmtKind::Unsigned => { let string = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { - VaArg::c_char(i) => fmt_int(fmt, i as c_uchar), + VaArg::c_char(i) => fmt_int::<_, T>(fmt, i as c_uchar), VaArg::c_double(i) => panic!("this should not be possible"), - VaArg::c_int(i) => fmt_int(fmt, i as c_uint), - VaArg::c_long(i) => fmt_int(fmt, i as c_ulong), - VaArg::c_longlong(i) => fmt_int(fmt, i as c_ulonglong), - VaArg::c_short(i) => fmt_int(fmt, i as c_ushort), - VaArg::intmax_t(i) => fmt_int(fmt, i as uintmax_t), - VaArg::pointer(i) => fmt_int(fmt, i as usize), - VaArg::ptrdiff_t(i) => fmt_int(fmt, i as size_t), - VaArg::ssize_t(i) => fmt_int(fmt, i as size_t), + 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"; @@ -791,8 +805,9 @@ unsafe fn inner_printf(w: W, format: CStr, mut ap: VaList) -> io::Resu len.max(precision.unwrap_or(0)) + if alternate && string != "0" { match fmt { - b'o' if no_precision => 1, - b'x' | b'X' | b'b' | b'B' => 2, + 'o' if no_precision => 1, + 'x' | 'X' => 2, + 'b' | 'B' if T::IS_THIN_NOT_WIDE => 2, _ => 0, } } else { @@ -804,11 +819,11 @@ unsafe fn inner_printf(w: W, format: CStr, mut ap: VaList) -> io::Resu if alternate && string != "0" { match fmt { - b'o' if no_precision => w.write_all(b"0")?, - b'x' => w.write_all(b"0x")?, - b'X' => w.write_all(b"0X")?, - b'b' => w.write_all(b"0b")?, - b'B' => w.write_all(b"0B")?, + '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")?, _ => (), } } @@ -856,7 +871,8 @@ unsafe fn inner_printf(w: W, format: CStr, mut ap: VaList) -> io::Resu }; if float.is_finite() { let (log, exp) = float_exp(float); - let exp_fmt = b'E' | (fmt & 32); + // 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; @@ -1263,6 +1279,6 @@ unsafe fn inner_printf(w: W, format: CStr, mut ap: VaList) -> io::Resu /// # 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: W, format: CStr, ap: VaList) -> c_int { - inner_printf(w, format, ap).unwrap_or(-1) +pub unsafe fn printf(w: impl Write, format: CStr, ap: VaList) -> c_int { + inner_printf::(w, format, ap).unwrap_or(-1) } diff --git a/src/header/wchar/wprintf.rs b/src/header/wchar/wprintf.rs index 67f6d0b516..1925a0369b 100644 --- a/src/header/wchar/wprintf.rs +++ b/src/header/wchar/wprintf.rs @@ -1,575 +1,13 @@ // TODO: reuse more code with the thin printf impl use crate::{ c_str::{self, WStr}, - header::stdio::printf::{FmtKind, IntKind, Number, PrintfFmt, PrintfIter, VaArg, VaListCache}, - io::{self, Write}, + header::stdio::printf::inner_printf, + io::Write, }; -use alloc::{ - collections::BTreeMap, - string::{String, ToString}, - vec::Vec, -}; -use core::{char, cmp, f64, ffi::VaList, fmt, num::FpCategory, ops::Range, slice}; +use core::ffi::VaList; -use crate::{ - header::errno::EILSEQ, - platform::{self, types::*}, -}; +use crate::platform::{self, types::*}; -// ___ _ _ _ _ -// |_ _|_ __ ___ _ __ | | ___ _ __ ___ ___ _ __ | |_ __ _| |_(_) ___ _ __ _ -// | || '_ ` _ \| '_ \| |/ _ \ '_ ` _ \ / _ \ '_ \| __/ _` | __| |/ _ \| '_ \(_) -// | || | | | | | |_) | | __/ | | | | | __/ | | | || (_| | |_| | (_) | | | |_ -// |___|_| |_| |_| .__/|_|\___|_| |_| |_|\___|_| |_|\__\__,_|\__|_|\___/|_| |_(_) -// |_| - -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 fmt_int(fmt: u32, i: I) -> String -where - I: fmt::Display + fmt::Octal + fmt::LowerHex + fmt::UpperHex, -{ - match char::from_u32(fmt).unwrap_or('\0') { - 'o' => format!("{:o}", i), - 'u' => i.to_string(), - 'x' => format!("{:x}", i), - 'X' => format!("{:X}", i), - _ => panic!( - "fmt_int should never be called with the fmt {:?}", - char::from_u32(fmt) - ), - } -} - -fn pad( - w: &mut W, - current_side: bool, - pad_char: u32, - range: Range, -) -> io::Result<()> { - if current_side { - for _ in range { - if let Some(c) = char::from_u32(pad_char) { - write!(w, "{}", c)?; - } - } - } - Ok(()) -} - -fn abs(float: c_double) -> c_double { - // Don't ask me whe float.abs() seems absent... - if float.is_sign_negative() { - -float - } else { - float - } -} - -fn float_string(float: c_double, precision: usize, trim: bool) -> String { - let mut string = format!("{:.p$}", float, p = precision); - 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 abs(float) >= 10.0 { - float /= 10.0; - exp += 1; - } - while f64::EPSILON < abs(float) && abs(float) < 1.0 { - float *= 10.0; - exp -= 1; - } - (float, exp) -} - -fn fmt_float_exp( - w: &mut W, - exp_fmt: u32, - trim: 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); - let len = string.len() + 2 + 2.max(exp_len); - - pad(w, !left, ' ' as u32, len..pad_space)?; - let bytes = if string.starts_with('-') { - w.write_all(&[b'-'])?; - &string.as_bytes()[1..] - } else { - string.as_bytes() - }; - pad(w, !left, '0' as u32, len..pad_zero)?; - w.write_all(bytes)?; - if let Some(c) = char::from_u32(exp_fmt) { - write!(w, "{}{:+03}", c, exp)?; - } - pad(w, left, ' ' as u32, len..pad_space)?; - - Ok(()) -} - -fn fmt_float_normal( - w: &mut W, - trim: bool, - precision: usize, - float: c_double, - left: bool, - pad_space: usize, - pad_zero: usize, -) -> io::Result { - let string = float_string(float, precision, trim); - - pad(w, !left, ' ' as u32, 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, '0' as u32, string.len()..pad_zero)?; - w.write_all(bytes)?; - pad(w, left, ' ' as u32, 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) -> io::Result<()> { - if float.is_sign_negative() { - w.write_all(&[b'-'])?; - } - - let nonfinite_str = 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!("this should not be possible") - } - }; - - w.write_all(nonfinite_str.as_bytes())?; - - Ok(()) -} - -unsafe fn inner_wprintf(w: W, format: WStr, 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(ap.arg::())), - 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(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 { - varargs.get(i, &mut ap, Some(arg)); - } - - // Main loop - for section in iterator { - let arg = match section { - Ok(PrintfFmt::Plain(text)) => { - for &wc in text.iter() { - if let Some(c) = char::from_u32(wc) { - write!(w, "{}", c)?; - } - } - 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 = arg.min_width.resolve(&mut varargs, &mut ap); - let precision = arg.precision.map(|n| n.resolve(&mut varargs, &mut ap)); - 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_char = arg.fmt; - let fmt = fmt_char as u32; - let fmtkind = arg.fmtkind; - let fmtcase = match fmt_char { - '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 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_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, ' ' as u32, 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, '0' as u32, len..precision.unwrap_or(pad_zero))?; - - if !zero { - w.write_all(bytes)?; - } - - pad(w, left, ' ' as u32, final_len..pad_space)?; - } - FmtKind::Unsigned => { - let string = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { - VaArg::c_char(i) => fmt_int(fmt, i as c_uchar), - VaArg::c_double(i) => panic!("this should not be possible"), - VaArg::c_int(i) => fmt_int(fmt, i as c_uint), - VaArg::c_long(i) => fmt_int(fmt, i as c_ulong), - VaArg::c_longlong(i) => fmt_int(fmt, i as c_ulonglong), - VaArg::c_short(i) => fmt_int(fmt, i as c_ushort), - VaArg::intmax_t(i) => fmt_int(fmt, i as uintmax_t), - VaArg::pointer(i) => fmt_int(fmt, i as usize), - VaArg::ptrdiff_t(i) => fmt_int(fmt, i as size_t), - VaArg::ssize_t(i) => fmt_int(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 char::from_u32(fmt).unwrap_or('\0') { - 'o' if no_precision => 1, - 'x' | 'X' => 2, - _ => 0, - } - } else { - 0 - } - }; - - pad(w, !left, ' ' as u32, final_len..pad_space)?; - - if alternate && string != "0" { - match char::from_u32(fmt).unwrap_or('\0') { - 'o' if no_precision => w.write_all(&[b'0'])?, - 'x' => w.write_all(&[b'0', b'x'])?, - 'X' => w.write_all(&[b'0', b'X'])?, - _ => (), - } - } - pad(w, true, '0' as u32, len..precision.unwrap_or(pad_zero))?; - - if !zero { - w.write_all(string.as_bytes())?; - } - - pad(w, left, ' ' as u32, final_len..pad_space)?; - } - FmtKind::Scientific => { - let float = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { - VaArg::c_double(i) => 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, precision, float, exp, left, pad_space, pad_zero, - )?; - } else { - fmt_float_nonfinite(w, float, fmtcase.unwrap())?; - } - } - FmtKind::Decimal => { - let float = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { - VaArg::c_double(i) => i, - _ => panic!("this should not be possible"), - }; - if float.is_finite() { - let precision = precision.unwrap_or(6); - - fmt_float_normal(w, false, precision, float, left, pad_space, pad_zero)?; - } else { - fmt_float_nonfinite(w, float, fmtcase.unwrap())?; - } - } - FmtKind::AnyNotation => { - let float = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { - VaArg::c_double(i) => i, - _ => panic!("this should not be possible"), - }; - if float.is_finite() { - let (log, exp) = float_exp(float); - let exp_fmt = ('E' as u32) | (fmt & 32); - 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, true, 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, true, precision, float, left, pad_space, pad_zero)?; - } - } else { - fmt_float_nonfinite(w, float, fmtcase.unwrap())?; - } - } - FmtKind::String => { - let ptr = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { - VaArg::pointer(p) => p, - _ => panic!("this should not be possible"), - } as *const c_char; - - if ptr.is_null() { - w.write_all(b"(null)")?; - } else { - let max = precision.unwrap_or(::core::usize::MAX); - - if intkind == IntKind::Long || intkind == IntKind::LongLong { - // Handle wchar_t - let mut ptr = ptr as *const wchar_t; - let mut string = String::new(); - - while *ptr != 0 { - let c = match char::from_u32(*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 = ptr.add(1); - } - - pad(w, !left, ' ' as u32, string.len()..pad_space)?; - w.write_all(string.as_bytes())?; - pad(w, left, ' ' as u32, string.len()..pad_space)?; - } else { - let mut len = 0; - while *ptr.add(len) != 0 && len < max { - len += 1; - } - - pad(w, !left, ' ' as u32, len..pad_space)?; - w.write_all(slice::from_raw_parts(ptr as *const u8, len))?; - pad(w, left, ' ' as u32, len..pad_space)?; - } - } - } - FmtKind::Char => match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { - VaArg::c_char(c) => { - pad(w, !left, ' ' as u32, 1..pad_space)?; - w.write_all(&[c as u8])?; - pad(w, left, ' ' as u32, 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, ' ' as u32, 1..pad_space)?; - w.write_all(c.encode_utf8(&mut buf).as_bytes())?; - pad(w, left, ' ' as u32, 1..pad_space)?; - } - _ => unreachable!("this should not be possible"), - }, - FmtKind::Pointer => { - let ptr = match 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, ' ' as u32, len..pad_space)?; - if ptr.is_null() { - write!(w, "(nil)")?; - } else { - write!(w, "0x{:x}", ptr as usize)?; - } - pad(w, left, ' ' as u32, len..pad_space)?; - } - FmtKind::GetWritten => { - let ptr = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { - VaArg::pointer(p) => p, - _ => panic!("this should not be possible"), - }; - - match intkind { - IntKind::Byte => *(ptr as *mut c_char) = w.written as c_char, - IntKind::Short => *(ptr as *mut c_short) = w.written as c_short, - IntKind::Int => *(ptr as *mut c_int) = w.written as c_int, - IntKind::Long => *(ptr as *mut c_long) = w.written as c_long, - IntKind::LongLong => *(ptr as *mut c_longlong) = w.written as c_longlong, - IntKind::IntMax => *(ptr as *mut intmax_t) = w.written as intmax_t, - IntKind::PtrDiff => *(ptr as *mut ptrdiff_t) = w.written as ptrdiff_t, - IntKind::Size => *(ptr as *mut size_t) = w.written as size_t, - } - } - } - } - Ok(w.written as c_int) -} - -pub unsafe fn wprintf(w: W, format: WStr, ap: VaList) -> c_int { - inner_wprintf(w, format, ap).unwrap_or(-1) +pub unsafe fn wprintf(w: impl Write, format: WStr, ap: VaList) -> c_int { + inner_printf::(w, format, ap).unwrap_or(-1) }