Luna/luna/Format.h

433 lines
10 KiB
C
Raw Normal View History

2022-11-19 11:30:36 +00:00
#pragma once
#include <CType.h>
#include <Types.h>
#include <stdarg.h>
typedef int (*callback_t)(char, void*);
#ifdef _LUNA_IMPLEMENTATION
#include <NumberParsing.h>
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');
}
template <typename T> static usize to_string(T value, T base, char* buf, usize max, bool uppercase)
2022-11-19 11:30:36 +00:00
{
usize i = 0;
if (!value && max)
{
buf[i] = '0';
return 1;
}
do {
int digit = (int)(value % base);
char c = (char)(digit < 10 ? '0' + digit : ((uppercase ? 'A' : 'a') + (digit - 10)));
buf[i++] = c;
value /= base;
} while (value && i < max);
return i;
}
static int output_integer(char specifier, const conv_state& vstate, format_state& state, va_list ap)
{
usize base = 10;
bool is_signed = false;
bool uppercase = false;
bool negative = false;
if (specifier == 'd' || specifier == 'i') is_signed = true;
switch (specifier)
{
case 'x':
case 'X': base = 16; break;
case 'o': base = 8; break;
case 'b': base = 2; break;
default: break;
}
if (specifier == 'X') uppercase = true;
char buf[1024];
usize buflen;
if (vstate.flags & FLAG_CHAR)
{
if (is_signed)
{
char v = (char)va_arg(ap, int);
if (v < 0)
{
v = -v;
negative = true;
}
buflen = to_string(v, (char)base, buf, sizeof(buf), uppercase);
}
else
{
unsigned char v = (unsigned char)va_arg(ap, unsigned int);
buflen = to_string(v, (unsigned char)base, buf, sizeof(buf), uppercase);
}
}
else if (vstate.flags & FLAG_SHORT)
{
if (is_signed)
{
short v = (short)va_arg(ap, int);
if (v < 0)
{
v = -v;
negative = true;
}
buflen = to_string(v, (short)base, buf, sizeof(buf), uppercase);
}
else
{
unsigned short v = (unsigned short)va_arg(ap, unsigned int);
buflen = to_string(v, (unsigned short)base, buf, sizeof(buf), uppercase);
}
}
else if (vstate.flags & FLAG_LONG_LONG)
{
if (is_signed)
{
long long v = va_arg(ap, long long);
if (v < 0)
{
v = -v;
negative = true;
}
buflen = to_string(v, (long long)base, buf, sizeof(buf), uppercase);
}
else
{
unsigned long long v = va_arg(ap, unsigned long long);
buflen = to_string(v, (unsigned long long)base, buf, sizeof(buf), uppercase);
}
}
else if (vstate.flags & FLAG_LONG)
{
if (is_signed)
{
long v = va_arg(ap, long);
if (v < 0)
{
v = -v;
negative = true;
}
buflen = to_string(v, (long)base, buf, sizeof(buf), uppercase);
}
else
{
unsigned long v = va_arg(ap, unsigned long);
buflen = to_string(v, (unsigned long)base, buf, sizeof(buf), uppercase);
}
}
else
{
if (is_signed)
{
int v = va_arg(ap, int);
if (v < 0)
{
v = -v;
negative = true;
}
buflen = to_string(v, (int)base, buf, sizeof(buf), uppercase);
}
else
{
unsigned int v = va_arg(ap, unsigned int);
buflen = to_string(v, (unsigned int)base, buf, sizeof(buf), uppercase);
}
}
// FIXME: Start padding and alternate forms.
if (buflen < sizeof(buf))
{
if (negative) buf[buflen++] = '-';
else if (vstate.flags & FLAG_SIGN)
buf[buflen++] = '+';
else if (vstate.flags & FLAG_BLANK_SIGNED)
buf[buflen++] = ' ';
}
usize i = buflen;
while (i--) TRY_PUTCHAR(buf[i], state);
TRY_END_PAD(vstate, state, buflen);
return 0;
2022-11-19 11:30:36 +00:00
}
#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