parseman 0.7.1 → 0.8.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.
package/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export type { LineIndex } from './compiler/line-index.ts';
22
22
  export { guard } from './combinators/guard.ts';
23
23
  export { withCtx } from './combinators/withCtx.ts';
24
24
  export { recover, isParseError } from './combinators/recover.ts';
25
+ export { expect, staticExpected } from './combinators/expect.ts';
25
26
  export { completionsAt } from './combinators/completions.ts';
26
27
  export { scanTo, balanced } from './combinators/scanTo.ts';
27
28
  export type { ScanToOptions } from './combinators/scanTo.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAEjM,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AACpE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAEhE,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAChD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAC/C,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEtD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AACrE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAClE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAE3F,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACxF,YAAY,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAEzD,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAE5D,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE5G,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAChG,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AACzF,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAEjM,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AACpE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAEhE,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAChD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAC/C,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEtD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AACrE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAClE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAE3F,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACxF,YAAY,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAEzD,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAChE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAE5D,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE5G,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAChG,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AACzF,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA"}
package/dist/index.js CHANGED
@@ -1278,13 +1278,15 @@ function noTrivia(root) {
1278
1278
  function parse(combinator, input, opts = {}) {
1279
1279
  const trackLines = opts.trackLines ?? false;
1280
1280
  const _errors = opts.recover ? [] : void 0;
1281
+ const _probe = opts.recover ? { offset: input.length, best: null } : void 0;
1281
1282
  const ctx = {
1282
1283
  trackLines,
1283
- ..._errors !== void 0 ? { _errors } : {}
1284
+ ..._errors !== void 0 ? { _errors } : {},
1285
+ ..._probe !== void 0 ? { _probe } : {}
1284
1286
  };
1285
1287
  const result = combinator.parse(input, 0, ctx);
1286
1288
  if (!result.ok) return result;
1287
- const withErrors = _errors !== void 0 ? { ...result, errors: _errors } : result;
1289
+ const withErrors = _errors !== void 0 ? { ...result, errors: _errors, furthestFail: _probe?.best ?? null } : result;
1288
1290
  if (trackLines) {
1289
1291
  const idx = buildLineIndex(input);
1290
1292
  return { ...withErrors, span: annotateSpan(withErrors.span, idx) };
@@ -1780,6 +1782,119 @@ function firstSetCond(codeVar, fs) {
1780
1782
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
1781
1783
  ).join(" || ");
1782
1784
  }
1785
+ function captureRestoreBody(mL, mR, mTl, mLg) {
1786
+ const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
1787
+ return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
1788
+ }
1789
+ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
1790
+ if (seen.has(p)) return false;
1791
+ seen.add(p);
1792
+ const d = p._def;
1793
+ switch (d.tag) {
1794
+ // Atomic / non-capturing-then-failing: a failure happens before any push.
1795
+ case "literal":
1796
+ case "regex":
1797
+ case "keywords":
1798
+ case "guard":
1799
+ case "not":
1800
+ case "trivia":
1801
+ case "scanTo":
1802
+ case "unknown":
1803
+ return false;
1804
+ // node() captures into its own private buffers and rolls them back on
1805
+ // failure (emitNode restores _ctx.* and never pushes on the !ok path).
1806
+ case "node":
1807
+ return false;
1808
+ // choice/firstMatch already roll back each failed arm; on overall failure
1809
+ // nothing committed remains.
1810
+ case "choice":
1811
+ return false;
1812
+ // optional never fails; many/oneOrMore only "fail" with zero captured items.
1813
+ case "optional":
1814
+ case "many":
1815
+ case "oneOrMore":
1816
+ return false;
1817
+ // sepBy emits its own per-iteration rollback in emitSepBy.
1818
+ case "sepBy":
1819
+ return false;
1820
+ // A sequence is the real case: an earlier capturing term followed by a term
1821
+ // that can fail leaves the earlier captures buffered.
1822
+ case "sequence": {
1823
+ const parts = d.parsers;
1824
+ if (parts.length < 2) return parts.some((x) => mayLeavePartialCapture(x, seen));
1825
+ for (let i = 0; i < parts.length - 1; i++) {
1826
+ if (hasNodeDef(parts[i]) || capturesLeaf(parts[i])) return true;
1827
+ }
1828
+ return false;
1829
+ }
1830
+ // Delegating wrappers: defer to the wrapped parser.
1831
+ case "transform":
1832
+ case "label":
1833
+ case "expect":
1834
+ case "withCtx":
1835
+ case "grammar":
1836
+ return mayLeavePartialCapture(d.parser, seen);
1837
+ case "skip":
1838
+ return mayLeavePartialCapture(d.main, seen);
1839
+ case "recover":
1840
+ return mayLeavePartialCapture(d.parser, seen);
1841
+ case "lazy": {
1842
+ try {
1843
+ return mayLeavePartialCapture(d.thunk(), seen);
1844
+ } catch {
1845
+ return true;
1846
+ }
1847
+ }
1848
+ // Unknown shapes: be safe and keep the rollback.
1849
+ default:
1850
+ return true;
1851
+ }
1852
+ }
1853
+ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
1854
+ if (seen.has(p)) return false;
1855
+ seen.add(p);
1856
+ const d = p._def;
1857
+ switch (d.tag) {
1858
+ case "literal":
1859
+ case "regex":
1860
+ case "keywords":
1861
+ case "node":
1862
+ return true;
1863
+ case "not":
1864
+ case "guard":
1865
+ case "trivia":
1866
+ case "unknown":
1867
+ return false;
1868
+ case "sequence":
1869
+ case "choice":
1870
+ return d.parsers.some((x) => capturesLeaf(x, seen));
1871
+ case "sepBy":
1872
+ return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
1873
+ case "many":
1874
+ case "oneOrMore":
1875
+ case "optional":
1876
+ case "transform":
1877
+ case "label":
1878
+ case "expect":
1879
+ case "withCtx":
1880
+ case "grammar":
1881
+ case "recover":
1882
+ return capturesLeaf(d.parser, seen);
1883
+ case "skip":
1884
+ return capturesLeaf(d.main, seen);
1885
+ case "scanTo":
1886
+ return true;
1887
+ case "lazy": {
1888
+ try {
1889
+ return capturesLeaf(d.thunk(), seen);
1890
+ } catch {
1891
+ return true;
1892
+ }
1893
+ }
1894
+ default:
1895
+ return true;
1896
+ }
1897
+ }
1783
1898
  function emitFallible(inner, ctx, pos) {
1784
1899
  const lbl = v(ctx, "_lbl");
1785
1900
  const okV = `${lbl}ok`;
@@ -1793,26 +1908,23 @@ function emitFallible(inner, ctx, pos) {
1793
1908
  ctx.failLabel = savedLabel;
1794
1909
  ctx.indent = savedIndent;
1795
1910
  const ind0 = ind(ctx);
1796
- const mL = ctx.capturing ? v(ctx, "_fcl") : null;
1797
- const mR = ctx.capturing ? v(ctx, "_fcr") : null;
1798
- const mTl = ctx.capturing ? v(ctx, "_fctl") : null;
1799
- const mLg = ctx.capturing ? v(ctx, "_fclg") : null;
1911
+ const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
1912
+ const mL = needsRollback ? v(ctx, "_fcl") : null;
1913
+ const mR = needsRollback ? v(ctx, "_fcr") : null;
1914
+ const mTl = needsRollback ? v(ctx, "_fctl") : null;
1800
1915
  const stmts = [
1801
1916
  `${ind0}let ${okV} = false, ${valV}, ${endV} = ${pos}`,
1802
1917
  ...mL ? [
1803
1918
  `${ind0}const ${mL} = _ctx._cstLeaves?.length ?? 0`,
1804
1919
  `${ind0}const ${mR} = _ctx._cstRawChildren?.length ?? 0`,
1805
- `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
1806
- `${ind0}const ${mLg} = _ctx._triviaLog?.length ?? 0`
1920
+ `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`
1807
1921
  ] : [],
1808
1922
  `${ind0}${lbl}: {`,
1809
1923
  ...r.stmts,
1810
- `${ind0} ${valV} = ${r.valueVar}`,
1811
- `${ind0} ${endV} = ${r.endVar}`,
1812
- `${ind0} ${okV} = true`,
1924
+ `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
1813
1925
  `${ind0}}`,
1814
1926
  ...mL ? [
1815
- `${ind0}if (!${okV}) { if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg} }`
1927
+ `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null)} }`
1816
1928
  ] : []
1817
1929
  ];
1818
1930
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
@@ -2086,11 +2198,13 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2086
2198
  gateCond = `_mf[${gateIdx}](_ctx.state)`;
2087
2199
  }
2088
2200
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
2089
- const markLeaves = ctx.capturing ? v(ctx, "_cml") : null;
2090
- const markRaw = ctx.capturing ? v(ctx, "_cmr") : null;
2091
- const markTl = ctx.capturing ? v(ctx, "_cmtl") : null;
2092
- const markLog = ctx.capturing ? v(ctx, "_cmlg") : null;
2093
- const rollback = markLeaves ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLeaves}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRaw}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}` : "";
2201
+ const armHasAutoNot = !!(autoNot && autoNot.length > 0);
2202
+ const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
2203
+ const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
2204
+ const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
2205
+ const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
2206
+ const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
2207
+ const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
2094
2208
  stmts.push(`${ind0}if (${skipCond}) {`);
2095
2209
  ctx.indent++;
2096
2210
  const ind1 = ind(ctx);
@@ -2120,11 +2234,7 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2120
2234
  stmts.push(`${ind0} }`);
2121
2235
  if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
2122
2236
  } else {
2123
- stmts.push(`${ind0} if (${okVar}) {`);
2124
- stmts.push(`${ind0} ${resValV} = ${valVar}`);
2125
- stmts.push(`${ind0} ${resEndV} = ${endVar}`);
2126
- stmts.push(`${ind0} ${resOkV} = true`);
2127
- stmts.push(`${ind0} }`);
2237
+ stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2128
2238
  if (rollback) stmts.push(`${ind0} else { ${rollback} }`);
2129
2239
  }
2130
2240
  stmts.push(`${ind0}}`);
@@ -2469,6 +2579,22 @@ function emitRecover(def, ctx, pos) {
2469
2579
  ];
2470
2580
  return { stmts, valueVar: valVar, endVar };
2471
2581
  }
2582
+ function emitExpect(def, ctx, pos) {
2583
+ const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
2584
+ const ind0 = ind(ctx);
2585
+ const errV = v(ctx, "_err");
2586
+ const stmts = [
2587
+ ...innerStmts,
2588
+ `${ind0}if (!${okVar}) {`,
2589
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${pos} }, expected: ${JSON.stringify(def.expected)} }`,
2590
+ `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
2591
+ `${ind0} ${valVar} = ${errV}`,
2592
+ `${ind0} ${endVar} = ${pos}`,
2593
+ `${ind0} ${okVar} = true`,
2594
+ `${ind0}}`
2595
+ ];
2596
+ return { stmts, valueVar: valVar, endVar };
2597
+ }
2472
2598
  function emit(p, ctx, pos) {
2473
2599
  const def = p._def;
2474
2600
  switch (def.tag) {
@@ -2577,6 +2703,8 @@ function emit(p, ctx, pos) {
2577
2703
  return emitScanTo(def, ctx, pos);
2578
2704
  case "recover":
2579
2705
  return emitRecover(def, ctx, pos);
2706
+ case "expect":
2707
+ return emitExpect(def, ctx, pos);
2580
2708
  case "guard": {
2581
2709
  const fnIdx = ctx.mapFns.length;
2582
2710
  ctx.mapFns.push(def.predicate);
@@ -2660,6 +2788,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
2660
2788
  return hasNodeDef(d.sentinel, seen) || d.skip.some((x) => hasNodeDef(x, seen));
2661
2789
  case "recover":
2662
2790
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.sentinel, seen);
2791
+ case "expect":
2792
+ return hasNodeDef(d.parser, seen);
2663
2793
  case "withCtx":
2664
2794
  return hasNodeDef(d.parser, seen);
2665
2795
  default:
@@ -2728,6 +2858,11 @@ function compile(combinator, mapFnSources) {
2728
2858
  parseWithContext(input, parseCtx, pos = 0) {
2729
2859
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, parseCtx);
2730
2860
  },
2861
+ // Note: collects recover()/expect() errors via _errors. Unlike interpreter
2862
+ // parse({recover:true}) it does NOT populate furthestFail — the compiled path
2863
+ // inlines failures for throughput and deliberately skips _probe bookkeeping.
2864
+ // Callers wanting a furthest-position diagnostic detect unconsumed input
2865
+ // (span.end < input.length) instead, which is mode-agnostic.
2731
2866
  parseWithErrors(input, pos = 0) {
2732
2867
  const errors = [];
2733
2868
  const result = fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, { ...defaultCtx, _errors: errors });
@@ -2839,6 +2974,58 @@ function isParseError(value) {
2839
2974
  return typeof value === "object" && value !== null && value._tag === "parseError";
2840
2975
  }
2841
2976
 
2977
+ // src/combinators/expect.ts
2978
+ function staticExpected(c) {
2979
+ const def = c._def;
2980
+ switch (def.tag) {
2981
+ case "literal":
2982
+ return [JSON.stringify(def.value)];
2983
+ case "regex":
2984
+ return [`/${def.source}/`];
2985
+ case "keywords":
2986
+ return def.words.map((w) => JSON.stringify(w));
2987
+ case "label":
2988
+ return [def.label];
2989
+ case "choice":
2990
+ return def.parsers.flatMap(staticExpected);
2991
+ case "sequence":
2992
+ return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
2993
+ case "node":
2994
+ case "grammar":
2995
+ case "trivia":
2996
+ case "optional":
2997
+ case "many":
2998
+ case "oneOrMore":
2999
+ case "transform":
3000
+ case "not":
3001
+ return staticExpected(def.parser);
3002
+ case "lazy":
3003
+ return staticExpected(def.thunk());
3004
+ default:
3005
+ return [];
3006
+ }
3007
+ }
3008
+ function expect(combinator, label2) {
3009
+ const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
3010
+ const meta = {
3011
+ firstSet: combinator._meta.firstSet,
3012
+ canMatchNewline: combinator._meta.canMatchNewline,
3013
+ isTrivia: false
3014
+ };
3015
+ return {
3016
+ _tag: "expect",
3017
+ _meta: meta,
3018
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
3019
+ parse(input, pos, ctx) {
3020
+ const result = combinator.parse(input, pos, ctx);
3021
+ if (result.ok) return result;
3022
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
3023
+ ctx._errors?.push(error);
3024
+ return { ok: true, value: error, span: { start: pos, end: pos } };
3025
+ }
3026
+ };
3027
+ }
3028
+
2842
3029
  // src/combinators/completions.ts
2843
3030
  function completionsAt(combinator, input, offset) {
2844
3031
  const probe = { offset, best: null };
@@ -2867,7 +3054,11 @@ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
2867
3054
  _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
2868
3055
  parse(input, pos, ctx) {
2869
3056
  let cur = pos;
2870
- const probeCtx = { trackLines: ctx.trackLines, state: ctx.state };
3057
+ const probeCtx = {
3058
+ trackLines: ctx.trackLines,
3059
+ state: ctx.state,
3060
+ ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
3061
+ };
2871
3062
  const emit2 = (end) => {
2872
3063
  if (end > pos && cstCaptureActive(ctx)) {
2873
3064
  const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
@@ -2903,20 +3094,46 @@ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
2903
3094
  }
2904
3095
  function balanced(open, close, options = {}) {
2905
3096
  const self = ref();
2906
- const inner = scanTo(literal(close), {
2907
- ...options,
2908
- skip: [self, ...options.skip ?? []]
2909
- });
3097
+ const skips = options.skip ?? [];
3098
+ const stop = /* @__PURE__ */ new Set([open, close]);
3099
+ let bounded = true;
3100
+ for (const sk of skips) {
3101
+ const cs = firstSetClassChars(sk);
3102
+ if (cs === null) {
3103
+ bounded = false;
3104
+ break;
3105
+ }
3106
+ for (const ch of cs) stop.add(ch);
3107
+ }
3108
+ const cls = [...stop].map(escapeClassChar).join("");
3109
+ const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
3110
+ const inner = many(choice(self, ...skips, content));
2910
3111
  const combi = transform(
2911
- sequence(literal(open), inner, literal(close)),
2912
- ([o, content, c]) => o + content + c
3112
+ sequence(literal(open), inner, expect(literal(close), close)),
3113
+ // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
3114
+ // ParseError (recovered close). `c` is the close string or a ParseError.
3115
+ ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
2913
3116
  );
2914
3117
  if (combi._def.tag === "transform") {
2915
- combi._def.fnSrc = "([o, content, c]) => o + content + c";
3118
+ combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
2916
3119
  }
2917
3120
  self.define(combi);
2918
3121
  return combi;
2919
3122
  }
3123
+ function firstSetClassChars(c) {
3124
+ const fs = c._meta.firstSet;
3125
+ if (fs.kind === "empty") return [];
3126
+ if (fs.kind !== "ranges") return null;
3127
+ const out = [];
3128
+ for (const { lo, hi } of fs.ranges) {
3129
+ if (hi - lo > 8) return null;
3130
+ for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
3131
+ }
3132
+ return out;
3133
+ }
3134
+ function escapeClassChar(ch) {
3135
+ return ch.replace(/[\\\]^-]/g, "\\$&");
3136
+ }
2920
3137
 
2921
3138
  // src/functional/doc.ts
2922
3139
  function isNode(x) {
@@ -3116,6 +3333,7 @@ export {
3116
3333
  choice,
3117
3334
  compile,
3118
3335
  completionsAt,
3336
+ expect,
3119
3337
  guard,
3120
3338
  isParseError,
3121
3339
  keywords,
@@ -3140,6 +3358,7 @@ export {
3140
3358
  sepBy,
3141
3359
  sequence,
3142
3360
  skip,
3361
+ staticExpected,
3143
3362
  transform,
3144
3363
  trivia,
3145
3364
  triviaEntries,