Something went wrong. Try again.
printf with inline color tags and variables
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695/* * crprintf - printf with inline color tags and variables, powered by a register-based VM * * Copyright (c) 2026 theMackabu (me@themackabu.dev) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */
#include <ctype.h>#include <stdio.h>#include <stdarg.h>#include <string.h>#include <stdlib.h>#include <stdint.h>#include <stdbool.h>#include <wchar.h>#include "crprintf.h"
static bool crprintf_no_color = false;static bool crprintf_debug = false;static bool crprintf_debug_hex = false;
void crprintf_set_color(bool enable) { crprintf_no_color = !enable; }bool crprintf_get_color(void) { return !crprintf_no_color; }
void crprintf_set_debug(bool enable) { crprintf_debug = enable; }bool crprintf_get_debug(void) { return crprintf_debug; }
void crprintf_set_debug_hex(bool enable) { crprintf_debug_hex = enable; }bool crprintf_get_debug_hex(void) { return crprintf_debug_hex; }
static inline int hex_digit(char c) { static const int8_t lookup[256] = { ['0']=0, ['1']=1, ['2']=2, ['3']=3, ['4']=4, ['5']=5, ['6']=6, ['7']=7, ['8']=8, ['9']=9, ['a']=10, ['b']=11, ['c']=12, ['d']=13, ['e']=14, ['f']=15, ['A']=10, ['B']=11, ['C']=12, ['D']=13, ['E']=14, ['F']=15, }; int8_t val = lookup[(unsigned char)c]; return val ? val : (c == '0' ? 0 : -1);}
typedef enum { OP_NOP = 0, OP_EMIT_LIT, OP_EMIT_FMT, OP_SET_FG, OP_SET_BG, OP_SET_FG_RGB, OP_SET_BG_RGB, OP_SET_BOLD, OP_SET_DIM, OP_SET_UL, OP_SET_ITALIC, OP_SET_STRIKE, OP_SET_INVERT, OP_STYLE_PUSH, OP_STYLE_FLUSH, OP_STYLE_RESET, OP_STYLE_RESET_ALL, OP_PAD_BEGIN, OP_RPAD_BEGIN, OP_PAD_END, OP_EMIT_SPACES, OP_EMIT_NEWLINES, OP_SNAPSHOT, OP_HALT, OP_MAX} opcode_t;
typedef struct { uint32_t op; uint32_t operand;} instruction_t;
typedef enum { COL_NONE = 0, COL_BLACK = 30, COL_RED, COL_GREEN, COL_YELLOW, COL_BLUE, COL_MAGENTA, COL_CYAN, COL_WHITE, COL_GRAY = 90, COL_BRIGHT_RED, COL_BRIGHT_GREEN, COL_BRIGHT_YELLOW, COL_BRIGHT_BLUE, COL_BRIGHT_MAGENTA, COL_BRIGHT_CYAN, COL_BRIGHT_WHITE, COL_RGB = 0xFF} color_t;
#define UNPACK_R(c) (((c)>>16)&0xFF)#define UNPACK_G(c) (((c)>>8)&0xFF)#define UNPACK_B(c) ((c)&0xFF)
#define PACK_RGB(r,g,b) \ (((uint32_t)(r)<<16)|((uint32_t)(g)<<8)|(uint32_t)(b))
#define STYLE_BOLD 0x01#define STYLE_DIM 0x02#define STYLE_UL 0x04#define STYLE_ITALIC 0x08#define STYLE_STRIKE 0x10#define STYLE_INVERT 0x20
typedef struct { size_t mark; int width; int right_align;} pad_entry_t;
typedef struct { uint32_t fg; uint32_t bg; uint32_t fg_rgb; uint32_t bg_rgb; uint8_t flags;} style_entry_t; typedef struct { style_entry_t current; style_entry_t style_stack[8]; pad_entry_t pad_stack[8]; int style_depth; int pad_depth;} vm_regs_t;
#define MAX_VARS 64#define MAX_VAR_NAME 32#define MAX_VAR_VALUE 128
typedef struct { char name[MAX_VAR_NAME]; char value[MAX_VAR_VALUE]; int nlen; int vlen; int is_fmt;} crprintf_var_t;
typedef struct { crprintf_var_t vars[MAX_VARS]; int count;} var_table_t;
typedef struct { const var_table_t *base; var_table_t local; bool has_local;} var_scope_t;
typedef struct { vm_regs_t regs; char *out_buf; size_t out_pos; size_t out_cap; size_t resume_ip; bool valid;} vm_checkpoint_t;
struct crprintf_compiled { instruction_t *code; size_t code_len; size_t code_cap; char *literals; size_t lit_len; size_t lit_cap; char *source; size_t source_len; uint32_t *src_map; uint32_t *lit_marks; size_t map_cap; const char *compile_base; uint32_t _cur_src_off; vm_checkpoint_t checkpoint;};
typedef struct crprintf_state { style_entry_t current; style_entry_t style_stack[8]; int style_depth;} crprintf_state;
static var_table_t global_vars = {0};
static inline const var_table_t *scope_read(const var_scope_t *s) { return s->has_local ? &s->local : s->base;}
static inline var_table_t *scope_write(var_scope_t *s) { if (!s->has_local) { s->local = *s->base; s->has_local = true; } return &s->local;}
static const char *scan_var_brace( crprintf_compiled *p, const char *ptr, const char **lit, var_scope_t *vars);
static crprintf_compiled *program_new(void) { crprintf_compiled *p = calloc(1, sizeof(*p)); *p = (crprintf_compiled){ .code_cap = 32, .code = malloc(32 * sizeof(instruction_t)), .lit_cap = 256, .literals = malloc(256), }; return p;}
static inline void emit_op(crprintf_compiled *p, uint32_t op, uint32_t operand) { if (__builtin_expect(p->code_len >= p->code_cap, 0)) { size_t new_cap = p->code_cap * 2; instruction_t *new_code = realloc(p->code, new_cap * sizeof(instruction_t)); if (!new_code) return; p->code = new_code; p->code_cap = new_cap; } if (p->src_map) { if (__builtin_expect(p->code_len >= p->map_cap, 0)) { size_t new_cap = p->map_cap * 2; p->src_map = realloc(p->src_map, new_cap * sizeof(uint32_t)); p->lit_marks = realloc(p->lit_marks, new_cap * sizeof(uint32_t)); p->map_cap = new_cap; } p->src_map[p->code_len] = p->_cur_src_off; p->lit_marks[p->code_len] = (uint32_t)p->lit_len; } p->code[p->code_len++] = (instruction_t){ op, operand };}
static uint32_t add_literal(crprintf_compiled *p, const char *s, size_t len) { size_t required = p->lit_len + len + 1; if (__builtin_expect(required > p->lit_cap, 0)) { size_t new_cap = p->lit_cap; while (new_cap < required) new_cap *= 2; char *new_literals = realloc(p->literals, new_cap); if (!new_literals) return 0; p->literals = new_literals; p->lit_cap = new_cap; } uint32_t off = (uint32_t)p->lit_len; memcpy(p->literals + p->lit_len, s, len); p->literals[p->lit_len + len] = '\0'; p->lit_len += len + 1; return off;}
typedef struct { const char *name; int nlen; color_t col; } color_entry_t;
static const color_entry_t fg_colors[] = { {"black",5,COL_BLACK}, {"red",3,COL_RED}, {"green",5,COL_GREEN}, {"yellow",6,COL_YELLOW}, {"blue",4,COL_BLUE}, {"magenta",7,COL_MAGENTA}, {"cyan",4,COL_CYAN}, {"white",5,COL_WHITE}, {"gray",4,COL_GRAY}, {"grey",4,COL_GRAY}, {"bright_red",10,COL_BRIGHT_RED}, {"bright_green",12,COL_BRIGHT_GREEN}, {"bright_yellow",13,COL_BRIGHT_YELLOW}, {"bright_blue",11,COL_BRIGHT_BLUE}, {"bright_magenta",14,COL_BRIGHT_MAGENTA}, {"bright_cyan",11,COL_BRIGHT_CYAN}, {"bright_white",12,COL_BRIGHT_WHITE},};
static const color_entry_t bg_colors[] = { {"bg_black",8,COL_BLACK}, {"bg_red",6,COL_RED}, {"bg_green",8,COL_GREEN}, {"bg_yellow",9,COL_YELLOW}, {"bg_blue",7,COL_BLUE}, {"bg_magenta",10,COL_MAGENTA}, {"bg_cyan",7,COL_CYAN}, {"bg_white",8,COL_WHITE},};
static const color_entry_t seg_bg_colors[] = { {"black",5,COL_BLACK}, {"red",3,COL_RED}, {"green",5,COL_GREEN}, {"yellow",6,COL_YELLOW}, {"blue",4,COL_BLUE}, {"magenta",7,COL_MAGENTA}, {"cyan",4,COL_CYAN}, {"white",5,COL_WHITE},};
#define FG_COUNT (sizeof(fg_colors)/sizeof(fg_colors[0]))#define BG_COUNT (sizeof(bg_colors)/sizeof(bg_colors[0]))#define SEG_BG_COUNT (sizeof(seg_bg_colors)/sizeof(seg_bg_colors[0]))
static inline int parse_hex_rgb(const char *hex, int len, uint32_t *rgb) { int r, g, b; if (len == 4) { int r1=hex_digit(hex[1]), g1=hex_digit(hex[2]), b1=hex_digit(hex[3]); if ((r1|g1|b1) < 0) return 0; r=r1*17; g=g1*17; b=b1*17; } else if (len == 7) { int r1=hex_digit(hex[1]),r2=hex_digit(hex[2]); int g1=hex_digit(hex[3]),g2=hex_digit(hex[4]); int b1=hex_digit(hex[5]),b2=hex_digit(hex[6]); if (((r1|r2|g1|g2|b1|b2) < 0)) return 0; r=(r1<<4)+r2; g=(g1<<4)+g2; b=(b1<<4)+b2; } else return 0; *rgb = PACK_RGB(r, g, b); return 1;}
static int compile_hex_fg(crprintf_compiled *p, const char *tag, int len) { uint32_t rgb; if (!parse_hex_rgb(tag, len, &rgb)) return 0; emit_op(p, OP_SET_FG_RGB, rgb); return 1;}
static int compile_hex_bg(crprintf_compiled *p, const char *hex, int hlen) { uint32_t rgb; if (!parse_hex_rgb(hex, hlen, &rgb)) return 0; emit_op(p, OP_SET_BG_RGB, rgb); return 1;}
static int tag_eq(const char *tag, int len, const char *lit) { int ll = (int)strlen(lit); return len == ll && memcmp(tag, lit, ll) == 0;}
static int tag_prefix(const char *tag, int len, const char *pfx) { int pl = (int)strlen(pfx); return len > pl && memcmp(tag, pfx, pl) == 0;}
static int match_fg(crprintf_compiled *p, const char *s, int len) { for (int i = 0; i < (int)FG_COUNT; i++) if ( len == fg_colors[i].nlen && memcmp(s, fg_colors[i].name, len) == 0) { emit_op(p, OP_SET_FG, fg_colors[i].col); return 1; } return 0;}
static int match_bg(crprintf_compiled *p, const char *s, int len) { for (int i = 0; i < (int)BG_COUNT; i++) if ( len == bg_colors[i].nlen && memcmp(s, bg_colors[i].name, len) == 0) { emit_op(p, OP_SET_BG, bg_colors[i].col); return 1; } return 0;}
static int match_seg_bg(crprintf_compiled *p, const char *s, int len) { for (int i = 0; i < (int)SEG_BG_COUNT; i++) if ( len == seg_bg_colors[i].nlen && memcmp(s, seg_bg_colors[i].name, len) == 0) { emit_op(p, OP_SET_BG, seg_bg_colors[i].col); return 1; } return 0;}
typedef struct { const char *name; int nlen; int op; } attr_entry_t;
static const attr_entry_t attrs[] = { { "bold", 4, OP_SET_BOLD }, { "dim", 3, OP_SET_DIM }, { "ul", 2, OP_SET_UL }, { "i", 1, OP_SET_ITALIC }, { "italic", 6, OP_SET_ITALIC }, { "strike", 6, OP_SET_STRIKE }, { "invert", 6, OP_SET_INVERT },};
#define ATTR_COUNT (sizeof(attrs) / sizeof(attrs[0]))
static int match_attr(crprintf_compiled *p, const char *s, int len) { for (int i = 0; i < (int)ATTR_COUNT; i++) if ( len == attrs[i].nlen && memcmp(s, attrs[i].name, len) == 0) { emit_op(p, attrs[i].op, 1); return 1; } return 0;}
static const char *next_seg(const char *cur, const char *end, int *out_len) { const char *sep = cur; while (sep < end && *sep != '_') sep++; *out_len = (int)(sep - cur); return sep;}
static int match_plus_seg(crprintf_compiled *p, const char *seg, int slen) { if (match_attr(p, seg, slen)) return 1; if (match_fg(p, seg, slen)) return 1; if (match_bg(p, seg, slen)) return 1; if (slen > 0 && seg[0] == '#') return compile_hex_fg(p, seg, slen); if (tag_prefix(seg, slen, "bg_#")) return compile_hex_bg(p, seg + 3, slen - 3); if (tag_prefix(seg, slen, "bg_")) return match_seg_bg(p, seg + 3, slen - 3); return 0;}
static int compile_plus_segs(crprintf_compiled *p, const char *s, int len) { const char *seg = s; const char *end = s + len; int emitted = 0;
while (seg < end) { const char *next = seg; while (next < end && *next != '+') next++; int slen = (int)(next - seg); if (!match_plus_seg(p, seg, slen)) return 0; emitted++; seg = (next < end) ? next + 1 : end; }
return emitted;}
static int compile_let(var_scope_t *scope, const char *body, int len) { if (len > 0 && body[len - 1] == '/') len--;
const char *p = body; const char *end = body + len;
while (p < end) { while (p < end && (*p == ' ' || *p == ',')) p++; if (p >= end) break;
const char *eq = memchr(p, '=', end - p); if (!eq) return 0;
int nlen = (int)(eq - p);
const char *vstart = eq + 1; const char *vend = vstart;
if (vstart < end && (*vstart == '\'' || *vstart == '"')) { char quote = *vstart; vstart++; vend = vstart; while (vend < end && *vend != quote) vend++; if (vend >= end) return 0; const char *after = vend + 1; while (after < end && (*after == ' ' || *after == ',')) after++; int vlen = (int)(vend - vstart); if (nlen <= 0 || nlen >= MAX_VAR_NAME || vlen < 0 || vlen >= MAX_VAR_VALUE) return 0; var_table_t *vars = scope_write(scope); if (vars->count >= MAX_VARS) return 0;
crprintf_var_t *v = &vars->vars[vars->count++]; memcpy(v->name, p, nlen); v->name[nlen] = '\0'; v->nlen = nlen; memcpy(v->value, vstart, vlen); v->value[vlen] = '\0'; v->vlen = vlen; v->is_fmt = 0; for (int j = 0; j < vlen; j++) { if (vstart[j] == '%' && j + 1 < vlen && vstart[j + 1] != '%') { v->is_fmt = 1; break; } } p = after; continue; }
while (vend < end && *vend != ',') vend++;
int vlen = (int)(vend - vstart); if (nlen <= 0 || nlen >= MAX_VAR_NAME || vlen <= 0 || vlen >= MAX_VAR_VALUE) return 0; var_table_t *vars = scope_write(scope); if (vars->count >= MAX_VARS) return 0;
crprintf_var_t *v = &vars->vars[vars->count++]; memcpy(v->name, p, nlen); v->name[nlen] = '\0'; v->nlen = nlen; memcpy(v->value, vstart, vlen); v->value[vlen] = '\0'; v->vlen = vlen; p = vend; }
return 1;}
void crprintf_var(const char *name, const char *value) { if (global_vars.count >= MAX_VARS) return; int nlen = (int)strlen(name); int vlen = (int)strlen(value); if (nlen <= 0 || nlen >= MAX_VAR_NAME || vlen <= 0 || vlen >= MAX_VAR_VALUE) return;
for (int i = 0; i < global_vars.count; i++) { crprintf_var_t *v = &global_vars.vars[i]; if (v->nlen == nlen && memcmp(v->name, name, nlen) == 0) { memcpy(v->value, value, vlen); v->value[vlen] = '\0'; v->vlen = vlen; return; } }
crprintf_var_t *v = &global_vars.vars[global_vars.count++]; memcpy(v->name, name, nlen); v->name[nlen] = '\0'; v->nlen = nlen; memcpy(v->value, value, vlen); v->value[vlen] = '\0'; v->vlen = vlen;}
crprintf_state *crprintf_state_new(void) { return calloc(1, sizeof(crprintf_state)); }void crprintf_state_free(crprintf_state *state) { free(state); }
crprintf_state *crprintf_state_clone(const crprintf_state *state) { if (!state) return crprintf_state_new(); crprintf_state *clone = malloc(sizeof(crprintf_state)); if (clone) memcpy(clone, state, sizeof(crprintf_state)); return clone;}
bool crprintf_state_eq(const crprintf_state *a, const crprintf_state *b) { if (!a && !b) return true; if (!a || !b) return false; if (a->style_depth != b->style_depth) return false; if (memcmp(&a->current, &b->current, sizeof(style_entry_t)) != 0) return false; for (int i = 0; i < a->style_depth; i++) { if (memcmp(&a->style_stack[i], &b->style_stack[i], sizeof(style_entry_t)) != 0) return false; } return true;}
static int emit_style_esc(char *esc, size_t esc_size, const style_entry_t *s) { int n = snprintf(esc, esc_size, "\x1b[0m"); if (s->flags & STYLE_BOLD) n += snprintf(esc+n, esc_size-n, "\x1b[1m"); if (s->flags & STYLE_DIM) n += snprintf(esc+n, esc_size-n, "\x1b[2m"); if (s->flags & STYLE_UL) n += snprintf(esc+n, esc_size-n, "\x1b[4m"); if (s->flags & STYLE_ITALIC) n += snprintf(esc+n, esc_size-n, "\x1b[3m"); if (s->flags & STYLE_STRIKE) n += snprintf(esc+n, esc_size-n, "\x1b[9m"); if (s->flags & STYLE_INVERT) n += snprintf(esc+n, esc_size-n, "\x1b[7m"); if (s->fg == COL_RGB) n += snprintf(esc+n, esc_size-n, "\x1b[38;2;%d;%d;%dm", UNPACK_R(s->fg_rgb), UNPACK_G(s->fg_rgb), UNPACK_B(s->fg_rgb)); else if (s->fg) n += snprintf(esc+n, esc_size-n, "\x1b[%dm", s->fg); if (s->bg == COL_RGB) n += snprintf(esc+n, esc_size-n, "\x1b[48;2;%d;%d;%dm", UNPACK_R(s->bg_rgb), UNPACK_G(s->bg_rgb), UNPACK_B(s->bg_rgb)); else if (s->bg) n += snprintf(esc+n, esc_size-n, "\x1b[%dm", s->bg + 10); return n;}
static int compile_var_ref(crprintf_compiled *p, var_scope_t *scope, const char *tag, int len) { const char *name = tag + 1; int nlen = len - 1;
const char *plus = memchr(name, '+', nlen); int var_nlen = plus ? (int)(plus - name) : nlen;
const var_table_t *vars = scope_read(scope); for (int i = 0; i < vars->count; i++) { const crprintf_var_t *v = &vars->vars[i]; if (var_nlen != v->nlen || memcmp(name, v->name, var_nlen) != 0) continue; emit_op(p, OP_STYLE_PUSH, 0); if (!compile_plus_segs(p, v->value, v->vlen)) return 0;
if (plus) { const char *rest = plus + 1; int rlen = nlen - var_nlen - 1; if (rlen > 0 && !compile_plus_segs(p, rest, rlen)) return 0; }
emit_op(p, OP_STYLE_FLUSH, 0); return 1; }
return 0;}
static int match_attr_off(crprintf_compiled *p, const char *s, int len) { for (int i = 0; i < (int)ATTR_COUNT; i++) if ( len == attrs[i].nlen && memcmp(s, attrs[i].name, len) == 0) { emit_op(p, attrs[i].op, 0); return 1; } return 0;}
static int match_fg_off(crprintf_compiled *p, const char *s, int len) { for (int i = 0; i < (int)FG_COUNT; i++) if ( len == fg_colors[i].nlen && memcmp(s, fg_colors[i].name, len) == 0) { emit_op(p, OP_SET_FG, COL_NONE); return 1; } if (len > 0 && s[0] == '#') { emit_op(p, OP_SET_FG, COL_NONE); return 1; } return 0;}
static int match_bg_off(crprintf_compiled *p, const char *s, int len) { for (int i = 0; i < (int)BG_COUNT; i++) if ( len == bg_colors[i].nlen && memcmp(s, bg_colors[i].name, len) == 0) { emit_op(p, OP_SET_BG, COL_NONE); return 1; } if (tag_prefix(s, len, "bg_#")) { emit_op(p, OP_SET_BG, COL_NONE); return 1; } return 0;}
static int compile_tag(crprintf_compiled *p, const char *tag, int len, int closing, var_scope_t *vars) { if (closing) { if (tag_eq(tag, len, "pad") || tag_eq(tag, len, "rpad")) { emit_op(p, OP_PAD_END, 0); return 1; } if (match_attr_off(p, tag, len) || match_fg_off(p, tag, len) || match_bg_off(p, tag, len)) { emit_op(p, OP_STYLE_FLUSH, 0); return 1; } emit_op(p, OP_STYLE_RESET, 0); return 1; }
if (tag_prefix(tag, len, "let ")) return compile_let(vars, tag + 4, len - 4); if (tag[0] == '$' && len > 1) return compile_var_ref(p, vars, tag, len);
if (tag_prefix(tag, len, "pad=")) { emit_op(p, OP_PAD_BEGIN, (uint32_t)atoi(tag + 4)); return 1; } if (tag_prefix(tag, len, "rpad=")) { emit_op(p, OP_RPAD_BEGIN, (uint32_t)atoi(tag + 5)); return 1; } if (tag_prefix(tag, len, "space=") && tag[len-1] == '/') { emit_op(p, OP_EMIT_SPACES, (uint32_t)atoi(tag + 6)); return 1; } if (tag_prefix(tag, len, "gap=") && tag[len-1] == '/') { emit_op(p, OP_EMIT_SPACES, (uint32_t)atoi(tag + 4)); return 1; }
if (tag_eq(tag, len, "reset/")) { emit_op(p, OP_STYLE_RESET_ALL, 0); return 1; } if (tag_eq(tag, len, "br/")) { emit_op(p, OP_EMIT_NEWLINES, 1); return 1; } if (tag_prefix(tag, len, "br=") && tag[len-1] == '/') { emit_op(p, OP_EMIT_NEWLINES, (uint32_t)atoi(tag + 3)); return 1; }
emit_op(p, OP_STYLE_PUSH, 0);
if (match_attr(p, tag, len)) { emit_op(p, OP_STYLE_FLUSH, 0); return 1; } if (match_fg(p, tag, len)) { emit_op(p, OP_STYLE_FLUSH, 0); return 1; } if (match_bg(p, tag, len)) { emit_op(p, OP_STYLE_FLUSH, 0); return 1; }
if (tag[0] == '#') { if (!compile_hex_fg(p, tag, len)) return 0; emit_op(p, OP_STYLE_FLUSH, 0); return 1; }
if (tag_prefix(tag, len, "bg_#")) { if (!compile_hex_bg(p, tag + 3, len - 3)) return 0; emit_op(p, OP_STYLE_FLUSH, 0); return 1; }
if (memchr(tag, '+', len)) { if (compile_plus_segs(p, tag, len)) { emit_op(p, OP_STYLE_FLUSH, 0); return 1; } }
const char *seg = tag; const char *end = tag + len; int emitted = 0;
while (seg < end) { int slen; const char *sep = next_seg(seg, end, &slen);
if (match_attr(p, seg, slen)) { } else if (tag_eq(seg, slen, "bg") && sep < end) { seg = sep + 1; sep = next_seg(seg, end, &slen); if (!match_seg_bg(p, seg, slen)) return 0; } else if (!match_fg(p, seg, slen)) return 0;
emitted++; seg = (sep < end) ? sep + 1 : end; }
if (emitted) { emit_op(p, OP_STYLE_FLUSH, 0); return 1; } return 0;}
static void flush_lit(crprintf_compiled *p, const char *lit, const char *end) { if (end <= lit) return; uint32_t saved_off = p->_cur_src_off; if (p->compile_base && lit >= p->compile_base) p->_cur_src_off = (uint32_t)(lit - p->compile_base); uint32_t off = add_literal(p, lit, end - lit); emit_op(p, OP_EMIT_LIT, off); p->_cur_src_off = saved_off;}
static const char *scan_tag(crprintf_compiled *p, const char *ptr, const char **lit, var_scope_t *vars) { flush_lit(p, *lit, ptr);
const char *start = ptr + 1; int closing = 0; if (*start == '/') { closing = 1; start++; }
if (closing && *start == '>') { emit_op(p, OP_STYLE_RESET, 0); *lit = start + 1; return *lit; }
const char *end = start; while (*end && *end != '>') end++;
if (*end == '>' && compile_tag(p, start, (int)(end - start), closing, vars)) { *lit = end + 1; return *lit; }
uint32_t off = add_literal(p, "<", 1); emit_op(p, OP_EMIT_LIT, off); *lit = ptr + 1; return *lit;}
typedef enum { ARG_NONE = 0, ARG_INT, ARG_LONG, ARG_LLONG, ARG_SIZE, ARG_DOUBLE, ARG_CSTR, ARG_PTR, ARG_WINT, ARG_WSTR,} arg_class_t;
static arg_class_t classify_arg(const char *spec, int len) { char conv = spec[len - 1]; if (conv == '%') return ARG_NONE; if (conv == 'n') return ARG_PTR; if (conv == 's') return ARG_CSTR; if (conv == 'p') return ARG_PTR; if ( conv == 'f' || conv == 'F' || conv == 'e' || conv == 'E' || conv == 'g' || conv == 'G' || conv == 'a' || conv == 'A' ) return ARG_DOUBLE;
const char *p = spec + 1; while (*p == '-' || *p == '+' || *p == ' ' || *p == '#' || *p == '0') p++; if (*p == '*') p++; else while (*p >= '0' && *p <= '9') p++; if (*p == '.') { p++; if (*p == '*') p++; else while (*p >= '0' && *p <= '9') p++; }
if (p[0] == 'z') return ARG_SIZE; if (p[0] == 'l' && p[1] == 'l') return ARG_LLONG; if (p[0] == 'l' && conv == 'c') return ARG_WINT; if (p[0] == 'l' && conv == 's') return ARG_WSTR; if (p[0] == 'l') return ARG_LONG; if (p[0] == 'j') return ARG_LLONG;
return ARG_INT;}
#if defined(__x86_64__) && !defined(_WIN32) #define CRP_VA_REF_T va_list #define CRP_VA_PASS(ap) (ap) #define CRP_VA_DEREF(ap) (ap)#else #define CRP_VA_REF_T va_list * #define CRP_VA_PASS(ap) (&(ap)) #define CRP_VA_DEREF(ap) (*(ap))#endif
static inline void advance_format_args(const char *spec, CRP_VA_REF_T ap) { const char *p = spec + 1; while ( *p == '-' || *p == '+' || *p == ' ' || *p == '#' || *p == '0' ) p++; if (*p == '*') { (void)va_arg(CRP_VA_DEREF(ap), int); p++; } else while (*p >= '0' && *p <= '9') p++;
if (*p == '.') { p++; if (*p == '*') { (void)va_arg(CRP_VA_DEREF(ap), int); p++; } else while (*p >= '0' && *p <= '9') p++; }
switch (classify_arg(spec, (int)strlen(spec))) { case ARG_INT: (void)va_arg(CRP_VA_DEREF(ap), int); break; case ARG_LONG: (void)va_arg(CRP_VA_DEREF(ap), long); break; case ARG_LLONG: (void)va_arg(CRP_VA_DEREF(ap), long long); break; case ARG_SIZE: (void)va_arg(CRP_VA_DEREF(ap), size_t); break; case ARG_DOUBLE: (void)va_arg(CRP_VA_DEREF(ap), double); break; case ARG_CSTR: (void)va_arg(CRP_VA_DEREF(ap), const char *); break; case ARG_PTR: (void)va_arg(CRP_VA_DEREF(ap), void *); break; case ARG_WINT: (void)va_arg(CRP_VA_DEREF(ap), wint_t); break; case ARG_WSTR: (void)va_arg(CRP_VA_DEREF(ap), wchar_t *); break; case ARG_NONE: break; }}
static const char *scan_fmt(crprintf_compiled *p, const char *ptr, const char **lit) { flush_lit(p, *lit, ptr);
const char *fs = ptr + 1; while (*fs=='-'||*fs=='+'||*fs==' '||*fs=='#'||*fs=='0') fs++; if (*fs == '*') fs++; else while (*fs >= '0' && *fs <= '9') fs++; if (*fs == '.') { fs++; if (*fs == '*') fs++; else while (*fs >= '0' && *fs <= '9') fs++; } while (*fs=='h'||*fs=='l'||*fs=='L'||*fs=='z'||*fs=='j'||*fs=='t') fs++; if (*fs) fs++;
uint32_t off = add_literal(p, ptr, fs - ptr); arg_class_t cls = classify_arg(ptr, (int)(fs - ptr)); emit_op(p, OP_EMIT_FMT, off | ((uint32_t)cls << 28)); *lit = fs; return fs;}
static const char *scan_escape( crprintf_compiled *p, const char *ptr, const char **lit, const char *emit, size_t elen) { flush_lit(p, *lit, ptr); uint32_t off = add_literal(p, emit, elen); emit_op(p, OP_EMIT_LIT, off); *lit = ptr + 2; return *lit;}
static void transform_case(char *dst, const char *src, int len, int lower) { for (int i = 0; i < len; i++) dst[i] = (char)(lower ? tolower((unsigned char)src[i]) : toupper((unsigned char)src[i]) );}
static const char *scan_let_brace(crprintf_compiled *p, const char *ptr, const char **lit, var_scope_t *vars) { flush_lit(p, *lit, ptr);
const char *body = ptr + 5; const char *end = body; while (*end && *end != '}') end++;
if (*end == '}' && compile_let(vars, body, (int)(end - body))) { *lit = end + 1; return *lit; }
uint32_t off = add_literal(p, "{", 1); emit_op(p, OP_EMIT_LIT, off); *lit = ptr + 1; return *lit;}
static void compile_fragment(crprintf_compiled *p, const char *fmt, var_scope_t *vars) { const char *ptr = fmt; const char *lit = ptr;
while (*ptr) { if (p->compile_base && fmt >= p->compile_base && fmt <= p->compile_base + p->source_len) p->_cur_src_off = (uint32_t)(ptr - p->compile_base); if (*ptr == '<' && ptr[1] == '<') ptr = scan_escape(p, ptr, &lit, "<", 1); else if (*ptr == '>' && ptr[1] == '>') ptr = scan_escape(p, ptr, &lit, ">", 1); else if (*ptr == '%' && ptr[1] == '%') ptr = scan_escape(p, ptr, &lit, "%", 1); else if (*ptr == '{' && memcmp(ptr, "{let ", 5) == 0) ptr = scan_let_brace(p, ptr, &lit, vars); else if (*ptr == '{') ptr = scan_var_brace(p, ptr, &lit, vars); else if (*ptr == '<') ptr = scan_tag(p, ptr, &lit, vars); else if (*ptr == '%' && ptr[1] && ptr[1] != '%') ptr = scan_fmt(p, ptr, &lit); else ptr++; }
if (p->compile_base && fmt >= p->compile_base && fmt <= p->compile_base + p->source_len) p->_cur_src_off = (uint32_t)(ptr - p->compile_base); flush_lit(p, lit, ptr);}
static const char *scan_var_brace(crprintf_compiled *p, const char *ptr, const char **lit, var_scope_t *vars) { flush_lit(p, *lit, ptr);
const char *name = ptr + 1; const char *end = name; while (*end && *end != '}') end++;
if (*end != '}') goto emit_brace;
int lower = 0, upper = 0; if (*name == '~') { lower = 1; name++; } else if (*name == '^') { upper = 1; name++; } int nlen = (int)(end - name);
if (*name == '\'' || *name == '"') { const char *s = name + 1; const char *e = memchr(s, *name, end - s); if (!e) goto emit_brace;
int slen = (int)(e - s); if (slen > 0 && slen < MAX_VAR_VALUE) { char buf[MAX_VAR_VALUE]; if (lower || upper) { transform_case(buf, s, slen, lower); s = buf; } uint32_t off = add_literal(p, s, slen); emit_op(p, OP_EMIT_LIT, off); }
*lit = end + 1; return *lit; }
const var_table_t *vtbl = scope_read(vars); for (int i = 0; i < vtbl->count; i++) { const crprintf_var_t *v = &vtbl->vars[i]; if (nlen != v->nlen || memcmp(name, v->name, nlen) != 0) continue;
const char *val = v->value; int vlen = v->vlen; char buf[MAX_VAR_VALUE]; if (lower || upper) { transform_case(buf, val, vlen, lower); val = buf; } if (memchr(val, '<', vlen)) { char tmp[MAX_VAR_VALUE + 1]; memcpy(tmp, val, vlen); tmp[vlen] = '\0'; const char *saved_base = p->compile_base; p->compile_base = NULL; compile_fragment(p, tmp, vars); p->compile_base = saved_base; } else if (v->is_fmt) { arg_class_t cls = classify_arg(val, vlen); uint32_t off = add_literal(p, val, vlen); emit_op(p, OP_EMIT_FMT, off | ((uint32_t)cls << 28)); } else { uint32_t off = add_literal(p, val, vlen); emit_op(p, OP_EMIT_LIT, off); } *lit = end + 1; return *lit; }
emit_brace:; uint32_t off = add_literal(p, "{", 1); emit_op(p, OP_EMIT_LIT, off); *lit = ptr + 1; return *lit;}
crprintf_compiled *crprintf_compile(const char *fmt) { crprintf_compiled *p = program_new(); var_scope_t vars = { .base = &global_vars };
compile_fragment(p, fmt, &vars); emit_op(p, OP_HALT, 0); return p;}
static size_t visible_len(const char *s, size_t n) { size_t vis = 0; for (size_t i = 0; i < n; i++) { if (s[i] == '\x1b') while (++i < n && !isalpha(s[i])); else vis++; } return vis;}
typedef struct { char *data; size_t len;} vm_output_t;
static vm_output_t crprintf_vm_run_ex(crprintf_compiled *prog, va_list ap, crprintf_state *state, const vm_checkpoint_t *ckpt);
static vm_output_t crprintf_vm_run(crprintf_compiled *prog, va_list ap, crprintf_state *state) { return crprintf_vm_run_ex(prog, ap, state, NULL);}
static vm_output_t crprintf_vm_run_ex(crprintf_compiled *prog, va_list ap, crprintf_state *state, const vm_checkpoint_t *ckpt) { vm_regs_t regs = {0}; size_t cap, pos; char *out;
if (ckpt && ckpt->valid) { regs = ckpt->regs; cap = ckpt->out_cap; pos = ckpt->out_pos; out = malloc(cap); if (!out) return (vm_output_t){ NULL, 0 }; memcpy(out, ckpt->out_buf, pos); } else { if (state) { regs.current = state->current; memcpy(regs.style_stack, state->style_stack, sizeof(state->style_stack)); regs.style_depth = state->style_depth; } cap = 512; pos = 0; out = malloc(cap); if (!out) return (vm_output_t){ NULL, 0 }; }
#define ENSURE(n) ({ \ while (pos + (n) + 1 > cap) { \ cap *= 2; out = realloc(out, cap); \ if (!out) return (vm_output_t){ NULL, 0 }; \ }}) #define OUT_STR(s, l) ({ ENSURE(l); memcpy(out+pos, s, l); pos += (l); }) #define OUT_CSTR(s) ({ size_t _l = strlen(s); OUT_STR(s, _l); })
if (!(ckpt && ckpt->valid) && state && !crprintf_no_color) { style_entry_t *cur = ®s.current; if (cur->fg || cur->bg || cur->flags) { char esc[128]; int n = emit_style_esc(esc, sizeof(esc), cur); OUT_STR(esc, (size_t)n); } }
const instruction_t *ip = (ckpt && ckpt->valid) ? prog->code + ckpt->resume_ip : prog->code; static const void *dispatch[OP_MAX] = { [OP_NOP] = &&op_nop, [OP_EMIT_LIT] = &&op_emit_lit, [OP_EMIT_FMT] = &&op_emit_fmt, [OP_SET_FG] = &&op_set_fg, [OP_SET_BG] = &&op_set_bg, [OP_SET_FG_RGB] = &&op_set_fg_rgb, [OP_SET_BG_RGB] = &&op_set_bg_rgb, [OP_SET_BOLD] = &&op_set_bold, [OP_SET_DIM] = &&op_set_dim, [OP_SET_UL] = &&op_set_ul, [OP_SET_ITALIC] = &&op_set_italic, [OP_SET_STRIKE] = &&op_set_strike, [OP_SET_INVERT] = &&op_set_invert, [OP_STYLE_PUSH] = &&op_style_push, [OP_STYLE_FLUSH] = &&op_style_flush, [OP_STYLE_RESET] = &&op_style_reset, [OP_STYLE_RESET_ALL] = &&op_style_reset_all, [OP_PAD_BEGIN] = &&op_pad_begin, [OP_RPAD_BEGIN] = &&op_rpad_begin, [OP_PAD_END] = &&op_pad_end, [OP_EMIT_SPACES] = &&op_emit_spaces, [OP_EMIT_NEWLINES] = &&op_emit_newlines, [OP_SNAPSHOT] = &&op_snapshot, [OP_HALT] = &&op_halt, };
#define DISPATCH() goto *dispatch[ip->op] #define NEXT() do { ip++; DISPATCH(); } while(0)
DISPATCH();
op_nop: NEXT();
op_emit_lit: { OUT_CSTR(prog->literals + ip->operand); NEXT(); }
op_emit_fmt: { uint32_t lit_off = ip->operand & 0x0FFFFFFF; const char *spec = prog->literals + lit_off; char tmp[256]; va_list ap_copy; va_copy(ap_copy, ap); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" int n = vsnprintf(tmp, sizeof(tmp), spec, ap_copy); va_end(ap_copy); if (n > 0 && (size_t)n < sizeof(tmp)) { OUT_STR(tmp, (size_t)n); } else if (n > 0) { ENSURE((size_t)n); va_copy(ap_copy, ap); vsnprintf(out + pos, (size_t)n + 1, spec, ap_copy); va_end(ap_copy); pos += (size_t)n; } #pragma GCC diagnostic pop advance_format_args(spec, CRP_VA_PASS(ap)); NEXT(); }
op_set_fg: { regs.current.fg = ip->operand; NEXT(); } op_set_bg: { regs.current.bg = ip->operand; NEXT(); } op_set_bold: { if (ip->operand) regs.current.flags |= STYLE_BOLD; else regs.current.flags &= ~STYLE_BOLD; NEXT(); } op_set_dim: { if (ip->operand) regs.current.flags |= STYLE_DIM; else regs.current.flags &= ~STYLE_DIM; NEXT(); } op_set_ul: { if (ip->operand) regs.current.flags |= STYLE_UL; else regs.current.flags &= ~STYLE_UL; NEXT(); }
op_set_italic: { if (ip->operand) regs.current.flags |= STYLE_ITALIC; else regs.current.flags &= ~STYLE_ITALIC; NEXT(); }
op_set_strike: { if (ip->operand) regs.current.flags |= STYLE_STRIKE; else regs.current.flags &= ~STYLE_STRIKE; NEXT(); }
op_set_invert: { if (ip->operand) regs.current.flags |= STYLE_INVERT; else regs.current.flags &= ~STYLE_INVERT; NEXT(); } op_set_fg_rgb: { regs.current.fg = COL_RGB; regs.current.fg_rgb = ip->operand; NEXT(); } op_set_bg_rgb: { regs.current.bg = COL_RGB; regs.current.bg_rgb = ip->operand; NEXT(); } op_style_push: { if (regs.style_depth < 8) regs.style_stack[regs.style_depth++] = regs.current; NEXT(); } op_pad_begin: { if (regs.pad_depth < 8) regs.pad_stack[regs.pad_depth++] = (pad_entry_t){ pos, (int)ip->operand, 0 }; NEXT(); } op_rpad_begin: { if (regs.pad_depth < 8) regs.pad_stack[regs.pad_depth++] = (pad_entry_t){ pos, (int)ip->operand, 1 }; NEXT(); } op_pad_end: { if (regs.pad_depth <= 0) NEXT(); regs.pad_depth--; pad_entry_t pe = regs.pad_stack[regs.pad_depth]; size_t vis = visible_len(out + pe.mark, pos - pe.mark); if ((size_t)pe.width <= vis) NEXT(); size_t pad_n = pe.width - vis; ENSURE(pad_n); if (pe.right_align) { memmove(out + pe.mark + pad_n, out + pe.mark, pos - pe.mark); memset(out + pe.mark, ' ', pad_n); } else memset(out + pos, ' ', pad_n); pos += pad_n; NEXT(); } op_emit_spaces: { size_t n = (size_t)ip->operand; ENSURE(n); memset(out + pos, ' ', n); pos += n; NEXT(); } op_emit_newlines: { size_t n = (size_t)ip->operand; ENSURE(n); memset(out + pos, '\n', n); pos += n; NEXT(); }
op_style_reset: { if (regs.style_depth > 0) regs.current = regs.style_stack[--regs.style_depth]; else regs.current = (style_entry_t){.fg = COL_NONE, .bg = COL_NONE};
if (!crprintf_no_color) { char esc[128]; int n = snprintf(esc, sizeof(esc), "\x1b[0m"); if (regs.current.flags & STYLE_BOLD) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[1m"); if (regs.current.flags & STYLE_DIM) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[2m"); if (regs.current.flags & STYLE_UL) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[4m"); if (regs.current.flags & STYLE_ITALIC) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[3m"); if (regs.current.flags & STYLE_STRIKE) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[9m"); if (regs.current.flags & STYLE_INVERT) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[7m"); if (regs.current.fg == COL_RGB) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[38;2;%d;%d;%dm", UNPACK_R(regs.current.fg_rgb), UNPACK_G(regs.current.fg_rgb), UNPACK_B(regs.current.fg_rgb)); else if (regs.current.fg) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[%dm", regs.current.fg); if (regs.current.bg == COL_RGB) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[48;2;%d;%d;%dm", UNPACK_R(regs.current.bg_rgb), UNPACK_G(regs.current.bg_rgb), UNPACK_B(regs.current.bg_rgb)); else if (regs.current.bg) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[%dm", regs.current.bg + 10); OUT_STR(esc, (size_t)n); } NEXT(); } op_style_reset_all: { regs.current = (style_entry_t){.fg = COL_NONE, .bg = COL_NONE}; regs.style_depth = 0; if (!crprintf_no_color) { OUT_CSTR("\x1b[0m"); } NEXT(); }
op_style_flush: { if (!crprintf_no_color) { char esc[128]; int n = snprintf(esc, sizeof(esc), "\x1b[0m"); if (regs.current.flags & STYLE_BOLD) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[1m"); if (regs.current.flags & STYLE_DIM) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[2m"); if (regs.current.flags & STYLE_UL) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[4m"); if (regs.current.flags & STYLE_ITALIC) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[3m"); if (regs.current.flags & STYLE_STRIKE) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[9m"); if (regs.current.flags & STYLE_INVERT) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[7m"); if (regs.current.fg == COL_RGB) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[38;2;%d;%d;%dm", UNPACK_R(regs.current.fg_rgb), UNPACK_G(regs.current.fg_rgb), UNPACK_B(regs.current.fg_rgb)); else if (regs.current.fg) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[%dm", regs.current.fg); if (regs.current.bg == COL_RGB) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[48;2;%d;%d;%dm", UNPACK_R(regs.current.bg_rgb), UNPACK_G(regs.current.bg_rgb), UNPACK_B(regs.current.bg_rgb)); else if (regs.current.bg) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[%dm", regs.current.bg + 10); OUT_STR(esc, (size_t)n); } NEXT(); } op_snapshot: { vm_checkpoint_t *save = &prog->checkpoint; free(save->out_buf); save->regs = regs; save->out_buf = malloc(pos); if (save->out_buf) { memcpy(save->out_buf, out, pos); save->out_pos = pos; save->out_cap = pos; save->resume_ip = (size_t)(ip + 1 - prog->code); save->valid = true; } NEXT(); }
op_halt: { if (state) { state->current = regs.current; memcpy(state->style_stack, regs.style_stack, sizeof(state->style_stack)); state->style_depth = regs.style_depth; } out[pos] = '\0'; return (vm_output_t){ out, pos }; }
#undef ENSURE #undef OUT_STR #undef OUT_CSTR}
int crprintf_exec(crprintf_compiled *prog, FILE *stream, ...) { va_list ap; va_start(ap, stream); vm_output_t o = crprintf_vm_run(prog, ap, NULL); va_end(ap); if (!o.data) return -1;
int ret = (int)fwrite(o.data, 1, o.len, stream); free(o.data); return ret;}
int crsprintf_inner(crprintf_compiled *prog, char *buf, size_t size, ...) { va_list ap; va_start(ap, size); vm_output_t o = crprintf_vm_run(prog, ap, NULL); va_end(ap); if (!o.data) return -1;
size_t copy = (o.len < size) ? o.len : size - 1; memcpy(buf, o.data, copy); buf[copy] = '\0'; free(o.data); return (int)o.len;}
int crsprintf_stateful(char *buf, size_t size, crprintf_state *state, const char *fmt, ...) { crprintf_compiled *prog = crprintf_compile(fmt); if (__builtin_expect(crprintf_get_debug(), 0)) crprintf_disasm(prog, stderr); if (__builtin_expect(crprintf_get_debug_hex(), 0)) crprintf_hexdump(prog, stderr); va_list ap; va_start(ap, fmt); vm_output_t o = crprintf_vm_run(prog, ap, state); va_end(ap); free(prog->code); free(prog->literals); free(prog->source); free(prog->src_map); free(prog->lit_marks); free(prog); if (!o.data) return -1; size_t copy = (o.len < size) ? o.len : size - 1; memcpy(buf, o.data, copy); buf[copy] = '\0'; free(o.data); return (int)o.len;}
int crfprintf_stateful(FILE *stream, crprintf_state *state, const char *fmt, ...) { crprintf_compiled *prog = crprintf_compile(fmt); if (__builtin_expect(crprintf_get_debug(), 0)) crprintf_disasm(prog, stderr); if (__builtin_expect(crprintf_get_debug_hex(), 0)) crprintf_hexdump(prog, stderr); va_list ap; va_start(ap, fmt); vm_output_t o = crprintf_vm_run(prog, ap, state); va_end(ap);
free(prog->code); free(prog->literals); free(prog->source); free(prog->src_map); free(prog->lit_marks); free(prog);
if (!o.data) return -1; int ret = (int)fwrite(o.data, 1, o.len, stream); free(o.data); return ret;}
int crsprintf_compiled(char *buf, size_t size, crprintf_state *state, crprintf_compiled *prog, ...) { va_list ap; va_start(ap, prog); const vm_checkpoint_t *ckpt = prog->checkpoint.valid ? &prog->checkpoint : NULL; vm_output_t o = crprintf_vm_run_ex(prog, ap, state, ckpt); va_end(ap); if (!o.data) return -1;
size_t copy = (o.len < size) ? o.len : size - 1; memcpy(buf, o.data, copy); buf[copy] = '\0'; free(o.data); return (int)o.len;}
void crprintf_compiled_free(crprintf_compiled *prog) { if (!prog) return; free(prog->code); free(prog->literals); free(prog->source); free(prog->src_map); free(prog->lit_marks); free(prog->checkpoint.out_buf); free(prog);}
static crprintf_compiled *compile_tracked(const char *fmt) { crprintf_compiled *p = program_new(); p->source_len = strlen(fmt); p->source = malloc(p->source_len + 1); memcpy(p->source, fmt, p->source_len + 1); p->map_cap = p->code_cap; p->src_map = malloc(p->map_cap * sizeof(uint32_t)); p->lit_marks = malloc(p->map_cap * sizeof(uint32_t)); p->compile_base = p->source;
var_scope_t vars = { .base = &global_vars }; compile_fragment(p, p->source, &vars); emit_op(p, OP_HALT, 0); p->compile_base = NULL; if (__builtin_expect(crprintf_get_debug(), 0)) crprintf_disasm(p, stderr); if (__builtin_expect(crprintf_get_debug_hex(), 0)) crprintf_hexdump(p, stderr); return p;}
crprintf_compiled *crprintf_recompile(crprintf_compiled *prev, const char *fmt) { if (!prev) return compile_tracked(fmt);
size_t new_len = strlen(fmt);
if (prev->source && prev->source_len == new_len && memcmp(prev->source, fmt, new_len) == 0) { return prev; }
if (!prev->src_map) { crprintf_compiled_free(prev); return compile_tracked(fmt); }
size_t min_len = prev->source_len < new_len ? prev->source_len : new_len; size_t diverge = 0; while (diverge < min_len && prev->source[diverge] == fmt[diverge]) diverge++;
size_t trunc_idx = prev->code_len; for (size_t i = 0; i < prev->code_len; i++) { if (prev->src_map[i] >= (uint32_t)diverge) { trunc_idx = i; break; } }
uint32_t resume_off = 0; if (trunc_idx > 0) { uint32_t boundary_src = prev->src_map[trunc_idx - 1]; while (trunc_idx > 0 && prev->src_map[trunc_idx - 1] == boundary_src) trunc_idx--; resume_off = boundary_src;
for (uint32_t s = resume_off; s <= resume_off; s--) { char c = fmt[s]; if (c == '<' || c == '{') { resume_off = s; break; } if (c == '>' || c == '}') break; } while (trunc_idx > 0 && prev->src_map[trunc_idx - 1] >= resume_off) trunc_idx--; }
prev->code_len = trunc_idx; prev->lit_len = (trunc_idx > 0) ? prev->lit_marks[trunc_idx - 1] : 0;
if (prev->checkpoint.valid && prev->checkpoint.resume_ip != trunc_idx + 1) { free(prev->checkpoint.out_buf); prev->checkpoint = (vm_checkpoint_t){0}; }
bool prefix_has_fmt = false; for (size_t i = 0; i < trunc_idx; i++) { if (prev->code[i].op == OP_EMIT_FMT) { prefix_has_fmt = true; break; } }
free(prev->source); prev->source_len = new_len; prev->source = malloc(new_len + 1); memcpy(prev->source, fmt, new_len + 1);
var_scope_t vars = { .base = &global_vars }; for (size_t i = 0; i + 5 < diverge; i++) { if (fmt[i] == '<' && memcmp(fmt + i + 1, "let ", 4) == 0) { const char *body = fmt + i + 5; const char *end = memchr(body, '>', diverge - (i + 5)); if (end) { compile_let(&vars, body, (int)(end - body)); i = (size_t)(end - fmt); } } else if (fmt[i] == '{' && memcmp(fmt + i + 1, "let ", 4) == 0) { const char *body = fmt + i + 5; const char *end = memchr(body, '}', diverge - (i + 5)); if (end) { compile_let(&vars, body, (int)(end - body)); i = (size_t)(end - fmt); } } }
if (trunc_idx > 0 && !prefix_has_fmt) emit_op(prev, OP_SNAPSHOT, 0);
prev->compile_base = prev->source; compile_fragment(prev, prev->source + resume_off, &vars); emit_op(prev, OP_HALT, 0); prev->compile_base = NULL; if (__builtin_expect(crprintf_get_debug(), 0)) crprintf_disasm(prev, stderr); if (__builtin_expect(crprintf_get_debug_hex(), 0)) crprintf_hexdump(prev, stderr);
return prev;}
static const char *op_names[OP_MAX] = { [OP_NOP] = "NOP", [OP_EMIT_LIT] = "EMIT_LIT", [OP_EMIT_FMT] = "EMIT_FMT", [OP_SET_FG] = "SET_FG", [OP_SET_BG] = "SET_BG", [OP_SET_FG_RGB] = "SET_FG_RGB", [OP_SET_BG_RGB] = "SET_BG_RGB", [OP_SET_BOLD] = "SET_BOLD", [OP_SET_DIM] = "SET_DIM", [OP_SET_UL] = "SET_UL", [OP_SET_ITALIC] = "SET_ITALIC", [OP_SET_STRIKE] = "SET_STRIKE", [OP_SET_INVERT] = "SET_INVERT", [OP_STYLE_PUSH] = "STYLE_PUSH", [OP_STYLE_FLUSH] = "STYLE_FLUSH", [OP_STYLE_RESET] = "STYLE_RESET", [OP_STYLE_RESET_ALL] = "STYLE_RESET_ALL", [OP_PAD_BEGIN] = "PAD_BEGIN", [OP_RPAD_BEGIN] = "RPAD_BEGIN", [OP_PAD_END] = "PAD_END", [OP_EMIT_SPACES] = "EMIT_SPACES", [OP_EMIT_NEWLINES] = "EMIT_NEWLINES", [OP_SNAPSHOT] = "SNAPSHOT", [OP_HALT] = "HALT",};
static const char *color_name(uint32_t col) {switch (col) { case COL_NONE: return "none"; case COL_BLACK: return "black"; case COL_RED: return "red"; case COL_GREEN: return "green"; case COL_YELLOW: return "yellow"; case COL_BLUE: return "blue"; case COL_MAGENTA: return "magenta"; case COL_CYAN: return "cyan"; case COL_WHITE: return "white"; case COL_GRAY: return "gray"; case COL_BRIGHT_RED: return "bright_red"; case COL_BRIGHT_GREEN: return "bright_green"; case COL_BRIGHT_YELLOW: return "bright_yellow"; case COL_BRIGHT_BLUE: return "bright_blue"; case COL_BRIGHT_MAGENTA: return "bright_magenta"; case COL_BRIGHT_CYAN: return "bright_cyan"; case COL_BRIGHT_WHITE: return "bright_white"; default: return "?";}}
static const char *arg_class_name(arg_class_t cls) {switch (cls) { case ARG_NONE: return "none"; case ARG_INT: return "int"; case ARG_LONG: return "long"; case ARG_LLONG: return "llong"; case ARG_SIZE: return "size_t"; case ARG_DOUBLE: return "double"; case ARG_CSTR: return "char*"; case ARG_PTR: return "void*"; case ARG_WINT: return "wint_t"; case ARG_WSTR: return "wchar_t*"; default: return "?";}}
static void fprint_escaped(FILE *out, const char *s, int max_chars) { for (int c = 0; *s && (max_chars < 0 || c < max_chars); s++, c++) { if (*s == '\n') fprintf(out, "\\n"); else if (*s == '\t') fprintf(out, "\\t"); else if (*s == '"') fprintf(out, "\\\""); else if (*s < 0x20) fprintf(out, "\\x%02x", (unsigned char)*s); else fputc(*s, out); } if (max_chars >= 0 && *s) fprintf(out, "...");}
static void fprint_quoted(FILE *out, const char *s, int max_chars) { fputc('"', out); fprint_escaped(out, s, max_chars); fputc('"', out);}
static void fprint_operand(FILE *out, crprintf_compiled *prog, instruction_t *ins, bool compact) { switch (ins->op) { case OP_EMIT_LIT: { const char *s = prog->literals + ins->operand; fprint_quoted(out, s, compact ? 24 : -1); break; }
case OP_EMIT_FMT: { uint32_t lit_off = ins->operand & 0x0FFFFFFF; arg_class_t cls = (arg_class_t)(ins->operand >> 28); const char *s = prog->literals + lit_off; fprint_quoted(out, s, compact ? 24 : -1); fprintf(out, " (%s)", arg_class_name(cls)); break; }
case OP_SET_FG: case OP_SET_BG: if (compact) fprintf(out, "%s", color_name(ins->operand)); else fprintf(out, "%s (ANSI %u)", color_name(ins->operand), ins->operand); break;
case OP_SET_FG_RGB: case OP_SET_BG_RGB: fprintf(out, "#%02x%02x%02x", UNPACK_R(ins->operand), UNPACK_G(ins->operand), UNPACK_B(ins->operand)); break;
case OP_SET_BOLD: case OP_SET_DIM: case OP_SET_UL: case OP_SET_ITALIC: case OP_SET_STRIKE: case OP_SET_INVERT: fprintf(out, "%s", ins->operand ? "ON" : "OFF"); break;
case OP_PAD_BEGIN: case OP_RPAD_BEGIN: fprintf(out, "width=%u", ins->operand); break;
case OP_EMIT_SPACES: fprintf(out, "%u", ins->operand); break; case OP_EMIT_NEWLINES: fprintf(out, "%u", ins->operand); break;
case OP_NOP: case OP_STYLE_PUSH: case OP_STYLE_FLUSH: case OP_STYLE_RESET: case OP_STYLE_RESET_ALL: case OP_PAD_END: case OP_SNAPSHOT: case OP_HALT: break;
default: if (compact) { if (ins->operand) fprintf(out, "0x%x", ins->operand); } else fprintf(out, "0x%08x", ins->operand); break; }}
void crprintf_disasm(crprintf_compiled *prog, FILE *out) { fprintf(out, "; crprintf bytecode — %zu instructions, %zu bytes literal pool\n", prog->code_len, prog->lit_len); fprintf(out, "; %-4s %-16s %s\n", "addr", "opcode", "operand"); fprintf(out, "; ---- ---------------- -------\n");
for (size_t i = 0; i < prog->code_len; i++) { instruction_t *ins = &prog->code[i]; const char *name = (ins->op < OP_MAX) ? op_names[ins->op] : "???";
fprintf(out, " %04zu %-16s ", i, name); fprint_operand(out, prog, ins, false); fputc('\n', out); }}
void crprintf_hexdump(crprintf_compiled *prog, FILE *out) { fprintf(out, "; crprintf hex dump — %zu instructions, %zu bytes literal pool\n", prog->code_len, prog->lit_len); fprintf(out, "; %-4s %-26s %s\n", "addr", "bytes", "decoded"); fprintf(out, "; ---- ------------------------- --------\n");
for (size_t i = 0; i < prog->code_len; i++) { instruction_t *ins = &prog->code[i]; const uint8_t *raw = (const uint8_t *)ins; const char *name = (ins->op < OP_MAX) ? op_names[ins->op] : "???";
fprintf(out, " %04zu ", i); for (size_t b = 0; b < sizeof(instruction_t); b++) fprintf(out, "%02x ", raw[b]);
fprintf(out, " ; %s ", name); fprint_operand(out, prog, ins, true); fputc('\n', out); }
if (prog->lit_len > 0) { fprintf(out, "\n; literal pool (%zu bytes):\n", prog->lit_len); const uint8_t *lit = (const uint8_t *)prog->literals; for (size_t off = 0; off < prog->lit_len; off += 16) { fprintf(out, " %04zx ", off);
size_t end = off + 16; if (end > prog->lit_len) end = prog->lit_len; for (size_t b = off; b < off + 16; b++) { if (b < end) fprintf(out, "%02x ", lit[b]); else fprintf(out, " "); if (b == off + 7) fputc(' ', out); }
fprintf(out, " |"); for (size_t b = off; b < end; b++) fputc((lit[b] >= 0x20 && lit[b] < 0x7f) ? lit[b] : '.', out); fprintf(out, "|\n"); } }}