From 55d147841f68bebd2d84cd012086ee89af040643 Mon Sep 17 00:00:00 2001 From: apio Date: Mon, 19 Jun 2023 00:59:42 +0200 Subject: [PATCH] libc+tests: Add type modifiers and integer conversion specifiers to scanf() --- libc/src/scanf.cpp | 115 +++++++++++++++++++++++++++++++++++++++ tests/libc/TestScanf.cpp | 49 +++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/libc/src/scanf.cpp b/libc/src/scanf.cpp index 777096e1..472b99b5 100644 --- a/libc/src/scanf.cpp +++ b/libc/src/scanf.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -7,6 +8,10 @@ #define FLAG_DISCARD (1 << 0) #define FLAG_ALLOC (1 << 1) #define FLAG_WIDTH (1 << 2) +#define FLAG_LONG (1 << 3) +#define FLAG_LONG_LONG (1 << 4) +#define FLAG_SHORT (1 << 5) +#define FLAG_CHAR (1 << 6) static int parse_flags(const char** format) { @@ -42,11 +47,73 @@ static size_t parse_width(const char** format, int& flags) return result; } +static void parse_type(const char** format, int& flags) +{ + // FIXME: Support %j (intmax_t/uintmax_t) + switch (**format) + { + case 'h': + flags |= FLAG_SHORT; + (*format)++; + if (**format == 'h') + { + flags |= FLAG_CHAR; + (*format)++; + } + break; + case 'l': + flags |= FLAG_LONG; + (*format)++; + if (**format == 'l') + { + flags |= FLAG_LONG_LONG; + (*format)++; + } + break; + case 't': + flags |= (sizeof(ptrdiff_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG; + (*format)++; + break; + case 'z': + flags |= (sizeof(size_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG; + (*format)++; + break; + default: break; + } +} + +static void write_parsed_signed_integer(ssize_t value, int flags, va_list ap) +{ + if (flags & FLAG_LONG_LONG) *va_arg(ap, signed long long*) = (signed long long)value; + else if (flags & FLAG_LONG) + *va_arg(ap, signed long*) = (signed long)value; + else if (flags & FLAG_SHORT) + *va_arg(ap, signed int*) = (signed short)value; + else if (flags & FLAG_CHAR) + *va_arg(ap, signed int*) = (signed char)value; + else + *va_arg(ap, signed int*) = (signed int)value; +} + +static void write_parsed_unsigned_integer(size_t value, int flags, va_list ap) +{ + if (flags & FLAG_LONG_LONG) *va_arg(ap, unsigned long long*) = (unsigned long long)value; + else if (flags & FLAG_LONG) + *va_arg(ap, unsigned long*) = (unsigned long)value; + else if (flags & FLAG_SHORT) + *va_arg(ap, unsigned int*) = (unsigned short)value; + else if (flags & FLAG_CHAR) + *va_arg(ap, unsigned int*) = (unsigned char)value; + else + *va_arg(ap, unsigned int*) = (unsigned int)value; +} + extern "C" { int vsscanf(const char* str, const char* format, va_list ap) { int parsed = 0; + const char* s = str; // Keep a pointer to the beginning of the string for %n if (*str == 0) return EOF; @@ -80,6 +147,7 @@ extern "C" int flags = parse_flags(&format); size_t width = parse_width(&format, flags); + parse_type(&format, flags); char specifier = *format++; if (!specifier) return parsed; @@ -128,6 +196,53 @@ extern "C" parsed++; break; } + case 'd': { + str += strspn(str, " \t\f\r\n\v"); + ssize_t value = scan_signed_integer(&str, 10); + if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap); + parsed++; + break; + } + case 'i': { + str += strspn(str, " \t\f\r\n\v"); + ssize_t value = scan_signed_integer(&str, 0); + if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap); + parsed++; + break; + } + case 'o': { + str += strspn(str, " \t\f\r\n\v"); + size_t value = scan_unsigned_integer(&str, 8); + if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap); + parsed++; + break; + } + case 'u': { + str += strspn(str, " \t\f\r\n\v"); + size_t value = scan_unsigned_integer(&str, 10); + if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap); + parsed++; + break; + } + case 'X': + case 'x': { + str += strspn(str, " \t\f\r\n\v"); + size_t value = scan_unsigned_integer(&str, 16); + if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap); + parsed++; + break; + } + case 'p': { + str += strspn(str, " \t\f\r\n\v"); + size_t value = scan_unsigned_integer(&str, 16); + if (!(flags & FLAG_DISCARD)) *va_arg(ap, void**) = (void*)value; + parsed++; + break; + } + case 'n': { + if (!(flags & FLAG_DISCARD)) *va_arg(ap, int*) = (int)(str - s); + break; + } default: { fprintf(stderr, "vsscanf: unknown conversion specifier: %%%c\n", specifier); return parsed; diff --git a/tests/libc/TestScanf.cpp b/tests/libc/TestScanf.cpp index 98a1e27d..a322649c 100644 --- a/tests/libc/TestScanf.cpp +++ b/tests/libc/TestScanf.cpp @@ -31,12 +31,61 @@ TestResult test_incomplete_scanf() test_success; } +TestResult test_integer_scanf() +{ + int hour; + int min; + + int parsed = sscanf("23:59", "%d:%d", &hour, &min); + validate(parsed == 2); + + validate(hour == 23); + validate(min == 59); + + test_success; +} + +TestResult test_integer_auto_base_scanf() +{ + int a; + int b; + int c; + + int parsed = sscanf("65, \t0x23, 0755", "%i, %i, %i", &a, &b, &c); + validate(parsed == 3); + + validate(a == 65); + validate(b == 0x23); + validate(c == 0755); + + test_success; +} + +TestResult test_scanf_characters_consumed() +{ + int hour; + int min; + int nr_chars; + + int parsed = sscanf("23:59", "%d:%d%n", &hour, &min, &nr_chars); + validate(parsed == 2); + + validate(hour == 23); + validate(min == 59); + validate(nr_chars == 5); + + test_success; +} + Result test_main() { test_prelude; run_test(test_basic_scanf); run_test(test_incomplete_scanf); + run_test(test_integer_scanf); + run_test(test_integer_auto_base_scanf); + run_test(test_scanf_characters_consumed); return {}; }