grammatik 0.1.1 → 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.
package/dist/cjs/index.js CHANGED
@@ -365,7 +365,338 @@ function firstCharSet(source, ignoreCase) {
365
365
  return found;
366
366
  }
367
367
 
368
- // src/lexer.js
368
+ // src/scan.js
369
+ var DEFER = -2;
370
+ var CLASS = 0;
371
+ var SEQ2 = 1;
372
+ var KEYWORD = 2;
373
+ var LINE = 3;
374
+ var QUOTED = 4;
375
+ var NUMBER = 5;
376
+ var STRING = 6;
377
+ var SCANNER_KINDS = Object.freeze({ CLASS, SEQ2, KEYWORD, LINE, QUOTED, NUMBER, STRING });
378
+ var NONE = new Uint8Array(128);
379
+ function make(kind, fields) {
380
+ return {
381
+ kind,
382
+ wide: false,
383
+ first: NONE,
384
+ rest: NONE,
385
+ second: NONE,
386
+ look: NONE,
387
+ lower: [],
388
+ upper: [],
389
+ prefix: "",
390
+ quote: -1,
391
+ sign: false,
392
+ jsonInt: false,
393
+ frac: false,
394
+ exp: false,
395
+ ctrl: false,
396
+ uni: false,
397
+ escAny: false,
398
+ esc: NONE,
399
+ ...fields
400
+ };
401
+ }
402
+ var SHORTHAND = {
403
+ d: (t) => {
404
+ for (let k = 48; k < 58; k++) t[k] = 1;
405
+ },
406
+ w: (t) => {
407
+ for (let k = 0; k < 128; k++) if (/\w/.test(String.fromCharCode(k))) t[k] = 1;
408
+ },
409
+ s: (t) => {
410
+ for (const k of [9, 10, 11, 12, 13, 32]) t[k] = 1;
411
+ }
412
+ };
413
+ var SIMPLE_ESCAPES2 = { t: " ", r: "\r", n: "\n", f: "\f", v: "\v" };
414
+ var ALNUM = /[a-zA-Z0-9]/;
415
+ function parseClass(body) {
416
+ if (body.length === 0 || body[0] === "^") return null;
417
+ const table = new Uint8Array(128);
418
+ for (let i = 0; i < body.length; i++) {
419
+ let c = body[i];
420
+ if (c === "\\") {
421
+ const n = body[++i];
422
+ if (n === "d" || n === "w") {
423
+ SHORTHAND[n](table);
424
+ continue;
425
+ }
426
+ if (n === void 0) return null;
427
+ if (ALNUM.test(n) && !(n in SIMPLE_ESCAPES2)) return null;
428
+ c = SIMPLE_ESCAPES2[n] ?? n;
429
+ }
430
+ if (body[i + 1] === "-" && i + 2 < body.length) {
431
+ let hi = body[i + 2];
432
+ let advance = 2;
433
+ if (hi === "\\") {
434
+ hi = body[i + 3];
435
+ advance = 3;
436
+ if (hi === void 0 || ALNUM.test(hi)) return null;
437
+ }
438
+ const from = c.charCodeAt(0);
439
+ const to = hi.charCodeAt(0);
440
+ if (from > to || to >= 128) return null;
441
+ for (let k = from; k <= to; k++) table[k] = 1;
442
+ i += advance;
443
+ continue;
444
+ }
445
+ const code = c.charCodeAt(0);
446
+ if (code >= 128) return null;
447
+ table[code] = 1;
448
+ }
449
+ return table;
450
+ }
451
+ var BODY = String.raw`((?:[^\]\\]|\\.)*)`;
452
+ var RE_CLASS_RUN = new RegExp(String.raw`^\[${BODY}\]([+*])$`);
453
+ var RE_CLASS_PAIR = new RegExp(String.raw`^\[${BODY}\]\[${BODY}\]\*$`);
454
+ var RE_CLASS_SEQ = new RegExp(String.raw`^\[${BODY}\]\+\[${BODY}\]\+$`);
455
+ var RE_SHORTHAND = /^\\([sdw])[+*]$/;
456
+ var RE_KEYWORD = new RegExp(String.raw`^([A-Za-z0-9_]+)\(\?!\[${BODY}\]\)$`);
457
+ var RE_LINE = new RegExp(String.raw`^([^\\\[\]()*+?.|^$]+)\[\^${BODY}\]\*$`);
458
+ var RE_QUOTED = /^(['"])\(\?:\1\1\|\[\^\1\]\)\*\1$/;
459
+ var RE_NUMBER = /^(-\?)?(?:(\(\?:0\|\[1-9\]\\d\*\))|\\d\+)(\(\?:\\\.\\d\+\)\?)?(\(\?:\[eE\]\[\+-\]\?\\d\+\)\?)?$/;
460
+ var RE_STRING_ESC = new RegExp(String.raw`^(["'])\(\?:\[\^\1\\\\(\\u0000-\\u001f|\\x00-\\x1f)?\]\|\\\\\(\?:\[${BODY}\](\|u\[0-9a-fA-F\]\{4\})?\)\)\*\1$`);
461
+ var RE_STRING_ANY = new RegExp(String.raw`^(["'])\(\?:\[\^\1\\\\\]\|\\\\\.\)\*\1$`);
462
+ function buildScanner(source, ignoreCase) {
463
+ let m = RE_KEYWORD.exec(source);
464
+ if (m) {
465
+ const look = parseClass(m[2]);
466
+ if (look === null) return null;
467
+ const word = m[1];
468
+ const lower = [];
469
+ const upper = [];
470
+ for (const ch of word) {
471
+ lower.push((ignoreCase ? ch.toLowerCase() : ch).charCodeAt(0));
472
+ upper.push((ignoreCase ? ch.toUpperCase() : ch).charCodeAt(0));
473
+ }
474
+ return make(KEYWORD, { look, lower, upper });
475
+ }
476
+ if (ignoreCase) return null;
477
+ m = RE_STRING_ESC.exec(source);
478
+ if (m) {
479
+ const esc = parseClass(m[3]);
480
+ if (esc === null) return null;
481
+ return make(STRING, { quote: m[1].charCodeAt(0), ctrl: m[2] !== void 0, uni: m[4] !== void 0, esc });
482
+ }
483
+ m = RE_STRING_ANY.exec(source);
484
+ if (m) return make(STRING, { quote: m[1].charCodeAt(0), escAny: true });
485
+ m = RE_NUMBER.exec(source.replace(/\[0-9\]/g, "\\d"));
486
+ if (m && (m[1] || m[2] || m[3] || m[4])) {
487
+ return make(NUMBER, { sign: m[1] !== void 0, jsonInt: m[2] !== void 0, frac: m[3] !== void 0, exp: m[4] !== void 0 });
488
+ }
489
+ m = RE_CLASS_RUN.exec(source);
490
+ if (m) {
491
+ const table = parseClass(m[1]);
492
+ return table && make(CLASS, { first: table, rest: table });
493
+ }
494
+ m = RE_CLASS_PAIR.exec(source);
495
+ if (m) {
496
+ const first = parseClass(m[1]);
497
+ const rest = parseClass(m[2]);
498
+ return first && rest && make(CLASS, { first, rest });
499
+ }
500
+ m = RE_CLASS_SEQ.exec(source);
501
+ if (m) {
502
+ const first = parseClass(m[1]);
503
+ const second = parseClass(m[2]);
504
+ if (first && second && first.every((v, i) => !(v && second[i]))) {
505
+ return make(SEQ2, { first, rest: first, second });
506
+ }
507
+ return null;
508
+ }
509
+ m = RE_SHORTHAND.exec(source);
510
+ if (m) {
511
+ const table = new Uint8Array(128);
512
+ SHORTHAND[m[1]](table);
513
+ return make(CLASS, { first: table, rest: table, wide: m[1] === "s" });
514
+ }
515
+ m = RE_QUOTED.exec(source);
516
+ if (m) return make(QUOTED, { quote: m[1].charCodeAt(0) });
517
+ m = RE_LINE.exec(source);
518
+ if (m) {
519
+ const stop = parseClass(m[2]);
520
+ return stop && make(LINE, { prefix: m[1], look: stop });
521
+ }
522
+ return null;
523
+ }
524
+ var skipDigits = (text, i, length) => {
525
+ while (i < length) {
526
+ const c = text.charCodeAt(i);
527
+ if (c < 48 || c > 57) break;
528
+ i++;
529
+ }
530
+ return i;
531
+ };
532
+ var isHex = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 70 || c >= 97 && c <= 102;
533
+ function scanEnd(sc, text, offset, length) {
534
+ switch (sc.kind) {
535
+ case CLASS: {
536
+ const code = text.charCodeAt(offset);
537
+ if (code >= 128) return sc.wide ? DEFER : -1;
538
+ if (sc.first[code] !== 1) return -1;
539
+ const rest = sc.rest;
540
+ let j = offset + 1;
541
+ while (j < length) {
542
+ const c = text.charCodeAt(j);
543
+ if (c >= 128) return sc.wide ? DEFER : j;
544
+ if (rest[c] !== 1) break;
545
+ j++;
546
+ }
547
+ return j;
548
+ }
549
+ case SEQ2: {
550
+ const c0 = text.charCodeAt(offset);
551
+ if (c0 >= 128 || sc.first[c0] !== 1) return -1;
552
+ const { first, second } = sc;
553
+ let j = offset + 1;
554
+ while (j < length) {
555
+ const c = text.charCodeAt(j);
556
+ if (c < 128 && first[c] === 1) j++;
557
+ else break;
558
+ }
559
+ const mid = j;
560
+ while (j < length) {
561
+ const c = text.charCodeAt(j);
562
+ if (c < 128 && second[c] === 1) j++;
563
+ else break;
564
+ }
565
+ return j > mid ? j : -1;
566
+ }
567
+ case KEYWORD: {
568
+ const { lower, upper } = sc;
569
+ const n = lower.length;
570
+ if (offset + n > length) return -1;
571
+ for (let i = 0; i < n; i++) {
572
+ const c = text.charCodeAt(offset + i);
573
+ if (c !== lower[i] && c !== upper[i]) return -1;
574
+ }
575
+ const end = offset + n;
576
+ if (end < length) {
577
+ const c = text.charCodeAt(end);
578
+ if (c < 128 && sc.look[c] === 1) return -1;
579
+ }
580
+ return end;
581
+ }
582
+ case LINE: {
583
+ if (!text.startsWith(sc.prefix, offset)) return -1;
584
+ let j = offset + sc.prefix.length;
585
+ const stop = sc.look;
586
+ while (j < length) {
587
+ const c = text.charCodeAt(j);
588
+ if (c < 128 && stop[c] === 1) break;
589
+ j++;
590
+ }
591
+ return j;
592
+ }
593
+ case NUMBER: {
594
+ let j = offset;
595
+ if (sc.sign && text.charCodeAt(j) === 45) j++;
596
+ const c = text.charCodeAt(j);
597
+ if (sc.jsonInt) {
598
+ if (c === 48) j++;
599
+ else if (c >= 49 && c <= 57) j = skipDigits(text, j + 1, length);
600
+ else return -1;
601
+ } else {
602
+ if (!(c >= 48 && c <= 57)) return -1;
603
+ j = skipDigits(text, j + 1, length);
604
+ }
605
+ if (sc.frac && text.charCodeAt(j) === 46) {
606
+ const k = skipDigits(text, j + 1, length);
607
+ if (k > j + 1) j = k;
608
+ }
609
+ if (sc.exp) {
610
+ const e = text.charCodeAt(j);
611
+ if (e === 101 || e === 69) {
612
+ let k = j + 1;
613
+ const s = text.charCodeAt(k);
614
+ if (s === 43 || s === 45) k++;
615
+ const end = skipDigits(text, k, length);
616
+ if (end > k) j = end;
617
+ }
618
+ }
619
+ return j;
620
+ }
621
+ case STRING: {
622
+ const q2 = sc.quote;
623
+ if (text.charCodeAt(offset) !== q2) return -1;
624
+ let j = offset + 1;
625
+ for (; ; ) {
626
+ if (j >= length) return -1;
627
+ const c = text.charCodeAt(j);
628
+ if (c === q2) return j + 1;
629
+ if (c === 92) {
630
+ const d = text.charCodeAt(j + 1);
631
+ if (sc.escAny) {
632
+ if (!(d >= 0) || d === 10 || d === 13 || d === 8232 || d === 8233) return -1;
633
+ j += 2;
634
+ } else if (d < 128 && sc.esc[d] === 1) {
635
+ j += 2;
636
+ } 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))) {
637
+ j += 6;
638
+ } else {
639
+ return -1;
640
+ }
641
+ continue;
642
+ }
643
+ if (sc.ctrl && c < 32) return -1;
644
+ j++;
645
+ }
646
+ }
647
+ default: {
648
+ const q2 = sc.quote;
649
+ if (text.charCodeAt(offset) !== q2) return -1;
650
+ const quote2 = String.fromCharCode(q2);
651
+ let fallback = -1;
652
+ let j = offset + 1;
653
+ for (; ; ) {
654
+ j = text.indexOf(quote2, j);
655
+ if (j === -1) return fallback;
656
+ if (text.charCodeAt(j + 1) !== q2) return j + 1;
657
+ fallback = j + 1;
658
+ j += 2;
659
+ }
660
+ }
661
+ }
662
+ }
663
+
664
+ // src/state.js
665
+ var FAIL = /* @__PURE__ */ Symbol("grammatik.fail");
666
+ var memoTables = (slots, length) => {
667
+ if (!slots) return null;
668
+ const tables = new Array(slots);
669
+ for (let i = 0; i < slots; i++) tables[i] = { ends: new Int32Array(length + 1), vals: new Array(length + 1) };
670
+ return tables;
671
+ };
672
+ var createState = (tokens, grammar, options = {}) => ({
673
+ tokens,
674
+ idx: 0,
675
+ maxIdx: 0,
676
+ expected: [],
677
+ grammar,
678
+ memo: options.memo ? /* @__PURE__ */ new Map() : null,
679
+ rm: memoTables(options.memoSlots, tokens.length),
680
+ // Entering a rule is one array store rather than an object allocation, and
681
+ // the stack is snapshotted only on a furthest failure — by the time an error
682
+ // is built it would otherwise have unwound to just the start rule.
683
+ ruleNames: [],
684
+ ruleDepth: 0,
685
+ failureStack: null,
686
+ track: options.track === true,
687
+ steps: 0,
688
+ maxSteps: options.maxSteps || 0
689
+ });
690
+ function expect(state, expectation) {
691
+ if (state.idx > state.maxIdx) {
692
+ state.maxIdx = state.idx;
693
+ if (!state.track) return;
694
+ state.expected = [];
695
+ state.failureStack = state.ruleNames.slice(0, state.ruleDepth);
696
+ }
697
+ if (!state.track || state.idx !== state.maxIdx) return;
698
+ if (!state.expected.includes(expectation)) state.expected.push(expectation);
699
+ }
369
700
  var LexError = class extends Error {
370
701
  /**
371
702
  * @param {string} message
@@ -379,6 +710,33 @@ var LexError = class extends Error {
379
710
  this.col = position.col;
380
711
  }
381
712
  };
713
+ var ParseError = class extends Error {
714
+ /**
715
+ * @param {string} message
716
+ * @param {{ token: import('./lexer.js').Token | null, expected: string[], ruleStack: string[] }} details
717
+ */
718
+ constructor(message, details) {
719
+ super(message);
720
+ this.name = "ParseError";
721
+ this.token = details.token;
722
+ this.expected = details.expected;
723
+ this.ruleStack = details.ruleStack;
724
+ }
725
+ };
726
+ var describeExpected = (expected) => expected.length === 0 ? "nothing" : expected.length === 1 ? expected[0] : `one of [${expected.join(", ")}]`;
727
+ function buildParseError(state) {
728
+ const token = state.tokens[state.maxIdx] || null;
729
+ const expected = [...state.expected].sort();
730
+ const ruleStack = state.failureStack || state.ruleNames.slice(0, state.ruleDepth);
731
+ const at = (t) => state.positions === "offset" ? `offset ${t.start}` : `line ${t.line} column ${t.col}`;
732
+ const found = token ? `--> ${token.image} <-- (${token.type}) at ${at(token)}` : "end of input";
733
+ return new ParseError(
734
+ `Expecting ${describeExpected(expected)} but found ${found}`,
735
+ { token, expected, ruleStack }
736
+ );
737
+ }
738
+
739
+ // src/lexer.js
382
740
  var DEFAULT_MODE = "default";
383
741
  function modesOf(def) {
384
742
  if (Array.isArray(def.modes) && def.modes.length > 0) return def.modes;
@@ -388,6 +746,8 @@ function modesOf(def) {
388
746
  function createLexer(defs, options = {}) {
389
747
  const positions = options.positions === "offset" ? "offset" : "full";
390
748
  const trackLines = positions === "full";
749
+ const useScanners = options.scanners !== false;
750
+ const useFuse = options.fuse !== false;
391
751
  if (!Array.isArray(defs) || defs.length === 0) throw new Error("createLexer: expected a non-empty array of token definitions");
392
752
  const tokenNames = defs.map((d) => d.name);
393
753
  const seen = /* @__PURE__ */ new Set();
@@ -431,9 +791,15 @@ function createLexer(defs, options = {}) {
431
791
  literal: def.literal,
432
792
  longerAlt: def.longerAlt || null,
433
793
  regex: def.pattern ? new RegExp(def.pattern, def.ignoreCase ? "iy" : "y") : null,
794
+ // A table-driven stand-in for the regex where its shape is recognised;
795
+ // null otherwise (and always, with `scanners: false`). It is a field on
796
+ // every definition so they all keep one shape. See scan.js.
797
+ scanner: useScanners && def.pattern ? buildScanner(def.pattern, !!def.ignoreCase) : null,
434
798
  modes: modesOf(def),
435
799
  pushMode: def.pushMode ? idOf(def.pushMode) : -1,
436
- popMode: !!def.popMode
800
+ popMode: !!def.popMode,
801
+ // Set only on the stand-in `fuse` builds for a run of regexes; see there.
802
+ members: null
437
803
  };
438
804
  });
439
805
  const byName = new Map(compiled.map((c) => [c.name, c]));
@@ -450,14 +816,47 @@ function createLexer(defs, options = {}) {
450
816
  for (const c of compiled) {
451
817
  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);
452
818
  }
453
- const modes = modeNames.map((name) => {
819
+ const FUSE_MIN = 3;
820
+ 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.
821
+ new RegExp(def.regex.source + "|").exec("").length === 1;
822
+ function fuse(list) {
823
+ if (!useFuse) return list;
824
+ const out = [];
825
+ for (let i = 0; i < list.length; ) {
826
+ let j = i;
827
+ while (j < list.length && fusible(list[j]) && list[j].regex.flags === list[i].regex.flags) j++;
828
+ if (j - i < FUSE_MIN) {
829
+ out.push(list[i++]);
830
+ continue;
831
+ }
832
+ const run = list.slice(i, j);
833
+ out.push({
834
+ name: run.map((d) => d.name).join("|"),
835
+ id: -1,
836
+ skip: false,
837
+ literal: void 0,
838
+ longerAlt: null,
839
+ longerAltDef: void 0,
840
+ regex: new RegExp(run.map((d) => `(${d.regex.source})`).join("|"), run[0].regex.flags),
841
+ scanner: null,
842
+ modes: run[0].modes,
843
+ pushMode: -1,
844
+ popMode: false,
845
+ members: run
846
+ });
847
+ i = j;
848
+ }
849
+ return out;
850
+ }
851
+ const modes = modeNames.map((name, id) => {
454
852
  const members = compiled.filter((c) => c.modes.includes(name));
455
853
  const alwaysTry = members.filter((c) => c.firstChars === null);
456
- const candidatesFor = (code) => members.filter((c) => c.firstChars === null || c.firstChars.has(code));
854
+ const candidatesFor = (code) => fuse(members.filter((c) => c.firstChars === null || c.firstChars.has(code)));
457
855
  const ascii = new Array(128);
458
856
  for (let code = 0; code < 128; code++) ascii[code] = candidatesFor(code);
459
857
  return {
460
858
  name,
859
+ id,
461
860
  members,
462
861
  ascii,
463
862
  wide: /* @__PURE__ */ new Map(),
@@ -481,6 +880,30 @@ function createLexer(defs, options = {}) {
481
880
  const multiMode = modes.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
482
881
  const namesModes = modes.length > 1;
483
882
  const initial = modes[0];
883
+ let fusedMember = null;
884
+ let fusedText = "";
885
+ function fusedEnd(run, text, offset) {
886
+ run.regex.lastIndex = offset;
887
+ const match = run.regex.exec(text);
888
+ if (match === null) return -1;
889
+ if (match[0].length === 0) {
890
+ for (const member of run.members) {
891
+ member.regex.lastIndex = offset;
892
+ const one = member.regex.exec(text);
893
+ if (one !== null && one[0].length > 0) {
894
+ fusedMember = member;
895
+ fusedText = one[0];
896
+ return offset + fusedText.length;
897
+ }
898
+ }
899
+ return -1;
900
+ }
901
+ let group = 1;
902
+ while (match[group] === void 0) group++;
903
+ fusedMember = run.members[group - 1];
904
+ fusedText = match[0];
905
+ return offset + fusedText.length;
906
+ }
484
907
  function longerAltEnd(def, text, offset) {
485
908
  const alt2 = def.longerAltDef;
486
909
  if (alt2.literal !== void 0) return text.startsWith(alt2.literal, offset) ? offset + alt2.literal.length : -1;
@@ -500,19 +923,30 @@ function createLexer(defs, options = {}) {
500
923
  outer: while (offset < length) {
501
924
  const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
502
925
  for (let i = 0; i < applicable.length; i++) {
503
- const def = applicable[i];
926
+ let def = applicable[i];
504
927
  let end = -1;
505
928
  let matched = null;
506
- if (def.literal !== void 0) {
929
+ if (def.members !== null) {
930
+ end = fusedEnd(def, text, offset);
931
+ if (end === -1) continue;
932
+ def = fusedMember;
933
+ matched = fusedText;
934
+ } else if (def.literal !== void 0) {
507
935
  if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
508
- } else if (def.skip) {
509
- def.regex.lastIndex = offset;
510
- if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
511
936
  } else {
512
- def.regex.lastIndex = offset;
513
- const match = def.regex.exec(text);
514
- if (match !== null && match[0].length > 0) matched = match[0];
515
- if (matched !== null) end = offset + matched.length;
937
+ end = def.scanner === null ? DEFER : scanEnd(def.scanner, text, offset, length);
938
+ if (end === DEFER) {
939
+ end = -1;
940
+ if (def.skip) {
941
+ def.regex.lastIndex = offset;
942
+ if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
943
+ } else {
944
+ def.regex.lastIndex = offset;
945
+ const match = def.regex.exec(text);
946
+ if (match !== null && match[0].length > 0) matched = match[0];
947
+ if (matched !== null) end = offset + matched.length;
948
+ }
949
+ }
516
950
  }
517
951
  if (end === -1) continue;
518
952
  let winner = def;
@@ -561,62 +995,238 @@ function createLexer(defs, options = {}) {
561
995
  }
562
996
  const inMode = (mode) => namesModes ? ` (in lexer mode '${mode.name}')` : "";
563
997
  const describe = (offset, line, lineStart) => trackLines ? `line ${line} column ${offset - lineStart + 1}` : `offset ${offset}`;
564
- return { tokenize, tokenNames, positions, modeNames };
565
- }
566
-
567
- // src/state.js
568
- var FAIL = /* @__PURE__ */ Symbol("grammatik.fail");
569
- var createState = (tokens, grammar, options = {}) => ({
570
- tokens,
571
- idx: 0,
572
- maxIdx: 0,
573
- expected: [],
574
- grammar,
575
- memo: options.memo ? /* @__PURE__ */ new Map() : null,
576
- // Entering a rule is one array store rather than an object allocation, and
577
- // the stack is snapshotted only on a furthest failure — by the time an error
578
- // is built it would otherwise have unwound to just the start rule.
579
- ruleNames: [],
580
- ruleDepth: 0,
581
- failureStack: null,
582
- track: options.track === true,
583
- steps: 0,
584
- maxSteps: options.maxSteps || 0
585
- });
586
- function expect(state, expectation) {
587
- if (state.idx > state.maxIdx) {
588
- state.maxIdx = state.idx;
589
- if (!state.track) return;
590
- state.expected = [];
591
- state.failureStack = state.ruleNames.slice(0, state.ruleDepth);
998
+ if (!options.incremental) return { tokenize, tokenNames, positions, modeNames };
999
+ const recover = !!options.recover;
1000
+ const errorTokenName = options.errorToken || "Error";
1001
+ function snapshotStack(stack, depth) {
1002
+ const ids = new Array(depth);
1003
+ for (let i = 0; i < depth; i++) ids[i] = stack[i].id;
1004
+ return ids;
592
1005
  }
593
- if (!state.track || state.idx !== state.maxIdx) return;
594
- if (!state.expected.includes(expectation)) state.expected.push(expectation);
595
- }
596
- var ParseError = class extends Error {
597
- /**
598
- * @param {string} message
599
- * @param {{ token: import('./lexer.js').Token | null, expected: string[], ruleStack: string[] }} details
600
- */
601
- constructor(message, details) {
602
- super(message);
603
- this.name = "ParseError";
604
- this.token = details.token;
605
- this.expected = details.expected;
606
- this.ruleStack = details.ruleStack;
1006
+ function sameStack(a, b) {
1007
+ if (a.length !== b.length) return false;
1008
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
1009
+ return true;
607
1010
  }
608
- };
609
- var describeExpected = (expected) => expected.length === 0 ? "nothing" : expected.length === 1 ? expected[0] : `one of [${expected.join(", ")}]`;
610
- function buildParseError(state) {
611
- const token = state.tokens[state.maxIdx] || null;
612
- const expected = [...state.expected].sort();
613
- const ruleStack = state.failureStack || state.ruleNames.slice(0, state.ruleDepth);
614
- const at = (t) => state.positions === "offset" ? `offset ${t.start}` : `line ${t.line} column ${t.col}`;
615
- const found = token ? `--> ${token.image} <-- (${token.type}) at ${at(token)}` : "end of input";
616
- return new ParseError(
617
- `Expecting ${describeExpected(expected)} but found ${found}`,
618
- { token, expected, ruleStack }
619
- );
1011
+ function scan(text, offset, mode, stack, depth, line, lineStart) {
1012
+ const tokens = [];
1013
+ const checkpoints = [];
1014
+ const length = text.length;
1015
+ outer: while (offset < length) {
1016
+ checkpoints.push({ modeId: mode.id, stack: snapshotStack(stack, depth), line, lineStart, offset });
1017
+ const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
1018
+ for (let i = 0; i < applicable.length; i++) {
1019
+ let def = applicable[i];
1020
+ let end = -1;
1021
+ let matched = null;
1022
+ if (def.members !== null) {
1023
+ end = fusedEnd(def, text, offset);
1024
+ if (end === -1) continue;
1025
+ def = fusedMember;
1026
+ matched = fusedText;
1027
+ } else if (def.literal !== void 0) {
1028
+ if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
1029
+ } else {
1030
+ end = def.scanner === null ? DEFER : scanEnd(def.scanner, text, offset, length);
1031
+ if (end === DEFER) {
1032
+ end = -1;
1033
+ if (def.skip) {
1034
+ def.regex.lastIndex = offset;
1035
+ if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
1036
+ } else {
1037
+ def.regex.lastIndex = offset;
1038
+ const match = def.regex.exec(text);
1039
+ if (match !== null && match[0].length > 0) matched = match[0];
1040
+ if (matched !== null) end = offset + matched.length;
1041
+ }
1042
+ }
1043
+ }
1044
+ if (end === -1) continue;
1045
+ let winner = def;
1046
+ if (def.longerAltDef) {
1047
+ const altEnd = longerAltEnd(def, text, offset);
1048
+ if (altEnd > end) {
1049
+ end = altEnd;
1050
+ winner = def.longerAltDef;
1051
+ }
1052
+ }
1053
+ if (!winner.skip) {
1054
+ const image = winner.literal !== void 0 ? winner.literal : winner === def && matched !== null ? matched : text.slice(offset, end);
1055
+ 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 });
1056
+ }
1057
+ if (trackLines) {
1058
+ for (let j = offset; j < end; j++) {
1059
+ if (text.charCodeAt(j) !== 10) continue;
1060
+ line++;
1061
+ lineStart = j + 1;
1062
+ }
1063
+ }
1064
+ if (multiMode) {
1065
+ if (winner.popMode) {
1066
+ if (depth === 0) {
1067
+ if (!recover) {
1068
+ throw new LexError(
1069
+ `Token '${winner.name}' popped the initial lexer mode at ${describe(offset, line, lineStart)}`,
1070
+ { offset, line: trackLines ? line : 0, col: trackLines ? offset - lineStart + 1 : 0 }
1071
+ );
1072
+ }
1073
+ } else {
1074
+ mode = stack[--depth];
1075
+ }
1076
+ } else if (winner.pushMode !== -1) {
1077
+ stack[depth++] = mode;
1078
+ mode = modes[winner.pushMode];
1079
+ }
1080
+ }
1081
+ offset = end;
1082
+ continue outer;
1083
+ }
1084
+ if (recover) {
1085
+ 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 });
1086
+ if (trackLines && text.charCodeAt(offset) === 10) {
1087
+ line++;
1088
+ lineStart = offset + 1;
1089
+ }
1090
+ offset++;
1091
+ continue outer;
1092
+ }
1093
+ const column = offset - lineStart + 1;
1094
+ throw new LexError(
1095
+ trackLines ? `Unexpected character '${text[offset]}' at line ${line} column ${column}${inMode(mode)}` : `Unexpected character '${text[offset]}' at offset ${offset}${inMode(mode)}`,
1096
+ { offset, line: trackLines ? line : 0, col: trackLines ? column : 0 }
1097
+ );
1098
+ }
1099
+ return { tokens, checkpoints, mode, stack, depth, line, lineStart };
1100
+ }
1101
+ function tokenizeIncremental(text) {
1102
+ if (typeof text !== "string") throw new TypeError("tokenizeIncremental: expected a string");
1103
+ const result = scan(text, 0, initial, multiMode ? [] : [], 0, 1, 0);
1104
+ 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 } };
1105
+ }
1106
+ function tokenFloor(tokens, offset) {
1107
+ let lo = 0;
1108
+ let hi = tokens.length;
1109
+ while (lo < hi) {
1110
+ const mid = lo + hi >>> 1;
1111
+ if (tokens[mid].start < offset) lo = mid + 1;
1112
+ else hi = mid;
1113
+ }
1114
+ return lo;
1115
+ }
1116
+ function checkpointFloor(checkpoints, eof, offset) {
1117
+ let lo = 0;
1118
+ let hi = checkpoints.length + 1;
1119
+ while (lo < hi) {
1120
+ const mid = lo + hi >>> 1;
1121
+ const at = mid < checkpoints.length ? checkpoints[mid].offset : eof.offset;
1122
+ if (at <= offset) lo = mid + 1;
1123
+ else hi = mid;
1124
+ }
1125
+ return lo - 1;
1126
+ }
1127
+ function checkpointAt(checkpoints, eof, offset) {
1128
+ if (eof.offset === offset) return eof;
1129
+ let lo = 0;
1130
+ let hi = checkpoints.length;
1131
+ while (lo < hi) {
1132
+ const mid = lo + hi >>> 1;
1133
+ if (checkpoints[mid].offset < offset) lo = mid + 1;
1134
+ else hi = mid;
1135
+ }
1136
+ return lo < checkpoints.length && checkpoints[lo].offset === offset ? checkpoints[lo] : null;
1137
+ }
1138
+ function relex(newText, prev, edit) {
1139
+ if (typeof newText !== "string") throw new TypeError("relex: expected a string");
1140
+ const tokens = prev.tokens;
1141
+ const checkpoints = prev.checkpoints;
1142
+ const delta = edit.newEnd - edit.oldEnd;
1143
+ let resumeIndex = checkpointFloor(checkpoints, prev.eof, edit.start);
1144
+ if (resumeIndex > 0) resumeIndex--;
1145
+ let boundary = resumeIndex < checkpoints.length ? checkpoints[resumeIndex].offset : prev.eof.offset;
1146
+ let ti = tokenFloor(tokens, boundary);
1147
+ while (ti > 0 && tokens[ti - 1].end === boundary) {
1148
+ ti--;
1149
+ boundary = tokens[ti].start;
1150
+ }
1151
+ if (boundary < (resumeIndex < checkpoints.length ? checkpoints[resumeIndex].offset : prev.eof.offset)) {
1152
+ resumeIndex = checkpointFloor(checkpoints, prev.eof, boundary);
1153
+ }
1154
+ const resume = resumeIndex < checkpoints.length ? checkpoints[resumeIndex] : prev.eof;
1155
+ const resumeMode = modes[resume.modeId];
1156
+ const resumeStack = resume.stack.map((id) => modes[id]);
1157
+ const scanned = scan(newText, resume.offset, resumeMode, resumeStack, resumeStack.length, resume.line, resume.lineStart);
1158
+ const scannedEof = { modeId: scanned.mode.id, stack: snapshotStack(scanned.stack, scanned.depth), line: scanned.line, lineStart: scanned.lineStart, offset: newText.length };
1159
+ const VERIFY_WINDOW = 4;
1160
+ function convergenceVerified(oldOffset, newOffset) {
1161
+ const oldStart = tokenFloor(tokens, oldOffset);
1162
+ const freshStart = tokenFloor(scanned.tokens, newOffset);
1163
+ for (let k = 0; k < VERIFY_WINDOW; k++) {
1164
+ const oldTok = tokens[oldStart + k];
1165
+ const freshTok = scanned.tokens[freshStart + k];
1166
+ if (oldTok === void 0 && freshTok === void 0) return true;
1167
+ if (oldTok === void 0 || freshTok === void 0) return false;
1168
+ if (oldTok.type !== freshTok.type || oldTok.image !== freshTok.image) return false;
1169
+ }
1170
+ return true;
1171
+ }
1172
+ let convergeNewOffset = -1;
1173
+ let convergeOldOffset = -1;
1174
+ let convergeOldCheckpoint = null;
1175
+ let freshCheckpointCount = 0;
1176
+ for (let s = 0; s <= scanned.checkpoints.length; s++) {
1177
+ const cp = s < scanned.checkpoints.length ? scanned.checkpoints[s] : scannedEof;
1178
+ if (cp.offset < edit.newEnd) continue;
1179
+ const oldOffset = cp.offset - delta;
1180
+ const oldCp = checkpointAt(checkpoints, prev.eof, oldOffset);
1181
+ if (oldCp && oldCp.modeId === cp.modeId && sameStack(oldCp.stack, cp.stack) && convergenceVerified(oldOffset, cp.offset)) {
1182
+ convergeNewOffset = cp.offset;
1183
+ convergeOldOffset = oldOffset;
1184
+ convergeOldCheckpoint = oldCp;
1185
+ freshCheckpointCount = s;
1186
+ break;
1187
+ }
1188
+ }
1189
+ const tokenFrom = tokenFloor(tokens, resume.offset);
1190
+ const checkpointFrom = resumeIndex;
1191
+ if (convergeNewOffset === -1) {
1192
+ tokens.splice(tokenFrom, tokens.length - tokenFrom, ...scanned.tokens);
1193
+ checkpoints.splice(checkpointFrom, checkpoints.length - checkpointFrom, ...scanned.checkpoints);
1194
+ prev.text = newText;
1195
+ prev.eof = scannedEof;
1196
+ return prev;
1197
+ }
1198
+ const freshTokenCount = tokenFloor(scanned.tokens, convergeNewOffset);
1199
+ const lineDelta = trackLines ? scanned.checkpoints.length > freshCheckpointCount ? scanned.checkpoints[freshCheckpointCount].line - convergeOldCheckpoint.line : scannedEof.line - convergeOldCheckpoint.line : 0;
1200
+ const boundaryOldLineStart = convergeOldCheckpoint.lineStart;
1201
+ const boundaryNewLineStart = (freshCheckpointCount < scanned.checkpoints.length ? scanned.checkpoints[freshCheckpointCount] : scannedEof).lineStart;
1202
+ const shiftedLineStart = (oldLineStart) => oldLineStart === boundaryOldLineStart ? boundaryNewLineStart : oldLineStart + delta;
1203
+ const tailTokenFrom = tokenFloor(tokens, convergeOldOffset);
1204
+ for (let i = tailTokenFrom; i < tokens.length; i++) {
1205
+ const t = tokens[i];
1206
+ if (trackLines) {
1207
+ const oldLineStart = t.start - (t.col - 1);
1208
+ t.col = t.start + delta - shiftedLineStart(oldLineStart) + 1;
1209
+ t.line += lineDelta;
1210
+ }
1211
+ t.start += delta;
1212
+ t.end += delta;
1213
+ }
1214
+ tokens.splice(tokenFrom, tailTokenFrom - tokenFrom, ...scanned.tokens.slice(0, freshTokenCount));
1215
+ const tailCheckpointFrom = checkpointFloor(checkpoints, prev.eof, convergeOldOffset - 1) + 1;
1216
+ for (let i = tailCheckpointFrom; i < checkpoints.length; i++) {
1217
+ const cp = checkpoints[i];
1218
+ cp.lineStart = shiftedLineStart(cp.lineStart);
1219
+ cp.line += lineDelta;
1220
+ cp.offset += delta;
1221
+ }
1222
+ checkpoints.splice(checkpointFrom, tailCheckpointFrom - checkpointFrom, ...scanned.checkpoints.slice(0, freshCheckpointCount));
1223
+ prev.text = newText;
1224
+ prev.eof.lineStart = shiftedLineStart(prev.eof.lineStart);
1225
+ prev.eof.line += lineDelta;
1226
+ prev.eof.offset += delta;
1227
+ return prev;
1228
+ }
1229
+ return { tokenize, tokenizeIncremental, relex, tokenNames, positions, modeNames };
620
1230
  }
621
1231
 
622
1232
  // src/analyze.js
@@ -649,6 +1259,7 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
649
1259
  "many",
650
1260
  "many1",
651
1261
  "option",
1262
+ "flag",
652
1263
  "manySep",
653
1264
  "many1Sep",
654
1265
  "infixLeft",
@@ -656,6 +1267,8 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
656
1267
  "postfix",
657
1268
  "prefix",
658
1269
  "between",
1270
+ "pick",
1271
+ "yields",
659
1272
  "as",
660
1273
  "oneOf",
661
1274
  "eof",
@@ -709,7 +1322,8 @@ function firstOf(node, ruleFirsts) {
709
1322
  return result;
710
1323
  }
711
1324
  case "many":
712
- case "option": {
1325
+ case "option":
1326
+ case "flag": {
713
1327
  const inner = firstOf(arg, ruleFirsts);
714
1328
  return { tokens: inner.tokens, nullable: true, unknown: inner.unknown };
715
1329
  }
@@ -732,6 +1346,9 @@ function firstOf(node, ruleFirsts) {
732
1346
  return firstOf(Array.isArray(arg) ? arg[0] : arg && arg.parser, ruleFirsts);
733
1347
  case "between":
734
1348
  return firstOf({ seq: [arg && arg.open, arg && arg.body, arg && arg.close] }, ruleFirsts);
1349
+ case "pick":
1350
+ case "yields":
1351
+ return firstOf({ seq: arg && Array.isArray(arg.parsers) ? arg.parsers : [] }, ruleFirsts);
735
1352
  case "prefix": {
736
1353
  const ops = firstOf(arg && arg.operator, ruleFirsts);
737
1354
  const inner = firstOf(arg && arg.operand, ruleFirsts);
@@ -795,7 +1412,36 @@ function recognizeSeqIdiom(children) {
795
1412
  if (rewritten !== BAIL) return { as: [parser, rewritten] };
796
1413
  }
797
1414
  }
798
- return null;
1415
+ if (children.length >= 2) {
1416
+ const last = children[children.length - 1];
1417
+ const head = children.slice(0, -1);
1418
+ if (opOf(last) === "action" && isConstant(last.action) && head.every(isPlainParser) && !head.some(usesOuterScope)) {
1419
+ return { yields: { parsers: head, value: last.action } };
1420
+ }
1421
+ if (opOf(last) === "action" && opOf(last.action) === "val" && typeof last.action.val === "string") {
1422
+ const name = last.action.val;
1423
+ const labelled = head.map((child, i) => opOf(child) === "label" ? i : -1).filter((i) => i !== -1);
1424
+ if (labelled.length === 1 && head[labelled[0]].label[0] === name && head.every((child, i) => i === labelled[0] || isPlainParser(child)) && !head.some(usesOuterScope)) {
1425
+ return { pick: { parsers: head.map((child, i) => i === labelled[0] ? child.label[1] : child), index: labelled[0] } };
1426
+ }
1427
+ }
1428
+ }
1429
+ return null;
1430
+ }
1431
+ var isConstant = (value) => value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1432
+ function usesOuterScope(logic) {
1433
+ if (Array.isArray(logic)) return logic.some(usesOuterScope);
1434
+ if (!logic || typeof logic !== "object") return false;
1435
+ for (const key of Object.keys(logic)) {
1436
+ const arg = logic[key];
1437
+ if (key === "val" && Array.isArray(arg) && Array.isArray(arg[0])) return true;
1438
+ if (key === "var") {
1439
+ if (typeof arg === "string" && arg.includes("../")) return true;
1440
+ if (Array.isArray(arg) && typeof arg[0] === "string" && arg[0].includes("../")) return true;
1441
+ }
1442
+ if (usesOuterScope(arg)) return true;
1443
+ }
1444
+ return false;
799
1445
  }
800
1446
  function isPlainParser(node) {
801
1447
  const op = opOf(node);
@@ -842,6 +1488,18 @@ function recognizeAltPrefixIdiom(branches) {
842
1488
  ]
843
1489
  };
844
1490
  }
1491
+ function recognizeAltConsumeIdiom(branches) {
1492
+ if (!Array.isArray(branches) || branches.length < 2) return null;
1493
+ const names = [];
1494
+ for (const branch of branches) {
1495
+ if (opOf(branch) !== "consume") return null;
1496
+ const arg = branch.consume;
1497
+ const name = typeof arg === "string" ? arg : Array.isArray(arg) && arg.length === 1 ? arg[0] : null;
1498
+ if (typeof name !== "string") return null;
1499
+ names.push(name);
1500
+ }
1501
+ return { oneOf: names };
1502
+ }
845
1503
  var BAIL = /* @__PURE__ */ Symbol("grammatik.bail");
846
1504
  function rebaseOnLabel(logic, name) {
847
1505
  if (Array.isArray(logic)) {
@@ -908,6 +1566,7 @@ function leftmostCalls(node, ruleFirsts, out) {
908
1566
  case "many":
909
1567
  case "many1":
910
1568
  case "option":
1569
+ case "flag":
911
1570
  case "lookahead":
912
1571
  case "negLookahead":
913
1572
  leftmostCalls(arg, ruleFirsts, out);
@@ -918,6 +1577,10 @@ function leftmostCalls(node, ruleFirsts, out) {
918
1577
  case "between":
919
1578
  if (arg) leftmostCalls({ seq: [arg.open, arg.body, arg.close] }, ruleFirsts, out);
920
1579
  break;
1580
+ case "pick":
1581
+ case "yields":
1582
+ if (arg && Array.isArray(arg.parsers)) leftmostCalls({ seq: arg.parsers }, ruleFirsts, out);
1583
+ break;
921
1584
  case "prefix":
922
1585
  if (arg) {
923
1586
  leftmostCalls(arg.operator, ruleFirsts, out);
@@ -984,6 +1647,7 @@ function validateNode(node, grammar, tokenNames, errors, path) {
984
1647
  case "many":
985
1648
  case "many1":
986
1649
  case "option":
1650
+ case "flag":
987
1651
  case "text":
988
1652
  case "lookahead":
989
1653
  case "negLookahead":
@@ -1005,6 +1669,26 @@ function validateNode(node, grammar, tokenNames, errors, path) {
1005
1669
  }
1006
1670
  break;
1007
1671
  }
1672
+ case "pick": {
1673
+ if (!arg || !Array.isArray(arg.parsers) || arg.parsers.length === 0) {
1674
+ errors.push(`${path}: 'pick' expects { parsers: [parser, ...], index }`);
1675
+ } else if (!Number.isInteger(arg.index) || arg.index < 0 || arg.index >= arg.parsers.length) {
1676
+ errors.push(`${path}: 'pick' index ${JSON.stringify(arg.index)} is out of range for ${arg.parsers.length} parser(s)`);
1677
+ } else {
1678
+ arg.parsers.forEach((c, i) => validateNode(c, grammar, tokenNames, errors, `${path}.pick[${i}]`));
1679
+ }
1680
+ break;
1681
+ }
1682
+ case "yields": {
1683
+ if (!arg || !Array.isArray(arg.parsers) || arg.parsers.length === 0 || !("value" in arg)) {
1684
+ errors.push(`${path}: 'yields' expects { parsers: [parser, ...], value }`);
1685
+ } else if (!isConstant(arg.value)) {
1686
+ errors.push(`${path}: 'yields' value must be a string, number, boolean or null, got ${JSON.stringify(arg.value)}`);
1687
+ } else {
1688
+ arg.parsers.forEach((c, i) => validateNode(c, grammar, tokenNames, errors, `${path}.yields[${i}]`));
1689
+ }
1690
+ break;
1691
+ }
1008
1692
  case "prefix": {
1009
1693
  if (!arg || typeof arg !== "object" || !("operator" in arg) || !("operand" in arg) || !("combine" in arg)) {
1010
1694
  errors.push(`${path}: 'prefix' expects { operator, operand, combine }`);
@@ -1131,20 +1815,6 @@ function declareSync(fn) {
1131
1815
  fn[SYNC] = true;
1132
1816
  return fn;
1133
1817
  }
1134
- function usesOuterScope(logic) {
1135
- if (Array.isArray(logic)) return logic.some(usesOuterScope);
1136
- if (!logic || typeof logic !== "object") return false;
1137
- for (const key of Object.keys(logic)) {
1138
- const arg = logic[key];
1139
- if (key === "val" && Array.isArray(arg) && Array.isArray(arg[0])) return true;
1140
- if (key === "var") {
1141
- if (typeof arg === "string" && arg.includes("../")) return true;
1142
- if (Array.isArray(arg) && typeof arg[0] === "string" && arg[0].includes("../")) return true;
1143
- }
1144
- if (usesOuterScope(arg)) return true;
1145
- }
1146
- return false;
1147
- }
1148
1818
  function mayReturnContext(logic) {
1149
1819
  if (Array.isArray(logic)) return logic.some(mayReturnContext);
1150
1820
  if (!logic || typeof logic !== "object") return false;
@@ -1157,7 +1827,7 @@ function mayReturnContext(logic) {
1157
1827
  return false;
1158
1828
  }
1159
1829
  function createPlanner(config) {
1160
- const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null } = config;
1830
+ const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), idioms = true } = config;
1161
1831
  const slots = /* @__PURE__ */ new Map();
1162
1832
  const planCache = /* @__PURE__ */ new WeakMap();
1163
1833
  const opCaches = /* @__PURE__ */ new Map();
@@ -1304,6 +1974,17 @@ function createPlanner(config) {
1304
1974
  return value;
1305
1975
  };
1306
1976
  };
1977
+ const buildFlag = (arg) => {
1978
+ const child = plan(arg);
1979
+ return (state) => {
1980
+ const start = state.idx;
1981
+ if (child(state) === FAIL) {
1982
+ state.idx = start;
1983
+ return false;
1984
+ }
1985
+ return true;
1986
+ };
1987
+ };
1307
1988
  const buildInfixLeft = (arg) => {
1308
1989
  const operand = plan(arg.operand);
1309
1990
  const operator = arg.operator === void 0 ? null : plan(arg.operator);
@@ -1429,6 +2110,26 @@ function createPlanner(config) {
1429
2110
  return value;
1430
2111
  };
1431
2112
  };
2113
+ const buildSequence = (arg, pickIndex, constant) => {
2114
+ const fns = arg.parsers.map(plan);
2115
+ const count = fns.length;
2116
+ return (state) => {
2117
+ tick(state);
2118
+ const start = state.idx;
2119
+ let picked;
2120
+ for (let i = 0; i < count; i++) {
2121
+ const value = fns[i](state);
2122
+ if (value === FAIL) {
2123
+ state.idx = start;
2124
+ return FAIL;
2125
+ }
2126
+ if (i === pickIndex) picked = value;
2127
+ }
2128
+ return pickIndex === -1 ? constant : picked;
2129
+ };
2130
+ };
2131
+ const buildPick = (arg) => buildSequence(arg, arg.index, void 0);
2132
+ const buildYields = (arg) => buildSequence(arg, -1, arg.value);
1432
2133
  const buildPrefix = (arg) => {
1433
2134
  const operator = plan(arg.operator);
1434
2135
  const operand = plan(arg.operand);
@@ -1512,6 +2213,31 @@ function createPlanner(config) {
1512
2213
  return value;
1513
2214
  };
1514
2215
  }
2216
+ const memoSlot = memoSlots.get(name);
2217
+ if (memoSlot !== void 0) {
2218
+ return (state) => {
2219
+ tick(state);
2220
+ const table = state.rm[memoSlot];
2221
+ const at = state.idx;
2222
+ const end = table.ends[at];
2223
+ if (end !== 0) {
2224
+ state.idx = end - 1;
2225
+ return table.vals[at];
2226
+ }
2227
+ let depth = 0;
2228
+ if (state.track) {
2229
+ depth = state.ruleDepth;
2230
+ state.ruleNames[depth] = name;
2231
+ state.ruleDepth = depth + 1;
2232
+ }
2233
+ const value = slot.fn(state);
2234
+ if (state.track) state.ruleDepth = depth;
2235
+ if (value === FAIL) state.idx = at;
2236
+ table.ends[at] = state.idx + 1;
2237
+ table.vals[at] = value;
2238
+ return value;
2239
+ };
2240
+ }
1515
2241
  return (state) => {
1516
2242
  tick(state);
1517
2243
  if (!state.track) return slot.fn(state);
@@ -1590,6 +2316,9 @@ function createPlanner(config) {
1590
2316
  infixRight: buildInfixRight,
1591
2317
  as: buildAs,
1592
2318
  between: buildBetween,
2319
+ flag: buildFlag,
2320
+ pick: buildPick,
2321
+ yields: buildYields,
1593
2322
  prefix: buildPrefix,
1594
2323
  postfix: buildPostfix,
1595
2324
  manySep: (arg) => planSeparated(arg, false),
@@ -1609,7 +2338,7 @@ function createPlanner(config) {
1609
2338
  }
1610
2339
  function planSeq(children) {
1611
2340
  if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
1612
- const idiom = recognizeSeqIdiom(children);
2341
+ const idiom = idioms ? recognizeSeqIdiom(children) : null;
1613
2342
  if (idiom !== null) return planUncached(idiom);
1614
2343
  const PARSER = 0;
1615
2344
  const LABELLED = 1;
@@ -1723,7 +2452,9 @@ function createPlanner(config) {
1723
2452
  }
1724
2453
  function planAlt(branches) {
1725
2454
  if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
1726
- const factored = recognizeAltPrefixIdiom(branches);
2455
+ const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
2456
+ if (oneOf !== null) return planUncached(oneOf);
2457
+ const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
1727
2458
  if (factored !== null) return planUncached(factored);
1728
2459
  const planned = branches.map(plan);
1729
2460
  const table = firsts ? buildDispatch(branches, firsts) : null;
@@ -1845,6 +2576,9 @@ var OPERATORS = [
1845
2576
  "prefix",
1846
2577
  "postfix",
1847
2578
  "between",
2579
+ "pick",
2580
+ "yields",
2581
+ "flag",
1848
2582
  "as",
1849
2583
  "subrule",
1850
2584
  "label",
@@ -1899,7 +2633,7 @@ var createValueMethods = () => ({
1899
2633
 
1900
2634
  // src/codegen.js
1901
2635
  function createCodegen(config) {
1902
- const { planner, rules, firsts, memo, maxSteps, tokenIds = null } = config;
2636
+ const { planner, rules, firsts, memo, maxSteps, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), inlineRules = true, idioms = true } = config;
1903
2637
  const typeCheck = (tokenVar, name) => {
1904
2638
  const id = tokenIds ? tokenIds.get(name) : void 0;
1905
2639
  if (id === void 0) return `${tokenVar}.type === ${JSON.stringify(name)}`;
@@ -1920,6 +2654,33 @@ function createCodegen(config) {
1920
2654
  }
1921
2655
  const actionHelper = (logic) => helper(planner.planAction(logic), { kind: "action", logic });
1922
2656
  const constantHelper = (node) => helper(planner.plan(node), { kind: "constant", node });
2657
+ const MAX_INLINE_DEPTH = 6;
2658
+ const ONE_OF_SWITCH_AT = 12;
2659
+ const INLINE_OPS = /* @__PURE__ */ new Set(["infixLeft", "infixRight", "prefix", "postfix"]);
2660
+ const callSites = /* @__PURE__ */ new Map();
2661
+ (function count(node) {
2662
+ if (Array.isArray(node)) {
2663
+ for (const child of node) count(child);
2664
+ return;
2665
+ }
2666
+ if (!node || typeof node !== "object") return;
2667
+ if ("subrule" in node) {
2668
+ const target = Array.isArray(node.subrule) ? node.subrule[0] : node.subrule;
2669
+ if (typeof target === "string") callSites.set(target, (callSites.get(target) || 0) + 1);
2670
+ }
2671
+ for (const key of Object.keys(node)) count(node[key]);
2672
+ })(rules);
2673
+ const inlining = /* @__PURE__ */ new Set();
2674
+ let coldDepth = 0;
2675
+ const cold = (write) => {
2676
+ coldDepth++;
2677
+ try {
2678
+ return write();
2679
+ } finally {
2680
+ coldDepth--;
2681
+ }
2682
+ };
2683
+ const canInline = (name) => inlineRules && coldDepth === 0 && !memo && !memoSlots.has(name) && !inlining.has(name) && inlining.size < MAX_INLINE_DEPTH && callSites.get(name) === 1 && INLINE_OPS.has(opOf(rules[name]));
1923
2684
  function indexOfRule(name) {
1924
2685
  let index = ruleIndex.get(name);
1925
2686
  if (index !== void 0) return index;
@@ -1980,6 +2741,16 @@ function createCodegen(config) {
1980
2741
  );
1981
2742
  return result;
1982
2743
  }
2744
+ case "flag": {
2745
+ const start = nextId();
2746
+ out.push(`let ${result};`, `const ${start} = c.idx;`, "{");
2747
+ const value = gen(arg, out, bindingsVar);
2748
+ out.push(
2749
+ `if (${value} === F) { c.idx = ${start}; ${result} = false; } else ${result} = true;`,
2750
+ "}"
2751
+ );
2752
+ return result;
2753
+ }
1983
2754
  case "many":
1984
2755
  case "many1": {
1985
2756
  const atLeastOne = op === "many1";
@@ -1997,7 +2768,7 @@ function createCodegen(config) {
1997
2768
  );
1998
2769
  }
1999
2770
  out.push("for (;;) {", `const ${before} = c.idx;`);
2000
- const value = gen(arg, out, bindingsVar);
2771
+ const value = atLeastOne ? cold(() => gen(arg, out, bindingsVar)) : gen(arg, out, bindingsVar);
2001
2772
  out.push(
2002
2773
  `if (${value} === F) { c.idx = ${before}; break; }`,
2003
2774
  `if (c.idx === ${before}) P(${JSON.stringify(op)}, ${before});`,
@@ -2027,7 +2798,7 @@ function createCodegen(config) {
2027
2798
  operatorVar = gen(arg.operator, out, bindingsVar);
2028
2799
  out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
2029
2800
  }
2030
- const right = gen(arg.operand, out, bindingsVar);
2801
+ const right = cold(() => gen(arg.operand, out, bindingsVar));
2031
2802
  out.push(
2032
2803
  `if (${right} === F) { c.idx = ${mark}; break; }`,
2033
2804
  `if (${bindings} === null) ${bindings} = { left: null, op: null, right: null };`,
@@ -2042,12 +2813,27 @@ function createCodegen(config) {
2042
2813
  case "oneOf": {
2043
2814
  const names = Array.isArray(arg) ? arg : [arg];
2044
2815
  const token = nextId();
2816
+ const ids = tokenIds ? names.map((n) => tokenIds.get(n)) : [];
2817
+ const failed = `else { ${names.map((n) => `E(c, ${JSON.stringify(n)});`).join(" ")} ${result} = F; }`;
2818
+ if (names.length > ONE_OF_SWITCH_AT && ids.every((id) => id !== void 0)) {
2819
+ const hit = nextId();
2820
+ const cases = [...new Set(ids)].map((id) => `case ${id}:`).join(" ");
2821
+ out.push(
2822
+ `let ${result};`,
2823
+ `const ${token} = c.tokens[c.idx];`,
2824
+ `let ${hit} = false;`,
2825
+ `if (${token} !== undefined) { switch (${token}.id !== undefined ? ${token}.id : I[${token}.type]) { ${cases} ${hit} = true; } }`,
2826
+ `if (${hit}) { c.idx++; ${result} = ${token}; }`,
2827
+ failed
2828
+ );
2829
+ return result;
2830
+ }
2045
2831
  const test = names.map((n) => typeCheck(token, n)).join(" || ");
2046
2832
  out.push(
2047
2833
  `let ${result};`,
2048
2834
  `const ${token} = c.tokens[c.idx];`,
2049
2835
  `if (${token} !== undefined && (${test})) { c.idx++; ${result} = ${token}; }`,
2050
- `else { ${names.map((n) => `E(c, ${JSON.stringify(n)});`).join(" ")} ${result} = F; }`
2836
+ failed
2051
2837
  );
2052
2838
  return result;
2053
2839
  }
@@ -2080,7 +2866,7 @@ function createCodegen(config) {
2080
2866
  operatorVar = gen(arg.operator, out, bindingsVar);
2081
2867
  out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
2082
2868
  }
2083
- const right = gen(arg.operand, out, bindingsVar);
2869
+ const right = cold(() => gen(arg.operand, out, bindingsVar));
2084
2870
  out.push(
2085
2871
  `if (${right} === F) { c.idx = ${mark}; break; }`,
2086
2872
  `if (${items} === null) ${items} = [];`,
@@ -2155,6 +2941,19 @@ function createCodegen(config) {
2155
2941
  );
2156
2942
  return result;
2157
2943
  }
2944
+ case "pick":
2945
+ case "yields": {
2946
+ const label2 = "L" + nextId();
2947
+ const start = nextId();
2948
+ out.push(tickSrc, `let ${result};`, `${label2}: {`, `const ${start} = c.idx;`);
2949
+ const values = arg.parsers.map((parser) => {
2950
+ const value = gen(parser, out, bindingsVar);
2951
+ out.push(`if (${value} === F) { c.idx = ${start}; ${result} = F; break ${label2}; }`);
2952
+ return value;
2953
+ });
2954
+ out.push(`${result} = ${op === "pick" ? values[arg.index] : JSON.stringify(arg.value)};`, "}");
2955
+ return result;
2956
+ }
2158
2957
  case "prefix": {
2159
2958
  const label2 = "L" + nextId();
2160
2959
  const start = nextId();
@@ -2231,7 +3030,7 @@ function createCodegen(config) {
2231
3030
  );
2232
3031
  const sep = gen(sepNode, out, bindingsVar);
2233
3032
  out.push(`if (${sep} === F) { c.idx = ${beforeSep}; break; }`, `const ${beforeItem} = c.idx;`);
2234
- const item = gen(arg.rule, out, bindingsVar);
3033
+ const item = cold(() => gen(arg.rule, out, bindingsVar));
2235
3034
  out.push(
2236
3035
  `if (${item} === F) { c.idx = ${trailing ? beforeItem : beforeSep}; break; }`,
2237
3036
  `${result}.push(${item});`,
@@ -2246,8 +3045,20 @@ function createCodegen(config) {
2246
3045
  out.push(`const ${result} = ${helper(planner.plan(node), { kind: "unknownRule", name })}(c);`);
2247
3046
  return result;
2248
3047
  }
2249
- const index = indexOfRule(name);
2250
3048
  const parent = nextId();
3049
+ if (canInline(name)) {
3050
+ out.push(tickSrc);
3051
+ out.push(
3052
+ `const ${parent} = c.ruleDepth;`,
3053
+ `if (c.track) { c.ruleNames[${parent}] = ${JSON.stringify(name)}; c.ruleDepth = ${parent} + 1; }`
3054
+ );
3055
+ inlining.add(name);
3056
+ const inner = gen(rules[name], out, null);
3057
+ inlining.delete(name);
3058
+ out.push(`if (c.track) c.ruleDepth = ${parent};`);
3059
+ return inner;
3060
+ }
3061
+ const index = indexOfRule(name);
2251
3062
  out.push(tickSrc);
2252
3063
  if (memo) {
2253
3064
  const key = nextId();
@@ -2268,6 +3079,26 @@ function createCodegen(config) {
2268
3079
  `c.memo.set(${key}, { value: ${result}, end: c.idx });`,
2269
3080
  "}"
2270
3081
  );
3082
+ } else if (memoSlots.has(name)) {
3083
+ const table = nextId();
3084
+ const at = nextId();
3085
+ const end = nextId();
3086
+ out.push(
3087
+ `let ${result};`,
3088
+ `const ${table} = c.rm[${memoSlots.get(name)}];`,
3089
+ `const ${at} = c.idx;`,
3090
+ `const ${end} = ${table}.ends[${at}];`,
3091
+ `if (${end} !== 0) { c.idx = ${end} - 1; ${result} = ${table}.vals[${at}]; }`,
3092
+ "else {",
3093
+ `const ${parent} = c.ruleDepth;`,
3094
+ `if (c.track) { c.ruleNames[${parent}] = ${JSON.stringify(name)}; c.ruleDepth = ${parent} + 1; }`,
3095
+ `${result} = R[${index}](c);`,
3096
+ `if (c.track) c.ruleDepth = ${parent};`,
3097
+ `if (${result} === F) c.idx = ${at};`,
3098
+ `${table}.ends[${at}] = c.idx + 1;`,
3099
+ `${table}.vals[${at}] = ${result};`,
3100
+ "}"
3101
+ );
2271
3102
  } else {
2272
3103
  out.push(
2273
3104
  `const ${parent} = c.ruleDepth;`,
@@ -2285,7 +3116,7 @@ function createCodegen(config) {
2285
3116
  }
2286
3117
  function genSeq(children, out, result) {
2287
3118
  if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
2288
- const idiom = recognizeSeqIdiom(children);
3119
+ const idiom = idioms ? recognizeSeqIdiom(children) : null;
2289
3120
  if (idiom !== null) return gen(idiom, out, null);
2290
3121
  let hasLabels = false;
2291
3122
  let hasAction = false;
@@ -2336,7 +3167,9 @@ function createCodegen(config) {
2336
3167
  }
2337
3168
  function genAlt(branches, out, result, bindingsVar) {
2338
3169
  if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
2339
- const factored = recognizeAltPrefixIdiom(branches);
3170
+ const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
3171
+ if (oneOf !== null) return gen(oneOf, out, bindingsVar);
3172
+ const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
2340
3173
  if (factored !== null) return gen(factored, out, bindingsVar);
2341
3174
  const table = firsts ? buildDispatch(branches, firsts) : null;
2342
3175
  const label2 = "L" + nextId();
@@ -2430,6 +3263,165 @@ return ${value};
2430
3263
  return { compile, functionFor, sourceFor, ruleIndex, RULES, SOURCES, ORIGINS, guard };
2431
3264
  }
2432
3265
 
3266
+ // src/memoplan.js
3267
+ function selectMemoRules(grammar, firsts, options = {}) {
3268
+ const ll1 = options.ll1 !== false;
3269
+ const names = Object.keys(grammar);
3270
+ const leftEdges = /* @__PURE__ */ new Map();
3271
+ const callEdges = /* @__PURE__ */ new Map();
3272
+ for (const name of names) {
3273
+ const left = /* @__PURE__ */ new Set();
3274
+ leftmostCalls(grammar[name], firsts, left);
3275
+ leftEdges.set(name, [...left].filter((r) => r in grammar));
3276
+ const calls = /* @__PURE__ */ new Set();
3277
+ walk(grammar[name], (node) => {
3278
+ if (opOf(node) !== "subrule") return;
3279
+ const arg = node.subrule;
3280
+ const target = Array.isArray(arg) ? arg[0] : arg;
3281
+ if (typeof target === "string" && target in grammar) calls.add(target);
3282
+ });
3283
+ callEdges.set(name, [...calls]);
3284
+ }
3285
+ const closure = (edges, from) => {
3286
+ const seen = /* @__PURE__ */ new Set();
3287
+ const stack = [...from];
3288
+ while (stack.length > 0) {
3289
+ const next = stack.pop();
3290
+ if (seen.has(next)) continue;
3291
+ seen.add(next);
3292
+ for (const edge of edges.get(next) || []) stack.push(edge);
3293
+ }
3294
+ return seen;
3295
+ };
3296
+ const leftCache = /* @__PURE__ */ new Map();
3297
+ const leftReach = (name) => {
3298
+ let reach = leftCache.get(name);
3299
+ if (reach === void 0) {
3300
+ reach = closure(leftEdges, leftEdges.get(name) || []);
3301
+ leftCache.set(name, reach);
3302
+ }
3303
+ return reach;
3304
+ };
3305
+ const startsWith = (branch) => {
3306
+ const direct = /* @__PURE__ */ new Set();
3307
+ leftmostCalls(branch, firsts, direct);
3308
+ const all = /* @__PURE__ */ new Set();
3309
+ for (const rule of direct) {
3310
+ if (!(rule in grammar)) continue;
3311
+ all.add(rule);
3312
+ for (const deeper of leftReach(rule)) all.add(deeper);
3313
+ }
3314
+ return all;
3315
+ };
3316
+ const sites = [];
3317
+ for (const name of names) {
3318
+ walk(grammar[name], (node) => {
3319
+ if (opOf(node) !== "alt" || !Array.isArray(node.alt) || node.alt.length < 2) return;
3320
+ for (const group of collisionGroups(node.alt, firsts, ll1)) {
3321
+ const counts = /* @__PURE__ */ new Map();
3322
+ for (const branch of group) {
3323
+ for (const rule of startsWith(branch)) counts.set(rule, (counts.get(rule) || 0) + 1);
3324
+ }
3325
+ const shared = [...counts].filter(([, n]) => n >= 2).map(([rule]) => rule);
3326
+ const topmost = shared.filter((rule) => !shared.some((other) => other !== rule && leftReach(other).has(rule) && !leftReach(rule).has(other)));
3327
+ if (topmost.length > 0) sites.push({ host: name, rules: topmost });
3328
+ }
3329
+ });
3330
+ }
3331
+ const hosts = new Set(sites.map((site) => site.host));
3332
+ const chosen = /* @__PURE__ */ new Set();
3333
+ for (const { rules } of sites) {
3334
+ for (const rule of rules) {
3335
+ const inside = closure(callEdges, [rule]);
3336
+ inside.add(rule);
3337
+ for (const host of hosts) {
3338
+ if (inside.has(host)) {
3339
+ chosen.add(rule);
3340
+ break;
3341
+ }
3342
+ }
3343
+ }
3344
+ }
3345
+ const dependsOnCaller = (name) => {
3346
+ const reachable = closure(callEdges, [name]);
3347
+ reachable.add(name);
3348
+ for (const rule of reachable) if (usesOuterScope(grammar[rule])) return true;
3349
+ return false;
3350
+ };
3351
+ return names.filter((name) => chosen.has(name) && !dependsOnCaller(name));
3352
+ }
3353
+ function collisionGroups(branches, firsts, ll1) {
3354
+ const table = ll1 ? buildDispatch(branches, firsts) : null;
3355
+ if (table === null) return [branches];
3356
+ const groups = /* @__PURE__ */ new Set();
3357
+ for (const entry of table.values()) if (Array.isArray(entry) && entry.length > 1) groups.add(entry);
3358
+ return [...groups];
3359
+ }
3360
+ function walk(node, visit) {
3361
+ if (Array.isArray(node)) {
3362
+ for (const child of node) walk(child, visit);
3363
+ return;
3364
+ }
3365
+ const op = opOf(node);
3366
+ if (op === null) return;
3367
+ visit(node);
3368
+ const arg = node[op];
3369
+ switch (op) {
3370
+ case "seq":
3371
+ case "alt":
3372
+ if (Array.isArray(arg)) for (const child of arg) walk(child, visit);
3373
+ break;
3374
+ case "label":
3375
+ if (Array.isArray(arg)) walk(arg[1], visit);
3376
+ break;
3377
+ case "many":
3378
+ case "many1":
3379
+ case "option":
3380
+ case "flag":
3381
+ case "text":
3382
+ case "lookahead":
3383
+ case "negLookahead":
3384
+ walk(arg, visit);
3385
+ break;
3386
+ case "as":
3387
+ walk(Array.isArray(arg) ? arg[0] : arg && arg.parser, visit);
3388
+ break;
3389
+ case "between":
3390
+ if (arg) for (const part of [arg.open, arg.body, arg.close]) walk(part, visit);
3391
+ break;
3392
+ case "pick":
3393
+ case "yields":
3394
+ if (arg && Array.isArray(arg.parsers)) for (const part of arg.parsers) walk(part, visit);
3395
+ break;
3396
+ case "prefix":
3397
+ if (arg) {
3398
+ walk(arg.operator, visit);
3399
+ walk(arg.operand, visit);
3400
+ }
3401
+ break;
3402
+ case "postfix":
3403
+ if (arg) {
3404
+ walk(arg.operand, visit);
3405
+ walk(arg.suffix, visit);
3406
+ }
3407
+ break;
3408
+ case "infixLeft":
3409
+ case "infixRight":
3410
+ if (arg) {
3411
+ walk(arg.operand, visit);
3412
+ if (arg.operator !== void 0) walk(arg.operator, visit);
3413
+ }
3414
+ break;
3415
+ case "manySep":
3416
+ case "many1Sep":
3417
+ if (arg) {
3418
+ walk(arg.rule, visit);
3419
+ if (typeof arg.sep !== "string") walk(arg.sep, visit);
3420
+ }
3421
+ break;
3422
+ }
3423
+ }
3424
+
2433
3425
  // src/parser.js
2434
3426
  var EXECUTION_MODES = /* @__PURE__ */ new Set(["generated", "interpreted"]);
2435
3427
  var GrammarError = class extends Error {
@@ -2445,6 +3437,9 @@ function createParser(spec, options = {}) {
2445
3437
  const { tokens: tokenDefs, rules, start } = spec;
2446
3438
  const {
2447
3439
  memo = false,
3440
+ autoMemo = true,
3441
+ inlineRules = true,
3442
+ idioms = true,
2448
3443
  strict = true,
2449
3444
  validate = true,
2450
3445
  maxSteps = 0,
@@ -2472,9 +3467,11 @@ function createParser(spec, options = {}) {
2472
3467
  const startRule = rules[start];
2473
3468
  const firsts = ll1 ? analysis.firsts : null;
2474
3469
  const tokenIds = new Map(lexer.tokenNames.map((name, id) => [name, id]));
2475
- const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval, tokenIds });
3470
+ const memoRules = autoMemo && !memo && analysis.errors.length === 0 ? selectMemoRules(rules, analysis.firsts, { ll1 }) : [];
3471
+ const memoSlots = new Map(memoRules.map((name, slot) => [name, slot]));
3472
+ const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval, tokenIds, memoSlots, idioms });
2476
3473
  engine.__jlGrammar = { planner };
2477
- const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds }) : null;
3474
+ const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds, memoSlots, inlineRules, idioms }) : null;
2478
3475
  const compiledStart = codegen && codegen.compile(start);
2479
3476
  if (codegen && !engine.disableInterpretedOptimization) {
2480
3477
  for (const name of Object.keys(rules)) {
@@ -2483,7 +3480,7 @@ function createParser(spec, options = {}) {
2483
3480
  }
2484
3481
  }
2485
3482
  function parseTokens(tokens, source, track = false) {
2486
- const state = createState(tokens, rules, { memo, maxSteps, track });
3483
+ const state = createState(tokens, rules, { memo, maxSteps, track, memoSlots: memoRules.length });
2487
3484
  state.firsts = firsts;
2488
3485
  state.source = source;
2489
3486
  state.positions = lexer.positions;
@@ -2513,6 +3510,8 @@ function createParser(spec, options = {}) {
2513
3510
  planner,
2514
3511
  codegen,
2515
3512
  execution: mode,
3513
+ /** Rules memoized by `autoMemo`, for introspection. */
3514
+ memoRules,
2516
3515
  compiled: Boolean(compiledStart),
2517
3516
  /** Rules whose `alt`s resolved to O(1) LL(1) dispatch, for introspection. */
2518
3517
  get firsts() {
@@ -2818,6 +3817,7 @@ var CONSTRUCTORS = {
2818
3817
  many: { arity: [1, 1], of: ([a]) => ({ many: a }) },
2819
3818
  many1: { arity: [1, 1], of: ([a]) => ({ many1: a }) },
2820
3819
  option: { arity: [1, 1], of: ([a]) => ({ option: a }) },
3820
+ flag: { arity: [1, 1], of: ([a]) => ({ flag: a }) },
2821
3821
  action: { arity: [1, 1], of: ([a]) => ({ action: a }) },
2822
3822
  lookahead: { arity: [1, 1], of: ([a]) => ({ lookahead: a }) },
2823
3823
  negLookahead: { arity: [1, 1], of: ([a]) => ({ negLookahead: a }) },
@@ -2829,6 +3829,10 @@ var CONSTRUCTORS = {
2829
3829
  manySep: { arity: [2, 2], of: ([a, b]) => ({ manySep: { rule: a, sep: b } }) },
2830
3830
  many1Sep: { arity: [2, 2], of: ([a, b]) => ({ many1Sep: { rule: a, sep: b } }) },
2831
3831
  between: { arity: [3, 3], of: ([a, b, c]) => ({ between: { open: a, body: b, close: c } }) },
3832
+ // pick(1, consume(Open), subrule(Body), consume(Close)) — the result at that index.
3833
+ pick: { arity: [2, Infinity], of: ([index, ...parsers]) => ({ pick: { parsers, index } }) },
3834
+ // yields(consume(True), true) — run the parsers, give back the constant.
3835
+ yields: { arity: [2, Infinity], of: (args) => ({ yields: { parsers: args.slice(0, -1), value: args[args.length - 1] } }) },
2832
3836
  infixLeft: { arity: [3, 3], of: ([a, b, c]) => ({ infixLeft: { operand: a, operator: b, combine: logicOf(c) } }) },
2833
3837
  infixRight: { arity: [3, 3], of: ([a, b, c]) => ({ infixRight: { operand: a, operator: b, combine: logicOf(c) } }) },
2834
3838
  prefix: { arity: [3, 3], of: ([a, b, c]) => ({ prefix: { operator: a, operand: b, combine: logicOf(c) } }) },
@@ -2849,6 +3853,355 @@ function createParserFromSource(source, options = {}) {
2849
3853
  return createParser(grammar, { ...options, methods });
2850
3854
  }
2851
3855
 
3856
+ // src/lexgen.js
3857
+ function generateLexer(defs, options = {}) {
3858
+ const positions = options.positions === "offset" ? "offset" : "full";
3859
+ const trackLines = positions === "full";
3860
+ const useScanners = options.scanners !== false;
3861
+ const useFuse = options.fuse !== false;
3862
+ const modeIds = /* @__PURE__ */ new Map([[DEFAULT_MODE2, 0]]);
3863
+ const modeNames = [DEFAULT_MODE2];
3864
+ const idOf = (name) => {
3865
+ let id = modeIds.get(name);
3866
+ if (id === void 0) {
3867
+ id = modeNames.length;
3868
+ modeIds.set(name, id);
3869
+ modeNames.push(name);
3870
+ }
3871
+ return id;
3872
+ };
3873
+ for (const def of defs) for (const mode of modesOf2(def)) idOf(mode);
3874
+ const compiled = defs.map((def, id) => {
3875
+ const flags = def.ignoreCase ? "iy" : "y";
3876
+ return {
3877
+ id,
3878
+ name: def.name,
3879
+ skip: !!def.skip,
3880
+ literal: def.literal,
3881
+ pattern: def.pattern,
3882
+ flags,
3883
+ longerAlt: def.longerAlt || null,
3884
+ scanner: useScanners && def.pattern ? buildScanner(def.pattern, !!def.ignoreCase) : null,
3885
+ modes: modesOf2(def),
3886
+ pushMode: def.pushMode ? idOf(def.pushMode) : -1,
3887
+ popMode: !!def.popMode,
3888
+ firstChars: def.literal !== void 0 ? def.literal.length > 0 ? /* @__PURE__ */ new Set([def.literal.charCodeAt(0)]) : null : firstCharSet(new RegExp(def.pattern, flags).source, !!def.ignoreCase)
3889
+ };
3890
+ });
3891
+ const byName = new Map(compiled.map((c) => [c.name, c]));
3892
+ const multiMode = modeNames.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
3893
+ const regexes = [];
3894
+ const regexIndex = /* @__PURE__ */ new Map();
3895
+ const rxFor = (c) => {
3896
+ if (!regexIndex.has(c.id)) {
3897
+ regexIndex.set(c.id, regexes.length);
3898
+ regexes.push([c.id, c.flags]);
3899
+ }
3900
+ return (
3901
+ /** @type {number} */
3902
+ regexIndex.get(c.id)
3903
+ );
3904
+ };
3905
+ const fusedRuns = [];
3906
+ const helpers = [];
3907
+ const keywords = [];
3908
+ const lookFns = /* @__PURE__ */ new Map();
3909
+ const need = { digits: false, hex: false, keyword: false };
3910
+ function emitRegex(c) {
3911
+ const rx = rxFor(c);
3912
+ if (c.skip) {
3913
+ return `RX[${rx}].lastIndex = offset; if (RX[${rx}].test(text) && RX[${rx}].lastIndex > offset) { id = ${c.id}; end = RX[${rx}].lastIndex; matched = null; break found }`;
3914
+ }
3915
+ return `RX[${rx}].lastIndex = offset; m = RX[${rx}].exec(text); if (m !== null && m[0].length > 0) { id = ${c.id}; matched = m[0]; end = offset + matched.length; break found }`;
3916
+ }
3917
+ const emitted = /* @__PURE__ */ new Map();
3918
+ function emitDef(c) {
3919
+ let code = emitted.get(c.id);
3920
+ if (code === void 0) {
3921
+ code = emitDefOnce(c);
3922
+ emitted.set(c.id, code);
3923
+ }
3924
+ return code;
3925
+ }
3926
+ function emitDefOnce(c) {
3927
+ if (c.literal !== void 0) {
3928
+ const lit = c.literal;
3929
+ const test = lit.length === 1 ? `code === ${lit.charCodeAt(0)}` : `text.startsWith(${q(lit)}, offset)`;
3930
+ return `if (${test}) { id = ${c.id}; end = offset + ${lit.length}; matched = ${q(lit)}; break found }`;
3931
+ }
3932
+ const s = c.scanner;
3933
+ if (s === null) return emitRegex(c);
3934
+ const hit = `{ id = ${c.id}; end = e; matched = null; break found }`;
3935
+ switch (s.kind) {
3936
+ case SCANNER_KINDS.CLASS: {
3937
+ if (s.wide) {
3938
+ helpers.push(wideClassHelper(c.id, s));
3939
+ return `{ const e = scan${c.id}(text, offset, length); if (e === -2) { ${emitRegex(c)} } else if (e !== -1) ${hit} }`;
3940
+ }
3941
+ return `if (${condition(s.first, "code")}) { let j = offset + 1; while (j < length) { const c = text.charCodeAt(j); if (!(${condition(s.rest, "c")})) break; j++ } id = ${c.id}; end = j; matched = null; break found }`;
3942
+ }
3943
+ case SCANNER_KINDS.SEQ2:
3944
+ return `if (${condition(s.first, "code")}) { let j = offset + 1; while (j < length) { const c = text.charCodeAt(j); if (${condition(s.first, "c")}) j++; else break } const mid = j; while (j < length) { const c = text.charCodeAt(j); if (${condition(s.second, "c")}) j++; else break } if (j > mid) { id = ${c.id}; end = j; matched = null; break found } }`;
3945
+ case SCANNER_KINDS.KEYWORD: {
3946
+ need.keyword = true;
3947
+ const cond = condition(s.look, "c");
3948
+ let look = lookFns.get(cond);
3949
+ if (look === void 0) {
3950
+ look = `look${lookFns.size}`;
3951
+ lookFns.set(cond, look);
3952
+ }
3953
+ keywords.push([s.lower, s.upper]);
3954
+ return `{ const e = scanKeyword(text, offset, length, KW[${keywords.length - 1}][0], KW[${keywords.length - 1}][1], ${look}); if (e !== -1) ${hit} }`;
3955
+ }
3956
+ case SCANNER_KINDS.LINE:
3957
+ return `if (text.startsWith(${q(s.prefix)}, offset)) { let j = offset + ${s.prefix.length}; while (j < length) { const c = text.charCodeAt(j); if (${condition(s.look, "c")}) break; j++ } id = ${c.id}; end = j; matched = null; break found }`;
3958
+ case SCANNER_KINDS.QUOTED:
3959
+ helpers.push(quotedHelper(c.id, s));
3960
+ return `{ const e = scan${c.id}(text, offset); if (e !== -1) ${hit} }`;
3961
+ case SCANNER_KINDS.NUMBER:
3962
+ need.digits = true;
3963
+ helpers.push(numberHelper(c.id, s));
3964
+ return `{ const e = scan${c.id}(text, offset, length); if (e !== -1) ${hit} }`;
3965
+ case SCANNER_KINDS.STRING:
3966
+ if (s.uni) need.hex = true;
3967
+ helpers.push(stringHelper(c.id, s));
3968
+ return `{ const e = scan${c.id}(text, offset, length); if (e !== -1) ${hit} }`;
3969
+ default:
3970
+ return emitRegex(c);
3971
+ }
3972
+ }
3973
+ const fusible = (c) => c.literal === void 0 && c.scanner === null && !/\\[1-9]|\\k</.test(c.pattern) && new RegExp(c.pattern + "|").exec("").length === 1;
3974
+ function emitList(list) {
3975
+ const out = [];
3976
+ for (let i = 0; i < list.length; ) {
3977
+ let j = i;
3978
+ while (useFuse && j < list.length && fusible(list[j]) && list[j].flags === list[i].flags) j++;
3979
+ if (j - i < 3) {
3980
+ out.push(emitDef(list[i]));
3981
+ i++;
3982
+ continue;
3983
+ }
3984
+ const run = list.slice(i, j);
3985
+ run.forEach(rxFor);
3986
+ const k = fusedRuns.length;
3987
+ fusedRuns.push({ flags: run[0].flags, ids: run.map((d) => d.id), rx: run.map((d) => (
3988
+ /** @type {number} */
3989
+ regexIndex.get(d.id)
3990
+ )) });
3991
+ out.push(
3992
+ `FX[${k}].lastIndex = offset; m = FX[${k}].exec(text)
3993
+ if (m !== null) {
3994
+ if (m[0].length > 0) { let g = 1; while (m[g] === undefined) g++; id = FIDS[${k}][g - 1]; matched = m[0]; end = offset + matched.length; break found }
3995
+ // An empty match is "no match" to a lexer, so the members after it still get their turn.
3996
+ for (let k = 0; k < FRX[${k}].length; k++) { const r = RX[FRX[${k}][k]]; r.lastIndex = offset; const mm = r.exec(text); if (mm !== null && mm[0].length > 0) { id = FIDS[${k}][k]; matched = mm[0]; end = offset + matched.length; break found } }
3997
+ }`
3998
+ );
3999
+ i = j;
4000
+ }
4001
+ return out.join("\n ");
4002
+ }
4003
+ const lists = [{ list: (
4004
+ /** @type {any[]} */
4005
+ []
4006
+ ) }];
4007
+ const listIds = /* @__PURE__ */ new Map([["", 0]]);
4008
+ const listFor = (list) => {
4009
+ const key = list.map((c) => c.id).join(",");
4010
+ let id = listIds.get(key);
4011
+ if (id === void 0) {
4012
+ id = lists.length;
4013
+ listIds.set(key, id);
4014
+ lists.push({ list });
4015
+ }
4016
+ return id;
4017
+ };
4018
+ const WIDTH = 129;
4019
+ const table = [];
4020
+ for (const name of modeNames) {
4021
+ const members = compiled.filter((c) => c.modes.includes(name));
4022
+ if (members.length === 0) throw new Error(`generateLexer: mode '${name}' has no tokens`);
4023
+ const dispatch = members.some((c) => c.firstChars !== null);
4024
+ for (let code = 0; code < 128; code++) {
4025
+ table.push(listFor(dispatch ? members.filter((c) => c.firstChars === null || c.firstChars.has(code)) : members));
4026
+ }
4027
+ table.push(listFor(dispatch ? members.filter((c) => c.firstChars === null || [...c.firstChars].some((x) => x >= 128)) : members));
4028
+ }
4029
+ if (lists.length > 6e4) throw new Error("generateLexer: too many distinct candidate lists");
4030
+ const dispatchTable = String.fromCharCode(...table.map((id) => 256 + id));
4031
+ const listCases = lists.slice(1).map(({ list }, i) => `case ${i + 1}: {
4032
+ ${emitList(list)}
4033
+ break
4034
+ }`);
4035
+ const longer = compiled.filter((c) => c.longerAlt);
4036
+ const longerCode = longer.length === 0 ? "" : `
4037
+ switch (id) {
4038
+ ${longer.map((c) => {
4039
+ const alt2 = (
4040
+ /** @type {any} */
4041
+ byName.get(
4042
+ /** @type {string} */
4043
+ c.longerAlt
4044
+ )
4045
+ );
4046
+ const altEnd = alt2.literal !== void 0 ? `text.startsWith(${q(alt2.literal)}, offset) ? offset + ${alt2.literal.length} : -1` : `(RX[${rxFor(alt2)}].lastIndex = offset, RX[${rxFor(alt2)}].test(text) ? RX[${rxFor(alt2)}].lastIndex : -1)`;
4047
+ return ` case ${c.id}: { const altEnd = ${altEnd}; if (altEnd > end) { end = altEnd; id = ${alt2.id}; matched = ${alt2.literal !== void 0 ? q(alt2.literal) : "null"} } break }`;
4048
+ }).join("\n")}
4049
+ }`;
4050
+ const transitions = compiled.filter((c) => c.popMode || c.pushMode !== -1);
4051
+ const describe = trackLines ? "'line ' + line + ' column ' + (offset - lineStart + 1)" : "'offset ' + offset";
4052
+ const transitionCode = transitions.length === 0 ? "" : `
4053
+ switch (id) {
4054
+ ${transitions.map((c) => c.popMode ? ` case ${c.id}:
4055
+ if (depth === 0) throw new LexError('Token \\'' + NAMES[id] + '\\' popped the initial lexer mode at ' + ${describe}, { offset, line: ${trackLines ? "line" : "0"}, col: ${trackLines ? "offset - lineStart + 1" : "0"} })
4056
+ mode = stack[--depth]
4057
+ break` : ` case ${c.id}: stack[depth++] = mode; mode = ${c.pushMode}; break`).join("\n")}
4058
+ }`;
4059
+ const where = trackLines ? "'line ' + line + ' column ' + column" : "'offset ' + offset";
4060
+ const inMode = modeNames.length > 1 ? " + ' (in lexer mode \\'' + MODE_NAMES[mode] + '\\')'" : "";
4061
+ return `(() => {
4062
+ const NAMES = tokens.map((t) => t.name)
4063
+ const SKIP = tokens.map((t) => (t.skip ? 1 : 0))
4064
+ const DISPATCH = ${q(dispatchTable)}
4065
+ ${modeNames.length > 1 ? `const MODE_NAMES = ${q(modeNames)}` : ""}
4066
+ // Patterns are read from the token table above rather than repeated here.
4067
+ const RX = ${q(regexes)}.map(([i, flags]) => new RegExp(tokens[i].pattern, flags))
4068
+ ${fusedRuns.length ? `const FX = ${q(fusedRuns.map((f) => [f.ids, f.flags]))}.map(([ids, flags]) => new RegExp(ids.map((i) => '(' + tokens[i].pattern + ')').join('|'), flags))
4069
+ const FIDS = ${q(fusedRuns.map((f) => f.ids))}
4070
+ const FRX = ${q(fusedRuns.map((f) => f.rx))}` : ""}
4071
+ ${need.digits ? "const skipDigits = (text, i, length) => { while (i < length) { const c = text.charCodeAt(i); if (c < 48 || c > 57) break; i++ } return i }" : ""}
4072
+ ${need.hex ? "const isHex = (c) => (c >= 48 && c <= 57) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102)" : ""}
4073
+ ${need.keyword ? `const KW = ${q(keywords)}
4074
+ ${[...lookFns].map(([cond, name]) => `const ${name} = (c) => ${cond}`).join("\n ")}
4075
+ ${KEYWORD_HELPER}` : ""}
4076
+ ${helpers.join("\n")}
4077
+
4078
+ function tokenize (text) {
4079
+ if (typeof text !== 'string') throw new TypeError('tokenize: expected a string')
4080
+ const tokens_ = []
4081
+ let offset = 0
4082
+ let line = 1
4083
+ let lineStart = 0
4084
+ const length = text.length
4085
+ ${multiMode ? "let mode = 0\n const stack = []\n let depth = 0" : ""}
4086
+ let m = null
4087
+ while (offset < length) {
4088
+ const code = text.charCodeAt(offset)
4089
+ let id = -1
4090
+ let end = -1
4091
+ let matched = null
4092
+ found: {
4093
+ switch (DISPATCH.charCodeAt(${multiMode ? `mode * ${WIDTH} + ` : ""}(code < 128 ? code : 128)) - 256) {
4094
+ ${listCases.join("\n ")}
4095
+ }
4096
+ }
4097
+ if (id === -1) {
4098
+ const column = offset - lineStart + 1
4099
+ throw new LexError('Unexpected character \\'' + text[offset] + '\\' at ' + ${where}${inMode}, { offset, line: ${trackLines ? "line" : "0"}, col: ${trackLines ? "column" : "0"} })
4100
+ }${longerCode}
4101
+ if (SKIP[id] === 0) {
4102
+ const image = matched !== null ? matched : text.slice(offset, end)
4103
+ tokens_.push(${trackLines ? "{ type: NAMES[id], image, start: offset, end, line, col: offset - lineStart + 1 }" : "{ type: NAMES[id], id, image, start: offset, end }"})
4104
+ }
4105
+ ${trackLines ? "for (let j = offset; j < end; j++) { if (text.charCodeAt(j) !== 10) continue; line++; lineStart = j + 1 }" : ""}${transitionCode}
4106
+ offset = end
4107
+ }
4108
+ return tokens_
4109
+ }
4110
+ return { tokenize, tokenNames: NAMES, positions: ${q(positions)}, modeNames: ${q(modeNames)} }
4111
+ })()`;
4112
+ }
4113
+ var DEFAULT_MODE2 = "default";
4114
+ var q = JSON.stringify;
4115
+ function modesOf2(def) {
4116
+ if (Array.isArray(def.modes) && def.modes.length > 0) return def.modes;
4117
+ if (def.mode) return [def.mode];
4118
+ return [DEFAULT_MODE2];
4119
+ }
4120
+ function condition(table, v) {
4121
+ const parts = [];
4122
+ for (let i = 0; i < 128; ) {
4123
+ if (table[i] !== 1) {
4124
+ i++;
4125
+ continue;
4126
+ }
4127
+ let j = i;
4128
+ while (j + 1 < 128 && table[j + 1] === 1) j++;
4129
+ parts.push(i === j ? `${v} === ${i}` : `(${v} >= ${i} && ${v} <= ${j})`);
4130
+ i = j + 1;
4131
+ }
4132
+ return parts.length > 0 ? parts.join(" || ") : "false";
4133
+ }
4134
+ var KEYWORD_HELPER = `function scanKeyword (text, offset, length, lower, upper, look) {
4135
+ const n = lower.length
4136
+ if (offset + n > length) return -1
4137
+ for (let i = 0; i < n; i++) {
4138
+ const c = text.charCodeAt(offset + i)
4139
+ if (c !== lower[i] && c !== upper[i]) return -1
4140
+ }
4141
+ const end = offset + n
4142
+ if (end < length && look(text.charCodeAt(end))) return -1
4143
+ return end
4144
+ }`;
4145
+ function wideClassHelper(id, s) {
4146
+ return `function scan${id} (text, offset, length) {
4147
+ const code = text.charCodeAt(offset)
4148
+ if (code >= 128) return -2
4149
+ if (!(${condition(s.first, "code")})) return -1
4150
+ let j = offset + 1
4151
+ while (j < length) {
4152
+ const c = text.charCodeAt(j)
4153
+ if (c >= 128) return -2
4154
+ if (!(${condition(s.rest, "c")})) break
4155
+ j++
4156
+ }
4157
+ return j
4158
+ }`;
4159
+ }
4160
+ function quotedHelper(id, s) {
4161
+ return `function scan${id} (text, offset) {
4162
+ if (text.charCodeAt(offset) !== ${s.quote}) return -1
4163
+ let fallback = -1
4164
+ let j = offset + 1
4165
+ for (;;) {
4166
+ j = text.indexOf(${q(String.fromCharCode(s.quote))}, j)
4167
+ if (j === -1) return fallback
4168
+ if (text.charCodeAt(j + 1) !== ${s.quote}) return j + 1
4169
+ fallback = j + 1
4170
+ j += 2
4171
+ }
4172
+ }`;
4173
+ }
4174
+ function numberHelper(id, s) {
4175
+ return `function scan${id} (text, offset, length) {
4176
+ let j = offset
4177
+ ${s.sign ? "if (text.charCodeAt(j) === 45) j++" : ""}
4178
+ const c = text.charCodeAt(j)
4179
+ ${s.jsonInt ? "if (c === 48) j++\n else if (c >= 49 && c <= 57) j = skipDigits(text, j + 1, length)\n else return -1" : "if (!(c >= 48 && c <= 57)) return -1\n j = skipDigits(text, j + 1, length)"}
4180
+ ${s.frac ? "if (text.charCodeAt(j) === 46) { const k = skipDigits(text, j + 1, length); if (k > j + 1) j = k }" : ""}
4181
+ ${s.exp ? "const e = text.charCodeAt(j)\n if (e === 101 || e === 69) { let k = j + 1; const sg = text.charCodeAt(k); if (sg === 43 || sg === 45) k++; const end = skipDigits(text, k, length); if (end > k) j = end }" : ""}
4182
+ return j
4183
+ }`;
4184
+ }
4185
+ function stringHelper(id, s) {
4186
+ return `function scan${id} (text, offset, length) {
4187
+ if (text.charCodeAt(offset) !== ${s.quote}) return -1
4188
+ let j = offset + 1
4189
+ for (;;) {
4190
+ if (j >= length) return -1
4191
+ const c = text.charCodeAt(j)
4192
+ if (c === ${s.quote}) return j + 1
4193
+ if (c === 92) {
4194
+ const d = text.charCodeAt(j + 1)
4195
+ ${s.escAny ? "if (!(d >= 0) || d === 10 || d === 13 || d === 0x2028 || d === 0x2029) return -1\n j += 2" : `if (${condition(s.esc, "d")}) j += 2
4196
+ ${s.uni ? "else if (d === 117 && isHex(text.charCodeAt(j + 2)) && isHex(text.charCodeAt(j + 3)) && isHex(text.charCodeAt(j + 4)) && isHex(text.charCodeAt(j + 5))) j += 6\n " : ""}else return -1`}
4197
+ continue
4198
+ }
4199
+ ${s.ctrl ? "if (c < 32) return -1" : ""}
4200
+ j++
4201
+ }
4202
+ }`;
4203
+ }
4204
+
2852
4205
  // src/emit.js
2853
4206
  var import_json_logic_engine2 = require("json-logic-engine");
2854
4207
  var UNSUPPORTED = /\b(coerceArray|precoerceNumber|compareCheck|assertAllowedDepth|assertSize|asyncIterators|notTraversed)\b|(?<!engine\.)\bmethods\[|engine\.(?!methods\b)/;
@@ -2897,11 +4250,21 @@ function emitModule(spec, options = {}) {
2897
4250
  const { tokens: tokenDefs, rules, start } = spec;
2898
4251
  const {
2899
4252
  memo = false,
4253
+ autoMemo = true,
4254
+ // Off here, on in `createParser`. Inlining a precedence chain is worth 2-3x on a bare chain but only
4255
+ // 1.02-1.04x on the formula and SQL examples, and it makes the file bigger: the formula module grows
4256
+ // 32% minified (38 to 50 KB) even after the second copy of each operand is kept as a call. A parser
4257
+ // built at run time pays nothing for that; a file someone ships does.
4258
+ inlineRules = false,
4259
+ idioms = true,
2900
4260
  strict = true,
2901
4261
  ll1 = true,
2902
4262
  maxSteps = 0,
2903
4263
  positions = "full",
2904
4264
  execution = "generated",
4265
+ lexer: lexerMode = "generated",
4266
+ scanners = true,
4267
+ fuse = true,
2905
4268
  format = "esm",
2906
4269
  validate = true,
2907
4270
  moduleName = spec.name || "parser",
@@ -2910,6 +4273,7 @@ function emitModule(spec, options = {}) {
2910
4273
  methods: semanticMethods
2911
4274
  } = options;
2912
4275
  if (format !== "esm" && format !== "cjs") throw new Error(`emitModule: format must be 'esm' or 'cjs', got ${quote(format)}`);
4276
+ if (lexerMode !== "generated" && lexerMode !== "runtime") throw new Error(`emitModule: lexer must be 'generated' or 'runtime', got ${quote(lexerMode)}`);
2913
4277
  const methodsBlock = options.methodsBlock !== void 0 ? options.methodsBlock : spec.methodsBlock;
2914
4278
  const block = methodsBlock ? emitMethodsBlock(methodsBlock, format) : null;
2915
4279
  if (!start || !(start in rules)) throw new Error(`emitModule: start rule '${start}' is not defined in rules`);
@@ -2922,8 +4286,10 @@ function emitModule(spec, options = {}) {
2922
4286
  throw new Error("not compiled during emit");
2923
4287
  }, run: () => {
2924
4288
  } };
2925
- const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval: false, tokenIds });
2926
- const codegen = createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds });
4289
+ const memoRules = autoMemo && !memo && analysis.errors.length === 0 ? selectMemoRules(rules, analysis.firsts, { ll1 }) : [];
4290
+ const memoSlots = new Map(memoRules.map((name, slot) => [name, slot]));
4291
+ const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval: false, tokenIds, memoSlots, idioms });
4292
+ const codegen = createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds, memoSlots, inlineRules, idioms });
2927
4293
  codegen.compile(start);
2928
4294
  const reached = [...codegen.ruleIndex.entries()].sort((a, b) => a[1] - b[1]);
2929
4295
  const unsupported = codegen.ORIGINS.map((origin, index) => ({ origin, index })).filter(({ origin }) => origin.kind === "unknownRule");
@@ -2964,7 +4330,8 @@ R[${index}] = ${codegen.SOURCES[index]}`
2964
4330
  ).join("\n\n");
2965
4331
  const interpretedActions = execution === "interpreted";
2966
4332
  const cjs = format === "cjs";
2967
- const runtimeImports = ["FAIL as F", "expect as E", "createState", "buildParseError", "createLexer"];
4333
+ const generatedLexer = lexerMode === "generated";
4334
+ const runtimeImports = ["FAIL as F", "expect as E", "createState", "buildParseError", generatedLexer ? "LexError" : "createLexer"];
2968
4335
  if (needsEngine) runtimeImports.push("createValueMethods");
2969
4336
  const importRuntime = cjs ? `const { ${runtimeImports.map((b) => b.replace(" as ", ": ")).join(", ")} } = require(${quote(runtimeSpecifier)})` : `import { ${runtimeImports.join(", ")} } from ${quote(runtimeSpecifier)}`;
2970
4337
  const importEngine = engineFree ? "" : cjs ? `const { LogicEngine } = require(${quote(engineSpecifier)})
@@ -2983,7 +4350,7 @@ export default { ${PUBLIC.join(", ")} }`;
2983
4350
  //
2984
4351
  // The grammar was analyzed when this file was written: FIRST sets, left-recursion
2985
4352
  // checks and rule specialization all happened then. Importing it does no grammar
2986
- // work and generates no code.
4353
+ // work and generates no code.${generatedLexer ? "\n// The lexer was written out as source too, so there is no lexer engine to load." : ""}
2987
4354
  ${engineFree ? "// Every semantic action was compiled to source too, so this file has no\n// dependency on json-logic-engine at all." : interpretedActions ? "// Semantic actions are interpreted, so nothing here calls `eval`." : "// Semantic actions that could not be compiled to source are shipped as JSON\n// Logic and built by json-logic-engine when this module loads."}
2988
4355
 
2989
4356
  ${importRuntime}
@@ -2993,7 +4360,7 @@ const tokens = ${literal(tokenDefs)}
2993
4360
 
2994
4361
  const start = ${quote(start)}
2995
4362
 
2996
- const lexer = createLexer(tokens, { positions: ${quote(positions)} })
4363
+ const lexer = ${generatedLexer ? generateLexer(tokenDefs, { positions, scanners, fuse }) : `createLexer(tokens, { positions: ${quote(positions)}, scanners: ${scanners}, fuse: ${fuse} })`}
2997
4364
  const tokenize = lexer.tokenize
2998
4365
 
2999
4366
  ${engineFree ? `// Every semantic action compiled to source, so the engine reduced to a table
@@ -3084,7 +4451,7 @@ const RULES = ${literal(Object.fromEntries(reached.map(([name, index]) => [name,
3084
4451
  * @param {string} [source]
3085
4452
  */
3086
4453
  function parseTokens (tokenList, source, track = false) {
3087
- const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track })
4454
+ const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track, memoSlots: ${memoRules.length} })
3088
4455
  state.source = source
3089
4456
  state.positions = ${quote(positions)}
3090
4457
  state.ruleNames[0] = start