grammatik 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,11 @@
1
1
  // src/state.js
2
2
  var FAIL = /* @__PURE__ */ Symbol("grammatik.fail");
3
+ var memoTables = (slots, length) => {
4
+ if (!slots) return null;
5
+ const tables = new Array(slots);
6
+ for (let i = 0; i < slots; i++) tables[i] = { ends: new Int32Array(length + 1), vals: new Array(length + 1) };
7
+ return tables;
8
+ };
3
9
  var createState = (tokens, grammar, options = {}) => ({
4
10
  tokens,
5
11
  idx: 0,
@@ -7,6 +13,7 @@ var createState = (tokens, grammar, options = {}) => ({
7
13
  expected: [],
8
14
  grammar,
9
15
  memo: options.memo ? /* @__PURE__ */ new Map() : null,
16
+ rm: memoTables(options.memoSlots, tokens.length),
10
17
  // Entering a rule is one array store rather than an object allocation, and
11
18
  // the stack is snapshotted only on a furthest failure — by the time an error
12
19
  // is built it would otherwise have unwound to just the start rule.
@@ -27,6 +34,19 @@ function expect(state, expectation) {
27
34
  if (!state.track || state.idx !== state.maxIdx) return;
28
35
  if (!state.expected.includes(expectation)) state.expected.push(expectation);
29
36
  }
37
+ var LexError = class extends Error {
38
+ /**
39
+ * @param {string} message
40
+ * @param {{ offset: number, line: number, col: number }} position
41
+ */
42
+ constructor(message, position) {
43
+ super(message);
44
+ this.name = "LexError";
45
+ this.offset = position.offset;
46
+ this.line = position.line;
47
+ this.col = position.col;
48
+ }
49
+ };
30
50
  var ParseError = class extends Error {
31
51
  /**
32
52
  * @param {string} message
@@ -367,20 +387,303 @@ function firstCharSet(source, ignoreCase) {
367
387
  return found;
368
388
  }
369
389
 
370
- // src/lexer.js
371
- var LexError = class extends Error {
372
- /**
373
- * @param {string} message
374
- * @param {{ offset: number, line: number, col: number }} position
375
- */
376
- constructor(message, position) {
377
- super(message);
378
- this.name = "LexError";
379
- this.offset = position.offset;
380
- this.line = position.line;
381
- this.col = position.col;
390
+ // src/scan.js
391
+ var DEFER = -2;
392
+ var CLASS = 0;
393
+ var SEQ2 = 1;
394
+ var KEYWORD = 2;
395
+ var LINE = 3;
396
+ var QUOTED = 4;
397
+ var NUMBER = 5;
398
+ var STRING = 6;
399
+ var SCANNER_KINDS = Object.freeze({ CLASS, SEQ2, KEYWORD, LINE, QUOTED, NUMBER, STRING });
400
+ var NONE = new Uint8Array(128);
401
+ function make(kind, fields) {
402
+ return {
403
+ kind,
404
+ wide: false,
405
+ first: NONE,
406
+ rest: NONE,
407
+ second: NONE,
408
+ look: NONE,
409
+ lower: [],
410
+ upper: [],
411
+ prefix: "",
412
+ quote: -1,
413
+ sign: false,
414
+ jsonInt: false,
415
+ frac: false,
416
+ exp: false,
417
+ ctrl: false,
418
+ uni: false,
419
+ escAny: false,
420
+ esc: NONE,
421
+ ...fields
422
+ };
423
+ }
424
+ var SHORTHAND = {
425
+ d: (t) => {
426
+ for (let k = 48; k < 58; k++) t[k] = 1;
427
+ },
428
+ w: (t) => {
429
+ for (let k = 0; k < 128; k++) if (/\w/.test(String.fromCharCode(k))) t[k] = 1;
430
+ },
431
+ s: (t) => {
432
+ for (const k of [9, 10, 11, 12, 13, 32]) t[k] = 1;
382
433
  }
383
434
  };
435
+ var SIMPLE_ESCAPES2 = { t: " ", r: "\r", n: "\n", f: "\f", v: "\v" };
436
+ var ALNUM = /[a-zA-Z0-9]/;
437
+ function parseClass(body) {
438
+ if (body.length === 0 || body[0] === "^") return null;
439
+ const table = new Uint8Array(128);
440
+ for (let i = 0; i < body.length; i++) {
441
+ let c = body[i];
442
+ if (c === "\\") {
443
+ const n = body[++i];
444
+ if (n === "d" || n === "w") {
445
+ SHORTHAND[n](table);
446
+ continue;
447
+ }
448
+ if (n === void 0) return null;
449
+ if (ALNUM.test(n) && !(n in SIMPLE_ESCAPES2)) return null;
450
+ c = SIMPLE_ESCAPES2[n] ?? n;
451
+ }
452
+ if (body[i + 1] === "-" && i + 2 < body.length) {
453
+ let hi = body[i + 2];
454
+ let advance = 2;
455
+ if (hi === "\\") {
456
+ hi = body[i + 3];
457
+ advance = 3;
458
+ if (hi === void 0 || ALNUM.test(hi)) return null;
459
+ }
460
+ const from = c.charCodeAt(0);
461
+ const to = hi.charCodeAt(0);
462
+ if (from > to || to >= 128) return null;
463
+ for (let k = from; k <= to; k++) table[k] = 1;
464
+ i += advance;
465
+ continue;
466
+ }
467
+ const code = c.charCodeAt(0);
468
+ if (code >= 128) return null;
469
+ table[code] = 1;
470
+ }
471
+ return table;
472
+ }
473
+ var BODY = String.raw`((?:[^\]\\]|\\.)*)`;
474
+ var RE_CLASS_RUN = new RegExp(String.raw`^\[${BODY}\]([+*])$`);
475
+ var RE_CLASS_PAIR = new RegExp(String.raw`^\[${BODY}\]\[${BODY}\]\*$`);
476
+ var RE_CLASS_SEQ = new RegExp(String.raw`^\[${BODY}\]\+\[${BODY}\]\+$`);
477
+ var RE_SHORTHAND = /^\\([sdw])[+*]$/;
478
+ var RE_KEYWORD = new RegExp(String.raw`^([A-Za-z0-9_]+)\(\?!\[${BODY}\]\)$`);
479
+ var RE_LINE = new RegExp(String.raw`^([^\\\[\]()*+?.|^$]+)\[\^${BODY}\]\*$`);
480
+ var RE_QUOTED = /^(['"])\(\?:\1\1\|\[\^\1\]\)\*\1$/;
481
+ var RE_NUMBER = /^(-\?)?(?:(\(\?:0\|\[1-9\]\\d\*\))|\\d\+)(\(\?:\\\.\\d\+\)\?)?(\(\?:\[eE\]\[\+-\]\?\\d\+\)\?)?$/;
482
+ var RE_STRING_ESC = new RegExp(String.raw`^(["'])\(\?:\[\^\1\\\\(\\u0000-\\u001f|\\x00-\\x1f)?\]\|\\\\\(\?:\[${BODY}\](\|u\[0-9a-fA-F\]\{4\})?\)\)\*\1$`);
483
+ var RE_STRING_ANY = new RegExp(String.raw`^(["'])\(\?:\[\^\1\\\\\]\|\\\\\.\)\*\1$`);
484
+ function buildScanner(source, ignoreCase) {
485
+ let m = RE_KEYWORD.exec(source);
486
+ if (m) {
487
+ const look = parseClass(m[2]);
488
+ if (look === null) return null;
489
+ const word = m[1];
490
+ const lower = [];
491
+ const upper = [];
492
+ for (const ch of word) {
493
+ lower.push((ignoreCase ? ch.toLowerCase() : ch).charCodeAt(0));
494
+ upper.push((ignoreCase ? ch.toUpperCase() : ch).charCodeAt(0));
495
+ }
496
+ return make(KEYWORD, { look, lower, upper });
497
+ }
498
+ if (ignoreCase) return null;
499
+ m = RE_STRING_ESC.exec(source);
500
+ if (m) {
501
+ const esc = parseClass(m[3]);
502
+ if (esc === null) return null;
503
+ return make(STRING, { quote: m[1].charCodeAt(0), ctrl: m[2] !== void 0, uni: m[4] !== void 0, esc });
504
+ }
505
+ m = RE_STRING_ANY.exec(source);
506
+ if (m) return make(STRING, { quote: m[1].charCodeAt(0), escAny: true });
507
+ m = RE_NUMBER.exec(source.replace(/\[0-9\]/g, "\\d"));
508
+ if (m && (m[1] || m[2] || m[3] || m[4])) {
509
+ return make(NUMBER, { sign: m[1] !== void 0, jsonInt: m[2] !== void 0, frac: m[3] !== void 0, exp: m[4] !== void 0 });
510
+ }
511
+ m = RE_CLASS_RUN.exec(source);
512
+ if (m) {
513
+ const table = parseClass(m[1]);
514
+ return table && make(CLASS, { first: table, rest: table });
515
+ }
516
+ m = RE_CLASS_PAIR.exec(source);
517
+ if (m) {
518
+ const first = parseClass(m[1]);
519
+ const rest = parseClass(m[2]);
520
+ return first && rest && make(CLASS, { first, rest });
521
+ }
522
+ m = RE_CLASS_SEQ.exec(source);
523
+ if (m) {
524
+ const first = parseClass(m[1]);
525
+ const second = parseClass(m[2]);
526
+ if (first && second && first.every((v, i) => !(v && second[i]))) {
527
+ return make(SEQ2, { first, rest: first, second });
528
+ }
529
+ return null;
530
+ }
531
+ m = RE_SHORTHAND.exec(source);
532
+ if (m) {
533
+ const table = new Uint8Array(128);
534
+ SHORTHAND[m[1]](table);
535
+ return make(CLASS, { first: table, rest: table, wide: m[1] === "s" });
536
+ }
537
+ m = RE_QUOTED.exec(source);
538
+ if (m) return make(QUOTED, { quote: m[1].charCodeAt(0) });
539
+ m = RE_LINE.exec(source);
540
+ if (m) {
541
+ const stop = parseClass(m[2]);
542
+ return stop && make(LINE, { prefix: m[1], look: stop });
543
+ }
544
+ return null;
545
+ }
546
+ var skipDigits = (text, i, length) => {
547
+ while (i < length) {
548
+ const c = text.charCodeAt(i);
549
+ if (c < 48 || c > 57) break;
550
+ i++;
551
+ }
552
+ return i;
553
+ };
554
+ var isHex = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 70 || c >= 97 && c <= 102;
555
+ function scanEnd(sc, text, offset, length) {
556
+ switch (sc.kind) {
557
+ case CLASS: {
558
+ const code = text.charCodeAt(offset);
559
+ if (code >= 128) return sc.wide ? DEFER : -1;
560
+ if (sc.first[code] !== 1) return -1;
561
+ const rest = sc.rest;
562
+ let j = offset + 1;
563
+ while (j < length) {
564
+ const c = text.charCodeAt(j);
565
+ if (c >= 128) return sc.wide ? DEFER : j;
566
+ if (rest[c] !== 1) break;
567
+ j++;
568
+ }
569
+ return j;
570
+ }
571
+ case SEQ2: {
572
+ const c0 = text.charCodeAt(offset);
573
+ if (c0 >= 128 || sc.first[c0] !== 1) return -1;
574
+ const { first, second } = sc;
575
+ let j = offset + 1;
576
+ while (j < length) {
577
+ const c = text.charCodeAt(j);
578
+ if (c < 128 && first[c] === 1) j++;
579
+ else break;
580
+ }
581
+ const mid = j;
582
+ while (j < length) {
583
+ const c = text.charCodeAt(j);
584
+ if (c < 128 && second[c] === 1) j++;
585
+ else break;
586
+ }
587
+ return j > mid ? j : -1;
588
+ }
589
+ case KEYWORD: {
590
+ const { lower, upper } = sc;
591
+ const n = lower.length;
592
+ if (offset + n > length) return -1;
593
+ for (let i = 0; i < n; i++) {
594
+ const c = text.charCodeAt(offset + i);
595
+ if (c !== lower[i] && c !== upper[i]) return -1;
596
+ }
597
+ const end = offset + n;
598
+ if (end < length) {
599
+ const c = text.charCodeAt(end);
600
+ if (c < 128 && sc.look[c] === 1) return -1;
601
+ }
602
+ return end;
603
+ }
604
+ case LINE: {
605
+ if (!text.startsWith(sc.prefix, offset)) return -1;
606
+ let j = offset + sc.prefix.length;
607
+ const stop = sc.look;
608
+ while (j < length) {
609
+ const c = text.charCodeAt(j);
610
+ if (c < 128 && stop[c] === 1) break;
611
+ j++;
612
+ }
613
+ return j;
614
+ }
615
+ case NUMBER: {
616
+ let j = offset;
617
+ if (sc.sign && text.charCodeAt(j) === 45) j++;
618
+ const c = text.charCodeAt(j);
619
+ if (sc.jsonInt) {
620
+ if (c === 48) j++;
621
+ else if (c >= 49 && c <= 57) j = skipDigits(text, j + 1, length);
622
+ else return -1;
623
+ } else {
624
+ if (!(c >= 48 && c <= 57)) return -1;
625
+ j = skipDigits(text, j + 1, length);
626
+ }
627
+ if (sc.frac && text.charCodeAt(j) === 46) {
628
+ const k = skipDigits(text, j + 1, length);
629
+ if (k > j + 1) j = k;
630
+ }
631
+ if (sc.exp) {
632
+ const e = text.charCodeAt(j);
633
+ if (e === 101 || e === 69) {
634
+ let k = j + 1;
635
+ const s = text.charCodeAt(k);
636
+ if (s === 43 || s === 45) k++;
637
+ const end = skipDigits(text, k, length);
638
+ if (end > k) j = end;
639
+ }
640
+ }
641
+ return j;
642
+ }
643
+ case STRING: {
644
+ const q = sc.quote;
645
+ if (text.charCodeAt(offset) !== q) return -1;
646
+ let j = offset + 1;
647
+ for (; ; ) {
648
+ if (j >= length) return -1;
649
+ const c = text.charCodeAt(j);
650
+ if (c === q) return j + 1;
651
+ if (c === 92) {
652
+ const d = text.charCodeAt(j + 1);
653
+ if (sc.escAny) {
654
+ if (!(d >= 0) || d === 10 || d === 13 || d === 8232 || d === 8233) return -1;
655
+ j += 2;
656
+ } else if (d < 128 && sc.esc[d] === 1) {
657
+ j += 2;
658
+ } else if (sc.uni && d === 117 && isHex(text.charCodeAt(j + 2)) && isHex(text.charCodeAt(j + 3)) && isHex(text.charCodeAt(j + 4)) && isHex(text.charCodeAt(j + 5))) {
659
+ j += 6;
660
+ } else {
661
+ return -1;
662
+ }
663
+ continue;
664
+ }
665
+ if (sc.ctrl && c < 32) return -1;
666
+ j++;
667
+ }
668
+ }
669
+ default: {
670
+ const q = sc.quote;
671
+ if (text.charCodeAt(offset) !== q) return -1;
672
+ const quote = String.fromCharCode(q);
673
+ let fallback = -1;
674
+ let j = offset + 1;
675
+ for (; ; ) {
676
+ j = text.indexOf(quote, j);
677
+ if (j === -1) return fallback;
678
+ if (text.charCodeAt(j + 1) !== q) return j + 1;
679
+ fallback = j + 1;
680
+ j += 2;
681
+ }
682
+ }
683
+ }
684
+ }
685
+
686
+ // src/lexer.js
384
687
  var DEFAULT_MODE = "default";
385
688
  function modesOf(def) {
386
689
  if (Array.isArray(def.modes) && def.modes.length > 0) return def.modes;
@@ -390,6 +693,8 @@ function modesOf(def) {
390
693
  function createLexer(defs, options = {}) {
391
694
  const positions = options.positions === "offset" ? "offset" : "full";
392
695
  const trackLines = positions === "full";
696
+ const useScanners = options.scanners !== false;
697
+ const useFuse = options.fuse !== false;
393
698
  if (!Array.isArray(defs) || defs.length === 0) throw new Error("createLexer: expected a non-empty array of token definitions");
394
699
  const tokenNames = defs.map((d) => d.name);
395
700
  const seen = /* @__PURE__ */ new Set();
@@ -409,7 +714,7 @@ function createLexer(defs, options = {}) {
409
714
  return id;
410
715
  };
411
716
  for (const def of defs) for (const mode of modesOf(def)) idOf(mode);
412
- const compiled = defs.map((def) => {
717
+ const compiled = defs.map((def, id) => {
413
718
  if (!def.name) throw new Error("createLexer: every token definition needs a name");
414
719
  if (!def.pattern && !def.literal) throw new Error(`createLexer: token '${def.name}' needs a pattern or a literal`);
415
720
  if (def.pattern && def.literal) throw new Error(`createLexer: token '${def.name}' has both a pattern and a literal; pick one`);
@@ -424,13 +729,24 @@ function createLexer(defs, options = {}) {
424
729
  }
425
730
  return {
426
731
  name: def.name,
732
+ // The declaration index. `createParser` derives the identical mapping
733
+ // from `tokenNames` independently (same array, same order), so a
734
+ // combinator that resolves a `consume` target's id at build time agrees
735
+ // with what a token actually carries without the two ever comparing notes.
736
+ id,
427
737
  skip: !!def.skip,
428
738
  literal: def.literal,
429
739
  longerAlt: def.longerAlt || null,
430
740
  regex: def.pattern ? new RegExp(def.pattern, def.ignoreCase ? "iy" : "y") : null,
741
+ // A table-driven stand-in for the regex where its shape is recognised;
742
+ // null otherwise (and always, with `scanners: false`). It is a field on
743
+ // every definition so they all keep one shape. See scan.js.
744
+ scanner: useScanners && def.pattern ? buildScanner(def.pattern, !!def.ignoreCase) : null,
431
745
  modes: modesOf(def),
432
746
  pushMode: def.pushMode ? idOf(def.pushMode) : -1,
433
- popMode: !!def.popMode
747
+ popMode: !!def.popMode,
748
+ // Set only on the stand-in `fuse` builds for a run of regexes; see there.
749
+ members: null
434
750
  };
435
751
  });
436
752
  const byName = new Map(compiled.map((c) => [c.name, c]));
@@ -447,14 +763,47 @@ function createLexer(defs, options = {}) {
447
763
  for (const c of compiled) {
448
764
  c.firstChars = c.literal !== void 0 ? c.literal.length > 0 ? /* @__PURE__ */ new Set([c.literal.charCodeAt(0)]) : null : firstCharSet(c.regex.source, !!c.regex.ignoreCase);
449
765
  }
450
- const modes = modeNames.map((name) => {
766
+ const FUSE_MIN = 3;
767
+ const fusible = (def) => def.literal === void 0 && def.scanner === null && def.regex !== null && !/\\[1-9]|\\k</.test(def.regex.source) && // `exec` of an always-matching `|` alternative returns one slot per capture group.
768
+ new RegExp(def.regex.source + "|").exec("").length === 1;
769
+ function fuse(list) {
770
+ if (!useFuse) return list;
771
+ const out = [];
772
+ for (let i = 0; i < list.length; ) {
773
+ let j = i;
774
+ while (j < list.length && fusible(list[j]) && list[j].regex.flags === list[i].regex.flags) j++;
775
+ if (j - i < FUSE_MIN) {
776
+ out.push(list[i++]);
777
+ continue;
778
+ }
779
+ const run = list.slice(i, j);
780
+ out.push({
781
+ name: run.map((d) => d.name).join("|"),
782
+ id: -1,
783
+ skip: false,
784
+ literal: void 0,
785
+ longerAlt: null,
786
+ longerAltDef: void 0,
787
+ regex: new RegExp(run.map((d) => `(${d.regex.source})`).join("|"), run[0].regex.flags),
788
+ scanner: null,
789
+ modes: run[0].modes,
790
+ pushMode: -1,
791
+ popMode: false,
792
+ members: run
793
+ });
794
+ i = j;
795
+ }
796
+ return out;
797
+ }
798
+ const modes = modeNames.map((name, id) => {
451
799
  const members = compiled.filter((c) => c.modes.includes(name));
452
800
  const alwaysTry = members.filter((c) => c.firstChars === null);
453
- const candidatesFor = (code) => members.filter((c) => c.firstChars === null || c.firstChars.has(code));
801
+ const candidatesFor = (code) => fuse(members.filter((c) => c.firstChars === null || c.firstChars.has(code)));
454
802
  const ascii = new Array(128);
455
803
  for (let code = 0; code < 128; code++) ascii[code] = candidatesFor(code);
456
804
  return {
457
805
  name,
806
+ id,
458
807
  members,
459
808
  ascii,
460
809
  wide: /* @__PURE__ */ new Map(),
@@ -478,6 +827,30 @@ function createLexer(defs, options = {}) {
478
827
  const multiMode = modes.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
479
828
  const namesModes = modes.length > 1;
480
829
  const initial = modes[0];
830
+ let fusedMember = null;
831
+ let fusedText = "";
832
+ function fusedEnd(run, text, offset) {
833
+ run.regex.lastIndex = offset;
834
+ const match = run.regex.exec(text);
835
+ if (match === null) return -1;
836
+ if (match[0].length === 0) {
837
+ for (const member of run.members) {
838
+ member.regex.lastIndex = offset;
839
+ const one = member.regex.exec(text);
840
+ if (one !== null && one[0].length > 0) {
841
+ fusedMember = member;
842
+ fusedText = one[0];
843
+ return offset + fusedText.length;
844
+ }
845
+ }
846
+ return -1;
847
+ }
848
+ let group = 1;
849
+ while (match[group] === void 0) group++;
850
+ fusedMember = run.members[group - 1];
851
+ fusedText = match[0];
852
+ return offset + fusedText.length;
853
+ }
481
854
  function longerAltEnd(def, text, offset) {
482
855
  const alt = def.longerAltDef;
483
856
  if (alt.literal !== void 0) return text.startsWith(alt.literal, offset) ? offset + alt.literal.length : -1;
@@ -497,19 +870,30 @@ function createLexer(defs, options = {}) {
497
870
  outer: while (offset < length) {
498
871
  const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
499
872
  for (let i = 0; i < applicable.length; i++) {
500
- const def = applicable[i];
873
+ let def = applicable[i];
501
874
  let end = -1;
502
875
  let matched = null;
503
- if (def.literal !== void 0) {
876
+ if (def.members !== null) {
877
+ end = fusedEnd(def, text, offset);
878
+ if (end === -1) continue;
879
+ def = fusedMember;
880
+ matched = fusedText;
881
+ } else if (def.literal !== void 0) {
504
882
  if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
505
- } else if (def.skip) {
506
- def.regex.lastIndex = offset;
507
- if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
508
883
  } else {
509
- def.regex.lastIndex = offset;
510
- const match = def.regex.exec(text);
511
- if (match !== null && match[0].length > 0) matched = match[0];
512
- if (matched !== null) end = offset + matched.length;
884
+ end = def.scanner === null ? DEFER : scanEnd(def.scanner, text, offset, length);
885
+ if (end === DEFER) {
886
+ end = -1;
887
+ if (def.skip) {
888
+ def.regex.lastIndex = offset;
889
+ if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
890
+ } else {
891
+ def.regex.lastIndex = offset;
892
+ const match = def.regex.exec(text);
893
+ if (match !== null && match[0].length > 0) matched = match[0];
894
+ if (matched !== null) end = offset + matched.length;
895
+ }
896
+ }
513
897
  }
514
898
  if (end === -1) continue;
515
899
  let winner = def;
@@ -522,14 +906,7 @@ function createLexer(defs, options = {}) {
522
906
  }
523
907
  if (!winner.skip) {
524
908
  const image = winner.literal !== void 0 ? winner.literal : winner === def && matched !== null ? matched : text.slice(offset, end);
525
- tokens.push({
526
- type: winner.name,
527
- image,
528
- start: offset,
529
- end,
530
- line: trackLines ? line : 0,
531
- col: trackLines ? offset - lineStart + 1 : 0
532
- });
909
+ tokens.push(trackLines ? { type: winner.name, image, start: offset, end, line, col: offset - lineStart + 1 } : { type: winner.name, id: winner.id, image, start: offset, end });
533
910
  }
534
911
  if (trackLines) {
535
912
  for (let j = offset; j < end; j++) {
@@ -565,7 +942,238 @@ function createLexer(defs, options = {}) {
565
942
  }
566
943
  const inMode = (mode) => namesModes ? ` (in lexer mode '${mode.name}')` : "";
567
944
  const describe = (offset, line, lineStart) => trackLines ? `line ${line} column ${offset - lineStart + 1}` : `offset ${offset}`;
568
- return { tokenize, tokenNames, positions, modeNames };
945
+ if (!options.incremental) return { tokenize, tokenNames, positions, modeNames };
946
+ const recover = !!options.recover;
947
+ const errorTokenName = options.errorToken || "Error";
948
+ function snapshotStack(stack, depth) {
949
+ const ids = new Array(depth);
950
+ for (let i = 0; i < depth; i++) ids[i] = stack[i].id;
951
+ return ids;
952
+ }
953
+ function sameStack(a, b) {
954
+ if (a.length !== b.length) return false;
955
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
956
+ return true;
957
+ }
958
+ function scan(text, offset, mode, stack, depth, line, lineStart) {
959
+ const tokens = [];
960
+ const checkpoints = [];
961
+ const length = text.length;
962
+ outer: while (offset < length) {
963
+ checkpoints.push({ modeId: mode.id, stack: snapshotStack(stack, depth), line, lineStart, offset });
964
+ const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
965
+ for (let i = 0; i < applicable.length; i++) {
966
+ let def = applicable[i];
967
+ let end = -1;
968
+ let matched = null;
969
+ if (def.members !== null) {
970
+ end = fusedEnd(def, text, offset);
971
+ if (end === -1) continue;
972
+ def = fusedMember;
973
+ matched = fusedText;
974
+ } else if (def.literal !== void 0) {
975
+ if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
976
+ } else {
977
+ end = def.scanner === null ? DEFER : scanEnd(def.scanner, text, offset, length);
978
+ if (end === DEFER) {
979
+ end = -1;
980
+ if (def.skip) {
981
+ def.regex.lastIndex = offset;
982
+ if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
983
+ } else {
984
+ def.regex.lastIndex = offset;
985
+ const match = def.regex.exec(text);
986
+ if (match !== null && match[0].length > 0) matched = match[0];
987
+ if (matched !== null) end = offset + matched.length;
988
+ }
989
+ }
990
+ }
991
+ if (end === -1) continue;
992
+ let winner = def;
993
+ if (def.longerAltDef) {
994
+ const altEnd = longerAltEnd(def, text, offset);
995
+ if (altEnd > end) {
996
+ end = altEnd;
997
+ winner = def.longerAltDef;
998
+ }
999
+ }
1000
+ if (!winner.skip) {
1001
+ const image = winner.literal !== void 0 ? winner.literal : winner === def && matched !== null ? matched : text.slice(offset, end);
1002
+ tokens.push(trackLines ? { type: winner.name, image, start: offset, end, line, col: offset - lineStart + 1 } : { type: winner.name, id: winner.id, image, start: offset, end });
1003
+ }
1004
+ if (trackLines) {
1005
+ for (let j = offset; j < end; j++) {
1006
+ if (text.charCodeAt(j) !== 10) continue;
1007
+ line++;
1008
+ lineStart = j + 1;
1009
+ }
1010
+ }
1011
+ if (multiMode) {
1012
+ if (winner.popMode) {
1013
+ if (depth === 0) {
1014
+ if (!recover) {
1015
+ throw new LexError(
1016
+ `Token '${winner.name}' popped the initial lexer mode at ${describe(offset, line, lineStart)}`,
1017
+ { offset, line: trackLines ? line : 0, col: trackLines ? offset - lineStart + 1 : 0 }
1018
+ );
1019
+ }
1020
+ } else {
1021
+ mode = stack[--depth];
1022
+ }
1023
+ } else if (winner.pushMode !== -1) {
1024
+ stack[depth++] = mode;
1025
+ mode = modes[winner.pushMode];
1026
+ }
1027
+ }
1028
+ offset = end;
1029
+ continue outer;
1030
+ }
1031
+ if (recover) {
1032
+ tokens.push(trackLines ? { type: errorTokenName, image: text[offset], start: offset, end: offset + 1, line, col: offset - lineStart + 1 } : { type: errorTokenName, id: -1, image: text[offset], start: offset, end: offset + 1 });
1033
+ if (trackLines && text.charCodeAt(offset) === 10) {
1034
+ line++;
1035
+ lineStart = offset + 1;
1036
+ }
1037
+ offset++;
1038
+ continue outer;
1039
+ }
1040
+ const column = offset - lineStart + 1;
1041
+ throw new LexError(
1042
+ trackLines ? `Unexpected character '${text[offset]}' at line ${line} column ${column}${inMode(mode)}` : `Unexpected character '${text[offset]}' at offset ${offset}${inMode(mode)}`,
1043
+ { offset, line: trackLines ? line : 0, col: trackLines ? column : 0 }
1044
+ );
1045
+ }
1046
+ return { tokens, checkpoints, mode, stack, depth, line, lineStart };
1047
+ }
1048
+ function tokenizeIncremental(text) {
1049
+ if (typeof text !== "string") throw new TypeError("tokenizeIncremental: expected a string");
1050
+ const result = scan(text, 0, initial, multiMode ? [] : [], 0, 1, 0);
1051
+ return { text, tokens: result.tokens, checkpoints: result.checkpoints, eof: { modeId: result.mode.id, stack: snapshotStack(result.stack, result.depth), line: result.line, lineStart: result.lineStart, offset: text.length } };
1052
+ }
1053
+ function tokenFloor(tokens, offset) {
1054
+ let lo = 0;
1055
+ let hi = tokens.length;
1056
+ while (lo < hi) {
1057
+ const mid = lo + hi >>> 1;
1058
+ if (tokens[mid].start < offset) lo = mid + 1;
1059
+ else hi = mid;
1060
+ }
1061
+ return lo;
1062
+ }
1063
+ function checkpointFloor(checkpoints, eof, offset) {
1064
+ let lo = 0;
1065
+ let hi = checkpoints.length + 1;
1066
+ while (lo < hi) {
1067
+ const mid = lo + hi >>> 1;
1068
+ const at = mid < checkpoints.length ? checkpoints[mid].offset : eof.offset;
1069
+ if (at <= offset) lo = mid + 1;
1070
+ else hi = mid;
1071
+ }
1072
+ return lo - 1;
1073
+ }
1074
+ function checkpointAt(checkpoints, eof, offset) {
1075
+ if (eof.offset === offset) return eof;
1076
+ let lo = 0;
1077
+ let hi = checkpoints.length;
1078
+ while (lo < hi) {
1079
+ const mid = lo + hi >>> 1;
1080
+ if (checkpoints[mid].offset < offset) lo = mid + 1;
1081
+ else hi = mid;
1082
+ }
1083
+ return lo < checkpoints.length && checkpoints[lo].offset === offset ? checkpoints[lo] : null;
1084
+ }
1085
+ function relex(newText, prev, edit) {
1086
+ if (typeof newText !== "string") throw new TypeError("relex: expected a string");
1087
+ const tokens = prev.tokens;
1088
+ const checkpoints = prev.checkpoints;
1089
+ const delta = edit.newEnd - edit.oldEnd;
1090
+ let resumeIndex = checkpointFloor(checkpoints, prev.eof, edit.start);
1091
+ if (resumeIndex > 0) resumeIndex--;
1092
+ let boundary = resumeIndex < checkpoints.length ? checkpoints[resumeIndex].offset : prev.eof.offset;
1093
+ let ti = tokenFloor(tokens, boundary);
1094
+ while (ti > 0 && tokens[ti - 1].end === boundary) {
1095
+ ti--;
1096
+ boundary = tokens[ti].start;
1097
+ }
1098
+ if (boundary < (resumeIndex < checkpoints.length ? checkpoints[resumeIndex].offset : prev.eof.offset)) {
1099
+ resumeIndex = checkpointFloor(checkpoints, prev.eof, boundary);
1100
+ }
1101
+ const resume = resumeIndex < checkpoints.length ? checkpoints[resumeIndex] : prev.eof;
1102
+ const resumeMode = modes[resume.modeId];
1103
+ const resumeStack = resume.stack.map((id) => modes[id]);
1104
+ const scanned = scan(newText, resume.offset, resumeMode, resumeStack, resumeStack.length, resume.line, resume.lineStart);
1105
+ const scannedEof = { modeId: scanned.mode.id, stack: snapshotStack(scanned.stack, scanned.depth), line: scanned.line, lineStart: scanned.lineStart, offset: newText.length };
1106
+ const VERIFY_WINDOW = 4;
1107
+ function convergenceVerified(oldOffset, newOffset) {
1108
+ const oldStart = tokenFloor(tokens, oldOffset);
1109
+ const freshStart = tokenFloor(scanned.tokens, newOffset);
1110
+ for (let k = 0; k < VERIFY_WINDOW; k++) {
1111
+ const oldTok = tokens[oldStart + k];
1112
+ const freshTok = scanned.tokens[freshStart + k];
1113
+ if (oldTok === void 0 && freshTok === void 0) return true;
1114
+ if (oldTok === void 0 || freshTok === void 0) return false;
1115
+ if (oldTok.type !== freshTok.type || oldTok.image !== freshTok.image) return false;
1116
+ }
1117
+ return true;
1118
+ }
1119
+ let convergeNewOffset = -1;
1120
+ let convergeOldOffset = -1;
1121
+ let convergeOldCheckpoint = null;
1122
+ let freshCheckpointCount = 0;
1123
+ for (let s = 0; s <= scanned.checkpoints.length; s++) {
1124
+ const cp = s < scanned.checkpoints.length ? scanned.checkpoints[s] : scannedEof;
1125
+ if (cp.offset < edit.newEnd) continue;
1126
+ const oldOffset = cp.offset - delta;
1127
+ const oldCp = checkpointAt(checkpoints, prev.eof, oldOffset);
1128
+ if (oldCp && oldCp.modeId === cp.modeId && sameStack(oldCp.stack, cp.stack) && convergenceVerified(oldOffset, cp.offset)) {
1129
+ convergeNewOffset = cp.offset;
1130
+ convergeOldOffset = oldOffset;
1131
+ convergeOldCheckpoint = oldCp;
1132
+ freshCheckpointCount = s;
1133
+ break;
1134
+ }
1135
+ }
1136
+ const tokenFrom = tokenFloor(tokens, resume.offset);
1137
+ const checkpointFrom = resumeIndex;
1138
+ if (convergeNewOffset === -1) {
1139
+ tokens.splice(tokenFrom, tokens.length - tokenFrom, ...scanned.tokens);
1140
+ checkpoints.splice(checkpointFrom, checkpoints.length - checkpointFrom, ...scanned.checkpoints);
1141
+ prev.text = newText;
1142
+ prev.eof = scannedEof;
1143
+ return prev;
1144
+ }
1145
+ const freshTokenCount = tokenFloor(scanned.tokens, convergeNewOffset);
1146
+ const lineDelta = trackLines ? scanned.checkpoints.length > freshCheckpointCount ? scanned.checkpoints[freshCheckpointCount].line - convergeOldCheckpoint.line : scannedEof.line - convergeOldCheckpoint.line : 0;
1147
+ const boundaryOldLineStart = convergeOldCheckpoint.lineStart;
1148
+ const boundaryNewLineStart = (freshCheckpointCount < scanned.checkpoints.length ? scanned.checkpoints[freshCheckpointCount] : scannedEof).lineStart;
1149
+ const shiftedLineStart = (oldLineStart) => oldLineStart === boundaryOldLineStart ? boundaryNewLineStart : oldLineStart + delta;
1150
+ const tailTokenFrom = tokenFloor(tokens, convergeOldOffset);
1151
+ for (let i = tailTokenFrom; i < tokens.length; i++) {
1152
+ const t = tokens[i];
1153
+ if (trackLines) {
1154
+ const oldLineStart = t.start - (t.col - 1);
1155
+ t.col = t.start + delta - shiftedLineStart(oldLineStart) + 1;
1156
+ t.line += lineDelta;
1157
+ }
1158
+ t.start += delta;
1159
+ t.end += delta;
1160
+ }
1161
+ tokens.splice(tokenFrom, tailTokenFrom - tokenFrom, ...scanned.tokens.slice(0, freshTokenCount));
1162
+ const tailCheckpointFrom = checkpointFloor(checkpoints, prev.eof, convergeOldOffset - 1) + 1;
1163
+ for (let i = tailCheckpointFrom; i < checkpoints.length; i++) {
1164
+ const cp = checkpoints[i];
1165
+ cp.lineStart = shiftedLineStart(cp.lineStart);
1166
+ cp.line += lineDelta;
1167
+ cp.offset += delta;
1168
+ }
1169
+ checkpoints.splice(checkpointFrom, tailCheckpointFrom - checkpointFrom, ...scanned.checkpoints.slice(0, freshCheckpointCount));
1170
+ prev.text = newText;
1171
+ prev.eof.lineStart = shiftedLineStart(prev.eof.lineStart);
1172
+ prev.eof.line += lineDelta;
1173
+ prev.eof.offset += delta;
1174
+ return prev;
1175
+ }
1176
+ return { tokenize, tokenizeIncremental, relex, tokenNames, positions, modeNames };
569
1177
  }
570
1178
 
571
1179
  // src/values.js