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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CHOICE COST — the policy layer.
|
|
3
|
+
* ===============================
|
|
4
|
+
*
|
|
5
|
+
* Analysis says what the grammar costs. This says which of those numbers fail a build.
|
|
6
|
+
* They are separate because they have different consumers and different lifetimes: a
|
|
7
|
+
* developer wants the ranked list on every run, CI wants one bit, and the threshold
|
|
8
|
+
* that produces that bit is a decision the owner makes once and writes down.
|
|
9
|
+
*
|
|
10
|
+
* This module is PURE — it takes a report and a baseline and returns a verdict. It
|
|
11
|
+
* reads no files, spawns nothing, and prints nothing, so it is testable without a
|
|
12
|
+
* fixture checkout and cannot be the reason a gate is flaky.
|
|
13
|
+
*
|
|
14
|
+
* THE BASELINE IS ABSOLUTE, NOT DIFFERENTIAL
|
|
15
|
+
* ------------------------------------------
|
|
16
|
+
* Gating each commit against its parent lets +2% per commit land forever: every
|
|
17
|
+
* individual step is under tolerance and the total is unbounded. So the committed
|
|
18
|
+
* baseline records the ACTUAL byte counts, and drift is measured against those
|
|
19
|
+
* numbers, not against yesterday's.
|
|
20
|
+
*
|
|
21
|
+
* A baseline is committed DATA, reviewed like any other diff. Nothing here writes one.
|
|
22
|
+
* Rebaselining is a deliberate act with a reviewable artifact and owner sign-off; there
|
|
23
|
+
* is no automatic refresh, because a gate that rebaselines itself is a gate that
|
|
24
|
+
* records regressions rather than catching them.
|
|
25
|
+
*
|
|
26
|
+
* AND IT IS TWO-SIDED — A WIN MUST BE BANKED
|
|
27
|
+
* ------------------------------------------
|
|
28
|
+
* Growth past the committed number fails, and so does an unbanked IMPROVEMENT. This is
|
|
29
|
+
* the half usually left to a comment asking a human politely, and it is the half that
|
|
30
|
+
* rots: `bench/grammar-density/config.json` and `bench/workloads/config.json` each
|
|
31
|
+
* carried exactly such a comment and sat unbumped for TEN releases. If a reorder takes
|
|
32
|
+
* a site from 7,846 wasted bytes to 2,000 and the baseline does not move with it, 5,846
|
|
33
|
+
* bytes of fresh headroom silently become budget for the next regression — and the gate
|
|
34
|
+
* that was supposed to catch that regression reads green through the whole of it.
|
|
35
|
+
*
|
|
36
|
+
* So the committed number is a BAND, not a floor: leaving it in either direction fails,
|
|
37
|
+
* and only the remedy differs. Raising a number needs owner sign-off; lowering one is
|
|
38
|
+
* mandatory. Both are the same one-line rebaseline and the same reviewable diff.
|
|
39
|
+
*
|
|
40
|
+
* `bench/size-guard.ts` is the shape this follows — it earned the tightness in 0.45 by
|
|
41
|
+
* catching +0.14% moves a 1% tolerance would have waved through.
|
|
42
|
+
*
|
|
43
|
+
* A BASELINE CANNOT LAUNDER A CEILING
|
|
44
|
+
* -----------------------------------
|
|
45
|
+
* If a ceiling is configured, a baseline that records a number ABOVE it is rejected as
|
|
46
|
+
* invalid rather than honoured. Otherwise the ceiling would be waivable by rebaselining,
|
|
47
|
+
* which makes it a suggestion. (`bench/size-guard.ts` validates its own baseline for
|
|
48
|
+
* exactly this reason; this follows it.)
|
|
49
|
+
*
|
|
50
|
+
* FAILS CLOSED
|
|
51
|
+
* ------------
|
|
52
|
+
* Every way of not having measured is a failure, never a pass: a missing baseline, a
|
|
53
|
+
* baseline of the wrong shape, an empty baseline, a report over zero corpus files, a
|
|
54
|
+
* report where no site was instrumentable, a report whose grammar walk was incomplete
|
|
55
|
+
* (`unresolvedRoots` non-empty), a baselined key that no longer exists, and a measured
|
|
56
|
+
* key with no baseline entry. This repo already contains the alternative — `ratio:
|
|
57
|
+
* ordered.length === 0 ? 1` in src/coverage.ts reports 100% covered when nothing was
|
|
58
|
+
* analysable — and the whole point of a gate is to not be that.
|
|
59
|
+
*/
|
|
60
|
+
import type { WastedWorkReport } from './choice-cost.ts';
|
|
61
|
+
export type WastedWorkBaseline = {
|
|
62
|
+
readonly schema: 'parseman.wasted-work-baseline/1';
|
|
63
|
+
/** Informational: which revision produced the numbers. Never compared. */
|
|
64
|
+
gitRev: string;
|
|
65
|
+
/** Informational, `YYYY-MM-DD`. Never compared — comparing it would make the
|
|
66
|
+
* verdict depend on the clock. */
|
|
67
|
+
updatedAt: string;
|
|
68
|
+
/**
|
|
69
|
+
* Absolute per-corpus totals, keyed by corpus id.
|
|
70
|
+
*
|
|
71
|
+
* `totalWastedBytes` is the INTERPRETED column, and that is deliberate even though
|
|
72
|
+
* `WastedWorkReport` names `totalGatedWastedBytes` as the headline. For BYTES the two
|
|
73
|
+
* are the same number, structurally: the modelled first-char guard is derived from the
|
|
74
|
+
* arm's first SET, which over-approximates what the arm can start with, so wherever the
|
|
75
|
+
* guard rejects, the arm's own leading terminal would have rejected at the same position
|
|
76
|
+
* having consumed nothing. Measured over four dialect grammars, zero arms differ (see the
|
|
77
|
+
* header of `choice-cost.ts`). What the guard removes is ATTEMPTS, not rescanned bytes —
|
|
78
|
+
* so `gatedAttempts`, `gatedFailures` and the inversion ranking DO read the gated columns,
|
|
79
|
+
* and only the byte totals are recorded from the interpreted one, where it is additionally
|
|
80
|
+
* the conservative UPPER bound of the two.
|
|
81
|
+
*
|
|
82
|
+
* That identity is not enforced anywhere it could be relied on silently: `checkWastedWork`
|
|
83
|
+
* judges the compiled column against this same band wherever the two columns part, so a
|
|
84
|
+
* divergence goes red instead of unnoticed.
|
|
85
|
+
*/
|
|
86
|
+
totals: Record<string, {
|
|
87
|
+
corpusBytes: number;
|
|
88
|
+
totalWastedBytes: number;
|
|
89
|
+
instrumentedSites: number;
|
|
90
|
+
}>;
|
|
91
|
+
/** Absolute per-site wasted bytes, keyed by `<corpus>::<siteKey>`. Interpreted column,
|
|
92
|
+
* for the reason given on `totals` above. */
|
|
93
|
+
sites: Record<string, number>;
|
|
94
|
+
};
|
|
95
|
+
export type WastedWorkPolicy = {
|
|
96
|
+
/**
|
|
97
|
+
* Maximum wasted bytes per corpus byte. A hard ceiling no rebaseline can waive.
|
|
98
|
+
* Omit for drift-only gating; a project should set it once it knows its own number.
|
|
99
|
+
*/
|
|
100
|
+
ceilingRatio?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Half-width of the band around each committed number, as a percentage. Default 1.
|
|
103
|
+
*
|
|
104
|
+
* SYMMETRIC: a measurement more than this ABOVE its baseline is a regression, and a
|
|
105
|
+
* measurement more than this BELOW it is an unbanked win. Both fail.
|
|
106
|
+
*
|
|
107
|
+
* This is not a noise allowance — the metric is a deterministic count of input bytes,
|
|
108
|
+
* so its noise floor is exactly zero and any non-zero value here is pure headroom.
|
|
109
|
+
* It exists only so incidental one-byte churn does not thrash the baseline in both
|
|
110
|
+
* directions. Set it as near zero as the corpus permits; `bench/choice-cost-guard.ts`
|
|
111
|
+
* runs at 0.1, matching `bench/size-guard.ts`'s ratchet slack.
|
|
112
|
+
*/
|
|
113
|
+
driftTolerancePct?: number;
|
|
114
|
+
/**
|
|
115
|
+
* Fail on an ordering inversion — an arm that failed every one of its attempts while
|
|
116
|
+
* a later arm at the same site matched. Off by default: on an existing grammar this
|
|
117
|
+
* fires immediately, and a gate that is red on arrival for a pre-existing condition
|
|
118
|
+
* gets disabled rather than fixed. Turn it on once the backlog is empty, to keep it
|
|
119
|
+
* empty.
|
|
120
|
+
*/
|
|
121
|
+
failOnInversions?: boolean;
|
|
122
|
+
};
|
|
123
|
+
export type GateBreach = {
|
|
124
|
+
/** `drift` is growth past the band; `shrank` is an improvement that was not banked.
|
|
125
|
+
* Both are failures, and the `detail` says which remedy applies. */
|
|
126
|
+
kind: 'ceiling' | 'drift' | 'shrank' | 'unbaselined' | 'stale' | 'inversion' | 'unmeasurable' | 'invalid-baseline';
|
|
127
|
+
key: string;
|
|
128
|
+
/** One line. Says what happened and what number to look at — never advice. */
|
|
129
|
+
detail: string;
|
|
130
|
+
};
|
|
131
|
+
export type GateVerdict = {
|
|
132
|
+
readonly schema: 'parseman.wasted-work-verdict/1';
|
|
133
|
+
ok: boolean;
|
|
134
|
+
/** Ascending by (kind, key). Deterministic, so a verdict is diffable. */
|
|
135
|
+
breaches: readonly GateBreach[];
|
|
136
|
+
checkedCorpora: number;
|
|
137
|
+
checkedSites: number;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Judge one or more measured corpora against a committed baseline.
|
|
141
|
+
*
|
|
142
|
+
* `reports` is keyed by corpus id — a grammar is usually gated over more than one body
|
|
143
|
+
* of input, and a single blended number would let a regression on one hide behind an
|
|
144
|
+
* improvement on another.
|
|
145
|
+
*/
|
|
146
|
+
export declare function checkWastedWork(reports: Readonly<Record<string, WastedWorkReport>>, baseline: unknown, policy?: WastedWorkPolicy): GateVerdict;
|
|
147
|
+
/**
|
|
148
|
+
* Build the baseline a passing measurement would record.
|
|
149
|
+
*
|
|
150
|
+
* Deliberately NOT called by the gate. It exists so a rebaseline is one explicit
|
|
151
|
+
* command whose output is a file in the diff — the reviewable record — rather than
|
|
152
|
+
* something the gate can do to itself on a red run.
|
|
153
|
+
*/
|
|
154
|
+
export declare function buildWastedWorkBaseline(reports: Readonly<Record<string, WastedWorkReport>>, meta: {
|
|
155
|
+
gitRev: string;
|
|
156
|
+
updatedAt: string;
|
|
157
|
+
}): WastedWorkBaseline;
|
|
158
|
+
//# sourceMappingURL=choice-cost-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"choice-cost-gate.d.ts","sourceRoot":"","sources":["../../src/analysis/choice-cost-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,MAAM,EAAE,iCAAiC,CAAA;IAClD,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAA;IACd;uCACmC;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACpG;kDAC8C;IAC9C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB;yEACqE;IACrE,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,aAAa,GAAG,OAAO,GAAG,WAAW,GAAG,cAAc,GAAG,kBAAkB,CAAA;IAClH,GAAG,EAAE,MAAM,CAAA;IACX,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,MAAM,EAAE,gCAAgC,CAAA;IACjD,EAAE,EAAE,OAAO,CAAA;IACX,yEAAyE;IACzE,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAA;IAC/B,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAQD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,EACnD,QAAQ,EAAE,OAAO,EACjB,MAAM,GAAE,gBAAqB,GAC5B,WAAW,CAkLb;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,EACnD,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC1C,kBAAkB,CAWpB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CHOICE COST — the human layer.
|
|
3
|
+
* ==============================
|
|
4
|
+
*
|
|
5
|
+
* Three layers, not two:
|
|
6
|
+
*
|
|
7
|
+
* ANALYSIS src/analysis/choice-cost.ts — what the sites are and what they cost.
|
|
8
|
+
* POLICY bench/choice-cost-guard.ts — which of those numbers fail a build.
|
|
9
|
+
* RENDERING this file — how a person reads either of them.
|
|
10
|
+
*
|
|
11
|
+
* The structured report is the source of truth. Nothing here computes a finding, and
|
|
12
|
+
* nothing here decides pass or fail; a gate consumes the data and never this text. If
|
|
13
|
+
* a number appears below that is not in the report, that is a bug in this file.
|
|
14
|
+
*
|
|
15
|
+
* WHAT A PARSER GENERATOR CAN SHOW THAT A COMPILER CANNOT
|
|
16
|
+
* -------------------------------------------------------
|
|
17
|
+
* A type error has one source to point at. A wasted-work finding has TWO, and relating
|
|
18
|
+
* them is the whole diagnostic:
|
|
19
|
+
*
|
|
20
|
+
* - the GRAMMAR site — the ordered choice whose arm ordering costs the time
|
|
21
|
+
* - the COST — measured, over a real corpus, per alternative
|
|
22
|
+
*
|
|
23
|
+
* So the ordering is printed WITH its measured cost beside each arm, which makes the
|
|
24
|
+
* fix self-evident without a paragraph explaining it:
|
|
25
|
+
*
|
|
26
|
+
* StylesheetAtRule › dispatch[0] 1.31 MB rescanned
|
|
27
|
+
* 0 RoutedAtRuleStatement failed 4,182 / 4,271 1.31 MB
|
|
28
|
+
* 1 RoutedLayerBlock matched 4,182
|
|
29
|
+
*
|
|
30
|
+
* Reading that, nobody needs to be told what to do.
|
|
31
|
+
*
|
|
32
|
+
* QUIET BY DEFAULT
|
|
33
|
+
* ----------------
|
|
34
|
+
* Nothing in this module prints. It returns strings. There is no console call and no
|
|
35
|
+
* process-wide default-on channel, deliberately: importing `examples/css/parser.ts`
|
|
36
|
+
* already emits roughly sixty lines of gating advice before any user code runs, and a
|
|
37
|
+
* beautiful diagnostic that nobody reads because it is buried in build noise is not
|
|
38
|
+
* beautiful. This one is available on request — from the CLI gate, or by calling it —
|
|
39
|
+
* and silent otherwise.
|
|
40
|
+
*
|
|
41
|
+
* DETERMINISM BINDS THE RENDERING TOO
|
|
42
|
+
* -----------------------------------
|
|
43
|
+
* The rendering is diffable: it preserves the report's ordering, contains no timings,
|
|
44
|
+
* no dates, no absolute paths, and colour is opt-in rather than auto-detected, so the
|
|
45
|
+
* same report always renders to the same bytes. A renderer that sniffed `isTTY` would
|
|
46
|
+
* produce two different outputs from one report and break exactly that.
|
|
47
|
+
*/
|
|
48
|
+
import type { ChoiceInventoryReport, ChoiceInventoryEntry, WastedWorkReport } from './choice-cost.ts';
|
|
49
|
+
export type RenderOptions = {
|
|
50
|
+
/** Rows to show. Default 20. The report always holds all of them. */
|
|
51
|
+
limit?: number;
|
|
52
|
+
/**
|
|
53
|
+
* ANSI colour. Default false — NOT auto-detected from `isTTY`, because a renderer
|
|
54
|
+
* whose bytes depend on where it is piped cannot be diffed or snapshotted.
|
|
55
|
+
*/
|
|
56
|
+
color?: boolean;
|
|
57
|
+
};
|
|
58
|
+
/** Deterministic thousands grouping. `toLocaleString()` is locale-dependent and would
|
|
59
|
+
* make the output differ between machines — the one thing a gateable rendering
|
|
60
|
+
* cannot do. */
|
|
61
|
+
export declare function groupDigits(n: number): string;
|
|
62
|
+
/** Byte counts at a glance. Binary units, fixed to one decimal, so 1,376,256 renders
|
|
63
|
+
* as `1.3 MB` on every machine. */
|
|
64
|
+
export declare function bytes(n: number): string;
|
|
65
|
+
/**
|
|
66
|
+
* The combinator source a left-factoring would produce, ready to paste.
|
|
67
|
+
*
|
|
68
|
+
* Offered ONLY for the shape where it is mechanical and total: every arm of the choice
|
|
69
|
+
* in one group. A partial group needs the author to decide which arms move and in what
|
|
70
|
+
* order — an ordering decision that changes what the grammar accepts, which no
|
|
71
|
+
* generated rewrite may make silently.
|
|
72
|
+
*
|
|
73
|
+
* This is a PREVIEW. It is not applied, and — until the digest-verification loop
|
|
74
|
+
* described in `docs/` is wired — it is not yet proven behaviour-preserving, so it is
|
|
75
|
+
* labelled a candidate rather than offered as a fix. An unverified rewrite presented
|
|
76
|
+
* as verified is worse than no suggestion at all.
|
|
77
|
+
*/
|
|
78
|
+
export declare function leftFactorPreview(e: ChoiceInventoryEntry): string | null;
|
|
79
|
+
export declare function renderChoiceInventory(r: ChoiceInventoryReport, opts?: RenderOptions): string;
|
|
80
|
+
/**
|
|
81
|
+
* The ranked list, with each top site expanded into its arm ordering.
|
|
82
|
+
*
|
|
83
|
+
* The expansion is the point. A ranked list of site keys tells you WHERE; the arm
|
|
84
|
+
* breakdown beside it tells you WHY, and "arm 0 failed 4,182 of 4,271 times" is an
|
|
85
|
+
* argument for moving arm 0 that needs no prose attached.
|
|
86
|
+
*/
|
|
87
|
+
export declare function renderWastedWork(r: WastedWorkReport, opts?: RenderOptions): string;
|
|
88
|
+
//# sourceMappingURL=choice-cost-render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"choice-cost-render.d.ts","sourceRoot":"","sources":["../../src/analysis/choice-cost-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EACV,qBAAqB,EAAE,oBAAoB,EAAE,gBAAgB,EAC9D,MAAM,kBAAkB,CAAA;AAEzB,MAAM,MAAM,aAAa,GAAG;IAC1B,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAID;;iBAEiB;AACjB,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ7C;AAED;oCACoC;AACpC,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAIvC;AA8CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,oBAAoB,GAAG,MAAM,GAAG,IAAI,CAOxE;AAID,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,qBAAqB,EAAE,IAAI,GAAE,aAAkB,GAAG,MAAM,CA0ChG;AAID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,GAAE,aAAkB,GAAG,MAAM,CAmEtF"}
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CHOICE COST — the shape of an ordered choice, and what that shape costs.
|
|
3
|
+
* ========================================================================
|
|
4
|
+
*
|
|
5
|
+
* READ THIS FIRST: THIS MEASURES THE INTERPRETER, NOT THE SHIPPED PARSER.
|
|
6
|
+
* ----------------------------------------------------------------------
|
|
7
|
+
* The interpreter's `firstMatch` loop (src/combinators/choice.ts:149-165) enters every
|
|
8
|
+
* alternative unconditionally. COMPILED output does not: `emitFirstMatch`
|
|
9
|
+
* (src/compiler/codegen.ts:2246-2277) emits a per-arm first-CHARACTER guard, so an arm
|
|
10
|
+
* whose first set excludes the character at the current position is never entered.
|
|
11
|
+
* First-set gating is this project's single largest parse lever — 25-48% across all
|
|
12
|
+
* four jess parsers — and a naive interpreted profile is blind to every byte of it.
|
|
13
|
+
*
|
|
14
|
+
* So `profileWastedWork` reports TWO columns and derives neither from the other:
|
|
15
|
+
* `attempts`/`wastedBytes` (interpreted) and `gatedAttempts`/`gatedWastedBytes`
|
|
16
|
+
* (modelling codegen's guard — see `compiledFirstCharGate`). Rankings use the compiled
|
|
17
|
+
* column. The DELTA is itself a result: an arm expensive interpreted and cheap gated is
|
|
18
|
+
* ALREADY SOLVED by codegen, and reordering it is unpaid work.
|
|
19
|
+
*
|
|
20
|
+
* WHAT THE GATE ACTUALLY REMOVES — measured, not assumed.
|
|
21
|
+
* Over jess's four dialect grammars (637 kB of CSS, 212 kB of Less):
|
|
22
|
+
*
|
|
23
|
+
* dialect arms whose GATED BYTES differ arm entries removed by the gate
|
|
24
|
+
* css 0 of 158 331,313 of 435,767 (76%)
|
|
25
|
+
* less 0 of 199 413,737 of 562,852 (74%)
|
|
26
|
+
* scss 0 of 200 617,735 of 777,356 (79%)
|
|
27
|
+
* jess 0 of 158 325,655 of 388,263 (84%)
|
|
28
|
+
*
|
|
29
|
+
* The gate removes ATTEMPTS, not rescanned BYTES — and the byte columns are identical
|
|
30
|
+
* to the byte in all four. That is structural, not a coincidence of these corpora: the
|
|
31
|
+
* guard is derived from the arm's first SET, which over-approximates what the arm can
|
|
32
|
+
* start with, so whenever the guard rejects, the arm's own leading terminal would have
|
|
33
|
+
* rejected at the same position having consumed nothing.
|
|
34
|
+
*
|
|
35
|
+
* The practical consequence, and the reason this warning is at the top: any claim of
|
|
36
|
+
* the form "this arm is entered on every X and fails N% of the time" is a statement
|
|
37
|
+
* about the INTERPRETER and is usually false of the shipped parser. A byte ranking
|
|
38
|
+
* survives the correction; a failure-RATE or attempt-count claim does not. Measured on
|
|
39
|
+
* jess's CSS `Value` choice, arm 0 `Percentage` reads as 25,939 entries at 97% failure
|
|
40
|
+
* interpreted, and 7,455 at 90% gated — the same arm, a fifth as important. Arm 4
|
|
41
|
+
* `IdentBlock` is 100% failure in BOTH columns and carries 27,131 bytes; it is the real
|
|
42
|
+
* finding, and only the gated column makes that legible.
|
|
43
|
+
*
|
|
44
|
+
* KNOWN LIMIT OF THE MODEL. On the compose/linkable path a named-rule arm gets a
|
|
45
|
+
* DEFERRED guard (an `@FS:name@` placeholder) that fuse time rewrites with the winning rule's
|
|
46
|
+
* first set — or with `true`, always-try, if it cannot be resolved. This model always
|
|
47
|
+
* resolves, so for cross-artifact ref arms `gatedAttempts` is a LOWER bound: the
|
|
48
|
+
* shipped artifact may enter such an arm more often than modelled. Where a compiled
|
|
49
|
+
* measurement disagrees with this report on an arm that is a bare rule reference, the
|
|
50
|
+
* compiled measurement is right.
|
|
51
|
+
*
|
|
52
|
+
* A PEG `choice` is ordered: arm `i` is only reached once arms `0..i-1` have been
|
|
53
|
+
* TRIED AND FAILED, and every one of those failures re-scanned input that the
|
|
54
|
+
* winning arm then scans again. So each additional alternative, and each earlier
|
|
55
|
+
* alternative, has a time cost — and that cost is invisible in the grammar source,
|
|
56
|
+
* which is why grammar shape drifts without anything going red.
|
|
57
|
+
*
|
|
58
|
+
* This module makes it visible, in two halves that answer two different questions.
|
|
59
|
+
*
|
|
60
|
+
* STATIC — `analyzeChoiceInventory()`
|
|
61
|
+
* Which choice sites have alternatives that share a leading term, and for each:
|
|
62
|
+
* did the compiler LEFT-FACTOR it, or did it decline, and why? `detectSharedPrefix`
|
|
63
|
+
* (src/combinators/choice.ts) already computes this to decide whether to emit the
|
|
64
|
+
* `sharedPrefix` strategy, but it is ALL-OR-NOTHING (every arm must share) and it
|
|
65
|
+
* returns `null` on the first arm that does not qualify — silently, with no record
|
|
66
|
+
* of how close the site came or which arm blocked it. The inventory reports the
|
|
67
|
+
* declines, with the blocking arm and the reason. That set is the refactor backlog,
|
|
68
|
+
* GENERATED rather than noticed by a human reading grammar source.
|
|
69
|
+
*
|
|
70
|
+
* It is complete, not sampled: every `choice` reachable from the rule map appears,
|
|
71
|
+
* including the ones with nothing to report (`groups: []`).
|
|
72
|
+
*
|
|
73
|
+
* DYNAMIC — `profileWastedWork()`
|
|
74
|
+
* How many BYTES were re-scanned because an alternative was tried and failed,
|
|
75
|
+
* attributed per choice site and per arm, over a real corpus. Ordering pathologies
|
|
76
|
+
* fall out of the ranking for free: an arm that fails 98% of the time while sitting
|
|
77
|
+
* first is exactly the top entry.
|
|
78
|
+
*
|
|
79
|
+
* The static half can only see shape. It cannot know that one declined site is on
|
|
80
|
+
* the hot path of every stylesheet and another is reached twice a year. The dynamic
|
|
81
|
+
* half supplies that weight. Neither is sufficient alone.
|
|
82
|
+
*
|
|
83
|
+
* WHY BYTES, AND WHY THIS IS GATEABLE
|
|
84
|
+
* -----------------------------------
|
|
85
|
+
* The metric is a COUNT of input bytes, not a timing. It is a pure function of
|
|
86
|
+
* (grammar, corpus): the same grammar over the same corpus yields the same number on
|
|
87
|
+
* an idle machine and on a machine at load 10. There is no noise floor to argue about
|
|
88
|
+
* and no rebaseline needed for a hardware change. That is what makes it usable as a
|
|
89
|
+
* deterministic gate rather than a benchmark.
|
|
90
|
+
*
|
|
91
|
+
* INTERPRETED MODE ONLY
|
|
92
|
+
* ---------------------
|
|
93
|
+
* Nothing here is emitted into a compiled parser and nothing here costs codegen a
|
|
94
|
+
* single byte or a single millisecond. The profiler does not add an instrumentation
|
|
95
|
+
* flag to the hot combinators either — `src/combinators/choice.ts` is UNTOUCHED by
|
|
96
|
+
* this file. Instrumentation is installed by temporarily substituting arm slots and
|
|
97
|
+
* terminal `parse` methods on the combinator tree, and removed in a `finally`. When
|
|
98
|
+
* you are not profiling, the cost is exactly zero because the shipping code contains
|
|
99
|
+
* no profiling branch to skip.
|
|
100
|
+
*
|
|
101
|
+
* (For contrast: `run({ profile })` — the node/slot-count profiler — was compiled-path
|
|
102
|
+
* only, was never implemented in the interpreter, and now throws rather than reporting
|
|
103
|
+
* zeros. See src/functional/run.ts. This is a different measurement and a new build,
|
|
104
|
+
* not a port of that one.)
|
|
105
|
+
*
|
|
106
|
+
* WHAT THIS INSTRUMENT CANNOT SEE
|
|
107
|
+
* -------------------------------
|
|
108
|
+
* Stated up front, because a diagnostic whose blind spots are undocumented gets read
|
|
109
|
+
* as complete:
|
|
110
|
+
*
|
|
111
|
+
* 0. It measures the INTERPRETER. See the top of this header — this is the blind spot
|
|
112
|
+
* that reorders results rather than merely shifting them, which is why it is not
|
|
113
|
+
* in this list but above it. The `gated*` columns model it; they do not remove it.
|
|
114
|
+
* 1. Only `firstMatch` and `sharedPrefix` choices are instrumented. Those are the
|
|
115
|
+
* two strategies that try arms in order and can fail one. A `disjoint` choice
|
|
116
|
+
* dispatches on the first character and tries exactly one arm; `greedyClassify`
|
|
117
|
+
* runs one regex; `literalsLongestFirst` captures its sorted arm array at
|
|
118
|
+
* construction, so slot substitution cannot reach it. Their sites still appear
|
|
119
|
+
* in the static inventory, with zero dynamic cost recorded and `instrumented:
|
|
120
|
+
* false` so the zero is not mistaken for a measurement.
|
|
121
|
+
* 2. Backtracking that is not a choice arm — a failed `many` element, a failed
|
|
122
|
+
* `optional`, a rolled-back `attempt` — is not attributed. Those bytes are real
|
|
123
|
+
* but they are not an ORDERING decision, which is what this instrument exists to
|
|
124
|
+
* rank.
|
|
125
|
+
* 3. An arm that SUCCEEDS and is then rejected by the `autoNot` check is recorded as
|
|
126
|
+
* a success, because the rejection happens inside the choice loop after the arm
|
|
127
|
+
* has returned. Its rescan is invisible here.
|
|
128
|
+
* 4. `wastedBytes` is input bytes re-scanned, not CPU. An arm that fails after one
|
|
129
|
+
* byte but allocated on the way is cheap by this metric and not by the clock.
|
|
130
|
+
* This ranks ORDERING, and is deliberately not a profiler.
|
|
131
|
+
*
|
|
132
|
+
* SET EXPECTATIONS ACCORDINGLY, because this has been measured. A jess lane acted
|
|
133
|
+
* on a finding from this report and cut rescanned bytes at one route from 27,678
|
|
134
|
+
* to 8,367 — a 69.8% reduction, exactly as this instrument predicts — for ZERO
|
|
135
|
+
* measurable wall-clock change. A large byte win that produces no time win is the
|
|
136
|
+
* expected outcome, not a failure of the fix: re-scanning is cheap per byte, and
|
|
137
|
+
* what makes a grammar slow is usually allocation and node construction, which
|
|
138
|
+
* this metric cannot see. Use it to find and rank ORDERING defects and to prove a
|
|
139
|
+
* restructure did what it claimed. Do not use it to predict a speedup, and do not
|
|
140
|
+
* let a flat benchmark be read as evidence the finding was wrong.
|
|
141
|
+
* 5. Reach is measured by the furthest position at which a TERMINAL was attempted
|
|
142
|
+
* (`literal`/`regex`/`keywords`/`scanTo`). A combinator that consumes without
|
|
143
|
+
* going through one of those would under-report; none exists today.
|
|
144
|
+
*/
|
|
145
|
+
import type { Combinator } from '../types.ts';
|
|
146
|
+
import type { ChoiceStrategyTag } from './gating.ts';
|
|
147
|
+
import type { FirstSet } from '../types.ts';
|
|
148
|
+
/** A combinator's location in the grammar: owning rule plus structural path. */
|
|
149
|
+
export type ChoiceSite = {
|
|
150
|
+
/** Nearest enclosing `_ruleName`, or the rule-map key it was reached under. */
|
|
151
|
+
rule: string;
|
|
152
|
+
/** Structural path inside that rule, e.g. `seq[2] › node(AtRule) › choice[0]`. */
|
|
153
|
+
path: string;
|
|
154
|
+
};
|
|
155
|
+
/** The stable identity of a site. Used as the sort key and the baseline key, so it
|
|
156
|
+
* must be derived only from grammar structure — never from a file path or a run. */
|
|
157
|
+
export declare const choiceSiteKey: (s: ChoiceSite) => string;
|
|
158
|
+
/** Why one ARM contributed no shareable leading term. */
|
|
159
|
+
export type ArmDeclineReason =
|
|
160
|
+
/** The arm is not a (wrapper-peeled) `sequence` — e.g. a bare ref, a nested
|
|
161
|
+
* `choice`, an `optional`. `leadingTermOfArm` peels only node/grammar/transform/
|
|
162
|
+
* label, because those are the wrappers that consume nothing before the sequence. */
|
|
163
|
+
'not-a-sequence'
|
|
164
|
+
/** A `sequence` of one term: there is no residual to factor out from. */
|
|
165
|
+
| 'sequence-shorter-than-2'
|
|
166
|
+
/** The leading term is a case-INSENSITIVE literal. Excluded deliberately: the
|
|
167
|
+
* matched text differs from the literal's own value, so a replayed leaf would
|
|
168
|
+
* carry the wrong string. */
|
|
169
|
+
| 'lead-case-insensitive-literal'
|
|
170
|
+
/** The leading term is not a bare literal/regex — a ref, a node, a label, a
|
|
171
|
+
* transform, another choice. Factoring through it would change the value or
|
|
172
|
+
* capture shape of the arm. */
|
|
173
|
+
| 'lead-not-concrete-terminal';
|
|
174
|
+
/** Why the SITE as a whole was not left-factored. */
|
|
175
|
+
export type SiteDeclineReason =
|
|
176
|
+
/** First-char dispatch already selects exactly one arm; there is nothing to factor.
|
|
177
|
+
* Not a backlog entry — this is the good state. */
|
|
178
|
+
'disjoint-dispatch'
|
|
179
|
+
/** At least one arm carries a runtime gate; per-arm predicates are incompatible
|
|
180
|
+
* with the factoring strategies. */
|
|
181
|
+
| 'gated-arms'
|
|
182
|
+
/** `greedyClassify` or `literalsLongestFirst` matched first. Both are stronger than
|
|
183
|
+
* `sharedPrefix` (neither backtracks), so this is also not a backlog entry. */
|
|
184
|
+
| 'strategy-preempted'
|
|
185
|
+
/** Fewer than two arms. */
|
|
186
|
+
| 'fewer-than-two-arms'
|
|
187
|
+
/** At least one arm produced no shareable leading term. See `armDeclines` for which
|
|
188
|
+
* arm and why. THIS is the backlog: a subset of arms may still share a prefix that
|
|
189
|
+
* the all-or-nothing detector never records. */
|
|
190
|
+
| 'arms-not-factorable'
|
|
191
|
+
/** Every arm produced a leading term, but they are not all the same term. Also the
|
|
192
|
+
* backlog: two arms out of nine sharing `@` is real, repeated work. */
|
|
193
|
+
| 'leads-differ';
|
|
194
|
+
/** A maximal set of arms at one site sharing an identical concrete leading term. */
|
|
195
|
+
export type PrefixGroup = {
|
|
196
|
+
/** Structural key of the shared term. Injective by construction (JSON-encoded), so
|
|
197
|
+
* two structurally different regexes can never collide onto one group. */
|
|
198
|
+
key: string;
|
|
199
|
+
/** Human rendering, e.g. `"@"` or `/[-\w]+/`. */
|
|
200
|
+
render: string;
|
|
201
|
+
/** Arm indices, ascending. */
|
|
202
|
+
members: readonly number[];
|
|
203
|
+
};
|
|
204
|
+
export type ChoiceInventoryEntry = {
|
|
205
|
+
site: ChoiceSite;
|
|
206
|
+
siteKey: string;
|
|
207
|
+
arity: number;
|
|
208
|
+
strategy: ChoiceStrategyTag;
|
|
209
|
+
disjoint: boolean;
|
|
210
|
+
gated: boolean;
|
|
211
|
+
/** Every group of >= 2 arms sharing a concrete leading term, ordered by member
|
|
212
|
+
* count descending then key. Empty when no two arms share one. */
|
|
213
|
+
groups: readonly PrefixGroup[];
|
|
214
|
+
/** Per-arm reasons, ascending by arm index. Only arms that produced no key. */
|
|
215
|
+
armDeclines: readonly {
|
|
216
|
+
arm: number;
|
|
217
|
+
reason: ArmDeclineReason;
|
|
218
|
+
detail: string;
|
|
219
|
+
}[];
|
|
220
|
+
factored: boolean;
|
|
221
|
+
/** Present iff `factored` is false. */
|
|
222
|
+
declineReason?: SiteDeclineReason;
|
|
223
|
+
/** Arms that sit in some group but whose site was NOT factored — the count of
|
|
224
|
+
* alternatives currently re-scanning a prefix a sibling already scanned. */
|
|
225
|
+
unfactoredArms: number;
|
|
226
|
+
};
|
|
227
|
+
export type ChoiceInventoryReport = {
|
|
228
|
+
readonly schema: 'parseman.choice-inventory/1';
|
|
229
|
+
rules: number;
|
|
230
|
+
/** Distinct `choice` combinator instances reachable from the rule map. */
|
|
231
|
+
choiceSites: number;
|
|
232
|
+
factoredSites: number;
|
|
233
|
+
/** Sites with >= 1 prefix group that were NOT factored. The backlog. */
|
|
234
|
+
backlogSites: number;
|
|
235
|
+
backlogArms: number;
|
|
236
|
+
/**
|
|
237
|
+
* Rule-map entries that are unresolvable references — a `g.X` hole that only
|
|
238
|
+
* `compose()` binds. Their subtrees were NOT walked, so this report is partial by
|
|
239
|
+
* exactly this much. Ascending. Empty is the only value that means "complete".
|
|
240
|
+
*/
|
|
241
|
+
unresolvedRoots: readonly string[];
|
|
242
|
+
/** Ascending by `siteKey`. Complete — every reachable choice, including the ones
|
|
243
|
+
* with nothing to report. */
|
|
244
|
+
entries: readonly ChoiceInventoryEntry[];
|
|
245
|
+
};
|
|
246
|
+
export type WastedWorkArm = {
|
|
247
|
+
siteKey: string;
|
|
248
|
+
site: ChoiceSite;
|
|
249
|
+
arm: number;
|
|
250
|
+
/** Short rendering of the arm's head, so the ranked list reads without opening
|
|
251
|
+
* the grammar. */
|
|
252
|
+
label: string;
|
|
253
|
+
/** INTERPRETED: every entry, because the interpreter's firstMatch loop gates nothing. */
|
|
254
|
+
attempts: number;
|
|
255
|
+
failures: number;
|
|
256
|
+
/** INTERPRETED: input bytes re-scanned by this arm's FAILED attempts, summed over the
|
|
257
|
+
* corpus. For one attempt: (furthest position a terminal was tried) - (entry position). */
|
|
258
|
+
wastedBytes: number;
|
|
259
|
+
/** True when codegen emits a first-char guard for this arm, i.e. the shipped parser
|
|
260
|
+
* enters it less often than the interpreter does. */
|
|
261
|
+
firstCharGated: boolean;
|
|
262
|
+
/** COMPILED MODEL: entries that survive codegen's first-char guard. This is the
|
|
263
|
+
* number the shipped parser actually pays. Equals `attempts` when `firstCharGated`
|
|
264
|
+
* is false. */
|
|
265
|
+
gatedAttempts: number;
|
|
266
|
+
gatedFailures: number;
|
|
267
|
+
/** COMPILED MODEL: the ranking column. Bytes re-scanned by failed attempts that
|
|
268
|
+
* codegen's guard would NOT have skipped. */
|
|
269
|
+
gatedWastedBytes: number;
|
|
270
|
+
};
|
|
271
|
+
export type WastedWorkSite = {
|
|
272
|
+
siteKey: string;
|
|
273
|
+
site: ChoiceSite;
|
|
274
|
+
strategy: ChoiceStrategyTag;
|
|
275
|
+
arity: number;
|
|
276
|
+
/** False when the strategy cannot be instrumented; then `wastedBytes` is 0 because
|
|
277
|
+
* nothing was measured, NOT because nothing was wasted. */
|
|
278
|
+
instrumented: boolean;
|
|
279
|
+
attempts: number;
|
|
280
|
+
failures: number;
|
|
281
|
+
wastedBytes: number;
|
|
282
|
+
gatedAttempts: number;
|
|
283
|
+
gatedFailures: number;
|
|
284
|
+
gatedWastedBytes: number;
|
|
285
|
+
};
|
|
286
|
+
export type WastedWorkReport = {
|
|
287
|
+
readonly schema: 'parseman.wasted-work/1';
|
|
288
|
+
corpusFiles: number;
|
|
289
|
+
corpusBytes: number;
|
|
290
|
+
parsedOk: number;
|
|
291
|
+
parsedFailed: number;
|
|
292
|
+
/** Sites the profiler installed instrumentation on. */
|
|
293
|
+
instrumentedSites: number;
|
|
294
|
+
/** Reachable choice sites whose strategy cannot be instrumented (see the module
|
|
295
|
+
* header, blind spot 1). Reported so a low total is not read as a clean grammar. */
|
|
296
|
+
uninstrumentableSites: number;
|
|
297
|
+
/** Rule-map entries that are unresolvable references; their subtrees carry no
|
|
298
|
+
* instrumentation, so the total below is a LOWER BOUND by exactly this much. */
|
|
299
|
+
unresolvedRoots: readonly string[];
|
|
300
|
+
/** INTERPRETED total. Read `totalGatedWastedBytes` for what the shipped parser pays. */
|
|
301
|
+
totalWastedBytes: number;
|
|
302
|
+
/** COMPILED MODEL total — the headline number, and the one a gate should use. */
|
|
303
|
+
totalGatedWastedBytes: number;
|
|
304
|
+
/** Descending by `gatedWastedBytes`, then `wastedBytes`, then `siteKey`, then `arm`. */
|
|
305
|
+
arms: readonly WastedWorkArm[];
|
|
306
|
+
/**
|
|
307
|
+
* ORDERING INVERSIONS: arms that were attempted at least `inversionMinAttempts`
|
|
308
|
+
* times and failed EVERY time, while a later arm at the same site matched.
|
|
309
|
+
*
|
|
310
|
+
* A second ranking, because bytes alone answer the wrong question for the clearest
|
|
311
|
+
* class of defect. Measured on jess's CSS grammar: `StylesheetAtRule › dispatch[3]`
|
|
312
|
+
* has arm 0 failing 19 of 19 attempts while arm 1 matches — an unambiguous ordering
|
|
313
|
+
* bug — yet it is only the 14th largest site by bytes, because the prelude it
|
|
314
|
+
* re-scans is short. Bytes rank what costs most; this ranks what is most obviously
|
|
315
|
+
* WRONG. Both are in the report and neither is derived from the other.
|
|
316
|
+
*
|
|
317
|
+
* Computed from the GATED columns, so an arm codegen already skips is not reported
|
|
318
|
+
* as an inversion in a parser that never enters it.
|
|
319
|
+
*
|
|
320
|
+
* Descending by `gatedAttempts` — NOT by bytes. An arm can fail 100% of its attempts
|
|
321
|
+
* and re-scan zero bytes, because it fails on its own first terminal; jess's `Value`
|
|
322
|
+
* arm 8 `CustomPropertyValue` does exactly that, 925 times, and is the one ungated arm
|
|
323
|
+
* at that site. Ranked by bytes it is invisible; ranked by attempts it is a finding.
|
|
324
|
+
*/
|
|
325
|
+
inversions: readonly WastedWorkArm[];
|
|
326
|
+
/** Descending by `wastedBytes`, then ascending by `siteKey`. */
|
|
327
|
+
sites: readonly WastedWorkSite[];
|
|
328
|
+
};
|
|
329
|
+
/** A short, deterministic rendering of an arm's head, for the ranked list. */
|
|
330
|
+
export declare function armLabel(arm: Combinator<unknown>): string;
|
|
331
|
+
/**
|
|
332
|
+
* Every `choice` reachable from `ruleMap`, with its shared-prefix groups and whether
|
|
333
|
+
* the compiler factored the site.
|
|
334
|
+
*
|
|
335
|
+
* Complete by construction: the walk visits every reachable combinator instance and
|
|
336
|
+
* emits an entry for each `choice`, whether or not it has anything to report. A site
|
|
337
|
+
* missing from this report is a site the walk could not reach, never a site that was
|
|
338
|
+
* judged uninteresting.
|
|
339
|
+
*/
|
|
340
|
+
export declare function analyzeChoiceInventory(ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>): ChoiceInventoryReport;
|
|
341
|
+
/** Exposed so the fidelity test can compare the model against emitted guards. */
|
|
342
|
+
export declare function modelledFirstCharGate(p: Combinator<unknown>): FirstSet | null;
|
|
343
|
+
export type WastedWorkCorpusEntry = {
|
|
344
|
+
/** Stable identifier. Use a repo-relative path, never an absolute one — it appears
|
|
345
|
+
* in the report, and an absolute path would make the report machine-dependent. */
|
|
346
|
+
id: string;
|
|
347
|
+
text: string;
|
|
348
|
+
};
|
|
349
|
+
export type ProfileWastedWorkOptions = {
|
|
350
|
+
/** The combinator rule map, for site naming and instrumentation. */
|
|
351
|
+
rules: ReadonlyArray<readonly [string, Combinator<unknown>]>;
|
|
352
|
+
/** The rule to start each parse at. A key of `rules`, or a combinator. */
|
|
353
|
+
entry: string | Combinator<unknown>;
|
|
354
|
+
corpus: ReadonlyArray<WastedWorkCorpusEntry>;
|
|
355
|
+
/** Passed through to `run()`. Must not contain anything machine-dependent. */
|
|
356
|
+
runOptions?: Record<string, unknown>;
|
|
357
|
+
/**
|
|
358
|
+
* How to parse one corpus entry. Defaults to `run()` in interpreted mode.
|
|
359
|
+
*
|
|
360
|
+
* Overridable because a grammar can need a driver of its own — jess's Less dialect,
|
|
361
|
+
* for instance, requires `state: { source: input }` because its reducers read it —
|
|
362
|
+
* and because a caller who has already built a driver should not have to duplicate
|
|
363
|
+
* it here. It must be a plain interpreted parse; anything compiled defeats the
|
|
364
|
+
* instrumentation silently, since a compiled artifact never reads `def.parsers`.
|
|
365
|
+
*/
|
|
366
|
+
runner?: (entry: Combinator<unknown>, input: string, options: Record<string, unknown>) => {
|
|
367
|
+
ok: boolean;
|
|
368
|
+
};
|
|
369
|
+
/** Minimum attempts before an always-failing arm counts as an ordering inversion.
|
|
370
|
+
* Default 4 — below that, "failed every time" is a sample size, not a finding. */
|
|
371
|
+
inversionMinAttempts?: number;
|
|
372
|
+
};
|
|
373
|
+
/**
|
|
374
|
+
* Parse `corpus` with `rules` in INTERPRETED mode, counting input bytes re-scanned
|
|
375
|
+
* by failed choice alternatives.
|
|
376
|
+
*
|
|
377
|
+
* Fails closed on every way of measuring nothing: an empty rule map, an unknown entry
|
|
378
|
+
* rule, an empty corpus, an empty corpus file, or a grammar in which no instrumentable
|
|
379
|
+
* choice site exists. Each of those would otherwise produce a report of zero wasted
|
|
380
|
+
* bytes, which reads as a clean grammar.
|
|
381
|
+
*/
|
|
382
|
+
export declare function profileWastedWork(opts: ProfileWastedWorkOptions): WastedWorkReport;
|
|
383
|
+
//# sourceMappingURL=choice-cost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"choice-cost.d.ts","sourceRoot":"","sources":["../../src/analysis/choice-cost.ts"],"names":[],"mappings":"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"}
|