From 023675d4491f8a813018e8e58fdc99ac529f1e9b Mon Sep 17 00:00:00 2001 From: Lephe Date: Mon, 1 Feb 2021 12:10:45 +0100 Subject: [PATCH] improve structure of sources and formatted printer * Create an `src/3rdparty` folder for third-party code (to add the Grisu2B alfogithm soon). * Split the formatted printer into gint's kprint (src/kprint), its extension and interface (include/gint/kprint.h), and its use in the standard stdio functions (src/std/print.c). * Slightly improve the interface of kformat_geometry() to avoid relying on knowing format specifiers. * Add a function to register more formatters, to allow floating-point formatters without requiring them. --- CMakeLists.txt | 7 +- README.md | 2 +- TODO | 1 + include/gint/kprint.h | 138 +++++++++++++++++ src/{std => 3rdparty}/tinymt32/LICENSE.txt | 0 src/{std => 3rdparty}/tinymt32/rand.c | 0 src/{std => 3rdparty}/tinymt32/tinymt32.c | 0 src/{std => 3rdparty}/tinymt32/tinymt32.h | 0 src/{std/stdio.c => kprint/kprint.c} | 168 ++++++--------------- src/std/print.c | 43 ++++++ 10 files changed, 229 insertions(+), 130 deletions(-) create mode 100644 include/gint/kprint.h rename src/{std => 3rdparty}/tinymt32/LICENSE.txt (100%) rename src/{std => 3rdparty}/tinymt32/rand.c (100%) rename src/{std => 3rdparty}/tinymt32/tinymt32.c (100%) rename src/{std => 3rdparty}/tinymt32/tinymt32.h (100%) rename src/{std/stdio.c => kprint/kprint.c} (74%) create mode 100644 src/std/print.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f09f7bc..e79a42b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ set(SOURCES_COMMON src/keysc/iokbd.c src/keysc/keycodes.c src/keysc/keysc.c + src/kprint/kprint.c src/mmu/mmu.c src/render/dhline.c src/render/dimage.c @@ -43,18 +44,18 @@ set(SOURCES_COMMON src/rtc/rtc.c src/rtc/rtc_ticks.c src/spu/spu.c - src/std/tinymt32/rand.c - src/std/tinymt32/tinymt32.c src/std/memcmp.s src/std/memcpy.s src/std/memmove.s src/std/memset.s - src/std/stdio.c + src/std/print.c src/std/string.c src/tmu/inth-etmu.s src/tmu/inth-tmu.s src/tmu/sleep.c src/tmu/tmu.c + src/3rdparty/tinymt32/rand.c + src/3rdparty/tinymt32/tinymt32.c ) set(SOURCES_FX src/gray/engine.c diff --git a/README.md b/README.md index 6b9d581..ef15a14 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Currently, this only includes: * A stripped-down version of the [TinyMT random number generator](http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/TINYMT/index.html) ([GitHub repository](https://github.com/MersenneTwister-Lab/TinyMT)) by - Mutsuo Saito and Makoto Matsumoto. See `src/std/tinymt32/LICENSE.txt`. + Mutsuo Saito and Makoto Matsumoto. See `src/3rdparty/tinymt32/LICENSE.txt`. ## Programming interface diff --git a/TODO b/TODO index fe76cca..744e586 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ Extensions on existing code: +* kernel: check if cpu_setVBR() really needs to be perma-mapped * stdio: support %f in printf * project: add license file * kernel: group linker script symbols in a single header file diff --git a/include/gint/kprint.h b/include/gint/kprint.h new file mode 100644 index 0000000..68ed661 --- /dev/null +++ b/include/gint/kprint.h @@ -0,0 +1,138 @@ +//--- +// gint:kprint - gint's printf(3)-like formatted printer +//--- + +#ifndef GINT_KPRINT +#define GINT_KPRINT + +#include +#include +#include + +//--- +// Formatted printing +//--- + +/* kvsprint(): Formatted print to string + This function is similar to vsnprintf(3), except that variadic arguments are + passed as a pointer to va_list. For standard functions like vsnprintf(3), + see . */ +size_t kvsprint(char *output, size_t len, char const *format, va_list *args); + +//--- +// Printer extensions +//--- + +/* Arguments passed to formatters */ +#define KFORMAT_ARGS \ + GUNUSED int spec, /* Specifier letter */ \ + GUNUSED kprint_options_t *opt, /* Other options in format */ \ + va_list *args /* Vararg list to read from */ + +/* kprint_options_t: Standard format options */ +typedef struct { + /* Minimal length of formatted string (padding can be added) */ + uint16_t length; + /* How much significant characters of data, meaning varies */ + uint16_t precision; + + /* Size specifier for integers (%o, %x, %i, %d, %u), may be one of: + (0) char (8-bit) + (1) short (16-bit) + (2) int (32-bit) + (3) long (32-bit) + (4) long long (64-bit) */ + uint8_t size; + + /* (#) Alternative form: base prefixes, decimal point */ + uint alternative :1; + /* ( ) Add a blank sign before nonnegative numbers */ + uint blank_sign :1; + /* (+) Always add a sign before a number (overrides ' ') */ + uint force_sign :1; + + /* Alignment options: each option overrides all others before itself + NUL By default, align right + (0) Left-pad numerical values with zeros + (-) Align left by adding space on the right, dropping zeros */ + uint8_t alignment; + +} GPACKED(2) kprint_options_t; + +/* kprint_formatter_t: Type of format function */ +typedef void (*kprint_formatter_t)(KFORMAT_ARGS); + +/* kprint_register(): Register a formatter + + The formatter designated by the specified lowercase letter (eg 'q') is + registered. It will handle formats for both the lowercase and uppercase + formats (eg "%q" and "%Q"). + + Default formatters can be overridden (although not advised), but formatters + for "h" and "l" and "z" cannot be set because these letters are used as size + specifiers (eg in "%hd" or "%zu"). + + A formatter can be removed or disabled by registering NULL. + + @spec Specifier to use, should be a lowercase letter + @kformat Format function */ +void kprint_register(int spec, kprint_formatter_t kformat); + +//--- +// Helper functions for formatters +//--- + +/* kprint_geometry_t: General geometry for a format + This helper defines the structure of a format as follows: + + sign v |< zeros >| |< content >| + _ _ _ _ _ _ _ _ _ + 0 x 0 0 0 0 0 0 8 a c 7 . 3 c _ _ _ _ _ _ _ _ _ _ + |< left_spaces >| ^^^ prefix |< right_spaces >| + + The sign character is absent if sign=0, the prefix is specified by length + and is also absent if prefix=0. */ +typedef struct +{ + uint16_t left_spaces; /* Spaces before content */ + uint8_t sign; /* Sign character (NUL, ' ', '+' or '-') */ + uint8_t prefix; /* Base prefix ('0', '0x', etc) length */ + uint16_t zeros; /* For integer displays, number of zeros */ + uint16_t content; /* Content length in bytes */ + uint16_t right_spaces; /* Spaces after content */ + + /* Style of display: + KPRINT_GENERIC Sign ignored, 0-padding ignored + KPRINT_INTEGER .precision causes 0-padding + KPRINT_NUMERICAL No effect */ + enum { KPRINT_GENERIC = 0, KPRINT_INTEGER, KPRINT_NUMERICAL } style; + +} GPACKED(2) kprint_geometry_t; + +/* kformat_geometry(): Calculate the geometry of a format + + The caller provides as input: + * opt, as passed by the main kprint() routine + * g->prefix, the length of the desired prefix (if unused, 0) + * g->content, the natural content length for the provided data + * g->sign, the sign of the input ("+" or "-"); for KPRINT_GENERIC, 0 + + This function outputs: + * g->sign, which can be changed to " " or NUL depending on opt + * All other fields of g that are not part of the input + + The algorithm for laying out the format is as follows. + 1. For numerical and integer formats, turn a "+" sign into " " if + opt->blank_sign is set, "+" if opt->force_sign is set, NUL otherwise. + 2. Compute the total amount of padding needed to reach opt->length. + 3. In integer style, if a precision is specified and more than content + length plus sign and prefix, turn some padding into zeros. + 4. If numerical and integer styles, if opt->alignment == '0' turn all the + padding into zeros. + 5. Turn remaining padding into spaces at the left (if opt->alignment == NUL) + or right (if opt->alignment == '-'). + + @opt Options provided by the main kprint() routine + @g Format geometry (input/output) */ +void kformat_geometry(kprint_options_t *opt, kprint_geometry_t *g); + +#endif /* GINT_KPRINT */ diff --git a/src/std/tinymt32/LICENSE.txt b/src/3rdparty/tinymt32/LICENSE.txt similarity index 100% rename from src/std/tinymt32/LICENSE.txt rename to src/3rdparty/tinymt32/LICENSE.txt diff --git a/src/std/tinymt32/rand.c b/src/3rdparty/tinymt32/rand.c similarity index 100% rename from src/std/tinymt32/rand.c rename to src/3rdparty/tinymt32/rand.c diff --git a/src/std/tinymt32/tinymt32.c b/src/3rdparty/tinymt32/tinymt32.c similarity index 100% rename from src/std/tinymt32/tinymt32.c rename to src/3rdparty/tinymt32/tinymt32.c diff --git a/src/std/tinymt32/tinymt32.h b/src/3rdparty/tinymt32/tinymt32.h similarity index 100% rename from src/std/tinymt32/tinymt32.h rename to src/3rdparty/tinymt32/tinymt32.h diff --git a/src/std/stdio.c b/src/kprint/kprint.c similarity index 74% rename from src/std/stdio.c rename to src/kprint/kprint.c index 14dcdcb..6853c05 100644 --- a/src/std/stdio.c +++ b/src/kprint/kprint.c @@ -1,5 +1,5 @@ //--- -// The stdio formatted printer. +// gint:kprint - gint's printf(3)-like formatted printer // // Things to change when creating new formats: // -> List of prototypes at the end of the definition section @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -21,8 +22,6 @@ //--- #define KPRINT_BUFSIZE 64 -#define KFORMAT_ARGS \ - GUNUSED int spec, GUNUSED struct kprint_options *opt, va_list *args /* Current position in the output buffer */ static char *kprint_ptr = NULL; @@ -44,41 +43,6 @@ static enum { // KprintOutputDevice = 1, } kprint_type; -/* - struct kprint_options -- standard format options -*/ -struct kprint_options -{ - /* Minimal length of formatted string (padding can be added) */ - uint16_t length; - /* How much significant characters of data, meaning varies */ - uint16_t precision; - - /* Size specifier for integers (%o, %x, %i, %d, %u), may be one of: - (0) char (8-bit) - (1) short (16-bit) - (2) int (32-bit) - (3) long (32-bit) - (4) long long (64-bit) */ - uint8_t size; - - /* (#) Alternative form: base prefixes, decimal point */ - uint alternative :1; - /* ( ) Add a blank sign before nonnegative numbers */ - uint blank_sign :1; - /* (+) Always add a sign before a number (overrides ' ') */ - uint force_sign :1; - - /* Alignment options: each option overrides all others before itself - NUL By default, align right - (0) Left-pad numerical values with zeros - (-) Align left by adding space on the right, dropping zeros */ - uint8_t alignment; - -} GPACKED(2); - -typedef void (*kprint_formatter_t)(KFORMAT_ARGS); - /* Default formatters */ static void kformat_char (KFORMAT_ARGS); static void kformat_int (KFORMAT_ARGS); @@ -99,6 +63,20 @@ kprint_formatter_t kprint_formatters[26] = { NULL, NULL, }; +/* kprint_register(): Register a formatter */ +void kprint_register(int spec, kprint_formatter_t kformat) +{ + spec |= 0x20; + int i = (spec | 0x20) - 'a'; + + /* Non-letters */ + if(i < 0 || i >= 26) return; + /* Size-specifying letters */ + if(spec == 'h' || spec == 'l' || spec == 'z') return; + + kprint_formatters[i] = kformat; +} + //--- // Output functions //--- @@ -144,10 +122,10 @@ GINLINE static void kprint_outn(int byte, size_t n) //--- /* kprint_opt(): Parse option strings */ -struct kprint_options kprint_opt(char const **options_ptr) +kprint_options_t kprint_opt(char const **options_ptr) { /* No options enabled by default, set the size to int */ - struct kprint_options opt = { .size = 2 }; + kprint_options_t opt = { .size = 2 }; /* This function acts as a deterministic finite automaton */ enum { @@ -195,6 +173,7 @@ struct kprint_options kprint_opt(char const **options_ptr) /* Data size */ if(c == 'h') opt.size--; if(c == 'l') opt.size++; + if(c == 'z') opt.size = 3; if(c >= '1' && c <= '9') state = length, options--; } @@ -211,7 +190,7 @@ struct kprint_options kprint_opt(char const **options_ptr) void kprint(char const *format, va_list *args) { int c, spec; - struct kprint_options opt; + kprint_options_t opt; while((c = *format++)) { @@ -262,37 +241,11 @@ size_t kvsprint(char *output, size_t length, char const *format, va_list *args) // Formatter helpers //--- -/* struct geometry: General geometry for a format */ -struct geometry +/* kformat_geometry(): Calculate the geometry of a format */ +void kformat_geometry(kprint_options_t *opt, kprint_geometry_t *g) { - uint16_t left_spaces; /* Spaces before content */ - uint8_t sign; /* Sign character (NUL, ' ', '+' or '-') */ - uint8_t prefix; /* Base prefix ('0', '0x', etc) length */ - uint16_t zeros; /* For numerical displays, number of zeros */ - uint16_t content; /* Content length in bytes */ - uint16_t right_spaces; /* Spaces after content */ - -} GPACKED(2); - -/* kformat_geometry(): Calculate the geometry of a format - - The caller must provide the [prefix] and [content] lengths in the geometry - structure. Additionally, a sign must be indicated: either '+' if the - formatted value is nonnegative, or '-' otherwise. The sign might be updated - by this function, but not the other two fields. - - This function is not *really* isolated from the standard kformat functions - (as would a precise API be), because the meaning of each options and the - process of resolving them varies so much. */ -static void kformat_geometry(int spec, struct kprint_options *opt, - struct geometry *g) -{ - /* Determining whether we are in numerical. The blacklist approach - allows custom specifiers which call this function to enable zeros if - they like (they can disable them by un-setting opt->alignment if - it's '0' in any case) */ - int numerical = (spec != 'c' && spec != 'p' && spec != 's'); - + int integral = (g->style == KPRINT_INTEGER); + int numerical = (g->style == KPRINT_NUMERICAL) || integral; ssize_t padding; /* Sign character (no discussion required for negative values) */ @@ -305,12 +258,12 @@ static void kformat_geometry(int spec, struct kprint_options *opt, g->zeros = 0; - padding = opt->length - !!g->sign - g->prefix + padding = opt->length - (g->sign != 0) - g->prefix - max(g->content, opt->precision); if(padding < 0) padding = 0; - /* In numerical modes, precision forces zeros */ - if(numerical && opt->precision) + /* In integral modes, precision forces zeros */ + if(integral && opt->precision) { if(opt->alignment == '0') opt->alignment = 0; @@ -384,7 +337,7 @@ static int digits_8(char *str, uint64_t n) static int64_t load_i(int size, va_list *args) { - /* All smaller types are promoted to int wth sign extension, so we + /* All smaller types are promoted to int with sign extension, so we don't need to care about them. */ if(size == 3) return va_arg(*args, long); if(size == 4) return va_arg(*args, long long); @@ -410,10 +363,10 @@ static void kformat_char(KFORMAT_ARGS) { int c = va_arg(*args, int); - struct geometry g = { + kprint_geometry_t g = { .prefix = 0, .sign = 0, .content = 1, }; - kformat_geometry(spec, opt, &g); + kformat_geometry(opt, &g); kprint_outn(' ', g.left_spaces); kprint_out(c); @@ -434,10 +387,10 @@ static void kformat_str(KFORMAT_ARGS) uint32_t precision = opt->precision ? opt->precision : (-1); while(s[len] && len < precision) len++; - struct geometry g = { + kprint_geometry_t g = { .prefix = 0, .sign = 0, .content = len, }; - kformat_geometry(spec, opt, &g); + kformat_geometry(opt, &g); kprint_outn(' ', g.left_spaces); for(size_t i = 0; i < len; i++) kprint_out(s[i]); @@ -456,9 +409,10 @@ static void kformat_int(KFORMAT_ARGS) int64_t n = load_i(opt->size, args); /* Compute the sign and the absolute value */ - struct geometry g = { + kprint_geometry_t g = { .sign = (n < 0) ? '-' : '+', .prefix = 0, + .style = KPRINT_INTEGER, }; if(n < 0) n = -n; @@ -470,7 +424,7 @@ static void kformat_int(KFORMAT_ARGS) total = max(pure, opt->precision); g.content = total; - kformat_geometry(spec, opt, &g); + kformat_geometry(opt, &g); /* Print the result */ kprint_outn(' ', g.left_spaces); @@ -507,10 +461,11 @@ static void kformat_uint(KFORMAT_ARGS) size_t prefix = 0; if(opt->alternative) prefix = (specl != 'u') + (specl == 'x'); - struct geometry g = { - .sign = 0, .prefix = prefix, .content = total + kprint_geometry_t g = { + .sign = 0, .prefix = prefix, .content = total, + .style = KPRINT_INTEGER, }; - kformat_geometry(spec, opt, &g); + kformat_geometry(opt, &g); /* Output */ kprint_outn(' ', g.left_spaces); @@ -551,9 +506,10 @@ static void kformat_fixed(KFORMAT_ARGS) int64_t n = load_i(opt->size, args); /* Compute the sign and the absolute value */ - struct geometry g = { + kprint_geometry_t g = { .sign = (n < 0) ? '-' : '+', .prefix = 0, + .style = KPRINT_NUMERICAL, }; if(n < 0) n = -n; @@ -561,7 +517,7 @@ static void kformat_fixed(KFORMAT_ARGS) char digits[32]; g.content = digits_10(digits, n) + 1; - kformat_geometry(spec, opt, &g); + kformat_geometry(opt, &g); /* Print the result */ kprint_outn(' ', g.left_spaces); @@ -576,43 +532,3 @@ static void kformat_fixed(KFORMAT_ARGS) kprint_outn(' ', g.right_spaces); } - -//--- -// Standard formatted printing functions -//--- - -/* sprintf() */ -GWEAK int sprintf(char *str, char const *format, ...) -{ - va_list args; - va_start(args, format); - - int count = kvsprint(str, 65536, format, &args); - - va_end(args); - return count; -} - -/* vsprintf() */ -GWEAK int vsprintf(char *str, char const *format, va_list args) -{ - return kvsprint(str, 65536, format, &args); -} - -/* snprintf() */ -GWEAK int snprintf(char *str, size_t n, char const *format, ...) -{ - va_list args; - va_start(args, format); - - int count = kvsprint(str, n, format, &args); - - va_end(args); - return count; -} - -/* vsprintf() */ -GWEAK int vsnprintf(char *str, size_t n, char const *format, va_list args) -{ - return kvsprint(str, n, format, &args); -} diff --git a/src/std/print.c b/src/std/print.c new file mode 100644 index 0000000..ff5ec75 --- /dev/null +++ b/src/std/print.c @@ -0,0 +1,43 @@ +//--- +// gint:src:print - Standard formatted printing functions +//--- + +#include +#include +#include + +/* sprintf() */ +GWEAK int sprintf(char *str, char const *format, ...) +{ + va_list args; + va_start(args, format); + + int count = kvsprint(str, 65536, format, &args); + + va_end(args); + return count; +} + +/* vsprintf() */ +GWEAK int vsprintf(char *str, char const *format, va_list args) +{ + return kvsprint(str, 65536, format, &args); +} + +/* snprintf() */ +GWEAK int snprintf(char *str, size_t n, char const *format, ...) +{ + va_list args; + va_start(args, format); + + int count = kvsprint(str, n, format, &args); + + va_end(args); + return count; +} + +/* vsprintf() */ +GWEAK int vsnprintf(char *str, size_t n, char const *format, va_list args) +{ + return kvsprint(str, n, format, &args); +}