parseman 0.55.4 → 0.56.0

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.
Files changed (88) hide show
  1. package/dist/analysis/choice-cost.d.ts.map +1 -1
  2. package/dist/analysis/commitment.d.ts.map +1 -1
  3. package/dist/analysis/diagnostics.js +3353 -3095
  4. package/dist/analysis/diagnostics.js.map +3 -3
  5. package/dist/analysis/duplication.d.ts.map +1 -1
  6. package/dist/analysis/gating.d.ts.map +1 -1
  7. package/dist/cli/index.js +1813 -1555
  8. package/dist/cli/index.js.map +3 -3
  9. package/dist/combinators/attempt.d.ts.map +1 -1
  10. package/dist/combinators/choice.d.ts.map +1 -1
  11. package/dist/combinators/dispatch.d.ts.map +1 -1
  12. package/dist/combinators/expect.d.ts.map +1 -1
  13. package/dist/combinators/first-set.d.ts.map +1 -1
  14. package/dist/combinators/lines.d.ts +79 -0
  15. package/dist/combinators/lines.d.ts.map +1 -0
  16. package/dist/combinators/trivia-skip.d.ts +1 -0
  17. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  18. package/dist/compiler/fields.d.ts.map +1 -1
  19. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  20. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  21. package/dist/compiler/token-alphabet.d.ts.map +1 -1
  22. package/dist/compiler/token-capability.js +25 -0
  23. package/dist/compiler/token-capability.js.map +1 -1
  24. package/dist/coverage.d.ts.map +1 -1
  25. package/dist/cst/reflection.d.ts.map +1 -1
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1905 -1625
  29. package/dist/index.js.map +3 -3
  30. package/dist/language-service/index.js +2 -0
  31. package/dist/language-service/index.js.map +1 -1
  32. package/dist/parse-context.d.ts.map +1 -1
  33. package/dist/plugin/evaluator.d.ts.map +1 -1
  34. package/dist/plugin/index.js +1318 -1045
  35. package/dist/plugin/index.js.map +3 -3
  36. package/dist/plugin/ir-eval.d.ts +0 -4
  37. package/dist/plugin/ir-eval.d.ts.map +1 -1
  38. package/dist/run/index.js +2 -0
  39. package/dist/run/index.js.map +1 -1
  40. package/dist/spec/index.js +15 -0
  41. package/dist/spec/index.js.map +1 -1
  42. package/dist/spec/model.d.ts.map +1 -1
  43. package/dist/table/assemble.d.ts +0 -65
  44. package/dist/table/assemble.d.ts.map +1 -1
  45. package/dist/table/emit.d.ts.map +1 -1
  46. package/dist/table/encode.d.ts.map +1 -1
  47. package/dist/table/exec.d.ts.map +1 -1
  48. package/dist/table/index.js +3442 -3192
  49. package/dist/table/index.js.map +3 -3
  50. package/dist/table/line-scan.d.ts +9 -0
  51. package/dist/table/line-scan.d.ts.map +1 -0
  52. package/dist/table/program.d.ts +11 -2
  53. package/dist/table/program.d.ts.map +1 -1
  54. package/dist/types.d.ts +21 -0
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/version.d.ts +1 -1
  57. package/package.json +1 -1
  58. package/src/analysis/choice-cost.ts +1 -0
  59. package/src/analysis/commitment.ts +13 -0
  60. package/src/analysis/duplication.ts +3 -0
  61. package/src/analysis/gating.ts +5 -0
  62. package/src/analysis/rebuild.ts +2 -2
  63. package/src/combinators/attempt.ts +2 -0
  64. package/src/combinators/choice.ts +3 -0
  65. package/src/combinators/dispatch.ts +3 -0
  66. package/src/combinators/expect.ts +5 -0
  67. package/src/combinators/first-set.ts +9 -1
  68. package/src/combinators/lines.ts +244 -0
  69. package/src/combinators/trivia-skip.ts +4 -2
  70. package/src/compiler/fields.ts +3 -0
  71. package/src/compiler/grammar-coverage-ids.ts +1 -0
  72. package/src/compiler/ir-serialize.ts +10 -0
  73. package/src/compiler/token-alphabet.ts +3 -1
  74. package/src/coverage.ts +6 -0
  75. package/src/cst/reflection.ts +3 -0
  76. package/src/index.ts +2 -0
  77. package/src/parse-context.ts +2 -0
  78. package/src/plugin/evaluator.ts +4 -0
  79. package/src/plugin/ir-eval.ts +3 -0
  80. package/src/spec/model.ts +15 -0
  81. package/src/table/assemble.ts +6 -12
  82. package/src/table/emit.ts +7 -6
  83. package/src/table/encode.ts +18 -6
  84. package/src/table/exec.ts +3 -0
  85. package/src/table/line-scan.ts +19 -0
  86. package/src/table/program.ts +17 -2
  87. package/src/types.ts +14 -0
  88. package/src/version.ts +1 -1
package/dist/index.js CHANGED
@@ -155,6 +155,11 @@ function matchesEmptyBody(p, seen, resolve) {
155
155
  return true;
156
156
  }
157
157
  }
158
+ // A run consumes at least its first line's prefix, and its body after that.
159
+ case "lines":
160
+ return me(d.first ?? d.prefix) && me(d.body);
161
+ case "columns":
162
+ return d.n === 0;
158
163
  default:
159
164
  return true;
160
165
  }
@@ -269,6 +274,11 @@ function firstSetBody(p, seen, resolve, activeTrivia) {
269
274
  resolve,
270
275
  d.clearTrivia ? null : d.triviaParser ?? activeTrivia
271
276
  );
277
+ // A run starts with its first line's prefix; a nullable prefix starts with anything.
278
+ case "lines": {
279
+ const opening = d.first ?? d.prefix;
280
+ return empties(opening) ? any() : fs(opening);
281
+ }
272
282
  default:
273
283
  return p._meta.firstSet;
274
284
  }
@@ -1293,6 +1303,8 @@ function createParseContext() {
1293
1303
  _triviaCaptureMask: void 0,
1294
1304
  trackLines: false,
1295
1305
  state: void 0,
1306
+ lineView: void 0,
1307
+ _columnsSplit: void 0,
1296
1308
  build: void 0,
1297
1309
  _errors: void 0,
1298
1310
  _tolerant: void 0,
@@ -2014,11 +2026,12 @@ function parseTriviaNoCapture(triviaP, input, cur, ctx) {
2014
2026
  return result;
2015
2027
  }
2016
2028
  function saveLookaheadMark(ctx) {
2017
- return { ...saveTriviaMark(ctx), probeBest: ctx._probe ? ctx._probe.best : null };
2029
+ return { ...saveTriviaMark(ctx), probeBest: ctx._probe ? ctx._probe.best : null, split: ctx._columnsSplit };
2018
2030
  }
2019
2031
  function rollbackLookahead(ctx, mark) {
2020
2032
  rollbackTrivia(ctx, mark);
2021
2033
  if (ctx._probe) ctx._probe.best = mark.probeBest;
2034
+ ctx._columnsSplit = mark.split;
2022
2035
  }
2023
2036
  function scanWithLabels(input, cur, ctx) {
2024
2037
  const triviaP = ctx.trivia;
@@ -2887,6 +2900,12 @@ function derive(c, seen) {
2887
2900
  return deriveExpected2(def.parser);
2888
2901
  case "expect":
2889
2902
  return def.expected;
2903
+ case "lines": {
2904
+ const opening = def.first ?? def.prefix;
2905
+ return matchesEmpty(opening) ? [...deriveExpected2(opening), ...deriveExpected2(def.body)] : deriveExpected2(opening);
2906
+ }
2907
+ case "columns":
2908
+ return [`columns(${def.n})`];
2890
2909
  case "node":
2891
2910
  case "grammar":
2892
2911
  case "trivia":
@@ -3074,9 +3093,11 @@ function choice(...args) {
3074
3093
  for (let i = 0; i < parsers.length; i++) {
3075
3094
  if (gates[i] && !gates[i](ctx.state)) continue;
3076
3095
  const mark = saveTriviaMark(ctx);
3096
+ const split = ctx._columnsSplit;
3077
3097
  const result = parsers[i].parse(input, pos, ctx);
3078
3098
  if (!result.ok) {
3079
3099
  rollbackTrivia(ctx, mark);
3100
+ ctx._columnsSplit = split;
3080
3101
  const at = result.span.start;
3081
3102
  if (at > expectedAt) {
3082
3103
  expectedAt = at;
@@ -3297,6 +3318,9 @@ function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
3297
3318
  return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen)) || def.recoverAt !== void 0 && parserUsesRouted(def.recoverAt, seen) || def.stopAt !== void 0 && parserUsesRouted(def.stopAt, seen);
3298
3319
  case "recover":
3299
3320
  return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
3321
+ // The body parses its own input, so only the checks on this one can reuse a routed head.
3322
+ case "lines":
3323
+ return [def.prefix, def.first, def.lazy].some((entry) => entry !== void 0 && parserUsesRouted(entry, seen));
3300
3324
  case "lazy":
3301
3325
  try {
3302
3326
  return parserUsesRouted(def.thunk(), seen);
@@ -3550,9 +3574,11 @@ function attempt(parser2, options) {
3550
3574
  const mark = saveCstMark(ctx);
3551
3575
  const log = ctx._triviaLog?.length;
3552
3576
  const rootLog = ctx._rootTriviaLog?.length;
3577
+ const split = ctx._columnsSplit;
3553
3578
  const result = parser2.parse(input, pos, ctx);
3554
3579
  if (result.ok) return result;
3555
3580
  rollbackCstCapture(ctx, mark);
3581
+ ctx._columnsSplit = split;
3556
3582
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
3557
3583
  if (rootLog !== void 0 && ctx._rootTriviaLog && ctx._rootTriviaLog.length !== rootLog) ctx._rootTriviaLog.length = rootLog;
3558
3584
  if (result.committed && !contain) return result;
@@ -4344,6 +4370,9 @@ function childrenOf(def) {
4344
4370
  ];
4345
4371
  case "routed":
4346
4372
  return def.fallback ? [def.fallback] : [];
4373
+ case "lines":
4374
+ return [def.prefix, def.body, ...def.first === void 0 ? [] : [def.first], ...def.lazy === void 0 ? [] : [def.lazy]];
4375
+ case "columns":
4347
4376
  case "literal":
4348
4377
  case "regex":
4349
4378
  case "keywords":
@@ -4655,12 +4684,12 @@ function beginCompileDegradationDrain() {
4655
4684
  return true;
4656
4685
  });
4657
4686
  if (fresh.length === 0) return;
4658
- const lines = formatDegradations(fresh);
4687
+ const lines2 = formatDegradations(fresh);
4659
4688
  if (resolveDegradationLevel() === "error") {
4660
4689
  throw new Error(`parseman: ${fresh.length} degraded compilation path(s)
4661
- ${lines.join("\n")}`);
4690
+ ${lines2.join("\n")}`);
4662
4691
  }
4663
- for (const l of lines) console.warn(l);
4692
+ for (const l of lines2) console.warn(l);
4664
4693
  };
4665
4694
  }
4666
4695
 
@@ -4878,6 +4907,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
4878
4907
  return parserHasOwnFields(d.sentinel, seen) || d.skip.some((x) => parserHasOwnFields(x, seen)) || d.recoverAt !== void 0 && parserHasOwnFields(d.recoverAt, seen) || d.stopAt !== void 0 && parserHasOwnFields(d.stopAt, seen);
4879
4908
  case "recover":
4880
4909
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.sentinel, seen);
4910
+ case "lines":
4911
+ return parserHasOwnFields(d.body, seen);
4881
4912
  case "routed":
4882
4913
  return d.fallback ? parserHasOwnFields(d.fallback, seen) : false;
4883
4914
  case "many":
@@ -5291,1605 +5322,1633 @@ function sourceLeaf(root, fn) {
5291
5322
  return makeLeaf(root, fn, true);
5292
5323
  }
5293
5324
 
5294
- // src/analysis/gating.ts
5295
- function childrenOf2(d) {
5296
- switch (d.tag) {
5297
- case "sequence":
5298
- case "choice":
5299
- return d.parsers;
5300
- case "dispatch":
5301
- return [
5302
- d.selector,
5303
- ...d.cases.map((c) => c.parser),
5304
- ...d.matchers ? d.matchers.map((c) => c.parser) : [],
5305
- ...d.otherwise === void 0 ? [] : [d.otherwise]
5306
- ];
5307
- case "sepBy":
5308
- return [d.parser, d.separator];
5309
- case "recover":
5310
- return [d.parser, d.sentinel];
5311
- case "scanTo":
5312
- return [
5313
- d.sentinel,
5314
- ...d.skip,
5315
- ...d.recoverAt === void 0 ? [] : [d.recoverAt],
5316
- ...d.stopAt === void 0 ? [] : [d.stopAt]
5317
- ];
5318
- case "grammar":
5319
- return d.triviaParser ? [d.parser, d.triviaParser] : [d.parser];
5320
- case "routed":
5321
- return d.fallback ? [d.fallback] : [];
5322
- // A `lazy` is a REFERENCE, not a subtree. Descending through it would make
5323
- // every rule's walk cover the whole reachable grammar — site paths become
5324
- // nonsense, and a structural hash includes half the grammar. Treated as a
5325
- // leaf, keyed by the rule name it refers to, which is also the right
5326
- // semantics: two productions referencing `g.Ident` really do fill that slot
5327
- // the same way.
5328
- case "lazy":
5329
- case "literal":
5330
- case "regex":
5331
- case "keywords":
5332
- case "guard":
5333
- case "adjacency":
5334
- case "unknown":
5335
- return [];
5336
- default: {
5337
- const rec = d;
5338
- return rec.parser ? [rec.parser] : [];
5339
- }
5325
+ // src/table/lex-body.ts
5326
+ function buildLexBodyRecognizer(spec, constants) {
5327
+ const raw = constants[spec[0]];
5328
+ if (!(raw instanceof RegExp)) {
5329
+ throw new TypeError("table lexical body does not reference a RegExp constant");
5340
5330
  }
5341
- }
5342
- var isAny = (fs) => fs.kind === "any";
5343
- function intersection(a, b) {
5344
- if (a.kind === "any") return b;
5345
- if (b.kind === "any") return a;
5346
- if (a.kind === "empty" || b.kind === "empty") return { kind: "empty" };
5347
- const ranges = [];
5348
- for (const ra of a.ranges) for (const rb of b.ranges) {
5349
- const lo = Math.max(ra.lo, rb.lo);
5350
- const hi = Math.min(ra.hi, rb.hi);
5351
- if (lo <= hi) ranges.push({ lo, hi });
5331
+ if (!Number.isInteger(spec[1]) || spec[1] < -1 || spec[1] > 65535) {
5332
+ throw new TypeError("table lexical body suffix is not absent or one UTF-16 code unit");
5352
5333
  }
5353
- return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
5334
+ const re = raw.sticky ? new RegExp(raw.source, raw.flags) : new RegExp(raw.source, `${raw.flags.replace(/[gy]/g, "")}y`);
5335
+ const suffix = spec[1];
5336
+ return (input, pos) => {
5337
+ re.lastIndex = pos;
5338
+ if (!re.test(input)) return -1;
5339
+ const end = re.lastIndex;
5340
+ if (suffix < 0) return end * 2;
5341
+ return input.charCodeAt(end) === suffix ? (end + 1) * 2 + 1 : end * 2;
5342
+ };
5354
5343
  }
5355
- function combine(sets) {
5356
- let acc = { kind: "empty" };
5357
- for (const s of sets) {
5358
- if (s.kind === "any") return { kind: "any" };
5359
- if (s.kind === "empty") continue;
5360
- acc = acc.kind === "ranges" ? { kind: "ranges", ranges: [...acc.ranges, ...s.ranges] } : s;
5344
+
5345
+ // src/table/lex-program.ts
5346
+ var LEX_NODE_TERMINAL = 0;
5347
+ var LEX_NODE_SEQUENCE2 = 1;
5348
+ var LEX_NODE_SEQUENCE3 = 2;
5349
+ var LEX_NODE_SEQUENCE4 = 3;
5350
+ var LEX_NODE_SEQUENCE5 = 4;
5351
+ var LEX_NODE_CHOICE2 = 5;
5352
+ var LEX_NODE_CHOICE4 = 6;
5353
+ var LEX_NODE_CHOICE8 = 7;
5354
+ var LEX_NODE_NOT = 8;
5355
+ var LEX_NODE_OPTIONAL = 9;
5356
+ function lexProgramDigest(words) {
5357
+ let hash = 2166136261;
5358
+ for (const word2 of words) {
5359
+ hash ^= word2 | 0;
5360
+ hash = Math.imul(hash, 16777619);
5361
5361
  }
5362
- return acc;
5363
- }
5364
- var ch = (c) => c >= 32 && c < 127 ? `'${String.fromCharCode(c)}'` : `\\u${c}`;
5365
- function firstSetToString(fs) {
5366
- if (fs.kind === "any") return "ANY";
5367
- if (fs.kind === "empty") return "(empty)";
5368
- return fs.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
5362
+ return hash | 0;
5369
5363
  }
5370
- var ruleNameOf2 = (p) => p._ruleName;
5371
- var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
5372
- var SUGGESTIONS = {
5373
- "broad-recognizer": "This arm can begin with any character, so no single-character test can rule it out. The parser has to enter it \u2014 set up, try, and undo \u2014 at every position it reaches, instead of skipping it for free.\nTo fix: make the arm begin with a fixed character, word or keyword. If it is meant to be a catch-all that matches anything, leave it and add the choice to the accept list at the end.",
5374
- "leading-not": "This arm begins with a not(...) check, which matches nothing and so says nothing about which character the arm starts with. The parser then cannot rule the arm out and enters it everywhere.\nTo fix: put the term that actually consumes text first and keep not(...) after it as a trailing boundary. If you need to REQUIRE something ahead without consuming it, use peek(X) \u2014 it checks the same thing but still tells parseman that the arm starts with X.",
5375
- "nullable-prefix": "This arm begins with something optional \u2014 an optional(...) or a repeat that allows zero items \u2014 so the arm can also start with whatever comes AFTER it, and that is broad enough that no character rules the arm out.\nTo fix: give the optional part its own arm so each arm has a definite beginning, or require at least one item. A plain sepBy(item, sep) also matches nothing at all; pass { min: 1 } when the list must not be empty.",
5376
- "cross-artifact-ref": "This arm hands off to another rule, and that rule has the same problem \u2014 it can begin with any character \u2014 so the cost is inherited rather than caused here.\nTo fix: run this check on the rule it refers to and fix it there. One rule given a definite beginning fixes every choice that uses it.",
5377
- "opaque-wrapper": "This arm begins with a wrapper that only inspects parser state (a gate/withCtx/recover) and consumes no text, so it says nothing about which character the arm starts with.\nTo fix: put a term that actually matches text in front of the wrapper.",
5378
- "ref-cycle": "This arm refers back to itself through a cycle of rules, and parseman could not work out a definite first character for any of them.\nTo fix: make sure the base case of the recursion begins with a fixed character or keyword, so the cycle has somewhere definite to start."
5379
- };
5380
- function refineSuggestion(cause, arm) {
5381
- if (cause !== "broad-recognizer") return SUGGESTIONS[cause];
5382
- const lead2 = peelToLeading(arm);
5383
- const d = lead2._def;
5384
- if (d.tag === "regex" && KEYWORD_REGEX_RE.test(d.source)) {
5385
- return `This arm matches the fixed word \`${d.source}\` with a regular expression, and parseman cannot always tell from a regular expression which character it starts with.
5386
- To fix: write it as word('\u2026') or keywords([\u2026]) instead. They match exactly the same text and compile to the same character scan, but they also tell parseman the first character, which is what lets the parser skip this arm when it cannot match.`;
5364
+ function classHas(cls, code) {
5365
+ if (code < 0) return false;
5366
+ if (code < 128) return cls.ascii[code] === 1;
5367
+ for (let i = 0; i < cls.hi.length; i += 2) {
5368
+ if (code >= cls.hi[i] && code <= cls.hi[i + 1]) return true;
5387
5369
  }
5388
- if (d.tag === "scanTo") {
5389
- return "This arm ends in a scanTo(...) catch-all, which reads forward until it finds something and so can begin at any character. The parser can never rule it out. That is usually exactly what you want from a fallback.\nTo fix: usually nothing. Add this choice to the accept list at the end so the check keeps flagging the choices that are real problems.";
5370
+ return false;
5371
+ }
5372
+ var committed = (ctx) => ctx._fc === true;
5373
+ function trackLexLines(ctx, input, end) {
5374
+ let at = ctx._lineScannedTo ?? 0;
5375
+ const starts = ctx._lineStarts;
5376
+ if (starts === void 0 || end <= at) return;
5377
+ while (at < end) {
5378
+ if (input.charCodeAt(at) === 10) starts.push(at + 1);
5379
+ at++;
5390
5380
  }
5391
- return SUGGESTIONS[cause];
5381
+ ctx._lineScannedTo = end;
5392
5382
  }
5393
- function classifyBroadArm(arm, resolve) {
5394
- const seen = /* @__PURE__ */ new Set();
5395
- const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
5396
- const walk = (p) => {
5397
- if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
5398
- seen.add(p);
5399
- const d = p._def;
5400
- switch (d.tag) {
5401
- case "literal":
5402
- case "regex":
5403
- case "keywords":
5404
- return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
5405
- case "not":
5406
- return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
5407
- // peek() CARRIES its body's first-set, so it is only broad when the body is
5408
- // (or the body is nullable) — walk in for the real cause.
5409
- case "peek":
5410
- return walk(d.parser);
5411
- case "scanTo":
5412
- return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
5413
- // `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
5414
- // its own — it is the poison. `withCtx`/`recover` FORWARD their inner
5415
- // first-set (see firstSetOf), so a broad result comes from the inner parser:
5416
- // walk into it for a precise cause.
5417
- case "guard":
5418
- return { cause: "opaque-wrapper", detail: `opaque wrapper (${d.tag})` };
5419
- // Zero-width and dropped from a sequence's first-set (isZeroWidthAssertion),
5420
- // so it is never the reason an arm is broad — but it can be reached as the
5421
- // sole term of a degenerate arm. Report it precisely rather than as a wrapper.
5422
- case "adjacency":
5423
- return { cause: "broad-recognizer", detail: `${d.polarity}() (zero-width assertion)` };
5424
- // attempt/withCtx/recover FORWARD their inner first-set (transparent for
5425
- // dispatch), so a broad result comes from the inner parser — walk into it.
5426
- case "attempt":
5427
- case "withCtx":
5428
- case "recover":
5429
- return walk(d.parser);
5430
- case "lazy": {
5431
- const name = p._ruleName;
5432
- let target;
5433
- try {
5434
- target = d.thunk();
5435
- } catch {
5436
- const bound = name !== void 0 ? resolve?.(name) : void 0;
5437
- if (bound === void 0)
5438
- return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
5439
- target = bound;
5440
- }
5441
- const inner = walk(target);
5442
- if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
5443
- return inner;
5444
- }
5445
- case "optional":
5446
- case "many": {
5447
- const inner = walk(d.parser);
5448
- return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
5449
- }
5450
- case "oneOrMore":
5451
- case "transform":
5452
- case "label":
5453
- case "field":
5454
- case "trivia":
5455
- case "token":
5456
- case "leaf":
5457
- case "node":
5458
- case "grammar":
5459
- case "expect":
5460
- return walk(d.parser);
5461
- case "sequence": {
5462
- let sawNullablePrefix = false;
5463
- for (const t of d.parsers) {
5464
- if (t._def.tag === "not") {
5465
- sawNullablePrefix = true;
5466
- continue;
5467
- }
5468
- if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve))) {
5469
- const inner = walk(t);
5470
- if (sawNullablePrefix)
5471
- return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
5472
- return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
5473
- }
5474
- if (matchesEmpty(t, /* @__PURE__ */ new Set(), resolve)) {
5475
- sawNullablePrefix = true;
5476
- continue;
5477
- }
5478
- return null;
5479
- }
5480
- return { cause: "broad-recognizer", detail: "sequence of only nullable/zero-width terms" };
5481
- }
5482
- case "choice": {
5483
- for (const a of d.parsers) {
5484
- const r = walk(a);
5485
- if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
5486
- }
5487
- return null;
5488
- }
5489
- case "dispatch":
5490
- return walk(d.selector);
5491
- default:
5492
- return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
5493
- }
5383
+ function terminalNode(matcher, failure, tracksLines) {
5384
+ const match = (input, pos) => {
5385
+ const result = matcher(input, pos);
5386
+ return result < 0 ? -1 : result / 2;
5494
5387
  };
5495
- return walk(arm) ?? { cause: "broad-recognizer", detail: "broad (cause not localized)" };
5496
- }
5497
- function peelToLeading(arm) {
5498
- let d = arm._def;
5499
- let cur = arm;
5500
- for (; ; ) {
5501
- if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label" || d.tag === "attempt") {
5502
- cur = d.parser;
5503
- d = cur._def;
5504
- continue;
5505
- }
5506
- break;
5507
- }
5508
- if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
5509
- return cur;
5510
- }
5511
- function detectAntiPatterns(rule, arms) {
5512
- const out = [];
5513
- arms.forEach((arm, i) => {
5514
- const lead2 = peelToLeading(arm);
5515
- const ld = lead2._def;
5516
- if (ld.tag === "not") {
5517
- const inner = ld.parser;
5518
- if (inner._def.tag === "not") {
5519
- out.push({
5520
- kind: "double-not",
5521
- rule,
5522
- armIndex: i,
5523
- message: 'not(not(X)) is a hand-written way of saying "X must come next, but do not consume it". parseman does that for you, and the hand-written form is worse than nothing here: because not(...) reveals no first character, the arm cannot be skipped, and among sibling arms that share a first character it can select the wrong one.\nTo fix: write peek(X). It checks the same thing without consuming, and it also tells parseman that the arm starts with whatever X starts with.'
5524
- });
5525
- } else {
5526
- out.push({
5527
- kind: "leading-not",
5528
- rule,
5529
- armIndex: i,
5530
- message: "This arm begins with not(...), which matches no text and so reveals no first character. The parser therefore cannot skip the arm and enters it at every position.\nTo fix: put the term that actually consumes text first, and keep not(...) after it as a trailing boundary check."
5531
- });
5388
+ return {
5389
+ match,
5390
+ run: (input, pos, ctx) => {
5391
+ const end = match(input, pos);
5392
+ if (end < 0) {
5393
+ ctx._fe = pos;
5394
+ ctx._fx = failure;
5395
+ if (ctx._probe !== void 0) failAt(ctx, ctx._fx, pos);
5396
+ return -1;
5532
5397
  }
5398
+ if (tracksLines) trackLexLines(ctx, input, end);
5399
+ return end;
5533
5400
  }
5534
- if (ld.tag === "regex" && KEYWORD_REGEX_RE.test(ld.source)) {
5535
- out.push({
5536
- kind: "keyword-regex",
5537
- rule,
5538
- armIndex: i,
5539
- message: `This arm starts with the fixed word \`${ld.source}\` written as a regular expression. Writing it as word('\u2026') or keywords([\u2026]) matches exactly the same text and compiles to the same character scan, but also tells parseman the first character, which is what lets the parser skip the arm when it cannot match.`
5540
- });
5541
- }
5542
- });
5543
- return out;
5544
- }
5545
- function analyzeGating(entry, opts) {
5546
- return analyzeGatingRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
5547
- }
5548
- function analyzeGatingRules(ruleMap, opts) {
5549
- const raw = [];
5550
- const antiPatterns = [];
5551
- const unanalysable = [];
5552
- const seenUnanalysable = /* @__PURE__ */ new Set();
5553
- const visited = /* @__PURE__ */ new Set();
5554
- const noteUnanalysable = (u) => {
5555
- const key = `${u.rule}\0${u.kind}`;
5556
- if (seenUnanalysable.has(key)) return;
5557
- seenUnanalysable.add(key);
5558
- unanalysable.push(u);
5559
5401
  };
5560
- const visit = (p, enclosingRule) => {
5561
- if (visited.has(p)) return;
5562
- visited.add(p);
5563
- const def = p?._def;
5564
- if (def === null || typeof def !== "object") {
5565
- noteUnanalysable(
5566
- typeof p === "function" ? {
5567
- rule: enclosingRule,
5568
- kind: "fused-rule",
5569
- reason: "fused rule function \u2014 a compose()/fuse result has no combinator graph. Analyze the composed grammar itself (analyzeGrammarGating) so the carried IR is re-lowered first."
5570
- } : {
5571
- rule: enclosingRule,
5572
- kind: "not-a-combinator",
5573
- reason: `value of type ${p === null ? "null" : typeof p} is not a combinator and carries no _def descriptor.`
5574
- }
5575
- );
5576
- return;
5577
- }
5578
- const d = def;
5579
- const rule = ruleNameOf2(p) ?? enclosingRule;
5580
- if (d.tag === "choice") {
5581
- raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule, arms: d.parsers });
5582
- antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
5402
+ }
5403
+ function sequence2(a, b) {
5404
+ return {
5405
+ match: (input, pos) => {
5406
+ const e0 = a.match(input, pos);
5407
+ return e0 < 0 ? -1 : b.match(input, e0);
5408
+ },
5409
+ run: (input, pos, ctx) => {
5410
+ const e0 = a.run(input, pos, ctx);
5411
+ if (e0 < 0) return -1;
5412
+ ctx._fc = false;
5413
+ return b.run(input, e0, ctx);
5583
5414
  }
5584
- const kids = [...childrenOf2(d)];
5585
- if (d.tag === "lazy") {
5586
- try {
5587
- kids.push(d.thunk());
5588
- } catch {
5589
- }
5415
+ };
5416
+ }
5417
+ function sequence3(a, b, c) {
5418
+ return {
5419
+ match: (input, pos) => {
5420
+ const e0 = a.match(input, pos);
5421
+ if (e0 < 0) return -1;
5422
+ const e1 = b.match(input, e0);
5423
+ return e1 < 0 ? -1 : c.match(input, e1);
5424
+ },
5425
+ run: (input, pos, ctx) => {
5426
+ const e0 = a.run(input, pos, ctx);
5427
+ if (e0 < 0) return -1;
5428
+ ctx._fc = false;
5429
+ const e1 = b.run(input, e0, ctx);
5430
+ if (e1 < 0) return -1;
5431
+ ctx._fc = false;
5432
+ return c.run(input, e1, ctx);
5590
5433
  }
5591
- for (const k of kids) visit(k, rule);
5592
5434
  };
5593
- for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name);
5594
- const perRule = /* @__PURE__ */ new Map();
5595
- for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
5596
- const seenInRule = /* @__PURE__ */ new Map();
5597
- const accept = new Set(opts?.accept ?? []);
5598
- const usedAccept = /* @__PURE__ */ new Set();
5599
- const choices = raw.map(({ g, rule, arms }) => {
5600
- const n = seenInRule.get(rule) ?? 0;
5601
- seenInRule.set(rule, n + 1);
5602
- const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
5603
- const isAccepted = g.gates === "no" && accept.has(id);
5604
- if (isAccepted) usedAccept.add(id);
5605
- const out = { ...g, id, accepted: isAccepted };
5606
- Object.defineProperty(out, "arms", { value: arms, enumerable: false, writable: false });
5607
- return out;
5608
- });
5609
- const gated = choices.filter((c) => c.gates === "yes").length;
5610
- const recoverable = choices.filter((c) => c.gates === "recoverable").length;
5611
- const ungated = choices.filter((c) => c.gates === "no" && !c.accepted && !c.deferred);
5612
- const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
5613
- const deferred = choices.filter((c) => c.gates === "no" && !c.accepted && c.deferred);
5614
- const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
5435
+ }
5436
+ function sequence4(a, b, c, d) {
5437
+ const abc = sequence3(a, b, c);
5615
5438
  return {
5616
- totalChoices: choices.length,
5617
- gated,
5618
- recoverable,
5619
- unanalysable,
5620
- ungated,
5621
- accepted,
5622
- deferred,
5623
- acceptedUnused,
5624
- choices,
5625
- antiPatterns
5439
+ match: (input, pos) => {
5440
+ const end = abc.match(input, pos);
5441
+ return end < 0 ? -1 : d.match(input, end);
5442
+ },
5443
+ run: (input, pos, ctx) => {
5444
+ const end = abc.run(input, pos, ctx);
5445
+ if (end < 0) return -1;
5446
+ ctx._fc = false;
5447
+ return d.run(input, end, ctx);
5448
+ }
5626
5449
  };
5627
5450
  }
5628
- function analyzeChoice(p, d, rule, resolve) {
5629
- const arms = d.parsers;
5630
- const shallow = arms.map((a) => a._meta.firstSet);
5631
- const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve));
5632
- const anyArms = deep.map((fs, index) => ({ fs, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
5633
- const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve);
5634
- return {
5635
- index,
5636
- cause,
5637
- detail,
5638
- shallowAnyOnly: false,
5639
- unresolvedExternal: unresolvedExternal === true,
5640
- suggestion: refineSuggestion(cause, arms[index])
5641
- };
5642
- });
5643
- const overlaps = [];
5644
- for (let i = 0; i < deep.length; i++)
5645
- for (let j = i + 1; j < deep.length; j++)
5646
- if (!isAny(deep[i]) && !isAny(deep[j]) && intersects(deep[i], deep[j]))
5647
- overlaps.push({
5648
- a: i,
5649
- b: j,
5650
- on: intersection(deep[i], deep[j]),
5651
- suggestion: "Two arms of this choice can begin with the same character, so the parser cannot tell from that character which one to try. It tries them in order and undoes the ones that do not match.\nTo fix: pull the shared beginning out in front of the choice so it is matched once \u2014 sequence(shared, choice(rest\u2026)) \u2014 instead of repeating it inside each arm. parseman recognises that shape automatically and turns it back into a single test."
5652
- });
5653
- let gates;
5654
- if (d.disjoint) gates = "yes";
5655
- else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
5656
- else gates = "no";
5657
- const deferred = gates === "no" && overlaps.length === 0 && anyArms.length > 0 && anyArms.every((a) => a.unresolvedExternal);
5451
+ function sequence5(a, b, c, d, e) {
5452
+ const abcd = sequence4(a, b, c, d);
5658
5453
  return {
5659
- rule,
5660
- strategy: d.strategy?.tag ?? "firstMatch",
5661
- gates,
5662
- deferred,
5663
- combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
5664
- anyArms,
5665
- overlaps
5454
+ match: (input, pos) => {
5455
+ const end = abcd.match(input, pos);
5456
+ return end < 0 ? -1 : e.match(input, end);
5457
+ },
5458
+ run: (input, pos, ctx) => {
5459
+ const end = abcd.run(input, pos, ctx);
5460
+ if (end < 0) return -1;
5461
+ ctx._fc = false;
5462
+ return e.run(input, end, ctx);
5463
+ }
5666
5464
  };
5667
5465
  }
5668
- function formatGatingWarnings(report) {
5669
- const lines = [];
5670
- if (report.unanalysable.length > 0) {
5671
- lines.push(
5672
- `parseman gating: ${report.unanalysable.length} rule(s) UNANALYSABLE \u2014 this report is PARTIAL (${report.totalChoices} choice(s) examined). An empty finding list below does NOT mean the grammar is clean.`
5673
- );
5674
- for (const u of report.unanalysable)
5675
- lines.push(` \xB7 ${u.rule} [${u.kind}]: ${u.reason}`);
5676
- }
5677
- for (const c of report.ungated) {
5678
- lines.push(`parseman gating: choice @ ${c.id} is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms.`);
5679
- for (const a of c.anyArms)
5680
- lines.push(` \xB7 arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
5681
- fix: ${a.suggestion}`);
5682
- for (const o of c.overlaps)
5683
- lines.push(` \xB7 arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
5684
- fix: ${o.suggestion}`);
5685
- lines.push(` (intentional? accept it in the gating snapshot: { accept: ['${c.id}'] }.)`);
5686
- }
5687
- for (const ap of report.antiPatterns)
5688
- lines.push(`parseman anti-pattern [${ap.kind}] @ ${ap.rule} arm[${ap.armIndex}]: ${ap.message}`);
5689
- return lines;
5466
+ function appendExpected(acc, values) {
5467
+ if (values === void 0 || values.length === 0) return acc;
5468
+ if (acc === void 0) return values.slice();
5469
+ acc.push(...values);
5470
+ return acc;
5690
5471
  }
5691
- function ruleDependencies(ruleMap) {
5692
- const nameOf2 = /* @__PURE__ */ new Map();
5693
- for (const [name, comb] of ruleMap) nameOf2.set(comb, name);
5694
- const deps = /* @__PURE__ */ new Map();
5695
- for (const [name, comb] of ruleMap) {
5696
- const found = /* @__PURE__ */ new Set();
5697
- const seen = /* @__PURE__ */ new Set();
5698
- const walk = (p, isRoot) => {
5699
- const def = p._def;
5700
- const boundary = def.tag === "lazy" ? nameOf2.get(p) ?? p._ruleName : nameOf2.get(p);
5701
- if (!isRoot && boundary !== void 0) {
5702
- found.add(boundary);
5703
- return;
5704
- }
5705
- if (seen.has(p)) return;
5706
- seen.add(p);
5707
- if (def.tag === "lazy") {
5708
- let resolved;
5709
- try {
5710
- resolved = def.thunk();
5711
- } catch {
5712
- return;
5713
- }
5714
- walk(resolved, false);
5715
- return;
5472
+ function choiceNode(arms, classes, armExpected, choiceExpected) {
5473
+ let matchTail = () => -1;
5474
+ for (let i = arms.length - 1; i >= 0; i--) {
5475
+ const arm = arms[i], next = matchTail;
5476
+ matchTail = (input, pos) => {
5477
+ const end = arm.match(input, pos);
5478
+ return end < 0 ? next(input, pos) : end;
5479
+ };
5480
+ }
5481
+ let tail = (_input, pos, ctx, _lead, acc) => {
5482
+ ctx._fe = pos;
5483
+ ctx._fx = acc ?? choiceExpected;
5484
+ return -1;
5485
+ };
5486
+ for (let i = arms.length - 1; i >= 0; i--) {
5487
+ const arm = arms[i], cls = classes[i], staticExpected = armExpected[i], next = tail;
5488
+ tail = (input, pos, ctx, lead2, acc, best) => {
5489
+ if (cls !== void 0 && !classHas(cls, lead2)) {
5490
+ return next(
5491
+ input,
5492
+ pos,
5493
+ ctx,
5494
+ lead2,
5495
+ best === pos ? appendExpected(acc, staticExpected) : acc,
5496
+ best
5497
+ );
5716
5498
  }
5717
- for (const child of childrenOf2(def)) walk(child, false);
5499
+ ctx._fc = false;
5500
+ const end = arm.run(input, pos, ctx);
5501
+ if (end >= 0) return end;
5502
+ const at = ctx._fe ?? pos;
5503
+ if (at > best) {
5504
+ best = at;
5505
+ acc = void 0;
5506
+ }
5507
+ const nextAcc = at === best ? appendExpected(acc, ctx._fx) : acc;
5508
+ if (committed(ctx)) {
5509
+ if (nextAcc !== void 0) ctx._fx = nextAcc;
5510
+ return -1;
5511
+ }
5512
+ return next(input, pos, ctx, lead2, nextAcc, best);
5718
5513
  };
5719
- walk(comb, true);
5720
- deps.set(name, [...found]);
5721
5514
  }
5722
- return deps;
5515
+ return {
5516
+ match: matchTail,
5517
+ run: (input, pos, ctx) => tail(
5518
+ input,
5519
+ pos,
5520
+ ctx,
5521
+ pos < input.length ? input.codePointAt(pos) : -1,
5522
+ void 0,
5523
+ pos
5524
+ )
5525
+ };
5723
5526
  }
5724
-
5725
- // src/analysis/commitment.ts
5726
- function mayFail(p, seen = /* @__PURE__ */ new Set()) {
5727
- if (seen.has(p)) return true;
5728
- seen.add(p);
5729
- const d = p._def;
5730
- switch (d.tag) {
5731
- case "literal":
5732
- return d.value.length > 0;
5733
- case "trivia":
5734
- return false;
5735
- // `optional`/`many`/a min-0 `sepBy` swallow an ORDINARY failure — zero
5736
- // repetitions is a match. They do NOT swallow a COMMITTED one: a dispatch
5737
- // that has selected an arm and then failed inside it breaks out past every
5738
- // enclosing repeat, straight to the fallible boundary. So these are total
5739
- // exactly when their body cannot commit. (Measured: treating them as
5740
- // unconditionally total left an extra zero-width leaf in
5741
- // `BareInterpolatedSelector` on benchmark.less — a rollback the legacy
5742
- // build fired and this one had deleted.)
5743
- case "optional":
5744
- case "many":
5745
- return mayCommitFailure(d.parser);
5746
- case "sepBy":
5747
- return d.min >= 1 ? mayFail(d.parser, seen) : mayCommitFailure(d.parser) || mayCommitFailure(d.separator);
5748
- case "oneOrMore":
5749
- return mayFail(d.parser, seen);
5750
- case "sequence":
5751
- return d.parsers.some((x) => mayFail(x, seen));
5752
- // Delegating wrappers: as fallible as what they wrap.
5753
- case "node":
5754
- case "transform":
5755
- case "label":
5756
- case "field":
5757
- case "expect":
5758
- case "withCtx":
5759
- case "grammar":
5760
- case "token":
5761
- case "leaf":
5762
- return mayFail(d.parser, seen);
5763
- case "lazy": {
5764
- try {
5765
- return mayFail(d.thunk(), seen);
5766
- } catch {
5767
- return true;
5768
- }
5527
+ function notNode(body, failure) {
5528
+ return {
5529
+ match: (input, pos) => body.match(input, pos) < 0 ? pos : -1,
5530
+ run: (input, pos, ctx) => {
5531
+ if (body.match(input, pos) < 0) return pos;
5532
+ ctx._fe = pos;
5533
+ ctx._fx = failure;
5534
+ return -1;
5769
5535
  }
5770
- // An adjacency assertion is a TEST — failing is its whole job.
5771
- case "adjacency":
5772
- return true;
5773
- default:
5774
- return true;
5775
- }
5536
+ };
5776
5537
  }
5777
- function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set(), triviaActive = true) {
5778
- if (seen.has(p)) return false;
5779
- seen.add(p);
5780
- const d = p._def;
5781
- switch (d.tag) {
5782
- // Atomic / non-capturing-then-failing: a failure happens before any push.
5783
- case "literal":
5784
- case "regex":
5785
- case "keywords":
5786
- case "guard":
5787
- // Adjacency probes with `_cap = 0` / no `commit()`: it cannot push anything.
5788
- case "adjacency":
5789
- case "not":
5790
- // peek(): emitted under a non-capturing probe ctx and zero-width on both
5791
- // outcomes, so it can never leave a partial capture behind.
5792
- case "peek":
5793
- case "trivia":
5794
- case "token":
5795
- case "leaf":
5796
- case "scanTo":
5797
- case "unknown":
5798
- return false;
5799
- // node() captures into its own private buffers and rolls them back on
5800
- // failure (emitNode restores _ctx.* and never pushes on the !ok path).
5801
- case "node":
5802
- return false;
5803
- // choice/firstMatch already roll back each failed arm; on overall failure
5804
- // nothing committed remains.
5805
- case "choice":
5806
- return false;
5807
- case "dispatch":
5808
- return capturesLeaf(d.selector, new Set(seen)) || d.cases.some((x) => mayLeavePartialCapture(x.parser, new Set(seen), triviaActive) || capturesLeaf(x.parser, new Set(seen))) || (d.matchers ? d.matchers.some((x) => mayLeavePartialCapture(x.parser, new Set(seen), triviaActive) || capturesLeaf(x.parser, new Set(seen))) : false) || (d.otherwise ? mayLeavePartialCapture(d.otherwise, new Set(seen), triviaActive) || capturesLeaf(d.otherwise, new Set(seen)) : false);
5809
- case "attempt":
5810
- return false;
5811
- // optional never fails; many/oneOrMore only "fail" with zero captured items.
5812
- case "optional":
5813
- case "many":
5814
- case "oneOrMore":
5815
- return false;
5816
- // sepBy emits its own per-iteration rollback in emitSepBy.
5817
- case "sepBy":
5818
- return false;
5819
- // A sequence is the real case: an earlier capturing term followed by a term
5820
- // that can fail leaves the earlier captures buffered.
5821
- //
5822
- // BOTH halves of that sentence are load-bearing, and the second half is what
5823
- // this walk exists to exploit. "Some earlier term captures" alone was the old
5824
- // test, and it is true of very nearly every sequence in a real grammar — a
5825
- // sequence almost always opens with a literal or a node. The rollback is only
5826
- // reachable if something AFTER the capture can still fail; once the remainder
5827
- // is total (`optional`/`many`/trivia/a min-0 `sepBy`), the sequence has no
5828
- // failure path left to roll back from, and the save+restore is dead code.
5829
- //
5830
- // `captured` tracks whether anything is in the buffers yet as the scan moves
5831
- // left to right. Two things put it there:
5832
- // - a term that captures (`capturesLeaf`/`hasNodeDef`), and
5833
- // - the inter-term TRIVIA scan, which under `capturing` pushes into
5834
- // `_cstRawChildren`/`_cstTriviaLog` BEFORE the term at each boundary
5835
- // i >= 1. `emitSeqValues` only rewinds that push on the term's SUCCESS
5836
- // path (the "matched empty, so leave the whitespace out of the span"
5837
- // branch); a term that FAILS breaks straight out with the trivia still
5838
- // buffered, so from boundary 1 onward the buffers are dirty regardless of
5839
- // what the earlier terms did.
5840
- // Each term is also asked about itself: a nested sequence can leave its own
5841
- // partial capture behind, and its terms are emitted inline into this same
5842
- // block with no boundary of their own to roll them back.
5843
- case "sequence": {
5844
- const parts = d.parsers;
5845
- let captured = false;
5846
- for (let i = 0; i < parts.length; i++) {
5847
- const part = parts[i];
5848
- if (i > 0 && triviaActive) captured = true;
5849
- if (captured && mayFail(part)) return true;
5850
- if (mayLeavePartialCapture(part, seen, triviaActive)) return true;
5851
- if (capturesLeaf(part) || hasNodeDef(part)) captured = true;
5852
- }
5853
- return false;
5538
+ function optionalNode(body) {
5539
+ return {
5540
+ match: (input, pos) => {
5541
+ const end = body.match(input, pos);
5542
+ return end < 0 ? pos : end;
5543
+ },
5544
+ run: (input, pos, ctx) => {
5545
+ ctx._fc = false;
5546
+ const end = body.run(input, pos, ctx);
5547
+ return end < 0 && !committed(ctx) ? pos : end;
5854
5548
  }
5855
- // Delegating wrappers: defer to the wrapped parser.
5856
- case "transform":
5857
- case "label":
5858
- case "field":
5859
- case "expect":
5860
- case "withCtx":
5861
- case "grammar":
5862
- return mayLeavePartialCapture(d.parser, seen, triviaActive);
5863
- case "recover":
5864
- return mayLeavePartialCapture(d.parser, seen, triviaActive);
5865
- case "lazy": {
5866
- try {
5867
- return mayLeavePartialCapture(d.thunk(), seen, triviaActive);
5868
- } catch {
5869
- return true;
5549
+ };
5550
+ }
5551
+ function fixedTreeRunner(words, matchers, classes, expected) {
5552
+ const stack = [];
5553
+ const pop = () => {
5554
+ const value = stack.pop();
5555
+ if (value === void 0) throw new TypeError("table lexical tree has an incomplete fixed body");
5556
+ return value;
5557
+ };
5558
+ for (let at = 0; at < words.length; ) {
5559
+ const op = words[at++];
5560
+ if (op === LEX_NODE_TERMINAL) {
5561
+ const matcher = matchers[words[at++]], failure = expected[words[at++]];
5562
+ const tracks = words[at++];
5563
+ if (matcher === void 0 || failure === void 0 || tracks !== 0 && tracks !== 1) {
5564
+ throw new TypeError("table lexical tree terminal references an invalid pool entry");
5870
5565
  }
5566
+ stack.push(terminalNode(matcher, failure, tracks === 1));
5567
+ continue;
5871
5568
  }
5872
- // Unknown shapes: be safe and keep the rollback.
5873
- default:
5874
- return true;
5875
- }
5876
- }
5877
- function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
5878
- if (seen.has(p)) return false;
5879
- seen.add(p);
5880
- const d = p._def;
5881
- switch (d.tag) {
5882
- case "literal":
5883
- case "regex":
5884
- case "keywords":
5885
- case "node":
5886
- case "token":
5887
- case "routed":
5888
- case "leaf":
5889
- return true;
5890
- case "not":
5891
- case "peek":
5892
- case "guard":
5893
- case "adjacency":
5894
- case "trivia":
5895
- case "unknown":
5896
- return false;
5897
- case "sequence":
5898
- case "choice":
5899
- return d.parsers.some((x) => capturesLeaf(x, seen));
5900
- case "dispatch":
5901
- return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => capturesLeaf(entry.parser, seen)) : false) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
5902
- case "sepBy":
5903
- return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
5904
- case "many":
5905
- case "oneOrMore":
5906
- case "optional":
5907
- case "attempt":
5908
- case "transform":
5909
- case "label":
5910
- case "field":
5911
- case "expect":
5912
- case "withCtx":
5913
- case "grammar":
5914
- case "recover":
5915
- return capturesLeaf(d.parser, seen);
5916
- case "scanTo":
5917
- return true;
5918
- case "lazy": {
5919
- try {
5920
- return capturesLeaf(d.thunk(), seen);
5921
- } catch {
5922
- return true;
5569
+ if (op >= LEX_NODE_SEQUENCE2 && op <= LEX_NODE_SEQUENCE5) {
5570
+ const count = op - LEX_NODE_SEQUENCE2 + 2;
5571
+ const nodes = Array.from({ length: count }, pop).reverse();
5572
+ if (count === 2) stack.push(sequence2(nodes[0], nodes[1]));
5573
+ else if (count === 3) stack.push(sequence3(nodes[0], nodes[1], nodes[2]));
5574
+ else if (count === 4) stack.push(sequence4(nodes[0], nodes[1], nodes[2], nodes[3]));
5575
+ else stack.push(sequence5(nodes[0], nodes[1], nodes[2], nodes[3], nodes[4]));
5576
+ continue;
5577
+ }
5578
+ if (op >= LEX_NODE_CHOICE2 && op <= LEX_NODE_CHOICE8) {
5579
+ const count = op === LEX_NODE_CHOICE2 ? 2 : op === LEX_NODE_CHOICE4 ? 4 : 8;
5580
+ const choiceFx = expected[words[at++]];
5581
+ if (choiceFx === void 0) throw new TypeError("table lexical tree choice has an invalid expected set");
5582
+ const armClasses = [];
5583
+ const armFx = [];
5584
+ for (let i = 0; i < count; i++) {
5585
+ const cls = words[at++], fx = expected[words[at++]];
5586
+ if (cls < -1 || cls >= 0 && classes[cls] === void 0 || fx === void 0) {
5587
+ throw new TypeError("table lexical tree choice references an invalid pool entry");
5588
+ }
5589
+ armClasses.push(cls < 0 ? void 0 : classes[cls]);
5590
+ armFx.push(fx);
5923
5591
  }
5592
+ const arms = Array.from({ length: count }, pop).reverse();
5593
+ stack.push(choiceNode(arms, armClasses, armFx, choiceFx));
5594
+ continue;
5924
5595
  }
5925
- default:
5926
- return true;
5596
+ if (op === LEX_NODE_NOT) {
5597
+ const failure = expected[words[at++]];
5598
+ if (failure === void 0) throw new TypeError("table lexical tree assertion has an invalid expected set");
5599
+ stack.push(notNode(pop(), failure));
5600
+ continue;
5601
+ }
5602
+ if (op === LEX_NODE_OPTIONAL) {
5603
+ stack.push(optionalNode(pop()));
5604
+ continue;
5605
+ }
5606
+ throw new TypeError("table lexical tree has an unknown fixed body");
5927
5607
  }
5608
+ if (stack.length !== 1) throw new TypeError("table lexical tree does not resolve to one root body");
5609
+ return stack[0].run;
5928
5610
  }
5929
- function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
5930
- if (seen.has(p)) return false;
5931
- seen.add(p);
5932
- const d = p._def;
5933
- switch (d.tag) {
5934
- case "node":
5935
- return true;
5936
- case "lazy": {
5937
- try {
5938
- return hasNodeDef(d.thunk(), seen);
5939
- } catch {
5940
- return false;
5941
- }
5611
+ function buildLexProgramRunner(spec, matchers, classes, expected) {
5612
+ if (spec[0] !== 0 && spec[0] !== 1 && spec[0] !== 2 && spec[0] !== 3 || spec[0] === 0 && spec.length !== 13 || spec[0] === 1 && spec.length !== 12 || spec[0] === 2 && spec.length < 6 || spec[0] === 3 && spec.length !== 3) {
5613
+ throw new TypeError("table lexical program has an invalid fixed body width");
5614
+ }
5615
+ if (spec[1] !== lexProgramDigest(spec.slice(2))) {
5616
+ throw new TypeError("table lexical program semantic digest is inconsistent");
5617
+ }
5618
+ if (spec[0] === 3) {
5619
+ if (!Number.isInteger(spec[2]) || spec[2] < 0) {
5620
+ throw new TypeError("table balanced lexical program has an invalid scan reference");
5942
5621
  }
5943
- case "grammar":
5944
- case "trivia":
5945
- case "token":
5946
- case "leaf":
5947
- case "label":
5948
- case "field":
5949
- case "optional":
5950
- case "many":
5951
- case "oneOrMore":
5952
- case "not":
5953
- case "peek":
5954
- case "transform":
5955
- return hasNodeDef(d.parser, seen);
5956
- case "sequence":
5957
- case "choice":
5958
- return d.parsers.some((x) => hasNodeDef(x, seen));
5959
- case "dispatch":
5960
- return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => hasNodeDef(entry.parser, seen)) : false) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
5961
- case "sepBy":
5962
- return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
5963
- case "scanTo":
5964
- return hasNodeDef(d.sentinel, seen) || d.skip.some((x) => hasNodeDef(x, seen)) || d.recoverAt !== void 0 && hasNodeDef(d.recoverAt, seen) || d.stopAt !== void 0 && hasNodeDef(d.stopAt, seen);
5965
- case "recover":
5966
- return hasNodeDef(d.parser, seen) || hasNodeDef(d.sentinel, seen);
5967
- case "expect":
5968
- return hasNodeDef(d.parser, seen);
5969
- case "withCtx":
5970
- return hasNodeDef(d.parser, seen);
5971
- default:
5972
- return false;
5622
+ const run3 = (_input, _pos, ctx, scan) => {
5623
+ if (scan === void 0) throw new TypeError("table balanced lexical program lost its scan body");
5624
+ const result = scan.parse(_input, _pos, ctx);
5625
+ if (!result.ok) {
5626
+ ctx._fe = result.span.start;
5627
+ ctx._fx = result.expected ?? [];
5628
+ return -1;
5629
+ }
5630
+ return result.span.end;
5631
+ };
5632
+ return Object.assign(run3, { scan: spec[2] });
5973
5633
  }
5974
- }
5975
- function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
5976
- if (seen.has(p)) return false;
5977
- seen.add(p);
5978
- const d = p._def;
5979
- switch (d.tag) {
5980
- case "node":
5981
- return d.build !== void 0 || hasDirectBuildDef(d.parser, seen);
5982
- case "lazy": {
5983
- try {
5984
- return hasDirectBuildDef(d.thunk(), seen);
5985
- } catch {
5986
- return false;
5634
+ if (spec[0] === 2) return fixedTreeRunner(spec.slice(2), matchers, classes, expected);
5635
+ if (spec[0] === 1) {
5636
+ const g00 = matchers[spec[2]], g01 = matchers[spec[3]];
5637
+ const g02 = matchers[spec[4]], g03 = matchers[spec[5]];
5638
+ const guard0Fx = expected[spec[6]];
5639
+ const g1 = matchers[spec[7]], guard1Fx = expected[spec[8]];
5640
+ const terminal = matchers[spec[9]], terminalFx = expected[spec[10]];
5641
+ const tracks2 = spec[11];
5642
+ if (g00 === void 0 || g01 === void 0 || g02 === void 0 || g03 === void 0 || guard0Fx === void 0 || g1 === void 0 || guard1Fx === void 0 || terminal === void 0 || terminalFx === void 0 || tracks2 !== 0 && tracks2 !== 1) {
5643
+ throw new TypeError("table lexical program references an invalid pool entry");
5644
+ }
5645
+ return (input, pos, ctx) => {
5646
+ ctx._fc = false;
5647
+ if (g00(input, pos) >= 0 || g01(input, pos) >= 0 || g02(input, pos) >= 0 || g03(input, pos) >= 0) {
5648
+ ctx._fe = pos;
5649
+ ctx._fx = guard0Fx;
5650
+ return -1;
5651
+ }
5652
+ ctx._fc = false;
5653
+ if (g1(input, pos) >= 0) {
5654
+ ctx._fe = pos;
5655
+ ctx._fx = guard1Fx;
5656
+ return -1;
5987
5657
  }
5658
+ ctx._fc = false;
5659
+ const result = terminal(input, pos);
5660
+ if (result < 0) {
5661
+ ctx._fe = pos;
5662
+ ctx._fx = terminalFx;
5663
+ if (ctx._probe !== void 0) failAt(ctx, ctx._fx, pos);
5664
+ return -1;
5665
+ }
5666
+ const end = result / 2;
5667
+ if (tracks2 === 1) trackLexLines(ctx, input, end);
5668
+ return end;
5669
+ };
5670
+ }
5671
+ const cls = classes[spec[2]];
5672
+ const choiceFx = expected[spec[3]];
5673
+ const m0 = matchers[spec[4]], x0 = expected[spec[5]];
5674
+ const m1 = matchers[spec[6]], x1 = expected[spec[7]];
5675
+ const m2 = matchers[spec[8]], x2 = expected[spec[9]];
5676
+ const m3 = matchers[spec[10]], x3 = expected[spec[11]];
5677
+ const tracks = spec[12];
5678
+ if (cls === void 0 || choiceFx === void 0 || m0 === void 0 || x0 === void 0 || m1 === void 0 || x1 === void 0 || m2 === void 0 || x2 === void 0 || m3 === void 0 || x3 === void 0 || tracks !== 0 && tracks !== 1) {
5679
+ throw new TypeError("table lexical program references an invalid pool entry");
5680
+ }
5681
+ return (input, pos, ctx) => {
5682
+ const lead2 = pos < input.length ? input.codePointAt(pos) : -1;
5683
+ if (!classHas(cls, lead2)) {
5684
+ ctx._fe = pos;
5685
+ ctx._fx = choiceFx.slice();
5686
+ return -1;
5988
5687
  }
5989
- case "grammar":
5990
- case "trivia":
5991
- case "token":
5992
- case "leaf":
5688
+ let acc;
5689
+ const failed = (fx) => {
5690
+ ctx._fe = pos;
5691
+ ctx._fx = fx;
5692
+ if (ctx._probe !== void 0) failAt(ctx, ctx._fx, pos);
5693
+ if (acc === void 0) acc = fx.slice();
5694
+ else acc.push(...fx);
5695
+ };
5696
+ ctx._fc = false;
5697
+ const success = (result2) => {
5698
+ const end = result2 / 2;
5699
+ if (tracks === 1) trackLexLines(ctx, input, end);
5700
+ return end;
5701
+ };
5702
+ let result = m0(input, pos);
5703
+ if (result >= 0) return success(result);
5704
+ failed(x0);
5705
+ ctx._fc = false;
5706
+ result = m1(input, pos);
5707
+ if (result >= 0) return success(result);
5708
+ failed(x1);
5709
+ ctx._fc = false;
5710
+ result = m2(input, pos);
5711
+ if (result >= 0) return success(result);
5712
+ failed(x2);
5713
+ ctx._fc = false;
5714
+ result = m3(input, pos);
5715
+ if (result >= 0) return success(result);
5716
+ failed(x3);
5717
+ ctx._fe = pos;
5718
+ ctx._fx = acc ?? choiceFx;
5719
+ return -1;
5720
+ };
5721
+ }
5722
+
5723
+ // src/table/program.ts
5724
+ function closureArtifact(prog) {
5725
+ return { ...prog, asm: [] };
5726
+ }
5727
+ function covKindCode(kind) {
5728
+ switch (kind) {
5729
+ case "rule":
5730
+ return 0;
5731
+ case "choice-arm":
5732
+ return 1;
5733
+ case "dispatch-arm":
5734
+ return 2;
5993
5735
  case "label":
5994
- case "field":
5995
- case "optional":
5996
- case "many":
5997
- case "oneOrMore":
5998
- case "attempt":
5999
- case "not":
6000
- case "peek":
6001
- case "withCtx":
6002
- case "expect":
6003
- case "transform":
6004
- return hasDirectBuildDef(d.parser, seen);
6005
- case "sequence":
6006
- case "choice":
6007
- return d.parsers.some((x) => hasDirectBuildDef(x, seen));
6008
- case "dispatch":
6009
- return hasDirectBuildDef(d.selector, seen) || d.cases.some((x) => hasDirectBuildDef(x.parser, seen)) || (d.matchers ? d.matchers.some((x) => hasDirectBuildDef(x.parser, seen)) : false) || (d.otherwise ? hasDirectBuildDef(d.otherwise, seen) : false);
6010
- case "sepBy":
6011
- return hasDirectBuildDef(d.parser, seen) || hasDirectBuildDef(d.separator, seen);
6012
- case "scanTo":
6013
- return hasDirectBuildDef(d.sentinel, seen) || d.skip.some((x) => hasDirectBuildDef(x, seen)) || d.recoverAt !== void 0 && hasDirectBuildDef(d.recoverAt, seen) || d.stopAt !== void 0 && hasDirectBuildDef(d.stopAt, seen);
6014
- case "recover":
6015
- return hasDirectBuildDef(d.parser, seen) || hasDirectBuildDef(d.sentinel, seen);
6016
- case "routed":
6017
- return d.fallback ? hasDirectBuildDef(d.fallback, seen) : false;
6018
- default:
6019
- return false;
5736
+ return 3;
6020
5737
  }
6021
5738
  }
6022
- function mayCommitFailure(p, resolve, memo = /* @__PURE__ */ new Map()) {
6023
- return reachesMarked(p, (node2) => commitFailureSuccessors(node2, resolve), memo);
6024
- }
6025
- var NO_EDGES = [];
6026
- function commitFailureSuccessors(p, resolve) {
6027
- const d = p._def;
6028
- switch (d.tag) {
6029
- case "dispatch":
6030
- case "unknown":
6031
- return true;
6032
- case "choice":
6033
- case "sequence":
6034
- return d.parsers;
6035
- // A contained transaction reports every failure uncommitted.
6036
- case "attempt":
6037
- return d.contain === true ? NO_EDGES : [d.parser];
6038
- case "many":
6039
- case "oneOrMore":
6040
- case "optional":
6041
- case "transform":
6042
- case "label":
6043
- case "field":
6044
- case "grammar":
6045
- case "node":
6046
- case "token":
6047
- case "leaf":
6048
- case "withCtx":
6049
- return [d.parser];
6050
- case "sepBy":
6051
- return [d.parser, d.separator];
6052
- case "scanTo":
6053
- return [
6054
- d.sentinel,
6055
- ...d.skip,
6056
- ...d.recoverAt === void 0 ? NO_EDGES : [d.recoverAt],
6057
- ...d.stopAt === void 0 ? NO_EDGES : [d.stopAt]
6058
- ];
6059
- case "lazy": {
6060
- try {
6061
- return [d.thunk()];
6062
- } catch {
6063
- const name = p._ruleName;
6064
- const target = name === void 0 ? void 0 : resolve?.(name);
6065
- return target === void 0 ? true : [target];
6066
- }
6067
- }
6068
- default:
6069
- return NO_EDGES;
5739
+ function covKindName(code) {
5740
+ switch (code) {
5741
+ case 0:
5742
+ return "rule";
5743
+ case 1:
5744
+ return "choice-arm";
5745
+ case 2:
5746
+ return "dispatch-arm";
5747
+ case 3:
5748
+ return "label";
6070
5749
  }
6071
5750
  }
6072
- function reachesMarked(root, successors, memo) {
6073
- const known = memo.get(root);
6074
- if (known !== void 0) return known;
6075
- const order = /* @__PURE__ */ new Map();
6076
- const low = [];
6077
- const found = [];
6078
- const stack = [];
6079
- const visit = (node2) => {
6080
- const at = low.length;
6081
- order.set(node2, at);
6082
- low.push(at);
6083
- found.push(false);
6084
- stack.push(node2);
6085
- const next = successors(node2);
6086
- if (next === true) found[at] = true;
6087
- else {
6088
- for (const child of next) {
6089
- if (found[at]) break;
6090
- const hit = memo.get(child);
6091
- if (hit !== void 0) {
6092
- if (hit) found[at] = true;
6093
- continue;
6094
- }
6095
- const seen = order.get(child);
6096
- if (seen !== void 0) {
6097
- if (seen < low[at]) low[at] = seen;
6098
- continue;
6099
- }
6100
- const k = visit(child);
6101
- if (low[k] < low[at]) low[at] = low[k];
6102
- if (found[k]) found[at] = true;
6103
- }
6104
- }
6105
- if (low[at] === at) {
6106
- const answer = found[at];
6107
- let member;
6108
- do {
6109
- member = stack.pop();
6110
- memo.set(member, answer);
6111
- } while (member !== node2);
6112
- }
6113
- return at;
5751
+ function covDefinitions(prog) {
5752
+ return (prog.cov ?? []).map(([id, kind]) => ({ id, kind: covKindName(kind) }));
5753
+ }
5754
+ function scanRefs(s) {
5755
+ const refs = [];
5756
+ for (const r of [s.sentinel, s.recoverAt, s.stopAt, s.prefix, s.body, s.first, s.lazy]) if (r !== void 0) refs.push(r);
5757
+ refs.push(...s.skip);
5758
+ return refs;
5759
+ }
5760
+ function expandCompact(p) {
5761
+ if ("code" in p) return p;
5762
+ return ownTableProgram({
5763
+ code: p.c,
5764
+ k: p.k,
5765
+ cc: p.x,
5766
+ fx: p.e,
5767
+ disp: p.d,
5768
+ rules: p.r,
5769
+ fns: p.f,
5770
+ lines: p.l ?? 0,
5771
+ dsp: p.p ?? [],
5772
+ ...p.lx === void 0 ? {} : { lex: p.lx },
5773
+ ...p.lp === void 0 ? {} : { lexPrograms: p.lp },
5774
+ ...p.lb === void 0 ? {} : { labels: p.lb },
5775
+ ...p.rc === void 0 ? {} : { classified: p.rc },
5776
+ ...p.h === void 0 ? {} : { hostMode: p.h },
5777
+ ...p.tv === void 0 ? {} : { triviaSpecs: p.tv },
5778
+ ...p.sc === void 0 ? {} : { scans: p.sc },
5779
+ ...p.ss === void 0 ? {} : { scanSkip: p.ss },
5780
+ ...p.so === void 0 ? {} : { scanSkipOf: p.so },
5781
+ ...p.rv === void 0 ? {} : { rec: p.rv },
5782
+ ...p.cv === void 0 ? {} : { cov: p.cv },
5783
+ ...p.a === void 0 ? {} : { asm: p.a }
5784
+ });
5785
+ }
5786
+ function validateDispatchSpec(spec, arity, fallbackRouted) {
5787
+ const invalid = (arms) => {
5788
+ for (const arm of arms) if (!Number.isInteger(arm) || arm < 0 || arm >= arity) return true;
5789
+ return false;
6114
5790
  };
6115
- return found[visit(root)];
5791
+ if (spec === void 0 || !Number.isInteger(arity) || arity < 0 || fallbackRouted !== 0 && fallbackRouted !== 1 || spec.routed.length !== arity || spec.routed.some((value) => value !== 0 && value !== 1) || invalid(spec.byKey.values()) || invalid(spec.byFold.values()) || spec.match.some((matcher) => invalid([matcher[3]]))) {
5792
+ throw new TypeError("table: malformed dispatch");
5793
+ }
6116
5794
  }
6117
- function hasSemanticReduction(roots, externalRefs) {
6118
- const seen = /* @__PURE__ */ new Set();
6119
- const visit = (parser2) => {
6120
- if (seen.has(parser2)) return false;
6121
- seen.add(parser2);
6122
- const def = parser2._def;
6123
- if (def.tag === "transform" && !def.recognitionOnly) return true;
6124
- if (def.tag === "choice" && def.gates.some(Boolean)) return true;
6125
- if (def.tag === "guard" || def.tag === "withCtx") return true;
6126
- if (def.tag === "node" && def.build !== void 0) return true;
6127
- if (def.tag === "lazy") {
6128
- if (externalRefs?.has(parser2)) return false;
6129
- try {
6130
- return visit(def.thunk());
6131
- } catch {
6132
- return true;
6133
- }
5795
+ var TABLE_RUNTIME = /* @__PURE__ */ Symbol("parseman.tableRuntime");
5796
+ function ownTableProgram(prog, resolved, caches = {}) {
5797
+ const runtime = prog[TABLE_RUNTIME];
5798
+ return {
5799
+ ...prog,
5800
+ [TABLE_RUNTIME]: {
5801
+ resolved,
5802
+ choiceRollbackMasks: caches.choiceRollbackMasks ?? runtime?.choiceRollbackMasks,
5803
+ nonCommittingChoiceSites: caches.nonCommittingChoiceSites ?? runtime?.nonCommittingChoiceSites,
5804
+ failureRollbackCleanSites: caches.failureRollbackCleanSites ?? runtime?.failureRollbackCleanSites
6134
5805
  }
6135
- return childrenOf2(def).some(visit);
6136
5806
  };
6137
- return roots.some(visit);
6138
5807
  }
6139
- function classifyRuleMap(ruleMap) {
6140
- const externalRefs = /* @__PURE__ */ new Set();
6141
- const scanned = /* @__PURE__ */ new Set();
6142
- const scanExternal = (p) => {
6143
- if (scanned.has(p)) return;
6144
- scanned.add(p);
6145
- const def = p._def;
6146
- if (def.tag === "lazy") {
6147
- let resolved;
6148
- try {
6149
- resolved = def.thunk();
6150
- } catch {
6151
- resolved = void 0;
6152
- }
6153
- if (resolved === void 0) {
6154
- if (p._ruleName) externalRefs.add(p);
6155
- return;
6156
- }
6157
- scanExternal(resolved);
6158
- return;
5808
+ function choiceRollbackMask(prog, ip) {
5809
+ return prog[TABLE_RUNTIME]?.choiceRollbackMasks?.get(ip);
5810
+ }
5811
+ function choiceCannotCommit(prog, ip) {
5812
+ return prog[TABLE_RUNTIME]?.nonCommittingChoiceSites?.has(ip) === true;
5813
+ }
5814
+ function failureRollbackClean(prog, ip) {
5815
+ return prog[TABLE_RUNTIME]?.failureRollbackCleanSites?.has(ip) === true;
5816
+ }
5817
+ var WIDE_CLASS_PREFIX_LO = 65535;
5818
+ var WIDE_CLASS_PREFIX_HI = 0;
5819
+ function validateClassRange(lo, hi) {
5820
+ for (const cp of [lo, hi]) {
5821
+ if (!Number.isInteger(cp) || cp < 0 || cp > 1114111) {
5822
+ throw new TypeError(`table char-class endpoint is outside Unicode: ${cp}`);
6159
5823
  }
6160
- for (const child of childrenOf2(def)) scanExternal(child);
6161
- };
6162
- for (const [, rule] of ruleMap) scanExternal(rule);
6163
- return {
6164
- hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
6165
- isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule), externalRefs)
6166
- };
5824
+ }
5825
+ if (lo > hi) {
5826
+ throw new TypeError(`table char-class range is descending: ${lo} > ${hi}`);
5827
+ }
6167
5828
  }
6168
-
6169
- // src/table/ops.ts
6170
- var OP_LIT = 1;
6171
- var OP_RX = 2;
6172
- var OP_SEQ = 3;
6173
- var OP_SEQV = 4;
6174
- var OP_CHOICE = 5;
6175
- var OP_REP = 6;
6176
- var OP_REPV = 7;
6177
- var OP_OPT = 8;
6178
- var OP_XFORM = 9;
6179
- var OP_NODE = 10;
6180
- var OP_RULE = 11;
6181
- var OP_GATE = 12;
6182
- var OP_NOT = 13;
6183
- var OP_PEEK = 14;
6184
- var OP_LEAF = 15;
6185
- var OP_EMPTY = 16;
6186
- var OP_LIT_TRACK = 17;
6187
- var OP_RX_TRACK = 18;
6188
- var OP_NODE_TRACK = 19;
6189
- var OP_SCOPE = 20;
6190
- var OP_EXPECT = 21;
6191
- var OP_SEQX = 22;
6192
- var OP_SCAN = 23;
6193
- var OP_FIELD = 24;
6194
- var OP_LIT_CI = 27;
6195
- var OP_LIT_CI_TRACK = 28;
6196
- var OP_TOKEN = 29;
6197
- var OP_SCOPE_CAP = 30;
6198
- var OP_WITHCTX = 31;
6199
- var OP_GUARD = 32;
6200
- var OP_ADJ = 33;
6201
- var OP_GREEDY = 34;
6202
- var OP_REJECT = 35;
6203
- var OP_ARMGATE = 36;
6204
- var OP_LIVE = 37;
6205
- var OP_ATTEMPT = 38;
6206
- var OP_LABEL = 39;
6207
- var OP_COV = 40;
6208
- var OP_SCOPE_PLAIN = 41;
6209
- var OP_LEX_BODY = 42;
6210
- var OP_LEX_PROGRAM = 43;
6211
- var OP_SOURCE_LEAF = 44;
6212
- var OP_DISPATCH = 25;
6213
- var OP_ROUTED = 26;
6214
- var OP_NAMES = {
6215
- [OP_LIT]: "LIT",
6216
- [OP_RX]: "RX",
6217
- [OP_SEQ]: "SEQ",
6218
- [OP_SEQV]: "SEQV",
6219
- [OP_CHOICE]: "CHOICE",
6220
- [OP_REP]: "REP",
6221
- [OP_REPV]: "REPV",
6222
- [OP_OPT]: "OPT",
6223
- [OP_XFORM]: "XFORM",
6224
- [OP_NODE]: "NODE",
6225
- [OP_RULE]: "RULE",
6226
- [OP_GATE]: "GATE",
6227
- [OP_NOT]: "NOT",
6228
- [OP_PEEK]: "PEEK",
6229
- [OP_LEAF]: "LEAF",
6230
- [OP_SOURCE_LEAF]: "SOURCE_LEAF",
6231
- [OP_EMPTY]: "EMPTY",
6232
- [OP_LIT_TRACK]: "LIT_TRACK",
6233
- [OP_RX_TRACK]: "RX_TRACK",
6234
- [OP_NODE_TRACK]: "NODE_TRACK",
6235
- [OP_SCOPE]: "SCOPE",
6236
- [OP_EXPECT]: "EXPECT",
6237
- [OP_SEQX]: "SEQX",
6238
- [OP_SCAN]: "SCAN",
6239
- [OP_FIELD]: "FIELD",
6240
- [OP_LIT_CI]: "LIT_CI",
6241
- [OP_LIT_CI_TRACK]: "LIT_CI_TRACK",
6242
- [OP_DISPATCH]: "DISPATCH",
6243
- [OP_ROUTED]: "ROUTED",
6244
- [OP_TOKEN]: "TOKEN",
6245
- [OP_SCOPE_CAP]: "SCOPE_CAP",
6246
- [OP_WITHCTX]: "WITHCTX",
6247
- [OP_GUARD]: "GUARD",
6248
- [OP_ADJ]: "ADJ",
6249
- [OP_GREEDY]: "GREEDY",
6250
- [OP_REJECT]: "REJECT",
6251
- [OP_ARMGATE]: "ARMGATE",
6252
- [OP_LIVE]: "LIVE",
6253
- [OP_ATTEMPT]: "ATTEMPT",
6254
- [OP_LABEL]: "LABEL",
6255
- [OP_COV]: "COV",
6256
- [OP_SCOPE_PLAIN]: "SCOPE_PLAIN",
6257
- [OP_LEX_BODY]: "LEX_BODY",
6258
- [OP_LEX_PROGRAM]: "LEX_PROGRAM"
6259
- };
6260
-
6261
- // src/table/lex-program.ts
6262
- var LEX_NODE_TERMINAL = 0;
6263
- var LEX_NODE_SEQUENCE2 = 1;
6264
- var LEX_NODE_SEQUENCE3 = 2;
6265
- var LEX_NODE_SEQUENCE4 = 3;
6266
- var LEX_NODE_SEQUENCE5 = 4;
6267
- var LEX_NODE_CHOICE2 = 5;
6268
- var LEX_NODE_CHOICE4 = 6;
6269
- var LEX_NODE_CHOICE8 = 7;
6270
- var LEX_NODE_NOT = 8;
6271
- var LEX_NODE_OPTIONAL = 9;
6272
- function lexProgramDigest(words) {
6273
- let hash = 2166136261;
6274
- for (const word2 of words) {
6275
- hash ^= word2 | 0;
6276
- hash = Math.imul(hash, 16777619);
5829
+ function encodeClassSpec(ranges) {
5830
+ let wide = false;
5831
+ for (const range of ranges) {
5832
+ validateClassRange(range.lo, range.hi);
5833
+ if (range.hi > 65535) wide = true;
6277
5834
  }
6278
- return hash | 0;
6279
- }
6280
- function classHas(cls, code) {
6281
- if (code < 0) return false;
6282
- if (code < 128) return cls.ascii[code] === 1;
6283
- for (let i = 0; i < cls.hi.length; i += 2) {
6284
- if (code >= cls.hi[i] && code <= cls.hi[i + 1]) return true;
5835
+ if (!wide) {
5836
+ let spec2 = "";
5837
+ for (const range of ranges) spec2 += String.fromCharCode(range.lo, range.hi);
5838
+ return spec2;
6285
5839
  }
6286
- return false;
6287
- }
6288
- var committed = (ctx) => ctx._fc === true;
6289
- function trackLexLines(ctx, input, end) {
6290
- let at = ctx._lineScannedTo ?? 0;
6291
- const starts = ctx._lineStarts;
6292
- if (starts === void 0 || end <= at) return;
6293
- while (at < end) {
6294
- if (input.charCodeAt(at) === 10) starts.push(at + 1);
6295
- at++;
5840
+ let spec = String.fromCharCode(WIDE_CLASS_PREFIX_LO, WIDE_CLASS_PREFIX_HI);
5841
+ for (const range of ranges) {
5842
+ spec += String.fromCharCode(
5843
+ range.lo >>> 16,
5844
+ range.lo & 65535,
5845
+ range.hi >>> 16,
5846
+ range.hi & 65535
5847
+ );
6296
5848
  }
6297
- ctx._lineScannedTo = end;
5849
+ return spec;
6298
5850
  }
6299
- function terminalNode(matcher, failure, tracksLines) {
6300
- const match = (input, pos) => {
6301
- const result = matcher(input, pos);
6302
- return result < 0 ? -1 : result / 2;
6303
- };
6304
- return {
6305
- match,
6306
- run: (input, pos, ctx) => {
6307
- const end = match(input, pos);
6308
- if (end < 0) {
6309
- ctx._fe = pos;
6310
- ctx._fx = failure;
6311
- if (ctx._probe !== void 0) failAt(ctx, ctx._fx, pos);
6312
- return -1;
6313
- }
6314
- if (tracksLines) trackLexLines(ctx, input, end);
6315
- return end;
5851
+ function decodeClassSpec(spec) {
5852
+ if (spec.charCodeAt(0) === WIDE_CLASS_PREFIX_LO && spec.charCodeAt(1) === WIDE_CLASS_PREFIX_HI) {
5853
+ if ((spec.length - 2) % 4 !== 0) throw new TypeError("table char-class spec has a truncated wide range");
5854
+ if (spec.length === 2) throw new TypeError("table char-class spec has a wide prefix but no ranges");
5855
+ const ranges2 = [];
5856
+ for (let i = 2; i < spec.length; i += 4) {
5857
+ const lo = spec.charCodeAt(i) * 65536 + spec.charCodeAt(i + 1);
5858
+ const hi = spec.charCodeAt(i + 2) * 65536 + spec.charCodeAt(i + 3);
5859
+ validateClassRange(lo, hi);
5860
+ ranges2.push({ lo, hi });
6316
5861
  }
6317
- };
5862
+ return ranges2;
5863
+ }
5864
+ if (spec.length % 2 !== 0) throw new TypeError("table char-class spec has an unmatched range endpoint");
5865
+ const ranges = [];
5866
+ for (let i = 0; i < spec.length; i += 2) {
5867
+ const lo = spec.charCodeAt(i);
5868
+ const hi = spec.charCodeAt(i + 1);
5869
+ validateClassRange(lo, hi);
5870
+ ranges.push({ lo, hi });
5871
+ }
5872
+ return ranges;
6318
5873
  }
6319
- function sequence2(a, b) {
6320
- return {
6321
- match: (input, pos) => {
6322
- const e0 = a.match(input, pos);
6323
- return e0 < 0 ? -1 : b.match(input, e0);
6324
- },
6325
- run: (input, pos, ctx) => {
6326
- const e0 = a.run(input, pos, ctx);
6327
- if (e0 < 0) return -1;
6328
- ctx._fc = false;
6329
- return b.run(input, e0, ctx);
6330
- }
6331
- };
5874
+ function resolveClass(spec) {
5875
+ const ascii = new Uint8Array(128);
5876
+ const hi = [];
5877
+ for (const { lo, hi: up } of decodeClassSpec(spec)) {
5878
+ if (lo < 128) for (let c = lo; c <= Math.min(up, 127); c++) ascii[c] = 1;
5879
+ if (up >= 128) hi.push(Math.max(lo, 128), up);
5880
+ }
5881
+ return { ascii, hi };
6332
5882
  }
6333
- function sequence3(a, b, c) {
6334
- return {
6335
- match: (input, pos) => {
6336
- const e0 = a.match(input, pos);
6337
- if (e0 < 0) return -1;
6338
- const e1 = b.match(input, e0);
6339
- return e1 < 0 ? -1 : c.match(input, e1);
6340
- },
6341
- run: (input, pos, ctx) => {
6342
- const e0 = a.run(input, pos, ctx);
6343
- if (e0 < 0) return -1;
6344
- ctx._fc = false;
6345
- const e1 = b.run(input, e0, ctx);
6346
- if (e1 < 0) return -1;
6347
- ctx._fc = false;
6348
- return c.run(input, e1, ctx);
6349
- }
6350
- };
5883
+ function classHas2(cls, code) {
5884
+ if (code < 0) return false;
5885
+ if (code < 128) return cls.ascii[code] === 1;
5886
+ const hi = cls.hi;
5887
+ for (let i = 0; i < hi.length; i += 2) if (code >= hi[i] && code <= hi[i + 1]) return true;
5888
+ return false;
6351
5889
  }
6352
- function sequence4(a, b, c, d) {
6353
- const abc = sequence3(a, b, c);
6354
- return {
6355
- match: (input, pos) => {
6356
- const end = abc.match(input, pos);
6357
- return end < 0 ? -1 : d.match(input, end);
6358
- },
6359
- run: (input, pos, ctx) => {
6360
- const end = abc.run(input, pos, ctx);
6361
- if (end < 0) return -1;
6362
- ctx._fc = false;
6363
- return d.run(input, end, ctx);
5890
+ function resolveDispatch(arms, cc) {
5891
+ const ascii = new Uint8Array(128);
5892
+ const hi = [];
5893
+ const open = [];
5894
+ const armCls = [];
5895
+ let exclusive = true;
5896
+ let hiOwners = 0;
5897
+ for (let a = 0; a < arms.length; a++) {
5898
+ const ci = arms[a];
5899
+ if (ci < 0) {
5900
+ open.push(a);
5901
+ armCls.push(null);
5902
+ exclusive = false;
5903
+ continue;
6364
5904
  }
6365
- };
5905
+ const cls = cc[ci];
5906
+ armCls.push(cls);
5907
+ for (let c = 0; c < 128; c++) {
5908
+ if (cls.ascii[c] !== 1) continue;
5909
+ if (ascii[c] === 0) ascii[c] = a + 1;
5910
+ else exclusive = false;
5911
+ }
5912
+ if (cls.hi.length > 0 && ++hiOwners > 1) exclusive = false;
5913
+ for (let i = 0; i < cls.hi.length; i += 2) hi.push(cls.hi[i], cls.hi[i + 1], a);
5914
+ }
5915
+ return { ascii, hi, open, armCls, exclusive };
6366
5916
  }
6367
- function sequence5(a, b, c, d, e) {
6368
- const abcd = sequence4(a, b, c, d);
6369
- return {
6370
- match: (input, pos) => {
6371
- const end = abcd.match(input, pos);
6372
- return end < 0 ? -1 : e.match(input, end);
6373
- },
6374
- run: (input, pos, ctx) => {
6375
- const end = abcd.run(input, pos, ctx);
6376
- if (end < 0) return -1;
6377
- ctx._fc = false;
6378
- return e.run(input, end, ctx);
6379
- }
6380
- };
6381
- }
6382
- function appendExpected(acc, values) {
6383
- if (values === void 0 || values.length === 0) return acc;
6384
- if (acc === void 0) return values.slice();
6385
- acc.push(...values);
6386
- return acc;
6387
- }
6388
- function choiceNode(arms, classes, armExpected, choiceExpected) {
6389
- let matchTail = () => -1;
6390
- for (let i = arms.length - 1; i >= 0; i--) {
6391
- const arm = arms[i], next = matchTail;
6392
- matchTail = (input, pos) => {
6393
- const end = arm.match(input, pos);
6394
- return end < 0 ? next(input, pos) : end;
6395
- };
6396
- }
6397
- let tail = (_input, pos, ctx, _lead, acc) => {
6398
- ctx._fe = pos;
6399
- ctx._fx = acc ?? choiceExpected;
6400
- return -1;
6401
- };
6402
- for (let i = arms.length - 1; i >= 0; i--) {
6403
- const arm = arms[i], cls = classes[i], staticExpected = armExpected[i], next = tail;
6404
- tail = (input, pos, ctx, lead2, acc, best) => {
6405
- if (cls !== void 0 && !classHas(cls, lead2)) {
6406
- return next(
6407
- input,
6408
- pos,
6409
- ctx,
6410
- lead2,
6411
- best === pos ? appendExpected(acc, staticExpected) : acc,
6412
- best
6413
- );
6414
- }
6415
- ctx._fc = false;
6416
- const end = arm.run(input, pos, ctx);
6417
- if (end >= 0) return end;
6418
- const at = ctx._fe ?? pos;
6419
- if (at > best) {
6420
- best = at;
6421
- acc = void 0;
6422
- }
6423
- const nextAcc = at === best ? appendExpected(acc, ctx._fx) : acc;
6424
- if (committed(ctx)) {
6425
- if (nextAcc !== void 0) ctx._fx = nextAcc;
6426
- return -1;
6427
- }
6428
- return next(input, pos, ctx, lead2, nextAcc, best);
6429
- };
5917
+ function buildTrivia(spec) {
5918
+ if (spec.live !== void 0) return spec.live;
5919
+ if (spec.plain !== void 0) return trivia(regex(new RegExp(spec.plain[0], spec.plain[1])));
5920
+ if (spec.alts !== void 0) {
5921
+ const [head, ...rest] = spec.alts.map(([source, flags]) => regex(new RegExp(source, flags)));
5922
+ if (head === void 0) throw new TypeError("trivia spec has an EMPTY alternation, which matches nothing");
5923
+ const body = rest.length === 0 ? head : choice(head, ...rest);
5924
+ return trivia(many(body, { min: spec.min ?? 1 }));
6430
5925
  }
6431
- return {
6432
- match: matchTail,
6433
- run: (input, pos, ctx) => tail(
6434
- input,
6435
- pos,
6436
- ctx,
6437
- pos < input.length ? input.codePointAt(pos) : -1,
6438
- void 0,
6439
- pos
6440
- )
6441
- };
5926
+ const arms = {};
5927
+ for (const [name, source, flags] of spec.arms) arms[name] = regex(new RegExp(source, flags));
5928
+ return classifiedTrivia(arms);
6442
5929
  }
6443
- function notNode(body, failure) {
6444
- return {
6445
- match: (input, pos) => body.match(input, pos) < 0 ? pos : -1,
6446
- run: (input, pos, ctx) => {
6447
- if (body.match(input, pos) < 0) return pos;
6448
- ctx._fe = pos;
6449
- ctx._fx = failure;
6450
- return -1;
6451
- }
5930
+ function resolveTable(prog) {
5931
+ const owner = prog[TABLE_RUNTIME];
5932
+ const hit = owner?.resolved;
5933
+ if (hit !== void 0) return hit;
5934
+ const cc = prog.cc.map(resolveClass);
5935
+ const triviaBuilt = (prog.triviaSpecs ?? []).map(buildTrivia);
5936
+ const lex = (prog.lex ?? []).map((spec) => buildLexBodyRecognizer(spec, prog.k));
5937
+ const built = {
5938
+ prog,
5939
+ code: Int32Array.from(prog.code),
5940
+ k: prog.k,
5941
+ fns: prog.fns,
5942
+ cc,
5943
+ fx: prog.fx,
5944
+ disp: prog.disp.map((d) => resolveDispatch(d, cc)),
5945
+ trivia: triviaBuilt,
5946
+ triviaScan: triviaBuilt.map(fastTriviaScanner),
5947
+ triviaLabelled: triviaBuilt.map((t) => t._meta.triviaKindLabels !== void 0),
5948
+ dsp: prog.dsp.map((d) => ({
5949
+ byKey: new Map(d.key.map((x, i) => [x, d.keyArm[i]])),
5950
+ byFold: new Map(d.fold.map((x, i) => [x, d.foldArm[i]])),
5951
+ match: d.match,
5952
+ routed: d.routed,
5953
+ expected: d.expected
5954
+ })),
5955
+ lex,
5956
+ lexPrograms: (prog.lexPrograms ?? []).map((spec) => buildLexProgramRunner(spec, lex, cc, prog.fx)),
5957
+ rules: prog.rules
6452
5958
  };
5959
+ if (owner !== void 0) owner.resolved = built;
5960
+ return built;
6453
5961
  }
6454
- function optionalNode(body) {
6455
- return {
6456
- match: (input, pos) => {
6457
- const end = body.match(input, pos);
6458
- return end < 0 ? pos : end;
6459
- },
6460
- run: (input, pos, ctx) => {
6461
- ctx._fc = false;
6462
- const end = body.run(input, pos, ctx);
6463
- return end < 0 && !committed(ctx) ? pos : end;
5962
+
5963
+ // src/analysis/gating.ts
5964
+ function childrenOf2(d) {
5965
+ switch (d.tag) {
5966
+ case "sequence":
5967
+ case "choice":
5968
+ return d.parsers;
5969
+ case "dispatch":
5970
+ return [
5971
+ d.selector,
5972
+ ...d.cases.map((c) => c.parser),
5973
+ ...d.matchers ? d.matchers.map((c) => c.parser) : [],
5974
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
5975
+ ];
5976
+ case "sepBy":
5977
+ return [d.parser, d.separator];
5978
+ case "recover":
5979
+ return [d.parser, d.sentinel];
5980
+ case "scanTo":
5981
+ return [
5982
+ d.sentinel,
5983
+ ...d.skip,
5984
+ ...d.recoverAt === void 0 ? [] : [d.recoverAt],
5985
+ ...d.stopAt === void 0 ? [] : [d.stopAt]
5986
+ ];
5987
+ case "grammar":
5988
+ return d.triviaParser ? [d.parser, d.triviaParser] : [d.parser];
5989
+ case "routed":
5990
+ return d.fallback ? [d.fallback] : [];
5991
+ case "lines":
5992
+ return [d.prefix, d.body, ...d.first === void 0 ? [] : [d.first], ...d.lazy === void 0 ? [] : [d.lazy]];
5993
+ case "columns":
5994
+ return [];
5995
+ // A `lazy` is a REFERENCE, not a subtree. Descending through it would make
5996
+ // every rule's walk cover the whole reachable grammar — site paths become
5997
+ // nonsense, and a structural hash includes half the grammar. Treated as a
5998
+ // leaf, keyed by the rule name it refers to, which is also the right
5999
+ // semantics: two productions referencing `g.Ident` really do fill that slot
6000
+ // the same way.
6001
+ case "lazy":
6002
+ case "literal":
6003
+ case "regex":
6004
+ case "keywords":
6005
+ case "guard":
6006
+ case "adjacency":
6007
+ case "unknown":
6008
+ return [];
6009
+ default: {
6010
+ const rec = d;
6011
+ return rec.parser ? [rec.parser] : [];
6464
6012
  }
6465
- };
6013
+ }
6466
6014
  }
6467
- function fixedTreeRunner(words, matchers, classes, expected) {
6468
- const stack = [];
6469
- const pop = () => {
6470
- const value = stack.pop();
6471
- if (value === void 0) throw new TypeError("table lexical tree has an incomplete fixed body");
6472
- return value;
6473
- };
6474
- for (let at = 0; at < words.length; ) {
6475
- const op = words[at++];
6476
- if (op === LEX_NODE_TERMINAL) {
6477
- const matcher = matchers[words[at++]], failure = expected[words[at++]];
6478
- const tracks = words[at++];
6479
- if (matcher === void 0 || failure === void 0 || tracks !== 0 && tracks !== 1) {
6480
- throw new TypeError("table lexical tree terminal references an invalid pool entry");
6481
- }
6482
- stack.push(terminalNode(matcher, failure, tracks === 1));
6483
- continue;
6484
- }
6485
- if (op >= LEX_NODE_SEQUENCE2 && op <= LEX_NODE_SEQUENCE5) {
6486
- const count = op - LEX_NODE_SEQUENCE2 + 2;
6487
- const nodes = Array.from({ length: count }, pop).reverse();
6488
- if (count === 2) stack.push(sequence2(nodes[0], nodes[1]));
6489
- else if (count === 3) stack.push(sequence3(nodes[0], nodes[1], nodes[2]));
6490
- else if (count === 4) stack.push(sequence4(nodes[0], nodes[1], nodes[2], nodes[3]));
6491
- else stack.push(sequence5(nodes[0], nodes[1], nodes[2], nodes[3], nodes[4]));
6492
- continue;
6493
- }
6494
- if (op >= LEX_NODE_CHOICE2 && op <= LEX_NODE_CHOICE8) {
6495
- const count = op === LEX_NODE_CHOICE2 ? 2 : op === LEX_NODE_CHOICE4 ? 4 : 8;
6496
- const choiceFx = expected[words[at++]];
6497
- if (choiceFx === void 0) throw new TypeError("table lexical tree choice has an invalid expected set");
6498
- const armClasses = [];
6499
- const armFx = [];
6500
- for (let i = 0; i < count; i++) {
6501
- const cls = words[at++], fx = expected[words[at++]];
6502
- if (cls < -1 || cls >= 0 && classes[cls] === void 0 || fx === void 0) {
6503
- throw new TypeError("table lexical tree choice references an invalid pool entry");
6504
- }
6505
- armClasses.push(cls < 0 ? void 0 : classes[cls]);
6506
- armFx.push(fx);
6507
- }
6508
- const arms = Array.from({ length: count }, pop).reverse();
6509
- stack.push(choiceNode(arms, armClasses, armFx, choiceFx));
6510
- continue;
6511
- }
6512
- if (op === LEX_NODE_NOT) {
6513
- const failure = expected[words[at++]];
6514
- if (failure === void 0) throw new TypeError("table lexical tree assertion has an invalid expected set");
6515
- stack.push(notNode(pop(), failure));
6516
- continue;
6517
- }
6518
- if (op === LEX_NODE_OPTIONAL) {
6519
- stack.push(optionalNode(pop()));
6520
- continue;
6521
- }
6522
- throw new TypeError("table lexical tree has an unknown fixed body");
6015
+ var isAny = (fs) => fs.kind === "any";
6016
+ function intersection(a, b) {
6017
+ if (a.kind === "any") return b;
6018
+ if (b.kind === "any") return a;
6019
+ if (a.kind === "empty" || b.kind === "empty") return { kind: "empty" };
6020
+ const ranges = [];
6021
+ for (const ra of a.ranges) for (const rb of b.ranges) {
6022
+ const lo = Math.max(ra.lo, rb.lo);
6023
+ const hi = Math.min(ra.hi, rb.hi);
6024
+ if (lo <= hi) ranges.push({ lo, hi });
6523
6025
  }
6524
- if (stack.length !== 1) throw new TypeError("table lexical tree does not resolve to one root body");
6525
- return stack[0].run;
6026
+ return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
6526
6027
  }
6527
- function buildLexProgramRunner(spec, matchers, classes, expected) {
6528
- if (spec[0] !== 0 && spec[0] !== 1 && spec[0] !== 2 && spec[0] !== 3 || spec[0] === 0 && spec.length !== 13 || spec[0] === 1 && spec.length !== 12 || spec[0] === 2 && spec.length < 6 || spec[0] === 3 && spec.length !== 3) {
6529
- throw new TypeError("table lexical program has an invalid fixed body width");
6028
+ function combine(sets) {
6029
+ let acc = { kind: "empty" };
6030
+ for (const s of sets) {
6031
+ if (s.kind === "any") return { kind: "any" };
6032
+ if (s.kind === "empty") continue;
6033
+ acc = acc.kind === "ranges" ? { kind: "ranges", ranges: [...acc.ranges, ...s.ranges] } : s;
6530
6034
  }
6531
- if (spec[1] !== lexProgramDigest(spec.slice(2))) {
6532
- throw new TypeError("table lexical program semantic digest is inconsistent");
6035
+ return acc;
6036
+ }
6037
+ var ch = (c) => c >= 32 && c < 127 ? `'${String.fromCharCode(c)}'` : `\\u${c}`;
6038
+ function firstSetToString(fs) {
6039
+ if (fs.kind === "any") return "ANY";
6040
+ if (fs.kind === "empty") return "(empty)";
6041
+ return fs.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
6042
+ }
6043
+ var ruleNameOf2 = (p) => p._ruleName;
6044
+ var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
6045
+ var SUGGESTIONS = {
6046
+ "broad-recognizer": "This arm can begin with any character, so no single-character test can rule it out. The parser has to enter it \u2014 set up, try, and undo \u2014 at every position it reaches, instead of skipping it for free.\nTo fix: make the arm begin with a fixed character, word or keyword. If it is meant to be a catch-all that matches anything, leave it and add the choice to the accept list at the end.",
6047
+ "leading-not": "This arm begins with a not(...) check, which matches nothing and so says nothing about which character the arm starts with. The parser then cannot rule the arm out and enters it everywhere.\nTo fix: put the term that actually consumes text first and keep not(...) after it as a trailing boundary. If you need to REQUIRE something ahead without consuming it, use peek(X) \u2014 it checks the same thing but still tells parseman that the arm starts with X.",
6048
+ "nullable-prefix": "This arm begins with something optional \u2014 an optional(...) or a repeat that allows zero items \u2014 so the arm can also start with whatever comes AFTER it, and that is broad enough that no character rules the arm out.\nTo fix: give the optional part its own arm so each arm has a definite beginning, or require at least one item. A plain sepBy(item, sep) also matches nothing at all; pass { min: 1 } when the list must not be empty.",
6049
+ "cross-artifact-ref": "This arm hands off to another rule, and that rule has the same problem \u2014 it can begin with any character \u2014 so the cost is inherited rather than caused here.\nTo fix: run this check on the rule it refers to and fix it there. One rule given a definite beginning fixes every choice that uses it.",
6050
+ "opaque-wrapper": "This arm begins with a wrapper that only inspects parser state (a gate/withCtx/recover) and consumes no text, so it says nothing about which character the arm starts with.\nTo fix: put a term that actually matches text in front of the wrapper.",
6051
+ "ref-cycle": "This arm refers back to itself through a cycle of rules, and parseman could not work out a definite first character for any of them.\nTo fix: make sure the base case of the recursion begins with a fixed character or keyword, so the cycle has somewhere definite to start."
6052
+ };
6053
+ function refineSuggestion(cause, arm) {
6054
+ if (cause !== "broad-recognizer") return SUGGESTIONS[cause];
6055
+ const lead2 = peelToLeading(arm);
6056
+ const d = lead2._def;
6057
+ if (d.tag === "regex" && KEYWORD_REGEX_RE.test(d.source)) {
6058
+ return `This arm matches the fixed word \`${d.source}\` with a regular expression, and parseman cannot always tell from a regular expression which character it starts with.
6059
+ To fix: write it as word('\u2026') or keywords([\u2026]) instead. They match exactly the same text and compile to the same character scan, but they also tell parseman the first character, which is what lets the parser skip this arm when it cannot match.`;
6533
6060
  }
6534
- if (spec[0] === 3) {
6535
- if (!Number.isInteger(spec[2]) || spec[2] < 0) {
6536
- throw new TypeError("table balanced lexical program has an invalid scan reference");
6537
- }
6538
- const run3 = (_input, _pos, ctx, scan) => {
6539
- if (scan === void 0) throw new TypeError("table balanced lexical program lost its scan body");
6540
- const result = scan.parse(_input, _pos, ctx);
6541
- if (!result.ok) {
6542
- ctx._fe = result.span.start;
6543
- ctx._fx = result.expected ?? [];
6544
- return -1;
6545
- }
6546
- return result.span.end;
6547
- };
6548
- return Object.assign(run3, { scan: spec[2] });
6061
+ if (d.tag === "scanTo") {
6062
+ return "This arm ends in a scanTo(...) catch-all, which reads forward until it finds something and so can begin at any character. The parser can never rule it out. That is usually exactly what you want from a fallback.\nTo fix: usually nothing. Add this choice to the accept list at the end so the check keeps flagging the choices that are real problems.";
6549
6063
  }
6550
- if (spec[0] === 2) return fixedTreeRunner(spec.slice(2), matchers, classes, expected);
6551
- if (spec[0] === 1) {
6552
- const g00 = matchers[spec[2]], g01 = matchers[spec[3]];
6553
- const g02 = matchers[spec[4]], g03 = matchers[spec[5]];
6554
- const guard0Fx = expected[spec[6]];
6555
- const g1 = matchers[spec[7]], guard1Fx = expected[spec[8]];
6556
- const terminal = matchers[spec[9]], terminalFx = expected[spec[10]];
6557
- const tracks2 = spec[11];
6558
- if (g00 === void 0 || g01 === void 0 || g02 === void 0 || g03 === void 0 || guard0Fx === void 0 || g1 === void 0 || guard1Fx === void 0 || terminal === void 0 || terminalFx === void 0 || tracks2 !== 0 && tracks2 !== 1) {
6559
- throw new TypeError("table lexical program references an invalid pool entry");
6560
- }
6561
- return (input, pos, ctx) => {
6562
- ctx._fc = false;
6563
- if (g00(input, pos) >= 0 || g01(input, pos) >= 0 || g02(input, pos) >= 0 || g03(input, pos) >= 0) {
6564
- ctx._fe = pos;
6565
- ctx._fx = guard0Fx;
6566
- return -1;
6064
+ return SUGGESTIONS[cause];
6065
+ }
6066
+ function classifyBroadArm(arm, resolve) {
6067
+ const seen = /* @__PURE__ */ new Set();
6068
+ const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
6069
+ const walk = (p) => {
6070
+ if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
6071
+ seen.add(p);
6072
+ const d = p._def;
6073
+ switch (d.tag) {
6074
+ case "literal":
6075
+ case "regex":
6076
+ case "keywords":
6077
+ return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
6078
+ case "not":
6079
+ return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
6080
+ // peek() CARRIES its body's first-set, so it is only broad when the body is
6081
+ // (or the body is nullable) — walk in for the real cause.
6082
+ case "peek":
6083
+ return walk(d.parser);
6084
+ case "scanTo":
6085
+ return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
6086
+ // A run opens with its first line's prefix.
6087
+ case "lines":
6088
+ return walk(d.first ?? d.prefix);
6089
+ // `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
6090
+ // its own — it is the poison. `withCtx`/`recover` FORWARD their inner
6091
+ // first-set (see firstSetOf), so a broad result comes from the inner parser:
6092
+ // walk into it for a precise cause.
6093
+ case "guard":
6094
+ return { cause: "opaque-wrapper", detail: `opaque wrapper (${d.tag})` };
6095
+ // Zero-width and dropped from a sequence's first-set (isZeroWidthAssertion),
6096
+ // so it is never the reason an arm is broad — but it can be reached as the
6097
+ // sole term of a degenerate arm. Report it precisely rather than as a wrapper.
6098
+ case "adjacency":
6099
+ return { cause: "broad-recognizer", detail: `${d.polarity}() (zero-width assertion)` };
6100
+ // attempt/withCtx/recover FORWARD their inner first-set (transparent for
6101
+ // dispatch), so a broad result comes from the inner parser — walk into it.
6102
+ case "attempt":
6103
+ case "withCtx":
6104
+ case "recover":
6105
+ return walk(d.parser);
6106
+ case "lazy": {
6107
+ const name = p._ruleName;
6108
+ let target;
6109
+ try {
6110
+ target = d.thunk();
6111
+ } catch {
6112
+ const bound = name !== void 0 ? resolve?.(name) : void 0;
6113
+ if (bound === void 0)
6114
+ return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
6115
+ target = bound;
6116
+ }
6117
+ const inner = walk(target);
6118
+ if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
6119
+ return inner;
6567
6120
  }
6568
- ctx._fc = false;
6569
- if (g1(input, pos) >= 0) {
6570
- ctx._fe = pos;
6571
- ctx._fx = guard1Fx;
6572
- return -1;
6121
+ case "optional":
6122
+ case "many": {
6123
+ const inner = walk(d.parser);
6124
+ return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
6573
6125
  }
6574
- ctx._fc = false;
6575
- const result = terminal(input, pos);
6576
- if (result < 0) {
6577
- ctx._fe = pos;
6578
- ctx._fx = terminalFx;
6579
- if (ctx._probe !== void 0) failAt(ctx, ctx._fx, pos);
6580
- return -1;
6126
+ case "oneOrMore":
6127
+ case "transform":
6128
+ case "label":
6129
+ case "field":
6130
+ case "trivia":
6131
+ case "token":
6132
+ case "leaf":
6133
+ case "node":
6134
+ case "grammar":
6135
+ case "expect":
6136
+ return walk(d.parser);
6137
+ case "sequence": {
6138
+ let sawNullablePrefix = false;
6139
+ for (const t of d.parsers) {
6140
+ if (t._def.tag === "not") {
6141
+ sawNullablePrefix = true;
6142
+ continue;
6143
+ }
6144
+ if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve))) {
6145
+ const inner = walk(t);
6146
+ if (sawNullablePrefix)
6147
+ return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
6148
+ return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
6149
+ }
6150
+ if (matchesEmpty(t, /* @__PURE__ */ new Set(), resolve)) {
6151
+ sawNullablePrefix = true;
6152
+ continue;
6153
+ }
6154
+ return null;
6155
+ }
6156
+ return { cause: "broad-recognizer", detail: "sequence of only nullable/zero-width terms" };
6581
6157
  }
6582
- const end = result / 2;
6583
- if (tracks2 === 1) trackLexLines(ctx, input, end);
6584
- return end;
6585
- };
6586
- }
6587
- const cls = classes[spec[2]];
6588
- const choiceFx = expected[spec[3]];
6589
- const m0 = matchers[spec[4]], x0 = expected[spec[5]];
6590
- const m1 = matchers[spec[6]], x1 = expected[spec[7]];
6591
- const m2 = matchers[spec[8]], x2 = expected[spec[9]];
6592
- const m3 = matchers[spec[10]], x3 = expected[spec[11]];
6593
- const tracks = spec[12];
6594
- if (cls === void 0 || choiceFx === void 0 || m0 === void 0 || x0 === void 0 || m1 === void 0 || x1 === void 0 || m2 === void 0 || x2 === void 0 || m3 === void 0 || x3 === void 0 || tracks !== 0 && tracks !== 1) {
6595
- throw new TypeError("table lexical program references an invalid pool entry");
6596
- }
6597
- return (input, pos, ctx) => {
6598
- const lead2 = pos < input.length ? input.codePointAt(pos) : -1;
6599
- if (!classHas(cls, lead2)) {
6600
- ctx._fe = pos;
6601
- ctx._fx = choiceFx.slice();
6602
- return -1;
6158
+ case "choice": {
6159
+ for (const a of d.parsers) {
6160
+ const r = walk(a);
6161
+ if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
6162
+ }
6163
+ return null;
6164
+ }
6165
+ case "dispatch":
6166
+ return walk(d.selector);
6167
+ default:
6168
+ return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
6603
6169
  }
6604
- let acc;
6605
- const failed = (fx) => {
6606
- ctx._fe = pos;
6607
- ctx._fx = fx;
6608
- if (ctx._probe !== void 0) failAt(ctx, ctx._fx, pos);
6609
- if (acc === void 0) acc = fx.slice();
6610
- else acc.push(...fx);
6611
- };
6612
- ctx._fc = false;
6613
- const success = (result2) => {
6614
- const end = result2 / 2;
6615
- if (tracks === 1) trackLexLines(ctx, input, end);
6616
- return end;
6617
- };
6618
- let result = m0(input, pos);
6619
- if (result >= 0) return success(result);
6620
- failed(x0);
6621
- ctx._fc = false;
6622
- result = m1(input, pos);
6623
- if (result >= 0) return success(result);
6624
- failed(x1);
6625
- ctx._fc = false;
6626
- result = m2(input, pos);
6627
- if (result >= 0) return success(result);
6628
- failed(x2);
6629
- ctx._fc = false;
6630
- result = m3(input, pos);
6631
- if (result >= 0) return success(result);
6632
- failed(x3);
6633
- ctx._fe = pos;
6634
- ctx._fx = acc ?? choiceFx;
6635
- return -1;
6636
6170
  };
6171
+ return walk(arm) ?? { cause: "broad-recognizer", detail: "broad (cause not localized)" };
6637
6172
  }
6638
-
6639
- // src/table/lex-body.ts
6640
- function buildLexBodyRecognizer(spec, constants) {
6641
- const raw = constants[spec[0]];
6642
- if (!(raw instanceof RegExp)) {
6643
- throw new TypeError("table lexical body does not reference a RegExp constant");
6644
- }
6645
- if (!Number.isInteger(spec[1]) || spec[1] < -1 || spec[1] > 65535) {
6646
- throw new TypeError("table lexical body suffix is not absent or one UTF-16 code unit");
6173
+ function peelToLeading(arm) {
6174
+ let d = arm._def;
6175
+ let cur = arm;
6176
+ for (; ; ) {
6177
+ if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label" || d.tag === "attempt") {
6178
+ cur = d.parser;
6179
+ d = cur._def;
6180
+ continue;
6181
+ }
6182
+ break;
6647
6183
  }
6648
- const re = raw.sticky ? new RegExp(raw.source, raw.flags) : new RegExp(raw.source, `${raw.flags.replace(/[gy]/g, "")}y`);
6649
- const suffix = spec[1];
6650
- return (input, pos) => {
6651
- re.lastIndex = pos;
6652
- if (!re.test(input)) return -1;
6653
- const end = re.lastIndex;
6654
- if (suffix < 0) return end * 2;
6655
- return input.charCodeAt(end) === suffix ? (end + 1) * 2 + 1 : end * 2;
6656
- };
6184
+ if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
6185
+ return cur;
6657
6186
  }
6658
-
6659
- // src/table/program.ts
6660
- function closureArtifact(prog) {
6661
- return { ...prog, asm: [] };
6662
- }
6663
- function covKindCode(kind) {
6664
- switch (kind) {
6665
- case "rule":
6666
- return 0;
6667
- case "choice-arm":
6668
- return 1;
6669
- case "dispatch-arm":
6670
- return 2;
6671
- case "label":
6672
- return 3;
6673
- }
6674
- }
6675
- function covKindName(code) {
6676
- switch (code) {
6677
- case 0:
6678
- return "rule";
6679
- case 1:
6680
- return "choice-arm";
6681
- case 2:
6682
- return "dispatch-arm";
6683
- case 3:
6684
- return "label";
6685
- }
6187
+ function detectAntiPatterns(rule, arms) {
6188
+ const out = [];
6189
+ arms.forEach((arm, i) => {
6190
+ const lead2 = peelToLeading(arm);
6191
+ const ld = lead2._def;
6192
+ if (ld.tag === "not") {
6193
+ const inner = ld.parser;
6194
+ if (inner._def.tag === "not") {
6195
+ out.push({
6196
+ kind: "double-not",
6197
+ rule,
6198
+ armIndex: i,
6199
+ message: 'not(not(X)) is a hand-written way of saying "X must come next, but do not consume it". parseman does that for you, and the hand-written form is worse than nothing here: because not(...) reveals no first character, the arm cannot be skipped, and among sibling arms that share a first character it can select the wrong one.\nTo fix: write peek(X). It checks the same thing without consuming, and it also tells parseman that the arm starts with whatever X starts with.'
6200
+ });
6201
+ } else {
6202
+ out.push({
6203
+ kind: "leading-not",
6204
+ rule,
6205
+ armIndex: i,
6206
+ message: "This arm begins with not(...), which matches no text and so reveals no first character. The parser therefore cannot skip the arm and enters it at every position.\nTo fix: put the term that actually consumes text first, and keep not(...) after it as a trailing boundary check."
6207
+ });
6208
+ }
6209
+ }
6210
+ if (ld.tag === "regex" && KEYWORD_REGEX_RE.test(ld.source)) {
6211
+ out.push({
6212
+ kind: "keyword-regex",
6213
+ rule,
6214
+ armIndex: i,
6215
+ message: `This arm starts with the fixed word \`${ld.source}\` written as a regular expression. Writing it as word('\u2026') or keywords([\u2026]) matches exactly the same text and compiles to the same character scan, but also tells parseman the first character, which is what lets the parser skip the arm when it cannot match.`
6216
+ });
6217
+ }
6218
+ });
6219
+ return out;
6686
6220
  }
6687
- function covDefinitions(prog) {
6688
- return (prog.cov ?? []).map(([id, kind]) => ({ id, kind: covKindName(kind) }));
6221
+ function analyzeGating(entry, opts) {
6222
+ return analyzeGatingRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
6689
6223
  }
6690
- function expandCompact(p) {
6691
- if ("code" in p) return p;
6692
- return ownTableProgram({
6693
- code: p.c,
6694
- k: p.k,
6695
- cc: p.x,
6696
- fx: p.e,
6697
- disp: p.d,
6698
- rules: p.r,
6699
- fns: p.f,
6700
- lines: p.l ?? 0,
6701
- dsp: p.p ?? [],
6702
- ...p.lx === void 0 ? {} : { lex: p.lx },
6703
- ...p.lp === void 0 ? {} : { lexPrograms: p.lp },
6704
- ...p.lb === void 0 ? {} : { labels: p.lb },
6705
- ...p.rc === void 0 ? {} : { classified: p.rc },
6706
- ...p.h === void 0 ? {} : { hostMode: p.h },
6707
- ...p.tv === void 0 ? {} : { triviaSpecs: p.tv },
6708
- ...p.sc === void 0 ? {} : { scans: p.sc },
6709
- ...p.ss === void 0 ? {} : { scanSkip: p.ss },
6710
- ...p.so === void 0 ? {} : { scanSkipOf: p.so },
6711
- ...p.rv === void 0 ? {} : { rec: p.rv },
6712
- ...p.cv === void 0 ? {} : { cov: p.cv },
6713
- ...p.a === void 0 ? {} : { asm: p.a }
6224
+ function analyzeGatingRules(ruleMap, opts) {
6225
+ const raw = [];
6226
+ const antiPatterns = [];
6227
+ const unanalysable = [];
6228
+ const seenUnanalysable = /* @__PURE__ */ new Set();
6229
+ const visited = /* @__PURE__ */ new Set();
6230
+ const noteUnanalysable = (u) => {
6231
+ const key = `${u.rule}\0${u.kind}`;
6232
+ if (seenUnanalysable.has(key)) return;
6233
+ seenUnanalysable.add(key);
6234
+ unanalysable.push(u);
6235
+ };
6236
+ const visit = (p, enclosingRule) => {
6237
+ if (visited.has(p)) return;
6238
+ visited.add(p);
6239
+ const def = p?._def;
6240
+ if (def === null || typeof def !== "object") {
6241
+ noteUnanalysable(
6242
+ typeof p === "function" ? {
6243
+ rule: enclosingRule,
6244
+ kind: "fused-rule",
6245
+ reason: "fused rule function \u2014 a compose()/fuse result has no combinator graph. Analyze the composed grammar itself (analyzeGrammarGating) so the carried IR is re-lowered first."
6246
+ } : {
6247
+ rule: enclosingRule,
6248
+ kind: "not-a-combinator",
6249
+ reason: `value of type ${p === null ? "null" : typeof p} is not a combinator and carries no _def descriptor.`
6250
+ }
6251
+ );
6252
+ return;
6253
+ }
6254
+ const d = def;
6255
+ const rule = ruleNameOf2(p) ?? enclosingRule;
6256
+ if (d.tag === "choice") {
6257
+ raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule, arms: d.parsers });
6258
+ antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
6259
+ }
6260
+ const kids = [...childrenOf2(d)];
6261
+ if (d.tag === "lazy") {
6262
+ try {
6263
+ kids.push(d.thunk());
6264
+ } catch {
6265
+ }
6266
+ }
6267
+ for (const k of kids) visit(k, rule);
6268
+ };
6269
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name);
6270
+ const perRule = /* @__PURE__ */ new Map();
6271
+ for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
6272
+ const seenInRule = /* @__PURE__ */ new Map();
6273
+ const accept = new Set(opts?.accept ?? []);
6274
+ const usedAccept = /* @__PURE__ */ new Set();
6275
+ const choices = raw.map(({ g, rule, arms }) => {
6276
+ const n = seenInRule.get(rule) ?? 0;
6277
+ seenInRule.set(rule, n + 1);
6278
+ const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
6279
+ const isAccepted = g.gates === "no" && accept.has(id);
6280
+ if (isAccepted) usedAccept.add(id);
6281
+ const out = { ...g, id, accepted: isAccepted };
6282
+ Object.defineProperty(out, "arms", { value: arms, enumerable: false, writable: false });
6283
+ return out;
6714
6284
  });
6715
- }
6716
- function validateDispatchSpec(spec, arity, fallbackRouted) {
6717
- const invalid = (arms) => {
6718
- for (const arm of arms) if (!Number.isInteger(arm) || arm < 0 || arm >= arity) return true;
6719
- return false;
6285
+ const gated = choices.filter((c) => c.gates === "yes").length;
6286
+ const recoverable = choices.filter((c) => c.gates === "recoverable").length;
6287
+ const ungated = choices.filter((c) => c.gates === "no" && !c.accepted && !c.deferred);
6288
+ const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
6289
+ const deferred = choices.filter((c) => c.gates === "no" && !c.accepted && c.deferred);
6290
+ const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
6291
+ return {
6292
+ totalChoices: choices.length,
6293
+ gated,
6294
+ recoverable,
6295
+ unanalysable,
6296
+ ungated,
6297
+ accepted,
6298
+ deferred,
6299
+ acceptedUnused,
6300
+ choices,
6301
+ antiPatterns
6720
6302
  };
6721
- if (spec === void 0 || !Number.isInteger(arity) || arity < 0 || fallbackRouted !== 0 && fallbackRouted !== 1 || spec.routed.length !== arity || spec.routed.some((value) => value !== 0 && value !== 1) || invalid(spec.byKey.values()) || invalid(spec.byFold.values()) || spec.match.some((matcher) => invalid([matcher[3]]))) {
6722
- throw new TypeError("table: malformed dispatch");
6723
- }
6724
6303
  }
6725
- var TABLE_RUNTIME = /* @__PURE__ */ Symbol("parseman.tableRuntime");
6726
- function ownTableProgram(prog, resolved, caches = {}) {
6727
- const runtime = prog[TABLE_RUNTIME];
6304
+ function analyzeChoice(p, d, rule, resolve) {
6305
+ const arms = d.parsers;
6306
+ const shallow = arms.map((a) => a._meta.firstSet);
6307
+ const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve));
6308
+ const anyArms = deep.map((fs, index) => ({ fs, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
6309
+ const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve);
6310
+ return {
6311
+ index,
6312
+ cause,
6313
+ detail,
6314
+ shallowAnyOnly: false,
6315
+ unresolvedExternal: unresolvedExternal === true,
6316
+ suggestion: refineSuggestion(cause, arms[index])
6317
+ };
6318
+ });
6319
+ const overlaps = [];
6320
+ for (let i = 0; i < deep.length; i++)
6321
+ for (let j = i + 1; j < deep.length; j++)
6322
+ if (!isAny(deep[i]) && !isAny(deep[j]) && intersects(deep[i], deep[j]))
6323
+ overlaps.push({
6324
+ a: i,
6325
+ b: j,
6326
+ on: intersection(deep[i], deep[j]),
6327
+ suggestion: "Two arms of this choice can begin with the same character, so the parser cannot tell from that character which one to try. It tries them in order and undoes the ones that do not match.\nTo fix: pull the shared beginning out in front of the choice so it is matched once \u2014 sequence(shared, choice(rest\u2026)) \u2014 instead of repeating it inside each arm. parseman recognises that shape automatically and turns it back into a single test."
6328
+ });
6329
+ let gates;
6330
+ if (d.disjoint) gates = "yes";
6331
+ else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
6332
+ else gates = "no";
6333
+ const deferred = gates === "no" && overlaps.length === 0 && anyArms.length > 0 && anyArms.every((a) => a.unresolvedExternal);
6728
6334
  return {
6729
- ...prog,
6730
- [TABLE_RUNTIME]: {
6731
- resolved,
6732
- choiceRollbackMasks: caches.choiceRollbackMasks ?? runtime?.choiceRollbackMasks,
6733
- nonCommittingChoiceSites: caches.nonCommittingChoiceSites ?? runtime?.nonCommittingChoiceSites,
6734
- failureRollbackCleanSites: caches.failureRollbackCleanSites ?? runtime?.failureRollbackCleanSites
6735
- }
6335
+ rule,
6336
+ strategy: d.strategy?.tag ?? "firstMatch",
6337
+ gates,
6338
+ deferred,
6339
+ combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
6340
+ anyArms,
6341
+ overlaps
6736
6342
  };
6737
6343
  }
6738
- function choiceRollbackMask(prog, ip) {
6739
- return prog[TABLE_RUNTIME]?.choiceRollbackMasks?.get(ip);
6344
+ function formatGatingWarnings(report) {
6345
+ const lines2 = [];
6346
+ if (report.unanalysable.length > 0) {
6347
+ lines2.push(
6348
+ `parseman gating: ${report.unanalysable.length} rule(s) UNANALYSABLE \u2014 this report is PARTIAL (${report.totalChoices} choice(s) examined). An empty finding list below does NOT mean the grammar is clean.`
6349
+ );
6350
+ for (const u of report.unanalysable)
6351
+ lines2.push(` \xB7 ${u.rule} [${u.kind}]: ${u.reason}`);
6352
+ }
6353
+ for (const c of report.ungated) {
6354
+ lines2.push(`parseman gating: choice @ ${c.id} is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms.`);
6355
+ for (const a of c.anyArms)
6356
+ lines2.push(` \xB7 arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
6357
+ fix: ${a.suggestion}`);
6358
+ for (const o of c.overlaps)
6359
+ lines2.push(` \xB7 arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
6360
+ fix: ${o.suggestion}`);
6361
+ lines2.push(` (intentional? accept it in the gating snapshot: { accept: ['${c.id}'] }.)`);
6362
+ }
6363
+ for (const ap of report.antiPatterns)
6364
+ lines2.push(`parseman anti-pattern [${ap.kind}] @ ${ap.rule} arm[${ap.armIndex}]: ${ap.message}`);
6365
+ return lines2;
6740
6366
  }
6741
- function choiceCannotCommit(prog, ip) {
6742
- return prog[TABLE_RUNTIME]?.nonCommittingChoiceSites?.has(ip) === true;
6367
+ function ruleDependencies(ruleMap) {
6368
+ const nameOf2 = /* @__PURE__ */ new Map();
6369
+ for (const [name, comb] of ruleMap) nameOf2.set(comb, name);
6370
+ const deps = /* @__PURE__ */ new Map();
6371
+ for (const [name, comb] of ruleMap) {
6372
+ const found = /* @__PURE__ */ new Set();
6373
+ const seen = /* @__PURE__ */ new Set();
6374
+ const walk = (p, isRoot) => {
6375
+ const def = p._def;
6376
+ const boundary = def.tag === "lazy" ? nameOf2.get(p) ?? p._ruleName : nameOf2.get(p);
6377
+ if (!isRoot && boundary !== void 0) {
6378
+ found.add(boundary);
6379
+ return;
6380
+ }
6381
+ if (seen.has(p)) return;
6382
+ seen.add(p);
6383
+ if (def.tag === "lazy") {
6384
+ let resolved;
6385
+ try {
6386
+ resolved = def.thunk();
6387
+ } catch {
6388
+ return;
6389
+ }
6390
+ walk(resolved, false);
6391
+ return;
6392
+ }
6393
+ for (const child of childrenOf2(def)) walk(child, false);
6394
+ };
6395
+ walk(comb, true);
6396
+ deps.set(name, [...found]);
6397
+ }
6398
+ return deps;
6743
6399
  }
6744
- function failureRollbackClean(prog, ip) {
6745
- return prog[TABLE_RUNTIME]?.failureRollbackCleanSites?.has(ip) === true;
6400
+
6401
+ // src/analysis/commitment.ts
6402
+ function mayFail(p, seen = /* @__PURE__ */ new Set()) {
6403
+ if (seen.has(p)) return true;
6404
+ seen.add(p);
6405
+ const d = p._def;
6406
+ switch (d.tag) {
6407
+ case "literal":
6408
+ return d.value.length > 0;
6409
+ case "trivia":
6410
+ return false;
6411
+ // `optional`/`many`/a min-0 `sepBy` swallow an ORDINARY failure — zero
6412
+ // repetitions is a match. They do NOT swallow a COMMITTED one: a dispatch
6413
+ // that has selected an arm and then failed inside it breaks out past every
6414
+ // enclosing repeat, straight to the fallible boundary. So these are total
6415
+ // exactly when their body cannot commit. (Measured: treating them as
6416
+ // unconditionally total left an extra zero-width leaf in
6417
+ // `BareInterpolatedSelector` on benchmark.less — a rollback the legacy
6418
+ // build fired and this one had deleted.)
6419
+ case "optional":
6420
+ case "many":
6421
+ return mayCommitFailure(d.parser);
6422
+ case "sepBy":
6423
+ return d.min >= 1 ? mayFail(d.parser, seen) : mayCommitFailure(d.parser) || mayCommitFailure(d.separator);
6424
+ case "oneOrMore":
6425
+ return mayFail(d.parser, seen);
6426
+ case "sequence":
6427
+ return d.parsers.some((x) => mayFail(x, seen));
6428
+ // Delegating wrappers: as fallible as what they wrap.
6429
+ case "node":
6430
+ case "transform":
6431
+ case "label":
6432
+ case "field":
6433
+ case "expect":
6434
+ case "withCtx":
6435
+ case "grammar":
6436
+ case "token":
6437
+ case "leaf":
6438
+ return mayFail(d.parser, seen);
6439
+ case "lazy": {
6440
+ try {
6441
+ return mayFail(d.thunk(), seen);
6442
+ } catch {
6443
+ return true;
6444
+ }
6445
+ }
6446
+ // An adjacency assertion is a TEST — failing is its whole job.
6447
+ case "adjacency":
6448
+ return true;
6449
+ default:
6450
+ return true;
6451
+ }
6746
6452
  }
6747
- var WIDE_CLASS_PREFIX_LO = 65535;
6748
- var WIDE_CLASS_PREFIX_HI = 0;
6749
- function validateClassRange(lo, hi) {
6750
- for (const cp of [lo, hi]) {
6751
- if (!Number.isInteger(cp) || cp < 0 || cp > 1114111) {
6752
- throw new TypeError(`table char-class endpoint is outside Unicode: ${cp}`);
6453
+ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set(), triviaActive = true) {
6454
+ if (seen.has(p)) return false;
6455
+ seen.add(p);
6456
+ const d = p._def;
6457
+ switch (d.tag) {
6458
+ // Atomic / non-capturing-then-failing: a failure happens before any push.
6459
+ case "literal":
6460
+ case "regex":
6461
+ case "keywords":
6462
+ case "guard":
6463
+ // Adjacency probes with `_cap = 0` / no `commit()`: it cannot push anything.
6464
+ case "adjacency":
6465
+ case "not":
6466
+ // peek(): emitted under a non-capturing probe ctx and zero-width on both
6467
+ // outcomes, so it can never leave a partial capture behind.
6468
+ case "peek":
6469
+ case "trivia":
6470
+ case "token":
6471
+ case "leaf":
6472
+ case "scanTo":
6473
+ case "unknown":
6474
+ case "columns":
6475
+ return false;
6476
+ // A run's prefixes are probed detached; only its body can capture.
6477
+ case "lines":
6478
+ return mayLeavePartialCapture(d.body, seen, triviaActive);
6479
+ // node() captures into its own private buffers and rolls them back on
6480
+ // failure (emitNode restores _ctx.* and never pushes on the !ok path).
6481
+ case "node":
6482
+ return false;
6483
+ // choice/firstMatch already roll back each failed arm; on overall failure
6484
+ // nothing committed remains.
6485
+ case "choice":
6486
+ return false;
6487
+ case "dispatch":
6488
+ return capturesLeaf(d.selector, new Set(seen)) || d.cases.some((x) => mayLeavePartialCapture(x.parser, new Set(seen), triviaActive) || capturesLeaf(x.parser, new Set(seen))) || (d.matchers ? d.matchers.some((x) => mayLeavePartialCapture(x.parser, new Set(seen), triviaActive) || capturesLeaf(x.parser, new Set(seen))) : false) || (d.otherwise ? mayLeavePartialCapture(d.otherwise, new Set(seen), triviaActive) || capturesLeaf(d.otherwise, new Set(seen)) : false);
6489
+ case "attempt":
6490
+ return false;
6491
+ // optional never fails; many/oneOrMore only "fail" with zero captured items.
6492
+ case "optional":
6493
+ case "many":
6494
+ case "oneOrMore":
6495
+ return false;
6496
+ // sepBy emits its own per-iteration rollback in emitSepBy.
6497
+ case "sepBy":
6498
+ return false;
6499
+ // A sequence is the real case: an earlier capturing term followed by a term
6500
+ // that can fail leaves the earlier captures buffered.
6501
+ //
6502
+ // BOTH halves of that sentence are load-bearing, and the second half is what
6503
+ // this walk exists to exploit. "Some earlier term captures" alone was the old
6504
+ // test, and it is true of very nearly every sequence in a real grammar — a
6505
+ // sequence almost always opens with a literal or a node. The rollback is only
6506
+ // reachable if something AFTER the capture can still fail; once the remainder
6507
+ // is total (`optional`/`many`/trivia/a min-0 `sepBy`), the sequence has no
6508
+ // failure path left to roll back from, and the save+restore is dead code.
6509
+ //
6510
+ // `captured` tracks whether anything is in the buffers yet as the scan moves
6511
+ // left to right. Two things put it there:
6512
+ // - a term that captures (`capturesLeaf`/`hasNodeDef`), and
6513
+ // - the inter-term TRIVIA scan, which under `capturing` pushes into
6514
+ // `_cstRawChildren`/`_cstTriviaLog` BEFORE the term at each boundary
6515
+ // i >= 1. `emitSeqValues` only rewinds that push on the term's SUCCESS
6516
+ // path (the "matched empty, so leave the whitespace out of the span"
6517
+ // branch); a term that FAILS breaks straight out with the trivia still
6518
+ // buffered, so from boundary 1 onward the buffers are dirty regardless of
6519
+ // what the earlier terms did.
6520
+ // Each term is also asked about itself: a nested sequence can leave its own
6521
+ // partial capture behind, and its terms are emitted inline into this same
6522
+ // block with no boundary of their own to roll them back.
6523
+ case "sequence": {
6524
+ const parts = d.parsers;
6525
+ let captured = false;
6526
+ for (let i = 0; i < parts.length; i++) {
6527
+ const part = parts[i];
6528
+ if (i > 0 && triviaActive) captured = true;
6529
+ if (captured && mayFail(part)) return true;
6530
+ if (mayLeavePartialCapture(part, seen, triviaActive)) return true;
6531
+ if (capturesLeaf(part) || hasNodeDef(part)) captured = true;
6532
+ }
6533
+ return false;
6534
+ }
6535
+ // Delegating wrappers: defer to the wrapped parser.
6536
+ case "transform":
6537
+ case "label":
6538
+ case "field":
6539
+ case "expect":
6540
+ case "withCtx":
6541
+ case "grammar":
6542
+ return mayLeavePartialCapture(d.parser, seen, triviaActive);
6543
+ case "recover":
6544
+ return mayLeavePartialCapture(d.parser, seen, triviaActive);
6545
+ case "lazy": {
6546
+ try {
6547
+ return mayLeavePartialCapture(d.thunk(), seen, triviaActive);
6548
+ } catch {
6549
+ return true;
6550
+ }
6551
+ }
6552
+ // Unknown shapes: be safe and keep the rollback.
6553
+ default:
6554
+ return true;
6555
+ }
6556
+ }
6557
+ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6558
+ if (seen.has(p)) return false;
6559
+ seen.add(p);
6560
+ const d = p._def;
6561
+ switch (d.tag) {
6562
+ case "literal":
6563
+ case "regex":
6564
+ case "keywords":
6565
+ case "node":
6566
+ case "token":
6567
+ case "routed":
6568
+ case "leaf":
6569
+ return true;
6570
+ case "not":
6571
+ case "peek":
6572
+ case "guard":
6573
+ case "adjacency":
6574
+ case "trivia":
6575
+ case "unknown":
6576
+ return false;
6577
+ case "sequence":
6578
+ case "choice":
6579
+ return d.parsers.some((x) => capturesLeaf(x, seen));
6580
+ case "dispatch":
6581
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => capturesLeaf(entry.parser, seen)) : false) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
6582
+ case "sepBy":
6583
+ return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6584
+ case "many":
6585
+ case "oneOrMore":
6586
+ case "optional":
6587
+ case "attempt":
6588
+ case "transform":
6589
+ case "label":
6590
+ case "field":
6591
+ case "expect":
6592
+ case "withCtx":
6593
+ case "grammar":
6594
+ case "recover":
6595
+ return capturesLeaf(d.parser, seen);
6596
+ case "scanTo":
6597
+ return true;
6598
+ case "lines":
6599
+ return capturesLeaf(d.body, seen);
6600
+ case "columns":
6601
+ return true;
6602
+ case "lazy": {
6603
+ try {
6604
+ return capturesLeaf(d.thunk(), seen);
6605
+ } catch {
6606
+ return true;
6607
+ }
6753
6608
  }
6754
- }
6755
- if (lo > hi) {
6756
- throw new TypeError(`table char-class range is descending: ${lo} > ${hi}`);
6609
+ default:
6610
+ return true;
6757
6611
  }
6758
6612
  }
6759
- function encodeClassSpec(ranges) {
6760
- let wide = false;
6761
- for (const range of ranges) {
6762
- validateClassRange(range.lo, range.hi);
6763
- if (range.hi > 65535) wide = true;
6764
- }
6765
- if (!wide) {
6766
- let spec2 = "";
6767
- for (const range of ranges) spec2 += String.fromCharCode(range.lo, range.hi);
6768
- return spec2;
6769
- }
6770
- let spec = String.fromCharCode(WIDE_CLASS_PREFIX_LO, WIDE_CLASS_PREFIX_HI);
6771
- for (const range of ranges) {
6772
- spec += String.fromCharCode(
6773
- range.lo >>> 16,
6774
- range.lo & 65535,
6775
- range.hi >>> 16,
6776
- range.hi & 65535
6777
- );
6613
+ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
6614
+ if (seen.has(p)) return false;
6615
+ seen.add(p);
6616
+ const d = p._def;
6617
+ switch (d.tag) {
6618
+ case "node":
6619
+ return true;
6620
+ case "lazy": {
6621
+ try {
6622
+ return hasNodeDef(d.thunk(), seen);
6623
+ } catch {
6624
+ return false;
6625
+ }
6626
+ }
6627
+ case "grammar":
6628
+ case "trivia":
6629
+ case "token":
6630
+ case "leaf":
6631
+ case "label":
6632
+ case "field":
6633
+ case "optional":
6634
+ case "many":
6635
+ case "oneOrMore":
6636
+ case "not":
6637
+ case "peek":
6638
+ case "transform":
6639
+ return hasNodeDef(d.parser, seen);
6640
+ case "sequence":
6641
+ case "choice":
6642
+ return d.parsers.some((x) => hasNodeDef(x, seen));
6643
+ case "dispatch":
6644
+ return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => hasNodeDef(entry.parser, seen)) : false) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
6645
+ case "sepBy":
6646
+ return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
6647
+ case "scanTo":
6648
+ return hasNodeDef(d.sentinel, seen) || d.skip.some((x) => hasNodeDef(x, seen)) || d.recoverAt !== void 0 && hasNodeDef(d.recoverAt, seen) || d.stopAt !== void 0 && hasNodeDef(d.stopAt, seen);
6649
+ case "lines":
6650
+ return hasNodeDef(d.body, seen);
6651
+ case "recover":
6652
+ return hasNodeDef(d.parser, seen) || hasNodeDef(d.sentinel, seen);
6653
+ case "expect":
6654
+ return hasNodeDef(d.parser, seen);
6655
+ case "withCtx":
6656
+ return hasNodeDef(d.parser, seen);
6657
+ default:
6658
+ return false;
6778
6659
  }
6779
- return spec;
6780
6660
  }
6781
- function decodeClassSpec(spec) {
6782
- if (spec.charCodeAt(0) === WIDE_CLASS_PREFIX_LO && spec.charCodeAt(1) === WIDE_CLASS_PREFIX_HI) {
6783
- if ((spec.length - 2) % 4 !== 0) throw new TypeError("table char-class spec has a truncated wide range");
6784
- if (spec.length === 2) throw new TypeError("table char-class spec has a wide prefix but no ranges");
6785
- const ranges2 = [];
6786
- for (let i = 2; i < spec.length; i += 4) {
6787
- const lo = spec.charCodeAt(i) * 65536 + spec.charCodeAt(i + 1);
6788
- const hi = spec.charCodeAt(i + 2) * 65536 + spec.charCodeAt(i + 3);
6789
- validateClassRange(lo, hi);
6790
- ranges2.push({ lo, hi });
6661
+ function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
6662
+ if (seen.has(p)) return false;
6663
+ seen.add(p);
6664
+ const d = p._def;
6665
+ switch (d.tag) {
6666
+ case "node":
6667
+ return d.build !== void 0 || hasDirectBuildDef(d.parser, seen);
6668
+ case "lazy": {
6669
+ try {
6670
+ return hasDirectBuildDef(d.thunk(), seen);
6671
+ } catch {
6672
+ return false;
6673
+ }
6791
6674
  }
6792
- return ranges2;
6793
- }
6794
- if (spec.length % 2 !== 0) throw new TypeError("table char-class spec has an unmatched range endpoint");
6795
- const ranges = [];
6796
- for (let i = 0; i < spec.length; i += 2) {
6797
- const lo = spec.charCodeAt(i);
6798
- const hi = spec.charCodeAt(i + 1);
6799
- validateClassRange(lo, hi);
6800
- ranges.push({ lo, hi });
6675
+ case "grammar":
6676
+ case "trivia":
6677
+ case "token":
6678
+ case "leaf":
6679
+ case "label":
6680
+ case "field":
6681
+ case "optional":
6682
+ case "many":
6683
+ case "oneOrMore":
6684
+ case "attempt":
6685
+ case "not":
6686
+ case "peek":
6687
+ case "withCtx":
6688
+ case "expect":
6689
+ case "transform":
6690
+ return hasDirectBuildDef(d.parser, seen);
6691
+ case "sequence":
6692
+ case "choice":
6693
+ return d.parsers.some((x) => hasDirectBuildDef(x, seen));
6694
+ case "dispatch":
6695
+ return hasDirectBuildDef(d.selector, seen) || d.cases.some((x) => hasDirectBuildDef(x.parser, seen)) || (d.matchers ? d.matchers.some((x) => hasDirectBuildDef(x.parser, seen)) : false) || (d.otherwise ? hasDirectBuildDef(d.otherwise, seen) : false);
6696
+ case "sepBy":
6697
+ return hasDirectBuildDef(d.parser, seen) || hasDirectBuildDef(d.separator, seen);
6698
+ case "scanTo":
6699
+ return hasDirectBuildDef(d.sentinel, seen) || d.skip.some((x) => hasDirectBuildDef(x, seen)) || d.recoverAt !== void 0 && hasDirectBuildDef(d.recoverAt, seen) || d.stopAt !== void 0 && hasDirectBuildDef(d.stopAt, seen);
6700
+ case "lines":
6701
+ return hasDirectBuildDef(d.body, seen);
6702
+ case "recover":
6703
+ return hasDirectBuildDef(d.parser, seen) || hasDirectBuildDef(d.sentinel, seen);
6704
+ case "routed":
6705
+ return d.fallback ? hasDirectBuildDef(d.fallback, seen) : false;
6706
+ default:
6707
+ return false;
6801
6708
  }
6802
- return ranges;
6803
6709
  }
6804
- function resolveClass(spec) {
6805
- const ascii = new Uint8Array(128);
6806
- const hi = [];
6807
- for (const { lo, hi: up } of decodeClassSpec(spec)) {
6808
- if (lo < 128) for (let c = lo; c <= Math.min(up, 127); c++) ascii[c] = 1;
6809
- if (up >= 128) hi.push(Math.max(lo, 128), up);
6810
- }
6811
- return { ascii, hi };
6710
+ function mayCommitFailure(p, resolve, memo = /* @__PURE__ */ new Map()) {
6711
+ return reachesMarked(p, (node2) => commitFailureSuccessors(node2, resolve), memo);
6812
6712
  }
6813
- function classHas2(cls, code) {
6814
- if (code < 0) return false;
6815
- if (code < 128) return cls.ascii[code] === 1;
6816
- const hi = cls.hi;
6817
- for (let i = 0; i < hi.length; i += 2) if (code >= hi[i] && code <= hi[i + 1]) return true;
6818
- return false;
6713
+ var NO_EDGES = [];
6714
+ function commitFailureSuccessors(p, resolve) {
6715
+ const d = p._def;
6716
+ switch (d.tag) {
6717
+ case "dispatch":
6718
+ case "unknown":
6719
+ return true;
6720
+ case "choice":
6721
+ case "sequence":
6722
+ return d.parsers;
6723
+ // A contained transaction reports every failure uncommitted.
6724
+ case "attempt":
6725
+ return d.contain === true ? NO_EDGES : [d.parser];
6726
+ case "many":
6727
+ case "oneOrMore":
6728
+ case "optional":
6729
+ case "transform":
6730
+ case "label":
6731
+ case "field":
6732
+ case "grammar":
6733
+ case "node":
6734
+ case "token":
6735
+ case "leaf":
6736
+ case "withCtx":
6737
+ return [d.parser];
6738
+ case "sepBy":
6739
+ return [d.parser, d.separator];
6740
+ case "scanTo":
6741
+ return [
6742
+ d.sentinel,
6743
+ ...d.skip,
6744
+ ...d.recoverAt === void 0 ? NO_EDGES : [d.recoverAt],
6745
+ ...d.stopAt === void 0 ? NO_EDGES : [d.stopAt]
6746
+ ];
6747
+ // A run's prefixes are probed detached; a committed failure reaches it only from its body.
6748
+ case "lines":
6749
+ return [d.body];
6750
+ case "lazy": {
6751
+ try {
6752
+ return [d.thunk()];
6753
+ } catch {
6754
+ const name = p._ruleName;
6755
+ const target = name === void 0 ? void 0 : resolve?.(name);
6756
+ return target === void 0 ? true : [target];
6757
+ }
6758
+ }
6759
+ default:
6760
+ return NO_EDGES;
6761
+ }
6819
6762
  }
6820
- function resolveDispatch(arms, cc) {
6821
- const ascii = new Uint8Array(128);
6822
- const hi = [];
6823
- const open = [];
6824
- const armCls = [];
6825
- let exclusive = true;
6826
- let hiOwners = 0;
6827
- for (let a = 0; a < arms.length; a++) {
6828
- const ci = arms[a];
6829
- if (ci < 0) {
6830
- open.push(a);
6831
- armCls.push(null);
6832
- exclusive = false;
6833
- continue;
6763
+ function reachesMarked(root, successors, memo) {
6764
+ const known = memo.get(root);
6765
+ if (known !== void 0) return known;
6766
+ const order = /* @__PURE__ */ new Map();
6767
+ const low = [];
6768
+ const found = [];
6769
+ const stack = [];
6770
+ const visit = (node2) => {
6771
+ const at = low.length;
6772
+ order.set(node2, at);
6773
+ low.push(at);
6774
+ found.push(false);
6775
+ stack.push(node2);
6776
+ const next = successors(node2);
6777
+ if (next === true) found[at] = true;
6778
+ else {
6779
+ for (const child of next) {
6780
+ if (found[at]) break;
6781
+ const hit = memo.get(child);
6782
+ if (hit !== void 0) {
6783
+ if (hit) found[at] = true;
6784
+ continue;
6785
+ }
6786
+ const seen = order.get(child);
6787
+ if (seen !== void 0) {
6788
+ if (seen < low[at]) low[at] = seen;
6789
+ continue;
6790
+ }
6791
+ const k = visit(child);
6792
+ if (low[k] < low[at]) low[at] = low[k];
6793
+ if (found[k]) found[at] = true;
6794
+ }
6834
6795
  }
6835
- const cls = cc[ci];
6836
- armCls.push(cls);
6837
- for (let c = 0; c < 128; c++) {
6838
- if (cls.ascii[c] !== 1) continue;
6839
- if (ascii[c] === 0) ascii[c] = a + 1;
6840
- else exclusive = false;
6796
+ if (low[at] === at) {
6797
+ const answer = found[at];
6798
+ let member;
6799
+ do {
6800
+ member = stack.pop();
6801
+ memo.set(member, answer);
6802
+ } while (member !== node2);
6841
6803
  }
6842
- if (cls.hi.length > 0 && ++hiOwners > 1) exclusive = false;
6843
- for (let i = 0; i < cls.hi.length; i += 2) hi.push(cls.hi[i], cls.hi[i + 1], a);
6844
- }
6845
- return { ascii, hi, open, armCls, exclusive };
6804
+ return at;
6805
+ };
6806
+ return found[visit(root)];
6846
6807
  }
6847
- function buildTrivia(spec) {
6848
- if (spec.live !== void 0) return spec.live;
6849
- if (spec.plain !== void 0) return trivia(regex(new RegExp(spec.plain[0], spec.plain[1])));
6850
- if (spec.alts !== void 0) {
6851
- const [head, ...rest] = spec.alts.map(([source, flags]) => regex(new RegExp(source, flags)));
6852
- if (head === void 0) throw new TypeError("trivia spec has an EMPTY alternation, which matches nothing");
6853
- const body = rest.length === 0 ? head : choice(head, ...rest);
6854
- return trivia(many(body, { min: spec.min ?? 1 }));
6855
- }
6856
- const arms = {};
6857
- for (const [name, source, flags] of spec.arms) arms[name] = regex(new RegExp(source, flags));
6858
- return classifiedTrivia(arms);
6808
+ function hasSemanticReduction(roots, externalRefs) {
6809
+ const seen = /* @__PURE__ */ new Set();
6810
+ const visit = (parser2) => {
6811
+ if (seen.has(parser2)) return false;
6812
+ seen.add(parser2);
6813
+ const def = parser2._def;
6814
+ if (def.tag === "transform" && !def.recognitionOnly) return true;
6815
+ if (def.tag === "choice" && def.gates.some(Boolean)) return true;
6816
+ if (def.tag === "guard" || def.tag === "withCtx") return true;
6817
+ if (def.tag === "node" && def.build !== void 0) return true;
6818
+ if (def.tag === "lazy") {
6819
+ if (externalRefs?.has(parser2)) return false;
6820
+ try {
6821
+ return visit(def.thunk());
6822
+ } catch {
6823
+ return true;
6824
+ }
6825
+ }
6826
+ return childrenOf2(def).some(visit);
6827
+ };
6828
+ return roots.some(visit);
6859
6829
  }
6860
- function resolveTable(prog) {
6861
- const owner = prog[TABLE_RUNTIME];
6862
- const hit = owner?.resolved;
6863
- if (hit !== void 0) return hit;
6864
- const cc = prog.cc.map(resolveClass);
6865
- const triviaBuilt = (prog.triviaSpecs ?? []).map(buildTrivia);
6866
- const lex = (prog.lex ?? []).map((spec) => buildLexBodyRecognizer(spec, prog.k));
6867
- const built = {
6868
- prog,
6869
- code: Int32Array.from(prog.code),
6870
- k: prog.k,
6871
- fns: prog.fns,
6872
- cc,
6873
- fx: prog.fx,
6874
- disp: prog.disp.map((d) => resolveDispatch(d, cc)),
6875
- trivia: triviaBuilt,
6876
- triviaScan: triviaBuilt.map(fastTriviaScanner),
6877
- triviaLabelled: triviaBuilt.map((t) => t._meta.triviaKindLabels !== void 0),
6878
- dsp: prog.dsp.map((d) => ({
6879
- byKey: new Map(d.key.map((x, i) => [x, d.keyArm[i]])),
6880
- byFold: new Map(d.fold.map((x, i) => [x, d.foldArm[i]])),
6881
- match: d.match,
6882
- routed: d.routed,
6883
- expected: d.expected
6884
- })),
6885
- lex,
6886
- lexPrograms: (prog.lexPrograms ?? []).map((spec) => buildLexProgramRunner(spec, lex, cc, prog.fx)),
6887
- rules: prog.rules
6830
+ function classifyRuleMap(ruleMap) {
6831
+ const externalRefs = /* @__PURE__ */ new Set();
6832
+ const scanned = /* @__PURE__ */ new Set();
6833
+ const scanExternal = (p) => {
6834
+ if (scanned.has(p)) return;
6835
+ scanned.add(p);
6836
+ const def = p._def;
6837
+ if (def.tag === "lazy") {
6838
+ let resolved;
6839
+ try {
6840
+ resolved = def.thunk();
6841
+ } catch {
6842
+ resolved = void 0;
6843
+ }
6844
+ if (resolved === void 0) {
6845
+ if (p._ruleName) externalRefs.add(p);
6846
+ return;
6847
+ }
6848
+ scanExternal(resolved);
6849
+ return;
6850
+ }
6851
+ for (const child of childrenOf2(def)) scanExternal(child);
6852
+ };
6853
+ for (const [, rule] of ruleMap) scanExternal(rule);
6854
+ return {
6855
+ hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
6856
+ isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule), externalRefs)
6888
6857
  };
6889
- if (owner !== void 0) owner.resolved = built;
6890
- return built;
6891
6858
  }
6892
6859
 
6860
+ // src/table/ops.ts
6861
+ var OP_LIT = 1;
6862
+ var OP_RX = 2;
6863
+ var OP_SEQ = 3;
6864
+ var OP_SEQV = 4;
6865
+ var OP_CHOICE = 5;
6866
+ var OP_REP = 6;
6867
+ var OP_REPV = 7;
6868
+ var OP_OPT = 8;
6869
+ var OP_XFORM = 9;
6870
+ var OP_NODE = 10;
6871
+ var OP_RULE = 11;
6872
+ var OP_GATE = 12;
6873
+ var OP_NOT = 13;
6874
+ var OP_PEEK = 14;
6875
+ var OP_LEAF = 15;
6876
+ var OP_EMPTY = 16;
6877
+ var OP_LIT_TRACK = 17;
6878
+ var OP_RX_TRACK = 18;
6879
+ var OP_NODE_TRACK = 19;
6880
+ var OP_SCOPE = 20;
6881
+ var OP_EXPECT = 21;
6882
+ var OP_SEQX = 22;
6883
+ var OP_SCAN = 23;
6884
+ var OP_FIELD = 24;
6885
+ var OP_LIT_CI = 27;
6886
+ var OP_LIT_CI_TRACK = 28;
6887
+ var OP_TOKEN = 29;
6888
+ var OP_SCOPE_CAP = 30;
6889
+ var OP_WITHCTX = 31;
6890
+ var OP_GUARD = 32;
6891
+ var OP_ADJ = 33;
6892
+ var OP_GREEDY = 34;
6893
+ var OP_REJECT = 35;
6894
+ var OP_ARMGATE = 36;
6895
+ var OP_LIVE = 37;
6896
+ var OP_ATTEMPT = 38;
6897
+ var OP_LABEL = 39;
6898
+ var OP_COV = 40;
6899
+ var OP_SCOPE_PLAIN = 41;
6900
+ var OP_LEX_BODY = 42;
6901
+ var OP_LEX_PROGRAM = 43;
6902
+ var OP_SOURCE_LEAF = 44;
6903
+ var OP_DISPATCH = 25;
6904
+ var OP_ROUTED = 26;
6905
+ var OP_NAMES = {
6906
+ [OP_LIT]: "LIT",
6907
+ [OP_RX]: "RX",
6908
+ [OP_SEQ]: "SEQ",
6909
+ [OP_SEQV]: "SEQV",
6910
+ [OP_CHOICE]: "CHOICE",
6911
+ [OP_REP]: "REP",
6912
+ [OP_REPV]: "REPV",
6913
+ [OP_OPT]: "OPT",
6914
+ [OP_XFORM]: "XFORM",
6915
+ [OP_NODE]: "NODE",
6916
+ [OP_RULE]: "RULE",
6917
+ [OP_GATE]: "GATE",
6918
+ [OP_NOT]: "NOT",
6919
+ [OP_PEEK]: "PEEK",
6920
+ [OP_LEAF]: "LEAF",
6921
+ [OP_SOURCE_LEAF]: "SOURCE_LEAF",
6922
+ [OP_EMPTY]: "EMPTY",
6923
+ [OP_LIT_TRACK]: "LIT_TRACK",
6924
+ [OP_RX_TRACK]: "RX_TRACK",
6925
+ [OP_NODE_TRACK]: "NODE_TRACK",
6926
+ [OP_SCOPE]: "SCOPE",
6927
+ [OP_EXPECT]: "EXPECT",
6928
+ [OP_SEQX]: "SEQX",
6929
+ [OP_SCAN]: "SCAN",
6930
+ [OP_FIELD]: "FIELD",
6931
+ [OP_LIT_CI]: "LIT_CI",
6932
+ [OP_LIT_CI_TRACK]: "LIT_CI_TRACK",
6933
+ [OP_DISPATCH]: "DISPATCH",
6934
+ [OP_ROUTED]: "ROUTED",
6935
+ [OP_TOKEN]: "TOKEN",
6936
+ [OP_SCOPE_CAP]: "SCOPE_CAP",
6937
+ [OP_WITHCTX]: "WITHCTX",
6938
+ [OP_GUARD]: "GUARD",
6939
+ [OP_ADJ]: "ADJ",
6940
+ [OP_GREEDY]: "GREEDY",
6941
+ [OP_REJECT]: "REJECT",
6942
+ [OP_ARMGATE]: "ARMGATE",
6943
+ [OP_LIVE]: "LIVE",
6944
+ [OP_ATTEMPT]: "ATTEMPT",
6945
+ [OP_LABEL]: "LABEL",
6946
+ [OP_COV]: "COV",
6947
+ [OP_SCOPE_PLAIN]: "SCOPE_PLAIN",
6948
+ [OP_LEX_BODY]: "LEX_BODY",
6949
+ [OP_LEX_PROGRAM]: "LEX_PROGRAM"
6950
+ };
6951
+
6893
6952
  // src/compiler/direct-projection.ts
6894
6953
  function directArrayProjection(fn, fnSrc, arity) {
6895
6954
  let source = fnSrc;
@@ -7770,6 +7829,7 @@ var Encoder = class {
7770
7829
  case "expect":
7771
7830
  case "recover":
7772
7831
  case "scanTo":
7832
+ case "lines":
7773
7833
  case "unknown":
7774
7834
  return true;
7775
7835
  case "lazy": {
@@ -7990,6 +8050,18 @@ var Encoder = class {
7990
8050
  open: bal.open,
7991
8051
  close: bal.close
7992
8052
  }));
8053
+ if (d.tag === "lines") {
8054
+ return this.emit(OP_SCAN, this.scanSlot({
8055
+ kind: 2,
8056
+ flags: 0,
8057
+ skip: [],
8058
+ prefix: this.subtree(d.prefix),
8059
+ body: this.subtree(d.body),
8060
+ ...d.first === void 0 ? {} : { first: this.subtree(d.first) },
8061
+ ...d.lazy === void 0 ? {} : { lazy: this.subtree(d.lazy) }
8062
+ }));
8063
+ }
8064
+ if (d.tag === "columns") return this.emit(OP_SCAN, this.scanSlot({ kind: 3, flags: 0, skip: [], n: d.n }));
7993
8065
  if (d.tag === "scanTo") {
7994
8066
  const sentDef = d.sentinel._def;
7995
8067
  return this.emit(OP_SCAN, this.scanSlot({
@@ -8436,12 +8508,7 @@ var Encoder = class {
8436
8508
  };
8437
8509
  const seen = /* @__PURE__ */ new Set();
8438
8510
  const refs = [];
8439
- for (const s of this.scans) {
8440
- if (s.sentinel !== void 0) refs.push(s.sentinel);
8441
- if (s.recoverAt !== void 0) refs.push(s.recoverAt);
8442
- if (s.stopAt !== void 0) refs.push(s.stopAt);
8443
- refs.push(...s.skip);
8444
- }
8511
+ for (const s of this.scans) refs.push(...scanRefs(s));
8445
8512
  for (const set of this.scanSkipSets) refs.push(...set);
8446
8513
  const stack = [...Object.values(this.rules), ...refs.map((r) => r[0])];
8447
8514
  while (stack.length > 0) {
@@ -8461,7 +8528,11 @@ var Encoder = class {
8461
8528
  skip: s.skip.map(res),
8462
8529
  ...s.sentinel === void 0 ? {} : { sentinel: res(s.sentinel) },
8463
8530
  ...s.recoverAt === void 0 ? {} : { recoverAt: res(s.recoverAt) },
8464
- ...s.stopAt === void 0 ? {} : { stopAt: res(s.stopAt) }
8531
+ ...s.stopAt === void 0 ? {} : { stopAt: res(s.stopAt) },
8532
+ ...s.prefix === void 0 ? {} : { prefix: res(s.prefix) },
8533
+ ...s.body === void 0 ? {} : { body: res(s.body) },
8534
+ ...s.first === void 0 ? {} : { first: res(s.first) },
8535
+ ...s.lazy === void 0 ? {} : { lazy: res(s.lazy) }
8465
8536
  }));
8466
8537
  this.scanSkipSets = this.scanSkipSets.map((set) => set.map(res));
8467
8538
  }
@@ -9306,7 +9377,7 @@ function emitShapeMatch(shape, start, mint, ind, firstChar) {
9306
9377
  if (shape.kind === "alt") {
9307
9378
  const okV = mint("_ok");
9308
9379
  const endV2 = mint("_end");
9309
- const lines = [`${ind}let ${okV} = false`, `${ind}let ${endV2} = ${start}`];
9380
+ const lines2 = [`${ind}let ${okV} = false`, `${ind}let ${endV2} = ${start}`];
9310
9381
  if (shape.arms.length >= 2 && shape.arms.every((a) => a.kind === "litFold" && a.open.length >= 1)) {
9311
9382
  const groups = /* @__PURE__ */ new Map();
9312
9383
  for (const a of shape.arms) {
@@ -9315,60 +9386,60 @@ function emitShapeMatch(shape, start, mint, ind, firstChar) {
9315
9386
  const lower = f >= 65 && f <= 90 ? f + 32 : f;
9316
9387
  (groups.get(lower) ?? groups.set(lower, []).get(lower)).push(open);
9317
9388
  }
9318
- lines.push(`${ind}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
9389
+ lines2.push(`${ind}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
9319
9390
  for (const [lower, arms] of groups) {
9320
9391
  const labels = lower >= 97 && lower <= 122 ? `case ${lower}: case ${lower - 32}:` : `case ${lower}:`;
9321
- lines.push(`${ind} ${labels} {`);
9392
+ lines2.push(`${ind} ${labels} {`);
9322
9393
  for (const open of arms) {
9323
9394
  const tail = open.slice(1).map((cp, i) => foldEq(`input.charCodeAt(${start} + ${i + 1})`, cp));
9324
9395
  const cond = tail.length ? ` && ${tail.join(" && ")}` : "";
9325
- lines.push(`${ind} if (${start} + ${open.length} <= input.length${cond}) { ${okV} = true; ${endV2} = ${start} + ${open.length}; break }`);
9396
+ lines2.push(`${ind} if (${start} + ${open.length} <= input.length${cond}) { ${okV} = true; ${endV2} = ${start} + ${open.length}; break }`);
9326
9397
  }
9327
- lines.push(`${ind} break`);
9328
- lines.push(`${ind} }`);
9398
+ lines2.push(`${ind} break`);
9399
+ lines2.push(`${ind} }`);
9329
9400
  }
9330
- lines.push(`${ind}}`);
9331
- return { setup: lines, ok: okV, end: endV2 };
9401
+ lines2.push(`${ind}}`);
9402
+ return { setup: lines2, ok: okV, end: endV2 };
9332
9403
  }
9333
9404
  if (shape.disjoint) {
9334
9405
  const plan = planAltSwitch(shape.firsts);
9335
9406
  if (plan.kind === "switch") {
9336
- lines.push(`${ind}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
9407
+ lines2.push(`${ind}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
9337
9408
  shape.arms.forEach((arm, k) => {
9338
9409
  const labels = plan.cases[k].map((cp) => `case ${cp}:`).join(" ");
9339
9410
  const m = emitShapeMatch(arm, start, mint, `${ind} `, firstChar);
9340
- lines.push(`${ind} ${labels} {`);
9341
- lines.push(...m.setup);
9342
- lines.push(`${ind} ${okV} = ${m.ok}`);
9343
- lines.push(`${ind} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
9344
- lines.push(`${ind} break`);
9345
- lines.push(`${ind} }`);
9411
+ lines2.push(`${ind} ${labels} {`);
9412
+ lines2.push(...m.setup);
9413
+ lines2.push(`${ind} ${okV} = ${m.ok}`);
9414
+ lines2.push(`${ind} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
9415
+ lines2.push(`${ind} break`);
9416
+ lines2.push(`${ind} }`);
9346
9417
  });
9347
- lines.push(`${ind}}`);
9418
+ lines2.push(`${ind}}`);
9348
9419
  } else {
9349
9420
  const c0 = codeAt(start, firstChar);
9350
9421
  shape.arms.forEach((arm, k) => {
9351
9422
  const fs = shape.firsts[k];
9352
9423
  const cond = fs.negated ? `!(${classCond(c0, fs.ranges)})` : `(${classCond(c0, fs.ranges)})`;
9353
9424
  const m = emitShapeMatch(arm, start, mint, `${ind} `, firstChar);
9354
- lines.push(`${ind}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
9355
- lines.push(...m.setup);
9356
- lines.push(`${ind} ${okV} = ${m.ok}`);
9357
- lines.push(`${ind} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
9358
- lines.push(`${ind}}`);
9425
+ lines2.push(`${ind}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
9426
+ lines2.push(...m.setup);
9427
+ lines2.push(`${ind} ${okV} = ${m.ok}`);
9428
+ lines2.push(`${ind} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
9429
+ lines2.push(`${ind}}`);
9359
9430
  });
9360
9431
  }
9361
9432
  } else {
9362
9433
  const lbl = mint("_alt");
9363
- lines.push(`${ind}${lbl}: {`);
9434
+ lines2.push(`${ind}${lbl}: {`);
9364
9435
  for (const arm of shape.arms) {
9365
9436
  const m = emitShapeMatch(arm, start, mint, `${ind} `, firstChar);
9366
- lines.push(...m.setup);
9367
- lines.push(`${ind} if (${m.ok}) { ${okV} = true; ${endV2} = ${m.end}; break ${lbl} }`);
9437
+ lines2.push(...m.setup);
9438
+ lines2.push(`${ind} if (${m.ok}) { ${okV} = true; ${endV2} = ${m.end}; break ${lbl} }`);
9368
9439
  }
9369
- lines.push(`${ind}}`);
9440
+ lines2.push(`${ind}}`);
9370
9441
  }
9371
- return { setup: lines, ok: okV, end: endV2 };
9442
+ return { setup: lines2, ok: okV, end: endV2 };
9372
9443
  }
9373
9444
  if (shape.kind === "chars") {
9374
9445
  const cur = mint("_e");
@@ -9403,64 +9474,64 @@ function emitShapeMatch(shape, start, mint, ind, firstChar) {
9403
9474
  const c = classCond(`input.charCodeAt(${at})`, p.ranges);
9404
9475
  return p.negated ? `!(${c})` : `(${c})`;
9405
9476
  };
9406
- const lines = [`${ind}let ${endV2} = ${start}`, `${ind}let ${okV} = false`, `${ind}do {`];
9477
+ const lines2 = [`${ind}let ${endV2} = ${start}`, `${ind}let ${okV} = false`, `${ind}do {`];
9407
9478
  let first = true;
9408
9479
  for (const p of shape.parts) {
9409
9480
  if (p.part === "lit") {
9410
9481
  const L = p.cps.length;
9411
9482
  const cond = litCond(endV2, p.cps, first ? firstChar : void 0);
9412
9483
  if (p.optional) {
9413
- lines.push(`${ind} if (${endV2} + ${L} <= input.length && (${cond})) ${endV2} += ${L}`);
9484
+ lines2.push(`${ind} if (${endV2} + ${L} <= input.length && (${cond})) ${endV2} += ${L}`);
9414
9485
  } else {
9415
- lines.push(`${ind} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
9416
- lines.push(`${ind} ${endV2} += ${L}`);
9486
+ lines2.push(`${ind} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
9487
+ lines2.push(`${ind} ${endV2} += ${L}`);
9417
9488
  }
9418
9489
  } else if (p.part === "run" && p.max === Infinity) {
9419
9490
  const s = mint("_s");
9420
- lines.push(`${ind} const ${s} = ${endV2}`);
9421
- lines.push(`${ind} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
9422
- if (p.min === 1) lines.push(`${ind} if (${endV2} === ${s}) break`);
9423
- else if (p.min > 1) lines.push(`${ind} if (${endV2} - ${s} < ${p.min}) break`);
9491
+ lines2.push(`${ind} const ${s} = ${endV2}`);
9492
+ lines2.push(`${ind} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
9493
+ if (p.min === 1) lines2.push(`${ind} if (${endV2} === ${s}) break`);
9494
+ else if (p.min > 1) lines2.push(`${ind} if (${endV2} - ${s} < ${p.min}) break`);
9424
9495
  } else if (p.part === "run" && p.max === 1) {
9425
9496
  if (p.min === 1) {
9426
- lines.push(`${ind} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
9427
- lines.push(`${ind} ${endV2}++`);
9497
+ lines2.push(`${ind} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
9498
+ lines2.push(`${ind} ${endV2}++`);
9428
9499
  } else {
9429
- lines.push(`${ind} if (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
9500
+ lines2.push(`${ind} if (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
9430
9501
  }
9431
9502
  } else if (p.part === "run") {
9432
9503
  const s = mint("_s");
9433
- lines.push(`${ind} const ${s} = ${endV2}`);
9434
- lines.push(`${ind} while (${endV2} - ${s} < ${p.max} && ${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
9435
- if (p.min > 0) lines.push(`${ind} if (${endV2} - ${s} < ${p.min}) break`);
9504
+ lines2.push(`${ind} const ${s} = ${endV2}`);
9505
+ lines2.push(`${ind} while (${endV2} - ${s} < ${p.max} && ${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
9506
+ if (p.min > 0) lines2.push(`${ind} if (${endV2} - ${s} < ${p.min}) break`);
9436
9507
  } else {
9437
9508
  const inner = emitShapeMatch(p.inner, endV2, mint, `${ind} `, first ? firstChar : void 0);
9438
9509
  if (!p.unbounded) {
9439
- lines.push(...inner.setup);
9510
+ lines2.push(...inner.setup);
9440
9511
  if (p.min === 1) {
9441
- lines.push(`${ind} if (!(${inner.ok})) break`);
9442
- lines.push(`${ind} ${endV2} = ${inner.end}`);
9512
+ lines2.push(`${ind} if (!(${inner.ok})) break`);
9513
+ lines2.push(`${ind} ${endV2} = ${inner.end}`);
9443
9514
  } else {
9444
- lines.push(`${ind} if (${inner.ok}) ${endV2} = ${inner.end}`);
9515
+ lines2.push(`${ind} if (${inner.ok}) ${endV2} = ${inner.end}`);
9445
9516
  }
9446
9517
  } else {
9447
9518
  const progV = mint("_prog");
9448
- lines.push(`${ind} let ${progV} = false`);
9449
- lines.push(`${ind} while (true) {`);
9450
- lines.push(...inner.setup);
9451
- lines.push(`${ind} if (!(${inner.ok})) break`);
9452
- lines.push(`${ind} ${endV2} = ${inner.end}`);
9453
- lines.push(`${ind} ${progV} = true`);
9454
- lines.push(`${ind} }`);
9455
- if (p.min === 1) lines.push(`${ind} if (!${progV}) break`);
9519
+ lines2.push(`${ind} let ${progV} = false`);
9520
+ lines2.push(`${ind} while (true) {`);
9521
+ lines2.push(...inner.setup);
9522
+ lines2.push(`${ind} if (!(${inner.ok})) break`);
9523
+ lines2.push(`${ind} ${endV2} = ${inner.end}`);
9524
+ lines2.push(`${ind} ${progV} = true`);
9525
+ lines2.push(`${ind} }`);
9526
+ if (p.min === 1) lines2.push(`${ind} if (!${progV}) break`);
9456
9527
  }
9457
9528
  }
9458
9529
  first = false;
9459
9530
  }
9460
- lines.push(`${ind} ${okV} = true`);
9461
- lines.push(`${ind}} while (false)`);
9462
- lines.push(`${ind}if (!${okV}) ${endV2} = ${start}`);
9463
- return { setup: lines, ok: okV, end: endV2 };
9531
+ lines2.push(`${ind} ${okV} = true`);
9532
+ lines2.push(`${ind}} while (false)`);
9533
+ lines2.push(`${ind}if (!${okV}) ${endV2} = ${start}`);
9534
+ return { setup: lines2, ok: okV, end: endV2 };
9464
9535
  }
9465
9536
  if (shape.kind === "litFold") {
9466
9537
  const openLen2 = shape.open.length;
@@ -11737,6 +11808,188 @@ finish:_finish
11737
11808
  };
11738
11809
  }
11739
11810
 
11811
+ // src/combinators/lines.ts
11812
+ function lineIndexAt(view, offset) {
11813
+ const lines2 = view.lines;
11814
+ let lo = 0, hi = lines2.length - 1;
11815
+ while (lo < hi) {
11816
+ const mid = lo + hi + 1 >> 1;
11817
+ if (lines2[mid].offset <= offset) lo = mid;
11818
+ else hi = mid - 1;
11819
+ }
11820
+ return lo;
11821
+ }
11822
+ var lineAt = (view, offset) => view.lines[lineIndexAt(view, offset)];
11823
+ function sourceOffset(view, offset) {
11824
+ const line = lineAt(view, offset);
11825
+ return line === void 0 ? offset : line.source + (offset - line.offset);
11826
+ }
11827
+ function columnAt(input, pos, ctx) {
11828
+ const view = ctx.lineView;
11829
+ let start, column, split;
11830
+ const line = view === void 0 ? void 0 : lineAt(view, pos);
11831
+ if (line !== void 0 && pos >= line.offset) {
11832
+ start = line.offset;
11833
+ column = line.column;
11834
+ split = line.split;
11835
+ } else {
11836
+ start = input.lastIndexOf("\n", pos - 1) + 1;
11837
+ column = 0;
11838
+ split = 0;
11839
+ }
11840
+ for (let i = start; i < pos; i++) {
11841
+ if (input.charCodeAt(i) === 9) {
11842
+ const width = 4 - (column - (i === start ? split : 0)) % 4 - (i === start ? split : 0);
11843
+ column += width;
11844
+ } else column++;
11845
+ split = 0;
11846
+ }
11847
+ return { column, split: pos === start ? split : 0 };
11848
+ }
11849
+ function columns(n) {
11850
+ if (!Number.isInteger(n) || n < 0) throw new TypeError(`columns(n) needs a non-negative integer, got ${n}`);
11851
+ const expected = [`columns(${n})`];
11852
+ const meta = {
11853
+ firstSet: n === 0 ? any() : { kind: "ranges", ranges: [{ lo: 9, hi: 9 }, { lo: 32, hi: 32 }] },
11854
+ canMatchNewline: false,
11855
+ isTrivia: false
11856
+ };
11857
+ return {
11858
+ _tag: "columns",
11859
+ _meta: meta,
11860
+ _def: { tag: "columns", n },
11861
+ parse(input, pos, ctx) {
11862
+ const c = ctx;
11863
+ let { column, split } = columnAt(input, pos, c);
11864
+ const goal = column + n;
11865
+ let at = pos;
11866
+ while (column < goal) {
11867
+ const code = input.charCodeAt(at);
11868
+ if (code === 32) {
11869
+ column++;
11870
+ split = 0;
11871
+ at++;
11872
+ continue;
11873
+ }
11874
+ if (code !== 9) return { ok: false, expected, span: { start: at, end: at } };
11875
+ const width = 4 - (column - split) % 4 - split;
11876
+ if (column + width > goal) {
11877
+ c._columnsSplit = { at, split: split + (goal - column) };
11878
+ return { ok: true, value: input.slice(pos, at), span: { start: pos, end: at } };
11879
+ }
11880
+ column += width;
11881
+ split = 0;
11882
+ at++;
11883
+ }
11884
+ return { ok: true, value: input.slice(pos, at), span: { start: pos, end: at } };
11885
+ }
11886
+ };
11887
+ }
11888
+ function lines(prefix, body, options = {}) {
11889
+ const { first, lazy } = options;
11890
+ const opening = first ?? prefix;
11891
+ const meta = {
11892
+ // An opening that matches empty starts a run at any character.
11893
+ firstSet: matchesEmpty(opening) ? any() : opening._meta.firstSet,
11894
+ canMatchNewline: true,
11895
+ isTrivia: false
11896
+ };
11897
+ return {
11898
+ _tag: "lines",
11899
+ _meta: meta,
11900
+ _def: { tag: "lines", prefix, body, ...first === void 0 ? {} : { first }, ...lazy === void 0 ? {} : { lazy } },
11901
+ parse(input, pos, ctx) {
11902
+ const c = ctx;
11903
+ const probe = createDetachedParseContext(false, ctx.state);
11904
+ probe.lineView = c.lineView;
11905
+ const run3 = [];
11906
+ const parts = [];
11907
+ let offset = 0;
11908
+ for (let start = pos; start < input.length || start === pos; ) {
11909
+ let end2 = input.indexOf("\n", start);
11910
+ if (end2 < 0) end2 = input.length;
11911
+ probe._columnsSplit = void 0;
11912
+ const head = (run3.length === 0 ? opening : prefix).parse(input, start, probe);
11913
+ let content;
11914
+ let joined;
11915
+ if (head.ok && head.span.end <= end2) {
11916
+ content = head.span.end;
11917
+ joined = "prefix";
11918
+ } else if (run3.length === 0) {
11919
+ return { ok: false, expected: head.ok ? ["line prefix"] : head.expected, span: { start, end: start } };
11920
+ } else if (lazy !== void 0 && lazy.parse(input, start, probe).ok) {
11921
+ content = start;
11922
+ joined = "lazy";
11923
+ } else break;
11924
+ const mark = probe._columnsSplit;
11925
+ const here = columnAt(input, content, c);
11926
+ const split = joined === "prefix" && mark !== void 0 && mark.at === content ? mark.split : here.split;
11927
+ run3.push({ offset, source: content, end: end2, column: here.column + (split - here.split), split, lazy: joined === "lazy" });
11928
+ const text = input.slice(content, end2);
11929
+ const newline = end2 < input.length;
11930
+ parts.push(newline ? `${text}
11931
+ ` : text);
11932
+ offset += text.length + (newline ? 1 : 0);
11933
+ start = end2 + 1;
11934
+ if (!newline) break;
11935
+ }
11936
+ const view = { lines: run3, parent: c.lineView };
11937
+ const saved = c.lineView, starts = c._lineStarts, lineIndex = c._lineIndex, scanned = c._lineScannedTo, probing = c._probe;
11938
+ const errors = c._errors?.length ?? 0;
11939
+ let cursor = -1;
11940
+ if (probing !== void 0) for (const line2 of run3) {
11941
+ if (probing.offset >= line2.source && probing.offset <= line2.end) {
11942
+ cursor = line2.offset + probing.offset - line2.source;
11943
+ break;
11944
+ }
11945
+ }
11946
+ const inner = cursor < 0 ? void 0 : { offset: cursor, best: null };
11947
+ c.lineView = view;
11948
+ c._lineStarts = void 0;
11949
+ c._lineIndex = void 0;
11950
+ c._lineScannedTo = void 0;
11951
+ c._probe = inner;
11952
+ let result;
11953
+ try {
11954
+ result = body.parse(parts.join(""), 0, ctx);
11955
+ } finally {
11956
+ c.lineView = saved;
11957
+ c._lineStarts = starts;
11958
+ c._lineIndex = lineIndex;
11959
+ c._lineScannedTo = scanned;
11960
+ c._probe = probing;
11961
+ }
11962
+ if (inner?.best) failAt(c, inner.best.expected, sourceOffset(view, inner.best.span.start));
11963
+ if (c._errors !== void 0) for (let i = errors; i < c._errors.length; i++) {
11964
+ const error = c._errors[i];
11965
+ error.span = { start: sourceOffset(view, error.span.start), end: sourceOffset(view, error.span.end) };
11966
+ }
11967
+ if (!result.ok) {
11968
+ const at = sourceOffset(view, result.span.start);
11969
+ return { ...result, span: { start: at, end: at } };
11970
+ }
11971
+ const stop = result.span.end;
11972
+ const index = lineIndexAt(view, stop);
11973
+ const line = run3[index];
11974
+ const end = stop === line.offset && index > 0 ? run3[index - 1].end + 1 : stop - line.offset > line.end - line.source ? line.end + 1 : line.source + (stop - line.offset);
11975
+ recordLineRangeFromContext(c, input, pos, end);
11976
+ return { ok: true, value: result.value, span: { start: pos, end } };
11977
+ }
11978
+ };
11979
+ }
11980
+
11981
+ // src/table/line-scan.ts
11982
+ function rebuildLineScan(s, subtree) {
11983
+ if (s.kind === 2) {
11984
+ return lines(subtree(s.prefix), subtree(s.body), {
11985
+ ...s.first === void 0 ? {} : { first: subtree(s.first) },
11986
+ ...s.lazy === void 0 ? {} : { lazy: subtree(s.lazy) }
11987
+ });
11988
+ }
11989
+ if (s.kind === 3) return columns(s.n);
11990
+ return void 0;
11991
+ }
11992
+
11740
11993
  // src/combinators/scanTo.ts
11741
11994
  function resolveScanSkip(explicitSkip, raw, ctx) {
11742
11995
  if (raw) return [];
@@ -12066,7 +12319,7 @@ function stampRuleMap(prog, d, artifactMetadata = {}, resolved = resolveTable(pr
12066
12319
  ...prog.labels === void 0 ? {} : { triviaKindLabels: prog.labels },
12067
12320
  ...prog.classified === 1 ? { rootTriviaClassified: true } : {}
12068
12321
  };
12069
- const lines = prog.lines === 1;
12322
+ const lines2 = prog.lines === 1;
12070
12323
  const mode = prog.hostMode ?? "ast";
12071
12324
  const elided = mode === "ast" && hasDirectBuilder(prog);
12072
12325
  const coverageDefinitions = prog.cov === void 0 ? void 0 : Object.freeze(covDefinitions(prog).map(Object.freeze));
@@ -12094,7 +12347,7 @@ function stampRuleMap(prog, d, artifactMetadata = {}, resolved = resolveTable(pr
12094
12347
  ctx._fe = -1;
12095
12348
  ctx._fx = EMPTY_FX;
12096
12349
  ctx._fc = false;
12097
- if (lines && ctx._lineStarts === void 0) {
12350
+ if (lines2 && ctx._lineStarts === void 0) {
12098
12351
  ctx._lineStarts = [0];
12099
12352
  ctx._lineScannedTo = 0;
12100
12353
  }
@@ -12102,10 +12355,10 @@ function stampRuleMap(prog, d, artifactMetadata = {}, resolved = resolveTable(pr
12102
12355
  if (end < 0) {
12103
12356
  const fe = ctx._fe;
12104
12357
  const at = fe === void 0 || fe < 0 ? pos : fe;
12105
- const fspan = lines ? spanLines(ctx, at, at) : { start: at, end: at };
12358
+ const fspan = lines2 ? spanLines(ctx, at, at) : { start: at, end: at };
12106
12359
  return committed2(ctx) ? { ok: false, expected: ctx._fx ?? EMPTY_FX, span: fspan, committed: true } : { ok: false, expected: ctx._fx ?? EMPTY_FX, span: fspan };
12107
12360
  }
12108
- return { ok: true, value: d.lastValue(), span: lines ? spanLines(ctx, pos, end) : { start: pos, end } };
12361
+ return { ok: true, value: d.lastValue(), span: lines2 ? spanLines(ctx, pos, end) : { start: pos, end } };
12109
12362
  };
12110
12363
  const ownTrivia = triviaOfRule(prog.rules[name]);
12111
12364
  const meta = ownTrivia === void 0 ? baseMeta : { ...baseMeta, grammarTrivia: ownTrivia };
@@ -12200,12 +12453,7 @@ function assemble(t, prog, cfg) {
12200
12453
  return recognize;
12201
12454
  }
12202
12455
  const labelExtraIps = [];
12203
- for (const s of prog.scans ?? []) {
12204
- for (const r of s.skip) labelExtraIps.push(r[0]);
12205
- if (s.sentinel !== void 0) labelExtraIps.push(s.sentinel[0]);
12206
- if (s.recoverAt !== void 0) labelExtraIps.push(s.recoverAt[0]);
12207
- if (s.stopAt !== void 0) labelExtraIps.push(s.stopAt[0]);
12208
- }
12456
+ for (const s of prog.scans ?? []) for (const r of scanRefs(s)) labelExtraIps.push(r[0]);
12209
12457
  for (const set of prog.scanSkip ?? []) for (const r of set) labelExtraIps.push(r[0]);
12210
12458
  const closureLabels = computeSiteLabels(
12211
12459
  code,
@@ -15016,12 +15264,7 @@ function assemble(t, prog, cfg) {
15016
15264
  let emitReached;
15017
15265
  {
15018
15266
  const extraIps = [];
15019
- for (const s of prog.scans ?? []) {
15020
- for (const r of s.skip) extraIps.push(r[0]);
15021
- if (s.sentinel !== void 0) extraIps.push(s.sentinel[0]);
15022
- if (s.recoverAt !== void 0) extraIps.push(s.recoverAt[0]);
15023
- if (s.stopAt !== void 0) extraIps.push(s.stopAt[0]);
15024
- }
15267
+ for (const s of prog.scans ?? []) for (const r of scanRefs(s)) extraIps.push(r[0]);
15025
15268
  for (const set of prog.scanSkip ?? []) for (const r of set) extraIps.push(r[0]);
15026
15269
  const roots = [...Object.values(prog.rules), ...extraIps];
15027
15270
  for (const ip of reachableSites(code, roots)) {
@@ -15218,6 +15461,11 @@ function assemble(t, prog, cfg) {
15218
15461
  return link(ip);
15219
15462
  }
15220
15463
  for (const s of prog.scans ?? []) {
15464
+ const rebuilt = rebuildLineScan(s, subtreeComb);
15465
+ if (rebuilt !== void 0) {
15466
+ scansArr.push(rebuilt);
15467
+ continue;
15468
+ }
15221
15469
  const skip = s.skip.map((r) => subtreeComb(r));
15222
15470
  const raw = (s.flags & 1) !== 0;
15223
15471
  if (s.kind === 1) {
@@ -15459,6 +15707,11 @@ function emitScanSpec(s) {
15459
15707
  if (s.sent !== void 0) parts.push(`sent:${s.sent === null ? "null" : jsString(s.sent)}`);
15460
15708
  if (s.open !== void 0) parts.push(`open:${jsString(s.open)}`);
15461
15709
  if (s.close !== void 0) parts.push(`close:${jsString(s.close)}`);
15710
+ for (const key of ["prefix", "body", "first", "lazy"]) {
15711
+ const r = s[key];
15712
+ if (r !== void 0) parts.push(`${key}:${emitRef(r)}`);
15713
+ }
15714
+ if (s.n !== void 0) parts.push(`n:${s.n}`);
15462
15715
  return `{${parts.join(",")}}`;
15463
15716
  }
15464
15717
  function emitDispatchSpec(d) {
@@ -15479,12 +15732,7 @@ function emitAssemblies(prog, cfgs) {
15479
15732
  if (cfgs.length === 0) return ["a:[],"];
15480
15733
  const t = resolveTable(prog);
15481
15734
  const extraIps = [];
15482
- for (const s of prog.scans ?? []) {
15483
- for (const r of s.skip) extraIps.push(r[0]);
15484
- if (s.sentinel !== void 0) extraIps.push(s.sentinel[0]);
15485
- if (s.recoverAt !== void 0) extraIps.push(s.recoverAt[0]);
15486
- if (s.stopAt !== void 0) extraIps.push(s.stopAt[0]);
15487
- }
15735
+ for (const s of prog.scans ?? []) for (const r of scanRefs(s)) extraIps.push(r[0]);
15488
15736
  for (const set of prog.scanSkip ?? []) for (const r of set) extraIps.push(r[0]);
15489
15737
  const out = [];
15490
15738
  const seen = /* @__PURE__ */ new Set();
@@ -15636,6 +15884,8 @@ function children(def, winners) {
15636
15884
  ...def.recoverAt === void 0 ? [] : [def.recoverAt],
15637
15885
  ...def.stopAt === void 0 ? [] : [def.stopAt]
15638
15886
  ];
15887
+ case "lines":
15888
+ return [def.prefix, def.body, ...def.first === void 0 ? [] : [def.first], ...def.lazy === void 0 ? [] : [def.lazy]];
15639
15889
  case "lazy": {
15640
15890
  let resolved;
15641
15891
  try {
@@ -15779,6 +16029,10 @@ function payloadKey(p, d) {
15779
16029
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
15780
16030
  case "scanTo":
15781
16031
  return `scanTo\0${d.raw}\0${d.orEOF}\0${d.recoverAt !== void 0}\0${d.stopAt !== void 0}`;
16032
+ case "lines":
16033
+ return `lines\0${d.first !== void 0}\0${d.lazy !== void 0}`;
16034
+ case "columns":
16035
+ return `columns\0${d.n}`;
15782
16036
  case "guard":
15783
16037
  return `guard\0${fnKey(d.predSrc, d.predicate)}`;
15784
16038
  case "adjacency":
@@ -15899,6 +16153,8 @@ function segment(d, index) {
15899
16153
  if (d.recoverAt !== void 0 && index === d.skip.length + 1) return "scanTo.recoverAt";
15900
16154
  return "scanTo.stopAt";
15901
16155
  }
16156
+ case "lines":
16157
+ return ["lines.prefix", "lines.body", d.first === void 0 ? "lines.lazy" : "lines.first", "lines.lazy"][index] ?? "lines";
15902
16158
  default:
15903
16159
  return d.tag;
15904
16160
  }
@@ -16941,60 +17197,60 @@ function findKeywordRegexes(visited) {
16941
17197
  var sitesLine = (sites) => sites.slice(0, 6).map(siteToString).join("\n ") + (sites.length > 6 ? `
16942
17198
  \u2026 and ${sites.length - 6} more` : "");
16943
17199
  function formatDuplicationFindings(report) {
16944
- const lines = [];
17200
+ const lines2 = [];
16945
17201
  for (const f of report.structureLoss) {
16946
- lines.push(`parseman BUG [structure-loss] @ ${siteToString(f.site)}: arm[${f.earlier}] flattens \`${f.nodeType}\` where arm[${f.later}] structures it, on ${firstSetToString(f.on)}`);
16947
- lines.push(` flat: ${f.earlierShape}`);
16948
- lines.push(` rich: ${f.laterShape}`);
16949
- lines.push(` lost: ${f.lostNodeTypes.map((t) => `\`${t}\``).join(", ")}`);
16950
- lines.push(` fix: ${f.suggestion}`);
17202
+ lines2.push(`parseman BUG [structure-loss] @ ${siteToString(f.site)}: arm[${f.earlier}] flattens \`${f.nodeType}\` where arm[${f.later}] structures it, on ${firstSetToString(f.on)}`);
17203
+ lines2.push(` flat: ${f.earlierShape}`);
17204
+ lines2.push(` rich: ${f.laterShape}`);
17205
+ lines2.push(` lost: ${f.lostNodeTypes.map((t) => `\`${t}\``).join(", ")}`);
17206
+ lines2.push(` fix: ${f.suggestion}`);
16951
17207
  }
16952
17208
  for (const f of report.rewrites) {
16953
- lines.push(`parseman ${f.bug ? "BUG" : "rewrite"} [${f.rewrite}${f.sepByVerdict === void 0 ? "" : `/${f.sepByVerdict}`}] @ ${siteToString(f.site)}${f.astNeutral ? "" : " (candidate \u2014 verify AST identity)"}`);
16954
- lines.push(` from: ${f.from}`);
16955
- lines.push(` to: ${f.to}`);
16956
- lines.push(` fix: ${f.suggestion}`);
16957
- if (f.perf !== "") lines.push(` perf: ${f.perf}`);
17209
+ lines2.push(`parseman ${f.bug ? "BUG" : "rewrite"} [${f.rewrite}${f.sepByVerdict === void 0 ? "" : `/${f.sepByVerdict}`}] @ ${siteToString(f.site)}${f.astNeutral ? "" : " (candidate \u2014 verify AST identity)"}`);
17210
+ lines2.push(` from: ${f.from}`);
17211
+ lines2.push(` to: ${f.to}`);
17212
+ lines2.push(` fix: ${f.suggestion}`);
17213
+ if (f.perf !== "") lines2.push(` perf: ${f.perf}`);
16958
17214
  }
16959
17215
  for (const f of report.divergentNodes) {
16960
- lines.push(`parseman divergent-node: \`${f.nodeType}\` is built by ${f.count} different productions sharing ${f.sharedTerms.length} term(s): ${f.sharedTerms.map((t) => `\`${t}\``).join(", ")}`);
16961
- for (const p of f.productions) lines.push(` ${siteToString(p.site)}
17216
+ lines2.push(`parseman divergent-node: \`${f.nodeType}\` is built by ${f.count} different productions sharing ${f.sharedTerms.length} term(s): ${f.sharedTerms.map((t) => `\`${t}\``).join(", ")}`);
17217
+ for (const p of f.productions) lines2.push(` ${siteToString(p.site)}
16962
17218
  ${p.shape}
16963
17219
  unique to it: ${p.distinctTerms.length === 0 ? "(none)" : p.distinctTerms.map((t) => `\`${t}\``).join(", ")}`);
16964
- lines.push(` fix: ${f.suggestion}`);
17220
+ lines2.push(` fix: ${f.suggestion}`);
16965
17221
  }
16966
17222
  for (const f of report.keywordRegexes) {
16967
17223
  const src = f.source.length > 90 ? `${f.source.slice(0, 88)}\u2026` : f.source;
16968
- lines.push(`parseman ${f.bug ? "BUG " : ""}keyword-regex @ ${siteToString(f.site)}: /${src}/${f.flags}${f.vocabulary ? ` [${f.words.length} literal words${f.longestFirst ? "" : ", NOT longest-first"}]` : ""}${f.hazards.length > 0 ? ` [${f.hazards.length} prefix hazard(s)${f.bug ? ", UNRESCUED" : ""}]` : ""}${f.caseFoldRisk ? " [CASE-FOLD RISK]" : ""}`);
16969
- lines.push(` fix: ${f.suggestion}`);
17224
+ lines2.push(`parseman ${f.bug ? "BUG " : ""}keyword-regex @ ${siteToString(f.site)}: /${src}/${f.flags}${f.vocabulary ? ` [${f.words.length} literal words${f.longestFirst ? "" : ", NOT longest-first"}]` : ""}${f.hazards.length > 0 ? ` [${f.hazards.length} prefix hazard(s)${f.bug ? ", UNRESCUED" : ""}]` : ""}${f.caseFoldRisk ? " [CASE-FOLD RISK]" : ""}`);
17225
+ lines2.push(` fix: ${f.suggestion}`);
16970
17226
  }
16971
17227
  for (const f of report.duplicates) {
16972
- lines.push(`parseman duplication: ${f.count}\xD7 identical ${f.size}-node shape (saves ${f.savings} nodes) \u2014 ${f.shape}`);
16973
- lines.push(` at: ${sitesLine(f.sites)}`);
16974
- lines.push(` fix: ${f.suggestion}`);
17228
+ lines2.push(`parseman duplication: ${f.count}\xD7 identical ${f.size}-node shape (saves ${f.savings} nodes) \u2014 ${f.shape}`);
17229
+ lines2.push(` at: ${sitesLine(f.sites)}`);
17230
+ lines2.push(` fix: ${f.suggestion}`);
16975
17231
  }
16976
17232
  for (const f of report.nearDuplicates) {
16977
- lines.push(`parseman near-duplication: ${f.count} clones of a ${f.sharedSize}-node shape differing at ONE slot (saves ${f.savings} nodes) \u2014 ${f.shape}`);
16978
- lines.push(` slot: ${f.slotPath} \u2208 { ${f.variants.join(" | ")} }`);
16979
- lines.push(` at: ${sitesLine(f.sites)}`);
16980
- lines.push(` fix: ${f.suggestion}`);
17233
+ lines2.push(`parseman near-duplication: ${f.count} clones of a ${f.sharedSize}-node shape differing at ONE slot (saves ${f.savings} nodes) \u2014 ${f.shape}`);
17234
+ lines2.push(` slot: ${f.slotPath} \u2208 { ${f.variants.join(" | ")} }`);
17235
+ lines2.push(` at: ${sitesLine(f.sites)}`);
17236
+ lines2.push(` fix: ${f.suggestion}`);
16981
17237
  }
16982
17238
  for (const f of report.regexFragments) {
16983
- lines.push(`parseman regex-fragment: \`${f.fragment}\` (${f.branches} branches) re-spelled in ${f.count} regex() terminals`);
16984
- lines.push(` at: ${sitesLine(f.sites)}`);
16985
- lines.push(` fix: ${f.suggestion}`);
17239
+ lines2.push(`parseman regex-fragment: \`${f.fragment}\` (${f.branches} branches) re-spelled in ${f.count} regex() terminals`);
17240
+ lines2.push(` at: ${sitesLine(f.sites)}`);
17241
+ lines2.push(` fix: ${f.suggestion}`);
16986
17242
  }
16987
17243
  for (const f of report.regexClasses) {
16988
- lines.push(`parseman regex-class${f.drifted ? " DRIFT" : ""}: [${f.canonical}] across ${f.count} regex() terminals${f.bmpCeiling ? " [BMP ceiling]" : ""}`);
16989
- for (const v of f.variants) lines.push(` [${v.source}] \xD7${v.count} ${v.delta}
17244
+ lines2.push(`parseman regex-class${f.drifted ? " DRIFT" : ""}: [${f.canonical}] across ${f.count} regex() terminals${f.bmpCeiling ? " [BMP ceiling]" : ""}`);
17245
+ for (const v of f.variants) lines2.push(` [${v.source}] \xD7${v.count} ${v.delta}
16990
17246
  ${sitesLine(v.sites)}`);
16991
- lines.push(` fix: ${f.suggestion}`);
17247
+ lines2.push(` fix: ${f.suggestion}`);
16992
17248
  }
16993
17249
  for (const f of report.overlaps) {
16994
- lines.push(`parseman overlap @ ${siteToString(f.site)}: arm[${f.a}] \u2229 arm[${f.b}] on ${firstSetToString(f.on)}${f.sharedPrefix === null ? "" : ` \u2014 shared prefix: ${f.sharedPrefix}`}${f.handledByStrategy ? " [sharedPrefix strategy already handles this]" : ""}`);
16995
- lines.push(` fix: ${f.suggestion}`);
17250
+ lines2.push(`parseman overlap @ ${siteToString(f.site)}: arm[${f.a}] \u2229 arm[${f.b}] on ${firstSetToString(f.on)}${f.sharedPrefix === null ? "" : ` \u2014 shared prefix: ${f.sharedPrefix}`}${f.handledByStrategy ? " [sharedPrefix strategy already handles this]" : ""}`);
17251
+ lines2.push(` fix: ${f.suggestion}`);
16996
17252
  }
16997
- return lines;
17253
+ return lines2;
16998
17254
  }
16999
17255
  function duplicationFindingCount(report) {
17000
17256
  return report.duplicates.length + report.nearDuplicates.length + report.regexFragments.length + report.regexClasses.length + report.overlaps.length + report.rewrites.length + report.divergentNodes.length + report.structureLoss.length + report.keywordRegexes.length;
@@ -17028,11 +17284,11 @@ function reportDuplication(opt, analyze) {
17028
17284
  );
17029
17285
  return void 0;
17030
17286
  }
17031
- const lines = formatDuplicationFindings(report);
17032
- if (lines.length > 0) {
17287
+ const lines2 = formatDuplicationFindings(report);
17288
+ if (lines2.length > 0) {
17033
17289
  if (level === "error") throw new Error(`parseman: ${duplicationFindingCount(report)} duplication/overlap finding(s)
17034
- ${lines.join("\n")}`);
17035
- for (const l of lines) console.warn(l);
17290
+ ${lines2.join("\n")}`);
17291
+ for (const l of lines2) console.warn(l);
17036
17292
  }
17037
17293
  return report;
17038
17294
  }
@@ -17379,6 +17635,9 @@ function childrenOf3(def) {
17379
17635
  ];
17380
17636
  case "routed":
17381
17637
  return def.fallback ? [def.fallback] : [];
17638
+ case "lines":
17639
+ return [def.prefix, def.body, ...def.first === void 0 ? [] : [def.first], ...def.lazy === void 0 ? [] : [def.lazy]];
17640
+ case "columns":
17382
17641
  case "lazy":
17383
17642
  case "literal":
17384
17643
  case "regex":
@@ -17705,6 +17964,15 @@ ${outerDecls.map((d) => " " + d).join("\n")}
17705
17964
  return `expect(${kid(def.parser)}${def.label !== void 0 ? `, ${JSON.stringify(def.label)}` : ""})`;
17706
17965
  case "scanTo":
17707
17966
  return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}]${def.raw ? ", raw: true" : ""}, orEOF: ${def.orEOF}${def.recoverAt === void 0 ? "" : `, recoverAt: ${kid(def.recoverAt)}`}${def.stopAt === void 0 ? "" : `, stopAt: ${kid(def.stopAt)}`} })`;
17967
+ case "lines": {
17968
+ const options = [
17969
+ ...def.first === void 0 ? [] : [`first: ${kid(def.first)}`],
17970
+ ...def.lazy === void 0 ? [] : [`lazy: ${kid(def.lazy)}`]
17971
+ ];
17972
+ return `lines(${kid(def.prefix)}, ${kid(def.body)}${options.length === 0 ? "" : `, { ${options.join(", ")} }`})`;
17973
+ }
17974
+ case "columns":
17975
+ return `columns(${def.n})`;
17708
17976
  case "transform": {
17709
17977
  if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
17710
17978
  return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)}${def.recognitionOnly ? ", true" : ""})`;
@@ -17769,7 +18037,7 @@ function fieldOptionsSrc(def) {
17769
18037
  }
17770
18038
 
17771
18039
  // src/version.ts
17772
- var PARSEMAN_VERSION = "0.55.4";
18040
+ var PARSEMAN_VERSION = "0.56.0";
17773
18041
 
17774
18042
  // src/compiler/compile-linkable-table.ts
17775
18043
  function isLocal(rule) {
@@ -18060,26 +18328,26 @@ function examinedNothing(d) {
18060
18328
  }
18061
18329
  function formatGrammarDiagnosis(d) {
18062
18330
  const s = d.summary;
18063
- const lines = [];
18064
- lines.push(
18331
+ const lines2 = [];
18332
+ lines2.push(
18065
18333
  examinedNothing(d) ? `parseman: COULD NOT ANALYSE \u2014 0 choice(s) examined; ${s.unanalysable} rule(s) unreadable. No verdict about this grammar is available, good or bad.` : d.ok ? `parseman: grammar OK \u2014 ${s.gated}/${s.totalChoices} choice(s) gate on first char${s.recoverable > 0 ? `, ${s.recoverable} recoverable` : ""}${s.accepted > 0 ? `, ${s.accepted} accepted` : ""}${s.deferred > 0 ? `, ${s.deferred} deferred to the fusing artifact` : ""}.` : `parseman: grammar NOT OK \u2014 ${blockingOf(d).length - s.unanalysable} blocking finding(s) over ${s.totalChoices} examined choice(s).`
18066
18334
  );
18067
18335
  if (s.unanalysable > 0 && !examinedNothing(d)) {
18068
- lines.push(
18336
+ lines2.push(
18069
18337
  ` ${s.unanalysable} rule(s) UNANALYSABLE \u2014 THIS REPORT IS PARTIAL. An empty finding list below does NOT mean the grammar is clean.`
18070
18338
  );
18071
18339
  }
18072
18340
  for (const f of d.findings) {
18073
- lines.push(`${f.severity === "blocking" ? "\u2717" : "\xB7"} [${f.code}] ${f.id}: ${f.message}`);
18074
- for (const detail of f.details) lines.push(` ${detail.replace(/\n/g, "\n ")}`);
18341
+ lines2.push(`${f.severity === "blocking" ? "\u2717" : "\xB7"} [${f.code}] ${f.id}: ${f.message}`);
18342
+ for (const detail of f.details) lines2.push(` ${detail.replace(/\n/g, "\n ")}`);
18075
18343
  if (f.acceptKey !== void 0) {
18076
- lines.push(` intentional? add to the gating snapshot: { accept: ['${f.acceptKey}'] }`);
18344
+ lines2.push(` intentional? add to the gating snapshot: { accept: ['${f.acceptKey}'] }`);
18077
18345
  }
18078
18346
  }
18079
18347
  if (!d.ok && d.acceptSnapshot.length > 0) {
18080
- lines.push(` accept-all snapshot: { accept: [${d.acceptSnapshot.map((i) => `'${i}'`).join(", ")}] }`);
18348
+ lines2.push(` accept-all snapshot: { accept: [${d.acceptSnapshot.map((i) => `'${i}'`).join(", ")}] }`);
18081
18349
  }
18082
- return lines;
18350
+ return lines2;
18083
18351
  }
18084
18352
  function runAnalysis(grammar, opts) {
18085
18353
  if (isCombinator2(grammar)) return analyzeGating(grammar, opts);
@@ -18581,6 +18849,8 @@ function slotLabel(d, index) {
18581
18849
  if (d.recoverAt !== void 0 && index === d.skip.length + 1) return "scanTo.recoverAt";
18582
18850
  return "scanTo.stopAt";
18583
18851
  }
18852
+ case "lines":
18853
+ return ["lines.prefix", "lines.body", d.first === void 0 ? "lines.lazy" : "lines.first", "lines.lazy"][index] ?? "lines";
18584
18854
  case "grammar":
18585
18855
  return index === 0 ? "grammar" : "grammar.trivia";
18586
18856
  case "node":
@@ -20285,6 +20555,13 @@ function coverageEntry(entry, collector, maps, trace) {
20285
20555
  ...def.recoverAt === void 0 ? {} : { recoverAt: build(def.recoverAt) },
20286
20556
  ...def.stopAt === void 0 ? {} : { stopAt: build(def.stopAt) }
20287
20557
  });
20558
+ case "lines":
20559
+ return lines(build(def.prefix), build(def.body), {
20560
+ ...def.first === void 0 ? {} : { first: build(def.first) },
20561
+ ...def.lazy === void 0 ? {} : { lazy: build(def.lazy) }
20562
+ });
20563
+ case "columns":
20564
+ return parser2;
20288
20565
  // Zero-width assertion with no coverage-bearing child; reuse it verbatim so
20289
20566
  // the rebuilt sequence still recognises the boundary marker.
20290
20567
  case "adjacency":
@@ -20452,6 +20729,7 @@ export {
20452
20729
  choice,
20453
20730
  choiceSiteKey,
20454
20731
  classifiedTrivia,
20732
+ columns,
20455
20733
  compile,
20456
20734
  compileLinkableTable,
20457
20735
  compiledGrammarCoverageDefinitions,
@@ -20487,6 +20765,7 @@ export {
20487
20765
  label,
20488
20766
  leaf,
20489
20767
  leftFactorPreview,
20768
+ lines,
20490
20769
  literal,
20491
20770
  makeWhen,
20492
20771
  makeWord,
@@ -20524,6 +20803,7 @@ export {
20524
20803
  shiftAbsolute,
20525
20804
  siteToString,
20526
20805
  sourceLeaf,
20806
+ sourceOffset,
20527
20807
  startsWith,
20528
20808
  token,
20529
20809
  transform,