From b3b99082f91f22ff0b460832bd24b8e1ab1891bc Mon Sep 17 00:00:00 2001 From: Meco Man <920369182@qq.com> Date: Sat, 4 Jan 2025 00:47:00 -0500 Subject: [PATCH] [rt_vsscanf] upgrade version and add test case from: https://github.com/phoenix-rtos/libphoenix/blob/master/stdio/scanf.c BSD-3-Clause license --- src/klibc/rt_vsscanf.c | 1038 +++++++++++++++++++------------- src/klibc/utest/TC_rt_sscanf.c | 250 ++++++++ 2 files changed, 868 insertions(+), 420 deletions(-) create mode 100644 src/klibc/utest/TC_rt_sscanf.c diff --git a/src/klibc/rt_vsscanf.c b/src/klibc/rt_vsscanf.c index 25c25c20a1f..8369b652497 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 - /* - * Copyright (c) 2012 Petteri Aimonen - * 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 +#include /* for strtod */ #include /* for isspace */ -#include -#include /* for CHAR_BIT */ - -static inline int digitval(int ch) +#include /* 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; } diff --git a/src/klibc/utest/TC_rt_sscanf.c b/src/klibc/utest/TC_rt_sscanf.c new file mode 100644 index 00000000000..c0453004e09 --- /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 +#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);