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/esm/index.js CHANGED
@@ -315,7 +315,338 @@ function firstCharSet(source, ignoreCase) {
315
315
  return found;
316
316
  }
317
317
 
318
- // src/lexer.js
318
+ // src/scan.js
319
+ var DEFER = -2;
320
+ var CLASS = 0;
321
+ var SEQ2 = 1;
322
+ var KEYWORD = 2;
323
+ var LINE = 3;
324
+ var QUOTED = 4;
325
+ var NUMBER = 5;
326
+ var STRING = 6;
327
+ var SCANNER_KINDS = Object.freeze({ CLASS, SEQ2, KEYWORD, LINE, QUOTED, NUMBER, STRING });
328
+ var NONE = new Uint8Array(128);
329
+ function make(kind, fields) {
330
+ return {
331
+ kind,
332
+ wide: false,
333
+ first: NONE,
334
+ rest: NONE,
335
+ second: NONE,
336
+ look: NONE,
337
+ lower: [],
338
+ upper: [],
339
+ prefix: "",
340
+ quote: -1,
341
+ sign: false,
342
+ jsonInt: false,
343
+ frac: false,
344
+ exp: false,
345
+ ctrl: false,
346
+ uni: false,
347
+ escAny: false,
348
+ esc: NONE,
349
+ ...fields
350
+ };
351
+ }
352
+ var SHORTHAND = {
353
+ d: (t) => {
354
+ for (let k = 48; k < 58; k++) t[k] = 1;
355
+ },
356
+ w: (t) => {
357
+ for (let k = 0; k < 128; k++) if (/\w/.test(String.fromCharCode(k))) t[k] = 1;
358
+ },
359
+ s: (t) => {
360
+ for (const k of [9, 10, 11, 12, 13, 32]) t[k] = 1;
361
+ }
362
+ };
363
+ var SIMPLE_ESCAPES2 = { t: " ", r: "\r", n: "\n", f: "\f", v: "\v" };
364
+ var ALNUM = /[a-zA-Z0-9]/;
365
+ function parseClass(body) {
366
+ if (body.length === 0 || body[0] === "^") return null;
367
+ const table = new Uint8Array(128);
368
+ for (let i = 0; i < body.length; i++) {
369
+ let c = body[i];
370
+ if (c === "\\") {
371
+ const n = body[++i];
372
+ if (n === "d" || n === "w") {
373
+ SHORTHAND[n](table);
374
+ continue;
375
+ }
376
+ if (n === void 0) return null;
377
+ if (ALNUM.test(n) && !(n in SIMPLE_ESCAPES2)) return null;
378
+ c = SIMPLE_ESCAPES2[n] ?? n;
379
+ }
380
+ if (body[i + 1] === "-" && i + 2 < body.length) {
381
+ let hi = body[i + 2];
382
+ let advance = 2;
383
+ if (hi === "\\") {
384
+ hi = body[i + 3];
385
+ advance = 3;
386
+ if (hi === void 0 || ALNUM.test(hi)) return null;
387
+ }
388
+ const from = c.charCodeAt(0);
389
+ const to = hi.charCodeAt(0);
390
+ if (from > to || to >= 128) return null;
391
+ for (let k = from; k <= to; k++) table[k] = 1;
392
+ i += advance;
393
+ continue;
394
+ }
395
+ const code = c.charCodeAt(0);
396
+ if (code >= 128) return null;
397
+ table[code] = 1;
398
+ }
399
+ return table;
400
+ }
401
+ var BODY = String.raw`((?:[^\]\\]|\\.)*)`;
402
+ var RE_CLASS_RUN = new RegExp(String.raw`^\[${BODY}\]([+*])$`);
403
+ var RE_CLASS_PAIR = new RegExp(String.raw`^\[${BODY}\]\[${BODY}\]\*$`);
404
+ var RE_CLASS_SEQ = new RegExp(String.raw`^\[${BODY}\]\+\[${BODY}\]\+$`);
405
+ var RE_SHORTHAND = /^\\([sdw])[+*]$/;
406
+ var RE_KEYWORD = new RegExp(String.raw`^([A-Za-z0-9_]+)\(\?!\[${BODY}\]\)$`);
407
+ var RE_LINE = new RegExp(String.raw`^([^\\\[\]()*+?.|^$]+)\[\^${BODY}\]\*$`);
408
+ var RE_QUOTED = /^(['"])\(\?:\1\1\|\[\^\1\]\)\*\1$/;
409
+ var RE_NUMBER = /^(-\?)?(?:(\(\?:0\|\[1-9\]\\d\*\))|\\d\+)(\(\?:\\\.\\d\+\)\?)?(\(\?:\[eE\]\[\+-\]\?\\d\+\)\?)?$/;
410
+ var RE_STRING_ESC = new RegExp(String.raw`^(["'])\(\?:\[\^\1\\\\(\\u0000-\\u001f|\\x00-\\x1f)?\]\|\\\\\(\?:\[${BODY}\](\|u\[0-9a-fA-F\]\{4\})?\)\)\*\1$`);
411
+ var RE_STRING_ANY = new RegExp(String.raw`^(["'])\(\?:\[\^\1\\\\\]\|\\\\\.\)\*\1$`);
412
+ function buildScanner(source, ignoreCase) {
413
+ let m = RE_KEYWORD.exec(source);
414
+ if (m) {
415
+ const look = parseClass(m[2]);
416
+ if (look === null) return null;
417
+ const word = m[1];
418
+ const lower = [];
419
+ const upper = [];
420
+ for (const ch of word) {
421
+ lower.push((ignoreCase ? ch.toLowerCase() : ch).charCodeAt(0));
422
+ upper.push((ignoreCase ? ch.toUpperCase() : ch).charCodeAt(0));
423
+ }
424
+ return make(KEYWORD, { look, lower, upper });
425
+ }
426
+ if (ignoreCase) return null;
427
+ m = RE_STRING_ESC.exec(source);
428
+ if (m) {
429
+ const esc = parseClass(m[3]);
430
+ if (esc === null) return null;
431
+ return make(STRING, { quote: m[1].charCodeAt(0), ctrl: m[2] !== void 0, uni: m[4] !== void 0, esc });
432
+ }
433
+ m = RE_STRING_ANY.exec(source);
434
+ if (m) return make(STRING, { quote: m[1].charCodeAt(0), escAny: true });
435
+ m = RE_NUMBER.exec(source.replace(/\[0-9\]/g, "\\d"));
436
+ if (m && (m[1] || m[2] || m[3] || m[4])) {
437
+ return make(NUMBER, { sign: m[1] !== void 0, jsonInt: m[2] !== void 0, frac: m[3] !== void 0, exp: m[4] !== void 0 });
438
+ }
439
+ m = RE_CLASS_RUN.exec(source);
440
+ if (m) {
441
+ const table = parseClass(m[1]);
442
+ return table && make(CLASS, { first: table, rest: table });
443
+ }
444
+ m = RE_CLASS_PAIR.exec(source);
445
+ if (m) {
446
+ const first = parseClass(m[1]);
447
+ const rest = parseClass(m[2]);
448
+ return first && rest && make(CLASS, { first, rest });
449
+ }
450
+ m = RE_CLASS_SEQ.exec(source);
451
+ if (m) {
452
+ const first = parseClass(m[1]);
453
+ const second = parseClass(m[2]);
454
+ if (first && second && first.every((v, i) => !(v && second[i]))) {
455
+ return make(SEQ2, { first, rest: first, second });
456
+ }
457
+ return null;
458
+ }
459
+ m = RE_SHORTHAND.exec(source);
460
+ if (m) {
461
+ const table = new Uint8Array(128);
462
+ SHORTHAND[m[1]](table);
463
+ return make(CLASS, { first: table, rest: table, wide: m[1] === "s" });
464
+ }
465
+ m = RE_QUOTED.exec(source);
466
+ if (m) return make(QUOTED, { quote: m[1].charCodeAt(0) });
467
+ m = RE_LINE.exec(source);
468
+ if (m) {
469
+ const stop = parseClass(m[2]);
470
+ return stop && make(LINE, { prefix: m[1], look: stop });
471
+ }
472
+ return null;
473
+ }
474
+ var skipDigits = (text, i, length) => {
475
+ while (i < length) {
476
+ const c = text.charCodeAt(i);
477
+ if (c < 48 || c > 57) break;
478
+ i++;
479
+ }
480
+ return i;
481
+ };
482
+ var isHex = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 70 || c >= 97 && c <= 102;
483
+ function scanEnd(sc, text, offset, length) {
484
+ switch (sc.kind) {
485
+ case CLASS: {
486
+ const code = text.charCodeAt(offset);
487
+ if (code >= 128) return sc.wide ? DEFER : -1;
488
+ if (sc.first[code] !== 1) return -1;
489
+ const rest = sc.rest;
490
+ let j = offset + 1;
491
+ while (j < length) {
492
+ const c = text.charCodeAt(j);
493
+ if (c >= 128) return sc.wide ? DEFER : j;
494
+ if (rest[c] !== 1) break;
495
+ j++;
496
+ }
497
+ return j;
498
+ }
499
+ case SEQ2: {
500
+ const c0 = text.charCodeAt(offset);
501
+ if (c0 >= 128 || sc.first[c0] !== 1) return -1;
502
+ const { first, second } = sc;
503
+ let j = offset + 1;
504
+ while (j < length) {
505
+ const c = text.charCodeAt(j);
506
+ if (c < 128 && first[c] === 1) j++;
507
+ else break;
508
+ }
509
+ const mid = j;
510
+ while (j < length) {
511
+ const c = text.charCodeAt(j);
512
+ if (c < 128 && second[c] === 1) j++;
513
+ else break;
514
+ }
515
+ return j > mid ? j : -1;
516
+ }
517
+ case KEYWORD: {
518
+ const { lower, upper } = sc;
519
+ const n = lower.length;
520
+ if (offset + n > length) return -1;
521
+ for (let i = 0; i < n; i++) {
522
+ const c = text.charCodeAt(offset + i);
523
+ if (c !== lower[i] && c !== upper[i]) return -1;
524
+ }
525
+ const end = offset + n;
526
+ if (end < length) {
527
+ const c = text.charCodeAt(end);
528
+ if (c < 128 && sc.look[c] === 1) return -1;
529
+ }
530
+ return end;
531
+ }
532
+ case LINE: {
533
+ if (!text.startsWith(sc.prefix, offset)) return -1;
534
+ let j = offset + sc.prefix.length;
535
+ const stop = sc.look;
536
+ while (j < length) {
537
+ const c = text.charCodeAt(j);
538
+ if (c < 128 && stop[c] === 1) break;
539
+ j++;
540
+ }
541
+ return j;
542
+ }
543
+ case NUMBER: {
544
+ let j = offset;
545
+ if (sc.sign && text.charCodeAt(j) === 45) j++;
546
+ const c = text.charCodeAt(j);
547
+ if (sc.jsonInt) {
548
+ if (c === 48) j++;
549
+ else if (c >= 49 && c <= 57) j = skipDigits(text, j + 1, length);
550
+ else return -1;
551
+ } else {
552
+ if (!(c >= 48 && c <= 57)) return -1;
553
+ j = skipDigits(text, j + 1, length);
554
+ }
555
+ if (sc.frac && text.charCodeAt(j) === 46) {
556
+ const k = skipDigits(text, j + 1, length);
557
+ if (k > j + 1) j = k;
558
+ }
559
+ if (sc.exp) {
560
+ const e = text.charCodeAt(j);
561
+ if (e === 101 || e === 69) {
562
+ let k = j + 1;
563
+ const s = text.charCodeAt(k);
564
+ if (s === 43 || s === 45) k++;
565
+ const end = skipDigits(text, k, length);
566
+ if (end > k) j = end;
567
+ }
568
+ }
569
+ return j;
570
+ }
571
+ case STRING: {
572
+ const q2 = sc.quote;
573
+ if (text.charCodeAt(offset) !== q2) return -1;
574
+ let j = offset + 1;
575
+ for (; ; ) {
576
+ if (j >= length) return -1;
577
+ const c = text.charCodeAt(j);
578
+ if (c === q2) return j + 1;
579
+ if (c === 92) {
580
+ const d = text.charCodeAt(j + 1);
581
+ if (sc.escAny) {
582
+ if (!(d >= 0) || d === 10 || d === 13 || d === 8232 || d === 8233) return -1;
583
+ j += 2;
584
+ } else if (d < 128 && sc.esc[d] === 1) {
585
+ j += 2;
586
+ } 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))) {
587
+ j += 6;
588
+ } else {
589
+ return -1;
590
+ }
591
+ continue;
592
+ }
593
+ if (sc.ctrl && c < 32) return -1;
594
+ j++;
595
+ }
596
+ }
597
+ default: {
598
+ const q2 = sc.quote;
599
+ if (text.charCodeAt(offset) !== q2) return -1;
600
+ const quote2 = String.fromCharCode(q2);
601
+ let fallback = -1;
602
+ let j = offset + 1;
603
+ for (; ; ) {
604
+ j = text.indexOf(quote2, j);
605
+ if (j === -1) return fallback;
606
+ if (text.charCodeAt(j + 1) !== q2) return j + 1;
607
+ fallback = j + 1;
608
+ j += 2;
609
+ }
610
+ }
611
+ }
612
+ }
613
+
614
+ // src/state.js
615
+ var FAIL = /* @__PURE__ */ Symbol("grammatik.fail");
616
+ var memoTables = (slots, length) => {
617
+ if (!slots) return null;
618
+ const tables = new Array(slots);
619
+ for (let i = 0; i < slots; i++) tables[i] = { ends: new Int32Array(length + 1), vals: new Array(length + 1) };
620
+ return tables;
621
+ };
622
+ var createState = (tokens, grammar, options = {}) => ({
623
+ tokens,
624
+ idx: 0,
625
+ maxIdx: 0,
626
+ expected: [],
627
+ grammar,
628
+ memo: options.memo ? /* @__PURE__ */ new Map() : null,
629
+ rm: memoTables(options.memoSlots, tokens.length),
630
+ // Entering a rule is one array store rather than an object allocation, and
631
+ // the stack is snapshotted only on a furthest failure — by the time an error
632
+ // is built it would otherwise have unwound to just the start rule.
633
+ ruleNames: [],
634
+ ruleDepth: 0,
635
+ failureStack: null,
636
+ track: options.track === true,
637
+ steps: 0,
638
+ maxSteps: options.maxSteps || 0
639
+ });
640
+ function expect(state, expectation) {
641
+ if (state.idx > state.maxIdx) {
642
+ state.maxIdx = state.idx;
643
+ if (!state.track) return;
644
+ state.expected = [];
645
+ state.failureStack = state.ruleNames.slice(0, state.ruleDepth);
646
+ }
647
+ if (!state.track || state.idx !== state.maxIdx) return;
648
+ if (!state.expected.includes(expectation)) state.expected.push(expectation);
649
+ }
319
650
  var LexError = class extends Error {
320
651
  /**
321
652
  * @param {string} message
@@ -329,6 +660,33 @@ var LexError = class extends Error {
329
660
  this.col = position.col;
330
661
  }
331
662
  };
663
+ var ParseError = class extends Error {
664
+ /**
665
+ * @param {string} message
666
+ * @param {{ token: import('./lexer.js').Token | null, expected: string[], ruleStack: string[] }} details
667
+ */
668
+ constructor(message, details) {
669
+ super(message);
670
+ this.name = "ParseError";
671
+ this.token = details.token;
672
+ this.expected = details.expected;
673
+ this.ruleStack = details.ruleStack;
674
+ }
675
+ };
676
+ var describeExpected = (expected) => expected.length === 0 ? "nothing" : expected.length === 1 ? expected[0] : `one of [${expected.join(", ")}]`;
677
+ function buildParseError(state) {
678
+ const token = state.tokens[state.maxIdx] || null;
679
+ const expected = [...state.expected].sort();
680
+ const ruleStack = state.failureStack || state.ruleNames.slice(0, state.ruleDepth);
681
+ const at = (t) => state.positions === "offset" ? `offset ${t.start}` : `line ${t.line} column ${t.col}`;
682
+ const found = token ? `--> ${token.image} <-- (${token.type}) at ${at(token)}` : "end of input";
683
+ return new ParseError(
684
+ `Expecting ${describeExpected(expected)} but found ${found}`,
685
+ { token, expected, ruleStack }
686
+ );
687
+ }
688
+
689
+ // src/lexer.js
332
690
  var DEFAULT_MODE = "default";
333
691
  function modesOf(def) {
334
692
  if (Array.isArray(def.modes) && def.modes.length > 0) return def.modes;
@@ -338,6 +696,8 @@ function modesOf(def) {
338
696
  function createLexer(defs, options = {}) {
339
697
  const positions = options.positions === "offset" ? "offset" : "full";
340
698
  const trackLines = positions === "full";
699
+ const useScanners = options.scanners !== false;
700
+ const useFuse = options.fuse !== false;
341
701
  if (!Array.isArray(defs) || defs.length === 0) throw new Error("createLexer: expected a non-empty array of token definitions");
342
702
  const tokenNames = defs.map((d) => d.name);
343
703
  const seen = /* @__PURE__ */ new Set();
@@ -381,9 +741,15 @@ function createLexer(defs, options = {}) {
381
741
  literal: def.literal,
382
742
  longerAlt: def.longerAlt || null,
383
743
  regex: def.pattern ? new RegExp(def.pattern, def.ignoreCase ? "iy" : "y") : null,
744
+ // A table-driven stand-in for the regex where its shape is recognised;
745
+ // null otherwise (and always, with `scanners: false`). It is a field on
746
+ // every definition so they all keep one shape. See scan.js.
747
+ scanner: useScanners && def.pattern ? buildScanner(def.pattern, !!def.ignoreCase) : null,
384
748
  modes: modesOf(def),
385
749
  pushMode: def.pushMode ? idOf(def.pushMode) : -1,
386
- popMode: !!def.popMode
750
+ popMode: !!def.popMode,
751
+ // Set only on the stand-in `fuse` builds for a run of regexes; see there.
752
+ members: null
387
753
  };
388
754
  });
389
755
  const byName = new Map(compiled.map((c) => [c.name, c]));
@@ -400,14 +766,47 @@ function createLexer(defs, options = {}) {
400
766
  for (const c of compiled) {
401
767
  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);
402
768
  }
403
- const modes = modeNames.map((name) => {
769
+ const FUSE_MIN = 3;
770
+ 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.
771
+ new RegExp(def.regex.source + "|").exec("").length === 1;
772
+ function fuse(list) {
773
+ if (!useFuse) return list;
774
+ const out = [];
775
+ for (let i = 0; i < list.length; ) {
776
+ let j = i;
777
+ while (j < list.length && fusible(list[j]) && list[j].regex.flags === list[i].regex.flags) j++;
778
+ if (j - i < FUSE_MIN) {
779
+ out.push(list[i++]);
780
+ continue;
781
+ }
782
+ const run = list.slice(i, j);
783
+ out.push({
784
+ name: run.map((d) => d.name).join("|"),
785
+ id: -1,
786
+ skip: false,
787
+ literal: void 0,
788
+ longerAlt: null,
789
+ longerAltDef: void 0,
790
+ regex: new RegExp(run.map((d) => `(${d.regex.source})`).join("|"), run[0].regex.flags),
791
+ scanner: null,
792
+ modes: run[0].modes,
793
+ pushMode: -1,
794
+ popMode: false,
795
+ members: run
796
+ });
797
+ i = j;
798
+ }
799
+ return out;
800
+ }
801
+ const modes = modeNames.map((name, id) => {
404
802
  const members = compiled.filter((c) => c.modes.includes(name));
405
803
  const alwaysTry = members.filter((c) => c.firstChars === null);
406
- const candidatesFor = (code) => members.filter((c) => c.firstChars === null || c.firstChars.has(code));
804
+ const candidatesFor = (code) => fuse(members.filter((c) => c.firstChars === null || c.firstChars.has(code)));
407
805
  const ascii = new Array(128);
408
806
  for (let code = 0; code < 128; code++) ascii[code] = candidatesFor(code);
409
807
  return {
410
808
  name,
809
+ id,
411
810
  members,
412
811
  ascii,
413
812
  wide: /* @__PURE__ */ new Map(),
@@ -431,6 +830,30 @@ function createLexer(defs, options = {}) {
431
830
  const multiMode = modes.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
432
831
  const namesModes = modes.length > 1;
433
832
  const initial = modes[0];
833
+ let fusedMember = null;
834
+ let fusedText = "";
835
+ function fusedEnd(run, text, offset) {
836
+ run.regex.lastIndex = offset;
837
+ const match = run.regex.exec(text);
838
+ if (match === null) return -1;
839
+ if (match[0].length === 0) {
840
+ for (const member of run.members) {
841
+ member.regex.lastIndex = offset;
842
+ const one = member.regex.exec(text);
843
+ if (one !== null && one[0].length > 0) {
844
+ fusedMember = member;
845
+ fusedText = one[0];
846
+ return offset + fusedText.length;
847
+ }
848
+ }
849
+ return -1;
850
+ }
851
+ let group = 1;
852
+ while (match[group] === void 0) group++;
853
+ fusedMember = run.members[group - 1];
854
+ fusedText = match[0];
855
+ return offset + fusedText.length;
856
+ }
434
857
  function longerAltEnd(def, text, offset) {
435
858
  const alt2 = def.longerAltDef;
436
859
  if (alt2.literal !== void 0) return text.startsWith(alt2.literal, offset) ? offset + alt2.literal.length : -1;
@@ -450,19 +873,30 @@ function createLexer(defs, options = {}) {
450
873
  outer: while (offset < length) {
451
874
  const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
452
875
  for (let i = 0; i < applicable.length; i++) {
453
- const def = applicable[i];
876
+ let def = applicable[i];
454
877
  let end = -1;
455
878
  let matched = null;
456
- if (def.literal !== void 0) {
879
+ if (def.members !== null) {
880
+ end = fusedEnd(def, text, offset);
881
+ if (end === -1) continue;
882
+ def = fusedMember;
883
+ matched = fusedText;
884
+ } else if (def.literal !== void 0) {
457
885
  if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
458
- } else if (def.skip) {
459
- def.regex.lastIndex = offset;
460
- if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
461
886
  } else {
462
- def.regex.lastIndex = offset;
463
- const match = def.regex.exec(text);
464
- if (match !== null && match[0].length > 0) matched = match[0];
465
- if (matched !== null) end = offset + matched.length;
887
+ end = def.scanner === null ? DEFER : scanEnd(def.scanner, text, offset, length);
888
+ if (end === DEFER) {
889
+ end = -1;
890
+ if (def.skip) {
891
+ def.regex.lastIndex = offset;
892
+ if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
893
+ } else {
894
+ def.regex.lastIndex = offset;
895
+ const match = def.regex.exec(text);
896
+ if (match !== null && match[0].length > 0) matched = match[0];
897
+ if (matched !== null) end = offset + matched.length;
898
+ }
899
+ }
466
900
  }
467
901
  if (end === -1) continue;
468
902
  let winner = def;
@@ -511,62 +945,238 @@ function createLexer(defs, options = {}) {
511
945
  }
512
946
  const inMode = (mode) => namesModes ? ` (in lexer mode '${mode.name}')` : "";
513
947
  const describe = (offset, line, lineStart) => trackLines ? `line ${line} column ${offset - lineStart + 1}` : `offset ${offset}`;
514
- return { tokenize, tokenNames, positions, modeNames };
515
- }
516
-
517
- // src/state.js
518
- var FAIL = /* @__PURE__ */ Symbol("grammatik.fail");
519
- var createState = (tokens, grammar, options = {}) => ({
520
- tokens,
521
- idx: 0,
522
- maxIdx: 0,
523
- expected: [],
524
- grammar,
525
- memo: options.memo ? /* @__PURE__ */ new Map() : null,
526
- // Entering a rule is one array store rather than an object allocation, and
527
- // the stack is snapshotted only on a furthest failure — by the time an error
528
- // is built it would otherwise have unwound to just the start rule.
529
- ruleNames: [],
530
- ruleDepth: 0,
531
- failureStack: null,
532
- track: options.track === true,
533
- steps: 0,
534
- maxSteps: options.maxSteps || 0
535
- });
536
- function expect(state, expectation) {
537
- if (state.idx > state.maxIdx) {
538
- state.maxIdx = state.idx;
539
- if (!state.track) return;
540
- state.expected = [];
541
- state.failureStack = state.ruleNames.slice(0, state.ruleDepth);
948
+ if (!options.incremental) return { tokenize, tokenNames, positions, modeNames };
949
+ const recover = !!options.recover;
950
+ const errorTokenName = options.errorToken || "Error";
951
+ function snapshotStack(stack, depth) {
952
+ const ids = new Array(depth);
953
+ for (let i = 0; i < depth; i++) ids[i] = stack[i].id;
954
+ return ids;
542
955
  }
543
- if (!state.track || state.idx !== state.maxIdx) return;
544
- if (!state.expected.includes(expectation)) state.expected.push(expectation);
545
- }
546
- var ParseError = class extends Error {
547
- /**
548
- * @param {string} message
549
- * @param {{ token: import('./lexer.js').Token | null, expected: string[], ruleStack: string[] }} details
550
- */
551
- constructor(message, details) {
552
- super(message);
553
- this.name = "ParseError";
554
- this.token = details.token;
555
- this.expected = details.expected;
556
- this.ruleStack = details.ruleStack;
956
+ function sameStack(a, b) {
957
+ if (a.length !== b.length) return false;
958
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
959
+ return true;
557
960
  }
558
- };
559
- var describeExpected = (expected) => expected.length === 0 ? "nothing" : expected.length === 1 ? expected[0] : `one of [${expected.join(", ")}]`;
560
- function buildParseError(state) {
561
- const token = state.tokens[state.maxIdx] || null;
562
- const expected = [...state.expected].sort();
563
- const ruleStack = state.failureStack || state.ruleNames.slice(0, state.ruleDepth);
564
- const at = (t) => state.positions === "offset" ? `offset ${t.start}` : `line ${t.line} column ${t.col}`;
565
- const found = token ? `--> ${token.image} <-- (${token.type}) at ${at(token)}` : "end of input";
566
- return new ParseError(
567
- `Expecting ${describeExpected(expected)} but found ${found}`,
568
- { token, expected, ruleStack }
569
- );
961
+ function scan(text, offset, mode, stack, depth, line, lineStart) {
962
+ const tokens = [];
963
+ const checkpoints = [];
964
+ const length = text.length;
965
+ outer: while (offset < length) {
966
+ checkpoints.push({ modeId: mode.id, stack: snapshotStack(stack, depth), line, lineStart, offset });
967
+ const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
968
+ for (let i = 0; i < applicable.length; i++) {
969
+ let def = applicable[i];
970
+ let end = -1;
971
+ let matched = null;
972
+ if (def.members !== null) {
973
+ end = fusedEnd(def, text, offset);
974
+ if (end === -1) continue;
975
+ def = fusedMember;
976
+ matched = fusedText;
977
+ } else if (def.literal !== void 0) {
978
+ if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
979
+ } else {
980
+ end = def.scanner === null ? DEFER : scanEnd(def.scanner, text, offset, length);
981
+ if (end === DEFER) {
982
+ end = -1;
983
+ if (def.skip) {
984
+ def.regex.lastIndex = offset;
985
+ if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
986
+ } else {
987
+ def.regex.lastIndex = offset;
988
+ const match = def.regex.exec(text);
989
+ if (match !== null && match[0].length > 0) matched = match[0];
990
+ if (matched !== null) end = offset + matched.length;
991
+ }
992
+ }
993
+ }
994
+ if (end === -1) continue;
995
+ let winner = def;
996
+ if (def.longerAltDef) {
997
+ const altEnd = longerAltEnd(def, text, offset);
998
+ if (altEnd > end) {
999
+ end = altEnd;
1000
+ winner = def.longerAltDef;
1001
+ }
1002
+ }
1003
+ if (!winner.skip) {
1004
+ const image = winner.literal !== void 0 ? winner.literal : winner === def && matched !== null ? matched : text.slice(offset, end);
1005
+ 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 });
1006
+ }
1007
+ if (trackLines) {
1008
+ for (let j = offset; j < end; j++) {
1009
+ if (text.charCodeAt(j) !== 10) continue;
1010
+ line++;
1011
+ lineStart = j + 1;
1012
+ }
1013
+ }
1014
+ if (multiMode) {
1015
+ if (winner.popMode) {
1016
+ if (depth === 0) {
1017
+ if (!recover) {
1018
+ throw new LexError(
1019
+ `Token '${winner.name}' popped the initial lexer mode at ${describe(offset, line, lineStart)}`,
1020
+ { offset, line: trackLines ? line : 0, col: trackLines ? offset - lineStart + 1 : 0 }
1021
+ );
1022
+ }
1023
+ } else {
1024
+ mode = stack[--depth];
1025
+ }
1026
+ } else if (winner.pushMode !== -1) {
1027
+ stack[depth++] = mode;
1028
+ mode = modes[winner.pushMode];
1029
+ }
1030
+ }
1031
+ offset = end;
1032
+ continue outer;
1033
+ }
1034
+ if (recover) {
1035
+ 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 });
1036
+ if (trackLines && text.charCodeAt(offset) === 10) {
1037
+ line++;
1038
+ lineStart = offset + 1;
1039
+ }
1040
+ offset++;
1041
+ continue outer;
1042
+ }
1043
+ const column = offset - lineStart + 1;
1044
+ throw new LexError(
1045
+ trackLines ? `Unexpected character '${text[offset]}' at line ${line} column ${column}${inMode(mode)}` : `Unexpected character '${text[offset]}' at offset ${offset}${inMode(mode)}`,
1046
+ { offset, line: trackLines ? line : 0, col: trackLines ? column : 0 }
1047
+ );
1048
+ }
1049
+ return { tokens, checkpoints, mode, stack, depth, line, lineStart };
1050
+ }
1051
+ function tokenizeIncremental(text) {
1052
+ if (typeof text !== "string") throw new TypeError("tokenizeIncremental: expected a string");
1053
+ const result = scan(text, 0, initial, multiMode ? [] : [], 0, 1, 0);
1054
+ 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 } };
1055
+ }
1056
+ function tokenFloor(tokens, offset) {
1057
+ let lo = 0;
1058
+ let hi = tokens.length;
1059
+ while (lo < hi) {
1060
+ const mid = lo + hi >>> 1;
1061
+ if (tokens[mid].start < offset) lo = mid + 1;
1062
+ else hi = mid;
1063
+ }
1064
+ return lo;
1065
+ }
1066
+ function checkpointFloor(checkpoints, eof, offset) {
1067
+ let lo = 0;
1068
+ let hi = checkpoints.length + 1;
1069
+ while (lo < hi) {
1070
+ const mid = lo + hi >>> 1;
1071
+ const at = mid < checkpoints.length ? checkpoints[mid].offset : eof.offset;
1072
+ if (at <= offset) lo = mid + 1;
1073
+ else hi = mid;
1074
+ }
1075
+ return lo - 1;
1076
+ }
1077
+ function checkpointAt(checkpoints, eof, offset) {
1078
+ if (eof.offset === offset) return eof;
1079
+ let lo = 0;
1080
+ let hi = checkpoints.length;
1081
+ while (lo < hi) {
1082
+ const mid = lo + hi >>> 1;
1083
+ if (checkpoints[mid].offset < offset) lo = mid + 1;
1084
+ else hi = mid;
1085
+ }
1086
+ return lo < checkpoints.length && checkpoints[lo].offset === offset ? checkpoints[lo] : null;
1087
+ }
1088
+ function relex(newText, prev, edit) {
1089
+ if (typeof newText !== "string") throw new TypeError("relex: expected a string");
1090
+ const tokens = prev.tokens;
1091
+ const checkpoints = prev.checkpoints;
1092
+ const delta = edit.newEnd - edit.oldEnd;
1093
+ let resumeIndex = checkpointFloor(checkpoints, prev.eof, edit.start);
1094
+ if (resumeIndex > 0) resumeIndex--;
1095
+ let boundary = resumeIndex < checkpoints.length ? checkpoints[resumeIndex].offset : prev.eof.offset;
1096
+ let ti = tokenFloor(tokens, boundary);
1097
+ while (ti > 0 && tokens[ti - 1].end === boundary) {
1098
+ ti--;
1099
+ boundary = tokens[ti].start;
1100
+ }
1101
+ if (boundary < (resumeIndex < checkpoints.length ? checkpoints[resumeIndex].offset : prev.eof.offset)) {
1102
+ resumeIndex = checkpointFloor(checkpoints, prev.eof, boundary);
1103
+ }
1104
+ const resume = resumeIndex < checkpoints.length ? checkpoints[resumeIndex] : prev.eof;
1105
+ const resumeMode = modes[resume.modeId];
1106
+ const resumeStack = resume.stack.map((id) => modes[id]);
1107
+ const scanned = scan(newText, resume.offset, resumeMode, resumeStack, resumeStack.length, resume.line, resume.lineStart);
1108
+ const scannedEof = { modeId: scanned.mode.id, stack: snapshotStack(scanned.stack, scanned.depth), line: scanned.line, lineStart: scanned.lineStart, offset: newText.length };
1109
+ const VERIFY_WINDOW = 4;
1110
+ function convergenceVerified(oldOffset, newOffset) {
1111
+ const oldStart = tokenFloor(tokens, oldOffset);
1112
+ const freshStart = tokenFloor(scanned.tokens, newOffset);
1113
+ for (let k = 0; k < VERIFY_WINDOW; k++) {
1114
+ const oldTok = tokens[oldStart + k];
1115
+ const freshTok = scanned.tokens[freshStart + k];
1116
+ if (oldTok === void 0 && freshTok === void 0) return true;
1117
+ if (oldTok === void 0 || freshTok === void 0) return false;
1118
+ if (oldTok.type !== freshTok.type || oldTok.image !== freshTok.image) return false;
1119
+ }
1120
+ return true;
1121
+ }
1122
+ let convergeNewOffset = -1;
1123
+ let convergeOldOffset = -1;
1124
+ let convergeOldCheckpoint = null;
1125
+ let freshCheckpointCount = 0;
1126
+ for (let s = 0; s <= scanned.checkpoints.length; s++) {
1127
+ const cp = s < scanned.checkpoints.length ? scanned.checkpoints[s] : scannedEof;
1128
+ if (cp.offset < edit.newEnd) continue;
1129
+ const oldOffset = cp.offset - delta;
1130
+ const oldCp = checkpointAt(checkpoints, prev.eof, oldOffset);
1131
+ if (oldCp && oldCp.modeId === cp.modeId && sameStack(oldCp.stack, cp.stack) && convergenceVerified(oldOffset, cp.offset)) {
1132
+ convergeNewOffset = cp.offset;
1133
+ convergeOldOffset = oldOffset;
1134
+ convergeOldCheckpoint = oldCp;
1135
+ freshCheckpointCount = s;
1136
+ break;
1137
+ }
1138
+ }
1139
+ const tokenFrom = tokenFloor(tokens, resume.offset);
1140
+ const checkpointFrom = resumeIndex;
1141
+ if (convergeNewOffset === -1) {
1142
+ tokens.splice(tokenFrom, tokens.length - tokenFrom, ...scanned.tokens);
1143
+ checkpoints.splice(checkpointFrom, checkpoints.length - checkpointFrom, ...scanned.checkpoints);
1144
+ prev.text = newText;
1145
+ prev.eof = scannedEof;
1146
+ return prev;
1147
+ }
1148
+ const freshTokenCount = tokenFloor(scanned.tokens, convergeNewOffset);
1149
+ const lineDelta = trackLines ? scanned.checkpoints.length > freshCheckpointCount ? scanned.checkpoints[freshCheckpointCount].line - convergeOldCheckpoint.line : scannedEof.line - convergeOldCheckpoint.line : 0;
1150
+ const boundaryOldLineStart = convergeOldCheckpoint.lineStart;
1151
+ const boundaryNewLineStart = (freshCheckpointCount < scanned.checkpoints.length ? scanned.checkpoints[freshCheckpointCount] : scannedEof).lineStart;
1152
+ const shiftedLineStart = (oldLineStart) => oldLineStart === boundaryOldLineStart ? boundaryNewLineStart : oldLineStart + delta;
1153
+ const tailTokenFrom = tokenFloor(tokens, convergeOldOffset);
1154
+ for (let i = tailTokenFrom; i < tokens.length; i++) {
1155
+ const t = tokens[i];
1156
+ if (trackLines) {
1157
+ const oldLineStart = t.start - (t.col - 1);
1158
+ t.col = t.start + delta - shiftedLineStart(oldLineStart) + 1;
1159
+ t.line += lineDelta;
1160
+ }
1161
+ t.start += delta;
1162
+ t.end += delta;
1163
+ }
1164
+ tokens.splice(tokenFrom, tailTokenFrom - tokenFrom, ...scanned.tokens.slice(0, freshTokenCount));
1165
+ const tailCheckpointFrom = checkpointFloor(checkpoints, prev.eof, convergeOldOffset - 1) + 1;
1166
+ for (let i = tailCheckpointFrom; i < checkpoints.length; i++) {
1167
+ const cp = checkpoints[i];
1168
+ cp.lineStart = shiftedLineStart(cp.lineStart);
1169
+ cp.line += lineDelta;
1170
+ cp.offset += delta;
1171
+ }
1172
+ checkpoints.splice(checkpointFrom, tailCheckpointFrom - checkpointFrom, ...scanned.checkpoints.slice(0, freshCheckpointCount));
1173
+ prev.text = newText;
1174
+ prev.eof.lineStart = shiftedLineStart(prev.eof.lineStart);
1175
+ prev.eof.line += lineDelta;
1176
+ prev.eof.offset += delta;
1177
+ return prev;
1178
+ }
1179
+ return { tokenize, tokenizeIncremental, relex, tokenNames, positions, modeNames };
570
1180
  }
571
1181
 
572
1182
  // src/analyze.js
@@ -599,6 +1209,7 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
599
1209
  "many",
600
1210
  "many1",
601
1211
  "option",
1212
+ "flag",
602
1213
  "manySep",
603
1214
  "many1Sep",
604
1215
  "infixLeft",
@@ -606,6 +1217,8 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
606
1217
  "postfix",
607
1218
  "prefix",
608
1219
  "between",
1220
+ "pick",
1221
+ "yields",
609
1222
  "as",
610
1223
  "oneOf",
611
1224
  "eof",
@@ -659,7 +1272,8 @@ function firstOf(node, ruleFirsts) {
659
1272
  return result;
660
1273
  }
661
1274
  case "many":
662
- case "option": {
1275
+ case "option":
1276
+ case "flag": {
663
1277
  const inner = firstOf(arg, ruleFirsts);
664
1278
  return { tokens: inner.tokens, nullable: true, unknown: inner.unknown };
665
1279
  }
@@ -682,6 +1296,9 @@ function firstOf(node, ruleFirsts) {
682
1296
  return firstOf(Array.isArray(arg) ? arg[0] : arg && arg.parser, ruleFirsts);
683
1297
  case "between":
684
1298
  return firstOf({ seq: [arg && arg.open, arg && arg.body, arg && arg.close] }, ruleFirsts);
1299
+ case "pick":
1300
+ case "yields":
1301
+ return firstOf({ seq: arg && Array.isArray(arg.parsers) ? arg.parsers : [] }, ruleFirsts);
685
1302
  case "prefix": {
686
1303
  const ops = firstOf(arg && arg.operator, ruleFirsts);
687
1304
  const inner = firstOf(arg && arg.operand, ruleFirsts);
@@ -745,7 +1362,36 @@ function recognizeSeqIdiom(children) {
745
1362
  if (rewritten !== BAIL) return { as: [parser, rewritten] };
746
1363
  }
747
1364
  }
748
- return null;
1365
+ if (children.length >= 2) {
1366
+ const last = children[children.length - 1];
1367
+ const head = children.slice(0, -1);
1368
+ if (opOf(last) === "action" && isConstant(last.action) && head.every(isPlainParser) && !head.some(usesOuterScope)) {
1369
+ return { yields: { parsers: head, value: last.action } };
1370
+ }
1371
+ if (opOf(last) === "action" && opOf(last.action) === "val" && typeof last.action.val === "string") {
1372
+ const name = last.action.val;
1373
+ const labelled = head.map((child, i) => opOf(child) === "label" ? i : -1).filter((i) => i !== -1);
1374
+ if (labelled.length === 1 && head[labelled[0]].label[0] === name && head.every((child, i) => i === labelled[0] || isPlainParser(child)) && !head.some(usesOuterScope)) {
1375
+ return { pick: { parsers: head.map((child, i) => i === labelled[0] ? child.label[1] : child), index: labelled[0] } };
1376
+ }
1377
+ }
1378
+ }
1379
+ return null;
1380
+ }
1381
+ var isConstant = (value) => value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1382
+ function usesOuterScope(logic) {
1383
+ if (Array.isArray(logic)) return logic.some(usesOuterScope);
1384
+ if (!logic || typeof logic !== "object") return false;
1385
+ for (const key of Object.keys(logic)) {
1386
+ const arg = logic[key];
1387
+ if (key === "val" && Array.isArray(arg) && Array.isArray(arg[0])) return true;
1388
+ if (key === "var") {
1389
+ if (typeof arg === "string" && arg.includes("../")) return true;
1390
+ if (Array.isArray(arg) && typeof arg[0] === "string" && arg[0].includes("../")) return true;
1391
+ }
1392
+ if (usesOuterScope(arg)) return true;
1393
+ }
1394
+ return false;
749
1395
  }
750
1396
  function isPlainParser(node) {
751
1397
  const op = opOf(node);
@@ -792,6 +1438,18 @@ function recognizeAltPrefixIdiom(branches) {
792
1438
  ]
793
1439
  };
794
1440
  }
1441
+ function recognizeAltConsumeIdiom(branches) {
1442
+ if (!Array.isArray(branches) || branches.length < 2) return null;
1443
+ const names = [];
1444
+ for (const branch of branches) {
1445
+ if (opOf(branch) !== "consume") return null;
1446
+ const arg = branch.consume;
1447
+ const name = typeof arg === "string" ? arg : Array.isArray(arg) && arg.length === 1 ? arg[0] : null;
1448
+ if (typeof name !== "string") return null;
1449
+ names.push(name);
1450
+ }
1451
+ return { oneOf: names };
1452
+ }
795
1453
  var BAIL = /* @__PURE__ */ Symbol("grammatik.bail");
796
1454
  function rebaseOnLabel(logic, name) {
797
1455
  if (Array.isArray(logic)) {
@@ -858,6 +1516,7 @@ function leftmostCalls(node, ruleFirsts, out) {
858
1516
  case "many":
859
1517
  case "many1":
860
1518
  case "option":
1519
+ case "flag":
861
1520
  case "lookahead":
862
1521
  case "negLookahead":
863
1522
  leftmostCalls(arg, ruleFirsts, out);
@@ -868,6 +1527,10 @@ function leftmostCalls(node, ruleFirsts, out) {
868
1527
  case "between":
869
1528
  if (arg) leftmostCalls({ seq: [arg.open, arg.body, arg.close] }, ruleFirsts, out);
870
1529
  break;
1530
+ case "pick":
1531
+ case "yields":
1532
+ if (arg && Array.isArray(arg.parsers)) leftmostCalls({ seq: arg.parsers }, ruleFirsts, out);
1533
+ break;
871
1534
  case "prefix":
872
1535
  if (arg) {
873
1536
  leftmostCalls(arg.operator, ruleFirsts, out);
@@ -934,6 +1597,7 @@ function validateNode(node, grammar, tokenNames, errors, path) {
934
1597
  case "many":
935
1598
  case "many1":
936
1599
  case "option":
1600
+ case "flag":
937
1601
  case "text":
938
1602
  case "lookahead":
939
1603
  case "negLookahead":
@@ -955,6 +1619,26 @@ function validateNode(node, grammar, tokenNames, errors, path) {
955
1619
  }
956
1620
  break;
957
1621
  }
1622
+ case "pick": {
1623
+ if (!arg || !Array.isArray(arg.parsers) || arg.parsers.length === 0) {
1624
+ errors.push(`${path}: 'pick' expects { parsers: [parser, ...], index }`);
1625
+ } else if (!Number.isInteger(arg.index) || arg.index < 0 || arg.index >= arg.parsers.length) {
1626
+ errors.push(`${path}: 'pick' index ${JSON.stringify(arg.index)} is out of range for ${arg.parsers.length} parser(s)`);
1627
+ } else {
1628
+ arg.parsers.forEach((c, i) => validateNode(c, grammar, tokenNames, errors, `${path}.pick[${i}]`));
1629
+ }
1630
+ break;
1631
+ }
1632
+ case "yields": {
1633
+ if (!arg || !Array.isArray(arg.parsers) || arg.parsers.length === 0 || !("value" in arg)) {
1634
+ errors.push(`${path}: 'yields' expects { parsers: [parser, ...], value }`);
1635
+ } else if (!isConstant(arg.value)) {
1636
+ errors.push(`${path}: 'yields' value must be a string, number, boolean or null, got ${JSON.stringify(arg.value)}`);
1637
+ } else {
1638
+ arg.parsers.forEach((c, i) => validateNode(c, grammar, tokenNames, errors, `${path}.yields[${i}]`));
1639
+ }
1640
+ break;
1641
+ }
958
1642
  case "prefix": {
959
1643
  if (!arg || typeof arg !== "object" || !("operator" in arg) || !("operand" in arg) || !("combine" in arg)) {
960
1644
  errors.push(`${path}: 'prefix' expects { operator, operand, combine }`);
@@ -1081,20 +1765,6 @@ function declareSync(fn) {
1081
1765
  fn[SYNC] = true;
1082
1766
  return fn;
1083
1767
  }
1084
- function usesOuterScope(logic) {
1085
- if (Array.isArray(logic)) return logic.some(usesOuterScope);
1086
- if (!logic || typeof logic !== "object") return false;
1087
- for (const key of Object.keys(logic)) {
1088
- const arg = logic[key];
1089
- if (key === "val" && Array.isArray(arg) && Array.isArray(arg[0])) return true;
1090
- if (key === "var") {
1091
- if (typeof arg === "string" && arg.includes("../")) return true;
1092
- if (Array.isArray(arg) && typeof arg[0] === "string" && arg[0].includes("../")) return true;
1093
- }
1094
- if (usesOuterScope(arg)) return true;
1095
- }
1096
- return false;
1097
- }
1098
1768
  function mayReturnContext(logic) {
1099
1769
  if (Array.isArray(logic)) return logic.some(mayReturnContext);
1100
1770
  if (!logic || typeof logic !== "object") return false;
@@ -1107,7 +1777,7 @@ function mayReturnContext(logic) {
1107
1777
  return false;
1108
1778
  }
1109
1779
  function createPlanner(config) {
1110
- const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null } = config;
1780
+ const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), idioms = true } = config;
1111
1781
  const slots = /* @__PURE__ */ new Map();
1112
1782
  const planCache = /* @__PURE__ */ new WeakMap();
1113
1783
  const opCaches = /* @__PURE__ */ new Map();
@@ -1254,6 +1924,17 @@ function createPlanner(config) {
1254
1924
  return value;
1255
1925
  };
1256
1926
  };
1927
+ const buildFlag = (arg) => {
1928
+ const child = plan(arg);
1929
+ return (state) => {
1930
+ const start = state.idx;
1931
+ if (child(state) === FAIL) {
1932
+ state.idx = start;
1933
+ return false;
1934
+ }
1935
+ return true;
1936
+ };
1937
+ };
1257
1938
  const buildInfixLeft = (arg) => {
1258
1939
  const operand = plan(arg.operand);
1259
1940
  const operator = arg.operator === void 0 ? null : plan(arg.operator);
@@ -1379,6 +2060,26 @@ function createPlanner(config) {
1379
2060
  return value;
1380
2061
  };
1381
2062
  };
2063
+ const buildSequence = (arg, pickIndex, constant) => {
2064
+ const fns = arg.parsers.map(plan);
2065
+ const count = fns.length;
2066
+ return (state) => {
2067
+ tick(state);
2068
+ const start = state.idx;
2069
+ let picked;
2070
+ for (let i = 0; i < count; i++) {
2071
+ const value = fns[i](state);
2072
+ if (value === FAIL) {
2073
+ state.idx = start;
2074
+ return FAIL;
2075
+ }
2076
+ if (i === pickIndex) picked = value;
2077
+ }
2078
+ return pickIndex === -1 ? constant : picked;
2079
+ };
2080
+ };
2081
+ const buildPick = (arg) => buildSequence(arg, arg.index, void 0);
2082
+ const buildYields = (arg) => buildSequence(arg, -1, arg.value);
1382
2083
  const buildPrefix = (arg) => {
1383
2084
  const operator = plan(arg.operator);
1384
2085
  const operand = plan(arg.operand);
@@ -1462,6 +2163,31 @@ function createPlanner(config) {
1462
2163
  return value;
1463
2164
  };
1464
2165
  }
2166
+ const memoSlot = memoSlots.get(name);
2167
+ if (memoSlot !== void 0) {
2168
+ return (state) => {
2169
+ tick(state);
2170
+ const table = state.rm[memoSlot];
2171
+ const at = state.idx;
2172
+ const end = table.ends[at];
2173
+ if (end !== 0) {
2174
+ state.idx = end - 1;
2175
+ return table.vals[at];
2176
+ }
2177
+ let depth = 0;
2178
+ if (state.track) {
2179
+ depth = state.ruleDepth;
2180
+ state.ruleNames[depth] = name;
2181
+ state.ruleDepth = depth + 1;
2182
+ }
2183
+ const value = slot.fn(state);
2184
+ if (state.track) state.ruleDepth = depth;
2185
+ if (value === FAIL) state.idx = at;
2186
+ table.ends[at] = state.idx + 1;
2187
+ table.vals[at] = value;
2188
+ return value;
2189
+ };
2190
+ }
1465
2191
  return (state) => {
1466
2192
  tick(state);
1467
2193
  if (!state.track) return slot.fn(state);
@@ -1540,6 +2266,9 @@ function createPlanner(config) {
1540
2266
  infixRight: buildInfixRight,
1541
2267
  as: buildAs,
1542
2268
  between: buildBetween,
2269
+ flag: buildFlag,
2270
+ pick: buildPick,
2271
+ yields: buildYields,
1543
2272
  prefix: buildPrefix,
1544
2273
  postfix: buildPostfix,
1545
2274
  manySep: (arg) => planSeparated(arg, false),
@@ -1559,7 +2288,7 @@ function createPlanner(config) {
1559
2288
  }
1560
2289
  function planSeq(children) {
1561
2290
  if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
1562
- const idiom = recognizeSeqIdiom(children);
2291
+ const idiom = idioms ? recognizeSeqIdiom(children) : null;
1563
2292
  if (idiom !== null) return planUncached(idiom);
1564
2293
  const PARSER = 0;
1565
2294
  const LABELLED = 1;
@@ -1673,7 +2402,9 @@ function createPlanner(config) {
1673
2402
  }
1674
2403
  function planAlt(branches) {
1675
2404
  if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
1676
- const factored = recognizeAltPrefixIdiom(branches);
2405
+ const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
2406
+ if (oneOf !== null) return planUncached(oneOf);
2407
+ const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
1677
2408
  if (factored !== null) return planUncached(factored);
1678
2409
  const planned = branches.map(plan);
1679
2410
  const table = firsts ? buildDispatch(branches, firsts) : null;
@@ -1795,6 +2526,9 @@ var OPERATORS = [
1795
2526
  "prefix",
1796
2527
  "postfix",
1797
2528
  "between",
2529
+ "pick",
2530
+ "yields",
2531
+ "flag",
1798
2532
  "as",
1799
2533
  "subrule",
1800
2534
  "label",
@@ -1849,7 +2583,7 @@ var createValueMethods = () => ({
1849
2583
 
1850
2584
  // src/codegen.js
1851
2585
  function createCodegen(config) {
1852
- const { planner, rules, firsts, memo, maxSteps, tokenIds = null } = config;
2586
+ const { planner, rules, firsts, memo, maxSteps, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), inlineRules = true, idioms = true } = config;
1853
2587
  const typeCheck = (tokenVar, name) => {
1854
2588
  const id = tokenIds ? tokenIds.get(name) : void 0;
1855
2589
  if (id === void 0) return `${tokenVar}.type === ${JSON.stringify(name)}`;
@@ -1870,6 +2604,33 @@ function createCodegen(config) {
1870
2604
  }
1871
2605
  const actionHelper = (logic) => helper(planner.planAction(logic), { kind: "action", logic });
1872
2606
  const constantHelper = (node) => helper(planner.plan(node), { kind: "constant", node });
2607
+ const MAX_INLINE_DEPTH = 6;
2608
+ const ONE_OF_SWITCH_AT = 12;
2609
+ const INLINE_OPS = /* @__PURE__ */ new Set(["infixLeft", "infixRight", "prefix", "postfix"]);
2610
+ const callSites = /* @__PURE__ */ new Map();
2611
+ (function count(node) {
2612
+ if (Array.isArray(node)) {
2613
+ for (const child of node) count(child);
2614
+ return;
2615
+ }
2616
+ if (!node || typeof node !== "object") return;
2617
+ if ("subrule" in node) {
2618
+ const target = Array.isArray(node.subrule) ? node.subrule[0] : node.subrule;
2619
+ if (typeof target === "string") callSites.set(target, (callSites.get(target) || 0) + 1);
2620
+ }
2621
+ for (const key of Object.keys(node)) count(node[key]);
2622
+ })(rules);
2623
+ const inlining = /* @__PURE__ */ new Set();
2624
+ let coldDepth = 0;
2625
+ const cold = (write) => {
2626
+ coldDepth++;
2627
+ try {
2628
+ return write();
2629
+ } finally {
2630
+ coldDepth--;
2631
+ }
2632
+ };
2633
+ 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]));
1873
2634
  function indexOfRule(name) {
1874
2635
  let index = ruleIndex.get(name);
1875
2636
  if (index !== void 0) return index;
@@ -1930,6 +2691,16 @@ function createCodegen(config) {
1930
2691
  );
1931
2692
  return result;
1932
2693
  }
2694
+ case "flag": {
2695
+ const start = nextId();
2696
+ out.push(`let ${result};`, `const ${start} = c.idx;`, "{");
2697
+ const value = gen(arg, out, bindingsVar);
2698
+ out.push(
2699
+ `if (${value} === F) { c.idx = ${start}; ${result} = false; } else ${result} = true;`,
2700
+ "}"
2701
+ );
2702
+ return result;
2703
+ }
1933
2704
  case "many":
1934
2705
  case "many1": {
1935
2706
  const atLeastOne = op === "many1";
@@ -1947,7 +2718,7 @@ function createCodegen(config) {
1947
2718
  );
1948
2719
  }
1949
2720
  out.push("for (;;) {", `const ${before} = c.idx;`);
1950
- const value = gen(arg, out, bindingsVar);
2721
+ const value = atLeastOne ? cold(() => gen(arg, out, bindingsVar)) : gen(arg, out, bindingsVar);
1951
2722
  out.push(
1952
2723
  `if (${value} === F) { c.idx = ${before}; break; }`,
1953
2724
  `if (c.idx === ${before}) P(${JSON.stringify(op)}, ${before});`,
@@ -1977,7 +2748,7 @@ function createCodegen(config) {
1977
2748
  operatorVar = gen(arg.operator, out, bindingsVar);
1978
2749
  out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
1979
2750
  }
1980
- const right = gen(arg.operand, out, bindingsVar);
2751
+ const right = cold(() => gen(arg.operand, out, bindingsVar));
1981
2752
  out.push(
1982
2753
  `if (${right} === F) { c.idx = ${mark}; break; }`,
1983
2754
  `if (${bindings} === null) ${bindings} = { left: null, op: null, right: null };`,
@@ -1992,12 +2763,27 @@ function createCodegen(config) {
1992
2763
  case "oneOf": {
1993
2764
  const names = Array.isArray(arg) ? arg : [arg];
1994
2765
  const token = nextId();
2766
+ const ids = tokenIds ? names.map((n) => tokenIds.get(n)) : [];
2767
+ const failed = `else { ${names.map((n) => `E(c, ${JSON.stringify(n)});`).join(" ")} ${result} = F; }`;
2768
+ if (names.length > ONE_OF_SWITCH_AT && ids.every((id) => id !== void 0)) {
2769
+ const hit = nextId();
2770
+ const cases = [...new Set(ids)].map((id) => `case ${id}:`).join(" ");
2771
+ out.push(
2772
+ `let ${result};`,
2773
+ `const ${token} = c.tokens[c.idx];`,
2774
+ `let ${hit} = false;`,
2775
+ `if (${token} !== undefined) { switch (${token}.id !== undefined ? ${token}.id : I[${token}.type]) { ${cases} ${hit} = true; } }`,
2776
+ `if (${hit}) { c.idx++; ${result} = ${token}; }`,
2777
+ failed
2778
+ );
2779
+ return result;
2780
+ }
1995
2781
  const test = names.map((n) => typeCheck(token, n)).join(" || ");
1996
2782
  out.push(
1997
2783
  `let ${result};`,
1998
2784
  `const ${token} = c.tokens[c.idx];`,
1999
2785
  `if (${token} !== undefined && (${test})) { c.idx++; ${result} = ${token}; }`,
2000
- `else { ${names.map((n) => `E(c, ${JSON.stringify(n)});`).join(" ")} ${result} = F; }`
2786
+ failed
2001
2787
  );
2002
2788
  return result;
2003
2789
  }
@@ -2030,7 +2816,7 @@ function createCodegen(config) {
2030
2816
  operatorVar = gen(arg.operator, out, bindingsVar);
2031
2817
  out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
2032
2818
  }
2033
- const right = gen(arg.operand, out, bindingsVar);
2819
+ const right = cold(() => gen(arg.operand, out, bindingsVar));
2034
2820
  out.push(
2035
2821
  `if (${right} === F) { c.idx = ${mark}; break; }`,
2036
2822
  `if (${items} === null) ${items} = [];`,
@@ -2105,6 +2891,19 @@ function createCodegen(config) {
2105
2891
  );
2106
2892
  return result;
2107
2893
  }
2894
+ case "pick":
2895
+ case "yields": {
2896
+ const label2 = "L" + nextId();
2897
+ const start = nextId();
2898
+ out.push(tickSrc, `let ${result};`, `${label2}: {`, `const ${start} = c.idx;`);
2899
+ const values = arg.parsers.map((parser) => {
2900
+ const value = gen(parser, out, bindingsVar);
2901
+ out.push(`if (${value} === F) { c.idx = ${start}; ${result} = F; break ${label2}; }`);
2902
+ return value;
2903
+ });
2904
+ out.push(`${result} = ${op === "pick" ? values[arg.index] : JSON.stringify(arg.value)};`, "}");
2905
+ return result;
2906
+ }
2108
2907
  case "prefix": {
2109
2908
  const label2 = "L" + nextId();
2110
2909
  const start = nextId();
@@ -2181,7 +2980,7 @@ function createCodegen(config) {
2181
2980
  );
2182
2981
  const sep = gen(sepNode, out, bindingsVar);
2183
2982
  out.push(`if (${sep} === F) { c.idx = ${beforeSep}; break; }`, `const ${beforeItem} = c.idx;`);
2184
- const item = gen(arg.rule, out, bindingsVar);
2983
+ const item = cold(() => gen(arg.rule, out, bindingsVar));
2185
2984
  out.push(
2186
2985
  `if (${item} === F) { c.idx = ${trailing ? beforeItem : beforeSep}; break; }`,
2187
2986
  `${result}.push(${item});`,
@@ -2196,8 +2995,20 @@ function createCodegen(config) {
2196
2995
  out.push(`const ${result} = ${helper(planner.plan(node), { kind: "unknownRule", name })}(c);`);
2197
2996
  return result;
2198
2997
  }
2199
- const index = indexOfRule(name);
2200
2998
  const parent = nextId();
2999
+ if (canInline(name)) {
3000
+ out.push(tickSrc);
3001
+ out.push(
3002
+ `const ${parent} = c.ruleDepth;`,
3003
+ `if (c.track) { c.ruleNames[${parent}] = ${JSON.stringify(name)}; c.ruleDepth = ${parent} + 1; }`
3004
+ );
3005
+ inlining.add(name);
3006
+ const inner = gen(rules[name], out, null);
3007
+ inlining.delete(name);
3008
+ out.push(`if (c.track) c.ruleDepth = ${parent};`);
3009
+ return inner;
3010
+ }
3011
+ const index = indexOfRule(name);
2201
3012
  out.push(tickSrc);
2202
3013
  if (memo) {
2203
3014
  const key = nextId();
@@ -2218,6 +3029,26 @@ function createCodegen(config) {
2218
3029
  `c.memo.set(${key}, { value: ${result}, end: c.idx });`,
2219
3030
  "}"
2220
3031
  );
3032
+ } else if (memoSlots.has(name)) {
3033
+ const table = nextId();
3034
+ const at = nextId();
3035
+ const end = nextId();
3036
+ out.push(
3037
+ `let ${result};`,
3038
+ `const ${table} = c.rm[${memoSlots.get(name)}];`,
3039
+ `const ${at} = c.idx;`,
3040
+ `const ${end} = ${table}.ends[${at}];`,
3041
+ `if (${end} !== 0) { c.idx = ${end} - 1; ${result} = ${table}.vals[${at}]; }`,
3042
+ "else {",
3043
+ `const ${parent} = c.ruleDepth;`,
3044
+ `if (c.track) { c.ruleNames[${parent}] = ${JSON.stringify(name)}; c.ruleDepth = ${parent} + 1; }`,
3045
+ `${result} = R[${index}](c);`,
3046
+ `if (c.track) c.ruleDepth = ${parent};`,
3047
+ `if (${result} === F) c.idx = ${at};`,
3048
+ `${table}.ends[${at}] = c.idx + 1;`,
3049
+ `${table}.vals[${at}] = ${result};`,
3050
+ "}"
3051
+ );
2221
3052
  } else {
2222
3053
  out.push(
2223
3054
  `const ${parent} = c.ruleDepth;`,
@@ -2235,7 +3066,7 @@ function createCodegen(config) {
2235
3066
  }
2236
3067
  function genSeq(children, out, result) {
2237
3068
  if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
2238
- const idiom = recognizeSeqIdiom(children);
3069
+ const idiom = idioms ? recognizeSeqIdiom(children) : null;
2239
3070
  if (idiom !== null) return gen(idiom, out, null);
2240
3071
  let hasLabels = false;
2241
3072
  let hasAction = false;
@@ -2286,7 +3117,9 @@ function createCodegen(config) {
2286
3117
  }
2287
3118
  function genAlt(branches, out, result, bindingsVar) {
2288
3119
  if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
2289
- const factored = recognizeAltPrefixIdiom(branches);
3120
+ const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
3121
+ if (oneOf !== null) return gen(oneOf, out, bindingsVar);
3122
+ const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
2290
3123
  if (factored !== null) return gen(factored, out, bindingsVar);
2291
3124
  const table = firsts ? buildDispatch(branches, firsts) : null;
2292
3125
  const label2 = "L" + nextId();
@@ -2380,6 +3213,165 @@ return ${value};
2380
3213
  return { compile, functionFor, sourceFor, ruleIndex, RULES, SOURCES, ORIGINS, guard };
2381
3214
  }
2382
3215
 
3216
+ // src/memoplan.js
3217
+ function selectMemoRules(grammar, firsts, options = {}) {
3218
+ const ll1 = options.ll1 !== false;
3219
+ const names = Object.keys(grammar);
3220
+ const leftEdges = /* @__PURE__ */ new Map();
3221
+ const callEdges = /* @__PURE__ */ new Map();
3222
+ for (const name of names) {
3223
+ const left = /* @__PURE__ */ new Set();
3224
+ leftmostCalls(grammar[name], firsts, left);
3225
+ leftEdges.set(name, [...left].filter((r) => r in grammar));
3226
+ const calls = /* @__PURE__ */ new Set();
3227
+ walk(grammar[name], (node) => {
3228
+ if (opOf(node) !== "subrule") return;
3229
+ const arg = node.subrule;
3230
+ const target = Array.isArray(arg) ? arg[0] : arg;
3231
+ if (typeof target === "string" && target in grammar) calls.add(target);
3232
+ });
3233
+ callEdges.set(name, [...calls]);
3234
+ }
3235
+ const closure = (edges, from) => {
3236
+ const seen = /* @__PURE__ */ new Set();
3237
+ const stack = [...from];
3238
+ while (stack.length > 0) {
3239
+ const next = stack.pop();
3240
+ if (seen.has(next)) continue;
3241
+ seen.add(next);
3242
+ for (const edge of edges.get(next) || []) stack.push(edge);
3243
+ }
3244
+ return seen;
3245
+ };
3246
+ const leftCache = /* @__PURE__ */ new Map();
3247
+ const leftReach = (name) => {
3248
+ let reach = leftCache.get(name);
3249
+ if (reach === void 0) {
3250
+ reach = closure(leftEdges, leftEdges.get(name) || []);
3251
+ leftCache.set(name, reach);
3252
+ }
3253
+ return reach;
3254
+ };
3255
+ const startsWith = (branch) => {
3256
+ const direct = /* @__PURE__ */ new Set();
3257
+ leftmostCalls(branch, firsts, direct);
3258
+ const all = /* @__PURE__ */ new Set();
3259
+ for (const rule of direct) {
3260
+ if (!(rule in grammar)) continue;
3261
+ all.add(rule);
3262
+ for (const deeper of leftReach(rule)) all.add(deeper);
3263
+ }
3264
+ return all;
3265
+ };
3266
+ const sites = [];
3267
+ for (const name of names) {
3268
+ walk(grammar[name], (node) => {
3269
+ if (opOf(node) !== "alt" || !Array.isArray(node.alt) || node.alt.length < 2) return;
3270
+ for (const group of collisionGroups(node.alt, firsts, ll1)) {
3271
+ const counts = /* @__PURE__ */ new Map();
3272
+ for (const branch of group) {
3273
+ for (const rule of startsWith(branch)) counts.set(rule, (counts.get(rule) || 0) + 1);
3274
+ }
3275
+ const shared = [...counts].filter(([, n]) => n >= 2).map(([rule]) => rule);
3276
+ const topmost = shared.filter((rule) => !shared.some((other) => other !== rule && leftReach(other).has(rule) && !leftReach(rule).has(other)));
3277
+ if (topmost.length > 0) sites.push({ host: name, rules: topmost });
3278
+ }
3279
+ });
3280
+ }
3281
+ const hosts = new Set(sites.map((site) => site.host));
3282
+ const chosen = /* @__PURE__ */ new Set();
3283
+ for (const { rules } of sites) {
3284
+ for (const rule of rules) {
3285
+ const inside = closure(callEdges, [rule]);
3286
+ inside.add(rule);
3287
+ for (const host of hosts) {
3288
+ if (inside.has(host)) {
3289
+ chosen.add(rule);
3290
+ break;
3291
+ }
3292
+ }
3293
+ }
3294
+ }
3295
+ const dependsOnCaller = (name) => {
3296
+ const reachable = closure(callEdges, [name]);
3297
+ reachable.add(name);
3298
+ for (const rule of reachable) if (usesOuterScope(grammar[rule])) return true;
3299
+ return false;
3300
+ };
3301
+ return names.filter((name) => chosen.has(name) && !dependsOnCaller(name));
3302
+ }
3303
+ function collisionGroups(branches, firsts, ll1) {
3304
+ const table = ll1 ? buildDispatch(branches, firsts) : null;
3305
+ if (table === null) return [branches];
3306
+ const groups = /* @__PURE__ */ new Set();
3307
+ for (const entry of table.values()) if (Array.isArray(entry) && entry.length > 1) groups.add(entry);
3308
+ return [...groups];
3309
+ }
3310
+ function walk(node, visit) {
3311
+ if (Array.isArray(node)) {
3312
+ for (const child of node) walk(child, visit);
3313
+ return;
3314
+ }
3315
+ const op = opOf(node);
3316
+ if (op === null) return;
3317
+ visit(node);
3318
+ const arg = node[op];
3319
+ switch (op) {
3320
+ case "seq":
3321
+ case "alt":
3322
+ if (Array.isArray(arg)) for (const child of arg) walk(child, visit);
3323
+ break;
3324
+ case "label":
3325
+ if (Array.isArray(arg)) walk(arg[1], visit);
3326
+ break;
3327
+ case "many":
3328
+ case "many1":
3329
+ case "option":
3330
+ case "flag":
3331
+ case "text":
3332
+ case "lookahead":
3333
+ case "negLookahead":
3334
+ walk(arg, visit);
3335
+ break;
3336
+ case "as":
3337
+ walk(Array.isArray(arg) ? arg[0] : arg && arg.parser, visit);
3338
+ break;
3339
+ case "between":
3340
+ if (arg) for (const part of [arg.open, arg.body, arg.close]) walk(part, visit);
3341
+ break;
3342
+ case "pick":
3343
+ case "yields":
3344
+ if (arg && Array.isArray(arg.parsers)) for (const part of arg.parsers) walk(part, visit);
3345
+ break;
3346
+ case "prefix":
3347
+ if (arg) {
3348
+ walk(arg.operator, visit);
3349
+ walk(arg.operand, visit);
3350
+ }
3351
+ break;
3352
+ case "postfix":
3353
+ if (arg) {
3354
+ walk(arg.operand, visit);
3355
+ walk(arg.suffix, visit);
3356
+ }
3357
+ break;
3358
+ case "infixLeft":
3359
+ case "infixRight":
3360
+ if (arg) {
3361
+ walk(arg.operand, visit);
3362
+ if (arg.operator !== void 0) walk(arg.operator, visit);
3363
+ }
3364
+ break;
3365
+ case "manySep":
3366
+ case "many1Sep":
3367
+ if (arg) {
3368
+ walk(arg.rule, visit);
3369
+ if (typeof arg.sep !== "string") walk(arg.sep, visit);
3370
+ }
3371
+ break;
3372
+ }
3373
+ }
3374
+
2383
3375
  // src/parser.js
2384
3376
  var EXECUTION_MODES = /* @__PURE__ */ new Set(["generated", "interpreted"]);
2385
3377
  var GrammarError = class extends Error {
@@ -2395,6 +3387,9 @@ function createParser(spec, options = {}) {
2395
3387
  const { tokens: tokenDefs, rules, start } = spec;
2396
3388
  const {
2397
3389
  memo = false,
3390
+ autoMemo = true,
3391
+ inlineRules = true,
3392
+ idioms = true,
2398
3393
  strict = true,
2399
3394
  validate = true,
2400
3395
  maxSteps = 0,
@@ -2422,9 +3417,11 @@ function createParser(spec, options = {}) {
2422
3417
  const startRule = rules[start];
2423
3418
  const firsts = ll1 ? analysis.firsts : null;
2424
3419
  const tokenIds = new Map(lexer.tokenNames.map((name, id) => [name, id]));
2425
- const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval, tokenIds });
3420
+ const memoRules = autoMemo && !memo && analysis.errors.length === 0 ? selectMemoRules(rules, analysis.firsts, { ll1 }) : [];
3421
+ const memoSlots = new Map(memoRules.map((name, slot) => [name, slot]));
3422
+ const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval, tokenIds, memoSlots, idioms });
2426
3423
  engine.__jlGrammar = { planner };
2427
- const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds }) : null;
3424
+ const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds, memoSlots, inlineRules, idioms }) : null;
2428
3425
  const compiledStart = codegen && codegen.compile(start);
2429
3426
  if (codegen && !engine.disableInterpretedOptimization) {
2430
3427
  for (const name of Object.keys(rules)) {
@@ -2433,7 +3430,7 @@ function createParser(spec, options = {}) {
2433
3430
  }
2434
3431
  }
2435
3432
  function parseTokens(tokens, source, track = false) {
2436
- const state = createState(tokens, rules, { memo, maxSteps, track });
3433
+ const state = createState(tokens, rules, { memo, maxSteps, track, memoSlots: memoRules.length });
2437
3434
  state.firsts = firsts;
2438
3435
  state.source = source;
2439
3436
  state.positions = lexer.positions;
@@ -2463,6 +3460,8 @@ function createParser(spec, options = {}) {
2463
3460
  planner,
2464
3461
  codegen,
2465
3462
  execution: mode,
3463
+ /** Rules memoized by `autoMemo`, for introspection. */
3464
+ memoRules,
2466
3465
  compiled: Boolean(compiledStart),
2467
3466
  /** Rules whose `alt`s resolved to O(1) LL(1) dispatch, for introspection. */
2468
3467
  get firsts() {
@@ -2768,6 +3767,7 @@ var CONSTRUCTORS = {
2768
3767
  many: { arity: [1, 1], of: ([a]) => ({ many: a }) },
2769
3768
  many1: { arity: [1, 1], of: ([a]) => ({ many1: a }) },
2770
3769
  option: { arity: [1, 1], of: ([a]) => ({ option: a }) },
3770
+ flag: { arity: [1, 1], of: ([a]) => ({ flag: a }) },
2771
3771
  action: { arity: [1, 1], of: ([a]) => ({ action: a }) },
2772
3772
  lookahead: { arity: [1, 1], of: ([a]) => ({ lookahead: a }) },
2773
3773
  negLookahead: { arity: [1, 1], of: ([a]) => ({ negLookahead: a }) },
@@ -2779,6 +3779,10 @@ var CONSTRUCTORS = {
2779
3779
  manySep: { arity: [2, 2], of: ([a, b]) => ({ manySep: { rule: a, sep: b } }) },
2780
3780
  many1Sep: { arity: [2, 2], of: ([a, b]) => ({ many1Sep: { rule: a, sep: b } }) },
2781
3781
  between: { arity: [3, 3], of: ([a, b, c]) => ({ between: { open: a, body: b, close: c } }) },
3782
+ // pick(1, consume(Open), subrule(Body), consume(Close)) — the result at that index.
3783
+ pick: { arity: [2, Infinity], of: ([index, ...parsers]) => ({ pick: { parsers, index } }) },
3784
+ // yields(consume(True), true) — run the parsers, give back the constant.
3785
+ yields: { arity: [2, Infinity], of: (args) => ({ yields: { parsers: args.slice(0, -1), value: args[args.length - 1] } }) },
2782
3786
  infixLeft: { arity: [3, 3], of: ([a, b, c]) => ({ infixLeft: { operand: a, operator: b, combine: logicOf(c) } }) },
2783
3787
  infixRight: { arity: [3, 3], of: ([a, b, c]) => ({ infixRight: { operand: a, operator: b, combine: logicOf(c) } }) },
2784
3788
  prefix: { arity: [3, 3], of: ([a, b, c]) => ({ prefix: { operator: a, operand: b, combine: logicOf(c) } }) },
@@ -2799,6 +3803,355 @@ function createParserFromSource(source, options = {}) {
2799
3803
  return createParser(grammar, { ...options, methods });
2800
3804
  }
2801
3805
 
3806
+ // src/lexgen.js
3807
+ function generateLexer(defs, options = {}) {
3808
+ const positions = options.positions === "offset" ? "offset" : "full";
3809
+ const trackLines = positions === "full";
3810
+ const useScanners = options.scanners !== false;
3811
+ const useFuse = options.fuse !== false;
3812
+ const modeIds = /* @__PURE__ */ new Map([[DEFAULT_MODE2, 0]]);
3813
+ const modeNames = [DEFAULT_MODE2];
3814
+ const idOf = (name) => {
3815
+ let id = modeIds.get(name);
3816
+ if (id === void 0) {
3817
+ id = modeNames.length;
3818
+ modeIds.set(name, id);
3819
+ modeNames.push(name);
3820
+ }
3821
+ return id;
3822
+ };
3823
+ for (const def of defs) for (const mode of modesOf2(def)) idOf(mode);
3824
+ const compiled = defs.map((def, id) => {
3825
+ const flags = def.ignoreCase ? "iy" : "y";
3826
+ return {
3827
+ id,
3828
+ name: def.name,
3829
+ skip: !!def.skip,
3830
+ literal: def.literal,
3831
+ pattern: def.pattern,
3832
+ flags,
3833
+ longerAlt: def.longerAlt || null,
3834
+ scanner: useScanners && def.pattern ? buildScanner(def.pattern, !!def.ignoreCase) : null,
3835
+ modes: modesOf2(def),
3836
+ pushMode: def.pushMode ? idOf(def.pushMode) : -1,
3837
+ popMode: !!def.popMode,
3838
+ 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)
3839
+ };
3840
+ });
3841
+ const byName = new Map(compiled.map((c) => [c.name, c]));
3842
+ const multiMode = modeNames.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
3843
+ const regexes = [];
3844
+ const regexIndex = /* @__PURE__ */ new Map();
3845
+ const rxFor = (c) => {
3846
+ if (!regexIndex.has(c.id)) {
3847
+ regexIndex.set(c.id, regexes.length);
3848
+ regexes.push([c.id, c.flags]);
3849
+ }
3850
+ return (
3851
+ /** @type {number} */
3852
+ regexIndex.get(c.id)
3853
+ );
3854
+ };
3855
+ const fusedRuns = [];
3856
+ const helpers = [];
3857
+ const keywords = [];
3858
+ const lookFns = /* @__PURE__ */ new Map();
3859
+ const need = { digits: false, hex: false, keyword: false };
3860
+ function emitRegex(c) {
3861
+ const rx = rxFor(c);
3862
+ if (c.skip) {
3863
+ 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 }`;
3864
+ }
3865
+ 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 }`;
3866
+ }
3867
+ const emitted = /* @__PURE__ */ new Map();
3868
+ function emitDef(c) {
3869
+ let code = emitted.get(c.id);
3870
+ if (code === void 0) {
3871
+ code = emitDefOnce(c);
3872
+ emitted.set(c.id, code);
3873
+ }
3874
+ return code;
3875
+ }
3876
+ function emitDefOnce(c) {
3877
+ if (c.literal !== void 0) {
3878
+ const lit = c.literal;
3879
+ const test = lit.length === 1 ? `code === ${lit.charCodeAt(0)}` : `text.startsWith(${q(lit)}, offset)`;
3880
+ return `if (${test}) { id = ${c.id}; end = offset + ${lit.length}; matched = ${q(lit)}; break found }`;
3881
+ }
3882
+ const s = c.scanner;
3883
+ if (s === null) return emitRegex(c);
3884
+ const hit = `{ id = ${c.id}; end = e; matched = null; break found }`;
3885
+ switch (s.kind) {
3886
+ case SCANNER_KINDS.CLASS: {
3887
+ if (s.wide) {
3888
+ helpers.push(wideClassHelper(c.id, s));
3889
+ return `{ const e = scan${c.id}(text, offset, length); if (e === -2) { ${emitRegex(c)} } else if (e !== -1) ${hit} }`;
3890
+ }
3891
+ 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 }`;
3892
+ }
3893
+ case SCANNER_KINDS.SEQ2:
3894
+ 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 } }`;
3895
+ case SCANNER_KINDS.KEYWORD: {
3896
+ need.keyword = true;
3897
+ const cond = condition(s.look, "c");
3898
+ let look = lookFns.get(cond);
3899
+ if (look === void 0) {
3900
+ look = `look${lookFns.size}`;
3901
+ lookFns.set(cond, look);
3902
+ }
3903
+ keywords.push([s.lower, s.upper]);
3904
+ return `{ const e = scanKeyword(text, offset, length, KW[${keywords.length - 1}][0], KW[${keywords.length - 1}][1], ${look}); if (e !== -1) ${hit} }`;
3905
+ }
3906
+ case SCANNER_KINDS.LINE:
3907
+ 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 }`;
3908
+ case SCANNER_KINDS.QUOTED:
3909
+ helpers.push(quotedHelper(c.id, s));
3910
+ return `{ const e = scan${c.id}(text, offset); if (e !== -1) ${hit} }`;
3911
+ case SCANNER_KINDS.NUMBER:
3912
+ need.digits = true;
3913
+ helpers.push(numberHelper(c.id, s));
3914
+ return `{ const e = scan${c.id}(text, offset, length); if (e !== -1) ${hit} }`;
3915
+ case SCANNER_KINDS.STRING:
3916
+ if (s.uni) need.hex = true;
3917
+ helpers.push(stringHelper(c.id, s));
3918
+ return `{ const e = scan${c.id}(text, offset, length); if (e !== -1) ${hit} }`;
3919
+ default:
3920
+ return emitRegex(c);
3921
+ }
3922
+ }
3923
+ const fusible = (c) => c.literal === void 0 && c.scanner === null && !/\\[1-9]|\\k</.test(c.pattern) && new RegExp(c.pattern + "|").exec("").length === 1;
3924
+ function emitList(list) {
3925
+ const out = [];
3926
+ for (let i = 0; i < list.length; ) {
3927
+ let j = i;
3928
+ while (useFuse && j < list.length && fusible(list[j]) && list[j].flags === list[i].flags) j++;
3929
+ if (j - i < 3) {
3930
+ out.push(emitDef(list[i]));
3931
+ i++;
3932
+ continue;
3933
+ }
3934
+ const run = list.slice(i, j);
3935
+ run.forEach(rxFor);
3936
+ const k = fusedRuns.length;
3937
+ fusedRuns.push({ flags: run[0].flags, ids: run.map((d) => d.id), rx: run.map((d) => (
3938
+ /** @type {number} */
3939
+ regexIndex.get(d.id)
3940
+ )) });
3941
+ out.push(
3942
+ `FX[${k}].lastIndex = offset; m = FX[${k}].exec(text)
3943
+ if (m !== null) {
3944
+ 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 }
3945
+ // An empty match is "no match" to a lexer, so the members after it still get their turn.
3946
+ 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 } }
3947
+ }`
3948
+ );
3949
+ i = j;
3950
+ }
3951
+ return out.join("\n ");
3952
+ }
3953
+ const lists = [{ list: (
3954
+ /** @type {any[]} */
3955
+ []
3956
+ ) }];
3957
+ const listIds = /* @__PURE__ */ new Map([["", 0]]);
3958
+ const listFor = (list) => {
3959
+ const key = list.map((c) => c.id).join(",");
3960
+ let id = listIds.get(key);
3961
+ if (id === void 0) {
3962
+ id = lists.length;
3963
+ listIds.set(key, id);
3964
+ lists.push({ list });
3965
+ }
3966
+ return id;
3967
+ };
3968
+ const WIDTH = 129;
3969
+ const table = [];
3970
+ for (const name of modeNames) {
3971
+ const members = compiled.filter((c) => c.modes.includes(name));
3972
+ if (members.length === 0) throw new Error(`generateLexer: mode '${name}' has no tokens`);
3973
+ const dispatch = members.some((c) => c.firstChars !== null);
3974
+ for (let code = 0; code < 128; code++) {
3975
+ table.push(listFor(dispatch ? members.filter((c) => c.firstChars === null || c.firstChars.has(code)) : members));
3976
+ }
3977
+ table.push(listFor(dispatch ? members.filter((c) => c.firstChars === null || [...c.firstChars].some((x) => x >= 128)) : members));
3978
+ }
3979
+ if (lists.length > 6e4) throw new Error("generateLexer: too many distinct candidate lists");
3980
+ const dispatchTable = String.fromCharCode(...table.map((id) => 256 + id));
3981
+ const listCases = lists.slice(1).map(({ list }, i) => `case ${i + 1}: {
3982
+ ${emitList(list)}
3983
+ break
3984
+ }`);
3985
+ const longer = compiled.filter((c) => c.longerAlt);
3986
+ const longerCode = longer.length === 0 ? "" : `
3987
+ switch (id) {
3988
+ ${longer.map((c) => {
3989
+ const alt2 = (
3990
+ /** @type {any} */
3991
+ byName.get(
3992
+ /** @type {string} */
3993
+ c.longerAlt
3994
+ )
3995
+ );
3996
+ 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)`;
3997
+ 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 }`;
3998
+ }).join("\n")}
3999
+ }`;
4000
+ const transitions = compiled.filter((c) => c.popMode || c.pushMode !== -1);
4001
+ const describe = trackLines ? "'line ' + line + ' column ' + (offset - lineStart + 1)" : "'offset ' + offset";
4002
+ const transitionCode = transitions.length === 0 ? "" : `
4003
+ switch (id) {
4004
+ ${transitions.map((c) => c.popMode ? ` case ${c.id}:
4005
+ 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"} })
4006
+ mode = stack[--depth]
4007
+ break` : ` case ${c.id}: stack[depth++] = mode; mode = ${c.pushMode}; break`).join("\n")}
4008
+ }`;
4009
+ const where = trackLines ? "'line ' + line + ' column ' + column" : "'offset ' + offset";
4010
+ const inMode = modeNames.length > 1 ? " + ' (in lexer mode \\'' + MODE_NAMES[mode] + '\\')'" : "";
4011
+ return `(() => {
4012
+ const NAMES = tokens.map((t) => t.name)
4013
+ const SKIP = tokens.map((t) => (t.skip ? 1 : 0))
4014
+ const DISPATCH = ${q(dispatchTable)}
4015
+ ${modeNames.length > 1 ? `const MODE_NAMES = ${q(modeNames)}` : ""}
4016
+ // Patterns are read from the token table above rather than repeated here.
4017
+ const RX = ${q(regexes)}.map(([i, flags]) => new RegExp(tokens[i].pattern, flags))
4018
+ ${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))
4019
+ const FIDS = ${q(fusedRuns.map((f) => f.ids))}
4020
+ const FRX = ${q(fusedRuns.map((f) => f.rx))}` : ""}
4021
+ ${need.digits ? "const skipDigits = (text, i, length) => { while (i < length) { const c = text.charCodeAt(i); if (c < 48 || c > 57) break; i++ } return i }" : ""}
4022
+ ${need.hex ? "const isHex = (c) => (c >= 48 && c <= 57) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102)" : ""}
4023
+ ${need.keyword ? `const KW = ${q(keywords)}
4024
+ ${[...lookFns].map(([cond, name]) => `const ${name} = (c) => ${cond}`).join("\n ")}
4025
+ ${KEYWORD_HELPER}` : ""}
4026
+ ${helpers.join("\n")}
4027
+
4028
+ function tokenize (text) {
4029
+ if (typeof text !== 'string') throw new TypeError('tokenize: expected a string')
4030
+ const tokens_ = []
4031
+ let offset = 0
4032
+ let line = 1
4033
+ let lineStart = 0
4034
+ const length = text.length
4035
+ ${multiMode ? "let mode = 0\n const stack = []\n let depth = 0" : ""}
4036
+ let m = null
4037
+ while (offset < length) {
4038
+ const code = text.charCodeAt(offset)
4039
+ let id = -1
4040
+ let end = -1
4041
+ let matched = null
4042
+ found: {
4043
+ switch (DISPATCH.charCodeAt(${multiMode ? `mode * ${WIDTH} + ` : ""}(code < 128 ? code : 128)) - 256) {
4044
+ ${listCases.join("\n ")}
4045
+ }
4046
+ }
4047
+ if (id === -1) {
4048
+ const column = offset - lineStart + 1
4049
+ throw new LexError('Unexpected character \\'' + text[offset] + '\\' at ' + ${where}${inMode}, { offset, line: ${trackLines ? "line" : "0"}, col: ${trackLines ? "column" : "0"} })
4050
+ }${longerCode}
4051
+ if (SKIP[id] === 0) {
4052
+ const image = matched !== null ? matched : text.slice(offset, end)
4053
+ tokens_.push(${trackLines ? "{ type: NAMES[id], image, start: offset, end, line, col: offset - lineStart + 1 }" : "{ type: NAMES[id], id, image, start: offset, end }"})
4054
+ }
4055
+ ${trackLines ? "for (let j = offset; j < end; j++) { if (text.charCodeAt(j) !== 10) continue; line++; lineStart = j + 1 }" : ""}${transitionCode}
4056
+ offset = end
4057
+ }
4058
+ return tokens_
4059
+ }
4060
+ return { tokenize, tokenNames: NAMES, positions: ${q(positions)}, modeNames: ${q(modeNames)} }
4061
+ })()`;
4062
+ }
4063
+ var DEFAULT_MODE2 = "default";
4064
+ var q = JSON.stringify;
4065
+ function modesOf2(def) {
4066
+ if (Array.isArray(def.modes) && def.modes.length > 0) return def.modes;
4067
+ if (def.mode) return [def.mode];
4068
+ return [DEFAULT_MODE2];
4069
+ }
4070
+ function condition(table, v) {
4071
+ const parts = [];
4072
+ for (let i = 0; i < 128; ) {
4073
+ if (table[i] !== 1) {
4074
+ i++;
4075
+ continue;
4076
+ }
4077
+ let j = i;
4078
+ while (j + 1 < 128 && table[j + 1] === 1) j++;
4079
+ parts.push(i === j ? `${v} === ${i}` : `(${v} >= ${i} && ${v} <= ${j})`);
4080
+ i = j + 1;
4081
+ }
4082
+ return parts.length > 0 ? parts.join(" || ") : "false";
4083
+ }
4084
+ var KEYWORD_HELPER = `function scanKeyword (text, offset, length, lower, upper, look) {
4085
+ const n = lower.length
4086
+ if (offset + n > length) return -1
4087
+ for (let i = 0; i < n; i++) {
4088
+ const c = text.charCodeAt(offset + i)
4089
+ if (c !== lower[i] && c !== upper[i]) return -1
4090
+ }
4091
+ const end = offset + n
4092
+ if (end < length && look(text.charCodeAt(end))) return -1
4093
+ return end
4094
+ }`;
4095
+ function wideClassHelper(id, s) {
4096
+ return `function scan${id} (text, offset, length) {
4097
+ const code = text.charCodeAt(offset)
4098
+ if (code >= 128) return -2
4099
+ if (!(${condition(s.first, "code")})) return -1
4100
+ let j = offset + 1
4101
+ while (j < length) {
4102
+ const c = text.charCodeAt(j)
4103
+ if (c >= 128) return -2
4104
+ if (!(${condition(s.rest, "c")})) break
4105
+ j++
4106
+ }
4107
+ return j
4108
+ }`;
4109
+ }
4110
+ function quotedHelper(id, s) {
4111
+ return `function scan${id} (text, offset) {
4112
+ if (text.charCodeAt(offset) !== ${s.quote}) return -1
4113
+ let fallback = -1
4114
+ let j = offset + 1
4115
+ for (;;) {
4116
+ j = text.indexOf(${q(String.fromCharCode(s.quote))}, j)
4117
+ if (j === -1) return fallback
4118
+ if (text.charCodeAt(j + 1) !== ${s.quote}) return j + 1
4119
+ fallback = j + 1
4120
+ j += 2
4121
+ }
4122
+ }`;
4123
+ }
4124
+ function numberHelper(id, s) {
4125
+ return `function scan${id} (text, offset, length) {
4126
+ let j = offset
4127
+ ${s.sign ? "if (text.charCodeAt(j) === 45) j++" : ""}
4128
+ const c = text.charCodeAt(j)
4129
+ ${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)"}
4130
+ ${s.frac ? "if (text.charCodeAt(j) === 46) { const k = skipDigits(text, j + 1, length); if (k > j + 1) j = k }" : ""}
4131
+ ${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 }" : ""}
4132
+ return j
4133
+ }`;
4134
+ }
4135
+ function stringHelper(id, s) {
4136
+ return `function scan${id} (text, offset, length) {
4137
+ if (text.charCodeAt(offset) !== ${s.quote}) return -1
4138
+ let j = offset + 1
4139
+ for (;;) {
4140
+ if (j >= length) return -1
4141
+ const c = text.charCodeAt(j)
4142
+ if (c === ${s.quote}) return j + 1
4143
+ if (c === 92) {
4144
+ const d = text.charCodeAt(j + 1)
4145
+ ${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
4146
+ ${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`}
4147
+ continue
4148
+ }
4149
+ ${s.ctrl ? "if (c < 32) return -1" : ""}
4150
+ j++
4151
+ }
4152
+ }`;
4153
+ }
4154
+
2802
4155
  // src/emit.js
2803
4156
  import { Compiler, LogicEngine as LogicEngine2 } from "json-logic-engine";
2804
4157
  var UNSUPPORTED = /\b(coerceArray|precoerceNumber|compareCheck|assertAllowedDepth|assertSize|asyncIterators|notTraversed)\b|(?<!engine\.)\bmethods\[|engine\.(?!methods\b)/;
@@ -2847,11 +4200,21 @@ function emitModule(spec, options = {}) {
2847
4200
  const { tokens: tokenDefs, rules, start } = spec;
2848
4201
  const {
2849
4202
  memo = false,
4203
+ autoMemo = true,
4204
+ // Off here, on in `createParser`. Inlining a precedence chain is worth 2-3x on a bare chain but only
4205
+ // 1.02-1.04x on the formula and SQL examples, and it makes the file bigger: the formula module grows
4206
+ // 32% minified (38 to 50 KB) even after the second copy of each operand is kept as a call. A parser
4207
+ // built at run time pays nothing for that; a file someone ships does.
4208
+ inlineRules = false,
4209
+ idioms = true,
2850
4210
  strict = true,
2851
4211
  ll1 = true,
2852
4212
  maxSteps = 0,
2853
4213
  positions = "full",
2854
4214
  execution = "generated",
4215
+ lexer: lexerMode = "generated",
4216
+ scanners = true,
4217
+ fuse = true,
2855
4218
  format = "esm",
2856
4219
  validate = true,
2857
4220
  moduleName = spec.name || "parser",
@@ -2860,6 +4223,7 @@ function emitModule(spec, options = {}) {
2860
4223
  methods: semanticMethods
2861
4224
  } = options;
2862
4225
  if (format !== "esm" && format !== "cjs") throw new Error(`emitModule: format must be 'esm' or 'cjs', got ${quote(format)}`);
4226
+ if (lexerMode !== "generated" && lexerMode !== "runtime") throw new Error(`emitModule: lexer must be 'generated' or 'runtime', got ${quote(lexerMode)}`);
2863
4227
  const methodsBlock = options.methodsBlock !== void 0 ? options.methodsBlock : spec.methodsBlock;
2864
4228
  const block = methodsBlock ? emitMethodsBlock(methodsBlock, format) : null;
2865
4229
  if (!start || !(start in rules)) throw new Error(`emitModule: start rule '${start}' is not defined in rules`);
@@ -2872,8 +4236,10 @@ function emitModule(spec, options = {}) {
2872
4236
  throw new Error("not compiled during emit");
2873
4237
  }, run: () => {
2874
4238
  } };
2875
- const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval: false, tokenIds });
2876
- const codegen = createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds });
4239
+ const memoRules = autoMemo && !memo && analysis.errors.length === 0 ? selectMemoRules(rules, analysis.firsts, { ll1 }) : [];
4240
+ const memoSlots = new Map(memoRules.map((name, slot) => [name, slot]));
4241
+ const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval: false, tokenIds, memoSlots, idioms });
4242
+ const codegen = createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds, memoSlots, inlineRules, idioms });
2877
4243
  codegen.compile(start);
2878
4244
  const reached = [...codegen.ruleIndex.entries()].sort((a, b) => a[1] - b[1]);
2879
4245
  const unsupported = codegen.ORIGINS.map((origin, index) => ({ origin, index })).filter(({ origin }) => origin.kind === "unknownRule");
@@ -2914,7 +4280,8 @@ R[${index}] = ${codegen.SOURCES[index]}`
2914
4280
  ).join("\n\n");
2915
4281
  const interpretedActions = execution === "interpreted";
2916
4282
  const cjs = format === "cjs";
2917
- const runtimeImports = ["FAIL as F", "expect as E", "createState", "buildParseError", "createLexer"];
4283
+ const generatedLexer = lexerMode === "generated";
4284
+ const runtimeImports = ["FAIL as F", "expect as E", "createState", "buildParseError", generatedLexer ? "LexError" : "createLexer"];
2918
4285
  if (needsEngine) runtimeImports.push("createValueMethods");
2919
4286
  const importRuntime = cjs ? `const { ${runtimeImports.map((b) => b.replace(" as ", ": ")).join(", ")} } = require(${quote(runtimeSpecifier)})` : `import { ${runtimeImports.join(", ")} } from ${quote(runtimeSpecifier)}`;
2920
4287
  const importEngine = engineFree ? "" : cjs ? `const { LogicEngine } = require(${quote(engineSpecifier)})
@@ -2933,7 +4300,7 @@ export default { ${PUBLIC.join(", ")} }`;
2933
4300
  //
2934
4301
  // The grammar was analyzed when this file was written: FIRST sets, left-recursion
2935
4302
  // checks and rule specialization all happened then. Importing it does no grammar
2936
- // work and generates no code.
4303
+ // work and generates no code.${generatedLexer ? "\n// The lexer was written out as source too, so there is no lexer engine to load." : ""}
2937
4304
  ${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."}
2938
4305
 
2939
4306
  ${importRuntime}
@@ -2943,7 +4310,7 @@ const tokens = ${literal(tokenDefs)}
2943
4310
 
2944
4311
  const start = ${quote(start)}
2945
4312
 
2946
- const lexer = createLexer(tokens, { positions: ${quote(positions)} })
4313
+ const lexer = ${generatedLexer ? generateLexer(tokenDefs, { positions, scanners, fuse }) : `createLexer(tokens, { positions: ${quote(positions)}, scanners: ${scanners}, fuse: ${fuse} })`}
2947
4314
  const tokenize = lexer.tokenize
2948
4315
 
2949
4316
  ${engineFree ? `// Every semantic action compiled to source, so the engine reduced to a table
@@ -3034,7 +4401,7 @@ const RULES = ${literal(Object.fromEntries(reached.map(([name, index]) => [name,
3034
4401
  * @param {string} [source]
3035
4402
  */
3036
4403
  function parseTokens (tokenList, source, track = false) {
3037
- const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track })
4404
+ const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track, memoSlots: ${memoRules.length} })
3038
4405
  state.source = source
3039
4406
  state.positions = ${quote(positions)}
3040
4407
  state.ruleNames[0] = start