From 603ff46d8c6e46219f9b9e20c49a510b910398bc Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 19 Nov 2022 12:30:36 +0100 Subject: [PATCH] Add a format implementation --- kernel/CMakeLists.txt | 3 +- kernel/src/arch/Serial.cpp | 16 ++ kernel/src/arch/Serial.h | 1 + kernel/src/luna/Format.cpp | 3 + kernel/src/luna/String.cpp | 3 + kernel/src/main.cpp | 2 + kernel/src/string.cpp | 2 - luna/Format.h | 290 +++++++++++++++++++++++++++++++++++++ luna/NumberParsing.h | 40 +++++ 9 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 kernel/src/luna/Format.cpp create mode 100644 kernel/src/luna/String.cpp delete mode 100644 kernel/src/string.cpp create mode 100644 luna/Format.h create mode 100644 luna/NumberParsing.h diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 85aad645..fbe0b719 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -1,10 +1,11 @@ set(SOURCES src/main.cpp - src/string.cpp + src/luna/String.cpp src/Framebuffer.cpp src/MemoryManager.cpp src/Init.cpp src/arch/Serial.cpp + src/luna/Format.cpp ) # x86-64 specific diff --git a/kernel/src/arch/Serial.cpp b/kernel/src/arch/Serial.cpp index ddf3a879..07d9ce26 100644 --- a/kernel/src/arch/Serial.cpp +++ b/kernel/src/arch/Serial.cpp @@ -1,4 +1,5 @@ #include "arch/Serial.h" +#include namespace Serial { @@ -17,4 +18,19 @@ namespace Serial print(str); putchar('\n'); } + + int printf(const char* format, ...) + { + va_list ap; + va_start(ap, format); + isize rc = cstyle_format( + format, + [](char c, void*) -> int { + putchar(c); + return 0; + }, + nullptr, ap); + va_end(ap); + return (int)rc; + } } \ No newline at end of file diff --git a/kernel/src/arch/Serial.h b/kernel/src/arch/Serial.h index 8d7b18aa..92a0725e 100644 --- a/kernel/src/arch/Serial.h +++ b/kernel/src/arch/Serial.h @@ -7,4 +7,5 @@ namespace Serial void write(const char* str, usize size); void print(const char* str); void println(const char* str); + int printf(const char* str, ...); } \ No newline at end of file diff --git a/kernel/src/luna/Format.cpp b/kernel/src/luna/Format.cpp new file mode 100644 index 00000000..c1307639 --- /dev/null +++ b/kernel/src/luna/Format.cpp @@ -0,0 +1,3 @@ +/* This file instantiates the functions from Luna's Format.h into a translation unit, and serves no other purpose. */ +#define _LUNA_IMPLEMENTATION +#include \ No newline at end of file diff --git a/kernel/src/luna/String.cpp b/kernel/src/luna/String.cpp new file mode 100644 index 00000000..20590d5d --- /dev/null +++ b/kernel/src/luna/String.cpp @@ -0,0 +1,3 @@ +/* This file instantiates the functions from Luna's String.h into a translation unit, and serves no other purpose. */ +#define _LUNA_IMPLEMENTATION +#include \ No newline at end of file diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index deec4804..e0c7d376 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -14,6 +14,8 @@ extern "C" [[noreturn]] void _start() Serial::println("Hello, world!"); + Serial::printf("Current platform: %s\n", CPU::platform_string()); + Framebuffer::rect(0, 0, 200, 200, 0xFF00FF00); auto cpu_name_or_error = CPU::identify(); diff --git a/kernel/src/string.cpp b/kernel/src/string.cpp deleted file mode 100644 index 7c431e6c..00000000 --- a/kernel/src/string.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define _LUNA_IMPLEMENTATION -#include \ No newline at end of file diff --git a/luna/Format.h b/luna/Format.h new file mode 100644 index 00000000..1be99216 --- /dev/null +++ b/luna/Format.h @@ -0,0 +1,290 @@ +#pragma once +#include +#include +#include + +typedef int (*callback_t)(char, void*); + +#ifdef _LUNA_IMPLEMENTATION +#include + +extern "C" usize strlen(const char*); + +typedef usize flags_t; +#define FLAG_ZERO_PAD 1 << 0 +#define FLAG_LEFT_ALIGN 1 << 1 +#define FLAG_BLANK_SIGNED 1 << 2 +#define FLAG_ALTERNATE 1 << 3 +#define FLAG_SIGN 1 << 4 +#define FLAG_USE_PRECISION 1 << 5 +#define FLAG_LONG 1 << 6 +#define FLAG_LONG_LONG 1 << 7 +#define FLAG_SHORT 1 << 8 +#define FLAG_CHAR 1 << 9 + +struct format_state +{ + usize count; + callback_t callback; + void* arg; +}; + +struct conv_state +{ + flags_t flags; + usize width; + usize precision; +}; + +static inline int cstyle_format_putchar(char c, format_state& state) +{ + state.count++; + return state.callback(c, state.arg); +} + +static inline int cstyle_format_puts(const char* s, format_state& state) +{ + while (*s) + { + if (cstyle_format_putchar(*s, state)) return -1; + s++; + } + + return 0; +} + +#define TRY_PUTCHAR(c, state) \ + if (cstyle_format_putchar(c, state)) return -1; + +#define TRY_PUTS(s, state) \ + if (cstyle_format_puts(s, state)) return -1; + +static int start_pad(const conv_state& vstate, format_state& state, usize start) +{ + if (!(vstate.flags & FLAG_LEFT_ALIGN)) + { + while (start++ < vstate.width) TRY_PUTCHAR(' ', state); + } + + return 0; +} + +#define TRY_START_PAD(vstate, state, start) \ + if (start_pad(vstate, state, start)) return -1; + +static int end_pad(const conv_state& vstate, format_state& state, usize start) +{ + if (vstate.flags & FLAG_LEFT_ALIGN) + { + while (start++ < vstate.width) TRY_PUTCHAR(' ', state); + } + + return 0; +} + +#define TRY_END_PAD(vstate, state, start) \ + if (end_pad(vstate, state, start)) return -1; + +static flags_t parse_flags(const char** format) +{ + flags_t result = 0; + + while (true) + { + switch (**format) + { + case '#': + result |= FLAG_ALTERNATE; + (*format)++; + break; + case '0': + result |= FLAG_ZERO_PAD; + (*format)++; + break; + case ' ': + result |= FLAG_BLANK_SIGNED; + (*format)++; + break; + case '-': + result |= FLAG_LEFT_ALIGN; + (*format)++; + break; + case '+': + result |= FLAG_SIGN; + (*format)++; + break; + default: return result; + } + } +} + +static usize parse_width(const char** format, flags_t& flags, va_list ap) +{ + usize result = 0; + + if (_isdigit(**format)) result = _atou(format); + else if (**format == '*') + { + const int width = va_arg(ap, int); + if (width >= 0) result = (usize)width; + else + { + flags |= FLAG_LEFT_ALIGN; + result = (usize)-width; + } + } + + return result; +} + +static usize parse_precision(const char** format, flags_t& flags, va_list ap) +{ + usize result = 0; + + if (**format == '.') + { + (*format)++; + + flags |= FLAG_USE_PRECISION; + + if (_isdigit(**format)) result = _atou(format); + else if (**format == '*') + { + const int precision = va_arg(ap, int); + if (precision >= 0) result = (usize)precision; + else + result = 0; + } + } + + return result; +} + +static void parse_length(const char** format, flags_t& 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 bool is_integer_format_specifier(char c) +{ + return (c == 'd') || (c == 'i') || (c == 'u') || (c == 'x') || (c == 'X') || (c == 'o') || (c == 'b'); +} + +static int output_integer(char, const conv_state&, format_state&, va_list) +{ + return -1; // FIXME: Implement this. +} + +#endif + +isize cstyle_format(const char* format, callback_t callback, void* arg, va_list ap) +#ifdef _LUNA_IMPLEMENTATION +{ + format_state state; + state.callback = callback; + state.arg = arg; + state.count = 0; + + while (*format) + { + if (*format != '%') + { + TRY_PUTCHAR(*format, state); + format++; + continue; + } + + format++; + + // %[flags][width][.precision][length]conversion + + flags_t flags = parse_flags(&format); + usize width = parse_width(&format, flags, ap); + usize precision = parse_precision(&format, flags, ap); + parse_length(&format, flags); + + conv_state vstate = {flags, width, precision}; + + char specifier = *format; + format++; + + if (is_integer_format_specifier(specifier)) + { + if (output_integer(specifier, vstate, state, ap)) return -1; + } + else if (specifier == 'c') + { + const char c = (char)va_arg(ap, int); + + TRY_START_PAD(vstate, state, 1); + TRY_PUTCHAR(c, state); + TRY_END_PAD(vstate, state, 1); + + continue; + } + else if (specifier == 's') + { + const char* str = va_arg(ap, const char*); + if (str == nullptr) + { + TRY_START_PAD(vstate, state, 6); + TRY_PUTS("(null)", state); + TRY_END_PAD(vstate, state, 6); + continue; + } + else + { + usize len = strlen(str); + + bool use_precision = (flags & FLAG_USE_PRECISION); + if (use_precision && len > precision) len = precision; + + TRY_START_PAD(vstate, state, len); + while (*str && (!use_precision || precision)) + { + TRY_PUTCHAR(*str, state); + precision--; + str++; + } + TRY_END_PAD(vstate, state, len); + continue; + } + } + else { continue; } + } + + return (isize)state.count; +} +#else + ; +#endif \ No newline at end of file diff --git a/luna/NumberParsing.h b/luna/NumberParsing.h new file mode 100644 index 00000000..aaa20c62 --- /dev/null +++ b/luna/NumberParsing.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include + +inline usize _atou(const char** str) +{ + usize val = 0; + + while (_isdigit(**str)) + { + val = (10 * val) + (**str - '0'); + (*str)++; + } + + return val; +} + +inline isize _atos(const char** str) +{ + bool neg = false; + isize val = 0; + + switch (**str) + { + case '-': + neg = true; + (*str)++; + break; + case '+': (*str)++; break; + default: break; + } + + while (_isdigit(**str)) + { + val = (10 * val) + (**str - '0'); + (*str)++; + } + + return neg ? -val : val; +}