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 }