parseman 0.49.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/analysis/commitment.d.ts +2 -1
  2. package/dist/analysis/commitment.d.ts.map +1 -1
  3. package/dist/analysis/diagnostics.js +1931 -861
  4. package/dist/analysis/diagnostics.js.map +3 -3
  5. package/dist/analysis/rebuild.d.ts.map +1 -1
  6. package/dist/cli/index.js +1235 -164
  7. package/dist/cli/index.js.map +3 -3
  8. package/dist/combinators/trivia-skip.d.ts +1 -1
  9. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  10. package/dist/compiler/build-arity.d.ts +16 -0
  11. package/dist/compiler/build-arity.d.ts.map +1 -1
  12. package/dist/compiler/degradation.d.ts +0 -2
  13. package/dist/compiler/degradation.d.ts.map +1 -1
  14. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  15. package/dist/compiler/token-alphabet.d.ts +11 -0
  16. package/dist/compiler/token-alphabet.d.ts.map +1 -1
  17. package/dist/compiler/token-capability.js +185 -20
  18. package/dist/compiler/token-capability.js.map +1 -1
  19. package/dist/cst/capture-buffer.d.ts +5 -1
  20. package/dist/cst/capture-buffer.d.ts.map +1 -1
  21. package/dist/index.js +1348 -279
  22. package/dist/index.js.map +3 -3
  23. package/dist/language-service/index.js +2 -1
  24. package/dist/language-service/index.js.map +1 -1
  25. package/dist/plugin/entry-supercompiler.d.ts +48 -0
  26. package/dist/plugin/entry-supercompiler.d.ts.map +1 -0
  27. package/dist/plugin/evaluator.d.ts.map +1 -1
  28. package/dist/plugin/index.d.ts.map +1 -1
  29. package/dist/plugin/index.js +1940 -385
  30. package/dist/plugin/index.js.map +3 -3
  31. package/dist/run/index.js +2 -1
  32. package/dist/run/index.js.map +1 -1
  33. package/dist/spec/index.js +28 -7
  34. package/dist/spec/index.js.map +1 -1
  35. package/dist/table/assemble.d.ts.map +1 -1
  36. package/dist/table/compile-rule-map.d.ts +6 -5
  37. package/dist/table/compile-rule-map.d.ts.map +1 -1
  38. package/dist/table/emit-assembly.d.ts +6 -1
  39. package/dist/table/emit-assembly.d.ts.map +1 -1
  40. package/dist/table/encode.d.ts.map +1 -1
  41. package/dist/table/index.js +1298 -231
  42. package/dist/table/index.js.map +3 -3
  43. package/dist/table/ops.d.ts +3 -2
  44. package/dist/table/ops.d.ts.map +1 -1
  45. package/dist/table/program.d.ts +15 -1
  46. package/dist/table/program.d.ts.map +1 -1
  47. package/dist/table/scalar-terminal.d.ts +30 -0
  48. package/dist/table/scalar-terminal.d.ts.map +1 -1
  49. package/dist/table/site-labels.d.ts +8 -0
  50. package/dist/table/site-labels.d.ts.map +1 -1
  51. package/dist/types.d.ts +2 -1
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/version.d.ts +1 -1
  54. package/package.json +1 -1
  55. package/src/analysis/commitment.ts +19 -8
  56. package/src/analysis/rebuild.ts +1 -0
  57. package/src/combinators/trivia-skip.ts +2 -2
  58. package/src/compiler/build-arity.ts +50 -0
  59. package/src/compiler/degradation.ts +0 -6
  60. package/src/compiler/ir-serialize.ts +6 -4
  61. package/src/compiler/token-alphabet.ts +322 -28
  62. package/src/cst/capture-buffer.ts +34 -11
  63. package/src/plugin/entry-supercompiler.ts +567 -0
  64. package/src/plugin/evaluator.ts +10 -0
  65. package/src/plugin/index.ts +7 -3
  66. package/src/table/assemble.ts +125 -42
  67. package/src/table/compile-rule-map.ts +8 -7
  68. package/src/table/compile.ts +7 -8
  69. package/src/table/emit-assembly.ts +646 -126
  70. package/src/table/emit.ts +1 -1
  71. package/src/table/encode.ts +132 -7
  72. package/src/table/ops.ts +3 -2
  73. package/src/table/program.ts +44 -3
  74. package/src/table/scalar-terminal.ts +213 -1
  75. package/src/table/site-labels.ts +22 -9
  76. package/src/types.ts +1 -1
  77. package/src/version.ts +1 -1
@@ -0,0 +1,567 @@
1
+ /**
2
+ * Experimental build-time Entry/SCC supercompiler for static table assemblies.
3
+ *
4
+ * The table emitter remains the sole semantic backend. This pass parses that
5
+ * backend's ordinary JavaScript and joins uniquely-owned piece functions with
6
+ * labelled blocks. It never evaluates source and it never recognizes grammar
7
+ * names or reducer text. Public entries, value references, shared joins,
8
+ * recursive SCCs, and effect rows are exact outline barriers.
9
+ */
10
+ import { parseSync } from 'oxc-parser'
11
+ import { OP_ATTEMPT, OP_NODE, OP_NODE_TRACK, OP_REPV, OP_SCAN } from '../table/ops.ts'
12
+ import { emitAssemblySource, type EmitResult } from '../table/emit-assembly.ts'
13
+ import { defaultAssemblyCfgs } from '../table/emit.ts'
14
+ import { resolveTable, type TableProgram } from '../table/program.ts'
15
+
16
+ type Ast = { type: string; start?: number; end?: number; [key: string]: unknown }
17
+
18
+ export type EntrySupercompileCfg = {
19
+ readonly hostCst: boolean
20
+ readonly hostReadsChildren?: boolean
21
+ readonly hostCaptureTrivia?: ((type: string) => boolean) | undefined
22
+ readonly trackLines: boolean
23
+ readonly tolerant: boolean
24
+ readonly coverage: boolean
25
+ readonly probe: boolean
26
+ }
27
+
28
+ export type EntrySupercompileStats = {
29
+ readonly activated: boolean
30
+ readonly reason?: string
31
+ readonly functionsBefore: number
32
+ readonly functionsAfter: number
33
+ readonly pieceCallsBefore: number
34
+ readonly pieceCallsAfter: number
35
+ readonly sourceBefore: number
36
+ readonly sourceAfter: number
37
+ readonly regions: number
38
+ readonly largestRegionSource: number
39
+ readonly largestRegionIp?: number
40
+ readonly protocolRegions: Readonly<{ node: number; repv: number; nodeAndRepv: number }>
41
+ readonly barriers: Readonly<Record<string, number>>
42
+ }
43
+
44
+ export type EntrySupercompileResult = EmitResult & { readonly supercompile: EntrySupercompileStats }
45
+
46
+ type Fn = {
47
+ readonly ip: number
48
+ readonly name: string
49
+ readonly node: Ast
50
+ readonly body: Ast
51
+ readonly calls: Call[]
52
+ }
53
+
54
+ type Call = {
55
+ readonly caller: number
56
+ readonly callee: number
57
+ readonly node: Ast
58
+ readonly statement: Ast
59
+ readonly args: readonly Ast[]
60
+ readonly transformable: boolean
61
+ }
62
+
63
+ type Edit = { readonly start: number; readonly end: number; readonly text: string }
64
+
65
+ const PIECE = /^_pf(\d+)$/
66
+ const WRAP = 'function __parseman_supercompile__(){\n'
67
+ const SUFFIX = '\n}'
68
+
69
+ function ast(value: unknown): Ast | undefined {
70
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Ast : undefined
71
+ }
72
+
73
+ function nodes(value: unknown): Ast[] {
74
+ return Array.isArray(value) ? value.filter(v => ast(v) !== undefined) as Ast[] : []
75
+ }
76
+
77
+ function nameOf(node: Ast | undefined): string | undefined {
78
+ return node?.type === 'Identifier' && typeof node.name === 'string' ? node.name : undefined
79
+ }
80
+
81
+ function range(node: Ast): { start: number; end: number } {
82
+ if (typeof node.start !== 'number' || typeof node.end !== 'number') throw new Error('entry supercompiler: AST node without range')
83
+ return { start: node.start - WRAP.length, end: node.end - WRAP.length }
84
+ }
85
+
86
+ function children(node: Ast): Ast[] {
87
+ const out: Ast[] = []
88
+ for (const [key, value] of Object.entries(node)) {
89
+ if (key === 'type' || key === 'start' || key === 'end') continue
90
+ if (Array.isArray(value)) {
91
+ for (const child of value) {
92
+ const c = ast(child)
93
+ if (c !== undefined) out.push(c)
94
+ }
95
+ } else {
96
+ const c = ast(value)
97
+ if (c !== undefined) out.push(c)
98
+ }
99
+ }
100
+ return out
101
+ }
102
+
103
+ function isFunction(node: Ast): boolean {
104
+ return node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression'
105
+ }
106
+
107
+ function isDirectCallUse(call: Ast, parent: Ast | undefined, statement: Ast): boolean {
108
+ if (parent === undefined) return false
109
+ if (parent.type === 'VariableDeclarator') {
110
+ const declarations = statement.type === 'VariableDeclaration' ? nodes(statement.declarations) : []
111
+ return declarations.length === 1 && declarations[0] === parent && parent.init === call
112
+ }
113
+ if (parent.type === 'AssignmentExpression') {
114
+ return statement.type === 'ExpressionStatement' && statement.expression === parent
115
+ && nameOf(ast(parent.left)) !== undefined && parent.right === call && parent.operator === '='
116
+ }
117
+ if (parent.type === 'ReturnStatement') return statement === parent && parent.argument === call
118
+ return false
119
+ }
120
+
121
+ function isListStatement(statement: Ast, parent: Ast | undefined): boolean {
122
+ if (parent?.type === 'BlockStatement') return nodes(parent.body).includes(statement)
123
+ if (parent?.type === 'SwitchCase') return nodes(parent.consequent).includes(statement)
124
+ return false
125
+ }
126
+
127
+ function hasInlineScopeHazard(fn: Fn): boolean {
128
+ if (fn.node.async === true || fn.node.generator === true) return true
129
+ let hazard = false
130
+ const walk = (node: Ast): void => {
131
+ if (hazard) return
132
+ if (node.type === 'Identifier' && node.name === 'arguments') hazard = true
133
+ else if (node.type === 'ThisExpression') hazard = true
134
+ else if (node.type === 'VariableDeclaration' && node.kind === 'var') hazard = true
135
+ else if (node.type === 'MetaProperty'
136
+ && nameOf(ast(node.meta)) === 'new' && nameOf(ast(node.property)) === 'target') hazard = true
137
+ else for (const child of children(node)) walk(child)
138
+ }
139
+ walk(fn.body)
140
+ return hazard
141
+ }
142
+
143
+ function hasInlineSignature(fn: Fn): boolean {
144
+ const params = nodes(fn.node.params)
145
+ return params.length === 3
146
+ && nameOf(params[0]) === 'input'
147
+ && nameOf(params[1]) === 'pos'
148
+ && nameOf(params[2]) === 'ctx'
149
+ }
150
+
151
+ function render(source: string, start: number, end: number, edits: readonly Edit[]): string {
152
+ const ordered = [...edits].sort((a, b) => a.start - b.start || a.end - b.end)
153
+ let at = start
154
+ let out = ''
155
+ for (const edit of ordered) {
156
+ if (edit.start < at || edit.end < edit.start || edit.start < start || edit.end > end) {
157
+ throw new Error('entry supercompiler: overlapping or out-of-range structured edit')
158
+ }
159
+ out += source.slice(at, edit.start) + edit.text
160
+ at = edit.end
161
+ }
162
+ return out + source.slice(at, end)
163
+ }
164
+
165
+ /** Tarjan over the actual emitted piece-call graph. */
166
+ function recursiveMembers(fns: ReadonlyMap<number, Fn>): Set<number> {
167
+ let next = 0
168
+ const index = new Map<number, number>()
169
+ const low = new Map<number, number>()
170
+ const stack: number[] = []
171
+ const on = new Set<number>()
172
+ const recursive = new Set<number>()
173
+ const visit = (ip: number): void => {
174
+ index.set(ip, next); low.set(ip, next); next++
175
+ stack.push(ip); on.add(ip)
176
+ for (const call of fns.get(ip)?.calls ?? []) {
177
+ if (!fns.has(call.callee)) continue
178
+ if (!index.has(call.callee)) {
179
+ visit(call.callee)
180
+ low.set(ip, Math.min(low.get(ip)!, low.get(call.callee)!))
181
+ } else if (on.has(call.callee)) low.set(ip, Math.min(low.get(ip)!, index.get(call.callee)!))
182
+ }
183
+ if (low.get(ip) !== index.get(ip)) return
184
+ const component: number[] = []
185
+ for (;;) {
186
+ const member = stack.pop()!
187
+ on.delete(member); component.push(member)
188
+ if (member === ip) break
189
+ }
190
+ if (component.length > 1 || component.some(member => fns.get(member)!.calls.some(c => c.callee === member))) {
191
+ for (const member of component) recursive.add(member)
192
+ }
193
+ }
194
+ for (const ip of fns.keys()) if (!index.has(ip)) visit(ip)
195
+ return recursive
196
+ }
197
+
198
+ function fallback(emitted: EmitResult, reason: string, before: Omit<EntrySupercompileStats, 'activated' | 'reason' | 'functionsAfter' | 'pieceCallsAfter' | 'sourceAfter' | 'regions' | 'largestRegionSource' | 'largestRegionIp' | 'protocolRegions'>): EntrySupercompileResult {
199
+ return {
200
+ ...emitted,
201
+ supercompile: {
202
+ ...before, activated: false, reason,
203
+ functionsAfter: before.functionsBefore,
204
+ pieceCallsAfter: before.pieceCallsBefore,
205
+ sourceAfter: before.sourceBefore,
206
+ regions: before.functionsBefore,
207
+ largestRegionSource: 0,
208
+ protocolRegions: { node: 0, repv: 0, nodeAndRepv: 0 },
209
+ },
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Emit and structurally join one static assembly. `extraIps` are the same
215
+ * externally-addressable scan roots passed to the canonical emitter.
216
+ */
217
+ export function supercompileEntryAssembly(
218
+ prog: TableProgram,
219
+ cfg: EntrySupercompileCfg,
220
+ extraIps: readonly number[] = [],
221
+ ): EntrySupercompileResult {
222
+ const emitted = emitAssemblySource(resolveTable(prog), prog, cfg, extraIps, true)
223
+ return supercompileEmittedAssembly(prog, cfg, extraIps, emitted)
224
+ }
225
+
226
+ /**
227
+ * Plugin-only final splice for an ordinary macro replacement. The table
228
+ * compiler emits its canonical callback-free public artifact first; this pass
229
+ * then finds the precompiled assembly through the table object's AST shape and
230
+ * replaces only that factory body. Any mismatch returns the canonical source.
231
+ */
232
+ export function supercompileRuleMapReplacement(prog: TableProgram, replacement: string): string {
233
+ const cfg = defaultAssemblyCfgs(prog)[0]
234
+ if (cfg === undefined) return replacement
235
+ const extraIps: number[] = []
236
+ for (const scan of prog.scans ?? []) {
237
+ for (const ref of scan.skip) extraIps.push(ref[0])
238
+ if (scan.sentinel !== undefined) extraIps.push(scan.sentinel[0])
239
+ }
240
+ for (const set of prog.scanSkip ?? []) for (const ref of set) extraIps.push(ref[0])
241
+
242
+ let canonical: EmitResult
243
+ let candidate: EntrySupercompileResult
244
+ try {
245
+ canonical = emitAssemblySource(resolveTable(prog), prog, cfg, extraIps, true)
246
+ candidate = supercompileEmittedAssembly(prog, cfg, extraIps, canonical)
247
+ } catch {
248
+ return replacement
249
+ }
250
+ if (!candidate.supercompile.activated) return replacement
251
+
252
+ const prefix = 'const __parseman_macro_replacement__='
253
+ const parsed = parseSync('parseman-entry-supercompile-replacement.js', prefix + replacement)
254
+ if (parsed.errors.length > 0) return replacement
255
+ const program = parsed.program as unknown as Ast
256
+ const declaration = nodes(program.body)[0]
257
+ const declarator = nodes(declaration?.declarations)[0]
258
+ const call = ast(declarator?.init)
259
+ const table = nodes(call?.arguments)[0]
260
+ if (declaration?.type !== 'VariableDeclaration' || declarator?.type !== 'VariableDeclarator'
261
+ || call?.type !== 'CallExpression' || table?.type !== 'ObjectExpression') return replacement
262
+
263
+ const property = (object: Ast, key: string): Ast | undefined => nodes(object.properties).find(prop =>
264
+ prop.type === 'Property' && nameOf(ast(prop.key)) === key)
265
+ const assembliesProp = property(table, 'a')
266
+ const assemblies = nodes(ast(assembliesProp?.value)?.elements)
267
+ if (assembliesProp === undefined || assemblies.length !== 1 || assemblies[0]?.type !== 'ObjectExpression') return replacement
268
+ const factoryProp = property(assemblies[0]!, 'factory')
269
+ const factory = ast(factoryProp?.value)
270
+ const body = ast(factory?.body)
271
+ if (factory?.type !== 'FunctionExpression' || body?.type !== 'BlockStatement') return replacement
272
+ if (typeof body.start !== 'number' || typeof body.end !== 'number') return replacement
273
+ const start = body.start - prefix.length + 1
274
+ const end = body.end - prefix.length - 1
275
+ if (replacement.slice(start, end) !== canonical.source + '\n') return replacement
276
+ const transformed = replacement.slice(0, start) + candidate.source + '\n' + replacement.slice(end)
277
+ const checked = parseSync('parseman-entry-supercompile-replacement-result.js', prefix + transformed)
278
+ return checked.errors.length === 0 ? transformed : replacement
279
+ }
280
+
281
+ /** Build-hook form: transform the exact canonical source already selected. */
282
+ export function supercompileEmittedAssembly(
283
+ prog: TableProgram,
284
+ _cfg: EntrySupercompileCfg,
285
+ extraIps: readonly number[],
286
+ emitted: EmitResult,
287
+ ): EntrySupercompileResult {
288
+ const source = emitted.source
289
+ const parsed = parseSync('parseman-entry-supercompile.js', WRAP + source + SUFFIX)
290
+ if (parsed.errors.length > 0) {
291
+ const zero = { functionsBefore: 0, pieceCallsBefore: 0, sourceBefore: source.length, barriers: {} }
292
+ return fallback(emitted, 'canonical emitted source did not parse', zero)
293
+ }
294
+ const program = parsed.program as unknown as Ast
295
+ const wrapper = nodes(program.body)[0]
296
+ const wrapperBody = ast(wrapper?.body)
297
+ if (wrapper?.type !== 'FunctionDeclaration' || wrapperBody?.type !== 'BlockStatement') {
298
+ const zero = { functionsBefore: 0, pieceCallsBefore: 0, sourceBefore: source.length, barriers: {} }
299
+ return fallback(emitted, 'missing compiler wrapper', zero)
300
+ }
301
+
302
+ const fns = new Map<number, Fn>()
303
+ for (const statement of nodes(wrapperBody.body)) {
304
+ if (statement.type !== 'FunctionDeclaration') continue
305
+ const name = nameOf(ast(statement.id))
306
+ const hit = name === undefined ? undefined : PIECE.exec(name)
307
+ const body = ast(statement.body)
308
+ if (hit !== undefined && hit !== null && body?.type === 'BlockStatement') {
309
+ const ip = Number(hit[1])
310
+ fns.set(ip, { ip, name: name!, node: statement, body, calls: [] })
311
+ }
312
+ }
313
+
314
+ const externalRefs = new Set<number>()
315
+ let reservedCollision = false
316
+ const allCalls: Call[] = []
317
+ const walk = (
318
+ node: Ast,
319
+ current: Fn | undefined,
320
+ parent: Ast | undefined,
321
+ statement: Ast | undefined,
322
+ statementParent: Ast | undefined,
323
+ ): void => {
324
+ if (isFunction(node) && node !== current?.node) {
325
+ const own = node.type === 'FunctionDeclaration' ? nameOf(ast(node.id)) : undefined
326
+ const hit = own === undefined ? undefined : PIECE.exec(own)
327
+ if (hit !== undefined && hit !== null) {
328
+ const fn = fns.get(Number(hit[1]))
329
+ if (fn !== undefined) {
330
+ for (const child of children(node)) {
331
+ if (child !== ast(node.id)) walk(child, fn, node, undefined, undefined)
332
+ }
333
+ return
334
+ }
335
+ }
336
+ // Non-piece closures may retain piece values. They are external roots;
337
+ // do not treat their returns as exits from a piece being joined.
338
+ current = undefined
339
+ }
340
+
341
+ let owner = statement
342
+ let ownerParent = statementParent
343
+ if (parent?.type === 'BlockStatement' && nodes(parent.body).includes(node)) { owner = node; ownerParent = parent }
344
+ if (parent?.type === 'SwitchCase' && nodes(parent.consequent).includes(node)) { owner = node; ownerParent = parent }
345
+
346
+ if (node.type === 'CallExpression') {
347
+ const calleeName = nameOf(ast(node.callee))
348
+ const hit = calleeName === undefined ? undefined : PIECE.exec(calleeName)
349
+ if (hit !== undefined && hit !== null) {
350
+ const callee = Number(hit[1])
351
+ const args = nodes(node.arguments)
352
+ const transformable = current !== undefined && owner !== undefined && args.length === 3
353
+ && nameOf(args[0]) === 'input' && nameOf(args[2]) === 'ctx'
354
+ && isDirectCallUse(node, parent, owner) && isListStatement(owner, ownerParent)
355
+ if (current === undefined || owner === undefined) externalRefs.add(callee)
356
+ else {
357
+ const call = { caller: current.ip, callee, node, statement: owner, args, transformable }
358
+ current.calls.push(call); allCalls.push(call)
359
+ }
360
+ }
361
+ }
362
+ if (node.type === 'Identifier') {
363
+ if (typeof node.name === 'string' && /^_z\d/.test(node.name)) reservedCollision = true
364
+ const hit = typeof node.name === 'string' ? PIECE.exec(node.name) : null
365
+ if (hit !== null) {
366
+ const isDecl = parent?.type === 'FunctionDeclaration' && parent.id === node
367
+ const isCall = parent?.type === 'CallExpression' && parent.callee === node
368
+ if (!isDecl && !isCall) externalRefs.add(Number(hit[1]))
369
+ }
370
+ }
371
+ for (const child of children(node)) walk(child, current, node, owner, ownerParent)
372
+ }
373
+ for (const statement of nodes(wrapperBody.body)) walk(statement, undefined, wrapperBody, undefined, undefined)
374
+
375
+ if (reservedCollision) {
376
+ const before = { functionsBefore: fns.size, pieceCallsBefore: allCalls.length, sourceBefore: source.length, barriers: { name: 1 } }
377
+ return fallback(emitted, 'canonical source uses the reserved _z<N> supercompiler prefix', before)
378
+ }
379
+
380
+ const pieceCallsBefore = allCalls.length
381
+ const recursive = recursiveMembers(fns)
382
+ const incoming = new Map<number, Call[]>()
383
+ for (const call of allCalls) {
384
+ const list = incoming.get(call.callee) ?? []
385
+ list.push(call); incoming.set(call.callee, list)
386
+ }
387
+ const publicRoots = new Set([...Object.values(prog.rules), ...extraIps])
388
+ const barrier = new Map<number, string>()
389
+ for (const ip of fns.keys()) {
390
+ // Context-saving TOKEN/LEAF/DISPATCH/SCOPE rows and REP loops are
391
+ // self-contained structured control. ATTEMPT can re-anchor failure state,
392
+ // while SCAN can invoke an interpreted parser, so neither effect may cross
393
+ // an outlined-region boundary.
394
+ if (publicRoots.has(ip) || externalRefs.has(ip)) barrier.set(ip, 'public')
395
+ else if (recursive.has(ip)) barrier.set(ip, 'recursive')
396
+ else if (prog.code[ip] === OP_ATTEMPT || prog.code[ip] === OP_SCAN) barrier.set(ip, 'effect')
397
+ else if (hasInlineScopeHazard(fns.get(ip)!)) barrier.set(ip, 'scope')
398
+ else if (!hasInlineSignature(fns.get(ip)!)) barrier.set(ip, 'shape')
399
+ else if ((incoming.get(ip)?.length ?? 0) !== 1) barrier.set(ip, 'shared')
400
+ else if (!incoming.get(ip)![0]!.transformable) barrier.set(ip, 'shape')
401
+ }
402
+
403
+ const roots = [...fns.keys()].filter(ip => barrier.has(ip))
404
+ const owner = new Map<number, number>()
405
+ const absorb = (ip: number, root: number): void => {
406
+ if (owner.has(ip)) return
407
+ owner.set(ip, root)
408
+ for (const call of fns.get(ip)?.calls ?? []) {
409
+ if (!fns.has(call.callee) || barrier.has(call.callee) || !call.transformable) continue
410
+ absorb(call.callee, root)
411
+ }
412
+ }
413
+ for (const root of roots) absorb(root, root)
414
+ for (const ip of fns.keys()) if (!owner.has(ip)) { barrier.set(ip, 'shared'); absorb(ip, ip) }
415
+
416
+ let serial = 0
417
+ const src = (node: Ast): string => { const r = range(node); return source.slice(r.start, r.end) }
418
+ const renderBody = (ip: number, embedded: { result: string; label: string } | undefined): string => {
419
+ const fn = fns.get(ip)!
420
+ const bodyRange = range(fn.body)
421
+ const start = bodyRange.start + 1
422
+ const end = bodyRange.end - 1
423
+ const edits: Edit[] = []
424
+ const callsByStatement = new Map<Ast, Call[]>()
425
+ for (const call of fn.calls) {
426
+ if (owner.get(call.callee) !== owner.get(ip) || barrier.has(call.callee) || !call.transformable) continue
427
+ const list = callsByStatement.get(call.statement) ?? []
428
+ list.push(call); callsByStatement.set(call.statement, list)
429
+ }
430
+ const returns: Ast[] = []
431
+ const collectReturns = (node: Ast): void => {
432
+ if (node !== fn.body && isFunction(node)) return
433
+ if (node.type === 'ReturnStatement') returns.push(node)
434
+ for (const child of children(node)) collectReturns(child)
435
+ }
436
+ if (embedded !== undefined) collectReturns(fn.body)
437
+
438
+ const callPrefix = (call: Call): { prefix: string; value: string } => {
439
+ const id = serial++
440
+ const result = `_z${id}r`
441
+ const label = `_z${id}`
442
+ const at = `_z${id}p`
443
+ const nested = renderBody(call.callee, { result, label })
444
+ return {
445
+ value: result,
446
+ prefix: `let ${result},${at}=${src(call.args[1]!)}\n${label}:{let pos=${at}\n${nested}\n}\n`,
447
+ }
448
+ }
449
+
450
+ for (const [statement, calls] of callsByStatement) {
451
+ let prefix = ''
452
+ const replacements: Edit[] = []
453
+ for (const call of calls) {
454
+ const made = callPrefix(call)
455
+ prefix += made.prefix
456
+ const cr = range(call.node)
457
+ replacements.push({ start: cr.start, end: cr.end, text: made.value })
458
+ }
459
+ if (statement.type === 'ReturnStatement' && embedded !== undefined) {
460
+ const sr = range(statement)
461
+ const arg = ast(statement.argument)
462
+ const value = arg === undefined ? 'undefined' : render(source, range(arg).start, range(arg).end, replacements)
463
+ edits.push({ start: sr.start, end: sr.end, text: `{${prefix}${embedded.result}=${value};break ${embedded.label}}` })
464
+ } else {
465
+ const sr = range(statement)
466
+ edits.push({ start: sr.start, end: sr.start, text: prefix })
467
+ edits.push(...replacements)
468
+ }
469
+ }
470
+ if (embedded !== undefined) {
471
+ for (const statement of returns) {
472
+ if (callsByStatement.has(statement)) continue
473
+ const sr = range(statement)
474
+ const arg = ast(statement.argument)
475
+ edits.push({
476
+ start: sr.start, end: sr.end,
477
+ text: `{${embedded.result}=${arg === undefined ? 'undefined' : src(arg)};break ${embedded.label}}`,
478
+ })
479
+ }
480
+ }
481
+ return render(source, start, end, edits)
482
+ }
483
+
484
+ const regionSizes = new Map<number, number>()
485
+ const regionProtocol = new Map<number, number>()
486
+ for (const [ip, root] of owner) {
487
+ const r = range(fns.get(ip)!.node)
488
+ regionSizes.set(root, (regionSizes.get(root) ?? 0) + r.end - r.start)
489
+ const op = prog.code[ip]
490
+ const bits = (op === OP_NODE || op === OP_NODE_TRACK ? 1 : 0) | (op === OP_REPV ? 2 : 0)
491
+ regionProtocol.set(root, (regionProtocol.get(root) ?? 0) | bits)
492
+ }
493
+ const oversized = new Set<number>()
494
+ for (const [ip, size] of regionSizes) if (size > 180_000) oversized.add(ip)
495
+ if (oversized.size > 0) {
496
+ const counts = Object.fromEntries([...barrier.values()].map(k => [k, 0]))
497
+ for (const kind of barrier.values()) counts[kind] = (counts[kind] ?? 0) + 1
498
+ const before = { functionsBefore: fns.size, pieceCallsBefore, sourceBefore: source.length, barriers: counts }
499
+ return fallback(emitted, 'a region exceeded the 180KB source cap', before)
500
+ }
501
+
502
+ const topEdits: Edit[] = []
503
+ for (const [ip, fn] of fns) {
504
+ if (owner.get(ip) === ip) {
505
+ const br = range(fn.body)
506
+ topEdits.push({ start: br.start + 1, end: br.end - 1, text: renderBody(ip, undefined) })
507
+ } else {
508
+ const fr = range(fn.node)
509
+ topEdits.push({ start: fr.start, end: fr.end, text: '' })
510
+ }
511
+ }
512
+ const candidate = render(source, 0, source.length, topEdits)
513
+ const counts: Record<string, number> = {}
514
+ for (const kind of barrier.values()) counts[kind] = (counts[kind] ?? 0) + 1
515
+ const before = { functionsBefore: fns.size, pieceCallsBefore, sourceBefore: source.length, barriers: counts }
516
+ if (candidate.length > Math.ceil(source.length * 1.05)) {
517
+ return fallback(emitted, `candidate exceeded the 5% total source cap (${candidate.length} > ${Math.ceil(source.length * 1.05)})`, before)
518
+ }
519
+ const functionsAfter = roots.length
520
+ const removed = fns.size - functionsAfter
521
+ // Parse the RESULT, not merely the input. A range bug (notably a UTF-8 byte
522
+ // offset mistaken for a JS UTF-16 index) or a malformed labelled exit must
523
+ // fail closed before one byte is serialized. Then prove every remaining
524
+ // piece identifier has a declaration: internal bodies are suppressed only
525
+ // after this reachable-reference relink check.
526
+ const checked = parseSync('parseman-entry-supercompile-result.js', WRAP + candidate + SUFFIX)
527
+ if (checked.errors.length > 0) return fallback(emitted, 'structured candidate did not parse', before)
528
+ const declared = new Set<string>()
529
+ const referenced = new Set<string>()
530
+ const checkRefs = (node: Ast, parent?: Ast): void => {
531
+ if (node.type === 'FunctionDeclaration') {
532
+ const name = nameOf(ast(node.id))
533
+ if (name !== undefined && PIECE.test(name)) declared.add(name)
534
+ }
535
+ if (node.type === 'Identifier' && typeof node.name === 'string' && PIECE.test(node.name)) {
536
+ const isDeclaration = parent?.type === 'FunctionDeclaration' && parent.id === node
537
+ if (!isDeclaration) referenced.add(node.name)
538
+ }
539
+ for (const child of children(node)) checkRefs(child, node)
540
+ }
541
+ checkRefs(checked.program as unknown as Ast)
542
+ if (declared.size !== functionsAfter || [...referenced].some(name => !declared.has(name))) {
543
+ return fallback(emitted, 'deleted piece body remains reachable after structured relinking', before)
544
+ }
545
+ const largestRegion = [...regionSizes].sort((a, b) => b[1] - a[1])[0]
546
+ const protocolRegions = {
547
+ node: [...regionProtocol.values()].filter(bits => (bits & 1) !== 0).length,
548
+ repv: [...regionProtocol.values()].filter(bits => (bits & 2) !== 0).length,
549
+ nodeAndRepv: [...regionProtocol.values()].filter(bits => bits === 3).length,
550
+ }
551
+ return {
552
+ ...emitted,
553
+ source: candidate,
554
+ supercompile: {
555
+ ...before,
556
+ activated: removed > 0,
557
+ ...(removed > 0 ? {} : { reason: 'no uniquely-owned region bodies' }),
558
+ functionsAfter,
559
+ pieceCallsAfter: pieceCallsBefore - removed,
560
+ sourceAfter: candidate.length,
561
+ regions: roots.length,
562
+ largestRegionSource: Math.max(0, ...regionSizes.values()),
563
+ ...(largestRegion === undefined ? {} : { largestRegionIp: largestRegion[0] }),
564
+ protocolRegions,
565
+ },
566
+ }
567
+ }
@@ -18,6 +18,7 @@ import type { DispatchArm } from '../combinators/dispatch.ts'
18
18
  import { ref } from '../combinators/ref.ts'
19
19
  import { rules, type RulesOptions } from '../combinators/parser.ts'
20
20
  import * as parseman from '../index.ts'
21
+ import { confirmedBuildParamUnused } from '../compiler/build-arity.ts'
21
22
  import { directBuilderBindings } from './direct-builder-static.ts'
22
23
  import type { ReducerResolver } from './reducer-resolver.ts'
23
24
 
@@ -674,6 +675,7 @@ function exprToCombi(node: Expression, scope: XScope, code?: string, mfs?: strin
674
675
  ? parseman.node(explicitType, inner, hasBuild ? () => null : undefined, opts as parseman.NodeOptions | undefined)
675
676
  : parseman.node(inner, hasBuild ? () => null : undefined, opts as parseman.NodeOptions | undefined)
676
677
  if (combi._def.tag === 'node' && buildSrc !== undefined) {
678
+ const authorDeclaredBuildArity = opts?.buildArity !== undefined
677
679
  combi._def.buildSrc = buildSrc
678
680
  // The type argument's IDENTIFIER, when it was written as one. A `node(type, …)`
679
681
  // inside a factory resolves `type` to a string here, which loses the fact that
@@ -722,6 +724,14 @@ function exprToCombi(node: Expression, scope: XScope, code?: string, mfs?: strin
722
724
  // lift walk the real body and report its real free names. An inline builder
723
725
  // has no `buildSigSrc`, so this is exactly `buildSrc` for that case.
724
726
  const analysisSrc = combi._def.buildSigSrc ?? buildSrc
727
+ // Arity answers which positional slots the author/compiler promises to
728
+ // supply; reducer liveness answers whether one of those values is actually
729
+ // observable. Only macro-resolved source can establish the latter. An
730
+ // explicit buildArity remains authority: it is a user declaration, not a
731
+ // compiler invitation to reinterpret the callback body.
732
+ if (!authorDeclaredBuildArity && confirmedBuildParamUnused(analysisSrc, 3)) {
733
+ combi._def.buildRawUnused = true
734
+ }
725
735
  const report = directBuilderBindings(analysisSrc)
726
736
  // Import-provenance rescue and free-name refusals apply ONLY to an INLINE
727
737
  // builder body, whose source (`buildSrc`) is exactly what the table inlines and
@@ -30,6 +30,7 @@ import { compileRuleMap } from '../table/compile-rule-map.ts'
30
30
  import { compileLinkableTable } from '../compiler/compile-linkable-table.ts'
31
31
  import { createReducerResolver } from './reducer-resolver.ts'
32
32
  import { findFreeIdentifiers } from './free-identifiers.ts'
33
+ import { supercompileRuleMapReplacement } from './entry-supercompiler.ts'
33
34
  import {
34
35
  beginDegradationCapture, endDegradationCapture, formatDegradation, formatDegradations,
35
36
  resolveDegradationLevel, recordDegradation, degradationCaptureDepth, unwindDegradationCapture,
@@ -1696,9 +1697,12 @@ function transformMacroImpl(
1696
1697
  if (compiled !== null) {
1697
1698
  usedTableRuntime = true
1698
1699
  return {
1699
- replacement: compiled.replacementWithMetadata(
1700
- staticTableMetadataSource({ reflection: compiled.reflection, leaf: true }),
1701
- { precompileDefault: true },
1700
+ replacement: supercompileRuleMapReplacement(
1701
+ compiled.prog,
1702
+ compiled.replacementWithMetadata(
1703
+ staticTableMetadataSource({ reflection: compiled.reflection, leaf: true }),
1704
+ { precompileDefault: true },
1705
+ ),
1702
1706
  ),
1703
1707
  ...(importedFactories.length ? { importedFactories } : {}),
1704
1708
  }