parseman 0.33.0 → 0.35.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/README.md +94 -72
- 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 +1 -1
- 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/cst/capture-buffer.d.ts.map +1 -1
- package/dist/index.cjs +462 -105
- 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 +462 -105
- 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 +529 -135
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +529 -135
- package/dist/plugin/index.js.map +4 -4
- package/dist/run/index.cjs +216 -0
- package/dist/run/index.cjs.map +7 -0
- package/dist/run/index.d.ts +28 -0
- package/dist/run/index.d.ts.map +1 -0
- package/dist/run/index.js +193 -0
- package/dist/run/index.js.map +7 -0
- 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 +11 -1
package/dist/plugin/index.cjs
CHANGED
|
@@ -42,6 +42,11 @@ var import_oxc_resolver = require("oxc-resolver");
|
|
|
42
42
|
var import_magic_string = __toESM(require("magic-string"), 1);
|
|
43
43
|
|
|
44
44
|
// src/combinators/first-set.ts
|
|
45
|
+
function resolveNamedRef(p, resolve2) {
|
|
46
|
+
if (resolve2 === void 0) return void 0;
|
|
47
|
+
const name = p._ruleName;
|
|
48
|
+
return name === void 0 ? void 0 : resolve2(name);
|
|
49
|
+
}
|
|
45
50
|
function union(a, b) {
|
|
46
51
|
if (a.kind === "any" || b.kind === "any") return { kind: "any" };
|
|
47
52
|
if (a.kind === "empty") return b;
|
|
@@ -79,10 +84,10 @@ function any() {
|
|
|
79
84
|
function empty() {
|
|
80
85
|
return { kind: "empty" };
|
|
81
86
|
}
|
|
82
|
-
function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
87
|
+
function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
|
|
83
88
|
if (seen.has(p)) return true;
|
|
84
89
|
seen.add(p);
|
|
85
|
-
const me = (c) => matchesEmpty(c, seen);
|
|
90
|
+
const me = (c) => matchesEmpty(c, seen, resolve2);
|
|
86
91
|
const d = p._def;
|
|
87
92
|
switch (d.tag) {
|
|
88
93
|
case "literal":
|
|
@@ -99,8 +104,16 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
99
104
|
case "many":
|
|
100
105
|
case "optional":
|
|
101
106
|
case "not":
|
|
107
|
+
case "peek":
|
|
102
108
|
return true;
|
|
103
109
|
// zero repetitions / absent / lookahead
|
|
110
|
+
// Default `sepBy` is `(item (sep item)*)?` — it MATCHES THE EMPTY STRING.
|
|
111
|
+
// Any `min >= 1` requires that many ITEMS, so it is nullable only when the
|
|
112
|
+
// item is. (Keying this off `min === 1` reported every `{ min: 2 }` list as
|
|
113
|
+
// nullable — safe, but wrong, and it put a bogus `nullable-prefix` note on
|
|
114
|
+
// the gating diagnostic for a list that can never match empty.)
|
|
115
|
+
case "sepBy":
|
|
116
|
+
return d.min >= 1 ? me(d.parser) : true;
|
|
104
117
|
case "oneOrMore":
|
|
105
118
|
return me(d.parser);
|
|
106
119
|
case "sequence":
|
|
@@ -124,7 +137,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
124
137
|
try {
|
|
125
138
|
return me(d.thunk());
|
|
126
139
|
} catch {
|
|
127
|
-
|
|
140
|
+
const t = resolveNamedRef(p, resolve2);
|
|
141
|
+
return t ? me(t) : true;
|
|
128
142
|
}
|
|
129
143
|
default:
|
|
130
144
|
return true;
|
|
@@ -133,18 +147,45 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
133
147
|
function isZeroWidthAssertion(p) {
|
|
134
148
|
return p._def.tag === "not";
|
|
135
149
|
}
|
|
150
|
+
function isPositiveLookahead(p) {
|
|
151
|
+
return p._def.tag === "peek";
|
|
152
|
+
}
|
|
153
|
+
function narrowBy(acc, constraint) {
|
|
154
|
+
if (constraint.kind === "any") return acc;
|
|
155
|
+
if (acc === null) return constraint;
|
|
156
|
+
if (acc.kind === "any") return constraint;
|
|
157
|
+
if (acc.kind === "empty" || constraint.kind === "empty") return { kind: "empty" };
|
|
158
|
+
const ranges = [];
|
|
159
|
+
for (const a of acc.ranges) for (const b of constraint.ranges) {
|
|
160
|
+
const lo = Math.max(a.lo, b.lo);
|
|
161
|
+
const hi = Math.min(a.hi, b.hi);
|
|
162
|
+
if (lo <= hi) ranges.push({ lo, hi });
|
|
163
|
+
}
|
|
164
|
+
return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
|
|
165
|
+
}
|
|
166
|
+
function applyAssertion(fs2, assertion) {
|
|
167
|
+
if (assertion === null) return fs2;
|
|
168
|
+
if (fs2.kind === "empty") return assertion;
|
|
169
|
+
return narrowBy(fs2, assertion) ?? fs2;
|
|
170
|
+
}
|
|
136
171
|
function sequenceFirstSet(parsers) {
|
|
137
172
|
let fs2 = empty();
|
|
173
|
+
let assertion = null;
|
|
138
174
|
for (const p of parsers) {
|
|
175
|
+
if (isPositiveLookahead(p)) {
|
|
176
|
+
assertion = narrowBy(assertion, p._meta.firstSet);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
139
179
|
if (!isZeroWidthAssertion(p)) fs2 = union(fs2, p._meta.firstSet);
|
|
140
|
-
if (!matchesEmpty(p)) return fs2;
|
|
180
|
+
if (!matchesEmpty(p)) return applyAssertion(fs2, assertion);
|
|
141
181
|
}
|
|
142
|
-
return fs2;
|
|
182
|
+
return applyAssertion(fs2, assertion);
|
|
143
183
|
}
|
|
144
|
-
function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
184
|
+
function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
|
|
145
185
|
if (seen.has(p)) return any();
|
|
146
186
|
seen.add(p);
|
|
147
|
-
const fs2 = (c) => firstSetOf(c, seen);
|
|
187
|
+
const fs2 = (c) => firstSetOf(c, seen, resolve2);
|
|
188
|
+
const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve2);
|
|
148
189
|
const d = p._def;
|
|
149
190
|
switch (d.tag) {
|
|
150
191
|
case "literal":
|
|
@@ -156,7 +197,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
156
197
|
try {
|
|
157
198
|
return fs2(d.thunk());
|
|
158
199
|
} catch {
|
|
159
|
-
|
|
200
|
+
const t = resolveNamedRef(p, resolve2);
|
|
201
|
+
return t ? fs2(t) : any();
|
|
160
202
|
}
|
|
161
203
|
case "choice": {
|
|
162
204
|
let out = empty();
|
|
@@ -165,11 +207,20 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
165
207
|
}
|
|
166
208
|
case "sequence": {
|
|
167
209
|
let out = empty();
|
|
210
|
+
let assertion = null;
|
|
168
211
|
for (const term of d.parsers) {
|
|
212
|
+
if (isPositiveLookahead(term)) {
|
|
213
|
+
assertion = narrowBy(assertion, fs2(term));
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
169
216
|
if (!isZeroWidthAssertion(term)) out = union(out, fs2(term));
|
|
170
|
-
if (!
|
|
217
|
+
if (!empties(term)) return applyAssertion(out, assertion);
|
|
171
218
|
}
|
|
172
|
-
return out;
|
|
219
|
+
return applyAssertion(out, assertion);
|
|
220
|
+
}
|
|
221
|
+
case "peek": {
|
|
222
|
+
const inner = d.parser;
|
|
223
|
+
return empties(inner) ? any() : fs2(inner);
|
|
173
224
|
}
|
|
174
225
|
case "oneOrMore":
|
|
175
226
|
case "many":
|
|
@@ -185,6 +236,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
185
236
|
return fs2(d.parser);
|
|
186
237
|
case "sepBy":
|
|
187
238
|
return fs2(d.parser);
|
|
239
|
+
// both min 0 and min 1 start with the item
|
|
188
240
|
case "skip":
|
|
189
241
|
return fs2(d.main);
|
|
190
242
|
default:
|
|
@@ -363,28 +415,28 @@ function rollbackBufList(b, keyMulti, keySingle, len) {
|
|
|
363
415
|
else if (len === 1) {
|
|
364
416
|
b[keySingle] = arr[0];
|
|
365
417
|
b[keyMulti] = void 0;
|
|
366
|
-
} else arr.length = len;
|
|
418
|
+
} else if (arr.length !== len) arr.length = len;
|
|
367
419
|
return;
|
|
368
420
|
}
|
|
369
421
|
if (len === 0) b[keySingle] = void 0;
|
|
370
422
|
}
|
|
371
423
|
function rollbackCstCapture(ctx, mark) {
|
|
372
|
-
if (ctx._errors && mark.errors !== void 0) ctx._errors.length = mark.errors;
|
|
424
|
+
if (ctx._errors && mark.errors !== void 0 && ctx._errors.length !== mark.errors) ctx._errors.length = mark.errors;
|
|
373
425
|
const b = ctx._cstBuf;
|
|
374
426
|
if (b) {
|
|
375
427
|
rollbackBufList(b, "raw", "rawSingle", mark.raw);
|
|
376
428
|
rollbackBufList(b, "ch", "single", mark.leaves);
|
|
377
429
|
if (b.tl) {
|
|
378
430
|
if (mark.tlog === 0) b.tl = void 0;
|
|
379
|
-
else b.tl.length = mark.tlog;
|
|
431
|
+
else if (b.tl.length !== mark.tlog) b.tl.length = mark.tlog;
|
|
380
432
|
}
|
|
381
|
-
if (ctx._fields) ctx._fields.length = mark.fields;
|
|
433
|
+
if (ctx._fields && ctx._fields.length !== mark.fields) ctx._fields.length = mark.fields;
|
|
382
434
|
return;
|
|
383
435
|
}
|
|
384
|
-
if (ctx._cstRawChildren) ctx._cstRawChildren.length = mark.raw;
|
|
385
|
-
if (ctx._cstTriviaLog) ctx._cstTriviaLog.length = mark.tlog;
|
|
386
|
-
if (ctx._cstLeaves) ctx._cstLeaves.length = mark.leaves;
|
|
387
|
-
if (ctx._fields) ctx._fields.length = mark.fields;
|
|
436
|
+
if (ctx._cstRawChildren && ctx._cstRawChildren.length !== mark.raw) ctx._cstRawChildren.length = mark.raw;
|
|
437
|
+
if (ctx._cstTriviaLog && ctx._cstTriviaLog.length !== mark.tlog) ctx._cstTriviaLog.length = mark.tlog;
|
|
438
|
+
if (ctx._cstLeaves && ctx._cstLeaves.length !== mark.leaves) ctx._cstLeaves.length = mark.leaves;
|
|
439
|
+
if (ctx._fields && ctx._fields.length !== mark.fields) ctx._fields.length = mark.fields;
|
|
388
440
|
}
|
|
389
441
|
function pushCstTriviaEntry(ctx, start, end, kindIndex) {
|
|
390
442
|
const insertIdx = cstRawLen(ctx);
|
|
@@ -906,9 +958,46 @@ function firstSetFromRegex(source) {
|
|
|
906
958
|
return { firstSet: firstSet(ast), canMatchNewline: firstCanBeNewline(ast) };
|
|
907
959
|
}
|
|
908
960
|
|
|
961
|
+
// src/combinators/case-fold.ts
|
|
962
|
+
function canonical(ch2) {
|
|
963
|
+
const u = ch2.toUpperCase();
|
|
964
|
+
if (u.length !== 1) return ch2;
|
|
965
|
+
if (ch2.codePointAt(0) >= 128 && u.codePointAt(0) < 128) return ch2;
|
|
966
|
+
return u;
|
|
967
|
+
}
|
|
968
|
+
var foldIndex = null;
|
|
969
|
+
function buildFoldIndex() {
|
|
970
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
971
|
+
for (let cp = 0; cp < 65536; cp++) {
|
|
972
|
+
if (cp >= 55296 && cp <= 57343) continue;
|
|
973
|
+
const key = canonical(String.fromCharCode(cp));
|
|
974
|
+
const bucket = byCanonical.get(key);
|
|
975
|
+
if (bucket) bucket.push(cp);
|
|
976
|
+
else byCanonical.set(key, [cp]);
|
|
977
|
+
}
|
|
978
|
+
const index = /* @__PURE__ */ new Map();
|
|
979
|
+
for (const members of byCanonical.values()) {
|
|
980
|
+
if (members.length < 2) continue;
|
|
981
|
+
for (const cp of members) index.set(cp, members);
|
|
982
|
+
}
|
|
983
|
+
return index;
|
|
984
|
+
}
|
|
985
|
+
function caseFoldVariants(cp) {
|
|
986
|
+
if (cp < 128) {
|
|
987
|
+
if (cp >= 65 && cp <= 90) return [cp, cp + 32];
|
|
988
|
+
if (cp >= 97 && cp <= 122) return [cp, cp - 32];
|
|
989
|
+
return [cp];
|
|
990
|
+
}
|
|
991
|
+
if (cp > 65535) return [cp];
|
|
992
|
+
foldIndex ??= buildFoldIndex();
|
|
993
|
+
return foldIndex.get(cp) ?? [cp];
|
|
994
|
+
}
|
|
995
|
+
|
|
909
996
|
// src/combinators/keywords.ts
|
|
910
|
-
function word(str,
|
|
911
|
-
|
|
997
|
+
function word(str, boundaryOrOpts, opts) {
|
|
998
|
+
const boundary = typeof boundaryOrOpts === "string" ? boundaryOrOpts : "_0-9A-Za-z";
|
|
999
|
+
const rest = typeof boundaryOrOpts === "object" && boundaryOrOpts !== null ? boundaryOrOpts : opts;
|
|
1000
|
+
return keywords([str], { ...rest, boundary });
|
|
912
1001
|
}
|
|
913
1002
|
function escapeRe(s) {
|
|
914
1003
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -917,7 +1006,7 @@ function keywords(words, opts = {}) {
|
|
|
917
1006
|
const sorted = [...new Set(words)].sort((a, b) => b.length - a.length);
|
|
918
1007
|
const alt = sorted.map(escapeRe).join("|");
|
|
919
1008
|
const boundary = opts.boundary ? `(?![${opts.boundary}])` : "";
|
|
920
|
-
const flags = opts.caseInsensitive ? "
|
|
1009
|
+
const flags = opts.caseInsensitive ? "iy" : "uy";
|
|
921
1010
|
const re = new RegExp(`(?:${alt})${boundary}`, flags);
|
|
922
1011
|
let firstSet2 = empty();
|
|
923
1012
|
for (const w of sorted) {
|
|
@@ -925,10 +1014,7 @@ function keywords(words, opts = {}) {
|
|
|
925
1014
|
const cp = w.codePointAt(0);
|
|
926
1015
|
firstSet2 = union(firstSet2, fromChar(cp));
|
|
927
1016
|
if (opts.caseInsensitive) {
|
|
928
|
-
const
|
|
929
|
-
const l = String.fromCodePoint(cp).toLowerCase().codePointAt(0);
|
|
930
|
-
if (u !== void 0) firstSet2 = union(firstSet2, fromChar(u));
|
|
931
|
-
if (l !== void 0) firstSet2 = union(firstSet2, fromChar(l));
|
|
1017
|
+
for (const v2 of caseFoldVariants(cp)) firstSet2 = union(firstSet2, fromChar(v2));
|
|
932
1018
|
}
|
|
933
1019
|
}
|
|
934
1020
|
const meta = { firstSet: firstSet2, canMatchNewline: false, isTrivia: false };
|
|
@@ -1039,6 +1125,10 @@ function captureError(ctx, error) {
|
|
|
1039
1125
|
|
|
1040
1126
|
// src/combinators/expect.ts
|
|
1041
1127
|
function deriveExpected(c) {
|
|
1128
|
+
return derive(c, /* @__PURE__ */ new Set());
|
|
1129
|
+
}
|
|
1130
|
+
function derive(c, seen) {
|
|
1131
|
+
const deriveExpected2 = (p) => derive(p, seen);
|
|
1042
1132
|
const def = c._def;
|
|
1043
1133
|
switch (def.tag) {
|
|
1044
1134
|
case "literal":
|
|
@@ -1050,20 +1140,32 @@ function deriveExpected(c) {
|
|
|
1050
1140
|
case "label":
|
|
1051
1141
|
return [def.label];
|
|
1052
1142
|
case "choice":
|
|
1053
|
-
return def.parsers.flatMap(
|
|
1054
|
-
|
|
1055
|
-
|
|
1143
|
+
return def.parsers.flatMap(deriveExpected2);
|
|
1144
|
+
// Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
|
|
1145
|
+
// `optional(…)`/`many(…)` can match nothing, so the term after it is equally
|
|
1146
|
+
// able to fail first — deriving from term 0 alone named a token the parse
|
|
1147
|
+
// never actually required. A leading `not(…)` is zero-width and contributes
|
|
1148
|
+
// nothing (its own expectation is about what must NOT be here), but it is
|
|
1149
|
+
// nullable, so keep scanning past it. Stop at the first term that must match.
|
|
1150
|
+
case "sequence": {
|
|
1151
|
+
const out = [];
|
|
1152
|
+
for (const term of def.parsers) {
|
|
1153
|
+
if (!isZeroWidthAssertion(term)) out.push(...deriveExpected2(term));
|
|
1154
|
+
if (!matchesEmpty(term)) break;
|
|
1155
|
+
}
|
|
1156
|
+
return out;
|
|
1157
|
+
}
|
|
1056
1158
|
case "attempt":
|
|
1057
|
-
return
|
|
1159
|
+
return deriveExpected2(def.parser);
|
|
1058
1160
|
// Delegating wrappers whose first-token failure is their inner parser's — must
|
|
1059
1161
|
// pass through so a start-failure (and the first-set-miss fast-path in attempt/
|
|
1060
1162
|
// node, which reads this) names the real expected token, not the wrapper tag.
|
|
1061
1163
|
case "field":
|
|
1062
1164
|
case "withCtx":
|
|
1063
1165
|
case "recover":
|
|
1064
|
-
return
|
|
1166
|
+
return deriveExpected2(def.parser);
|
|
1065
1167
|
case "skip":
|
|
1066
|
-
return
|
|
1168
|
+
return deriveExpected2(def.main);
|
|
1067
1169
|
case "expect":
|
|
1068
1170
|
return def.expected;
|
|
1069
1171
|
case "node":
|
|
@@ -1076,13 +1178,18 @@ function deriveExpected(c) {
|
|
|
1076
1178
|
case "oneOrMore":
|
|
1077
1179
|
case "transform":
|
|
1078
1180
|
case "not":
|
|
1079
|
-
|
|
1181
|
+
case "peek":
|
|
1182
|
+
return deriveExpected2(def.parser);
|
|
1080
1183
|
case "lazy": {
|
|
1081
1184
|
const name = c._ruleName;
|
|
1185
|
+
if (seen.has(c)) return name ? [name] : [];
|
|
1186
|
+
seen.add(c);
|
|
1082
1187
|
try {
|
|
1083
|
-
return
|
|
1188
|
+
return deriveExpected2(def.thunk());
|
|
1084
1189
|
} catch {
|
|
1085
1190
|
return name ? [name] : [];
|
|
1191
|
+
} finally {
|
|
1192
|
+
seen.delete(c);
|
|
1086
1193
|
}
|
|
1087
1194
|
}
|
|
1088
1195
|
default:
|
|
@@ -1210,14 +1317,14 @@ function choice(...args) {
|
|
|
1210
1317
|
const result = parsers[i].parse(input, pos, ctx);
|
|
1211
1318
|
if (!result.ok) {
|
|
1212
1319
|
rollbackCstCapture(ctx, mark);
|
|
1213
|
-
if (logLen !== void 0 && ctx._triviaLog) ctx._triviaLog.length = logLen;
|
|
1320
|
+
if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
|
|
1214
1321
|
expected.push(...result.expected);
|
|
1215
1322
|
continue;
|
|
1216
1323
|
}
|
|
1217
1324
|
const checks = autoNot[i];
|
|
1218
1325
|
if (checks && autoNotFires(input, result.span.end, checks)) {
|
|
1219
1326
|
rollbackCstCapture(ctx, mark);
|
|
1220
|
-
if (logLen !== void 0 && ctx._triviaLog) ctx._triviaLog.length = logLen;
|
|
1327
|
+
if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
|
|
1221
1328
|
continue;
|
|
1222
1329
|
}
|
|
1223
1330
|
return result;
|
|
@@ -1544,7 +1651,14 @@ function saveTriviaMark(ctx) {
|
|
|
1544
1651
|
}
|
|
1545
1652
|
function rollbackTrivia(ctx, mark) {
|
|
1546
1653
|
rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
|
|
1547
|
-
if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
|
|
1654
|
+
if (ctx._triviaLog && ctx._triviaLog.length !== mark.log) ctx._triviaLog.length = mark.log;
|
|
1655
|
+
}
|
|
1656
|
+
function saveLookaheadMark(ctx) {
|
|
1657
|
+
return { ...saveTriviaMark(ctx), probeBest: ctx._probe ? ctx._probe.best : null };
|
|
1658
|
+
}
|
|
1659
|
+
function rollbackLookahead(ctx, mark) {
|
|
1660
|
+
rollbackTrivia(ctx, mark);
|
|
1661
|
+
if (ctx._probe) ctx._probe.best = mark.probeBest;
|
|
1548
1662
|
}
|
|
1549
1663
|
function scanWithLabels(input, cur, ctx) {
|
|
1550
1664
|
const triviaP = ctx.trivia;
|
|
@@ -1905,7 +2019,7 @@ function attempt(parser2) {
|
|
|
1905
2019
|
const result = parser2.parse(input, pos, ctx);
|
|
1906
2020
|
if (result.ok) return result;
|
|
1907
2021
|
rollbackCstCapture(ctx, mark);
|
|
1908
|
-
if (log !== void 0 && ctx._triviaLog) ctx._triviaLog.length = log;
|
|
2022
|
+
if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
|
|
1909
2023
|
return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
|
|
1910
2024
|
}
|
|
1911
2025
|
};
|
|
@@ -1943,13 +2057,26 @@ function repItem(combinator, input, cur, ctx, guardable) {
|
|
|
1943
2057
|
}
|
|
1944
2058
|
return { value: result.value, end: result.span.end };
|
|
1945
2059
|
}
|
|
1946
|
-
function
|
|
2060
|
+
function resolveBounds(what, opts) {
|
|
2061
|
+
const min = opts.min ?? 0;
|
|
2062
|
+
const max = opts.max ?? Infinity;
|
|
2063
|
+
const bad = (msg) => {
|
|
2064
|
+
throw new RangeError(`parseman: ${what} ${msg}`);
|
|
2065
|
+
};
|
|
2066
|
+
if (!Number.isInteger(min) || min < 0) bad(`min must be a non-negative integer (got ${String(opts.min)})`);
|
|
2067
|
+
if (max !== Infinity && (!Number.isInteger(max) || max < 1)) bad(`max must be a positive integer (got ${String(opts.max)})`);
|
|
2068
|
+
if (max < min) bad(`max (${max}) is less than min (${min}) \u2014 the combinator could never succeed`);
|
|
2069
|
+
return { min, max };
|
|
2070
|
+
}
|
|
2071
|
+
function many(combinator, opts = {}) {
|
|
2072
|
+
const { min, max } = resolveBounds("many()", opts);
|
|
2073
|
+
if (min >= 1) return atLeast(combinator, min, max);
|
|
1947
2074
|
const meta = {
|
|
1948
2075
|
firstSet: combinator._meta.firstSet,
|
|
1949
2076
|
canMatchNewline: combinator._meta.canMatchNewline,
|
|
1950
2077
|
isTrivia: false
|
|
1951
2078
|
};
|
|
1952
|
-
const def = { tag: "many", parser: combinator, min: 0 };
|
|
2079
|
+
const def = { tag: "many", parser: combinator, min: 0, ...max === Infinity ? {} : { max } };
|
|
1953
2080
|
let expected;
|
|
1954
2081
|
const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
|
|
1955
2082
|
return {
|
|
@@ -1959,7 +2086,9 @@ function many(combinator) {
|
|
|
1959
2086
|
parse(input, pos, ctx) {
|
|
1960
2087
|
const values = def.valueUnused ? void 0 : [];
|
|
1961
2088
|
let cur = pos;
|
|
2089
|
+
let count = 0;
|
|
1962
2090
|
while (cur < input.length) {
|
|
2091
|
+
if (count >= max) break;
|
|
1963
2092
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1964
2093
|
if (item === "stop") break;
|
|
1965
2094
|
if ("fail" in item) {
|
|
@@ -1970,23 +2099,28 @@ function many(combinator) {
|
|
|
1970
2099
|
const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
|
|
1971
2100
|
if (values !== void 0) values.push(error);
|
|
1972
2101
|
captureError(ctx, error);
|
|
2102
|
+
count++;
|
|
1973
2103
|
cur = end;
|
|
1974
2104
|
continue;
|
|
1975
2105
|
}
|
|
1976
2106
|
if (values !== void 0) values.push(item.value);
|
|
2107
|
+
count++;
|
|
1977
2108
|
cur = item.end;
|
|
1978
2109
|
}
|
|
1979
2110
|
return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
|
|
1980
2111
|
}
|
|
1981
2112
|
};
|
|
1982
2113
|
}
|
|
1983
|
-
function oneOrMore(combinator) {
|
|
2114
|
+
function oneOrMore(combinator, opts = {}) {
|
|
2115
|
+
return many(combinator, { ...opts, min: opts.min ?? 1 });
|
|
2116
|
+
}
|
|
2117
|
+
function atLeast(combinator, min, max) {
|
|
1984
2118
|
const meta = {
|
|
1985
2119
|
firstSet: combinator._meta.firstSet,
|
|
1986
2120
|
canMatchNewline: combinator._meta.canMatchNewline,
|
|
1987
2121
|
isTrivia: false
|
|
1988
2122
|
};
|
|
1989
|
-
const def = { tag: "oneOrMore", parser: combinator, min:
|
|
2123
|
+
const def = { tag: "oneOrMore", parser: combinator, min, ...max === Infinity ? {} : { max } };
|
|
1990
2124
|
let expected;
|
|
1991
2125
|
const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
|
|
1992
2126
|
return {
|
|
@@ -1998,7 +2132,19 @@ function oneOrMore(combinator) {
|
|
|
1998
2132
|
if (!first.ok) return first;
|
|
1999
2133
|
const values = def.valueUnused ? void 0 : [first.value];
|
|
2000
2134
|
let cur = first.span.end;
|
|
2135
|
+
let count = 1;
|
|
2136
|
+
while (count < min) {
|
|
2137
|
+
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
2138
|
+
if (item === "stop" || "fail" in item) {
|
|
2139
|
+
expected ??= deriveExpected(combinator);
|
|
2140
|
+
return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
|
|
2141
|
+
}
|
|
2142
|
+
if (values !== void 0) values.push(item.value);
|
|
2143
|
+
cur = item.end;
|
|
2144
|
+
count++;
|
|
2145
|
+
}
|
|
2001
2146
|
while (cur < input.length) {
|
|
2147
|
+
if (count >= max) break;
|
|
2002
2148
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
2003
2149
|
if (item === "stop") break;
|
|
2004
2150
|
if ("fail" in item) {
|
|
@@ -2009,10 +2155,12 @@ function oneOrMore(combinator) {
|
|
|
2009
2155
|
const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
|
|
2010
2156
|
if (values !== void 0) values.push(error);
|
|
2011
2157
|
captureError(ctx, error);
|
|
2158
|
+
count++;
|
|
2012
2159
|
cur = end;
|
|
2013
2160
|
continue;
|
|
2014
2161
|
}
|
|
2015
2162
|
if (values !== void 0) values.push(item.value);
|
|
2163
|
+
count++;
|
|
2016
2164
|
cur = item.end;
|
|
2017
2165
|
}
|
|
2018
2166
|
return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
|
|
@@ -2042,17 +2190,33 @@ function optional(combinator) {
|
|
|
2042
2190
|
}
|
|
2043
2191
|
};
|
|
2044
2192
|
}
|
|
2045
|
-
function
|
|
2193
|
+
function oneOrMoreSep(combinator, separator, opts = {}) {
|
|
2194
|
+
return sepBy(combinator, separator, { ...opts, min: opts.min ?? 1 });
|
|
2195
|
+
}
|
|
2196
|
+
function sepBy(combinator, separator, opts = {}) {
|
|
2197
|
+
const { min, max } = resolveBounds("sepBy()", opts);
|
|
2198
|
+
const trailing = opts.trailing ?? "forbid";
|
|
2046
2199
|
const meta = {
|
|
2047
2200
|
firstSet: combinator._meta.firstSet,
|
|
2048
2201
|
canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
|
|
2049
2202
|
isTrivia: false
|
|
2050
2203
|
};
|
|
2051
2204
|
let expected;
|
|
2205
|
+
const failAt2 = (at) => {
|
|
2206
|
+
expected ??= deriveExpected(combinator);
|
|
2207
|
+
return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: at, end: at } };
|
|
2208
|
+
};
|
|
2052
2209
|
return {
|
|
2053
2210
|
_tag: "sepBy",
|
|
2054
2211
|
_meta: meta,
|
|
2055
|
-
_def: {
|
|
2212
|
+
_def: {
|
|
2213
|
+
tag: "sepBy",
|
|
2214
|
+
parser: combinator,
|
|
2215
|
+
separator,
|
|
2216
|
+
min,
|
|
2217
|
+
...max === Infinity ? {} : { max },
|
|
2218
|
+
...trailing === "forbid" ? {} : { trailing }
|
|
2219
|
+
},
|
|
2056
2220
|
parse(input, pos, ctx) {
|
|
2057
2221
|
const first = combinator.parse(input, pos, ctx);
|
|
2058
2222
|
const values = [];
|
|
@@ -2063,6 +2227,7 @@ function sepBy(combinator, separator) {
|
|
|
2063
2227
|
} else {
|
|
2064
2228
|
const term = ctx._tolerant ? ctx._sync : void 0;
|
|
2065
2229
|
if (term === void 0 || matchesAt(term, input, pos, ctx)) {
|
|
2230
|
+
if (min >= 1) return failAt2(pos);
|
|
2066
2231
|
return { ok: true, value: [], span: { start: pos, end: pos } };
|
|
2067
2232
|
}
|
|
2068
2233
|
expected ??= deriveExpected(combinator);
|
|
@@ -2072,6 +2237,7 @@ function sepBy(combinator, separator) {
|
|
|
2072
2237
|
cur = rec.end;
|
|
2073
2238
|
}
|
|
2074
2239
|
while (cur < input.length) {
|
|
2240
|
+
if (values.length >= max) break;
|
|
2075
2241
|
const loopMark = saveTriviaMark(ctx);
|
|
2076
2242
|
let sepPos = cur;
|
|
2077
2243
|
if (ctx.trivia) {
|
|
@@ -2088,6 +2254,7 @@ function sepBy(combinator, separator) {
|
|
|
2088
2254
|
rollbackTrivia(ctx, loopMark);
|
|
2089
2255
|
break;
|
|
2090
2256
|
}
|
|
2257
|
+
const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
|
|
2091
2258
|
let nextPos = sep2.span.end;
|
|
2092
2259
|
if (ctx.trivia) {
|
|
2093
2260
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
@@ -2109,12 +2276,18 @@ function sepBy(combinator, separator) {
|
|
|
2109
2276
|
cur = rec.end;
|
|
2110
2277
|
continue;
|
|
2111
2278
|
}
|
|
2279
|
+
if (sepMark !== void 0) {
|
|
2280
|
+
rollbackTrivia(ctx, sepMark);
|
|
2281
|
+
cur = sep2.span.end;
|
|
2282
|
+
break;
|
|
2283
|
+
}
|
|
2112
2284
|
rollbackTrivia(ctx, loopMark);
|
|
2113
2285
|
break;
|
|
2114
2286
|
}
|
|
2115
2287
|
values.push(next.value);
|
|
2116
2288
|
cur = next.span.end;
|
|
2117
2289
|
}
|
|
2290
|
+
if (values.length < min) return failAt2(cur);
|
|
2118
2291
|
return { ok: true, value: values, span: { start: pos, end: cur } };
|
|
2119
2292
|
}
|
|
2120
2293
|
};
|
|
@@ -2167,6 +2340,7 @@ function markUnusedValues(root) {
|
|
|
2167
2340
|
visit(def.separator, false);
|
|
2168
2341
|
return;
|
|
2169
2342
|
case "not":
|
|
2343
|
+
case "peek":
|
|
2170
2344
|
visit(def.parser, false);
|
|
2171
2345
|
return;
|
|
2172
2346
|
case "grammar":
|
|
@@ -2285,9 +2459,9 @@ function not(combinator) {
|
|
|
2285
2459
|
_meta: meta,
|
|
2286
2460
|
_def: { tag: "not", parser: combinator },
|
|
2287
2461
|
parse(input, pos, ctx) {
|
|
2288
|
-
const mark =
|
|
2462
|
+
const mark = saveLookaheadMark(ctx);
|
|
2289
2463
|
const result = combinator.parse(input, pos, ctx);
|
|
2290
|
-
|
|
2464
|
+
rollbackLookahead(ctx, mark);
|
|
2291
2465
|
if (result.ok) {
|
|
2292
2466
|
return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
|
|
2293
2467
|
}
|
|
@@ -2296,6 +2470,28 @@ function not(combinator) {
|
|
|
2296
2470
|
};
|
|
2297
2471
|
}
|
|
2298
2472
|
|
|
2473
|
+
// src/combinators/peek.ts
|
|
2474
|
+
function peek(combinator) {
|
|
2475
|
+
const meta = {
|
|
2476
|
+
// A nullable body imposes no first-char constraint — `any()`, not its set.
|
|
2477
|
+
firstSet: matchesEmpty(combinator) ? any() : combinator._meta.firstSet,
|
|
2478
|
+
canMatchNewline: false,
|
|
2479
|
+
isTrivia: false
|
|
2480
|
+
};
|
|
2481
|
+
return {
|
|
2482
|
+
_tag: "peek",
|
|
2483
|
+
_meta: meta,
|
|
2484
|
+
_def: { tag: "peek", parser: combinator },
|
|
2485
|
+
parse(input, pos, ctx) {
|
|
2486
|
+
const mark = saveLookaheadMark(ctx);
|
|
2487
|
+
const result = combinator.parse(input, pos, ctx);
|
|
2488
|
+
rollbackLookahead(ctx, mark);
|
|
2489
|
+
if (result.ok) return { ok: true, value: null, span: { start: pos, end: pos } };
|
|
2490
|
+
return { ok: false, expected: [`peek(${combinator._tag})`], span: { start: pos, end: pos } };
|
|
2491
|
+
}
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2299
2495
|
// src/compiler/build-arity.ts
|
|
2300
2496
|
var PARAM_LIST_RE = (
|
|
2301
2497
|
// (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
|
|
@@ -2394,6 +2590,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2394
2590
|
case "expect":
|
|
2395
2591
|
case "withCtx":
|
|
2396
2592
|
case "not":
|
|
2593
|
+
case "peek":
|
|
2397
2594
|
return parserHasOwnFields(d.parser, seen);
|
|
2398
2595
|
default:
|
|
2399
2596
|
return false;
|
|
@@ -2433,6 +2630,7 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2433
2630
|
case "expect":
|
|
2434
2631
|
case "withCtx":
|
|
2435
2632
|
case "not":
|
|
2633
|
+
case "peek":
|
|
2436
2634
|
case "field":
|
|
2437
2635
|
return parserHasTriviaSite(d.parser, seen);
|
|
2438
2636
|
case "choice":
|
|
@@ -2482,6 +2680,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2482
2680
|
case "expect":
|
|
2483
2681
|
case "withCtx":
|
|
2484
2682
|
case "not":
|
|
2683
|
+
case "peek":
|
|
2485
2684
|
return parserEnablesTriviaCapture(d.parser, seen);
|
|
2486
2685
|
// leaf() clears the inner CST/trivia collector and publishes only its own
|
|
2487
2686
|
// terminal result. An inner parser() capture cannot reach this node.
|
|
@@ -2830,7 +3029,7 @@ function leaf(root, fn) {
|
|
|
2830
3029
|
}
|
|
2831
3030
|
|
|
2832
3031
|
// src/version.ts
|
|
2833
|
-
var PARSEMAN_VERSION = "0.
|
|
3032
|
+
var PARSEMAN_VERSION = "0.35.0";
|
|
2834
3033
|
|
|
2835
3034
|
// src/analysis/gating.ts
|
|
2836
3035
|
var isAny = (fs2) => fs2.kind === "any";
|
|
@@ -2870,14 +3069,15 @@ function firstSetToString(fs2) {
|
|
|
2870
3069
|
var ruleNameOf = (p) => p._ruleName;
|
|
2871
3070
|
var SUGGESTIONS = {
|
|
2872
3071
|
"broad-recognizer": "if this arm leads with a keyword regex, use word('kw', boundary) / keywords([...]) \u2014 they expose an EXACT, gating first-set and lower to the same charCodeAt scan. A genuine scanTo fallback is fine; accept it in the gating snapshot if intentional.",
|
|
2873
|
-
"leading-not": "let the arm lead with its actual consuming terminal (first-sets gate it automatically); keep not(...) only as a TRAILING boundary. Do not hand-roll not(not(...)) to fake gating.",
|
|
2874
|
-
"nullable-prefix": "a leading optional/many lets a later, broad term start the arm. Split the empty case into its own arm, or gate on the prefix first char.",
|
|
3072
|
+
"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.",
|
|
3073
|
+
"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.",
|
|
2875
3074
|
"cross-artifact-ref": "parseman >=0.32.0 resolves a g.Foo ref first-set at fuse time; if still ANY the target rule is itself ungated \u2014 analyze it and give it a concrete non-nullable lead.",
|
|
2876
3075
|
"opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
|
|
2877
3076
|
"ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
|
|
2878
3077
|
};
|
|
2879
|
-
function classifyBroadArm(arm) {
|
|
3078
|
+
function classifyBroadArm(arm, resolve2) {
|
|
2880
3079
|
const seen = /* @__PURE__ */ new Set();
|
|
3080
|
+
const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
|
|
2881
3081
|
const walk2 = (p) => {
|
|
2882
3082
|
if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
|
|
2883
3083
|
seen.add(p);
|
|
@@ -2889,6 +3089,10 @@ function classifyBroadArm(arm) {
|
|
|
2889
3089
|
return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
|
|
2890
3090
|
case "not":
|
|
2891
3091
|
return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
|
|
3092
|
+
// peek() CARRIES its body's first-set, so it is only broad when the body is
|
|
3093
|
+
// (or the body is nullable) — walk in for the real cause.
|
|
3094
|
+
case "peek":
|
|
3095
|
+
return walk2(d.parser);
|
|
2892
3096
|
case "scanTo":
|
|
2893
3097
|
return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
|
|
2894
3098
|
// `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
|
|
@@ -2905,18 +3109,23 @@ function classifyBroadArm(arm) {
|
|
|
2905
3109
|
return walk2(d.parser);
|
|
2906
3110
|
case "lazy": {
|
|
2907
3111
|
const name = p._ruleName;
|
|
3112
|
+
let target;
|
|
2908
3113
|
try {
|
|
2909
|
-
|
|
2910
|
-
if (name !== void 0) return inner ? { cause: "cross-artifact-ref", detail: `via ref g.${name} \u2192 ${inner.detail}` } : null;
|
|
2911
|
-
return inner;
|
|
3114
|
+
target = d.thunk();
|
|
2912
3115
|
} catch {
|
|
2913
|
-
|
|
3116
|
+
const bound = name !== void 0 ? resolve2?.(name) : void 0;
|
|
3117
|
+
if (bound === void 0)
|
|
3118
|
+
return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
|
|
3119
|
+
target = bound;
|
|
2914
3120
|
}
|
|
3121
|
+
const inner = walk2(target);
|
|
3122
|
+
if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
|
|
3123
|
+
return inner;
|
|
2915
3124
|
}
|
|
2916
3125
|
case "optional":
|
|
2917
3126
|
case "many": {
|
|
2918
3127
|
const inner = walk2(d.parser);
|
|
2919
|
-
return
|
|
3128
|
+
return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
|
|
2920
3129
|
}
|
|
2921
3130
|
case "oneOrMore":
|
|
2922
3131
|
case "transform":
|
|
@@ -2938,13 +3147,13 @@ function classifyBroadArm(arm) {
|
|
|
2938
3147
|
sawNullablePrefix = true;
|
|
2939
3148
|
continue;
|
|
2940
3149
|
}
|
|
2941
|
-
if (isAny(firstSetOf(t))) {
|
|
3150
|
+
if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve2))) {
|
|
2942
3151
|
const inner = walk2(t);
|
|
2943
3152
|
if (sawNullablePrefix)
|
|
2944
|
-
return
|
|
3153
|
+
return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
|
|
2945
3154
|
return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
|
|
2946
3155
|
}
|
|
2947
|
-
if (matchesEmpty(t)) {
|
|
3156
|
+
if (matchesEmpty(t, /* @__PURE__ */ new Set(), resolve2)) {
|
|
2948
3157
|
sawNullablePrefix = true;
|
|
2949
3158
|
continue;
|
|
2950
3159
|
}
|
|
@@ -2955,7 +3164,7 @@ function classifyBroadArm(arm) {
|
|
|
2955
3164
|
case "choice": {
|
|
2956
3165
|
for (const a of d.parsers) {
|
|
2957
3166
|
const r = walk2(a);
|
|
2958
|
-
if (r) return
|
|
3167
|
+
if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
|
|
2959
3168
|
}
|
|
2960
3169
|
return null;
|
|
2961
3170
|
}
|
|
@@ -2992,7 +3201,7 @@ function detectAntiPatterns(rule, arms) {
|
|
|
2992
3201
|
kind: "double-not",
|
|
2993
3202
|
rule,
|
|
2994
3203
|
armIndex: i,
|
|
2995
|
-
message: "not(not(...)) hand-rolls automatic first-char gating and MISCOMPILES among shared-first-char sibling arms (its first-set is ANY, poisoning dispatch).
|
|
3204
|
+
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."
|
|
2996
3205
|
});
|
|
2997
3206
|
} else {
|
|
2998
3207
|
out.push({
|
|
@@ -3015,6 +3224,9 @@ function detectAntiPatterns(rule, arms) {
|
|
|
3015
3224
|
return out;
|
|
3016
3225
|
}
|
|
3017
3226
|
function analyzeGating(entry, opts) {
|
|
3227
|
+
return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
|
|
3228
|
+
}
|
|
3229
|
+
function analyzeGatingRules(ruleMap, opts) {
|
|
3018
3230
|
const raw = [];
|
|
3019
3231
|
const antiPatterns = [];
|
|
3020
3232
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -3024,7 +3236,7 @@ function analyzeGating(entry, opts) {
|
|
|
3024
3236
|
const d = p._def;
|
|
3025
3237
|
const rule = ruleNameOf(p) ?? enclosingRule;
|
|
3026
3238
|
if (d.tag === "choice") {
|
|
3027
|
-
raw.push({ g: analyzeChoice(p, d, rule), rule });
|
|
3239
|
+
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
|
|
3028
3240
|
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
3029
3241
|
}
|
|
3030
3242
|
const rec = d;
|
|
@@ -3041,7 +3253,7 @@ function analyzeGating(entry, opts) {
|
|
|
3041
3253
|
}
|
|
3042
3254
|
for (const k of kids) visit(k, rule);
|
|
3043
3255
|
};
|
|
3044
|
-
visit(
|
|
3256
|
+
for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
|
|
3045
3257
|
const perRule = /* @__PURE__ */ new Map();
|
|
3046
3258
|
for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
|
|
3047
3259
|
const seenInRule = /* @__PURE__ */ new Map();
|
|
@@ -3057,18 +3269,19 @@ function analyzeGating(entry, opts) {
|
|
|
3057
3269
|
});
|
|
3058
3270
|
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
3059
3271
|
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
3060
|
-
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted);
|
|
3272
|
+
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted && !c.deferred);
|
|
3061
3273
|
const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
|
|
3274
|
+
const deferred = choices.filter((c) => c.gates === "no" && !c.accepted && c.deferred);
|
|
3062
3275
|
const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
|
|
3063
|
-
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, acceptedUnused, choices, antiPatterns };
|
|
3276
|
+
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, deferred, acceptedUnused, choices, antiPatterns };
|
|
3064
3277
|
}
|
|
3065
|
-
function analyzeChoice(p, d, rule) {
|
|
3278
|
+
function analyzeChoice(p, d, rule, resolve2) {
|
|
3066
3279
|
const arms = d.parsers;
|
|
3067
3280
|
const shallow = arms.map((a) => a._meta.firstSet);
|
|
3068
|
-
const deep = arms.map((a) => firstSetOf(a));
|
|
3281
|
+
const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve2));
|
|
3069
3282
|
const anyArms = deep.map((fs2, index) => ({ fs: fs2, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
|
|
3070
|
-
const { cause, detail } = classifyBroadArm(arms[index]);
|
|
3071
|
-
return { index, cause, detail, shallowAnyOnly: false, suggestion: SUGGESTIONS[cause] };
|
|
3283
|
+
const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve2);
|
|
3284
|
+
return { index, cause, detail, shallowAnyOnly: false, unresolvedExternal: unresolvedExternal === true, suggestion: SUGGESTIONS[cause] };
|
|
3072
3285
|
});
|
|
3073
3286
|
const overlaps = [];
|
|
3074
3287
|
for (let i = 0; i < deep.length; i++)
|
|
@@ -3084,10 +3297,12 @@ function analyzeChoice(p, d, rule) {
|
|
|
3084
3297
|
if (d.disjoint) gates = "yes";
|
|
3085
3298
|
else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
|
|
3086
3299
|
else gates = "no";
|
|
3300
|
+
const deferred = gates === "no" && overlaps.length === 0 && anyArms.length > 0 && anyArms.every((a) => a.unresolvedExternal);
|
|
3087
3301
|
return {
|
|
3088
3302
|
rule,
|
|
3089
3303
|
strategy: d.strategy?.tag ?? "firstMatch",
|
|
3090
3304
|
gates,
|
|
3305
|
+
deferred,
|
|
3091
3306
|
combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
|
|
3092
3307
|
anyArms,
|
|
3093
3308
|
overlaps
|
|
@@ -3254,6 +3469,7 @@ function children(def, winners) {
|
|
|
3254
3469
|
case "field":
|
|
3255
3470
|
case "grammar":
|
|
3256
3471
|
case "not":
|
|
3472
|
+
case "peek":
|
|
3257
3473
|
case "node":
|
|
3258
3474
|
case "guard":
|
|
3259
3475
|
case "withCtx":
|
|
@@ -5012,9 +5228,9 @@ function planDisjointDispatch(parsers) {
|
|
|
5012
5228
|
return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
|
|
5013
5229
|
}
|
|
5014
5230
|
function captureRestoreBody(mL, mR, mTl, mLg, mF = null) {
|
|
5015
|
-
const fieldRestore = mF ? `; if (_ctx._fields) _ctx._fields.length = ${mF}` : "";
|
|
5016
|
-
const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
|
|
5017
|
-
return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
|
|
5231
|
+
const fieldRestore = mF ? `; if (_ctx._fields && _ctx._fields.length !== ${mF}) _ctx._fields.length = ${mF}` : "";
|
|
5232
|
+
const base = `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${mL}) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${mR}) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${mTl}) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
|
|
5233
|
+
return mLg ? `${base}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${mLg}) _ctx._triviaLog.length = ${mLg}` : base;
|
|
5018
5234
|
}
|
|
5019
5235
|
function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
|
|
5020
5236
|
if (seen.has(p)) return false;
|
|
@@ -5027,6 +5243,9 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
5027
5243
|
case "keywords":
|
|
5028
5244
|
case "guard":
|
|
5029
5245
|
case "not":
|
|
5246
|
+
// peek(): emitted under a non-capturing probe ctx and zero-width on both
|
|
5247
|
+
// outcomes, so it can never leave a partial capture behind.
|
|
5248
|
+
case "peek":
|
|
5030
5249
|
case "trivia":
|
|
5031
5250
|
case "token":
|
|
5032
5251
|
case "leaf":
|
|
@@ -5098,6 +5317,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
5098
5317
|
case "leaf":
|
|
5099
5318
|
return true;
|
|
5100
5319
|
case "not":
|
|
5320
|
+
case "peek":
|
|
5101
5321
|
case "guard":
|
|
5102
5322
|
case "trivia":
|
|
5103
5323
|
case "unknown":
|
|
@@ -5224,6 +5444,7 @@ function emitKeywordsFast(def, ctx, pos) {
|
|
|
5224
5444
|
if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
|
|
5225
5445
|
if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
|
|
5226
5446
|
if (def.caseInsensitive && def.boundary) return null;
|
|
5447
|
+
if (def.caseInsensitive && def.words.some((w) => w.split("").some((c) => c.charCodeAt(0) > 127))) return null;
|
|
5227
5448
|
let boundary = null;
|
|
5228
5449
|
if (def.boundary) {
|
|
5229
5450
|
let body = def.boundary;
|
|
@@ -5271,7 +5492,7 @@ function emitKeywords(def, ctx, pos) {
|
|
|
5271
5492
|
if (fast) return fast;
|
|
5272
5493
|
const alt = def.words.map(escapeKeywordRe).join("|");
|
|
5273
5494
|
const boundary = def.boundary ? `(?![${def.boundary}])` : "";
|
|
5274
|
-
const flags = def.caseInsensitive ? "
|
|
5495
|
+
const flags = def.caseInsensitive ? "iy" : "uy";
|
|
5275
5496
|
const source = `(?:${alt})${boundary}`;
|
|
5276
5497
|
const key = `${source}/${flags}`;
|
|
5277
5498
|
let rName = ctx.regexMap.get(key);
|
|
@@ -5371,7 +5592,7 @@ function emitSeqValues(def, ctx, pos) {
|
|
|
5371
5592
|
const endAfterV = v(ctx, "_sea");
|
|
5372
5593
|
stmts.push(
|
|
5373
5594
|
`${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
|
|
5374
|
-
`${ind(ctx)}if (${endAfterV} > ${scanEndV}) { ${curV} = ${endAfterV} } else { if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; }`
|
|
5595
|
+
`${ind(ctx)}if (${endAfterV} > ${scanEndV}) { ${curV} = ${endAfterV} } else { if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markV}) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${markTl}) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${markLog}) _ctx._triviaLog.length = ${markLog}; }`
|
|
5375
5596
|
);
|
|
5376
5597
|
valueVars.push(r2.valueVar);
|
|
5377
5598
|
continue;
|
|
@@ -5417,6 +5638,7 @@ function failsAtStart(p) {
|
|
|
5417
5638
|
case "keywords":
|
|
5418
5639
|
case "guard":
|
|
5419
5640
|
case "not":
|
|
5641
|
+
case "peek":
|
|
5420
5642
|
return true;
|
|
5421
5643
|
case "transform":
|
|
5422
5644
|
case "label":
|
|
@@ -5830,14 +6052,17 @@ function emitMany(def, ctx, pos) {
|
|
|
5830
6052
|
];
|
|
5831
6053
|
const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
|
|
5832
6054
|
if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
|
|
5833
|
-
|
|
6055
|
+
for (let i = 0; i < def.min; i++) {
|
|
5834
6056
|
const firstR = emit(def.parser, ctx, curV);
|
|
5835
6057
|
stmts.push(...firstR.stmts);
|
|
5836
6058
|
if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${firstR.valueVar})`);
|
|
5837
6059
|
stmts.push(`${ind(ctx)}${curV} = ${firstR.endVar}`);
|
|
5838
6060
|
}
|
|
6061
|
+
const maxV = def.max === void 0 ? null : wantValue ? `${arrV}.length` : v(ctx, "_cnt");
|
|
6062
|
+
if (maxV !== null && !wantValue) stmts.push(`${ind(ctx)}let ${maxV} = ${def.min}`);
|
|
5839
6063
|
stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
|
|
5840
6064
|
ctx.indent++;
|
|
6065
|
+
if (maxV !== null) stmts.push(`${ind(ctx)}if (${maxV} >= ${def.max}) break`);
|
|
5841
6066
|
let itemPos = curV;
|
|
5842
6067
|
let rollback = "";
|
|
5843
6068
|
if (ctx.activeTrivia) {
|
|
@@ -5854,7 +6079,7 @@ function emitMany(def, ctx, pos) {
|
|
|
5854
6079
|
`${ind(ctx)}const ${npV} = ${capFn}(input, ${curV}, _ctx, 1)`
|
|
5855
6080
|
);
|
|
5856
6081
|
itemPos = npV;
|
|
5857
|
-
rollback = `if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; `;
|
|
6082
|
+
rollback = `if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markV}) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${markTl}) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${markLog}) _ctx._triviaLog.length = ${markLog}; `;
|
|
5858
6083
|
} else {
|
|
5859
6084
|
const trivFn = ensureTriviaFn(ctx);
|
|
5860
6085
|
const npV = v(ctx, "_np");
|
|
@@ -5878,7 +6103,7 @@ function emitMany(def, ctx, pos) {
|
|
|
5878
6103
|
`${ind(ctx)}if (!${iterOk}) {`,
|
|
5879
6104
|
`${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
|
|
5880
6105
|
`${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
|
|
5881
|
-
...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
|
|
6106
|
+
...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : maxV !== null ? [`${ind(ctx)} ${maxV}++`] : [],
|
|
5882
6107
|
`${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
|
|
5883
6108
|
`${ind(ctx)} ${curV} = ${rrV}.end`,
|
|
5884
6109
|
`${ind(ctx)} continue`,
|
|
@@ -5891,6 +6116,7 @@ function emitMany(def, ctx, pos) {
|
|
|
5891
6116
|
stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
|
|
5892
6117
|
}
|
|
5893
6118
|
if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
|
|
6119
|
+
else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
|
|
5894
6120
|
stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
|
|
5895
6121
|
ctx.indent--;
|
|
5896
6122
|
stmts.push(`${ind(ctx)}}`);
|
|
@@ -5916,7 +6142,7 @@ function emitAttempt(p, def, ctx, pos) {
|
|
|
5916
6142
|
const log = v(ctx, "_atg");
|
|
5917
6143
|
const fields = v(ctx, "_atf");
|
|
5918
6144
|
const errors = v(ctx, "_ate");
|
|
5919
|
-
const rollback = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${leaves}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${raw}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${trivia2}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${log}; if (_ctx._fields) _ctx._fields.length = ${fields}; if (_ctx._errors) _ctx._errors.length = ${errors}`;
|
|
6145
|
+
const rollback = `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${leaves}) _ctx._cstLeaves.length = ${leaves}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${raw}) _ctx._cstRawChildren.length = ${raw}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${trivia2}) _ctx._cstTriviaLog.length = ${trivia2}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${log}) _ctx._triviaLog.length = ${log}; if (_ctx._fields && _ctx._fields.length !== ${fields}) _ctx._fields.length = ${fields}; if (_ctx._errors && _ctx._errors.length !== ${errors}) _ctx._errors.length = ${errors}`;
|
|
5920
6146
|
const traceId = ctx.coverage?.plan.attempts.get(p);
|
|
5921
6147
|
const traceRollback = traceId === void 0 ? "" : ` _ctx._grammarTrace?.write({ id: ${JSON.stringify(traceId)}, phase: 'rollback', offset: ${pos} });`;
|
|
5922
6148
|
const preGuard = [];
|
|
@@ -5955,8 +6181,8 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5955
6181
|
...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
|
|
5956
6182
|
...firstStmts
|
|
5957
6183
|
];
|
|
5958
|
-
const failItem = (nextOkVar, itemPosVar,
|
|
5959
|
-
if (!rec) return [
|
|
6184
|
+
const failItem = (nextOkVar, itemPosVar, breakStmt) => {
|
|
6185
|
+
if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
|
|
5960
6186
|
const rr = v(ctx, "_rr");
|
|
5961
6187
|
return [
|
|
5962
6188
|
`${ind(ctx)}if (!${nextOkVar}) {`,
|
|
@@ -5964,10 +6190,24 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5964
6190
|
`${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
|
|
5965
6191
|
`${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
|
|
5966
6192
|
`${ind(ctx)} }`,
|
|
5967
|
-
`${ind(ctx)} ${
|
|
6193
|
+
`${ind(ctx)} ${breakStmt}`,
|
|
5968
6194
|
`${ind(ctx)}}`
|
|
5969
6195
|
];
|
|
5970
6196
|
};
|
|
6197
|
+
const itemFailBreak = (sepEndVar, sepRb, postSepRb) => def.trailing === void 0 ? `${sepRb}break` : `${postSepRb}${curV} = ${sepEndVar}; break`;
|
|
6198
|
+
const postSepMarks = () => {
|
|
6199
|
+
if (def.trailing === void 0 || !ctx.capturing) return { decl: [], rb: "" };
|
|
6200
|
+
const lv = v(ctx, "_tlv"), rw = v(ctx, "_trw"), tl = v(ctx, "_ttl"), lg = v(ctx, "_tlg");
|
|
6201
|
+
return {
|
|
6202
|
+
decl: [
|
|
6203
|
+
`${ind(ctx)}const ${lv} = _ctx._cstLeaves ? _ctx._cstLeaves.length : 0`,
|
|
6204
|
+
`${ind(ctx)}const ${rw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`,
|
|
6205
|
+
`${ind(ctx)}const ${tl} = _ctx._cstTriviaLog ? _ctx._cstTriviaLog.length : 0`,
|
|
6206
|
+
`${ind(ctx)}const ${lg} = _ctx._triviaLog ? _ctx._triviaLog.length : 0`
|
|
6207
|
+
],
|
|
6208
|
+
rb: `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${lv}) _ctx._cstLeaves.length = ${lv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${rw}) _ctx._cstRawChildren.length = ${rw}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${tl}) _ctx._cstTriviaLog.length = ${tl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${lg}) _ctx._triviaLog.length = ${lg}; `
|
|
6209
|
+
};
|
|
6210
|
+
};
|
|
5971
6211
|
if (rec) {
|
|
5972
6212
|
const rr0 = v(ctx, "_rr0");
|
|
5973
6213
|
const didV = v(ctx, "_did");
|
|
@@ -5993,6 +6233,7 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5993
6233
|
);
|
|
5994
6234
|
ctx.indent++;
|
|
5995
6235
|
}
|
|
6236
|
+
if (def.max !== void 0) stmts.push(`${ind(ctx)}if (${arrV}.length >= ${def.max}) break`);
|
|
5996
6237
|
let sepAtPos = curV;
|
|
5997
6238
|
if (ctx.activeTrivia) {
|
|
5998
6239
|
if (ctx.capturing) {
|
|
@@ -6010,15 +6251,17 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6010
6251
|
`${ind(ctx)}const ${spV} = ${capFn}(input, ${curV}, _ctx, 1)`
|
|
6011
6252
|
);
|
|
6012
6253
|
sepAtPos = spV;
|
|
6013
|
-
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}; `;
|
|
6254
|
+
const rollbackToSep = `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${markLv}) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markV}) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${markTl}) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${markLog}) _ctx._triviaLog.length = ${markLog}; `;
|
|
6014
6255
|
const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
|
|
6015
6256
|
stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
|
|
6257
|
+
const post = postSepMarks();
|
|
6258
|
+
stmts.push(...post.decl);
|
|
6016
6259
|
const npV = v(ctx, "_np");
|
|
6017
6260
|
stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
|
|
6018
6261
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
|
|
6019
6262
|
stmts.push(
|
|
6020
6263
|
...nextStmts,
|
|
6021
|
-
...failItem(nextOk, npV, rollbackToSep,
|
|
6264
|
+
...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
|
|
6022
6265
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6023
6266
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6024
6267
|
);
|
|
@@ -6034,7 +6277,7 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6034
6277
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
|
|
6035
6278
|
stmts.push(
|
|
6036
6279
|
...nextStmts,
|
|
6037
|
-
...failItem(nextOk, npV, "",
|
|
6280
|
+
...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
|
|
6038
6281
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6039
6282
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6040
6283
|
);
|
|
@@ -6050,11 +6293,13 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6050
6293
|
}
|
|
6051
6294
|
const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
|
|
6052
6295
|
stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
|
|
6053
|
-
const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
|
|
6296
|
+
const nextRb = markLv ? `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${markLv}) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markRw}) _ctx._cstRawChildren.length = ${markRw}; ` : "";
|
|
6297
|
+
const post = postSepMarks();
|
|
6298
|
+
stmts.push(...post.decl);
|
|
6054
6299
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
|
|
6055
6300
|
stmts.push(
|
|
6056
6301
|
...nextStmts,
|
|
6057
|
-
...failItem(nextOk, sepEnd, nextRb,
|
|
6302
|
+
...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
|
|
6058
6303
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6059
6304
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6060
6305
|
);
|
|
@@ -6063,6 +6308,9 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6063
6308
|
stmts.push(`${ind(ctx)}}`);
|
|
6064
6309
|
ctx.indent--;
|
|
6065
6310
|
stmts.push(`${ind(ctx)}}`);
|
|
6311
|
+
if (def.min >= 1) {
|
|
6312
|
+
stmts.push(...emitIfFail(ctx, `${arrV}.length < ${def.min}`, failArrBody(ctx, deriveExpectedArr([def.parser]), curV)));
|
|
6313
|
+
}
|
|
6066
6314
|
return { stmts, valueVar: arrV, endVar: curV };
|
|
6067
6315
|
}
|
|
6068
6316
|
function emitScanTo(def, ctx, pos) {
|
|
@@ -6114,17 +6362,44 @@ function emitScanTo(def, ctx, pos) {
|
|
|
6114
6362
|
return { stmts, valueVar: valV, endVar: curV };
|
|
6115
6363
|
}
|
|
6116
6364
|
function emitNot(def, ctx, pos) {
|
|
6365
|
+
const sinksLive = !!ctx.capturing || !!ctx.recovery;
|
|
6366
|
+
const leaves = sinksLive ? v(ctx, "_ntl") : null;
|
|
6367
|
+
const raw = sinksLive ? v(ctx, "_ntr") : null;
|
|
6368
|
+
const tl = sinksLive ? v(ctx, "_ntt") : null;
|
|
6369
|
+
const log = sinksLive ? v(ctx, "_ntg") : null;
|
|
6370
|
+
const fields = sinksLive ? v(ctx, "_ntf") : null;
|
|
6371
|
+
const errors = sinksLive ? v(ctx, "_nte") : null;
|
|
6117
6372
|
const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
|
|
6118
6373
|
const label2 = JSON.stringify(`not(${def.parser._tag})`);
|
|
6119
6374
|
return {
|
|
6120
6375
|
stmts: [
|
|
6376
|
+
...sinksLive ? [
|
|
6377
|
+
`${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`
|
|
6378
|
+
] : [],
|
|
6121
6379
|
...stmts,
|
|
6380
|
+
...sinksLive ? [
|
|
6381
|
+
`${ind(ctx)}if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${leaves}) _ctx._cstLeaves.length = ${leaves}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${raw}) _ctx._cstRawChildren.length = ${raw}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${tl}) _ctx._cstTriviaLog.length = ${tl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${log}) _ctx._triviaLog.length = ${log}; if (_ctx._fields && _ctx._fields.length !== ${fields}) _ctx._fields.length = ${fields}; if (_ctx._errors && _ctx._errors.length !== ${errors}) _ctx._errors.length = ${errors}`
|
|
6382
|
+
] : [],
|
|
6122
6383
|
...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
|
|
6123
6384
|
],
|
|
6124
6385
|
valueVar: "null",
|
|
6125
6386
|
endVar: pos
|
|
6126
6387
|
};
|
|
6127
6388
|
}
|
|
6389
|
+
function emitPeek(def, ctx, pos) {
|
|
6390
|
+
const probeCtx = { ...ctx, capturing: false, noHoist: true };
|
|
6391
|
+
const { stmts, okVar } = emitFallible(def.parser, probeCtx, pos, true);
|
|
6392
|
+
ctx.vars = probeCtx.vars;
|
|
6393
|
+
const label2 = JSON.stringify(`peek(${def.parser._tag})`);
|
|
6394
|
+
return {
|
|
6395
|
+
stmts: [
|
|
6396
|
+
...stmts,
|
|
6397
|
+
...emitIfFail(ctx, `!${okVar}`, failBody(ctx, label2, pos))
|
|
6398
|
+
],
|
|
6399
|
+
valueVar: "null",
|
|
6400
|
+
endVar: pos
|
|
6401
|
+
};
|
|
6402
|
+
}
|
|
6128
6403
|
function escapeTokenLiteral(value) {
|
|
6129
6404
|
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
6130
6405
|
}
|
|
@@ -6412,9 +6687,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
|
|
|
6412
6687
|
return { stmts, valueVar: vv, endVar: ev };
|
|
6413
6688
|
}
|
|
6414
6689
|
function emitLazy(p, def, ctx, pos) {
|
|
6415
|
-
const
|
|
6690
|
+
const canonical2 = ctx.ruleNames?.get(p);
|
|
6416
6691
|
const usage = ctx.lazyUsage;
|
|
6417
|
-
if (!
|
|
6692
|
+
if (!canonical2 && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
|
|
6418
6693
|
let resolved;
|
|
6419
6694
|
try {
|
|
6420
6695
|
resolved = def.thunk();
|
|
@@ -6424,7 +6699,7 @@ function emitLazy(p, def, ctx, pos) {
|
|
|
6424
6699
|
return emit(resolved, ctx, pos);
|
|
6425
6700
|
}
|
|
6426
6701
|
if (!ctx.namedParsers.has(p)) {
|
|
6427
|
-
const fnName2 =
|
|
6702
|
+
const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
|
|
6428
6703
|
ctx.namedParsers.set(p, fnName2);
|
|
6429
6704
|
let resolved;
|
|
6430
6705
|
try {
|
|
@@ -6707,6 +6982,8 @@ function emitDispatch(p, ctx, pos) {
|
|
|
6707
6982
|
}
|
|
6708
6983
|
case "not":
|
|
6709
6984
|
return emitNot(def, ctx, pos);
|
|
6985
|
+
case "peek":
|
|
6986
|
+
return emitPeek(def, ctx, pos);
|
|
6710
6987
|
case "node":
|
|
6711
6988
|
return emitNode(def, ctx, pos);
|
|
6712
6989
|
case "scanTo":
|
|
@@ -6795,6 +7072,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
6795
7072
|
case "many":
|
|
6796
7073
|
case "oneOrMore":
|
|
6797
7074
|
case "not":
|
|
7075
|
+
case "peek":
|
|
6798
7076
|
case "transform":
|
|
6799
7077
|
return hasNodeDef(d.parser, seen);
|
|
6800
7078
|
case "skip":
|
|
@@ -6846,17 +7124,18 @@ function childrenOf(def) {
|
|
|
6846
7124
|
case "field":
|
|
6847
7125
|
case "grammar":
|
|
6848
7126
|
case "not":
|
|
7127
|
+
case "peek":
|
|
6849
7128
|
case "node":
|
|
6850
7129
|
case "withCtx":
|
|
6851
7130
|
case "expect":
|
|
6852
7131
|
return [def.parser];
|
|
6853
|
-
// emitMany's
|
|
6854
|
-
// (
|
|
6855
|
-
// emit() call
|
|
7132
|
+
// emitMany's min>=1 branch and emitSepBy both codegen `def.parser` more than
|
|
7133
|
+
// once (`min` mandatory matches, then again inside the repeat loop) — each is a
|
|
7134
|
+
// real emit() call site, so the usage analysis must see one edge per site or it
|
|
6856
7135
|
// undercounts a single-use `parser` ref as inline-safe when it's actually
|
|
6857
|
-
// referenced from
|
|
7136
|
+
// referenced from several positions within this one compiled function.
|
|
6858
7137
|
case "oneOrMore":
|
|
6859
|
-
return
|
|
7138
|
+
return Array.from({ length: def.min + 1 }, () => def.parser);
|
|
6860
7139
|
case "sepBy":
|
|
6861
7140
|
return [def.parser, def.parser, def.separator];
|
|
6862
7141
|
case "skip":
|
|
@@ -7002,12 +7281,41 @@ function resolveGatingLevel(opt) {
|
|
|
7002
7281
|
return "warn";
|
|
7003
7282
|
}
|
|
7004
7283
|
function runGatingDiagnostic(combinator, opt) {
|
|
7284
|
+
return reportGating(opt, (o) => analyzeGating(combinator, o));
|
|
7285
|
+
}
|
|
7286
|
+
function runGatingDiagnosticRules(ruleMap, opt) {
|
|
7287
|
+
return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
|
|
7288
|
+
}
|
|
7289
|
+
function runFusedGatingDiagnostic(getRuleMaps, opt) {
|
|
7290
|
+
if (resolveGatingLevel(opt) === "off") return void 0;
|
|
7291
|
+
const ruleMaps = getRuleMaps();
|
|
7292
|
+
if (!ruleMaps.some((map) => hasExternalRuleRef(map))) return void 0;
|
|
7293
|
+
const winners = /* @__PURE__ */ new Map();
|
|
7294
|
+
for (const map of ruleMaps) for (const [name, rule] of map) {
|
|
7295
|
+
if (rule._def.tag === "lazy") {
|
|
7296
|
+
try {
|
|
7297
|
+
rule._def.thunk();
|
|
7298
|
+
} catch {
|
|
7299
|
+
continue;
|
|
7300
|
+
}
|
|
7301
|
+
}
|
|
7302
|
+
winners.set(name, rule);
|
|
7303
|
+
}
|
|
7304
|
+
const entries = [...winners];
|
|
7305
|
+
return reportGating(opt, (o) => {
|
|
7306
|
+
const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
|
|
7307
|
+
const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
|
|
7308
|
+
return { ...fused, ungated: fused.ungated.filter((c) => deferredHere.has(c.id)), antiPatterns: [] };
|
|
7309
|
+
});
|
|
7310
|
+
}
|
|
7311
|
+
function reportGating(opt, analyze) {
|
|
7005
7312
|
const level = resolveGatingLevel(opt);
|
|
7006
7313
|
if (level === "off") return void 0;
|
|
7007
|
-
const
|
|
7314
|
+
const obj = opt !== null && typeof opt === "object" ? opt : void 0;
|
|
7315
|
+
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;
|
|
7008
7316
|
let report;
|
|
7009
7317
|
try {
|
|
7010
|
-
report =
|
|
7318
|
+
report = analyze(analyzeOpts);
|
|
7011
7319
|
} catch {
|
|
7012
7320
|
return void 0;
|
|
7013
7321
|
}
|
|
@@ -7143,6 +7451,7 @@ function publicRuleWrapperSource(rule, fnSource) {
|
|
|
7143
7451
|
return `Object.assign(${fnSource}, { _meta: { triviaKindLabels: ${JSON.stringify(labels)} } })`;
|
|
7144
7452
|
}
|
|
7145
7453
|
function compileRuleMap(ruleMap, opts) {
|
|
7454
|
+
runGatingDiagnosticRules(ruleMap, opts?.gating);
|
|
7146
7455
|
for (const [, rule] of ruleMap) markUnusedValues(rule);
|
|
7147
7456
|
const coverageWinners = opts?.coverage ? Object.fromEntries(ruleMap.filter(([, rule]) => rule._def.tag !== "lazy")) : void 0;
|
|
7148
7457
|
const grammarTrivia = opts?.trivia;
|
|
@@ -7261,6 +7570,7 @@ function compileRuleMap(ruleMap, opts) {
|
|
|
7261
7570
|
}
|
|
7262
7571
|
function compileLinkable(ruleMapArg, ns, opts) {
|
|
7263
7572
|
if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
|
|
7573
|
+
if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
|
|
7264
7574
|
for (const [, rule] of ruleMapArg) markUnusedValues(rule);
|
|
7265
7575
|
const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
|
|
7266
7576
|
const grammarScanSkip = opts?.scanSkip ?? ruleMapArg.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
|
|
@@ -7317,6 +7627,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7317
7627
|
fnNameToKey.set(fn, key);
|
|
7318
7628
|
}
|
|
7319
7629
|
ctx.ruleNames = ruleNames;
|
|
7630
|
+
const externalRefs = /* @__PURE__ */ new Set();
|
|
7320
7631
|
{
|
|
7321
7632
|
const scanned = /* @__PURE__ */ new Set();
|
|
7322
7633
|
const scanExternal = (p) => {
|
|
@@ -7332,11 +7643,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7332
7643
|
}
|
|
7333
7644
|
if (resolved === void 0) {
|
|
7334
7645
|
const name = p._ruleName;
|
|
7335
|
-
if (name
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7646
|
+
if (name) {
|
|
7647
|
+
externalRefs.add(p);
|
|
7648
|
+
if (!ctx.namedParsers.has(p)) {
|
|
7649
|
+
assertRuleName(name);
|
|
7650
|
+
const fn = `_r_${name}`;
|
|
7651
|
+
ruleNames.set(p, fn);
|
|
7652
|
+
ctx.namedParsers.set(p, fn);
|
|
7653
|
+
}
|
|
7340
7654
|
}
|
|
7341
7655
|
return;
|
|
7342
7656
|
}
|
|
@@ -7449,12 +7763,32 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7449
7763
|
needsEmptyTl: !!ctx.needsEmptyTl,
|
|
7450
7764
|
needsHostReads: !!ctx.needsHostReads,
|
|
7451
7765
|
hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
|
|
7452
|
-
isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule)),
|
|
7766
|
+
isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule), externalRefs),
|
|
7453
7767
|
mfFns: mfSrcs ? [] : ctx.mapFns,
|
|
7454
7768
|
buildFns: buildSrcs ? [] : ctx.buildFns
|
|
7455
7769
|
};
|
|
7456
7770
|
}
|
|
7457
|
-
function
|
|
7771
|
+
function hasExternalRuleRef(ruleMap) {
|
|
7772
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7773
|
+
const visit = (p) => {
|
|
7774
|
+
if (seen.has(p)) return false;
|
|
7775
|
+
seen.add(p);
|
|
7776
|
+
const def = p._def;
|
|
7777
|
+
if (def.tag === "lazy") {
|
|
7778
|
+
let resolved;
|
|
7779
|
+
try {
|
|
7780
|
+
resolved = def.thunk();
|
|
7781
|
+
} catch {
|
|
7782
|
+
resolved = void 0;
|
|
7783
|
+
}
|
|
7784
|
+
if (resolved === void 0) return p._ruleName !== void 0;
|
|
7785
|
+
return visit(resolved);
|
|
7786
|
+
}
|
|
7787
|
+
return childrenOf(def).some(visit);
|
|
7788
|
+
};
|
|
7789
|
+
return ruleMap.some(([, rule]) => visit(rule));
|
|
7790
|
+
}
|
|
7791
|
+
function hasSemanticReduction(roots, externalRefs) {
|
|
7458
7792
|
const seen = /* @__PURE__ */ new Set();
|
|
7459
7793
|
const visit = (parser2) => {
|
|
7460
7794
|
if (seen.has(parser2)) return false;
|
|
@@ -7465,6 +7799,7 @@ function hasSemanticReduction(roots) {
|
|
|
7465
7799
|
if (def.tag === "guard" || def.tag === "withCtx") return true;
|
|
7466
7800
|
if (def.tag === "node" && def.build !== void 0) return true;
|
|
7467
7801
|
if (def.tag === "lazy") {
|
|
7802
|
+
if (externalRefs?.has(parser2)) return false;
|
|
7468
7803
|
try {
|
|
7469
7804
|
return visit(def.thunk());
|
|
7470
7805
|
} catch {
|
|
@@ -7556,6 +7891,7 @@ function childrenOf2(def) {
|
|
|
7556
7891
|
case "label":
|
|
7557
7892
|
case "field":
|
|
7558
7893
|
case "not":
|
|
7894
|
+
case "peek":
|
|
7559
7895
|
case "node":
|
|
7560
7896
|
case "withCtx":
|
|
7561
7897
|
case "expect":
|
|
@@ -7589,6 +7925,13 @@ function lazyTarget(c) {
|
|
|
7589
7925
|
}
|
|
7590
7926
|
var Unserializable = class extends Error {
|
|
7591
7927
|
};
|
|
7928
|
+
function repeatOpts(min, max, trailing) {
|
|
7929
|
+
const parts = [];
|
|
7930
|
+
if (min !== 0) parts.push(`min: ${min}`);
|
|
7931
|
+
if (max !== void 0) parts.push(`max: ${max}`);
|
|
7932
|
+
if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
|
|
7933
|
+
return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
|
|
7934
|
+
}
|
|
7592
7935
|
function serializeRuleMap(ruleMap, scanSkip) {
|
|
7593
7936
|
try {
|
|
7594
7937
|
return new Serializer(ruleMap, scanSkip ?? []).run();
|
|
@@ -7692,6 +8035,7 @@ function evalRuleMapIR(ir) {
|
|
|
7692
8035
|
"optional",
|
|
7693
8036
|
"sepBy",
|
|
7694
8037
|
"not",
|
|
8038
|
+
"peek",
|
|
7695
8039
|
"node",
|
|
7696
8040
|
"parser",
|
|
7697
8041
|
"scanTo",
|
|
@@ -7724,6 +8068,7 @@ function evalRuleMapIR(ir) {
|
|
|
7724
8068
|
optional,
|
|
7725
8069
|
sepBy,
|
|
7726
8070
|
not,
|
|
8071
|
+
peek,
|
|
7727
8072
|
node,
|
|
7728
8073
|
parser,
|
|
7729
8074
|
scanTo,
|
|
@@ -7898,17 +8243,21 @@ ${outerDecls.map((d) => " " + d).join("\n")}
|
|
|
7898
8243
|
return `_gch([${items.join(", ")}])`;
|
|
7899
8244
|
}
|
|
7900
8245
|
case "many":
|
|
7901
|
-
return `many(${kid(def.parser)})`;
|
|
8246
|
+
return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
|
|
8247
|
+
// A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
|
|
8248
|
+
// it through `many` so the bounds survive (oneOrMore defaults min to 1).
|
|
7902
8249
|
case "oneOrMore":
|
|
7903
|
-
return `oneOrMore(${kid(def.parser)})`;
|
|
8250
|
+
return def.min === 1 && def.max === void 0 ? `oneOrMore(${kid(def.parser)})` : `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
|
|
7904
8251
|
case "optional":
|
|
7905
8252
|
return `optional(${kid(def.parser)})`;
|
|
7906
8253
|
case "attempt":
|
|
7907
8254
|
return `attempt(${kid(def.parser)})`;
|
|
7908
8255
|
case "sepBy":
|
|
7909
|
-
return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
|
|
8256
|
+
return `sepBy(${kid(def.parser)}, ${kid(def.separator)}${repeatOpts(def.min, def.max, def.trailing)})`;
|
|
7910
8257
|
case "not":
|
|
7911
8258
|
return `not(${kid(def.parser)})`;
|
|
8259
|
+
case "peek":
|
|
8260
|
+
return `peek(${kid(def.parser)})`;
|
|
7912
8261
|
case "trivia":
|
|
7913
8262
|
return `trivia(${kid(def.parser)})`;
|
|
7914
8263
|
case "token":
|
|
@@ -8344,15 +8693,12 @@ var SUPPORTED = {
|
|
|
8344
8693
|
literal: (...a) => literal(a[0], a[1]),
|
|
8345
8694
|
regex: (...a) => regex(a[0], a[1]),
|
|
8346
8695
|
keywords: (...a) => keywords(a[0], a[1]),
|
|
8347
|
-
word: (...a) => word(a[0], a[1]),
|
|
8696
|
+
word: (...a) => word(a[0], a[1], a[2]),
|
|
8348
8697
|
sequence: (...a) => sequence(...a),
|
|
8349
8698
|
choice: (...a) => choice(...a),
|
|
8350
8699
|
attempt: (...a) => attempt(a[0]),
|
|
8351
|
-
many: (...a) => many(a[0]),
|
|
8352
|
-
oneOrMore: (...a) => oneOrMore(a[0]),
|
|
8353
8700
|
optional: (...a) => optional(a[0]),
|
|
8354
8701
|
skip: (...a) => skip(a[0], a[1]),
|
|
8355
|
-
sepBy: (...a) => sepBy(a[0], a[1]),
|
|
8356
8702
|
trivia: (...a) => trivia(a[0]),
|
|
8357
8703
|
label: (...a) => label(a[0], a[1]),
|
|
8358
8704
|
field: (...a) => field(a[0], a[1]),
|
|
@@ -8528,8 +8874,8 @@ function exprToCombi(node2, scope, code, mfs) {
|
|
|
8528
8874
|
return null;
|
|
8529
8875
|
}
|
|
8530
8876
|
}
|
|
8531
|
-
if (callee.name === "sepBy") {
|
|
8532
|
-
const [itemArg, sepArg] = node2.arguments;
|
|
8877
|
+
if (callee.name === "sepBy" || callee.name === "oneOrMoreSep") {
|
|
8878
|
+
const [itemArg, sepArg, optsArg] = node2.arguments;
|
|
8533
8879
|
if (!itemArg || !sepArg || itemArg.type === "SpreadElement" || sepArg.type === "SpreadElement") return null;
|
|
8534
8880
|
const itemMfs = [];
|
|
8535
8881
|
const itemCombi = anyValue(itemArg, scope, code, itemMfs);
|
|
@@ -8537,25 +8883,42 @@ function exprToCombi(node2, scope, code, mfs) {
|
|
|
8537
8883
|
const sepMfs = [];
|
|
8538
8884
|
const sepCombi = anyValue(sepArg, scope, code, sepMfs);
|
|
8539
8885
|
if (!isCombinator(sepCombi)) return null;
|
|
8886
|
+
let opts;
|
|
8887
|
+
if (optsArg) {
|
|
8888
|
+
if (optsArg.type === "SpreadElement") return null;
|
|
8889
|
+
const v2 = anyValue(optsArg, scope, code, []);
|
|
8890
|
+
if (!v2 || typeof v2 !== "object") return null;
|
|
8891
|
+
opts = v2;
|
|
8892
|
+
}
|
|
8540
8893
|
if (mfs) mfs.push(...itemMfs, ...sepMfs, ...itemMfs);
|
|
8541
8894
|
try {
|
|
8542
|
-
return sepBy(itemCombi, sepCombi);
|
|
8895
|
+
return callee.name === "sepBy" ? sepBy(itemCombi, sepCombi, opts) : oneOrMoreSep(itemCombi, sepCombi, opts);
|
|
8543
8896
|
} catch {
|
|
8544
8897
|
return null;
|
|
8545
8898
|
}
|
|
8546
8899
|
}
|
|
8547
|
-
if (callee.name === "oneOrMore") {
|
|
8548
|
-
const [itemArg] = node2.arguments;
|
|
8900
|
+
if (callee.name === "oneOrMore" || callee.name === "many") {
|
|
8901
|
+
const [itemArg, optsArg] = node2.arguments;
|
|
8549
8902
|
if (!itemArg || itemArg.type === "SpreadElement") return null;
|
|
8550
8903
|
const itemMfs = [];
|
|
8551
8904
|
const itemCombi = anyValue(itemArg, scope, code, itemMfs);
|
|
8552
8905
|
if (!isCombinator(itemCombi)) return null;
|
|
8553
|
-
|
|
8906
|
+
let opts;
|
|
8907
|
+
if (optsArg) {
|
|
8908
|
+
if (optsArg.type === "SpreadElement") return null;
|
|
8909
|
+
const v2 = anyValue(optsArg, scope, code, []);
|
|
8910
|
+
if (!v2 || typeof v2 !== "object") return null;
|
|
8911
|
+
opts = v2;
|
|
8912
|
+
}
|
|
8913
|
+
let combi;
|
|
8554
8914
|
try {
|
|
8555
|
-
|
|
8915
|
+
combi = callee.name === "many" ? many(itemCombi, opts) : oneOrMore(itemCombi, opts);
|
|
8556
8916
|
} catch {
|
|
8557
8917
|
return null;
|
|
8558
8918
|
}
|
|
8919
|
+
const min = combi._def.tag === "oneOrMore" ? combi._def.min : 0;
|
|
8920
|
+
if (mfs) for (let i = 0; i <= min; i++) mfs.push(...itemMfs);
|
|
8921
|
+
return combi;
|
|
8559
8922
|
}
|
|
8560
8923
|
if (callee.name === "not") {
|
|
8561
8924
|
const [innerArg] = node2.arguments;
|
|
@@ -8568,6 +8931,17 @@ function exprToCombi(node2, scope, code, mfs) {
|
|
|
8568
8931
|
return null;
|
|
8569
8932
|
}
|
|
8570
8933
|
}
|
|
8934
|
+
if (callee.name === "peek") {
|
|
8935
|
+
const [innerArg] = node2.arguments;
|
|
8936
|
+
if (!innerArg || innerArg.type === "SpreadElement") return null;
|
|
8937
|
+
const inner = anyValue(innerArg, scope, code, mfs);
|
|
8938
|
+
if (!isCombinator(inner)) return null;
|
|
8939
|
+
try {
|
|
8940
|
+
return peek(inner);
|
|
8941
|
+
} catch {
|
|
8942
|
+
return null;
|
|
8943
|
+
}
|
|
8944
|
+
}
|
|
8571
8945
|
if (callee.name === "balanced") {
|
|
8572
8946
|
const [openArg, closeArg, optsArg] = node2.arguments;
|
|
8573
8947
|
if (!openArg || !closeArg || openArg.type === "SpreadElement" || closeArg.type === "SpreadElement") return null;
|
|
@@ -9197,6 +9571,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9197
9571
|
const warnings = [];
|
|
9198
9572
|
beginLoweringCapture();
|
|
9199
9573
|
let anyUnresolved = false;
|
|
9574
|
+
let keepMacroImport = false;
|
|
9200
9575
|
let runtimeComposeFallback = false;
|
|
9201
9576
|
let ruleMapHolderCounter = 0;
|
|
9202
9577
|
const lineOf = (pos) => {
|
|
@@ -9245,11 +9620,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9245
9620
|
const evaluated = evaluateRulesFactory(init, label2);
|
|
9246
9621
|
if (!evaluated) return null;
|
|
9247
9622
|
const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {}, recovery, coverage: grammarCoverage });
|
|
9248
|
-
|
|
9249
|
-
warn(init.start, `${label2}: rule map couldn't be inlined`);
|
|
9250
|
-
return null;
|
|
9251
|
-
}
|
|
9252
|
-
return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
|
|
9623
|
+
return { replacement: compiled?.replacement ?? null, ruleMap: evaluated.ruleMap, ...compiled?.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
|
|
9253
9624
|
};
|
|
9254
9625
|
const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
|
|
9255
9626
|
const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
|
|
@@ -9344,6 +9715,17 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9344
9715
|
}
|
|
9345
9716
|
return out;
|
|
9346
9717
|
};
|
|
9718
|
+
const carriedRuleMaps = (items) => {
|
|
9719
|
+
const out = [];
|
|
9720
|
+
const add = (it) => {
|
|
9721
|
+
if (isIR(it)) out.push(evalRuleMapIR(it.ir));
|
|
9722
|
+
};
|
|
9723
|
+
for (const it of items) {
|
|
9724
|
+
if (isSpread(it)) for (const p of importedPieces(it.__spreadLocal) ?? []) add(p);
|
|
9725
|
+
else add(it);
|
|
9726
|
+
}
|
|
9727
|
+
return out;
|
|
9728
|
+
};
|
|
9347
9729
|
const materializeLeafCoverage = (items, localRules, localNs, composing, captureTerminals = false) => {
|
|
9348
9730
|
const sources = [];
|
|
9349
9731
|
const add = (item) => {
|
|
@@ -9477,6 +9859,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9477
9859
|
}
|
|
9478
9860
|
carried.push(...r.carried);
|
|
9479
9861
|
}
|
|
9862
|
+
runFusedGatingDiagnostic(() => carriedRuleMaps(carried));
|
|
9480
9863
|
const pieces = materializeCarried(carried, composing);
|
|
9481
9864
|
try {
|
|
9482
9865
|
return { replacement: emitFusedSource(pieces), carried, ...composing ? { trivia: composing } : {} };
|
|
@@ -9525,6 +9908,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9525
9908
|
}
|
|
9526
9909
|
try {
|
|
9527
9910
|
const localNs = nsFor(`composeLeaf${init.start}`);
|
|
9911
|
+
runFusedGatingDiagnostic(() => [...carriedRuleMaps(carried), [...localRules]]);
|
|
9528
9912
|
const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, ...localScanSkip ? { scanSkip: localScanSkip } : {}, recovery });
|
|
9529
9913
|
if (!plainLocalPiece) {
|
|
9530
9914
|
warn(init.start, "composeLeaf(): local rules could not be statically compiled");
|
|
@@ -9613,7 +9997,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9613
9997
|
const refEntry = scope.get(varName);
|
|
9614
9998
|
const refCombi = refEntry?.combi ?? null;
|
|
9615
9999
|
if (refCombi) {
|
|
9616
|
-
const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage });
|
|
10000
|
+
const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
|
|
9617
10001
|
if (compiled2.inlineExpression === null) {
|
|
9618
10002
|
warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
|
|
9619
10003
|
continue;
|
|
@@ -9628,20 +10012,26 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9628
10012
|
localRuleMaps.set(varName, compiledRules.ruleMap);
|
|
9629
10013
|
if (compiledRules.trivia) localGrammarTrivia.set(varName, compiledRules.trivia);
|
|
9630
10014
|
if (compiledRules.scanSkip) localGrammarScanSkip.set(varName, compiledRules.scanSkip);
|
|
10015
|
+
const ns = nsFor(varName);
|
|
10016
|
+
const pieces = exportPrefix || compiledRules.replacement === null ? compileLinkable([...compiledRules.ruleMap], ns, {
|
|
10017
|
+
...compiledRules.scanSkip ? { scanSkip: compiledRules.scanSkip } : {},
|
|
10018
|
+
recovery
|
|
10019
|
+
}) : null;
|
|
10020
|
+
if (compiledRules.replacement === null) {
|
|
10021
|
+
if (!pieces || !hasExternalRuleRef([...compiledRules.ruleMap])) {
|
|
10022
|
+
warn(init.start, `${varName}: rule map couldn't be inlined`);
|
|
10023
|
+
continue;
|
|
10024
|
+
}
|
|
10025
|
+
keepMacroImport = true;
|
|
10026
|
+
}
|
|
10027
|
+
const source = compiledRules.replacement ?? code.slice(init.start, init.end);
|
|
9631
10028
|
let replacement = withCoverageDefinitions(
|
|
9632
|
-
|
|
9633
|
-
compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(
|
|
10029
|
+
source,
|
|
10030
|
+
compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(source)
|
|
9634
10031
|
);
|
|
9635
|
-
if (exportPrefix) {
|
|
9636
|
-
const
|
|
9637
|
-
|
|
9638
|
-
...compiledRules.scanSkip ? { scanSkip: compiledRules.scanSkip } : {},
|
|
9639
|
-
recovery
|
|
9640
|
-
});
|
|
9641
|
-
if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
|
|
9642
|
-
const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
|
|
9643
|
-
replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
|
|
9644
|
-
}
|
|
10032
|
+
if (exportPrefix && pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
|
|
10033
|
+
const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
|
|
10034
|
+
replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
|
|
9645
10035
|
}
|
|
9646
10036
|
replacements.push({ start: init.start, end: init.end, replacement });
|
|
9647
10037
|
continue;
|
|
@@ -9684,7 +10074,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9684
10074
|
warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
|
|
9685
10075
|
continue;
|
|
9686
10076
|
}
|
|
9687
|
-
const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage });
|
|
10077
|
+
const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
|
|
9688
10078
|
if (compiled.inlineExpression === null) {
|
|
9689
10079
|
warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
|
|
9690
10080
|
continue;
|
|
@@ -9703,6 +10093,10 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9703
10093
|
}
|
|
9704
10094
|
const compiledRules = compileRulesFactory(init, "{ \u2026 }");
|
|
9705
10095
|
if (!compiledRules) continue;
|
|
10096
|
+
if (compiledRules.replacement === null) {
|
|
10097
|
+
warn(init.start, `{ \u2026 }: rule map couldn't be inlined`);
|
|
10098
|
+
continue;
|
|
10099
|
+
}
|
|
9706
10100
|
const pattern = d.id;
|
|
9707
10101
|
const bindings = [];
|
|
9708
10102
|
let allOk = true;
|
|
@@ -9744,7 +10138,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9744
10138
|
}
|
|
9745
10139
|
if (replacements.length === 0 && warnings.length === 0) return null;
|
|
9746
10140
|
for (const imp of macroImports) {
|
|
9747
|
-
imp.fullyResolved = !anyUnresolved;
|
|
10141
|
+
imp.fullyResolved = !anyUnresolved && !keepMacroImport;
|
|
9748
10142
|
}
|
|
9749
10143
|
const ms = new import_magic_string.default(code);
|
|
9750
10144
|
if (!anyUnresolved) {
|