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
package/src/table/emit.ts
CHANGED
|
@@ -208,7 +208,7 @@ function emitAssemblies(prog: TableProgram, cfgs: readonly RunCfg[]): string[] {
|
|
|
208
208
|
seen.add(key)
|
|
209
209
|
let em
|
|
210
210
|
try {
|
|
211
|
-
em = emitAssemblySource(t, prog, cfg, extraIps)
|
|
211
|
+
em = emitAssemblySource(t, prog, cfg, extraIps, true)
|
|
212
212
|
} catch (e) {
|
|
213
213
|
if (e instanceof Unemittable) continue
|
|
214
214
|
throw e
|
package/src/table/encode.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AutoNotCheck, Combinator, FirstSet, ParserDef } from '../types.ts'
|
|
2
|
-
import { classifyFinalChoice, firstSetOf, matchesEmpty, union, type RefResolver } from '../combinators/first-set.ts'
|
|
2
|
+
import { classifyFinalChoice, firstSetOf, intersects, matchesEmpty, union, type RefResolver } from '../combinators/first-set.ts'
|
|
3
3
|
import { childrenOf } from '../analysis/gating.ts'
|
|
4
|
+
import { capturesLeaf, mayCommitFailure, mayLeavePartialCapture } from '../analysis/commitment.ts'
|
|
4
5
|
import { getCoreLiteralValue } from '../combinators/choice.ts'
|
|
5
6
|
import { deriveExpected } from '../combinators/expect.ts'
|
|
6
7
|
import { assertionFailureExpected, directTerminalFailureExpected } from '../combinators/expected.ts'
|
|
@@ -33,7 +34,7 @@ import { covKindCode, encodeClassSpec, ownTableProgram } from './program.ts'
|
|
|
33
34
|
import type { GrammarCoveragePlan } from '../compiler/grammar-coverage-ids.ts'
|
|
34
35
|
import { directArrayProjection } from '../compiler/direct-projection.ts'
|
|
35
36
|
import {
|
|
36
|
-
|
|
37
|
+
collectLexicalCapabilities, winnerWrapsReference,
|
|
37
38
|
} from '../compiler/token-capability.ts'
|
|
38
39
|
import { directExecutableTokenBody, type ExecutableLexNode } from '../compiler/token-alphabet.ts'
|
|
39
40
|
|
|
@@ -141,6 +142,25 @@ class Encoder {
|
|
|
141
142
|
private lexIndex = new Map<string, number>()
|
|
142
143
|
lexPrograms: LexProgramSpec[] = []
|
|
143
144
|
private lexProgramIndex = new Map<string, number>()
|
|
145
|
+
private choiceRollbackMasks = new Map<number, number>()
|
|
146
|
+
private nonCommittingChoiceSites = new Set<number>()
|
|
147
|
+
private failureRollbackCleanSites = new Set<number>()
|
|
148
|
+
/**
|
|
149
|
+
* `hasSideWriter` memo for `failureNeedsRollback`, shared across every call
|
|
150
|
+
* for the lifetime of this `Encoder`. A cached entry is written only when
|
|
151
|
+
* `complete` is true, which means the answer is a path-independent fact
|
|
152
|
+
* about that combinator node — it does not depend on which root's traversal
|
|
153
|
+
* asked, only on the node's own reachable subgraph. Scoping the memo per call
|
|
154
|
+
* (as a local inside `failureNeedsRollback`) was correct but discarded that
|
|
155
|
+
* fact after every arm, so a grammar with many ordered-choice arms / optional
|
|
156
|
+
* / attempt sites re-walked the SAME shared rule graph from scratch for each
|
|
157
|
+
* site — O(sites × graph size) instead of O(graph size). Measured on the
|
|
158
|
+
* scss-parser grammar (composed CSS-base superset, the deepest/largest of the
|
|
159
|
+
* four grammars): 47-50s for one `compileRuleMap` with the per-call memo,
|
|
160
|
+
* versus the whole macro transform completing in under a second once this
|
|
161
|
+
* memo is hoisted here.
|
|
162
|
+
*/
|
|
163
|
+
private sideWriterMemo = new Map<Combinator<unknown>, boolean>()
|
|
144
164
|
labels: readonly string[] | undefined = undefined
|
|
145
165
|
/** Trivia table in scope at the row being encoded — codegen's `ctx.activeTrivia`. */
|
|
146
166
|
activeTrivia: Combinator<unknown> | undefined = undefined
|
|
@@ -611,6 +631,81 @@ class Encoder {
|
|
|
611
631
|
return w === undefined ? undefined : (name: string) => w[name]
|
|
612
632
|
}
|
|
613
633
|
|
|
634
|
+
/**
|
|
635
|
+
* Whether a failed ordered-choice arm can leave state that the enclosing
|
|
636
|
+
* choice must restore before trying its successor.
|
|
637
|
+
*
|
|
638
|
+
* `mayLeavePartialCapture` is the existing sound CST/trivia proof. The extra
|
|
639
|
+
* writer census covers the side sinks that proof deliberately does not model:
|
|
640
|
+
* fields, recovered errors, and interpreted scanners. Strict assemblies do
|
|
641
|
+
* not run repeat recovery, but these rows can still publish through a caller's
|
|
642
|
+
* context. Cycles are safe to stop: revisiting the same combinator cannot
|
|
643
|
+
* discover a writer the first traversal did not already reach.
|
|
644
|
+
*/
|
|
645
|
+
private failureNeedsRollback(
|
|
646
|
+
p: Combinator<unknown>,
|
|
647
|
+
autoNot: readonly AutoNotCheck[] | null | undefined,
|
|
648
|
+
): boolean {
|
|
649
|
+
const refName = (p as unknown as { _ruleName?: string })._ruleName
|
|
650
|
+
const winner = refName === undefined ? undefined : this.winners?.[refName]
|
|
651
|
+
const root = winner !== undefined && !winnerWrapsReference(winner, p) ? winner : p
|
|
652
|
+
const sideWriterMemo = this.sideWriterMemo
|
|
653
|
+
// A single mutable `seen` set threaded through the whole call, rather than
|
|
654
|
+
// copied at every recursion level: this DFS visits children strictly
|
|
655
|
+
// sequentially, so marking `current` in progress before recursing and
|
|
656
|
+
// clearing it once its subtree is fully explored reproduces the exact same
|
|
657
|
+
// ancestor-path membership an immutable copy-per-level would have computed,
|
|
658
|
+
// without the O(depth) allocation on every node.
|
|
659
|
+
const seen = new Set<Combinator<unknown>>()
|
|
660
|
+
const hasSideWriter = (current: Combinator<unknown>): { found: boolean; complete: boolean } => {
|
|
661
|
+
const cached = sideWriterMemo.get(current)
|
|
662
|
+
if (cached !== undefined) return { found: cached, complete: true }
|
|
663
|
+
if (seen.has(current)) return { found: false, complete: false }
|
|
664
|
+
seen.add(current)
|
|
665
|
+
const def = current._def
|
|
666
|
+
let result: { found: boolean; complete: boolean }
|
|
667
|
+
switch (def.tag) {
|
|
668
|
+
case 'field':
|
|
669
|
+
case 'expect':
|
|
670
|
+
case 'recover':
|
|
671
|
+
case 'scanTo':
|
|
672
|
+
case 'unknown':
|
|
673
|
+
result = { found: true, complete: true }
|
|
674
|
+
break
|
|
675
|
+
case 'lazy': {
|
|
676
|
+
const name = (current as unknown as { _ruleName?: string })._ruleName
|
|
677
|
+
const resolvedWinner = name === undefined ? undefined : this.winners?.[name]
|
|
678
|
+
if (resolvedWinner !== undefined && !winnerWrapsReference(resolvedWinner, current)) {
|
|
679
|
+
result = hasSideWriter(resolvedWinner)
|
|
680
|
+
break
|
|
681
|
+
}
|
|
682
|
+
try { result = hasSideWriter(def.thunk()) }
|
|
683
|
+
catch { result = { found: true, complete: true } }
|
|
684
|
+
break
|
|
685
|
+
}
|
|
686
|
+
default: {
|
|
687
|
+
let complete = true
|
|
688
|
+
result = { found: false, complete: true }
|
|
689
|
+
for (const child of childrenOf(def)) {
|
|
690
|
+
const childResult = hasSideWriter(child)
|
|
691
|
+
if (childResult.found) {
|
|
692
|
+
result = { found: true, complete: true }
|
|
693
|
+
break
|
|
694
|
+
}
|
|
695
|
+
complete &&= childResult.complete
|
|
696
|
+
}
|
|
697
|
+
if (!result.found) result = { found: false, complete }
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
seen.delete(current)
|
|
701
|
+
if (result.found || result.complete) sideWriterMemo.set(current, result.found)
|
|
702
|
+
return result
|
|
703
|
+
}
|
|
704
|
+
return mayLeavePartialCapture(root, new Set(), true)
|
|
705
|
+
|| hasSideWriter(root).found
|
|
706
|
+
|| ((autoNot?.length ?? 0) > 0 && capturesLeaf(root))
|
|
707
|
+
}
|
|
708
|
+
|
|
614
709
|
/** A first set becomes a char class string, or −1 for `any`. */
|
|
615
710
|
private charClass(fs: FirstSet): number {
|
|
616
711
|
if (fs.kind !== 'ranges' || fs.ranges.length === 0) return -1
|
|
@@ -1074,14 +1169,53 @@ class Encoder {
|
|
|
1074
1169
|
// Arm ORDER is preserved on both, which is what makes this a PEG-safe
|
|
1075
1170
|
// change rather than a reordering.
|
|
1076
1171
|
const finalChoice = classifyFinalChoice(arms, rr)
|
|
1077
|
-
|
|
1078
|
-
|
|
1172
|
+
// attempt() preserves its child's success language, but the broad
|
|
1173
|
+
// classifier deliberately treats the transaction as nullable/unknown so
|
|
1174
|
+
// it can never by itself authorize exclusive one-arm selection. Recover
|
|
1175
|
+
// only the narrower fact needed by an ORDERED choice: a non-nullable
|
|
1176
|
+
// child can receive an individual rejection gate. Keep that authority
|
|
1177
|
+
// out if it would make an otherwise open site fully disjoint, preserving
|
|
1178
|
+
// the transaction's standing exclusion from exclusive dispatch.
|
|
1179
|
+
const transactionalFirst = arms.map((arm, i): FirstSet | undefined => {
|
|
1180
|
+
if (!finalChoice.nullable[i]) return undefined
|
|
1181
|
+
const def = arm._def as ParserDef
|
|
1182
|
+
if (def.tag !== 'attempt' || matchesEmpty(def.parser, new Set(), rr)) return undefined
|
|
1183
|
+
return firstSetOf(def.parser, new Set(), rr)
|
|
1184
|
+
})
|
|
1185
|
+
const gateFirst = finalChoice.firstSets.map((first, i) => transactionalFirst[i] ?? first)
|
|
1186
|
+
const classes = gateFirst.map((first, i) => finalChoice.nullable[i] && transactionalFirst[i] === undefined
|
|
1187
|
+
? -1 : this.charClass(first))
|
|
1188
|
+
if (transactionalFirst.some(first => first !== undefined) && classes.every(cls => cls >= 0)) {
|
|
1189
|
+
let overlap = false
|
|
1190
|
+
for (let i = 0; i < gateFirst.length && !overlap; i++) {
|
|
1191
|
+
for (let j = i + 1; j < gateFirst.length; j++) {
|
|
1192
|
+
if (intersects(gateFirst[i]!, gateFirst[j]!)) { overlap = true; break }
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (!overlap) for (let i = 0; i < classes.length; i++) {
|
|
1196
|
+
if (transactionalFirst[i] !== undefined) classes[i] = -1
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1079
1199
|
const dispIdx = this.disp.length
|
|
1080
1200
|
this.disp.push(classes)
|
|
1081
1201
|
// PER-ARM EXPECTED SETS RIDE ALONG, after the arm offsets. While the best
|
|
1082
1202
|
// failure remains at the choice position, an arm declined by the driver's
|
|
1083
1203
|
// char gate contributes the static opener set the interpreter would get by
|
|
1084
1204
|
// entering it. A deeper dynamic arm failure discards those shallower sets.
|
|
1205
|
+
// One compiler-only bit mask records which failed arms can actually
|
|
1206
|
+
// dirty a rollback sink. It is consumed while printing emitted strict
|
|
1207
|
+
// assemblies and never serialized; tolerant assemblies retain the
|
|
1208
|
+
// universal rollback because repeat recovery can publish an error from
|
|
1209
|
+
// inside any arm. Arity >31 falls back to -1 rather than truncating.
|
|
1210
|
+
let rollbackMask = kids.length > 31 ? -1 : 0
|
|
1211
|
+
if (rollbackMask === 0) {
|
|
1212
|
+
for (let i = 0; i < arms.length; i++) {
|
|
1213
|
+
const src = order[i]!
|
|
1214
|
+
if (this.failureNeedsRollback(arms[i]!, d.autoNot[src])) {
|
|
1215
|
+
rollbackMask |= 1 << i
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1085
1219
|
const head = this.emitHead(OP_CHOICE, 3 + 2 * kids.length)
|
|
1086
1220
|
this.code[head + 1] = dispIdx
|
|
1087
1221
|
this.code[head + 2] = kids.length
|
|
@@ -1093,6 +1227,10 @@ class Encoder {
|
|
|
1093
1227
|
this.code[head + 4 + i] = kids[i]!
|
|
1094
1228
|
this.code[head + 4 + kids.length + i] = this.expected(deriveExpected(arms[i]!))
|
|
1095
1229
|
}
|
|
1230
|
+
this.choiceRollbackMasks.set(head, rollbackMask)
|
|
1231
|
+
if (arms.every(arm => !mayCommitFailure(arm, new Set(), rr))) {
|
|
1232
|
+
this.nonCommittingChoiceSites.add(head)
|
|
1233
|
+
}
|
|
1096
1234
|
return head
|
|
1097
1235
|
}
|
|
1098
1236
|
case 'many':
|
|
@@ -1143,8 +1281,11 @@ class Encoder {
|
|
|
1143
1281
|
? this.emit(OP_REP, child, d.min, d.max ?? -1, sep, flags, this.expected(deriveExpected(d.parser)))
|
|
1144
1282
|
: this.emit(OP_REP, child, d.min, d.max ?? -1, sep, flags)
|
|
1145
1283
|
}
|
|
1146
|
-
case 'optional':
|
|
1147
|
-
|
|
1284
|
+
case 'optional': {
|
|
1285
|
+
const head = this.emit(OP_OPT, this.node(d.parser).ip)
|
|
1286
|
+
if (!this.failureNeedsRollback(d.parser, undefined)) this.failureRollbackCleanSites.add(head)
|
|
1287
|
+
return head
|
|
1288
|
+
}
|
|
1148
1289
|
case 'transform': {
|
|
1149
1290
|
// Declared on the def and not lowered here. Refuse rather than assume it is
|
|
1150
1291
|
// inert: a recognition-only transform suppresses its value, and a table that
|
|
@@ -1444,6 +1585,7 @@ class Encoder {
|
|
|
1444
1585
|
// lowering was correct only for a choice arm.
|
|
1445
1586
|
case 'attempt': {
|
|
1446
1587
|
const inner = this.emit(OP_ATTEMPT, this.node(d.parser).ip)
|
|
1588
|
+
if (!this.failureNeedsRollback(d.parser, undefined)) this.failureRollbackCleanSites.add(inner)
|
|
1447
1589
|
// THE FIRST-SET FAIL-FAST GUARD, lowered as the `OP_GATE` row the `node()`
|
|
1448
1590
|
// case already uses — it is the same guard, written twice in the
|
|
1449
1591
|
// interpreter (`attempt.ts:22`, `node.ts:239`). It is NOT merely an
|
|
@@ -1652,6 +1794,10 @@ class Encoder {
|
|
|
1652
1794
|
...(this.rec ? { rec: 1 as const } : {}),
|
|
1653
1795
|
...(this.cov === undefined ? {} : { cov: this.cov }),
|
|
1654
1796
|
lines: this.track ? 1 : 0,
|
|
1797
|
+
}, undefined, {
|
|
1798
|
+
choiceRollbackMasks: this.choiceRollbackMasks,
|
|
1799
|
+
failureRollbackCleanSites: this.failureRollbackCleanSites,
|
|
1800
|
+
nonCommittingChoiceSites: this.nonCommittingChoiceSites,
|
|
1655
1801
|
})
|
|
1656
1802
|
}
|
|
1657
1803
|
}
|
|
@@ -1748,7 +1894,6 @@ export function encodeTableProgram(
|
|
|
1748
1894
|
const lexicalRoots = [...names].sort().map(name => ruleMap[name]!)
|
|
1749
1895
|
const resolveLexical = (name: string): Combinator<unknown> | undefined => ruleMap[name]
|
|
1750
1896
|
const capabilities = collectLexicalCapabilities(lexicalRoots, resolveLexical)
|
|
1751
|
-
assertLexicalCapabilityClosure(lexicalRoots, capabilities, resolveLexical)
|
|
1752
1897
|
const enc = new Encoder(resolvedSettings, capabilities.capabilityComplete)
|
|
1753
1898
|
enc.winners = ruleMap
|
|
1754
1899
|
for (const name of names) enc.encodeRule(name, ruleMap[name]!)
|
package/src/table/ops.ts
CHANGED
|
@@ -27,8 +27,9 @@ export const OP_SEQ = 3
|
|
|
27
27
|
/** `SEQV n c1 … cn` — `SEQ` with `valueUnused`: terms run, no tuple is built. */
|
|
28
28
|
export const OP_SEQV = 4
|
|
29
29
|
/**
|
|
30
|
-
* `CHOICE d n fx c1 … cn` — `d` indexes a dispatch table (or −1 for
|
|
31
|
-
* try); `fx` indexes the choice's OWN expected set
|
|
30
|
+
* `CHOICE d n fx c1 … cn e1 … en` — `d` indexes a dispatch table (or −1 for
|
|
31
|
+
* ordered try); `fx` indexes the choice's OWN expected set and each `e` is one
|
|
32
|
+
* arm's set. Compiler-only rollback authority is deliberately not on the wire.
|
|
32
33
|
*
|
|
33
34
|
* Ordered failure keeps the expected sets at the deepest arm offset, merging
|
|
34
35
|
* exact ties in source order. Exclusive dispatch propagates its sole selected
|
package/src/table/program.ts
CHANGED
|
@@ -490,12 +490,53 @@ export type ResolvedTable = {
|
|
|
490
490
|
*/
|
|
491
491
|
const TABLE_RUNTIME = Symbol('parseman.tableRuntime')
|
|
492
492
|
type RuntimeTableProgram = TableProgram & {
|
|
493
|
-
readonly [TABLE_RUNTIME]?: {
|
|
493
|
+
readonly [TABLE_RUNTIME]?: {
|
|
494
|
+
resolved: ResolvedTable | undefined
|
|
495
|
+
readonly choiceRollbackMasks?: ReadonlyMap<number, number>
|
|
496
|
+
readonly nonCommittingChoiceSites?: ReadonlySet<number>
|
|
497
|
+
readonly failureRollbackCleanSites?: ReadonlySet<number>
|
|
498
|
+
}
|
|
494
499
|
}
|
|
495
500
|
|
|
496
501
|
/** Give an internally-created program its fixed-shape runtime owner. */
|
|
497
|
-
export function ownTableProgram(
|
|
498
|
-
|
|
502
|
+
export function ownTableProgram(
|
|
503
|
+
prog: TableProgram,
|
|
504
|
+
resolved?: ResolvedTable,
|
|
505
|
+
caches: {
|
|
506
|
+
readonly choiceRollbackMasks?: ReadonlyMap<number, number>
|
|
507
|
+
readonly failureRollbackCleanSites?: ReadonlySet<number>
|
|
508
|
+
readonly nonCommittingChoiceSites?: ReadonlySet<number>
|
|
509
|
+
} = {},
|
|
510
|
+
): TableProgram {
|
|
511
|
+
const runtime = (prog as RuntimeTableProgram)[TABLE_RUNTIME]
|
|
512
|
+
return {
|
|
513
|
+
...prog,
|
|
514
|
+
[TABLE_RUNTIME]: {
|
|
515
|
+
resolved,
|
|
516
|
+
choiceRollbackMasks: caches.choiceRollbackMasks ?? runtime?.choiceRollbackMasks,
|
|
517
|
+
nonCommittingChoiceSites: caches.nonCommittingChoiceSites ?? runtime?.nonCommittingChoiceSites,
|
|
518
|
+
failureRollbackCleanSites: caches.failureRollbackCleanSites ?? runtime?.failureRollbackCleanSites,
|
|
519
|
+
},
|
|
520
|
+
} as RuntimeTableProgram
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** Compiler-only choice rollback authority, consumed while printing a static
|
|
524
|
+
* assembly and never serialized into the shipped table. */
|
|
525
|
+
export function choiceRollbackMask(prog: TableProgram, ip: number): number | undefined {
|
|
526
|
+
return (prog as RuntimeTableProgram)[TABLE_RUNTIME]?.choiceRollbackMasks?.get(ip)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** Compiler-only proof that no arm at this choice can raise a committed
|
|
530
|
+
* failure. Static assembly may defer its diagnostic merge until total failure. */
|
|
531
|
+
export function choiceCannotCommit(prog: TableProgram, ip: number): boolean {
|
|
532
|
+
return (prog as RuntimeTableProgram)[TABLE_RUNTIME]?.nonCommittingChoiceSites?.has(ip) === true
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** Compiler-only authority that a failed transactional child cannot leak any
|
|
536
|
+
* capture, trivia, field, or error sink. Static assembly consumes it to omit the
|
|
537
|
+
* enclosing mark and rollback; it is never serialized into the shipped table. */
|
|
538
|
+
export function failureRollbackClean(prog: TableProgram, ip: number): boolean {
|
|
539
|
+
return (prog as RuntimeTableProgram)[TABLE_RUNTIME]?.failureRollbackCleanSites?.has(ip) === true
|
|
499
540
|
}
|
|
500
541
|
|
|
501
542
|
/** A compiled entry, shaped exactly like a codegen rule function. */
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
OP_ATTEMPT, OP_CHOICE, OP_FIELD, OP_GATE, OP_LABEL, OP_LEAF, OP_LIT, OP_NODE, OP_NOT,
|
|
3
|
+
OP_PEEK, OP_RULE, OP_RX, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV,
|
|
4
|
+
OP_SEQX, OP_TOKEN, OP_XFORM,
|
|
5
|
+
} from './ops.ts'
|
|
2
6
|
|
|
3
7
|
/** Pure recognition only. Consumption owns value, CST, line, failure and cursor effects. */
|
|
4
8
|
export type ScalarRecognizer = (input: string, pos: number, seed?: number) => number
|
|
@@ -49,3 +53,211 @@ export function scalarTerminalNotChild(code: ArrayLike<number>, ip: number): num
|
|
|
49
53
|
const op = code[child]
|
|
50
54
|
return op === OP_RX || op === OP_LIT ? child : -1
|
|
51
55
|
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A direct scalar terminal that every successful execution of `ip` must match
|
|
59
|
+
* first, through wrappers that cannot consume or select before their child.
|
|
60
|
+
* Returns only after `minDepth` edges so a caller never duplicates recognition
|
|
61
|
+
* merely to avoid one already-cheap terminal call.
|
|
62
|
+
*/
|
|
63
|
+
export function leadingScalarTerminal(
|
|
64
|
+
code: ArrayLike<number>, ip: number, minDepth = 2, throughAttempt = true, throughPeek = false,
|
|
65
|
+
): number {
|
|
66
|
+
const seen = new Set<number>()
|
|
67
|
+
let at = ip
|
|
68
|
+
let depth = 0
|
|
69
|
+
while (!seen.has(at)) {
|
|
70
|
+
seen.add(at)
|
|
71
|
+
const op = code[at]
|
|
72
|
+
if (op === OP_LIT || op === OP_RX) return depth >= minDepth ? at : -1
|
|
73
|
+
if (op === OP_RULE || op === OP_LABEL || op === OP_TOKEN || (throughAttempt && op === OP_ATTEMPT)) {
|
|
74
|
+
at = code[at + 1]!
|
|
75
|
+
depth++
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
// A positive lookahead has exactly its child's success language and no
|
|
79
|
+
// effects on success. Choice predecisions may therefore execute a scalar
|
|
80
|
+
// child to reject the enclosing arm early. This is opt-in because callers
|
|
81
|
+
// that require a CONSUMING opener (notably exact start-failure diagnostics)
|
|
82
|
+
// must continue to treat peek() as zero-width.
|
|
83
|
+
if (throughPeek && op === OP_PEEK) {
|
|
84
|
+
at = code[at + 1]!
|
|
85
|
+
depth++
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
if (op === OP_GATE || op === OP_SCOPE || op === OP_SCOPE_CAP || op === OP_SCOPE_PLAIN
|
|
89
|
+
|| op === OP_XFORM || op === OP_NODE || op === OP_FIELD || op === OP_LEAF) {
|
|
90
|
+
at = code[at + 2]!
|
|
91
|
+
depth++
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
if (op === OP_SEQ || op === OP_SEQV) {
|
|
95
|
+
if (code[at + 1]! < 1) return -1
|
|
96
|
+
at = code[at + 2]!
|
|
97
|
+
depth++
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
if (op === OP_SEQX) {
|
|
101
|
+
if (code[at + 2]! < 1) return -1
|
|
102
|
+
at = code[at + 3]!
|
|
103
|
+
depth++
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
return -1
|
|
107
|
+
}
|
|
108
|
+
return -1
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type LeadingScalarSequence = {
|
|
112
|
+
readonly terminals: readonly number[]
|
|
113
|
+
/** The explicit ambient trivia slot between terms; -1 means no trivia. */
|
|
114
|
+
readonly trivia: number
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The first three complete scalar terms of a sequence under an explicit trivia
|
|
119
|
+
* scope. Unlike `leadingScalarTerminal`, each returned terminal is the WHOLE
|
|
120
|
+
* corresponding sequence term: recognizing it therefore identifies the exact
|
|
121
|
+
* position at which the enclosing sequence would scan trivia for the next one.
|
|
122
|
+
*
|
|
123
|
+
* This is rejection authority only. Gates, attempts, transforms and builders
|
|
124
|
+
* may still make the arm fail after the prefix matched, so callers must enter
|
|
125
|
+
* the ordinary arm on a positive result and may only skip it on a miss.
|
|
126
|
+
*/
|
|
127
|
+
export function leadingScalarSequence(
|
|
128
|
+
code: ArrayLike<number>, ip: number,
|
|
129
|
+
): LeadingScalarSequence | undefined {
|
|
130
|
+
const wholeScalar = (start: number): number => {
|
|
131
|
+
const seen = new Set<number>()
|
|
132
|
+
let at = start
|
|
133
|
+
while (!seen.has(at)) {
|
|
134
|
+
seen.add(at)
|
|
135
|
+
const op = code[at]
|
|
136
|
+
if (op === OP_LIT || op === OP_RX) return at
|
|
137
|
+
if (op === OP_RULE || op === OP_TOKEN || op === OP_ATTEMPT) {
|
|
138
|
+
at = code[at + 1]!
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
if (op === OP_GATE || op === OP_SCOPE_PLAIN
|
|
142
|
+
|| op === OP_XFORM || op === OP_NODE || op === OP_FIELD || op === OP_LEAF) {
|
|
143
|
+
at = code[at + 2]!
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
if (op === OP_SEQ || op === OP_SEQV) {
|
|
147
|
+
if (code[at + 1] !== 1) return -1
|
|
148
|
+
at = code[at + 2]!
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
if (op === OP_SEQX) {
|
|
152
|
+
if (code[at + 2] !== 1) return -1
|
|
153
|
+
at = code[at + 3]!
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
return -1
|
|
157
|
+
}
|
|
158
|
+
return -1
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const seen = new Set<number>()
|
|
162
|
+
let at = ip
|
|
163
|
+
let ambientTrivia: number | undefined
|
|
164
|
+
while (!seen.has(at)) {
|
|
165
|
+
seen.add(at)
|
|
166
|
+
const op = code[at]
|
|
167
|
+
if (op === OP_RULE) {
|
|
168
|
+
at = code[at + 1]!
|
|
169
|
+
continue
|
|
170
|
+
}
|
|
171
|
+
if (op === OP_SCOPE_PLAIN) {
|
|
172
|
+
ambientTrivia = code[at + 1]!
|
|
173
|
+
at = code[at + 2]!
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
if (op === OP_GATE || op === OP_XFORM || op === OP_NODE || op === OP_FIELD || op === OP_LEAF) {
|
|
177
|
+
at = code[at + 2]!
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const n = op === OP_SEQ || op === OP_SEQV
|
|
182
|
+
? code[at + 1]!
|
|
183
|
+
: op === OP_SEQX ? code[at + 2]! : 0
|
|
184
|
+
if (n < 3 || ambientTrivia === undefined) return undefined
|
|
185
|
+
const base = op === OP_SEQX ? at + 3 : at + 2
|
|
186
|
+
const terminals = [
|
|
187
|
+
wholeScalar(code[base]!),
|
|
188
|
+
wholeScalar(code[base + 1]!),
|
|
189
|
+
wholeScalar(code[base + 2]!),
|
|
190
|
+
]
|
|
191
|
+
return terminals.every(terminal => terminal >= 0)
|
|
192
|
+
? { terminals, trivia: ambientTrivia }
|
|
193
|
+
: undefined
|
|
194
|
+
}
|
|
195
|
+
return undefined
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* A bounded union of literal prefixes that every successful execution of `ip`
|
|
200
|
+
* must enter through. Unlike `leadingScalarTerminal`, this may cross nested
|
|
201
|
+
* choices, but only when EVERY arm yields literal authority. Longer literals
|
|
202
|
+
* covered by a shorter prefix are removed (`@{-}` is already covered by `@{`).
|
|
203
|
+
*/
|
|
204
|
+
export function leadingLiteralFamily(
|
|
205
|
+
code: ArrayLike<number>, constants: readonly unknown[], ip: number,
|
|
206
|
+
minDepth = 2, limit = 4,
|
|
207
|
+
): readonly number[] | undefined {
|
|
208
|
+
const active = new Set<number>()
|
|
209
|
+
|
|
210
|
+
const walk = (at: number, depth: number): number[] | undefined => {
|
|
211
|
+
if (active.has(at)) return undefined
|
|
212
|
+
active.add(at)
|
|
213
|
+
try {
|
|
214
|
+
const op = code[at]
|
|
215
|
+
if (op === OP_LIT) {
|
|
216
|
+
const value = constants[code[at + 1]!]
|
|
217
|
+
return depth >= minDepth && typeof value === 'string' && value.length > 0 ? [at] : undefined
|
|
218
|
+
}
|
|
219
|
+
if (op === OP_RULE || op === OP_LABEL || op === OP_TOKEN || op === OP_ATTEMPT || op === OP_PEEK) {
|
|
220
|
+
return walk(code[at + 1]!, depth + 1)
|
|
221
|
+
}
|
|
222
|
+
if (op === OP_GATE || op === OP_SCOPE || op === OP_SCOPE_CAP || op === OP_SCOPE_PLAIN
|
|
223
|
+
|| op === OP_XFORM || op === OP_NODE || op === OP_FIELD || op === OP_LEAF) {
|
|
224
|
+
return walk(code[at + 2]!, depth + 1)
|
|
225
|
+
}
|
|
226
|
+
if (op === OP_SEQ || op === OP_SEQV) {
|
|
227
|
+
return code[at + 1]! < 1 ? undefined : walk(code[at + 2]!, depth + 1)
|
|
228
|
+
}
|
|
229
|
+
if (op === OP_SEQX) {
|
|
230
|
+
return code[at + 2]! < 1 ? undefined : walk(code[at + 3]!, depth + 1)
|
|
231
|
+
}
|
|
232
|
+
if (op !== OP_CHOICE) return undefined
|
|
233
|
+
|
|
234
|
+
const n = code[at + 2]!
|
|
235
|
+
if (n < 1) return undefined
|
|
236
|
+
const out: number[] = []
|
|
237
|
+
for (let i = 0; i < n; i++) {
|
|
238
|
+
const branch = walk(code[at + 4 + i]!, depth + 1)
|
|
239
|
+
if (branch === undefined) return undefined
|
|
240
|
+
for (const terminal of branch) {
|
|
241
|
+
const value = constants[code[terminal + 1]!] as string
|
|
242
|
+
let covered = false
|
|
243
|
+
for (let j = 0; j < out.length; j++) {
|
|
244
|
+
const prior = constants[code[out[j]! + 1]!] as string
|
|
245
|
+
if (value.startsWith(prior)) { covered = true; break }
|
|
246
|
+
}
|
|
247
|
+
if (covered) continue
|
|
248
|
+
for (let j = out.length - 1; j >= 0; j--) {
|
|
249
|
+
const prior = constants[code[out[j]! + 1]!] as string
|
|
250
|
+
if (prior.startsWith(value)) out.splice(j, 1)
|
|
251
|
+
}
|
|
252
|
+
out.push(terminal)
|
|
253
|
+
if (out.length > limit) return undefined
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return out
|
|
257
|
+
} finally {
|
|
258
|
+
active.delete(at)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return walk(ip, 0)
|
|
263
|
+
}
|
package/src/table/site-labels.ts
CHANGED
|
@@ -79,6 +79,13 @@ export const CAP_OFF = 1
|
|
|
79
79
|
/** `ctx.captureTrivia === true` is guaranteed here. */
|
|
80
80
|
export const CAP_ON = 2
|
|
81
81
|
|
|
82
|
+
/** Buffer raw-entry mode is not provable at this site. */
|
|
83
|
+
export const RAW_UNKNOWN = 0
|
|
84
|
+
/** Buffer retains raw entry values. */
|
|
85
|
+
export const RAW_CAPTURE = 1
|
|
86
|
+
/** Buffer retains only a raw source-order count. */
|
|
87
|
+
export const RAW_OMIT = 2
|
|
88
|
+
|
|
82
89
|
export type SiteLabel = {
|
|
83
90
|
/** `TRI_UNKNOWN`, `TRI_NONE`, or the trivia slot the enclosing scope installed. */
|
|
84
91
|
readonly tri: number
|
|
@@ -87,19 +94,22 @@ export type SiteLabel = {
|
|
|
87
94
|
* asymmetry note in this file's header. Never read as "guaranteed absent".
|
|
88
95
|
*/
|
|
89
96
|
readonly buf: boolean
|
|
97
|
+
/** `RAW_UNKNOWN` / `RAW_CAPTURE` / `RAW_OMIT`. */
|
|
98
|
+
readonly raw: number
|
|
90
99
|
/** `CAP_UNKNOWN` / `CAP_OFF` / `CAP_ON`. */
|
|
91
100
|
readonly cap: number
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
/** The lattice's top: every entry point starts here. */
|
|
95
|
-
export const TOP: SiteLabel = { tri: TRI_UNKNOWN, buf: false, cap: CAP_UNKNOWN }
|
|
104
|
+
export const TOP: SiteLabel = { tri: TRI_UNKNOWN, buf: false, raw: RAW_UNKNOWN, cap: CAP_UNKNOWN }
|
|
96
105
|
|
|
97
106
|
function meet(a: SiteLabel, b: SiteLabel): SiteLabel {
|
|
98
107
|
const tri = a.tri === b.tri ? a.tri : TRI_UNKNOWN
|
|
99
108
|
const buf = a.buf && b.buf
|
|
109
|
+
const raw = a.raw === b.raw ? a.raw : RAW_UNKNOWN
|
|
100
110
|
const cap = a.cap === b.cap ? a.cap : CAP_UNKNOWN
|
|
101
|
-
if (tri === a.tri && buf === a.buf && cap === a.cap) return a
|
|
102
|
-
return { tri, buf, cap }
|
|
111
|
+
if (tri === a.tri && buf === a.buf && raw === a.raw && cap === a.cap) return a
|
|
112
|
+
return { tri, buf, raw, cap }
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
/**
|
|
@@ -145,7 +155,7 @@ function transfer(code: Int32Array, ip: number, at: SiteLabel, hostCst: boolean)
|
|
|
145
155
|
// (`encode.ts:1153-1158`), so it can only ever raise `cap` to `CAP_ON`.
|
|
146
156
|
const cap = op === OP_SCOPE_CAP ? CAP_ON : at.cap
|
|
147
157
|
if (tri === at.tri && cap === at.cap) return at
|
|
148
|
-
return { tri, buf: at.buf, cap }
|
|
158
|
+
return { tri, buf: at.buf, raw: at.raw, cap }
|
|
149
159
|
}
|
|
150
160
|
if (op === OP_NODE || op === OP_NODE_TRACK) {
|
|
151
161
|
const flags = code[ip + 3]!
|
|
@@ -156,14 +166,17 @@ function transfer(code: Int32Array, ip: number, at: SiteLabel, hostCst: boolean)
|
|
|
156
166
|
// arrays instead of a CstCaptureBuf. Descendants still capture, but they
|
|
157
167
|
// must take the generic collector branch rather than dereference `_cstBuf`.
|
|
158
168
|
// False means UNKNOWN in this lattice, which is exactly the safe answer.
|
|
159
|
-
return { tri: at.tri, buf: false, cap: CAP_OFF }
|
|
169
|
+
return { tri: at.tri, buf: false, raw: RAW_UNKNOWN, cap: CAP_OFF }
|
|
160
170
|
}
|
|
161
171
|
// `ctx._cstBuf = buf` and `ctx.captureTrivia = <literal>`, both unconditional
|
|
162
172
|
// — `emit-assembly.ts`'s `OP_NODE` body opens the buffer before it descends
|
|
163
173
|
// and closes it after, whatever the host mode is.
|
|
164
174
|
const cap = ((flags & 4) !== 0 || hostCst) ? CAP_ON : CAP_OFF
|
|
165
|
-
|
|
166
|
-
|
|
175
|
+
const raw = !hostCst && code[ip + 1]! >= 0 && code[ip + 4]! < 0 && (flags & 2) !== 0
|
|
176
|
+
? RAW_OMIT
|
|
177
|
+
: RAW_CAPTURE
|
|
178
|
+
if (at.buf && raw === at.raw && cap === at.cap) return at
|
|
179
|
+
return { tri: at.tri, buf: true, raw, cap }
|
|
167
180
|
}
|
|
168
181
|
// THE TWO BOUNDARIES clear every capture sink for their child and contribute
|
|
169
182
|
// ONE leaf for the whole match; `token()` clears `ctx.trivia` as well. So a
|
|
@@ -178,11 +191,11 @@ function transfer(code: Int32Array, ip: number, at: SiteLabel, hostCst: boolean)
|
|
|
178
191
|
// an option.
|
|
179
192
|
if (op === OP_TOKEN) {
|
|
180
193
|
if (at.tri === TRI_NONE && !at.buf) return at
|
|
181
|
-
return { tri: TRI_NONE, buf: false, cap: at.cap }
|
|
194
|
+
return { tri: TRI_NONE, buf: false, raw: RAW_UNKNOWN, cap: at.cap }
|
|
182
195
|
}
|
|
183
196
|
if (op === OP_LEAF) {
|
|
184
197
|
if (!at.buf) return at
|
|
185
|
-
return { tri: at.tri, buf: false, cap: at.cap }
|
|
198
|
+
return { tri: at.tri, buf: false, raw: RAW_UNKNOWN, cap: at.cap }
|
|
186
199
|
}
|
|
187
200
|
return at
|
|
188
201
|
}
|
package/src/types.ts
CHANGED
|
@@ -89,7 +89,7 @@ export type ParserDef =
|
|
|
89
89
|
// it requires, so it carries `parser`'s first-set and a leading `peek()` gates
|
|
90
90
|
// its choice arm (see `isPositiveLookahead` / `sequenceFirstSet`).
|
|
91
91
|
| { tag: 'peek'; parser: Combinator<unknown> }
|
|
92
|
-
| { tag: 'node'; type?: string; parser: Combinator<unknown>; build?: ((children: ReadonlyArray<unknown>, fields: FieldMap | undefined, span: { start: number; end: number }, rawChildren: ReadonlyArray<unknown>, triviaLog: readonly number[], state: unknown) => unknown) | undefined; buildSrc?: string; /** The IDENTIFIER the type argument was written as at the `node()` call site, when it was a plain identifier rather than a literal — ANALYSIS ONLY, never emitted. Lets the inline-`mk` matcher recognise `node(t, …, (…) => mk(t, …))` from a factory, where the two `t`s are provably one binding. */ typeSrc?: string; /** Resolved source of a NAMED `buildSrc` — ANALYSIS ONLY, never emitted. See `buildAnalysisSrc`. */ buildSigSrc?: string; /** Declared positional arity of `build`: author-declared via `node(..., { buildArity })`, or resolved from the reducer's declaration. Wins over source inspection. */ buildArity?: number; /** Why arity could not be resolved, for the diagnostic. */ buildArityUnresolved?: string; buildStaticError?: readonly string[]; /** Import provenance for a direct builder's free names — each `{ local, source, imported }` lets a downstream compose() re-emit `import { imported as local } from source` so the inlined builder source can bind. Carried through the IR round-trip. */ buildImports?: ReadonlyArray<{ local: string; source: string; imported: string }>; unwrap?: boolean; collapse?: boolean; project?: number; captureTrivia?: boolean; trailingTrivia?: boolean; tags?: readonly string[] }
|
|
92
|
+
| { tag: 'node'; type?: string; parser: Combinator<unknown>; build?: ((children: ReadonlyArray<unknown>, fields: FieldMap | undefined, span: { start: number; end: number }, rawChildren: ReadonlyArray<unknown>, triviaLog: readonly number[], state: unknown) => unknown) | undefined; buildSrc?: string; /** The IDENTIFIER the type argument was written as at the `node()` call site, when it was a plain identifier rather than a literal — ANALYSIS ONLY, never emitted. Lets the inline-`mk` matcher recognise `node(t, …, (…) => mk(t, …))` from a factory, where the two `t`s are provably one binding. */ typeSrc?: string; /** Resolved source of a NAMED `buildSrc` — ANALYSIS ONLY, never emitted. See `buildAnalysisSrc`. */ buildSigSrc?: string; /** Macro analysis proved the fourth formal (`rawChildren`) unreferenced. Never inferred from an author-declared `buildArity`; carried through composed IR. */ buildRawUnused?: true; /** Declared positional arity of `build`: author-declared via `node(..., { buildArity })`, or resolved from the reducer's declaration. Wins over source inspection. */ buildArity?: number; /** Why arity could not be resolved, for the diagnostic. */ buildArityUnresolved?: string; buildStaticError?: readonly string[]; /** Import provenance for a direct builder's free names — each `{ local, source, imported }` lets a downstream compose() re-emit `import { imported as local } from source` so the inlined builder source can bind. Carried through the IR round-trip. */ buildImports?: ReadonlyArray<{ local: string; source: string; imported: string }>; unwrap?: boolean; collapse?: boolean; project?: number; captureTrivia?: boolean; trailingTrivia?: boolean; tags?: readonly string[] }
|
|
93
93
|
// `predSrc`/`extraSrc` (set by the macro evaluator): SOURCE TEXT of the guard
|
|
94
94
|
// predicate / the withCtx `extra` value, so codegen inlines them into `_mf`
|
|
95
95
|
// rather than pushing a `null` source. Absent under runtime compile() (the real
|
package/src/version.ts
CHANGED