relibc/header/stdio/scanf.rs
1use super::reader::Reader;
2use crate::{
3 c_str::Kind,
4 header::stdio::printf::IntKind,
5 platform::types::{
6 c_char, c_double, c_float, c_int, c_long, c_longlong, c_short, c_uchar, c_uint, c_ulong,
7 c_ulonglong, c_ushort, c_void, intmax_t, ptrdiff_t, size_t, ssize_t, uintmax_t, wchar_t,
8 },
9};
10use alloc::{string::String, vec::Vec};
11use core::{ffi::VaList as va_list, iter::Peekable};
12
13#[derive(PartialEq, Eq)]
14enum CharKind {
15 Ascii,
16 Wide,
17}
18
19fn next_char<T: Kind>(lar: &mut Peekable<Reader<'_, T>>) -> Result<char, c_int> {
20 if let Some(c) = lar.next().transpose()? {
21 char::try_from(c.into()).map_err(|_| -1)
22 } else {
23 Ok('\0')
24 }
25}
26
27macro_rules! wc_as_char {
28 ($c:ident) => {
29 char::try_from($c.into()).map_err(|_| -1)?
30 };
31}
32
33#[expect(clippy::cast_lossless)] // not all users of `parse_type!` are lossless
34pub unsafe fn inner_scanf<T: Kind>(
35 mut r: Reader<T>,
36 format: Reader<T>,
37 mut ap: va_list,
38) -> Result<c_int, c_int> {
39 let mut matched = 0;
40 let mut character: char = '\0';
41 let mut skip_read = false;
42 let mut count = 0;
43 let mut format = format.peekable();
44
45 macro_rules! read {
46 () => {{
47 match r.next() {
48 None => false,
49 Some(Ok(b)) => {
50 character = wc_as_char!(b);
51 count += 1;
52 true
53 }
54 Some(Err(x)) => return Err(x),
55 }
56 }};
57 }
58
59 macro_rules! maybe_read {
60 () => {
61 maybe_read!(inner false);
62 };
63 (noreset) => {
64 maybe_read!(inner);
65 };
66 (inner $($placeholder:expr)*) => {
67 if !skip_read && !read!() {
68 match matched {
69 0 => return Ok(-1),
70 a => return Ok(a),
71 }
72 }
73 $(else {
74 // Hacky way of having this optional
75 skip_read = $placeholder;
76 })*
77 }
78 }
79
80 while format.peek().is_some() {
81 let mut c = next_char(&mut format)?;
82
83 if c == ' ' {
84 maybe_read!(noreset);
85
86 while (character).is_whitespace() {
87 if !read!() {
88 return Ok(matched);
89 }
90 }
91
92 skip_read = true;
93 } else if c != '%' {
94 maybe_read!();
95 if c != character {
96 return Ok(matched);
97 }
98 } else {
99 c = next_char(&mut format)?;
100
101 let mut ignore = false;
102 if c == '*' {
103 ignore = true;
104 c = next_char(&mut format)?;
105 }
106
107 let mut width = String::new();
108 while c.is_ascii_digit() {
109 width.push(c);
110 c = next_char(&mut format)?;
111 }
112 let mut width = if width.is_empty() {
113 None
114 } else {
115 match width.parse::<usize>() {
116 Ok(n) => Some(n),
117 Err(_) => return Err(-1),
118 }
119 };
120
121 // When an EOF occurs, eof is set, stuff is marked matched
122 // as usual, and finally it is returned
123 let mut eof = false;
124
125 let mut kind = IntKind::Int;
126 let mut c_kind = CharKind::Ascii;
127 loop {
128 match c {
129 'h' => {
130 if kind == IntKind::Short || kind == IntKind::Byte {
131 kind = IntKind::Byte;
132 } else {
133 kind = IntKind::Short;
134 }
135 }
136 'j' => kind = IntKind::IntMax,
137 'l' => {
138 if kind == IntKind::Long || kind == IntKind::LongLong {
139 kind = IntKind::LongLong;
140 } else {
141 kind = IntKind::Long;
142 }
143 }
144 'q' | 'L' => kind = IntKind::LongLong,
145 't' => kind = IntKind::PtrDiff,
146 'z' => kind = IntKind::Size,
147 // If kind is Long, means we found a 'l' before finding 'c' or 's'. In this
148 // case the format corresponds to a wide char/string
149 'c' | 's' if kind == IntKind::Long && !T::IS_THIN_NOT_WIDE => {
150 c_kind = CharKind::Wide;
151 break;
152 }
153 _ => break,
154 }
155
156 c = next_char(&mut format)?;
157 }
158
159 if c != 'n' {
160 maybe_read!(noreset);
161 }
162 match c {
163 '%' => {
164 while (character).is_whitespace() {
165 if !read!() {
166 return Ok(matched);
167 }
168 }
169
170 if character != '%' {
171 return Err(matched);
172 } else if !read!() {
173 return Ok(matched);
174 }
175 }
176
177 'd' | 'i' | 'o' | 'u' | 'x' | 'X' | 'f' | 'e' | 'g' | 'E' | 'a' | 'p' => {
178 while character.is_whitespace() {
179 if !read!() {
180 return Ok(matched);
181 }
182 }
183
184 let pointer = c == 'p';
185 // Pointers aren't automatic, but we do want to parse "0x"
186 let auto = c == 'i' || pointer;
187 let float = c == 'f' || c == 'e' || c == 'g' || c == 'E' || c == 'a';
188
189 let mut radix = match c {
190 'o' => 8,
191 'x' | 'X' | 'p' => 16,
192 _ => 10,
193 };
194
195 let mut n = String::new();
196 let mut dot = false;
197
198 while width.map(|w| w > 0).unwrap_or(true)
199 && (('0'..='7').contains(&character)
200 || (radix >= 10 && ('8'..='9').contains(&character))
201 || (float && !dot && character == '.')
202 || (radix == 16
203 && (('a'..='f').contains(&character)
204 || ('A'..='F').contains(&character))))
205 {
206 if auto
207 && n.is_empty()
208 && character == '0'
209 && width.map(|w| w > 0).unwrap_or(true)
210 {
211 if !pointer {
212 radix = 8;
213 }
214 width = width.map(|w| w - 1);
215 if !read!() {
216 if character == '0' {
217 // Parse last 0 as number instead of radix
218 break;
219 }
220 return Ok(matched);
221 }
222 if width.map(|w| w > 0).unwrap_or(true)
223 && (character == 'x' || character == 'X')
224 {
225 radix = 16;
226 width = width.map(|w| w - 1);
227 if width.map(|w| w > 0).unwrap_or(true) && !read!() {
228 return Ok(matched);
229 }
230 }
231 continue;
232 }
233 if character == '.' {
234 // Don't allow another dot
235 dot = true;
236 }
237 n.push(character);
238 width = width.map(|w| w - 1);
239 if width.map(|w| w > 0).unwrap_or(true) && !read!() {
240 break;
241 }
242 }
243
244 macro_rules! parse_type {
245 (noformat $type:ident) => {{
246 let n = if n.is_empty() {
247 0 as $type
248 } else {
249 n.parse::<$type>().map_err(|_| 0)?
250 };
251 if !ignore {
252 unsafe { *ap.next_arg::<*mut $type>() = n };
253 matched += 1;
254 }
255 }};
256 (c_double) => {
257 parse_type!(noformat c_double)
258 };
259 (c_float) => {
260 parse_type!(noformat c_float)
261 };
262 ($type:ident) => {
263 parse_type!($type, $type)
264 };
265 ($type:ident, $final:ty) => {{
266 let n = if n.is_empty() {
267 0 as $type
268 } else {
269 $type::from_str_radix(&n, radix).map_err(|_| 0)?
270 };
271 if !ignore {
272 unsafe { *ap.next_arg::<*mut $final>() = n as $final };
273 matched += 1;
274 }
275 }};
276 }
277
278 if float {
279 if kind == IntKind::Long || kind == IntKind::LongLong {
280 parse_type!(c_double);
281 } else {
282 parse_type!(c_float);
283 }
284 } else if c == 'p' {
285 parse_type!(size_t, *mut c_void);
286 } else {
287 let unsigned = c == 'o' || c == 'u' || c == 'x' || c == 'X';
288
289 match kind {
290 IntKind::Byte => {
291 if unsigned {
292 parse_type!(c_uchar);
293 } else {
294 parse_type!(c_char);
295 }
296 }
297 IntKind::Short => {
298 if unsigned {
299 parse_type!(c_ushort)
300 } else {
301 parse_type!(c_short)
302 }
303 }
304 IntKind::Int => {
305 if unsigned {
306 parse_type!(c_uint)
307 } else {
308 parse_type!(c_int)
309 }
310 }
311 IntKind::Long => {
312 if unsigned {
313 parse_type!(c_ulong)
314 } else {
315 parse_type!(c_long)
316 }
317 }
318 IntKind::LongLong => {
319 if unsigned {
320 parse_type!(c_ulonglong)
321 } else {
322 parse_type!(c_longlong)
323 }
324 }
325 IntKind::IntMax => {
326 if unsigned {
327 parse_type!(uintmax_t)
328 } else {
329 parse_type!(intmax_t)
330 }
331 }
332 IntKind::PtrDiff => parse_type!(ptrdiff_t),
333 IntKind::Size => {
334 if unsigned {
335 parse_type!(size_t)
336 } else {
337 parse_type!(ssize_t)
338 }
339 }
340 }
341 }
342 }
343
344 's' => {
345 macro_rules! parse_string_type {
346 ($type:ident) => {
347 while character.is_whitespace() {
348 if !read!() {
349 return Ok(matched);
350 }
351 }
352
353 let mut ptr: Option<*mut $type> =
354 if ignore { None } else { Some(ap.next_arg()) };
355
356 while width.map(|w| w > 0).unwrap_or(true) && !character.is_whitespace()
357 {
358 if let Some(ref mut ptr) = ptr {
359 **ptr = character as $type;
360 *ptr = ptr.offset(1);
361 }
362 width = width.map(|w| w - 1);
363 if width.map(|w| w > 0).unwrap_or(true) && !read!() {
364 eof = true;
365 break;
366 }
367 }
368
369 // If we read the width, we end up in the last character of was
370 // intended to be read, so we advance one
371 if let Some(0) = width {
372 read!();
373 }
374
375 if let Some(ptr) = ptr {
376 *ptr = 0;
377 matched += 1;
378 }
379 };
380 }
381
382 if c_kind == CharKind::Ascii {
383 unsafe {
384 parse_string_type!(c_char);
385 }
386 } else {
387 unsafe {
388 parse_string_type!(wchar_t);
389 }
390 }
391 }
392
393 'c' => {
394 macro_rules! parse_char_type {
395 ($type:ident) => {
396 let ptr: Option<*mut $type> = if ignore {
397 None
398 } else {
399 Some(unsafe { ap.next_arg() })
400 };
401
402 for i in 0..width.unwrap_or(1) {
403 if let Some(ptr) = ptr {
404 unsafe { *ptr.add(i) = character as $type };
405 }
406 width = width.map(|w| w - 1);
407 if width.map(|w| w > 0).unwrap_or(true) && !read!() {
408 eof = true;
409 break;
410 }
411 }
412
413 if ptr.is_some() {
414 matched += 1;
415 }
416 };
417 }
418
419 if c_kind == CharKind::Ascii {
420 parse_char_type!(c_char);
421 } else {
422 parse_char_type!(wchar_t);
423 }
424 }
425
426 '[' => {
427 c = next_char(&mut format)?;
428
429 let mut matches = Vec::new();
430 let invert = if c == '^' {
431 c = next_char(&mut format)?;
432 true
433 } else {
434 false
435 };
436
437 let mut prev: u32;
438 loop {
439 matches.push(c);
440 prev = c.into();
441 c = next_char(&mut format)?;
442 if c == '-' {
443 if prev as u8 == b']' {
444 continue;
445 }
446 c = next_char(&mut format)?;
447 if c == ']' {
448 matches.push('-');
449 break;
450 }
451 prev += 1;
452 while prev < c.into() {
453 matches.push(char::try_from(prev).map_err(|_| -1)?);
454 prev += 1;
455 }
456 } else if c == ']' {
457 break;
458 }
459 }
460
461 let mut ptr: Option<*mut c_char> = if ignore {
462 None
463 } else {
464 Some(unsafe { ap.next_arg() })
465 };
466
467 // While we haven't used up all the width, and it matches
468 let mut data_stored = false;
469 while width.map(|w| w > 0).unwrap_or(true)
470 && invert != matches.contains(&character)
471 {
472 if let Some(ref mut ptr) = ptr {
473 unsafe { **ptr = character as c_char };
474 *ptr = unsafe { ptr.offset(1) };
475 data_stored = true;
476 }
477 // Decrease the width, and read a new character unless the width is 0
478 width = width.map(|w| w - 1);
479 if width.map(|w| w > 0).unwrap_or(true) && !read!() {
480 // Reading a new character has failed, return after
481 // actually marking this as matched
482 eof = true;
483 break;
484 }
485 }
486
487 if data_stored {
488 unsafe { *ptr.unwrap() = 0 };
489 matched += 1;
490 }
491 }
492 'n' => {
493 if !ignore {
494 unsafe { *ap.next_arg::<*mut c_int>() = count as c_int };
495 }
496 }
497 _ => return Err(-1),
498 }
499
500 if eof {
501 return Ok(matched);
502 }
503
504 if width != Some(0) && c != 'n' {
505 // It didn't hit the width, so an extra character was read and matched.
506 // But this character did not match so let's reuse it.
507 skip_read = true;
508 }
509 }
510 }
511 Ok(matched)
512}
513
514pub unsafe fn scanf<T: Kind>(r: Reader<T>, format: Reader<T>, ap: va_list) -> c_int {
515 match unsafe { inner_scanf(r, format, ap) } {
516 Ok(n) => n,
517 Err(n) => n,
518 }
519}