parseman 0.50.6 → 0.51.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 (59) 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 +214 -67
  4. package/dist/analysis/diagnostics.js.map +2 -2
  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 +220 -68
  8. package/dist/cli/index.js.map +2 -2
  9. package/dist/combinators/dispatch.d.ts.map +1 -1
  10. package/dist/combinators/first-set.d.ts +2 -26
  11. package/dist/combinators/first-set.d.ts.map +1 -1
  12. package/dist/combinators/scanTo.d.ts +23 -1
  13. package/dist/combinators/scanTo.d.ts.map +1 -1
  14. package/dist/compiler/fields.d.ts.map +1 -1
  15. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  16. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  17. package/dist/compiler/token-capability.js +40 -29
  18. package/dist/compiler/token-capability.js.map +1 -1
  19. package/dist/coverage.d.ts.map +1 -1
  20. package/dist/cst/reflection.d.ts.map +1 -1
  21. package/dist/index.js +241 -74
  22. package/dist/index.js.map +2 -2
  23. package/dist/plugin/index.js +228 -71
  24. package/dist/plugin/index.js.map +2 -2
  25. package/dist/spec/index.js +164 -48
  26. package/dist/spec/index.js.map +2 -2
  27. package/dist/table/assemble.d.ts.map +1 -1
  28. package/dist/table/emit.d.ts.map +1 -1
  29. package/dist/table/encode.d.ts.map +1 -1
  30. package/dist/table/exec.d.ts.map +1 -1
  31. package/dist/table/index.js +220 -68
  32. package/dist/table/index.js.map +2 -2
  33. package/dist/table/program.d.ts +2 -0
  34. package/dist/table/program.d.ts.map +1 -1
  35. package/dist/types.d.ts +2 -0
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/package.json +1 -1
  39. package/src/analysis/choice-cost.ts +6 -1
  40. package/src/analysis/commitment.ts +10 -2
  41. package/src/analysis/duplication.ts +7 -2
  42. package/src/analysis/gating.ts +6 -1
  43. package/src/analysis/rebuild.ts +1 -1
  44. package/src/combinators/dispatch.ts +4 -1
  45. package/src/combinators/first-set.ts +58 -36
  46. package/src/combinators/scanTo.ts +163 -9
  47. package/src/compiler/fields.ts +8 -2
  48. package/src/compiler/grammar-coverage-ids.ts +6 -1
  49. package/src/compiler/ir-serialize.ts +7 -2
  50. package/src/compiler/token-alphabet.ts +1 -1
  51. package/src/coverage.ts +7 -1
  52. package/src/cst/reflection.ts +6 -1
  53. package/src/table/assemble.ts +11 -1
  54. package/src/table/emit.ts +4 -0
  55. package/src/table/encode.ts +16 -8
  56. package/src/table/exec.ts +7 -1
  57. package/src/table/program.ts +2 -0
  58. package/src/types.ts +1 -1
  59. package/src/version.ts +1 -1
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // src/combinators/first-set.ts
2
- function classifyFinalChoice(arms, resolve) {
2
+ function classifyFinalChoice(arms, resolve, activeTrivia) {
3
3
  const firstSets = [];
4
4
  const nullable2 = [];
5
5
  let exclusive = true;
6
6
  for (const arm of arms) {
7
7
  const empty2 = matchesEmpty(arm, /* @__PURE__ */ new Set(), resolve);
8
8
  nullable2.push(empty2);
9
- firstSets.push(empty2 ? { kind: "any" } : firstSetOf(arm, /* @__PURE__ */ new Set(), resolve));
9
+ firstSets.push(empty2 ? { kind: "any" } : firstSetOf(arm, /* @__PURE__ */ new Set(), resolve, activeTrivia));
10
10
  if (empty2) exclusive = false;
11
11
  }
12
12
  if (exclusive) {
@@ -67,7 +67,7 @@ function intersects(a, b) {
67
67
  return false;
68
68
  }
69
69
  function fromChar(code) {
70
- return { kind: "ranges", ranges: [{ lo: code, hi: code }] };
70
+ return fromRange(code, code);
71
71
  }
72
72
  function startsFirstSet(combinator, input, pos) {
73
73
  const fs = combinator._meta.firstSet;
@@ -160,11 +160,10 @@ function matchesEmptyBody(p, seen, resolve) {
160
160
  }
161
161
  }
162
162
  function isZeroWidthAssertion(p) {
163
- const tag = p._def.tag;
164
- return tag === "not" || tag === "adjacency";
163
+ return p._tag === "not" || p._tag === "adjacency";
165
164
  }
166
165
  function isPositiveLookahead(p) {
167
- return p._def.tag === "peek";
166
+ return p._tag === "peek";
168
167
  }
169
168
  function narrowBy(acc, constraint) {
170
169
  if (constraint.kind === "any") return acc;
@@ -180,34 +179,42 @@ function narrowBy(acc, constraint) {
180
179
  return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
181
180
  }
182
181
  function applyAssertion(fs, assertion) {
183
- if (assertion === null) return fs;
184
- if (fs.kind === "empty") return assertion;
185
- return narrowBy(fs, assertion) ?? fs;
182
+ return assertion === null ? fs : fs.kind === "empty" ? assertion : narrowBy(fs, assertion);
186
183
  }
187
- function sequenceFirstSet(parsers) {
188
- let fs = empty();
189
- let assertion = null;
190
- for (const p of parsers) {
184
+ function sequenceSet(parsers, fs, empties, trivia2) {
185
+ const afterBoundary = (i) => {
186
+ const rest = at(i);
187
+ return trivia2 ? union(rest, fs(trivia2)) : rest;
188
+ };
189
+ const at = (i) => {
190
+ const p = parsers[i];
191
191
  if (isPositiveLookahead(p)) {
192
- assertion = narrowBy(assertion, p._meta.firstSet);
193
- continue;
192
+ const constraint = fs(p);
193
+ return i + 1 === parsers.length ? constraint : applyAssertion(afterBoundary(i + 1), constraint);
194
194
  }
195
- if (!isZeroWidthAssertion(p)) fs = union(fs, p._meta.firstSet);
196
- if (!matchesEmpty(p)) return applyAssertion(fs, assertion);
197
- }
198
- return applyAssertion(fs, assertion);
195
+ if (isZeroWidthAssertion(p)) {
196
+ return i + 1 === parsers.length ? empty() : afterBoundary(i + 1);
197
+ }
198
+ const first = fs(p);
199
+ if (!empties(p) || i + 1 === parsers.length) return first;
200
+ return union(first, afterBoundary(i + 1));
201
+ };
202
+ return at(0);
203
+ }
204
+ function sequenceFirstSet(parsers) {
205
+ return sequenceSet(parsers, (p) => p._meta.firstSet, matchesEmpty);
199
206
  }
200
- function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve) {
207
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve, activeTrivia) {
201
208
  if (seen.has(p)) return any();
202
209
  seen.add(p);
203
210
  try {
204
- return firstSetBody(p, seen, resolve);
211
+ return firstSetBody(p, seen, resolve, activeTrivia);
205
212
  } finally {
206
213
  seen.delete(p);
207
214
  }
208
215
  }
209
- function firstSetBody(p, seen, resolve) {
210
- const fs = (c) => firstSetOf(c, seen, resolve);
216
+ function firstSetBody(p, seen, resolve, activeTrivia) {
217
+ const fs = (c) => firstSetOf(c, seen, resolve, activeTrivia);
211
218
  const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve);
212
219
  const d = p._def;
213
220
  switch (d.tag) {
@@ -233,17 +240,7 @@ function firstSetBody(p, seen, resolve) {
233
240
  return out;
234
241
  }
235
242
  case "sequence": {
236
- let out = empty();
237
- let assertion = null;
238
- for (const term of d.parsers) {
239
- if (isPositiveLookahead(term)) {
240
- assertion = narrowBy(assertion, fs(term));
241
- continue;
242
- }
243
- if (!isZeroWidthAssertion(term)) out = union(out, fs(term));
244
- if (!empties(term)) return applyAssertion(out, assertion);
245
- }
246
- return applyAssertion(out, assertion);
243
+ return sequenceSet(d.parsers, fs, empties, activeTrivia);
247
244
  }
248
245
  case "peek": {
249
246
  const inner = d.parser;
@@ -258,12 +255,16 @@ function firstSetBody(p, seen, resolve) {
258
255
  case "token":
259
256
  case "leaf":
260
257
  case "node":
261
- case "grammar":
262
- case "expect":
263
- return fs(d.parser);
264
258
  case "sepBy":
259
+ case "expect":
265
260
  return fs(d.parser);
266
- // both min 0 and min 1 start with the item
261
+ case "grammar":
262
+ return firstSetOf(
263
+ d.parser,
264
+ seen,
265
+ resolve,
266
+ d.clearTrivia ? null : d.triviaParser ?? activeTrivia
267
+ );
267
268
  default:
268
269
  return p._meta.firstSet;
269
270
  }
@@ -3313,7 +3314,7 @@ function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
3313
3314
  case "grammar":
3314
3315
  return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false);
3315
3316
  case "scanTo":
3316
- return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen));
3317
+ 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);
3317
3318
  case "recover":
3318
3319
  return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
3319
3320
  case "lazy":
@@ -4341,7 +4342,12 @@ function childrenOf(def) {
4341
4342
  case "sepBy":
4342
4343
  return [def.parser, def.separator];
4343
4344
  case "scanTo":
4344
- return [def.sentinel, ...def.skip];
4345
+ return [
4346
+ def.sentinel,
4347
+ ...def.skip,
4348
+ ...def.recoverAt === void 0 ? [] : [def.recoverAt],
4349
+ ...def.stopAt === void 0 ? [] : [def.stopAt]
4350
+ ];
4345
4351
  case "routed":
4346
4352
  return def.fallback ? [def.fallback] : [];
4347
4353
  case "literal":
@@ -4862,7 +4868,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
4862
4868
  case "grammar":
4863
4869
  return parserHasOwnFields(d.parser, seen) || (d.triviaParser ? parserHasOwnFields(d.triviaParser, seen) : false);
4864
4870
  case "scanTo":
4865
- return parserHasOwnFields(d.sentinel, seen) || d.skip.some((x) => parserHasOwnFields(x, seen));
4871
+ 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);
4866
4872
  case "recover":
4867
4873
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.sentinel, seen);
4868
4874
  case "routed":
@@ -5245,7 +5251,12 @@ function childrenOf2(d) {
5245
5251
  case "recover":
5246
5252
  return [d.parser, d.sentinel];
5247
5253
  case "scanTo":
5248
- return [d.sentinel, ...d.skip];
5254
+ return [
5255
+ d.sentinel,
5256
+ ...d.skip,
5257
+ ...d.recoverAt === void 0 ? [] : [d.recoverAt],
5258
+ ...d.stopAt === void 0 ? [] : [d.stopAt]
5259
+ ];
5249
5260
  case "grammar":
5250
5261
  return d.triviaParser ? [d.parser, d.triviaParser] : [d.parser];
5251
5262
  case "routed":
@@ -5892,7 +5903,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
5892
5903
  case "sepBy":
5893
5904
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
5894
5905
  case "scanTo":
5895
- return hasNodeDef(d.sentinel, seen) || d.skip.some((x) => hasNodeDef(x, seen));
5906
+ 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);
5896
5907
  case "recover":
5897
5908
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.sentinel, seen);
5898
5909
  case "expect":
@@ -5941,7 +5952,7 @@ function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
5941
5952
  case "sepBy":
5942
5953
  return hasDirectBuildDef(d.parser, seen) || hasDirectBuildDef(d.separator, seen);
5943
5954
  case "scanTo":
5944
- return hasDirectBuildDef(d.sentinel, seen) || d.skip.some((x) => hasDirectBuildDef(x, seen));
5955
+ 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);
5945
5956
  case "recover":
5946
5957
  return hasDirectBuildDef(d.parser, seen) || hasDirectBuildDef(d.sentinel, seen);
5947
5958
  case "routed":
@@ -5978,7 +5989,7 @@ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set(), resolve) {
5978
5989
  case "withCtx":
5979
5990
  return mayCommitFailure(d.parser, seen, resolve);
5980
5991
  case "scanTo":
5981
- return mayCommitFailure(d.sentinel, seen, resolve) || d.skip.some((x) => mayCommitFailure(x, seen, resolve));
5992
+ return mayCommitFailure(d.sentinel, seen, resolve) || d.skip.some((x) => mayCommitFailure(x, seen, resolve)) || d.recoverAt !== void 0 && mayCommitFailure(d.recoverAt, seen, resolve) || d.stopAt !== void 0 && mayCommitFailure(d.stopAt, seen, resolve);
5982
5993
  case "lazy": {
5983
5994
  try {
5984
5995
  return mayCommitFailure(d.thunk(), seen, resolve);
@@ -7519,7 +7530,9 @@ var Encoder = class {
7519
7530
  followClass(parsers, i) {
7520
7531
  let fs = { kind: "empty" };
7521
7532
  const rr = this.refResolver();
7522
- for (let j = i + 1; j < parsers.length; j++) fs = union(fs, firstSetOf(parsers[j], /* @__PURE__ */ new Set(), rr));
7533
+ for (let j = i + 1; j < parsers.length; j++) {
7534
+ fs = union(fs, firstSetOf(parsers[j], /* @__PURE__ */ new Set(), rr, this.activeTrivia));
7535
+ }
7523
7536
  return this.charClass(fs);
7524
7537
  }
7525
7538
  constant(v) {
@@ -7736,7 +7749,7 @@ var Encoder = class {
7736
7749
  */
7737
7750
  subtree(c) {
7738
7751
  const ip = this.node(c).ip;
7739
- const fs = firstSetOf(c, /* @__PURE__ */ new Set(), this.refResolver());
7752
+ const fs = firstSetOf(c, /* @__PURE__ */ new Set(), this.refResolver(), this.activeTrivia);
7740
7753
  return [ip, fs.kind === "empty" ? -2 : this.charClass(fs)];
7741
7754
  }
7742
7755
  /** Pool one scan spec. One per combinator: `node()` memoizes by identity. */
@@ -7882,6 +7895,8 @@ var Encoder = class {
7882
7895
  flags: (d.raw ? 1 : 0) | (d.orEOF ? 2 : 0),
7883
7896
  skip: d.skip.map((c) => this.subtree(c)),
7884
7897
  sentinel: this.subtree(d.sentinel),
7898
+ ...d.recoverAt === void 0 ? {} : { recoverAt: this.subtree(d.recoverAt) },
7899
+ ...d.stopAt === void 0 ? {} : { stopAt: this.subtree(d.stopAt) },
7885
7900
  sent: sentDef.tag === "literal" ? sentDef.value : null
7886
7901
  }));
7887
7902
  }
@@ -7955,12 +7970,12 @@ var Encoder = class {
7955
7970
  return this.covWrap(gated, armCovIds?.[src], 1);
7956
7971
  });
7957
7972
  const rr = this.refResolver();
7958
- const finalChoice = classifyFinalChoice(arms, rr);
7973
+ const finalChoice = classifyFinalChoice(arms, rr, this.activeTrivia);
7959
7974
  const transactionalFirst = arms.map((arm, i) => {
7960
7975
  if (!finalChoice.nullable[i]) return void 0;
7961
7976
  const def = arm._def;
7962
7977
  if (def.tag !== "attempt" || matchesEmpty(def.parser, /* @__PURE__ */ new Set(), rr)) return void 0;
7963
- return firstSetOf(def.parser, /* @__PURE__ */ new Set(), rr);
7978
+ return firstSetOf(def.parser, /* @__PURE__ */ new Set(), rr, this.activeTrivia);
7964
7979
  });
7965
7980
  const gateFirst = finalChoice.firstSets.map((first, i) => transactionalFirst[i] ?? first);
7966
7981
  const classes = gateFirst.map((first, i) => finalChoice.nullable[i] && transactionalFirst[i] === void 0 ? -1 : this.charClass(first));
@@ -8007,7 +8022,7 @@ var Encoder = class {
8007
8022
  case "oneOrMore": {
8008
8023
  const child = this.node(d.parser).ip;
8009
8024
  const rr = this.refResolver();
8010
- const itemClass = matchesEmpty(d.parser, /* @__PURE__ */ new Set(), rr) ? -1 : this.existingCharClass(firstSetOf(d.parser, /* @__PURE__ */ new Set(), rr));
8025
+ const itemClass = matchesEmpty(d.parser, /* @__PURE__ */ new Set(), rr) ? -1 : this.existingCharClass(firstSetOf(d.parser, /* @__PURE__ */ new Set(), rr, this.activeTrivia));
8011
8026
  const op = d.valueUnused ? OP_REPV : OP_REP;
8012
8027
  const min = d.tag === "many" ? 0 : d.min;
8013
8028
  return this.rec ? this.emit(op, child, min, d.max ?? -1, -1, 0, this.expected(deriveExpected(d.parser)), itemClass) : this.emit(op, child, min, d.max ?? -1, -1, 0);
@@ -8025,7 +8040,7 @@ var Encoder = class {
8025
8040
  sep,
8026
8041
  flags,
8027
8042
  this.expected(deriveExpected(d.parser)),
8028
- this.charClass(firstSetOf(d.separator, /* @__PURE__ */ new Set(), this.refResolver()))
8043
+ this.charClass(firstSetOf(d.separator, /* @__PURE__ */ new Set(), this.refResolver(), this.activeTrivia))
8029
8044
  );
8030
8045
  }
8031
8046
  return d.min >= 2 ? this.emit(OP_REP, child, d.min, d.max ?? -1, sep, flags, this.expected(deriveExpected(d.parser))) : this.emit(OP_REP, child, d.min, d.max ?? -1, sep, flags);
@@ -8080,7 +8095,7 @@ var Encoder = class {
8080
8095
  d.tags === void 0 ? -1 : this.constant(d.tags)
8081
8096
  );
8082
8097
  if (matchesEmpty(d.parser)) return body;
8083
- const fs = d.parser._def.tag === "lazy" ? firstSetOf(d.parser, /* @__PURE__ */ new Set(), this.refResolver()) : d.parser._meta.firstSet;
8098
+ const fs = d.parser._def.tag === "lazy" ? firstSetOf(d.parser, /* @__PURE__ */ new Set(), this.refResolver(), this.activeTrivia) : d.parser._meta.firstSet;
8084
8099
  if (fs.kind === "any") return body;
8085
8100
  const cls = this.charClass(fs);
8086
8101
  if (cls < 0) return body;
@@ -8194,7 +8209,7 @@ var Encoder = class {
8194
8209
  case "attempt": {
8195
8210
  const inner = this.emit(OP_ATTEMPT, this.node(d.parser).ip);
8196
8211
  if (!this.failureNeedsRollback(d.parser, void 0)) this.failureRollbackCleanSites.add(inner);
8197
- const fs = d.parser._def.tag === "lazy" ? firstSetOf(d.parser, /* @__PURE__ */ new Set(), this.refResolver()) : d.parser._meta.firstSet;
8212
+ const fs = d.parser._def.tag === "lazy" ? firstSetOf(d.parser, /* @__PURE__ */ new Set(), this.refResolver(), this.activeTrivia) : d.parser._meta.firstSet;
8198
8213
  if (fs.kind === "any" || matchesEmpty(d.parser)) return inner;
8199
8214
  const cls = this.charClass(fs);
8200
8215
  if (cls < 0) return inner;
@@ -8315,6 +8330,8 @@ var Encoder = class {
8315
8330
  const refs = [];
8316
8331
  for (const s of this.scans) {
8317
8332
  if (s.sentinel !== void 0) refs.push(s.sentinel);
8333
+ if (s.recoverAt !== void 0) refs.push(s.recoverAt);
8334
+ if (s.stopAt !== void 0) refs.push(s.stopAt);
8318
8335
  refs.push(...s.skip);
8319
8336
  }
8320
8337
  for (const set of this.scanSkipSets) refs.push(...set);
@@ -8334,7 +8351,9 @@ var Encoder = class {
8334
8351
  this.scans = this.scans.map((s) => ({
8335
8352
  ...s,
8336
8353
  skip: s.skip.map(res),
8337
- ...s.sentinel === void 0 ? {} : { sentinel: res(s.sentinel) }
8354
+ ...s.sentinel === void 0 ? {} : { sentinel: res(s.sentinel) },
8355
+ ...s.recoverAt === void 0 ? {} : { recoverAt: res(s.recoverAt) },
8356
+ ...s.stopAt === void 0 ? {} : { stopAt: res(s.stopAt) }
8338
8357
  }));
8339
8358
  this.scanSkipSets = this.scanSkipSets.map((set) => set.map(res));
8340
8359
  }
@@ -11581,7 +11600,7 @@ function resolveScanSkip(explicitSkip, raw, ctx) {
11581
11600
  out.push(...explicitSkip);
11582
11601
  return out;
11583
11602
  }
11584
- function scanTo(sentinel, { skip, raw = false, orEOF = false } = {}) {
11603
+ function scanTo(sentinel, { skip, raw = false, orEOF = false, recoverAt, stopAt } = {}) {
11585
11604
  const meta = {
11586
11605
  firstSet: any(),
11587
11606
  canMatchNewline: true,
@@ -11591,7 +11610,15 @@ function scanTo(sentinel, { skip, raw = false, orEOF = false } = {}) {
11591
11610
  return {
11592
11611
  _tag: "scanTo",
11593
11612
  _meta: meta,
11594
- _def: { tag: "scanTo", sentinel, skip: explicitSkip, raw, orEOF },
11613
+ _def: {
11614
+ tag: "scanTo",
11615
+ sentinel,
11616
+ skip: explicitSkip,
11617
+ raw,
11618
+ orEOF,
11619
+ ...recoverAt === void 0 ? {} : { recoverAt },
11620
+ ...stopAt === void 0 ? {} : { stopAt }
11621
+ },
11595
11622
  parse(input, pos, ctx) {
11596
11623
  let cur = pos;
11597
11624
  const skip2 = resolveScanSkip(explicitSkip, raw, ctx);
@@ -11603,28 +11630,130 @@ function scanTo(sentinel, { skip, raw = false, orEOF = false } = {}) {
11603
11630
  pushCstLeaf(ctx, leaf2);
11604
11631
  }
11605
11632
  };
11606
- while (cur < input.length) {
11607
- const s = sentinel.parse(input, cur, probeCtx);
11608
- if (s.ok) {
11633
+ if (recoverAt === void 0 && stopAt === void 0) {
11634
+ while (cur < input.length) {
11635
+ const s = sentinel.parse(input, cur, probeCtx);
11636
+ if (s.ok) {
11637
+ if (ctx.trackLines) recordLineRangeFromContext(ctx, input, pos, cur);
11638
+ emit(cur);
11639
+ return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
11640
+ }
11641
+ let advanced = false;
11642
+ for (const skipper of skip2) {
11643
+ const r = skipper.parse(input, cur, probeCtx);
11644
+ if (r.ok && r.span.end > cur) {
11645
+ cur = r.span.end;
11646
+ advanced = true;
11647
+ break;
11648
+ }
11649
+ }
11650
+ if (!advanced) cur++;
11651
+ }
11652
+ if (orEOF) {
11609
11653
  if (ctx.trackLines) recordLineRangeFromContext(ctx, input, pos, cur);
11610
11654
  emit(cur);
11611
11655
  return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
11612
11656
  }
11657
+ const sentDef2 = sentinel._def;
11658
+ const expected2 = sentDef2.tag === "literal" ? [JSON.stringify(sentDef2.value)] : ["sentinel"];
11659
+ return { ok: false, expected: expected2, span: { start: pos, end: cur } };
11660
+ }
11661
+ let recovery = -1;
11662
+ let firstSentinel = -1;
11663
+ let sentinelCount = 0;
11664
+ let recoveryErrorCount = -1;
11665
+ let sentinelErrorCount = -1;
11666
+ const succeed = (end) => {
11667
+ if (ctx.trackLines) recordLineRangeFromContext(ctx, input, pos, end);
11668
+ emit(end);
11669
+ return { ok: true, value: input.slice(pos, end), span: { start: pos, end } };
11670
+ };
11671
+ const recoveredEnd = () => {
11672
+ const acceptSentinel = firstSentinel >= 0 && (sentinelCount & 1) === 1;
11673
+ const errors = ctx._errors;
11674
+ if (errors !== void 0) {
11675
+ const keep = acceptSentinel ? sentinelErrorCount : recoveryErrorCount;
11676
+ if (keep >= 0 && errors.length > keep) errors.length = keep;
11677
+ }
11678
+ return acceptSentinel ? firstSentinel : recovery;
11679
+ };
11680
+ while (cur < input.length) {
11681
+ const sentinelProbeErrorCount = ctx._errors?.length ?? -1;
11682
+ const s = sentinel.parse(input, cur, probeCtx);
11683
+ const sentinelProbeErrors = ctx._errors;
11684
+ if (sentinelProbeErrors !== void 0 && sentinelProbeErrorCount >= 0 && sentinelProbeErrors.length > sentinelProbeErrorCount) {
11685
+ sentinelProbeErrors.length = sentinelProbeErrorCount;
11686
+ }
11687
+ if (s.ok) {
11688
+ if (recovery < 0) return succeed(cur);
11689
+ if (s.span.end <= cur) {
11690
+ throw new TypeError("scanTo recovery requires a sentinel that consumes input");
11691
+ }
11692
+ if (firstSentinel < 0) {
11693
+ firstSentinel = cur;
11694
+ sentinelErrorCount = sentinelProbeErrorCount;
11695
+ }
11696
+ sentinelCount++;
11697
+ cur = s.span.end;
11698
+ continue;
11699
+ }
11700
+ if (stopAt !== void 0) {
11701
+ const errorCount = ctx._errors?.length ?? -1;
11702
+ const stop = stopAt.parse(input, cur, probeCtx);
11703
+ const errors = ctx._errors;
11704
+ if (errors !== void 0 && errorCount >= 0 && errors.length > errorCount) {
11705
+ errors.length = errorCount;
11706
+ }
11707
+ if (stop.ok) {
11708
+ if (recovery < 0) {
11709
+ recovery = cur;
11710
+ recoveryErrorCount = errorCount;
11711
+ }
11712
+ return succeed(recoveredEnd());
11713
+ }
11714
+ }
11613
11715
  let advanced = false;
11614
11716
  for (const skipper of skip2) {
11717
+ const errorCount = ctx._errors?.length ?? -1;
11615
11718
  const r = skipper.parse(input, cur, probeCtx);
11616
11719
  if (r.ok && r.span.end > cur) {
11617
11720
  cur = r.span.end;
11618
11721
  advanced = true;
11619
11722
  break;
11620
11723
  }
11724
+ const errors = ctx._errors;
11725
+ if (errors !== void 0 && errorCount >= 0 && errors.length > errorCount) {
11726
+ errors.length = errorCount;
11727
+ }
11621
11728
  }
11622
- if (!advanced) cur++;
11729
+ if (advanced) continue;
11730
+ if (recoverAt !== void 0) {
11731
+ const errorCount = ctx._errors?.length ?? -1;
11732
+ const recover = recoverAt.parse(input, cur, probeCtx);
11733
+ const errors = ctx._errors;
11734
+ if (errors !== void 0 && errorCount >= 0 && errors.length > errorCount) {
11735
+ errors.length = errorCount;
11736
+ }
11737
+ if (recover.ok) {
11738
+ if (recover.span.end <= cur) {
11739
+ throw new TypeError("scanTo recoverAt must consume input");
11740
+ }
11741
+ if (recovery < 0) {
11742
+ recovery = cur;
11743
+ recoveryErrorCount = errorCount;
11744
+ }
11745
+ if (firstSentinel >= 0 && (sentinelCount & 1) === 1) {
11746
+ return succeed(recoveredEnd());
11747
+ }
11748
+ cur = recover.span.end;
11749
+ continue;
11750
+ }
11751
+ }
11752
+ cur++;
11623
11753
  }
11754
+ if (recovery >= 0) return succeed(recoveredEnd());
11624
11755
  if (orEOF) {
11625
- if (ctx.trackLines) recordLineRangeFromContext(ctx, input, pos, cur);
11626
- emit(cur);
11627
- return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
11756
+ return succeed(cur);
11628
11757
  }
11629
11758
  const sentDef = sentinel._def;
11630
11759
  const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
@@ -11925,6 +12054,8 @@ function assemble(t, prog, cfg) {
11925
12054
  for (const s of prog.scans ?? []) {
11926
12055
  for (const r of s.skip) labelExtraIps.push(r[0]);
11927
12056
  if (s.sentinel !== void 0) labelExtraIps.push(s.sentinel[0]);
12057
+ if (s.recoverAt !== void 0) labelExtraIps.push(s.recoverAt[0]);
12058
+ if (s.stopAt !== void 0) labelExtraIps.push(s.stopAt[0]);
11928
12059
  }
11929
12060
  for (const set of prog.scanSkip ?? []) for (const r of set) labelExtraIps.push(r[0]);
11930
12061
  const closureLabels = computeSiteLabels(
@@ -14610,6 +14741,8 @@ function assemble(t, prog, cfg) {
14610
14741
  for (const s of prog.scans ?? []) {
14611
14742
  for (const r of s.skip) extraIps.push(r[0]);
14612
14743
  if (s.sentinel !== void 0) extraIps.push(s.sentinel[0]);
14744
+ if (s.recoverAt !== void 0) extraIps.push(s.recoverAt[0]);
14745
+ if (s.stopAt !== void 0) extraIps.push(s.stopAt[0]);
14613
14746
  }
14614
14747
  for (const set of prog.scanSkip ?? []) for (const r of set) extraIps.push(r[0]);
14615
14748
  const roots = [...Object.values(prog.rules), ...extraIps];
@@ -14821,7 +14954,13 @@ function assemble(t, prog, cfg) {
14821
14954
  continue;
14822
14955
  }
14823
14956
  const sentDef = typeof s.sent === "string" ? { tag: "literal", value: s.sent, caseInsensitive: false } : void 0;
14824
- scansArr.push(scanTo(subtreeComb(s.sentinel, sentDef), { skip, raw, orEOF: (s.flags & 2) !== 0 }));
14957
+ scansArr.push(scanTo(subtreeComb(s.sentinel, sentDef), {
14958
+ skip,
14959
+ raw,
14960
+ orEOF: (s.flags & 2) !== 0,
14961
+ ...s.recoverAt === void 0 ? {} : { recoverAt: subtreeComb(s.recoverAt) },
14962
+ ...s.stopAt === void 0 ? {} : { stopAt: subtreeComb(s.stopAt) }
14963
+ }));
14825
14964
  }
14826
14965
  const scans = scansArr;
14827
14966
  const scanSkip = (prog.scanSkip ?? []).map((set) => set.map((r) => subtreeComb(r)));
@@ -15038,6 +15177,8 @@ function emitRef(r) {
15038
15177
  function emitScanSpec(s) {
15039
15178
  const parts = [`kind:${s.kind}`, `flags:${s.flags}`, `skip:[${s.skip.map(emitRef).join(",")}]`];
15040
15179
  if (s.sentinel !== void 0) parts.push(`sentinel:${emitRef(s.sentinel)}`);
15180
+ if (s.recoverAt !== void 0) parts.push(`recoverAt:${emitRef(s.recoverAt)}`);
15181
+ if (s.stopAt !== void 0) parts.push(`stopAt:${emitRef(s.stopAt)}`);
15041
15182
  if (s.sent !== void 0) parts.push(`sent:${s.sent === null ? "null" : jsString(s.sent)}`);
15042
15183
  if (s.open !== void 0) parts.push(`open:${jsString(s.open)}`);
15043
15184
  if (s.close !== void 0) parts.push(`close:${jsString(s.close)}`);
@@ -15064,6 +15205,8 @@ function emitAssemblies(prog, cfgs) {
15064
15205
  for (const s of prog.scans ?? []) {
15065
15206
  for (const r of s.skip) extraIps.push(r[0]);
15066
15207
  if (s.sentinel !== void 0) extraIps.push(s.sentinel[0]);
15208
+ if (s.recoverAt !== void 0) extraIps.push(s.recoverAt[0]);
15209
+ if (s.stopAt !== void 0) extraIps.push(s.stopAt[0]);
15067
15210
  }
15068
15211
  for (const set of prog.scanSkip ?? []) for (const r of set) extraIps.push(r[0]);
15069
15212
  const out = [];
@@ -15206,7 +15349,12 @@ function children(def, winners) {
15206
15349
  case "sepBy":
15207
15350
  return [def.parser, def.separator];
15208
15351
  case "scanTo":
15209
- return [def.sentinel, ...def.skip];
15352
+ return [
15353
+ def.sentinel,
15354
+ ...def.skip,
15355
+ ...def.recoverAt === void 0 ? [] : [def.recoverAt],
15356
+ ...def.stopAt === void 0 ? [] : [def.stopAt]
15357
+ ];
15210
15358
  case "lazy": {
15211
15359
  let resolved;
15212
15360
  try {
@@ -15319,7 +15467,7 @@ function payloadKey(p, d) {
15319
15467
  case "expect":
15320
15468
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
15321
15469
  case "scanTo":
15322
- return `scanTo\0${d.raw}\0${d.orEOF}`;
15470
+ return `scanTo\0${d.raw}\0${d.orEOF}\0${d.recoverAt !== void 0}\0${d.stopAt !== void 0}`;
15323
15471
  case "guard":
15324
15472
  return `guard\0${fnKey(d.predSrc, d.predicate)}`;
15325
15473
  case "adjacency":
@@ -15434,8 +15582,12 @@ function segment(d, index) {
15434
15582
  return index === 0 ? "sepBy.item" : "sepBy.sep";
15435
15583
  case "recover":
15436
15584
  return index === 0 ? "recover.body" : "recover.sentinel";
15437
- case "scanTo":
15438
- return index === 0 ? "scanTo.sentinel" : `scanTo.skip[${index - 1}]`;
15585
+ case "scanTo": {
15586
+ if (index === 0) return "scanTo.sentinel";
15587
+ if (index <= d.skip.length) return `scanTo.skip[${index - 1}]`;
15588
+ if (d.recoverAt !== void 0 && index === d.skip.length + 1) return "scanTo.recoverAt";
15589
+ return "scanTo.stopAt";
15590
+ }
15439
15591
  default:
15440
15592
  return d.tag;
15441
15593
  }
@@ -16711,7 +16863,12 @@ function childrenOf3(def) {
16711
16863
  case "sepBy":
16712
16864
  return [def.parser, def.separator];
16713
16865
  case "scanTo":
16714
- return [def.sentinel, ...def.skip];
16866
+ return [
16867
+ def.sentinel,
16868
+ ...def.skip,
16869
+ ...def.recoverAt === void 0 ? [] : [def.recoverAt],
16870
+ ...def.stopAt === void 0 ? [] : [def.stopAt]
16871
+ ];
16715
16872
  case "routed":
16716
16873
  return def.fallback ? [def.fallback] : [];
16717
16874
  case "lazy":
@@ -17205,7 +17362,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
17205
17362
  case "expect":
17206
17363
  return `expect(${kid(def.parser)}${def.label !== void 0 ? `, ${JSON.stringify(def.label)}` : ""})`;
17207
17364
  case "scanTo":
17208
- return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}]${def.raw ? ", raw: true" : ""}, orEOF: ${def.orEOF} })`;
17365
+ 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)}`} })`;
17209
17366
  case "transform": {
17210
17367
  if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
17211
17368
  return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)}${def.recognitionOnly ? ", true" : ""})`;
@@ -17385,7 +17542,7 @@ function compileRuleMap(ruleMap, opts = {}) {
17385
17542
  }
17386
17543
 
17387
17544
  // src/version.ts
17388
- var PARSEMAN_VERSION = "0.50.6";
17545
+ var PARSEMAN_VERSION = "0.51.0";
17389
17546
 
17390
17547
  // src/compiler/compile-linkable-table.ts
17391
17548
  function isLocal(rule) {
@@ -18451,8 +18608,12 @@ function slotLabel(d, index) {
18451
18608
  return index === 0 ? "sepBy.item" : "sepBy.sep";
18452
18609
  case "recover":
18453
18610
  return index === 0 ? "recover.parser" : "recover.sentinel";
18454
- case "scanTo":
18455
- return index === 0 ? "scanTo.sentinel" : `scanTo.skip[${index - 1}]`;
18611
+ case "scanTo": {
18612
+ if (index === 0) return "scanTo.sentinel";
18613
+ if (index <= d.skip.length) return `scanTo.skip[${index - 1}]`;
18614
+ if (d.recoverAt !== void 0 && index === d.skip.length + 1) return "scanTo.recoverAt";
18615
+ return "scanTo.stopAt";
18616
+ }
18456
18617
  case "grammar":
18457
18618
  return index === 0 ? "grammar" : "grammar.trivia";
18458
18619
  case "node":
@@ -20118,7 +20279,13 @@ function coverageEntry(entry, collector, maps, trace) {
20118
20279
  case "expect":
20119
20280
  return expect(build(def.parser), def.label);
20120
20281
  case "scanTo":
20121
- return scanTo(build(def.sentinel), { skip: def.skip.map(build), orEOF: def.orEOF });
20282
+ return scanTo(build(def.sentinel), {
20283
+ skip: def.skip.map(build),
20284
+ raw: def.raw,
20285
+ orEOF: def.orEOF,
20286
+ ...def.recoverAt === void 0 ? {} : { recoverAt: build(def.recoverAt) },
20287
+ ...def.stopAt === void 0 ? {} : { stopAt: build(def.stopAt) }
20288
+ });
20122
20289
  // Zero-width assertion with no coverage-bearing child; reuse it verbatim so
20123
20290
  // the rebuilt sequence still recognises the boundary marker.
20124
20291
  case "adjacency":