libc+tests: Add type modifiers and integer conversion specifiers to scanf()
This commit is contained in:
parent
a2c081f219
commit
55d147841f
@ -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;
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user