Procházet zdrojové kódy

[rt_vsscanf] upgrade version and add test case
from: https://github.com/phoenix-rtos/libphoenix/blob/master/stdio/scanf.c
BSD-3-Clause license

Meco Man před 7 měsíci
rodič
revize
b3b99082f9
2 změnil soubory, kde provedl 868 přidání a 420 odebrání
  1. 618 420
      src/klibc/rt_vsscanf.c
  2. 250 0
      src/klibc/utest/TC_rt_sscanf.c

+ 618 - 420
src/klibc/rt_vsscanf.c

@@ -1,502 +1,700 @@
 /*
- * Copyright (c) 2006-2024, RT-Thread Development Team
+ * Copyright (c) 2006-2025, RT-Thread Development Team
  *
  * SPDX-License-Identifier: Apache-2.0
  *
  * Change Logs:
  * Date           Author              Notes
  * 2024-11-24     Meco Man            port to klibc
+ * 2025-01-04     Meco Man            using Phoenix version
  */
 
-#include <rtklibc.h>
-
 /*
- * Copyright (c) 2012 Petteri Aimonen <jpa at blc.mail.kapsi.fi>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *   3. Neither the name of Kustaa Nyholm or SpareTimeLabs nor the
- *      names of its contributors may be used to endorse or promote products
- *      derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Copyright 2017, 2022-2023 Phoenix Systems
+ * Author: Adrian Kepka, Gerard Swiderski
  */
 
+#include <rtthread.h>
+#include <stdlib.h> /* for strtod */
 #include <ctype.h> /* for isspace */
-#include <stdarg.h>
-#include <limits.h> /* for CHAR_BIT */
-
-static inline int digitval(int ch)
+#include <stdarg.h> /* for va_list */
+
+#define FORMAT_NIL_STR     "(nil)"
+#define FORMAT_NIL_STR_LEN (sizeof(FORMAT_NIL_STR) - 1)
+
+#define LONG       0x01   /* l: long or double */
+#define LONGDOUBLE 0x02   /* L: long double */
+#define SHORT      0x04   /* h: short */
+#define SUPPRESS   0x08   /* *: suppress assignment */
+#define POINTER    0x10   /* p: void * (as hex) */
+#define NOSKIP     0x20   /* [ or c: do not skip blanks */
+#define LONGLONG   0x400  /* ll: long long (+ deprecated q: quad) */
+#define PTRDIFF    0x800  /* t: ptrdiff_t */
+#define SHORTSHORT 0x4000 /* hh: char */
+#define UNSIGNED   0x8000 /* %[oupxX] conversions */
+
+#define SIGNOK     0x40  /* +/- is (still) legal */
+#define NDIGITS    0x80  /* no digits detected */
+#define PFXOK      0x100 /* 0x prefix is (still) legal */
+#define NZDIGITS   0x200 /* no zero digits detected */
+
+#define CT_CHAR    0 /* %c conversion */
+#define CT_CCL     1 /* %[...] conversion */
+#define CT_STRING  2 /* %s conversion */
+#define CT_INT     3 /* %[dioupxX] conversion */
+#define CT_FLOAT   4 /* %[aefgAEFG] conversion */
+#define CT_NONE    5 /* No conversion (ex. %n) */
+
+static const unsigned char *__sccl(char *tab, const unsigned char *fmt)
 {
-    if (ch >= '0' && ch <= '9') {
-        return ch - '0';
-    } else if (ch >= 'A' && ch <= 'Z') {
-        return ch - 'A' + 10;
-    } else if (ch >= 'a' && ch <= 'z') {
-        return ch - 'a' + 10;
-    } else {
-        return -1;
+    int c, n, v;
+
+    c = *fmt++;
+    if (c == '^') {
+        v = 1;
+        c = *fmt++;
+    }
+    else {
+        v = 0;
     }
-}
 
-static rt_size_t _strntoumax(const char *nptr, char **endptr, int base, rt_size_t n)
-{
-    int minus = 0;
-    rt_size_t v = 0;
-    int d;
+    rt_memset(tab, (uint8_t)v, 256);
 
-    while (n && isspace((unsigned char)*nptr)) {
-        nptr++;
-        n--;
+    if (c == 0) {
+        return (fmt - 1);
     }
 
-    /* Single optional + or - */
-    if (n) {
-        char c = *nptr;
-        if (c == '-' || c == '+') {
-            minus = (c == '-');
-            nptr++;
-            n--;
-        }
-    }
+    v = 1 - v;
+    tab[c] = v;
+    for (;;) {
+        n = *fmt++;
+        switch (n) {
 
-    if (base == 0) {
-        if (n >= 2 && nptr[0] == '0' &&
-            (nptr[1] == 'x' || nptr[1] == 'X')) {
-            n -= 2;
-            nptr += 2;
-            base = 16;
-        } else if (n >= 1 && nptr[0] == '0') {
-            n--;
-            nptr++;
-            base = 8;
-        } else {
-            base = 10;
-        }
-    } else if (base == 16) {
-        if (n >= 2 && nptr[0] == '0' &&
-            (nptr[1] == 'x' || nptr[1] == 'X')) {
-            n -= 2;
-            nptr += 2;
-        }
-    }
+            case 0:
+                return (fmt - 1);
 
-    while (n && (d = digitval(*nptr)) >= 0 && d < base) {
-        v = v * base + d;
-        n--;
-        nptr++;
-    }
+            case '-':
+                n = *fmt;
+                if ((n == ']') || (n < c)) {
+                    c = '-';
+                    tab[c] = v;
+                    break;
+                }
+                fmt++;
 
-    if (endptr)
-        *endptr = (char *)nptr;
+                do {
+                    tab[++c] = v;
+                } while (c < n);
+                c = n;
+                break;
 
-    return minus ? -v : v;
-}
+            case ']':
+                return (fmt);
 
-#ifndef LONG_BIT
-#define LONG_BIT (CHAR_BIT*sizeof(long))
-#endif
-
-enum flags {
-    FL_SPLAT = 0x01,    /* Drop the value, do not assign */
-    FL_INV   = 0x02,    /* Character-set with inverse */
-    FL_WIDTH = 0x04,    /* Field width specified */
-    FL_MINUS = 0x08,    /* Negative number */
-};
-
-enum ranks {
-    rank_char     = -2,
-    rank_short    = -1,
-    rank_int      = 0,
-    rank_long     = 1,
-    rank_longlong = 2,
-    rank_ptr      = INT_MAX /* Special value used for pointers */
-};
-
-#define MIN_RANK    rank_char
-#define MAX_RANK    rank_longlong
-
-#define INTMAX_RANK rank_longlong
-#define SIZE_T_RANK rank_long
-#define PTRDIFF_T_RANK  rank_long
-
-enum bail {
-    bail_none = 0,      /* No error condition */
-    bail_eof,       /* Hit EOF */
-    bail_err        /* Conversion mismatch */
-};
-
-static inline const char *skipspace(const char *p)
-{
-    while (isspace((unsigned char)*p))
-        p++;
-    return p;
+            default:
+                c = n;
+                tab[c] = v;
+                break;
+        }
+    }
 }
 
-static inline void _set_bit(unsigned long *bitmap, unsigned int bit)
+static int scanf_parse(char *ccltab, const char *inp, int *inr, char const *fmt0, va_list ap)
 {
-    bitmap[bit / LONG_BIT] |= 1UL << (bit % LONG_BIT);
-}
+    const unsigned char *fmt = (const unsigned char *)fmt0;
+    int c, n, flags, nassigned, nconversions, nread, base;
+    rt_size_t width;
+    char *p, *p0;
+    char buf[32];
+
+    static const short basefix[17] = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+
+    *inr = rt_strlen(inp);
+
+    nassigned = 0;
+    nconversions = 0;
+    nread = 0;
+    base = 0;
+    for (;;) {
+        int convType = CT_NONE;
+        c = *fmt++;
+        if (c == '\0') {
+            return (nassigned);
+        }
 
-static inline int _test_bit(unsigned long *bitmap, unsigned int bit)
-{
-    return (int)(bitmap[bit / LONG_BIT] >> (bit % LONG_BIT)) & 1;
-}
+        if (isspace(c) != 0) {
+            while ((*inr > 0) && (isspace((int)*inp) != 0)) {
+                nread++;
+                (*inr)--;
+                inp++;
+            }
+            continue;
+        }
 
-int rt_vsscanf(const char *buffer, const char *format, va_list ap)
-{
-    const char *p = format;
-    char ch;
-    unsigned char uc;
-    const char *q = buffer;
-    const char *qq;
-    rt_size_t val = 0;
-    int rank = rank_int;    /* Default rank */
-    unsigned int width = UINT_MAX;
-    int base;
-    enum flags flags = 0;
-    enum {
-        st_normal,  /* Ground state */
-        st_flags,   /* Special flags */
-        st_width,   /* Field width */
-        st_modifiers,   /* Length or conversion modifiers */
-        st_match_init,  /* Initial state of %[ sequence */
-        st_match,   /* Main state of %[ sequence */
-        st_match_range, /* After - in a %[ sequence */
-    } state = st_normal;
-    char *sarg = NULL;  /* %s %c or %[ string argument */
-    enum bail bail = bail_none;
-    int sign;
-    int converted = 0;  /* Successful conversions */
-    unsigned long matchmap[((1 << CHAR_BIT) + (LONG_BIT - 1)) / LONG_BIT];
-    int matchinv = 0;   /* Is match map inverted? */
-    unsigned char range_start = 0;
-    (void)sign;
-
-    while ((ch = *p++) && !bail) {
-        switch (state) {
-        case st_normal:
-            if (ch == '%') {
-                state = st_flags;
-                flags = 0;
-                rank = rank_int;
-                width = UINT_MAX;
-            } else if (isspace((unsigned char)ch)) {
-                q = skipspace(q);
-            } else {
-                if (*q == ch)
-                    q++;
-                else
-                    bail = bail_err; /* Match failure */
+        if (c != '%') {
+            if (*inr <= 0) {
+                return (nconversions != 0 ? nassigned : -1);
             }
-            break;
 
-        case st_flags:
-            switch (ch) {
-            case '*':
-                flags |= FL_SPLAT;
-                break;
-            case '0': /* falls-through */
-            case '1': /* falls-through */
-            case '2': /* falls-through */
-            case '3': /* falls-through */
-            case '4': /* falls-through */
-            case '5': /* falls-through */
-            case '6': /* falls-through */
-            case '7': /* falls-through */
-            case '8': /* falls-through */
-            case '9':
-                width = (ch - '0');
-                state = st_width;
-                flags |= FL_WIDTH;
-                break;
-            default:
-                state = st_modifiers;
-                p--;    /* Process this character again */
-                break;
+            if (*inp != c) {
+                return nassigned;
             }
-            break;
 
-        case st_width:
-            if (ch >= '0' && ch <= '9') {
-                width = width * 10 + (ch - '0');
-            } else {
-                state = st_modifiers;
-                p--;    /* Process this character again */
+            nread++;
+            (*inr)--;
+            inp++;
+            continue;
+        }
+
+        width = 0;
+        flags = 0;
+        for (;;) {
+            c = *fmt++;
+            if (c == '\0') {
+                return nassigned;
             }
-            break;
 
-        case st_modifiers:
-            switch (ch) {
-                /* Length modifiers - nonterminal sequences */
-            case 'h':
-                rank--; /* Shorter rank */
-                break;
-            case 'l':
-                rank++; /* Longer rank */
-                break;
-            case 'j':
-                rank = INTMAX_RANK;
-                break;
-            case 'z':
-                rank = SIZE_T_RANK;
-                break;
-            case 't':
-                rank = PTRDIFF_T_RANK;
-                break;
-            case 'L':
-            case 'q':
-                rank = rank_longlong;   /* long double/long long */
+            if (c == '%') {
+                if (*inr <= 0) {
+                    return (nconversions != 0 ? nassigned : -1);
+                }
+
+                if (*inp != c) {
+                    return nassigned;
+                }
+
+                nread++;
+                (*inr)--;
+                inp++;
                 break;
+            }
 
-            default:
-                /* Output modifiers - terminal sequences */
-                /* Next state will be normal */
-                state = st_normal;
-
-                /* Canonicalize rank */
-                if (rank < MIN_RANK)
-                    rank = MIN_RANK;
-                else if (rank > MAX_RANK)
-                    rank = MAX_RANK;
-
-                switch (ch) {
-                case 'P':   /* Upper case pointer */
-                case 'p':   /* Pointer */
-                    rank = rank_ptr;
-                    base = 0;
-                    sign = 0;
-                    goto scan_int;
+            switch (c) {
+                case '*':
+                    flags |= SUPPRESS;
+                    continue;
 
-                case 'i':   /* Base-independent integer */
-                    base = 0;
-                    sign = 1;
-                    goto scan_int;
+                case 'l':
+                    if ((flags & LONG) != 0) {
+                        flags &= ~LONG;
+                        flags |= LONGLONG;
+                    }
+                    else {
+                        flags |= LONG;
+                    }
+                    continue;
+
+                case 'L':
+                    flags |= LONGDOUBLE;
+                    continue;
 
-                case 'd':   /* Decimal integer */
+                case 'q':
+                case 'j':
+                    flags |= LONGLONG;
+                    continue;
+
+                case 't':
+                    flags |= PTRDIFF;
+                    continue;
+
+                case 'z':
+                    if (sizeof(rt_size_t) == sizeof(uint64_t)) {
+                        flags |= LONGLONG;
+                    }
+                    continue;
+
+                case 'h':
+                    if ((flags & SHORT) != 0) {
+                        flags &= ~SHORT;
+                        flags |= SHORTSHORT;
+                    }
+                    else {
+                        flags |= SHORT;
+                    }
+                    continue;
+
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                case '8':
+                case '9':
+                    width = width * 10 + c - '0';
+                    continue;
+                default:
+                    break;
+            }
+
+            /* conversions */
+            switch (c) {
+                case 'd':
+                    convType = CT_INT;
                     base = 10;
-                    sign = 1;
-                    goto scan_int;
+                    break;
 
-                case 'o':   /* Octal integer */
+                case 'i':
+                    convType = CT_INT;
+                    base = 0;
+                    break;
+
+                case 'o':
+                    convType = CT_INT;
+                    flags |= UNSIGNED;
                     base = 8;
-                    sign = 0;
-                    goto scan_int;
+                    break;
 
-                case 'u':   /* Unsigned decimal integer */
+                case 'u':
+                    convType = CT_INT;
+                    flags |= UNSIGNED;
                     base = 10;
-                    sign = 0;
-                    goto scan_int;
+                    break;
 
-                case 'x':   /* Hexadecimal integer */
                 case 'X':
+                case 'x':
+                    flags |= PFXOK; /* enable 0x prefixing */
+                    convType = CT_INT;
+                    flags |= UNSIGNED;
                     base = 16;
-                    sign = 0;
-                    goto scan_int;
+                    break;
+
+                case 'A':
+                case 'E':
+                case 'F':
+                case 'G':
+                case 'a':
+                case 'e':
+                case 'f':
+                case 'g':
+                    convType = CT_FLOAT;
+                    break;
+
+
+                case 's':
+                    convType = CT_STRING;
+                    break;
 
-                case 'n':   /* # of characters consumed */
-                    val = (q - buffer);
-                    goto set_integer;
+                case '[':
+                    fmt = __sccl(ccltab, fmt);
+                    flags |= NOSKIP;
+                    convType = CT_CCL;
+                    break;
+
+                case 'c':
+                    flags |= NOSKIP;
+                    convType = CT_CHAR;
+                    break;
 
-                      scan_int:
-                    q = skipspace(q);
-                    if (!*q) {
-                        bail = bail_eof;
+                case 'p':
+                    flags |= POINTER | PFXOK | UNSIGNED;
+                    convType = CT_INT;
+                    base = 16;
+                    break;
+
+                case 'n':
+                    nconversions++;
+                    if ((flags & SUPPRESS) != 0) {
                         break;
                     }
-                    val =
-                        _strntoumax(q, (char **)&qq, base,
-                               width);
-                    if (qq == q) {
-                        bail = bail_err;
-                        break;
+                    if ((flags & SHORTSHORT) != 0) {
+                        *va_arg(ap, char *) = nread;
+                    }
+                    else if ((flags & SHORT) != 0) {
+                        *va_arg(ap, short *) = nread;
+                    }
+                    else if ((flags & LONG) != 0) {
+                        *va_arg(ap, long *) = nread;
+                    }
+                    else if ((flags & LONGLONG) != 0) {
+                        *va_arg(ap, long long *) = nread;
+                    }
+                    else if ((flags & PTRDIFF) != 0) {
+                        *va_arg(ap, ptrdiff_t *) = nread;
+                    }
+                    else {
+                        *va_arg(ap, int *) = nread;
+                    }
+                    break;
+
+                default:
+                    /* Character not a conversion specifier; end parsing */
+                    return nassigned;
+            }
+
+            break;
+        }
+
+        if (convType == CT_NONE) {
+            continue;
+        }
+
+        if (*inr <= 0) {
+            return (nconversions != 0 ? nassigned : -1);
+        }
+
+        if ((flags & NOSKIP) == 0) {
+            while (isspace((int)*inp) != 0) {
+                nread++;
+                if (--(*inr) > 0) {
+                    inp++;
+                }
+                else {
+                    return (nconversions != 0 ? nassigned : -1);
+                }
+            }
+        }
+
+        /* do the conversion */
+        switch (convType) {
+            case CT_CHAR:
+                if (width == 0) {
+                    width = 1;
+                }
+
+                if (*inr <= 0) {
+                    return (nconversions != 0 ? nassigned : -1);
+                }
+
+                if (width > *inr) {
+                    width = *inr;
+                }
+
+                if ((flags & SUPPRESS) == 0) {
+                    rt_memcpy(va_arg(ap, char *), inp, width);
+                    nassigned++;
+                }
+
+                *inr -= width;
+                inp += width;
+                nread += width;
+                nconversions++;
+                break;
+
+            case CT_CCL:
+                if (width == 0) {
+                    width = (rt_size_t)~0;
+                }
+                if ((flags & SUPPRESS) != 0) {
+                    n = 0;
+                    while (ccltab[(unsigned char)*inp] != 0) {
+                        n++;
+                        (*inr)--;
+                        inp++;
+                        if (--width == 0) {
+                            break;
+                        }
+                        if (*inr <= 0) {
+                            if (n == 0) {
+                                return (nconversions != 0 ? nassigned : -1);
+                            }
+                            break;
+                        }
                     }
-                    q = qq;
-                    if (!(flags & FL_SPLAT))
-                        converted++;
-                    /* fall through */
-
-                      set_integer:
-                    if (!(flags & FL_SPLAT)) {
-                        switch (rank) {
-                        case rank_char:
-                            *va_arg(ap,
-                                unsigned char *)
-                                = val;
+                    if (n == 0) {
+                        return nassigned;
+                    }
+                }
+                else {
+                    p0 = p = va_arg(ap, char *);
+                    while (ccltab[(unsigned char)*inp] != 0) {
+                        (*inr)--;
+                        *p++ = *inp++;
+                        if (--width == 0) {
                             break;
-                        case rank_short:
-                            *va_arg(ap,
-                                unsigned short
-                                *) = val;
+                        }
+                        if (*inr <= 0) {
+                            if (p == p0) {
+                                return (nconversions != 0 ? nassigned : -1);
+                            }
                             break;
-                        case rank_int:
-                            *va_arg(ap,
-                                unsigned int *)
-                                = val;
+                        }
+                    }
+                    n = p - p0;
+                    if (n == 0) {
+                        return nassigned;
+                    }
+                    *p = 0;
+                    nassigned++;
+                }
+                nread += n;
+                nconversions++;
+                break;
+
+            case CT_STRING:
+                if (width == 0) {
+                    width = (rt_size_t)~0;
+                }
+                if ((flags & SUPPRESS) != 0) {
+                    while (isspace((int)*inp) == 0) {
+                        nread++;
+                        (*inr)--;
+                        inp++;
+                        if (--width == 0) {
                             break;
-                        case rank_long:
-                            *va_arg(ap,
-                                unsigned long *)
-                                = val;
+                        }
+                        if (*inr <= 0) {
                             break;
-                        case rank_longlong:
-                            *va_arg(ap,
-                                unsigned long
-                                long *) = val;
+                        }
+                    }
+                }
+                else {
+                    p0 = p = va_arg(ap, char *);
+                    while (isspace((int)*inp) == 0) {
+                        (*inr)--;
+                        *p++ = *inp++;
+                        if (--width == 0) {
                             break;
-                        case rank_ptr:
-                            *va_arg(ap, void **) =
-                                (void *)
-                                (uintptr_t)val;
+                        }
+                        if (*inr <= 0) {
                             break;
                         }
                     }
+                    *p = 0;
+                    nread += p - p0;
+                    nassigned++;
+                }
+                nconversions++;
+                continue;
+
+            case CT_INT:
+                if (((flags & POINTER) != 0) && ((*inr) >= FORMAT_NIL_STR_LEN) && (rt_strncmp(FORMAT_NIL_STR, inp, FORMAT_NIL_STR_LEN) == 0)) {
+                    *va_arg(ap, void **) = RT_NULL;
+                    nassigned++;
+                    nconversions++;
+                    nread += FORMAT_NIL_STR_LEN;
+                    inp += FORMAT_NIL_STR_LEN;
+                    (*inr) -= FORMAT_NIL_STR_LEN;
                     break;
+                }
 
-                case 'c':   /* Character */
-                    /* Default width == 1 */
-                    width = (flags & FL_WIDTH) ? width : 1;
-                    if (flags & FL_SPLAT) {
-                        while (width--) {
-                            if (!*q) {
-                                bail = bail_eof;
-                                break;
+                if (--width > (sizeof(buf) - 2)) {
+                    width = sizeof(buf) - 2;
+                }
+                width++;
+
+                if ((flags & SUPPRESS) != 0) {
+                    width = ~0;
+                }
+
+                flags |= SIGNOK | NDIGITS | NZDIGITS;
+                for (p = buf; width; width--) {
+                    int ok = 0;
+                    c = *inp;
+                    switch (c) {
+                        case '0':
+                            if (base == 0) {
+                                base = 8;
+                                flags |= PFXOK;
                             }
-                        }
-                    } else {
-                        sarg = va_arg(ap, char *);
-                        while (width--) {
-                            if (!*q) {
-                                bail = bail_eof;
+                            if ((flags & NZDIGITS) != 0) {
+                                flags &= ~(SIGNOK | NZDIGITS | NDIGITS);
+                            }
+                            else {
+                                flags &= ~(SIGNOK | PFXOK | NDIGITS);
+                            }
+                            ok = 1;
+                            break;
+
+                        case '1':
+                        case '2':
+                        case '3':
+                        case '4':
+                        case '5':
+                        case '6':
+                        case '7':
+                            base = basefix[base];
+                            flags &= ~(SIGNOK | PFXOK | NDIGITS);
+                            ok = 1;
+                            break;
+
+                        case '8':
+                        case '9':
+                            base = basefix[base];
+                            if (base <= 8) {
+                                break; /* not legal here */
+                            }
+                            flags &= ~(SIGNOK | PFXOK | NDIGITS);
+                            ok = 1;
+                            break;
+
+                        case 'A':
+                        case 'B':
+                        case 'C':
+                        case 'D':
+                        case 'E':
+                        case 'F':
+                        case 'a':
+                        case 'b':
+                        case 'c':
+                        case 'd':
+                        case 'e':
+                        case 'f':
+                            if (base <= 10) {
                                 break;
                             }
-                            *sarg++ = *q++;
-                        }
-                        if (!bail)
-                            converted++;
+                            flags &= ~(SIGNOK | PFXOK | NDIGITS);
+                            ok = 1;
+                            break;
+
+                        case '+':
+                        case '-':
+                            if ((flags & SIGNOK) != 0) {
+                                flags &= ~SIGNOK;
+                                ok = 1;
+                            }
+                            break;
+
+                        case 'x':
+                        case 'X':
+                            if (((flags & PFXOK) != 0) && (p == buf + 1)) {
+                                base = 16; /* if %i */
+                                flags &= ~PFXOK;
+                                ok = 1;
+                            }
+                            break;
                     }
-                    break;
+                    if (!ok)
+                        break;
 
-                case 's':   /* String */
-                    uc = 1; /* Anything nonzero */
-                    if (flags & FL_SPLAT) {
-                        while (width-- && (uc = *q) &&
-                               !isspace(uc)) {
-                            q++;
-                        }
-                    } else {
-                        char *sp;
-                        sp = sarg = va_arg(ap, char *);
-                        while (width-- && (uc = *q) &&
-                               !isspace(uc)) {
-                            *sp++ = uc;
-                            q++;
-                        }
-                        if (sarg != sp) {
-                            /* Terminate output */
-                            *sp = '\0';
-                            converted++;
-                        }
+                    if ((flags & SUPPRESS) == 0) {
+                        *p++ = c;
                     }
-                    if (!uc)
-                        bail = bail_eof;
-                    break;
+                    if (--(*inr) > 0) {
+                        inp++;
+                    }
+                    else {
+                        break;
+                    }
+                }
+                if ((flags & NDIGITS) != 0) {
+                    return (nconversions != 0 ? nassigned : -1);
+                }
 
-                case '[':   /* Character range */
-                    sarg = (flags & FL_SPLAT) ? NULL
-                        : va_arg(ap, char *);
-                    state = st_match_init;
-                    matchinv = 0;
-                    rt_memset(matchmap, 0, sizeof matchmap);
-                    break;
+                c = ((unsigned char *)p)[-1];
+                if ((c == 'x') || (c == 'X')) {
+                    --p;
+                    inp--;
+                    (*inr)++;
+                }
 
-                case '%':   /* %% sequence */
-                    if (*q == '%')
-                        q++;
-                    else
-                        bail = bail_err;
-                    break;
+                if ((flags & SUPPRESS) == 0) {
+                    uint64_t res;
 
-                default:    /* Anything else */
-                    /* Unknown sequence */
-                    bail = bail_err;
-                    break;
+                    *p = 0;
+                    if ((flags & UNSIGNED) == 0) {
+                        res = strtoll(buf, (char **)RT_NULL, base);
+                    }
+                    else {
+                        res = strtoull(buf, (char **)RT_NULL, base);
+                    }
+                    if ((flags & POINTER) != 0) {
+                        *va_arg(ap, void **) = (void *)(unsigned long)res;
+                    }
+                    else if ((flags & SHORTSHORT) != 0) {
+                        *va_arg(ap, char *) = res;
+                    }
+                    else if ((flags & SHORT) != 0) {
+                        *va_arg(ap, short *) = res;
+                    }
+                    else if ((flags & LONG) != 0) {
+                        *va_arg(ap, long *) = res;
+                    }
+                    else if ((flags & LONGLONG) != 0) {
+                        *va_arg(ap, long long *) = res;
+                    }
+                    else if ((flags & PTRDIFF) != 0) {
+                        *va_arg(ap, ptrdiff_t *) = res;
+                    }
+                    else {
+                        *va_arg(ap, int *) = res;
+                    }
+                    nassigned++;
                 }
-            }
-            break;
 
-        case st_match_init: /* Initial state for %[ match */
-            if (ch == '^' && !(flags & FL_INV)) {
-                matchinv = 1;
-            } else {
-                _set_bit(matchmap, (unsigned char)ch);
-                state = st_match;
-            }
-            break;
+                nread += p - buf;
+                nconversions++;
+                break;
 
-        case st_match:  /* Main state for %[ match */
-            if (ch == ']') {
-                goto match_run;
-            } else if (ch == '-') {
-                range_start = (unsigned char)ch;
-                state = st_match_range;
-            } else {
-                _set_bit(matchmap, (unsigned char)ch);
-            }
-            break;
+            case CT_FLOAT: {
+                union {
+                    float f;
+                    double d;
+                    long double ld;
+                } res;
+
+                const char *srcbuf = inp;
+                if ((width != 0) && (width < *inr)) {
+                    /* TODO: handle larger widths */
+                    if (width > (sizeof(buf) - 1)) {
+                        return (nconversions != 0 ? nassigned : -1);
+                    }
 
-        case st_match_range:    /* %[ match after - */
-            if (ch == ']') {
-                /* - was last character */
-                _set_bit(matchmap, (unsigned char)'-');
-                goto match_run;
-            } else {
-                int i;
-                for (i = range_start; i < (unsigned char)ch;
-                     i++)
-                    _set_bit(matchmap, i);
-                state = st_match;
-            }
-            break;
+                    rt_memcpy(buf, inp, width);
+                    buf[width] = '\0';
+                    srcbuf = buf;
+                }
 
-              match_run:    /* Match expression finished */
-            qq = q;
-            uc = 1; /* Anything nonzero */
-            while (width && (uc = *q)
-                   && _test_bit(matchmap, uc)^matchinv) {
-                if (sarg)
-                    *sarg++ = uc;
-                q++;
-            }
-            if (q != qq && sarg) {
-                *sarg = '\0';
-                converted++;
-            } else {
-                bail = bail_err;
+                int is_zero;
+                if ((flags & LONGDOUBLE) != 0) {
+                    res.ld = strtold(srcbuf, &p);
+                    is_zero = res.ld == 0;
+                }
+                else if ((flags & LONG) != 0) {
+                    res.d = strtod(srcbuf, &p);
+                    is_zero = res.d == 0;
+                }
+                else {
+                    res.f = strtof(srcbuf, &p);
+                    is_zero = res.f == 0;
+                }
+
+                if (is_zero && (srcbuf == p)) {
+                    return (nconversions != 0 ? nassigned : -1);
+                }
+
+                int consumed = p - srcbuf;
+                *inr -= consumed;
+                inp += consumed;
+                nread += consumed;
+                nconversions++;
+                if ((flags & SUPPRESS) == 0) {
+                    if ((flags & LONGDOUBLE) != 0) {
+                        *va_arg(ap, long double *) = res.ld;
+                    }
+                    else if ((flags & LONG) != 0) {
+                        *va_arg(ap, double *) = res.d;
+                    }
+                    else {
+                        *va_arg(ap, float *) = res.f;
+                    }
+
+                    nassigned++;
+                }
+
+                break;
             }
-            if (!uc)
-                bail = bail_eof;
-            break;
+
+            default:
+                break;
         }
     }
+    /* never reached */
+}
+
+int rt_vsscanf(const char *str, const char *format, va_list ap)
+{
+    int ret, nremain;
+    char *ccltab = rt_malloc(256);
+
+    if (ccltab == RT_NULL) {
+        return -1;
+    }
 
-    if (bail == bail_eof && !converted)
-        converted = -1; /* Return EOF (-1) */
+    ret = scanf_parse(ccltab, str, &nremain, format, ap);
+    rt_free(ccltab);
 
-    return converted;
+    return ret;
 }

+ 250 - 0
src/klibc/utest/TC_rt_sscanf.c

@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2006-2025, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2025-01-04     Meco Man     the first version
+ */
+
+#include <rtklibc.h>
+#include "utest.h"
+
+static void TC_rt_sscanf_char(void)
+{
+    const char str[] = "A B";
+    char a, b;
+    rt_sscanf(str, "%c %c", &a, &b);
+    uassert_true(a == 'A' && b == 'B');
+    /* Move to the next character after space for the second %c */
+    rt_sscanf(str + 2, "%c", &b);
+    uassert_true(b == 'B');
+}
+
+static void TC_rt_sscanf_basic_int(void)
+{
+    const char str[] = "12345";
+    int value;
+    int result = rt_sscanf(str, "%d", &value);
+    uassert_int_equal(result, 1);
+    uassert_int_equal(value, 12345);
+}
+
+static void TC_rt_sscanf_basic_float(void)
+{
+    const char str[] = "123.45";
+    float value;
+    int result = rt_sscanf(str, "%f", &value);
+    uassert_int_equal(result, 1);
+    uassert_in_range(value, 123.445, 123.455); /* Floating point comparison with tolerance */
+}
+
+static void TC_rt_sscanf_basic_string(void)
+{
+    const char str[] = "Hello, World!";
+    char buffer[20];
+    int result = rt_sscanf(str, "%s", buffer);
+    uassert_int_equal(result, 1);
+    uassert_str_equal(buffer, "Hello,");
+}
+
+static void TC_rt_sscanf_string_with_space(void)
+{
+    const char str[] = "Hello   World";
+    char a[20];
+    rt_sscanf(str, "%*s %s", a);
+    uassert_str_equal(a, "World");
+}
+
+static void TC_rt_sscanf_basic_char(void)
+{
+    const char str[] = "A";
+    char value;
+    int result = rt_sscanf(str, "%c", &value);
+    uassert_int_equal(result, 1);
+    uassert_int_equal(value, 'A');
+}
+
+static void TC_rt_sscanf_hex_1(void)
+{
+    const char str[] = "0x1A3F";
+    int value;
+    int result = rt_sscanf(str, "%x", &value);
+    uassert_int_equal(result, 1);
+    uassert_int_equal(value, 0x1A3F);
+}
+
+static void TC_rt_sscanf_hex_2(void)
+{
+    const char str[] = "0x1A 0XFF";
+    int a, b;
+    rt_sscanf(str, "%x %x", &a, &b);
+    uassert_true(a == 0x1A && b == 0XFF);
+}
+
+static void TC_rt_sscanf_oct_1(void)
+{
+    const char str[] = "0755";
+    int value;
+    int result = rt_sscanf(str, "%o", &value);
+    uassert_int_equal(result, 1);
+    uassert_int_equal(value, 0755);
+}
+
+static void TC_rt_sscanf_oct_2(void)
+{
+    const char str[] = "012 077";
+    int a, b;
+    rt_sscanf(str, "%o %o", &a, &b);
+    uassert_true(a == 012 && b == 077);
+}
+
+static void TC_rt_sscanf_multiple_args(void)
+{
+    const char str[] = "123 Hello";
+    int int_value;
+    char str_value[20];
+    int result = rt_sscanf(str, "%d %s", &int_value, str_value);
+    uassert_int_equal(result, 2);
+    uassert_int_equal(int_value, 123);
+    uassert_str_equal(str_value, "Hello");
+}
+
+static void TC_rt_sscanf_pointer(void)
+{
+    const char str[] = "0x12345678";
+    void *ptr;
+    int result = rt_sscanf(str, "%p", &ptr);
+    uassert_int_equal(result, 1);
+    uassert_ptr_equal(ptr, (void *)0x12345678);
+}
+
+static void TC_rt_sscanf_width_specifier(void)
+{
+    const char str[] = "123456789";
+    int value;
+    int result = rt_sscanf(str, "%4d", &value);
+    uassert_int_equal(result, 1);
+    uassert_int_equal(value, 1234);
+}
+
+static void TC_rt_sscanf_suppression(void)
+{
+    const char str[] = "123 456";
+    int second_value;
+    int result = rt_sscanf(str, "%*d %d", &second_value);
+    uassert_int_equal(result, 1);
+    uassert_int_equal(second_value, 456);
+}
+
+static void TC_rt_sscanf_match_set(void)
+{
+    const char str[] = "abc123";
+    char buffer[10] = {0};
+    int result = rt_sscanf(str, "%[a-z]", buffer);
+    uassert_int_equal(result, 1);
+    uassert_str_equal(buffer, "abc");
+}
+
+static void TC_rt_sscanf_match_set_negated(void)
+{
+    const char str[] = "abc123";
+    char buffer[10];
+    int result = rt_sscanf(str, "%[^0-9]", buffer);
+    uassert_int_equal(result, 1);
+    uassert_str_equal(buffer, "abc");
+}
+
+static void TC_rt_sscanf_match_set_range(void)
+{
+    const char str[] = "a-zA-Z";
+    char buffer[10];
+    int result = rt_sscanf(str, "%[a-z-A-Z]", buffer);
+    uassert_int_equal(result, 1);
+    uassert_str_equal(buffer, "a-zA-Z");
+}
+
+static void TC_rt_sscanf_whitespace_skip(void)
+{
+    const char str[] = "   12345";
+    int value;
+    int result = rt_sscanf(str, "%d", &value);
+    uassert_int_equal(result, 1);
+    uassert_int_equal(value, 12345);
+}
+
+static void TC_rt_sscanf_unsigned_int(void)
+{
+    const char str[] = "4294967295";
+    unsigned int value;
+    int result = rt_sscanf(str, "%u", &value);
+    uassert_int_equal(result, 1);
+    uassert_int_equal(value, 4294967295U);
+}
+
+static void TC_rt_sscanf_long_long_int(void)
+{
+    const char str[] = "9223372036854775807";
+    long long value;
+    int result = rt_sscanf(str, "%lld", &value);
+    uassert_int_equal(result, 1);
+    uassert_int_equal(value, 9223372036854775807LL);
+}
+
+static void TC_rt_sscanf_short_int(void)
+{
+    const char str[] = "32767";
+    short value;
+    int result = rt_sscanf(str, "%hd", &value);
+    uassert_int_equal(result, 1);
+    uassert_int_equal(value, 32767);
+}
+
+static void TC_rt_sscanf_null_string(void)
+{
+    const char str[] = "";
+    int value;
+    int result = rt_sscanf(str, "%d", &value);
+    uassert_int_equal(result, -1);
+}
+
+/* https://github.com/RT-Thread/rt-thread/issues/9853 */
+static void TC_rt_sscanf_issue_9853(void)
+{
+    int device_socket = 255;
+    int bfsz = 255;
+    const char str[] = "+MIPURC: \"rtcp\",0,240,HTTP/1.1 200 OK";
+    rt_sscanf(str, "+MIPURC:%*[^,],%d,%d", &device_socket, (int *)&bfsz);
+    uassert_int_equal(device_socket, 0);
+    uassert_int_equal(bfsz, 240);
+}
+
+static void utest_do_tc(void)
+{
+    UTEST_UNIT_RUN(TC_rt_sscanf_char);
+    UTEST_UNIT_RUN(TC_rt_sscanf_basic_int);
+    UTEST_UNIT_RUN(TC_rt_sscanf_basic_float);
+    UTEST_UNIT_RUN(TC_rt_sscanf_basic_string);
+    UTEST_UNIT_RUN(TC_rt_sscanf_string_with_space);
+    UTEST_UNIT_RUN(TC_rt_sscanf_basic_char);
+    UTEST_UNIT_RUN(TC_rt_sscanf_hex_1);
+    UTEST_UNIT_RUN(TC_rt_sscanf_hex_2);
+    UTEST_UNIT_RUN(TC_rt_sscanf_oct_1);
+    UTEST_UNIT_RUN(TC_rt_sscanf_oct_2);
+    UTEST_UNIT_RUN(TC_rt_sscanf_multiple_args);
+    UTEST_UNIT_RUN(TC_rt_sscanf_pointer);
+    UTEST_UNIT_RUN(TC_rt_sscanf_width_specifier);
+    UTEST_UNIT_RUN(TC_rt_sscanf_suppression);
+    UTEST_UNIT_RUN(TC_rt_sscanf_match_set);
+    UTEST_UNIT_RUN(TC_rt_sscanf_match_set_negated);
+    UTEST_UNIT_RUN(TC_rt_sscanf_match_set_range);
+    UTEST_UNIT_RUN(TC_rt_sscanf_whitespace_skip);
+    UTEST_UNIT_RUN(TC_rt_sscanf_unsigned_int);
+    UTEST_UNIT_RUN(TC_rt_sscanf_long_long_int);
+    UTEST_UNIT_RUN(TC_rt_sscanf_short_int);
+    UTEST_UNIT_RUN(TC_rt_sscanf_null_string);
+    UTEST_UNIT_RUN(TC_rt_sscanf_issue_9853);
+}
+
+UTEST_TC_EXPORT(utest_do_tc, "klibc.rt_sscanf", RT_NULL, RT_NULL, 1000);