parseman 0.7.0 → 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.
@@ -0,0 +1,36 @@
1
+ import type { Combinator, ParseError } from '../types.ts';
2
+ export type { ParseError };
3
+ /**
4
+ * Statically derive the "expected" token set from a combinator's structure,
5
+ * so a failure can name what it wanted without running anything:
6
+ *
7
+ * literal('}') -> ['"}"']
8
+ * keywords(['a','b']) -> ['"a"', '"b"']
9
+ * choice(x, y) -> expected(x) ++ expected(y) (all alternatives)
10
+ * sequence(a, b, …) -> expected(a) (only the first term can fail first)
11
+ * label('s', x) -> ['s'] (an explicit name wins)
12
+ *
13
+ * This is what lets expect() report a meaningful, IDENTICAL expectation in both
14
+ * the interpreter and the compiled output (the compiled path does not rebuild the
15
+ * runtime `expected` array, so it reads this precomputed set instead).
16
+ */
17
+ export declare function staticExpected(c: Combinator<unknown>): string[];
18
+ /**
19
+ * Required-token combinator. Tries `combinator`; on success returns it verbatim.
20
+ * On failure it does NOT fail — it records a {@link ParseError} (the statically
21
+ * derived expected set, or `label` when given) into `ctx._errors` and RECOVERS IN
22
+ * PLACE, returning a zero-width success so the enclosing sequence continues as if
23
+ * the token were present.
24
+ *
25
+ * Use it to mark required delimiters/terminators (`}` `)` `;`, a selector before a
26
+ * block) so a missing one is reported with position + expectation rather than
27
+ * aborting the whole parse or being silently swallowed by a catch-all.
28
+ *
29
+ * sequence(literal('{'), declList, expect(literal('}'), '}'))
30
+ *
31
+ * Errors are only collected when the parse runs with `{ recover: true }`; without
32
+ * it, expect() still recovers in place but records nothing (zero overhead beyond
33
+ * the inner attempt).
34
+ */
35
+ export declare function expect<T>(combinator: Combinator<T>, label?: string): Combinator<T | ParseError>;
36
+ //# sourceMappingURL=expect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/combinators/expect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,UAAU,EAAE,MAAM,aAAa,CAAA;AAEhG,YAAY,EAAE,UAAU,EAAE,CAAA;AAE1B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,MAAM,EAAE,CAoB/D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,GAAG,UAAU,CAAC,CAmB/F"}
@@ -1 +1 @@
1
- {"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../src/combinators/grammar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAGxE,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG;IACzC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;IACnC,gFAAgF;IAChF,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,CAAA;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IACtD,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IACpC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;CACrE;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAwCrF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAElE;AAED,wBAAgB,KAAK,CAAC,CAAC,EACrB,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EACzB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,YAAiB,GACtB,WAAW,CAAC,CAAC,CAAC,CAehB"}
1
+ {"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../src/combinators/grammar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAa,MAAM,aAAa,CAAA;AAGnF,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG;IACzC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;IACnC,gFAAgF;IAChF,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,CAAA;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IACtD,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IACpC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;CACrE;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAwCrF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAElE;AAED,wBAAgB,KAAK,CAAC,CAAC,EACrB,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EACzB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,YAAiB,GACtB,WAAW,CAAC,CAAC,CAAC,CAuBhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"scanTo.d.ts","sourceRoot":"","sources":["../../src/combinators/scanTo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,MAAM,aAAa,CAAA;AAOpF,MAAM,MAAM,aAAa,GAAG;IAC1B,uGAAuG;IACvG,IAAI,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAA;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CACpB,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,EAC7B,EAAE,IAAS,EAAE,KAAa,EAAE,GAAE,aAAkB,GAC/C,UAAU,CAAC,MAAM,CAAC,CA8DpB;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GAC1B,UAAU,CAAC,MAAM,CAAC,CAYpB"}
1
+ {"version":3,"file":"scanTo.d.ts","sourceRoot":"","sources":["../../src/combinators/scanTo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,MAAM,aAAa,CAAA;AAYpF,MAAM,MAAM,aAAa,GAAG;IAC1B,uGAAuG;IACvG,IAAI,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAA;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CACpB,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,EAC7B,EAAE,IAAS,EAAE,KAAa,EAAE,GAAE,aAAkB,GAC/C,UAAU,CAAC,MAAM,CAAC,CAoEpB;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GAC1B,UAAU,CAAC,MAAM,CAAC,CA0CpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/compiler/codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,UAAU,EAAuB,WAAW,EAAE,YAAY,EAAE,UAAU,EAAkB,MAAM,aAAa,CAAA;AAm1CzH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAClD,kGAAkG;IAClG,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAChF;;;;OAIG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG;QAAE,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE,CAAA;IACvF,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAA;IACd;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC,CAAA;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAgGhG"}
1
+ {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/compiler/codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,UAAU,EAAuB,WAAW,EAAE,YAAY,EAAE,UAAU,EAAkB,MAAM,aAAa,CAAA;AA2/CzH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAClD,kGAAkG;IAClG,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAChF;;;;OAIG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG;QAAE,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE,CAAA;IACvF,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAA;IACd;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC,CAAA;AAkCD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAqGhG"}
package/dist/index.cjs CHANGED
@@ -37,6 +37,7 @@ __export(index_exports, {
37
37
  choice: () => choice,
38
38
  compile: () => compile,
39
39
  completionsAt: () => completionsAt,
40
+ expect: () => expect,
40
41
  guard: () => guard,
41
42
  isParseError: () => isParseError,
42
43
  keywords: () => keywords,
@@ -61,6 +62,7 @@ __export(index_exports, {
61
62
  sepBy: () => sepBy,
62
63
  sequence: () => sequence,
63
64
  skip: () => skip,
65
+ staticExpected: () => staticExpected,
64
66
  transform: () => transform,
65
67
  trivia: () => trivia,
66
68
  triviaEntries: () => triviaEntries,
@@ -1349,13 +1351,15 @@ function noTrivia(root) {
1349
1351
  function parse(combinator, input, opts = {}) {
1350
1352
  const trackLines = opts.trackLines ?? false;
1351
1353
  const _errors = opts.recover ? [] : void 0;
1354
+ const _probe = opts.recover ? { offset: input.length, best: null } : void 0;
1352
1355
  const ctx = {
1353
1356
  trackLines,
1354
- ..._errors !== void 0 ? { _errors } : {}
1357
+ ..._errors !== void 0 ? { _errors } : {},
1358
+ ..._probe !== void 0 ? { _probe } : {}
1355
1359
  };
1356
1360
  const result = combinator.parse(input, 0, ctx);
1357
1361
  if (!result.ok) return result;
1358
- const withErrors = _errors !== void 0 ? { ...result, errors: _errors } : result;
1362
+ const withErrors = _errors !== void 0 ? { ...result, errors: _errors, furthestFail: _probe?.best ?? null } : result;
1359
1363
  if (trackLines) {
1360
1364
  const idx = buildLineIndex(input);
1361
1365
  return { ...withErrors, span: annotateSpan(withErrors.span, idx) };
@@ -1851,6 +1855,119 @@ function firstSetCond(codeVar, fs) {
1851
1855
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
1852
1856
  ).join(" || ");
1853
1857
  }
1858
+ function captureRestoreBody(mL, mR, mTl, mLg) {
1859
+ const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
1860
+ return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
1861
+ }
1862
+ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
1863
+ if (seen.has(p)) return false;
1864
+ seen.add(p);
1865
+ const d = p._def;
1866
+ switch (d.tag) {
1867
+ // Atomic / non-capturing-then-failing: a failure happens before any push.
1868
+ case "literal":
1869
+ case "regex":
1870
+ case "keywords":
1871
+ case "guard":
1872
+ case "not":
1873
+ case "trivia":
1874
+ case "scanTo":
1875
+ case "unknown":
1876
+ return false;
1877
+ // node() captures into its own private buffers and rolls them back on
1878
+ // failure (emitNode restores _ctx.* and never pushes on the !ok path).
1879
+ case "node":
1880
+ return false;
1881
+ // choice/firstMatch already roll back each failed arm; on overall failure
1882
+ // nothing committed remains.
1883
+ case "choice":
1884
+ return false;
1885
+ // optional never fails; many/oneOrMore only "fail" with zero captured items.
1886
+ case "optional":
1887
+ case "many":
1888
+ case "oneOrMore":
1889
+ return false;
1890
+ // sepBy emits its own per-iteration rollback in emitSepBy.
1891
+ case "sepBy":
1892
+ return false;
1893
+ // A sequence is the real case: an earlier capturing term followed by a term
1894
+ // that can fail leaves the earlier captures buffered.
1895
+ case "sequence": {
1896
+ const parts = d.parsers;
1897
+ if (parts.length < 2) return parts.some((x) => mayLeavePartialCapture(x, seen));
1898
+ for (let i = 0; i < parts.length - 1; i++) {
1899
+ if (hasNodeDef(parts[i]) || capturesLeaf(parts[i])) return true;
1900
+ }
1901
+ return false;
1902
+ }
1903
+ // Delegating wrappers: defer to the wrapped parser.
1904
+ case "transform":
1905
+ case "label":
1906
+ case "expect":
1907
+ case "withCtx":
1908
+ case "grammar":
1909
+ return mayLeavePartialCapture(d.parser, seen);
1910
+ case "skip":
1911
+ return mayLeavePartialCapture(d.main, seen);
1912
+ case "recover":
1913
+ return mayLeavePartialCapture(d.parser, seen);
1914
+ case "lazy": {
1915
+ try {
1916
+ return mayLeavePartialCapture(d.thunk(), seen);
1917
+ } catch {
1918
+ return true;
1919
+ }
1920
+ }
1921
+ // Unknown shapes: be safe and keep the rollback.
1922
+ default:
1923
+ return true;
1924
+ }
1925
+ }
1926
+ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
1927
+ if (seen.has(p)) return false;
1928
+ seen.add(p);
1929
+ const d = p._def;
1930
+ switch (d.tag) {
1931
+ case "literal":
1932
+ case "regex":
1933
+ case "keywords":
1934
+ case "node":
1935
+ return true;
1936
+ case "not":
1937
+ case "guard":
1938
+ case "trivia":
1939
+ case "unknown":
1940
+ return false;
1941
+ case "sequence":
1942
+ case "choice":
1943
+ return d.parsers.some((x) => capturesLeaf(x, seen));
1944
+ case "sepBy":
1945
+ return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
1946
+ case "many":
1947
+ case "oneOrMore":
1948
+ case "optional":
1949
+ case "transform":
1950
+ case "label":
1951
+ case "expect":
1952
+ case "withCtx":
1953
+ case "grammar":
1954
+ case "recover":
1955
+ return capturesLeaf(d.parser, seen);
1956
+ case "skip":
1957
+ return capturesLeaf(d.main, seen);
1958
+ case "scanTo":
1959
+ return true;
1960
+ case "lazy": {
1961
+ try {
1962
+ return capturesLeaf(d.thunk(), seen);
1963
+ } catch {
1964
+ return true;
1965
+ }
1966
+ }
1967
+ default:
1968
+ return true;
1969
+ }
1970
+ }
1854
1971
  function emitFallible(inner, ctx, pos) {
1855
1972
  const lbl = v(ctx, "_lbl");
1856
1973
  const okV = `${lbl}ok`;
@@ -1864,26 +1981,23 @@ function emitFallible(inner, ctx, pos) {
1864
1981
  ctx.failLabel = savedLabel;
1865
1982
  ctx.indent = savedIndent;
1866
1983
  const ind0 = ind(ctx);
1867
- const mL = ctx.capturing ? v(ctx, "_fcl") : null;
1868
- const mR = ctx.capturing ? v(ctx, "_fcr") : null;
1869
- const mTl = ctx.capturing ? v(ctx, "_fctl") : null;
1870
- const mLg = ctx.capturing ? v(ctx, "_fclg") : null;
1984
+ const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
1985
+ const mL = needsRollback ? v(ctx, "_fcl") : null;
1986
+ const mR = needsRollback ? v(ctx, "_fcr") : null;
1987
+ const mTl = needsRollback ? v(ctx, "_fctl") : null;
1871
1988
  const stmts = [
1872
1989
  `${ind0}let ${okV} = false, ${valV}, ${endV} = ${pos}`,
1873
1990
  ...mL ? [
1874
1991
  `${ind0}const ${mL} = _ctx._cstLeaves?.length ?? 0`,
1875
1992
  `${ind0}const ${mR} = _ctx._cstRawChildren?.length ?? 0`,
1876
- `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
1877
- `${ind0}const ${mLg} = _ctx._triviaLog?.length ?? 0`
1993
+ `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`
1878
1994
  ] : [],
1879
1995
  `${ind0}${lbl}: {`,
1880
1996
  ...r.stmts,
1881
- `${ind0} ${valV} = ${r.valueVar}`,
1882
- `${ind0} ${endV} = ${r.endVar}`,
1883
- `${ind0} ${okV} = true`,
1997
+ `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
1884
1998
  `${ind0}}`,
1885
1999
  ...mL ? [
1886
- `${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} }`
2000
+ `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null)} }`
1887
2001
  ] : []
1888
2002
  ];
1889
2003
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
@@ -2157,11 +2271,13 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2157
2271
  gateCond = `_mf[${gateIdx}](_ctx.state)`;
2158
2272
  }
2159
2273
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
2160
- const markLeaves = ctx.capturing ? v(ctx, "_cml") : null;
2161
- const markRaw = ctx.capturing ? v(ctx, "_cmr") : null;
2162
- const markTl = ctx.capturing ? v(ctx, "_cmtl") : null;
2163
- const markLog = ctx.capturing ? v(ctx, "_cmlg") : null;
2164
- 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}` : "";
2274
+ const armHasAutoNot = !!(autoNot && autoNot.length > 0);
2275
+ const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
2276
+ const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
2277
+ const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
2278
+ const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
2279
+ const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
2280
+ const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
2165
2281
  stmts.push(`${ind0}if (${skipCond}) {`);
2166
2282
  ctx.indent++;
2167
2283
  const ind1 = ind(ctx);
@@ -2191,11 +2307,7 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2191
2307
  stmts.push(`${ind0} }`);
2192
2308
  if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
2193
2309
  } else {
2194
- stmts.push(`${ind0} if (${okVar}) {`);
2195
- stmts.push(`${ind0} ${resValV} = ${valVar}`);
2196
- stmts.push(`${ind0} ${resEndV} = ${endVar}`);
2197
- stmts.push(`${ind0} ${resOkV} = true`);
2198
- stmts.push(`${ind0} }`);
2310
+ stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2199
2311
  if (rollback) stmts.push(`${ind0} else { ${rollback} }`);
2200
2312
  }
2201
2313
  stmts.push(`${ind0}}`);
@@ -2540,6 +2652,22 @@ function emitRecover(def, ctx, pos) {
2540
2652
  ];
2541
2653
  return { stmts, valueVar: valVar, endVar };
2542
2654
  }
2655
+ function emitExpect(def, ctx, pos) {
2656
+ const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
2657
+ const ind0 = ind(ctx);
2658
+ const errV = v(ctx, "_err");
2659
+ const stmts = [
2660
+ ...innerStmts,
2661
+ `${ind0}if (!${okVar}) {`,
2662
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${pos} }, expected: ${JSON.stringify(def.expected)} }`,
2663
+ `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
2664
+ `${ind0} ${valVar} = ${errV}`,
2665
+ `${ind0} ${endVar} = ${pos}`,
2666
+ `${ind0} ${okVar} = true`,
2667
+ `${ind0}}`
2668
+ ];
2669
+ return { stmts, valueVar: valVar, endVar };
2670
+ }
2543
2671
  function emit(p, ctx, pos) {
2544
2672
  const def = p._def;
2545
2673
  switch (def.tag) {
@@ -2648,6 +2776,8 @@ function emit(p, ctx, pos) {
2648
2776
  return emitScanTo(def, ctx, pos);
2649
2777
  case "recover":
2650
2778
  return emitRecover(def, ctx, pos);
2779
+ case "expect":
2780
+ return emitExpect(def, ctx, pos);
2651
2781
  case "guard": {
2652
2782
  const fnIdx = ctx.mapFns.length;
2653
2783
  ctx.mapFns.push(def.predicate);
@@ -2731,6 +2861,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
2731
2861
  return hasNodeDef(d.sentinel, seen) || d.skip.some((x) => hasNodeDef(x, seen));
2732
2862
  case "recover":
2733
2863
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.sentinel, seen);
2864
+ case "expect":
2865
+ return hasNodeDef(d.parser, seen);
2734
2866
  case "withCtx":
2735
2867
  return hasNodeDef(d.parser, seen);
2736
2868
  default:
@@ -2799,6 +2931,11 @@ function compile(combinator, mapFnSources) {
2799
2931
  parseWithContext(input, parseCtx, pos = 0) {
2800
2932
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, parseCtx);
2801
2933
  },
2934
+ // Note: collects recover()/expect() errors via _errors. Unlike interpreter
2935
+ // parse({recover:true}) it does NOT populate furthestFail — the compiled path
2936
+ // inlines failures for throughput and deliberately skips _probe bookkeeping.
2937
+ // Callers wanting a furthest-position diagnostic detect unconsumed input
2938
+ // (span.end < input.length) instead, which is mode-agnostic.
2802
2939
  parseWithErrors(input, pos = 0) {
2803
2940
  const errors = [];
2804
2941
  const result = fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, { ...defaultCtx, _errors: errors });
@@ -2910,6 +3047,58 @@ function isParseError(value) {
2910
3047
  return typeof value === "object" && value !== null && value._tag === "parseError";
2911
3048
  }
2912
3049
 
3050
+ // src/combinators/expect.ts
3051
+ function staticExpected(c) {
3052
+ const def = c._def;
3053
+ switch (def.tag) {
3054
+ case "literal":
3055
+ return [JSON.stringify(def.value)];
3056
+ case "regex":
3057
+ return [`/${def.source}/`];
3058
+ case "keywords":
3059
+ return def.words.map((w) => JSON.stringify(w));
3060
+ case "label":
3061
+ return [def.label];
3062
+ case "choice":
3063
+ return def.parsers.flatMap(staticExpected);
3064
+ case "sequence":
3065
+ return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
3066
+ case "node":
3067
+ case "grammar":
3068
+ case "trivia":
3069
+ case "optional":
3070
+ case "many":
3071
+ case "oneOrMore":
3072
+ case "transform":
3073
+ case "not":
3074
+ return staticExpected(def.parser);
3075
+ case "lazy":
3076
+ return staticExpected(def.thunk());
3077
+ default:
3078
+ return [];
3079
+ }
3080
+ }
3081
+ function expect(combinator, label2) {
3082
+ const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
3083
+ const meta = {
3084
+ firstSet: combinator._meta.firstSet,
3085
+ canMatchNewline: combinator._meta.canMatchNewline,
3086
+ isTrivia: false
3087
+ };
3088
+ return {
3089
+ _tag: "expect",
3090
+ _meta: meta,
3091
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
3092
+ parse(input, pos, ctx) {
3093
+ const result = combinator.parse(input, pos, ctx);
3094
+ if (result.ok) return result;
3095
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
3096
+ ctx._errors?.push(error);
3097
+ return { ok: true, value: error, span: { start: pos, end: pos } };
3098
+ }
3099
+ };
3100
+ }
3101
+
2913
3102
  // src/combinators/completions.ts
2914
3103
  function completionsAt(combinator, input, offset) {
2915
3104
  const probe = { offset, best: null };
@@ -2938,7 +3127,11 @@ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
2938
3127
  _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
2939
3128
  parse(input, pos, ctx) {
2940
3129
  let cur = pos;
2941
- const probeCtx = { trackLines: ctx.trackLines, state: ctx.state };
3130
+ const probeCtx = {
3131
+ trackLines: ctx.trackLines,
3132
+ state: ctx.state,
3133
+ ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
3134
+ };
2942
3135
  const emit2 = (end) => {
2943
3136
  if (end > pos && cstCaptureActive(ctx)) {
2944
3137
  const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
@@ -2973,16 +3166,47 @@ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
2973
3166
  };
2974
3167
  }
2975
3168
  function balanced(open, close, options = {}) {
2976
- const inner = scanTo(literal(close), options);
3169
+ const self = ref();
3170
+ const skips = options.skip ?? [];
3171
+ const stop = /* @__PURE__ */ new Set([open, close]);
3172
+ let bounded = true;
3173
+ for (const sk of skips) {
3174
+ const cs = firstSetClassChars(sk);
3175
+ if (cs === null) {
3176
+ bounded = false;
3177
+ break;
3178
+ }
3179
+ for (const ch of cs) stop.add(ch);
3180
+ }
3181
+ const cls = [...stop].map(escapeClassChar).join("");
3182
+ const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
3183
+ const inner = many(choice(self, ...skips, content));
2977
3184
  const combi = transform(
2978
- sequence(literal(open), inner, literal(close)),
2979
- ([o, content, c]) => o + content + c
3185
+ sequence(literal(open), inner, expect(literal(close), close)),
3186
+ // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
3187
+ // ParseError (recovered close). `c` is the close string or a ParseError.
3188
+ ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
2980
3189
  );
2981
3190
  if (combi._def.tag === "transform") {
2982
- combi._def.fnSrc = "([o, content, c]) => o + content + c";
3191
+ combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
2983
3192
  }
3193
+ self.define(combi);
2984
3194
  return combi;
2985
3195
  }
3196
+ function firstSetClassChars(c) {
3197
+ const fs = c._meta.firstSet;
3198
+ if (fs.kind === "empty") return [];
3199
+ if (fs.kind !== "ranges") return null;
3200
+ const out = [];
3201
+ for (const { lo, hi } of fs.ranges) {
3202
+ if (hi - lo > 8) return null;
3203
+ for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
3204
+ }
3205
+ return out;
3206
+ }
3207
+ function escapeClassChar(ch) {
3208
+ return ch.replace(/[\\\]^-]/g, "\\$&");
3209
+ }
2986
3210
 
2987
3211
  // src/functional/doc.ts
2988
3212
  function isNode(x) {