Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 111 additions & 118 deletions example/format.c
Original file line number Diff line number Diff line change
@@ -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)
Loading
Loading