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.
@@ -1722,6 +1722,119 @@ function firstSetCond(codeVar, fs) {
1722
1722
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
1723
1723
  ).join(" || ");
1724
1724
  }
1725
+ function captureRestoreBody(mL, mR, mTl, mLg) {
1726
+ const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
1727
+ return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
1728
+ }
1729
+ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
1730
+ if (seen.has(p)) return false;
1731
+ seen.add(p);
1732
+ const d = p._def;
1733
+ switch (d.tag) {
1734
+ // Atomic / non-capturing-then-failing: a failure happens before any push.
1735
+ case "literal":
1736
+ case "regex":
1737
+ case "keywords":
1738
+ case "guard":
1739
+ case "not":
1740
+ case "trivia":
1741
+ case "scanTo":
1742
+ case "unknown":
1743
+ return false;
1744
+ // node() captures into its own private buffers and rolls them back on
1745
+ // failure (emitNode restores _ctx.* and never pushes on the !ok path).
1746
+ case "node":
1747
+ return false;
1748
+ // choice/firstMatch already roll back each failed arm; on overall failure
1749
+ // nothing committed remains.
1750
+ case "choice":
1751
+ return false;
1752
+ // optional never fails; many/oneOrMore only "fail" with zero captured items.
1753
+ case "optional":
1754
+ case "many":
1755
+ case "oneOrMore":
1756
+ return false;
1757
+ // sepBy emits its own per-iteration rollback in emitSepBy.
1758
+ case "sepBy":
1759
+ return false;
1760
+ // A sequence is the real case: an earlier capturing term followed by a term
1761
+ // that can fail leaves the earlier captures buffered.
1762
+ case "sequence": {
1763
+ const parts = d.parsers;
1764
+ if (parts.length < 2) return parts.some((x) => mayLeavePartialCapture(x, seen));
1765
+ for (let i = 0; i < parts.length - 1; i++) {
1766
+ if (hasNodeDef(parts[i]) || capturesLeaf(parts[i])) return true;
1767
+ }
1768
+ return false;
1769
+ }
1770
+ // Delegating wrappers: defer to the wrapped parser.
1771
+ case "transform":
1772
+ case "label":
1773
+ case "expect":
1774
+ case "withCtx":
1775
+ case "grammar":
1776
+ return mayLeavePartialCapture(d.parser, seen);
1777
+ case "skip":
1778
+ return mayLeavePartialCapture(d.main, seen);
1779
+ case "recover":
1780
+ return mayLeavePartialCapture(d.parser, seen);
1781
+ case "lazy": {
1782
+ try {
1783
+ return mayLeavePartialCapture(d.thunk(), seen);
1784
+ } catch {
1785
+ return true;
1786
+ }
1787
+ }
1788
+ // Unknown shapes: be safe and keep the rollback.
1789
+ default:
1790
+ return true;
1791
+ }
1792
+ }
1793
+ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
1794
+ if (seen.has(p)) return false;
1795
+ seen.add(p);
1796
+ const d = p._def;
1797
+ switch (d.tag) {
1798
+ case "literal":
1799
+ case "regex":
1800
+ case "keywords":
1801
+ case "node":
1802
+ return true;
1803
+ case "not":
1804
+ case "guard":
1805
+ case "trivia":
1806
+ case "unknown":
1807
+ return false;
1808
+ case "sequence":
1809
+ case "choice":
1810
+ return d.parsers.some((x) => capturesLeaf(x, seen));
1811
+ case "sepBy":
1812
+ return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
1813
+ case "many":
1814
+ case "oneOrMore":
1815
+ case "optional":
1816
+ case "transform":
1817
+ case "label":
1818
+ case "expect":
1819
+ case "withCtx":
1820
+ case "grammar":
1821
+ case "recover":
1822
+ return capturesLeaf(d.parser, seen);
1823
+ case "skip":
1824
+ return capturesLeaf(d.main, seen);
1825
+ case "scanTo":
1826
+ return true;
1827
+ case "lazy": {
1828
+ try {
1829
+ return capturesLeaf(d.thunk(), seen);
1830
+ } catch {
1831
+ return true;
1832
+ }
1833
+ }
1834
+ default:
1835
+ return true;
1836
+ }
1837
+ }
1725
1838
  function emitFallible(inner, ctx, pos) {
1726
1839
  const lbl = v(ctx, "_lbl");
1727
1840
  const okV = `${lbl}ok`;
@@ -1735,26 +1848,23 @@ function emitFallible(inner, ctx, pos) {
1735
1848
  ctx.failLabel = savedLabel;
1736
1849
  ctx.indent = savedIndent;
1737
1850
  const ind0 = ind(ctx);
1738
- const mL = ctx.capturing ? v(ctx, "_fcl") : null;
1739
- const mR = ctx.capturing ? v(ctx, "_fcr") : null;
1740
- const mTl = ctx.capturing ? v(ctx, "_fctl") : null;
1741
- const mLg = ctx.capturing ? v(ctx, "_fclg") : null;
1851
+ const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
1852
+ const mL = needsRollback ? v(ctx, "_fcl") : null;
1853
+ const mR = needsRollback ? v(ctx, "_fcr") : null;
1854
+ const mTl = needsRollback ? v(ctx, "_fctl") : null;
1742
1855
  const stmts = [
1743
1856
  `${ind0}let ${okV} = false, ${valV}, ${endV} = ${pos}`,
1744
1857
  ...mL ? [
1745
1858
  `${ind0}const ${mL} = _ctx._cstLeaves?.length ?? 0`,
1746
1859
  `${ind0}const ${mR} = _ctx._cstRawChildren?.length ?? 0`,
1747
- `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
1748
- `${ind0}const ${mLg} = _ctx._triviaLog?.length ?? 0`
1860
+ `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`
1749
1861
  ] : [],
1750
1862
  `${ind0}${lbl}: {`,
1751
1863
  ...r.stmts,
1752
- `${ind0} ${valV} = ${r.valueVar}`,
1753
- `${ind0} ${endV} = ${r.endVar}`,
1754
- `${ind0} ${okV} = true`,
1864
+ `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
1755
1865
  `${ind0}}`,
1756
1866
  ...mL ? [
1757
- `${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} }`
1867
+ `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null)} }`
1758
1868
  ] : []
1759
1869
  ];
1760
1870
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
@@ -2028,11 +2138,13 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2028
2138
  gateCond = `_mf[${gateIdx}](_ctx.state)`;
2029
2139
  }
2030
2140
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
2031
- const markLeaves = ctx.capturing ? v(ctx, "_cml") : null;
2032
- const markRaw = ctx.capturing ? v(ctx, "_cmr") : null;
2033
- const markTl = ctx.capturing ? v(ctx, "_cmtl") : null;
2034
- const markLog = ctx.capturing ? v(ctx, "_cmlg") : null;
2035
- 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}` : "";
2141
+ const armHasAutoNot = !!(autoNot && autoNot.length > 0);
2142
+ const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
2143
+ const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
2144
+ const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
2145
+ const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
2146
+ const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
2147
+ const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
2036
2148
  stmts.push(`${ind0}if (${skipCond}) {`);
2037
2149
  ctx.indent++;
2038
2150
  const ind1 = ind(ctx);
@@ -2062,11 +2174,7 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2062
2174
  stmts.push(`${ind0} }`);
2063
2175
  if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
2064
2176
  } else {
2065
- stmts.push(`${ind0} if (${okVar}) {`);
2066
- stmts.push(`${ind0} ${resValV} = ${valVar}`);
2067
- stmts.push(`${ind0} ${resEndV} = ${endVar}`);
2068
- stmts.push(`${ind0} ${resOkV} = true`);
2069
- stmts.push(`${ind0} }`);
2177
+ stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2070
2178
  if (rollback) stmts.push(`${ind0} else { ${rollback} }`);
2071
2179
  }
2072
2180
  stmts.push(`${ind0}}`);
@@ -2411,6 +2519,22 @@ function emitRecover(def, ctx, pos) {
2411
2519
  ];
2412
2520
  return { stmts, valueVar: valVar, endVar };
2413
2521
  }
2522
+ function emitExpect(def, ctx, pos) {
2523
+ const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
2524
+ const ind0 = ind(ctx);
2525
+ const errV = v(ctx, "_err");
2526
+ const stmts = [
2527
+ ...innerStmts,
2528
+ `${ind0}if (!${okVar}) {`,
2529
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${pos} }, expected: ${JSON.stringify(def.expected)} }`,
2530
+ `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
2531
+ `${ind0} ${valVar} = ${errV}`,
2532
+ `${ind0} ${endVar} = ${pos}`,
2533
+ `${ind0} ${okVar} = true`,
2534
+ `${ind0}}`
2535
+ ];
2536
+ return { stmts, valueVar: valVar, endVar };
2537
+ }
2414
2538
  function emit(p, ctx, pos) {
2415
2539
  const def = p._def;
2416
2540
  switch (def.tag) {
@@ -2519,6 +2643,8 @@ function emit(p, ctx, pos) {
2519
2643
  return emitScanTo(def, ctx, pos);
2520
2644
  case "recover":
2521
2645
  return emitRecover(def, ctx, pos);
2646
+ case "expect":
2647
+ return emitExpect(def, ctx, pos);
2522
2648
  case "guard": {
2523
2649
  const fnIdx = ctx.mapFns.length;
2524
2650
  ctx.mapFns.push(def.predicate);
@@ -2602,6 +2728,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
2602
2728
  return hasNodeDef(d.sentinel, seen) || d.skip.some((x) => hasNodeDef(x, seen));
2603
2729
  case "recover":
2604
2730
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.sentinel, seen);
2731
+ case "expect":
2732
+ return hasNodeDef(d.parser, seen);
2605
2733
  case "withCtx":
2606
2734
  return hasNodeDef(d.parser, seen);
2607
2735
  default:
@@ -2670,6 +2798,11 @@ function compile(combinator, mapFnSources) {
2670
2798
  parseWithContext(input, parseCtx, pos = 0) {
2671
2799
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, parseCtx);
2672
2800
  },
2801
+ // Note: collects recover()/expect() errors via _errors. Unlike interpreter
2802
+ // parse({recover:true}) it does NOT populate furthestFail — the compiled path
2803
+ // inlines failures for throughput and deliberately skips _probe bookkeeping.
2804
+ // Callers wanting a furthest-position diagnostic detect unconsumed input
2805
+ // (span.end < input.length) instead, which is mode-agnostic.
2673
2806
  parseWithErrors(input, pos = 0) {
2674
2807
  const errors = [];
2675
2808
  const result = fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, { ...defaultCtx, _errors: errors });
@@ -2712,6 +2845,58 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
2712
2845
  ].join("\n");
2713
2846
  }
2714
2847
 
2848
+ // src/combinators/expect.ts
2849
+ function staticExpected(c) {
2850
+ const def = c._def;
2851
+ switch (def.tag) {
2852
+ case "literal":
2853
+ return [JSON.stringify(def.value)];
2854
+ case "regex":
2855
+ return [`/${def.source}/`];
2856
+ case "keywords":
2857
+ return def.words.map((w) => JSON.stringify(w));
2858
+ case "label":
2859
+ return [def.label];
2860
+ case "choice":
2861
+ return def.parsers.flatMap(staticExpected);
2862
+ case "sequence":
2863
+ return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
2864
+ case "node":
2865
+ case "grammar":
2866
+ case "trivia":
2867
+ case "optional":
2868
+ case "many":
2869
+ case "oneOrMore":
2870
+ case "transform":
2871
+ case "not":
2872
+ return staticExpected(def.parser);
2873
+ case "lazy":
2874
+ return staticExpected(def.thunk());
2875
+ default:
2876
+ return [];
2877
+ }
2878
+ }
2879
+ function expect(combinator, label2) {
2880
+ const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
2881
+ const meta = {
2882
+ firstSet: combinator._meta.firstSet,
2883
+ canMatchNewline: combinator._meta.canMatchNewline,
2884
+ isTrivia: false
2885
+ };
2886
+ return {
2887
+ _tag: "expect",
2888
+ _meta: meta,
2889
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
2890
+ parse(input, pos, ctx) {
2891
+ const result = combinator.parse(input, pos, ctx);
2892
+ if (result.ok) return result;
2893
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
2894
+ ctx._errors?.push(error);
2895
+ return { ok: true, value: error, span: { start: pos, end: pos } };
2896
+ }
2897
+ };
2898
+ }
2899
+
2715
2900
  // src/combinators/scanTo.ts
2716
2901
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
2717
2902
  const meta = {
@@ -2725,7 +2910,11 @@ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
2725
2910
  _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
2726
2911
  parse(input, pos, ctx) {
2727
2912
  let cur = pos;
2728
- const probeCtx = { trackLines: ctx.trackLines, state: ctx.state };
2913
+ const probeCtx = {
2914
+ trackLines: ctx.trackLines,
2915
+ state: ctx.state,
2916
+ ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
2917
+ };
2729
2918
  const emit2 = (end) => {
2730
2919
  if (end > pos && cstCaptureActive(ctx)) {
2731
2920
  const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
@@ -2760,16 +2949,47 @@ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
2760
2949
  };
2761
2950
  }
2762
2951
  function balanced(open, close, options = {}) {
2763
- const inner = scanTo(literal(close), options);
2952
+ const self = ref();
2953
+ const skips = options.skip ?? [];
2954
+ const stop = /* @__PURE__ */ new Set([open, close]);
2955
+ let bounded = true;
2956
+ for (const sk of skips) {
2957
+ const cs = firstSetClassChars(sk);
2958
+ if (cs === null) {
2959
+ bounded = false;
2960
+ break;
2961
+ }
2962
+ for (const ch of cs) stop.add(ch);
2963
+ }
2964
+ const cls = [...stop].map(escapeClassChar).join("");
2965
+ const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
2966
+ const inner = many(choice(self, ...skips, content));
2764
2967
  const combi = transform(
2765
- sequence(literal(open), inner, literal(close)),
2766
- ([o, content, c]) => o + content + c
2968
+ sequence(literal(open), inner, expect(literal(close), close)),
2969
+ // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
2970
+ // ParseError (recovered close). `c` is the close string or a ParseError.
2971
+ ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
2767
2972
  );
2768
2973
  if (combi._def.tag === "transform") {
2769
- combi._def.fnSrc = "([o, content, c]) => o + content + c";
2974
+ combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
2770
2975
  }
2976
+ self.define(combi);
2771
2977
  return combi;
2772
2978
  }
2979
+ function firstSetClassChars(c) {
2980
+ const fs = c._meta.firstSet;
2981
+ if (fs.kind === "empty") return [];
2982
+ if (fs.kind !== "ranges") return null;
2983
+ const out = [];
2984
+ for (const { lo, hi } of fs.ranges) {
2985
+ if (hi - lo > 8) return null;
2986
+ for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
2987
+ }
2988
+ return out;
2989
+ }
2990
+ function escapeClassChar(ch) {
2991
+ return ch.replace(/[\\\]^-]/g, "\\$&");
2992
+ }
2773
2993
 
2774
2994
  // src/plugin/evaluator.ts
2775
2995
  function isWordFactory(v2) {
@@ -2788,7 +3008,8 @@ var SUPPORTED = {
2788
3008
  sepBy: (...a) => sepBy(a[0], a[1]),
2789
3009
  trivia: (...a) => trivia(a[0]),
2790
3010
  label: (...a) => label(a[0], a[1]),
2791
- noTrivia: (...a) => noTrivia(a[0])
3011
+ noTrivia: (...a) => noTrivia(a[0]),
3012
+ expect: (...a) => expect(a[0], a[1])
2792
3013
  };
2793
3014
  function isScopeEntry(v2) {
2794
3015
  return !!v2 && typeof v2 === "object" && "combi" in v2 && "mfSrcs" in v2;
@@ -2924,13 +3145,14 @@ function exprToCombi(node2, scope, code, mfs) {
2924
3145
  }
2925
3146
  }
2926
3147
  if (callee.name === "balanced") {
2927
- const [openArg, closeArg] = node2.arguments;
3148
+ const [openArg, closeArg, optsArg] = node2.arguments;
2928
3149
  if (!openArg || !closeArg || openArg.type === "SpreadElement" || closeArg.type === "SpreadElement") return null;
2929
3150
  const open = anyValue(openArg, scope, code, []);
2930
3151
  const close = anyValue(closeArg, scope, code, []);
2931
3152
  if (typeof open !== "string" || typeof close !== "string") return null;
3153
+ const opts = optsArg && optsArg.type !== "SpreadElement" ? anyValue(optsArg, scope, code, []) : void 0;
2932
3154
  try {
2933
- return balanced(open, close);
3155
+ return balanced(open, close, opts);
2934
3156
  } catch {
2935
3157
  return null;
2936
3158
  }