parseman 0.60.3 → 0.60.4
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 +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/compiler/ir-serialize.d.ts +3 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +10 -0
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/index.js +28 -15
- package/dist/index.js.map +1 -1
- package/dist/plugin/evaluator.d.ts +8 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +91 -17
- package/dist/plugin/index.js.map +2 -2
- package/dist/table/index.js +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/compiler/ir-serialize.ts +14 -3
- package/src/compiler/linker.ts +28 -12
- package/src/plugin/evaluator.ts +11 -0
- package/src/plugin/index.ts +29 -11
- package/src/version.ts +1 -1
package/dist/table/index.js
CHANGED
|
@@ -5736,7 +5736,7 @@ function projectChild(children2, project, type) {
|
|
|
5736
5736
|
}
|
|
5737
5737
|
|
|
5738
5738
|
// src/version.ts
|
|
5739
|
-
var PARSEMAN_VERSION = "0.60.
|
|
5739
|
+
var PARSEMAN_VERSION = "0.60.4";
|
|
5740
5740
|
|
|
5741
5741
|
// src/table/assemble.ts
|
|
5742
5742
|
import { PlainBuf as PlainBuf2, PlainField, PlainLeaf as PlainLeaf8, PlainNode as PlainNode2, PlainSpan as PlainSpan9 } from "../cst/plain.js";
|
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.4";
|
|
17
17
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -147,9 +147,11 @@ function matcherExpr(entry: DispatchMatcherCase): string {
|
|
|
147
147
|
export function serializeRuleMap(
|
|
148
148
|
ruleMap: ReadonlyArray<readonly [string, Comb]>,
|
|
149
149
|
scanSkip?: readonly Comb[] | null,
|
|
150
|
+
/** External names the factory read through `g`; any no rule body reaches is kept as `void g[name]`. */
|
|
151
|
+
accessed?: readonly string[],
|
|
150
152
|
): string | null {
|
|
151
153
|
try {
|
|
152
|
-
return new Serializer(ruleMap, scanSkip ?? []).run()
|
|
154
|
+
return new Serializer(ruleMap, scanSkip ?? [], accessed ?? []).run()
|
|
153
155
|
} catch (e) {
|
|
154
156
|
if (e instanceof Unserializable) {
|
|
155
157
|
// `compileLinkableTable()` reaches this at run time, so it runs in browsers too.
|
|
@@ -190,9 +192,13 @@ class Serializer {
|
|
|
190
192
|
private ruleMap: ReadonlyArray<readonly [string, Comb]>
|
|
191
193
|
private scanSkip: readonly Comb[]
|
|
192
194
|
|
|
193
|
-
|
|
195
|
+
private accessed: readonly string[]
|
|
196
|
+
private referenced = new Set<string>()
|
|
197
|
+
|
|
198
|
+
constructor(ruleMap: ReadonlyArray<readonly [string, Comb]>, scanSkip: readonly Comb[] = [], accessed: readonly string[] = []) {
|
|
194
199
|
this.ruleMap = ruleMap
|
|
195
200
|
this.scanSkip = scanSkip
|
|
201
|
+
this.accessed = accessed
|
|
196
202
|
for (const [name, c] of ruleMap) {
|
|
197
203
|
this.localRuleNames.set(c, name)
|
|
198
204
|
this.ruleValues.add(this.body(c))
|
|
@@ -294,6 +300,11 @@ class Serializer {
|
|
|
294
300
|
const body = this.ruleMap
|
|
295
301
|
.map(([name, c]) => ` ${JSON.stringify(name)}: ${this.wrap(this.body(c), [])}`)
|
|
296
302
|
.join(',\n')
|
|
303
|
+
// A name the factory read through `g` that no rule body reaches still creates
|
|
304
|
+
// its slot, so a composition that defines it nowhere refuses it.
|
|
305
|
+
for (const name of this.accessed) {
|
|
306
|
+
if (!this.referenced.has(name)) innerDecls.push(`void g[${JSON.stringify(name)}]`)
|
|
307
|
+
}
|
|
297
308
|
// Shared consts go INSIDE the factory: they can reference `g[name]` rule refs,
|
|
298
309
|
// and `g` only exists in the factory scope.
|
|
299
310
|
const factory = innerDecls.length === 0
|
|
@@ -412,7 +423,7 @@ class Serializer {
|
|
|
412
423
|
switch (def.tag) {
|
|
413
424
|
case 'lazy': {
|
|
414
425
|
const name = this.localRuleNames.get(c) ?? ruleNameOf(c)
|
|
415
|
-
if (name !== undefined) return `g[${JSON.stringify(name)}]`
|
|
426
|
+
if (name !== undefined) { this.referenced.add(name); return `g[${JSON.stringify(name)}]` }
|
|
416
427
|
const target = lazyTarget(c)!
|
|
417
428
|
// Innermost matching frame wins.
|
|
418
429
|
for (let i = frames.length - 1; i >= 0; i--) {
|
package/src/compiler/linker.ts
CHANGED
|
@@ -372,24 +372,40 @@ function declaredNamesOf(items: Array<LinkableTable | Record<string, unknown>>):
|
|
|
372
372
|
return [...names]
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
/**
|
|
376
|
+
* The reason a linked map is incomplete, or `undefined` when every `g.X` some piece
|
|
377
|
+
* reads is defined by some piece. A linked namespace holds every name a factory
|
|
378
|
+
* merely ACCESSED, so a name nobody defines is a slot that stays undefined. Runtime
|
|
379
|
+
* `compose()` and the macro's build-time fuse both refuse it with these words, naming
|
|
380
|
+
* the rule that reaches the hole when one does.
|
|
381
|
+
*/
|
|
382
|
+
export function danglingReference(
|
|
383
|
+
map: Record<string, Combinator<unknown>>,
|
|
384
|
+
/** Names read through `g` that `map` itself does not hold (a merge-by-name fuse drops them). */
|
|
385
|
+
accessed: Iterable<string> = [],
|
|
386
|
+
): string | undefined {
|
|
387
|
+
const holes = new Set<string>()
|
|
388
|
+
for (const [name, rule] of Object.entries(map)) {
|
|
389
|
+
if (rule._def.tag !== 'lazy') continue
|
|
390
|
+
try { rule._def.thunk() } catch { holes.add(name) }
|
|
391
|
+
}
|
|
392
|
+
for (const name of accessed) if (!Object.hasOwn(map, name)) holes.add(name)
|
|
393
|
+
if (holes.size === 0) return undefined
|
|
394
|
+
const missing = [...holes]
|
|
395
|
+
for (const [name, deps] of ruleDependencies(Object.entries(map).filter(([n]) => !holes.has(n)))) {
|
|
396
|
+
for (const dep of deps) if (holes.has(dep)) return `rule "${name}" references missing rule "${dep}"`
|
|
397
|
+
}
|
|
398
|
+
return `rule(s) ${missing.map(n => `"${n}"`).join(', ')} are referenced but defined by no composed grammar`
|
|
399
|
+
}
|
|
400
|
+
|
|
375
401
|
/** Link recipes into one interpreter map, refusing a name nobody defines. */
|
|
376
402
|
function linkComposition(
|
|
377
403
|
recipes: readonly RulesRecipe[],
|
|
378
404
|
link: LinkOptions,
|
|
379
405
|
): Record<string, Combinator<unknown>> {
|
|
380
406
|
const map = linkRules(recipes, link)
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
if (rule._def.tag !== 'lazy') continue
|
|
384
|
-
try { rule._def.thunk() } catch { missing.push(name) }
|
|
385
|
-
}
|
|
386
|
-
if (missing.length > 0) {
|
|
387
|
-
const holes = new Set(missing)
|
|
388
|
-
for (const [name, deps] of ruleDependencies(Object.entries(map).filter(([n]) => !holes.has(n)))) {
|
|
389
|
-
for (const dep of deps) if (holes.has(dep)) throw new Error(`compose: rule "${name}" references missing rule "${dep}"`)
|
|
390
|
-
}
|
|
391
|
-
throw new Error(`compose: rule(s) ${missing.map(n => `"${n}"`).join(', ')} are referenced but defined by no composed grammar`)
|
|
392
|
-
}
|
|
407
|
+
const dangling = danglingReference(map)
|
|
408
|
+
if (dangling !== undefined) throw new Error(`compose: ${dangling}`)
|
|
393
409
|
// A borrowed rule object keeps its own grammar's bindings here but would be rebound
|
|
394
410
|
// by name in `compile()` of this composition: refuse it rather than let the two
|
|
395
411
|
// engines accept different input.
|
package/src/plugin/evaluator.ts
CHANGED
|
@@ -1613,9 +1613,20 @@ export function evaluateParserFactory(
|
|
|
1613
1613
|
*/
|
|
1614
1614
|
const map = new Map<string, Combinator<unknown>>()
|
|
1615
1615
|
for (const key of keys) map.set(key, built[key]!)
|
|
1616
|
+
const declared = new Set(keys)
|
|
1617
|
+
externalAccesses.set(map, Object.keys(built).filter(name => !declared.has(name)))
|
|
1616
1618
|
return map
|
|
1617
1619
|
}
|
|
1618
1620
|
|
|
1621
|
+
/**
|
|
1622
|
+
* Every EXTERNAL name an evaluated factory read through `g`, keyed by the map
|
|
1623
|
+
* `evaluateParserFactory` returned. The map holds declared rules only, so a `g.X`
|
|
1624
|
+
* read outside every rule body (`const unused = g.X`) leaves no trace in it; the
|
|
1625
|
+
* IR serializer reads this to keep that access, so a composition refuses an `X`
|
|
1626
|
+
* nobody defines exactly as runtime `compose()` does.
|
|
1627
|
+
*/
|
|
1628
|
+
export const externalAccesses = new WeakMap<ReadonlyMap<string, Combinator<unknown>>, readonly string[]>()
|
|
1629
|
+
|
|
1619
1630
|
/** A combinator slot created by ref() — has a callable `define`. */
|
|
1620
1631
|
type DefinableRef = Combinator<unknown> & { define(p: Combinator<unknown>): void }
|
|
1621
1632
|
|
package/src/plugin/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { createUnplugin } from 'unplugin'
|
|
|
24
24
|
import { parseSync } from 'oxc-parser'
|
|
25
25
|
import { ResolverFactory } from 'oxc-resolver'
|
|
26
26
|
import MagicString from 'magic-string'
|
|
27
|
-
import { evaluateExpr, evaluateCombinatorArray, evaluateParserFactory, evaluateStaticValue, evaluateWordFactory, evaluateWhenFactory, evaluateRefDeclaration, applyDefineStatement, referencesAny, setReducerResolver, setBuilderImportResolver, propName, ModuleScope, type FreeNameResolution, type Scope, type ScopeEntry } from './evaluator.ts'
|
|
27
|
+
import { evaluateExpr, evaluateCombinatorArray, evaluateParserFactory, externalAccesses, evaluateStaticValue, evaluateWordFactory, evaluateWhenFactory, evaluateRefDeclaration, applyDefineStatement, referencesAny, setReducerResolver, setBuilderImportResolver, propName, ModuleScope, type FreeNameResolution, type Scope, type ScopeEntry } from './evaluator.ts'
|
|
28
28
|
import { classifyRuleMap } from '../analysis/commitment.ts'
|
|
29
29
|
import { compile } from '../table/compile.ts'
|
|
30
30
|
import { compileRuleMap, type CompiledRuleMapTable } from '../table/compile-rule-map.ts'
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
resolveDegradationLevel, recordDegradation, degradationCaptureDepth, unwindDegradationCapture,
|
|
38
38
|
} from '../compiler/degradation.ts'
|
|
39
39
|
import type { HostMode } from '../cst/host-mode.ts'
|
|
40
|
-
import { COMPOSED_PIECES, type IRPiece } from '../compiler/linker.ts'
|
|
40
|
+
import { COMPOSED_PIECES, danglingReference, type IRPiece } from '../compiler/linker.ts'
|
|
41
41
|
import { serializeCombinator, serializeRuleMap } from '../compiler/ir-serialize.ts'
|
|
42
42
|
import { evalCombinatorIR, evalRulesIR, INERT_BUILDER } from './ir-eval.ts'
|
|
43
43
|
import { countSiteVisits } from '../table/assemble.ts'
|
|
@@ -220,6 +220,15 @@ const DEFAULT_ASSEMBLY_BUDGET = 0.25
|
|
|
220
220
|
* ~79 ms against ~14 ms on `benchmark.less`. "Couldn't be inlined" alone does not
|
|
221
221
|
* tell an author which construct to change; the compiler knows, so it says.
|
|
222
222
|
*/
|
|
223
|
+
/** A build failure the lowering must not soften into a warning and a fallback. */
|
|
224
|
+
class LinkRefusal extends Error {}
|
|
225
|
+
|
|
226
|
+
/** Whether a rule-map entry has a definition (a `g.X` merely read does not). */
|
|
227
|
+
function isResolved(rule: Combinator<unknown>): boolean {
|
|
228
|
+
if (rule._def.tag !== 'lazy') return true
|
|
229
|
+
try { rule._def.thunk(); return true } catch { return false }
|
|
230
|
+
}
|
|
231
|
+
|
|
223
232
|
function reasonSuffix(runtimeOnly: readonly string[] | undefined): string {
|
|
224
233
|
return runtimeOnly === undefined || runtimeOnly.length === 0
|
|
225
234
|
? ''
|
|
@@ -2136,17 +2145,15 @@ function transformMacroImpl(
|
|
|
2136
2145
|
const mergedCarriedRules = (
|
|
2137
2146
|
items: CarriedItem[],
|
|
2138
2147
|
link: LinkOptions,
|
|
2139
|
-
): { rules: Array<[string, Combinator<unknown>]>; trackLines: boolean } | null => {
|
|
2148
|
+
): { rules: Array<[string, Combinator<unknown>]>; trackLines: boolean; dangling: string | undefined } | null => {
|
|
2140
2149
|
const carriedMaps = carriedRuleMaps(items)
|
|
2141
2150
|
if (carriedMaps === null) return null
|
|
2142
2151
|
const linked = linkRules(carriedMaps.pieces.flatMap(p => p.recipes), link)
|
|
2143
2152
|
// A name some piece only REFERENCES and none defines stays an undefined slot in
|
|
2144
|
-
// the namespace.
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
})
|
|
2149
|
-
return { rules, trackLines: carriedMaps.trackLines }
|
|
2153
|
+
// the namespace. Runtime `compose()` refuses that grammar at link, so the build
|
|
2154
|
+
// refuses it too (the caller throws `dangling`) rather than encode around the hole.
|
|
2155
|
+
const dangling = danglingReference(linked)
|
|
2156
|
+
return { rules: Object.entries(linked), trackLines: carriedMaps.trackLines, dangling }
|
|
2150
2157
|
}
|
|
2151
2158
|
|
|
2152
2159
|
/**
|
|
@@ -2183,7 +2190,7 @@ function transformMacroImpl(
|
|
|
2183
2190
|
// `rules({ scanSkip }, …)` options, which stamps `_meta.grammarScanSkip` when the IR
|
|
2184
2191
|
// is re-lowered (materializePiece → compileLinkable picks it up), and survives to a
|
|
2185
2192
|
// downstream re-compose. The full-pieces fallback bakes it via the compile option.
|
|
2186
|
-
const ir = serializeRuleMap(entries as never, scanSkip)
|
|
2193
|
+
const ir = serializeRuleMap(entries as never, scanSkip, rm instanceof Map ? externalAccesses.get(rm) : undefined)
|
|
2187
2194
|
if (ir) return { carried: [{ ns, ir, ...(trackLines ? { trackLines: true as const } : {}) }] }
|
|
2188
2195
|
// NO FULL-PIECES FALLBACK. It used to bake the grammar to lowered source when the IR
|
|
2189
2196
|
// would not serialize; with one lowering there is nothing to bake to, and IR is not
|
|
@@ -2337,6 +2344,7 @@ function transformMacroImpl(
|
|
|
2337
2344
|
trivia: composing,
|
|
2338
2345
|
...(cHostMode ? { hostMode: cHostMode as HostMode } : {}),
|
|
2339
2346
|
})
|
|
2347
|
+
if (merged?.dangling !== undefined) throw new Error(`${id}:${lineOf(init.start)} — compose: ${merged.dangling}`)
|
|
2340
2348
|
if (merged !== null) {
|
|
2341
2349
|
// Harvest the direct-builder import provenance carried on the merged graph so
|
|
2342
2350
|
// the re-lower pass at the end of this transform re-emits those imports.
|
|
@@ -2559,6 +2567,15 @@ function transformMacroImpl(
|
|
|
2559
2567
|
}
|
|
2560
2568
|
}
|
|
2561
2569
|
const leafMerged = mergeRuleMaps([...carriedMaps.pieces.map(p => p.rules), localEntries])
|
|
2570
|
+
// The same refusal as `compose()`, over a merge by name: a name some piece only
|
|
2571
|
+
// reads (an unresolved entry of a carried map, or a local factory's external
|
|
2572
|
+
// access) that no piece defines.
|
|
2573
|
+
const accessed = [
|
|
2574
|
+
...carriedMaps.pieces.flatMap(p => p.rules).filter(([, rule]) => !isResolved(rule)).map(([name]) => name),
|
|
2575
|
+
...(localRules instanceof Map ? externalAccesses.get(localRules) ?? [] : []),
|
|
2576
|
+
]
|
|
2577
|
+
const dangling = danglingReference(Object.fromEntries(leafMerged), accessed)
|
|
2578
|
+
if (dangling !== undefined) throw new LinkRefusal(`${id}:${lineOf(init.start)} — composeLeaf: ${dangling}`)
|
|
2562
2579
|
// Composing-wins governs the leaf fuse too. The local entries are NOT freshly
|
|
2563
2580
|
// evaluated (they come from this module's own `rules()` factory), so they are
|
|
2564
2581
|
// excluded from the override — their declared trivia IS the composing candidate,
|
|
@@ -2595,6 +2612,7 @@ function transformMacroImpl(
|
|
|
2595
2612
|
warn(init.start, 'composeLeaf(): a recognition piece has no re-lowerable IR')
|
|
2596
2613
|
return null
|
|
2597
2614
|
} catch (e) {
|
|
2615
|
+
if (e instanceof LinkRefusal) throw e
|
|
2598
2616
|
warn(init.start, `composeLeaf(): ${(e as Error).message}`)
|
|
2599
2617
|
return null
|
|
2600
2618
|
}
|
|
@@ -2796,7 +2814,7 @@ function transformMacroImpl(
|
|
|
2796
2814
|
// lowering there is no such artifact, and a grammar that cannot serialize
|
|
2797
2815
|
// simply is not carried: an absent carried list makes the downstream compose
|
|
2798
2816
|
// say so, where a frozen one did not.
|
|
2799
|
-
const ir = serializeRuleMap([...compiledRules.ruleMap] as never, compiledRules.scanSkip)
|
|
2817
|
+
const ir = serializeRuleMap([...compiledRules.ruleMap] as never, compiledRules.scanSkip, externalAccesses.get(compiledRules.ruleMap))
|
|
2800
2818
|
if (ir) carriedMetadata = [{ ns, ir, ...(compiledRules.trackLines ? { trackLines: true as const } : {}) }]
|
|
2801
2819
|
// NOT SILENT. An export with no carried IR cannot be composed downstream at
|
|
2802
2820
|
// all — the consumer's `compose()` will fall back to runtime and say so, but
|
package/src/version.ts
CHANGED