From e76a91d5d0f927ab75c1899d761ef4cb8cd17023 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 2 Sep 2023 15:48:58 +0200 Subject: [PATCH] libc+libluna: Move the scanf implementation from libc to libluna --- libc/CMakeLists.txt | 1 - libc/src/scanf.cpp | 263 ---------------------------------- libc/src/stdio.cpp | 10 +- libluna/CMakeLists.txt | 1 + libluna/include/luna/Scanf.h | 21 +++ libluna/src/Scanf.cpp | 270 +++++++++++++++++++++++++++++++++++ 6 files changed, 300 insertions(+), 266 deletions(-) delete mode 100644 libc/src/scanf.cpp create mode 100644 libluna/include/luna/Scanf.h create mode 100644 libluna/src/Scanf.cpp diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt index aef47b36..e2eaceea 100644 --- a/libc/CMakeLists.txt +++ b/libc/CMakeLists.txt @@ -20,7 +20,6 @@ set(SOURCES src/pwd.cpp src/grp.cpp src/locale.cpp - src/scanf.cpp src/signal.cpp src/termios.cpp src/utime.cpp diff --git a/libc/src/scanf.cpp b/libc/src/scanf.cpp deleted file mode 100644 index acb9d7a5..00000000 --- a/libc/src/scanf.cpp +++ /dev/null @@ -1,263 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#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) -{ - int result = 0; - - while (true) - { - switch (**format) - { - case '*': - result |= FLAG_DISCARD; - (*format)++; - break; - case 'm': - result |= FLAG_ALLOC; - (*format)++; - break; - default: return result; - } - } -} - -static size_t parse_width(const char** format, int& flags) -{ - size_t result = 0; - - if (_isdigit(**format)) - { - result = scan_unsigned_integer(format); - flags |= FLAG_WIDTH; - } - - 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; -} - -#define WHITESPACE_CHARACTERS " \t\f\r\n\v" - -static void skip_whitespace(const char** str) -{ - *str += strspn(*str, WHITESPACE_CHARACTERS); -} - -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; - - while (*format) - { - if (*format != '%') - { - normal: - if (!_isspace(*format)) - { - if (*str != *format) return parsed; - str++; - format++; - if (*str == 0) return parsed; - continue; - } - - skip_whitespace(&format); - skip_whitespace(&str); - if (*str == 0) return parsed; - continue; - } - else - { - format++; - if (*format == '%') - { - skip_whitespace(&str); - goto normal; - } - - int flags = parse_flags(&format); - size_t width = parse_width(&format, flags); - parse_type(&format, flags); - char specifier = *format++; - if (!specifier) return parsed; - - switch (specifier) - { - case 's': { - skip_whitespace(&str); - size_t chars = strcspn(str, WHITESPACE_CHARACTERS); - if (!chars) return parsed; - if ((flags & FLAG_WIDTH) && chars > width) chars = width; - if (!(flags & FLAG_DISCARD)) - { - char* ptr; - if (flags & FLAG_ALLOC) - { - ptr = (char*)malloc(chars + 1); - if (!ptr) return parsed; - *va_arg(ap, char**) = ptr; - } - else - ptr = va_arg(ap, char*); - memcpy(ptr, str, chars); - ptr[chars] = 0; - } - str += chars; - parsed++; - break; - } - case 'c': { - if (strlen(str) < width) return parsed; - if (!(flags & FLAG_WIDTH)) width = 1; - if (!(flags & FLAG_DISCARD)) - { - char* ptr; - if (flags & FLAG_ALLOC) - { - ptr = (char*)malloc(width); - if (!ptr) return parsed; - *va_arg(ap, char**) = ptr; - } - else - ptr = va_arg(ap, char*); - memcpy(ptr, str, width); - } - str += width; - parsed++; - break; - } - case 'd': { - skip_whitespace(&str); - ssize_t value = scan_signed_integer(&str, 10); - if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap); - parsed++; - break; - } - case 'i': { - skip_whitespace(&str); - ssize_t value = scan_signed_integer(&str, 0); - if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap); - parsed++; - break; - } - case 'o': { - skip_whitespace(&str); - size_t value = scan_unsigned_integer(&str, 8); - if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap); - parsed++; - break; - } - case 'u': { - skip_whitespace(&str); - 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': { - skip_whitespace(&str); - size_t value = scan_unsigned_integer(&str, 16); - if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap); - parsed++; - break; - } - case 'p': { - skip_whitespace(&str); - 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; - } - } - } - } - - return parsed; - } -} diff --git a/libc/src/stdio.cpp b/libc/src/stdio.cpp index a4308457..c6b15c3d 100644 --- a/libc/src/stdio.cpp +++ b/libc/src/stdio.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -618,12 +619,17 @@ extern "C" return rc; } + int vsscanf(const char* str, const char* format, va_list ap) + { + return scanf_impl(str, format, ap); + } + int sscanf(const char* str, const char* format, ...) { va_list ap; va_start(ap, format); - int rc = vsscanf(str, format, ap); + int rc = scanf_impl(str, format, ap); va_end(ap); @@ -634,7 +640,7 @@ extern "C" { char buf[BUFSIZ]; if (!fgets(buf, sizeof(buf), stream)) return EOF; - return vsscanf(buf, format, ap); + return scanf_impl(buf, format, ap); } int fscanf(FILE* stream, const char* format, ...) diff --git a/libluna/CMakeLists.txt b/libluna/CMakeLists.txt index de882d57..31ddb6fa 100644 --- a/libluna/CMakeLists.txt +++ b/libluna/CMakeLists.txt @@ -14,6 +14,7 @@ set(FREESTANDING_SOURCES src/SystemError.cpp src/Bitmap.cpp src/Buffer.cpp + src/Scanf.cpp src/Stack.cpp src/String.cpp src/StringBuilder.cpp diff --git a/libluna/include/luna/Scanf.h b/libluna/include/luna/Scanf.h new file mode 100644 index 00000000..35bff5f8 --- /dev/null +++ b/libluna/include/luna/Scanf.h @@ -0,0 +1,21 @@ +/** + * @file Scanf.h + * @author apio (cloudapio.eu) + * @brief Scanf implementation. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include + +/** + * @brief scanf() implementation. + * + * @param str The string to read input from. + * @param format The format string. + * @param ap The variadic argument list. + * @return int The number of arguments read, or -1 if the string was empty. + */ +int scanf_impl(const char* str, const char* format, va_list ap); diff --git a/libluna/src/Scanf.cpp b/libluna/src/Scanf.cpp new file mode 100644 index 00000000..67922e7d --- /dev/null +++ b/libluna/src/Scanf.cpp @@ -0,0 +1,270 @@ +/** + * @file Scanf.cpp + * @author apio (cloudapio.eu) + * @brief Scanf implementation. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#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) +{ + int result = 0; + + while (true) + { + switch (**format) + { + case '*': + result |= FLAG_DISCARD; + (*format)++; + break; + case 'm': + result |= FLAG_ALLOC; + (*format)++; + break; + default: return result; + } + } +} + +static size_t parse_width(const char** format, int& flags) +{ + size_t result = 0; + + if (_isdigit(**format)) + { + result = scan_unsigned_integer(format); + flags |= FLAG_WIDTH; + } + + 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; +} + +#define WHITESPACE_CHARACTERS " \t\f\r\n\v" + +static void skip_whitespace(const char** str) +{ + *str += strspn(*str, WHITESPACE_CHARACTERS); +} + +int scanf_impl(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 -1; + + while (*format) + { + if (*format != '%') + { + normal: + if (!_isspace(*format)) + { + if (*str != *format) return parsed; + str++; + format++; + if (*str == 0) return parsed; + continue; + } + + skip_whitespace(&format); + skip_whitespace(&str); + if (*str == 0) return parsed; + continue; + } + else + { + format++; + if (*format == '%') + { + skip_whitespace(&str); + goto normal; + } + + int flags = parse_flags(&format); + size_t width = parse_width(&format, flags); + parse_type(&format, flags); + char specifier = *format++; + if (!specifier) return parsed; + + switch (specifier) + { + case 's': { + skip_whitespace(&str); + size_t chars = strcspn(str, WHITESPACE_CHARACTERS); + if (!chars) return parsed; + if ((flags & FLAG_WIDTH) && chars > width) chars = width; + if (!(flags & FLAG_DISCARD)) + { + char* ptr; + if (flags & FLAG_ALLOC) + { + ptr = (char*)malloc_impl(chars + 1).value_or(nullptr); + if (!ptr) return parsed; + *va_arg(ap, char**) = ptr; + } + else + ptr = va_arg(ap, char*); + memcpy(ptr, str, chars); + ptr[chars] = 0; + } + str += chars; + parsed++; + break; + } + case 'c': { + if (strlen(str) < width) return parsed; + if (!(flags & FLAG_WIDTH)) width = 1; + if (!(flags & FLAG_DISCARD)) + { + char* ptr; + if (flags & FLAG_ALLOC) + { + ptr = (char*)malloc_impl(width).value_or(nullptr); + if (!ptr) return parsed; + *va_arg(ap, char**) = ptr; + } + else + ptr = va_arg(ap, char*); + memcpy(ptr, str, width); + } + str += width; + parsed++; + break; + } + case 'd': { + skip_whitespace(&str); + ssize_t value = scan_signed_integer(&str, 10); + if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap); + parsed++; + break; + } + case 'i': { + skip_whitespace(&str); + ssize_t value = scan_signed_integer(&str, 0); + if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap); + parsed++; + break; + } + case 'o': { + skip_whitespace(&str); + size_t value = scan_unsigned_integer(&str, 8); + if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap); + parsed++; + break; + } + case 'u': { + skip_whitespace(&str); + 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': { + skip_whitespace(&str); + size_t value = scan_unsigned_integer(&str, 16); + if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap); + parsed++; + break; + } + case 'p': { + skip_whitespace(&str); + 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: { + dbgln("vsscanf: unknown conversion specifier: %%%c\n", specifier); + return parsed; + } + } + } + } + + return parsed; +}