mirror of https://github.com/flysand7/ciabatta.git
719 lines
18 KiB
C
719 lines
18 KiB
C
|
|
#include <math.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
|
|
// This stuff is kinda related to what's going on in this file, so I left it
|
|
// in a rather ugly manner here.
|
|
#if !defined(once)
|
|
#define once
|
|
|
|
#define countof(arr) (sizeof(arr)/sizeof(arr[0]))
|
|
|
|
#define CALL_PRINTF(fmt_func, ctx, out, fmt) \
|
|
va_list args; \
|
|
va_start(args, fmt); \
|
|
int result = fmt_func(ctx, out, fmt, args); \
|
|
va_end(args)
|
|
|
|
enum ArgLength {
|
|
LEN_HH,
|
|
LEN_H,
|
|
LEN_I,
|
|
LEN_L,
|
|
LEN_LL,
|
|
LEN_J,
|
|
LEN_Z,
|
|
LEN_T,
|
|
} typedef ArgLength;
|
|
|
|
enum ArgConv {
|
|
CONV_INT,
|
|
CONV_FLT,
|
|
CONV_EXP,
|
|
CONV_SCI,
|
|
CONV_CHAR,
|
|
CONV_STR,
|
|
CONV_PTR,
|
|
CONV_N,
|
|
CONV_PERCENT,
|
|
} typedef ArgConv;
|
|
|
|
struct Format typedef Format;
|
|
struct Format {
|
|
bool flag_left;
|
|
bool flag_sign;
|
|
bool flag_space;
|
|
bool flag_form;
|
|
bool flag_zero;
|
|
bool flag_upper;
|
|
bool flag_unsigned;
|
|
int base;
|
|
int width;
|
|
int prec;
|
|
ArgLength arg_len;
|
|
ArgConv arg_conv;
|
|
};
|
|
|
|
inline static uint64_t get_int_arg(Format *fmt, va_list args, bool *signp) {
|
|
bool sign = 0;
|
|
uint64_t num = 0;
|
|
|
|
// Read the integer from the args and split it into tuple of (sign, digits)
|
|
// If we've got HH or H we need to parse as int, and do the necessary conv
|
|
// to unsigned if needed.
|
|
if(fmt->arg_len < LEN_I) {
|
|
int ch = va_arg(args, int);
|
|
if(ch < 0) {
|
|
if(fmt->flag_unsigned) {
|
|
num = ch + (fmt->arg_len == LEN_HH? UCHAR_MAX : USHRT_MAX);
|
|
}
|
|
else {
|
|
num = -ch;
|
|
sign = 1;
|
|
}
|
|
}
|
|
}
|
|
// Otherwise we see whether the number is signed or unsigned. For unsigned
|
|
// numbers we just parse according to the length, for signed we do that
|
|
// plus handle the sign
|
|
else if(fmt->flag_unsigned) {
|
|
switch(fmt->arg_len) {
|
|
case LEN_I: num = va_arg(args, unsigned int); break;
|
|
case LEN_L: num = va_arg(args, unsigned long); break;
|
|
case LEN_LL: num = va_arg(args, unsigned long long); break;
|
|
case LEN_J: num = va_arg(args, uintmax_t); break;
|
|
case LEN_Z: num = va_arg(args, size_t); break;
|
|
case LEN_T: num = va_arg(args, size_t); break;
|
|
default:;
|
|
}
|
|
}
|
|
else {
|
|
// TODO: signed size_t
|
|
int64_t i = 0;
|
|
switch(fmt->arg_len) {
|
|
case LEN_I: i = va_arg(args, int); break;
|
|
case LEN_L: i = va_arg(args, long); break;
|
|
case LEN_LL: i = va_arg(args, long long); break;
|
|
case LEN_J: i = va_arg(args, uintmax_t); break;
|
|
case LEN_Z: i = va_arg(args, int64_t); break;
|
|
case LEN_T: i = va_arg(args, ptrdiff_t); break;
|
|
default:;
|
|
}
|
|
if(i < 0) {
|
|
num = -i;
|
|
sign = 1;
|
|
}
|
|
else {
|
|
num = i;
|
|
}
|
|
}
|
|
|
|
*signp = sign;
|
|
return num;
|
|
}
|
|
#endif
|
|
|
|
// Generic printf function that is used to print to stdout, strings and files.
|
|
// The basic idea is to make generic printf that uses output function as
|
|
// parameter and that function is distinct for printing to files, strings etc.
|
|
// Furthermore, this generic printf is abbstracted over character type, and
|
|
// depending on the macro fchar it could print in char's or wchar_t's.
|
|
|
|
typedef void (suffix(out_func_t))(void* ctx, fchar ch);
|
|
|
|
inline static int suffix(fmt_atoi)(fchar const *str, int *value) {
|
|
int i = 0;
|
|
int val = 0;
|
|
while('0' <= str[i] && str[i] <= '9') {
|
|
int digit = str[i] - '0';
|
|
if(val > (INT_MAX - digit) / 10) {
|
|
return -1;
|
|
}
|
|
val = 10*val + digit;
|
|
++i;
|
|
}
|
|
*value = val;
|
|
return i;
|
|
}
|
|
|
|
inline static int suffix(fmt_parse)(
|
|
Format *format,
|
|
fchar const *str,
|
|
va_list args
|
|
) {
|
|
*format = (Format){0};
|
|
// Parse flags
|
|
int i = 0;
|
|
for(;;) {
|
|
if (str[i] == '-') format->flag_left = true;
|
|
else if(str[i] == '+') format->flag_sign = true;
|
|
else if(str[i] == ' ') format->flag_space = true;
|
|
else if(str[i] == '#') format->flag_form = true;
|
|
else if(str[i] == '0') format->flag_zero = true;
|
|
else break;
|
|
++i;
|
|
}
|
|
// Optional width specifier
|
|
int width = 0;
|
|
if(str[i] == '*') {
|
|
++i;
|
|
width = va_arg(args, int);
|
|
if(width < 0) {
|
|
width = -width;
|
|
format->flag_zero = true;
|
|
}
|
|
}
|
|
else {
|
|
int width_len = suffix(fmt_atoi)(str+i, &width);
|
|
if(width_len < 0) return -1;
|
|
i += width_len;
|
|
}
|
|
// Optional precision specifier
|
|
int prec = 0;
|
|
if(str[i] == '.') {
|
|
++i;
|
|
if(str[i] == '*') {
|
|
++i;
|
|
prec = va_arg(args, int);
|
|
if(prec < 0) {
|
|
prec = 0;
|
|
}
|
|
}
|
|
else {
|
|
int prec_len = suffix(fmt_atoi)(str+i, &prec);
|
|
if(prec_len < 0) return -1;
|
|
i += prec_len;
|
|
}
|
|
}
|
|
format->width = width;
|
|
format->prec = prec;
|
|
// Optional length modifier
|
|
format->arg_len = LEN_I;
|
|
if(str[i] == 'h') {
|
|
++i;
|
|
format->arg_len = LEN_H;
|
|
if(str[i] == 'h') {
|
|
++i;
|
|
format->arg_len = LEN_HH;
|
|
}
|
|
}
|
|
else if(str[i] == 'l') {
|
|
++i;
|
|
format->arg_len = LEN_L;
|
|
if(str[i] == 'l') {
|
|
++i;
|
|
format->arg_len = LEN_LL;
|
|
}
|
|
}
|
|
else if(str[i] == 'j') {
|
|
++i;
|
|
format->arg_len = LEN_J;
|
|
}
|
|
else if(str[i] == 'z') {
|
|
++i;
|
|
format->arg_len = LEN_Z;
|
|
}
|
|
else if(str[i] == 't') {
|
|
++i;
|
|
format->arg_len = LEN_T;
|
|
}
|
|
else if(str[i] == 'L') {
|
|
++i;
|
|
format->arg_len = LEN_L;
|
|
}
|
|
// Conversion specifier
|
|
switch(str[i]) {
|
|
case 'd':
|
|
case 'i':
|
|
format->arg_conv = CONV_INT;
|
|
format->base = 10;
|
|
break;
|
|
case 'u':
|
|
format->arg_conv = CONV_INT;
|
|
format->flag_unsigned = true;
|
|
format->base = 10;
|
|
break;
|
|
case 'o':
|
|
format->arg_conv = CONV_INT;
|
|
format->flag_unsigned = true;
|
|
format->base = 8;
|
|
break;
|
|
case 'x':
|
|
format->arg_conv = CONV_INT;
|
|
format->flag_unsigned = true;
|
|
format->base = 16;
|
|
break;
|
|
case 'X':
|
|
format->arg_conv = CONV_INT;
|
|
format->flag_unsigned = true;
|
|
format->flag_upper = true;
|
|
format->base = 16;
|
|
break;
|
|
case 'f':
|
|
format->arg_conv = CONV_FLT;
|
|
format->base = 10;
|
|
break;
|
|
case 'F':
|
|
format->arg_conv = CONV_FLT;
|
|
format->flag_upper = true;
|
|
format->base = 10;
|
|
break;
|
|
case 'e':
|
|
format->arg_conv = CONV_EXP;
|
|
format->base = 10;
|
|
break;
|
|
case 'E':
|
|
format->arg_conv = CONV_EXP;
|
|
format->flag_upper = true;
|
|
format->base = 10;
|
|
break;
|
|
case 'g':
|
|
format->arg_conv = CONV_SCI;
|
|
format->base = 10;
|
|
break;
|
|
case 'G':
|
|
format->arg_conv = CONV_SCI;
|
|
format->flag_upper = true;
|
|
format->base = 10;
|
|
break;
|
|
case 'a':
|
|
format->arg_conv = CONV_FLT;
|
|
format->base = 16;
|
|
break;
|
|
case 'A':
|
|
format->arg_conv = CONV_FLT;
|
|
format->flag_upper = true;
|
|
format->base = 16;
|
|
break;
|
|
case 'c':
|
|
format->arg_conv = CONV_CHAR;
|
|
break;
|
|
case 's':
|
|
format->arg_conv = CONV_STR;
|
|
break;
|
|
case 'p':
|
|
format->arg_conv = CONV_PTR;
|
|
break;
|
|
case 'n':
|
|
format->arg_conv = CONV_N;
|
|
break;
|
|
case '%':
|
|
format->arg_conv = CONV_PERCENT;
|
|
break;
|
|
default: return -1;
|
|
}
|
|
++i;
|
|
// Ignoring flags etc
|
|
if(format->flag_sign && format->flag_zero) {
|
|
format->flag_zero = 0;
|
|
}
|
|
if(format->flag_sign && format->flag_space) {
|
|
format->flag_space = 0;
|
|
}
|
|
|
|
if(format->prec != 0) {
|
|
format->flag_zero = 0;
|
|
}
|
|
else if(format->flag_zero) {
|
|
// HACK: NOTE: I hate printf formats
|
|
format->prec = format->width;
|
|
format->width = 0;
|
|
}
|
|
if(format->arg_conv == CONV_FLT && format->prec == 0) {
|
|
format->prec = 6;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
inline static int suffix(fmt_fprint_int)(
|
|
void *ctx,
|
|
suffix(out_func_t) *out,
|
|
Format *fmt,
|
|
va_list args
|
|
) {
|
|
int w = 0;
|
|
|
|
bool sign;
|
|
uint64_t num = get_int_arg(fmt, args, &sign);
|
|
|
|
// Find the actual length the number takes up
|
|
int prefix_len = 0;
|
|
int digits_num = 0;
|
|
if(fmt->flag_space || fmt->flag_sign || sign) {
|
|
prefix_len = 1;
|
|
}
|
|
if(num != 0 && fmt->flag_form) {
|
|
if(fmt->base == 16) prefix_len += 2; // 0x
|
|
if(fmt->base == 8) digits_num += 1; // 0
|
|
}
|
|
|
|
{
|
|
uint64_t digits = num;
|
|
do {
|
|
digits_num += 1;
|
|
digits /= fmt->base;
|
|
} while(digits != 0);
|
|
}
|
|
int s_len = prefix_len + digits_num; // length of significant chars
|
|
int d_len = s_len; // length of all displayed chars
|
|
int zpad = 0; // pre-pad of zeroes from precision
|
|
if(digits_num < fmt->prec) {
|
|
zpad = fmt->prec - digits_num;
|
|
d_len = s_len + zpad;
|
|
}
|
|
|
|
// See if padding is necessary and extract info about padding
|
|
bool left_pad = false;
|
|
bool right_pad = true;
|
|
char pad_ch = ' ';
|
|
int pad_len = 0;
|
|
if(!sign && fmt->flag_space) { // make space part of pad
|
|
d_len += 1;
|
|
pad_len = 1;
|
|
}
|
|
if(d_len < fmt->width) {
|
|
pad_len = fmt->width - d_len;
|
|
}
|
|
if(fmt->flag_left) {
|
|
left_pad = true;
|
|
right_pad = false;
|
|
}
|
|
if(fmt->flag_zero) {
|
|
pad_ch = '0';
|
|
}
|
|
|
|
// Fill up an array of significant digits
|
|
char digits_arr[33] = {0};
|
|
char *digits = digits_arr + countof(digits_arr);
|
|
{
|
|
uint64_t temp = num;
|
|
do {
|
|
int digit = temp % fmt->base;
|
|
char ch = 0;
|
|
if(digit < 10) ch = digit + '0';
|
|
else if(fmt->flag_upper) ch = digit - 10 + 'A';
|
|
else ch = digit - 10 + 'a';
|
|
*--digits = ch;
|
|
temp /= fmt->base;
|
|
} while(temp != 0);
|
|
}
|
|
|
|
// Start displaying number
|
|
// Left-pad if necessary
|
|
if(left_pad) while(pad_len--) {
|
|
out(ctx, pad_ch);
|
|
++w;
|
|
}
|
|
|
|
// Print sign
|
|
if(sign) { out(ctx, '-'); ++w; }
|
|
else if(fmt->flag_sign) { out(ctx, '+'); ++w; }
|
|
else if(fmt->flag_space) { out(ctx, ' '); ++w; }
|
|
|
|
// Print 0x, 0X
|
|
if(num != 0 && fmt->base == 16 && fmt->flag_form) {
|
|
out(ctx, '0');
|
|
if(fmt->flag_upper) out(ctx, 'X');
|
|
else out(ctx, 'x');
|
|
w += 2;
|
|
}
|
|
|
|
// Print zpad
|
|
while(zpad--) {
|
|
out(ctx, '0');
|
|
++ w;
|
|
}
|
|
|
|
// Print digit string
|
|
while(*digits != 0) {
|
|
out(ctx, *digits);
|
|
++digits;
|
|
++w;
|
|
}
|
|
|
|
// Right-pad if necessary
|
|
if(right_pad) while(pad_len--) {
|
|
out(ctx, pad_ch);
|
|
++w;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
inline static int suffix(fmt_fprint_ptr)(
|
|
void *ctx,
|
|
suffix(out_func_t) *out,
|
|
Format *fmt,
|
|
va_list args
|
|
) {
|
|
int w = 0;
|
|
uintptr_t num = va_arg(args, uintptr_t);
|
|
|
|
int s_len = 2 + 16; // length of significant chars
|
|
int d_len = s_len; // length of all displayed chars
|
|
int zpad = 0; // pre-pad of zeroes from precision
|
|
if(16 < fmt->prec) {
|
|
zpad = fmt->prec - 16;
|
|
d_len = s_len + zpad;
|
|
}
|
|
|
|
// See if padding is necessary and extract info about padding
|
|
bool left_pad = true;
|
|
bool right_pad = false;
|
|
char pad_ch = ' ';
|
|
int pad_len = 0;
|
|
if(d_len < fmt->width) {
|
|
pad_len = fmt->width - d_len;
|
|
}
|
|
if(fmt->flag_left) {
|
|
left_pad = false;
|
|
right_pad = true;
|
|
}
|
|
if(fmt->flag_zero) {
|
|
pad_ch = '0';
|
|
}
|
|
|
|
// Fill up an array of significant digits
|
|
char digits_arr[33] = {0};
|
|
char *digits = digits_arr + countof(digits_arr);
|
|
{
|
|
uint64_t temp = num;
|
|
do {
|
|
int digit = temp % 16;
|
|
char ch = 0;
|
|
if(digit < 10) ch = digit + '0';
|
|
else if(fmt->flag_upper) ch = digit - 10 + 'A';
|
|
else ch = digit - 10 + 'a';
|
|
*--digits = ch;
|
|
temp /= 16;
|
|
} while(s_len-- != 0);
|
|
}
|
|
|
|
// Start displaying number
|
|
// Left-pad if necessary
|
|
if(left_pad) while(pad_len--) {
|
|
out(ctx, pad_ch);
|
|
++w;
|
|
}
|
|
|
|
out(ctx, '0');
|
|
out(ctx, 'x');
|
|
w += 2;
|
|
|
|
// Print zpad
|
|
while(zpad--) {
|
|
out(ctx, '0');
|
|
++ w;
|
|
}
|
|
|
|
// Print digit string
|
|
while(*digits != 0) {
|
|
out(ctx, *digits);
|
|
++digits;
|
|
++w;
|
|
}
|
|
|
|
// Right-pad if necessary
|
|
if(right_pad) while(pad_len--) {
|
|
out(ctx, pad_ch);
|
|
++w;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
inline static int suffix(fmt_fprint_char)(
|
|
void *ctx,
|
|
suffix(out_func_t) *out,
|
|
Format *fmt,
|
|
va_list args
|
|
) {
|
|
int w = 0;
|
|
bool sign;
|
|
uint64_t num = get_int_arg(fmt, args, &sign);
|
|
|
|
// Calculate padding
|
|
bool left_pad = true;
|
|
bool right_pad = false;
|
|
uint64_t pad = 0;
|
|
if(1 < fmt->width) {
|
|
pad = fmt->width - 1;
|
|
}
|
|
if(fmt->flag_left) {
|
|
left_pad = false;
|
|
right_pad = true;
|
|
}
|
|
|
|
// Print left pad
|
|
if(left_pad) while(pad--) {
|
|
out(ctx, ' ');
|
|
++w;
|
|
}
|
|
|
|
out(ctx, num);
|
|
++w;
|
|
|
|
// Print right pad
|
|
if(right_pad) while(pad--) {
|
|
out(ctx, ' ');
|
|
++w;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
inline static int suffix(fmt_just_fucking_print_float_someone_improve_this_later)(
|
|
void *ctx,
|
|
suffix(out_func_t) *out,
|
|
Format *fmt,
|
|
va_list args
|
|
) {
|
|
int w = 0;
|
|
double f = va_arg(args, double);
|
|
uint64_t i = (uint64_t)f;
|
|
|
|
char digits_arr[33] = {0};
|
|
char *digits = digits_arr + countof(digits_arr);
|
|
{
|
|
uint64_t t = i;
|
|
do {
|
|
char ch = (t % 10) + '0';
|
|
*--digits = ch;
|
|
t /= 10;
|
|
} while(t-- != 0);
|
|
}
|
|
|
|
while(*digits != 0) {
|
|
out(ctx, *digits);
|
|
++digits;
|
|
++w;
|
|
}
|
|
|
|
f -= i;
|
|
uint64_t nprec = fmt->prec;
|
|
if(nprec != 0) {
|
|
out(ctx, '.');
|
|
w++;
|
|
}
|
|
|
|
for(int i = 0; i != fmt->prec; ++i) {
|
|
f *= 10;
|
|
uint64_t digit = (uint64_t)f;
|
|
out(ctx, digit + '0');
|
|
++w;
|
|
f -= digit;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
|
|
inline static int suffix(fmt_fprint_str)(
|
|
void *ctx,
|
|
suffix(out_func_t) *out,
|
|
Format *fmt,
|
|
va_list args
|
|
) {
|
|
int w = 0;
|
|
char *str = va_arg(args, char *);
|
|
uint64_t len = strlen(str);
|
|
|
|
// Calculate padding
|
|
bool left_pad = true;
|
|
bool right_pad = false;
|
|
uint64_t pad = 0;
|
|
if(len < fmt->width) {
|
|
pad = fmt->width - len;
|
|
}
|
|
if(fmt->flag_left) {
|
|
left_pad = false;
|
|
right_pad = true;
|
|
}
|
|
|
|
// Print left pad
|
|
if(left_pad) while(pad--) {
|
|
out(ctx, ' ');
|
|
++w;
|
|
}
|
|
|
|
while(*str) {
|
|
out(ctx, *str++);
|
|
++w;
|
|
}
|
|
|
|
// Print right pad
|
|
if(right_pad) while(pad--) {
|
|
out(ctx, ' ');
|
|
++w;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
inline static int suffix(fmt_print)(
|
|
void *ctx,
|
|
suffix(out_func_t) *out,
|
|
const fchar *fmt,
|
|
va_list args
|
|
) {
|
|
int w = 0; // N chars output
|
|
int i = 0; // N chars read from fmt
|
|
while(fmt[i] != 0) {
|
|
if(fmt[i] == '%') {
|
|
++i;
|
|
Format format;
|
|
int fmt_len = suffix(fmt_parse)(&format, fmt+i, args);
|
|
if(fmt_len < 0) return -1;
|
|
i += fmt_len;
|
|
int written;
|
|
switch(format.arg_conv) {
|
|
case CONV_INT: {
|
|
written = suffix(fmt_fprint_int)(ctx, out, &format, args);
|
|
} break;
|
|
case CONV_FLT: {
|
|
written = suffix(fmt_just_fucking_print_float_someone_improve_this_later)(ctx, out, &format, args);
|
|
} break;
|
|
case CONV_EXP: {
|
|
written = suffix(fmt_just_fucking_print_float_someone_improve_this_later)(ctx, out, &format, args);
|
|
} break;
|
|
case CONV_SCI: {
|
|
written = suffix(fmt_just_fucking_print_float_someone_improve_this_later)(ctx, out, &format, args);
|
|
} break;
|
|
case CONV_CHAR: {
|
|
written = suffix(fmt_fprint_char)(ctx, out, &format, args);
|
|
} break;
|
|
case CONV_STR: {
|
|
written = suffix(fmt_fprint_str)(ctx, out, &format, args);
|
|
} break;
|
|
case CONV_PTR: {
|
|
written = suffix(fmt_fprint_ptr)(ctx, out, &format, args);
|
|
} break;
|
|
case CONV_N: {
|
|
int *n = va_arg(args, int *);
|
|
*n = w;
|
|
written = 0;
|
|
} break;
|
|
case CONV_PERCENT: {
|
|
out(ctx, '%');
|
|
written = 1;
|
|
} break;
|
|
if(written < 0) {
|
|
return -1;
|
|
}
|
|
w += written;
|
|
}
|
|
}
|
|
// Print non-formatted characters as god intended
|
|
else while(fmt[i] != '%' && fmt[i] != 0) {
|
|
out(ctx, fmt[i]);
|
|
++i;
|
|
++w;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|