parseman 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/gating.d.ts +115 -0
- package/dist/analysis/gating.d.ts.map +1 -0
- package/dist/combinators/first-set.d.ts +25 -0
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/gate.d.ts +24 -0
- package/dist/combinators/gate.d.ts.map +1 -0
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/guard.d.ts +5 -12
- package/dist/combinators/guard.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +7 -0
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/scanTo.d.ts +45 -3
- package/dist/combinators/scanTo.d.ts.map +1 -1
- package/dist/compiler/codegen.d.ts +78 -14
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/index.cjs +715 -237
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +715 -237
- package/dist/index.js.map +4 -4
- package/dist/language-service/index.cjs +2 -0
- package/dist/language-service/index.cjs.map +2 -2
- package/dist/language-service/index.js +2 -0
- package/dist/language-service/index.js.map +2 -2
- package/dist/plugin/index.cjs +741 -249
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +741 -249
- package/dist/plugin/index.js.map +4 -4
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.js.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +17 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
absoluteSpanCST: () => absoluteSpanCST,
|
|
27
27
|
absolutize: () => absolutize,
|
|
28
28
|
absolutizeCST: () => absolutizeCST,
|
|
29
|
+
analyzeGating: () => analyzeGating,
|
|
29
30
|
annotateSpan: () => annotateSpan,
|
|
30
31
|
applyEdit: () => applyEdit,
|
|
31
32
|
attempt: () => attempt,
|
|
@@ -50,8 +51,11 @@ __export(index_exports, {
|
|
|
50
51
|
cstBuildHost: () => cstBuildHost,
|
|
51
52
|
expect: () => expect,
|
|
52
53
|
field: () => field,
|
|
54
|
+
firstSetToString: () => firstSetToString,
|
|
55
|
+
formatGatingWarnings: () => formatGatingWarnings,
|
|
53
56
|
gapIsSignificant: () => gapIsSignificant,
|
|
54
57
|
gapText: () => gapText,
|
|
58
|
+
gate: () => gate,
|
|
55
59
|
grammarCoverageDefinitions: () => grammarCoverageDefinitions,
|
|
56
60
|
guard: () => guard,
|
|
57
61
|
indentMixed: () => indentMixed,
|
|
@@ -186,10 +190,13 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
186
190
|
return true;
|
|
187
191
|
}
|
|
188
192
|
}
|
|
193
|
+
function isZeroWidthAssertion(p) {
|
|
194
|
+
return p._def.tag === "not";
|
|
195
|
+
}
|
|
189
196
|
function sequenceFirstSet(parsers) {
|
|
190
197
|
let fs = empty();
|
|
191
198
|
for (const p of parsers) {
|
|
192
|
-
fs = union(fs, p._meta.firstSet);
|
|
199
|
+
if (!isZeroWidthAssertion(p)) fs = union(fs, p._meta.firstSet);
|
|
193
200
|
if (!matchesEmpty(p)) return fs;
|
|
194
201
|
}
|
|
195
202
|
return fs;
|
|
@@ -219,7 +226,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
219
226
|
case "sequence": {
|
|
220
227
|
let out = empty();
|
|
221
228
|
for (const term of d.parsers) {
|
|
222
|
-
out = union(out, fs(term));
|
|
229
|
+
if (!isZeroWidthAssertion(term)) out = union(out, fs(term));
|
|
223
230
|
if (!matchesEmpty(term)) return out;
|
|
224
231
|
}
|
|
225
232
|
return out;
|
|
@@ -507,9 +514,9 @@ var SPACE_RANGES = [
|
|
|
507
514
|
[12288, 12288],
|
|
508
515
|
[65279, 65279]
|
|
509
516
|
];
|
|
510
|
-
function shorthandRanges(
|
|
511
|
-
if (
|
|
512
|
-
if (
|
|
517
|
+
function shorthandRanges(ch2) {
|
|
518
|
+
if (ch2 === "d") return [[48, 57]];
|
|
519
|
+
if (ch2 === "s") return SPACE_RANGES;
|
|
513
520
|
return [[48, 57], [65, 90], [97, 122], [95, 95]];
|
|
514
521
|
}
|
|
515
522
|
function readUnicodeEscape(body, i) {
|
|
@@ -522,9 +529,9 @@ function parseClassRanges(body) {
|
|
|
522
529
|
const ranges = [];
|
|
523
530
|
let i = 0;
|
|
524
531
|
const readAtom = () => {
|
|
525
|
-
const
|
|
526
|
-
if (
|
|
527
|
-
if (
|
|
532
|
+
const ch2 = body[i];
|
|
533
|
+
if (ch2 === void 0) return null;
|
|
534
|
+
if (ch2 === "\\") {
|
|
528
535
|
const uni = readUnicodeEscape(body, i);
|
|
529
536
|
if (uni) {
|
|
530
537
|
i = uni.next;
|
|
@@ -538,8 +545,8 @@ function parseClassRanges(body) {
|
|
|
538
545
|
if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
|
|
539
546
|
return { cp: e.codePointAt(0) };
|
|
540
547
|
}
|
|
541
|
-
i +=
|
|
542
|
-
return { cp:
|
|
548
|
+
i += ch2.length;
|
|
549
|
+
return { cp: ch2.codePointAt(0) };
|
|
543
550
|
};
|
|
544
551
|
while (i < body.length) {
|
|
545
552
|
const lo = readAtom();
|
|
@@ -606,12 +613,35 @@ function shortScanner(source, flags) {
|
|
|
606
613
|
return minOne && end === pos ? null : end;
|
|
607
614
|
};
|
|
608
615
|
}
|
|
616
|
+
function asciiCaseFold(fs) {
|
|
617
|
+
if (fs.kind !== "ranges") return fs;
|
|
618
|
+
const ranges = fs.ranges.map((r) => ({ lo: r.lo, hi: r.hi }));
|
|
619
|
+
const add = (lo, hi) => {
|
|
620
|
+
if (lo <= hi) ranges.push({ lo, hi });
|
|
621
|
+
};
|
|
622
|
+
for (const r of fs.ranges) {
|
|
623
|
+
const uLo = Math.max(r.lo, 65), uHi = Math.min(r.hi, 90);
|
|
624
|
+
add(uLo + 32, uHi + 32);
|
|
625
|
+
const lLo = Math.max(r.lo, 97), lHi = Math.min(r.hi, 122);
|
|
626
|
+
add(lLo - 32, lHi - 32);
|
|
627
|
+
}
|
|
628
|
+
ranges.sort((a, b) => a.lo - b.lo);
|
|
629
|
+
const merged = [];
|
|
630
|
+
for (const r of ranges) {
|
|
631
|
+
const last = merged[merged.length - 1];
|
|
632
|
+
if (last && r.lo <= last.hi + 1) last.hi = Math.max(last.hi, r.hi);
|
|
633
|
+
else merged.push({ lo: r.lo, hi: r.hi });
|
|
634
|
+
}
|
|
635
|
+
return { kind: "ranges", ranges: merged };
|
|
636
|
+
}
|
|
609
637
|
function regex(pattern, flags = "") {
|
|
610
638
|
const source = typeof pattern === "string" ? pattern : pattern.source;
|
|
611
639
|
const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
|
|
612
640
|
const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
|
|
613
641
|
const scan = shortScanner(source, resolvedFlags);
|
|
614
|
-
const
|
|
642
|
+
const raw = (firstSetAnalyzer ?? permissiveFirstSet)(source);
|
|
643
|
+
const firstSet2 = !resolvedFlags.includes("i") ? raw.firstSet : resolvedFlags.includes("u") || resolvedFlags.includes("v") ? any() : asciiCaseFold(raw.firstSet);
|
|
644
|
+
const canMatchNewline = raw.canMatchNewline;
|
|
615
645
|
const meta = { firstSet: firstSet2, canMatchNewline, isTrivia: false };
|
|
616
646
|
return {
|
|
617
647
|
_tag: "regex",
|
|
@@ -666,8 +696,8 @@ function parseRegex(src) {
|
|
|
666
696
|
const parseSeq = () => {
|
|
667
697
|
const parts = [];
|
|
668
698
|
while (i < src.length) {
|
|
669
|
-
const
|
|
670
|
-
if (
|
|
699
|
+
const ch2 = src[i];
|
|
700
|
+
if (ch2 === "|" || ch2 === ")") break;
|
|
671
701
|
parts.push(parseQuantified());
|
|
672
702
|
}
|
|
673
703
|
if (parts.length === 0) return EMPTY2;
|
|
@@ -675,18 +705,18 @@ function parseRegex(src) {
|
|
|
675
705
|
};
|
|
676
706
|
const parseQuantified = () => {
|
|
677
707
|
const atom = parseAtom();
|
|
678
|
-
const
|
|
708
|
+
const ch2 = src[i];
|
|
679
709
|
let min = null;
|
|
680
|
-
if (
|
|
710
|
+
if (ch2 === "*") {
|
|
681
711
|
min = 0;
|
|
682
712
|
i++;
|
|
683
|
-
} else if (
|
|
713
|
+
} else if (ch2 === "+") {
|
|
684
714
|
min = 1;
|
|
685
715
|
i++;
|
|
686
|
-
} else if (
|
|
716
|
+
} else if (ch2 === "?") {
|
|
687
717
|
min = 0;
|
|
688
718
|
i++;
|
|
689
|
-
} else if (
|
|
719
|
+
} else if (ch2 === "{") {
|
|
690
720
|
min = parseBraceQuantifier();
|
|
691
721
|
}
|
|
692
722
|
if (min === null) return atom;
|
|
@@ -702,8 +732,8 @@ function parseRegex(src) {
|
|
|
702
732
|
return Number.parseInt(m[1], 10);
|
|
703
733
|
};
|
|
704
734
|
const parseAtom = () => {
|
|
705
|
-
const
|
|
706
|
-
switch (
|
|
735
|
+
const ch2 = src[i];
|
|
736
|
+
switch (ch2) {
|
|
707
737
|
case "(":
|
|
708
738
|
return parseGroup();
|
|
709
739
|
case "[":
|
|
@@ -724,8 +754,8 @@ function parseRegex(src) {
|
|
|
724
754
|
return charNode(fromRange(123, 123), false);
|
|
725
755
|
// a `{` with nothing to quantify is a literal brace
|
|
726
756
|
default:
|
|
727
|
-
i +=
|
|
728
|
-
return charNode(fromRange(
|
|
757
|
+
i += ch2.length;
|
|
758
|
+
return charNode(fromRange(ch2.codePointAt(0), ch2.codePointAt(0)), ch2 === "\n");
|
|
729
759
|
}
|
|
730
760
|
};
|
|
731
761
|
const parseGroup = () => {
|
|
@@ -1165,8 +1195,8 @@ function choice(...args) {
|
|
|
1165
1195
|
}
|
|
1166
1196
|
}
|
|
1167
1197
|
if (idx >= 0) {
|
|
1168
|
-
const
|
|
1169
|
-
if (
|
|
1198
|
+
const gate2 = gates[idx];
|
|
1199
|
+
if (gate2 && !gate2(ctx.state)) {
|
|
1170
1200
|
return { ok: false, expected: deriveExpected(parsers[idx]), span: { start: pos, end: pos } };
|
|
1171
1201
|
}
|
|
1172
1202
|
const result = parsers[idx].parse(input, pos, ctx);
|
|
@@ -1512,11 +1542,11 @@ function tryFastLabeledScan(input, cur, trivia2) {
|
|
|
1512
1542
|
function recordTriviaChunks(ctx, chunks) {
|
|
1513
1543
|
const kinds = ctx.triviaKindLabels;
|
|
1514
1544
|
const mask = ctx._triviaCaptureMask;
|
|
1515
|
-
for (const
|
|
1516
|
-
pushTriviaLogEntry(ctx,
|
|
1545
|
+
for (const ch2 of chunks) {
|
|
1546
|
+
pushTriviaLogEntry(ctx, ch2.start, ch2.end, kinds ? ch2.kindIndex : void 0);
|
|
1517
1547
|
if (ctx.captureTrivia && (ctx._cstBuf !== void 0 || ctx._cstTriviaLog !== void 0)) {
|
|
1518
|
-
if (mask === void 0 || kinds === void 0 || (mask & 1 <<
|
|
1519
|
-
pushCstTriviaEntry(ctx,
|
|
1548
|
+
if (mask === void 0 || kinds === void 0 || (mask & 1 << ch2.kindIndex) !== 0) {
|
|
1549
|
+
pushCstTriviaEntry(ctx, ch2.start, ch2.end, kinds ? ch2.kindIndex : void 0);
|
|
1520
1550
|
}
|
|
1521
1551
|
}
|
|
1522
1552
|
}
|
|
@@ -2292,6 +2322,12 @@ function rules(a, b) {
|
|
|
2292
2322
|
if (rule && !rule._meta.isTrivia) rule._meta.grammarTrivia = options.trivia;
|
|
2293
2323
|
}
|
|
2294
2324
|
}
|
|
2325
|
+
if (options?.scanSkip != null) {
|
|
2326
|
+
for (const key of Object.keys(definitions)) {
|
|
2327
|
+
const rule = cache[key];
|
|
2328
|
+
if (rule && !rule._meta.isTrivia) rule._meta.grammarScanSkip = options.scanSkip;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2295
2331
|
for (const key of Object.keys(definitions)) {
|
|
2296
2332
|
markUnusedValues(cache[key]);
|
|
2297
2333
|
}
|
|
@@ -2774,9 +2810,11 @@ function parse(combinator, input, opts = {}) {
|
|
|
2774
2810
|
const _errors = opts.recover ? [] : void 0;
|
|
2775
2811
|
const _probe = opts.recover ? { offset: input.length, best: null } : void 0;
|
|
2776
2812
|
const grammarTrivia = combinator._meta.grammarTrivia;
|
|
2813
|
+
const grammarScanSkip = combinator._meta.grammarScanSkip;
|
|
2777
2814
|
const ctx = {
|
|
2778
2815
|
trackLines,
|
|
2779
2816
|
...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...grammarTrivia._meta.triviaKindLabels ? { triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {} } : {},
|
|
2817
|
+
...grammarScanSkip !== void 0 ? { scanSkip: grammarScanSkip } : {},
|
|
2780
2818
|
..._errors !== void 0 ? { _errors } : {},
|
|
2781
2819
|
..._probe !== void 0 ? { _probe } : {}
|
|
2782
2820
|
};
|
|
@@ -2885,6 +2923,413 @@ function leaf(root, fn) {
|
|
|
2885
2923
|
};
|
|
2886
2924
|
}
|
|
2887
2925
|
|
|
2926
|
+
// src/version.ts
|
|
2927
|
+
var PARSEMAN_VERSION = "0.32.0";
|
|
2928
|
+
|
|
2929
|
+
// src/analysis/gating.ts
|
|
2930
|
+
var isAny = (fs) => fs.kind === "any";
|
|
2931
|
+
function intersects2(a, b) {
|
|
2932
|
+
if (a.kind === "any" || b.kind === "any") return true;
|
|
2933
|
+
if (a.kind === "empty" || b.kind === "empty") return false;
|
|
2934
|
+
for (const ra of a.ranges) for (const rb of b.ranges) if (ra.lo <= rb.hi && rb.lo <= ra.hi) return true;
|
|
2935
|
+
return false;
|
|
2936
|
+
}
|
|
2937
|
+
function intersection(a, b) {
|
|
2938
|
+
if (a.kind === "any") return b;
|
|
2939
|
+
if (b.kind === "any") return a;
|
|
2940
|
+
if (a.kind === "empty" || b.kind === "empty") return { kind: "empty" };
|
|
2941
|
+
const ranges = [];
|
|
2942
|
+
for (const ra of a.ranges) for (const rb of b.ranges) {
|
|
2943
|
+
const lo = Math.max(ra.lo, rb.lo);
|
|
2944
|
+
const hi = Math.min(ra.hi, rb.hi);
|
|
2945
|
+
if (lo <= hi) ranges.push({ lo, hi });
|
|
2946
|
+
}
|
|
2947
|
+
return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
|
|
2948
|
+
}
|
|
2949
|
+
function combine(sets) {
|
|
2950
|
+
let acc = { kind: "empty" };
|
|
2951
|
+
for (const s of sets) {
|
|
2952
|
+
if (s.kind === "any") return { kind: "any" };
|
|
2953
|
+
if (s.kind === "empty") continue;
|
|
2954
|
+
acc = acc.kind === "ranges" ? { kind: "ranges", ranges: [...acc.ranges, ...s.ranges] } : s;
|
|
2955
|
+
}
|
|
2956
|
+
return acc;
|
|
2957
|
+
}
|
|
2958
|
+
var ch = (c) => c >= 32 && c < 127 ? `'${String.fromCharCode(c)}'` : `\\u${c}`;
|
|
2959
|
+
function firstSetToString(fs) {
|
|
2960
|
+
if (fs.kind === "any") return "ANY";
|
|
2961
|
+
if (fs.kind === "empty") return "(empty)";
|
|
2962
|
+
return fs.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
|
|
2963
|
+
}
|
|
2964
|
+
var ruleNameOf = (p) => p._ruleName;
|
|
2965
|
+
var SUGGESTIONS = {
|
|
2966
|
+
"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.",
|
|
2967
|
+
"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.",
|
|
2968
|
+
"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.",
|
|
2969
|
+
"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.",
|
|
2970
|
+
"opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
|
|
2971
|
+
"ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
|
|
2972
|
+
};
|
|
2973
|
+
function classifyBroadArm(arm) {
|
|
2974
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2975
|
+
const walk2 = (p) => {
|
|
2976
|
+
if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
|
|
2977
|
+
seen.add(p);
|
|
2978
|
+
const d = p._def;
|
|
2979
|
+
switch (d.tag) {
|
|
2980
|
+
case "literal":
|
|
2981
|
+
case "regex":
|
|
2982
|
+
case "keywords":
|
|
2983
|
+
return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
|
|
2984
|
+
case "not":
|
|
2985
|
+
return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
|
|
2986
|
+
case "scanTo":
|
|
2987
|
+
return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
|
|
2988
|
+
// `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
|
|
2989
|
+
// its own — it is the poison. `withCtx`/`recover` FORWARD their inner
|
|
2990
|
+
// first-set (see firstSetOf), so a broad result comes from the inner parser:
|
|
2991
|
+
// walk into it for a precise cause.
|
|
2992
|
+
case "guard":
|
|
2993
|
+
return { cause: "opaque-wrapper", detail: `opaque wrapper (${d.tag})` };
|
|
2994
|
+
// attempt/withCtx/recover FORWARD their inner first-set (transparent for
|
|
2995
|
+
// dispatch), so a broad result comes from the inner parser — walk into it.
|
|
2996
|
+
case "attempt":
|
|
2997
|
+
case "withCtx":
|
|
2998
|
+
case "recover":
|
|
2999
|
+
return walk2(d.parser);
|
|
3000
|
+
case "lazy": {
|
|
3001
|
+
const name = p._ruleName;
|
|
3002
|
+
try {
|
|
3003
|
+
const inner = walk2(d.thunk());
|
|
3004
|
+
if (name !== void 0) return inner ? { cause: "cross-artifact-ref", detail: `via ref g.${name} \u2192 ${inner.detail}` } : null;
|
|
3005
|
+
return inner;
|
|
3006
|
+
} catch {
|
|
3007
|
+
return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}` };
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
case "optional":
|
|
3011
|
+
case "many": {
|
|
3012
|
+
const inner = walk2(d.parser);
|
|
3013
|
+
return { cause: "nullable-prefix", detail: inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term" };
|
|
3014
|
+
}
|
|
3015
|
+
case "oneOrMore":
|
|
3016
|
+
case "transform":
|
|
3017
|
+
case "label":
|
|
3018
|
+
case "field":
|
|
3019
|
+
case "trivia":
|
|
3020
|
+
case "token":
|
|
3021
|
+
case "leaf":
|
|
3022
|
+
case "node":
|
|
3023
|
+
case "grammar":
|
|
3024
|
+
case "expect":
|
|
3025
|
+
return walk2(d.parser);
|
|
3026
|
+
case "skip":
|
|
3027
|
+
return walk2(d.main);
|
|
3028
|
+
case "sequence": {
|
|
3029
|
+
let sawNullablePrefix = false;
|
|
3030
|
+
for (const t of d.parsers) {
|
|
3031
|
+
if (t._def.tag === "not") {
|
|
3032
|
+
sawNullablePrefix = true;
|
|
3033
|
+
continue;
|
|
3034
|
+
}
|
|
3035
|
+
if (isAny(firstSetOf(t))) {
|
|
3036
|
+
const inner = walk2(t);
|
|
3037
|
+
if (sawNullablePrefix)
|
|
3038
|
+
return { cause: "nullable-prefix", detail: inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term" };
|
|
3039
|
+
return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
|
|
3040
|
+
}
|
|
3041
|
+
if (matchesEmpty(t)) {
|
|
3042
|
+
sawNullablePrefix = true;
|
|
3043
|
+
continue;
|
|
3044
|
+
}
|
|
3045
|
+
return null;
|
|
3046
|
+
}
|
|
3047
|
+
return { cause: "broad-recognizer", detail: "sequence of only nullable/zero-width terms" };
|
|
3048
|
+
}
|
|
3049
|
+
case "choice": {
|
|
3050
|
+
for (const a of d.parsers) {
|
|
3051
|
+
const r = walk2(a);
|
|
3052
|
+
if (r) return { cause: r.cause, detail: `choice arm \u2192 ${r.detail}` };
|
|
3053
|
+
}
|
|
3054
|
+
return null;
|
|
3055
|
+
}
|
|
3056
|
+
default:
|
|
3057
|
+
return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
|
|
3058
|
+
}
|
|
3059
|
+
};
|
|
3060
|
+
return walk2(arm) ?? { cause: "broad-recognizer", detail: "broad (cause not localized)" };
|
|
3061
|
+
}
|
|
3062
|
+
function peelToLeading(arm) {
|
|
3063
|
+
let d = arm._def;
|
|
3064
|
+
let cur = arm;
|
|
3065
|
+
for (; ; ) {
|
|
3066
|
+
if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label" || d.tag === "attempt") {
|
|
3067
|
+
cur = d.parser;
|
|
3068
|
+
d = cur._def;
|
|
3069
|
+
continue;
|
|
3070
|
+
}
|
|
3071
|
+
break;
|
|
3072
|
+
}
|
|
3073
|
+
if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
|
|
3074
|
+
return cur;
|
|
3075
|
+
}
|
|
3076
|
+
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
3077
|
+
function detectAntiPatterns(rule, arms) {
|
|
3078
|
+
const out = [];
|
|
3079
|
+
arms.forEach((arm, i) => {
|
|
3080
|
+
const lead = peelToLeading(arm);
|
|
3081
|
+
const ld = lead._def;
|
|
3082
|
+
if (ld.tag === "not") {
|
|
3083
|
+
const inner = ld.parser;
|
|
3084
|
+
if (inner._def.tag === "not") {
|
|
3085
|
+
out.push({
|
|
3086
|
+
kind: "double-not",
|
|
3087
|
+
rule,
|
|
3088
|
+
armIndex: i,
|
|
3089
|
+
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."
|
|
3090
|
+
});
|
|
3091
|
+
} else {
|
|
3092
|
+
out.push({
|
|
3093
|
+
kind: "leading-not",
|
|
3094
|
+
rule,
|
|
3095
|
+
armIndex: i,
|
|
3096
|
+
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."
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
if (ld.tag === "regex" && KEYWORD_REGEX_RE.test(ld.source)) {
|
|
3101
|
+
out.push({
|
|
3102
|
+
kind: "keyword-regex",
|
|
3103
|
+
rule,
|
|
3104
|
+
armIndex: i,
|
|
3105
|
+
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).`
|
|
3106
|
+
});
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3109
|
+
return out;
|
|
3110
|
+
}
|
|
3111
|
+
function analyzeGating(entry, opts) {
|
|
3112
|
+
const raw = [];
|
|
3113
|
+
const antiPatterns = [];
|
|
3114
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3115
|
+
const visit = (p, enclosingRule) => {
|
|
3116
|
+
if (visited.has(p)) return;
|
|
3117
|
+
visited.add(p);
|
|
3118
|
+
const d = p._def;
|
|
3119
|
+
const rule = ruleNameOf(p) ?? enclosingRule;
|
|
3120
|
+
if (d.tag === "choice") {
|
|
3121
|
+
raw.push({ g: analyzeChoice(p, d, rule), rule });
|
|
3122
|
+
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
3123
|
+
}
|
|
3124
|
+
const rec = d;
|
|
3125
|
+
const kids = [];
|
|
3126
|
+
if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
|
|
3127
|
+
for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
|
|
3128
|
+
if (rec[k]) kids.push(rec[k]);
|
|
3129
|
+
if (Array.isArray(rec.skip)) kids.push(...rec.skip);
|
|
3130
|
+
if (d.tag === "lazy") {
|
|
3131
|
+
try {
|
|
3132
|
+
kids.push(d.thunk());
|
|
3133
|
+
} catch {
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
for (const k of kids) visit(k, rule);
|
|
3137
|
+
};
|
|
3138
|
+
visit(entry, ruleNameOf(entry) ?? "<entry>");
|
|
3139
|
+
const perRule = /* @__PURE__ */ new Map();
|
|
3140
|
+
for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
|
|
3141
|
+
const seenInRule = /* @__PURE__ */ new Map();
|
|
3142
|
+
const accept = new Set(opts?.accept ?? []);
|
|
3143
|
+
const usedAccept = /* @__PURE__ */ new Set();
|
|
3144
|
+
const choices = raw.map(({ g, rule }) => {
|
|
3145
|
+
const n = seenInRule.get(rule) ?? 0;
|
|
3146
|
+
seenInRule.set(rule, n + 1);
|
|
3147
|
+
const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
|
|
3148
|
+
const isAccepted = g.gates === "no" && accept.has(id);
|
|
3149
|
+
if (isAccepted) usedAccept.add(id);
|
|
3150
|
+
return { ...g, id, accepted: isAccepted };
|
|
3151
|
+
});
|
|
3152
|
+
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
3153
|
+
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
3154
|
+
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted);
|
|
3155
|
+
const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
|
|
3156
|
+
const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
|
|
3157
|
+
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, acceptedUnused, choices, antiPatterns };
|
|
3158
|
+
}
|
|
3159
|
+
function analyzeChoice(p, d, rule) {
|
|
3160
|
+
const arms = d.parsers;
|
|
3161
|
+
const shallow = arms.map((a) => a._meta.firstSet);
|
|
3162
|
+
const deep = arms.map((a) => firstSetOf(a));
|
|
3163
|
+
const anyArms = deep.map((fs, index) => ({ fs, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
|
|
3164
|
+
const { cause, detail } = classifyBroadArm(arms[index]);
|
|
3165
|
+
return { index, cause, detail, shallowAnyOnly: false, suggestion: SUGGESTIONS[cause] };
|
|
3166
|
+
});
|
|
3167
|
+
const overlaps = [];
|
|
3168
|
+
for (let i = 0; i < deep.length; i++)
|
|
3169
|
+
for (let j = i + 1; j < deep.length; j++)
|
|
3170
|
+
if (!isAny(deep[i]) && !isAny(deep[j]) && intersects2(deep[i], deep[j]))
|
|
3171
|
+
overlaps.push({
|
|
3172
|
+
a: i,
|
|
3173
|
+
b: j,
|
|
3174
|
+
on: intersection(deep[i], deep[j]),
|
|
3175
|
+
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."
|
|
3176
|
+
});
|
|
3177
|
+
let gates;
|
|
3178
|
+
if (d.disjoint) gates = "yes";
|
|
3179
|
+
else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
|
|
3180
|
+
else gates = "no";
|
|
3181
|
+
return {
|
|
3182
|
+
rule,
|
|
3183
|
+
strategy: d.strategy?.tag ?? "firstMatch",
|
|
3184
|
+
gates,
|
|
3185
|
+
combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
|
|
3186
|
+
anyArms,
|
|
3187
|
+
overlaps
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3190
|
+
function formatGatingWarnings(report) {
|
|
3191
|
+
const lines = [];
|
|
3192
|
+
for (const c of report.ungated) {
|
|
3193
|
+
lines.push(`parseman gating: choice @ ${c.id} is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms.`);
|
|
3194
|
+
for (const a of c.anyArms)
|
|
3195
|
+
lines.push(` \xB7 arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
|
|
3196
|
+
fix: ${a.suggestion}`);
|
|
3197
|
+
for (const o of c.overlaps)
|
|
3198
|
+
lines.push(` \xB7 arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
|
|
3199
|
+
fix: ${o.suggestion}`);
|
|
3200
|
+
lines.push(` (intentional? accept it in the gating snapshot: { accept: ['${c.id}'] }.)`);
|
|
3201
|
+
}
|
|
3202
|
+
for (const ap of report.antiPatterns)
|
|
3203
|
+
lines.push(`parseman anti-pattern [${ap.kind}] @ ${ap.rule} arm[${ap.armIndex}]: ${ap.message}`);
|
|
3204
|
+
return lines;
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
// src/combinators/scanTo.ts
|
|
3208
|
+
function resolveScanSkip(explicitSkip, raw, ctx) {
|
|
3209
|
+
if (raw) return [];
|
|
3210
|
+
const trivia2 = ctx.trivia;
|
|
3211
|
+
const ambient = ctx.scanSkip;
|
|
3212
|
+
if (!trivia2 && !ambient) return explicitSkip;
|
|
3213
|
+
const out = [];
|
|
3214
|
+
if (trivia2) out.push(trivia2);
|
|
3215
|
+
if (ambient) out.push(...ambient);
|
|
3216
|
+
out.push(...explicitSkip);
|
|
3217
|
+
return out;
|
|
3218
|
+
}
|
|
3219
|
+
function scanTo(sentinel, { skip: skip2, raw = false, orEOF = false } = {}) {
|
|
3220
|
+
const meta = {
|
|
3221
|
+
firstSet: any(),
|
|
3222
|
+
canMatchNewline: true,
|
|
3223
|
+
isTrivia: false
|
|
3224
|
+
};
|
|
3225
|
+
const explicitSkip = skip2 ?? [];
|
|
3226
|
+
return {
|
|
3227
|
+
_tag: "scanTo",
|
|
3228
|
+
_meta: meta,
|
|
3229
|
+
_def: { tag: "scanTo", sentinel, skip: explicitSkip, raw, orEOF },
|
|
3230
|
+
parse(input, pos, ctx) {
|
|
3231
|
+
let cur = pos;
|
|
3232
|
+
const skip3 = resolveScanSkip(explicitSkip, raw, ctx);
|
|
3233
|
+
const probeCtx = {
|
|
3234
|
+
trackLines: ctx.trackLines,
|
|
3235
|
+
state: ctx.state,
|
|
3236
|
+
...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
|
|
3237
|
+
};
|
|
3238
|
+
const emit2 = (end) => {
|
|
3239
|
+
if (end > pos && cstCaptureActive(ctx)) {
|
|
3240
|
+
const leaf2 = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
|
|
3241
|
+
pushCstLeaf(ctx, leaf2);
|
|
3242
|
+
}
|
|
3243
|
+
};
|
|
3244
|
+
while (cur < input.length) {
|
|
3245
|
+
const s = sentinel.parse(input, cur, probeCtx);
|
|
3246
|
+
if (s.ok) {
|
|
3247
|
+
emit2(cur);
|
|
3248
|
+
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
3249
|
+
}
|
|
3250
|
+
let advanced = false;
|
|
3251
|
+
for (const skipper of skip3) {
|
|
3252
|
+
const r = skipper.parse(input, cur, probeCtx);
|
|
3253
|
+
if (r.ok && r.span.end > cur) {
|
|
3254
|
+
cur = r.span.end;
|
|
3255
|
+
advanced = true;
|
|
3256
|
+
break;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
if (!advanced) cur++;
|
|
3260
|
+
}
|
|
3261
|
+
if (orEOF) {
|
|
3262
|
+
emit2(cur);
|
|
3263
|
+
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
3264
|
+
}
|
|
3265
|
+
const sentDef = sentinel._def;
|
|
3266
|
+
const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
|
|
3267
|
+
return { ok: false, expected, span: { start: pos, end: cur } };
|
|
3268
|
+
}
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
function balanced(open, close, options = {}) {
|
|
3272
|
+
const ownSkip = options.skip ?? [];
|
|
3273
|
+
const combi = buildBalancedInterior(open, close, ownSkip);
|
|
3274
|
+
if (options.raw) return combi;
|
|
3275
|
+
const eagerParse = combi.parse.bind(combi);
|
|
3276
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3277
|
+
combi._balancedAmbient = { open, close, ownSkip };
|
|
3278
|
+
combi.parse = (input, pos, ctx) => {
|
|
3279
|
+
const amb = ctx.scanSkip;
|
|
3280
|
+
if (!amb || amb.length === 0) return eagerParse(input, pos, ctx);
|
|
3281
|
+
let interior = cache.get(amb);
|
|
3282
|
+
if (!interior) {
|
|
3283
|
+
interior = buildBalancedInterior(open, close, [...amb, ...ownSkip]);
|
|
3284
|
+
cache.set(amb, interior);
|
|
3285
|
+
}
|
|
3286
|
+
return interior.parse(input, pos, ctx);
|
|
3287
|
+
};
|
|
3288
|
+
return combi;
|
|
3289
|
+
}
|
|
3290
|
+
function buildBalancedInterior(open, close, skips) {
|
|
3291
|
+
const self = ref();
|
|
3292
|
+
const stop = /* @__PURE__ */ new Set([open, close]);
|
|
3293
|
+
let bounded = true;
|
|
3294
|
+
for (const sk of skips) {
|
|
3295
|
+
const cs = firstSetClassChars(sk);
|
|
3296
|
+
if (cs === null) {
|
|
3297
|
+
bounded = false;
|
|
3298
|
+
break;
|
|
3299
|
+
}
|
|
3300
|
+
for (const ch2 of cs) stop.add(ch2);
|
|
3301
|
+
}
|
|
3302
|
+
const cls = [...stop].map(escapeClassChar).join("");
|
|
3303
|
+
const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
|
|
3304
|
+
const inner = many(choice(self, ...skips, content));
|
|
3305
|
+
const combi = transform(
|
|
3306
|
+
sequence(literal(open), inner, expect(literal(close))),
|
|
3307
|
+
// parts: strings (content/self) or arrays (a sequence-shaped skip) or a
|
|
3308
|
+
// ParseError (recovered close). `c` is the close string or a ParseError.
|
|
3309
|
+
([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
|
|
3310
|
+
);
|
|
3311
|
+
if (combi._def.tag === "transform") {
|
|
3312
|
+
combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
|
|
3313
|
+
combi._def.recognitionOnly = true;
|
|
3314
|
+
}
|
|
3315
|
+
self.define(combi);
|
|
3316
|
+
return combi;
|
|
3317
|
+
}
|
|
3318
|
+
function firstSetClassChars(c) {
|
|
3319
|
+
const fs = c._meta.firstSet;
|
|
3320
|
+
if (fs.kind === "empty") return [];
|
|
3321
|
+
if (fs.kind !== "ranges") return null;
|
|
3322
|
+
const out = [];
|
|
3323
|
+
for (const { lo, hi } of fs.ranges) {
|
|
3324
|
+
if (hi - lo > 8) return null;
|
|
3325
|
+
for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
|
|
3326
|
+
}
|
|
3327
|
+
return out;
|
|
3328
|
+
}
|
|
3329
|
+
function escapeClassChar(ch2) {
|
|
3330
|
+
return ch2.replace(/[\\\]^-]/g, "\\$&");
|
|
3331
|
+
}
|
|
3332
|
+
|
|
2888
3333
|
// src/compiler/grammar-coverage-ids.ts
|
|
2889
3334
|
function children(def, winners) {
|
|
2890
3335
|
switch (def.tag) {
|
|
@@ -2997,16 +3442,16 @@ function literalCodePoints(frag) {
|
|
|
2997
3442
|
const out = [];
|
|
2998
3443
|
let i = 0;
|
|
2999
3444
|
while (i < frag.length) {
|
|
3000
|
-
const
|
|
3001
|
-
if (
|
|
3445
|
+
const ch2 = frag[i];
|
|
3446
|
+
if (ch2 === "\\") {
|
|
3002
3447
|
const e = frag[i + 1];
|
|
3003
3448
|
if (e === void 0) return null;
|
|
3004
3449
|
out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
|
|
3005
3450
|
i += 2;
|
|
3006
3451
|
continue;
|
|
3007
3452
|
}
|
|
3008
|
-
if (META.has(
|
|
3009
|
-
out.push(
|
|
3453
|
+
if (META.has(ch2)) return null;
|
|
3454
|
+
out.push(ch2.codePointAt(0));
|
|
3010
3455
|
i += 1;
|
|
3011
3456
|
}
|
|
3012
3457
|
return out.length ? out : null;
|
|
@@ -3085,8 +3530,8 @@ function parseSeqParts(source) {
|
|
|
3085
3530
|
};
|
|
3086
3531
|
let i = 0;
|
|
3087
3532
|
while (i < source.length) {
|
|
3088
|
-
const
|
|
3089
|
-
if (
|
|
3533
|
+
const ch2 = source[i];
|
|
3534
|
+
if (ch2 === "[") {
|
|
3090
3535
|
let k = i + 1;
|
|
3091
3536
|
const neg = source[k] === "^";
|
|
3092
3537
|
if (neg) k++;
|
|
@@ -3108,7 +3553,7 @@ function parseSeqParts(source) {
|
|
|
3108
3553
|
i += runFrom(ranges, neg, i);
|
|
3109
3554
|
continue;
|
|
3110
3555
|
}
|
|
3111
|
-
if (
|
|
3556
|
+
if (ch2 === "\\") {
|
|
3112
3557
|
const e = source[i + 1];
|
|
3113
3558
|
if (e === "d" || e === "w" || e === "s") {
|
|
3114
3559
|
flush();
|
|
@@ -3137,7 +3582,7 @@ function parseSeqParts(source) {
|
|
|
3137
3582
|
} else lit.push(cp2);
|
|
3138
3583
|
continue;
|
|
3139
3584
|
}
|
|
3140
|
-
if (
|
|
3585
|
+
if (ch2 === "(") {
|
|
3141
3586
|
if (source[i + 1] !== "?" || source[i + 2] !== ":") return null;
|
|
3142
3587
|
let k = i + 3;
|
|
3143
3588
|
let depth = 1;
|
|
@@ -3186,9 +3631,9 @@ function parseSeqParts(source) {
|
|
|
3186
3631
|
}
|
|
3187
3632
|
continue;
|
|
3188
3633
|
}
|
|
3189
|
-
if (META.has(
|
|
3190
|
-
const cp =
|
|
3191
|
-
i +=
|
|
3634
|
+
if (META.has(ch2)) return null;
|
|
3635
|
+
const cp = ch2.codePointAt(0);
|
|
3636
|
+
i += ch2.length;
|
|
3192
3637
|
if (source[i] === "?") {
|
|
3193
3638
|
flush();
|
|
3194
3639
|
parts.push({ part: "lit", cps: [cp], optional: true });
|
|
@@ -3365,12 +3810,12 @@ function unwrapOuterGroup(source) {
|
|
|
3365
3810
|
let i = 3;
|
|
3366
3811
|
let ok = true;
|
|
3367
3812
|
while (i < source.length && depth > 0) {
|
|
3368
|
-
const
|
|
3369
|
-
if (
|
|
3813
|
+
const ch2 = source[i];
|
|
3814
|
+
if (ch2 === "\\") {
|
|
3370
3815
|
i += 2;
|
|
3371
3816
|
continue;
|
|
3372
3817
|
}
|
|
3373
|
-
if (
|
|
3818
|
+
if (ch2 === "[") {
|
|
3374
3819
|
let j = i + 1;
|
|
3375
3820
|
if (source[j] === "^") j++;
|
|
3376
3821
|
while (j < source.length && source[j] !== "]") {
|
|
@@ -3384,8 +3829,8 @@ function unwrapOuterGroup(source) {
|
|
|
3384
3829
|
i = j + 1;
|
|
3385
3830
|
continue;
|
|
3386
3831
|
}
|
|
3387
|
-
if (
|
|
3388
|
-
else if (
|
|
3832
|
+
if (ch2 === "(") depth++;
|
|
3833
|
+
else if (ch2 === ")") depth--;
|
|
3389
3834
|
i++;
|
|
3390
3835
|
}
|
|
3391
3836
|
if (!ok || depth !== 0 || i !== source.length) break;
|
|
@@ -3400,12 +3845,12 @@ function splitTopLevelAlternation(source) {
|
|
|
3400
3845
|
let last = 0;
|
|
3401
3846
|
let i = 0;
|
|
3402
3847
|
while (i < body.length) {
|
|
3403
|
-
const
|
|
3404
|
-
if (
|
|
3848
|
+
const ch2 = body[i];
|
|
3849
|
+
if (ch2 === "\\") {
|
|
3405
3850
|
i += 2;
|
|
3406
3851
|
continue;
|
|
3407
3852
|
}
|
|
3408
|
-
if (
|
|
3853
|
+
if (ch2 === "[") {
|
|
3409
3854
|
let j = i + 1;
|
|
3410
3855
|
if (body[j] === "^") j++;
|
|
3411
3856
|
while (j < body.length && body[j] !== "]") {
|
|
@@ -3416,18 +3861,18 @@ function splitTopLevelAlternation(source) {
|
|
|
3416
3861
|
i = j + 1;
|
|
3417
3862
|
continue;
|
|
3418
3863
|
}
|
|
3419
|
-
if (
|
|
3864
|
+
if (ch2 === "(") {
|
|
3420
3865
|
depth++;
|
|
3421
3866
|
i++;
|
|
3422
3867
|
continue;
|
|
3423
3868
|
}
|
|
3424
|
-
if (
|
|
3869
|
+
if (ch2 === ")") {
|
|
3425
3870
|
depth--;
|
|
3426
3871
|
if (depth < 0) return null;
|
|
3427
3872
|
i++;
|
|
3428
3873
|
continue;
|
|
3429
3874
|
}
|
|
3430
|
-
if (
|
|
3875
|
+
if (ch2 === "|" && depth === 0) {
|
|
3431
3876
|
parts.push(body.slice(last, i));
|
|
3432
3877
|
last = i + 1;
|
|
3433
3878
|
i++;
|
|
@@ -3546,12 +3991,12 @@ function splitDelimArms(body) {
|
|
|
3546
3991
|
let last = 0;
|
|
3547
3992
|
let i = 0;
|
|
3548
3993
|
while (i < body.length) {
|
|
3549
|
-
const
|
|
3550
|
-
if (
|
|
3994
|
+
const ch2 = body[i];
|
|
3995
|
+
if (ch2 === "\\") {
|
|
3551
3996
|
i += 2;
|
|
3552
3997
|
continue;
|
|
3553
3998
|
}
|
|
3554
|
-
if (
|
|
3999
|
+
if (ch2 === "[") {
|
|
3555
4000
|
let j = i + 1;
|
|
3556
4001
|
if (body[j] === "^") j++;
|
|
3557
4002
|
while (j < body.length && body[j] !== "]") {
|
|
@@ -3562,18 +4007,18 @@ function splitDelimArms(body) {
|
|
|
3562
4007
|
i = j + 1;
|
|
3563
4008
|
continue;
|
|
3564
4009
|
}
|
|
3565
|
-
if (
|
|
4010
|
+
if (ch2 === "(") {
|
|
3566
4011
|
depth++;
|
|
3567
4012
|
i++;
|
|
3568
4013
|
continue;
|
|
3569
4014
|
}
|
|
3570
|
-
if (
|
|
4015
|
+
if (ch2 === ")") {
|
|
3571
4016
|
depth--;
|
|
3572
4017
|
if (depth < 0) return null;
|
|
3573
4018
|
i++;
|
|
3574
4019
|
continue;
|
|
3575
4020
|
}
|
|
3576
|
-
if (
|
|
4021
|
+
if (ch2 === "|" && depth === 0) {
|
|
3577
4022
|
arms.push(body.slice(last, i));
|
|
3578
4023
|
last = i + 1;
|
|
3579
4024
|
}
|
|
@@ -3625,12 +4070,12 @@ function parseDelimited(source) {
|
|
|
3625
4070
|
let k = i + 3;
|
|
3626
4071
|
let depth = 1;
|
|
3627
4072
|
while (k < source.length && depth > 0) {
|
|
3628
|
-
const
|
|
3629
|
-
if (
|
|
4073
|
+
const ch2 = source[k];
|
|
4074
|
+
if (ch2 === "\\") {
|
|
3630
4075
|
k += 2;
|
|
3631
4076
|
continue;
|
|
3632
4077
|
}
|
|
3633
|
-
if (
|
|
4078
|
+
if (ch2 === "[") {
|
|
3634
4079
|
let j = k + 1;
|
|
3635
4080
|
if (source[j] === "^") j++;
|
|
3636
4081
|
while (j < source.length && source[j] !== "]") {
|
|
@@ -3641,8 +4086,8 @@ function parseDelimited(source) {
|
|
|
3641
4086
|
k = j + 1;
|
|
3642
4087
|
continue;
|
|
3643
4088
|
}
|
|
3644
|
-
if (
|
|
3645
|
-
else if (
|
|
4089
|
+
if (ch2 === "(") depth++;
|
|
4090
|
+
else if (ch2 === ")") depth--;
|
|
3646
4091
|
k++;
|
|
3647
4092
|
}
|
|
3648
4093
|
if (depth !== 0) return null;
|
|
@@ -4314,58 +4759,88 @@ function tryInlineDestructureTransform(src, valueVars) {
|
|
|
4314
4759
|
|
|
4315
4760
|
// src/compiler/codegen.ts
|
|
4316
4761
|
function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
|
|
4317
|
-
if (seen.has(p)) return { concrete: empty(), refs: [] };
|
|
4318
|
-
seen.add(p);
|
|
4319
4762
|
const d = p._def;
|
|
4763
|
+
if (d.tag === "lazy") {
|
|
4764
|
+
const name = p._ruleName;
|
|
4765
|
+
if (name !== void 0) return { alts: [[{ set: empty(), nullable: false, ref: name }]] };
|
|
4766
|
+
}
|
|
4767
|
+
if (seen.has(p)) return { alts: [[]] };
|
|
4768
|
+
seen.add(p);
|
|
4320
4769
|
const rec = (c) => leadingFirstSetRecipe(c, seen);
|
|
4321
|
-
const
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4770
|
+
const seg = (set, nullable2) => ({ set, nullable: nullable2 });
|
|
4771
|
+
const forceNullable = (r) => ({ alts: r.alts.map((alt) => alt.map((s) => ({ ...s, nullable: true }))) });
|
|
4772
|
+
const collapse = (r, nullable2) => {
|
|
4773
|
+
let set = empty();
|
|
4774
|
+
let hasRef = false;
|
|
4775
|
+
for (const alt of r.alts) {
|
|
4776
|
+
for (const s of alt) {
|
|
4777
|
+
if (s.ref !== void 0) hasRef = true;
|
|
4778
|
+
set = union(set, s.set);
|
|
4779
|
+
if (!s.nullable) break;
|
|
4330
4780
|
}
|
|
4331
4781
|
}
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
let out = { concrete: empty(), refs: [] };
|
|
4343
|
-
for (const term of d.parsers) {
|
|
4344
|
-
out = merge2(out, rec(term));
|
|
4345
|
-
if (!matchesEmpty(term)) return out;
|
|
4782
|
+
return seg(hasRef ? any() : set, nullable2);
|
|
4783
|
+
};
|
|
4784
|
+
const compute = () => {
|
|
4785
|
+
switch (d.tag) {
|
|
4786
|
+
case "lazy": {
|
|
4787
|
+
try {
|
|
4788
|
+
return rec(d.thunk());
|
|
4789
|
+
} catch {
|
|
4790
|
+
return { alts: [[seg(any(), false)]] };
|
|
4791
|
+
}
|
|
4346
4792
|
}
|
|
4347
|
-
|
|
4793
|
+
case "literal":
|
|
4794
|
+
case "regex":
|
|
4795
|
+
case "keywords":
|
|
4796
|
+
return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
|
|
4797
|
+
case "choice": {
|
|
4798
|
+
const alts = [];
|
|
4799
|
+
for (const arm of d.parsers) alts.push(...rec(arm).alts);
|
|
4800
|
+
return { alts };
|
|
4801
|
+
}
|
|
4802
|
+
case "sequence": {
|
|
4803
|
+
const chain = [];
|
|
4804
|
+
for (const term of d.parsers) {
|
|
4805
|
+
if (!isZeroWidthAssertion(term)) {
|
|
4806
|
+
const tr = rec(term);
|
|
4807
|
+
if (tr.alts.length === 1) chain.push(...tr.alts[0]);
|
|
4808
|
+
else chain.push(collapse(tr, matchesEmpty(term)));
|
|
4809
|
+
}
|
|
4810
|
+
if (!matchesEmpty(term)) return { alts: [chain] };
|
|
4811
|
+
}
|
|
4812
|
+
return { alts: [chain] };
|
|
4813
|
+
}
|
|
4814
|
+
// `many`/`optional` are skippable as a whole → their leading chars start the
|
|
4815
|
+
// sequence but the chain must continue past them (force nullable). `oneOrMore`
|
|
4816
|
+
// requires at least one repetition, so it is NOT made nullable here.
|
|
4817
|
+
case "many":
|
|
4818
|
+
case "optional":
|
|
4819
|
+
return forceNullable(rec(d.parser));
|
|
4820
|
+
case "oneOrMore":
|
|
4821
|
+
case "transform":
|
|
4822
|
+
case "label":
|
|
4823
|
+
case "field":
|
|
4824
|
+
case "trivia":
|
|
4825
|
+
case "token":
|
|
4826
|
+
case "leaf":
|
|
4827
|
+
case "node":
|
|
4828
|
+
case "grammar":
|
|
4829
|
+
case "expect":
|
|
4830
|
+
return rec(d.parser);
|
|
4831
|
+
case "sepBy":
|
|
4832
|
+
return rec(d.parser);
|
|
4833
|
+
case "skip":
|
|
4834
|
+
return rec(d.main);
|
|
4835
|
+
// not / scanTo / guard / withCtx / recover / unknown → shallow set (safe).
|
|
4836
|
+
default:
|
|
4837
|
+
return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
|
|
4348
4838
|
}
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
case "field":
|
|
4355
|
-
case "trivia":
|
|
4356
|
-
case "token":
|
|
4357
|
-
case "leaf":
|
|
4358
|
-
case "node":
|
|
4359
|
-
case "grammar":
|
|
4360
|
-
case "expect":
|
|
4361
|
-
return rec(d.parser);
|
|
4362
|
-
case "sepBy":
|
|
4363
|
-
return rec(d.parser);
|
|
4364
|
-
case "skip":
|
|
4365
|
-
return rec(d.main);
|
|
4366
|
-
// not / scanTo / guard / withCtx / recover / unknown → shallow set (safe).
|
|
4367
|
-
default:
|
|
4368
|
-
return { concrete: p._meta.firstSet, refs: [] };
|
|
4839
|
+
};
|
|
4840
|
+
try {
|
|
4841
|
+
return compute();
|
|
4842
|
+
} finally {
|
|
4843
|
+
seen.delete(p);
|
|
4369
4844
|
}
|
|
4370
4845
|
}
|
|
4371
4846
|
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 }";
|
|
@@ -4561,16 +5036,19 @@ function ensureTriviaFn(ctx) {
|
|
|
4561
5036
|
const savedIndent = ctx.indent;
|
|
4562
5037
|
const savedFailLabel = ctx.failLabel;
|
|
4563
5038
|
const savedTrivia = ctx.activeTrivia;
|
|
5039
|
+
const savedScanSkip = ctx.activeScanSkip;
|
|
4564
5040
|
const savedCapAsTrivia = ctx.capAsTrivia;
|
|
4565
5041
|
ctx.indent = 2;
|
|
4566
5042
|
ctx.failLabel = "_triv";
|
|
4567
5043
|
ctx.capAsTrivia = true;
|
|
4568
5044
|
ctx.activeTrivia = void 0;
|
|
5045
|
+
ctx.activeScanSkip = void 0;
|
|
4569
5046
|
const r = emit(trivia2, ctx, "_pos");
|
|
4570
5047
|
ctx.indent = savedIndent;
|
|
4571
5048
|
ctx.failLabel = savedFailLabel;
|
|
4572
5049
|
ctx.capAsTrivia = savedCapAsTrivia;
|
|
4573
5050
|
ctx.activeTrivia = savedTrivia;
|
|
5051
|
+
ctx.activeScanSkip = savedScanSkip;
|
|
4574
5052
|
ctx.namedFnDecls.push([
|
|
4575
5053
|
`function ${fnName}(input, _pos, _ctx, _cap) {`,
|
|
4576
5054
|
` let _e = _pos`,
|
|
@@ -4848,7 +5326,7 @@ function emitKeywordsFast(def, ctx, pos) {
|
|
|
4848
5326
|
const bodyInd = ind(ctx) + " ";
|
|
4849
5327
|
const tries = [];
|
|
4850
5328
|
for (const w of def.words) {
|
|
4851
|
-
const cps = Array.from(w, (
|
|
5329
|
+
const cps = Array.from(w, (ch2) => ch2.codePointAt(0));
|
|
4852
5330
|
let shape = def.caseInsensitive ? { kind: "litFold", open: cps } : { kind: "seq", parts: [{ part: "lit", cps, optional: false }] };
|
|
4853
5331
|
if (boundary) {
|
|
4854
5332
|
shape = { kind: "lookahead", inner: shape, ranges: boundary.ranges, classNegated: boundary.negated, negative: true };
|
|
@@ -5041,7 +5519,7 @@ function canMatchEmptyAtStart(p) {
|
|
|
5041
5519
|
const d = p._def;
|
|
5042
5520
|
switch (d.tag) {
|
|
5043
5521
|
case "regex":
|
|
5044
|
-
return
|
|
5522
|
+
return regexMatchesEmpty(d.source, d.flags);
|
|
5045
5523
|
case "optional":
|
|
5046
5524
|
return true;
|
|
5047
5525
|
case "many":
|
|
@@ -5093,10 +5571,10 @@ function emitDisjointArm(p, ctx, pos, valV, endV, coverageId) {
|
|
|
5093
5571
|
}
|
|
5094
5572
|
function emitDisjointArmGated(def, i, ctx, pos, valV, endV, coverageId) {
|
|
5095
5573
|
const p = def.parsers[i];
|
|
5096
|
-
const
|
|
5097
|
-
if (!
|
|
5574
|
+
const gate2 = def.gates[i];
|
|
5575
|
+
if (!gate2) return emitDisjointArm(p, ctx, pos, valV, endV, coverageId);
|
|
5098
5576
|
const gateIdx = ctx.mapFns.length;
|
|
5099
|
-
ctx.mapFns.push(
|
|
5577
|
+
ctx.mapFns.push(gate2);
|
|
5100
5578
|
ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
|
|
5101
5579
|
const gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
|
|
5102
5580
|
const stmts = [`${ind(ctx)}if (${gateCond}) {`];
|
|
@@ -5254,7 +5732,7 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
|
|
|
5254
5732
|
if (preStmts) stmts.push(...preStmts);
|
|
5255
5733
|
for (let i = 0; i < def.parsers.length; i++) {
|
|
5256
5734
|
const p = def.parsers[i];
|
|
5257
|
-
const
|
|
5735
|
+
const gate2 = def.gates[i];
|
|
5258
5736
|
const autoNot = def.autoNot[i];
|
|
5259
5737
|
const atStart = failsAtStart(p);
|
|
5260
5738
|
const staticFx = armStaticExpected(ctx, p);
|
|
@@ -5266,9 +5744,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
|
|
|
5266
5744
|
})();
|
|
5267
5745
|
const fsGuard = armGuardPrefix === void 0 ? baseFsGuard : baseFsGuard === null ? armGuardPrefix : `${armGuardPrefix} && (${baseFsGuard})`;
|
|
5268
5746
|
let gateCond = null;
|
|
5269
|
-
if (
|
|
5747
|
+
if (gate2) {
|
|
5270
5748
|
const gateIdx = ctx.mapFns.length;
|
|
5271
|
-
ctx.mapFns.push(
|
|
5749
|
+
ctx.mapFns.push(gate2);
|
|
5272
5750
|
ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
|
|
5273
5751
|
gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
|
|
5274
5752
|
}
|
|
@@ -5685,10 +6163,15 @@ function emitScanTo(def, ctx, pos) {
|
|
|
5685
6163
|
const probeCtx = { ...ctx, capturing: false, noHoist: true };
|
|
5686
6164
|
const { stmts: sentStmts, okVar: sentOk } = emitFallible(def.sentinel, probeCtx, curV);
|
|
5687
6165
|
stmts.push(...sentStmts, `${ind(ctx)}if (${sentOk}) { ${foundV} = true; break }`);
|
|
5688
|
-
|
|
6166
|
+
const effectiveSkip = def.raw ? def.skip : [
|
|
6167
|
+
...ctx.activeTrivia ? [ctx.activeTrivia] : [],
|
|
6168
|
+
...ctx.activeScanSkip ?? [],
|
|
6169
|
+
...def.skip
|
|
6170
|
+
];
|
|
6171
|
+
if (effectiveSkip.length > 0) {
|
|
5689
6172
|
const advV = v(ctx, "_stadv");
|
|
5690
6173
|
stmts.push(`${ind(ctx)}let ${advV} = false`);
|
|
5691
|
-
for (const skipper of
|
|
6174
|
+
for (const skipper of effectiveSkip) {
|
|
5692
6175
|
const { stmts: skStmts, okVar: skOk, endVar: skEnd } = emitFallible(skipper, probeCtx, curV);
|
|
5693
6176
|
stmts.push(
|
|
5694
6177
|
`${ind(ctx)}if (!${advV}) {`,
|
|
@@ -6158,6 +6641,24 @@ function emit(p, ctx, pos) {
|
|
|
6158
6641
|
return instrument(dispatch());
|
|
6159
6642
|
}
|
|
6160
6643
|
function emitDispatch(p, ctx, pos) {
|
|
6644
|
+
const bal = p._balancedAmbient;
|
|
6645
|
+
if (bal && ctx.activeScanSkip && ctx.activeScanSkip.length > 0 && !ctx.balancedRebuildStack?.has(p)) {
|
|
6646
|
+
const interior = buildBalancedInterior(bal.open, bal.close, [...ctx.activeScanSkip, ...bal.ownSkip]);
|
|
6647
|
+
markUnusedValues(interior);
|
|
6648
|
+
if (ctx.lazyUsage) {
|
|
6649
|
+
const u = analyzeLazyUsage(interior);
|
|
6650
|
+
for (const [k, n] of u.counts) ctx.lazyUsage.counts.set(k, (ctx.lazyUsage.counts.get(k) ?? 0) + n);
|
|
6651
|
+
for (const k of u.recursive) ctx.lazyUsage.recursive.add(k);
|
|
6652
|
+
for (const [k, s] of u.sizes) ctx.lazyUsage.sizes.set(k, s);
|
|
6653
|
+
}
|
|
6654
|
+
const stack = ctx.balancedRebuildStack ?? (ctx.balancedRebuildStack = /* @__PURE__ */ new Set());
|
|
6655
|
+
stack.add(p);
|
|
6656
|
+
try {
|
|
6657
|
+
return emit(interior, ctx, pos);
|
|
6658
|
+
} finally {
|
|
6659
|
+
stack.delete(p);
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6161
6662
|
const def = p._def;
|
|
6162
6663
|
switch (def.tag) {
|
|
6163
6664
|
case "literal":
|
|
@@ -6579,9 +7080,36 @@ function ruleDependencies(ruleMap) {
|
|
|
6579
7080
|
}
|
|
6580
7081
|
return deps;
|
|
6581
7082
|
}
|
|
7083
|
+
function resolveGatingLevel(opt) {
|
|
7084
|
+
const explicit = typeof opt === "string" ? opt : opt?.level;
|
|
7085
|
+
if (explicit !== void 0) return explicit;
|
|
7086
|
+
const env = typeof process !== "undefined" ? process.env?.PARSEMAN_GATING : void 0;
|
|
7087
|
+
if (env === "off" || env === "warn" || env === "error") return env;
|
|
7088
|
+
return "warn";
|
|
7089
|
+
}
|
|
7090
|
+
function runGatingDiagnostic(combinator, opt) {
|
|
7091
|
+
const level = resolveGatingLevel(opt);
|
|
7092
|
+
if (level === "off") return void 0;
|
|
7093
|
+
const accept = opt !== null && typeof opt === "object" ? opt.accept : void 0;
|
|
7094
|
+
let report;
|
|
7095
|
+
try {
|
|
7096
|
+
report = analyzeGating(combinator, accept ? { accept } : void 0);
|
|
7097
|
+
} catch {
|
|
7098
|
+
return void 0;
|
|
7099
|
+
}
|
|
7100
|
+
const lines = formatGatingWarnings(report);
|
|
7101
|
+
if (lines.length > 0) {
|
|
7102
|
+
if (level === "error") throw new Error(`parseman: ${report.ungated.length} ungated hot choice(s) / ${report.antiPatterns.length} anti-pattern(s)
|
|
7103
|
+
${lines.join("\n")}`);
|
|
7104
|
+
for (const l of lines) console.warn(l);
|
|
7105
|
+
}
|
|
7106
|
+
return report;
|
|
7107
|
+
}
|
|
6582
7108
|
function compile(combinator, mapFnSources, opts) {
|
|
7109
|
+
const gatingReport = runGatingDiagnostic(combinator, opts?.gating);
|
|
6583
7110
|
markUnusedValues(combinator);
|
|
6584
7111
|
const grammarTrivia = combinator._meta.grammarTrivia;
|
|
7112
|
+
const grammarScanSkip = combinator._meta.grammarScanSkip;
|
|
6585
7113
|
const ctx = {
|
|
6586
7114
|
vars: 0,
|
|
6587
7115
|
indent: 1,
|
|
@@ -6603,7 +7131,8 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
6603
7131
|
recovery: opts?.recovery ?? false,
|
|
6604
7132
|
...opts?.coverage ? { coverage: { plan: buildGrammarPlan(combinator), entry: combinator } } : {},
|
|
6605
7133
|
lazyUsage: analyzeLazyUsage(combinator),
|
|
6606
|
-
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
|
|
7134
|
+
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {},
|
|
7135
|
+
...grammarScanSkip ? { activeScanSkip: grammarScanSkip } : {}
|
|
6607
7136
|
};
|
|
6608
7137
|
const r = emit(combinator, ctx, "_pos");
|
|
6609
7138
|
const coverageRootRuleId = ctx.coverage?.plan.rules.get(combinator);
|
|
@@ -6667,6 +7196,7 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
6667
7196
|
source,
|
|
6668
7197
|
inlineExpression,
|
|
6669
7198
|
...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions },
|
|
7199
|
+
...gatingReport === void 0 ? {} : { gating: gatingReport },
|
|
6670
7200
|
parse(input, pos = 0) {
|
|
6671
7201
|
return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, defaultCtx);
|
|
6672
7202
|
},
|
|
@@ -6702,6 +7232,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6702
7232
|
if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
|
|
6703
7233
|
for (const [, rule] of ruleMapArg) markUnusedValues(rule);
|
|
6704
7234
|
const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
|
|
7235
|
+
const grammarScanSkip = opts?.scanSkip ?? ruleMapArg.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
|
|
6705
7236
|
const ruleMap = ruleMapArg.filter(([, val]) => {
|
|
6706
7237
|
const d = val._def;
|
|
6707
7238
|
if (d.tag !== "lazy") return true;
|
|
@@ -6739,7 +7270,8 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6739
7270
|
lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
|
|
6740
7271
|
ns,
|
|
6741
7272
|
deferFirstSetRefs: true,
|
|
6742
|
-
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
|
|
7273
|
+
...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {},
|
|
7274
|
+
...grammarScanSkip ? { activeScanSkip: grammarScanSkip } : {}
|
|
6743
7275
|
};
|
|
6744
7276
|
const seen = /* @__PURE__ */ new Map();
|
|
6745
7277
|
const ruleNames = /* @__PURE__ */ new Map();
|
|
@@ -6803,9 +7335,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6803
7335
|
ctx.failLabel = "_pfail";
|
|
6804
7336
|
ctx.recordFail = true;
|
|
6805
7337
|
const savedTrivia = ctx.activeTrivia;
|
|
6806
|
-
|
|
7338
|
+
const savedScanSkip = ctx.activeScanSkip;
|
|
7339
|
+
if (rule._meta.isTrivia) {
|
|
7340
|
+
ctx.activeTrivia = void 0;
|
|
7341
|
+
ctx.activeScanSkip = void 0;
|
|
7342
|
+
}
|
|
6807
7343
|
const body = emit(resolved, ctx, "_pos");
|
|
6808
7344
|
ctx.activeTrivia = savedTrivia;
|
|
7345
|
+
ctx.activeScanSkip = savedScanSkip;
|
|
6809
7346
|
ctx.indent = savedIndent;
|
|
6810
7347
|
ctx.failLabel = savedFail;
|
|
6811
7348
|
ctx.recordFail = savedRec;
|
|
@@ -6852,6 +7389,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6852
7389
|
}
|
|
6853
7390
|
const firstSets = /* @__PURE__ */ new Map();
|
|
6854
7391
|
const firstSetRecipes = /* @__PURE__ */ new Map();
|
|
7392
|
+
const nullable2 = /* @__PURE__ */ new Map();
|
|
6855
7393
|
for (const [key, rule] of ruleMap) {
|
|
6856
7394
|
let resolved = rule;
|
|
6857
7395
|
const d = rule._def;
|
|
@@ -6863,15 +7401,18 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
6863
7401
|
}
|
|
6864
7402
|
}
|
|
6865
7403
|
firstSets.set(key, canMatchEmptyAtStart(resolved) ? { kind: "any" } : resolved._meta.firstSet);
|
|
6866
|
-
|
|
7404
|
+
nullable2.set(key, matchesEmpty(resolved));
|
|
7405
|
+
firstSetRecipes.set(key, canMatchEmptyAtStart(resolved) ? { alts: [[{ set: any(), nullable: false }]] } : leadingFirstSetRecipe(resolved));
|
|
6867
7406
|
}
|
|
6868
7407
|
return {
|
|
7408
|
+
v: PARSEMAN_VERSION,
|
|
6869
7409
|
ns,
|
|
6870
7410
|
keys: perEntry.map((e) => e.key),
|
|
6871
7411
|
prelude,
|
|
6872
7412
|
ruleFns,
|
|
6873
7413
|
wrappers,
|
|
6874
7414
|
firstSets,
|
|
7415
|
+
nullable: nullable2,
|
|
6875
7416
|
firstSetRecipes,
|
|
6876
7417
|
deps: ruleDependencies(ruleMap),
|
|
6877
7418
|
needsEmptyTl: !!ctx.needsEmptyTl,
|
|
@@ -6950,101 +7491,6 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootR
|
|
|
6950
7491
|
].join("\n");
|
|
6951
7492
|
}
|
|
6952
7493
|
|
|
6953
|
-
// src/combinators/scanTo.ts
|
|
6954
|
-
function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
|
|
6955
|
-
const meta = {
|
|
6956
|
-
firstSet: any(),
|
|
6957
|
-
canMatchNewline: true,
|
|
6958
|
-
isTrivia: false
|
|
6959
|
-
};
|
|
6960
|
-
return {
|
|
6961
|
-
_tag: "scanTo",
|
|
6962
|
-
_meta: meta,
|
|
6963
|
-
_def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
|
|
6964
|
-
parse(input, pos, ctx) {
|
|
6965
|
-
let cur = pos;
|
|
6966
|
-
const probeCtx = {
|
|
6967
|
-
trackLines: ctx.trackLines,
|
|
6968
|
-
state: ctx.state,
|
|
6969
|
-
...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
|
|
6970
|
-
};
|
|
6971
|
-
const emit2 = (end) => {
|
|
6972
|
-
if (end > pos && cstCaptureActive(ctx)) {
|
|
6973
|
-
const leaf2 = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
|
|
6974
|
-
pushCstLeaf(ctx, leaf2);
|
|
6975
|
-
}
|
|
6976
|
-
};
|
|
6977
|
-
while (cur < input.length) {
|
|
6978
|
-
const s = sentinel.parse(input, cur, probeCtx);
|
|
6979
|
-
if (s.ok) {
|
|
6980
|
-
emit2(cur);
|
|
6981
|
-
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
6982
|
-
}
|
|
6983
|
-
let advanced = false;
|
|
6984
|
-
for (const skipper of skip2) {
|
|
6985
|
-
const r = skipper.parse(input, cur, probeCtx);
|
|
6986
|
-
if (r.ok && r.span.end > cur) {
|
|
6987
|
-
cur = r.span.end;
|
|
6988
|
-
advanced = true;
|
|
6989
|
-
break;
|
|
6990
|
-
}
|
|
6991
|
-
}
|
|
6992
|
-
if (!advanced) cur++;
|
|
6993
|
-
}
|
|
6994
|
-
if (orEOF) {
|
|
6995
|
-
emit2(cur);
|
|
6996
|
-
return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
|
|
6997
|
-
}
|
|
6998
|
-
const sentDef = sentinel._def;
|
|
6999
|
-
const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
|
|
7000
|
-
return { ok: false, expected, span: { start: pos, end: cur } };
|
|
7001
|
-
}
|
|
7002
|
-
};
|
|
7003
|
-
}
|
|
7004
|
-
function balanced(open, close, options = {}) {
|
|
7005
|
-
const self = ref();
|
|
7006
|
-
const skips = options.skip ?? [];
|
|
7007
|
-
const stop = /* @__PURE__ */ new Set([open, close]);
|
|
7008
|
-
let bounded = true;
|
|
7009
|
-
for (const sk of skips) {
|
|
7010
|
-
const cs = firstSetClassChars(sk);
|
|
7011
|
-
if (cs === null) {
|
|
7012
|
-
bounded = false;
|
|
7013
|
-
break;
|
|
7014
|
-
}
|
|
7015
|
-
for (const ch of cs) stop.add(ch);
|
|
7016
|
-
}
|
|
7017
|
-
const cls = [...stop].map(escapeClassChar).join("");
|
|
7018
|
-
const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
|
|
7019
|
-
const inner = many(choice(self, ...skips, content));
|
|
7020
|
-
const combi = transform(
|
|
7021
|
-
sequence(literal(open), inner, expect(literal(close))),
|
|
7022
|
-
// parts: strings (content/self) or arrays (a sequence-shaped skip) or a
|
|
7023
|
-
// ParseError (recovered close). `c` is the close string or a ParseError.
|
|
7024
|
-
([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
|
|
7025
|
-
);
|
|
7026
|
-
if (combi._def.tag === "transform") {
|
|
7027
|
-
combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
|
|
7028
|
-
combi._def.recognitionOnly = true;
|
|
7029
|
-
}
|
|
7030
|
-
self.define(combi);
|
|
7031
|
-
return combi;
|
|
7032
|
-
}
|
|
7033
|
-
function firstSetClassChars(c) {
|
|
7034
|
-
const fs = c._meta.firstSet;
|
|
7035
|
-
if (fs.kind === "empty") return [];
|
|
7036
|
-
if (fs.kind !== "ranges") return null;
|
|
7037
|
-
const out = [];
|
|
7038
|
-
for (const { lo, hi } of fs.ranges) {
|
|
7039
|
-
if (hi - lo > 8) return null;
|
|
7040
|
-
for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
|
|
7041
|
-
}
|
|
7042
|
-
return out;
|
|
7043
|
-
}
|
|
7044
|
-
function escapeClassChar(ch) {
|
|
7045
|
-
return ch.replace(/[\\\]^-]/g, "\\$&");
|
|
7046
|
-
}
|
|
7047
|
-
|
|
7048
7494
|
// src/combinators/withCtx.ts
|
|
7049
7495
|
function withCtx(extra, combinator) {
|
|
7050
7496
|
const meta = {
|
|
@@ -7101,7 +7547,7 @@ function childrenOf2(def) {
|
|
|
7101
7547
|
return [];
|
|
7102
7548
|
}
|
|
7103
7549
|
}
|
|
7104
|
-
var
|
|
7550
|
+
var ruleNameOf2 = (c) => c._ruleName;
|
|
7105
7551
|
function lazyTarget(c) {
|
|
7106
7552
|
if (c._def.tag !== "lazy") return null;
|
|
7107
7553
|
try {
|
|
@@ -7168,15 +7614,15 @@ function evalRuleMapIR(ir) {
|
|
|
7168
7614
|
if (Array.isArray(it)) {
|
|
7169
7615
|
const [src, comb] = it;
|
|
7170
7616
|
gateSrcs.push(src);
|
|
7171
|
-
let
|
|
7617
|
+
let gate2;
|
|
7172
7618
|
try {
|
|
7173
|
-
|
|
7619
|
+
gate2 = (0, eval)(`(${src})`);
|
|
7174
7620
|
} catch {
|
|
7175
|
-
|
|
7621
|
+
gate2 = () => {
|
|
7176
7622
|
throw new Error("IR gate fn not materialized");
|
|
7177
7623
|
};
|
|
7178
7624
|
}
|
|
7179
|
-
return { gate, combinator: comb };
|
|
7625
|
+
return { gate: gate2, combinator: comb };
|
|
7180
7626
|
}
|
|
7181
7627
|
gateSrcs.push(null);
|
|
7182
7628
|
return it;
|
|
@@ -7283,7 +7729,7 @@ var Serializer = class {
|
|
|
7283
7729
|
* its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
|
|
7284
7730
|
* `g[name]` ref — only the top-level entry is unwrapped. */
|
|
7285
7731
|
body(c) {
|
|
7286
|
-
return c._def.tag === "lazy" &&
|
|
7732
|
+
return c._def.tag === "lazy" && ruleNameOf2(c) !== void 0 ? lazyTarget(c) ?? c : c;
|
|
7287
7733
|
}
|
|
7288
7734
|
run() {
|
|
7289
7735
|
for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
|
|
@@ -7308,7 +7754,7 @@ ${body}
|
|
|
7308
7754
|
analyze(c, active) {
|
|
7309
7755
|
const def = c._def;
|
|
7310
7756
|
if (def.tag === "lazy") {
|
|
7311
|
-
if (
|
|
7757
|
+
if (ruleNameOf2(c) !== void 0) return;
|
|
7312
7758
|
const target = lazyTarget(c);
|
|
7313
7759
|
if (!target) throw new Unserializable("unresolved unnamed ref");
|
|
7314
7760
|
if (active.has(target)) {
|
|
@@ -7360,7 +7806,7 @@ ${body}
|
|
|
7360
7806
|
const kid = (x) => this.ref(x, selfOf);
|
|
7361
7807
|
switch (def.tag) {
|
|
7362
7808
|
case "lazy": {
|
|
7363
|
-
if (
|
|
7809
|
+
if (ruleNameOf2(c) !== void 0) return `g[${JSON.stringify(ruleNameOf2(c))}]`;
|
|
7364
7810
|
const target = lazyTarget(c);
|
|
7365
7811
|
if (selfOf && target === selfOf.comb) return selfOf.var;
|
|
7366
7812
|
return this.ref(target, selfOf);
|
|
@@ -7414,7 +7860,7 @@ ${body}
|
|
|
7414
7860
|
case "skip":
|
|
7415
7861
|
return `skip(${kid(def.main)}, ${kid(def.skipped)})`;
|
|
7416
7862
|
case "scanTo":
|
|
7417
|
-
return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}], orEOF: ${def.orEOF} })`;
|
|
7863
|
+
return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}]${def.raw ? ", raw: true" : ""}, orEOF: ${def.orEOF} })`;
|
|
7418
7864
|
case "transform": {
|
|
7419
7865
|
if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
|
|
7420
7866
|
return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)}${def.recognitionOnly ? ", true" : ""})`;
|
|
@@ -7484,6 +7930,18 @@ function cstBuildHost(typeOrOptions, children2, _fields, span, rawChildren, triv
|
|
|
7484
7930
|
}
|
|
7485
7931
|
cstBuildHost._parsemanCstOutput = true;
|
|
7486
7932
|
function fusedBody(pieces) {
|
|
7933
|
+
for (const p of pieces) {
|
|
7934
|
+
if (p.v === void 0) {
|
|
7935
|
+
throw new Error(
|
|
7936
|
+
`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.`
|
|
7937
|
+
);
|
|
7938
|
+
}
|
|
7939
|
+
if (p.v !== PARSEMAN_VERSION) {
|
|
7940
|
+
throw new Error(
|
|
7941
|
+
`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.`
|
|
7942
|
+
);
|
|
7943
|
+
}
|
|
7944
|
+
}
|
|
7487
7945
|
const winner = /* @__PURE__ */ new Map();
|
|
7488
7946
|
for (const p of pieces) for (const k of p.keys) winner.set(k, p);
|
|
7489
7947
|
for (const [k, p] of winner) {
|
|
@@ -7509,23 +7967,38 @@ function fusedBody(pieces) {
|
|
|
7509
7967
|
const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
|
|
7510
7968
|
const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
|
|
7511
7969
|
const ANY2 = { kind: "any" };
|
|
7970
|
+
const EMPTY3 = { kind: "empty" };
|
|
7512
7971
|
const recipes = /* @__PURE__ */ new Map();
|
|
7513
7972
|
const shallow = /* @__PURE__ */ new Map();
|
|
7973
|
+
const nullable2 = /* @__PURE__ */ new Map();
|
|
7514
7974
|
for (const [k, p] of winner) {
|
|
7515
7975
|
const r = p.firstSetRecipes?.get(k);
|
|
7516
7976
|
if (r) recipes.set(k, r);
|
|
7517
7977
|
const fs = p.firstSets?.get(k);
|
|
7518
7978
|
if (fs) shallow.set(k, fs);
|
|
7519
|
-
|
|
7979
|
+
const nu = p.nullable?.get(k);
|
|
7980
|
+
if (nu !== void 0) nullable2.set(k, nu);
|
|
7981
|
+
}
|
|
7982
|
+
const knownRef = (ref2) => recipes.has(ref2) || shallow.has(ref2);
|
|
7983
|
+
const refFS = (ref2) => knownRef(ref2) ? finalFS.get(ref2) ?? ANY2 : ANY2;
|
|
7984
|
+
const refNullable = (ref2) => knownRef(ref2) ? nullable2.get(ref2) ?? true : true;
|
|
7985
|
+
const resolveRecipe = (r) => {
|
|
7986
|
+
let out = EMPTY3;
|
|
7987
|
+
for (const chain of r.alts) {
|
|
7988
|
+
for (const seg of chain) {
|
|
7989
|
+
out = union(out, seg.ref !== void 0 ? refFS(seg.ref) : seg.set);
|
|
7990
|
+
const skippable = seg.ref !== void 0 ? seg.nullable || refNullable(seg.ref) : seg.nullable;
|
|
7991
|
+
if (!skippable) break;
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
return out;
|
|
7995
|
+
};
|
|
7520
7996
|
const finalFS = /* @__PURE__ */ new Map();
|
|
7521
|
-
for (const [k] of winner) finalFS.set(k, recipes.
|
|
7997
|
+
for (const [k] of winner) finalFS.set(k, recipes.has(k) ? EMPTY3 : shallow.get(k) ?? ANY2);
|
|
7522
7998
|
for (let iter = 0; iter <= recipes.size; iter++) {
|
|
7523
7999
|
let changed = false;
|
|
7524
8000
|
for (const [k, r] of recipes) {
|
|
7525
|
-
|
|
7526
|
-
for (const ref2 of r.refs) {
|
|
7527
|
-
fs = union(fs, recipes.has(ref2) || shallow.has(ref2) ? finalFS.get(ref2) ?? ANY2 : ANY2);
|
|
7528
|
-
}
|
|
8001
|
+
const fs = resolveRecipe(r);
|
|
7529
8002
|
if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs)) {
|
|
7530
8003
|
finalFS.set(k, fs);
|
|
7531
8004
|
changed = true;
|
|
@@ -7657,8 +8130,8 @@ function composeLeaf(_items) {
|
|
|
7657
8130
|
throw new Error("composeLeaf(): requires Parseman macro lowering; runtime composition is forbidden");
|
|
7658
8131
|
}
|
|
7659
8132
|
|
|
7660
|
-
// src/combinators/
|
|
7661
|
-
function
|
|
8133
|
+
// src/combinators/gate.ts
|
|
8134
|
+
function gate(predicate) {
|
|
7662
8135
|
const meta = {
|
|
7663
8136
|
firstSet: { kind: "any" },
|
|
7664
8137
|
canMatchNewline: false,
|
|
@@ -7676,6 +8149,9 @@ function guard(predicate) {
|
|
|
7676
8149
|
};
|
|
7677
8150
|
}
|
|
7678
8151
|
|
|
8152
|
+
// src/combinators/guard.ts
|
|
8153
|
+
var guard = gate;
|
|
8154
|
+
|
|
7679
8155
|
// src/combinators/completions.ts
|
|
7680
8156
|
function completionsAt(target, input, offset, options = {}) {
|
|
7681
8157
|
const probe = { offset, best: null };
|
|
@@ -8207,12 +8683,14 @@ function runOnce(entry, input, options, phase, profileState) {
|
|
|
8207
8683
|
const profile = profileState ?? (phase === void 0 ? void 0 : { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 });
|
|
8208
8684
|
const skipGlobalSinks = phase === "recognizer" || phase === "capture";
|
|
8209
8685
|
const grammarTrivia = typeof entry !== "function" ? entry._meta.grammarTrivia : void 0;
|
|
8686
|
+
const grammarScanSkip = typeof entry !== "function" ? entry._meta.grammarScanSkip : void 0;
|
|
8210
8687
|
const ctx = {
|
|
8211
8688
|
trackLines: false,
|
|
8212
8689
|
...skipGlobalSinks ? { _pmProfile: profile } : { _triviaLog: triviaLog, _errors: errors, ...profile === void 0 ? {} : { _pmProfile: profile } },
|
|
8213
8690
|
build: options.build,
|
|
8214
8691
|
state: options.state,
|
|
8215
8692
|
...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...grammarTrivia._meta.triviaKindLabels ? { triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {} } : {},
|
|
8693
|
+
...grammarScanSkip !== void 0 ? { scanSkip: grammarScanSkip } : {},
|
|
8216
8694
|
...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {},
|
|
8217
8695
|
...options.tolerant ? { _tolerant: true, _rec: REC } : {},
|
|
8218
8696
|
...options.instrumentation === void 0 ? {} : options.instrumentation
|
|
@@ -8498,7 +8976,7 @@ function coverageEntry(entry, collector, maps, trace) {
|
|
|
8498
8976
|
return def.type === void 0 ? node(build(def.parser), def.build, opts) : node(def.type, build(def.parser), def.build, opts);
|
|
8499
8977
|
}
|
|
8500
8978
|
case "guard":
|
|
8501
|
-
return
|
|
8979
|
+
return gate(def.predicate);
|
|
8502
8980
|
case "withCtx":
|
|
8503
8981
|
return withCtx(def.extra, build(def.parser));
|
|
8504
8982
|
case "expect":
|
|
@@ -8553,9 +9031,9 @@ function buildTriviaIndex(root, input = "", opts) {
|
|
|
8553
9031
|
if (prev) merge(after, prev.span.end, run2);
|
|
8554
9032
|
}
|
|
8555
9033
|
}
|
|
8556
|
-
const
|
|
8557
|
-
if (
|
|
8558
|
-
for (const c of
|
|
9034
|
+
const ch2 = node2.children;
|
|
9035
|
+
if (ch2) {
|
|
9036
|
+
for (const c of ch2) {
|
|
8559
9037
|
if (typeof c === "object" && c !== null && c.children !== void 0) {
|
|
8560
9038
|
visit(c);
|
|
8561
9039
|
}
|