parseman 0.60.0 → 0.60.2
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/diagnostics.js +3 -3
- package/dist/analysis/diagnostics.js.map +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/plugin/index.d.ts +18 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +115 -47
- package/dist/plugin/index.js.map +1 -1
- package/dist/table/assemble.d.ts +1 -1
- package/dist/table/compile-rule-map.d.ts +1 -1
- package/dist/table/compile.d.ts +1 -1
- package/dist/table/exec.d.ts +0 -35
- package/dist/table/exec.d.ts.map +1 -1
- package/dist/table/index.js +3 -3
- package/dist/table/index.js.map +1 -1
- package/dist/table/ops.d.ts +1 -10
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +2 -4
- package/src/plugin/index.ts +189 -63
- package/src/table/assemble.ts +5 -5
- package/src/table/compile-rule-map.ts +1 -1
- package/src/table/compile.ts +1 -1
- package/src/table/emit-assembly.ts +2 -2
- package/src/table/encode.ts +1 -1
- package/src/table/exec.ts +0 -69
- package/src/table/ops.ts +1 -10
- package/src/version.ts +1 -1
- package/dist/table/encode-baseline.d.ts +0 -16
- package/dist/table/encode-baseline.d.ts.map +0 -1
- package/dist/table/exec-baseline.d.ts +0 -9
- package/dist/table/exec-baseline.d.ts.map +0 -1
- package/src/table/encode-baseline.ts +0 -346
- package/src/table/exec-baseline.ts +0 -557
package/dist/table/ops.d.ts
CHANGED
|
@@ -73,13 +73,7 @@ export declare const OP_XFORM = 9;
|
|
|
73
73
|
* from the reducer's declared arity by the same analysis codegen runs, and
|
|
74
74
|
* forced on under `hostMode: 'cst'`. The driver reads the bits and re-derives
|
|
75
75
|
* nothing.
|
|
76
|
-
|
|
77
|
-
* TWO ENCODERS SHARE THIS OPCODE and they do NOT agree on its length. The layout
|
|
78
|
-
* above is `encode.ts` / `exec.ts`. `encode-baseline.ts` / `exec-baseline.ts` emit
|
|
79
|
-
* the FOUR-word form `NODE b c flags` with bits 2 and 3 only, and refuse
|
|
80
|
-
* `collapse` / `unwrap` / `project` outright. Neither driver walks the stream by
|
|
81
|
-
* instruction length, so the divergence is contained — but read the pair you are
|
|
82
|
-
* in, not this comment alone. */
|
|
76
|
+
*/
|
|
83
77
|
export declare const OP_NODE = 10;
|
|
84
78
|
/** `RULE r` — `r` indexes `prog.rules`. */
|
|
85
79
|
export declare const OP_RULE = 11;
|
|
@@ -92,9 +86,6 @@ export declare const OP_NOT = 13;
|
|
|
92
86
|
* `['peek(<child tag>)']`, because `peek.ts:60` DISCARDS the body's expectation:
|
|
93
87
|
* a lookahead's failure is "the guard did not hold", not a request for whatever
|
|
94
88
|
* token the body happened to stop on.
|
|
95
|
-
*
|
|
96
|
-
* `encode-baseline.ts` / `exec-baseline.ts` emit and read the two-word form and
|
|
97
|
-
* are internally consistent; read the pair you are in.
|
|
98
89
|
*/
|
|
99
90
|
export declare const OP_PEEK = 14;
|
|
100
91
|
/** `LEAF f c` — `leaf()`: reduce, then record as a spanned leaf. */
|
package/dist/table/ops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../../src/table/ops.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,6FAA6F;AAC7F,eAAO,MAAM,MAAM,IAAI,CAAA;AACvB,kFAAkF;AAClF,eAAO,MAAM,KAAK,IAAI,CAAA;AACtB,6EAA6E;AAC7E,eAAO,MAAM,MAAM,IAAI,CAAA;AACvB,iFAAiF;AACjF,eAAO,MAAM,OAAO,IAAI,CAAA;AACxB;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,IAAI,CAAA;AAC1B;;;;;;;;;;GAUG;AACH,eAAO,MAAM,MAAM,IAAI,CAAA;AACvB,2CAA2C;AAC3C,eAAO,MAAM,OAAO,IAAI,CAAA;AACxB;;;;kDAIkD;AAClD,eAAO,MAAM,MAAM,IAAI,CAAA;AACvB,qEAAqE;AACrE,eAAO,MAAM,QAAQ,IAAI,CAAA;AACzB
|
|
1
|
+
{"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../../src/table/ops.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
* Kept in sync with `package.json` by `test/unit/version-sync.test.ts`, which fails
|
|
14
14
|
* the build if this constant drifts from the published version.
|
|
15
15
|
*/
|
|
16
|
-
export declare const PARSEMAN_VERSION = "0.60.
|
|
16
|
+
export declare const PARSEMAN_VERSION = "0.60.2";
|
|
17
17
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "parseman",
|
|
3
|
-
"version": "0.60.
|
|
3
|
+
"version": "0.60.2",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^20.19.0 || >=22.13.0"
|
|
@@ -84,13 +84,10 @@
|
|
|
84
84
|
"bench": "node --import tsx/esm bench/run.ts",
|
|
85
85
|
"bench:parseman": "node --import tsx/esm bench/parseman.ts",
|
|
86
86
|
"bench:closure-json": "node --import tsx/esm bench/closure-json-path.ts",
|
|
87
|
-
"bench:composeleaf": "node --import tsx/esm bench/composeleaf-firstset.ts",
|
|
88
87
|
"bench:incremental": "node --import tsx/esm bench/incremental-run.ts",
|
|
89
88
|
"bench:literal": "node --import tsx/esm bench/literal-match.ts",
|
|
90
89
|
"bench:dispatch": "node --import tsx/esm bench/dispatch-vs-choice.ts",
|
|
91
|
-
"bench:codegen": "node --import tsx/esm bench/codegen-ab.ts",
|
|
92
90
|
"bench:treeidentity": "node --import tsx/esm bench/tree-identity.ts",
|
|
93
|
-
"bench:sharedprefix": "node --import tsx/esm bench/shared-prefix-ab.ts",
|
|
94
91
|
"bench:baseline": "node --import tsx/esm bench/update-parseman-baseline.ts",
|
|
95
92
|
"perf:guard": "node --import tsx/esm bench/perf-guard.ts",
|
|
96
93
|
"perf:guard:grammars": "node --import tsx/esm bench/grammar-perf-guard.ts",
|
|
@@ -109,6 +106,7 @@
|
|
|
109
106
|
"bench:release-compare-svg": "node --import tsx/esm bench/gen-release-compare-svg.ts",
|
|
110
107
|
"bench:size": "node --import tsx/esm bench/measure-expansion.ts",
|
|
111
108
|
"docs:favicons": "node scripts/gen-favicons.mjs",
|
|
109
|
+
"docs:railroad": "node --import tsx/esm scripts/gen-railroad-svg.ts",
|
|
112
110
|
"build:chevrotain-bench": "node --import tsx/esm scripts/build-chevrotain-bench.mjs",
|
|
113
111
|
"bench:compile-grammars": "peggy --format es -o bench/json-parser.js bench/json.pegjs && peggy --format es -o bench/csv-parser.js bench/csv.pegjs && peggy --format es -o bench/graphql-parser.js bench/graphql.pegjs && nearleyc bench/vendor/nearley/json.ne -o bench/json-nearley.cjs && nearleyc bench/vendor/nearley/csv.ne -o bench/csv-nearley.cjs && nearleyc bench/vendor/nearley/graphql.ne -o bench/graphql-nearley.cjs && node bench/compile-jison.mjs",
|
|
114
112
|
"perf:workloads": "node --import tsx/esm bench/workload-perf-guard.ts",
|
package/src/plugin/index.ts
CHANGED
|
@@ -83,29 +83,60 @@ const TABLE_RUNTIME_SPECIFIER = 'parseman/table'
|
|
|
83
83
|
* from the artifact's own location.
|
|
84
84
|
*/
|
|
85
85
|
export type CarriedSidecarItem = IRPiece | { from: string; name: string }
|
|
86
|
+
/**
|
|
87
|
+
* A recorded item: a reference also carries the pieces it stands for, expanded, so a
|
|
88
|
+
* build that BUNDLES the referenced package rather than keeping it external writes
|
|
89
|
+
* those pieces instead of a reference to a package its artifact no longer imports
|
|
90
|
+
* (`emitCarriedSidecars`).
|
|
91
|
+
*/
|
|
92
|
+
export type CarriedSinkItem = IRPiece | { from: string; name: string; expanded?: readonly CarriedSidecarItem[] }
|
|
86
93
|
/** One module's carried lists, by export name. */
|
|
87
|
-
export type CarriedSink = Map<string,
|
|
94
|
+
export type CarriedSink = Map<string, CarriedSinkItem[]>
|
|
88
95
|
export const CARRIED_SIDECAR_SUFFIX = '.parseman.json'
|
|
89
|
-
|
|
96
|
+
/**
|
|
97
|
+
* `artifact` is `artifactDigest` of the chunk the sidecar was written beside. A
|
|
98
|
+
* reader that finds a different artifact there is reading a sidecar some other
|
|
99
|
+
* build left behind, and fails. A sidecar without one predates the check.
|
|
100
|
+
*/
|
|
101
|
+
type CarriedSidecar = { parseman: string; artifact?: string; exports: Record<string, CarriedSidecarItem[]> }
|
|
90
102
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
/**
|
|
104
|
+
* The digest a sidecar records of its artifact: SHA-256 of the chunk's code, less a
|
|
105
|
+
* trailing `sourceMappingURL` comment, which a bundler appends after the hook that
|
|
106
|
+
* writes the sidecar has seen the code.
|
|
107
|
+
*/
|
|
108
|
+
export function artifactDigest(code: string): string {
|
|
109
|
+
const body = code.replace(/\n?\/\/# sourceMappingURL=[^\n]*\n?$/, '')
|
|
110
|
+
return createHash('sha256').update(body).digest('hex').slice(0, 32)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
type SidecarRead = { sidecar: CarriedSidecar; stale: boolean } | null
|
|
114
|
+
const _sidecarCache = new Map<string, { mtimeMs: number; artifactMtimeMs: number; read: SidecarRead }>()
|
|
115
|
+
/**
|
|
116
|
+
* The carried-pieces sidecar written beside a compiled artifact, or null; `stale`
|
|
117
|
+
* when it records a digest the artifact beside it does not have.
|
|
118
|
+
*/
|
|
119
|
+
function readCarriedSidecar(artifact: string): SidecarRead {
|
|
94
120
|
const file = artifact + CARRIED_SIDECAR_SUFFIX
|
|
95
121
|
let mtimeMs: number
|
|
96
|
-
|
|
122
|
+
let artifactMtimeMs: number
|
|
123
|
+
try { mtimeMs = fs.statSync(file).mtimeMs; artifactMtimeMs = fs.statSync(artifact).mtimeMs } catch { return null }
|
|
97
124
|
const hit = _sidecarCache.get(file)
|
|
98
|
-
if (hit && hit.mtimeMs === mtimeMs) return hit.
|
|
99
|
-
let
|
|
125
|
+
if (hit && hit.mtimeMs === mtimeMs && hit.artifactMtimeMs === artifactMtimeMs) return hit.read
|
|
126
|
+
let read: SidecarRead = null
|
|
100
127
|
try {
|
|
101
128
|
// A file without an `exports` object is not a sidecar this plugin wrote: treat
|
|
102
129
|
// it as absent, so the inline literal is looked for and a missing one reports.
|
|
103
130
|
const parsed: unknown = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
104
131
|
const exports = typeof parsed === 'object' && parsed !== null ? (parsed as { exports?: unknown }).exports : undefined
|
|
105
|
-
if (typeof exports === 'object' && exports !== null)
|
|
132
|
+
if (typeof exports === 'object' && exports !== null) {
|
|
133
|
+
const sidecar = parsed as CarriedSidecar
|
|
134
|
+
const stale = typeof sidecar.artifact === 'string' && sidecar.artifact !== artifactDigest(fs.readFileSync(artifact, 'utf8'))
|
|
135
|
+
read = { sidecar, stale }
|
|
136
|
+
}
|
|
106
137
|
} catch { /* unreadable: no sidecar */ }
|
|
107
|
-
_sidecarCache.set(file, { mtimeMs,
|
|
108
|
-
return
|
|
138
|
+
_sidecarCache.set(file, { mtimeMs, artifactMtimeMs, read })
|
|
139
|
+
return read
|
|
109
140
|
}
|
|
110
141
|
|
|
111
142
|
export type ParsecraftPluginOptions = {
|
|
@@ -360,7 +391,9 @@ function parseModuleCached(filePath: string): ParsedModule | null {
|
|
|
360
391
|
}
|
|
361
392
|
|
|
362
393
|
/** The rollup-family hook that writes each chunk's carried pieces beside it. */
|
|
363
|
-
type SidecarBundle = Record<string, { type: string; fileName: string; exports?: string[]; moduleIds?: string[]; facadeModuleId?: string | null }>
|
|
394
|
+
type SidecarBundle = Record<string, { type: string; fileName: string; code?: string; exports?: string[]; moduleIds?: string[]; facadeModuleId?: string | null }>
|
|
395
|
+
/** A module's recorded list for one export, and the module that recorded it. */
|
|
396
|
+
type Recorded = { readonly items: readonly CarriedSinkItem[]; readonly recorder: string }
|
|
364
397
|
type SidecarEmitter = {
|
|
365
398
|
emitFile: (file: { type: 'asset'; fileName: string; source: string }) => unknown
|
|
366
399
|
getModuleInfo: (id: string) => { code: string | null } | null
|
|
@@ -368,77 +401,131 @@ type SidecarEmitter = {
|
|
|
368
401
|
}
|
|
369
402
|
|
|
370
403
|
/**
|
|
371
|
-
* Where a module's export `exported` comes from
|
|
372
|
-
*
|
|
373
|
-
*
|
|
404
|
+
* Where a module's export `exported` comes from: a re-export, possibly renamed
|
|
405
|
+
* (`export { base as renamed } from './grammar'`, or an imported binding exported
|
|
406
|
+
* under another name); the module's own declaration (`'local'`); or, when the
|
|
407
|
+
* module neither declares nor names it, its `export * from` sources, in source
|
|
408
|
+
* order (none: a form this does not read, such as `export default`).
|
|
374
409
|
*/
|
|
375
|
-
|
|
410
|
+
type ExportOrigin = { readonly from: string; readonly imported: string } | { readonly stars: readonly string[] } | 'local'
|
|
411
|
+
|
|
412
|
+
function exportOrigin(body: AnyNode[], exported: string): ExportOrigin {
|
|
413
|
+
const stars: string[] = []
|
|
376
414
|
for (const st of body) {
|
|
415
|
+
if (st.type === 'ExportAllDeclaration') {
|
|
416
|
+
const from = (st.source as { value?: string } | null | undefined)?.value
|
|
417
|
+
if (st.exported == null && from !== undefined) stars.push(from)
|
|
418
|
+
continue
|
|
419
|
+
}
|
|
377
420
|
if (st.type !== 'ExportNamedDeclaration') continue
|
|
421
|
+
const decl = st.declaration as AnyNode | null | undefined
|
|
422
|
+
if (decl != null) {
|
|
423
|
+
const ids = decl.type === 'VariableDeclaration'
|
|
424
|
+
? ((decl.declarations as AnyNode[] | undefined) ?? []).map(d => (d.id as { name?: string } | undefined)?.name)
|
|
425
|
+
: [(decl.id as { name?: string } | null | undefined)?.name]
|
|
426
|
+
if (ids.includes(exported)) return 'local'
|
|
427
|
+
}
|
|
378
428
|
for (const sp of (st.specifiers as AnyNode[] | undefined) ?? []) {
|
|
379
429
|
const exp = sp.exported as { name?: string; value?: string } | undefined
|
|
380
430
|
if ((exp?.name ?? exp?.value) !== exported) continue
|
|
381
431
|
const local = sp.local as { name?: string; value?: string } | undefined
|
|
382
432
|
const name = local?.name ?? local?.value
|
|
383
|
-
if (name === undefined) return
|
|
433
|
+
if (name === undefined) return 'local'
|
|
384
434
|
const from = (st.source as { value?: string } | null | undefined)?.value
|
|
385
|
-
|
|
435
|
+
if (from !== undefined) return { from, imported: name }
|
|
436
|
+
const imported = extractImportBindings(body).get(name)
|
|
437
|
+
return imported === undefined ? 'local' : { from: imported.source, imported: imported.imported }
|
|
386
438
|
}
|
|
387
439
|
}
|
|
388
|
-
return
|
|
440
|
+
return { stars }
|
|
389
441
|
}
|
|
390
442
|
|
|
391
443
|
/**
|
|
392
444
|
* The carried list behind an entry chunk's export, followed from the entry module
|
|
393
|
-
* through its re-exports to the module that
|
|
394
|
-
* exports it as. A chunk's export names are its entry
|
|
395
|
-
* renames a grammar (`export { base as renamed } from
|
|
396
|
-
* pieces recorded as `base`.
|
|
445
|
+
* through its re-exports (named or `export *`) to the module that declares it,
|
|
446
|
+
* under the name that module exports it as. A chunk's export names are its entry
|
|
447
|
+
* module's, so an entry that renames a grammar (`export { base as renamed } from
|
|
448
|
+
* './grammar'`) still finds the pieces recorded as `base`.
|
|
449
|
+
*
|
|
450
|
+
* `null` when the export is traced to a module that recorded nothing under that
|
|
451
|
+
* name — a precompiled dependency, or a value that is not a grammar: it has no
|
|
452
|
+
* carried pieces, and no other module's may stand in for them. `undefined` when
|
|
453
|
+
* the trace cannot be completed (no module code, an external, a parse error).
|
|
397
454
|
*/
|
|
398
|
-
async function followCarried(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
455
|
+
async function followCarried(
|
|
456
|
+
this: SidecarEmitter, sinks: Map<string, CarriedSink>, moduleId: string, name: string, depth = 0,
|
|
457
|
+
): Promise<Recorded | null | undefined> {
|
|
458
|
+
if (depth > 64) return undefined
|
|
459
|
+
const own = sinks.get(moduleId)?.get(name)
|
|
460
|
+
if (own !== undefined) return { items: own, recorder: moduleId }
|
|
461
|
+
const code = this.getModuleInfo(moduleId)?.code
|
|
462
|
+
if (!code) return undefined
|
|
463
|
+
const parsed = parseSync(moduleId, code)
|
|
464
|
+
if (parsed.errors.length > 0) return undefined
|
|
465
|
+
const origin = exportOrigin(parsed.program.body as unknown as AnyNode[], name)
|
|
466
|
+
if (origin === 'local') return null
|
|
467
|
+
const follow = async (source: string, imported: string): Promise<Recorded | null | undefined> => {
|
|
468
|
+
const resolved = await this.resolve(source, moduleId)
|
|
409
469
|
if (resolved === null || resolved.external) return undefined
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
470
|
+
return followCarried.call(this, sinks, resolved.id, imported, depth + 1)
|
|
471
|
+
}
|
|
472
|
+
if ('from' in origin) return follow(origin.from, origin.imported)
|
|
473
|
+
// `export *` re-exports a name only from the one star source that has it; two
|
|
474
|
+
// that reach different declarations make it ambiguous, and ESM exports neither.
|
|
475
|
+
// Two paths to the same declaration are one binding.
|
|
476
|
+
let found: Recorded | undefined
|
|
477
|
+
let unknown = false
|
|
478
|
+
for (const source of origin.stars) {
|
|
479
|
+
const got = await follow(source, name)
|
|
480
|
+
if (got === undefined) unknown = true
|
|
481
|
+
else if (got !== null) {
|
|
482
|
+
if (found !== undefined && found.items !== got.items) return null
|
|
483
|
+
found = got
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return found ?? (unknown || origin.stars.length === 0 ? undefined : null)
|
|
414
487
|
}
|
|
415
488
|
|
|
416
489
|
/**
|
|
417
490
|
* Write `<chunk>.parseman.json` for every chunk that exports a grammar with carried
|
|
418
491
|
* pieces. An entry chunk's export is followed from its entry module to the module
|
|
419
|
-
* that
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
* and
|
|
423
|
-
* artifact.
|
|
492
|
+
* that declares it (`followCarried`). An export that cannot be followed is matched
|
|
493
|
+
* by name to the one module of the chunk itself that recorded it. Nothing else is
|
|
494
|
+
* written: a module outside the chunk recording the same name is not the export's
|
|
495
|
+
* source, and a downstream compose of an export with no pieces fails its build
|
|
496
|
+
* naming the artifact.
|
|
424
497
|
*/
|
|
425
498
|
async function emitCarriedSidecars(this: SidecarEmitter, sinks: Map<string, CarriedSink>, bundle: SidecarBundle): Promise<void> {
|
|
426
|
-
|
|
427
|
-
|
|
499
|
+
// A reference names an export of another package, resolved from the artifact's
|
|
500
|
+
// location. That holds only where this build keeps the package EXTERNAL; a
|
|
501
|
+
// bundled one is in the chunk and may be missing, or a different version,
|
|
502
|
+
// wherever the artifact is composed. So a bundled reference is written expanded.
|
|
503
|
+
const write = async (rec: Recorded): Promise<CarriedSidecarItem[]> => {
|
|
504
|
+
const out: CarriedSidecarItem[] = []
|
|
505
|
+
for (const item of rec.items) {
|
|
506
|
+
if ('ir' in item) { out.push(item); continue }
|
|
507
|
+
const resolved = item.expanded === undefined ? null : await this.resolve(item.from, rec.recorder)
|
|
508
|
+
if (resolved !== null && !resolved.external) out.push(...item.expanded!)
|
|
509
|
+
else out.push({ from: item.from, name: item.name })
|
|
510
|
+
}
|
|
511
|
+
return out
|
|
512
|
+
}
|
|
428
513
|
for (const chunk of Object.values(bundle)) {
|
|
429
514
|
if (chunk.type !== 'chunk') continue
|
|
430
515
|
const exports: Record<string, CarriedSidecarItem[]> = {}
|
|
431
516
|
for (const name of chunk.exports ?? []) {
|
|
432
517
|
const followed = chunk.facadeModuleId ? await followCarried.call(this, sinks, chunk.facadeModuleId, name) : undefined
|
|
433
|
-
if (followed
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const moduleId = own.length === 1 ? own[0] : own.length === 0 && ids.length === 1 ? ids[0] : undefined
|
|
438
|
-
if (moduleId !== undefined) exports[name] = sinks.get(moduleId)!.get(name)!
|
|
518
|
+
if (followed === null) continue
|
|
519
|
+
if (followed !== undefined) { exports[name] = await write(followed); continue }
|
|
520
|
+
const own = (chunk.moduleIds ?? []).filter(m => sinks.get(m)?.has(name) === true)
|
|
521
|
+
if (own.length === 1) exports[name] = await write({ items: sinks.get(own[0]!)!.get(name)!, recorder: own[0]! })
|
|
439
522
|
}
|
|
440
523
|
if (Object.keys(exports).length === 0) continue
|
|
441
|
-
const sidecar: CarriedSidecar = {
|
|
524
|
+
const sidecar: CarriedSidecar = {
|
|
525
|
+
parseman: PARSEMAN_VERSION,
|
|
526
|
+
...(chunk.code === undefined ? {} : { artifact: artifactDigest(chunk.code) }),
|
|
527
|
+
exports,
|
|
528
|
+
}
|
|
442
529
|
this.emitFile({ type: 'asset', fileName: chunk.fileName + CARRIED_SIDECAR_SUFFIX, source: JSON.stringify(sidecar) })
|
|
443
530
|
}
|
|
444
531
|
}
|
|
@@ -449,8 +536,14 @@ export default createUnplugin((opts: ParsecraftPluginOptions = {}, meta) => {
|
|
|
449
536
|
// Module id → its carried lists, under a bundler that writes them beside the chunk.
|
|
450
537
|
// Any other bundler gets them inline, as before.
|
|
451
538
|
const sinks = SIDECAR_FRAMEWORKS.has(meta.framework) ? new Map<string, CarriedSink>() : undefined
|
|
539
|
+
// `order: 'post'`: the sidecar records a digest of each chunk's code, so it is
|
|
540
|
+
// taken after every other plugin's `generateBundle` has rewritten that code (Vite
|
|
541
|
+
// rewrites a dynamic `import()` through its preload helper there).
|
|
452
542
|
const generateBundle = sinks === undefined ? undefined : {
|
|
453
|
-
generateBundle
|
|
543
|
+
generateBundle: {
|
|
544
|
+
order: 'post' as const,
|
|
545
|
+
handler(this: SidecarEmitter, _options: unknown, bundle: SidecarBundle) { return emitCarriedSidecars.call(this, sinks, bundle) },
|
|
546
|
+
},
|
|
454
547
|
}
|
|
455
548
|
return {
|
|
456
549
|
name: 'parseman',
|
|
@@ -465,6 +558,9 @@ export default createUnplugin((opts: ParsecraftPluginOptions = {}, meta) => {
|
|
|
465
558
|
},
|
|
466
559
|
|
|
467
560
|
transform(this: { warn?: (msg: string) => void; addWatchFile?: (file: string) => void }, code: string, id: string) {
|
|
561
|
+
// What this module recorded on an earlier build of a watch session is stale
|
|
562
|
+
// from here: it records again below, or it no longer lowers anything.
|
|
563
|
+
sinks?.delete(id)
|
|
468
564
|
if (!code.includes('parseman')) return null
|
|
469
565
|
if (!code.includes('macro')) return null
|
|
470
566
|
// A corpus edit changes which sites a profiled build generates, so a watch
|
|
@@ -473,10 +569,7 @@ export default createUnplugin((opts: ParsecraftPluginOptions = {}, meta) => {
|
|
|
473
569
|
const moduleAliases = new Set([PARSEMAN_MODULE, ...(opts.moduleAliases ?? [])])
|
|
474
570
|
const sink: CarriedSink | undefined = sinks && new Map()
|
|
475
571
|
const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true, opts.recovery === true, opts.grammarCoverage === true, opts.assemblyProfile, opts.assemblyBudget, sink)
|
|
476
|
-
if (sinks && sink)
|
|
477
|
-
if (sink.size > 0) sinks.set(id, sink)
|
|
478
|
-
else sinks.delete(id)
|
|
479
|
-
}
|
|
572
|
+
if (sinks && sink && sink.size > 0) sinks.set(id, sink)
|
|
480
573
|
if (result?.warnings.length) {
|
|
481
574
|
for (const w of result.warnings) {
|
|
482
575
|
if (typeof this?.warn === 'function') this.warn(`[parseman] ${w}`)
|
|
@@ -1797,7 +1890,13 @@ function transformMacroImpl(
|
|
|
1797
1890
|
const fail = (why: string): null => { unresolved.why ??= `${exportName} in ${file}: ${why}`; return null }
|
|
1798
1891
|
// A build-only sidecar wins over an inline literal: an artifact that has one
|
|
1799
1892
|
// carries no literal (see `CarriedSidecarItem`).
|
|
1800
|
-
const
|
|
1893
|
+
const sidecarRead = module.source || local ? null : readCarriedSidecar(file)
|
|
1894
|
+
if (sidecarRead?.stale === true) {
|
|
1895
|
+
return fail(`${path.basename(file)}${CARRIED_SIDECAR_SUFFIX} was written for a different build of ${path.basename(file)} `
|
|
1896
|
+
+ '(the artifact digest it records does not match). Rebuild the package so the two are written together')
|
|
1897
|
+
}
|
|
1898
|
+
const sidecarExports = sidecarRead?.sidecar.exports
|
|
1899
|
+
const sidecarItems = sidecarExports !== undefined && Object.hasOwn(sidecarExports, exportName) ? sidecarExports[exportName] : undefined
|
|
1801
1900
|
if (sidecarItems !== undefined) {
|
|
1802
1901
|
const list: RawItem[] = []
|
|
1803
1902
|
for (const item of sidecarItems) {
|
|
@@ -1821,10 +1920,32 @@ function transformMacroImpl(
|
|
|
1821
1920
|
if (!literalRange) {
|
|
1822
1921
|
// A compiled grammar with neither a literal nor a sidecar entry was built to be
|
|
1823
1922
|
// composed or it was not, and a runtime compose() cannot link it either way.
|
|
1824
|
-
|
|
1825
|
-
|
|
1923
|
+
if (initNode !== null && !module.source && mod.src.slice(initNode.start, initNode.end).includes('tableRules(')) {
|
|
1924
|
+
return fail(`is a compiled grammar with no carried pieces — no inline literal, and no entry in ${path.basename(file)}${CARRIED_SIDECAR_SUFFIX}. `
|
|
1826
1925
|
+ 'Rebuild it with this parseman, and keep that file beside the artifact')
|
|
1827
|
-
|
|
1926
|
+
}
|
|
1927
|
+
// A COMPILED ARTIFACT RE-EXPORTING ITS GRAMMAR — a bundler's facade chunk,
|
|
1928
|
+
// `import { t as grammar } from './shared-HASH.js'; export { grammar }`, or an
|
|
1929
|
+
// `export *` entry. Follow the export to the module that declares it. A
|
|
1930
|
+
// compiled artifact whose export cannot be followed to carried pieces fails
|
|
1931
|
+
// the build: falling back to a runtime compose() of it would throw at import.
|
|
1932
|
+
if (module.source || local) return null
|
|
1933
|
+
const origin = exportOrigin(mod.body as AnyNode[], exportName)
|
|
1934
|
+
const targets = origin === 'local' ? [] : 'from' in origin
|
|
1935
|
+
? [{ source: origin.from, imported: origin.imported }]
|
|
1936
|
+
: origin.stars.map(source => ({ source, imported: exportName }))
|
|
1937
|
+
for (const target of targets) {
|
|
1938
|
+
const sub = resolveGrammarModule(file, target.source)
|
|
1939
|
+
if (sub === null || seen.has(sub.file)) continue
|
|
1940
|
+
const pieces = resolveModulePieces(sub, target.imported, new Set(seen).add(sub.file), unresolved)
|
|
1941
|
+
if (pieces !== null) return pieces
|
|
1942
|
+
}
|
|
1943
|
+
// Neither this module nor anything it re-exports from is a compiled grammar:
|
|
1944
|
+
// not a build-compiled argument, so the caller decides.
|
|
1945
|
+
if (unresolved.why === undefined && !mod.src.includes(TABLE_RUNTIME_SPECIFIER)) return null
|
|
1946
|
+
return fail(`is exported by a compiled grammar artifact, but no carried pieces could be found for it — no inline literal, `
|
|
1947
|
+
+ `no entry in ${path.basename(file)}${CARRIED_SIDECAR_SUFFIX}, and no re-export of it that has either. `
|
|
1948
|
+
+ 'Rebuild it with this parseman, and keep each chunk\'s .parseman.json beside it')
|
|
1828
1949
|
}
|
|
1829
1950
|
const literal = mod.src.slice(literalRange.start, literalRange.end)
|
|
1830
1951
|
|
|
@@ -1909,12 +2030,17 @@ function transformMacroImpl(
|
|
|
1909
2030
|
*/
|
|
1910
2031
|
const sinkCarried = (name: string, list: CarriedItem[]): boolean => {
|
|
1911
2032
|
if (carriedSink === undefined) return false
|
|
1912
|
-
const out:
|
|
2033
|
+
const out: CarriedSinkItem[] = []
|
|
1913
2034
|
for (const it of list) {
|
|
1914
2035
|
if (!isSpread(it)) { out.push(it); continue }
|
|
1915
2036
|
const b = importBindings.get(it.__spreadLocal)
|
|
1916
2037
|
if (b === undefined) return false
|
|
1917
|
-
if (!b.source.startsWith('.') && !path.isAbsolute(b.source)) {
|
|
2038
|
+
if (!b.source.startsWith('.') && !path.isAbsolute(b.source)) {
|
|
2039
|
+
// Expanded too, for a build that bundles the package (`emitCarriedSidecars`).
|
|
2040
|
+
const expanded = importedPieces(it.__spreadLocal)
|
|
2041
|
+
out.push({ from: b.source, name: b.imported, ...(expanded === null ? {} : { expanded: expanded as CarriedSidecarItem[] }) })
|
|
2042
|
+
continue
|
|
2043
|
+
}
|
|
1918
2044
|
const pieces = importedPieces(it.__spreadLocal)
|
|
1919
2045
|
if (pieces === null) return false
|
|
1920
2046
|
out.push(...pieces)
|
package/src/table/assemble.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* making the swaps on rules / sub-rules (leaves) at that point, then run with NO
|
|
6
6
|
* logic branching for that option input"*. `exec.ts` builds the same table and
|
|
7
7
|
* then interprets it: one `switch (code[ip])` over every opcode (29 then), executed ONCE PER
|
|
8
|
-
* ROW — 497,360 rows for `benchmark.less` (`
|
|
8
|
+
* ROW — 497,360 rows for `benchmark.less` (measured with a one-off `g5-rows` bench, last at `4a5d0341`) — each
|
|
9
9
|
* re-reading its opcode and re-decoding its operands from the `Int32Array`, and
|
|
10
10
|
* re-testing the same per-parse options. That is the per-node branching the
|
|
11
11
|
* design exists to remove.
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
*
|
|
21
21
|
* The numbers that make this obviously the right trade, measured not assumed:
|
|
22
22
|
*
|
|
23
|
-
* 2,241 distinct reachable sites in the less table (`
|
|
23
|
+
* 2,241 distinct reachable sites in the less table (`g5-sites`)
|
|
24
24
|
* 497,360 rows executed for one parse of `benchmark.less`
|
|
25
25
|
*
|
|
26
26
|
* Pieces are GRAMMAR-sized; rows are INPUT-sized. Assembly allocates ~2.2k
|
|
27
27
|
* closures once per process and removes a dispatch plus an operand decode from
|
|
28
28
|
* ~497k executions — a 222x ratio. Assembly cost is paid once and is not the
|
|
29
|
-
* metric; it is measured anyway (`
|
|
29
|
+
* metric; it is measured anyway (`g5-ms`, `g5-profile`).
|
|
30
30
|
*
|
|
31
31
|
* ## Why this is not "just tuning the switch"
|
|
32
32
|
*
|
|
@@ -4298,7 +4298,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
4298
4298
|
// Two engines that can only be compared across two checkouts cannot be
|
|
4299
4299
|
// compared at all: the bench harness's own guidance is that a
|
|
4300
4300
|
// cross-worktree measurement carries a bias no repetition removes. Same
|
|
4301
|
-
// spelling as `PM_TABLE_COUNT`
|
|
4301
|
+
// spelling as the removed `PM_TABLE_COUNT` counters.
|
|
4302
4302
|
if (!EMIT_ENABLED) throw new Unemittable('PM_TABLE_EMIT=0 (measurement toggle)')
|
|
4303
4303
|
const em = emitAssemblySource(t, prog, cfg, extraIps)
|
|
4304
4304
|
/**
|
|
@@ -4569,7 +4569,7 @@ export class AssemblyCache {
|
|
|
4569
4569
|
* That is not a hole in G5, it is G5's own first clause: "quickly building the
|
|
4570
4570
|
* grammar reference ON RUN START, making some swaps on rules or sub-rules, and
|
|
4571
4571
|
* then the run actually runs with no logic branching for that option input"
|
|
4572
|
-
* (`notes/TABLE-DRIVER.md`). This IS the run-start step. What the criterion
|
|
4572
|
+
* (`notes/history/TABLE-DRIVER.md`). This IS the run-start step. What the criterion
|
|
4573
4573
|
* forbids is the second sentence, and past this call there is no option read
|
|
4574
4574
|
* anywhere — `scripts/check-invariants.mjs` INV-6 decides that mechanically.
|
|
4575
4575
|
*
|
|
@@ -68,7 +68,7 @@ export type TableRuleMapOptions = {
|
|
|
68
68
|
* `coverage: grammarCoverage` here for every `rules()` grammar — so this is what
|
|
69
69
|
* makes `{ grammarCoverage: true }` a working build under the table lowering.
|
|
70
70
|
*
|
|
71
|
-
* COUNTERS ONLY, per the owner ruling recorded in `notes/RELEASE-0.48-TARGET.md`
|
|
71
|
+
* COUNTERS ONLY, per the owner ruling recorded in `notes/history/RELEASE-0.48-TARGET.md`
|
|
72
72
|
* §1; `_grammarTrace` parity is 0.48 work. The plugin's coverage path needs the
|
|
73
73
|
* DEFINITIONS STAMP and the HITS, and both are here.
|
|
74
74
|
*/
|
package/src/table/compile.ts
CHANGED
|
@@ -90,7 +90,7 @@ export type TableCompileOptions = {
|
|
|
90
90
|
*
|
|
91
91
|
* COUNTERS ONLY — owner ruling. `_grammarTrace`'s six phases are emitted at
|
|
92
92
|
* ~40 fine-grained sites by codegen and are deferred to 0.48
|
|
93
|
-
* (`notes/RELEASE-0.48-TARGET.md` §1). A table entry rejects a context carrying
|
|
93
|
+
* (`notes/history/RELEASE-0.48-TARGET.md` §1). A table entry rejects a context carrying
|
|
94
94
|
* a trace sink before parsing: silently returning an empty trace would look
|
|
95
95
|
* exactly like a real trace whose grammar sites were never visited. The
|
|
96
96
|
* plugin's coverage path reads `_grammarCoverage` and the definitions stamp,
|
|
@@ -1400,8 +1400,8 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
1400
1400
|
}
|
|
1401
1401
|
|
|
1402
1402
|
case OP_EMPTY:
|
|
1403
|
-
// `''`, not `null` — the zero-width match's value. The other
|
|
1404
|
-
// (exec.ts,
|
|
1403
|
+
// `''`, not `null` — the zero-width match's value. The other engines
|
|
1404
|
+
// (exec.ts, assemble.ts) both return `''`; this one returned
|
|
1405
1405
|
// `null`, so the SAME program yielded a different `value` depending only on
|
|
1406
1406
|
// which engine ran. Unreached from the combinator API today — `OP_EMPTY` is
|
|
1407
1407
|
// emitted only as `finish()` padding for an EMPTY rule map, which then has no
|
package/src/table/encode.ts
CHANGED
|
@@ -783,7 +783,7 @@ class Encoder {
|
|
|
783
783
|
* simply parses it with the gate switched off at every choice arm that reaches
|
|
784
784
|
* a hole.
|
|
785
785
|
*
|
|
786
|
-
* Measured on jess's less grammar (`
|
|
786
|
+
* Measured on jess's less grammar (with the one-off `macro-program-diff` bench, last at `4a5d0341`): 195 of
|
|
787
787
|
* 562 reachable choice arms carried NO first set in the shipped artifact
|
|
788
788
|
* against 103 of 540 in the runtime encode, and 10 dispatch sites lost the O(1)
|
|
789
789
|
* `exclusive` piece (`assemble.ts:1758`) for the candidate-mask loop. With this
|