summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMeco Man <[email protected]>2025-01-04 00:47:00 -0500
committerRbb666 <[email protected]>2025-01-04 18:07:05 +0800
commitb3b99082f91f22ff0b460832bd24b8e1ab1891bc (patch)
tree6b9a5fbe629b11ce5c2db4596d58ca0395437163 /src
parentd583615afef31fe4937d49558a5d4d23a872fdee (diff)
[rt_vsscanf] upgrade version and add test case
from: https://github.com/phoenix-rtos/libphoenix/blob/master/stdio/scanf.c BSD-3-Clause license
Diffstat (limited to 'src')
-rw-r--r--src/klibc/rt_vsscanf.c1022
-rw-r--r--src/klibc/utest/TC_rt_sscanf.c250
2 files changed, 860 insertions, 412 deletions
diff --git a/src/klibc/rt_vsscanf.c b/src/klibc/rt_vsscanf.c
index 25c25c20a1..8369b65249 100644
--- a/src/klibc/rt_vsscanf.c
+++ b/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 */
+#include <stdarg.h> /* for va_list */
-static inline int digitval(int ch)
-{
- 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;
- }
-}
+#define FORMAT_NIL_STR "(nil)"
+#define FORMAT_NIL_STR_LEN (sizeof(FORMAT_NIL_STR) - 1)
-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;
+#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 */
- while (n && isspace((unsigned char)*nptr)) {
- nptr++;
- n--;
- }
+#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 */
- /* Single optional + or - */
- if (n) {
- char c = *nptr;
- if (c == '-' || c == '+') {
- minus = (c == '-');
- nptr++;
- n--;
- }
- }
+#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) */
- 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;
- }
- }
+static const unsigned char *__sccl(char *tab, const unsigned char *fmt)
+{
+ int c, n, v;
- while (n && (d = digitval(*nptr)) >= 0 && d < base) {
- v = v * base + d;
- n--;
- nptr++;
+ c = *fmt++;
+ if (c == '^') {
+ v = 1;
+ c = *fmt++;
+ }
+ else {
+ v = 0;
}
- if (endptr)
- *endptr = (char *)nptr;
-
- return minus ? -v : v;
-}
+ rt_memset(tab, (uint8_t)v, 256);
-#ifndef LONG_BIT
-#define LONG_BIT (CHAR_BIT*sizeof(long))
-#endif
+ if (c == 0) {
+ return (fmt - 1);
+ }
-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 */
-};
+ v = 1 - v;
+ tab[c] = v;
+ for (;;) {
+ n = *fmt++;
+ switch (n) {
-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 */
-};
+ case 0:
+ return (fmt - 1);
-#define MIN_RANK rank_char
-#define MAX_RANK rank_longlong
+ case '-':
+ n = *fmt;
+ if ((n == ']') || (n < c)) {
+ c = '-';
+ tab[c] = v;
+ break;
+ }
+ fmt++;
-#define INTMAX_RANK rank_longlong
-#define SIZE_T_RANK rank_long
-#define PTRDIFF_T_RANK rank_long
+ do {
+ tab[++c] = v;
+ } while (c < n);
+ c = n;
+ break;
-enum bail {
- bail_none = 0, /* No error condition */
- bail_eof, /* Hit EOF */
- bail_err /* Conversion mismatch */
-};
+ case ']':
+ return (fmt);
-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 inline int _test_bit(unsigned long *bitmap, unsigned int bit)
-{
- return (int)(bitmap[bit / LONG_BIT] >> (bit % LONG_BIT)) & 1;
-}
+ static const short basefix[17] = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
-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;
+ *inr = rt_strlen(inp);
+
+ nassigned = 0;
+ nconversions = 0;
+ nread = 0;
+ base = 0;
+ for (;;) {
+ int convType = CT_NONE;
+ c = *fmt++;
+ if (c == '\0') {
+ return (nassigned);
+ }
- 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 (isspace(c) != 0) {
+ while ((*inr > 0) && (isspace((int)*inp) != 0)) {
+ nread++;
+ (*inr)--;
+ inp++;
}
- break;
+ continue;
+ }
- 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 (c != '%') {
+ if (*inr <= 0) {
+ return (nconversions != 0 ? nassigned : -1);
}
- break;
- case st_width:
- if (ch >= '0' && ch <= '9') {
- width = width * 10 + (ch - '0');
- } else {
- state = st_modifiers;
- p--; /* Process this character again */
+ if (*inp != c) {
+ 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 */
+ nread++;
+ (*inr)--;
+ inp++;
+ continue;
+ }
+
+ width = 0;
+ flags = 0;
+ for (;;) {
+ c = *fmt++;
+ if (c == '\0') {
+ return nassigned;
+ }
+
+ 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;
+ switch (c) {
+ case '*':
+ flags |= SUPPRESS;
+ continue;
+
+ case 'l':
+ if ((flags & LONG) != 0) {
+ flags &= ~LONG;
+ flags |= LONGLONG;
+ }
+ else {
+ flags |= LONG;
+ }
+ continue;
- /* Canonicalize rank */
- if (rank < MIN_RANK)
- rank = MIN_RANK;
- else if (rank > MAX_RANK)
- rank = MAX_RANK;
+ case 'L':
+ flags |= LONGDOUBLE;
+ continue;
- switch (ch) {
- case 'P': /* Upper case pointer */
- case 'p': /* Pointer */
- rank = rank_ptr;
- base = 0;
- sign = 0;
- goto scan_int;
+ case 'q':
+ case 'j':
+ flags |= LONGLONG;
+ continue;
- case 'i': /* Base-independent integer */
- base = 0;
- sign = 1;
- goto scan_int;
+ case 't':
+ flags |= PTRDIFF;
+ continue;
- case 'd': /* Decimal integer */
+ 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 'n': /* # of characters consumed */
- val = (q - buffer);
- goto set_integer;
+ case 's':
+ convType = CT_STRING;
+ break;
+
+ case '[':
+ fmt = __sccl(ccltab, fmt);
+ flags |= NOSKIP;
+ convType = CT_CCL;
+ break;
- scan_int:
- q = skipspace(q);
- if (!*q) {
- bail = bail_eof;
+ case 'c':
+ flags |= NOSKIP;
+ convType = CT_CHAR;
+ break;
+
+ 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;
}
- q = qq;
- if (!(flags & FL_SPLAT))
- converted++;
- /* fall through */
+ 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;
+ }
- set_integer:
- if (!(flags & FL_SPLAT)) {
- switch (rank) {
- case rank_char:
- *va_arg(ap,
- unsigned char *)
- = val;
+ 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;
- case rank_short:
- *va_arg(ap,
- unsigned short
- *) = val;
+ }
+ if (*inr <= 0) {
+ if (n == 0) {
+ return (nconversions != 0 ? nassigned : -1);
+ }
break;
- case rank_int:
- *va_arg(ap,
- unsigned int *)
- = 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_long:
- *va_arg(ap,
- unsigned long *)
- = val;
+ }
+ if (*inr <= 0) {
+ if (p == p0) {
+ return (nconversions != 0 ? nassigned : -1);
+ }
break;
- case rank_longlong:
- *va_arg(ap,
- unsigned long
- long *) = 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;
+ }
+ if (*inr <= 0) {
break;
- case rank_ptr:
- *va_arg(ap, void **) =
- (void *)
- (uintptr_t)val;
+ }
+ }
+ }
+ else {
+ p0 = p = va_arg(ap, char *);
+ while (isspace((int)*inp) == 0) {
+ (*inr)--;
+ *p++ = *inp++;
+ if (--width == 0) {
+ break;
+ }
+ 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;
- 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;
+ const char *srcbuf = inp;
+ if ((width != 0) && (width < *inr)) {
+ /* TODO: handle larger widths */
+ if (width > (sizeof(buf) - 1)) {
+ return (nconversions != 0 ? nassigned : -1);
+ }
- 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;
+ rt_memcpy(buf, inp, width);
+ buf[width] = '\0';
+ srcbuf = buf;
+ }
+
+ 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;
}
diff --git a/src/klibc/utest/TC_rt_sscanf.c b/src/klibc/utest/TC_rt_sscanf.c
new file mode 100644
index 0000000000..c0453004e0
--- /dev/null
+++ b/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);