#pragma once #include #include #include typedef int (*callback_t)(char, void*); #ifdef _LUNA_IMPLEMENTATION #include 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 static usize to_string(T value, T base, char* buf, usize max, bool uppercase) { 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; } #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