parseman 0.31.0 → 0.32.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/analysis/gating.d.ts +115 -0
- package/dist/analysis/gating.d.ts.map +1 -0
- package/dist/combinators/first-set.d.ts +25 -0
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/gate.d.ts +24 -0
- package/dist/combinators/gate.d.ts.map +1 -0
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/guard.d.ts +5 -12
- package/dist/combinators/guard.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +7 -0
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/scanTo.d.ts +45 -3
- package/dist/combinators/scanTo.d.ts.map +1 -1
- package/dist/compiler/codegen.d.ts +78 -14
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/index.cjs +715 -237
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +715 -237
- package/dist/index.js.map +4 -4
- package/dist/language-service/index.cjs +2 -0
- package/dist/language-service/index.cjs.map +2 -2
- package/dist/language-service/index.js +2 -0
- package/dist/language-service/index.js.map +2 -2
- package/dist/plugin/index.cjs +741 -249
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +741 -249
- package/dist/plugin/index.js.map +4 -4
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.js.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +17 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +2 -1
package/dist/plugin/index.cjs
CHANGED
|
@@ -130,10 +130,13 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
130
130
|
return true;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
function isZeroWidthAssertion(p) {
|
|
134
|
+
return p._def.tag === "not";
|
|
135
|
+
}
|
|
133
136
|
function sequenceFirstSet(parsers) {
|
|
134
137
|
let fs2 = empty();
|
|
135
138
|
for (const p of parsers) {
|
|
136
|
-
fs2 = union(fs2, p._meta.firstSet);
|
|
139
|
+
if (!isZeroWidthAssertion(p)) fs2 = union(fs2, p._meta.firstSet);
|
|
137
140
|
if (!matchesEmpty(p)) return fs2;
|
|
138
141
|
}
|
|
139
142
|
return fs2;
|
|
@@ -163,7 +166,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
163
166
|
case "sequence": {
|
|
164
167
|
let out = empty();
|
|
165
168
|
for (const term of d.parsers) {
|
|
166
|
-
out = union(out, fs2(term));
|
|
169
|
+
if (!isZeroWidthAssertion(term)) out = union(out, fs2(term));
|
|
167
170
|
if (!matchesEmpty(term)) return out;
|
|
168
171
|
}
|
|
169
172
|
return out;
|
|
@@ -488,9 +491,9 @@ var SPACE_RANGES = [
|
|
|
488
491
|
[12288, 12288],
|
|
489
492
|
[65279, 65279]
|
|
490
493
|
];
|
|
491
|
-
function shorthandRanges(
|
|
492
|
-
if (
|
|
493
|
-
if (
|
|
494
|
+
function shorthandRanges(ch2) {
|
|
495
|
+
if (ch2 === "d") return [[48, 57]];
|
|
496
|
+
if (ch2 === "s") return SPACE_RANGES;
|
|
494
497
|
return [[48, 57], [65, 90], [97, 122], [95, 95]];
|
|
495
498
|
}
|
|
496
499
|
function readUnicodeEscape(body, i) {
|
|
@@ -503,9 +506,9 @@ function parseClassRanges(body) {
|
|
|
503
506
|
const ranges = [];
|
|
504
507
|
let i = 0;
|
|
505
508
|
const readAtom = () => {
|
|
506
|
-
const
|
|
507
|
-
if (
|
|
508
|
-
if (
|
|
509
|
+
const ch2 = body[i];
|
|
510
|
+
if (ch2 === void 0) return null;
|
|
511
|
+
if (ch2 === "\\") {
|
|
509
512
|
const uni = readUnicodeEscape(body, i);
|
|
510
513
|
if (uni) {
|
|
511
514
|
i = uni.next;
|
|
@@ -519,8 +522,8 @@ function parseClassRanges(body) {
|
|
|
519
522
|
if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
|
|
520
523
|
return { cp: e.codePointAt(0) };
|
|
521
524
|
}
|
|
522
|
-
i +=
|
|
523
|
-
return { cp:
|
|
525
|
+
i += ch2.length;
|
|
526
|
+
return { cp: ch2.codePointAt(0) };
|
|
524
527
|
};
|
|
525
528
|
while (i < body.length) {
|
|
526
529
|
const lo = readAtom();
|
|
@@ -587,12 +590,35 @@ function shortScanner(source, flags) {
|
|
|
587
590
|
return minOne && end === pos ? null : end;
|
|
588
591
|
};
|
|
589
592
|
}
|
|
593
|
+
function asciiCaseFold(fs2) {
|
|
594
|
+
if (fs2.kind !== "ranges") return fs2;
|
|
595
|
+
const ranges = fs2.ranges.map((r) => ({ lo: r.lo, hi: r.hi }));
|
|
596
|
+
const add = (lo, hi) => {
|
|
597
|
+
if (lo <= hi) ranges.push({ lo, hi });
|
|
598
|
+
};
|
|
599
|
+
for (const r of fs2.ranges) {
|
|
600
|
+
const uLo = Math.max(r.lo, 65), uHi = Math.min(r.hi, 90);
|
|
601
|
+
add(uLo + 32, uHi + 32);
|
|
602
|
+
const lLo = Math.max(r.lo, 97), lHi = Math.min(r.hi, 122);
|
|
603
|
+
add(lLo - 32, lHi - 32);
|
|
604
|
+
}
|
|
605
|
+
ranges.sort((a, b) => a.lo - b.lo);
|
|
606
|
+
const merged = [];
|
|
607
|
+
for (const r of ranges) {
|
|
608
|
+
const last = merged[merged.length - 1];
|
|
609
|
+
if (last && r.lo <= last.hi + 1) last.hi = Math.max(last.hi, r.hi);
|
|
610
|
+
else merged.push({ lo: r.lo, hi: r.hi });
|
|
611
|
+
}
|
|
612
|
+
return { kind: "ranges", ranges: merged };
|
|
613
|
+
}
|
|
590
614
|
function regex(pattern, flags = "") {
|
|
591
615
|
const source = typeof pattern === "string" ? pattern : pattern.source;
|
|
592
616
|
const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
|
|
593
617
|
const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
|
|
594
618
|
const scan = shortScanner(source, resolvedFlags);
|
|
595
|
-
const
|
|
619
|
+
const raw = (firstSetAnalyzer ?? permissiveFirstSet)(source);
|
|
620
|
+
const firstSet2 = !resolvedFlags.includes("i") ? raw.firstSet : resolvedFlags.includes("u") || resolvedFlags.includes("v") ? any() : asciiCaseFold(raw.firstSet);
|
|
621
|
+
const canMatchNewline = raw.canMatchNewline;
|
|
596
622
|
const meta = { firstSet: firstSet2, canMatchNewline, isTrivia: false };
|
|
597
623
|
return {
|
|
598
624
|
_tag: "regex",
|
|
@@ -647,8 +673,8 @@ function parseRegex(src) {
|
|
|
647
673
|
const parseSeq = () => {
|
|
648
674
|
const parts = [];
|
|
649
675
|
while (i < src.length) {
|
|
650
|
-
const
|
|
651
|
-
if (
|
|
676
|
+
const ch2 = src[i];
|
|
677
|
+
if (ch2 === "|" || ch2 === ")") break;
|
|
652
678
|
parts.push(parseQuantified());
|
|
653
679
|
}
|
|
654
680
|
if (parts.length === 0) return EMPTY2;
|
|
@@ -656,18 +682,18 @@ function parseRegex(src) {
|
|
|
656
682
|
};
|
|
657
683
|
const parseQuantified = () => {
|
|
658
684
|
const atom = parseAtom();
|
|
659
|
-
const
|
|
685
|
+
const ch2 = src[i];
|
|
660
686
|
let min = null;
|
|
661
|
-
if (
|
|
687
|
+
if (ch2 === "*") {
|
|
662
688
|
min = 0;
|
|
663
689
|
i++;
|
|
664
|
-
} else if (
|
|
690
|
+
} else if (ch2 === "+") {
|
|
665
691
|
min = 1;
|
|
666
692
|
i++;
|
|
667
|
-
} else if (
|
|
693
|
+
} else if (ch2 === "?") {
|
|
668
694
|
min = 0;
|
|
669
695
|
i++;
|
|
670
|
-
} else if (
|
|
696
|
+
} else if (ch2 === "{") {
|
|
671
697
|
min = parseBraceQuantifier();
|
|
672
698
|
}
|
|
673
699
|
if (min === null) return atom;
|
|
@@ -683,8 +709,8 @@ function parseRegex(src) {
|
|
|
683
709
|
return Number.parseInt(m[1], 10);
|
|
684
710
|
};
|
|
685
711
|
const parseAtom = () => {
|
|
686
|
-
const
|
|
687
|
-
switch (
|
|
712
|
+
const ch2 = src[i];
|
|
713
|
+
switch (ch2) {
|
|
688
714
|
case "(":
|
|
689
715
|
return parseGroup();
|
|
690
716
|
case "[":
|
|
@@ -705,8 +731,8 @@ function parseRegex(src) {
|
|
|
705
731
|
return charNode(fromRange(123, 123), false);
|
|
706
732
|
// a `{` with nothing to quantify is a literal brace
|
|
707
733
|
default:
|
|
708
|
-
i +=
|
|
709
|
-
return charNode(fromRange(
|
|
734
|
+
i += ch2.length;
|
|
735
|
+
return charNode(fromRange(ch2.codePointAt(0), ch2.codePointAt(0)), ch2 === "\n");
|
|
710
736
|
}
|
|
711
737
|
};
|
|
712
738
|
const parseGroup = () => {
|
|
@@ -1139,8 +1165,8 @@ function choice(...args) {
|
|
|
1139
1165
|
}
|
|
1140
1166
|
}
|
|
1141
1167
|
if (idx >= 0) {
|
|
1142
|
-
const
|
|
1143
|
-
if (
|
|
1168
|
+
const gate2 = gates[idx];
|
|
1169
|
+
if (gate2 && !gate2(ctx.state)) {
|
|
1144
1170
|
return { ok: false, expected: deriveExpected(parsers[idx]), span: { start: pos, end: pos } };
|
|
1145
1171
|
}
|
|
1146
1172
|
const result = parsers[idx].parse(input, pos, ctx);
|
|
@@ -1486,11 +1512,11 @@ function tryFastLabeledScan(input, cur, trivia2) {
|
|
|
1486
1512
|
function recordTriviaChunks(ctx, chunks) {
|
|
1487
1513
|
const kinds = ctx.triviaKindLabels;
|
|
1488
1514
|
const mask = ctx._triviaCaptureMask;
|
|
1489
|
-
for (const
|
|
1490
|
-
pushTriviaLogEntry(ctx,
|
|
1515
|
+
for (const ch2 of chunks) {
|
|
1516
|
+
pushTriviaLogEntry(ctx, ch2.start, ch2.end, kinds ? ch2.kindIndex : void 0);
|
|
1491
1517
|
if (ctx.captureTrivia && (ctx._cstBuf !== void 0 || ctx._cstTriviaLog !== void 0)) {
|
|
1492
|
-
if (mask === void 0 || kinds === void 0 || (mask & 1 <<
|
|
1493
|
-
pushCstTriviaEntry(ctx,
|
|
1518
|
+
if (mask === void 0 || kinds === void 0 || (mask & 1 << ch2.kindIndex) !== 0) {
|
|
1519
|
+
pushCstTriviaEntry(ctx, ch2.start, ch2.end, kinds ? ch2.kindIndex : void 0);
|
|
1494
1520
|
}
|
|
1495
1521
|
}
|
|
1496
1522
|
}
|
|
@@ -2229,6 +2255,12 @@ function rules(a, b) {
|
|
|
2229
2255
|
if (rule && !rule._meta.isTrivia) rule._meta.grammarTrivia = options.trivia;
|
|
2230
2256
|
}
|
|
2231
2257
|
}
|
|
2258
|
+
if (options?.scanSkip != null) {
|
|
2259
|
+
for (const key of Object.keys(definitions)) {
|
|
2260
|
+
const rule = cache[key];
|
|
2261
|
+
if (rule && !rule._meta.isTrivia) rule._meta.grammarScanSkip = options.scanSkip;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2232
2264
|
for (const key of Object.keys(definitions)) {
|
|
2233
2265
|
markUnusedValues(cache[key]);
|
|
2234
2266
|
}
|
|
@@ -2797,6 +2829,413 @@ function leaf(root, fn) {
|
|
|
2797
2829
|
};
|
|
2798
2830
|
}
|
|
2799
2831
|
|
|
2832
|
+
// src/version.ts
|
|
2833
|
+
var PARSEMAN_VERSION = "0.32.0";
|
|
2834
|
+
|
|
2835
|
+
// src/analysis/gating.ts
|
|
2836
|
+
var isAny = (fs2) => fs2.kind === "any";
|
|
2837
|
+
function intersects2(a, b) {
|
|
2838
|
+
if (a.kind === "any" || b.kind === "any") return true;
|
|
2839
|
+
if (a.kind === "empty" || b.kind === "empty") return false;
|
|
2840
|
+
for (const ra of a.ranges) for (const rb of b.ranges) if (ra.lo <= rb.hi && rb.lo <= ra.hi) return true;
|
|
2841
|
+
return false;
|
|
2842
|
+
}
|
|
2843
|
+
function intersection(a, b) {
|
|
2844
|
+
if (a.kind === "any") return b;
|
|
2845
|
+
if (b.kind === "any") return a;
|
|
2846
|
+
if (a.kind === "empty" || b.kind === "empty") return { kind: "empty" };
|
|
2847
|
+
const ranges = [];
|
|
2848
|
+
for (const ra of a.ranges) for (const rb of b.ranges) {
|
|
2849
|
+
const lo = Math.max(ra.lo, rb.lo);
|
|
2850
|
+
const hi = Math.min(ra.hi, rb.hi);
|
|
2851
|
+
if (lo <= hi) ranges.push({ lo, hi });
|
|
2852
|
+
}
|
|
2853
|
+
return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
|
|
2854
|
+
}
|
|
2855
|
+
function combine(sets) {
|
|
2856
|
+
let acc = { kind: "empty" };
|
|
2857
|
+
for (const s of sets) {
|
|
2858
|
+
if (s.kind === "any") return { kind: "any" };
|
|
2859
|
+
if (s.kind === "empty") continue;
|
|
2860
|
+
acc = acc.kind === "ranges" ? { kind: "ranges", ranges: [...acc.ranges, ...s.ranges] } : s;
|
|
2861
|
+
}
|
|
2862
|
+
return acc;
|
|
2863
|
+
}
|
|
2864
|
+
var ch = (c) => c >= 32 && c < 127 ? `'${String.fromCharCode(c)}'` : `\\u${c}`;
|
|
2865
|
+
function firstSetToString(fs2) {
|
|
2866
|
+
if (fs2.kind === "any") return "ANY";
|
|
2867
|
+
if (fs2.kind === "empty") return "(empty)";
|
|
2868
|
+
return fs2.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
|
|
2869
|
+
}
|
|
2870
|
+
var ruleNameOf = (p) => p._ruleName;
|
|
2871
|
+
var SUGGESTIONS = {
|
|
2872
|
+
"broad-recognizer": "if this arm leads with a keyword regex, use word('kw', boundary) / keywords([...]) \u2014 they expose an EXACT, gating first-set and lower to the same charCodeAt scan. A genuine scanTo fallback is fine; accept it in the gating snapshot if intentional.",
|
|
2873
|
+
"leading-not": "let the arm lead with its actual consuming terminal (first-sets gate it automatically); keep not(...) only as a TRAILING boundary. Do not hand-roll not(not(...)) to fake gating.",
|
|
2874
|
+
"nullable-prefix": "a leading optional/many lets a later, broad term start the arm. Split the empty case into its own arm, or gate on the prefix first char.",
|
|
2875
|
+
"cross-artifact-ref": "parseman >=0.32.0 resolves a g.Foo ref first-set at fuse time; if still ANY the target rule is itself ungated \u2014 analyze it and give it a concrete non-nullable lead.",
|
|
2876
|
+
"opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
|
|
2877
|
+
"ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
|
|
2878
|
+
};
|
|
2879
|
+
function classifyBroadArm(arm) {
|
|
2880
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2881
|
+
const walk2 = (p) => {
|
|
2882
|
+
if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
|
|
2883
|
+
seen.add(p);
|
|
2884
|
+
const d = p._def;
|
|
2885
|
+
switch (d.tag) {
|
|
2886
|
+
case "literal":
|
|
2887
|
+
case "regex":
|
|
2888
|
+
case "keywords":
|
|
2889
|
+
return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
|
|
2890
|
+
case "not":
|
|
2891
|
+
return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
|
|
2892
|
+
case "scanTo":
|
|
2893
|
+
return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
|
|
2894
|
+
// `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
|
|
2895
|
+
// its own — it is the poison. `withCtx`/`recover` FORWARD their inner
|
|
2896
|
+
// first-set (see firstSetOf), so a broad result comes from the inner parser:
|
|
2897
|
+
// walk into it for a precise cause.
|
|
2898
|
+
case "guard":
|
|
2899
|
+
return { cause: "opaque-wrapper", detail: `opaque wrapper (${d.tag})` };
|
|
2900
|
+
// attempt/withCtx/recover FORWARD their inner first-set (transparent for
|
|
2901
|
+
// dispatch), so a broad result comes from the inner parser — walk into it.
|
|
2902
|
+
case "attempt":
|
|
2903
|
+
case "withCtx":
|
|
2904
|
+
case "recover":
|
|
2905
|
+
return walk2(d.parser);
|
|
2906
|
+
case "lazy": {
|
|
2907
|
+
const name = p._ruleName;
|
|
2908
|
+
try {
|
|
2909
|
+
const inner = walk2(d.thunk());
|
|
2910
|
+
if (name !== void 0) return inner ? { cause: "cross-artifact-ref", detail: `via ref g.${name} \u2192 ${inner.detail}` } : null;
|
|
2911
|
+
return inner;
|
|
2912
|
+
} catch {
|
|
2913
|
+
return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}` };
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
case "optional":
|
|
2917
|
+
case "many": {
|
|
2918
|
+
const inner = walk2(d.parser);
|
|
2919
|
+
return { cause: "nullable-prefix", detail: inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term" };
|
|
2920
|
+
}
|
|
2921
|
+
case "oneOrMore":
|
|
2922
|
+
case "transform":
|
|
2923
|
+
case "label":
|
|
2924
|
+
case "field":
|
|
2925
|
+
case "trivia":
|
|
2926
|
+
case "token":
|
|
2927
|
+
case "leaf":
|
|
2928
|
+
case "node":
|
|
2929
|
+
case "grammar":
|
|
2930
|
+
case "expect":
|
|
2931
|
+
return walk2(d.parser);
|
|
2932
|
+
case "skip":
|
|
2933
|
+
return walk2(d.main);
|
|
2934
|
+
case "sequence": {
|
|
2935
|
+
let sawNullablePrefix = false;
|
|
2936
|
+
for (const t of d.parsers) {
|
|
2937
|
+
if (t._def.tag === "not") {
|
|
2938
|
+
sawNullablePrefix = true;
|
|
2939
|
+
continue;
|
|
2940
|
+
}
|
|
2941
|
+
if (isAny(firstSetOf(t))) {
|
|
2942
|
+
const inner = walk2(t);
|
|
2943
|
+
if (sawNullablePrefix)
|
|
2944
|
+
return { cause: "nullable-prefix", detail: inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term" };
|
|
2945
|
+
return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
|
|
2946
|
+
}
|
|
2947
|
+
if (matchesEmpty(t)) {
|
|
2948
|
+
sawNullablePrefix = true;
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
return null;
|
|
2952
|
+
}
|
|
2953
|
+
return { cause: "broad-recognizer", detail: "sequence of only nullable/zero-width terms" };
|
|
2954
|
+
}
|
|
2955
|
+
case "choice": {
|
|
2956
|
+
for (const a of d.parsers) {
|
|
2957
|
+
const r = walk2(a);
|
|
2958
|
+
if (r) return { cause: r.cause, detail: `choice arm \u2192 ${r.detail}` };
|
|
2959
|
+
}
|
|
2960
|
+
return null;
|
|
2961
|
+
}
|
|
2962
|
+
default:
|
|
2963
|
+
return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
return walk2(arm) ?? { cause: "broad-recognizer", detail: "broad (cause not localized)" };
|
|
2967
|
+
}
|
|
2968
|
+
function peelToLeading(arm) {
|
|
2969
|
+
let d = arm._def;
|
|
2970
|
+
let cur = arm;
|
|
2971
|
+
for (; ; ) {
|
|
2972
|
+
if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label" || d.tag === "attempt") {
|
|
2973
|
+
cur = d.parser;
|
|
2974
|
+
d = cur._def;
|
|
2975
|
+
continue;
|
|
2976
|
+
}
|
|
2977
|
+
break;
|
|
2978
|
+
}
|
|
2979
|
+
if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
|
|
2980
|
+
return cur;
|
|
2981
|
+
}
|
|
2982
|
+
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
2983
|
+
function detectAntiPatterns(rule, arms) {
|
|
2984
|
+
const out = [];
|
|
2985
|
+
arms.forEach((arm, i) => {
|
|
2986
|
+
const lead = peelToLeading(arm);
|
|
2987
|
+
const ld = lead._def;
|
|
2988
|
+
if (ld.tag === "not") {
|
|
2989
|
+
const inner = ld.parser;
|
|
2990
|
+
if (inner._def.tag === "not") {
|
|
2991
|
+
out.push({
|
|
2992
|
+
kind: "double-not",
|
|
2993
|
+
rule,
|
|
2994
|
+
armIndex: i,
|
|
2995
|
+
message: "not(not(...)) hand-rolls automatic first-char gating and MISCOMPILES among shared-first-char sibling arms (its first-set is ANY, poisoning dispatch). Remove it; let the arm lead with its consuming terminal, or use word()/keywords() for an exact first-set."
|
|
2996
|
+
});
|
|
2997
|
+
} else {
|
|
2998
|
+
out.push({
|
|
2999
|
+
kind: "leading-not",
|
|
3000
|
+
rule,
|
|
3001
|
+
armIndex: i,
|
|
3002
|
+
message: "a leading not(...) on a choice arm hand-rolls gating and poisons the choice first-set (not() is ANY). Reorder so a consuming terminal leads; keep not(...) as a TRAILING boundary only."
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
if (ld.tag === "regex" && KEYWORD_REGEX_RE.test(ld.source)) {
|
|
3007
|
+
out.push({
|
|
3008
|
+
kind: "keyword-regex",
|
|
3009
|
+
rule,
|
|
3010
|
+
armIndex: i,
|
|
3011
|
+
message: `regex(/${ld.source}/) leads this arm but is a keyword \u2014 use word('\u2026', boundary) / keywords([\u2026]) for an EXACT resolvable first-set (they lower to the same charCodeAt scan).`
|
|
3012
|
+
});
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
return out;
|
|
3016
|
+
}
|
|
3017
|
+
function analyzeGating(entry, opts) {
|
|
3018
|
+
const raw = [];
|
|
3019
|
+
const antiPatterns = [];
|
|
3020
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3021
|
+
const visit = (p, enclosingRule) => {
|
|
3022
|
+
if (visited.has(p)) return;
|
|
3023
|
+
visited.add(p);
|
|
3024
|
+
const d = p._def;
|
|
3025
|
+
const rule = ruleNameOf(p) ?? enclosingRule;
|
|
3026
|
+
if (d.tag === "choice") {
|
|
3027
|
+
raw.push({ g: analyzeChoice(p, d, rule), rule });
|
|
3028
|
+
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
3029
|
+
}
|
|
3030
|
+
const rec = d;
|
|
3031
|
+
const kids = [];
|
|
3032
|
+
if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
|
|
3033
|
+
for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
|
|
3034
|
+
if (rec[k]) kids.push(rec[k]);
|
|
3035
|
+
if (Array.isArray(rec.skip)) kids.push(...rec.skip);
|
|
3036
|
+
if (d.tag === "lazy") {
|
|
3037
|
+
try {
|
|
3038
|
+
kids.push(d.thunk());
|
|
3039
|
+
} catch {
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
for (const k of kids) visit(k, rule);
|
|
3043
|
+
};
|
|
3044
|
+
visit(entry, ruleNameOf(entry) ?? "<entry>");
|
|
3045
|
+
const perRule = /* @__PURE__ */ new Map();
|
|
3046
|
+
for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
|
|
3047
|
+
const seenInRule = /* @__PURE__ */ new Map();
|
|
3048
|
+
const accept = new Set(opts?.accept ?? []);
|
|
3049
|
+
const usedAccept = /* @__PURE__ */ new Set();
|
|
3050
|
+
const choices = raw.map(({ g, rule }) => {
|
|
3051
|
+
const n = seenInRule.get(rule) ?? 0;
|
|
3052
|
+
seenInRule.set(rule, n + 1);
|
|
3053
|
+
const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
|
|
3054
|
+
const isAccepted = g.gates === "no" && accept.has(id);
|
|
3055
|
+
if (isAccepted) usedAccept.add(id);
|
|
3056
|
+
return { ...g, id, accepted: isAccepted };
|
|
3057
|
+
});
|
|
3058
|
+
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
3059
|
+
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
3060
|
+
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted);
|
|
3061
|
+
const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
|
|
3062
|
+
const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
|
|
3063
|
+
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, acceptedUnused, choices, antiPatterns };
|
|
3064
|
+
}
|
|
3065
|
+
function analyzeChoice(p, d, rule) {
|
|
3066
|
+
const arms = d.parsers;
|
|
3067
|
+
const shallow = arms.map((a) => a._meta.firstSet);
|
|
3068
|
+
const deep = arms.map((a) => firstSetOf(a));
|
|
3069
|
+
const anyArms = deep.map((fs2, index) => ({ fs: fs2, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
|
|
3070
|
+
const { cause, detail } = classifyBroadArm(arms[index]);
|
|
3071
|
+
return { index, cause, detail, shallowAnyOnly: false, suggestion: SUGGESTIONS[cause] };
|
|
3072
|
+
});
|
|
3073
|
+
const overlaps = [];
|
|
3074
|
+
for (let i = 0; i < deep.length; i++)
|
|
3075
|
+
for (let j = i + 1; j < deep.length; j++)
|
|
3076
|
+
if (!isAny(deep[i]) && !isAny(deep[j]) && intersects2(deep[i], deep[j]))
|
|
3077
|
+
overlaps.push({
|
|
3078
|
+
a: i,
|
|
3079
|
+
b: j,
|
|
3080
|
+
on: intersection(deep[i], deep[j]),
|
|
3081
|
+
suggestion: "arms share a first char \u2014 left-factor. parseman auto-detects the sharedPrefix strategy for bare sequences (choice.ts); make the arms bare sequences with a common leading terminal, or restructure."
|
|
3082
|
+
});
|
|
3083
|
+
let gates;
|
|
3084
|
+
if (d.disjoint) gates = "yes";
|
|
3085
|
+
else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
|
|
3086
|
+
else gates = "no";
|
|
3087
|
+
return {
|
|
3088
|
+
rule,
|
|
3089
|
+
strategy: d.strategy?.tag ?? "firstMatch",
|
|
3090
|
+
gates,
|
|
3091
|
+
combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
|
|
3092
|
+
anyArms,
|
|
3093
|
+
overlaps
|
|
3094
|
+
};
|
|
3095
|
+
}
|
|
3096
|
+
function formatGatingWarnings(report) {
|
|
3097
|
+
const lines = [];
|
|
3098
|
+
for (const c of report.ungated) {
|
|
3099
|
+
lines.push(`parseman gating: choice @ ${c.id} is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms.`);
|
|
3100
|
+
for (const a of c.anyArms)
|
|
3101
|
+
lines.push(` \xB7 arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
|
|
3102
|
+
fix: ${a.suggestion}`);
|
|
3103
|
+
for (const o of c.overlaps)
|
|
3104
|
+
lines.push(` \xB7 arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
|
|
3105
|
+
fix: ${o.suggestion}`);
|
|
3106
|
+
lines.push(` (intentional? accept it in the gating snapshot: { accept: ['${c.id}'] }.)`);
|
|
3107
|
+
}
|
|
3108
|
+
for (const ap of report.antiPatterns)
|
|
3109
|
+
lines.push(`parseman anti-pattern [${ap.kind}] @ ${ap.rule} arm[${ap.armIndex}]: ${ap.message}`);
|
|
3110
|
+
return lines;
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
// src/combinators/scanTo.ts
|
|
3114
|
+
function resolveScanSkip(explicitSkip, raw, ctx) {
|
|
3115
|
+
if (raw) return [];
|
|
3116
|
+
const trivia2 = ctx.trivia;
|
|
3117
|
+
const ambient = ctx.scanSkip;
|
|
3118
|
+
if (!trivia2 && !ambient) return explicitSkip;
|
|
3119
|
+
const out = [];
|
|
3120
|
+
if (trivia2) out.push(trivia2);
|
|
3121
|
+
if (ambient) out.push(...ambient);
|
|
3122
|
+
out.push(...explicitSkip);
|
|
3123
|
+
return out;
|
|
3124
|
+
}
|
|
3125
|
+
function scanTo(sentinel, { skip: skip2, raw = false, orEOF = false } = {}) {
|
|
3126
|
+
const meta = {
|
|
3127
|
+
firstSet: any(),
|
|
3128
|
+
canMatchNewline: true,
|
|
3129
|
+
isTrivia: false
|
|
3130
|
+
};
|
|
3131
|
+
const explicitSkip = skip2 ?? [];
|
|
3132
|
+
return {
|
|
3133
|
+
_tag: "scanTo",
|
|
3134
|
+
_meta: meta,
|
|
3135
|
+
_def: { tag: "scanTo", sentinel, skip: explicitSkip, raw, orEOF },
|
|
3136
|
+
parse(input, pos, ctx) {
|
|
3137
|
+
let cur = pos;
|
|
3138
|
+
const skip3 = resolveScanSkip(explicitSkip, raw, ctx);
|
|
3139
|
+
const probeCtx = {
|
|
3140
|
+
trackLines: ctx.trackLines,
|
|
3141
|
+
state: ctx.state,
|
|
3142
|
+
...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
|
|
3143
|
+
};
|
|
3144
|
+
const emit2 = (end) => {
|
|
3145
|
+
if (end > pos && cstCaptureActive(ctx)) {
|
|
3146
|
+
const leaf2 = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
|
|
3147
|
+
pushCstLeaf(ctx, leaf2);
|
|
3148
|
+
}
|
|
3149
|
+
};
|
|
3150
|
+
while (cur < input.length) {
|
|
3151
|
+
const s = sentinel.parse(input, cur, probeCtx);
|
|
3152
|
+
if (s.ok) {
|
|
3153
|
+
emit2(cur);
|
|
3154
|
+
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
3155
|
+
}
|
|
3156
|
+
let advanced = false;
|
|
3157
|
+
for (const skipper of skip3) {
|
|
3158
|
+
const r = skipper.parse(input, cur, probeCtx);
|
|
3159
|
+
if (r.ok && r.span.end > cur) {
|
|
3160
|
+
cur = r.span.end;
|
|
3161
|
+
advanced = true;
|
|
3162
|
+
break;
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
if (!advanced) cur++;
|
|
3166
|
+
}
|
|
3167
|
+
if (orEOF) {
|
|
3168
|
+
emit2(cur);
|
|
3169
|
+
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
3170
|
+
}
|
|
3171
|
+
const sentDef = sentinel._def;
|
|
3172
|
+
const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
|
|
3173
|
+
return { ok: false, expected, span: { start: pos, end: cur } };
|
|
3174
|
+
}
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
function balanced(open, close, options = {}) {
|
|
3178
|
+
const ownSkip = options.skip ?? [];
|
|
3179
|
+
const combi = buildBalancedInterior(open, close, ownSkip);
|
|
3180
|
+
if (options.raw) return combi;
|
|
3181
|
+
const eagerParse = combi.parse.bind(combi);
|
|
3182
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3183
|
+
combi._balancedAmbient = { open, close, ownSkip };
|
|
3184
|
+
combi.parse = (input, pos, ctx) => {
|
|
3185
|
+
const amb = ctx.scanSkip;
|
|
3186
|
+
if (!amb || amb.length === 0) return eagerParse(input, pos, ctx);
|
|
3187
|
+
let interior = cache.get(amb);
|
|
3188
|
+
if (!interior) {
|
|
3189
|
+
interior = buildBalancedInterior(open, close, [...amb, ...ownSkip]);
|
|
3190
|
+
cache.set(amb, interior);
|
|
3191
|
+
}
|
|
3192
|
+
return interior.parse(input, pos, ctx);
|
|
3193
|
+
};
|
|
3194
|
+
return combi;
|
|
3195
|
+
}
|
|
3196
|
+
function buildBalancedInterior(open, close, skips) {
|
|
3197
|
+
const self = ref();
|
|
3198
|
+
const stop = /* @__PURE__ */ new Set([open, close]);
|
|
3199
|
+
let bounded = true;
|
|
3200
|
+
for (const sk of skips) {
|
|
3201
|
+
const cs = firstSetClassChars(sk);
|
|
3202
|
+
if (cs === null) {
|
|
3203
|
+
bounded = false;
|
|
3204
|
+
break;
|
|
3205
|
+
}
|
|
3206
|
+
for (const ch2 of cs) stop.add(ch2);
|
|
3207
|
+
}
|
|
3208
|
+
const cls = [...stop].map(escapeClassChar).join("");
|
|
3209
|
+
const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
|
|
3210
|
+
const inner = many(choice(self, ...skips, content));
|
|
3211
|
+
const combi = transform(
|
|
3212
|
+
sequence(literal(open), inner, expect(literal(close))),
|
|
3213
|
+
// parts: strings (content/self) or arrays (a sequence-shaped skip) or a
|
|
3214
|
+
// ParseError (recovered close). `c` is the close string or a ParseError.
|
|
3215
|
+
([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
|
|
3216
|
+
);
|
|
3217
|
+
if (combi._def.tag === "transform") {
|
|
3218
|
+
combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
|
|
3219
|
+
combi._def.recognitionOnly = true;
|
|
3220
|
+
}
|
|
3221
|
+
self.define(combi);
|
|
3222
|
+
return combi;
|
|
3223
|
+
}
|
|
3224
|
+
function firstSetClassChars(c) {
|
|
3225
|
+
const fs2 = c._meta.firstSet;
|
|
3226
|
+
if (fs2.kind === "empty") return [];
|
|
3227
|
+
if (fs2.kind !== "ranges") return null;
|
|
3228
|
+
const out = [];
|
|
3229
|
+
for (const { lo, hi } of fs2.ranges) {
|
|
3230
|
+
if (hi - lo > 8) return null;
|
|
3231
|
+
for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
|
|
3232
|
+
}
|
|
3233
|
+
return out;
|
|
3234
|
+
}
|
|
3235
|
+
function escapeClassChar(ch2) {
|
|
3236
|
+
return ch2.replace(/[\\\]^-]/g, "\\$&");
|
|
3237
|
+
}
|
|
3238
|
+
|
|
2800
3239
|
// src/compiler/grammar-coverage-ids.ts
|
|
2801
3240
|
function children(def, winners) {
|
|
2802
3241
|
switch (def.tag) {
|
|
@@ -2909,16 +3348,16 @@ function literalCodePoints(frag) {
|
|
|
2909
3348
|
const out = [];
|
|
2910
3349
|
let i = 0;
|
|
2911
3350
|
while (i < frag.length) {
|
|
2912
|
-
const
|
|
2913
|
-
if (
|
|
3351
|
+
const ch2 = frag[i];
|
|
3352
|
+
if (ch2 === "\\") {
|
|
2914
3353
|
const e = frag[i + 1];
|
|
2915
3354
|
if (e === void 0) return null;
|
|
2916
3355
|
out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
|
|
2917
3356
|
i += 2;
|
|
2918
3357
|
continue;
|
|
2919
3358
|
}
|
|
2920
|
-
if (META.has(
|
|
2921
|
-
out.push(
|
|
3359
|
+
if (META.has(ch2)) return null;
|
|
3360
|
+
out.push(ch2.codePointAt(0));
|
|
2922
3361
|
i += 1;
|
|
2923
3362
|
}
|
|
2924
3363
|
return out.length ? out : null;
|
|
@@ -2997,8 +3436,8 @@ function parseSeqParts(source) {
|
|
|
2997
3436
|
};
|
|
2998
3437
|
let i = 0;
|
|
2999
3438
|
while (i < source.length) {
|
|
3000
|
-
const
|
|
3001
|
-
if (
|
|
3439
|
+
const ch2 = source[i];
|
|
3440
|
+
if (ch2 === "[") {
|
|
3002
3441
|
let k = i + 1;
|
|
3003
3442
|
const neg = source[k] === "^";
|
|
3004
3443
|
if (neg) k++;
|
|
@@ -3020,7 +3459,7 @@ function parseSeqParts(source) {
|
|
|
3020
3459
|
i += runFrom(ranges, neg, i);
|
|
3021
3460
|
continue;
|
|
3022
3461
|
}
|
|
3023
|
-
if (
|
|
3462
|
+
if (ch2 === "\\") {
|
|
3024
3463
|
const e = source[i + 1];
|
|
3025
3464
|
if (e === "d" || e === "w" || e === "s") {
|
|
3026
3465
|
flush();
|
|
@@ -3049,7 +3488,7 @@ function parseSeqParts(source) {
|
|
|
3049
3488
|
} else lit.push(cp2);
|
|
3050
3489
|
continue;
|
|
3051
3490
|
}
|
|
3052
|
-
if (
|
|
3491
|
+
if (ch2 === "(") {
|
|
3053
3492
|
if (source[i + 1] !== "?" || source[i + 2] !== ":") return null;
|
|
3054
3493
|
let k = i + 3;
|
|
3055
3494
|
let depth = 1;
|
|
@@ -3098,9 +3537,9 @@ function parseSeqParts(source) {
|
|
|
3098
3537
|
}
|
|
3099
3538
|
continue;
|
|
3100
3539
|
}
|
|
3101
|
-
if (META.has(
|
|
3102
|
-
const cp =
|
|
3103
|
-
i +=
|
|
3540
|
+
if (META.has(ch2)) return null;
|
|
3541
|
+
const cp = ch2.codePointAt(0);
|
|
3542
|
+
i += ch2.length;
|
|
3104
3543
|
if (source[i] === "?") {
|
|
3105
3544
|
flush();
|
|
3106
3545
|
parts.push({ part: "lit", cps: [cp], optional: true });
|
|
@@ -3277,12 +3716,12 @@ function unwrapOuterGroup(source) {
|
|
|
3277
3716
|
let i = 3;
|
|
3278
3717
|
let ok = true;
|
|
3279
3718
|
while (i < source.length && depth > 0) {
|
|
3280
|
-
const
|
|
3281
|
-
if (
|
|
3719
|
+
const ch2 = source[i];
|
|
3720
|
+
if (ch2 === "\\") {
|
|
3282
3721
|
i += 2;
|
|
3283
3722
|
continue;
|
|
3284
3723
|
}
|
|
3285
|
-
if (
|
|
3724
|
+
if (ch2 === "[") {
|
|
3286
3725
|
let j = i + 1;
|
|
3287
3726
|
if (source[j] === "^") j++;
|
|
3288
3727
|
while (j < source.length && source[j] !== "]") {
|
|
@@ -3296,8 +3735,8 @@ function unwrapOuterGroup(source) {
|
|
|
3296
3735
|
i = j + 1;
|
|
3297
3736
|
continue;
|
|
3298
3737
|
}
|
|
3299
|
-
if (
|
|
3300
|
-
else if (
|
|
3738
|
+
if (ch2 === "(") depth++;
|
|
3739
|
+
else if (ch2 === ")") depth--;
|
|
3301
3740
|
i++;
|
|
3302
3741
|
}
|
|
3303
3742
|
if (!ok || depth !== 0 || i !== source.length) break;
|
|
@@ -3312,12 +3751,12 @@ function splitTopLevelAlternation(source) {
|
|
|
3312
3751
|
let last = 0;
|
|
3313
3752
|
let i = 0;
|
|
3314
3753
|
while (i < body.length) {
|
|
3315
|
-
const
|
|
3316
|
-
if (
|
|
3754
|
+
const ch2 = body[i];
|
|
3755
|
+
if (ch2 === "\\") {
|
|
3317
3756
|
i += 2;
|
|
3318
3757
|
continue;
|
|
3319
3758
|
}
|
|
3320
|
-
if (
|
|
3759
|
+
if (ch2 === "[") {
|
|
3321
3760
|
let j = i + 1;
|
|
3322
3761
|
if (body[j] === "^") j++;
|
|
3323
3762
|
while (j < body.length && body[j] !== "]") {
|
|
@@ -3328,18 +3767,18 @@ function splitTopLevelAlternation(source) {
|
|
|
3328
3767
|
i = j + 1;
|
|
3329
3768
|
continue;
|
|
3330
3769
|
}
|
|
3331
|
-
if (
|
|
3770
|
+
if (ch2 === "(") {
|
|
3332
3771
|
depth++;
|
|
3333
3772
|
i++;
|
|
3334
3773
|
continue;
|
|
3335
3774
|
}
|
|
3336
|
-
if (
|
|
3775
|
+
if (ch2 === ")") {
|
|
3337
3776
|
depth--;
|
|
3338
3777
|
if (depth < 0) return null;
|
|
3339
3778
|
i++;
|
|
3340
3779
|
continue;
|
|
3341
3780
|
}
|
|
3342
|
-
if (
|
|
3781
|
+
if (ch2 === "|" && depth === 0) {
|
|
3343
3782
|
parts.push(body.slice(last, i));
|
|
3344
3783
|
last = i + 1;
|
|
3345
3784
|
i++;
|
|
@@ -3458,12 +3897,12 @@ function splitDelimArms(body) {
|
|
|
3458
3897
|
let last = 0;
|
|
3459
3898
|
let i = 0;
|
|
3460
3899
|
while (i < body.length) {
|
|
3461
|
-
const
|
|
3462
|
-
if (
|
|
3900
|
+
const ch2 = body[i];
|
|
3901
|
+
if (ch2 === "\\") {
|
|
3463
3902
|
i += 2;
|
|
3464
3903
|
continue;
|
|
3465
3904
|
}
|
|
3466
|
-
if (
|
|
3905
|
+
if (ch2 === "[") {
|
|
3467
3906
|
let j = i + 1;
|
|
3468
3907
|
if (body[j] === "^") j++;
|
|
3469
3908
|
while (j < body.length && body[j] !== "]") {
|
|
@@ -3474,18 +3913,18 @@ function splitDelimArms(body) {
|
|
|
3474
3913
|
i = j + 1;
|
|
3475
3914
|
continue;
|
|
3476
3915
|
}
|
|
3477
|
-
if (
|
|
3916
|
+
if (ch2 === "(") {
|
|
3478
3917
|
depth++;
|
|
3479
3918
|
i++;
|
|
3480
3919
|
continue;
|
|
3481
3920
|
}
|
|
3482
|
-
if (
|
|
3921
|
+
if (ch2 === ")") {
|
|
3483
3922
|
depth--;
|
|
3484
3923
|
if (depth < 0) return null;
|
|
3485
3924
|
i++;
|
|
3486
3925
|
continue;
|
|
3487
3926
|
}
|
|
3488
|
-
if (
|
|
3927
|
+
if (ch2 === "|" && depth === 0) {
|
|
3489
3928
|
arms.push(body.slice(last, i));
|
|
3490
3929
|
last = i + 1;
|
|
3491
3930
|
}
|
|
@@ -3537,12 +3976,12 @@ function parseDelimited(source) {
|
|
|
3537
3976
|
let k = i + 3;
|
|
3538
3977
|
let depth = 1;
|
|
3539
3978
|
while (k < source.length && depth > 0) {
|
|
3540
|
-
const
|
|
3541
|
-
if (
|
|
3979
|
+
const ch2 = source[k];
|
|
3980
|
+
if (ch2 === "\\") {
|
|
3542
3981
|
k += 2;
|
|
3543
3982
|
continue;
|
|
3544
3983
|
}
|
|
3545
|
-
if (
|
|
3984
|
+
if (ch2 === "[") {
|
|
3546
3985
|
let j = k + 1;
|
|
3547
3986
|
if (source[j] === "^") j++;
|
|
3548
3987
|
while (j < source.length && source[j] !== "]") {
|
|
@@ -3553,8 +3992,8 @@ function parseDelimited(source) {
|
|
|
3553
3992
|
k = j + 1;
|
|
3554
3993
|
continue;
|
|
3555
3994
|
}
|
|
3556
|
-
if (
|
|
3557
|
-
else if (
|
|
3995
|
+
if (ch2 === "(") depth++;
|
|
3996
|
+
else if (ch2 === ")") depth--;
|
|
3558
3997
|
k++;
|
|
3559
3998
|
}
|
|
3560
3999
|
if (depth !== 0) return null;
|
|
@@ -4226,58 +4665,88 @@ function tryInlineDestructureTransform(src, valueVars) {
|
|
|
4226
4665
|
|
|
4227
4666
|
// src/compiler/codegen.ts
|
|
4228
4667
|
function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
|
|
4229
|
-
if (seen.has(p)) return { concrete: empty(), refs: [] };
|
|
4230
|
-
seen.add(p);
|
|
4231
4668
|
const d = p._def;
|
|
4669
|
+
if (d.tag === "lazy") {
|
|
4670
|
+
const name = p._ruleName;
|
|
4671
|
+
if (name !== void 0) return { alts: [[{ set: empty(), nullable: false, ref: name }]] };
|
|
4672
|
+
}
|
|
4673
|
+
if (seen.has(p)) return { alts: [[]] };
|
|
4674
|
+
seen.add(p);
|
|
4232
4675
|
const rec = (c) => leadingFirstSetRecipe(c, seen);
|
|
4233
|
-
const
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4676
|
+
const seg = (set, nullable2) => ({ set, nullable: nullable2 });
|
|
4677
|
+
const forceNullable = (r) => ({ alts: r.alts.map((alt) => alt.map((s) => ({ ...s, nullable: true }))) });
|
|
4678
|
+
const collapse = (r, nullable2) => {
|
|
4679
|
+
let set = empty();
|
|
4680
|
+
let hasRef = false;
|
|
4681
|
+
for (const alt of r.alts) {
|
|
4682
|
+
for (const s of alt) {
|
|
4683
|
+
if (s.ref !== void 0) hasRef = true;
|
|
4684
|
+
set = union(set, s.set);
|
|
4685
|
+
if (!s.nullable) break;
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
return seg(hasRef ? any() : set, nullable2);
|
|
4689
|
+
};
|
|
4690
|
+
const compute = () => {
|
|
4691
|
+
switch (d.tag) {
|
|
4692
|
+
case "lazy": {
|
|
4693
|
+
try {
|
|
4694
|
+
return rec(d.thunk());
|
|
4695
|
+
} catch {
|
|
4696
|
+
return { alts: [[seg(any(), false)]] };
|
|
4697
|
+
}
|
|
4242
4698
|
}
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4699
|
+
case "literal":
|
|
4700
|
+
case "regex":
|
|
4701
|
+
case "keywords":
|
|
4702
|
+
return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
|
|
4703
|
+
case "choice": {
|
|
4704
|
+
const alts = [];
|
|
4705
|
+
for (const arm of d.parsers) alts.push(...rec(arm).alts);
|
|
4706
|
+
return { alts };
|
|
4707
|
+
}
|
|
4708
|
+
case "sequence": {
|
|
4709
|
+
const chain = [];
|
|
4710
|
+
for (const term of d.parsers) {
|
|
4711
|
+
if (!isZeroWidthAssertion(term)) {
|
|
4712
|
+
const tr = rec(term);
|
|
4713
|
+
if (tr.alts.length === 1) chain.push(...tr.alts[0]);
|
|
4714
|
+
else chain.push(collapse(tr, matchesEmpty(term)));
|
|
4715
|
+
}
|
|
4716
|
+
if (!matchesEmpty(term)) return { alts: [chain] };
|
|
4717
|
+
}
|
|
4718
|
+
return { alts: [chain] };
|
|
4258
4719
|
}
|
|
4259
|
-
|
|
4720
|
+
// `many`/`optional` are skippable as a whole → their leading chars start the
|
|
4721
|
+
// sequence but the chain must continue past them (force nullable). `oneOrMore`
|
|
4722
|
+
// requires at least one repetition, so it is NOT made nullable here.
|
|
4723
|
+
case "many":
|
|
4724
|
+
case "optional":
|
|
4725
|
+
return forceNullable(rec(d.parser));
|
|
4726
|
+
case "oneOrMore":
|
|
4727
|
+
case "transform":
|
|
4728
|
+
case "label":
|
|
4729
|
+
case "field":
|
|
4730
|
+
case "trivia":
|
|
4731
|
+
case "token":
|
|
4732
|
+
case "leaf":
|
|
4733
|
+
case "node":
|
|
4734
|
+
case "grammar":
|
|
4735
|
+
case "expect":
|
|
4736
|
+
return rec(d.parser);
|
|
4737
|
+
case "sepBy":
|
|
4738
|
+
return rec(d.parser);
|
|
4739
|
+
case "skip":
|
|
4740
|
+
return rec(d.main);
|
|
4741
|
+
// not / scanTo / guard / withCtx / recover / unknown → shallow set (safe).
|
|
4742
|
+
default:
|
|
4743
|
+
return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
|
|
4260
4744
|
}
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
case "field":
|
|
4267
|
-
case "trivia":
|
|
4268
|
-
case "token":
|
|
4269
|
-
case "leaf":
|
|
4270
|
-
case "node":
|
|
4271
|
-
case "grammar":
|
|
4272
|
-
case "expect":
|
|
4273
|
-
return rec(d.parser);
|
|
4274
|
-
case "sepBy":
|
|
4275
|
-
return rec(d.parser);
|
|
4276
|
-
case "skip":
|
|
4277
|
-
return rec(d.main);
|
|
4278
|
-
// not / scanTo / guard / withCtx / recover / unknown → shallow set (safe).
|
|
4279
|
-
default:
|
|
4280
|
-
return { concrete: p._meta.firstSet, refs: [] };
|
|
4745
|
+
};
|
|
4746
|
+
try {
|
|
4747
|
+
return compute();
|
|
4748
|
+
} finally {
|
|
4749
|
+
seen.delete(p);
|
|
4281
4750
|
}
|
|
4282
4751
|
}
|
|
4283
4752
|
var HOST_READS_DECL = "const _hostReads = (b, n) => { if (b === undefined) return false; let s; try { s = Function.prototype.toString.call(b) } catch (e) { return true } if (/\\barguments\\b/.test(s)) return true; const m = /^[^(]*\\(([\\s\\S]*?)\\)/.exec(s); if (m && /\\.\\.\\.|=/.test(m[1])) return true; return b.length > n }";
|
|
@@ -4481,16 +4950,19 @@ function ensureTriviaFn(ctx) {
|
|
|
4481
4950
|
const savedIndent = ctx.indent;
|
|
4482
4951
|
const savedFailLabel = ctx.failLabel;
|
|
4483
4952
|
const savedTrivia = ctx.activeTrivia;
|
|
4953
|
+
const savedScanSkip = ctx.activeScanSkip;
|
|
4484
4954
|
const savedCapAsTrivia = ctx.capAsTrivia;
|
|
4485
4955
|
ctx.indent = 2;
|
|
4486
4956
|
ctx.failLabel = "_triv";
|
|
4487
4957
|
ctx.capAsTrivia = true;
|
|
4488
4958
|
ctx.activeTrivia = void 0;
|
|
4959
|
+
ctx.activeScanSkip = void 0;
|
|
4489
4960
|
const r = emit(trivia2, ctx, "_pos");
|
|
4490
4961
|
ctx.indent = savedIndent;
|
|
4491
4962
|
ctx.failLabel = savedFailLabel;
|
|
4492
4963
|
ctx.capAsTrivia = savedCapAsTrivia;
|
|
4493
4964
|
ctx.activeTrivia = savedTrivia;
|
|
4965
|
+
ctx.activeScanSkip = savedScanSkip;
|
|
4494
4966
|
ctx.namedFnDecls.push([
|
|
4495
4967
|
`function ${fnName}(input, _pos, _ctx, _cap) {`,
|
|
4496
4968
|
` let _e = _pos`,
|
|
@@ -4768,7 +5240,7 @@ function emitKeywordsFast(def, ctx, pos) {
|
|
|
4768
5240
|
const bodyInd = ind(ctx) + " ";
|
|
4769
5241
|
const tries = [];
|
|
4770
5242
|
for (const w of def.words) {
|
|
4771
|
-
const cps = Array.from(w, (
|
|
5243
|
+
const cps = Array.from(w, (ch2) => ch2.codePointAt(0));
|
|
4772
5244
|
let shape = def.caseInsensitive ? { kind: "litFold", open: cps } : { kind: "seq", parts: [{ part: "lit", cps, optional: false }] };
|
|
4773
5245
|
if (boundary) {
|
|
4774
5246
|
shape = { kind: "lookahead", inner: shape, ranges: boundary.ranges, classNegated: boundary.negated, negative: true };
|
|
@@ -4961,7 +5433,7 @@ function canMatchEmptyAtStart(p) {
|
|
|
4961
5433
|
const d = p._def;
|
|
4962
5434
|
switch (d.tag) {
|
|
4963
5435
|
case "regex":
|
|
4964
|
-
return
|
|
5436
|
+
return regexMatchesEmpty(d.source, d.flags);
|
|
4965
5437
|
case "optional":
|
|
4966
5438
|
return true;
|
|
4967
5439
|
case "many":
|
|
@@ -5013,10 +5485,10 @@ function emitDisjointArm(p, ctx, pos, valV, endV, coverageId) {
|
|
|
5013
5485
|
}
|
|
5014
5486
|
function emitDisjointArmGated(def, i, ctx, pos, valV, endV, coverageId) {
|
|
5015
5487
|
const p = def.parsers[i];
|
|
5016
|
-
const
|
|
5017
|
-
if (!
|
|
5488
|
+
const gate2 = def.gates[i];
|
|
5489
|
+
if (!gate2) return emitDisjointArm(p, ctx, pos, valV, endV, coverageId);
|
|
5018
5490
|
const gateIdx = ctx.mapFns.length;
|
|
5019
|
-
ctx.mapFns.push(
|
|
5491
|
+
ctx.mapFns.push(gate2);
|
|
5020
5492
|
ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
|
|
5021
5493
|
const gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
|
|
5022
5494
|
const stmts = [`${ind(ctx)}if (${gateCond}) {`];
|
|
@@ -5174,7 +5646,7 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
|
|
|
5174
5646
|
if (preStmts) stmts.push(...preStmts);
|
|
5175
5647
|
for (let i = 0; i < def.parsers.length; i++) {
|
|
5176
5648
|
const p = def.parsers[i];
|
|
5177
|
-
const
|
|
5649
|
+
const gate2 = def.gates[i];
|
|
5178
5650
|
const autoNot = def.autoNot[i];
|
|
5179
5651
|
const atStart = failsAtStart(p);
|
|
5180
5652
|
const staticFx = armStaticExpected(ctx, p);
|
|
@@ -5186,9 +5658,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
|
|
|
5186
5658
|
})();
|
|
5187
5659
|
const fsGuard = armGuardPrefix === void 0 ? baseFsGuard : baseFsGuard === null ? armGuardPrefix : `${armGuardPrefix} && (${baseFsGuard})`;
|
|
5188
5660
|
let gateCond = null;
|
|
5189
|
-
if (
|
|
5661
|
+
if (gate2) {
|
|
5190
5662
|
const gateIdx = ctx.mapFns.length;
|
|
5191
|
-
ctx.mapFns.push(
|
|
5663
|
+
ctx.mapFns.push(gate2);
|
|
5192
5664
|
ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
|
|
5193
5665
|
gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
|
|
5194
5666
|
}
|
|
@@ -5605,10 +6077,15 @@ function emitScanTo(def, ctx, pos) {
|
|
|
5605
6077
|
const probeCtx = { ...ctx, capturing: false, noHoist: true };
|
|
5606
6078
|
const { stmts: sentStmts, okVar: sentOk } = emitFallible(def.sentinel, probeCtx, curV);
|
|
5607
6079
|
stmts.push(...sentStmts, `${ind(ctx)}if (${sentOk}) { ${foundV} = true; break }`);
|
|
5608
|
-
|
|
6080
|
+
const effectiveSkip = def.raw ? def.skip : [
|
|
6081
|
+
...ctx.activeTrivia ? [ctx.activeTrivia] : [],
|
|
6082
|
+
...ctx.activeScanSkip ?? [],
|
|
6083
|
+
...def.skip
|
|
6084
|
+
];
|
|
6085
|
+
if (effectiveSkip.length > 0) {
|
|
5609
6086
|
const advV = v(ctx, "_stadv");
|
|
5610
6087
|
stmts.push(`${ind(ctx)}let ${advV} = false`);
|
|
5611
|
-
for (const skipper of
|
|
6088
|
+
for (const skipper of effectiveSkip) {
|
|
5612
6089
|
const { stmts: skStmts, okVar: skOk, endVar: skEnd } = emitFallible(skipper, probeCtx, curV);
|
|
5613
6090
|
stmts.push(
|
|
5614
6091
|
`${ind(ctx)}if (!${advV}) {`,
|
|
@@ -6078,6 +6555,24 @@ function emit(p, ctx, pos) {
|
|
|
6078
6555
|
return instrument(dispatch());
|
|
6079
6556
|
}
|
|
6080
6557
|
function emitDispatch(p, ctx, pos) {
|
|
6558
|
+
const bal = p._balancedAmbient;
|
|
6559
|
+
if (bal && ctx.activeScanSkip && ctx.activeScanSkip.length > 0 && !ctx.balancedRebuildStack?.has(p)) {
|
|
6560
|
+
const interior = buildBalancedInterior(bal.open, bal.close, [...ctx.activeScanSkip, ...bal.ownSkip]);
|
|
6561
|
+
markUnusedValues(interior);
|
|
6562
|
+
if (ctx.lazyUsage) {
|
|
6563
|
+
const u = analyzeLazyUsage(interior);
|
|
6564
|
+
for (const [k, n] of u.counts) ctx.lazyUsage.counts.set(k, (ctx.lazyUsage.counts.get(k) ?? 0) + n);
|
|
6565
|
+
for (const k of u.recursive) ctx.lazyUsage.recursive.add(k);
|
|
6566
|
+
for (const [k, s] of u.sizes) ctx.lazyUsage.sizes.set(k, s);
|
|
6567
|
+
}
|
|
6568
|
+
const stack = ctx.balancedRebuildStack ?? (ctx.balancedRebuildStack = /* @__PURE__ */ new Set());
|
|
6569
|
+
stack.add(p);
|
|
6570
|
+
try {
|
|
6571
|
+
return emit(interior, ctx, pos);
|
|
6572
|
+
} finally {
|
|
6573
|
+
stack.delete(p);
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6081
6576
|
const def = p._def;
|
|
6082
6577
|
switch (def.tag) {
|
|
6083
6578
|
case "literal":
|
|
@@ -6499,9 +6994,36 @@ function ruleDependencies(ruleMap) {
|
|
|
6499
6994
|
}
|
|
6500
6995
|
return deps;
|
|
6501
6996
|
}
|
|
6997
|
+
function resolveGatingLevel(opt) {
|
|
6998
|
+
const explicit = typeof opt === "string" ? opt : opt?.level;
|
|
6999
|
+
if (explicit !== void 0) return explicit;
|
|
7000
|
+
const env = typeof process !== "undefined" ? process.env?.PARSEMAN_GATING : void 0;
|
|
7001
|
+
if (env === "off" || env === "warn" || env === "error") return env;
|
|
7002
|
+
return "warn";
|
|
7003
|
+
}
|
|
7004
|
+
function runGatingDiagnostic(combinator, opt) {
|
|
7005
|
+
const level = resolveGatingLevel(opt);
|
|
7006
|
+
if (level === "off") return void 0;
|
|
7007
|
+
const accept = opt !== null && typeof opt === "object" ? opt.accept : void 0;
|
|
7008
|
+
let report;
|
|
7009
|
+
try {
|
|
7010
|
+
report = analyzeGating(combinator, accept ? { accept } : void 0);
|
|
7011
|
+
} catch {
|
|
7012
|
+
return void 0;
|
|
7013
|
+
}
|
|
7014
|
+
const lines = formatGatingWarnings(report);
|
|
7015
|
+
if (lines.length > 0) {
|
|
7016
|
+
if (level === "error") throw new Error(`parseman: ${report.ungated.length} ungated hot choice(s) / ${report.antiPatterns.length} anti-pattern(s)
|
|
7017
|
+
${lines.join("\n")}`);
|
|
7018
|
+
for (const l of lines) console.warn(l);
|
|
7019
|
+
}
|
|
7020
|
+
return report;
|
|
7021
|
+
}
|
|
6502
7022
|
function compile(combinator, mapFnSources, opts) {
|
|
7023
|
+
const gatingReport = runGatingDiagnostic(combinator, opts?.gating);
|
|
6503
7024
|
markUnusedValues(combinator);
|
|
6504
7025
|
const grammarTrivia = combinator._meta.grammarTrivia;
|
|
7026
|
+
const grammarScanSkip = combinator._meta.grammarScanSkip;
|
|
6505
7027
|
const ctx = {
|
|
6506
7028
|
vars: 0,
|
|
6507
7029
|
indent: 1,
|
|
@@ -6523,7 +7045,8 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
6523
7045
|
recovery: opts?.recovery ?? false,
|
|
6524
7046
|
...opts?.coverage ? { coverage: { plan: buildGrammarPlan(combinator), entry: combinator } } : {},
|
|
6525
7047
|
lazyUsage: analyzeLazyUsage(combinator),
|
|
6526
|
-
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
|
|
7048
|
+
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {},
|
|
7049
|
+
...grammarScanSkip ? { activeScanSkip: grammarScanSkip } : {}
|
|
6527
7050
|
};
|
|
6528
7051
|
const r = emit(combinator, ctx, "_pos");
|
|
6529
7052
|
const coverageRootRuleId = ctx.coverage?.plan.rules.get(combinator);
|
|
@@ -6587,6 +7110,7 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
6587
7110
|
source,
|
|
6588
7111
|
inlineExpression,
|
|
6589
7112
|
...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions },
|
|
7113
|
+
...gatingReport === void 0 ? {} : { gating: gatingReport },
|
|
6590
7114
|
parse(input, pos = 0) {
|
|
6591
7115
|
return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, defaultCtx);
|
|
6592
7116
|
},
|
|
@@ -6622,6 +7146,7 @@ function compileRuleMap(ruleMap, opts) {
|
|
|
6622
7146
|
for (const [, rule] of ruleMap) markUnusedValues(rule);
|
|
6623
7147
|
const coverageWinners = opts?.coverage ? Object.fromEntries(ruleMap.filter(([, rule]) => rule._def.tag !== "lazy")) : void 0;
|
|
6624
7148
|
const grammarTrivia = opts?.trivia;
|
|
7149
|
+
const grammarScanSkip = opts?.scanSkip ?? ruleMap.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
|
|
6625
7150
|
const ctx = {
|
|
6626
7151
|
vars: 0,
|
|
6627
7152
|
indent: 1,
|
|
@@ -6643,7 +7168,8 @@ function compileRuleMap(ruleMap, opts) {
|
|
|
6643
7168
|
recovery: opts?.recovery ?? false,
|
|
6644
7169
|
...opts?.coverage ? { coverage: { plan: buildGrammarPlan(ruleMap.map(([, rule]) => rule), coverageWinners) } } : {},
|
|
6645
7170
|
lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
|
|
6646
|
-
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
|
|
7171
|
+
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {},
|
|
7172
|
+
...grammarScanSkip ? { activeScanSkip: grammarScanSkip } : {}
|
|
6647
7173
|
};
|
|
6648
7174
|
const seenNames = /* @__PURE__ */ new Map();
|
|
6649
7175
|
const ruleNames = /* @__PURE__ */ new Map();
|
|
@@ -6657,9 +7183,14 @@ function compileRuleMap(ruleMap, opts) {
|
|
|
6657
7183
|
ctx.ruleNames = ruleNames;
|
|
6658
7184
|
const perEntry = ruleMap.map(([key, rule]) => {
|
|
6659
7185
|
const savedTrivia = ctx.activeTrivia;
|
|
6660
|
-
|
|
7186
|
+
const savedScanSkip = ctx.activeScanSkip;
|
|
7187
|
+
if (rule._meta.isTrivia) {
|
|
7188
|
+
ctx.activeTrivia = void 0;
|
|
7189
|
+
ctx.activeScanSkip = void 0;
|
|
7190
|
+
}
|
|
6661
7191
|
const r = emit(rule, ctx, "_pos");
|
|
6662
7192
|
ctx.activeTrivia = savedTrivia;
|
|
7193
|
+
ctx.activeScanSkip = savedScanSkip;
|
|
6663
7194
|
return { key, rule, r };
|
|
6664
7195
|
});
|
|
6665
7196
|
const derivedSrcs = ctx.mapFnSrcs.length === ctx.mapFns.length && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : void 0;
|
|
@@ -6732,6 +7263,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6732
7263
|
if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
|
|
6733
7264
|
for (const [, rule] of ruleMapArg) markUnusedValues(rule);
|
|
6734
7265
|
const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
|
|
7266
|
+
const grammarScanSkip = opts?.scanSkip ?? ruleMapArg.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
|
|
6735
7267
|
const ruleMap = ruleMapArg.filter(([, val]) => {
|
|
6736
7268
|
const d = val._def;
|
|
6737
7269
|
if (d.tag !== "lazy") return true;
|
|
@@ -6769,7 +7301,8 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6769
7301
|
lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
|
|
6770
7302
|
ns,
|
|
6771
7303
|
deferFirstSetRefs: true,
|
|
6772
|
-
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
|
|
7304
|
+
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {},
|
|
7305
|
+
...grammarScanSkip ? { activeScanSkip: grammarScanSkip } : {}
|
|
6773
7306
|
};
|
|
6774
7307
|
const seen = /* @__PURE__ */ new Map();
|
|
6775
7308
|
const ruleNames = /* @__PURE__ */ new Map();
|
|
@@ -6833,9 +7366,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6833
7366
|
ctx.failLabel = "_pfail";
|
|
6834
7367
|
ctx.recordFail = true;
|
|
6835
7368
|
const savedTrivia = ctx.activeTrivia;
|
|
6836
|
-
|
|
7369
|
+
const savedScanSkip = ctx.activeScanSkip;
|
|
7370
|
+
if (rule._meta.isTrivia) {
|
|
7371
|
+
ctx.activeTrivia = void 0;
|
|
7372
|
+
ctx.activeScanSkip = void 0;
|
|
7373
|
+
}
|
|
6837
7374
|
const body = emit(resolved, ctx, "_pos");
|
|
6838
7375
|
ctx.activeTrivia = savedTrivia;
|
|
7376
|
+
ctx.activeScanSkip = savedScanSkip;
|
|
6839
7377
|
ctx.indent = savedIndent;
|
|
6840
7378
|
ctx.failLabel = savedFail;
|
|
6841
7379
|
ctx.recordFail = savedRec;
|
|
@@ -6882,6 +7420,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6882
7420
|
}
|
|
6883
7421
|
const firstSets = /* @__PURE__ */ new Map();
|
|
6884
7422
|
const firstSetRecipes = /* @__PURE__ */ new Map();
|
|
7423
|
+
const nullable2 = /* @__PURE__ */ new Map();
|
|
6885
7424
|
for (const [key, rule] of ruleMap) {
|
|
6886
7425
|
let resolved = rule;
|
|
6887
7426
|
const d = rule._def;
|
|
@@ -6893,15 +7432,18 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6893
7432
|
}
|
|
6894
7433
|
}
|
|
6895
7434
|
firstSets.set(key, canMatchEmptyAtStart(resolved) ? { kind: "any" } : resolved._meta.firstSet);
|
|
6896
|
-
|
|
7435
|
+
nullable2.set(key, matchesEmpty(resolved));
|
|
7436
|
+
firstSetRecipes.set(key, canMatchEmptyAtStart(resolved) ? { alts: [[{ set: any(), nullable: false }]] } : leadingFirstSetRecipe(resolved));
|
|
6897
7437
|
}
|
|
6898
7438
|
return {
|
|
7439
|
+
v: PARSEMAN_VERSION,
|
|
6899
7440
|
ns,
|
|
6900
7441
|
keys: perEntry.map((e) => e.key),
|
|
6901
7442
|
prelude,
|
|
6902
7443
|
ruleFns,
|
|
6903
7444
|
wrappers,
|
|
6904
7445
|
firstSets,
|
|
7446
|
+
nullable: nullable2,
|
|
6905
7447
|
firstSetRecipes,
|
|
6906
7448
|
deps: ruleDependencies(ruleMap),
|
|
6907
7449
|
needsEmptyTl: !!ctx.needsEmptyTl,
|
|
@@ -6980,101 +7522,6 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootR
|
|
|
6980
7522
|
].join("\n");
|
|
6981
7523
|
}
|
|
6982
7524
|
|
|
6983
|
-
// src/combinators/scanTo.ts
|
|
6984
|
-
function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
|
|
6985
|
-
const meta = {
|
|
6986
|
-
firstSet: any(),
|
|
6987
|
-
canMatchNewline: true,
|
|
6988
|
-
isTrivia: false
|
|
6989
|
-
};
|
|
6990
|
-
return {
|
|
6991
|
-
_tag: "scanTo",
|
|
6992
|
-
_meta: meta,
|
|
6993
|
-
_def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
|
|
6994
|
-
parse(input, pos, ctx) {
|
|
6995
|
-
let cur = pos;
|
|
6996
|
-
const probeCtx = {
|
|
6997
|
-
trackLines: ctx.trackLines,
|
|
6998
|
-
state: ctx.state,
|
|
6999
|
-
...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
|
|
7000
|
-
};
|
|
7001
|
-
const emit2 = (end) => {
|
|
7002
|
-
if (end > pos && cstCaptureActive(ctx)) {
|
|
7003
|
-
const leaf2 = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
|
|
7004
|
-
pushCstLeaf(ctx, leaf2);
|
|
7005
|
-
}
|
|
7006
|
-
};
|
|
7007
|
-
while (cur < input.length) {
|
|
7008
|
-
const s = sentinel.parse(input, cur, probeCtx);
|
|
7009
|
-
if (s.ok) {
|
|
7010
|
-
emit2(cur);
|
|
7011
|
-
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
7012
|
-
}
|
|
7013
|
-
let advanced = false;
|
|
7014
|
-
for (const skipper of skip2) {
|
|
7015
|
-
const r = skipper.parse(input, cur, probeCtx);
|
|
7016
|
-
if (r.ok && r.span.end > cur) {
|
|
7017
|
-
cur = r.span.end;
|
|
7018
|
-
advanced = true;
|
|
7019
|
-
break;
|
|
7020
|
-
}
|
|
7021
|
-
}
|
|
7022
|
-
if (!advanced) cur++;
|
|
7023
|
-
}
|
|
7024
|
-
if (orEOF) {
|
|
7025
|
-
emit2(cur);
|
|
7026
|
-
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
7027
|
-
}
|
|
7028
|
-
const sentDef = sentinel._def;
|
|
7029
|
-
const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
|
|
7030
|
-
return { ok: false, expected, span: { start: pos, end: cur } };
|
|
7031
|
-
}
|
|
7032
|
-
};
|
|
7033
|
-
}
|
|
7034
|
-
function balanced(open, close, options = {}) {
|
|
7035
|
-
const self = ref();
|
|
7036
|
-
const skips = options.skip ?? [];
|
|
7037
|
-
const stop = /* @__PURE__ */ new Set([open, close]);
|
|
7038
|
-
let bounded = true;
|
|
7039
|
-
for (const sk of skips) {
|
|
7040
|
-
const cs = firstSetClassChars(sk);
|
|
7041
|
-
if (cs === null) {
|
|
7042
|
-
bounded = false;
|
|
7043
|
-
break;
|
|
7044
|
-
}
|
|
7045
|
-
for (const ch of cs) stop.add(ch);
|
|
7046
|
-
}
|
|
7047
|
-
const cls = [...stop].map(escapeClassChar).join("");
|
|
7048
|
-
const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
|
|
7049
|
-
const inner = many(choice(self, ...skips, content));
|
|
7050
|
-
const combi = transform(
|
|
7051
|
-
sequence(literal(open), inner, expect(literal(close))),
|
|
7052
|
-
// parts: strings (content/self) or arrays (a sequence-shaped skip) or a
|
|
7053
|
-
// ParseError (recovered close). `c` is the close string or a ParseError.
|
|
7054
|
-
([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
|
|
7055
|
-
);
|
|
7056
|
-
if (combi._def.tag === "transform") {
|
|
7057
|
-
combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
|
|
7058
|
-
combi._def.recognitionOnly = true;
|
|
7059
|
-
}
|
|
7060
|
-
self.define(combi);
|
|
7061
|
-
return combi;
|
|
7062
|
-
}
|
|
7063
|
-
function firstSetClassChars(c) {
|
|
7064
|
-
const fs2 = c._meta.firstSet;
|
|
7065
|
-
if (fs2.kind === "empty") return [];
|
|
7066
|
-
if (fs2.kind !== "ranges") return null;
|
|
7067
|
-
const out = [];
|
|
7068
|
-
for (const { lo, hi } of fs2.ranges) {
|
|
7069
|
-
if (hi - lo > 8) return null;
|
|
7070
|
-
for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
|
|
7071
|
-
}
|
|
7072
|
-
return out;
|
|
7073
|
-
}
|
|
7074
|
-
function escapeClassChar(ch) {
|
|
7075
|
-
return ch.replace(/[\\\]^-]/g, "\\$&");
|
|
7076
|
-
}
|
|
7077
|
-
|
|
7078
7525
|
// src/combinators/withCtx.ts
|
|
7079
7526
|
function withCtx(extra, combinator) {
|
|
7080
7527
|
const meta = {
|
|
@@ -7131,7 +7578,7 @@ function childrenOf2(def) {
|
|
|
7131
7578
|
return [];
|
|
7132
7579
|
}
|
|
7133
7580
|
}
|
|
7134
|
-
var
|
|
7581
|
+
var ruleNameOf2 = (c) => c._ruleName;
|
|
7135
7582
|
function lazyTarget(c) {
|
|
7136
7583
|
if (c._def.tag !== "lazy") return null;
|
|
7137
7584
|
try {
|
|
@@ -7198,15 +7645,15 @@ function evalRuleMapIR(ir) {
|
|
|
7198
7645
|
if (Array.isArray(it)) {
|
|
7199
7646
|
const [src, comb] = it;
|
|
7200
7647
|
gateSrcs.push(src);
|
|
7201
|
-
let
|
|
7648
|
+
let gate2;
|
|
7202
7649
|
try {
|
|
7203
|
-
|
|
7650
|
+
gate2 = (0, eval)(`(${src})`);
|
|
7204
7651
|
} catch {
|
|
7205
|
-
|
|
7652
|
+
gate2 = () => {
|
|
7206
7653
|
throw new Error("IR gate fn not materialized");
|
|
7207
7654
|
};
|
|
7208
7655
|
}
|
|
7209
|
-
return { gate, combinator: comb };
|
|
7656
|
+
return { gate: gate2, combinator: comb };
|
|
7210
7657
|
}
|
|
7211
7658
|
gateSrcs.push(null);
|
|
7212
7659
|
return it;
|
|
@@ -7313,7 +7760,7 @@ var Serializer = class {
|
|
|
7313
7760
|
* its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
|
|
7314
7761
|
* `g[name]` ref — only the top-level entry is unwrapped. */
|
|
7315
7762
|
body(c) {
|
|
7316
|
-
return c._def.tag === "lazy" &&
|
|
7763
|
+
return c._def.tag === "lazy" && ruleNameOf2(c) !== void 0 ? lazyTarget(c) ?? c : c;
|
|
7317
7764
|
}
|
|
7318
7765
|
run() {
|
|
7319
7766
|
for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
|
|
@@ -7338,7 +7785,7 @@ ${body}
|
|
|
7338
7785
|
analyze(c, active) {
|
|
7339
7786
|
const def = c._def;
|
|
7340
7787
|
if (def.tag === "lazy") {
|
|
7341
|
-
if (
|
|
7788
|
+
if (ruleNameOf2(c) !== void 0) return;
|
|
7342
7789
|
const target = lazyTarget(c);
|
|
7343
7790
|
if (!target) throw new Unserializable("unresolved unnamed ref");
|
|
7344
7791
|
if (active.has(target)) {
|
|
@@ -7390,7 +7837,7 @@ ${body}
|
|
|
7390
7837
|
const kid = (x) => this.ref(x, selfOf);
|
|
7391
7838
|
switch (def.tag) {
|
|
7392
7839
|
case "lazy": {
|
|
7393
|
-
if (
|
|
7840
|
+
if (ruleNameOf2(c) !== void 0) return `g[${JSON.stringify(ruleNameOf2(c))}]`;
|
|
7394
7841
|
const target = lazyTarget(c);
|
|
7395
7842
|
if (selfOf && target === selfOf.comb) return selfOf.var;
|
|
7396
7843
|
return this.ref(target, selfOf);
|
|
@@ -7444,7 +7891,7 @@ ${body}
|
|
|
7444
7891
|
case "skip":
|
|
7445
7892
|
return `skip(${kid(def.main)}, ${kid(def.skipped)})`;
|
|
7446
7893
|
case "scanTo":
|
|
7447
|
-
return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}], orEOF: ${def.orEOF} })`;
|
|
7894
|
+
return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}]${def.raw ? ", raw: true" : ""}, orEOF: ${def.orEOF} })`;
|
|
7448
7895
|
case "transform": {
|
|
7449
7896
|
if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
|
|
7450
7897
|
return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)}${def.recognitionOnly ? ", true" : ""})`;
|
|
@@ -7525,6 +7972,18 @@ function pickPieces(pieces, names) {
|
|
|
7525
7972
|
return pieces.map((pc) => ({ ...pc, keys: pc.keys.filter((k) => keep.has(k) && winner.get(k) === pc), ruleFns: filt(pc.ruleFns, pc), wrappers: filt(pc.wrappers, pc), deps: filt(pc.deps, pc) })).filter((pc) => pc.keys.length > 0);
|
|
7526
7973
|
}
|
|
7527
7974
|
function fusedBody(pieces) {
|
|
7975
|
+
for (const p of pieces) {
|
|
7976
|
+
if (p.v === void 0) {
|
|
7977
|
+
throw new Error(
|
|
7978
|
+
`parseman: artifact "${p.ns}" is UNSTAMPED (compiled before the version-lock invariant). Recompile the grammar with parseman ${PARSEMAN_VERSION}; parseman does not fuse unversioned or cross-version artifacts.`
|
|
7979
|
+
);
|
|
7980
|
+
}
|
|
7981
|
+
if (p.v !== PARSEMAN_VERSION) {
|
|
7982
|
+
throw new Error(
|
|
7983
|
+
`parseman: artifact "${p.ns}" was compiled with parseman ${p.v}, but is being fused with parseman ${PARSEMAN_VERSION}. Compiled grammar artifacts are version-locked \u2014 recompile the grammar with parseman ${PARSEMAN_VERSION}; parseman does not fuse across versions.`
|
|
7984
|
+
);
|
|
7985
|
+
}
|
|
7986
|
+
}
|
|
7528
7987
|
const winner = /* @__PURE__ */ new Map();
|
|
7529
7988
|
for (const p of pieces) for (const k of p.keys) winner.set(k, p);
|
|
7530
7989
|
for (const [k, p] of winner) {
|
|
@@ -7550,23 +8009,38 @@ function fusedBody(pieces) {
|
|
|
7550
8009
|
const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
|
|
7551
8010
|
const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
|
|
7552
8011
|
const ANY2 = { kind: "any" };
|
|
8012
|
+
const EMPTY3 = { kind: "empty" };
|
|
7553
8013
|
const recipes = /* @__PURE__ */ new Map();
|
|
7554
8014
|
const shallow = /* @__PURE__ */ new Map();
|
|
8015
|
+
const nullable2 = /* @__PURE__ */ new Map();
|
|
7555
8016
|
for (const [k, p] of winner) {
|
|
7556
8017
|
const r = p.firstSetRecipes?.get(k);
|
|
7557
8018
|
if (r) recipes.set(k, r);
|
|
7558
8019
|
const fs2 = p.firstSets?.get(k);
|
|
7559
8020
|
if (fs2) shallow.set(k, fs2);
|
|
8021
|
+
const nu = p.nullable?.get(k);
|
|
8022
|
+
if (nu !== void 0) nullable2.set(k, nu);
|
|
7560
8023
|
}
|
|
8024
|
+
const knownRef = (ref2) => recipes.has(ref2) || shallow.has(ref2);
|
|
8025
|
+
const refFS = (ref2) => knownRef(ref2) ? finalFS.get(ref2) ?? ANY2 : ANY2;
|
|
8026
|
+
const refNullable = (ref2) => knownRef(ref2) ? nullable2.get(ref2) ?? true : true;
|
|
8027
|
+
const resolveRecipe = (r) => {
|
|
8028
|
+
let out = EMPTY3;
|
|
8029
|
+
for (const chain of r.alts) {
|
|
8030
|
+
for (const seg of chain) {
|
|
8031
|
+
out = union(out, seg.ref !== void 0 ? refFS(seg.ref) : seg.set);
|
|
8032
|
+
const skippable = seg.ref !== void 0 ? seg.nullable || refNullable(seg.ref) : seg.nullable;
|
|
8033
|
+
if (!skippable) break;
|
|
8034
|
+
}
|
|
8035
|
+
}
|
|
8036
|
+
return out;
|
|
8037
|
+
};
|
|
7561
8038
|
const finalFS = /* @__PURE__ */ new Map();
|
|
7562
|
-
for (const [k] of winner) finalFS.set(k, recipes.
|
|
8039
|
+
for (const [k] of winner) finalFS.set(k, recipes.has(k) ? EMPTY3 : shallow.get(k) ?? ANY2);
|
|
7563
8040
|
for (let iter = 0; iter <= recipes.size; iter++) {
|
|
7564
8041
|
let changed = false;
|
|
7565
8042
|
for (const [k, r] of recipes) {
|
|
7566
|
-
|
|
7567
|
-
for (const ref2 of r.refs) {
|
|
7568
|
-
fs2 = union(fs2, recipes.has(ref2) || shallow.has(ref2) ? finalFS.get(ref2) ?? ANY2 : ANY2);
|
|
7569
|
-
}
|
|
8043
|
+
const fs2 = resolveRecipe(r);
|
|
7570
8044
|
if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs2)) {
|
|
7571
8045
|
finalFS.set(k, fs2);
|
|
7572
8046
|
changed = true;
|
|
@@ -7614,8 +8088,8 @@ function materializePiece(p, trivia2, captureTerminals = false) {
|
|
|
7614
8088
|
return pieces;
|
|
7615
8089
|
}
|
|
7616
8090
|
|
|
7617
|
-
// src/combinators/
|
|
7618
|
-
function
|
|
8091
|
+
// src/combinators/gate.ts
|
|
8092
|
+
function gate(predicate) {
|
|
7619
8093
|
const meta = {
|
|
7620
8094
|
firstSet: { kind: "any" },
|
|
7621
8095
|
canMatchNewline: false,
|
|
@@ -7872,17 +8346,17 @@ function isGatedArmExpr(e) {
|
|
|
7872
8346
|
return hasGate && hasCombinator;
|
|
7873
8347
|
}
|
|
7874
8348
|
function gatedArmParts(e) {
|
|
7875
|
-
let
|
|
8349
|
+
let gate2;
|
|
7876
8350
|
let combinator;
|
|
7877
8351
|
for (const prop of e.properties) {
|
|
7878
8352
|
if (prop.type !== "Property") return null;
|
|
7879
8353
|
const op = prop;
|
|
7880
8354
|
const name = propName(op);
|
|
7881
|
-
if (name === "gate")
|
|
8355
|
+
if (name === "gate") gate2 = op.value;
|
|
7882
8356
|
else if (name === "combinator") combinator = op.value;
|
|
7883
8357
|
else return null;
|
|
7884
8358
|
}
|
|
7885
|
-
return
|
|
8359
|
+
return gate2 && combinator ? { gate: gate2, combinator } : null;
|
|
7886
8360
|
}
|
|
7887
8361
|
function isCombinator(v2) {
|
|
7888
8362
|
return !!v2 && typeof v2 === "object" && "_def" in v2;
|
|
@@ -8084,14 +8558,14 @@ function exprToCombi(node2, scope, code, mfs) {
|
|
|
8084
8558
|
return null;
|
|
8085
8559
|
}
|
|
8086
8560
|
}
|
|
8087
|
-
if (callee.name === "guard") {
|
|
8561
|
+
if (callee.name === "gate" || callee.name === "guard") {
|
|
8088
8562
|
if (code === void 0 || mfs === void 0) return null;
|
|
8089
8563
|
const [predArg] = node2.arguments;
|
|
8090
8564
|
if (!predArg || predArg.type === "SpreadElement") return null;
|
|
8091
8565
|
const predSrc = stripTsFromSource(predArg, code);
|
|
8092
8566
|
mfs.push(predSrc);
|
|
8093
8567
|
try {
|
|
8094
|
-
const combi =
|
|
8568
|
+
const combi = gate(() => true);
|
|
8095
8569
|
if (combi._def.tag !== "guard") return null;
|
|
8096
8570
|
combi._def.predSrc = predSrc;
|
|
8097
8571
|
return combi;
|
|
@@ -8715,21 +9189,24 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8715
9189
|
return null;
|
|
8716
9190
|
}
|
|
8717
9191
|
const optionsArg = optionsFirst ? arg0 : arg1;
|
|
8718
|
-
const
|
|
8719
|
-
(p) => p.key?.name ===
|
|
9192
|
+
const optionValue = (name) => optionsArg?.type === "ObjectExpression" ? (optionsArg.properties ?? []).find(
|
|
9193
|
+
(p) => p.key?.name === name
|
|
8720
9194
|
)?.value : void 0;
|
|
9195
|
+
const triviaValue = optionValue("trivia");
|
|
8721
9196
|
const gTrivia = triviaValue ? evaluateExpr(triviaValue, scope, code, []) : void 0;
|
|
8722
|
-
|
|
9197
|
+
const scanSkipValue = optionValue("scanSkip");
|
|
9198
|
+
const gScanSkip = scanSkipValue ? evaluateCombinatorArray(scanSkipValue, scope, code) ?? void 0 : void 0;
|
|
9199
|
+
return { ruleMap, ...gTrivia ? { trivia: gTrivia } : {}, ...gScanSkip ? { scanSkip: gScanSkip } : {} };
|
|
8723
9200
|
};
|
|
8724
9201
|
const compileRulesFactory = (init, label2) => {
|
|
8725
9202
|
const evaluated = evaluateRulesFactory(init, label2);
|
|
8726
9203
|
if (!evaluated) return null;
|
|
8727
|
-
const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, recovery, coverage: grammarCoverage });
|
|
9204
|
+
const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {}, recovery, coverage: grammarCoverage });
|
|
8728
9205
|
if (!compiled) {
|
|
8729
9206
|
warn(init.start, `${label2}: rule map couldn't be inlined`);
|
|
8730
9207
|
return null;
|
|
8731
9208
|
}
|
|
8732
|
-
return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {} };
|
|
9209
|
+
return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
|
|
8733
9210
|
};
|
|
8734
9211
|
const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
|
|
8735
9212
|
const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
|
|
@@ -8737,13 +9214,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8737
9214
|
const isPickCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "pick";
|
|
8738
9215
|
const localRuleMaps = /* @__PURE__ */ new Map();
|
|
8739
9216
|
const localGrammarTrivia = /* @__PURE__ */ new Map();
|
|
9217
|
+
const localGrammarScanSkip = /* @__PURE__ */ new Map();
|
|
8740
9218
|
const nsFor = (label2) => `_${(0, import_node_crypto.createHash)("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
|
|
8741
9219
|
const stripIndent = (s) => s.replace(/\n[ \t]+/g, "\n");
|
|
8742
9220
|
const serializePieces = (p) => {
|
|
8743
9221
|
const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
|
|
8744
9222
|
([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
|
|
8745
9223
|
).join(", ")}])`;
|
|
8746
|
-
return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude.map(stripIndent))}, ruleFns: ${mapLit(p.ruleFns, true)}, wrappers: ${mapLit(p.wrappers, true)}, firstSets: ${mapLit(p.firstSets)}, firstSetRecipes: ${p.firstSetRecipes ? mapLit(p.firstSetRecipes) : "new Map()"}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, hasDirectBuilders: ${p.hasDirectBuilders === true}, isRecognitionOnly: ${p.isRecognitionOnly === true}, mfFns: [], buildFns: [] }`;
|
|
9224
|
+
return `{ v: ${JSON.stringify(p.v)}, ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude.map(stripIndent))}, ruleFns: ${mapLit(p.ruleFns, true)}, wrappers: ${mapLit(p.wrappers, true)}, firstSets: ${mapLit(p.firstSets)}, nullable: ${p.nullable ? mapLit(p.nullable) : "new Map()"}, firstSetRecipes: ${p.firstSetRecipes ? mapLit(p.firstSetRecipes) : "new Map()"}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, hasDirectBuilders: ${p.hasDirectBuilders === true}, isRecognitionOnly: ${p.isRecognitionOnly === true}, mfFns: [], buildFns: [] }`;
|
|
8747
9225
|
};
|
|
8748
9226
|
const isSpread = (it) => "__spreadLocal" in it;
|
|
8749
9227
|
const isIR = (it) => "ir" in it && !("ruleFns" in it);
|
|
@@ -8983,11 +9461,15 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8983
9461
|
}
|
|
8984
9462
|
const localArg = elements[elements.length - 1];
|
|
8985
9463
|
let localRules = null;
|
|
9464
|
+
let localScanSkip;
|
|
8986
9465
|
if (isRulesCall(localArg)) {
|
|
8987
9466
|
const evaluated = evaluateRulesFactory(localArg, `composeLeaf${init.start}`);
|
|
8988
9467
|
localRules = evaluated?.ruleMap ?? null;
|
|
9468
|
+
localScanSkip = evaluated?.scanSkip;
|
|
8989
9469
|
} else if (localArg.type === "Identifier") {
|
|
8990
|
-
|
|
9470
|
+
const name = localArg.name;
|
|
9471
|
+
localRules = localRuleMaps.get(name) ?? null;
|
|
9472
|
+
localScanSkip = localGrammarScanSkip.get(name);
|
|
8991
9473
|
}
|
|
8992
9474
|
if (!localRules) {
|
|
8993
9475
|
warn(init.start, "composeLeaf(): final argument must be a local rules() map");
|
|
@@ -9005,7 +9487,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9005
9487
|
}
|
|
9006
9488
|
try {
|
|
9007
9489
|
const localNs = nsFor(`composeLeaf${init.start}`);
|
|
9008
|
-
const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, recovery });
|
|
9490
|
+
const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, ...localScanSkip ? { scanSkip: localScanSkip } : {}, recovery });
|
|
9009
9491
|
if (!plainLocalPiece) {
|
|
9010
9492
|
warn(init.start, "composeLeaf(): local rules could not be statically compiled");
|
|
9011
9493
|
return null;
|
|
@@ -9107,6 +9589,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9107
9589
|
if (!compiledRules) continue;
|
|
9108
9590
|
localRuleMaps.set(varName, compiledRules.ruleMap);
|
|
9109
9591
|
if (compiledRules.trivia) localGrammarTrivia.set(varName, compiledRules.trivia);
|
|
9592
|
+
if (compiledRules.scanSkip) localGrammarScanSkip.set(varName, compiledRules.scanSkip);
|
|
9110
9593
|
let replacement = withCoverageDefinitions(
|
|
9111
9594
|
compiledRules.replacement,
|
|
9112
9595
|
compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(compiledRules.replacement)
|
|
@@ -9235,9 +9718,18 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9235
9718
|
ms.overwrite(imp.start, imp.end, stripped);
|
|
9236
9719
|
}
|
|
9237
9720
|
}
|
|
9238
|
-
|
|
9721
|
+
const applied = runtimeComposeFallback ? [] : replacements;
|
|
9722
|
+
for (const { start, end, replacement } of applied.slice().sort((a, b) => b.start - a.start)) {
|
|
9239
9723
|
ms.overwrite(start, end, replacement);
|
|
9240
9724
|
}
|
|
9725
|
+
if (applied.length > 0) {
|
|
9726
|
+
ms.prepend(
|
|
9727
|
+
`// Generated by parseman v${PARSEMAN_VERSION} \u2014 DO NOT EDIT.
|
|
9728
|
+
// Version-locked: compile AND fuse/link this artifact with parseman v${PARSEMAN_VERSION} ONLY.
|
|
9729
|
+
// Parseman does not read artifacts across versions; recompile if the version differs.
|
|
9730
|
+
`
|
|
9731
|
+
);
|
|
9732
|
+
}
|
|
9241
9733
|
const unlowered = endLoweringCapture();
|
|
9242
9734
|
if (warnUnloweredRegex) {
|
|
9243
9735
|
for (const src of unlowered) {
|