#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'); } static int output_integer(char, const conv_state&, format_state&, va_list) { return -1; // FIXME: Implement this. } #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