parseman 0.44.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/choice-cost-gate.d.ts +158 -0
- package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
- package/dist/analysis/choice-cost-render.d.ts +88 -0
- package/dist/analysis/choice-cost-render.d.ts.map +1 -0
- package/dist/analysis/choice-cost.d.ts +383 -0
- package/dist/analysis/choice-cost.d.ts.map +1 -0
- package/dist/analysis/corpus.d.ts +73 -0
- package/dist/analysis/corpus.d.ts.map +1 -0
- package/dist/analysis/diagnose-render.d.ts +65 -0
- package/dist/analysis/diagnose-render.d.ts.map +1 -0
- package/dist/analysis/diagnose.d.ts +133 -0
- package/dist/analysis/diagnose.d.ts.map +1 -0
- package/dist/analysis/diagnostics.cjs +11272 -0
- package/dist/analysis/diagnostics.cjs.map +7 -0
- package/dist/analysis/diagnostics.d.ts +27 -0
- package/dist/analysis/diagnostics.d.ts.map +1 -0
- package/dist/analysis/diagnostics.js +11249 -0
- package/dist/analysis/diagnostics.js.map +7 -0
- package/dist/analysis/fix-render.d.ts +43 -0
- package/dist/analysis/fix-render.d.ts.map +1 -0
- package/dist/analysis/fix.d.ts +172 -0
- package/dist/analysis/fix.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +17 -9
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/grammar.d.ts.map +1 -1
- package/dist/analysis/rebuild.d.ts +61 -0
- package/dist/analysis/rebuild.d.ts.map +1 -0
- package/dist/analysis/terminal.d.ts +142 -0
- package/dist/analysis/terminal.d.ts.map +1 -0
- package/dist/cli/index.cjs +12507 -0
- package/dist/cli/index.cjs.map +7 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +12484 -0
- package/dist/cli/index.js.map +7 -0
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +19 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +19 -0
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/compiler/build-arity.d.ts +28 -0
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/codegen.d.ts +80 -60
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/degradation.d.ts +123 -0
- package/dist/compiler/degradation.d.ts.map +1 -0
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/inline-build.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +45 -7
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/module-hoist.d.ts +106 -0
- package/dist/compiler/module-hoist.d.ts.map +1 -0
- package/dist/coverage.d.ts +13 -0
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/index.cjs +1846 -610
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +11 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1846 -610
- package/dist/index.js.map +4 -4
- package/dist/language-service/index.cjs +40 -30
- package/dist/language-service/index.cjs.map +3 -3
- package/dist/language-service/index.js +40 -30
- package/dist/language-service/index.js.map +3 -3
- package/dist/oracle/digest.d.ts +132 -6
- package/dist/oracle/digest.d.ts.map +1 -1
- package/dist/oracle/index.cjs +136 -305
- package/dist/oracle/index.cjs.map +4 -4
- package/dist/oracle/index.d.ts +43 -31
- package/dist/oracle/index.d.ts.map +1 -1
- package/dist/oracle/index.js +136 -305
- package/dist/oracle/index.js.map +4 -4
- package/dist/plugin/evaluator.d.ts +3 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/free-identifiers.d.ts +23 -0
- package/dist/plugin/free-identifiers.d.ts.map +1 -0
- package/dist/plugin/index.cjs +1529 -763
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts +18 -0
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +1529 -763
- package/dist/plugin/index.js.map +4 -4
- package/dist/plugin/reducer-resolver.d.ts +57 -0
- package/dist/plugin/reducer-resolver.d.ts.map +1 -0
- package/dist/run/index.cjs +33 -30
- package/dist/run/index.cjs.map +2 -2
- package/dist/run/index.js +33 -30
- package/dist/run/index.js.map +2 -2
- package/dist/spec/index.cjs +222 -34
- package/dist/spec/index.cjs.map +4 -4
- package/dist/spec/index.js +222 -34
- package/dist/spec/index.js.map +4 -4
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +23 -4
- package/dist/combinators/guard.d.ts +0 -8
- package/dist/combinators/guard.d.ts.map +0 -1
- package/dist/cst/offset-model.d.ts +0 -119
- package/dist/cst/offset-model.d.ts.map +0 -1
- package/dist/oracle/corpus.d.ts +0 -32
- package/dist/oracle/corpus.d.ts.map +0 -1
- package/dist/oracle/identity.d.ts +0 -117
- package/dist/oracle/identity.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -21,12 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
GRAMMAR_COVERAGE_DEFINITIONS: () => GRAMMAR_COVERAGE_DEFINITIONS,
|
|
24
|
-
OffsetIndex: () => OffsetIndex,
|
|
25
24
|
absoluteSpanAt: () => absoluteSpanAt,
|
|
26
25
|
absoluteSpanCST: () => absoluteSpanCST,
|
|
27
26
|
absolutize: () => absolutize,
|
|
28
27
|
absolutizeCST: () => absolutizeCST,
|
|
29
28
|
alternationGroups: () => alternationGroups,
|
|
29
|
+
analyzeChoiceInventory: () => analyzeChoiceInventory,
|
|
30
30
|
analyzeDuplication: () => analyzeDuplication,
|
|
31
31
|
analyzeDuplicationRules: () => analyzeDuplicationRules,
|
|
32
32
|
analyzeGating: () => analyzeGating,
|
|
@@ -35,18 +35,18 @@ __export(index_exports, {
|
|
|
35
35
|
annotateSpan: () => annotateSpan,
|
|
36
36
|
annotateTreeSpans: () => annotateTreeSpans,
|
|
37
37
|
applyEdit: () => applyEdit,
|
|
38
|
+
armLabel: () => armLabel,
|
|
38
39
|
attempt: () => attempt,
|
|
39
40
|
balanced: () => balanced,
|
|
40
|
-
blankLinesIn: () => blankLinesIn,
|
|
41
41
|
buildLineIndex: () => buildLineIndex,
|
|
42
|
-
buildOffsetIndex: () => buildOffsetIndex,
|
|
43
42
|
buildRootTriviaIndex: () => buildRootTriviaIndex,
|
|
44
43
|
buildTriviaIndex: () => buildTriviaIndex,
|
|
44
|
+
buildWastedWorkBaseline: () => buildWastedWorkBaseline,
|
|
45
45
|
charClassMembers: () => charClassMembers,
|
|
46
|
+
checkWastedWork: () => checkWastedWork,
|
|
46
47
|
choice: () => choice,
|
|
48
|
+
choiceSiteKey: () => choiceSiteKey,
|
|
47
49
|
classifiedTrivia: () => classifiedTrivia,
|
|
48
|
-
collectLeafSlots: () => collectLeafSlots,
|
|
49
|
-
commentsIn: () => commentsIn,
|
|
50
50
|
compile: () => compile,
|
|
51
51
|
compiledGrammarCoverageDefinitions: () => compiledGrammarCoverageDefinitions,
|
|
52
52
|
completionsAt: () => completionsAt,
|
|
@@ -59,30 +59,29 @@ __export(index_exports, {
|
|
|
59
59
|
createLineIndex: () => createLineIndex,
|
|
60
60
|
createVisitor: () => createVisitor,
|
|
61
61
|
cstBuildHost: () => cstBuildHost,
|
|
62
|
+
diagnoseGrammar: () => diagnoseGrammar,
|
|
62
63
|
dispatch: () => dispatch,
|
|
63
64
|
duplicationFindingCount: () => duplicationFindingCount,
|
|
64
65
|
endsWith: () => endsWith,
|
|
66
|
+
examinedNothing: () => examinedNothing,
|
|
65
67
|
expect: () => expect,
|
|
66
68
|
extractCharClasses: () => extractCharClasses,
|
|
67
69
|
field: () => field,
|
|
68
70
|
firstSetToString: () => firstSetToString,
|
|
69
71
|
formatDuplicationFindings: () => formatDuplicationFindings,
|
|
70
72
|
formatGatingWarnings: () => formatGatingWarnings,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
formatGrammarDiagnosis: () => formatGrammarDiagnosis,
|
|
74
|
+
fuseInterpreted: () => fuseInterpreted,
|
|
73
75
|
gate: () => gate,
|
|
74
76
|
grammarCoverageDefinitions: () => grammarCoverageDefinitions,
|
|
75
|
-
|
|
76
|
-
indentMixed: () => indentMixed,
|
|
77
|
-
indentWidth: () => indentWidth,
|
|
77
|
+
isInterpretedFuse: () => isInterpretedFuse,
|
|
78
78
|
isParseError: () => isParseError,
|
|
79
79
|
keywordAlternationHazards: () => keywordAlternationHazards,
|
|
80
80
|
keywordRegexShape: () => keywordRegexShape,
|
|
81
81
|
keywords: () => keywords,
|
|
82
82
|
label: () => label,
|
|
83
83
|
leaf: () => leaf,
|
|
84
|
-
|
|
85
|
-
lineStartWithin: () => lineStartWithin,
|
|
84
|
+
leftFactorPreview: () => leftFactorPreview,
|
|
86
85
|
literal: () => literal,
|
|
87
86
|
makeWhen: () => makeWhen,
|
|
88
87
|
makeWord: () => makeWord,
|
|
@@ -101,11 +100,14 @@ __export(index_exports, {
|
|
|
101
100
|
parseDoc: () => parseDoc,
|
|
102
101
|
parser: () => parser,
|
|
103
102
|
peek: () => peek,
|
|
103
|
+
profileWastedWork: () => profileWastedWork,
|
|
104
104
|
recordLineRange: () => recordLineRange,
|
|
105
105
|
ref: () => ref,
|
|
106
106
|
regex: () => regex,
|
|
107
107
|
relativize: () => relativize,
|
|
108
108
|
relativizeCST: () => relativizeCST,
|
|
109
|
+
renderChoiceInventory: () => renderChoiceInventory,
|
|
110
|
+
renderWastedWork: () => renderWastedWork,
|
|
109
111
|
routed: () => routed,
|
|
110
112
|
rules: () => rules,
|
|
111
113
|
run: () => run,
|
|
@@ -181,8 +183,9 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
|
181
183
|
return d.value.length === 0;
|
|
182
184
|
case "keywords":
|
|
183
185
|
return false;
|
|
186
|
+
// The routed token itself is the selector's match, never empty; a fallback can be.
|
|
184
187
|
case "routed":
|
|
185
|
-
return false;
|
|
188
|
+
return d.fallback === void 0 ? false : me(d.fallback);
|
|
186
189
|
case "regex":
|
|
187
190
|
try {
|
|
188
191
|
const m = new RegExp(d.source).exec("");
|
|
@@ -1944,8 +1947,11 @@ function derive(c, seen) {
|
|
|
1944
1947
|
case "not":
|
|
1945
1948
|
case "peek":
|
|
1946
1949
|
return deriveExpected2(def.parser);
|
|
1950
|
+
// A bare routed() can only fail as "there is no routed token here". With a
|
|
1951
|
+
// fallback, THAT is the branch that can fail, so its expectations are the useful
|
|
1952
|
+
// ones — the routed path either succeeds or is not taken.
|
|
1947
1953
|
case "routed":
|
|
1948
|
-
return ["routed()"];
|
|
1954
|
+
return def.fallback === void 0 ? ["routed()"] : deriveExpected2(def.fallback);
|
|
1949
1955
|
case "lazy": {
|
|
1950
1956
|
const name = c._ruleName;
|
|
1951
1957
|
if (seen.has(c)) return name ? [name] : [];
|
|
@@ -2135,7 +2141,7 @@ function detectStrategy(parsers) {
|
|
|
2135
2141
|
function bareLeadingTermKey(term) {
|
|
2136
2142
|
const d = term._def;
|
|
2137
2143
|
if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
|
|
2138
|
-
if (d.tag === "regex") return `R:${d.source
|
|
2144
|
+
if (d.tag === "regex") return `R:${JSON.stringify([d.source, d.flags])}`;
|
|
2139
2145
|
return null;
|
|
2140
2146
|
}
|
|
2141
2147
|
function detectSharedPrefix(parsers) {
|
|
@@ -2393,17 +2399,21 @@ function makeWhen(opts = {}) {
|
|
|
2393
2399
|
function otherwise(parser2) {
|
|
2394
2400
|
return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
|
|
2395
2401
|
}
|
|
2396
|
-
function routed() {
|
|
2402
|
+
function routed(fallback) {
|
|
2397
2403
|
return {
|
|
2398
2404
|
_tag: "routed",
|
|
2399
|
-
_meta: {
|
|
2400
|
-
|
|
2405
|
+
_meta: {
|
|
2406
|
+
// The routed token is whatever the selector matched, so the first set stays
|
|
2407
|
+
// `any` even with a fallback — `any` already subsumes the fallback's.
|
|
2408
|
+
firstSet: { kind: "any" },
|
|
2409
|
+
canMatchNewline: fallback?._meta.canMatchNewline ?? false,
|
|
2410
|
+
isTrivia: false
|
|
2411
|
+
},
|
|
2412
|
+
_def: fallback === void 0 ? { tag: "routed" } : { tag: "routed", fallback },
|
|
2401
2413
|
parse(_input, pos, ctx) {
|
|
2402
2414
|
const item = ctx._routed;
|
|
2403
|
-
if (item === void 0) {
|
|
2404
|
-
|
|
2405
|
-
}
|
|
2406
|
-
if (pos !== item.span.start) {
|
|
2415
|
+
if (item === void 0 || pos !== item.span.start) {
|
|
2416
|
+
if (fallback !== void 0) return fallback.parse(_input, pos, ctx);
|
|
2407
2417
|
return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
|
|
2408
2418
|
}
|
|
2409
2419
|
if (cstCaptureActive(ctx)) {
|
|
@@ -2946,13 +2956,13 @@ function markUnusedValues(root) {
|
|
|
2946
2956
|
return;
|
|
2947
2957
|
}
|
|
2948
2958
|
};
|
|
2949
|
-
const
|
|
2950
|
-
const
|
|
2959
|
+
const resolveRoot2 = (c) => {
|
|
2960
|
+
const guard = /* @__PURE__ */ new Set();
|
|
2951
2961
|
let cur = c;
|
|
2952
|
-
while (!
|
|
2962
|
+
while (!guard.has(cur)) {
|
|
2953
2963
|
const d = cur._def;
|
|
2954
2964
|
if (d.tag !== "lazy") return cur;
|
|
2955
|
-
|
|
2965
|
+
guard.add(cur);
|
|
2956
2966
|
try {
|
|
2957
2967
|
cur = d.thunk();
|
|
2958
2968
|
} catch {
|
|
@@ -2961,7 +2971,7 @@ function markUnusedValues(root) {
|
|
|
2961
2971
|
}
|
|
2962
2972
|
return cur;
|
|
2963
2973
|
};
|
|
2964
|
-
visit(
|
|
2974
|
+
visit(resolveRoot2(root), true);
|
|
2965
2975
|
}
|
|
2966
2976
|
|
|
2967
2977
|
// src/combinators/grammar.ts
|
|
@@ -3121,10 +3131,11 @@ function childrenOf(def) {
|
|
|
3121
3131
|
return [def.main, def.skipped];
|
|
3122
3132
|
case "scanTo":
|
|
3123
3133
|
return [def.sentinel, ...def.skip];
|
|
3134
|
+
case "routed":
|
|
3135
|
+
return def.fallback ? [def.fallback] : [];
|
|
3124
3136
|
case "literal":
|
|
3125
3137
|
case "regex":
|
|
3126
3138
|
case "keywords":
|
|
3127
|
-
case "routed":
|
|
3128
3139
|
case "guard":
|
|
3129
3140
|
case "lazy":
|
|
3130
3141
|
case "unknown":
|
|
@@ -3350,54 +3361,272 @@ function peek(combinator) {
|
|
|
3350
3361
|
};
|
|
3351
3362
|
}
|
|
3352
3363
|
|
|
3364
|
+
// src/compiler/degradation.ts
|
|
3365
|
+
function formatDegradation(d) {
|
|
3366
|
+
return `[parseman] degraded [${d.code}] ${d.where}: ${d.subject} \u2014 ${d.fellBackTo}; otherwise ${d.otherwise}`;
|
|
3367
|
+
}
|
|
3368
|
+
var DETAIL_CAP = 8;
|
|
3369
|
+
function formatDegradations(list) {
|
|
3370
|
+
const byCode = /* @__PURE__ */ new Map();
|
|
3371
|
+
for (const d of list) {
|
|
3372
|
+
const bucket = byCode.get(d.code);
|
|
3373
|
+
if (bucket) bucket.push(d);
|
|
3374
|
+
else byCode.set(d.code, [d]);
|
|
3375
|
+
}
|
|
3376
|
+
const out = [];
|
|
3377
|
+
for (const [code, items] of byCode) {
|
|
3378
|
+
for (const d of items.slice(0, DETAIL_CAP)) out.push(formatDegradation(d));
|
|
3379
|
+
if (items.length > DETAIL_CAP) {
|
|
3380
|
+
out.push(
|
|
3381
|
+
`[parseman] degraded [${code}] +${items.length - DETAIL_CAP} more site(s) not listed (${items.length} total). Set PARSEMAN_DEGRADATION=error to fail the build on these.`
|
|
3382
|
+
);
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
return out;
|
|
3386
|
+
}
|
|
3387
|
+
var _sinks = [];
|
|
3388
|
+
function beginDegradationCapture() {
|
|
3389
|
+
_sinks.push(/* @__PURE__ */ new Map());
|
|
3390
|
+
}
|
|
3391
|
+
function degradationCaptureDepth() {
|
|
3392
|
+
return _sinks.length;
|
|
3393
|
+
}
|
|
3394
|
+
function unwindDegradationCapture(depth) {
|
|
3395
|
+
const found = [];
|
|
3396
|
+
while (_sinks.length > depth) {
|
|
3397
|
+
const sink = _sinks.pop();
|
|
3398
|
+
found.unshift(...sink.values());
|
|
3399
|
+
}
|
|
3400
|
+
return found;
|
|
3401
|
+
}
|
|
3402
|
+
function resolveDegradationLevel(explicit) {
|
|
3403
|
+
if (explicit !== void 0) return explicit;
|
|
3404
|
+
const env = typeof process !== "undefined" ? process.env?.PARSEMAN_DEGRADATION : void 0;
|
|
3405
|
+
if (env === "off" || env === "warn" || env === "error") return env;
|
|
3406
|
+
return "warn";
|
|
3407
|
+
}
|
|
3408
|
+
function degradationKey(d) {
|
|
3409
|
+
return JSON.stringify([d.code, d.where, d.subject]);
|
|
3410
|
+
}
|
|
3411
|
+
function recordDegradation(d) {
|
|
3412
|
+
const level = resolveDegradationLevel();
|
|
3413
|
+
if (level === "off") return;
|
|
3414
|
+
const key = degradationKey(d);
|
|
3415
|
+
const sink = _sinks[_sinks.length - 1];
|
|
3416
|
+
if (sink) {
|
|
3417
|
+
if (!sink.has(key)) sink.set(key, d);
|
|
3418
|
+
return;
|
|
3419
|
+
}
|
|
3420
|
+
if (_immediate.has(key)) return;
|
|
3421
|
+
_immediate.add(key);
|
|
3422
|
+
if (level === "error") throw new Error(`parseman: degraded compilation path
|
|
3423
|
+
${formatDegradation(d)}`);
|
|
3424
|
+
console.warn(formatDegradation(d));
|
|
3425
|
+
}
|
|
3426
|
+
var _immediate = /* @__PURE__ */ new Set();
|
|
3427
|
+
function beginCompileDegradationDrain() {
|
|
3428
|
+
if (resolveDegradationLevel() === "off" || _sinks.length > 0) return () => {
|
|
3429
|
+
};
|
|
3430
|
+
const depth = degradationCaptureDepth();
|
|
3431
|
+
beginDegradationCapture();
|
|
3432
|
+
return (report) => {
|
|
3433
|
+
const found = unwindDegradationCapture(depth);
|
|
3434
|
+
if (!report || found.length === 0) return;
|
|
3435
|
+
const fresh = found.filter((d) => {
|
|
3436
|
+
const k = degradationKey(d);
|
|
3437
|
+
if (_immediate.has(k)) return false;
|
|
3438
|
+
_immediate.add(k);
|
|
3439
|
+
return true;
|
|
3440
|
+
});
|
|
3441
|
+
if (fresh.length === 0) return;
|
|
3442
|
+
const lines = formatDegradations(fresh);
|
|
3443
|
+
if (resolveDegradationLevel() === "error") {
|
|
3444
|
+
throw new Error(`parseman: ${fresh.length} degraded compilation path(s)
|
|
3445
|
+
${lines.join("\n")}`);
|
|
3446
|
+
}
|
|
3447
|
+
for (const l of lines) console.warn(l);
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3353
3451
|
// src/compiler/build-arity.ts
|
|
3354
3452
|
function cstOutputHost(build) {
|
|
3355
3453
|
return build?._parsemanCstOutput === true;
|
|
3356
3454
|
}
|
|
3357
|
-
var
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
var CONFIRMABLE_PARAM_RE = /^[A-Za-z_$][\w$]*\s*\??\s*(?::[^,=]+)?$/;
|
|
3455
|
+
var SINGLE_IDENT_ARROW_RE = /^[A-Za-z_$][\w$]*\s*=>/;
|
|
3456
|
+
var PARAM_LIST_OPEN_RE = /^(?:function\b[^(]*)?\(/;
|
|
3457
|
+
var NATIVE_CODE_RE = /\{\s*\[native code\]\s*\}/;
|
|
3458
|
+
var PARAM_HEAD_RE = /^[A-Za-z_$][\w$]*\s*\??\s*/;
|
|
3362
3459
|
function confirmedBuildArity(src) {
|
|
3363
3460
|
const s = src.trim();
|
|
3364
|
-
|
|
3365
|
-
if (
|
|
3366
|
-
if (
|
|
3367
|
-
const inner = (
|
|
3368
|
-
if (inner ===
|
|
3369
|
-
|
|
3461
|
+
if (NATIVE_CODE_RE.test(s)) return null;
|
|
3462
|
+
if (/\barguments\b/.test(s)) return null;
|
|
3463
|
+
if (SINGLE_IDENT_ARROW_RE.test(s)) return 1;
|
|
3464
|
+
const inner = paramListText(s);
|
|
3465
|
+
if (inner === null) return null;
|
|
3466
|
+
if (inner.trim() === "") return 0;
|
|
3467
|
+
const parts = splitTopLevel(inner);
|
|
3468
|
+
if (parts === null) return null;
|
|
3370
3469
|
for (const part of parts) {
|
|
3371
|
-
if (!
|
|
3470
|
+
if (!isConfirmableParam(part.trim())) return null;
|
|
3372
3471
|
}
|
|
3373
|
-
if (/\barguments\b/.test(s)) return null;
|
|
3374
3472
|
return parts.length;
|
|
3375
3473
|
}
|
|
3474
|
+
function paramListText(s) {
|
|
3475
|
+
const open = PARAM_LIST_OPEN_RE.exec(s);
|
|
3476
|
+
if (!open) return null;
|
|
3477
|
+
const start = open[0].length;
|
|
3478
|
+
const end = matchingParen(s, start);
|
|
3479
|
+
if (end === null) return null;
|
|
3480
|
+
if (!s.startsWith("function") && !/^\s*=>/.test(s.slice(end + 1))) return null;
|
|
3481
|
+
return s.slice(start, end);
|
|
3482
|
+
}
|
|
3483
|
+
function matchingParen(s, start) {
|
|
3484
|
+
let depth = 0;
|
|
3485
|
+
for (let i = start; i < s.length; i++) {
|
|
3486
|
+
const c = s[i];
|
|
3487
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
3488
|
+
const close = skipString(s, i);
|
|
3489
|
+
if (close === null) return null;
|
|
3490
|
+
i = close;
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
if (c === "(" || c === "[" || c === "{") depth++;
|
|
3494
|
+
else if (c === "]" || c === "}") {
|
|
3495
|
+
if (--depth < 0) return null;
|
|
3496
|
+
} else if (c === ")") {
|
|
3497
|
+
if (depth === 0) return i;
|
|
3498
|
+
depth--;
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
return null;
|
|
3502
|
+
}
|
|
3503
|
+
function skipString(s, open) {
|
|
3504
|
+
const quote = s[open];
|
|
3505
|
+
for (let i = open + 1; i < s.length; i++) {
|
|
3506
|
+
if (s[i] === "\\") {
|
|
3507
|
+
i++;
|
|
3508
|
+
continue;
|
|
3509
|
+
}
|
|
3510
|
+
if (s[i] === quote) return i;
|
|
3511
|
+
}
|
|
3512
|
+
return null;
|
|
3513
|
+
}
|
|
3514
|
+
function splitTopLevel(inner) {
|
|
3515
|
+
const parts = [];
|
|
3516
|
+
let depth = 0;
|
|
3517
|
+
let angle = 0;
|
|
3518
|
+
let last = 0;
|
|
3519
|
+
for (let i = 0; i < inner.length; i++) {
|
|
3520
|
+
const c = inner[i];
|
|
3521
|
+
if (c === "=" && inner[i + 1] === ">") {
|
|
3522
|
+
i++;
|
|
3523
|
+
continue;
|
|
3524
|
+
}
|
|
3525
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
3526
|
+
const close = skipString(inner, i);
|
|
3527
|
+
if (close === null) return null;
|
|
3528
|
+
i = close;
|
|
3529
|
+
continue;
|
|
3530
|
+
}
|
|
3531
|
+
if (c === "(" || c === "[" || c === "{") depth++;
|
|
3532
|
+
else if (c === ")" || c === "]" || c === "}") {
|
|
3533
|
+
if (--depth < 0) return null;
|
|
3534
|
+
} else if (c === "<") angle++;
|
|
3535
|
+
else if (c === ">") {
|
|
3536
|
+
if (--angle < 0) return null;
|
|
3537
|
+
} else if (c === "," && depth === 0 && angle === 0) {
|
|
3538
|
+
parts.push(inner.slice(last, i));
|
|
3539
|
+
last = i + 1;
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
if (depth !== 0 || angle !== 0) return null;
|
|
3543
|
+
parts.push(inner.slice(last));
|
|
3544
|
+
return parts;
|
|
3545
|
+
}
|
|
3546
|
+
function isConfirmableParam(part) {
|
|
3547
|
+
const head = PARAM_HEAD_RE.exec(part);
|
|
3548
|
+
if (!head) return false;
|
|
3549
|
+
const rest = part.slice(head[0].length);
|
|
3550
|
+
if (rest === "") return true;
|
|
3551
|
+
if (rest[0] !== ":") return false;
|
|
3552
|
+
return !hasDefaultAssign(rest.slice(1));
|
|
3553
|
+
}
|
|
3554
|
+
function hasDefaultAssign(annotation) {
|
|
3555
|
+
for (let i = 0; i < annotation.length; i++) {
|
|
3556
|
+
if (annotation[i] !== "=") continue;
|
|
3557
|
+
if (annotation[i + 1] === ">" || annotation[i + 1] === "=") {
|
|
3558
|
+
i++;
|
|
3559
|
+
continue;
|
|
3560
|
+
}
|
|
3561
|
+
const prev = annotation[i - 1];
|
|
3562
|
+
if (prev === "<" || prev === ">" || prev === "!" || prev === "=") continue;
|
|
3563
|
+
return true;
|
|
3564
|
+
}
|
|
3565
|
+
return false;
|
|
3566
|
+
}
|
|
3567
|
+
function buildAnalysisSrc(def) {
|
|
3568
|
+
return def.buildSigSrc ?? def.buildSrc ?? def.build.toString();
|
|
3569
|
+
}
|
|
3570
|
+
var RESOLVER_REASONS = {
|
|
3571
|
+
"rest-parameter": "its parameter list uses a rest parameter, so the declared arity is unbounded",
|
|
3572
|
+
"arguments": "its body references `arguments`",
|
|
3573
|
+
"reassigned": "the binding it names is reassigned, so the function read here is not decidable",
|
|
3574
|
+
"not-a-function": "the binding it names is not a function declaration",
|
|
3575
|
+
"unresolved-import": "its module could not be resolved or parsed",
|
|
3576
|
+
"not-found": "the name did not resolve to any binding",
|
|
3577
|
+
"computed": "it is a computed expression",
|
|
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"
|
|
3580
|
+
};
|
|
3581
|
+
function unconfirmableReason(def, src) {
|
|
3582
|
+
const declared = def.buildArityUnresolved;
|
|
3583
|
+
if (declared !== void 0 && RESOLVER_REASONS[declared]) return RESOLVER_REASONS[declared];
|
|
3584
|
+
const s = src.trim();
|
|
3585
|
+
const params = s.slice(0, s.indexOf(")") + 1);
|
|
3586
|
+
if (params.includes("...")) return RESOLVER_REASONS["rest-parameter"];
|
|
3587
|
+
if (/\barguments\b/.test(s)) return RESOLVER_REASONS["arguments"];
|
|
3588
|
+
if (/^[A-Za-z_$][\w$]*(\s*\.\s*[A-Za-z_$][\w$]*)?$/.test(s)) {
|
|
3589
|
+
return "it names a binding this build could not resolve to a function declaration";
|
|
3590
|
+
}
|
|
3591
|
+
return "its parameter list could not be parsed";
|
|
3592
|
+
}
|
|
3593
|
+
function confirmedArityForDef(def) {
|
|
3594
|
+
if (def.buildArity !== void 0) return def.buildArity;
|
|
3595
|
+
const src = buildAnalysisSrc(def);
|
|
3596
|
+
const arity = confirmedBuildArity(src);
|
|
3597
|
+
if (arity !== null) return arity;
|
|
3598
|
+
const subject = `build reducer \`${(def.buildSrc ?? "<runtime fn>").trim().split("\n")[0].slice(0, 60)}\``;
|
|
3599
|
+
recordDegradation({
|
|
3600
|
+
code: "build-arity-unconfirmed",
|
|
3601
|
+
severity: "warn",
|
|
3602
|
+
where: `node("${def.type ?? "<inferred>"}")`,
|
|
3603
|
+
subject,
|
|
3604
|
+
fellBackTo: `could not confirm its declared arity \u2014 ${unconfirmableReason(def, src)} \u2014 so this node captures children, fields and raw children, logs trivia, and clones \`_ctx.state\` on every match`,
|
|
3605
|
+
otherwise: "only the tiers the reducer actually declares would be captured (arity >= 1 children, >= 2 fields, >= 4 raw, >= 5 trivia, >= 6 state). Declare it with `node(..., { buildArity: n })` if it cannot be derived"
|
|
3606
|
+
});
|
|
3607
|
+
return null;
|
|
3608
|
+
}
|
|
3376
3609
|
function buildReadsChildren(def) {
|
|
3377
3610
|
if (!def.build) return true;
|
|
3378
|
-
const
|
|
3379
|
-
const arity = confirmedBuildArity(src);
|
|
3611
|
+
const arity = confirmedArityForDef(def);
|
|
3380
3612
|
if (arity === null) return true;
|
|
3381
3613
|
return arity >= 1;
|
|
3382
3614
|
}
|
|
3383
3615
|
function buildReadsRaw(def) {
|
|
3384
3616
|
if (!def.build) return true;
|
|
3385
|
-
const
|
|
3386
|
-
const arity = confirmedBuildArity(src);
|
|
3617
|
+
const arity = confirmedArityForDef(def);
|
|
3387
3618
|
if (arity === null) return true;
|
|
3388
3619
|
return arity >= 4;
|
|
3389
3620
|
}
|
|
3390
3621
|
function buildReadsTrivia(def) {
|
|
3391
3622
|
if (!def.build) return true;
|
|
3392
|
-
const
|
|
3393
|
-
const arity = confirmedBuildArity(src);
|
|
3623
|
+
const arity = confirmedArityForDef(def);
|
|
3394
3624
|
if (arity === null) return true;
|
|
3395
3625
|
return arity >= 5;
|
|
3396
3626
|
}
|
|
3397
3627
|
function buildReadsState(def) {
|
|
3398
3628
|
if (!def.build) return true;
|
|
3399
|
-
const
|
|
3400
|
-
const arity = confirmedBuildArity(src);
|
|
3629
|
+
const arity = confirmedArityForDef(def);
|
|
3401
3630
|
if (arity === null) return true;
|
|
3402
3631
|
return arity >= 6;
|
|
3403
3632
|
}
|
|
@@ -3405,8 +3634,7 @@ function buildReadsState(def) {
|
|
|
3405
3634
|
// src/compiler/fields.ts
|
|
3406
3635
|
function buildReadsFields(def) {
|
|
3407
3636
|
if (!def.build) return true;
|
|
3408
|
-
const
|
|
3409
|
-
const arity = confirmedBuildArity(src);
|
|
3637
|
+
const arity = confirmedArityForDef(def);
|
|
3410
3638
|
if (arity === null) return true;
|
|
3411
3639
|
return arity >= 2;
|
|
3412
3640
|
}
|
|
@@ -3587,6 +3815,13 @@ function isCstChild(value) {
|
|
|
3587
3815
|
function missingInferredType() {
|
|
3588
3816
|
throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
|
|
3589
3817
|
}
|
|
3818
|
+
function normalizeBuildArity(arity) {
|
|
3819
|
+
if (arity === void 0) return void 0;
|
|
3820
|
+
if (!Number.isInteger(arity) || arity < 0 || arity > 6) {
|
|
3821
|
+
throw new Error("node() buildArity must be an integer 0..6 (children, fields, span, rawChildren, triviaLog, state)");
|
|
3822
|
+
}
|
|
3823
|
+
return arity;
|
|
3824
|
+
}
|
|
3590
3825
|
function normalizeProject(project) {
|
|
3591
3826
|
if (project === void 0) return void 0;
|
|
3592
3827
|
if (!Number.isInteger(project) || project < 0) {
|
|
@@ -3622,13 +3857,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
|
|
|
3622
3857
|
const captureTrivia = opts?.captureTrivia === true;
|
|
3623
3858
|
const trailingTrivia = opts?.trailingTrivia === true;
|
|
3624
3859
|
const tags = opts?.tags;
|
|
3860
|
+
const buildArity = normalizeBuildArity(opts?.buildArity);
|
|
3625
3861
|
if (unwrap && collapse) {
|
|
3626
3862
|
throw new Error("node() options cannot set both unwrap and collapse");
|
|
3627
3863
|
}
|
|
3628
3864
|
if (project !== void 0 && (unwrap || collapse)) {
|
|
3629
3865
|
throw new Error("node() options cannot combine project with unwrap or collapse");
|
|
3630
3866
|
}
|
|
3631
|
-
const def = unwrap || collapse || project !== void 0 || captureTrivia || trailingTrivia || tags !== void 0 && tags.length > 0 ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...project !== void 0 ? { project } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {}, ...tags !== void 0 && tags.length > 0 ? { tags } : {} } : baseDef;
|
|
3867
|
+
const def = unwrap || collapse || project !== void 0 || captureTrivia || trailingTrivia || buildArity !== void 0 || tags !== void 0 && tags.length > 0 ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...project !== void 0 ? { project } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {}, ...buildArity !== void 0 ? { buildArity } : {}, ...tags !== void 0 && tags.length > 0 ? { tags } : {} } : baseDef;
|
|
3632
3868
|
const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
|
|
3633
3869
|
const clonesState = build ? buildReadsState(def) : project === void 0;
|
|
3634
3870
|
const hasOwnFields = parserHasOwnFields(combinator);
|
|
@@ -3674,7 +3910,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
|
|
|
3674
3910
|
const cstOutput = cstOutputHost(ctx.build);
|
|
3675
3911
|
const span = ctx.trackLines ? annotateSpanFromLineContext(r.span, ctx) : r.span;
|
|
3676
3912
|
const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
|
|
3677
|
-
const built = unwrap && children2.length === 1 ? unwrapChild(children2[0]) : collapse && children2.length === 1 ? children2[0] : !build && project === void 0 && ctx.build?._parsemanCstCollapse && children2.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children2[0], children2, rawChildren) ? children2[0] : project !== void 0 ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : projectChild(children2, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : build(children2, fields, span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, st, tags) : { _tag: "node", type: nodeType, span: ctx.trackLines ? { ...span } : { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
|
|
3913
|
+
const built = unwrap && children2.length === 1 ? unwrapChild(children2[0]) : collapse && children2.length === 1 ? children2[0] : (cstOutput || !build && project === void 0) && ctx.build?._parsemanCstCollapse && children2.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children2[0], children2, rawChildren) ? children2[0] : project !== void 0 ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : projectChild(children2, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : build(children2, fields, span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, st, tags) : { _tag: "node", type: nodeType, span: ctx.trackLines ? { ...span } : { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
|
|
3678
3914
|
const rawEntry = isCstChild(built) ? built : { _tag: "leaf", value: typeof built === "string" ? built : typeof built === "object" && built !== null ? input.slice(r.span.start, r.span.end) : "", span };
|
|
3679
3915
|
if (saved.buf !== void 0 || saved.ch !== void 0) {
|
|
3680
3916
|
pushCstChild(ctx, built, rawEntry);
|
|
@@ -3869,7 +4105,7 @@ function leaf(root, fn) {
|
|
|
3869
4105
|
}
|
|
3870
4106
|
|
|
3871
4107
|
// src/version.ts
|
|
3872
|
-
var PARSEMAN_VERSION = "0.
|
|
4108
|
+
var PARSEMAN_VERSION = "0.46.0";
|
|
3873
4109
|
|
|
3874
4110
|
// src/cst/host-mode.ts
|
|
3875
4111
|
var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
|
|
@@ -3924,14 +4160,28 @@ function firstSetToString(fs) {
|
|
|
3924
4160
|
return fs.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
|
|
3925
4161
|
}
|
|
3926
4162
|
var ruleNameOf2 = (p) => p._ruleName;
|
|
4163
|
+
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
3927
4164
|
var SUGGESTIONS = {
|
|
3928
|
-
"broad-recognizer": "
|
|
3929
|
-
"leading-not": "
|
|
3930
|
-
"nullable-prefix": "
|
|
3931
|
-
"cross-artifact-ref": "
|
|
3932
|
-
"opaque-wrapper": "
|
|
3933
|
-
"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."
|
|
3934
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
|
+
}
|
|
3935
4185
|
function classifyBroadArm(arm, resolve) {
|
|
3936
4186
|
const seen = /* @__PURE__ */ new Set();
|
|
3937
4187
|
const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
|
|
@@ -4047,7 +4297,6 @@ function peelToLeading(arm) {
|
|
|
4047
4297
|
if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
|
|
4048
4298
|
return cur;
|
|
4049
4299
|
}
|
|
4050
|
-
var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
|
|
4051
4300
|
function detectAntiPatterns(rule, arms) {
|
|
4052
4301
|
const out = [];
|
|
4053
4302
|
arms.forEach((arm, i) => {
|
|
@@ -4060,14 +4309,14 @@ function detectAntiPatterns(rule, arms) {
|
|
|
4060
4309
|
kind: "double-not",
|
|
4061
4310
|
rule,
|
|
4062
4311
|
armIndex: i,
|
|
4063
|
-
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.'
|
|
4064
4313
|
});
|
|
4065
4314
|
} else {
|
|
4066
4315
|
out.push({
|
|
4067
4316
|
kind: "leading-not",
|
|
4068
4317
|
rule,
|
|
4069
4318
|
armIndex: i,
|
|
4070
|
-
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."
|
|
4071
4320
|
});
|
|
4072
4321
|
}
|
|
4073
4322
|
}
|
|
@@ -4076,7 +4325,7 @@ function detectAntiPatterns(rule, arms) {
|
|
|
4076
4325
|
kind: "keyword-regex",
|
|
4077
4326
|
rule,
|
|
4078
4327
|
armIndex: i,
|
|
4079
|
-
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.`
|
|
4080
4329
|
});
|
|
4081
4330
|
}
|
|
4082
4331
|
});
|
|
@@ -4118,7 +4367,7 @@ function analyzeGatingRules(ruleMap, opts) {
|
|
|
4118
4367
|
const d = def;
|
|
4119
4368
|
const rule = ruleNameOf2(p) ?? enclosingRule;
|
|
4120
4369
|
if (d.tag === "choice") {
|
|
4121
|
-
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
|
|
4370
|
+
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule, arms: d.parsers });
|
|
4122
4371
|
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
4123
4372
|
}
|
|
4124
4373
|
const rec = d;
|
|
@@ -4147,13 +4396,15 @@ function analyzeGatingRules(ruleMap, opts) {
|
|
|
4147
4396
|
const seenInRule = /* @__PURE__ */ new Map();
|
|
4148
4397
|
const accept = new Set(opts?.accept ?? []);
|
|
4149
4398
|
const usedAccept = /* @__PURE__ */ new Set();
|
|
4150
|
-
const choices = raw.map(({ g, rule }) => {
|
|
4399
|
+
const choices = raw.map(({ g, rule, arms }) => {
|
|
4151
4400
|
const n = seenInRule.get(rule) ?? 0;
|
|
4152
4401
|
seenInRule.set(rule, n + 1);
|
|
4153
4402
|
const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
|
|
4154
4403
|
const isAccepted = g.gates === "no" && accept.has(id);
|
|
4155
4404
|
if (isAccepted) usedAccept.add(id);
|
|
4156
|
-
|
|
4405
|
+
const out = { ...g, id, accepted: isAccepted };
|
|
4406
|
+
Object.defineProperty(out, "arms", { value: arms, enumerable: false, writable: false });
|
|
4407
|
+
return out;
|
|
4157
4408
|
});
|
|
4158
4409
|
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
4159
4410
|
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
@@ -4180,7 +4431,14 @@ function analyzeChoice(p, d, rule, resolve) {
|
|
|
4180
4431
|
const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve));
|
|
4181
4432
|
const anyArms = deep.map((fs, index) => ({ fs, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
|
|
4182
4433
|
const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve);
|
|
4183
|
-
return {
|
|
4434
|
+
return {
|
|
4435
|
+
index,
|
|
4436
|
+
cause,
|
|
4437
|
+
detail,
|
|
4438
|
+
shallowAnyOnly: false,
|
|
4439
|
+
unresolvedExternal: unresolvedExternal === true,
|
|
4440
|
+
suggestion: refineSuggestion(cause, arms[index])
|
|
4441
|
+
};
|
|
4184
4442
|
});
|
|
4185
4443
|
const overlaps = [];
|
|
4186
4444
|
for (let i = 0; i < deep.length; i++)
|
|
@@ -4190,7 +4448,7 @@ function analyzeChoice(p, d, rule, resolve) {
|
|
|
4190
4448
|
a: i,
|
|
4191
4449
|
b: j,
|
|
4192
4450
|
on: intersection(deep[i], deep[j]),
|
|
4193
|
-
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."
|
|
4194
4452
|
});
|
|
4195
4453
|
let gates;
|
|
4196
4454
|
if (d.disjoint) gates = "yes";
|
|
@@ -4744,7 +5002,7 @@ function findDivergentSlot(members, shapeOf) {
|
|
|
4744
5002
|
const path = [];
|
|
4745
5003
|
const defs = [];
|
|
4746
5004
|
let cur = [...members];
|
|
4747
|
-
for (let
|
|
5005
|
+
for (let guard = 0; guard < 64; guard++) {
|
|
4748
5006
|
const d0 = cur[0]._def;
|
|
4749
5007
|
const kidLists = cur.map((c) => childrenOf2(c._def));
|
|
4750
5008
|
const arity = kidLists[0].length;
|
|
@@ -5703,7 +5961,15 @@ function children(def, winners) {
|
|
|
5703
5961
|
let resolved;
|
|
5704
5962
|
try {
|
|
5705
5963
|
resolved = def.thunk();
|
|
5706
|
-
} catch {
|
|
5964
|
+
} catch (e) {
|
|
5965
|
+
recordDegradation({
|
|
5966
|
+
code: "coverage-definitions-unavailable",
|
|
5967
|
+
severity: "warn",
|
|
5968
|
+
where: "<lazy rule reference>",
|
|
5969
|
+
subject: `unresolvable reference (${e.message})`,
|
|
5970
|
+
fellBackTo: "the coverage walk stopped at this reference, so every definition below it is ABSENT from the denominator \u2014 which raises the reported ratio rather than lowering it",
|
|
5971
|
+
otherwise: "the referenced rule and its whole subtree would contribute definitions"
|
|
5972
|
+
});
|
|
5707
5973
|
return [];
|
|
5708
5974
|
}
|
|
5709
5975
|
const name = resolved._ruleName;
|
|
@@ -5994,15 +6260,15 @@ function parseStringShape(source) {
|
|
|
5994
6260
|
if (source.length < 3 || source[source.length - 1] !== q) return null;
|
|
5995
6261
|
const inner = source.slice(1, -1);
|
|
5996
6262
|
const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
|
|
5997
|
-
const
|
|
6263
|
+
const ESC2 = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
|
|
5998
6264
|
let body;
|
|
5999
6265
|
let esc;
|
|
6000
|
-
let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${
|
|
6266
|
+
let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC2}\\)\\*$`).exec(inner);
|
|
6001
6267
|
if (m) {
|
|
6002
6268
|
body = m[1];
|
|
6003
6269
|
esc = m[2];
|
|
6004
6270
|
} else {
|
|
6005
|
-
m = new RegExp(`^\\((?:\\?:)?${
|
|
6271
|
+
m = new RegExp(`^\\((?:\\?:)?${ESC2}\\|${CLS}\\)\\*$`).exec(inner);
|
|
6006
6272
|
if (m) {
|
|
6007
6273
|
esc = m[1];
|
|
6008
6274
|
body = m[2];
|
|
@@ -6012,8 +6278,8 @@ function parseStringShape(source) {
|
|
|
6012
6278
|
const excluded = parseClassRanges(body);
|
|
6013
6279
|
if (!excluded) return null;
|
|
6014
6280
|
const qcp = q.codePointAt(0);
|
|
6015
|
-
const
|
|
6016
|
-
if (!
|
|
6281
|
+
const inSet2 = (cp) => excluded.some(([lo, hi]) => cp >= lo && cp <= hi);
|
|
6282
|
+
if (!inSet2(qcp) || !inSet2(BACKSLASH)) return null;
|
|
6017
6283
|
return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
|
|
6018
6284
|
}
|
|
6019
6285
|
function parseSeqParts(source) {
|
|
@@ -7204,27 +7470,53 @@ function emitScannableTerminal(shape, c) {
|
|
|
7204
7470
|
}
|
|
7205
7471
|
|
|
7206
7472
|
// src/compiler/inline-build.ts
|
|
7207
|
-
var
|
|
7473
|
+
var ANN = String.raw`\s*\??\s*(?::[^,)=]+)?`;
|
|
7474
|
+
var ANY_P = String.raw`\w+${ANN}`;
|
|
7475
|
+
var MK_BUILD_RE = new RegExp(
|
|
7476
|
+
String.raw`^\(\s*(\w+)${ANN}\s*,\s*${ANY_P}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*\)\s*=>\s*mk\s*\(\s*(['"])([^'"]+)\5\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
|
|
7477
|
+
);
|
|
7208
7478
|
function escapeRegExp(s) {
|
|
7209
7479
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7210
7480
|
}
|
|
7481
|
+
function identMkBuildRe(typeIdent) {
|
|
7482
|
+
return new RegExp(
|
|
7483
|
+
String.raw`^\(\s*(\w+)${ANN}\s*,\s*${ANY_P}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*\)\s*=>\s*(?:[\w$.]*\.)?mk\s*\(\s*${escapeRegExp(typeIdent)}\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
|
|
7484
|
+
);
|
|
7485
|
+
}
|
|
7211
7486
|
function looseMkBuildRe(type) {
|
|
7212
7487
|
return new RegExp(
|
|
7213
|
-
String.raw`^\(\s*(\w+)\s*,\s
|
|
7488
|
+
String.raw`^\(\s*(\w+)${ANN}\s*,\s*${ANY_P}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*\)\s*=>\s*.+\(\s*(['"])${escapeRegExp(type)}\5\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
|
|
7214
7489
|
);
|
|
7215
7490
|
}
|
|
7491
|
+
var LOOKS_LIKE_MK_RE = /=>\s*(?:[\w$.]*\.)?mk\s*\(/;
|
|
7216
7492
|
function analyzeMkInlineBuild(def) {
|
|
7217
7493
|
if (!def.build) return null;
|
|
7218
7494
|
if (def.type === void 0) return null;
|
|
7219
|
-
const src = (def
|
|
7495
|
+
const src = buildAnalysisSrc(def).trim();
|
|
7220
7496
|
const strict = src.match(MK_BUILD_RE);
|
|
7221
7497
|
if (strict) {
|
|
7222
7498
|
const mkType = strict[6];
|
|
7223
|
-
|
|
7499
|
+
if (mkType === def.type) return def.type;
|
|
7500
|
+
reportMkMiss(def.type, src, `it builds a "${mkType}" node, not "${def.type}"`);
|
|
7501
|
+
return null;
|
|
7224
7502
|
}
|
|
7225
7503
|
if (looseMkBuildRe(def.type).test(src)) return def.type;
|
|
7504
|
+
if (def.typeSrc !== void 0 && identMkBuildRe(def.typeSrc).test(src)) return def.type;
|
|
7505
|
+
if (LOOKS_LIKE_MK_RE.test(src)) {
|
|
7506
|
+
reportMkMiss(def.type, src, "its parameter list or argument order does not match `(children, fields, span, rawChildren, triviaLog) => mk(type, children, rawChildren, span, triviaLog)`");
|
|
7507
|
+
}
|
|
7226
7508
|
return null;
|
|
7227
7509
|
}
|
|
7510
|
+
function reportMkMiss(type, src, why) {
|
|
7511
|
+
recordDegradation({
|
|
7512
|
+
code: "mk-inline-missed",
|
|
7513
|
+
severity: "warn",
|
|
7514
|
+
where: `node("${type}")`,
|
|
7515
|
+
subject: `build reducer \`${src.split("\n")[0].slice(0, 80)}\``,
|
|
7516
|
+
fellBackTo: `looks like an inline-\`mk\` builder but ${why}, so every match pays a \`_build[n](...)\` call instead of an inlined object literal`,
|
|
7517
|
+
otherwise: "the node object would be constructed at the call site with no call frame"
|
|
7518
|
+
});
|
|
7519
|
+
}
|
|
7228
7520
|
function emitInlineMkNodeExpr(type, chV, rawV, spanExpr, tlV) {
|
|
7229
7521
|
return `{ _tag: 'node', type: ${JSON.stringify(type)}, span: ${spanExpr}, children: ${chV}, rawCount: ${rawV}.length, localTriviaLen: ${tlV}.length }`;
|
|
7230
7522
|
}
|
|
@@ -7407,10 +7699,34 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
7407
7699
|
seen.delete(p);
|
|
7408
7700
|
}
|
|
7409
7701
|
}
|
|
7702
|
+
function tern(cond, whenTrue, whenFalse) {
|
|
7703
|
+
if (cond === "false") return whenFalse;
|
|
7704
|
+
if (cond === "true") return whenTrue;
|
|
7705
|
+
return `${cond} ? ${whenTrue} : ${whenFalse}`;
|
|
7706
|
+
}
|
|
7707
|
+
function notGate(cond) {
|
|
7708
|
+
if (cond === "false") return "true";
|
|
7709
|
+
if (cond === "true") return "false";
|
|
7710
|
+
return `!(${cond})`;
|
|
7711
|
+
}
|
|
7712
|
+
function orGate(a, b) {
|
|
7713
|
+
if (a === "false") return b;
|
|
7714
|
+
if (b === "false") return a;
|
|
7715
|
+
if (a === "true" || b === "true") return "true";
|
|
7716
|
+
return `${a} || ${b}`;
|
|
7717
|
+
}
|
|
7718
|
+
function andGate(a, b) {
|
|
7719
|
+
if (a === "false" || b === "false") return "false";
|
|
7720
|
+
if (a === "true") return b;
|
|
7721
|
+
if (b === "true") return a;
|
|
7722
|
+
return `${a} && ${b}`;
|
|
7723
|
+
}
|
|
7410
7724
|
var HOST_READS_DECL = "const _hostReads = (b, n) => { if (b === undefined) return false; let s; try { s = Function.prototype.toString.call(b) } catch (e) { return true } if (/\\barguments\\b/.test(s)) return true; const m = /^[^(]*\\(([\\s\\S]*?)\\)/.exec(s); if (m && /\\.\\.\\.|=/.test(m[1])) return true; return b.length > n }";
|
|
7725
|
+
var RAW_ENTRY_DECL = "const _rawEntry = (v, input, s, e, span) => (typeof v === 'object' && v !== null && (v._tag === 'node' || v._tag === 'leaf' || v._tag === 'parseError')) ? v : { _tag: 'leaf', value: typeof v === 'string' ? v : (typeof v === 'object' && v !== null ? input.slice(s, e) : ''), span: span ?? { start: s, end: e } }";
|
|
7411
7726
|
var LINE_TRACK_DECL = "const _trackLines = (_ctx, input, start, end) => { const from = _ctx._lineScannedTo ?? 0; if (end <= from) return; for (let i = from; i < end; i++) if (input.charCodeAt(i) === 10) _ctx._lineStarts.push(i + 1); _ctx._lineScannedTo = end }";
|
|
7412
7727
|
var LINE_SPAN_DECL = "const _lineCol = (_ctx, offset) => { const starts = _ctx._lineStarts; let lo = 0, hi = starts.length - 1; while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (starts[mid] <= offset) lo = mid; else hi = mid - 1 } return [lo + 1, offset - starts[lo] + 1] }\nconst _spanLines = (_ctx, start, end) => { const s = _lineCol(_ctx, start), e = _lineCol(_ctx, end); return { start, end, startLine: s[0], startColumn: s[1], endLine: e[0], endColumn: e[1] } }";
|
|
7413
7728
|
var _loweringSink = null;
|
|
7729
|
+
var _inlineCapSink = null;
|
|
7414
7730
|
function hasSelectedRootTrivia(ctx) {
|
|
7415
7731
|
return ctx.rootTriviaClassified === true;
|
|
7416
7732
|
}
|
|
@@ -8159,9 +8475,11 @@ function emitSeqValues(def, ctx, pos) {
|
|
|
8159
8475
|
const markLog = v(ctx, "_mklg");
|
|
8160
8476
|
const markRootLog = hasSelectedRootTrivia(ctx) ? v(ctx, "_mkrlg") : null;
|
|
8161
8477
|
const scanEndV = v(ctx, "_sne");
|
|
8162
|
-
const
|
|
8478
|
+
const logV = v(ctx, "_tlg");
|
|
8479
|
+
const capArg = ctx.noHoist ? "0" : hasSelectedRootTrivia(ctx) ? `(${logV} !== undefined || _ctx._rootTriviaLog !== undefined) ? 2 : 0` : `${logV} !== undefined ? 2 : 0`;
|
|
8163
8480
|
stmts.push(
|
|
8164
|
-
`${ind(ctx)}const ${
|
|
8481
|
+
`${ind(ctx)}const ${logV} = _ctx._triviaLog`,
|
|
8482
|
+
`${ind(ctx)}const ${markLog} = ${logV} !== undefined ? ${logV}.length : 0`,
|
|
8165
8483
|
...markRootLog ? [`${ind(ctx)}const ${markRootLog} = _ctx._rootTriviaLog ? _ctx._rootTriviaLog.length : 0`] : [],
|
|
8166
8484
|
`${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx, ${capArg})`,
|
|
8167
8485
|
...emitLineTrack(ctx, curV, scanEndV)
|
|
@@ -8171,7 +8489,7 @@ function emitSeqValues(def, ctx, pos) {
|
|
|
8171
8489
|
const endAfterV = v(ctx, "_sea");
|
|
8172
8490
|
stmts.push(
|
|
8173
8491
|
`${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
|
|
8174
|
-
`${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}; else { if (
|
|
8492
|
+
`${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}; else { if (${logV} !== undefined && ${logV}.length !== ${markLog}) ${logV}.length = ${markLog};${markRootLog ? ` if (_ctx._rootTriviaLog && _ctx._rootTriviaLog.length !== ${markRootLog}) _ctx._rootTriviaLog.length = ${markRootLog};` : ""} }`
|
|
8175
8493
|
);
|
|
8176
8494
|
valueVars.push(r2.valueVar);
|
|
8177
8495
|
continue;
|
|
@@ -9243,9 +9561,18 @@ function emitPeek(def, ctx, pos) {
|
|
|
9243
9561
|
endVar: pos
|
|
9244
9562
|
};
|
|
9245
9563
|
}
|
|
9246
|
-
function emitRouted(ctx, pos) {
|
|
9564
|
+
function emitRouted(ctx, pos, def) {
|
|
9247
9565
|
const local = ctx.routedLocal;
|
|
9566
|
+
const fallback = def.fallback;
|
|
9248
9567
|
if (local !== void 0) {
|
|
9568
|
+
if (fallback !== void 0 && local.startVar !== pos) {
|
|
9569
|
+
return emitRoutedWithFallback(ctx, pos, fallback, {
|
|
9570
|
+
test: `${local.startVar} === ${pos}`,
|
|
9571
|
+
valExpr: local.valueVar,
|
|
9572
|
+
startExpr: local.startVar,
|
|
9573
|
+
endExpr: local.endVar
|
|
9574
|
+
});
|
|
9575
|
+
}
|
|
9249
9576
|
return {
|
|
9250
9577
|
stmts: [
|
|
9251
9578
|
...emitIfFail(ctx, `${local.startVar} !== ${pos}`, failBody(ctx, '"routed()"', pos)),
|
|
@@ -9256,9 +9583,19 @@ function emitRouted(ctx, pos) {
|
|
|
9256
9583
|
};
|
|
9257
9584
|
}
|
|
9258
9585
|
const item = v(ctx, "_rt");
|
|
9586
|
+
const read = `${ind(ctx)}const ${item} = _ctx._routed`;
|
|
9587
|
+
if (fallback !== void 0) {
|
|
9588
|
+
const r = emitRoutedWithFallback(ctx, pos, fallback, {
|
|
9589
|
+
test: `${item} !== undefined && ${item}.span.start === ${pos}`,
|
|
9590
|
+
valExpr: `${item}.value`,
|
|
9591
|
+
startExpr: `${item}.span.start`,
|
|
9592
|
+
endExpr: `${item}.span.end`
|
|
9593
|
+
});
|
|
9594
|
+
return { ...r, stmts: [read, ...r.stmts] };
|
|
9595
|
+
}
|
|
9259
9596
|
return {
|
|
9260
9597
|
stmts: [
|
|
9261
|
-
|
|
9598
|
+
read,
|
|
9262
9599
|
...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
|
|
9263
9600
|
...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
|
|
9264
9601
|
],
|
|
@@ -9266,6 +9603,30 @@ function emitRouted(ctx, pos) {
|
|
|
9266
9603
|
endVar: `${item}.span.end`
|
|
9267
9604
|
};
|
|
9268
9605
|
}
|
|
9606
|
+
function emitRoutedWithFallback(ctx, pos, fallback, routed2) {
|
|
9607
|
+
const valV = v(ctx, "_rtv");
|
|
9608
|
+
const endV = v(ctx, "_rte");
|
|
9609
|
+
const i = ind(ctx);
|
|
9610
|
+
ctx.indent++;
|
|
9611
|
+
const capture = emitLeafCapture(ctx, routed2.valExpr, routed2.startExpr, routed2.endExpr);
|
|
9612
|
+
const fb = emit(fallback, ctx, pos);
|
|
9613
|
+
const inner = ind(ctx);
|
|
9614
|
+
ctx.indent--;
|
|
9615
|
+
return {
|
|
9616
|
+
stmts: [
|
|
9617
|
+
`${i}let ${valV}, ${endV}`,
|
|
9618
|
+
`${i}if (${routed2.test}) {`,
|
|
9619
|
+
...capture,
|
|
9620
|
+
`${inner}${valV} = ${routed2.valExpr}; ${endV} = ${routed2.endExpr}`,
|
|
9621
|
+
`${i}} else {`,
|
|
9622
|
+
...fb.stmts,
|
|
9623
|
+
`${inner}${valV} = ${fb.valueVar}; ${endV} = ${fb.endVar}`,
|
|
9624
|
+
`${i}}`
|
|
9625
|
+
],
|
|
9626
|
+
valueVar: valV,
|
|
9627
|
+
endVar: endV
|
|
9628
|
+
};
|
|
9629
|
+
}
|
|
9269
9630
|
function escapeTokenLiteral(value) {
|
|
9270
9631
|
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
9271
9632
|
}
|
|
@@ -9447,22 +9808,16 @@ function emitNode(def, ctx, pos) {
|
|
|
9447
9808
|
const smk = structural ? v(ctx, "_smk") : null;
|
|
9448
9809
|
if (structural) ctx.needsHostReads = true;
|
|
9449
9810
|
const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
|
|
9450
|
-
const
|
|
9451
|
-
const
|
|
9452
|
-
const profileRecognizer = recV;
|
|
9453
|
-
const profileCapture = capV;
|
|
9454
|
-
const cstMode = ctx.hostMode === "cst";
|
|
9455
|
-
const directCstV = !structural && !cstMode && (!capturesChildren || !capturesRaw) ? v(ctx, "_dcst") : null;
|
|
9456
|
-
const directCstGate = directCstV === null ? "false" : profileCapture;
|
|
9811
|
+
const profileRecognizer = "false";
|
|
9812
|
+
const profileCapture = "false";
|
|
9457
9813
|
const structuralCapturesTrivia = structural && (def.captureTrivia === true || def.trailingTrivia === true);
|
|
9458
9814
|
const chNeededExpr = def.unwrap || def.collapse ? "true" : `(_ctx._pmReadsCh ??= (_ctx.build === undefined || _ctx.build._parsemanReadsChildren !== false || _ctx.build._parsemanCstCollapse !== undefined))`;
|
|
9459
|
-
const chAlloc = def.unwrap || def.collapse ?
|
|
9460
|
-
const
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
` : ""}${i}const ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}`;
|
|
9815
|
+
const chAlloc = def.unwrap || def.collapse ? tern(profileRecognizer, "undefined", "[]") : tern(profileRecognizer, "undefined", `${tern(orGate(profileCapture, chNeededExpr), "[]", "undefined")}`);
|
|
9816
|
+
const dcstAlloc = "undefined";
|
|
9817
|
+
const allocStmt = structural ? `${i}const ${capTLv} = ${andGate(notGate(profileRecognizer), orGate(profileCapture, structuralCapturesTrivia ? "true" : hostTriviaGate))}, ${capSTv} = ${andGate(notGate(orGate(profileRecognizer, profileCapture)), "(_ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 6)))")}${capFv ? `, ${capFv} = ${andGate(notGate(profileRecognizer), orGate(profileCapture, "(_ctx.build !== undefined && _hostReads(_ctx.build, 2))"))}` : ""}
|
|
9818
|
+
${i}const ${chV} = ${chAlloc}, ${rawV} = ${tern(profileRecognizer, "undefined", "[]")}, ${tlV} = ${tern(profileRecognizer, "undefined", innerEnablesTriviaCapture ? "[]" : `${capTLv} ? [] : _EMPTY_TL`)}` : capturesTrivia ? `${i}const ${capTLv} = ${notGate(profileRecognizer)}, ${chV} = ${tern(profileRecognizer, "undefined", capturesChildren ? "[]" : dcstAlloc)}, ${rawV} = ${tern(profileRecognizer, "undefined", capturesRaw ? "[]" : dcstAlloc)}, ${tlV} = ${tern(profileRecognizer, "undefined", "[]")}` : `${i}const ${chV} = ${tern(profileRecognizer, "undefined", capturesChildren ? "[]" : dcstAlloc)}, ${rawV} = ${tern(profileRecognizer, "undefined", capturesRaw ? "[]" : dcstAlloc)}`;
|
|
9464
9819
|
const innerTl = structural || capturesTrivia ? structural && !innerEnablesTriviaCapture ? `${capTLv} ? ${tlV} : undefined` : tlV : "undefined";
|
|
9465
|
-
const fieldsOn = structural ? capFv ?? "false" :
|
|
9820
|
+
const fieldsOn = structural ? capFv ?? "false" : tern(profileRecognizer, "false", capturesFields ? "true" : "false");
|
|
9466
9821
|
const sf = hasFields ? v(ctx, "_sf") : null;
|
|
9467
9822
|
const fArr = hasFields ? v(ctx, "_fa") : null;
|
|
9468
9823
|
const fObj = hasFields ? v(ctx, "_fields") : "undefined";
|
|
@@ -9471,7 +9826,7 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
|
|
|
9471
9826
|
const installTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${capTLv ?? "false"}; _ctx._cstTriviaLog = ${innerTl}${smk ? `; _ctx._triviaCaptureMask = ${capTLv} && _ctx.build !== undefined && _ctx.build._parsemanTriviaKinds !== undefined ? _ctx.build._parsemanTriviaKinds(${JSON.stringify(def.type)}) : ${smk}` : ""}` : "";
|
|
9472
9827
|
const restoreTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${smk ? `; _ctx._triviaCaptureMask = ${smk}` : ""}` : "";
|
|
9473
9828
|
const preGuard = [];
|
|
9474
|
-
if (!ctx.recovery && !ctx.coverage &&
|
|
9829
|
+
if (!ctx.recovery && !ctx.coverage && needsFirstSetGuard(def.parser)) {
|
|
9475
9830
|
const gcV = v(ctx, "_ngc");
|
|
9476
9831
|
const gExp = armStaticExpected(ctx, def.parser);
|
|
9477
9832
|
const gFail = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${pos}; _ctx._fx = ${gExp}; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : failReturnArr(ctx, gExp, pos);
|
|
@@ -9482,7 +9837,6 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
|
|
|
9482
9837
|
}
|
|
9483
9838
|
const stmts = [
|
|
9484
9839
|
...preGuard,
|
|
9485
|
-
profHoist,
|
|
9486
9840
|
allocStmt,
|
|
9487
9841
|
`${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren${saveTrivia}${sf ? `, ${sf} = _ctx._fields` : ""}`,
|
|
9488
9842
|
`${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}${installTrivia}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
|
|
@@ -9499,21 +9853,13 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
|
|
|
9499
9853
|
}
|
|
9500
9854
|
stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}${restoreTrivia}${sf ? `; _ctx._fields = ${sf}` : ""}`);
|
|
9501
9855
|
stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
|
|
9502
|
-
stmts.push(
|
|
9503
|
-
`${i}if (_ctx._pmProfile) {`,
|
|
9504
|
-
`${i} _ctx._pmProfile.nodes++`,
|
|
9505
|
-
`${i} if (${profileCapture}) {`,
|
|
9506
|
-
`${i} _ctx._pmProfile.childSlots += ${chV}.length; _ctx._pmProfile.rawSlots += ${rawV}.length; _ctx._pmProfile.triviaSlots += ${tlV}.length${fArr ? `; _ctx._pmProfile.fieldSlots += ${fArr} ? ${fArr}.length : 0` : ""}`,
|
|
9507
|
-
`${i} }`,
|
|
9508
|
-
`${i}}`
|
|
9509
|
-
);
|
|
9510
9856
|
let stV = "undefined";
|
|
9511
9857
|
if (structural) {
|
|
9512
9858
|
stV = v(ctx, "_nst");
|
|
9513
9859
|
stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
|
|
9514
9860
|
} else if (clonesState) {
|
|
9515
9861
|
stV = v(ctx, "_nst");
|
|
9516
|
-
stmts.push(`${i}const ${stV} =
|
|
9862
|
+
stmts.push(`${i}const ${stV} = ${andGate(notGate(orGate(profileRecognizer, profileCapture)), "_ctx.state !== undefined")} ? Object.assign({}, _ctx.state) : undefined`);
|
|
9517
9863
|
}
|
|
9518
9864
|
if (sf && fArr) {
|
|
9519
9865
|
const fe = v(ctx, "_fe");
|
|
@@ -9537,19 +9883,22 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
|
|
|
9537
9883
|
if (ctx.lineTracking) stmts.push(`${i}const ${nodeSpanV} = ${nodeSpanExpr}`);
|
|
9538
9884
|
const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: ${nodeSpanV}, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, nodeSpanV, tlV) : def.project !== void 0 ? emitNodeProjectExpr(def.type, def.project, chV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV})`;
|
|
9539
9885
|
const hostBuildArgs = `${JSON.stringify(def.type)}, ${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV}${def.tags !== void 0 && def.tags.length > 0 ? `, ${JSON.stringify(def.tags)}` : ""}`;
|
|
9540
|
-
const hostBuildExpr = `_ctx.
|
|
9886
|
+
const hostBuildExpr = `_ctx.build(${hostBuildArgs})`;
|
|
9541
9887
|
const ndExpr = structural ? `_ctx.build !== undefined ? (${hostBuildExpr}) : (${buildExpr})` : cstOut ? hostBuildExpr : buildExpr;
|
|
9542
|
-
const
|
|
9888
|
+
const hostCollapses = structural || cstOut;
|
|
9889
|
+
const hostCollapseExpr = hostCollapses ? `_ctx.build !== undefined && _ctx.build._parsemanCstCollapse !== undefined && ${chV}.length === 1 && ${rawV}.length === 1 && _ctx.build._parsemanCstCollapse(${JSON.stringify(def.type)}, ${chV}[0], ${chV}, ${rawV}) ? ${chV}[0] : (${ndExpr})` : ndExpr;
|
|
9543
9890
|
const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
|
|
9544
9891
|
const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
|
|
9545
9892
|
const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
);
|
|
9893
|
+
const ndGate = orGate(profileRecognizer, profileCapture);
|
|
9894
|
+
const recGate = notGate(profileRecognizer);
|
|
9895
|
+
const pushIndent = recGate === "true" ? i : `${i} `;
|
|
9896
|
+
const pushCh = `${pushIndent}if (${sc}) ${sc}.push(${ndV})`;
|
|
9897
|
+
ctx.needsRawEntry = true;
|
|
9898
|
+
const pushRaw = `${pushIndent}if (${sr}) ${sr}.push(_rawEntry(${ndV}, input, ${pos}, ${endVar}${ctx.lineTracking ? `, ${nodeSpanV}` : ""}))`;
|
|
9899
|
+
stmts.push(`${i}const ${ndV} = ${tern(ndGate, "undefined", `(${finalExpr})`)}`);
|
|
9900
|
+
if (recGate === "true") stmts.push(pushCh, pushRaw);
|
|
9901
|
+
else stmts.push(`${i}if (${recGate}) {`, pushCh, pushRaw, `${i}}`);
|
|
9553
9902
|
return { stmts, valueVar: ndV, endVar };
|
|
9554
9903
|
}
|
|
9555
9904
|
function emitRuntimeFallback(parser2, ctx, pos) {
|
|
@@ -9578,7 +9927,12 @@ function emitLazy(p, def, ctx, pos) {
|
|
|
9578
9927
|
} catch {
|
|
9579
9928
|
return emitRuntimeFallback(p, ctx, pos);
|
|
9580
9929
|
}
|
|
9581
|
-
|
|
9930
|
+
const cost = usage.sizes.get(resolved) ?? 1;
|
|
9931
|
+
if (cost <= ctx.inlineLeft) {
|
|
9932
|
+
ctx.inlineLeft -= cost;
|
|
9933
|
+
return emit(resolved, ctx, pos);
|
|
9934
|
+
}
|
|
9935
|
+
_inlineCapSink?.push({ fn: ctx.currentFnName ?? "<root>", nodes: cost });
|
|
9582
9936
|
}
|
|
9583
9937
|
if (!ctx.namedParsers.has(p)) {
|
|
9584
9938
|
const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
|
|
@@ -9593,6 +9947,9 @@ function emitLazy(p, def, ctx, pos) {
|
|
|
9593
9947
|
const savedIndent = ctx.indent;
|
|
9594
9948
|
const savedFailLabel = ctx.failLabel;
|
|
9595
9949
|
const savedRecord = ctx.recordFail;
|
|
9950
|
+
const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
|
|
9951
|
+
ctx.inlineLeft = ctx.inlineMax;
|
|
9952
|
+
ctx.currentFnName = fnName2;
|
|
9596
9953
|
const failureRuleId = p === ctx.coverage?.entry ? void 0 : ctx.coverage?.plan.rules.get(p) ?? ctx.coverage?.plan.rules.get(resolved);
|
|
9597
9954
|
const savedActiveRule = ctx.activeCoverageRuleId;
|
|
9598
9955
|
const savedSuppressFailure = ctx.suppressCoverageFailure;
|
|
@@ -9607,6 +9964,8 @@ function emitLazy(p, def, ctx, pos) {
|
|
|
9607
9964
|
ctx.indent = savedIndent;
|
|
9608
9965
|
ctx.failLabel = savedFailLabel;
|
|
9609
9966
|
ctx.recordFail = savedRecord;
|
|
9967
|
+
ctx.inlineLeft = savedInlineLeft;
|
|
9968
|
+
ctx.currentFnName = savedFnName;
|
|
9610
9969
|
ctx.activeCoverageRuleId = savedActiveRule;
|
|
9611
9970
|
ctx.suppressCoverageFailure = savedSuppressFailure;
|
|
9612
9971
|
pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar, failureRuleId);
|
|
@@ -9701,6 +10060,9 @@ function emit(p, ctx, pos) {
|
|
|
9701
10060
|
const savedIndent = ctx.indent;
|
|
9702
10061
|
const savedFailLabel = ctx.failLabel;
|
|
9703
10062
|
const savedRecord = ctx.recordFail;
|
|
10063
|
+
const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
|
|
10064
|
+
ctx.inlineLeft = ctx.inlineMax;
|
|
10065
|
+
ctx.currentFnName = fnName;
|
|
9704
10066
|
ctx.indent = 1;
|
|
9705
10067
|
ctx.failLabel = "_pfail";
|
|
9706
10068
|
ctx.recordFail = true;
|
|
@@ -9708,6 +10070,8 @@ function emit(p, ctx, pos) {
|
|
|
9708
10070
|
ctx.indent = savedIndent;
|
|
9709
10071
|
ctx.failLabel = savedFailLabel;
|
|
9710
10072
|
ctx.recordFail = savedRecord;
|
|
10073
|
+
ctx.inlineLeft = savedInlineLeft;
|
|
10074
|
+
ctx.currentFnName = savedFnName;
|
|
9711
10075
|
pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
|
|
9712
10076
|
return instrument(emitNamedFnCall(ctx, fnName, pos));
|
|
9713
10077
|
}
|
|
@@ -9901,7 +10265,7 @@ function emitDispatch(p, ctx, pos) {
|
|
|
9901
10265
|
case "peek":
|
|
9902
10266
|
return emitPeek(def, ctx, pos);
|
|
9903
10267
|
case "routed":
|
|
9904
|
-
return emitRouted(ctx, pos);
|
|
10268
|
+
return emitRouted(ctx, pos, def);
|
|
9905
10269
|
case "node":
|
|
9906
10270
|
return emitNode(def, ctx, pos);
|
|
9907
10271
|
case "scanTo":
|
|
@@ -9936,6 +10300,9 @@ function emitDispatch(p, ctx, pos) {
|
|
|
9936
10300
|
const savedIndent = ctx.indent;
|
|
9937
10301
|
const savedFailLabel = ctx.failLabel;
|
|
9938
10302
|
const savedRecord = ctx.recordFail;
|
|
10303
|
+
const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
|
|
10304
|
+
ctx.inlineLeft = ctx.inlineMax;
|
|
10305
|
+
ctx.currentFnName = fnName;
|
|
9939
10306
|
ctx.indent = 1;
|
|
9940
10307
|
ctx.failLabel = "_pfail";
|
|
9941
10308
|
ctx.recordFail = true;
|
|
@@ -9943,6 +10310,8 @@ function emitDispatch(p, ctx, pos) {
|
|
|
9943
10310
|
ctx.indent = savedIndent;
|
|
9944
10311
|
ctx.failLabel = savedFailLabel;
|
|
9945
10312
|
ctx.recordFail = savedRecord;
|
|
10313
|
+
ctx.inlineLeft = savedInlineLeft;
|
|
10314
|
+
ctx.currentFnName = savedFnName;
|
|
9946
10315
|
pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
|
|
9947
10316
|
}
|
|
9948
10317
|
const fn = ctx.namedParsers.get(innerParser);
|
|
@@ -10101,12 +10470,16 @@ function childrenOf3(def) {
|
|
|
10101
10470
|
return [def.parser, def.sentinel];
|
|
10102
10471
|
case "scanTo":
|
|
10103
10472
|
return [def.sentinel, ...def.skip];
|
|
10473
|
+
// A `routed()` fallback IS emitted at this site (emitRouted), so the usage
|
|
10474
|
+
// analysis must see the edge — otherwise a fallback shared by several routed()
|
|
10475
|
+
// sites is miscounted as single-use and inlined at each.
|
|
10476
|
+
case "routed":
|
|
10477
|
+
return def.fallback ? [def.fallback] : [];
|
|
10104
10478
|
case "lazy":
|
|
10105
10479
|
case "literal":
|
|
10106
10480
|
case "regex":
|
|
10107
10481
|
case "keywords":
|
|
10108
10482
|
case "guard":
|
|
10109
|
-
case "routed":
|
|
10110
10483
|
case "unknown":
|
|
10111
10484
|
return [];
|
|
10112
10485
|
}
|
|
@@ -10127,6 +10500,17 @@ function isHoistableTag(tag) {
|
|
|
10127
10500
|
return HOISTABLE_TAGS.has(tag);
|
|
10128
10501
|
}
|
|
10129
10502
|
var HOIST_MIN_SUBTREE = 3;
|
|
10503
|
+
var INLINE_MAX_NODES = 1e3;
|
|
10504
|
+
function resolveInlineMax(explicit) {
|
|
10505
|
+
if (explicit !== void 0) return explicit;
|
|
10506
|
+
const env = typeof process !== "undefined" ? process.env?.PARSEMAN_MAX_INLINE : void 0;
|
|
10507
|
+
if (env !== void 0 && env !== "") {
|
|
10508
|
+
if (env === "off" || env === "infinity") return Infinity;
|
|
10509
|
+
const n = Number(env);
|
|
10510
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
10511
|
+
}
|
|
10512
|
+
return INLINE_MAX_NODES;
|
|
10513
|
+
}
|
|
10130
10514
|
function analyzeLazyUsage(root) {
|
|
10131
10515
|
return analyzeLazyUsageMulti([root]);
|
|
10132
10516
|
}
|
|
@@ -10232,102 +10616,6 @@ function ruleDependencies(ruleMap) {
|
|
|
10232
10616
|
}
|
|
10233
10617
|
return deps;
|
|
10234
10618
|
}
|
|
10235
|
-
function resolveGatingLevel(opt) {
|
|
10236
|
-
const explicit = typeof opt === "string" ? opt : opt?.level;
|
|
10237
|
-
if (explicit !== void 0) return explicit;
|
|
10238
|
-
const env = typeof process !== "undefined" ? process.env?.PARSEMAN_GATING : void 0;
|
|
10239
|
-
if (env === "off" || env === "warn" || env === "error") return env;
|
|
10240
|
-
return "warn";
|
|
10241
|
-
}
|
|
10242
|
-
function runGatingDiagnostic(combinator, opt) {
|
|
10243
|
-
return reportGating(opt, (o) => analyzeGating(combinator, o));
|
|
10244
|
-
}
|
|
10245
|
-
function runGatingDiagnosticRules(ruleMap, opt) {
|
|
10246
|
-
return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
|
|
10247
|
-
}
|
|
10248
|
-
function runFusedGatingDiagnostic(getRuleMaps, opt, getOpaque) {
|
|
10249
|
-
if (resolveGatingLevel(opt) === "off") return void 0;
|
|
10250
|
-
const ruleMaps = getRuleMaps();
|
|
10251
|
-
const opaque = getOpaque?.() ?? [];
|
|
10252
|
-
const opaqueFindings = opaque.map((o) => ({
|
|
10253
|
-
rule: o.ruleNames.length > 0 ? o.ruleNames.join(", ") : `<artifact ${o.ns}>`,
|
|
10254
|
-
kind: "opaque-artifact",
|
|
10255
|
-
reason: `precompiled artifact "${o.ns}" carries compiled rule functions, not re-lowerable IR \u2014 its choices were not examined at this fuse.`
|
|
10256
|
-
}));
|
|
10257
|
-
if (!ruleMaps.some((map) => hasExternalRuleRef(map))) {
|
|
10258
|
-
if (opaqueFindings.length === 0) return void 0;
|
|
10259
|
-
return reportGating(opt, () => ({
|
|
10260
|
-
totalChoices: 0,
|
|
10261
|
-
gated: 0,
|
|
10262
|
-
recoverable: 0,
|
|
10263
|
-
unanalysable: opaqueFindings,
|
|
10264
|
-
ungated: [],
|
|
10265
|
-
accepted: [],
|
|
10266
|
-
deferred: [],
|
|
10267
|
-
acceptedUnused: [],
|
|
10268
|
-
choices: [],
|
|
10269
|
-
antiPatterns: []
|
|
10270
|
-
}));
|
|
10271
|
-
}
|
|
10272
|
-
const winners = /* @__PURE__ */ new Map();
|
|
10273
|
-
for (const map of ruleMaps) for (const [name, rule] of map) {
|
|
10274
|
-
if (rule._def.tag === "lazy") {
|
|
10275
|
-
try {
|
|
10276
|
-
rule._def.thunk();
|
|
10277
|
-
} catch {
|
|
10278
|
-
continue;
|
|
10279
|
-
}
|
|
10280
|
-
}
|
|
10281
|
-
winners.set(name, rule);
|
|
10282
|
-
}
|
|
10283
|
-
const entries = [...winners];
|
|
10284
|
-
return reportGating(opt, (o) => {
|
|
10285
|
-
const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
|
|
10286
|
-
const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
|
|
10287
|
-
return {
|
|
10288
|
-
...fused,
|
|
10289
|
-
ungated: fused.ungated.filter((c) => deferredHere.has(c.id)),
|
|
10290
|
-
antiPatterns: [],
|
|
10291
|
-
unanalysable: [...fused.unanalysable, ...opaqueFindings]
|
|
10292
|
-
};
|
|
10293
|
-
});
|
|
10294
|
-
}
|
|
10295
|
-
function reportGating(opt, analyze) {
|
|
10296
|
-
const level = resolveGatingLevel(opt);
|
|
10297
|
-
if (level === "off") return void 0;
|
|
10298
|
-
const obj = opt !== null && typeof opt === "object" ? opt : void 0;
|
|
10299
|
-
const analyzeOpts = obj?.accept !== void 0 || obj?.entryName !== void 0 ? { ...obj.accept !== void 0 ? { accept: obj.accept } : {}, ...obj.entryName !== void 0 ? { entryName: obj.entryName } : {} } : void 0;
|
|
10300
|
-
let report;
|
|
10301
|
-
try {
|
|
10302
|
-
report = analyze(analyzeOpts);
|
|
10303
|
-
} catch (e) {
|
|
10304
|
-
const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
|
|
10305
|
-
const failed = {
|
|
10306
|
-
totalChoices: 0,
|
|
10307
|
-
gated: 0,
|
|
10308
|
-
recoverable: 0,
|
|
10309
|
-
unanalysable: [{ rule: "<whole grammar>", kind: "not-a-combinator", reason: `gating analysis threw \u2014 ${reason}` }],
|
|
10310
|
-
ungated: [],
|
|
10311
|
-
accepted: [],
|
|
10312
|
-
deferred: [],
|
|
10313
|
-
acceptedUnused: [],
|
|
10314
|
-
choices: [],
|
|
10315
|
-
antiPatterns: []
|
|
10316
|
-
};
|
|
10317
|
-
const failLines = formatGatingWarnings(failed);
|
|
10318
|
-
if (level === "error") throw new Error(`parseman: gating analysis FAILED (no verdict available)
|
|
10319
|
-
${failLines.join("\n")}`);
|
|
10320
|
-
for (const l of failLines) console.warn(l);
|
|
10321
|
-
return failed;
|
|
10322
|
-
}
|
|
10323
|
-
const lines = formatGatingWarnings(report);
|
|
10324
|
-
if (lines.length > 0) {
|
|
10325
|
-
if (level === "error") throw new Error(`parseman: ${report.ungated.length} ungated hot choice(s) / ${report.antiPatterns.length} anti-pattern(s) / ${report.unanalysable.length} unanalysable rule(s)
|
|
10326
|
-
${lines.join("\n")}`);
|
|
10327
|
-
for (const l of lines) console.warn(l);
|
|
10328
|
-
}
|
|
10329
|
-
return report;
|
|
10330
|
-
}
|
|
10331
10619
|
function resolveDuplicationLevel(opt) {
|
|
10332
10620
|
const explicit = typeof opt === "string" ? opt : opt?.level;
|
|
10333
10621
|
if (explicit !== void 0) return explicit;
|
|
@@ -10370,7 +10658,17 @@ function runDuplicationDiagnosticRules(ruleMap, opt) {
|
|
|
10370
10658
|
return reportDuplication(opt, (o) => analyzeDuplicationRules(ruleMap, o));
|
|
10371
10659
|
}
|
|
10372
10660
|
function compile(combinator, mapFnSources, opts) {
|
|
10373
|
-
const
|
|
10661
|
+
const drain = beginCompileDegradationDrain();
|
|
10662
|
+
let completed = false;
|
|
10663
|
+
try {
|
|
10664
|
+
const compiled = compileImpl(combinator, mapFnSources, opts);
|
|
10665
|
+
completed = true;
|
|
10666
|
+
return compiled;
|
|
10667
|
+
} finally {
|
|
10668
|
+
drain(completed);
|
|
10669
|
+
}
|
|
10670
|
+
}
|
|
10671
|
+
function compileImpl(combinator, mapFnSources, opts) {
|
|
10374
10672
|
runDuplicationDiagnostic(combinator, opts?.duplication);
|
|
10375
10673
|
markUnusedValues(combinator);
|
|
10376
10674
|
const grammarTrivia = combinator._meta.grammarTrivia;
|
|
@@ -10378,9 +10676,12 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
10378
10676
|
const grammarScanSkip = combinator._meta.grammarScanSkip;
|
|
10379
10677
|
const grammarHostMode = combinator._meta.grammarHostMode;
|
|
10380
10678
|
const grammarTrackLines = combinator._meta.grammarTrackLines;
|
|
10679
|
+
const _inlineMax = resolveInlineMax(opts?.maxInline);
|
|
10381
10680
|
const ctx = {
|
|
10382
10681
|
vars: 0,
|
|
10383
10682
|
indent: 1,
|
|
10683
|
+
inlineMax: _inlineMax,
|
|
10684
|
+
inlineLeft: _inlineMax,
|
|
10384
10685
|
regexDecls: [],
|
|
10385
10686
|
regexMap: /* @__PURE__ */ new Map(),
|
|
10386
10687
|
expectedDecls: [],
|
|
@@ -10426,11 +10727,13 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
10426
10727
|
const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
|
|
10427
10728
|
const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
|
|
10428
10729
|
const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
|
|
10730
|
+
const rawEntryDecls = ctx.needsRawEntry ? [RAW_ENTRY_DECL] : [];
|
|
10429
10731
|
const lineTrackDecls = ctx.needsLineTrack ? [LINE_TRACK_DECL] : [];
|
|
10430
10732
|
const lineSpanDecls = ctx.needsLineSpan ? [LINE_SPAN_DECL] : [];
|
|
10431
10733
|
const source = [
|
|
10432
10734
|
...emptyTlDecls,
|
|
10433
10735
|
...hostReadsDecls,
|
|
10736
|
+
...rawEntryDecls,
|
|
10434
10737
|
...lineTrackDecls,
|
|
10435
10738
|
...lineSpanDecls,
|
|
10436
10739
|
...ctx.regexDecls,
|
|
@@ -10445,6 +10748,7 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
10445
10748
|
const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
|
|
10446
10749
|
...emptyTlDecls,
|
|
10447
10750
|
...hostReadsDecls,
|
|
10751
|
+
...rawEntryDecls,
|
|
10448
10752
|
...lineTrackDecls,
|
|
10449
10753
|
...lineSpanDecls,
|
|
10450
10754
|
...ctx.regexDecls,
|
|
@@ -10494,8 +10798,7 @@ function compile(combinator, mapFnSources, opts) {
|
|
|
10494
10798
|
const compiledMeta = {
|
|
10495
10799
|
source,
|
|
10496
10800
|
inlineExpression,
|
|
10497
|
-
...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
|
|
10498
|
-
...gatingReport === void 0 ? {} : { gating: gatingReport }
|
|
10801
|
+
...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
|
|
10499
10802
|
};
|
|
10500
10803
|
if (!ctx.lineTracking) {
|
|
10501
10804
|
return {
|
|
@@ -10561,7 +10864,6 @@ function publicRuleWrapperSource(rule, fnSource, ambientTriviaKindLabels, ambien
|
|
|
10561
10864
|
}
|
|
10562
10865
|
function compileLinkable(ruleMapArg, ns, opts) {
|
|
10563
10866
|
if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
|
|
10564
|
-
if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
|
|
10565
10867
|
if (opts?.duplication !== void 0) runDuplicationDiagnosticRules(ruleMapArg, opts.duplication);
|
|
10566
10868
|
for (const [, rule] of ruleMapArg) markUnusedValues(rule);
|
|
10567
10869
|
const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
|
|
@@ -10580,9 +10882,12 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
10580
10882
|
}
|
|
10581
10883
|
});
|
|
10582
10884
|
const nodeMeta = new Map(ruleMap.map(([name, rule]) => [name, collectGrammarReflection([[name, rule]], { followLazy: false })]));
|
|
10885
|
+
const _inlineMax = resolveInlineMax(opts?.maxInline);
|
|
10583
10886
|
const ctx = {
|
|
10584
10887
|
vars: 0,
|
|
10585
10888
|
indent: 1,
|
|
10889
|
+
inlineMax: _inlineMax,
|
|
10890
|
+
inlineLeft: _inlineMax,
|
|
10586
10891
|
regexDecls: [],
|
|
10587
10892
|
regexMap: /* @__PURE__ */ new Map(),
|
|
10588
10893
|
expectedDecls: [],
|
|
@@ -10678,6 +10983,9 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
10678
10983
|
}
|
|
10679
10984
|
}
|
|
10680
10985
|
const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
|
|
10986
|
+
const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
|
|
10987
|
+
ctx.inlineLeft = ctx.inlineMax;
|
|
10988
|
+
ctx.currentFnName = fn;
|
|
10681
10989
|
ctx.indent = 1;
|
|
10682
10990
|
ctx.failLabel = "_pfail";
|
|
10683
10991
|
ctx.recordFail = true;
|
|
@@ -10693,6 +11001,8 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
10693
11001
|
ctx.indent = savedIndent;
|
|
10694
11002
|
ctx.failLabel = savedFail;
|
|
10695
11003
|
ctx.recordFail = savedRec;
|
|
11004
|
+
ctx.inlineLeft = savedInlineLeft;
|
|
11005
|
+
ctx.currentFnName = savedFnName;
|
|
10696
11006
|
const ruleId = ctx.coverage?.plan.rules.get(rule);
|
|
10697
11007
|
const stmts = ruleId === void 0 ? body.stmts : [
|
|
10698
11008
|
`${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(ruleId)})`,
|
|
@@ -10777,6 +11087,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
10777
11087
|
deps: ruleDependencies(ruleMap),
|
|
10778
11088
|
needsEmptyTl: !!ctx.needsEmptyTl,
|
|
10779
11089
|
needsHostReads: !!ctx.needsHostReads,
|
|
11090
|
+
needsRawEntry: !!ctx.needsRawEntry,
|
|
10780
11091
|
hostMode: ctx.hostMode ?? "ast",
|
|
10781
11092
|
hostBranchElided: !!ctx.hostBranchElided,
|
|
10782
11093
|
hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
|
|
@@ -10786,26 +11097,6 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
10786
11097
|
buildFns: buildSrcs ? [] : ctx.buildFns
|
|
10787
11098
|
};
|
|
10788
11099
|
}
|
|
10789
|
-
function hasExternalRuleRef(ruleMap) {
|
|
10790
|
-
const seen = /* @__PURE__ */ new Set();
|
|
10791
|
-
const visit = (p) => {
|
|
10792
|
-
if (seen.has(p)) return false;
|
|
10793
|
-
seen.add(p);
|
|
10794
|
-
const def = p._def;
|
|
10795
|
-
if (def.tag === "lazy") {
|
|
10796
|
-
let resolved;
|
|
10797
|
-
try {
|
|
10798
|
-
resolved = def.thunk();
|
|
10799
|
-
} catch {
|
|
10800
|
-
resolved = void 0;
|
|
10801
|
-
}
|
|
10802
|
-
if (resolved === void 0) return p._ruleName !== void 0;
|
|
10803
|
-
return visit(resolved);
|
|
10804
|
-
}
|
|
10805
|
-
return childrenOf3(def).some(visit);
|
|
10806
|
-
};
|
|
10807
|
-
return ruleMap.some(([, rule]) => visit(rule));
|
|
10808
|
-
}
|
|
10809
11100
|
function hasSemanticReduction(roots, externalRefs) {
|
|
10810
11101
|
const seen = /* @__PURE__ */ new Set();
|
|
10811
11102
|
const visit = (parser2) => {
|
|
@@ -10851,14 +11142,16 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootR
|
|
|
10851
11142
|
const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
|
|
10852
11143
|
const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
|
|
10853
11144
|
const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
|
|
11145
|
+
const rawEntryDecl = ctx.needsRawEntry ? ` ${RAW_ENTRY_DECL}` : "";
|
|
10854
11146
|
const lineTrackDecl = ctx.needsLineTrack ? ` ${LINE_TRACK_DECL}` : "";
|
|
10855
11147
|
const lineSpanDecl = ctx.needsLineSpan ? ` ${LINE_SPAN_DECL}` : "";
|
|
10856
|
-
const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!lineTrackDecl || !!lineSpanDecl;
|
|
11148
|
+
const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!rawEntryDecl || !!lineTrackDecl || !!lineSpanDecl;
|
|
10857
11149
|
if (!needsWrapper) return innerFn;
|
|
10858
11150
|
const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
|
|
10859
11151
|
const hoistedDecls = [
|
|
10860
11152
|
emptyTlDecl,
|
|
10861
11153
|
hostReadsDecl,
|
|
11154
|
+
rawEntryDecl,
|
|
10862
11155
|
lineTrackDecl,
|
|
10863
11156
|
lineSpanDecl,
|
|
10864
11157
|
...ctx.regexDecls.map((d) => ` ${d}`),
|
|
@@ -10928,11 +11221,12 @@ function childrenOf4(def) {
|
|
|
10928
11221
|
return [def.main, def.skipped];
|
|
10929
11222
|
case "scanTo":
|
|
10930
11223
|
return [def.sentinel, ...def.skip];
|
|
11224
|
+
case "routed":
|
|
11225
|
+
return def.fallback ? [def.fallback] : [];
|
|
10931
11226
|
case "lazy":
|
|
10932
11227
|
case "literal":
|
|
10933
11228
|
case "regex":
|
|
10934
11229
|
case "keywords":
|
|
10935
|
-
case "routed":
|
|
10936
11230
|
case "guard":
|
|
10937
11231
|
case "recover":
|
|
10938
11232
|
case "unknown":
|
|
@@ -11014,7 +11308,7 @@ function evalRuleMapIR(ir) {
|
|
|
11014
11308
|
l._def.fnSrc = src;
|
|
11015
11309
|
return l;
|
|
11016
11310
|
};
|
|
11017
|
-
const _nd = (type, child, src, opts, staticError) => {
|
|
11311
|
+
const _nd = (type, child, src, opts, staticError, sigSrc) => {
|
|
11018
11312
|
if (staticError !== void 0 && staticError.length > 0) {
|
|
11019
11313
|
throw new Error(`IR direct node builder for ${type} must be macro-static and self-contained; unsupported binding(s): ${staticError.join(", ")}`);
|
|
11020
11314
|
}
|
|
@@ -11023,6 +11317,7 @@ function evalRuleMapIR(ir) {
|
|
|
11023
11317
|
}), opts);
|
|
11024
11318
|
n._def.buildSrc = src;
|
|
11025
11319
|
if (staticError !== void 0) n._def.buildStaticError = staticError;
|
|
11320
|
+
if (sigSrc !== void 0) n._def.buildSigSrc = sigSrc;
|
|
11026
11321
|
return n;
|
|
11027
11322
|
};
|
|
11028
11323
|
const _gch = (items) => {
|
|
@@ -11335,7 +11630,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
|
|
|
11335
11630
|
case "peek":
|
|
11336
11631
|
return `peek(${kid(def.parser)})`;
|
|
11337
11632
|
case "routed":
|
|
11338
|
-
return "routed()"
|
|
11633
|
+
return def.fallback === void 0 ? "routed()" : `routed(${kid(def.fallback)})`;
|
|
11339
11634
|
case "trivia":
|
|
11340
11635
|
return `trivia(${kid(def.parser)})`;
|
|
11341
11636
|
case "token":
|
|
@@ -11368,6 +11663,9 @@ ${outerDecls.map((d) => " " + d).join("\n")}
|
|
|
11368
11663
|
...projectEntry === void 0 ? [] : [projectEntry],
|
|
11369
11664
|
...def.captureTrivia ? ["captureTrivia: true"] : [],
|
|
11370
11665
|
...def.trailingTrivia ? ["trailingTrivia: true"] : [],
|
|
11666
|
+
// Declared or resolved arity. Carried so a re-lowered composed artifact does
|
|
11667
|
+
// not silently re-acquire the fail-open capture cost this module resolved away.
|
|
11668
|
+
...def.buildArity === void 0 ? [] : [`buildArity: ${def.buildArity}`],
|
|
11371
11669
|
...tagEntry === void 0 ? [] : [tagEntry]
|
|
11372
11670
|
];
|
|
11373
11671
|
const opts = optEntries.length > 0 ? `, { ${optEntries.join(", ")} }` : "";
|
|
@@ -11376,8 +11674,14 @@ ${outerDecls.map((d) => " " + d).join("\n")}
|
|
|
11376
11674
|
return opts ? `node(${kid(def.parser)}, undefined${opts})` : `node(${kid(def.parser)})`;
|
|
11377
11675
|
}
|
|
11378
11676
|
if (def.buildSrc !== void 0) {
|
|
11379
|
-
const
|
|
11380
|
-
|
|
11677
|
+
const tail = [];
|
|
11678
|
+
if (def.buildStaticError !== void 0 || def.buildSigSrc !== void 0) {
|
|
11679
|
+
if (!opts) tail.push("undefined");
|
|
11680
|
+
tail.push(def.buildStaticError === void 0 ? "undefined" : JSON.stringify(def.buildStaticError));
|
|
11681
|
+
}
|
|
11682
|
+
if (def.buildSigSrc !== void 0) tail.push(JSON.stringify(def.buildSigSrc));
|
|
11683
|
+
const trailing = tail.length > 0 ? `, ${tail.join(", ")}` : "";
|
|
11684
|
+
return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts}${trailing})`;
|
|
11381
11685
|
}
|
|
11382
11686
|
return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
|
|
11383
11687
|
}
|
|
@@ -11434,7 +11738,7 @@ function cstBuildHost(typeOrOptions, children2, _fields, span, rawChildren, triv
|
|
|
11434
11738
|
return host;
|
|
11435
11739
|
}
|
|
11436
11740
|
cstBuildHost._parsemanCstOutput = true;
|
|
11437
|
-
function fusedBody(pieces) {
|
|
11741
|
+
function fusedBody(pieces, hoist) {
|
|
11438
11742
|
for (const p of pieces) {
|
|
11439
11743
|
if (p.v === void 0) {
|
|
11440
11744
|
throw new Error(
|
|
@@ -11458,12 +11762,14 @@ function fusedBody(pieces) {
|
|
|
11458
11762
|
const contributingPieces = [...contributing];
|
|
11459
11763
|
const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
|
|
11460
11764
|
const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
|
|
11765
|
+
const needsRawEntry = contributingPieces.some((p) => p.needsRawEntry);
|
|
11461
11766
|
const lines = [
|
|
11462
11767
|
// Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
|
|
11463
11768
|
"const _pfFail = {}",
|
|
11464
11769
|
"let _pfEnd",
|
|
11465
11770
|
...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
|
|
11466
11771
|
...needsHostReads ? [HOST_READS_DECL] : [],
|
|
11772
|
+
...needsRawEntry ? [RAW_ENTRY_DECL] : [],
|
|
11467
11773
|
// Each contributing artifact's namespaced private prelude (regexes, _pf, …).
|
|
11468
11774
|
...new Set(contributingPieces.flatMap((p) => p.prelude)),
|
|
11469
11775
|
// The winning `_r_<Name>` function for each rule (one per name → no redeclare).
|
|
@@ -11472,8 +11778,7 @@ function fusedBody(pieces) {
|
|
|
11472
11778
|
const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
|
|
11473
11779
|
const reflection = mergeGrammarReflections([...winner].map(([k, p]) => p.nodeMeta.get(k) ?? { nodes: [] }));
|
|
11474
11780
|
const { mode, elided } = hostModeOfPieces(pieces);
|
|
11475
|
-
const
|
|
11476
|
-
...lines,
|
|
11781
|
+
const tailSrc = [
|
|
11477
11782
|
"const _map = {",
|
|
11478
11783
|
wrapperEntries.join(",\n"),
|
|
11479
11784
|
"}",
|
|
@@ -11489,6 +11794,7 @@ function fusedBody(pieces) {
|
|
|
11489
11794
|
"}",
|
|
11490
11795
|
"return _map"
|
|
11491
11796
|
].join("\n");
|
|
11797
|
+
const rawBody = [...lines, tailSrc].join("\n");
|
|
11492
11798
|
const ANY2 = { kind: "any" };
|
|
11493
11799
|
const EMPTY3 = { kind: "empty" };
|
|
11494
11800
|
const recipes = /* @__PURE__ */ new Map();
|
|
@@ -11529,7 +11835,7 @@ function fusedBody(pieces) {
|
|
|
11529
11835
|
}
|
|
11530
11836
|
if (!changed) break;
|
|
11531
11837
|
}
|
|
11532
|
-
const
|
|
11838
|
+
const resolveFS = (src) => src.replace(
|
|
11533
11839
|
// Rule name + code-point var are both JS identifiers (rule names are validated
|
|
11534
11840
|
// at compile time — see assertRuleName in codegen), so an identifier class
|
|
11535
11841
|
// matches every well-formed placeholder.
|
|
@@ -11540,6 +11846,7 @@ function fusedBody(pieces) {
|
|
|
11540
11846
|
return `(${firstSetCond(codevar, fs)})`;
|
|
11541
11847
|
}
|
|
11542
11848
|
);
|
|
11849
|
+
const body = hoist === void 0 ? resolveFS(rawBody) : [...hoist.claim(lines.map(resolveFS)), resolveFS(tailSrc)].join("\n");
|
|
11543
11850
|
const env = {};
|
|
11544
11851
|
for (const p of contributingPieces) {
|
|
11545
11852
|
if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
|
|
@@ -11579,17 +11886,6 @@ var COMPOSED_COVERAGE_RULES = /* @__PURE__ */ Symbol.for("parseman.composedCover
|
|
|
11579
11886
|
function isIRPiece(p) {
|
|
11580
11887
|
return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
|
|
11581
11888
|
}
|
|
11582
|
-
function once(fn) {
|
|
11583
|
-
let done = false;
|
|
11584
|
-
let value;
|
|
11585
|
-
return () => {
|
|
11586
|
-
if (!done) {
|
|
11587
|
-
value = fn();
|
|
11588
|
-
done = true;
|
|
11589
|
-
}
|
|
11590
|
-
return value;
|
|
11591
|
-
};
|
|
11592
|
-
}
|
|
11593
11889
|
function carriedRuleMapsDetailed(carried) {
|
|
11594
11890
|
const maps = [];
|
|
11595
11891
|
const opaque = [];
|
|
@@ -11706,12 +12002,6 @@ function compose(items, opts) {
|
|
|
11706
12002
|
const used = /* @__PURE__ */ new Set();
|
|
11707
12003
|
const trivia2 = composingTriviaOf(items);
|
|
11708
12004
|
const carried = items.flatMap((item) => itemCarried(item, used, trivia2, opts?.hostMode));
|
|
11709
|
-
const detailed = once(() => carriedRuleMapsDetailed(carried));
|
|
11710
|
-
runFusedGatingDiagnostic(
|
|
11711
|
-
() => detailed().maps,
|
|
11712
|
-
void 0,
|
|
11713
|
-
() => detailed().opaque
|
|
11714
|
-
);
|
|
11715
12005
|
const pieces = carried.map((p) => materializePiece(p, trivia2, false, opts?.hostMode));
|
|
11716
12006
|
const map = fuseRules(pieces);
|
|
11717
12007
|
Object.defineProperty(map, COMPOSED_PIECES, { value: carried, enumerable: false });
|
|
@@ -11720,8 +12010,168 @@ function compose(items, opts) {
|
|
|
11720
12010
|
if (coverageRules) Object.defineProperty(map, COMPOSED_COVERAGE_RULES, { value: coverageRules, enumerable: false });
|
|
11721
12011
|
return map;
|
|
11722
12012
|
}
|
|
11723
|
-
function composeLeaf(
|
|
11724
|
-
|
|
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(", ")}`;
|
|
11725
12175
|
}
|
|
11726
12176
|
|
|
11727
12177
|
// src/analysis/grammar.ts
|
|
@@ -11738,14 +12188,1123 @@ function analyzeGrammarGating(grammar, opts) {
|
|
|
11738
12188
|
kind: "opaque-artifact",
|
|
11739
12189
|
reason: `precompiled artifact "${o.ns}" carries compiled rule functions, not re-lowerable IR \u2014 its choices cannot be examined. Recompile the contributing grammar so it carries IR (the macro emits IR by default) to bring it back into analysis.`
|
|
11740
12190
|
}));
|
|
12191
|
+
for (const o of opaque) {
|
|
12192
|
+
recordDegradation({
|
|
12193
|
+
code: "opaque-artifact",
|
|
12194
|
+
// `info`, not `warn`: the artifact is usually someone else's package, so the author
|
|
12195
|
+
// of THIS grammar has nothing to act on — but the blind spot still has to be counted.
|
|
12196
|
+
severity: "info",
|
|
12197
|
+
where: o.ruleNames.length > 0 ? o.ruleNames.join(", ") : `<artifact ${o.ns}>`,
|
|
12198
|
+
subject: `precompiled artifact "${o.ns}"`,
|
|
12199
|
+
fellBackTo: `it carries compiled rule functions rather than re-lowerable IR, so its ${o.ruleNames.length > 0 ? `${o.ruleNames.length} rule(s)` : "rules"} were NOT analysed and a clean gating report must not be read as a pass`,
|
|
12200
|
+
otherwise: "its choices would be walked like any other rule. Recompile the contributing grammar so it carries IR (the macro emits IR by default)"
|
|
12201
|
+
});
|
|
12202
|
+
}
|
|
11741
12203
|
return fromOpaque.length === 0 ? report : { ...report, unanalysable: [...report.unanalysable, ...fromOpaque] };
|
|
11742
12204
|
}
|
|
11743
12205
|
|
|
11744
|
-
// src/
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
12206
|
+
// src/analysis/diagnose.ts
|
|
12207
|
+
var isCombinator = (g) => typeof g === "object" && g !== null && "_def" in g;
|
|
12208
|
+
var isRuleEntries = (g) => Array.isArray(g);
|
|
12209
|
+
function diagnoseGrammar(grammar, opts) {
|
|
12210
|
+
const depth = degradationCaptureDepth();
|
|
12211
|
+
beginDegradationCapture();
|
|
12212
|
+
let report;
|
|
12213
|
+
let degradations;
|
|
12214
|
+
try {
|
|
12215
|
+
report = runAnalysis(grammar, opts);
|
|
12216
|
+
} catch (e) {
|
|
12217
|
+
const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
|
|
12218
|
+
report = emptyReport(`gating analysis threw \u2014 ${reason}`);
|
|
12219
|
+
} finally {
|
|
12220
|
+
degradations = unwindDegradationCapture(depth);
|
|
12221
|
+
}
|
|
12222
|
+
return assemble(report, degradations);
|
|
12223
|
+
}
|
|
12224
|
+
function examinedNothing(d) {
|
|
12225
|
+
return d.summary.totalChoices === 0 && d.summary.unanalysable > 0;
|
|
12226
|
+
}
|
|
12227
|
+
function formatGrammarDiagnosis(d) {
|
|
12228
|
+
const s = d.summary;
|
|
12229
|
+
const lines = [];
|
|
12230
|
+
lines.push(
|
|
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).`
|
|
12232
|
+
);
|
|
12233
|
+
if (s.unanalysable > 0 && !examinedNothing(d)) {
|
|
12234
|
+
lines.push(
|
|
12235
|
+
` ${s.unanalysable} rule(s) UNANALYSABLE \u2014 THIS REPORT IS PARTIAL. An empty finding list below does NOT mean the grammar is clean.`
|
|
12236
|
+
);
|
|
12237
|
+
}
|
|
12238
|
+
for (const f of d.findings) {
|
|
12239
|
+
lines.push(`${f.severity === "blocking" ? "\u2717" : "\xB7"} [${f.code}] ${f.id}: ${f.message}`);
|
|
12240
|
+
for (const detail of f.details) lines.push(` ${detail.replace(/\n/g, "\n ")}`);
|
|
12241
|
+
if (f.acceptKey !== void 0) {
|
|
12242
|
+
lines.push(` intentional? add to the gating snapshot: { accept: ['${f.acceptKey}'] }`);
|
|
12243
|
+
}
|
|
12244
|
+
}
|
|
12245
|
+
if (!d.ok && d.acceptSnapshot.length > 0) {
|
|
12246
|
+
lines.push(` accept-all snapshot: { accept: [${d.acceptSnapshot.map((i) => `'${i}'`).join(", ")}] }`);
|
|
12247
|
+
}
|
|
12248
|
+
return lines;
|
|
12249
|
+
}
|
|
12250
|
+
function runAnalysis(grammar, opts) {
|
|
12251
|
+
if (isCombinator(grammar)) return analyzeGating(grammar, opts);
|
|
12252
|
+
if (isRuleEntries(grammar)) return analyzeGatingRules(grammar, opts);
|
|
12253
|
+
return analyzeGrammarGating(grammar, opts);
|
|
12254
|
+
}
|
|
12255
|
+
function emptyReport(reason) {
|
|
12256
|
+
return {
|
|
12257
|
+
totalChoices: 0,
|
|
12258
|
+
gated: 0,
|
|
12259
|
+
recoverable: 0,
|
|
12260
|
+
unanalysable: [{ rule: "<whole grammar>", kind: "not-a-combinator", reason }],
|
|
12261
|
+
ungated: [],
|
|
12262
|
+
accepted: [],
|
|
12263
|
+
deferred: [],
|
|
12264
|
+
acceptedUnused: [],
|
|
12265
|
+
choices: [],
|
|
12266
|
+
antiPatterns: []
|
|
12267
|
+
};
|
|
12268
|
+
}
|
|
12269
|
+
var blockingOf = (d) => d.findings.filter((f) => f.severity === "blocking");
|
|
12270
|
+
var SEVERITY_RANK = { blocking: 0, advisory: 1 };
|
|
12271
|
+
var CODE_RANK = {
|
|
12272
|
+
unanalysable: 0,
|
|
12273
|
+
"ungated-choice": 1,
|
|
12274
|
+
"anti-pattern": 2,
|
|
12275
|
+
degraded: 3,
|
|
12276
|
+
"stale-accept": 4
|
|
12277
|
+
};
|
|
12278
|
+
function assemble(report, degradations) {
|
|
12279
|
+
const findings = [];
|
|
12280
|
+
for (const u of report.unanalysable) {
|
|
12281
|
+
findings.push({
|
|
12282
|
+
id: u.rule,
|
|
12283
|
+
code: "unanalysable",
|
|
12284
|
+
severity: "blocking",
|
|
12285
|
+
rule: u.rule,
|
|
12286
|
+
message: `could not be examined [${u.kind}] \u2014 no verdict about it is available`,
|
|
12287
|
+
details: [u.reason]
|
|
12288
|
+
});
|
|
12289
|
+
}
|
|
12290
|
+
for (const c of report.ungated) {
|
|
12291
|
+
findings.push({
|
|
12292
|
+
id: c.id,
|
|
12293
|
+
code: "ungated-choice",
|
|
12294
|
+
severity: "blocking",
|
|
12295
|
+
rule: c.rule,
|
|
12296
|
+
message: `choice is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms`,
|
|
12297
|
+
details: [
|
|
12298
|
+
...c.anyArms.map((a) => `arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
|
|
12299
|
+
fix: ${a.suggestion}`),
|
|
12300
|
+
...c.overlaps.map((o) => `arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
|
|
12301
|
+
fix: ${o.suggestion}`)
|
|
12302
|
+
],
|
|
12303
|
+
acceptKey: c.id
|
|
12304
|
+
});
|
|
12305
|
+
}
|
|
12306
|
+
for (const ap of report.antiPatterns) {
|
|
12307
|
+
findings.push({
|
|
12308
|
+
id: `${ap.rule}#arm${ap.armIndex}`,
|
|
12309
|
+
code: "anti-pattern",
|
|
12310
|
+
severity: "blocking",
|
|
12311
|
+
rule: ap.rule,
|
|
12312
|
+
message: `[${ap.kind}] ${ap.message}`,
|
|
12313
|
+
details: []
|
|
12314
|
+
});
|
|
12315
|
+
}
|
|
12316
|
+
for (const d of degradations) {
|
|
12317
|
+
findings.push({
|
|
12318
|
+
// `where` + `code` is the degradation channel's own identity; reuse it so the two
|
|
12319
|
+
// surfaces name the same thing the same way.
|
|
12320
|
+
id: `${d.code}@${d.where}`,
|
|
12321
|
+
code: "degraded",
|
|
12322
|
+
// Mirrors `DegradationSeverity`: 'warn' is a real cost the author can remove,
|
|
12323
|
+
// 'info' is a fact they cannot act on (someone else's package).
|
|
12324
|
+
severity: d.severity === "warn" ? "blocking" : "advisory",
|
|
12325
|
+
rule: d.where,
|
|
12326
|
+
message: formatDegradation(d),
|
|
12327
|
+
details: []
|
|
12328
|
+
});
|
|
12329
|
+
}
|
|
12330
|
+
for (const id of report.acceptedUnused) {
|
|
12331
|
+
findings.push({
|
|
12332
|
+
id,
|
|
12333
|
+
code: "stale-accept",
|
|
12334
|
+
severity: "advisory",
|
|
12335
|
+
rule: id,
|
|
12336
|
+
message: "accept entry matched no ungated choice \u2014 the grammar was fixed; prune this line",
|
|
12337
|
+
details: []
|
|
12338
|
+
});
|
|
12339
|
+
}
|
|
12340
|
+
findings.sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || CODE_RANK[a.code] - CODE_RANK[b.code] || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
12341
|
+
return {
|
|
12342
|
+
schema: "parseman.diagnosis/1",
|
|
12343
|
+
ok: !findings.some((f) => f.severity === "blocking"),
|
|
12344
|
+
summary: {
|
|
12345
|
+
totalChoices: report.totalChoices,
|
|
12346
|
+
gated: report.gated,
|
|
12347
|
+
recoverable: report.recoverable,
|
|
12348
|
+
ungated: report.ungated.length,
|
|
12349
|
+
accepted: report.accepted.length,
|
|
12350
|
+
deferred: report.deferred.length,
|
|
12351
|
+
antiPatterns: report.antiPatterns.length,
|
|
12352
|
+
unanalysable: report.unanalysable.length,
|
|
12353
|
+
degraded: degradations.length,
|
|
12354
|
+
staleAccepts: report.acceptedUnused.length
|
|
12355
|
+
},
|
|
12356
|
+
findings,
|
|
12357
|
+
acceptSnapshot: report.ungated.map((c) => c.id).sort(),
|
|
12358
|
+
gating: report,
|
|
12359
|
+
degradations
|
|
12360
|
+
};
|
|
12361
|
+
}
|
|
12362
|
+
|
|
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;
|
|
12392
|
+
}
|
|
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);
|
|
12399
|
+
}
|
|
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;
|
|
12405
|
+
}
|
|
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;
|
|
12410
|
+
}
|
|
12411
|
+
return index;
|
|
12412
|
+
}
|
|
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
|
+
);
|
|
12418
|
+
}
|
|
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.");
|
|
12431
|
+
}
|
|
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
|
+
}
|
|
12438
|
+
}
|
|
12439
|
+
if (captureRootTrivia && !rootTriviaClassifiedFromRunnable(entry) && !rootTriviaClassifiedFromRunnable(options.trivia)) {
|
|
12440
|
+
throw new TypeError(
|
|
12441
|
+
"run(): rootTrivia.select requires classifiedTrivia() at the root."
|
|
12442
|
+
);
|
|
12443
|
+
}
|
|
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,
|
|
11749
13308
|
isTrivia: false
|
|
11750
13309
|
};
|
|
11751
13310
|
return {
|
|
@@ -11760,9 +13319,6 @@ function gate(predicate) {
|
|
|
11760
13319
|
};
|
|
11761
13320
|
}
|
|
11762
13321
|
|
|
11763
|
-
// src/combinators/guard.ts
|
|
11764
|
-
var guard = gate;
|
|
11765
|
-
|
|
11766
13322
|
// src/combinators/completions.ts
|
|
11767
13323
|
function completionsAt(target, input, offset, options = {}) {
|
|
11768
13324
|
const probe = { offset, best: null };
|
|
@@ -11989,9 +13545,9 @@ function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build, to
|
|
|
11989
13545
|
const ctx = mkCtx(null, build, tolerant);
|
|
11990
13546
|
const out = [];
|
|
11991
13547
|
let pos = reStart;
|
|
11992
|
-
let
|
|
13548
|
+
let guard = 0;
|
|
11993
13549
|
while (pos < reEnd) {
|
|
11994
|
-
if (++
|
|
13550
|
+
if (++guard > reEnd - reStart + 2) return null;
|
|
11995
13551
|
if (separator && out.length > 0 && out[out.length - 1]._tag === "node") {
|
|
11996
13552
|
if (pos + separator.length > reEnd || !input.startsWith(separator, pos)) return null;
|
|
11997
13553
|
out.push({ _tag: "leaf", value: separator, span: { start: pos - cStart, end: pos + separator.length - cStart } });
|
|
@@ -12283,142 +13839,6 @@ function parseDoc(registry, rootRule, input, opts = {}) {
|
|
|
12283
13839
|
return new ParseDocImpl(registry, rootRule, opts, { abs: null }, [fail], null, input);
|
|
12284
13840
|
}
|
|
12285
13841
|
|
|
12286
|
-
// src/functional/run.ts
|
|
12287
|
-
var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
|
|
12288
|
-
function triviaKindLabelsFromRunnable(r) {
|
|
12289
|
-
if (!r) return void 0;
|
|
12290
|
-
if (typeof r === "function") return r._meta?.triviaKindLabels;
|
|
12291
|
-
const grammarTrivia = r._meta.grammarTrivia;
|
|
12292
|
-
return grammarTrivia?._meta.triviaKindLabels ?? r._meta.triviaKindLabels ?? (r._def.tag === "grammar" ? r._def.triviaParser?._meta.triviaKindLabels : void 0);
|
|
12293
|
-
}
|
|
12294
|
-
function rootTriviaClassifiedFromRunnable(r) {
|
|
12295
|
-
if (!r) return false;
|
|
12296
|
-
if (typeof r === "function") return r._meta?.rootTriviaClassified === true;
|
|
12297
|
-
const grammarTrivia = r._meta.grammarTrivia;
|
|
12298
|
-
return grammarTrivia?._meta.rootTriviaClassified === true || r._meta.rootTriviaClassified === true;
|
|
12299
|
-
}
|
|
12300
|
-
function makeSelectedRootLabelIndex(labels) {
|
|
12301
|
-
const index = /* @__PURE__ */ Object.create(null);
|
|
12302
|
-
for (let i = 0; i < labels.length; i++) {
|
|
12303
|
-
if (index[labels[i]] === void 0) index[labels[i]] = i;
|
|
12304
|
-
}
|
|
12305
|
-
return index;
|
|
12306
|
-
}
|
|
12307
|
-
function runOnce(entry, input, options, phase, profileState) {
|
|
12308
|
-
if (typeof entry !== "function" && typeof entry?.parse !== "function") {
|
|
12309
|
-
throw new TypeError(
|
|
12310
|
-
`run(): start production is ${entry === null ? "null" : typeof entry}, not a rule \u2014 the requested grammar rule does not exist (check the rule name).`
|
|
12311
|
-
);
|
|
12312
|
-
}
|
|
12313
|
-
const rootTriviaSelection = options.rootTrivia?.select;
|
|
12314
|
-
const captureRootTrivia = rootTriviaSelection !== void 0 && rootTriviaSelection.length > 0;
|
|
12315
|
-
const selectedRootLog = captureRootTrivia ? [] : void 0;
|
|
12316
|
-
const selectedRootLabelIndex = captureRootTrivia ? makeSelectedRootLabelIndex(rootTriviaSelection) : void 0;
|
|
12317
|
-
const errors = [];
|
|
12318
|
-
const profile = profileState ?? (phase === void 0 ? void 0 : { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 });
|
|
12319
|
-
const skipGlobalSinks = phase === "recognizer" || phase === "capture";
|
|
12320
|
-
const grammarTrivia = typeof entry !== "function" ? entry._meta.grammarTrivia : void 0;
|
|
12321
|
-
const grammarScanSkip = typeof entry !== "function" ? entry._meta.grammarScanSkip : void 0;
|
|
12322
|
-
const triviaKindLabels = triviaKindLabelsFromRunnable(entry) ?? triviaKindLabelsFromRunnable(options.trivia);
|
|
12323
|
-
if (captureRootTrivia && triviaKindLabels === void 0) {
|
|
12324
|
-
throw new TypeError("run(): rootTrivia.select requires labeled grammar trivia.");
|
|
12325
|
-
}
|
|
12326
|
-
if (captureRootTrivia) {
|
|
12327
|
-
for (const label2 of rootTriviaSelection) {
|
|
12328
|
-
if (!triviaKindLabels.includes(label2)) {
|
|
12329
|
-
throw new TypeError(`run(): rootTrivia.select contains unknown trivia label ${JSON.stringify(label2)}.`);
|
|
12330
|
-
}
|
|
12331
|
-
}
|
|
12332
|
-
}
|
|
12333
|
-
if (captureRootTrivia && !rootTriviaClassifiedFromRunnable(entry) && !rootTriviaClassifiedFromRunnable(options.trivia)) {
|
|
12334
|
-
throw new TypeError(
|
|
12335
|
-
"run(): rootTrivia.select requires classifiedTrivia() at the root."
|
|
12336
|
-
);
|
|
12337
|
-
}
|
|
12338
|
-
const stamped = entry;
|
|
12339
|
-
assertHostModeCompatible(
|
|
12340
|
-
typeof entry === "function" ? stamped[FUSED_HOST_MODE] ?? "ast" : entry._meta.grammarHostMode ?? "ast",
|
|
12341
|
-
options.build,
|
|
12342
|
-
typeof entry === "function" && stamped[FUSED_HOST_ELIDED] === true
|
|
12343
|
-
);
|
|
12344
|
-
const ctx = {
|
|
12345
|
-
trackLines: false,
|
|
12346
|
-
...skipGlobalSinks ? { _pmProfile: profile } : {
|
|
12347
|
-
...captureRootTrivia ? {
|
|
12348
|
-
_rootTriviaLog: selectedRootLog,
|
|
12349
|
-
_rootTriviaKindIndex: selectedRootLabelIndex,
|
|
12350
|
-
_rootTriviaStrictScopes: true
|
|
12351
|
-
} : {},
|
|
12352
|
-
_errors: errors,
|
|
12353
|
-
...profile === void 0 ? {} : { _pmProfile: profile }
|
|
12354
|
-
},
|
|
12355
|
-
build: options.build,
|
|
12356
|
-
state: options.state,
|
|
12357
|
-
...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...triviaKindLabels ? { triviaKindLabels } : {} } : triviaKindLabels ? { triviaKindLabels } : {},
|
|
12358
|
-
...grammarScanSkip !== void 0 ? { scanSkip: grammarScanSkip } : {},
|
|
12359
|
-
...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {},
|
|
12360
|
-
...options.tolerant ? { _tolerant: true, _rec: REC } : {},
|
|
12361
|
-
...options.instrumentation === void 0 ? {} : options.instrumentation
|
|
12362
|
-
};
|
|
12363
|
-
const r = invoke(entry, input, 0, ctx);
|
|
12364
|
-
let unconsumedFrom = null;
|
|
12365
|
-
if (r.ok) {
|
|
12366
|
-
let pos = r.span?.end ?? 0;
|
|
12367
|
-
if (options.trivia && pos < input.length) {
|
|
12368
|
-
const t = invoke(options.trivia, input, pos, { trackLines: false });
|
|
12369
|
-
if (t.ok && t.span.end > pos) pos = t.span.end;
|
|
12370
|
-
}
|
|
12371
|
-
unconsumedFrom = pos < input.length ? pos : null;
|
|
12372
|
-
}
|
|
12373
|
-
return {
|
|
12374
|
-
ok: r.ok,
|
|
12375
|
-
value: r.ok ? r.value : void 0,
|
|
12376
|
-
span: r.span ?? { start: 0, end: 0 },
|
|
12377
|
-
expected: r.ok ? [] : r.expected ?? [],
|
|
12378
|
-
errors,
|
|
12379
|
-
...selectedRootLog !== void 0 && selectedRootLog.length > 0 ? {
|
|
12380
|
-
rootTrivia: {
|
|
12381
|
-
rows: selectedRootLog,
|
|
12382
|
-
select: rootTriviaSelection,
|
|
12383
|
-
index: buildRootTriviaIndex(selectedRootLog, rootTriviaSelection)
|
|
12384
|
-
}
|
|
12385
|
-
} : {},
|
|
12386
|
-
unconsumedFrom
|
|
12387
|
-
};
|
|
12388
|
-
}
|
|
12389
|
-
function clonePassState(state) {
|
|
12390
|
-
if (state === null || typeof state !== "object") return state;
|
|
12391
|
-
return Array.isArray(state) ? [...state] : { ...state };
|
|
12392
|
-
}
|
|
12393
|
-
function profilePass(entry, input, options, phase) {
|
|
12394
|
-
const state = { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 };
|
|
12395
|
-
const passOptions = options.state === void 0 ? options : { ...options, state: clonePassState(options.state) };
|
|
12396
|
-
const start = performance.now();
|
|
12397
|
-
const result = runOnce(entry, input, passOptions, phase, state);
|
|
12398
|
-
const { phase: _phase, ...counts } = state;
|
|
12399
|
-
return { result, profile: { ms: performance.now() - start, ...counts } };
|
|
12400
|
-
}
|
|
12401
|
-
function run(entry, input, options = {}) {
|
|
12402
|
-
if (!options.profile) return runOnce(entry, input, options);
|
|
12403
|
-
if (typeof entry !== "function") {
|
|
12404
|
-
throw new TypeError("run({ profile: true }) requires a compiled parser entry");
|
|
12405
|
-
}
|
|
12406
|
-
const recognizer = profilePass(entry, input, options, "recognizer");
|
|
12407
|
-
const capture = profilePass(entry, input, options, "capture");
|
|
12408
|
-
const host = profilePass(entry, input, options, "host");
|
|
12409
|
-
if (recognizer.result.ok !== host.result.ok || capture.result.ok !== host.result.ok || recognizer.result.unconsumedFrom !== host.result.unconsumedFrom || capture.result.unconsumedFrom !== host.result.unconsumedFrom) {
|
|
12410
|
-
throw new Error("run({ profile: true }) changed recognition; the grammar is not profile-safe");
|
|
12411
|
-
}
|
|
12412
|
-
return {
|
|
12413
|
-
...host.result,
|
|
12414
|
-
profile: {
|
|
12415
|
-
recognizer: recognizer.profile,
|
|
12416
|
-
structuralCapture: capture.profile,
|
|
12417
|
-
hostConstruction: host.profile
|
|
12418
|
-
}
|
|
12419
|
-
};
|
|
12420
|
-
}
|
|
12421
|
-
|
|
12422
13842
|
// src/coverage.ts
|
|
12423
13843
|
function dispatchMatcher(entry) {
|
|
12424
13844
|
switch (entry.kind) {
|
|
@@ -12433,7 +13853,7 @@ function dispatchMatcher(entry) {
|
|
|
12433
13853
|
var GRAMMAR_COVERAGE_DEFINITIONS = /* @__PURE__ */ Symbol.for("parseman.grammarCoverageDefinitions");
|
|
12434
13854
|
function compiledGrammarCoverageDefinitions(grammar) {
|
|
12435
13855
|
const definitions = grammar[GRAMMAR_COVERAGE_DEFINITIONS];
|
|
12436
|
-
if (!Array.isArray(definitions) || !definitions.every((definition) => typeof definition === "object" && definition !== null && typeof definition.id === "string" && (definition.kind === "rule" || definition.kind === "choice-arm" || definition.kind === "dispatch-arm" || definition.kind === "label"))) {
|
|
13856
|
+
if (!Array.isArray(definitions) || definitions.length === 0 || !definitions.every((definition) => typeof definition === "object" && definition !== null && typeof definition.id === "string" && (definition.kind === "rule" || definition.kind === "choice-arm" || definition.kind === "dispatch-arm" || definition.kind === "label"))) {
|
|
12437
13857
|
throw new TypeError("grammar has no coverage definitions; enable grammarCoverage for this macro build");
|
|
12438
13858
|
}
|
|
12439
13859
|
return definitions;
|
|
@@ -12487,7 +13907,13 @@ function createGrammarCoverageCollector(definitions) {
|
|
|
12487
13907
|
snapshot() {
|
|
12488
13908
|
const hitList = [...hits].sort();
|
|
12489
13909
|
const unhit = ordered.map((definition) => definition.id).filter((id) => !hits.has(id));
|
|
12490
|
-
return {
|
|
13910
|
+
return {
|
|
13911
|
+
definitions: ordered,
|
|
13912
|
+
hits: hitList,
|
|
13913
|
+
unhit,
|
|
13914
|
+
ratio: hitList.length / ordered.length,
|
|
13915
|
+
measurable: ordered.length > 0
|
|
13916
|
+
};
|
|
12491
13917
|
},
|
|
12492
13918
|
reset() {
|
|
12493
13919
|
hits.clear();
|
|
@@ -12807,13 +14233,13 @@ function buildTriviaIndex(root, input = "", opts) {
|
|
|
12807
14233
|
}
|
|
12808
14234
|
|
|
12809
14235
|
// src/cst/walk.ts
|
|
12810
|
-
function
|
|
14236
|
+
function isCombinator2(value) {
|
|
12811
14237
|
return !!value && typeof value === "object" && "_def" in value && "_meta" in value && typeof value.parse === "function";
|
|
12812
14238
|
}
|
|
12813
14239
|
function reflectionFor(grammar) {
|
|
12814
14240
|
const stamped = grammarReflectionOf(grammar);
|
|
12815
14241
|
if (stamped) return stamped;
|
|
12816
|
-
const entries = Object.entries(grammar).filter((entry) =>
|
|
14242
|
+
const entries = Object.entries(grammar).filter((entry) => isCombinator2(entry[1]));
|
|
12817
14243
|
return collectGrammarReflection(entries);
|
|
12818
14244
|
}
|
|
12819
14245
|
function createVisitor(grammar, spec) {
|
|
@@ -12851,196 +14277,6 @@ function createVisitor(grammar, spec) {
|
|
|
12851
14277
|
};
|
|
12852
14278
|
}
|
|
12853
14279
|
|
|
12854
|
-
// src/cst/offset-model.ts
|
|
12855
|
-
var { isFinite } = Number;
|
|
12856
|
-
function floorIndex(arr, len, x) {
|
|
12857
|
-
let lo = 0;
|
|
12858
|
-
let hi = len - 1;
|
|
12859
|
-
let ans = -1;
|
|
12860
|
-
while (lo <= hi) {
|
|
12861
|
-
const mid = lo + hi >>> 1;
|
|
12862
|
-
if (arr[mid] <= x) {
|
|
12863
|
-
ans = mid;
|
|
12864
|
-
lo = mid + 1;
|
|
12865
|
-
} else {
|
|
12866
|
-
hi = mid - 1;
|
|
12867
|
-
}
|
|
12868
|
-
}
|
|
12869
|
-
return ans;
|
|
12870
|
-
}
|
|
12871
|
-
var OffsetIndex = class {
|
|
12872
|
-
/** Slot starts, sorted ascending. Parallel to `_ends`. */
|
|
12873
|
-
_starts;
|
|
12874
|
-
_ends;
|
|
12875
|
-
count;
|
|
12876
|
-
docStart;
|
|
12877
|
-
docEnd;
|
|
12878
|
-
constructor(slots, docStart = 0, docEnd) {
|
|
12879
|
-
const sorted = [...slots].sort((a, b) => a.start - b.start);
|
|
12880
|
-
const starts = [];
|
|
12881
|
-
const ends = [];
|
|
12882
|
-
let prevEnd = docStart;
|
|
12883
|
-
for (const s of sorted) {
|
|
12884
|
-
if (!isFinite(s.start) || !isFinite(s.end) || s.end < s.start) {
|
|
12885
|
-
throw new RangeError(`OffsetIndex: invalid slot [${s.start}, ${s.end}]`);
|
|
12886
|
-
}
|
|
12887
|
-
if (s.start < prevEnd) {
|
|
12888
|
-
throw new RangeError(`OffsetIndex: overlapping/out-of-order slot [${s.start}, ${s.end}] after end ${prevEnd}`);
|
|
12889
|
-
}
|
|
12890
|
-
starts.push(s.start);
|
|
12891
|
-
ends.push(s.end);
|
|
12892
|
-
prevEnd = s.end;
|
|
12893
|
-
}
|
|
12894
|
-
this._starts = starts;
|
|
12895
|
-
this._ends = ends;
|
|
12896
|
-
this.count = starts.length;
|
|
12897
|
-
this.docStart = docStart;
|
|
12898
|
-
this.docEnd = docEnd ?? (this.count ? Math.max(prevEnd, ends[ends.length - 1]) : docStart);
|
|
12899
|
-
}
|
|
12900
|
-
/** The slot at index `i` (0-based, source order), or undefined. */
|
|
12901
|
-
slot(i) {
|
|
12902
|
-
if (i < 0 || i >= this.count) return void 0;
|
|
12903
|
-
return { start: this._starts[i], end: this._ends[i] };
|
|
12904
|
-
}
|
|
12905
|
-
/**
|
|
12906
|
-
* Index of the gap that CONTAINS `offset`, in `[0, count]`. Gap `i` spans
|
|
12907
|
-
* `[endOf(i-1), startOf(i))` where `endOf(-1)=docStart` and `startOf(count)=docEnd`.
|
|
12908
|
-
* An offset that lands inside a slot returns that slot's TRAILING gap index
|
|
12909
|
-
* (i.e. the gap after it) — callers querying at slot boundaries get the
|
|
12910
|
-
* intuitive answer via `gapBefore`/`gapAfter`.
|
|
12911
|
-
*/
|
|
12912
|
-
gapIndexAt(offset) {
|
|
12913
|
-
const i = floorIndex(this._starts, this.count, offset);
|
|
12914
|
-
if (i < 0) return 0;
|
|
12915
|
-
if (offset < this._ends[i]) return i + 1;
|
|
12916
|
-
return i + 1;
|
|
12917
|
-
}
|
|
12918
|
-
/** The gap at index `i` in `[0, count]`. */
|
|
12919
|
-
gap(i) {
|
|
12920
|
-
if (i < 0 || i > this.count) return void 0;
|
|
12921
|
-
const start = i === 0 ? this.docStart : this._ends[i - 1];
|
|
12922
|
-
const end = i === this.count ? this.docEnd : this._starts[i];
|
|
12923
|
-
return { start, end };
|
|
12924
|
-
}
|
|
12925
|
-
/**
|
|
12926
|
-
* The trivia gap immediately BEFORE `offset` (`[prevSlotEnd, offset)`), where
|
|
12927
|
-
* `prevSlotEnd` is the end of the last slot ending at or before `offset` (or
|
|
12928
|
-
* `docStart`). For a real slot start this is the gap preceding that slot; for an
|
|
12929
|
-
* adjacent slot the gap is empty and agrees with `gapAfter` on the same seam.
|
|
12930
|
-
*/
|
|
12931
|
-
gapBefore(offset) {
|
|
12932
|
-
const e = floorIndex(this._ends, this.count, offset);
|
|
12933
|
-
const start = e >= 0 ? this._ends[e] : this.docStart;
|
|
12934
|
-
return { start, end: offset };
|
|
12935
|
-
}
|
|
12936
|
-
/**
|
|
12937
|
-
* The trivia gap immediately AFTER `offset` (`[offset, nextSlotStart)`), where
|
|
12938
|
-
* `nextSlotStart` is the least slot start at or after `offset` (or `docEnd`).
|
|
12939
|
-
*/
|
|
12940
|
-
gapAfter(offset) {
|
|
12941
|
-
return { start: offset, end: this._ceilStart(offset) };
|
|
12942
|
-
}
|
|
12943
|
-
/** Least slot start `>= offset`, else `docEnd`. */
|
|
12944
|
-
_ceilStart(offset) {
|
|
12945
|
-
let lo = 0;
|
|
12946
|
-
let hi = this.count - 1;
|
|
12947
|
-
let ans = this.docEnd;
|
|
12948
|
-
while (lo <= hi) {
|
|
12949
|
-
const mid = lo + hi >>> 1;
|
|
12950
|
-
if (this._starts[mid] >= offset) {
|
|
12951
|
-
ans = this._starts[mid];
|
|
12952
|
-
hi = mid - 1;
|
|
12953
|
-
} else {
|
|
12954
|
-
lo = mid + 1;
|
|
12955
|
-
}
|
|
12956
|
-
}
|
|
12957
|
-
return ans;
|
|
12958
|
-
}
|
|
12959
|
-
/** Is this gap empty (adjacent slots, no trivia)? */
|
|
12960
|
-
isEmpty(gap) {
|
|
12961
|
-
return gap.end <= gap.start;
|
|
12962
|
-
}
|
|
12963
|
-
};
|
|
12964
|
-
var NL = 10;
|
|
12965
|
-
var CR = 13;
|
|
12966
|
-
var SP = 32;
|
|
12967
|
-
var TAB = 9;
|
|
12968
|
-
function gapText(source, gap) {
|
|
12969
|
-
return gap.end > gap.start ? source.slice(gap.start, gap.end) : "";
|
|
12970
|
-
}
|
|
12971
|
-
function lineBreaksIn(source, gap) {
|
|
12972
|
-
let n = 0;
|
|
12973
|
-
for (let i = gap.start; i < gap.end; i++) {
|
|
12974
|
-
const c = source.charCodeAt(i);
|
|
12975
|
-
if (c === NL) n++;
|
|
12976
|
-
else if (c === CR && source.charCodeAt(i + 1) !== NL) n++;
|
|
12977
|
-
}
|
|
12978
|
-
return n;
|
|
12979
|
-
}
|
|
12980
|
-
function blankLinesIn(source, gap) {
|
|
12981
|
-
return Math.max(0, lineBreaksIn(source, gap) - 1);
|
|
12982
|
-
}
|
|
12983
|
-
function lineStartWithin(source, gap) {
|
|
12984
|
-
for (let i = gap.end - 1; i >= gap.start; i--) {
|
|
12985
|
-
const c = source.charCodeAt(i);
|
|
12986
|
-
if (c === NL || c === CR) return i + 1;
|
|
12987
|
-
}
|
|
12988
|
-
return gap.start;
|
|
12989
|
-
}
|
|
12990
|
-
function indentWidth(source, gap) {
|
|
12991
|
-
const ls = lineStartWithin(source, gap);
|
|
12992
|
-
if (ls === gap.start) return 0;
|
|
12993
|
-
return gap.end - ls;
|
|
12994
|
-
}
|
|
12995
|
-
function indentMixed(source, gap) {
|
|
12996
|
-
const ls = lineStartWithin(source, gap);
|
|
12997
|
-
let sawSpace = false;
|
|
12998
|
-
let sawTab = false;
|
|
12999
|
-
for (let i = ls; i < gap.end; i++) {
|
|
13000
|
-
const c = source.charCodeAt(i);
|
|
13001
|
-
if (c === SP) sawSpace = true;
|
|
13002
|
-
else if (c === TAB) sawTab = true;
|
|
13003
|
-
}
|
|
13004
|
-
return sawSpace && sawTab;
|
|
13005
|
-
}
|
|
13006
|
-
function commentsIn(source, gap, comment) {
|
|
13007
|
-
const re = new RegExp(comment.source, comment.flags.replace(/[gy]/g, "") + "g");
|
|
13008
|
-
re.lastIndex = gap.start;
|
|
13009
|
-
const out = [];
|
|
13010
|
-
let m;
|
|
13011
|
-
while ((m = re.exec(source)) && m.index < gap.end) {
|
|
13012
|
-
if (m[0].length === 0) {
|
|
13013
|
-
re.lastIndex++;
|
|
13014
|
-
continue;
|
|
13015
|
-
}
|
|
13016
|
-
if (m.index + m[0].length > gap.end) break;
|
|
13017
|
-
out.push({ start: m.index, end: m.index + m[0].length });
|
|
13018
|
-
}
|
|
13019
|
-
return out;
|
|
13020
|
-
}
|
|
13021
|
-
function gapIsSignificant(source, gap, comment) {
|
|
13022
|
-
if (gap.end <= gap.start) return false;
|
|
13023
|
-
if (lineBreaksIn(source, gap) > 0) return true;
|
|
13024
|
-
if (comment && commentsIn(source, gap, comment).length > 0) return true;
|
|
13025
|
-
return false;
|
|
13026
|
-
}
|
|
13027
|
-
function collectLeafSlots(root) {
|
|
13028
|
-
const slots = [];
|
|
13029
|
-
const visit = (n) => {
|
|
13030
|
-
const kids = n.children;
|
|
13031
|
-
if (kids && kids.length) {
|
|
13032
|
-
for (const c of kids) visit(c);
|
|
13033
|
-
} else if (n.span) {
|
|
13034
|
-
slots.push({ start: n.span.start, end: n.span.end });
|
|
13035
|
-
}
|
|
13036
|
-
};
|
|
13037
|
-
if (root) visit(root);
|
|
13038
|
-
return slots;
|
|
13039
|
-
}
|
|
13040
|
-
function buildOffsetIndex(root, input = "") {
|
|
13041
|
-
return new OffsetIndex(collectLeafSlots(root), 0, input.length);
|
|
13042
|
-
}
|
|
13043
|
-
|
|
13044
14280
|
// src/index.ts
|
|
13045
14281
|
registerRegexAnalyzer(firstSetFromRegex);
|
|
13046
14282
|
//# sourceMappingURL=index.cjs.map
|