Luna/libluna/src/Format.cpp

533 lines
14 KiB
C++
Raw Normal View History

#include <luna/CType.h>
#include <luna/Format.h>
#include <luna/NumberParsing.h>
2022-12-07 10:40:02 +00:00
#include <luna/Result.h>
#include <stdarg.h>
2022-12-07 11:25:42 +00:00
#include <stddef.h>
2022-11-19 14:43:09 +00:00
extern "C" usize strlen(const char*);
typedef int 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)
2022-11-19 14:43:09 +00:00
struct format_state
{
usize count;
callback_t callback;
void* arg;
};
struct conv_state
{
flags_t flags;
usize width;
usize precision;
};
2022-11-19 15:13:25 +00:00
static Result<void> format_putchar(char c, format_state& state)
2022-11-19 14:43:09 +00:00
{
state.count++;
return state.callback(c, state.arg);
}
2022-11-19 15:13:25 +00:00
static Result<void> format_puts(const char* s, format_state& state)
2022-11-19 14:43:09 +00:00
{
while (*s)
{
2022-11-19 15:13:25 +00:00
TRY(format_putchar(*s, state));
2022-11-19 14:43:09 +00:00
s++;
}
2022-11-19 15:13:25 +00:00
return {};
2022-11-19 14:43:09 +00:00
}
2022-11-19 15:13:25 +00:00
static Result<void> start_pad(const conv_state& vstate, format_state& state, usize start)
2022-11-19 14:43:09 +00:00
{
if (!(vstate.flags & FLAG_LEFT_ALIGN))
{
2022-11-19 15:13:25 +00:00
while (start++ < vstate.width) TRY(format_putchar(' ', state));
2022-11-19 14:43:09 +00:00
}
2022-11-19 15:13:25 +00:00
return {};
2022-11-19 14:43:09 +00:00
}
2022-11-19 15:13:25 +00:00
static Result<void> end_pad(const conv_state& vstate, format_state& state, usize start)
2022-11-19 14:43:09 +00:00
{
if (vstate.flags & FLAG_LEFT_ALIGN)
{
2022-11-19 15:13:25 +00:00
while (start++ < vstate.width) TRY(format_putchar(' ', state));
2022-11-19 14:43:09 +00:00
}
2022-11-19 15:13:25 +00:00
return {};
2022-11-19 14:43:09 +00:00
}
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 = scan_unsigned_integer(format);
2022-11-19 14:43:09 +00:00
else if (**format == '*')
{
const int width = va_arg(ap, int);
if (width >= 0) result = (usize)width;
else
{
flags |= FLAG_LEFT_ALIGN;
result = (usize)-width;
}
(*format)++;
2022-11-19 14:43:09 +00:00
}
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 = scan_unsigned_integer(format);
2022-11-19 14:43:09 +00:00
else if (**format == '*')
{
const int precision = va_arg(ap, int);
if (precision >= 0) result = (usize)precision;
else
{
2022-11-19 14:43:09 +00:00
result = 0;
flags &= ~FLAG_USE_PRECISION;
}
(*format)++;
2022-11-19 14:43:09 +00:00
}
}
return result;
}
static void parse_type(const char** format, flags_t& flags)
2022-11-19 14:43:09 +00:00
{
// 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(usize) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
2022-11-19 14:43:09 +00:00
(*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');
}
static usize to_string(usize value, usize base, char* buf, usize max, bool uppercase)
{
usize i = 0;
2022-11-19 19:01:01 +00:00
if (!max) return 0;
if (!value)
2022-11-19 14:43:09 +00:00
{
buf[i] = '0';
return 1;
}
do {
2022-12-05 11:58:59 +00:00
const int digit = (int)(value % base);
const char c = (char)(digit < 10 ? '0' + digit : ((uppercase ? 'A' : 'a') + (digit - 10)));
2022-11-19 14:43:09 +00:00
buf[i++] = c;
value /= base;
} while (value && i < max);
return i;
}
static Result<void> output_integer_data(const conv_state& vstate, format_state& state, char* buf, usize len)
{
if (!(vstate.flags & FLAG_ZERO_PAD)) TRY(start_pad(vstate, state, len));
usize i = len;
while (i--) TRY(format_putchar(buf[i], state));
TRY(end_pad(vstate, state, len));
return {};
}
static Result<void> output_integer(char specifier, conv_state& vstate, format_state& state, usize value, bool negative)
2022-11-19 14:43:09 +00:00
{
usize base = 10;
bool uppercase = false;
// When 0 is printed with an explicit precision 0, the output is empty.
if ((vstate.flags & FLAG_USE_PRECISION) && vstate.precision == 0 && value == 0) return {};
2022-11-19 14:43:09 +00:00
switch (specifier)
{
case 'p':
case 'x':
case 'X': base = 16; break;
case 'o': base = 8; break;
case 'b': base = 2; break;
default: break;
}
if (specifier == 'X') uppercase = true;
if (base == 10) vstate.flags &= ~FLAG_ALTERNATE; // decimal doesn't have an alternate form
char buf[1024];
usize buflen = to_string(value, base, buf, sizeof(buf), uppercase);
if (!(vstate.flags & FLAG_LEFT_ALIGN) &&
(vstate.flags & FLAG_ZERO_PAD)) // we're padding with zeroes from the beginning
{
2022-12-05 11:58:59 +00:00
const bool extra_char =
2022-11-19 14:43:09 +00:00
negative || ((vstate.flags & FLAG_SIGN) ||
(vstate.flags & FLAG_BLANK_SIGNED)); // are we adding an extra character after the buffer?
if (vstate.width && extra_char) vstate.width--;
if (vstate.width && (vstate.flags & FLAG_ALTERNATE)) // fit in the characters we're using for the alternate form
{
vstate.width--;
if (vstate.width && (base == 2 || base == 16)) vstate.width--;
}
while (buflen < vstate.width && buflen < sizeof(buf)) buf[buflen++] = '0';
}
while (buflen < vstate.precision && buflen < sizeof(buf)) buf[buflen++] = '0';
if (vstate.flags & FLAG_ALTERNATE)
{
if (base == 16 && !uppercase && buflen < sizeof(buf)) buf[buflen++] = 'x';
if (base == 16 && uppercase && buflen < sizeof(buf)) buf[buflen++] = 'X';
if (base == 2 && buflen < sizeof(buf)) buf[buflen++] = 'b';
if (buflen < sizeof(buf)) buf[buflen++] = '0';
}
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++] = ' ';
}
return output_integer_data(vstate, state, buf, buflen);
}
2022-11-19 14:43:09 +00:00
static Result<void> va_output_integer(char specifier, conv_state& vstate, format_state& state, va_list ap)
2022-11-19 14:43:09 +00:00
{
bool is_signed = false;
bool negative = false;
if (specifier == 'd' || specifier == 'i') is_signed = true;
if (!is_signed) vstate.flags &= ~(FLAG_SIGN | FLAG_BLANK_SIGNED);
if (vstate.flags & FLAG_CHAR)
{
if (is_signed)
{
char v = (char)va_arg(ap, int);
if (v < 0)
{
v = -v;
negative = true;
}
return output_integer(specifier, vstate, state, (unsigned char)v, negative);
2022-11-19 14:43:09 +00:00
}
else
{
2022-12-05 11:58:59 +00:00
const unsigned char v = (unsigned char)va_arg(ap, unsigned int);
return output_integer(specifier, vstate, state, v, false);
2022-11-19 14:43:09 +00:00
}
}
else if (vstate.flags & FLAG_SHORT)
{
if (is_signed)
{
short v = (short)va_arg(ap, int);
if (v < 0)
{
v = -v;
negative = true;
}
return output_integer(specifier, vstate, state, (unsigned short)v, negative);
2022-11-19 14:43:09 +00:00
}
else
{
2022-12-05 11:58:59 +00:00
const unsigned short v = (unsigned short)va_arg(ap, unsigned int);
return output_integer(specifier, vstate, state, v, false);
2022-11-19 14:43:09 +00:00
}
}
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;
}
return output_integer(specifier, vstate, state, (unsigned long long)v, negative);
2022-11-19 14:43:09 +00:00
}
else
{
2022-12-05 11:58:59 +00:00
const unsigned long long v = va_arg(ap, unsigned long long);
return output_integer(specifier, vstate, state, v, false);
2022-11-19 14:43:09 +00:00
}
}
else if (vstate.flags & FLAG_LONG)
{
if (is_signed)
{
long v = va_arg(ap, long);
if (v < 0)
{
v = -v;
negative = true;
}
return output_integer(specifier, vstate, state, (unsigned long)v, negative);
2022-11-19 14:43:09 +00:00
}
else
{
2022-12-05 11:58:59 +00:00
const unsigned long v = va_arg(ap, unsigned long);
return output_integer(specifier, vstate, state, v, false);
2022-11-19 14:43:09 +00:00
}
}
else
{
if (is_signed)
{
int v = va_arg(ap, int);
if (v < 0)
{
v = -v;
negative = true;
}
return output_integer(specifier, vstate, state, (unsigned int)v, negative);
2022-11-19 14:43:09 +00:00
}
else
{
2022-12-05 11:58:59 +00:00
const unsigned int v = va_arg(ap, unsigned int);
return output_integer(specifier, vstate, state, v, false);
2022-11-19 14:43:09 +00:00
}
}
}
2022-11-19 15:13:25 +00:00
Result<usize> cstyle_format(const char* format, callback_t callback, void* arg, va_list ap)
2022-11-19 14:43:09 +00:00
{
format_state state;
state.callback = callback;
state.arg = arg;
state.count = 0;
while (*format)
{
if (*format != '%')
{
2022-11-19 15:13:25 +00:00
TRY(format_putchar(*format, state));
2022-11-19 14:43:09 +00:00
format++;
continue;
}
format++;
if (*format == '%')
{
2022-11-19 15:13:25 +00:00
TRY(format_putchar('%', state));
2023-02-27 15:27:45 +00:00
format++;
2022-11-19 14:43:09 +00:00
continue;
}
// %[flags][width][.precision][length]conversion
flags_t flags = parse_flags(&format);
2022-12-05 11:58:59 +00:00
const usize width = parse_width(&format, flags, ap);
2022-11-19 14:43:09 +00:00
usize precision = parse_precision(&format, flags, ap);
parse_type(&format, flags);
2022-11-19 14:43:09 +00:00
2022-12-21 19:22:44 +00:00
conv_state vstate = { flags, width, precision };
2022-11-19 14:43:09 +00:00
2022-12-05 11:58:59 +00:00
const char specifier = *format;
2022-11-19 14:43:09 +00:00
format++;
if (is_integer_format_specifier(specifier))
{
2022-11-19 15:13:25 +00:00
TRY(va_output_integer(specifier, vstate, state, ap));
2022-11-19 14:43:09 +00:00
continue;
}
else if (specifier == 'p')
{
2022-12-05 11:58:59 +00:00
const void* ptr = va_arg(ap, void*);
2022-11-19 14:43:09 +00:00
if (ptr == nullptr)
{
2022-11-19 15:13:25 +00:00
TRY(start_pad(vstate, state, 5));
TRY(format_puts("(nil)", state));
TRY(end_pad(vstate, state, 5));
2022-11-19 14:43:09 +00:00
continue;
}
vstate.width = (sizeof(void*) * 2) + 2;
vstate.flags |= (FLAG_ZERO_PAD | FLAG_ALTERNATE);
2022-11-19 15:13:25 +00:00
TRY(output_integer('p', vstate, state, (usize)ptr, false));
2022-11-19 14:43:09 +00:00
continue;
}
else if (specifier == 'c')
{
// FIXME: If FLAG_LONG is set, we should use a wint_t.
2022-11-19 14:43:09 +00:00
const char c = (char)va_arg(ap, int);
2022-11-19 15:13:25 +00:00
TRY(start_pad(vstate, state, 1));
TRY(format_putchar(c, state));
TRY(end_pad(vstate, state, 1));
2022-11-19 14:43:09 +00:00
continue;
}
else if (specifier == 's')
{
// FIXME: If FLAG_LONG is set, we should use a wide string.
2022-11-19 14:43:09 +00:00
const char* str = va_arg(ap, const char*);
if (str == nullptr)
{
2022-11-19 15:13:25 +00:00
TRY(start_pad(vstate, state, 6));
TRY(format_puts("(null)", state));
TRY(end_pad(vstate, state, 6));
2022-11-19 14:43:09 +00:00
continue;
}
else
{
usize len = strlen(str);
bool use_precision = (flags & FLAG_USE_PRECISION);
if (use_precision && len > precision) len = precision;
2022-11-19 15:13:25 +00:00
TRY(start_pad(vstate, state, len));
2022-11-19 14:43:09 +00:00
while (*str && (!use_precision || precision))
{
2022-11-19 15:13:25 +00:00
TRY(format_putchar(*str, state));
if (use_precision) precision--;
2022-11-19 14:43:09 +00:00
str++;
}
2022-11-19 15:13:25 +00:00
TRY(end_pad(vstate, state, len));
2022-11-19 14:43:09 +00:00
continue;
}
}
else { continue; }
}
2022-11-19 15:13:25 +00:00
return state.count;
2022-11-30 11:36:21 +00:00
}
struct StringFormatInfo
{
char* buffer;
2022-12-07 16:06:29 +00:00
usize remaining;
2022-11-30 11:36:21 +00:00
};
usize vstring_format(char* buf, usize max, const char* format, va_list ap)
2022-11-30 11:36:21 +00:00
{
2022-12-21 19:22:44 +00:00
StringFormatInfo info = { .buffer = buf, .remaining = max - 1 };
2022-11-30 11:36:21 +00:00
usize result = cstyle_format(
format,
[](char c, void* arg) -> Result<void> {
StringFormatInfo* info_arg = (StringFormatInfo*)arg;
if (!info_arg->remaining) return {};
if (info_arg->buffer)
{
*(info_arg->buffer) = c;
info_arg->buffer++;
}
info_arg->remaining--;
return {};
},
&info, ap)
.value();
2022-11-30 11:36:21 +00:00
if (info.buffer) *(info.buffer) = 0;
2022-11-30 11:36:21 +00:00
return result;
}
usize string_format(char* buf, usize max, const char* format, ...)
2022-11-30 11:36:21 +00:00
{
va_list ap;
va_start(ap, format);
usize result = vstring_format(buf, max, format, ap);
2022-11-30 11:36:21 +00:00
va_end(ap);
return result;
2023-01-02 12:07:29 +00:00
}