parseman 0.33.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.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 +414 -77
- 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 +414 -77
- 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 +481 -107
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +481 -107
- 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.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
absolutize: () => absolutize,
|
|
28
28
|
absolutizeCST: () => absolutizeCST,
|
|
29
29
|
analyzeGating: () => analyzeGating,
|
|
30
|
+
analyzeGatingRules: () => analyzeGatingRules,
|
|
30
31
|
annotateSpan: () => annotateSpan,
|
|
31
32
|
applyEdit: () => applyEdit,
|
|
32
33
|
attempt: () => attempt,
|
|
@@ -74,10 +75,12 @@ __export(index_exports, {
|
|
|
74
75
|
not: () => not,
|
|
75
76
|
offsetToLineCol: () => offsetToLineCol,
|
|
76
77
|
oneOrMore: () => oneOrMore,
|
|
78
|
+
oneOrMoreSep: () => oneOrMoreSep,
|
|
77
79
|
optional: () => optional,
|
|
78
80
|
parse: () => parse,
|
|
79
81
|
parseDoc: () => parseDoc,
|
|
80
82
|
parser: () => parser,
|
|
83
|
+
peek: () => peek,
|
|
81
84
|
ref: () => ref,
|
|
82
85
|
regex: () => regex,
|
|
83
86
|
relativize: () => relativize,
|
|
@@ -102,6 +105,11 @@ __export(index_exports, {
|
|
|
102
105
|
module.exports = __toCommonJS(index_exports);
|
|
103
106
|
|
|
104
107
|
// src/combinators/first-set.ts
|
|
108
|
+
function resolveNamedRef(p, resolve) {
|
|
109
|
+
if (resolve === void 0) return void 0;
|
|
110
|
+
const name = p._ruleName;
|
|
111
|
+
return name === void 0 ? void 0 : resolve(name);
|
|
112
|
+
}
|
|
105
113
|
function union(a, b) {
|
|
106
114
|
if (a.kind === "any" || b.kind === "any") return { kind: "any" };
|
|
107
115
|
if (a.kind === "empty") return b;
|
|
@@ -139,10 +147,10 @@ function any() {
|
|
|
139
147
|
function empty() {
|
|
140
148
|
return { kind: "empty" };
|
|
141
149
|
}
|
|
142
|
-
function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
150
|
+
function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
143
151
|
if (seen.has(p)) return true;
|
|
144
152
|
seen.add(p);
|
|
145
|
-
const me = (c) => matchesEmpty(c, seen);
|
|
153
|
+
const me = (c) => matchesEmpty(c, seen, resolve);
|
|
146
154
|
const d = p._def;
|
|
147
155
|
switch (d.tag) {
|
|
148
156
|
case "literal":
|
|
@@ -159,8 +167,16 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
159
167
|
case "many":
|
|
160
168
|
case "optional":
|
|
161
169
|
case "not":
|
|
170
|
+
case "peek":
|
|
162
171
|
return true;
|
|
163
172
|
// zero repetitions / absent / lookahead
|
|
173
|
+
// Default `sepBy` is `(item (sep item)*)?` — it MATCHES THE EMPTY STRING.
|
|
174
|
+
// Any `min >= 1` requires that many ITEMS, so it is nullable only when the
|
|
175
|
+
// item is. (Keying this off `min === 1` reported every `{ min: 2 }` list as
|
|
176
|
+
// nullable — safe, but wrong, and it put a bogus `nullable-prefix` note on
|
|
177
|
+
// the gating diagnostic for a list that can never match empty.)
|
|
178
|
+
case "sepBy":
|
|
179
|
+
return d.min >= 1 ? me(d.parser) : true;
|
|
164
180
|
case "oneOrMore":
|
|
165
181
|
return me(d.parser);
|
|
166
182
|
case "sequence":
|
|
@@ -184,7 +200,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
184
200
|
try {
|
|
185
201
|
return me(d.thunk());
|
|
186
202
|
} catch {
|
|
187
|
-
|
|
203
|
+
const t = resolveNamedRef(p, resolve);
|
|
204
|
+
return t ? me(t) : true;
|
|
188
205
|
}
|
|
189
206
|
default:
|
|
190
207
|
return true;
|
|
@@ -193,18 +210,45 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
193
210
|
function isZeroWidthAssertion(p) {
|
|
194
211
|
return p._def.tag === "not";
|
|
195
212
|
}
|
|
213
|
+
function isPositiveLookahead(p) {
|
|
214
|
+
return p._def.tag === "peek";
|
|
215
|
+
}
|
|
216
|
+
function narrowBy(acc, constraint) {
|
|
217
|
+
if (constraint.kind === "any") return acc;
|
|
218
|
+
if (acc === null) return constraint;
|
|
219
|
+
if (acc.kind === "any") return constraint;
|
|
220
|
+
if (acc.kind === "empty" || constraint.kind === "empty") return { kind: "empty" };
|
|
221
|
+
const ranges = [];
|
|
222
|
+
for (const a of acc.ranges) for (const b of constraint.ranges) {
|
|
223
|
+
const lo = Math.max(a.lo, b.lo);
|
|
224
|
+
const hi = Math.min(a.hi, b.hi);
|
|
225
|
+
if (lo <= hi) ranges.push({ lo, hi });
|
|
226
|
+
}
|
|
227
|
+
return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
|
|
228
|
+
}
|
|
229
|
+
function applyAssertion(fs, assertion) {
|
|
230
|
+
if (assertion === null) return fs;
|
|
231
|
+
if (fs.kind === "empty") return assertion;
|
|
232
|
+
return narrowBy(fs, assertion) ?? fs;
|
|
233
|
+
}
|
|
196
234
|
function sequenceFirstSet(parsers) {
|
|
197
235
|
let fs = empty();
|
|
236
|
+
let assertion = null;
|
|
198
237
|
for (const p of parsers) {
|
|
238
|
+
if (isPositiveLookahead(p)) {
|
|
239
|
+
assertion = narrowBy(assertion, p._meta.firstSet);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
199
242
|
if (!isZeroWidthAssertion(p)) fs = union(fs, p._meta.firstSet);
|
|
200
|
-
if (!matchesEmpty(p)) return fs;
|
|
243
|
+
if (!matchesEmpty(p)) return applyAssertion(fs, assertion);
|
|
201
244
|
}
|
|
202
|
-
return fs;
|
|
245
|
+
return applyAssertion(fs, assertion);
|
|
203
246
|
}
|
|
204
|
-
function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
247
|
+
function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
205
248
|
if (seen.has(p)) return any();
|
|
206
249
|
seen.add(p);
|
|
207
|
-
const fs = (c) => firstSetOf(c, seen);
|
|
250
|
+
const fs = (c) => firstSetOf(c, seen, resolve);
|
|
251
|
+
const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve);
|
|
208
252
|
const d = p._def;
|
|
209
253
|
switch (d.tag) {
|
|
210
254
|
case "literal":
|
|
@@ -216,7 +260,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
216
260
|
try {
|
|
217
261
|
return fs(d.thunk());
|
|
218
262
|
} catch {
|
|
219
|
-
|
|
263
|
+
const t = resolveNamedRef(p, resolve);
|
|
264
|
+
return t ? fs(t) : any();
|
|
220
265
|
}
|
|
221
266
|
case "choice": {
|
|
222
267
|
let out = empty();
|
|
@@ -225,11 +270,20 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
225
270
|
}
|
|
226
271
|
case "sequence": {
|
|
227
272
|
let out = empty();
|
|
273
|
+
let assertion = null;
|
|
228
274
|
for (const term of d.parsers) {
|
|
275
|
+
if (isPositiveLookahead(term)) {
|
|
276
|
+
assertion = narrowBy(assertion, fs(term));
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
229
279
|
if (!isZeroWidthAssertion(term)) out = union(out, fs(term));
|
|
230
|
-
if (!
|
|
280
|
+
if (!empties(term)) return applyAssertion(out, assertion);
|
|
231
281
|
}
|
|
232
|
-
return out;
|
|
282
|
+
return applyAssertion(out, assertion);
|
|
283
|
+
}
|
|
284
|
+
case "peek": {
|
|
285
|
+
const inner = d.parser;
|
|
286
|
+
return empties(inner) ? any() : fs(inner);
|
|
233
287
|
}
|
|
234
288
|
case "oneOrMore":
|
|
235
289
|
case "many":
|
|
@@ -245,6 +299,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
245
299
|
return fs(d.parser);
|
|
246
300
|
case "sepBy":
|
|
247
301
|
return fs(d.parser);
|
|
302
|
+
// both min 0 and min 1 start with the item
|
|
248
303
|
case "skip":
|
|
249
304
|
return fs(d.main);
|
|
250
305
|
default:
|
|
@@ -929,9 +984,46 @@ function firstSetFromRegex(source) {
|
|
|
929
984
|
return { firstSet: firstSet(ast), canMatchNewline: firstCanBeNewline(ast) };
|
|
930
985
|
}
|
|
931
986
|
|
|
987
|
+
// src/combinators/case-fold.ts
|
|
988
|
+
function canonical(ch2) {
|
|
989
|
+
const u = ch2.toUpperCase();
|
|
990
|
+
if (u.length !== 1) return ch2;
|
|
991
|
+
if (ch2.codePointAt(0) >= 128 && u.codePointAt(0) < 128) return ch2;
|
|
992
|
+
return u;
|
|
993
|
+
}
|
|
994
|
+
var foldIndex = null;
|
|
995
|
+
function buildFoldIndex() {
|
|
996
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
997
|
+
for (let cp = 0; cp < 65536; cp++) {
|
|
998
|
+
if (cp >= 55296 && cp <= 57343) continue;
|
|
999
|
+
const key = canonical(String.fromCharCode(cp));
|
|
1000
|
+
const bucket = byCanonical.get(key);
|
|
1001
|
+
if (bucket) bucket.push(cp);
|
|
1002
|
+
else byCanonical.set(key, [cp]);
|
|
1003
|
+
}
|
|
1004
|
+
const index = /* @__PURE__ */ new Map();
|
|
1005
|
+
for (const members of byCanonical.values()) {
|
|
1006
|
+
if (members.length < 2) continue;
|
|
1007
|
+
for (const cp of members) index.set(cp, members);
|
|
1008
|
+
}
|
|
1009
|
+
return index;
|
|
1010
|
+
}
|
|
1011
|
+
function caseFoldVariants(cp) {
|
|
1012
|
+
if (cp < 128) {
|
|
1013
|
+
if (cp >= 65 && cp <= 90) return [cp, cp + 32];
|
|
1014
|
+
if (cp >= 97 && cp <= 122) return [cp, cp - 32];
|
|
1015
|
+
return [cp];
|
|
1016
|
+
}
|
|
1017
|
+
if (cp > 65535) return [cp];
|
|
1018
|
+
foldIndex ??= buildFoldIndex();
|
|
1019
|
+
return foldIndex.get(cp) ?? [cp];
|
|
1020
|
+
}
|
|
1021
|
+
|
|
932
1022
|
// src/combinators/keywords.ts
|
|
933
|
-
function word(str,
|
|
934
|
-
|
|
1023
|
+
function word(str, boundaryOrOpts, opts) {
|
|
1024
|
+
const boundary = typeof boundaryOrOpts === "string" ? boundaryOrOpts : "_0-9A-Za-z";
|
|
1025
|
+
const rest = typeof boundaryOrOpts === "object" && boundaryOrOpts !== null ? boundaryOrOpts : opts;
|
|
1026
|
+
return keywords([str], { ...rest, boundary });
|
|
935
1027
|
}
|
|
936
1028
|
function makeWord(boundary = "_0-9A-Za-z") {
|
|
937
1029
|
return (str) => keywords([str], { boundary });
|
|
@@ -943,7 +1035,7 @@ function keywords(words, opts = {}) {
|
|
|
943
1035
|
const sorted = [...new Set(words)].sort((a, b) => b.length - a.length);
|
|
944
1036
|
const alt = sorted.map(escapeRe).join("|");
|
|
945
1037
|
const boundary = opts.boundary ? `(?![${opts.boundary}])` : "";
|
|
946
|
-
const flags = opts.caseInsensitive ? "
|
|
1038
|
+
const flags = opts.caseInsensitive ? "iy" : "uy";
|
|
947
1039
|
const re = new RegExp(`(?:${alt})${boundary}`, flags);
|
|
948
1040
|
let firstSet2 = empty();
|
|
949
1041
|
for (const w of sorted) {
|
|
@@ -951,10 +1043,7 @@ function keywords(words, opts = {}) {
|
|
|
951
1043
|
const cp = w.codePointAt(0);
|
|
952
1044
|
firstSet2 = union(firstSet2, fromChar(cp));
|
|
953
1045
|
if (opts.caseInsensitive) {
|
|
954
|
-
const
|
|
955
|
-
const l = String.fromCodePoint(cp).toLowerCase().codePointAt(0);
|
|
956
|
-
if (u !== void 0) firstSet2 = union(firstSet2, fromChar(u));
|
|
957
|
-
if (l !== void 0) firstSet2 = union(firstSet2, fromChar(l));
|
|
1046
|
+
for (const v2 of caseFoldVariants(cp)) firstSet2 = union(firstSet2, fromChar(v2));
|
|
958
1047
|
}
|
|
959
1048
|
}
|
|
960
1049
|
const meta = { firstSet: firstSet2, canMatchNewline: false, isTrivia: false };
|
|
@@ -1103,6 +1192,7 @@ function deriveExpected(c) {
|
|
|
1103
1192
|
case "oneOrMore":
|
|
1104
1193
|
case "transform":
|
|
1105
1194
|
case "not":
|
|
1195
|
+
case "peek":
|
|
1106
1196
|
return deriveExpected(def.parser);
|
|
1107
1197
|
case "lazy": {
|
|
1108
1198
|
const name = c._ruleName;
|
|
@@ -1576,6 +1666,13 @@ function rollbackTrivia(ctx, mark) {
|
|
|
1576
1666
|
rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
|
|
1577
1667
|
if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
|
|
1578
1668
|
}
|
|
1669
|
+
function saveLookaheadMark(ctx) {
|
|
1670
|
+
return { ...saveTriviaMark(ctx), probeBest: ctx._probe ? ctx._probe.best : null };
|
|
1671
|
+
}
|
|
1672
|
+
function rollbackLookahead(ctx, mark) {
|
|
1673
|
+
rollbackTrivia(ctx, mark);
|
|
1674
|
+
if (ctx._probe) ctx._probe.best = mark.probeBest;
|
|
1675
|
+
}
|
|
1579
1676
|
function scanWithLabels(input, cur, ctx) {
|
|
1580
1677
|
const triviaP = ctx.trivia;
|
|
1581
1678
|
const spec = analyzeLabeledTrivia(triviaP);
|
|
@@ -1973,13 +2070,26 @@ function repItem(combinator, input, cur, ctx, guardable) {
|
|
|
1973
2070
|
}
|
|
1974
2071
|
return { value: result.value, end: result.span.end };
|
|
1975
2072
|
}
|
|
1976
|
-
function
|
|
2073
|
+
function resolveBounds(what, opts) {
|
|
2074
|
+
const min = opts.min ?? 0;
|
|
2075
|
+
const max = opts.max ?? Infinity;
|
|
2076
|
+
const bad = (msg) => {
|
|
2077
|
+
throw new RangeError(`parseman: ${what} ${msg}`);
|
|
2078
|
+
};
|
|
2079
|
+
if (!Number.isInteger(min) || min < 0) bad(`min must be a non-negative integer (got ${String(opts.min)})`);
|
|
2080
|
+
if (max !== Infinity && (!Number.isInteger(max) || max < 1)) bad(`max must be a positive integer (got ${String(opts.max)})`);
|
|
2081
|
+
if (max < min) bad(`max (${max}) is less than min (${min}) \u2014 the combinator could never succeed`);
|
|
2082
|
+
return { min, max };
|
|
2083
|
+
}
|
|
2084
|
+
function many(combinator, opts = {}) {
|
|
2085
|
+
const { min, max } = resolveBounds("many()", opts);
|
|
2086
|
+
if (min >= 1) return atLeast(combinator, min, max);
|
|
1977
2087
|
const meta = {
|
|
1978
2088
|
firstSet: combinator._meta.firstSet,
|
|
1979
2089
|
canMatchNewline: combinator._meta.canMatchNewline,
|
|
1980
2090
|
isTrivia: false
|
|
1981
2091
|
};
|
|
1982
|
-
const def = { tag: "many", parser: combinator, min: 0 };
|
|
2092
|
+
const def = { tag: "many", parser: combinator, min: 0, ...max === Infinity ? {} : { max } };
|
|
1983
2093
|
let expected;
|
|
1984
2094
|
const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
|
|
1985
2095
|
return {
|
|
@@ -1989,7 +2099,9 @@ function many(combinator) {
|
|
|
1989
2099
|
parse(input, pos, ctx) {
|
|
1990
2100
|
const values = def.valueUnused ? void 0 : [];
|
|
1991
2101
|
let cur = pos;
|
|
2102
|
+
let count = 0;
|
|
1992
2103
|
while (cur < input.length) {
|
|
2104
|
+
if (count >= max) break;
|
|
1993
2105
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1994
2106
|
if (item === "stop") break;
|
|
1995
2107
|
if ("fail" in item) {
|
|
@@ -2000,23 +2112,28 @@ function many(combinator) {
|
|
|
2000
2112
|
const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
|
|
2001
2113
|
if (values !== void 0) values.push(error);
|
|
2002
2114
|
captureError(ctx, error);
|
|
2115
|
+
count++;
|
|
2003
2116
|
cur = end;
|
|
2004
2117
|
continue;
|
|
2005
2118
|
}
|
|
2006
2119
|
if (values !== void 0) values.push(item.value);
|
|
2120
|
+
count++;
|
|
2007
2121
|
cur = item.end;
|
|
2008
2122
|
}
|
|
2009
2123
|
return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
|
|
2010
2124
|
}
|
|
2011
2125
|
};
|
|
2012
2126
|
}
|
|
2013
|
-
function oneOrMore(combinator) {
|
|
2127
|
+
function oneOrMore(combinator, opts = {}) {
|
|
2128
|
+
return many(combinator, { ...opts, min: opts.min ?? 1 });
|
|
2129
|
+
}
|
|
2130
|
+
function atLeast(combinator, min, max) {
|
|
2014
2131
|
const meta = {
|
|
2015
2132
|
firstSet: combinator._meta.firstSet,
|
|
2016
2133
|
canMatchNewline: combinator._meta.canMatchNewline,
|
|
2017
2134
|
isTrivia: false
|
|
2018
2135
|
};
|
|
2019
|
-
const def = { tag: "oneOrMore", parser: combinator, min:
|
|
2136
|
+
const def = { tag: "oneOrMore", parser: combinator, min, ...max === Infinity ? {} : { max } };
|
|
2020
2137
|
let expected;
|
|
2021
2138
|
const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
|
|
2022
2139
|
return {
|
|
@@ -2028,7 +2145,19 @@ function oneOrMore(combinator) {
|
|
|
2028
2145
|
if (!first.ok) return first;
|
|
2029
2146
|
const values = def.valueUnused ? void 0 : [first.value];
|
|
2030
2147
|
let cur = first.span.end;
|
|
2148
|
+
let count = 1;
|
|
2149
|
+
while (count < min) {
|
|
2150
|
+
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
2151
|
+
if (item === "stop" || "fail" in item) {
|
|
2152
|
+
expected ??= deriveExpected(combinator);
|
|
2153
|
+
return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
|
|
2154
|
+
}
|
|
2155
|
+
if (values !== void 0) values.push(item.value);
|
|
2156
|
+
cur = item.end;
|
|
2157
|
+
count++;
|
|
2158
|
+
}
|
|
2031
2159
|
while (cur < input.length) {
|
|
2160
|
+
if (count >= max) break;
|
|
2032
2161
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
2033
2162
|
if (item === "stop") break;
|
|
2034
2163
|
if ("fail" in item) {
|
|
@@ -2039,10 +2168,12 @@ function oneOrMore(combinator) {
|
|
|
2039
2168
|
const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
|
|
2040
2169
|
if (values !== void 0) values.push(error);
|
|
2041
2170
|
captureError(ctx, error);
|
|
2171
|
+
count++;
|
|
2042
2172
|
cur = end;
|
|
2043
2173
|
continue;
|
|
2044
2174
|
}
|
|
2045
2175
|
if (values !== void 0) values.push(item.value);
|
|
2176
|
+
count++;
|
|
2046
2177
|
cur = item.end;
|
|
2047
2178
|
}
|
|
2048
2179
|
return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
|
|
@@ -2072,17 +2203,33 @@ function optional(combinator) {
|
|
|
2072
2203
|
}
|
|
2073
2204
|
};
|
|
2074
2205
|
}
|
|
2075
|
-
function
|
|
2206
|
+
function oneOrMoreSep(combinator, separator, opts = {}) {
|
|
2207
|
+
return sepBy(combinator, separator, { ...opts, min: opts.min ?? 1 });
|
|
2208
|
+
}
|
|
2209
|
+
function sepBy(combinator, separator, opts = {}) {
|
|
2210
|
+
const { min, max } = resolveBounds("sepBy()", opts);
|
|
2211
|
+
const trailing = opts.trailing ?? "forbid";
|
|
2076
2212
|
const meta = {
|
|
2077
2213
|
firstSet: combinator._meta.firstSet,
|
|
2078
2214
|
canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
|
|
2079
2215
|
isTrivia: false
|
|
2080
2216
|
};
|
|
2081
2217
|
let expected;
|
|
2218
|
+
const failAt2 = (at) => {
|
|
2219
|
+
expected ??= deriveExpected(combinator);
|
|
2220
|
+
return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: at, end: at } };
|
|
2221
|
+
};
|
|
2082
2222
|
return {
|
|
2083
2223
|
_tag: "sepBy",
|
|
2084
2224
|
_meta: meta,
|
|
2085
|
-
_def: {
|
|
2225
|
+
_def: {
|
|
2226
|
+
tag: "sepBy",
|
|
2227
|
+
parser: combinator,
|
|
2228
|
+
separator,
|
|
2229
|
+
min,
|
|
2230
|
+
...max === Infinity ? {} : { max },
|
|
2231
|
+
...trailing === "forbid" ? {} : { trailing }
|
|
2232
|
+
},
|
|
2086
2233
|
parse(input, pos, ctx) {
|
|
2087
2234
|
const first = combinator.parse(input, pos, ctx);
|
|
2088
2235
|
const values = [];
|
|
@@ -2093,6 +2240,7 @@ function sepBy(combinator, separator) {
|
|
|
2093
2240
|
} else {
|
|
2094
2241
|
const term = ctx._tolerant ? ctx._sync : void 0;
|
|
2095
2242
|
if (term === void 0 || matchesAt(term, input, pos, ctx)) {
|
|
2243
|
+
if (min >= 1) return failAt2(pos);
|
|
2096
2244
|
return { ok: true, value: [], span: { start: pos, end: pos } };
|
|
2097
2245
|
}
|
|
2098
2246
|
expected ??= deriveExpected(combinator);
|
|
@@ -2102,6 +2250,7 @@ function sepBy(combinator, separator) {
|
|
|
2102
2250
|
cur = rec.end;
|
|
2103
2251
|
}
|
|
2104
2252
|
while (cur < input.length) {
|
|
2253
|
+
if (values.length >= max) break;
|
|
2105
2254
|
const loopMark = saveTriviaMark(ctx);
|
|
2106
2255
|
let sepPos = cur;
|
|
2107
2256
|
if (ctx.trivia) {
|
|
@@ -2118,6 +2267,7 @@ function sepBy(combinator, separator) {
|
|
|
2118
2267
|
rollbackTrivia(ctx, loopMark);
|
|
2119
2268
|
break;
|
|
2120
2269
|
}
|
|
2270
|
+
const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
|
|
2121
2271
|
let nextPos = sep.span.end;
|
|
2122
2272
|
if (ctx.trivia) {
|
|
2123
2273
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
@@ -2139,12 +2289,18 @@ function sepBy(combinator, separator) {
|
|
|
2139
2289
|
cur = rec.end;
|
|
2140
2290
|
continue;
|
|
2141
2291
|
}
|
|
2292
|
+
if (sepMark !== void 0) {
|
|
2293
|
+
rollbackTrivia(ctx, sepMark);
|
|
2294
|
+
cur = sep.span.end;
|
|
2295
|
+
break;
|
|
2296
|
+
}
|
|
2142
2297
|
rollbackTrivia(ctx, loopMark);
|
|
2143
2298
|
break;
|
|
2144
2299
|
}
|
|
2145
2300
|
values.push(next.value);
|
|
2146
2301
|
cur = next.span.end;
|
|
2147
2302
|
}
|
|
2303
|
+
if (values.length < min) return failAt2(cur);
|
|
2148
2304
|
return { ok: true, value: values, span: { start: pos, end: cur } };
|
|
2149
2305
|
}
|
|
2150
2306
|
};
|
|
@@ -2234,6 +2390,7 @@ function markUnusedValues(root) {
|
|
|
2234
2390
|
visit(def.separator, false);
|
|
2235
2391
|
return;
|
|
2236
2392
|
case "not":
|
|
2393
|
+
case "peek":
|
|
2237
2394
|
visit(def.parser, false);
|
|
2238
2395
|
return;
|
|
2239
2396
|
case "grammar":
|
|
@@ -2352,9 +2509,9 @@ function not(combinator) {
|
|
|
2352
2509
|
_meta: meta,
|
|
2353
2510
|
_def: { tag: "not", parser: combinator },
|
|
2354
2511
|
parse(input, pos, ctx) {
|
|
2355
|
-
const mark =
|
|
2512
|
+
const mark = saveLookaheadMark(ctx);
|
|
2356
2513
|
const result = combinator.parse(input, pos, ctx);
|
|
2357
|
-
|
|
2514
|
+
rollbackLookahead(ctx, mark);
|
|
2358
2515
|
if (result.ok) {
|
|
2359
2516
|
return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
|
|
2360
2517
|
}
|
|
@@ -2363,6 +2520,28 @@ function not(combinator) {
|
|
|
2363
2520
|
};
|
|
2364
2521
|
}
|
|
2365
2522
|
|
|
2523
|
+
// src/combinators/peek.ts
|
|
2524
|
+
function peek(combinator) {
|
|
2525
|
+
const meta = {
|
|
2526
|
+
// A nullable body imposes no first-char constraint — `any()`, not its set.
|
|
2527
|
+
firstSet: matchesEmpty(combinator) ? any() : combinator._meta.firstSet,
|
|
2528
|
+
canMatchNewline: false,
|
|
2529
|
+
isTrivia: false
|
|
2530
|
+
};
|
|
2531
|
+
return {
|
|
2532
|
+
_tag: "peek",
|
|
2533
|
+
_meta: meta,
|
|
2534
|
+
_def: { tag: "peek", parser: combinator },
|
|
2535
|
+
parse(input, pos, ctx) {
|
|
2536
|
+
const mark = saveLookaheadMark(ctx);
|
|
2537
|
+
const result = combinator.parse(input, pos, ctx);
|
|
2538
|
+
rollbackLookahead(ctx, mark);
|
|
2539
|
+
if (result.ok) return { ok: true, value: null, span: { start: pos, end: pos } };
|
|
2540
|
+
return { ok: false, expected: [`peek(${combinator._tag})`], span: { start: pos, end: pos } };
|
|
2541
|
+
}
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2366
2545
|
// src/compiler/build-arity.ts
|
|
2367
2546
|
var PARAM_LIST_RE = (
|
|
2368
2547
|
// (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
|
|
@@ -2461,6 +2640,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2461
2640
|
case "expect":
|
|
2462
2641
|
case "withCtx":
|
|
2463
2642
|
case "not":
|
|
2643
|
+
case "peek":
|
|
2464
2644
|
return parserHasOwnFields(d.parser, seen);
|
|
2465
2645
|
default:
|
|
2466
2646
|
return false;
|
|
@@ -2500,6 +2680,7 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2500
2680
|
case "expect":
|
|
2501
2681
|
case "withCtx":
|
|
2502
2682
|
case "not":
|
|
2683
|
+
case "peek":
|
|
2503
2684
|
case "field":
|
|
2504
2685
|
return parserHasTriviaSite(d.parser, seen);
|
|
2505
2686
|
case "choice":
|
|
@@ -2549,6 +2730,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2549
2730
|
case "expect":
|
|
2550
2731
|
case "withCtx":
|
|
2551
2732
|
case "not":
|
|
2733
|
+
case "peek":
|
|
2552
2734
|
return parserEnablesTriviaCapture(d.parser, seen);
|
|
2553
2735
|
// leaf() clears the inner CST/trivia collector and publishes only its own
|
|
2554
2736
|
// terminal result. An inner parser() capture cannot reach this node.
|
|
@@ -2924,7 +3106,7 @@ function leaf(root, fn) {
|
|
|
2924
3106
|
}
|
|
2925
3107
|
|
|
2926
3108
|
// src/version.ts
|
|
2927
|
-
var PARSEMAN_VERSION = "0.
|
|
3109
|
+
var PARSEMAN_VERSION = "0.34.0";
|
|
2928
3110
|
|
|
2929
3111
|
// src/analysis/gating.ts
|
|
2930
3112
|
var isAny = (fs) => fs.kind === "any";
|
|
@@ -2964,14 +3146,15 @@ function firstSetToString(fs) {
|
|
|
2964
3146
|
var ruleNameOf = (p) => p._ruleName;
|
|
2965
3147
|
var SUGGESTIONS = {
|
|
2966
3148
|
"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.",
|
|
3149
|
+
"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.",
|
|
3150
|
+
"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.",
|
|
2969
3151
|
"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
3152
|
"opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
|
|
2971
3153
|
"ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
|
|
2972
3154
|
};
|
|
2973
|
-
function classifyBroadArm(arm) {
|
|
3155
|
+
function classifyBroadArm(arm, resolve) {
|
|
2974
3156
|
const seen = /* @__PURE__ */ new Set();
|
|
3157
|
+
const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
|
|
2975
3158
|
const walk2 = (p) => {
|
|
2976
3159
|
if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
|
|
2977
3160
|
seen.add(p);
|
|
@@ -2983,6 +3166,10 @@ function classifyBroadArm(arm) {
|
|
|
2983
3166
|
return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
|
|
2984
3167
|
case "not":
|
|
2985
3168
|
return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
|
|
3169
|
+
// peek() CARRIES its body's first-set, so it is only broad when the body is
|
|
3170
|
+
// (or the body is nullable) — walk in for the real cause.
|
|
3171
|
+
case "peek":
|
|
3172
|
+
return walk2(d.parser);
|
|
2986
3173
|
case "scanTo":
|
|
2987
3174
|
return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
|
|
2988
3175
|
// `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
|
|
@@ -2999,18 +3186,23 @@ function classifyBroadArm(arm) {
|
|
|
2999
3186
|
return walk2(d.parser);
|
|
3000
3187
|
case "lazy": {
|
|
3001
3188
|
const name = p._ruleName;
|
|
3189
|
+
let target;
|
|
3002
3190
|
try {
|
|
3003
|
-
|
|
3004
|
-
if (name !== void 0) return inner ? { cause: "cross-artifact-ref", detail: `via ref g.${name} \u2192 ${inner.detail}` } : null;
|
|
3005
|
-
return inner;
|
|
3191
|
+
target = d.thunk();
|
|
3006
3192
|
} catch {
|
|
3007
|
-
|
|
3193
|
+
const bound = name !== void 0 ? resolve?.(name) : void 0;
|
|
3194
|
+
if (bound === void 0)
|
|
3195
|
+
return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
|
|
3196
|
+
target = bound;
|
|
3008
3197
|
}
|
|
3198
|
+
const inner = walk2(target);
|
|
3199
|
+
if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
|
|
3200
|
+
return inner;
|
|
3009
3201
|
}
|
|
3010
3202
|
case "optional":
|
|
3011
3203
|
case "many": {
|
|
3012
3204
|
const inner = walk2(d.parser);
|
|
3013
|
-
return
|
|
3205
|
+
return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
|
|
3014
3206
|
}
|
|
3015
3207
|
case "oneOrMore":
|
|
3016
3208
|
case "transform":
|
|
@@ -3032,13 +3224,13 @@ function classifyBroadArm(arm) {
|
|
|
3032
3224
|
sawNullablePrefix = true;
|
|
3033
3225
|
continue;
|
|
3034
3226
|
}
|
|
3035
|
-
if (isAny(firstSetOf(t))) {
|
|
3227
|
+
if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve))) {
|
|
3036
3228
|
const inner = walk2(t);
|
|
3037
3229
|
if (sawNullablePrefix)
|
|
3038
|
-
return
|
|
3230
|
+
return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
|
|
3039
3231
|
return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
|
|
3040
3232
|
}
|
|
3041
|
-
if (matchesEmpty(t)) {
|
|
3233
|
+
if (matchesEmpty(t, /* @__PURE__ */ new Set(), resolve)) {
|
|
3042
3234
|
sawNullablePrefix = true;
|
|
3043
3235
|
continue;
|
|
3044
3236
|
}
|
|
@@ -3049,7 +3241,7 @@ function classifyBroadArm(arm) {
|
|
|
3049
3241
|
case "choice": {
|
|
3050
3242
|
for (const a of d.parsers) {
|
|
3051
3243
|
const r = walk2(a);
|
|
3052
|
-
if (r) return
|
|
3244
|
+
if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
|
|
3053
3245
|
}
|
|
3054
3246
|
return null;
|
|
3055
3247
|
}
|
|
@@ -3086,7 +3278,7 @@ function detectAntiPatterns(rule, arms) {
|
|
|
3086
3278
|
kind: "double-not",
|
|
3087
3279
|
rule,
|
|
3088
3280
|
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).
|
|
3281
|
+
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."
|
|
3090
3282
|
});
|
|
3091
3283
|
} else {
|
|
3092
3284
|
out.push({
|
|
@@ -3109,6 +3301,9 @@ function detectAntiPatterns(rule, arms) {
|
|
|
3109
3301
|
return out;
|
|
3110
3302
|
}
|
|
3111
3303
|
function analyzeGating(entry, opts) {
|
|
3304
|
+
return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
|
|
3305
|
+
}
|
|
3306
|
+
function analyzeGatingRules(ruleMap, opts) {
|
|
3112
3307
|
const raw = [];
|
|
3113
3308
|
const antiPatterns = [];
|
|
3114
3309
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -3118,7 +3313,7 @@ function analyzeGating(entry, opts) {
|
|
|
3118
3313
|
const d = p._def;
|
|
3119
3314
|
const rule = ruleNameOf(p) ?? enclosingRule;
|
|
3120
3315
|
if (d.tag === "choice") {
|
|
3121
|
-
raw.push({ g: analyzeChoice(p, d, rule), rule });
|
|
3316
|
+
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
|
|
3122
3317
|
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
3123
3318
|
}
|
|
3124
3319
|
const rec = d;
|
|
@@ -3135,7 +3330,7 @@ function analyzeGating(entry, opts) {
|
|
|
3135
3330
|
}
|
|
3136
3331
|
for (const k of kids) visit(k, rule);
|
|
3137
3332
|
};
|
|
3138
|
-
visit(
|
|
3333
|
+
for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
|
|
3139
3334
|
const perRule = /* @__PURE__ */ new Map();
|
|
3140
3335
|
for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
|
|
3141
3336
|
const seenInRule = /* @__PURE__ */ new Map();
|
|
@@ -3151,18 +3346,19 @@ function analyzeGating(entry, opts) {
|
|
|
3151
3346
|
});
|
|
3152
3347
|
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
3153
3348
|
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
3154
|
-
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted);
|
|
3349
|
+
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted && !c.deferred);
|
|
3155
3350
|
const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
|
|
3351
|
+
const deferred = choices.filter((c) => c.gates === "no" && !c.accepted && c.deferred);
|
|
3156
3352
|
const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
|
|
3157
|
-
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, acceptedUnused, choices, antiPatterns };
|
|
3353
|
+
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, deferred, acceptedUnused, choices, antiPatterns };
|
|
3158
3354
|
}
|
|
3159
|
-
function analyzeChoice(p, d, rule) {
|
|
3355
|
+
function analyzeChoice(p, d, rule, resolve) {
|
|
3160
3356
|
const arms = d.parsers;
|
|
3161
3357
|
const shallow = arms.map((a) => a._meta.firstSet);
|
|
3162
|
-
const deep = arms.map((a) => firstSetOf(a));
|
|
3358
|
+
const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve));
|
|
3163
3359
|
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] };
|
|
3360
|
+
const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve);
|
|
3361
|
+
return { index, cause, detail, shallowAnyOnly: false, unresolvedExternal: unresolvedExternal === true, suggestion: SUGGESTIONS[cause] };
|
|
3166
3362
|
});
|
|
3167
3363
|
const overlaps = [];
|
|
3168
3364
|
for (let i = 0; i < deep.length; i++)
|
|
@@ -3178,10 +3374,12 @@ function analyzeChoice(p, d, rule) {
|
|
|
3178
3374
|
if (d.disjoint) gates = "yes";
|
|
3179
3375
|
else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
|
|
3180
3376
|
else gates = "no";
|
|
3377
|
+
const deferred = gates === "no" && overlaps.length === 0 && anyArms.length > 0 && anyArms.every((a) => a.unresolvedExternal);
|
|
3181
3378
|
return {
|
|
3182
3379
|
rule,
|
|
3183
3380
|
strategy: d.strategy?.tag ?? "firstMatch",
|
|
3184
3381
|
gates,
|
|
3382
|
+
deferred,
|
|
3185
3383
|
combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
|
|
3186
3384
|
anyArms,
|
|
3187
3385
|
overlaps
|
|
@@ -3348,6 +3546,7 @@ function children(def, winners) {
|
|
|
3348
3546
|
case "field":
|
|
3349
3547
|
case "grammar":
|
|
3350
3548
|
case "not":
|
|
3549
|
+
case "peek":
|
|
3351
3550
|
case "node":
|
|
3352
3551
|
case "guard":
|
|
3353
3552
|
case "withCtx":
|
|
@@ -5113,6 +5312,9 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
5113
5312
|
case "keywords":
|
|
5114
5313
|
case "guard":
|
|
5115
5314
|
case "not":
|
|
5315
|
+
// peek(): emitted under a non-capturing probe ctx and zero-width on both
|
|
5316
|
+
// outcomes, so it can never leave a partial capture behind.
|
|
5317
|
+
case "peek":
|
|
5116
5318
|
case "trivia":
|
|
5117
5319
|
case "token":
|
|
5118
5320
|
case "leaf":
|
|
@@ -5184,6 +5386,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
5184
5386
|
case "leaf":
|
|
5185
5387
|
return true;
|
|
5186
5388
|
case "not":
|
|
5389
|
+
case "peek":
|
|
5187
5390
|
case "guard":
|
|
5188
5391
|
case "trivia":
|
|
5189
5392
|
case "unknown":
|
|
@@ -5310,6 +5513,7 @@ function emitKeywordsFast(def, ctx, pos) {
|
|
|
5310
5513
|
if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
|
|
5311
5514
|
if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
|
|
5312
5515
|
if (def.caseInsensitive && def.boundary) return null;
|
|
5516
|
+
if (def.caseInsensitive && def.words.some((w) => w.split("").some((c) => c.charCodeAt(0) > 127))) return null;
|
|
5313
5517
|
let boundary = null;
|
|
5314
5518
|
if (def.boundary) {
|
|
5315
5519
|
let body = def.boundary;
|
|
@@ -5357,7 +5561,7 @@ function emitKeywords(def, ctx, pos) {
|
|
|
5357
5561
|
if (fast) return fast;
|
|
5358
5562
|
const alt = def.words.map(escapeKeywordRe).join("|");
|
|
5359
5563
|
const boundary = def.boundary ? `(?![${def.boundary}])` : "";
|
|
5360
|
-
const flags = def.caseInsensitive ? "
|
|
5564
|
+
const flags = def.caseInsensitive ? "iy" : "uy";
|
|
5361
5565
|
const source = `(?:${alt})${boundary}`;
|
|
5362
5566
|
const key = `${source}/${flags}`;
|
|
5363
5567
|
let rName = ctx.regexMap.get(key);
|
|
@@ -5503,6 +5707,7 @@ function failsAtStart(p) {
|
|
|
5503
5707
|
case "keywords":
|
|
5504
5708
|
case "guard":
|
|
5505
5709
|
case "not":
|
|
5710
|
+
case "peek":
|
|
5506
5711
|
return true;
|
|
5507
5712
|
case "transform":
|
|
5508
5713
|
case "label":
|
|
@@ -5916,14 +6121,17 @@ function emitMany(def, ctx, pos) {
|
|
|
5916
6121
|
];
|
|
5917
6122
|
const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
|
|
5918
6123
|
if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
|
|
5919
|
-
|
|
6124
|
+
for (let i = 0; i < def.min; i++) {
|
|
5920
6125
|
const firstR = emit(def.parser, ctx, curV);
|
|
5921
6126
|
stmts.push(...firstR.stmts);
|
|
5922
6127
|
if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${firstR.valueVar})`);
|
|
5923
6128
|
stmts.push(`${ind(ctx)}${curV} = ${firstR.endVar}`);
|
|
5924
6129
|
}
|
|
6130
|
+
const maxV = def.max === void 0 ? null : wantValue ? `${arrV}.length` : v(ctx, "_cnt");
|
|
6131
|
+
if (maxV !== null && !wantValue) stmts.push(`${ind(ctx)}let ${maxV} = ${def.min}`);
|
|
5925
6132
|
stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
|
|
5926
6133
|
ctx.indent++;
|
|
6134
|
+
if (maxV !== null) stmts.push(`${ind(ctx)}if (${maxV} >= ${def.max}) break`);
|
|
5927
6135
|
let itemPos = curV;
|
|
5928
6136
|
let rollback = "";
|
|
5929
6137
|
if (ctx.activeTrivia) {
|
|
@@ -5964,7 +6172,7 @@ function emitMany(def, ctx, pos) {
|
|
|
5964
6172
|
`${ind(ctx)}if (!${iterOk}) {`,
|
|
5965
6173
|
`${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
|
|
5966
6174
|
`${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
|
|
5967
|
-
...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
|
|
6175
|
+
...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : maxV !== null ? [`${ind(ctx)} ${maxV}++`] : [],
|
|
5968
6176
|
`${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
|
|
5969
6177
|
`${ind(ctx)} ${curV} = ${rrV}.end`,
|
|
5970
6178
|
`${ind(ctx)} continue`,
|
|
@@ -5977,6 +6185,7 @@ function emitMany(def, ctx, pos) {
|
|
|
5977
6185
|
stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
|
|
5978
6186
|
}
|
|
5979
6187
|
if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
|
|
6188
|
+
else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
|
|
5980
6189
|
stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
|
|
5981
6190
|
ctx.indent--;
|
|
5982
6191
|
stmts.push(`${ind(ctx)}}`);
|
|
@@ -6041,8 +6250,8 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6041
6250
|
...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
|
|
6042
6251
|
...firstStmts
|
|
6043
6252
|
];
|
|
6044
|
-
const failItem = (nextOkVar, itemPosVar,
|
|
6045
|
-
if (!rec) return [
|
|
6253
|
+
const failItem = (nextOkVar, itemPosVar, breakStmt) => {
|
|
6254
|
+
if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
|
|
6046
6255
|
const rr = v(ctx, "_rr");
|
|
6047
6256
|
return [
|
|
6048
6257
|
`${ind(ctx)}if (!${nextOkVar}) {`,
|
|
@@ -6050,10 +6259,24 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6050
6259
|
`${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
|
|
6051
6260
|
`${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
|
|
6052
6261
|
`${ind(ctx)} }`,
|
|
6053
|
-
`${ind(ctx)} ${
|
|
6262
|
+
`${ind(ctx)} ${breakStmt}`,
|
|
6054
6263
|
`${ind(ctx)}}`
|
|
6055
6264
|
];
|
|
6056
6265
|
};
|
|
6266
|
+
const itemFailBreak = (sepEndVar, sepRb, postSepRb) => def.trailing === void 0 ? `${sepRb}break` : `${postSepRb}${curV} = ${sepEndVar}; break`;
|
|
6267
|
+
const postSepMarks = () => {
|
|
6268
|
+
if (def.trailing === void 0 || !ctx.capturing) return { decl: [], rb: "" };
|
|
6269
|
+
const lv = v(ctx, "_tlv"), rw = v(ctx, "_trw"), tl = v(ctx, "_ttl"), lg = v(ctx, "_tlg");
|
|
6270
|
+
return {
|
|
6271
|
+
decl: [
|
|
6272
|
+
`${ind(ctx)}const ${lv} = _ctx._cstLeaves ? _ctx._cstLeaves.length : 0`,
|
|
6273
|
+
`${ind(ctx)}const ${rw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`,
|
|
6274
|
+
`${ind(ctx)}const ${tl} = _ctx._cstTriviaLog ? _ctx._cstTriviaLog.length : 0`,
|
|
6275
|
+
`${ind(ctx)}const ${lg} = _ctx._triviaLog ? _ctx._triviaLog.length : 0`
|
|
6276
|
+
],
|
|
6277
|
+
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}; `
|
|
6278
|
+
};
|
|
6279
|
+
};
|
|
6057
6280
|
if (rec) {
|
|
6058
6281
|
const rr0 = v(ctx, "_rr0");
|
|
6059
6282
|
const didV = v(ctx, "_did");
|
|
@@ -6079,6 +6302,7 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6079
6302
|
);
|
|
6080
6303
|
ctx.indent++;
|
|
6081
6304
|
}
|
|
6305
|
+
if (def.max !== void 0) stmts.push(`${ind(ctx)}if (${arrV}.length >= ${def.max}) break`);
|
|
6082
6306
|
let sepAtPos = curV;
|
|
6083
6307
|
if (ctx.activeTrivia) {
|
|
6084
6308
|
if (ctx.capturing) {
|
|
@@ -6099,12 +6323,14 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6099
6323
|
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}; `;
|
|
6100
6324
|
const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
|
|
6101
6325
|
stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
|
|
6326
|
+
const post = postSepMarks();
|
|
6327
|
+
stmts.push(...post.decl);
|
|
6102
6328
|
const npV = v(ctx, "_np");
|
|
6103
6329
|
stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
|
|
6104
6330
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
|
|
6105
6331
|
stmts.push(
|
|
6106
6332
|
...nextStmts,
|
|
6107
|
-
...failItem(nextOk, npV, rollbackToSep,
|
|
6333
|
+
...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
|
|
6108
6334
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6109
6335
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6110
6336
|
);
|
|
@@ -6120,7 +6346,7 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6120
6346
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
|
|
6121
6347
|
stmts.push(
|
|
6122
6348
|
...nextStmts,
|
|
6123
|
-
...failItem(nextOk, npV, "",
|
|
6349
|
+
...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
|
|
6124
6350
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6125
6351
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6126
6352
|
);
|
|
@@ -6137,10 +6363,12 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6137
6363
|
const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
|
|
6138
6364
|
stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
|
|
6139
6365
|
const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
|
|
6366
|
+
const post = postSepMarks();
|
|
6367
|
+
stmts.push(...post.decl);
|
|
6140
6368
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
|
|
6141
6369
|
stmts.push(
|
|
6142
6370
|
...nextStmts,
|
|
6143
|
-
...failItem(nextOk, sepEnd, nextRb,
|
|
6371
|
+
...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
|
|
6144
6372
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6145
6373
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6146
6374
|
);
|
|
@@ -6149,6 +6377,9 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6149
6377
|
stmts.push(`${ind(ctx)}}`);
|
|
6150
6378
|
ctx.indent--;
|
|
6151
6379
|
stmts.push(`${ind(ctx)}}`);
|
|
6380
|
+
if (def.min >= 1) {
|
|
6381
|
+
stmts.push(...emitIfFail(ctx, `${arrV}.length < ${def.min}`, failArrBody(ctx, deriveExpectedArr([def.parser]), curV)));
|
|
6382
|
+
}
|
|
6152
6383
|
return { stmts, valueVar: arrV, endVar: curV };
|
|
6153
6384
|
}
|
|
6154
6385
|
function emitScanTo(def, ctx, pos) {
|
|
@@ -6200,17 +6431,44 @@ function emitScanTo(def, ctx, pos) {
|
|
|
6200
6431
|
return { stmts, valueVar: valV, endVar: curV };
|
|
6201
6432
|
}
|
|
6202
6433
|
function emitNot(def, ctx, pos) {
|
|
6434
|
+
const sinksLive = !!ctx.capturing || !!ctx.recovery;
|
|
6435
|
+
const leaves = sinksLive ? v(ctx, "_ntl") : null;
|
|
6436
|
+
const raw = sinksLive ? v(ctx, "_ntr") : null;
|
|
6437
|
+
const tl = sinksLive ? v(ctx, "_ntt") : null;
|
|
6438
|
+
const log = sinksLive ? v(ctx, "_ntg") : null;
|
|
6439
|
+
const fields = sinksLive ? v(ctx, "_ntf") : null;
|
|
6440
|
+
const errors = sinksLive ? v(ctx, "_nte") : null;
|
|
6203
6441
|
const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
|
|
6204
6442
|
const label2 = JSON.stringify(`not(${def.parser._tag})`);
|
|
6205
6443
|
return {
|
|
6206
6444
|
stmts: [
|
|
6445
|
+
...sinksLive ? [
|
|
6446
|
+
`${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`
|
|
6447
|
+
] : [],
|
|
6207
6448
|
...stmts,
|
|
6449
|
+
...sinksLive ? [
|
|
6450
|
+
`${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}`
|
|
6451
|
+
] : [],
|
|
6208
6452
|
...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
|
|
6209
6453
|
],
|
|
6210
6454
|
valueVar: "null",
|
|
6211
6455
|
endVar: pos
|
|
6212
6456
|
};
|
|
6213
6457
|
}
|
|
6458
|
+
function emitPeek(def, ctx, pos) {
|
|
6459
|
+
const probeCtx = { ...ctx, capturing: false, noHoist: true };
|
|
6460
|
+
const { stmts, okVar } = emitFallible(def.parser, probeCtx, pos, true);
|
|
6461
|
+
ctx.vars = probeCtx.vars;
|
|
6462
|
+
const label2 = JSON.stringify(`peek(${def.parser._tag})`);
|
|
6463
|
+
return {
|
|
6464
|
+
stmts: [
|
|
6465
|
+
...stmts,
|
|
6466
|
+
...emitIfFail(ctx, `!${okVar}`, failBody(ctx, label2, pos))
|
|
6467
|
+
],
|
|
6468
|
+
valueVar: "null",
|
|
6469
|
+
endVar: pos
|
|
6470
|
+
};
|
|
6471
|
+
}
|
|
6214
6472
|
function escapeTokenLiteral(value) {
|
|
6215
6473
|
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
6216
6474
|
}
|
|
@@ -6498,9 +6756,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
|
|
|
6498
6756
|
return { stmts, valueVar: vv, endVar: ev };
|
|
6499
6757
|
}
|
|
6500
6758
|
function emitLazy(p, def, ctx, pos) {
|
|
6501
|
-
const
|
|
6759
|
+
const canonical2 = ctx.ruleNames?.get(p);
|
|
6502
6760
|
const usage = ctx.lazyUsage;
|
|
6503
|
-
if (!
|
|
6761
|
+
if (!canonical2 && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
|
|
6504
6762
|
let resolved;
|
|
6505
6763
|
try {
|
|
6506
6764
|
resolved = def.thunk();
|
|
@@ -6510,7 +6768,7 @@ function emitLazy(p, def, ctx, pos) {
|
|
|
6510
6768
|
return emit(resolved, ctx, pos);
|
|
6511
6769
|
}
|
|
6512
6770
|
if (!ctx.namedParsers.has(p)) {
|
|
6513
|
-
const fnName2 =
|
|
6771
|
+
const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
|
|
6514
6772
|
ctx.namedParsers.set(p, fnName2);
|
|
6515
6773
|
let resolved;
|
|
6516
6774
|
try {
|
|
@@ -6793,6 +7051,8 @@ function emitDispatch(p, ctx, pos) {
|
|
|
6793
7051
|
}
|
|
6794
7052
|
case "not":
|
|
6795
7053
|
return emitNot(def, ctx, pos);
|
|
7054
|
+
case "peek":
|
|
7055
|
+
return emitPeek(def, ctx, pos);
|
|
6796
7056
|
case "node":
|
|
6797
7057
|
return emitNode(def, ctx, pos);
|
|
6798
7058
|
case "scanTo":
|
|
@@ -6881,6 +7141,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
6881
7141
|
case "many":
|
|
6882
7142
|
case "oneOrMore":
|
|
6883
7143
|
case "not":
|
|
7144
|
+
case "peek":
|
|
6884
7145
|
case "transform":
|
|
6885
7146
|
return hasNodeDef(d.parser, seen);
|
|
6886
7147
|
case "skip":
|
|
@@ -6932,17 +7193,18 @@ function childrenOf(def) {
|
|
|
6932
7193
|
case "field":
|
|
6933
7194
|
case "grammar":
|
|
6934
7195
|
case "not":
|
|
7196
|
+
case "peek":
|
|
6935
7197
|
case "node":
|
|
6936
7198
|
case "withCtx":
|
|
6937
7199
|
case "expect":
|
|
6938
7200
|
return [def.parser];
|
|
6939
|
-
// emitMany's
|
|
6940
|
-
// (
|
|
6941
|
-
// emit() call
|
|
7201
|
+
// emitMany's min>=1 branch and emitSepBy both codegen `def.parser` more than
|
|
7202
|
+
// once (`min` mandatory matches, then again inside the repeat loop) — each is a
|
|
7203
|
+
// real emit() call site, so the usage analysis must see one edge per site or it
|
|
6942
7204
|
// undercounts a single-use `parser` ref as inline-safe when it's actually
|
|
6943
|
-
// referenced from
|
|
7205
|
+
// referenced from several positions within this one compiled function.
|
|
6944
7206
|
case "oneOrMore":
|
|
6945
|
-
return
|
|
7207
|
+
return Array.from({ length: def.min + 1 }, () => def.parser);
|
|
6946
7208
|
case "sepBy":
|
|
6947
7209
|
return [def.parser, def.parser, def.separator];
|
|
6948
7210
|
case "skip":
|
|
@@ -7088,12 +7350,41 @@ function resolveGatingLevel(opt) {
|
|
|
7088
7350
|
return "warn";
|
|
7089
7351
|
}
|
|
7090
7352
|
function runGatingDiagnostic(combinator, opt) {
|
|
7353
|
+
return reportGating(opt, (o) => analyzeGating(combinator, o));
|
|
7354
|
+
}
|
|
7355
|
+
function runGatingDiagnosticRules(ruleMap, opt) {
|
|
7356
|
+
return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
|
|
7357
|
+
}
|
|
7358
|
+
function runFusedGatingDiagnostic(getRuleMaps, opt) {
|
|
7359
|
+
if (resolveGatingLevel(opt) === "off") return void 0;
|
|
7360
|
+
const ruleMaps = getRuleMaps();
|
|
7361
|
+
if (!ruleMaps.some((map) => hasExternalRuleRef(map))) return void 0;
|
|
7362
|
+
const winners = /* @__PURE__ */ new Map();
|
|
7363
|
+
for (const map of ruleMaps) for (const [name, rule] of map) {
|
|
7364
|
+
if (rule._def.tag === "lazy") {
|
|
7365
|
+
try {
|
|
7366
|
+
rule._def.thunk();
|
|
7367
|
+
} catch {
|
|
7368
|
+
continue;
|
|
7369
|
+
}
|
|
7370
|
+
}
|
|
7371
|
+
winners.set(name, rule);
|
|
7372
|
+
}
|
|
7373
|
+
const entries = [...winners];
|
|
7374
|
+
return reportGating(opt, (o) => {
|
|
7375
|
+
const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
|
|
7376
|
+
const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
|
|
7377
|
+
return { ...fused, ungated: fused.ungated.filter((c) => deferredHere.has(c.id)), antiPatterns: [] };
|
|
7378
|
+
});
|
|
7379
|
+
}
|
|
7380
|
+
function reportGating(opt, analyze) {
|
|
7091
7381
|
const level = resolveGatingLevel(opt);
|
|
7092
7382
|
if (level === "off") return void 0;
|
|
7093
|
-
const
|
|
7383
|
+
const obj = opt !== null && typeof opt === "object" ? opt : void 0;
|
|
7384
|
+
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;
|
|
7094
7385
|
let report;
|
|
7095
7386
|
try {
|
|
7096
|
-
report =
|
|
7387
|
+
report = analyze(analyzeOpts);
|
|
7097
7388
|
} catch {
|
|
7098
7389
|
return void 0;
|
|
7099
7390
|
}
|
|
@@ -7230,6 +7521,7 @@ function publicRuleWrapperSource(rule, fnSource) {
|
|
|
7230
7521
|
}
|
|
7231
7522
|
function compileLinkable(ruleMapArg, ns, opts) {
|
|
7232
7523
|
if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
|
|
7524
|
+
if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
|
|
7233
7525
|
for (const [, rule] of ruleMapArg) markUnusedValues(rule);
|
|
7234
7526
|
const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
|
|
7235
7527
|
const grammarScanSkip = opts?.scanSkip ?? ruleMapArg.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
|
|
@@ -7286,6 +7578,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7286
7578
|
fnNameToKey.set(fn, key);
|
|
7287
7579
|
}
|
|
7288
7580
|
ctx.ruleNames = ruleNames;
|
|
7581
|
+
const externalRefs = /* @__PURE__ */ new Set();
|
|
7289
7582
|
{
|
|
7290
7583
|
const scanned = /* @__PURE__ */ new Set();
|
|
7291
7584
|
const scanExternal = (p) => {
|
|
@@ -7301,11 +7594,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7301
7594
|
}
|
|
7302
7595
|
if (resolved === void 0) {
|
|
7303
7596
|
const name = p._ruleName;
|
|
7304
|
-
if (name
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7597
|
+
if (name) {
|
|
7598
|
+
externalRefs.add(p);
|
|
7599
|
+
if (!ctx.namedParsers.has(p)) {
|
|
7600
|
+
assertRuleName(name);
|
|
7601
|
+
const fn = `_r_${name}`;
|
|
7602
|
+
ruleNames.set(p, fn);
|
|
7603
|
+
ctx.namedParsers.set(p, fn);
|
|
7604
|
+
}
|
|
7309
7605
|
}
|
|
7310
7606
|
return;
|
|
7311
7607
|
}
|
|
@@ -7418,12 +7714,32 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7418
7714
|
needsEmptyTl: !!ctx.needsEmptyTl,
|
|
7419
7715
|
needsHostReads: !!ctx.needsHostReads,
|
|
7420
7716
|
hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
|
|
7421
|
-
isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule)),
|
|
7717
|
+
isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule), externalRefs),
|
|
7422
7718
|
mfFns: mfSrcs ? [] : ctx.mapFns,
|
|
7423
7719
|
buildFns: buildSrcs ? [] : ctx.buildFns
|
|
7424
7720
|
};
|
|
7425
7721
|
}
|
|
7426
|
-
function
|
|
7722
|
+
function hasExternalRuleRef(ruleMap) {
|
|
7723
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7724
|
+
const visit = (p) => {
|
|
7725
|
+
if (seen.has(p)) return false;
|
|
7726
|
+
seen.add(p);
|
|
7727
|
+
const def = p._def;
|
|
7728
|
+
if (def.tag === "lazy") {
|
|
7729
|
+
let resolved;
|
|
7730
|
+
try {
|
|
7731
|
+
resolved = def.thunk();
|
|
7732
|
+
} catch {
|
|
7733
|
+
resolved = void 0;
|
|
7734
|
+
}
|
|
7735
|
+
if (resolved === void 0) return p._ruleName !== void 0;
|
|
7736
|
+
return visit(resolved);
|
|
7737
|
+
}
|
|
7738
|
+
return childrenOf(def).some(visit);
|
|
7739
|
+
};
|
|
7740
|
+
return ruleMap.some(([, rule]) => visit(rule));
|
|
7741
|
+
}
|
|
7742
|
+
function hasSemanticReduction(roots, externalRefs) {
|
|
7427
7743
|
const seen = /* @__PURE__ */ new Set();
|
|
7428
7744
|
const visit = (parser2) => {
|
|
7429
7745
|
if (seen.has(parser2)) return false;
|
|
@@ -7434,6 +7750,7 @@ function hasSemanticReduction(roots) {
|
|
|
7434
7750
|
if (def.tag === "guard" || def.tag === "withCtx") return true;
|
|
7435
7751
|
if (def.tag === "node" && def.build !== void 0) return true;
|
|
7436
7752
|
if (def.tag === "lazy") {
|
|
7753
|
+
if (externalRefs?.has(parser2)) return false;
|
|
7437
7754
|
try {
|
|
7438
7755
|
return visit(def.thunk());
|
|
7439
7756
|
} catch {
|
|
@@ -7525,6 +7842,7 @@ function childrenOf2(def) {
|
|
|
7525
7842
|
case "label":
|
|
7526
7843
|
case "field":
|
|
7527
7844
|
case "not":
|
|
7845
|
+
case "peek":
|
|
7528
7846
|
case "node":
|
|
7529
7847
|
case "withCtx":
|
|
7530
7848
|
case "expect":
|
|
@@ -7558,6 +7876,13 @@ function lazyTarget(c) {
|
|
|
7558
7876
|
}
|
|
7559
7877
|
var Unserializable = class extends Error {
|
|
7560
7878
|
};
|
|
7879
|
+
function repeatOpts(min, max, trailing) {
|
|
7880
|
+
const parts = [];
|
|
7881
|
+
if (min !== 0) parts.push(`min: ${min}`);
|
|
7882
|
+
if (max !== void 0) parts.push(`max: ${max}`);
|
|
7883
|
+
if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
|
|
7884
|
+
return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
|
|
7885
|
+
}
|
|
7561
7886
|
function serializeRuleMap(ruleMap, scanSkip) {
|
|
7562
7887
|
try {
|
|
7563
7888
|
return new Serializer(ruleMap, scanSkip ?? []).run();
|
|
@@ -7661,6 +7986,7 @@ function evalRuleMapIR(ir) {
|
|
|
7661
7986
|
"optional",
|
|
7662
7987
|
"sepBy",
|
|
7663
7988
|
"not",
|
|
7989
|
+
"peek",
|
|
7664
7990
|
"node",
|
|
7665
7991
|
"parser",
|
|
7666
7992
|
"scanTo",
|
|
@@ -7693,6 +8019,7 @@ function evalRuleMapIR(ir) {
|
|
|
7693
8019
|
optional,
|
|
7694
8020
|
sepBy,
|
|
7695
8021
|
not,
|
|
8022
|
+
peek,
|
|
7696
8023
|
node,
|
|
7697
8024
|
parser,
|
|
7698
8025
|
scanTo,
|
|
@@ -7867,17 +8194,21 @@ ${outerDecls.map((d) => " " + d).join("\n")}
|
|
|
7867
8194
|
return `_gch([${items.join(", ")}])`;
|
|
7868
8195
|
}
|
|
7869
8196
|
case "many":
|
|
7870
|
-
return `many(${kid(def.parser)})`;
|
|
8197
|
+
return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
|
|
8198
|
+
// A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
|
|
8199
|
+
// it through `many` so the bounds survive (oneOrMore defaults min to 1).
|
|
7871
8200
|
case "oneOrMore":
|
|
7872
|
-
return `oneOrMore(${kid(def.parser)})`;
|
|
8201
|
+
return def.min === 1 && def.max === void 0 ? `oneOrMore(${kid(def.parser)})` : `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
|
|
7873
8202
|
case "optional":
|
|
7874
8203
|
return `optional(${kid(def.parser)})`;
|
|
7875
8204
|
case "attempt":
|
|
7876
8205
|
return `attempt(${kid(def.parser)})`;
|
|
7877
8206
|
case "sepBy":
|
|
7878
|
-
return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
|
|
8207
|
+
return `sepBy(${kid(def.parser)}, ${kid(def.separator)}${repeatOpts(def.min, def.max, def.trailing)})`;
|
|
7879
8208
|
case "not":
|
|
7880
8209
|
return `not(${kid(def.parser)})`;
|
|
8210
|
+
case "peek":
|
|
8211
|
+
return `peek(${kid(def.parser)})`;
|
|
7881
8212
|
case "trivia":
|
|
7882
8213
|
return `trivia(${kid(def.parser)})`;
|
|
7883
8214
|
case "token":
|
|
@@ -8070,6 +8401,9 @@ var COMPOSED_COVERAGE_RULES = /* @__PURE__ */ Symbol.for("parseman.composedCover
|
|
|
8070
8401
|
function isIRPiece(p) {
|
|
8071
8402
|
return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
|
|
8072
8403
|
}
|
|
8404
|
+
function carriedRuleMaps(carried) {
|
|
8405
|
+
return carried.filter(isIRPiece).map((p) => evalRuleMapIR(p.ir));
|
|
8406
|
+
}
|
|
8073
8407
|
function coverageRulesOf(carried) {
|
|
8074
8408
|
const winners = {};
|
|
8075
8409
|
for (const piece of carried) {
|
|
@@ -8154,6 +8488,7 @@ function compose(items) {
|
|
|
8154
8488
|
const used = /* @__PURE__ */ new Set();
|
|
8155
8489
|
const trivia2 = composingTriviaOf(items);
|
|
8156
8490
|
const carried = items.flatMap((item) => itemCarried(item, used, trivia2));
|
|
8491
|
+
runFusedGatingDiagnostic(() => carriedRuleMaps(carried));
|
|
8157
8492
|
const pieces = carried.map((p) => materializePiece(p, trivia2));
|
|
8158
8493
|
const map = fuseRules(pieces);
|
|
8159
8494
|
Object.defineProperty(map, COMPOSED_PIECES, { value: carried, enumerable: false });
|
|
@@ -8986,7 +9321,7 @@ function coverageEntry(entry, collector, maps, trace) {
|
|
|
8986
9321
|
};
|
|
8987
9322
|
}
|
|
8988
9323
|
case "sepBy":
|
|
8989
|
-
return sepBy(build(def.parser), build(def.separator));
|
|
9324
|
+
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 } });
|
|
8990
9325
|
case "transform":
|
|
8991
9326
|
return transform(build(def.parser), def.fn);
|
|
8992
9327
|
case "skip":
|
|
@@ -9007,6 +9342,8 @@ function coverageEntry(entry, collector, maps, trace) {
|
|
|
9007
9342
|
}, build(def.parser));
|
|
9008
9343
|
case "not":
|
|
9009
9344
|
return not(build(def.parser));
|
|
9345
|
+
case "peek":
|
|
9346
|
+
return peek(build(def.parser));
|
|
9010
9347
|
case "node": {
|
|
9011
9348
|
const opts = { ...def.unwrap ? { unwrap: true } : {}, ...def.collapse ? { collapse: true } : {}, ...def.captureTrivia ? { captureTrivia: true } : {}, ...def.trailingTrivia ? { trailingTrivia: true } : {} };
|
|
9012
9349
|
return def.type === void 0 ? node(build(def.parser), def.build, opts) : node(def.type, build(def.parser), def.build, opts);
|