Skip to main content

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}