use super::reader::Reader; use crate::{ c_str::Kind, header::stdio::printf::IntKind, platform::types::{ c_char, c_double, c_float, c_int, c_long, 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, }, }; use alloc::{string::String, vec::Vec}; use core::{ffi::VaList as va_list, iter::Peekable}; #[derive(PartialEq, Eq)] enum CharKind { Ascii, Wide, } fn next_char(lar: &mut Peekable>) -> Result { if let Some(c) = lar.next().transpose()? { char::try_from(c.into()).map_err(|_| -1) } else { Ok('\0') } } macro_rules! wc_as_char { ($c:ident) => { char::try_from($c.into()).map_err(|_| -1)? }; } #[expect(clippy::cast_lossless)] // not all users of `parse_type!` are lossless pub unsafe fn inner_scanf( mut r: Reader, format: Reader, mut ap: va_list, ) -> Result { let mut matched = 0; let mut character: char = '\0'; let mut skip_read = false; let mut count = 0; let mut format = format.peekable(); macro_rules! read { () => {{ match r.next() { None => false, Some(Ok(b)) => { character = wc_as_char!(b); count += 1; true } Some(Err(x)) => return Err(x), } }}; } macro_rules! maybe_read { () => { maybe_read!(inner false); }; (noreset) => { maybe_read!(inner); }; (inner $($placeholder:expr)*) => { if !skip_read && !read!() { match matched { 0 => return Ok(-1), a => return Ok(a), } } $(else { // Hacky way of having this optional skip_read = $placeholder; })* } } while format.peek().is_some() { let mut c = next_char(&mut format)?; if c == ' ' { maybe_read!(noreset); while (character).is_whitespace() { if !read!() { return Ok(matched); } } skip_read = true; } else if c != '%' { maybe_read!(); if c != character { return Ok(matched); } } else { c = next_char(&mut format)?; let mut ignore = false; if c == '*' { ignore = true; c = next_char(&mut format)?; } let mut width = String::new(); while c.is_ascii_digit() { width.push(c); c = next_char(&mut format)?; } let mut width = if width.is_empty() { None } else { match width.parse::() { Ok(n) => Some(n), Err(_) => return Err(-1), } }; // When an EOF occurs, eof is set, stuff is marked matched // as usual, and finally it is returned let mut eof = false; let mut kind = IntKind::Int; let mut c_kind = CharKind::Ascii; loop { match c { 'h' => { if kind == IntKind::Short || kind == IntKind::Byte { kind = IntKind::Byte; } else { kind = IntKind::Short; } } 'j' => kind = IntKind::IntMax, 'l' => { if kind == IntKind::Long || kind == IntKind::LongLong { kind = IntKind::LongLong; } else { kind = IntKind::Long; } } 'q' | 'L' => kind = IntKind::LongLong, 't' => kind = IntKind::PtrDiff, 'z' => kind = IntKind::Size, // If kind is Long, means we found a 'l' before finding 'c' or 's'. In this // case the format corresponds to a wide char/string 'c' | 's' if kind == IntKind::Long && !T::IS_THIN_NOT_WIDE => { c_kind = CharKind::Wide; break; } _ => break, } c = next_char(&mut format)?; } if c != 'n' { maybe_read!(noreset); } match c { '%' => { while (character).is_whitespace() { if !read!() { return Ok(matched); } } if character != '%' { return Err(matched); } else if !read!() { return Ok(matched); } } 'd' | 'i' | 'o' | 'u' | 'x' | 'X' | 'f' | 'e' | 'g' | 'E' | 'a' | 'p' => { while character.is_whitespace() { if !read!() { return Ok(matched); } } let pointer = c == 'p'; // Pointers aren't automatic, but we do want to parse "0x" let auto = c == 'i' || pointer; let float = c == 'f' || c == 'e' || c == 'g' || c == 'E' || c == 'a'; let mut radix = match c { 'o' => 8, 'x' | 'X' | 'p' => 16, _ => 10, }; let mut n = String::new(); let mut dot = false; while width.map(|w| w > 0).unwrap_or(true) && (('0'..='7').contains(&character) || (radix >= 10 && ('8'..='9').contains(&character)) || (float && !dot && character == '.') || (radix == 16 && (('a'..='f').contains(&character) || ('A'..='F').contains(&character)))) { if auto && n.is_empty() && character == '0' && width.map(|w| w > 0).unwrap_or(true) { if !pointer { radix = 8; } width = width.map(|w| w - 1); if !read!() { if character == '0' { // Parse last 0 as number instead of radix break; } return Ok(matched); } if width.map(|w| w > 0).unwrap_or(true) && (character == 'x' || character == 'X') { radix = 16; width = width.map(|w| w - 1); if width.map(|w| w > 0).unwrap_or(true) && !read!() { return Ok(matched); } } continue; } if character == '.' { // Don't allow another dot dot = true; } n.push(character); width = width.map(|w| w - 1); if width.map(|w| w > 0).unwrap_or(true) && !read!() { break; } } macro_rules! parse_type { (noformat $type:ident) => {{ let n = if n.is_empty() { 0 as $type } else { n.parse::<$type>().map_err(|_| 0)? }; if !ignore { unsafe { *ap.next_arg::<*mut $type>() = n }; matched += 1; } }}; (c_double) => { parse_type!(noformat c_double) }; (c_float) => { parse_type!(noformat c_float) }; ($type:ident) => { parse_type!($type, $type) }; ($type:ident, $final:ty) => {{ let n = if n.is_empty() { 0 as $type } else { $type::from_str_radix(&n, radix).map_err(|_| 0)? }; if !ignore { unsafe { *ap.next_arg::<*mut $final>() = n as $final }; matched += 1; } }}; } if float { if kind == IntKind::Long || kind == IntKind::LongLong { parse_type!(c_double); } else { parse_type!(c_float); } } else if c == 'p' { parse_type!(size_t, *mut c_void); } else { let unsigned = c == 'o' || c == 'u' || c == 'x' || c == 'X'; match kind { IntKind::Byte => { if unsigned { parse_type!(c_uchar); } else { parse_type!(c_char); } } IntKind::Short => { if unsigned { parse_type!(c_ushort) } else { parse_type!(c_short) } } IntKind::Int => { if unsigned { parse_type!(c_uint) } else { parse_type!(c_int) } } IntKind::Long => { if unsigned { parse_type!(c_ulong) } else { parse_type!(c_long) } } IntKind::LongLong => { if unsigned { parse_type!(c_ulonglong) } else { parse_type!(c_longlong) } } IntKind::IntMax => { if unsigned { parse_type!(uintmax_t) } else { parse_type!(intmax_t) } } IntKind::PtrDiff => parse_type!(ptrdiff_t), IntKind::Size => { if unsigned { parse_type!(size_t) } else { parse_type!(ssize_t) } } } } } 's' => { macro_rules! parse_string_type { ($type:ident) => { while character.is_whitespace() { if !read!() { return Ok(matched); } } let mut ptr: Option<*mut $type> = if ignore { None } else { Some(ap.next_arg()) }; while width.map(|w| w > 0).unwrap_or(true) && !character.is_whitespace() { if let Some(ref mut ptr) = ptr { **ptr = character as $type; *ptr = ptr.offset(1); } width = width.map(|w| w - 1); if width.map(|w| w > 0).unwrap_or(true) && !read!() { eof = true; break; } } // If we read the width, we end up in the last character of was // intended to be read, so we advance one if let Some(0) = width { read!(); } if let Some(ptr) = ptr { *ptr = 0; matched += 1; } }; } if c_kind == CharKind::Ascii { unsafe { parse_string_type!(c_char); } } else { unsafe { parse_string_type!(wchar_t); } } } 'c' => { macro_rules! parse_char_type { ($type:ident) => { let ptr: Option<*mut $type> = if ignore { None } else { Some(unsafe { ap.next_arg() }) }; for i in 0..width.unwrap_or(1) { if let Some(ptr) = ptr { unsafe { *ptr.add(i) = character as $type }; } width = width.map(|w| w - 1); if width.map(|w| w > 0).unwrap_or(true) && !read!() { eof = true; break; } } if ptr.is_some() { matched += 1; } }; } if c_kind == CharKind::Ascii { parse_char_type!(c_char); } else { parse_char_type!(wchar_t); } } '[' => { c = next_char(&mut format)?; let mut matches = Vec::new(); let invert = if c == '^' { c = next_char(&mut format)?; true } else { false }; let mut prev: u32; loop { matches.push(c); prev = c.into(); c = next_char(&mut format)?; if c == '-' { if prev as u8 == b']' { continue; } c = next_char(&mut format)?; if c == ']' { matches.push('-'); break; } prev += 1; while prev < c.into() { matches.push(char::try_from(prev).map_err(|_| -1)?); prev += 1; } } else if c == ']' { break; } } let mut ptr: Option<*mut c_char> = if ignore { None } else { Some(unsafe { ap.next_arg() }) }; // While we haven't used up all the width, and it matches let mut data_stored = false; while width.map(|w| w > 0).unwrap_or(true) && invert != matches.contains(&character) { if let Some(ref mut ptr) = ptr { unsafe { **ptr = character as c_char }; *ptr = unsafe { ptr.add(1) }; data_stored = true; } // Decrease the width, and read a new character unless the width is 0 width = width.map(|w| w - 1); if width.map(|w| w > 0).unwrap_or(true) && !read!() { // Reading a new character has failed, return after // actually marking this as matched eof = true; break; } } if data_stored { unsafe { *ptr.unwrap() = 0 }; matched += 1; } } 'n' => { if !ignore { unsafe { *ap.next_arg::<*mut c_int>() = count as c_int }; } } _ => return Err(-1), } if eof { return Ok(matched); } if width != Some(0) && c != 'n' { // It didn't hit the width, so an extra character was read and matched. // But this character did not match so let's reuse it. skip_read = true; } } } Ok(matched) } pub unsafe fn scanf(r: Reader, format: Reader, ap: va_list) -> c_int { match unsafe { inner_scanf(r, format, ap) } { Ok(n) => n, Err(n) => n, } }