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.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
absolutize: () => absolutize,
|
|
27
27
|
absolutizeCST: () => absolutizeCST,
|
|
28
28
|
alternationGroups: () => alternationGroups,
|
|
29
|
+
analyzeChoiceInventory: () => analyzeChoiceInventory,
|
|
29
30
|
analyzeDuplication: () => analyzeDuplication,
|
|
30
31
|
analyzeDuplicationRules: () => analyzeDuplicationRules,
|
|
31
32
|
analyzeGating: () => analyzeGating,
|
|
@@ -34,13 +35,17 @@ __export(index_exports, {
|
|
|
34
35
|
annotateSpan: () => annotateSpan,
|
|
35
36
|
annotateTreeSpans: () => annotateTreeSpans,
|
|
36
37
|
applyEdit: () => applyEdit,
|
|
38
|
+
armLabel: () => armLabel,
|
|
37
39
|
attempt: () => attempt,
|
|
38
40
|
balanced: () => balanced,
|
|
39
41
|
buildLineIndex: () => buildLineIndex,
|
|
40
42
|
buildRootTriviaIndex: () => buildRootTriviaIndex,
|
|
41
43
|
buildTriviaIndex: () => buildTriviaIndex,
|
|
44
|
+
buildWastedWorkBaseline: () => buildWastedWorkBaseline,
|
|
42
45
|
charClassMembers: () => charClassMembers,
|
|
46
|
+
checkWastedWork: () => checkWastedWork,
|
|
43
47
|
choice: () => choice,
|
|
48
|
+
choiceSiteKey: () => choiceSiteKey,
|
|
44
49
|
classifiedTrivia: () => classifiedTrivia,
|
|
45
50
|
compile: () => compile,
|
|
46
51
|
compiledGrammarCoverageDefinitions: () => compiledGrammarCoverageDefinitions,
|
|
@@ -58,6 +63,7 @@ __export(index_exports, {
|
|
|
58
63
|
dispatch: () => dispatch,
|
|
59
64
|
duplicationFindingCount: () => duplicationFindingCount,
|
|
60
65
|
endsWith: () => endsWith,
|
|
66
|
+
examinedNothing: () => examinedNothing,
|
|
61
67
|
expect: () => expect,
|
|
62
68
|
extractCharClasses: () => extractCharClasses,
|
|
63
69
|
field: () => field,
|
|
@@ -65,14 +71,17 @@ __export(index_exports, {
|
|
|
65
71
|
formatDuplicationFindings: () => formatDuplicationFindings,
|
|
66
72
|
formatGatingWarnings: () => formatGatingWarnings,
|
|
67
73
|
formatGrammarDiagnosis: () => formatGrammarDiagnosis,
|
|
74
|
+
fuseInterpreted: () => fuseInterpreted,
|
|
68
75
|
gate: () => gate,
|
|
69
76
|
grammarCoverageDefinitions: () => grammarCoverageDefinitions,
|
|
77
|
+
isInterpretedFuse: () => isInterpretedFuse,
|
|
70
78
|
isParseError: () => isParseError,
|
|
71
79
|
keywordAlternationHazards: () => keywordAlternationHazards,
|
|
72
80
|
keywordRegexShape: () => keywordRegexShape,
|
|
73
81
|
keywords: () => keywords,
|
|
74
82
|
label: () => label,
|
|
75
83
|
leaf: () => leaf,
|
|
84
|
+
leftFactorPreview: () => leftFactorPreview,
|
|
76
85
|
literal: () => literal,
|
|
77
86
|
makeWhen: () => makeWhen,
|
|
78
87
|
makeWord: () => makeWord,
|
|
@@ -91,11 +100,14 @@ __export(index_exports, {
|
|
|
91
100
|
parseDoc: () => parseDoc,
|
|
92
101
|
parser: () => parser,
|
|
93
102
|
peek: () => peek,
|
|
103
|
+
profileWastedWork: () => profileWastedWork,
|
|
94
104
|
recordLineRange: () => recordLineRange,
|
|
95
105
|
ref: () => ref,
|
|
96
106
|
regex: () => regex,
|
|
97
107
|
relativize: () => relativize,
|
|
98
108
|
relativizeCST: () => relativizeCST,
|
|
109
|
+
renderChoiceInventory: () => renderChoiceInventory,
|
|
110
|
+
renderWastedWork: () => renderWastedWork,
|
|
99
111
|
routed: () => routed,
|
|
100
112
|
rules: () => rules,
|
|
101
113
|
run: () => run,
|
|
@@ -2944,7 +2956,7 @@ function markUnusedValues(root) {
|
|
|
2944
2956
|
return;
|
|
2945
2957
|
}
|
|
2946
2958
|
};
|
|
2947
|
-
const
|
|
2959
|
+
const resolveRoot2 = (c) => {
|
|
2948
2960
|
const guard = /* @__PURE__ */ new Set();
|
|
2949
2961
|
let cur = c;
|
|
2950
2962
|
while (!guard.has(cur)) {
|
|
@@ -2959,7 +2971,7 @@ function markUnusedValues(root) {
|
|
|
2959
2971
|
}
|
|
2960
2972
|
return cur;
|
|
2961
2973
|
};
|
|
2962
|
-
visit(
|
|
2974
|
+
visit(resolveRoot2(root), true);
|
|
2963
2975
|
}
|
|
2964
2976
|
|
|
2965
2977
|
// src/combinators/grammar.ts
|
|
@@ -3563,7 +3575,8 @@ var RESOLVER_REASONS = {
|
|
|
3563
3575
|
"unresolved-import": "its module could not be resolved or parsed",
|
|
3564
3576
|
"not-found": "the name did not resolve to any binding",
|
|
3565
3577
|
"computed": "it is a computed expression",
|
|
3566
|
-
"foreign-source": "it is named
|
|
3578
|
+
"foreign-source": "it is named in a module the resolver was never told about, so its offsets index no scope tree the resolver holds",
|
|
3579
|
+
"ambiguous-source": "two distinct registered modules hold byte-identical text, so that text no longer names the module its offsets index"
|
|
3567
3580
|
};
|
|
3568
3581
|
function unconfirmableReason(def, src) {
|
|
3569
3582
|
const declared = def.buildArityUnresolved;
|
|
@@ -4092,7 +4105,7 @@ function leaf(root, fn) {
|
|
|
4092
4105
|
}
|
|
4093
4106
|
|
|
4094
4107
|
// src/version.ts
|
|
4095
|
-
var PARSEMAN_VERSION = "0.
|
|
4108
|
+
var PARSEMAN_VERSION = "0.46.0";
|
|
4096
4109
|
|
|
4097
4110
|
// src/cst/host-mode.ts
|
|
4098
4111
|
var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
|
|
@@ -4147,14 +4160,28 @@ function firstSetToString(fs) {
|
|
|
4147
4160
|
return fs.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
|
|
4148
4161
|
}
|
|
4149
4162
|
var ruleNameOf2 = (p) => p._ruleName;
|
|
4163
|
+
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
4150
4164
|
var SUGGESTIONS = {
|
|
4151
|
-
"broad-recognizer": "
|
|
4152
|
-
"leading-not": "
|
|
4153
|
-
"nullable-prefix": "
|
|
4154
|
-
"cross-artifact-ref": "
|
|
4155
|
-
"opaque-wrapper": "
|
|
4156
|
-
"ref-cycle": "
|
|
4165
|
+
"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.",
|
|
4166
|
+
"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.",
|
|
4167
|
+
"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.",
|
|
4168
|
+
"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.",
|
|
4169
|
+
"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.",
|
|
4170
|
+
"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."
|
|
4157
4171
|
};
|
|
4172
|
+
function refineSuggestion(cause, arm) {
|
|
4173
|
+
if (cause !== "broad-recognizer") return SUGGESTIONS[cause];
|
|
4174
|
+
const lead = peelToLeading(arm);
|
|
4175
|
+
const d = lead._def;
|
|
4176
|
+
if (d.tag === "regex" && KEYWORD_REGEX_RE.test(d.source)) {
|
|
4177
|
+
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.
|
|
4178
|
+
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.`;
|
|
4179
|
+
}
|
|
4180
|
+
if (d.tag === "scanTo") {
|
|
4181
|
+
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.";
|
|
4182
|
+
}
|
|
4183
|
+
return SUGGESTIONS[cause];
|
|
4184
|
+
}
|
|
4158
4185
|
function classifyBroadArm(arm, resolve) {
|
|
4159
4186
|
const seen = /* @__PURE__ */ new Set();
|
|
4160
4187
|
const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
|
|
@@ -4270,7 +4297,6 @@ function peelToLeading(arm) {
|
|
|
4270
4297
|
if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
|
|
4271
4298
|
return cur;
|
|
4272
4299
|
}
|
|
4273
|
-
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
4274
4300
|
function detectAntiPatterns(rule, arms) {
|
|
4275
4301
|
const out = [];
|
|
4276
4302
|
arms.forEach((arm, i) => {
|
|
@@ -4283,14 +4309,14 @@ function detectAntiPatterns(rule, arms) {
|
|
|
4283
4309
|
kind: "double-not",
|
|
4284
4310
|
rule,
|
|
4285
4311
|
armIndex: i,
|
|
4286
|
-
message:
|
|
4312
|
+
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.'
|
|
4287
4313
|
});
|
|
4288
4314
|
} else {
|
|
4289
4315
|
out.push({
|
|
4290
4316
|
kind: "leading-not",
|
|
4291
4317
|
rule,
|
|
4292
4318
|
armIndex: i,
|
|
4293
|
-
message: "
|
|
4319
|
+
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."
|
|
4294
4320
|
});
|
|
4295
4321
|
}
|
|
4296
4322
|
}
|
|
@@ -4299,7 +4325,7 @@ function detectAntiPatterns(rule, arms) {
|
|
|
4299
4325
|
kind: "keyword-regex",
|
|
4300
4326
|
rule,
|
|
4301
4327
|
armIndex: i,
|
|
4302
|
-
message: `
|
|
4328
|
+
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.`
|
|
4303
4329
|
});
|
|
4304
4330
|
}
|
|
4305
4331
|
});
|
|
@@ -4341,7 +4367,7 @@ function analyzeGatingRules(ruleMap, opts) {
|
|
|
4341
4367
|
const d = def;
|
|
4342
4368
|
const rule = ruleNameOf2(p) ?? enclosingRule;
|
|
4343
4369
|
if (d.tag === "choice") {
|
|
4344
|
-
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
|
|
4370
|
+
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule, arms: d.parsers });
|
|
4345
4371
|
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
4346
4372
|
}
|
|
4347
4373
|
const rec = d;
|
|
@@ -4370,13 +4396,15 @@ function analyzeGatingRules(ruleMap, opts) {
|
|
|
4370
4396
|
const seenInRule = /* @__PURE__ */ new Map();
|
|
4371
4397
|
const accept = new Set(opts?.accept ?? []);
|
|
4372
4398
|
const usedAccept = /* @__PURE__ */ new Set();
|
|
4373
|
-
const choices = raw.map(({ g, rule }) => {
|
|
4399
|
+
const choices = raw.map(({ g, rule, arms }) => {
|
|
4374
4400
|
const n = seenInRule.get(rule) ?? 0;
|
|
4375
4401
|
seenInRule.set(rule, n + 1);
|
|
4376
4402
|
const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
|
|
4377
4403
|
const isAccepted = g.gates === "no" && accept.has(id);
|
|
4378
4404
|
if (isAccepted) usedAccept.add(id);
|
|
4379
|
-
|
|
4405
|
+
const out = { ...g, id, accepted: isAccepted };
|
|
4406
|
+
Object.defineProperty(out, "arms", { value: arms, enumerable: false, writable: false });
|
|
4407
|
+
return out;
|
|
4380
4408
|
});
|
|
4381
4409
|
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
4382
4410
|
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
@@ -4403,7 +4431,14 @@ function analyzeChoice(p, d, rule, resolve) {
|
|
|
4403
4431
|
const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve));
|
|
4404
4432
|
const anyArms = deep.map((fs, index) => ({ fs, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
|
|
4405
4433
|
const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve);
|
|
4406
|
-
return {
|
|
4434
|
+
return {
|
|
4435
|
+
index,
|
|
4436
|
+
cause,
|
|
4437
|
+
detail,
|
|
4438
|
+
shallowAnyOnly: false,
|
|
4439
|
+
unresolvedExternal: unresolvedExternal === true,
|
|
4440
|
+
suggestion: refineSuggestion(cause, arms[index])
|
|
4441
|
+
};
|
|
4407
4442
|
});
|
|
4408
4443
|
const overlaps = [];
|
|
4409
4444
|
for (let i = 0; i < deep.length; i++)
|
|
@@ -4413,7 +4448,7 @@ function analyzeChoice(p, d, rule, resolve) {
|
|
|
4413
4448
|
a: i,
|
|
4414
4449
|
b: j,
|
|
4415
4450
|
on: intersection(deep[i], deep[j]),
|
|
4416
|
-
suggestion: "arms
|
|
4451
|
+
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."
|
|
4417
4452
|
});
|
|
4418
4453
|
let gates;
|
|
4419
4454
|
if (d.disjoint) gates = "yes";
|
|
@@ -6225,15 +6260,15 @@ function parseStringShape(source) {
|
|
|
6225
6260
|
if (source.length < 3 || source[source.length - 1] !== q) return null;
|
|
6226
6261
|
const inner = source.slice(1, -1);
|
|
6227
6262
|
const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
|
|
6228
|
-
const
|
|
6263
|
+
const ESC2 = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
|
|
6229
6264
|
let body;
|
|
6230
6265
|
let esc;
|
|
6231
|
-
let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${
|
|
6266
|
+
let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC2}\\)\\*$`).exec(inner);
|
|
6232
6267
|
if (m) {
|
|
6233
6268
|
body = m[1];
|
|
6234
6269
|
esc = m[2];
|
|
6235
6270
|
} else {
|
|
6236
|
-
m = new RegExp(`^\\((?:\\?:)?${
|
|
6271
|
+
m = new RegExp(`^\\((?:\\?:)?${ESC2}\\|${CLS}\\)\\*$`).exec(inner);
|
|
6237
6272
|
if (m) {
|
|
6238
6273
|
esc = m[1];
|
|
6239
6274
|
body = m[2];
|
|
@@ -6243,8 +6278,8 @@ function parseStringShape(source) {
|
|
|
6243
6278
|
const excluded = parseClassRanges(body);
|
|
6244
6279
|
if (!excluded) return null;
|
|
6245
6280
|
const qcp = q.codePointAt(0);
|
|
6246
|
-
const
|
|
6247
|
-
if (!
|
|
6281
|
+
const inSet2 = (cp) => excluded.some(([lo, hi]) => cp >= lo && cp <= hi);
|
|
6282
|
+
if (!inSet2(qcp) || !inSet2(BACKSLASH)) return null;
|
|
6248
6283
|
return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
|
|
6249
6284
|
}
|
|
6250
6285
|
function parseSeqParts(source) {
|
|
@@ -11703,7 +11738,7 @@ function cstBuildHost(typeOrOptions, children2, _fields, span, rawChildren, triv
|
|
|
11703
11738
|
return host;
|
|
11704
11739
|
}
|
|
11705
11740
|
cstBuildHost._parsemanCstOutput = true;
|
|
11706
|
-
function fusedBody(pieces) {
|
|
11741
|
+
function fusedBody(pieces, hoist) {
|
|
11707
11742
|
for (const p of pieces) {
|
|
11708
11743
|
if (p.v === void 0) {
|
|
11709
11744
|
throw new Error(
|
|
@@ -11743,8 +11778,7 @@ function fusedBody(pieces) {
|
|
|
11743
11778
|
const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
|
|
11744
11779
|
const reflection = mergeGrammarReflections([...winner].map(([k, p]) => p.nodeMeta.get(k) ?? { nodes: [] }));
|
|
11745
11780
|
const { mode, elided } = hostModeOfPieces(pieces);
|
|
11746
|
-
const
|
|
11747
|
-
...lines,
|
|
11781
|
+
const tailSrc = [
|
|
11748
11782
|
"const _map = {",
|
|
11749
11783
|
wrapperEntries.join(",\n"),
|
|
11750
11784
|
"}",
|
|
@@ -11760,6 +11794,7 @@ function fusedBody(pieces) {
|
|
|
11760
11794
|
"}",
|
|
11761
11795
|
"return _map"
|
|
11762
11796
|
].join("\n");
|
|
11797
|
+
const rawBody = [...lines, tailSrc].join("\n");
|
|
11763
11798
|
const ANY2 = { kind: "any" };
|
|
11764
11799
|
const EMPTY3 = { kind: "empty" };
|
|
11765
11800
|
const recipes = /* @__PURE__ */ new Map();
|
|
@@ -11800,7 +11835,7 @@ function fusedBody(pieces) {
|
|
|
11800
11835
|
}
|
|
11801
11836
|
if (!changed) break;
|
|
11802
11837
|
}
|
|
11803
|
-
const
|
|
11838
|
+
const resolveFS = (src) => src.replace(
|
|
11804
11839
|
// Rule name + code-point var are both JS identifiers (rule names are validated
|
|
11805
11840
|
// at compile time — see assertRuleName in codegen), so an identifier class
|
|
11806
11841
|
// matches every well-formed placeholder.
|
|
@@ -11811,6 +11846,7 @@ function fusedBody(pieces) {
|
|
|
11811
11846
|
return `(${firstSetCond(codevar, fs)})`;
|
|
11812
11847
|
}
|
|
11813
11848
|
);
|
|
11849
|
+
const body = hoist === void 0 ? resolveFS(rawBody) : [...hoist.claim(lines.map(resolveFS)), resolveFS(tailSrc)].join("\n");
|
|
11814
11850
|
const env = {};
|
|
11815
11851
|
for (const p of contributingPieces) {
|
|
11816
11852
|
if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
|
|
@@ -11974,8 +12010,168 @@ function compose(items, opts) {
|
|
|
11974
12010
|
if (coverageRules) Object.defineProperty(map, COMPOSED_COVERAGE_RULES, { value: coverageRules, enumerable: false });
|
|
11975
12011
|
return map;
|
|
11976
12012
|
}
|
|
11977
|
-
function composeLeaf(
|
|
11978
|
-
|
|
12013
|
+
function composeLeaf(items) {
|
|
12014
|
+
const pieces = items.flatMap(interpretedPieces);
|
|
12015
|
+
let fused;
|
|
12016
|
+
const map = {};
|
|
12017
|
+
for (const name of ruleNamesOf(pieces)) {
|
|
12018
|
+
Object.defineProperty(map, name, {
|
|
12019
|
+
enumerable: true,
|
|
12020
|
+
configurable: true,
|
|
12021
|
+
get: () => (fused ??= fusePieces(pieces))[name]
|
|
12022
|
+
});
|
|
12023
|
+
}
|
|
12024
|
+
Object.defineProperty(map, LEAF_COMPOSED, { value: true, enumerable: false });
|
|
12025
|
+
Object.defineProperty(map, INTERPRETED_PIECES, {
|
|
12026
|
+
value: pieces.filter((p) => p.plain).map((p) => p.entries),
|
|
12027
|
+
enumerable: false
|
|
12028
|
+
});
|
|
12029
|
+
return map;
|
|
12030
|
+
}
|
|
12031
|
+
function isRefSlot(c) {
|
|
12032
|
+
return c._def.tag === "lazy" && typeof c.define === "function";
|
|
12033
|
+
}
|
|
12034
|
+
var FUSE_ORIGINAL = /* @__PURE__ */ Symbol.for("parseman.interpretedFuseOriginal");
|
|
12035
|
+
var FUSE_TARGET = /* @__PURE__ */ Symbol.for("parseman.interpretedFuseTarget");
|
|
12036
|
+
var INTERPRETED_PIECES = /* @__PURE__ */ Symbol.for("parseman.interpretedPieces");
|
|
12037
|
+
function isInterpretedFuse(map) {
|
|
12038
|
+
return Array.isArray(map[INTERPRETED_PIECES]);
|
|
12039
|
+
}
|
|
12040
|
+
function definitionOf(entry) {
|
|
12041
|
+
if (isRefSlot(entry)) {
|
|
12042
|
+
const original = entry[FUSE_ORIGINAL];
|
|
12043
|
+
if (original !== void 0) return original ?? void 0;
|
|
12044
|
+
try {
|
|
12045
|
+
return entry._def.thunk();
|
|
12046
|
+
} catch {
|
|
12047
|
+
return void 0;
|
|
12048
|
+
}
|
|
12049
|
+
}
|
|
12050
|
+
if (entry._def.tag === "lazy") {
|
|
12051
|
+
try {
|
|
12052
|
+
entry._def.thunk();
|
|
12053
|
+
} catch {
|
|
12054
|
+
return void 0;
|
|
12055
|
+
}
|
|
12056
|
+
}
|
|
12057
|
+
return entry;
|
|
12058
|
+
}
|
|
12059
|
+
function repointRef(slot, target, name) {
|
|
12060
|
+
const tagged = slot;
|
|
12061
|
+
const bound = tagged[FUSE_TARGET];
|
|
12062
|
+
const original = FUSE_ORIGINAL in tagged ? tagged[FUSE_ORIGINAL] : (() => {
|
|
12063
|
+
try {
|
|
12064
|
+
return slot._def.thunk();
|
|
12065
|
+
} catch {
|
|
12066
|
+
return null;
|
|
12067
|
+
}
|
|
12068
|
+
})();
|
|
12069
|
+
if ((bound ?? original) === target) return;
|
|
12070
|
+
if (bound !== void 0) {
|
|
12071
|
+
throw new Error(
|
|
12072
|
+
`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.`
|
|
12073
|
+
);
|
|
12074
|
+
}
|
|
12075
|
+
if (!(FUSE_ORIGINAL in tagged)) Object.defineProperty(slot, FUSE_ORIGINAL, { value: original, enumerable: false });
|
|
12076
|
+
Object.defineProperty(slot, FUSE_TARGET, { value: target, enumerable: false });
|
|
12077
|
+
slot._def.thunk = () => target;
|
|
12078
|
+
slot.parse = (input, pos, ctx) => target.parse(input, pos, ctx);
|
|
12079
|
+
const meta = slot._meta;
|
|
12080
|
+
meta.firstSet = target._meta.firstSet;
|
|
12081
|
+
meta.canMatchNewline = target._meta.canMatchNewline;
|
|
12082
|
+
meta.isTrivia = target._meta.isTrivia;
|
|
12083
|
+
if (target._meta.triviaKindLabels !== void 0) meta.triviaKindLabels = target._meta.triviaKindLabels;
|
|
12084
|
+
else delete meta.triviaKindLabels;
|
|
12085
|
+
if (target._meta.disjoint !== void 0) meta.disjoint = target._meta.disjoint;
|
|
12086
|
+
else delete meta.disjoint;
|
|
12087
|
+
}
|
|
12088
|
+
function interpretedPieces(item) {
|
|
12089
|
+
const fused = item[INTERPRETED_PIECES];
|
|
12090
|
+
if (Array.isArray(fused)) return fused.map((entries) => ({ entries, plain: false }));
|
|
12091
|
+
const carried = composedPiecesOf(item);
|
|
12092
|
+
if (carried !== void 0) {
|
|
12093
|
+
const { maps, opaque } = carriedRuleMapsDetailed(carried);
|
|
12094
|
+
if (opaque.length > 0) {
|
|
12095
|
+
throw new Error(
|
|
12096
|
+
`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.`
|
|
12097
|
+
);
|
|
12098
|
+
}
|
|
12099
|
+
return maps.map((entries) => ({ entries, plain: false }));
|
|
12100
|
+
}
|
|
12101
|
+
if (item.ruleFns instanceof Map) {
|
|
12102
|
+
throw new Error("fuseInterpreted: a precompiled linkable artifact has no combinator graph to interpret; pass the source grammar (a rules() map) instead");
|
|
12103
|
+
}
|
|
12104
|
+
return [{ entries: Object.entries(item), plain: true }];
|
|
12105
|
+
}
|
|
12106
|
+
function fuseInterpreted(items, opts) {
|
|
12107
|
+
return fusePieces(items.flatMap(interpretedPieces), opts);
|
|
12108
|
+
}
|
|
12109
|
+
function ruleNamesOf(pieces) {
|
|
12110
|
+
const names = /* @__PURE__ */ new Set();
|
|
12111
|
+
for (const piece of pieces) {
|
|
12112
|
+
for (const [name, value] of piece.entries) if (definitionOf(value) !== void 0) names.add(name);
|
|
12113
|
+
}
|
|
12114
|
+
return [...names];
|
|
12115
|
+
}
|
|
12116
|
+
function fusePieces(pieces, opts) {
|
|
12117
|
+
let trivia2;
|
|
12118
|
+
for (let i = pieces.length - 1; i >= 0 && trivia2 === void 0; i--) {
|
|
12119
|
+
if (!pieces[i].plain) continue;
|
|
12120
|
+
for (const [, rule] of pieces[i].entries) {
|
|
12121
|
+
const t = rule._meta.grammarTrivia;
|
|
12122
|
+
if (t) {
|
|
12123
|
+
trivia2 = t;
|
|
12124
|
+
break;
|
|
12125
|
+
}
|
|
12126
|
+
}
|
|
12127
|
+
}
|
|
12128
|
+
const winner = /* @__PURE__ */ new Map();
|
|
12129
|
+
const entry = /* @__PURE__ */ new Map();
|
|
12130
|
+
for (const piece of pieces) {
|
|
12131
|
+
for (const [name, value] of piece.entries) {
|
|
12132
|
+
const def = definitionOf(value);
|
|
12133
|
+
if (def === void 0) continue;
|
|
12134
|
+
winner.set(name, def);
|
|
12135
|
+
entry.set(name, value);
|
|
12136
|
+
}
|
|
12137
|
+
}
|
|
12138
|
+
const missing = /* @__PURE__ */ new Set();
|
|
12139
|
+
for (const piece of pieces) {
|
|
12140
|
+
for (const [name, value] of piece.entries) {
|
|
12141
|
+
if (!isRefSlot(value)) continue;
|
|
12142
|
+
const target = winner.get(name);
|
|
12143
|
+
if (target === void 0) {
|
|
12144
|
+
missing.add(name);
|
|
12145
|
+
continue;
|
|
12146
|
+
}
|
|
12147
|
+
repointRef(value, target, name);
|
|
12148
|
+
}
|
|
12149
|
+
}
|
|
12150
|
+
if (missing.size > 0) throw new Error(missingRuleMessage(pieces, missing));
|
|
12151
|
+
const out = {};
|
|
12152
|
+
for (const [name, value] of entry) {
|
|
12153
|
+
out[name] = value;
|
|
12154
|
+
const meta = value._meta;
|
|
12155
|
+
if (trivia2 !== void 0 && !meta.isTrivia) meta.grammarTrivia = trivia2;
|
|
12156
|
+
if (opts?.hostMode !== void 0 && !meta.isTrivia) {
|
|
12157
|
+
if (opts.hostMode === "cst") meta.grammarHostMode = "cst";
|
|
12158
|
+
else delete meta.grammarHostMode;
|
|
12159
|
+
}
|
|
12160
|
+
}
|
|
12161
|
+
Object.defineProperty(out, INTERPRETED_PIECES, {
|
|
12162
|
+
value: pieces.filter((p) => p.plain).map((p) => p.entries),
|
|
12163
|
+
enumerable: false
|
|
12164
|
+
});
|
|
12165
|
+
return out;
|
|
12166
|
+
}
|
|
12167
|
+
function missingRuleMessage(pieces, missing) {
|
|
12168
|
+
for (const piece of pieces) {
|
|
12169
|
+
const deps = ruleDependencies(piece.entries.filter(([, v2]) => definitionOf(v2) !== void 0));
|
|
12170
|
+
for (const [name, ds] of deps) {
|
|
12171
|
+
for (const d of ds) if (missing.has(d)) return `fuseInterpreted: rule "${name}" references missing rule "${d}"`;
|
|
12172
|
+
}
|
|
12173
|
+
}
|
|
12174
|
+
return `fuseInterpreted: missing rule(s) ${[...missing].map((n) => `"${n}"`).join(", ")}`;
|
|
11979
12175
|
}
|
|
11980
12176
|
|
|
11981
12177
|
// src/analysis/grammar.ts
|
|
@@ -12025,13 +12221,16 @@ function diagnoseGrammar(grammar, opts) {
|
|
|
12025
12221
|
}
|
|
12026
12222
|
return assemble(report, degradations);
|
|
12027
12223
|
}
|
|
12224
|
+
function examinedNothing(d) {
|
|
12225
|
+
return d.summary.totalChoices === 0 && d.summary.unanalysable > 0;
|
|
12226
|
+
}
|
|
12028
12227
|
function formatGrammarDiagnosis(d) {
|
|
12029
12228
|
const s = d.summary;
|
|
12030
12229
|
const lines = [];
|
|
12031
12230
|
lines.push(
|
|
12032
|
-
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).`
|
|
12231
|
+
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).`
|
|
12033
12232
|
);
|
|
12034
|
-
if (s.unanalysable > 0) {
|
|
12233
|
+
if (s.unanalysable > 0 && !examinedNothing(d)) {
|
|
12035
12234
|
lines.push(
|
|
12036
12235
|
` ${s.unanalysable} rule(s) UNANALYSABLE \u2014 THIS REPORT IS PARTIAL. An empty finding list below does NOT mean the grammar is clean.`
|
|
12037
12236
|
);
|
|
@@ -12161,141 +12360,1081 @@ fix: ${o.suggestion}`)
|
|
|
12161
12360
|
};
|
|
12162
12361
|
}
|
|
12163
12362
|
|
|
12164
|
-
// src/
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
12182
|
-
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
|
|
12186
|
-
|
|
12187
|
-
|
|
12188
|
-
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
function deeperFail(a, b) {
|
|
12194
|
-
if (!a) return b;
|
|
12195
|
-
if (!b) return a;
|
|
12196
|
-
return a.span.start >= b.span.start ? a : b;
|
|
12363
|
+
// src/functional/run.ts
|
|
12364
|
+
var SELECT_EXAMPLE = "run(entry, input, { rootTrivia: { select: ['blockComment', 'lineComment'] } })";
|
|
12365
|
+
var REMOVED_RUN_RESULT_FIELDS = [
|
|
12366
|
+
[
|
|
12367
|
+
"triviaMap",
|
|
12368
|
+
`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.`
|
|
12369
|
+
],
|
|
12370
|
+
[
|
|
12371
|
+
"triviaLog",
|
|
12372
|
+
`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.`
|
|
12373
|
+
],
|
|
12374
|
+
[
|
|
12375
|
+
"triviaKindLabels",
|
|
12376
|
+
`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\`.`
|
|
12377
|
+
]
|
|
12378
|
+
];
|
|
12379
|
+
function guardRemovedFields(result) {
|
|
12380
|
+
for (const [name, detail] of REMOVED_RUN_RESULT_FIELDS) {
|
|
12381
|
+
Object.defineProperty(result, name, {
|
|
12382
|
+
configurable: true,
|
|
12383
|
+
enumerable: false,
|
|
12384
|
+
get() {
|
|
12385
|
+
throw new TypeError(
|
|
12386
|
+
`RunResult.${name} was REMOVED in parseman 0.44.0 and has no default replacement. ${detail}`
|
|
12387
|
+
);
|
|
12388
|
+
}
|
|
12389
|
+
});
|
|
12390
|
+
}
|
|
12391
|
+
return result;
|
|
12197
12392
|
}
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
return
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
children: node2.children.map((c) => relativize(c, node2.start))
|
|
12205
|
-
};
|
|
12393
|
+
var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
|
|
12394
|
+
function triviaKindLabelsFromRunnable(r) {
|
|
12395
|
+
if (!r) return void 0;
|
|
12396
|
+
if (typeof r === "function") return r._meta?.triviaKindLabels;
|
|
12397
|
+
const grammarTrivia = r._meta.grammarTrivia;
|
|
12398
|
+
return grammarTrivia?._meta.triviaKindLabels ?? r._meta.triviaKindLabels ?? (r._def.tag === "grammar" ? r._def.triviaParser?._meta.triviaKindLabels : void 0);
|
|
12206
12399
|
}
|
|
12207
|
-
function
|
|
12208
|
-
|
|
12209
|
-
return
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
children: node2.children.map((c) => absolutize(c, start))
|
|
12213
|
-
};
|
|
12400
|
+
function rootTriviaClassifiedFromRunnable(r) {
|
|
12401
|
+
if (!r) return false;
|
|
12402
|
+
if (typeof r === "function") return r._meta?.rootTriviaClassified === true;
|
|
12403
|
+
const grammarTrivia = r._meta.grammarTrivia;
|
|
12404
|
+
return grammarTrivia?._meta.rootTriviaClassified === true || r._meta.rootTriviaClassified === true;
|
|
12214
12405
|
}
|
|
12215
|
-
function
|
|
12216
|
-
|
|
12217
|
-
let
|
|
12218
|
-
|
|
12219
|
-
base += node2.start;
|
|
12220
|
-
const child = node2.children[idx];
|
|
12221
|
-
if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
|
|
12222
|
-
node2 = child;
|
|
12406
|
+
function makeSelectedRootLabelIndex(labels) {
|
|
12407
|
+
const index = /* @__PURE__ */ Object.create(null);
|
|
12408
|
+
for (let i = 0; i < labels.length; i++) {
|
|
12409
|
+
if (index[labels[i]] === void 0) index[labels[i]] = i;
|
|
12223
12410
|
}
|
|
12224
|
-
return
|
|
12411
|
+
return index;
|
|
12225
12412
|
}
|
|
12226
|
-
function
|
|
12227
|
-
|
|
12228
|
-
|
|
12229
|
-
|
|
12230
|
-
|
|
12413
|
+
function runOnce(entry, input, options, phase, profileState) {
|
|
12414
|
+
if (typeof entry !== "function" && typeof entry?.parse !== "function") {
|
|
12415
|
+
throw new TypeError(
|
|
12416
|
+
`run(): start production is ${entry === null ? "null" : typeof entry}, not a rule \u2014 the requested grammar rule does not exist (check the rule name).`
|
|
12417
|
+
);
|
|
12231
12418
|
}
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
const
|
|
12236
|
-
const
|
|
12237
|
-
|
|
12238
|
-
|
|
12419
|
+
const rootTriviaSelection = options.rootTrivia?.select;
|
|
12420
|
+
const captureRootTrivia = rootTriviaSelection !== void 0 && rootTriviaSelection.length > 0;
|
|
12421
|
+
const selectedRootLog = captureRootTrivia ? [] : void 0;
|
|
12422
|
+
const selectedRootLabelIndex = captureRootTrivia ? makeSelectedRootLabelIndex(rootTriviaSelection) : void 0;
|
|
12423
|
+
const errors = [];
|
|
12424
|
+
const profile = profileState ?? (phase === void 0 ? void 0 : { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 });
|
|
12425
|
+
const skipGlobalSinks = phase === "recognizer" || phase === "capture";
|
|
12426
|
+
const grammarTrivia = typeof entry !== "function" ? entry._meta.grammarTrivia : void 0;
|
|
12427
|
+
const grammarScanSkip = typeof entry !== "function" ? entry._meta.grammarScanSkip : void 0;
|
|
12428
|
+
const triviaKindLabels = triviaKindLabelsFromRunnable(entry) ?? triviaKindLabelsFromRunnable(options.trivia);
|
|
12429
|
+
if (captureRootTrivia && triviaKindLabels === void 0) {
|
|
12430
|
+
throw new TypeError("run(): rootTrivia.select requires labeled grammar trivia.");
|
|
12239
12431
|
}
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
base += node2.span.start;
|
|
12247
|
-
const child = node2.children?.[idx];
|
|
12248
|
-
if (!child) throw new RangeError(`absoluteSpanCST: no child ${idx} on path ${path.join(".")}`);
|
|
12249
|
-
node2 = child;
|
|
12432
|
+
if (captureRootTrivia) {
|
|
12433
|
+
for (const label2 of rootTriviaSelection) {
|
|
12434
|
+
if (!triviaKindLabels.includes(label2)) {
|
|
12435
|
+
throw new TypeError(`run(): rootTrivia.select contains unknown trivia label ${JSON.stringify(label2)}.`);
|
|
12436
|
+
}
|
|
12437
|
+
}
|
|
12250
12438
|
}
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
const e = node2.end >= at ? node2.end + delta : node2.end;
|
|
12256
|
-
return {
|
|
12257
|
-
start: s,
|
|
12258
|
-
end: e,
|
|
12259
|
-
children: node2.children.map((c) => shiftAbsolute(c, at, delta))
|
|
12260
|
-
};
|
|
12261
|
-
}
|
|
12262
|
-
function applyEdit(root, at, delta, stats) {
|
|
12263
|
-
return editNode(root, at, delta, 0, 0, stats);
|
|
12264
|
-
}
|
|
12265
|
-
function editNode(node2, at, delta, parentBase, parentShift, stats) {
|
|
12266
|
-
const absStart = parentBase + node2.start;
|
|
12267
|
-
const absEnd = parentBase + node2.end;
|
|
12268
|
-
if (absEnd < at) return node2;
|
|
12269
|
-
const startShifts = absStart >= at;
|
|
12270
|
-
if (startShifts && parentShift === delta) return node2;
|
|
12271
|
-
if (stats) stats.allocated++;
|
|
12272
|
-
const newAbsStart = startShifts ? absStart + delta : absStart;
|
|
12273
|
-
const newAbsEnd = absEnd + delta;
|
|
12274
|
-
const newParentBase = parentBase + parentShift;
|
|
12275
|
-
const newStart = newAbsStart - newParentBase;
|
|
12276
|
-
const newEnd = newAbsEnd - newParentBase;
|
|
12277
|
-
const ownShift = newAbsStart - absStart;
|
|
12278
|
-
let changed = false;
|
|
12279
|
-
const newChildren = [];
|
|
12280
|
-
for (const c of node2.children) {
|
|
12281
|
-
const nc = editNode(c, at, delta, absStart, ownShift, stats);
|
|
12282
|
-
if (nc !== c) changed = true;
|
|
12283
|
-
newChildren.push(nc);
|
|
12439
|
+
if (captureRootTrivia && !rootTriviaClassifiedFromRunnable(entry) && !rootTriviaClassifiedFromRunnable(options.trivia)) {
|
|
12440
|
+
throw new TypeError(
|
|
12441
|
+
"run(): rootTrivia.select requires classifiedTrivia() at the root."
|
|
12442
|
+
);
|
|
12284
12443
|
}
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12444
|
+
const stamped = entry;
|
|
12445
|
+
assertHostModeCompatible(
|
|
12446
|
+
typeof entry === "function" ? stamped[FUSED_HOST_MODE] ?? "ast" : entry._meta.grammarHostMode ?? "ast",
|
|
12447
|
+
options.build,
|
|
12448
|
+
typeof entry === "function" && stamped[FUSED_HOST_ELIDED] === true
|
|
12449
|
+
);
|
|
12450
|
+
const ctx = {
|
|
12451
|
+
trackLines: false,
|
|
12452
|
+
...skipGlobalSinks ? { _pmProfile: profile } : {
|
|
12453
|
+
...captureRootTrivia ? {
|
|
12454
|
+
_rootTriviaLog: selectedRootLog,
|
|
12455
|
+
_rootTriviaKindIndex: selectedRootLabelIndex,
|
|
12456
|
+
_rootTriviaStrictScopes: true
|
|
12457
|
+
} : {},
|
|
12458
|
+
_errors: errors,
|
|
12459
|
+
...profile === void 0 ? {} : { _pmProfile: profile }
|
|
12460
|
+
},
|
|
12461
|
+
build: options.build,
|
|
12462
|
+
state: options.state,
|
|
12463
|
+
...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...triviaKindLabels ? { triviaKindLabels } : {} } : triviaKindLabels ? { triviaKindLabels } : {},
|
|
12464
|
+
...grammarScanSkip !== void 0 ? { scanSkip: grammarScanSkip } : {},
|
|
12465
|
+
...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {},
|
|
12466
|
+
...options.tolerant ? { _tolerant: true, _rec: REC } : {},
|
|
12467
|
+
...options.instrumentation === void 0 ? {} : options.instrumentation
|
|
12468
|
+
};
|
|
12469
|
+
const r = invoke(entry, input, 0, ctx);
|
|
12470
|
+
let unconsumedFrom = null;
|
|
12471
|
+
if (r.ok) {
|
|
12472
|
+
let pos = r.span?.end ?? 0;
|
|
12473
|
+
if (options.trivia && pos < input.length) {
|
|
12474
|
+
const t = invoke(options.trivia, input, pos, { trackLines: false });
|
|
12475
|
+
if (t.ok && t.span.end > pos) pos = t.span.end;
|
|
12476
|
+
}
|
|
12477
|
+
unconsumedFrom = pos < input.length ? pos : null;
|
|
12478
|
+
}
|
|
12479
|
+
return {
|
|
12480
|
+
ok: r.ok,
|
|
12481
|
+
value: r.ok ? r.value : void 0,
|
|
12482
|
+
span: r.span ?? { start: 0, end: 0 },
|
|
12483
|
+
expected: r.ok ? [] : r.expected ?? [],
|
|
12484
|
+
errors,
|
|
12485
|
+
...selectedRootLog !== void 0 && selectedRootLog.length > 0 ? {
|
|
12486
|
+
rootTrivia: {
|
|
12487
|
+
rows: selectedRootLog,
|
|
12488
|
+
select: rootTriviaSelection,
|
|
12489
|
+
index: buildRootTriviaIndex(selectedRootLog, rootTriviaSelection)
|
|
12490
|
+
}
|
|
12491
|
+
} : {},
|
|
12492
|
+
unconsumedFrom
|
|
12493
|
+
};
|
|
12494
|
+
}
|
|
12495
|
+
function run(entry, input, options = {}) {
|
|
12496
|
+
if (!options.profile) return guardRemovedFields(runOnce(entry, input, options));
|
|
12497
|
+
throw new TypeError(
|
|
12498
|
+
"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."
|
|
12499
|
+
);
|
|
12500
|
+
}
|
|
12501
|
+
|
|
12502
|
+
// src/analysis/choice-cost.ts
|
|
12503
|
+
var choiceSiteKey = (s) => s.path === "" ? s.rule : `${s.rule} \u203A ${s.path}`;
|
|
12504
|
+
function childrenOf5(d) {
|
|
12505
|
+
switch (d.tag) {
|
|
12506
|
+
case "sequence":
|
|
12507
|
+
case "choice":
|
|
12508
|
+
return d.parsers;
|
|
12509
|
+
case "dispatch":
|
|
12510
|
+
return [
|
|
12511
|
+
d.selector,
|
|
12512
|
+
...d.cases.map((c) => c.parser),
|
|
12513
|
+
...d.otherwise === void 0 ? [] : [d.otherwise]
|
|
12514
|
+
];
|
|
12515
|
+
case "skip":
|
|
12516
|
+
return [d.main, d.skipped];
|
|
12517
|
+
case "sepBy":
|
|
12518
|
+
return [d.parser, d.separator];
|
|
12519
|
+
case "recover":
|
|
12520
|
+
return [d.parser, d.sentinel];
|
|
12521
|
+
case "scanTo":
|
|
12522
|
+
return [d.sentinel, ...d.skip];
|
|
12523
|
+
case "grammar":
|
|
12524
|
+
return d.triviaParser ? [d.parser, d.triviaParser] : [d.parser];
|
|
12525
|
+
case "lazy":
|
|
12526
|
+
case "literal":
|
|
12527
|
+
case "regex":
|
|
12528
|
+
case "keywords":
|
|
12529
|
+
case "guard":
|
|
12530
|
+
case "unknown":
|
|
12531
|
+
return [];
|
|
12532
|
+
default: {
|
|
12533
|
+
const rec = d;
|
|
12534
|
+
return rec.parser ? [rec.parser] : [];
|
|
12535
|
+
}
|
|
12536
|
+
}
|
|
12537
|
+
}
|
|
12538
|
+
var ruleNameOf5 = (p) => p._ruleName;
|
|
12539
|
+
function slotLabel(d, index) {
|
|
12540
|
+
switch (d.tag) {
|
|
12541
|
+
case "sequence":
|
|
12542
|
+
return `seq[${index}]`;
|
|
12543
|
+
case "choice":
|
|
12544
|
+
return `choice[${index}]`;
|
|
12545
|
+
case "dispatch":
|
|
12546
|
+
return index === 0 ? "dispatch.selector" : `dispatch[${index - 1}]`;
|
|
12547
|
+
case "skip":
|
|
12548
|
+
return index === 0 ? "skip.main" : "skip.skipped";
|
|
12549
|
+
case "sepBy":
|
|
12550
|
+
return index === 0 ? "sepBy.item" : "sepBy.sep";
|
|
12551
|
+
case "recover":
|
|
12552
|
+
return index === 0 ? "recover.parser" : "recover.sentinel";
|
|
12553
|
+
case "scanTo":
|
|
12554
|
+
return index === 0 ? "scanTo.sentinel" : `scanTo.skip[${index - 1}]`;
|
|
12555
|
+
case "grammar":
|
|
12556
|
+
return index === 0 ? "grammar" : "grammar.trivia";
|
|
12557
|
+
case "node":
|
|
12558
|
+
return `node(${d.type ?? "anonymous"})`;
|
|
12559
|
+
case "label":
|
|
12560
|
+
return `label(${d.label})`;
|
|
12561
|
+
case "field":
|
|
12562
|
+
return `field(${d.name})`;
|
|
12563
|
+
default:
|
|
12564
|
+
return d.tag;
|
|
12565
|
+
}
|
|
12566
|
+
}
|
|
12567
|
+
function resolveRoot(p) {
|
|
12568
|
+
let cur = p;
|
|
12569
|
+
for (let hop = 0; hop < 8; hop++) {
|
|
12570
|
+
const d = cur._def;
|
|
12571
|
+
if (d.tag !== "lazy") return { ok: true, p: cur };
|
|
12572
|
+
let next;
|
|
12573
|
+
try {
|
|
12574
|
+
next = d.thunk();
|
|
12575
|
+
} catch {
|
|
12576
|
+
return { ok: false };
|
|
12577
|
+
}
|
|
12578
|
+
if (next === cur) return { ok: false };
|
|
12579
|
+
cur = next;
|
|
12580
|
+
}
|
|
12581
|
+
return { ok: false };
|
|
12582
|
+
}
|
|
12583
|
+
function walkRules(ruleMap) {
|
|
12584
|
+
assertAnalysable(ruleMap);
|
|
12585
|
+
const out = [];
|
|
12586
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12587
|
+
const unresolvedRoots = [];
|
|
12588
|
+
const visit = (p, rule, path) => {
|
|
12589
|
+
if (seen.has(p)) return;
|
|
12590
|
+
seen.add(p);
|
|
12591
|
+
const d = p._def;
|
|
12592
|
+
const own = ruleNameOf5(p);
|
|
12593
|
+
const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path };
|
|
12594
|
+
out.push({ p, d, site: here });
|
|
12595
|
+
const kids = childrenOf5(d);
|
|
12596
|
+
for (let i = 0; i < kids.length; i++) {
|
|
12597
|
+
const seg = slotLabel(d, i);
|
|
12598
|
+
visit(kids[i], here.rule, here.path === "" ? seg : `${here.path} \u203A ${seg}`);
|
|
12599
|
+
}
|
|
12600
|
+
};
|
|
12601
|
+
for (const [name, rule] of ruleMap) {
|
|
12602
|
+
const r = resolveRoot(rule);
|
|
12603
|
+
if (!r.ok) {
|
|
12604
|
+
unresolvedRoots.push(name);
|
|
12605
|
+
continue;
|
|
12606
|
+
}
|
|
12607
|
+
visit(r.p, name, "");
|
|
12608
|
+
}
|
|
12609
|
+
unresolvedRoots.sort();
|
|
12610
|
+
return { walked: out, unresolvedRoots };
|
|
12611
|
+
}
|
|
12612
|
+
function assertAnalysable(ruleMap) {
|
|
12613
|
+
if (ruleMap.length === 0) {
|
|
12614
|
+
throw new TypeError(
|
|
12615
|
+
"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)."
|
|
12616
|
+
);
|
|
12617
|
+
}
|
|
12618
|
+
for (const [name, rule] of ruleMap) {
|
|
12619
|
+
const def = rule?._def;
|
|
12620
|
+
if (def !== null && typeof def === "object" && typeof def.tag === "string") continue;
|
|
12621
|
+
throw new TypeError(
|
|
12622
|
+
`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.`
|
|
12623
|
+
);
|
|
12624
|
+
}
|
|
12625
|
+
}
|
|
12626
|
+
function leadingTermOfArm2(arm) {
|
|
12627
|
+
let d = arm._def;
|
|
12628
|
+
for (; ; ) {
|
|
12629
|
+
if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label") {
|
|
12630
|
+
d = d.parser._def;
|
|
12631
|
+
continue;
|
|
12632
|
+
}
|
|
12633
|
+
break;
|
|
12634
|
+
}
|
|
12635
|
+
if (d.tag !== "sequence") {
|
|
12636
|
+
return {
|
|
12637
|
+
reason: "not-a-sequence",
|
|
12638
|
+
detail: `arm is \`${d.tag}\`; only node/grammar/transform/label wrappers are peeled, because they are the ones that consume nothing before the sequence`
|
|
12639
|
+
};
|
|
12640
|
+
}
|
|
12641
|
+
if (d.parsers.length < 2) {
|
|
12642
|
+
return { reason: "sequence-shorter-than-2", detail: `sequence has ${d.parsers.length} term(s); nothing would remain after factoring` };
|
|
12643
|
+
}
|
|
12644
|
+
return { term: d.parsers[0] };
|
|
12645
|
+
}
|
|
12646
|
+
function leadingTermKey(term) {
|
|
12647
|
+
const d = term._def;
|
|
12648
|
+
if (d.tag === "literal") {
|
|
12649
|
+
if (d.caseInsensitive) {
|
|
12650
|
+
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` };
|
|
12651
|
+
}
|
|
12652
|
+
return { key: `L:${JSON.stringify(d.value)}`, render: JSON.stringify(d.value) };
|
|
12653
|
+
}
|
|
12654
|
+
if (d.tag === "regex") {
|
|
12655
|
+
return { key: `R:${JSON.stringify([d.source, d.flags])}`, render: `/${d.source}/${d.flags.replace(/[gy]/g, "")}` };
|
|
12656
|
+
}
|
|
12657
|
+
return {
|
|
12658
|
+
reason: "lead-not-concrete-terminal",
|
|
12659
|
+
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`
|
|
12660
|
+
};
|
|
12661
|
+
}
|
|
12662
|
+
function armLabel(arm) {
|
|
12663
|
+
const name = ruleNameOf5(arm);
|
|
12664
|
+
if (name !== void 0) return name;
|
|
12665
|
+
let d = arm._def;
|
|
12666
|
+
for (let hop = 0; hop < 8; hop++) {
|
|
12667
|
+
if (d.tag === "node" && d.type !== void 0) return `node(${d.type})`;
|
|
12668
|
+
if (d.tag === "label") return `label(${d.label})`;
|
|
12669
|
+
if (d.tag === "field") return `field(${d.name})`;
|
|
12670
|
+
if (d.tag === "literal") return JSON.stringify(d.value);
|
|
12671
|
+
if (d.tag === "regex") return `/${d.source}/`;
|
|
12672
|
+
if (d.tag === "keywords") return `keywords(${d.words.length})`;
|
|
12673
|
+
if (d.tag === "sequence" || d.tag === "choice") {
|
|
12674
|
+
const first = d.parsers[0];
|
|
12675
|
+
if (first === void 0) return d.tag;
|
|
12676
|
+
const inner = ruleNameOf5(first);
|
|
12677
|
+
if (inner !== void 0) return `${d.tag}(${inner}, \u2026)`;
|
|
12678
|
+
d = first._def;
|
|
12679
|
+
continue;
|
|
12680
|
+
}
|
|
12681
|
+
const rec = d;
|
|
12682
|
+
if (rec.parser === void 0) return d.tag;
|
|
12683
|
+
d = rec.parser._def;
|
|
12684
|
+
}
|
|
12685
|
+
return arm._def.tag;
|
|
12686
|
+
}
|
|
12687
|
+
function analyzeChoiceInventory(ruleMap) {
|
|
12688
|
+
const { walked, unresolvedRoots } = walkRules(ruleMap);
|
|
12689
|
+
const entries = [];
|
|
12690
|
+
for (const w of walked) {
|
|
12691
|
+
if (w.d.tag !== "choice") continue;
|
|
12692
|
+
const d = w.d;
|
|
12693
|
+
const arity = d.parsers.length;
|
|
12694
|
+
const strategy = d.strategy.tag;
|
|
12695
|
+
const gated = d.gates.some((g) => g !== null);
|
|
12696
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
12697
|
+
const armDeclines = [];
|
|
12698
|
+
for (let i = 0; i < arity; i++) {
|
|
12699
|
+
const lead = leadingTermOfArm2(d.parsers[i]);
|
|
12700
|
+
if ("reason" in lead) {
|
|
12701
|
+
armDeclines.push({ arm: i, reason: lead.reason, detail: lead.detail });
|
|
12702
|
+
continue;
|
|
12703
|
+
}
|
|
12704
|
+
const keyed = leadingTermKey(lead.term);
|
|
12705
|
+
if ("reason" in keyed) {
|
|
12706
|
+
armDeclines.push({ arm: i, reason: keyed.reason, detail: keyed.detail });
|
|
12707
|
+
continue;
|
|
12708
|
+
}
|
|
12709
|
+
const slot = byKey.get(keyed.key);
|
|
12710
|
+
if (slot === void 0) byKey.set(keyed.key, { render: keyed.render, members: [i] });
|
|
12711
|
+
else slot.members.push(i);
|
|
12712
|
+
}
|
|
12713
|
+
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));
|
|
12714
|
+
const factored = strategy === "sharedPrefix";
|
|
12715
|
+
let declineReason;
|
|
12716
|
+
if (!factored) {
|
|
12717
|
+
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";
|
|
12718
|
+
}
|
|
12719
|
+
const inGroups = /* @__PURE__ */ new Set();
|
|
12720
|
+
for (const g of groups) for (const m of g.members) inGroups.add(m);
|
|
12721
|
+
entries.push({
|
|
12722
|
+
site: w.site,
|
|
12723
|
+
siteKey: choiceSiteKey(w.site),
|
|
12724
|
+
arity,
|
|
12725
|
+
strategy,
|
|
12726
|
+
disjoint: d.disjoint,
|
|
12727
|
+
gated,
|
|
12728
|
+
groups,
|
|
12729
|
+
armDeclines,
|
|
12730
|
+
factored,
|
|
12731
|
+
...declineReason === void 0 ? {} : { declineReason },
|
|
12732
|
+
unfactoredArms: factored ? 0 : inGroups.size
|
|
12733
|
+
});
|
|
12734
|
+
}
|
|
12735
|
+
entries.sort((a, b) => a.siteKey < b.siteKey ? -1 : a.siteKey > b.siteKey ? 1 : 0);
|
|
12736
|
+
const backlog = entries.filter((e) => !e.factored && e.groups.length > 0 && e.declineReason !== "disjoint-dispatch" && e.declineReason !== "strategy-preempted");
|
|
12737
|
+
return {
|
|
12738
|
+
schema: "parseman.choice-inventory/1",
|
|
12739
|
+
rules: ruleMap.length,
|
|
12740
|
+
choiceSites: entries.length,
|
|
12741
|
+
factoredSites: entries.filter((e) => e.factored).length,
|
|
12742
|
+
backlogSites: backlog.length,
|
|
12743
|
+
backlogArms: backlog.reduce((n, e) => n + e.unfactoredArms, 0),
|
|
12744
|
+
unresolvedRoots,
|
|
12745
|
+
entries
|
|
12746
|
+
};
|
|
12747
|
+
}
|
|
12748
|
+
function compiledFirstCharGate(p) {
|
|
12749
|
+
const fs = p._meta.firstSet;
|
|
12750
|
+
if (fs.kind !== "any" && !canMatchEmptyAtStart2(p)) return fs;
|
|
12751
|
+
if (fs.kind !== "any" || canMatchEmptyAtStart2(p)) return null;
|
|
12752
|
+
let deep;
|
|
12753
|
+
try {
|
|
12754
|
+
deep = firstSetOf(p);
|
|
12755
|
+
} catch {
|
|
12756
|
+
return null;
|
|
12757
|
+
}
|
|
12758
|
+
return deep.kind === "ranges" ? deep : null;
|
|
12759
|
+
}
|
|
12760
|
+
function canMatchEmptyAtStart2(p) {
|
|
12761
|
+
const d = p._def;
|
|
12762
|
+
switch (d.tag) {
|
|
12763
|
+
case "regex":
|
|
12764
|
+
return regexMatchesEmpty2(d.source, d.flags);
|
|
12765
|
+
case "optional":
|
|
12766
|
+
case "many":
|
|
12767
|
+
return true;
|
|
12768
|
+
case "transform":
|
|
12769
|
+
case "label":
|
|
12770
|
+
case "field":
|
|
12771
|
+
return canMatchEmptyAtStart2(d.parser);
|
|
12772
|
+
case "literal":
|
|
12773
|
+
return d.value.length === 0;
|
|
12774
|
+
default:
|
|
12775
|
+
return false;
|
|
12776
|
+
}
|
|
12777
|
+
}
|
|
12778
|
+
function regexMatchesEmpty2(source, flags) {
|
|
12779
|
+
try {
|
|
12780
|
+
return new RegExp(`^(?:${source})$`, flags.replace(/[gy]/g, "")).test("");
|
|
12781
|
+
} catch {
|
|
12782
|
+
return true;
|
|
12783
|
+
}
|
|
12784
|
+
}
|
|
12785
|
+
function inSet(code, fs) {
|
|
12786
|
+
if (fs.kind === "any") return true;
|
|
12787
|
+
if (fs.kind === "empty") return false;
|
|
12788
|
+
for (const r of fs.ranges) if (code >= r.lo && code <= r.hi) return true;
|
|
12789
|
+
return false;
|
|
12790
|
+
}
|
|
12791
|
+
var TERMINAL_TAGS = /* @__PURE__ */ new Set(["literal", "regex", "keywords", "scanTo"]);
|
|
12792
|
+
var INSTRUMENTABLE = /* @__PURE__ */ new Set(["firstMatch", "sharedPrefix"]);
|
|
12793
|
+
function profileWastedWork(opts) {
|
|
12794
|
+
const { rules: rules2, corpus } = opts;
|
|
12795
|
+
const { walked, unresolvedRoots } = walkRules(rules2);
|
|
12796
|
+
const entryParser = typeof opts.entry === "string" ? rules2.find(([n]) => n === opts.entry)?.[1] : opts.entry;
|
|
12797
|
+
if (entryParser === void 0) {
|
|
12798
|
+
throw new TypeError(
|
|
12799
|
+
`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" : ""}).`
|
|
12800
|
+
);
|
|
12801
|
+
}
|
|
12802
|
+
if (corpus.length === 0) {
|
|
12803
|
+
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.");
|
|
12804
|
+
}
|
|
12805
|
+
for (const f of corpus) {
|
|
12806
|
+
if (f.text.length === 0) throw new TypeError(`choice-cost: corpus entry '${f.id}' is EMPTY.`);
|
|
12807
|
+
}
|
|
12808
|
+
let highWater = 0;
|
|
12809
|
+
const armRecords = [];
|
|
12810
|
+
const siteTotals = /* @__PURE__ */ new Map();
|
|
12811
|
+
const restore = [];
|
|
12812
|
+
const undoAll = () => {
|
|
12813
|
+
for (let i = restore.length - 1; i >= 0; i--) restore[i]();
|
|
12814
|
+
};
|
|
12815
|
+
let instrumentedSites = 0;
|
|
12816
|
+
let uninstrumentableSites = 0;
|
|
12817
|
+
try {
|
|
12818
|
+
for (const w of walked) {
|
|
12819
|
+
if (w.d.tag !== "choice") continue;
|
|
12820
|
+
const siteKey = choiceSiteKey(w.site);
|
|
12821
|
+
const strategy = w.d.strategy.tag;
|
|
12822
|
+
const canInstrument = INSTRUMENTABLE.has(strategy) && !w.d.disjoint;
|
|
12823
|
+
const rec = {
|
|
12824
|
+
siteKey,
|
|
12825
|
+
site: w.site,
|
|
12826
|
+
strategy,
|
|
12827
|
+
arity: w.d.parsers.length,
|
|
12828
|
+
instrumented: canInstrument,
|
|
12829
|
+
attempts: 0,
|
|
12830
|
+
failures: 0,
|
|
12831
|
+
wastedBytes: 0,
|
|
12832
|
+
gatedAttempts: 0,
|
|
12833
|
+
gatedFailures: 0,
|
|
12834
|
+
gatedWastedBytes: 0
|
|
12835
|
+
};
|
|
12836
|
+
if (siteTotals.has(siteKey)) {
|
|
12837
|
+
uninstrumentableSites++;
|
|
12838
|
+
continue;
|
|
12839
|
+
}
|
|
12840
|
+
siteTotals.set(siteKey, rec);
|
|
12841
|
+
if (!canInstrument) {
|
|
12842
|
+
uninstrumentableSites++;
|
|
12843
|
+
continue;
|
|
12844
|
+
}
|
|
12845
|
+
instrumentedSites++;
|
|
12846
|
+
const slots = w.d.parsers;
|
|
12847
|
+
for (let i = 0; i < slots.length; i++) {
|
|
12848
|
+
const original = slots[i];
|
|
12849
|
+
const gate2 = compiledFirstCharGate(original);
|
|
12850
|
+
const armRec = {
|
|
12851
|
+
site: w.site,
|
|
12852
|
+
siteKey,
|
|
12853
|
+
arm: i,
|
|
12854
|
+
label: armLabel(original),
|
|
12855
|
+
gate: gate2,
|
|
12856
|
+
attempts: 0,
|
|
12857
|
+
failures: 0,
|
|
12858
|
+
wastedBytes: 0,
|
|
12859
|
+
gatedAttempts: 0,
|
|
12860
|
+
gatedFailures: 0,
|
|
12861
|
+
gatedWastedBytes: 0
|
|
12862
|
+
};
|
|
12863
|
+
armRecords.push(armRec);
|
|
12864
|
+
const wrapper = {
|
|
12865
|
+
_tag: original._tag,
|
|
12866
|
+
_meta: original._meta,
|
|
12867
|
+
_def: original._def,
|
|
12868
|
+
parse(input, pos, ctx) {
|
|
12869
|
+
const code = pos < input.length ? input.codePointAt(pos) ?? -1 : -1;
|
|
12870
|
+
const entered = gate2 === null || code >= 0 && inSet(code, gate2);
|
|
12871
|
+
const saved = highWater;
|
|
12872
|
+
highWater = pos;
|
|
12873
|
+
const r = original.parse(input, pos, ctx);
|
|
12874
|
+
const reach = highWater;
|
|
12875
|
+
highWater = saved > reach ? saved : reach;
|
|
12876
|
+
armRec.attempts++;
|
|
12877
|
+
rec.attempts++;
|
|
12878
|
+
if (entered) {
|
|
12879
|
+
armRec.gatedAttempts++;
|
|
12880
|
+
rec.gatedAttempts++;
|
|
12881
|
+
}
|
|
12882
|
+
if (!r.ok) {
|
|
12883
|
+
armRec.failures++;
|
|
12884
|
+
rec.failures++;
|
|
12885
|
+
if (entered) {
|
|
12886
|
+
armRec.gatedFailures++;
|
|
12887
|
+
rec.gatedFailures++;
|
|
12888
|
+
}
|
|
12889
|
+
const wasted = reach - pos;
|
|
12890
|
+
if (wasted > 0) {
|
|
12891
|
+
armRec.wastedBytes += wasted;
|
|
12892
|
+
rec.wastedBytes += wasted;
|
|
12893
|
+
if (entered) {
|
|
12894
|
+
armRec.gatedWastedBytes += wasted;
|
|
12895
|
+
rec.gatedWastedBytes += wasted;
|
|
12896
|
+
}
|
|
12897
|
+
}
|
|
12898
|
+
}
|
|
12899
|
+
return r;
|
|
12900
|
+
}
|
|
12901
|
+
};
|
|
12902
|
+
const rn = ruleNameOf5(original);
|
|
12903
|
+
if (rn !== void 0) wrapper._ruleName = rn;
|
|
12904
|
+
slots[i] = wrapper;
|
|
12905
|
+
restore.push(() => {
|
|
12906
|
+
slots[i] = original;
|
|
12907
|
+
});
|
|
12908
|
+
}
|
|
12909
|
+
}
|
|
12910
|
+
if (instrumentedSites === 0) {
|
|
12911
|
+
throw new TypeError(
|
|
12912
|
+
"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."
|
|
12913
|
+
);
|
|
12914
|
+
}
|
|
12915
|
+
for (const w of walked) {
|
|
12916
|
+
if (!TERMINAL_TAGS.has(w.d.tag)) continue;
|
|
12917
|
+
const p = w.p;
|
|
12918
|
+
const original = p.parse;
|
|
12919
|
+
p.parse = ((input, pos, ctx) => {
|
|
12920
|
+
if (pos > highWater) highWater = pos;
|
|
12921
|
+
return original.call(w.p, input, pos, ctx);
|
|
12922
|
+
});
|
|
12923
|
+
restore.push(() => {
|
|
12924
|
+
p.parse = original;
|
|
12925
|
+
});
|
|
12926
|
+
}
|
|
12927
|
+
} catch (e) {
|
|
12928
|
+
undoAll();
|
|
12929
|
+
throw e;
|
|
12930
|
+
}
|
|
12931
|
+
let parsedOk = 0;
|
|
12932
|
+
let parsedFailed = 0;
|
|
12933
|
+
let corpusBytes = 0;
|
|
12934
|
+
try {
|
|
12935
|
+
const runner = opts.runner ?? ((e, input, options) => run(e, input, options));
|
|
12936
|
+
for (const f of corpus) {
|
|
12937
|
+
corpusBytes += f.text.length;
|
|
12938
|
+
highWater = 0;
|
|
12939
|
+
const r = runner(entryParser, f.text, opts.runOptions ?? {});
|
|
12940
|
+
if (r.ok) parsedOk++;
|
|
12941
|
+
else parsedFailed++;
|
|
12942
|
+
}
|
|
12943
|
+
} finally {
|
|
12944
|
+
undoAll();
|
|
12945
|
+
}
|
|
12946
|
+
const arms = armRecords.filter((a) => a.attempts > 0).map((a) => ({
|
|
12947
|
+
siteKey: a.siteKey,
|
|
12948
|
+
site: a.site,
|
|
12949
|
+
arm: a.arm,
|
|
12950
|
+
label: a.label,
|
|
12951
|
+
attempts: a.attempts,
|
|
12952
|
+
failures: a.failures,
|
|
12953
|
+
wastedBytes: a.wastedBytes,
|
|
12954
|
+
firstCharGated: a.gate !== null,
|
|
12955
|
+
gatedAttempts: a.gatedAttempts,
|
|
12956
|
+
gatedFailures: a.gatedFailures,
|
|
12957
|
+
gatedWastedBytes: a.gatedWastedBytes
|
|
12958
|
+
})).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);
|
|
12959
|
+
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));
|
|
12960
|
+
const minAttempts = opts.inversionMinAttempts ?? 4;
|
|
12961
|
+
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);
|
|
12962
|
+
return {
|
|
12963
|
+
schema: "parseman.wasted-work/1",
|
|
12964
|
+
corpusFiles: corpus.length,
|
|
12965
|
+
corpusBytes,
|
|
12966
|
+
parsedOk,
|
|
12967
|
+
parsedFailed,
|
|
12968
|
+
instrumentedSites,
|
|
12969
|
+
uninstrumentableSites,
|
|
12970
|
+
unresolvedRoots,
|
|
12971
|
+
totalWastedBytes: sites.reduce((n, s) => n + s.wastedBytes, 0),
|
|
12972
|
+
totalGatedWastedBytes: sites.reduce((n, s) => n + s.gatedWastedBytes, 0),
|
|
12973
|
+
arms,
|
|
12974
|
+
inversions,
|
|
12975
|
+
sites
|
|
12976
|
+
};
|
|
12977
|
+
}
|
|
12978
|
+
|
|
12979
|
+
// src/analysis/choice-cost-render.ts
|
|
12980
|
+
function groupDigits(n) {
|
|
12981
|
+
const s = String(Math.trunc(Math.abs(n)));
|
|
12982
|
+
let out = "";
|
|
12983
|
+
for (let i = 0; i < s.length; i++) {
|
|
12984
|
+
if (i > 0 && (s.length - i) % 3 === 0) out += ",";
|
|
12985
|
+
out += s[i];
|
|
12986
|
+
}
|
|
12987
|
+
return (n < 0 ? "-" : "") + out;
|
|
12988
|
+
}
|
|
12989
|
+
function bytes(n) {
|
|
12990
|
+
if (n < 1024) return `${groupDigits(n)} B`;
|
|
12991
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} kB`;
|
|
12992
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
12993
|
+
}
|
|
12994
|
+
var ESC = "\x1B";
|
|
12995
|
+
var ANSI = {
|
|
12996
|
+
dim: `${ESC}[2m`,
|
|
12997
|
+
bold: `${ESC}[1m`,
|
|
12998
|
+
red: `${ESC}[31m`,
|
|
12999
|
+
yellow: `${ESC}[33m`,
|
|
13000
|
+
cyan: `${ESC}[36m`,
|
|
13001
|
+
reset: `${ESC}[0m`
|
|
13002
|
+
};
|
|
13003
|
+
var paint = (on) => (code, s) => on ? `${ANSI[code]}${s}${ANSI.reset}` : s;
|
|
13004
|
+
var pad = (s, w) => s.length >= w ? s : s + " ".repeat(w - s.length);
|
|
13005
|
+
var padStart = (s, w) => s.length >= w ? s : " ".repeat(w - s.length) + s;
|
|
13006
|
+
var ARM_DECLINE_LINE = {
|
|
13007
|
+
"not-a-sequence": "arm is not a sequence, so there is no leading term to lift out",
|
|
13008
|
+
"sequence-shorter-than-2": "arm is a one-term sequence; lifting its only term would leave an empty arm",
|
|
13009
|
+
"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",
|
|
13010
|
+
"lead-not-concrete-terminal": "leading term is not a bare literal or regex; lifting it would change the arm\u2019s value or capture shape"
|
|
13011
|
+
};
|
|
13012
|
+
var SITE_DECLINE_LINE = {
|
|
13013
|
+
"disjoint-dispatch": "first-char dispatch already selects one arm \u2014 nothing is re-scanned",
|
|
13014
|
+
"gated-arms": "an arm carries a runtime gate; per-arm predicates are incompatible with factoring",
|
|
13015
|
+
"strategy-preempted": "a stronger non-backtracking strategy already applies",
|
|
13016
|
+
"fewer-than-two-arms": "fewer than two arms",
|
|
13017
|
+
"arms-not-factorable": "at least one arm cannot contribute a leading term (see below)",
|
|
13018
|
+
"leads-differ": "the arms do not all begin with the same term"
|
|
13019
|
+
};
|
|
13020
|
+
function leftFactorPreview(e) {
|
|
13021
|
+
if (e.factored) return null;
|
|
13022
|
+
if (e.groups.length !== 1) return null;
|
|
13023
|
+
const g = e.groups[0];
|
|
13024
|
+
if (g.members.length !== e.arity) return null;
|
|
13025
|
+
const tails = g.members.map((i) => `sequence(\u2026arm[${i}] tail\u2026)`).join(",\n ");
|
|
13026
|
+
return `sequence(
|
|
13027
|
+
${g.render === "" ? "<prefix>" : g.render.startsWith("/") ? `regex(${g.render})` : `literal(${g.render})`},
|
|
13028
|
+
choice(
|
|
13029
|
+
${tails},
|
|
13030
|
+
),
|
|
13031
|
+
)`;
|
|
13032
|
+
}
|
|
13033
|
+
function renderChoiceInventory(r, opts = {}) {
|
|
13034
|
+
const c = paint(opts.color === true);
|
|
13035
|
+
const limit = opts.limit ?? 20;
|
|
13036
|
+
const out = [];
|
|
13037
|
+
out.push(c("bold", "shared-prefix inventory"));
|
|
13038
|
+
out.push(c("dim", " Static grammar shape only. A site listed here may already cost nothing at runtime:"));
|
|
13039
|
+
out.push(c("dim", " compiled output gates each arm on its first character. Rank with profileWastedWork()."));
|
|
13040
|
+
out.push(` ${groupDigits(r.rules)} rules, ${groupDigits(r.choiceSites)} choice sites`);
|
|
13041
|
+
out.push(` ${groupDigits(r.factoredSites)} left-factored by the compiler`);
|
|
13042
|
+
out.push(` ${groupDigits(r.backlogSites)} sites where alternatives share a leading term and the compiler DECLINED (${groupDigits(r.backlogArms)} arms)`);
|
|
13043
|
+
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));
|
|
13044
|
+
if (backlog.length === 0) {
|
|
13045
|
+
out.push("");
|
|
13046
|
+
out.push(" no declined shared prefixes.");
|
|
13047
|
+
return out.join("\n");
|
|
13048
|
+
}
|
|
13049
|
+
out.push("");
|
|
13050
|
+
for (const e of backlog.slice(0, limit)) {
|
|
13051
|
+
out.push(` ${c("cyan", e.siteKey)} ${c("dim", `${e.arity} arms \xB7 ${e.strategy}`)}`);
|
|
13052
|
+
for (const g of e.groups) {
|
|
13053
|
+
out.push(` arms ${g.members.join(", ")} all begin with ${c("bold", g.render)}`);
|
|
13054
|
+
}
|
|
13055
|
+
out.push(` ${c("yellow", "declined")}: ${SITE_DECLINE_LINE[e.declineReason]}`);
|
|
13056
|
+
for (const a of e.armDeclines) {
|
|
13057
|
+
out.push(` arm[${a.arm}] \u2014 ${ARM_DECLINE_LINE[a.reason]}`);
|
|
13058
|
+
}
|
|
13059
|
+
const fix = leftFactorPreview(e);
|
|
13060
|
+
if (fix !== null) {
|
|
13061
|
+
out.push(` ${c("dim", "candidate rewrite (preview \u2014 not applied, not yet digest-verified):")}`);
|
|
13062
|
+
for (const line of fix.split("\n")) out.push(` ${line}`);
|
|
13063
|
+
}
|
|
13064
|
+
out.push("");
|
|
13065
|
+
}
|
|
13066
|
+
if (backlog.length > limit) out.push(` \u2026 and ${groupDigits(backlog.length - limit)} more (the report holds all of them)`);
|
|
13067
|
+
return out.join("\n");
|
|
13068
|
+
}
|
|
13069
|
+
function renderWastedWork(r, opts = {}) {
|
|
13070
|
+
const c = paint(opts.color === true);
|
|
13071
|
+
const limit = opts.limit ?? 20;
|
|
13072
|
+
const out = [];
|
|
13073
|
+
const removed = r.arms.reduce((n, a) => n + (a.attempts - a.gatedAttempts), 0);
|
|
13074
|
+
const totalAttempts = r.arms.reduce((n, a) => n + a.attempts, 0);
|
|
13075
|
+
out.push(c("bold", "wasted work \u2014 input bytes re-scanned after a failed alternative"));
|
|
13076
|
+
out.push(...interpretedBanner(c, removed, totalAttempts));
|
|
13077
|
+
out.push(` corpus: ${groupDigits(r.corpusFiles)} files, ${bytes(r.corpusBytes)} (${groupDigits(r.parsedOk)} parsed, ${groupDigits(r.parsedFailed)} failed)`);
|
|
13078
|
+
out.push(` sites: ${groupDigits(r.instrumentedSites)} instrumented, ${groupDigits(r.uninstrumentableSites)} not instrumentable`);
|
|
13079
|
+
if (r.unresolvedRoots.length > 0) {
|
|
13080
|
+
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`);
|
|
13081
|
+
}
|
|
13082
|
+
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)})`));
|
|
13083
|
+
const ranked = r.sites.filter((s) => s.gatedWastedBytes > 0 || s.wastedBytes > 0);
|
|
13084
|
+
if (ranked.length === 0) {
|
|
13085
|
+
out.push("");
|
|
13086
|
+
out.push(" no alternative failed on this corpus.");
|
|
13087
|
+
return out.join("\n");
|
|
13088
|
+
}
|
|
13089
|
+
out.push("");
|
|
13090
|
+
out.push(c("dim", ' per arm: "entered" counts what the COMPILED parser reaches; the parenthesised'));
|
|
13091
|
+
out.push(c("dim", " number is the interpreted count, shown only where the first-char gate differs."));
|
|
13092
|
+
out.push("");
|
|
13093
|
+
const w = Math.min(52, Math.max(...ranked.slice(0, limit).map((s) => s.siteKey.length)));
|
|
13094
|
+
for (const s of ranked.slice(0, limit)) {
|
|
13095
|
+
const share = r.totalGatedWastedBytes > 0 ? ` ${padStart((100 * s.gatedWastedBytes / r.totalGatedWastedBytes).toFixed(1) + "%", 7)}` : "";
|
|
13096
|
+
out.push(` ${c("cyan", pad(s.siteKey.slice(0, w), w))} ${padStart(bytes(s.gatedWastedBytes), 10)}${c("dim", share)}`);
|
|
13097
|
+
const arms = r.arms.filter((a) => a.siteKey === s.siteKey).sort((a, b) => a.arm - b.arm);
|
|
13098
|
+
for (const a of arms) {
|
|
13099
|
+
const entered = a.gatedAttempts === a.attempts ? groupDigits(a.attempts) : `${groupDigits(a.gatedAttempts)} (${groupDigits(a.attempts)} interp)`;
|
|
13100
|
+
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 };
|
|
13101
|
+
const cell = verdict.color === null ? pad(verdict.text, 34) : c(verdict.color, pad(verdict.text, 34));
|
|
13102
|
+
out.push(` ${padStart(String(a.arm), 2)} ${pad(a.label.slice(0, 30), 30)} ${cell} ${a.gatedWastedBytes > 0 ? padStart(bytes(a.gatedWastedBytes), 10) : ""}`);
|
|
13103
|
+
}
|
|
13104
|
+
out.push("");
|
|
13105
|
+
}
|
|
13106
|
+
if (ranked.length > limit) out.push(` \u2026 and ${groupDigits(ranked.length - limit)} more sites (the report holds all of them)`);
|
|
13107
|
+
if (r.inversions.length > 0) {
|
|
13108
|
+
out.push(c("bold", " ordering inversions \u2014 arm failed EVERY compiled entry while a later arm matched"));
|
|
13109
|
+
out.push(c("dim", " ranked by entries, not bytes: an arm can fail 100% of the time and re-scan nothing."));
|
|
13110
|
+
for (const a of r.inversions.slice(0, limit)) {
|
|
13111
|
+
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)}`);
|
|
13112
|
+
}
|
|
13113
|
+
if (r.inversions.length > limit) out.push(` \u2026 and ${groupDigits(r.inversions.length - limit)} more`);
|
|
13114
|
+
}
|
|
13115
|
+
return out.join("\n");
|
|
13116
|
+
}
|
|
13117
|
+
function interpretedBanner(c, removed, total) {
|
|
13118
|
+
const share = total > 0 ? ` \u2014 ${(100 * removed / total).toFixed(0)}% of interpreted arm entries` : "";
|
|
13119
|
+
return [
|
|
13120
|
+
c("yellow", " MEASURED IN THE INTERPRETER. Compiled output gates each arm on its first"),
|
|
13121
|
+
c("yellow", ` character; ${groupDigits(removed)} entries below never happen in the shipped parser${share}.`),
|
|
13122
|
+
c("dim", " Counts shown are the modelled COMPILED ones. Byte totals are unaffected by the"),
|
|
13123
|
+
c("dim", " gate (a gated-out arm consumes nothing); entry counts and failure RATES are not.")
|
|
13124
|
+
];
|
|
13125
|
+
}
|
|
13126
|
+
|
|
13127
|
+
// src/analysis/choice-cost-gate.ts
|
|
13128
|
+
var KIND_ORDER = [
|
|
13129
|
+
"invalid-baseline",
|
|
13130
|
+
"unmeasurable",
|
|
13131
|
+
"ceiling",
|
|
13132
|
+
"drift",
|
|
13133
|
+
"shrank",
|
|
13134
|
+
"inversion",
|
|
13135
|
+
"unbaselined",
|
|
13136
|
+
"stale"
|
|
13137
|
+
];
|
|
13138
|
+
var pct = (actual, base) => base === 0 ? actual === 0 ? 0 : Infinity : (actual / base - 1) * 100;
|
|
13139
|
+
function checkWastedWork(reports, baseline, policy = {}) {
|
|
13140
|
+
const breaches = [];
|
|
13141
|
+
const tolerance = policy.driftTolerancePct ?? 1;
|
|
13142
|
+
const add = (kind, key, detail) => {
|
|
13143
|
+
breaches.push({ kind, key, detail });
|
|
13144
|
+
};
|
|
13145
|
+
const finish = (checkedCorpora, checkedSites2) => {
|
|
13146
|
+
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));
|
|
13147
|
+
return {
|
|
13148
|
+
schema: "parseman.wasted-work-verdict/1",
|
|
13149
|
+
ok: breaches.length === 0,
|
|
13150
|
+
breaches,
|
|
13151
|
+
checkedCorpora,
|
|
13152
|
+
checkedSites: checkedSites2
|
|
13153
|
+
};
|
|
13154
|
+
};
|
|
13155
|
+
const b = baseline;
|
|
13156
|
+
if (b === null || b === void 0 || typeof b !== "object") {
|
|
13157
|
+
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");
|
|
13158
|
+
return finish(0, 0);
|
|
13159
|
+
}
|
|
13160
|
+
if (b.schema !== "parseman.wasted-work-baseline/1") {
|
|
13161
|
+
add("invalid-baseline", "<baseline>", `baseline schema is ${JSON.stringify(b.schema)}, expected "parseman.wasted-work-baseline/1"`);
|
|
13162
|
+
return finish(0, 0);
|
|
13163
|
+
}
|
|
13164
|
+
const isRecord = (v2) => typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
|
|
13165
|
+
if (!isRecord(b.totals) || !isRecord(b.sites)) {
|
|
13166
|
+
add("invalid-baseline", "<baseline>", "baseline `totals` or `sites` is missing or is not an object");
|
|
13167
|
+
return finish(0, 0);
|
|
13168
|
+
}
|
|
13169
|
+
if (Object.keys(b.totals).length === 0) {
|
|
13170
|
+
add("invalid-baseline", "<baseline>", "baseline records ZERO corpora");
|
|
13171
|
+
return finish(0, 0);
|
|
13172
|
+
}
|
|
13173
|
+
const finite = (v2) => typeof v2 === "number" && Number.isFinite(v2);
|
|
13174
|
+
for (const [id, t] of Object.entries(b.totals).sort(([x], [y]) => x < y ? -1 : 1)) {
|
|
13175
|
+
if (!isRecord(t) || !finite(t.corpusBytes) || !finite(t.totalWastedBytes)) {
|
|
13176
|
+
add("invalid-baseline", id, "baseline entry has no finite numeric `corpusBytes` / `totalWastedBytes` \u2014 the comparison against it would silently produce NaN and no breach");
|
|
13177
|
+
}
|
|
13178
|
+
}
|
|
13179
|
+
for (const [key, v2] of Object.entries(b.sites).sort(([x], [y]) => x < y ? -1 : 1)) {
|
|
13180
|
+
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");
|
|
13181
|
+
}
|
|
13182
|
+
if (breaches.length > 0) return finish(0, 0);
|
|
13183
|
+
if (policy.ceilingRatio !== void 0) {
|
|
13184
|
+
for (const [id, t] of Object.entries(b.totals).sort(([x], [y]) => x < y ? -1 : 1)) {
|
|
13185
|
+
if (t.corpusBytes > 0 && t.totalWastedBytes / t.corpusBytes > policy.ceilingRatio) {
|
|
13186
|
+
add(
|
|
13187
|
+
"invalid-baseline",
|
|
13188
|
+
id,
|
|
13189
|
+
`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`
|
|
13190
|
+
);
|
|
13191
|
+
}
|
|
13192
|
+
}
|
|
13193
|
+
if (breaches.length > 0) return finish(0, 0);
|
|
13194
|
+
}
|
|
13195
|
+
if (Object.keys(reports).length === 0) {
|
|
13196
|
+
add("unmeasurable", "<reports>", "no corpus was measured \u2014 refusing to report a pass on an empty measurement");
|
|
13197
|
+
return finish(0, 0);
|
|
13198
|
+
}
|
|
13199
|
+
let checkedSites = 0;
|
|
13200
|
+
for (const id of Object.keys(reports).sort()) {
|
|
13201
|
+
const r = reports[id];
|
|
13202
|
+
if (r.corpusFiles === 0) {
|
|
13203
|
+
add("unmeasurable", id, "report covers ZERO corpus files");
|
|
13204
|
+
continue;
|
|
13205
|
+
}
|
|
13206
|
+
if (r.instrumentedSites === 0) {
|
|
13207
|
+
add("unmeasurable", id, "report instrumented ZERO choice sites \u2014 the total below would be zero for a reason unrelated to the grammar");
|
|
13208
|
+
continue;
|
|
13209
|
+
}
|
|
13210
|
+
if (r.unresolvedRoots.length > 0) {
|
|
13211
|
+
add(
|
|
13212
|
+
"unmeasurable",
|
|
13213
|
+
id,
|
|
13214
|
+
`${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`
|
|
13215
|
+
);
|
|
13216
|
+
continue;
|
|
13217
|
+
}
|
|
13218
|
+
const base = b.totals[id];
|
|
13219
|
+
if (base === void 0) {
|
|
13220
|
+
add("unbaselined", id, `measured but absent from the baseline (${r.totalWastedBytes} wasted bytes over ${r.corpusBytes})`);
|
|
13221
|
+
continue;
|
|
13222
|
+
}
|
|
13223
|
+
if (policy.ceilingRatio !== void 0 && r.corpusBytes > 0) {
|
|
13224
|
+
const ratio = r.totalWastedBytes / r.corpusBytes;
|
|
13225
|
+
if (ratio > policy.ceilingRatio) {
|
|
13226
|
+
add("ceiling", id, `${r.totalWastedBytes} wasted / ${r.corpusBytes} corpus bytes = ${ratio.toFixed(3)}x, over the ${policy.ceilingRatio}x ceiling`);
|
|
13227
|
+
}
|
|
13228
|
+
}
|
|
13229
|
+
if (r.corpusBytes !== base.corpusBytes) {
|
|
13230
|
+
add("unmeasurable", id, `corpus changed: baseline ${base.corpusBytes} bytes, measured ${r.corpusBytes} \u2014 totals are not comparable until the baseline is refreshed`);
|
|
13231
|
+
continue;
|
|
13232
|
+
}
|
|
13233
|
+
const d = pct(r.totalWastedBytes, base.totalWastedBytes);
|
|
13234
|
+
if (d > tolerance) {
|
|
13235
|
+
add("drift", id, `total wasted ${base.totalWastedBytes} -> ${r.totalWastedBytes} bytes (${d >= 0 ? "+" : ""}${d.toFixed(2)}%, band \xB1${tolerance}%)`);
|
|
13236
|
+
} else if (d < -tolerance) {
|
|
13237
|
+
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`);
|
|
13238
|
+
}
|
|
13239
|
+
if (r.totalGatedWastedBytes !== r.totalWastedBytes) {
|
|
13240
|
+
const dg = pct(r.totalGatedWastedBytes, base.totalWastedBytes);
|
|
13241
|
+
if (dg > tolerance) {
|
|
13242
|
+
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`);
|
|
13243
|
+
} else if (dg < -tolerance) {
|
|
13244
|
+
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`);
|
|
13245
|
+
}
|
|
13246
|
+
}
|
|
13247
|
+
for (const s of r.sites) {
|
|
13248
|
+
const key = `${id}::${s.siteKey}`;
|
|
13249
|
+
const bs = b.sites[key];
|
|
13250
|
+
if (bs === void 0) {
|
|
13251
|
+
if (s.wastedBytes > 0) add("unbaselined", key, `site measured ${s.wastedBytes} wasted bytes but has no baseline entry`);
|
|
13252
|
+
continue;
|
|
13253
|
+
}
|
|
13254
|
+
checkedSites++;
|
|
13255
|
+
const sd = pct(s.wastedBytes, bs);
|
|
13256
|
+
if (sd > tolerance) add("drift", key, `${bs} -> ${s.wastedBytes} bytes (${sd >= 0 ? "+" : ""}${sd.toFixed(2)}%, band \xB1${tolerance}%)`);
|
|
13257
|
+
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`);
|
|
13258
|
+
if (s.gatedWastedBytes !== s.wastedBytes) {
|
|
13259
|
+
const sg = pct(s.gatedWastedBytes, bs);
|
|
13260
|
+
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})`);
|
|
13261
|
+
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})`);
|
|
13262
|
+
}
|
|
13263
|
+
}
|
|
13264
|
+
if (policy.failOnInversions === true) {
|
|
13265
|
+
for (const inv of r.inversions) {
|
|
13266
|
+
add(
|
|
13267
|
+
"inversion",
|
|
13268
|
+
`${id}::${inv.siteKey}#${inv.arm}`,
|
|
13269
|
+
// The GATED columns, because that is what `inversions` was computed from
|
|
13270
|
+
// (choice-cost.ts filters on `gatedAttempts`/`gatedFailures`). Printing the
|
|
13271
|
+
// interpreted counts here would state "failed all N" with an N the condition
|
|
13272
|
+
// never tested — the two differ wherever `firstCharGated` is true.
|
|
13273
|
+
`arm ${inv.arm} (${inv.label}) failed all ${inv.gatedAttempts} compiled entries while a later arm matched; ${inv.gatedWastedBytes} bytes re-scanned`
|
|
13274
|
+
);
|
|
13275
|
+
}
|
|
13276
|
+
}
|
|
13277
|
+
}
|
|
13278
|
+
const measuredKeys = /* @__PURE__ */ new Set();
|
|
13279
|
+
for (const id of Object.keys(reports)) for (const s of reports[id].sites) measuredKeys.add(`${id}::${s.siteKey}`);
|
|
13280
|
+
for (const key of Object.keys(b.sites).sort()) {
|
|
13281
|
+
if (!measuredKeys.has(key)) {
|
|
13282
|
+
add("stale", key, "baselined site matches no measured site \u2014 a rule was renamed or removed, and ignoring it silently shrinks the gated set");
|
|
13283
|
+
}
|
|
13284
|
+
}
|
|
13285
|
+
for (const id of Object.keys(b.totals).sort()) {
|
|
13286
|
+
if (reports[id] === void 0) add("stale", id, "baselined corpus was not measured");
|
|
13287
|
+
}
|
|
13288
|
+
return finish(Object.keys(reports).length, checkedSites);
|
|
13289
|
+
}
|
|
13290
|
+
function buildWastedWorkBaseline(reports, meta) {
|
|
13291
|
+
const totals = {};
|
|
13292
|
+
const sites = {};
|
|
13293
|
+
for (const id of Object.keys(reports).sort()) {
|
|
13294
|
+
const r = reports[id];
|
|
13295
|
+
totals[id] = { corpusBytes: r.corpusBytes, totalWastedBytes: r.totalWastedBytes, instrumentedSites: r.instrumentedSites };
|
|
13296
|
+
for (const s of [...r.sites].sort((a, c) => a.siteKey < c.siteKey ? -1 : 1)) {
|
|
13297
|
+
if (s.wastedBytes > 0) sites[`${id}::${s.siteKey}`] = s.wastedBytes;
|
|
13298
|
+
}
|
|
13299
|
+
}
|
|
13300
|
+
return { schema: "parseman.wasted-work-baseline/1", gitRev: meta.gitRev, updatedAt: meta.updatedAt, totals, sites };
|
|
13301
|
+
}
|
|
13302
|
+
|
|
13303
|
+
// src/combinators/gate.ts
|
|
13304
|
+
function gate(predicate) {
|
|
13305
|
+
const meta = {
|
|
13306
|
+
firstSet: { kind: "any" },
|
|
13307
|
+
canMatchNewline: false,
|
|
13308
|
+
isTrivia: false
|
|
13309
|
+
};
|
|
13310
|
+
return {
|
|
13311
|
+
_tag: "guard",
|
|
13312
|
+
_meta: meta,
|
|
13313
|
+
_def: { tag: "guard", predicate },
|
|
13314
|
+
parse(_input, pos, ctx) {
|
|
13315
|
+
if (predicate(ctx.state))
|
|
13316
|
+
return { ok: true, value: null, span: { start: pos, end: pos } };
|
|
13317
|
+
return { ok: false, expected: ["guard"], span: { start: pos, end: pos } };
|
|
13318
|
+
}
|
|
13319
|
+
};
|
|
13320
|
+
}
|
|
13321
|
+
|
|
13322
|
+
// src/combinators/completions.ts
|
|
13323
|
+
function completionsAt(target, input, offset, options = {}) {
|
|
13324
|
+
const probe = { offset, best: null };
|
|
13325
|
+
const ctx = { trackLines: false, _probe: probe, ...options.tolerant ? { _tolerant: true, _rec: REC } : {} };
|
|
13326
|
+
const sliced = input.slice(0, offset);
|
|
13327
|
+
const result = "parseWithContext" in target ? target.parseWithContext(sliced, ctx, 0) : target.parse(sliced, 0, ctx);
|
|
13328
|
+
if (result.ok) return options.tolerant ? probe.best?.expected ?? [] : [];
|
|
13329
|
+
const best = deeperFail(probe.best, result);
|
|
13330
|
+
return best?.expected ?? [];
|
|
13331
|
+
}
|
|
13332
|
+
function deeperFail(a, b) {
|
|
13333
|
+
if (!a) return b;
|
|
13334
|
+
if (!b) return a;
|
|
13335
|
+
return a.span.start >= b.span.start ? a : b;
|
|
13336
|
+
}
|
|
13337
|
+
|
|
13338
|
+
// src/cst/relative-spans.ts
|
|
13339
|
+
function relativize(node2, parentBase = 0) {
|
|
13340
|
+
return {
|
|
13341
|
+
start: node2.start - parentBase,
|
|
13342
|
+
end: node2.end - parentBase,
|
|
13343
|
+
children: node2.children.map((c) => relativize(c, node2.start))
|
|
13344
|
+
};
|
|
13345
|
+
}
|
|
13346
|
+
function absolutize(node2, parentBase = 0) {
|
|
13347
|
+
const start = parentBase + node2.start;
|
|
13348
|
+
return {
|
|
13349
|
+
start,
|
|
13350
|
+
end: parentBase + node2.end,
|
|
13351
|
+
children: node2.children.map((c) => absolutize(c, start))
|
|
13352
|
+
};
|
|
13353
|
+
}
|
|
13354
|
+
function absoluteSpanAt(root, path) {
|
|
13355
|
+
let base = 0;
|
|
13356
|
+
let node2 = root;
|
|
13357
|
+
for (const idx of path) {
|
|
13358
|
+
base += node2.start;
|
|
13359
|
+
const child = node2.children[idx];
|
|
13360
|
+
if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
|
|
13361
|
+
node2 = child;
|
|
13362
|
+
}
|
|
13363
|
+
return { start: base + node2.start, end: base + node2.end };
|
|
13364
|
+
}
|
|
13365
|
+
function relativizeCST(node2, parentBase = 0) {
|
|
13366
|
+
const span = { start: node2.span.start - parentBase, end: node2.span.end - parentBase };
|
|
13367
|
+
if (node2.children) {
|
|
13368
|
+
const base = node2.span.start;
|
|
13369
|
+
return { ...node2, span, children: node2.children.map((c) => relativizeCST(c, base)) };
|
|
13370
|
+
}
|
|
13371
|
+
return { ...node2, span };
|
|
13372
|
+
}
|
|
13373
|
+
function absolutizeCST(node2, parentBase = 0) {
|
|
13374
|
+
const start = parentBase + node2.span.start;
|
|
13375
|
+
const span = { start, end: parentBase + node2.span.end };
|
|
13376
|
+
if (node2.children) {
|
|
13377
|
+
return { ...node2, span, children: node2.children.map((c) => absolutizeCST(c, start)) };
|
|
13378
|
+
}
|
|
13379
|
+
return { ...node2, span };
|
|
13380
|
+
}
|
|
13381
|
+
function absoluteSpanCST(root, path) {
|
|
13382
|
+
let base = 0;
|
|
13383
|
+
let node2 = root;
|
|
13384
|
+
for (const idx of path) {
|
|
13385
|
+
base += node2.span.start;
|
|
13386
|
+
const child = node2.children?.[idx];
|
|
13387
|
+
if (!child) throw new RangeError(`absoluteSpanCST: no child ${idx} on path ${path.join(".")}`);
|
|
13388
|
+
node2 = child;
|
|
13389
|
+
}
|
|
13390
|
+
return { start: base + node2.span.start, end: base + node2.span.end };
|
|
13391
|
+
}
|
|
13392
|
+
function shiftAbsolute(node2, at, delta) {
|
|
13393
|
+
const s = node2.start >= at ? node2.start + delta : node2.start;
|
|
13394
|
+
const e = node2.end >= at ? node2.end + delta : node2.end;
|
|
13395
|
+
return {
|
|
13396
|
+
start: s,
|
|
13397
|
+
end: e,
|
|
13398
|
+
children: node2.children.map((c) => shiftAbsolute(c, at, delta))
|
|
13399
|
+
};
|
|
13400
|
+
}
|
|
13401
|
+
function applyEdit(root, at, delta, stats) {
|
|
13402
|
+
return editNode(root, at, delta, 0, 0, stats);
|
|
13403
|
+
}
|
|
13404
|
+
function editNode(node2, at, delta, parentBase, parentShift, stats) {
|
|
13405
|
+
const absStart = parentBase + node2.start;
|
|
13406
|
+
const absEnd = parentBase + node2.end;
|
|
13407
|
+
if (absEnd < at) return node2;
|
|
13408
|
+
const startShifts = absStart >= at;
|
|
13409
|
+
if (startShifts && parentShift === delta) return node2;
|
|
13410
|
+
if (stats) stats.allocated++;
|
|
13411
|
+
const newAbsStart = startShifts ? absStart + delta : absStart;
|
|
13412
|
+
const newAbsEnd = absEnd + delta;
|
|
13413
|
+
const newParentBase = parentBase + parentShift;
|
|
13414
|
+
const newStart = newAbsStart - newParentBase;
|
|
13415
|
+
const newEnd = newAbsEnd - newParentBase;
|
|
13416
|
+
const ownShift = newAbsStart - absStart;
|
|
13417
|
+
let changed = false;
|
|
13418
|
+
const newChildren = [];
|
|
13419
|
+
for (const c of node2.children) {
|
|
13420
|
+
const nc = editNode(c, at, delta, absStart, ownShift, stats);
|
|
13421
|
+
if (nc !== c) changed = true;
|
|
13422
|
+
newChildren.push(nc);
|
|
13423
|
+
}
|
|
13424
|
+
return {
|
|
13425
|
+
start: newStart,
|
|
13426
|
+
end: newEnd,
|
|
13427
|
+
children: changed ? newChildren : node2.children
|
|
13428
|
+
};
|
|
13429
|
+
}
|
|
13430
|
+
|
|
13431
|
+
// src/functional/doc.ts
|
|
13432
|
+
function mkCtx(rawState, build, tolerant) {
|
|
13433
|
+
const state = rawState ?? void 0;
|
|
13434
|
+
return tolerant ? { trackLines: false, state, build, _tolerant: true, _rec: REC, _errors: [] } : { trackLines: false, state, build };
|
|
13435
|
+
}
|
|
13436
|
+
function asRuleFn(entry) {
|
|
13437
|
+
return typeof entry === "function" ? entry : (input, pos, ctx) => entry.parse(input, pos, ctx);
|
|
12299
13438
|
}
|
|
12300
13439
|
function defOf(entry) {
|
|
12301
13440
|
return typeof entry === "object" && entry !== null && "_def" in entry ? entry._def : void 0;
|
|
@@ -12700,145 +13839,6 @@ function parseDoc(registry, rootRule, input, opts = {}) {
|
|
|
12700
13839
|
return new ParseDocImpl(registry, rootRule, opts, { abs: null }, [fail], null, input);
|
|
12701
13840
|
}
|
|
12702
13841
|
|
|
12703
|
-
// src/functional/run.ts
|
|
12704
|
-
var SELECT_EXAMPLE = "run(entry, input, { rootTrivia: { select: ['blockComment', 'lineComment'] } })";
|
|
12705
|
-
var REMOVED_RUN_RESULT_FIELDS = [
|
|
12706
|
-
[
|
|
12707
|
-
"triviaMap",
|
|
12708
|
-
`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.`
|
|
12709
|
-
],
|
|
12710
|
-
[
|
|
12711
|
-
"triviaLog",
|
|
12712
|
-
`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.`
|
|
12713
|
-
],
|
|
12714
|
-
[
|
|
12715
|
-
"triviaKindLabels",
|
|
12716
|
-
`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\`.`
|
|
12717
|
-
]
|
|
12718
|
-
];
|
|
12719
|
-
function guardRemovedFields(result) {
|
|
12720
|
-
for (const [name, detail] of REMOVED_RUN_RESULT_FIELDS) {
|
|
12721
|
-
Object.defineProperty(result, name, {
|
|
12722
|
-
configurable: true,
|
|
12723
|
-
enumerable: false,
|
|
12724
|
-
get() {
|
|
12725
|
-
throw new TypeError(
|
|
12726
|
-
`RunResult.${name} was REMOVED in parseman 0.44.0 and has no default replacement. ${detail}`
|
|
12727
|
-
);
|
|
12728
|
-
}
|
|
12729
|
-
});
|
|
12730
|
-
}
|
|
12731
|
-
return result;
|
|
12732
|
-
}
|
|
12733
|
-
var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
|
|
12734
|
-
function triviaKindLabelsFromRunnable(r) {
|
|
12735
|
-
if (!r) return void 0;
|
|
12736
|
-
if (typeof r === "function") return r._meta?.triviaKindLabels;
|
|
12737
|
-
const grammarTrivia = r._meta.grammarTrivia;
|
|
12738
|
-
return grammarTrivia?._meta.triviaKindLabels ?? r._meta.triviaKindLabels ?? (r._def.tag === "grammar" ? r._def.triviaParser?._meta.triviaKindLabels : void 0);
|
|
12739
|
-
}
|
|
12740
|
-
function rootTriviaClassifiedFromRunnable(r) {
|
|
12741
|
-
if (!r) return false;
|
|
12742
|
-
if (typeof r === "function") return r._meta?.rootTriviaClassified === true;
|
|
12743
|
-
const grammarTrivia = r._meta.grammarTrivia;
|
|
12744
|
-
return grammarTrivia?._meta.rootTriviaClassified === true || r._meta.rootTriviaClassified === true;
|
|
12745
|
-
}
|
|
12746
|
-
function makeSelectedRootLabelIndex(labels) {
|
|
12747
|
-
const index = /* @__PURE__ */ Object.create(null);
|
|
12748
|
-
for (let i = 0; i < labels.length; i++) {
|
|
12749
|
-
if (index[labels[i]] === void 0) index[labels[i]] = i;
|
|
12750
|
-
}
|
|
12751
|
-
return index;
|
|
12752
|
-
}
|
|
12753
|
-
function runOnce(entry, input, options, phase, profileState) {
|
|
12754
|
-
if (typeof entry !== "function" && typeof entry?.parse !== "function") {
|
|
12755
|
-
throw new TypeError(
|
|
12756
|
-
`run(): start production is ${entry === null ? "null" : typeof entry}, not a rule \u2014 the requested grammar rule does not exist (check the rule name).`
|
|
12757
|
-
);
|
|
12758
|
-
}
|
|
12759
|
-
const rootTriviaSelection = options.rootTrivia?.select;
|
|
12760
|
-
const captureRootTrivia = rootTriviaSelection !== void 0 && rootTriviaSelection.length > 0;
|
|
12761
|
-
const selectedRootLog = captureRootTrivia ? [] : void 0;
|
|
12762
|
-
const selectedRootLabelIndex = captureRootTrivia ? makeSelectedRootLabelIndex(rootTriviaSelection) : void 0;
|
|
12763
|
-
const errors = [];
|
|
12764
|
-
const profile = profileState ?? (phase === void 0 ? void 0 : { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 });
|
|
12765
|
-
const skipGlobalSinks = phase === "recognizer" || phase === "capture";
|
|
12766
|
-
const grammarTrivia = typeof entry !== "function" ? entry._meta.grammarTrivia : void 0;
|
|
12767
|
-
const grammarScanSkip = typeof entry !== "function" ? entry._meta.grammarScanSkip : void 0;
|
|
12768
|
-
const triviaKindLabels = triviaKindLabelsFromRunnable(entry) ?? triviaKindLabelsFromRunnable(options.trivia);
|
|
12769
|
-
if (captureRootTrivia && triviaKindLabels === void 0) {
|
|
12770
|
-
throw new TypeError("run(): rootTrivia.select requires labeled grammar trivia.");
|
|
12771
|
-
}
|
|
12772
|
-
if (captureRootTrivia) {
|
|
12773
|
-
for (const label2 of rootTriviaSelection) {
|
|
12774
|
-
if (!triviaKindLabels.includes(label2)) {
|
|
12775
|
-
throw new TypeError(`run(): rootTrivia.select contains unknown trivia label ${JSON.stringify(label2)}.`);
|
|
12776
|
-
}
|
|
12777
|
-
}
|
|
12778
|
-
}
|
|
12779
|
-
if (captureRootTrivia && !rootTriviaClassifiedFromRunnable(entry) && !rootTriviaClassifiedFromRunnable(options.trivia)) {
|
|
12780
|
-
throw new TypeError(
|
|
12781
|
-
"run(): rootTrivia.select requires classifiedTrivia() at the root."
|
|
12782
|
-
);
|
|
12783
|
-
}
|
|
12784
|
-
const stamped = entry;
|
|
12785
|
-
assertHostModeCompatible(
|
|
12786
|
-
typeof entry === "function" ? stamped[FUSED_HOST_MODE] ?? "ast" : entry._meta.grammarHostMode ?? "ast",
|
|
12787
|
-
options.build,
|
|
12788
|
-
typeof entry === "function" && stamped[FUSED_HOST_ELIDED] === true
|
|
12789
|
-
);
|
|
12790
|
-
const ctx = {
|
|
12791
|
-
trackLines: false,
|
|
12792
|
-
...skipGlobalSinks ? { _pmProfile: profile } : {
|
|
12793
|
-
...captureRootTrivia ? {
|
|
12794
|
-
_rootTriviaLog: selectedRootLog,
|
|
12795
|
-
_rootTriviaKindIndex: selectedRootLabelIndex,
|
|
12796
|
-
_rootTriviaStrictScopes: true
|
|
12797
|
-
} : {},
|
|
12798
|
-
_errors: errors,
|
|
12799
|
-
...profile === void 0 ? {} : { _pmProfile: profile }
|
|
12800
|
-
},
|
|
12801
|
-
build: options.build,
|
|
12802
|
-
state: options.state,
|
|
12803
|
-
...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...triviaKindLabels ? { triviaKindLabels } : {} } : triviaKindLabels ? { triviaKindLabels } : {},
|
|
12804
|
-
...grammarScanSkip !== void 0 ? { scanSkip: grammarScanSkip } : {},
|
|
12805
|
-
...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {},
|
|
12806
|
-
...options.tolerant ? { _tolerant: true, _rec: REC } : {},
|
|
12807
|
-
...options.instrumentation === void 0 ? {} : options.instrumentation
|
|
12808
|
-
};
|
|
12809
|
-
const r = invoke(entry, input, 0, ctx);
|
|
12810
|
-
let unconsumedFrom = null;
|
|
12811
|
-
if (r.ok) {
|
|
12812
|
-
let pos = r.span?.end ?? 0;
|
|
12813
|
-
if (options.trivia && pos < input.length) {
|
|
12814
|
-
const t = invoke(options.trivia, input, pos, { trackLines: false });
|
|
12815
|
-
if (t.ok && t.span.end > pos) pos = t.span.end;
|
|
12816
|
-
}
|
|
12817
|
-
unconsumedFrom = pos < input.length ? pos : null;
|
|
12818
|
-
}
|
|
12819
|
-
return {
|
|
12820
|
-
ok: r.ok,
|
|
12821
|
-
value: r.ok ? r.value : void 0,
|
|
12822
|
-
span: r.span ?? { start: 0, end: 0 },
|
|
12823
|
-
expected: r.ok ? [] : r.expected ?? [],
|
|
12824
|
-
errors,
|
|
12825
|
-
...selectedRootLog !== void 0 && selectedRootLog.length > 0 ? {
|
|
12826
|
-
rootTrivia: {
|
|
12827
|
-
rows: selectedRootLog,
|
|
12828
|
-
select: rootTriviaSelection,
|
|
12829
|
-
index: buildRootTriviaIndex(selectedRootLog, rootTriviaSelection)
|
|
12830
|
-
}
|
|
12831
|
-
} : {},
|
|
12832
|
-
unconsumedFrom
|
|
12833
|
-
};
|
|
12834
|
-
}
|
|
12835
|
-
function run(entry, input, options = {}) {
|
|
12836
|
-
if (!options.profile) return guardRemovedFields(runOnce(entry, input, options));
|
|
12837
|
-
throw new TypeError(
|
|
12838
|
-
"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."
|
|
12839
|
-
);
|
|
12840
|
-
}
|
|
12841
|
-
|
|
12842
13842
|
// src/coverage.ts
|
|
12843
13843
|
function dispatchMatcher(entry) {
|
|
12844
13844
|
switch (entry.kind) {
|