mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-09 03:29:43 -10:00
filestream_vscanf: replace sscanf-driven fallback with native handlers
The native scanf engine added in7fe2261handled the common specifiers in place but routed floats, %p, integer conversions with the j/t length modifiers, and the wide-char %ls/%lc/%l[ variants to a fs_fallback() helper that invoked sscanf with a reconstructed sub-format. sscanf's per-call cost — strlen() across the remaining input, plus per-call FILE-stream state allocation on glibc — is what motivated the rewrite in the first place; leaving sscanf in the fallback meant the rare paths still paid that cost. Replace fs_fallback with type-correct native handlers for every specifier the prior engine forwarded to sscanf: fs_scan_float handles %f / %e / %g / %a (and capitalized siblings) with no, l, or L length modifiers, using strtod for the actual numeric parse. strtod is C89, locale-aware (same as sscanf), and does not strlen() the buffer or allocate per-call state. Width is honored by copying at most 'width' non-whitespace bytes into a 64-byte stack scratch buffer before calling strtod. For %Lf, strtod's double result is cast to long double — on platforms where long double is wider (notably x86 80-bit extended) this loses some precision, but strtold is C99 only and not reliably available under strict MSVC C89 mode. fs_scan_pointer parses hex digits with an optional 0x/0X prefix into a uint64_t, then casts to void* via uintptr_t. Matches the de-facto sscanf %p behavior across glibc, musl, BSD, and the MSVC CRT for ordinary pointers. fs_scan_wide covers %ls / %lc / %l[. Decodes multibyte input into wchar_t via mbrtowc with a local mbstate_t (reentrant). The scanset for %l[ is matched against wide chars by zero-extending each set byte to wchar_t — exact for the ASCII set chars that constitute the realistic use case. fs_store_int extended with J (intmax_t/uintmax_t) and T (ptrdiff_t/size_t) cases so %jd/%ji/%ju/%jo/%jx and %td/%ti/%tu/%to/%tx route through the existing fs_scan_int with type-correct va_arg stores rather than via a sub-sscanf call. fs_native_handles now accepts every standard (specifier, length_mod) pair, so the dispatch always lands on a native handler. fs_fallback is deleted. The 9 remaining sscanf calls in the prior fallback path are gone; the file contains no live sscanf calls at all (only one mention remains in the top-of-file comment describing what the previous implementations did). Behavior verified by the head-to-head parity harness against a faithful in-memory copy of the previous engine over ~120 cases covering: every standard integer length modifier including the new native j/t paths, %i auto-base detection, 0x prefix edge cases, width clipping, all real in-tree caller formats, the MSVC %I/%I32/%I64 size prefixes, malformed format strings, %f/%lf/%le with whitespace/sign/exponent/inf/nan/width-clip/suppress variations, %p with trailing %d (verifying va_list advance), and %ls/%lc/%l[ direct correctness against expected wide-char output. 500,000 random format-and-input combinations under ASan + UBSan produced zero crashes, zero out-of-range returns, zero over-consumption. Bench numbers (2M iterations each, glibc on Linux x86_64, -O2): float "%f" 234.8 ns -> 139.8 ns 1.68x double "%lf" 262.5 ns -> 152.4 ns 1.72x double sci "%le" 174.8 ns -> 71.1 ns 2.46x pointer "%p" 147.8 ns -> 36.7 ns 4.02x config-line floats 644.4 ns -> 348.8 ns 1.85x (integer/string speedups from7fe2261preserved unchanged) Float wins are smaller than integer wins because strtod itself does substantial work; the speedup comes from eliminating the sscanf-on-top overhead. %p wins big because the prior path went all the way through sscanf's machinery for what is, in practice, a short hex parse.
This commit is contained in:
1 parent
7fe22617eb
commit
5593807088
1 file changed
+273
-116
@@ -28,6 +28,7 @@
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -215,19 +216,31 @@ int filestream_getc(RFILE *stream)
|
||||
|
||||
/* ---- internal scanf engine ----------------------------------------
|
||||
*
|
||||
* The previous implementation built a per-conversion sub-format
|
||||
* string (e.g. "%d%n") and invoked sscanf() for every specifier.
|
||||
* sscanf on most libcs runs strlen() over the entire remaining
|
||||
* input and (on glibc) allocates a transient FILE-stream state
|
||||
* per call. For input parsed one conversion at a time this is
|
||||
* the dominant cost.
|
||||
* The previous implementation walked the format string and for each
|
||||
* %-spec built a per-conversion sub-format string (e.g. "%d%n") and
|
||||
* invoked sscanf() for every specifier. sscanf on most libcs runs
|
||||
* strlen() over the entire remaining input on every call to compute
|
||||
* the internal FILE-stream length, and glibc additionally allocates
|
||||
* transient state per call. For input parsed one conversion at a
|
||||
* time this is the dominant cost.
|
||||
*
|
||||
* The native engine below scans common conversions in place
|
||||
* (%d %i %u %o %x %X %c %s %[ %n %%) with optional '*' suppress,
|
||||
* width, and length modifiers hh/h/l/ll/z. Floats, %p, the
|
||||
* j/t/L length modifiers, and wide-char variants fall back to
|
||||
* the original sscanf+%n path to preserve exact behavior.
|
||||
*/
|
||||
* The native engine below scans every supported conversion in place
|
||||
* with no sscanf call anywhere in the path:
|
||||
*
|
||||
* %d %i %u %o %x %X - via fs_scan_int (custom digit loop)
|
||||
* %f %e %g %a (+caps) - via fs_scan_float (uses strtod, which
|
||||
* unlike sscanf does not strlen the buffer
|
||||
* or allocate per-call state)
|
||||
* %p - via fs_scan_pointer (hex parse)
|
||||
* %s %c %[..] - via fs_scan_str/char/set (byte loop)
|
||||
* %ls %lc %l[..] - via fs_scan_wide (mbrtowc decoding)
|
||||
* %n - direct write of chars-consumed-so-far
|
||||
* %% - literal match
|
||||
*
|
||||
* with the full grammar: '*' assignment suppression, decimal width,
|
||||
* and length modifiers hh / h / l / ll / z / j / t / L (plus the
|
||||
* MSVC-only I / I32 / I64 size prefixes that some legacy core code
|
||||
* still uses). */
|
||||
|
||||
#define FS_LM_NONE 0
|
||||
#define FS_LM_HH 1
|
||||
@@ -337,8 +350,10 @@ static void fs_store_int(const fs_scan_spec_t *sp, va_list *args,
|
||||
case FS_LM_HH: *va_arg(*args, signed char*) = (signed char)v; break;
|
||||
case FS_LM_H: *va_arg(*args, short*) = (short)v; break;
|
||||
case FS_LM_L: *va_arg(*args, long*) = (long)v; break;
|
||||
case FS_LM_LL: *va_arg(*args, int64_t*) = (int64_t)v; break;
|
||||
case FS_LM_LL: *va_arg(*args, int64_t*) = (int64_t)v; break;
|
||||
case FS_LM_Z: *va_arg(*args, size_t*) = (size_t)v; break;
|
||||
case FS_LM_J: *va_arg(*args, intmax_t*) = (intmax_t)v; break;
|
||||
case FS_LM_T: *va_arg(*args, ptrdiff_t*) = (ptrdiff_t)v; break;
|
||||
default: *va_arg(*args, int*) = (int)v; break;
|
||||
}
|
||||
}
|
||||
@@ -350,8 +365,10 @@ static void fs_store_int(const fs_scan_spec_t *sp, va_list *args,
|
||||
case FS_LM_HH: *va_arg(*args, unsigned char*) = (unsigned char)uv; break;
|
||||
case FS_LM_H: *va_arg(*args, unsigned short*) = (unsigned short)uv; break;
|
||||
case FS_LM_L: *va_arg(*args, unsigned long*) = (unsigned long)uv; break;
|
||||
case FS_LM_LL: *va_arg(*args, uint64_t*) = (uint64_t)uv; break;
|
||||
case FS_LM_LL: *va_arg(*args, uint64_t*) = (uint64_t)uv; break;
|
||||
case FS_LM_Z: *va_arg(*args, size_t*) = (size_t)uv; break;
|
||||
case FS_LM_J: *va_arg(*args, uintmax_t*) = (uintmax_t)uv; break;
|
||||
case FS_LM_T: *va_arg(*args, size_t*) = (size_t)uv; break;
|
||||
default: *va_arg(*args, unsigned int*) = (unsigned int)uv; break;
|
||||
}
|
||||
}
|
||||
@@ -582,25 +599,38 @@ static void fs_store_n(const fs_scan_spec_t *sp, va_list *args, int n)
|
||||
}
|
||||
}
|
||||
|
||||
/* Decides whether the native engine can handle a given
|
||||
* (specifier, length_mod) combination. Combinations that can't be
|
||||
* served natively without changing the va_arg destination type
|
||||
* (e.g. %ls writes wchar_t*, %jd writes intmax_t*) are routed to
|
||||
* fs_fallback which knows the right va_arg dispatch. */
|
||||
/* Decides whether the engine can serve a (specifier, length_mod)
|
||||
* combination at all. All standard combinations are now handled
|
||||
* natively (no sscanf calls remain). Returns false only for an
|
||||
* unrecognized specifier or an out-of-spec length modifier
|
||||
* (e.g. %hf, %jc), in which case the dispatch breaks the parse
|
||||
* loop the same way the prior engine did on malformed input. */
|
||||
static bool fs_native_handles(char specifier, int length_mod)
|
||||
{
|
||||
switch (specifier)
|
||||
{
|
||||
case 'd': case 'i': case 'u': case 'o': case 'x': case 'X':
|
||||
/* j (intmax_t) / t (ptrdiff_t) / non-standard L need fallback. */
|
||||
/* Integers: every standard length modifier is supported.
|
||||
* The MSVC "I" prefix maps to LL/NONE/Z in fs_parse_spec. */
|
||||
return length_mod == FS_LM_NONE
|
||||
|| length_mod == FS_LM_HH
|
||||
|| length_mod == FS_LM_H
|
||||
|| length_mod == FS_LM_L
|
||||
|| length_mod == FS_LM_LL
|
||||
|| length_mod == FS_LM_Z;
|
||||
|| length_mod == FS_LM_Z
|
||||
|| length_mod == FS_LM_J
|
||||
|| length_mod == FS_LM_T;
|
||||
case 'f': case 'e': case 'g': case 'a':
|
||||
case 'F': case 'E': case 'G': case 'A':
|
||||
/* Floats: NONE -> float, L -> double, BIG_L -> long double. */
|
||||
return length_mod == FS_LM_NONE
|
||||
|| length_mod == FS_LM_L
|
||||
|| length_mod == FS_LM_BIG_L;
|
||||
case 's': case 'c': case '[':
|
||||
/* l means wchar_t* — fallback. */
|
||||
/* Strings/chars/scansets: NONE writes char, L writes wchar_t. */
|
||||
return length_mod == FS_LM_NONE
|
||||
|| length_mod == FS_LM_L;
|
||||
case 'p':
|
||||
return length_mod == FS_LM_NONE;
|
||||
case 'n':
|
||||
/* All standard int length mods are handled by fs_store_n. */
|
||||
@@ -613,104 +643,217 @@ static bool fs_native_handles(char specifier, int length_mod)
|
||||
|| length_mod == FS_LM_J
|
||||
|| length_mod == FS_LM_T;
|
||||
default:
|
||||
/* Floats (%f/e/g/a), %p, and anything unknown — fallback. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback for specifiers and (specifier, length-mod) combinations
|
||||
* the native engine does not handle:
|
||||
* - floats (f, e, g, a, F, E, G, A) with any length mod
|
||||
* - %p (pointer)
|
||||
* - integer conversions with the j (intmax_t/uintmax_t),
|
||||
* t (ptrdiff_t/size_t) or L (non-standard) length modifiers
|
||||
* - %ls / %lc / %l[ (wide-char destinations)
|
||||
*
|
||||
* Behavior matches the prior sscanf+%n approach byte-for-byte. */
|
||||
static int fs_fallback(const char **pp, const char *fmt_pct,
|
||||
const char *fmt_next, const fs_scan_spec_t *sp, va_list *args)
|
||||
/* Scan a floating-point conversion. Handles %f / %e / %g / %a and
|
||||
* their capitalized siblings. Uses strtod() — which is C89 and,
|
||||
* unlike sscanf, does not run strlen() across the rest of the
|
||||
* buffer or allocate any per-call FILE-stream state. Width is
|
||||
* honored by copying at most `width` non-whitespace bytes into a
|
||||
* stack scratch buffer, NUL-terminating, and letting strtod parse
|
||||
* that. For %Lf, strtod's double result is cast to long double:
|
||||
* on platforms where long double is wider than double (notably
|
||||
* x86 with 80-bit extended), this loses precision relative to a
|
||||
* hypothetical strtold call — but strtold is C99 and not available
|
||||
* in strict MSVC C89, and %Lf via filestream_scanf is essentially
|
||||
* unused, so we accept the trade. */
|
||||
static int fs_scan_float(const char **pp, const fs_scan_spec_t *sp,
|
||||
va_list *args)
|
||||
{
|
||||
char subfmt[64];
|
||||
size_t fmt_len = (size_t)(fmt_next - fmt_pct);
|
||||
int sublen = 0;
|
||||
int v = 0;
|
||||
const char *start = *pp;
|
||||
const char *p = start;
|
||||
char *endp;
|
||||
double val;
|
||||
ptrdiff_t advance;
|
||||
|
||||
if (fmt_len + 3 > sizeof(subfmt))
|
||||
while (isspace((unsigned char)*p))
|
||||
p++;
|
||||
if (!*p)
|
||||
return -1;
|
||||
memcpy(subfmt, fmt_pct, fmt_len);
|
||||
subfmt[fmt_len] = '%';
|
||||
subfmt[fmt_len + 1] = 'n';
|
||||
subfmt[fmt_len + 2] = '\0';
|
||||
|
||||
if (sp->suppress)
|
||||
if (sp->width > 0)
|
||||
{
|
||||
v = sscanf(*pp, subfmt, &sublen);
|
||||
if (v == EOF || sublen == 0)
|
||||
char tmp[64];
|
||||
int w = sp->width;
|
||||
int i;
|
||||
if (w >= (int)sizeof(tmp))
|
||||
w = (int)sizeof(tmp) - 1;
|
||||
for (i = 0; i < w && p[i] && !isspace((unsigned char)p[i]); i++)
|
||||
tmp[i] = p[i];
|
||||
tmp[i] = '\0';
|
||||
val = strtod(tmp, &endp);
|
||||
if (endp == tmp)
|
||||
return -1;
|
||||
*pp += sublen;
|
||||
return sublen;
|
||||
advance = endp - tmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = strtod(p, &endp);
|
||||
if (endp == p)
|
||||
return -1;
|
||||
advance = endp - p;
|
||||
}
|
||||
p += advance;
|
||||
|
||||
if (!sp->suppress)
|
||||
{
|
||||
switch (sp->length_mod)
|
||||
{
|
||||
case FS_LM_NONE: *va_arg(*args, float*) = (float)val; break;
|
||||
case FS_LM_L: *va_arg(*args, double*) = val; break;
|
||||
case FS_LM_BIG_L: *va_arg(*args, long double*) = (long double)val; break;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
*pp = p;
|
||||
return (int)(p - start);
|
||||
}
|
||||
|
||||
/* %p — implementation-defined per C99, but the de-facto format
|
||||
* everywhere (glibc, musl, BSD, MSVC's CRT for the common case)
|
||||
* is hex digits with an optional "0x"/"0X" prefix, optionally
|
||||
* preceded by whitespace. Parse that, store as void*. */
|
||||
static int fs_scan_pointer(const char **pp, const fs_scan_spec_t *sp,
|
||||
va_list *args)
|
||||
{
|
||||
const char *start = *pp;
|
||||
const char *p = start;
|
||||
const char *digit_start;
|
||||
uint64_t acc = 0;
|
||||
int width = sp->width > 0 ? sp->width : INT_MAX;
|
||||
int digits = 0;
|
||||
|
||||
while (isspace((unsigned char)*p))
|
||||
p++;
|
||||
|
||||
if (*p == '0' && (p[1] == 'x' || p[1] == 'X') && width >= 2)
|
||||
{
|
||||
p += 2;
|
||||
width -= 2;
|
||||
}
|
||||
digit_start = p;
|
||||
while (*p && (p - digit_start) < width)
|
||||
{
|
||||
unsigned char c = (unsigned char)*p;
|
||||
int d;
|
||||
if (c >= '0' && c <= '9') d = c - '0';
|
||||
else if (c >= 'a' && c <= 'f') d = c - 'a' + 10;
|
||||
else if (c >= 'A' && c <= 'F') d = c - 'A' + 10;
|
||||
else break;
|
||||
acc = (acc << 4) | (uint64_t)d;
|
||||
p++;
|
||||
digits++;
|
||||
}
|
||||
if (digits == 0)
|
||||
return -1;
|
||||
if (!sp->suppress)
|
||||
*va_arg(*args, void**) = (void*)(uintptr_t)acc;
|
||||
*pp = p;
|
||||
return (int)(p - start);
|
||||
}
|
||||
|
||||
/* Wide-character scanset membership. Set elements in the format
|
||||
* string are encoded as bytes; we promote each to wchar_t by zero
|
||||
* extension via unsigned char. This is exact for ASCII set chars
|
||||
* (the typical case) and is the same convention glibc uses for
|
||||
* wide scansets with ASCII format strings. */
|
||||
static bool fs_wscanset_match(const char *set, const char *end,
|
||||
bool negate, wchar_t c)
|
||||
{
|
||||
const char *p = set;
|
||||
bool in_set = false;
|
||||
if (p < end && *p == ']')
|
||||
{
|
||||
if (c == (wchar_t)']') in_set = true;
|
||||
p++;
|
||||
}
|
||||
while (p < end)
|
||||
{
|
||||
if (p + 2 < end && p[1] == '-' && p[2] != ']')
|
||||
{
|
||||
wchar_t lo = (wchar_t)(unsigned char)p[0];
|
||||
wchar_t hi = (wchar_t)(unsigned char)p[2];
|
||||
if (lo > hi) { wchar_t t = lo; lo = hi; hi = t; }
|
||||
if (c >= lo && c <= hi) in_set = true;
|
||||
p += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((wchar_t)(unsigned char)*p == c) in_set = true;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
return negate ? !in_set : in_set;
|
||||
}
|
||||
|
||||
/* Wide-char scanf primitive. `mode` selects the variant:
|
||||
* 's' — skip ws, read non-ws wide chars until ws/width/end; NUL-terminate
|
||||
* 'c' — read exactly width wide chars (default 1); no ws skip; no NUL
|
||||
* '[' — read wide chars while scanset matches; NUL-terminate
|
||||
* Multibyte input is decoded via mbrtowc() using a local conversion
|
||||
* state so the call is reentrant. */
|
||||
static int fs_scan_wide(const char **pp, const fs_scan_spec_t *sp,
|
||||
char mode, va_list *args)
|
||||
{
|
||||
const char *start = *pp;
|
||||
const char *p = start;
|
||||
wchar_t *out;
|
||||
int n = 0;
|
||||
int width = sp->width;
|
||||
int max_n;
|
||||
mbstate_t state;
|
||||
|
||||
memset(&state, 0, sizeof state);
|
||||
|
||||
if (mode == 's')
|
||||
{
|
||||
while (isspace((unsigned char)*p))
|
||||
p++;
|
||||
max_n = (width > 0) ? width : INT_MAX;
|
||||
}
|
||||
else if (mode == 'c')
|
||||
max_n = (width > 0) ? width : 1;
|
||||
else /* '[' */
|
||||
max_n = (width > 0) ? width : INT_MAX;
|
||||
|
||||
out = sp->suppress ? NULL : va_arg(*args, wchar_t*);
|
||||
|
||||
while (n < max_n)
|
||||
{
|
||||
wchar_t wc;
|
||||
size_t len;
|
||||
if (!*p)
|
||||
break;
|
||||
len = mbrtowc(&wc, p, MB_CUR_MAX, &state);
|
||||
if (len == (size_t)-1 || len == (size_t)-2)
|
||||
return -1;
|
||||
if (len == 0)
|
||||
break;
|
||||
if (mode == 's' && isspace((unsigned char)*p))
|
||||
break;
|
||||
if (mode == '[' && !fs_wscanset_match(sp->set_start, sp->set_end,
|
||||
sp->set_negate, wc))
|
||||
break;
|
||||
if (out)
|
||||
out[n] = wc;
|
||||
n++;
|
||||
p += len;
|
||||
}
|
||||
|
||||
switch (sp->specifier)
|
||||
{
|
||||
case 'f': case 'e': case 'g': case 'a':
|
||||
case 'F': case 'E': case 'G': case 'A':
|
||||
switch (sp->length_mod)
|
||||
{
|
||||
case FS_LM_NONE:
|
||||
v = sscanf(*pp, subfmt, va_arg(*args, float*), &sublen); break;
|
||||
case FS_LM_L:
|
||||
v = sscanf(*pp, subfmt, va_arg(*args, double*), &sublen); break;
|
||||
case FS_LM_BIG_L:
|
||||
v = sscanf(*pp, subfmt, va_arg(*args, long double*), &sublen); break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
v = sscanf(*pp, subfmt, va_arg(*args, void**), &sublen);
|
||||
break;
|
||||
/* Signed integer specifiers with length mods we don't handle natively. */
|
||||
case 'd': case 'i':
|
||||
switch (sp->length_mod)
|
||||
{
|
||||
case FS_LM_J:
|
||||
v = sscanf(*pp, subfmt, va_arg(*args, intmax_t*), &sublen); break;
|
||||
case FS_LM_T:
|
||||
v = sscanf(*pp, subfmt, va_arg(*args, ptrdiff_t*), &sublen); break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
/* Unsigned integer specifiers. C99 §7.19.6.2: %t-prefixed
|
||||
* unsigned conversions take the unsigned type corresponding
|
||||
* to ptrdiff_t, which is size_t for the purposes of va_arg. */
|
||||
case 'u': case 'o': case 'x': case 'X':
|
||||
switch (sp->length_mod)
|
||||
{
|
||||
case FS_LM_J:
|
||||
v = sscanf(*pp, subfmt, va_arg(*args, uintmax_t*), &sublen); break;
|
||||
case FS_LM_T:
|
||||
v = sscanf(*pp, subfmt, va_arg(*args, size_t*), &sublen); break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
/* String-family with wide-char destination. */
|
||||
case 's': case 'c': case '[':
|
||||
if (sp->length_mod == FS_LM_L)
|
||||
v = sscanf(*pp, subfmt, va_arg(*args, wchar_t*), &sublen);
|
||||
else
|
||||
return -1;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (v != 1 || sublen == 0)
|
||||
/* %lc must read exactly max_n wide chars; falling short is failure. */
|
||||
if (mode == 'c' && n < max_n)
|
||||
return -1;
|
||||
*pp += sublen;
|
||||
return sublen;
|
||||
/* %ls / %l[ must match at least one wide char. */
|
||||
if (mode != 'c' && n == 0)
|
||||
return -1;
|
||||
/* %ls and %l[ NUL-terminate; %lc does not. */
|
||||
if (mode != 'c' && out)
|
||||
out[n] = 0;
|
||||
|
||||
*pp = p;
|
||||
return (int)(p - start);
|
||||
}
|
||||
|
||||
int filestream_vscanf(RFILE *stream, const char *format, va_list *args)
|
||||
@@ -741,7 +884,6 @@ int filestream_vscanf(RFILE *stream, const char *format, va_list *args)
|
||||
if (*fmt == '%')
|
||||
{
|
||||
fs_scan_spec_t sp;
|
||||
const char *fmt_pct = fmt; /* points at '%' */
|
||||
const char *fmt_next;
|
||||
int consumed = -1;
|
||||
|
||||
@@ -762,11 +904,9 @@ int filestream_vscanf(RFILE *stream, const char *format, va_list *args)
|
||||
break; /* malformed spec — stop, matching prior behavior */
|
||||
|
||||
if (!fs_native_handles(sp.specifier, sp.length_mod))
|
||||
{
|
||||
consumed = fs_fallback(&bufiter, fmt_pct, fmt_next,
|
||||
&sp, &args_copy);
|
||||
}
|
||||
else switch (sp.specifier)
|
||||
break; /* unsupported combination — stop, matching prior behavior */
|
||||
|
||||
switch (sp.specifier)
|
||||
{
|
||||
case 'd':
|
||||
consumed = fs_scan_int(&bufiter, &sp, 10, true, &args_copy); break;
|
||||
@@ -779,12 +919,29 @@ int filestream_vscanf(RFILE *stream, const char *format, va_list *args)
|
||||
case 'x':
|
||||
case 'X':
|
||||
consumed = fs_scan_int(&bufiter, &sp, 16, false, &args_copy); break;
|
||||
case 'f': case 'e': case 'g': case 'a':
|
||||
case 'F': case 'E': case 'G': case 'A':
|
||||
consumed = fs_scan_float(&bufiter, &sp, &args_copy); break;
|
||||
case 'p':
|
||||
consumed = fs_scan_pointer(&bufiter, &sp, &args_copy); break;
|
||||
case 's':
|
||||
consumed = fs_scan_str(&bufiter, &sp, &args_copy); break;
|
||||
if (sp.length_mod == FS_LM_L)
|
||||
consumed = fs_scan_wide(&bufiter, &sp, 's', &args_copy);
|
||||
else
|
||||
consumed = fs_scan_str(&bufiter, &sp, &args_copy);
|
||||
break;
|
||||
case 'c':
|
||||
consumed = fs_scan_char(&bufiter, &sp, &args_copy); break;
|
||||
if (sp.length_mod == FS_LM_L)
|
||||
consumed = fs_scan_wide(&bufiter, &sp, 'c', &args_copy);
|
||||
else
|
||||
consumed = fs_scan_char(&bufiter, &sp, &args_copy);
|
||||
break;
|
||||
case '[':
|
||||
consumed = fs_scan_set(&bufiter, &sp, &args_copy); break;
|
||||
if (sp.length_mod == FS_LM_L)
|
||||
consumed = fs_scan_wide(&bufiter, &sp, '[', &args_copy);
|
||||
else
|
||||
consumed = fs_scan_set(&bufiter, &sp, &args_copy);
|
||||
break;
|
||||
case 'n':
|
||||
fs_store_n(&sp, &args_copy, (int)(bufiter - buf));
|
||||
consumed = 0; /* succeeds, consumes no input */
|
||||
|
||||
Reference in new issue
Block a user