diff --git a/example/format.c b/example/format.c index b06d662..bcb3657 100644 --- a/example/format.c +++ b/example/format.c @@ -1,140 +1,133 @@ #define SP_IMPLEMENTATION #include "sp.h" -// Custom formatters are straightforward. Instead of pulling the value to be formatted from -// the value-typed members of the union in sp_fmt_arg_t, you cast the pointer member. -// -// From there, you can append arbitrary bytes to the string builder being used for this -// call to sp_fmt(mem, ...).value. You can (and are encouraged to) use sp.h's internal helpers for -// printing primitives, and you have access to the :specifier's width and precision. -// -// Then, to provide a convenient and type-safe call site, define the sp_fmt_*() macros that -// callers will use as wrappers over sp_fmt_custom() -typedef struct { f32 x; f32 y; } point_t; - -void format_point(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - point_t* point = (point_t*)arg->value.custom.ptr; - u32 precision = sp_opt_is_null(arg->spec.precision) ? 2 : sp_opt_get(arg->spec.precision); - sp_io_write_c8(io, '('); - sp_fmt_write_f64(io, point->x, precision); - sp_io_write_cstr(io, ", ", SP_NULLPTR); - sp_fmt_write_f64(io, point->y, precision); - sp_io_write_c8(io, ')'); -} -#define sp_fmt_point(p) sp_fmt_custom(point_t, format_point, (p)) -#define sp_fmt_point_v(...) sp_fmt_custom_v(point_t, format_point, (point_t){__VA_ARGS__}) - -static void section(const c8* title) { - sp_log(""); - sp_log("{.gray .italic}", sp_fmt_cstr(title)); -} - -void specifier(s64 n, c8 fill, sp_fmt_align_t align, u8 width) { - const c8* fmt = SP_NULLPTR; - switch (align) { - case SP_FMT_ALIGN_LEFT: fmt = ":{.cyan}{.magenta}{.yellow} -> {:$<$}"; break; - case SP_FMT_ALIGN_CENTER: fmt = ":{.cyan}{.magenta}{.yellow} -> {:$^$}"; break; - case SP_FMT_ALIGN_RIGHT: fmt = ":{.cyan}{.magenta}{.yellow} -> {:$>$}"; break; - case SP_FMT_ALIGN_NONE: break; - } +void render_table(); +void push_row(sp_str_t fmt, sp_str_t args, sp_str_t result); +#define row(_fmt, ...) push_row(sp_str_lit(_fmt), sp_str_lit(#__VA_ARGS__), sp_fmt(mem, _fmt, ##__VA_ARGS__).value) - c8 aligner = 0; - switch (align) { - case SP_FMT_ALIGN_LEFT: aligner = '<'; - case SP_FMT_ALIGN_CENTER: aligner = '^'; - case SP_FMT_ALIGN_RIGHT: aligner = '>'; - case SP_FMT_ALIGN_NONE: aligner = 0; - } - sp_log( - fmt, - sp_fmt_char(fill), - sp_fmt_char(aligner), sp_fmt_uint(width), - sp_fmt_int(fill), sp_fmt_uint(width), - sp_fmt_int(n) - ); -} +static sp_mem_t mem; s32 run(s32 num_args, const c8** args) { sp_mem_heap_t* heap = sp_mem_heap_new(); - sp_mem_t mem = sp_mem_heap_as_allocator(heap); - - sp_log("{.green} has Zig/Rust style format specifiers (fill, align, width), plus named directives which may:", sp_fmt_cstr("sp.h")); - sp_log("- {} text from a format argument", sp_fmt_cstr("render")); - sp_log("- {.bold .cyan} the rendered text", sp_fmt_cstr("decorate")); + mem = sp_mem_heap_as_allocator(heap); - section("decorators"); - sp_log("{:<14 .italic} -> hello, {.bold}", sp_fmt_cstr("hello, {.bold}"), sp_fmt_cstr("world")); - sp_log("{:<14 .italic} -> {.hyperlink}", sp_fmt_cstr("{.hyperlink}"), sp_fmt_cstr("https://spader.zone")); - sp_log("{:<14 .italic} -> {.quote}", sp_fmt_cstr("{.quote}"), sp_fmt_cstr("supposedly")); - - section(".bytes"); - u64 byte_samples[] = { 0ULL, 512ULL, 1536ULL, 10485760ULL, 5368709120ULL }; - sp_carr_for(byte_samples, i) { - sp_log("{:<10} -> {.bytes}", sp_fmt_uint(byte_samples[i]), sp_fmt_uint(byte_samples[i])); + { + sp_str_t str = sp_fmt(mem, "hello, {}!", sp_fmt_cstr("world")).value; } - section(".iso"); - u64 epoch_samples[] = { 0ULL, 1705330245ULL, 1735689599ULL }; - sp_carr_for(epoch_samples, i) { - sp_log("{:<10} -> {.iso}", sp_fmt_uint(epoch_samples[i]), sp_fmt_uint(epoch_samples[i])); + { + sp_str_r result = sp_fmt(mem, "hello, {}!", sp_fmt_cstr("world")); + if (result.err) { + // ... + } + sp_str_t str = result.value; } - section(".ordinal"); - s64 ord_samples[] = { 1, 2, 3, 4, 11, 12, 13, 21, 22, 23, 101, 102, 113 }; - sp_carr_for(ord_samples, i) { - sp_log("{:<3} -> {.ordinal}", sp_fmt_int(ord_samples[i]), sp_fmt_int(ord_samples[i])); + { + c8 buffer [64] = sp_zero; + sp_io_mem_writer_t io = sp_zero; + sp_io_mem_writer_from_buffer(&io, buffer, sizeof(buffer)); + if (sp_fmt_io(&io.base, "hello, {}", sp_fmt_cstr("world"))) { + // ... + } + sp_str_t str = sp_io_mem_writer_as_str(&io); } - section(".duration"); - u64 dur_samples[] = { 500ULL, 1500ULL, 2500000ULL, 3500000000ULL, 90000000000ULL }; - sp_carr_for(dur_samples, i) { - sp_log("{:<11} -> {.duration}", sp_fmt_uint(dur_samples[i]), sp_fmt_uint(dur_samples[i])); + { + sp_io_dyn_mem_writer_t io = sp_zero; + sp_io_dyn_mem_writer_init(mem, &io); + sp_fmt_io(&io.base, "hello, {}!", sp_fmt_cstr("world")); + sp_str_t str = sp_io_dyn_mem_writer_as_str(&io); + sp_str_t owned = sp_io_dyn_mem_writer_take_str(&io); } - section(":specifier"); - specifier(69, '*', SP_FMT_ALIGN_LEFT, 9); - specifier(69, '*', SP_FMT_ALIGN_CENTER, 9); - specifier(69, '*', SP_FMT_ALIGN_RIGHT, 9); - specifier(69, '0', SP_FMT_ALIGN_RIGHT, 5); - specifier(694, '0', SP_FMT_ALIGN_RIGHT, 5); - specifier(6942, '0', SP_FMT_ALIGN_RIGHT, 5); - specifier(69420, '0', SP_FMT_ALIGN_RIGHT, 5); - - section("composition"); - struct { const c8* name; sp_str_t str; } examples [] = { - { ".italic + .bold", sp_fmt(mem, "i never {.italic .bold} the kenosha kid", sp_fmt_cstr("did")).value }, - { ".bold + .cyan", sp_fmt(mem, "i never {.bold .cyan} the kenosha kid", sp_fmt_cstr("did")).value }, - { "kitchen sink", sp_fmt(mem, "i never {.quote .bold .italic .green} the kenosha kid", sp_fmt_cstr("did")).value }, - { - ".bytes + :specifier", - sp_fmt(mem, "{} bytes is [{:^$ .bytes}]", sp_fmt_uint(1536), sp_fmt_uint(12), sp_fmt_uint(1536)).value - }, - { - ".duration + .bold", - sp_fmt(mem, "{.bold .duration}", sp_fmt_uint(2500000)).value - } + row("{}", sp_fmt_int(-42)); + row("{}", sp_fmt_uint(42)); + row("{}", sp_fmt_float(1.5)); + row("{}", sp_fmt_cstr("hi")); + row("{}", sp_fmt_ptr((void*)0xabcd)); + row("{{}}"); + row("{:6}", sp_fmt_int(42)); + row("{:<6}", sp_fmt_int(42)); + row("{:^6}", sp_fmt_int(42)); + row("{:>6}", sp_fmt_int(42)); + row("{:*^9}", sp_fmt_int(42)); + row("{:0>5}", sp_fmt_int(42)); + row("{:.2}", sp_fmt_float(3.14159)); + row("{:.3}", sp_fmt_cstr("hello")); + row("{:$}", sp_fmt_uint(6), sp_fmt_int(42)); + row("{:.$}", sp_fmt_uint(3), sp_fmt_float(3.14159)); + row("{:$^9}", sp_fmt_int('*'), sp_fmt_int(42)); + row("{:$<$.$}", sp_fmt_int('*'), sp_fmt_uint(8), sp_fmt_uint(2), sp_fmt_float(1.5)); + row("{:x}", sp_fmt_uint(255)); + row("{:X}", sp_fmt_uint(255)); + row("{:o}", sp_fmt_uint(255)); + row("{:b}", sp_fmt_uint(255)); + row("{:c}", sp_fmt_uint('A')); + row("{B}", sp_fmt_uint(1536)); + row("{.red}", sp_fmt_cstr("error")); + row("{.bold}", sp_fmt_cstr("strong")); + row("{.italic}", sp_fmt_cstr("emphasis")); + row("{.quote}", sp_fmt_cstr("supposedly")); + row("{.bold .italic .green}", sp_fmt_cstr("composed")); + render_table(); + + + return SP_OK; +} +SP_MAIN(run) - }; +//////////// +// TABLES // +//////////// +// This is a tiny table renderer to visualize the examples. It has no bearing +// on the example itself and you should skip over it. +typedef struct { + sp_str_t text; + sp_fmt_style_t style; +} cell_t; + +typedef struct { + cell_t cols [3]; +} row_t; + +typedef struct { + sp_da(row_t) rows; + u32 width [3]; +} ctx_t; + +static ctx_t ctx; + +void push_row(sp_str_t fmt, sp_str_t args, sp_str_t result) { + if (!ctx.rows) sp_da_init(mem, ctx.rows); + sp_da_push(ctx.rows, ((row_t){{ + { .text = fmt, .style = sp_fmt_style_yellow }, + { .text = args, .style = sp_fmt_style_none }, + { .text = result, .style = sp_fmt_style_none }, + }})); +} - u32 width = 0; - sp_carr_for(examples, it) { - width = sp_max(width, sp_cstr_len(examples[it].name)); +void render_row(row_t row) { + sp_str_t cells [3]; + sp_for(it, 3) { + cells[it] = sp_fmt(mem, "{:<$ .$}", + sp_fmt_uint(ctx.width[it]), + sp_fmt_uint(row.cols[it].style), + sp_fmt_str(row.cols[it].text) + ).value; } - sp_carr_for(examples, it) { - sp_log("{:<$} -> {}", sp_fmt_uint(width), sp_fmt_cstr(examples[it].name), sp_fmt_str(examples[it].str)); - } - - section("custom"); - point_t point = { 69, 420 }; + sp_log("{}", sp_fmt_str(sp_str_join_n(mem, cells, 3, sp_str_lit(" ")))); +} - // In C, you can use the &(foo_t) { 69 } syntax to take the address of a temporary and omit - // the variable, which is pretty nice. But it's not possible in C++. - #if !defined(SP_CPP) - sp_log("{}", sp_fmt_point(((point_t) { 69, 420 }))); - sp_log("{}", sp_fmt_point_v(69, 420)); - #endif - sp_log("{.cyan .bold}", sp_fmt_point(point)); +void render_table() { + sp_da_for(ctx.rows, r) { + sp_for(c, 3) ctx.width[c] = sp_max(ctx.width[c], ctx.rows[r].cols[c].text.len); + } - return 0; + render_row((row_t){{ + { sp_str_lit("fmt"), sp_fmt_style_gray }, + { sp_str_lit("args"), sp_fmt_style_gray }, + { sp_str_lit("result"), sp_fmt_style_gray }, + }}); + sp_da_for(ctx.rows, r) render_row(ctx.rows[r]); } -SP_MAIN(run) diff --git a/sp.h b/sp.h index 3526c5a..0e70a50 100644 --- a/sp.h +++ b/sp.h @@ -464,6 +464,7 @@ #define sp_max(a, b) (((a) > (b)) ? (a) : (b)) #define sp_min(a, b) (((a) > (b)) ? (b) : (a)) #define sp_clamp(v, lo, hi) (((v) < (lo)) ? (lo) : ((v) > (hi)) ? (hi) : (v)) +#define sp_uabs(v) ((v) < 0 ? (0ull - (u64)(v)) : (u64)(v)) #define sp_swap(t, a, b) { t SP_UNIQUE_ID() = (a); (a) = (b); (b) = SP_UNIQUE_ID(); } #define SP_QSORT_A_FIRST -1 @@ -765,19 +766,17 @@ typedef enum { SP_ERR_IO_EOF = 1010, SP_ERR_IO_INVALID_WRITE = 1011, SP_ERR_IO_UNIMPLEMENTED = 1012, - SP_ERR_FMT_TOO_MANY_RENDERERS = 1100, - SP_ERR_FMT_WRONG_PARAM_KIND = 1101, SP_ERR_FMT_UNKNOWN_DIRECTIVE = 1102, SP_ERR_FMT_BAD_DIRECTIVE = 1103, SP_ERR_FMT_TOO_MANY_DIRECTIVES = 1104, SP_ERR_FMT_BAD_PRECISION = 1105, SP_ERR_FMT_BAD_PLACEHOLDER = 1106, - SP_ERR_FMT_DIRECTIVE_ARG_MISSING = 1107, - SP_ERR_FMT_DIRECTIVE_ARG_UNEXPECTED = 1108, - SP_ERR_FMT_DIRECTIVE_ARG_WRONG_KIND = 1109, SP_ERR_FMT_UNTERMINATED_PLACEHOLDER = 1111, - SP_ERR_FMT_CUSTOM_WITHOUT_FN = 1112, - SP_ERR_FMT_WIDTH_ON_OPAQUE_RENDERER = 1113, + SP_ERR_FMT_BAD_ARG = 1114, + SP_ERR_FMT_WRONG_FILL_KIND = 1115, + SP_ERR_FMT_WRONG_WIDTH_KIND = 1116, + SP_ERR_FMT_WRONG_PRECISION_KIND = 1117, + SP_ERR_FMT_WRONG_STYLE_KIND = 1118, SP_ERR_LAZY, SP_ERR_OS, } sp_err_t; @@ -2919,13 +2918,54 @@ SP_API void sp_assert_f(sp_str_t file, sp_str_t line, sp_str_t func, s SP_API sp_str_r sp_fmt(sp_mem_t mem, const c8* fmt, ...); SP_API const c8* sp_fmt_mem_cstr(sp_mem_t mem, const c8* fmt, ...); SP_API sp_str_r sp_fmt_mem_v(sp_mem_t mem, sp_str_t fmt, va_list args); -SP_API sp_str_r sp_fmt_buf(c8* buffer, u64 len, const c8* fmt, ...); +SP_API sp_str_r __sp_fmt_buf(c8* buffer, u64 len, const c8* fmt, ...); SP_API sp_str_r sp_fmt_buf_v(c8* buffer, u64 len, sp_str_t fmt, va_list args); SP_API sp_err_t sp_fmt_io(sp_io_writer_t* io, const c8* fmt, ...); SP_API sp_err_t sp_fmt_io_v(sp_io_writer_t* io, sp_str_t fmt, va_list args); SP_API sp_err_t sp_fmt_std_out(const c8* fmt, ...); SP_API sp_err_t sp_fmt_std_err(const c8* fmt, ...); +SP_API sp_err_t sp_fmt_write_s64(sp_io_writer_t* io, s64 value); +SP_API sp_err_t sp_fmt_write_ptr(sp_io_writer_t* io, void* value); +SP_API void sp_fmt_write_hex(sp_io_writer_t* io, u64 value); +SP_API void sp_fmt_write_hex_upper(sp_io_writer_t* io, u64 value); +SP_API void sp_fmt_write_bin(sp_io_writer_t* io, u64 value); +SP_API void sp_fmt_write_oct(sp_io_writer_t* io, u64 value); +SP_API sp_err_t sp_fmt_write_bool(sp_io_writer_t* io, bool value); +SP_API sp_err_t sp_fmt_write_size(sp_io_writer_t* io, u64 bytes); +SP_API sp_err_t sp_fmt_write_duration(sp_io_writer_t* io, u64 ns); +SP_API sp_err_t sp_fmt_write_ordinal(sp_io_writer_t* io, s64 value); + +typedef enum { + SP_FMT_RADIX_DECIMAL, + SP_FMT_RADIX_BINARY, + SP_FMT_RADIX_OCTAL, + SP_FMT_RADIX_HEX, + SP_FMT_RADIX_HEX_UPPER, +} sp_fmt_radix_t; + +SP_API sp_err_t sp_fmt_write_u64(sp_io_writer_t* io, u64 value); +SP_API sp_err_t sp_fmt_write_u64_ex(sp_io_writer_t* io, u64 value, sp_fmt_radix_t radix); +SP_API sp_str_r sp_fmt_write_u64_mem(sp_mem_t mem, u64 value, sp_fmt_radix_t radix); +SP_API sp_str_r sp_fmt_write_u64_buf(c8* buffer, u64 len, u64 value, sp_fmt_radix_t radix); +SP_API sp_err_t sp_fmt_write_f64(sp_io_writer_t* io, f64 value); +SP_API sp_err_t sp_fmt_write_f64_ex(sp_io_writer_t* io, f64 value, u32 precision); + +SP_API sp_str_r sp_fmt_write_s64_mem(sp_mem_t mem, s64 value); +SP_API sp_str_r sp_fmt_write_s64_buf(c8* buffer, u64 len, s64 value); +SP_API sp_str_r sp_fmt_write_f64_mem(sp_mem_t mem, f64 value, u32 precision); +SP_API sp_str_r sp_fmt_write_f64_buf(c8* buffer, u64 len, f64 value, u32 precision); +SP_API sp_str_r sp_fmt_write_ptr_mem(sp_mem_t mem, void* value); +SP_API sp_str_r sp_fmt_write_ptr_buf(c8* buffer, u64 len, void* value); +SP_API sp_str_r sp_fmt_write_bool_mem(sp_mem_t mem, bool value); +SP_API sp_str_r sp_fmt_write_bool_buf(c8* buffer, u64 len, bool value); +SP_API sp_str_r sp_fmt_write_size_mem(sp_mem_t mem, u64 bytes); +SP_API sp_str_r sp_fmt_write_size_buf(c8* buffer, u64 len, u64 bytes); +SP_API sp_str_r sp_fmt_write_duration_mem(sp_mem_t mem, u64 ns); +SP_API sp_str_r sp_fmt_write_duration_buf(c8* buffer, u64 len, u64 ns); +SP_API sp_str_r sp_fmt_write_ordinal_mem(sp_mem_t mem, s64 value); +SP_API sp_str_r sp_fmt_write_ordinal_buf(c8* buffer, u64 len, s64 value); + typedef enum { SP_FMT_ALIGN_NONE, SP_FMT_ALIGN_LEFT, @@ -2936,36 +2976,61 @@ typedef enum { #define SP_FMT_MAX_DIRECTIVES 8 #define SP_FMT_WIDTH_MAX 4096 +#define sp_fmt_dynamic_directive(_i) (1u << (_i)) +#define SP_FMT_DYNAMIC_FILL (1u << (SP_FMT_MAX_DIRECTIVES + 0)) +#define SP_FMT_DYNAMIC_WIDTH (1u << (SP_FMT_MAX_DIRECTIVES + 1)) +#define SP_FMT_DYNAMIC_PRECISION (1u << (SP_FMT_MAX_DIRECTIVES + 2)) + typedef enum { - sp_fmt_id_none = 0, - sp_fmt_id_u64 = 1 << 0, - sp_fmt_id_s64 = 1 << 1, - sp_fmt_id_f64 = 1 << 2, - sp_fmt_id_str = 1 << 3, - sp_fmt_id_ptr = 1 << 4, - sp_fmt_id_custom = 1 << 5, + sp_fmt_id_none = 0, + sp_fmt_id_u64, + sp_fmt_id_s64, + sp_fmt_id_f64, + sp_fmt_id_str, + sp_fmt_id_ptr, } sp_fmt_arg_kind_t; +typedef enum { + sp_fmt_style_none = 0, + sp_fmt_style_unknown, + sp_fmt_style_black, + sp_fmt_style_red, + sp_fmt_style_green, + sp_fmt_style_yellow, + sp_fmt_style_blue, + sp_fmt_style_magenta, + sp_fmt_style_cyan, + sp_fmt_style_white, + sp_fmt_style_gray, + sp_fmt_style_br_red, + sp_fmt_style_br_green, + sp_fmt_style_br_yellow, + sp_fmt_style_br_blue, + sp_fmt_style_br_magenta, + sp_fmt_style_br_cyan, + sp_fmt_style_br_white, + sp_fmt_style_bold, + sp_fmt_style_italic, + sp_fmt_style_hyperlink, + sp_fmt_style_quote, +} sp_fmt_style_t; + typedef struct { + c8 renderer; u32 width; sp_fmt_align_t align; u8 fill; sp_opt(u8) precision; - struct { - u8 fill; - u8 width; - u8 precision; - u8 directive; - } dynamic; + u16 dynamic; struct { u8 num; sp_str_t names [SP_FMT_MAX_DIRECTIVES]; - sp_str_t args [SP_FMT_MAX_DIRECTIVES]; + sp_fmt_style_t styles [SP_FMT_MAX_DIRECTIVES]; } directive; } sp_fmt_spec_t; typedef struct sp_fmt_arg sp_fmt_arg_t; -SP_TYPEDEF_FN(void, sp_fmt_fn_t, sp_io_writer_t*, sp_fmt_arg_t*, sp_fmt_arg_t*); +SP_TYPEDEF_FN(sp_err_t, sp_fmt_fn_t, sp_io_writer_t*, sp_fmt_arg_t*); typedef union { u64 u; @@ -2973,40 +3038,21 @@ typedef union { f64 f; sp_str_t s; void* p; - struct { sp_fmt_fn_t fn; void* ptr; } custom; } sp_fmt_value_t; typedef struct { sp_fmt_arg_kind_t id; sp_fmt_value_t value; + sp_fmt_fn_t fn; } sp_fmt_argv_t; struct sp_fmt_arg { sp_fmt_arg_kind_t id; sp_fmt_spec_t spec; sp_fmt_value_t value; + sp_fmt_fn_t fn; }; -typedef enum { - sp_fmt_directive_renderer, - sp_fmt_directive_decorator, -} sp_fmt_directive_kind_t; - -typedef struct { - sp_fmt_directive_kind_t kind; - sp_fmt_arg_kind_t args; - sp_fmt_arg_kind_t params; - union { - struct { - sp_fmt_fn_t before; - sp_fmt_fn_t after; - } decorator; - sp_fmt_fn_t renderer; - }; -} sp_fmt_directive_t; - -SP_API void sp_fmt_render_default(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param); -SP_API void sp_fmt_directive_register(const c8* name, sp_fmt_directive_t directive); #define sp_fmt_uint(_value) (sp_fmt_argv_t) { .id = sp_fmt_id_u64, .value = { .u = (_value) } } #define sp_fmt_int(_value) (sp_fmt_argv_t) { .id = sp_fmt_id_s64, .value = { .i = (_value) } } @@ -3015,6 +3061,29 @@ SP_API void sp_fmt_directive_register(const c8* name, sp_fmt_directive_t di #define sp_fmt_str(_value) (sp_fmt_argv_t) { .id = sp_fmt_id_str, .value = { .s = (_value) } } #define sp_fmt_cstr(_value) (sp_fmt_argv_t) { .id = sp_fmt_id_str, .value = { .s = sp_cstr_as_str(_value) } } #define sp_fmt_ptr(_value) (sp_fmt_argv_t) { .id = sp_fmt_id_ptr, .value = { .p = (_value) } } +#define sp_fmt_u64_custom(_value, _fn) (sp_fmt_argv_t) { .id = sp_fmt_id_u64, .value = { .u = (_value) }, .fn = (_fn) } + +#define sp_fmt_style(_style) (sp_fmt_argv_t) { .id = sp_fmt_id_s64, .value = { .i = (_style) } } +#define sp_fmt_black() sp_fmt_style(sp_fmt_style_black) +#define sp_fmt_red() sp_fmt_style(sp_fmt_style_red) +#define sp_fmt_green() sp_fmt_style(sp_fmt_style_green) +#define sp_fmt_yellow() sp_fmt_style(sp_fmt_style_yellow) +#define sp_fmt_blue() sp_fmt_style(sp_fmt_style_blue) +#define sp_fmt_magenta() sp_fmt_style(sp_fmt_style_magenta) +#define sp_fmt_cyan() sp_fmt_style(sp_fmt_style_cyan) +#define sp_fmt_white() sp_fmt_style(sp_fmt_style_white) +#define sp_fmt_gray() sp_fmt_style(sp_fmt_style_gray) +#define sp_fmt_br_red() sp_fmt_style(sp_fmt_style_br_red) +#define sp_fmt_br_green() sp_fmt_style(sp_fmt_style_br_green) +#define sp_fmt_br_yellow() sp_fmt_style(sp_fmt_style_br_yellow) +#define sp_fmt_br_blue() sp_fmt_style(sp_fmt_style_br_blue) +#define sp_fmt_br_magenta() sp_fmt_style(sp_fmt_style_br_magenta) +#define sp_fmt_br_cyan() sp_fmt_style(sp_fmt_style_br_cyan) +#define sp_fmt_br_white() sp_fmt_style(sp_fmt_style_br_white) +#define sp_fmt_bold() sp_fmt_style(sp_fmt_style_bold) +#define sp_fmt_italic() sp_fmt_style(sp_fmt_style_italic) +#define sp_fmt_hyperlink() sp_fmt_style(sp_fmt_style_hyperlink) +#define sp_fmt_quote() sp_fmt_style(sp_fmt_style_quote) // Use a tiny, portable trick to get some damn good type safety for custom format string arguments: // @@ -3044,44 +3113,11 @@ SP_API void sp_fmt_directive_register(const c8* name, sp_fmt_directive_t di // sp_fmt("{}", sp_fmt_foo(my_foo)); // #define sp_fmt_custom(T, _fn, _value) ( \ - (void)sizeof(*(T*)0 = (_value)), \ - (sp_fmt_argv_t) { \ - .id = sp_fmt_id_custom, \ - .value = { \ - .custom = { \ - .fn = (_fn), \ - .ptr = (void*)&(_value) \ - } \ - } \ - }) -#define sp_fmt_custom_v(T, _fn, ...) ( \ - (void)sizeof(*(T*)0 = (__VA_ARGS__)), \ - (sp_fmt_argv_t) { \ - .id = sp_fmt_id_custom, \ - .value.custom = { \ - .fn = (_fn), \ - .ptr = (void*)&(__VA_ARGS__) \ - } \ - }) - -#define sp_fmt_register_renderer(_name, _fn, _args) \ - sp_fmt_directive_register(_name, (sp_fmt_directive_t) { \ - .kind = sp_fmt_directive_renderer, \ - .args = _args, \ - .renderer = _fn, \ - }) - -#define sp_fmt_register_decorator(_name, _before, _after) \ - sp_fmt_directive_register(_name, (sp_fmt_directive_t) { \ - .kind = sp_fmt_directive_decorator, \ - .decorator = { .before = (_before), .after = (_after) }, \ - }) - -#define sp_fmt_register_decorator_p(_name, _before, _after, _params) \ - sp_fmt_directive_register(_name, (sp_fmt_directive_t) { \ - .kind = sp_fmt_directive_decorator, \ - .params = sp_cast(sp_fmt_arg_kind_t, _params), \ - .decorator = { .before = (_before), .after = (_after) }, \ + (void)sizeof(*(T*)0 = (_value)), \ + (sp_fmt_argv_t) { \ + .id = sp_fmt_id_ptr, \ + .value = { .p = (void*)&(_value) }, \ + .fn = (_fn), \ }) @@ -3167,9 +3203,6 @@ typedef struct { } mem; sp_mem_arena_t* scratch [2]; sp_env_t env; - struct { - sp_ht(sp_str_t, sp_fmt_directive_t) directives; - } format; struct { sp_io_stream_writer_t* out; sp_io_stream_writer_t* err; @@ -3725,18 +3758,15 @@ typedef struct { u32 i; } sp_fmt_parser_t; -SP_IMP sp_fmt_directive_t* sp_fmt_directive_lookup(sp_str_t name); -SP_IMP void sp_fmt_register_builtins(); +SP_IMP sp_fmt_style_t sp_fmt_style_from_name(sp_str_t name); SP_IMP c8* sp_fmt_uint_to_buf_dec(u64 value, c8* buf_end); SP_IMP c8* sp_fmt_uint_to_buf_hex_ex(u64 value, c8* buf_end, const c8* digits); SP_IMP c8* sp_fmt_uint_to_buf_hex(u64 value, c8* buf_end); -SP_IMP void sp_fmt_write_u64(sp_io_writer_t* io, u64 value); -SP_IMP void sp_fmt_write_s64(sp_io_writer_t* io, s64 value); -SP_IMP void sp_fmt_write_f64(sp_io_writer_t* io, f64 value, u32 precision); -SP_IMP void sp_fmt_write_ptr(sp_io_writer_t* io, void* value); -SP_IMP sp_err_t sp_fmt_render(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params); +SP_IMP c8* sp_fmt_uint_to_buf_bin(u64 value, c8* buf_end); +SP_IMP c8* sp_fmt_uint_to_buf_oct(u64 value, c8* buf_end); +SP_IMP sp_err_t sp_fmt_render(sp_io_writer_t* io, sp_fmt_arg_t* arg); +SP_IMP sp_err_t sp_fmt_render_bytes(sp_io_writer_t* io, sp_fmt_arg_t* arg); SP_IMP sp_err_t sp_fmt_parse_specifier(sp_fmt_parser_t* p, sp_fmt_spec_t* spec); -SP_IMP sp_str_t sp_fmt_color_to_ansi_fg(sp_str_t id); SP_IMP sp_fmt_arg_t sp_fmt_arg_from_argv(sp_fmt_argv_t v); @@ -6592,7 +6622,7 @@ s64 sp_sys_canonicalize_path(const c8* path, u32 len, c8* buf, u64 size) { if (fd < 0) return fd; c8 proc [64] = sp_zero; - sp_fmt_buf(proc, 64, "/proc/self/fd/{}", sp_fmt_int(fd)); + __sp_fmt_buf(proc, 64, "/proc/self/fd/{}", sp_fmt_int(fd)); s64 n = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, proc, buf, size); sp_sys_close(fd); @@ -7121,18 +7151,6 @@ void* sp_rb_grow_ex(void* arr, u32 stride, u32 capacity) { // █████ ░░░███████░ █████ █████ █████ █████ █████ █████ █████ // ░░░░░ ░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ // @format -void sp_fmt_directive_register(const c8* name, sp_fmt_directive_t directive) { - sp_tls_rt_t* tls = sp_tls_rt_get(); - sp_str_t id = sp_str_from_cstr(tls->mem.heap, name); - sp_str_ht_insert(tls->format.directives, id, directive); -} - -sp_fmt_directive_t* sp_fmt_directive_lookup(sp_str_t name) { - sp_tls_rt_t* tls = sp_tls_rt_get(); - u64 index = 0; - return sp_str_ht_get_ex(tls->format.directives, name, index); -} - static u8 sp_fmt_peek(sp_fmt_parser_t* p, u32 offset) { u32 idx = p->i + offset; if (idx >= p->str.len) return 0; @@ -7182,6 +7200,10 @@ static bool sp_fmt_is_align(u8 c) { return c == '<' || c == '^' || c == '>'; } +static bool sp_fmt_is_renderer(u8 c) { + return c == 'B' || c == 'b' || c == 'x' || c == 'X' || c == 'o' || c == 'c'; +} + static sp_str_t sp_fmt_sub(sp_fmt_parser_t* p) { return (sp_str_t) { .data = p->str.data + p->i, @@ -7204,17 +7226,6 @@ static sp_str_t sp_fmt_directive_name(sp_fmt_parser_t* p) { return word; } -static sp_str_t sp_fmt_directive_arg(sp_fmt_parser_t* p) { - sp_str_t arg = sp_fmt_sub(p); - while (true) { - c8 c = sp_fmt_peek(p, 0); - if (!c || sp_fmt_is_whitespace(c) || c == '}' || c == '.' || c == '$') break; - sp_fmt_advance(p); - arg.len++; - } - return arg; -} - static bool sp_fmt_at_directive_boundary(sp_fmt_parser_t* p) { c8 c = sp_fmt_peek(p, 0); return c == 0 || c == '}' || sp_fmt_is_whitespace(c); @@ -7239,11 +7250,11 @@ static sp_err_t sp_fmt_parse_number(sp_fmt_parser_t* p, u32* out) { } // Parses the spec body that follows the `:` introducer: -// [fill_align] [width] [.precision] +// [fill_align] [width] [.precision] [renderer] static sp_err_t sp_fmt_parse_spec_body(sp_fmt_parser_t* p, sp_fmt_spec_t* spec) { sp_fmt_peek_t peek = sp_fmt_peek2(p); if (sp_fmt_is_align(peek.second)) { - if (peek.first == '$') spec->dynamic.fill = 1; + if (peek.first == '$') spec->dynamic |= SP_FMT_DYNAMIC_FILL; else spec->fill = peek.first; spec->align = sp_fmt_align_from_char(peek.second); sp_fmt_advance(p); @@ -7257,17 +7268,18 @@ static sp_err_t sp_fmt_parse_spec_body(sp_fmt_parser_t* p, sp_fmt_spec_t* spec) c8 c = sp_fmt_peek(p, 0); if (c == '$') { sp_fmt_advance(p); - spec->dynamic.width = 1; + spec->dynamic |= SP_FMT_DYNAMIC_WIDTH; } else if (sp_fmt_is_digit(c)) { sp_fmt_parse_number(p, &spec->width); + if (spec->width > SP_FMT_WIDTH_MAX) spec->width = SP_FMT_WIDTH_MAX; } if (sp_fmt_peek(p, 0) == '.') { sp_fmt_advance(p); if (sp_fmt_peek(p, 0) == '$') { sp_fmt_advance(p); - spec->dynamic.precision = 1; + spec->dynamic |= SP_FMT_DYNAMIC_PRECISION; } else { u32 prec = 0; @@ -7275,43 +7287,33 @@ static sp_err_t sp_fmt_parse_spec_body(sp_fmt_parser_t* p, sp_fmt_spec_t* spec) sp_opt_set(spec->precision, (u8)prec); } } + + if (sp_fmt_is_renderer(sp_fmt_peek(p, 0))) spec->renderer = sp_fmt_advance(p); return SP_OK; } -// Parses a single directive that follows the `.` introducer: -// name [whitespace (dynamic | literal_arg)] -// On success, leaves the cursor on a directive boundary (whitespace, `}`, or -// EOF). The trailing whitespace — if any — is left for the top-level loop to -// consume: it separates this directive from the next one. +// Parses a single directive that follows the `.` introducer: just a name. On +// success, leaves the cursor on a directive boundary (whitespace, `}`, or EOF). +// The trailing whitespace — if any — is left for the top-level loop to consume: +// it separates this directive from the next one. static sp_err_t sp_fmt_parse_directive(sp_fmt_parser_t* p, sp_fmt_spec_t* spec) { if (spec->directive.num >= SP_FMT_MAX_DIRECTIVES) { return SP_ERR_FMT_TOO_MANY_DIRECTIVES; } + if (sp_fmt_match(p, '$')) { + u32 index = spec->directive.num++; + spec->dynamic |= sp_fmt_dynamic_directive(index); + if (!sp_fmt_at_directive_boundary(p)) return SP_ERR_FMT_BAD_PLACEHOLDER; + return SP_OK; + } + sp_str_t name = sp_fmt_directive_name(p); if (!name.len) return SP_ERR_FMT_BAD_DIRECTIVE; u32 index = spec->directive.num++; spec->directive.names[index] = name; - // An arg must be preceded by whitespace. Peek past any whitespace to - // decide whether one follows — if not, leave the cursor on the whitespace - // (or wherever it is) for the boundary check and the top-level loop. - if (sp_fmt_is_whitespace(sp_fmt_peek(p, 0))) { - u32 off = 1; - while (sp_fmt_is_whitespace(sp_fmt_peek(p, off))) off++; - c8 c = sp_fmt_peek(p, off); - if (c == '$') { - sp_fmt_eat_whitespace(p); - sp_fmt_advance(p); - spec->dynamic.directive |= (u8)(1u << index); - } - else if (c && c != '}' && c != '.') { - sp_fmt_eat_whitespace(p); - spec->directive.args[index] = sp_fmt_directive_arg(p); - } - } - if (!sp_fmt_at_directive_boundary(p)) return SP_ERR_FMT_BAD_PLACEHOLDER; return SP_OK; } @@ -7339,258 +7341,87 @@ sp_err_t sp_fmt_parse_specifier(sp_fmt_parser_t* p, sp_fmt_spec_t* spec) { static sp_err_t sp_fmt_pull_specifier_arg(sp_fmt_argv_t a, s64* out) { if (a.id != sp_fmt_id_u64 && a.id != sp_fmt_id_s64) { - return SP_ERR_FMT_DIRECTIVE_ARG_WRONG_KIND; + return SP_ERR; } *out = (a.id == sp_fmt_id_s64) ? a.value.i : (s64)a.value.u; return SP_OK; } -static void sp_fmt_directive_bold(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - (void)arg; sp_unused(params); - sp_io_write_cstr(io, "\033[1m", SP_NULLPTR); -} - -static void sp_fmt_directive_italic(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - (void)arg; sp_unused(params); - sp_io_write_cstr(io, "\033[3m", SP_NULLPTR); -} - -static void sp_fmt_directive_red(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_RED, SP_NULLPTR); -} - -static void sp_fmt_directive_green(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_GREEN, SP_NULLPTR); -} - -static void sp_fmt_directive_yellow(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_YELLOW, SP_NULLPTR); -} - -static void sp_fmt_directive_blue(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_BLUE, SP_NULLPTR); -} - -static void sp_fmt_directive_cyan(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_CYAN, SP_NULLPTR); -} - -static void sp_fmt_directive_magenta(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_MAGENTA, SP_NULLPTR); -} - -static void sp_fmt_directive_white(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_WHITE, SP_NULLPTR); -} - -static void sp_fmt_directive_black(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_BLACK, SP_NULLPTR); -} - -static void sp_fmt_directive_bright_red(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_BRIGHT_RED, SP_NULLPTR); -} - -static void sp_fmt_directive_bright_green(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_BRIGHT_GREEN, SP_NULLPTR); -} - -static void sp_fmt_directive_bright_yellow(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_BRIGHT_YELLOW, SP_NULLPTR); -} - -static void sp_fmt_directive_bright_blue(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_BRIGHT_BLUE, SP_NULLPTR); -} - -static void sp_fmt_directive_bright_cyan(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_BRIGHT_CYAN, SP_NULLPTR); -} - -static void sp_fmt_directive_bright_magenta(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_BRIGHT_MAGENTA, SP_NULLPTR); -} - -static void sp_fmt_directive_bright_white(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_BRIGHT_WHITE, SP_NULLPTR); -} - -static void sp_fmt_directive_gray(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(arg); sp_unused(params); - sp_io_write_cstr(io, SP_ANSI_FG_BRIGHT_BLACK, SP_NULLPTR); -} - -static void sp_fmt_directive_ansi_reset(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - sp_unused(arg); sp_unused(param); - sp_io_write_cstr(io, SP_ANSI_RESET, SP_NULLPTR); -} - -static void sp_fmt_directive_hyperlink(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(params); - sp_io_write_cstr(io, "\033]8;;", SP_NULLPTR); - if (arg->id == sp_fmt_id_str) sp_io_write_str(io, arg->value.s, SP_NULLPTR); - sp_io_write_cstr(io, "\033\\", SP_NULLPTR); -} - -static void sp_fmt_directive_quote(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - (void)arg; sp_unused(params); - sp_io_write_c8(io, '"'); -} - -static void sp_fmt_directive_quote_after(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - (void)arg; sp_unused(params); - sp_io_write_c8(io, '"'); +sp_fmt_style_t sp_fmt_style_from_name(sp_str_t name) { + if (sp_str_equal_cstr(name, "black")) return sp_fmt_style_black; + if (sp_str_equal_cstr(name, "red")) return sp_fmt_style_red; + if (sp_str_equal_cstr(name, "green")) return sp_fmt_style_green; + if (sp_str_equal_cstr(name, "yellow")) return sp_fmt_style_yellow; + if (sp_str_equal_cstr(name, "blue")) return sp_fmt_style_blue; + if (sp_str_equal_cstr(name, "magenta")) return sp_fmt_style_magenta; + if (sp_str_equal_cstr(name, "cyan")) return sp_fmt_style_cyan; + if (sp_str_equal_cstr(name, "white")) return sp_fmt_style_white; + if (sp_str_equal_cstr(name, "gray")) return sp_fmt_style_gray; + if (sp_str_equal_cstr(name, "br_black")) return sp_fmt_style_gray; + if (sp_str_equal_cstr(name, "br_red")) return sp_fmt_style_br_red; + if (sp_str_equal_cstr(name, "br_green")) return sp_fmt_style_br_green; + if (sp_str_equal_cstr(name, "br_yellow")) return sp_fmt_style_br_yellow; + if (sp_str_equal_cstr(name, "br_blue")) return sp_fmt_style_br_blue; + if (sp_str_equal_cstr(name, "br_magenta")) return sp_fmt_style_br_magenta; + if (sp_str_equal_cstr(name, "br_cyan")) return sp_fmt_style_br_cyan; + if (sp_str_equal_cstr(name, "br_white")) return sp_fmt_style_br_white; + if (sp_str_equal_cstr(name, "bold")) return sp_fmt_style_bold; + if (sp_str_equal_cstr(name, "italic")) return sp_fmt_style_italic; + if (sp_str_equal_cstr(name, "hyperlink")) return sp_fmt_style_hyperlink; + if (sp_str_equal_cstr(name, "quote")) return sp_fmt_style_quote; + return sp_fmt_style_unknown; +} + +static const c8* sp_fmt_style_ansi(sp_fmt_style_t style) { + switch (style) { + case sp_fmt_style_black: return SP_ANSI_FG_BLACK; + case sp_fmt_style_red: return SP_ANSI_FG_RED; + case sp_fmt_style_green: return SP_ANSI_FG_GREEN; + case sp_fmt_style_yellow: return SP_ANSI_FG_YELLOW; + case sp_fmt_style_blue: return SP_ANSI_FG_BLUE; + case sp_fmt_style_magenta: return SP_ANSI_FG_MAGENTA; + case sp_fmt_style_cyan: return SP_ANSI_FG_CYAN; + case sp_fmt_style_white: return SP_ANSI_FG_WHITE; + case sp_fmt_style_gray: return SP_ANSI_FG_BRIGHT_BLACK; + case sp_fmt_style_br_red: return SP_ANSI_FG_BRIGHT_RED; + case sp_fmt_style_br_green: return SP_ANSI_FG_BRIGHT_GREEN; + case sp_fmt_style_br_yellow: return SP_ANSI_FG_BRIGHT_YELLOW; + case sp_fmt_style_br_blue: return SP_ANSI_FG_BRIGHT_BLUE; + case sp_fmt_style_br_magenta: return SP_ANSI_FG_BRIGHT_MAGENTA; + case sp_fmt_style_br_cyan: return SP_ANSI_FG_BRIGHT_CYAN; + case sp_fmt_style_br_white: return SP_ANSI_FG_BRIGHT_WHITE; + case sp_fmt_style_bold: return "\033[1m"; + case sp_fmt_style_italic: return "\033[3m"; + case sp_fmt_style_none: + case sp_fmt_style_unknown: + case sp_fmt_style_hyperlink: + case sp_fmt_style_quote: return SP_NULLPTR; + } + return SP_NULLPTR; } -static void sp_fmt_directive_bytes_render(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - u64 bytes = arg->value.u; sp_unused(params); - static const c8* units[] = { "B", "KB", "MB", "GB", "TB", "PB" }; - u32 unit_idx = 0; - u64 whole = bytes; - u64 rem = 0; - while (whole >= 1024 && unit_idx < 5) { - rem = whole & 1023; - whole >>= 10; - unit_idx++; +static void sp_fmt_style_open(sp_io_writer_t* io, sp_fmt_style_t style, sp_fmt_arg_t* arg) { + const c8* ansi = sp_fmt_style_ansi(style); + if (ansi) { + sp_io_write_cstr(io, ansi, SP_NULLPTR); } - sp_fmt_write_u64(io, whole); - if (unit_idx > 0) { - u32 tenths = (u32)((rem * 10) >> 10); - if (tenths > 0) { - sp_io_write_c8(io, '.'); - sp_io_write_c8(io, (c8)('0' + tenths)); - } + else if (style == sp_fmt_style_hyperlink) { + sp_io_write_cstr(io, "\033]8;;", SP_NULLPTR); + if (arg->id == sp_fmt_id_str) sp_io_write_str(io, arg->value.s, SP_NULLPTR); + sp_io_write_cstr(io, "\033\\", SP_NULLPTR); } - sp_io_write_c8(io, ' '); - sp_io_write_cstr(io, units[unit_idx], SP_NULLPTR); -} - -static void sp_fmt_directive_iso_render(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(params); - sp_tm_epoch_to_iso8601_w(io, (sp_tm_epoch_t) { .s = arg->value.u }); -} - -static void sp_fmt_directive_hex_render(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(params); - u64 value = (arg->id == sp_fmt_id_s64) ? (u64)arg->value.i : arg->value.u; - c8 buf[16]; - c8* end = buf + sizeof(buf); - c8* start = sp_fmt_uint_to_buf_hex_ex(value, end, "0123456789ABCDEF"); - sp_io_write_cstr(io, "0x", SP_NULLPTR); - sp_io_write_all(io, start, (u64)(end - start), SP_NULLPTR); -} - -static void sp_fmt_directive_ordinal_render(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - s64 value = (arg->id == sp_fmt_id_s64) ? arg->value.i : (s64)arg->value.u; sp_unused(params); - sp_fmt_write_s64(io, value); - s64 abs = value < 0 ? -value : value; - u32 mod100 = (u32)(abs % 100); - u32 mod10 = (u32)(abs % 10); - const c8* suffix = "th"; - if (mod100 < 11 || mod100 > 13) { - if (mod10 == 1) suffix = "st"; - else if (mod10 == 2) suffix = "nd"; - else if (mod10 == 3) suffix = "rd"; + else if (style == sp_fmt_style_quote) { + sp_io_write_c8(io, '"'); } - sp_io_write_cstr(io, suffix, SP_NULLPTR); } -static void sp_fmt_directive_duration_render(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* params) { - sp_unused(params); - u64 ns = arg->value.u; - if (ns < 1000) { - sp_fmt_write_u64(io, ns); - sp_io_write_cstr(io, " ns", SP_NULLPTR); - return; +static void sp_fmt_style_close(sp_io_writer_t* io, sp_fmt_style_t style) { + if (style == sp_fmt_style_quote) { + sp_io_write_c8(io, '"'); } - static const c8* units[] = { "us", "ms", "s" }; - u32 unit_idx = 0; - u64 whole = ns / 1000; - u64 rem = ns % 1000; - while (whole >= 1000 && unit_idx < 2) { - rem = whole % 1000; - whole /= 1000; - unit_idx++; + else if (style != sp_fmt_style_none && style != sp_fmt_style_unknown) { + sp_io_write_cstr(io, SP_ANSI_RESET, SP_NULLPTR); } - sp_fmt_write_u64(io, whole); - if (rem >= 100) { - sp_io_write_c8(io, '.'); - sp_io_write_c8(io, (c8)('0' + rem / 100)); - } - sp_io_write_c8(io, ' '); - sp_io_write_cstr(io, units[unit_idx], SP_NULLPTR); -} - -sp_str_t sp_fmt_color_to_ansi_fg(sp_str_t id) { - if (sp_str_equal_cstr(id, "black")) return sp_str_lit(SP_ANSI_FG_BLACK); - if (sp_str_equal_cstr(id, "red")) return sp_str_lit(SP_ANSI_FG_RED); - if (sp_str_equal_cstr(id, "green")) return sp_str_lit(SP_ANSI_FG_GREEN); - if (sp_str_equal_cstr(id, "yellow")) return sp_str_lit(SP_ANSI_FG_YELLOW); - if (sp_str_equal_cstr(id, "blue")) return sp_str_lit(SP_ANSI_FG_BLUE); - if (sp_str_equal_cstr(id, "magenta")) return sp_str_lit(SP_ANSI_FG_MAGENTA); - if (sp_str_equal_cstr(id, "cyan")) return sp_str_lit(SP_ANSI_FG_CYAN); - if (sp_str_equal_cstr(id, "white")) return sp_str_lit(SP_ANSI_FG_WHITE); - if (sp_str_equal_cstr(id, "brightblack")) return sp_str_lit(SP_ANSI_FG_BRIGHT_BLACK); - if (sp_str_equal_cstr(id, "brightred")) return sp_str_lit(SP_ANSI_FG_BRIGHT_RED); - if (sp_str_equal_cstr(id, "brightgreen")) return sp_str_lit(SP_ANSI_FG_BRIGHT_GREEN); - if (sp_str_equal_cstr(id, "brightyellow")) return sp_str_lit(SP_ANSI_FG_BRIGHT_YELLOW); - if (sp_str_equal_cstr(id, "brightblue")) return sp_str_lit(SP_ANSI_FG_BRIGHT_BLUE); - if (sp_str_equal_cstr(id, "brightmagenta")) return sp_str_lit(SP_ANSI_FG_BRIGHT_MAGENTA); - if (sp_str_equal_cstr(id, "brightcyan")) return sp_str_lit(SP_ANSI_FG_BRIGHT_CYAN); - if (sp_str_equal_cstr(id, "brightwhite")) return sp_str_lit(SP_ANSI_FG_BRIGHT_WHITE); - return sp_str_lit(SP_ANSI_RESET); -} - -static void sp_fmt_directive_fg(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - sp_unused(arg); - sp_str_t ansi = sp_fmt_color_to_ansi_fg(param->value.s); - sp_io_write_str(io, ansi, SP_NULLPTR); -} - -void sp_fmt_register_builtins() { - sp_fmt_register_decorator_p("fg", sp_fmt_directive_fg, sp_fmt_directive_ansi_reset, sp_fmt_id_str); - sp_fmt_register_decorator("red", sp_fmt_directive_red, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("green", sp_fmt_directive_green, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("yellow", sp_fmt_directive_yellow, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("blue", sp_fmt_directive_blue, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("cyan", sp_fmt_directive_cyan, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("magenta", sp_fmt_directive_magenta, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("white", sp_fmt_directive_white, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("black", sp_fmt_directive_black, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("br_red", sp_fmt_directive_bright_red, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("br_green", sp_fmt_directive_bright_green, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("br_yellow", sp_fmt_directive_bright_yellow, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("br_blue", sp_fmt_directive_bright_blue, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("br_cyan", sp_fmt_directive_bright_cyan, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("br_magenta", sp_fmt_directive_bright_magenta, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("br_white", sp_fmt_directive_bright_white, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("br_black", sp_fmt_directive_gray, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("gray", sp_fmt_directive_gray, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("bold", sp_fmt_directive_bold, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("italic", sp_fmt_directive_italic, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("hyperlink", sp_fmt_directive_hyperlink, sp_fmt_directive_ansi_reset); - sp_fmt_register_decorator("quote", sp_fmt_directive_quote, sp_fmt_directive_quote_after); - sp_fmt_register_renderer("bytes", sp_fmt_directive_bytes_render, sp_fmt_id_none); - sp_fmt_register_renderer("hex", sp_fmt_directive_hex_render, sp_fmt_id_none); - sp_fmt_register_renderer("iso", sp_fmt_directive_iso_render, sp_fmt_id_none); - sp_fmt_register_renderer("ordinal", sp_fmt_directive_ordinal_render, sp_fmt_id_none); - sp_fmt_register_renderer("duration", sp_fmt_directive_duration_render, sp_fmt_id_none); - } bool sp_parse_u64_ex(sp_str_t str, u64* out) { @@ -8001,7 +7832,6 @@ void sp_tls_rt_deinit(void* ptr) { sp_tls_rt_t* tls = (sp_tls_rt_t*)ptr; if (tls->std.out) sp_mem_allocator_free(tls->mem.heap, tls->std.out, sizeof(sp_io_stream_writer_t)); if (tls->std.err) sp_mem_allocator_free(tls->mem.heap, tls->std.err, sizeof(sp_io_stream_writer_t)); - sp_str_ht_free(tls->format.directives); sp_carr_for(tls->scratch, it) { sp_mem_arena_destroy(tls->scratch[it]); } @@ -8029,8 +7859,6 @@ sp_tls_rt_t* sp_tls_rt_get() { // std.out/std.err are wired lazily (see sp_tls_std_out/_err). Wiring them here // would take the address of sp_io_stream_writer_write in code that sp_main always // reaches, which on WASM forces a fd_write import even for programs that never write. - sp_str_ht_init(tls->mem.heap, tls->format.directives); - sp_fmt_register_builtins(); sp_sys_tls_init(tls); } return tls; @@ -14833,26 +14661,11 @@ const c8* sp_io_dyn_mem_writer_as_cstr(sp_io_dyn_mem_writer_t* w) { return sp_mem_buffer_as_cstr(&w->storage); } -sp_err_t sp_fmt_check_type(sp_fmt_arg_kind_t expected, sp_fmt_arg_kind_t actual, sp_err_t err) { - return (expected && !(expected & actual)) ? err : SP_OK; -} - -sp_err_t sp_fmt_check_arg(sp_fmt_arg_kind_t expected, sp_fmt_arg_kind_t actual) { - return sp_fmt_check_type(expected, actual, SP_ERR_FMT_DIRECTIVE_ARG_WRONG_KIND); -} - -sp_err_t sp_fmt_check_param(sp_fmt_arg_kind_t expected, sp_fmt_arg_kind_t actual) { - return sp_fmt_check_type(expected, actual, SP_ERR_FMT_WRONG_PARAM_KIND); -} - -bool sp_fmt_is_directive_dynamic(u8 dynamic, u8 index) { - return dynamic & (1u << index); -} - sp_fmt_arg_t sp_fmt_arg_from_argv(sp_fmt_argv_t v) { sp_fmt_arg_t arg = sp_zero; arg.id = v.id; arg.value = v.value; + arg.fn = v.fn; return arg; } @@ -14891,7 +14704,7 @@ sp_err_t sp_fmt_std_err(const c8* fmt, ...) { return result; } -sp_str_r sp_fmt_buf(c8* buffer, u64 len, const c8* fmt, ...) { +sp_str_r __sp_fmt_buf(c8* buffer, u64 len, const c8* fmt, ...) { va_list args; va_start(args, fmt); sp_str_r str = sp_fmt_buf_v(buffer, len, sp_cstr_as_str(fmt), args); @@ -14953,32 +14766,33 @@ sp_err_t sp_fmt_io_v(sp_io_writer_t* io, sp_str_t fmt, va_list args) { sp_fmt_spec_t spec = sp_zero; sp_try(sp_fmt_parse_specifier(&p, &spec)); - if (spec.dynamic.fill) { + if (spec.dynamic & SP_FMT_DYNAMIC_FILL) { s64 v; - sp_try(sp_fmt_pull_specifier_arg(va_arg(args, sp_fmt_argv_t), &v)); + sp_try_as(sp_fmt_pull_specifier_arg(va_arg(args, sp_fmt_argv_t), &v), SP_ERR_FMT_WRONG_FILL_KIND); spec.fill = sp_cast(u8, v); } - if (spec.dynamic.width) { + if (spec.dynamic & SP_FMT_DYNAMIC_WIDTH) { s64 v; - sp_try(sp_fmt_pull_specifier_arg(va_arg(args, sp_fmt_argv_t), &v)); + sp_try_as(sp_fmt_pull_specifier_arg(va_arg(args, sp_fmt_argv_t), &v), SP_ERR_FMT_WRONG_WIDTH_KIND); spec.width = sp_cast(u32, sp_clamp(v, 0, SP_FMT_WIDTH_MAX)); } - if (spec.dynamic.precision) { + if (spec.dynamic & SP_FMT_DYNAMIC_PRECISION) { s64 v; - sp_try(sp_fmt_pull_specifier_arg(va_arg(args, sp_fmt_argv_t), &v)); + sp_try_as(sp_fmt_pull_specifier_arg(va_arg(args, sp_fmt_argv_t), &v), SP_ERR_FMT_WRONG_PRECISION_KIND); sp_opt_set(spec.precision, sp_cast(u8, sp_clamp(v, 0, 255))); } - - sp_fmt_arg_t params [SP_FMT_MAX_DIRECTIVES] = sp_zero; - for (u8 it = 0; it < spec.directive.num; it++) { - if (sp_fmt_is_directive_dynamic(spec.dynamic.directive, it)) { - params[it] = sp_fmt_arg_from_argv(va_arg(args, sp_fmt_argv_t)); + sp_for(i, spec.directive.num) { + if (spec.dynamic & sp_fmt_dynamic_directive(i)) { + s64 v; + sp_try_as(sp_fmt_pull_specifier_arg(va_arg(args, sp_fmt_argv_t), &v), SP_ERR_FMT_WRONG_STYLE_KIND); + if (v <= sp_fmt_style_unknown || v > sp_fmt_style_quote) return SP_ERR_FMT_UNKNOWN_DIRECTIVE; + spec.directive.styles[i] = sp_cast(sp_fmt_style_t, v); } } sp_fmt_arg_t arg = sp_fmt_arg_from_argv(va_arg(args, sp_fmt_argv_t)); arg.spec = spec; - sp_try(sp_fmt_render(io, &arg, params)); + sp_try(sp_fmt_render(io, &arg)); continue; } @@ -15050,107 +14864,101 @@ void sp_print_err(const c8* fmt, ...) { va_end(args); } -bool sp_fmt_dir_has_params(sp_fmt_directive_t* dir) { - return dir->params != sp_fmt_id_none; -} - -sp_err_t sp_fmt_render(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* directive_params) { - sp_fmt_directive_t* directives [SP_FMT_MAX_DIRECTIVES] = sp_zero; - sp_fmt_arg_t* args [SP_FMT_MAX_DIRECTIVES] = sp_zero; - - u8 num_dirs = arg->spec.directive.num; - sp_fmt_fn_t render_fn = SP_NULLPTR; - sp_fmt_arg_t* render_param = SP_NULLPTR; - - sp_for(i, arg->spec.directive.num) { - sp_fmt_directive_t* d = sp_fmt_directive_lookup(arg->spec.directive.names[i]); - if (!d) return SP_ERR_FMT_UNKNOWN_DIRECTIVE; - - sp_try(sp_fmt_check_param(d->args, arg->id)); - - //sp_fmt_arg_t arg = sp_zero; - bool is_dynamic = (arg->spec.dynamic.directive & (1u << i)) != 0; - bool is_literal = !sp_str_empty(arg->spec.directive.args[i]); - if (is_literal) { - directive_params[i] = (sp_fmt_arg_t){ - .id = sp_fmt_id_str, - .value = { - .s = arg->spec.directive.args[i], - } - }; - } - if (is_dynamic || is_literal) args[i] = &directive_params[i]; - - if (!sp_fmt_dir_has_params(d)) { - if (args[i]) return SP_ERR_FMT_DIRECTIVE_ARG_UNEXPECTED; - } - else { - if (!args[i]) return SP_ERR_FMT_DIRECTIVE_ARG_MISSING; - if (!(d->params & args[i]->id)) return SP_ERR_FMT_DIRECTIVE_ARG_WRONG_KIND; - } - - if (d->kind == sp_fmt_directive_renderer) { - if (render_fn) return SP_ERR_FMT_TOO_MANY_RENDERERS; - render_fn = d->renderer; - render_param = args[i]; +sp_err_t sp_fmt_render_bytes(sp_io_writer_t* io, sp_fmt_arg_t* arg) { + u64 n = 0; + switch (arg->id) { + case sp_fmt_id_u64: n = arg->value.u; break; + case sp_fmt_id_s64: n = arg->value.i < 0 ? 0 : (u64)arg->value.i; break; + case sp_fmt_id_f64: { + f64 f = arg->value.f; + n = !(f >= 0) ? 0 : (f >= 1.8446744073709552e19 ? SP_LIMIT_U64_MAX : (u64)f); + break; } - - directives[i] = d; - } - - bool is_opaque = (render_fn != SP_NULLPTR); - if (arg->id == sp_fmt_id_custom) { - if (!arg->value.custom.fn) return SP_ERR_FMT_CUSTOM_WITHOUT_FN; - render_fn = arg->value.custom.fn; - render_param = SP_NULLPTR; - is_opaque = true; + case sp_fmt_id_str: n = arg->value.s.len; break; + case sp_fmt_id_ptr: + case sp_fmt_id_none: return SP_ERR_FMT_BAD_ARG; } + return sp_fmt_write_size(io, n); +} - if (is_opaque && arg->spec.width) return SP_ERR_FMT_WIDTH_ON_OPAQUE_RENDERER; +sp_err_t sp_fmt_render(sp_io_writer_t* io, sp_fmt_arg_t* arg) { + u8 num_dirs = arg->spec.directive.num; - if (is_opaque) { - sp_for(it, num_dirs) { - if (directives[it]->kind != sp_fmt_directive_decorator) continue; - sp_fmt_fn_t before = directives[it]->decorator.before; - if (before) before(io, arg, args[it]); - } - render_fn(io, arg, render_param); - u8 j = num_dirs; - while (j--) { - if (directives[j]->kind != sp_fmt_directive_decorator) continue; - sp_fmt_fn_t after = directives[j]->decorator.after; - if (after) after(io, arg, args[j]); - } - return SP_OK; + sp_for(i, num_dirs) { + sp_fmt_style_t style = (arg->spec.dynamic & sp_fmt_dynamic_directive(i)) + ? arg->spec.directive.styles[i] + : sp_fmt_style_from_name(arg->spec.directive.names[i]); + if (style <= sp_fmt_style_unknown || style > sp_fmt_style_quote) return SP_ERR_FMT_UNKNOWN_DIRECTIVE; + arg->spec.directive.styles[i] = style; } sp_str_t content = sp_zero; c8 buf[64]; sp_io_mem_writer_t cw = sp_zero; - switch (arg->id) { - case sp_fmt_id_u64: - case sp_fmt_id_s64: - case sp_fmt_id_f64: - case sp_fmt_id_ptr: { - sp_io_mem_writer_from_buffer(&cw, buf, sizeof(buf)); - sp_fmt_render_default(&cw.base, arg, SP_NULLPTR); - content = sp_io_mem_writer_as_str(&cw); - break; - } - case sp_fmt_id_str: { - content = arg->value.s; - if (!sp_opt_is_null(arg->spec.precision)) { - u32 max = sp_opt_get(arg->spec.precision); - if (max < content.len) content.len = max; + sp_io_dyn_mem_writer_t dw = sp_zero; + + if (arg->fn) { + sp_io_dyn_mem_writer_init(sp_mem_get_scratch(), &dw); + sp_try(arg->fn(&dw.base, arg)); + content = sp_io_dyn_mem_writer_as_str(&dw); + } + else if (arg->spec.renderer == 'B') { + sp_io_dyn_mem_writer_init(sp_mem_get_scratch(), &dw); + sp_try(sp_fmt_render_bytes(&dw.base, arg)); + content = sp_io_dyn_mem_writer_as_str(&dw); + } + else if (arg->spec.renderer == 'c') { + if (arg->id != sp_fmt_id_u64 && arg->id != sp_fmt_id_s64) return SP_ERR_FMT_BAD_ARG; + sp_io_mem_writer_from_buffer(&cw, buf, sizeof(buf)); + sp_io_write_c8(&cw.base, sp_cast(c8, arg->value.u)); + content = sp_io_mem_writer_as_str(&cw); + } + else { + sp_fmt_radix_t radix = SP_FMT_RADIX_DECIMAL; + switch (arg->spec.renderer) { + case 'b': radix = SP_FMT_RADIX_BINARY; break; + case 'o': radix = SP_FMT_RADIX_OCTAL; break; + case 'x': radix = SP_FMT_RADIX_HEX; break; + case 'X': radix = SP_FMT_RADIX_HEX_UPPER; break; + } + if (radix != SP_FMT_RADIX_DECIMAL && arg->id != sp_fmt_id_u64 && arg->id != sp_fmt_id_s64) { + return SP_ERR_FMT_BAD_ARG; + } + + sp_io_mem_writer_from_buffer(&cw, buf, sizeof(buf)); + switch (arg->id) { + case sp_fmt_id_u64: + sp_fmt_write_u64_ex(&cw.base, arg->value.u, radix); + content = sp_io_mem_writer_as_str(&cw); + break; + case sp_fmt_id_s64: + if (radix == SP_FMT_RADIX_DECIMAL) sp_fmt_write_s64(&cw.base, arg->value.i); + else sp_fmt_write_u64_ex(&cw.base, (u64)arg->value.i, radix); + content = sp_io_mem_writer_as_str(&cw); + break; + case sp_fmt_id_f64: { + u32 p = sp_opt_is_null(arg->spec.precision) ? 6 : sp_opt_get(arg->spec.precision); + sp_fmt_write_f64_ex(&cw.base, arg->value.f, p); + content = sp_io_mem_writer_as_str(&cw); + break; } - break; + case sp_fmt_id_ptr: + sp_fmt_write_ptr(&cw.base, arg->value.p); + content = sp_io_mem_writer_as_str(&cw); + break; + case sp_fmt_id_str: + content = arg->value.s; + if (!sp_opt_is_null(arg->spec.precision)) { + u32 max = sp_opt_get(arg->spec.precision); + if (max < content.len) content.len = max; + } + break; + case sp_fmt_id_none: + break; } - case sp_fmt_id_custom: - case sp_fmt_id_none: break; } - u32 width = arg->spec.width > SP_FMT_WIDTH_MAX ? SP_FMT_WIDTH_MAX : arg->spec.width; - u32 pad = (width > content.len) ? (u32)(width - content.len) : 0; + u32 pad = (arg->spec.width > content.len) ? (u32)(arg->spec.width - content.len) : 0; u8 fill = arg->spec.fill ? arg->spec.fill : ' '; sp_fmt_align_t align = arg->spec.align; if (align == SP_FMT_ALIGN_NONE) align = SP_FMT_ALIGN_RIGHT; @@ -15165,22 +14973,10 @@ sp_err_t sp_fmt_render(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* dire } sp_for(it, left_pad) sp_io_write_c8(io, fill); - - sp_for(it, num_dirs) { - if (directives[it]->kind != sp_fmt_directive_decorator) continue; - sp_fmt_fn_t before = directives[it]->decorator.before; - if (before) before(io, arg, args[it]); - } - + sp_for(it, num_dirs) sp_fmt_style_open(io, arg->spec.directive.styles[it], arg); sp_io_write_str(io, content, SP_NULLPTR); - u8 j = num_dirs; - while (j--) { - if (directives[j]->kind != sp_fmt_directive_decorator) continue; - sp_fmt_fn_t after = directives[j]->decorator.after; - if (after) after(io, arg, args[j]); - } - + while (j--) sp_fmt_style_close(io, arg->spec.directive.styles[j]); sp_for(it, right_pad) sp_io_write_c8(io, fill); return SP_OK; } @@ -15233,32 +15029,290 @@ c8* sp_fmt_uint_to_buf_hex(u64 value, c8* buf_end) { return sp_fmt_uint_to_buf_hex_ex(value, buf_end, "0123456789abcdef"); } -void sp_fmt_write_u64(sp_io_writer_t* io, u64 value) { - c8 buf[20]; +c8* sp_fmt_uint_to_buf_bin(u64 value, c8* buf_end) { + c8* p = buf_end; + if (value == 0) { + *--p = '0'; + return p; + } + while (value) { + *--p = (c8)('0' + (value & 1)); + value >>= 1; + } + return p; +} + +c8* sp_fmt_uint_to_buf_oct(u64 value, c8* buf_end) { + c8* p = buf_end; + if (value == 0) { + *--p = '0'; + return p; + } + while (value) { + *--p = (c8)('0' + (value & 7)); + value >>= 3; + } + return p; +} + +sp_err_t sp_fmt_write_u64(sp_io_writer_t* io, u64 value) { + return sp_fmt_write_u64_ex(io, value, SP_FMT_RADIX_DECIMAL); +} + +sp_err_t sp_fmt_write_u64_ex(sp_io_writer_t* io, u64 value, sp_fmt_radix_t radix) { + c8 buf[64]; c8* end = buf + sizeof(buf); - c8* start = sp_fmt_uint_to_buf_dec(value, end); - sp_io_write_all(io, start, (u64)(end - start), SP_NULLPTR); + c8* start = end; + switch (radix) { + case SP_FMT_RADIX_DECIMAL: start = sp_fmt_uint_to_buf_dec(value, end); break; + case SP_FMT_RADIX_BINARY: start = sp_fmt_uint_to_buf_bin(value, end); break; + case SP_FMT_RADIX_OCTAL: start = sp_fmt_uint_to_buf_oct(value, end); break; + case SP_FMT_RADIX_HEX: start = sp_fmt_uint_to_buf_hex(value, end); break; + case SP_FMT_RADIX_HEX_UPPER: start = sp_fmt_uint_to_buf_hex_ex(value, end, "0123456789ABCDEF"); break; + } + return sp_io_write_all(io, start, (u64)(end - start), SP_NULLPTR); +} + +sp_str_r sp_fmt_write_u64_mem(sp_mem_t mem, u64 value, sp_fmt_radix_t radix) { + sp_str_r str = sp_zero; + sp_io_dyn_mem_writer_t io = sp_zero; + sp_io_dyn_mem_writer_init(mem, &io); + str.err = sp_fmt_write_u64_ex(&io.base, value, radix); + if (!str.err) str.value = sp_io_dyn_mem_writer_as_str(&io); + return str; +} + +sp_str_r sp_fmt_write_u64_buf(c8* buffer, u64 len, u64 value, sp_fmt_radix_t radix) { + sp_str_r str = sp_zero; + sp_io_mem_writer_t io = sp_zero; + sp_io_mem_writer_from_buffer(&io, buffer, len); + str.err = sp_fmt_write_u64_ex(&io.base, value, radix); + if (!str.err) str.value = sp_io_mem_writer_as_str(&io); + return str; } -void sp_fmt_write_s64(sp_io_writer_t* io, s64 value) { +sp_err_t sp_fmt_write_s64(sp_io_writer_t* io, s64 value) { c8 buf[21]; c8* end = buf + sizeof(buf); - u64 abs = (value < 0) ? ((u64)(-(value + 1)) + 1) : (u64)value; + u64 abs = sp_uabs(value); c8* start = sp_fmt_uint_to_buf_dec(abs, end); if (value < 0) *--start = '-'; - sp_io_write_all(io, start, (u64)(end - start), SP_NULLPTR); + return sp_io_write_all(io, start, (u64)(end - start), SP_NULLPTR); } -void sp_fmt_write_ptr(sp_io_writer_t* io, void* value) { +sp_str_r sp_fmt_write_s64_mem(sp_mem_t mem, s64 value) { + sp_str_r str = sp_zero; + sp_io_dyn_mem_writer_t io = sp_zero; + sp_io_dyn_mem_writer_init(mem, &io); + str.err = sp_fmt_write_s64(&io.base, value); + if (!str.err) str.value = sp_io_dyn_mem_writer_as_str(&io); + return str; +} + +sp_str_r sp_fmt_write_s64_buf(c8* buffer, u64 len, s64 value) { + sp_str_r str = sp_zero; + sp_io_mem_writer_t io = sp_zero; + sp_io_mem_writer_from_buffer(&io, buffer, len); + str.err = sp_fmt_write_s64(&io.base, value); + if (!str.err) str.value = sp_io_mem_writer_as_str(&io); + return str; +} + +sp_err_t sp_fmt_write_ptr(sp_io_writer_t* io, void* value) { c8 buf[18]; c8* end = buf + sizeof(buf); c8* start = sp_fmt_uint_to_buf_hex((u64)(uintptr_t)value, end); *--start = 'x'; *--start = '0'; + return sp_io_write_all(io, start, (u64)(end - start), SP_NULLPTR); +} + +sp_str_r sp_fmt_write_ptr_mem(sp_mem_t mem, void* value) { + sp_str_r str = sp_zero; + sp_io_dyn_mem_writer_t io = sp_zero; + sp_io_dyn_mem_writer_init(mem, &io); + str.err = sp_fmt_write_ptr(&io.base, value); + if (!str.err) str.value = sp_io_dyn_mem_writer_as_str(&io); + return str; +} + +sp_str_r sp_fmt_write_ptr_buf(c8* buffer, u64 len, void* value) { + sp_str_r str = sp_zero; + sp_io_mem_writer_t io = sp_zero; + sp_io_mem_writer_from_buffer(&io, buffer, len); + str.err = sp_fmt_write_ptr(&io.base, value); + if (!str.err) str.value = sp_io_mem_writer_as_str(&io); + return str; +} + +void sp_fmt_write_hex(sp_io_writer_t* io, u64 value) { + c8 buf[16]; + c8* end = buf + sizeof(buf); + c8* start = sp_fmt_uint_to_buf_hex(value, end); + sp_io_write_all(io, start, (u64)(end - start), SP_NULLPTR); +} + +void sp_fmt_write_hex_upper(sp_io_writer_t* io, u64 value) { + c8 buf[16]; + c8* end = buf + sizeof(buf); + c8* start = sp_fmt_uint_to_buf_hex_ex(value, end, "0123456789ABCDEF"); + sp_io_write_all(io, start, (u64)(end - start), SP_NULLPTR); +} + +void sp_fmt_write_bin(sp_io_writer_t* io, u64 value) { + c8 buf[64]; + c8* end = buf + sizeof(buf); + c8* start = sp_fmt_uint_to_buf_bin(value, end); + sp_io_write_all(io, start, (u64)(end - start), SP_NULLPTR); +} + +void sp_fmt_write_oct(sp_io_writer_t* io, u64 value) { + c8 buf[22]; + c8* end = buf + sizeof(buf); + c8* start = sp_fmt_uint_to_buf_oct(value, end); sp_io_write_all(io, start, (u64)(end - start), SP_NULLPTR); } -void sp_fmt_write_f64(sp_io_writer_t* io, f64 value, u32 precision) { +sp_err_t sp_fmt_write_bool(sp_io_writer_t* io, bool value) { + return sp_io_write_cstr(io, value ? "true" : "false", SP_NULLPTR); +} + +sp_str_r sp_fmt_write_bool_mem(sp_mem_t mem, bool value) { + sp_str_r str = sp_zero; + sp_io_dyn_mem_writer_t io = sp_zero; + sp_io_dyn_mem_writer_init(mem, &io); + str.err = sp_fmt_write_bool(&io.base, value); + if (!str.err) str.value = sp_io_dyn_mem_writer_as_str(&io); + return str; +} + +sp_str_r sp_fmt_write_bool_buf(c8* buffer, u64 len, bool value) { + sp_str_r str = sp_zero; + sp_io_mem_writer_t io = sp_zero; + sp_io_mem_writer_from_buffer(&io, buffer, len); + str.err = sp_fmt_write_bool(&io.base, value); + if (!str.err) str.value = sp_io_mem_writer_as_str(&io); + return str; +} + +sp_err_t sp_fmt_write_size(sp_io_writer_t* io, u64 bytes) { + static const c8* units[] = { "B", "KB", "MB", "GB", "TB", "PB" }; + u32 unit_idx = 0; + u64 whole = bytes; + u64 rem = 0; + while (whole >= 1024 && unit_idx < 5) { + rem = whole & 1023; + whole >>= 10; + unit_idx++; + } + sp_try(sp_fmt_write_u64(io, whole)); + if (unit_idx > 0) { + u32 tenths = (u32)((rem * 10) >> 10); + if (tenths > 0) { + sp_try(sp_io_write_c8(io, '.')); + sp_try(sp_io_write_c8(io, (c8)('0' + tenths))); + } + } + sp_try(sp_io_write_c8(io, ' ')); + return sp_io_write_cstr(io, units[unit_idx], SP_NULLPTR); +} + +sp_str_r sp_fmt_write_size_mem(sp_mem_t mem, u64 bytes) { + sp_str_r str = sp_zero; + sp_io_dyn_mem_writer_t io = sp_zero; + sp_io_dyn_mem_writer_init(mem, &io); + str.err = sp_fmt_write_size(&io.base, bytes); + if (!str.err) str.value = sp_io_dyn_mem_writer_as_str(&io); + return str; +} + +sp_str_r sp_fmt_write_size_buf(c8* buffer, u64 len, u64 bytes) { + sp_str_r str = sp_zero; + sp_io_mem_writer_t io = sp_zero; + sp_io_mem_writer_from_buffer(&io, buffer, len); + str.err = sp_fmt_write_size(&io.base, bytes); + if (!str.err) str.value = sp_io_mem_writer_as_str(&io); + return str; +} + +sp_err_t sp_fmt_write_duration(sp_io_writer_t* io, u64 ns) { + if (ns < 1000) { + sp_try(sp_fmt_write_u64(io, ns)); + return sp_io_write_cstr(io, " ns", SP_NULLPTR); + } + static const c8* units[] = { "us", "ms", "s" }; + u32 unit_idx = 0; + u64 whole = ns / 1000; + u64 rem = ns % 1000; + while (whole >= 1000 && unit_idx < 2) { + rem = whole % 1000; + whole /= 1000; + unit_idx++; + } + sp_try(sp_fmt_write_u64(io, whole)); + if (rem >= 100) { + sp_try(sp_io_write_c8(io, '.')); + sp_try(sp_io_write_c8(io, (c8)('0' + rem / 100))); + } + sp_try(sp_io_write_c8(io, ' ')); + return sp_io_write_cstr(io, units[unit_idx], SP_NULLPTR); +} + +sp_str_r sp_fmt_write_duration_mem(sp_mem_t mem, u64 ns) { + sp_str_r str = sp_zero; + sp_io_dyn_mem_writer_t io = sp_zero; + sp_io_dyn_mem_writer_init(mem, &io); + str.err = sp_fmt_write_duration(&io.base, ns); + if (!str.err) str.value = sp_io_dyn_mem_writer_as_str(&io); + return str; +} + +sp_str_r sp_fmt_write_duration_buf(c8* buffer, u64 len, u64 ns) { + sp_str_r str = sp_zero; + sp_io_mem_writer_t io = sp_zero; + sp_io_mem_writer_from_buffer(&io, buffer, len); + str.err = sp_fmt_write_duration(&io.base, ns); + if (!str.err) str.value = sp_io_mem_writer_as_str(&io); + return str; +} + +sp_err_t sp_fmt_write_ordinal(sp_io_writer_t* io, s64 value) { + sp_try(sp_fmt_write_s64(io, value)); + u64 abs = sp_uabs(value); + u32 mod100 = (u32)(abs % 100); + u32 mod10 = (u32)(abs % 10); + const c8* suffix = "th"; + if (mod100 < 11 || mod100 > 13) { + if (mod10 == 1) suffix = "st"; + else if (mod10 == 2) suffix = "nd"; + else if (mod10 == 3) suffix = "rd"; + } + return sp_io_write_cstr(io, suffix, SP_NULLPTR); +} + +sp_str_r sp_fmt_write_ordinal_mem(sp_mem_t mem, s64 value) { + sp_str_r str = sp_zero; + sp_io_dyn_mem_writer_t io = sp_zero; + sp_io_dyn_mem_writer_init(mem, &io); + str.err = sp_fmt_write_ordinal(&io.base, value); + if (!str.err) str.value = sp_io_dyn_mem_writer_as_str(&io); + return str; +} + +sp_str_r sp_fmt_write_ordinal_buf(c8* buffer, u64 len, s64 value) { + sp_str_r str = sp_zero; + sp_io_mem_writer_t io = sp_zero; + sp_io_mem_writer_from_buffer(&io, buffer, len); + str.err = sp_fmt_write_ordinal(&io.base, value); + if (!str.err) str.value = sp_io_mem_writer_as_str(&io); + return str; +} + +sp_err_t sp_fmt_write_f64(sp_io_writer_t* io, f64 value) { + return sp_fmt_write_f64_ex(io, value, 6); +} + +sp_err_t sp_fmt_write_f64_ex(sp_io_writer_t* io, f64 value, u32 precision) { union { f64 f; u64 u; } bits; bits.f = value; u64 exponent = (bits.u >> 52) & 0x7ffULL; @@ -15267,15 +15321,13 @@ void sp_fmt_write_f64(sp_io_writer_t* io, f64 value, u32 precision) { if (exponent == 0x7ff) { if (mantissa == 0) { - sp_io_write_cstr(io, is_neg ? "-inf" : "inf", SP_NULLPTR); - } else { - sp_io_write_cstr(io, "nan", SP_NULLPTR); + return sp_io_write_cstr(io, is_neg ? "-inf" : "inf", SP_NULLPTR); } - return; + return sp_io_write_cstr(io, "nan", SP_NULLPTR); } if (is_neg) { - sp_io_write_c8(io, '-'); + sp_try(sp_io_write_c8(io, '-')); value = -value; } @@ -15291,8 +15343,7 @@ void sp_fmt_write_f64(sp_io_writer_t* io, f64 value, u32 precision) { u64 scale = pow10[precision]; if (value >= 1.8446744073709552e19) { - sp_io_write_cstr(io, "inf", SP_NULLPTR); - return; + return sp_io_write_cstr(io, "inf", SP_NULLPTR); } u64 int_part = (u64)value; @@ -15303,58 +15354,40 @@ void sp_fmt_write_f64(sp_io_writer_t* io, f64 value, u32 precision) { frac_scaled -= scale; } - sp_fmt_write_u64(io, int_part); + sp_try(sp_fmt_write_u64(io, int_part)); if (precision > 0) { - sp_io_write_c8(io, '.'); + sp_try(sp_io_write_c8(io, '.')); c8 frac_buf[18]; u32 i = precision; while (i--) { frac_buf[i] = (c8)('0' + (frac_scaled % 10)); frac_scaled /= 10; } - sp_io_write_all(io, frac_buf, precision, SP_NULLPTR); + return sp_io_write_all(io, frac_buf, precision, SP_NULLPTR); } + + return SP_OK; } -void sp_fmt_render_default(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - sp_unused(param); - switch (arg->id) { - case sp_fmt_id_u64: - sp_fmt_write_u64(io, arg->value.u); - break; - case sp_fmt_id_s64: - sp_fmt_write_s64(io, arg->value.i); - break; - case sp_fmt_id_f64: { - u32 p = sp_opt_is_null(arg->spec.precision) ? 6 : sp_opt_get(arg->spec.precision); - sp_fmt_write_f64(io, arg->value.f, p); - break; - } - case sp_fmt_id_str: { - sp_str_t s = arg->value.s; - if (!sp_opt_is_null(arg->spec.precision)) { - u32 max = sp_opt_get(arg->spec.precision); - if (max < s.len) s.len = max; - } - sp_io_write_str(io, s, SP_NULLPTR); - break; - } - case sp_fmt_id_ptr: { - sp_fmt_write_ptr(io, arg->value.p); - break; - } - case sp_fmt_id_custom: { - if (arg->value.custom.fn) { - arg->value.custom.fn(io, arg, SP_NULLPTR); - } - break; - } - case sp_fmt_id_none: { - break; - } - } +sp_str_r sp_fmt_write_f64_mem(sp_mem_t mem, f64 value, u32 precision) { + sp_str_r str = sp_zero; + sp_io_dyn_mem_writer_t io = sp_zero; + sp_io_dyn_mem_writer_init(mem, &io); + str.err = sp_fmt_write_f64_ex(&io.base, value, precision); + if (!str.err) str.value = sp_io_dyn_mem_writer_as_str(&io); + return str; +} + +sp_str_r sp_fmt_write_f64_buf(c8* buffer, u64 len, f64 value, u32 precision) { + sp_str_r str = sp_zero; + sp_io_mem_writer_t io = sp_zero; + sp_io_mem_writer_from_buffer(&io, buffer, len); + str.err = sp_fmt_write_f64_ex(&io.base, value, precision); + if (!str.err) str.value = sp_io_mem_writer_as_str(&io); + return str; } + sp_str_t sp_fs_get_name(sp_str_t path) { // The only valid separator is '/'. If you're calling this function on a path // that might contain a '\', normalize it first. Garbage in, garbage out. @@ -15581,7 +15614,7 @@ sp_str_t sp_fs_resolve(sp_mem_t mem, sp_sys_fd_t fd) { #elif defined(SP_LINUX) c8 self [64] = sp_zero; - sp_fmt_buf(self, 64, "/proc/self/fd/{}", sp_fmt_int(fd)); + __sp_fmt_buf(self, 64, "/proc/self/fd/{}", sp_fmt_int(fd)); c8 buf [SP_PATH_MAX + 1] = sp_zero; s64 n = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, self, buf, SP_PATH_MAX); diff --git a/test/bench/ubench.h b/test/bench/ubench.h index 41b4b29..fea6076 100644 --- a/test/bench/ubench.h +++ b/test/bench/ubench.h @@ -1382,9 +1382,6 @@ s32 ubench_main(s32 argc, const c8 *const argv[]) { const c8 *filter = SP_NULLPTR; u64 ran_benchmarks = 0; - sp_fmt_register_decorator("green", ubench_fmt_tty_green, ubench_fmt_tty_reset); - sp_fmt_register_decorator("red", ubench_fmt_tty_red, ubench_fmt_tty_reset); - #if defined(UBENCH_ENABLE_PERF_COUNTERS) && defined(SP_LINUX) struct ubench_perf_s perf; ubench_perf_init(&perf); diff --git a/test/format.c b/test/format.c index a14e769..923ca87 100644 --- a/test/format.c +++ b/test/format.c @@ -1,1592 +1,10 @@ -#define SP_PRIVATE_HEADER // We need a few internal parser structs and functions -#include "sp.h" - -#include "test.h" - -#include "utest.h" - +#define SP_PRIVATE_HEADER // We need a few internal parser things +#include "format/format.h" SP_TEST_MAIN() -UTEST_EMPTY_FIXTURE(format) -UTEST_EMPTY_FIXTURE(format_specifier) - -static sp_ht(sp_str_t, sp_fmt_directive_t) sp_fmt_directives = SP_NULLPTR; - -void sp_fmt_directive_reset() { - sp_tls_rt_t* tls = sp_tls_rt_get(); - sp_str_ht_free(tls->format.directives); - sp_str_ht_init(tls->mem.heap, tls->format.directives); - sp_fmt_register_builtins(); -} - -typedef struct { - const c8* str; - sp_fmt_spec_t expected; - sp_err_t err; -} format_test_t; - -static sp_err_t parse_spec(const c8* str, sp_fmt_spec_t* spec) { - sp_fmt_parser_t parser = { .str = sp_str_view(str), .i = 0 }; - return sp_fmt_parse_specifier(&parser, spec); -} - -void run_case(s32* utest_result, format_test_t test) { - sp_fmt_spec_t specifier = sp_zero; - sp_err_t err = parse_spec(test.str, &specifier); - EXPECT_EQ(err, test.err); - if (err != SP_OK) return; - EXPECT_EQ(specifier.align, test.expected.align); - EXPECT_EQ(specifier.fill, test.expected.fill); - EXPECT_EQ(specifier.precision.some, test.expected.precision.some); - EXPECT_EQ(specifier.precision.value, test.expected.precision.value); - EXPECT_EQ(specifier.width, test.expected.width); - EXPECT_EQ(specifier.directive.num, test.expected.directive.num); - EXPECT_TRUE(specifier.dynamic.fill == test.expected.dynamic.fill); - EXPECT_TRUE(specifier.dynamic.width == test.expected.dynamic.width); - EXPECT_TRUE(specifier.dynamic.precision == test.expected.dynamic.precision); -} - -UTEST_F(format_specifier, smoke) { - run_case(utest_result, (format_test_t) { - .str = "{:*^9}", - .expected = { - .width = 9, - .align = SP_FMT_ALIGN_CENTER, - .fill = '*', - }, - }); -} - -UTEST_F(format_specifier, empty) { - run_case(utest_result, (format_test_t) { - .str = "{}", - .expected = { .align = SP_FMT_ALIGN_NONE }, - }); -} - -UTEST_F(format_specifier, empty_with_colon) { - run_case(utest_result, (format_test_t) { - .str = "{:}", - .expected = { .align = SP_FMT_ALIGN_NONE }, - }); -} - -UTEST_F(format_specifier, width_only) { - run_case(utest_result, (format_test_t) { - .str = "{:42}", - .expected = { .width = 42 }, - }); -} - -UTEST_F(format_specifier, precision_only) { - run_case(utest_result, (format_test_t) { - .str = "{:.5}", - .expected = { .precision = sp_opt_some(5) }, - }); -} - -UTEST_F(format_specifier, width_and_precision) { - run_case(utest_result, (format_test_t) { - .str = "{:10.3}", - .expected = { .width = 10, .precision = sp_opt_some(3) }, - }); -} - -UTEST_F(format_specifier, bare_align_left) { - run_case(utest_result, (format_test_t) { - .str = "{:<7}", - .expected = { .width = 7, .align = SP_FMT_ALIGN_LEFT }, - }); -} - -UTEST_F(format_specifier, bare_align_right) { - run_case(utest_result, (format_test_t) { - .str = "{:>4}", - .expected = { .width = 4, .align = SP_FMT_ALIGN_RIGHT }, - }); -} - -UTEST_F(format_specifier, fill_with_left_align) { - run_case(utest_result, (format_test_t) { - .str = "{:-<8}", - .expected = { .width = 8, .align = SP_FMT_ALIGN_LEFT, .fill = '-' }, - }); -} - -UTEST_F(format_specifier, everything) { - run_case(utest_result, (format_test_t) { - .str = "{:*^12.4}", - .expected = { - .width = 12, - .align = SP_FMT_ALIGN_CENTER, - .fill = '*', - .precision = sp_opt_some(4), - }, - }); -} - -UTEST_F(format_specifier, zero_leading_width) { - run_case(utest_result, (format_test_t) { - .str = "{:05}", - .expected = { .width = 5 }, - }); -} - -UTEST_F(format_specifier, dynamic_width) { - run_case(utest_result, (format_test_t) { - .str = "{:$}", - .expected = { .dynamic = { .width = 1 } }, - }); -} - -UTEST_F(format_specifier, dynamic_precision) { - run_case(utest_result, (format_test_t) { - .str = "{:.$}", - .expected = { .dynamic = { .precision = 1 } }, - }); -} - -UTEST_F(format_specifier, dynamic_fill_center) { - run_case(utest_result, (format_test_t) { - .str = "{:$^9}", - .expected = { - .width = 9, - .align = SP_FMT_ALIGN_CENTER, - .dynamic = { .fill = 1 }, - }, - }); -} - -UTEST_F(format_specifier, dynamic_fill_and_width) { - run_case(utest_result, (format_test_t) { - .str = "{:$^$}", - .expected = { - .align = SP_FMT_ALIGN_CENTER, - .dynamic = { .fill = 1, .width = 1 }, - }, - }); -} - -UTEST_F(format_specifier, dynamic_everything) { - run_case(utest_result, (format_test_t) { - .str = "{:$<$.$}", - .expected = { - .align = SP_FMT_ALIGN_LEFT, - .dynamic = { .fill = 1, .width = 1, .precision = 1 }, - }, - }); -} - -UTEST_F(format_specifier, dynamic_width_with_literal_precision) { - run_case(utest_result, (format_test_t) { - .str = "{:$.4}", - .expected = { - .precision = sp_opt_some(4), - .dynamic = { .width = 1 }, - }, - }); -} - -UTEST_F(format_specifier, err_dynamic_precision_without_dot) { - run_case(utest_result, (format_test_t) { - .str = "{:5$}", - .err = SP_ERR_FMT_BAD_PLACEHOLDER - }); -} - -UTEST_F(format, err_missing_open_brace) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec(":5}", &spec), SP_ERR_FMT_BAD_PLACEHOLDER); -} - -UTEST_F(format, err_missing_close_brace) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{:5", &spec), SP_ERR_FMT_UNTERMINATED_PLACEHOLDER); -} - -UTEST_F(format, err_dot_without_digits) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{:5.}", &spec), SP_ERR_FMT_BAD_PRECISION); -} - -UTEST_F(format, single) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.red}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 1); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[0], "red")); -} - -UTEST_F(format, after_width) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{:10 .red}", &spec), SP_OK); - EXPECT_EQ(spec.width, 10); - EXPECT_EQ(spec.directive.num, 1); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[0], "red")); -} - -UTEST_F(format, full_spec) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{:*^9.2 .red}", &spec), SP_OK); - EXPECT_EQ(spec.width, 9); - EXPECT_EQ(spec.precision.value, 2); - EXPECT_EQ(spec.precision.some, SP_OPT_SOME); - EXPECT_EQ(spec.align, SP_FMT_ALIGN_CENTER); - EXPECT_EQ(spec.fill, '*'); - EXPECT_EQ(spec.directive.num, 1); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[0], "red")); -} - -UTEST_F(format, multiple) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.red .bold .italic}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 3); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[0], "red")); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[1], "bold")); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[2], "italic")); -} - -UTEST_F(format, max_count) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.a .b .c .d .e .f .g .h}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, SP_FMT_MAX_DIRECTIVES); -} - -UTEST_F(format, err_too_many) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.a .b .c .d .e .f .g .h .i}", &spec), SP_ERR_FMT_TOO_MANY_DIRECTIVES); -} - -UTEST_F(format, err_empty_name) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{:10 .}", &spec), SP_ERR_FMT_BAD_DIRECTIVE); -} - -UTEST_F(format, err_bad_char) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{:10 .red!}", &spec), SP_ERR_FMT_BAD_PLACEHOLDER); -} - -UTEST_F(format, err_no_space_between_directives) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.red.bold}", &spec), SP_ERR_FMT_BAD_PLACEHOLDER); -} - -UTEST_F(format, err_no_dot) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{:10 red}", &spec), SP_ERR_FMT_BAD_PLACEHOLDER); -} - -UTEST_F(format, digit_in_tail) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.base64}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 1); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[0], "base64")); -} - -UTEST_F(format, hyphen_in_name) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.utf-8}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 1); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[0], "utf-8")); -} - -UTEST_F(format, underscore_in_name) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.http_url}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 1); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[0], "http_url")); -} - -UTEST_F(format, err_leading_digit) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{: .4red}", &spec), SP_ERR_FMT_BAD_DIRECTIVE); -} - -UTEST_F(format, err_leading_hyphen) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.-red}", &spec), SP_ERR_FMT_BAD_DIRECTIVE); -} - -UTEST_F(format, no_width_just_directive) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{: .red}", &spec), SP_OK); - EXPECT_EQ(spec.width, 0); - EXPECT_EQ(spec.directive.num, 1); -} - -static sp_str_t render_value_to_str(sp_fmt_argv_t argv) { - sp_fmt_arg_t arg = sp_fmt_arg_from_argv(argv); - sp_io_dyn_mem_writer_t io = sp_zero; - sp_io_dyn_mem_writer_init(sp_mem_get_scratch(), &io); - sp_fmt_render_default(&io.base, &arg, SP_NULLPTR); - return sp_io_dyn_mem_writer_as_str(&io); -} - -UTEST_F(format, u64_zero) { - sp_str_t got = render_value_to_str(sp_fmt_uint(0)); - EXPECT_TRUE(sp_str_equal_cstr(got, "0")); -} - -UTEST_F(format, u64_large) { - sp_str_t got = render_value_to_str(sp_fmt_uint(1234567)); - EXPECT_TRUE(sp_str_equal_cstr(got, "1234567")); -} - -UTEST_F(format, s64_negative) { - sp_str_t got = render_value_to_str(sp_fmt_int(-42)); - EXPECT_TRUE(sp_str_equal_cstr(got, "-42")); -} - -UTEST_F(format, s64_positive) { - sp_str_t got = render_value_to_str(sp_fmt_int(42)); - EXPECT_TRUE(sp_str_equal_cstr(got, "42")); -} - -UTEST_F(format, str) { - sp_str_t got = render_value_to_str(sp_fmt_cstr("hello")); - EXPECT_TRUE(sp_str_equal_cstr(got, "hello")); -} - -UTEST_F(format, empty_str) { - sp_str_t got = render_value_to_str(sp_fmt_cstr("")); - EXPECT_TRUE(sp_str_equal_cstr(got, "")); -} - -UTEST_F(format, u64_ten) { - sp_str_t got = render_value_to_str(sp_fmt_uint(10)); - EXPECT_TRUE(sp_str_equal_cstr(got, "10")); -} - -UTEST_F(format, u64_ninety_nine) { - sp_str_t got = render_value_to_str(sp_fmt_uint(99)); - EXPECT_TRUE(sp_str_equal_cstr(got, "99")); -} - -UTEST_F(format, u64_one_hundred) { - sp_str_t got = render_value_to_str(sp_fmt_uint(100)); - EXPECT_TRUE(sp_str_equal_cstr(got, "100")); -} - -UTEST_F(format, u64_max) { - sp_str_t got = render_value_to_str(sp_fmt_uint(0xffffffffffffffffULL)); - EXPECT_TRUE(sp_str_equal_cstr(got, "18446744073709551615")); -} - -UTEST_F(format, s64_min) { - sp_str_t got = render_value_to_str(sp_fmt_int((s64)0x8000000000000000LL)); - EXPECT_TRUE(sp_str_equal_cstr(got, "-9223372036854775808")); -} - -UTEST_F(format, s64_max) { - sp_str_t got = render_value_to_str(sp_fmt_int((s64)0x7fffffffffffffffLL)); - EXPECT_TRUE(sp_str_equal_cstr(got, "9223372036854775807")); -} - -UTEST_F(format, f64_zero) { - sp_str_t got = render_value_to_str(sp_fmt_float(0.0)); - EXPECT_TRUE(sp_str_equal_cstr(got, "0.000000")); -} - -UTEST_F(format, f64_one) { - sp_str_t got = render_value_to_str(sp_fmt_float(1.0)); - EXPECT_TRUE(sp_str_equal_cstr(got, "1.000000")); -} - -UTEST_F(format, f64_half) { - sp_str_t got = render_value_to_str(sp_fmt_float(0.5)); - EXPECT_TRUE(sp_str_equal_cstr(got, "0.500000")); -} - -UTEST_F(format, f64_negative) { - sp_str_t got = render_value_to_str(sp_fmt_float(-3.25)); - EXPECT_TRUE(sp_str_equal_cstr(got, "-3.250000")); -} - -UTEST_F(format, f64_carry) { - sp_str_t got = render_value_to_str(sp_fmt_float(0.9999995)); - EXPECT_TRUE(sp_str_equal_cstr(got, "1.000000")); -} - -UTEST_F(format, f64_nan) { - union { f64 f; u64 u; } nan_bits = { .u = 0x7ff8000000000000ULL }; - sp_str_t got = render_value_to_str(sp_fmt_float(nan_bits.f)); - EXPECT_TRUE(sp_str_equal_cstr(got, "nan")); -} - -UTEST_F(format, f64_pos_inf) { - union { f64 f; u64 u; } inf_bits = { .u = 0x7ff0000000000000ULL }; - sp_str_t got = render_value_to_str(sp_fmt_float(inf_bits.f)); - EXPECT_TRUE(sp_str_equal_cstr(got, "inf")); -} - -UTEST_F(format, f64_neg_inf) { - union { f64 f; u64 u; } inf_bits = { .u = 0xfff0000000000000ULL }; - sp_str_t got = render_value_to_str(sp_fmt_float(inf_bits.f)); - EXPECT_TRUE(sp_str_equal_cstr(got, "-inf")); -} - -UTEST_F(format, f64_custom_precision_via_spec) { - sp_fmt_arg_t arg = sp_fmt_arg_from_argv(sp_fmt_float(3.14159)); - sp_opt_set(arg.spec.precision, 2); - sp_io_dyn_mem_writer_t io = sp_zero; - sp_io_dyn_mem_writer_init(sp_mem_get_scratch(), &io); - sp_fmt_render_default(&io.base, &arg, SP_NULLPTR); - sp_str_t got = sp_io_dyn_mem_writer_as_str(&io); - EXPECT_TRUE(sp_str_equal_cstr(got, "3.14")); -} - -UTEST_F(format, ptr_null) { - sp_str_t got = render_value_to_str(sp_fmt_ptr(SP_NULLPTR)); - EXPECT_TRUE(sp_str_equal_cstr(got, "0x0")); -} - -UTEST_F(format, ptr_nonzero) { - sp_str_t got = render_value_to_str(sp_fmt_ptr((void*)(uintptr_t)0xdeadbeefULL)); - EXPECT_TRUE(sp_str_equal_cstr(got, "0xdeadbeef")); -} - -static void _test_before_lt(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - sp_io_write_cstr(io, "<", SP_NULLPTR); -} - -static void _test_after_gt(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - sp_io_write_cstr(io, ">", SP_NULLPTR); -} - -static void _test_render_x(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - sp_io_write_cstr(io, "X", SP_NULLPTR); -} - -static sp_io_dyn_mem_writer_t _test_log; -static u32 _test_render_y_calls; - -static void _test_before_a(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - sp_io_write_cstr(io, "[a", SP_NULLPTR); - sp_io_write_cstr(&_test_log.base, "ba", SP_NULLPTR); -} - -static void _test_after_a(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - sp_io_write_cstr(io, "a]", SP_NULLPTR); - sp_io_write_cstr(&_test_log.base, "aa", SP_NULLPTR); -} - -static void _test_before_b(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - sp_io_write_cstr(io, "[b", SP_NULLPTR); - sp_io_write_cstr(&_test_log.base, "bb", SP_NULLPTR); -} - -static void _test_after_b(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - sp_io_write_cstr(io, "b]", SP_NULLPTR); - sp_io_write_cstr(&_test_log.base, "ab", SP_NULLPTR); -} - -static void _test_render_y(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - _test_render_y_calls++; - sp_io_write_cstr(io, "Y", SP_NULLPTR); -} - - -UTEST_F(format, register_and_lookup) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("wrap", _test_before_lt, _test_after_gt); - - sp_fmt_directive_t* got = sp_fmt_directive_lookup(sp_str_lit("wrap")); - EXPECT_TRUE(got != SP_NULLPTR); - EXPECT_EQ(got->kind, sp_fmt_directive_decorator); - EXPECT_TRUE(got->decorator.before == _test_before_lt); - EXPECT_TRUE(got->decorator.after == _test_after_gt); - - sp_str_t rendered = sp_fmt(sp_mem_get_scratch(), "{.wrap}", sp_fmt_cstr("ok")).value; - EXPECT_TRUE(sp_str_equal_cstr(rendered, "")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, lookup_unknown_returns_null) { - sp_fmt_directive_reset(); - sp_fmt_directive_t* got = sp_fmt_directive_lookup(sp_str_lit("nope")); - EXPECT_EQ(got, SP_NULLPTR); -} - -UTEST_F(format, reset_clears) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("foo", _test_before_lt, SP_NULLPTR); - EXPECT_TRUE(sp_fmt_directive_lookup(sp_str_lit("foo")) != SP_NULLPTR); - sp_fmt_directive_reset(); - EXPECT_EQ(sp_fmt_directive_lookup(sp_str_lit("foo")), SP_NULLPTR); -} - -UTEST_F(format, wraps_content) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("wrap", _test_before_lt, _test_after_gt); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.wrap}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, render_replaces_value) { - sp_fmt_directive_reset(); - sp_fmt_register_renderer("x", _test_render_x, sp_fmt_id_none); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.x}", sp_fmt_int(999)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "X")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, err_unknown_directive) { - sp_fmt_directive_reset(); - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{.missing}", sp_fmt_int(42)).err, SP_ERR_FMT_UNKNOWN_DIRECTIVE); -} - -UTEST_F(format, ordering_bracket_nested) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("a", _test_before_a, _test_after_a); - sp_fmt_register_decorator("b", _test_before_b, _test_after_b); - - _test_log = (sp_io_dyn_mem_writer_t)sp_zero; - sp_io_dyn_mem_writer_init(sp_mem_get_scratch(), &_test_log); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.a .b}", sp_fmt_cstr("x")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "[a[bxb]a]")); - - sp_str_t log_str = sp_io_dyn_mem_writer_as_str(&_test_log); - EXPECT_TRUE(sp_str_equal_cstr(log_str, "babbabaa")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, err_multiple_renders) { - sp_fmt_directive_reset(); - _test_render_y_calls = 0; - sp_fmt_register_renderer("x", _test_render_x, sp_fmt_id_none); - sp_fmt_register_renderer("y", _test_render_y, sp_fmt_id_none); - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{.x .y}", sp_fmt_int(0)).err, SP_ERR_FMT_TOO_MANY_RENDERERS); - EXPECT_EQ(_test_render_y_calls, 0); - sp_fmt_directive_reset(); -} - -UTEST_F(format, padding_outside_wrappers) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("wrap", _test_before_lt, _test_after_gt); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:6 .wrap}", sp_fmt_int(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " <42>")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, padding_with_center_and_wrapper) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("wrap", _test_before_lt, _test_after_gt); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:*^8 .wrap}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "******")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, literal_only) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "hello, world").value; - EXPECT_TRUE(sp_str_equal_cstr(got, "hello, world")); -} - -UTEST_F(format, empty_placeholder_int) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{}", sp_fmt_int(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "42")); -} - -UTEST_F(format, empty_placeholder_str) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{}", sp_fmt_cstr("world")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "world")); -} - -UTEST_F(format, multi_arg) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{} + {} = {}", - sp_fmt_int(2), sp_fmt_int(3), sp_fmt_int(5)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "2 + 3 = 5")); -} - -UTEST_F(format, literals_around_placeholder) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "hello, {}!", sp_fmt_cstr("thomas")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "hello, thomas!")); -} - -UTEST_F(format, width_right_align) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:6}", sp_fmt_int(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " 42")); -} - -UTEST_F(format, fill_center) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:*^9}", sp_fmt_int(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "***42****")); -} - -UTEST_F(format, brace_escapes) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{{{}}}", sp_fmt_int(7)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "{7}")); -} - -UTEST_F(format, close_brace_escape) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "hello }} world").value; - EXPECT_TRUE(sp_str_equal_cstr(got, "hello } world")); -} - -UTEST_F(format, err_lone_close_brace) { - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "oops } here").err, SP_ERR_FMT_BAD_PLACEHOLDER); -} - -UTEST_F(format, str_with_padding) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "[{:->8}]", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "[------hi]")); -} - -UTEST_F(format, custom_fn_fallback) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("wrap", _test_before_lt, _test_after_gt); - u32 value = 0; - sp_fmt_argv_t arg = sp_fmt_custom(u32, _test_render_x, value); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.wrap}", arg).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, default_render_with_wrappers_on_int) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("wrap", _test_before_lt, _test_after_gt); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.wrap}", sp_fmt_int(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "<42>")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, content_wider_than_width_with_wrapper) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("wrap", _test_before_lt, _test_after_gt); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:3 .wrap}", sp_fmt_cstr("hello")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "")); - sp_fmt_directive_reset(); -} - -static void _test_render_prefixed(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - sp_io_write_cstr(io, "rendered", SP_NULLPTR); -} - -UTEST_F(format, err_alpha_after_digit) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{:5red}", &spec), SP_ERR_FMT_BAD_PLACEHOLDER); -} - -UTEST_F(format, escaped_braces_around_placeholder) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{{{:5}}}", sp_fmt_int(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "{ 42}")); -} - -UTEST_F(format, dynamic_width) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:$}", sp_fmt_int(6), sp_fmt_int(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " 42")); -} - -UTEST_F(format, dynamic_fill_center) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:$^9}", sp_fmt_int('*'), sp_fmt_int(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "***42****")); -} - -UTEST_F(format, dynamic_fill_and_width) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:$^$}", - sp_fmt_int('-'), - sp_fmt_int(8), - sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "---hi---")); -} - -UTEST_F(format, dynamic_precision) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:.$}", sp_fmt_int(3), sp_fmt_float(3.14159)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "3.142")); -} - -UTEST_F(format, dynamic_width_with_literal_precision) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:$.2}", sp_fmt_int(8), sp_fmt_float(1.5)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " 1.50")); -} - -UTEST_F(format, err_parse_stops_formatting) { - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "a {:5.} b {}", sp_fmt_int(99)).err, SP_ERR_FMT_BAD_PRECISION); -} - -UTEST_F(format, err_unterminated_placeholder) { - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "hi {nope", sp_fmt_int(1)).err, SP_ERR_FMT_BAD_PLACEHOLDER); -} - -UTEST_F(format, err_dynamic_fill_wrong_kind) { - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{:$^5}", sp_fmt_float(1.0), sp_fmt_int(42)).err, SP_ERR_FMT_DIRECTIVE_ARG_WRONG_KIND); -} - -UTEST_F(format, err_dynamic_width_wrong_kind) { - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{:$}", sp_fmt_cstr("oops"), sp_fmt_int(42)).err, SP_ERR_FMT_DIRECTIVE_ARG_WRONG_KIND); -} - -UTEST_F(format, err_dynamic_precision_wrong_kind) { - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{:.$}", sp_fmt_float(3.0), sp_fmt_float(3.14)).err, SP_ERR_FMT_DIRECTIVE_ARG_WRONG_KIND); -} - -UTEST_F(format, err_stops_subsequent_placeholders) { - sp_fmt_directive_reset(); - sp_err_t err = sp_fmt(sp_mem_get_scratch(), "{} {.nope} {}", sp_fmt_int(1), sp_fmt_int(2), sp_fmt_int(3)).err; - EXPECT_EQ(err, SP_ERR_FMT_UNKNOWN_DIRECTIVE); -} - -UTEST_F(format, str_precision_truncates) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:.3}", sp_fmt_cstr("hello")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "hel")); -} - -UTEST_F(format, str_precision_longer_than_string) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:.10}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "hi")); -} - -UTEST_F(format, str_dynamic_precision_truncates) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:.$}", sp_fmt_int(2), sp_fmt_cstr("hello")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "he")); -} - -UTEST_F(format, str_precision_with_width) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "[{:>6.3}]", sp_fmt_cstr("hello")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "[ hel]")); -} - -UTEST_F(format, f64_precision_zero_means_zero) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:.0}", sp_fmt_float(3.7)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "4")); -} - -UTEST_F(format, f64_dynamic_precision_zero) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:.$}", sp_fmt_int(0), sp_fmt_float(3.7)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "4")); -} - -UTEST_F(format, f64_no_precision_defaults_to_six) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{}", sp_fmt_float(1.5)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "1.500000")); -} - -UTEST_F(format, width_clamped_literal) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:99999}", sp_fmt_cstr("x")).value; - EXPECT_EQ(got.len, SP_FMT_WIDTH_MAX); -} - -UTEST_F(format, width_clamped_dynamic_huge) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:$}", sp_fmt_uint(999999999ULL), sp_fmt_cstr("x")).value; - EXPECT_EQ(got.len, SP_FMT_WIDTH_MAX); -} - -UTEST_F(format, width_clamped_dynamic_negative) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:$}", sp_fmt_int(-5), sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "hi")); -} - -static void _test_render_u64_only(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)param; - sp_fmt_write_u64(io, arg->value.u); -} - -UTEST_F(format, kinds_single_accepts_match) { - sp_fmt_directive_reset(); - sp_fmt_register_renderer("only_u64", _test_render_u64_only, sp_fmt_id_u64); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.only_u64}", sp_fmt_uint(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "42")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, kinds_single_rejects_mismatch) { - sp_fmt_directive_reset(); - sp_fmt_register_renderer("only_u64", _test_render_u64_only, sp_fmt_id_u64); - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{.only_u64}", sp_fmt_float(1.5)).err, SP_ERR_FMT_WRONG_PARAM_KIND); - sp_fmt_directive_reset(); -} - -UTEST_F(format, kinds_multiple_accepts_either) { - sp_fmt_directive_reset(); - sp_fmt_directive_register("num", (sp_fmt_directive_t){ - .kind = sp_fmt_directive_decorator, - .args = sp_cast(sp_fmt_arg_kind_t, sp_fmt_id_u64 | sp_fmt_id_s64), - }); - sp_str_t a = sp_fmt(sp_mem_get_scratch(), "{.num}", sp_fmt_uint(7)).value; - sp_str_t b = sp_fmt(sp_mem_get_scratch(), "{.num}", sp_fmt_int(-3)).value; - EXPECT_TRUE(sp_str_equal_cstr(a, "7")); - EXPECT_TRUE(sp_str_equal_cstr(b, "-3")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, kinds_multiple_rejects_outsider) { - sp_fmt_directive_reset(); - sp_fmt_directive_register("num", (sp_fmt_directive_t){ - .kind = sp_fmt_directive_decorator, - .args = sp_cast(sp_fmt_arg_kind_t, sp_fmt_id_u64 | sp_fmt_id_s64), - }); - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{.num}", sp_fmt_cstr("nope")).err, SP_ERR_FMT_WRONG_PARAM_KIND); - sp_fmt_directive_reset(); -} - -UTEST_F(format, kinds_unset_accepts_all) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("any", _test_before_lt, _test_after_gt); - sp_str_t a = sp_fmt(sp_mem_get_scratch(), "{.any}", sp_fmt_int(1)).value; - sp_str_t b = sp_fmt(sp_mem_get_scratch(), "{.any}", sp_fmt_cstr("hi")).value; - sp_str_t c = sp_fmt(sp_mem_get_scratch(), "{.any}", sp_fmt_float(2.0)).value; - EXPECT_TRUE(sp_str_equal_cstr(a, "<1>")); - EXPECT_TRUE(sp_str_equal_cstr(b, "")); - EXPECT_TRUE(sp_str_equal_cstr(c, "<2.000000>")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, literal) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.fg red}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 1); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[0], "fg")); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.args[0], "red")); - EXPECT_EQ(spec.dynamic.directive, 0); -} - -UTEST_F(format, dynamic) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.fg $}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 1); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[0], "fg")); - EXPECT_EQ(spec.directive.args[0].len, 0); - EXPECT_EQ(spec.dynamic.directive, 1); -} - -UTEST_F(format, literal_then_directive) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.fg red .bold}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 2); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[0], "fg")); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.args[0], "red")); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[1], "bold")); - EXPECT_EQ(spec.directive.args[1].len, 0); -} - -UTEST_F(format, dynamic_then_directive) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.fg $ .bold}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 2); - EXPECT_EQ(spec.dynamic.directive, 1); -} - -UTEST_F(format, two_literals) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.fg red .bg blue}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 2); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.args[0], "red")); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.args[1], "blue")); - EXPECT_EQ(spec.dynamic.directive, 0); -} - -UTEST_F(format, two_dynamics) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.fg $ .bg $}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 2); - EXPECT_EQ(spec.dynamic.directive, 0b11); -} - -UTEST_F(format, mixed) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.fg red .bg $ .bold}", &spec), SP_OK); - EXPECT_EQ(spec.directive.num, 3); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.args[0], "red")); - EXPECT_EQ(spec.directive.args[1].len, 0); - EXPECT_EQ(spec.dynamic.directive, 0b010); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[2], "bold")); -} - -UTEST_F(format, with_full_spec) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{:*^9.2 .fg red}", &spec), SP_OK); - EXPECT_EQ(spec.width, 9); - EXPECT_EQ(spec.directive.num, 1); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.args[0], "red")); -} - -UTEST_F(format, arg_with_digits_and_symbols) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.hex 0xff}", &spec), SP_OK); - EXPECT_TRUE(sp_str_equal_cstr(spec.directive.args[0], "0xff")); -} - -UTEST_F(format, err_dollar_inside_literal) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.fg r$d}", &spec), SP_ERR_FMT_BAD_PLACEHOLDER); -} - -UTEST_F(format, err_dollar_followed_by_literal) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.fg $red}", &spec), SP_ERR_FMT_BAD_PLACEHOLDER); -} - -UTEST_F(format, err_space_in_arg) { - sp_fmt_spec_t spec = sp_zero; - EXPECT_EQ(parse_spec("{.fg red blue}", &spec), SP_ERR_FMT_BAD_PLACEHOLDER); -} - -static sp_str_t _last_fg_param; -static bool _last_fg_had_param; -static void _test_fg_before(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; - _last_fg_had_param = (param != SP_NULLPTR); - if (param && param->id == sp_fmt_id_str) { - _last_fg_param = param->value.s; - sp_io_write_cstr(io, "value.s, SP_NULLPTR); - sp_io_write_cstr(io, ">", SP_NULLPTR); - } - else { - sp_io_write_cstr(io, "", SP_NULLPTR); - } -} - -static void _test_fg_after(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - sp_io_write_cstr(io, "", SP_NULLPTR); -} - -UTEST_F(format, literal_passed_as_str) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator_p("fg", _test_fg_before, _test_fg_after, sp_fmt_id_str); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.fg red}", sp_fmt_cstr("hello")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "hello")); - EXPECT_TRUE(_last_fg_had_param); - EXPECT_TRUE(sp_str_equal_cstr(_last_fg_param, "red")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, dynamic_passed_as_str) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator_p("fg", _test_fg_before, _test_fg_after, sp_fmt_id_str); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.fg $}", sp_fmt_cstr("blue"), sp_fmt_cstr("hello")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "hello")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, dynamic_accepts_u64_with_mask) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator_p("fg", _test_fg_before, _test_fg_after, sp_fmt_id_str | sp_fmt_id_u64); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.fg $}", sp_fmt_uint(31), sp_fmt_cstr("x")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "x")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, err_missing_arg) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator_p("fg", _test_fg_before, _test_fg_after, sp_fmt_id_str); - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{.fg}", sp_fmt_cstr("hi")).err, SP_ERR_FMT_DIRECTIVE_ARG_MISSING); - sp_fmt_directive_reset(); -} - -UTEST_F(format, err_unexpected_arg) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("bold", _test_before_lt, _test_after_gt); - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{.bold red}", sp_fmt_cstr("hi")).err, SP_ERR_FMT_DIRECTIVE_ARG_UNEXPECTED); - sp_fmt_directive_reset(); -} - -UTEST_F(format, err_wrong_literal_kind) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator_p("numpad", _test_before_lt, _test_after_gt, sp_fmt_id_u64); - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{.numpad abc}", sp_fmt_cstr("hi")).err, SP_ERR_FMT_DIRECTIVE_ARG_WRONG_KIND); - sp_fmt_directive_reset(); -} - -UTEST_F(format, err_wrong_dynamic_kind) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator_p("fg", _test_fg_before, _test_fg_after, sp_fmt_id_str); - EXPECT_EQ(sp_fmt(sp_mem_get_scratch(), "{.fg $}", sp_fmt_uint(5), sp_fmt_cstr("hi")).err, SP_ERR_FMT_DIRECTIVE_ARG_WRONG_KIND); - sp_fmt_directive_reset(); -} - -UTEST_F(format, chain_of_literal_and_dynamic) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator_p("fg", _test_fg_before, _test_fg_after, sp_fmt_id_str); - sp_fmt_register_decorator("bold", _test_before_lt, _test_after_gt); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.fg $ .bold}", sp_fmt_cstr("cyan"), sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, literal_color) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.fg red}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "\033[31mhi\033[0m")); -} - -UTEST_F(format, literal_bright_cyan) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.fg brightcyan}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "\033[96mhi\033[0m")); -} - -UTEST_F(format, dynamic_color) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.fg $}", sp_fmt_cstr("green"), sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "\033[32mhi\033[0m")); -} - -UTEST_F(format, composes_with_padding) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:*^6 .fg red}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "**\033[31mhi\033[0m**")); -} - -UTEST_F(format, dynamic_param_interleaved_with_width) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator_p("fg", _test_fg_before, _test_fg_after, sp_fmt_id_str); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:$ .fg $}", sp_fmt_int(4), sp_fmt_cstr("red"), sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " hi")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, unsigned_integers) { - // sp_parse_u8 - ASSERT_EQ(sp_parse_u8(sp_str_lit("0")), 0); - ASSERT_EQ(sp_parse_u8(sp_str_lit("255")), 255); - ASSERT_EQ(sp_parse_u8(sp_str_lit("128")), 128); - ASSERT_EQ(sp_parse_u8(sp_str_lit("42")), 42); - // Would assert: "256", "-1", "abc", "" - - // sp_parse_u16 - ASSERT_EQ(sp_parse_u16(sp_str_lit("0")), 0); - ASSERT_EQ(sp_parse_u16(sp_str_lit("65535")), 65535); - ASSERT_EQ(sp_parse_u16(sp_str_lit("32768")), 32768); - ASSERT_EQ(sp_parse_u16(sp_str_lit("1234")), 1234); - // Would assert: "65536", "-1", "text" - - // sp_parse_u32 - ASSERT_EQ(sp_parse_u32(sp_str_lit("0")), 0); - ASSERT_EQ(sp_parse_u32(sp_str_lit("4294967295")), 4294967295U); - ASSERT_EQ(sp_parse_u32(sp_str_lit("2147483648")), 2147483648U); - ASSERT_EQ(sp_parse_u32(sp_str_lit("123456789")), 123456789U); - // Would assert: "4294967296", "-1", "not_a_number" - - // sp_parse_u64 - ASSERT_EQ(sp_parse_u64(sp_str_lit("0")), 0ULL); - ASSERT_EQ(sp_parse_u64(sp_str_lit("18446744073709551615")), 18446744073709551615ULL); - ASSERT_EQ(sp_parse_u64(sp_str_lit("9223372036854775808")), 9223372036854775808ULL); - ASSERT_EQ(sp_parse_u64(sp_str_lit("1234567890123")), 1234567890123ULL); - // Would assert: "18446744073709551616", "-1", "invalid" -} - -UTEST_F(format, signed_integers) { - // sp_parse_s8 - ASSERT_EQ(sp_parse_s8(sp_str_lit("0")), 0); - ASSERT_EQ(sp_parse_s8(sp_str_lit("127")), 127); - ASSERT_EQ(sp_parse_s8(sp_str_lit("-128")), -128); - ASSERT_EQ(sp_parse_s8(sp_str_lit("-42")), -42); - ASSERT_EQ(sp_parse_s8(sp_str_lit("42")), 42); - // Would assert: "128", "-129", "text" - - // sp_parse_s16 - ASSERT_EQ(sp_parse_s16(sp_str_lit("0")), 0); - ASSERT_EQ(sp_parse_s16(sp_str_lit("32767")), 32767); - ASSERT_EQ(sp_parse_s16(sp_str_lit("-32768")), -32768); - ASSERT_EQ(sp_parse_s16(sp_str_lit("-1234")), -1234); - ASSERT_EQ(sp_parse_s16(sp_str_lit("1234")), 1234); - // Would assert: "32768", "-32769", "invalid" - - // sp_parse_s32 - ASSERT_EQ(sp_parse_s32(sp_str_lit("0")), 0); - ASSERT_EQ(sp_parse_s32(sp_str_lit("2147483647")), 2147483647); - ASSERT_EQ(sp_parse_s32(sp_str_lit("-2147483648")), INT32_MIN); - ASSERT_EQ(sp_parse_s32(sp_str_lit("-123456789")), -123456789); - ASSERT_EQ(sp_parse_s32(sp_str_lit("123456789")), 123456789); - // Would assert: "2147483648", "-2147483649", "not_number" - - // sp_parse_s64 - ASSERT_EQ(sp_parse_s64(sp_str_lit("0")), 0LL); - ASSERT_EQ(sp_parse_s64(sp_str_lit("9223372036854775807")), 9223372036854775807LL); - ASSERT_EQ(sp_parse_s64(sp_str_lit("-9223372036854775808")), INT64_MIN); - ASSERT_EQ(sp_parse_s64(sp_str_lit("-1234567890123")), -1234567890123LL); - ASSERT_EQ(sp_parse_s64(sp_str_lit("1234567890123")), 1234567890123LL); - // Would assert: "9223372036854775808", "-9223372036854775809", "abc" -} - -UTEST_F(format, floating_point) { - // sp_parse_f32 - ASSERT_NEAR(sp_parse_f32(sp_str_lit("0")), 0.0f, 1e-5f); - ASSERT_NEAR(sp_parse_f32(sp_str_lit("0.0")), 0.0f, 1e-5f); - ASSERT_NEAR(sp_parse_f32(sp_str_lit("3.14159")), 3.14159f, 1e-5f); - ASSERT_NEAR(sp_parse_f32(sp_str_lit("-3.14159")), -3.14159f, 1e-5f); - ASSERT_NEAR(sp_parse_f32(sp_str_lit("1.23e2")), 123.0f, 1e-5f); - ASSERT_NEAR(sp_parse_f32(sp_str_lit("1.23e-2")), 0.0123f, 1e-5f); - ASSERT_NEAR(sp_parse_f32(sp_str_lit("-1.23e2")), -123.0f, 1e-5f); - ASSERT_NEAR(sp_parse_f32(sp_str_lit("42")), 42.0f, 1e-5f); - ASSERT_NEAR(sp_parse_f32(sp_str_lit("-42")), -42.0f, 1e-5f); - // Would assert: "nan", "inf", "text", "" - - // sp_parse_f64 - NOT IMPLEMENTED (SP_UNIMPLEMENTED) - // ASSERT_NEAR(sp_parse_f64(sp_str_lit("0")), 0.0, 1e-10); - // ASSERT_NEAR(sp_parse_f64(sp_str_lit("0.0")), 0.0, 1e-10); - // ASSERT_NEAR(sp_parse_f64(sp_str_lit("3.141592653589793")), 3.141592653589793, 1e-10); - // ASSERT_NEAR(sp_parse_f64(sp_str_lit("-3.141592653589793")), -3.141592653589793, 1e-10); - // ASSERT_NEAR(sp_parse_f64(sp_str_lit("1.23e10")), 1.23e10, 1e-10); - // ASSERT_NEAR(sp_parse_f64(sp_str_lit("1.23e-10")), 1.23e-10, 1e-20); - // ASSERT_NEAR(sp_parse_f64(sp_str_lit("-1.23e10")), -1.23e10, 1e-10); - // ASSERT_NEAR(sp_parse_f64(sp_str_lit("42.0")), 42.0, 1e-10); - // ASSERT_NEAR(sp_parse_f64(sp_str_lit("-42.0")), -42.0, 1e-10); - // Would assert: "nan", "inf", "invalid", "" -} - -UTEST_F(format, hex) { - ASSERT_EQ(sp_parse_hex(sp_str_lit("0")), 0ULL); - ASSERT_EQ(sp_parse_hex(sp_str_lit("F")), 0xFULL); - ASSERT_EQ(sp_parse_hex(sp_str_lit("f")), 0xfULL); - ASSERT_EQ(sp_parse_hex(sp_str_lit("FF")), 0xFFULL); - ASSERT_EQ(sp_parse_hex(sp_str_lit("ff")), 0xffULL); - ASSERT_EQ(sp_parse_hex(sp_str_lit("DEADBEEF")), 0xDEADBEEFULL); - ASSERT_EQ(sp_parse_hex(sp_str_lit("deadbeef")), 0xdeadbeefULL); - ASSERT_EQ(sp_parse_hex(sp_str_lit("123ABC")), 0x123ABCULL); - ASSERT_EQ(sp_parse_hex(sp_str_lit("FFFFFFFFFFFFFFFF")), 0xFFFFFFFFFFFFFFFFULL); - // Would assert: "G", "xyz", "-F", "", "0x" prefix, "0123" octal notation -} - -UTEST_F(format, hash) { - ASSERT_EQ(sp_parse_hash(sp_str_lit("0")), 0U); - ASSERT_EQ(sp_parse_hash(sp_str_lit("FFFFFFFF")), 0xFFFFFFFFU); - ASSERT_EQ(sp_parse_hash(sp_str_lit("12345678")), 0x12345678U); - ASSERT_EQ(sp_parse_hash(sp_str_lit("DEADBEEF")), 0xDEADBEEFU); - ASSERT_EQ(sp_parse_hash(sp_str_lit("deadbeef")), 0xdeadbeefU); - ASSERT_EQ(sp_parse_hash(sp_str_lit("ABCD")), 0xABCDU); - // Would assert: "G", "12345678901", "-1", "" -} - -UTEST_F(format, boolean) { - ASSERT_EQ(sp_parse_bool(sp_str_lit("true")), true); - ASSERT_EQ(sp_parse_bool(sp_str_lit("false")), false); - ASSERT_EQ(sp_parse_bool(sp_str_lit("1")), true); - ASSERT_EQ(sp_parse_bool(sp_str_lit("0")), false); - // yes/no, on/off not supported - only true/false and 1/0 - // Would assert: "maybe", "2", "TRUE", "", "yes", "no", "on", "off" -} - -UTEST_F(format, characters) { - // sp_parse_c8 - expects single quoted chars like 'A' - ASSERT_EQ(sp_parse_c8(sp_str_lit("'A'")), 'A'); - ASSERT_EQ(sp_parse_c8(sp_str_lit("'z'")), 'z'); - ASSERT_EQ(sp_parse_c8(sp_str_lit("'0'")), '0'); - ASSERT_EQ(sp_parse_c8(sp_str_lit("' '")), ' '); - ASSERT_EQ(sp_parse_c8(sp_str_lit("'!'")), '!'); - // Would assert: "AB", "", "abc", "A" (no quotes) - - // sp_parse_c16 - expects single quoted chars like 'A' - ASSERT_EQ(sp_parse_c16(sp_str_lit("'A'")), L'A'); - ASSERT_EQ(sp_parse_c16(sp_str_lit("'z'")), L'z'); - ASSERT_EQ(sp_parse_c16(sp_str_lit("'0'")), L'0'); - ASSERT_EQ(sp_parse_c16(sp_str_lit("' '")), L' '); - ASSERT_EQ(sp_parse_c16(sp_str_lit("'!'")), L'!'); - // Would assert: "AB", "", "abc", "A" (no quotes) -} - -UTEST_F(format, extended) { - u32 u32_val; - ASSERT_TRUE(sp_parse_u32_ex(sp_str_lit("42"), &u32_val)); - ASSERT_EQ(u32_val, 42U); - ASSERT_TRUE(sp_parse_u32_ex(sp_str_lit("0"), &u32_val)); - ASSERT_EQ(u32_val, 0U); - ASSERT_TRUE(sp_parse_u32_ex(sp_str_lit("4294967295"), &u32_val)); - ASSERT_EQ(u32_val, 4294967295U); - ASSERT_FALSE(sp_parse_u32_ex(sp_str_lit("4294967296"), &u32_val)); - ASSERT_FALSE(sp_parse_u32_ex(sp_str_lit("-1"), &u32_val)); - ASSERT_FALSE(sp_parse_u32_ex(sp_str_lit("abc"), &u32_val)); - ASSERT_FALSE(sp_parse_u32_ex(sp_str_lit(""), &u32_val)); - - // sp_parse_s32_ex - s32 s32_val; - ASSERT_TRUE(sp_parse_s32_ex(sp_str_lit("42"), &s32_val)); - ASSERT_EQ(s32_val, 42); - ASSERT_TRUE(sp_parse_s32_ex(sp_str_lit("-42"), &s32_val)); - ASSERT_EQ(s32_val, -42); - ASSERT_TRUE(sp_parse_s32_ex(sp_str_lit("0"), &s32_val)); - ASSERT_EQ(s32_val, 0); - ASSERT_TRUE(sp_parse_s32_ex(sp_str_lit("2147483647"), &s32_val)); - ASSERT_EQ(s32_val, 2147483647); - ASSERT_TRUE(sp_parse_s32_ex(sp_str_lit("-2147483648"), &s32_val)); - ASSERT_EQ(s32_val, INT32_MIN); - ASSERT_FALSE(sp_parse_s32_ex(sp_str_lit("2147483648"), &s32_val)); - ASSERT_FALSE(sp_parse_s32_ex(sp_str_lit("-2147483649"), &s32_val)); - ASSERT_FALSE(sp_parse_s32_ex(sp_str_lit("text"), &s32_val)); - ASSERT_FALSE(sp_parse_s32_ex(sp_str_lit(""), &s32_val)); - - // sp_parse_f32_ex - f32 f32_val; - ASSERT_TRUE(sp_parse_f32_ex(sp_str_lit("3.14"), &f32_val)); - ASSERT_NEAR(f32_val, 3.14f, 1e-5f); - ASSERT_TRUE(sp_parse_f32_ex(sp_str_lit("-3.14"), &f32_val)); - ASSERT_NEAR(f32_val, -3.14f, 1e-5f); - ASSERT_TRUE(sp_parse_f32_ex(sp_str_lit("0"), &f32_val)); - ASSERT_NEAR(f32_val, 0.0f, 1e-5f); - ASSERT_TRUE(sp_parse_f32_ex(sp_str_lit("1.23e2"), &f32_val)); - ASSERT_NEAR(f32_val, 123.0f, 1e-5f); - ASSERT_FALSE(sp_parse_f32_ex(sp_str_lit("abc"), &f32_val)); - ASSERT_FALSE(sp_parse_f32_ex(sp_str_lit(""), &f32_val)); - - // sp_parse_f64_ex - NOT IMPLEMENTED (SP_UNIMPLEMENTED) - // f64 f64_val; - // ASSERT_TRUE(sp_parse_f64_ex(sp_str_lit("3.14"), &f64_val)); - - // sp_parse_bool_ex - bool bool_val; - ASSERT_TRUE(sp_parse_bool_ex(sp_str_lit("true"), &bool_val)); - ASSERT_EQ(bool_val, true); - ASSERT_TRUE(sp_parse_bool_ex(sp_str_lit("false"), &bool_val)); - ASSERT_EQ(bool_val, false); - ASSERT_TRUE(sp_parse_bool_ex(sp_str_lit("1"), &bool_val)); - ASSERT_EQ(bool_val, true); - ASSERT_TRUE(sp_parse_bool_ex(sp_str_lit("0"), &bool_val)); - ASSERT_EQ(bool_val, false); - ASSERT_FALSE(sp_parse_bool_ex(sp_str_lit("maybe"), &bool_val)); - ASSERT_FALSE(sp_parse_bool_ex(sp_str_lit(""), &bool_val)); - - // sp_parse_hex_ex - u64 hex_val; - ASSERT_TRUE(sp_parse_hex_ex(sp_str_lit("DEADBEEF"), &hex_val)); - ASSERT_EQ(hex_val, 0xDEADBEEFULL); - ASSERT_TRUE(sp_parse_hex_ex(sp_str_lit("0"), &hex_val)); - ASSERT_EQ(hex_val, 0ULL); - ASSERT_TRUE(sp_parse_hex_ex(sp_str_lit("FF"), &hex_val)); - ASSERT_EQ(hex_val, 0xFFULL); - ASSERT_FALSE(sp_parse_hex_ex(sp_str_lit("XYZ"), &hex_val)); - ASSERT_FALSE(sp_parse_hex_ex(sp_str_lit(""), &hex_val)); - - // sp_parse_hash_ex - sp_hash_t hash_val; - ASSERT_TRUE(sp_parse_hash_ex(sp_str_lit("DEADBEEF"), &hash_val)); - ASSERT_EQ(hash_val, 0xDEADBEEF); - ASSERT_TRUE(sp_parse_hash_ex(sp_str_lit("0"), &hash_val)); - ASSERT_EQ(hash_val, 0); - ASSERT_FALSE(sp_parse_hash_ex(sp_str_lit("GHIJKLMN"), &hash_val)); - ASSERT_FALSE(sp_parse_hash_ex(sp_str_lit(""), &hash_val)); - - // sp_parse_c8_ex - c8 c8_val; - ASSERT_TRUE(sp_parse_c8_ex(sp_str_lit("'A'"), &c8_val)); - ASSERT_EQ(c8_val, 'A'); - ASSERT_TRUE(sp_parse_c8_ex(sp_str_lit("' '"), &c8_val)); - ASSERT_EQ(c8_val, ' '); - ASSERT_FALSE(sp_parse_c8_ex(sp_str_lit("AB"), &c8_val)); - ASSERT_FALSE(sp_parse_c8_ex(sp_str_lit(""), &c8_val)); - - // sp_parse_c16_ex - u16 c16_val; - ASSERT_TRUE(sp_parse_c16_ex(sp_str_lit("'Z'"), &c16_val)); - ASSERT_EQ(c16_val, L'Z'); - ASSERT_TRUE(sp_parse_c16_ex(sp_str_lit("'!'"), &c16_val)); - ASSERT_EQ(c16_val, L'!'); - ASSERT_FALSE(sp_parse_c16_ex(sp_str_lit("XY"), &c16_val)); - ASSERT_FALSE(sp_parse_c16_ex(sp_str_lit(""), &c16_val)); - - // Additional extended tests for completeness - u8 u8_val; - ASSERT_TRUE(sp_parse_u8_ex(sp_str_lit("255"), &u8_val)); - ASSERT_EQ(u8_val, 255); - ASSERT_FALSE(sp_parse_u8_ex(sp_str_lit("256"), &u8_val)); - - u16 u16_val; - ASSERT_TRUE(sp_parse_u16_ex(sp_str_lit("65535"), &u16_val)); - ASSERT_EQ(u16_val, 65535); - ASSERT_FALSE(sp_parse_u16_ex(sp_str_lit("65536"), &u16_val)); - - u64 u64_val; - ASSERT_TRUE(sp_parse_u64_ex(sp_str_lit("18446744073709551615"), &u64_val)); - ASSERT_EQ(u64_val, 18446744073709551615ULL); - ASSERT_FALSE(sp_parse_u64_ex(sp_str_lit("not_a_number"), &u64_val)); - - s8 s8_val; - ASSERT_TRUE(sp_parse_s8_ex(sp_str_lit("-128"), &s8_val)); - ASSERT_EQ(s8_val, -128); - ASSERT_FALSE(sp_parse_s8_ex(sp_str_lit("-129"), &s8_val)); - - s16 s16_val; - ASSERT_TRUE(sp_parse_s16_ex(sp_str_lit("32767"), &s16_val)); - ASSERT_EQ(s16_val, 32767); - ASSERT_FALSE(sp_parse_s16_ex(sp_str_lit("32768"), &s16_val)); - - s64 s64_val; - ASSERT_TRUE(sp_parse_s64_ex(sp_str_lit("9223372036854775807"), &s64_val)); - ASSERT_EQ(s64_val, 9223372036854775807LL); - ASSERT_FALSE(sp_parse_s64_ex(sp_str_lit("invalid"), &s64_val)); -} - -UTEST_F(format, parse_edge_cases) { - ASSERT_EQ(sp_parse_u32(sp_str_lit("00042")), 42U); - ASSERT_EQ(sp_parse_s32(sp_str_lit("-00042")), -42); - ASSERT_NEAR(sp_parse_f32(sp_str_lit("003.14")), 3.14f, 1e-5f); - - ASSERT_EQ(sp_parse_s32(sp_str_lit("+42")), 42); - ASSERT_NEAR(sp_parse_f32(sp_str_lit("+3.14")), 3.14f, 1e-5f); - - ASSERT_EQ(sp_parse_hex(sp_str_lit("DeAdBeEf")), 0xdeadbeefULL); - ASSERT_EQ(sp_parse_hex(sp_str_lit("DEADBEEF")), 0xDEADBEEFULL); - ASSERT_EQ(sp_parse_hex(sp_str_lit("deadbeef")), 0xdeadbeefULL); - - ASSERT_EQ(sp_parse_u8(sp_str_lit("255")), 255); - ASSERT_EQ(sp_parse_u16(sp_str_lit("65535")), 65535); - ASSERT_EQ(sp_parse_u32(sp_str_lit("4294967295")), 4294967295U); - ASSERT_EQ(sp_parse_u64(sp_str_lit("18446744073709551615")), 18446744073709551615ULL); -} - -UTEST_F(format, hex_zero) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.hex}", sp_fmt_uint(0)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "0x0")); -} - -UTEST_F(format, hex_small) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.hex}", sp_fmt_uint(0xa)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "0xA")); -} - -UTEST_F(format, hex_no_pad) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.hex}", sp_fmt_uint(0xa5)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "0xA5")); -} - -UTEST_F(format, hex_word) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.hex}", sp_fmt_uint(0xdeadbeef)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "0xDEADBEEF")); -} - -UTEST_F(format, hex_u64_max) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.hex}", sp_fmt_uint(0xffffffffffffffffULL)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "0xFFFFFFFFFFFFFFFF")); -} - -UTEST_F(format, hex_signed_negative) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.hex}", sp_fmt_int(-1)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "0xFFFFFFFFFFFFFFFF")); -} - -UTEST_F(format, hex_mixed_digits) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.hex}", sp_fmt_uint(0x1234abcd)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "0x1234ABCD")); -} - -UTEST_F(format, italic) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.italic}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "\033[3mhi\033[0m")); -} - -UTEST_F(format, gray) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.gray}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "\033[90mhi\033[0m")); -} - -UTEST_F(format, bright_red) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.br_red}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "\033[91mhi\033[0m")); -} - -UTEST_F(format, bold_int) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.bold}", sp_fmt_int(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "\033[1m42\033[0m")); -} - -UTEST_F(format, hyperlink) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.hyperlink}", sp_fmt_cstr("https://x")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "\033]8;;https://x\033\\https://x\033[0m")); -} - -UTEST_F(format, quote_basic) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.quote}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "\"hi\"")); -} - -UTEST_F(format, quote_with_color) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.quote .red}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "\"\033[31mhi\033[0m\"")); -} - -UTEST_F(format, red_with_width_pads_outside_ansi) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:>10 .red}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " \033[31mhi\033[0m")); -} - -UTEST_F(format, cyan_with_fill_center_pads_outside_ansi) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:*^9 .cyan}", sp_fmt_cstr("x")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "****\033[36mx\033[0m****")); -} - -UTEST_F(format, bold_with_width_on_int) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:>6 .bold}", sp_fmt_int(42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " \033[1m42\033[0m")); -} - -UTEST_F(format, quote_with_width_pads_outside_quotes) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:*^8 .quote}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "***\"hi\"***")); -} - -static void _test_before_only(sp_io_writer_t* io, sp_fmt_arg_t* arg, sp_fmt_arg_t* param) { - (void)arg; (void)param; - sp_io_write_cstr(io, "[", SP_NULLPTR); -} - -UTEST_F(format, null_after_callback) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("openonly", _test_before_only, SP_NULLPTR); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.openonly}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "[hi")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, null_before_callback) { - sp_fmt_directive_reset(); - sp_fmt_register_decorator("closeonly", SP_NULLPTR, _test_after_gt); - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.closeonly}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "hi>")); - sp_fmt_directive_reset(); -} - -UTEST_F(format, no_allocator) { - c8 buffer[64] = sp_zero; - sp_str_t got = sp_fmt_buf(buffer, 64, "hello, {}", sp_fmt_cstr("world")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "hello, world")); -} - -UTEST_F(format, with_decorator_and_width) { - c8 buffer[64] = sp_zero; - sp_str_t got = sp_fmt_buf(buffer, 64, "{:>6 .red}", sp_fmt_cstr("hi")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " \033[31mhi\033[0m")); -} - -UTEST_F(format, multiple_calls_same_writer) { - c8 buffer[64] = sp_zero; - sp_io_mem_writer_t w = sp_zero; - sp_io_mem_writer_from_buffer(&w, buffer, sizeof(buffer)); - sp_fmt_io(&w.base, "[{}]", sp_fmt_cstr("a")); - sp_io_write_c8(&w.base, ' '); - sp_fmt_io(&w.base, "[{}]", sp_fmt_cstr("b")); - sp_str_t got = sp_io_mem_writer_as_str(&w); - EXPECT_TRUE(sp_str_equal_cstr(got, "[a] [b]")); -} - -UTEST_F(format, pointer) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), - "{:>16}", sp_fmt_ptr((void*)(uintptr_t)0xabcdULL)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " 0xabcd")); -} - -UTEST_F(format, negative_int_right_align) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:>8}", sp_fmt_int(-42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " -42")); -} - -UTEST_F(format, negative_int_zero_fill) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:0>8}", sp_fmt_int(-42)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "00000-42")); -} - -UTEST_F(format, empty_string_center_fill) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:*^6}", sp_fmt_cstr("")).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "******")); -} - -UTEST_F(format, float_precision_and_width) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:>10.2}", sp_fmt_float(3.14159)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, " 3.14")); -} - -UTEST_F(format, float_precision_center_fill) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{:*^10.2}", sp_fmt_float(1.5)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "***1.50***")); -} - -UTEST_F(format, bytes_zero) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.bytes}", sp_fmt_uint(0)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "0 B")); -} - -UTEST_F(format, bytes_below_kb) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.bytes}", sp_fmt_uint(1023)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "1023 B")); -} - -UTEST_F(format, bytes_exact_kb) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.bytes}", sp_fmt_uint(1024)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "1 KB")); -} - -UTEST_F(format, bytes_exact_mb) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.bytes}", sp_fmt_uint(1024ULL * 1024ULL)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "1 MB")); -} - -UTEST_F(format, bytes_fractional) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.bytes}", sp_fmt_uint(1536)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "1.5 KB")); -} - -UTEST_F(format, ordinal_1st) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.ordinal}", sp_fmt_int(1)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "1st")); -} - -UTEST_F(format, ordinal_2nd) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.ordinal}", sp_fmt_int(2)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "2nd")); -} - -UTEST_F(format, ordinal_3rd) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.ordinal}", sp_fmt_int(3)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "3rd")); -} - -UTEST_F(format, ordinal_4th) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.ordinal}", sp_fmt_int(4)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "4th")); -} - -UTEST_F(format, ordinal_11th) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.ordinal}", sp_fmt_int(11)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "11th")); -} - -UTEST_F(format, ordinal_12th) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.ordinal}", sp_fmt_int(12)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "12th")); -} - -UTEST_F(format, ordinal_13th) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.ordinal}", sp_fmt_int(13)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "13th")); -} - -UTEST_F(format, ordinal_21st) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.ordinal}", sp_fmt_int(21)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "21st")); -} - -UTEST_F(format, ordinal_negative) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.ordinal}", sp_fmt_int(-1)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "-1st")); -} - -UTEST_F(format, duration_sub_us) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.duration}", sp_fmt_uint(999)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "999 ns")); -} - -UTEST_F(format, duration_exact_us) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.duration}", sp_fmt_uint(1000)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "1 us")); -} - -UTEST_F(format, duration_fractional_ms) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.duration}", sp_fmt_uint(1500000)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "1.5 ms")); -} - -UTEST_F(format, duration_exact_s) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.duration}", sp_fmt_uint(1000000000)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "1 s")); -} - -UTEST_F(format, iso_epoch_zero) { - sp_str_t got = sp_fmt(sp_mem_get_scratch(), "{.iso}", sp_fmt_uint(0)).value; - EXPECT_TRUE(sp_str_equal_cstr(got, "1970-01-01T00:00:00.000Z")); -} - +#include "format/spec.c" +#include "format/fmt.c" +#include "format/builtins.c" +#include "format/custom.c" +#include "format/parse.c" +#include "format/writers.c" diff --git a/test/format/builtins.c b/test/format/builtins.c new file mode 100644 index 0000000..b49cc65 --- /dev/null +++ b/test/format/builtins.c @@ -0,0 +1,236 @@ +#include "format.h" + +UTEST(format_builtin, color_style) { + format_fmt_test_t cases[] = { + { + .fmt = "{.red}", + .args = { sp_fmt_cstr("hi") }, + .expect = "\033[31mhi\033[0m" + }, + { + .fmt = "{.br_cyan}", + .args = { sp_fmt_cstr("hi") }, + .expect = "\033[96mhi\033[0m" + }, + { + .fmt = "{.italic}", + .args = { sp_fmt_cstr("hi") }, + .expect = "\033[3mhi\033[0m" + }, + { + .fmt = "{.gray}", + .args = { sp_fmt_cstr("hi") }, + .expect = "\033[90mhi\033[0m" + }, + { + .fmt = "{.br_red}", + .args = { sp_fmt_cstr("hi") }, + .expect = "\033[91mhi\033[0m" + }, + { + .fmt = "{.bold}", + .args = { sp_fmt_int(42) }, + .expect = "\033[1m42\033[0m" + }, + { + .fmt = "{.hyperlink}", + .args = { sp_fmt_cstr("https://x") }, + .expect = "\033]8;;https://x\033\\https://x\033[0m" + }, + { + .fmt = "{.quote}", + .args = { sp_fmt_cstr("hi") }, + .expect = "\"hi\"" + }, + { + .fmt = "{.quote .red}", + .args = { sp_fmt_cstr("hi") }, + .expect = "\"\033[31mhi\033[0m\"" + }, + { + .fmt = "{:*^8 .red}", + .args = { sp_fmt_cstr("hi") }, + .expect = "***\033[31mhi\033[0m***" + }, + { + .fmt = "{.$}", + .args = { sp_fmt_red(), sp_fmt_cstr("hi") }, + .expect = "\033[31mhi\033[0m" + }, + { + .fmt = "{.$}", + .args = { sp_fmt_bold(), sp_fmt_int(42) }, + .expect = "\033[1m42\033[0m" + }, + { + .fmt = "{.$ .bold}", + .args = { sp_fmt_green(), sp_fmt_cstr("hi") }, + .expect = "\033[32m\033[1mhi\033[0m\033[0m" + }, + { + .fmt = "{:*^8 .$}", + .args = { sp_fmt_cyan(), sp_fmt_cstr("hi") }, + .expect = "***\033[36mhi\033[0m***" + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +UTEST(format_builtin, bytes) { + format_fmt_test_t cases[] = { + { + .fmt = "{:B}", + .args = { sp_fmt_uint(2560) }, + .expect = "2.5 KB" + }, + { + .fmt = "{:9B}", + .args = { sp_fmt_uint(2560) }, + .expect = " 2.5 KB" + }, + { + .fmt = "{:B}", + .args = { sp_fmt_cstr("hello") }, + .expect = "5 B" + }, + { + .fmt = "{:B}", + .args = { sp_fmt_int(2560) }, + .expect = "2.5 KB" + }, + { + .fmt = "{:B}", + .args = { sp_fmt_int(-5) }, + .expect = "0 B" + }, + { + .fmt = "{:B}", + .args = { sp_fmt_float(2560.0) }, + .expect = "2.5 KB" + }, + { + .fmt = "{:B}", + .args = { sp_fmt_float(-1.0) }, + .expect = "0 B" + }, + { + .fmt = "{:B}", + .args = { sp_fmt_ptr(SP_NULLPTR) }, + .err = SP_ERR_FMT_BAD_ARG + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +UTEST(format_builtin, radix) { + format_fmt_test_t cases[] = { + { + .fmt = "{:x}", + .args = { sp_fmt_uint(255) }, + .expect = "ff" + }, + { + .fmt = "{:X}", + .args = { sp_fmt_uint(0xdeadbeef) }, + .expect = "DEADBEEF" + }, + { + .fmt = "0x{:0>8x}", + .args = { sp_fmt_uint(0xabcd) }, + .expect = "0x0000abcd" + }, + { + .fmt = "{:b}", + .args = { sp_fmt_uint(5) }, + .expect = "101" + }, + { + .fmt = "{:o}", + .args = { sp_fmt_uint(8) }, + .expect = "10" + }, + { + .fmt = "{:x}", + .args = { sp_fmt_int(255) }, + .expect = "ff" + }, + { + .fmt = "{:x}", + .args = { sp_fmt_int(-1) }, + .expect = "ffffffffffffffff" + }, + { + .fmt = "{:>6x .cyan}", + .args = { sp_fmt_uint(255) }, + .expect = " \033[36mff\033[0m" + }, + { + .fmt = "{:x}", + .args = { sp_fmt_cstr("nope") }, + .err = SP_ERR_FMT_BAD_ARG + }, + { + .fmt = "{:x}", + .args = { sp_fmt_float(1.0) }, + .err = SP_ERR_FMT_BAD_ARG + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +UTEST(format_builtin, character) { + format_fmt_test_t cases[] = { + { + .fmt = "{:c}", + .args = { sp_fmt_uint(65) }, + .expect = "A" + }, + { + .fmt = "{:c}{:c}{:c}", + .args = { sp_fmt_uint('a'), sp_fmt_uint('b'), sp_fmt_uint('c') }, + .expect = "abc" + }, + { + .fmt = "{:c}", + .args = { sp_fmt_int(122) }, + .expect = "z" + }, + { + .fmt = "[{:*^5c}]", + .args = { sp_fmt_uint('Q') }, + .expect = "[**Q**]" + }, + { + .fmt = "{:c}", + .args = { sp_fmt_cstr("x") }, + .err = SP_ERR_FMT_BAD_ARG + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +UTEST(format_builtin, unknown_tag) { + format_fmt_test_t cases[] = { + { + .fmt = "{.bogus}", + .args = { sp_fmt_cstr("hi") }, + .err = SP_ERR_FMT_UNKNOWN_DIRECTIVE + }, + { + .fmt = "{.$}", + .args = { sp_fmt_style(9999), sp_fmt_cstr("hi") }, + .err = SP_ERR_FMT_UNKNOWN_DIRECTIVE + }, + { + .fmt = "{.$}", + .args = { sp_fmt_style(-1), sp_fmt_cstr("hi") }, + .err = SP_ERR_FMT_UNKNOWN_DIRECTIVE + }, + { + .fmt = "{.$}", + .args = { sp_fmt_style(sp_fmt_style_unknown), sp_fmt_cstr("hi") }, + .err = SP_ERR_FMT_UNKNOWN_DIRECTIVE + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} diff --git a/test/format/custom.c b/test/format/custom.c new file mode 100644 index 0000000..ac16bee --- /dev/null +++ b/test/format/custom.c @@ -0,0 +1,40 @@ +#include "format.h" + +static sp_err_t render_hex(sp_io_writer_t* io, sp_fmt_arg_t* arg) { + return sp_fmt_write_u64_ex(io, arg->value.u, SP_FMT_RADIX_HEX); +} + +UTEST(format_custom, fn) { + format_fmt_test_t cases[] = { + { + .fmt = "{}", + .args = { sp_fmt_u64_custom(255, render_hex) }, + .expect = "ff" + }, + { + .fmt = "{:>4}", + .args = { sp_fmt_u64_custom(255, render_hex) }, + .expect = " ff" + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +typedef struct { s32 x; s32 y; } point_t; + +static sp_err_t render_point(sp_io_writer_t* io, sp_fmt_arg_t* arg) { + point_t* p = (point_t*)arg->value.p; + sp_io_write_c8(io, '('); + sp_fmt_write_s64(io, p->x); + sp_io_write_cstr(io, ", ", SP_NULLPTR); + sp_fmt_write_s64(io, p->y); + return sp_io_write_c8(io, ')'); +} + +#define sp_fmt_point(_p) sp_fmt_custom(point_t, render_point, (_p)) + +UTEST(format_custom, by_pointer) { + point_t pt = { 3, 4 }; + SP_EXPECT_STR_EQ_CSTR(sp_fmt(sp_mem_get_scratch(), "{}", sp_fmt_point(pt)).value, "(3, 4)"); + SP_EXPECT_STR_EQ_CSTR(sp_fmt(sp_mem_get_scratch(), "{:>8}", sp_fmt_point(pt)).value, " (3, 4)"); +} diff --git a/test/format/fmt.c b/test/format/fmt.c new file mode 100644 index 0000000..2495081 --- /dev/null +++ b/test/format/fmt.c @@ -0,0 +1,259 @@ +#include "format.h" + +UTEST(format_fmt, basic) { + format_fmt_test_t cases[] = { + { + "hello, world", + sp_zero, + "hello, world" + }, + { + "{}", + { sp_fmt_int(42) }, + "42" + }, + { + "{}", + { sp_fmt_cstr("world") }, + "world" + }, + { + "{} + {} = {}", + { sp_fmt_int(2), sp_fmt_int(3), sp_fmt_int(5) }, + "2 + 3 = 5" + }, + { + "hello, {}!", + { sp_fmt_cstr("thomas") }, + "hello, thomas!" + }, + { + "{:6}", + { sp_fmt_int(42) }, + " 42" + }, + { + "{:*^9}", + { sp_fmt_int(42) }, + "***42****" + }, + { + "{{{}}}", + { sp_fmt_int(7) }, + "{7}" + }, + { + "hello }} world", + sp_zero, + "hello } world" + }, + { + "[{:->8}]", + { sp_fmt_cstr("hi") }, + "[------hi]" + }, + { + "{{{:5}}}", + { sp_fmt_int(42) }, + "{ 42}" + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +UTEST(format_fmt, dynamic) { + format_fmt_test_t cases[] = { + { + "{:$}", + { sp_fmt_int(6), sp_fmt_int(42) }, + " 42" + }, + { + "{:$^9}", + { sp_fmt_int('*'), sp_fmt_int(42) }, + "***42****" + }, + { + "{:$^$}", + { sp_fmt_int('-'), sp_fmt_int(8), sp_fmt_cstr("hi") }, + "---hi---" + }, + { + "{:.$}", + { sp_fmt_int(3), sp_fmt_float(3.14159) }, + "3.142" + }, + { + "{:$.2}", + { sp_fmt_int(8), sp_fmt_float(1.5) }, + " 1.50" + }, + { + "{:$<$.$}", + { sp_fmt_int('*'), sp_fmt_int(8), sp_fmt_int(2), sp_fmt_float(1.5) }, + "1.50****" + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +UTEST(format_fmt, precision) { + format_fmt_test_t cases[] = { + { + "{:.3}", + { sp_fmt_cstr("hello") }, + "hel" + }, + { + "{:.10}", + { sp_fmt_cstr("hi") }, + "hi" + }, + { + "{:.$}", + { sp_fmt_int(2), sp_fmt_cstr("hello") }, + "he" + }, + { + "[{:>6.3}]", + { sp_fmt_cstr("hello") }, + "[ hel]" + }, + { + "{:.0}", + { sp_fmt_float(3.7) }, + "4" + }, + { + "{:.$}", + { sp_fmt_int(0), sp_fmt_float(3.7) }, + "4" + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +UTEST(format_fmt, alignment) { + format_fmt_test_t cases[] = { + { + "{:>16}", + { sp_fmt_ptr((void*)(uintptr_t)0xabcdULL) }, + " 0xabcd" + }, + { + "{:>8}", + { sp_fmt_int(-42) }, + " -42" + }, + { + "{:0>8}", + { sp_fmt_int(-42) }, + "00000-42" + }, + { + "{:*^6}", + { sp_fmt_cstr("") }, + "******" + }, + { + "{:>10.2}", + { sp_fmt_float(3.14159) }, + " 3.14" + }, + { + "{:*^10.2}", + { sp_fmt_float(1.5) }, + "***1.50***" + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +UTEST(format_fmt, width_clamp) { + format_fmt_test_t cases[] = { + { + .fmt = "{:99999}", + .args = { sp_fmt_cstr("x") }, + .expect_len = SP_FMT_WIDTH_MAX + }, + { + .fmt = "{:$}", + .args = { sp_fmt_uint(999999999ULL), sp_fmt_cstr("x") }, + .expect_len = SP_FMT_WIDTH_MAX + }, + { + .fmt = "{:$}", + .args = { sp_fmt_int(-5), sp_fmt_cstr("hi") }, + .expect = "hi" + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +UTEST(format_fmt, errors) { + format_fmt_test_t cases[] = { + { + .fmt = "oops } here", + .args = sp_zero, + .err = SP_ERR_FMT_BAD_PLACEHOLDER + }, + { + .fmt = "a {:5.} b {}", + .args = { sp_fmt_int(99) }, + .err = SP_ERR_FMT_BAD_PRECISION + }, + { + .fmt = "hi {nope", + .args = { sp_fmt_int(1) }, + .err = SP_ERR_FMT_BAD_PLACEHOLDER + }, + { + .fmt = "{:$^5}", + .args = { sp_fmt_float(1.0), sp_fmt_int(42) }, + .err = SP_ERR_FMT_WRONG_FILL_KIND + }, + { + .fmt = "{:$}", + .args = { sp_fmt_cstr("oops"), sp_fmt_int(42) }, + .err = SP_ERR_FMT_WRONG_WIDTH_KIND + }, + { + .fmt = "{:.$}", + .args = { sp_fmt_float(3.0), sp_fmt_float(3.14) }, + .err = SP_ERR_FMT_WRONG_PRECISION_KIND + }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} + +UTEST(format_fmt, writer_variants) { + c8 buffer[64] = sp_zero; + SP_EXPECT_STR_EQ_CSTR(__sp_fmt_buf(buffer, 64, "hello, {}", sp_fmt_cstr("world")).value, "hello, world"); + + c8 decorated[64] = sp_zero; + SP_EXPECT_STR_EQ_CSTR(__sp_fmt_buf(decorated, 64, "{:>6 .red}", sp_fmt_cstr("hi")).value, " \033[31mhi\033[0m"); + + c8 multi[64] = sp_zero; + sp_io_mem_writer_t w = sp_zero; + sp_io_mem_writer_from_buffer(&w, multi, sizeof(multi)); + sp_fmt_io(&w.base, "[{}]", sp_fmt_cstr("a")); + sp_io_write_c8(&w.base, ' '); + sp_fmt_io(&w.base, "[{}]", sp_fmt_cstr("b")); + SP_EXPECT_STR_EQ_CSTR(sp_io_mem_writer_as_str(&w), "[a] [b]"); +} + +static f64 format_f64_from_bits(u64 bits) { + union { f64 f; u64 u; } b = { .u = bits }; + return b.f; +} + +UTEST(format_fmt, float_default) { + format_fmt_test_t cases[] = { + { "{}", { sp_fmt_float(1.5) }, "1.500000" }, + { "{}", { sp_fmt_float(0.9999995) }, "1.000000" }, + { "{}", { sp_fmt_float(format_f64_from_bits(0x7ff8000000000000ULL)) }, "nan" }, + { "{}", { sp_fmt_float(format_f64_from_bits(0x7ff0000000000000ULL)) }, "inf" }, + { "{}", { sp_fmt_float(format_f64_from_bits(0xfff0000000000000ULL)) }, "-inf" }, + }; + SP_CARR_FOR(cases, i) run_format_fmt(utest_result, cases[i]); +} diff --git a/test/format/format.h b/test/format/format.h new file mode 100644 index 0000000..594e7ec --- /dev/null +++ b/test/format/format.h @@ -0,0 +1,28 @@ +#ifndef FORMAT_TEST_H +#define FORMAT_TEST_H + +#include "sp.h" +#include "test.h" +#include "utest.h" + +typedef struct { + const c8* fmt; + sp_fmt_argv_t args [4]; + const c8* expect; + sp_err_t err; + u32 expect_len; +} format_fmt_test_t; + +static void run_format_fmt(s32* utest_result, format_fmt_test_t t) { + sp_str_r result = sp_fmt(sp_mem_get_scratch(), t.fmt, t.args[0], t.args[1], t.args[2], t.args[3]); + EXPECT_EQ(result.err, t.err); + if (t.err != SP_OK) return; + if (t.expect_len) { + EXPECT_EQ(result.value.len, t.expect_len); + } + else { + SP_EXPECT_STR_EQ_CSTR(result.value, t.expect); + } +} + +#endif // FORMAT_TEST_H diff --git a/test/format/parse.c b/test/format/parse.c new file mode 100644 index 0000000..d143758 --- /dev/null +++ b/test/format/parse.c @@ -0,0 +1,249 @@ +#include "format.h" + +typedef enum { + PARSE_U8, + PARSE_U16, + PARSE_U32, + PARSE_U64, + PARSE_S8, + PARSE_S16, + PARSE_S32, + PARSE_S64, + PARSE_F32, + PARSE_HEX, + PARSE_HASH, + PARSE_BOOL, + PARSE_C8, + PARSE_C16, +} format_parse_kind_t; + +typedef struct { + format_parse_kind_t kind; + const c8* input; + u64 u; + s64 i; + f64 f; +} format_parse_test_t; + +static void run_format_parse_test(s32* utest_result, format_parse_test_t t) { + sp_str_t in = sp_str_view(t.input); + switch (t.kind) { + case PARSE_U8: EXPECT_EQ(sp_parse_u8(in), (u8)t.u); break; + case PARSE_U16: EXPECT_EQ(sp_parse_u16(in), (u16)t.u); break; + case PARSE_U32: EXPECT_EQ(sp_parse_u32(in), (u32)t.u); break; + case PARSE_U64: EXPECT_EQ(sp_parse_u64(in), t.u); break; + case PARSE_S8: EXPECT_EQ(sp_parse_s8(in), (s8)t.i); break; + case PARSE_S16: EXPECT_EQ(sp_parse_s16(in), (s16)t.i); break; + case PARSE_S32: EXPECT_EQ(sp_parse_s32(in), (s32)t.i); break; + case PARSE_S64: EXPECT_EQ(sp_parse_s64(in), t.i); break; + case PARSE_F32: EXPECT_NEAR(sp_parse_f32(in), (f32)t.f, 1e-5f); break; + case PARSE_HEX: EXPECT_EQ(sp_parse_hex(in), t.u); break; + case PARSE_HASH: EXPECT_EQ(sp_parse_hash(in), (sp_hash_t)t.u); break; + case PARSE_BOOL: EXPECT_EQ(sp_parse_bool(in), (bool)t.u); break; + case PARSE_C8: EXPECT_EQ(sp_parse_c8(in), (c8)t.u); break; + case PARSE_C16: EXPECT_EQ(sp_parse_c16(in), (u16)t.u); break; + } +} + +typedef struct { + format_parse_kind_t kind; + const c8* input; + bool ok; + u64 u; + s64 i; + f64 f; +} format_parse_ex_test_t; + +static void run_format_parse_ex_test(s32* utest_result, format_parse_ex_test_t t) { + sp_str_t in = sp_str_view(t.input); + switch (t.kind) { + case PARSE_U8: { u8 v; EXPECT_EQ(sp_parse_u8_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, (u8)t.u); break; } + case PARSE_U16: { u16 v; EXPECT_EQ(sp_parse_u16_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, (u16)t.u); break; } + case PARSE_U32: { u32 v; EXPECT_EQ(sp_parse_u32_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, (u32)t.u); break; } + case PARSE_U64: { u64 v; EXPECT_EQ(sp_parse_u64_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, t.u); break; } + case PARSE_S8: { s8 v; EXPECT_EQ(sp_parse_s8_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, (s8)t.i); break; } + case PARSE_S16: { s16 v; EXPECT_EQ(sp_parse_s16_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, (s16)t.i); break; } + case PARSE_S32: { s32 v; EXPECT_EQ(sp_parse_s32_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, (s32)t.i); break; } + case PARSE_S64: { s64 v; EXPECT_EQ(sp_parse_s64_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, t.i); break; } + case PARSE_F32: { f32 v; EXPECT_EQ(sp_parse_f32_ex(in, &v), t.ok); if (t.ok) EXPECT_NEAR(v, (f32)t.f, 1e-5f); break; } + case PARSE_HEX: { u64 v; EXPECT_EQ(sp_parse_hex_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, t.u); break; } + case PARSE_HASH: { sp_hash_t v; EXPECT_EQ(sp_parse_hash_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, (sp_hash_t)t.u); break; } + case PARSE_BOOL: { bool v; EXPECT_EQ(sp_parse_bool_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, (bool)t.u); break; } + case PARSE_C8: { c8 v; EXPECT_EQ(sp_parse_c8_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, (c8)t.u); break; } + case PARSE_C16: { u16 v; EXPECT_EQ(sp_parse_c16_ex(in, &v), t.ok); if (t.ok) EXPECT_EQ(v, (u16)t.u); break; } + } +} + +UTEST(format_parse, unsigned_integers) { + format_parse_test_t cases[] = { + { .kind = PARSE_U8, .input = "0", .u = 0 }, + { .kind = PARSE_U8, .input = "255", .u = 255 }, + { .kind = PARSE_U8, .input = "128", .u = 128 }, + { .kind = PARSE_U8, .input = "42", .u = 42 }, + { .kind = PARSE_U16, .input = "0", .u = 0 }, + { .kind = PARSE_U16, .input = "65535", .u = 65535 }, + { .kind = PARSE_U16, .input = "32768", .u = 32768 }, + { .kind = PARSE_U16, .input = "1234", .u = 1234 }, + { .kind = PARSE_U32, .input = "0", .u = 0 }, + { .kind = PARSE_U32, .input = "4294967295", .u = 4294967295U }, + { .kind = PARSE_U32, .input = "2147483648", .u = 2147483648U }, + { .kind = PARSE_U32, .input = "123456789", .u = 123456789U }, + { .kind = PARSE_U64, .input = "0", .u = 0ULL }, + { .kind = PARSE_U64, .input = "18446744073709551615", .u = 18446744073709551615ULL }, + { .kind = PARSE_U64, .input = "9223372036854775808", .u = 9223372036854775808ULL }, + { .kind = PARSE_U64, .input = "1234567890123", .u = 1234567890123ULL }, + }; + SP_CARR_FOR(cases, i) run_format_parse_test(utest_result, cases[i]); +} + +UTEST(format_parse, signed_integers) { + format_parse_test_t cases[] = { + { .kind = PARSE_S8, .input = "0", .i = 0 }, + { .kind = PARSE_S8, .input = "127", .i = 127 }, + { .kind = PARSE_S8, .input = "-128", .i = -128 }, + { .kind = PARSE_S8, .input = "-42", .i = -42 }, + { .kind = PARSE_S8, .input = "42", .i = 42 }, + { .kind = PARSE_S16, .input = "0", .i = 0 }, + { .kind = PARSE_S16, .input = "32767", .i = 32767 }, + { .kind = PARSE_S16, .input = "-32768", .i = -32768 }, + { .kind = PARSE_S16, .input = "-1234", .i = -1234 }, + { .kind = PARSE_S16, .input = "1234", .i = 1234 }, + { .kind = PARSE_S32, .input = "0", .i = 0 }, + { .kind = PARSE_S32, .input = "2147483647", .i = 2147483647 }, + { .kind = PARSE_S32, .input = "-2147483648", .i = INT32_MIN }, + { .kind = PARSE_S32, .input = "-123456789", .i = -123456789 }, + { .kind = PARSE_S32, .input = "123456789", .i = 123456789 }, + { .kind = PARSE_S64, .input = "0", .i = 0LL }, + { .kind = PARSE_S64, .input = "9223372036854775807", .i = 9223372036854775807LL }, + { .kind = PARSE_S64, .input = "-9223372036854775808", .i = INT64_MIN }, + { .kind = PARSE_S64, .input = "-1234567890123", .i = -1234567890123LL }, + { .kind = PARSE_S64, .input = "1234567890123", .i = 1234567890123LL }, + }; + SP_CARR_FOR(cases, i) run_format_parse_test(utest_result, cases[i]); +} + +UTEST(format_parse, floating_point) { + format_parse_test_t cases[] = { + { .kind = PARSE_F32, .input = "0", .f = 0.0 }, + { .kind = PARSE_F32, .input = "0.0", .f = 0.0 }, + { .kind = PARSE_F32, .input = "3.14159", .f = 3.14159 }, + { .kind = PARSE_F32, .input = "-3.14159",.f = -3.14159 }, + { .kind = PARSE_F32, .input = "1.23e2", .f = 123.0 }, + { .kind = PARSE_F32, .input = "1.23e-2", .f = 0.0123 }, + { .kind = PARSE_F32, .input = "-1.23e2", .f = -123.0 }, + { .kind = PARSE_F32, .input = "42", .f = 42.0 }, + { .kind = PARSE_F32, .input = "-42", .f = -42.0 }, + }; + SP_CARR_FOR(cases, i) run_format_parse_test(utest_result, cases[i]); +} + +UTEST(format_parse, hex) { + format_parse_test_t cases[] = { + { .kind = PARSE_HEX, .input = "0", .u = 0ULL }, + { .kind = PARSE_HEX, .input = "F", .u = 0xFULL }, + { .kind = PARSE_HEX, .input = "f", .u = 0xfULL }, + { .kind = PARSE_HEX, .input = "FF", .u = 0xFFULL }, + { .kind = PARSE_HEX, .input = "ff", .u = 0xffULL }, + { .kind = PARSE_HEX, .input = "DEADBEEF", .u = 0xDEADBEEFULL }, + { .kind = PARSE_HEX, .input = "deadbeef", .u = 0xdeadbeefULL }, + { .kind = PARSE_HEX, .input = "123ABC", .u = 0x123ABCULL }, + { .kind = PARSE_HEX, .input = "FFFFFFFFFFFFFFFF", .u = 0xFFFFFFFFFFFFFFFFULL }, + }; + SP_CARR_FOR(cases, i) run_format_parse_test(utest_result, cases[i]); +} + +UTEST(format_parse, hash) { + format_parse_test_t cases[] = { + { .kind = PARSE_HASH, .input = "0", .u = 0U }, + { .kind = PARSE_HASH, .input = "FFFFFFFF", .u = 0xFFFFFFFFU }, + { .kind = PARSE_HASH, .input = "12345678", .u = 0x12345678U }, + { .kind = PARSE_HASH, .input = "DEADBEEF", .u = 0xDEADBEEFU }, + { .kind = PARSE_HASH, .input = "deadbeef", .u = 0xdeadbeefU }, + { .kind = PARSE_HASH, .input = "ABCD", .u = 0xABCDU }, + }; + SP_CARR_FOR(cases, i) run_format_parse_test(utest_result, cases[i]); +} + +UTEST(format_parse, boolean) { + format_parse_test_t cases[] = { + { .kind = PARSE_BOOL, .input = "true", .u = true }, + { .kind = PARSE_BOOL, .input = "false", .u = false }, + { .kind = PARSE_BOOL, .input = "1", .u = true }, + { .kind = PARSE_BOOL, .input = "0", .u = false }, + }; + SP_CARR_FOR(cases, i) run_format_parse_test(utest_result, cases[i]); +} + +UTEST(format_parse, characters) { + format_parse_test_t cases[] = { + { .kind = PARSE_C8, .input = "'A'", .u = 'A' }, + { .kind = PARSE_C8, .input = "'z'", .u = 'z' }, + { .kind = PARSE_C8, .input = "'0'", .u = '0' }, + { .kind = PARSE_C8, .input = "' '", .u = ' ' }, + { .kind = PARSE_C8, .input = "'!'", .u = '!' }, + { .kind = PARSE_C16, .input = "'A'", .u = L'A' }, + { .kind = PARSE_C16, .input = "'z'", .u = L'z' }, + { .kind = PARSE_C16, .input = "'0'", .u = L'0' }, + { .kind = PARSE_C16, .input = "' '", .u = L' ' }, + { .kind = PARSE_C16, .input = "'!'", .u = L'!' }, + }; + SP_CARR_FOR(cases, i) run_format_parse_test(utest_result, cases[i]); +} + +UTEST(format_parse, lenient) { + format_parse_test_t cases[] = { + { .kind = PARSE_U32, .input = "00042", .u = 42U }, + { .kind = PARSE_S32, .input = "-00042", .i = -42 }, + { .kind = PARSE_F32, .input = "003.14", .f = 3.14 }, + { .kind = PARSE_S32, .input = "+42", .i = 42 }, + { .kind = PARSE_F32, .input = "+3.14", .f = 3.14 }, + { .kind = PARSE_HEX, .input = "DeAdBeEf", .u = 0xdeadbeefULL }, + }; + SP_CARR_FOR(cases, i) run_format_parse_test(utest_result, cases[i]); +} + +UTEST(format_parse, extended) { + format_parse_ex_test_t cases[] = { + { .kind = PARSE_U8, .input = "255", .ok = true, .u = 255 }, + { .kind = PARSE_U8, .input = "256", .ok = false }, + { .kind = PARSE_U16, .input = "65535", .ok = true, .u = 65535 }, + { .kind = PARSE_U16, .input = "65536", .ok = false }, + { .kind = PARSE_U32, .input = "42", .ok = true, .u = 42U }, + { .kind = PARSE_U32, .input = "4294967296", .ok = false }, + { .kind = PARSE_U32, .input = "-1", .ok = false }, + { .kind = PARSE_U32, .input = "abc", .ok = false }, + { .kind = PARSE_U32, .input = "", .ok = false }, + { .kind = PARSE_U64, .input = "18446744073709551615", .ok = true, .u = 18446744073709551615ULL }, + { .kind = PARSE_U64, .input = "not_a_number", .ok = false }, + { .kind = PARSE_S8, .input = "-128", .ok = true, .i = -128 }, + { .kind = PARSE_S8, .input = "-129", .ok = false }, + { .kind = PARSE_S16, .input = "32767", .ok = true, .i = 32767 }, + { .kind = PARSE_S16, .input = "32768", .ok = false }, + { .kind = PARSE_S32, .input = "42", .ok = true, .i = 42 }, + { .kind = PARSE_S32, .input = "2147483648", .ok = false }, + { .kind = PARSE_S32, .input = "-2147483649", .ok = false }, + { .kind = PARSE_S32, .input = "text", .ok = false }, + { .kind = PARSE_S32, .input = "", .ok = false }, + { .kind = PARSE_S64, .input = "9223372036854775807", .ok = true, .i = 9223372036854775807LL }, + { .kind = PARSE_S64, .input = "invalid", .ok = false }, + { .kind = PARSE_F32, .input = "3.14", .ok = true, .f = 3.14 }, + { .kind = PARSE_F32, .input = "abc", .ok = false }, + { .kind = PARSE_F32, .input = "", .ok = false }, + { .kind = PARSE_BOOL, .input = "true", .ok = true, .u = true }, + { .kind = PARSE_BOOL, .input = "maybe", .ok = false }, + { .kind = PARSE_BOOL, .input = "", .ok = false }, + { .kind = PARSE_HEX, .input = "DEADBEEF", .ok = true, .u = 0xDEADBEEFULL }, + { .kind = PARSE_HEX, .input = "XYZ", .ok = false }, + { .kind = PARSE_HEX, .input = "", .ok = false }, + { .kind = PARSE_HASH, .input = "DEADBEEF", .ok = true, .u = 0xDEADBEEF }, + { .kind = PARSE_HASH, .input = "GHIJKLMN", .ok = false }, + { .kind = PARSE_HASH, .input = "", .ok = false }, + { .kind = PARSE_C8, .input = "'A'", .ok = true, .u = 'A' }, + { .kind = PARSE_C8, .input = "AB", .ok = false }, + { .kind = PARSE_C8, .input = "", .ok = false }, + { .kind = PARSE_C16, .input = "'Z'", .ok = true, .u = L'Z' }, + { .kind = PARSE_C16, .input = "XY", .ok = false }, + { .kind = PARSE_C16, .input = "", .ok = false }, + }; + SP_CARR_FOR(cases, i) run_format_parse_ex_test(utest_result, cases[i]); +} diff --git a/test/format/spec.c b/test/format/spec.c new file mode 100644 index 0000000..418856f --- /dev/null +++ b/test/format/spec.c @@ -0,0 +1,104 @@ +#include "format.h" + +typedef struct { + const c8* str; + sp_err_t err; + sp_fmt_spec_t spec; + const c8* names [SP_FMT_MAX_DIRECTIVES]; +} format_spec_test_t; + +static sp_err_t format_parse_spec(const c8* str, sp_fmt_spec_t* spec) { + sp_fmt_parser_t parser = { .str = sp_str_view(str), .i = 0 }; + return sp_fmt_parse_specifier(&parser, spec); +} + +static void run_format_spec(s32* utest_result, format_spec_test_t t) { + sp_fmt_spec_t spec = sp_zero; + sp_err_t err = format_parse_spec(t.str, &spec); + EXPECT_EQ(err, t.err); + if (err != SP_OK) return; + + EXPECT_EQ(spec.align, t.spec.align); + EXPECT_EQ(spec.fill, t.spec.fill); + EXPECT_EQ(spec.precision.some, t.spec.precision.some); + EXPECT_EQ(spec.precision.value, t.spec.precision.value); + EXPECT_EQ(spec.width, t.spec.width); + EXPECT_EQ(spec.renderer, t.spec.renderer); + + u32 num = 0; + while (num < SP_FMT_MAX_DIRECTIVES && (t.names[num] || (t.spec.dynamic & sp_fmt_dynamic_directive(num)))) num++; + EXPECT_EQ(spec.directive.num, num); + EXPECT_EQ(spec.dynamic, t.spec.dynamic); + + sp_for(i, spec.directive.num) { + if (t.names[i]) { + EXPECT_TRUE(sp_str_equal_cstr(spec.directive.names[i], t.names[i])); + } + } +} + +UTEST(format_spec, fields) { + format_spec_test_t cases[] = { + { .str = "{:*^9}", .spec = { .width = 9, .align = SP_FMT_ALIGN_CENTER, .fill = '*' } }, + { .str = "{:.5}", .spec = { .precision = sp_opt_some(5) } }, + { .str = "{:10.3}", .spec = { .width = 10, .precision = sp_opt_some(3) } }, + { .str = "{:<7}", .spec = { .width = 7, .align = SP_FMT_ALIGN_LEFT } }, + { .str = "{:>4}", .spec = { .width = 4, .align = SP_FMT_ALIGN_RIGHT } }, + { .str = "{:-<8}", .spec = { .width = 8, .align = SP_FMT_ALIGN_LEFT, .fill = '-' } }, + { .str = "{:*^12.4}", .spec = { .width = 12, .align = SP_FMT_ALIGN_CENTER, .fill = '*', .precision = sp_opt_some(4) } }, + { .str = "{:05}", .spec = { .width = 5 } }, + { .str = "{:$}", .spec = { .dynamic = SP_FMT_DYNAMIC_WIDTH } }, + { .str = "{:.$}", .spec = { .dynamic = SP_FMT_DYNAMIC_PRECISION } }, + { .str = "{:$^9}", .spec = { .width = 9, .align = SP_FMT_ALIGN_CENTER, .dynamic = SP_FMT_DYNAMIC_FILL } }, + { .str = "{:$^$}", .spec = { .align = SP_FMT_ALIGN_CENTER, .dynamic = SP_FMT_DYNAMIC_FILL | SP_FMT_DYNAMIC_WIDTH } }, + { .str = "{:$<$.$}", .spec = { .align = SP_FMT_ALIGN_LEFT, .dynamic = SP_FMT_DYNAMIC_FILL | SP_FMT_DYNAMIC_WIDTH | SP_FMT_DYNAMIC_PRECISION } }, + { .str = "{:$.4}", .spec = { .precision = sp_opt_some(4), .dynamic = SP_FMT_DYNAMIC_WIDTH } }, + { .str = "{}" }, + { .str = "{:}" }, + { .str = "{:B}", .spec = { .renderer = 'B' } }, + { .str = "{:9B}", .spec = { .renderer = 'B', .width = 9 } }, + { .str = "{:x}", .spec = { .renderer = 'x' } }, + { .str = "{:X}", .spec = { .renderer = 'X' } }, + { .str = "{:b}", .spec = { .renderer = 'b' } }, + { .str = "{:o}", .spec = { .renderer = 'o' } }, + { .str = "{:c}", .spec = { .renderer = 'c' } }, + { .str = "{:0>8x}", .spec = { .renderer = 'x', .width = 8, .align = SP_FMT_ALIGN_RIGHT, .fill = '0' } }, + { .str = "{:x .red}", .spec = { .renderer = 'x' }, .names = { "red" } }, + { .str = "{:>6x .red}", .spec = { .renderer = 'x', .width = 6, .align = SP_FMT_ALIGN_RIGHT }, .names = { "red" } }, + { .str = "{.red}", .names = { "red" } }, + { .str = "{:10 .red}", .spec = { .width = 10 }, .names = { "red" } }, + { + .str = "{:*^9.2 .red}", + .spec = { .width = 9, .align = SP_FMT_ALIGN_CENTER, .fill = '*', .precision = sp_opt_some(2) }, + .names = { "red" }, + }, + { .str = "{.$}", .spec = { .dynamic = sp_fmt_dynamic_directive(0) } }, + { .str = "{.red .$}", .spec = { .dynamic = sp_fmt_dynamic_directive(1) }, .names = { "red" } }, + { .str = "{.$ .red}", .spec = { .dynamic = sp_fmt_dynamic_directive(0) }, .names = { SP_NULLPTR, "red" } }, + { .str = "{:>6x .$}", .spec = { .renderer = 'x', .width = 6, .align = SP_FMT_ALIGN_RIGHT, .dynamic = sp_fmt_dynamic_directive(0) } }, + { .str = "{.$x}", .err = SP_ERR_FMT_BAD_PLACEHOLDER }, + { .str = "{.$ .$}", .spec = { .dynamic = sp_fmt_dynamic_directive(0) | sp_fmt_dynamic_directive(1) } }, + { .str = "{.red .bold .italic}", .names = { "red", "bold", "italic" } }, + { .str = "{.a .b .c .d .e .f .g .h}", .names = { "a", "b", "c", "d", "e", "f", "g", "h" } }, + { .str = "{.da-sh}", .names = { "da-sh" } }, + { .str = "{.under_score}", .names = { "under_score" } }, + { .str = "{: .red}", .names = { "red" } }, + { .str = "{:5$}", .err = SP_ERR_FMT_BAD_PLACEHOLDER }, + { .str = ":5}", .err = SP_ERR_FMT_BAD_PLACEHOLDER }, + { .str = "{:5", .err = SP_ERR_FMT_UNTERMINATED_PLACEHOLDER }, + { .str = "{:5.}", .err = SP_ERR_FMT_BAD_PRECISION }, + { .str = "{.a .b .c .d .e .f .g .h .i}", .err = SP_ERR_FMT_TOO_MANY_DIRECTIVES }, + { .str = "{:10 .}", .err = SP_ERR_FMT_BAD_DIRECTIVE }, + { .str = "{:10 .red!}", .err = SP_ERR_FMT_BAD_PLACEHOLDER }, + { .str = "{.red.bold}", .err = SP_ERR_FMT_BAD_PLACEHOLDER }, + { .str = "{:10 red}", .err = SP_ERR_FMT_BAD_PLACEHOLDER }, + { .str = "{: .4red}", .err = SP_ERR_FMT_BAD_DIRECTIVE }, + { .str = "{.-red}", .err = SP_ERR_FMT_BAD_DIRECTIVE }, + { .str = "{:5red}", .err = SP_ERR_FMT_BAD_PLACEHOLDER }, + { .str = "{.red junk}", .err = SP_ERR_FMT_BAD_PLACEHOLDER }, + }; + + sp_carr_for(cases, it) { + run_format_spec(utest_result, cases[it]); + } +} diff --git a/test/format/writers.c b/test/format/writers.c new file mode 100644 index 0000000..ac51323 --- /dev/null +++ b/test/format/writers.c @@ -0,0 +1,145 @@ +#include "format.h" + +typedef enum { + WRITE_U64, + WRITE_S64, + WRITE_F64, + WRITE_PTR, + WRITE_BOOL, + WRITE_SIZE, + WRITE_DURATION, + WRITE_ORDINAL, +} write_kind_t; + +typedef struct { + write_kind_t kind; + const c8* expect; + union { + struct { u64 value; sp_fmt_radix_t radix; } u; + struct { s64 value; } s; + struct { f64 value; u32 precision; } f; + struct { void* value; } p; + struct { bool value; } b; + struct { u64 value; } size; + struct { u64 value; } duration; + struct { s64 value; } ordinal; + }; +} write_test_t; + +static void run_write_test(s32* utest_result, write_test_t t) { + c8 buf[128]; + sp_io_mem_writer_t io = sp_zero; + sp_io_mem_writer_from_buffer(&io, buf, sizeof(buf)); + + switch (t.kind) { + case WRITE_U64: sp_fmt_write_u64_ex(&io.base, t.u.value, t.u.radix); break; + case WRITE_S64: sp_fmt_write_s64(&io.base, t.s.value); break; + case WRITE_F64: sp_fmt_write_f64_ex(&io.base, t.f.value, t.f.precision); break; + case WRITE_PTR: sp_fmt_write_ptr(&io.base, t.p.value); break; + case WRITE_BOOL: sp_fmt_write_bool(&io.base, t.b.value); break; + case WRITE_SIZE: sp_fmt_write_size(&io.base, t.size.value); break; + case WRITE_DURATION: sp_fmt_write_duration(&io.base, t.duration.value); break; + case WRITE_ORDINAL: sp_fmt_write_ordinal(&io.base, t.ordinal.value); break; + } + + sp_str_t got = sp_io_mem_writer_as_str(&io); + EXPECT_TRUE(sp_str_equal_cstr(got, t.expect)); +} + +UTEST(format_write, primitives) { + write_test_t cases [] = { + { .kind = WRITE_U64, .expect = "0", .u = { .value = 0, .radix = SP_FMT_RADIX_DECIMAL } }, + { .kind = WRITE_U64, .expect = "7", .u = { .value = 7, .radix = SP_FMT_RADIX_DECIMAL } }, + { .kind = WRITE_U64, .expect = "99", .u = { .value = 99, .radix = SP_FMT_RADIX_DECIMAL } }, + { .kind = WRITE_U64, .expect = "100", .u = { .value = 100, .radix = SP_FMT_RADIX_DECIMAL } }, + { .kind = WRITE_U64, .expect = "18446744073709551615", .u = { .value = 18446744073709551615ULL, .radix = SP_FMT_RADIX_DECIMAL } }, + { .kind = WRITE_U64, .expect = "0", .u = { .value = 0, .radix = SP_FMT_RADIX_HEX } }, + { .kind = WRITE_U64, .expect = "ff", .u = { .value = 255, .radix = SP_FMT_RADIX_HEX } }, + { .kind = WRITE_U64, .expect = "deadbeef", .u = { .value = 0xdeadbeef, .radix = SP_FMT_RADIX_HEX } }, + { .kind = WRITE_U64, .expect = "FF", .u = { .value = 255, .radix = SP_FMT_RADIX_HEX_UPPER } }, + { .kind = WRITE_U64, .expect = "DEADBEEF", .u = { .value = 0xdeadbeef, .radix = SP_FMT_RADIX_HEX_UPPER } }, + { .kind = WRITE_U64, .expect = "0", .u = { .value = 0, .radix = SP_FMT_RADIX_BINARY } }, + { .kind = WRITE_U64, .expect = "101", .u = { .value = 5, .radix = SP_FMT_RADIX_BINARY } }, + { .kind = WRITE_U64, .expect = "11111111", .u = { .value = 255, .radix = SP_FMT_RADIX_BINARY } }, + { .kind = WRITE_U64, .expect = "0", .u = { .value = 0, .radix = SP_FMT_RADIX_OCTAL } }, + { .kind = WRITE_U64, .expect = "10", .u = { .value = 8, .radix = SP_FMT_RADIX_OCTAL } }, + { .kind = WRITE_U64, .expect = "777", .u = { .value = 511, .radix = SP_FMT_RADIX_OCTAL } }, + { .kind = WRITE_S64, .expect = "0", .s = { .value = 0 } }, + { .kind = WRITE_S64, .expect = "42", .s = { .value = 42 } }, + { .kind = WRITE_S64, .expect = "-42", .s = { .value = -42 } }, + { .kind = WRITE_S64, .expect = "9223372036854775807", .s = { .value = 9223372036854775807LL } }, + { .kind = WRITE_S64, .expect = "-9223372036854775808", .s = { .value = (-9223372036854775807LL - 1) } }, + { .kind = WRITE_F64, .expect = "0.00", .f = { .value = 0.0, .precision = 2 } }, + { .kind = WRITE_F64, .expect = "1", .f = { .value = 1.0, .precision = 0 } }, + { .kind = WRITE_F64, .expect = "0.5", .f = { .value = 0.5, .precision = 1 } }, + { .kind = WRITE_F64, .expect = "2.5", .f = { .value = 2.5, .precision = 1 } }, + { .kind = WRITE_F64, .expect = "-2.5", .f = { .value = -2.5, .precision = 1 } }, + { .kind = WRITE_F64, .expect = "3.14", .f = { .value = 3.14159, .precision = 2 } }, + { .kind = WRITE_F64, .expect = "3.1416", .f = { .value = 3.14159, .precision = 4 } }, + { .kind = WRITE_F64, .expect = "inf", .f = { .value = 2.0e19, .precision = 2 } }, + { .kind = WRITE_PTR, .expect = "0x0", .p = { .value = (void*)0 } }, + { .kind = WRITE_PTR, .expect = "0xdeadbeef", .p = { .value = (void*)(uintptr_t)0xdeadbeef } }, + { .kind = WRITE_BOOL, .expect = "true", .b = { .value = true } }, + { .kind = WRITE_BOOL, .expect = "false", .b = { .value = false } }, + { .kind = WRITE_SIZE, .expect = "0 B", .size = { .value = 0 } }, + { .kind = WRITE_SIZE, .expect = "512 B", .size = { .value = 512 } }, + { .kind = WRITE_SIZE, .expect = "1 KB", .size = { .value = 1024 } }, + { .kind = WRITE_SIZE, .expect = "1 KB", .size = { .value = 1124 } }, + { .kind = WRITE_SIZE, .expect = "1.5 KB", .size = { .value = 1536 } }, + { .kind = WRITE_SIZE, .expect = "1 MB", .size = { .value = 1048576 } }, + { .kind = WRITE_SIZE, .expect = "1024 PB", .size = { .value = 1152921504606846976ULL } }, + { .kind = WRITE_DURATION, .expect = "0 ns", .duration = { .value = 0 } }, + { .kind = WRITE_DURATION, .expect = "999 ns", .duration = { .value = 999 } }, + { .kind = WRITE_DURATION, .expect = "1 us", .duration = { .value = 1000 } }, + { .kind = WRITE_DURATION, .expect = "1 us", .duration = { .value = 1099 } }, + { .kind = WRITE_DURATION, .expect = "1.5 us", .duration = { .value = 1500 } }, + { .kind = WRITE_DURATION, .expect = "1 ms", .duration = { .value = 1000000 } }, + { .kind = WRITE_DURATION, .expect = "1 s", .duration = { .value = 1000000000 } }, + { .kind = WRITE_DURATION, .expect = "1000 s", .duration = { .value = 1000000000000ULL } }, + { .kind = WRITE_ORDINAL, .expect = "0th", .ordinal = { .value = 0 } }, + { .kind = WRITE_ORDINAL, .expect = "1st", .ordinal = { .value = 1 } }, + { .kind = WRITE_ORDINAL, .expect = "2nd", .ordinal = { .value = 2 } }, + { .kind = WRITE_ORDINAL, .expect = "3rd", .ordinal = { .value = 3 } }, + { .kind = WRITE_ORDINAL, .expect = "4th", .ordinal = { .value = 4 } }, + { .kind = WRITE_ORDINAL, .expect = "5th", .ordinal = { .value = 5 } }, + { .kind = WRITE_ORDINAL, .expect = "6th", .ordinal = { .value = 6 } }, + { .kind = WRITE_ORDINAL, .expect = "7th", .ordinal = { .value = 7 } }, + { .kind = WRITE_ORDINAL, .expect = "8th", .ordinal = { .value = 8 } }, + { .kind = WRITE_ORDINAL, .expect = "9th", .ordinal = { .value = 9 } }, + { .kind = WRITE_ORDINAL, .expect = "10th", .ordinal = { .value = 10 } }, + { .kind = WRITE_ORDINAL, .expect = "11th", .ordinal = { .value = 11 } }, + { .kind = WRITE_ORDINAL, .expect = "12th", .ordinal = { .value = 12 } }, + { .kind = WRITE_ORDINAL, .expect = "13th", .ordinal = { .value = 13 } }, + { .kind = WRITE_ORDINAL, .expect = "14th", .ordinal = { .value = 14 } }, + { .kind = WRITE_ORDINAL, .expect = "15th", .ordinal = { .value = 15 } }, + { .kind = WRITE_ORDINAL, .expect = "16th", .ordinal = { .value = 16 } }, + { .kind = WRITE_ORDINAL, .expect = "17th", .ordinal = { .value = 17 } }, + { .kind = WRITE_ORDINAL, .expect = "18th", .ordinal = { .value = 18 } }, + { .kind = WRITE_ORDINAL, .expect = "19th", .ordinal = { .value = 19 } }, + { .kind = WRITE_ORDINAL, .expect = "20th", .ordinal = { .value = 20 } }, + { .kind = WRITE_ORDINAL, .expect = "21st", .ordinal = { .value = 21 } }, + { .kind = WRITE_ORDINAL, .expect = "22nd", .ordinal = { .value = 22 } }, + { .kind = WRITE_ORDINAL, .expect = "23rd", .ordinal = { .value = 23 } }, + { .kind = WRITE_ORDINAL, .expect = "24th", .ordinal = { .value = 24 } }, + { .kind = WRITE_ORDINAL, .expect = "25th", .ordinal = { .value = 25 } }, + { .kind = WRITE_ORDINAL, .expect = "26th", .ordinal = { .value = 26 } }, + { .kind = WRITE_ORDINAL, .expect = "27th", .ordinal = { .value = 27 } }, + { .kind = WRITE_ORDINAL, .expect = "28th", .ordinal = { .value = 28 } }, + { .kind = WRITE_ORDINAL, .expect = "29th", .ordinal = { .value = 29 } }, + { .kind = WRITE_ORDINAL, .expect = "111th", .ordinal = { .value = 111 } }, + { .kind = WRITE_ORDINAL, .expect = "-1st", .ordinal = { .value = -1 } }, + }; + + sp_carr_for(cases, it) run_write_test(utest_result, cases[it]); +} + +UTEST(format_write, destinations) { + sp_str_r m = sp_fmt_write_u64_mem(sp_mem_get_scratch(), 255, SP_FMT_RADIX_HEX); + EXPECT_EQ(m.err, SP_OK); + SP_EXPECT_STR_EQ_CSTR(m.value, "ff"); + + c8 buf[16]; + sp_str_r b = sp_fmt_write_u64_buf(buf, sizeof(buf), 255, SP_FMT_RADIX_HEX); + EXPECT_EQ(b.err, SP_OK); + SP_EXPECT_STR_EQ_CSTR(b.value, "ff"); +} diff --git a/tools/windows/sp/format.vcxproj b/tools/windows/sp/format.vcxproj index c5e7a94..2926fce 100644 --- a/tools/windows/sp/format.vcxproj +++ b/tools/windows/sp/format.vcxproj @@ -19,7 +19,7 @@ 10.0 format ..\..\..\test\format.c - ..\..\..\sp.h;..\..\..\test\tools\utest.h + ..\..\..\sp.h;..\..\..\test\tools\utest.h;..\..\..\test\format\format.h;..\..\..\test\format\spec.c;..\..\..\test\format\fmt.c;..\..\..\test\format\builtins.c;..\..\..\test\format\custom.c;..\..\..\test\format\parse.c;..\..\..\test\format\writers.c