libc+tests: Add type modifiers and integer conversion specifiers to scanf()

This commit is contained in:
apio 2023-06-19 00:59:42 +02:00
parent a2c081f219
commit 55d147841f
Signed by: apio
GPG Key ID: B8A7D06E42258954
2 changed files with 164 additions and 0 deletions

View File

@ -1,3 +1,4 @@
#include <errno.h>
#include <luna/CType.h>
#include <luna/NumberParsing.h>
#include <stdio.h>
@ -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;

View File

@ -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<void> 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 {};
}