parseman 0.32.0 → 0.34.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 +66 -1
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/combinators/case-fold.d.ts +39 -0
- package/dist/combinators/case-fold.d.ts.map +1 -0
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts +38 -11
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/keywords.d.ts +12 -3
- package/dist/combinators/keywords.d.ts.map +1 -1
- package/dist/combinators/not.d.ts.map +1 -1
- package/dist/combinators/peek.d.ts +27 -0
- package/dist/combinators/peek.d.ts.map +1 -0
- package/dist/combinators/repeat.d.ts +68 -3
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/trivia-skip.d.ts +23 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/codegen.d.ts +40 -0
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/fields.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 +5 -0
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/value-usage.d.ts.map +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/index.cjs +459 -86
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +459 -86
- package/dist/index.js.map +4 -4
- package/dist/language-service/index.cjs.map +1 -1
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.cjs +538 -123
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +538 -123
- package/dist/plugin/index.js.map +4 -4
- package/dist/spec/ebnf.d.ts.map +1 -1
- package/dist/spec/index.cjs +79 -17
- package/dist/spec/index.cjs.map +2 -2
- package/dist/spec/index.js +79 -17
- package/dist/spec/index.js.map +2 -2
- package/dist/spec/model.d.ts +23 -3
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/spec/railroad.d.ts.map +1 -1
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
// src/combinators/first-set.ts
|
|
2
|
+
function resolveNamedRef(p, resolve) {
|
|
3
|
+
if (resolve === void 0) return void 0;
|
|
4
|
+
const name = p._ruleName;
|
|
5
|
+
return name === void 0 ? void 0 : resolve(name);
|
|
6
|
+
}
|
|
2
7
|
function union(a, b) {
|
|
3
8
|
if (a.kind === "any" || b.kind === "any") return { kind: "any" };
|
|
4
9
|
if (a.kind === "empty") return b;
|
|
@@ -36,10 +41,10 @@ function any() {
|
|
|
36
41
|
function empty() {
|
|
37
42
|
return { kind: "empty" };
|
|
38
43
|
}
|
|
39
|
-
function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
44
|
+
function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
40
45
|
if (seen.has(p)) return true;
|
|
41
46
|
seen.add(p);
|
|
42
|
-
const me = (c) => matchesEmpty(c, seen);
|
|
47
|
+
const me = (c) => matchesEmpty(c, seen, resolve);
|
|
43
48
|
const d = p._def;
|
|
44
49
|
switch (d.tag) {
|
|
45
50
|
case "literal":
|
|
@@ -56,8 +61,16 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
56
61
|
case "many":
|
|
57
62
|
case "optional":
|
|
58
63
|
case "not":
|
|
64
|
+
case "peek":
|
|
59
65
|
return true;
|
|
60
66
|
// zero repetitions / absent / lookahead
|
|
67
|
+
// Default `sepBy` is `(item (sep item)*)?` — it MATCHES THE EMPTY STRING.
|
|
68
|
+
// Any `min >= 1` requires that many ITEMS, so it is nullable only when the
|
|
69
|
+
// item is. (Keying this off `min === 1` reported every `{ min: 2 }` list as
|
|
70
|
+
// nullable — safe, but wrong, and it put a bogus `nullable-prefix` note on
|
|
71
|
+
// the gating diagnostic for a list that can never match empty.)
|
|
72
|
+
case "sepBy":
|
|
73
|
+
return d.min >= 1 ? me(d.parser) : true;
|
|
61
74
|
case "oneOrMore":
|
|
62
75
|
return me(d.parser);
|
|
63
76
|
case "sequence":
|
|
@@ -81,7 +94,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
81
94
|
try {
|
|
82
95
|
return me(d.thunk());
|
|
83
96
|
} catch {
|
|
84
|
-
|
|
97
|
+
const t = resolveNamedRef(p, resolve);
|
|
98
|
+
return t ? me(t) : true;
|
|
85
99
|
}
|
|
86
100
|
default:
|
|
87
101
|
return true;
|
|
@@ -90,18 +104,45 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
90
104
|
function isZeroWidthAssertion(p) {
|
|
91
105
|
return p._def.tag === "not";
|
|
92
106
|
}
|
|
107
|
+
function isPositiveLookahead(p) {
|
|
108
|
+
return p._def.tag === "peek";
|
|
109
|
+
}
|
|
110
|
+
function narrowBy(acc, constraint) {
|
|
111
|
+
if (constraint.kind === "any") return acc;
|
|
112
|
+
if (acc === null) return constraint;
|
|
113
|
+
if (acc.kind === "any") return constraint;
|
|
114
|
+
if (acc.kind === "empty" || constraint.kind === "empty") return { kind: "empty" };
|
|
115
|
+
const ranges = [];
|
|
116
|
+
for (const a of acc.ranges) for (const b of constraint.ranges) {
|
|
117
|
+
const lo = Math.max(a.lo, b.lo);
|
|
118
|
+
const hi = Math.min(a.hi, b.hi);
|
|
119
|
+
if (lo <= hi) ranges.push({ lo, hi });
|
|
120
|
+
}
|
|
121
|
+
return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
|
|
122
|
+
}
|
|
123
|
+
function applyAssertion(fs, assertion) {
|
|
124
|
+
if (assertion === null) return fs;
|
|
125
|
+
if (fs.kind === "empty") return assertion;
|
|
126
|
+
return narrowBy(fs, assertion) ?? fs;
|
|
127
|
+
}
|
|
93
128
|
function sequenceFirstSet(parsers) {
|
|
94
129
|
let fs = empty();
|
|
130
|
+
let assertion = null;
|
|
95
131
|
for (const p of parsers) {
|
|
132
|
+
if (isPositiveLookahead(p)) {
|
|
133
|
+
assertion = narrowBy(assertion, p._meta.firstSet);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
96
136
|
if (!isZeroWidthAssertion(p)) fs = union(fs, p._meta.firstSet);
|
|
97
|
-
if (!matchesEmpty(p)) return fs;
|
|
137
|
+
if (!matchesEmpty(p)) return applyAssertion(fs, assertion);
|
|
98
138
|
}
|
|
99
|
-
return fs;
|
|
139
|
+
return applyAssertion(fs, assertion);
|
|
100
140
|
}
|
|
101
|
-
function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
141
|
+
function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
102
142
|
if (seen.has(p)) return any();
|
|
103
143
|
seen.add(p);
|
|
104
|
-
const fs = (c) => firstSetOf(c, seen);
|
|
144
|
+
const fs = (c) => firstSetOf(c, seen, resolve);
|
|
145
|
+
const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve);
|
|
105
146
|
const d = p._def;
|
|
106
147
|
switch (d.tag) {
|
|
107
148
|
case "literal":
|
|
@@ -113,7 +154,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
113
154
|
try {
|
|
114
155
|
return fs(d.thunk());
|
|
115
156
|
} catch {
|
|
116
|
-
|
|
157
|
+
const t = resolveNamedRef(p, resolve);
|
|
158
|
+
return t ? fs(t) : any();
|
|
117
159
|
}
|
|
118
160
|
case "choice": {
|
|
119
161
|
let out = empty();
|
|
@@ -122,11 +164,20 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
122
164
|
}
|
|
123
165
|
case "sequence": {
|
|
124
166
|
let out = empty();
|
|
167
|
+
let assertion = null;
|
|
125
168
|
for (const term of d.parsers) {
|
|
169
|
+
if (isPositiveLookahead(term)) {
|
|
170
|
+
assertion = narrowBy(assertion, fs(term));
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
126
173
|
if (!isZeroWidthAssertion(term)) out = union(out, fs(term));
|
|
127
|
-
if (!
|
|
174
|
+
if (!empties(term)) return applyAssertion(out, assertion);
|
|
128
175
|
}
|
|
129
|
-
return out;
|
|
176
|
+
return applyAssertion(out, assertion);
|
|
177
|
+
}
|
|
178
|
+
case "peek": {
|
|
179
|
+
const inner = d.parser;
|
|
180
|
+
return empties(inner) ? any() : fs(inner);
|
|
130
181
|
}
|
|
131
182
|
case "oneOrMore":
|
|
132
183
|
case "many":
|
|
@@ -142,6 +193,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
142
193
|
return fs(d.parser);
|
|
143
194
|
case "sepBy":
|
|
144
195
|
return fs(d.parser);
|
|
196
|
+
// both min 0 and min 1 start with the item
|
|
145
197
|
case "skip":
|
|
146
198
|
return fs(d.main);
|
|
147
199
|
default:
|
|
@@ -826,9 +878,46 @@ function firstSetFromRegex(source) {
|
|
|
826
878
|
return { firstSet: firstSet(ast), canMatchNewline: firstCanBeNewline(ast) };
|
|
827
879
|
}
|
|
828
880
|
|
|
881
|
+
// src/combinators/case-fold.ts
|
|
882
|
+
function canonical(ch2) {
|
|
883
|
+
const u = ch2.toUpperCase();
|
|
884
|
+
if (u.length !== 1) return ch2;
|
|
885
|
+
if (ch2.codePointAt(0) >= 128 && u.codePointAt(0) < 128) return ch2;
|
|
886
|
+
return u;
|
|
887
|
+
}
|
|
888
|
+
var foldIndex = null;
|
|
889
|
+
function buildFoldIndex() {
|
|
890
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
891
|
+
for (let cp = 0; cp < 65536; cp++) {
|
|
892
|
+
if (cp >= 55296 && cp <= 57343) continue;
|
|
893
|
+
const key = canonical(String.fromCharCode(cp));
|
|
894
|
+
const bucket = byCanonical.get(key);
|
|
895
|
+
if (bucket) bucket.push(cp);
|
|
896
|
+
else byCanonical.set(key, [cp]);
|
|
897
|
+
}
|
|
898
|
+
const index = /* @__PURE__ */ new Map();
|
|
899
|
+
for (const members of byCanonical.values()) {
|
|
900
|
+
if (members.length < 2) continue;
|
|
901
|
+
for (const cp of members) index.set(cp, members);
|
|
902
|
+
}
|
|
903
|
+
return index;
|
|
904
|
+
}
|
|
905
|
+
function caseFoldVariants(cp) {
|
|
906
|
+
if (cp < 128) {
|
|
907
|
+
if (cp >= 65 && cp <= 90) return [cp, cp + 32];
|
|
908
|
+
if (cp >= 97 && cp <= 122) return [cp, cp - 32];
|
|
909
|
+
return [cp];
|
|
910
|
+
}
|
|
911
|
+
if (cp > 65535) return [cp];
|
|
912
|
+
foldIndex ??= buildFoldIndex();
|
|
913
|
+
return foldIndex.get(cp) ?? [cp];
|
|
914
|
+
}
|
|
915
|
+
|
|
829
916
|
// src/combinators/keywords.ts
|
|
830
|
-
function word(str,
|
|
831
|
-
|
|
917
|
+
function word(str, boundaryOrOpts, opts) {
|
|
918
|
+
const boundary = typeof boundaryOrOpts === "string" ? boundaryOrOpts : "_0-9A-Za-z";
|
|
919
|
+
const rest = typeof boundaryOrOpts === "object" && boundaryOrOpts !== null ? boundaryOrOpts : opts;
|
|
920
|
+
return keywords([str], { ...rest, boundary });
|
|
832
921
|
}
|
|
833
922
|
function makeWord(boundary = "_0-9A-Za-z") {
|
|
834
923
|
return (str) => keywords([str], { boundary });
|
|
@@ -840,7 +929,7 @@ function keywords(words, opts = {}) {
|
|
|
840
929
|
const sorted = [...new Set(words)].sort((a, b) => b.length - a.length);
|
|
841
930
|
const alt = sorted.map(escapeRe).join("|");
|
|
842
931
|
const boundary = opts.boundary ? `(?![${opts.boundary}])` : "";
|
|
843
|
-
const flags = opts.caseInsensitive ? "
|
|
932
|
+
const flags = opts.caseInsensitive ? "iy" : "uy";
|
|
844
933
|
const re = new RegExp(`(?:${alt})${boundary}`, flags);
|
|
845
934
|
let firstSet2 = empty();
|
|
846
935
|
for (const w of sorted) {
|
|
@@ -848,10 +937,7 @@ function keywords(words, opts = {}) {
|
|
|
848
937
|
const cp = w.codePointAt(0);
|
|
849
938
|
firstSet2 = union(firstSet2, fromChar(cp));
|
|
850
939
|
if (opts.caseInsensitive) {
|
|
851
|
-
const
|
|
852
|
-
const l = String.fromCodePoint(cp).toLowerCase().codePointAt(0);
|
|
853
|
-
if (u !== void 0) firstSet2 = union(firstSet2, fromChar(u));
|
|
854
|
-
if (l !== void 0) firstSet2 = union(firstSet2, fromChar(l));
|
|
940
|
+
for (const v2 of caseFoldVariants(cp)) firstSet2 = union(firstSet2, fromChar(v2));
|
|
855
941
|
}
|
|
856
942
|
}
|
|
857
943
|
const meta = { firstSet: firstSet2, canMatchNewline: false, isTrivia: false };
|
|
@@ -1000,6 +1086,7 @@ function deriveExpected(c) {
|
|
|
1000
1086
|
case "oneOrMore":
|
|
1001
1087
|
case "transform":
|
|
1002
1088
|
case "not":
|
|
1089
|
+
case "peek":
|
|
1003
1090
|
return deriveExpected(def.parser);
|
|
1004
1091
|
case "lazy": {
|
|
1005
1092
|
const name = c._ruleName;
|
|
@@ -1473,6 +1560,13 @@ function rollbackTrivia(ctx, mark) {
|
|
|
1473
1560
|
rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
|
|
1474
1561
|
if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
|
|
1475
1562
|
}
|
|
1563
|
+
function saveLookaheadMark(ctx) {
|
|
1564
|
+
return { ...saveTriviaMark(ctx), probeBest: ctx._probe ? ctx._probe.best : null };
|
|
1565
|
+
}
|
|
1566
|
+
function rollbackLookahead(ctx, mark) {
|
|
1567
|
+
rollbackTrivia(ctx, mark);
|
|
1568
|
+
if (ctx._probe) ctx._probe.best = mark.probeBest;
|
|
1569
|
+
}
|
|
1476
1570
|
function scanWithLabels(input, cur, ctx) {
|
|
1477
1571
|
const triviaP = ctx.trivia;
|
|
1478
1572
|
const spec = analyzeLabeledTrivia(triviaP);
|
|
@@ -1870,13 +1964,26 @@ function repItem(combinator, input, cur, ctx, guardable) {
|
|
|
1870
1964
|
}
|
|
1871
1965
|
return { value: result.value, end: result.span.end };
|
|
1872
1966
|
}
|
|
1873
|
-
function
|
|
1967
|
+
function resolveBounds(what, opts) {
|
|
1968
|
+
const min = opts.min ?? 0;
|
|
1969
|
+
const max = opts.max ?? Infinity;
|
|
1970
|
+
const bad = (msg) => {
|
|
1971
|
+
throw new RangeError(`parseman: ${what} ${msg}`);
|
|
1972
|
+
};
|
|
1973
|
+
if (!Number.isInteger(min) || min < 0) bad(`min must be a non-negative integer (got ${String(opts.min)})`);
|
|
1974
|
+
if (max !== Infinity && (!Number.isInteger(max) || max < 1)) bad(`max must be a positive integer (got ${String(opts.max)})`);
|
|
1975
|
+
if (max < min) bad(`max (${max}) is less than min (${min}) \u2014 the combinator could never succeed`);
|
|
1976
|
+
return { min, max };
|
|
1977
|
+
}
|
|
1978
|
+
function many(combinator, opts = {}) {
|
|
1979
|
+
const { min, max } = resolveBounds("many()", opts);
|
|
1980
|
+
if (min >= 1) return atLeast(combinator, min, max);
|
|
1874
1981
|
const meta = {
|
|
1875
1982
|
firstSet: combinator._meta.firstSet,
|
|
1876
1983
|
canMatchNewline: combinator._meta.canMatchNewline,
|
|
1877
1984
|
isTrivia: false
|
|
1878
1985
|
};
|
|
1879
|
-
const def = { tag: "many", parser: combinator, min: 0 };
|
|
1986
|
+
const def = { tag: "many", parser: combinator, min: 0, ...max === Infinity ? {} : { max } };
|
|
1880
1987
|
let expected;
|
|
1881
1988
|
const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
|
|
1882
1989
|
return {
|
|
@@ -1886,7 +1993,9 @@ function many(combinator) {
|
|
|
1886
1993
|
parse(input, pos, ctx) {
|
|
1887
1994
|
const values = def.valueUnused ? void 0 : [];
|
|
1888
1995
|
let cur = pos;
|
|
1996
|
+
let count = 0;
|
|
1889
1997
|
while (cur < input.length) {
|
|
1998
|
+
if (count >= max) break;
|
|
1890
1999
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1891
2000
|
if (item === "stop") break;
|
|
1892
2001
|
if ("fail" in item) {
|
|
@@ -1897,23 +2006,28 @@ function many(combinator) {
|
|
|
1897
2006
|
const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
|
|
1898
2007
|
if (values !== void 0) values.push(error);
|
|
1899
2008
|
captureError(ctx, error);
|
|
2009
|
+
count++;
|
|
1900
2010
|
cur = end;
|
|
1901
2011
|
continue;
|
|
1902
2012
|
}
|
|
1903
2013
|
if (values !== void 0) values.push(item.value);
|
|
2014
|
+
count++;
|
|
1904
2015
|
cur = item.end;
|
|
1905
2016
|
}
|
|
1906
2017
|
return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
|
|
1907
2018
|
}
|
|
1908
2019
|
};
|
|
1909
2020
|
}
|
|
1910
|
-
function oneOrMore(combinator) {
|
|
2021
|
+
function oneOrMore(combinator, opts = {}) {
|
|
2022
|
+
return many(combinator, { ...opts, min: opts.min ?? 1 });
|
|
2023
|
+
}
|
|
2024
|
+
function atLeast(combinator, min, max) {
|
|
1911
2025
|
const meta = {
|
|
1912
2026
|
firstSet: combinator._meta.firstSet,
|
|
1913
2027
|
canMatchNewline: combinator._meta.canMatchNewline,
|
|
1914
2028
|
isTrivia: false
|
|
1915
2029
|
};
|
|
1916
|
-
const def = { tag: "oneOrMore", parser: combinator, min:
|
|
2030
|
+
const def = { tag: "oneOrMore", parser: combinator, min, ...max === Infinity ? {} : { max } };
|
|
1917
2031
|
let expected;
|
|
1918
2032
|
const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
|
|
1919
2033
|
return {
|
|
@@ -1925,7 +2039,19 @@ function oneOrMore(combinator) {
|
|
|
1925
2039
|
if (!first.ok) return first;
|
|
1926
2040
|
const values = def.valueUnused ? void 0 : [first.value];
|
|
1927
2041
|
let cur = first.span.end;
|
|
2042
|
+
let count = 1;
|
|
2043
|
+
while (count < min) {
|
|
2044
|
+
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
2045
|
+
if (item === "stop" || "fail" in item) {
|
|
2046
|
+
expected ??= deriveExpected(combinator);
|
|
2047
|
+
return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
|
|
2048
|
+
}
|
|
2049
|
+
if (values !== void 0) values.push(item.value);
|
|
2050
|
+
cur = item.end;
|
|
2051
|
+
count++;
|
|
2052
|
+
}
|
|
1928
2053
|
while (cur < input.length) {
|
|
2054
|
+
if (count >= max) break;
|
|
1929
2055
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1930
2056
|
if (item === "stop") break;
|
|
1931
2057
|
if ("fail" in item) {
|
|
@@ -1936,10 +2062,12 @@ function oneOrMore(combinator) {
|
|
|
1936
2062
|
const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
|
|
1937
2063
|
if (values !== void 0) values.push(error);
|
|
1938
2064
|
captureError(ctx, error);
|
|
2065
|
+
count++;
|
|
1939
2066
|
cur = end;
|
|
1940
2067
|
continue;
|
|
1941
2068
|
}
|
|
1942
2069
|
if (values !== void 0) values.push(item.value);
|
|
2070
|
+
count++;
|
|
1943
2071
|
cur = item.end;
|
|
1944
2072
|
}
|
|
1945
2073
|
return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
|
|
@@ -1969,17 +2097,33 @@ function optional(combinator) {
|
|
|
1969
2097
|
}
|
|
1970
2098
|
};
|
|
1971
2099
|
}
|
|
1972
|
-
function
|
|
2100
|
+
function oneOrMoreSep(combinator, separator, opts = {}) {
|
|
2101
|
+
return sepBy(combinator, separator, { ...opts, min: opts.min ?? 1 });
|
|
2102
|
+
}
|
|
2103
|
+
function sepBy(combinator, separator, opts = {}) {
|
|
2104
|
+
const { min, max } = resolveBounds("sepBy()", opts);
|
|
2105
|
+
const trailing = opts.trailing ?? "forbid";
|
|
1973
2106
|
const meta = {
|
|
1974
2107
|
firstSet: combinator._meta.firstSet,
|
|
1975
2108
|
canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
|
|
1976
2109
|
isTrivia: false
|
|
1977
2110
|
};
|
|
1978
2111
|
let expected;
|
|
2112
|
+
const failAt2 = (at) => {
|
|
2113
|
+
expected ??= deriveExpected(combinator);
|
|
2114
|
+
return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: at, end: at } };
|
|
2115
|
+
};
|
|
1979
2116
|
return {
|
|
1980
2117
|
_tag: "sepBy",
|
|
1981
2118
|
_meta: meta,
|
|
1982
|
-
_def: {
|
|
2119
|
+
_def: {
|
|
2120
|
+
tag: "sepBy",
|
|
2121
|
+
parser: combinator,
|
|
2122
|
+
separator,
|
|
2123
|
+
min,
|
|
2124
|
+
...max === Infinity ? {} : { max },
|
|
2125
|
+
...trailing === "forbid" ? {} : { trailing }
|
|
2126
|
+
},
|
|
1983
2127
|
parse(input, pos, ctx) {
|
|
1984
2128
|
const first = combinator.parse(input, pos, ctx);
|
|
1985
2129
|
const values = [];
|
|
@@ -1990,6 +2134,7 @@ function sepBy(combinator, separator) {
|
|
|
1990
2134
|
} else {
|
|
1991
2135
|
const term = ctx._tolerant ? ctx._sync : void 0;
|
|
1992
2136
|
if (term === void 0 || matchesAt(term, input, pos, ctx)) {
|
|
2137
|
+
if (min >= 1) return failAt2(pos);
|
|
1993
2138
|
return { ok: true, value: [], span: { start: pos, end: pos } };
|
|
1994
2139
|
}
|
|
1995
2140
|
expected ??= deriveExpected(combinator);
|
|
@@ -1999,6 +2144,7 @@ function sepBy(combinator, separator) {
|
|
|
1999
2144
|
cur = rec.end;
|
|
2000
2145
|
}
|
|
2001
2146
|
while (cur < input.length) {
|
|
2147
|
+
if (values.length >= max) break;
|
|
2002
2148
|
const loopMark = saveTriviaMark(ctx);
|
|
2003
2149
|
let sepPos = cur;
|
|
2004
2150
|
if (ctx.trivia) {
|
|
@@ -2015,6 +2161,7 @@ function sepBy(combinator, separator) {
|
|
|
2015
2161
|
rollbackTrivia(ctx, loopMark);
|
|
2016
2162
|
break;
|
|
2017
2163
|
}
|
|
2164
|
+
const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
|
|
2018
2165
|
let nextPos = sep.span.end;
|
|
2019
2166
|
if (ctx.trivia) {
|
|
2020
2167
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
@@ -2036,12 +2183,18 @@ function sepBy(combinator, separator) {
|
|
|
2036
2183
|
cur = rec.end;
|
|
2037
2184
|
continue;
|
|
2038
2185
|
}
|
|
2186
|
+
if (sepMark !== void 0) {
|
|
2187
|
+
rollbackTrivia(ctx, sepMark);
|
|
2188
|
+
cur = sep.span.end;
|
|
2189
|
+
break;
|
|
2190
|
+
}
|
|
2039
2191
|
rollbackTrivia(ctx, loopMark);
|
|
2040
2192
|
break;
|
|
2041
2193
|
}
|
|
2042
2194
|
values.push(next.value);
|
|
2043
2195
|
cur = next.span.end;
|
|
2044
2196
|
}
|
|
2197
|
+
if (values.length < min) return failAt2(cur);
|
|
2045
2198
|
return { ok: true, value: values, span: { start: pos, end: cur } };
|
|
2046
2199
|
}
|
|
2047
2200
|
};
|
|
@@ -2131,6 +2284,7 @@ function markUnusedValues(root) {
|
|
|
2131
2284
|
visit(def.separator, false);
|
|
2132
2285
|
return;
|
|
2133
2286
|
case "not":
|
|
2287
|
+
case "peek":
|
|
2134
2288
|
visit(def.parser, false);
|
|
2135
2289
|
return;
|
|
2136
2290
|
case "grammar":
|
|
@@ -2249,9 +2403,9 @@ function not(combinator) {
|
|
|
2249
2403
|
_meta: meta,
|
|
2250
2404
|
_def: { tag: "not", parser: combinator },
|
|
2251
2405
|
parse(input, pos, ctx) {
|
|
2252
|
-
const mark =
|
|
2406
|
+
const mark = saveLookaheadMark(ctx);
|
|
2253
2407
|
const result = combinator.parse(input, pos, ctx);
|
|
2254
|
-
|
|
2408
|
+
rollbackLookahead(ctx, mark);
|
|
2255
2409
|
if (result.ok) {
|
|
2256
2410
|
return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
|
|
2257
2411
|
}
|
|
@@ -2260,6 +2414,28 @@ function not(combinator) {
|
|
|
2260
2414
|
};
|
|
2261
2415
|
}
|
|
2262
2416
|
|
|
2417
|
+
// src/combinators/peek.ts
|
|
2418
|
+
function peek(combinator) {
|
|
2419
|
+
const meta = {
|
|
2420
|
+
// A nullable body imposes no first-char constraint — `any()`, not its set.
|
|
2421
|
+
firstSet: matchesEmpty(combinator) ? any() : combinator._meta.firstSet,
|
|
2422
|
+
canMatchNewline: false,
|
|
2423
|
+
isTrivia: false
|
|
2424
|
+
};
|
|
2425
|
+
return {
|
|
2426
|
+
_tag: "peek",
|
|
2427
|
+
_meta: meta,
|
|
2428
|
+
_def: { tag: "peek", parser: combinator },
|
|
2429
|
+
parse(input, pos, ctx) {
|
|
2430
|
+
const mark = saveLookaheadMark(ctx);
|
|
2431
|
+
const result = combinator.parse(input, pos, ctx);
|
|
2432
|
+
rollbackLookahead(ctx, mark);
|
|
2433
|
+
if (result.ok) return { ok: true, value: null, span: { start: pos, end: pos } };
|
|
2434
|
+
return { ok: false, expected: [`peek(${combinator._tag})`], span: { start: pos, end: pos } };
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2263
2439
|
// src/compiler/build-arity.ts
|
|
2264
2440
|
var PARAM_LIST_RE = (
|
|
2265
2441
|
// (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
|
|
@@ -2358,6 +2534,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2358
2534
|
case "expect":
|
|
2359
2535
|
case "withCtx":
|
|
2360
2536
|
case "not":
|
|
2537
|
+
case "peek":
|
|
2361
2538
|
return parserHasOwnFields(d.parser, seen);
|
|
2362
2539
|
default:
|
|
2363
2540
|
return false;
|
|
@@ -2397,6 +2574,7 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2397
2574
|
case "expect":
|
|
2398
2575
|
case "withCtx":
|
|
2399
2576
|
case "not":
|
|
2577
|
+
case "peek":
|
|
2400
2578
|
case "field":
|
|
2401
2579
|
return parserHasTriviaSite(d.parser, seen);
|
|
2402
2580
|
case "choice":
|
|
@@ -2446,6 +2624,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2446
2624
|
case "expect":
|
|
2447
2625
|
case "withCtx":
|
|
2448
2626
|
case "not":
|
|
2627
|
+
case "peek":
|
|
2449
2628
|
return parserEnablesTriviaCapture(d.parser, seen);
|
|
2450
2629
|
// leaf() clears the inner CST/trivia collector and publishes only its own
|
|
2451
2630
|
// terminal result. An inner parser() capture cannot reach this node.
|
|
@@ -2821,7 +3000,7 @@ function leaf(root, fn) {
|
|
|
2821
3000
|
}
|
|
2822
3001
|
|
|
2823
3002
|
// src/version.ts
|
|
2824
|
-
var PARSEMAN_VERSION = "0.
|
|
3003
|
+
var PARSEMAN_VERSION = "0.34.0";
|
|
2825
3004
|
|
|
2826
3005
|
// src/analysis/gating.ts
|
|
2827
3006
|
var isAny = (fs) => fs.kind === "any";
|
|
@@ -2861,14 +3040,15 @@ function firstSetToString(fs) {
|
|
|
2861
3040
|
var ruleNameOf = (p) => p._ruleName;
|
|
2862
3041
|
var SUGGESTIONS = {
|
|
2863
3042
|
"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.",
|
|
2864
|
-
"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.",
|
|
2865
|
-
"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.",
|
|
3043
|
+
"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 \u2014 use peek(X), which is zero-width AND carries X first-set.",
|
|
3044
|
+
"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. A plain sepBy(item, sep) is ALSO nullable \u2014 pass { min: 1 } for a list that cannot be empty.",
|
|
2866
3045
|
"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.",
|
|
2867
3046
|
"opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
|
|
2868
3047
|
"ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
|
|
2869
3048
|
};
|
|
2870
|
-
function classifyBroadArm(arm) {
|
|
3049
|
+
function classifyBroadArm(arm, resolve) {
|
|
2871
3050
|
const seen = /* @__PURE__ */ new Set();
|
|
3051
|
+
const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
|
|
2872
3052
|
const walk2 = (p) => {
|
|
2873
3053
|
if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
|
|
2874
3054
|
seen.add(p);
|
|
@@ -2880,6 +3060,10 @@ function classifyBroadArm(arm) {
|
|
|
2880
3060
|
return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
|
|
2881
3061
|
case "not":
|
|
2882
3062
|
return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
|
|
3063
|
+
// peek() CARRIES its body's first-set, so it is only broad when the body is
|
|
3064
|
+
// (or the body is nullable) — walk in for the real cause.
|
|
3065
|
+
case "peek":
|
|
3066
|
+
return walk2(d.parser);
|
|
2883
3067
|
case "scanTo":
|
|
2884
3068
|
return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
|
|
2885
3069
|
// `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
|
|
@@ -2896,18 +3080,23 @@ function classifyBroadArm(arm) {
|
|
|
2896
3080
|
return walk2(d.parser);
|
|
2897
3081
|
case "lazy": {
|
|
2898
3082
|
const name = p._ruleName;
|
|
3083
|
+
let target;
|
|
2899
3084
|
try {
|
|
2900
|
-
|
|
2901
|
-
if (name !== void 0) return inner ? { cause: "cross-artifact-ref", detail: `via ref g.${name} \u2192 ${inner.detail}` } : null;
|
|
2902
|
-
return inner;
|
|
3085
|
+
target = d.thunk();
|
|
2903
3086
|
} catch {
|
|
2904
|
-
|
|
3087
|
+
const bound = name !== void 0 ? resolve?.(name) : void 0;
|
|
3088
|
+
if (bound === void 0)
|
|
3089
|
+
return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
|
|
3090
|
+
target = bound;
|
|
2905
3091
|
}
|
|
3092
|
+
const inner = walk2(target);
|
|
3093
|
+
if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
|
|
3094
|
+
return inner;
|
|
2906
3095
|
}
|
|
2907
3096
|
case "optional":
|
|
2908
3097
|
case "many": {
|
|
2909
3098
|
const inner = walk2(d.parser);
|
|
2910
|
-
return
|
|
3099
|
+
return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
|
|
2911
3100
|
}
|
|
2912
3101
|
case "oneOrMore":
|
|
2913
3102
|
case "transform":
|
|
@@ -2929,13 +3118,13 @@ function classifyBroadArm(arm) {
|
|
|
2929
3118
|
sawNullablePrefix = true;
|
|
2930
3119
|
continue;
|
|
2931
3120
|
}
|
|
2932
|
-
if (isAny(firstSetOf(t))) {
|
|
3121
|
+
if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve))) {
|
|
2933
3122
|
const inner = walk2(t);
|
|
2934
3123
|
if (sawNullablePrefix)
|
|
2935
|
-
return
|
|
3124
|
+
return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
|
|
2936
3125
|
return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
|
|
2937
3126
|
}
|
|
2938
|
-
if (matchesEmpty(t)) {
|
|
3127
|
+
if (matchesEmpty(t, /* @__PURE__ */ new Set(), resolve)) {
|
|
2939
3128
|
sawNullablePrefix = true;
|
|
2940
3129
|
continue;
|
|
2941
3130
|
}
|
|
@@ -2946,7 +3135,7 @@ function classifyBroadArm(arm) {
|
|
|
2946
3135
|
case "choice": {
|
|
2947
3136
|
for (const a of d.parsers) {
|
|
2948
3137
|
const r = walk2(a);
|
|
2949
|
-
if (r) return
|
|
3138
|
+
if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
|
|
2950
3139
|
}
|
|
2951
3140
|
return null;
|
|
2952
3141
|
}
|
|
@@ -2983,7 +3172,7 @@ function detectAntiPatterns(rule, arms) {
|
|
|
2983
3172
|
kind: "double-not",
|
|
2984
3173
|
rule,
|
|
2985
3174
|
armIndex: i,
|
|
2986
|
-
message: "not(not(...)) hand-rolls automatic first-char gating and MISCOMPILES among shared-first-char sibling arms (its first-set is ANY, poisoning dispatch).
|
|
3175
|
+
message: "not(not(...)) hand-rolls automatic first-char gating and MISCOMPILES among shared-first-char sibling arms (its first-set is ANY, poisoning dispatch). Prefer letting the arm lead with its consuming terminal; where a real positive lookahead IS needed, use peek(X) \u2014 zero-width like not(not(X)) but it CARRIES X's first-set, so the arm still dispatches."
|
|
2987
3176
|
});
|
|
2988
3177
|
} else {
|
|
2989
3178
|
out.push({
|
|
@@ -3006,6 +3195,9 @@ function detectAntiPatterns(rule, arms) {
|
|
|
3006
3195
|
return out;
|
|
3007
3196
|
}
|
|
3008
3197
|
function analyzeGating(entry, opts) {
|
|
3198
|
+
return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
|
|
3199
|
+
}
|
|
3200
|
+
function analyzeGatingRules(ruleMap, opts) {
|
|
3009
3201
|
const raw = [];
|
|
3010
3202
|
const antiPatterns = [];
|
|
3011
3203
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -3015,7 +3207,7 @@ function analyzeGating(entry, opts) {
|
|
|
3015
3207
|
const d = p._def;
|
|
3016
3208
|
const rule = ruleNameOf(p) ?? enclosingRule;
|
|
3017
3209
|
if (d.tag === "choice") {
|
|
3018
|
-
raw.push({ g: analyzeChoice(p, d, rule), rule });
|
|
3210
|
+
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
|
|
3019
3211
|
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
3020
3212
|
}
|
|
3021
3213
|
const rec = d;
|
|
@@ -3032,7 +3224,7 @@ function analyzeGating(entry, opts) {
|
|
|
3032
3224
|
}
|
|
3033
3225
|
for (const k of kids) visit(k, rule);
|
|
3034
3226
|
};
|
|
3035
|
-
visit(
|
|
3227
|
+
for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
|
|
3036
3228
|
const perRule = /* @__PURE__ */ new Map();
|
|
3037
3229
|
for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
|
|
3038
3230
|
const seenInRule = /* @__PURE__ */ new Map();
|
|
@@ -3048,18 +3240,19 @@ function analyzeGating(entry, opts) {
|
|
|
3048
3240
|
});
|
|
3049
3241
|
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
3050
3242
|
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
3051
|
-
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted);
|
|
3243
|
+
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted && !c.deferred);
|
|
3052
3244
|
const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
|
|
3245
|
+
const deferred = choices.filter((c) => c.gates === "no" && !c.accepted && c.deferred);
|
|
3053
3246
|
const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
|
|
3054
|
-
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, acceptedUnused, choices, antiPatterns };
|
|
3247
|
+
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, deferred, acceptedUnused, choices, antiPatterns };
|
|
3055
3248
|
}
|
|
3056
|
-
function analyzeChoice(p, d, rule) {
|
|
3249
|
+
function analyzeChoice(p, d, rule, resolve) {
|
|
3057
3250
|
const arms = d.parsers;
|
|
3058
3251
|
const shallow = arms.map((a) => a._meta.firstSet);
|
|
3059
|
-
const deep = arms.map((a) => firstSetOf(a));
|
|
3252
|
+
const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve));
|
|
3060
3253
|
const anyArms = deep.map((fs, index) => ({ fs, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
|
|
3061
|
-
const { cause, detail } = classifyBroadArm(arms[index]);
|
|
3062
|
-
return { index, cause, detail, shallowAnyOnly: false, suggestion: SUGGESTIONS[cause] };
|
|
3254
|
+
const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve);
|
|
3255
|
+
return { index, cause, detail, shallowAnyOnly: false, unresolvedExternal: unresolvedExternal === true, suggestion: SUGGESTIONS[cause] };
|
|
3063
3256
|
});
|
|
3064
3257
|
const overlaps = [];
|
|
3065
3258
|
for (let i = 0; i < deep.length; i++)
|
|
@@ -3075,10 +3268,12 @@ function analyzeChoice(p, d, rule) {
|
|
|
3075
3268
|
if (d.disjoint) gates = "yes";
|
|
3076
3269
|
else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
|
|
3077
3270
|
else gates = "no";
|
|
3271
|
+
const deferred = gates === "no" && overlaps.length === 0 && anyArms.length > 0 && anyArms.every((a) => a.unresolvedExternal);
|
|
3078
3272
|
return {
|
|
3079
3273
|
rule,
|
|
3080
3274
|
strategy: d.strategy?.tag ?? "firstMatch",
|
|
3081
3275
|
gates,
|
|
3276
|
+
deferred,
|
|
3082
3277
|
combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
|
|
3083
3278
|
anyArms,
|
|
3084
3279
|
overlaps
|
|
@@ -3245,6 +3440,7 @@ function children(def, winners) {
|
|
|
3245
3440
|
case "field":
|
|
3246
3441
|
case "grammar":
|
|
3247
3442
|
case "not":
|
|
3443
|
+
case "peek":
|
|
3248
3444
|
case "node":
|
|
3249
3445
|
case "guard":
|
|
3250
3446
|
case "withCtx":
|
|
@@ -5010,6 +5206,9 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
5010
5206
|
case "keywords":
|
|
5011
5207
|
case "guard":
|
|
5012
5208
|
case "not":
|
|
5209
|
+
// peek(): emitted under a non-capturing probe ctx and zero-width on both
|
|
5210
|
+
// outcomes, so it can never leave a partial capture behind.
|
|
5211
|
+
case "peek":
|
|
5013
5212
|
case "trivia":
|
|
5014
5213
|
case "token":
|
|
5015
5214
|
case "leaf":
|
|
@@ -5081,6 +5280,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
5081
5280
|
case "leaf":
|
|
5082
5281
|
return true;
|
|
5083
5282
|
case "not":
|
|
5283
|
+
case "peek":
|
|
5084
5284
|
case "guard":
|
|
5085
5285
|
case "trivia":
|
|
5086
5286
|
case "unknown":
|
|
@@ -5207,6 +5407,7 @@ function emitKeywordsFast(def, ctx, pos) {
|
|
|
5207
5407
|
if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
|
|
5208
5408
|
if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
|
|
5209
5409
|
if (def.caseInsensitive && def.boundary) return null;
|
|
5410
|
+
if (def.caseInsensitive && def.words.some((w) => w.split("").some((c) => c.charCodeAt(0) > 127))) return null;
|
|
5210
5411
|
let boundary = null;
|
|
5211
5412
|
if (def.boundary) {
|
|
5212
5413
|
let body = def.boundary;
|
|
@@ -5254,7 +5455,7 @@ function emitKeywords(def, ctx, pos) {
|
|
|
5254
5455
|
if (fast) return fast;
|
|
5255
5456
|
const alt = def.words.map(escapeKeywordRe).join("|");
|
|
5256
5457
|
const boundary = def.boundary ? `(?![${def.boundary}])` : "";
|
|
5257
|
-
const flags = def.caseInsensitive ? "
|
|
5458
|
+
const flags = def.caseInsensitive ? "iy" : "uy";
|
|
5258
5459
|
const source = `(?:${alt})${boundary}`;
|
|
5259
5460
|
const key = `${source}/${flags}`;
|
|
5260
5461
|
let rName = ctx.regexMap.get(key);
|
|
@@ -5400,6 +5601,7 @@ function failsAtStart(p) {
|
|
|
5400
5601
|
case "keywords":
|
|
5401
5602
|
case "guard":
|
|
5402
5603
|
case "not":
|
|
5604
|
+
case "peek":
|
|
5403
5605
|
return true;
|
|
5404
5606
|
case "transform":
|
|
5405
5607
|
case "label":
|
|
@@ -5813,14 +6015,17 @@ function emitMany(def, ctx, pos) {
|
|
|
5813
6015
|
];
|
|
5814
6016
|
const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
|
|
5815
6017
|
if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
|
|
5816
|
-
|
|
6018
|
+
for (let i = 0; i < def.min; i++) {
|
|
5817
6019
|
const firstR = emit(def.parser, ctx, curV);
|
|
5818
6020
|
stmts.push(...firstR.stmts);
|
|
5819
6021
|
if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${firstR.valueVar})`);
|
|
5820
6022
|
stmts.push(`${ind(ctx)}${curV} = ${firstR.endVar}`);
|
|
5821
6023
|
}
|
|
6024
|
+
const maxV = def.max === void 0 ? null : wantValue ? `${arrV}.length` : v(ctx, "_cnt");
|
|
6025
|
+
if (maxV !== null && !wantValue) stmts.push(`${ind(ctx)}let ${maxV} = ${def.min}`);
|
|
5822
6026
|
stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
|
|
5823
6027
|
ctx.indent++;
|
|
6028
|
+
if (maxV !== null) stmts.push(`${ind(ctx)}if (${maxV} >= ${def.max}) break`);
|
|
5824
6029
|
let itemPos = curV;
|
|
5825
6030
|
let rollback = "";
|
|
5826
6031
|
if (ctx.activeTrivia) {
|
|
@@ -5861,7 +6066,7 @@ function emitMany(def, ctx, pos) {
|
|
|
5861
6066
|
`${ind(ctx)}if (!${iterOk}) {`,
|
|
5862
6067
|
`${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
|
|
5863
6068
|
`${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
|
|
5864
|
-
...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
|
|
6069
|
+
...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : maxV !== null ? [`${ind(ctx)} ${maxV}++`] : [],
|
|
5865
6070
|
`${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
|
|
5866
6071
|
`${ind(ctx)} ${curV} = ${rrV}.end`,
|
|
5867
6072
|
`${ind(ctx)} continue`,
|
|
@@ -5874,6 +6079,7 @@ function emitMany(def, ctx, pos) {
|
|
|
5874
6079
|
stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
|
|
5875
6080
|
}
|
|
5876
6081
|
if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
|
|
6082
|
+
else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
|
|
5877
6083
|
stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
|
|
5878
6084
|
ctx.indent--;
|
|
5879
6085
|
stmts.push(`${ind(ctx)}}`);
|
|
@@ -5938,8 +6144,8 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5938
6144
|
...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
|
|
5939
6145
|
...firstStmts
|
|
5940
6146
|
];
|
|
5941
|
-
const failItem = (nextOkVar, itemPosVar,
|
|
5942
|
-
if (!rec) return [
|
|
6147
|
+
const failItem = (nextOkVar, itemPosVar, breakStmt) => {
|
|
6148
|
+
if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
|
|
5943
6149
|
const rr = v(ctx, "_rr");
|
|
5944
6150
|
return [
|
|
5945
6151
|
`${ind(ctx)}if (!${nextOkVar}) {`,
|
|
@@ -5947,10 +6153,24 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5947
6153
|
`${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
|
|
5948
6154
|
`${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
|
|
5949
6155
|
`${ind(ctx)} }`,
|
|
5950
|
-
`${ind(ctx)} ${
|
|
6156
|
+
`${ind(ctx)} ${breakStmt}`,
|
|
5951
6157
|
`${ind(ctx)}}`
|
|
5952
6158
|
];
|
|
5953
6159
|
};
|
|
6160
|
+
const itemFailBreak = (sepEndVar, sepRb, postSepRb) => def.trailing === void 0 ? `${sepRb}break` : `${postSepRb}${curV} = ${sepEndVar}; break`;
|
|
6161
|
+
const postSepMarks = () => {
|
|
6162
|
+
if (def.trailing === void 0 || !ctx.capturing) return { decl: [], rb: "" };
|
|
6163
|
+
const lv = v(ctx, "_tlv"), rw = v(ctx, "_trw"), tl = v(ctx, "_ttl"), lg = v(ctx, "_tlg");
|
|
6164
|
+
return {
|
|
6165
|
+
decl: [
|
|
6166
|
+
`${ind(ctx)}const ${lv} = _ctx._cstLeaves ? _ctx._cstLeaves.length : 0`,
|
|
6167
|
+
`${ind(ctx)}const ${rw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`,
|
|
6168
|
+
`${ind(ctx)}const ${tl} = _ctx._cstTriviaLog ? _ctx._cstTriviaLog.length : 0`,
|
|
6169
|
+
`${ind(ctx)}const ${lg} = _ctx._triviaLog ? _ctx._triviaLog.length : 0`
|
|
6170
|
+
],
|
|
6171
|
+
rb: `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${lv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${rw}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${tl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${lg}; `
|
|
6172
|
+
};
|
|
6173
|
+
};
|
|
5954
6174
|
if (rec) {
|
|
5955
6175
|
const rr0 = v(ctx, "_rr0");
|
|
5956
6176
|
const didV = v(ctx, "_did");
|
|
@@ -5976,6 +6196,7 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5976
6196
|
);
|
|
5977
6197
|
ctx.indent++;
|
|
5978
6198
|
}
|
|
6199
|
+
if (def.max !== void 0) stmts.push(`${ind(ctx)}if (${arrV}.length >= ${def.max}) break`);
|
|
5979
6200
|
let sepAtPos = curV;
|
|
5980
6201
|
if (ctx.activeTrivia) {
|
|
5981
6202
|
if (ctx.capturing) {
|
|
@@ -5996,12 +6217,14 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5996
6217
|
const rollbackToSep = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; `;
|
|
5997
6218
|
const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
|
|
5998
6219
|
stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
|
|
6220
|
+
const post = postSepMarks();
|
|
6221
|
+
stmts.push(...post.decl);
|
|
5999
6222
|
const npV = v(ctx, "_np");
|
|
6000
6223
|
stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
|
|
6001
6224
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
|
|
6002
6225
|
stmts.push(
|
|
6003
6226
|
...nextStmts,
|
|
6004
|
-
...failItem(nextOk, npV, rollbackToSep,
|
|
6227
|
+
...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
|
|
6005
6228
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6006
6229
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6007
6230
|
);
|
|
@@ -6017,7 +6240,7 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6017
6240
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
|
|
6018
6241
|
stmts.push(
|
|
6019
6242
|
...nextStmts,
|
|
6020
|
-
...failItem(nextOk, npV, "",
|
|
6243
|
+
...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
|
|
6021
6244
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6022
6245
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6023
6246
|
);
|
|
@@ -6034,10 +6257,12 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6034
6257
|
const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
|
|
6035
6258
|
stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
|
|
6036
6259
|
const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
|
|
6260
|
+
const post = postSepMarks();
|
|
6261
|
+
stmts.push(...post.decl);
|
|
6037
6262
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
|
|
6038
6263
|
stmts.push(
|
|
6039
6264
|
...nextStmts,
|
|
6040
|
-
...failItem(nextOk, sepEnd, nextRb,
|
|
6265
|
+
...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
|
|
6041
6266
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6042
6267
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6043
6268
|
);
|
|
@@ -6046,6 +6271,9 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6046
6271
|
stmts.push(`${ind(ctx)}}`);
|
|
6047
6272
|
ctx.indent--;
|
|
6048
6273
|
stmts.push(`${ind(ctx)}}`);
|
|
6274
|
+
if (def.min >= 1) {
|
|
6275
|
+
stmts.push(...emitIfFail(ctx, `${arrV}.length < ${def.min}`, failArrBody(ctx, deriveExpectedArr([def.parser]), curV)));
|
|
6276
|
+
}
|
|
6049
6277
|
return { stmts, valueVar: arrV, endVar: curV };
|
|
6050
6278
|
}
|
|
6051
6279
|
function emitScanTo(def, ctx, pos) {
|
|
@@ -6097,17 +6325,44 @@ function emitScanTo(def, ctx, pos) {
|
|
|
6097
6325
|
return { stmts, valueVar: valV, endVar: curV };
|
|
6098
6326
|
}
|
|
6099
6327
|
function emitNot(def, ctx, pos) {
|
|
6328
|
+
const sinksLive = !!ctx.capturing || !!ctx.recovery;
|
|
6329
|
+
const leaves = sinksLive ? v(ctx, "_ntl") : null;
|
|
6330
|
+
const raw = sinksLive ? v(ctx, "_ntr") : null;
|
|
6331
|
+
const tl = sinksLive ? v(ctx, "_ntt") : null;
|
|
6332
|
+
const log = sinksLive ? v(ctx, "_ntg") : null;
|
|
6333
|
+
const fields = sinksLive ? v(ctx, "_ntf") : null;
|
|
6334
|
+
const errors = sinksLive ? v(ctx, "_nte") : null;
|
|
6100
6335
|
const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
|
|
6101
6336
|
const label2 = JSON.stringify(`not(${def.parser._tag})`);
|
|
6102
6337
|
return {
|
|
6103
6338
|
stmts: [
|
|
6339
|
+
...sinksLive ? [
|
|
6340
|
+
`${ind(ctx)}const ${leaves} = _ctx._cstLeaves?.length ?? 0, ${raw} = _ctx._cstRawChildren?.length ?? 0, ${tl} = _ctx._cstTriviaLog?.length ?? 0, ${log} = _ctx._triviaLog?.length ?? 0, ${fields} = _ctx._fields?.length ?? 0, ${errors} = _ctx._errors?.length ?? 0`
|
|
6341
|
+
] : [],
|
|
6104
6342
|
...stmts,
|
|
6343
|
+
...sinksLive ? [
|
|
6344
|
+
`${ind(ctx)}if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${leaves}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${raw}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${tl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${log}; if (_ctx._fields) _ctx._fields.length = ${fields}; if (_ctx._errors) _ctx._errors.length = ${errors}`
|
|
6345
|
+
] : [],
|
|
6105
6346
|
...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
|
|
6106
6347
|
],
|
|
6107
6348
|
valueVar: "null",
|
|
6108
6349
|
endVar: pos
|
|
6109
6350
|
};
|
|
6110
6351
|
}
|
|
6352
|
+
function emitPeek(def, ctx, pos) {
|
|
6353
|
+
const probeCtx = { ...ctx, capturing: false, noHoist: true };
|
|
6354
|
+
const { stmts, okVar } = emitFallible(def.parser, probeCtx, pos, true);
|
|
6355
|
+
ctx.vars = probeCtx.vars;
|
|
6356
|
+
const label2 = JSON.stringify(`peek(${def.parser._tag})`);
|
|
6357
|
+
return {
|
|
6358
|
+
stmts: [
|
|
6359
|
+
...stmts,
|
|
6360
|
+
...emitIfFail(ctx, `!${okVar}`, failBody(ctx, label2, pos))
|
|
6361
|
+
],
|
|
6362
|
+
valueVar: "null",
|
|
6363
|
+
endVar: pos
|
|
6364
|
+
};
|
|
6365
|
+
}
|
|
6111
6366
|
function escapeTokenLiteral(value) {
|
|
6112
6367
|
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
6113
6368
|
}
|
|
@@ -6395,9 +6650,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
|
|
|
6395
6650
|
return { stmts, valueVar: vv, endVar: ev };
|
|
6396
6651
|
}
|
|
6397
6652
|
function emitLazy(p, def, ctx, pos) {
|
|
6398
|
-
const
|
|
6653
|
+
const canonical2 = ctx.ruleNames?.get(p);
|
|
6399
6654
|
const usage = ctx.lazyUsage;
|
|
6400
|
-
if (!
|
|
6655
|
+
if (!canonical2 && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
|
|
6401
6656
|
let resolved;
|
|
6402
6657
|
try {
|
|
6403
6658
|
resolved = def.thunk();
|
|
@@ -6407,7 +6662,7 @@ function emitLazy(p, def, ctx, pos) {
|
|
|
6407
6662
|
return emit(resolved, ctx, pos);
|
|
6408
6663
|
}
|
|
6409
6664
|
if (!ctx.namedParsers.has(p)) {
|
|
6410
|
-
const fnName2 =
|
|
6665
|
+
const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
|
|
6411
6666
|
ctx.namedParsers.set(p, fnName2);
|
|
6412
6667
|
let resolved;
|
|
6413
6668
|
try {
|
|
@@ -6690,6 +6945,8 @@ function emitDispatch(p, ctx, pos) {
|
|
|
6690
6945
|
}
|
|
6691
6946
|
case "not":
|
|
6692
6947
|
return emitNot(def, ctx, pos);
|
|
6948
|
+
case "peek":
|
|
6949
|
+
return emitPeek(def, ctx, pos);
|
|
6693
6950
|
case "node":
|
|
6694
6951
|
return emitNode(def, ctx, pos);
|
|
6695
6952
|
case "scanTo":
|
|
@@ -6778,6 +7035,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
6778
7035
|
case "many":
|
|
6779
7036
|
case "oneOrMore":
|
|
6780
7037
|
case "not":
|
|
7038
|
+
case "peek":
|
|
6781
7039
|
case "transform":
|
|
6782
7040
|
return hasNodeDef(d.parser, seen);
|
|
6783
7041
|
case "skip":
|
|
@@ -6829,17 +7087,18 @@ function childrenOf(def) {
|
|
|
6829
7087
|
case "field":
|
|
6830
7088
|
case "grammar":
|
|
6831
7089
|
case "not":
|
|
7090
|
+
case "peek":
|
|
6832
7091
|
case "node":
|
|
6833
7092
|
case "withCtx":
|
|
6834
7093
|
case "expect":
|
|
6835
7094
|
return [def.parser];
|
|
6836
|
-
// emitMany's
|
|
6837
|
-
// (
|
|
6838
|
-
// emit() call
|
|
7095
|
+
// emitMany's min>=1 branch and emitSepBy both codegen `def.parser` more than
|
|
7096
|
+
// once (`min` mandatory matches, then again inside the repeat loop) — each is a
|
|
7097
|
+
// real emit() call site, so the usage analysis must see one edge per site or it
|
|
6839
7098
|
// undercounts a single-use `parser` ref as inline-safe when it's actually
|
|
6840
|
-
// referenced from
|
|
7099
|
+
// referenced from several positions within this one compiled function.
|
|
6841
7100
|
case "oneOrMore":
|
|
6842
|
-
return
|
|
7101
|
+
return Array.from({ length: def.min + 1 }, () => def.parser);
|
|
6843
7102
|
case "sepBy":
|
|
6844
7103
|
return [def.parser, def.parser, def.separator];
|
|
6845
7104
|
case "skip":
|
|
@@ -6985,12 +7244,41 @@ function resolveGatingLevel(opt) {
|
|
|
6985
7244
|
return "warn";
|
|
6986
7245
|
}
|
|
6987
7246
|
function runGatingDiagnostic(combinator, opt) {
|
|
7247
|
+
return reportGating(opt, (o) => analyzeGating(combinator, o));
|
|
7248
|
+
}
|
|
7249
|
+
function runGatingDiagnosticRules(ruleMap, opt) {
|
|
7250
|
+
return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
|
|
7251
|
+
}
|
|
7252
|
+
function runFusedGatingDiagnostic(getRuleMaps, opt) {
|
|
7253
|
+
if (resolveGatingLevel(opt) === "off") return void 0;
|
|
7254
|
+
const ruleMaps = getRuleMaps();
|
|
7255
|
+
if (!ruleMaps.some((map) => hasExternalRuleRef(map))) return void 0;
|
|
7256
|
+
const winners = /* @__PURE__ */ new Map();
|
|
7257
|
+
for (const map of ruleMaps) for (const [name, rule] of map) {
|
|
7258
|
+
if (rule._def.tag === "lazy") {
|
|
7259
|
+
try {
|
|
7260
|
+
rule._def.thunk();
|
|
7261
|
+
} catch {
|
|
7262
|
+
continue;
|
|
7263
|
+
}
|
|
7264
|
+
}
|
|
7265
|
+
winners.set(name, rule);
|
|
7266
|
+
}
|
|
7267
|
+
const entries = [...winners];
|
|
7268
|
+
return reportGating(opt, (o) => {
|
|
7269
|
+
const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
|
|
7270
|
+
const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
|
|
7271
|
+
return { ...fused, ungated: fused.ungated.filter((c) => deferredHere.has(c.id)), antiPatterns: [] };
|
|
7272
|
+
});
|
|
7273
|
+
}
|
|
7274
|
+
function reportGating(opt, analyze) {
|
|
6988
7275
|
const level = resolveGatingLevel(opt);
|
|
6989
7276
|
if (level === "off") return void 0;
|
|
6990
|
-
const
|
|
7277
|
+
const obj = opt !== null && typeof opt === "object" ? opt : void 0;
|
|
7278
|
+
const analyzeOpts = obj?.accept !== void 0 || obj?.entryName !== void 0 ? { ...obj.accept !== void 0 ? { accept: obj.accept } : {}, ...obj.entryName !== void 0 ? { entryName: obj.entryName } : {} } : void 0;
|
|
6991
7279
|
let report;
|
|
6992
7280
|
try {
|
|
6993
|
-
report =
|
|
7281
|
+
report = analyze(analyzeOpts);
|
|
6994
7282
|
} catch {
|
|
6995
7283
|
return void 0;
|
|
6996
7284
|
}
|
|
@@ -7127,6 +7415,7 @@ function publicRuleWrapperSource(rule, fnSource) {
|
|
|
7127
7415
|
}
|
|
7128
7416
|
function compileLinkable(ruleMapArg, ns, opts) {
|
|
7129
7417
|
if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
|
|
7418
|
+
if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
|
|
7130
7419
|
for (const [, rule] of ruleMapArg) markUnusedValues(rule);
|
|
7131
7420
|
const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
|
|
7132
7421
|
const grammarScanSkip = opts?.scanSkip ?? ruleMapArg.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
|
|
@@ -7183,6 +7472,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7183
7472
|
fnNameToKey.set(fn, key);
|
|
7184
7473
|
}
|
|
7185
7474
|
ctx.ruleNames = ruleNames;
|
|
7475
|
+
const externalRefs = /* @__PURE__ */ new Set();
|
|
7186
7476
|
{
|
|
7187
7477
|
const scanned = /* @__PURE__ */ new Set();
|
|
7188
7478
|
const scanExternal = (p) => {
|
|
@@ -7198,11 +7488,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7198
7488
|
}
|
|
7199
7489
|
if (resolved === void 0) {
|
|
7200
7490
|
const name = p._ruleName;
|
|
7201
|
-
if (name
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7491
|
+
if (name) {
|
|
7492
|
+
externalRefs.add(p);
|
|
7493
|
+
if (!ctx.namedParsers.has(p)) {
|
|
7494
|
+
assertRuleName(name);
|
|
7495
|
+
const fn = `_r_${name}`;
|
|
7496
|
+
ruleNames.set(p, fn);
|
|
7497
|
+
ctx.namedParsers.set(p, fn);
|
|
7498
|
+
}
|
|
7206
7499
|
}
|
|
7207
7500
|
return;
|
|
7208
7501
|
}
|
|
@@ -7315,12 +7608,32 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7315
7608
|
needsEmptyTl: !!ctx.needsEmptyTl,
|
|
7316
7609
|
needsHostReads: !!ctx.needsHostReads,
|
|
7317
7610
|
hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
|
|
7318
|
-
isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule)),
|
|
7611
|
+
isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule), externalRefs),
|
|
7319
7612
|
mfFns: mfSrcs ? [] : ctx.mapFns,
|
|
7320
7613
|
buildFns: buildSrcs ? [] : ctx.buildFns
|
|
7321
7614
|
};
|
|
7322
7615
|
}
|
|
7323
|
-
function
|
|
7616
|
+
function hasExternalRuleRef(ruleMap) {
|
|
7617
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7618
|
+
const visit = (p) => {
|
|
7619
|
+
if (seen.has(p)) return false;
|
|
7620
|
+
seen.add(p);
|
|
7621
|
+
const def = p._def;
|
|
7622
|
+
if (def.tag === "lazy") {
|
|
7623
|
+
let resolved;
|
|
7624
|
+
try {
|
|
7625
|
+
resolved = def.thunk();
|
|
7626
|
+
} catch {
|
|
7627
|
+
resolved = void 0;
|
|
7628
|
+
}
|
|
7629
|
+
if (resolved === void 0) return p._ruleName !== void 0;
|
|
7630
|
+
return visit(resolved);
|
|
7631
|
+
}
|
|
7632
|
+
return childrenOf(def).some(visit);
|
|
7633
|
+
};
|
|
7634
|
+
return ruleMap.some(([, rule]) => visit(rule));
|
|
7635
|
+
}
|
|
7636
|
+
function hasSemanticReduction(roots, externalRefs) {
|
|
7324
7637
|
const seen = /* @__PURE__ */ new Set();
|
|
7325
7638
|
const visit = (parser2) => {
|
|
7326
7639
|
if (seen.has(parser2)) return false;
|
|
@@ -7331,6 +7644,7 @@ function hasSemanticReduction(roots) {
|
|
|
7331
7644
|
if (def.tag === "guard" || def.tag === "withCtx") return true;
|
|
7332
7645
|
if (def.tag === "node" && def.build !== void 0) return true;
|
|
7333
7646
|
if (def.tag === "lazy") {
|
|
7647
|
+
if (externalRefs?.has(parser2)) return false;
|
|
7334
7648
|
try {
|
|
7335
7649
|
return visit(def.thunk());
|
|
7336
7650
|
} catch {
|
|
@@ -7422,6 +7736,7 @@ function childrenOf2(def) {
|
|
|
7422
7736
|
case "label":
|
|
7423
7737
|
case "field":
|
|
7424
7738
|
case "not":
|
|
7739
|
+
case "peek":
|
|
7425
7740
|
case "node":
|
|
7426
7741
|
case "withCtx":
|
|
7427
7742
|
case "expect":
|
|
@@ -7455,9 +7770,16 @@ function lazyTarget(c) {
|
|
|
7455
7770
|
}
|
|
7456
7771
|
var Unserializable = class extends Error {
|
|
7457
7772
|
};
|
|
7458
|
-
function
|
|
7773
|
+
function repeatOpts(min, max, trailing) {
|
|
7774
|
+
const parts = [];
|
|
7775
|
+
if (min !== 0) parts.push(`min: ${min}`);
|
|
7776
|
+
if (max !== void 0) parts.push(`max: ${max}`);
|
|
7777
|
+
if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
|
|
7778
|
+
return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
|
|
7779
|
+
}
|
|
7780
|
+
function serializeRuleMap(ruleMap, scanSkip) {
|
|
7459
7781
|
try {
|
|
7460
|
-
return new Serializer(ruleMap).run();
|
|
7782
|
+
return new Serializer(ruleMap, scanSkip ?? []).run();
|
|
7461
7783
|
} catch (e) {
|
|
7462
7784
|
if (e instanceof Unserializable) {
|
|
7463
7785
|
if (process.env.PARSEMAN_IR_DEBUG) console.error(`[ir] fallback: ${e.message}`);
|
|
@@ -7558,6 +7880,7 @@ function evalRuleMapIR(ir) {
|
|
|
7558
7880
|
"optional",
|
|
7559
7881
|
"sepBy",
|
|
7560
7882
|
"not",
|
|
7883
|
+
"peek",
|
|
7561
7884
|
"node",
|
|
7562
7885
|
"parser",
|
|
7563
7886
|
"scanTo",
|
|
@@ -7590,6 +7913,7 @@ function evalRuleMapIR(ir) {
|
|
|
7590
7913
|
optional,
|
|
7591
7914
|
sepBy,
|
|
7592
7915
|
not,
|
|
7916
|
+
peek,
|
|
7593
7917
|
node,
|
|
7594
7918
|
parser,
|
|
7595
7919
|
scanTo,
|
|
@@ -7618,10 +7942,28 @@ var Serializer = class {
|
|
|
7618
7942
|
decls = [];
|
|
7619
7943
|
ruleValues = /* @__PURE__ */ new Set();
|
|
7620
7944
|
ruleMap;
|
|
7621
|
-
|
|
7945
|
+
scanSkip;
|
|
7946
|
+
constructor(ruleMap, scanSkip = []) {
|
|
7622
7947
|
this.ruleMap = ruleMap;
|
|
7948
|
+
this.scanSkip = scanSkip;
|
|
7623
7949
|
for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
|
|
7624
7950
|
}
|
|
7951
|
+
/** A grammar-level `scanSkip` unit is an opaque TERMINAL (a string, a balanced
|
|
7952
|
+
* bracket) — it must not reach a grammar-rule ref (`g[name]`), because the
|
|
7953
|
+
* serialized `scanSkip` array sits in the `rules({ scanSkip })` OPTIONS position,
|
|
7954
|
+
* outside the `(g) => …` factory where `g` is bound. A unit that does reference a
|
|
7955
|
+
* rule can't be carried in options; bail to the full-pieces fallback. */
|
|
7956
|
+
assertNoRuleRef(c, seen) {
|
|
7957
|
+
if (seen.has(c)) return;
|
|
7958
|
+
seen.add(c);
|
|
7959
|
+
if (c._def.tag === "lazy") {
|
|
7960
|
+
if (ruleNameOf2(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
|
|
7961
|
+
const target = lazyTarget(c);
|
|
7962
|
+
if (target) this.assertNoRuleRef(target, seen);
|
|
7963
|
+
return;
|
|
7964
|
+
}
|
|
7965
|
+
for (const child of childrenOf2(c._def)) this.assertNoRuleRef(child, seen);
|
|
7966
|
+
}
|
|
7625
7967
|
/** A `rules()` map wraps each rule value in a named `lazy` proxy; the real body is
|
|
7626
7968
|
* its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
|
|
7627
7969
|
* `g[name]` ref — only the top-level entry is unwrapped. */
|
|
@@ -7630,21 +7972,38 @@ var Serializer = class {
|
|
|
7630
7972
|
}
|
|
7631
7973
|
run() {
|
|
7632
7974
|
for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
|
|
7975
|
+
for (const c of this.scanSkip) {
|
|
7976
|
+
this.assertNoRuleRef(c, /* @__PURE__ */ new Set());
|
|
7977
|
+
this.analyze(c, /* @__PURE__ */ new Set());
|
|
7978
|
+
}
|
|
7633
7979
|
let n = 0;
|
|
7634
7980
|
for (const [c, count] of this.counts) {
|
|
7635
7981
|
if (this.ruleValues.has(c)) continue;
|
|
7636
7982
|
if (count >= 2 || this.selfRef.has(c)) this.constName.set(c, `_s${n++}`);
|
|
7637
7983
|
}
|
|
7984
|
+
const outerDecls = [];
|
|
7985
|
+
const innerDecls = [];
|
|
7986
|
+
let optionsArg = "";
|
|
7987
|
+
if (this.scanSkip.length > 0) {
|
|
7988
|
+
this.decls = outerDecls;
|
|
7989
|
+
const units = this.scanSkip.map((c) => this.ref(c, null));
|
|
7990
|
+
optionsArg = `{ scanSkip: [${units.join(", ")}] }, `;
|
|
7991
|
+
}
|
|
7992
|
+
this.decls = innerDecls;
|
|
7638
7993
|
const body = this.ruleMap.map(([name, c]) => ` ${JSON.stringify(name)}: ${this.wrap(this.body(c))}`).join(",\n");
|
|
7639
|
-
|
|
7994
|
+
const factory = innerDecls.length === 0 ? `(g) => ({
|
|
7640
7995
|
${body}
|
|
7641
|
-
}))
|
|
7642
|
-
|
|
7643
|
-
${this.decls.map((d) => " " + d).join("\n")}
|
|
7996
|
+
})` : `(g) => {
|
|
7997
|
+
${innerDecls.map((d) => " " + d).join("\n")}
|
|
7644
7998
|
return ({
|
|
7645
7999
|
${body}
|
|
7646
8000
|
})
|
|
7647
|
-
}
|
|
8001
|
+
}`;
|
|
8002
|
+
const call = `rules(${optionsArg}${factory})`;
|
|
8003
|
+
return outerDecls.length === 0 ? call : `(() => {
|
|
8004
|
+
${outerDecls.map((d) => " " + d).join("\n")}
|
|
8005
|
+
return ${call}
|
|
8006
|
+
})()`;
|
|
7648
8007
|
}
|
|
7649
8008
|
/** Count identity references and flag self-referential subtrees. `active` is the
|
|
7650
8009
|
* set of combinators on the current DFS path (to detect a lazy pointing back). */
|
|
@@ -7729,17 +8088,21 @@ ${body}
|
|
|
7729
8088
|
return `_gch([${items.join(", ")}])`;
|
|
7730
8089
|
}
|
|
7731
8090
|
case "many":
|
|
7732
|
-
return `many(${kid(def.parser)})`;
|
|
8091
|
+
return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
|
|
8092
|
+
// A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
|
|
8093
|
+
// it through `many` so the bounds survive (oneOrMore defaults min to 1).
|
|
7733
8094
|
case "oneOrMore":
|
|
7734
|
-
return `oneOrMore(${kid(def.parser)})`;
|
|
8095
|
+
return def.min === 1 && def.max === void 0 ? `oneOrMore(${kid(def.parser)})` : `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
|
|
7735
8096
|
case "optional":
|
|
7736
8097
|
return `optional(${kid(def.parser)})`;
|
|
7737
8098
|
case "attempt":
|
|
7738
8099
|
return `attempt(${kid(def.parser)})`;
|
|
7739
8100
|
case "sepBy":
|
|
7740
|
-
return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
|
|
8101
|
+
return `sepBy(${kid(def.parser)}, ${kid(def.separator)}${repeatOpts(def.min, def.max, def.trailing)})`;
|
|
7741
8102
|
case "not":
|
|
7742
8103
|
return `not(${kid(def.parser)})`;
|
|
8104
|
+
case "peek":
|
|
8105
|
+
return `peek(${kid(def.parser)})`;
|
|
7743
8106
|
case "trivia":
|
|
7744
8107
|
return `trivia(${kid(def.parser)})`;
|
|
7745
8108
|
case "token":
|
|
@@ -7932,6 +8295,9 @@ var COMPOSED_COVERAGE_RULES = /* @__PURE__ */ Symbol.for("parseman.composedCover
|
|
|
7932
8295
|
function isIRPiece(p) {
|
|
7933
8296
|
return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
|
|
7934
8297
|
}
|
|
8298
|
+
function carriedRuleMaps(carried) {
|
|
8299
|
+
return carried.filter(isIRPiece).map((p) => evalRuleMapIR(p.ir));
|
|
8300
|
+
}
|
|
7935
8301
|
function coverageRulesOf(carried) {
|
|
7936
8302
|
const winners = {};
|
|
7937
8303
|
for (const piece of carried) {
|
|
@@ -7994,7 +8360,8 @@ function itemCarried(item, used, trivia2) {
|
|
|
7994
8360
|
return false;
|
|
7995
8361
|
}
|
|
7996
8362
|
});
|
|
7997
|
-
const
|
|
8363
|
+
const scanSkip = entries.map(([, val]) => val._meta.grammarScanSkip).find(Boolean);
|
|
8364
|
+
const ir = serializeRuleMap(entries, scanSkip);
|
|
7998
8365
|
return ir ? [{ ns, ir }] : [linkable(map, ns, trivia2)];
|
|
7999
8366
|
}
|
|
8000
8367
|
function composingTriviaOf(items) {
|
|
@@ -8015,6 +8382,7 @@ function compose(items) {
|
|
|
8015
8382
|
const used = /* @__PURE__ */ new Set();
|
|
8016
8383
|
const trivia2 = composingTriviaOf(items);
|
|
8017
8384
|
const carried = items.flatMap((item) => itemCarried(item, used, trivia2));
|
|
8385
|
+
runFusedGatingDiagnostic(() => carriedRuleMaps(carried));
|
|
8018
8386
|
const pieces = carried.map((p) => materializePiece(p, trivia2));
|
|
8019
8387
|
const map = fuseRules(pieces);
|
|
8020
8388
|
Object.defineProperty(map, COMPOSED_PIECES, { value: carried, enumerable: false });
|
|
@@ -8847,7 +9215,7 @@ function coverageEntry(entry, collector, maps, trace) {
|
|
|
8847
9215
|
};
|
|
8848
9216
|
}
|
|
8849
9217
|
case "sepBy":
|
|
8850
|
-
return sepBy(build(def.parser), build(def.separator));
|
|
9218
|
+
return sepBy(build(def.parser), build(def.separator), { min: def.min, ...def.max === void 0 ? {} : { max: def.max }, ...def.trailing === void 0 ? {} : { trailing: def.trailing } });
|
|
8851
9219
|
case "transform":
|
|
8852
9220
|
return transform(build(def.parser), def.fn);
|
|
8853
9221
|
case "skip":
|
|
@@ -8868,6 +9236,8 @@ function coverageEntry(entry, collector, maps, trace) {
|
|
|
8868
9236
|
}, build(def.parser));
|
|
8869
9237
|
case "not":
|
|
8870
9238
|
return not(build(def.parser));
|
|
9239
|
+
case "peek":
|
|
9240
|
+
return peek(build(def.parser));
|
|
8871
9241
|
case "node": {
|
|
8872
9242
|
const opts = { ...def.unwrap ? { unwrap: true } : {}, ...def.collapse ? { collapse: true } : {}, ...def.captureTrivia ? { captureTrivia: true } : {}, ...def.trailingTrivia ? { trailingTrivia: true } : {} };
|
|
8873
9243
|
return def.type === void 0 ? node(build(def.parser), def.build, opts) : node(def.type, build(def.parser), def.build, opts);
|
|
@@ -9242,6 +9612,7 @@ export {
|
|
|
9242
9612
|
absolutize,
|
|
9243
9613
|
absolutizeCST,
|
|
9244
9614
|
analyzeGating,
|
|
9615
|
+
analyzeGatingRules,
|
|
9245
9616
|
annotateSpan,
|
|
9246
9617
|
applyEdit,
|
|
9247
9618
|
attempt,
|
|
@@ -9289,10 +9660,12 @@ export {
|
|
|
9289
9660
|
not,
|
|
9290
9661
|
offsetToLineCol,
|
|
9291
9662
|
oneOrMore,
|
|
9663
|
+
oneOrMoreSep,
|
|
9292
9664
|
optional,
|
|
9293
9665
|
parse,
|
|
9294
9666
|
parseDoc,
|
|
9295
9667
|
parser,
|
|
9668
|
+
peek,
|
|
9296
9669
|
ref,
|
|
9297
9670
|
regex,
|
|
9298
9671
|
relativize,
|