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.
@@ -1 +1 @@
1
- {"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/plugin/evaluator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EACV,UAAU,EAAE,IAAI,EAMjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAa7C,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AACD,MAAM,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAM3C,KAAK,gBAAgB,GAAG;IAAE,GAAG,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AA8ShE,0EAA0E;AAC1E,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,gBAAgB,GAAG,IAAI,CAUzB;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAC1B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,CAAC,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,UAAU,CAAC,OAAO,CAAC,GAAG,IAAI,CAE5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,UAAU,EACvB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EAAE,GACrB,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAuGzC;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAcnF"}
1
+ {"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/plugin/evaluator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EACV,UAAU,EAAE,IAAI,EAMjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAa7C,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AACD,MAAM,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAM3C,KAAK,gBAAgB,GAAG;IAAE,GAAG,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAmThE,0EAA0E;AAC1E,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,gBAAgB,GAAG,IAAI,CAUzB;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAC1B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,CAAC,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,UAAU,CAAC,OAAO,CAAC,GAAG,IAAI,CAE5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,UAAU,EACvB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EAAE,GACrB,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAuGzC;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAcnF"}
@@ -1757,6 +1757,119 @@ function firstSetCond(codeVar, fs) {
1757
1757
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
1758
1758
  ).join(" || ");
1759
1759
  }
1760
+ function captureRestoreBody(mL, mR, mTl, mLg) {
1761
+ const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
1762
+ return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
1763
+ }
1764
+ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
1765
+ if (seen.has(p)) return false;
1766
+ seen.add(p);
1767
+ const d = p._def;
1768
+ switch (d.tag) {
1769
+ // Atomic / non-capturing-then-failing: a failure happens before any push.
1770
+ case "literal":
1771
+ case "regex":
1772
+ case "keywords":
1773
+ case "guard":
1774
+ case "not":
1775
+ case "trivia":
1776
+ case "scanTo":
1777
+ case "unknown":
1778
+ return false;
1779
+ // node() captures into its own private buffers and rolls them back on
1780
+ // failure (emitNode restores _ctx.* and never pushes on the !ok path).
1781
+ case "node":
1782
+ return false;
1783
+ // choice/firstMatch already roll back each failed arm; on overall failure
1784
+ // nothing committed remains.
1785
+ case "choice":
1786
+ return false;
1787
+ // optional never fails; many/oneOrMore only "fail" with zero captured items.
1788
+ case "optional":
1789
+ case "many":
1790
+ case "oneOrMore":
1791
+ return false;
1792
+ // sepBy emits its own per-iteration rollback in emitSepBy.
1793
+ case "sepBy":
1794
+ return false;
1795
+ // A sequence is the real case: an earlier capturing term followed by a term
1796
+ // that can fail leaves the earlier captures buffered.
1797
+ case "sequence": {
1798
+ const parts = d.parsers;
1799
+ if (parts.length < 2) return parts.some((x) => mayLeavePartialCapture(x, seen));
1800
+ for (let i = 0; i < parts.length - 1; i++) {
1801
+ if (hasNodeDef(parts[i]) || capturesLeaf(parts[i])) return true;
1802
+ }
1803
+ return false;
1804
+ }
1805
+ // Delegating wrappers: defer to the wrapped parser.
1806
+ case "transform":
1807
+ case "label":
1808
+ case "expect":
1809
+ case "withCtx":
1810
+ case "grammar":
1811
+ return mayLeavePartialCapture(d.parser, seen);
1812
+ case "skip":
1813
+ return mayLeavePartialCapture(d.main, seen);
1814
+ case "recover":
1815
+ return mayLeavePartialCapture(d.parser, seen);
1816
+ case "lazy": {
1817
+ try {
1818
+ return mayLeavePartialCapture(d.thunk(), seen);
1819
+ } catch {
1820
+ return true;
1821
+ }
1822
+ }
1823
+ // Unknown shapes: be safe and keep the rollback.
1824
+ default:
1825
+ return true;
1826
+ }
1827
+ }
1828
+ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
1829
+ if (seen.has(p)) return false;
1830
+ seen.add(p);
1831
+ const d = p._def;
1832
+ switch (d.tag) {
1833
+ case "literal":
1834
+ case "regex":
1835
+ case "keywords":
1836
+ case "node":
1837
+ return true;
1838
+ case "not":
1839
+ case "guard":
1840
+ case "trivia":
1841
+ case "unknown":
1842
+ return false;
1843
+ case "sequence":
1844
+ case "choice":
1845
+ return d.parsers.some((x) => capturesLeaf(x, seen));
1846
+ case "sepBy":
1847
+ return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
1848
+ case "many":
1849
+ case "oneOrMore":
1850
+ case "optional":
1851
+ case "transform":
1852
+ case "label":
1853
+ case "expect":
1854
+ case "withCtx":
1855
+ case "grammar":
1856
+ case "recover":
1857
+ return capturesLeaf(d.parser, seen);
1858
+ case "skip":
1859
+ return capturesLeaf(d.main, seen);
1860
+ case "scanTo":
1861
+ return true;
1862
+ case "lazy": {
1863
+ try {
1864
+ return capturesLeaf(d.thunk(), seen);
1865
+ } catch {
1866
+ return true;
1867
+ }
1868
+ }
1869
+ default:
1870
+ return true;
1871
+ }
1872
+ }
1760
1873
  function emitFallible(inner, ctx, pos) {
1761
1874
  const lbl = v(ctx, "_lbl");
1762
1875
  const okV = `${lbl}ok`;
@@ -1770,26 +1883,23 @@ function emitFallible(inner, ctx, pos) {
1770
1883
  ctx.failLabel = savedLabel;
1771
1884
  ctx.indent = savedIndent;
1772
1885
  const ind0 = ind(ctx);
1773
- const mL = ctx.capturing ? v(ctx, "_fcl") : null;
1774
- const mR = ctx.capturing ? v(ctx, "_fcr") : null;
1775
- const mTl = ctx.capturing ? v(ctx, "_fctl") : null;
1776
- const mLg = ctx.capturing ? v(ctx, "_fclg") : null;
1886
+ const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
1887
+ const mL = needsRollback ? v(ctx, "_fcl") : null;
1888
+ const mR = needsRollback ? v(ctx, "_fcr") : null;
1889
+ const mTl = needsRollback ? v(ctx, "_fctl") : null;
1777
1890
  const stmts = [
1778
1891
  `${ind0}let ${okV} = false, ${valV}, ${endV} = ${pos}`,
1779
1892
  ...mL ? [
1780
1893
  `${ind0}const ${mL} = _ctx._cstLeaves?.length ?? 0`,
1781
1894
  `${ind0}const ${mR} = _ctx._cstRawChildren?.length ?? 0`,
1782
- `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
1783
- `${ind0}const ${mLg} = _ctx._triviaLog?.length ?? 0`
1895
+ `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`
1784
1896
  ] : [],
1785
1897
  `${ind0}${lbl}: {`,
1786
1898
  ...r.stmts,
1787
- `${ind0} ${valV} = ${r.valueVar}`,
1788
- `${ind0} ${endV} = ${r.endVar}`,
1789
- `${ind0} ${okV} = true`,
1899
+ `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
1790
1900
  `${ind0}}`,
1791
1901
  ...mL ? [
1792
- `${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} }`
1902
+ `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null)} }`
1793
1903
  ] : []
1794
1904
  ];
1795
1905
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
@@ -2063,11 +2173,13 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2063
2173
  gateCond = `_mf[${gateIdx}](_ctx.state)`;
2064
2174
  }
2065
2175
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
2066
- const markLeaves = ctx.capturing ? v(ctx, "_cml") : null;
2067
- const markRaw = ctx.capturing ? v(ctx, "_cmr") : null;
2068
- const markTl = ctx.capturing ? v(ctx, "_cmtl") : null;
2069
- const markLog = ctx.capturing ? v(ctx, "_cmlg") : null;
2070
- 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}` : "";
2176
+ const armHasAutoNot = !!(autoNot && autoNot.length > 0);
2177
+ const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
2178
+ const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
2179
+ const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
2180
+ const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
2181
+ const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
2182
+ const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
2071
2183
  stmts.push(`${ind0}if (${skipCond}) {`);
2072
2184
  ctx.indent++;
2073
2185
  const ind1 = ind(ctx);
@@ -2097,11 +2209,7 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2097
2209
  stmts.push(`${ind0} }`);
2098
2210
  if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
2099
2211
  } else {
2100
- stmts.push(`${ind0} if (${okVar}) {`);
2101
- stmts.push(`${ind0} ${resValV} = ${valVar}`);
2102
- stmts.push(`${ind0} ${resEndV} = ${endVar}`);
2103
- stmts.push(`${ind0} ${resOkV} = true`);
2104
- stmts.push(`${ind0} }`);
2212
+ stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2105
2213
  if (rollback) stmts.push(`${ind0} else { ${rollback} }`);
2106
2214
  }
2107
2215
  stmts.push(`${ind0}}`);
@@ -2446,6 +2554,22 @@ function emitRecover(def, ctx, pos) {
2446
2554
  ];
2447
2555
  return { stmts, valueVar: valVar, endVar };
2448
2556
  }
2557
+ function emitExpect(def, ctx, pos) {
2558
+ const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
2559
+ const ind0 = ind(ctx);
2560
+ const errV = v(ctx, "_err");
2561
+ const stmts = [
2562
+ ...innerStmts,
2563
+ `${ind0}if (!${okVar}) {`,
2564
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${pos} }, expected: ${JSON.stringify(def.expected)} }`,
2565
+ `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
2566
+ `${ind0} ${valVar} = ${errV}`,
2567
+ `${ind0} ${endVar} = ${pos}`,
2568
+ `${ind0} ${okVar} = true`,
2569
+ `${ind0}}`
2570
+ ];
2571
+ return { stmts, valueVar: valVar, endVar };
2572
+ }
2449
2573
  function emit(p, ctx, pos) {
2450
2574
  const def = p._def;
2451
2575
  switch (def.tag) {
@@ -2554,6 +2678,8 @@ function emit(p, ctx, pos) {
2554
2678
  return emitScanTo(def, ctx, pos);
2555
2679
  case "recover":
2556
2680
  return emitRecover(def, ctx, pos);
2681
+ case "expect":
2682
+ return emitExpect(def, ctx, pos);
2557
2683
  case "guard": {
2558
2684
  const fnIdx = ctx.mapFns.length;
2559
2685
  ctx.mapFns.push(def.predicate);
@@ -2637,6 +2763,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
2637
2763
  return hasNodeDef(d.sentinel, seen) || d.skip.some((x) => hasNodeDef(x, seen));
2638
2764
  case "recover":
2639
2765
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.sentinel, seen);
2766
+ case "expect":
2767
+ return hasNodeDef(d.parser, seen);
2640
2768
  case "withCtx":
2641
2769
  return hasNodeDef(d.parser, seen);
2642
2770
  default:
@@ -2705,6 +2833,11 @@ function compile(combinator, mapFnSources) {
2705
2833
  parseWithContext(input, parseCtx, pos = 0) {
2706
2834
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, parseCtx);
2707
2835
  },
2836
+ // Note: collects recover()/expect() errors via _errors. Unlike interpreter
2837
+ // parse({recover:true}) it does NOT populate furthestFail — the compiled path
2838
+ // inlines failures for throughput and deliberately skips _probe bookkeeping.
2839
+ // Callers wanting a furthest-position diagnostic detect unconsumed input
2840
+ // (span.end < input.length) instead, which is mode-agnostic.
2708
2841
  parseWithErrors(input, pos = 0) {
2709
2842
  const errors = [];
2710
2843
  const result = fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, { ...defaultCtx, _errors: errors });
@@ -2747,6 +2880,58 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
2747
2880
  ].join("\n");
2748
2881
  }
2749
2882
 
2883
+ // src/combinators/expect.ts
2884
+ function staticExpected(c) {
2885
+ const def = c._def;
2886
+ switch (def.tag) {
2887
+ case "literal":
2888
+ return [JSON.stringify(def.value)];
2889
+ case "regex":
2890
+ return [`/${def.source}/`];
2891
+ case "keywords":
2892
+ return def.words.map((w) => JSON.stringify(w));
2893
+ case "label":
2894
+ return [def.label];
2895
+ case "choice":
2896
+ return def.parsers.flatMap(staticExpected);
2897
+ case "sequence":
2898
+ return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
2899
+ case "node":
2900
+ case "grammar":
2901
+ case "trivia":
2902
+ case "optional":
2903
+ case "many":
2904
+ case "oneOrMore":
2905
+ case "transform":
2906
+ case "not":
2907
+ return staticExpected(def.parser);
2908
+ case "lazy":
2909
+ return staticExpected(def.thunk());
2910
+ default:
2911
+ return [];
2912
+ }
2913
+ }
2914
+ function expect(combinator, label2) {
2915
+ const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
2916
+ const meta = {
2917
+ firstSet: combinator._meta.firstSet,
2918
+ canMatchNewline: combinator._meta.canMatchNewline,
2919
+ isTrivia: false
2920
+ };
2921
+ return {
2922
+ _tag: "expect",
2923
+ _meta: meta,
2924
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
2925
+ parse(input, pos, ctx) {
2926
+ const result = combinator.parse(input, pos, ctx);
2927
+ if (result.ok) return result;
2928
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
2929
+ ctx._errors?.push(error);
2930
+ return { ok: true, value: error, span: { start: pos, end: pos } };
2931
+ }
2932
+ };
2933
+ }
2934
+
2750
2935
  // src/combinators/scanTo.ts
2751
2936
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
2752
2937
  const meta = {
@@ -2760,7 +2945,11 @@ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
2760
2945
  _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
2761
2946
  parse(input, pos, ctx) {
2762
2947
  let cur = pos;
2763
- const probeCtx = { trackLines: ctx.trackLines, state: ctx.state };
2948
+ const probeCtx = {
2949
+ trackLines: ctx.trackLines,
2950
+ state: ctx.state,
2951
+ ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
2952
+ };
2764
2953
  const emit2 = (end) => {
2765
2954
  if (end > pos && cstCaptureActive(ctx)) {
2766
2955
  const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
@@ -2795,16 +2984,47 @@ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
2795
2984
  };
2796
2985
  }
2797
2986
  function balanced(open, close, options = {}) {
2798
- const inner = scanTo(literal(close), options);
2987
+ const self = ref();
2988
+ const skips = options.skip ?? [];
2989
+ const stop = /* @__PURE__ */ new Set([open, close]);
2990
+ let bounded = true;
2991
+ for (const sk of skips) {
2992
+ const cs = firstSetClassChars(sk);
2993
+ if (cs === null) {
2994
+ bounded = false;
2995
+ break;
2996
+ }
2997
+ for (const ch of cs) stop.add(ch);
2998
+ }
2999
+ const cls = [...stop].map(escapeClassChar).join("");
3000
+ const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
3001
+ const inner = many(choice(self, ...skips, content));
2799
3002
  const combi = transform(
2800
- sequence(literal(open), inner, literal(close)),
2801
- ([o, content, c]) => o + content + c
3003
+ sequence(literal(open), inner, expect(literal(close), close)),
3004
+ // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
3005
+ // ParseError (recovered close). `c` is the close string or a ParseError.
3006
+ ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
2802
3007
  );
2803
3008
  if (combi._def.tag === "transform") {
2804
- combi._def.fnSrc = "([o, content, c]) => o + content + c";
3009
+ combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
2805
3010
  }
3011
+ self.define(combi);
2806
3012
  return combi;
2807
3013
  }
3014
+ function firstSetClassChars(c) {
3015
+ const fs = c._meta.firstSet;
3016
+ if (fs.kind === "empty") return [];
3017
+ if (fs.kind !== "ranges") return null;
3018
+ const out = [];
3019
+ for (const { lo, hi } of fs.ranges) {
3020
+ if (hi - lo > 8) return null;
3021
+ for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
3022
+ }
3023
+ return out;
3024
+ }
3025
+ function escapeClassChar(ch) {
3026
+ return ch.replace(/[\\\]^-]/g, "\\$&");
3027
+ }
2808
3028
 
2809
3029
  // src/plugin/evaluator.ts
2810
3030
  function isWordFactory(v2) {
@@ -2823,7 +3043,8 @@ var SUPPORTED = {
2823
3043
  sepBy: (...a) => sepBy(a[0], a[1]),
2824
3044
  trivia: (...a) => trivia(a[0]),
2825
3045
  label: (...a) => label(a[0], a[1]),
2826
- noTrivia: (...a) => noTrivia(a[0])
3046
+ noTrivia: (...a) => noTrivia(a[0]),
3047
+ expect: (...a) => expect(a[0], a[1])
2827
3048
  };
2828
3049
  function isScopeEntry(v2) {
2829
3050
  return !!v2 && typeof v2 === "object" && "combi" in v2 && "mfSrcs" in v2;
@@ -2959,13 +3180,14 @@ function exprToCombi(node2, scope, code, mfs) {
2959
3180
  }
2960
3181
  }
2961
3182
  if (callee.name === "balanced") {
2962
- const [openArg, closeArg] = node2.arguments;
3183
+ const [openArg, closeArg, optsArg] = node2.arguments;
2963
3184
  if (!openArg || !closeArg || openArg.type === "SpreadElement" || closeArg.type === "SpreadElement") return null;
2964
3185
  const open = anyValue(openArg, scope, code, []);
2965
3186
  const close = anyValue(closeArg, scope, code, []);
2966
3187
  if (typeof open !== "string" || typeof close !== "string") return null;
3188
+ const opts = optsArg && optsArg.type !== "SpreadElement" ? anyValue(optsArg, scope, code, []) : void 0;
2967
3189
  try {
2968
- return balanced(open, close);
3190
+ return balanced(open, close, opts);
2969
3191
  } catch {
2970
3192
  return null;
2971
3193
  }