parseman 0.60.2 → 0.60.4

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 (40) hide show
  1. package/dist/analysis/commitment.d.ts +1 -1
  2. package/dist/analysis/commitment.d.ts.map +1 -1
  3. package/dist/analysis/diagnostics.js +128 -43
  4. package/dist/analysis/diagnostics.js.map +2 -2
  5. package/dist/cli/index.js +128 -43
  6. package/dist/cli/index.js.map +2 -2
  7. package/dist/compiler/ir-serialize.d.ts +3 -1
  8. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  9. package/dist/compiler/linker.d.ts +10 -0
  10. package/dist/compiler/linker.d.ts.map +1 -1
  11. package/dist/index.js +155 -57
  12. package/dist/index.js.map +2 -2
  13. package/dist/plugin/evaluator.d.ts +8 -0
  14. package/dist/plugin/evaluator.d.ts.map +1 -1
  15. package/dist/plugin/index.d.ts.map +1 -1
  16. package/dist/plugin/index.js +218 -59
  17. package/dist/plugin/index.js.map +2 -2
  18. package/dist/table/assemble.d.ts +11 -0
  19. package/dist/table/assemble.d.ts.map +1 -1
  20. package/dist/table/emit-assembly.d.ts +1 -0
  21. package/dist/table/emit-assembly.d.ts.map +1 -1
  22. package/dist/table/encode.d.ts.map +1 -1
  23. package/dist/table/exec.d.ts.map +1 -1
  24. package/dist/table/index.js +128 -43
  25. package/dist/table/index.js.map +2 -2
  26. package/dist/table/program.d.ts +9 -0
  27. package/dist/table/program.d.ts.map +1 -1
  28. package/dist/version.d.ts +1 -1
  29. package/package.json +1 -1
  30. package/src/analysis/commitment.ts +29 -15
  31. package/src/compiler/ir-serialize.ts +14 -3
  32. package/src/compiler/linker.ts +28 -12
  33. package/src/plugin/evaluator.ts +11 -0
  34. package/src/plugin/index.ts +29 -11
  35. package/src/table/assemble.ts +35 -10
  36. package/src/table/emit-assembly.ts +41 -11
  37. package/src/table/encode.ts +57 -9
  38. package/src/table/exec.ts +5 -2
  39. package/src/table/program.ts +19 -0
  40. package/src/version.ts +1 -1
@@ -6217,7 +6217,9 @@ function ownTableProgram(prog, resolved, caches = {}) {
6217
6217
  resolved,
6218
6218
  choiceRollbackMasks: caches.choiceRollbackMasks ?? runtime?.choiceRollbackMasks,
6219
6219
  nonCommittingChoiceSites: caches.nonCommittingChoiceSites ?? runtime?.nonCommittingChoiceSites,
6220
- failureRollbackCleanSites: caches.failureRollbackCleanSites ?? runtime?.failureRollbackCleanSites
6220
+ failureRollbackCleanSites: caches.failureRollbackCleanSites ?? runtime?.failureRollbackCleanSites,
6221
+ choiceLogRollbackMasks: caches.choiceLogRollbackMasks ?? runtime?.choiceLogRollbackMasks,
6222
+ failureLogRollbackSites: caches.failureLogRollbackSites ?? runtime?.failureLogRollbackSites
6221
6223
  }
6222
6224
  };
6223
6225
  }
@@ -6230,6 +6232,12 @@ function choiceCannotCommit(prog, ip) {
6230
6232
  function failureRollbackClean(prog, ip) {
6231
6233
  return prog[TABLE_RUNTIME]?.failureRollbackCleanSites?.has(ip) === true;
6232
6234
  }
6235
+ function choiceLogRollbackMask(prog, ip) {
6236
+ return prog[TABLE_RUNTIME]?.choiceLogRollbackMasks?.get(ip) ?? 0;
6237
+ }
6238
+ function failureLogRollback(prog, ip) {
6239
+ return prog[TABLE_RUNTIME]?.failureLogRollbackSites?.has(ip) === true;
6240
+ }
6233
6241
  var WIDE_CLASS_PREFIX_LO = 65535;
6234
6242
  var WIDE_CLASS_PREFIX_HI = 0;
6235
6243
  function validateClassRange(lo, hi) {
@@ -6439,6 +6447,39 @@ function firstSetToString(fs3) {
6439
6447
  if (fs3.kind === "empty") return "(empty)";
6440
6448
  return fs3.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
6441
6449
  }
6450
+ function ruleDependencies(ruleMap) {
6451
+ const nameOf3 = /* @__PURE__ */ new Map();
6452
+ for (const [name, comb] of ruleMap) nameOf3.set(comb, name);
6453
+ const deps = /* @__PURE__ */ new Map();
6454
+ for (const [name, comb] of ruleMap) {
6455
+ const found = /* @__PURE__ */ new Set();
6456
+ const seen = /* @__PURE__ */ new Set();
6457
+ const walk = (p, isRoot) => {
6458
+ const def = p._def;
6459
+ const boundary = def.tag === "lazy" ? nameOf3.get(p) ?? p._ruleName : nameOf3.get(p);
6460
+ if (!isRoot && boundary !== void 0) {
6461
+ found.add(boundary);
6462
+ return;
6463
+ }
6464
+ if (seen.has(p)) return;
6465
+ seen.add(p);
6466
+ if (def.tag === "lazy") {
6467
+ let resolved;
6468
+ try {
6469
+ resolved = def.thunk();
6470
+ } catch {
6471
+ return;
6472
+ }
6473
+ walk(resolved, false);
6474
+ return;
6475
+ }
6476
+ for (const child of childrenOf2(def)) walk(child, false);
6477
+ };
6478
+ walk(comb, true);
6479
+ deps.set(name, [...found]);
6480
+ }
6481
+ return deps;
6482
+ }
6442
6483
 
6443
6484
  // src/analysis/commitment.ts
6444
6485
  function mayFail(p, seen = /* @__PURE__ */ new Set()) {
@@ -6492,7 +6533,7 @@ function mayFail(p, seen = /* @__PURE__ */ new Set()) {
6492
6533
  return true;
6493
6534
  }
6494
6535
  }
6495
- function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set(), triviaActive = true) {
6536
+ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set(), triviaActive = true, throughNodes = false) {
6496
6537
  if (seen.has(p)) return false;
6497
6538
  seen.add(p);
6498
6539
  const d = p._def;
@@ -6517,17 +6558,26 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set(), triviaActiv
6517
6558
  return false;
6518
6559
  // A run's prefixes are probed detached; only its body can capture.
6519
6560
  case "lines":
6520
- return mayLeavePartialCapture(d.body, seen, triviaActive);
6521
- // node() captures into its own private buffers and rolls them back on
6522
- // failure (emitNode restores _ctx.* and never pushes on the !ok path).
6561
+ return mayLeavePartialCapture(d.body, seen, triviaActive, throughNodes);
6562
+ // node() captures its children into a frame of its own and drops it on
6563
+ // failure. The trivia logs (`_triviaLog`, `_rootTriviaLog`) are not part of
6564
+ // that frame: trivia its body scanned before failing stays recorded. With
6565
+ // `throughNodes` (a program that installs trivia somewhere) a node leaves
6566
+ // what its body leaves; without, it leaves nothing.
6523
6567
  case "node":
6524
- return false;
6525
- // choice/firstMatch already roll back each failed arm; on overall failure
6526
- // nothing committed remains.
6568
+ return throughNodes && mayLeavePartialCapture(d.parser, seen, triviaActive, throughNodes);
6569
+ // choice/firstMatch already roll back each failed arm's captures; on overall
6570
+ // failure nothing committed remains. The trivia logs are the exception: a
6571
+ // choice the table lowers as an exclusive dispatch runs its one selected arm
6572
+ // with no mark of its own (`emit-assembly.ts`, `table.exclusive`), so the
6573
+ // rows that arm's trivia scans recorded stay when it fails. With
6574
+ // `throughNodes` a choice leaves what one of its arms leaves, unless
6575
+ // `throughNodes` is the encoder's answer that this choice marks and restores
6576
+ // each arm it enters.
6527
6577
  case "choice":
6528
- return false;
6578
+ return throughNodes !== false && !(typeof throughNodes === "function" && throughNodes(p)) && d.parsers.some((arm) => mayLeavePartialCapture(arm, new Set(seen), triviaActive, throughNodes));
6529
6579
  case "dispatch":
6530
- 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);
6580
+ return capturesLeaf(d.selector, new Set(seen)) || d.cases.some((x) => mayLeavePartialCapture(x.parser, new Set(seen), triviaActive, throughNodes) || capturesLeaf(x.parser, new Set(seen))) || (d.matchers ? d.matchers.some((x) => mayLeavePartialCapture(x.parser, new Set(seen), triviaActive, throughNodes) || capturesLeaf(x.parser, new Set(seen))) : false) || (d.otherwise ? mayLeavePartialCapture(d.otherwise, new Set(seen), triviaActive, throughNodes) || capturesLeaf(d.otherwise, new Set(seen)) : false);
6531
6581
  case "attempt":
6532
6582
  return false;
6533
6583
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6569,7 +6619,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set(), triviaActiv
6569
6619
  const part = parts[i];
6570
6620
  if (i > 0 && triviaActive) captured = true;
6571
6621
  if (captured && mayFail(part)) return true;
6572
- if (mayLeavePartialCapture(part, seen, triviaActive)) return true;
6622
+ if (mayLeavePartialCapture(part, seen, triviaActive, throughNodes)) return true;
6573
6623
  if (capturesLeaf(part) || hasNodeDef(part)) captured = true;
6574
6624
  }
6575
6625
  return false;
@@ -6581,12 +6631,12 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set(), triviaActiv
6581
6631
  case "expect":
6582
6632
  case "withCtx":
6583
6633
  case "grammar":
6584
- return mayLeavePartialCapture(d.parser, seen, triviaActive);
6634
+ return mayLeavePartialCapture(d.parser, seen, triviaActive, throughNodes);
6585
6635
  case "recover":
6586
- return mayLeavePartialCapture(d.parser, seen, triviaActive);
6636
+ return mayLeavePartialCapture(d.parser, seen, triviaActive, throughNodes);
6587
6637
  case "lazy": {
6588
6638
  try {
6589
- return mayLeavePartialCapture(d.thunk(), seen, triviaActive);
6639
+ return mayLeavePartialCapture(d.thunk(), seen, triviaActive, throughNodes);
6590
6640
  } catch {
6591
6641
  return true;
6592
6642
  }
@@ -7589,6 +7639,32 @@ var Encoder = class {
7589
7639
  choiceRollbackMasks = /* @__PURE__ */ new Map();
7590
7640
  nonCommittingChoiceSites = /* @__PURE__ */ new Set();
7591
7641
  failureRollbackCleanSites = /* @__PURE__ */ new Set();
7642
+ /**
7643
+ * THE TRIVIA LOGS A FAILED NODE LEAVES. A node drops its own capture frame when
7644
+ * it fails, but the trivia its body recorded into `_triviaLog`/`_rootTriviaLog`
7645
+ * is not part of that frame. So a child the census above counts as clean can
7646
+ * still leave trivia rows when it reaches a node whose body can fail after a
7647
+ * trivia scan (`mayLeavePartialCapture`'s `throughNodes`). These are the arms
7648
+ * and sites that need only the two logs restored. A program with no trivia of
7649
+ * its own records rows only under trivia its caller supplies, so its emitted
7650
+ * assembly takes these marks only for such a parse (`RunCfg.inheritedTrivia`).
7651
+ */
7652
+ choiceLogRollbackMasks = /* @__PURE__ */ new Map();
7653
+ failureLogRollbackSites = /* @__PURE__ */ new Set();
7654
+ /**
7655
+ * HOW EACH CHOICE WAS LOWERED, for the census above. A choice on the ordered
7656
+ * path whose arms cannot commit a failure restores the logs after every arm
7657
+ * it enters (its full mark, or its own two-log mark), so it leaves no rows.
7658
+ * An exclusive dispatch runs its one arm with no mark, and an ordered choice
7659
+ * returns a committed arm's failure before restoring, so either leaves what an
7660
+ * arm leaves. A choice in neither set, or in both, is taken to leave rows. Both
7661
+ * sets are complete only once every rule is encoded, so the trivia-log census
7662
+ * runs then (`logCensus`).
7663
+ */
7664
+ restoringChoices = /* @__PURE__ */ new Set();
7665
+ leakingChoices = /* @__PURE__ */ new Set();
7666
+ logCensus = [];
7667
+ restoresArms = (choice2) => this.restoringChoices.has(choice2) && !this.leakingChoices.has(choice2);
7592
7668
  /**
7593
7669
  * `reachesSideWriter`'s answers, shared by every `failureNeedsRollback` call
7594
7670
  * for the lifetime of this `Encoder`. Every entry is exact — it depends only on
@@ -8163,11 +8239,19 @@ var Encoder = class {
8163
8239
  * not run repeat recovery, but these rows can still publish through a caller's
8164
8240
  * context.
8165
8241
  */
8166
- failureNeedsRollback(p, autoNot) {
8242
+ failureNeedsRollback(p, autoNot, throughNodes) {
8167
8243
  const refName = p._ruleName;
8168
8244
  const winner = refName === void 0 ? void 0 : this.winners?.[refName];
8169
8245
  const root = winner !== void 0 && !winnerWrapsReference2(winner, p) ? winner : p;
8170
- return mayLeavePartialCapture(root, /* @__PURE__ */ new Set(), true) || this.reachesSideWriter(root) || (autoNot?.length ?? 0) > 0 && capturesLeaf(root);
8246
+ return mayLeavePartialCapture(root, /* @__PURE__ */ new Set(), true, throughNodes) || this.reachesSideWriter(root) || (autoNot?.length ?? 0) > 0 && capturesLeaf(root);
8247
+ }
8248
+ /** The rollback census of an `optional()` or `attempt()` row at `ip` over `child`. */
8249
+ censusTransaction(child, ip) {
8250
+ if (this.failureNeedsRollback(child, void 0, false)) return;
8251
+ this.failureRollbackCleanSites.add(ip);
8252
+ this.logCensus.push(() => {
8253
+ if (this.failureNeedsRollback(child, void 0, this.restoresArms)) this.failureLogRollbackSites.add(ip);
8254
+ });
8171
8255
  }
8172
8256
  /**
8173
8257
  * Whether a field, recovered error, interpreted scanner or unresolvable shape
@@ -8789,13 +8873,11 @@ var Encoder = class {
8789
8873
  }
8790
8874
  const dispIdx = this.disp.length;
8791
8875
  this.disp.push(classes);
8876
+ const exclusive = dispatchIsExclusive(classes, this.cc);
8792
8877
  let rollbackMask = kids.length > 31 ? -1 : 0;
8793
8878
  if (rollbackMask === 0) {
8794
8879
  for (let i = 0; i < arms.length; i++) {
8795
- const src = order[i];
8796
- if (this.failureNeedsRollback(arms[i], d.autoNot[src])) {
8797
- rollbackMask |= 1 << i;
8798
- }
8880
+ if (this.failureNeedsRollback(arms[i], d.autoNot[order[i]], false)) rollbackMask |= 1 << i;
8799
8881
  }
8800
8882
  }
8801
8883
  const head = this.emitHead(OP_CHOICE, 3 + 2 * kids.length);
@@ -8807,9 +8889,19 @@ var Encoder = class {
8807
8889
  this.code[head + 4 + kids.length + i] = this.expected(deriveExpected(arms[i]));
8808
8890
  }
8809
8891
  this.choiceRollbackMasks.set(head, rollbackMask);
8810
- if (arms.every((arm) => !mayCommitFailure(arm, rr, this.commitFailureMemo))) {
8811
- this.nonCommittingChoiceSites.add(head);
8892
+ if (rollbackMask !== -1) {
8893
+ const full = rollbackMask;
8894
+ this.logCensus.push(() => {
8895
+ let logMask = 0;
8896
+ for (let i = 0; i < arms.length; i++) {
8897
+ if ((full & 1 << i) === 0 && this.failureNeedsRollback(arms[i], d.autoNot[order[i]], this.restoresArms)) logMask |= 1 << i;
8898
+ }
8899
+ if (logMask !== 0) this.choiceLogRollbackMasks.set(head, logMask);
8900
+ });
8812
8901
  }
8902
+ const nonCommitting = arms.every((arm) => !mayCommitFailure(arm, rr, this.commitFailureMemo));
8903
+ if (nonCommitting) this.nonCommittingChoiceSites.add(head);
8904
+ (!exclusive && nonCommitting ? this.restoringChoices : this.leakingChoices).add(p);
8813
8905
  return head;
8814
8906
  }
8815
8907
  case "many":
@@ -8845,7 +8937,7 @@ var Encoder = class {
8845
8937
  }
8846
8938
  case "optional": {
8847
8939
  const head = this.emit(OP_OPT, this.lookaheadGate(d.parser, this.node(d.parser).ip));
8848
- if (!this.failureNeedsRollback(d.parser, void 0)) this.failureRollbackCleanSites.add(head);
8940
+ this.censusTransaction(d.parser, head);
8849
8941
  return head;
8850
8942
  }
8851
8943
  case "transform": {
@@ -9016,7 +9108,7 @@ var Encoder = class {
9016
9108
  // lowering was correct only for a choice arm.
9017
9109
  case "attempt": {
9018
9110
  const inner = this.emit(OP_ATTEMPT, this.node(d.parser).ip, d.contain === true ? 1 : 0);
9019
- if (!this.failureNeedsRollback(d.parser, void 0)) this.failureRollbackCleanSites.add(inner);
9111
+ this.censusTransaction(d.parser, inner);
9020
9112
  const fs3 = this.gateFirstSet(d.parser, d.guarded === true, false);
9021
9113
  if (fs3.kind === "any" || matchesEmpty(d.parser, /* @__PURE__ */ new Set(), this.refResolver())) return inner;
9022
9114
  const cls = this.charClass(fs3);
@@ -9180,6 +9272,7 @@ var Encoder = class {
9180
9272
  finish() {
9181
9273
  if (this.code.length === 0) this.emit(OP_EMPTY);
9182
9274
  this.collapseIndirection();
9275
+ for (const census of this.logCensus) census();
9183
9276
  return ownTableProgram({
9184
9277
  code: this.code,
9185
9278
  k: this.k,
@@ -9204,6 +9297,8 @@ var Encoder = class {
9204
9297
  }, void 0, {
9205
9298
  choiceRollbackMasks: this.choiceRollbackMasks,
9206
9299
  failureRollbackCleanSites: this.failureRollbackCleanSites,
9300
+ choiceLogRollbackMasks: this.choiceLogRollbackMasks,
9301
+ failureLogRollbackSites: this.failureLogRollbackSites,
9207
9302
  nonCommittingChoiceSites: this.nonCommittingChoiceSites
9208
9303
  });
9209
9304
  }
@@ -11090,6 +11185,13 @@ if(ctx._errors!==undefined&&ctx._errors.length!==${t}er)ctx._errors.length=${t}e
11090
11185
  if (!s.fd && !s.er) return `if(${t}n)rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,undefined,undefined,${t}lg,${t}rt)`;
11091
11186
  return `if(${t}n){rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,${s.fd ? `${t}fd` : "undefined"},${s.er ? `${t}er` : "undefined"},${t}lg,${t}rt)}`;
11092
11187
  }
11188
+ function emitLogMark(t) {
11189
+ return `const ${t}lg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0,${t}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0`;
11190
+ }
11191
+ function emitLogRollback(t) {
11192
+ return `{const g=ctx._triviaLog;if(g!==undefined)while(g.length>${t}lg)g.pop()}
11193
+ {const r=ctx._rootTriviaLog;if(r!==undefined)while(r.length>${t}rt)r.pop()}`;
11194
+ }
11093
11195
  function emitTerm(callee, dst, t, l, skip, miss) {
11094
11196
  const fast = `const ${t}v=${callee}(input,cur,ctx)
11095
11197
  if(${t}v===FAIL)return FAIL
@@ -11173,6 +11275,7 @@ function emitAssemblySource(t, prog, cfg, extraIps = [], staticBuild = false, se
11173
11275
  const { code, k, fx, disp, dsp, triviaLabelled } = t;
11174
11276
  const swapLegal = !cfg.trackLines;
11175
11277
  const hostCst = cfg.hostCst;
11278
+ const LOGS = (prog.triviaSpecs?.length ?? 0) > 0 || cfg.inheritedTrivia === true;
11176
11279
  const REC2 = prog.rec === 1 && cfg.tolerant;
11177
11280
  if (cfg.coverage) throw new Unemittable("a coverage assembly");
11178
11281
  const roots = [...Object.values(prog.rules), ...extraIps];
@@ -11941,12 +12044,13 @@ return v
11941
12044
  case OP_ATTEMPT: {
11942
12045
  const child = link(code[ip + 1]);
11943
12046
  const clean = !REC2 && failureRollbackClean(prog, ip);
12047
+ const logs = clean && LOGS && failureLogRollback(prog, ip);
11944
12048
  const p = tmp();
11945
12049
  return `${head}
11946
- ${clean ? "" : emitMark(p, L.buf, L.raw, sinks)}
12050
+ ${clean ? logs ? emitLogMark(p) : "" : emitMark(p, L.buf, L.raw, sinks)}
11947
12051
  const v=${child}(input,pos,ctx)
11948
12052
  if(v!==FAIL)return v
11949
- ${clean ? "" : emitRollback(p, L.buf, L.raw, sinks)}
12053
+ ${clean ? logs ? emitLogRollback(p) : "" : emitRollback(p, L.buf, L.raw, sinks)}
11950
12054
  ${code[ip + 2] === 1 ? "ctx._fc=false" : "if(ctx._fc===true)return FAIL"}
11951
12055
  ctx._fe=pos
11952
12056
  return FAIL
@@ -11972,6 +12076,7 @@ return FAIL
11972
12076
  ${lookaheadMiss(code[ip + 1], "EC.e=pos;return null")}${emitMark(p, L.buf, L.raw, sinks)}
11973
12077
  ${cfg.probe ? "const pb=ctx._probe?.best\n" : ""}const v=${child}(input,pos,ctx)
11974
12078
  ${emitRollback(p, L.buf, L.raw, sinks)}
12079
+ ctx._fc=false
11975
12080
  ${cfg.probe ? "if(ctx._probe!==undefined)ctx._probe.best=pb??null\n" : ""}if(v===FAIL){EC.e=pos;return null}
11976
12081
  ctx._fe=pos
11977
12082
  ctx._fx=${xf}
@@ -11996,6 +12101,7 @@ return null
11996
12101
  ${lookaheadMiss(code[ip + 1], `ctx._fe=pos;ctx._fx=${xf};return FAIL`)}${emitMark(p, L.buf, L.raw, sinks)}
11997
12102
  ${cfg.probe ? "const pb=ctx._probe?.best\n" : ""}const v=${child}(input,pos,ctx)
11998
12103
  ${emitRollback(p, L.buf, L.raw, sinks)}
12104
+ ctx._fc=false
11999
12105
  ${cfg.probe ? "if(ctx._probe!==undefined)ctx._probe.best=pb??null\n" : ""}if(v===FAIL){ctx._fe=pos;ctx._fx=${xf};return FAIL}
12000
12106
  EC.e=pos
12001
12107
  return null
@@ -12004,14 +12110,15 @@ return null
12004
12110
  case OP_OPT: {
12005
12111
  const child = link(code[ip + 1]);
12006
12112
  const clean = !REC2 && failureRollbackClean(prog, ip);
12113
+ const logs = clean && LOGS && failureLogRollback(prog, ip);
12007
12114
  const p = tmp();
12008
12115
  return `${head}
12009
- ${lookaheadMiss(code[ip + 1], "EC.e=pos;return null")}${clean ? "" : emitMark(p, L.buf, L.raw, sinks)}
12116
+ ${lookaheadMiss(code[ip + 1], "EC.e=pos;return null")}${clean ? logs ? emitLogMark(p) : "" : emitMark(p, L.buf, L.raw, sinks)}
12010
12117
  ctx._fc=false
12011
12118
  const v=${child}(input,pos,ctx)
12012
12119
  if(v===FAIL){
12013
12120
  if(ctx._fc===true)return FAIL
12014
- ${clean ? "" : emitRollback(p, L.buf, L.raw, sinks)}
12121
+ ${clean ? logs ? emitLogRollback(p) : "" : emitRollback(p, L.buf, L.raw, sinks)}
12015
12122
  EC.e=pos
12016
12123
  return null
12017
12124
  }
@@ -12161,6 +12268,10 @@ return FAIL
12161
12268
  const encodedRollbackMask = choiceRollbackMask(prog, ip) ?? -1;
12162
12269
  const rollbackMask = REC2 ? -1 : encodedRollbackMask;
12163
12270
  const hasRollback = rollbackMask !== 0;
12271
+ const logMask = rollbackMask === -1 || !LOGS ? 0 : choiceLogRollbackMask(prog, ip);
12272
+ const choiceMark = hasRollback ? emitMark(p, L.buf, L.raw, sinks) : "";
12273
+ const armMark = (i) => !hasRollback && (logMask & 1 << i) !== 0 ? `${emitLogMark(p)}
12274
+ ` : "";
12164
12275
  const startFailureExact = arms.every((_arm, i) => {
12165
12276
  const terminal = leadingScalarTerminal(code, code[base + i], 0, false);
12166
12277
  if (terminal < 0) return false;
@@ -12172,7 +12283,7 @@ return FAIL
12172
12283
  if (op2 === OP_LIT) return typeof spec === "string" && spec.length > 0;
12173
12284
  return op2 === OP_RX && spec instanceof RegExp && !regexCanMatchEmpty(spec.source);
12174
12285
  });
12175
- const rollbackFor = (i) => rollbackMask === -1 || (rollbackMask & 1 << i) !== 0 ? emitRollback(p, L.buf, L.raw, sinks) : "";
12286
+ const rollbackFor = (i) => rollbackMask === -1 || (rollbackMask & 1 << i) !== 0 ? emitRollback(p, L.buf, L.raw, sinks) : (logMask & 1 << i) === 0 ? "" : hasRollback ? emitRollback(p, L.buf, L.raw, sinks) : emitLogRollback(p);
12176
12287
  if (choiceCannotCommit(prog, ip) && !startFailureExact) {
12177
12288
  const deepAt = Array.from({ length: n }, () => tmp());
12178
12289
  const deepFx = Array.from({ length: n }, () => tmp());
@@ -12198,7 +12309,7 @@ ${remember2(i, "at", pretest.sequence.expected.map((value, at) => `${decision}==
12198
12309
  }`;
12199
12310
  return `${decision === "" ? "" : `let ${decision}=${pretest?.sequence === void 0 ? -1 : 0}
12200
12311
  `}if(${gate2}){
12201
- ctx._fc=false
12312
+ ${armMark(i)}ctx._fc=false
12202
12313
  {const v=${arm}(input,pos,ctx)
12203
12314
  if(v!==FAIL)return v}
12204
12315
  {const at=ctx._fe??pos
@@ -12217,7 +12328,7 @@ ctx._fe=pos;ctx._fx=acc??${choiceFx}
12217
12328
  return FAIL`;
12218
12329
  return `${head}
12219
12330
  const c=${directAsciiMask ? "input.charCodeAt(pos)" : "lead(input,pos)"}
12220
- ${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ""}
12331
+ ${choiceMark}
12221
12332
  ${deepAt.map((at, i) => `let ${at},${deepFx[i]}`).join("\n")}
12222
12333
  ${maskable ? `if(c<128){
12223
12334
  const bits=${maskName}[${directAsciiMask ? "c" : "c<0?128:c"}]
@@ -12264,7 +12375,7 @@ if(at===best${startFailureExact ? "&&at>pos" : ""})acc=_accSet(${pretest.sequenc
12264
12375
  }`;
12265
12376
  return `${decision === "" ? "" : `let ${decision}=${pretest?.sequence === void 0 ? -1 : 0}
12266
12377
  `}if((bits&${1 << i})!==0${condition}){
12267
- ctx._fc=false
12378
+ ${armMark(i)}ctx._fc=false
12268
12379
  {const v=${arm}(input,pos,ctx)
12269
12380
  if(v!==FAIL)return v}
12270
12381
  ${startFailureExact ? "" : `if(best===pos)acc=${catchName}(${i},prev,acc)
@@ -12295,7 +12406,7 @@ if(at===best${startFailureExact ? "&&at>pos" : ""})acc=_accSet(${pretest.token.e
12295
12406
  }${startFailureExact ? "" : `else if(best===pos)acc=_accSet(${expected[i]},acc)`}`;
12296
12407
  return `${decision === "" ? "" : `let ${decision}=${pretest?.sequence === void 0 ? -1 : 0}
12297
12408
  `}if((${gateRefs[i]}===null||classHas(${gateRefs[i]},c))${condition}){
12298
- ctx._fc=false
12409
+ ${armMark(i)}ctx._fc=false
12299
12410
  {const v=${arm}(input,pos,ctx)
12300
12411
  if(v!==FAIL)return v}
12301
12412
  {const at=ctx._fe??pos
@@ -12307,7 +12418,7 @@ ${rollbackFor(i)}
12307
12418
  }).join("\n");
12308
12419
  return `${head}
12309
12420
  const c=${directAsciiMask ? "input.charCodeAt(pos)" : "lead(input,pos)"}
12310
- ${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ""}
12421
+ ${choiceMark}
12311
12422
  let acc
12312
12423
  let best=pos
12313
12424
  ${maskable ? `if(c<128){
@@ -13387,7 +13498,7 @@ function clearOf(ranges, input, from, to) {
13387
13498
  }
13388
13499
 
13389
13500
  // src/version.ts
13390
- var PARSEMAN_VERSION = "0.60.2";
13501
+ var PARSEMAN_VERSION = "0.60.4";
13391
13502
 
13392
13503
  // src/table/assemble.ts
13393
13504
  import { PlainBuf as PlainBuf2, PlainField, PlainLeaf as PlainLeaf9, PlainNode as PlainNode2, PlainSpan as PlainSpan10 } from "../cst/plain.js";
@@ -13659,11 +13770,11 @@ function linkMatcher(m) {
13659
13770
  }
13660
13771
  var NO_COVERAGE = (_id) => {
13661
13772
  };
13662
- function cfgKeyOf(hostCst, trackLines, tolerant, coverage, probe, hostReadsChildren = true) {
13663
- return (hostCst ? 1 : 0) | (trackLines ? 2 : 0) | (tolerant ? 4 : 0) | (coverage ? 8 : 0) | (probe ? 16 : 0) | (hostReadsChildren ? 0 : 32);
13773
+ function cfgKeyOf(hostCst, trackLines, tolerant, coverage, probe, hostReadsChildren = true, inheritedTrivia = false) {
13774
+ return (hostCst ? 1 : 0) | (trackLines ? 2 : 0) | (tolerant ? 4 : 0) | (coverage ? 8 : 0) | (probe ? 16 : 0) | (hostReadsChildren ? 0 : 32) | (inheritedTrivia ? 64 : 0);
13664
13775
  }
13665
13776
  function cfgKey(c) {
13666
- return cfgKeyOf(c.hostCst, c.trackLines, c.tolerant, c.coverage, c.probe, c.hostReadsChildren);
13777
+ return cfgKeyOf(c.hostCst, c.trackLines, c.tolerant, c.coverage, c.probe, c.hostReadsChildren, c.inheritedTrivia);
13667
13778
  }
13668
13779
  function assertFactoryArity(pre) {
13669
13780
  if (pre.arity === EMITTED_PARAMS.length) return;
@@ -15038,6 +15149,7 @@ function assemble(t, prog, cfg) {
15038
15149
  const probeBest = PROBE ? ctx._probe?.best : void 0;
15039
15150
  const v = child(input, pos, ctx);
15040
15151
  if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot);
15152
+ ctx._fc = false;
15041
15153
  if (PROBE && ctx._probe !== void 0) ctx._probe.best = probeBest ?? null;
15042
15154
  if (v === FAIL) {
15043
15155
  EC.e = pos;
@@ -15083,6 +15195,7 @@ function assemble(t, prog, cfg) {
15083
15195
  const probeBest = PROBE ? ctx._probe?.best : void 0;
15084
15196
  const v = child(input, pos, ctx);
15085
15197
  if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot);
15198
+ ctx._fc = false;
15086
15199
  if (PROBE && ctx._probe !== void 0) ctx._probe.best = probeBest ?? null;
15087
15200
  if (v === FAIL) {
15088
15201
  ctx._fe = pos;
@@ -16941,7 +17054,7 @@ function assemble(t, prog, cfg) {
16941
17054
  if (emitted !== void 0) throw new ServedByBuild();
16942
17055
  if (prog.asm !== void 0) {
16943
17056
  throw new Unemittable(
16944
- `option set ${cfgKey(cfg)} (hostCst=${cfg.hostCst} trackLines=${cfg.trackLines} tolerant=${cfg.tolerant} coverage=${cfg.coverage} probe=${cfg.probe}), which this build did not pre-compile. The Function constructor is NOT used for an artifact that carries assemblies \u2014 see \`EmitOptions.assemblies\` to add this option set to the build`
17057
+ `option set ${cfgKey(cfg)} (hostCst=${cfg.hostCst} trackLines=${cfg.trackLines} tolerant=${cfg.tolerant} coverage=${cfg.coverage} probe=${cfg.probe} inheritedTrivia=${cfg.inheritedTrivia === true}), which this build did not pre-compile. The Function constructor is NOT used for an artifact that carries assemblies \u2014 see \`EmitOptions.assemblies\` to add this option set to the build`
16945
17058
  );
16946
17059
  }
16947
17060
  if (!EMIT_ENABLED) throw new Unemittable("PM_TABLE_EMIT=0 (measurement toggle)");
@@ -17151,7 +17264,9 @@ function countSiteVisits(prog, parse2, fns = prog.fns) {
17151
17264
  var AssemblyCache = class {
17152
17265
  t;
17153
17266
  prog;
17154
- byCfg = Array.from({ length: 64 });
17267
+ byCfg = Array.from({ length: 128 });
17268
+ /** Whether this program installs no trivia of its own (`RunCfg.inheritedTrivia`). */
17269
+ noOwnTrivia;
17155
17270
  /**
17156
17271
  * Monomorphic predicate inline cache. An assembly depends on the predicate
17157
17272
  * FUNCTION and the scalar cfg key, not on the host object that carries it, so
@@ -17164,6 +17279,7 @@ var AssemblyCache = class {
17164
17279
  constructor(prog, resolved = resolveTable(prog)) {
17165
17280
  this.prog = prog;
17166
17281
  this.t = resolved;
17282
+ this.noOwnTrivia = prog.triviaSpecs === void 0 || prog.triviaSpecs.length === 0;
17167
17283
  }
17168
17284
  for(cfg) {
17169
17285
  const key = cfgKey(cfg);
@@ -17227,13 +17343,14 @@ var AssemblyCache = class {
17227
17343
  const tolerant = ctx._tolerant === true;
17228
17344
  const coverage = ctx._grammarCoverage !== void 0;
17229
17345
  const probe = ctx._probe !== void 0;
17230
- const key = cfgKeyOf(hostCst, trackLines, tolerant, coverage, probe, hostReadsChildren);
17346
+ const inheritedTrivia = this.noOwnTrivia && ctx.trivia !== void 0;
17347
+ const key = cfgKeyOf(hostCst, trackLines, tolerant, coverage, probe, hostReadsChildren, inheritedTrivia);
17231
17348
  let cache = this.byCfg;
17232
17349
  if (hostCaptureTrivia !== void 0) {
17233
17350
  if (this.hostPredicate === hostCaptureTrivia) {
17234
17351
  cache = this.hostAssemblies;
17235
17352
  } else {
17236
- cache = Array.from({ length: 64 });
17353
+ cache = Array.from({ length: 128 });
17237
17354
  this.hostPredicate = hostCaptureTrivia;
17238
17355
  this.hostAssemblies = cache;
17239
17356
  }
@@ -17247,7 +17364,8 @@ var AssemblyCache = class {
17247
17364
  trackLines,
17248
17365
  tolerant,
17249
17366
  coverage,
17250
- probe
17367
+ probe,
17368
+ inheritedTrivia
17251
17369
  });
17252
17370
  cache[key] = made;
17253
17371
  return made;
@@ -19387,9 +19505,9 @@ function matcherExpr(entry) {
19387
19505
  return `matches(${new RegExp(entry.value, entry.flags ?? "").toString()})`;
19388
19506
  }
19389
19507
  }
19390
- function serializeRuleMap(ruleMap, scanSkip) {
19508
+ function serializeRuleMap(ruleMap, scanSkip, accessed) {
19391
19509
  try {
19392
- return new Serializer(ruleMap, scanSkip ?? []).run();
19510
+ return new Serializer(ruleMap, scanSkip ?? [], accessed ?? []).run();
19393
19511
  } catch (e) {
19394
19512
  if (e instanceof Unserializable) {
19395
19513
  if (typeof process !== "undefined" && process.env?.PARSEMAN_IR_DEBUG) console.error(`[ir] fallback: ${e.message}`);
@@ -19414,9 +19532,12 @@ var Serializer = class {
19414
19532
  localRuleNames = /* @__PURE__ */ new Map();
19415
19533
  ruleMap;
19416
19534
  scanSkip;
19417
- constructor(ruleMap, scanSkip = []) {
19535
+ accessed;
19536
+ referenced = /* @__PURE__ */ new Set();
19537
+ constructor(ruleMap, scanSkip = [], accessed = []) {
19418
19538
  this.ruleMap = ruleMap;
19419
19539
  this.scanSkip = scanSkip;
19540
+ this.accessed = accessed;
19420
19541
  for (const [name, c] of ruleMap) {
19421
19542
  this.localRuleNames.set(c, name);
19422
19543
  this.ruleValues.add(this.body(c));
@@ -19492,6 +19613,9 @@ var Serializer = class {
19492
19613
  }
19493
19614
  this.decls = innerDecls;
19494
19615
  const body = this.ruleMap.map(([name, c]) => ` ${JSON.stringify(name)}: ${this.wrap(this.body(c), [])}`).join(",\n");
19616
+ for (const name of this.accessed) {
19617
+ if (!this.referenced.has(name)) innerDecls.push(`void g[${JSON.stringify(name)}]`);
19618
+ }
19495
19619
  const factory = innerDecls.length === 0 ? `(g) => ({
19496
19620
  ${body}
19497
19621
  })` : `(g) => {
@@ -19590,7 +19714,10 @@ ${outerDecls.map((d) => " " + d).join("\n")}
19590
19714
  switch (def.tag) {
19591
19715
  case "lazy": {
19592
19716
  const name = this.localRuleNames.get(c) ?? ruleNameOf3(c);
19593
- if (name !== void 0) return `g[${JSON.stringify(name)}]`;
19717
+ if (name !== void 0) {
19718
+ this.referenced.add(name);
19719
+ return `g[${JSON.stringify(name)}]`;
19720
+ }
19594
19721
  const target = lazyTarget(c);
19595
19722
  for (let i = frames.length - 1; i >= 0; i--) {
19596
19723
  const f = frames[i];
@@ -19831,6 +19958,24 @@ function cstBuildHost(typeOrOptions, children3, _fields, span, rawChildren, triv
19831
19958
  }
19832
19959
  cstBuildHost._parsemanCstOutput = true;
19833
19960
  var COMPOSED_PIECES = /* @__PURE__ */ Symbol.for("parseman.composedPieces");
19961
+ function danglingReference(map, accessed = []) {
19962
+ const holes = /* @__PURE__ */ new Set();
19963
+ for (const [name, rule] of Object.entries(map)) {
19964
+ if (rule._def.tag !== "lazy") continue;
19965
+ try {
19966
+ rule._def.thunk();
19967
+ } catch {
19968
+ holes.add(name);
19969
+ }
19970
+ }
19971
+ for (const name of accessed) if (!Object.hasOwn(map, name)) holes.add(name);
19972
+ if (holes.size === 0) return void 0;
19973
+ const missing = [...holes];
19974
+ for (const [name, deps] of ruleDependencies(Object.entries(map).filter(([n]) => !holes.has(n)))) {
19975
+ for (const dep of deps) if (holes.has(dep)) return `rule "${name}" references missing rule "${dep}"`;
19976
+ }
19977
+ return `rule(s) ${missing.map((n) => `"${n}"`).join(", ")} are referenced but defined by no composed grammar`;
19978
+ }
19834
19979
 
19835
19980
  // src/cst/trivia-entries.ts
19836
19981
  var EMPTY_INDICES = Object.freeze([]);
@@ -21443,8 +21588,11 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources, out, opti
21443
21588
  }
21444
21589
  const map = /* @__PURE__ */ new Map();
21445
21590
  for (const key of keys) map.set(key, built[key]);
21591
+ const declared = new Set(keys);
21592
+ externalAccesses.set(map, Object.keys(built).filter((name) => !declared.has(name)));
21446
21593
  return map;
21447
21594
  }
21595
+ var externalAccesses = /* @__PURE__ */ new WeakMap();
21448
21596
  function isDefinableRef2(v) {
21449
21597
  return isCombinator3(v) && typeof v.define === "function" && v._def.tag === "lazy";
21450
21598
  }
@@ -22785,6 +22933,17 @@ function corpusTexts(profile) {
22785
22933
  }
22786
22934
  var PARSEMAN_MODULE = "parseman";
22787
22935
  var DEFAULT_ASSEMBLY_BUDGET = 0.25;
22936
+ var LinkRefusal = class extends Error {
22937
+ };
22938
+ function isResolved(rule) {
22939
+ if (rule._def.tag !== "lazy") return true;
22940
+ try {
22941
+ rule._def.thunk();
22942
+ return true;
22943
+ } catch {
22944
+ return false;
22945
+ }
22946
+ }
22788
22947
  function reasonSuffix(runtimeOnly) {
22789
22948
  return runtimeOnly === void 0 || runtimeOnly.length === 0 ? "" : ` \u2014 ${runtimeOnly.join("; ")}`;
22790
22949
  }
@@ -23937,16 +24096,8 @@ ${names.map((n) => ` - ${n}`).join("\n")}` : " (it is not a static combinator/v
23937
24096
  const carriedMaps = carriedRuleMaps(items);
23938
24097
  if (carriedMaps === null) return null;
23939
24098
  const linked = linkRules(carriedMaps.pieces.flatMap((p) => p.recipes), link);
23940
- const rules2 = Object.entries(linked).filter(([, rule]) => {
23941
- if (rule._def.tag !== "lazy") return true;
23942
- try {
23943
- rule._def.thunk();
23944
- return true;
23945
- } catch {
23946
- return false;
23947
- }
23948
- });
23949
- return { rules: rules2, trackLines: carriedMaps.trackLines };
24099
+ const dangling = danglingReference(linked);
24100
+ return { rules: Object.entries(linked), trackLines: carriedMaps.trackLines, dangling };
23950
24101
  };
23951
24102
  const applyComposingTrivia = (ruleMap, composing) => {
23952
24103
  for (const [, rule] of ruleMap) {
@@ -23958,7 +24109,7 @@ ${names.map((n) => ` - ${n}`).join("\n")}` : " (it is not a static combinator/v
23958
24109
  const entries = [...rm];
23959
24110
  const ns = nsFor(label2);
23960
24111
  const trackLines = entries.some(([, rule]) => rule?._meta?.grammarTrackLines === true);
23961
- const ir = serializeRuleMap(entries, scanSkip);
24112
+ const ir = serializeRuleMap(entries, scanSkip, rm instanceof Map ? externalAccesses.get(rm) : void 0);
23962
24113
  if (ir) return { carried: [{ ns, ir, ...trackLines ? { trackLines: true } : {} }] };
23963
24114
  warn(0, `${label2}: rule map could not be serialized to IR, so it cannot be carried for composition. Re-run with PARSEMAN_IR_DEBUG=1 to print the exact combinator that blocked serialization.`);
23964
24115
  return null;
@@ -24045,6 +24196,7 @@ ${names.map((n) => ` - ${n}`).join("\n")}` : " (it is not a static combinator/v
24045
24196
  trivia: composing,
24046
24197
  ...cHostMode ? { hostMode: cHostMode } : {}
24047
24198
  });
24199
+ if (merged?.dangling !== void 0) throw new Error(`${id}:${lineOf(init.start)} \u2014 compose: ${merged.dangling}`);
24048
24200
  if (merged !== null) {
24049
24201
  collectBuilderImports(merged.rules);
24050
24202
  const refusals = [];
@@ -24181,6 +24333,12 @@ ${names.map((n) => ` - ${n}`).join("\n")}` : " (it is not a static combinator/v
24181
24333
  }
24182
24334
  }
24183
24335
  const leafMerged = mergeRuleMaps([...carriedMaps.pieces.map((p) => p.rules), localEntries]);
24336
+ const accessed = [
24337
+ ...carriedMaps.pieces.flatMap((p) => p.rules).filter(([, rule]) => !isResolved(rule)).map(([name]) => name),
24338
+ ...localRules instanceof Map ? externalAccesses.get(localRules) ?? [] : []
24339
+ ];
24340
+ const dangling = danglingReference(Object.fromEntries(leafMerged), accessed);
24341
+ if (dangling !== void 0) throw new LinkRefusal(`${id}:${lineOf(init.start)} \u2014 composeLeaf: ${dangling}`);
24184
24342
  if (composing) applyComposingTrivia(carriedMaps.pieces.flatMap((p) => p.rules), composing);
24185
24343
  const refusals = [];
24186
24344
  const compiled = compileRuleMap(
@@ -24210,6 +24368,7 @@ ${names.map((n) => ` - ${n}`).join("\n")}` : " (it is not a static combinator/v
24210
24368
  warn(init.start, "composeLeaf(): a recognition piece has no re-lowerable IR");
24211
24369
  return null;
24212
24370
  } catch (e) {
24371
+ if (e instanceof LinkRefusal) throw e;
24213
24372
  warn(init.start, `composeLeaf(): ${e.message}`);
24214
24373
  return null;
24215
24374
  }
@@ -24315,7 +24474,7 @@ ${names.map((n) => ` - ${n}`).join("\n")}` : " (it is not a static combinator/v
24315
24474
  const source = compiledRules.replacement ?? code.slice(init.start, init.end);
24316
24475
  let carriedMetadata;
24317
24476
  if (exportPrefix && pieces) {
24318
- const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
24477
+ const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip, externalAccesses.get(compiledRules.ruleMap));
24319
24478
  if (ir) carriedMetadata = [{ ns, ir, ...compiledRules.trackLines ? { trackLines: true } : {} }];
24320
24479
  else warn(init.start, `${varName}: exported grammar could not be serialized to IR, so it carries no composable pieces \u2014 a downstream compose() of this grammar will fall back to runtime. Re-run with PARSEMAN_IR_DEBUG=1 to print the combinator that blocked serialization.`);
24321
24480
  if (carriedMetadata !== void 0 && sinkCarried(varName, carriedMetadata)) carriedMetadata = void 0;