kit

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

lex_tokens.c (33538B)


      1 /* kit_gram_lex_tokens.c - token-alphabet (%machine) lexer pipeline.
      2  *
      3  * A %machine declares a regular language whose symbols are abstract named
      4  * tokens (not bytes/scalars). The combinator layer (concat / | / ( ) / ? * +
      5  * {n,m} / %def) is alphabet-independent, so this TU supplies only the *leaves*
      6  * (a symbol
      7  * -> [k,k]; a [ A B ] set -> a union of singletons; . -> [0,NSYM-1]) and a
      8  * direct atom-DFA emit, feeding the shared range-set NFA + one determinization
      9  * in gen/kit_gram_lex_range.c. There is no UTF-8 lowering: the symbol id IS the
     10  * atom, so the determinized atom DFA is the final table (after minimize +
     11  * relabel).
     12  *
     13  * Codegen-only: a token machine never materializes an KitGramLexGrammar, so this
     14  * carries its uint16 symbol->class map / per-state liveness in
     15  * generator-internal LexDFA fields and never crosses the runtime ABI. Non-gated
     16  * (the token alphabet is unrelated to Unicode), so it survives
     17  * KIT_GRAM_NO_UNICODE. See doc/DESIGN.md "Token machines". */
     18 #include "internal.h"
     19 
     20 /* ---- symbol alphabet ---------------------------------------------------- */
     21 
     22 typedef struct {
     23   GramgenContext* ctx;
     24   char** names; /* interned symbol name by id (first-use order) */
     25   size_t n, cap;
     26   NameIndexMap index; /* interned name -> id */
     27 } TokenAlphabet;
     28 
     29 /* Intern a symbol name and return its id, assigning a fresh id (in first-use
     30  * order) on first sight. `name` must be an interned, NUL-terminated pointer. */
     31 static int token_alpha_intern(TokenAlphabet* a, const char* name) {
     32   int id;
     33   if (name_index_find(&a->index, name, &id)) return id;
     34   if (a->n == a->cap) {
     35     a->cap = a->cap ? a->cap * 2 : 16;
     36     a->names = xrealloc(a->ctx, a->names, a->cap * sizeof *a->names);
     37   }
     38   id = (int)a->n;
     39   a->names[a->n++] = (char*)name;
     40   name_index_put(a->ctx, &a->index, name, id);
     41   return id;
     42 }
     43 
     44 /* Tokenize a `[ A B C ]` / `[^ A B ]` symbol set into its raw member
     45  * identifiers
     46  * ([A-Za-z0-9_]+, interned) and the `[^ … ]` complement flag. No case check and
     47  * no empty check: callers impose their own (kit_gram_machine_class_names
     48  * requires UPPERCASE leaves; the set resolver also accepts lowercase set
     49  * references). Caller frees *names. */
     50 static char* const* machine_class_raw(GramgenContext* ctx, Str raw, Loc loc,
     51                                       size_t* nout, int* complement) {
     52   if (raw.len < 2 || raw.s[0] != '[' || raw.s[raw.len - 1] != ']')
     53     kit_gram_error(ctx, loc, "malformed symbol set");
     54   size_t i = 1, end = raw.len - 1;
     55   *complement = 0;
     56   if (i < end && raw.s[i] == '^') {
     57     *complement = 1;
     58     i++;
     59   }
     60   char** names = NULL;
     61   size_t n = 0, cap = 0;
     62   while (i < end) {
     63     unsigned char c = (unsigned char)raw.s[i];
     64     if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == ',') {
     65       i++;
     66       continue;
     67     }
     68     size_t start = i;
     69     while (i < end) {
     70       unsigned char d = (unsigned char)raw.s[i];
     71       if ((d >= 'A' && d <= 'Z') || (d >= 'a' && d <= 'z') ||
     72           (d >= '0' && d <= '9') || d == '_')
     73         i++;
     74       else
     75         break;
     76     }
     77     if (i == start) kit_gram_error(ctx, loc, "invalid symbol in token set");
     78     char* name = intern_len(ctx, raw.s + start, i - start);
     79     if (n == cap) {
     80       cap = cap ? cap * 2 : 8;
     81       names = xrealloc(ctx, names, cap * sizeof *names);
     82     }
     83     names[n++] = name;
     84   }
     85   *nout = n;
     86   return (char* const*)names;
     87 }
     88 
     89 /* Parse a `[ A B C ]` / `[^ A B ]` symbol set into its member names (interned,
     90  * validated UPPERCASE), returning the count and the `[^ … ]` complement flag.
     91  * Pure: it neither interns into an alphabet nor resolves the complement, so it
     92  * is shared by NFA construction and the post-compile sampler. Any lowercase
     93  * set reference was already inlined to UPPERCASE by kit_gram_compile_machine, so
     94  * a lowercase member here is an internal/grammar error. Caller frees *names. */
     95 char* const* kit_gram_machine_class_names(GramgenContext* ctx, Str raw, Loc loc,
     96                                          size_t* nout, int* complement) {
     97   size_t n = 0;
     98   char* const* names = machine_class_raw(ctx, raw, loc, &n, complement);
     99   for (size_t k = 0; k < n; k++)
    100     if (!is_token_name(names[k]))
    101       kit_gram_error(ctx, loc,
    102                     "token-set member '%s' must be an UPPERCASE symbol name",
    103                     names[k]);
    104   if (!n && !*complement) kit_gram_error(ctx, loc, "empty symbol set");
    105   *nout = n;
    106   return names;
    107 }
    108 
    109 /* Parse a symbol set and intern each member into the alphabet, appending its id
    110  * to *ids (caller frees). Sets *complement for the `[^ ... ]` form. */
    111 static void token_parse_class(GramgenContext* ctx, TokenAlphabet* alpha,
    112                               Str raw, Loc loc, int** ids, size_t* nids,
    113                               size_t* cap_ids, int* complement) {
    114   size_t nm = 0;
    115   char* const* names =
    116       kit_gram_machine_class_names(ctx, raw, loc, &nm, complement);
    117   for (size_t k = 0; k < nm; k++) {
    118     int id = token_alpha_intern(alpha, names[k]);
    119     if (*nids == *cap_ids) {
    120       *cap_ids = *cap_ids ? *cap_ids * 2 : 8;
    121       *ids = xrealloc(ctx, *ids, *cap_ids * sizeof **ids);
    122     }
    123     (*ids)[(*nids)++] = id;
    124   }
    125   xfree(ctx, (void*)names);
    126 }
    127 
    128 /* ---- alphabet collection (pass 1) --------------------------------------- */
    129 
    130 /* DFS the desugared AST assigning symbol ids in first-use order, so `.` /
    131  * complement leaves (pass 2) see the final NSYM. UPPERCASE LEX_NAME survives
    132  * desugaring as a symbol; LEX_CLASS holds a `[ … ]` symbol set. */
    133 static void token_collect_alt(GramgenContext* ctx, TokenAlphabet* alpha,
    134                               AstLexAlt* alt);
    135 
    136 static void token_collect_node(GramgenContext* ctx, TokenAlphabet* alpha,
    137                                AstLexNode* node) {
    138   switch (node->kind) {
    139     case LEX_NAME:
    140       token_alpha_intern(alpha, node->value.s);
    141       break;
    142     case LEX_CLASS: {
    143       int* ids = NULL;
    144       size_t nids = 0, cap = 0, complement = 0;
    145       token_parse_class(ctx, alpha, node->value, node->loc, &ids, &nids, &cap,
    146                         (int*)&complement);
    147       xfree(ctx, ids);
    148       break;
    149     }
    150     case LEX_ANY:
    151       break;
    152     case LEX_GROUP:
    153     case LEX_OPT:
    154     case LEX_REP:
    155       token_collect_alt(ctx, alpha, node->alts);
    156       break;
    157     case LEX_LITERAL:
    158       kit_gram_error(ctx, node->loc,
    159                     "string literals are not allowed in %%machine (symbols are "
    160                     "UPPERCASE names)");
    161       break;
    162     case LEX_PROP:
    163       kit_gram_error(ctx, node->loc,
    164                     "Unicode properties are not allowed in %%machine");
    165       break;
    166     case LEX_ANCHOR:
    167       kit_gram_error(ctx, node->loc, "anchors are not supported in %%machine");
    168       break;
    169     case LEX_REPEAT:
    170       kit_gram_error(ctx, node->loc, "internal: %%machine node not desugared");
    171       break;
    172   }
    173 }
    174 
    175 static void token_collect_alt(GramgenContext* ctx, TokenAlphabet* alpha,
    176                               AstLexAlt* alt) {
    177   for (size_t i = 0; i < alt->nseqs; i++) {
    178     AstLexSeq* seq = alt->seqs[i];
    179     for (size_t j = 0; j < seq->nitems; j++)
    180       token_collect_node(ctx, alpha, seq->items[j]);
    181   }
    182 }
    183 
    184 /* ---- symbol set -> ScalarSet -------------------------------------------- */
    185 
    186 static int int_cmp_asc(const void* a, const void* b) {
    187   int x = *(const int*)a, y = *(const int*)b;
    188   return (x > y) - (x < y);
    189 }
    190 
    191 /* Canonical (sorted, merged, non-adjacent) ScalarSet from a list of symbol ids.
    192  */
    193 static ScalarSet token_set_from_ids(GramgenContext* ctx, int* ids, size_t n) {
    194   ScalarSet out = {.ctx = ctx};
    195   if (!n) return out;
    196   qsort(ids, n, sizeof *ids, int_cmp_asc);
    197   size_t cap = 0;
    198   for (size_t i = 0; i < n;) {
    199     uint32_t lo = (uint32_t)ids[i], hi = lo;
    200     size_t j = i + 1;
    201     while (j < n && (uint32_t)ids[j] <= hi + 1u) {
    202       if ((uint32_t)ids[j] > hi) hi = (uint32_t)ids[j];
    203       j++;
    204     }
    205     if (out.n == cap) {
    206       cap = cap ? cap * 2 : 4;
    207       out.v = xrealloc(ctx, out.v, cap * sizeof *out.v);
    208     }
    209     out.v[out.n++] = (ScalarRange){lo, hi};
    210     i = j;
    211   }
    212   out.cap = cap;
    213   return out;
    214 }
    215 
    216 /* Full symbol universe [0, nsym-1]. */
    217 static ScalarSet token_set_universe(GramgenContext* ctx, uint16_t nsym) {
    218   ScalarSet out = {.ctx = ctx};
    219   out.v = xmalloc(ctx, sizeof *out.v);
    220   out.v[0] = (ScalarRange){0, (uint32_t)nsym - 1u};
    221   out.n = 1;
    222   out.cap = 1;
    223   return out;
    224 }
    225 
    226 /* Complement of a canonical id-set over [0, nsym-1] (the gaps). */
    227 static ScalarSet token_set_complement(GramgenContext* ctx, const ScalarSet* in,
    228                                       uint16_t nsym) {
    229   ScalarSet out = {.ctx = ctx};
    230   size_t cap = 0;
    231   uint32_t cur = 0;
    232   for (size_t i = 0; i < in->n; i++) {
    233     if (in->v[i].lo > cur) {
    234       if (out.n == cap) {
    235         cap = cap ? cap * 2 : 4;
    236         out.v = xrealloc(ctx, out.v, cap * sizeof *out.v);
    237       }
    238       out.v[out.n++] = (ScalarRange){cur, in->v[i].lo - 1u};
    239     }
    240     cur = in->v[i].hi + 1u;
    241   }
    242   if (cur <= (uint32_t)nsym - 1u) {
    243     if (out.n == cap) {
    244       cap = cap ? cap * 2 : 4;
    245       out.v = xrealloc(ctx, out.v, cap * sizeof *out.v);
    246     }
    247     out.v[out.n++] = (ScalarRange){cur, (uint32_t)nsym - 1u};
    248   }
    249   out.cap = cap;
    250   return out;
    251 }
    252 
    253 /* ---- token NFA leaves (the token instance of LexAlphabet) ---------------- */
    254 
    255 /* Per-build leaf state: the (already collected) alphabet plus NSYM, reached via
    256  * LexAlphabet.ud. Symbol interning here is idempotent — pass 1 assigned every
    257  * id. */
    258 typedef struct {
    259   TokenAlphabet* alpha;
    260   uint16_t nsym;
    261 } TokenLeafCtx;
    262 
    263 static NfaFrag token_leaf_name(ScalarNfaBuilder* nfa, const LexAlphabet* a,
    264                                AstLexNode* node) {
    265   TokenLeafCtx* t = a->ud;
    266   int id = token_alpha_intern(t->alpha, node->value.s);
    267   return scalar_nfa_scalar_set(
    268       nfa, scalar_set_from_single_value(nfa->ctx, (uint32_t)id), node->loc);
    269 }
    270 static NfaFrag token_leaf_class(ScalarNfaBuilder* nfa, const LexAlphabet* a,
    271                                 AstLexNode* node) {
    272   TokenLeafCtx* t = a->ud;
    273   GramgenContext* ctx = nfa->ctx;
    274   int* ids = NULL;
    275   size_t nids = 0, cap = 0;
    276   int complement = 0;
    277   token_parse_class(ctx, t->alpha, node->value, node->loc, &ids, &nids, &cap,
    278                     &complement);
    279   ScalarSet set = token_set_from_ids(ctx, ids, nids);
    280   xfree(ctx, ids);
    281   if (complement) {
    282     ScalarSet comp = token_set_complement(ctx, &set, t->nsym);
    283     xfree(ctx, set.v);
    284     set = comp;
    285   }
    286   return scalar_nfa_scalar_set(nfa, set, node->loc);
    287 }
    288 static NfaFrag token_leaf_any(ScalarNfaBuilder* nfa, const LexAlphabet* a,
    289                               AstLexNode* node) {
    290   TokenLeafCtx* t = a->ud;
    291   return scalar_nfa_scalar_set(nfa, token_set_universe(nfa->ctx, t->nsym),
    292                                node->loc);
    293 }
    294 /* String literals and Unicode properties are rejected earlier (token_collect),
    295  * so these never fire; keep them as a clear internal guard. */
    296 static NfaFrag token_leaf_invalid(ScalarNfaBuilder* nfa, const LexAlphabet* a,
    297                                   AstLexNode* node) {
    298   (void)a;
    299   kit_gram_error(nfa->ctx, node->loc, "internal: invalid %%machine leaf");
    300   return scalar_nfa_empty(nfa);
    301 }
    302 
    303 /* ---- per-state liveness ------------------------------------------------- */
    304 
    305 /* live[s] = an accepting state is reachable from s over non-dead edges. Reverse
    306  * reachability from the accepting set, by iterate-to-fixpoint (nstates small).
    307  */
    308 static uint8_t* token_compute_live(GramgenContext* ctx, const uint16_t* trans,
    309                                    const uint16_t* accept, uint16_t nstates,
    310                                    uint16_t nclasses) {
    311   uint8_t* live = xcalloc(ctx, nstates ? nstates : 1, sizeof *live);
    312   for (uint16_t s = 0; s < nstates; s++)
    313     if (accept[s] != UINT16_MAX) live[s] = 1;
    314   int changed = 1;
    315   while (changed) {
    316     changed = 0;
    317     for (uint16_t s = 0; s < nstates; s++) {
    318       if (live[s]) continue;
    319       const uint16_t* row = &trans[(size_t)s * nclasses];
    320       for (uint16_t c = 0; c < nclasses; c++) {
    321         uint16_t t = row[c];
    322         if (t != UINT16_MAX && live[t]) {
    323           live[s] = 1;
    324           changed = 1;
    325           break;
    326         }
    327       }
    328     }
    329   }
    330   return live;
    331 }
    332 
    333 /* ---- named symbol sets + bracket set algebra ----------------------------
    334  * Inside a %machine a `[ … ]` is a set expression with the same algebra the
    335  * UTF-8 character classes use (gen/kit_gram_lex_unicode.c): atoms are UPPERCASE
    336  * symbols and lowercase %def set references; juxtaposition is union, `&&` is
    337  * intersection, `--` is difference, and a leading `^` complements the whole
    338  * class. A %def used as a set names a set expression too (its body, a union
    339  * written with `|`). Composition is by named set, not by literal nesting — the
    340  * meta lexer's class token ends at the first `]`, just as for UTF-8 classes.
    341  *
    342  * Resolution works over symbol *names*, never ids, by carrying a complement
    343  * flag (an RSet is either a finite name list or its complement over the
    344  * alphabet) and applying De Morgan identities — so the alphabet need not exist
    345  * yet. Each class resolves to one RSet which is then re-emitted as plain `[ …
    346  * ]` / `[^ … ]` text; the NFA leaf, the alphabet pass, and both samplers
    347  * re-parse that text and only ever see UPPERCASE symbols (the leaf still
    348  * applies a top-level complement over the final alphabet). */
    349 
    350 /* complement ? (alphabet \ names) : names. `names` is deduped, interned. */
    351 typedef struct {
    352   int complement;
    353   char** names;
    354   size_t n, cap;
    355 } RSet;
    356 
    357 typedef struct {
    358   RSet set;
    359   uint8_t state; /* 0 unseen, 1 resolving (cycle guard), 2 resolved */
    360 } SetSlot;
    361 
    362 typedef struct {
    363   GramgenContext* ctx;
    364   AstLexLine** defs; /* the machine's %def lines (candidate named sets) */
    365   size_t ndefs;
    366   SetSlot* slots; /* parallel to defs; memoized resolutions */
    367 } MachineSets;
    368 
    369 static int nl_contains(char* const* a, size_t n, const char* name) {
    370   for (size_t i = 0; i < n; i++)
    371     if (strcmp(a[i], name) == 0) return 1;
    372   return 0;
    373 }
    374 
    375 static void rset_push(GramgenContext* ctx, RSet* r, char* name) {
    376   if (nl_contains(r->names, r->n, name)) return;
    377   if (r->n == r->cap) {
    378     r->cap = r->cap ? r->cap * 2 : 8;
    379     r->names = xrealloc(ctx, r->names, r->cap * sizeof *r->names);
    380   }
    381   r->names[r->n++] = name;
    382 }
    383 
    384 static void rset_free(GramgenContext* ctx, RSet* r) {
    385   if (r->names) xfree(ctx, r->names);
    386   r->names = NULL;
    387   r->n = r->cap = 0;
    388 }
    389 
    390 static RSet rset_single(GramgenContext* ctx, char* name) {
    391   RSet r = {0};
    392   rset_push(ctx, &r, name);
    393   return r;
    394 }
    395 
    396 static RSet rset_copy(GramgenContext* ctx, const RSet* s) {
    397   RSet r = {.complement = s->complement};
    398   for (size_t i = 0; i < s->n; i++) rset_push(ctx, &r, s->names[i]);
    399   return r;
    400 }
    401 
    402 /* Positive-list primitives: union / intersection / difference of two finite
    403  * name lists, each returning a fresh positive RSet. */
    404 static RSet pl_union(GramgenContext* ctx, const RSet* a, const RSet* b) {
    405   RSet r = {0};
    406   for (size_t i = 0; i < a->n; i++) rset_push(ctx, &r, a->names[i]);
    407   for (size_t i = 0; i < b->n; i++) rset_push(ctx, &r, b->names[i]);
    408   return r;
    409 }
    410 static RSet pl_intersect(GramgenContext* ctx, const RSet* a, const RSet* b) {
    411   RSet r = {0};
    412   for (size_t i = 0; i < a->n; i++)
    413     if (nl_contains(b->names, b->n, a->names[i]))
    414       rset_push(ctx, &r, a->names[i]);
    415   return r;
    416 }
    417 static RSet pl_diff(GramgenContext* ctx, const RSet* a, const RSet* b) {
    418   RSet r = {0};
    419   for (size_t i = 0; i < a->n; i++)
    420     if (!nl_contains(b->names, b->n, a->names[i]))
    421       rset_push(ctx, &r, a->names[i]);
    422   return r;
    423 }
    424 
    425 /* Set algebra over the complement-tagged representation (De Morgan). C(x) is
    426  * the complement of x over the alphabet; the four cases reduce every op to the
    427  * positive-list primitives without ever materializing the alphabet. */
    428 static RSet rset_union(GramgenContext* ctx, const RSet* a, const RSet* b) {
    429   if (!a->complement && !b->complement) return pl_union(ctx, a, b);
    430   RSet r;
    431   if (!a->complement) /* P(a) ∪ C(b) = C(b\a) */
    432     r = pl_diff(ctx, b, a);
    433   else if (!b->complement) /* C(a) ∪ P(b) = C(a\b) */
    434     r = pl_diff(ctx, a, b);
    435   else /* C(a) ∪ C(b) = C(a∩b) */
    436     r = pl_intersect(ctx, a, b);
    437   r.complement = 1;
    438   return r;
    439 }
    440 static RSet rset_intersect(GramgenContext* ctx, const RSet* a, const RSet* b) {
    441   if (!a->complement && !b->complement) return pl_intersect(ctx, a, b);
    442   if (!a->complement) return pl_diff(ctx, a, b); /* P(a) ∩ C(b) = a\b */
    443   if (!b->complement) return pl_diff(ctx, b, a); /* C(a) ∩ P(b) = b\a */
    444   RSet r = pl_union(ctx, a, b);                  /* C(a) ∩ C(b) = C(a∪b) */
    445   r.complement = 1;
    446   return r;
    447 }
    448 static RSet rset_diff(GramgenContext* ctx, const RSet* a, const RSet* b) {
    449   if (!a->complement && !b->complement) return pl_diff(ctx, a, b);
    450   if (!a->complement) return pl_intersect(ctx, a, b); /* P(a) \ C(b) = a∩b */
    451   if (!b->complement) {                               /* C(a) \ P(b) = C(a∪b) */
    452     RSet r = pl_union(ctx, a, b);
    453     r.complement = 1;
    454     return r;
    455   }
    456   return pl_diff(ctx, b, a); /* C(a) \ C(b) = b\a */
    457 }
    458 
    459 static int machine_sets_find(const MachineSets* st, const char* name) {
    460   for (size_t i = 0; i < st->ndefs; i++)
    461     if (strcmp(st->defs[i]->name, name) == 0) return (int)i;
    462   return -1;
    463 }
    464 
    465 static const RSet* resolve_set(MachineSets* st, size_t idx);
    466 
    467 /* ---- bracket set-expression parser (a char cursor over the `[ … ]` text) --
    468  */
    469 
    470 typedef struct {
    471   MachineSets* st;
    472   const char* s;
    473   size_t i, len;
    474   Loc loc;
    475 } ClassParser;
    476 
    477 static int cp_peek(const ClassParser* p, size_t k) {
    478   return p->i + k < p->len ? (unsigned char)p->s[p->i + k] : 0;
    479 }
    480 static void cp_skip_ws(ClassParser* p) {
    481   while (p->i < p->len) {
    482     char c = p->s[p->i];
    483     if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == ',')
    484       p->i++;
    485     else
    486       break;
    487   }
    488 }
    489 
    490 /* atom = UPPERCASE symbol | lowercase set reference. (There is no literal
    491  * nested
    492  * `[ … ]`: the meta lexer's class token ends at the first `]`, exactly as in
    493  * the UTF-8 classes; compose with named %def sets instead.) */
    494 static RSet cp_atom(ClassParser* p) {
    495   cp_skip_ws(p);
    496   size_t start = p->i;
    497   while (p->i < p->len) {
    498     unsigned char d = (unsigned char)p->s[p->i];
    499     if ((d >= 'A' && d <= 'Z') || (d >= 'a' && d <= 'z') ||
    500         (d >= '0' && d <= '9') || d == '_')
    501       p->i++;
    502     else
    503       break;
    504   }
    505   if (p->i == start)
    506     kit_gram_error(p->st->ctx, p->loc, "invalid symbol in token set");
    507   char* name = intern_len(p->st->ctx, p->s + start, p->i - start);
    508   if (is_token_name(name)) return rset_single(p->st->ctx, name);
    509   int idx = machine_sets_find(p->st, name);
    510   if (idx < 0)
    511     kit_gram_error(p->st->ctx, p->loc, "unknown symbol set '%s'", name);
    512   return rset_copy(p->st->ctx, resolve_set(p->st, (size_t)idx));
    513 }
    514 
    515 /* union = atom+ : juxtaposition, stopping at `]`, `&&`, `--`, or end. */
    516 static RSet cp_union(ClassParser* p, int* saw) {
    517   RSet out = {0};
    518   *saw = 0;
    519   for (;;) {
    520     cp_skip_ws(p);
    521     int c = cp_peek(p, 0);
    522     if (!c || c == ']') break;
    523     if (c == '&' && cp_peek(p, 1) == '&') break;
    524     if (c == '-' && cp_peek(p, 1) == '-') break;
    525     RSet item = cp_atom(p);
    526     if (!*saw) {
    527       out = item;
    528       *saw = 1;
    529     } else {
    530       RSet u = rset_union(p->st->ctx, &out, &item);
    531       rset_free(p->st->ctx, &out);
    532       rset_free(p->st->ctx, &item);
    533       out = u;
    534     }
    535   }
    536   return out;
    537 }
    538 
    539 /* expr = union ( ('&&' | '--') union )* : intersection/difference, left to
    540  * right, both below union in precedence (matching the UTF-8 class parser). */
    541 static RSet cp_expr(ClassParser* p) {
    542   int saw;
    543   RSet out = cp_union(p, &saw);
    544   for (;;) {
    545     cp_skip_ws(p);
    546     int c = cp_peek(p, 0);
    547     int is_and = c == '&' && cp_peek(p, 1) == '&';
    548     int is_sub = c == '-' && cp_peek(p, 1) == '-';
    549     if (!is_and && !is_sub) break;
    550     p->i += 2;
    551     int saw_rhs;
    552     RSet rhs = cp_union(p, &saw_rhs);
    553     if (!saw_rhs)
    554       kit_gram_error(p->st->ctx, p->loc, "expected a symbol set after '%s'",
    555                     is_and ? "&&" : "--");
    556     RSet r = is_and ? rset_intersect(p->st->ctx, &out, &rhs)
    557                     : rset_diff(p->st->ctx, &out, &rhs);
    558     rset_free(p->st->ctx, &out);
    559     rset_free(p->st->ctx, &rhs);
    560     out = r;
    561   }
    562   return out;
    563 }
    564 
    565 static RSet cp_class(ClassParser* p) {
    566   cp_skip_ws(p);
    567   if (cp_peek(p, 0) != '[')
    568     kit_gram_error(p->st->ctx, p->loc, "malformed symbol set");
    569   p->i++; /* '[' */
    570   cp_skip_ws(p);
    571   int neg = 0;
    572   if (cp_peek(p, 0) == '^') {
    573     neg = 1;
    574     p->i++;
    575   }
    576   RSet r = cp_expr(p);
    577   cp_skip_ws(p);
    578   if (cp_peek(p, 0) != ']')
    579     kit_gram_error(p->st->ctx, p->loc, "malformed symbol set");
    580   p->i++; /* ']' */
    581   if (neg) r.complement = !r.complement;
    582   /* A positive class that resolves to nothing (e.g. `[ ]`, `[ A -- A ]`) is
    583    * empty; `[^ ]` is the universe and stays valid. */
    584   if (!r.complement && r.n == 0)
    585     kit_gram_error(p->st->ctx, p->loc, "empty symbol set");
    586   return r;
    587 }
    588 
    589 /* ---- %def-as-set resolution (the body AST, post-desugar) ----------------- */
    590 
    591 static RSet resolve_set_alt(MachineSets* st, const AstLexAlt* alt, Loc def_loc);
    592 
    593 static RSet resolve_set_atom(MachineSets* st, const AstLexNode* node,
    594                              Loc def_loc) {
    595   switch (node->kind) {
    596     case LEX_NAME:
    597       if (is_token_name(node->value.s))
    598         return rset_single(st->ctx, node->value.s);
    599       else {
    600         int idx = machine_sets_find(st, node->value.s);
    601         if (idx < 0)
    602           kit_gram_error(st->ctx, node->loc, "unknown symbol set '%s'",
    603                         node->value.s);
    604         return rset_copy(st->ctx, resolve_set(st, (size_t)idx));
    605       }
    606     case LEX_CLASS: {
    607       ClassParser p = {.st = st,
    608                        .s = node->value.s,
    609                        .i = 0,
    610                        .len = node->value.len,
    611                        .loc = node->loc};
    612       return cp_class(&p);
    613     }
    614     case LEX_GROUP: /* a parenthesized / desugared sub-union */
    615       return resolve_set_alt(st, node->alts, def_loc);
    616     default:
    617       kit_gram_error(st->ctx, node->loc,
    618                     "a set definition must be a union of symbols, sets, and "
    619                     "[ … ] classes");
    620       return (RSet){0}; /* unreachable */
    621   }
    622 }
    623 
    624 /* A set body is a union: each alternative (`|`) is a single set atom. */
    625 static RSet resolve_set_alt(MachineSets* st, const AstLexAlt* alt,
    626                             Loc def_loc) {
    627   RSet out = {0};
    628   int saw = 0;
    629   for (size_t i = 0; i < alt->nseqs; i++) {
    630     const AstLexSeq* seq = alt->seqs[i];
    631     if (seq->nitems != 1)
    632       kit_gram_error(
    633           st->ctx, def_loc,
    634           "a set definition must be a union of symbols and sets, not a "
    635           "sequence");
    636     RSet item = resolve_set_atom(st, seq->items[0], def_loc);
    637     if (!saw) {
    638       out = item;
    639       saw = 1;
    640     } else {
    641       RSet u = rset_union(st->ctx, &out, &item);
    642       rset_free(st->ctx, &out);
    643       rset_free(st->ctx, &item);
    644       out = u;
    645     }
    646   }
    647   return out;
    648 }
    649 
    650 static const RSet* resolve_set(MachineSets* st, size_t idx) {
    651   SetSlot* slot = &st->slots[idx];
    652   if (slot->state == 2) return &slot->set;
    653   if (slot->state == 1)
    654     kit_gram_error(st->ctx, st->defs[idx]->loc, "recursive set '%s'",
    655                   st->defs[idx]->name);
    656   slot->state = 1;
    657   slot->set = resolve_set_alt(st, st->defs[idx]->alts, st->defs[idx]->loc);
    658   slot->state = 2;
    659   return &slot->set;
    660 }
    661 
    662 /* Re-emit a class node's text as `[ … ]` / `[^ … ]` over its resolved members.
    663  */
    664 static void machine_class_rewrite(GramgenContext* ctx, AstLexNode* node,
    665                                   const RSet* r) {
    666   Buf b;
    667   buf_init(ctx, &b);
    668   buf_append(&b, r->complement ? "[^" : "[");
    669   for (size_t i = 0; i < r->n; i++) {
    670     buf_append(&b, " ");
    671     buf_append(&b, r->names[i]);
    672   }
    673   buf_append(&b, " ]");
    674   node->value = str_dup_len(ctx, b.s, b.len);
    675   xfree(ctx, b.s);
    676 }
    677 
    678 /* Resolve a class's set expression and re-emit it as plain symbol text. */
    679 static void machine_expand_class(MachineSets* st, AstLexNode* node) {
    680   ClassParser p = {.st = st,
    681                    .s = node->value.s,
    682                    .i = 0,
    683                    .len = node->value.len,
    684                    .loc = node->loc};
    685   RSet r = cp_class(&p);
    686   cp_skip_ws(&p);
    687   if (p.i != p.len)
    688     kit_gram_error(st->ctx, node->loc, "trailing characters in symbol set");
    689   machine_class_rewrite(st->ctx, node, &r);
    690   rset_free(st->ctx, &r);
    691 }
    692 
    693 static void machine_expand_alt(MachineSets* st, AstLexAlt* alt);
    694 
    695 static void machine_expand_node(MachineSets* st, AstLexNode* node) {
    696   switch (node->kind) {
    697     case LEX_CLASS:
    698       machine_expand_class(st, node);
    699       break;
    700     case LEX_GROUP:
    701     case LEX_OPT:
    702     case LEX_REP:
    703       machine_expand_alt(st, node->alts);
    704       break;
    705     default:
    706       break;
    707   }
    708 }
    709 
    710 static void machine_expand_alt(MachineSets* st, AstLexAlt* alt) {
    711   for (size_t i = 0; i < alt->nseqs; i++)
    712     for (size_t j = 0; j < alt->seqs[i]->nitems; j++)
    713       machine_expand_node(st, alt->seqs[i]->items[j]);
    714 }
    715 
    716 /* ---- compile ------------------------------------------------------------ */
    717 
    718 LexDFA* kit_gram_compile_machine(GramgenContext* ctx, const char* name,
    719                                 LexRecognizer* recognizers, size_t nrecognizers,
    720                                 AstLexLine** sets, size_t nsets, Loc loc) {
    721   if (!nrecognizers) kit_gram_error(ctx, loc, "%%machine %s has no rules", name);
    722   if (nrecognizers >= 65535)
    723     kit_gram_error(ctx, loc, "%%machine %s has too many rules", name);
    724 
    725   /* Pass 0: resolve each `[ … ]` set expression (algebra + %def set references)
    726    * and re-emit it as plain symbol text, so the alphabet pass, NFA leaves, and
    727    * samplers only ever see UPPERCASE symbols. */
    728   MachineSets st = {.ctx = ctx, .defs = sets, .ndefs = nsets};
    729   st.slots = nsets ? xcalloc(ctx, nsets, sizeof *st.slots) : NULL;
    730   for (size_t i = 0; i < nrecognizers; i++)
    731     machine_expand_alt(&st, recognizers[i].alts);
    732   for (size_t i = 0; i < nsets; i++) rset_free(ctx, &st.slots[i].set);
    733   xfree(ctx, st.slots);
    734 
    735   /* Pass 1: collect the symbol alphabet in first-use order. */
    736   TokenAlphabet alpha = {.ctx = ctx};
    737   for (size_t i = 0; i < nrecognizers; i++)
    738     token_collect_alt(ctx, &alpha, recognizers[i].alts);
    739   if (!alpha.n) kit_gram_error(ctx, loc, "%%machine %s has no symbols", name);
    740   if (alpha.n >= 65535)
    741     kit_gram_error(ctx, loc, "%%machine %s has too many symbols", name);
    742   uint16_t nsym = (uint16_t)alpha.n;
    743 
    744   /* Pass 2: one Thompson NFA over the symbol alphabet, accepts stamped per
    745    * recognizer (rule). No anchors -> all start contexts coincide at state 0. */
    746   TokenLeafCtx tctx = {.alpha = &alpha, .nsym = nsym};
    747   LexAlphabet token_alphabet = {
    748       .ud = &tctx,
    749       .exclude_surrogates = 0,
    750       .leaf_literal = token_leaf_invalid,
    751       .leaf_class = token_leaf_class,
    752       .leaf_prop = token_leaf_invalid,
    753       .leaf_any = token_leaf_any,
    754       .leaf_name = token_leaf_name,
    755   };
    756   ScalarNfaBuilder nfa = {.ctx = ctx};
    757   int start = scalar_nfa_new_state(&nfa);
    758   for (size_t i = 0; i < nrecognizers; i++) {
    759     NfaFrag frag =
    760         range_nfa_from_lex_alt(&nfa, &token_alphabet, recognizers[i].alts);
    761     scalar_nfa_eps(&nfa, start, frag.start);
    762     nfa.states[frag.end].accept = (int)i;
    763   }
    764 
    765   ScalarClasses sc = {0};
    766   uint16_t *trans = NULL, *accept = NULL, nstates = 0;
    767   AcceptSigMap sigmap = {0};
    768   uint16_t start_text = 0, start_line = 0;
    769   scalar_nfa_to_dfa(ctx, loc, &nfa, start, /*exclude_surrogates=*/0, &sc,
    770                     &trans, &accept, &nstates, &sigmap, &start_text,
    771                     &start_line);
    772 
    773   /* The atom DFA is the final DFA (symbol id == atom; no UTF-8 lowering).
    774    * Minimize + canonically relabel over the symbol classes. accept[] is the
    775    * opaque accept-signature key through both passes. */
    776   dfa_minimize(ctx, loc, &trans, &accept, &nstates, sc.nclasses, NULL, 0);
    777   dfa_canonical_relabel(ctx, trans, accept, nstates, sc.nclasses, NULL, 0, NULL,
    778                         0);
    779 
    780   /* Expand the per-state accept signature into the winning recognizer index
    781    * (the kind report). Token machines have no anchors, so only `plain` is set.
    782    */
    783   for (uint16_t s = 0; s < nstates; s++) {
    784     uint16_t sig = accept[s];
    785     accept[s] = (sig == UINT16_MAX) ? UINT16_MAX : sigmap.triples[sig].plain;
    786   }
    787 
    788   /* Dense symbol -> class map straight from the atom partition. */
    789   uint16_t* sym_class_of = xmalloc(ctx, (size_t)nsym * sizeof *sym_class_of);
    790   for (uint16_t c = 0; c < sc.nclasses; c++)
    791     for (uint32_t r = sc.off[c]; r < sc.off[c + 1]; r++)
    792       for (uint32_t v = sc.ranges[r].lo; v <= sc.ranges[r].hi; v++)
    793         sym_class_of[v] = c;
    794 
    795   uint8_t* live = token_compute_live(ctx, trans, accept, nstates, sc.nclasses);
    796 
    797   char** sym_names = xmalloc(ctx, (size_t)nsym * sizeof *sym_names);
    798   for (uint16_t i = 0; i < nsym; i++) sym_names[i] = alpha.names[i];
    799 
    800   LexDFA* dfa = xcalloc(ctx, 1, sizeof *dfa);
    801   if (!dfa) die_oom(ctx);
    802   dfa->name = (char*)name;
    803   dfa->loc = loc;
    804   dfa->recognizers = recognizers;
    805   dfa->nrecognizers = nrecognizers;
    806   dfa->input = KIT_GRAM_LEX_INPUT_TOKENS;
    807   dfa->is_machine = 1;
    808   dfa->nclasses = sc.nclasses;
    809   dfa->trans = trans;
    810   dfa->nstates = nstates;
    811   dfa->accept = accept;
    812   dfa->sym_class_of = sym_class_of;
    813   dfa->nsym = nsym;
    814   dfa->sym_names = sym_names;
    815   dfa->live = live;
    816 
    817   scalar_classes_free(ctx, &sc);
    818   accept_sig_map_free(ctx, &sigmap);
    819   xfree(ctx, alpha.names);
    820   xfree(ctx, alpha.index.keys);
    821   xfree(ctx, alpha.index.values);
    822   return dfa;
    823 }
    824 
    825 /* ---- in-memory AST-directed sampler ------------------------------------- */
    826 /* Mirror of the emitted gen_* sampler (same PRNG, same stop-coin), so the CLI's
    827  * --sample-traces prints exactly what the generated code would produce for a
    828  * given seed. Produces symbol-name pointers (into the machine's sym_names). */
    829 
    830 typedef struct {
    831   GramgenContext* ctx;
    832   const char** out;
    833   size_t cap, n, max_tokens, max_repeat;
    834   uint64_t rng;
    835   double stop_prob;
    836   int limit;
    837   char* const* sym_names;
    838   uint16_t nsym;
    839 } MachineSampler;
    840 
    841 static uint64_t ms_rng_next(uint64_t* s) {
    842   *s = *s * 6364136223846793005ull + 1442695040888963407ull;
    843   uint64_t x = *s;
    844   x ^= x >> 33;
    845   x *= 0xff51afd7ed558ccdull;
    846   x ^= x >> 33;
    847   return x;
    848 }
    849 static int ms_coin(MachineSampler* s) {
    850   uint64_t r = ms_rng_next(&s->rng);
    851   double u = (double)(r >> 11) * (1.0 / 9007199254740992.0);
    852   return u >= s->stop_prob;
    853 }
    854 static void ms_emit(MachineSampler* s, const char* name) {
    855   if (s->n >= s->max_tokens) {
    856     s->limit = 1;
    857     return;
    858   }
    859   if (s->n < s->cap) s->out[s->n] = name;
    860   s->n++;
    861 }
    862 
    863 static void ms_alt(MachineSampler* s, const AstLexAlt* alt);
    864 
    865 static void ms_node(MachineSampler* s, const AstLexNode* node) {
    866   switch (node->kind) {
    867     case LEX_NAME:
    868       ms_emit(s, node->value.s);
    869       break;
    870     case LEX_ANY: {
    871       uint64_t r = ms_rng_next(&s->rng);
    872       ms_emit(s, s->sym_names[r % s->nsym]);
    873       break;
    874     }
    875     case LEX_CLASS: {
    876       size_t nm = 0;
    877       int comp = 0;
    878       char* const* names = kit_gram_machine_class_names(s->ctx, node->value,
    879                                                        node->loc, &nm, &comp);
    880       /* Resolve to machine-owned sym_names pointers (the parsed names live in a
    881        * scratch arena). Member order matches the emitted sampler: declaration
    882        * order for an enumerated set, symbol-id order for a complement. */
    883       const char** mem = xmalloc(s->ctx, (size_t)s->nsym * sizeof *mem);
    884       size_t k = 0;
    885       if (!comp) {
    886         for (size_t j = 0; j < nm; j++)
    887           for (uint16_t si = 0; si < s->nsym; si++)
    888             if (strcmp(s->sym_names[si], names[j]) == 0) {
    889               mem[k++] = s->sym_names[si];
    890               break;
    891             }
    892       } else {
    893         for (uint16_t si = 0; si < s->nsym; si++) {
    894           int in = 0;
    895           for (size_t j = 0; j < nm; j++)
    896             if (strcmp(s->sym_names[si], names[j]) == 0) {
    897               in = 1;
    898               break;
    899             }
    900           if (!in) mem[k++] = s->sym_names[si];
    901         }
    902       }
    903       if (k) {
    904         uint64_t r = ms_rng_next(&s->rng);
    905         ms_emit(s, mem[r % k]);
    906       }
    907       xfree(s->ctx, mem);
    908       xfree(s->ctx, (void*)names);
    909       break;
    910     }
    911     case LEX_GROUP:
    912       ms_alt(s, node->alts);
    913       break;
    914     case LEX_OPT:
    915       if (ms_coin(s)) ms_alt(s, node->alts);
    916       break;
    917     case LEX_REP:
    918       for (size_t i = 0; i < s->max_repeat && ms_coin(s); i++)
    919         ms_alt(s, node->alts);
    920       break;
    921     default:
    922       break;
    923   }
    924 }
    925 
    926 static void ms_seq(MachineSampler* s, const AstLexSeq* seq) {
    927   for (size_t i = 0; i < seq->nitems; i++) ms_node(s, seq->items[i]);
    928 }
    929 
    930 static void ms_alt(MachineSampler* s, const AstLexAlt* alt) {
    931   if (alt->nseqs == 1) {
    932     ms_seq(s, alt->seqs[0]);
    933     return;
    934   }
    935   uint64_t r = ms_rng_next(&s->rng);
    936   ms_seq(s, alt->seqs[r % alt->nseqs]);
    937 }
    938 
    939 KitGramGenStatus kit_gram_machine_sample(GramgenContext* ctx, const LexDFA* m,
    940                                        size_t which, uint64_t* seed,
    941                                        double stop_prob, size_t max_repeat,
    942                                        size_t max_tokens, const char** out,
    943                                        size_t cap, size_t* n) {
    944   MachineSampler s = {
    945       .ctx = ctx,
    946       .out = out,
    947       .cap = cap,
    948       .n = 0,
    949       .max_tokens = max_tokens,
    950       .max_repeat = max_repeat,
    951       .rng = *seed,
    952       .stop_prob = stop_prob,
    953       .limit = 0,
    954       .sym_names = m->sym_names,
    955       .nsym = m->nsym,
    956   };
    957   if (which < m->nrecognizers) ms_alt(&s, m->recognizers[which].alts);
    958   *seed = s.rng;
    959   if (n) *n = s.n;
    960   return s.limit ? KIT_GRAM_GEN_LIMIT : KIT_GRAM_GEN_DONE;
    961 }