/** * @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; }