fxlibc/src/libc/stdio/printf/print.c

270 lines
6.4 KiB
C
Raw Normal View History

#include <stdio.h>
#include <ctype.h>
#include <stdint.h>
#include <string.h>
#include <fxlibc/printf.h>
/* Internal buffer, used when no buffer is specified for output */
#define _PRINTF_BUFSIZE 64
static char __printf_buffer[_PRINTF_BUFSIZE];
/* Notation for a letter that cannot be used as a specifier */
#define _PRINTF_USED ((__printf_formatter_t *)1)
/* Default formatter functions */
__printf_formatter_t __printf_format_c;
__printf_formatter_t __printf_format_di;
__printf_formatter_t __printf_format_m;
__printf_formatter_t __printf_format_n;
__printf_formatter_t __printf_format_ouxX;
__printf_formatter_t __printf_format_p;
__printf_formatter_t __printf_format_s;
static __printf_formatter_t *__printf_formatters[52] = {
/* Uppercase letters */
NULL, /* A: Hexadecimal floating-point */
NULL,
_PRINTF_USED, /* C: Deprecated synonym for lc */
NULL,
NULL, /* E: Exponent floating-point */
NULL, /* F: Floating-point */
NULL, /* G: General floating-point */
NULL,
_PRINTF_USED, /* I: Locale-aware digits */
NULL,
NULL,
_PRINTF_USED, /* L: long double size */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
_PRINTF_USED, /* S: Deprecated synonym for ls */
NULL,
NULL,
NULL,
NULL,
__printf_format_ouxX, /* X: Hexadecimal integer */
NULL,
_PRINTF_USED, /* Z: Old synonym for z */
/* Lowercase letters */
NULL, /* a: Hexadecimal floating-point */
NULL,
__printf_format_c, /* c: Character */
__printf_format_di, /* d: Decimal integer */
NULL, /* e: Exponent floating-point */
NULL, /* f: Floating-point */
NULL, /* g: General floating-point */
_PRINTF_USED, /* h: short or char size */
__printf_format_di, /* i: Integer */
_PRINTF_USED, /* j: intmax_t size */
NULL, /* k: _ */
_PRINTF_USED, /* l: long or long long size */
__printf_format_m, /* m: Error message from errno */
__printf_format_n, /* n: Number of characters written */
__printf_format_ouxX, /* o: Octal integer */
__printf_format_p, /* p: Pointer */
_PRINTF_USED, /* q: Nonstandard synonym for ll */
NULL, /* r: _ */
__printf_format_s, /* s: String */
_PRINTF_USED, /* t: ptrdiff_t size */
__printf_format_ouxX, /* u: Unsigned decimal integer */
NULL, /* v: _ */
NULL, /* w: _ */
__printf_format_ouxX, /* x: Hexadecimal integer */
NULL, /* y: _ */
_PRINTF_USED, /* z: size_t size */
};
void __printf_register(int spec, __printf_formatter_t *format)
{
if(isupper(spec))
spec = spec - 'A';
else if(islower(spec))
spec = spec - 'a' + 26;
else return;
if(__printf_formatters[spec] == _PRINTF_USED || format == _PRINTF_USED)
return;
__printf_formatters[spec] = format;
}
void __printf_flush(struct __printf_output *out)
{
/* Update the number of flushed characters */
out->count += out->ptr - out->buffer;
/* String: write a NUL at the end even if overriding a character */
if(out->str && out->str != __printf_buffer)
{
char *nul = out->ptr;
if(out->limit - 1 < nul) nul = out->limit - 1;
*nul = 0x00;
}
/* File pointer: output with fwrite */
else if(out->fp)
{
// fwrite(out->buffer, out->ptr - out->buffer, 1, out->fp);
}
/* File pointer: output with write */
else if(out->fd)
{
// write(fd, out->buffer, out->ptr - out->buffer);
}
/* Switch to the internal buffer (when writing to a string that string
is now full, and when writing to files we've always been there) */
out->buffer = __printf_buffer;
out->limit = __printf_buffer + _PRINTF_BUFSIZE;
out->ptr = out->buffer;
}
/* Parse format strings. */
static struct __printf_format parse_fmt(char const * restrict *options_ptr)
{
/* No options enabled by default, set the size to int */
struct __printf_format opt = { .size = sizeof(int), .precision = -1 };
/* This function acts as a deterministic finite automaton */
enum {
basic, /* Reading option characters */
length, /* Reading length digits */
precision, /* Reading precision digits */
} state = basic;
char const *options = *options_ptr;
/* Previous size letter (used to keep track of hh and ll) */
int size_letter = 0;
for(int c; (c = *options); options++)
{
int index = -1;
if(isupper(c)) index = c - 'A';
if(islower(c)) index = c - 'a' + 26;
if(index >= 0 && __printf_formatters[index] != _PRINTF_USED)
break;
if(c == '.')
{
state = precision;
opt.precision = 0;
continue;
}
else if(state == length && c >= '0' && c <= '9')
{
opt.length = opt.length * 10 + (c - '0');
continue;
}
else if(state == precision && c >= '0' && c <= '9')
{
opt.precision = opt.precision * 10 + (c - '0');
continue;
}
/* Remain in basic state mode */
state = basic;
if(c == '#') opt.alternative = 1;
if(c == ' ') opt.blank_sign = 1;
if(c == '+') opt.force_sign = 1;
/* Alignment options, including priority */
if((c == '-' || c == '0') && opt.alignment != '0')
{
opt.alignment = c;
}
/* Data size */
if(strchr("hlzjtL", c))
{
if(c == 'h' && size_letter == 'h')
opt.size = sizeof(char);
else if(c == 'h')
opt.size = sizeof(short);
else if(c == 'l' && size_letter == 'l')
opt.size = sizeof(long long);
else if(c == 'l')
opt.size = sizeof(long);
else if(c == 'z')
opt.size = sizeof(size_t);
else if(c == 'j')
opt.size = sizeof(intmax_t);
else if(c == 't')
opt.size = sizeof(ptrdiff_t);
else if(c == 'L')
opt.size = sizeof(long double);
size_letter = c;
}
if(c >= '1' && c <= '9') state = length, options--;
}
*options_ptr = options;
return opt;
}
/* The formatted printer. */
int __printf(
struct __printf_output * restrict out,
char const * restrict format,
va_list *args)
{
/* Set up the buffer for the output */
if(out->fp || out->fd)
{
out->buffer = __printf_buffer;
out->limit = __printf_buffer + _PRINTF_BUFSIZE;
}
else
{
out->buffer = out->str;
out->limit = out->str + out->size;
}
out->ptr = out->buffer;
out->count = 0;
int c;
struct __printf_format opt;
while((c = *format++))
{
if(c != '%')
{
__printf_out(out, c);
continue;
}
if(!(c = *format)) break;
/* '%' character */
if(c == '%')
{
__printf_out(out, '%');
format++;
continue;
}
opt = parse_fmt(&format);
opt.spec = *format++;
int id;
if(isupper(opt.spec))
id = opt.spec - 'A';
else if(islower(opt.spec))
id = opt.spec - 'a' + 26;
else continue;
__printf_formatter_t *f = __printf_formatters[id];
if(f == NULL || f == _PRINTF_USED) continue;
f(out, &opt, args);
}
__printf_flush(out);
return (int)out->count;
}