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.
This commit is contained in:
Lephe 2021-02-01 12:10:45 +01:00
parent ee7b4f27b8
commit 023675d449
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
10 changed files with 229 additions and 130 deletions

View file

@ -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

View file

@ -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

1
TODO
View file

@ -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

138
include/gint/kprint.h Normal file
View file

@ -0,0 +1,138 @@
//---
// gint:kprint - gint's printf(3)-like formatted printer
//---
#ifndef GINT_KPRINT
#define GINT_KPRINT
#include <gint/defs/types.h>
#include <gint/defs/attributes.h>
#include <stdarg.h>
//---
// 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 <stdio.h>. */
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 */

View file

@ -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 <gint/std/stdio.h>
#include <gint/std/string.h>
#include <gint/kprint.h>
#include <gint/defs/attributes.h>
#include <gint/defs/types.h>
@ -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);
}

43
src/std/print.c Normal file
View file

@ -0,0 +1,43 @@
//---
// gint:src:print - Standard formatted printing functions
//---
#include <gint/std/stdio.h>
#include <gint/kprint.h>
#include <stdarg.h>
/* 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);
}