parseman 0.44.0 → 0.46.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/choice-cost-gate.d.ts +158 -0
- package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
- package/dist/analysis/choice-cost-render.d.ts +88 -0
- package/dist/analysis/choice-cost-render.d.ts.map +1 -0
- package/dist/analysis/choice-cost.d.ts +383 -0
- package/dist/analysis/choice-cost.d.ts.map +1 -0
- package/dist/analysis/corpus.d.ts +73 -0
- package/dist/analysis/corpus.d.ts.map +1 -0
- package/dist/analysis/diagnose-render.d.ts +65 -0
- package/dist/analysis/diagnose-render.d.ts.map +1 -0
- package/dist/analysis/diagnose.d.ts +133 -0
- package/dist/analysis/diagnose.d.ts.map +1 -0
- package/dist/analysis/diagnostics.cjs +11272 -0
- package/dist/analysis/diagnostics.cjs.map +7 -0
- package/dist/analysis/diagnostics.d.ts +27 -0
- package/dist/analysis/diagnostics.d.ts.map +1 -0
- package/dist/analysis/diagnostics.js +11249 -0
- package/dist/analysis/diagnostics.js.map +7 -0
- package/dist/analysis/fix-render.d.ts +43 -0
- package/dist/analysis/fix-render.d.ts.map +1 -0
- package/dist/analysis/fix.d.ts +172 -0
- package/dist/analysis/fix.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +17 -9
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/grammar.d.ts.map +1 -1
- package/dist/analysis/rebuild.d.ts +61 -0
- package/dist/analysis/rebuild.d.ts.map +1 -0
- package/dist/analysis/terminal.d.ts +142 -0
- package/dist/analysis/terminal.d.ts.map +1 -0
- package/dist/cli/index.cjs +12507 -0
- package/dist/cli/index.cjs.map +7 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +12484 -0
- package/dist/cli/index.js.map +7 -0
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +19 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +19 -0
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/compiler/build-arity.d.ts +28 -0
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/codegen.d.ts +80 -60
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/degradation.d.ts +123 -0
- package/dist/compiler/degradation.d.ts.map +1 -0
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/inline-build.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +45 -7
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/module-hoist.d.ts +106 -0
- package/dist/compiler/module-hoist.d.ts.map +1 -0
- package/dist/coverage.d.ts +13 -0
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/index.cjs +1846 -610
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +11 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1846 -610
- package/dist/index.js.map +4 -4
- package/dist/language-service/index.cjs +40 -30
- package/dist/language-service/index.cjs.map +3 -3
- package/dist/language-service/index.js +40 -30
- package/dist/language-service/index.js.map +3 -3
- package/dist/oracle/digest.d.ts +132 -6
- package/dist/oracle/digest.d.ts.map +1 -1
- package/dist/oracle/index.cjs +136 -305
- package/dist/oracle/index.cjs.map +4 -4
- package/dist/oracle/index.d.ts +43 -31
- package/dist/oracle/index.d.ts.map +1 -1
- package/dist/oracle/index.js +136 -305
- package/dist/oracle/index.js.map +4 -4
- package/dist/plugin/evaluator.d.ts +3 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/free-identifiers.d.ts +23 -0
- package/dist/plugin/free-identifiers.d.ts.map +1 -0
- package/dist/plugin/index.cjs +1529 -763
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts +18 -0
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +1529 -763
- package/dist/plugin/index.js.map +4 -4
- package/dist/plugin/reducer-resolver.d.ts +57 -0
- package/dist/plugin/reducer-resolver.d.ts.map +1 -0
- package/dist/run/index.cjs +33 -30
- package/dist/run/index.cjs.map +2 -2
- package/dist/run/index.js +33 -30
- package/dist/run/index.js.map +2 -2
- package/dist/spec/index.cjs +222 -34
- package/dist/spec/index.cjs.map +4 -4
- package/dist/spec/index.js +222 -34
- package/dist/spec/index.js.map +4 -4
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +23 -4
- package/dist/combinators/guard.d.ts +0 -8
- package/dist/combinators/guard.d.ts.map +0 -1
- package/dist/cst/offset-model.d.ts +0 -119
- package/dist/cst/offset-model.d.ts.map +0 -1
- package/dist/oracle/corpus.d.ts +0 -32
- package/dist/oracle/corpus.d.ts.map +0 -1
- package/dist/oracle/identity.d.ts +0 -117
- package/dist/oracle/identity.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -51,8 +51,9 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
|
51
51
|
return d.value.length === 0;
|
|
52
52
|
case "keywords":
|
|
53
53
|
return false;
|
|
54
|
+
// The routed token itself is the selector's match, never empty; a fallback can be.
|
|
54
55
|
case "routed":
|
|
55
|
-
return false;
|
|
56
|
+
return d.fallback === void 0 ? false : me(d.fallback);
|
|
56
57
|
case "regex":
|
|
57
58
|
try {
|
|
58
59
|
const m = new RegExp(d.source).exec("");
|
|
@@ -1814,8 +1815,11 @@ function derive(c, seen) {
|
|
|
1814
1815
|
case "not":
|
|
1815
1816
|
case "peek":
|
|
1816
1817
|
return deriveExpected2(def.parser);
|
|
1818
|
+
// A bare routed() can only fail as "there is no routed token here". With a
|
|
1819
|
+
// fallback, THAT is the branch that can fail, so its expectations are the useful
|
|
1820
|
+
// ones — the routed path either succeeds or is not taken.
|
|
1817
1821
|
case "routed":
|
|
1818
|
-
return ["routed()"];
|
|
1822
|
+
return def.fallback === void 0 ? ["routed()"] : deriveExpected2(def.fallback);
|
|
1819
1823
|
case "lazy": {
|
|
1820
1824
|
const name = c._ruleName;
|
|
1821
1825
|
if (seen.has(c)) return name ? [name] : [];
|
|
@@ -2005,7 +2009,7 @@ function detectStrategy(parsers) {
|
|
|
2005
2009
|
function bareLeadingTermKey(term) {
|
|
2006
2010
|
const d = term._def;
|
|
2007
2011
|
if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
|
|
2008
|
-
if (d.tag === "regex") return `R:${d.source
|
|
2012
|
+
if (d.tag === "regex") return `R:${JSON.stringify([d.source, d.flags])}`;
|
|
2009
2013
|
return null;
|
|
2010
2014
|
}
|
|
2011
2015
|
function detectSharedPrefix(parsers) {
|
|
@@ -2263,17 +2267,21 @@ function makeWhen(opts = {}) {
|
|
|
2263
2267
|
function otherwise(parser2) {
|
|
2264
2268
|
return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
|
|
2265
2269
|
}
|
|
2266
|
-
function routed() {
|
|
2270
|
+
function routed(fallback) {
|
|
2267
2271
|
return {
|
|
2268
2272
|
_tag: "routed",
|
|
2269
|
-
_meta: {
|
|
2270
|
-
|
|
2273
|
+
_meta: {
|
|
2274
|
+
// The routed token is whatever the selector matched, so the first set stays
|
|
2275
|
+
// `any` even with a fallback — `any` already subsumes the fallback's.
|
|
2276
|
+
firstSet: { kind: "any" },
|
|
2277
|
+
canMatchNewline: fallback?._meta.canMatchNewline ?? false,
|
|
2278
|
+
isTrivia: false
|
|
2279
|
+
},
|
|
2280
|
+
_def: fallback === void 0 ? { tag: "routed" } : { tag: "routed", fallback },
|
|
2271
2281
|
parse(_input, pos, ctx) {
|
|
2272
2282
|
const item = ctx._routed;
|
|
2273
|
-
if (item === void 0) {
|
|
2274
|
-
|
|
2275
|
-
}
|
|
2276
|
-
if (pos !== item.span.start) {
|
|
2283
|
+
if (item === void 0 || pos !== item.span.start) {
|
|
2284
|
+
if (fallback !== void 0) return fallback.parse(_input, pos, ctx);
|
|
2277
2285
|
return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
|
|
2278
2286
|
}
|
|
2279
2287
|
if (cstCaptureActive(ctx)) {
|
|
@@ -2816,13 +2824,13 @@ function markUnusedValues(root) {
|
|
|
2816
2824
|
return;
|
|
2817
2825
|
}
|
|
2818
2826
|
};
|
|
2819
|
-
const
|
|
2820
|
-
const
|
|
2827
|
+
const resolveRoot2 = (c) => {
|
|
2828
|
+
const guard = /* @__PURE__ */ new Set();
|
|
2821
2829
|
let cur = c;
|
|
2822
|
-
while (!
|
|
2830
|
+
while (!guard.has(cur)) {
|
|
2823
2831
|
const d = cur._def;
|
|
2824
2832
|
if (d.tag !== "lazy") return cur;
|
|
2825
|
-
|
|
2833
|
+
guard.add(cur);
|
|
2826
2834
|
try {
|
|
2827
2835
|
cur = d.thunk();
|
|
2828
2836
|
} catch {
|
|
@@ -2831,7 +2839,7 @@ function markUnusedValues(root) {
|
|
|
2831
2839
|
}
|
|
2832
2840
|
return cur;
|
|
2833
2841
|
};
|
|
2834
|
-
visit(
|
|
2842
|
+
visit(resolveRoot2(root), true);
|
|
2835
2843
|
}
|
|
2836
2844
|
|
|
2837
2845
|
// src/combinators/grammar.ts
|
|
@@ -2991,10 +2999,11 @@ function childrenOf(def) {
|
|
|
2991
2999
|
return [def.main, def.skipped];
|
|
2992
3000
|
case "scanTo":
|
|
2993
3001
|
return [def.sentinel, ...def.skip];
|
|
3002
|
+
case "routed":
|
|
3003
|
+
return def.fallback ? [def.fallback] : [];
|
|
2994
3004
|
case "literal":
|
|
2995
3005
|
case "regex":
|
|
2996
3006
|
case "keywords":
|
|
2997
|
-
case "routed":
|
|
2998
3007
|
case "guard":
|
|
2999
3008
|
case "lazy":
|
|
3000
3009
|
case "unknown":
|
|
@@ -3220,54 +3229,272 @@ function peek(combinator) {
|
|
|
3220
3229
|
};
|
|
3221
3230
|
}
|
|
3222
3231
|
|
|
3232
|
+
// src/compiler/degradation.ts
|
|
3233
|
+
function formatDegradation(d) {
|
|
3234
|
+
return `[parseman] degraded [${d.code}] ${d.where}: ${d.subject} \u2014 ${d.fellBackTo}; otherwise ${d.otherwise}`;
|
|
3235
|
+
}
|
|
3236
|
+
var DETAIL_CAP = 8;
|
|
3237
|
+
function formatDegradations(list) {
|
|
3238
|
+
const byCode = /* @__PURE__ */ new Map();
|
|
3239
|
+
for (const d of list) {
|
|
3240
|
+
const bucket = byCode.get(d.code);
|
|
3241
|
+
if (bucket) bucket.push(d);
|
|
3242
|
+
else byCode.set(d.code, [d]);
|
|
3243
|
+
}
|
|
3244
|
+
const out = [];
|
|
3245
|
+
for (const [code, items] of byCode) {
|
|
3246
|
+
for (const d of items.slice(0, DETAIL_CAP)) out.push(formatDegradation(d));
|
|
3247
|
+
if (items.length > DETAIL_CAP) {
|
|
3248
|
+
out.push(
|
|
3249
|
+
`[parseman] degraded [${code}] +${items.length - DETAIL_CAP} more site(s) not listed (${items.length} total). Set PARSEMAN_DEGRADATION=error to fail the build on these.`
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
return out;
|
|
3254
|
+
}
|
|
3255
|
+
var _sinks = [];
|
|
3256
|
+
function beginDegradationCapture() {
|
|
3257
|
+
_sinks.push(/* @__PURE__ */ new Map());
|
|
3258
|
+
}
|
|
3259
|
+
function degradationCaptureDepth() {
|
|
3260
|
+
return _sinks.length;
|
|
3261
|
+
}
|
|
3262
|
+
function unwindDegradationCapture(depth) {
|
|
3263
|
+
const found = [];
|
|
3264
|
+
while (_sinks.length > depth) {
|
|
3265
|
+
const sink = _sinks.pop();
|
|
3266
|
+
found.unshift(...sink.values());
|
|
3267
|
+
}
|
|
3268
|
+
return found;
|
|
3269
|
+
}
|
|
3270
|
+
function resolveDegradationLevel(explicit) {
|
|
3271
|
+
if (explicit !== void 0) return explicit;
|
|
3272
|
+
const env = typeof process !== "undefined" ? process.env?.PARSEMAN_DEGRADATION : void 0;
|
|
3273
|
+
if (env === "off" || env === "warn" || env === "error") return env;
|
|
3274
|
+
return "warn";
|
|
3275
|
+
}
|
|
3276
|
+
function degradationKey(d) {
|
|
3277
|
+
return JSON.stringify([d.code, d.where, d.subject]);
|
|
3278
|
+
}
|
|
3279
|
+
function recordDegradation(d) {
|
|
3280
|
+
const level = resolveDegradationLevel();
|
|
3281
|
+
if (level === "off") return;
|
|
3282
|
+
const key = degradationKey(d);
|
|
3283
|
+
const sink = _sinks[_sinks.length - 1];
|
|
3284
|
+
if (sink) {
|
|
3285
|
+
if (!sink.has(key)) sink.set(key, d);
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
if (_immediate.has(key)) return;
|
|
3289
|
+
_immediate.add(key);
|
|
3290
|
+
if (level === "error") throw new Error(`parseman: degraded compilation path
|
|
3291
|
+
${formatDegradation(d)}`);
|
|
3292
|
+
console.warn(formatDegradation(d));
|
|
3293
|
+
}
|
|
3294
|
+
var _immediate = /* @__PURE__ */ new Set();
|
|
3295
|
+
function beginCompileDegradationDrain() {
|
|
3296
|
+
if (resolveDegradationLevel() === "off" || _sinks.length > 0) return () => {
|
|
3297
|
+
};
|
|
3298
|
+
const depth = degradationCaptureDepth();
|
|
3299
|
+
beginDegradationCapture();
|
|
3300
|
+
return (report) => {
|
|
3301
|
+
const found = unwindDegradationCapture(depth);
|
|
3302
|
+
if (!report || found.length === 0) return;
|
|
3303
|
+
const fresh = found.filter((d) => {
|
|
3304
|
+
const k = degradationKey(d);
|
|
3305
|
+
if (_immediate.has(k)) return false;
|
|
3306
|
+
_immediate.add(k);
|
|
3307
|
+
return true;
|
|
3308
|
+
});
|
|
3309
|
+
if (fresh.length === 0) return;
|
|
3310
|
+
const lines = formatDegradations(fresh);
|
|
3311
|
+
if (resolveDegradationLevel() === "error") {
|
|
3312
|
+
throw new Error(`parseman: ${fresh.length} degraded compilation path(s)
|
|
3313
|
+
${lines.join("\n")}`);
|
|
3314
|
+
}
|
|
3315
|
+
for (const l of lines) console.warn(l);
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3223
3319
|
// src/compiler/build-arity.ts
|
|
3224
3320
|
function cstOutputHost(build) {
|
|
3225
3321
|
return build?._parsemanCstOutput === true;
|
|
3226
3322
|
}
|
|
3227
|
-
var
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
var CONFIRMABLE_PARAM_RE = /^[A-Za-z_$][\w$]*\s*\??\s*(?::[^,=]+)?$/;
|
|
3323
|
+
var SINGLE_IDENT_ARROW_RE = /^[A-Za-z_$][\w$]*\s*=>/;
|
|
3324
|
+
var PARAM_LIST_OPEN_RE = /^(?:function\b[^(]*)?\(/;
|
|
3325
|
+
var NATIVE_CODE_RE = /\{\s*\[native code\]\s*\}/;
|
|
3326
|
+
var PARAM_HEAD_RE = /^[A-Za-z_$][\w$]*\s*\??\s*/;
|
|
3232
3327
|
function confirmedBuildArity(src) {
|
|
3233
3328
|
const s = src.trim();
|
|
3234
|
-
|
|
3235
|
-
if (
|
|
3236
|
-
if (
|
|
3237
|
-
const inner = (
|
|
3238
|
-
if (inner ===
|
|
3239
|
-
|
|
3329
|
+
if (NATIVE_CODE_RE.test(s)) return null;
|
|
3330
|
+
if (/\barguments\b/.test(s)) return null;
|
|
3331
|
+
if (SINGLE_IDENT_ARROW_RE.test(s)) return 1;
|
|
3332
|
+
const inner = paramListText(s);
|
|
3333
|
+
if (inner === null) return null;
|
|
3334
|
+
if (inner.trim() === "") return 0;
|
|
3335
|
+
const parts = splitTopLevel(inner);
|
|
3336
|
+
if (parts === null) return null;
|
|
3240
3337
|
for (const part of parts) {
|
|
3241
|
-
if (!
|
|
3338
|
+
if (!isConfirmableParam(part.trim())) return null;
|
|
3242
3339
|
}
|
|
3243
|
-
if (/\barguments\b/.test(s)) return null;
|
|
3244
3340
|
return parts.length;
|
|
3245
3341
|
}
|
|
3342
|
+
function paramListText(s) {
|
|
3343
|
+
const open = PARAM_LIST_OPEN_RE.exec(s);
|
|
3344
|
+
if (!open) return null;
|
|
3345
|
+
const start = open[0].length;
|
|
3346
|
+
const end = matchingParen(s, start);
|
|
3347
|
+
if (end === null) return null;
|
|
3348
|
+
if (!s.startsWith("function") && !/^\s*=>/.test(s.slice(end + 1))) return null;
|
|
3349
|
+
return s.slice(start, end);
|
|
3350
|
+
}
|
|
3351
|
+
function matchingParen(s, start) {
|
|
3352
|
+
let depth = 0;
|
|
3353
|
+
for (let i = start; i < s.length; i++) {
|
|
3354
|
+
const c = s[i];
|
|
3355
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
3356
|
+
const close = skipString(s, i);
|
|
3357
|
+
if (close === null) return null;
|
|
3358
|
+
i = close;
|
|
3359
|
+
continue;
|
|
3360
|
+
}
|
|
3361
|
+
if (c === "(" || c === "[" || c === "{") depth++;
|
|
3362
|
+
else if (c === "]" || c === "}") {
|
|
3363
|
+
if (--depth < 0) return null;
|
|
3364
|
+
} else if (c === ")") {
|
|
3365
|
+
if (depth === 0) return i;
|
|
3366
|
+
depth--;
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
return null;
|
|
3370
|
+
}
|
|
3371
|
+
function skipString(s, open) {
|
|
3372
|
+
const quote = s[open];
|
|
3373
|
+
for (let i = open + 1; i < s.length; i++) {
|
|
3374
|
+
if (s[i] === "\\") {
|
|
3375
|
+
i++;
|
|
3376
|
+
continue;
|
|
3377
|
+
}
|
|
3378
|
+
if (s[i] === quote) return i;
|
|
3379
|
+
}
|
|
3380
|
+
return null;
|
|
3381
|
+
}
|
|
3382
|
+
function splitTopLevel(inner) {
|
|
3383
|
+
const parts = [];
|
|
3384
|
+
let depth = 0;
|
|
3385
|
+
let angle = 0;
|
|
3386
|
+
let last = 0;
|
|
3387
|
+
for (let i = 0; i < inner.length; i++) {
|
|
3388
|
+
const c = inner[i];
|
|
3389
|
+
if (c === "=" && inner[i + 1] === ">") {
|
|
3390
|
+
i++;
|
|
3391
|
+
continue;
|
|
3392
|
+
}
|
|
3393
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
3394
|
+
const close = skipString(inner, i);
|
|
3395
|
+
if (close === null) return null;
|
|
3396
|
+
i = close;
|
|
3397
|
+
continue;
|
|
3398
|
+
}
|
|
3399
|
+
if (c === "(" || c === "[" || c === "{") depth++;
|
|
3400
|
+
else if (c === ")" || c === "]" || c === "}") {
|
|
3401
|
+
if (--depth < 0) return null;
|
|
3402
|
+
} else if (c === "<") angle++;
|
|
3403
|
+
else if (c === ">") {
|
|
3404
|
+
if (--angle < 0) return null;
|
|
3405
|
+
} else if (c === "," && depth === 0 && angle === 0) {
|
|
3406
|
+
parts.push(inner.slice(last, i));
|
|
3407
|
+
last = i + 1;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
if (depth !== 0 || angle !== 0) return null;
|
|
3411
|
+
parts.push(inner.slice(last));
|
|
3412
|
+
return parts;
|
|
3413
|
+
}
|
|
3414
|
+
function isConfirmableParam(part) {
|
|
3415
|
+
const head = PARAM_HEAD_RE.exec(part);
|
|
3416
|
+
if (!head) return false;
|
|
3417
|
+
const rest = part.slice(head[0].length);
|
|
3418
|
+
if (rest === "") return true;
|
|
3419
|
+
if (rest[0] !== ":") return false;
|
|
3420
|
+
return !hasDefaultAssign(rest.slice(1));
|
|
3421
|
+
}
|
|
3422
|
+
function hasDefaultAssign(annotation) {
|
|
3423
|
+
for (let i = 0; i < annotation.length; i++) {
|
|
3424
|
+
if (annotation[i] !== "=") continue;
|
|
3425
|
+
if (annotation[i + 1] === ">" || annotation[i + 1] === "=") {
|
|
3426
|
+
i++;
|
|
3427
|
+
continue;
|
|
3428
|
+
}
|
|
3429
|
+
const prev = annotation[i - 1];
|
|
3430
|
+
if (prev === "<" || prev === ">" || prev === "!" || prev === "=") continue;
|
|
3431
|
+
return true;
|
|
3432
|
+
}
|
|
3433
|
+
return false;
|
|
3434
|
+
}
|
|
3435
|
+
function buildAnalysisSrc(def) {
|
|
3436
|
+
return def.buildSigSrc ?? def.buildSrc ?? def.build.toString();
|
|
3437
|
+
}
|
|
3438
|
+
var RESOLVER_REASONS = {
|
|
3439
|
+
"rest-parameter": "its parameter list uses a rest parameter, so the declared arity is unbounded",
|
|
3440
|
+
"arguments": "its body references `arguments`",
|
|
3441
|
+
"reassigned": "the binding it names is reassigned, so the function read here is not decidable",
|
|
3442
|
+
"not-a-function": "the binding it names is not a function declaration",
|
|
3443
|
+
"unresolved-import": "its module could not be resolved or parsed",
|
|
3444
|
+
"not-found": "the name did not resolve to any binding",
|
|
3445
|
+
"computed": "it is a computed expression",
|
|
3446
|
+
"foreign-source": "it is named in a module the resolver was never told about, so its offsets index no scope tree the resolver holds",
|
|
3447
|
+
"ambiguous-source": "two distinct registered modules hold byte-identical text, so that text no longer names the module its offsets index"
|
|
3448
|
+
};
|
|
3449
|
+
function unconfirmableReason(def, src) {
|
|
3450
|
+
const declared = def.buildArityUnresolved;
|
|
3451
|
+
if (declared !== void 0 && RESOLVER_REASONS[declared]) return RESOLVER_REASONS[declared];
|
|
3452
|
+
const s = src.trim();
|
|
3453
|
+
const params = s.slice(0, s.indexOf(")") + 1);
|
|
3454
|
+
if (params.includes("...")) return RESOLVER_REASONS["rest-parameter"];
|
|
3455
|
+
if (/\barguments\b/.test(s)) return RESOLVER_REASONS["arguments"];
|
|
3456
|
+
if (/^[A-Za-z_$][\w$]*(\s*\.\s*[A-Za-z_$][\w$]*)?$/.test(s)) {
|
|
3457
|
+
return "it names a binding this build could not resolve to a function declaration";
|
|
3458
|
+
}
|
|
3459
|
+
return "its parameter list could not be parsed";
|
|
3460
|
+
}
|
|
3461
|
+
function confirmedArityForDef(def) {
|
|
3462
|
+
if (def.buildArity !== void 0) return def.buildArity;
|
|
3463
|
+
const src = buildAnalysisSrc(def);
|
|
3464
|
+
const arity = confirmedBuildArity(src);
|
|
3465
|
+
if (arity !== null) return arity;
|
|
3466
|
+
const subject = `build reducer \`${(def.buildSrc ?? "<runtime fn>").trim().split("\n")[0].slice(0, 60)}\``;
|
|
3467
|
+
recordDegradation({
|
|
3468
|
+
code: "build-arity-unconfirmed",
|
|
3469
|
+
severity: "warn",
|
|
3470
|
+
where: `node("${def.type ?? "<inferred>"}")`,
|
|
3471
|
+
subject,
|
|
3472
|
+
fellBackTo: `could not confirm its declared arity \u2014 ${unconfirmableReason(def, src)} \u2014 so this node captures children, fields and raw children, logs trivia, and clones \`_ctx.state\` on every match`,
|
|
3473
|
+
otherwise: "only the tiers the reducer actually declares would be captured (arity >= 1 children, >= 2 fields, >= 4 raw, >= 5 trivia, >= 6 state). Declare it with `node(..., { buildArity: n })` if it cannot be derived"
|
|
3474
|
+
});
|
|
3475
|
+
return null;
|
|
3476
|
+
}
|
|
3246
3477
|
function buildReadsChildren(def) {
|
|
3247
3478
|
if (!def.build) return true;
|
|
3248
|
-
const
|
|
3249
|
-
const arity = confirmedBuildArity(src);
|
|
3479
|
+
const arity = confirmedArityForDef(def);
|
|
3250
3480
|
if (arity === null) return true;
|
|
3251
3481
|
return arity >= 1;
|
|
3252
3482
|
}
|
|
3253
3483
|
function buildReadsRaw(def) {
|
|
3254
3484
|
if (!def.build) return true;
|
|
3255
|
-
const
|
|
3256
|
-
const arity = confirmedBuildArity(src);
|
|
3485
|
+
const arity = confirmedArityForDef(def);
|
|
3257
3486
|
if (arity === null) return true;
|
|
3258
3487
|
return arity >= 4;
|
|
3259
3488
|
}
|
|
3260
3489
|
function buildReadsTrivia(def) {
|
|
3261
3490
|
if (!def.build) return true;
|
|
3262
|
-
const
|
|
3263
|
-
const arity = confirmedBuildArity(src);
|
|
3491
|
+
const arity = confirmedArityForDef(def);
|
|
3264
3492
|
if (arity === null) return true;
|
|
3265
3493
|
return arity >= 5;
|
|
3266
3494
|
}
|
|
3267
3495
|
function buildReadsState(def) {
|
|
3268
3496
|
if (!def.build) return true;
|
|
3269
|
-
const
|
|
3270
|
-
const arity = confirmedBuildArity(src);
|
|
3497
|
+
const arity = confirmedArityForDef(def);
|
|
3271
3498
|
if (arity === null) return true;
|
|
3272
3499
|
return arity >= 6;
|
|
3273
3500
|
}
|
|
@@ -3275,8 +3502,7 @@ function buildReadsState(def) {
|
|
|
3275
3502
|
// src/compiler/fields.ts
|
|
3276
3503
|
function buildReadsFields(def) {
|
|
3277
3504
|
if (!def.build) return true;
|
|
3278
|
-
const
|
|
3279
|
-
const arity = confirmedBuildArity(src);
|
|
3505
|
+
const arity = confirmedArityForDef(def);
|
|
3280
3506
|
if (arity === null) return true;
|
|
3281
3507
|
return arity >= 2;
|
|
3282
3508
|
}
|
|
@@ -3457,6 +3683,13 @@ function isCstChild(value) {
|
|
|
3457
3683
|
function missingInferredType() {
|
|
3458
3684
|
throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
|
|
3459
3685
|
}
|
|
3686
|
+
function normalizeBuildArity(arity) {
|
|
3687
|
+
if (arity === void 0) return void 0;
|
|
3688
|
+
if (!Number.isInteger(arity) || arity < 0 || arity > 6) {
|
|
3689
|
+
throw new Error("node() buildArity must be an integer 0..6 (children, fields, span, rawChildren, triviaLog, state)");
|
|
3690
|
+
}
|
|
3691
|
+
return arity;
|
|
3692
|
+
}
|
|
3460
3693
|
function normalizeProject(project) {
|
|
3461
3694
|
if (project === void 0) return void 0;
|
|
3462
3695
|
if (!Number.isInteger(project) || project < 0) {
|
|
@@ -3492,13 +3725,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
|
|
|
3492
3725
|
const captureTrivia = opts?.captureTrivia === true;
|
|
3493
3726
|
const trailingTrivia = opts?.trailingTrivia === true;
|
|
3494
3727
|
const tags = opts?.tags;
|
|
3728
|
+
const buildArity = normalizeBuildArity(opts?.buildArity);
|
|
3495
3729
|
if (unwrap && collapse) {
|
|
3496
3730
|
throw new Error("node() options cannot set both unwrap and collapse");
|
|
3497
3731
|
}
|
|
3498
3732
|
if (project !== void 0 && (unwrap || collapse)) {
|
|
3499
3733
|
throw new Error("node() options cannot combine project with unwrap or collapse");
|
|
3500
3734
|
}
|
|
3501
|
-
const def = unwrap || collapse || project !== void 0 || captureTrivia || trailingTrivia || tags !== void 0 && tags.length > 0 ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...project !== void 0 ? { project } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {}, ...tags !== void 0 && tags.length > 0 ? { tags } : {} } : baseDef;
|
|
3735
|
+
const def = unwrap || collapse || project !== void 0 || captureTrivia || trailingTrivia || buildArity !== void 0 || tags !== void 0 && tags.length > 0 ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...project !== void 0 ? { project } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {}, ...buildArity !== void 0 ? { buildArity } : {}, ...tags !== void 0 && tags.length > 0 ? { tags } : {} } : baseDef;
|
|
3502
3736
|
const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
|
|
3503
3737
|
const clonesState = build ? buildReadsState(def) : project === void 0;
|
|
3504
3738
|
const hasOwnFields = parserHasOwnFields(combinator);
|
|
@@ -3544,7 +3778,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
|
|
|
3544
3778
|
const cstOutput = cstOutputHost(ctx.build);
|
|
3545
3779
|
const span = ctx.trackLines ? annotateSpanFromLineContext(r.span, ctx) : r.span;
|
|
3546
3780
|
const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
|
|
3547
|
-
const built = unwrap && children2.length === 1 ? unwrapChild(children2[0]) : collapse && children2.length === 1 ? children2[0] : !build && project === void 0 && ctx.build?._parsemanCstCollapse && children2.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children2[0], children2, rawChildren) ? children2[0] : project !== void 0 ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : projectChild(children2, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : build(children2, fields, span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, st, tags) : { _tag: "node", type: nodeType, span: ctx.trackLines ? { ...span } : { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
|
|
3781
|
+
const built = unwrap && children2.length === 1 ? unwrapChild(children2[0]) : collapse && children2.length === 1 ? children2[0] : (cstOutput || !build && project === void 0) && ctx.build?._parsemanCstCollapse && children2.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children2[0], children2, rawChildren) ? children2[0] : project !== void 0 ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : projectChild(children2, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : build(children2, fields, span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, st, tags) : { _tag: "node", type: nodeType, span: ctx.trackLines ? { ...span } : { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
|
|
3548
3782
|
const rawEntry = isCstChild(built) ? built : { _tag: "leaf", value: typeof built === "string" ? built : typeof built === "object" && built !== null ? input.slice(r.span.start, r.span.end) : "", span };
|
|
3549
3783
|
if (saved.buf !== void 0 || saved.ch !== void 0) {
|
|
3550
3784
|
pushCstChild(ctx, built, rawEntry);
|
|
@@ -3739,7 +3973,7 @@ function leaf(root, fn) {
|
|
|
3739
3973
|
}
|
|
3740
3974
|
|
|
3741
3975
|
// src/version.ts
|
|
3742
|
-
var PARSEMAN_VERSION = "0.
|
|
3976
|
+
var PARSEMAN_VERSION = "0.46.0";
|
|
3743
3977
|
|
|
3744
3978
|
// src/cst/host-mode.ts
|
|
3745
3979
|
var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
|
|
@@ -3794,14 +4028,28 @@ function firstSetToString(fs) {
|
|
|
3794
4028
|
return fs.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
|
|
3795
4029
|
}
|
|
3796
4030
|
var ruleNameOf2 = (p) => p._ruleName;
|
|
4031
|
+
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
3797
4032
|
var SUGGESTIONS = {
|
|
3798
|
-
"broad-recognizer": "
|
|
3799
|
-
"leading-not": "
|
|
3800
|
-
"nullable-prefix": "
|
|
3801
|
-
"cross-artifact-ref": "
|
|
3802
|
-
"opaque-wrapper": "
|
|
3803
|
-
"ref-cycle": "
|
|
4033
|
+
"broad-recognizer": "This arm can begin with any character, so no single-character test can rule it out. The parser has to enter it \u2014 set up, try, and undo \u2014 at every position it reaches, instead of skipping it for free.\nTo fix: make the arm begin with a fixed character, word or keyword. If it is meant to be a catch-all that matches anything, leave it and add the choice to the accept list at the end.",
|
|
4034
|
+
"leading-not": "This arm begins with a not(...) check, which matches nothing and so says nothing about which character the arm starts with. The parser then cannot rule the arm out and enters it everywhere.\nTo fix: put the term that actually consumes text first and keep not(...) after it as a trailing boundary. If you need to REQUIRE something ahead without consuming it, use peek(X) \u2014 it checks the same thing but still tells parseman that the arm starts with X.",
|
|
4035
|
+
"nullable-prefix": "This arm begins with something optional \u2014 an optional(...) or a repeat that allows zero items \u2014 so the arm can also start with whatever comes AFTER it, and that is broad enough that no character rules the arm out.\nTo fix: give the optional part its own arm so each arm has a definite beginning, or require at least one item. A plain sepBy(item, sep) also matches nothing at all; pass { min: 1 } when the list must not be empty.",
|
|
4036
|
+
"cross-artifact-ref": "This arm hands off to another rule, and that rule has the same problem \u2014 it can begin with any character \u2014 so the cost is inherited rather than caused here.\nTo fix: run this check on the rule it refers to and fix it there. One rule given a definite beginning fixes every choice that uses it.",
|
|
4037
|
+
"opaque-wrapper": "This arm begins with a wrapper that only inspects parser state (a gate/withCtx/recover) and consumes no text, so it says nothing about which character the arm starts with.\nTo fix: put a term that actually matches text in front of the wrapper.",
|
|
4038
|
+
"ref-cycle": "This arm refers back to itself through a cycle of rules, and parseman could not work out a definite first character for any of them.\nTo fix: make sure the base case of the recursion begins with a fixed character or keyword, so the cycle has somewhere definite to start."
|
|
3804
4039
|
};
|
|
4040
|
+
function refineSuggestion(cause, arm) {
|
|
4041
|
+
if (cause !== "broad-recognizer") return SUGGESTIONS[cause];
|
|
4042
|
+
const lead = peelToLeading(arm);
|
|
4043
|
+
const d = lead._def;
|
|
4044
|
+
if (d.tag === "regex" && KEYWORD_REGEX_RE.test(d.source)) {
|
|
4045
|
+
return `This arm matches the fixed word \`${d.source}\` with a regular expression, and parseman cannot always tell from a regular expression which character it starts with.
|
|
4046
|
+
To fix: write it as word('\u2026') or keywords([\u2026]) instead. They match exactly the same text and compile to the same character scan, but they also tell parseman the first character, which is what lets the parser skip this arm when it cannot match.`;
|
|
4047
|
+
}
|
|
4048
|
+
if (d.tag === "scanTo") {
|
|
4049
|
+
return "This arm ends in a scanTo(...) catch-all, which reads forward until it finds something and so can begin at any character. The parser can never rule it out. That is usually exactly what you want from a fallback.\nTo fix: usually nothing. Add this choice to the accept list at the end so the check keeps flagging the choices that are real problems.";
|
|
4050
|
+
}
|
|
4051
|
+
return SUGGESTIONS[cause];
|
|
4052
|
+
}
|
|
3805
4053
|
function classifyBroadArm(arm, resolve) {
|
|
3806
4054
|
const seen = /* @__PURE__ */ new Set();
|
|
3807
4055
|
const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
|
|
@@ -3917,7 +4165,6 @@ function peelToLeading(arm) {
|
|
|
3917
4165
|
if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
|
|
3918
4166
|
return cur;
|
|
3919
4167
|
}
|
|
3920
|
-
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
3921
4168
|
function detectAntiPatterns(rule, arms) {
|
|
3922
4169
|
const out = [];
|
|
3923
4170
|
arms.forEach((arm, i) => {
|
|
@@ -3930,14 +4177,14 @@ function detectAntiPatterns(rule, arms) {
|
|
|
3930
4177
|
kind: "double-not",
|
|
3931
4178
|
rule,
|
|
3932
4179
|
armIndex: i,
|
|
3933
|
-
message:
|
|
4180
|
+
message: 'not(not(X)) is a hand-written way of saying "X must come next, but do not consume it". parseman does that for you, and the hand-written form is worse than nothing here: because not(...) reveals no first character, the arm cannot be skipped, and among sibling arms that share a first character it can select the wrong one.\nTo fix: write peek(X). It checks the same thing without consuming, and it also tells parseman that the arm starts with whatever X starts with.'
|
|
3934
4181
|
});
|
|
3935
4182
|
} else {
|
|
3936
4183
|
out.push({
|
|
3937
4184
|
kind: "leading-not",
|
|
3938
4185
|
rule,
|
|
3939
4186
|
armIndex: i,
|
|
3940
|
-
message: "
|
|
4187
|
+
message: "This arm begins with not(...), which matches no text and so reveals no first character. The parser therefore cannot skip the arm and enters it at every position.\nTo fix: put the term that actually consumes text first, and keep not(...) after it as a trailing boundary check."
|
|
3941
4188
|
});
|
|
3942
4189
|
}
|
|
3943
4190
|
}
|
|
@@ -3946,7 +4193,7 @@ function detectAntiPatterns(rule, arms) {
|
|
|
3946
4193
|
kind: "keyword-regex",
|
|
3947
4194
|
rule,
|
|
3948
4195
|
armIndex: i,
|
|
3949
|
-
message: `
|
|
4196
|
+
message: `This arm starts with the fixed word \`${ld.source}\` written as a regular expression. Writing it as word('\u2026') or keywords([\u2026]) matches exactly the same text and compiles to the same character scan, but also tells parseman the first character, which is what lets the parser skip the arm when it cannot match.`
|
|
3950
4197
|
});
|
|
3951
4198
|
}
|
|
3952
4199
|
});
|
|
@@ -3988,7 +4235,7 @@ function analyzeGatingRules(ruleMap, opts) {
|
|
|
3988
4235
|
const d = def;
|
|
3989
4236
|
const rule = ruleNameOf2(p) ?? enclosingRule;
|
|
3990
4237
|
if (d.tag === "choice") {
|
|
3991
|
-
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
|
|
4238
|
+
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule, arms: d.parsers });
|
|
3992
4239
|
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
3993
4240
|
}
|
|
3994
4241
|
const rec = d;
|
|
@@ -4017,13 +4264,15 @@ function analyzeGatingRules(ruleMap, opts) {
|
|
|
4017
4264
|
const seenInRule = /* @__PURE__ */ new Map();
|
|
4018
4265
|
const accept = new Set(opts?.accept ?? []);
|
|
4019
4266
|
const usedAccept = /* @__PURE__ */ new Set();
|
|
4020
|
-
const choices = raw.map(({ g, rule }) => {
|
|
4267
|
+
const choices = raw.map(({ g, rule, arms }) => {
|
|
4021
4268
|
const n = seenInRule.get(rule) ?? 0;
|
|
4022
4269
|
seenInRule.set(rule, n + 1);
|
|
4023
4270
|
const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
|
|
4024
4271
|
const isAccepted = g.gates === "no" && accept.has(id);
|
|
4025
4272
|
if (isAccepted) usedAccept.add(id);
|
|
4026
|
-
|
|
4273
|
+
const out = { ...g, id, accepted: isAccepted };
|
|
4274
|
+
Object.defineProperty(out, "arms", { value: arms, enumerable: false, writable: false });
|
|
4275
|
+
return out;
|
|
4027
4276
|
});
|
|
4028
4277
|
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
4029
4278
|
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
@@ -4050,7 +4299,14 @@ function analyzeChoice(p, d, rule, resolve) {
|
|
|
4050
4299
|
const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve));
|
|
4051
4300
|
const anyArms = deep.map((fs, index) => ({ fs, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
|
|
4052
4301
|
const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve);
|
|
4053
|
-
return {
|
|
4302
|
+
return {
|
|
4303
|
+
index,
|
|
4304
|
+
cause,
|
|
4305
|
+
detail,
|
|
4306
|
+
shallowAnyOnly: false,
|
|
4307
|
+
unresolvedExternal: unresolvedExternal === true,
|
|
4308
|
+
suggestion: refineSuggestion(cause, arms[index])
|
|
4309
|
+
};
|
|
4054
4310
|
});
|
|
4055
4311
|
const overlaps = [];
|
|
4056
4312
|
for (let i = 0; i < deep.length; i++)
|
|
@@ -4060,7 +4316,7 @@ function analyzeChoice(p, d, rule, resolve) {
|
|
|
4060
4316
|
a: i,
|
|
4061
4317
|
b: j,
|
|
4062
4318
|
on: intersection(deep[i], deep[j]),
|
|
4063
|
-
suggestion: "arms
|
|
4319
|
+
suggestion: "Two arms of this choice can begin with the same character, so the parser cannot tell from that character which one to try. It tries them in order and undoes the ones that do not match.\nTo fix: pull the shared beginning out in front of the choice so it is matched once \u2014 sequence(shared, choice(rest\u2026)) \u2014 instead of repeating it inside each arm. parseman recognises that shape automatically and turns it back into a single test."
|
|
4064
4320
|
});
|
|
4065
4321
|
let gates;
|
|
4066
4322
|
if (d.disjoint) gates = "yes";
|
|
@@ -4614,7 +4870,7 @@ function findDivergentSlot(members, shapeOf) {
|
|
|
4614
4870
|
const path = [];
|
|
4615
4871
|
const defs = [];
|
|
4616
4872
|
let cur = [...members];
|
|
4617
|
-
for (let
|
|
4873
|
+
for (let guard = 0; guard < 64; guard++) {
|
|
4618
4874
|
const d0 = cur[0]._def;
|
|
4619
4875
|
const kidLists = cur.map((c) => childrenOf2(c._def));
|
|
4620
4876
|
const arity = kidLists[0].length;
|
|
@@ -5573,7 +5829,15 @@ function children(def, winners) {
|
|
|
5573
5829
|
let resolved;
|
|
5574
5830
|
try {
|
|
5575
5831
|
resolved = def.thunk();
|
|
5576
|
-
} catch {
|
|
5832
|
+
} catch (e) {
|
|
5833
|
+
recordDegradation({
|
|
5834
|
+
code: "coverage-definitions-unavailable",
|
|
5835
|
+
severity: "warn",
|
|
5836
|
+
where: "<lazy rule reference>",
|
|
5837
|
+
subject: `unresolvable reference (${e.message})`,
|
|
5838
|
+
fellBackTo: "the coverage walk stopped at this reference, so every definition below it is ABSENT from the denominator \u2014 which raises the reported ratio rather than lowering it",
|
|
5839
|
+
otherwise: "the referenced rule and its whole subtree would contribute definitions"
|
|
5840
|
+
});
|
|
5577
5841
|
return [];
|
|
5578
5842
|
}
|
|
5579
5843
|
const name = resolved._ruleName;
|
|
@@ -5864,15 +6128,15 @@ function parseStringShape(source) {
|
|
|
5864
6128
|
if (source.length < 3 || source[source.length - 1] !== q) return null;
|
|
5865
6129
|
const inner = source.slice(1, -1);
|
|
5866
6130
|
const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
|
|
5867
|
-
const
|
|
6131
|
+
const ESC2 = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
|
|
5868
6132
|
let body;
|
|
5869
6133
|
let esc;
|
|
5870
|
-
let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${
|
|
6134
|
+
let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC2}\\)\\*$`).exec(inner);
|
|
5871
6135
|
if (m) {
|
|
5872
6136
|
body = m[1];
|
|
5873
6137
|
esc = m[2];
|
|
5874
6138
|
} else {
|
|
5875
|
-
m = new RegExp(`^\\((?:\\?:)?${
|
|
6139
|
+
m = new RegExp(`^\\((?:\\?:)?${ESC2}\\|${CLS}\\)\\*$`).exec(inner);
|
|
5876
6140
|
if (m) {
|
|
5877
6141
|
esc = m[1];
|
|
5878
6142
|
body = m[2];
|
|
@@ -5882,8 +6146,8 @@ function parseStringShape(source) {
|
|
|
5882
6146
|
const excluded = parseClassRanges(body);
|
|
5883
6147
|
if (!excluded) return null;
|
|
5884
6148
|
const qcp = q.codePointAt(0);
|
|
5885
|
-
const
|
|
5886
|
-
if (!
|
|
6149
|
+
const inSet2 = (cp) => excluded.some(([lo, hi]) => cp >= lo && cp <= hi);
|
|
6150
|
+
if (!inSet2(qcp) || !inSet2(BACKSLASH)) return null;
|
|
5887
6151
|
return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
|
|
5888
6152
|
}
|
|
5889
6153
|
function parseSeqParts(source) {
|
|
@@ -7074,27 +7338,53 @@ function emitScannableTerminal(shape, c) {
|
|
|
7074
7338
|
}
|
|
7075
7339
|
|
|
7076
7340
|
// src/compiler/inline-build.ts
|
|
7077
|
-
var
|
|
7341
|
+
var ANN = String.raw`\s*\??\s*(?::[^,)=]+)?`;
|
|
7342
|
+
var ANY_P = String.raw`\w+${ANN}`;
|
|
7343
|
+
var MK_BUILD_RE = new RegExp(
|
|
7344
|
+
String.raw`^\(\s*(\w+)${ANN}\s*,\s*${ANY_P}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*\)\s*=>\s*mk\s*\(\s*(['"])([^'"]+)\5\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
|
|
7345
|
+
);
|
|
7078
7346
|
function escapeRegExp(s) {
|
|
7079
7347
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7080
7348
|
}
|
|
7349
|
+
function identMkBuildRe(typeIdent) {
|
|
7350
|
+
return new RegExp(
|
|
7351
|
+
String.raw`^\(\s*(\w+)${ANN}\s*,\s*${ANY_P}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*\)\s*=>\s*(?:[\w$.]*\.)?mk\s*\(\s*${escapeRegExp(typeIdent)}\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
|
|
7352
|
+
);
|
|
7353
|
+
}
|
|
7081
7354
|
function looseMkBuildRe(type) {
|
|
7082
7355
|
return new RegExp(
|
|
7083
|
-
String.raw`^\(\s*(\w+)\s*,\s
|
|
7356
|
+
String.raw`^\(\s*(\w+)${ANN}\s*,\s*${ANY_P}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*\)\s*=>\s*.+\(\s*(['"])${escapeRegExp(type)}\5\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
|
|
7084
7357
|
);
|
|
7085
7358
|
}
|
|
7359
|
+
var LOOKS_LIKE_MK_RE = /=>\s*(?:[\w$.]*\.)?mk\s*\(/;
|
|
7086
7360
|
function analyzeMkInlineBuild(def) {
|
|
7087
7361
|
if (!def.build) return null;
|
|
7088
7362
|
if (def.type === void 0) return null;
|
|
7089
|
-
const src = (def
|
|
7363
|
+
const src = buildAnalysisSrc(def).trim();
|
|
7090
7364
|
const strict = src.match(MK_BUILD_RE);
|
|
7091
7365
|
if (strict) {
|
|
7092
7366
|
const mkType = strict[6];
|
|
7093
|
-
|
|
7367
|
+
if (mkType === def.type) return def.type;
|
|
7368
|
+
reportMkMiss(def.type, src, `it builds a "${mkType}" node, not "${def.type}"`);
|
|
7369
|
+
return null;
|
|
7094
7370
|
}
|
|
7095
7371
|
if (looseMkBuildRe(def.type).test(src)) return def.type;
|
|
7372
|
+
if (def.typeSrc !== void 0 && identMkBuildRe(def.typeSrc).test(src)) return def.type;
|
|
7373
|
+
if (LOOKS_LIKE_MK_RE.test(src)) {
|
|
7374
|
+
reportMkMiss(def.type, src, "its parameter list or argument order does not match `(children, fields, span, rawChildren, triviaLog) => mk(type, children, rawChildren, span, triviaLog)`");
|
|
7375
|
+
}
|
|
7096
7376
|
return null;
|
|
7097
7377
|
}
|
|
7378
|
+
function reportMkMiss(type, src, why) {
|
|
7379
|
+
recordDegradation({
|
|
7380
|
+
code: "mk-inline-missed",
|
|
7381
|
+
severity: "warn",
|
|
7382
|
+
where: `node("${type}")`,
|
|
7383
|
+
subject: `build reducer \`${src.split("\n")[0].slice(0, 80)}\``,
|
|
7384
|
+
fellBackTo: `looks like an inline-\`mk\` builder but ${why}, so every match pays a \`_build[n](...)\` call instead of an inlined object literal`,
|
|
7385
|
+
otherwise: "the node object would be constructed at the call site with no call frame"
|
|
7386
|
+
});
|
|
7387
|
+
}
|
|
7098
7388
|
function emitInlineMkNodeExpr(type, chV, rawV, spanExpr, tlV) {
|
|
7099
7389
|
return `{ _tag: 'node', type: ${JSON.stringify(type)}, span: ${spanExpr}, children: ${chV}, rawCount: ${rawV}.length, localTriviaLen: ${tlV}.length }`;
|
|
7100
7390
|
}
|
|
@@ -7277,10 +7567,34 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
7277
7567
|
seen.delete(p);
|
|
7278
7568
|
}
|
|
7279
7569
|
}
|
|
7570
|
+
function tern(cond, whenTrue, whenFalse) {
|
|
7571
|
+
if (cond === "false") return whenFalse;
|
|
7572
|
+
if (cond === "true") return whenTrue;
|
|
7573
|
+
return `${cond} ? ${whenTrue} : ${whenFalse}`;
|
|
7574
|
+
}
|
|
7575
|
+
function notGate(cond) {
|
|
7576
|
+
if (cond === "false") return "true";
|
|
7577
|
+
if (cond === "true") return "false";
|
|
7578
|
+
return `!(${cond})`;
|
|
7579
|
+
}
|
|
7580
|
+
function orGate(a, b) {
|
|
7581
|
+
if (a === "false") return b;
|
|
7582
|
+
if (b === "false") return a;
|
|
7583
|
+
if (a === "true" || b === "true") return "true";
|
|
7584
|
+
return `${a} || ${b}`;
|
|
7585
|
+
}
|
|
7586
|
+
function andGate(a, b) {
|
|
7587
|
+
if (a === "false" || b === "false") return "false";
|
|
7588
|
+
if (a === "true") return b;
|
|
7589
|
+
if (b === "true") return a;
|
|
7590
|
+
return `${a} && ${b}`;
|
|
7591
|
+
}
|
|
7280
7592
|
var HOST_READS_DECL = "const _hostReads = (b, n) => { if (b === undefined) return false; let s; try { s = Function.prototype.toString.call(b) } catch (e) { return true } if (/\\barguments\\b/.test(s)) return true; const m = /^[^(]*\\(([\\s\\S]*?)\\)/.exec(s); if (m && /\\.\\.\\.|=/.test(m[1])) return true; return b.length > n }";
|
|
7593
|
+
var RAW_ENTRY_DECL = "const _rawEntry = (v, input, s, e, span) => (typeof v === 'object' && v !== null && (v._tag === 'node' || v._tag === 'leaf' || v._tag === 'parseError')) ? v : { _tag: 'leaf', value: typeof v === 'string' ? v : (typeof v === 'object' && v !== null ? input.slice(s, e) : ''), span: span ?? { start: s, end: e } }";
|
|
7281
7594
|
var LINE_TRACK_DECL = "const _trackLines = (_ctx, input, start, end) => { const from = _ctx._lineScannedTo ?? 0; if (end <= from) return; for (let i = from; i < end; i++) if (input.charCodeAt(i) === 10) _ctx._lineStarts.push(i + 1); _ctx._lineScannedTo = end }";
|
|
7282
7595
|
var LINE_SPAN_DECL = "const _lineCol = (_ctx, offset) => { const starts = _ctx._lineStarts; let lo = 0, hi = starts.length - 1; while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (starts[mid] <= offset) lo = mid; else hi = mid - 1 } return [lo + 1, offset - starts[lo] + 1] }\nconst _spanLines = (_ctx, start, end) => { const s = _lineCol(_ctx, start), e = _lineCol(_ctx, end); return { start, end, startLine: s[0], startColumn: s[1], endLine: e[0], endColumn: e[1] } }";
|
|
7283
7596
|
var _loweringSink = null;
|
|
7597
|
+
var _inlineCapSink = null;
|
|
7284
7598
|
function hasSelectedRootTrivia(ctx) {
|
|
7285
7599
|
return ctx.rootTriviaClassified === true;
|
|
7286
7600
|
}
|
|
@@ -8029,9 +8343,11 @@ function emitSeqValues(def, ctx, pos) {
|
|
|
8029
8343
|
const markLog = v(ctx, "_mklg");
|
|
8030
8344
|
const markRootLog = hasSelectedRootTrivia(ctx) ? v(ctx, "_mkrlg") : null;
|
|
8031
8345
|
const scanEndV = v(ctx, "_sne");
|
|
8032
|
-
const
|
|
8346
|
+
const logV = v(ctx, "_tlg");
|
|
8347
|
+
const capArg = ctx.noHoist ? "0" : hasSelectedRootTrivia(ctx) ? `(${logV} !== undefined || _ctx._rootTriviaLog !== undefined) ? 2 : 0` : `${logV} !== undefined ? 2 : 0`;
|
|
8033
8348
|
stmts.push(
|
|
8034
|
-
`${ind(ctx)}const ${
|
|
8349
|
+
`${ind(ctx)}const ${logV} = _ctx._triviaLog`,
|
|
8350
|
+
`${ind(ctx)}const ${markLog} = ${logV} !== undefined ? ${logV}.length : 0`,
|
|
8035
8351
|
...markRootLog ? [`${ind(ctx)}const ${markRootLog} = _ctx._rootTriviaLog ? _ctx._rootTriviaLog.length : 0`] : [],
|
|
8036
8352
|
`${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx, ${capArg})`,
|
|
8037
8353
|
...emitLineTrack(ctx, curV, scanEndV)
|
|
@@ -8041,7 +8357,7 @@ function emitSeqValues(def, ctx, pos) {
|
|
|
8041
8357
|
const endAfterV = v(ctx, "_sea");
|
|
8042
8358
|
stmts.push(
|
|
8043
8359
|
`${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
|
|
8044
|
-
`${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}; else { if (
|
|
8360
|
+
`${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}; else { if (${logV} !== undefined && ${logV}.length !== ${markLog}) ${logV}.length = ${markLog};${markRootLog ? ` if (_ctx._rootTriviaLog && _ctx._rootTriviaLog.length !== ${markRootLog}) _ctx._rootTriviaLog.length = ${markRootLog};` : ""} }`
|
|
8045
8361
|
);
|
|
8046
8362
|
valueVars.push(r2.valueVar);
|
|
8047
8363
|
continue;
|
|
@@ -9113,9 +9429,18 @@ function emitPeek(def, ctx, pos) {
|
|
|
9113
9429
|
endVar: pos
|
|
9114
9430
|
};
|
|
9115
9431
|
}
|
|
9116
|
-
function emitRouted(ctx, pos) {
|
|
9432
|
+
function emitRouted(ctx, pos, def) {
|
|
9117
9433
|
const local = ctx.routedLocal;
|
|
9434
|
+
const fallback = def.fallback;
|
|
9118
9435
|
if (local !== void 0) {
|
|
9436
|
+
if (fallback !== void 0 && local.startVar !== pos) {
|
|
9437
|
+
return emitRoutedWithFallback(ctx, pos, fallback, {
|
|
9438
|
+
test: `${local.startVar} === ${pos}`,
|
|
9439
|
+
valExpr: local.valueVar,
|
|
9440
|
+
startExpr: local.startVar,
|
|
9441
|
+
endExpr: local.endVar
|
|
9442
|
+
});
|
|
9443
|
+
}
|
|
9119
9444
|
return {
|
|
9120
9445
|
stmts: [
|
|
9121
9446
|
...emitIfFail(ctx, `${local.startVar} !== ${pos}`, failBody(ctx, '"routed()"', pos)),
|
|
@@ -9126,9 +9451,19 @@ function emitRouted(ctx, pos) {
|
|
|
9126
9451
|
};
|
|
9127
9452
|
}
|
|
9128
9453
|
const item = v(ctx, "_rt");
|
|
9454
|
+
const read = `${ind(ctx)}const ${item} = _ctx._routed`;
|
|
9455
|
+
if (fallback !== void 0) {
|
|
9456
|
+
const r = emitRoutedWithFallback(ctx, pos, fallback, {
|
|
9457
|
+
test: `${item} !== undefined && ${item}.span.start === ${pos}`,
|
|
9458
|
+
valExpr: `${item}.value`,
|
|
9459
|
+
startExpr: `${item}.span.start`,
|
|
9460
|
+
endExpr: `${item}.span.end`
|
|
9461
|
+
});
|
|
9462
|
+
return { ...r, stmts: [read, ...r.stmts] };
|
|
9463
|
+
}
|
|
9129
9464
|
return {
|
|
9130
9465
|
stmts: [
|
|
9131
|
-
|
|
9466
|
+
read,
|
|
9132
9467
|
...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
|
|
9133
9468
|
...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
|
|
9134
9469
|
],
|
|
@@ -9136,6 +9471,30 @@ function emitRouted(ctx, pos) {
|
|
|
9136
9471
|
endVar: `${item}.span.end`
|
|
9137
9472
|
};
|
|
9138
9473
|
}
|
|
9474
|
+
function emitRoutedWithFallback(ctx, pos, fallback, routed2) {
|
|
9475
|
+
const valV = v(ctx, "_rtv");
|
|
9476
|
+
const endV = v(ctx, "_rte");
|
|
9477
|
+
const i = ind(ctx);
|
|
9478
|
+
ctx.indent++;
|
|
9479
|
+
const capture = emitLeafCapture(ctx, routed2.valExpr, routed2.startExpr, routed2.endExpr);
|
|
9480
|
+
const fb = emit(fallback, ctx, pos);
|
|
9481
|
+
const inner = ind(ctx);
|
|
9482
|
+
ctx.indent--;
|
|
9483
|
+
return {
|
|
9484
|
+
stmts: [
|
|
9485
|
+
`${i}let ${valV}, ${endV}`,
|
|
9486
|
+
`${i}if (${routed2.test}) {`,
|
|
9487
|
+
...capture,
|
|
9488
|
+
`${inner}${valV} = ${routed2.valExpr}; ${endV} = ${routed2.endExpr}`,
|
|
9489
|
+
`${i}} else {`,
|
|
9490
|
+
...fb.stmts,
|
|
9491
|
+
`${inner}${valV} = ${fb.valueVar}; ${endV} = ${fb.endVar}`,
|
|
9492
|
+
`${i}}`
|
|
9493
|
+
],
|
|
9494
|
+
valueVar: valV,
|
|
9495
|
+
endVar: endV
|
|
9496
|
+
};
|
|
9497
|
+
}
|
|
9139
9498
|
function escapeTokenLiteral(value) {
|
|
9140
9499
|
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
9141
9500
|
}
|
|
@@ -9317,22 +9676,16 @@ function emitNode(def, ctx, pos) {
|
|
|
9317
9676
|
const smk = structural ? v(ctx, "_smk") : null;
|
|
9318
9677
|
if (structural) ctx.needsHostReads = true;
|
|
9319
9678
|
const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
|
|
9320
|
-
const
|
|
9321
|
-
const
|
|
9322
|
-
const profileRecognizer = recV;
|
|
9323
|
-
const profileCapture = capV;
|
|
9324
|
-
const cstMode = ctx.hostMode === "cst";
|
|
9325
|
-
const directCstV = !structural && !cstMode && (!capturesChildren || !capturesRaw) ? v(ctx, "_dcst") : null;
|
|
9326
|
-
const directCstGate = directCstV === null ? "false" : profileCapture;
|
|
9679
|
+
const profileRecognizer = "false";
|
|
9680
|
+
const profileCapture = "false";
|
|
9327
9681
|
const structuralCapturesTrivia = structural && (def.captureTrivia === true || def.trailingTrivia === true);
|
|
9328
9682
|
const chNeededExpr = def.unwrap || def.collapse ? "true" : `(_ctx._pmReadsCh ??= (_ctx.build === undefined || _ctx.build._parsemanReadsChildren !== false || _ctx.build._parsemanCstCollapse !== undefined))`;
|
|
9329
|
-
const chAlloc = def.unwrap || def.collapse ?
|
|
9330
|
-
const
|
|
9331
|
-
|
|
9332
|
-
|
|
9333
|
-
` : ""}${i}const ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}`;
|
|
9683
|
+
const chAlloc = def.unwrap || def.collapse ? tern(profileRecognizer, "undefined", "[]") : tern(profileRecognizer, "undefined", `${tern(orGate(profileCapture, chNeededExpr), "[]", "undefined")}`);
|
|
9684
|
+
const dcstAlloc = "undefined";
|
|
9685
|
+
const allocStmt = structural ? `${i}const ${capTLv} = ${andGate(notGate(profileRecognizer), orGate(profileCapture, structuralCapturesTrivia ? "true" : hostTriviaGate))}, ${capSTv} = ${andGate(notGate(orGate(profileRecognizer, profileCapture)), "(_ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 6)))")}${capFv ? `, ${capFv} = ${andGate(notGate(profileRecognizer), orGate(profileCapture, "(_ctx.build !== undefined && _hostReads(_ctx.build, 2))"))}` : ""}
|
|
9686
|
+
${i}const ${chV} = ${chAlloc}, ${rawV} = ${tern(profileRecognizer, "undefined", "[]")}, ${tlV} = ${tern(profileRecognizer, "undefined", innerEnablesTriviaCapture ? "[]" : `${capTLv} ? [] : _EMPTY_TL`)}` : capturesTrivia ? `${i}const ${capTLv} = ${notGate(profileRecognizer)}, ${chV} = ${tern(profileRecognizer, "undefined", capturesChildren ? "[]" : dcstAlloc)}, ${rawV} = ${tern(profileRecognizer, "undefined", capturesRaw ? "[]" : dcstAlloc)}, ${tlV} = ${tern(profileRecognizer, "undefined", "[]")}` : `${i}const ${chV} = ${tern(profileRecognizer, "undefined", capturesChildren ? "[]" : dcstAlloc)}, ${rawV} = ${tern(profileRecognizer, "undefined", capturesRaw ? "[]" : dcstAlloc)}`;
|
|
9334
9687
|
const innerTl = structural || capturesTrivia ? structural && !innerEnablesTriviaCapture ? `${capTLv} ? ${tlV} : undefined` : tlV : "undefined";
|
|
9335
|
-
const fieldsOn = structural ? capFv ?? "false" :
|
|
9688
|
+
const fieldsOn = structural ? capFv ?? "false" : tern(profileRecognizer, "false", capturesFields ? "true" : "false");
|
|
9336
9689
|
const sf = hasFields ? v(ctx, "_sf") : null;
|
|
9337
9690
|
const fArr = hasFields ? v(ctx, "_fa") : null;
|
|
9338
9691
|
const fObj = hasFields ? v(ctx, "_fields") : "undefined";
|
|
@@ -9341,7 +9694,7 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
|
|
|
9341
9694
|
const installTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${capTLv ?? "false"}; _ctx._cstTriviaLog = ${innerTl}${smk ? `; _ctx._triviaCaptureMask = ${capTLv} && _ctx.build !== undefined && _ctx.build._parsemanTriviaKinds !== undefined ? _ctx.build._parsemanTriviaKinds(${JSON.stringify(def.type)}) : ${smk}` : ""}` : "";
|
|
9342
9695
|
const restoreTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${smk ? `; _ctx._triviaCaptureMask = ${smk}` : ""}` : "";
|
|
9343
9696
|
const preGuard = [];
|
|
9344
|
-
if (!ctx.recovery && !ctx.coverage &&
|
|
9697
|
+
if (!ctx.recovery && !ctx.coverage && needsFirstSetGuard(def.parser)) {
|
|
9345
9698
|
const gcV = v(ctx, "_ngc");
|
|
9346
9699
|
const gExp = armStaticExpected(ctx, def.parser);
|
|
9347
9700
|
const gFail = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${pos}; _ctx._fx = ${gExp}; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : failReturnArr(ctx, gExp, pos);
|
|
@@ -9352,7 +9705,6 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
|
|
|
9352
9705
|
}
|
|
9353
9706
|
const stmts = [
|
|
9354
9707
|
...preGuard,
|
|
9355
|
-
profHoist,
|
|
9356
9708
|
allocStmt,
|
|
9357
9709
|
`${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren${saveTrivia}${sf ? `, ${sf} = _ctx._fields` : ""}`,
|
|
9358
9710
|
`${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}${installTrivia}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
|
|
@@ -9369,21 +9721,13 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
|
|
|
9369
9721
|
}
|
|
9370
9722
|
stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}${restoreTrivia}${sf ? `; _ctx._fields = ${sf}` : ""}`);
|
|
9371
9723
|
stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
|
|
9372
|
-
stmts.push(
|
|
9373
|
-
`${i}if (_ctx._pmProfile) {`,
|
|
9374
|
-
`${i} _ctx._pmProfile.nodes++`,
|
|
9375
|
-
`${i} if (${profileCapture}) {`,
|
|
9376
|
-
`${i} _ctx._pmProfile.childSlots += ${chV}.length; _ctx._pmProfile.rawSlots += ${rawV}.length; _ctx._pmProfile.triviaSlots += ${tlV}.length${fArr ? `; _ctx._pmProfile.fieldSlots += ${fArr} ? ${fArr}.length : 0` : ""}`,
|
|
9377
|
-
`${i} }`,
|
|
9378
|
-
`${i}}`
|
|
9379
|
-
);
|
|
9380
9724
|
let stV = "undefined";
|
|
9381
9725
|
if (structural) {
|
|
9382
9726
|
stV = v(ctx, "_nst");
|
|
9383
9727
|
stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
|
|
9384
9728
|
} else if (clonesState) {
|
|
9385
9729
|
stV = v(ctx, "_nst");
|
|
9386
|
-
stmts.push(`${i}const ${stV} =
|
|
9730
|
+
stmts.push(`${i}const ${stV} = ${andGate(notGate(orGate(profileRecognizer, profileCapture)), "_ctx.state !== undefined")} ? Object.assign({}, _ctx.state) : undefined`);
|
|
9387
9731
|
}
|
|
9388
9732
|
if (sf && fArr) {
|
|
9389
9733
|
const fe = v(ctx, "_fe");
|
|
@@ -9407,19 +9751,22 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
|
|
|
9407
9751
|
if (ctx.lineTracking) stmts.push(`${i}const ${nodeSpanV} = ${nodeSpanExpr}`);
|
|
9408
9752
|
const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: ${nodeSpanV}, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, nodeSpanV, tlV) : def.project !== void 0 ? emitNodeProjectExpr(def.type, def.project, chV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV})`;
|
|
9409
9753
|
const hostBuildArgs = `${JSON.stringify(def.type)}, ${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV}${def.tags !== void 0 && def.tags.length > 0 ? `, ${JSON.stringify(def.tags)}` : ""}`;
|
|
9410
|
-
const hostBuildExpr = `_ctx.
|
|
9754
|
+
const hostBuildExpr = `_ctx.build(${hostBuildArgs})`;
|
|
9411
9755
|
const ndExpr = structural ? `_ctx.build !== undefined ? (${hostBuildExpr}) : (${buildExpr})` : cstOut ? hostBuildExpr : buildExpr;
|
|
9412
|
-
const
|
|
9756
|
+
const hostCollapses = structural || cstOut;
|
|
9757
|
+
const hostCollapseExpr = hostCollapses ? `_ctx.build !== undefined && _ctx.build._parsemanCstCollapse !== undefined && ${chV}.length === 1 && ${rawV}.length === 1 && _ctx.build._parsemanCstCollapse(${JSON.stringify(def.type)}, ${chV}[0], ${chV}, ${rawV}) ? ${chV}[0] : (${ndExpr})` : ndExpr;
|
|
9413
9758
|
const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
|
|
9414
9759
|
const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
|
|
9415
9760
|
const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
);
|
|
9761
|
+
const ndGate = orGate(profileRecognizer, profileCapture);
|
|
9762
|
+
const recGate = notGate(profileRecognizer);
|
|
9763
|
+
const pushIndent = recGate === "true" ? i : `${i} `;
|
|
9764
|
+
const pushCh = `${pushIndent}if (${sc}) ${sc}.push(${ndV})`;
|
|
9765
|
+
ctx.needsRawEntry = true;
|
|
9766
|
+
const pushRaw = `${pushIndent}if (${sr}) ${sr}.push(_rawEntry(${ndV}, input, ${pos}, ${endVar}${ctx.lineTracking ? `, ${nodeSpanV}` : ""}))`;
|
|
9767
|
+
stmts.push(`${i}const ${ndV} = ${tern(ndGate, "undefined", `(${finalExpr})`)}`);
|
|
9768
|
+
if (recGate === "true") stmts.push(pushCh, pushRaw);
|
|
9769
|
+
else stmts.push(`${i}if (${recGate}) {`, pushCh, pushRaw, `${i}}`);
|
|
9423
9770
|
return { stmts, valueVar: ndV, endVar };
|
|
9424
9771
|
}
|
|
9425
9772
|
function emitRuntimeFallback(parser2, ctx, pos) {
|
|
@@ -9448,7 +9795,12 @@ function emitLazy(p, def, ctx, pos) {
|
|
|
9448
9795
|
} catch {
|
|
9449
9796
|
return emitRuntimeFallback(p, ctx, pos);
|
|
9450
9797
|
}
|
|
9451
|
-
|
|
9798
|
+
const cost = usage.sizes.get(resolved) ?? 1;
|
|
9799
|
+
if (cost <= ctx.inlineLeft) {
|
|
9800
|
+
ctx.inlineLeft -= cost;
|
|
9801
|
+
return emit(resolved, ctx, pos);
|
|
9802
|
+
}
|
|
9803
|
+
_inlineCapSink?.push({ fn: ctx.currentFnName ?? "<root>", nodes: cost });
|
|
9452
9804
|
}
|
|
9453
9805
|
if (!ctx.namedParsers.has(p)) {
|
|
9454
9806
|
const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
|
|
@@ -9463,6 +9815,9 @@ function emitLazy(p, def, ctx, pos) {
|
|
|
9463
9815
|
const savedIndent = ctx.indent;
|
|
9464
9816
|
const savedFailLabel = ctx.failLabel;
|
|
9465
9817
|
const savedRecord = ctx.recordFail;
|
|
9818
|
+
const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
|
|
9819
|
+
ctx.inlineLeft = ctx.inlineMax;
|
|
9820
|
+
ctx.currentFnName = fnName2;
|
|
9466
9821
|
const failureRuleId = p === ctx.coverage?.entry ? void 0 : ctx.coverage?.plan.rules.get(p) ?? ctx.coverage?.plan.rules.get(resolved);
|
|
9467
9822
|
const savedActiveRule = ctx.activeCoverageRuleId;
|
|
9468
9823
|
const savedSuppressFailure = ctx.suppressCoverageFailure;
|
|
@@ -9477,6 +9832,8 @@ function emitLazy(p, def, ctx, pos) {
|
|
|
9477
9832
|
ctx.indent = savedIndent;
|
|
9478
9833
|
ctx.failLabel = savedFailLabel;
|
|
9479
9834
|
ctx.recordFail = savedRecord;
|
|
9835
|
+
ctx.inlineLeft = savedInlineLeft;
|
|
9836
|
+
ctx.currentFnName = savedFnName;
|
|
9480
9837
|
ctx.activeCoverageRuleId = savedActiveRule;
|
|
9481
9838
|
ctx.suppressCoverageFailure = savedSuppressFailure;
|
|
9482
9839
|
pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar, failureRuleId);
|
|
@@ -9571,6 +9928,9 @@ function emit(p, ctx, pos) {
|
|
|
9571
9928
|
const savedIndent = ctx.indent;
|
|
9572
9929
|
const savedFailLabel = ctx.failLabel;
|
|
9573
9930
|
const savedRecord = ctx.recordFail;
|
|
9931
|
+
const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
|
|
9932
|
+
ctx.inlineLeft = ctx.inlineMax;
|
|
9933
|
+
ctx.currentFnName = fnName;
|
|
9574
9934
|
ctx.indent = 1;
|
|
9575
9935
|
ctx.failLabel = "_pfail";
|
|
9576
9936
|
ctx.recordFail = true;
|
|
@@ -9578,6 +9938,8 @@ function emit(p, ctx, pos) {
|
|
|
9578
9938
|
ctx.indent = savedIndent;
|
|
9579
9939
|
ctx.failLabel = savedFailLabel;
|
|
9580
9940
|
ctx.recordFail = savedRecord;
|
|
9941
|
+
ctx.inlineLeft = savedInlineLeft;
|
|
9942
|
+
ctx.currentFnName = savedFnName;
|
|
9581
9943
|
pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
|
|
9582
9944
|
return instrument(emitNamedFnCall(ctx, fnName, pos));
|
|
9583
9945
|
}
|
|
@@ -9771,7 +10133,7 @@ function emitDispatch(p, ctx, pos) {
|
|
|
9771
10133
|
case "peek":
|
|
9772
10134
|
return emitPeek(def, ctx, pos);
|
|
9773
10135
|
case "routed":
|
|
9774
|
-
return emitRouted(ctx, pos);
|
|
10136
|
+
return emitRouted(ctx, pos, def);
|
|
9775
10137
|
case "node":
|
|
9776
10138
|
return emitNode(def, ctx, pos);
|
|
9777
10139
|
case "scanTo":
|
|
@@ -9806,6 +10168,9 @@ function emitDispatch(p, ctx, pos) {
|
|
|
9806
10168
|
const savedIndent = ctx.indent;
|
|
9807
10169
|
const savedFailLabel = ctx.failLabel;
|
|
9808
10170
|
const savedRecord = ctx.recordFail;
|
|
10171
|
+
const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
|
|
10172
|
+
ctx.inlineLeft = ctx.inlineMax;
|
|
10173
|
+
ctx.currentFnName = fnName;
|
|
9809
10174
|
ctx.indent = 1;
|
|
9810
10175
|
ctx.failLabel = "_pfail";
|
|
9811
10176
|
ctx.recordFail = true;
|
|
@@ -9813,6 +10178,8 @@ function emitDispatch(p, ctx, pos) {
|
|
|
9813
10178
|
ctx.indent = savedIndent;
|
|
9814
10179
|
ctx.failLabel = savedFailLabel;
|
|
9815
10180
|
ctx.recordFail = savedRecord;
|
|
10181
|
+
ctx.inlineLeft = savedInlineLeft;
|
|
10182
|
+
ctx.currentFnName = savedFnName;
|
|
9816
10183
|
pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
|
|
9817
10184
|
}
|
|
9818
10185
|
const fn = ctx.namedParsers.get(innerParser);
|
|
@@ -9971,12 +10338,16 @@ function childrenOf3(def) {
|
|
|
9971
10338
|
return [def.parser, def.sentinel];
|
|
9972
10339
|
case "scanTo":
|
|
9973
10340
|
return [def.sentinel, ...def.skip];
|
|
10341
|
+
// A `routed()` fallback IS emitted at this site (emitRouted), so the usage
|
|
10342
|
+
// analysis must see the edge — otherwise a fallback shared by several routed()
|
|
10343
|
+
// sites is miscounted as single-use and inlined at each.
|
|
10344
|
+
case "routed":
|
|
10345
|
+
return def.fallback ? [def.fallback] : [];
|
|
9974
10346
|
case "lazy":
|
|
9975
10347
|
case "literal":
|
|
9976
10348
|
case "regex":
|
|
9977
10349
|
case "keywords":
|
|
9978
10350
|
case "guard":
|
|
9979
|
-
case "routed":
|
|
9980
10351
|
case "unknown":
|
|
9981
10352
|
return [];
|
|
9982
10353
|
}
|
|
@@ -9997,6 +10368,17 @@ function isHoistableTag(tag) {
|
|
|
9997
10368
|
return HOISTABLE_TAGS.has(tag);
|
|
9998
10369
|
}
|
|
9999
10370
|
var HOIST_MIN_SUBTREE = 3;
|
|
10371
|
+
var INLINE_MAX_NODES = 1e3;
|
|
10372
|
+
function resolveInlineMax(explicit) {
|
|
10373
|
+
if (explicit !== void 0) return explicit;
|
|
10374
|
+
const env = typeof process !== "undefined" ? process.env?.PARSEMAN_MAX_INLINE : void 0;
|
|
10375
|
+
if (env !== void 0 && env !== "") {
|
|
10376
|
+
if (env === "off" || env === "infinity") return Infinity;
|
|
10377
|
+
const n = Number(env);
|
|
10378
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
10379
|
+
}
|
|
10380
|
+
return INLINE_MAX_NODES;
|
|
10381
|
+
}
|
|
10000
10382
|
function analyzeLazyUsage(root) {
|
|
10001
10383
|
return analyzeLazyUsageMulti([root]);
|
|
10002
10384
|
}
|
|
@@ -10102,102 +10484,6 @@ function ruleDependencies(ruleMap) {
|
|
|
10102
10484
|
}
|
|
10103
10485
|
return deps;
|
|
10104
10486
|
}
|
|
10105
|
-
function resolveGatingLevel(opt) {
|
|
10106
|
-
const explicit = typeof opt === "string" ? opt : opt?.level;
|
|
10107
|
-
if (explicit !== void 0) return explicit;
|
|
10108
|
-
const env = typeof process !== "undefined" ? process.env?.PARSEMAN_GATING : void 0;
|
|
10109
|
-
if (env === "off" || env === "warn" || env === "error") return env;
|
|
10110
|
-
return "warn";
|
|
10111
|
-
}
|
|
10112
|
-
function runGatingDiagnostic(combinator, opt) {
|
|
10113
|
-
return reportGating(opt, (o) => analyzeGating(combinator, o));
|
|
10114
|
-
}
|
|
10115
|
-
function runGatingDiagnosticRules(ruleMap, opt) {
|
|
10116
|
-
return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
|
|
10117
|
-
}
|
|
10118
|
-
function runFusedGatingDiagnostic(getRuleMaps, opt, getOpaque) {
|
|
10119
|
-
if (resolveGatingLevel(opt) === "off") return void 0;
|
|
10120
|
-
const ruleMaps = getRuleMaps();
|
|
10121
|
-
const opaque = getOpaque?.() ?? [];
|
|
10122
|
-
const opaqueFindings = opaque.map((o) => ({
|
|
10123
|
-
rule: o.ruleNames.length > 0 ? o.ruleNames.join(", ") : `<artifact ${o.ns}>`,
|
|
10124
|
-
kind: "opaque-artifact",
|
|
10125
|
-
reason: `precompiled artifact "${o.ns}" carries compiled rule functions, not re-lowerable IR \u2014 its choices were not examined at this fuse.`
|
|
10126
|
-
}));
|
|
10127
|
-
if (!ruleMaps.some((map) => hasExternalRuleRef(map))) {
|
|
10128
|
-
if (opaqueFindings.length === 0) return void 0;
|
|
10129
|
-
return reportGating(opt, () => ({
|
|
10130
|
-
totalChoices: 0,
|
|
10131
|
-
gated: 0,
|
|
10132
|
-
recoverable: 0,
|
|
10133
|
-
unanalysable: opaqueFindings,
|
|
10134
|
-
ungated: [],
|
|
10135
|
-
accepted: [],
|
|
10136
|
-
deferred: [],
|
|
10137
|
-
acceptedUnused: [],
|
|
10138
|
-
choices: [],
|
|
10139
|
-
antiPatterns: []
|
|
10140
|
-
}));
|
|
10141
|
-
}
|
|
10142
|
-
const winners = /* @__PURE__ */ new Map();
|
|
10143
|
-
for (const map of ruleMaps) for (const [name, rule] of map) {
|
|
10144
|
-
if (rule._def.tag === "lazy") {
|
|
10145
|
-
try {
|
|
10146
|
-
rule._def.thunk();
|
|
10147
|
-
} catch {
|
|
10148
|
-
continue;
|
|
10149
|
-
}
|
|
10150
|
-
}
|
|
10151
|
-
winners.set(name, rule);
|
|
10152
|
-
}
|
|
10153
|
-
const entries = [...winners];
|
|
10154
|
-
return reportGating(opt, (o) => {
|
|
10155
|
-
const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
|
|
10156
|
-
const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
|
|
10157
|
-
return {
|
|
10158
|
-
...fused,
|
|
10159
|
-
ungated: fused.ungated.filter((c) => deferredHere.has(c.id)),
|
|
10160
|
-
antiPatterns: [],
|
|
10161
|
-
unanalysable: [...fused.unanalysable, ...opaqueFindings]
|
|
10162
|
-
};
|
|
10163
|
-
});
|
|
10164
|
-
}
|
|
10165
|
-
function reportGating(opt, analyze) {
|
|
10166
|
-
const level = resolveGatingLevel(opt);
|
|
10167
|
-
if (level === "off") return void 0;
|
|
10168
|
-
const obj = opt !== null && typeof opt === "object" ? opt : void 0;
|
|
10169
|
-
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;
|
|
10170
|
-
let report;
|
|
10171
|
-
try {
|
|
10172
|
-
report = analyze(analyzeOpts);
|
|
10173
|
-
} catch (e) {
|
|
10174
|
-
const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
|
|
10175
|
-
const failed = {
|
|
10176
|
-
totalChoices: 0,
|
|
10177
|
-
gated: 0,
|
|
10178
|
-
recoverable: 0,
|
|
10179
|
-
unanalysable: [{ rule: "<whole grammar>", kind: "not-a-combinator", reason: `gating analysis threw \u2014 ${reason}` }],
|
|
10180
|
-
ungated: [],
|
|
10181
|
-
accepted: [],
|
|
10182
|
-
deferred: [],
|
|
10183
|
-
acceptedUnused: [],
|
|
10184
|
-
choices: [],
|
|
10185
|
-
antiPatterns: []
|
|
10186
|
-
};
|
|
10187
|
-
const failLines = formatGatingWarnings(failed);
|
|
10188
|
-
if (level === "error") throw new Error(`parseman: gating analysis FAILED (no verdict available)
|
|
10189
|
-
${failLines.join("\n")}`);
|
|
10190
|
-
for (const l of failLines) console.warn(l);
|
|
10191
|
-
return failed;
|
|
10192
|
-
}
|
|
10193
|
-
const lines = formatGatingWarnings(report);
|
|
10194
|
-
if (lines.length > 0) {
|
|
10195
|
-
if (level === "error") throw new Error(`parseman: ${report.ungated.length} ungated hot choice(s) / ${report.antiPatterns.length} anti-pattern(s) / ${report.unanalysable.length} unanalysable rule(s)
|
|
10196
|
-
${lines.join("\n")}`);
|
|
10197
|
-
for (const l of lines) console.warn(l);
|
|
10198
|
-
}
|
|
10199
|
-
return report;
|
|
10200
|
-
}
|
|
10201
10487
|
function resolveDuplicationLevel(opt) {
|
|
10202
10488
|
const explicit = typeof opt === "string" ? opt : opt?.level;
|
|
10203
10489
|
if (explicit !== void 0) return explicit;
|
|
@@ -10240,7 +10526,17 @@ function runDuplicationDiagnosticRules(ruleMap, opt) {
|
|
|
10240
10526
|
return reportDuplication(opt, (o) => analyzeDuplicationRules(ruleMap, o));
|
|
10241
10527
|
}
|
|
10242
10528
|
function compile(combinator, mapFnSources, opts) {
|
|
10243
|
-
const
|
|
10529
|
+
const drain = beginCompileDegradationDrain();
|
|
10530
|
+
let completed = false;
|
|
10531
|
+
try {
|
|
10532
|
+
const compiled = compileImpl(combinator, mapFnSources, opts);
|
|
10533
|
+
completed = true;
|
|
10534
|
+
return compiled;
|
|
10535
|
+
} finally {
|
|
10536
|
+
drain(completed);
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
function compileImpl(combinator, mapFnSources, opts) {
|
|
10244
10540
|
runDuplicationDiagnostic(combinator, opts?.duplication);
|
|
10245
10541
|
markUnusedValues(combinator);
|
|
10246
10542
|
const grammarTrivia = combinator._meta.grammarTrivia;
|
|
@@ -10248,9 +10544,12 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
10248
10544
|
const grammarScanSkip = combinator._meta.grammarScanSkip;
|
|
10249
10545
|
const grammarHostMode = combinator._meta.grammarHostMode;
|
|
10250
10546
|
const grammarTrackLines = combinator._meta.grammarTrackLines;
|
|
10547
|
+
const _inlineMax = resolveInlineMax(opts?.maxInline);
|
|
10251
10548
|
const ctx = {
|
|
10252
10549
|
vars: 0,
|
|
10253
10550
|
indent: 1,
|
|
10551
|
+
inlineMax: _inlineMax,
|
|
10552
|
+
inlineLeft: _inlineMax,
|
|
10254
10553
|
regexDecls: [],
|
|
10255
10554
|
regexMap: /* @__PURE__ */ new Map(),
|
|
10256
10555
|
expectedDecls: [],
|
|
@@ -10296,11 +10595,13 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
10296
10595
|
const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
|
|
10297
10596
|
const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
|
|
10298
10597
|
const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
|
|
10598
|
+
const rawEntryDecls = ctx.needsRawEntry ? [RAW_ENTRY_DECL] : [];
|
|
10299
10599
|
const lineTrackDecls = ctx.needsLineTrack ? [LINE_TRACK_DECL] : [];
|
|
10300
10600
|
const lineSpanDecls = ctx.needsLineSpan ? [LINE_SPAN_DECL] : [];
|
|
10301
10601
|
const source = [
|
|
10302
10602
|
...emptyTlDecls,
|
|
10303
10603
|
...hostReadsDecls,
|
|
10604
|
+
...rawEntryDecls,
|
|
10304
10605
|
...lineTrackDecls,
|
|
10305
10606
|
...lineSpanDecls,
|
|
10306
10607
|
...ctx.regexDecls,
|
|
@@ -10315,6 +10616,7 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
10315
10616
|
const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
|
|
10316
10617
|
...emptyTlDecls,
|
|
10317
10618
|
...hostReadsDecls,
|
|
10619
|
+
...rawEntryDecls,
|
|
10318
10620
|
...lineTrackDecls,
|
|
10319
10621
|
...lineSpanDecls,
|
|
10320
10622
|
...ctx.regexDecls,
|
|
@@ -10364,8 +10666,7 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
10364
10666
|
const compiledMeta = {
|
|
10365
10667
|
source,
|
|
10366
10668
|
inlineExpression,
|
|
10367
|
-
...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
|
|
10368
|
-
...gatingReport === void 0 ? {} : { gating: gatingReport }
|
|
10669
|
+
...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
|
|
10369
10670
|
};
|
|
10370
10671
|
if (!ctx.lineTracking) {
|
|
10371
10672
|
return {
|
|
@@ -10431,7 +10732,6 @@ function publicRuleWrapperSource(rule, fnSource, ambientTriviaKindLabels, ambien
|
|
|
10431
10732
|
}
|
|
10432
10733
|
function compileLinkable(ruleMapArg, ns, opts) {
|
|
10433
10734
|
if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
|
|
10434
|
-
if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
|
|
10435
10735
|
if (opts?.duplication !== void 0) runDuplicationDiagnosticRules(ruleMapArg, opts.duplication);
|
|
10436
10736
|
for (const [, rule] of ruleMapArg) markUnusedValues(rule);
|
|
10437
10737
|
const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
|
|
@@ -10450,9 +10750,12 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
10450
10750
|
}
|
|
10451
10751
|
});
|
|
10452
10752
|
const nodeMeta = new Map(ruleMap.map(([name, rule]) => [name, collectGrammarReflection([[name, rule]], { followLazy: false })]));
|
|
10753
|
+
const _inlineMax = resolveInlineMax(opts?.maxInline);
|
|
10453
10754
|
const ctx = {
|
|
10454
10755
|
vars: 0,
|
|
10455
10756
|
indent: 1,
|
|
10757
|
+
inlineMax: _inlineMax,
|
|
10758
|
+
inlineLeft: _inlineMax,
|
|
10456
10759
|
regexDecls: [],
|
|
10457
10760
|
regexMap: /* @__PURE__ */ new Map(),
|
|
10458
10761
|
expectedDecls: [],
|
|
@@ -10548,6 +10851,9 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
10548
10851
|
}
|
|
10549
10852
|
}
|
|
10550
10853
|
const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
|
|
10854
|
+
const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
|
|
10855
|
+
ctx.inlineLeft = ctx.inlineMax;
|
|
10856
|
+
ctx.currentFnName = fn;
|
|
10551
10857
|
ctx.indent = 1;
|
|
10552
10858
|
ctx.failLabel = "_pfail";
|
|
10553
10859
|
ctx.recordFail = true;
|
|
@@ -10563,6 +10869,8 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
10563
10869
|
ctx.indent = savedIndent;
|
|
10564
10870
|
ctx.failLabel = savedFail;
|
|
10565
10871
|
ctx.recordFail = savedRec;
|
|
10872
|
+
ctx.inlineLeft = savedInlineLeft;
|
|
10873
|
+
ctx.currentFnName = savedFnName;
|
|
10566
10874
|
const ruleId = ctx.coverage?.plan.rules.get(rule);
|
|
10567
10875
|
const stmts = ruleId === void 0 ? body.stmts : [
|
|
10568
10876
|
`${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(ruleId)})`,
|
|
@@ -10647,6 +10955,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
10647
10955
|
deps: ruleDependencies(ruleMap),
|
|
10648
10956
|
needsEmptyTl: !!ctx.needsEmptyTl,
|
|
10649
10957
|
needsHostReads: !!ctx.needsHostReads,
|
|
10958
|
+
needsRawEntry: !!ctx.needsRawEntry,
|
|
10650
10959
|
hostMode: ctx.hostMode ?? "ast",
|
|
10651
10960
|
hostBranchElided: !!ctx.hostBranchElided,
|
|
10652
10961
|
hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
|
|
@@ -10656,26 +10965,6 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
10656
10965
|
buildFns: buildSrcs ? [] : ctx.buildFns
|
|
10657
10966
|
};
|
|
10658
10967
|
}
|
|
10659
|
-
function hasExternalRuleRef(ruleMap) {
|
|
10660
|
-
const seen = /* @__PURE__ */ new Set();
|
|
10661
|
-
const visit = (p) => {
|
|
10662
|
-
if (seen.has(p)) return false;
|
|
10663
|
-
seen.add(p);
|
|
10664
|
-
const def = p._def;
|
|
10665
|
-
if (def.tag === "lazy") {
|
|
10666
|
-
let resolved;
|
|
10667
|
-
try {
|
|
10668
|
-
resolved = def.thunk();
|
|
10669
|
-
} catch {
|
|
10670
|
-
resolved = void 0;
|
|
10671
|
-
}
|
|
10672
|
-
if (resolved === void 0) return p._ruleName !== void 0;
|
|
10673
|
-
return visit(resolved);
|
|
10674
|
-
}
|
|
10675
|
-
return childrenOf3(def).some(visit);
|
|
10676
|
-
};
|
|
10677
|
-
return ruleMap.some(([, rule]) => visit(rule));
|
|
10678
|
-
}
|
|
10679
10968
|
function hasSemanticReduction(roots, externalRefs) {
|
|
10680
10969
|
const seen = /* @__PURE__ */ new Set();
|
|
10681
10970
|
const visit = (parser2) => {
|
|
@@ -10721,14 +11010,16 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootR
|
|
|
10721
11010
|
const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
|
|
10722
11011
|
const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
|
|
10723
11012
|
const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
|
|
11013
|
+
const rawEntryDecl = ctx.needsRawEntry ? ` ${RAW_ENTRY_DECL}` : "";
|
|
10724
11014
|
const lineTrackDecl = ctx.needsLineTrack ? ` ${LINE_TRACK_DECL}` : "";
|
|
10725
11015
|
const lineSpanDecl = ctx.needsLineSpan ? ` ${LINE_SPAN_DECL}` : "";
|
|
10726
|
-
const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!lineTrackDecl || !!lineSpanDecl;
|
|
11016
|
+
const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!rawEntryDecl || !!lineTrackDecl || !!lineSpanDecl;
|
|
10727
11017
|
if (!needsWrapper) return innerFn;
|
|
10728
11018
|
const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
|
|
10729
11019
|
const hoistedDecls = [
|
|
10730
11020
|
emptyTlDecl,
|
|
10731
11021
|
hostReadsDecl,
|
|
11022
|
+
rawEntryDecl,
|
|
10732
11023
|
lineTrackDecl,
|
|
10733
11024
|
lineSpanDecl,
|
|
10734
11025
|
...ctx.regexDecls.map((d) => ` ${d}`),
|
|
@@ -10798,11 +11089,12 @@ function childrenOf4(def) {
|
|
|
10798
11089
|
return [def.main, def.skipped];
|
|
10799
11090
|
case "scanTo":
|
|
10800
11091
|
return [def.sentinel, ...def.skip];
|
|
11092
|
+
case "routed":
|
|
11093
|
+
return def.fallback ? [def.fallback] : [];
|
|
10801
11094
|
case "lazy":
|
|
10802
11095
|
case "literal":
|
|
10803
11096
|
case "regex":
|
|
10804
11097
|
case "keywords":
|
|
10805
|
-
case "routed":
|
|
10806
11098
|
case "guard":
|
|
10807
11099
|
case "recover":
|
|
10808
11100
|
case "unknown":
|
|
@@ -10884,7 +11176,7 @@ function evalRuleMapIR(ir) {
|
|
|
10884
11176
|
l._def.fnSrc = src;
|
|
10885
11177
|
return l;
|
|
10886
11178
|
};
|
|
10887
|
-
const _nd = (type, child, src, opts, staticError) => {
|
|
11179
|
+
const _nd = (type, child, src, opts, staticError, sigSrc) => {
|
|
10888
11180
|
if (staticError !== void 0 && staticError.length > 0) {
|
|
10889
11181
|
throw new Error(`IR direct node builder for ${type} must be macro-static and self-contained; unsupported binding(s): ${staticError.join(", ")}`);
|
|
10890
11182
|
}
|
|
@@ -10893,6 +11185,7 @@ function evalRuleMapIR(ir) {
|
|
|
10893
11185
|
}), opts);
|
|
10894
11186
|
n._def.buildSrc = src;
|
|
10895
11187
|
if (staticError !== void 0) n._def.buildStaticError = staticError;
|
|
11188
|
+
if (sigSrc !== void 0) n._def.buildSigSrc = sigSrc;
|
|
10896
11189
|
return n;
|
|
10897
11190
|
};
|
|
10898
11191
|
const _gch = (items) => {
|
|
@@ -11205,7 +11498,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
|
|
|
11205
11498
|
case "peek":
|
|
11206
11499
|
return `peek(${kid(def.parser)})`;
|
|
11207
11500
|
case "routed":
|
|
11208
|
-
return "routed()"
|
|
11501
|
+
return def.fallback === void 0 ? "routed()" : `routed(${kid(def.fallback)})`;
|
|
11209
11502
|
case "trivia":
|
|
11210
11503
|
return `trivia(${kid(def.parser)})`;
|
|
11211
11504
|
case "token":
|
|
@@ -11238,6 +11531,9 @@ ${outerDecls.map((d) => " " + d).join("\n")}
|
|
|
11238
11531
|
...projectEntry === void 0 ? [] : [projectEntry],
|
|
11239
11532
|
...def.captureTrivia ? ["captureTrivia: true"] : [],
|
|
11240
11533
|
...def.trailingTrivia ? ["trailingTrivia: true"] : [],
|
|
11534
|
+
// Declared or resolved arity. Carried so a re-lowered composed artifact does
|
|
11535
|
+
// not silently re-acquire the fail-open capture cost this module resolved away.
|
|
11536
|
+
...def.buildArity === void 0 ? [] : [`buildArity: ${def.buildArity}`],
|
|
11241
11537
|
...tagEntry === void 0 ? [] : [tagEntry]
|
|
11242
11538
|
];
|
|
11243
11539
|
const opts = optEntries.length > 0 ? `, { ${optEntries.join(", ")} }` : "";
|
|
@@ -11246,8 +11542,14 @@ ${outerDecls.map((d) => " " + d).join("\n")}
|
|
|
11246
11542
|
return opts ? `node(${kid(def.parser)}, undefined${opts})` : `node(${kid(def.parser)})`;
|
|
11247
11543
|
}
|
|
11248
11544
|
if (def.buildSrc !== void 0) {
|
|
11249
|
-
const
|
|
11250
|
-
|
|
11545
|
+
const tail = [];
|
|
11546
|
+
if (def.buildStaticError !== void 0 || def.buildSigSrc !== void 0) {
|
|
11547
|
+
if (!opts) tail.push("undefined");
|
|
11548
|
+
tail.push(def.buildStaticError === void 0 ? "undefined" : JSON.stringify(def.buildStaticError));
|
|
11549
|
+
}
|
|
11550
|
+
if (def.buildSigSrc !== void 0) tail.push(JSON.stringify(def.buildSigSrc));
|
|
11551
|
+
const trailing = tail.length > 0 ? `, ${tail.join(", ")}` : "";
|
|
11552
|
+
return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts}${trailing})`;
|
|
11251
11553
|
}
|
|
11252
11554
|
return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
|
|
11253
11555
|
}
|
|
@@ -11304,7 +11606,7 @@ function cstBuildHost(typeOrOptions, children2, _fields, span, rawChildren, triv
|
|
|
11304
11606
|
return host;
|
|
11305
11607
|
}
|
|
11306
11608
|
cstBuildHost._parsemanCstOutput = true;
|
|
11307
|
-
function fusedBody(pieces) {
|
|
11609
|
+
function fusedBody(pieces, hoist) {
|
|
11308
11610
|
for (const p of pieces) {
|
|
11309
11611
|
if (p.v === void 0) {
|
|
11310
11612
|
throw new Error(
|
|
@@ -11328,12 +11630,14 @@ function fusedBody(pieces) {
|
|
|
11328
11630
|
const contributingPieces = [...contributing];
|
|
11329
11631
|
const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
|
|
11330
11632
|
const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
|
|
11633
|
+
const needsRawEntry = contributingPieces.some((p) => p.needsRawEntry);
|
|
11331
11634
|
const lines = [
|
|
11332
11635
|
// Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
|
|
11333
11636
|
"const _pfFail = {}",
|
|
11334
11637
|
"let _pfEnd",
|
|
11335
11638
|
...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
|
|
11336
11639
|
...needsHostReads ? [HOST_READS_DECL] : [],
|
|
11640
|
+
...needsRawEntry ? [RAW_ENTRY_DECL] : [],
|
|
11337
11641
|
// Each contributing artifact's namespaced private prelude (regexes, _pf, …).
|
|
11338
11642
|
...new Set(contributingPieces.flatMap((p) => p.prelude)),
|
|
11339
11643
|
// The winning `_r_<Name>` function for each rule (one per name → no redeclare).
|
|
@@ -11342,8 +11646,7 @@ function fusedBody(pieces) {
|
|
|
11342
11646
|
const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
|
|
11343
11647
|
const reflection = mergeGrammarReflections([...winner].map(([k, p]) => p.nodeMeta.get(k) ?? { nodes: [] }));
|
|
11344
11648
|
const { mode, elided } = hostModeOfPieces(pieces);
|
|
11345
|
-
const
|
|
11346
|
-
...lines,
|
|
11649
|
+
const tailSrc = [
|
|
11347
11650
|
"const _map = {",
|
|
11348
11651
|
wrapperEntries.join(",\n"),
|
|
11349
11652
|
"}",
|
|
@@ -11359,6 +11662,7 @@ function fusedBody(pieces) {
|
|
|
11359
11662
|
"}",
|
|
11360
11663
|
"return _map"
|
|
11361
11664
|
].join("\n");
|
|
11665
|
+
const rawBody = [...lines, tailSrc].join("\n");
|
|
11362
11666
|
const ANY2 = { kind: "any" };
|
|
11363
11667
|
const EMPTY3 = { kind: "empty" };
|
|
11364
11668
|
const recipes = /* @__PURE__ */ new Map();
|
|
@@ -11399,7 +11703,7 @@ function fusedBody(pieces) {
|
|
|
11399
11703
|
}
|
|
11400
11704
|
if (!changed) break;
|
|
11401
11705
|
}
|
|
11402
|
-
const
|
|
11706
|
+
const resolveFS = (src) => src.replace(
|
|
11403
11707
|
// Rule name + code-point var are both JS identifiers (rule names are validated
|
|
11404
11708
|
// at compile time — see assertRuleName in codegen), so an identifier class
|
|
11405
11709
|
// matches every well-formed placeholder.
|
|
@@ -11410,6 +11714,7 @@ function fusedBody(pieces) {
|
|
|
11410
11714
|
return `(${firstSetCond(codevar, fs)})`;
|
|
11411
11715
|
}
|
|
11412
11716
|
);
|
|
11717
|
+
const body = hoist === void 0 ? resolveFS(rawBody) : [...hoist.claim(lines.map(resolveFS)), resolveFS(tailSrc)].join("\n");
|
|
11413
11718
|
const env = {};
|
|
11414
11719
|
for (const p of contributingPieces) {
|
|
11415
11720
|
if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
|
|
@@ -11449,17 +11754,6 @@ var COMPOSED_COVERAGE_RULES = /* @__PURE__ */ Symbol.for("parseman.composedCover
|
|
|
11449
11754
|
function isIRPiece(p) {
|
|
11450
11755
|
return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
|
|
11451
11756
|
}
|
|
11452
|
-
function once(fn) {
|
|
11453
|
-
let done = false;
|
|
11454
|
-
let value;
|
|
11455
|
-
return () => {
|
|
11456
|
-
if (!done) {
|
|
11457
|
-
value = fn();
|
|
11458
|
-
done = true;
|
|
11459
|
-
}
|
|
11460
|
-
return value;
|
|
11461
|
-
};
|
|
11462
|
-
}
|
|
11463
11757
|
function carriedRuleMapsDetailed(carried) {
|
|
11464
11758
|
const maps = [];
|
|
11465
11759
|
const opaque = [];
|
|
@@ -11576,12 +11870,6 @@ function compose(items, opts) {
|
|
|
11576
11870
|
const used = /* @__PURE__ */ new Set();
|
|
11577
11871
|
const trivia2 = composingTriviaOf(items);
|
|
11578
11872
|
const carried = items.flatMap((item) => itemCarried(item, used, trivia2, opts?.hostMode));
|
|
11579
|
-
const detailed = once(() => carriedRuleMapsDetailed(carried));
|
|
11580
|
-
runFusedGatingDiagnostic(
|
|
11581
|
-
() => detailed().maps,
|
|
11582
|
-
void 0,
|
|
11583
|
-
() => detailed().opaque
|
|
11584
|
-
);
|
|
11585
11873
|
const pieces = carried.map((p) => materializePiece(p, trivia2, false, opts?.hostMode));
|
|
11586
11874
|
const map = fuseRules(pieces);
|
|
11587
11875
|
Object.defineProperty(map, COMPOSED_PIECES, { value: carried, enumerable: false });
|
|
@@ -11590,8 +11878,168 @@ function compose(items, opts) {
|
|
|
11590
11878
|
if (coverageRules) Object.defineProperty(map, COMPOSED_COVERAGE_RULES, { value: coverageRules, enumerable: false });
|
|
11591
11879
|
return map;
|
|
11592
11880
|
}
|
|
11593
|
-
function composeLeaf(
|
|
11594
|
-
|
|
11881
|
+
function composeLeaf(items) {
|
|
11882
|
+
const pieces = items.flatMap(interpretedPieces);
|
|
11883
|
+
let fused;
|
|
11884
|
+
const map = {};
|
|
11885
|
+
for (const name of ruleNamesOf(pieces)) {
|
|
11886
|
+
Object.defineProperty(map, name, {
|
|
11887
|
+
enumerable: true,
|
|
11888
|
+
configurable: true,
|
|
11889
|
+
get: () => (fused ??= fusePieces(pieces))[name]
|
|
11890
|
+
});
|
|
11891
|
+
}
|
|
11892
|
+
Object.defineProperty(map, LEAF_COMPOSED, { value: true, enumerable: false });
|
|
11893
|
+
Object.defineProperty(map, INTERPRETED_PIECES, {
|
|
11894
|
+
value: pieces.filter((p) => p.plain).map((p) => p.entries),
|
|
11895
|
+
enumerable: false
|
|
11896
|
+
});
|
|
11897
|
+
return map;
|
|
11898
|
+
}
|
|
11899
|
+
function isRefSlot(c) {
|
|
11900
|
+
return c._def.tag === "lazy" && typeof c.define === "function";
|
|
11901
|
+
}
|
|
11902
|
+
var FUSE_ORIGINAL = /* @__PURE__ */ Symbol.for("parseman.interpretedFuseOriginal");
|
|
11903
|
+
var FUSE_TARGET = /* @__PURE__ */ Symbol.for("parseman.interpretedFuseTarget");
|
|
11904
|
+
var INTERPRETED_PIECES = /* @__PURE__ */ Symbol.for("parseman.interpretedPieces");
|
|
11905
|
+
function isInterpretedFuse(map) {
|
|
11906
|
+
return Array.isArray(map[INTERPRETED_PIECES]);
|
|
11907
|
+
}
|
|
11908
|
+
function definitionOf(entry) {
|
|
11909
|
+
if (isRefSlot(entry)) {
|
|
11910
|
+
const original = entry[FUSE_ORIGINAL];
|
|
11911
|
+
if (original !== void 0) return original ?? void 0;
|
|
11912
|
+
try {
|
|
11913
|
+
return entry._def.thunk();
|
|
11914
|
+
} catch {
|
|
11915
|
+
return void 0;
|
|
11916
|
+
}
|
|
11917
|
+
}
|
|
11918
|
+
if (entry._def.tag === "lazy") {
|
|
11919
|
+
try {
|
|
11920
|
+
entry._def.thunk();
|
|
11921
|
+
} catch {
|
|
11922
|
+
return void 0;
|
|
11923
|
+
}
|
|
11924
|
+
}
|
|
11925
|
+
return entry;
|
|
11926
|
+
}
|
|
11927
|
+
function repointRef(slot, target, name) {
|
|
11928
|
+
const tagged = slot;
|
|
11929
|
+
const bound = tagged[FUSE_TARGET];
|
|
11930
|
+
const original = FUSE_ORIGINAL in tagged ? tagged[FUSE_ORIGINAL] : (() => {
|
|
11931
|
+
try {
|
|
11932
|
+
return slot._def.thunk();
|
|
11933
|
+
} catch {
|
|
11934
|
+
return null;
|
|
11935
|
+
}
|
|
11936
|
+
})();
|
|
11937
|
+
if ((bound ?? original) === target) return;
|
|
11938
|
+
if (bound !== void 0) {
|
|
11939
|
+
throw new Error(
|
|
11940
|
+
`fuseInterpreted: rule "${name}" is already bound by a DIFFERENT interpreted fusion of the same grammar piece. An interpreted fuse binds the shared placeholder objects in place, so two fusions cannot share a piece \u2014 build a fresh instance of the piece (call its rules() factory again, or import the module under a distinct specifier) for the second fusion.`
|
|
11941
|
+
);
|
|
11942
|
+
}
|
|
11943
|
+
if (!(FUSE_ORIGINAL in tagged)) Object.defineProperty(slot, FUSE_ORIGINAL, { value: original, enumerable: false });
|
|
11944
|
+
Object.defineProperty(slot, FUSE_TARGET, { value: target, enumerable: false });
|
|
11945
|
+
slot._def.thunk = () => target;
|
|
11946
|
+
slot.parse = (input, pos, ctx) => target.parse(input, pos, ctx);
|
|
11947
|
+
const meta = slot._meta;
|
|
11948
|
+
meta.firstSet = target._meta.firstSet;
|
|
11949
|
+
meta.canMatchNewline = target._meta.canMatchNewline;
|
|
11950
|
+
meta.isTrivia = target._meta.isTrivia;
|
|
11951
|
+
if (target._meta.triviaKindLabels !== void 0) meta.triviaKindLabels = target._meta.triviaKindLabels;
|
|
11952
|
+
else delete meta.triviaKindLabels;
|
|
11953
|
+
if (target._meta.disjoint !== void 0) meta.disjoint = target._meta.disjoint;
|
|
11954
|
+
else delete meta.disjoint;
|
|
11955
|
+
}
|
|
11956
|
+
function interpretedPieces(item) {
|
|
11957
|
+
const fused = item[INTERPRETED_PIECES];
|
|
11958
|
+
if (Array.isArray(fused)) return fused.map((entries) => ({ entries, plain: false }));
|
|
11959
|
+
const carried = composedPiecesOf(item);
|
|
11960
|
+
if (carried !== void 0) {
|
|
11961
|
+
const { maps, opaque } = carriedRuleMapsDetailed(carried);
|
|
11962
|
+
if (opaque.length > 0) {
|
|
11963
|
+
throw new Error(
|
|
11964
|
+
`fuseInterpreted: cannot interpret a composed grammar containing precompiled artifact(s) ${opaque.map((o) => `"${o.ns}" (${o.ruleNames.length} rules)`).join(", ")} \u2014 they carry compiled functions, not a combinator graph. Pass the source grammars (the same items you passed to compose()) instead.`
|
|
11965
|
+
);
|
|
11966
|
+
}
|
|
11967
|
+
return maps.map((entries) => ({ entries, plain: false }));
|
|
11968
|
+
}
|
|
11969
|
+
if (item.ruleFns instanceof Map) {
|
|
11970
|
+
throw new Error("fuseInterpreted: a precompiled linkable artifact has no combinator graph to interpret; pass the source grammar (a rules() map) instead");
|
|
11971
|
+
}
|
|
11972
|
+
return [{ entries: Object.entries(item), plain: true }];
|
|
11973
|
+
}
|
|
11974
|
+
function fuseInterpreted(items, opts) {
|
|
11975
|
+
return fusePieces(items.flatMap(interpretedPieces), opts);
|
|
11976
|
+
}
|
|
11977
|
+
function ruleNamesOf(pieces) {
|
|
11978
|
+
const names = /* @__PURE__ */ new Set();
|
|
11979
|
+
for (const piece of pieces) {
|
|
11980
|
+
for (const [name, value] of piece.entries) if (definitionOf(value) !== void 0) names.add(name);
|
|
11981
|
+
}
|
|
11982
|
+
return [...names];
|
|
11983
|
+
}
|
|
11984
|
+
function fusePieces(pieces, opts) {
|
|
11985
|
+
let trivia2;
|
|
11986
|
+
for (let i = pieces.length - 1; i >= 0 && trivia2 === void 0; i--) {
|
|
11987
|
+
if (!pieces[i].plain) continue;
|
|
11988
|
+
for (const [, rule] of pieces[i].entries) {
|
|
11989
|
+
const t = rule._meta.grammarTrivia;
|
|
11990
|
+
if (t) {
|
|
11991
|
+
trivia2 = t;
|
|
11992
|
+
break;
|
|
11993
|
+
}
|
|
11994
|
+
}
|
|
11995
|
+
}
|
|
11996
|
+
const winner = /* @__PURE__ */ new Map();
|
|
11997
|
+
const entry = /* @__PURE__ */ new Map();
|
|
11998
|
+
for (const piece of pieces) {
|
|
11999
|
+
for (const [name, value] of piece.entries) {
|
|
12000
|
+
const def = definitionOf(value);
|
|
12001
|
+
if (def === void 0) continue;
|
|
12002
|
+
winner.set(name, def);
|
|
12003
|
+
entry.set(name, value);
|
|
12004
|
+
}
|
|
12005
|
+
}
|
|
12006
|
+
const missing = /* @__PURE__ */ new Set();
|
|
12007
|
+
for (const piece of pieces) {
|
|
12008
|
+
for (const [name, value] of piece.entries) {
|
|
12009
|
+
if (!isRefSlot(value)) continue;
|
|
12010
|
+
const target = winner.get(name);
|
|
12011
|
+
if (target === void 0) {
|
|
12012
|
+
missing.add(name);
|
|
12013
|
+
continue;
|
|
12014
|
+
}
|
|
12015
|
+
repointRef(value, target, name);
|
|
12016
|
+
}
|
|
12017
|
+
}
|
|
12018
|
+
if (missing.size > 0) throw new Error(missingRuleMessage(pieces, missing));
|
|
12019
|
+
const out = {};
|
|
12020
|
+
for (const [name, value] of entry) {
|
|
12021
|
+
out[name] = value;
|
|
12022
|
+
const meta = value._meta;
|
|
12023
|
+
if (trivia2 !== void 0 && !meta.isTrivia) meta.grammarTrivia = trivia2;
|
|
12024
|
+
if (opts?.hostMode !== void 0 && !meta.isTrivia) {
|
|
12025
|
+
if (opts.hostMode === "cst") meta.grammarHostMode = "cst";
|
|
12026
|
+
else delete meta.grammarHostMode;
|
|
12027
|
+
}
|
|
12028
|
+
}
|
|
12029
|
+
Object.defineProperty(out, INTERPRETED_PIECES, {
|
|
12030
|
+
value: pieces.filter((p) => p.plain).map((p) => p.entries),
|
|
12031
|
+
enumerable: false
|
|
12032
|
+
});
|
|
12033
|
+
return out;
|
|
12034
|
+
}
|
|
12035
|
+
function missingRuleMessage(pieces, missing) {
|
|
12036
|
+
for (const piece of pieces) {
|
|
12037
|
+
const deps = ruleDependencies(piece.entries.filter(([, v2]) => definitionOf(v2) !== void 0));
|
|
12038
|
+
for (const [name, ds] of deps) {
|
|
12039
|
+
for (const d of ds) if (missing.has(d)) return `fuseInterpreted: rule "${name}" references missing rule "${d}"`;
|
|
12040
|
+
}
|
|
12041
|
+
}
|
|
12042
|
+
return `fuseInterpreted: missing rule(s) ${[...missing].map((n) => `"${n}"`).join(", ")}`;
|
|
11595
12043
|
}
|
|
11596
12044
|
|
|
11597
12045
|
// src/analysis/grammar.ts
|
|
@@ -11608,14 +12056,1123 @@ function analyzeGrammarGating(grammar, opts) {
|
|
|
11608
12056
|
kind: "opaque-artifact",
|
|
11609
12057
|
reason: `precompiled artifact "${o.ns}" carries compiled rule functions, not re-lowerable IR \u2014 its choices cannot be examined. Recompile the contributing grammar so it carries IR (the macro emits IR by default) to bring it back into analysis.`
|
|
11610
12058
|
}));
|
|
12059
|
+
for (const o of opaque) {
|
|
12060
|
+
recordDegradation({
|
|
12061
|
+
code: "opaque-artifact",
|
|
12062
|
+
// `info`, not `warn`: the artifact is usually someone else's package, so the author
|
|
12063
|
+
// of THIS grammar has nothing to act on — but the blind spot still has to be counted.
|
|
12064
|
+
severity: "info",
|
|
12065
|
+
where: o.ruleNames.length > 0 ? o.ruleNames.join(", ") : `<artifact ${o.ns}>`,
|
|
12066
|
+
subject: `precompiled artifact "${o.ns}"`,
|
|
12067
|
+
fellBackTo: `it carries compiled rule functions rather than re-lowerable IR, so its ${o.ruleNames.length > 0 ? `${o.ruleNames.length} rule(s)` : "rules"} were NOT analysed and a clean gating report must not be read as a pass`,
|
|
12068
|
+
otherwise: "its choices would be walked like any other rule. Recompile the contributing grammar so it carries IR (the macro emits IR by default)"
|
|
12069
|
+
});
|
|
12070
|
+
}
|
|
11611
12071
|
return fromOpaque.length === 0 ? report : { ...report, unanalysable: [...report.unanalysable, ...fromOpaque] };
|
|
11612
12072
|
}
|
|
11613
12073
|
|
|
11614
|
-
// src/
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
12074
|
+
// src/analysis/diagnose.ts
|
|
12075
|
+
var isCombinator = (g) => typeof g === "object" && g !== null && "_def" in g;
|
|
12076
|
+
var isRuleEntries = (g) => Array.isArray(g);
|
|
12077
|
+
function diagnoseGrammar(grammar, opts) {
|
|
12078
|
+
const depth = degradationCaptureDepth();
|
|
12079
|
+
beginDegradationCapture();
|
|
12080
|
+
let report;
|
|
12081
|
+
let degradations;
|
|
12082
|
+
try {
|
|
12083
|
+
report = runAnalysis(grammar, opts);
|
|
12084
|
+
} catch (e) {
|
|
12085
|
+
const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
|
|
12086
|
+
report = emptyReport(`gating analysis threw \u2014 ${reason}`);
|
|
12087
|
+
} finally {
|
|
12088
|
+
degradations = unwindDegradationCapture(depth);
|
|
12089
|
+
}
|
|
12090
|
+
return assemble(report, degradations);
|
|
12091
|
+
}
|
|
12092
|
+
function examinedNothing(d) {
|
|
12093
|
+
return d.summary.totalChoices === 0 && d.summary.unanalysable > 0;
|
|
12094
|
+
}
|
|
12095
|
+
function formatGrammarDiagnosis(d) {
|
|
12096
|
+
const s = d.summary;
|
|
12097
|
+
const lines = [];
|
|
12098
|
+
lines.push(
|
|
12099
|
+
examinedNothing(d) ? `parseman: COULD NOT ANALYSE \u2014 0 choice(s) examined; ${s.unanalysable} rule(s) unreadable. No verdict about this grammar is available, good or bad.` : d.ok ? `parseman: grammar OK \u2014 ${s.gated}/${s.totalChoices} choice(s) gate on first char${s.recoverable > 0 ? `, ${s.recoverable} recoverable` : ""}${s.accepted > 0 ? `, ${s.accepted} accepted` : ""}${s.deferred > 0 ? `, ${s.deferred} deferred to the fusing artifact` : ""}.` : `parseman: grammar NOT OK \u2014 ${blockingOf(d).length - s.unanalysable} blocking finding(s) over ${s.totalChoices} examined choice(s).`
|
|
12100
|
+
);
|
|
12101
|
+
if (s.unanalysable > 0 && !examinedNothing(d)) {
|
|
12102
|
+
lines.push(
|
|
12103
|
+
` ${s.unanalysable} rule(s) UNANALYSABLE \u2014 THIS REPORT IS PARTIAL. An empty finding list below does NOT mean the grammar is clean.`
|
|
12104
|
+
);
|
|
12105
|
+
}
|
|
12106
|
+
for (const f of d.findings) {
|
|
12107
|
+
lines.push(`${f.severity === "blocking" ? "\u2717" : "\xB7"} [${f.code}] ${f.id}: ${f.message}`);
|
|
12108
|
+
for (const detail of f.details) lines.push(` ${detail.replace(/\n/g, "\n ")}`);
|
|
12109
|
+
if (f.acceptKey !== void 0) {
|
|
12110
|
+
lines.push(` intentional? add to the gating snapshot: { accept: ['${f.acceptKey}'] }`);
|
|
12111
|
+
}
|
|
12112
|
+
}
|
|
12113
|
+
if (!d.ok && d.acceptSnapshot.length > 0) {
|
|
12114
|
+
lines.push(` accept-all snapshot: { accept: [${d.acceptSnapshot.map((i) => `'${i}'`).join(", ")}] }`);
|
|
12115
|
+
}
|
|
12116
|
+
return lines;
|
|
12117
|
+
}
|
|
12118
|
+
function runAnalysis(grammar, opts) {
|
|
12119
|
+
if (isCombinator(grammar)) return analyzeGating(grammar, opts);
|
|
12120
|
+
if (isRuleEntries(grammar)) return analyzeGatingRules(grammar, opts);
|
|
12121
|
+
return analyzeGrammarGating(grammar, opts);
|
|
12122
|
+
}
|
|
12123
|
+
function emptyReport(reason) {
|
|
12124
|
+
return {
|
|
12125
|
+
totalChoices: 0,
|
|
12126
|
+
gated: 0,
|
|
12127
|
+
recoverable: 0,
|
|
12128
|
+
unanalysable: [{ rule: "<whole grammar>", kind: "not-a-combinator", reason }],
|
|
12129
|
+
ungated: [],
|
|
12130
|
+
accepted: [],
|
|
12131
|
+
deferred: [],
|
|
12132
|
+
acceptedUnused: [],
|
|
12133
|
+
choices: [],
|
|
12134
|
+
antiPatterns: []
|
|
12135
|
+
};
|
|
12136
|
+
}
|
|
12137
|
+
var blockingOf = (d) => d.findings.filter((f) => f.severity === "blocking");
|
|
12138
|
+
var SEVERITY_RANK = { blocking: 0, advisory: 1 };
|
|
12139
|
+
var CODE_RANK = {
|
|
12140
|
+
unanalysable: 0,
|
|
12141
|
+
"ungated-choice": 1,
|
|
12142
|
+
"anti-pattern": 2,
|
|
12143
|
+
degraded: 3,
|
|
12144
|
+
"stale-accept": 4
|
|
12145
|
+
};
|
|
12146
|
+
function assemble(report, degradations) {
|
|
12147
|
+
const findings = [];
|
|
12148
|
+
for (const u of report.unanalysable) {
|
|
12149
|
+
findings.push({
|
|
12150
|
+
id: u.rule,
|
|
12151
|
+
code: "unanalysable",
|
|
12152
|
+
severity: "blocking",
|
|
12153
|
+
rule: u.rule,
|
|
12154
|
+
message: `could not be examined [${u.kind}] \u2014 no verdict about it is available`,
|
|
12155
|
+
details: [u.reason]
|
|
12156
|
+
});
|
|
12157
|
+
}
|
|
12158
|
+
for (const c of report.ungated) {
|
|
12159
|
+
findings.push({
|
|
12160
|
+
id: c.id,
|
|
12161
|
+
code: "ungated-choice",
|
|
12162
|
+
severity: "blocking",
|
|
12163
|
+
rule: c.rule,
|
|
12164
|
+
message: `choice is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms`,
|
|
12165
|
+
details: [
|
|
12166
|
+
...c.anyArms.map((a) => `arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
|
|
12167
|
+
fix: ${a.suggestion}`),
|
|
12168
|
+
...c.overlaps.map((o) => `arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
|
|
12169
|
+
fix: ${o.suggestion}`)
|
|
12170
|
+
],
|
|
12171
|
+
acceptKey: c.id
|
|
12172
|
+
});
|
|
12173
|
+
}
|
|
12174
|
+
for (const ap of report.antiPatterns) {
|
|
12175
|
+
findings.push({
|
|
12176
|
+
id: `${ap.rule}#arm${ap.armIndex}`,
|
|
12177
|
+
code: "anti-pattern",
|
|
12178
|
+
severity: "blocking",
|
|
12179
|
+
rule: ap.rule,
|
|
12180
|
+
message: `[${ap.kind}] ${ap.message}`,
|
|
12181
|
+
details: []
|
|
12182
|
+
});
|
|
12183
|
+
}
|
|
12184
|
+
for (const d of degradations) {
|
|
12185
|
+
findings.push({
|
|
12186
|
+
// `where` + `code` is the degradation channel's own identity; reuse it so the two
|
|
12187
|
+
// surfaces name the same thing the same way.
|
|
12188
|
+
id: `${d.code}@${d.where}`,
|
|
12189
|
+
code: "degraded",
|
|
12190
|
+
// Mirrors `DegradationSeverity`: 'warn' is a real cost the author can remove,
|
|
12191
|
+
// 'info' is a fact they cannot act on (someone else's package).
|
|
12192
|
+
severity: d.severity === "warn" ? "blocking" : "advisory",
|
|
12193
|
+
rule: d.where,
|
|
12194
|
+
message: formatDegradation(d),
|
|
12195
|
+
details: []
|
|
12196
|
+
});
|
|
12197
|
+
}
|
|
12198
|
+
for (const id of report.acceptedUnused) {
|
|
12199
|
+
findings.push({
|
|
12200
|
+
id,
|
|
12201
|
+
code: "stale-accept",
|
|
12202
|
+
severity: "advisory",
|
|
12203
|
+
rule: id,
|
|
12204
|
+
message: "accept entry matched no ungated choice \u2014 the grammar was fixed; prune this line",
|
|
12205
|
+
details: []
|
|
12206
|
+
});
|
|
12207
|
+
}
|
|
12208
|
+
findings.sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || CODE_RANK[a.code] - CODE_RANK[b.code] || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
12209
|
+
return {
|
|
12210
|
+
schema: "parseman.diagnosis/1",
|
|
12211
|
+
ok: !findings.some((f) => f.severity === "blocking"),
|
|
12212
|
+
summary: {
|
|
12213
|
+
totalChoices: report.totalChoices,
|
|
12214
|
+
gated: report.gated,
|
|
12215
|
+
recoverable: report.recoverable,
|
|
12216
|
+
ungated: report.ungated.length,
|
|
12217
|
+
accepted: report.accepted.length,
|
|
12218
|
+
deferred: report.deferred.length,
|
|
12219
|
+
antiPatterns: report.antiPatterns.length,
|
|
12220
|
+
unanalysable: report.unanalysable.length,
|
|
12221
|
+
degraded: degradations.length,
|
|
12222
|
+
staleAccepts: report.acceptedUnused.length
|
|
12223
|
+
},
|
|
12224
|
+
findings,
|
|
12225
|
+
acceptSnapshot: report.ungated.map((c) => c.id).sort(),
|
|
12226
|
+
gating: report,
|
|
12227
|
+
degradations
|
|
12228
|
+
};
|
|
12229
|
+
}
|
|
12230
|
+
|
|
12231
|
+
// src/functional/run.ts
|
|
12232
|
+
var SELECT_EXAMPLE = "run(entry, input, { rootTrivia: { select: ['blockComment', 'lineComment'] } })";
|
|
12233
|
+
var REMOVED_RUN_RESULT_FIELDS = [
|
|
12234
|
+
[
|
|
12235
|
+
"triviaMap",
|
|
12236
|
+
`It was a dense root-trivia index built on every parse; root trivia is now an OPT-IN sparse capture, because most grammars paid for an index they never read. Migration: name the trivia labels your grammar defines \u2014 ${SELECT_EXAMPLE} \u2014 and read \`RunResult.rootTrivia\`, whose \`.index\` carries the labels and gap lookups \`triviaMap\` exposed.`
|
|
12237
|
+
],
|
|
12238
|
+
[
|
|
12239
|
+
"triviaLog",
|
|
12240
|
+
`It was the flat \`start, end[, kindIndex]\` log every other trivia view was derived from, and it is no longer allocated at all \u2014 not empty, absent. Migration: request the labels you need \u2014 ${SELECT_EXAMPLE} \u2014 and read \`RunResult.rootTrivia.rows\`. Do NOT reuse a stride-2/3 reader: rows are a different width and a different unit (one row per SELECTED marker, not one per trivia chunk), so an old loop reads confidently wrong offsets rather than failing.`
|
|
12241
|
+
],
|
|
12242
|
+
[
|
|
12243
|
+
"triviaKindLabels",
|
|
12244
|
+
`It was already optional, so nothing ever flagged its disappearance \u2014 and \`triviaKindMask(undefined, \u2026)\` means "capture everything", so reading it now silently WIDENS capture instead of erroring. Migration: request labels \u2014 ${SELECT_EXAMPLE} \u2014 then the set you asked for is \`RunResult.rootTrivia.select\` and the resolved table is \`RunResult.rootTrivia.index.labels\`.`
|
|
12245
|
+
]
|
|
12246
|
+
];
|
|
12247
|
+
function guardRemovedFields(result) {
|
|
12248
|
+
for (const [name, detail] of REMOVED_RUN_RESULT_FIELDS) {
|
|
12249
|
+
Object.defineProperty(result, name, {
|
|
12250
|
+
configurable: true,
|
|
12251
|
+
enumerable: false,
|
|
12252
|
+
get() {
|
|
12253
|
+
throw new TypeError(
|
|
12254
|
+
`RunResult.${name} was REMOVED in parseman 0.44.0 and has no default replacement. ${detail}`
|
|
12255
|
+
);
|
|
12256
|
+
}
|
|
12257
|
+
});
|
|
12258
|
+
}
|
|
12259
|
+
return result;
|
|
12260
|
+
}
|
|
12261
|
+
var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
|
|
12262
|
+
function triviaKindLabelsFromRunnable(r) {
|
|
12263
|
+
if (!r) return void 0;
|
|
12264
|
+
if (typeof r === "function") return r._meta?.triviaKindLabels;
|
|
12265
|
+
const grammarTrivia = r._meta.grammarTrivia;
|
|
12266
|
+
return grammarTrivia?._meta.triviaKindLabels ?? r._meta.triviaKindLabels ?? (r._def.tag === "grammar" ? r._def.triviaParser?._meta.triviaKindLabels : void 0);
|
|
12267
|
+
}
|
|
12268
|
+
function rootTriviaClassifiedFromRunnable(r) {
|
|
12269
|
+
if (!r) return false;
|
|
12270
|
+
if (typeof r === "function") return r._meta?.rootTriviaClassified === true;
|
|
12271
|
+
const grammarTrivia = r._meta.grammarTrivia;
|
|
12272
|
+
return grammarTrivia?._meta.rootTriviaClassified === true || r._meta.rootTriviaClassified === true;
|
|
12273
|
+
}
|
|
12274
|
+
function makeSelectedRootLabelIndex(labels) {
|
|
12275
|
+
const index = /* @__PURE__ */ Object.create(null);
|
|
12276
|
+
for (let i = 0; i < labels.length; i++) {
|
|
12277
|
+
if (index[labels[i]] === void 0) index[labels[i]] = i;
|
|
12278
|
+
}
|
|
12279
|
+
return index;
|
|
12280
|
+
}
|
|
12281
|
+
function runOnce(entry, input, options, phase, profileState) {
|
|
12282
|
+
if (typeof entry !== "function" && typeof entry?.parse !== "function") {
|
|
12283
|
+
throw new TypeError(
|
|
12284
|
+
`run(): start production is ${entry === null ? "null" : typeof entry}, not a rule \u2014 the requested grammar rule does not exist (check the rule name).`
|
|
12285
|
+
);
|
|
12286
|
+
}
|
|
12287
|
+
const rootTriviaSelection = options.rootTrivia?.select;
|
|
12288
|
+
const captureRootTrivia = rootTriviaSelection !== void 0 && rootTriviaSelection.length > 0;
|
|
12289
|
+
const selectedRootLog = captureRootTrivia ? [] : void 0;
|
|
12290
|
+
const selectedRootLabelIndex = captureRootTrivia ? makeSelectedRootLabelIndex(rootTriviaSelection) : void 0;
|
|
12291
|
+
const errors = [];
|
|
12292
|
+
const profile = profileState ?? (phase === void 0 ? void 0 : { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 });
|
|
12293
|
+
const skipGlobalSinks = phase === "recognizer" || phase === "capture";
|
|
12294
|
+
const grammarTrivia = typeof entry !== "function" ? entry._meta.grammarTrivia : void 0;
|
|
12295
|
+
const grammarScanSkip = typeof entry !== "function" ? entry._meta.grammarScanSkip : void 0;
|
|
12296
|
+
const triviaKindLabels = triviaKindLabelsFromRunnable(entry) ?? triviaKindLabelsFromRunnable(options.trivia);
|
|
12297
|
+
if (captureRootTrivia && triviaKindLabels === void 0) {
|
|
12298
|
+
throw new TypeError("run(): rootTrivia.select requires labeled grammar trivia.");
|
|
12299
|
+
}
|
|
12300
|
+
if (captureRootTrivia) {
|
|
12301
|
+
for (const label2 of rootTriviaSelection) {
|
|
12302
|
+
if (!triviaKindLabels.includes(label2)) {
|
|
12303
|
+
throw new TypeError(`run(): rootTrivia.select contains unknown trivia label ${JSON.stringify(label2)}.`);
|
|
12304
|
+
}
|
|
12305
|
+
}
|
|
12306
|
+
}
|
|
12307
|
+
if (captureRootTrivia && !rootTriviaClassifiedFromRunnable(entry) && !rootTriviaClassifiedFromRunnable(options.trivia)) {
|
|
12308
|
+
throw new TypeError(
|
|
12309
|
+
"run(): rootTrivia.select requires classifiedTrivia() at the root."
|
|
12310
|
+
);
|
|
12311
|
+
}
|
|
12312
|
+
const stamped = entry;
|
|
12313
|
+
assertHostModeCompatible(
|
|
12314
|
+
typeof entry === "function" ? stamped[FUSED_HOST_MODE] ?? "ast" : entry._meta.grammarHostMode ?? "ast",
|
|
12315
|
+
options.build,
|
|
12316
|
+
typeof entry === "function" && stamped[FUSED_HOST_ELIDED] === true
|
|
12317
|
+
);
|
|
12318
|
+
const ctx = {
|
|
12319
|
+
trackLines: false,
|
|
12320
|
+
...skipGlobalSinks ? { _pmProfile: profile } : {
|
|
12321
|
+
...captureRootTrivia ? {
|
|
12322
|
+
_rootTriviaLog: selectedRootLog,
|
|
12323
|
+
_rootTriviaKindIndex: selectedRootLabelIndex,
|
|
12324
|
+
_rootTriviaStrictScopes: true
|
|
12325
|
+
} : {},
|
|
12326
|
+
_errors: errors,
|
|
12327
|
+
...profile === void 0 ? {} : { _pmProfile: profile }
|
|
12328
|
+
},
|
|
12329
|
+
build: options.build,
|
|
12330
|
+
state: options.state,
|
|
12331
|
+
...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...triviaKindLabels ? { triviaKindLabels } : {} } : triviaKindLabels ? { triviaKindLabels } : {},
|
|
12332
|
+
...grammarScanSkip !== void 0 ? { scanSkip: grammarScanSkip } : {},
|
|
12333
|
+
...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {},
|
|
12334
|
+
...options.tolerant ? { _tolerant: true, _rec: REC } : {},
|
|
12335
|
+
...options.instrumentation === void 0 ? {} : options.instrumentation
|
|
12336
|
+
};
|
|
12337
|
+
const r = invoke(entry, input, 0, ctx);
|
|
12338
|
+
let unconsumedFrom = null;
|
|
12339
|
+
if (r.ok) {
|
|
12340
|
+
let pos = r.span?.end ?? 0;
|
|
12341
|
+
if (options.trivia && pos < input.length) {
|
|
12342
|
+
const t = invoke(options.trivia, input, pos, { trackLines: false });
|
|
12343
|
+
if (t.ok && t.span.end > pos) pos = t.span.end;
|
|
12344
|
+
}
|
|
12345
|
+
unconsumedFrom = pos < input.length ? pos : null;
|
|
12346
|
+
}
|
|
12347
|
+
return {
|
|
12348
|
+
ok: r.ok,
|
|
12349
|
+
value: r.ok ? r.value : void 0,
|
|
12350
|
+
span: r.span ?? { start: 0, end: 0 },
|
|
12351
|
+
expected: r.ok ? [] : r.expected ?? [],
|
|
12352
|
+
errors,
|
|
12353
|
+
...selectedRootLog !== void 0 && selectedRootLog.length > 0 ? {
|
|
12354
|
+
rootTrivia: {
|
|
12355
|
+
rows: selectedRootLog,
|
|
12356
|
+
select: rootTriviaSelection,
|
|
12357
|
+
index: buildRootTriviaIndex(selectedRootLog, rootTriviaSelection)
|
|
12358
|
+
}
|
|
12359
|
+
} : {},
|
|
12360
|
+
unconsumedFrom
|
|
12361
|
+
};
|
|
12362
|
+
}
|
|
12363
|
+
function run(entry, input, options = {}) {
|
|
12364
|
+
if (!options.profile) return guardRemovedFields(runOnce(entry, input, options));
|
|
12365
|
+
throw new TypeError(
|
|
12366
|
+
"run({ profile: true }) is unavailable: profiling counters are no longer compiled into parser artifacts, and the interpreter does not implement them yet. Profiling is moving to interpreted mode."
|
|
12367
|
+
);
|
|
12368
|
+
}
|
|
12369
|
+
|
|
12370
|
+
// src/analysis/choice-cost.ts
|
|
12371
|
+
var choiceSiteKey = (s) => s.path === "" ? s.rule : `${s.rule} \u203A ${s.path}`;
|
|
12372
|
+
function childrenOf5(d) {
|
|
12373
|
+
switch (d.tag) {
|
|
12374
|
+
case "sequence":
|
|
12375
|
+
case "choice":
|
|
12376
|
+
return d.parsers;
|
|
12377
|
+
case "dispatch":
|
|
12378
|
+
return [
|
|
12379
|
+
d.selector,
|
|
12380
|
+
...d.cases.map((c) => c.parser),
|
|
12381
|
+
...d.otherwise === void 0 ? [] : [d.otherwise]
|
|
12382
|
+
];
|
|
12383
|
+
case "skip":
|
|
12384
|
+
return [d.main, d.skipped];
|
|
12385
|
+
case "sepBy":
|
|
12386
|
+
return [d.parser, d.separator];
|
|
12387
|
+
case "recover":
|
|
12388
|
+
return [d.parser, d.sentinel];
|
|
12389
|
+
case "scanTo":
|
|
12390
|
+
return [d.sentinel, ...d.skip];
|
|
12391
|
+
case "grammar":
|
|
12392
|
+
return d.triviaParser ? [d.parser, d.triviaParser] : [d.parser];
|
|
12393
|
+
case "lazy":
|
|
12394
|
+
case "literal":
|
|
12395
|
+
case "regex":
|
|
12396
|
+
case "keywords":
|
|
12397
|
+
case "guard":
|
|
12398
|
+
case "unknown":
|
|
12399
|
+
return [];
|
|
12400
|
+
default: {
|
|
12401
|
+
const rec = d;
|
|
12402
|
+
return rec.parser ? [rec.parser] : [];
|
|
12403
|
+
}
|
|
12404
|
+
}
|
|
12405
|
+
}
|
|
12406
|
+
var ruleNameOf5 = (p) => p._ruleName;
|
|
12407
|
+
function slotLabel(d, index) {
|
|
12408
|
+
switch (d.tag) {
|
|
12409
|
+
case "sequence":
|
|
12410
|
+
return `seq[${index}]`;
|
|
12411
|
+
case "choice":
|
|
12412
|
+
return `choice[${index}]`;
|
|
12413
|
+
case "dispatch":
|
|
12414
|
+
return index === 0 ? "dispatch.selector" : `dispatch[${index - 1}]`;
|
|
12415
|
+
case "skip":
|
|
12416
|
+
return index === 0 ? "skip.main" : "skip.skipped";
|
|
12417
|
+
case "sepBy":
|
|
12418
|
+
return index === 0 ? "sepBy.item" : "sepBy.sep";
|
|
12419
|
+
case "recover":
|
|
12420
|
+
return index === 0 ? "recover.parser" : "recover.sentinel";
|
|
12421
|
+
case "scanTo":
|
|
12422
|
+
return index === 0 ? "scanTo.sentinel" : `scanTo.skip[${index - 1}]`;
|
|
12423
|
+
case "grammar":
|
|
12424
|
+
return index === 0 ? "grammar" : "grammar.trivia";
|
|
12425
|
+
case "node":
|
|
12426
|
+
return `node(${d.type ?? "anonymous"})`;
|
|
12427
|
+
case "label":
|
|
12428
|
+
return `label(${d.label})`;
|
|
12429
|
+
case "field":
|
|
12430
|
+
return `field(${d.name})`;
|
|
12431
|
+
default:
|
|
12432
|
+
return d.tag;
|
|
12433
|
+
}
|
|
12434
|
+
}
|
|
12435
|
+
function resolveRoot(p) {
|
|
12436
|
+
let cur = p;
|
|
12437
|
+
for (let hop = 0; hop < 8; hop++) {
|
|
12438
|
+
const d = cur._def;
|
|
12439
|
+
if (d.tag !== "lazy") return { ok: true, p: cur };
|
|
12440
|
+
let next;
|
|
12441
|
+
try {
|
|
12442
|
+
next = d.thunk();
|
|
12443
|
+
} catch {
|
|
12444
|
+
return { ok: false };
|
|
12445
|
+
}
|
|
12446
|
+
if (next === cur) return { ok: false };
|
|
12447
|
+
cur = next;
|
|
12448
|
+
}
|
|
12449
|
+
return { ok: false };
|
|
12450
|
+
}
|
|
12451
|
+
function walkRules(ruleMap) {
|
|
12452
|
+
assertAnalysable(ruleMap);
|
|
12453
|
+
const out = [];
|
|
12454
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12455
|
+
const unresolvedRoots = [];
|
|
12456
|
+
const visit = (p, rule, path) => {
|
|
12457
|
+
if (seen.has(p)) return;
|
|
12458
|
+
seen.add(p);
|
|
12459
|
+
const d = p._def;
|
|
12460
|
+
const own = ruleNameOf5(p);
|
|
12461
|
+
const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path };
|
|
12462
|
+
out.push({ p, d, site: here });
|
|
12463
|
+
const kids = childrenOf5(d);
|
|
12464
|
+
for (let i = 0; i < kids.length; i++) {
|
|
12465
|
+
const seg = slotLabel(d, i);
|
|
12466
|
+
visit(kids[i], here.rule, here.path === "" ? seg : `${here.path} \u203A ${seg}`);
|
|
12467
|
+
}
|
|
12468
|
+
};
|
|
12469
|
+
for (const [name, rule] of ruleMap) {
|
|
12470
|
+
const r = resolveRoot(rule);
|
|
12471
|
+
if (!r.ok) {
|
|
12472
|
+
unresolvedRoots.push(name);
|
|
12473
|
+
continue;
|
|
12474
|
+
}
|
|
12475
|
+
visit(r.p, name, "");
|
|
12476
|
+
}
|
|
12477
|
+
unresolvedRoots.sort();
|
|
12478
|
+
return { walked: out, unresolvedRoots };
|
|
12479
|
+
}
|
|
12480
|
+
function assertAnalysable(ruleMap) {
|
|
12481
|
+
if (ruleMap.length === 0) {
|
|
12482
|
+
throw new TypeError(
|
|
12483
|
+
"choice-cost: the rule map is EMPTY. Analysing nothing and reporting no findings is indistinguishable from analysing a clean grammar, so this is refused rather than answered. Pass the rules() map (or the map given to compileRuleMap/compileLinkable)."
|
|
12484
|
+
);
|
|
12485
|
+
}
|
|
12486
|
+
for (const [name, rule] of ruleMap) {
|
|
12487
|
+
const def = rule?._def;
|
|
12488
|
+
if (def !== null && typeof def === "object" && typeof def.tag === "string") continue;
|
|
12489
|
+
throw new TypeError(
|
|
12490
|
+
`choice-cost: rule '${name}' is not a combinator (no _def). This analysis walks the COMBINATOR TREE; the value returned by compose()/composeLeaf() is a fused artifact whose entries are parse functions. Pass the rules() map itself instead.`
|
|
12491
|
+
);
|
|
12492
|
+
}
|
|
12493
|
+
}
|
|
12494
|
+
function leadingTermOfArm2(arm) {
|
|
12495
|
+
let d = arm._def;
|
|
12496
|
+
for (; ; ) {
|
|
12497
|
+
if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label") {
|
|
12498
|
+
d = d.parser._def;
|
|
12499
|
+
continue;
|
|
12500
|
+
}
|
|
12501
|
+
break;
|
|
12502
|
+
}
|
|
12503
|
+
if (d.tag !== "sequence") {
|
|
12504
|
+
return {
|
|
12505
|
+
reason: "not-a-sequence",
|
|
12506
|
+
detail: `arm is \`${d.tag}\`; only node/grammar/transform/label wrappers are peeled, because they are the ones that consume nothing before the sequence`
|
|
12507
|
+
};
|
|
12508
|
+
}
|
|
12509
|
+
if (d.parsers.length < 2) {
|
|
12510
|
+
return { reason: "sequence-shorter-than-2", detail: `sequence has ${d.parsers.length} term(s); nothing would remain after factoring` };
|
|
12511
|
+
}
|
|
12512
|
+
return { term: d.parsers[0] };
|
|
12513
|
+
}
|
|
12514
|
+
function leadingTermKey(term) {
|
|
12515
|
+
const d = term._def;
|
|
12516
|
+
if (d.tag === "literal") {
|
|
12517
|
+
if (d.caseInsensitive) {
|
|
12518
|
+
return { reason: "lead-case-insensitive-literal", detail: `literal ${JSON.stringify(d.value)} is case-insensitive; the matched text can differ from the literal's own value` };
|
|
12519
|
+
}
|
|
12520
|
+
return { key: `L:${JSON.stringify(d.value)}`, render: JSON.stringify(d.value) };
|
|
12521
|
+
}
|
|
12522
|
+
if (d.tag === "regex") {
|
|
12523
|
+
return { key: `R:${JSON.stringify([d.source, d.flags])}`, render: `/${d.source}/${d.flags.replace(/[gy]/g, "")}` };
|
|
12524
|
+
}
|
|
12525
|
+
return {
|
|
12526
|
+
reason: "lead-not-concrete-terminal",
|
|
12527
|
+
detail: `leading term is \`${d.tag}\`${d.tag === "lazy" ? ` (ref${ruleNameOf5(term) === void 0 ? "" : ` to ${ruleNameOf5(term)}`})` : ""}; factoring through it would change the arm's value or capture shape`
|
|
12528
|
+
};
|
|
12529
|
+
}
|
|
12530
|
+
function armLabel(arm) {
|
|
12531
|
+
const name = ruleNameOf5(arm);
|
|
12532
|
+
if (name !== void 0) return name;
|
|
12533
|
+
let d = arm._def;
|
|
12534
|
+
for (let hop = 0; hop < 8; hop++) {
|
|
12535
|
+
if (d.tag === "node" && d.type !== void 0) return `node(${d.type})`;
|
|
12536
|
+
if (d.tag === "label") return `label(${d.label})`;
|
|
12537
|
+
if (d.tag === "field") return `field(${d.name})`;
|
|
12538
|
+
if (d.tag === "literal") return JSON.stringify(d.value);
|
|
12539
|
+
if (d.tag === "regex") return `/${d.source}/`;
|
|
12540
|
+
if (d.tag === "keywords") return `keywords(${d.words.length})`;
|
|
12541
|
+
if (d.tag === "sequence" || d.tag === "choice") {
|
|
12542
|
+
const first = d.parsers[0];
|
|
12543
|
+
if (first === void 0) return d.tag;
|
|
12544
|
+
const inner = ruleNameOf5(first);
|
|
12545
|
+
if (inner !== void 0) return `${d.tag}(${inner}, \u2026)`;
|
|
12546
|
+
d = first._def;
|
|
12547
|
+
continue;
|
|
12548
|
+
}
|
|
12549
|
+
const rec = d;
|
|
12550
|
+
if (rec.parser === void 0) return d.tag;
|
|
12551
|
+
d = rec.parser._def;
|
|
12552
|
+
}
|
|
12553
|
+
return arm._def.tag;
|
|
12554
|
+
}
|
|
12555
|
+
function analyzeChoiceInventory(ruleMap) {
|
|
12556
|
+
const { walked, unresolvedRoots } = walkRules(ruleMap);
|
|
12557
|
+
const entries = [];
|
|
12558
|
+
for (const w of walked) {
|
|
12559
|
+
if (w.d.tag !== "choice") continue;
|
|
12560
|
+
const d = w.d;
|
|
12561
|
+
const arity = d.parsers.length;
|
|
12562
|
+
const strategy = d.strategy.tag;
|
|
12563
|
+
const gated = d.gates.some((g) => g !== null);
|
|
12564
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
12565
|
+
const armDeclines = [];
|
|
12566
|
+
for (let i = 0; i < arity; i++) {
|
|
12567
|
+
const lead = leadingTermOfArm2(d.parsers[i]);
|
|
12568
|
+
if ("reason" in lead) {
|
|
12569
|
+
armDeclines.push({ arm: i, reason: lead.reason, detail: lead.detail });
|
|
12570
|
+
continue;
|
|
12571
|
+
}
|
|
12572
|
+
const keyed = leadingTermKey(lead.term);
|
|
12573
|
+
if ("reason" in keyed) {
|
|
12574
|
+
armDeclines.push({ arm: i, reason: keyed.reason, detail: keyed.detail });
|
|
12575
|
+
continue;
|
|
12576
|
+
}
|
|
12577
|
+
const slot = byKey.get(keyed.key);
|
|
12578
|
+
if (slot === void 0) byKey.set(keyed.key, { render: keyed.render, members: [i] });
|
|
12579
|
+
else slot.members.push(i);
|
|
12580
|
+
}
|
|
12581
|
+
const groups = [...byKey.entries()].filter(([, v2]) => v2.members.length >= 2).map(([key, v2]) => ({ key, render: v2.render, members: v2.members })).sort((a, b) => b.members.length - a.members.length || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
12582
|
+
const factored = strategy === "sharedPrefix";
|
|
12583
|
+
let declineReason;
|
|
12584
|
+
if (!factored) {
|
|
12585
|
+
declineReason = arity < 2 ? "fewer-than-two-arms" : d.disjoint ? "disjoint-dispatch" : gated ? "gated-arms" : strategy !== "firstMatch" ? "strategy-preempted" : armDeclines.length > 0 ? "arms-not-factorable" : "leads-differ";
|
|
12586
|
+
}
|
|
12587
|
+
const inGroups = /* @__PURE__ */ new Set();
|
|
12588
|
+
for (const g of groups) for (const m of g.members) inGroups.add(m);
|
|
12589
|
+
entries.push({
|
|
12590
|
+
site: w.site,
|
|
12591
|
+
siteKey: choiceSiteKey(w.site),
|
|
12592
|
+
arity,
|
|
12593
|
+
strategy,
|
|
12594
|
+
disjoint: d.disjoint,
|
|
12595
|
+
gated,
|
|
12596
|
+
groups,
|
|
12597
|
+
armDeclines,
|
|
12598
|
+
factored,
|
|
12599
|
+
...declineReason === void 0 ? {} : { declineReason },
|
|
12600
|
+
unfactoredArms: factored ? 0 : inGroups.size
|
|
12601
|
+
});
|
|
12602
|
+
}
|
|
12603
|
+
entries.sort((a, b) => a.siteKey < b.siteKey ? -1 : a.siteKey > b.siteKey ? 1 : 0);
|
|
12604
|
+
const backlog = entries.filter((e) => !e.factored && e.groups.length > 0 && e.declineReason !== "disjoint-dispatch" && e.declineReason !== "strategy-preempted");
|
|
12605
|
+
return {
|
|
12606
|
+
schema: "parseman.choice-inventory/1",
|
|
12607
|
+
rules: ruleMap.length,
|
|
12608
|
+
choiceSites: entries.length,
|
|
12609
|
+
factoredSites: entries.filter((e) => e.factored).length,
|
|
12610
|
+
backlogSites: backlog.length,
|
|
12611
|
+
backlogArms: backlog.reduce((n, e) => n + e.unfactoredArms, 0),
|
|
12612
|
+
unresolvedRoots,
|
|
12613
|
+
entries
|
|
12614
|
+
};
|
|
12615
|
+
}
|
|
12616
|
+
function compiledFirstCharGate(p) {
|
|
12617
|
+
const fs = p._meta.firstSet;
|
|
12618
|
+
if (fs.kind !== "any" && !canMatchEmptyAtStart2(p)) return fs;
|
|
12619
|
+
if (fs.kind !== "any" || canMatchEmptyAtStart2(p)) return null;
|
|
12620
|
+
let deep;
|
|
12621
|
+
try {
|
|
12622
|
+
deep = firstSetOf(p);
|
|
12623
|
+
} catch {
|
|
12624
|
+
return null;
|
|
12625
|
+
}
|
|
12626
|
+
return deep.kind === "ranges" ? deep : null;
|
|
12627
|
+
}
|
|
12628
|
+
function canMatchEmptyAtStart2(p) {
|
|
12629
|
+
const d = p._def;
|
|
12630
|
+
switch (d.tag) {
|
|
12631
|
+
case "regex":
|
|
12632
|
+
return regexMatchesEmpty2(d.source, d.flags);
|
|
12633
|
+
case "optional":
|
|
12634
|
+
case "many":
|
|
12635
|
+
return true;
|
|
12636
|
+
case "transform":
|
|
12637
|
+
case "label":
|
|
12638
|
+
case "field":
|
|
12639
|
+
return canMatchEmptyAtStart2(d.parser);
|
|
12640
|
+
case "literal":
|
|
12641
|
+
return d.value.length === 0;
|
|
12642
|
+
default:
|
|
12643
|
+
return false;
|
|
12644
|
+
}
|
|
12645
|
+
}
|
|
12646
|
+
function regexMatchesEmpty2(source, flags) {
|
|
12647
|
+
try {
|
|
12648
|
+
return new RegExp(`^(?:${source})$`, flags.replace(/[gy]/g, "")).test("");
|
|
12649
|
+
} catch {
|
|
12650
|
+
return true;
|
|
12651
|
+
}
|
|
12652
|
+
}
|
|
12653
|
+
function inSet(code, fs) {
|
|
12654
|
+
if (fs.kind === "any") return true;
|
|
12655
|
+
if (fs.kind === "empty") return false;
|
|
12656
|
+
for (const r of fs.ranges) if (code >= r.lo && code <= r.hi) return true;
|
|
12657
|
+
return false;
|
|
12658
|
+
}
|
|
12659
|
+
var TERMINAL_TAGS = /* @__PURE__ */ new Set(["literal", "regex", "keywords", "scanTo"]);
|
|
12660
|
+
var INSTRUMENTABLE = /* @__PURE__ */ new Set(["firstMatch", "sharedPrefix"]);
|
|
12661
|
+
function profileWastedWork(opts) {
|
|
12662
|
+
const { rules: rules2, corpus } = opts;
|
|
12663
|
+
const { walked, unresolvedRoots } = walkRules(rules2);
|
|
12664
|
+
const entryParser = typeof opts.entry === "string" ? rules2.find(([n]) => n === opts.entry)?.[1] : opts.entry;
|
|
12665
|
+
if (entryParser === void 0) {
|
|
12666
|
+
throw new TypeError(
|
|
12667
|
+
`choice-cost: entry rule '${String(opts.entry)}' is not in the rule map (have: ${rules2.map(([n]) => n).slice(0, 12).join(", ")}${rules2.length > 12 ? ", \u2026" : ""}).`
|
|
12668
|
+
);
|
|
12669
|
+
}
|
|
12670
|
+
if (corpus.length === 0) {
|
|
12671
|
+
throw new TypeError("choice-cost: the corpus is EMPTY. A profile over no input measures nothing; refusing to report zero wasted bytes as a result.");
|
|
12672
|
+
}
|
|
12673
|
+
for (const f of corpus) {
|
|
12674
|
+
if (f.text.length === 0) throw new TypeError(`choice-cost: corpus entry '${f.id}' is EMPTY.`);
|
|
12675
|
+
}
|
|
12676
|
+
let highWater = 0;
|
|
12677
|
+
const armRecords = [];
|
|
12678
|
+
const siteTotals = /* @__PURE__ */ new Map();
|
|
12679
|
+
const restore = [];
|
|
12680
|
+
const undoAll = () => {
|
|
12681
|
+
for (let i = restore.length - 1; i >= 0; i--) restore[i]();
|
|
12682
|
+
};
|
|
12683
|
+
let instrumentedSites = 0;
|
|
12684
|
+
let uninstrumentableSites = 0;
|
|
12685
|
+
try {
|
|
12686
|
+
for (const w of walked) {
|
|
12687
|
+
if (w.d.tag !== "choice") continue;
|
|
12688
|
+
const siteKey = choiceSiteKey(w.site);
|
|
12689
|
+
const strategy = w.d.strategy.tag;
|
|
12690
|
+
const canInstrument = INSTRUMENTABLE.has(strategy) && !w.d.disjoint;
|
|
12691
|
+
const rec = {
|
|
12692
|
+
siteKey,
|
|
12693
|
+
site: w.site,
|
|
12694
|
+
strategy,
|
|
12695
|
+
arity: w.d.parsers.length,
|
|
12696
|
+
instrumented: canInstrument,
|
|
12697
|
+
attempts: 0,
|
|
12698
|
+
failures: 0,
|
|
12699
|
+
wastedBytes: 0,
|
|
12700
|
+
gatedAttempts: 0,
|
|
12701
|
+
gatedFailures: 0,
|
|
12702
|
+
gatedWastedBytes: 0
|
|
12703
|
+
};
|
|
12704
|
+
if (siteTotals.has(siteKey)) {
|
|
12705
|
+
uninstrumentableSites++;
|
|
12706
|
+
continue;
|
|
12707
|
+
}
|
|
12708
|
+
siteTotals.set(siteKey, rec);
|
|
12709
|
+
if (!canInstrument) {
|
|
12710
|
+
uninstrumentableSites++;
|
|
12711
|
+
continue;
|
|
12712
|
+
}
|
|
12713
|
+
instrumentedSites++;
|
|
12714
|
+
const slots = w.d.parsers;
|
|
12715
|
+
for (let i = 0; i < slots.length; i++) {
|
|
12716
|
+
const original = slots[i];
|
|
12717
|
+
const gate2 = compiledFirstCharGate(original);
|
|
12718
|
+
const armRec = {
|
|
12719
|
+
site: w.site,
|
|
12720
|
+
siteKey,
|
|
12721
|
+
arm: i,
|
|
12722
|
+
label: armLabel(original),
|
|
12723
|
+
gate: gate2,
|
|
12724
|
+
attempts: 0,
|
|
12725
|
+
failures: 0,
|
|
12726
|
+
wastedBytes: 0,
|
|
12727
|
+
gatedAttempts: 0,
|
|
12728
|
+
gatedFailures: 0,
|
|
12729
|
+
gatedWastedBytes: 0
|
|
12730
|
+
};
|
|
12731
|
+
armRecords.push(armRec);
|
|
12732
|
+
const wrapper = {
|
|
12733
|
+
_tag: original._tag,
|
|
12734
|
+
_meta: original._meta,
|
|
12735
|
+
_def: original._def,
|
|
12736
|
+
parse(input, pos, ctx) {
|
|
12737
|
+
const code = pos < input.length ? input.codePointAt(pos) ?? -1 : -1;
|
|
12738
|
+
const entered = gate2 === null || code >= 0 && inSet(code, gate2);
|
|
12739
|
+
const saved = highWater;
|
|
12740
|
+
highWater = pos;
|
|
12741
|
+
const r = original.parse(input, pos, ctx);
|
|
12742
|
+
const reach = highWater;
|
|
12743
|
+
highWater = saved > reach ? saved : reach;
|
|
12744
|
+
armRec.attempts++;
|
|
12745
|
+
rec.attempts++;
|
|
12746
|
+
if (entered) {
|
|
12747
|
+
armRec.gatedAttempts++;
|
|
12748
|
+
rec.gatedAttempts++;
|
|
12749
|
+
}
|
|
12750
|
+
if (!r.ok) {
|
|
12751
|
+
armRec.failures++;
|
|
12752
|
+
rec.failures++;
|
|
12753
|
+
if (entered) {
|
|
12754
|
+
armRec.gatedFailures++;
|
|
12755
|
+
rec.gatedFailures++;
|
|
12756
|
+
}
|
|
12757
|
+
const wasted = reach - pos;
|
|
12758
|
+
if (wasted > 0) {
|
|
12759
|
+
armRec.wastedBytes += wasted;
|
|
12760
|
+
rec.wastedBytes += wasted;
|
|
12761
|
+
if (entered) {
|
|
12762
|
+
armRec.gatedWastedBytes += wasted;
|
|
12763
|
+
rec.gatedWastedBytes += wasted;
|
|
12764
|
+
}
|
|
12765
|
+
}
|
|
12766
|
+
}
|
|
12767
|
+
return r;
|
|
12768
|
+
}
|
|
12769
|
+
};
|
|
12770
|
+
const rn = ruleNameOf5(original);
|
|
12771
|
+
if (rn !== void 0) wrapper._ruleName = rn;
|
|
12772
|
+
slots[i] = wrapper;
|
|
12773
|
+
restore.push(() => {
|
|
12774
|
+
slots[i] = original;
|
|
12775
|
+
});
|
|
12776
|
+
}
|
|
12777
|
+
}
|
|
12778
|
+
if (instrumentedSites === 0) {
|
|
12779
|
+
throw new TypeError(
|
|
12780
|
+
"choice-cost: the grammar contains NO instrumentable choice site (no `firstMatch` or `sharedPrefix` choice). A wasted-work profile over it would report zero for a reason that has nothing to do with the grammar being efficient, so it is refused."
|
|
12781
|
+
);
|
|
12782
|
+
}
|
|
12783
|
+
for (const w of walked) {
|
|
12784
|
+
if (!TERMINAL_TAGS.has(w.d.tag)) continue;
|
|
12785
|
+
const p = w.p;
|
|
12786
|
+
const original = p.parse;
|
|
12787
|
+
p.parse = ((input, pos, ctx) => {
|
|
12788
|
+
if (pos > highWater) highWater = pos;
|
|
12789
|
+
return original.call(w.p, input, pos, ctx);
|
|
12790
|
+
});
|
|
12791
|
+
restore.push(() => {
|
|
12792
|
+
p.parse = original;
|
|
12793
|
+
});
|
|
12794
|
+
}
|
|
12795
|
+
} catch (e) {
|
|
12796
|
+
undoAll();
|
|
12797
|
+
throw e;
|
|
12798
|
+
}
|
|
12799
|
+
let parsedOk = 0;
|
|
12800
|
+
let parsedFailed = 0;
|
|
12801
|
+
let corpusBytes = 0;
|
|
12802
|
+
try {
|
|
12803
|
+
const runner = opts.runner ?? ((e, input, options) => run(e, input, options));
|
|
12804
|
+
for (const f of corpus) {
|
|
12805
|
+
corpusBytes += f.text.length;
|
|
12806
|
+
highWater = 0;
|
|
12807
|
+
const r = runner(entryParser, f.text, opts.runOptions ?? {});
|
|
12808
|
+
if (r.ok) parsedOk++;
|
|
12809
|
+
else parsedFailed++;
|
|
12810
|
+
}
|
|
12811
|
+
} finally {
|
|
12812
|
+
undoAll();
|
|
12813
|
+
}
|
|
12814
|
+
const arms = armRecords.filter((a) => a.attempts > 0).map((a) => ({
|
|
12815
|
+
siteKey: a.siteKey,
|
|
12816
|
+
site: a.site,
|
|
12817
|
+
arm: a.arm,
|
|
12818
|
+
label: a.label,
|
|
12819
|
+
attempts: a.attempts,
|
|
12820
|
+
failures: a.failures,
|
|
12821
|
+
wastedBytes: a.wastedBytes,
|
|
12822
|
+
firstCharGated: a.gate !== null,
|
|
12823
|
+
gatedAttempts: a.gatedAttempts,
|
|
12824
|
+
gatedFailures: a.gatedFailures,
|
|
12825
|
+
gatedWastedBytes: a.gatedWastedBytes
|
|
12826
|
+
})).sort((x, y) => y.gatedWastedBytes - x.gatedWastedBytes || y.wastedBytes - x.wastedBytes || (x.siteKey < y.siteKey ? -1 : x.siteKey > y.siteKey ? 1 : 0) || x.arm - y.arm);
|
|
12827
|
+
const sites = [...siteTotals.values()].sort((x, y) => y.gatedWastedBytes - x.gatedWastedBytes || y.wastedBytes - x.wastedBytes || (x.siteKey < y.siteKey ? -1 : x.siteKey > y.siteKey ? 1 : 0));
|
|
12828
|
+
const minAttempts = opts.inversionMinAttempts ?? 4;
|
|
12829
|
+
const inversions = arms.filter((a) => a.gatedAttempts >= minAttempts && a.gatedFailures === a.gatedAttempts && arms.some((b) => b.siteKey === a.siteKey && b.arm > a.arm && b.gatedAttempts > b.gatedFailures)).sort((x, y) => y.gatedAttempts - x.gatedAttempts || y.gatedWastedBytes - x.gatedWastedBytes || (x.siteKey < y.siteKey ? -1 : x.siteKey > y.siteKey ? 1 : 0) || x.arm - y.arm);
|
|
12830
|
+
return {
|
|
12831
|
+
schema: "parseman.wasted-work/1",
|
|
12832
|
+
corpusFiles: corpus.length,
|
|
12833
|
+
corpusBytes,
|
|
12834
|
+
parsedOk,
|
|
12835
|
+
parsedFailed,
|
|
12836
|
+
instrumentedSites,
|
|
12837
|
+
uninstrumentableSites,
|
|
12838
|
+
unresolvedRoots,
|
|
12839
|
+
totalWastedBytes: sites.reduce((n, s) => n + s.wastedBytes, 0),
|
|
12840
|
+
totalGatedWastedBytes: sites.reduce((n, s) => n + s.gatedWastedBytes, 0),
|
|
12841
|
+
arms,
|
|
12842
|
+
inversions,
|
|
12843
|
+
sites
|
|
12844
|
+
};
|
|
12845
|
+
}
|
|
12846
|
+
|
|
12847
|
+
// src/analysis/choice-cost-render.ts
|
|
12848
|
+
function groupDigits(n) {
|
|
12849
|
+
const s = String(Math.trunc(Math.abs(n)));
|
|
12850
|
+
let out = "";
|
|
12851
|
+
for (let i = 0; i < s.length; i++) {
|
|
12852
|
+
if (i > 0 && (s.length - i) % 3 === 0) out += ",";
|
|
12853
|
+
out += s[i];
|
|
12854
|
+
}
|
|
12855
|
+
return (n < 0 ? "-" : "") + out;
|
|
12856
|
+
}
|
|
12857
|
+
function bytes(n) {
|
|
12858
|
+
if (n < 1024) return `${groupDigits(n)} B`;
|
|
12859
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} kB`;
|
|
12860
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
12861
|
+
}
|
|
12862
|
+
var ESC = "\x1B";
|
|
12863
|
+
var ANSI = {
|
|
12864
|
+
dim: `${ESC}[2m`,
|
|
12865
|
+
bold: `${ESC}[1m`,
|
|
12866
|
+
red: `${ESC}[31m`,
|
|
12867
|
+
yellow: `${ESC}[33m`,
|
|
12868
|
+
cyan: `${ESC}[36m`,
|
|
12869
|
+
reset: `${ESC}[0m`
|
|
12870
|
+
};
|
|
12871
|
+
var paint = (on) => (code, s) => on ? `${ANSI[code]}${s}${ANSI.reset}` : s;
|
|
12872
|
+
var pad = (s, w) => s.length >= w ? s : s + " ".repeat(w - s.length);
|
|
12873
|
+
var padStart = (s, w) => s.length >= w ? s : " ".repeat(w - s.length) + s;
|
|
12874
|
+
var ARM_DECLINE_LINE = {
|
|
12875
|
+
"not-a-sequence": "arm is not a sequence, so there is no leading term to lift out",
|
|
12876
|
+
"sequence-shorter-than-2": "arm is a one-term sequence; lifting its only term would leave an empty arm",
|
|
12877
|
+
"lead-case-insensitive-literal": "leading literal is case-insensitive, so the matched text can differ from the literal \u2014 a lifted prefix would carry the wrong string",
|
|
12878
|
+
"lead-not-concrete-terminal": "leading term is not a bare literal or regex; lifting it would change the arm\u2019s value or capture shape"
|
|
12879
|
+
};
|
|
12880
|
+
var SITE_DECLINE_LINE = {
|
|
12881
|
+
"disjoint-dispatch": "first-char dispatch already selects one arm \u2014 nothing is re-scanned",
|
|
12882
|
+
"gated-arms": "an arm carries a runtime gate; per-arm predicates are incompatible with factoring",
|
|
12883
|
+
"strategy-preempted": "a stronger non-backtracking strategy already applies",
|
|
12884
|
+
"fewer-than-two-arms": "fewer than two arms",
|
|
12885
|
+
"arms-not-factorable": "at least one arm cannot contribute a leading term (see below)",
|
|
12886
|
+
"leads-differ": "the arms do not all begin with the same term"
|
|
12887
|
+
};
|
|
12888
|
+
function leftFactorPreview(e) {
|
|
12889
|
+
if (e.factored) return null;
|
|
12890
|
+
if (e.groups.length !== 1) return null;
|
|
12891
|
+
const g = e.groups[0];
|
|
12892
|
+
if (g.members.length !== e.arity) return null;
|
|
12893
|
+
const tails = g.members.map((i) => `sequence(\u2026arm[${i}] tail\u2026)`).join(",\n ");
|
|
12894
|
+
return `sequence(
|
|
12895
|
+
${g.render === "" ? "<prefix>" : g.render.startsWith("/") ? `regex(${g.render})` : `literal(${g.render})`},
|
|
12896
|
+
choice(
|
|
12897
|
+
${tails},
|
|
12898
|
+
),
|
|
12899
|
+
)`;
|
|
12900
|
+
}
|
|
12901
|
+
function renderChoiceInventory(r, opts = {}) {
|
|
12902
|
+
const c = paint(opts.color === true);
|
|
12903
|
+
const limit = opts.limit ?? 20;
|
|
12904
|
+
const out = [];
|
|
12905
|
+
out.push(c("bold", "shared-prefix inventory"));
|
|
12906
|
+
out.push(c("dim", " Static grammar shape only. A site listed here may already cost nothing at runtime:"));
|
|
12907
|
+
out.push(c("dim", " compiled output gates each arm on its first character. Rank with profileWastedWork()."));
|
|
12908
|
+
out.push(` ${groupDigits(r.rules)} rules, ${groupDigits(r.choiceSites)} choice sites`);
|
|
12909
|
+
out.push(` ${groupDigits(r.factoredSites)} left-factored by the compiler`);
|
|
12910
|
+
out.push(` ${groupDigits(r.backlogSites)} sites where alternatives share a leading term and the compiler DECLINED (${groupDigits(r.backlogArms)} arms)`);
|
|
12911
|
+
const backlog = r.entries.filter((e) => !e.factored && e.groups.length > 0 && e.declineReason !== "disjoint-dispatch" && e.declineReason !== "strategy-preempted").sort((a, b) => b.unfactoredArms - a.unfactoredArms || (a.siteKey < b.siteKey ? -1 : 1));
|
|
12912
|
+
if (backlog.length === 0) {
|
|
12913
|
+
out.push("");
|
|
12914
|
+
out.push(" no declined shared prefixes.");
|
|
12915
|
+
return out.join("\n");
|
|
12916
|
+
}
|
|
12917
|
+
out.push("");
|
|
12918
|
+
for (const e of backlog.slice(0, limit)) {
|
|
12919
|
+
out.push(` ${c("cyan", e.siteKey)} ${c("dim", `${e.arity} arms \xB7 ${e.strategy}`)}`);
|
|
12920
|
+
for (const g of e.groups) {
|
|
12921
|
+
out.push(` arms ${g.members.join(", ")} all begin with ${c("bold", g.render)}`);
|
|
12922
|
+
}
|
|
12923
|
+
out.push(` ${c("yellow", "declined")}: ${SITE_DECLINE_LINE[e.declineReason]}`);
|
|
12924
|
+
for (const a of e.armDeclines) {
|
|
12925
|
+
out.push(` arm[${a.arm}] \u2014 ${ARM_DECLINE_LINE[a.reason]}`);
|
|
12926
|
+
}
|
|
12927
|
+
const fix = leftFactorPreview(e);
|
|
12928
|
+
if (fix !== null) {
|
|
12929
|
+
out.push(` ${c("dim", "candidate rewrite (preview \u2014 not applied, not yet digest-verified):")}`);
|
|
12930
|
+
for (const line of fix.split("\n")) out.push(` ${line}`);
|
|
12931
|
+
}
|
|
12932
|
+
out.push("");
|
|
12933
|
+
}
|
|
12934
|
+
if (backlog.length > limit) out.push(` \u2026 and ${groupDigits(backlog.length - limit)} more (the report holds all of them)`);
|
|
12935
|
+
return out.join("\n");
|
|
12936
|
+
}
|
|
12937
|
+
function renderWastedWork(r, opts = {}) {
|
|
12938
|
+
const c = paint(opts.color === true);
|
|
12939
|
+
const limit = opts.limit ?? 20;
|
|
12940
|
+
const out = [];
|
|
12941
|
+
const removed = r.arms.reduce((n, a) => n + (a.attempts - a.gatedAttempts), 0);
|
|
12942
|
+
const totalAttempts = r.arms.reduce((n, a) => n + a.attempts, 0);
|
|
12943
|
+
out.push(c("bold", "wasted work \u2014 input bytes re-scanned after a failed alternative"));
|
|
12944
|
+
out.push(...interpretedBanner(c, removed, totalAttempts));
|
|
12945
|
+
out.push(` corpus: ${groupDigits(r.corpusFiles)} files, ${bytes(r.corpusBytes)} (${groupDigits(r.parsedOk)} parsed, ${groupDigits(r.parsedFailed)} failed)`);
|
|
12946
|
+
out.push(` sites: ${groupDigits(r.instrumentedSites)} instrumented, ${groupDigits(r.uninstrumentableSites)} not instrumentable`);
|
|
12947
|
+
if (r.unresolvedRoots.length > 0) {
|
|
12948
|
+
out.push(` ${c("yellow", "PARTIAL")}: ${groupDigits(r.unresolvedRoots.length)} rule(s) could not be resolved and were NOT walked \u2014 the total below is a lower bound`);
|
|
12949
|
+
}
|
|
12950
|
+
out.push(` total: ${c("bold", bytes(r.totalGatedWastedBytes))} re-scanned` + (r.corpusBytes > 0 ? ` \u2014 ${(r.totalGatedWastedBytes / r.corpusBytes).toFixed(2)}x the corpus` : "") + (r.totalWastedBytes === r.totalGatedWastedBytes ? "" : ` (interpreted: ${bytes(r.totalWastedBytes)})`));
|
|
12951
|
+
const ranked = r.sites.filter((s) => s.gatedWastedBytes > 0 || s.wastedBytes > 0);
|
|
12952
|
+
if (ranked.length === 0) {
|
|
12953
|
+
out.push("");
|
|
12954
|
+
out.push(" no alternative failed on this corpus.");
|
|
12955
|
+
return out.join("\n");
|
|
12956
|
+
}
|
|
12957
|
+
out.push("");
|
|
12958
|
+
out.push(c("dim", ' per arm: "entered" counts what the COMPILED parser reaches; the parenthesised'));
|
|
12959
|
+
out.push(c("dim", " number is the interpreted count, shown only where the first-char gate differs."));
|
|
12960
|
+
out.push("");
|
|
12961
|
+
const w = Math.min(52, Math.max(...ranked.slice(0, limit).map((s) => s.siteKey.length)));
|
|
12962
|
+
for (const s of ranked.slice(0, limit)) {
|
|
12963
|
+
const share = r.totalGatedWastedBytes > 0 ? ` ${padStart((100 * s.gatedWastedBytes / r.totalGatedWastedBytes).toFixed(1) + "%", 7)}` : "";
|
|
12964
|
+
out.push(` ${c("cyan", pad(s.siteKey.slice(0, w), w))} ${padStart(bytes(s.gatedWastedBytes), 10)}${c("dim", share)}`);
|
|
12965
|
+
const arms = r.arms.filter((a) => a.siteKey === s.siteKey).sort((a, b) => a.arm - b.arm);
|
|
12966
|
+
for (const a of arms) {
|
|
12967
|
+
const entered = a.gatedAttempts === a.attempts ? groupDigits(a.attempts) : `${groupDigits(a.gatedAttempts)} (${groupDigits(a.attempts)} interp)`;
|
|
12968
|
+
const verdict = a.gatedAttempts === 0 ? { text: "never entered when compiled", color: "dim" } : a.gatedFailures === a.gatedAttempts ? { text: `failed ALL ${entered}`, color: "red" } : a.gatedFailures === 0 ? { text: `matched ${entered}`, color: "dim" } : { text: `failed ${groupDigits(a.gatedFailures)} / ${entered}`, color: null };
|
|
12969
|
+
const cell = verdict.color === null ? pad(verdict.text, 34) : c(verdict.color, pad(verdict.text, 34));
|
|
12970
|
+
out.push(` ${padStart(String(a.arm), 2)} ${pad(a.label.slice(0, 30), 30)} ${cell} ${a.gatedWastedBytes > 0 ? padStart(bytes(a.gatedWastedBytes), 10) : ""}`);
|
|
12971
|
+
}
|
|
12972
|
+
out.push("");
|
|
12973
|
+
}
|
|
12974
|
+
if (ranked.length > limit) out.push(` \u2026 and ${groupDigits(ranked.length - limit)} more sites (the report holds all of them)`);
|
|
12975
|
+
if (r.inversions.length > 0) {
|
|
12976
|
+
out.push(c("bold", " ordering inversions \u2014 arm failed EVERY compiled entry while a later arm matched"));
|
|
12977
|
+
out.push(c("dim", " ranked by entries, not bytes: an arm can fail 100% of the time and re-scan nothing."));
|
|
12978
|
+
for (const a of r.inversions.slice(0, limit)) {
|
|
12979
|
+
out.push(` ${c("cyan", pad(a.siteKey.slice(0, w), w))} arm ${padStart(String(a.arm), 2)} ${pad(a.label.slice(0, 26), 26)} ${padStart(groupDigits(a.gatedAttempts), 8)} entries ${a.gatedWastedBytes > 0 ? padStart(bytes(a.gatedWastedBytes), 10) : padStart("0 B", 10)}`);
|
|
12980
|
+
}
|
|
12981
|
+
if (r.inversions.length > limit) out.push(` \u2026 and ${groupDigits(r.inversions.length - limit)} more`);
|
|
12982
|
+
}
|
|
12983
|
+
return out.join("\n");
|
|
12984
|
+
}
|
|
12985
|
+
function interpretedBanner(c, removed, total) {
|
|
12986
|
+
const share = total > 0 ? ` \u2014 ${(100 * removed / total).toFixed(0)}% of interpreted arm entries` : "";
|
|
12987
|
+
return [
|
|
12988
|
+
c("yellow", " MEASURED IN THE INTERPRETER. Compiled output gates each arm on its first"),
|
|
12989
|
+
c("yellow", ` character; ${groupDigits(removed)} entries below never happen in the shipped parser${share}.`),
|
|
12990
|
+
c("dim", " Counts shown are the modelled COMPILED ones. Byte totals are unaffected by the"),
|
|
12991
|
+
c("dim", " gate (a gated-out arm consumes nothing); entry counts and failure RATES are not.")
|
|
12992
|
+
];
|
|
12993
|
+
}
|
|
12994
|
+
|
|
12995
|
+
// src/analysis/choice-cost-gate.ts
|
|
12996
|
+
var KIND_ORDER = [
|
|
12997
|
+
"invalid-baseline",
|
|
12998
|
+
"unmeasurable",
|
|
12999
|
+
"ceiling",
|
|
13000
|
+
"drift",
|
|
13001
|
+
"shrank",
|
|
13002
|
+
"inversion",
|
|
13003
|
+
"unbaselined",
|
|
13004
|
+
"stale"
|
|
13005
|
+
];
|
|
13006
|
+
var pct = (actual, base) => base === 0 ? actual === 0 ? 0 : Infinity : (actual / base - 1) * 100;
|
|
13007
|
+
function checkWastedWork(reports, baseline, policy = {}) {
|
|
13008
|
+
const breaches = [];
|
|
13009
|
+
const tolerance = policy.driftTolerancePct ?? 1;
|
|
13010
|
+
const add = (kind, key, detail) => {
|
|
13011
|
+
breaches.push({ kind, key, detail });
|
|
13012
|
+
};
|
|
13013
|
+
const finish = (checkedCorpora, checkedSites2) => {
|
|
13014
|
+
breaches.sort((a, b2) => KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b2.kind) || (a.key < b2.key ? -1 : a.key > b2.key ? 1 : 0));
|
|
13015
|
+
return {
|
|
13016
|
+
schema: "parseman.wasted-work-verdict/1",
|
|
13017
|
+
ok: breaches.length === 0,
|
|
13018
|
+
breaches,
|
|
13019
|
+
checkedCorpora,
|
|
13020
|
+
checkedSites: checkedSites2
|
|
13021
|
+
};
|
|
13022
|
+
};
|
|
13023
|
+
const b = baseline;
|
|
13024
|
+
if (b === null || b === void 0 || typeof b !== "object") {
|
|
13025
|
+
add("invalid-baseline", "<baseline>", "no baseline supplied \u2014 a gate with no baseline measures nothing, and passing here is how a budget stops being enforced");
|
|
13026
|
+
return finish(0, 0);
|
|
13027
|
+
}
|
|
13028
|
+
if (b.schema !== "parseman.wasted-work-baseline/1") {
|
|
13029
|
+
add("invalid-baseline", "<baseline>", `baseline schema is ${JSON.stringify(b.schema)}, expected "parseman.wasted-work-baseline/1"`);
|
|
13030
|
+
return finish(0, 0);
|
|
13031
|
+
}
|
|
13032
|
+
const isRecord = (v2) => typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
|
|
13033
|
+
if (!isRecord(b.totals) || !isRecord(b.sites)) {
|
|
13034
|
+
add("invalid-baseline", "<baseline>", "baseline `totals` or `sites` is missing or is not an object");
|
|
13035
|
+
return finish(0, 0);
|
|
13036
|
+
}
|
|
13037
|
+
if (Object.keys(b.totals).length === 0) {
|
|
13038
|
+
add("invalid-baseline", "<baseline>", "baseline records ZERO corpora");
|
|
13039
|
+
return finish(0, 0);
|
|
13040
|
+
}
|
|
13041
|
+
const finite = (v2) => typeof v2 === "number" && Number.isFinite(v2);
|
|
13042
|
+
for (const [id, t] of Object.entries(b.totals).sort(([x], [y]) => x < y ? -1 : 1)) {
|
|
13043
|
+
if (!isRecord(t) || !finite(t.corpusBytes) || !finite(t.totalWastedBytes)) {
|
|
13044
|
+
add("invalid-baseline", id, "baseline entry has no finite numeric `corpusBytes` / `totalWastedBytes` \u2014 the comparison against it would silently produce NaN and no breach");
|
|
13045
|
+
}
|
|
13046
|
+
}
|
|
13047
|
+
for (const [key, v2] of Object.entries(b.sites).sort(([x], [y]) => x < y ? -1 : 1)) {
|
|
13048
|
+
if (!finite(v2)) add("invalid-baseline", key, "baseline site value is not a finite number \u2014 the comparison against it would silently produce NaN and no breach");
|
|
13049
|
+
}
|
|
13050
|
+
if (breaches.length > 0) return finish(0, 0);
|
|
13051
|
+
if (policy.ceilingRatio !== void 0) {
|
|
13052
|
+
for (const [id, t] of Object.entries(b.totals).sort(([x], [y]) => x < y ? -1 : 1)) {
|
|
13053
|
+
if (t.corpusBytes > 0 && t.totalWastedBytes / t.corpusBytes > policy.ceilingRatio) {
|
|
13054
|
+
add(
|
|
13055
|
+
"invalid-baseline",
|
|
13056
|
+
id,
|
|
13057
|
+
`baseline records ${t.totalWastedBytes} wasted / ${t.corpusBytes} corpus bytes = ${(t.totalWastedBytes / t.corpusBytes).toFixed(3)}x, above the ${policy.ceilingRatio}x ceiling \u2014 a rebaseline cannot accept an over-ceiling number`
|
|
13058
|
+
);
|
|
13059
|
+
}
|
|
13060
|
+
}
|
|
13061
|
+
if (breaches.length > 0) return finish(0, 0);
|
|
13062
|
+
}
|
|
13063
|
+
if (Object.keys(reports).length === 0) {
|
|
13064
|
+
add("unmeasurable", "<reports>", "no corpus was measured \u2014 refusing to report a pass on an empty measurement");
|
|
13065
|
+
return finish(0, 0);
|
|
13066
|
+
}
|
|
13067
|
+
let checkedSites = 0;
|
|
13068
|
+
for (const id of Object.keys(reports).sort()) {
|
|
13069
|
+
const r = reports[id];
|
|
13070
|
+
if (r.corpusFiles === 0) {
|
|
13071
|
+
add("unmeasurable", id, "report covers ZERO corpus files");
|
|
13072
|
+
continue;
|
|
13073
|
+
}
|
|
13074
|
+
if (r.instrumentedSites === 0) {
|
|
13075
|
+
add("unmeasurable", id, "report instrumented ZERO choice sites \u2014 the total below would be zero for a reason unrelated to the grammar");
|
|
13076
|
+
continue;
|
|
13077
|
+
}
|
|
13078
|
+
if (r.unresolvedRoots.length > 0) {
|
|
13079
|
+
add(
|
|
13080
|
+
"unmeasurable",
|
|
13081
|
+
id,
|
|
13082
|
+
`${r.unresolvedRoots.length} rule(s) could not be resolved and were NOT walked (${r.unresolvedRoots.slice(0, 4).join(", ")}${r.unresolvedRoots.length > 4 ? ", \u2026" : ""}) \u2014 the total is a lower bound over an unknown fraction of the grammar`
|
|
13083
|
+
);
|
|
13084
|
+
continue;
|
|
13085
|
+
}
|
|
13086
|
+
const base = b.totals[id];
|
|
13087
|
+
if (base === void 0) {
|
|
13088
|
+
add("unbaselined", id, `measured but absent from the baseline (${r.totalWastedBytes} wasted bytes over ${r.corpusBytes})`);
|
|
13089
|
+
continue;
|
|
13090
|
+
}
|
|
13091
|
+
if (policy.ceilingRatio !== void 0 && r.corpusBytes > 0) {
|
|
13092
|
+
const ratio = r.totalWastedBytes / r.corpusBytes;
|
|
13093
|
+
if (ratio > policy.ceilingRatio) {
|
|
13094
|
+
add("ceiling", id, `${r.totalWastedBytes} wasted / ${r.corpusBytes} corpus bytes = ${ratio.toFixed(3)}x, over the ${policy.ceilingRatio}x ceiling`);
|
|
13095
|
+
}
|
|
13096
|
+
}
|
|
13097
|
+
if (r.corpusBytes !== base.corpusBytes) {
|
|
13098
|
+
add("unmeasurable", id, `corpus changed: baseline ${base.corpusBytes} bytes, measured ${r.corpusBytes} \u2014 totals are not comparable until the baseline is refreshed`);
|
|
13099
|
+
continue;
|
|
13100
|
+
}
|
|
13101
|
+
const d = pct(r.totalWastedBytes, base.totalWastedBytes);
|
|
13102
|
+
if (d > tolerance) {
|
|
13103
|
+
add("drift", id, `total wasted ${base.totalWastedBytes} -> ${r.totalWastedBytes} bytes (${d >= 0 ? "+" : ""}${d.toFixed(2)}%, band \xB1${tolerance}%)`);
|
|
13104
|
+
} else if (d < -tolerance) {
|
|
13105
|
+
add("shrank", id, `total wasted ${base.totalWastedBytes} -> ${r.totalWastedBytes} bytes (${d.toFixed(2)}%, band \xB1${tolerance}%) \u2014 BANK THE WIN: rebaseline so the ${base.totalWastedBytes - r.totalWastedBytes} bytes you just saved cannot become headroom for the next regression`);
|
|
13106
|
+
}
|
|
13107
|
+
if (r.totalGatedWastedBytes !== r.totalWastedBytes) {
|
|
13108
|
+
const dg = pct(r.totalGatedWastedBytes, base.totalWastedBytes);
|
|
13109
|
+
if (dg > tolerance) {
|
|
13110
|
+
add("drift", id, `compiled-model total wasted ${base.totalWastedBytes} -> ${r.totalGatedWastedBytes} bytes (${dg >= 0 ? "+" : ""}${dg.toFixed(2)}%, band \xB1${tolerance}%) \u2014 the compiled and interpreted columns have PARTED (${r.totalGatedWastedBytes} vs ${r.totalWastedBytes}); the baseline records the interpreted one`);
|
|
13111
|
+
} else if (dg < -tolerance) {
|
|
13112
|
+
add("shrank", id, `compiled-model total wasted ${base.totalWastedBytes} -> ${r.totalGatedWastedBytes} bytes (${dg.toFixed(2)}%, band \xB1${tolerance}%) \u2014 the compiled and interpreted columns have PARTED (${r.totalGatedWastedBytes} vs ${r.totalWastedBytes}); the baseline records the interpreted one`);
|
|
13113
|
+
}
|
|
13114
|
+
}
|
|
13115
|
+
for (const s of r.sites) {
|
|
13116
|
+
const key = `${id}::${s.siteKey}`;
|
|
13117
|
+
const bs = b.sites[key];
|
|
13118
|
+
if (bs === void 0) {
|
|
13119
|
+
if (s.wastedBytes > 0) add("unbaselined", key, `site measured ${s.wastedBytes} wasted bytes but has no baseline entry`);
|
|
13120
|
+
continue;
|
|
13121
|
+
}
|
|
13122
|
+
checkedSites++;
|
|
13123
|
+
const sd = pct(s.wastedBytes, bs);
|
|
13124
|
+
if (sd > tolerance) add("drift", key, `${bs} -> ${s.wastedBytes} bytes (${sd >= 0 ? "+" : ""}${sd.toFixed(2)}%, band \xB1${tolerance}%)`);
|
|
13125
|
+
else if (sd < -tolerance) add("shrank", key, `${bs} -> ${s.wastedBytes} bytes (${sd.toFixed(2)}%, band \xB1${tolerance}%) \u2014 BANK THE WIN: rebaseline, or this site's ${bs - s.wastedBytes} recovered bytes become budget`);
|
|
13126
|
+
if (s.gatedWastedBytes !== s.wastedBytes) {
|
|
13127
|
+
const sg = pct(s.gatedWastedBytes, bs);
|
|
13128
|
+
if (sg > tolerance) add("drift", key, `compiled-model ${bs} -> ${s.gatedWastedBytes} bytes (${sg >= 0 ? "+" : ""}${sg.toFixed(2)}%, band \xB1${tolerance}%) \u2014 columns PARTED (${s.gatedWastedBytes} vs ${s.wastedBytes})`);
|
|
13129
|
+
else if (sg < -tolerance) add("shrank", key, `compiled-model ${bs} -> ${s.gatedWastedBytes} bytes (${sg.toFixed(2)}%, band \xB1${tolerance}%) \u2014 columns PARTED (${s.gatedWastedBytes} vs ${s.wastedBytes})`);
|
|
13130
|
+
}
|
|
13131
|
+
}
|
|
13132
|
+
if (policy.failOnInversions === true) {
|
|
13133
|
+
for (const inv of r.inversions) {
|
|
13134
|
+
add(
|
|
13135
|
+
"inversion",
|
|
13136
|
+
`${id}::${inv.siteKey}#${inv.arm}`,
|
|
13137
|
+
// The GATED columns, because that is what `inversions` was computed from
|
|
13138
|
+
// (choice-cost.ts filters on `gatedAttempts`/`gatedFailures`). Printing the
|
|
13139
|
+
// interpreted counts here would state "failed all N" with an N the condition
|
|
13140
|
+
// never tested — the two differ wherever `firstCharGated` is true.
|
|
13141
|
+
`arm ${inv.arm} (${inv.label}) failed all ${inv.gatedAttempts} compiled entries while a later arm matched; ${inv.gatedWastedBytes} bytes re-scanned`
|
|
13142
|
+
);
|
|
13143
|
+
}
|
|
13144
|
+
}
|
|
13145
|
+
}
|
|
13146
|
+
const measuredKeys = /* @__PURE__ */ new Set();
|
|
13147
|
+
for (const id of Object.keys(reports)) for (const s of reports[id].sites) measuredKeys.add(`${id}::${s.siteKey}`);
|
|
13148
|
+
for (const key of Object.keys(b.sites).sort()) {
|
|
13149
|
+
if (!measuredKeys.has(key)) {
|
|
13150
|
+
add("stale", key, "baselined site matches no measured site \u2014 a rule was renamed or removed, and ignoring it silently shrinks the gated set");
|
|
13151
|
+
}
|
|
13152
|
+
}
|
|
13153
|
+
for (const id of Object.keys(b.totals).sort()) {
|
|
13154
|
+
if (reports[id] === void 0) add("stale", id, "baselined corpus was not measured");
|
|
13155
|
+
}
|
|
13156
|
+
return finish(Object.keys(reports).length, checkedSites);
|
|
13157
|
+
}
|
|
13158
|
+
function buildWastedWorkBaseline(reports, meta) {
|
|
13159
|
+
const totals = {};
|
|
13160
|
+
const sites = {};
|
|
13161
|
+
for (const id of Object.keys(reports).sort()) {
|
|
13162
|
+
const r = reports[id];
|
|
13163
|
+
totals[id] = { corpusBytes: r.corpusBytes, totalWastedBytes: r.totalWastedBytes, instrumentedSites: r.instrumentedSites };
|
|
13164
|
+
for (const s of [...r.sites].sort((a, c) => a.siteKey < c.siteKey ? -1 : 1)) {
|
|
13165
|
+
if (s.wastedBytes > 0) sites[`${id}::${s.siteKey}`] = s.wastedBytes;
|
|
13166
|
+
}
|
|
13167
|
+
}
|
|
13168
|
+
return { schema: "parseman.wasted-work-baseline/1", gitRev: meta.gitRev, updatedAt: meta.updatedAt, totals, sites };
|
|
13169
|
+
}
|
|
13170
|
+
|
|
13171
|
+
// src/combinators/gate.ts
|
|
13172
|
+
function gate(predicate) {
|
|
13173
|
+
const meta = {
|
|
13174
|
+
firstSet: { kind: "any" },
|
|
13175
|
+
canMatchNewline: false,
|
|
11619
13176
|
isTrivia: false
|
|
11620
13177
|
};
|
|
11621
13178
|
return {
|
|
@@ -11630,9 +13187,6 @@ function gate(predicate) {
|
|
|
11630
13187
|
};
|
|
11631
13188
|
}
|
|
11632
13189
|
|
|
11633
|
-
// src/combinators/guard.ts
|
|
11634
|
-
var guard = gate;
|
|
11635
|
-
|
|
11636
13190
|
// src/combinators/completions.ts
|
|
11637
13191
|
function completionsAt(target, input, offset, options = {}) {
|
|
11638
13192
|
const probe = { offset, best: null };
|
|
@@ -11859,9 +13413,9 @@ function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build, to
|
|
|
11859
13413
|
const ctx = mkCtx(null, build, tolerant);
|
|
11860
13414
|
const out = [];
|
|
11861
13415
|
let pos = reStart;
|
|
11862
|
-
let
|
|
13416
|
+
let guard = 0;
|
|
11863
13417
|
while (pos < reEnd) {
|
|
11864
|
-
if (++
|
|
13418
|
+
if (++guard > reEnd - reStart + 2) return null;
|
|
11865
13419
|
if (separator && out.length > 0 && out[out.length - 1]._tag === "node") {
|
|
11866
13420
|
if (pos + separator.length > reEnd || !input.startsWith(separator, pos)) return null;
|
|
11867
13421
|
out.push({ _tag: "leaf", value: separator, span: { start: pos - cStart, end: pos + separator.length - cStart } });
|
|
@@ -12153,142 +13707,6 @@ function parseDoc(registry, rootRule, input, opts = {}) {
|
|
|
12153
13707
|
return new ParseDocImpl(registry, rootRule, opts, { abs: null }, [fail], null, input);
|
|
12154
13708
|
}
|
|
12155
13709
|
|
|
12156
|
-
// src/functional/run.ts
|
|
12157
|
-
var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
|
|
12158
|
-
function triviaKindLabelsFromRunnable(r) {
|
|
12159
|
-
if (!r) return void 0;
|
|
12160
|
-
if (typeof r === "function") return r._meta?.triviaKindLabels;
|
|
12161
|
-
const grammarTrivia = r._meta.grammarTrivia;
|
|
12162
|
-
return grammarTrivia?._meta.triviaKindLabels ?? r._meta.triviaKindLabels ?? (r._def.tag === "grammar" ? r._def.triviaParser?._meta.triviaKindLabels : void 0);
|
|
12163
|
-
}
|
|
12164
|
-
function rootTriviaClassifiedFromRunnable(r) {
|
|
12165
|
-
if (!r) return false;
|
|
12166
|
-
if (typeof r === "function") return r._meta?.rootTriviaClassified === true;
|
|
12167
|
-
const grammarTrivia = r._meta.grammarTrivia;
|
|
12168
|
-
return grammarTrivia?._meta.rootTriviaClassified === true || r._meta.rootTriviaClassified === true;
|
|
12169
|
-
}
|
|
12170
|
-
function makeSelectedRootLabelIndex(labels) {
|
|
12171
|
-
const index = /* @__PURE__ */ Object.create(null);
|
|
12172
|
-
for (let i = 0; i < labels.length; i++) {
|
|
12173
|
-
if (index[labels[i]] === void 0) index[labels[i]] = i;
|
|
12174
|
-
}
|
|
12175
|
-
return index;
|
|
12176
|
-
}
|
|
12177
|
-
function runOnce(entry, input, options, phase, profileState) {
|
|
12178
|
-
if (typeof entry !== "function" && typeof entry?.parse !== "function") {
|
|
12179
|
-
throw new TypeError(
|
|
12180
|
-
`run(): start production is ${entry === null ? "null" : typeof entry}, not a rule \u2014 the requested grammar rule does not exist (check the rule name).`
|
|
12181
|
-
);
|
|
12182
|
-
}
|
|
12183
|
-
const rootTriviaSelection = options.rootTrivia?.select;
|
|
12184
|
-
const captureRootTrivia = rootTriviaSelection !== void 0 && rootTriviaSelection.length > 0;
|
|
12185
|
-
const selectedRootLog = captureRootTrivia ? [] : void 0;
|
|
12186
|
-
const selectedRootLabelIndex = captureRootTrivia ? makeSelectedRootLabelIndex(rootTriviaSelection) : void 0;
|
|
12187
|
-
const errors = [];
|
|
12188
|
-
const profile = profileState ?? (phase === void 0 ? void 0 : { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 });
|
|
12189
|
-
const skipGlobalSinks = phase === "recognizer" || phase === "capture";
|
|
12190
|
-
const grammarTrivia = typeof entry !== "function" ? entry._meta.grammarTrivia : void 0;
|
|
12191
|
-
const grammarScanSkip = typeof entry !== "function" ? entry._meta.grammarScanSkip : void 0;
|
|
12192
|
-
const triviaKindLabels = triviaKindLabelsFromRunnable(entry) ?? triviaKindLabelsFromRunnable(options.trivia);
|
|
12193
|
-
if (captureRootTrivia && triviaKindLabels === void 0) {
|
|
12194
|
-
throw new TypeError("run(): rootTrivia.select requires labeled grammar trivia.");
|
|
12195
|
-
}
|
|
12196
|
-
if (captureRootTrivia) {
|
|
12197
|
-
for (const label2 of rootTriviaSelection) {
|
|
12198
|
-
if (!triviaKindLabels.includes(label2)) {
|
|
12199
|
-
throw new TypeError(`run(): rootTrivia.select contains unknown trivia label ${JSON.stringify(label2)}.`);
|
|
12200
|
-
}
|
|
12201
|
-
}
|
|
12202
|
-
}
|
|
12203
|
-
if (captureRootTrivia && !rootTriviaClassifiedFromRunnable(entry) && !rootTriviaClassifiedFromRunnable(options.trivia)) {
|
|
12204
|
-
throw new TypeError(
|
|
12205
|
-
"run(): rootTrivia.select requires classifiedTrivia() at the root."
|
|
12206
|
-
);
|
|
12207
|
-
}
|
|
12208
|
-
const stamped = entry;
|
|
12209
|
-
assertHostModeCompatible(
|
|
12210
|
-
typeof entry === "function" ? stamped[FUSED_HOST_MODE] ?? "ast" : entry._meta.grammarHostMode ?? "ast",
|
|
12211
|
-
options.build,
|
|
12212
|
-
typeof entry === "function" && stamped[FUSED_HOST_ELIDED] === true
|
|
12213
|
-
);
|
|
12214
|
-
const ctx = {
|
|
12215
|
-
trackLines: false,
|
|
12216
|
-
...skipGlobalSinks ? { _pmProfile: profile } : {
|
|
12217
|
-
...captureRootTrivia ? {
|
|
12218
|
-
_rootTriviaLog: selectedRootLog,
|
|
12219
|
-
_rootTriviaKindIndex: selectedRootLabelIndex,
|
|
12220
|
-
_rootTriviaStrictScopes: true
|
|
12221
|
-
} : {},
|
|
12222
|
-
_errors: errors,
|
|
12223
|
-
...profile === void 0 ? {} : { _pmProfile: profile }
|
|
12224
|
-
},
|
|
12225
|
-
build: options.build,
|
|
12226
|
-
state: options.state,
|
|
12227
|
-
...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...triviaKindLabels ? { triviaKindLabels } : {} } : triviaKindLabels ? { triviaKindLabels } : {},
|
|
12228
|
-
...grammarScanSkip !== void 0 ? { scanSkip: grammarScanSkip } : {},
|
|
12229
|
-
...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {},
|
|
12230
|
-
...options.tolerant ? { _tolerant: true, _rec: REC } : {},
|
|
12231
|
-
...options.instrumentation === void 0 ? {} : options.instrumentation
|
|
12232
|
-
};
|
|
12233
|
-
const r = invoke(entry, input, 0, ctx);
|
|
12234
|
-
let unconsumedFrom = null;
|
|
12235
|
-
if (r.ok) {
|
|
12236
|
-
let pos = r.span?.end ?? 0;
|
|
12237
|
-
if (options.trivia && pos < input.length) {
|
|
12238
|
-
const t = invoke(options.trivia, input, pos, { trackLines: false });
|
|
12239
|
-
if (t.ok && t.span.end > pos) pos = t.span.end;
|
|
12240
|
-
}
|
|
12241
|
-
unconsumedFrom = pos < input.length ? pos : null;
|
|
12242
|
-
}
|
|
12243
|
-
return {
|
|
12244
|
-
ok: r.ok,
|
|
12245
|
-
value: r.ok ? r.value : void 0,
|
|
12246
|
-
span: r.span ?? { start: 0, end: 0 },
|
|
12247
|
-
expected: r.ok ? [] : r.expected ?? [],
|
|
12248
|
-
errors,
|
|
12249
|
-
...selectedRootLog !== void 0 && selectedRootLog.length > 0 ? {
|
|
12250
|
-
rootTrivia: {
|
|
12251
|
-
rows: selectedRootLog,
|
|
12252
|
-
select: rootTriviaSelection,
|
|
12253
|
-
index: buildRootTriviaIndex(selectedRootLog, rootTriviaSelection)
|
|
12254
|
-
}
|
|
12255
|
-
} : {},
|
|
12256
|
-
unconsumedFrom
|
|
12257
|
-
};
|
|
12258
|
-
}
|
|
12259
|
-
function clonePassState(state) {
|
|
12260
|
-
if (state === null || typeof state !== "object") return state;
|
|
12261
|
-
return Array.isArray(state) ? [...state] : { ...state };
|
|
12262
|
-
}
|
|
12263
|
-
function profilePass(entry, input, options, phase) {
|
|
12264
|
-
const state = { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 };
|
|
12265
|
-
const passOptions = options.state === void 0 ? options : { ...options, state: clonePassState(options.state) };
|
|
12266
|
-
const start = performance.now();
|
|
12267
|
-
const result = runOnce(entry, input, passOptions, phase, state);
|
|
12268
|
-
const { phase: _phase, ...counts } = state;
|
|
12269
|
-
return { result, profile: { ms: performance.now() - start, ...counts } };
|
|
12270
|
-
}
|
|
12271
|
-
function run(entry, input, options = {}) {
|
|
12272
|
-
if (!options.profile) return runOnce(entry, input, options);
|
|
12273
|
-
if (typeof entry !== "function") {
|
|
12274
|
-
throw new TypeError("run({ profile: true }) requires a compiled parser entry");
|
|
12275
|
-
}
|
|
12276
|
-
const recognizer = profilePass(entry, input, options, "recognizer");
|
|
12277
|
-
const capture = profilePass(entry, input, options, "capture");
|
|
12278
|
-
const host = profilePass(entry, input, options, "host");
|
|
12279
|
-
if (recognizer.result.ok !== host.result.ok || capture.result.ok !== host.result.ok || recognizer.result.unconsumedFrom !== host.result.unconsumedFrom || capture.result.unconsumedFrom !== host.result.unconsumedFrom) {
|
|
12280
|
-
throw new Error("run({ profile: true }) changed recognition; the grammar is not profile-safe");
|
|
12281
|
-
}
|
|
12282
|
-
return {
|
|
12283
|
-
...host.result,
|
|
12284
|
-
profile: {
|
|
12285
|
-
recognizer: recognizer.profile,
|
|
12286
|
-
structuralCapture: capture.profile,
|
|
12287
|
-
hostConstruction: host.profile
|
|
12288
|
-
}
|
|
12289
|
-
};
|
|
12290
|
-
}
|
|
12291
|
-
|
|
12292
13710
|
// src/coverage.ts
|
|
12293
13711
|
function dispatchMatcher(entry) {
|
|
12294
13712
|
switch (entry.kind) {
|
|
@@ -12303,7 +13721,7 @@ function dispatchMatcher(entry) {
|
|
|
12303
13721
|
var GRAMMAR_COVERAGE_DEFINITIONS = /* @__PURE__ */ Symbol.for("parseman.grammarCoverageDefinitions");
|
|
12304
13722
|
function compiledGrammarCoverageDefinitions(grammar) {
|
|
12305
13723
|
const definitions = grammar[GRAMMAR_COVERAGE_DEFINITIONS];
|
|
12306
|
-
if (!Array.isArray(definitions) || !definitions.every((definition) => typeof definition === "object" && definition !== null && typeof definition.id === "string" && (definition.kind === "rule" || definition.kind === "choice-arm" || definition.kind === "dispatch-arm" || definition.kind === "label"))) {
|
|
13724
|
+
if (!Array.isArray(definitions) || definitions.length === 0 || !definitions.every((definition) => typeof definition === "object" && definition !== null && typeof definition.id === "string" && (definition.kind === "rule" || definition.kind === "choice-arm" || definition.kind === "dispatch-arm" || definition.kind === "label"))) {
|
|
12307
13725
|
throw new TypeError("grammar has no coverage definitions; enable grammarCoverage for this macro build");
|
|
12308
13726
|
}
|
|
12309
13727
|
return definitions;
|
|
@@ -12357,7 +13775,13 @@ function createGrammarCoverageCollector(definitions) {
|
|
|
12357
13775
|
snapshot() {
|
|
12358
13776
|
const hitList = [...hits].sort();
|
|
12359
13777
|
const unhit = ordered.map((definition) => definition.id).filter((id) => !hits.has(id));
|
|
12360
|
-
return {
|
|
13778
|
+
return {
|
|
13779
|
+
definitions: ordered,
|
|
13780
|
+
hits: hitList,
|
|
13781
|
+
unhit,
|
|
13782
|
+
ratio: hitList.length / ordered.length,
|
|
13783
|
+
measurable: ordered.length > 0
|
|
13784
|
+
};
|
|
12361
13785
|
},
|
|
12362
13786
|
reset() {
|
|
12363
13787
|
hits.clear();
|
|
@@ -12677,13 +14101,13 @@ function buildTriviaIndex(root, input = "", opts) {
|
|
|
12677
14101
|
}
|
|
12678
14102
|
|
|
12679
14103
|
// src/cst/walk.ts
|
|
12680
|
-
function
|
|
14104
|
+
function isCombinator2(value) {
|
|
12681
14105
|
return !!value && typeof value === "object" && "_def" in value && "_meta" in value && typeof value.parse === "function";
|
|
12682
14106
|
}
|
|
12683
14107
|
function reflectionFor(grammar) {
|
|
12684
14108
|
const stamped = grammarReflectionOf(grammar);
|
|
12685
14109
|
if (stamped) return stamped;
|
|
12686
|
-
const entries = Object.entries(grammar).filter((entry) =>
|
|
14110
|
+
const entries = Object.entries(grammar).filter((entry) => isCombinator2(entry[1]));
|
|
12687
14111
|
return collectGrammarReflection(entries);
|
|
12688
14112
|
}
|
|
12689
14113
|
function createVisitor(grammar, spec) {
|
|
@@ -12721,206 +14145,16 @@ function createVisitor(grammar, spec) {
|
|
|
12721
14145
|
};
|
|
12722
14146
|
}
|
|
12723
14147
|
|
|
12724
|
-
// src/cst/offset-model.ts
|
|
12725
|
-
var { isFinite } = Number;
|
|
12726
|
-
function floorIndex(arr, len, x) {
|
|
12727
|
-
let lo = 0;
|
|
12728
|
-
let hi = len - 1;
|
|
12729
|
-
let ans = -1;
|
|
12730
|
-
while (lo <= hi) {
|
|
12731
|
-
const mid = lo + hi >>> 1;
|
|
12732
|
-
if (arr[mid] <= x) {
|
|
12733
|
-
ans = mid;
|
|
12734
|
-
lo = mid + 1;
|
|
12735
|
-
} else {
|
|
12736
|
-
hi = mid - 1;
|
|
12737
|
-
}
|
|
12738
|
-
}
|
|
12739
|
-
return ans;
|
|
12740
|
-
}
|
|
12741
|
-
var OffsetIndex = class {
|
|
12742
|
-
/** Slot starts, sorted ascending. Parallel to `_ends`. */
|
|
12743
|
-
_starts;
|
|
12744
|
-
_ends;
|
|
12745
|
-
count;
|
|
12746
|
-
docStart;
|
|
12747
|
-
docEnd;
|
|
12748
|
-
constructor(slots, docStart = 0, docEnd) {
|
|
12749
|
-
const sorted = [...slots].sort((a, b) => a.start - b.start);
|
|
12750
|
-
const starts = [];
|
|
12751
|
-
const ends = [];
|
|
12752
|
-
let prevEnd = docStart;
|
|
12753
|
-
for (const s of sorted) {
|
|
12754
|
-
if (!isFinite(s.start) || !isFinite(s.end) || s.end < s.start) {
|
|
12755
|
-
throw new RangeError(`OffsetIndex: invalid slot [${s.start}, ${s.end}]`);
|
|
12756
|
-
}
|
|
12757
|
-
if (s.start < prevEnd) {
|
|
12758
|
-
throw new RangeError(`OffsetIndex: overlapping/out-of-order slot [${s.start}, ${s.end}] after end ${prevEnd}`);
|
|
12759
|
-
}
|
|
12760
|
-
starts.push(s.start);
|
|
12761
|
-
ends.push(s.end);
|
|
12762
|
-
prevEnd = s.end;
|
|
12763
|
-
}
|
|
12764
|
-
this._starts = starts;
|
|
12765
|
-
this._ends = ends;
|
|
12766
|
-
this.count = starts.length;
|
|
12767
|
-
this.docStart = docStart;
|
|
12768
|
-
this.docEnd = docEnd ?? (this.count ? Math.max(prevEnd, ends[ends.length - 1]) : docStart);
|
|
12769
|
-
}
|
|
12770
|
-
/** The slot at index `i` (0-based, source order), or undefined. */
|
|
12771
|
-
slot(i) {
|
|
12772
|
-
if (i < 0 || i >= this.count) return void 0;
|
|
12773
|
-
return { start: this._starts[i], end: this._ends[i] };
|
|
12774
|
-
}
|
|
12775
|
-
/**
|
|
12776
|
-
* Index of the gap that CONTAINS `offset`, in `[0, count]`. Gap `i` spans
|
|
12777
|
-
* `[endOf(i-1), startOf(i))` where `endOf(-1)=docStart` and `startOf(count)=docEnd`.
|
|
12778
|
-
* An offset that lands inside a slot returns that slot's TRAILING gap index
|
|
12779
|
-
* (i.e. the gap after it) — callers querying at slot boundaries get the
|
|
12780
|
-
* intuitive answer via `gapBefore`/`gapAfter`.
|
|
12781
|
-
*/
|
|
12782
|
-
gapIndexAt(offset) {
|
|
12783
|
-
const i = floorIndex(this._starts, this.count, offset);
|
|
12784
|
-
if (i < 0) return 0;
|
|
12785
|
-
if (offset < this._ends[i]) return i + 1;
|
|
12786
|
-
return i + 1;
|
|
12787
|
-
}
|
|
12788
|
-
/** The gap at index `i` in `[0, count]`. */
|
|
12789
|
-
gap(i) {
|
|
12790
|
-
if (i < 0 || i > this.count) return void 0;
|
|
12791
|
-
const start = i === 0 ? this.docStart : this._ends[i - 1];
|
|
12792
|
-
const end = i === this.count ? this.docEnd : this._starts[i];
|
|
12793
|
-
return { start, end };
|
|
12794
|
-
}
|
|
12795
|
-
/**
|
|
12796
|
-
* The trivia gap immediately BEFORE `offset` (`[prevSlotEnd, offset)`), where
|
|
12797
|
-
* `prevSlotEnd` is the end of the last slot ending at or before `offset` (or
|
|
12798
|
-
* `docStart`). For a real slot start this is the gap preceding that slot; for an
|
|
12799
|
-
* adjacent slot the gap is empty and agrees with `gapAfter` on the same seam.
|
|
12800
|
-
*/
|
|
12801
|
-
gapBefore(offset) {
|
|
12802
|
-
const e = floorIndex(this._ends, this.count, offset);
|
|
12803
|
-
const start = e >= 0 ? this._ends[e] : this.docStart;
|
|
12804
|
-
return { start, end: offset };
|
|
12805
|
-
}
|
|
12806
|
-
/**
|
|
12807
|
-
* The trivia gap immediately AFTER `offset` (`[offset, nextSlotStart)`), where
|
|
12808
|
-
* `nextSlotStart` is the least slot start at or after `offset` (or `docEnd`).
|
|
12809
|
-
*/
|
|
12810
|
-
gapAfter(offset) {
|
|
12811
|
-
return { start: offset, end: this._ceilStart(offset) };
|
|
12812
|
-
}
|
|
12813
|
-
/** Least slot start `>= offset`, else `docEnd`. */
|
|
12814
|
-
_ceilStart(offset) {
|
|
12815
|
-
let lo = 0;
|
|
12816
|
-
let hi = this.count - 1;
|
|
12817
|
-
let ans = this.docEnd;
|
|
12818
|
-
while (lo <= hi) {
|
|
12819
|
-
const mid = lo + hi >>> 1;
|
|
12820
|
-
if (this._starts[mid] >= offset) {
|
|
12821
|
-
ans = this._starts[mid];
|
|
12822
|
-
hi = mid - 1;
|
|
12823
|
-
} else {
|
|
12824
|
-
lo = mid + 1;
|
|
12825
|
-
}
|
|
12826
|
-
}
|
|
12827
|
-
return ans;
|
|
12828
|
-
}
|
|
12829
|
-
/** Is this gap empty (adjacent slots, no trivia)? */
|
|
12830
|
-
isEmpty(gap) {
|
|
12831
|
-
return gap.end <= gap.start;
|
|
12832
|
-
}
|
|
12833
|
-
};
|
|
12834
|
-
var NL = 10;
|
|
12835
|
-
var CR = 13;
|
|
12836
|
-
var SP = 32;
|
|
12837
|
-
var TAB = 9;
|
|
12838
|
-
function gapText(source, gap) {
|
|
12839
|
-
return gap.end > gap.start ? source.slice(gap.start, gap.end) : "";
|
|
12840
|
-
}
|
|
12841
|
-
function lineBreaksIn(source, gap) {
|
|
12842
|
-
let n = 0;
|
|
12843
|
-
for (let i = gap.start; i < gap.end; i++) {
|
|
12844
|
-
const c = source.charCodeAt(i);
|
|
12845
|
-
if (c === NL) n++;
|
|
12846
|
-
else if (c === CR && source.charCodeAt(i + 1) !== NL) n++;
|
|
12847
|
-
}
|
|
12848
|
-
return n;
|
|
12849
|
-
}
|
|
12850
|
-
function blankLinesIn(source, gap) {
|
|
12851
|
-
return Math.max(0, lineBreaksIn(source, gap) - 1);
|
|
12852
|
-
}
|
|
12853
|
-
function lineStartWithin(source, gap) {
|
|
12854
|
-
for (let i = gap.end - 1; i >= gap.start; i--) {
|
|
12855
|
-
const c = source.charCodeAt(i);
|
|
12856
|
-
if (c === NL || c === CR) return i + 1;
|
|
12857
|
-
}
|
|
12858
|
-
return gap.start;
|
|
12859
|
-
}
|
|
12860
|
-
function indentWidth(source, gap) {
|
|
12861
|
-
const ls = lineStartWithin(source, gap);
|
|
12862
|
-
if (ls === gap.start) return 0;
|
|
12863
|
-
return gap.end - ls;
|
|
12864
|
-
}
|
|
12865
|
-
function indentMixed(source, gap) {
|
|
12866
|
-
const ls = lineStartWithin(source, gap);
|
|
12867
|
-
let sawSpace = false;
|
|
12868
|
-
let sawTab = false;
|
|
12869
|
-
for (let i = ls; i < gap.end; i++) {
|
|
12870
|
-
const c = source.charCodeAt(i);
|
|
12871
|
-
if (c === SP) sawSpace = true;
|
|
12872
|
-
else if (c === TAB) sawTab = true;
|
|
12873
|
-
}
|
|
12874
|
-
return sawSpace && sawTab;
|
|
12875
|
-
}
|
|
12876
|
-
function commentsIn(source, gap, comment) {
|
|
12877
|
-
const re = new RegExp(comment.source, comment.flags.replace(/[gy]/g, "") + "g");
|
|
12878
|
-
re.lastIndex = gap.start;
|
|
12879
|
-
const out = [];
|
|
12880
|
-
let m;
|
|
12881
|
-
while ((m = re.exec(source)) && m.index < gap.end) {
|
|
12882
|
-
if (m[0].length === 0) {
|
|
12883
|
-
re.lastIndex++;
|
|
12884
|
-
continue;
|
|
12885
|
-
}
|
|
12886
|
-
if (m.index + m[0].length > gap.end) break;
|
|
12887
|
-
out.push({ start: m.index, end: m.index + m[0].length });
|
|
12888
|
-
}
|
|
12889
|
-
return out;
|
|
12890
|
-
}
|
|
12891
|
-
function gapIsSignificant(source, gap, comment) {
|
|
12892
|
-
if (gap.end <= gap.start) return false;
|
|
12893
|
-
if (lineBreaksIn(source, gap) > 0) return true;
|
|
12894
|
-
if (comment && commentsIn(source, gap, comment).length > 0) return true;
|
|
12895
|
-
return false;
|
|
12896
|
-
}
|
|
12897
|
-
function collectLeafSlots(root) {
|
|
12898
|
-
const slots = [];
|
|
12899
|
-
const visit = (n) => {
|
|
12900
|
-
const kids = n.children;
|
|
12901
|
-
if (kids && kids.length) {
|
|
12902
|
-
for (const c of kids) visit(c);
|
|
12903
|
-
} else if (n.span) {
|
|
12904
|
-
slots.push({ start: n.span.start, end: n.span.end });
|
|
12905
|
-
}
|
|
12906
|
-
};
|
|
12907
|
-
if (root) visit(root);
|
|
12908
|
-
return slots;
|
|
12909
|
-
}
|
|
12910
|
-
function buildOffsetIndex(root, input = "") {
|
|
12911
|
-
return new OffsetIndex(collectLeafSlots(root), 0, input.length);
|
|
12912
|
-
}
|
|
12913
|
-
|
|
12914
14148
|
// src/index.ts
|
|
12915
14149
|
registerRegexAnalyzer(firstSetFromRegex);
|
|
12916
14150
|
export {
|
|
12917
14151
|
GRAMMAR_COVERAGE_DEFINITIONS,
|
|
12918
|
-
OffsetIndex,
|
|
12919
14152
|
absoluteSpanAt,
|
|
12920
14153
|
absoluteSpanCST,
|
|
12921
14154
|
absolutize,
|
|
12922
14155
|
absolutizeCST,
|
|
12923
14156
|
alternationGroups,
|
|
14157
|
+
analyzeChoiceInventory,
|
|
12924
14158
|
analyzeDuplication,
|
|
12925
14159
|
analyzeDuplicationRules,
|
|
12926
14160
|
analyzeGating,
|
|
@@ -12929,18 +14163,18 @@ export {
|
|
|
12929
14163
|
annotateSpan,
|
|
12930
14164
|
annotateTreeSpans,
|
|
12931
14165
|
applyEdit,
|
|
14166
|
+
armLabel,
|
|
12932
14167
|
attempt,
|
|
12933
14168
|
balanced,
|
|
12934
|
-
blankLinesIn,
|
|
12935
14169
|
buildLineIndex,
|
|
12936
|
-
buildOffsetIndex,
|
|
12937
14170
|
buildRootTriviaIndex,
|
|
12938
14171
|
buildTriviaIndex,
|
|
14172
|
+
buildWastedWorkBaseline,
|
|
12939
14173
|
charClassMembers,
|
|
14174
|
+
checkWastedWork,
|
|
12940
14175
|
choice,
|
|
14176
|
+
choiceSiteKey,
|
|
12941
14177
|
classifiedTrivia,
|
|
12942
|
-
collectLeafSlots,
|
|
12943
|
-
commentsIn,
|
|
12944
14178
|
compile,
|
|
12945
14179
|
compiledGrammarCoverageDefinitions,
|
|
12946
14180
|
completionsAt,
|
|
@@ -12953,30 +14187,29 @@ export {
|
|
|
12953
14187
|
createLineIndex,
|
|
12954
14188
|
createVisitor,
|
|
12955
14189
|
cstBuildHost,
|
|
14190
|
+
diagnoseGrammar,
|
|
12956
14191
|
dispatch,
|
|
12957
14192
|
duplicationFindingCount,
|
|
12958
14193
|
endsWith,
|
|
14194
|
+
examinedNothing,
|
|
12959
14195
|
expect,
|
|
12960
14196
|
extractCharClasses,
|
|
12961
14197
|
field,
|
|
12962
14198
|
firstSetToString,
|
|
12963
14199
|
formatDuplicationFindings,
|
|
12964
14200
|
formatGatingWarnings,
|
|
12965
|
-
|
|
12966
|
-
|
|
14201
|
+
formatGrammarDiagnosis,
|
|
14202
|
+
fuseInterpreted,
|
|
12967
14203
|
gate,
|
|
12968
14204
|
grammarCoverageDefinitions,
|
|
12969
|
-
|
|
12970
|
-
indentMixed,
|
|
12971
|
-
indentWidth,
|
|
14205
|
+
isInterpretedFuse,
|
|
12972
14206
|
isParseError,
|
|
12973
14207
|
keywordAlternationHazards,
|
|
12974
14208
|
keywordRegexShape,
|
|
12975
14209
|
keywords,
|
|
12976
14210
|
label,
|
|
12977
14211
|
leaf,
|
|
12978
|
-
|
|
12979
|
-
lineStartWithin,
|
|
14212
|
+
leftFactorPreview,
|
|
12980
14213
|
literal,
|
|
12981
14214
|
makeWhen,
|
|
12982
14215
|
makeWord,
|
|
@@ -12995,11 +14228,14 @@ export {
|
|
|
12995
14228
|
parseDoc,
|
|
12996
14229
|
parser,
|
|
12997
14230
|
peek,
|
|
14231
|
+
profileWastedWork,
|
|
12998
14232
|
recordLineRange,
|
|
12999
14233
|
ref,
|
|
13000
14234
|
regex,
|
|
13001
14235
|
relativize,
|
|
13002
14236
|
relativizeCST,
|
|
14237
|
+
renderChoiceInventory,
|
|
14238
|
+
renderWastedWork,
|
|
13003
14239
|
routed,
|
|
13004
14240
|
rules,
|
|
13005
14241
|
run,
|