jz 0.1.0 → 0.1.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/src/prepare.js CHANGED
@@ -24,8 +24,17 @@
24
24
 
25
25
  import { parse } from 'subscript/jessie'
26
26
  import { ctx, err, derive } from './ctx.js'
27
- import { T, STMT_OPS, VAL, extractParams, collectParamNames, classifyParam } from './analyze.js'
28
- import * as mods from '../module/index.js'
27
+ import { T, STMT_OPS, VAL, valTypeOf, typedElemCtor, extractParams, collectParamNames, classifyParam } from './analyze.js'
28
+ import { staticPropertyKey } from './key.js'
29
+ import { normalizeSource } from './source.js'
30
+ import {
31
+ CTORS, TIMER_NAMES,
32
+ hasModule, includeModule,
33
+ includeForArrayAccess, includeForArrayLiteral, includeForArrayPattern, includeForCallableValue,
34
+ includeForGenericMethod, includeForKnownKeyIteration, includeForNamedCall, includeForNumericCoercion,
35
+ includeForObjectLiteral, includeForObjectPattern, includeForOp, includeForProperty, includeForRuntimeCtor,
36
+ includeForRuntimeKeyIteration, includeForStringOnly, includeForStringValue, includeForTimerRuntime,
37
+ } from './autoload.js'
29
38
 
30
39
  let depth = 0 // arrow nesting depth (0=top-level, >0=inside function)
31
40
  let scopes = [] // block scope stack: [{names: Set, renames: Map}]
@@ -50,6 +59,79 @@ const addHostImport = (mod, name, alias, spec) => {
50
59
  if (vt) ctx.module.hostImportValTypes.set(alias, vt)
51
60
  }
52
61
 
62
+ function recordGlobalValueFact(name, expr) {
63
+ if (typeof name !== 'string') return
64
+ const vt = valTypeOf(expr)
65
+ if (vt) {
66
+ ;(ctx.scope.globalValTypes ||= new Map()).set(name, vt)
67
+ if (vt === VAL.REGEX && ctx.runtime.regex) ctx.runtime.regex.vars.set(name, expr)
68
+ }
69
+ const ctor = typedElemCtor(expr)
70
+ if (ctor) (ctx.scope.globalTypedElem ||= new Map()).set(name, ctor)
71
+ }
72
+
73
+ function recordModuleInitFacts(root) {
74
+ const facts = ctx.module.initFacts ||= {
75
+ dynVars: new Set(), anyDyn: false, hasSchemaLiterals: false,
76
+ hasFuncValue: false, timerNames: new Set(),
77
+ maxDef: 0, maxCall: 0, hasRest: false, hasSpread: false,
78
+ }
79
+ const isLiteralStr = idx => Array.isArray(idx) && idx[0] === 'str' && typeof idx[1] === 'string'
80
+ const visitFuncValue = (node) => {
81
+ if (facts.hasFuncValue || !Array.isArray(node)) return
82
+ const [op, ...args] = node
83
+ if (op === '()') {
84
+ for (let i = 1; i < args.length; i++) {
85
+ const a = args[i]
86
+ if (typeof a === 'string' && ctx.func.names.has(a)) { facts.hasFuncValue = true; return }
87
+ visitFuncValue(a)
88
+ }
89
+ return
90
+ }
91
+ if (op === '.' || op === '?.') {
92
+ if (typeof args[0] === 'string' && ctx.func.names.has(args[0])) { facts.hasFuncValue = true; return }
93
+ visitFuncValue(args[0])
94
+ return
95
+ }
96
+ if (op === '=>') { visitFuncValue(args[1]); return }
97
+ for (const a of args) {
98
+ if (typeof a === 'string' && ctx.func.names.has(a)) { facts.hasFuncValue = true; return }
99
+ visitFuncValue(a)
100
+ }
101
+ }
102
+ const walk = (node) => {
103
+ if (!Array.isArray(node)) {
104
+ if (typeof node === 'string' && TIMER_NAMES.has(node)) facts.timerNames.add(node)
105
+ return
106
+ }
107
+ const [op, ...args] = node
108
+ if (op === '[]') {
109
+ const [obj, idx] = args
110
+ if (!isLiteralStr(idx)) { facts.anyDyn = true; if (typeof obj === 'string') facts.dynVars.add(obj) }
111
+ } else if (op === 'for-in') {
112
+ facts.anyDyn = true
113
+ if (typeof args[1] === 'string') facts.dynVars.add(args[1])
114
+ } else if (op === '{}') {
115
+ facts.hasSchemaLiterals = true
116
+ } else if (op === '=>') {
117
+ let fixedN = 0
118
+ for (const r of extractParams(args[0])) {
119
+ if (classifyParam(r).kind === 'rest') facts.hasRest = true
120
+ else fixedN++
121
+ }
122
+ if (fixedN > facts.maxDef) facts.maxDef = fixedN
123
+ } else if (op === '()') {
124
+ const a = args[1]
125
+ const callArgs = a == null ? [] : (Array.isArray(a) && a[0] === ',') ? a.slice(1) : [a]
126
+ if (callArgs.some(x => Array.isArray(x) && x[0] === '...')) facts.hasSpread = true
127
+ if (callArgs.length > facts.maxCall) facts.maxCall = callArgs.length
128
+ }
129
+ for (const a of args) walk(a)
130
+ }
131
+ visitFuncValue(root)
132
+ walk(root)
133
+ }
134
+
53
135
  /**
54
136
  * @typedef {null|number|string|ASTNode[]} ASTNode
55
137
  */
@@ -103,13 +185,12 @@ export default function prepare(node) {
103
185
  }
104
186
  let needs = visit(ast)
105
187
  if (!needs) for (const f of ctx.func.list) if (f.body && visit(f.body)) { needs = true; break }
106
- if (!needs && ctx.module.moduleInits) for (const mi of ctx.module.moduleInits) if (visit(mi)) { needs = true; break }
107
- if (needs) includeModule('fn')
188
+ if (!needs && ctx.module.initFacts?.hasFuncValue) needs = true
189
+ if (needs) includeForCallableValue()
108
190
  }
109
191
 
110
192
  // Native timers: inline WASM timer queue when referenced (no host imports needed)
111
- const TIMER_NAMES = new Set(['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'])
112
- const usedTimers = new Set()
193
+ const usedTimers = new Set(ctx.module.initFacts?.timerNames || [])
113
194
  const scanTimers = (n) => {
114
195
  if (!Array.isArray(n)) {
115
196
  if (typeof n === 'string' && TIMER_NAMES.has(n)) usedTimers.add(n)
@@ -117,12 +198,10 @@ export default function prepare(node) {
117
198
  }
118
199
  for (let i = 0; i < n.length; i++) scanTimers(n[i])
119
200
  }
120
- const allNodes = [ast, ...ctx.func.list.map(f => f.body), ...(ctx.module.moduleInits || [])]
201
+ const allNodes = [ast, ...ctx.func.list.map(f => f.body)]
121
202
  for (const node of allNodes) scanTimers(node)
122
203
  if (usedTimers.size) {
123
- ctx.features.timers = true // Timer module included — compile.js needs to emit __timer_init
124
- includeModule('timer')
125
- includeModule('fn') // call_indirect dispatch for callbacks
204
+ includeForTimerRuntime()
126
205
  }
127
206
 
128
207
  return ast
@@ -146,6 +225,14 @@ function isDeclared(name) {
146
225
  return scopes.some(s => s.has(name))
147
226
  }
148
227
 
228
+ const hasFunc = name => ctx.func.names.has(name)
229
+
230
+ const renameFunc = (func, nextName) => {
231
+ ctx.func.names.delete(func.name)
232
+ func.name = nextName
233
+ ctx.func.names.add(nextName)
234
+ }
235
+
149
236
  /** Map JS typeof strings to jz type checks. Codes < 0 trigger specialized emitTypeofCmp paths. */
150
237
  const TYPEOF_MAP = { 'number': -1, 'string': -2, 'undefined': -3, 'boolean': -4, 'object': -5, 'function': -6 }
151
238
  function resolveTypeof(node) {
@@ -163,56 +250,6 @@ function resolveTypeof(node) {
163
250
  return node
164
251
  }
165
252
 
166
- // Property-based narrowing: unambiguous prop names limit module load.
167
- // Map value is the exact module set for that property (always includes 'core').
168
- // Properties not in this table fall back to the generic '.' receiver-type heuristic.
169
- // Uses null-prototype dict so inherited names (constructor, toString) don't match.
170
- const PROP_MODULES = Object.assign(Object.create(null), {
171
- // array-only methods
172
- push: ['core', 'array'], pop: ['core', 'array'], shift: ['core', 'array'], unshift: ['core', 'array'],
173
- splice: ['core', 'array'], reverse: ['core', 'array'], sort: ['core', 'array'], fill: ['core', 'array'],
174
- map: ['core', 'array'], filter: ['core', 'array'], reduce: ['core', 'array'], reduceRight: ['core', 'array'],
175
- forEach: ['core', 'array'], find: ['core', 'array'], findIndex: ['core', 'array'],
176
- findLast: ['core', 'array'], findLastIndex: ['core', 'array'],
177
- every: ['core', 'array'], some: ['core', 'array'], flat: ['core', 'array'], flatMap: ['core', 'array'],
178
- join: ['core', 'array'], copyWithin: ['core', 'array'], at: ['core', 'array'],
179
- // string-only methods
180
- charAt: ['core', 'string'], charCodeAt: ['core', 'string'], codePointAt: ['core', 'string'],
181
- toUpperCase: ['core', 'string'], toLowerCase: ['core', 'string'], trim: ['core', 'string'],
182
- trimStart: ['core', 'string'], trimEnd: ['core', 'string'],
183
- split: ['core', 'string'], replace: ['core', 'string'], replaceAll: ['core', 'string'],
184
- repeat: ['core', 'string'], startsWith: ['core', 'string'], endsWith: ['core', 'string'],
185
- padStart: ['core', 'string'], padEnd: ['core', 'string'], normalize: ['core', 'string'],
186
- matchAll: ['core', 'string'], match: ['core', 'string'],
187
- substring: ['core', 'string'], substr: ['core', 'string'],
188
- // collection-only methods
189
- add: ['core', 'collection'], clear: ['core', 'collection'],
190
- // string + array (cross-type but not object/collection)
191
- slice: ['core', 'string', 'array'], concat: ['core', 'string', 'array'],
192
- indexOf: ['core', 'string', 'array'], lastIndexOf: ['core', 'string', 'array'],
193
- includes: ['core', 'string', 'array'],
194
- // string + array + typedarray (length shared across all sequence types)
195
- length: ['core', 'string', 'array', 'typedarray'],
196
- // Note: toString/toFixed/toPrecision/toExponential not narrowed — they only have
197
- // number-schema emitters; unknown receivers fall through to __ext_call (collection module).
198
- })
199
-
200
- const OP_MODULES = {
201
- // '.' handled inline (see '.' handler) for property-based narrowing
202
- '?.': ['core', 'string', 'collection'],
203
- '?.[]': ['core', 'array', 'collection'],
204
- '?.()': ['core', 'fn'],
205
- 'u+': ['number', 'string'],
206
- 'in': ['core', 'collection', 'string'],
207
- '==': ['core', 'string'],
208
- '!=': ['core', 'string'],
209
- 'typeof': ['core', 'string'],
210
- '[': ['core', 'array'],
211
- '{': ['core', 'object', 'string', 'collection'],
212
- '//': ['core', 'string', 'regex'],
213
- }
214
- const dict = obj => Object.assign(Object.create(null), obj)
215
-
216
253
  const cloneNode = (node) => {
217
254
  if (!Array.isArray(node)) return node
218
255
  const copy = node.map(cloneNode)
@@ -220,64 +257,6 @@ const cloneNode = (node) => {
220
257
  return copy
221
258
  }
222
259
 
223
- // Call-site module inclusion. Keyed by either a bare callee name (e.g. 'parseFloat')
224
- // or a dotted path ('console.log'). Value is the module set to load.
225
- // The lookup fires from the '()' handler before scope resolution.
226
- // Architectural note: fully eliminating this (auto-imports via jzify) was considered but
227
- // rejected — Object.fromEntries needs collection+string while o.x does not, so MOD_DEPS
228
- // transitive inclusion alone over-loads simple paths. This table pinpoints load per callee.
229
- const CALL_MODULES = dict({
230
- // Bare-identifier calls
231
- 'ArrayBuffer': ['core', 'typedarray'],
232
- 'DataView': ['core', 'typedarray'],
233
- 'BigInt64Array': ['core', 'typedarray'],
234
- 'BigUint64Array': ['core', 'typedarray'],
235
- 'parseFloat': ['number', 'string'],
236
- 'parseInt': ['number', 'string'],
237
- 'String': ['core', 'string', 'number'],
238
- 'Number': ['number', 'string'],
239
- 'Boolean': ['number'],
240
- 'TextEncoder': ['core', 'string'],
241
- 'TextDecoder': ['core', 'string'],
242
- 'Error': ['core', 'string'],
243
- 'BigInt': ['number'],
244
- // Namespace.method calls
245
- 'console.log': ['core', 'string', 'number', 'console'],
246
- 'console.warn': ['core', 'string', 'number', 'console'],
247
- 'console.error': ['core', 'string', 'number', 'console'],
248
- 'Object.fromEntries': ['collection', 'string'],
249
- 'Object.keys': ['string'],
250
- 'Object.entries': ['string'],
251
- 'Date.now': ['core', 'console'],
252
- 'performance.now': ['core', 'console'],
253
- 'String.fromCharCode': ['core', 'string'],
254
- 'String.fromCodePoint': ['core', 'string'],
255
- 'BigInt.asIntN': ['number'],
256
- 'BigInt.asUintN': ['number'],
257
- 'Float64Array.from': ['core', 'typedarray', 'array'],
258
- 'Float32Array.from': ['core', 'typedarray', 'array'],
259
- 'Int32Array.from': ['core', 'typedarray', 'array'],
260
- 'Uint32Array.from': ['core', 'typedarray', 'array'],
261
- 'Int16Array.from': ['core', 'typedarray', 'array'],
262
- 'Uint16Array.from': ['core', 'typedarray', 'array'],
263
- 'Int8Array.from': ['core', 'typedarray', 'array'],
264
- 'Uint8Array.from': ['core', 'typedarray', 'array'],
265
- 'ArrayBuffer.isView': ['core', 'typedarray'],
266
- })
267
-
268
- // Method-call inclusion for unknown-receiver cases (e.g. `(x) => x.toFixed(2)`).
269
- // Keyed by bare method name — fires only when receiver can't be resolved to a namespace.
270
- const GENERIC_METHOD_MODULES = dict({
271
- 'toString': ['core', 'string', 'number'],
272
- 'toFixed': ['core', 'string', 'number'],
273
- 'toPrecision': ['core', 'string', 'number'],
274
- 'toExponential': ['core', 'string', 'number'],
275
- })
276
-
277
- // Constructor names that users may call without `new` — `Float64Array(5)` → `new Float64Array(5)`.
278
- // Keeps user source ergonomic; these cannot be plain function calls in JS semantics anyway.
279
- const CTORS = ['Float64Array','Float32Array','Int32Array','Uint32Array','Int16Array','Uint16Array','Int8Array','Uint8Array','BigInt64Array','BigUint64Array','Set','Map']
280
-
281
260
  /** Sparse-read .map fusion: rewrite `const b = a.map(arrow); for(...; j<b.length; ...) USE(b[j])`
282
261
  * into a fused for-loop that inlines `arrow(a[j])` at the read site, eliminating the materialized
283
262
  * intermediate array. Pre-prep AST mutation; only fires on shapes where every use of `b` is a
@@ -427,7 +406,7 @@ function cloneAndBind(node, PARAM, replacement) {
427
406
  }
428
407
 
429
408
  function prep(node) {
430
- if (Array.isArray(node) && OP_MODULES[node[0]]) includeMods(...OP_MODULES[node[0]])
409
+ if (Array.isArray(node)) includeForOp(node[0])
431
410
  if (Array.isArray(node) && node.loc != null) ctx.error.loc = node.loc
432
411
  if (node == null) return [, 0] // null/undefined → 0 literal
433
412
  if (node === true) return [, 1]
@@ -438,7 +417,7 @@ function prep(node) {
438
417
  if (node in F64_CONSTANTS) return [, F64_CONSTANTS[node]]
439
418
  if (PROHIBITED[node]) err(PROHIBITED[node])
440
419
  // Boolean/Number as value → identity arrow (for .filter(Boolean), .map(Number) etc.)
441
- if (node === 'Boolean' || node === 'Number') { includeMods('core', 'fn'); return ['=>', 'x', 'x'] }
420
+ if (node === 'Boolean' || node === 'Number') { includeForCallableValue(); return ['=>', 'x', 'x'] }
442
421
  const resolved = ctx.scope.chain[node]
443
422
  if (resolved?.includes('.')) return resolved
444
423
  // Cross-module import: mangled name (e.g. __util_js$clone)
@@ -453,7 +432,7 @@ function prep(node) {
453
432
  if (op === 'void' && ctx.transform.strict) err('strict mode: `void` is prohibited. It diverges from JS by evaluating to 0.')
454
433
  if (op == null) {
455
434
  if (typeof args[0] === 'string') {
456
- includeMods('core', 'string', 'number')
435
+ includeForStringValue()
457
436
  return ['str', args[0]] // string literal
458
437
  }
459
438
  return [, args[0]] // number literal
@@ -517,7 +496,7 @@ function expandDestruct(pattern, source, out, decls = null) {
517
496
  if (!isDestructPattern(pattern)) return
518
497
 
519
498
  if (pattern[0] === '[]') {
520
- includeMods('core', 'array', 'collection')
499
+ includeForArrayPattern()
521
500
  const items = patternItems(pattern[1])
522
501
  for (let j = 0; j < items.length; j++) {
523
502
  const item = items[j]
@@ -533,7 +512,7 @@ function expandDestruct(pattern, source, out, decls = null) {
533
512
  return
534
513
  }
535
514
 
536
- includeMods('core', 'object', 'string', 'collection')
515
+ includeForObjectPattern()
537
516
  const items = patternItems(pattern[1])
538
517
 
539
518
  // Collect explicit keys and detect rest pattern
@@ -592,6 +571,11 @@ function expandDestruct(pattern, source, out, decls = null) {
592
571
  function prepDecl(op, ...inits) {
593
572
  const rest = []
594
573
  for (const i of inits) {
574
+ if (Array.isArray(i) && i[0] === '()' && typeof i[1] === 'string' && Array.isArray(i[2]) && i[2][0] === '=' && isDestructPattern(i[2][1])) {
575
+ if (rest.length === 0 && inits.length === 1) return [';', [op, i[1]], prep(i[2])]
576
+ err('destructuring assignment after declaration must be a separate statement')
577
+ }
578
+
595
579
  if (!Array.isArray(i) || i[0] !== '=') { rest.push(i); continue }
596
580
  const [, name, init] = i, normed = prep(init)
597
581
 
@@ -633,6 +617,7 @@ function prepDecl(op, ...inits) {
633
617
  ctx.scope.consts.delete(declName)
634
618
  ctx.scope.constStrs?.delete(declName)
635
619
  }
620
+ recordGlobalValueFact(declName, normed)
636
621
  }
637
622
  // Track object schemas (after prefix so schema is keyed to final name)
638
623
  if (typeof declName === 'string' && Array.isArray(normed) && normed[0] === '{}' && normed.length > 1) {
@@ -662,8 +647,7 @@ function prepDecl(op, ...inits) {
662
647
  const handlers = {
663
648
  // Spread operator: [...expr] in arrays, f(...args) in calls, {...obj} in objects
664
649
  '...'(expr) {
665
- // Spread is valid in arrays, calls, and objects - just prep the inner expression
666
- includeModule('array')
650
+ includeForArrayLiteral()
667
651
  return ['...', prep(expr)]
668
652
  },
669
653
 
@@ -673,6 +657,7 @@ const handlers = {
673
657
  'await': () => err('async/await not supported: WASM is synchronous'),
674
658
  'class': () => err('class not supported: use object literals'),
675
659
  'yield': () => err('generators not supported: use loops'),
660
+ 'debugger': () => null,
676
661
  'delete': () => err('delete not supported: object shape is fixed'),
677
662
  'in'(key, obj) { return ['in', prep(key), prep(obj)] },
678
663
  'instanceof': () => err('instanceof not supported: use typeof'),
@@ -708,7 +693,7 @@ const handlers = {
708
693
  }
709
694
  // Function property assignment: fn.prop = arrow → extract as top-level function fn$prop
710
695
  if (depth === 0 && Array.isArray(lhs) && lhs[0] === '.' && typeof lhs[1] === 'string'
711
- && ctx.func.list.some(f => f.name === lhs[1]) && Array.isArray(rhs) && rhs[0] === '=>') {
696
+ && hasFunc(lhs[1]) && Array.isArray(rhs) && rhs[0] === '=>') {
712
697
  const name = `${lhs[1]}$${lhs[2]}`
713
698
  if (defFunc(name, prep(rhs))) return null // extracted as function, no assignment needed
714
699
  }
@@ -736,16 +721,12 @@ const handlers = {
736
721
  },
737
722
  'throw'(expr) { return ['throw', prep(expr)] },
738
723
 
739
- // Template literal: [``, part, ...] → chain of str_concat calls
740
- // First node is always a string (empty if template starts with ${...}) so concat dispatches correctly.
724
+ // Template literal: [``, part, ...] → fused single-allocation string concat.
741
725
  '`'(...parts) {
742
- includeMods('core', 'string', 'number')
726
+ includeForStringValue()
743
727
  const nodes = parts.map(p =>
744
728
  Array.isArray(p) && p[0] == null && typeof p[1] === 'string' ? ['str', p[1]] : prep(p))
745
- // Ensure first element is a string so concat chain starts with string dispatch
746
- if (nodes.length && !(Array.isArray(nodes[0]) && nodes[0][0] === 'str'))
747
- nodes.unshift(['str', ''])
748
- return nodes.reduce((acc, n) => ['()', ['.', acc, 'concat'], n])
729
+ return ['strcat', ...nodes]
749
730
  },
750
731
 
751
732
  // Tagged template: tag`a${x}b` → tag(['a','b'], x)
@@ -794,7 +775,7 @@ const handlers = {
794
775
  }
795
776
  }
796
777
  if (builtinItems.length === 0) return null
797
- if (!mods[MOD_ALIAS[mod] || mod]) {
778
+ if (!hasModule(mod)) {
798
779
  const name = typeof builtinItems[0] === 'string' ? builtinItems[0] : builtinItems[0][1]
799
780
  err(`'${name}' not declared in host module '${mod}'`)
800
781
  }
@@ -805,7 +786,7 @@ const handlers = {
805
786
  }
806
787
 
807
788
  // Tier 1: Built-in module
808
- if (mods[MOD_ALIAS[mod] || mod]) {
789
+ if (hasModule(mod)) {
809
790
  includeModule(mod)
810
791
  const bind = (name, alias) => {
811
792
  const key = mod + '.' + name
@@ -930,7 +911,7 @@ const handlers = {
930
911
  if (Array.isArray(decl) && decl[0] === 'default') {
931
912
  const val = decl[1]
932
913
  // export default name → export existing name as 'default'
933
- if (typeof val === 'string' && (ctx.func.list.some(f => f.name === val) || ctx.scope.globals.has(val))) {
914
+ if (typeof val === 'string' && (hasFunc(val) || ctx.scope.globals.has(val))) {
934
915
  ctx.func.exports['default'] = val // alias
935
916
  return null
936
917
  }
@@ -949,7 +930,7 @@ const handlers = {
949
930
 
950
931
  // Arrow: don't prep params. Track depth for nested function detection.
951
932
  '=>': (params, body) => {
952
- if (depth > 0) { includeMods('core', 'fn') }
933
+ if (depth > 0) { includeForCallableValue() }
953
934
  const raw = extractParams(params)
954
935
  const fnScope = new Map()
955
936
  for (const n of collectParamNames(raw)) fnScope.set(n, n)
@@ -1019,7 +1000,7 @@ const handlers = {
1019
1000
  },
1020
1001
  // Boolean literals NaN-box as f64 — typeof at runtime returns 'number'. Fold here so the JS-spec value survives.
1021
1002
  'typeof'(a) {
1022
- if (Array.isArray(a) && a[0] == null && typeof a[1] === 'boolean') { includeMods('core', 'string'); return ['str', 'boolean'] }
1003
+ if (Array.isArray(a) && a[0] == null && typeof a[1] === 'boolean') { includeForStringOnly(); return ['str', 'boolean'] }
1023
1004
  return ['typeof', prep(a)]
1024
1005
  },
1025
1006
 
@@ -1028,7 +1009,7 @@ const handlers = {
1028
1009
  if (b === undefined) {
1029
1010
  const na = prep(a)
1030
1011
  if (isLit(na) && typeof na[1] === 'number') return na
1031
- includeMods('number', 'string')
1012
+ includeForNumericCoercion()
1032
1013
  return ['u+', na]
1033
1014
  }
1034
1015
  return ['+', prep(a), prep(b)]
@@ -1060,8 +1041,7 @@ const handlers = {
1060
1041
  return ['//', pattern, flags]
1061
1042
  },
1062
1043
 
1063
- // auto-include math for ** operator
1064
- '**'(a, b) { includeModule('math'); return ['**', prep(a), prep(b)] },
1044
+ '**'(a, b) { return ['**', prep(a), prep(b)] },
1065
1045
 
1066
1046
  // Function call or grouping parens
1067
1047
  '()'(callee, ...args) {
@@ -1074,9 +1054,7 @@ const handlers = {
1074
1054
  if (PROHIBITED[callee]) err(PROHIBITED[callee])
1075
1055
  if (CTORS.includes(callee)) return handlers['new'](['()', callee, ...args])
1076
1056
 
1077
- const mods = CALL_MODULES[callee]
1078
- if (mods) {
1079
- includeMods(...mods)
1057
+ if (includeForNamedCall(callee)) {
1080
1058
  if (callee === 'BigInt64Array' || callee === 'BigUint64Array') {
1081
1059
  return ['()', callee, ...args.filter(a => a != null).map(prep)]
1082
1060
  }
@@ -1084,12 +1062,12 @@ const handlers = {
1084
1062
 
1085
1063
  const resolved = ctx.scope.chain[callee]
1086
1064
  if (resolved?.includes('.')) callee = resolved
1087
- else if (resolved && ctx.func.list.some(f => f.name === resolved)) callee = resolved
1065
+ else if (resolved && hasFunc(resolved)) callee = resolved
1088
1066
  else if (resolved && !resolved.includes('.')) {
1089
1067
  if (!ctx.module.imports.some(i => i[3]?.[1] === `$${resolved}`)) includeModule(resolved)
1090
1068
  }
1091
1069
  else if (depth > 0 && !resolved && !ctx.func.exports[callee] && !ctx.module.imports.some(i => i[3]?.[1] === `$${callee}`)) {
1092
- includeMods('core', 'fn')
1070
+ includeForCallableValue()
1093
1071
  }
1094
1072
  } else if (Array.isArray(callee) && callee[0] === '.') {
1095
1073
  const [, obj, prop] = callee
@@ -1099,29 +1077,27 @@ const handlers = {
1099
1077
  const alias = `${obj}$${prop}`
1100
1078
  addHostImport(obj, prop, alias, spec)
1101
1079
  callee = alias
1102
- } else if (key && CALL_MODULES[key]) {
1103
- includeMods(...CALL_MODULES[key])
1080
+ } else if (key && includeForNamedCall(key)) {
1104
1081
  callee = key
1105
- } else if (GENERIC_METHOD_MODULES[prop]) {
1106
- includeMods(...GENERIC_METHOD_MODULES[prop])
1082
+ } else if (includeForGenericMethod(prop)) {
1107
1083
  callee = prep(callee)
1108
1084
  } else {
1109
1085
  const mod = ctx.scope.chain[obj]
1110
- if (typeof obj === 'string' && mod && !mod.includes('.') && mods[MOD_ALIAS[mod] || mod]) {
1086
+ if (typeof obj === 'string' && mod && !mod.includes('.') && hasModule(mod)) {
1111
1087
  callee = (includeModule(mod), mod + '.' + prop)
1112
1088
  } else {
1113
1089
  callee = prep(callee)
1114
1090
  }
1115
1091
  }
1116
1092
  } else {
1117
- includeMods('core', 'fn')
1093
+ includeForCallableValue()
1118
1094
  callee = prep(callee)
1119
1095
  }
1120
1096
 
1121
1097
  const preppedArgs = args.filter(a => a != null).map(prep)
1122
1098
  for (const a of preppedArgs) {
1123
- if (typeof a === 'string' && ctx.func.list.some(f => f.name === a)) {
1124
- includeMods('core', 'fn'); break
1099
+ if (typeof a === 'string' && hasFunc(a)) {
1100
+ includeForCallableValue(); break
1125
1101
  }
1126
1102
  }
1127
1103
  const result = ['()', callee, ...preppedArgs]
@@ -1135,13 +1111,13 @@ const handlers = {
1135
1111
  '[]'(...args) {
1136
1112
  if (args.length === 1) {
1137
1113
  const inner = args[0]
1138
- includeMods('core', 'array')
1114
+ includeForArrayLiteral()
1139
1115
  if (inner == null) return ['[']
1140
1116
  if (Array.isArray(inner) && inner[0] === ',') { const items = inner.slice(1); if (items.length && items[items.length - 1] === null) items.pop(); return ['[', ...items.map(item => item == null ? [, JZ_NULL] : prep(item))] }
1141
1117
  return ['[', prep(inner)]
1142
1118
  }
1143
1119
  if (typeof args[0] === 'string' && ctx.module.namespaces?.[args[0]]) {
1144
- includeMods('core', 'string')
1120
+ includeForStringOnly()
1145
1121
  const key = prep(args[1])
1146
1122
  const exports = [...ctx.module.namespaces[args[0]].entries()]
1147
1123
  let fallback = [, undefined]
@@ -1151,7 +1127,7 @@ const handlers = {
1151
1127
  }
1152
1128
  return fallback
1153
1129
  }
1154
- includeMods('core', 'array', 'collection')
1130
+ includeForArrayAccess()
1155
1131
  return ['[]', prep(args[0]), prep(args[1])]
1156
1132
  },
1157
1133
 
@@ -1174,12 +1150,16 @@ const handlers = {
1174
1150
  return result
1175
1151
  }
1176
1152
 
1177
- includeMods('core', 'object')
1153
+ includeForObjectLiteral()
1178
1154
  if (inner == null) return ['{}']
1179
1155
  // Process properties: shorthand 'x' → [':', 'x', 'x'], or [':', key, val] → prep val only
1180
1156
  const prop = p => {
1181
1157
  if (typeof p === 'string') return [':', p, prep(p)]
1182
- if (Array.isArray(p) && p[0] === ':') return [':', p[1], prep(p[2])]
1158
+ if (Array.isArray(p) && p[0] === ':') {
1159
+ const key = typeof p[1] === 'string' ? p[1] : staticPropertyKey(p[1])
1160
+ if (key == null) err('computed property name not supported for fixed-shape object: use a compile-time string/number key')
1161
+ return [':', key, prep(p[2])]
1162
+ }
1183
1163
  return prep(p)
1184
1164
  }
1185
1165
  const result = Array.isArray(inner) && inner[0] === ','
@@ -1240,7 +1220,7 @@ const handlers = {
1240
1220
  // Known schema → compile-time unrolling with string keys
1241
1221
  const keys = ctx.schema.list[sid]
1242
1222
  if (!keys || !keys.length) { scopes.pop(); return null }
1243
- includeMods('core', 'string')
1223
+ includeForKnownKeyIteration()
1244
1224
  const stmts = []
1245
1225
  for (let i = 0; i < keys.length; i++) {
1246
1226
  stmts.push(i === 0
@@ -1251,7 +1231,7 @@ const handlers = {
1251
1231
  r = prep([';', ...stmts])
1252
1232
  } else {
1253
1233
  // Dynamic object → HASH runtime iteration
1254
- includeMods('core', 'string', 'collection')
1234
+ includeForRuntimeKeyIteration()
1255
1235
  r = ['for-in', varName, prep(src), prep(body)]
1256
1236
  }
1257
1237
  } else {
@@ -1263,24 +1243,17 @@ const handlers = {
1263
1243
 
1264
1244
  // Property access - resolve namespaces or object/array properties
1265
1245
  '.'(obj, prop) {
1246
+ if (prop === 'caller' || prop === 'callee') err('`.caller` and `.callee` are prohibited: deprecated stack introspection')
1266
1247
  const mod = ctx.scope.chain[obj]
1267
1248
  // Only treat as module namespace if it's a known built-in module (not a mangled import name)
1268
- if (typeof obj === 'string' && mod && !mod.includes('.') && mods[MOD_ALIAS[mod] || mod])
1249
+ if (typeof obj === 'string' && mod && !mod.includes('.') && hasModule(mod))
1269
1250
  return includeModule(mod), mod + '.' + prop
1270
1251
  // Source module namespace: import * as X → X.prop resolved to mangled name
1271
1252
  if (typeof obj === 'string' && ctx.module.namespaces?.[obj]) {
1272
1253
  const mangled = ctx.module.namespaces[obj].get(prop)
1273
1254
  if (mangled) return mangled
1274
1255
  }
1275
- // Typed-array/buffer properties auto-include typedarray module
1276
- if (prop === 'byteLength' || prop === 'byteOffset' || prop === 'buffer') {
1277
- includeMods('core', 'typedarray')
1278
- }
1279
- // Narrowing: unambiguous property name limits module load; otherwise pessimistic.
1280
- // Receiver-literal narrowing would still need collection for __dyn_get_expr fallback
1281
- // on unknown props, so it saves nothing — stick with property-name narrowing only.
1282
- if (typeof prop === 'string' && PROP_MODULES[prop]) includeMods(...PROP_MODULES[prop])
1283
- else includeMods('core', 'object', 'array', 'string', 'collection')
1256
+ includeForProperty(prop)
1284
1257
  return ['.', prep(obj), prop]
1285
1258
  },
1286
1259
 
@@ -1297,15 +1270,7 @@ const handlers = {
1297
1270
  const wrapArgs = (args) => args.length === 0 ? [null]
1298
1271
  : args.length === 1 ? [prep(args[0])]
1299
1272
  : [[',', ...args.map(prep)]]
1300
- // TypedArray / buffer constructors
1301
- const typedArrays = ['Float64Array','Float32Array','Int32Array','Uint32Array','Int16Array','Uint16Array','Int8Array','Uint8Array','BigInt64Array','BigUint64Array','ArrayBuffer','DataView']
1302
- if (typedArrays.includes(name)) {
1303
- includeMods('core', 'typedarray')
1304
- return ['()', `new.${name}`, ...wrapArgs(ctorArgs)]
1305
- }
1306
- // Set/Map constructors
1307
- if (name === 'Set' || name === 'Map') {
1308
- includeMods('core', 'collection')
1273
+ if (includeForRuntimeCtor(name)) {
1309
1274
  return ['()', `new.${name}`, ...wrapArgs(ctorArgs)]
1310
1275
  }
1311
1276
 
@@ -1317,37 +1282,6 @@ const handlers = {
1317
1282
  }
1318
1283
  }
1319
1284
 
1320
- // Namespace → module mapping (namespaces that share a module)
1321
- const MOD_ALIAS = { Number: 'number', Array: 'array', Object: 'object', Symbol: 'symbol', JSON: 'json', BigInt: 'number', Error: 'core', TextEncoder: 'string', TextDecoder: 'string' }
1322
- /** Auto-inclusion graph: loading a module also loads its listed prerequisites.
1323
- * Not a strict ordering: module init() functions only register emitters/stdlib entries,
1324
- * so relative init order does not affect correctness — emitters are looked up lazily
1325
- * at compile time. Cycles (e.g. number ↔ string) are broken via the in-progress guard. */
1326
- const MOD_DEPS = {
1327
- number: ['core', 'string'],
1328
- string: ['core', 'number'],
1329
- array: ['core'],
1330
- object: ['core'],
1331
- collection: ['core', 'number'],
1332
- symbol: ['core'],
1333
- json: ['core', 'string', 'number', 'collection'],
1334
- console: ['core', 'string', 'number'],
1335
- regex: ['core', 'string', 'array'],
1336
- }
1337
-
1338
- const includeMods = (...names) => names.forEach(includeModule)
1339
-
1340
- /** Register a module and its transitive deps. Idempotent; cycle-safe via early-mark. */
1341
- function includeModule(name) {
1342
- const modName = MOD_ALIAS[name] || name
1343
- const init = mods[modName]
1344
- if (!init) return err(`Module not found: ${name}`)
1345
- if (ctx.module.modules[modName]) return
1346
- ctx.module.modules[modName] = true // mark before deps so cycles terminate
1347
- for (const dep of MOD_DEPS[modName] || []) includeModule(dep)
1348
- init(ctx) // modules receive ctx explicitly instead of importing it
1349
- }
1350
-
1351
1285
  /** Merge source schemas into target via Object.assign for compile-time schema inference. */
1352
1286
  function inferAssignSchema(callNode) {
1353
1287
  // After prep, args may be comma-grouped: ['()', callee, [',', target, s1, s2]]
@@ -1421,6 +1355,7 @@ function defFunc(name, node) {
1421
1355
  const funcInfo = { name, body, exported, sig, ...(hasDefaults && { defaults }) }
1422
1356
  if (hasRest.length) funcInfo.rest = hasRest[0] // track rest param name
1423
1357
  ctx.func.list.push(funcInfo)
1358
+ ctx.func.names.add(name)
1424
1359
  return true
1425
1360
  }
1426
1361
 
@@ -1485,7 +1420,7 @@ function prepareModule(specifier, source) {
1485
1420
  ctx.module.currentPrefix = prefix
1486
1421
 
1487
1422
  // Parse + prepare imported source (may trigger recursive imports)
1488
- let ast = parse(source)
1423
+ let ast = parse(normalizeSource(source))
1489
1424
  if (ctx.transform.jzify) ast = ctx.transform.jzify(ast)
1490
1425
  const savedDepth = depth; depth = 0
1491
1426
  const moduleInit = prep(ast)
@@ -1504,7 +1439,7 @@ function prepareModule(specifier, source) {
1504
1439
  moduleExports.set(name, mangled)
1505
1440
  // Rename the function in ctx.func.list
1506
1441
  const func = ctx.func.list.find(f => f.name === name)
1507
- if (func) func.name = mangled
1442
+ if (func) renameFunc(func, mangled)
1508
1443
  // Rename globals
1509
1444
  if (ctx.scope.globals.has(name)) {
1510
1445
  const wat = ctx.scope.globals.get(name).replace(`$${name}`, `$${mangled}`)
@@ -1525,7 +1460,7 @@ function prepareModule(specifier, source) {
1525
1460
  const mangled = `${prefix}$${alias}`
1526
1461
  moduleExports.set('default', mangled)
1527
1462
  const func = ctx.func.list.find(f => f.name === alias)
1528
- if (func) func.name = mangled
1463
+ if (func) renameFunc(func, mangled)
1529
1464
  if (ctx.scope.globals.has(alias)) {
1530
1465
  const wat = ctx.scope.globals.get(alias).replace(`$${alias}`, `$${mangled}`)
1531
1466
  ctx.scope.globals.delete(alias)
@@ -1544,7 +1479,7 @@ function prepareModule(specifier, source) {
1544
1479
  if (func.name.includes('__') && func.name.includes('$')) continue
1545
1480
  const mangled = `${prefix}$${func.name}`
1546
1481
  moduleExports.set(func.name, mangled)
1547
- func.name = mangled
1482
+ renameFunc(func, mangled)
1548
1483
  }
1549
1484
 
1550
1485
  // Add mangled non-exported globals to moduleExports for walk renaming
@@ -1584,6 +1519,7 @@ function prepareModule(specifier, source) {
1584
1519
  if (moduleInit) {
1585
1520
  if (!ctx.module.moduleInits) ctx.module.moduleInits = []
1586
1521
  ctx.module.moduleInits.push(moduleInit)
1522
+ recordModuleInitFacts(moduleInit)
1587
1523
  }
1588
1524
 
1589
1525
  // Restore caller state