lex_runtime.c (43857B)
1 /* gramlex.c - shared borrowed-input DFA driver for gramgen-generated lexers. */ 2 #include <string.h> 3 4 #include <kit/support/gram_lex_tables.h> 5 #include <kit/gram_unicode.h> 6 7 static const uint64_t KW_FNV_OFFSET = 0xcbf29ce484222325ull; 8 static const uint64_t KW_FNV_PRIME = 0x100000001b3ull; 9 static const uint64_t KW_MIX1 = 0x9e3779b97f4a7c15ull; 10 static const uint64_t KW_MIX2 = 0xff51afd7ed558ccdull; 11 static const uint64_t KW_MIX3 = 0xc4ceb9fe1a85ec53ull; 12 13 typedef struct { 14 KitGramLexInputSpan* span; 15 size_t off; 16 } span_pos; 17 18 typedef struct { 19 uint32_t line, col; 20 KitGramUnicodePos upos; 21 } lex_pos; 22 23 typedef struct { 24 const KitGramLexGrammar* g; 25 } kit_gram_matcher_impl; 26 _Static_assert(sizeof(kit_gram_matcher_impl) <= KIT_GRAM_MATCHER_SIZE, 27 "KitGramMatcher too small"); 28 static kit_gram_matcher_impl* matcher_impl(KitGramMatcher* m) { 29 return (kit_gram_matcher_impl*)m; 30 } 31 32 typedef struct { 33 const KitGramLexGrammar* g; 34 KitGramLexInput* in; 35 size_t from; 36 bool multiline; 37 } kit_gram_match_iter_impl; 38 _Static_assert(sizeof(kit_gram_match_iter_impl) <= KIT_GRAM_MATCH_ITER_SIZE, 39 "KitGramMatchIter too small"); 40 static kit_gram_match_iter_impl* iter_impl(KitGramMatchIter* it) { 41 return (kit_gram_match_iter_impl*)it; 42 } 43 44 typedef struct kit_gram_lex_input_impl { 45 KitGramLexInputConfig cfg; 46 KitGramLexInputSpan* head; 47 KitGramLexInputSpan* tail; 48 size_t off; 49 50 bool finished; 51 bool failed; 52 bool mode_set; 53 KitGramLexInputMode mode; 54 55 uint32_t line, col; 56 KitGramUnicodePos upos; 57 KitGramLexError error; 58 59 size_t pending_len; 60 lex_pos pending_pos; 61 bool token_valid; 62 span_pos token_start; 63 size_t token_len; 64 const unsigned char* token_lexeme; 65 } kit_gram_lex_input_impl; 66 67 typedef struct kit_gram_lexer_impl { 68 const KitGramLexGrammar* g; 69 KitGramLexInput* input_mem; 70 kit_gram_lex_input_impl* in; 71 KitGramLexConfig cfg; 72 } kit_gram_lexer_impl; 73 74 typedef struct kit_gram_lex_lexeme_impl { 75 kit_gram_lex_input_impl* in; 76 KitGramLexInputSpan* span; 77 size_t off; 78 size_t remaining; 79 const unsigned char* single; 80 size_t single_len; 81 bool yielded_single; 82 } kit_gram_lex_lexeme_impl; 83 84 _Static_assert(sizeof(kit_gram_lex_input_impl) <= sizeof(KitGramLexInput), 85 "KitGramLexInput storage too small - raise KIT_GRAM_LEX_INPUT_SIZE " 86 "in gramlex.h"); 87 _Static_assert(_Alignof(kit_gram_lex_input_impl) <= _Alignof(KitGramLexInput), 88 "KitGramLexInput storage under-aligned"); 89 _Static_assert( 90 sizeof(kit_gram_lexer_impl) <= sizeof(KitGramLexer), 91 "KitGramLexer storage too small - raise KIT_GRAM_LEXER_SIZE in gramlex.h"); 92 _Static_assert(_Alignof(kit_gram_lexer_impl) <= _Alignof(KitGramLexer), 93 "KitGramLexer storage under-aligned"); 94 _Static_assert(sizeof(kit_gram_lex_lexeme_impl) <= sizeof(KitGramLexLexeme), 95 "KitGramLexLexeme storage too small - raise KIT_GRAM_LEX_LEXEME_SIZE " 96 "in gramlex.h"); 97 _Static_assert(_Alignof(kit_gram_lex_lexeme_impl) <= _Alignof(KitGramLexLexeme), 98 "KitGramLexLexeme storage under-aligned"); 99 100 static kit_gram_lex_input_impl* in_impl(KitGramLexInput* in) { 101 return (kit_gram_lex_input_impl*)in; 102 } 103 static const kit_gram_lex_input_impl* cin_impl(const KitGramLexInput* in) { 104 return (const kit_gram_lex_input_impl*)in; 105 } 106 static kit_gram_lexer_impl* lx_impl(KitGramLexer* lx) { return (kit_gram_lexer_impl*)lx; } 107 static kit_gram_lex_lexeme_impl* it_impl(KitGramLexLexeme* it) { 108 return (kit_gram_lex_lexeme_impl*)it; 109 } 110 111 static uint32_t add_u32_wrap(uint32_t a, size_t b) { return a + (uint32_t)b; } 112 113 static uint32_t cur_line(const kit_gram_lex_input_impl* in) { 114 return in->mode_set && in->mode == KIT_GRAM_LEX_INPUT_UTF8 ? in->upos.line 115 : in->line; 116 } 117 118 static uint32_t cur_col(const kit_gram_lex_input_impl* in) { 119 return in->mode_set && in->mode == KIT_GRAM_LEX_INPUT_UTF8 ? in->upos.col 120 : in->col; 121 } 122 123 static unsigned char current_byte(const kit_gram_lex_input_impl* in) { 124 return in->head && in->off < in->head->len ? in->head->bytes[in->off] : 0; 125 } 126 127 static lex_pos current_pos(const kit_gram_lex_input_impl* in) { 128 return (lex_pos){.line = in->line, .col = in->col, .upos = in->upos}; 129 } 130 131 static void set_pos(kit_gram_lex_input_impl* in, const lex_pos* pos) { 132 if (in->mode_set && in->mode == KIT_GRAM_LEX_INPUT_UTF8) { 133 in->upos = pos->upos; 134 } else { 135 in->line = pos->line; 136 in->col = pos->col; 137 } 138 } 139 140 typedef struct { 141 lex_pos pos; 142 uint32_t cp; 143 uint8_t need; 144 } scan_pos; 145 146 static void scan_pos_init(scan_pos* sp, const kit_gram_lex_input_impl* in) { 147 *sp = (scan_pos){.pos = current_pos(in)}; 148 } 149 150 static void scan_pos_advance(scan_pos* sp, KitGramLexInputMode mode, 151 unsigned char b) { 152 if (mode != KIT_GRAM_LEX_INPUT_UTF8) { 153 if (b == '\n') { 154 sp->pos.line++; 155 sp->pos.col = 1; 156 } else { 157 sp->pos.col = add_u32_wrap(sp->pos.col, 1); 158 } 159 return; 160 } 161 162 if (!sp->need) { 163 if (b < 0x80u) { 164 kit_gram_unicode_pos_advance(&sp->pos.upos, b); 165 } else if (b >= 0xC2u && b <= 0xDFu) { 166 sp->cp = (uint32_t)(b & 0x1Fu); 167 sp->need = 1; 168 } else if (b >= 0xE0u && b <= 0xEFu) { 169 sp->cp = (uint32_t)(b & 0x0Fu); 170 sp->need = 2; 171 } else if (b >= 0xF0u && b <= 0xF4u) { 172 sp->cp = (uint32_t)(b & 0x07u); 173 sp->need = 3; 174 } 175 return; 176 } 177 178 if (!kit_gram_utf8_is_cont(b)) { 179 sp->need = 0; 180 return; 181 } 182 sp->cp = (sp->cp << 6) | (uint32_t)(b & 0x3Fu); 183 if (--sp->need == 0) kit_gram_unicode_pos_advance(&sp->pos.upos, sp->cp); 184 } 185 186 static void release_span(KitGramLexInputSpan* span) { 187 if (span->on_consumed) span->on_consumed(span->ud, span->bytes, span->len); 188 span->next = NULL; 189 } 190 191 static void release_consumed_front(kit_gram_lex_input_impl* in) { 192 while (in->head && in->off >= in->head->len) { 193 KitGramLexInputSpan* span = in->head; 194 in->head = span->next; 195 if (in->tail == span) in->tail = NULL; 196 in->off = 0; 197 release_span(span); 198 } 199 } 200 201 static void release_all(kit_gram_lex_input_impl* in) { 202 KitGramLexInputSpan* span = in->head; 203 while (span) { 204 KitGramLexInputSpan* next = span->next; 205 release_span(span); 206 span = next; 207 } 208 in->head = NULL; 209 in->tail = NULL; 210 in->off = 0; 211 } 212 213 static void set_error(kit_gram_lex_input_impl* in, uint32_t line, uint32_t col, 214 unsigned char byte, const char* message) { 215 if (!in || in->failed) return; 216 in->failed = true; 217 in->pending_len = 0; 218 in->token_valid = false; 219 in->error = (KitGramLexError){ 220 .line = line, .col = col, .byte = byte, .message = message}; 221 release_all(in); 222 } 223 224 static void set_current_error(kit_gram_lex_input_impl* in, unsigned char byte, 225 const char* message) { 226 set_error(in, cur_line(in), cur_col(in), byte, message); 227 } 228 229 static void set_hook_error(kit_gram_lex_input_impl* in, const KitGramToken* tok, 230 const KitGramLexError* err, const char* fallback) { 231 uint32_t line = err && err->line ? err->line : tok->line; 232 uint32_t col = err && err->col ? err->col : tok->col; 233 unsigned char byte = err ? err->byte : 0; 234 const char* message = err && err->message ? err->message : fallback; 235 set_error(in, line, col, byte, message); 236 } 237 238 static size_t gather_from(span_pos pos, unsigned char* out, size_t cap) { 239 size_t n = 0; 240 KitGramLexInputSpan* span = pos.span; 241 size_t off = pos.off; 242 while (span && n < cap) { 243 while (off < span->len && n < cap) out[n++] = span->bytes[off++]; 244 span = span->next; 245 off = 0; 246 } 247 return n; 248 } 249 250 static bool diagnose_utf8_at_current(const kit_gram_lex_input_impl* in, 251 const char** message) { 252 if (!in->mode_set || in->mode != KIT_GRAM_LEX_INPUT_UTF8 || !in->head || 253 in->off >= in->head->len) 254 return false; 255 unsigned char tmp[4]; 256 uint32_t cp = 0; 257 size_t nbytes = 0; 258 size_t n = gather_from((span_pos){in->head, in->off}, tmp, sizeof tmp); 259 KitGramUtf8Status st = kit_gram_utf8_decode_one(tmp, n, &cp, &nbytes); 260 (void)cp; 261 (void)nbytes; 262 if (st == KIT_GRAM_UTF8_INVALID) { 263 *message = "invalid UTF-8"; 264 return true; 265 } 266 if (st == KIT_GRAM_UTF8_NEED_MORE && in->finished) { 267 *message = "incomplete UTF-8"; 268 return true; 269 } 270 return false; 271 } 272 273 static bool advance_cursor_raw(kit_gram_lex_input_impl* in, size_t n) { 274 if (!n) return true; 275 if (in->head && in->off + n <= in->head->len) { 276 in->off += n; 277 release_consumed_front(in); 278 return true; 279 } 280 size_t remaining = n; 281 while (remaining && in->head) { 282 size_t avail = in->head->len - in->off; 283 size_t take = remaining < avail ? remaining : avail; 284 in->off += take; 285 remaining -= take; 286 release_consumed_front(in); 287 } 288 if (remaining) { 289 set_current_error(in, 0, "input cursor out of range"); 290 return false; 291 } 292 return true; 293 } 294 295 static bool consume_committed(kit_gram_lex_input_impl* in, size_t n, 296 const lex_pos* pos) { 297 if (!advance_cursor_raw(in, n)) return false; 298 set_pos(in, pos); 299 return true; 300 } 301 302 static void commit_pending(kit_gram_lex_input_impl* in) { 303 if (!in || !in->pending_len || in->failed) return; 304 size_t n = in->pending_len; 305 in->pending_len = 0; 306 in->token_valid = false; 307 (void)consume_committed(in, n, &in->pending_pos); 308 } 309 310 /* ---- Tier-1 edge anchors (zero-width DFA transitions) -------------------- 311 * Edge anchors are baked into the DFA: start anchors (\A/^) select an alternate 312 * start state, and end anchors (\z/$) surface as the per-state end-context 313 * accept tables. The driver picks the start state from the start-of-match 314 * context and, at each scanned length, the highest-priority accept whose end 315 * context holds. Both the tokenizer and the match API share these two helpers. 316 */ 317 318 /* Pick the start DFA state for a scan beginning at a position that is (or is 319 * not) the start of text / a line. start_text/start_line are 0 when the grammar 320 * has no start anchors, so state 0 serves every context. */ 321 static uint16_t lex_start_state(const KitGramLexGrammar* g, bool multiline, 322 bool text_start, bool line_start) { 323 if (text_start) return g->start_text; 324 if (multiline && line_start) return g->start_line; 325 return 0; 326 } 327 328 /* Priority-winning accept at `state` for a match whose end is at_text_end 329 * and/or before a line break. Plain accepts are always eligible; \z (text) 330 * accepts at text end; $ (line) accepts at text end or, under multiline, before 331 * a break. Lower accept id = higher source priority; KIT_GRAM_LEX_ACCEPT_NONE = 332 * none. 333 */ 334 static uint16_t lex_pick_accept(const KitGramLexGrammar* g, uint16_t state, 335 bool at_text_end, bool before_break, 336 bool multiline) { 337 uint16_t best = g->accept[state]; 338 if (at_text_end && g->accept_text) { 339 uint16_t a = g->accept_text[state]; 340 if (a < best) best = a; 341 } 342 if (g->accept_line && (at_text_end || (multiline && before_break))) { 343 uint16_t a = g->accept_line[state]; 344 if (a < best) best = a; 345 } 346 return best; 347 } 348 349 /* End-of-match context at the lookahead position (span,off): whether it is the 350 * end of text, and whether a line break begins there. `determined` is false 351 * when the deciding bytes are not yet available and the input is not finished 352 * (the tokenizer then reports NEED_MORE). `want_break` is false when no line 353 * ($) end anchor is in play, so the break scalar need not be decoded. */ 354 typedef struct { 355 bool determined; 356 bool at_text_end; 357 bool before_break; 358 } scan_end_ctx; 359 360 static scan_end_ctx scan_end_context(const kit_gram_lex_input_impl* in, 361 KitGramLexInputSpan* span, size_t off, 362 bool want_break) { 363 while (span && off >= span->len) { 364 off -= span->len; 365 span = span->next; 366 } 367 if (!span) { 368 if (in->finished) return (scan_end_ctx){true, true, false}; 369 return (scan_end_ctx){false, false, false}; 370 } 371 if (!want_break) return (scan_end_ctx){true, false, false}; 372 bool brk; 373 if (in->mode == KIT_GRAM_LEX_INPUT_UTF8) { 374 unsigned char tmp[4]; 375 size_t n = gather_from((span_pos){span, off}, tmp, sizeof tmp); 376 uint32_t cp = 0; 377 size_t nbytes = 0; 378 KitGramUtf8Status st = kit_gram_utf8_decode_one(tmp, n, &cp, &nbytes); 379 if (st == KIT_GRAM_UTF8_NEED_MORE && !in->finished) 380 return (scan_end_ctx){false, false, false}; 381 brk = st == KIT_GRAM_UTF8_OK && kit_gram_unicode_is_line_break(cp); 382 } else { 383 brk = span->bytes[off] == '\n'; 384 } 385 return (scan_end_ctx){true, false, brk}; 386 } 387 388 static bool state_has_outgoing(const KitGramLexGrammar* g, uint16_t state) { 389 if (!g || state >= g->nstates) return false; 390 uint16_t stride = g->class_stride ? g->class_stride : g->nclasses; 391 const uint16_t* row = &g->trans[(size_t)state * stride]; 392 for (uint16_t i = 0; i < g->nclasses; i++) 393 if (row[i] != KIT_GRAM_LEX_DEAD) return true; 394 return false; 395 } 396 397 typedef enum { 398 SCAN_ACCEPT, 399 SCAN_NEED_MORE, 400 SCAN_ERROR, 401 } scan_status; 402 403 static scan_status scan_one(kit_gram_lexer_impl* lx, uint16_t* accept_out, 404 size_t* len_out, lex_pos* pos_out) { 405 kit_gram_lex_input_impl* in = lx->in; 406 const KitGramLexGrammar* g = lx->g; 407 const uint8_t* class_of = g->class_of; 408 const uint16_t* trans = g->trans; 409 const uint16_t stride = g->class_stride ? g->class_stride : g->nclasses; 410 const bool ml = g->multiline; 411 const bool has_end = g->accept_text || g->accept_line; 412 const KitGramLexInputMode mode = g->input; 413 414 uint16_t state = 0; 415 if (g->start_text || g->start_line) { 416 bool ts = cur_line(in) == 1 && cur_col(in) == 1; 417 bool ls = cur_col(in) == 1; /* text start or just after a line break */ 418 state = lex_start_state(g, ml, ts, ls); 419 } 420 uint16_t best_accept = KIT_GRAM_LEX_ACCEPT_NONE; 421 size_t best_len = 0; 422 lex_pos best_pos = current_pos(in); 423 /* scan_pos drives UTF-8 scalar position folding; the byte fast path tracks 424 * position arithmetically and never touches it, so initialize it lazily only 425 * in the branches that use it. */ 426 scan_pos sp; 427 size_t seen = 0; 428 KitGramLexInputSpan* span = in->head; 429 size_t off = in->off; 430 431 if (!has_end) { 432 /* Fast path: no end anchors, so accepts need no end-context lookahead. 433 * The per-byte loop is specialized on input mode (loop unswitching). In 434 * byte mode the column advances exactly one per byte and resets only 435 * across the rare embedded newline, so the winning end position is 436 * arithmetic — avoiding both the per-accept 20-byte position-struct copy 437 * (whose bulk, upos, is unused in byte mode) and the per-byte runtime 438 * mode branch inside scan_pos_advance. UTF-8 mode keeps the folded 439 * scan_pos scalar decode. */ 440 const uint16_t* accept_tbl = g->accept; 441 bool dead = false; 442 if (mode != KIT_GRAM_LEX_INPUT_UTF8) { 443 const uint32_t start_col = in->col; 444 uint32_t line = in->line; 445 size_t last_nl = 0; /* seen index just past the last '\n'; 0 = none */ 446 while (span) { 447 const unsigned char* buf = span->bytes; 448 size_t len = span->len; 449 while (off < len) { 450 unsigned char b = buf[off]; 451 uint16_t next = trans[(size_t)state * stride + class_of[b]]; 452 if (next == KIT_GRAM_LEX_DEAD) { 453 dead = true; 454 goto fast_done; 455 } 456 state = next; 457 off++; 458 seen++; 459 if (b == '\n') { 460 line++; 461 last_nl = seen; 462 } 463 uint16_t accept = accept_tbl[state]; 464 if (accept != KIT_GRAM_LEX_ACCEPT_NONE) { 465 best_accept = accept; 466 best_len = seen; 467 best_pos.line = line; 468 best_pos.col = last_nl ? (uint32_t)(1u + (seen - last_nl)) 469 : add_u32_wrap(start_col, seen); 470 } 471 } 472 span = span->next; 473 off = 0; 474 } 475 } else { 476 scan_pos_init(&sp, in); 477 while (span) { 478 const unsigned char* buf = span->bytes; 479 size_t len = span->len; 480 while (off < len) { 481 unsigned char b = buf[off]; 482 uint16_t next = trans[(size_t)state * stride + class_of[b]]; 483 if (next == KIT_GRAM_LEX_DEAD) { 484 dead = true; 485 goto fast_done; 486 } 487 state = next; 488 off++; 489 seen++; 490 scan_pos_advance(&sp, KIT_GRAM_LEX_INPUT_UTF8, b); 491 uint16_t accept = accept_tbl[state]; 492 if (accept != KIT_GRAM_LEX_ACCEPT_NONE) { 493 best_accept = accept; 494 best_len = seen; 495 best_pos = sp.pos; 496 } 497 } 498 span = span->next; 499 off = 0; 500 } 501 } 502 fast_done: 503 if (dead) { 504 if (best_accept != KIT_GRAM_LEX_ACCEPT_NONE) { 505 *accept_out = best_accept; 506 *len_out = best_len; 507 *pos_out = best_pos; 508 return SCAN_ACCEPT; 509 } 510 const char* message = "invalid token"; 511 diagnose_utf8_at_current(in, &message); 512 set_current_error(in, current_byte(in), message); 513 return SCAN_ERROR; 514 } 515 516 if (best_accept != KIT_GRAM_LEX_ACCEPT_NONE) { 517 if (in->finished || !state_has_outgoing(g, state)) { 518 *accept_out = best_accept; 519 *len_out = best_len; 520 *pos_out = best_pos; 521 return SCAN_ACCEPT; 522 } 523 } else if (in->finished) { 524 const char* message = "incomplete token"; 525 diagnose_utf8_at_current(in, &message); 526 set_current_error(in, current_byte(in), message); 527 return SCAN_ERROR; 528 } else if (!state_has_outgoing(g, state)) { 529 const char* message = "invalid token"; 530 diagnose_utf8_at_current(in, &message); 531 set_current_error(in, current_byte(in), message); 532 return SCAN_ERROR; 533 } 534 return SCAN_NEED_MORE; 535 } 536 537 /* End-anchored grammar: evaluate the full candidate (plain plus the eligible 538 * end-context accepts) at each scanned length. This needs one position of 539 * lookahead for the end context, so it can report NEED_MORE mid-token. */ 540 const bool want_break = g->accept_line && ml; 541 bool dead = false; 542 scan_pos_init(&sp, in); 543 for (;;) { 544 while (span && off >= span->len) { 545 span = span->next; 546 off = 0; 547 } 548 scan_end_ctx ec = scan_end_context(in, span, off, want_break); 549 if (!ec.determined) { 550 /* End context unresolved (more bytes may arrive). Record the 551 * context-free plain accept; wait unless nothing more can change the 552 * outcome (no extension and no pending end accept at this state). */ 553 uint16_t plain = g->accept[state]; 554 if (plain != KIT_GRAM_LEX_ACCEPT_NONE) { 555 best_accept = plain; 556 best_len = seen; 557 best_pos = sp.pos; 558 } 559 bool end_pending = 560 (g->accept_text && g->accept_text[state] != KIT_GRAM_LEX_ACCEPT_NONE) || 561 (g->accept_line && g->accept_line[state] != KIT_GRAM_LEX_ACCEPT_NONE); 562 if (state_has_outgoing(g, state) || end_pending) return SCAN_NEED_MORE; 563 break; 564 } 565 uint16_t cand = 566 lex_pick_accept(g, state, ec.at_text_end, ec.before_break, ml); 567 if (cand != KIT_GRAM_LEX_ACCEPT_NONE) { 568 best_accept = cand; 569 best_len = seen; 570 best_pos = sp.pos; 571 } 572 if (!span) break; /* end of text (input finished) */ 573 unsigned char b = span->bytes[off]; 574 uint16_t next = trans[(size_t)state * stride + class_of[b]]; 575 if (next == KIT_GRAM_LEX_DEAD) { 576 dead = true; 577 break; 578 } 579 state = next; 580 off++; 581 seen++; 582 scan_pos_advance(&sp, mode, b); 583 } 584 585 if (best_accept != KIT_GRAM_LEX_ACCEPT_NONE) { 586 *accept_out = best_accept; 587 *len_out = best_len; 588 *pos_out = best_pos; 589 return SCAN_ACCEPT; 590 } 591 const char* message = dead ? "invalid token" : "incomplete token"; 592 diagnose_utf8_at_current(in, &message); 593 set_current_error(in, current_byte(in), message); 594 return SCAN_ERROR; 595 } 596 597 static const unsigned char* prepare_lexeme(kit_gram_lex_input_impl* in, 598 size_t len) { 599 in->token_start = (span_pos){in->head, in->off}; 600 in->token_len = len; 601 in->token_valid = true; 602 603 if (!len) { 604 in->token_lexeme = (const unsigned char*)""; 605 return in->token_lexeme; 606 } 607 if (in->head && in->off + len <= in->head->len) { 608 in->token_lexeme = in->head->bytes + in->off; 609 return in->token_lexeme; 610 } 611 if (in->cfg.carry && len <= in->cfg.carry_cap) { 612 size_t copied = 0; 613 KitGramLexInputSpan* span = in->head; 614 size_t off = in->off; 615 while (span && copied < len) { 616 size_t avail = span->len - off; 617 size_t take = len - copied < avail ? len - copied : avail; 618 memcpy(in->cfg.carry + copied, span->bytes + off, take); 619 copied += take; 620 span = span->next; 621 off = 0; 622 } 623 in->token_lexeme = in->cfg.carry; 624 return in->token_lexeme; 625 } 626 in->token_lexeme = NULL; 627 return NULL; 628 } 629 630 /* ---- Extracted-keyword lookup (shared) ----------------------------------- 631 * A literal token shadowed by a more general host recognizer is moved out of 632 * the DFA into a sorted (lexeme -> kind) table keyed by the host kind. After 633 * the DFA picks a token whose kind is a host, the runtime binary-searches the 634 * matched lexeme and rewrites the kind on an exact hit. The two callers (the 635 * tokenizer and the match API) read the matched bytes differently, so they pass 636 * a byte accessor; the table is sorted in the same memcmp order the comparator 637 * uses. */ 638 static const KitGramLexKeywordTable* keyword_table_for( 639 const KitGramLexGrammar* g, KitGramTokenKind host) { 640 for (uint16_t i = 0; i < g->nkeyword_tables; i++) 641 if (g->keyword_tables[i].host == host) return &g->keyword_tables[i]; 642 return NULL; 643 } 644 645 typedef unsigned char (*kw_byte_fn)(void* ud, size_t i); 646 647 static uint64_t kw_fnv_bytes(const unsigned char* s, size_t len) { 648 uint64_t h = KW_FNV_OFFSET; 649 for (size_t i = 0; i < len; i++) h = (h ^ s[i]) * KW_FNV_PRIME; 650 return h; 651 } 652 653 static uint64_t kw_fnv_indirect(kw_byte_fn at, void* ud, size_t len) { 654 uint64_t h = KW_FNV_OFFSET; 655 for (size_t i = 0; i < len; i++) h = (h ^ at(ud, i)) * KW_FNV_PRIME; 656 return h; 657 } 658 659 /* Finish a seed-independent FNV byte hash the same way kit_gram_lex_kw_hash64 does 660 * after its byte loop. This lets lookup hash the lexeme bytes once, then derive 661 * both the CHD bucket and displaced slot from the same accumulator. */ 662 static uint64_t kw_hash_finish(uint64_t h, uint32_t seed) { 663 h ^= (uint64_t)seed * KW_MIX1; 664 h ^= h >> 33; 665 h *= KW_MIX2; 666 h ^= h >> 33; 667 h *= KW_MIX3; 668 h ^= h >> 33; 669 return h; 670 } 671 672 static bool kw_eq_indirect(kw_byte_fn at, void* ud, size_t len, 673 const KitGramLexKeyword* kw) { 674 if (len != kw->len) return false; 675 for (size_t i = 0; i < len; i++) 676 if (at(ud, i) != (unsigned char)kw->lexeme[i]) return false; 677 return true; 678 } 679 680 static bool kw_eq_bytes(const unsigned char* s, size_t len, 681 const KitGramLexKeyword* kw) { 682 return len == kw->len && memcmp(s, kw->lexeme, len) == 0; 683 } 684 685 /* Minimal perfect hash (CHD): bucket via seed 0, displace via the bucket's 686 * seed, then one byte compare confirms the lexeme. nseeds and nkeywords are 687 * powers of two (the generator rounds up, padding the slot array with empty 688 * entries), so both indices are a mask rather than a per-token modulo. */ 689 static bool kw_lookup_hashed(const KitGramLexKeywordTable* t, 690 uint64_t byte_hash, const unsigned char* bytes, 691 kw_byte_fn at, void* ud, size_t len, 692 KitGramTokenKind* out) { 693 if (!t->nseeds || !t->nkeywords) return false; 694 if ((t->min_len || t->max_len) && (len < t->min_len || len > t->max_len)) 695 return false; 696 uint32_t bucket = 697 (uint32_t)(kw_hash_finish(byte_hash, 0) & (uint64_t)(t->nseeds - 1u)); 698 uint32_t slot = (uint32_t)(kw_hash_finish(byte_hash, t->seeds[bucket]) & 699 (uint64_t)(t->nkeywords - 1u)); 700 const KitGramLexKeyword* kw = &t->keywords[slot]; 701 if (!kw->lexeme) return false; 702 if (bytes) { 703 if (!kw_eq_bytes(bytes, len, kw)) return false; 704 } else if (!kw_eq_indirect(at, ud, len, kw)) { 705 return false; 706 } 707 *out = kw->kind; 708 return true; 709 } 710 711 static bool kw_lookup_bytes(const KitGramLexKeywordTable* t, 712 const unsigned char* bytes, size_t len, 713 KitGramTokenKind* out) { 714 if (!bytes) return false; 715 if ((t->min_len || t->max_len) && (len < t->min_len || len > t->max_len)) 716 return false; 717 return kw_lookup_hashed(t, kw_fnv_bytes(bytes, len), bytes, NULL, NULL, len, 718 out); 719 } 720 721 static bool kw_lookup_indirect(const KitGramLexKeywordTable* t, kw_byte_fn at, 722 void* ud, size_t len, KitGramTokenKind* out) { 723 if ((t->min_len || t->max_len) && (len < t->min_len || len > t->max_len)) 724 return false; 725 return kw_lookup_hashed(t, kw_fnv_indirect(at, ud, len), NULL, at, ud, len, 726 out); 727 } 728 729 KitGramTokenKind kit_gram_lex_keyword_rewrite(const KitGramLexGrammar* g, 730 KitGramTokenKind kind, 731 const unsigned char* bytes, size_t len) { 732 const KitGramLexKeywordTable* t = keyword_table_for(g, kind); 733 if (!t) return kind; 734 KitGramTokenKind out; 735 if (kw_lookup_bytes(t, bytes, len, &out)) return out; 736 return kind; 737 } 738 739 /* Byte i of a token starting at a span_pos, walking the span list. */ 740 static unsigned char tok_kw_byte(void* ud, size_t i) { 741 span_pos p = *(const span_pos*)ud; 742 KitGramLexInputSpan* span = p.span; 743 size_t off = p.off + i; 744 while (span && off >= span->len) { 745 off -= span->len; 746 span = span->next; 747 } 748 return span ? span->bytes[off] : 0; 749 } 750 751 void kit_gram_lex_input_init(KitGramLexInput* mem, 752 const KitGramLexInputConfig* cfg) { 753 if (!mem) return; 754 kit_gram_lex_input_impl* in = in_impl(mem); 755 *in = (kit_gram_lex_input_impl){ 756 .cfg = cfg ? *cfg : (KitGramLexInputConfig){0}, .line = 1, .col = 1}; 757 kit_gram_unicode_pos_init(&in->upos); 758 if (in->cfg.carry_cap && !in->cfg.carry) 759 set_error(in, 1, 1, 0, "invalid lexer input configuration"); 760 } 761 762 void kit_gram_lex_input_push(KitGramLexInput* mem, KitGramLexInputSpan* span) { 763 if (!mem || !span) return; 764 kit_gram_lex_input_impl* in = in_impl(mem); 765 if (in->failed) return; 766 commit_pending(in); 767 if (in->failed) return; 768 if (in->finished) { 769 set_current_error(in, 0, "cannot push after lexer input finish"); 770 return; 771 } 772 if (span->len && !span->bytes) { 773 set_current_error(in, 0, "invalid lexer input span"); 774 return; 775 } 776 span->next = NULL; 777 if (in->tail) 778 in->tail->next = span; 779 else 780 in->head = span; 781 in->tail = span; 782 release_consumed_front(in); 783 } 784 785 void kit_gram_lex_input_finish(KitGramLexInput* mem) { 786 if (!mem) return; 787 kit_gram_lex_input_impl* in = in_impl(mem); 788 if (in->failed) return; 789 commit_pending(in); 790 if (!in->failed) { 791 in->finished = true; 792 release_consumed_front(in); 793 } 794 } 795 796 const KitGramLexError* kit_gram_lex_input_error(const KitGramLexInput* in) { 797 static const KitGramLexError null_error = { 798 .line = 1, .col = 1, .message = "null lexer input"}; 799 return in ? &cin_impl(in)->error : &null_error; 800 } 801 802 void kit_gram_lexer_init(KitGramLexer* mem, const KitGramLexGrammar* g, 803 KitGramLexInput* input, const KitGramLexConfig* cfg) { 804 if (!mem) return; 805 kit_gram_lexer_impl* lx = lx_impl(mem); 806 *lx = (kit_gram_lexer_impl){.g = g, 807 .input_mem = input, 808 .in = input ? in_impl(input) : NULL, 809 .cfg = cfg ? *cfg : (KitGramLexConfig){0}}; 810 if (!input) return; 811 kit_gram_lex_input_impl* in = lx->in; 812 if (in->failed) return; 813 if (!g || !g->trans || !g->accept || !g->accepts || g->nclasses == 0 || 814 g->nstates == 0 || (g->class_stride && g->class_stride < g->nclasses)) { 815 set_error(in, cur_line(in), cur_col(in), 0, "invalid lexer configuration"); 816 return; 817 } 818 if (!in->mode_set) { 819 in->mode = g->input; 820 in->mode_set = true; 821 } else if (in->mode != g->input) { 822 set_error(in, cur_line(in), cur_col(in), 0, "lexer input mode mismatch"); 823 } 824 } 825 826 KitGramLexStatus kit_gram_lexer_next(KitGramLexer* mem, KitGramToken* out) { 827 kit_gram_lexer_impl* lx = lx_impl(mem); 828 kit_gram_lex_input_impl* in = lx->in; 829 if (!in || !lx->g) return KIT_GRAM_LEX_ERROR; 830 if (in->failed) return KIT_GRAM_LEX_ERROR; 831 if (!out) { 832 set_error(in, cur_line(in), cur_col(in), 0, "null token output"); 833 return KIT_GRAM_LEX_ERROR; 834 } 835 836 commit_pending(in); 837 if (in->failed) return KIT_GRAM_LEX_ERROR; 838 839 for (;;) { 840 release_consumed_front(in); 841 if (!in->head) return in->finished ? KIT_GRAM_LEX_EOF : KIT_GRAM_LEX_NEED_MORE; 842 843 uint16_t accept = KIT_GRAM_LEX_ACCEPT_NONE; 844 size_t tok_len = 0; 845 lex_pos tok_end = current_pos(in); 846 scan_status st = scan_one(lx, &accept, &tok_len, &tok_end); 847 if (st == SCAN_NEED_MORE) return KIT_GRAM_LEX_NEED_MORE; 848 if (st == SCAN_ERROR) return KIT_GRAM_LEX_ERROR; 849 if (accept >= lx->g->naccepts) { 850 set_error(in, cur_line(in), cur_col(in), 0, "invalid lexer accept table"); 851 return KIT_GRAM_LEX_ERROR; 852 } 853 854 const KitGramLexAccept* a = &lx->g->accepts[accept]; 855 /* Edge anchors are already resolved inside scan_one (start-state choice 856 * plus end-context accept selection), so the accepted token is final. */ 857 858 if (a->skip) { 859 if (!consume_committed(in, tok_len, &tok_end)) return KIT_GRAM_LEX_ERROR; 860 continue; 861 } 862 863 const unsigned char* lexeme = prepare_lexeme(in, tok_len); 864 KitGramToken tok = { 865 .kind = a->tok, 866 .lexeme = (const char*)lexeme, 867 .len = tok_len, 868 .line = cur_line(in), 869 .col = cur_col(in), 870 }; 871 in->pending_len = tok_len; 872 in->pending_pos = tok_end; 873 874 /* Rewrite an extracted keyword's kind on the whole lexeme, before the 875 * hook, so the hook sees final kinds (as with the in-DFA representation). 876 */ 877 if (lx->g->nkeyword_tables) { 878 const KitGramLexKeywordTable* kt = keyword_table_for(lx->g, a->tok); 879 KitGramTokenKind kw_kind; 880 if (kt) { 881 if (lexeme) { 882 if (kw_lookup_bytes(kt, lexeme, tok_len, &kw_kind)) 883 tok.kind = kw_kind; 884 } else { 885 span_pos sp = in->token_start; 886 if (kw_lookup_indirect(kt, tok_kw_byte, &sp, tok_len, &kw_kind)) 887 tok.kind = kw_kind; 888 } 889 } 890 } 891 892 if (lx->cfg.token_hook) { 893 KitGramLexError hook_err = {.line = tok.line, .col = tok.col}; 894 KitGramLexHookResult hr = 895 lx->cfg.token_hook(mem, lx->cfg.hook_ud, &tok, &hook_err); 896 switch (hr) { 897 case KIT_GRAM_LEX_HOOK_KEEP: 898 break; 899 case KIT_GRAM_LEX_HOOK_SKIP: 900 commit_pending(in); 901 if (in->failed) return KIT_GRAM_LEX_ERROR; 902 continue; 903 case KIT_GRAM_LEX_HOOK_ERROR: 904 set_hook_error(in, &tok, &hook_err, "lexer token hook failed"); 905 return KIT_GRAM_LEX_ERROR; 906 default: 907 set_hook_error(in, &tok, &hook_err, 908 "invalid lexer token hook result"); 909 return KIT_GRAM_LEX_ERROR; 910 } 911 } 912 913 *out = tok; 914 return KIT_GRAM_LEX_TOKEN; 915 } 916 } 917 918 void kit_gram_lexer_lexeme(KitGramLexer* mem, KitGramLexLexeme* it_mem) { 919 if (!it_mem) return; 920 kit_gram_lex_lexeme_impl* it = it_impl(it_mem); 921 *it = (kit_gram_lex_lexeme_impl){0}; 922 if (!mem) return; 923 kit_gram_lexer_impl* lx = lx_impl(mem); 924 kit_gram_lex_input_impl* in = lx->in; 925 if (!in || !in->token_valid) return; 926 it->in = in; 927 it->remaining = in->token_len; 928 if (in->token_lexeme) { 929 it->single = in->token_lexeme; 930 it->single_len = in->token_len; 931 } else { 932 it->span = in->token_start.span; 933 it->off = in->token_start.off; 934 } 935 } 936 937 bool kit_gram_lex_lexeme_next(KitGramLexLexeme* it_mem, KitGramLexSegment* seg) { 938 if (!it_mem || !seg) return false; 939 kit_gram_lex_lexeme_impl* it = it_impl(it_mem); 940 if (it->single) { 941 if (it->yielded_single) return false; 942 it->yielded_single = true; 943 *seg = (KitGramLexSegment){.bytes = it->single, .len = it->single_len}; 944 return true; 945 } 946 while (it->span && it->remaining) { 947 if (it->off >= it->span->len) { 948 it->span = it->span->next; 949 it->off = 0; 950 continue; 951 } 952 size_t avail = it->span->len - it->off; 953 size_t take = it->remaining < avail ? it->remaining : avail; 954 *seg = (KitGramLexSegment){.bytes = it->span->bytes + it->off, .len = take}; 955 it->off += take; 956 it->remaining -= take; 957 return true; 958 } 959 return false; 960 } 961 962 /* ---- Match API (re2::Set, winner-only) ----------------------------------- 963 * 964 * A read-only view over the input's current span list. Logical offset 0 is the 965 * first unconsumed byte (in->head at in->off). The view never mutates or 966 * consumes the input, so the same input may be matched repeatedly. */ 967 typedef struct { 968 KitGramLexInputSpan* head; 969 size_t head_off; 970 size_t total; 971 KitGramLexInputMode mode; 972 } match_view; 973 974 /* Locate the span/offset holding logical position pos. Returns false if pos is 975 * at or beyond the end of available bytes. */ 976 static bool view_locate(const match_view* v, size_t pos, 977 KitGramLexInputSpan** span_out, size_t* off_out) { 978 KitGramLexInputSpan* s = v->head; 979 size_t off = v->head_off; 980 size_t remaining = pos; 981 while (s) { 982 size_t avail = s->len - off; 983 if (remaining < avail) { 984 *span_out = s; 985 *off_out = off + remaining; 986 return true; 987 } 988 remaining -= avail; 989 s = s->next; 990 off = 0; 991 } 992 return false; 993 } 994 995 static bool view_byte(const match_view* v, size_t pos, unsigned char* out) { 996 KitGramLexInputSpan* s; 997 size_t off; 998 if (!view_locate(v, pos, &s, &off)) return false; 999 *out = s->bytes[off]; 1000 return true; 1001 } 1002 1003 static size_t view_gather(const match_view* v, size_t pos, unsigned char* out, 1004 size_t cap) { 1005 KitGramLexInputSpan* s; 1006 size_t off; 1007 if (!view_locate(v, pos, &s, &off)) return 0; 1008 return gather_from((span_pos){s, off}, out, cap); 1009 } 1010 1011 /* True iff a line-break scalar begins exactly at logical position pos. */ 1012 static bool view_break_at(const match_view* v, size_t pos) { 1013 if (v->mode == KIT_GRAM_LEX_INPUT_UTF8) { 1014 unsigned char tmp[4]; 1015 size_t n = view_gather(v, pos, tmp, sizeof tmp); 1016 uint32_t cp = 0; 1017 size_t nbytes = 0; 1018 if (kit_gram_utf8_decode_one(tmp, n, &cp, &nbytes) != KIT_GRAM_UTF8_OK) 1019 return false; 1020 return kit_gram_unicode_is_line_break(cp); 1021 } 1022 unsigned char b = 0; 1023 return view_byte(v, pos, &b) && b == '\n'; 1024 } 1025 1026 /* True iff a line-break scalar ends exactly at logical position pos (pos > 0), 1027 * i.e. pos is immediately after a newline. */ 1028 static bool view_break_before(const match_view* v, size_t pos) { 1029 if (pos == 0) return false; 1030 if (v->mode != KIT_GRAM_LEX_INPUT_UTF8) { 1031 unsigned char b = 0; 1032 return view_byte(v, pos - 1, &b) && b == '\n'; 1033 } 1034 /* Walk back over UTF-8 continuation bytes to the lead byte, then decode 1035 * forward and require the scalar to end exactly at pos. */ 1036 size_t start = pos - 1; 1037 unsigned char b = 0; 1038 while (start > 0) { 1039 if (!view_byte(v, start, &b)) return false; 1040 if ((b & 0xC0u) != 0x80u) break; /* not a continuation byte */ 1041 if (pos - start >= 4) return false; 1042 start--; 1043 } 1044 unsigned char tmp[4]; 1045 size_t n = view_gather(v, start, tmp, sizeof tmp); 1046 uint32_t cp = 0; 1047 size_t nbytes = 0; 1048 if (kit_gram_utf8_decode_one(tmp, n, &cp, &nbytes) != KIT_GRAM_UTF8_OK) return false; 1049 return start + nbytes == pos && kit_gram_unicode_is_line_break(cp); 1050 } 1051 1052 /* Longest-match DFA scan from logical position `start`, honoring edge anchors 1053 * structurally: the start-of-match context picks the start state, and at each 1054 * length the highest-priority accept whose end context holds wins. Mirrors 1055 * scan_one over a resident view — running out of bytes is simply end of text, 1056 * so there is no NEED_MORE. */ 1057 static bool match_scan(const KitGramLexGrammar* g, const match_view* v, 1058 size_t start, bool multiline, uint16_t* accept_out, 1059 size_t* len_out) { 1060 KitGramLexInputSpan* span; 1061 size_t off; 1062 if (!view_locate(v, start, &span, &off)) return false; 1063 1064 const uint8_t* class_of = g->class_of; 1065 const uint16_t* trans = g->trans; 1066 const uint16_t stride = g->class_stride ? g->class_stride : g->nclasses; 1067 const bool has_end = g->accept_text || g->accept_line; 1068 1069 uint16_t state = 0; 1070 if (g->start_text || g->start_line) { 1071 bool ts = start == 0; 1072 bool ls = ts || view_break_before(v, start); 1073 state = lex_start_state(g, multiline, ts, ls); 1074 } 1075 uint16_t best_accept = KIT_GRAM_LEX_ACCEPT_NONE; 1076 size_t best_len = 0, seen = 0; 1077 1078 if (!has_end) { 1079 const uint16_t* accept_tbl = g->accept; 1080 while (span) { 1081 const unsigned char* buf = span->bytes; 1082 size_t len = span->len; 1083 while (off < len) { 1084 uint16_t next = trans[(size_t)state * stride + class_of[buf[off]]]; 1085 if (next == KIT_GRAM_LEX_DEAD) goto done; 1086 state = next; 1087 off++; 1088 seen++; 1089 uint16_t acc = accept_tbl[state]; 1090 if (acc != KIT_GRAM_LEX_ACCEPT_NONE) { 1091 best_accept = acc; 1092 best_len = seen; 1093 } 1094 } 1095 span = span->next; 1096 off = 0; 1097 } 1098 done: 1099 if (best_accept == KIT_GRAM_LEX_ACCEPT_NONE) return false; 1100 *accept_out = best_accept; 1101 *len_out = best_len; 1102 return true; 1103 } 1104 1105 const bool want_break = g->accept_line && multiline; 1106 for (;;) { 1107 bool at_text_end = start + seen == v->total; 1108 bool before_break = 1109 !at_text_end && want_break && view_break_at(v, start + seen); 1110 uint16_t cand = 1111 lex_pick_accept(g, state, at_text_end, before_break, multiline); 1112 if (cand != KIT_GRAM_LEX_ACCEPT_NONE) { 1113 best_accept = cand; 1114 best_len = seen; 1115 } 1116 while (span && off >= span->len) { 1117 span = span->next; 1118 off = 0; 1119 } 1120 if (!span) break; 1121 uint16_t next = trans[(size_t)state * stride + class_of[span->bytes[off]]]; 1122 if (next == KIT_GRAM_LEX_DEAD) break; 1123 state = next; 1124 off++; 1125 seen++; 1126 } 1127 if (best_accept == KIT_GRAM_LEX_ACCEPT_NONE) return false; 1128 *accept_out = best_accept; 1129 *len_out = best_len; 1130 return true; 1131 } 1132 1133 static bool match_valid(const KitGramLexGrammar* g, const KitGramLexInput* in) { 1134 return g && in && g->trans && g->accept && g->accepts && g->nclasses && 1135 g->nstates && (!g->class_stride || g->class_stride >= g->nclasses) && 1136 !cin_impl(in)->failed; 1137 } 1138 1139 /* Byte i of a match at logical offset `base` of the view (in range by 1140 * construction, since the match length came from the same span list). */ 1141 typedef struct { 1142 const match_view* v; 1143 size_t base; 1144 } view_kw_ud; 1145 static unsigned char view_kw_byte(void* ud, size_t i) { 1146 const view_kw_ud* u = ud; 1147 unsigned char b = 0; 1148 view_byte(u->v, u->base + i, &b); 1149 return b; 1150 } 1151 1152 static const unsigned char* view_contiguous(const match_view* v, size_t base, 1153 size_t len) { 1154 KitGramLexInputSpan* s; 1155 size_t off; 1156 if (!view_locate(v, base, &s, &off)) 1157 return len ? NULL : (const unsigned char*)""; 1158 return off + len <= s->len ? s->bytes + off : NULL; 1159 } 1160 1161 /* Longest match at exactly logical position `from`, honoring in-pattern 1162 * anchors. Writes [from, from+len) and kind on success. Extracted keywords are 1163 * rewritten the same way the tokenizer does, so a Set match reports the same 1164 * winning kind whether or not the literal was pulled out of the DFA. */ 1165 static bool match_one_at(const KitGramLexGrammar* g, const match_view* v, 1166 bool multiline, size_t from, KitGramMatch* out) { 1167 uint16_t accept = KIT_GRAM_LEX_ACCEPT_NONE; 1168 size_t len = 0; 1169 if (!match_scan(g, v, from, multiline, &accept, &len)) return false; 1170 if (accept >= g->naccepts) return false; 1171 const KitGramLexAccept* a = &g->accepts[accept]; 1172 KitGramTokenKind kind = a->tok; 1173 if (g->nkeyword_tables) { 1174 const KitGramLexKeywordTable* kt = keyword_table_for(g, a->tok); 1175 KitGramTokenKind kw_kind; 1176 if (kt) { 1177 const unsigned char* bytes = view_contiguous(v, from, len); 1178 if (bytes) { 1179 if (kw_lookup_bytes(kt, bytes, len, &kw_kind)) kind = kw_kind; 1180 } else { 1181 view_kw_ud ud = {v, from}; 1182 if (kw_lookup_indirect(kt, view_kw_byte, &ud, len, &kw_kind)) 1183 kind = kw_kind; 1184 } 1185 } 1186 } 1187 out->start = from; 1188 out->end = from + len; 1189 out->kind = kind; 1190 return true; 1191 } 1192 1193 static match_view view_of(const KitGramLexInput* in) { 1194 const kit_gram_lex_input_impl* imp = cin_impl(in); 1195 match_view v = {imp->head, imp->off, 0, 1196 imp->mode_set ? imp->mode : KIT_GRAM_LEX_INPUT_BYTES}; 1197 for (KitGramLexInputSpan* s = imp->head; s; s = s->next) { 1198 size_t base = s == imp->head ? imp->off : 0; 1199 if (s->len > base) v.total += s->len - base; 1200 } 1201 return v; 1202 } 1203 1204 void kit_gram_matcher_bind(KitGramMatcher* m, const KitGramLexGrammar* g) { 1205 matcher_impl(m)->g = g; 1206 } 1207 1208 bool kit_gram_match_anchored(KitGramMatcher* m, KitGramLexInput* in, 1209 const KitGramMatchOpts* opts, KitGramMatch* out) { 1210 const KitGramLexGrammar* g = matcher_impl(m)->g; 1211 if (!out || !match_valid(g, in)) return false; 1212 bool multiline = opts && opts->multiline; 1213 match_view v = view_of(in); 1214 v.mode = g->input; /* the grammar defines byte vs utf8 newline semantics */ 1215 return match_one_at(g, &v, multiline, 0, out); 1216 } 1217 1218 bool kit_gram_match_full(KitGramMatcher* m, KitGramLexInput* in, 1219 const KitGramMatchOpts* opts, KitGramMatch* out) { 1220 const KitGramLexGrammar* g = matcher_impl(m)->g; 1221 if (!out || !match_valid(g, in)) return false; 1222 bool multiline = opts && opts->multiline; 1223 match_view v = view_of(in); 1224 v.mode = g->input; /* the grammar defines byte vs utf8 newline semantics */ 1225 KitGramMatch m2; 1226 if (!match_one_at(g, &v, multiline, 0, &m2)) return false; 1227 if (m2.end != v.total) return false; /* the whole input must be one match */ 1228 *out = m2; 1229 return true; 1230 } 1231 1232 static bool match_find_from(const KitGramLexGrammar* g, const match_view* v, 1233 bool multiline, size_t from, KitGramMatch* out) { 1234 for (size_t p = from; p < v->total; p++) 1235 if (match_one_at(g, v, multiline, p, out)) return true; 1236 return false; 1237 } 1238 1239 bool kit_gram_match_find(KitGramMatcher* m, KitGramLexInput* in, 1240 const KitGramMatchOpts* opts, KitGramMatch* out) { 1241 const KitGramLexGrammar* g = matcher_impl(m)->g; 1242 if (!out || !match_valid(g, in)) return false; 1243 bool multiline = opts && opts->multiline; 1244 match_view v = view_of(in); 1245 v.mode = g->input; /* the grammar defines byte vs utf8 newline semantics */ 1246 return match_find_from(g, &v, multiline, 0, out); 1247 } 1248 1249 void kit_gram_match_iter_init(KitGramMatchIter* it, KitGramMatcher* m, 1250 KitGramLexInput* in, const KitGramMatchOpts* opts) { 1251 if (!it) return; 1252 const KitGramLexGrammar* g = matcher_impl(m)->g; 1253 iter_impl(it)->g = g; 1254 iter_impl(it)->in = in; 1255 iter_impl(it)->from = 0; 1256 iter_impl(it)->multiline = opts && opts->multiline; 1257 } 1258 1259 bool kit_gram_match_iter_next(KitGramMatchIter* it, KitGramMatch* out) { 1260 if (!it || !out || !match_valid(iter_impl(it)->g, iter_impl(it)->in)) 1261 return false; 1262 match_view v = view_of(iter_impl(it)->in); 1263 v.mode = 1264 iter_impl(it) 1265 ->g->input; /* the grammar defines byte vs utf8 newline semantics */ 1266 KitGramMatch m; 1267 if (!match_find_from(iter_impl(it)->g, &v, iter_impl(it)->multiline, 1268 iter_impl(it)->from, &m)) 1269 return false; 1270 *out = m; 1271 iter_impl(it)->from = 1272 m.end > iter_impl(it)->from 1273 ? m.end 1274 : iter_impl(it)->from + 1; /* non-overlap, progress */ 1275 return true; 1276 }