parseman 0.35.0 → 0.37.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/README.md +34 -19
- package/dist/analysis/duplication.d.ts +421 -0
- package/dist/analysis/duplication.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +25 -0
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/grammar.d.ts +12 -0
- package/dist/analysis/grammar.d.ts.map +1 -0
- package/dist/combinators/expect.d.ts +12 -0
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +26 -0
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/compiler/build-arity.d.ts +12 -0
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/codegen.d.ts +43 -1
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +64 -7
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/cst/host-mode.d.ts +43 -0
- package/dist/cst/host-mode.d.ts.map +1 -0
- package/dist/functional/doc.d.ts.map +1 -1
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/index.cjs +1621 -55
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1621 -55
- package/dist/index.js.map +4 -4
- package/dist/language-service/index.cjs +37 -1
- package/dist/language-service/index.cjs.map +3 -3
- package/dist/language-service/index.js +37 -1
- package/dist/language-service/index.js.map +3 -3
- package/dist/oracle/corpus.d.ts +32 -0
- package/dist/oracle/corpus.d.ts.map +1 -0
- package/dist/oracle/digest.d.ts +21 -0
- package/dist/oracle/digest.d.ts.map +1 -0
- package/dist/oracle/identity.d.ts +117 -0
- package/dist/oracle/identity.d.ts.map +1 -0
- package/dist/oracle/index.cjs +423 -0
- package/dist/oracle/index.cjs.map +7 -0
- package/dist/oracle/index.d.ts +37 -0
- package/dist/oracle/index.d.ts.map +1 -0
- package/dist/oracle/index.js +400 -0
- package/dist/oracle/index.js.map +7 -0
- package/dist/plugin/index.cjs +1628 -62
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +1628 -62
- package/dist/plugin/index.js.map +4 -4
- package/dist/run/index.cjs +23 -0
- package/dist/run/index.cjs.map +3 -3
- package/dist/run/index.js +23 -0
- package/dist/run/index.js.map +3 -3
- package/dist/spec/index.cjs +2977 -56
- package/dist/spec/index.cjs.map +4 -4
- package/dist/spec/index.js +2977 -56
- package/dist/spec/index.js.map +4 -4
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -4,16 +4,37 @@
|
|
|
4
4
|
|
|
5
5
|
# Parséman (PAR-zə-mahn)
|
|
6
6
|
|
|
7
|
-
Write parsers as
|
|
7
|
+
Write parsers as ordinary functions. Ship them like hand-written parsers.
|
|
8
8
|
|
|
9
9
|
Parser combinators are pleasant to write and usually slow. Parser generators are fast and
|
|
10
|
-
usually mean grammar files, generated code, and extra tooling. Parséman is a
|
|
11
|
-
|
|
12
|
-
macro turns it into flat JavaScript that behaves like a parser you wrote by hand.
|
|
10
|
+
usually mean grammar files, generated code, and extra tooling. Parséman is a combinator
|
|
11
|
+
library with an optional compiler, and it gives you both.
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
**Parsing to JS values, the macro build is the fastest general-purpose JS parser in the
|
|
14
|
+
suite** — ahead of every other library measured, at every grammar and every input size.
|
|
15
|
+
Every parser in the suite builds real output: objects, row arrays, AST nodes. On a 7.7 kB
|
|
16
|
+
GraphQL document Parséman takes **131 µs**; Peggy takes 328 µs. Only a purpose-built native
|
|
17
|
+
edges it out, `JSON.parse` on JSON.
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
Two more results. The compiled CST path beats Lezer on the JSON CST fixture — **174 µs** vs
|
|
22
|
+
594 µs at 11.9 kB — while producing a richer tree carrying spans and trivia. And `parseDoc`
|
|
23
|
+
stores parent-relative spans, so an in-place edit costs a fraction of a full reparse rather
|
|
24
|
+
than a multiple of it. Results move with grammar shape, input size and runtime — which is
|
|
25
|
+
why [the suite](#benchmarks) ships with the library rather than only its conclusions.
|
|
26
|
+
|
|
27
|
+
You get there by writing normal code. Add the bundler plugin, mark one import, and the
|
|
28
|
+
combinators you already wrote compile to flat JavaScript — the compiler computes the first
|
|
29
|
+
sets and left-factors the choices for you.
|
|
30
|
+
|
|
31
|
+
Grammars work the same in plain JavaScript: the macro compiles a `.js` grammar to the same
|
|
32
|
+
output as a `.ts` one, and the package ships both ESM and CJS. Write in TypeScript and result
|
|
33
|
+
types are inferred across the whole combinator chain — types you didn't have to write out.
|
|
34
|
+
|
|
35
|
+
Reach for it when you want CST/AST nodes with spans and trivia, error recovery and
|
|
36
|
+
incremental re-parsing for editor tooling, or simply a fast parser for a DSL, config
|
|
37
|
+
language, formatter, or linter.
|
|
17
38
|
|
|
18
39
|
> **📖 Full documentation: [matthew-dean.github.io/parseman](https://matthew-dean.github.io/parseman/)**
|
|
19
40
|
|
|
@@ -123,24 +144,18 @@ hand-written parsers in
|
|
|
123
144
|
Benchmarked against [Peggy](https://peggyjs.org/),
|
|
124
145
|
[Parsimmon](https://github.com/jneen/parsimmon), [Chevrotain](https://chevrotain.io/),
|
|
125
146
|
[Nearley](https://nearley.js.org/), [Jison](https://github.com/zaach/jison) and
|
|
126
|
-
[Lezer](https://lezer.codemirror.net/) on JSON, CSV and GraphQL
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
**Parsing to JS values, the macro build is the fastest general-purpose JS parser in the
|
|
130
|
-
suite** — ahead of every library above at every grammar and size (GraphQL large: **142 µs**
|
|
131
|
-
vs Peggy's 339 µs). Only a purpose-built native edges it out, `JSON.parse` on JSON.
|
|
147
|
+
[Lezer](https://lezer.codemirror.net/) on JSON, CSV and GraphQL, at three input sizes each.
|
|
148
|
+
Each chart's legend names the libraries measured for that grammar.
|
|
132
149
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
150
|
+
Largest fixture of each, macro build against the fastest other library on that chart:
|
|
151
|
+
GraphQL **131 µs** vs Peggy's 328 µs, JSON **133 µs** vs Chevrotain's 241 µs, CSV
|
|
152
|
+
**75.3 µs** vs Peggy's 420 µs. Native `JSON.parse` does JSON large in 51.6 µs. On the CST
|
|
153
|
+
chart, macro build runs 174 µs against Lezer's 594 µs parse-only.
|
|
137
154
|
|
|
138
155
|

|
|
139
156
|
|
|
140
157
|

|
|
141
158
|
|
|
142
|
-

|
|
143
|
-
|
|
144
159
|

|
|
145
160
|
|
|
146
161
|
Per-fixture figures, initialization costs, hardware, grammar provenance and how to reproduce
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static grammar-duplication / overlap / rewrite diagnostic.
|
|
3
|
+
*
|
|
4
|
+
* A parseman grammar is a COMBINATOR TREE, not source text — so the question
|
|
5
|
+
* "did I write this production twice?" is a structural one a machine answers
|
|
6
|
+
* exactly, and a reviewer answers badly. A few hundred productions is tens of
|
|
7
|
+
* thousands of pairs; nobody reads that. This module walks the same tree
|
|
8
|
+
* `analyzeGating` walks and reports nine families:
|
|
9
|
+
*
|
|
10
|
+
* 1. `duplicates` — subtrees that are structurally IDENTICAL, in ≥2 places.
|
|
11
|
+
* 2. `nearDuplicates` — subtrees identical except at ONE slot. This is the
|
|
12
|
+
* high-value case: a general rule cloned with one term
|
|
13
|
+
* swapped, where the fix is one production whose varying
|
|
14
|
+
* slot is a `choice`, not N copies of the scaffolding.
|
|
15
|
+
* 3. `regexFragments` — alternation runs re-spelled across several `regex()`
|
|
16
|
+
* terminals. Structural hashing cannot see inside a
|
|
17
|
+
* regex, so this is its own pass.
|
|
18
|
+
* 4. `regexClasses` — character classes re-spelled across terminals, and, more
|
|
19
|
+
* usefully, NEAR-identical ones with the drift shown side
|
|
20
|
+
* by side.
|
|
21
|
+
* 5. `overlaps` — `choice` arms whose first-sets intersect, reported as
|
|
22
|
+
* "these two arms, on these chars, sharing these leading
|
|
23
|
+
* terms" rather than gating's "dispatch failed".
|
|
24
|
+
* 6. `rewrites` — ALGEBRAIC simplifications: mechanically-derived exact
|
|
25
|
+
* rewrites (`choice(sequence(A,B), B)` → `sequence(
|
|
26
|
+
* optional(A), B)`), plus the dead-arm cases that are
|
|
27
|
+
* outright bugs.
|
|
28
|
+
* 7. `divergentNodes` — ONE `node()` type built by several structurally different
|
|
29
|
+
* productions that are nonetheless variants of one shape.
|
|
30
|
+
* 8. `structureLoss` — an earlier `choice` arm that FLATTENS what a later arm
|
|
31
|
+
* would have structured: same `node()` type, overlapping
|
|
32
|
+
* first-sets, and the earlier body contains no `node()`
|
|
33
|
+
* at all. The parse succeeds either way, so nothing else
|
|
34
|
+
* — not a test suite, not an output diff — reports it.
|
|
35
|
+
* The ordered, consequential half of `divergentNodes`.
|
|
36
|
+
* 9. `keywordRegexes` — hand-rolled keyword regexes (`regex(/not(?![-\w])/i)`)
|
|
37
|
+
* that should be `word()`/`keywords()`. Not a style note:
|
|
38
|
+
* `/i` without `/u` gets non-ASCII case folding wrong,
|
|
39
|
+
* and the fix that lives inside `keywords()` (see
|
|
40
|
+
* `combinators/case-fold.ts`) never reached the copies.
|
|
41
|
+
*
|
|
42
|
+
* ## What this CANNOT see
|
|
43
|
+
*
|
|
44
|
+
* Structural hashing is exactly that: STRUCTURAL. Two productions that accept the
|
|
45
|
+
* same language but are shaped differently — `many(x)` vs `optional(oneOrMore(x))`,
|
|
46
|
+
* a `regex` spelling of what another rule builds from combinators, a rule inlined
|
|
47
|
+
* once and referenced once — are invisible to `duplicates`/`nearDuplicates` unless
|
|
48
|
+
* they happen to fall into the `rewrites` algebra below. Equally, two subtrees that
|
|
49
|
+
* differ only in a `transform`/`node` CALLBACK are reported as distinct when the
|
|
50
|
+
* callbacks' source text differs, even if the functions are equivalent. This finds
|
|
51
|
+
* COPY-PASTE and mechanical redundancy. It does not find semantic redundancy, and a
|
|
52
|
+
* clean report is not a proof that a grammar has none.
|
|
53
|
+
*
|
|
54
|
+
* `analyzeDuplication()` / `analyzeDuplicationRules()` are the programmatic
|
|
55
|
+
* surface, mirroring `analyzeGating` / `analyzeGatingRules`. The compile-time
|
|
56
|
+
* wiring is OPT-IN (`compile(g, { duplication: 'warn' })` /
|
|
57
|
+
* `PARSEMAN_DUPLICATION=warn`) and runs on ALL THREE lowering paths — `compile`,
|
|
58
|
+
* `compileRuleMap` and `compileLinkable` — because the macro build takes the
|
|
59
|
+
* latter two and a diagnostic that only runs on the first is a diagnostic that
|
|
60
|
+
* reports zero findings forever.
|
|
61
|
+
*/
|
|
62
|
+
import type { Combinator, FirstSet } from '../types.ts';
|
|
63
|
+
import { type RefResolver } from '../combinators/first-set.ts';
|
|
64
|
+
/** Where a combinator sits: its owning rule, and the accessor path within it. */
|
|
65
|
+
export type Site = {
|
|
66
|
+
/** Nearest enclosing `_ruleName`, or the seed name it was reached under. */
|
|
67
|
+
rule: string;
|
|
68
|
+
/** Structural path inside that rule, e.g. `choice[0] › node(Declaration) › seq[4]`. */
|
|
69
|
+
path: string;
|
|
70
|
+
};
|
|
71
|
+
export declare const siteToString: (s: Site) => string;
|
|
72
|
+
/** N structurally identical copies of one subtree. */
|
|
73
|
+
export type DuplicateFinding = {
|
|
74
|
+
kind: 'exact-duplicate';
|
|
75
|
+
id: string;
|
|
76
|
+
/** Node count of the repeated subtree. */
|
|
77
|
+
size: number;
|
|
78
|
+
/** How many DISTINCT combinator instances share the shape (shared-by-reference
|
|
79
|
+
* reuse is not duplication and is never counted here). */
|
|
80
|
+
count: number;
|
|
81
|
+
/** Nodes removed by hoisting the shape to one shared const: `(count - 1) * size`. */
|
|
82
|
+
savings: number;
|
|
83
|
+
/** One-line rendering of the repeated shape. */
|
|
84
|
+
shape: string;
|
|
85
|
+
sites: Site[];
|
|
86
|
+
suggestion: string;
|
|
87
|
+
};
|
|
88
|
+
/** N subtrees identical except at ONE slot. */
|
|
89
|
+
export type NearDuplicateFinding = {
|
|
90
|
+
kind: 'near-duplicate';
|
|
91
|
+
id: string;
|
|
92
|
+
/** Node count shared by every member (the scaffolding). */
|
|
93
|
+
sharedSize: number;
|
|
94
|
+
count: number;
|
|
95
|
+
savings: number;
|
|
96
|
+
/** The scaffolding with the varying slot rendered as `‹slot›`. */
|
|
97
|
+
shape: string;
|
|
98
|
+
/** Path to the varying slot, relative to the members' root. */
|
|
99
|
+
slotPath: string;
|
|
100
|
+
/** What each member puts in that slot, aligned with `sites`. */
|
|
101
|
+
variants: string[];
|
|
102
|
+
sites: Site[];
|
|
103
|
+
suggestion: string;
|
|
104
|
+
};
|
|
105
|
+
/** An alternation run re-spelled across several `regex()` terminals. */
|
|
106
|
+
export type RegexFragmentFinding = {
|
|
107
|
+
kind: 'regex-fragment';
|
|
108
|
+
id: string;
|
|
109
|
+
/** The shared run, as it appears in the sources (`>=|<=|=>|=<|=~|[<>=]`). */
|
|
110
|
+
fragment: string;
|
|
111
|
+
/** Number of alternation branches in the run. */
|
|
112
|
+
branches: number;
|
|
113
|
+
count: number;
|
|
114
|
+
/** Characters removed by hoisting: `(count - 1) * fragment.length`. */
|
|
115
|
+
savings: number;
|
|
116
|
+
/** The full source of each regex that carries it, aligned with `sites`. */
|
|
117
|
+
sources: string[];
|
|
118
|
+
sites: Site[];
|
|
119
|
+
suggestion: string;
|
|
120
|
+
};
|
|
121
|
+
/** One spelling of a character class, and where it appears. */
|
|
122
|
+
export type RegexClassVariant = {
|
|
123
|
+
/** The class as written, including any `^`: `-_a-zA-Z0-9-`. */
|
|
124
|
+
source: string;
|
|
125
|
+
/** Members this spelling has that the cluster's most common spelling does not,
|
|
126
|
+
* and vice versa — the DRIFT, rendered `+a-f / -0-9`. */
|
|
127
|
+
delta: string;
|
|
128
|
+
count: number;
|
|
129
|
+
sites: Site[];
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* A character class (or boundary lookahead class) re-spelled across several
|
|
133
|
+
* `regex()` terminals — and, when the spellings are not identical, the drift
|
|
134
|
+
* between them.
|
|
135
|
+
*/
|
|
136
|
+
export type RegexClassFinding = {
|
|
137
|
+
kind: 'regex-class';
|
|
138
|
+
id: string;
|
|
139
|
+
/** The cluster's most common spelling. */
|
|
140
|
+
canonical: string;
|
|
141
|
+
/** Every spelling in the cluster, most common first. Two variants side by side
|
|
142
|
+
* IS the finding: one of them is wrong and reading cannot tell you which. */
|
|
143
|
+
variants: RegexClassVariant[];
|
|
144
|
+
/** Distinct `regex()` terminals across the whole cluster. */
|
|
145
|
+
count: number;
|
|
146
|
+
/** True when the cluster holds more than one spelling. */
|
|
147
|
+
drifted: boolean;
|
|
148
|
+
/** The class's highest code point is U+FFFF and it was written as an explicit
|
|
149
|
+
* range — astral-plane characters fall outside it. */
|
|
150
|
+
bmpCeiling: boolean;
|
|
151
|
+
suggestion: string;
|
|
152
|
+
};
|
|
153
|
+
/** Two arms of one `choice` whose first-sets intersect. */
|
|
154
|
+
export type ArmOverlapFinding = {
|
|
155
|
+
kind: 'arm-overlap';
|
|
156
|
+
id: string;
|
|
157
|
+
site: Site;
|
|
158
|
+
a: number;
|
|
159
|
+
b: number;
|
|
160
|
+
/** The SHARED first characters (not the union). */
|
|
161
|
+
on: FirstSet;
|
|
162
|
+
/** How many leading terms the two arms spell identically. */
|
|
163
|
+
sharedLeadingTerms: number;
|
|
164
|
+
/** Rendering of those shared leading terms, when there are any. */
|
|
165
|
+
sharedPrefix: string | null;
|
|
166
|
+
/** True when parseman's `sharedPrefix` choice strategy already recognizes the
|
|
167
|
+
* common prefix once at runtime — the finding is then about READABILITY, not
|
|
168
|
+
* speed. */
|
|
169
|
+
handledByStrategy: boolean;
|
|
170
|
+
/** Both arms lead with a `regex()` whose character classes intersect — usually
|
|
171
|
+
* a sign the two arms want to be one terminal. */
|
|
172
|
+
regexPair: boolean;
|
|
173
|
+
suggestion: string;
|
|
174
|
+
};
|
|
175
|
+
export type RewriteKind = 'optional-prefix' | 'optional-suffix' | 'left-factor' | 'hand-rolled-sepby' | 'idempotent-nesting' | 'single-element' | 'duplicate-arm' | 'shadowed-arm';
|
|
176
|
+
/** A mechanically-derived rewrite. */
|
|
177
|
+
export type RewriteFinding = {
|
|
178
|
+
kind: 'rewrite';
|
|
179
|
+
id: string;
|
|
180
|
+
rewrite: RewriteKind;
|
|
181
|
+
site: Site;
|
|
182
|
+
/** What is there now. */
|
|
183
|
+
from: string;
|
|
184
|
+
/** What it is equal to. */
|
|
185
|
+
to: string;
|
|
186
|
+
/**
|
|
187
|
+
* `true` only when the rewrite provably cannot move the parse VALUE — which,
|
|
188
|
+
* here, means it only deletes an arm that can never be selected. Every other
|
|
189
|
+
* rewrite in this family changes the child arity or nesting of the value the
|
|
190
|
+
* site produces, so a `node()` build / `transform` / downstream consumer that
|
|
191
|
+
* reads positionally WILL see a different tree. Those are reported as
|
|
192
|
+
* CANDIDATES to verify, never as "fix this".
|
|
193
|
+
*/
|
|
194
|
+
astNeutral: boolean;
|
|
195
|
+
/** Non-empty when the rewrite removes speculative work at parse time. */
|
|
196
|
+
perf: string;
|
|
197
|
+
/** Only on `hand-rolled-sepby`: whether this SITE can actually take `sepBy`. A
|
|
198
|
+
* count of matches is not a worklist — in a real grammar most matches are
|
|
199
|
+
* blocked, and reporting them all as convertible generates false work. */
|
|
200
|
+
sepByVerdict?: SepByVerdict;
|
|
201
|
+
/** True when this is a latent BUG (an unreachable arm), not a verbosity smell. */
|
|
202
|
+
bug: boolean;
|
|
203
|
+
suggestion: string;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* One AST node type built by two or more STRUCTURALLY DIFFERENT productions that
|
|
207
|
+
* are nonetheless variants of a single shape (they spell several of the same
|
|
208
|
+
* terms). This is the clone family near-duplicate detection cannot see: the copies
|
|
209
|
+
* diverge in more than one slot — extra scaffolding, a hand-rolled whitespace run,
|
|
210
|
+
* a terminal guard — so no single hole explains them, yet every edit to "the
|
|
211
|
+
* declaration shape" still has to land in all of them, and nothing checks that it did.
|
|
212
|
+
*/
|
|
213
|
+
export type DivergentNodeFinding = {
|
|
214
|
+
kind: 'divergent-node';
|
|
215
|
+
id: string;
|
|
216
|
+
/** The `node()` type every member produces. */
|
|
217
|
+
nodeType: string;
|
|
218
|
+
/** Number of structurally distinct productions building it. */
|
|
219
|
+
count: number;
|
|
220
|
+
/** Terms every member spells identically — the evidence they are one shape. */
|
|
221
|
+
sharedTerms: string[];
|
|
222
|
+
productions: {
|
|
223
|
+
shape: string;
|
|
224
|
+
site: Site;
|
|
225
|
+
/** Terms this production has that at least one sibling does not. */
|
|
226
|
+
distinctTerms: string[];
|
|
227
|
+
}[];
|
|
228
|
+
suggestion: string;
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* An earlier `choice` arm that FLATTENS what a later arm would have STRUCTURED.
|
|
232
|
+
*
|
|
233
|
+
* Both arms build the same `node()` type and can start on the same character, so
|
|
234
|
+
* on the inputs both accept the earlier one wins — and its body contains no
|
|
235
|
+
* `node()` at all, so the tree it produces is that node over bare leaves. The
|
|
236
|
+
* later arm, on those same inputs, would have produced child nodes. Nothing about
|
|
237
|
+
* this is visible in a parse that succeeds: `ok` is true, the span is right, the
|
|
238
|
+
* text round-trips. Only the SHAPE moved, and only on the subset of inputs the
|
|
239
|
+
* flattening arm happens to accept — which is why it survives a test suite that
|
|
240
|
+
* checks "does it parse" and a corpus diff that checks emitted output. It shows
|
|
241
|
+
* up in whatever consumes the tree: a language service that lints number tokens,
|
|
242
|
+
* a formatter, a refactor.
|
|
243
|
+
*
|
|
244
|
+
* This is the ordered, consequential half of `divergent-node`. That finding says
|
|
245
|
+
* two productions build one type and nothing keeps them in sync, and it expressly
|
|
246
|
+
* allows "the variants exist for a parse-order reason (a fast path tried first)".
|
|
247
|
+
* This one is the case where that excuse is the bug: the fast path is not
|
|
248
|
+
* tree-neutral, and here is the list of node types it deletes.
|
|
249
|
+
*/
|
|
250
|
+
export type StructureLossFinding = {
|
|
251
|
+
kind: 'structure-loss';
|
|
252
|
+
id: string;
|
|
253
|
+
/** The `choice` that orders the two arms. */
|
|
254
|
+
site: Site;
|
|
255
|
+
/** The `node()` type BOTH arms build. */
|
|
256
|
+
nodeType: string;
|
|
257
|
+
/** Index of the flattening arm (the earlier one). */
|
|
258
|
+
earlier: number;
|
|
259
|
+
/** Index of the structuring arm it shadows. */
|
|
260
|
+
later: number;
|
|
261
|
+
/** The characters on which both arms can start — where the shadowing bites. */
|
|
262
|
+
on: FirstSet;
|
|
263
|
+
/** Node types the later arm builds under `nodeType` and the earlier cannot. */
|
|
264
|
+
lostNodeTypes: string[];
|
|
265
|
+
earlierShape: string;
|
|
266
|
+
laterShape: string;
|
|
267
|
+
suggestion: string;
|
|
268
|
+
};
|
|
269
|
+
/** A `regex()` that hand-rolls a keyword + word boundary. */
|
|
270
|
+
export type KeywordRegexFinding = {
|
|
271
|
+
kind: 'keyword-regex';
|
|
272
|
+
id: string;
|
|
273
|
+
site: Site;
|
|
274
|
+
source: string;
|
|
275
|
+
flags: string;
|
|
276
|
+
words: string[];
|
|
277
|
+
/** The boundary character class, as a `word()`/`keywords()` `boundary` argument.
|
|
278
|
+
* `null` when the regex enumerates a vocabulary with no guard at all. */
|
|
279
|
+
boundary: string | null;
|
|
280
|
+
/** `/i` without `/u`: the fold class is NOT `{c, upper(c), lower(c)}`. */
|
|
281
|
+
caseFoldRisk: boolean;
|
|
282
|
+
/** Sibling arm indices in the same `choice` that are also hand-rolled keywords. */
|
|
283
|
+
siblingArms: number[];
|
|
284
|
+
/**
|
|
285
|
+
* >= 3 literal alternatives: a fixed VOCABULARY enumerated by hand rather than a
|
|
286
|
+
* keyword with a guard. The interesting sub-case, because ordering starts to matter
|
|
287
|
+
* and the list is usually long enough that nobody re-reads it.
|
|
288
|
+
*/
|
|
289
|
+
vocabulary: boolean;
|
|
290
|
+
/**
|
|
291
|
+
* Alternatives are in non-increasing length order. `keywords()` sorts longest-first
|
|
292
|
+
* by construction; a hand-written alternation does not, and nothing checks it.
|
|
293
|
+
*/
|
|
294
|
+
longestFirst: boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Earlier alternatives that are strict PREFIXES of later ones. Regex alternation is
|
|
297
|
+
* first-match, so the longer branch is reachable only when a trailing boundary guard
|
|
298
|
+
* rejects the following character and forces a backtrack (`rescuedByBoundary`).
|
|
299
|
+
*/
|
|
300
|
+
hazards: {
|
|
301
|
+
shorter: string;
|
|
302
|
+
longer: string;
|
|
303
|
+
at: string;
|
|
304
|
+
rescuedByBoundary: boolean;
|
|
305
|
+
}[];
|
|
306
|
+
/**
|
|
307
|
+
* At least one hazard is NOT rescued by a boundary guard — a later alternative can
|
|
308
|
+
* never match. That is a live bug, not a cleanup.
|
|
309
|
+
*/
|
|
310
|
+
bug: boolean;
|
|
311
|
+
suggestion: string;
|
|
312
|
+
};
|
|
313
|
+
export type DuplicationReport = {
|
|
314
|
+
duplicates: DuplicateFinding[];
|
|
315
|
+
nearDuplicates: NearDuplicateFinding[];
|
|
316
|
+
regexFragments: RegexFragmentFinding[];
|
|
317
|
+
regexClasses: RegexClassFinding[];
|
|
318
|
+
overlaps: ArmOverlapFinding[];
|
|
319
|
+
rewrites: RewriteFinding[];
|
|
320
|
+
divergentNodes: DivergentNodeFinding[];
|
|
321
|
+
structureLoss: StructureLossFinding[];
|
|
322
|
+
keywordRegexes: KeywordRegexFinding[];
|
|
323
|
+
/** Ids listed in `accept` that matched no finding — stale entries to prune. */
|
|
324
|
+
acceptedUnused: string[];
|
|
325
|
+
stats: {
|
|
326
|
+
rules: number;
|
|
327
|
+
/** Distinct combinator INSTANCES reached. */
|
|
328
|
+
nodes: number;
|
|
329
|
+
/** Distinct structural shapes among them. */
|
|
330
|
+
shapes: number;
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
export type AnalyzeDuplicationOptions = {
|
|
334
|
+
/** Smallest repeated subtree worth reporting, in nodes. Default 3 — which is
|
|
335
|
+
* what keeps `optional(ws)` (2 nodes) out of the ranking. */
|
|
336
|
+
minSize?: number;
|
|
337
|
+
/** Cap per category, applied AFTER ranking. Default 25. */
|
|
338
|
+
maxFindings?: number;
|
|
339
|
+
/** Finding `id`s to suppress — the single per-finding acknowledgement channel,
|
|
340
|
+
* mirroring the gating snapshot allowlist. */
|
|
341
|
+
accept?: Iterable<string>;
|
|
342
|
+
/** Bind cross-artifact `g.Foo` holes when computing first-sets for `overlaps`. */
|
|
343
|
+
resolveRef?: RefResolver;
|
|
344
|
+
/** Name to attribute an unnamed entry to, instead of `<entry>`. */
|
|
345
|
+
entryName?: string;
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* Every alternation branch list in a regex source, at ANY nesting depth. Depth
|
|
349
|
+
* matters: `/a|b/` and `/x(?:a|b)y/` re-spell the same run, and only one of them
|
|
350
|
+
* has it at top level.
|
|
351
|
+
*/
|
|
352
|
+
export declare function alternationGroups(src: string): string[][];
|
|
353
|
+
/**
|
|
354
|
+
* Recognize a `regex()` that hand-rolls what `word()`/`keywords()` owns.
|
|
355
|
+
*
|
|
356
|
+
* TWO shapes, because they fail differently:
|
|
357
|
+
*
|
|
358
|
+
* - a word (or word alternation) plus a trailing word-boundary guard —
|
|
359
|
+
* `regex(/not(?![-\w])/i)`;
|
|
360
|
+
* - a bare alternation of >= 3 literal words with NO guard — a fixed vocabulary
|
|
361
|
+
* enumerated by hand. A regex enumerating a fixed vocabulary is a keyword set
|
|
362
|
+
* written the hard way: it loses first-set gating, it hand-maintains an ordering
|
|
363
|
+
* the combinator guarantees, and with `/i` and no `/u` it inherits the non-ASCII
|
|
364
|
+
* case-folding bug `keywords()` fixes internally. The no-guard form is also where
|
|
365
|
+
* ordering is load-bearing, since nothing backtracks past a shorter match.
|
|
366
|
+
*
|
|
367
|
+
* Returns the words and the boundary CLASS in the exact form `word(str, boundary)` /
|
|
368
|
+
* `keywords(words, { boundary })` take, so the suggestion names a real call rather
|
|
369
|
+
* than describing one.
|
|
370
|
+
*/
|
|
371
|
+
export declare function keywordRegexShape(source: string): {
|
|
372
|
+
words: string[];
|
|
373
|
+
boundary: string | null;
|
|
374
|
+
} | null;
|
|
375
|
+
/**
|
|
376
|
+
* Ordering hazards in a hand-written alternation: an EARLIER alternative that is a
|
|
377
|
+
* strict prefix of a LATER one. Regex alternation is first-match, not longest-match,
|
|
378
|
+
* so `red|redish` matches only `red` — unless a trailing boundary guard rejects the
|
|
379
|
+
* character that follows, which makes the engine backtrack into the longer branch.
|
|
380
|
+
*
|
|
381
|
+
* That distinction is the whole point of reporting this: without a rescuing guard the
|
|
382
|
+
* longer word is UNREACHABLE, which is a live bug, not a style finding. `keywords()`
|
|
383
|
+
* sorts longest-first by construction and the hazard cannot exist there at all.
|
|
384
|
+
*/
|
|
385
|
+
export declare function keywordAlternationHazards(words: readonly string[], boundary: string | null): {
|
|
386
|
+
shorter: string;
|
|
387
|
+
longer: string;
|
|
388
|
+
at: string;
|
|
389
|
+
rescuedByBoundary: boolean;
|
|
390
|
+
}[];
|
|
391
|
+
export declare function analyzeDuplication(entry: Combinator<unknown>, opts?: AnalyzeDuplicationOptions): DuplicationReport;
|
|
392
|
+
export declare function analyzeDuplicationRules(ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>, opts?: AnalyzeDuplicationOptions): DuplicationReport;
|
|
393
|
+
/**
|
|
394
|
+
* Every `[...]` in a regex source, INCLUDING the ones inside boundary lookaheads
|
|
395
|
+
* (`(?![-\w])` yields `-\w`) — those are the same duplication in a different
|
|
396
|
+
* costume, and the same drift risk.
|
|
397
|
+
*/
|
|
398
|
+
export declare function extractCharClasses(src: string): string[];
|
|
399
|
+
/**
|
|
400
|
+
* Split a class body into normalized MEMBERS (`a-z`, `\w`, `-`, `-`).
|
|
401
|
+
* Non-ASCII literals are rewritten to `\uXXXX` so a class typed with a raw ``
|
|
402
|
+
* and one typed with `` compare equal — otherwise the diagnostic reports
|
|
403
|
+
* drift that is only an editor's.
|
|
404
|
+
*/
|
|
405
|
+
export declare function charClassMembers(body: string): string[];
|
|
406
|
+
/**
|
|
407
|
+
* Whether a hand-rolled separated list can ACTUALLY become `sepBy` — a verdict per
|
|
408
|
+
* site, not a count. Reporting every match as convertible generates false work: on
|
|
409
|
+
* the reference Less grammar only a minority are, and the rest are blocked for two
|
|
410
|
+
* concrete, detectable reasons.
|
|
411
|
+
*/
|
|
412
|
+
export type SepByVerdict = 'convertible' | 'blocked-by-capture' | 'reducer-stride-review';
|
|
413
|
+
export type DuplicationWarnLevel = 'off' | 'warn' | 'error';
|
|
414
|
+
/**
|
|
415
|
+
* Ranked, actionable lines — same tone and shape as `formatGatingWarnings`: what
|
|
416
|
+
* it is, where it is, and one concrete thing to do about it.
|
|
417
|
+
*/
|
|
418
|
+
export declare function formatDuplicationFindings(report: DuplicationReport): string[];
|
|
419
|
+
/** Total findings across every category — the number the `'error'` gate keys on. */
|
|
420
|
+
export declare function duplicationFindingCount(report: DuplicationReport): number;
|
|
421
|
+
//# sourceMappingURL=duplication.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplication.d.ts","sourceRoot":"","sources":["../../src/analysis/duplication.ts"],"names":[],"mappings":"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"}
|
|
@@ -134,10 +134,35 @@ export type AnalyzeGatingOptions = {
|
|
|
134
134
|
*/
|
|
135
135
|
resolveRef?: RefResolver;
|
|
136
136
|
};
|
|
137
|
+
/**
|
|
138
|
+
* A rule the walk could NOT introspect. Its choices were never examined, so no
|
|
139
|
+
* verdict about it — clean or otherwise — is available.
|
|
140
|
+
*
|
|
141
|
+
* This exists because the alternative is silence. A `compose()` result is a map of
|
|
142
|
+
* FUSED rule functions with no `_def` combinator graph; walking one used to throw a
|
|
143
|
+
* bare `TypeError: Cannot read properties of undefined (reading 'tag')`, and the
|
|
144
|
+
* default-on diagnostic swallowed that throw and returned `undefined` — a failed
|
|
145
|
+
* analysis and a clean grammar were indistinguishable. Every unanalysable input is
|
|
146
|
+
* now counted and named here, and `formatGatingWarnings` always reports it.
|
|
147
|
+
*/
|
|
148
|
+
export type Unanalysable = {
|
|
149
|
+
/** The rule name (or seed name) whose walk stopped. */
|
|
150
|
+
rule: string;
|
|
151
|
+
/** Why it could not be walked, in terms the caller can act on. */
|
|
152
|
+
reason: string;
|
|
153
|
+
kind: 'fused-rule' | 'opaque-artifact' | 'not-a-combinator';
|
|
154
|
+
};
|
|
137
155
|
export type GatingReport = {
|
|
138
156
|
totalChoices: number;
|
|
139
157
|
gated: number;
|
|
140
158
|
recoverable: number;
|
|
159
|
+
/**
|
|
160
|
+
* Rules the walk could not introspect (see `Unanalysable`). NON-EMPTY MEANS THE
|
|
161
|
+
* REPORT IS PARTIAL: `totalChoices === 0` with a non-empty `unanalysable` is a
|
|
162
|
+
* blind walk, not a clean grammar. Callers that treat an empty `ungated` as a pass
|
|
163
|
+
* MUST also assert this is empty.
|
|
164
|
+
*/
|
|
165
|
+
unanalysable: Unanalysable[];
|
|
141
166
|
/** Genuinely-ungated choices NOT in the accepted allowlist — warned + gate-failing. */
|
|
142
167
|
ungated: ChoiceGating[];
|
|
143
168
|
/** Ungated choices whose id was in the accepted allowlist — silent, accepted with intent. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gating.d.ts","sourceRoot":"","sources":["../../src/analysis/gating.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"gating.d.ts","sourceRoot":"","sources":["../../src/analysis/gating.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AnalyzeGatingOptions, type GatingReport } from './gating.ts';
|
|
2
|
+
/** A `rules()` map, a `compose()` result, or any rule-name → value record. */
|
|
3
|
+
export type AnalysableGrammar = Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* Analyze a grammar's choices, resolving a composed grammar back to its carried IR
|
|
6
|
+
* first. Accepts a `rules()` map (walked directly) or a `compose()` result (recovered).
|
|
7
|
+
*
|
|
8
|
+
* The returned report's `unanalysable` is authoritative: non-empty means part of the
|
|
9
|
+
* grammar was never examined, and a clean `ungated` must NOT be read as a pass.
|
|
10
|
+
*/
|
|
11
|
+
export declare function analyzeGrammarGating(grammar: AnalysableGrammar, opts?: AnalyzeGatingOptions): GatingReport;
|
|
12
|
+
//# sourceMappingURL=grammar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../src/analysis/grammar.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAsB,KAAK,oBAAoB,EAAE,KAAK,YAAY,EAAqB,MAAM,aAAa,CAAA;AAGjH,8EAA8E;AAC9E,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEvD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,CAAC,EAAE,oBAAoB,GAC1B,YAAY,CAkBd"}
|
|
@@ -13,6 +13,18 @@ export type { ParseError };
|
|
|
13
13
|
* This is what lets expect() report a meaningful, IDENTICAL expectation in both
|
|
14
14
|
* the interpreter and the compiled output (the compiled path does not rebuild the
|
|
15
15
|
* runtime `expected` array, so it reads this precomputed set instead).
|
|
16
|
+
*
|
|
17
|
+
* The result is DEDUPLICATED. Deriving through a nullable prefix re-reaches the
|
|
18
|
+
* same token from several terms — a value grammar whose leading terms are all
|
|
19
|
+
* optional names its value-start set once per term — and an expectation list that
|
|
20
|
+
* offers `"@{"` five times is wrong for a reader and expensive for a parser: a
|
|
21
|
+
* choice that loses every arm materializes `[...arm0, ...arm1, …]` at runtime, and
|
|
22
|
+
* those arrays feed the enclosing choice's concat in turn, so duplicates compound
|
|
23
|
+
* multiplicatively up the nesting. On jess's Less grammar the un-deduped sets cost
|
|
24
|
+
* 32% of parse time on `benchmark.less` (medians 20.7 → 14.2 ms over 100
|
|
25
|
+
* interleaved pairs, 99/100 wins, min 19.1 → 13.0), which is the whole of the Less
|
|
26
|
+
* regression 0.35.0 shipped. Dedup is not an optimisation bolted onto the
|
|
27
|
+
* derivation — a SET is what the derivation always meant.
|
|
16
28
|
*/
|
|
17
29
|
export declare function deriveExpected(c: Combinator<unknown>): string[];
|
|
18
30
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/combinators/expect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,UAAU,EAAE,MAAM,aAAa,CAAA;AAIhG,YAAY,EAAE,UAAU,EAAE,CAAA;AAE1B
|
|
1
|
+
{"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/combinators/expect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,UAAU,EAAE,MAAM,aAAa,CAAA;AAIhG,YAAY,EAAE,UAAU,EAAE,CAAA;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,MAAM,EAAE,CAE/D;AAkED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,GAAG,UAAU,CAAC,CA2B/F;AAED,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAEhE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Combinator } from '../types.ts';
|
|
2
|
+
import type { HostMode } from '../compiler/codegen.ts';
|
|
2
3
|
/**
|
|
3
4
|
* Non-enumerable key on a `rules()` result holding the factory's declaration
|
|
4
5
|
* order (the returned object's key order), which differs from the result's own
|
|
@@ -45,6 +46,31 @@ export type RulesOptions = {
|
|
|
45
46
|
* and inherited everywhere, mirroring `trivia`. `null`/absent = none.
|
|
46
47
|
*/
|
|
47
48
|
scanSkip?: Combinator<unknown>[] | null;
|
|
49
|
+
/**
|
|
50
|
+
* Compile-time host mode for this grammar, same meaning as `compile(g, { hostMode })`
|
|
51
|
+
* and `compose(items, { hostMode })`: `'ast'` (default) emits each direct builder's
|
|
52
|
+
* own result and NO positioned-CST branch; `'cst'` builds every node through the
|
|
53
|
+
* `ctx.build` host and captures unconditionally.
|
|
54
|
+
*
|
|
55
|
+
* Declaring it HERE is what lets ONE grammar source serve both consumers under the
|
|
56
|
+
* macro, which cannot take a compile option any other way:
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* const factory = (g: any) => ({ … })
|
|
60
|
+
* export const grammar = rules({ trivia: rw }, factory)
|
|
61
|
+
* export const cstGrammar = rules({ trivia: rw, hostMode: 'cst' }, factory)
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* Two call sites over one factory, so the macro emits two independent top-level
|
|
65
|
+
* artifacts and each bundle tree-shakes away the one it does not import. Neither
|
|
66
|
+
* pays the other's cost — which is the whole reason host mode is a compile-time
|
|
67
|
+
* decision rather than a per-node runtime read.
|
|
68
|
+
*
|
|
69
|
+
* The INTERPRETER routes dynamically off the host and does not need this; it is
|
|
70
|
+
* still recorded, so `run()` can refuse a mismatched host once per parse instead of
|
|
71
|
+
* quietly producing the wrong tree shape.
|
|
72
|
+
*/
|
|
73
|
+
hostMode?: HostMode;
|
|
48
74
|
};
|
|
49
75
|
export declare function rules<T extends Record<string, Combinator<unknown>>>(factory: (self: any) => T): T;
|
|
50
76
|
export declare function rules<T extends Record<string, Combinator<unknown>>>(options: RulesOptions, factory: (self: any) => T): T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/combinators/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/combinators/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAItD;;;;GAIG;AACH,eAAO,MAAM,UAAU,wBAAwB,CAAA;AAO/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB;8EAC0E;IAC1E,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,IAAI,CAAA;IACvC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;CACpB,CAAA;AAKD,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;AAClG,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA"}
|