kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

generator.c (212221B)


      1 #include "meta_tables.h"
      2 #include "internal.h"
      3 
      4 /* Meta-parser value-channel carriers. The actions (cb_lift/cb_reduce) build the
      5  * real Ast directly; the only `Node` values left on the value stack are token
      6  * leaves (N_LEAF, carrying lexeme bytes + source Loc) and generic lists
      7  * (N_LIST, holding child pointers of whatever Ast type the rule collects). */
      8 typedef enum {
      9   N_LEAF,
     10   N_LIST,
     11 } NodeKind;
     12 
     13 typedef struct Node {
     14   GramgenContext* ctx;
     15   NodeKind kind;
     16   Loc loc; /* source location of a token leaf */
     17   char* text;
     18   size_t len;
     19   char* scalar_text;
     20   size_t scalar_len;
     21   struct Node** items; /* N_LIST: collected child pointers (cast per rule) */
     22   size_t nitems, cap;
     23 } Node;
     24 
     25 #define META_MAX_EXPECTED 32
     26 
     27 typedef struct {
     28   int err_seen;
     29   KitGramTokenKind err_tok;
     30   KitGramRuleId err_rule;
     31   KitGramTokenKind expected[META_MAX_EXPECTED];
     32   size_t nexpected;
     33   int expected_truncated;
     34   uint32_t err_line, err_col;
     35   char err_lexeme[80];
     36   size_t err_lexeme_len;
     37 } Meta;
     38 
     39 /* ---- bump arena ----
     40  * Each allocation is [16-byte header | user data], the header holding the
     41  * rounded data size. Allocation bumps `used`; the most-recent allocation can be
     42  * freed (rewind) or grown in place, which covers the common LIFO temporaries.
     43  * Other frees are no-ops; all space is reclaimed by mem_release. */
     44 #define ARENA_HDR 16u
     45 #define ARENA_ALIGN 16u
     46 #define ARENA_BLOCK_MIN (64u * 1024u)
     47 
     48 /* Adapter from the upstream realloc-for-everything allocator onto the kit heap
     49  * (KitContext.heap): a NULL pointer means allocate, a zero new size means free,
     50  * otherwise resize. Replaces the per-compile gramgen_allocator. */
     51 static void* gmem_realloc(KitHeap* h, void* p, size_t old, size_t n,
     52                           size_t align) {
     53   if (n == 0) {
     54     if (p) h->free(h, p, old);
     55     return NULL;
     56   }
     57   if (!p) return h->alloc(h, n, align);
     58   return h->realloc(h, p, old, n, align);
     59 }
     60 
     61 static size_t arena_round(size_t n) {
     62   return (n + (ARENA_ALIGN - 1u)) & ~(size_t)(ARENA_ALIGN - 1u);
     63 }
     64 
     65 static char* arena_base(ArenaBlock* b) {
     66   uintptr_t p = (uintptr_t)(b + 1);
     67   p = (p + (ARENA_ALIGN - 1u)) & ~(uintptr_t)(ARENA_ALIGN - 1u);
     68   return (char*)p;
     69 }
     70 
     71 static ArenaBlock* arena_new_block(GramgenMem* m, size_t need) {
     72   size_t cap = need > ARENA_BLOCK_MIN ? need : ARENA_BLOCK_MIN;
     73   /* +ARENA_ALIGN covers the alignment slack consumed by arena_base. */
     74   ArenaBlock* b =
     75       gmem_realloc(m->heap, NULL, 0, sizeof *b + cap + ARENA_ALIGN,
     76                        _Alignof(max_align_t));
     77   if (!b) return NULL;
     78   b->next = NULL;
     79   b->cap = cap;
     80   b->used = 0;
     81   return b;
     82 }
     83 
     84 void diag_vset(GramgenContext* ctx, Loc loc, const char* fmt, va_list ap) {
     85   if (!ctx || !ctx->diag) return;
     86   GramDiag* d = ctx->diag;
     87   d->path = loc.path;
     88   d->line = loc.line;
     89   d->col = loc.col;
     90   vsnprintf(d->message, sizeof d->message, fmt, ap);
     91   d->message[sizeof d->message - 1] = '\0';
     92 }
     93 
     94 void diag_set(GramgenContext* ctx, Loc loc, const char* fmt, ...) {
     95   va_list ap;
     96   va_start(ap, fmt);
     97   diag_vset(ctx, loc, fmt, ap);
     98   va_end(ap);
     99 }
    100 
    101 void die_oom(GramgenContext* ctx) {
    102   Loc loc = {0};
    103   diag_set(ctx, loc, "out of memory");
    104   if (ctx && ctx->can_jump) longjmp(ctx->jmp, 1);
    105   for (;;) {
    106   }
    107 }
    108 
    109 /* libkit operational-diagnostic bridge (src/core/diag.c). Forward-declared
    110  * rather than including "core/diag.h" so this TU's `Buf`/`Str`/`Loc` cannot
    111  * collide with kit's internal core types. */
    112 void kit_ctx_diagf(const KitContext* ctx, const char* fmt, ...);
    113 
    114 /* Flush the pending diagnostic (set by diag_set / parse errors) to the caller's
    115  * KitContext.diag sink, then clear it. Called on every error return from the
    116  * public API entry points; a no-op when no message was captured. */
    117 static void gram_flush_diag(GramgenContext* ctx) {
    118   GramDiag* d = ctx ? ctx->diag : NULL;
    119   if (!d || d->message[0] == '\0') return;
    120   if (d->path)
    121     kit_ctx_diagf(ctx->kit, "%s:%u:%u: %s", d->path, d->line, d->col,
    122                   d->message);
    123   else
    124     kit_ctx_diagf(ctx->kit, "%s", d->message);
    125   d->message[0] = '\0';
    126 }
    127 
    128 static GramgenMem* active_mem(GramgenContext* ctx) {
    129   return ctx->active ? ctx->active : &ctx->mem;
    130 }
    131 
    132 /* Freeing (list) allocator: one tracked block per allocation, immediate free.
    133  */
    134 static void* list_alloc(GramgenContext* ctx, GramgenMem* m, size_t size) {
    135   void* p = gmem_realloc(m->heap, NULL, 0, size, _Alignof(max_align_t));
    136   if (!p) die_oom(ctx);
    137   MemBlock* blk = gmem_realloc(m->heap, NULL, 0, sizeof *blk,
    138                                    _Alignof(max_align_t));
    139   if (!blk) {
    140     gmem_realloc(m->heap, p, size, 0, _Alignof(max_align_t));
    141     die_oom(ctx);
    142   }
    143   *blk = (MemBlock){.ptr = p, .size = size, .next = m->blocks};
    144   m->blocks = blk;
    145   return p;
    146 }
    147 
    148 void* xmalloc(GramgenContext* ctx, size_t n) {
    149   if (!ctx || !ctx->mem.heap) die_oom(ctx);
    150   GramgenMem* m = active_mem(ctx);
    151   if (m->kind == MEM_LIST) return list_alloc(ctx, m, n ? n : 1);
    152   size_t rn = arena_round(n ? n : 1);
    153   size_t need = ARENA_HDR + rn;
    154   ArenaBlock* b = m->cur;
    155   if (!b || b->cap - b->used < need) {
    156     ArenaBlock* nb = arena_new_block(m, need);
    157     if (!nb) die_oom(ctx);
    158     if (m->cur)
    159       m->cur->next = nb;
    160     else
    161       m->head = nb;
    162     m->cur = nb;
    163     b = nb;
    164   }
    165   char* hdr = arena_base(b) + b->used;
    166   *(size_t*)hdr = rn;
    167   b->used += need;
    168   return hdr + ARENA_HDR;
    169 }
    170 
    171 /* True iff p is the most-recent allocation in the current block (so it ends
    172  * exactly at the bump pointer), with *rn_out set to its rounded size. */
    173 static int arena_is_top(GramgenMem* m, void* p, size_t* rn_out) {
    174   ArenaBlock* b = m->cur;
    175   if (!b || !p) return 0;
    176   char* base = arena_base(b);
    177   char* up = (char*)p;
    178   if (up < base + ARENA_HDR || up > base + b->used) return 0;
    179   size_t rn = *(size_t*)(up - ARENA_HDR);
    180   if (up + rn != base + b->used) return 0;
    181   *rn_out = rn;
    182   return 1;
    183 }
    184 
    185 /* Find a tracked block by user pointer (MEM_LIST only); returns its predecessor
    186  * (or NULL if head) via *prev, and the block itself via the return value. */
    187 static MemBlock* list_find(GramgenMem* m, void* p, MemBlock** prev) {
    188   MemBlock* pv = NULL;
    189   for (MemBlock* b = m->blocks; b; b = b->next) {
    190     if (b->ptr == p) {
    191       *prev = pv;
    192       return b;
    193     }
    194     pv = b;
    195   }
    196   *prev = NULL;
    197   return NULL;
    198 }
    199 
    200 void* xrealloc(GramgenContext* ctx, void* p, size_t n) {
    201   if (!ctx || !ctx->mem.heap) die_oom(ctx);
    202   if (!p) return xmalloc(ctx, n);
    203   GramgenMem* m = active_mem(ctx);
    204   if (m->kind == MEM_LIST) {
    205     MemBlock *prev, *blk = list_find(m, p, &prev);
    206     if (!blk) die_oom(ctx);
    207     void* q = gmem_realloc(m->heap, p, blk->size, n ? n : 1,
    208                                _Alignof(max_align_t));
    209     if (!q) die_oom(ctx);
    210     blk->ptr = q;
    211     blk->size = n ? n : 1;
    212     return q;
    213   }
    214   size_t rn_new = arena_round(n ? n : 1);
    215   size_t rn_old;
    216   if (arena_is_top(m, p, &rn_old)) {
    217     /* Grow/shrink in place when the bump block has room. */
    218     char* base = arena_base(m->cur);
    219     size_t off = (size_t)((char*)p - ARENA_HDR - base);
    220     if (off + ARENA_HDR + rn_new <= m->cur->cap) {
    221       m->cur->used = off + ARENA_HDR + rn_new;
    222       *(size_t*)((char*)p - ARENA_HDR) = rn_new;
    223       return p;
    224     }
    225   } else {
    226     rn_old = *(size_t*)((char*)p - ARENA_HDR);
    227   }
    228   void* q = xmalloc(ctx, n);
    229   memcpy(q, p, rn_old < rn_new ? rn_old : rn_new);
    230   return q;
    231 }
    232 
    233 void* xcalloc(GramgenContext* ctx, size_t n, size_t size) {
    234   if (size && n > (SIZE_MAX / size)) die_oom(ctx);
    235   void* p = xmalloc(ctx, n * size);
    236   memset(p, 0, n * size);
    237   return p;
    238 }
    239 
    240 void xfree(GramgenContext* ctx, void* p) {
    241   if (!p || !ctx) return;
    242   GramgenMem* m = active_mem(ctx);
    243   if (m->kind == MEM_LIST) {
    244     MemBlock *prev, *blk = list_find(m, p, &prev);
    245     if (!blk) die_oom(ctx);
    246     if (prev)
    247       prev->next = blk->next;
    248     else
    249       m->blocks = blk->next;
    250     gmem_realloc(m->heap, blk->ptr, blk->size, 0,
    251                      _Alignof(max_align_t));
    252     gmem_realloc(m->heap, blk, sizeof *blk, 0, _Alignof(max_align_t));
    253     return;
    254   }
    255   size_t rn;
    256   if (arena_is_top(m, p, &rn)) /* LIFO free: rewind the bump pointer */
    257     m->cur->used = (size_t)((char*)p - ARENA_HDR - arena_base(m->cur));
    258   /* else: reclaimed wholesale at mem_release */
    259 }
    260 
    261 void mem_release(GramgenMem* m) {
    262   if (m->kind == MEM_LIST) {
    263     MemBlock* blk = m->blocks;
    264     while (blk) {
    265       MemBlock* next = blk->next;
    266       gmem_realloc(m->heap, blk->ptr, blk->size, 0,
    267                        _Alignof(max_align_t));
    268       gmem_realloc(m->heap, blk, sizeof *blk, 0, _Alignof(max_align_t));
    269       blk = next;
    270     }
    271     m->blocks = NULL;
    272     return;
    273   }
    274   ArenaBlock* b = m->head;
    275   while (b) {
    276     ArenaBlock* next = b->next;
    277     gmem_realloc(m->heap, b, sizeof *b + b->cap + ARENA_ALIGN, 0,
    278                      _Alignof(max_align_t));
    279     b = next;
    280   }
    281   m->head = m->cur = NULL;
    282 }
    283 
    284 /* Scratch region: x* allocations between scratch_enter and scratch_leave land
    285  * in a separate arena that scratch_release frees wholesale. Used to bound peak
    286  * for a self-contained phase (lexer construction) whose intermediates would
    287  * otherwise linger in the permanent arena until the compiled grammar is freed.
    288  * Anything that must survive the phase is copied back to the perm arena before
    289  * release. Not nestable. */
    290 void scratch_enter(GramgenContext* ctx) {
    291   ctx->scratch.heap = ctx->mem.heap;
    292   ctx->scratch.kind = MEM_LIST; /* incremental frees keep mid-build peak low */
    293   ctx->active = &ctx->scratch;
    294 }
    295 void scratch_leave(GramgenContext* ctx) {
    296   ctx->active = NULL; /* back to the permanent arena */
    297 }
    298 void scratch_release(GramgenContext* ctx) { mem_release(&ctx->scratch); }
    299 
    300 char* xstrndup(GramgenContext* ctx, const char* s, size_t n) {
    301   char* out = xmalloc(ctx, n + 1);
    302   memcpy(out, s, n);
    303   out[n] = '\0';
    304   return out;
    305 }
    306 
    307 char* xstrdup(GramgenContext* ctx, const char* s) {
    308   return xstrndup(ctx, s, strlen(s));
    309 }
    310 
    311 static uint64_t hash_bytes(const char* s, size_t len) {
    312   uint64_t h = KIT_GRAM_HASH_OFFSET_BASIS;
    313   for (size_t i = 0; i < len; i++) {
    314     h ^= (unsigned char)s[i];
    315     h *= KIT_GRAM_HASH_PRIME;
    316   }
    317   h ^= len;
    318   h *= KIT_GRAM_HASH_PRIME;
    319   return h;
    320 }
    321 
    322 static void intern_rehash(GramgenContext* ctx) {
    323   size_t old_n = ctx->intern_nbuckets;
    324   InternEntry** old = ctx->intern_buckets;
    325   size_t new_n = old_n ? old_n * 2 : 1024;
    326   InternEntry** buckets = xcalloc(ctx, new_n, sizeof *buckets);
    327   for (size_t i = 0; i < old_n; i++) {
    328     InternEntry* entry = old[i];
    329     while (entry) {
    330       InternEntry* next = entry->next;
    331       size_t bucket = (size_t)(entry->hash & (uint64_t)(new_n - 1));
    332       entry->next = buckets[bucket];
    333       buckets[bucket] = entry;
    334       entry = next;
    335     }
    336   }
    337   ctx->intern_buckets = buckets;
    338   ctx->intern_nbuckets = new_n;
    339 }
    340 
    341 char* intern_len(GramgenContext* ctx, const char* s, size_t len) {
    342   if (!ctx || !s) die_oom(ctx);
    343   if (!ctx->intern_nbuckets ||
    344       (ctx->intern_count + 1) * 2 > ctx->intern_nbuckets)
    345     intern_rehash(ctx);
    346   uint64_t hash = hash_bytes(s, len);
    347   size_t bucket = (size_t)(hash & (uint64_t)(ctx->intern_nbuckets - 1));
    348   for (InternEntry* entry = ctx->intern_buckets[bucket]; entry;
    349        entry = entry->next) {
    350     if (entry->hash == hash && entry->len == len &&
    351         memcmp(entry->s, s, len) == 0)
    352       return entry->s;
    353   }
    354   InternEntry* entry = xmalloc(ctx, sizeof *entry);
    355   entry->s = xstrndup(ctx, s, len);
    356   entry->len = len;
    357   entry->hash = hash;
    358   entry->next = ctx->intern_buckets[bucket];
    359   ctx->intern_buckets[bucket] = entry;
    360   ctx->intern_count++;
    361   return entry->s;
    362 }
    363 
    364 char* intern_c(GramgenContext* ctx, const char* s) {
    365   return intern_len(ctx, s, strlen(s));
    366 }
    367 
    368 static uint64_t hash_ptr_key(const char* key) {
    369   uintptr_t x = (uintptr_t)key;
    370   x >>= 3;
    371   x ^= x >> 33;
    372   x *= (uintptr_t)0xff51afd7ed558ccdull;
    373   x ^= x >> 33;
    374   return (uint64_t)x;
    375 }
    376 
    377 static void name_index_rehash(GramgenContext* ctx, NameIndexMap* map) {
    378   size_t old_cap = map->cap;
    379   const char** old_keys = map->keys;
    380   int* old_values = map->values;
    381   size_t new_cap = old_cap ? old_cap * 2 : 64;
    382   map->keys = xcalloc(ctx, new_cap, sizeof *map->keys);
    383   map->values = xmalloc(ctx, new_cap * sizeof *map->values);
    384   map->cap = new_cap;
    385   map->n = 0;
    386   for (size_t i = 0; i < old_cap; i++) {
    387     if (!old_keys[i]) continue;
    388     name_index_put(ctx, map, old_keys[i], old_values[i]);
    389   }
    390 }
    391 
    392 int name_index_find(const NameIndexMap* map, const char* key, int* out) {
    393   if (!map->cap || !key) return 0;
    394   size_t mask = map->cap - 1;
    395   size_t pos = (size_t)(hash_ptr_key(key) & (uint64_t)mask);
    396   for (;;) {
    397     const char* cur = map->keys[pos];
    398     if (!cur) return 0;
    399     if (cur == key) {
    400       if (out) *out = map->values[pos];
    401       return 1;
    402     }
    403     pos = (pos + 1) & mask;
    404   }
    405 }
    406 
    407 void name_index_put(GramgenContext* ctx, NameIndexMap* map, const char* key,
    408                     int value) {
    409   if (!key) die_oom(ctx);
    410   if (!map->cap || (map->n + 1) * 2 > map->cap) name_index_rehash(ctx, map);
    411   size_t mask = map->cap - 1;
    412   size_t pos = (size_t)(hash_ptr_key(key) & (uint64_t)mask);
    413   for (;;) {
    414     if (!map->keys[pos]) {
    415       map->keys[pos] = key;
    416       map->values[pos] = value;
    417       map->n++;
    418       return;
    419     }
    420     if (map->keys[pos] == key) {
    421       map->values[pos] = value;
    422       return;
    423     }
    424     pos = (pos + 1) & mask;
    425   }
    426 }
    427 
    428 char* xasprintf(GramgenContext* ctx, const char* fmt, ...) {
    429   va_list ap, aq;
    430   va_start(ap, fmt);
    431   va_copy(aq, ap);
    432   int n = vsnprintf(NULL, 0, fmt, ap);
    433   va_end(ap);
    434   if (n < 0) die_oom(ctx);
    435   char* out = xmalloc(ctx, (size_t)n + 1);
    436   vsnprintf(out, (size_t)n + 1, fmt, aq);
    437   va_end(aq);
    438   return out;
    439 }
    440 
    441 static void die_write_failed(Buf* b) {
    442   Loc loc = {.path = b->write_path, .line = 1, .col = 1};
    443   diag_set(b->ctx, loc, "codegen write failed");
    444   if (b->ctx && b->ctx->can_jump) longjmp(b->ctx->jmp, 1);
    445   for (;;) {
    446   }
    447 }
    448 
    449 void buf_init(GramgenContext* ctx, Buf* b) {
    450   b->ctx = ctx;
    451   b->cap = 64;
    452   b->len = 0;
    453   b->s = xmalloc(ctx, b->cap);
    454   b->s[0] = '\0';
    455   b->writer = NULL;
    456   b->write_path = NULL;
    457 }
    458 
    459 void buf_init_writer(GramgenContext* ctx, Buf* b, KitWriter* writer,
    460                      const char* path) {
    461   b->ctx = ctx;
    462   b->s = NULL;
    463   b->len = 0;
    464   b->cap = 0;
    465   b->writer = writer;
    466   b->write_path = path;
    467 }
    468 
    469 void buf_reserve(Buf* b, size_t add) {
    470   if (b->writer) return;
    471   size_t need = b->len + add + 1;
    472   if (need <= b->cap) return;
    473   while (b->cap < need) b->cap *= 2;
    474   b->s = xrealloc(b->ctx, b->s, b->cap);
    475 }
    476 
    477 void buf_appendn(Buf* b, const char* s, size_t n) {
    478   if (!n) return;
    479   if (b->writer) {
    480     if (kit_writer_write(b->writer, s, n) != KIT_OK) die_write_failed(b);
    481     b->len += n;
    482     return;
    483   }
    484   buf_reserve(b, n);
    485   memcpy(b->s + b->len, s, n);
    486   b->len += n;
    487   b->s[b->len] = '\0';
    488 }
    489 
    490 void buf_append(Buf* b, const char* s) { buf_appendn(b, s, strlen(s)); }
    491 
    492 /* Append an unsigned integer as decimal without going through printf. The
    493  * dense lexer table emits tens of thousands of small integers; a hand-rolled
    494  * formatter avoids a malloc + vsnprintf per cell. */
    495 void buf_append_uint(Buf* b, unsigned v) {
    496   char tmp[10];
    497   size_t n = sizeof tmp;
    498   tmp[--n] = (char)('0' + v % 10u);
    499   while ((v /= 10u)) tmp[--n] = (char)('0' + v % 10u);
    500   buf_appendn(b, tmp + n, sizeof tmp - n);
    501 }
    502 
    503 char* buf_take(Buf* b) { return b->s; }
    504 
    505 static Node* node_new(GramgenContext* ctx, NodeKind kind) {
    506   Node* n = xcalloc(ctx, 1, sizeof *n);
    507   if (!n) die_oom(ctx);
    508   n->ctx = ctx;
    509   n->kind = kind;
    510   return n;
    511 }
    512 
    513 static void list_append(Node* list, Node* item) {
    514   if (list->nitems == list->cap) {
    515     list->cap = list->cap ? list->cap * 2 : 4;
    516     list->items =
    517         xrealloc(list->ctx, list->items, list->cap * sizeof *list->items);
    518   }
    519   list->items[list->nitems++] = item;
    520 }
    521 
    522 static char* quote_len(GramgenContext* ctx, const char* s, size_t n,
    523                        bool use_octal) {
    524   Buf b;
    525   buf_init(ctx, &b);
    526   buf_append(&b, "\"");
    527   for (size_t i = 0; i < n; i++) {
    528     unsigned char ch = (unsigned char)s[i];
    529     switch (ch) {
    530       case '\\':
    531         buf_append(&b, "\\\\");
    532         break;
    533       case '"':
    534         buf_append(&b, "\\\"");
    535         break;
    536       case '\n':
    537         buf_append(&b, "\\n");
    538         break;
    539       case '\r':
    540         buf_append(&b, "\\r");
    541         break;
    542       case '\t':
    543         buf_append(&b, "\\t");
    544         break;
    545       default:
    546         if (ch >= 32 && ch <= 126) {
    547           char c = (char)ch;
    548           buf_appendn(&b, &c, 1);
    549         } else {
    550           char tmp[5];
    551           if (use_octal)
    552             snprintf(tmp, sizeof tmp, "\\%03o", ch);
    553           else
    554             snprintf(tmp, sizeof tmp, "\\x%02x", ch);
    555           buf_append(&b, tmp);
    556         }
    557         break;
    558     }
    559   }
    560   buf_append(&b, "\"");
    561   return buf_take(&b);
    562 }
    563 
    564 /* ---- AST -> s-expression renderer (the `--dump-sexpr` debug surface) --------
    565  * This is the ONLY consumer of the s-expression form; the compile path uses the
    566  * Ast directly. Output is held byte-identical with gen/gramgen.py's renderer.
    567  */
    568 static void quote_str_into(Buf* b, Str s) {
    569   char* q = quote_len(b->ctx, s.s, s.len, false);
    570   buf_append(b, q);
    571   xfree(b->ctx, q);
    572 }
    573 
    574 static void ast_alt_sexpr(Buf* b, const AstAlt* alt);
    575 
    576 static void ast_node_sexpr(Buf* b, const AstNode* node) {
    577   switch (node->kind) {
    578     case AST_NAME:
    579       buf_append(b, "(name ");
    580       buf_appendn(b, node->value.s, node->value.len);
    581       buf_append(b, ")");
    582       break;
    583     case AST_LITERAL:
    584       buf_append(b, "(literal ");
    585       quote_str_into(b, node->value);
    586       buf_append(b, ")");
    587       break;
    588     case AST_GROUP:
    589       buf_append(b, "(group ");
    590       ast_alt_sexpr(b, node->alts);
    591       buf_append(b, ")");
    592       break;
    593     case AST_OPT:
    594       buf_append(b, "(opt ");
    595       ast_alt_sexpr(b, node->alts);
    596       buf_append(b, ")");
    597       break;
    598     case AST_REP:
    599       buf_append(b, "(rep ");
    600       ast_alt_sexpr(b, node->alts);
    601       buf_append(b, ")");
    602       break;
    603   }
    604 }
    605 
    606 static void ast_seq_sexpr(Buf* b, const AstSeq* seq) {
    607   buf_append(b, "(seq");
    608   for (size_t i = 0; i < seq->nitems; i++) {
    609     buf_append(b, " ");
    610     ast_node_sexpr(b, seq->items[i]);
    611   }
    612   buf_append(b, ")");
    613 }
    614 
    615 static void ast_alt_sexpr(Buf* b, const AstAlt* alt) {
    616   buf_append(b, "(alt");
    617   for (size_t i = 0; i < alt->nseqs; i++) {
    618     buf_append(b, " ");
    619     ast_seq_sexpr(b, alt->seqs[i]);
    620   }
    621   buf_append(b, ")");
    622 }
    623 
    624 static void ast_pratt_sexpr(Buf* b, const AstPrattSpec* spec) {
    625   buf_append(b, "(pratt");
    626   for (size_t i = 0; i < spec->nlines; i++) {
    627     const AstPrattLine* line = spec->lines[i];
    628     buf_append(b, " (");
    629     buf_append(b, line->kind);
    630     for (size_t j = 0; j < line->natoms; j++) {
    631       const AstPrattAtom* atom = line->atoms[j];
    632       if (atom->kind == PRATT_NAME) {
    633         buf_append(b, " (name ");
    634         buf_appendn(b, atom->value.s, atom->value.len);
    635         buf_append(b, ")");
    636       } else {
    637         buf_append(b, " (literal ");
    638         quote_str_into(b, atom->value);
    639         buf_append(b, ")");
    640       }
    641     }
    642     buf_append(b, ")");
    643   }
    644   buf_append(b, ")");
    645 }
    646 
    647 static void ast_lex_alt_sexpr(Buf* b, const AstLexAlt* alt);
    648 
    649 static void ast_lex_node_sexpr(Buf* b, const AstLexNode* node) {
    650   switch (node->kind) {
    651     case LEX_LITERAL:
    652       buf_append(b, "(literal ");
    653       quote_str_into(b, node->value);
    654       buf_append(b, " ");
    655       quote_str_into(b,
    656                      node->scalar_value.s ? node->scalar_value : node->value);
    657       buf_append(b, ")");
    658       break;
    659     case LEX_CLASS:
    660       buf_append(b, "(class ");
    661       quote_str_into(b, node->value);
    662       buf_append(b, ")");
    663       break;
    664     case LEX_PROP:
    665       buf_append(b, "(prop ");
    666       quote_str_into(b, node->value);
    667       buf_append(b, ")");
    668       break;
    669     case LEX_ANY:
    670       buf_append(b, "(any)");
    671       break;
    672     case LEX_ANCHOR:
    673       buf_append(b, "(anchor ");
    674       buf_appendn(b, node->value.s, node->value.len);
    675       buf_append(b, ")");
    676       break;
    677     case LEX_NAME:
    678       buf_append(b, "(name ");
    679       buf_appendn(b, node->value.s, node->value.len);
    680       buf_append(b, ")");
    681       break;
    682     case LEX_GROUP:
    683       buf_append(b, "(group ");
    684       ast_lex_alt_sexpr(b, node->alts);
    685       buf_append(b, ")");
    686       break;
    687     case LEX_OPT:
    688       buf_append(b, "(opt ");
    689       ast_lex_alt_sexpr(b, node->alts);
    690       buf_append(b, ")");
    691       break;
    692     case LEX_REP:
    693       buf_append(b, "(rep ");
    694       ast_lex_alt_sexpr(b, node->alts);
    695       buf_append(b, ")");
    696       break;
    697     case LEX_REPEAT:
    698       buf_append(b, "(repeat ");
    699       quote_str_into(b, node->value);
    700       buf_append(b, " ");
    701       ast_lex_alt_sexpr(b, node->alts);
    702       buf_append(b, ")");
    703       break;
    704   }
    705 }
    706 
    707 static void ast_lex_seq_sexpr(Buf* b, const AstLexSeq* seq) {
    708   buf_append(b, "(seq");
    709   for (size_t i = 0; i < seq->nitems; i++) {
    710     buf_append(b, " ");
    711     ast_lex_node_sexpr(b, seq->items[i]);
    712   }
    713   buf_append(b, ")");
    714 }
    715 
    716 static void ast_lex_alt_sexpr(Buf* b, const AstLexAlt* alt) {
    717   buf_append(b, "(alt");
    718   for (size_t i = 0; i < alt->nseqs; i++) {
    719     buf_append(b, " ");
    720     ast_lex_seq_sexpr(b, alt->seqs[i]);
    721   }
    722   buf_append(b, ")");
    723 }
    724 
    725 static void ast_lex_line_sexpr(Buf* b, const AstLexLine* line) {
    726   if (line->kind == LEX_LINE_KEYWORDS) {
    727     buf_append(b, "(keywords ");
    728     buf_append(b, line->name);
    729     for (size_t i = 0; i < line->nkw_entries; i++) {
    730       const AstKwEntry* e = &line->kw_entries[i];
    731       if (e->name && e->has_literal) {
    732         buf_append(b, " (kwfull ");
    733         buf_append(b, e->name);
    734         buf_append(b, " ");
    735         quote_str_into(b, e->literal);
    736         buf_append(b, ")");
    737       } else if (e->name) {
    738         buf_append(b, " (kwname ");
    739         buf_append(b, e->name);
    740         buf_append(b, ")");
    741       } else {
    742         buf_append(b, " (kwlit ");
    743         quote_str_into(b, e->literal);
    744         buf_append(b, ")");
    745       }
    746     }
    747     buf_append(b, ")");
    748     return;
    749   }
    750   const char* head = line->kind == LEX_LINE_TOKEN  ? "token"
    751                      : line->kind == LEX_LINE_SKIP ? "skip"
    752                                                    : "def";
    753   buf_append(b, "(");
    754   buf_append(b, head);
    755   buf_append(b, " ");
    756   buf_append(b, line->name);
    757   buf_append(b, " ");
    758   ast_lex_alt_sexpr(b, line->alts);
    759   buf_append(b, ")");
    760 }
    761 
    762 static void ast_lex_block_sexpr(Buf* b, const AstLexBlock* block) {
    763   buf_append(b, "(lex ");
    764   buf_append(b, block->name);
    765   buf_append(b, " ");
    766   buf_append(b, block->mode == KIT_GRAM_LEX_INPUT_UTF8     ? "utf8"
    767                 : block->mode == KIT_GRAM_LEX_INPUT_TOKENS ? "tokens"
    768                                                        : "bytes");
    769   for (size_t i = 0; i < block->nlines; i++) {
    770     buf_append(b, " ");
    771     ast_lex_line_sexpr(b, block->lines[i]);
    772   }
    773   buf_append(b, ")");
    774 }
    775 
    776 static void ast_rule_sexpr(Buf* b, const AstRule* rule) {
    777   buf_append(b, "(rule ");
    778   buf_append(b, rule->name);
    779   buf_append(b, " ");
    780   if (rule->pratt)
    781     ast_pratt_sexpr(b, rule->pratt);
    782   else
    783     ast_alt_sexpr(b, rule->alts);
    784   buf_append(b, ")");
    785 }
    786 
    787 typedef struct {
    788   GramgenContext* ctx;
    789   const char* path;  /* source path, stamped into every token Loc */
    790   ParsedGrammar* pg; /* accumulated as top-level items reduce */
    791   Meta meta;
    792 } ParseUd;
    793 
    794 static void pratt_line_append(AstPrattLine* line, AstPrattAtom* atom);
    795 static void pratt_spec_append(AstPrattSpec* spec, AstPrattLine* line);
    796 static void lex_block_append(AstLexBlock* block, AstLexLine* line);
    797 static void parsed_add_rule(ParsedGrammar* g, AstRule* rule);
    798 static void parsed_add_token_decl(ParsedGrammar* g, TokenDecl* decl);
    799 static void parsed_add_lex_block(ParsedGrammar* g, AstLexBlock* block);
    800 
    801 static KitGramSem cb_lift(void* ud, KitGramToken t) {
    802   ParseUd* pu = ud;
    803   GramgenContext* ctx = pu->ctx;
    804   Node* n = node_new(ctx, N_LEAF);
    805   n->loc = (Loc){.path = pu->path, .line = t.line, .col = t.col};
    806   /* Token bytes are interned to a single canonical, NUL-terminated copy. That
    807    * dedups the many repeated names a grammar contains (every rule reference)
    808    * and gives them the pointer identity the builder's name indexes key on — so
    809    * cb_reduce can use the bytes directly with no further copy. (Literals are
    810    * value-compared, but interning them is harmless and dedups duplicates.)
    811    * String literals arrive from the lexer hook as "<bytelen>:<value><scalar>";
    812    * the value and scalar halves are interned separately. */
    813   if (t.kind == KIT_GRAM_META_TOK_STRING && t.lexeme && t.len) {
    814     size_t prefix = 0;
    815     while (prefix < t.len && t.lexeme[prefix] >= '0' && t.lexeme[prefix] <= '9')
    816       prefix++;
    817     if (prefix < t.len && t.lexeme[prefix] == ':') {
    818       size_t byte_len = 0;
    819       for (size_t i = 0; i < prefix; i++)
    820         byte_len = byte_len * 10 + (size_t)(t.lexeme[i] - '0');
    821       size_t start = prefix + 1;
    822       if (start + byte_len <= t.len) {
    823         n->text = intern_len(ctx, t.lexeme + start, byte_len);
    824         n->len = byte_len;
    825         n->scalar_len = t.len - start - byte_len;
    826         n->scalar_text =
    827             intern_len(ctx, t.lexeme + start + byte_len, n->scalar_len);
    828         return n;
    829       }
    830     }
    831   }
    832   n->text = intern_len(ctx, t.lexeme ? t.lexeme : "", t.len);
    833   n->len = t.len;
    834   return n;
    835 }
    836 
    837 static KitGramSem cb_list_empty(void* ud) {
    838   ParseUd* pu = ud;
    839   return node_new(pu->ctx, N_LIST);
    840 }
    841 
    842 static KitGramSem cb_list_push(void* ud, KitGramSem list, KitGramSem item) {
    843   (void)ud;
    844   list_append((Node*)list, (Node*)item);
    845   return list;
    846 }
    847 
    848 static KitGramSem cb_opt_none(void* ud) {
    849   (void)ud;
    850   return NULL;
    851 }
    852 
    853 static KitGramSem cb_opt_some(void* ud, KitGramSem item) {
    854   (void)ud;
    855   return item;
    856 }
    857 
    858 /* A leaf's bytes — already interned to a canonical, NUL-terminated pointer by
    859  * cb_lift, so this is both the value form (Str) and, via n->text, the name form
    860  * (char *) the builder's pointer-keyed name indexes use. */
    861 static Str leaf_str(const Node* n) {
    862   return (Str){.s = n->text, .len = n->len};
    863 }
    864 
    865 /* Build a single-symbol AstAlt `(alt (seq node))`, used to wrap a quantified
    866  * primary into an opt/rep/repeat node body. */
    867 static AstAlt* single_node_alt(GramgenContext* ctx, AstNode* node) {
    868   AstSeq* seq = ast_seq_new(ctx, node->loc);
    869   ast_seq_append(seq, node);
    870   AstAlt* alt = ast_alt_new(ctx, node->loc);
    871   ast_alt_append(alt, seq);
    872   return alt;
    873 }
    874 static AstLexAlt* single_lex_node_alt(GramgenContext* ctx, AstLexNode* node) {
    875   AstLexSeq* seq = ast_lex_seq_new(ctx, node->loc);
    876   ast_lex_seq_append(seq, node);
    877   AstLexAlt* alt = ast_lex_alt_new(ctx, node->loc);
    878   ast_lex_alt_append(alt, seq);
    879   return alt;
    880 }
    881 
    882 static KitGramSem cb_reduce(void* ud, KitGramRuleId r, int prod, KitGramSem* k,
    883                           size_t n) {
    884   ParseUd* pu = ud;
    885   GramgenContext* ctx = pu->ctx;
    886   (void)n;
    887   switch (r) {
    888     case KIT_GRAM_META_R_grammar:
    889     case KIT_GRAM_META_R_item:
    890     case KIT_GRAM_META_R_decl:
    891       return NULL; /* top-level items self-append to pu->pg */
    892 
    893     case KIT_GRAM_META_R_token_decl: { /* "%token" NAME "=" STRING ";" */
    894       Node *kw = k[0], *name = k[1], *lit = k[3];
    895       TokenDecl* d = xcalloc(ctx, 1, sizeof *d);
    896       if (!d) die_oom(ctx);
    897       d->name = name->text;
    898       d->literal = leaf_str(lit);
    899       d->loc = kw->loc;
    900       parsed_add_token_decl(pu->pg, d);
    901       return NULL;
    902     }
    903     case KIT_GRAM_META_R_pratt_rule: { /* "%pratt" NAME "{" pratt_line* "}" */
    904       Node *name = k[1], *lines = k[3];
    905       AstPrattSpec* spec = xcalloc(ctx, 1, sizeof *spec);
    906       if (!spec) die_oom(ctx);
    907       spec->ctx = ctx;
    908       for (size_t i = 0; i < lines->nitems; i++)
    909         pratt_spec_append(spec, (AstPrattLine*)lines->items[i]);
    910       AstRule* rule = xcalloc(ctx, 1, sizeof *rule);
    911       if (!rule) die_oom(ctx);
    912       rule->name = name->text;
    913       rule->loc = name->loc;
    914       rule->pratt = spec;
    915       parsed_add_rule(pu->pg, rule);
    916       return NULL;
    917     }
    918     case KIT_GRAM_META_R_pratt_line: { /* NAME pratt_atom pratt_atom* */
    919       Node *kind = k[0], *rest = k[2];
    920       AstPrattLine* line = xcalloc(ctx, 1, sizeof *line);
    921       if (!line) die_oom(ctx);
    922       line->ctx = ctx;
    923       line->kind = kind->text;
    924       line->loc = kind->loc;
    925       pratt_line_append(line, (AstPrattAtom*)k[1]);
    926       for (size_t i = 0; i < rest->nitems; i++)
    927         pratt_line_append(line, (AstPrattAtom*)rest->items[i]);
    928       return line;
    929     }
    930     case KIT_GRAM_META_R_pratt_atom: { /* NAME | STRING */
    931       Node* tok = k[0];
    932       AstPrattAtom* atom = xcalloc(ctx, 1, sizeof *atom);
    933       if (!atom) die_oom(ctx);
    934       atom->kind = prod == 0 ? PRATT_NAME : PRATT_LITERAL;
    935       atom->loc = tok->loc;
    936       atom->value = leaf_str(tok);
    937       return atom;
    938     }
    939 
    940     case KIT_GRAM_META_R_lex_block: { /* "%lex" lex_name lex_mode "{" lex_line* "}"
    941                                    */
    942       Node *kw = k[0], *name = k[1], *mode = k[2], *lines = k[4];
    943       AstLexBlock* block = xcalloc(ctx, 1, sizeof *block);
    944       if (!block) die_oom(ctx);
    945       block->ctx = ctx;
    946       block->name = name ? name->text : intern_c(ctx, "main");
    947       block->loc = kw->loc;
    948       block->mode = KIT_GRAM_LEX_INPUT_BYTES;
    949       if (mode) {
    950         if (strcmp(mode->text, "utf8") == 0)
    951           block->mode = KIT_GRAM_LEX_INPUT_UTF8;
    952         else
    953           kit_gram_error(ctx, mode->loc, "unknown %%lex mode '%s'", mode->text);
    954       }
    955       for (size_t i = 0; i < lines->nitems; i++)
    956         lex_block_append(block, (AstLexLine*)lines->items[i]);
    957       parsed_add_lex_block(pu->pg, block);
    958       return NULL;
    959     }
    960     case KIT_GRAM_META_R_machine_block: { /* "%machine" NAME "{" lex_line* "}" */
    961       Node *kw = k[0], *name = k[1], *lines = k[3];
    962       AstLexBlock* block = xcalloc(ctx, 1, sizeof *block);
    963       if (!block) die_oom(ctx);
    964       block->ctx = ctx;
    965       block->name = name->text;
    966       block->loc = kw->loc;
    967       block->mode = KIT_GRAM_LEX_INPUT_TOKENS;
    968       for (size_t i = 0; i < lines->nitems; i++)
    969         lex_block_append(block, (AstLexLine*)lines->items[i]);
    970       parsed_add_lex_block(pu->pg, block);
    971       return NULL;
    972     }
    973     case KIT_GRAM_META_R_lex_name: /* NAME | %empty */
    974       return prod == 0 ? k[0] : NULL;
    975     case KIT_GRAM_META_R_lex_mode: /* ":" NAME | %empty */
    976       return prod == 0 ? k[1] : NULL;
    977     case KIT_GRAM_META_R_lex_line:
    978       return k[0];
    979     case KIT_GRAM_META_R_lex_rule:   /* NAME "=" lex_alt ";" */
    980     case KIT_GRAM_META_R_skip_rule:  /* "%skip" NAME "=" lex_alt ";" */
    981     case KIT_GRAM_META_R_def_rule: { /* "%def" NAME "=" lex_alt ";" */
    982       AstLexLine* line = xcalloc(ctx, 1, sizeof *line);
    983       if (!line) die_oom(ctx);
    984       if (r == KIT_GRAM_META_R_lex_rule) {
    985         Node* name = k[0];
    986         line->kind = LEX_LINE_TOKEN;
    987         line->name = name->text;
    988         line->loc = name->loc;
    989         line->alts = (AstLexAlt*)k[2];
    990       } else {
    991         Node *kw = k[0], *name = k[1];
    992         line->kind = r == KIT_GRAM_META_R_skip_rule ? LEX_LINE_SKIP : LEX_LINE_DEF;
    993         line->name = name->text;
    994         line->loc = kw->loc;
    995         line->alts = (AstLexAlt*)k[3];
    996       }
    997       return line;
    998     }
    999     case KIT_GRAM_META_R_keywords_block: { /* "%keywords" NAME "{" kw_entry* "}" */
   1000       Node *kw = k[0], *host = k[1], *entries = k[3];
   1001       AstLexLine* line = xcalloc(ctx, 1, sizeof *line);
   1002       if (!line) die_oom(ctx);
   1003       line->kind = LEX_LINE_KEYWORDS;
   1004       line->name = host->text;
   1005       line->loc = kw->loc;
   1006       line->nkw_entries = entries->nitems;
   1007       if (entries->nitems) {
   1008         line->kw_entries =
   1009             xmalloc(ctx, entries->nitems * sizeof *line->kw_entries);
   1010         for (size_t i = 0; i < entries->nitems; i++)
   1011           line->kw_entries[i] = *(AstKwEntry*)entries->items[i];
   1012       }
   1013       return line;
   1014     }
   1015     case KIT_GRAM_META_R_kw_entry: { /* NAME kw_value? ";" | STRING ";" */
   1016       AstKwEntry* e = xcalloc(ctx, 1, sizeof *e);
   1017       if (!e) die_oom(ctx);
   1018       if (prod == 0) {
   1019         Node *name = k[0], *value = k[1]; /* value: STRING leaf or NULL */
   1020         e->name = name->text;
   1021         e->loc = name->loc;
   1022         if (value) {
   1023           e->literal = leaf_str(value);
   1024           e->has_literal = 1;
   1025         }
   1026       } else {
   1027         Node* lit = k[0];
   1028         e->literal = leaf_str(lit);
   1029         e->has_literal = 1;
   1030         e->loc = lit->loc;
   1031       }
   1032       return e;
   1033     }
   1034     case KIT_GRAM_META_R_kw_value:
   1035       return k[1]; /* the STRING leaf */
   1036 
   1037     case KIT_GRAM_META_R_lex_alt: { /* lex_seq lex_alt_tail* */
   1038       AstLexSeq* seq = k[0];
   1039       Node* tails = k[1];
   1040       AstLexAlt* alt = ast_lex_alt_new(ctx, seq->loc);
   1041       ast_lex_alt_append(alt, seq);
   1042       for (size_t i = 0; i < tails->nitems; i++)
   1043         ast_lex_alt_append(alt, (AstLexSeq*)tails->items[i]);
   1044       return alt;
   1045     }
   1046     case KIT_GRAM_META_R_lex_alt_tail:
   1047       return k[1];
   1048     case KIT_GRAM_META_R_lex_seq: { /* lex_factor lex_factor* */
   1049       AstLexNode* first = k[0];
   1050       Node* rest = k[1];
   1051       AstLexSeq* seq = ast_lex_seq_new(ctx, first->loc);
   1052       ast_lex_seq_append(seq, first);
   1053       for (size_t i = 0; i < rest->nitems; i++)
   1054         ast_lex_seq_append(seq, (AstLexNode*)rest->items[i]);
   1055       return seq;
   1056     }
   1057     case KIT_GRAM_META_R_lex_factor: { /* lex_primary lex_quant? */
   1058       AstLexNode* prim = k[0];
   1059       Node* quant = k[1];
   1060       if (!quant) return prim;
   1061       AstLexNodeKind kind;
   1062       Str count = {0};
   1063       if (quant->text[0] == '?')
   1064         kind = LEX_OPT;
   1065       else if (quant->text[0] == '*')
   1066         kind = LEX_REP;
   1067       else if (quant->text[0] == '+') {
   1068         kind = LEX_REPEAT;
   1069         count = (Str){.s = (char*)"1,", .len = 2};
   1070       } else {
   1071         kind = LEX_REPEAT;
   1072         count = (Str){.s = quant->text + 1,
   1073                       .len = quant->len >= 2 ? quant->len - 2 : 0};
   1074       }
   1075       AstLexNode* out = ast_lex_node_new(ctx, kind, quant->loc);
   1076       if (kind == LEX_REPEAT) out->value = count;
   1077       out->alts = single_lex_node_alt(ctx, prim);
   1078       return out;
   1079     }
   1080     case KIT_GRAM_META_R_lex_quant:
   1081       /* "?" | "*" | "+" | REPEAT — pass the quantifier token to lex_factor. */
   1082       return k[0];
   1083     case KIT_GRAM_META_R_lex_primary: {
   1084       Node* tok = k[0];
   1085       AstLexNode* node;
   1086       switch (prod) {
   1087         case 0: /* STRING */
   1088           node = ast_lex_node_new(ctx, LEX_LITERAL, tok->loc);
   1089           node->value = leaf_str(tok);
   1090           node->scalar_value = tok->scalar_text ? (Str){.s = tok->scalar_text,
   1091                                                         .len = tok->scalar_len}
   1092                                                 : leaf_str(tok);
   1093           return node;
   1094         case 1: /* CHAR_CLASS */
   1095           node = ast_lex_node_new(ctx, LEX_CLASS, tok->loc);
   1096           node->value = leaf_str(tok);
   1097           return node;
   1098         case 2: /* UNICODE_PROP */
   1099           node = ast_lex_node_new(ctx, LEX_PROP, tok->loc);
   1100           node->value = leaf_str(tok);
   1101           return node;
   1102         case 3: { /* SHORTHAND "\d" -> class "[\d]" */
   1103           node = ast_lex_node_new(ctx, LEX_CLASS, tok->loc);
   1104           char* raw = xasprintf(ctx, "[%s]", tok->text);
   1105           node->value = (Str){.s = raw, .len = strlen(raw)};
   1106           return node;
   1107         }
   1108         case 4: /* "." */
   1109           return ast_lex_node_new(ctx, LEX_ANY, tok->loc);
   1110         case 5: /* NAME */
   1111           node = ast_lex_node_new(ctx, LEX_NAME, tok->loc);
   1112           node->value = leaf_str(tok);
   1113           return node;
   1114         case 6: /* "(" lex_alt ")" */
   1115           node = ast_lex_node_new(ctx, LEX_GROUP, ((AstLexAlt*)k[1])->loc);
   1116           node->alts = (AstLexAlt*)k[1];
   1117           return node;
   1118         default: { /* ANCHOR: tag by the last byte (start A/^, end z/$) */
   1119           node = ast_lex_node_new(ctx, LEX_ANCHOR, tok->loc);
   1120           node->value =
   1121               (Str){.s = tok->len ? tok->text + tok->len - 1 : tok->text,
   1122                     .len = tok->len ? 1 : 0};
   1123           return node;
   1124         }
   1125       }
   1126     }
   1127 
   1128     case KIT_GRAM_META_R_rule: { /* NAME "=" alt ";" */
   1129       Node* name = k[0];
   1130       AstRule* rule = xcalloc(ctx, 1, sizeof *rule);
   1131       if (!rule) die_oom(ctx);
   1132       rule->name = name->text;
   1133       rule->loc = name->loc;
   1134       rule->alts = (AstAlt*)k[2];
   1135       parsed_add_rule(pu->pg, rule);
   1136       return NULL;
   1137     }
   1138     case KIT_GRAM_META_R_alt: { /* seq alt_tail* */
   1139       AstSeq* seq = k[0];
   1140       Node* tails = k[1];
   1141       AstAlt* alt = ast_alt_new(ctx, seq->loc);
   1142       ast_alt_append(alt, seq);
   1143       for (size_t i = 0; i < tails->nitems; i++)
   1144         ast_alt_append(alt, (AstSeq*)tails->items[i]);
   1145       return alt;
   1146     }
   1147     case KIT_GRAM_META_R_alt_tail:
   1148       return k[1];
   1149     case KIT_GRAM_META_R_seq: { /* "%empty" | factor* */
   1150       if (prod == 0) {
   1151         Node* empty = k[0];
   1152         return ast_seq_new(ctx, empty->loc);
   1153       }
   1154       Node* factors = k[0];
   1155       Loc loc = factors->nitems ? ((AstNode*)factors->items[0])->loc : (Loc){0};
   1156       AstSeq* seq = ast_seq_new(ctx, loc);
   1157       for (size_t i = 0; i < factors->nitems; i++)
   1158         ast_seq_append(seq, (AstNode*)factors->items[i]);
   1159       return seq;
   1160     }
   1161     case KIT_GRAM_META_R_factor: { /* primary quant? */
   1162       AstNode* prim = k[0];
   1163       Node* quant = k[1];
   1164       if (!quant) return prim;
   1165       AstNode* out = ast_node_new(
   1166           ctx, quant->text[0] == '?' ? AST_OPT : AST_REP, quant->loc);
   1167       out->alts = single_node_alt(ctx, prim);
   1168       return out;
   1169     }
   1170     case KIT_GRAM_META_R_primary: {
   1171       Node* tok = k[0];
   1172       AstNode* node;
   1173       switch (prod) {
   1174         case 0: /* NAME */
   1175           node = ast_node_new(ctx, AST_NAME, tok->loc);
   1176           node->value = leaf_str(tok);
   1177           return node;
   1178         case 1: /* STRING */
   1179           node = ast_node_new(ctx, AST_LITERAL, tok->loc);
   1180           node->value = leaf_str(tok);
   1181           return node;
   1182         default: /* "(" alt ")" */
   1183           node = ast_node_new(ctx, AST_GROUP, ((AstAlt*)k[1])->loc);
   1184           node->alts = (AstAlt*)k[1];
   1185           return node;
   1186       }
   1187     }
   1188     case KIT_GRAM_META_R_quant:
   1189       /* "?" | "*" — pass the quantifier token to factor. */
   1190       return k[0];
   1191   }
   1192   return NULL;
   1193 }
   1194 
   1195 static KitGramErrorAction cb_error(void* ud, const KitGramError* e) {
   1196   ParseUd* pu = ud;
   1197   Meta* m = &pu->meta;
   1198   m->err_seen = 1;
   1199   m->err_tok = e->found.kind;
   1200   m->err_rule = e->in_rule;
   1201   m->err_line = e->found.line;
   1202   m->err_col = e->found.col;
   1203   m->nexpected =
   1204       e->nexpected < META_MAX_EXPECTED ? e->nexpected : META_MAX_EXPECTED;
   1205   m->expected_truncated = e->nexpected > META_MAX_EXPECTED;
   1206   for (size_t i = 0; i < m->nexpected; i++) m->expected[i] = e->expected[i];
   1207   m->err_lexeme_len = 0;
   1208   if (e->found.lexeme && e->found.len) {
   1209     m->err_lexeme_len = e->found.len < sizeof m->err_lexeme - 1
   1210                             ? e->found.len
   1211                             : sizeof m->err_lexeme - 1;
   1212     memcpy(m->err_lexeme, e->found.lexeme, m->err_lexeme_len);
   1213   }
   1214   m->err_lexeme[m->err_lexeme_len] = '\0';
   1215   return KIT_GRAM_ABORT;
   1216 }
   1217 
   1218 static void append_str(char* buf, size_t cap, size_t* off, const char* s) {
   1219   if (!buf || !cap || !s) return;
   1220   while (*s && *off + 1 < cap) buf[(*off)++] = *s++;
   1221   buf[*off < cap ? *off : cap - 1] = '\0';
   1222 }
   1223 
   1224 static void append_fmt(char* buf, size_t cap, size_t* off, const char* fmt,
   1225                        ...) {
   1226   if (!buf || !cap || *off >= cap) return;
   1227   va_list ap;
   1228   va_start(ap, fmt);
   1229   int n = vsnprintf(buf + *off, cap - *off, fmt, ap);
   1230   va_end(ap);
   1231   if (n < 0) return;
   1232   size_t wrote = (size_t)n;
   1233   if (wrote >= cap - *off)
   1234     *off = cap - 1;
   1235   else
   1236     *off += wrote;
   1237 }
   1238 
   1239 static void append_quoted(char* buf, size_t cap, size_t* off, const char* s,
   1240                           size_t n) {
   1241   append_str(buf, cap, off, "\"");
   1242   for (size_t i = 0; i < n; i++) {
   1243     unsigned char ch = (unsigned char)s[i];
   1244     switch (ch) {
   1245       case '\\':
   1246         append_str(buf, cap, off, "\\\\");
   1247         break;
   1248       case '"':
   1249         append_str(buf, cap, off, "\\\"");
   1250         break;
   1251       case '\n':
   1252         append_str(buf, cap, off, "\\n");
   1253         break;
   1254       case '\r':
   1255         append_str(buf, cap, off, "\\r");
   1256         break;
   1257       case '\t':
   1258         append_str(buf, cap, off, "\\t");
   1259         break;
   1260       default:
   1261         if (ch >= 32 && ch <= 126) {
   1262           char c[2] = {(char)ch, 0};
   1263           append_str(buf, cap, off, c);
   1264         } else {
   1265           append_fmt(buf, cap, off, "\\x%02x", ch);
   1266         }
   1267         break;
   1268     }
   1269   }
   1270   append_str(buf, cap, off, "\"");
   1271 }
   1272 
   1273 static void append_token_desc(char* buf, size_t cap, size_t* off,
   1274                               const KitGramParser* ps, KitGramTokenKind kind,
   1275                               const char* lexeme, size_t len) {
   1276   if (kind == KIT_GRAM_META_TOK_EOF) {
   1277     append_str(buf, cap, off, "end of input");
   1278     return;
   1279   }
   1280   const char* name = kit_gram_parser_tok_name(ps, kind);
   1281   if (strcmp(name, "?") == 0)
   1282     append_fmt(buf, cap, off, "invalid token %u", (unsigned)kind);
   1283   else
   1284     append_str(buf, cap, off, name);
   1285   if (lexeme && len) {
   1286     append_str(buf, cap, off, " ");
   1287     append_quoted(buf, cap, off, lexeme, len);
   1288   }
   1289 }
   1290 
   1291 static void format_parse_error(char* buf, size_t cap, const KitGramParser* ps,
   1292                                const Meta* m) {
   1293   size_t off = 0;
   1294   append_str(buf, cap, &off, "parse error");
   1295   const char* rule = kit_gram_parser_rule_name(ps, m->err_rule);
   1296   if (strcmp(rule, "?") != 0) {
   1297     append_str(buf, cap, &off, " in ");
   1298     append_str(buf, cap, &off, rule);
   1299   }
   1300   append_str(buf, cap, &off, ": unexpected ");
   1301   append_token_desc(buf, cap, &off, ps, m->err_tok, m->err_lexeme,
   1302                     m->err_lexeme_len);
   1303   if (m->nexpected) {
   1304     append_str(buf, cap, &off, ", expected ");
   1305     for (size_t i = 0; i < m->nexpected; i++) {
   1306       if (i) append_str(buf, cap, &off, ", ");
   1307       append_token_desc(buf, cap, &off, ps, m->expected[i], NULL, 0);
   1308     }
   1309     if (m->expected_truncated) append_str(buf, cap, &off, ", ...");
   1310   }
   1311 }
   1312 
   1313 typedef struct {
   1314   GramgenContext* ctx;
   1315   const char* s;
   1316   size_t len, i;
   1317   uint32_t line, col;
   1318   char err_msg[120];
   1319   uint32_t err_line, err_col;
   1320 } StringDecoder;
   1321 
   1322 static int sd_peek(const StringDecoder* sd, size_t n) {
   1323   size_t j = sd->i + n;
   1324   return j < sd->len ? (unsigned char)sd->s[j] : 0;
   1325 }
   1326 
   1327 static int sd_advance(StringDecoder* sd) {
   1328   int c = sd_peek(sd, 0);
   1329   if (!c) return 0;
   1330   sd->i++;
   1331   if (c == '\n') {
   1332     sd->line++;
   1333     sd->col = 1;
   1334   } else {
   1335     sd->col++;
   1336   }
   1337   return c;
   1338 }
   1339 
   1340 static void sd_error(StringDecoder* sd, uint32_t line, uint32_t col,
   1341                      const char* msg) {
   1342   sd->err_line = line;
   1343   sd->err_col = col;
   1344   snprintf(sd->err_msg, sizeof sd->err_msg, "%s", msg);
   1345 }
   1346 
   1347 static void decoded_string_append(GramgenContext* ctx, char** buf, size_t* cap,
   1348                                   size_t* len, unsigned char byte) {
   1349   if (*len + 1 >= *cap) {
   1350     *cap *= 2;
   1351     *buf = xrealloc(ctx, *buf, *cap);
   1352   }
   1353   (*buf)[(*len)++] = (char)byte;
   1354 }
   1355 
   1356 static int sd_read_hex(StringDecoder* sd, int n, uint32_t line, uint32_t col,
   1357                        const char* what, uint32_t* out) {
   1358   uint32_t value = 0;
   1359   for (int i = 0; i < n; i++) {
   1360     int c = sd_peek(sd, 0);
   1361     if (!c) {
   1362       char msg[64];
   1363       snprintf(msg, sizeof msg, "short %s escape", what);
   1364       sd_error(sd, line, col, msg);
   1365       return 0;
   1366     }
   1367     int digit = hex_val(c);
   1368     if (digit < 0) {
   1369       char msg[64];
   1370       snprintf(msg, sizeof msg, "bad %s escape", what);
   1371       sd_error(sd, sd->line, sd->col, msg);
   1372       return 0;
   1373     }
   1374     sd_advance(sd);
   1375     value = (value << 4) | (uint32_t)digit;
   1376   }
   1377   *out = value;
   1378   return 1;
   1379 }
   1380 
   1381 static int sd_read_braced_hex(StringDecoder* sd, uint32_t line, uint32_t col,
   1382                               const char* what, uint32_t* out) {
   1383   if (sd_peek(sd, 0) != '{') {
   1384     char msg[64];
   1385     snprintf(msg, sizeof msg, "bad %s escape", what);
   1386     sd_error(sd, line, col, msg);
   1387     return 0;
   1388   }
   1389   sd_advance(sd);
   1390   uint32_t value = 0;
   1391   size_t ndigits = 0;
   1392   while (1) {
   1393     int c = sd_peek(sd, 0);
   1394     if (!c) {
   1395       char msg[80];
   1396       snprintf(msg, sizeof msg, "unterminated %s escape", what);
   1397       sd_error(sd, line, col, msg);
   1398       return 0;
   1399     }
   1400     if (c == '\n') {
   1401       char msg[80];
   1402       snprintf(msg, sizeof msg, "newline in %s escape", what);
   1403       sd_error(sd, sd->line, sd->col, msg);
   1404       return 0;
   1405     }
   1406     if (c == '}') {
   1407       sd_advance(sd);
   1408       if (!ndigits) {
   1409         char msg[80];
   1410         snprintf(msg, sizeof msg, "empty %s escape", what);
   1411         sd_error(sd, line, col, msg);
   1412         return 0;
   1413       }
   1414       *out = value;
   1415       return 1;
   1416     }
   1417     int digit = hex_val(c);
   1418     if (digit < 0) {
   1419       char msg[64];
   1420       snprintf(msg, sizeof msg, "bad %s escape", what);
   1421       sd_error(sd, sd->line, sd->col, msg);
   1422       return 0;
   1423     }
   1424     sd_advance(sd);
   1425     ndigits++;
   1426     value = (value << 4) | (uint32_t)digit;
   1427     if (value > 0x10FFFFu) {
   1428       char msg[80];
   1429       snprintf(msg, sizeof msg, "invalid Unicode scalar U+%04X",
   1430                (unsigned)value);
   1431       sd_error(sd, line, col, msg);
   1432       return 0;
   1433     }
   1434   }
   1435 }
   1436 
   1437 static int decoded_string_append_utf8(StringDecoder* sd, char** buf,
   1438                                       size_t* cap, size_t* len, uint32_t cp,
   1439                                       uint32_t line, uint32_t col) {
   1440   if (cp > 0x10FFFFu || (cp >= 0xD800u && cp <= 0xDFFFu)) {
   1441     char msg[80];
   1442     snprintf(msg, sizeof msg, "invalid Unicode scalar U+%04X", (unsigned)cp);
   1443     sd_error(sd, line, col, msg);
   1444     return 0;
   1445   }
   1446   if (cp <= 0x7Fu) {
   1447     decoded_string_append(sd->ctx, buf, cap, len, (unsigned char)cp);
   1448   } else if (cp <= 0x7FFu) {
   1449     decoded_string_append(sd->ctx, buf, cap, len,
   1450                           (unsigned char)(0xC0u | (cp >> 6)));
   1451     decoded_string_append(sd->ctx, buf, cap, len,
   1452                           (unsigned char)(0x80u | (cp & 0x3Fu)));
   1453   } else if (cp <= 0xFFFFu) {
   1454     decoded_string_append(sd->ctx, buf, cap, len,
   1455                           (unsigned char)(0xE0u | (cp >> 12)));
   1456     decoded_string_append(sd->ctx, buf, cap, len,
   1457                           (unsigned char)(0x80u | ((cp >> 6) & 0x3Fu)));
   1458     decoded_string_append(sd->ctx, buf, cap, len,
   1459                           (unsigned char)(0x80u | (cp & 0x3Fu)));
   1460   } else {
   1461     decoded_string_append(sd->ctx, buf, cap, len,
   1462                           (unsigned char)(0xF0u | (cp >> 18)));
   1463     decoded_string_append(sd->ctx, buf, cap, len,
   1464                           (unsigned char)(0x80u | ((cp >> 12) & 0x3Fu)));
   1465     decoded_string_append(sd->ctx, buf, cap, len,
   1466                           (unsigned char)(0x80u | ((cp >> 6) & 0x3Fu)));
   1467     decoded_string_append(sd->ctx, buf, cap, len,
   1468                           (unsigned char)(0x80u | (cp & 0x3Fu)));
   1469   }
   1470   return 1;
   1471 }
   1472 
   1473 static int decode_string_token(GramgenContext* ctx, KitGramToken* tok,
   1474                                char** owned, char* err_msg, size_t err_cap,
   1475                                uint32_t* err_line, uint32_t* err_col) {
   1476   StringDecoder sd = {
   1477       .ctx = ctx,
   1478       .s = tok->lexeme,
   1479       .len = tok->len,
   1480       .line = tok->line,
   1481       .col = tok->col,
   1482   };
   1483   uint32_t line = sd.line, col = sd.col;
   1484   if (sd_peek(&sd, 0) != '"') {
   1485     snprintf(err_msg, err_cap, "malformed string literal");
   1486     *err_line = line;
   1487     *err_col = col;
   1488     return 0;
   1489   }
   1490   sd_advance(&sd);
   1491 
   1492   size_t cap = 32, len = 0;
   1493   size_t scalar_cap = 32, scalar_len = 0;
   1494   char* buf = xmalloc(ctx, cap);
   1495   char* scalar = xmalloc(ctx, scalar_cap);
   1496   while (1) {
   1497     int c = sd_peek(&sd, 0);
   1498     if (!c) {
   1499       xfree(ctx, buf);
   1500       xfree(ctx, scalar);
   1501       sd_error(&sd, line, col, "unterminated string literal");
   1502       break;
   1503     }
   1504     if (c == '\n') {
   1505       xfree(ctx, buf);
   1506       xfree(ctx, scalar);
   1507       sd_error(&sd, sd.line, sd.col, "newline in string literal");
   1508       break;
   1509     }
   1510     if (c == '"') {
   1511       sd_advance(&sd);
   1512       char prefix[32];
   1513       int pn = snprintf(prefix, sizeof prefix, "%zu:", len);
   1514       if (pn < 0 || (size_t)pn >= sizeof prefix) die_oom(ctx);
   1515       char* payload = xmalloc(ctx, (size_t)pn + len + scalar_len);
   1516       memcpy(payload, prefix, (size_t)pn);
   1517       memcpy(payload + pn, buf, len);
   1518       memcpy(payload + pn + len, scalar, scalar_len);
   1519       xfree(ctx, buf);
   1520       xfree(ctx, scalar);
   1521       tok->lexeme = payload;
   1522       tok->len = (size_t)pn + len + scalar_len;
   1523       *owned = payload;
   1524       return 1;
   1525     }
   1526     if (c == '\\') {
   1527       sd_advance(&sd);
   1528       uint32_t esc_line = sd.line, esc_col = sd.col;
   1529       int e = sd_peek(&sd, 0);
   1530       if (!e) {
   1531         xfree(ctx, buf);
   1532         xfree(ctx, scalar);
   1533         sd_error(&sd, line, col, "unterminated string literal");
   1534         break;
   1535       }
   1536       if (e == '\n') {
   1537         xfree(ctx, buf);
   1538         xfree(ctx, scalar);
   1539         sd_error(&sd, sd.line, sd.col, "newline in string literal");
   1540         break;
   1541       }
   1542       sd_advance(&sd);
   1543       switch (e) {
   1544         case 'n':
   1545           decoded_string_append(ctx, &buf, &cap, &len, '\n');
   1546           decoded_string_append(ctx, &scalar, &scalar_cap, &scalar_len, '\n');
   1547           break;
   1548         case 'r':
   1549           decoded_string_append(ctx, &buf, &cap, &len, '\r');
   1550           decoded_string_append(ctx, &scalar, &scalar_cap, &scalar_len, '\r');
   1551           break;
   1552         case 't':
   1553           decoded_string_append(ctx, &buf, &cap, &len, '\t');
   1554           decoded_string_append(ctx, &scalar, &scalar_cap, &scalar_len, '\t');
   1555           break;
   1556         case '0':
   1557           decoded_string_append(ctx, &buf, &cap, &len, '\0');
   1558           decoded_string_append(ctx, &scalar, &scalar_cap, &scalar_len, '\0');
   1559           break;
   1560         case '"':
   1561           decoded_string_append(ctx, &buf, &cap, &len, '"');
   1562           decoded_string_append(ctx, &scalar, &scalar_cap, &scalar_len, '"');
   1563           break;
   1564         case '\\':
   1565           decoded_string_append(ctx, &buf, &cap, &len, '\\');
   1566           decoded_string_append(ctx, &scalar, &scalar_cap, &scalar_len, '\\');
   1567           break;
   1568         case 'x': {
   1569           uint32_t value = 0;
   1570           if (!sd_read_hex(&sd, 2, esc_line, esc_col, "hex", &value)) {
   1571             xfree(ctx, buf);
   1572             xfree(ctx, scalar);
   1573             break;
   1574           }
   1575           decoded_string_append(ctx, &buf, &cap, &len, (unsigned char)value);
   1576           if (!decoded_string_append_utf8(&sd, &scalar, &scalar_cap,
   1577                                           &scalar_len, value, esc_line,
   1578                                           esc_col)) {
   1579             xfree(ctx, buf);
   1580             xfree(ctx, scalar);
   1581             break;
   1582           }
   1583           break;
   1584         }
   1585         case 'u': {
   1586           uint32_t value = 0;
   1587           int ok =
   1588               sd_peek(&sd, 0) == '{'
   1589                   ? sd_read_braced_hex(&sd, esc_line, esc_col, "Unicode",
   1590                                        &value)
   1591                   : sd_read_hex(&sd, 4, esc_line, esc_col, "Unicode", &value);
   1592           if (!ok || !decoded_string_append_utf8(&sd, &buf, &cap, &len, value,
   1593                                                  esc_line, esc_col)) {
   1594             xfree(ctx, buf);
   1595             xfree(ctx, scalar);
   1596             break;
   1597           }
   1598           if (!decoded_string_append_utf8(&sd, &scalar, &scalar_cap,
   1599                                           &scalar_len, value, esc_line,
   1600                                           esc_col)) {
   1601             xfree(ctx, buf);
   1602             xfree(ctx, scalar);
   1603             break;
   1604           }
   1605           break;
   1606         }
   1607         case 'U': {
   1608           uint32_t value = 0;
   1609           if (!sd_read_hex(&sd, 8, esc_line, esc_col, "Unicode", &value) ||
   1610               !decoded_string_append_utf8(&sd, &buf, &cap, &len, value,
   1611                                           esc_line, esc_col)) {
   1612             xfree(ctx, buf);
   1613             xfree(ctx, scalar);
   1614             break;
   1615           }
   1616           if (!decoded_string_append_utf8(&sd, &scalar, &scalar_cap,
   1617                                           &scalar_len, value, esc_line,
   1618                                           esc_col)) {
   1619             xfree(ctx, buf);
   1620             xfree(ctx, scalar);
   1621             break;
   1622           }
   1623           break;
   1624         }
   1625         default:
   1626           decoded_string_append(ctx, &buf, &cap, &len, (unsigned char)e);
   1627           decoded_string_append(ctx, &scalar, &scalar_cap, &scalar_len,
   1628                                 (unsigned char)e);
   1629           break;
   1630       }
   1631     } else {
   1632       unsigned char b = (unsigned char)sd_advance(&sd);
   1633       decoded_string_append(ctx, &buf, &cap, &len, b);
   1634       decoded_string_append(ctx, &scalar, &scalar_cap, &scalar_len, b);
   1635     }
   1636     if (sd.err_msg[0]) break;
   1637   }
   1638 
   1639   snprintf(err_msg, err_cap, "%s",
   1640            sd.err_msg[0] ? sd.err_msg : "malformed string literal");
   1641   *err_line = sd.err_line ? sd.err_line : line;
   1642   *err_col = sd.err_col ? sd.err_col : col;
   1643   return 0;
   1644 }
   1645 
   1646 typedef struct {
   1647   GramgenContext* ctx;
   1648   char* owned;
   1649   char err_msg[120];
   1650 } MetaLexHook;
   1651 
   1652 static KitGramLexHookResult meta_token_hook(KitGramLexer* lx, void* ud,
   1653                                             KitGramToken* tok,
   1654                                             KitGramLexError* err) {
   1655   (void)lx;
   1656   MetaLexHook* hook = ud;
   1657   if (tok->kind != KIT_GRAM_META_TOK_STRING) return KIT_GRAM_LEX_HOOK_KEEP;
   1658 
   1659   char* owned = NULL;
   1660   uint32_t err_line = tok->line, err_col = tok->col;
   1661   if (!decode_string_token(hook->ctx, tok, &owned, hook->err_msg,
   1662                            sizeof hook->err_msg, &err_line, &err_col)) {
   1663     if (err) {
   1664       err->line = err_line;
   1665       err->col = err_col;
   1666       err->byte = 0;
   1667       err->message = hook->err_msg;
   1668     }
   1669     return KIT_GRAM_LEX_HOOK_ERROR;
   1670   }
   1671 
   1672   hook->owned = owned;
   1673   return KIT_GRAM_LEX_HOOK_KEEP;
   1674 }
   1675 
   1676 static size_t source_offset_for_loc(const char* text, size_t len, uint32_t line,
   1677                                     uint32_t col) {
   1678   uint32_t cur_line = 1, cur_col = 1;
   1679   for (size_t i = 0; i < len; i++) {
   1680     if (cur_line == line && cur_col == col) return i;
   1681     if (text[i] == '\n') {
   1682       cur_line++;
   1683       cur_col = 1;
   1684     } else {
   1685       cur_col++;
   1686     }
   1687   }
   1688   return len;
   1689 }
   1690 
   1691 static void source_advance(const char* text, size_t len, size_t* i,
   1692                            uint32_t* line, uint32_t* col) {
   1693   if (*i >= len) return;
   1694   unsigned char c = (unsigned char)text[*i];
   1695   (*i)++;
   1696   if (c == '\n') {
   1697     (*line)++;
   1698     *col = 1;
   1699   } else {
   1700     (*col)++;
   1701   }
   1702 }
   1703 
   1704 static int diagnose_string_lex_error(const char* text, size_t len, size_t off,
   1705                                      uint32_t line, uint32_t col, char* msg,
   1706                                      size_t msg_cap, uint32_t* err_line,
   1707                                      uint32_t* err_col) {
   1708   if (off >= len || text[off] != '"') return 0;
   1709   size_t i = off;
   1710   uint32_t cur_line = line, cur_col = col;
   1711   source_advance(text, len, &i, &cur_line, &cur_col);
   1712   while (i < len) {
   1713     unsigned char c = (unsigned char)text[i];
   1714     if (c == '\n') {
   1715       snprintf(msg, msg_cap, "newline in string literal");
   1716       *err_line = cur_line;
   1717       *err_col = cur_col;
   1718       return 1;
   1719     }
   1720     if (c == '"') return 0;
   1721     if (c == '\\') {
   1722       source_advance(text, len, &i, &cur_line, &cur_col);
   1723       if (i >= len) break;
   1724       if (text[i] == '\n') {
   1725         snprintf(msg, msg_cap, "newline in string literal");
   1726         *err_line = cur_line;
   1727         *err_col = cur_col;
   1728         return 1;
   1729       }
   1730     }
   1731     source_advance(text, len, &i, &cur_line, &cur_col);
   1732   }
   1733   snprintf(msg, msg_cap, "unterminated string literal");
   1734   *err_line = line;
   1735   *err_col = col;
   1736   return 1;
   1737 }
   1738 
   1739 static int diagnose_char_class_lex_error(const char* text, size_t len,
   1740                                          size_t off, uint32_t line,
   1741                                          uint32_t col, char* msg,
   1742                                          size_t msg_cap, uint32_t* err_line,
   1743                                          uint32_t* err_col) {
   1744   if (off >= len || text[off] != '[') return 0;
   1745   size_t start = off;
   1746   size_t i = off;
   1747   uint32_t cur_line = line, cur_col = col;
   1748   source_advance(text, len, &i, &cur_line, &cur_col);
   1749   while (i < len) {
   1750     unsigned char c = (unsigned char)text[i];
   1751     if (c == '\n') {
   1752       snprintf(msg, msg_cap, "newline in character class");
   1753       *err_line = cur_line;
   1754       *err_col = cur_col;
   1755       return 1;
   1756     }
   1757     if (c == '\\') {
   1758       source_advance(text, len, &i, &cur_line, &cur_col);
   1759       if (i >= len) break;
   1760       if (text[i] == '\n') {
   1761         snprintf(msg, msg_cap, "newline in character class");
   1762         *err_line = cur_line;
   1763         *err_col = cur_col;
   1764         return 1;
   1765       }
   1766       source_advance(text, len, &i, &cur_line, &cur_col);
   1767       continue;
   1768     }
   1769     if (c == ']') {
   1770       size_t content = start + 1;
   1771       if (content < i && text[content] == '^') content++;
   1772       if (content >= i) {
   1773         snprintf(msg, msg_cap, "empty character class");
   1774         *err_line = line;
   1775         *err_col = col;
   1776         return 1;
   1777       }
   1778       return 0;
   1779     }
   1780     source_advance(text, len, &i, &cur_line, &cur_col);
   1781   }
   1782   snprintf(msg, msg_cap, "unterminated character class");
   1783   *err_line = line;
   1784   *err_col = col;
   1785   return 1;
   1786 }
   1787 
   1788 static int diagnose_unicode_prop_lex_error(const char* text, size_t len,
   1789                                            size_t off, uint32_t line,
   1790                                            uint32_t col, char* msg,
   1791                                            size_t msg_cap, uint32_t* err_line,
   1792                                            uint32_t* err_col) {
   1793   if (off + 2 >= len || text[off] != '\\' ||
   1794       (text[off + 1] != 'p' && text[off + 1] != 'P') || text[off + 2] != '{')
   1795     return 0;
   1796 
   1797   size_t i = off;
   1798   uint32_t cur_line = line, cur_col = col;
   1799   source_advance(text, len, &i, &cur_line, &cur_col);
   1800   source_advance(text, len, &i, &cur_line, &cur_col);
   1801   source_advance(text, len, &i, &cur_line, &cur_col);
   1802   size_t n = 0;
   1803   while (i < len) {
   1804     unsigned char c = (unsigned char)text[i];
   1805     if (c == '\n') {
   1806       snprintf(msg, msg_cap, "newline in Unicode property escape");
   1807       *err_line = cur_line;
   1808       *err_col = cur_col;
   1809       return 1;
   1810     }
   1811     if (c == '}') {
   1812       if (!n) {
   1813         snprintf(msg, msg_cap, "empty Unicode property escape");
   1814         *err_line = line;
   1815         *err_col = col;
   1816         return 1;
   1817       }
   1818       return 0;
   1819     }
   1820     source_advance(text, len, &i, &cur_line, &cur_col);
   1821     n++;
   1822   }
   1823   snprintf(msg, msg_cap, "unterminated Unicode property escape");
   1824   *err_line = line;
   1825   *err_col = col;
   1826   return 1;
   1827 }
   1828 
   1829 static int set_unknown_decl_diag(const char* text, size_t len, size_t off,
   1830                                  char* msg, size_t msg_cap) {
   1831   if (off >= len || text[off] != '%') return 0;
   1832   size_t i = off + 1;
   1833   if (i >= len || !(isalpha((unsigned char)text[i]) || text[i] == '_')) {
   1834     snprintf(msg, msg_cap, "expected declaration name after '%%'");
   1835     return 1;
   1836   }
   1837   while (i < len && (isalnum((unsigned char)text[i]) || text[i] == '_')) i++;
   1838   snprintf(msg, msg_cap, "unknown declaration %.*s", (int)(i - off),
   1839            text + off);
   1840   return 1;
   1841 }
   1842 
   1843 static int token_decl_has_ident_suffix(const char* text, size_t len,
   1844                                        const KitGramToken* tok, char* msg,
   1845                                        size_t msg_cap, uint32_t* line,
   1846                                        uint32_t* col) {
   1847   if (tok->kind != KIT_GRAM_META_TOK_TOKEN_DECL &&
   1848       tok->kind != KIT_GRAM_META_TOK_EMPTY_DECL &&
   1849       tok->kind != KIT_GRAM_META_TOK_PRATT_DECL &&
   1850       tok->kind != KIT_GRAM_META_TOK_LEX_DECL &&
   1851       tok->kind != KIT_GRAM_META_TOK_SKIP_DECL)
   1852     return 0;
   1853   size_t off = source_offset_for_loc(text, len, tok->line, tok->col);
   1854   if (off >= len || off + tok->len >= len) return 0;
   1855   unsigned char next = (unsigned char)text[off + tok->len];
   1856   if (!(isalnum(next) || next == '_')) return 0;
   1857   if (!set_unknown_decl_diag(text, len, off, msg, msg_cap)) return 0;
   1858   *line = tok->line;
   1859   *col = tok->col;
   1860   return 1;
   1861 }
   1862 
   1863 static void set_generated_lex_diag(GramDiag* diag, const char* path,
   1864                                    const char* text, size_t len,
   1865                                    const KitGramLexError* err) {
   1866   if (!diag) return;
   1867   diag->path = path;
   1868   diag->line = err->line;
   1869   diag->col = err->col;
   1870 
   1871   char msg[120];
   1872   uint32_t msg_line = err->line, msg_col = err->col;
   1873   size_t off = source_offset_for_loc(text, len, err->line, err->col);
   1874   if ((diagnose_string_lex_error(text, len, off, err->line, err->col, msg,
   1875                                  sizeof msg, &msg_line, &msg_col)) ||
   1876       (diagnose_char_class_lex_error(text, len, off, err->line, err->col, msg,
   1877                                      sizeof msg, &msg_line, &msg_col)) ||
   1878       (diagnose_unicode_prop_lex_error(text, len, off, err->line, err->col, msg,
   1879                                        sizeof msg, &msg_line, &msg_col)) ||
   1880       (set_unknown_decl_diag(text, len, off, msg, sizeof msg))) {
   1881     diag->line = msg_line;
   1882     diag->col = msg_col;
   1883     snprintf(diag->message, sizeof diag->message, "%s", msg);
   1884     return;
   1885   }
   1886 
   1887   if (strcmp(err->message, "invalid token") == 0 && off < len) {
   1888     unsigned char c = (unsigned char)text[off];
   1889     if (isprint(c))
   1890       snprintf(diag->message, sizeof diag->message, "unexpected character '%c'",
   1891                c);
   1892     else
   1893       snprintf(diag->message, sizeof diag->message, "unexpected byte 0x%02x",
   1894                c);
   1895     return;
   1896   }
   1897   snprintf(diag->message, sizeof diag->message, "%s", err->message);
   1898 }
   1899 
   1900 static void source_end_loc(const char* text, size_t len, uint32_t* line,
   1901                            uint32_t* col) {
   1902   uint32_t out_line = 1, out_col = 1;
   1903   for (size_t i = 0; i < len; i++) {
   1904     if (text[i] == '\n') {
   1905       out_line++;
   1906       out_col = 1;
   1907     } else {
   1908       out_col++;
   1909     }
   1910   }
   1911   *line = out_line;
   1912   *col = out_col;
   1913 }
   1914 
   1915 /* Meta-parser stack sizing. Real grammars touch only a few dozen control slots;
   1916  * nesting (groups, quantifiers, pratt blocks) grows the stacks ~9 ctl per
   1917  * level. Rather than reserve a fixed worst case (live for the whole compile),
   1918  * we start small and double on overflow. META_STACK_MAX is a backstop against
   1919  * pathological input (a file that is almost entirely nested punctuation). */
   1920 enum { META_STACK_START = 256, META_STACK_MAX = 1u << 20 };
   1921 
   1922 /* One meta lexer+parser pass with the given stack cap. Returns 1 on accept
   1923  * (*result set). On failure returns 0: if *overflowed is set the stacks were
   1924  * too small for this grammar's nesting (the caller may retry with a larger
   1925  * cap), otherwise diag holds a genuine lex/parse error. */
   1926 static int parse_text_attempt(GramgenContext* ctx, const char* path,
   1927                               const char* text, size_t len, size_t cap,
   1928                               ParsedGrammar** result, GramDiag* diag,
   1929                               bool* overflowed) {
   1930   *overflowed = false;
   1931   ParseUd pu;
   1932   memset(&pu, 0, sizeof pu);
   1933   pu.ctx = ctx;
   1934   pu.path = path;
   1935   pu.pg = xcalloc(ctx, 1, sizeof *pu.pg);
   1936   if (!pu.pg) die_oom(ctx);
   1937   pu.pg->ctx = ctx;
   1938   KitGramActions acts = {
   1939       .reduce = cb_reduce,
   1940       .lift_token = cb_lift,
   1941       .list_empty = cb_list_empty,
   1942       .list_push = cb_list_push,
   1943       .opt_none = cb_opt_none,
   1944       .opt_some = cb_opt_some,
   1945       .on_error = cb_error,
   1946   };
   1947   KitGramParser ps;
   1948   /* Stacks live in the generator arena (reclaimed by mem_release on the normal
   1949    * and longjmp-error paths), not on the C stack: at 96 B/slot even a modest
   1950    * cap would be a large stack frame. `cap` is grown by the caller on overflow.
   1951    */
   1952   KitGramSlot* ctl = xmalloc(ctx, cap * sizeof *ctl);
   1953   KitGramSem* vals = xmalloc(ctx, cap * sizeof *vals);
   1954   KitGramConfig cfg = {.actions = &acts,
   1955                      .ud = &pu,
   1956                      .recover = false,
   1957                      .ctl_stack = ctl,
   1958                      .ctl_cap = cap,
   1959                      .val_stack = vals,
   1960                      .val_cap = cap};
   1961   kit_gram_meta_parser_init(&ps, &cfg);
   1962 
   1963   KitGramLexInput in;
   1964   kit_gram_lex_input_init(&in, &(KitGramLexInputConfig){0});
   1965   KitGramLexInputSpan span = {
   1966       .bytes = (const unsigned char*)text,
   1967       .len = len,
   1968   };
   1969   KitGramLexer lx;
   1970   MetaLexHook hook = {.ctx = ctx};
   1971   KitGramLexConfig lex_cfg = {
   1972       .token_hook = meta_token_hook,
   1973       .hook_ud = &hook,
   1974   };
   1975   kit_gram_meta_lexer_init(&lx, &in, &lex_cfg);
   1976   kit_gram_lex_input_push(&in, &span);
   1977   kit_gram_lex_input_finish(&in);
   1978 
   1979   uint32_t eof_line = 1, eof_col = 1;
   1980   source_end_loc(text, len, &eof_line, &eof_col);
   1981 
   1982   KitGramToken t;
   1983   for (;;) {
   1984     KitGramLexStatus lst = kit_gram_lexer_next(&lx, &t);
   1985     if (lst == KIT_GRAM_LEX_EOF) break;
   1986     if (lst == KIT_GRAM_LEX_NEED_MORE) {
   1987       if (diag) {
   1988         diag->path = path;
   1989         diag->line = eof_line;
   1990         diag->col = eof_col;
   1991         snprintf(diag->message, sizeof diag->message,
   1992                  "lexer needs more input after end of input");
   1993       }
   1994       return 0;
   1995     }
   1996     if (lst == KIT_GRAM_LEX_ERROR) {
   1997       set_generated_lex_diag(diag, path, text, len, kit_gram_lex_input_error(&in));
   1998       return 0;
   1999     }
   2000 
   2001     char* owned = hook.owned;
   2002     hook.owned = NULL;
   2003     if (t.kind != KIT_GRAM_META_TOK_STRING) {
   2004       char msg[120];
   2005       uint32_t err_line = t.line, err_col = t.col;
   2006       if (token_decl_has_ident_suffix(text, len, &t, msg, sizeof msg, &err_line,
   2007                                       &err_col)) {
   2008         if (diag) {
   2009           diag->path = path;
   2010           diag->line = err_line;
   2011           diag->col = err_col;
   2012           snprintf(diag->message, sizeof diag->message, "%s", msg);
   2013         }
   2014         xfree(ctx, owned);
   2015         return 0;
   2016       }
   2017     }
   2018 
   2019     KitGramStatus st = kit_gram_parser_push(&ps, t);
   2020     xfree(ctx, owned);
   2021     if (st == KIT_GRAM_PARSE_ERROR) {
   2022       if (kit_gram_parser_overflowed(&ps)) {
   2023         *overflowed = true;
   2024         return 0;
   2025       }
   2026       if (diag) {
   2027         diag->path = path;
   2028         diag->line = pu.meta.err_line ? pu.meta.err_line : t.line;
   2029         diag->col = pu.meta.err_col ? pu.meta.err_col : t.col;
   2030         if (pu.meta.err_seen)
   2031           format_parse_error(diag->message, sizeof diag->message, &ps,
   2032                              &pu.meta);
   2033         else
   2034           snprintf(diag->message, sizeof diag->message, "parse error");
   2035       }
   2036       return 0;
   2037     }
   2038   }
   2039   if (kit_gram_parser_finish(&ps) != KIT_GRAM_PARSE_ACCEPT) {
   2040     if (kit_gram_parser_overflowed(&ps)) {
   2041       *overflowed = true;
   2042       return 0;
   2043     }
   2044     if (diag) {
   2045       diag->path = path;
   2046       diag->line = pu.meta.err_line ? pu.meta.err_line : eof_line;
   2047       diag->col = pu.meta.err_col ? pu.meta.err_col : eof_col;
   2048       if (pu.meta.err_seen)
   2049         format_parse_error(diag->message, sizeof diag->message, &ps, &pu.meta);
   2050       else
   2051         snprintf(diag->message, sizeof diag->message,
   2052                  "parse error at end of input");
   2053     }
   2054     return 0;
   2055   }
   2056   (void)kit_gram_parser_result(
   2057       &ps); /* items self-appended to pu.pg as they reduced */
   2058   *result = pu.pg;
   2059   return 1;
   2060 }
   2061 
   2062 /* Parse `text`, growing the meta-parser stacks on overflow. A failed attempt's
   2063  * arena allocations are abandoned (reclaimed with everything else at
   2064  * mem_release); the common case fits the initial cap and runs exactly one
   2065  * attempt. */
   2066 static int parse_text(GramgenContext* ctx, const char* path, const char* text,
   2067                       size_t len, ParsedGrammar** result,
   2068                       GramDiag* diag) {
   2069   for (size_t cap = META_STACK_START;; cap *= 2) {
   2070     bool overflowed = false;
   2071     if (parse_text_attempt(ctx, path, text, len, cap, result, diag,
   2072                            &overflowed))
   2073       return 1;
   2074     if (!overflowed) return 0;
   2075     if (cap >= META_STACK_MAX) {
   2076       if (diag) {
   2077         uint32_t line = 1, col = 1;
   2078         source_end_loc(text, len, &line, &col);
   2079         diag->path = path;
   2080         diag->line = line;
   2081         diag->col = col;
   2082         snprintf(
   2083             diag->message, sizeof diag->message,
   2084             "grammar too deeply nested (meta-parser stack exceeded %u slots)",
   2085             (unsigned)META_STACK_MAX);
   2086       }
   2087       return 0;
   2088     }
   2089   }
   2090 }
   2091 
   2092 /* Render a compiled ParsedGrammar to the `(grammar ...)` s-expression: token
   2093  * decls, then lex blocks, then rules — matching gen/gramgen.py's
   2094  * dump_ast_sexpr.
   2095  */
   2096 static char* dump_grammar_text(GramgenContext* ctx, const ParsedGrammar* pg,
   2097                                size_t* len_out) {
   2098   Buf out;
   2099   buf_init(ctx, &out);
   2100   buf_append(&out, "(grammar\n");
   2101   for (size_t i = 0; i < pg->ntoken_decls; i++) {
   2102     const TokenDecl* d = pg->token_decls[i];
   2103     buf_append(&out, "  (token ");
   2104     buf_append(&out, d->name);
   2105     buf_append(&out, " ");
   2106     quote_str_into(&out, d->literal);
   2107     buf_append(&out, ")\n");
   2108   }
   2109   for (size_t i = 0; i < pg->nlex_blocks; i++) {
   2110     buf_append(&out, "  ");
   2111     ast_lex_block_sexpr(&out, pg->lex_blocks[i]);
   2112     buf_append(&out, "\n");
   2113   }
   2114   for (size_t i = 0; i < pg->nrules; i++) {
   2115     buf_append(&out, "  ");
   2116     ast_rule_sexpr(&out, pg->rules[i]);
   2117     buf_append(&out, "\n");
   2118   }
   2119   buf_append(&out, ")");
   2120   char* exact = xstrndup(ctx, out.s, out.len);
   2121   if (len_out) *len_out = out.len;
   2122   xfree(ctx, out.s);
   2123   return exact;
   2124 }
   2125 
   2126 KitStatus kit_gram_dump_sexpr(const KitContext* kit, KitSlice text,
   2127                               KitSlice path, const KitGramOptions* opts,
   2128                               KitWriter* out) {
   2129   if (!kit || !kit->heap || !opts || !out) return KIT_INVALID;
   2130   if (!text.s && text.len) return KIT_INVALID;
   2131 
   2132   GramgenContext ctx;
   2133   memset(&ctx, 0, sizeof ctx);
   2134   ctx.kit = kit;
   2135   ctx.mem.heap = kit->heap;
   2136   ctx.diag = &ctx.diag_storage;
   2137   ctx.can_jump = 1;
   2138 
   2139   if (setjmp(ctx.jmp)) {
   2140     gram_flush_diag(&ctx); /* before release: diag->path may point into arena */
   2141     mem_release(&ctx.mem);
   2142     return KIT_ERR;
   2143   }
   2144 
   2145   const char* use_path =
   2146       path.len ? xstrndup(&ctx, path.s, path.len) : "<memory>";
   2147   ParsedGrammar* pg = NULL;
   2148   if (!parse_text(&ctx, use_path, text.len ? text.s : "", text.len, &pg,
   2149                   ctx.diag)) {
   2150     gram_flush_diag(&ctx);
   2151     mem_release(&ctx.mem);
   2152     return KIT_ERR;
   2153   }
   2154 
   2155   size_t out_len = 0;
   2156   char* data = dump_grammar_text(&ctx, pg, &out_len);
   2157   KitStatus wst = kit_writer_write(out, data, out_len);
   2158   if (wst != KIT_OK)
   2159     kit_ctx_diagf(kit, "%s: writing s-expr dump failed", use_path);
   2160   mem_release(&ctx.mem);
   2161   return wst == KIT_OK ? KIT_OK : KIT_ERR;
   2162 }
   2163 
   2164 Str str_from_owned(char* s, size_t len) {
   2165   Str out = {.s = s, .len = len};
   2166   return out;
   2167 }
   2168 
   2169 Str str_dup_len(GramgenContext* ctx, const char* s, size_t len) {
   2170   return str_from_owned(xstrndup(ctx, s, len), len);
   2171 }
   2172 
   2173 Str str_dup_c(GramgenContext* ctx, const char* s) {
   2174   return str_dup_len(ctx, s, strlen(s));
   2175 }
   2176 
   2177 int str_eq(Str a, Str b) {
   2178   return a.len == b.len && memcmp(a.s, b.s, a.len) == 0;
   2179 }
   2180 
   2181 int str_eq_c(Str a, const char* b) {
   2182   size_t n = strlen(b);
   2183   return a.len == n && memcmp(a.s, b, n) == 0;
   2184 }
   2185 
   2186 char* str_to_c(GramgenContext* ctx, Str s) { return xstrndup(ctx, s.s, s.len); }
   2187 
   2188 AstSeq* ast_seq_new(GramgenContext* ctx, Loc loc) {
   2189   AstSeq* seq = xcalloc(ctx, 1, sizeof *seq);
   2190   if (!seq) die_oom(ctx);
   2191   seq->ctx = ctx;
   2192   seq->loc = loc;
   2193   return seq;
   2194 }
   2195 
   2196 AstAlt* ast_alt_new(GramgenContext* ctx, Loc loc) {
   2197   AstAlt* alt = xcalloc(ctx, 1, sizeof *alt);
   2198   if (!alt) die_oom(ctx);
   2199   alt->ctx = ctx;
   2200   alt->loc = loc;
   2201   return alt;
   2202 }
   2203 
   2204 AstNode* ast_node_new(GramgenContext* ctx, AstNodeKind kind, Loc loc) {
   2205   AstNode* node = xcalloc(ctx, 1, sizeof *node);
   2206   if (!node) die_oom(ctx);
   2207   node->kind = kind;
   2208   node->loc = loc;
   2209   return node;
   2210 }
   2211 
   2212 AstLexSeq* ast_lex_seq_new(GramgenContext* ctx, Loc loc) {
   2213   AstLexSeq* seq = xcalloc(ctx, 1, sizeof *seq);
   2214   if (!seq) die_oom(ctx);
   2215   seq->ctx = ctx;
   2216   seq->loc = loc;
   2217   return seq;
   2218 }
   2219 
   2220 AstLexAlt* ast_lex_alt_new(GramgenContext* ctx, Loc loc) {
   2221   AstLexAlt* alt = xcalloc(ctx, 1, sizeof *alt);
   2222   if (!alt) die_oom(ctx);
   2223   alt->ctx = ctx;
   2224   alt->loc = loc;
   2225   return alt;
   2226 }
   2227 
   2228 AstLexNode* ast_lex_node_new(GramgenContext* ctx, AstLexNodeKind kind,
   2229                              Loc loc) {
   2230   AstLexNode* node = xcalloc(ctx, 1, sizeof *node);
   2231   if (!node) die_oom(ctx);
   2232   node->kind = kind;
   2233   node->loc = loc;
   2234   return node;
   2235 }
   2236 
   2237 void ast_seq_append(AstSeq* seq, AstNode* node) {
   2238   if (seq->nitems == seq->cap) {
   2239     seq->cap = seq->cap ? seq->cap * 2 : 4;
   2240     seq->items = xrealloc(seq->ctx, seq->items, seq->cap * sizeof *seq->items);
   2241   }
   2242   seq->items[seq->nitems++] = node;
   2243 }
   2244 
   2245 void ast_alt_append(AstAlt* alt, AstSeq* seq) {
   2246   if (alt->nseqs == alt->cap) {
   2247     alt->cap = alt->cap ? alt->cap * 2 : 4;
   2248     alt->seqs = xrealloc(alt->ctx, alt->seqs, alt->cap * sizeof *alt->seqs);
   2249   }
   2250   alt->seqs[alt->nseqs++] = seq;
   2251 }
   2252 
   2253 void ast_lex_seq_append(AstLexSeq* seq, AstLexNode* node) {
   2254   if (seq->nitems == seq->cap) {
   2255     seq->cap = seq->cap ? seq->cap * 2 : 4;
   2256     seq->items = xrealloc(seq->ctx, seq->items, seq->cap * sizeof *seq->items);
   2257   }
   2258   seq->items[seq->nitems++] = node;
   2259 }
   2260 
   2261 void ast_lex_alt_append(AstLexAlt* alt, AstLexSeq* seq) {
   2262   if (alt->nseqs == alt->cap) {
   2263     alt->cap = alt->cap ? alt->cap * 2 : 4;
   2264     alt->seqs = xrealloc(alt->ctx, alt->seqs, alt->cap * sizeof *alt->seqs);
   2265   }
   2266   alt->seqs[alt->nseqs++] = seq;
   2267 }
   2268 
   2269 static void parsed_add_rule(ParsedGrammar* g, AstRule* rule) {
   2270   if (g->nrules == g->cap_rules) {
   2271     g->cap_rules = g->cap_rules ? g->cap_rules * 2 : 8;
   2272     g->rules = xrealloc(g->ctx, g->rules, g->cap_rules * sizeof *g->rules);
   2273   }
   2274   g->rules[g->nrules++] = rule;
   2275 }
   2276 
   2277 static void parsed_add_token_decl(ParsedGrammar* g, TokenDecl* decl) {
   2278   if (g->ntoken_decls == g->cap_token_decls) {
   2279     g->cap_token_decls = g->cap_token_decls ? g->cap_token_decls * 2 : 4;
   2280     g->token_decls = xrealloc(g->ctx, g->token_decls,
   2281                               g->cap_token_decls * sizeof *g->token_decls);
   2282   }
   2283   g->token_decls[g->ntoken_decls++] = decl;
   2284 }
   2285 
   2286 static void parsed_add_lex_block(ParsedGrammar* g, AstLexBlock* block) {
   2287   if (g->nlex_blocks == g->cap_lex_blocks) {
   2288     g->cap_lex_blocks = g->cap_lex_blocks ? g->cap_lex_blocks * 2 : 2;
   2289     g->lex_blocks = xrealloc(g->ctx, g->lex_blocks,
   2290                              g->cap_lex_blocks * sizeof *g->lex_blocks);
   2291   }
   2292   g->lex_blocks[g->nlex_blocks++] = block;
   2293 }
   2294 
   2295 static void pratt_line_append(AstPrattLine* line, AstPrattAtom* atom) {
   2296   if (line->natoms == line->cap) {
   2297     line->cap = line->cap ? line->cap * 2 : 4;
   2298     line->atoms =
   2299         xrealloc(line->ctx, line->atoms, line->cap * sizeof *line->atoms);
   2300   }
   2301   line->atoms[line->natoms++] = atom;
   2302 }
   2303 
   2304 static void pratt_spec_append(AstPrattSpec* spec, AstPrattLine* line) {
   2305   if (spec->nlines == spec->cap) {
   2306     spec->cap = spec->cap ? spec->cap * 2 : 4;
   2307     spec->lines =
   2308         xrealloc(spec->ctx, spec->lines, spec->cap * sizeof *spec->lines);
   2309   }
   2310   spec->lines[spec->nlines++] = line;
   2311 }
   2312 
   2313 static void lex_block_append(AstLexBlock* block, AstLexLine* line) {
   2314   if (block->nlines == block->cap) {
   2315     block->cap = block->cap ? block->cap * 2 : 4;
   2316     block->lines =
   2317         xrealloc(block->ctx, block->lines, block->cap * sizeof *block->lines);
   2318   }
   2319   block->lines[block->nlines++] = line;
   2320 }
   2321 
   2322 int hex_val(int c) {
   2323   if (c >= '0' && c <= '9') return c - '0';
   2324   if (c >= 'a' && c <= 'f') return c - 'a' + 10;
   2325   if (c >= 'A' && c <= 'F') return c - 'A' + 10;
   2326   return -1;
   2327 }
   2328 
   2329 void kit_gram_error(GramgenContext* ctx, Loc loc, const char* fmt, ...) {
   2330   va_list ap;
   2331   va_start(ap, fmt);
   2332   if (ctx && ctx->can_jump) {
   2333     diag_vset(ctx, loc, fmt, ap);
   2334     va_end(ap);
   2335     longjmp(ctx->jmp, 1);
   2336   }
   2337   va_end(ap);
   2338   for (;;) {
   2339   }
   2340 }
   2341 
   2342 int is_c_ident_str(Str s) {
   2343   if (!s.len) return 0;
   2344   unsigned char c = (unsigned char)s.s[0];
   2345   if (!(isalpha(c) || c == '_')) return 0;
   2346   for (size_t i = 1; i < s.len; i++) {
   2347     c = (unsigned char)s.s[i];
   2348     if (!(isalnum(c) || c == '_')) return 0;
   2349   }
   2350   return 1;
   2351 }
   2352 
   2353 int is_rule_name(const char* name) {
   2354   unsigned char c = (unsigned char)name[0];
   2355   if (!(c >= 'a' && c <= 'z')) return 0;
   2356   for (size_t i = 1; name[i]; i++) {
   2357     c = (unsigned char)name[i];
   2358     if (!(isalnum(c) || c == '_')) return 0;
   2359   }
   2360   return 1;
   2361 }
   2362 
   2363 int is_token_name(const char* name) {
   2364   unsigned char c = (unsigned char)name[0];
   2365   if (!(c >= 'A' && c <= 'Z')) return 0;
   2366   for (size_t i = 1; name[i]; i++) {
   2367     c = (unsigned char)name[i];
   2368     if (!((c >= 'A' && c <= 'Z') || isdigit(c) || c == '_')) return 0;
   2369   }
   2370   return 1;
   2371 }
   2372 
   2373 static int int_cmp(const void* a, const void* b) {
   2374   int x = *(const int*)a;
   2375   int y = *(const int*)b;
   2376   return (x > y) - (x < y);
   2377 }
   2378 
   2379 /* IntSet keeps its elements sorted ascending, so membership is a binary search
   2380  * and union is a linear merge. The FIRST/FOLLOW fixpoint unions sets
   2381  * repeatedly; the old unsorted array made each union O(|dst|*|src|). */
   2382 static size_t intset_lower_bound(const IntSet* s, int v) {
   2383   size_t lo = 0, hi = s->n;
   2384   while (lo < hi) {
   2385     size_t mid = lo + (hi - lo) / 2;
   2386     if (s->v[mid] < v)
   2387       lo = mid + 1;
   2388     else
   2389       hi = mid;
   2390   }
   2391   return lo;
   2392 }
   2393 
   2394 int intset_contains(const IntSet* s, int v) {
   2395   size_t i = intset_lower_bound(s, v);
   2396   return i < s->n && s->v[i] == v;
   2397 }
   2398 
   2399 int intset_add(GramgenContext* ctx, IntSet* s, int v) {
   2400   size_t i = intset_lower_bound(s, v);
   2401   if (i < s->n && s->v[i] == v) return 0;
   2402   if (s->n == s->cap) {
   2403     s->cap = s->cap ? s->cap * 2 : 4;
   2404     s->v = xrealloc(ctx, s->v, s->cap * sizeof *s->v);
   2405   }
   2406   memmove(&s->v[i + 1], &s->v[i], (s->n - i) * sizeof *s->v);
   2407   s->v[i] = v;
   2408   s->n++;
   2409   return 1;
   2410 }
   2411 
   2412 int intset_union(GramgenContext* ctx, IntSet* dst, const IntSet* src) {
   2413   if (src->n == 0) return 0;
   2414   /* Merge the two sorted runs into a reused scratch buffer, then copy back into
   2415    * dst. Reusing the buffer avoids an allocation per union (the FIRST/FOLLOW
   2416    * fixpoint does many), which matters under the bump arena where the old
   2417    * per-union buffer could not be reclaimed until the whole arena is freed. */
   2418   size_t need = dst->n + src->n;
   2419   if (need > ctx->union_tmp_cap) {
   2420     ctx->union_tmp =
   2421         xrealloc(ctx, ctx->union_tmp, need * sizeof *ctx->union_tmp);
   2422     ctx->union_tmp_cap = need;
   2423   }
   2424   int* out = ctx->union_tmp;
   2425   size_t i = 0, j = 0, k = 0;
   2426   int changed = 0;
   2427   while (i < dst->n && j < src->n) {
   2428     int a = dst->v[i], b = src->v[j];
   2429     if (a < b) {
   2430       out[k++] = a;
   2431       i++;
   2432     } else if (a > b) {
   2433       out[k++] = b;
   2434       j++;
   2435       changed = 1;
   2436     } else {
   2437       out[k++] = a;
   2438       i++;
   2439       j++;
   2440     }
   2441   }
   2442   while (i < dst->n) out[k++] = dst->v[i++];
   2443   while (j < src->n) {
   2444     out[k++] = src->v[j++];
   2445     changed = 1;
   2446   }
   2447   if (!changed) return 0; /* src was a subset; dst unchanged */
   2448   if (k > dst->cap) {     /* grow dst in place (capacity reused) */
   2449     dst->v = xrealloc(ctx, dst->v, k * sizeof *dst->v);
   2450     dst->cap = k;
   2451   }
   2452   memcpy(dst->v, out, k * sizeof *dst->v);
   2453   dst->n = k;
   2454   return 1;
   2455 }
   2456 
   2457 IntSet intset_copy(GramgenContext* ctx, const IntSet* src) {
   2458   IntSet out = {0};
   2459   if (src->n) {
   2460     out.v = xmalloc(ctx, src->n * sizeof *out.v);
   2461     memcpy(out.v, src->v, src->n * sizeof *out.v);
   2462     out.n = out.cap = src->n;
   2463   }
   2464   return out;
   2465 }
   2466 
   2467 int* intset_sorted(GramgenContext* ctx, const IntSet* s) {
   2468   int* out = NULL;
   2469   if (s->n) {
   2470     out = xmalloc(ctx, s->n * sizeof *out);
   2471     memcpy(out, s->v, s->n * sizeof *out);
   2472     qsort(out, s->n, sizeof *out, int_cmp);
   2473   }
   2474   return out;
   2475 }
   2476 
   2477 int intset_equal(const IntSet* a, const IntSet* b) {
   2478   if (a->n != b->n) return 0;
   2479   for (size_t i = 0; i < a->n; i++)
   2480     if (!intset_contains(b, a->v[i])) return 0;
   2481   return 1;
   2482 }
   2483 
   2484 int intset_intersection_min(const IntSet* a, const IntSet* b) {
   2485   int found = -1;
   2486   for (size_t i = 0; i < a->n; i++) {
   2487     int v = a->v[i];
   2488     if (intset_contains(b, v) && (found < 0 || v < found)) found = v;
   2489   }
   2490   return found;
   2491 }
   2492 
   2493 char* c_string_str(GramgenContext* ctx, Str s) {
   2494   return quote_len(ctx, s.s, s.len, true);
   2495 }
   2496 
   2497 static const char* base_name(const char* path) {
   2498   const char* slash = strrchr(path, '/');
   2499   return slash ? slash + 1 : path;
   2500 }
   2501 
   2502 static char* path_stem(GramgenContext* ctx, const char* path) {
   2503   const char* base = base_name(path);
   2504   const char* dot = strrchr(base, '.');
   2505   size_t n = dot && dot > base ? (size_t)(dot - base) : strlen(base);
   2506   return xstrndup(ctx, base, n);
   2507 }
   2508 
   2509 static char* header_guard_name(GramgenContext* ctx, const char* path) {
   2510   const char* base = base_name(path);
   2511   Buf b;
   2512   buf_init(ctx, &b);
   2513   for (size_t i = 0; base[i]; i++) {
   2514     unsigned char c = (unsigned char)base[i];
   2515     if (isalnum(c)) {
   2516       char up = (char)toupper(c);
   2517       buf_appendn(&b, &up, 1);
   2518     } else {
   2519       buf_append(&b, "_");
   2520     }
   2521   }
   2522   while (b.len && b.s[b.len - 1] == '_') b.s[--b.len] = '\0';
   2523   if (!b.len) buf_append(&b, "KIT_GRAM_OUTPUT");
   2524   if (isdigit((unsigned char)b.s[0])) {
   2525     Buf out;
   2526     buf_init(ctx, &out);
   2527     buf_append(&out, "_");
   2528     buf_append(&out, b.s);
   2529     xfree(ctx, b.s);
   2530     b = out;
   2531   }
   2532   buf_append(&b, "_");
   2533   return buf_take(&b);
   2534 }
   2535 
   2536 static void validate_prefix_arg(GramgenContext* ctx, const char* prefix,
   2537                                 const char* path) {
   2538   char* g = xasprintf(ctx, "%sgrammar", prefix);
   2539   char* p = xasprintf(ctx, "%sparser_init", prefix);
   2540   if (!is_c_ident_str(str_dup_c(ctx, g)) ||
   2541       !is_c_ident_str(str_dup_c(ctx, p))) {
   2542     Loc loc = {.path = path, .line = 1, .col = 1};
   2543     kit_gram_error(ctx, loc, "invalid identifier prefix '%s'", prefix);
   2544   }
   2545 }
   2546 
   2547 static void emit_line(Buf* b, const char* fmt, ...) {
   2548   char stack[512];
   2549   va_list ap, aq;
   2550   va_start(ap, fmt);
   2551   va_copy(aq, ap);
   2552   int n = vsnprintf(stack, sizeof stack, fmt, ap);
   2553   va_end(ap);
   2554   if (n < 0) die_oom(b->ctx);
   2555   if ((size_t)n < sizeof stack) {
   2556     stack[n] = '\n';
   2557     buf_appendn(b, stack, (size_t)n + 1u);
   2558     va_end(aq);
   2559     return;
   2560   }
   2561   char* tmp = xmalloc(b->ctx, (size_t)n + 2u);
   2562   vsnprintf(tmp, (size_t)n + 1u, fmt, aq);
   2563   va_end(aq);
   2564   tmp[n] = '\n';
   2565   tmp[(size_t)n + 1u] = '\0';
   2566   buf_appendn(b, tmp, (size_t)n + 1u);
   2567   xfree(b->ctx, tmp);
   2568 }
   2569 
   2570 static char* make_upper_prefix(GramgenContext* ctx, const char* prefix) {
   2571   size_t n = strlen(prefix);
   2572   char* up = xmalloc(ctx, n + 1);
   2573   for (size_t i = 0; i < n; i++)
   2574     up[i] = (char)toupper((unsigned char)prefix[i]);
   2575   up[n] = '\0';
   2576   return up;
   2577 }
   2578 
   2579 static char* token_ref(GramgenContext* ctx, Builder* b, const char* up,
   2580                        int tok) {
   2581   return xasprintf(ctx, "%sTOK_%s", up, b->tokens[tok].name);
   2582 }
   2583 
   2584 static char* rule_ref(GramgenContext* ctx, Builder* b, const char* up,
   2585                       int idx) {
   2586   if ((size_t)idx < b->public_count)
   2587     return xasprintf(ctx, "%sR_%s", up, b->rules[idx].name);
   2588   return xasprintf(ctx, "%sR__SYN_%d", up, idx - (int)b->public_count);
   2589 }
   2590 
   2591 static const char* pratt_role_ref(Builder* b, const char* role) {
   2592   if (role == b->role.prefix) return "KIT_GRAM_PO_PREFIX";
   2593   if (role == b->role.postfix) return "KIT_GRAM_PO_POSTFIX";
   2594   if (role == b->role.ternary) return "KIT_GRAM_PO_TERNARY";
   2595   if (role == b->role.circumfix) return "KIT_GRAM_PO_CIRCUMFIX";
   2596   return "KIT_GRAM_PO_INFIX";
   2597 }
   2598 
   2599 static int lex_name_is_main_c(const char* name) {
   2600   return strcmp(name, "main") == 0;
   2601 }
   2602 
   2603 static char* lex_grammar_symbol(GramgenContext* ctx, const char* prefix,
   2604                                 const char* name) {
   2605   if (lex_name_is_main_c(name)) return xasprintf(ctx, "%slex_grammar", prefix);
   2606   return xasprintf(ctx, "%s%s_lex_grammar", prefix, name);
   2607 }
   2608 
   2609 static char* lex_constructor_symbol(GramgenContext* ctx, const char* prefix,
   2610                                     const char* name) {
   2611   if (lex_name_is_main_c(name)) return xasprintf(ctx, "%slexer_init", prefix);
   2612   return xasprintf(ctx, "%slexer_%s_init", prefix, name);
   2613 }
   2614 
   2615 static char* lex_scan_symbol(GramgenContext* ctx, const char* prefix,
   2616                              const char* name) {
   2617   if (lex_name_is_main_c(name)) return xasprintf(ctx, "%sscan", prefix);
   2618   return xasprintf(ctx, "%sscan_%s", prefix, name);
   2619 }
   2620 
   2621 /* Standalone (--lexer-standalone) tokenizer entry base: <prefix>lex for the
   2622  * main mode and <prefix><name>_lex for each additional %lex mode. The _init /
   2623  * _next suffixes hang off this; all modes share the one <prefix>lex state
   2624  * struct. */
   2625 static char* std_lex_base(GramgenContext* ctx, const char* prefix,
   2626                           const char* name) {
   2627   if (lex_name_is_main_c(name)) return xasprintf(ctx, "%slex", prefix);
   2628   return xasprintf(ctx, "%s%s_lex", prefix, name);
   2629 }
   2630 
   2631 /* Standalone match-API base: <prefix>match for the main lexer and
   2632  * <prefix><name>_match for each named %lex mode. The _anchored/_full/_find /
   2633  * _iter_init/_iter_next suffixes and the <base>_iter struct hang off this, so
   2634  * every lexer (including sub-lexers) drives its own match/iterator API. */
   2635 static char* std_match_base(GramgenContext* ctx, const char* prefix,
   2636                             const char* name) {
   2637   if (lex_name_is_main_c(name)) return xasprintf(ctx, "%smatch", prefix);
   2638   return xasprintf(ctx, "%s%s_match", prefix, name);
   2639 }
   2640 
   2641 /* The pull-RD parser covers the full supported grammar set: LL(1) rules,
   2642  * postfix
   2643  * `?`/`*`, %empty, and every Pratt role (prefix/postfix/infixl/infixr and the
   2644  * mixfix ternary/circumfix). Kept as a hook for any future unsupported form. */
   2645 static int parser_codegen_ok(Builder* b) {
   2646   (void)b;
   2647   return 1;
   2648 }
   2649 
   2650 /* Fusion is available when the standalone lexer and parser codegen are both on
   2651  * and there is a main lexer, so the parser can pull tokens by running the
   2652  * standalone scanner inline. The standalone scanner handles every mode itself
   2653  * (byte/UTF-8, start and end anchors), so there is no DFA-shape restriction. */
   2654 static int parser_fused_ok(Builder* b) {
   2655   if (!b->lexer_standalone || !b->parser_codegen || !parser_codegen_ok(b))
   2656     return 0;
   2657   for (size_t i = 0; i < b->nlex_dfas; i++)
   2658     if (lex_name_is_main_c(b->lex_dfas[i].name)) return 1;
   2659   return 0;
   2660 }
   2661 
   2662 static char* sym_init(GramgenContext* ctx, Builder* b, const char* up,
   2663                       Sym* sym) {
   2664   if (sym->kind == SYM_TERM) {
   2665     char* tok = token_ref(ctx, b, up, sym->value);
   2666     char* out = xasprintf(ctx, "KIT_GRAM_TERM(%s)", tok);
   2667     xfree(ctx, tok);
   2668     return out;
   2669   }
   2670   if (sym->kind == SYM_RULE) {
   2671     char* rule = rule_ref(ctx, b, up, sym->value);
   2672     char* out = xasprintf(ctx, "KIT_GRAM_RULE(%s)", rule);
   2673     xfree(ctx, rule);
   2674     return out;
   2675   }
   2676   const char* kind = sym->kind == SYM_REP ? "KIT_GRAM_S_REP" : "KIT_GRAM_S_OPT";
   2677   return xasprintf(ctx, "{ .kind = %s, .first = %d, .sub = sub_%d, .nsub = 1 }",
   2678                    kind, sym->set_index, sym->sub_array_id);
   2679 }
   2680 
   2681 static char* join_syms(GramgenContext* ctx, Builder* b, const char* up,
   2682                        Sym* syms, size_t nsyms) {
   2683   Buf out;
   2684   buf_init(ctx, &out);
   2685   for (size_t i = 0; i < nsyms; i++) {
   2686     if (i) buf_append(&out, ", ");
   2687     char* s = sym_init(ctx, b, up, &syms[i]);
   2688     buf_append(&out, s);
   2689     xfree(ctx, s);
   2690   }
   2691   return buf_take(&out);
   2692 }
   2693 
   2694 static char* tok_list(GramgenContext* ctx, Builder* b, const char* up,
   2695                       const IntSet* set) {
   2696   int* sorted = intset_sorted(ctx, set);
   2697   Buf out;
   2698   buf_init(ctx, &out);
   2699   for (size_t i = 0; i < set->n; i++) {
   2700     if (i) buf_append(&out, ", ");
   2701     char* tok = token_ref(ctx, b, up, sorted[i]);
   2702     buf_append(&out, tok);
   2703     xfree(ctx, tok);
   2704   }
   2705   xfree(ctx, sorted);
   2706   return buf_take(&out);
   2707 }
   2708 
   2709 static void emit_machine_decls(Builder* b, Buf* out, const char* prefix);
   2710 static void emit_machines(Builder* b, Buf* out, const char* prefix);
   2711 
   2712 static void emit_header_text(Buf* out, Builder* b, const char* grammar_path,
   2713                              const char* header_path, const char* prefix) {
   2714   GramgenContext* ctx = out->ctx;
   2715   char* guard = header_guard_name(ctx, header_path);
   2716   emit_line(out, "/* Generated by gramgen from %s. */",
   2717             base_name(grammar_path));
   2718   emit_line(out, "#ifndef %s", guard);
   2719   emit_line(out, "#define %s", guard);
   2720   emit_line(out, "");
   2721   /* A machine-only file emits no parser grammar: just the codegen-only token
   2722    * machines (steppable verifier + sampler), self-contained. */
   2723   if (b->public_count == 0 && b->nmachines) {
   2724     emit_line(out, "#include <kit/gram_lex.h>");
   2725     emit_line(out, "");
   2726     emit_machine_decls(b, out, prefix);
   2727     emit_line(out, "#endif /* %s */", guard);
   2728     return;
   2729   }
   2730   emit_line(out, "#include <kit/gram_parse.h>");
   2731   if (b->nlex_dfas || b->nmachines) emit_line(out, "#include <kit/gram_lex.h>");
   2732   emit_line(out, "");
   2733   char* up = make_upper_prefix(ctx, prefix);
   2734   emit_line(out, "enum {");
   2735   for (size_t i = 0; i < b->ntokens; i++) {
   2736     if (i == 0)
   2737       emit_line(out, "    %sTOK_EOF = 0,", up);
   2738     else
   2739       emit_line(out, "    %sTOK_%s,", up, b->tokens[i].name);
   2740   }
   2741   emit_line(out, "    %sTOK__COUNT", up);
   2742   emit_line(out, "};");
   2743   emit_line(out, "");
   2744   emit_line(out, "enum {");
   2745   for (size_t i = 0; i < b->public_count; i++) {
   2746     if (i == 0)
   2747       emit_line(out, "    %sR_%s = 0,", up, b->rules[i].name);
   2748     else
   2749       emit_line(out, "    %sR_%s,", up, b->rules[i].name);
   2750   }
   2751   emit_line(out, "    %sR__COUNT", up);
   2752   emit_line(out, "};");
   2753   emit_line(out, "");
   2754   for (size_t i = 0; i < b->public_count; i++) {
   2755     Rule* rule = &b->rules[i];
   2756     if (!rule->pratt) continue;
   2757     emit_line(out, "enum {");
   2758     char* primary =
   2759         kit_gram_pratt_prod_enum_name(ctx, b, rule->name, b->role.primary, 0);
   2760     emit_line(out, "    %s%s = 0,", up, primary);
   2761     xfree(ctx, primary);
   2762     for (size_t j = 0; j < rule->pratt->nops; j++)
   2763       emit_line(out, "    %s%s,", up, rule->pratt->ops[j].enum_name);
   2764     emit_line(out, "};");
   2765     emit_line(out, "");
   2766   }
   2767   xfree(ctx, up);
   2768   emit_line(out,
   2769             "void %sstack_bounds(size_t max_depth, size_t* ctl_cap, size_t* "
   2770             "val_cap);",
   2771             prefix);
   2772   emit_line(out, "size_t %sgenerate_scratch_count(void);", prefix);
   2773   emit_line(out,
   2774             "void %sparser_init(KitGramParser *mem, const KitGramConfig *cfg);",
   2775             prefix);
   2776   if (b->parser_codegen && parser_codegen_ok(b)) {
   2777     emit_line(out,
   2778               "/* Experimental pull-based recursive-descent parser over a "
   2779               "buffered token");
   2780     emit_line(out,
   2781               "   array (no EOF token; end = ntok). Runs the same KitGramActions "
   2782               "value channel");
   2783     emit_line(
   2784         out,
   2785         "   as the table parser; returns the start rule's value, sets *ok.");
   2786     emit_line(out,
   2787               "   On a syntax error it fills *err (if non-NULL) with the "
   2788               "offending token,");
   2789     emit_line(out,
   2790               "   its location, the expected set, and the enclosing rule, and "
   2791               "also calls");
   2792     emit_line(out, "   KitGramActions.on_error if bound; *ok is set to 0. */");
   2793     emit_line(out, "KitGramSem %sparse_rd(const KitGramToken *toks, size_t ntok,",
   2794               prefix);
   2795     emit_line(out,
   2796               "                  const KitGramActions *act, void *ud, KitGramError "
   2797               "*err, int *ok);");
   2798     if (parser_fused_ok(b)) {
   2799       emit_line(out,
   2800                 "/* Fused lex+parse: pulls tokens by running the generated "
   2801                 "scanner inline");
   2802       emit_line(out,
   2803                 "   over the raw byte buffer (no token materialization). */");
   2804       emit_line(out,
   2805                 "KitGramSem %sparse_fused(const unsigned char *buf, size_t len,",
   2806                 prefix);
   2807       emit_line(out,
   2808                 "                     const KitGramActions *act, void *ud, "
   2809                 "KitGramError *err, int *ok);");
   2810     }
   2811   }
   2812   if (b->nlex_dfas && b->lexer_standalone) {
   2813     /* Self-contained resident tokenizer + match API for the main lexer; no
   2814      * KitGramLexGrammar (the table is gone). KitGramToken / KitGramLexStatus /
   2815      * KitGramMatch come from the included gramlex.h (header-only types, nothing
   2816      * linked). */
   2817     emit_line(out, "");
   2818     emit_line(out,
   2819               "/* Standalone tokenizer (--lexer-standalone): resident "
   2820               "(buf,len) input, table-free. */");
   2821     emit_line(out, "typedef struct {");
   2822     emit_line(out, "    const unsigned char *cur, *end;");
   2823     emit_line(out, "    uint32_t line, col;");
   2824     emit_line(out,
   2825               "    bool     after_cr;     /* UTF-8 CRLF coalescing state */");
   2826     emit_line(out, "} %slex;", prefix);
   2827     /* One resident tokenizer per %lex mode: the main lexer plus any named
   2828      * sub-lexers. Re-lex a token's bytes by pointing a sub-lexer at its
   2829      * lexeme/len (e.g. a NUMBER or STRING payload). All share the struct above.
   2830      */
   2831     for (size_t i = 0; i < b->nlex_dfas; i++) {
   2832       char* base = std_lex_base(ctx, prefix, b->lex_dfas[i].name);
   2833       emit_line(out,
   2834                 "void          %s_init(%slex *lx, const unsigned char *buf, "
   2835                 "size_t len);",
   2836                 base, prefix);
   2837       emit_line(out,
   2838                 "KitGramLexStatus %s_next(%slex *lx, KitGramToken *out);  /* "
   2839                 "TOKEN/EOF/ERROR */",
   2840                 base, prefix);
   2841       xfree(ctx, base);
   2842     }
   2843     /* One match API per %lex mode (resident (buf,len)); each mirrors
   2844      * kit_gram_match_anchored/full/find + KitGramMatchIter over its own scanner. */
   2845     emit_line(out,
   2846               "/* Match API per lexer (resident (buf,len)); mirrors "
   2847               "kit_gram_match_anchored/full/find + KitGramMatchIter. */");
   2848     for (size_t i = 0; i < b->nlex_dfas; i++) {
   2849       char* mb = std_match_base(ctx, prefix, b->lex_dfas[i].name);
   2850       emit_line(out,
   2851                 "bool   %s_anchored(const unsigned char *buf, size_t len, "
   2852                 "const KitGramMatchOpts *opts, KitGramMatch *out);",
   2853                 mb);
   2854       emit_line(out,
   2855                 "bool   %s_full    (const unsigned char *buf, size_t len, "
   2856                 "const KitGramMatchOpts *opts, KitGramMatch *out);",
   2857                 mb);
   2858       emit_line(out,
   2859                 "bool   %s_find    (const unsigned char *buf, size_t len, "
   2860                 "const KitGramMatchOpts *opts, KitGramMatch *out);",
   2861                 mb);
   2862       emit_line(out,
   2863                 "typedef struct { const unsigned char *buf; size_t len, from; "
   2864                 "int multiline; } %s_iter;",
   2865                 mb);
   2866       emit_line(out,
   2867                 "void %s_iter_init(%s_iter *it, const unsigned char *buf, "
   2868                 "size_t len, const KitGramMatchOpts *opts);",
   2869                 mb, mb);
   2870       emit_line(out, "bool %s_iter_next(%s_iter *it, KitGramMatch *out);", mb,
   2871                 mb);
   2872       xfree(ctx, mb);
   2873     }
   2874   } else if (b->nlex_dfas) {
   2875     emit_line(out, "");
   2876     for (size_t i = 0; i < b->nlex_dfas; i++) {
   2877       LexDFA* dfa = &b->lex_dfas[i];
   2878       char* grammar = lex_grammar_symbol(ctx, prefix, dfa->name);
   2879       char* ctor = lex_constructor_symbol(ctx, prefix, dfa->name);
   2880       char* minit =
   2881           lex_name_is_main_c(dfa->name)
   2882               ? xasprintf(ctx, "%smatcher_init", prefix)
   2883               : xasprintf(ctx, "%s%s_matcher_init", prefix, dfa->name);
   2884       emit_line(out, "void %s(KitGramMatcher *m);", minit);
   2885       emit_line(out,
   2886                 "void %s(KitGramLexer *mem, KitGramLexInput *in, const "
   2887                 "KitGramLexConfig *cfg);",
   2888                 ctor);
   2889       xfree(ctx, grammar);
   2890       xfree(ctx, ctor);
   2891       xfree(ctx, minit);
   2892     }
   2893   }
   2894   if (b->nmachines) {
   2895     emit_line(out, "");
   2896     emit_machine_decls(b, out, prefix);
   2897   }
   2898   emit_line(out, "");
   2899   emit_line(out, "#endif /* %s */", guard);
   2900 }
   2901 
   2902 static void emit_sets(Builder* b, Buf* out, const char* up) {
   2903   GramgenContext* ctx = out->ctx;
   2904   if (!b->nsets) return;
   2905   int* flat = NULL;
   2906   size_t nflat = 0, cap_flat = 0;
   2907   int* offs = xmalloc(ctx, (b->nsets + 1) * sizeof *offs);
   2908   offs[0] = 0;
   2909   for (size_t i = 0; i < b->nsets; i++) {
   2910     int* sorted = intset_sorted(ctx, &b->sets[i]);
   2911     for (size_t j = 0; j < b->sets[i].n; j++) {
   2912       if (nflat == cap_flat) {
   2913         cap_flat = cap_flat ? cap_flat * 2 : 16;
   2914         flat = xrealloc(ctx, flat, cap_flat * sizeof *flat);
   2915       }
   2916       flat[nflat++] = sorted[j];
   2917     }
   2918     offs[i + 1] = (int)nflat;
   2919     xfree(ctx, sorted);
   2920   }
   2921   emit_line(out, "static const KitGramTokenKind g_sets[] = {");
   2922   if (nflat) {
   2923     Buf joined;
   2924     buf_init(ctx, &joined);
   2925     for (size_t i = 0; i < nflat; i++) {
   2926       if (i) buf_append(&joined, ", ");
   2927       char* tok = token_ref(ctx, b, up, flat[i]);
   2928       buf_append(&joined, tok);
   2929       xfree(ctx, tok);
   2930     }
   2931     emit_line(out, "    %s", joined.s);
   2932   }
   2933   emit_line(out, "};");
   2934   Buf off;
   2935   buf_init(ctx, &off);
   2936   for (size_t i = 0; i < b->nsets + 1; i++) {
   2937     if (i) buf_append(&off, ", ");
   2938     char tmp[32];
   2939     snprintf(tmp, sizeof tmp, "%d", offs[i]);
   2940     buf_append(&off, tmp);
   2941   }
   2942   emit_line(out, "static const uint16_t g_set_off[] = { %s };", off.s);
   2943   emit_line(out, "");
   2944 }
   2945 
   2946 static void emit_sub_arrays(Builder* b, Buf* out, const char* up) {
   2947   GramgenContext* ctx = out->ctx;
   2948   for (size_t i = 0; i < b->nwrapper_syms; i++) {
   2949     char* s = sym_init(ctx, b, up, b->wrapper_syms[i]->sub);
   2950     emit_line(out, "static const KitGramSym sub_%zu[] = { %s };", i, s);
   2951     xfree(ctx, s);
   2952   }
   2953   if (b->nwrapper_syms) emit_line(out, "");
   2954 }
   2955 
   2956 static void emit_rule_arrays(Builder* b, Buf* out, const char* up) {
   2957   GramgenContext* ctx = out->ctx;
   2958   for (size_t ri = 0; ri < b->nrules; ri++) {
   2959     Rule* rule = &b->rules[ri];
   2960     if (rule->pratt) continue;
   2961     for (size_t pi = 0; pi < rule->nprods; pi++) {
   2962       Prod* prod = &rule->prods[pi];
   2963       if (!prod->nsyms) continue;
   2964       char* body = join_syms(ctx, b, up, prod->syms, prod->nsyms);
   2965       emit_line(out, "static const KitGramSym p_r%zu_%zu[] = { %s };", ri, pi,
   2966                 body);
   2967       xfree(ctx, body);
   2968     }
   2969   }
   2970   emit_line(out, "");
   2971   emit_line(out,
   2972             "#define PROD(arr) { .syms = (arr), .nsyms = "
   2973             "(uint16_t)(sizeof(arr) / sizeof((arr)[0])) }");
   2974   emit_line(out, "#define PROD_EMPTY { .syms = NULL, .nsyms = 0 }");
   2975   emit_line(out, "");
   2976   for (size_t ri = 0; ri < b->nrules; ri++) {
   2977     Rule* rule = &b->rules[ri];
   2978     if (rule->pratt) continue;
   2979     Buf items;
   2980     buf_init(ctx, &items);
   2981     for (size_t pi = 0; pi < rule->nprods; pi++) {
   2982       if (pi) buf_append(&items, ", ");
   2983       if (rule->prods[pi].nsyms) {
   2984         char* tmp = xasprintf(ctx, "PROD(p_r%zu_%zu)", ri, pi);
   2985         buf_append(&items, tmp);
   2986         xfree(ctx, tmp);
   2987       } else {
   2988         buf_append(&items, "PROD_EMPTY");
   2989       }
   2990     }
   2991     emit_line(out, "static const KitGramProd prods_r%zu[] = { %s };", ri,
   2992               items.s);
   2993   }
   2994   emit_line(out, "");
   2995 }
   2996 
   2997 typedef struct {
   2998   int tok, prod;
   2999 } PredictEntry;
   3000 
   3001 static int predict_entry_cmp(const void* a, const void* b) {
   3002   const PredictEntry* x = a;
   3003   const PredictEntry* y = b;
   3004   if (x->tok != y->tok) return x->tok < y->tok ? -1 : 1;
   3005   return (x->prod > y->prod) - (x->prod < y->prod);
   3006 }
   3007 
   3008 static void emit_first_follow(Builder* b, Buf* out, const char* up) {
   3009   GramgenContext* ctx = out->ctx;
   3010   for (size_t ri = 0; ri < b->nrules; ri++) {
   3011     Rule* rule = &b->rules[ri];
   3012     if (rule->first.n) {
   3013       char* first = tok_list(ctx, b, up, &rule->first);
   3014       emit_line(out, "static const KitGramTokenKind fst_r%zu[] = { %s };", ri,
   3015                 first);
   3016       xfree(ctx, first);
   3017       if (!rule->pratt) {
   3018         PredictEntry* entries = NULL;
   3019         size_t nentries = 0, cap_entries = 0;
   3020         for (size_t pi = 0; pi < rule->nprods; pi++) {
   3021           Prod* prod = &rule->prods[pi];
   3022           for (size_t ti = 0; ti < prod->first.n; ti++) {
   3023             if (nentries == cap_entries) {
   3024               cap_entries = cap_entries ? cap_entries * 2 : 8;
   3025               entries = xrealloc(ctx, entries, cap_entries * sizeof *entries);
   3026             }
   3027             entries[nentries++] = (PredictEntry){prod->first.v[ti], (int)pi};
   3028           }
   3029         }
   3030         if (nentries > 1)
   3031           qsort(entries, nentries, sizeof *entries, predict_entry_cmp);
   3032         Buf pp;
   3033         buf_init(ctx, &pp);
   3034         for (size_t i = 0; i < nentries; i++) {
   3035           if (i) buf_append(&pp, ", ");
   3036           char tmp[32];
   3037           snprintf(tmp, sizeof tmp, "%d", entries[i].prod);
   3038           buf_append(&pp, tmp);
   3039         }
   3040         emit_line(out, "static const uint8_t pp_r%zu[] = { %s };", ri, pp.s);
   3041       }
   3042     }
   3043     if (rule->follow.n) {
   3044       char* follow = tok_list(ctx, b, up, &rule->follow);
   3045       emit_line(out, "static const KitGramTokenKind flw_r%zu[] = { %s };", ri,
   3046                 follow);
   3047       xfree(ctx, follow);
   3048     }
   3049   }
   3050   emit_line(out, "");
   3051 }
   3052 
   3053 static void emit_pratt_tables(Builder* b, Buf* out, const char* up) {
   3054   GramgenContext* ctx = out->ctx;
   3055   for (size_t ri = 0; ri < b->nrules; ri++) {
   3056     Rule* rule = &b->rules[ri];
   3057     if (!rule->pratt) continue;
   3058     const char* ops_name = "NULL";
   3059     char nops_buf[64] = "0";
   3060     if (rule->pratt->nops) {
   3061       emit_line(out, "static const KitGramPrattOp pratt_ops_r%zu[] = {", ri);
   3062       for (size_t i = 0; i < rule->pratt->nops; i++) {
   3063         PrattOp* op = &rule->pratt->ops[i];
   3064         char* tok = token_ref(ctx, b, up, op->tok);
   3065         char* prod_name = xasprintf(ctx, "%s%s", up, op->enum_name);
   3066         if (op->role == b->role.ternary) {
   3067           char* tok2 = token_ref(ctx, b, up, op->tok2);
   3068           emit_line(out,
   3069                     "    { .role = %s, .tok = %s, .tok2 = %s, .prod = %s, .lbp "
   3070                     "= %d, .rbp = %d },",
   3071                     pratt_role_ref(b, op->role), tok, tok2, prod_name, op->lbp,
   3072                     op->rbp);
   3073           xfree(ctx, tok2);
   3074         } else if (op->role == b->role.circumfix) {
   3075           char* tok2 = token_ref(ctx, b, up, op->tok2);
   3076           char* inner = rule_ref(ctx, b, up, op->inner_rule);
   3077           emit_line(out,
   3078                     "    { .role = %s, .tok = %s, .tok2 = %s, .prod = %s, .lbp "
   3079                     "= %d, .rbp = %d, .inner = KIT_GRAM_RULE(%s) },",
   3080                     pratt_role_ref(b, op->role), tok, tok2, prod_name, op->lbp,
   3081                     op->rbp, inner);
   3082           xfree(ctx, tok2);
   3083           xfree(ctx, inner);
   3084         } else {
   3085           emit_line(out,
   3086                     "    { .role = %s, .tok = %s, .prod = %s, .lbp = %d, .rbp "
   3087                     "= %d },",
   3088                     pratt_role_ref(b, op->role), tok, prod_name, op->lbp,
   3089                     op->rbp);
   3090         }
   3091         xfree(ctx, tok);
   3092         xfree(ctx, prod_name);
   3093       }
   3094       emit_line(out, "};");
   3095       ops_name = "pratt_ops_r";
   3096       snprintf(nops_buf, sizeof nops_buf, "N(pratt_ops_r%zu)", ri);
   3097     }
   3098     char* primary_ref = rule_ref(ctx, b, up, rule->pratt->primary_rule);
   3099     char* primary_name_raw =
   3100         kit_gram_pratt_prod_enum_name(ctx, b, rule->name, b->role.primary, 0);
   3101     char* primary_name = xasprintf(ctx, "%s%s", up, primary_name_raw);
   3102     xfree(ctx, primary_name_raw);
   3103     emit_line(out, "static const KitGramPratt pratt_r%zu = {", ri);
   3104     emit_line(out, "    .primary = %s,", primary_ref);
   3105     emit_line(out, "    .primary_prod = %s,", primary_name);
   3106     if (rule->pratt->nops)
   3107       emit_line(out, "    .ops = pratt_ops_r%zu, .nops = %s,", ri, nops_buf);
   3108     else
   3109       emit_line(out, "    .ops = %s, .nops = %s,", ops_name, nops_buf);
   3110     emit_line(out, "};");
   3111     emit_line(out, "");
   3112     xfree(ctx, primary_ref);
   3113     xfree(ctx, primary_name);
   3114   }
   3115 }
   3116 
   3117 static void arr_ptr(GramgenContext* ctx, const char* prefix, size_t ri,
   3118                     const IntSet* set, char** ptr, char** nptr) {
   3119   if (!set->n) {
   3120     *ptr = xstrdup(ctx, "NULL");
   3121     *nptr = xstrdup(ctx, "0");
   3122   } else {
   3123     *ptr = xasprintf(ctx, "%s_r%zu", prefix, ri);
   3124     *nptr = xasprintf(ctx, "N(%s_r%zu)", prefix, ri);
   3125   }
   3126 }
   3127 
   3128 static void predict_ptr(GramgenContext* ctx, Rule* rule, size_t ri, char** tok,
   3129                         char** prod, char** n) {
   3130   if (rule->pratt || !rule->first.n) {
   3131     *tok = xstrdup(ctx, "NULL");
   3132     *prod = xstrdup(ctx, "NULL");
   3133     *n = xstrdup(ctx, "0");
   3134   } else {
   3135     *tok = xasprintf(ctx, "fst_r%zu", ri);
   3136     *prod = xasprintf(ctx, "pp_r%zu", ri);
   3137     *n = xasprintf(ctx, "N(fst_r%zu)", ri);
   3138   }
   3139 }
   3140 
   3141 static void emit_rules(Builder* b, Buf* out, const char* up) {
   3142   GramgenContext* ctx = out->ctx;
   3143   emit_line(out, "static const KitGramRule g_rules[] = {");
   3144   for (size_t ri = 0; ri < b->nrules; ri++) {
   3145     Rule* rule = &b->rules[ri];
   3146     char *pred_tok, *pred_prod, *npred;
   3147     char *first, *nfirst, *follow, *nfollow;
   3148     predict_ptr(ctx, rule, ri, &pred_tok, &pred_prod, &npred);
   3149     arr_ptr(ctx, "fst", ri, &rule->first, &first, &nfirst);
   3150     arr_ptr(ctx, "flw", ri, &rule->follow, &follow, &nfollow);
   3151     char* rref = rule_ref(ctx, b, up, (int)ri);
   3152     char* name = quote_len(ctx, rule->name, strlen(rule->name), false);
   3153     const char* hidden = rule->public_rule ? "false" : "true";
   3154     const char* is_pratt = rule->pratt ? "true" : "false";
   3155     char* pratt =
   3156         rule->pratt ? xasprintf(ctx, "&pratt_r%zu", ri) : xstrdup(ctx, "NULL");
   3157     char* prods =
   3158         rule->pratt ? xstrdup(ctx, "NULL") : xasprintf(ctx, "prods_r%zu", ri);
   3159     char* nprods =
   3160         rule->pratt ? xstrdup(ctx, "0") : xasprintf(ctx, "N(prods_r%zu)", ri);
   3161     emit_line(out,
   3162               "    [%s] = { .name = %s, .hidden = %s, .is_pratt = %s, .pratt = "
   3163               "%s, .prods = %s, .nprods = %s, .predict_tok = %s, .predict_prod "
   3164               "= %s, .npredict = %s, .nullable = %s, .empty_prod = %d, .first "
   3165               "= %s, .nfirst = %s, .follow = %s, .nfollow = %s },",
   3166               rref, name, hidden, is_pratt, pratt, prods, nprods, pred_tok,
   3167               pred_prod, npred, rule->nullable ? "true" : "false",
   3168               rule->empty_prod, first, nfirst, follow, nfollow);
   3169     xfree(ctx, pred_tok);
   3170     xfree(ctx, pred_prod);
   3171     xfree(ctx, npred);
   3172     xfree(ctx, first);
   3173     xfree(ctx, nfirst);
   3174     xfree(ctx, follow);
   3175     xfree(ctx, nfollow);
   3176     xfree(ctx, rref);
   3177     xfree(ctx, name);
   3178     xfree(ctx, pratt);
   3179     xfree(ctx, prods);
   3180     xfree(ctx, nprods);
   3181   }
   3182   emit_line(out, "};");
   3183   emit_line(out, "");
   3184 }
   3185 
   3186 /* One per-state accept table (accept / accept_text / accept_line). `suffix` is
   3187  * "", "_text", or "_line". Each cell is LXA (none) or an A_LEX_di_n accept id.
   3188  */
   3189 static void emit_lex_accept_array(Buf* out, size_t di, const char* suffix,
   3190                                   const uint16_t* arr, size_t n) {
   3191   emit_line(out, "static const uint16_t lex_%zu_accept%s[] = {", di, suffix);
   3192   for (size_t i = 0; i < n; i += 12) {
   3193     size_t end = i + 12 < n ? i + 12 : n;
   3194     buf_append(out, "    ");
   3195     for (size_t j = i; j < end; j++) {
   3196       if (j > i) buf_append(out, ", ");
   3197       uint16_t acc = arr[j];
   3198       if (acc == UINT16_MAX)
   3199         buf_append(out, "LXA");
   3200       else {
   3201         buf_append(out, "A_LEX_");
   3202         buf_append_uint(out, (unsigned)di);
   3203         buf_append(out, "_");
   3204         buf_append_uint(out, acc);
   3205       }
   3206     }
   3207     buf_append(out, ",\n");
   3208   }
   3209   emit_line(out, "};");
   3210   emit_line(out, "");
   3211 }
   3212 
   3213 static void emit_lexer_tables(Builder* b, Buf* out, const char* grammar_path,
   3214                               const char* prefix) {
   3215   GramgenContext* ctx = out->ctx;
   3216   if (!b->nlex_dfas) return;
   3217   char* stem = path_stem(ctx, grammar_path);
   3218   char* up = make_upper_prefix(ctx, prefix);
   3219 
   3220   for (size_t di = 0; di < b->nlex_dfas; di++) {
   3221     LexDFA* dfa = &b->lex_dfas[di];
   3222     emit_line(out, "enum {");
   3223     for (size_t i = 0; i < dfa->nrecognizers; i++)
   3224       emit_line(out, "    A_LEX_%zu_%zu,", di, i);
   3225     emit_line(out, "    A_LEX_%zu_COUNT", di);
   3226     emit_line(out, "};");
   3227     emit_line(out, "");
   3228     emit_line(out, "static const KitGramLexAccept lex_%zu_accepts[] = {", di);
   3229     for (size_t i = 0; i < dfa->nrecognizers; i++) {
   3230       LexRecognizer* rec = &dfa->recognizers[i];
   3231       char* base;
   3232       if (rec->skip) {
   3233         base = xstrdup(ctx, ".skip = true");
   3234       } else {
   3235         char* tok = token_ref(ctx, b, up, rec->tok);
   3236         base = xasprintf(ctx, ".tok = %s", tok);
   3237         xfree(ctx, tok);
   3238       }
   3239       emit_line(out, "    [A_LEX_%zu_%zu] = { %s },", di, i, base);
   3240       xfree(ctx, base);
   3241     }
   3242     emit_line(out, "};");
   3243     emit_line(out, "");
   3244     emit_line(out, "#define LXA KIT_GRAM_LEX_ACCEPT_NONE");
   3245     emit_line(out, "#define LXD KIT_GRAM_LEX_DEAD");
   3246     emit_line(out, "");
   3247     emit_lex_accept_array(out, di, "", dfa->accept, dfa->nstates);
   3248     /* End-of-match context accept tables, only when the grammar uses that
   3249      * end anchor (NULL otherwise keeps anchor-free output byte-identical). */
   3250     if (dfa->accept_text)
   3251       emit_lex_accept_array(out, di, "_text", dfa->accept_text, dfa->nstates);
   3252     if (dfa->accept_line)
   3253       emit_lex_accept_array(out, di, "_line", dfa->accept_line, dfa->nstates);
   3254     uint16_t stride = dfa->nclasses;
   3255     emit_line(out, "static const uint16_t lex_%zu_trans[] = {", di);
   3256     {
   3257       /* Dense by-state table: cell (s, c) lives at s * class_stride + c,
   3258        * indexed directly by state. Generated tables use nclasses as the
   3259        * stride; class_stride exists so older and experimental tables can
   3260        * still be consumed. */
   3261       size_t ncells = (size_t)dfa->nstates * stride;
   3262       for (size_t i = 0; i < ncells; i += 12) {
   3263         size_t end = i + 12 < ncells ? i + 12 : ncells;
   3264         buf_append(out, "    ");
   3265         for (size_t j = i; j < end; j++) {
   3266           if (j > i) buf_append(out, ", ");
   3267           uint16_t dst = dfa->trans[j];
   3268           if (dst == UINT16_MAX)
   3269             buf_append(out, "LXD");
   3270           else
   3271             buf_append_uint(out, dst);
   3272         }
   3273         buf_append(out, ",\n");
   3274       }
   3275     }
   3276     emit_line(out, "};");
   3277     emit_line(out, "");
   3278     emit_line(out, "#undef LXA");
   3279     emit_line(out, "#undef LXD");
   3280     emit_line(out, "");
   3281 
   3282     /* Extracted-keyword tables: per host, CHD bucket seeds + the minimal
   3283      * perfect hash slot array. */
   3284     for (size_t ki = 0; ki < dfa->nkeyword_tables; ki++) {
   3285       LexKeywordTable* kt = &dfa->keyword_tables[ki];
   3286       emit_line(out, "static const uint32_t lex_%zu_kw%zu_seeds[] = {", di, ki);
   3287       for (size_t i = 0; i < kt->nseeds; i += 12) {
   3288         size_t end = i + 12 < kt->nseeds ? i + 12 : kt->nseeds;
   3289         buf_append(out, "    ");
   3290         for (size_t j = i; j < end; j++) {
   3291           if (j > i) buf_append(out, ", ");
   3292           buf_append_uint(out, (unsigned)kt->seeds[j]);
   3293         }
   3294         buf_append(out, ",\n");
   3295       }
   3296       emit_line(out, "};");
   3297       emit_line(out, "");
   3298       emit_line(out, "static const KitGramLexKeyword lex_%zu_kw%zu[] = {", di,
   3299                 ki);
   3300       for (size_t wi = 0; wi < kt->nslots; wi++) {
   3301         LexKeyword* kw = &kt->slots[wi];
   3302         if (!kw->literal.s) {
   3303           emit_line(out, "    { 0 },");
   3304           continue;
   3305         }
   3306         char* q = quote_len(ctx, kw->literal.s, kw->literal.len, false);
   3307         char* tr = token_ref(ctx, b, up, kw->tok);
   3308         emit_line(out, "    { %s, %u, %s },", q, (unsigned)kw->literal.len, tr);
   3309         xfree(ctx, q);
   3310         xfree(ctx, tr);
   3311       }
   3312       emit_line(out, "};");
   3313       emit_line(out, "");
   3314     }
   3315     if (dfa->nkeyword_tables) {
   3316       emit_line(
   3317           out,
   3318           "static const KitGramLexKeywordTable lex_%zu_keyword_tables[] = {",
   3319           di);
   3320       for (size_t ki = 0; ki < dfa->nkeyword_tables; ki++) {
   3321         LexKeywordTable* kt = &dfa->keyword_tables[ki];
   3322         char* hr = token_ref(ctx, b, up, kt->host);
   3323         emit_line(
   3324             out,
   3325             "    { .host = %s, .seeds = lex_%zu_kw%zu_seeds, .nseeds = %zu,"
   3326             " .keywords = lex_%zu_kw%zu, .nkeywords = %zu,"
   3327             " .min_len = %zu, .max_len = %zu },",
   3328             hr, di, ki, kt->nseeds, di, ki, kt->nslots, kt->min_len,
   3329             kt->max_len);
   3330         xfree(ctx, hr);
   3331       }
   3332       emit_line(out, "};");
   3333       emit_line(out, "");
   3334     }
   3335 
   3336     char* runtime_name;
   3337     if (lex_name_is_main_c(dfa->name))
   3338       runtime_name = xstrdup(ctx, stem);
   3339     else
   3340       runtime_name = xasprintf(ctx, "%s:%s", stem, dfa->name);
   3341     char* runtime_name_q =
   3342         quote_len(ctx, runtime_name, strlen(runtime_name), false);
   3343     char* grammar = lex_grammar_symbol(ctx, prefix, dfa->name);
   3344     char* ctor = lex_constructor_symbol(ctx, prefix, dfa->name);
   3345     emit_line(out, "const KitGramLexGrammar %s = {", grammar);
   3346     emit_line(out, "    .name = %s,", runtime_name_q);
   3347     emit_line(out, "    .input = %s,",
   3348               dfa->input == KIT_GRAM_LEX_INPUT_UTF8 ? "KIT_GRAM_LEX_INPUT_UTF8"
   3349                                                 : "KIT_GRAM_LEX_INPUT_BYTES");
   3350     emit_line(out, "    .class_of = {");
   3351     for (int i = 0; i < 256; i += 16) {
   3352       buf_append(out, "        ");
   3353       for (int j = 0; j < 16; j++) {
   3354         if (j) buf_append(out, ", ");
   3355         buf_append_uint(out, (unsigned)dfa->class_of[i + j]);
   3356       }
   3357       buf_append(out, ",\n");
   3358     }
   3359     emit_line(out, "    },");
   3360     emit_line(out, "    .nclasses = %u,", (unsigned)dfa->nclasses);
   3361     emit_line(out, "    .class_stride = %u,", (unsigned)stride);
   3362     emit_line(out, "    .trans = lex_%zu_trans,", di);
   3363     emit_line(out, "    .nstates = %u,", (unsigned)dfa->nstates);
   3364     emit_line(out, "    .accept = lex_%zu_accept,", di);
   3365     if (dfa->accept_text)
   3366       emit_line(out, "    .accept_text = lex_%zu_accept_text,", di);
   3367     if (dfa->accept_line)
   3368       emit_line(out, "    .accept_line = lex_%zu_accept_line,", di);
   3369     emit_line(out, "    .accepts = lex_%zu_accepts,", di);
   3370     emit_line(out, "    .naccepts = A_LEX_%zu_COUNT,", di);
   3371     if (dfa->start_text)
   3372       emit_line(out, "    .start_text = %u,", (unsigned)dfa->start_text);
   3373     if (dfa->start_line)
   3374       emit_line(out, "    .start_line = %u,", (unsigned)dfa->start_line);
   3375     if (b->multiline) emit_line(out, "    .multiline = true,");
   3376     if (dfa->nkeyword_tables) {
   3377       emit_line(out, "    .keyword_tables = lex_%zu_keyword_tables,", di);
   3378       emit_line(out, "    .nkeyword_tables = %zu,", dfa->nkeyword_tables);
   3379     }
   3380     emit_line(out, "};");
   3381     emit_line(out, "");
   3382     char* minit = lex_name_is_main_c(dfa->name)
   3383                       ? xasprintf(ctx, "%smatcher_init", prefix)
   3384                       : xasprintf(ctx, "%s%s_matcher_init", prefix, dfa->name);
   3385     emit_line(out, "void %s(KitGramMatcher *m) {", minit);
   3386     emit_line(out, "    kit_gram_matcher_bind(m, &%s);", grammar);
   3387     emit_line(out, "}");
   3388     emit_line(out, "");
   3389     emit_line(out,
   3390               "void %s(KitGramLexer *mem, KitGramLexInput *in, const "
   3391               "KitGramLexConfig *cfg) {",
   3392               ctor);
   3393     emit_line(out, "    kit_gram_lexer_init(mem, &%s, in, cfg);", grammar);
   3394     emit_line(out, "}");
   3395     if (di + 1 < b->nlex_dfas) emit_line(out, "");
   3396     xfree(ctx, runtime_name);
   3397     xfree(ctx, runtime_name_q);
   3398     xfree(ctx, grammar);
   3399     xfree(ctx, ctor);
   3400     xfree(ctx, minit);
   3401   }
   3402   xfree(ctx, up);
   3403   xfree(ctx, stem);
   3404 }
   3405 
   3406 /* ---- standalone (table-free) lexer emission (--lexer-standalone) ----------
   3407  * A self-contained tokenizer + match API: the DFA table is dropped and the
   3408  * generated `.c` links nothing from the lexer runtime. The scanner is a
   3409  * re2c-style state machine — one label per state, byte transitions as inline
   3410  * range comparisons — with the accepted token kind and skip-ness baked in
   3411  * directly (no accepts[] lookup) and the edge-anchor end context decided inline
   3412  * per accepting state. %keywords default to a minimal perfect hash (an inline
   3413  * lookup over the same seeds/slots the table path emits); --fold-keywords folds
   3414  * them into the DFA instead. One resident-buffer driver per %lex mode returns
   3415  * KitGramToken streams identical to kit_gram_lexer_next; the main lexer's match
   3416  * drivers mirror kit_gram_match_anchored/full/find + KitGramMatchIter.
   3417  */
   3418 
   3419 /* Min of two accept ids; KIT_GRAM_LEX_ACCEPT_NONE (UINT16_MAX) is the additive max,
   3420  * so plain min naturally drops the "no accept" sentinel and keeps the lowest
   3421  * source priority (smallest id) among the eligible accepts. */
   3422 static uint16_t std_min2(uint16_t a, uint16_t b) { return a < b ? a : b; }
   3423 
   3424 /* Emit the longest-match backup for accept id `id` (an index into recognizers),
   3425  * baking the resolved token kind / skip-ness; nothing when id is NONE. */
   3426 static void emit_std_record(Buf* out, Builder* b, LexDFA* dfa, uint16_t id,
   3427                             const char* ind, const char* up) {
   3428   if (id == UINT16_MAX) return;
   3429   GramgenContext* ctx = out->ctx;
   3430   LexRecognizer* r = &dfa->recognizers[id];
   3431   /* position: lazy drops the line-state snapshot from every accept. */
   3432   const char* pos =
   3433       b->position_lazy ? "" : " best_nlines = nlines; best_last_nl = last_nl;";
   3434   if (r->skip) {
   3435     emit_line(out, "%sbest_skip = 1; best_kind = 0; mark = p; found = 1;%s",
   3436               ind, pos);
   3437   } else {
   3438     char* tok = token_ref(ctx, b, up, r->tok);
   3439     emit_line(out, "%sbest_skip = 0; best_kind = %s; mark = p; found = 1;%s",
   3440               ind, tok, pos);
   3441     xfree(ctx, tok);
   3442   }
   3443 }
   3444 
   3445 static void emit_lexer_standalone(Builder* b, Buf* out, const char* prefix) {
   3446   GramgenContext* ctx = out->ctx;
   3447   if (!b->nlex_dfas) return;
   3448   int lazy = b->position_lazy; /* omit per-token line/col tracking */
   3449   char* up = make_upper_prefix(ctx, prefix);
   3450   /* Every %lex mode gets its own resident tokenizer and its own match API, so a
   3451    * caller can drive each lexer independently and re-lex a token's bytes (a
   3452    * NUMBER or STRING payload, say) with a named sub-mode. */
   3453 
   3454   /* The UTF-8 position/line-break helpers are prefix-scoped (shared by every
   3455    * mode), so emit each once if any mode needs it (union over all modes). */
   3456   int any_utf8 = 0, any_break_at = 0, any_break_before = 0;
   3457   for (size_t i = 0; i < b->nlex_dfas; i++) {
   3458     LexDFA* d = &b->lex_dfas[i];
   3459     if (d->input != KIT_GRAM_LEX_INPUT_UTF8) continue;
   3460     any_utf8 = 1;
   3461     if (d->accept_line) any_break_at = 1;
   3462     if (d->start_line) any_break_before = 1;
   3463   }
   3464   const char* ML = b->multiline ? "1" : "0";
   3465 
   3466   emit_line(out, "");
   3467   emit_line(
   3468       out,
   3469       "/* ---- standalone tokenizer + match API (--lexer-standalone) ---- */");
   3470   emit_line(
   3471       out,
   3472       "typedef struct { KitGramTokenKind kind; int skip; int found; size_t len;");
   3473   emit_line(out, "                 size_t nlines, last_nl; } %ssres;", prefix);
   3474   emit_line(out, "");
   3475 
   3476   /* UTF-8 scalar position folding (mirrors scan_pos_advance + the CRLF-aware
   3477    * kit_gram_unicode_pos_advance) — only the byte arithmetic is needed in byte
   3478    * mode.
   3479    */
   3480   if (any_utf8 && !lazy) {
   3481     /* Pointer-taking so both the tokenizer and the fused parser fold position
   3482      * into their own line/col/after_cr state. */
   3483     emit_line(out,
   3484               "static void %scp_advance(uint32_t *line, uint32_t *col, bool "
   3485               "*after_cr, uint32_t cp) {",
   3486               prefix);
   3487     emit_line(
   3488         out,
   3489         "    if (cp == 0x0Au && *after_cr) { *after_cr = false; return; }");
   3490     emit_line(out,
   3491               "    if (cp == 0x0Du) { (*line)++; *col = 1; *after_cr = true; "
   3492               "return; }");
   3493     emit_line(out, "    *after_cr = false;");
   3494     emit_line(
   3495         out,
   3496         "    if (cp == 0x0Au || cp == 0x0Bu || cp == 0x0Cu || cp == 0x0Du ||");
   3497     emit_line(out,
   3498               "        cp == 0x85u || cp == 0x2028u || cp == 0x2029u) { "
   3499               "(*line)++; *col = 1; }");
   3500     emit_line(out, "    else (*col)++;");
   3501     emit_line(out, "}");
   3502     emit_line(
   3503         out,
   3504         "static void %sfold_pos(uint32_t *line, uint32_t *col, bool *after_cr,",
   3505         prefix);
   3506     emit_line(out,
   3507               "                       const unsigned char *s, size_t len) {");
   3508     emit_line(out, "    uint32_t cp = 0; unsigned need = 0;");
   3509     emit_line(out, "    for (size_t i = 0; i < len; i++) {");
   3510     emit_line(out, "        unsigned char bb = s[i];");
   3511     emit_line(out, "        if (!need) {");
   3512     emit_line(
   3513         out,
   3514         "            if (bb < 0x80u) %scp_advance(line, col, after_cr, bb);",
   3515         prefix);
   3516     emit_line(out,
   3517               "            else if (bb >= 0xC2u && bb <= 0xDFu) { cp = "
   3518               "(uint32_t)(bb & 0x1Fu); need = 1; }");
   3519     emit_line(out,
   3520               "            else if (bb >= 0xE0u && bb <= 0xEFu) { cp = "
   3521               "(uint32_t)(bb & 0x0Fu); need = 2; }");
   3522     emit_line(out,
   3523               "            else if (bb >= 0xF0u && bb <= 0xF4u) { cp = "
   3524               "(uint32_t)(bb & 0x07u); need = 3; }");
   3525     emit_line(out, "        } else if ((bb & 0xC0u) != 0x80u) { need = 0; }");
   3526     emit_line(out,
   3527               "        else { cp = (cp << 6) | (uint32_t)(bb & 0x3Fu); if "
   3528               "(--need == 0) %scp_advance(line, col, after_cr, cp); }",
   3529               prefix);
   3530     emit_line(out, "    }");
   3531     emit_line(out, "}");
   3532     emit_line(out, "");
   3533   }
   3534 
   3535   /* UTF-8 line-break peek helpers, emitted only when an end/start line anchor
   3536    * needs them (a byte-mode peek is just `*p == '\\n'`, no helper). */
   3537   if (any_break_at || any_break_before) {
   3538     emit_line(out,
   3539               "static size_t %sdecode(const unsigned char *p, size_t avail, "
   3540               "uint32_t *cp) {",
   3541               prefix);
   3542     emit_line(out, "    if (!avail) return 0;");
   3543     emit_line(out, "    unsigned char b0 = p[0];");
   3544     emit_line(out, "    if (b0 < 0x80u) { *cp = b0; return 1; }");
   3545     emit_line(out, "    if (b0 >= 0xC2u && b0 <= 0xDFu) {");
   3546     emit_line(out,
   3547               "        if (avail < 2 || (p[1] & 0xC0u) != 0x80u) return 0;");
   3548     emit_line(out,
   3549               "        *cp = ((uint32_t)(b0 & 0x1Fu) << 6) | (uint32_t)(p[1] & "
   3550               "0x3Fu); return 2;");
   3551     emit_line(out, "    }");
   3552     emit_line(out, "    if (b0 >= 0xE0u && b0 <= 0xEFu) {");
   3553     emit_line(out,
   3554               "        if (avail < 3 || (p[1] & 0xC0u) != 0x80u || (p[2] & "
   3555               "0xC0u) != 0x80u) return 0;");
   3556     emit_line(out,
   3557               "        *cp = ((uint32_t)(b0 & 0x0Fu) << 12) | ((uint32_t)(p[1] "
   3558               "& 0x3Fu) << 6) | (uint32_t)(p[2] & 0x3Fu);");
   3559     emit_line(out, "        return 3;");
   3560     emit_line(out, "    }");
   3561     emit_line(out, "    if (b0 >= 0xF0u && b0 <= 0xF4u) {");
   3562     emit_line(out,
   3563               "        if (avail < 4 || (p[1] & 0xC0u) != 0x80u || (p[2] & "
   3564               "0xC0u) != 0x80u || (p[3] & 0xC0u) != 0x80u) return 0;");
   3565     emit_line(out,
   3566               "        *cp = ((uint32_t)(b0 & 0x07u) << 18) | ((uint32_t)(p[1] "
   3567               "& 0x3Fu) << 12) |");
   3568     emit_line(out,
   3569               "              ((uint32_t)(p[2] & 0x3Fu) << 6) | (uint32_t)(p[3] "
   3570               "& 0x3Fu); return 4;");
   3571     emit_line(out, "    }");
   3572     emit_line(out, "    return 0;");
   3573     emit_line(out, "}");
   3574   }
   3575   if (any_break_at) {
   3576     emit_line(out,
   3577               "static int %sbreak_at(const unsigned char *p, const unsigned "
   3578               "char *end) {",
   3579               prefix);
   3580     emit_line(out, "    uint32_t cp;");
   3581     emit_line(out, "    if (!%sdecode(p, (size_t)(end - p), &cp)) return 0;",
   3582               prefix);
   3583     emit_line(out,
   3584               "    return cp == 0x0Au || cp == 0x0Bu || cp == 0x0Cu || cp == "
   3585               "0x0Du ||");
   3586     emit_line(out, "           cp == 0x85u || cp == 0x2028u || cp == 0x2029u;");
   3587     emit_line(out, "}");
   3588   }
   3589   if (any_break_before) {
   3590     emit_line(out,
   3591               "static int %sbreak_before(const unsigned char *buf, size_t len, "
   3592               "size_t pos) {",
   3593               prefix);
   3594     emit_line(out, "    if (pos == 0) return 0;");
   3595     emit_line(out, "    size_t start = pos - 1;");
   3596     emit_line(out, "    while (start > 0) {");
   3597     emit_line(out, "        unsigned char bb = buf[start];");
   3598     emit_line(out, "        if ((bb & 0xC0u) != 0x80u) break;");
   3599     emit_line(out, "        if (pos - start >= 4) return 0;");
   3600     emit_line(out, "        start--;");
   3601     emit_line(out, "    }");
   3602     emit_line(
   3603         out,
   3604         "    uint32_t cp; size_t n = %sdecode(buf + start, len - start, &cp);",
   3605         prefix);
   3606     emit_line(out,
   3607               "    return n && start + n == pos && (cp == 0x0Au || cp == 0x0Bu "
   3608               "|| cp == 0x0Cu ||");
   3609     emit_line(out,
   3610               "           cp == 0x0Du || cp == 0x85u || cp == 0x2028u || cp == "
   3611               "0x2029u);");
   3612     emit_line(out, "}");
   3613   }
   3614   if (any_break_at || any_break_before) emit_line(out, "");
   3615 
   3616   /* Shared keyword-compare for the MPH lookup (default; --fold-keywords skips
   3617    * this and bakes keywords into the DFA). One byte loop keeps the standalone
   3618    * lexer free of <string.h>. */
   3619   int any_kw = 0;
   3620   for (size_t i = 0; i < b->nlex_dfas; i++)
   3621     if (b->lex_dfas[i].nkeyword_tables) {
   3622       any_kw = 1;
   3623       break;
   3624     }
   3625   if (any_kw) {
   3626     emit_line(
   3627         out,
   3628         "static int %skw_eq(const char *a, const unsigned char *b, size_t "
   3629         "n) {",
   3630         prefix);
   3631     emit_line(out,
   3632               "    for (size_t i = 0; i < n; i++) if ((unsigned char)a[i] != "
   3633               "b[i]) return 0;");
   3634     emit_line(out, "    return 1;");
   3635     emit_line(out, "}");
   3636     emit_line(out, "");
   3637   }
   3638 
   3639   /* One scanner + resident tokenizer per %lex mode (declaration order). Each
   3640    * mode also gets its own match API below. */
   3641   for (size_t mi = 0; mi < b->nlex_dfas; mi++) {
   3642     LexDFA* dfa = &b->lex_dfas[mi];
   3643     int utf8 = dfa->input == KIT_GRAM_LEX_INPUT_UTF8;
   3644     int has_end = dfa->accept_text || dfa->accept_line;
   3645     int has_start = dfa->start_text || dfa->start_line;
   3646     size_t nclasses = dfa->nclasses;
   3647     char* scan = lex_scan_symbol(ctx, prefix, dfa->name);
   3648     char* base = std_lex_base(ctx, prefix, dfa->name);
   3649     char* brk_at = utf8 ? xasprintf(ctx, "%sbreak_at(p, end)", prefix)
   3650                         : xstrdup(ctx, "*p == 10");
   3651 
   3652     /* Extracted-keyword MPH tables + inline lookup (the default;
   3653      * --fold-keywords folds keywords into the DFA above and leaves
   3654      * nkeyword_tables == 0). Each host gets the same CHD seeds + slot array the
   3655      * table runtime would, and the rewrite mirrors kw_lookup_hashed using the
   3656      * header-only kit_gram_lex_kw_hash64, so it is table-free yet agrees with
   3657      * construction byte-for-byte. */
   3658     int has_kw = dfa->nkeyword_tables > 0;
   3659     if (has_kw) {
   3660       for (size_t ki = 0; ki < dfa->nkeyword_tables; ki++) {
   3661         LexKeywordTable* kt = &dfa->keyword_tables[ki];
   3662         emit_line(out, "static const uint32_t %skw%zu_%zu_seeds[] = {", prefix,
   3663                   mi, ki);
   3664         for (size_t j = 0; j < kt->nseeds; j += 12) {
   3665           size_t e = j + 12 < kt->nseeds ? j + 12 : kt->nseeds;
   3666           buf_append(out, "    ");
   3667           for (size_t k = j; k < e; k++) {
   3668             if (k > j) buf_append(out, ", ");
   3669             buf_append_uint(out, (unsigned)kt->seeds[k]);
   3670           }
   3671           buf_append(out, ",\n");
   3672         }
   3673         emit_line(out, "};");
   3674         emit_line(out, "static const KitGramLexKeyword %skw%zu_%zu[] = {",
   3675                   prefix, mi, ki);
   3676         for (size_t wi = 0; wi < kt->nslots; wi++) {
   3677           LexKeyword* kw = &kt->slots[wi];
   3678           if (!kw->literal.s) {
   3679             emit_line(out, "    { 0 },");
   3680             continue;
   3681           }
   3682           char* q = quote_len(ctx, kw->literal.s, kw->literal.len, false);
   3683           char* tr = token_ref(ctx, b, up, kw->tok);
   3684           emit_line(out, "    { %s, %u, %s },", q, (unsigned)kw->literal.len,
   3685                     tr);
   3686           xfree(ctx, q);
   3687           xfree(ctx, tr);
   3688         }
   3689         emit_line(out, "};");
   3690       }
   3691       emit_line(out,
   3692                 "static KitGramTokenKind %skw_rw%zu(KitGramTokenKind host, const "
   3693                 "unsigned char *s, size_t len) {",
   3694                 prefix, mi);
   3695       emit_line(out, "    switch (host) {");
   3696       for (size_t ki = 0; ki < dfa->nkeyword_tables; ki++) {
   3697         LexKeywordTable* kt = &dfa->keyword_tables[ki];
   3698         char* hr = token_ref(ctx, b, up, kt->host);
   3699         emit_line(out, "    case %s: {", hr);
   3700         emit_line(out, "        if (len < %zu || len > %zu) return host;",
   3701                   (size_t)kt->min_len, (size_t)kt->max_len);
   3702         emit_line(out,
   3703                   "        uint32_t bk = (uint32_t)(kit_gram_lex_kw_hash64(0, s, "
   3704                   "len) & %zuu);",
   3705                   (size_t)kt->nseeds - 1);
   3706         emit_line(out,
   3707                   "        uint32_t sl = (uint32_t)(kit_gram_lex_kw_hash64("
   3708                   "%skw%zu_%zu_seeds[bk], s, len) & %zuu);",
   3709                   prefix, mi, ki, (size_t)kt->nslots - 1);
   3710         emit_line(out, "        const KitGramLexKeyword *kw = &%skw%zu_%zu[sl];",
   3711                   prefix, mi, ki);
   3712         emit_line(out,
   3713                   "        if (kw->lexeme && kw->len == len && %skw_eq(kw->"
   3714                   "lexeme, s, len)) return kw->kind;",
   3715                   prefix);
   3716         emit_line(out, "        return host;");
   3717         emit_line(out, "    }");
   3718         xfree(ctx, hr);
   3719       }
   3720       emit_line(out, "    default: return host;");
   3721       emit_line(out, "    }");
   3722       emit_line(out, "}");
   3723     }
   3724 
   3725     /* The scanner. Parameterized on `ml` (the tokenizer passes the baked
   3726      * multiline constant; the match API passes opts->multiline at runtime). */
   3727     emit_line(out,
   3728               "static %ssres %s(uint16_t start_state, const unsigned char *p,",
   3729               prefix, scan);
   3730     emit_line(
   3731         out,
   3732         "                              const unsigned char *end, int ml) {");
   3733     emit_line(out, "    const unsigned char *const s0 = p;");
   3734     emit_line(out, "    const unsigned char *mark = p;");
   3735     emit_line(out, "    KitGramTokenKind best_kind = 0;");
   3736     emit_line(out, "    int best_skip = 0, found = 0;");
   3737     if (!lazy)
   3738       emit_line(out,
   3739                 "    size_t nlines = 0, last_nl = 0, best_nlines = 0, "
   3740                 "best_last_nl = 0;");
   3741     emit_line(out, "    unsigned char yych;");
   3742     emit_line(out, "    (void)ml;");
   3743     if (has_start) {
   3744       emit_line(out, "    switch (start_state) {");
   3745       if (dfa->start_text)
   3746         emit_line(out, "    case %u: goto st%u;", (unsigned)dfa->start_text,
   3747                   (unsigned)dfa->start_text);
   3748       if (dfa->start_line && dfa->start_line != dfa->start_text)
   3749         emit_line(out, "    case %u: goto st%u;", (unsigned)dfa->start_line,
   3750                   (unsigned)dfa->start_line);
   3751       emit_line(out, "    default: goto st0;");
   3752       emit_line(out, "    }");
   3753     } else {
   3754       emit_line(out, "    switch (start_state) { default: goto st0; }");
   3755     }
   3756     for (size_t s = 0; s < dfa->nstates; s++) {
   3757       const uint16_t* row = &dfa->trans[s * nclasses];
   3758       emit_line(out, "  st%zu:", s);
   3759       int has_out = 0;
   3760       for (int by = 0; by < 256; by++)
   3761         if (row[dfa->class_of[by]] != UINT16_MAX) {
   3762           has_out = 1;
   3763           break;
   3764         }
   3765       if (!has_end) {
   3766         emit_std_record(out, b, dfa, dfa->accept[s], "    ", up);
   3767         if (!has_out) {
   3768           emit_line(out, "    goto done;");
   3769           continue;
   3770         }
   3771         emit_line(out, "    if (p == end) goto done;");
   3772         emit_line(out, "    yych = *p;");
   3773       } else {
   3774         uint16_t plain = dfa->accept[s];
   3775         uint16_t text = dfa->accept_text ? dfa->accept_text[s] : UINT16_MAX;
   3776         uint16_t line = dfa->accept_line ? dfa->accept_line[s] : UINT16_MAX;
   3777         uint16_t win_textend = std_min2(std_min2(plain, text), line);
   3778         uint16_t win_break = std_min2(plain, line);
   3779         emit_line(out, "    if (p == end) {");
   3780         emit_std_record(out, b, dfa, win_textend, "        ", up);
   3781         emit_line(out, "        goto done;");
   3782         emit_line(out, "    }");
   3783         if (line != UINT16_MAX) {
   3784           emit_line(out, "    if (ml && %s) {", brk_at);
   3785           emit_std_record(out, b, dfa, win_break, "        ", up);
   3786           emit_line(out, "    } else {");
   3787           emit_std_record(out, b, dfa, plain, "        ", up);
   3788           emit_line(out, "    }");
   3789         } else {
   3790           emit_std_record(out, b, dfa, plain, "    ", up);
   3791         }
   3792         if (!has_out) {
   3793           emit_line(out, "    goto done;");
   3794           continue;
   3795         }
   3796         emit_line(out, "    yych = *p;");
   3797       }
   3798       int by = 0, wildcard = 0;
   3799       while (by < 256) {
   3800         uint16_t tgt = row[dfa->class_of[by]];
   3801         int lo = by, hi = by;
   3802         while (hi + 1 < 256 && row[dfa->class_of[hi + 1]] == tgt) hi++;
   3803         by = hi + 1;
   3804         if (tgt == UINT16_MAX) continue;
   3805         int has_nl = (lo <= 10 && 10 <= hi);
   3806         const char* nl = (has_nl && !lazy)
   3807                              ? "if (yych == 10) { nlines++; last_nl = "
   3808                                "(size_t)(p - s0) + 1; } "
   3809                              : "";
   3810         if (lo == 0 && hi == 255) {
   3811           emit_line(out, "    %sp++; goto st%u;", nl, (unsigned)tgt);
   3812           wildcard = 1;
   3813           break;
   3814         } else if (lo == hi) {
   3815           emit_line(out, "    if (yych == %d) { %sp++; goto st%u; }", lo, nl,
   3816                     (unsigned)tgt);
   3817         } else if (lo == 0) {
   3818           emit_line(out, "    if (yych <= %d) { %sp++; goto st%u; }", hi, nl,
   3819                     (unsigned)tgt);
   3820         } else if (hi == 255) {
   3821           emit_line(out, "    if (yych >= %d) { %sp++; goto st%u; }", lo, nl,
   3822                     (unsigned)tgt);
   3823         } else {
   3824           emit_line(out,
   3825                     "    if (yych >= %d && yych <= %d) { %sp++; goto st%u; }",
   3826                     lo, hi, nl, (unsigned)tgt);
   3827         }
   3828       }
   3829       if (!wildcard) emit_line(out, "    goto done;");
   3830     }
   3831     emit_line(out, "  done:");
   3832     /* Rewrite a host token's kind by MPH lookup over its matched lexeme
   3833      * [s0, mark) before returning, so the tokenizer, match API, and fused
   3834      * parser all see the keyword kind without a separate pass. */
   3835     if (has_kw)
   3836       emit_line(out,
   3837                 "    if (found) best_kind = %skw_rw%zu(best_kind, s0, "
   3838                 "(size_t)(mark - s0));",
   3839                 prefix, mi);
   3840     emit_line(
   3841         out,
   3842         "    return (%ssres){ .kind = best_kind, .skip = best_skip, .found "
   3843         "= found,",
   3844         prefix);
   3845     if (lazy)
   3846       emit_line(out, "        .len = (size_t)(mark - s0) };");
   3847     else
   3848       emit_line(out,
   3849                 "        .len = (size_t)(mark - s0), .nlines = best_nlines, "
   3850                 ".last_nl = best_last_nl };");
   3851     emit_line(out, "}");
   3852 
   3853     /* The tokenizer (one per mode; the struct is shared, the entry base is
   3854      * not). */
   3855     emit_line(out, "");
   3856     emit_line(out,
   3857               "void %s_init(%slex *lx, const unsigned char *buf, size_t len) {",
   3858               base, prefix);
   3859     emit_line(out,
   3860               "    lx->cur = buf; lx->end = buf + len; lx->line = 1; lx->col = "
   3861               "1; lx->after_cr = false;");
   3862     emit_line(out, "}");
   3863     emit_line(out, "");
   3864     emit_line(out, "KitGramLexStatus %s_next(%slex *lx, KitGramToken *out) {",
   3865               base, prefix);
   3866     emit_line(out, "    for (;;) {");
   3867     emit_line(out, "        if (lx->cur == lx->end) {");
   3868     emit_line(
   3869         out,
   3870         "            if (out) *out = (KitGramToken){ .kind = %sTOK_EOF, .lexeme "
   3871         "= (const char *)lx->cur,",
   3872         up);
   3873     if (lazy)
   3874       emit_line(out,
   3875                 "                                        .line = 0, .col = "
   3876                 "0 };");
   3877     else
   3878       emit_line(
   3879           out,
   3880           "                                        .line = lx->line, .col "
   3881           "= lx->col };");
   3882     emit_line(out, "            return KIT_GRAM_LEX_EOF;");
   3883     emit_line(out, "        }");
   3884     emit_line(out, "        uint16_t start = 0;");
   3885     if (has_start) {
   3886       emit_line(out, "        if (lx->line == 1u && lx->col == 1u) start = %u;",
   3887                 (unsigned)dfa->start_text);
   3888       if (dfa->start_line)
   3889         emit_line(out, "        else if (%s && lx->col == 1u) start = %u;", ML,
   3890                   (unsigned)dfa->start_line);
   3891     }
   3892     emit_line(out, "        %ssres r = %s(start, lx->cur, lx->end, %s);",
   3893               prefix, scan, ML);
   3894     emit_line(out, "        if (!r.found) {");
   3895     emit_line(
   3896         out,
   3897         "            if (out) *out = (KitGramToken){ .kind = %sTOK_EOF, .lexeme "
   3898         "= (const char *)lx->cur,",
   3899         up);
   3900     if (lazy)
   3901       emit_line(out,
   3902                 "                                        .len = 1, .line = "
   3903                 "0, .col = 0 };");
   3904     else
   3905       emit_line(out,
   3906                 "                                        .len = 1, .line = "
   3907                 "lx->line, .col = lx->col };");
   3908     emit_line(out, "            return KIT_GRAM_LEX_ERROR;");
   3909     emit_line(out, "        }");
   3910     emit_line(out, "        const unsigned char *lex = lx->cur;");
   3911     if (!lazy) {
   3912       emit_line(out, "        uint32_t tline = lx->line, tcol = lx->col;");
   3913       if (utf8) {
   3914         emit_line(out,
   3915                   "        %sfold_pos(&lx->line, &lx->col, &lx->after_cr, lex, "
   3916                   "r.len);",
   3917                   prefix);
   3918       } else {
   3919         emit_line(out, "        lx->line += (uint32_t)r.nlines;");
   3920         emit_line(out,
   3921                   "        lx->col = r.last_nl ? (uint32_t)(1u + (r.len - "
   3922                   "r.last_nl)) : lx->col + (uint32_t)r.len;");
   3923       }
   3924     }
   3925     emit_line(out, "        lx->cur += r.len;");
   3926     emit_line(out, "        if (r.skip) continue;");
   3927     emit_line(out,
   3928               "        if (out) *out = (KitGramToken){ .kind = r.kind, .lexeme = "
   3929               "(const char *)lex, .len = r.len,");
   3930     if (lazy)
   3931       emit_line(out,
   3932                 "                                    .line = 0, .col = 0 };");
   3933     else
   3934       emit_line(
   3935           out,
   3936           "                                    .line = tline, .col = tcol };");
   3937     emit_line(out, "        return KIT_GRAM_LEX_TOKEN;");
   3938     emit_line(out, "    }");
   3939     emit_line(out, "}");
   3940 
   3941     xfree(ctx, scan);
   3942     xfree(ctx, base);
   3943     xfree(ctx, brk_at);
   3944   } /* for each %lex mode */
   3945 
   3946   /* One match API per %lex mode: each scans with its own generated scanner,
   3947    * start anchors, and line-break test. */
   3948   for (size_t i = 0; i < b->nlex_dfas; i++) {
   3949     LexDFA* dfa = &b->lex_dfas[i];
   3950     int has_start = dfa->start_text || dfa->start_line;
   3951     char* scan = lex_scan_symbol(ctx, prefix, dfa->name);
   3952     char* mb = std_match_base(ctx, prefix, dfa->name);
   3953     char* brk_before =
   3954         (dfa->input == KIT_GRAM_LEX_INPUT_UTF8)
   3955             ? xasprintf(ctx, "%sbreak_before(buf, len, p)", prefix)
   3956             : xstrdup(ctx, "buf[p - 1] == 10");
   3957     emit_line(out, "");
   3958     emit_line(
   3959         out, "static int %s_from(const unsigned char *buf, size_t len, int ml,",
   3960         mb);
   3961     emit_line(out, "                        size_t from, KitGramMatch *out) {");
   3962     emit_line(out, "    for (size_t p = from; p < len; p++) {");
   3963     emit_line(out, "        uint16_t start = 0;");
   3964     if (has_start) {
   3965       emit_line(out, "        if (p == 0) start = %u;",
   3966                 (unsigned)dfa->start_text);
   3967       if (dfa->start_line)
   3968         emit_line(out, "        else if (ml && %s) start = %u;", brk_before,
   3969                   (unsigned)dfa->start_line);
   3970     }
   3971     emit_line(out, "        %ssres r = %s(start, buf + p, buf + len, ml);",
   3972               prefix, scan);
   3973     emit_line(out,
   3974               "        if (r.found) { out->start = p; out->end = p + r.len; "
   3975               "out->kind = r.kind; return 1; }");
   3976     emit_line(out, "    }");
   3977     emit_line(out, "    return 0;");
   3978     emit_line(out, "}");
   3979     emit_line(out, "");
   3980     emit_line(out, "bool %s_anchored(const unsigned char *buf, size_t len,",
   3981               mb);
   3982     emit_line(out,
   3983               "                      const KitGramMatchOpts *opts, KitGramMatch "
   3984               "*out) {");
   3985     emit_line(out, "    int ml = opts && opts->multiline;");
   3986     emit_line(out, "    %ssres r = %s(%u, buf, buf + len, ml);", prefix, scan,
   3987               (unsigned)dfa->start_text);
   3988     emit_line(out, "    if (!r.found) return false;");
   3989     emit_line(out, "    out->start = 0; out->end = r.len; out->kind = r.kind;");
   3990     emit_line(out, "    return true;");
   3991     emit_line(out, "}");
   3992     emit_line(out, "bool %s_full(const unsigned char *buf, size_t len,", mb);
   3993     emit_line(
   3994         out,
   3995         "                  const KitGramMatchOpts *opts, KitGramMatch *out) {");
   3996     emit_line(out, "    KitGramMatch m;");
   3997     emit_line(out, "    if (!%s_anchored(buf, len, opts, &m)) return false;",
   3998               mb);
   3999     emit_line(out, "    if (m.end != len) return false;");
   4000     emit_line(out, "    *out = m;");
   4001     emit_line(out, "    return true;");
   4002     emit_line(out, "}");
   4003     emit_line(out, "bool %s_find(const unsigned char *buf, size_t len,", mb);
   4004     emit_line(
   4005         out,
   4006         "                  const KitGramMatchOpts *opts, KitGramMatch *out) {");
   4007     emit_line(out, "    int ml = opts && opts->multiline;");
   4008     emit_line(out, "    return %s_from(buf, len, ml, 0, out) != 0;", mb);
   4009     emit_line(out, "}");
   4010     emit_line(out, "void %s_iter_init(%s_iter *it, const unsigned char *buf,",
   4011               mb, mb);
   4012     emit_line(
   4013         out,
   4014         "                       size_t len, const KitGramMatchOpts *opts) {");
   4015     emit_line(out,
   4016               "    it->buf = buf; it->len = len; it->from = 0; it->multiline = "
   4017               "opts && opts->multiline;");
   4018     emit_line(out, "}");
   4019     emit_line(out, "bool %s_iter_next(%s_iter *it, KitGramMatch *out) {", mb, mb);
   4020     emit_line(out,
   4021               "    if (!%s_from(it->buf, it->len, it->multiline, it->from, "
   4022               "out)) return false;",
   4023               mb);
   4024     emit_line(out,
   4025               "    it->from = out->end > it->from ? out->end : it->from + 1;");
   4026     emit_line(out, "    return true;");
   4027     emit_line(out, "}");
   4028 
   4029     xfree(ctx, scan);
   4030     xfree(ctx, mb);
   4031     xfree(ctx, brk_before);
   4032   }
   4033   xfree(ctx, up);
   4034 }
   4035 
   4036 /* ---- experimental pull recursive-descent parser emission (--parser-codegen)
   4037  * ---- One static C function per rule: the prediction `switch (lookahead)`
   4038  * replaces the table search, rule references become calls (the hardware call
   4039  * stack replaces the control stack), `*`/`?` become a loop/if guarded by the
   4040  * body's FIRST set, and a %pratt rule becomes a precedence-climbing pair. The
   4041  * same KitGramActions value channel (lift_token, reduce, the list and opt
   4042  * builders) fires, so the value semantics match the table parser. Errors unwind
   4043  * with setjmp/longjmp (fail-fast). Buffered token array (end of buffer == EOF);
   4044  * enter/exit/on_token listeners and streaming suspension are intentionally out
   4045  * of scope for the prototype. */
   4046 
   4047 /* A disjunction `la == A || la == B ...` with no outer parens (callers wrap as
   4048  * needed) so a single-token guard stays `la == A` rather than `(la == A)`,
   4049  * which -Wparentheses-equality flags when it lands directly inside an `if`. */
   4050 static char* rd_guard_str(GramgenContext* ctx, Builder* b, const char* up,
   4051                           const IntSet* set) {
   4052   int* s = intset_sorted(ctx, set);
   4053   Buf t;
   4054   buf_init(ctx, &t);
   4055   if (set->n == 0) buf_append(&t, "0");
   4056   for (size_t i = 0; i < set->n; i++) {
   4057     if (i) buf_append(&t, " || ");
   4058     char* tk = token_ref(ctx, b, up, s[i]);
   4059     buf_append(&t, "la == ");
   4060     buf_append(&t, tk);
   4061     xfree(ctx, tk);
   4062   }
   4063   xfree(ctx, s);
   4064   return buf_take(&t);
   4065 }
   4066 
   4067 static void rd_emit_cases(Buf* out, Builder* b, const char* up,
   4068                           const IntSet* set) {
   4069   int* s = intset_sorted(out->ctx, set);
   4070   for (size_t i = 0; i < set->n; i++) {
   4071     char* tk = token_ref(out->ctx, b, up, s[i]);
   4072     emit_line(out, "    case %s:", tk);
   4073     xfree(out->ctx, tk);
   4074   }
   4075   xfree(out->ctx, s);
   4076 }
   4077 
   4078 /* Parse one TERM or RULE symbol, assigning its value to lvalue `dst`. `R` is
   4079  * the enclosing rule's id, threaded into rd_match for the in_rule of an error.
   4080  */
   4081 static void rd_emit_one(Buf* out, Builder* b, const char* up, const Sym* sym,
   4082                         const char* dst, const char* ind, const char* R) {
   4083   if (sym->kind == SYM_TERM) {
   4084     char* tk = token_ref(out->ctx, b, up, sym->value);
   4085     emit_line(out, "%s%s = rd_match(p, %s, %s);", ind, dst, tk, R);
   4086     xfree(out->ctx, tk);
   4087   } else { /* SYM_RULE */
   4088     emit_line(out, "%s%s = rd_r%d(p);", ind, dst, sym->value);
   4089   }
   4090 }
   4091 
   4092 /* Parse one top-level factor of a production into k[idx]. */
   4093 static void rd_emit_factor(Buf* out, Builder* b, const char* up, const Sym* sym,
   4094                            size_t idx, const char* R) {
   4095   GramgenContext* ctx = out->ctx;
   4096   char* dst = xasprintf(ctx, "k[%zu]", idx);
   4097   if (sym->kind == SYM_TERM || sym->kind == SYM_RULE) {
   4098     rd_emit_one(out, b, up, sym, dst, "        ", R);
   4099     xfree(ctx, dst);
   4100     return;
   4101   }
   4102   const IntSet* guard = &b->sets[sym->set_index];
   4103   char* g = rd_guard_str(ctx, b, up, guard);
   4104   if (sym->kind == SYM_REP) {
   4105     emit_line(out, "        {");
   4106     emit_line(out,
   4107               "            KitGramSem _l = p->act->list_empty ? "
   4108               "p->act->list_empty(p->ud) : (KitGramSem)0;");
   4109     emit_line(out, "            for (;;) {");
   4110     emit_line(out, "                KitGramTokenKind la = rd_look(p);");
   4111     emit_line(out, "                if (!(%s)) break;", g);
   4112     emit_line(out, "                KitGramSem _it;");
   4113     rd_emit_one(out, b, up, sym->sub, "_it", "                ", R);
   4114     emit_line(out,
   4115               "                _l = p->act->list_push ? "
   4116               "p->act->list_push(p->ud, _l, _it) : _it;");
   4117     emit_line(out, "            }");
   4118     emit_line(out, "            %s = _l;", dst);
   4119     emit_line(out, "        }");
   4120   } else { /* SYM_OPT */
   4121     emit_line(out, "        {");
   4122     emit_line(out, "            KitGramTokenKind la = rd_look(p);");
   4123     emit_line(out, "            if (%s) {", g);
   4124     emit_line(out, "                KitGramSem _it;");
   4125     rd_emit_one(out, b, up, sym->sub, "_it", "                ", R);
   4126     emit_line(out,
   4127               "                %s = p->act->opt_some ? p->act->opt_some(p->ud, "
   4128               "_it) : _it;",
   4129               dst);
   4130     emit_line(out, "            } else {");
   4131     emit_line(out,
   4132               "                %s = p->act->opt_none ? p->act->opt_none(p->ud) "
   4133               ": (KitGramSem)0;",
   4134               dst);
   4135     emit_line(out, "            }");
   4136     emit_line(out, "        }");
   4137   }
   4138   xfree(ctx, g);
   4139   xfree(ctx, dst);
   4140 }
   4141 
   4142 static void rd_emit_pratt(Buf* out, Builder* b, int ri, const char* prefix,
   4143                           int recover) {
   4144   GramgenContext* ctx = out->ctx;
   4145   Rule* rule = &b->rules[ri];
   4146   PrattInfo* pr = rule->pratt;
   4147   char* up = make_upper_prefix(ctx, prefix);
   4148   char* R = rule_ref(ctx, b, up, ri);
   4149   /* The episode entry checks the lookahead against the rule's FIRST set the way
   4150    * the interpreter does at the LL(1)->Pratt boundary, so an expression that
   4151    * cannot start is attributed to this rule (not the primary delegate). */
   4152   int has_exp = rule->first.n > 0;
   4153   if (has_exp) {
   4154     char* toks = tok_list(ctx, b, up, &rule->first);
   4155     emit_line(out, "static const KitGramTokenKind rd_exp_%d[] = { %s };", ri,
   4156               toks);
   4157     xfree(ctx, toks);
   4158   }
   4159   emit_line(out, "static KitGramSem rd_r%d_climb(RD *p, int min_bp);", ri);
   4160   emit_line(out, "static KitGramSem rd_r%d(RD *p) {", ri);
   4161   if (has_exp) {
   4162     char* g = rd_guard_str(ctx, b, up, &rule->first);
   4163     if (recover) {
   4164       emit_line(out, "  retry:"); /* re-predict after recovery */
   4165       emit_line(out, "    {");
   4166       emit_line(out, "        KitGramTokenKind la = rd_look(p);");
   4167       emit_line(
   4168           out,
   4169           "        if (!(%s)) { if (rd_recover_predict(p, rd_exp_%d, %zu, %s, "
   4170           "&%sgrammar.rules[%d])) goto retry; return (KitGramSem)0; }",
   4171           g, ri, rule->first.n, R, prefix, ri);
   4172       emit_line(out, "    }");
   4173     } else {
   4174       emit_line(out, "    KitGramTokenKind la = rd_look(p);");
   4175       emit_line(out, "    if (!(%s)) rd_fail(p, rd_exp_%d, %zu, %s);", g, ri,
   4176                 rule->first.n, R);
   4177     }
   4178     xfree(ctx, g);
   4179   }
   4180   /* enter/exit bracket the whole Pratt episode once (with primary_prod),
   4181    * matching the interpreter; the per-operator reduces in the climb are
   4182    * value-channel only. */
   4183   emit_line(out, "    if (p->act->enter) p->act->enter(p->ud, %s, %d);", R,
   4184             pr->primary_prod);
   4185   emit_line(out, "    KitGramSem r = rd_r%d_climb(p, 0);", ri);
   4186   emit_line(out, "    if (p->act->exit) p->act->exit(p->ud, %s, %d);", R,
   4187             pr->primary_prod);
   4188   emit_line(out, "    return r;");
   4189   emit_line(out, "}");
   4190   emit_line(out, "static KitGramSem rd_r%d_climb(RD *p, int min_bp) {", ri);
   4191   emit_line(out, "    KitGramSem lhs;");
   4192   emit_line(out,
   4193             "    switch (rd_look(p)) {                       /* nud: prefix op "
   4194             "or primary */");
   4195   for (size_t oi = 0; oi < pr->nops; oi++) {
   4196     PrattOp* op = &pr->ops[oi];
   4197     if (op->role != b->role.prefix) continue;
   4198     char* tk = token_ref(ctx, b, up, op->tok);
   4199     emit_line(out, "    case %s: {", tk);
   4200     emit_line(out, "        KitGramSem op = rd_match(p, %s, %s);", tk, R);
   4201     emit_line(out, "        KitGramSem rhs = rd_r%d_climb(p, %d);", ri, op->rbp);
   4202     emit_line(out, "        KitGramSem k[2] = { op, rhs };");
   4203     emit_line(out, "        lhs = rd_reduce(p, %s, %d, k, 2);", R, op->prod);
   4204     emit_line(out, "        break;");
   4205     emit_line(out, "    }");
   4206     xfree(ctx, tk);
   4207   }
   4208   emit_line(out, "    default: {");
   4209   emit_line(out, "        KitGramSem pv = rd_r%d(p);", pr->primary_rule);
   4210   emit_line(out, "        KitGramSem k[1] = { pv };");
   4211   emit_line(out, "        lhs = rd_reduce(p, %s, %d, k, 1);", R,
   4212             pr->primary_prod);
   4213   emit_line(out, "        break;");
   4214   emit_line(out, "    }");
   4215   emit_line(out, "    }");
   4216   emit_line(out,
   4217             "    for (;;) {                                  /* led: operator "
   4218             "loop */");
   4219   emit_line(out, "        switch (rd_look(p)) {");
   4220   for (size_t oi = 0; oi < pr->nops; oi++) {
   4221     PrattOp* op = &pr->ops[oi];
   4222     if (op->role == b->role.prefix) continue; /* nud only */
   4223     char* tk =
   4224         token_ref(ctx, b, up,
   4225                   op->tok); /* infix/postfix op, ternary op1, circumfix open */
   4226     emit_line(out, "        case %s: {", tk);
   4227     emit_line(out, "            if (%d <= min_bp) goto done;", op->lbp);
   4228     if (op->role == b->role.postfix) {
   4229       emit_line(out, "            KitGramSem op = rd_match(p, %s, %s);", tk, R);
   4230       emit_line(out, "            KitGramSem k[2] = { lhs, op };");
   4231       emit_line(out, "            lhs = rd_reduce(p, %s, %d, k, 2);", R,
   4232                 op->prod);
   4233     } else if (op->role == b->role.ternary) {
   4234       char* t2 = token_ref(ctx, b, up, op->tok2);
   4235       emit_line(out, "            KitGramSem op1 = rd_match(p, %s, %s);", tk, R);
   4236       emit_line(out, "            KitGramSem mid = rd_r%d_climb(p, 0);", ri);
   4237       emit_line(out, "            KitGramSem op2 = rd_match(p, %s, %s);", t2, R);
   4238       emit_line(out, "            KitGramSem els = rd_r%d_climb(p, %d);", ri,
   4239                 op->rbp);
   4240       emit_line(out,
   4241                 "            KitGramSem k[5] = { lhs, op1, mid, op2, els };");
   4242       emit_line(out, "            lhs = rd_reduce(p, %s, %d, k, 5);", R,
   4243                 op->prod);
   4244       xfree(ctx, t2);
   4245     } else if (op->role == b->role.circumfix) {
   4246       char* t2 = token_ref(ctx, b, up, op->tok2);
   4247       emit_line(out, "            KitGramSem open = rd_match(p, %s, %s);", tk, R);
   4248       emit_line(out, "            KitGramSem inner = rd_r%d(p);", op->inner_rule);
   4249       emit_line(out, "            KitGramSem close = rd_match(p, %s, %s);", t2,
   4250                 R);
   4251       emit_line(out,
   4252                 "            KitGramSem k[4] = { lhs, open, inner, close };");
   4253       emit_line(out, "            lhs = rd_reduce(p, %s, %d, k, 4);", R,
   4254                 op->prod);
   4255       xfree(ctx, t2);
   4256     } else { /* infixl / infixr / infix */
   4257       emit_line(out, "            KitGramSem op = rd_match(p, %s, %s);", tk, R);
   4258       emit_line(out, "            KitGramSem rhs = rd_r%d_climb(p, %d);", ri,
   4259                 op->rbp);
   4260       emit_line(out, "            KitGramSem k[3] = { lhs, op, rhs };");
   4261       emit_line(out, "            lhs = rd_reduce(p, %s, %d, k, 3);", R,
   4262                 op->prod);
   4263     }
   4264     emit_line(out, "            break;");
   4265     emit_line(out, "        }");
   4266     xfree(ctx, tk);
   4267   }
   4268   emit_line(out, "        default: goto done;");
   4269   emit_line(out, "        }");
   4270   emit_line(out, "    }");
   4271   emit_line(out, "done:");
   4272   emit_line(out, "    return lhs;");
   4273   emit_line(out, "}");
   4274   xfree(ctx, up);
   4275   xfree(ctx, R);
   4276 }
   4277 
   4278 static void rd_emit_rule(Buf* out, Builder* b, int ri, const char* prefix,
   4279                          int recover) {
   4280   GramgenContext* ctx = out->ctx;
   4281   Rule* rule = &b->rules[ri];
   4282   if (rule->pratt) {
   4283     rd_emit_pratt(out, b, ri, prefix, recover);
   4284     return;
   4285   }
   4286   char* up = make_upper_prefix(ctx, prefix);
   4287   int hidden = !rule->public_rule;
   4288   char* R = rule_ref(ctx, b, up, ri);
   4289   /* On a prediction miss the expected set is the rule's FIRST set — the same
   4290    * tokens the interpreter reports in KitGramError.expected. Emit it as static
   4291    * data the error path points at (so it outlives the parse for the out-param).
   4292    */
   4293   int has_exp = rule->first.n > 0;
   4294   if (has_exp) {
   4295     char* toks = tok_list(ctx, b, up, &rule->first);
   4296     emit_line(out, "static const KitGramTokenKind rd_exp_%d[] = { %s };", ri,
   4297               toks);
   4298     xfree(ctx, toks);
   4299   }
   4300   emit_line(out, "static KitGramSem rd_r%d(RD *p) {", ri);
   4301   if (recover) emit_line(out, "  retry:"); /* re-predict after recovery */
   4302   emit_line(out, "    switch (rd_look(p)) {");
   4303   for (size_t pi = 0; pi < rule->nprods; pi++) {
   4304     Prod* prod = &rule->prods[pi];
   4305     rd_emit_cases(out, b, up, &prod->first);
   4306     /* A nullable alternative (e.g. %empty) is also predicted by the rule's
   4307      * FOLLOW set; FIRST and FOLLOW are disjoint here (LL(1)), so no dup case.
   4308      */
   4309     if (prod->nullable) rd_emit_cases(out, b, up, &rule->follow);
   4310     emit_line(out, "    {");
   4311     if (hidden) {
   4312       /* hidden synthetic rule: pass the single kid through, no
   4313        * enter/exit/reduce */
   4314       emit_line(out, "        KitGramSem v;");
   4315       rd_emit_one(out, b, up, &prod->syms[0], "v", "        ", R);
   4316       emit_line(out, "        return v;");
   4317     } else if (prod->nsyms == 0) {
   4318       emit_line(out,
   4319                 "        if (p->act->enter) p->act->enter(p->ud, %s, %zu);", R,
   4320                 pi);
   4321       emit_line(
   4322           out, "        KitGramSem _r = rd_reduce(p, %s, %zu, (KitGramSem *)0, 0);",
   4323           R, pi);
   4324       emit_line(out, "        if (p->act->exit) p->act->exit(p->ud, %s, %zu);",
   4325                 R, pi);
   4326       emit_line(out, "        return _r;");
   4327     } else {
   4328       emit_line(out,
   4329                 "        if (p->act->enter) p->act->enter(p->ud, %s, %zu);", R,
   4330                 pi);
   4331       emit_line(out, "        KitGramSem k[%zu];", prod->nsyms);
   4332       for (size_t si = 0; si < prod->nsyms; si++)
   4333         rd_emit_factor(out, b, up, &prod->syms[si], si, R);
   4334       emit_line(out, "        KitGramSem _r = rd_reduce(p, %s, %zu, k, %zu);", R,
   4335                 pi, prod->nsyms);
   4336       emit_line(out, "        if (p->act->exit) p->act->exit(p->ud, %s, %zu);",
   4337                 R, pi);
   4338       emit_line(out, "        return _r;");
   4339     }
   4340     emit_line(out, "    }");
   4341   }
   4342   /* Prediction miss. Fail-fast: rd_fail (noreturn) — the hot switch keeps its
   4343    * single-return shape. Recover: skip to a sync point (cold) and abandon the
   4344    * rule with a NULL value; an enclosing `*`/`?` loop re-checks FIRST and
   4345    * retries. */
   4346   const char* exp = has_exp ? NULL : "(const KitGramTokenKind *)0";
   4347   if (recover) {
   4348     if (has_exp)
   4349       emit_line(out,
   4350                 "    default: if (rd_recover_predict(p, rd_exp_%d, %zu, %s, "
   4351                 "&%sgrammar.rules[%d])) goto retry; return (KitGramSem)0;",
   4352                 ri, rule->first.n, R, prefix, ri);
   4353     else
   4354       emit_line(out,
   4355                 "    default: if (rd_recover_predict(p, %s, 0, %s, "
   4356                 "&%sgrammar.rules[%d])) goto retry; return (KitGramSem)0;",
   4357                 exp, R, prefix, ri);
   4358   } else {
   4359     if (has_exp)
   4360       emit_line(out, "    default: rd_fail(p, rd_exp_%d, %zu, %s);", ri,
   4361                 rule->first.n, R);
   4362     else
   4363       emit_line(out, "    default: rd_fail(p, %s, 0, %s);", exp, R);
   4364   }
   4365   emit_line(out, "    }");
   4366   emit_line(out, "    return (KitGramSem)0;");
   4367   emit_line(out, "}");
   4368   xfree(ctx, up);
   4369   xfree(ctx, R);
   4370 }
   4371 
   4372 /* ---- token machine (%machine) emission ------------------------------------
   4373  * A %machine compiles, codegen-only, to a steppable DFA over an abstract symbol
   4374  * alphabet plus an AST-directed sampler. Per machine we emit the
   4375  * <prefix><name>_sym / _kind enums, the static DFA tables (class_of/trans/
   4376  * accept/live), the sym_name[] array, the fsm_* surface + whole-trace accepts,
   4377  * and the gen_* sampler. It links nothing from libgram (cf.
   4378  * emit_lexer_standalone). The per-machine identifier base is "<prefix><name>_"
   4379  * (lowercase) with an uppercased twin for enum values. See doc/DESIGN.md "Token
   4380  * machines". */
   4381 
   4382 static char* machine_base(GramgenContext* ctx, const char* prefix,
   4383                           const char* name) {
   4384   return xasprintf(ctx, "%s%s_", prefix, name);
   4385 }
   4386 static char* machine_base_upper(GramgenContext* ctx, const char* base) {
   4387   char* u = xstrdup(ctx, base);
   4388   for (char* p = u; *p; p++) *p = (char)toupper((unsigned char)*p);
   4389   return u;
   4390 }
   4391 
   4392 /* Emit a uint16 table body, 12 per line; UINT16_MAX -> "0xffff". */
   4393 static void emit_machine_u16(Buf* out, const uint16_t* arr, size_t n) {
   4394   for (size_t i = 0; i < n; i += 12) {
   4395     size_t end = i + 12 < n ? i + 12 : n;
   4396     buf_append(out, "    ");
   4397     for (size_t j = i; j < end; j++) {
   4398       if (j > i) buf_append(out, ", ");
   4399       if (arr[j] == UINT16_MAX)
   4400         buf_append(out, "0xffff");
   4401       else
   4402         buf_append_uint(out, arr[j]);
   4403     }
   4404     buf_append(out, ",\n");
   4405   }
   4406 }
   4407 
   4408 static void emit_machine_decls(Builder* b, Buf* out, const char* prefix) {
   4409   GramgenContext* ctx = out->ctx;
   4410   for (size_t mi = 0; mi < b->nmachines; mi++) {
   4411     LexDFA* m = &b->machines[mi];
   4412     char* lb = machine_base(ctx, prefix, m->name);
   4413     char* ub = machine_base_upper(ctx, lb);
   4414     emit_line(
   4415         out,
   4416         "/* token machine \"%s\": regular language over a symbol alphabet. */",
   4417         m->name);
   4418     emit_line(out, "typedef enum {");
   4419     for (uint16_t i = 0; i < m->nsym; i++)
   4420       emit_line(out, "    %s%s,", ub, m->sym_names[i]);
   4421     emit_line(out, "    %sSYM__COUNT", ub);
   4422     emit_line(out, "} %ssym;", lb);
   4423     emit_line(out, "typedef enum {");
   4424     for (size_t i = 0; i < m->nrecognizers; i++)
   4425       emit_line(out, "    %s%s,", ub, m->recognizers[i].name);
   4426     emit_line(out, "    %sKIND__COUNT", ub);
   4427     emit_line(out, "} %skind;", lb);
   4428     emit_line(out, "extern const char *const %ssym_name[%sSYM__COUNT];", lb,
   4429               ub);
   4430     emit_line(out, "typedef struct { uint16_t state; } %sfsm;", lb);
   4431     emit_line(out, "void          %sfsm_init(%sfsm *m);", lb, lb);
   4432     emit_line(out, "KitGramFsmStatus %sfsm_step(%sfsm *m, %ssym s);", lb, lb,
   4433               lb);
   4434     emit_line(out,
   4435               "bool          %sfsm_accepting(const %sfsm *m, %skind *which);",
   4436               lb, lb, lb);
   4437     emit_line(out, "bool          %sfsm_live(const %sfsm *m);", lb, lb);
   4438     emit_line(
   4439         out,
   4440         "size_t        %sfsm_enabled(const %sfsm *m, %ssym *out, size_t cap);",
   4441         lb, lb, lb);
   4442     emit_line(out,
   4443               "bool          %sfsm_accepts(const %ssym *trace, size_t n, "
   4444               "%skind *which);",
   4445               lb, lb, lb);
   4446     /* AST-directed sampler: deterministic by seed; stop_prob governs each opt
   4447      * and rep loop, max_repeat caps a loop, max_tokens caps the trace (-> LIMIT
   4448      * on overflow). `which` selects the rule to sample (the first is the spec).
   4449      */
   4450     emit_line(out,
   4451               "typedef struct { uint64_t seed; double stop_prob; size_t "
   4452               "max_repeat; size_t max_tokens; } %sgen_config;",
   4453               lb);
   4454     emit_line(out,
   4455               "KitGramGenStatus %ssample(%skind which, %sgen_config *cfg, %ssym "
   4456               "*out, size_t cap, size_t *n);",
   4457               lb, lb, lb, lb);
   4458     emit_line(out, "");
   4459     xfree(ctx, lb);
   4460     xfree(ctx, ub);
   4461   }
   4462 }
   4463 
   4464 /* ---- AST-directed sampler emission ---------------------------------------
   4465  * The sampler walks the *desugared* recognizer AST (%def inlined, +/{n,m}
   4466  * lowered, so only seq/alt/group/opt/rep + symbol/class/. leaves remain) and
   4467  * emits structure-as-code: a symbol leaf emits that symbol, an alt picks a
   4468  * branch, an opt/rep flips a stop coin (continue with prob 1-stop_prob, capped
   4469  * by max_repeat). A trace is valid by construction. */
   4470 
   4471 static void emit_gen_indent(Buf* out, int ind) {
   4472   for (int i = 0; i < ind; i++) buf_append(out, " ");
   4473 }
   4474 
   4475 static void emit_gen_alt(Buf* out, const char* lb, const char* ub,
   4476                          const LexDFA* m, const AstLexAlt* alt, int ind,
   4477                          int* ctr);
   4478 
   4479 static void emit_gen_node(Buf* out, const char* lb, const char* ub,
   4480                           const LexDFA* m, const AstLexNode* node, int ind,
   4481                           int* ctr) {
   4482   GramgenContext* ctx = out->ctx;
   4483   switch (node->kind) {
   4484     case LEX_NAME:
   4485       emit_gen_indent(out, ind);
   4486       emit_line(out, "%sgen_emit(c, %s%s);", lb, ub, node->value.s);
   4487       break;
   4488     case LEX_ANY:
   4489       emit_gen_indent(out, ind);
   4490       emit_line(out,
   4491                 "%sgen_emit(c, (%ssym)(%srng_next(&c->rng) %% %sSYM__COUNT));",
   4492                 lb, lb, lb, ub);
   4493       break;
   4494     case LEX_CLASS: {
   4495       size_t nm = 0;
   4496       int comp = 0;
   4497       char* const* names =
   4498           kit_gram_machine_class_names(ctx, node->value, node->loc, &nm, &comp);
   4499       int id = (*ctr)++;
   4500       /* Resolve to a concrete member list (the complement is the alphabet minus
   4501        * the listed names); pick one uniformly. */
   4502       emit_gen_indent(out, ind);
   4503       emit_line(out, "{");
   4504       emit_gen_indent(out, ind + 4);
   4505       buf_append(out, "static const ");
   4506       buf_append(out, lb);
   4507       buf_append(out, "sym _cls");
   4508       buf_append_uint(out, (unsigned)id);
   4509       buf_append(out, "[] = { ");
   4510       size_t k = 0;
   4511       if (!comp) {
   4512         for (size_t j = 0; j < nm; j++) {
   4513           if (k) buf_append(out, ", ");
   4514           buf_append(out, ub);
   4515           buf_append(out, names[j]);
   4516           k++;
   4517         }
   4518       } else {
   4519         for (uint16_t s = 0; s < m->nsym; s++) {
   4520           int in = 0;
   4521           for (size_t j = 0; j < nm; j++)
   4522             if (strcmp(m->sym_names[s], names[j]) == 0) {
   4523               in = 1;
   4524               break;
   4525             }
   4526           if (in) continue;
   4527           if (k) buf_append(out, ", ");
   4528           buf_append(out, ub);
   4529           buf_append(out, m->sym_names[s]);
   4530           k++;
   4531         }
   4532       }
   4533       buf_append(out, " };\n");
   4534       emit_gen_indent(out, ind + 4);
   4535       emit_line(out, "%sgen_emit(c, _cls%d[%srng_next(&c->rng) %% %zuu]);", lb,
   4536                 id, lb, k);
   4537       emit_gen_indent(out, ind);
   4538       emit_line(out, "}");
   4539       xfree(ctx, (void*)names);
   4540       break;
   4541     }
   4542     case LEX_GROUP:
   4543       emit_gen_alt(out, lb, ub, m, node->alts, ind, ctr);
   4544       break;
   4545     case LEX_OPT:
   4546       emit_gen_indent(out, ind);
   4547       emit_line(out, "if (%sgen_coin(c)) {", lb);
   4548       emit_gen_alt(out, lb, ub, m, node->alts, ind + 4, ctr);
   4549       emit_gen_indent(out, ind);
   4550       emit_line(out, "}");
   4551       break;
   4552     case LEX_REP: {
   4553       int id = (*ctr)++;
   4554       emit_gen_indent(out, ind);
   4555       emit_line(out,
   4556                 "for (size_t _r%d = 0; _r%d < c->max_repeat && %sgen_coin(c); "
   4557                 "_r%d++) {",
   4558                 id, id, lb, id);
   4559       emit_gen_alt(out, lb, ub, m, node->alts, ind + 4, ctr);
   4560       emit_gen_indent(out, ind);
   4561       emit_line(out, "}");
   4562       break;
   4563     }
   4564     default: /* LITERAL/PROP/ANCHOR/REPEAT cannot occur post-desugar in token
   4565                 mode */
   4566       break;
   4567   }
   4568 }
   4569 
   4570 static void emit_gen_seq(Buf* out, const char* lb, const char* ub,
   4571                          const LexDFA* m, const AstLexSeq* seq, int ind,
   4572                          int* ctr) {
   4573   for (size_t i = 0; i < seq->nitems; i++)
   4574     emit_gen_node(out, lb, ub, m, seq->items[i], ind, ctr);
   4575 }
   4576 
   4577 static void emit_gen_alt(Buf* out, const char* lb, const char* ub,
   4578                          const LexDFA* m, const AstLexAlt* alt, int ind,
   4579                          int* ctr) {
   4580   if (alt->nseqs == 1) {
   4581     emit_gen_seq(out, lb, ub, m, alt->seqs[0], ind, ctr);
   4582     return;
   4583   }
   4584   emit_gen_indent(out, ind);
   4585   emit_line(out, "switch (%srng_next(&c->rng) %% %zuu) {", lb, alt->nseqs);
   4586   for (size_t i = 0; i < alt->nseqs; i++) {
   4587     emit_gen_indent(out, ind);
   4588     emit_line(out, "case %zu: {", i);
   4589     emit_gen_seq(out, lb, ub, m, alt->seqs[i], ind + 4, ctr);
   4590     emit_gen_indent(out, ind + 4);
   4591     emit_line(out, "break;");
   4592     emit_gen_indent(out, ind);
   4593     emit_line(out, "}");
   4594   }
   4595   emit_gen_indent(out, ind);
   4596   emit_line(out, "default: break;");
   4597   emit_gen_indent(out, ind);
   4598   emit_line(out, "}");
   4599 }
   4600 
   4601 static void emit_machine_sampler(Buf* out, const char* lb, const char* ub,
   4602                                  const LexDFA* m) {
   4603   /* Deterministic PRNG + the emit/coin helpers (static inline so an
   4604    * alt/opt/rep-free machine does not trip -Wunused-function). */
   4605   emit_line(out, "typedef struct {");
   4606   emit_line(out, "    %ssym *out; size_t cap, n, max_tokens, max_repeat;", lb);
   4607   emit_line(out, "    uint64_t rng; double stop_prob; int limit;");
   4608   emit_line(out, "} %sgen_ctx;", lb);
   4609   emit_line(out, "static inline uint64_t %srng_next(uint64_t *s) {", lb);
   4610   emit_line(out,
   4611             "    *s = *s * 6364136223846793005ull + 1442695040888963407ull;");
   4612   emit_line(out,
   4613             "    uint64_t x = *s; x ^= x >> 33; x *= 0xff51afd7ed558ccdull; x "
   4614             "^= x >> 33; return x;");
   4615   emit_line(out, "}");
   4616   emit_line(out, "static inline void %sgen_emit(%sgen_ctx *c, %ssym s) {", lb,
   4617             lb, lb);
   4618   emit_line(out, "    if (c->n >= c->max_tokens) { c->limit = 1; return; }");
   4619   emit_line(out, "    if (c->n < c->cap) c->out[c->n] = s;");
   4620   emit_line(out, "    c->n++;");
   4621   emit_line(out, "}");
   4622   emit_line(out,
   4623             "static inline int %sgen_coin(%sgen_ctx *c) {  /* continue with "
   4624             "prob 1 - stop_prob */",
   4625             lb, lb);
   4626   emit_line(out, "    uint64_t r = %srng_next(&c->rng);", lb);
   4627   emit_line(out,
   4628             "    double u = (double)(r >> 11) * (1.0 / 9007199254740992.0);");
   4629   emit_line(out, "    return u >= c->stop_prob;");
   4630   emit_line(out, "}");
   4631   for (size_t r = 0; r < m->nrecognizers; r++) {
   4632     int ctr = 0;
   4633     emit_line(out, "static void %sgen_%s(%sgen_ctx *c) {", lb,
   4634               m->recognizers[r].name, lb);
   4635     emit_gen_alt(out, lb, ub, m, m->recognizers[r].alts, 4, &ctr);
   4636     emit_line(out, "}");
   4637   }
   4638   emit_line(out,
   4639             "KitGramGenStatus %ssample(%skind which, %sgen_config *cfg, %ssym "
   4640             "*out, size_t cap, size_t *n) {",
   4641             lb, lb, lb, lb);
   4642   /* Reference the helpers so a machine with no alt/opt/rep leaf (which would
   4643    * not call coin/rng) does not trip -Wunused-function. */
   4644   emit_line(out, "    (void)%srng_next; (void)%sgen_emit; (void)%sgen_coin;",
   4645             lb, lb, lb);
   4646   emit_line(out,
   4647             "    %sgen_ctx c = { .out = out, .cap = cap, .n = 0, .max_tokens = "
   4648             "cfg->max_tokens,",
   4649             lb);
   4650   emit_line(out,
   4651             "                   .max_repeat = cfg->max_repeat, .rng = "
   4652             "cfg->seed, .stop_prob = cfg->stop_prob, .limit = 0 };");
   4653   emit_line(out, "    switch (which) {");
   4654   for (size_t r = 0; r < m->nrecognizers; r++)
   4655     emit_line(out, "    case %s%s: %sgen_%s(&c); break;", ub,
   4656               m->recognizers[r].name, lb, m->recognizers[r].name);
   4657   emit_line(out, "    default: break;");
   4658   emit_line(out, "    }");
   4659   emit_line(out, "    cfg->seed = c.rng;");
   4660   emit_line(out, "    if (n) *n = c.n;");
   4661   emit_line(out, "    return c.limit ? KIT_GRAM_GEN_LIMIT : KIT_GRAM_GEN_DONE;");
   4662   emit_line(out, "}");
   4663 }
   4664 
   4665 static void emit_machines(Builder* b, Buf* out, const char* prefix) {
   4666   GramgenContext* ctx = out->ctx;
   4667   for (size_t mi = 0; mi < b->nmachines; mi++) {
   4668     LexDFA* m = &b->machines[mi];
   4669     char* lb = machine_base(ctx, prefix, m->name);
   4670     char* ub = machine_base_upper(ctx, lb);
   4671     size_t nc = m->nclasses;
   4672     emit_line(out, "/* ---- token machine \"%s\" (%%machine) ---- */", m->name);
   4673 
   4674     emit_line(out, "const char *const %ssym_name[%sSYM__COUNT] = {", lb, ub);
   4675     for (uint16_t i = 0; i < m->nsym; i++) {
   4676       char* q = quote_len(ctx, m->sym_names[i], strlen(m->sym_names[i]), false);
   4677       emit_line(out, "    %s,", q);
   4678       xfree(ctx, q);
   4679     }
   4680     emit_line(out, "};");
   4681 
   4682     emit_line(out, "static const uint16_t %sclass_of[%sSYM__COUNT] = {", lb,
   4683               ub);
   4684     emit_machine_u16(out, m->sym_class_of, m->nsym);
   4685     emit_line(out, "};");
   4686 
   4687     emit_line(out, "static const uint16_t %strans[] = {", lb);
   4688     emit_machine_u16(out, m->trans, (size_t)m->nstates * nc);
   4689     emit_line(out, "};");
   4690 
   4691     emit_line(out, "static const uint16_t %saccept[] = {", lb);
   4692     emit_machine_u16(out, m->accept, m->nstates);
   4693     emit_line(out, "};");
   4694 
   4695     emit_line(out, "static const uint8_t %slive[] = {", lb);
   4696     for (size_t i = 0; i < m->nstates; i += 24) {
   4697       size_t end = i + 24 < m->nstates ? i + 24 : m->nstates;
   4698       buf_append(out, "    ");
   4699       for (size_t j = i; j < end; j++) {
   4700         if (j > i) buf_append(out, ", ");
   4701         buf_append_uint(out, m->live[j] ? 1u : 0u);
   4702       }
   4703       buf_append(out, ",\n");
   4704     }
   4705     emit_line(out, "};");
   4706     emit_line(out, "");
   4707 
   4708     emit_line(out, "void %sfsm_init(%sfsm *m) { m->state = 0; }", lb, lb);
   4709     emit_line(out, "KitGramFsmStatus %sfsm_step(%sfsm *m, %ssym s) {", lb, lb,
   4710               lb);
   4711     emit_line(
   4712         out,
   4713         "    uint16_t nx = %strans[(size_t)m->state * %zuu + %sclass_of[s]];",
   4714         lb, nc, lb);
   4715     emit_line(out, "    if (nx == 0xffffu) return KIT_GRAM_FSM_DEAD;");
   4716     emit_line(out, "    m->state = nx; return KIT_GRAM_FSM_OK;");
   4717     emit_line(out, "}");
   4718     emit_line(out, "bool %sfsm_accepting(const %sfsm *m, %skind *which) {", lb,
   4719               lb, lb);
   4720     emit_line(out, "    uint16_t a = %saccept[m->state];", lb);
   4721     emit_line(out, "    if (a == 0xffffu) return false;");
   4722     emit_line(out, "    if (which) *which = (%skind)a;", lb);
   4723     emit_line(out, "    return true;");
   4724     emit_line(out, "}");
   4725     emit_line(
   4726         out,
   4727         "bool %sfsm_live(const %sfsm *m) { return %slive[m->state] != 0; }", lb,
   4728         lb, lb);
   4729     emit_line(out,
   4730               "size_t %sfsm_enabled(const %sfsm *m, %ssym *out, size_t cap) {",
   4731               lb, lb, lb);
   4732     emit_line(out, "    size_t n = 0;");
   4733     emit_line(out, "    for (uint16_t s = 0; s < %sSYM__COUNT; s++)", ub);
   4734     emit_line(out,
   4735               "        if (%strans[(size_t)m->state * %zuu + %sclass_of[s]] != "
   4736               "0xffffu) {",
   4737               lb, nc, lb);
   4738     emit_line(out, "            if (n < cap) out[n] = (%ssym)s;", lb);
   4739     emit_line(out, "            n++;");
   4740     emit_line(out, "        }");
   4741     emit_line(out, "    return n;");
   4742     emit_line(out, "}");
   4743     emit_line(
   4744         out,
   4745         "bool %sfsm_accepts(const %ssym *trace, size_t n, %skind *which) {", lb,
   4746         lb, lb);
   4747     emit_line(out, "    %sfsm m; %sfsm_init(&m);", lb, lb);
   4748     emit_line(out, "    for (size_t i = 0; i < n; i++)");
   4749     emit_line(
   4750         out,
   4751         "        if (%sfsm_step(&m, trace[i]) == KIT_GRAM_FSM_DEAD) return false;",
   4752         lb);
   4753     emit_line(out, "    return %sfsm_accepting(&m, which);", lb);
   4754     emit_line(out, "}");
   4755     emit_line(out, "");
   4756     emit_machine_sampler(out, lb, ub, m);
   4757     emit_line(out, "");
   4758     xfree(ctx, lb);
   4759     xfree(ctx, ub);
   4760   }
   4761 }
   4762 
   4763 static void emit_parser_codegen(Builder* b, Buf* out, const char* prefix) {
   4764   if (!parser_codegen_ok(b)) return;
   4765   GramgenContext* ctx = out->ctx;
   4766   char* up = make_upper_prefix(ctx, prefix);
   4767   /* Fusion target: when the main lexer also has a generated scanner, the parser
   4768    * pulls its lookahead by running that scanner inline (no KitGramToken vector,
   4769    * no kit_gram_lexer_next/kit_gram_parser_push boundary). Both the buffered and fused
   4770    * entries share the rule functions; only the `refill` hook that loads the
   4771    * lookahead differs. */
   4772   LexDFA* mainlex = NULL;
   4773   for (size_t i = 0; i < b->nlex_dfas; i++)
   4774     if (lex_name_is_main_c(b->lex_dfas[i].name)) {
   4775       mainlex = &b->lex_dfas[i];
   4776       break;
   4777     }
   4778   /* Fusion drives the standalone table-free scanner inline — no KitGramToken
   4779    * vector, no kit_gram_lexer_next/kit_gram_parser_push boundary, no KitGramLexGrammar.
   4780    * It requires the standalone lexer; --parser-codegen on its own emits only
   4781    * the buffered parse_rd. */
   4782   int standalone = b->lexer_standalone && mainlex;
   4783   int fused = standalone;
   4784   int recover = b->parser_recover;
   4785   int lex_utf8 = mainlex && mainlex->input == KIT_GRAM_LEX_INPUT_UTF8;
   4786   int lex_has_start = mainlex && (mainlex->start_text || mainlex->start_line);
   4787   const char* ML = b->multiline ? "1" : "0";
   4788   char* scan_sym = fused ? lex_scan_symbol(ctx, prefix, "main") : NULL;
   4789 
   4790   emit_line(out, "");
   4791   emit_line(out,
   4792             "/* ---- pull recursive-descent parser (--parser-codegen) ---- */");
   4793   emit_line(out, "#include <setjmp.h>");
   4794   emit_line(out, "");
   4795   emit_line(out, "typedef struct RD RD;");
   4796   emit_line(out, "struct RD {");
   4797   emit_line(
   4798       out,
   4799       "    KitGramToken look;              /* current lookahead token        */");
   4800   emit_line(out, "    const KitGramActions *act;");
   4801   emit_line(out, "    void *ud;");
   4802   emit_line(out, "    jmp_buf err;");
   4803   emit_line(out,
   4804             "    KitGramError e;                 /* error detail, filled on "
   4805             "failure */");
   4806   emit_line(out,
   4807             "    size_t nerrors;             /* errors reported (incl. "
   4808             "recovered) */");
   4809   emit_line(
   4810       out,
   4811       "    void (*refill)(RD *);       /* load the next token into look   */");
   4812   emit_line(out,
   4813             "    const KitGramToken *toks;       /* buffered source              "
   4814             "   */");
   4815   emit_line(out, "    size_t ntok, pos;");
   4816   emit_line(
   4817       out,
   4818       "    const unsigned char *cur, *end;  /* fused (byte) source        */");
   4819   emit_line(out, "    uint32_t line, col;");
   4820   if (standalone && lex_utf8)
   4821     emit_line(
   4822         out,
   4823         "    bool after_cr;              /* UTF-8 CRLF coalescing state    */");
   4824   emit_line(out, "};");
   4825   emit_line(out, "");
   4826   /* rd_self[k] == k, so &rd_self[k] is a one-element expected set {k} backed by
   4827    * static storage (safe to hand back through the error out-param). */
   4828   emit_line(out, "static const KitGramTokenKind rd_self[] = {");
   4829   for (size_t i = 0; i < b->ntokens; i += 16) {
   4830     buf_append(out, "    ");
   4831     for (size_t j = i; j < i + 16 && j < b->ntokens; j++) {
   4832       if (j > i) buf_append(out, ", ");
   4833       buf_append_uint(out, (unsigned)j);
   4834     }
   4835     buf_append(out, ",\n");
   4836   }
   4837   emit_line(out, "};");
   4838   emit_line(out,
   4839             "static KitGramTokenKind rd_look(RD *p) { return p->look.kind; }");
   4840   /* Always-abort failure (lex errors, and every parse error in fail-fast mode).
   4841    * In recovery mode parse errors go through rd_error instead, so rd_fail is
   4842    * needed only for the fused/standalone refill's lex-error path. */
   4843   if (!recover || fused) {
   4844     emit_line(out,
   4845               "static _Noreturn void rd_fail(RD *p, const KitGramTokenKind *exp, "
   4846               "size_t nexp, KitGramRuleId in_rule) {");
   4847     emit_line(out,
   4848               "    p->e.found = p->look; p->e.expected = exp; p->e.nexpected = "
   4849               "nexp; p->e.in_rule = in_rule;");
   4850     emit_line(out, "    p->nerrors++;");
   4851     emit_line(out, "    if (p->act->on_error) p->act->on_error(p->ud, &p->e);");
   4852     emit_line(out, "    longjmp(p->err, 1);");
   4853     emit_line(out, "}");
   4854   }
   4855   if (recover) {
   4856     /* Recovery (opt-in). Report the error and act on the on_error return: ABORT
   4857      * (the default with no on_error bound) longjmps; SKIP discards the
   4858      * lookahead and retries the same expectation; RESYNC skips to a sync set.
   4859      * No stack unwinding. Driven by the lookahead only — no hidden state. */
   4860     emit_line(out,
   4861               "static int rd_in(const KitGramTokenKind *s, uint16_t n, "
   4862               "KitGramTokenKind k) {");
   4863     emit_line(out,
   4864               "    for (uint16_t i = 0; i < n; i++) if (s[i] == k) return 1;");
   4865     emit_line(out, "    return 0;");
   4866     emit_line(out, "}");
   4867     emit_line(
   4868         out,
   4869         "static KitGramErrorAction rd_error(RD *p, const KitGramTokenKind *exp, "
   4870         "size_t nexp, KitGramRuleId in_rule) {");
   4871     emit_line(out,
   4872               "    p->e.found = p->look; p->e.expected = exp; p->e.nexpected = "
   4873               "nexp; p->e.in_rule = in_rule;");
   4874     emit_line(out, "    p->nerrors++;");
   4875     emit_line(out,
   4876               "    KitGramErrorAction a = p->act->on_error ? "
   4877               "p->act->on_error(p->ud, &p->e) : KIT_GRAM_ABORT;");
   4878     emit_line(out, "    if (a == KIT_GRAM_ABORT) longjmp(p->err, 1);");
   4879     emit_line(out, "    return a;");
   4880     emit_line(out, "}");
   4881     /* Recover a terminal mismatch: SKIP discards and retries; RESYNC skips to k
   4882      * or to a token that can follow the rule, then matches k or returns NULL.
   4883      */
   4884     emit_line(out,
   4885               "static KitGramSem rd_match(RD *p, KitGramTokenKind k, KitGramRuleId "
   4886               "in_rule) {");
   4887     emit_line(out, "    while (p->look.kind != k) {");
   4888     emit_line(
   4889         out,
   4890         "        if (rd_error(p, &rd_self[k], 1, in_rule) == KIT_GRAM_SKIP) { "
   4891         "p->refill(p); continue; }");
   4892     emit_line(out, "        const KitGramRule *_r = &%sgrammar.rules[in_rule];",
   4893               prefix);
   4894     emit_line(
   4895         out, "        while (p->look.kind != k && p->look.kind != %sTOK_EOF &&",
   4896         up);
   4897     emit_line(out,
   4898               "               !rd_in(_r->follow, _r->nfollow, p->look.kind)) "
   4899               "p->refill(p);");
   4900     emit_line(out,
   4901               "        if (p->look.kind != k) return (KitGramSem)0;     /* k "
   4902               "missing; resume the rule */");
   4903     emit_line(out, "        break;");
   4904     emit_line(out, "    }");
   4905     emit_line(out, "    KitGramToken t = p->look;");
   4906     emit_line(out, "    p->refill(p);");
   4907     emit_line(out,
   4908               "    KitGramSem v = p->act->lift_token ? p->act->lift_token(p->ud, "
   4909               "t) : (KitGramSem)0;");
   4910     emit_line(out, "    if (p->act->on_token) p->act->on_token(p->ud, t);");
   4911     emit_line(out, "    return v;");
   4912     emit_line(out, "}");
   4913     /* Recover a prediction miss: returns 1 to re-predict (SKIP, or RESYNC
   4914      * landed on a token that can start the rule) or 0 to abandon the rule with
   4915      * NULL. */
   4916     emit_line(out,
   4917               "static int rd_recover_predict(RD *p, const KitGramTokenKind *exp, "
   4918               "size_t nexp, KitGramRuleId in_rule, const KitGramRule *_r) {");
   4919     emit_line(out,
   4920               "    if (rd_error(p, exp, nexp, in_rule) == KIT_GRAM_SKIP) { "
   4921               "p->refill(p); return 1; }");
   4922     emit_line(out,
   4923               "    while (p->look.kind != %sTOK_EOF && !rd_in(_r->first, "
   4924               "_r->nfirst, p->look.kind) &&",
   4925               up);
   4926     emit_line(out,
   4927               "           !rd_in(_r->follow, _r->nfollow, p->look.kind)) "
   4928               "p->refill(p);");
   4929     emit_line(out, "    return rd_in(_r->first, _r->nfirst, p->look.kind);");
   4930     emit_line(out, "}");
   4931   } else {
   4932     /* Fail-fast: a mismatch never returns (rd_fail longjmps), so the compiler
   4933      * optimizes the hot path with a single return — no recovery overhead. */
   4934     emit_line(out,
   4935               "static KitGramSem rd_match(RD *p, KitGramTokenKind k, KitGramRuleId "
   4936               "in_rule) {");
   4937     emit_line(
   4938         out, "    if (p->look.kind != k) rd_fail(p, &rd_self[k], 1, in_rule);");
   4939     emit_line(out, "    KitGramToken t = p->look;");
   4940     emit_line(out, "    p->refill(p);");
   4941     emit_line(out,
   4942               "    KitGramSem v = p->act->lift_token ? p->act->lift_token(p->ud, "
   4943               "t) : (KitGramSem)0;");
   4944     emit_line(out, "    if (p->act->on_token) p->act->on_token(p->ud, t);");
   4945     emit_line(out, "    return v;");
   4946     emit_line(out, "}");
   4947   }
   4948   emit_line(
   4949       out,
   4950       "static KitGramSem rd_reduce(RD *p, KitGramRuleId r, int prod, KitGramSem "
   4951       "*kids, size_t n) {");
   4952   emit_line(out,
   4953             "    return p->act->reduce ? p->act->reduce(p->ud, r, prod, kids, "
   4954             "n) : (KitGramSem)0;");
   4955   emit_line(out, "}");
   4956   emit_line(out, "static void rd_refill_buf(RD *p) {");
   4957   emit_line(out, "    if (p->pos < p->ntok) p->look = p->toks[p->pos++];");
   4958   emit_line(out, "    else p->look = (KitGramToken){ .kind = %sTOK_EOF };", up);
   4959   emit_line(out, "}");
   4960   if (standalone) {
   4961     /* Self-contained refill: drives the table-free scanner (kind+skip and the
   4962      * %keywords rewrite baked in, end-context decided inline), so there is no
   4963      * KitGramLexGrammar and no runtime-lexer dependency. */
   4964     emit_line(out, "static void rd_refill_lex(RD *p) {");
   4965     emit_line(out, "    for (;;) {");
   4966     emit_line(out, "        if (p->cur == p->end) {");
   4967     emit_line(
   4968         out,
   4969         "            p->look = (KitGramToken){ .kind = %sTOK_EOF, .lexeme = "
   4970         "(const char *)p->cur,",
   4971         up);
   4972     emit_line(
   4973         out,
   4974         "                                  .line = p->line, .col = p->col };");
   4975     emit_line(out, "            return;");
   4976     emit_line(out, "        }");
   4977     emit_line(out, "        uint16_t start = 0;");
   4978     if (lex_has_start) {
   4979       emit_line(out, "        if (p->line == 1u && p->col == 1u) start = %u;",
   4980                 (unsigned)mainlex->start_text);
   4981       if (mainlex->start_line)
   4982         emit_line(out, "        else if (%s && p->col == 1u) start = %u;", ML,
   4983                   (unsigned)mainlex->start_line);
   4984     }
   4985     emit_line(out, "        %ssres r = %s(start, p->cur, p->end, %s);", prefix,
   4986               scan_sym, ML);
   4987     emit_line(out,
   4988               "        if (!r.found) {                         /* lex error at "
   4989               "the cursor */");
   4990     emit_line(
   4991         out,
   4992         "            p->look = (KitGramToken){ .kind = %sTOK_EOF, .lexeme = "
   4993         "(const char *)p->cur,",
   4994         up);
   4995     emit_line(out,
   4996               "                                  .len = (size_t)(p->cur < "
   4997               "p->end), .line = p->line, .col = p->col };");
   4998     emit_line(out, "            rd_fail(p, (const KitGramTokenKind *)0, 0, 0);");
   4999     emit_line(out, "        }");
   5000     emit_line(out, "        const unsigned char *lex = p->cur;");
   5001     emit_line(out, "        uint32_t tline = p->line, tcol = p->col;");
   5002     if (lex_utf8) {
   5003       emit_line(
   5004           out,
   5005           "        %sfold_pos(&p->line, &p->col, &p->after_cr, lex, r.len);",
   5006           prefix);
   5007     } else {
   5008       emit_line(out, "        p->line += (uint32_t)r.nlines;");
   5009       emit_line(out,
   5010                 "        p->col = r.last_nl ? (uint32_t)(1u + (r.len - "
   5011                 "r.last_nl)) : p->col + (uint32_t)r.len;");
   5012     }
   5013     emit_line(out, "        p->cur += r.len;");
   5014     emit_line(out, "        if (r.skip) continue;");
   5015     emit_line(
   5016         out,
   5017         "        p->look = (KitGramToken){ .kind = r.kind, .lexeme = (const "
   5018         "char *)lex, .len = r.len,");
   5019     emit_line(out,
   5020               "                              .line = tline, .col = tcol };");
   5021     emit_line(out, "        return;");
   5022     emit_line(out, "    }");
   5023     emit_line(out, "}");
   5024   }
   5025   emit_line(out, "");
   5026   for (size_t ri = 0; ri < b->nrules; ri++)
   5027     emit_line(out, "static KitGramSem rd_r%zu(RD *p);", ri);
   5028   emit_line(out, "");
   5029   for (size_t ri = 0; ri < b->nrules; ri++) {
   5030     rd_emit_rule(out, b, (int)ri, prefix, recover);
   5031     emit_line(out, "");
   5032   }
   5033   emit_line(out,
   5034             "KitGramSem %sparse_rd(const KitGramToken *toks, size_t ntok, const "
   5035             "KitGramActions *act, void *ud, KitGramError *err, int *ok) {",
   5036             prefix);
   5037   emit_line(out,
   5038             "    RD p = { .act = act, .ud = ud, .refill = rd_refill_buf, .toks "
   5039             "= toks, .ntok = ntok };");
   5040   emit_line(out,
   5041             "    if (setjmp(p.err)) { if (err) *err = p.e; if (ok) *ok = 0; "
   5042             "return (KitGramSem)0; }");
   5043   emit_line(out, "    p.refill(&p);");
   5044   emit_line(out, "    KitGramSem r = rd_r0(&p);");
   5045   if (recover) {
   5046     emit_line(out,
   5047               "    if (p.look.kind != %sTOK_EOF) {                 /* trailing "
   5048               "tokens */",
   5049               up);
   5050     emit_line(out,
   5051               "        (void)rd_error(&p, &rd_self[%sTOK_EOF], 1, 0);   /* "
   5052               "aborts unless recovering */",
   5053               up);
   5054     emit_line(out, "        while (p.look.kind != %sTOK_EOF) p.refill(&p);",
   5055               up);
   5056     emit_line(out, "    }");
   5057   } else {
   5058     emit_line(
   5059         out,
   5060         "    if (p.look.kind != %sTOK_EOF) rd_fail(&p, &rd_self[%sTOK_EOF], "
   5061         "1, 0);  /* trailing tokens */",
   5062         up, up);
   5063   }
   5064   emit_line(out, "    if (ok) *ok = 1;");
   5065   emit_line(out, "    return r;");
   5066   emit_line(out, "}");
   5067   if (fused) {
   5068     emit_line(out, "");
   5069     emit_line(out,
   5070               "KitGramSem %sparse_fused(const unsigned char *buf, size_t len, "
   5071               "const KitGramActions *act, void *ud, KitGramError *err, int *ok) {",
   5072               prefix);
   5073     emit_line(out,
   5074               "    RD p = { .act = act, .ud = ud, .refill = rd_refill_lex,");
   5075     emit_line(
   5076         out,
   5077         "             .cur = buf, .end = buf + len, .line = 1, .col = 1 };");
   5078     emit_line(out,
   5079               "    if (setjmp(p.err)) { if (err) *err = p.e; if (ok) *ok = 0; "
   5080               "return (KitGramSem)0; }");
   5081     emit_line(out, "    p.refill(&p);");
   5082     emit_line(out, "    KitGramSem r = rd_r0(&p);");
   5083     if (recover) {
   5084       emit_line(out, "    if (p.look.kind != %sTOK_EOF) {", up);
   5085       emit_line(out, "        (void)rd_error(&p, &rd_self[%sTOK_EOF], 1, 0);",
   5086                 up);
   5087       emit_line(out, "        while (p.look.kind != %sTOK_EOF) p.refill(&p);",
   5088                 up);
   5089       emit_line(out, "    }");
   5090     } else {
   5091       emit_line(out,
   5092                 "        if (p.look.kind != %sTOK_EOF) rd_fail(&p, "
   5093                 "&rd_self[%sTOK_EOF], 1, 0);",
   5094                 up, up);
   5095     }
   5096     emit_line(out, "    if (ok) *ok = 1;");
   5097     emit_line(out, "    return r;");
   5098     emit_line(out, "}");
   5099   }
   5100   if (scan_sym) xfree(ctx, scan_sym);
   5101   xfree(ctx, up);
   5102 }
   5103 
   5104 static void emit_c_text(Buf* out, Builder* b, const char* grammar_path,
   5105                         const char* header_path, const char* prefix) {
   5106   GramgenContext* ctx = out->ctx;
   5107   size_t hidden_count = b->nrules - b->public_count;
   5108   emit_line(out, "/* Generated by gramgen from %s. */",
   5109             base_name(grammar_path));
   5110   emit_line(out, "#include \"%s\"", base_name(header_path));
   5111   /* Machine-only file: emit just the codegen-only token machines, table-free
   5112    * (no parser grammar, no gramparse_tables.h / gramlex_tables.h contract). */
   5113   if (b->public_count == 0 && b->nmachines) {
   5114     emit_line(out, "");
   5115     emit_machines(b, out, prefix);
   5116     return;
   5117   }
   5118   emit_line(out, "#include <kit/support/gram_parse_tables.h>");
   5119   /* Standalone mode emits no lexer table, so it normally needs none of the
   5120    * table contract (KitGramLexGrammar / KitGramLexAccept). The exception is a
   5121    * standalone lexer that keeps %keywords in a minimal perfect hash (the
   5122    * default): it reuses the header-only KitGramLexKeyword record and
   5123    * kit_gram_lex_kw_hash64 so its inline lookup matches the construction
   5124    * byte-for-byte, with no runtime link dependency. */
   5125   int sa_kw_tables = 0;
   5126   if (b->lexer_standalone)
   5127     for (size_t i = 0; i < b->nlex_dfas; i++)
   5128       if (b->lex_dfas[i].nkeyword_tables) {
   5129         sa_kw_tables = 1;
   5130         break;
   5131       }
   5132   if (b->nlex_dfas && (!b->lexer_standalone || sa_kw_tables))
   5133     emit_line(out, "#include <kit/support/gram_lex_tables.h>");
   5134   emit_line(out, "");
   5135   char* up = make_upper_prefix(ctx, prefix);
   5136   if (hidden_count) {
   5137     emit_line(out, "enum {");
   5138     for (size_t i = 0; i < hidden_count; i++) {
   5139       if (i == 0)
   5140         emit_line(out, "    %sR__SYN_0 = %sR__COUNT,", up, up);
   5141       else
   5142         emit_line(out, "    %sR__SYN_%zu,", up, i);
   5143     }
   5144     emit_line(out, "    %sR__ALL_COUNT", up);
   5145     emit_line(out, "};");
   5146     emit_line(out, "");
   5147   }
   5148   emit_sets(b, out, up);
   5149   emit_sub_arrays(b, out, up);
   5150   emit_rule_arrays(b, out, up);
   5151   emit_first_follow(b, out, up);
   5152   emit_line(out, "#define N(arr) (uint16_t)(sizeof(arr) / sizeof((arr)[0]))");
   5153   emit_line(out, "");
   5154   emit_pratt_tables(b, out, up);
   5155   emit_rules(b, out, up);
   5156   emit_line(out, "static const char *const g_tok_names[] = {");
   5157   Buf names;
   5158   buf_init(ctx, &names);
   5159   for (size_t i = 0; i < b->ntokens; i++) {
   5160     if (i) buf_append(&names, ", ");
   5161     char* q = c_string_str(ctx, b->tokens[i].display);
   5162     buf_append(&names, q);
   5163     xfree(ctx, q);
   5164   }
   5165   emit_line(out, "    %s", names.s);
   5166   emit_line(out, "};");
   5167   emit_line(out, "");
   5168   char* stem = path_stem(ctx, grammar_path);
   5169   char* stem_q = quote_len(ctx, stem, strlen(stem), false);
   5170   const char* nrules = hidden_count ? xasprintf(ctx, "%sR__ALL_COUNT", up)
   5171                                     : xasprintf(ctx, "%sR__COUNT", up);
   5172   const char* sets_ptr = b->nsets ? "g_sets" : "NULL";
   5173   const char* off_ptr = b->nsets ? "g_set_off" : "NULL";
   5174   char* start = rule_ref(ctx, b, up, 0);
   5175   emit_line(out, "const KitGramGrammar %sgrammar = {", prefix);
   5176   emit_line(out, "    .name = %s,", stem_q);
   5177   emit_line(out, "    .rules = g_rules, .nrules = %s,", nrules);
   5178   emit_line(out, "    .start = %s, .eof = %sTOK_EOF,", start, up);
   5179   emit_line(out, "    .sets = %s, .set_off = %s, .nsets = %zu,", sets_ptr,
   5180             off_ptr, b->nsets);
   5181   emit_line(out, "    .tok_names = g_tok_names, .ntoks = %sTOK__COUNT,", up);
   5182   emit_line(out, "};");
   5183   emit_line(out, "");
   5184   emit_line(out,
   5185             "void %sstack_bounds(size_t max_depth, size_t* ctl_cap, size_t* "
   5186             "val_cap) {",
   5187             prefix);
   5188   emit_line(out,
   5189             "    kit_gram_stack_bounds(&%sgrammar, max_depth, ctl_cap, val_cap);",
   5190             prefix);
   5191   emit_line(out, "}");
   5192   emit_line(out, "size_t %sgenerate_scratch_count(void) {", prefix);
   5193   emit_line(out, "    return kit_gram_parser_generate_scratch_count(&%sgrammar);",
   5194             prefix);
   5195   emit_line(out, "}");
   5196   emit_line(out, "");
   5197   emit_line(out,
   5198             "void %sparser_init(KitGramParser *mem, const KitGramConfig *cfg) {",
   5199             prefix);
   5200   emit_line(out, "    kit_gram_parser_init(mem, &%sgrammar, cfg);", prefix);
   5201   emit_line(out, "}");
   5202   if (b->nlex_dfas) emit_line(out, "");
   5203   if (b->lexer_standalone) {
   5204     emit_lexer_standalone(b, out, prefix);
   5205   } else {
   5206     emit_lexer_tables(b, out, grammar_path, prefix);
   5207   }
   5208   if (b->parser_codegen) emit_parser_codegen(b, out, prefix);
   5209   if (b->nmachines) {
   5210     emit_line(out, "");
   5211     emit_machines(b, out, prefix);
   5212   }
   5213   xfree(ctx, up);
   5214   xfree(ctx, stem);
   5215   xfree(ctx, stem_q);
   5216   xfree(ctx, start);
   5217 }
   5218 
   5219 /* One materialized %lex block: its KitGramLexGrammar plus the table arrays the
   5220  * grammar points into (all arena-allocated, reclaimed wholesale at
   5221  * mem_release). The main block and every named sub-lexer get one of these. */
   5222 typedef struct {
   5223   const char* name; /* "main" for the main block, else the %lex <name> */
   5224   int is_main;
   5225   KitGramLexGrammar grammar;
   5226   KitGramLexAccept* accepts;
   5227   uint16_t* accept;
   5228   uint16_t* accept_text;
   5229   uint16_t* accept_line;
   5230   uint16_t* trans;
   5231   KitGramLexKeywordTable* keyword_tables;
   5232   KitGramLexKeyword* keywords;
   5233   uint32_t* kw_seeds;
   5234 } LexMat;
   5235 
   5236 struct KitGramCompiled {
   5237   GramgenMem mem;
   5238   const KitContext* kit; /* caller context, for kit_gram_emit_c diagnostics */
   5239   Builder* b;
   5240   char* path;
   5241   char* name;
   5242 
   5243   KitGramGrammar grammar;
   5244   KitGramRule* rules;
   5245   KitGramSym** prod_syms;
   5246   size_t nprod_syms;
   5247   KitGramProd** rule_prods;
   5248   KitGramSym* wrapper_subs;
   5249   KitGramPratt* pratts;
   5250   KitGramPrattOp** pratt_ops;
   5251   KitGramTokenKind** first_arrays;
   5252   KitGramTokenKind** follow_arrays;
   5253   uint8_t** predict_arrays;
   5254   KitGramTokenKind* sets_flat;
   5255   uint16_t* set_off;
   5256   const char** tok_names;
   5257 
   5258   int has_lex;
   5259   /* One materialized lexer per %lex block (main + named sub-lexers), in source
   5260    * order with the main block first. Each owns its table arrays in the arena.
   5261    */
   5262   LexMat* lexers;
   5263   size_t nlexers;
   5264   size_t main_lexer; /* index of the main block in lexers[] */
   5265 };
   5266 
   5267 static KitGramTokenKind* copy_intset_toks(GramgenContext* ctx, const IntSet* set) {
   5268   if (!set->n) return NULL;
   5269   int* sorted = intset_sorted(ctx, set);
   5270   KitGramTokenKind* out = xmalloc(ctx, set->n * sizeof *out);
   5271   for (size_t i = 0; i < set->n; i++) out[i] = (KitGramTokenKind)sorted[i];
   5272   xfree(ctx, sorted);
   5273   return out;
   5274 }
   5275 
   5276 static KitGramSym runtime_sym(KitGramCompiled* c, const Sym* sym) {
   5277   switch (sym->kind) {
   5278     case SYM_TERM:
   5279       return (KitGramSym){.kind = KIT_GRAM_S_TERM, .tok = (KitGramTokenKind)sym->value};
   5280     case SYM_RULE:
   5281       return (KitGramSym){.kind = KIT_GRAM_S_RULE, .rule = (KitGramRuleId)sym->value};
   5282     case SYM_REP:
   5283     case SYM_OPT:
   5284       return (KitGramSym){
   5285           .kind = sym->kind == SYM_REP ? KIT_GRAM_S_REP : KIT_GRAM_S_OPT,
   5286           .first = (uint16_t)sym->set_index,
   5287           .sub = &c->wrapper_subs[sym->sub_array_id],
   5288           .nsub = 1,
   5289       };
   5290   }
   5291   return (KitGramSym){0};
   5292 }
   5293 
   5294 static uint8_t* build_predict_array(GramgenContext* ctx, Rule* rule,
   5295                                     size_t ntokens) {
   5296   if (rule->pratt || !rule->first.n) return NULL;
   5297   int* prod_for_tok = xmalloc(ctx, ntokens * sizeof *prod_for_tok);
   5298   for (size_t i = 0; i < ntokens; i++) prod_for_tok[i] = -1;
   5299   for (size_t pi = 0; pi < rule->nprods; pi++) {
   5300     Prod* prod = &rule->prods[pi];
   5301     for (size_t ti = 0; ti < prod->first.n; ti++) {
   5302       int tok = prod->first.v[ti];
   5303       if (tok >= 0 && (size_t)tok < ntokens) prod_for_tok[tok] = (int)pi;
   5304     }
   5305   }
   5306   int* sorted = intset_sorted(ctx, &rule->first);
   5307   uint8_t* out = xmalloc(ctx, rule->first.n * sizeof *out);
   5308   for (size_t ti = 0; ti < rule->first.n; ti++) {
   5309     int tok = sorted[ti];
   5310     int prod_idx = (tok >= 0 && (size_t)tok < ntokens) ? prod_for_tok[tok] : 0;
   5311     if (prod_idx < 0) prod_idx = 0;
   5312     out[ti] = (uint8_t)prod_idx;
   5313   }
   5314   xfree(ctx, prod_for_tok);
   5315   xfree(ctx, sorted);
   5316   return out;
   5317 }
   5318 
   5319 static void materialize_sets(GramgenContext* ctx, KitGramCompiled* c) {
   5320   Builder* b = c->b;
   5321   if (!b->nsets) return;
   5322   size_t nflat = 0;
   5323   for (size_t i = 0; i < b->nsets; i++) nflat += b->sets[i].n;
   5324   c->sets_flat = nflat ? xmalloc(ctx, nflat * sizeof *c->sets_flat) : NULL;
   5325   c->set_off = xmalloc(ctx, (b->nsets + 1) * sizeof *c->set_off);
   5326   size_t off = 0;
   5327   c->set_off[0] = 0;
   5328   for (size_t i = 0; i < b->nsets; i++) {
   5329     int* sorted = intset_sorted(ctx, &b->sets[i]);
   5330     for (size_t j = 0; j < b->sets[i].n; j++)
   5331       c->sets_flat[off++] = (KitGramTokenKind)sorted[j];
   5332     c->set_off[i + 1] = (uint16_t)off;
   5333     xfree(ctx, sorted);
   5334   }
   5335 }
   5336 
   5337 static void materialize_parser(GramgenContext* ctx, KitGramCompiled* c) {
   5338   Builder* b = c->b;
   5339   materialize_sets(ctx, c);
   5340 
   5341   if (b->nwrapper_syms) {
   5342     c->wrapper_subs = xmalloc(ctx, b->nwrapper_syms * sizeof *c->wrapper_subs);
   5343     for (size_t i = 0; i < b->nwrapper_syms; i++)
   5344       c->wrapper_subs[i] = runtime_sym(c, b->wrapper_syms[i]->sub);
   5345   }
   5346 
   5347   c->rules = xcalloc(ctx, b->nrules, sizeof *c->rules);
   5348   c->rule_prods = xcalloc(ctx, b->nrules, sizeof *c->rule_prods);
   5349   c->first_arrays = xcalloc(ctx, b->nrules, sizeof *c->first_arrays);
   5350   c->follow_arrays = xcalloc(ctx, b->nrules, sizeof *c->follow_arrays);
   5351   c->predict_arrays = xcalloc(ctx, b->nrules, sizeof *c->predict_arrays);
   5352   c->pratts = xcalloc(ctx, b->nrules, sizeof *c->pratts);
   5353   c->pratt_ops = xcalloc(ctx, b->nrules, sizeof *c->pratt_ops);
   5354   c->tok_names = xmalloc(ctx, b->ntokens * sizeof *c->tok_names);
   5355   for (size_t i = 0; i < b->ntokens; i++)
   5356     c->tok_names[i] = b->tokens[i].display.s;
   5357 
   5358   size_t total_prods = 0;
   5359   for (size_t ri = 0; ri < b->nrules; ri++) total_prods += b->rules[ri].nprods;
   5360   c->prod_syms =
   5361       total_prods ? xcalloc(ctx, total_prods, sizeof *c->prod_syms) : NULL;
   5362 
   5363   size_t prod_array_idx = 0;
   5364   for (size_t ri = 0; ri < b->nrules; ri++) {
   5365     Rule* rule = &b->rules[ri];
   5366     if (!rule->pratt && rule->nprods) {
   5367       c->rule_prods[ri] = xcalloc(ctx, rule->nprods, sizeof *c->rule_prods[ri]);
   5368       for (size_t pi = 0; pi < rule->nprods; pi++) {
   5369         Prod* prod = &rule->prods[pi];
   5370         if (prod->nsyms) {
   5371           KitGramSym* syms = xmalloc(ctx, prod->nsyms * sizeof *syms);
   5372           for (size_t si = 0; si < prod->nsyms; si++)
   5373             syms[si] = runtime_sym(c, &prod->syms[si]);
   5374           c->prod_syms[prod_array_idx++] = syms;
   5375           c->rule_prods[ri][pi] =
   5376               (KitGramProd){.syms = syms, .nsyms = (uint16_t)prod->nsyms};
   5377         } else {
   5378           c->rule_prods[ri][pi] = (KitGramProd){0};
   5379         }
   5380       }
   5381     }
   5382 
   5383     c->first_arrays[ri] = copy_intset_toks(ctx, &rule->first);
   5384     c->follow_arrays[ri] = copy_intset_toks(ctx, &rule->follow);
   5385     c->predict_arrays[ri] = build_predict_array(ctx, rule, b->ntokens);
   5386 
   5387     const KitGramPratt* pratt_ptr = NULL;
   5388     if (rule->pratt) {
   5389       if (rule->pratt->nops) {
   5390         c->pratt_ops[ri] =
   5391             xmalloc(ctx, rule->pratt->nops * sizeof *c->pratt_ops[ri]);
   5392         for (size_t oi = 0; oi < rule->pratt->nops; oi++) {
   5393           PrattOp* op = &rule->pratt->ops[oi];
   5394           KitGramPrattOpRole role = KIT_GRAM_PO_INFIX;
   5395           if (op->role == b->role.prefix)
   5396             role = KIT_GRAM_PO_PREFIX;
   5397           else if (op->role == b->role.postfix)
   5398             role = KIT_GRAM_PO_POSTFIX;
   5399           else if (op->role == b->role.ternary)
   5400             role = KIT_GRAM_PO_TERNARY;
   5401           else if (op->role == b->role.circumfix)
   5402             role = KIT_GRAM_PO_CIRCUMFIX;
   5403           KitGramSym inner = {0};
   5404           if (role == KIT_GRAM_PO_CIRCUMFIX)
   5405             inner = (KitGramSym){.kind = KIT_GRAM_S_RULE,
   5406                                .rule = (KitGramRuleId)op->inner_rule};
   5407           c->pratt_ops[ri][oi] = (KitGramPrattOp){
   5408               .role = role,
   5409               .tok = (KitGramTokenKind)op->tok,
   5410               .tok2 = (KitGramTokenKind)op->tok2,
   5411               .prod = (uint16_t)op->prod,
   5412               .lbp = (uint16_t)op->lbp,
   5413               .rbp = (uint16_t)op->rbp,
   5414               .inner = inner,
   5415           };
   5416         }
   5417       }
   5418       c->pratts[ri] = (KitGramPratt){
   5419           .primary = (KitGramRuleId)rule->pratt->primary_rule,
   5420           .primary_prod = (uint16_t)rule->pratt->primary_prod,
   5421           .ops = c->pratt_ops[ri],
   5422           .nops = (uint16_t)rule->pratt->nops,
   5423       };
   5424       pratt_ptr = &c->pratts[ri];
   5425     }
   5426 
   5427     c->rules[ri] = (KitGramRule){
   5428         .name = rule->name,
   5429         .hidden = !rule->public_rule,
   5430         .is_pratt = rule->pratt != NULL,
   5431         .pratt = pratt_ptr,
   5432         .prods = c->rule_prods[ri],
   5433         .nprods = (uint16_t)rule->nprods,
   5434         .predict_tok = c->first_arrays[ri],
   5435         .predict_prod = c->predict_arrays[ri],
   5436         .npredict = rule->pratt ? 0 : (uint16_t)rule->first.n,
   5437         .nullable = rule->nullable != 0,
   5438         .empty_prod = (uint8_t)rule->empty_prod,
   5439         .first = c->first_arrays[ri],
   5440         .nfirst = (uint16_t)rule->first.n,
   5441         .follow = c->follow_arrays[ri],
   5442         .nfollow = (uint16_t)rule->follow.n,
   5443     };
   5444   }
   5445 
   5446   c->grammar = (KitGramGrammar){
   5447       .name = c->name,
   5448       .rules = c->rules,
   5449       .nrules = (uint16_t)b->nrules,
   5450       .start = 0,
   5451       .eof = 0,
   5452       .sets = c->sets_flat,
   5453       .set_off = c->set_off,
   5454       .nsets = (uint16_t)b->nsets,
   5455       .tok_names = c->tok_names,
   5456       .ntoks = (uint16_t)b->ntokens,
   5457   };
   5458 }
   5459 
   5460 /* Materialize one %lex block's DFA into an owned KitGramLexGrammar. The same
   5461  * table layout the codegen path emits as static arrays, built in the arena
   5462  * instead. The grammar name mirrors codegen: the stem for the main block,
   5463  * "stem:name" for a named sub-lexer. */
   5464 static void materialize_one_lexer(GramgenContext* ctx, KitGramCompiled* c,
   5465                                   LexDFA* dfa, LexMat* m) {
   5466   m->name = dfa->name;
   5467   m->is_main = lex_name_is_main_c(dfa->name);
   5468   m->accepts = xcalloc(ctx, dfa->nrecognizers, sizeof *m->accepts);
   5469   for (size_t i = 0; i < dfa->nrecognizers; i++) {
   5470     LexRecognizer* rec = &dfa->recognizers[i];
   5471     m->accepts[i] = (KitGramLexAccept){
   5472         .tok = (KitGramTokenKind)rec->tok,
   5473         .skip = rec->skip != 0,
   5474     };
   5475   }
   5476   m->accept = xmalloc(ctx, dfa->nstates * sizeof *m->accept);
   5477   memcpy(m->accept, dfa->accept, dfa->nstates * sizeof *m->accept);
   5478   if (dfa->accept_text) {
   5479     m->accept_text = xmalloc(ctx, dfa->nstates * sizeof *m->accept_text);
   5480     memcpy(m->accept_text, dfa->accept_text,
   5481            dfa->nstates * sizeof *m->accept_text);
   5482   }
   5483   if (dfa->accept_line) {
   5484     m->accept_line = xmalloc(ctx, dfa->nstates * sizeof *m->accept_line);
   5485     memcpy(m->accept_line, dfa->accept_line,
   5486            dfa->nstates * sizeof *m->accept_line);
   5487   }
   5488 
   5489   /* Dense by-state transition table: nstates * nclasses uint16. */
   5490   uint16_t stride = dfa->nclasses;
   5491   size_t trans_cells = (size_t)dfa->nstates * stride;
   5492   m->trans = xmalloc(ctx, trans_cells * sizeof *m->trans);
   5493   memcpy(m->trans, dfa->trans, trans_cells * sizeof *m->trans);
   5494 
   5495   /* Extracted-keyword tables, mirroring the emitted static arrays. The lexeme
   5496    * bytes point straight into the permanent lex AST owned by this compiled
   5497    * object, so no copy is needed. */
   5498   if (dfa->nkeyword_tables) {
   5499     size_t total_kw = 0, total_seeds = 0;
   5500     for (size_t i = 0; i < dfa->nkeyword_tables; i++) {
   5501       total_kw += dfa->keyword_tables[i].nslots;
   5502       total_seeds += dfa->keyword_tables[i].nseeds;
   5503     }
   5504     m->keywords = xmalloc(ctx, (total_kw ? total_kw : 1) * sizeof *m->keywords);
   5505     m->kw_seeds =
   5506         xmalloc(ctx, (total_seeds ? total_seeds : 1) * sizeof *m->kw_seeds);
   5507     m->keyword_tables =
   5508         xcalloc(ctx, dfa->nkeyword_tables, sizeof *m->keyword_tables);
   5509     size_t koff = 0, soff = 0;
   5510     for (size_t i = 0; i < dfa->nkeyword_tables; i++) {
   5511       LexKeywordTable* kt = &dfa->keyword_tables[i];
   5512       KitGramLexKeyword* kbase = m->keywords + koff;
   5513       for (size_t j = 0; j < kt->nslots; j++) {
   5514         if (kt->slots[j].literal.s) {
   5515           kbase[j].lexeme = kt->slots[j].literal.s;
   5516           kbase[j].len = (uint16_t)kt->slots[j].literal.len;
   5517           kbase[j].kind = (KitGramTokenKind)kt->slots[j].tok;
   5518         } else {
   5519           kbase[j] = (KitGramLexKeyword){0};
   5520         }
   5521       }
   5522       uint32_t* sbase = m->kw_seeds + soff;
   5523       for (size_t j = 0; j < kt->nseeds; j++) sbase[j] = kt->seeds[j];
   5524       m->keyword_tables[i] = (KitGramLexKeywordTable){
   5525           .host = (KitGramTokenKind)kt->host,
   5526           .seeds = sbase,
   5527           .nseeds = (uint16_t)kt->nseeds,
   5528           .keywords = kbase,
   5529           .nkeywords = (uint16_t)kt->nslots,
   5530           .min_len = (uint16_t)kt->min_len,
   5531           .max_len = (uint16_t)kt->max_len,
   5532       };
   5533       koff += kt->nslots;
   5534       soff += kt->nseeds;
   5535     }
   5536   }
   5537 
   5538   const char* gname =
   5539       m->is_main ? c->name : xasprintf(ctx, "%s:%s", c->name, dfa->name);
   5540   m->grammar = (KitGramLexGrammar){
   5541       .name = gname,
   5542       .input = dfa->input,
   5543       .nclasses = dfa->nclasses,
   5544       .class_stride = stride,
   5545       .trans = m->trans,
   5546       .nstates = dfa->nstates,
   5547       .accept = m->accept,
   5548       .accept_text = m->accept_text,
   5549       .accept_line = m->accept_line,
   5550       .accepts = m->accepts,
   5551       .naccepts = (uint16_t)dfa->nrecognizers,
   5552       .start_text = dfa->start_text,
   5553       .start_line = dfa->start_line,
   5554       .multiline = c->b->multiline != 0,
   5555       .keyword_tables = m->keyword_tables,
   5556       .nkeyword_tables = (uint16_t)dfa->nkeyword_tables,
   5557   };
   5558   memcpy(m->grammar.class_of, dfa->class_of, sizeof m->grammar.class_of);
   5559 }
   5560 
   5561 /* Materialize every %lex block (the main lexer plus each named sub-lexer) into
   5562  * its own KitGramLexGrammar, all reachable through the kit_gram_lexer_*
   5563  * introspection API. lexers[main_lexer] is the main block (also returned by the
   5564  * kit_gram_lexer_grammar shorthand). Machines live in a separate array and never
   5565  * materialize an KitGramLexGrammar, so they are not represented here. */
   5566 static void materialize_lexer(GramgenContext* ctx, KitGramCompiled* c) {
   5567   Builder* b = c->b;
   5568   if (!b->nlex_dfas || !b->lex_dfa) return;
   5569   c->has_lex = 1;
   5570   c->nlexers = b->nlex_dfas;
   5571   c->lexers = xcalloc(ctx, c->nlexers, sizeof *c->lexers);
   5572   c->main_lexer = 0;
   5573   for (size_t i = 0; i < b->nlex_dfas; i++) {
   5574     materialize_one_lexer(ctx, c, &b->lex_dfas[i], &c->lexers[i]);
   5575     if (c->lexers[i].is_main) c->main_lexer = i;
   5576   }
   5577 }
   5578 
   5579 static void materialize_compiled(GramgenContext* ctx, KitGramCompiled* c) {
   5580   materialize_parser(ctx, c);
   5581   materialize_lexer(ctx, c);
   5582 }
   5583 
   5584 KitStatus kit_gram_compile_text(const KitContext* kit, KitSlice text,
   5585                                 KitSlice path, const KitGramOptions* opts,
   5586                                 KitGramCompiled** out) {
   5587   if (out) *out = NULL;
   5588   if (!kit || !kit->heap || !opts || !out) return KIT_INVALID;
   5589   if (!text.s && text.len) return KIT_INVALID;
   5590 
   5591   GramgenContext ctx;
   5592   memset(&ctx, 0, sizeof ctx);
   5593   ctx.kit = kit;
   5594   ctx.mem.heap = kit->heap;
   5595   ctx.diag = &ctx.diag_storage;
   5596   ctx.can_jump = 1;
   5597 
   5598   if (setjmp(ctx.jmp)) {
   5599     gram_flush_diag(&ctx); /* before release: diag->path may point into arena */
   5600     mem_release(&ctx.scratch);
   5601     mem_release(&ctx.mem);
   5602     return KIT_ERR;
   5603   }
   5604 
   5605   const char* use_path =
   5606       path.len ? xstrndup(&ctx, path.s, path.len) : "<memory>";
   5607   ParsedGrammar* pg = NULL;
   5608   if (!parse_text(&ctx, use_path, text.len ? text.s : "", text.len, &pg,
   5609                   ctx.diag)) {
   5610     gram_flush_diag(&ctx);
   5611     mem_release(&ctx.mem);
   5612     return KIT_ERR;
   5613   }
   5614   Builder* b = kit_gram_builder_new(&ctx, pg);
   5615   b->multiline = opts->multiline ? 1 : 0;
   5616   b->lexer_standalone = opts->lexer_standalone ? 1 : 0;
   5617   b->fold_keywords = opts->fold_keywords ? 1 : 0;
   5618   b->position_lazy = opts->position_lazy ? 1 : 0;
   5619   b->parser_codegen = opts->parser_codegen ? 1 : 0;
   5620   b->parser_recover = opts->parser_recover ? 1 : 0;
   5621   b = kit_gram_builder_build(b);
   5622   if (b->fold_keywords && !b->lexer_standalone) {
   5623     Loc loc = {0};
   5624     kit_gram_error(&ctx, loc, "fold-keywords requires --lexer-standalone");
   5625   }
   5626   if (b->position_lazy) {
   5627     Loc loc = {0};
   5628     if (!b->lexer_standalone)
   5629       kit_gram_error(&ctx, loc, "position: lazy requires --lexer-standalone");
   5630     if (b->parser_codegen)
   5631       kit_gram_error(&ctx, loc,
   5632                     "position: lazy is not supported with --parser-codegen");
   5633     for (size_t i = 0; i < b->nlex_dfas; i++)
   5634       if (b->lex_dfas[i].start_text || b->lex_dfas[i].start_line)
   5635         kit_gram_error(&ctx, loc,
   5636                       "position: lazy is incompatible with ^ / \\A start "
   5637                       "anchors (they need column tracking)");
   5638   }
   5639   KitGramCompiled* c = xcalloc(&ctx, 1, sizeof *c);
   5640   c->b = b;
   5641   c->kit = kit;
   5642   c->path = xstrdup(&ctx, use_path);
   5643   if (opts->name)
   5644     c->name = xstrdup(&ctx, opts->name);
   5645   else
   5646     c->name = path_stem(&ctx, use_path);
   5647   materialize_compiled(&ctx, c);
   5648   b->ctx = NULL;
   5649 
   5650   c->mem = ctx.mem;
   5651   ctx.mem.head = ctx.mem.cur = NULL;
   5652   *out = c;
   5653   return KIT_OK;
   5654 }
   5655 
   5656 void kit_gram_free(KitGramCompiled* c) {
   5657   if (!c) return;
   5658   GramgenMem mem = c->mem;
   5659   mem_release(&mem);
   5660 }
   5661 
   5662 const KitGramGrammar* kit_gram_parser_grammar(const KitGramCompiled* c) {
   5663   return c ? &c->grammar : NULL;
   5664 }
   5665 
   5666 const KitGramLexGrammar* kit_gram_lexer_grammar(const KitGramCompiled* c) {
   5667   return (c && c->has_lex) ? &c->lexers[c->main_lexer].grammar : NULL;
   5668 }
   5669 
   5670 size_t kit_gram_lexer_count(const KitGramCompiled* c) {
   5671   return c ? c->nlexers : 0;
   5672 }
   5673 
   5674 const char* kit_gram_lexer_name(const KitGramCompiled* c, size_t i) {
   5675   return (c && i < c->nlexers) ? c->lexers[i].name : NULL;
   5676 }
   5677 
   5678 const KitGramLexGrammar* kit_gram_lexer_grammar_at(const KitGramCompiled* c,
   5679                                                  size_t i) {
   5680   return (c && i < c->nlexers) ? &c->lexers[i].grammar : NULL;
   5681 }
   5682 
   5683 bool kit_gram_find_lexer(const KitGramCompiled* c, const char* name,
   5684                         size_t* out) {
   5685   if (!c || !name) return false;
   5686   for (size_t i = 0; i < c->nlexers; i++) {
   5687     if (strcmp(c->lexers[i].name, name) == 0) {
   5688       if (out) *out = i;
   5689       return true;
   5690     }
   5691   }
   5692   return false;
   5693 }
   5694 
   5695 /* Single-pattern convenience: wrap the pattern into a one-rule grammar and
   5696  * compile it with the existing pipeline, so the lexer DFA, anchors, longest
   5697  * match, and priority logic are all reused with no special casing. */
   5698 #define GRAMREGEX_TOKEN "GRAMRE"
   5699 KitStatus kit_gram_regex_compile(const KitContext* kit, KitSlice pattern,
   5700                                  const KitGramOptions* opts,
   5701                                  KitGramCompiled** out) {
   5702   if (out) *out = NULL;
   5703   if (!kit || !kit->heap || !opts || !out) return KIT_INVALID;
   5704   if (!pattern.s && pattern.len) return KIT_INVALID;
   5705   const char* head = "%lex {\n" GRAMREGEX_TOKEN " = ";
   5706   const char* tail = ";\n}\nllre = " GRAMREGEX_TOKEN ";\n";
   5707   size_t hlen = strlen(head), plen = pattern.len, tlen = strlen(tail);
   5708   size_t len = hlen + plen + tlen;
   5709   KitHeap* h = kit->heap;
   5710   char* text = h->alloc(h, len + 1, 1);
   5711   if (!text) {
   5712     kit_ctx_diagf(kit, "out of memory");
   5713     return KIT_NOMEM;
   5714   }
   5715   memcpy(text, head, hlen);
   5716   if (plen) memcpy(text + hlen, pattern.s, plen);
   5717   memcpy(text + hlen + plen, tail, tlen);
   5718   text[len] = '\0';
   5719   KitStatus st =
   5720       kit_gram_compile_text(kit, (KitSlice){.s = text, .len = len},
   5721                             KIT_SLICE_LIT("<regex>"), opts, out);
   5722   h->free(h, text, len + 1);
   5723   return st;
   5724 }
   5725 
   5726 const KitGramLexGrammar* kit_gram_regex_grammar(const KitGramCompiled* re) {
   5727   return kit_gram_lexer_grammar(re);
   5728 }
   5729 
   5730 KitGramTokenKind kit_gram_regex_kind(const KitGramCompiled* re) {
   5731   KitGramTokenKind kind = 0;
   5732   if (re) kit_gram_find_token(re, GRAMREGEX_TOKEN, &kind);
   5733   return kind;
   5734 }
   5735 
   5736 size_t kit_gram_token_count(const KitGramCompiled* c) {
   5737   return c ? c->b->ntokens : 0;
   5738 }
   5739 
   5740 const char* kit_gram_token_name(const KitGramCompiled* c, KitGramTokenKind tok) {
   5741   return (c && tok < c->b->ntokens) ? c->b->tokens[tok].name : NULL;
   5742 }
   5743 
   5744 const char* kit_gram_token_display(const KitGramCompiled* c,
   5745                                   KitGramTokenKind tok) {
   5746   return (c && tok < c->b->ntokens) ? c->b->tokens[tok].display.s : NULL;
   5747 }
   5748 
   5749 bool kit_gram_find_token(const KitGramCompiled* c, const char* name,
   5750                         KitGramTokenKind* out) {
   5751   if (!c || !name) return false;
   5752   for (size_t i = 0; i < c->b->ntokens; i++) {
   5753     if (strcmp(c->b->tokens[i].name, name) == 0) {
   5754       if (out) *out = (KitGramTokenKind)i;
   5755       return true;
   5756     }
   5757   }
   5758   return false;
   5759 }
   5760 
   5761 size_t kit_gram_rule_count(const KitGramCompiled* c) {
   5762   return c ? c->b->public_count : 0;
   5763 }
   5764 
   5765 const char* kit_gram_rule_name(const KitGramCompiled* c, KitGramRuleId rule) {
   5766   return (c && rule < c->b->public_count) ? c->b->rules[rule].name : NULL;
   5767 }
   5768 
   5769 bool kit_gram_find_rule(const KitGramCompiled* c, const char* name,
   5770                        KitGramRuleId* out) {
   5771   if (!c || !name) return false;
   5772   for (size_t i = 0; i < c->b->public_count; i++) {
   5773     if (strcmp(c->b->rules[i].name, name) == 0) {
   5774       if (out) *out = (KitGramRuleId)i;
   5775       return true;
   5776     }
   5777   }
   5778   return false;
   5779 }
   5780 
   5781 /* ---- token machine (%machine) introspection + sampling --------------------
   5782  */
   5783 
   5784 size_t kit_gram_machine_count(const KitGramCompiled* c) {
   5785   return c ? c->b->nmachines : 0;
   5786 }
   5787 
   5788 const char* kit_gram_machine_name(const KitGramCompiled* c, size_t mi) {
   5789   return (c && mi < c->b->nmachines) ? c->b->machines[mi].name : NULL;
   5790 }
   5791 
   5792 bool kit_gram_find_machine(const KitGramCompiled* c, const char* name,
   5793                           size_t* out) {
   5794   if (!c || !name) return false;
   5795   for (size_t i = 0; i < c->b->nmachines; i++) {
   5796     if (strcmp(c->b->machines[i].name, name) == 0) {
   5797       if (out) *out = i;
   5798       return true;
   5799     }
   5800   }
   5801   return false;
   5802 }
   5803 
   5804 size_t kit_gram_machine_rule_count(const KitGramCompiled* c, size_t mi) {
   5805   return (c && mi < c->b->nmachines) ? c->b->machines[mi].nrecognizers : 0;
   5806 }
   5807 
   5808 const char* kit_gram_machine_rule_name(const KitGramCompiled* c, size_t mi,
   5809                                       size_t rule) {
   5810   if (!c || mi >= c->b->nmachines) return NULL;
   5811   const LexDFA* m = &c->b->machines[mi];
   5812   return rule < m->nrecognizers ? m->recognizers[rule].name : NULL;
   5813 }
   5814 
   5815 KitGramGenStatus kit_gram_machine_generate(const KitGramCompiled* c, size_t mi,
   5816                                          size_t rule, uint64_t* seed,
   5817                                          double stop_prob, size_t max_repeat,
   5818                                          size_t max_tokens, const char** out,
   5819                                          size_t cap, size_t* n) {
   5820   if (!c || mi >= c->b->nmachines) {
   5821     if (n) *n = 0; /* header documents *n as the produced length on every return */
   5822     return KIT_GRAM_GEN_ERROR;
   5823   }
   5824   /* The compiled object detached its context; sampling allocates only a small
   5825    * complement scratch, so borrow a temporary list-arena context. */
   5826   GramgenContext ctx;
   5827   memset(&ctx, 0, sizeof ctx);
   5828   ctx.kit = c->kit;
   5829   ctx.mem.heap = c->mem.heap;
   5830   ctx.can_jump = 0;
   5831   KitGramGenStatus st =
   5832       kit_gram_machine_sample(&ctx, &c->b->machines[mi], rule, seed, stop_prob,
   5833                              max_repeat, max_tokens, out, cap, n);
   5834   mem_release(&ctx.mem);
   5835   return st;
   5836 }
   5837 
   5838 KitStatus kit_gram_emit_c(const KitGramCompiled* c,
   5839                           const KitGramEmitOptions* opts, KitWriter* header,
   5840                           KitWriter* source) {
   5841   if (!c || !opts || !opts->header_path || !opts->source_path ||
   5842       !opts->prefix || !header || !source)
   5843     return KIT_INVALID;
   5844 
   5845   GramgenContext ctx;
   5846   memset(&ctx, 0, sizeof ctx);
   5847   ctx.kit = c->kit;
   5848   ctx.mem.heap = ((KitGramCompiled*)c)->mem.heap;
   5849   ctx.diag = &ctx.diag_storage;
   5850   ctx.can_jump = 1;
   5851 
   5852   if (setjmp(ctx.jmp)) {
   5853     gram_flush_diag(&ctx);
   5854     mem_release(&ctx.mem);
   5855     return KIT_ERR;
   5856   }
   5857 
   5858   validate_prefix_arg(&ctx, opts->prefix, c->path);
   5859   Buf h;
   5860   Buf src;
   5861   buf_init_writer(&ctx, &h, header, opts->header_path);
   5862   buf_init_writer(&ctx, &src, source, opts->source_path);
   5863   emit_header_text(&h, c->b, c->path, opts->header_path, opts->prefix);
   5864   emit_c_text(&src, c->b, c->path, opts->header_path, opts->prefix);
   5865 
   5866   mem_release(&ctx.mem);
   5867   return KIT_OK;
   5868 }