parseman 0.49.0 → 0.50.1
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/commitment.d.ts +2 -1
- package/dist/analysis/commitment.d.ts.map +1 -1
- package/dist/analysis/diagnostics.js +1824 -737
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/analysis/rebuild.d.ts.map +1 -1
- package/dist/cli/index.js +1252 -164
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/trivia-skip.d.ts +1 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/build-arity.d.ts +16 -0
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/degradation.d.ts +0 -2
- package/dist/compiler/degradation.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +11 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +185 -20
- package/dist/compiler/token-capability.js.map +1 -1
- package/dist/cst/capture-buffer.d.ts +5 -1
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/index.js +1365 -279
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +2 -1
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/entry-supercompiler.d.ts +48 -0
- package/dist/plugin/entry-supercompiler.d.ts.map +1 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +1957 -385
- package/dist/plugin/index.js.map +3 -3
- package/dist/run/index.js +2 -1
- package/dist/run/index.js.map +1 -1
- package/dist/spec/index.js +28 -7
- package/dist/spec/index.js.map +1 -1
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts +6 -5
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +6 -1
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/index.js +1315 -231
- package/dist/table/index.js.map +3 -3
- package/dist/table/ops.d.ts +3 -2
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +15 -1
- package/dist/table/program.d.ts.map +1 -1
- package/dist/table/scalar-terminal.d.ts +30 -0
- package/dist/table/scalar-terminal.d.ts.map +1 -1
- package/dist/table/site-labels.d.ts +8 -0
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/analysis/commitment.ts +19 -8
- package/src/analysis/rebuild.ts +1 -0
- package/src/combinators/trivia-skip.ts +2 -2
- package/src/compiler/build-arity.ts +50 -0
- package/src/compiler/degradation.ts +0 -6
- package/src/compiler/ir-serialize.ts +6 -4
- package/src/compiler/token-alphabet.ts +322 -28
- package/src/cst/capture-buffer.ts +34 -11
- package/src/plugin/entry-supercompiler.ts +567 -0
- package/src/plugin/evaluator.ts +10 -0
- package/src/plugin/index.ts +7 -3
- package/src/table/assemble.ts +125 -42
- package/src/table/compile-rule-map.ts +8 -7
- package/src/table/compile.ts +7 -8
- package/src/table/emit-assembly.ts +646 -126
- package/src/table/emit.ts +1 -1
- package/src/table/encode.ts +152 -7
- package/src/table/ops.ts +3 -2
- package/src/table/program.ts +44 -3
- package/src/table/scalar-terminal.ts +213 -1
- package/src/table/site-labels.ts +22 -9
- package/src/types.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -2313,6 +2313,7 @@ function familyHasContextFreeFiniteEnd(
|
|
|
2313
2313
|
function compatibleLanguageViews(
|
|
2314
2314
|
arm: LexicalIr,
|
|
2315
2315
|
family: LexicalIr,
|
|
2316
|
+
coversContinuation: (family: LexicalIr, boundary: string) => boolean = boundaryCoversContinuation,
|
|
2316
2317
|
): ReadonlyArray<{ readonly ir: LexicalIr; readonly relation: 'equal' | 'prefix' }> | undefined {
|
|
2317
2318
|
if (JSON.stringify(arm) === JSON.stringify(family)) return [{ ir: arm, relation: 'equal' }]
|
|
2318
2319
|
const values = finiteLexicalValues(arm)
|
|
@@ -2332,7 +2333,7 @@ function compatibleLanguageViews(
|
|
|
2332
2333
|
? [value] : asciiCaseVariants(value, armCaseInsensitive)
|
|
2333
2334
|
const endIsTotal = spellings !== undefined
|
|
2334
2335
|
&& (familyHasContextFreeFiniteEnd(family, spellings)
|
|
2335
|
-
|| armBoundary !== undefined &&
|
|
2336
|
+
|| armBoundary !== undefined && coversContinuation(family, armBoundary))
|
|
2336
2337
|
if (endIsTotal && spellings !== undefined
|
|
2337
2338
|
&& spellings.every(spelling => lexicalIrAcceptsExact(family, spelling))) {
|
|
2338
2339
|
relation = 'equal'
|
|
@@ -2463,18 +2464,45 @@ function ownedScanConsumers(
|
|
|
2463
2464
|
return out
|
|
2464
2465
|
}
|
|
2465
2466
|
|
|
2466
|
-
|
|
2467
|
-
|
|
2467
|
+
/**
|
|
2468
|
+
* Whether each parser can reach a dynamic scan consumer.
|
|
2469
|
+
*
|
|
2470
|
+
* This is reverse reachability rather than one DFS per decision/arm. Starting
|
|
2471
|
+
* from scan consumers and walking parents is cycle-safe by construction: every
|
|
2472
|
+
* node in an SCC that can reach a consumer is eventually marked, without the
|
|
2473
|
+
* false negative a recursive "currently visiting" memo creates.
|
|
2474
|
+
*/
|
|
2475
|
+
function dynamicScanReachability(
|
|
2476
|
+
roots: ReadonlyArray<Combinator<unknown>>,
|
|
2468
2477
|
resolve?: (name: string) => Combinator<unknown> | undefined,
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2478
|
+
): ReadonlySet<Combinator<unknown>> {
|
|
2479
|
+
const parents = new Map<Combinator<unknown>, Set<Combinator<unknown>>>()
|
|
2480
|
+
const nodes: Combinator<unknown>[] = []
|
|
2481
|
+
const seen = new Set<Combinator<unknown>>()
|
|
2482
|
+
const walk = (parser: Combinator<unknown>): void => {
|
|
2483
|
+
if (seen.has(parser)) return
|
|
2484
|
+
seen.add(parser)
|
|
2485
|
+
nodes.push(parser)
|
|
2486
|
+
for (const child of tokenChildren(parser, resolve)) {
|
|
2487
|
+
let owners = parents.get(child)
|
|
2488
|
+
if (owners === undefined) parents.set(child, owners = new Set())
|
|
2489
|
+
owners.add(parser)
|
|
2490
|
+
walk(child)
|
|
2491
|
+
}
|
|
2476
2492
|
}
|
|
2477
|
-
|
|
2493
|
+
for (const root of roots) walk(root)
|
|
2494
|
+
|
|
2495
|
+
const reachable = new Set(nodes.filter(parser =>
|
|
2496
|
+
parser._def.tag === 'scanTo' || balancedSpecOf(parser) !== undefined))
|
|
2497
|
+
const queue = [...reachable]
|
|
2498
|
+
for (let index = 0; index < queue.length; index++) {
|
|
2499
|
+
for (const parent of parents.get(queue[index]!) ?? []) {
|
|
2500
|
+
if (reachable.has(parent)) continue
|
|
2501
|
+
reachable.add(parent)
|
|
2502
|
+
queue.push(parent)
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
return reachable
|
|
2478
2506
|
}
|
|
2479
2507
|
|
|
2480
2508
|
function predicateViews(def: Extract<ParserDef, { tag: 'dispatch' }>): Array<{
|
|
@@ -2545,6 +2573,248 @@ type BindingEdgeCandidate = Omit<
|
|
|
2545
2573
|
readonly childOrdinal: number
|
|
2546
2574
|
}
|
|
2547
2575
|
|
|
2576
|
+
type LexicalCapabilityRawReceipt = Readonly<{
|
|
2577
|
+
capabilities: readonly Readonly<Pick<CapabilityCandidate,
|
|
2578
|
+
'bodyId' | 'parser' | 'atom' | 'path' | 'context' | 'contextKey' | 'recognitionContextKey'>>[]
|
|
2579
|
+
bindingEdges: readonly Readonly<BindingEdgeCandidate>[]
|
|
2580
|
+
parserAuthorities: readonly Combinator<unknown>[]
|
|
2581
|
+
}>
|
|
2582
|
+
|
|
2583
|
+
function capabilityOccurrenceReceipt(inventory: {
|
|
2584
|
+
candidates: readonly CapabilityCandidate[]
|
|
2585
|
+
bindingEdges: readonly BindingEdgeCandidate[]
|
|
2586
|
+
}): LexicalCapabilityRawReceipt {
|
|
2587
|
+
const parserAuthorities: Combinator<unknown>[] = []
|
|
2588
|
+
const parserAuthoritySet = new Set<Combinator<unknown>>()
|
|
2589
|
+
const addParserAuthority = (parser: Combinator<unknown> | undefined): void => {
|
|
2590
|
+
if (parser === undefined || parserAuthoritySet.has(parser)) return
|
|
2591
|
+
parserAuthoritySet.add(parser)
|
|
2592
|
+
parserAuthorities.push(parser)
|
|
2593
|
+
}
|
|
2594
|
+
for (const candidate of inventory.candidates) {
|
|
2595
|
+
addParserAuthority(candidate.parser)
|
|
2596
|
+
addParserAuthority(candidate.context.trivia)
|
|
2597
|
+
for (const parser of candidate.context.scanSkip) addParserAuthority(parser)
|
|
2598
|
+
}
|
|
2599
|
+
return Object.freeze({
|
|
2600
|
+
capabilities: Object.freeze(inventory.candidates.map(candidate => Object.freeze({
|
|
2601
|
+
bodyId: candidate.bodyId,
|
|
2602
|
+
parser: candidate.parser,
|
|
2603
|
+
atom: candidate.atom,
|
|
2604
|
+
path: candidate.path,
|
|
2605
|
+
context: candidate.context,
|
|
2606
|
+
contextKey: candidate.contextKey,
|
|
2607
|
+
recognitionContextKey: candidate.recognitionContextKey,
|
|
2608
|
+
}))),
|
|
2609
|
+
bindingEdges: Object.freeze(inventory.bindingEdges.map(edge => Object.freeze({ ...edge }))),
|
|
2610
|
+
parserAuthorities: Object.freeze(parserAuthorities),
|
|
2611
|
+
})
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
type LexicalCapabilityClosureTables = Readonly<{
|
|
2615
|
+
capabilities: readonly LexicalCapabilitySite[]
|
|
2616
|
+
languages: readonly LexicalCapabilityLanguage[]
|
|
2617
|
+
bindingEdges: readonly LexicalBindingEdge[]
|
|
2618
|
+
bindingProjections: readonly LexicalBindingProjection[]
|
|
2619
|
+
terminalProjections: readonly LexicalTerminalProjection[]
|
|
2620
|
+
decisionFamilies: readonly LexicalDecisionFamily[]
|
|
2621
|
+
decisionOutcomes: readonly LexicalDecisionOutcome[]
|
|
2622
|
+
decisions: readonly LexicalDecisionSite[]
|
|
2623
|
+
decisionEffectPlan: LexicalDecisionEffectInventory
|
|
2624
|
+
transitionDiagnostics: readonly LexicalTransitionDiagnosticPlan[]
|
|
2625
|
+
controlPlans: readonly LexicalControlPlan[]
|
|
2626
|
+
boundaryPlans: readonly LexicalBoundaryPlan[]
|
|
2627
|
+
materializationPlans: readonly LexicalMaterializationPlan[]
|
|
2628
|
+
grammarWrapperSpecs: readonly LexicalGrammarWrapperSpec[]
|
|
2629
|
+
grammarCaptureTriviaKinds: readonly (readonly string[])[]
|
|
2630
|
+
boundaryTopologies: readonly LexicalBoundaryTopology[]
|
|
2631
|
+
contextSnapshots: readonly LexicalContextSnapshot[]
|
|
2632
|
+
ownSkipPlans: readonly LexicalOwnSkipPlan[]
|
|
2633
|
+
scanConsumerPolicies: readonly LexicalScanConsumerPolicy[]
|
|
2634
|
+
}>
|
|
2635
|
+
|
|
2636
|
+
type LexicalCapabilityClosureReceipt = Readonly<{
|
|
2637
|
+
raw: LexicalCapabilityRawReceipt
|
|
2638
|
+
callbacks: readonly LexicalDecisionCallbackAuthority['callback'][]
|
|
2639
|
+
snapshot: LexicalCapabilityClosureTables
|
|
2640
|
+
}>
|
|
2641
|
+
|
|
2642
|
+
function lexicalClosureSnapshotValue(
|
|
2643
|
+
value: unknown,
|
|
2644
|
+
parserAuthorities: ReadonlySet<Combinator<unknown>>,
|
|
2645
|
+
seen: Set<object> = new Set(),
|
|
2646
|
+
): unknown {
|
|
2647
|
+
if (value === null || typeof value !== 'object') return value
|
|
2648
|
+
if (parserAuthorities.has(value as Combinator<unknown>)) return value
|
|
2649
|
+
if (seen.has(value)) throw new Error('parseman: cyclic lexical closure receipt value')
|
|
2650
|
+
seen.add(value)
|
|
2651
|
+
let result: unknown
|
|
2652
|
+
if (Array.isArray(value)) {
|
|
2653
|
+
result = Object.freeze(value.map(entry => lexicalClosureSnapshotValue(
|
|
2654
|
+
entry, parserAuthorities, seen,
|
|
2655
|
+
)))
|
|
2656
|
+
} else {
|
|
2657
|
+
result = Object.freeze(Object.fromEntries(Object.keys(value).map(key => [
|
|
2658
|
+
key, lexicalClosureSnapshotValue(
|
|
2659
|
+
(value as Record<string, unknown>)[key], parserAuthorities, seen,
|
|
2660
|
+
),
|
|
2661
|
+
])))
|
|
2662
|
+
}
|
|
2663
|
+
seen.delete(value)
|
|
2664
|
+
return result
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
function lexicalClosureSnapshot(
|
|
2668
|
+
tables: LexicalCapabilityClosureTables,
|
|
2669
|
+
raw: LexicalCapabilityRawReceipt,
|
|
2670
|
+
): LexicalCapabilityClosureTables {
|
|
2671
|
+
return lexicalClosureSnapshotValue(
|
|
2672
|
+
tables, new Set(raw.parserAuthorities),
|
|
2673
|
+
) as LexicalCapabilityClosureTables
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
function lexicalClosureValuesEqual(
|
|
2677
|
+
expected: unknown,
|
|
2678
|
+
actual: unknown,
|
|
2679
|
+
parserAuthorities: ReadonlySet<Combinator<unknown>>,
|
|
2680
|
+
): boolean {
|
|
2681
|
+
if (Object.is(expected, actual)) return true
|
|
2682
|
+
if (expected === null || actual === null || typeof expected !== typeof actual) return false
|
|
2683
|
+
if (typeof expected === 'function') return false
|
|
2684
|
+
if (typeof expected !== 'object' || typeof actual !== 'object') return false
|
|
2685
|
+
if (parserAuthorities.has(expected as Combinator<unknown>)) return false
|
|
2686
|
+
if (Array.isArray(expected) || Array.isArray(actual)) {
|
|
2687
|
+
return Array.isArray(expected) && Array.isArray(actual)
|
|
2688
|
+
&& expected.length === actual.length
|
|
2689
|
+
&& expected.every((entry, index) => lexicalClosureValuesEqual(
|
|
2690
|
+
entry, actual[index], parserAuthorities,
|
|
2691
|
+
))
|
|
2692
|
+
}
|
|
2693
|
+
const expectedKeys = Object.keys(expected)
|
|
2694
|
+
const actualKeys = Object.keys(actual)
|
|
2695
|
+
return expectedKeys.length === actualKeys.length
|
|
2696
|
+
&& expectedKeys.every(key => Object.hasOwn(actual, key)
|
|
2697
|
+
&& lexicalClosureValuesEqual(
|
|
2698
|
+
(expected as Record<string, unknown>)[key],
|
|
2699
|
+
(actual as Record<string, unknown>)[key],
|
|
2700
|
+
parserAuthorities,
|
|
2701
|
+
))
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
function lexicalCapabilityClosureReceipt(
|
|
2705
|
+
raw: LexicalCapabilityRawReceipt,
|
|
2706
|
+
tables: LexicalCapabilityClosureTables,
|
|
2707
|
+
): LexicalCapabilityClosureReceipt {
|
|
2708
|
+
const callbacks = Object.freeze(tables.decisionEffectPlan.decisionCallbackAuthorities
|
|
2709
|
+
.map(authority => authority.callback))
|
|
2710
|
+
return Object.freeze({
|
|
2711
|
+
raw,
|
|
2712
|
+
callbacks,
|
|
2713
|
+
snapshot: lexicalClosureSnapshot(tables, raw),
|
|
2714
|
+
})
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
function closureTablesFromInventory(
|
|
2718
|
+
alphabet: Omit<LexicalCapabilityInventory, 'capabilityComplete'>,
|
|
2719
|
+
): LexicalCapabilityClosureTables {
|
|
2720
|
+
const { capabilityLanguages: languages, ...tables } = alphabet
|
|
2721
|
+
return { ...tables, languages }
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
function rawLexicalClosureMatches(
|
|
2725
|
+
raw: LexicalCapabilityRawReceipt,
|
|
2726
|
+
alphabet: Omit<LexicalCapabilityInventory, 'capabilityComplete'>,
|
|
2727
|
+
): boolean {
|
|
2728
|
+
if (raw.capabilities.length !== alphabet.capabilities.length
|
|
2729
|
+
|| raw.bindingEdges.length !== alphabet.bindingEdges.length
|
|
2730
|
+
|| raw.bindingEdges.length !== alphabet.bindingProjections.length) return false
|
|
2731
|
+
const snapshotIdByKey = new Map(alphabet.contextSnapshots.map(snapshot => [snapshot.key, snapshot.id]))
|
|
2732
|
+
const parserAuthorities = new Set(raw.parserAuthorities)
|
|
2733
|
+
for (let id = 0; id < raw.capabilities.length; id++) {
|
|
2734
|
+
const expected = raw.capabilities[id]!
|
|
2735
|
+
const actual = alphabet.capabilities[id]
|
|
2736
|
+
if (actual === undefined
|
|
2737
|
+
|| actual.id !== id
|
|
2738
|
+
|| actual.bodyId !== expected.bodyId
|
|
2739
|
+
|| actual.parser !== expected.parser
|
|
2740
|
+
|| actual.atom !== expected.atom
|
|
2741
|
+
|| actual.path !== expected.path
|
|
2742
|
+
|| actual.contextKey !== expected.contextKey
|
|
2743
|
+
|| actual.contextSnapshotId !== snapshotIdByKey.get(expected.contextKey)
|
|
2744
|
+
|| actual.recognitionContextKey !== expected.recognitionContextKey
|
|
2745
|
+
|| !lexicalClosureValuesEqual(expected.context, actual.context, parserAuthorities)) return false
|
|
2746
|
+
}
|
|
2747
|
+
for (let id = 0; id < raw.bindingEdges.length; id++) {
|
|
2748
|
+
const expected = raw.bindingEdges[id]!
|
|
2749
|
+
const actual = alphabet.bindingEdges[id]
|
|
2750
|
+
const expectedProjection = fixedBindingProjection(expected, id, id)
|
|
2751
|
+
const actualProjection = alphabet.bindingProjections[id]
|
|
2752
|
+
const complete = expectedProjection.readerMask === ALL_BINDING_READERS
|
|
2753
|
+
&& expectedProjection.variantMask === ALL_BINDING_VARIANTS
|
|
2754
|
+
const expectedStatus = complete ? COMPLETE_CAPABILITY : gap(FIXED_TUPLE_BINDING_GAP)
|
|
2755
|
+
if (actual === undefined || actualProjection === undefined
|
|
2756
|
+
|| actual.id !== id
|
|
2757
|
+
|| actual.path !== expected.path
|
|
2758
|
+
|| actual.contextKey !== expected.contextKey
|
|
2759
|
+
|| actual.childContextKey !== expected.childContextKey
|
|
2760
|
+
|| actual.contextSnapshotId !== snapshotIdByKey.get(expected.contextKey)
|
|
2761
|
+
|| actual.parentTag !== expected.parentTag
|
|
2762
|
+
|| actual.childTag !== expected.childTag
|
|
2763
|
+
|| actual.projectionId !== id
|
|
2764
|
+
|| !lexicalClosureValuesEqual(expectedStatus, actual.status, parserAuthorities)
|
|
2765
|
+
|| !lexicalClosureValuesEqual(expectedProjection, actualProjection, parserAuthorities)) return false
|
|
2766
|
+
}
|
|
2767
|
+
return true
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
function lexicalClosureFailure(detail: string): never {
|
|
2771
|
+
throw new Error(
|
|
2772
|
+
`parseman: lexical capability census is incomplete: occurrence/derived bijection is incomplete (${detail})`,
|
|
2773
|
+
)
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
function assertLexicalCapabilityBijection(
|
|
2777
|
+
receipt: LexicalCapabilityClosureReceipt,
|
|
2778
|
+
alphabet: Omit<LexicalCapabilityInventory, 'capabilityComplete'>,
|
|
2779
|
+
): void {
|
|
2780
|
+
if (!rawLexicalClosureMatches(receipt.raw, alphabet)) lexicalClosureFailure('raw')
|
|
2781
|
+
const actual = closureTablesFromInventory(alphabet)
|
|
2782
|
+
const parserAuthorities = new Set(receipt.raw.parserAuthorities)
|
|
2783
|
+
const failedTable = (Object.keys(receipt.snapshot) as (keyof LexicalCapabilityClosureTables)[])
|
|
2784
|
+
.find(key => !lexicalClosureValuesEqual(
|
|
2785
|
+
receipt.snapshot[key], actual[key], parserAuthorities,
|
|
2786
|
+
))
|
|
2787
|
+
if (failedTable !== undefined) lexicalClosureFailure(failedTable)
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
/** Test-only production-seam model: snapshot the supplied derived tables while
|
|
2791
|
+
* retaining a raw graph receipt captured before derivation. A dropped raw
|
|
2792
|
+
* occurrence must fail even though the post-derivation snapshot agrees. */
|
|
2793
|
+
export function assertLexicalCapabilityProductionBijection(
|
|
2794
|
+
roots: ReadonlyArray<Combinator<unknown>>,
|
|
2795
|
+
alphabet: Omit<LexicalCapabilityInventory, 'capabilityComplete'>,
|
|
2796
|
+
resolve?: (name: string) => Combinator<unknown> | undefined,
|
|
2797
|
+
): void {
|
|
2798
|
+
const raw = capabilityOccurrenceReceipt(lexicalCapabilityCandidates(roots, resolve))
|
|
2799
|
+
const receipt = lexicalCapabilityClosureReceipt(raw, closureTablesFromInventory(alphabet))
|
|
2800
|
+
assertLexicalCapabilityBijection(receipt, alphabet)
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
/**
|
|
2804
|
+
* Test/diagnostic entry for the one-pass production closure guard. This helper
|
|
2805
|
+
* deliberately rebuilds the expected inventory for a supplied test artifact;
|
|
2806
|
+
* production consumes the session-local raw receipt and never walks the graph
|
|
2807
|
+
* or recomputes lexical analyses a second time.
|
|
2808
|
+
*/
|
|
2809
|
+
export function assertLexicalCapabilityOccurrenceBijection(
|
|
2810
|
+
roots: ReadonlyArray<Combinator<unknown>>,
|
|
2811
|
+
alphabet: Omit<LexicalCapabilityInventory, 'capabilityComplete'>,
|
|
2812
|
+
resolve?: (name: string) => Combinator<unknown> | undefined,
|
|
2813
|
+
): void {
|
|
2814
|
+
const expected = lexicalCapabilityInventory(roots, resolve)
|
|
2815
|
+
assertLexicalCapabilityBijection(expected.closureReceipt, alphabet)
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2548
2818
|
type CapabilityContext = LexicalCapabilityContext
|
|
2549
2819
|
|
|
2550
2820
|
/**
|
|
@@ -2687,6 +2957,7 @@ function decisionViewKey(view: LexicalDecisionOutcomeView): string {
|
|
|
2687
2957
|
function lexicalDecisionInventory(
|
|
2688
2958
|
candidates: readonly CapabilityCandidate[],
|
|
2689
2959
|
contextSnapshotIdByKey: ReadonlyMap<string, number>,
|
|
2960
|
+
dynamicScanConsumers: ReadonlySet<Combinator<unknown>>,
|
|
2690
2961
|
resolve?: (name: string) => Combinator<unknown> | undefined,
|
|
2691
2962
|
): {
|
|
2692
2963
|
families: LexicalDecisionFamily[]
|
|
@@ -2697,6 +2968,15 @@ function lexicalDecisionInventory(
|
|
|
2697
2968
|
const familyIrByKey = new Map<string, LexicalIr>()
|
|
2698
2969
|
const viewsByKey = new Map<string, PendingDecisionView>()
|
|
2699
2970
|
const pending: PendingDecisionSite[] = []
|
|
2971
|
+
const boundaryCoverage = new Map<string, boolean>()
|
|
2972
|
+
const coversContinuation = (family: LexicalIr, boundary: string): boolean => {
|
|
2973
|
+
const key = `${JSON.stringify(family)}\u0000${boundary}`
|
|
2974
|
+
const prior = boundaryCoverage.get(key)
|
|
2975
|
+
if (prior !== undefined) return prior
|
|
2976
|
+
const covered = boundaryCoversContinuation(family, boundary)
|
|
2977
|
+
boundaryCoverage.set(key, covered)
|
|
2978
|
+
return covered
|
|
2979
|
+
}
|
|
2700
2980
|
const addFamily = (lead: DecisionLead): string => {
|
|
2701
2981
|
if (!familyIrByKey.has(lead.key)) familyIrByKey.set(lead.key, lead.ir)
|
|
2702
2982
|
return lead.key
|
|
@@ -2717,7 +2997,7 @@ function lexicalDecisionInventory(
|
|
|
2717
2997
|
pure = []
|
|
2718
2998
|
}
|
|
2719
2999
|
for (let armId = 0; armId < arms.length; armId++) {
|
|
2720
|
-
if (!
|
|
3000
|
+
if (!dynamicScanConsumers.has(arms[armId]!)) {
|
|
2721
3001
|
pure.push(armId)
|
|
2722
3002
|
continue
|
|
2723
3003
|
}
|
|
@@ -2747,8 +3027,8 @@ function lexicalDecisionInventory(
|
|
|
2747
3027
|
const contextSnapshotId = contextSnapshotIdByKey.get(candidate.contextKey)
|
|
2748
3028
|
if (contextSnapshotId === undefined) throw new Error('parseman: decision occurrence lost its context snapshot')
|
|
2749
3029
|
const dynamicScan = candidate.atom === 'dispatch' && def.tag === 'dispatch'
|
|
2750
|
-
?
|
|
2751
|
-
:
|
|
3030
|
+
? dynamicScanConsumers.has(def.selector)
|
|
3031
|
+
: dynamicScanConsumers.has(candidate.parser)
|
|
2752
3032
|
if (dynamicScan) {
|
|
2753
3033
|
precisionNotes.push('dynamic scan consumer forbids pending range reuse')
|
|
2754
3034
|
}
|
|
@@ -2904,7 +3184,7 @@ function lexicalDecisionInventory(
|
|
|
2904
3184
|
&& !intersects(armFirst, familyFirst)) {
|
|
2905
3185
|
return { armId, acceptance: { kind: 'impossible' }, usesRouted: false, dynamicGate }
|
|
2906
3186
|
}
|
|
2907
|
-
const compatible = compatibleLanguageViews(lead.ir, familyIr)
|
|
3187
|
+
const compatible = compatibleLanguageViews(lead.ir, familyIr, coversContinuation)
|
|
2908
3188
|
if (compatible !== undefined) {
|
|
2909
3189
|
if (compatible.every(view => view.relation === 'equal')
|
|
2910
3190
|
&& familyPredicates.length > 0
|
|
@@ -3294,6 +3574,7 @@ function lexicalCapabilityInventory(
|
|
|
3294
3574
|
roots: ReadonlyArray<Combinator<unknown>>,
|
|
3295
3575
|
resolve?: (name: string) => Combinator<unknown> | undefined,
|
|
3296
3576
|
): {
|
|
3577
|
+
closureReceipt: LexicalCapabilityClosureReceipt
|
|
3297
3578
|
capabilities: LexicalCapabilitySite[]
|
|
3298
3579
|
languages: LexicalCapabilityLanguage[]
|
|
3299
3580
|
bindingEdges: LexicalBindingEdge[]
|
|
@@ -3315,6 +3596,11 @@ function lexicalCapabilityInventory(
|
|
|
3315
3596
|
decisionEffectPlan: LexicalDecisionEffectInventory
|
|
3316
3597
|
} {
|
|
3317
3598
|
const inventory = lexicalCapabilityCandidates(roots, resolve)
|
|
3599
|
+
// Capture graph occurrence, context, and fixed-edge identity before any
|
|
3600
|
+
// language/decision/effect derivation. The final closure transcript below is
|
|
3601
|
+
// canonicalized independently against this immutable authority.
|
|
3602
|
+
const rawClosureReceipt = capabilityOccurrenceReceipt(inventory)
|
|
3603
|
+
const dynamicScanConsumers = dynamicScanReachability(roots, resolve)
|
|
3318
3604
|
const occurrenceContexts = new Map<Combinator<unknown>, Set<string>>()
|
|
3319
3605
|
for (const candidate of inventory.candidates) {
|
|
3320
3606
|
let contexts = occurrenceContexts.get(candidate.parser)
|
|
@@ -3350,7 +3636,7 @@ function lexicalCapabilityInventory(
|
|
|
3350
3636
|
}))
|
|
3351
3637
|
const contextSnapshotIdByKey = new Map(contextSnapshots.map(snapshot => [snapshot.key, snapshot.id]))
|
|
3352
3638
|
const decisionInventory = lexicalDecisionInventory(
|
|
3353
|
-
inventory.candidates, contextSnapshotIdByKey, resolve,
|
|
3639
|
+
inventory.candidates, contextSnapshotIdByKey, dynamicScanConsumers, resolve,
|
|
3354
3640
|
)
|
|
3355
3641
|
const transitionDiagnostics: LexicalTransitionDiagnosticPlan[] = []
|
|
3356
3642
|
const transitionDiagnosticIdByKey = new Map<string, number>()
|
|
@@ -3805,7 +4091,11 @@ function lexicalCapabilityInventory(
|
|
|
3805
4091
|
)
|
|
3806
4092
|
return { ...site, obligations, status: derivedCapabilityStatus(obligations) }
|
|
3807
4093
|
})
|
|
3808
|
-
|
|
4094
|
+
const boundaryPlans: LexicalBoundaryPlan[] = boundaryTopologies.length === 0
|
|
4095
|
+
? [] : [{ id: 0, kind: 'token-context-transaction' }]
|
|
4096
|
+
const materializationPlans: LexicalMaterializationPlan[] = boundaryTopologies.length === 0
|
|
4097
|
+
? [] : [{ id: 0, kind: 'token-source-range' }]
|
|
4098
|
+
const closureTables = {
|
|
3809
4099
|
capabilities, languages, bindingEdges, bindingProjections, terminalProjections,
|
|
3810
4100
|
decisionFamilies: decisionInventory.families,
|
|
3811
4101
|
decisionOutcomes: decisionInventory.outcomes,
|
|
@@ -3813,10 +4103,8 @@ function lexicalCapabilityInventory(
|
|
|
3813
4103
|
decisionEffectPlan: decisionEffects,
|
|
3814
4104
|
transitionDiagnostics,
|
|
3815
4105
|
controlPlans,
|
|
3816
|
-
boundaryPlans
|
|
3817
|
-
|
|
3818
|
-
materializationPlans: boundaryTopologies.length === 0
|
|
3819
|
-
? [] : [{ id: 0, kind: 'token-source-range' }],
|
|
4106
|
+
boundaryPlans,
|
|
4107
|
+
materializationPlans,
|
|
3820
4108
|
grammarWrapperSpecs,
|
|
3821
4109
|
grammarCaptureTriviaKinds,
|
|
3822
4110
|
boundaryTopologies,
|
|
@@ -3824,6 +4112,10 @@ function lexicalCapabilityInventory(
|
|
|
3824
4112
|
ownSkipPlans,
|
|
3825
4113
|
scanConsumerPolicies,
|
|
3826
4114
|
}
|
|
4115
|
+
return {
|
|
4116
|
+
closureReceipt: lexicalCapabilityClosureReceipt(rawClosureReceipt, closureTables),
|
|
4117
|
+
...closureTables,
|
|
4118
|
+
}
|
|
3827
4119
|
}
|
|
3828
4120
|
|
|
3829
4121
|
/** Re-enumerate the final graph so a dropped/filtered candidate fails closed. */
|
|
@@ -3941,17 +4233,19 @@ export function collectLexicalCapabilities(
|
|
|
3941
4233
|
roots: ReadonlyArray<Combinator<unknown>>,
|
|
3942
4234
|
resolve?: (name: string) => Combinator<unknown> | undefined,
|
|
3943
4235
|
): LexicalCapabilityInventory {
|
|
3944
|
-
const
|
|
3945
|
-
const { languages: capabilityLanguages, ...fields } =
|
|
3946
|
-
|
|
4236
|
+
const built = lexicalCapabilityInventory(roots, resolve)
|
|
4237
|
+
const { closureReceipt, languages: capabilityLanguages, ...fields } = built
|
|
4238
|
+
const inventory: LexicalCapabilityInventory = {
|
|
3947
4239
|
...fields,
|
|
3948
4240
|
capabilityLanguages,
|
|
3949
|
-
capabilityComplete:
|
|
3950
|
-
&&
|
|
3951
|
-
&&
|
|
4241
|
+
capabilityComplete: built.capabilities.every(site => site.status.kind !== 'gap')
|
|
4242
|
+
&& built.bindingEdges.every(edge => edge.status.kind !== 'gap')
|
|
4243
|
+
&& built.decisionEffectPlan.decisionEffects.every(effect =>
|
|
3952
4244
|
effect.phase.executableLowering.kind !== 'gap')
|
|
3953
|
-
&&
|
|
4245
|
+
&& built.scanConsumerPolicies.every(policy => policy.status.kind !== 'gap'),
|
|
3954
4246
|
}
|
|
4247
|
+
assertLexicalCapabilityBijection(closureReceipt, inventory)
|
|
4248
|
+
return inventory
|
|
3955
4249
|
}
|
|
3956
4250
|
|
|
3957
4251
|
function tokenBoundary(
|
|
@@ -6,6 +6,10 @@ export type CstCaptureBuf = {
|
|
|
6
6
|
/** Table-host specialisation: collect source-order raw entries without the
|
|
7
7
|
* duplicate semantic children view. Selected once for the enclosing node. */
|
|
8
8
|
rawOnly?: true | undefined
|
|
9
|
+
/** Direct reducer proved `rawChildren` unobservable. Preserve only the source-order
|
|
10
|
+
* COUNT needed by trivia insertion/rollback; never retain raw entry values. */
|
|
11
|
+
noRaw?: true | undefined
|
|
12
|
+
rawLen?: number | undefined
|
|
9
13
|
single?: unknown | undefined
|
|
10
14
|
ch?: unknown[] | undefined
|
|
11
15
|
rawSingle?: unknown | undefined
|
|
@@ -54,7 +58,9 @@ export function finishCstBuf(buf: CstCaptureBuf | undefined): {
|
|
|
54
58
|
} {
|
|
55
59
|
if (!buf) return { children: EMPTY, rawChildren: EMPTY, triviaLog: EMPTY_TL }
|
|
56
60
|
const children = buf.ch ?? (buf.single !== undefined ? [buf.single] : EMPTY)
|
|
57
|
-
const rawChildren = buf.
|
|
61
|
+
const rawChildren = buf.noRaw === true
|
|
62
|
+
? EMPTY
|
|
63
|
+
: buf.raw ?? (buf.rawSingle !== undefined ? [buf.rawSingle] : EMPTY)
|
|
58
64
|
const triviaLog = buf.tl ?? EMPTY_TL
|
|
59
65
|
return { children, rawChildren, triviaLog }
|
|
60
66
|
}
|
|
@@ -94,7 +100,8 @@ export function pushCstChild(ctx: ParseContext, built: unknown, rawEntry: unknow
|
|
|
94
100
|
else b.single = built
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
if (b.
|
|
103
|
+
if (b.noRaw === true) b.rawLen = (b.rawLen ?? 0) + 1
|
|
104
|
+
else if (b.raw) b.raw.push(rawEntry)
|
|
98
105
|
else if (b.rawSingle !== undefined) { b.raw = [b.rawSingle, rawEntry]; b.rawSingle = undefined }
|
|
99
106
|
else b.rawSingle = rawEntry
|
|
100
107
|
return
|
|
@@ -107,6 +114,7 @@ export function pushCstChild(ctx: ParseContext, built: unknown, rawEntry: unknow
|
|
|
107
114
|
export function cstRawLen(ctx: ParseContext): number {
|
|
108
115
|
const b = ctx._cstBuf
|
|
109
116
|
if (b) {
|
|
117
|
+
if (b.noRaw === true) return b.rawLen ?? 0
|
|
110
118
|
if (b.raw) return b.raw.length
|
|
111
119
|
return b.rawSingle !== undefined ? 1 : 0
|
|
112
120
|
}
|
|
@@ -156,9 +164,10 @@ function rollbackBufList(
|
|
|
156
164
|
if (len === 0) b[keyMulti] = undefined
|
|
157
165
|
else if (len === 1) { b[keySingle] = arr[0]; b[keyMulti] = undefined }
|
|
158
166
|
// Guarded like every other truncation — see rollbackCstCapture. This is the
|
|
159
|
-
// buffered node-capture path
|
|
160
|
-
//
|
|
161
|
-
//
|
|
167
|
+
// buffered node-capture path. `demoteCapturedToRaw` is its remaining caller;
|
|
168
|
+
// the table closure's fixed raw/child rollback fields stay inline so its hot
|
|
169
|
+
// speculative path does not rediscover them through dynamic property keys.
|
|
170
|
+
// This is where the redundant-store rate is highest: over a backtracking fixture
|
|
162
171
|
// this branch ran 32,800 times and 31,198 of those — 95% — restored a
|
|
163
172
|
// length that had not moved.
|
|
164
173
|
else if (arr.length !== len) arr.length = len
|
|
@@ -221,8 +230,8 @@ export function rollbackCstCaptureAt(
|
|
|
221
230
|
raw: number,
|
|
222
231
|
tlog: number,
|
|
223
232
|
leaves: number,
|
|
224
|
-
fields: number,
|
|
225
|
-
errors: number,
|
|
233
|
+
fields: number | undefined,
|
|
234
|
+
errors: number | undefined,
|
|
226
235
|
): void {
|
|
227
236
|
// Truncate the flat recovery-error sink alongside the CST, so a rolled-back
|
|
228
237
|
// speculative branch leaves no ghost error (see saveCstMark). Guarded on a
|
|
@@ -238,19 +247,33 @@ export function rollbackCstCaptureAt(
|
|
|
238
247
|
if (ctx._errors && errors !== undefined && ctx._errors.length !== errors) ctx._errors.length = errors
|
|
239
248
|
const b = ctx._cstBuf
|
|
240
249
|
if (b) {
|
|
241
|
-
|
|
242
|
-
|
|
250
|
+
if (b.noRaw === true) {
|
|
251
|
+
if (b.rawLen !== raw) b.rawLen = raw
|
|
252
|
+
} else {
|
|
253
|
+
const rawList = b.raw
|
|
254
|
+
if (rawList) {
|
|
255
|
+
if (raw === 0) b.raw = undefined
|
|
256
|
+
else if (raw === 1) { b.rawSingle = rawList[0]; b.raw = undefined }
|
|
257
|
+
else if (rawList.length !== raw) rawList.length = raw
|
|
258
|
+
} else if (raw === 0) b.rawSingle = undefined
|
|
259
|
+
}
|
|
260
|
+
const childList = b.ch
|
|
261
|
+
if (childList) {
|
|
262
|
+
if (leaves === 0) b.ch = undefined
|
|
263
|
+
else if (leaves === 1) { b.single = childList[0]; b.ch = undefined }
|
|
264
|
+
else if (childList.length !== leaves) childList.length = leaves
|
|
265
|
+
} else if (leaves === 0) b.single = undefined
|
|
243
266
|
if (b.tl) {
|
|
244
267
|
if (tlog === 0) b.tl = undefined
|
|
245
268
|
else if (b.tl.length !== tlog) b.tl.length = tlog
|
|
246
269
|
}
|
|
247
|
-
if (ctx._fields && ctx._fields.length !== fields) ctx._fields.length = fields
|
|
270
|
+
if (ctx._fields && fields !== undefined && ctx._fields.length !== fields) ctx._fields.length = fields
|
|
248
271
|
return
|
|
249
272
|
}
|
|
250
273
|
if (ctx._cstRawChildren && ctx._cstRawChildren.length !== raw) ctx._cstRawChildren.length = raw
|
|
251
274
|
if (ctx._cstTriviaLog && ctx._cstTriviaLog.length !== tlog) ctx._cstTriviaLog.length = tlog
|
|
252
275
|
if (ctx._cstLeaves && ctx._cstLeaves.length !== leaves) ctx._cstLeaves.length = leaves
|
|
253
|
-
if (ctx._fields && ctx._fields.length !== fields) ctx._fields.length = fields
|
|
276
|
+
if (ctx._fields && fields !== undefined && ctx._fields.length !== fields) ctx._fields.length = fields
|
|
254
277
|
}
|
|
255
278
|
|
|
256
279
|
export function pushCstTriviaEntry(
|