parseman 0.31.0 → 0.33.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/ir-serialize.d.ts +1 -1
- package/dist/compiler/ir-serialize.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 +760 -246
- 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 +760 -246
- 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 +803 -270
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +803 -270
- 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.33.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 {
|
|
@@ -7142,9 +7589,9 @@ function lazyTarget(c) {
|
|
|
7142
7589
|
}
|
|
7143
7590
|
var Unserializable = class extends Error {
|
|
7144
7591
|
};
|
|
7145
|
-
function serializeRuleMap(ruleMap) {
|
|
7592
|
+
function serializeRuleMap(ruleMap, scanSkip) {
|
|
7146
7593
|
try {
|
|
7147
|
-
return new Serializer(ruleMap).run();
|
|
7594
|
+
return new Serializer(ruleMap, scanSkip ?? []).run();
|
|
7148
7595
|
} catch (e) {
|
|
7149
7596
|
if (e instanceof Unserializable) {
|
|
7150
7597
|
if (process.env.PARSEMAN_IR_DEBUG) console.error(`[ir] fallback: ${e.message}`);
|
|
@@ -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;
|
|
@@ -7305,40 +7752,75 @@ var Serializer = class {
|
|
|
7305
7752
|
decls = [];
|
|
7306
7753
|
ruleValues = /* @__PURE__ */ new Set();
|
|
7307
7754
|
ruleMap;
|
|
7308
|
-
|
|
7755
|
+
scanSkip;
|
|
7756
|
+
constructor(ruleMap, scanSkip = []) {
|
|
7309
7757
|
this.ruleMap = ruleMap;
|
|
7758
|
+
this.scanSkip = scanSkip;
|
|
7310
7759
|
for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
|
|
7311
7760
|
}
|
|
7761
|
+
/** A grammar-level `scanSkip` unit is an opaque TERMINAL (a string, a balanced
|
|
7762
|
+
* bracket) — it must not reach a grammar-rule ref (`g[name]`), because the
|
|
7763
|
+
* serialized `scanSkip` array sits in the `rules({ scanSkip })` OPTIONS position,
|
|
7764
|
+
* outside the `(g) => …` factory where `g` is bound. A unit that does reference a
|
|
7765
|
+
* rule can't be carried in options; bail to the full-pieces fallback. */
|
|
7766
|
+
assertNoRuleRef(c, seen) {
|
|
7767
|
+
if (seen.has(c)) return;
|
|
7768
|
+
seen.add(c);
|
|
7769
|
+
if (c._def.tag === "lazy") {
|
|
7770
|
+
if (ruleNameOf2(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
|
|
7771
|
+
const target = lazyTarget(c);
|
|
7772
|
+
if (target) this.assertNoRuleRef(target, seen);
|
|
7773
|
+
return;
|
|
7774
|
+
}
|
|
7775
|
+
for (const child of childrenOf2(c._def)) this.assertNoRuleRef(child, seen);
|
|
7776
|
+
}
|
|
7312
7777
|
/** A `rules()` map wraps each rule value in a named `lazy` proxy; the real body is
|
|
7313
7778
|
* its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
|
|
7314
7779
|
* `g[name]` ref — only the top-level entry is unwrapped. */
|
|
7315
7780
|
body(c) {
|
|
7316
|
-
return c._def.tag === "lazy" &&
|
|
7781
|
+
return c._def.tag === "lazy" && ruleNameOf2(c) !== void 0 ? lazyTarget(c) ?? c : c;
|
|
7317
7782
|
}
|
|
7318
7783
|
run() {
|
|
7319
7784
|
for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
|
|
7785
|
+
for (const c of this.scanSkip) {
|
|
7786
|
+
this.assertNoRuleRef(c, /* @__PURE__ */ new Set());
|
|
7787
|
+
this.analyze(c, /* @__PURE__ */ new Set());
|
|
7788
|
+
}
|
|
7320
7789
|
let n = 0;
|
|
7321
7790
|
for (const [c, count] of this.counts) {
|
|
7322
7791
|
if (this.ruleValues.has(c)) continue;
|
|
7323
7792
|
if (count >= 2 || this.selfRef.has(c)) this.constName.set(c, `_s${n++}`);
|
|
7324
7793
|
}
|
|
7794
|
+
const outerDecls = [];
|
|
7795
|
+
const innerDecls = [];
|
|
7796
|
+
let optionsArg = "";
|
|
7797
|
+
if (this.scanSkip.length > 0) {
|
|
7798
|
+
this.decls = outerDecls;
|
|
7799
|
+
const units = this.scanSkip.map((c) => this.ref(c, null));
|
|
7800
|
+
optionsArg = `{ scanSkip: [${units.join(", ")}] }, `;
|
|
7801
|
+
}
|
|
7802
|
+
this.decls = innerDecls;
|
|
7325
7803
|
const body = this.ruleMap.map(([name, c]) => ` ${JSON.stringify(name)}: ${this.wrap(this.body(c))}`).join(",\n");
|
|
7326
|
-
|
|
7804
|
+
const factory = innerDecls.length === 0 ? `(g) => ({
|
|
7327
7805
|
${body}
|
|
7328
|
-
}))
|
|
7329
|
-
|
|
7330
|
-
${this.decls.map((d) => " " + d).join("\n")}
|
|
7806
|
+
})` : `(g) => {
|
|
7807
|
+
${innerDecls.map((d) => " " + d).join("\n")}
|
|
7331
7808
|
return ({
|
|
7332
7809
|
${body}
|
|
7333
7810
|
})
|
|
7334
|
-
}
|
|
7811
|
+
}`;
|
|
7812
|
+
const call = `rules(${optionsArg}${factory})`;
|
|
7813
|
+
return outerDecls.length === 0 ? call : `(() => {
|
|
7814
|
+
${outerDecls.map((d) => " " + d).join("\n")}
|
|
7815
|
+
return ${call}
|
|
7816
|
+
})()`;
|
|
7335
7817
|
}
|
|
7336
7818
|
/** Count identity references and flag self-referential subtrees. `active` is the
|
|
7337
7819
|
* set of combinators on the current DFS path (to detect a lazy pointing back). */
|
|
7338
7820
|
analyze(c, active) {
|
|
7339
7821
|
const def = c._def;
|
|
7340
7822
|
if (def.tag === "lazy") {
|
|
7341
|
-
if (
|
|
7823
|
+
if (ruleNameOf2(c) !== void 0) return;
|
|
7342
7824
|
const target = lazyTarget(c);
|
|
7343
7825
|
if (!target) throw new Unserializable("unresolved unnamed ref");
|
|
7344
7826
|
if (active.has(target)) {
|
|
@@ -7390,7 +7872,7 @@ ${body}
|
|
|
7390
7872
|
const kid = (x) => this.ref(x, selfOf);
|
|
7391
7873
|
switch (def.tag) {
|
|
7392
7874
|
case "lazy": {
|
|
7393
|
-
if (
|
|
7875
|
+
if (ruleNameOf2(c) !== void 0) return `g[${JSON.stringify(ruleNameOf2(c))}]`;
|
|
7394
7876
|
const target = lazyTarget(c);
|
|
7395
7877
|
if (selfOf && target === selfOf.comb) return selfOf.var;
|
|
7396
7878
|
return this.ref(target, selfOf);
|
|
@@ -7444,7 +7926,7 @@ ${body}
|
|
|
7444
7926
|
case "skip":
|
|
7445
7927
|
return `skip(${kid(def.main)}, ${kid(def.skipped)})`;
|
|
7446
7928
|
case "scanTo":
|
|
7447
|
-
return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}], orEOF: ${def.orEOF} })`;
|
|
7929
|
+
return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}]${def.raw ? ", raw: true" : ""}, orEOF: ${def.orEOF} })`;
|
|
7448
7930
|
case "transform": {
|
|
7449
7931
|
if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
|
|
7450
7932
|
return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)}${def.recognitionOnly ? ", true" : ""})`;
|
|
@@ -7525,6 +8007,18 @@ function pickPieces(pieces, names) {
|
|
|
7525
8007
|
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
8008
|
}
|
|
7527
8009
|
function fusedBody(pieces) {
|
|
8010
|
+
for (const p of pieces) {
|
|
8011
|
+
if (p.v === void 0) {
|
|
8012
|
+
throw new Error(
|
|
8013
|
+
`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.`
|
|
8014
|
+
);
|
|
8015
|
+
}
|
|
8016
|
+
if (p.v !== PARSEMAN_VERSION) {
|
|
8017
|
+
throw new Error(
|
|
8018
|
+
`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.`
|
|
8019
|
+
);
|
|
8020
|
+
}
|
|
8021
|
+
}
|
|
7528
8022
|
const winner = /* @__PURE__ */ new Map();
|
|
7529
8023
|
for (const p of pieces) for (const k of p.keys) winner.set(k, p);
|
|
7530
8024
|
for (const [k, p] of winner) {
|
|
@@ -7550,23 +8044,38 @@ function fusedBody(pieces) {
|
|
|
7550
8044
|
const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
|
|
7551
8045
|
const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
|
|
7552
8046
|
const ANY2 = { kind: "any" };
|
|
8047
|
+
const EMPTY3 = { kind: "empty" };
|
|
7553
8048
|
const recipes = /* @__PURE__ */ new Map();
|
|
7554
8049
|
const shallow = /* @__PURE__ */ new Map();
|
|
8050
|
+
const nullable2 = /* @__PURE__ */ new Map();
|
|
7555
8051
|
for (const [k, p] of winner) {
|
|
7556
8052
|
const r = p.firstSetRecipes?.get(k);
|
|
7557
8053
|
if (r) recipes.set(k, r);
|
|
7558
8054
|
const fs2 = p.firstSets?.get(k);
|
|
7559
8055
|
if (fs2) shallow.set(k, fs2);
|
|
8056
|
+
const nu = p.nullable?.get(k);
|
|
8057
|
+
if (nu !== void 0) nullable2.set(k, nu);
|
|
7560
8058
|
}
|
|
8059
|
+
const knownRef = (ref2) => recipes.has(ref2) || shallow.has(ref2);
|
|
8060
|
+
const refFS = (ref2) => knownRef(ref2) ? finalFS.get(ref2) ?? ANY2 : ANY2;
|
|
8061
|
+
const refNullable = (ref2) => knownRef(ref2) ? nullable2.get(ref2) ?? true : true;
|
|
8062
|
+
const resolveRecipe = (r) => {
|
|
8063
|
+
let out = EMPTY3;
|
|
8064
|
+
for (const chain of r.alts) {
|
|
8065
|
+
for (const seg of chain) {
|
|
8066
|
+
out = union(out, seg.ref !== void 0 ? refFS(seg.ref) : seg.set);
|
|
8067
|
+
const skippable = seg.ref !== void 0 ? seg.nullable || refNullable(seg.ref) : seg.nullable;
|
|
8068
|
+
if (!skippable) break;
|
|
8069
|
+
}
|
|
8070
|
+
}
|
|
8071
|
+
return out;
|
|
8072
|
+
};
|
|
7561
8073
|
const finalFS = /* @__PURE__ */ new Map();
|
|
7562
|
-
for (const [k] of winner) finalFS.set(k, recipes.
|
|
8074
|
+
for (const [k] of winner) finalFS.set(k, recipes.has(k) ? EMPTY3 : shallow.get(k) ?? ANY2);
|
|
7563
8075
|
for (let iter = 0; iter <= recipes.size; iter++) {
|
|
7564
8076
|
let changed = false;
|
|
7565
8077
|
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
|
-
}
|
|
8078
|
+
const fs2 = resolveRecipe(r);
|
|
7570
8079
|
if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs2)) {
|
|
7571
8080
|
finalFS.set(k, fs2);
|
|
7572
8081
|
changed = true;
|
|
@@ -7614,8 +8123,8 @@ function materializePiece(p, trivia2, captureTerminals = false) {
|
|
|
7614
8123
|
return pieces;
|
|
7615
8124
|
}
|
|
7616
8125
|
|
|
7617
|
-
// src/combinators/
|
|
7618
|
-
function
|
|
8126
|
+
// src/combinators/gate.ts
|
|
8127
|
+
function gate(predicate) {
|
|
7619
8128
|
const meta = {
|
|
7620
8129
|
firstSet: { kind: "any" },
|
|
7621
8130
|
canMatchNewline: false,
|
|
@@ -7872,17 +8381,17 @@ function isGatedArmExpr(e) {
|
|
|
7872
8381
|
return hasGate && hasCombinator;
|
|
7873
8382
|
}
|
|
7874
8383
|
function gatedArmParts(e) {
|
|
7875
|
-
let
|
|
8384
|
+
let gate2;
|
|
7876
8385
|
let combinator;
|
|
7877
8386
|
for (const prop of e.properties) {
|
|
7878
8387
|
if (prop.type !== "Property") return null;
|
|
7879
8388
|
const op = prop;
|
|
7880
8389
|
const name = propName(op);
|
|
7881
|
-
if (name === "gate")
|
|
8390
|
+
if (name === "gate") gate2 = op.value;
|
|
7882
8391
|
else if (name === "combinator") combinator = op.value;
|
|
7883
8392
|
else return null;
|
|
7884
8393
|
}
|
|
7885
|
-
return
|
|
8394
|
+
return gate2 && combinator ? { gate: gate2, combinator } : null;
|
|
7886
8395
|
}
|
|
7887
8396
|
function isCombinator(v2) {
|
|
7888
8397
|
return !!v2 && typeof v2 === "object" && "_def" in v2;
|
|
@@ -8084,14 +8593,14 @@ function exprToCombi(node2, scope, code, mfs) {
|
|
|
8084
8593
|
return null;
|
|
8085
8594
|
}
|
|
8086
8595
|
}
|
|
8087
|
-
if (callee.name === "guard") {
|
|
8596
|
+
if (callee.name === "gate" || callee.name === "guard") {
|
|
8088
8597
|
if (code === void 0 || mfs === void 0) return null;
|
|
8089
8598
|
const [predArg] = node2.arguments;
|
|
8090
8599
|
if (!predArg || predArg.type === "SpreadElement") return null;
|
|
8091
8600
|
const predSrc = stripTsFromSource(predArg, code);
|
|
8092
8601
|
mfs.push(predSrc);
|
|
8093
8602
|
try {
|
|
8094
|
-
const combi =
|
|
8603
|
+
const combi = gate(() => true);
|
|
8095
8604
|
if (combi._def.tag !== "guard") return null;
|
|
8096
8605
|
combi._def.predSrc = predSrc;
|
|
8097
8606
|
return combi;
|
|
@@ -8715,21 +9224,32 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8715
9224
|
return null;
|
|
8716
9225
|
}
|
|
8717
9226
|
const optionsArg = optionsFirst ? arg0 : arg1;
|
|
8718
|
-
const
|
|
8719
|
-
(p) => p.key?.name ===
|
|
9227
|
+
const optionValue = (name) => optionsArg?.type === "ObjectExpression" ? (optionsArg.properties ?? []).find(
|
|
9228
|
+
(p) => p.key?.name === name
|
|
8720
9229
|
)?.value : void 0;
|
|
9230
|
+
const triviaValue = optionValue("trivia");
|
|
8721
9231
|
const gTrivia = triviaValue ? evaluateExpr(triviaValue, scope, code, []) : void 0;
|
|
8722
|
-
|
|
9232
|
+
const scanSkipValue = optionValue("scanSkip");
|
|
9233
|
+
const gScanSkip = scanSkipValue ? evaluateCombinatorArray(scanSkipValue, scope, code) ?? void 0 : void 0;
|
|
9234
|
+
if (gScanSkip) {
|
|
9235
|
+
for (const rule of ruleMap.values()) {
|
|
9236
|
+
if (rule && !rule._meta.isTrivia) {
|
|
9237
|
+
;
|
|
9238
|
+
rule._meta.grammarScanSkip = gScanSkip;
|
|
9239
|
+
}
|
|
9240
|
+
}
|
|
9241
|
+
}
|
|
9242
|
+
return { ruleMap, ...gTrivia ? { trivia: gTrivia } : {}, ...gScanSkip ? { scanSkip: gScanSkip } : {} };
|
|
8723
9243
|
};
|
|
8724
9244
|
const compileRulesFactory = (init, label2) => {
|
|
8725
9245
|
const evaluated = evaluateRulesFactory(init, label2);
|
|
8726
9246
|
if (!evaluated) return null;
|
|
8727
|
-
const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, recovery, coverage: grammarCoverage });
|
|
9247
|
+
const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {}, recovery, coverage: grammarCoverage });
|
|
8728
9248
|
if (!compiled) {
|
|
8729
9249
|
warn(init.start, `${label2}: rule map couldn't be inlined`);
|
|
8730
9250
|
return null;
|
|
8731
9251
|
}
|
|
8732
|
-
return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {} };
|
|
9252
|
+
return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
|
|
8733
9253
|
};
|
|
8734
9254
|
const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
|
|
8735
9255
|
const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
|
|
@@ -8737,13 +9257,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8737
9257
|
const isPickCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "pick";
|
|
8738
9258
|
const localRuleMaps = /* @__PURE__ */ new Map();
|
|
8739
9259
|
const localGrammarTrivia = /* @__PURE__ */ new Map();
|
|
9260
|
+
const localGrammarScanSkip = /* @__PURE__ */ new Map();
|
|
8740
9261
|
const nsFor = (label2) => `_${(0, import_node_crypto.createHash)("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
|
|
8741
9262
|
const stripIndent = (s) => s.replace(/\n[ \t]+/g, "\n");
|
|
8742
9263
|
const serializePieces = (p) => {
|
|
8743
9264
|
const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
|
|
8744
9265
|
([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
|
|
8745
9266
|
).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: [] }`;
|
|
9267
|
+
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
9268
|
};
|
|
8748
9269
|
const isSpread = (it) => "__spreadLocal" in it;
|
|
8749
9270
|
const isIR = (it) => "ir" in it && !("ruleFns" in it);
|
|
@@ -8854,12 +9375,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8854
9375
|
}
|
|
8855
9376
|
return pieces;
|
|
8856
9377
|
};
|
|
8857
|
-
const localCarried = (rm, label2, composing) => {
|
|
9378
|
+
const localCarried = (rm, label2, composing, scanSkip) => {
|
|
8858
9379
|
const entries = [...rm];
|
|
8859
9380
|
const ns = nsFor(label2);
|
|
8860
|
-
const ir = serializeRuleMap(entries);
|
|
9381
|
+
const ir = serializeRuleMap(entries, scanSkip);
|
|
8861
9382
|
if (ir) return { carried: [{ ns, ir }] };
|
|
8862
|
-
const p = compileLinkable(entries, ns, { ...composing ? { trivia: composing } : {}, recovery });
|
|
9383
|
+
const p = compileLinkable(entries, ns, { ...composing ? { trivia: composing } : {}, ...scanSkip ? { scanSkip } : {}, recovery });
|
|
8863
9384
|
return p ? { carried: [p] } : null;
|
|
8864
9385
|
};
|
|
8865
9386
|
const argPieces = (arg, label2, composing) => {
|
|
@@ -8884,18 +9405,13 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8884
9405
|
}
|
|
8885
9406
|
}
|
|
8886
9407
|
if (isRulesCall(arg)) {
|
|
8887
|
-
const
|
|
8888
|
-
|
|
8889
|
-
const a1 = rulesArgs[1];
|
|
8890
|
-
const optionsFirst = a0?.type === "ObjectExpression";
|
|
8891
|
-
const factory = optionsFirst ? a1 : a0;
|
|
8892
|
-
const rm = factory ? evaluateParserFactory(factory, scope, code, []) : null;
|
|
8893
|
-
return rm ? localCarried(rm, label2, composing) : null;
|
|
9408
|
+
const evaluated = evaluateRulesFactory(arg, label2);
|
|
9409
|
+
return evaluated ? localCarried(evaluated.ruleMap, label2, composing, evaluated.scanSkip) : null;
|
|
8894
9410
|
}
|
|
8895
9411
|
if (arg.type === "Identifier") {
|
|
8896
9412
|
const name = arg.name;
|
|
8897
9413
|
const rm = localRuleMaps.get(name);
|
|
8898
|
-
if (rm) return localCarried(rm, label2, composing);
|
|
9414
|
+
if (rm) return localCarried(rm, label2, composing, localGrammarScanSkip.get(name));
|
|
8899
9415
|
const composed = localComposedCarried.get(name);
|
|
8900
9416
|
if (composed) return { carried: composed };
|
|
8901
9417
|
const pieces = importedPieces(name);
|
|
@@ -8983,11 +9499,15 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8983
9499
|
}
|
|
8984
9500
|
const localArg = elements[elements.length - 1];
|
|
8985
9501
|
let localRules = null;
|
|
9502
|
+
let localScanSkip;
|
|
8986
9503
|
if (isRulesCall(localArg)) {
|
|
8987
9504
|
const evaluated = evaluateRulesFactory(localArg, `composeLeaf${init.start}`);
|
|
8988
9505
|
localRules = evaluated?.ruleMap ?? null;
|
|
9506
|
+
localScanSkip = evaluated?.scanSkip;
|
|
8989
9507
|
} else if (localArg.type === "Identifier") {
|
|
8990
|
-
|
|
9508
|
+
const name = localArg.name;
|
|
9509
|
+
localRules = localRuleMaps.get(name) ?? null;
|
|
9510
|
+
localScanSkip = localGrammarScanSkip.get(name);
|
|
8991
9511
|
}
|
|
8992
9512
|
if (!localRules) {
|
|
8993
9513
|
warn(init.start, "composeLeaf(): final argument must be a local rules() map");
|
|
@@ -9005,7 +9525,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9005
9525
|
}
|
|
9006
9526
|
try {
|
|
9007
9527
|
const localNs = nsFor(`composeLeaf${init.start}`);
|
|
9008
|
-
const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, recovery });
|
|
9528
|
+
const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, ...localScanSkip ? { scanSkip: localScanSkip } : {}, recovery });
|
|
9009
9529
|
if (!plainLocalPiece) {
|
|
9010
9530
|
warn(init.start, "composeLeaf(): local rules could not be statically compiled");
|
|
9011
9531
|
return null;
|
|
@@ -9107,15 +9627,19 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9107
9627
|
if (!compiledRules) continue;
|
|
9108
9628
|
localRuleMaps.set(varName, compiledRules.ruleMap);
|
|
9109
9629
|
if (compiledRules.trivia) localGrammarTrivia.set(varName, compiledRules.trivia);
|
|
9630
|
+
if (compiledRules.scanSkip) localGrammarScanSkip.set(varName, compiledRules.scanSkip);
|
|
9110
9631
|
let replacement = withCoverageDefinitions(
|
|
9111
9632
|
compiledRules.replacement,
|
|
9112
9633
|
compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(compiledRules.replacement)
|
|
9113
9634
|
);
|
|
9114
9635
|
if (exportPrefix) {
|
|
9115
9636
|
const ns = nsFor(varName);
|
|
9116
|
-
const pieces = compileLinkable([...compiledRules.ruleMap], ns, {
|
|
9637
|
+
const pieces = compileLinkable([...compiledRules.ruleMap], ns, {
|
|
9638
|
+
...compiledRules.scanSkip ? { scanSkip: compiledRules.scanSkip } : {},
|
|
9639
|
+
recovery
|
|
9640
|
+
});
|
|
9117
9641
|
if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
|
|
9118
|
-
const ir = serializeRuleMap([...compiledRules.ruleMap]);
|
|
9642
|
+
const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
|
|
9119
9643
|
replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
|
|
9120
9644
|
}
|
|
9121
9645
|
}
|
|
@@ -9235,9 +9759,18 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9235
9759
|
ms.overwrite(imp.start, imp.end, stripped);
|
|
9236
9760
|
}
|
|
9237
9761
|
}
|
|
9238
|
-
|
|
9762
|
+
const applied = runtimeComposeFallback ? [] : replacements;
|
|
9763
|
+
for (const { start, end, replacement } of applied.slice().sort((a, b) => b.start - a.start)) {
|
|
9239
9764
|
ms.overwrite(start, end, replacement);
|
|
9240
9765
|
}
|
|
9766
|
+
if (applied.length > 0) {
|
|
9767
|
+
ms.prepend(
|
|
9768
|
+
`// Generated by parseman v${PARSEMAN_VERSION} \u2014 DO NOT EDIT.
|
|
9769
|
+
// Version-locked: compile AND fuse/link this artifact with parseman v${PARSEMAN_VERSION} ONLY.
|
|
9770
|
+
// Parseman does not read artifacts across versions; recompile if the version differs.
|
|
9771
|
+
`
|
|
9772
|
+
);
|
|
9773
|
+
}
|
|
9241
9774
|
const unlowered = endLoweringCapture();
|
|
9242
9775
|
if (warnUnloweredRegex) {
|
|
9243
9776
|
for (const src of unlowered) {
|