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.js
CHANGED
|
@@ -95,10 +95,13 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
95
95
|
return true;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
function isZeroWidthAssertion(p) {
|
|
99
|
+
return p._def.tag === "not";
|
|
100
|
+
}
|
|
98
101
|
function sequenceFirstSet(parsers) {
|
|
99
102
|
let fs2 = empty();
|
|
100
103
|
for (const p of parsers) {
|
|
101
|
-
fs2 = union(fs2, p._meta.firstSet);
|
|
104
|
+
if (!isZeroWidthAssertion(p)) fs2 = union(fs2, p._meta.firstSet);
|
|
102
105
|
if (!matchesEmpty(p)) return fs2;
|
|
103
106
|
}
|
|
104
107
|
return fs2;
|
|
@@ -128,7 +131,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
128
131
|
case "sequence": {
|
|
129
132
|
let out = empty();
|
|
130
133
|
for (const term of d.parsers) {
|
|
131
|
-
out = union(out, fs2(term));
|
|
134
|
+
if (!isZeroWidthAssertion(term)) out = union(out, fs2(term));
|
|
132
135
|
if (!matchesEmpty(term)) return out;
|
|
133
136
|
}
|
|
134
137
|
return out;
|
|
@@ -453,9 +456,9 @@ var SPACE_RANGES = [
|
|
|
453
456
|
[12288, 12288],
|
|
454
457
|
[65279, 65279]
|
|
455
458
|
];
|
|
456
|
-
function shorthandRanges(
|
|
457
|
-
if (
|
|
458
|
-
if (
|
|
459
|
+
function shorthandRanges(ch2) {
|
|
460
|
+
if (ch2 === "d") return [[48, 57]];
|
|
461
|
+
if (ch2 === "s") return SPACE_RANGES;
|
|
459
462
|
return [[48, 57], [65, 90], [97, 122], [95, 95]];
|
|
460
463
|
}
|
|
461
464
|
function readUnicodeEscape(body, i) {
|
|
@@ -468,9 +471,9 @@ function parseClassRanges(body) {
|
|
|
468
471
|
const ranges = [];
|
|
469
472
|
let i = 0;
|
|
470
473
|
const readAtom = () => {
|
|
471
|
-
const
|
|
472
|
-
if (
|
|
473
|
-
if (
|
|
474
|
+
const ch2 = body[i];
|
|
475
|
+
if (ch2 === void 0) return null;
|
|
476
|
+
if (ch2 === "\\") {
|
|
474
477
|
const uni = readUnicodeEscape(body, i);
|
|
475
478
|
if (uni) {
|
|
476
479
|
i = uni.next;
|
|
@@ -484,8 +487,8 @@ function parseClassRanges(body) {
|
|
|
484
487
|
if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
|
|
485
488
|
return { cp: e.codePointAt(0) };
|
|
486
489
|
}
|
|
487
|
-
i +=
|
|
488
|
-
return { cp:
|
|
490
|
+
i += ch2.length;
|
|
491
|
+
return { cp: ch2.codePointAt(0) };
|
|
489
492
|
};
|
|
490
493
|
while (i < body.length) {
|
|
491
494
|
const lo = readAtom();
|
|
@@ -552,12 +555,35 @@ function shortScanner(source, flags) {
|
|
|
552
555
|
return minOne && end === pos ? null : end;
|
|
553
556
|
};
|
|
554
557
|
}
|
|
558
|
+
function asciiCaseFold(fs2) {
|
|
559
|
+
if (fs2.kind !== "ranges") return fs2;
|
|
560
|
+
const ranges = fs2.ranges.map((r) => ({ lo: r.lo, hi: r.hi }));
|
|
561
|
+
const add = (lo, hi) => {
|
|
562
|
+
if (lo <= hi) ranges.push({ lo, hi });
|
|
563
|
+
};
|
|
564
|
+
for (const r of fs2.ranges) {
|
|
565
|
+
const uLo = Math.max(r.lo, 65), uHi = Math.min(r.hi, 90);
|
|
566
|
+
add(uLo + 32, uHi + 32);
|
|
567
|
+
const lLo = Math.max(r.lo, 97), lHi = Math.min(r.hi, 122);
|
|
568
|
+
add(lLo - 32, lHi - 32);
|
|
569
|
+
}
|
|
570
|
+
ranges.sort((a, b) => a.lo - b.lo);
|
|
571
|
+
const merged = [];
|
|
572
|
+
for (const r of ranges) {
|
|
573
|
+
const last = merged[merged.length - 1];
|
|
574
|
+
if (last && r.lo <= last.hi + 1) last.hi = Math.max(last.hi, r.hi);
|
|
575
|
+
else merged.push({ lo: r.lo, hi: r.hi });
|
|
576
|
+
}
|
|
577
|
+
return { kind: "ranges", ranges: merged };
|
|
578
|
+
}
|
|
555
579
|
function regex(pattern, flags = "") {
|
|
556
580
|
const source = typeof pattern === "string" ? pattern : pattern.source;
|
|
557
581
|
const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
|
|
558
582
|
const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
|
|
559
583
|
const scan = shortScanner(source, resolvedFlags);
|
|
560
|
-
const
|
|
584
|
+
const raw = (firstSetAnalyzer ?? permissiveFirstSet)(source);
|
|
585
|
+
const firstSet2 = !resolvedFlags.includes("i") ? raw.firstSet : resolvedFlags.includes("u") || resolvedFlags.includes("v") ? any() : asciiCaseFold(raw.firstSet);
|
|
586
|
+
const canMatchNewline = raw.canMatchNewline;
|
|
561
587
|
const meta = { firstSet: firstSet2, canMatchNewline, isTrivia: false };
|
|
562
588
|
return {
|
|
563
589
|
_tag: "regex",
|
|
@@ -612,8 +638,8 @@ function parseRegex(src) {
|
|
|
612
638
|
const parseSeq = () => {
|
|
613
639
|
const parts = [];
|
|
614
640
|
while (i < src.length) {
|
|
615
|
-
const
|
|
616
|
-
if (
|
|
641
|
+
const ch2 = src[i];
|
|
642
|
+
if (ch2 === "|" || ch2 === ")") break;
|
|
617
643
|
parts.push(parseQuantified());
|
|
618
644
|
}
|
|
619
645
|
if (parts.length === 0) return EMPTY2;
|
|
@@ -621,18 +647,18 @@ function parseRegex(src) {
|
|
|
621
647
|
};
|
|
622
648
|
const parseQuantified = () => {
|
|
623
649
|
const atom = parseAtom();
|
|
624
|
-
const
|
|
650
|
+
const ch2 = src[i];
|
|
625
651
|
let min = null;
|
|
626
|
-
if (
|
|
652
|
+
if (ch2 === "*") {
|
|
627
653
|
min = 0;
|
|
628
654
|
i++;
|
|
629
|
-
} else if (
|
|
655
|
+
} else if (ch2 === "+") {
|
|
630
656
|
min = 1;
|
|
631
657
|
i++;
|
|
632
|
-
} else if (
|
|
658
|
+
} else if (ch2 === "?") {
|
|
633
659
|
min = 0;
|
|
634
660
|
i++;
|
|
635
|
-
} else if (
|
|
661
|
+
} else if (ch2 === "{") {
|
|
636
662
|
min = parseBraceQuantifier();
|
|
637
663
|
}
|
|
638
664
|
if (min === null) return atom;
|
|
@@ -648,8 +674,8 @@ function parseRegex(src) {
|
|
|
648
674
|
return Number.parseInt(m[1], 10);
|
|
649
675
|
};
|
|
650
676
|
const parseAtom = () => {
|
|
651
|
-
const
|
|
652
|
-
switch (
|
|
677
|
+
const ch2 = src[i];
|
|
678
|
+
switch (ch2) {
|
|
653
679
|
case "(":
|
|
654
680
|
return parseGroup();
|
|
655
681
|
case "[":
|
|
@@ -670,8 +696,8 @@ function parseRegex(src) {
|
|
|
670
696
|
return charNode(fromRange(123, 123), false);
|
|
671
697
|
// a `{` with nothing to quantify is a literal brace
|
|
672
698
|
default:
|
|
673
|
-
i +=
|
|
674
|
-
return charNode(fromRange(
|
|
699
|
+
i += ch2.length;
|
|
700
|
+
return charNode(fromRange(ch2.codePointAt(0), ch2.codePointAt(0)), ch2 === "\n");
|
|
675
701
|
}
|
|
676
702
|
};
|
|
677
703
|
const parseGroup = () => {
|
|
@@ -1104,8 +1130,8 @@ function choice(...args) {
|
|
|
1104
1130
|
}
|
|
1105
1131
|
}
|
|
1106
1132
|
if (idx >= 0) {
|
|
1107
|
-
const
|
|
1108
|
-
if (
|
|
1133
|
+
const gate2 = gates[idx];
|
|
1134
|
+
if (gate2 && !gate2(ctx.state)) {
|
|
1109
1135
|
return { ok: false, expected: deriveExpected(parsers[idx]), span: { start: pos, end: pos } };
|
|
1110
1136
|
}
|
|
1111
1137
|
const result = parsers[idx].parse(input, pos, ctx);
|
|
@@ -1451,11 +1477,11 @@ function tryFastLabeledScan(input, cur, trivia2) {
|
|
|
1451
1477
|
function recordTriviaChunks(ctx, chunks) {
|
|
1452
1478
|
const kinds = ctx.triviaKindLabels;
|
|
1453
1479
|
const mask = ctx._triviaCaptureMask;
|
|
1454
|
-
for (const
|
|
1455
|
-
pushTriviaLogEntry(ctx,
|
|
1480
|
+
for (const ch2 of chunks) {
|
|
1481
|
+
pushTriviaLogEntry(ctx, ch2.start, ch2.end, kinds ? ch2.kindIndex : void 0);
|
|
1456
1482
|
if (ctx.captureTrivia && (ctx._cstBuf !== void 0 || ctx._cstTriviaLog !== void 0)) {
|
|
1457
|
-
if (mask === void 0 || kinds === void 0 || (mask & 1 <<
|
|
1458
|
-
pushCstTriviaEntry(ctx,
|
|
1483
|
+
if (mask === void 0 || kinds === void 0 || (mask & 1 << ch2.kindIndex) !== 0) {
|
|
1484
|
+
pushCstTriviaEntry(ctx, ch2.start, ch2.end, kinds ? ch2.kindIndex : void 0);
|
|
1459
1485
|
}
|
|
1460
1486
|
}
|
|
1461
1487
|
}
|
|
@@ -2194,6 +2220,12 @@ function rules(a, b) {
|
|
|
2194
2220
|
if (rule && !rule._meta.isTrivia) rule._meta.grammarTrivia = options.trivia;
|
|
2195
2221
|
}
|
|
2196
2222
|
}
|
|
2223
|
+
if (options?.scanSkip != null) {
|
|
2224
|
+
for (const key of Object.keys(definitions)) {
|
|
2225
|
+
const rule = cache[key];
|
|
2226
|
+
if (rule && !rule._meta.isTrivia) rule._meta.grammarScanSkip = options.scanSkip;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2197
2229
|
for (const key of Object.keys(definitions)) {
|
|
2198
2230
|
markUnusedValues(cache[key]);
|
|
2199
2231
|
}
|
|
@@ -2762,6 +2794,413 @@ function leaf(root, fn) {
|
|
|
2762
2794
|
};
|
|
2763
2795
|
}
|
|
2764
2796
|
|
|
2797
|
+
// src/version.ts
|
|
2798
|
+
var PARSEMAN_VERSION = "0.33.0";
|
|
2799
|
+
|
|
2800
|
+
// src/analysis/gating.ts
|
|
2801
|
+
var isAny = (fs2) => fs2.kind === "any";
|
|
2802
|
+
function intersects2(a, b) {
|
|
2803
|
+
if (a.kind === "any" || b.kind === "any") return true;
|
|
2804
|
+
if (a.kind === "empty" || b.kind === "empty") return false;
|
|
2805
|
+
for (const ra of a.ranges) for (const rb of b.ranges) if (ra.lo <= rb.hi && rb.lo <= ra.hi) return true;
|
|
2806
|
+
return false;
|
|
2807
|
+
}
|
|
2808
|
+
function intersection(a, b) {
|
|
2809
|
+
if (a.kind === "any") return b;
|
|
2810
|
+
if (b.kind === "any") return a;
|
|
2811
|
+
if (a.kind === "empty" || b.kind === "empty") return { kind: "empty" };
|
|
2812
|
+
const ranges = [];
|
|
2813
|
+
for (const ra of a.ranges) for (const rb of b.ranges) {
|
|
2814
|
+
const lo = Math.max(ra.lo, rb.lo);
|
|
2815
|
+
const hi = Math.min(ra.hi, rb.hi);
|
|
2816
|
+
if (lo <= hi) ranges.push({ lo, hi });
|
|
2817
|
+
}
|
|
2818
|
+
return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
|
|
2819
|
+
}
|
|
2820
|
+
function combine(sets) {
|
|
2821
|
+
let acc = { kind: "empty" };
|
|
2822
|
+
for (const s of sets) {
|
|
2823
|
+
if (s.kind === "any") return { kind: "any" };
|
|
2824
|
+
if (s.kind === "empty") continue;
|
|
2825
|
+
acc = acc.kind === "ranges" ? { kind: "ranges", ranges: [...acc.ranges, ...s.ranges] } : s;
|
|
2826
|
+
}
|
|
2827
|
+
return acc;
|
|
2828
|
+
}
|
|
2829
|
+
var ch = (c) => c >= 32 && c < 127 ? `'${String.fromCharCode(c)}'` : `\\u${c}`;
|
|
2830
|
+
function firstSetToString(fs2) {
|
|
2831
|
+
if (fs2.kind === "any") return "ANY";
|
|
2832
|
+
if (fs2.kind === "empty") return "(empty)";
|
|
2833
|
+
return fs2.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
|
|
2834
|
+
}
|
|
2835
|
+
var ruleNameOf = (p) => p._ruleName;
|
|
2836
|
+
var SUGGESTIONS = {
|
|
2837
|
+
"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.",
|
|
2838
|
+
"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.",
|
|
2839
|
+
"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.",
|
|
2840
|
+
"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.",
|
|
2841
|
+
"opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
|
|
2842
|
+
"ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
|
|
2843
|
+
};
|
|
2844
|
+
function classifyBroadArm(arm) {
|
|
2845
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2846
|
+
const walk2 = (p) => {
|
|
2847
|
+
if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
|
|
2848
|
+
seen.add(p);
|
|
2849
|
+
const d = p._def;
|
|
2850
|
+
switch (d.tag) {
|
|
2851
|
+
case "literal":
|
|
2852
|
+
case "regex":
|
|
2853
|
+
case "keywords":
|
|
2854
|
+
return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
|
|
2855
|
+
case "not":
|
|
2856
|
+
return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
|
|
2857
|
+
case "scanTo":
|
|
2858
|
+
return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
|
|
2859
|
+
// `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
|
|
2860
|
+
// its own — it is the poison. `withCtx`/`recover` FORWARD their inner
|
|
2861
|
+
// first-set (see firstSetOf), so a broad result comes from the inner parser:
|
|
2862
|
+
// walk into it for a precise cause.
|
|
2863
|
+
case "guard":
|
|
2864
|
+
return { cause: "opaque-wrapper", detail: `opaque wrapper (${d.tag})` };
|
|
2865
|
+
// attempt/withCtx/recover FORWARD their inner first-set (transparent for
|
|
2866
|
+
// dispatch), so a broad result comes from the inner parser — walk into it.
|
|
2867
|
+
case "attempt":
|
|
2868
|
+
case "withCtx":
|
|
2869
|
+
case "recover":
|
|
2870
|
+
return walk2(d.parser);
|
|
2871
|
+
case "lazy": {
|
|
2872
|
+
const name = p._ruleName;
|
|
2873
|
+
try {
|
|
2874
|
+
const inner = walk2(d.thunk());
|
|
2875
|
+
if (name !== void 0) return inner ? { cause: "cross-artifact-ref", detail: `via ref g.${name} \u2192 ${inner.detail}` } : null;
|
|
2876
|
+
return inner;
|
|
2877
|
+
} catch {
|
|
2878
|
+
return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}` };
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
case "optional":
|
|
2882
|
+
case "many": {
|
|
2883
|
+
const inner = walk2(d.parser);
|
|
2884
|
+
return { cause: "nullable-prefix", detail: inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term" };
|
|
2885
|
+
}
|
|
2886
|
+
case "oneOrMore":
|
|
2887
|
+
case "transform":
|
|
2888
|
+
case "label":
|
|
2889
|
+
case "field":
|
|
2890
|
+
case "trivia":
|
|
2891
|
+
case "token":
|
|
2892
|
+
case "leaf":
|
|
2893
|
+
case "node":
|
|
2894
|
+
case "grammar":
|
|
2895
|
+
case "expect":
|
|
2896
|
+
return walk2(d.parser);
|
|
2897
|
+
case "skip":
|
|
2898
|
+
return walk2(d.main);
|
|
2899
|
+
case "sequence": {
|
|
2900
|
+
let sawNullablePrefix = false;
|
|
2901
|
+
for (const t of d.parsers) {
|
|
2902
|
+
if (t._def.tag === "not") {
|
|
2903
|
+
sawNullablePrefix = true;
|
|
2904
|
+
continue;
|
|
2905
|
+
}
|
|
2906
|
+
if (isAny(firstSetOf(t))) {
|
|
2907
|
+
const inner = walk2(t);
|
|
2908
|
+
if (sawNullablePrefix)
|
|
2909
|
+
return { cause: "nullable-prefix", detail: inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term" };
|
|
2910
|
+
return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
|
|
2911
|
+
}
|
|
2912
|
+
if (matchesEmpty(t)) {
|
|
2913
|
+
sawNullablePrefix = true;
|
|
2914
|
+
continue;
|
|
2915
|
+
}
|
|
2916
|
+
return null;
|
|
2917
|
+
}
|
|
2918
|
+
return { cause: "broad-recognizer", detail: "sequence of only nullable/zero-width terms" };
|
|
2919
|
+
}
|
|
2920
|
+
case "choice": {
|
|
2921
|
+
for (const a of d.parsers) {
|
|
2922
|
+
const r = walk2(a);
|
|
2923
|
+
if (r) return { cause: r.cause, detail: `choice arm \u2192 ${r.detail}` };
|
|
2924
|
+
}
|
|
2925
|
+
return null;
|
|
2926
|
+
}
|
|
2927
|
+
default:
|
|
2928
|
+
return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
|
|
2929
|
+
}
|
|
2930
|
+
};
|
|
2931
|
+
return walk2(arm) ?? { cause: "broad-recognizer", detail: "broad (cause not localized)" };
|
|
2932
|
+
}
|
|
2933
|
+
function peelToLeading(arm) {
|
|
2934
|
+
let d = arm._def;
|
|
2935
|
+
let cur = arm;
|
|
2936
|
+
for (; ; ) {
|
|
2937
|
+
if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label" || d.tag === "attempt") {
|
|
2938
|
+
cur = d.parser;
|
|
2939
|
+
d = cur._def;
|
|
2940
|
+
continue;
|
|
2941
|
+
}
|
|
2942
|
+
break;
|
|
2943
|
+
}
|
|
2944
|
+
if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
|
|
2945
|
+
return cur;
|
|
2946
|
+
}
|
|
2947
|
+
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
2948
|
+
function detectAntiPatterns(rule, arms) {
|
|
2949
|
+
const out = [];
|
|
2950
|
+
arms.forEach((arm, i) => {
|
|
2951
|
+
const lead = peelToLeading(arm);
|
|
2952
|
+
const ld = lead._def;
|
|
2953
|
+
if (ld.tag === "not") {
|
|
2954
|
+
const inner = ld.parser;
|
|
2955
|
+
if (inner._def.tag === "not") {
|
|
2956
|
+
out.push({
|
|
2957
|
+
kind: "double-not",
|
|
2958
|
+
rule,
|
|
2959
|
+
armIndex: i,
|
|
2960
|
+
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."
|
|
2961
|
+
});
|
|
2962
|
+
} else {
|
|
2963
|
+
out.push({
|
|
2964
|
+
kind: "leading-not",
|
|
2965
|
+
rule,
|
|
2966
|
+
armIndex: i,
|
|
2967
|
+
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."
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
if (ld.tag === "regex" && KEYWORD_REGEX_RE.test(ld.source)) {
|
|
2972
|
+
out.push({
|
|
2973
|
+
kind: "keyword-regex",
|
|
2974
|
+
rule,
|
|
2975
|
+
armIndex: i,
|
|
2976
|
+
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).`
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
});
|
|
2980
|
+
return out;
|
|
2981
|
+
}
|
|
2982
|
+
function analyzeGating(entry, opts) {
|
|
2983
|
+
const raw = [];
|
|
2984
|
+
const antiPatterns = [];
|
|
2985
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2986
|
+
const visit = (p, enclosingRule) => {
|
|
2987
|
+
if (visited.has(p)) return;
|
|
2988
|
+
visited.add(p);
|
|
2989
|
+
const d = p._def;
|
|
2990
|
+
const rule = ruleNameOf(p) ?? enclosingRule;
|
|
2991
|
+
if (d.tag === "choice") {
|
|
2992
|
+
raw.push({ g: analyzeChoice(p, d, rule), rule });
|
|
2993
|
+
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
2994
|
+
}
|
|
2995
|
+
const rec = d;
|
|
2996
|
+
const kids = [];
|
|
2997
|
+
if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
|
|
2998
|
+
for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
|
|
2999
|
+
if (rec[k]) kids.push(rec[k]);
|
|
3000
|
+
if (Array.isArray(rec.skip)) kids.push(...rec.skip);
|
|
3001
|
+
if (d.tag === "lazy") {
|
|
3002
|
+
try {
|
|
3003
|
+
kids.push(d.thunk());
|
|
3004
|
+
} catch {
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
for (const k of kids) visit(k, rule);
|
|
3008
|
+
};
|
|
3009
|
+
visit(entry, ruleNameOf(entry) ?? "<entry>");
|
|
3010
|
+
const perRule = /* @__PURE__ */ new Map();
|
|
3011
|
+
for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
|
|
3012
|
+
const seenInRule = /* @__PURE__ */ new Map();
|
|
3013
|
+
const accept = new Set(opts?.accept ?? []);
|
|
3014
|
+
const usedAccept = /* @__PURE__ */ new Set();
|
|
3015
|
+
const choices = raw.map(({ g, rule }) => {
|
|
3016
|
+
const n = seenInRule.get(rule) ?? 0;
|
|
3017
|
+
seenInRule.set(rule, n + 1);
|
|
3018
|
+
const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
|
|
3019
|
+
const isAccepted = g.gates === "no" && accept.has(id);
|
|
3020
|
+
if (isAccepted) usedAccept.add(id);
|
|
3021
|
+
return { ...g, id, accepted: isAccepted };
|
|
3022
|
+
});
|
|
3023
|
+
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
3024
|
+
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
3025
|
+
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted);
|
|
3026
|
+
const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
|
|
3027
|
+
const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
|
|
3028
|
+
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, acceptedUnused, choices, antiPatterns };
|
|
3029
|
+
}
|
|
3030
|
+
function analyzeChoice(p, d, rule) {
|
|
3031
|
+
const arms = d.parsers;
|
|
3032
|
+
const shallow = arms.map((a) => a._meta.firstSet);
|
|
3033
|
+
const deep = arms.map((a) => firstSetOf(a));
|
|
3034
|
+
const anyArms = deep.map((fs2, index) => ({ fs: fs2, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
|
|
3035
|
+
const { cause, detail } = classifyBroadArm(arms[index]);
|
|
3036
|
+
return { index, cause, detail, shallowAnyOnly: false, suggestion: SUGGESTIONS[cause] };
|
|
3037
|
+
});
|
|
3038
|
+
const overlaps = [];
|
|
3039
|
+
for (let i = 0; i < deep.length; i++)
|
|
3040
|
+
for (let j = i + 1; j < deep.length; j++)
|
|
3041
|
+
if (!isAny(deep[i]) && !isAny(deep[j]) && intersects2(deep[i], deep[j]))
|
|
3042
|
+
overlaps.push({
|
|
3043
|
+
a: i,
|
|
3044
|
+
b: j,
|
|
3045
|
+
on: intersection(deep[i], deep[j]),
|
|
3046
|
+
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."
|
|
3047
|
+
});
|
|
3048
|
+
let gates;
|
|
3049
|
+
if (d.disjoint) gates = "yes";
|
|
3050
|
+
else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
|
|
3051
|
+
else gates = "no";
|
|
3052
|
+
return {
|
|
3053
|
+
rule,
|
|
3054
|
+
strategy: d.strategy?.tag ?? "firstMatch",
|
|
3055
|
+
gates,
|
|
3056
|
+
combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
|
|
3057
|
+
anyArms,
|
|
3058
|
+
overlaps
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
function formatGatingWarnings(report) {
|
|
3062
|
+
const lines = [];
|
|
3063
|
+
for (const c of report.ungated) {
|
|
3064
|
+
lines.push(`parseman gating: choice @ ${c.id} is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms.`);
|
|
3065
|
+
for (const a of c.anyArms)
|
|
3066
|
+
lines.push(` \xB7 arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
|
|
3067
|
+
fix: ${a.suggestion}`);
|
|
3068
|
+
for (const o of c.overlaps)
|
|
3069
|
+
lines.push(` \xB7 arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
|
|
3070
|
+
fix: ${o.suggestion}`);
|
|
3071
|
+
lines.push(` (intentional? accept it in the gating snapshot: { accept: ['${c.id}'] }.)`);
|
|
3072
|
+
}
|
|
3073
|
+
for (const ap of report.antiPatterns)
|
|
3074
|
+
lines.push(`parseman anti-pattern [${ap.kind}] @ ${ap.rule} arm[${ap.armIndex}]: ${ap.message}`);
|
|
3075
|
+
return lines;
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
// src/combinators/scanTo.ts
|
|
3079
|
+
function resolveScanSkip(explicitSkip, raw, ctx) {
|
|
3080
|
+
if (raw) return [];
|
|
3081
|
+
const trivia2 = ctx.trivia;
|
|
3082
|
+
const ambient = ctx.scanSkip;
|
|
3083
|
+
if (!trivia2 && !ambient) return explicitSkip;
|
|
3084
|
+
const out = [];
|
|
3085
|
+
if (trivia2) out.push(trivia2);
|
|
3086
|
+
if (ambient) out.push(...ambient);
|
|
3087
|
+
out.push(...explicitSkip);
|
|
3088
|
+
return out;
|
|
3089
|
+
}
|
|
3090
|
+
function scanTo(sentinel, { skip: skip2, raw = false, orEOF = false } = {}) {
|
|
3091
|
+
const meta = {
|
|
3092
|
+
firstSet: any(),
|
|
3093
|
+
canMatchNewline: true,
|
|
3094
|
+
isTrivia: false
|
|
3095
|
+
};
|
|
3096
|
+
const explicitSkip = skip2 ?? [];
|
|
3097
|
+
return {
|
|
3098
|
+
_tag: "scanTo",
|
|
3099
|
+
_meta: meta,
|
|
3100
|
+
_def: { tag: "scanTo", sentinel, skip: explicitSkip, raw, orEOF },
|
|
3101
|
+
parse(input, pos, ctx) {
|
|
3102
|
+
let cur = pos;
|
|
3103
|
+
const skip3 = resolveScanSkip(explicitSkip, raw, ctx);
|
|
3104
|
+
const probeCtx = {
|
|
3105
|
+
trackLines: ctx.trackLines,
|
|
3106
|
+
state: ctx.state,
|
|
3107
|
+
...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
|
|
3108
|
+
};
|
|
3109
|
+
const emit2 = (end) => {
|
|
3110
|
+
if (end > pos && cstCaptureActive(ctx)) {
|
|
3111
|
+
const leaf2 = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
|
|
3112
|
+
pushCstLeaf(ctx, leaf2);
|
|
3113
|
+
}
|
|
3114
|
+
};
|
|
3115
|
+
while (cur < input.length) {
|
|
3116
|
+
const s = sentinel.parse(input, cur, probeCtx);
|
|
3117
|
+
if (s.ok) {
|
|
3118
|
+
emit2(cur);
|
|
3119
|
+
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
3120
|
+
}
|
|
3121
|
+
let advanced = false;
|
|
3122
|
+
for (const skipper of skip3) {
|
|
3123
|
+
const r = skipper.parse(input, cur, probeCtx);
|
|
3124
|
+
if (r.ok && r.span.end > cur) {
|
|
3125
|
+
cur = r.span.end;
|
|
3126
|
+
advanced = true;
|
|
3127
|
+
break;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
if (!advanced) cur++;
|
|
3131
|
+
}
|
|
3132
|
+
if (orEOF) {
|
|
3133
|
+
emit2(cur);
|
|
3134
|
+
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
3135
|
+
}
|
|
3136
|
+
const sentDef = sentinel._def;
|
|
3137
|
+
const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
|
|
3138
|
+
return { ok: false, expected, span: { start: pos, end: cur } };
|
|
3139
|
+
}
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
function balanced(open, close, options = {}) {
|
|
3143
|
+
const ownSkip = options.skip ?? [];
|
|
3144
|
+
const combi = buildBalancedInterior(open, close, ownSkip);
|
|
3145
|
+
if (options.raw) return combi;
|
|
3146
|
+
const eagerParse = combi.parse.bind(combi);
|
|
3147
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3148
|
+
combi._balancedAmbient = { open, close, ownSkip };
|
|
3149
|
+
combi.parse = (input, pos, ctx) => {
|
|
3150
|
+
const amb = ctx.scanSkip;
|
|
3151
|
+
if (!amb || amb.length === 0) return eagerParse(input, pos, ctx);
|
|
3152
|
+
let interior = cache.get(amb);
|
|
3153
|
+
if (!interior) {
|
|
3154
|
+
interior = buildBalancedInterior(open, close, [...amb, ...ownSkip]);
|
|
3155
|
+
cache.set(amb, interior);
|
|
3156
|
+
}
|
|
3157
|
+
return interior.parse(input, pos, ctx);
|
|
3158
|
+
};
|
|
3159
|
+
return combi;
|
|
3160
|
+
}
|
|
3161
|
+
function buildBalancedInterior(open, close, skips) {
|
|
3162
|
+
const self = ref();
|
|
3163
|
+
const stop = /* @__PURE__ */ new Set([open, close]);
|
|
3164
|
+
let bounded = true;
|
|
3165
|
+
for (const sk of skips) {
|
|
3166
|
+
const cs = firstSetClassChars(sk);
|
|
3167
|
+
if (cs === null) {
|
|
3168
|
+
bounded = false;
|
|
3169
|
+
break;
|
|
3170
|
+
}
|
|
3171
|
+
for (const ch2 of cs) stop.add(ch2);
|
|
3172
|
+
}
|
|
3173
|
+
const cls = [...stop].map(escapeClassChar).join("");
|
|
3174
|
+
const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
|
|
3175
|
+
const inner = many(choice(self, ...skips, content));
|
|
3176
|
+
const combi = transform(
|
|
3177
|
+
sequence(literal(open), inner, expect(literal(close))),
|
|
3178
|
+
// parts: strings (content/self) or arrays (a sequence-shaped skip) or a
|
|
3179
|
+
// ParseError (recovered close). `c` is the close string or a ParseError.
|
|
3180
|
+
([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
|
|
3181
|
+
);
|
|
3182
|
+
if (combi._def.tag === "transform") {
|
|
3183
|
+
combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
|
|
3184
|
+
combi._def.recognitionOnly = true;
|
|
3185
|
+
}
|
|
3186
|
+
self.define(combi);
|
|
3187
|
+
return combi;
|
|
3188
|
+
}
|
|
3189
|
+
function firstSetClassChars(c) {
|
|
3190
|
+
const fs2 = c._meta.firstSet;
|
|
3191
|
+
if (fs2.kind === "empty") return [];
|
|
3192
|
+
if (fs2.kind !== "ranges") return null;
|
|
3193
|
+
const out = [];
|
|
3194
|
+
for (const { lo, hi } of fs2.ranges) {
|
|
3195
|
+
if (hi - lo > 8) return null;
|
|
3196
|
+
for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
|
|
3197
|
+
}
|
|
3198
|
+
return out;
|
|
3199
|
+
}
|
|
3200
|
+
function escapeClassChar(ch2) {
|
|
3201
|
+
return ch2.replace(/[\\\]^-]/g, "\\$&");
|
|
3202
|
+
}
|
|
3203
|
+
|
|
2765
3204
|
// src/compiler/grammar-coverage-ids.ts
|
|
2766
3205
|
function children(def, winners) {
|
|
2767
3206
|
switch (def.tag) {
|
|
@@ -2874,16 +3313,16 @@ function literalCodePoints(frag) {
|
|
|
2874
3313
|
const out = [];
|
|
2875
3314
|
let i = 0;
|
|
2876
3315
|
while (i < frag.length) {
|
|
2877
|
-
const
|
|
2878
|
-
if (
|
|
3316
|
+
const ch2 = frag[i];
|
|
3317
|
+
if (ch2 === "\\") {
|
|
2879
3318
|
const e = frag[i + 1];
|
|
2880
3319
|
if (e === void 0) return null;
|
|
2881
3320
|
out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
|
|
2882
3321
|
i += 2;
|
|
2883
3322
|
continue;
|
|
2884
3323
|
}
|
|
2885
|
-
if (META.has(
|
|
2886
|
-
out.push(
|
|
3324
|
+
if (META.has(ch2)) return null;
|
|
3325
|
+
out.push(ch2.codePointAt(0));
|
|
2887
3326
|
i += 1;
|
|
2888
3327
|
}
|
|
2889
3328
|
return out.length ? out : null;
|
|
@@ -2962,8 +3401,8 @@ function parseSeqParts(source) {
|
|
|
2962
3401
|
};
|
|
2963
3402
|
let i = 0;
|
|
2964
3403
|
while (i < source.length) {
|
|
2965
|
-
const
|
|
2966
|
-
if (
|
|
3404
|
+
const ch2 = source[i];
|
|
3405
|
+
if (ch2 === "[") {
|
|
2967
3406
|
let k = i + 1;
|
|
2968
3407
|
const neg = source[k] === "^";
|
|
2969
3408
|
if (neg) k++;
|
|
@@ -2985,7 +3424,7 @@ function parseSeqParts(source) {
|
|
|
2985
3424
|
i += runFrom(ranges, neg, i);
|
|
2986
3425
|
continue;
|
|
2987
3426
|
}
|
|
2988
|
-
if (
|
|
3427
|
+
if (ch2 === "\\") {
|
|
2989
3428
|
const e = source[i + 1];
|
|
2990
3429
|
if (e === "d" || e === "w" || e === "s") {
|
|
2991
3430
|
flush();
|
|
@@ -3014,7 +3453,7 @@ function parseSeqParts(source) {
|
|
|
3014
3453
|
} else lit.push(cp2);
|
|
3015
3454
|
continue;
|
|
3016
3455
|
}
|
|
3017
|
-
if (
|
|
3456
|
+
if (ch2 === "(") {
|
|
3018
3457
|
if (source[i + 1] !== "?" || source[i + 2] !== ":") return null;
|
|
3019
3458
|
let k = i + 3;
|
|
3020
3459
|
let depth = 1;
|
|
@@ -3063,9 +3502,9 @@ function parseSeqParts(source) {
|
|
|
3063
3502
|
}
|
|
3064
3503
|
continue;
|
|
3065
3504
|
}
|
|
3066
|
-
if (META.has(
|
|
3067
|
-
const cp =
|
|
3068
|
-
i +=
|
|
3505
|
+
if (META.has(ch2)) return null;
|
|
3506
|
+
const cp = ch2.codePointAt(0);
|
|
3507
|
+
i += ch2.length;
|
|
3069
3508
|
if (source[i] === "?") {
|
|
3070
3509
|
flush();
|
|
3071
3510
|
parts.push({ part: "lit", cps: [cp], optional: true });
|
|
@@ -3242,12 +3681,12 @@ function unwrapOuterGroup(source) {
|
|
|
3242
3681
|
let i = 3;
|
|
3243
3682
|
let ok = true;
|
|
3244
3683
|
while (i < source.length && depth > 0) {
|
|
3245
|
-
const
|
|
3246
|
-
if (
|
|
3684
|
+
const ch2 = source[i];
|
|
3685
|
+
if (ch2 === "\\") {
|
|
3247
3686
|
i += 2;
|
|
3248
3687
|
continue;
|
|
3249
3688
|
}
|
|
3250
|
-
if (
|
|
3689
|
+
if (ch2 === "[") {
|
|
3251
3690
|
let j = i + 1;
|
|
3252
3691
|
if (source[j] === "^") j++;
|
|
3253
3692
|
while (j < source.length && source[j] !== "]") {
|
|
@@ -3261,8 +3700,8 @@ function unwrapOuterGroup(source) {
|
|
|
3261
3700
|
i = j + 1;
|
|
3262
3701
|
continue;
|
|
3263
3702
|
}
|
|
3264
|
-
if (
|
|
3265
|
-
else if (
|
|
3703
|
+
if (ch2 === "(") depth++;
|
|
3704
|
+
else if (ch2 === ")") depth--;
|
|
3266
3705
|
i++;
|
|
3267
3706
|
}
|
|
3268
3707
|
if (!ok || depth !== 0 || i !== source.length) break;
|
|
@@ -3277,12 +3716,12 @@ function splitTopLevelAlternation(source) {
|
|
|
3277
3716
|
let last = 0;
|
|
3278
3717
|
let i = 0;
|
|
3279
3718
|
while (i < body.length) {
|
|
3280
|
-
const
|
|
3281
|
-
if (
|
|
3719
|
+
const ch2 = body[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 (body[j] === "^") j++;
|
|
3288
3727
|
while (j < body.length && body[j] !== "]") {
|
|
@@ -3293,18 +3732,18 @@ function splitTopLevelAlternation(source) {
|
|
|
3293
3732
|
i = j + 1;
|
|
3294
3733
|
continue;
|
|
3295
3734
|
}
|
|
3296
|
-
if (
|
|
3735
|
+
if (ch2 === "(") {
|
|
3297
3736
|
depth++;
|
|
3298
3737
|
i++;
|
|
3299
3738
|
continue;
|
|
3300
3739
|
}
|
|
3301
|
-
if (
|
|
3740
|
+
if (ch2 === ")") {
|
|
3302
3741
|
depth--;
|
|
3303
3742
|
if (depth < 0) return null;
|
|
3304
3743
|
i++;
|
|
3305
3744
|
continue;
|
|
3306
3745
|
}
|
|
3307
|
-
if (
|
|
3746
|
+
if (ch2 === "|" && depth === 0) {
|
|
3308
3747
|
parts.push(body.slice(last, i));
|
|
3309
3748
|
last = i + 1;
|
|
3310
3749
|
i++;
|
|
@@ -3423,12 +3862,12 @@ function splitDelimArms(body) {
|
|
|
3423
3862
|
let last = 0;
|
|
3424
3863
|
let i = 0;
|
|
3425
3864
|
while (i < body.length) {
|
|
3426
|
-
const
|
|
3427
|
-
if (
|
|
3865
|
+
const ch2 = body[i];
|
|
3866
|
+
if (ch2 === "\\") {
|
|
3428
3867
|
i += 2;
|
|
3429
3868
|
continue;
|
|
3430
3869
|
}
|
|
3431
|
-
if (
|
|
3870
|
+
if (ch2 === "[") {
|
|
3432
3871
|
let j = i + 1;
|
|
3433
3872
|
if (body[j] === "^") j++;
|
|
3434
3873
|
while (j < body.length && body[j] !== "]") {
|
|
@@ -3439,18 +3878,18 @@ function splitDelimArms(body) {
|
|
|
3439
3878
|
i = j + 1;
|
|
3440
3879
|
continue;
|
|
3441
3880
|
}
|
|
3442
|
-
if (
|
|
3881
|
+
if (ch2 === "(") {
|
|
3443
3882
|
depth++;
|
|
3444
3883
|
i++;
|
|
3445
3884
|
continue;
|
|
3446
3885
|
}
|
|
3447
|
-
if (
|
|
3886
|
+
if (ch2 === ")") {
|
|
3448
3887
|
depth--;
|
|
3449
3888
|
if (depth < 0) return null;
|
|
3450
3889
|
i++;
|
|
3451
3890
|
continue;
|
|
3452
3891
|
}
|
|
3453
|
-
if (
|
|
3892
|
+
if (ch2 === "|" && depth === 0) {
|
|
3454
3893
|
arms.push(body.slice(last, i));
|
|
3455
3894
|
last = i + 1;
|
|
3456
3895
|
}
|
|
@@ -3502,12 +3941,12 @@ function parseDelimited(source) {
|
|
|
3502
3941
|
let k = i + 3;
|
|
3503
3942
|
let depth = 1;
|
|
3504
3943
|
while (k < source.length && depth > 0) {
|
|
3505
|
-
const
|
|
3506
|
-
if (
|
|
3944
|
+
const ch2 = source[k];
|
|
3945
|
+
if (ch2 === "\\") {
|
|
3507
3946
|
k += 2;
|
|
3508
3947
|
continue;
|
|
3509
3948
|
}
|
|
3510
|
-
if (
|
|
3949
|
+
if (ch2 === "[") {
|
|
3511
3950
|
let j = k + 1;
|
|
3512
3951
|
if (source[j] === "^") j++;
|
|
3513
3952
|
while (j < source.length && source[j] !== "]") {
|
|
@@ -3518,8 +3957,8 @@ function parseDelimited(source) {
|
|
|
3518
3957
|
k = j + 1;
|
|
3519
3958
|
continue;
|
|
3520
3959
|
}
|
|
3521
|
-
if (
|
|
3522
|
-
else if (
|
|
3960
|
+
if (ch2 === "(") depth++;
|
|
3961
|
+
else if (ch2 === ")") depth--;
|
|
3523
3962
|
k++;
|
|
3524
3963
|
}
|
|
3525
3964
|
if (depth !== 0) return null;
|
|
@@ -4191,58 +4630,88 @@ function tryInlineDestructureTransform(src, valueVars) {
|
|
|
4191
4630
|
|
|
4192
4631
|
// src/compiler/codegen.ts
|
|
4193
4632
|
function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
|
|
4194
|
-
if (seen.has(p)) return { concrete: empty(), refs: [] };
|
|
4195
|
-
seen.add(p);
|
|
4196
4633
|
const d = p._def;
|
|
4634
|
+
if (d.tag === "lazy") {
|
|
4635
|
+
const name = p._ruleName;
|
|
4636
|
+
if (name !== void 0) return { alts: [[{ set: empty(), nullable: false, ref: name }]] };
|
|
4637
|
+
}
|
|
4638
|
+
if (seen.has(p)) return { alts: [[]] };
|
|
4639
|
+
seen.add(p);
|
|
4197
4640
|
const rec = (c) => leadingFirstSetRecipe(c, seen);
|
|
4198
|
-
const
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4641
|
+
const seg = (set, nullable2) => ({ set, nullable: nullable2 });
|
|
4642
|
+
const forceNullable = (r) => ({ alts: r.alts.map((alt) => alt.map((s) => ({ ...s, nullable: true }))) });
|
|
4643
|
+
const collapse = (r, nullable2) => {
|
|
4644
|
+
let set = empty();
|
|
4645
|
+
let hasRef = false;
|
|
4646
|
+
for (const alt of r.alts) {
|
|
4647
|
+
for (const s of alt) {
|
|
4648
|
+
if (s.ref !== void 0) hasRef = true;
|
|
4649
|
+
set = union(set, s.set);
|
|
4650
|
+
if (!s.nullable) break;
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
return seg(hasRef ? any() : set, nullable2);
|
|
4654
|
+
};
|
|
4655
|
+
const compute = () => {
|
|
4656
|
+
switch (d.tag) {
|
|
4657
|
+
case "lazy": {
|
|
4658
|
+
try {
|
|
4659
|
+
return rec(d.thunk());
|
|
4660
|
+
} catch {
|
|
4661
|
+
return { alts: [[seg(any(), false)]] };
|
|
4662
|
+
}
|
|
4207
4663
|
}
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4664
|
+
case "literal":
|
|
4665
|
+
case "regex":
|
|
4666
|
+
case "keywords":
|
|
4667
|
+
return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
|
|
4668
|
+
case "choice": {
|
|
4669
|
+
const alts = [];
|
|
4670
|
+
for (const arm of d.parsers) alts.push(...rec(arm).alts);
|
|
4671
|
+
return { alts };
|
|
4672
|
+
}
|
|
4673
|
+
case "sequence": {
|
|
4674
|
+
const chain = [];
|
|
4675
|
+
for (const term of d.parsers) {
|
|
4676
|
+
if (!isZeroWidthAssertion(term)) {
|
|
4677
|
+
const tr = rec(term);
|
|
4678
|
+
if (tr.alts.length === 1) chain.push(...tr.alts[0]);
|
|
4679
|
+
else chain.push(collapse(tr, matchesEmpty(term)));
|
|
4680
|
+
}
|
|
4681
|
+
if (!matchesEmpty(term)) return { alts: [chain] };
|
|
4682
|
+
}
|
|
4683
|
+
return { alts: [chain] };
|
|
4223
4684
|
}
|
|
4224
|
-
|
|
4685
|
+
// `many`/`optional` are skippable as a whole → their leading chars start the
|
|
4686
|
+
// sequence but the chain must continue past them (force nullable). `oneOrMore`
|
|
4687
|
+
// requires at least one repetition, so it is NOT made nullable here.
|
|
4688
|
+
case "many":
|
|
4689
|
+
case "optional":
|
|
4690
|
+
return forceNullable(rec(d.parser));
|
|
4691
|
+
case "oneOrMore":
|
|
4692
|
+
case "transform":
|
|
4693
|
+
case "label":
|
|
4694
|
+
case "field":
|
|
4695
|
+
case "trivia":
|
|
4696
|
+
case "token":
|
|
4697
|
+
case "leaf":
|
|
4698
|
+
case "node":
|
|
4699
|
+
case "grammar":
|
|
4700
|
+
case "expect":
|
|
4701
|
+
return rec(d.parser);
|
|
4702
|
+
case "sepBy":
|
|
4703
|
+
return rec(d.parser);
|
|
4704
|
+
case "skip":
|
|
4705
|
+
return rec(d.main);
|
|
4706
|
+
// not / scanTo / guard / withCtx / recover / unknown → shallow set (safe).
|
|
4707
|
+
default:
|
|
4708
|
+
return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
|
|
4225
4709
|
}
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
case "field":
|
|
4232
|
-
case "trivia":
|
|
4233
|
-
case "token":
|
|
4234
|
-
case "leaf":
|
|
4235
|
-
case "node":
|
|
4236
|
-
case "grammar":
|
|
4237
|
-
case "expect":
|
|
4238
|
-
return rec(d.parser);
|
|
4239
|
-
case "sepBy":
|
|
4240
|
-
return rec(d.parser);
|
|
4241
|
-
case "skip":
|
|
4242
|
-
return rec(d.main);
|
|
4243
|
-
// not / scanTo / guard / withCtx / recover / unknown → shallow set (safe).
|
|
4244
|
-
default:
|
|
4245
|
-
return { concrete: p._meta.firstSet, refs: [] };
|
|
4710
|
+
};
|
|
4711
|
+
try {
|
|
4712
|
+
return compute();
|
|
4713
|
+
} finally {
|
|
4714
|
+
seen.delete(p);
|
|
4246
4715
|
}
|
|
4247
4716
|
}
|
|
4248
4717
|
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 }";
|
|
@@ -4446,16 +4915,19 @@ function ensureTriviaFn(ctx) {
|
|
|
4446
4915
|
const savedIndent = ctx.indent;
|
|
4447
4916
|
const savedFailLabel = ctx.failLabel;
|
|
4448
4917
|
const savedTrivia = ctx.activeTrivia;
|
|
4918
|
+
const savedScanSkip = ctx.activeScanSkip;
|
|
4449
4919
|
const savedCapAsTrivia = ctx.capAsTrivia;
|
|
4450
4920
|
ctx.indent = 2;
|
|
4451
4921
|
ctx.failLabel = "_triv";
|
|
4452
4922
|
ctx.capAsTrivia = true;
|
|
4453
4923
|
ctx.activeTrivia = void 0;
|
|
4924
|
+
ctx.activeScanSkip = void 0;
|
|
4454
4925
|
const r = emit(trivia2, ctx, "_pos");
|
|
4455
4926
|
ctx.indent = savedIndent;
|
|
4456
4927
|
ctx.failLabel = savedFailLabel;
|
|
4457
4928
|
ctx.capAsTrivia = savedCapAsTrivia;
|
|
4458
4929
|
ctx.activeTrivia = savedTrivia;
|
|
4930
|
+
ctx.activeScanSkip = savedScanSkip;
|
|
4459
4931
|
ctx.namedFnDecls.push([
|
|
4460
4932
|
`function ${fnName}(input, _pos, _ctx, _cap) {`,
|
|
4461
4933
|
` let _e = _pos`,
|
|
@@ -4733,7 +5205,7 @@ function emitKeywordsFast(def, ctx, pos) {
|
|
|
4733
5205
|
const bodyInd = ind(ctx) + " ";
|
|
4734
5206
|
const tries = [];
|
|
4735
5207
|
for (const w of def.words) {
|
|
4736
|
-
const cps = Array.from(w, (
|
|
5208
|
+
const cps = Array.from(w, (ch2) => ch2.codePointAt(0));
|
|
4737
5209
|
let shape = def.caseInsensitive ? { kind: "litFold", open: cps } : { kind: "seq", parts: [{ part: "lit", cps, optional: false }] };
|
|
4738
5210
|
if (boundary) {
|
|
4739
5211
|
shape = { kind: "lookahead", inner: shape, ranges: boundary.ranges, classNegated: boundary.negated, negative: true };
|
|
@@ -4926,7 +5398,7 @@ function canMatchEmptyAtStart(p) {
|
|
|
4926
5398
|
const d = p._def;
|
|
4927
5399
|
switch (d.tag) {
|
|
4928
5400
|
case "regex":
|
|
4929
|
-
return
|
|
5401
|
+
return regexMatchesEmpty(d.source, d.flags);
|
|
4930
5402
|
case "optional":
|
|
4931
5403
|
return true;
|
|
4932
5404
|
case "many":
|
|
@@ -4978,10 +5450,10 @@ function emitDisjointArm(p, ctx, pos, valV, endV, coverageId) {
|
|
|
4978
5450
|
}
|
|
4979
5451
|
function emitDisjointArmGated(def, i, ctx, pos, valV, endV, coverageId) {
|
|
4980
5452
|
const p = def.parsers[i];
|
|
4981
|
-
const
|
|
4982
|
-
if (!
|
|
5453
|
+
const gate2 = def.gates[i];
|
|
5454
|
+
if (!gate2) return emitDisjointArm(p, ctx, pos, valV, endV, coverageId);
|
|
4983
5455
|
const gateIdx = ctx.mapFns.length;
|
|
4984
|
-
ctx.mapFns.push(
|
|
5456
|
+
ctx.mapFns.push(gate2);
|
|
4985
5457
|
ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
|
|
4986
5458
|
const gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
|
|
4987
5459
|
const stmts = [`${ind(ctx)}if (${gateCond}) {`];
|
|
@@ -5139,7 +5611,7 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
|
|
|
5139
5611
|
if (preStmts) stmts.push(...preStmts);
|
|
5140
5612
|
for (let i = 0; i < def.parsers.length; i++) {
|
|
5141
5613
|
const p = def.parsers[i];
|
|
5142
|
-
const
|
|
5614
|
+
const gate2 = def.gates[i];
|
|
5143
5615
|
const autoNot = def.autoNot[i];
|
|
5144
5616
|
const atStart = failsAtStart(p);
|
|
5145
5617
|
const staticFx = armStaticExpected(ctx, p);
|
|
@@ -5151,9 +5623,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
|
|
|
5151
5623
|
})();
|
|
5152
5624
|
const fsGuard = armGuardPrefix === void 0 ? baseFsGuard : baseFsGuard === null ? armGuardPrefix : `${armGuardPrefix} && (${baseFsGuard})`;
|
|
5153
5625
|
let gateCond = null;
|
|
5154
|
-
if (
|
|
5626
|
+
if (gate2) {
|
|
5155
5627
|
const gateIdx = ctx.mapFns.length;
|
|
5156
|
-
ctx.mapFns.push(
|
|
5628
|
+
ctx.mapFns.push(gate2);
|
|
5157
5629
|
ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
|
|
5158
5630
|
gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
|
|
5159
5631
|
}
|
|
@@ -5570,10 +6042,15 @@ function emitScanTo(def, ctx, pos) {
|
|
|
5570
6042
|
const probeCtx = { ...ctx, capturing: false, noHoist: true };
|
|
5571
6043
|
const { stmts: sentStmts, okVar: sentOk } = emitFallible(def.sentinel, probeCtx, curV);
|
|
5572
6044
|
stmts.push(...sentStmts, `${ind(ctx)}if (${sentOk}) { ${foundV} = true; break }`);
|
|
5573
|
-
|
|
6045
|
+
const effectiveSkip = def.raw ? def.skip : [
|
|
6046
|
+
...ctx.activeTrivia ? [ctx.activeTrivia] : [],
|
|
6047
|
+
...ctx.activeScanSkip ?? [],
|
|
6048
|
+
...def.skip
|
|
6049
|
+
];
|
|
6050
|
+
if (effectiveSkip.length > 0) {
|
|
5574
6051
|
const advV = v(ctx, "_stadv");
|
|
5575
6052
|
stmts.push(`${ind(ctx)}let ${advV} = false`);
|
|
5576
|
-
for (const skipper of
|
|
6053
|
+
for (const skipper of effectiveSkip) {
|
|
5577
6054
|
const { stmts: skStmts, okVar: skOk, endVar: skEnd } = emitFallible(skipper, probeCtx, curV);
|
|
5578
6055
|
stmts.push(
|
|
5579
6056
|
`${ind(ctx)}if (!${advV}) {`,
|
|
@@ -6043,6 +6520,24 @@ function emit(p, ctx, pos) {
|
|
|
6043
6520
|
return instrument(dispatch());
|
|
6044
6521
|
}
|
|
6045
6522
|
function emitDispatch(p, ctx, pos) {
|
|
6523
|
+
const bal = p._balancedAmbient;
|
|
6524
|
+
if (bal && ctx.activeScanSkip && ctx.activeScanSkip.length > 0 && !ctx.balancedRebuildStack?.has(p)) {
|
|
6525
|
+
const interior = buildBalancedInterior(bal.open, bal.close, [...ctx.activeScanSkip, ...bal.ownSkip]);
|
|
6526
|
+
markUnusedValues(interior);
|
|
6527
|
+
if (ctx.lazyUsage) {
|
|
6528
|
+
const u = analyzeLazyUsage(interior);
|
|
6529
|
+
for (const [k, n] of u.counts) ctx.lazyUsage.counts.set(k, (ctx.lazyUsage.counts.get(k) ?? 0) + n);
|
|
6530
|
+
for (const k of u.recursive) ctx.lazyUsage.recursive.add(k);
|
|
6531
|
+
for (const [k, s] of u.sizes) ctx.lazyUsage.sizes.set(k, s);
|
|
6532
|
+
}
|
|
6533
|
+
const stack = ctx.balancedRebuildStack ?? (ctx.balancedRebuildStack = /* @__PURE__ */ new Set());
|
|
6534
|
+
stack.add(p);
|
|
6535
|
+
try {
|
|
6536
|
+
return emit(interior, ctx, pos);
|
|
6537
|
+
} finally {
|
|
6538
|
+
stack.delete(p);
|
|
6539
|
+
}
|
|
6540
|
+
}
|
|
6046
6541
|
const def = p._def;
|
|
6047
6542
|
switch (def.tag) {
|
|
6048
6543
|
case "literal":
|
|
@@ -6464,9 +6959,36 @@ function ruleDependencies(ruleMap) {
|
|
|
6464
6959
|
}
|
|
6465
6960
|
return deps;
|
|
6466
6961
|
}
|
|
6962
|
+
function resolveGatingLevel(opt) {
|
|
6963
|
+
const explicit = typeof opt === "string" ? opt : opt?.level;
|
|
6964
|
+
if (explicit !== void 0) return explicit;
|
|
6965
|
+
const env = typeof process !== "undefined" ? process.env?.PARSEMAN_GATING : void 0;
|
|
6966
|
+
if (env === "off" || env === "warn" || env === "error") return env;
|
|
6967
|
+
return "warn";
|
|
6968
|
+
}
|
|
6969
|
+
function runGatingDiagnostic(combinator, opt) {
|
|
6970
|
+
const level = resolveGatingLevel(opt);
|
|
6971
|
+
if (level === "off") return void 0;
|
|
6972
|
+
const accept = opt !== null && typeof opt === "object" ? opt.accept : void 0;
|
|
6973
|
+
let report;
|
|
6974
|
+
try {
|
|
6975
|
+
report = analyzeGating(combinator, accept ? { accept } : void 0);
|
|
6976
|
+
} catch {
|
|
6977
|
+
return void 0;
|
|
6978
|
+
}
|
|
6979
|
+
const lines = formatGatingWarnings(report);
|
|
6980
|
+
if (lines.length > 0) {
|
|
6981
|
+
if (level === "error") throw new Error(`parseman: ${report.ungated.length} ungated hot choice(s) / ${report.antiPatterns.length} anti-pattern(s)
|
|
6982
|
+
${lines.join("\n")}`);
|
|
6983
|
+
for (const l of lines) console.warn(l);
|
|
6984
|
+
}
|
|
6985
|
+
return report;
|
|
6986
|
+
}
|
|
6467
6987
|
function compile(combinator, mapFnSources, opts) {
|
|
6988
|
+
const gatingReport = runGatingDiagnostic(combinator, opts?.gating);
|
|
6468
6989
|
markUnusedValues(combinator);
|
|
6469
6990
|
const grammarTrivia = combinator._meta.grammarTrivia;
|
|
6991
|
+
const grammarScanSkip = combinator._meta.grammarScanSkip;
|
|
6470
6992
|
const ctx = {
|
|
6471
6993
|
vars: 0,
|
|
6472
6994
|
indent: 1,
|
|
@@ -6488,7 +7010,8 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
6488
7010
|
recovery: opts?.recovery ?? false,
|
|
6489
7011
|
...opts?.coverage ? { coverage: { plan: buildGrammarPlan(combinator), entry: combinator } } : {},
|
|
6490
7012
|
lazyUsage: analyzeLazyUsage(combinator),
|
|
6491
|
-
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
|
|
7013
|
+
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {},
|
|
7014
|
+
...grammarScanSkip ? { activeScanSkip: grammarScanSkip } : {}
|
|
6492
7015
|
};
|
|
6493
7016
|
const r = emit(combinator, ctx, "_pos");
|
|
6494
7017
|
const coverageRootRuleId = ctx.coverage?.plan.rules.get(combinator);
|
|
@@ -6552,6 +7075,7 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
6552
7075
|
source,
|
|
6553
7076
|
inlineExpression,
|
|
6554
7077
|
...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions },
|
|
7078
|
+
...gatingReport === void 0 ? {} : { gating: gatingReport },
|
|
6555
7079
|
parse(input, pos = 0) {
|
|
6556
7080
|
return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, defaultCtx);
|
|
6557
7081
|
},
|
|
@@ -6587,6 +7111,7 @@ function compileRuleMap(ruleMap, opts) {
|
|
|
6587
7111
|
for (const [, rule] of ruleMap) markUnusedValues(rule);
|
|
6588
7112
|
const coverageWinners = opts?.coverage ? Object.fromEntries(ruleMap.filter(([, rule]) => rule._def.tag !== "lazy")) : void 0;
|
|
6589
7113
|
const grammarTrivia = opts?.trivia;
|
|
7114
|
+
const grammarScanSkip = opts?.scanSkip ?? ruleMap.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
|
|
6590
7115
|
const ctx = {
|
|
6591
7116
|
vars: 0,
|
|
6592
7117
|
indent: 1,
|
|
@@ -6608,7 +7133,8 @@ function compileRuleMap(ruleMap, opts) {
|
|
|
6608
7133
|
recovery: opts?.recovery ?? false,
|
|
6609
7134
|
...opts?.coverage ? { coverage: { plan: buildGrammarPlan(ruleMap.map(([, rule]) => rule), coverageWinners) } } : {},
|
|
6610
7135
|
lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
|
|
6611
|
-
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
|
|
7136
|
+
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {},
|
|
7137
|
+
...grammarScanSkip ? { activeScanSkip: grammarScanSkip } : {}
|
|
6612
7138
|
};
|
|
6613
7139
|
const seenNames = /* @__PURE__ */ new Map();
|
|
6614
7140
|
const ruleNames = /* @__PURE__ */ new Map();
|
|
@@ -6622,9 +7148,14 @@ function compileRuleMap(ruleMap, opts) {
|
|
|
6622
7148
|
ctx.ruleNames = ruleNames;
|
|
6623
7149
|
const perEntry = ruleMap.map(([key, rule]) => {
|
|
6624
7150
|
const savedTrivia = ctx.activeTrivia;
|
|
6625
|
-
|
|
7151
|
+
const savedScanSkip = ctx.activeScanSkip;
|
|
7152
|
+
if (rule._meta.isTrivia) {
|
|
7153
|
+
ctx.activeTrivia = void 0;
|
|
7154
|
+
ctx.activeScanSkip = void 0;
|
|
7155
|
+
}
|
|
6626
7156
|
const r = emit(rule, ctx, "_pos");
|
|
6627
7157
|
ctx.activeTrivia = savedTrivia;
|
|
7158
|
+
ctx.activeScanSkip = savedScanSkip;
|
|
6628
7159
|
return { key, rule, r };
|
|
6629
7160
|
});
|
|
6630
7161
|
const derivedSrcs = ctx.mapFnSrcs.length === ctx.mapFns.length && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : void 0;
|
|
@@ -6697,6 +7228,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6697
7228
|
if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
|
|
6698
7229
|
for (const [, rule] of ruleMapArg) markUnusedValues(rule);
|
|
6699
7230
|
const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
|
|
7231
|
+
const grammarScanSkip = opts?.scanSkip ?? ruleMapArg.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
|
|
6700
7232
|
const ruleMap = ruleMapArg.filter(([, val]) => {
|
|
6701
7233
|
const d = val._def;
|
|
6702
7234
|
if (d.tag !== "lazy") return true;
|
|
@@ -6734,7 +7266,8 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6734
7266
|
lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
|
|
6735
7267
|
ns,
|
|
6736
7268
|
deferFirstSetRefs: true,
|
|
6737
|
-
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
|
|
7269
|
+
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {},
|
|
7270
|
+
...grammarScanSkip ? { activeScanSkip: grammarScanSkip } : {}
|
|
6738
7271
|
};
|
|
6739
7272
|
const seen = /* @__PURE__ */ new Map();
|
|
6740
7273
|
const ruleNames = /* @__PURE__ */ new Map();
|
|
@@ -6798,9 +7331,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6798
7331
|
ctx.failLabel = "_pfail";
|
|
6799
7332
|
ctx.recordFail = true;
|
|
6800
7333
|
const savedTrivia = ctx.activeTrivia;
|
|
6801
|
-
|
|
7334
|
+
const savedScanSkip = ctx.activeScanSkip;
|
|
7335
|
+
if (rule._meta.isTrivia) {
|
|
7336
|
+
ctx.activeTrivia = void 0;
|
|
7337
|
+
ctx.activeScanSkip = void 0;
|
|
7338
|
+
}
|
|
6802
7339
|
const body = emit(resolved, ctx, "_pos");
|
|
6803
7340
|
ctx.activeTrivia = savedTrivia;
|
|
7341
|
+
ctx.activeScanSkip = savedScanSkip;
|
|
6804
7342
|
ctx.indent = savedIndent;
|
|
6805
7343
|
ctx.failLabel = savedFail;
|
|
6806
7344
|
ctx.recordFail = savedRec;
|
|
@@ -6847,6 +7385,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6847
7385
|
}
|
|
6848
7386
|
const firstSets = /* @__PURE__ */ new Map();
|
|
6849
7387
|
const firstSetRecipes = /* @__PURE__ */ new Map();
|
|
7388
|
+
const nullable2 = /* @__PURE__ */ new Map();
|
|
6850
7389
|
for (const [key, rule] of ruleMap) {
|
|
6851
7390
|
let resolved = rule;
|
|
6852
7391
|
const d = rule._def;
|
|
@@ -6858,15 +7397,18 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6858
7397
|
}
|
|
6859
7398
|
}
|
|
6860
7399
|
firstSets.set(key, canMatchEmptyAtStart(resolved) ? { kind: "any" } : resolved._meta.firstSet);
|
|
6861
|
-
|
|
7400
|
+
nullable2.set(key, matchesEmpty(resolved));
|
|
7401
|
+
firstSetRecipes.set(key, canMatchEmptyAtStart(resolved) ? { alts: [[{ set: any(), nullable: false }]] } : leadingFirstSetRecipe(resolved));
|
|
6862
7402
|
}
|
|
6863
7403
|
return {
|
|
7404
|
+
v: PARSEMAN_VERSION,
|
|
6864
7405
|
ns,
|
|
6865
7406
|
keys: perEntry.map((e) => e.key),
|
|
6866
7407
|
prelude,
|
|
6867
7408
|
ruleFns,
|
|
6868
7409
|
wrappers,
|
|
6869
7410
|
firstSets,
|
|
7411
|
+
nullable: nullable2,
|
|
6870
7412
|
firstSetRecipes,
|
|
6871
7413
|
deps: ruleDependencies(ruleMap),
|
|
6872
7414
|
needsEmptyTl: !!ctx.needsEmptyTl,
|
|
@@ -6945,101 +7487,6 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootR
|
|
|
6945
7487
|
].join("\n");
|
|
6946
7488
|
}
|
|
6947
7489
|
|
|
6948
|
-
// src/combinators/scanTo.ts
|
|
6949
|
-
function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
|
|
6950
|
-
const meta = {
|
|
6951
|
-
firstSet: any(),
|
|
6952
|
-
canMatchNewline: true,
|
|
6953
|
-
isTrivia: false
|
|
6954
|
-
};
|
|
6955
|
-
return {
|
|
6956
|
-
_tag: "scanTo",
|
|
6957
|
-
_meta: meta,
|
|
6958
|
-
_def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
|
|
6959
|
-
parse(input, pos, ctx) {
|
|
6960
|
-
let cur = pos;
|
|
6961
|
-
const probeCtx = {
|
|
6962
|
-
trackLines: ctx.trackLines,
|
|
6963
|
-
state: ctx.state,
|
|
6964
|
-
...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
|
|
6965
|
-
};
|
|
6966
|
-
const emit2 = (end) => {
|
|
6967
|
-
if (end > pos && cstCaptureActive(ctx)) {
|
|
6968
|
-
const leaf2 = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
|
|
6969
|
-
pushCstLeaf(ctx, leaf2);
|
|
6970
|
-
}
|
|
6971
|
-
};
|
|
6972
|
-
while (cur < input.length) {
|
|
6973
|
-
const s = sentinel.parse(input, cur, probeCtx);
|
|
6974
|
-
if (s.ok) {
|
|
6975
|
-
emit2(cur);
|
|
6976
|
-
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
6977
|
-
}
|
|
6978
|
-
let advanced = false;
|
|
6979
|
-
for (const skipper of skip2) {
|
|
6980
|
-
const r = skipper.parse(input, cur, probeCtx);
|
|
6981
|
-
if (r.ok && r.span.end > cur) {
|
|
6982
|
-
cur = r.span.end;
|
|
6983
|
-
advanced = true;
|
|
6984
|
-
break;
|
|
6985
|
-
}
|
|
6986
|
-
}
|
|
6987
|
-
if (!advanced) cur++;
|
|
6988
|
-
}
|
|
6989
|
-
if (orEOF) {
|
|
6990
|
-
emit2(cur);
|
|
6991
|
-
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
6992
|
-
}
|
|
6993
|
-
const sentDef = sentinel._def;
|
|
6994
|
-
const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
|
|
6995
|
-
return { ok: false, expected, span: { start: pos, end: cur } };
|
|
6996
|
-
}
|
|
6997
|
-
};
|
|
6998
|
-
}
|
|
6999
|
-
function balanced(open, close, options = {}) {
|
|
7000
|
-
const self = ref();
|
|
7001
|
-
const skips = options.skip ?? [];
|
|
7002
|
-
const stop = /* @__PURE__ */ new Set([open, close]);
|
|
7003
|
-
let bounded = true;
|
|
7004
|
-
for (const sk of skips) {
|
|
7005
|
-
const cs = firstSetClassChars(sk);
|
|
7006
|
-
if (cs === null) {
|
|
7007
|
-
bounded = false;
|
|
7008
|
-
break;
|
|
7009
|
-
}
|
|
7010
|
-
for (const ch of cs) stop.add(ch);
|
|
7011
|
-
}
|
|
7012
|
-
const cls = [...stop].map(escapeClassChar).join("");
|
|
7013
|
-
const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
|
|
7014
|
-
const inner = many(choice(self, ...skips, content));
|
|
7015
|
-
const combi = transform(
|
|
7016
|
-
sequence(literal(open), inner, expect(literal(close))),
|
|
7017
|
-
// parts: strings (content/self) or arrays (a sequence-shaped skip) or a
|
|
7018
|
-
// ParseError (recovered close). `c` is the close string or a ParseError.
|
|
7019
|
-
([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
|
|
7020
|
-
);
|
|
7021
|
-
if (combi._def.tag === "transform") {
|
|
7022
|
-
combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
|
|
7023
|
-
combi._def.recognitionOnly = true;
|
|
7024
|
-
}
|
|
7025
|
-
self.define(combi);
|
|
7026
|
-
return combi;
|
|
7027
|
-
}
|
|
7028
|
-
function firstSetClassChars(c) {
|
|
7029
|
-
const fs2 = c._meta.firstSet;
|
|
7030
|
-
if (fs2.kind === "empty") return [];
|
|
7031
|
-
if (fs2.kind !== "ranges") return null;
|
|
7032
|
-
const out = [];
|
|
7033
|
-
for (const { lo, hi } of fs2.ranges) {
|
|
7034
|
-
if (hi - lo > 8) return null;
|
|
7035
|
-
for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
|
|
7036
|
-
}
|
|
7037
|
-
return out;
|
|
7038
|
-
}
|
|
7039
|
-
function escapeClassChar(ch) {
|
|
7040
|
-
return ch.replace(/[\\\]^-]/g, "\\$&");
|
|
7041
|
-
}
|
|
7042
|
-
|
|
7043
7490
|
// src/combinators/withCtx.ts
|
|
7044
7491
|
function withCtx(extra, combinator) {
|
|
7045
7492
|
const meta = {
|
|
@@ -7096,7 +7543,7 @@ function childrenOf2(def) {
|
|
|
7096
7543
|
return [];
|
|
7097
7544
|
}
|
|
7098
7545
|
}
|
|
7099
|
-
var
|
|
7546
|
+
var ruleNameOf2 = (c) => c._ruleName;
|
|
7100
7547
|
function lazyTarget(c) {
|
|
7101
7548
|
if (c._def.tag !== "lazy") return null;
|
|
7102
7549
|
try {
|
|
@@ -7107,9 +7554,9 @@ function lazyTarget(c) {
|
|
|
7107
7554
|
}
|
|
7108
7555
|
var Unserializable = class extends Error {
|
|
7109
7556
|
};
|
|
7110
|
-
function serializeRuleMap(ruleMap) {
|
|
7557
|
+
function serializeRuleMap(ruleMap, scanSkip) {
|
|
7111
7558
|
try {
|
|
7112
|
-
return new Serializer(ruleMap).run();
|
|
7559
|
+
return new Serializer(ruleMap, scanSkip ?? []).run();
|
|
7113
7560
|
} catch (e) {
|
|
7114
7561
|
if (e instanceof Unserializable) {
|
|
7115
7562
|
if (process.env.PARSEMAN_IR_DEBUG) console.error(`[ir] fallback: ${e.message}`);
|
|
@@ -7163,15 +7610,15 @@ function evalRuleMapIR(ir) {
|
|
|
7163
7610
|
if (Array.isArray(it)) {
|
|
7164
7611
|
const [src, comb] = it;
|
|
7165
7612
|
gateSrcs.push(src);
|
|
7166
|
-
let
|
|
7613
|
+
let gate2;
|
|
7167
7614
|
try {
|
|
7168
|
-
|
|
7615
|
+
gate2 = (0, eval)(`(${src})`);
|
|
7169
7616
|
} catch {
|
|
7170
|
-
|
|
7617
|
+
gate2 = () => {
|
|
7171
7618
|
throw new Error("IR gate fn not materialized");
|
|
7172
7619
|
};
|
|
7173
7620
|
}
|
|
7174
|
-
return { gate, combinator: comb };
|
|
7621
|
+
return { gate: gate2, combinator: comb };
|
|
7175
7622
|
}
|
|
7176
7623
|
gateSrcs.push(null);
|
|
7177
7624
|
return it;
|
|
@@ -7270,40 +7717,75 @@ var Serializer = class {
|
|
|
7270
7717
|
decls = [];
|
|
7271
7718
|
ruleValues = /* @__PURE__ */ new Set();
|
|
7272
7719
|
ruleMap;
|
|
7273
|
-
|
|
7720
|
+
scanSkip;
|
|
7721
|
+
constructor(ruleMap, scanSkip = []) {
|
|
7274
7722
|
this.ruleMap = ruleMap;
|
|
7723
|
+
this.scanSkip = scanSkip;
|
|
7275
7724
|
for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
|
|
7276
7725
|
}
|
|
7726
|
+
/** A grammar-level `scanSkip` unit is an opaque TERMINAL (a string, a balanced
|
|
7727
|
+
* bracket) — it must not reach a grammar-rule ref (`g[name]`), because the
|
|
7728
|
+
* serialized `scanSkip` array sits in the `rules({ scanSkip })` OPTIONS position,
|
|
7729
|
+
* outside the `(g) => …` factory where `g` is bound. A unit that does reference a
|
|
7730
|
+
* rule can't be carried in options; bail to the full-pieces fallback. */
|
|
7731
|
+
assertNoRuleRef(c, seen) {
|
|
7732
|
+
if (seen.has(c)) return;
|
|
7733
|
+
seen.add(c);
|
|
7734
|
+
if (c._def.tag === "lazy") {
|
|
7735
|
+
if (ruleNameOf2(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
|
|
7736
|
+
const target = lazyTarget(c);
|
|
7737
|
+
if (target) this.assertNoRuleRef(target, seen);
|
|
7738
|
+
return;
|
|
7739
|
+
}
|
|
7740
|
+
for (const child of childrenOf2(c._def)) this.assertNoRuleRef(child, seen);
|
|
7741
|
+
}
|
|
7277
7742
|
/** A `rules()` map wraps each rule value in a named `lazy` proxy; the real body is
|
|
7278
7743
|
* its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
|
|
7279
7744
|
* `g[name]` ref — only the top-level entry is unwrapped. */
|
|
7280
7745
|
body(c) {
|
|
7281
|
-
return c._def.tag === "lazy" &&
|
|
7746
|
+
return c._def.tag === "lazy" && ruleNameOf2(c) !== void 0 ? lazyTarget(c) ?? c : c;
|
|
7282
7747
|
}
|
|
7283
7748
|
run() {
|
|
7284
7749
|
for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
|
|
7750
|
+
for (const c of this.scanSkip) {
|
|
7751
|
+
this.assertNoRuleRef(c, /* @__PURE__ */ new Set());
|
|
7752
|
+
this.analyze(c, /* @__PURE__ */ new Set());
|
|
7753
|
+
}
|
|
7285
7754
|
let n = 0;
|
|
7286
7755
|
for (const [c, count] of this.counts) {
|
|
7287
7756
|
if (this.ruleValues.has(c)) continue;
|
|
7288
7757
|
if (count >= 2 || this.selfRef.has(c)) this.constName.set(c, `_s${n++}`);
|
|
7289
7758
|
}
|
|
7759
|
+
const outerDecls = [];
|
|
7760
|
+
const innerDecls = [];
|
|
7761
|
+
let optionsArg = "";
|
|
7762
|
+
if (this.scanSkip.length > 0) {
|
|
7763
|
+
this.decls = outerDecls;
|
|
7764
|
+
const units = this.scanSkip.map((c) => this.ref(c, null));
|
|
7765
|
+
optionsArg = `{ scanSkip: [${units.join(", ")}] }, `;
|
|
7766
|
+
}
|
|
7767
|
+
this.decls = innerDecls;
|
|
7290
7768
|
const body = this.ruleMap.map(([name, c]) => ` ${JSON.stringify(name)}: ${this.wrap(this.body(c))}`).join(",\n");
|
|
7291
|
-
|
|
7769
|
+
const factory = innerDecls.length === 0 ? `(g) => ({
|
|
7292
7770
|
${body}
|
|
7293
|
-
}))
|
|
7294
|
-
|
|
7295
|
-
${this.decls.map((d) => " " + d).join("\n")}
|
|
7771
|
+
})` : `(g) => {
|
|
7772
|
+
${innerDecls.map((d) => " " + d).join("\n")}
|
|
7296
7773
|
return ({
|
|
7297
7774
|
${body}
|
|
7298
7775
|
})
|
|
7299
|
-
}
|
|
7776
|
+
}`;
|
|
7777
|
+
const call = `rules(${optionsArg}${factory})`;
|
|
7778
|
+
return outerDecls.length === 0 ? call : `(() => {
|
|
7779
|
+
${outerDecls.map((d) => " " + d).join("\n")}
|
|
7780
|
+
return ${call}
|
|
7781
|
+
})()`;
|
|
7300
7782
|
}
|
|
7301
7783
|
/** Count identity references and flag self-referential subtrees. `active` is the
|
|
7302
7784
|
* set of combinators on the current DFS path (to detect a lazy pointing back). */
|
|
7303
7785
|
analyze(c, active) {
|
|
7304
7786
|
const def = c._def;
|
|
7305
7787
|
if (def.tag === "lazy") {
|
|
7306
|
-
if (
|
|
7788
|
+
if (ruleNameOf2(c) !== void 0) return;
|
|
7307
7789
|
const target = lazyTarget(c);
|
|
7308
7790
|
if (!target) throw new Unserializable("unresolved unnamed ref");
|
|
7309
7791
|
if (active.has(target)) {
|
|
@@ -7355,7 +7837,7 @@ ${body}
|
|
|
7355
7837
|
const kid = (x) => this.ref(x, selfOf);
|
|
7356
7838
|
switch (def.tag) {
|
|
7357
7839
|
case "lazy": {
|
|
7358
|
-
if (
|
|
7840
|
+
if (ruleNameOf2(c) !== void 0) return `g[${JSON.stringify(ruleNameOf2(c))}]`;
|
|
7359
7841
|
const target = lazyTarget(c);
|
|
7360
7842
|
if (selfOf && target === selfOf.comb) return selfOf.var;
|
|
7361
7843
|
return this.ref(target, selfOf);
|
|
@@ -7409,7 +7891,7 @@ ${body}
|
|
|
7409
7891
|
case "skip":
|
|
7410
7892
|
return `skip(${kid(def.main)}, ${kid(def.skipped)})`;
|
|
7411
7893
|
case "scanTo":
|
|
7412
|
-
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} })`;
|
|
7413
7895
|
case "transform": {
|
|
7414
7896
|
if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
|
|
7415
7897
|
return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)}${def.recognitionOnly ? ", true" : ""})`;
|
|
@@ -7490,6 +7972,18 @@ function pickPieces(pieces, names) {
|
|
|
7490
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);
|
|
7491
7973
|
}
|
|
7492
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
|
+
}
|
|
7493
7987
|
const winner = /* @__PURE__ */ new Map();
|
|
7494
7988
|
for (const p of pieces) for (const k of p.keys) winner.set(k, p);
|
|
7495
7989
|
for (const [k, p] of winner) {
|
|
@@ -7515,23 +8009,38 @@ function fusedBody(pieces) {
|
|
|
7515
8009
|
const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
|
|
7516
8010
|
const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
|
|
7517
8011
|
const ANY2 = { kind: "any" };
|
|
8012
|
+
const EMPTY3 = { kind: "empty" };
|
|
7518
8013
|
const recipes = /* @__PURE__ */ new Map();
|
|
7519
8014
|
const shallow = /* @__PURE__ */ new Map();
|
|
8015
|
+
const nullable2 = /* @__PURE__ */ new Map();
|
|
7520
8016
|
for (const [k, p] of winner) {
|
|
7521
8017
|
const r = p.firstSetRecipes?.get(k);
|
|
7522
8018
|
if (r) recipes.set(k, r);
|
|
7523
8019
|
const fs2 = p.firstSets?.get(k);
|
|
7524
8020
|
if (fs2) shallow.set(k, fs2);
|
|
8021
|
+
const nu = p.nullable?.get(k);
|
|
8022
|
+
if (nu !== void 0) nullable2.set(k, nu);
|
|
7525
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
|
+
};
|
|
7526
8038
|
const finalFS = /* @__PURE__ */ new Map();
|
|
7527
|
-
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);
|
|
7528
8040
|
for (let iter = 0; iter <= recipes.size; iter++) {
|
|
7529
8041
|
let changed = false;
|
|
7530
8042
|
for (const [k, r] of recipes) {
|
|
7531
|
-
|
|
7532
|
-
for (const ref2 of r.refs) {
|
|
7533
|
-
fs2 = union(fs2, recipes.has(ref2) || shallow.has(ref2) ? finalFS.get(ref2) ?? ANY2 : ANY2);
|
|
7534
|
-
}
|
|
8043
|
+
const fs2 = resolveRecipe(r);
|
|
7535
8044
|
if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs2)) {
|
|
7536
8045
|
finalFS.set(k, fs2);
|
|
7537
8046
|
changed = true;
|
|
@@ -7579,8 +8088,8 @@ function materializePiece(p, trivia2, captureTerminals = false) {
|
|
|
7579
8088
|
return pieces;
|
|
7580
8089
|
}
|
|
7581
8090
|
|
|
7582
|
-
// src/combinators/
|
|
7583
|
-
function
|
|
8091
|
+
// src/combinators/gate.ts
|
|
8092
|
+
function gate(predicate) {
|
|
7584
8093
|
const meta = {
|
|
7585
8094
|
firstSet: { kind: "any" },
|
|
7586
8095
|
canMatchNewline: false,
|
|
@@ -7837,17 +8346,17 @@ function isGatedArmExpr(e) {
|
|
|
7837
8346
|
return hasGate && hasCombinator;
|
|
7838
8347
|
}
|
|
7839
8348
|
function gatedArmParts(e) {
|
|
7840
|
-
let
|
|
8349
|
+
let gate2;
|
|
7841
8350
|
let combinator;
|
|
7842
8351
|
for (const prop of e.properties) {
|
|
7843
8352
|
if (prop.type !== "Property") return null;
|
|
7844
8353
|
const op = prop;
|
|
7845
8354
|
const name = propName(op);
|
|
7846
|
-
if (name === "gate")
|
|
8355
|
+
if (name === "gate") gate2 = op.value;
|
|
7847
8356
|
else if (name === "combinator") combinator = op.value;
|
|
7848
8357
|
else return null;
|
|
7849
8358
|
}
|
|
7850
|
-
return
|
|
8359
|
+
return gate2 && combinator ? { gate: gate2, combinator } : null;
|
|
7851
8360
|
}
|
|
7852
8361
|
function isCombinator(v2) {
|
|
7853
8362
|
return !!v2 && typeof v2 === "object" && "_def" in v2;
|
|
@@ -8049,14 +8558,14 @@ function exprToCombi(node2, scope, code, mfs) {
|
|
|
8049
8558
|
return null;
|
|
8050
8559
|
}
|
|
8051
8560
|
}
|
|
8052
|
-
if (callee.name === "guard") {
|
|
8561
|
+
if (callee.name === "gate" || callee.name === "guard") {
|
|
8053
8562
|
if (code === void 0 || mfs === void 0) return null;
|
|
8054
8563
|
const [predArg] = node2.arguments;
|
|
8055
8564
|
if (!predArg || predArg.type === "SpreadElement") return null;
|
|
8056
8565
|
const predSrc = stripTsFromSource(predArg, code);
|
|
8057
8566
|
mfs.push(predSrc);
|
|
8058
8567
|
try {
|
|
8059
|
-
const combi =
|
|
8568
|
+
const combi = gate(() => true);
|
|
8060
8569
|
if (combi._def.tag !== "guard") return null;
|
|
8061
8570
|
combi._def.predSrc = predSrc;
|
|
8062
8571
|
return combi;
|
|
@@ -8680,21 +9189,32 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8680
9189
|
return null;
|
|
8681
9190
|
}
|
|
8682
9191
|
const optionsArg = optionsFirst ? arg0 : arg1;
|
|
8683
|
-
const
|
|
8684
|
-
(p) => p.key?.name ===
|
|
9192
|
+
const optionValue = (name) => optionsArg?.type === "ObjectExpression" ? (optionsArg.properties ?? []).find(
|
|
9193
|
+
(p) => p.key?.name === name
|
|
8685
9194
|
)?.value : void 0;
|
|
9195
|
+
const triviaValue = optionValue("trivia");
|
|
8686
9196
|
const gTrivia = triviaValue ? evaluateExpr(triviaValue, scope, code, []) : void 0;
|
|
8687
|
-
|
|
9197
|
+
const scanSkipValue = optionValue("scanSkip");
|
|
9198
|
+
const gScanSkip = scanSkipValue ? evaluateCombinatorArray(scanSkipValue, scope, code) ?? void 0 : void 0;
|
|
9199
|
+
if (gScanSkip) {
|
|
9200
|
+
for (const rule of ruleMap.values()) {
|
|
9201
|
+
if (rule && !rule._meta.isTrivia) {
|
|
9202
|
+
;
|
|
9203
|
+
rule._meta.grammarScanSkip = gScanSkip;
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
}
|
|
9207
|
+
return { ruleMap, ...gTrivia ? { trivia: gTrivia } : {}, ...gScanSkip ? { scanSkip: gScanSkip } : {} };
|
|
8688
9208
|
};
|
|
8689
9209
|
const compileRulesFactory = (init, label2) => {
|
|
8690
9210
|
const evaluated = evaluateRulesFactory(init, label2);
|
|
8691
9211
|
if (!evaluated) return null;
|
|
8692
|
-
const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, recovery, coverage: grammarCoverage });
|
|
9212
|
+
const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {}, recovery, coverage: grammarCoverage });
|
|
8693
9213
|
if (!compiled) {
|
|
8694
9214
|
warn(init.start, `${label2}: rule map couldn't be inlined`);
|
|
8695
9215
|
return null;
|
|
8696
9216
|
}
|
|
8697
|
-
return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {} };
|
|
9217
|
+
return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
|
|
8698
9218
|
};
|
|
8699
9219
|
const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
|
|
8700
9220
|
const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
|
|
@@ -8702,13 +9222,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8702
9222
|
const isPickCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "pick";
|
|
8703
9223
|
const localRuleMaps = /* @__PURE__ */ new Map();
|
|
8704
9224
|
const localGrammarTrivia = /* @__PURE__ */ new Map();
|
|
9225
|
+
const localGrammarScanSkip = /* @__PURE__ */ new Map();
|
|
8705
9226
|
const nsFor = (label2) => `_${createHash("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
|
|
8706
9227
|
const stripIndent = (s) => s.replace(/\n[ \t]+/g, "\n");
|
|
8707
9228
|
const serializePieces = (p) => {
|
|
8708
9229
|
const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
|
|
8709
9230
|
([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
|
|
8710
9231
|
).join(", ")}])`;
|
|
8711
|
-
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: [] }`;
|
|
9232
|
+
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: [] }`;
|
|
8712
9233
|
};
|
|
8713
9234
|
const isSpread = (it) => "__spreadLocal" in it;
|
|
8714
9235
|
const isIR = (it) => "ir" in it && !("ruleFns" in it);
|
|
@@ -8819,12 +9340,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8819
9340
|
}
|
|
8820
9341
|
return pieces;
|
|
8821
9342
|
};
|
|
8822
|
-
const localCarried = (rm, label2, composing) => {
|
|
9343
|
+
const localCarried = (rm, label2, composing, scanSkip) => {
|
|
8823
9344
|
const entries = [...rm];
|
|
8824
9345
|
const ns = nsFor(label2);
|
|
8825
|
-
const ir = serializeRuleMap(entries);
|
|
9346
|
+
const ir = serializeRuleMap(entries, scanSkip);
|
|
8826
9347
|
if (ir) return { carried: [{ ns, ir }] };
|
|
8827
|
-
const p = compileLinkable(entries, ns, { ...composing ? { trivia: composing } : {}, recovery });
|
|
9348
|
+
const p = compileLinkable(entries, ns, { ...composing ? { trivia: composing } : {}, ...scanSkip ? { scanSkip } : {}, recovery });
|
|
8828
9349
|
return p ? { carried: [p] } : null;
|
|
8829
9350
|
};
|
|
8830
9351
|
const argPieces = (arg, label2, composing) => {
|
|
@@ -8849,18 +9370,13 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8849
9370
|
}
|
|
8850
9371
|
}
|
|
8851
9372
|
if (isRulesCall(arg)) {
|
|
8852
|
-
const
|
|
8853
|
-
|
|
8854
|
-
const a1 = rulesArgs[1];
|
|
8855
|
-
const optionsFirst = a0?.type === "ObjectExpression";
|
|
8856
|
-
const factory = optionsFirst ? a1 : a0;
|
|
8857
|
-
const rm = factory ? evaluateParserFactory(factory, scope, code, []) : null;
|
|
8858
|
-
return rm ? localCarried(rm, label2, composing) : null;
|
|
9373
|
+
const evaluated = evaluateRulesFactory(arg, label2);
|
|
9374
|
+
return evaluated ? localCarried(evaluated.ruleMap, label2, composing, evaluated.scanSkip) : null;
|
|
8859
9375
|
}
|
|
8860
9376
|
if (arg.type === "Identifier") {
|
|
8861
9377
|
const name = arg.name;
|
|
8862
9378
|
const rm = localRuleMaps.get(name);
|
|
8863
|
-
if (rm) return localCarried(rm, label2, composing);
|
|
9379
|
+
if (rm) return localCarried(rm, label2, composing, localGrammarScanSkip.get(name));
|
|
8864
9380
|
const composed = localComposedCarried.get(name);
|
|
8865
9381
|
if (composed) return { carried: composed };
|
|
8866
9382
|
const pieces = importedPieces(name);
|
|
@@ -8948,11 +9464,15 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8948
9464
|
}
|
|
8949
9465
|
const localArg = elements[elements.length - 1];
|
|
8950
9466
|
let localRules = null;
|
|
9467
|
+
let localScanSkip;
|
|
8951
9468
|
if (isRulesCall(localArg)) {
|
|
8952
9469
|
const evaluated = evaluateRulesFactory(localArg, `composeLeaf${init.start}`);
|
|
8953
9470
|
localRules = evaluated?.ruleMap ?? null;
|
|
9471
|
+
localScanSkip = evaluated?.scanSkip;
|
|
8954
9472
|
} else if (localArg.type === "Identifier") {
|
|
8955
|
-
|
|
9473
|
+
const name = localArg.name;
|
|
9474
|
+
localRules = localRuleMaps.get(name) ?? null;
|
|
9475
|
+
localScanSkip = localGrammarScanSkip.get(name);
|
|
8956
9476
|
}
|
|
8957
9477
|
if (!localRules) {
|
|
8958
9478
|
warn(init.start, "composeLeaf(): final argument must be a local rules() map");
|
|
@@ -8970,7 +9490,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
8970
9490
|
}
|
|
8971
9491
|
try {
|
|
8972
9492
|
const localNs = nsFor(`composeLeaf${init.start}`);
|
|
8973
|
-
const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, recovery });
|
|
9493
|
+
const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, ...localScanSkip ? { scanSkip: localScanSkip } : {}, recovery });
|
|
8974
9494
|
if (!plainLocalPiece) {
|
|
8975
9495
|
warn(init.start, "composeLeaf(): local rules could not be statically compiled");
|
|
8976
9496
|
return null;
|
|
@@ -9072,15 +9592,19 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9072
9592
|
if (!compiledRules) continue;
|
|
9073
9593
|
localRuleMaps.set(varName, compiledRules.ruleMap);
|
|
9074
9594
|
if (compiledRules.trivia) localGrammarTrivia.set(varName, compiledRules.trivia);
|
|
9595
|
+
if (compiledRules.scanSkip) localGrammarScanSkip.set(varName, compiledRules.scanSkip);
|
|
9075
9596
|
let replacement = withCoverageDefinitions(
|
|
9076
9597
|
compiledRules.replacement,
|
|
9077
9598
|
compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(compiledRules.replacement)
|
|
9078
9599
|
);
|
|
9079
9600
|
if (exportPrefix) {
|
|
9080
9601
|
const ns = nsFor(varName);
|
|
9081
|
-
const pieces = compileLinkable([...compiledRules.ruleMap], ns, {
|
|
9602
|
+
const pieces = compileLinkable([...compiledRules.ruleMap], ns, {
|
|
9603
|
+
...compiledRules.scanSkip ? { scanSkip: compiledRules.scanSkip } : {},
|
|
9604
|
+
recovery
|
|
9605
|
+
});
|
|
9082
9606
|
if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
|
|
9083
|
-
const ir = serializeRuleMap([...compiledRules.ruleMap]);
|
|
9607
|
+
const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
|
|
9084
9608
|
replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
|
|
9085
9609
|
}
|
|
9086
9610
|
}
|
|
@@ -9200,9 +9724,18 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9200
9724
|
ms.overwrite(imp.start, imp.end, stripped);
|
|
9201
9725
|
}
|
|
9202
9726
|
}
|
|
9203
|
-
|
|
9727
|
+
const applied = runtimeComposeFallback ? [] : replacements;
|
|
9728
|
+
for (const { start, end, replacement } of applied.slice().sort((a, b) => b.start - a.start)) {
|
|
9204
9729
|
ms.overwrite(start, end, replacement);
|
|
9205
9730
|
}
|
|
9731
|
+
if (applied.length > 0) {
|
|
9732
|
+
ms.prepend(
|
|
9733
|
+
`// Generated by parseman v${PARSEMAN_VERSION} \u2014 DO NOT EDIT.
|
|
9734
|
+
// Version-locked: compile AND fuse/link this artifact with parseman v${PARSEMAN_VERSION} ONLY.
|
|
9735
|
+
// Parseman does not read artifacts across versions; recompile if the version differs.
|
|
9736
|
+
`
|
|
9737
|
+
);
|
|
9738
|
+
}
|
|
9206
9739
|
const unlowered = endLoweringCapture();
|
|
9207
9740
|
if (warnUnloweredRegex) {
|
|
9208
9741
|
for (const src of unlowered) {
|