jz 0.1.1 → 0.2.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.
- package/README.md +257 -90
- package/cli.js +34 -8
- package/index.js +102 -13
- package/module/array.js +396 -133
- package/module/collection.js +455 -212
- package/module/console.js +169 -88
- package/module/core.js +246 -144
- package/module/date.js +691 -0
- package/module/function.js +81 -21
- package/module/index.js +2 -1
- package/module/json.js +483 -138
- package/module/math.js +79 -39
- package/module/number.js +188 -78
- package/module/object.js +227 -47
- package/module/regex.js +33 -30
- package/module/schema.js +14 -17
- package/module/string.js +434 -235
- package/module/symbol.js +4 -3
- package/module/timer.js +89 -31
- package/module/typedarray.js +174 -44
- package/package.json +3 -10
- package/src/analyze.js +626 -132
- package/src/autoload.js +14 -6
- package/src/compile.js +399 -70
- package/src/ctx.js +41 -12
- package/src/emit.js +634 -145
- package/src/host.js +244 -51
- package/src/ir.js +81 -31
- package/src/jzify.js +181 -24
- package/src/narrow.js +32 -4
- package/src/optimize.js +628 -42
- package/src/plan.js +1132 -4
- package/src/prepare.js +321 -75
- package/src/vectorize.js +1016 -0
- package/wasi.js +13 -0
- package/src/key.js +0 -73
- package/src/source.js +0 -76
package/src/prepare.js
CHANGED
|
@@ -22,11 +22,10 @@
|
|
|
22
22
|
* @module prepare
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import { parse } from 'subscript/jessie'
|
|
25
|
+
import { parse } from 'subscript/feature/jessie'
|
|
26
26
|
import { ctx, err, derive } from './ctx.js'
|
|
27
|
-
import { T, STMT_OPS, VAL, valTypeOf, typedElemCtor, extractParams, collectParamNames, classifyParam } from './analyze.js'
|
|
28
|
-
import {
|
|
29
|
-
import { normalizeSource } from './source.js'
|
|
27
|
+
import { T, STMT_OPS, VAL, valTypeOf, typedElemCtor, extractParams, collectParamNames, classifyParam, observeNodeFacts, staticPropertyKey } from './analyze.js'
|
|
28
|
+
import { isFuncRef } from './ir.js'
|
|
30
29
|
import {
|
|
31
30
|
CTORS, TIMER_NAMES,
|
|
32
31
|
hasModule, includeModule,
|
|
@@ -38,6 +37,30 @@ import {
|
|
|
38
37
|
|
|
39
38
|
let depth = 0 // arrow nesting depth (0=top-level, >0=inside function)
|
|
40
39
|
let scopes = [] // block scope stack: [{names: Set, renames: Map}]
|
|
40
|
+
// Per-arrow set of names already declared anywhere in the function body. Used
|
|
41
|
+
// to force a rename when the same identifier is declared in two sibling blocks
|
|
42
|
+
// (else-if arms, separate { ... } chunks): without renaming, both decls lower
|
|
43
|
+
// to the same WASM local, but downstream optimizations (directClosures) gate
|
|
44
|
+
// on per-decl `isReassigned`, not per-WASM-local — they'd read a stale binding.
|
|
45
|
+
let funcLocalNames = []
|
|
46
|
+
|
|
47
|
+
// ES spec: identifier with \uHHHH or \u{...} escape is equivalent to the decoded
|
|
48
|
+
// form. subscript preserves raw spelling in the AST; normalize once before prep.
|
|
49
|
+
const IDESC = /\\u\{([0-9a-fA-F]+)\}|\\u([0-9a-fA-F]{4})/g
|
|
50
|
+
const decodeIdent = s => s.includes('\\u')
|
|
51
|
+
? s.replace(IDESC, (_, b, p) => String.fromCodePoint(parseInt(b || p, 16)))
|
|
52
|
+
: s
|
|
53
|
+
|
|
54
|
+
const normalizeIdents = node => {
|
|
55
|
+
if (!Array.isArray(node)) return
|
|
56
|
+
// Literal-value wrapper [null, X] / [undefined, X]: X is a value, not an identifier
|
|
57
|
+
if (node.length === 2 && node[0] == null) return
|
|
58
|
+
for (let i = 1; i < node.length; i++) {
|
|
59
|
+
const v = node[i]
|
|
60
|
+
if (typeof v === 'string') node[i] = decodeIdent(v)
|
|
61
|
+
else if (Array.isArray(v)) normalizeIdents(v)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
41
64
|
|
|
42
65
|
const hostReturnValType = spec => {
|
|
43
66
|
if (!spec || typeof spec === 'function') return null
|
|
@@ -50,15 +73,51 @@ const hostReturnValType = spec => {
|
|
|
50
73
|
|
|
51
74
|
const addHostImport = (mod, name, alias, spec) => {
|
|
52
75
|
const nParams = typeof spec === 'function' ? spec.length : (spec?.params || 0)
|
|
53
|
-
|
|
76
|
+
// User-supplied imports carry NaN-boxed values via i64 (not f64) so V8 cannot
|
|
77
|
+
// canonicalize the NaN payload across the wasm↔JS function boundary —
|
|
78
|
+
// same hazard as env.print / __ext_*. Call sites wrap args with asI64()
|
|
79
|
+
// and unwrap the i64 return with f64.reinterpret_i64.
|
|
80
|
+
const params = Array(nParams).fill(['param', 'i64'])
|
|
54
81
|
if (!ctx.module.imports.some(i => i[3]?.[1] === `$${alias}`)) {
|
|
55
|
-
ctx.module.imports.push(['import', `"${mod}"`, `"${name}"`, ['func', `$${alias}`, ...params, ['result', '
|
|
82
|
+
ctx.module.imports.push(['import', `"${mod}"`, `"${name}"`, ['func', `$${alias}`, ...params, ['result', 'i64']]])
|
|
56
83
|
}
|
|
57
84
|
ctx.scope.chain[alias] = alias
|
|
58
85
|
const vt = hostReturnValType(spec)
|
|
59
86
|
if (vt) ctx.module.hostImportValTypes.set(alias, vt)
|
|
60
87
|
}
|
|
61
88
|
|
|
89
|
+
const isImportMeta = node => Array.isArray(node) && node[0] === '.' && node[1] === 'import' && node[2] === 'meta'
|
|
90
|
+
const isImportMetaProp = (node, prop) => Array.isArray(node) && node[0] === '.' && isImportMeta(node[1]) && node[2] === prop
|
|
91
|
+
const stringValue = node => Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
|
|
92
|
+
const flatArgs = args => args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',' ? args[0].slice(1) : args
|
|
93
|
+
const MUTATING_ARRAY_METHODS = new Set(['copyWithin', 'fill', 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'])
|
|
94
|
+
|
|
95
|
+
function stringArrayValues(expr) {
|
|
96
|
+
if (!Array.isArray(expr) || expr[0] !== '[' || expr.length === 1) return null
|
|
97
|
+
const out = []
|
|
98
|
+
for (const item of expr.slice(1)) {
|
|
99
|
+
if (!Array.isArray(item) || item[0] !== 'str' || typeof item[1] !== 'string') return null
|
|
100
|
+
out.push(item[1])
|
|
101
|
+
}
|
|
102
|
+
return out
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function staticString(value) {
|
|
106
|
+
includeForStringValue()
|
|
107
|
+
return ['str', value]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function importMetaUrl() {
|
|
111
|
+
if (!ctx.transform.importMetaUrl) err('`import.meta.url` requires compile option `importMetaUrl`')
|
|
112
|
+
return ctx.transform.importMetaUrl
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resolveImportMeta(spec) {
|
|
116
|
+
const base = importMetaUrl()
|
|
117
|
+
try { return new URL(spec, base).href }
|
|
118
|
+
catch { err(`Cannot resolve import.meta specifier '${spec}' from '${base}'`) }
|
|
119
|
+
}
|
|
120
|
+
|
|
62
121
|
function recordGlobalValueFact(name, expr) {
|
|
63
122
|
if (typeof name !== 'string') return
|
|
64
123
|
const vt = valTypeOf(expr)
|
|
@@ -76,26 +135,25 @@ function recordModuleInitFacts(root) {
|
|
|
76
135
|
hasFuncValue: false, timerNames: new Set(),
|
|
77
136
|
maxDef: 0, maxCall: 0, hasRest: false, hasSpread: false,
|
|
78
137
|
}
|
|
79
|
-
const isLiteralStr = idx => Array.isArray(idx) && idx[0] === 'str' && typeof idx[1] === 'string'
|
|
80
138
|
const visitFuncValue = (node) => {
|
|
81
139
|
if (facts.hasFuncValue || !Array.isArray(node)) return
|
|
82
140
|
const [op, ...args] = node
|
|
83
141
|
if (op === '()') {
|
|
84
142
|
for (let i = 1; i < args.length; i++) {
|
|
85
143
|
const a = args[i]
|
|
86
|
-
if (
|
|
144
|
+
if (isFuncRef(a, ctx.func.names)) { facts.hasFuncValue = true; return }
|
|
87
145
|
visitFuncValue(a)
|
|
88
146
|
}
|
|
89
147
|
return
|
|
90
148
|
}
|
|
91
149
|
if (op === '.' || op === '?.') {
|
|
92
|
-
if (
|
|
150
|
+
if (isFuncRef(args[0], ctx.func.names)) { facts.hasFuncValue = true; return }
|
|
93
151
|
visitFuncValue(args[0])
|
|
94
152
|
return
|
|
95
153
|
}
|
|
96
154
|
if (op === '=>') { visitFuncValue(args[1]); return }
|
|
97
155
|
for (const a of args) {
|
|
98
|
-
if (
|
|
156
|
+
if (isFuncRef(a, ctx.func.names)) { facts.hasFuncValue = true; return }
|
|
99
157
|
visitFuncValue(a)
|
|
100
158
|
}
|
|
101
159
|
}
|
|
@@ -104,29 +162,8 @@ function recordModuleInitFacts(root) {
|
|
|
104
162
|
if (typeof node === 'string' && TIMER_NAMES.has(node)) facts.timerNames.add(node)
|
|
105
163
|
return
|
|
106
164
|
}
|
|
107
|
-
|
|
108
|
-
|
|
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)
|
|
165
|
+
observeNodeFacts(node, facts)
|
|
166
|
+
for (const a of node.slice(1)) walk(a)
|
|
130
167
|
}
|
|
131
168
|
visitFuncValue(root)
|
|
132
169
|
walk(root)
|
|
@@ -144,18 +181,28 @@ function recordModuleInitFacts(root) {
|
|
|
144
181
|
export default function prepare(node) {
|
|
145
182
|
depth = 0
|
|
146
183
|
scopes = []
|
|
184
|
+
funcLocalNames = [new Set()]
|
|
147
185
|
includeModule('core')
|
|
186
|
+
normalizeIdents(node)
|
|
148
187
|
fuseSparseMapReads(node)
|
|
149
188
|
const ast = prep(node)
|
|
150
189
|
// Top-level functions referenced as first-class values (e.g. `let o = { fn: g }`,
|
|
151
190
|
// `arr.push(g)`, `return g`) need trampoline emission, which depends on the fn
|
|
152
191
|
// module's closure.table machinery. defFunc paths don't trigger fn-module load,
|
|
153
192
|
// so scan post-prep and include `fn` if any user func appears in a value position.
|
|
154
|
-
|
|
193
|
+
// Same scan also catches inline arrows that survive prep (e.g. `{ m: (x) => x }`)
|
|
194
|
+
// — defFunc only lifts arrows that are the direct RHS of a let/const/export default,
|
|
195
|
+
// and depth-0 arrows in any other position (object property, ternary arm, return
|
|
196
|
+
// value, ...) skip the depth>0 prep-time include, so they reach emit unsupported
|
|
197
|
+
// unless we catch them here.
|
|
198
|
+
if (!ctx.module.modules.fn) {
|
|
155
199
|
const funcNames = new Set(ctx.func.list.map(f => f.name))
|
|
156
200
|
const visit = (n) => {
|
|
157
201
|
if (!Array.isArray(n)) return false
|
|
158
202
|
const [op, ...args] = n
|
|
203
|
+
// Any inline arrow surviving prep is a closure value (defFunc-lifted ones
|
|
204
|
+
// are extracted from the AST into ctx.func.list).
|
|
205
|
+
if (op === '=>') return true
|
|
159
206
|
if (op === '()') {
|
|
160
207
|
// callee at args[0]: skip if it's a bare func name (direct call); recurse rest
|
|
161
208
|
if (typeof args[0] !== 'string' || !funcNames.has(args[0])) {
|
|
@@ -173,10 +220,6 @@ export default function prepare(node) {
|
|
|
173
220
|
if (typeof args[0] === 'string' && funcNames.has(args[0])) return true
|
|
174
221
|
return visit(args[0])
|
|
175
222
|
}
|
|
176
|
-
if (op === '=>') {
|
|
177
|
-
// body only — params are bindings, not refs
|
|
178
|
-
return visit(args[1])
|
|
179
|
-
}
|
|
180
223
|
for (const a of args) {
|
|
181
224
|
if (typeof a === 'string' && funcNames.has(a)) return true
|
|
182
225
|
if (visit(a)) return true
|
|
@@ -204,6 +247,34 @@ export default function prepare(node) {
|
|
|
204
247
|
includeForTimerRuntime()
|
|
205
248
|
}
|
|
206
249
|
|
|
250
|
+
// Invalidate shapeStrs for any module-level binding that's later assigned to.
|
|
251
|
+
// shapeStrs is "effectively-const string literals at module scope" — used by
|
|
252
|
+
// analyze.js's jsonConstString to enable shape inference on `let SRC = '{...}'`
|
|
253
|
+
// patterns (bench convention) without enabling the const-only static fold.
|
|
254
|
+
// The scan must skip `=` nodes that are children of `let`/`const`/`export` —
|
|
255
|
+
// those are decl-initializers, not reassignments.
|
|
256
|
+
if (ctx.scope.shapeStrs?.size || ctx.scope.shapeStrArrays?.size) {
|
|
257
|
+
const writes = new Set()
|
|
258
|
+
const scan = (n, inDecl) => {
|
|
259
|
+
if (!Array.isArray(n)) return
|
|
260
|
+
const [op, lhs] = n
|
|
261
|
+
if (op === '=' && typeof lhs === 'string' && !inDecl) writes.add(lhs)
|
|
262
|
+
if (op === '=' && Array.isArray(lhs) && lhs[0] === '[]' && typeof lhs[1] === 'string' && !inDecl) writes.add(lhs[1])
|
|
263
|
+
// Compound assigns desugar to `=`; increments emit as `++`/`--` post-prep.
|
|
264
|
+
if ((op === '++' || op === '--') && typeof lhs === 'string') writes.add(lhs)
|
|
265
|
+
if ((op === '++' || op === '--') && Array.isArray(lhs) && lhs[0] === '[]' && typeof lhs[1] === 'string') writes.add(lhs[1])
|
|
266
|
+
if (op === '()' && Array.isArray(lhs) && lhs[0] === '.' && typeof lhs[1] === 'string' && MUTATING_ARRAY_METHODS.has(lhs[2])) writes.add(lhs[1])
|
|
267
|
+
const childInDecl = (op === 'let' || op === 'const' || op === 'var' || op === 'export')
|
|
268
|
+
for (let i = 1; i < n.length; i++) scan(n[i], childInDecl)
|
|
269
|
+
}
|
|
270
|
+
scan(ast, false)
|
|
271
|
+
for (const f of ctx.func.list) if (f.body) scan(f.body, false)
|
|
272
|
+
for (const name of writes) {
|
|
273
|
+
ctx.scope.shapeStrs?.delete(name)
|
|
274
|
+
ctx.scope.shapeStrArrays?.delete(name)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
207
278
|
return ast
|
|
208
279
|
}
|
|
209
280
|
|
|
@@ -234,16 +305,28 @@ const renameFunc = (func, nextName) => {
|
|
|
234
305
|
}
|
|
235
306
|
|
|
236
307
|
/** Map JS typeof strings to jz type checks. Codes < 0 trigger specialized emitTypeofCmp paths. */
|
|
237
|
-
const TYPEOF_MAP = { 'number': -1, 'string': -2, 'undefined': -3, 'boolean': -4, 'object': -5, 'function': -6 }
|
|
308
|
+
const TYPEOF_MAP = { 'number': -1, 'string': -2, 'undefined': -3, 'boolean': -4, 'object': -5, 'function': -6, 'bigint': -7 }
|
|
309
|
+
// Constant fold typeof for known builtin namespaces (e.g. Math.exp). prep(x) resolves Math.exp → 'math.exp'.
|
|
310
|
+
function staticTypeofString(x) {
|
|
311
|
+
// Bare callable global: parseInt, parseFloat, isNaN, isFinite, Error, BigInt, etc.
|
|
312
|
+
if (typeof x === 'string' && !ctx.func?.locals?.has(x) && GLOBALS[x] && ctx.core.emit?.[x]?.length > 0) return 'function'
|
|
313
|
+
const px = prep(x)
|
|
314
|
+
if (typeof px === 'string' && px.includes('.') && ctx.core.emit?.[px]?.length > 0) return 'function'
|
|
315
|
+
return null
|
|
316
|
+
}
|
|
238
317
|
function resolveTypeof(node) {
|
|
239
318
|
const [op, a, b] = node
|
|
240
319
|
// typeof x == 'string' → type check
|
|
241
320
|
if (Array.isArray(a) && a[0] === 'typeof' && Array.isArray(b) && b[0] == null && typeof b[1] === 'string') {
|
|
321
|
+
const known = staticTypeofString(a[1])
|
|
322
|
+
if (known != null) return [, op === '==' ? known === b[1] : known !== b[1]]
|
|
242
323
|
const code = TYPEOF_MAP[b[1]]
|
|
243
324
|
if (code != null) return [op, ['typeof', a[1]], [, code]]
|
|
244
325
|
}
|
|
245
326
|
// 'string' == typeof x
|
|
246
327
|
if (Array.isArray(b) && b[0] === 'typeof' && Array.isArray(a) && a[0] == null && typeof a[1] === 'string') {
|
|
328
|
+
const known = staticTypeofString(b[1])
|
|
329
|
+
if (known != null) return [, op === '==' ? known === a[1] : known !== a[1]]
|
|
247
330
|
const code = TYPEOF_MAP[a[1]]
|
|
248
331
|
if (code != null) return [op, ['typeof', b[1]], [, code]]
|
|
249
332
|
}
|
|
@@ -418,6 +501,8 @@ function prep(node) {
|
|
|
418
501
|
if (PROHIBITED[node]) err(PROHIBITED[node])
|
|
419
502
|
// Boolean/Number as value → identity arrow (for .filter(Boolean), .map(Number) etc.)
|
|
420
503
|
if (node === 'Boolean' || node === 'Number') { includeForCallableValue(); return ['=>', 'x', 'x'] }
|
|
504
|
+
// Block locals shadow module imports/globals, even when the local keeps the same name.
|
|
505
|
+
if (scopes.length && isDeclared(node)) return resolveScope(node)
|
|
421
506
|
const resolved = ctx.scope.chain[node]
|
|
422
507
|
if (resolved?.includes('.')) return resolved
|
|
423
508
|
// Cross-module import: mangled name (e.g. __util_js$clone)
|
|
@@ -462,6 +547,7 @@ export const GLOBALS = {
|
|
|
462
547
|
Object: 'Object',
|
|
463
548
|
Symbol: 'Symbol',
|
|
464
549
|
JSON: 'JSON',
|
|
550
|
+
Date: 'Date',
|
|
465
551
|
isNaN: 'number',
|
|
466
552
|
isFinite: 'number',
|
|
467
553
|
parseInt: 'number',
|
|
@@ -475,6 +561,34 @@ export const GLOBALS = {
|
|
|
475
561
|
const patternItems = (node) => node?.[0] === ',' ? node.slice(1) : [node]
|
|
476
562
|
const isDestructPattern = (node) => Array.isArray(node) && (node[0] === '[]' || node[0] === '{}')
|
|
477
563
|
|
|
564
|
+
const simpleArrayPatternItems = (pattern) => {
|
|
565
|
+
if (!Array.isArray(pattern) || pattern[0] !== '[]' || pattern.length !== 2) return null
|
|
566
|
+
const items = patternItems(pattern[1])
|
|
567
|
+
return items.every(item => typeof item === 'string') ? items : null
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const arrayLiteralItems = (expr) => {
|
|
571
|
+
if (!Array.isArray(expr) || expr[0] !== '[]' || expr.length !== 2) return null
|
|
572
|
+
if (expr[1] == null) return []
|
|
573
|
+
const items = patternItems(expr[1])
|
|
574
|
+
return items.every(item => item != null && !(Array.isArray(item) && item[0] === '...')) ? items : null
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function scalarArrayDestruct(pattern, rhs) {
|
|
578
|
+
const targets = simpleArrayPatternItems(pattern)
|
|
579
|
+
const values = arrayLiteralItems(rhs)
|
|
580
|
+
if (!targets || !values || targets.length !== values.length) return null
|
|
581
|
+
|
|
582
|
+
const decls = []
|
|
583
|
+
const assigns = []
|
|
584
|
+
for (let i = 0; i < targets.length; i++) {
|
|
585
|
+
const tmp = `${T}d${ctx.func.uniq++}`
|
|
586
|
+
decls.push(['=', tmp, prep(values[i])])
|
|
587
|
+
assigns.push(['=', targets[i], tmp])
|
|
588
|
+
}
|
|
589
|
+
return prep([';', ['let', ...decls], ...assigns])
|
|
590
|
+
}
|
|
591
|
+
|
|
478
592
|
function pushPatternAssign(target, valueExpr, out, decls = null) {
|
|
479
593
|
if (Array.isArray(target) && target[0] === '=') {
|
|
480
594
|
pushPatternAssign(target[1], ['??', valueExpr, prep(target[2])], out, decls)
|
|
@@ -595,13 +709,18 @@ function prepDecl(op, ...inits) {
|
|
|
595
709
|
|
|
596
710
|
if (!defFunc(name, normed)) {
|
|
597
711
|
let declName = name
|
|
598
|
-
// Block scope: rename if shadowing an outer declaration
|
|
599
|
-
|
|
712
|
+
// Block scope: rename if shadowing an outer declaration, OR if a sibling
|
|
713
|
+
// block at the same arrow scope already declared this name (sibling
|
|
714
|
+
// blocks both lower to the same WASM local; see funcLocalNames comment).
|
|
715
|
+
const fnNames = funcLocalNames[funcLocalNames.length - 1]
|
|
716
|
+
const inCurrentBlock = scopes.length > 0 && scopes[scopes.length - 1].has(name)
|
|
717
|
+
if (typeof name === 'string' && scopes.length > 0 && (isDeclared(name) || (fnNames?.has(name) && !inCurrentBlock))) {
|
|
600
718
|
declName = `${name}${T}${ctx.func.uniq++}`
|
|
601
719
|
scopes[scopes.length - 1].set(name, declName)
|
|
602
720
|
} else if (typeof name === 'string' && scopes.length > 0) {
|
|
603
721
|
scopes[scopes.length - 1].set(name, name)
|
|
604
722
|
}
|
|
723
|
+
if (typeof declName === 'string' && fnNames) fnNames.add(declName)
|
|
605
724
|
// Track const for reassignment checks — only module-scope consts (depth 0)
|
|
606
725
|
if (typeof declName === 'string' && depth === 0) {
|
|
607
726
|
if (ctx.module.currentPrefix) {
|
|
@@ -613,10 +732,19 @@ function prepDecl(op, ...inits) {
|
|
|
613
732
|
ctx.scope.consts.add(declName)
|
|
614
733
|
if (Array.isArray(normed) && normed[0] === 'str' && typeof normed[1] === 'string')
|
|
615
734
|
(ctx.scope.constStrs ||= new Map()).set(declName, normed[1])
|
|
735
|
+
const strs = stringArrayValues(normed)
|
|
736
|
+
if (strs) (ctx.scope.shapeStrArrays ||= new Map()).set(declName, strs)
|
|
616
737
|
} else if (op === 'let' && ctx.scope.consts?.has(declName)) {
|
|
617
738
|
ctx.scope.consts.delete(declName)
|
|
618
739
|
ctx.scope.constStrs?.delete(declName)
|
|
740
|
+
ctx.scope.shapeStrArrays?.delete(declName)
|
|
619
741
|
}
|
|
742
|
+
// Effectively-const string literals: shape inference for `let SRC = '{...}'`
|
|
743
|
+
// patterns (bench convention to defeat compile-time JSON.parse fold without
|
|
744
|
+
// losing schema knowledge). Recorded on init; post-prep scan removes any
|
|
745
|
+
// entry whose name is later assigned to.
|
|
746
|
+
if (Array.isArray(normed) && normed[0] === 'str' && typeof normed[1] === 'string')
|
|
747
|
+
(ctx.scope.shapeStrs ||= new Map()).set(declName, normed[1])
|
|
620
748
|
recordGlobalValueFact(declName, normed)
|
|
621
749
|
}
|
|
622
750
|
// Track object schemas (after prefix so schema is keyed to final name)
|
|
@@ -671,6 +799,9 @@ const handlers = {
|
|
|
671
799
|
// Destructuring assignment: [a, ...r] = expr or ({x: a} = expr)
|
|
672
800
|
// Distinguishing from index assignment: destructuring patterns have exactly one payload node.
|
|
673
801
|
if (isDestructPattern(lhs) && lhs.length === 2) {
|
|
802
|
+
const scalar = scalarArrayDestruct(lhs, rhs)
|
|
803
|
+
if (scalar) return scalar
|
|
804
|
+
|
|
674
805
|
const normed = prep(rhs)
|
|
675
806
|
const tmp = `${T}d${ctx.func.uniq++}`
|
|
676
807
|
const decls = [['=', tmp, normed]]
|
|
@@ -695,7 +826,7 @@ const handlers = {
|
|
|
695
826
|
if (depth === 0 && Array.isArray(lhs) && lhs[0] === '.' && typeof lhs[1] === 'string'
|
|
696
827
|
&& hasFunc(lhs[1]) && Array.isArray(rhs) && rhs[0] === '=>') {
|
|
697
828
|
const name = `${lhs[1]}$${lhs[2]}`
|
|
698
|
-
if (defFunc(name, prep(rhs))) return
|
|
829
|
+
if (defFunc(name, prep(rhs))) return ['=', prep(lhs), name]
|
|
699
830
|
}
|
|
700
831
|
return ['=', prep(lhs), prep(rhs)]
|
|
701
832
|
},
|
|
@@ -762,7 +893,7 @@ const handlers = {
|
|
|
762
893
|
if (hostMod && Array.isArray(specifiers) && specifiers[0] === '{}') {
|
|
763
894
|
const inner = specifiers[1]
|
|
764
895
|
if (inner != null) {
|
|
765
|
-
const items = Array.isArray(inner) && inner[0] === ',' ? inner.slice(1) : [inner]
|
|
896
|
+
const items = (Array.isArray(inner) && inner[0] === ',' ? inner.slice(1) : [inner]).filter(x => x != null)
|
|
766
897
|
const builtinItems = []
|
|
767
898
|
for (const item of items) {
|
|
768
899
|
const name = typeof item === 'string' ? item : item[1]
|
|
@@ -800,7 +931,7 @@ const handlers = {
|
|
|
800
931
|
if (Array.isArray(remaining) && remaining[0] === '{}') {
|
|
801
932
|
const inner = remaining[1]
|
|
802
933
|
if (inner == null) return null
|
|
803
|
-
const items = Array.isArray(inner) && inner[0] === ',' ? inner.slice(1) : [inner]
|
|
934
|
+
const items = (Array.isArray(inner) && inner[0] === ',' ? inner.slice(1) : [inner]).filter(x => x != null)
|
|
804
935
|
for (const item of items)
|
|
805
936
|
if (typeof item === 'string') bind(item)
|
|
806
937
|
else if (Array.isArray(item) && item[0] === 'as') bind(item[1], item[2])
|
|
@@ -831,7 +962,7 @@ const handlers = {
|
|
|
831
962
|
if (Array.isArray(specifiers) && specifiers[0] === '{}') {
|
|
832
963
|
const inner = specifiers[1]
|
|
833
964
|
if (inner == null) return null
|
|
834
|
-
const items = Array.isArray(inner) && inner[0] === ',' ? inner.slice(1) : [inner]
|
|
965
|
+
const items = (Array.isArray(inner) && inner[0] === ',' ? inner.slice(1) : [inner]).filter(x => x != null)
|
|
835
966
|
for (const item of items) {
|
|
836
967
|
const name = typeof item === 'string' ? item : item[1]
|
|
837
968
|
const alias = typeof item === 'string' ? item : item[2]
|
|
@@ -848,7 +979,7 @@ const handlers = {
|
|
|
848
979
|
if (Array.isArray(specifiers) && specifiers[0] === '{}') {
|
|
849
980
|
const inner = specifiers[1]
|
|
850
981
|
if (inner == null) return null
|
|
851
|
-
const items = Array.isArray(inner) && inner[0] === ',' ? inner.slice(1) : [inner]
|
|
982
|
+
const items = (Array.isArray(inner) && inner[0] === ',' ? inner.slice(1) : [inner]).filter(x => x != null)
|
|
852
983
|
for (const item of items) {
|
|
853
984
|
const name = typeof item === 'string' ? item : item[1]
|
|
854
985
|
const alias = typeof item === 'string' ? item : item[2]
|
|
@@ -890,6 +1021,34 @@ const handlers = {
|
|
|
890
1021
|
for (const i of decl.slice(1))
|
|
891
1022
|
if (Array.isArray(i) && i[0] === '=' && typeof i[1] === 'string')
|
|
892
1023
|
ctx.func.exports[i[1]] = true
|
|
1024
|
+
// export { name, name as alias } from './mod' or export * from './mod'
|
|
1025
|
+
if (Array.isArray(decl) && decl[0] === 'from') {
|
|
1026
|
+
const mod = decl[2]?.[1]
|
|
1027
|
+
if (!mod || typeof mod !== 'string') return null
|
|
1028
|
+
// Source module re-export
|
|
1029
|
+
if (ctx.module.importSources?.[mod]) {
|
|
1030
|
+
const resolved = prepareModule(mod, ctx.module.importSources[mod])
|
|
1031
|
+
if (decl[1] === '*') {
|
|
1032
|
+
// export * from './mod' → register all exports
|
|
1033
|
+
for (const [name, mangled] of resolved.exports) {
|
|
1034
|
+
if (name !== 'default') ctx.func.exports[name] = mangled
|
|
1035
|
+
}
|
|
1036
|
+
} else if (Array.isArray(decl[1]) && decl[1][0] === '{}') {
|
|
1037
|
+
// export { a, b as c } from './mod'
|
|
1038
|
+
const inner = decl[1][1]
|
|
1039
|
+
if (inner == null) return null
|
|
1040
|
+
const items = (Array.isArray(inner) && inner[0] === ',' ? inner.slice(1) : [inner]).filter(x => x != null)
|
|
1041
|
+
for (const item of items) {
|
|
1042
|
+
const name = typeof item === 'string' ? item : item[1]
|
|
1043
|
+
const alias = typeof item === 'string' ? item : item[2]
|
|
1044
|
+
const mangled = resolved.exports.get(name)
|
|
1045
|
+
if (!mangled) err(`'${name}' is not exported from '${mod}'`)
|
|
1046
|
+
ctx.func.exports[alias] = mangled
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return null
|
|
1051
|
+
}
|
|
893
1052
|
// export { name1, name2 as alias } → register named exports
|
|
894
1053
|
if (Array.isArray(decl) && decl[0] === '{}') {
|
|
895
1054
|
const inner = decl[1]
|
|
@@ -937,6 +1096,7 @@ const handlers = {
|
|
|
937
1096
|
|
|
938
1097
|
depth++
|
|
939
1098
|
scopes.push(fnScope)
|
|
1099
|
+
funcLocalNames.push(new Set(collectParamNames(raw)))
|
|
940
1100
|
|
|
941
1101
|
const nextParams = []
|
|
942
1102
|
const bodyPrefix = []
|
|
@@ -969,6 +1129,7 @@ const handlers = {
|
|
|
969
1129
|
const inner = nextParams.length === 0 ? null : nextParams.length === 1 ? nextParams[0] : [',', ...nextParams]
|
|
970
1130
|
const result = ['=>', Array.isArray(params) && params[0] === '()' ? ['()', inner] : inner, preparedBody]
|
|
971
1131
|
scopes.pop()
|
|
1132
|
+
funcLocalNames.pop()
|
|
972
1133
|
depth--
|
|
973
1134
|
return result
|
|
974
1135
|
},
|
|
@@ -1001,6 +1162,8 @@ const handlers = {
|
|
|
1001
1162
|
// Boolean literals NaN-box as f64 — typeof at runtime returns 'number'. Fold here so the JS-spec value survives.
|
|
1002
1163
|
'typeof'(a) {
|
|
1003
1164
|
if (Array.isArray(a) && a[0] == null && typeof a[1] === 'boolean') { includeForStringOnly(); return ['str', 'boolean'] }
|
|
1165
|
+
const known = staticTypeofString(a)
|
|
1166
|
+
if (known != null) { includeForStringOnly(); return ['str', known] }
|
|
1004
1167
|
return ['typeof', prep(a)]
|
|
1005
1168
|
},
|
|
1006
1169
|
|
|
@@ -1048,6 +1211,14 @@ const handlers = {
|
|
|
1048
1211
|
// Grouping: (expr) → ['()', expr] with no args. Call: f() → ['()', 'f', null] with null arg.
|
|
1049
1212
|
if (args.length === 0) return prep(callee)
|
|
1050
1213
|
|
|
1214
|
+
if (isImportMetaProp(callee, 'resolve')) {
|
|
1215
|
+
const callArgs = flatArgs(args).filter(a => a != null)
|
|
1216
|
+
if (callArgs.length !== 1) err('`import.meta.resolve` requires one string literal argument')
|
|
1217
|
+
const spec = stringValue(callArgs[0])
|
|
1218
|
+
if (spec == null) err('`import.meta.resolve` supports only string literal arguments')
|
|
1219
|
+
return staticString(resolveImportMeta(spec))
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1051
1222
|
const hasRealArgs = args.some(a => a != null)
|
|
1052
1223
|
|
|
1053
1224
|
if (typeof callee === 'string') {
|
|
@@ -1060,11 +1231,13 @@ const handlers = {
|
|
|
1060
1231
|
}
|
|
1061
1232
|
}
|
|
1062
1233
|
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1234
|
+
const local = scopes.length && isDeclared(callee)
|
|
1235
|
+
const resolved = local ? null : ctx.scope.chain[callee]
|
|
1236
|
+
if (local) callee = resolveScope(callee)
|
|
1237
|
+
else if (resolved?.includes('.')) callee = resolved
|
|
1065
1238
|
else if (resolved && hasFunc(resolved)) callee = resolved
|
|
1066
1239
|
else if (resolved && !resolved.includes('.')) {
|
|
1067
|
-
if (!ctx.module.imports.some(i => i[3]?.[1] === `$${resolved}`)) includeModule(resolved)
|
|
1240
|
+
if (hasModule(resolved) && !ctx.module.imports.some(i => i[3]?.[1] === `$${resolved}`)) includeModule(resolved)
|
|
1068
1241
|
}
|
|
1069
1242
|
else if (depth > 0 && !resolved && !ctx.func.exports[callee] && !ctx.module.imports.some(i => i[3]?.[1] === `$${callee}`)) {
|
|
1070
1243
|
includeForCallableValue()
|
|
@@ -1094,6 +1267,16 @@ const handlers = {
|
|
|
1094
1267
|
callee = prep(callee)
|
|
1095
1268
|
}
|
|
1096
1269
|
|
|
1270
|
+
// Drop trailing-comma sentinel inside a comma group: `f(a, b,)` parses as
|
|
1271
|
+
// ['()', 'f', [',', a, b, null]] — without trimming, the trailing null
|
|
1272
|
+
// becomes a [, 0] literal and inflates arguments.length.
|
|
1273
|
+
if (args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',') {
|
|
1274
|
+
let end = args[0].length
|
|
1275
|
+
while (end > 1 && args[0][end - 1] == null) end--
|
|
1276
|
+
if (end < args[0].length) {
|
|
1277
|
+
args[0] = end === 2 ? args[0][1] : args[0].slice(0, end)
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1097
1280
|
const preppedArgs = args.filter(a => a != null).map(prep)
|
|
1098
1281
|
for (const a of preppedArgs) {
|
|
1099
1282
|
if (typeof a === 'string' && hasFunc(a)) {
|
|
@@ -1162,9 +1345,28 @@ const handlers = {
|
|
|
1162
1345
|
}
|
|
1163
1346
|
return prep(p)
|
|
1164
1347
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1348
|
+
// Drop trailing-comma artifacts: subscript represents `{a:1, b,}` as
|
|
1349
|
+
// `[",", [":","a",1], "b", null]` — the trailing `null` would prep to a
|
|
1350
|
+
// literal-0 entry, leaving the literal carrying a phantom slot and
|
|
1351
|
+
// shifting any subsequent slot-position resolution.
|
|
1352
|
+
const items = Array.isArray(inner) && inner[0] === ','
|
|
1353
|
+
? inner.slice(1).filter(p => p != null)
|
|
1354
|
+
: [inner]
|
|
1355
|
+
let prepped = items.map(prop)
|
|
1356
|
+
// ES spec: duplicate keys allowed; key takes first-seen position, last-seen value.
|
|
1357
|
+
const lastValue = new Map()
|
|
1358
|
+
for (const p of prepped) if (Array.isArray(p) && p[0] === ':') lastValue.set(p[1], p[2])
|
|
1359
|
+
if (lastValue.size < prepped.filter(p => Array.isArray(p) && p[0] === ':').length) {
|
|
1360
|
+
const seen = new Set()
|
|
1361
|
+
prepped = prepped.filter(p => {
|
|
1362
|
+
if (!Array.isArray(p) || p[0] !== ':') return true
|
|
1363
|
+
if (seen.has(p[1])) return false
|
|
1364
|
+
seen.add(p[1])
|
|
1365
|
+
p[2] = lastValue.get(p[1])
|
|
1366
|
+
return true
|
|
1367
|
+
})
|
|
1368
|
+
}
|
|
1369
|
+
const result = ['{}', ...prepped]
|
|
1168
1370
|
// Register schema so property access works for function params (duck typing)
|
|
1169
1371
|
const props = result.slice(1).filter(p => Array.isArray(p) && p[0] === ':').map(p => p[1])
|
|
1170
1372
|
if (props.length && ctx.schema.register) ctx.schema.register(props)
|
|
@@ -1178,15 +1380,19 @@ const handlers = {
|
|
|
1178
1380
|
if (Array.isArray(head) && head[0] === ';') {
|
|
1179
1381
|
let [, init, cond, step] = head
|
|
1180
1382
|
// Hoist .length / .size / .byteLength from for-condition:
|
|
1181
|
-
// `i < arr.length` → `let __len = arr.length; ... i < __len`
|
|
1182
|
-
//
|
|
1183
|
-
//
|
|
1383
|
+
// `i < arr.length` → `let __len = arr.length | 0; ... i < __len`
|
|
1384
|
+
// The `| 0` forces i32 even for unknown-typed receivers (where __length
|
|
1385
|
+
// returns f64). NaN→0 via i32.trunc_sat matches JS semantics: a NaN bound
|
|
1386
|
+
// makes `i < NaN` false on both representations, so the loop is skipped
|
|
1387
|
+
// either way. Keeping the hoisted bound i32 lets the counter `i` stay i32
|
|
1388
|
+
// through the comparison and `i++`, eliminating the per-iteration
|
|
1389
|
+
// f64.convert_i32_s + f64.lt + f64.add + i32.trunc_sat_f64_s sequence.
|
|
1184
1390
|
if (cond && Array.isArray(cond) && (cond[0] === '<' || cond[0] === '<=' || cond[0] === '>' || cond[0] === '>=')) {
|
|
1185
1391
|
const lenExpr = cond[0] === '<' || cond[0] === '<=' ? cond[2] : cond[1]
|
|
1186
1392
|
if (Array.isArray(lenExpr) && lenExpr[0] === '.' &&
|
|
1187
1393
|
(lenExpr[2] === 'length' || lenExpr[2] === 'size' || lenExpr[2] === 'byteLength')) {
|
|
1188
1394
|
const lenVar = `${T}len${ctx.func.uniq++}`
|
|
1189
|
-
const lenDecl = ['let', ['=', lenVar, lenExpr]]
|
|
1395
|
+
const lenDecl = ['let', ['=', lenVar, ['|', lenExpr, [, 0]]]]
|
|
1190
1396
|
init = init ? [';', init, lenDecl] : lenDecl
|
|
1191
1397
|
if (cond[0] === '<' || cond[0] === '<=') cond = [cond[0], cond[1], lenVar]
|
|
1192
1398
|
else cond = [cond[0], lenVar, cond[2]]
|
|
@@ -1203,9 +1409,12 @@ const handlers = {
|
|
|
1203
1409
|
const lenVar = `${T}len${ctx.func.uniq++}`
|
|
1204
1410
|
const trivial = typeof src === 'string'
|
|
1205
1411
|
const arrVar = trivial ? src : `${T}arr${ctx.func.uniq++}`
|
|
1412
|
+
// Wrap .length in `| 0` so the hoisted bound is i32 even for unknown
|
|
1413
|
+
// receivers (same rationale as the for-cond hoist above).
|
|
1414
|
+
const lenE = ['|', ['.', arrVar, 'length'], [, 0]]
|
|
1206
1415
|
const decls = trivial
|
|
1207
|
-
? ['let', ['=', idx, [, 0]], ['=', lenVar,
|
|
1208
|
-
: ['let', ['=', arrVar, src], ['=', idx, [, 0]], ['=', lenVar,
|
|
1416
|
+
? ['let', ['=', idx, [, 0]], ['=', lenVar, lenE]]
|
|
1417
|
+
: ['let', ['=', arrVar, src], ['=', idx, [, 0]], ['=', lenVar, lenE]]
|
|
1209
1418
|
const cond = ['<', idx, lenVar]
|
|
1210
1419
|
const step = ['++', idx]
|
|
1211
1420
|
const inner = [';', ['let', ['=', varName, ['[]', arrVar, idx]]], body]
|
|
@@ -1213,9 +1422,9 @@ const handlers = {
|
|
|
1213
1422
|
} else if (Array.isArray(head) && head[0] === 'in') {
|
|
1214
1423
|
// for (let k in obj) → unroll at compile time when schema known, else HASH runtime iteration
|
|
1215
1424
|
const [, decl, src] = head
|
|
1216
|
-
const varName = Array.isArray(decl) && decl[0] === 'let' ? decl[1] : decl
|
|
1425
|
+
const varName = Array.isArray(decl) && (decl[0] === 'let' || decl[0] === 'const') ? decl[1] : decl
|
|
1217
1426
|
const srcName = typeof src === 'string' ? (ctx.scope.chain[src] || src) : null
|
|
1218
|
-
const sid = typeof srcName === 'string'
|
|
1427
|
+
const sid = typeof srcName === 'string' ? ctx.schema.vars.get(srcName) : null
|
|
1219
1428
|
if (sid != null) {
|
|
1220
1429
|
// Known schema → compile-time unrolling with string keys
|
|
1221
1430
|
const keys = ctx.schema.list[sid]
|
|
@@ -1244,10 +1453,15 @@ const handlers = {
|
|
|
1244
1453
|
// Property access - resolve namespaces or object/array properties
|
|
1245
1454
|
'.'(obj, prop) {
|
|
1246
1455
|
if (prop === 'caller' || prop === 'callee') err('`.caller` and `.callee` are prohibited: deprecated stack introspection')
|
|
1456
|
+
if (prop === 'url' && isImportMeta(obj)) return staticString(importMetaUrl())
|
|
1247
1457
|
const mod = ctx.scope.chain[obj]
|
|
1248
1458
|
// Only treat as module namespace if it's a known built-in module (not a mangled import name)
|
|
1249
|
-
if (typeof obj === 'string' && mod && !mod.includes('.') && hasModule(mod))
|
|
1250
|
-
|
|
1459
|
+
if (typeof obj === 'string' && mod && !mod.includes('.') && hasModule(mod)) {
|
|
1460
|
+
includeModule(mod)
|
|
1461
|
+
const key = mod + '.' + prop
|
|
1462
|
+
if (ctx.core.emit[key]?.length > 0) includeForCallableValue()
|
|
1463
|
+
return key
|
|
1464
|
+
}
|
|
1251
1465
|
// Source module namespace: import * as X → X.prop resolved to mangled name
|
|
1252
1466
|
if (typeof obj === 'string' && ctx.module.namespaces?.[obj]) {
|
|
1253
1467
|
const mangled = ctx.module.namespaces[obj].get(prop)
|
|
@@ -1265,6 +1479,15 @@ const handlers = {
|
|
|
1265
1479
|
if (ctorArgs.length === 1 && Array.isArray(ctorArgs[0]) && ctorArgs[0][0] === ',')
|
|
1266
1480
|
ctorArgs = ctorArgs[0].slice(1)
|
|
1267
1481
|
|
|
1482
|
+
if (name === 'URL') {
|
|
1483
|
+
const literalArgs = ctorArgs.filter(a => a != null)
|
|
1484
|
+
if (literalArgs.length === 2 && isImportMetaProp(literalArgs[1], 'url')) {
|
|
1485
|
+
const spec = stringValue(literalArgs[0])
|
|
1486
|
+
if (spec == null) err('`new URL(relative, import.meta.url)` supports only string literal relatives')
|
|
1487
|
+
return staticString(resolveImportMeta(spec))
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1268
1491
|
// Wrap multi-arg ctor arg lists back into a single comma-group — the '()' op
|
|
1269
1492
|
// expects callArgs as a single element (possibly comma-grouped).
|
|
1270
1493
|
const wrapArgs = (args) => args.length === 0 ? [null]
|
|
@@ -1420,7 +1643,7 @@ function prepareModule(specifier, source) {
|
|
|
1420
1643
|
ctx.module.currentPrefix = prefix
|
|
1421
1644
|
|
|
1422
1645
|
// Parse + prepare imported source (may trigger recursive imports)
|
|
1423
|
-
let ast = parse(
|
|
1646
|
+
let ast = parse(source)
|
|
1424
1647
|
if (ctx.transform.jzify) ast = ctx.transform.jzify(ast)
|
|
1425
1648
|
const savedDepth = depth; depth = 0
|
|
1426
1649
|
const moduleInit = prep(ast)
|
|
@@ -1428,6 +1651,19 @@ function prepareModule(specifier, source) {
|
|
|
1428
1651
|
|
|
1429
1652
|
// Collect exports: rename exported funcs with prefix
|
|
1430
1653
|
const moduleExports = new Map()
|
|
1654
|
+
const exportLocal = (exportName, localName) => {
|
|
1655
|
+
const mangled = `${prefix}$${localName}`
|
|
1656
|
+
moduleExports.set(exportName, mangled)
|
|
1657
|
+
const func = ctx.func.list.find(f => f.name === localName)
|
|
1658
|
+
if (func) renameFunc(func, mangled)
|
|
1659
|
+
if (ctx.scope.globals.has(localName)) {
|
|
1660
|
+
const wat = ctx.scope.globals.get(localName).replace(`$${localName}`, `$${mangled}`)
|
|
1661
|
+
ctx.scope.globals.delete(localName)
|
|
1662
|
+
ctx.scope.globals.set(mangled, wat)
|
|
1663
|
+
if (ctx.scope.userGlobals.has(localName)) { ctx.scope.userGlobals.delete(localName); ctx.scope.userGlobals.add(mangled) }
|
|
1664
|
+
if (ctx.scope.globalTypes.has(localName)) { ctx.scope.globalTypes.set(mangled, ctx.scope.globalTypes.get(localName)); ctx.scope.globalTypes.delete(localName) }
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1431
1667
|
for (const name of Object.keys(ctx.func.exports)) {
|
|
1432
1668
|
const val = ctx.func.exports[name]
|
|
1433
1669
|
// Default export alias: export default existingName → map 'default' to that name's mangled form
|
|
@@ -1435,19 +1671,29 @@ function prepareModule(specifier, source) {
|
|
|
1435
1671
|
// Will resolve after all named exports are mangled
|
|
1436
1672
|
continue
|
|
1437
1673
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
if (
|
|
1449
|
-
|
|
1674
|
+
// Re-export alias: export { x } from './mod' → pass through inner module's mangled name
|
|
1675
|
+
if (typeof val === 'string') {
|
|
1676
|
+
if (val.startsWith(prefix + '$')) {
|
|
1677
|
+
moduleExports.set(name, val)
|
|
1678
|
+
continue
|
|
1679
|
+
}
|
|
1680
|
+
// Re-export of a binding imported from another module: val already carries
|
|
1681
|
+
// that other module's prefix (e.g. `__c$x`). Renaming it under our own
|
|
1682
|
+
// prefix would break in-module call sites that still reference the
|
|
1683
|
+
// original mangled name. Pass through verbatim.
|
|
1684
|
+
if (val.includes('$') &&
|
|
1685
|
+
(ctx.func.list.some(f => f.name === val) || ctx.scope.globals.has(val))) {
|
|
1686
|
+
moduleExports.set(name, val)
|
|
1687
|
+
continue
|
|
1688
|
+
}
|
|
1689
|
+
if (ctx.func.list.some(f => f.name === val || f.name === `${prefix}$${val}`) || ctx.scope.globals.has(val) || ctx.scope.globals.has(`${prefix}$${val}`)) {
|
|
1690
|
+
exportLocal(name, val)
|
|
1691
|
+
continue
|
|
1692
|
+
}
|
|
1693
|
+
moduleExports.set(name, val)
|
|
1694
|
+
continue
|
|
1450
1695
|
}
|
|
1696
|
+
exportLocal(name, name)
|
|
1451
1697
|
}
|
|
1452
1698
|
// Resolve default export alias after named exports are mangled
|
|
1453
1699
|
if (typeof ctx.func.exports['default'] === 'string') {
|