parseman 0.45.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 +15 -0
- package/dist/analysis/diagnose.d.ts.map +1 -1
- 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 +8 -0
- package/dist/analysis/gating.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/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +44 -6
- 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/index.cjs +1297 -297
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1297 -297
- package/dist/index.js.map +4 -4
- package/dist/language-service/index.cjs.map +2 -2
- package/dist/language-service/index.js.map +2 -2
- package/dist/plugin/evaluator.d.ts +1 -1
- 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 +429 -43
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +429 -43
- package/dist/plugin/index.js.map +4 -4
- package/dist/plugin/reducer-resolver.d.ts +17 -1
- package/dist/plugin/reducer-resolver.d.ts.map +1 -1
- package/dist/spec/index.cjs +2 -1
- package/dist/spec/index.cjs.map +2 -2
- package/dist/spec/index.js +2 -1
- package/dist/spec/index.js.map +2 -2
- package/dist/version.d.ts +1 -1
- package/package.json +18 -3
package/dist/index.js
CHANGED
|
@@ -2824,7 +2824,7 @@ function markUnusedValues(root) {
|
|
|
2824
2824
|
return;
|
|
2825
2825
|
}
|
|
2826
2826
|
};
|
|
2827
|
-
const
|
|
2827
|
+
const resolveRoot2 = (c) => {
|
|
2828
2828
|
const guard = /* @__PURE__ */ new Set();
|
|
2829
2829
|
let cur = c;
|
|
2830
2830
|
while (!guard.has(cur)) {
|
|
@@ -2839,7 +2839,7 @@ function markUnusedValues(root) {
|
|
|
2839
2839
|
}
|
|
2840
2840
|
return cur;
|
|
2841
2841
|
};
|
|
2842
|
-
visit(
|
|
2842
|
+
visit(resolveRoot2(root), true);
|
|
2843
2843
|
}
|
|
2844
2844
|
|
|
2845
2845
|
// src/combinators/grammar.ts
|
|
@@ -3443,7 +3443,8 @@ var RESOLVER_REASONS = {
|
|
|
3443
3443
|
"unresolved-import": "its module could not be resolved or parsed",
|
|
3444
3444
|
"not-found": "the name did not resolve to any binding",
|
|
3445
3445
|
"computed": "it is a computed expression",
|
|
3446
|
-
"foreign-source": "it is named
|
|
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"
|
|
3447
3448
|
};
|
|
3448
3449
|
function unconfirmableReason(def, src) {
|
|
3449
3450
|
const declared = def.buildArityUnresolved;
|
|
@@ -3972,7 +3973,7 @@ function leaf(root, fn) {
|
|
|
3972
3973
|
}
|
|
3973
3974
|
|
|
3974
3975
|
// src/version.ts
|
|
3975
|
-
var PARSEMAN_VERSION = "0.
|
|
3976
|
+
var PARSEMAN_VERSION = "0.46.0";
|
|
3976
3977
|
|
|
3977
3978
|
// src/cst/host-mode.ts
|
|
3978
3979
|
var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
|
|
@@ -4027,14 +4028,28 @@ function firstSetToString(fs) {
|
|
|
4027
4028
|
return fs.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
|
|
4028
4029
|
}
|
|
4029
4030
|
var ruleNameOf2 = (p) => p._ruleName;
|
|
4031
|
+
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
4030
4032
|
var SUGGESTIONS = {
|
|
4031
|
-
"broad-recognizer": "
|
|
4032
|
-
"leading-not": "
|
|
4033
|
-
"nullable-prefix": "
|
|
4034
|
-
"cross-artifact-ref": "
|
|
4035
|
-
"opaque-wrapper": "
|
|
4036
|
-
"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."
|
|
4037
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
|
+
}
|
|
4038
4053
|
function classifyBroadArm(arm, resolve) {
|
|
4039
4054
|
const seen = /* @__PURE__ */ new Set();
|
|
4040
4055
|
const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
|
|
@@ -4150,7 +4165,6 @@ function peelToLeading(arm) {
|
|
|
4150
4165
|
if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
|
|
4151
4166
|
return cur;
|
|
4152
4167
|
}
|
|
4153
|
-
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
4154
4168
|
function detectAntiPatterns(rule, arms) {
|
|
4155
4169
|
const out = [];
|
|
4156
4170
|
arms.forEach((arm, i) => {
|
|
@@ -4163,14 +4177,14 @@ function detectAntiPatterns(rule, arms) {
|
|
|
4163
4177
|
kind: "double-not",
|
|
4164
4178
|
rule,
|
|
4165
4179
|
armIndex: i,
|
|
4166
|
-
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.'
|
|
4167
4181
|
});
|
|
4168
4182
|
} else {
|
|
4169
4183
|
out.push({
|
|
4170
4184
|
kind: "leading-not",
|
|
4171
4185
|
rule,
|
|
4172
4186
|
armIndex: i,
|
|
4173
|
-
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."
|
|
4174
4188
|
});
|
|
4175
4189
|
}
|
|
4176
4190
|
}
|
|
@@ -4179,7 +4193,7 @@ function detectAntiPatterns(rule, arms) {
|
|
|
4179
4193
|
kind: "keyword-regex",
|
|
4180
4194
|
rule,
|
|
4181
4195
|
armIndex: i,
|
|
4182
|
-
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.`
|
|
4183
4197
|
});
|
|
4184
4198
|
}
|
|
4185
4199
|
});
|
|
@@ -4221,7 +4235,7 @@ function analyzeGatingRules(ruleMap, opts) {
|
|
|
4221
4235
|
const d = def;
|
|
4222
4236
|
const rule = ruleNameOf2(p) ?? enclosingRule;
|
|
4223
4237
|
if (d.tag === "choice") {
|
|
4224
|
-
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
|
|
4238
|
+
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule, arms: d.parsers });
|
|
4225
4239
|
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
4226
4240
|
}
|
|
4227
4241
|
const rec = d;
|
|
@@ -4250,13 +4264,15 @@ function analyzeGatingRules(ruleMap, opts) {
|
|
|
4250
4264
|
const seenInRule = /* @__PURE__ */ new Map();
|
|
4251
4265
|
const accept = new Set(opts?.accept ?? []);
|
|
4252
4266
|
const usedAccept = /* @__PURE__ */ new Set();
|
|
4253
|
-
const choices = raw.map(({ g, rule }) => {
|
|
4267
|
+
const choices = raw.map(({ g, rule, arms }) => {
|
|
4254
4268
|
const n = seenInRule.get(rule) ?? 0;
|
|
4255
4269
|
seenInRule.set(rule, n + 1);
|
|
4256
4270
|
const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
|
|
4257
4271
|
const isAccepted = g.gates === "no" && accept.has(id);
|
|
4258
4272
|
if (isAccepted) usedAccept.add(id);
|
|
4259
|
-
|
|
4273
|
+
const out = { ...g, id, accepted: isAccepted };
|
|
4274
|
+
Object.defineProperty(out, "arms", { value: arms, enumerable: false, writable: false });
|
|
4275
|
+
return out;
|
|
4260
4276
|
});
|
|
4261
4277
|
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
4262
4278
|
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
@@ -4283,7 +4299,14 @@ function analyzeChoice(p, d, rule, resolve) {
|
|
|
4283
4299
|
const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve));
|
|
4284
4300
|
const anyArms = deep.map((fs, index) => ({ fs, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
|
|
4285
4301
|
const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve);
|
|
4286
|
-
return {
|
|
4302
|
+
return {
|
|
4303
|
+
index,
|
|
4304
|
+
cause,
|
|
4305
|
+
detail,
|
|
4306
|
+
shallowAnyOnly: false,
|
|
4307
|
+
unresolvedExternal: unresolvedExternal === true,
|
|
4308
|
+
suggestion: refineSuggestion(cause, arms[index])
|
|
4309
|
+
};
|
|
4287
4310
|
});
|
|
4288
4311
|
const overlaps = [];
|
|
4289
4312
|
for (let i = 0; i < deep.length; i++)
|
|
@@ -4293,7 +4316,7 @@ function analyzeChoice(p, d, rule, resolve) {
|
|
|
4293
4316
|
a: i,
|
|
4294
4317
|
b: j,
|
|
4295
4318
|
on: intersection(deep[i], deep[j]),
|
|
4296
|
-
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."
|
|
4297
4320
|
});
|
|
4298
4321
|
let gates;
|
|
4299
4322
|
if (d.disjoint) gates = "yes";
|
|
@@ -6105,15 +6128,15 @@ function parseStringShape(source) {
|
|
|
6105
6128
|
if (source.length < 3 || source[source.length - 1] !== q) return null;
|
|
6106
6129
|
const inner = source.slice(1, -1);
|
|
6107
6130
|
const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
|
|
6108
|
-
const
|
|
6131
|
+
const ESC2 = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
|
|
6109
6132
|
let body;
|
|
6110
6133
|
let esc;
|
|
6111
|
-
let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${
|
|
6134
|
+
let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC2}\\)\\*$`).exec(inner);
|
|
6112
6135
|
if (m) {
|
|
6113
6136
|
body = m[1];
|
|
6114
6137
|
esc = m[2];
|
|
6115
6138
|
} else {
|
|
6116
|
-
m = new RegExp(`^\\((?:\\?:)?${
|
|
6139
|
+
m = new RegExp(`^\\((?:\\?:)?${ESC2}\\|${CLS}\\)\\*$`).exec(inner);
|
|
6117
6140
|
if (m) {
|
|
6118
6141
|
esc = m[1];
|
|
6119
6142
|
body = m[2];
|
|
@@ -6123,8 +6146,8 @@ function parseStringShape(source) {
|
|
|
6123
6146
|
const excluded = parseClassRanges(body);
|
|
6124
6147
|
if (!excluded) return null;
|
|
6125
6148
|
const qcp = q.codePointAt(0);
|
|
6126
|
-
const
|
|
6127
|
-
if (!
|
|
6149
|
+
const inSet2 = (cp) => excluded.some(([lo, hi]) => cp >= lo && cp <= hi);
|
|
6150
|
+
if (!inSet2(qcp) || !inSet2(BACKSLASH)) return null;
|
|
6128
6151
|
return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
|
|
6129
6152
|
}
|
|
6130
6153
|
function parseSeqParts(source) {
|
|
@@ -11583,7 +11606,7 @@ function cstBuildHost(typeOrOptions, children2, _fields, span, rawChildren, triv
|
|
|
11583
11606
|
return host;
|
|
11584
11607
|
}
|
|
11585
11608
|
cstBuildHost._parsemanCstOutput = true;
|
|
11586
|
-
function fusedBody(pieces) {
|
|
11609
|
+
function fusedBody(pieces, hoist) {
|
|
11587
11610
|
for (const p of pieces) {
|
|
11588
11611
|
if (p.v === void 0) {
|
|
11589
11612
|
throw new Error(
|
|
@@ -11623,8 +11646,7 @@ function fusedBody(pieces) {
|
|
|
11623
11646
|
const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
|
|
11624
11647
|
const reflection = mergeGrammarReflections([...winner].map(([k, p]) => p.nodeMeta.get(k) ?? { nodes: [] }));
|
|
11625
11648
|
const { mode, elided } = hostModeOfPieces(pieces);
|
|
11626
|
-
const
|
|
11627
|
-
...lines,
|
|
11649
|
+
const tailSrc = [
|
|
11628
11650
|
"const _map = {",
|
|
11629
11651
|
wrapperEntries.join(",\n"),
|
|
11630
11652
|
"}",
|
|
@@ -11640,6 +11662,7 @@ function fusedBody(pieces) {
|
|
|
11640
11662
|
"}",
|
|
11641
11663
|
"return _map"
|
|
11642
11664
|
].join("\n");
|
|
11665
|
+
const rawBody = [...lines, tailSrc].join("\n");
|
|
11643
11666
|
const ANY2 = { kind: "any" };
|
|
11644
11667
|
const EMPTY3 = { kind: "empty" };
|
|
11645
11668
|
const recipes = /* @__PURE__ */ new Map();
|
|
@@ -11680,7 +11703,7 @@ function fusedBody(pieces) {
|
|
|
11680
11703
|
}
|
|
11681
11704
|
if (!changed) break;
|
|
11682
11705
|
}
|
|
11683
|
-
const
|
|
11706
|
+
const resolveFS = (src) => src.replace(
|
|
11684
11707
|
// Rule name + code-point var are both JS identifiers (rule names are validated
|
|
11685
11708
|
// at compile time — see assertRuleName in codegen), so an identifier class
|
|
11686
11709
|
// matches every well-formed placeholder.
|
|
@@ -11691,6 +11714,7 @@ function fusedBody(pieces) {
|
|
|
11691
11714
|
return `(${firstSetCond(codevar, fs)})`;
|
|
11692
11715
|
}
|
|
11693
11716
|
);
|
|
11717
|
+
const body = hoist === void 0 ? resolveFS(rawBody) : [...hoist.claim(lines.map(resolveFS)), resolveFS(tailSrc)].join("\n");
|
|
11694
11718
|
const env = {};
|
|
11695
11719
|
for (const p of contributingPieces) {
|
|
11696
11720
|
if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
|
|
@@ -11854,8 +11878,168 @@ function compose(items, opts) {
|
|
|
11854
11878
|
if (coverageRules) Object.defineProperty(map, COMPOSED_COVERAGE_RULES, { value: coverageRules, enumerable: false });
|
|
11855
11879
|
return map;
|
|
11856
11880
|
}
|
|
11857
|
-
function composeLeaf(
|
|
11858
|
-
|
|
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(", ")}`;
|
|
11859
12043
|
}
|
|
11860
12044
|
|
|
11861
12045
|
// src/analysis/grammar.ts
|
|
@@ -11905,13 +12089,16 @@ function diagnoseGrammar(grammar, opts) {
|
|
|
11905
12089
|
}
|
|
11906
12090
|
return assemble(report, degradations);
|
|
11907
12091
|
}
|
|
12092
|
+
function examinedNothing(d) {
|
|
12093
|
+
return d.summary.totalChoices === 0 && d.summary.unanalysable > 0;
|
|
12094
|
+
}
|
|
11908
12095
|
function formatGrammarDiagnosis(d) {
|
|
11909
12096
|
const s = d.summary;
|
|
11910
12097
|
const lines = [];
|
|
11911
12098
|
lines.push(
|
|
11912
|
-
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} blocking finding(s) over ${s.totalChoices} examined choice(s).`
|
|
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).`
|
|
11913
12100
|
);
|
|
11914
|
-
if (s.unanalysable > 0) {
|
|
12101
|
+
if (s.unanalysable > 0 && !examinedNothing(d)) {
|
|
11915
12102
|
lines.push(
|
|
11916
12103
|
` ${s.unanalysable} rule(s) UNANALYSABLE \u2014 THIS REPORT IS PARTIAL. An empty finding list below does NOT mean the grammar is clean.`
|
|
11917
12104
|
);
|
|
@@ -12041,141 +12228,1081 @@ fix: ${o.suggestion}`)
|
|
|
12041
12228
|
};
|
|
12042
12229
|
}
|
|
12043
12230
|
|
|
12044
|
-
// src/
|
|
12045
|
-
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
function deeperFail(a, b) {
|
|
12074
|
-
if (!a) return b;
|
|
12075
|
-
if (!b) return a;
|
|
12076
|
-
return a.span.start >= b.span.start ? a : b;
|
|
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;
|
|
12077
12260
|
}
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
return
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
children: node2.children.map((c) => relativize(c, node2.start))
|
|
12085
|
-
};
|
|
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);
|
|
12086
12267
|
}
|
|
12087
|
-
function
|
|
12088
|
-
|
|
12089
|
-
return
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
children: node2.children.map((c) => absolutize(c, start))
|
|
12093
|
-
};
|
|
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;
|
|
12094
12273
|
}
|
|
12095
|
-
function
|
|
12096
|
-
|
|
12097
|
-
let
|
|
12098
|
-
|
|
12099
|
-
base += node2.start;
|
|
12100
|
-
const child = node2.children[idx];
|
|
12101
|
-
if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
|
|
12102
|
-
node2 = child;
|
|
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;
|
|
12103
12278
|
}
|
|
12104
|
-
return
|
|
12279
|
+
return index;
|
|
12105
12280
|
}
|
|
12106
|
-
function
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
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
|
+
);
|
|
12111
12286
|
}
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
const
|
|
12116
|
-
const
|
|
12117
|
-
|
|
12118
|
-
|
|
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.");
|
|
12119
12299
|
}
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
base += node2.span.start;
|
|
12127
|
-
const child = node2.children?.[idx];
|
|
12128
|
-
if (!child) throw new RangeError(`absoluteSpanCST: no child ${idx} on path ${path.join(".")}`);
|
|
12129
|
-
node2 = child;
|
|
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
|
+
}
|
|
12130
12306
|
}
|
|
12131
|
-
|
|
12132
|
-
|
|
12133
|
-
|
|
12134
|
-
|
|
12135
|
-
const e = node2.end >= at ? node2.end + delta : node2.end;
|
|
12136
|
-
return {
|
|
12137
|
-
start: s,
|
|
12138
|
-
end: e,
|
|
12139
|
-
children: node2.children.map((c) => shiftAbsolute(c, at, delta))
|
|
12140
|
-
};
|
|
12141
|
-
}
|
|
12142
|
-
function applyEdit(root, at, delta, stats) {
|
|
12143
|
-
return editNode(root, at, delta, 0, 0, stats);
|
|
12144
|
-
}
|
|
12145
|
-
function editNode(node2, at, delta, parentBase, parentShift, stats) {
|
|
12146
|
-
const absStart = parentBase + node2.start;
|
|
12147
|
-
const absEnd = parentBase + node2.end;
|
|
12148
|
-
if (absEnd < at) return node2;
|
|
12149
|
-
const startShifts = absStart >= at;
|
|
12150
|
-
if (startShifts && parentShift === delta) return node2;
|
|
12151
|
-
if (stats) stats.allocated++;
|
|
12152
|
-
const newAbsStart = startShifts ? absStart + delta : absStart;
|
|
12153
|
-
const newAbsEnd = absEnd + delta;
|
|
12154
|
-
const newParentBase = parentBase + parentShift;
|
|
12155
|
-
const newStart = newAbsStart - newParentBase;
|
|
12156
|
-
const newEnd = newAbsEnd - newParentBase;
|
|
12157
|
-
const ownShift = newAbsStart - absStart;
|
|
12158
|
-
let changed = false;
|
|
12159
|
-
const newChildren = [];
|
|
12160
|
-
for (const c of node2.children) {
|
|
12161
|
-
const nc = editNode(c, at, delta, absStart, ownShift, stats);
|
|
12162
|
-
if (nc !== c) changed = true;
|
|
12163
|
-
newChildren.push(nc);
|
|
12307
|
+
if (captureRootTrivia && !rootTriviaClassifiedFromRunnable(entry) && !rootTriviaClassifiedFromRunnable(options.trivia)) {
|
|
12308
|
+
throw new TypeError(
|
|
12309
|
+
"run(): rootTrivia.select requires classifiedTrivia() at the root."
|
|
12310
|
+
);
|
|
12164
12311
|
}
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
12178
|
-
|
|
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,
|
|
13176
|
+
isTrivia: false
|
|
13177
|
+
};
|
|
13178
|
+
return {
|
|
13179
|
+
_tag: "guard",
|
|
13180
|
+
_meta: meta,
|
|
13181
|
+
_def: { tag: "guard", predicate },
|
|
13182
|
+
parse(_input, pos, ctx) {
|
|
13183
|
+
if (predicate(ctx.state))
|
|
13184
|
+
return { ok: true, value: null, span: { start: pos, end: pos } };
|
|
13185
|
+
return { ok: false, expected: ["guard"], span: { start: pos, end: pos } };
|
|
13186
|
+
}
|
|
13187
|
+
};
|
|
13188
|
+
}
|
|
13189
|
+
|
|
13190
|
+
// src/combinators/completions.ts
|
|
13191
|
+
function completionsAt(target, input, offset, options = {}) {
|
|
13192
|
+
const probe = { offset, best: null };
|
|
13193
|
+
const ctx = { trackLines: false, _probe: probe, ...options.tolerant ? { _tolerant: true, _rec: REC } : {} };
|
|
13194
|
+
const sliced = input.slice(0, offset);
|
|
13195
|
+
const result = "parseWithContext" in target ? target.parseWithContext(sliced, ctx, 0) : target.parse(sliced, 0, ctx);
|
|
13196
|
+
if (result.ok) return options.tolerant ? probe.best?.expected ?? [] : [];
|
|
13197
|
+
const best = deeperFail(probe.best, result);
|
|
13198
|
+
return best?.expected ?? [];
|
|
13199
|
+
}
|
|
13200
|
+
function deeperFail(a, b) {
|
|
13201
|
+
if (!a) return b;
|
|
13202
|
+
if (!b) return a;
|
|
13203
|
+
return a.span.start >= b.span.start ? a : b;
|
|
13204
|
+
}
|
|
13205
|
+
|
|
13206
|
+
// src/cst/relative-spans.ts
|
|
13207
|
+
function relativize(node2, parentBase = 0) {
|
|
13208
|
+
return {
|
|
13209
|
+
start: node2.start - parentBase,
|
|
13210
|
+
end: node2.end - parentBase,
|
|
13211
|
+
children: node2.children.map((c) => relativize(c, node2.start))
|
|
13212
|
+
};
|
|
13213
|
+
}
|
|
13214
|
+
function absolutize(node2, parentBase = 0) {
|
|
13215
|
+
const start = parentBase + node2.start;
|
|
13216
|
+
return {
|
|
13217
|
+
start,
|
|
13218
|
+
end: parentBase + node2.end,
|
|
13219
|
+
children: node2.children.map((c) => absolutize(c, start))
|
|
13220
|
+
};
|
|
13221
|
+
}
|
|
13222
|
+
function absoluteSpanAt(root, path) {
|
|
13223
|
+
let base = 0;
|
|
13224
|
+
let node2 = root;
|
|
13225
|
+
for (const idx of path) {
|
|
13226
|
+
base += node2.start;
|
|
13227
|
+
const child = node2.children[idx];
|
|
13228
|
+
if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
|
|
13229
|
+
node2 = child;
|
|
13230
|
+
}
|
|
13231
|
+
return { start: base + node2.start, end: base + node2.end };
|
|
13232
|
+
}
|
|
13233
|
+
function relativizeCST(node2, parentBase = 0) {
|
|
13234
|
+
const span = { start: node2.span.start - parentBase, end: node2.span.end - parentBase };
|
|
13235
|
+
if (node2.children) {
|
|
13236
|
+
const base = node2.span.start;
|
|
13237
|
+
return { ...node2, span, children: node2.children.map((c) => relativizeCST(c, base)) };
|
|
13238
|
+
}
|
|
13239
|
+
return { ...node2, span };
|
|
13240
|
+
}
|
|
13241
|
+
function absolutizeCST(node2, parentBase = 0) {
|
|
13242
|
+
const start = parentBase + node2.span.start;
|
|
13243
|
+
const span = { start, end: parentBase + node2.span.end };
|
|
13244
|
+
if (node2.children) {
|
|
13245
|
+
return { ...node2, span, children: node2.children.map((c) => absolutizeCST(c, start)) };
|
|
13246
|
+
}
|
|
13247
|
+
return { ...node2, span };
|
|
13248
|
+
}
|
|
13249
|
+
function absoluteSpanCST(root, path) {
|
|
13250
|
+
let base = 0;
|
|
13251
|
+
let node2 = root;
|
|
13252
|
+
for (const idx of path) {
|
|
13253
|
+
base += node2.span.start;
|
|
13254
|
+
const child = node2.children?.[idx];
|
|
13255
|
+
if (!child) throw new RangeError(`absoluteSpanCST: no child ${idx} on path ${path.join(".")}`);
|
|
13256
|
+
node2 = child;
|
|
13257
|
+
}
|
|
13258
|
+
return { start: base + node2.span.start, end: base + node2.span.end };
|
|
13259
|
+
}
|
|
13260
|
+
function shiftAbsolute(node2, at, delta) {
|
|
13261
|
+
const s = node2.start >= at ? node2.start + delta : node2.start;
|
|
13262
|
+
const e = node2.end >= at ? node2.end + delta : node2.end;
|
|
13263
|
+
return {
|
|
13264
|
+
start: s,
|
|
13265
|
+
end: e,
|
|
13266
|
+
children: node2.children.map((c) => shiftAbsolute(c, at, delta))
|
|
13267
|
+
};
|
|
13268
|
+
}
|
|
13269
|
+
function applyEdit(root, at, delta, stats) {
|
|
13270
|
+
return editNode(root, at, delta, 0, 0, stats);
|
|
13271
|
+
}
|
|
13272
|
+
function editNode(node2, at, delta, parentBase, parentShift, stats) {
|
|
13273
|
+
const absStart = parentBase + node2.start;
|
|
13274
|
+
const absEnd = parentBase + node2.end;
|
|
13275
|
+
if (absEnd < at) return node2;
|
|
13276
|
+
const startShifts = absStart >= at;
|
|
13277
|
+
if (startShifts && parentShift === delta) return node2;
|
|
13278
|
+
if (stats) stats.allocated++;
|
|
13279
|
+
const newAbsStart = startShifts ? absStart + delta : absStart;
|
|
13280
|
+
const newAbsEnd = absEnd + delta;
|
|
13281
|
+
const newParentBase = parentBase + parentShift;
|
|
13282
|
+
const newStart = newAbsStart - newParentBase;
|
|
13283
|
+
const newEnd = newAbsEnd - newParentBase;
|
|
13284
|
+
const ownShift = newAbsStart - absStart;
|
|
13285
|
+
let changed = false;
|
|
13286
|
+
const newChildren = [];
|
|
13287
|
+
for (const c of node2.children) {
|
|
13288
|
+
const nc = editNode(c, at, delta, absStart, ownShift, stats);
|
|
13289
|
+
if (nc !== c) changed = true;
|
|
13290
|
+
newChildren.push(nc);
|
|
13291
|
+
}
|
|
13292
|
+
return {
|
|
13293
|
+
start: newStart,
|
|
13294
|
+
end: newEnd,
|
|
13295
|
+
children: changed ? newChildren : node2.children
|
|
13296
|
+
};
|
|
13297
|
+
}
|
|
13298
|
+
|
|
13299
|
+
// src/functional/doc.ts
|
|
13300
|
+
function mkCtx(rawState, build, tolerant) {
|
|
13301
|
+
const state = rawState ?? void 0;
|
|
13302
|
+
return tolerant ? { trackLines: false, state, build, _tolerant: true, _rec: REC, _errors: [] } : { trackLines: false, state, build };
|
|
13303
|
+
}
|
|
13304
|
+
function asRuleFn(entry) {
|
|
13305
|
+
return typeof entry === "function" ? entry : (input, pos, ctx) => entry.parse(input, pos, ctx);
|
|
12179
13306
|
}
|
|
12180
13307
|
function defOf(entry) {
|
|
12181
13308
|
return typeof entry === "object" && entry !== null && "_def" in entry ? entry._def : void 0;
|
|
@@ -12580,145 +13707,6 @@ function parseDoc(registry, rootRule, input, opts = {}) {
|
|
|
12580
13707
|
return new ParseDocImpl(registry, rootRule, opts, { abs: null }, [fail], null, input);
|
|
12581
13708
|
}
|
|
12582
13709
|
|
|
12583
|
-
// src/functional/run.ts
|
|
12584
|
-
var SELECT_EXAMPLE = "run(entry, input, { rootTrivia: { select: ['blockComment', 'lineComment'] } })";
|
|
12585
|
-
var REMOVED_RUN_RESULT_FIELDS = [
|
|
12586
|
-
[
|
|
12587
|
-
"triviaMap",
|
|
12588
|
-
`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.`
|
|
12589
|
-
],
|
|
12590
|
-
[
|
|
12591
|
-
"triviaLog",
|
|
12592
|
-
`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.`
|
|
12593
|
-
],
|
|
12594
|
-
[
|
|
12595
|
-
"triviaKindLabels",
|
|
12596
|
-
`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\`.`
|
|
12597
|
-
]
|
|
12598
|
-
];
|
|
12599
|
-
function guardRemovedFields(result) {
|
|
12600
|
-
for (const [name, detail] of REMOVED_RUN_RESULT_FIELDS) {
|
|
12601
|
-
Object.defineProperty(result, name, {
|
|
12602
|
-
configurable: true,
|
|
12603
|
-
enumerable: false,
|
|
12604
|
-
get() {
|
|
12605
|
-
throw new TypeError(
|
|
12606
|
-
`RunResult.${name} was REMOVED in parseman 0.44.0 and has no default replacement. ${detail}`
|
|
12607
|
-
);
|
|
12608
|
-
}
|
|
12609
|
-
});
|
|
12610
|
-
}
|
|
12611
|
-
return result;
|
|
12612
|
-
}
|
|
12613
|
-
var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
|
|
12614
|
-
function triviaKindLabelsFromRunnable(r) {
|
|
12615
|
-
if (!r) return void 0;
|
|
12616
|
-
if (typeof r === "function") return r._meta?.triviaKindLabels;
|
|
12617
|
-
const grammarTrivia = r._meta.grammarTrivia;
|
|
12618
|
-
return grammarTrivia?._meta.triviaKindLabels ?? r._meta.triviaKindLabels ?? (r._def.tag === "grammar" ? r._def.triviaParser?._meta.triviaKindLabels : void 0);
|
|
12619
|
-
}
|
|
12620
|
-
function rootTriviaClassifiedFromRunnable(r) {
|
|
12621
|
-
if (!r) return false;
|
|
12622
|
-
if (typeof r === "function") return r._meta?.rootTriviaClassified === true;
|
|
12623
|
-
const grammarTrivia = r._meta.grammarTrivia;
|
|
12624
|
-
return grammarTrivia?._meta.rootTriviaClassified === true || r._meta.rootTriviaClassified === true;
|
|
12625
|
-
}
|
|
12626
|
-
function makeSelectedRootLabelIndex(labels) {
|
|
12627
|
-
const index = /* @__PURE__ */ Object.create(null);
|
|
12628
|
-
for (let i = 0; i < labels.length; i++) {
|
|
12629
|
-
if (index[labels[i]] === void 0) index[labels[i]] = i;
|
|
12630
|
-
}
|
|
12631
|
-
return index;
|
|
12632
|
-
}
|
|
12633
|
-
function runOnce(entry, input, options, phase, profileState) {
|
|
12634
|
-
if (typeof entry !== "function" && typeof entry?.parse !== "function") {
|
|
12635
|
-
throw new TypeError(
|
|
12636
|
-
`run(): start production is ${entry === null ? "null" : typeof entry}, not a rule \u2014 the requested grammar rule does not exist (check the rule name).`
|
|
12637
|
-
);
|
|
12638
|
-
}
|
|
12639
|
-
const rootTriviaSelection = options.rootTrivia?.select;
|
|
12640
|
-
const captureRootTrivia = rootTriviaSelection !== void 0 && rootTriviaSelection.length > 0;
|
|
12641
|
-
const selectedRootLog = captureRootTrivia ? [] : void 0;
|
|
12642
|
-
const selectedRootLabelIndex = captureRootTrivia ? makeSelectedRootLabelIndex(rootTriviaSelection) : void 0;
|
|
12643
|
-
const errors = [];
|
|
12644
|
-
const profile = profileState ?? (phase === void 0 ? void 0 : { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 });
|
|
12645
|
-
const skipGlobalSinks = phase === "recognizer" || phase === "capture";
|
|
12646
|
-
const grammarTrivia = typeof entry !== "function" ? entry._meta.grammarTrivia : void 0;
|
|
12647
|
-
const grammarScanSkip = typeof entry !== "function" ? entry._meta.grammarScanSkip : void 0;
|
|
12648
|
-
const triviaKindLabels = triviaKindLabelsFromRunnable(entry) ?? triviaKindLabelsFromRunnable(options.trivia);
|
|
12649
|
-
if (captureRootTrivia && triviaKindLabels === void 0) {
|
|
12650
|
-
throw new TypeError("run(): rootTrivia.select requires labeled grammar trivia.");
|
|
12651
|
-
}
|
|
12652
|
-
if (captureRootTrivia) {
|
|
12653
|
-
for (const label2 of rootTriviaSelection) {
|
|
12654
|
-
if (!triviaKindLabels.includes(label2)) {
|
|
12655
|
-
throw new TypeError(`run(): rootTrivia.select contains unknown trivia label ${JSON.stringify(label2)}.`);
|
|
12656
|
-
}
|
|
12657
|
-
}
|
|
12658
|
-
}
|
|
12659
|
-
if (captureRootTrivia && !rootTriviaClassifiedFromRunnable(entry) && !rootTriviaClassifiedFromRunnable(options.trivia)) {
|
|
12660
|
-
throw new TypeError(
|
|
12661
|
-
"run(): rootTrivia.select requires classifiedTrivia() at the root."
|
|
12662
|
-
);
|
|
12663
|
-
}
|
|
12664
|
-
const stamped = entry;
|
|
12665
|
-
assertHostModeCompatible(
|
|
12666
|
-
typeof entry === "function" ? stamped[FUSED_HOST_MODE] ?? "ast" : entry._meta.grammarHostMode ?? "ast",
|
|
12667
|
-
options.build,
|
|
12668
|
-
typeof entry === "function" && stamped[FUSED_HOST_ELIDED] === true
|
|
12669
|
-
);
|
|
12670
|
-
const ctx = {
|
|
12671
|
-
trackLines: false,
|
|
12672
|
-
...skipGlobalSinks ? { _pmProfile: profile } : {
|
|
12673
|
-
...captureRootTrivia ? {
|
|
12674
|
-
_rootTriviaLog: selectedRootLog,
|
|
12675
|
-
_rootTriviaKindIndex: selectedRootLabelIndex,
|
|
12676
|
-
_rootTriviaStrictScopes: true
|
|
12677
|
-
} : {},
|
|
12678
|
-
_errors: errors,
|
|
12679
|
-
...profile === void 0 ? {} : { _pmProfile: profile }
|
|
12680
|
-
},
|
|
12681
|
-
build: options.build,
|
|
12682
|
-
state: options.state,
|
|
12683
|
-
...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...triviaKindLabels ? { triviaKindLabels } : {} } : triviaKindLabels ? { triviaKindLabels } : {},
|
|
12684
|
-
...grammarScanSkip !== void 0 ? { scanSkip: grammarScanSkip } : {},
|
|
12685
|
-
...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {},
|
|
12686
|
-
...options.tolerant ? { _tolerant: true, _rec: REC } : {},
|
|
12687
|
-
...options.instrumentation === void 0 ? {} : options.instrumentation
|
|
12688
|
-
};
|
|
12689
|
-
const r = invoke(entry, input, 0, ctx);
|
|
12690
|
-
let unconsumedFrom = null;
|
|
12691
|
-
if (r.ok) {
|
|
12692
|
-
let pos = r.span?.end ?? 0;
|
|
12693
|
-
if (options.trivia && pos < input.length) {
|
|
12694
|
-
const t = invoke(options.trivia, input, pos, { trackLines: false });
|
|
12695
|
-
if (t.ok && t.span.end > pos) pos = t.span.end;
|
|
12696
|
-
}
|
|
12697
|
-
unconsumedFrom = pos < input.length ? pos : null;
|
|
12698
|
-
}
|
|
12699
|
-
return {
|
|
12700
|
-
ok: r.ok,
|
|
12701
|
-
value: r.ok ? r.value : void 0,
|
|
12702
|
-
span: r.span ?? { start: 0, end: 0 },
|
|
12703
|
-
expected: r.ok ? [] : r.expected ?? [],
|
|
12704
|
-
errors,
|
|
12705
|
-
...selectedRootLog !== void 0 && selectedRootLog.length > 0 ? {
|
|
12706
|
-
rootTrivia: {
|
|
12707
|
-
rows: selectedRootLog,
|
|
12708
|
-
select: rootTriviaSelection,
|
|
12709
|
-
index: buildRootTriviaIndex(selectedRootLog, rootTriviaSelection)
|
|
12710
|
-
}
|
|
12711
|
-
} : {},
|
|
12712
|
-
unconsumedFrom
|
|
12713
|
-
};
|
|
12714
|
-
}
|
|
12715
|
-
function run(entry, input, options = {}) {
|
|
12716
|
-
if (!options.profile) return guardRemovedFields(runOnce(entry, input, options));
|
|
12717
|
-
throw new TypeError(
|
|
12718
|
-
"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."
|
|
12719
|
-
);
|
|
12720
|
-
}
|
|
12721
|
-
|
|
12722
13710
|
// src/coverage.ts
|
|
12723
13711
|
function dispatchMatcher(entry) {
|
|
12724
13712
|
switch (entry.kind) {
|
|
@@ -13166,6 +14154,7 @@ export {
|
|
|
13166
14154
|
absolutize,
|
|
13167
14155
|
absolutizeCST,
|
|
13168
14156
|
alternationGroups,
|
|
14157
|
+
analyzeChoiceInventory,
|
|
13169
14158
|
analyzeDuplication,
|
|
13170
14159
|
analyzeDuplicationRules,
|
|
13171
14160
|
analyzeGating,
|
|
@@ -13174,13 +14163,17 @@ export {
|
|
|
13174
14163
|
annotateSpan,
|
|
13175
14164
|
annotateTreeSpans,
|
|
13176
14165
|
applyEdit,
|
|
14166
|
+
armLabel,
|
|
13177
14167
|
attempt,
|
|
13178
14168
|
balanced,
|
|
13179
14169
|
buildLineIndex,
|
|
13180
14170
|
buildRootTriviaIndex,
|
|
13181
14171
|
buildTriviaIndex,
|
|
14172
|
+
buildWastedWorkBaseline,
|
|
13182
14173
|
charClassMembers,
|
|
14174
|
+
checkWastedWork,
|
|
13183
14175
|
choice,
|
|
14176
|
+
choiceSiteKey,
|
|
13184
14177
|
classifiedTrivia,
|
|
13185
14178
|
compile,
|
|
13186
14179
|
compiledGrammarCoverageDefinitions,
|
|
@@ -13198,6 +14191,7 @@ export {
|
|
|
13198
14191
|
dispatch,
|
|
13199
14192
|
duplicationFindingCount,
|
|
13200
14193
|
endsWith,
|
|
14194
|
+
examinedNothing,
|
|
13201
14195
|
expect,
|
|
13202
14196
|
extractCharClasses,
|
|
13203
14197
|
field,
|
|
@@ -13205,14 +14199,17 @@ export {
|
|
|
13205
14199
|
formatDuplicationFindings,
|
|
13206
14200
|
formatGatingWarnings,
|
|
13207
14201
|
formatGrammarDiagnosis,
|
|
14202
|
+
fuseInterpreted,
|
|
13208
14203
|
gate,
|
|
13209
14204
|
grammarCoverageDefinitions,
|
|
14205
|
+
isInterpretedFuse,
|
|
13210
14206
|
isParseError,
|
|
13211
14207
|
keywordAlternationHazards,
|
|
13212
14208
|
keywordRegexShape,
|
|
13213
14209
|
keywords,
|
|
13214
14210
|
label,
|
|
13215
14211
|
leaf,
|
|
14212
|
+
leftFactorPreview,
|
|
13216
14213
|
literal,
|
|
13217
14214
|
makeWhen,
|
|
13218
14215
|
makeWord,
|
|
@@ -13231,11 +14228,14 @@ export {
|
|
|
13231
14228
|
parseDoc,
|
|
13232
14229
|
parser,
|
|
13233
14230
|
peek,
|
|
14231
|
+
profileWastedWork,
|
|
13234
14232
|
recordLineRange,
|
|
13235
14233
|
ref,
|
|
13236
14234
|
regex,
|
|
13237
14235
|
relativize,
|
|
13238
14236
|
relativizeCST,
|
|
14237
|
+
renderChoiceInventory,
|
|
14238
|
+
renderWastedWork,
|
|
13239
14239
|
routed,
|
|
13240
14240
|
rules,
|
|
13241
14241
|
run,
|