jz 0.1.0 → 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 +221 -62
- package/cli.js +34 -8
- package/index.js +133 -14
- package/module/array.js +399 -131
- package/module/collection.js +457 -212
- package/module/console.js +170 -87
- package/module/core.js +247 -143
- package/module/date.js +691 -0
- package/module/function.js +84 -23
- package/module/index.js +2 -1
- package/module/json.js +485 -138
- package/module/math.js +81 -40
- package/module/number.js +189 -83
- package/module/object.js +228 -46
- package/module/regex.js +34 -30
- package/module/schema.js +17 -18
- package/module/string.js +483 -227
- package/module/symbol.js +6 -4
- package/module/timer.js +90 -32
- package/module/typedarray.js +176 -44
- package/package.json +5 -10
- package/src/analyze.js +639 -124
- package/src/autoload.js +183 -0
- package/src/compile.js +448 -1083
- package/src/ctx.js +42 -12
- package/src/emit.js +646 -147
- package/src/host.js +273 -68
- package/src/ir.js +81 -31
- package/src/jzify.js +220 -36
- package/src/narrow.js +956 -0
- package/src/optimize.js +628 -42
- package/src/plan.js +1233 -0
- package/src/prepare.js +438 -256
- package/src/vectorize.js +1016 -0
- package/wasi.js +23 -4
package/src/prepare.js
CHANGED
|
@@ -22,13 +22,45 @@
|
|
|
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, extractParams, collectParamNames, classifyParam } from './analyze.js'
|
|
28
|
-
import
|
|
27
|
+
import { T, STMT_OPS, VAL, valTypeOf, typedElemCtor, extractParams, collectParamNames, classifyParam, observeNodeFacts, staticPropertyKey } from './analyze.js'
|
|
28
|
+
import { isFuncRef } from './ir.js'
|
|
29
|
+
import {
|
|
30
|
+
CTORS, TIMER_NAMES,
|
|
31
|
+
hasModule, includeModule,
|
|
32
|
+
includeForArrayAccess, includeForArrayLiteral, includeForArrayPattern, includeForCallableValue,
|
|
33
|
+
includeForGenericMethod, includeForKnownKeyIteration, includeForNamedCall, includeForNumericCoercion,
|
|
34
|
+
includeForObjectLiteral, includeForObjectPattern, includeForOp, includeForProperty, includeForRuntimeCtor,
|
|
35
|
+
includeForRuntimeKeyIteration, includeForStringOnly, includeForStringValue, includeForTimerRuntime,
|
|
36
|
+
} from './autoload.js'
|
|
29
37
|
|
|
30
38
|
let depth = 0 // arrow nesting depth (0=top-level, >0=inside function)
|
|
31
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
|
+
}
|
|
32
64
|
|
|
33
65
|
const hostReturnValType = spec => {
|
|
34
66
|
if (!spec || typeof spec === 'function') return null
|
|
@@ -41,15 +73,102 @@ const hostReturnValType = spec => {
|
|
|
41
73
|
|
|
42
74
|
const addHostImport = (mod, name, alias, spec) => {
|
|
43
75
|
const nParams = typeof spec === 'function' ? spec.length : (spec?.params || 0)
|
|
44
|
-
|
|
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'])
|
|
45
81
|
if (!ctx.module.imports.some(i => i[3]?.[1] === `$${alias}`)) {
|
|
46
|
-
ctx.module.imports.push(['import', `"${mod}"`, `"${name}"`, ['func', `$${alias}`, ...params, ['result', '
|
|
82
|
+
ctx.module.imports.push(['import', `"${mod}"`, `"${name}"`, ['func', `$${alias}`, ...params, ['result', 'i64']]])
|
|
47
83
|
}
|
|
48
84
|
ctx.scope.chain[alias] = alias
|
|
49
85
|
const vt = hostReturnValType(spec)
|
|
50
86
|
if (vt) ctx.module.hostImportValTypes.set(alias, vt)
|
|
51
87
|
}
|
|
52
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
|
+
|
|
121
|
+
function recordGlobalValueFact(name, expr) {
|
|
122
|
+
if (typeof name !== 'string') return
|
|
123
|
+
const vt = valTypeOf(expr)
|
|
124
|
+
if (vt) {
|
|
125
|
+
;(ctx.scope.globalValTypes ||= new Map()).set(name, vt)
|
|
126
|
+
if (vt === VAL.REGEX && ctx.runtime.regex) ctx.runtime.regex.vars.set(name, expr)
|
|
127
|
+
}
|
|
128
|
+
const ctor = typedElemCtor(expr)
|
|
129
|
+
if (ctor) (ctx.scope.globalTypedElem ||= new Map()).set(name, ctor)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function recordModuleInitFacts(root) {
|
|
133
|
+
const facts = ctx.module.initFacts ||= {
|
|
134
|
+
dynVars: new Set(), anyDyn: false, hasSchemaLiterals: false,
|
|
135
|
+
hasFuncValue: false, timerNames: new Set(),
|
|
136
|
+
maxDef: 0, maxCall: 0, hasRest: false, hasSpread: false,
|
|
137
|
+
}
|
|
138
|
+
const visitFuncValue = (node) => {
|
|
139
|
+
if (facts.hasFuncValue || !Array.isArray(node)) return
|
|
140
|
+
const [op, ...args] = node
|
|
141
|
+
if (op === '()') {
|
|
142
|
+
for (let i = 1; i < args.length; i++) {
|
|
143
|
+
const a = args[i]
|
|
144
|
+
if (isFuncRef(a, ctx.func.names)) { facts.hasFuncValue = true; return }
|
|
145
|
+
visitFuncValue(a)
|
|
146
|
+
}
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
if (op === '.' || op === '?.') {
|
|
150
|
+
if (isFuncRef(args[0], ctx.func.names)) { facts.hasFuncValue = true; return }
|
|
151
|
+
visitFuncValue(args[0])
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
if (op === '=>') { visitFuncValue(args[1]); return }
|
|
155
|
+
for (const a of args) {
|
|
156
|
+
if (isFuncRef(a, ctx.func.names)) { facts.hasFuncValue = true; return }
|
|
157
|
+
visitFuncValue(a)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const walk = (node) => {
|
|
161
|
+
if (!Array.isArray(node)) {
|
|
162
|
+
if (typeof node === 'string' && TIMER_NAMES.has(node)) facts.timerNames.add(node)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
observeNodeFacts(node, facts)
|
|
166
|
+
for (const a of node.slice(1)) walk(a)
|
|
167
|
+
}
|
|
168
|
+
visitFuncValue(root)
|
|
169
|
+
walk(root)
|
|
170
|
+
}
|
|
171
|
+
|
|
53
172
|
/**
|
|
54
173
|
* @typedef {null|number|string|ASTNode[]} ASTNode
|
|
55
174
|
*/
|
|
@@ -62,18 +181,28 @@ const addHostImport = (mod, name, alias, spec) => {
|
|
|
62
181
|
export default function prepare(node) {
|
|
63
182
|
depth = 0
|
|
64
183
|
scopes = []
|
|
184
|
+
funcLocalNames = [new Set()]
|
|
65
185
|
includeModule('core')
|
|
186
|
+
normalizeIdents(node)
|
|
66
187
|
fuseSparseMapReads(node)
|
|
67
188
|
const ast = prep(node)
|
|
68
189
|
// Top-level functions referenced as first-class values (e.g. `let o = { fn: g }`,
|
|
69
190
|
// `arr.push(g)`, `return g`) need trampoline emission, which depends on the fn
|
|
70
191
|
// module's closure.table machinery. defFunc paths don't trigger fn-module load,
|
|
71
192
|
// so scan post-prep and include `fn` if any user func appears in a value position.
|
|
72
|
-
|
|
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) {
|
|
73
199
|
const funcNames = new Set(ctx.func.list.map(f => f.name))
|
|
74
200
|
const visit = (n) => {
|
|
75
201
|
if (!Array.isArray(n)) return false
|
|
76
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
|
|
77
206
|
if (op === '()') {
|
|
78
207
|
// callee at args[0]: skip if it's a bare func name (direct call); recurse rest
|
|
79
208
|
if (typeof args[0] !== 'string' || !funcNames.has(args[0])) {
|
|
@@ -91,10 +220,6 @@ export default function prepare(node) {
|
|
|
91
220
|
if (typeof args[0] === 'string' && funcNames.has(args[0])) return true
|
|
92
221
|
return visit(args[0])
|
|
93
222
|
}
|
|
94
|
-
if (op === '=>') {
|
|
95
|
-
// body only — params are bindings, not refs
|
|
96
|
-
return visit(args[1])
|
|
97
|
-
}
|
|
98
223
|
for (const a of args) {
|
|
99
224
|
if (typeof a === 'string' && funcNames.has(a)) return true
|
|
100
225
|
if (visit(a)) return true
|
|
@@ -103,13 +228,12 @@ export default function prepare(node) {
|
|
|
103
228
|
}
|
|
104
229
|
let needs = visit(ast)
|
|
105
230
|
if (!needs) for (const f of ctx.func.list) if (f.body && visit(f.body)) { needs = true; break }
|
|
106
|
-
if (!needs && ctx.module.
|
|
107
|
-
if (needs)
|
|
231
|
+
if (!needs && ctx.module.initFacts?.hasFuncValue) needs = true
|
|
232
|
+
if (needs) includeForCallableValue()
|
|
108
233
|
}
|
|
109
234
|
|
|
110
235
|
// Native timers: inline WASM timer queue when referenced (no host imports needed)
|
|
111
|
-
const
|
|
112
|
-
const usedTimers = new Set()
|
|
236
|
+
const usedTimers = new Set(ctx.module.initFacts?.timerNames || [])
|
|
113
237
|
const scanTimers = (n) => {
|
|
114
238
|
if (!Array.isArray(n)) {
|
|
115
239
|
if (typeof n === 'string' && TIMER_NAMES.has(n)) usedTimers.add(n)
|
|
@@ -117,12 +241,38 @@ export default function prepare(node) {
|
|
|
117
241
|
}
|
|
118
242
|
for (let i = 0; i < n.length; i++) scanTimers(n[i])
|
|
119
243
|
}
|
|
120
|
-
const allNodes = [ast, ...ctx.func.list.map(f => f.body)
|
|
244
|
+
const allNodes = [ast, ...ctx.func.list.map(f => f.body)]
|
|
121
245
|
for (const node of allNodes) scanTimers(node)
|
|
122
246
|
if (usedTimers.size) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
247
|
+
includeForTimerRuntime()
|
|
248
|
+
}
|
|
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
|
+
}
|
|
126
276
|
}
|
|
127
277
|
|
|
128
278
|
return ast
|
|
@@ -146,73 +296,43 @@ function isDeclared(name) {
|
|
|
146
296
|
return scopes.some(s => s.has(name))
|
|
147
297
|
}
|
|
148
298
|
|
|
299
|
+
const hasFunc = name => ctx.func.names.has(name)
|
|
300
|
+
|
|
301
|
+
const renameFunc = (func, nextName) => {
|
|
302
|
+
ctx.func.names.delete(func.name)
|
|
303
|
+
func.name = nextName
|
|
304
|
+
ctx.func.names.add(nextName)
|
|
305
|
+
}
|
|
306
|
+
|
|
149
307
|
/** Map JS typeof strings to jz type checks. Codes < 0 trigger specialized emitTypeofCmp paths. */
|
|
150
|
-
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
|
+
}
|
|
151
317
|
function resolveTypeof(node) {
|
|
152
318
|
const [op, a, b] = node
|
|
153
319
|
// typeof x == 'string' → type check
|
|
154
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]]
|
|
155
323
|
const code = TYPEOF_MAP[b[1]]
|
|
156
324
|
if (code != null) return [op, ['typeof', a[1]], [, code]]
|
|
157
325
|
}
|
|
158
326
|
// 'string' == typeof x
|
|
159
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]]
|
|
160
330
|
const code = TYPEOF_MAP[a[1]]
|
|
161
331
|
if (code != null) return [op, ['typeof', b[1]], [, code]]
|
|
162
332
|
}
|
|
163
333
|
return node
|
|
164
334
|
}
|
|
165
335
|
|
|
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
336
|
const cloneNode = (node) => {
|
|
217
337
|
if (!Array.isArray(node)) return node
|
|
218
338
|
const copy = node.map(cloneNode)
|
|
@@ -220,64 +340,6 @@ const cloneNode = (node) => {
|
|
|
220
340
|
return copy
|
|
221
341
|
}
|
|
222
342
|
|
|
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
343
|
/** Sparse-read .map fusion: rewrite `const b = a.map(arrow); for(...; j<b.length; ...) USE(b[j])`
|
|
282
344
|
* into a fused for-loop that inlines `arrow(a[j])` at the read site, eliminating the materialized
|
|
283
345
|
* intermediate array. Pre-prep AST mutation; only fires on shapes where every use of `b` is a
|
|
@@ -427,7 +489,7 @@ function cloneAndBind(node, PARAM, replacement) {
|
|
|
427
489
|
}
|
|
428
490
|
|
|
429
491
|
function prep(node) {
|
|
430
|
-
if (Array.isArray(node)
|
|
492
|
+
if (Array.isArray(node)) includeForOp(node[0])
|
|
431
493
|
if (Array.isArray(node) && node.loc != null) ctx.error.loc = node.loc
|
|
432
494
|
if (node == null) return [, 0] // null/undefined → 0 literal
|
|
433
495
|
if (node === true) return [, 1]
|
|
@@ -438,7 +500,9 @@ function prep(node) {
|
|
|
438
500
|
if (node in F64_CONSTANTS) return [, F64_CONSTANTS[node]]
|
|
439
501
|
if (PROHIBITED[node]) err(PROHIBITED[node])
|
|
440
502
|
// Boolean/Number as value → identity arrow (for .filter(Boolean), .map(Number) etc.)
|
|
441
|
-
if (node === 'Boolean' || node === 'Number') {
|
|
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)
|
|
442
506
|
const resolved = ctx.scope.chain[node]
|
|
443
507
|
if (resolved?.includes('.')) return resolved
|
|
444
508
|
// Cross-module import: mangled name (e.g. __util_js$clone)
|
|
@@ -453,7 +517,7 @@ function prep(node) {
|
|
|
453
517
|
if (op === 'void' && ctx.transform.strict) err('strict mode: `void` is prohibited. It diverges from JS by evaluating to 0.')
|
|
454
518
|
if (op == null) {
|
|
455
519
|
if (typeof args[0] === 'string') {
|
|
456
|
-
|
|
520
|
+
includeForStringValue()
|
|
457
521
|
return ['str', args[0]] // string literal
|
|
458
522
|
}
|
|
459
523
|
return [, args[0]] // number literal
|
|
@@ -483,6 +547,7 @@ export const GLOBALS = {
|
|
|
483
547
|
Object: 'Object',
|
|
484
548
|
Symbol: 'Symbol',
|
|
485
549
|
JSON: 'JSON',
|
|
550
|
+
Date: 'Date',
|
|
486
551
|
isNaN: 'number',
|
|
487
552
|
isFinite: 'number',
|
|
488
553
|
parseInt: 'number',
|
|
@@ -496,6 +561,34 @@ export const GLOBALS = {
|
|
|
496
561
|
const patternItems = (node) => node?.[0] === ',' ? node.slice(1) : [node]
|
|
497
562
|
const isDestructPattern = (node) => Array.isArray(node) && (node[0] === '[]' || node[0] === '{}')
|
|
498
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
|
+
|
|
499
592
|
function pushPatternAssign(target, valueExpr, out, decls = null) {
|
|
500
593
|
if (Array.isArray(target) && target[0] === '=') {
|
|
501
594
|
pushPatternAssign(target[1], ['??', valueExpr, prep(target[2])], out, decls)
|
|
@@ -517,7 +610,7 @@ function expandDestruct(pattern, source, out, decls = null) {
|
|
|
517
610
|
if (!isDestructPattern(pattern)) return
|
|
518
611
|
|
|
519
612
|
if (pattern[0] === '[]') {
|
|
520
|
-
|
|
613
|
+
includeForArrayPattern()
|
|
521
614
|
const items = patternItems(pattern[1])
|
|
522
615
|
for (let j = 0; j < items.length; j++) {
|
|
523
616
|
const item = items[j]
|
|
@@ -533,7 +626,7 @@ function expandDestruct(pattern, source, out, decls = null) {
|
|
|
533
626
|
return
|
|
534
627
|
}
|
|
535
628
|
|
|
536
|
-
|
|
629
|
+
includeForObjectPattern()
|
|
537
630
|
const items = patternItems(pattern[1])
|
|
538
631
|
|
|
539
632
|
// Collect explicit keys and detect rest pattern
|
|
@@ -592,6 +685,11 @@ function expandDestruct(pattern, source, out, decls = null) {
|
|
|
592
685
|
function prepDecl(op, ...inits) {
|
|
593
686
|
const rest = []
|
|
594
687
|
for (const i of inits) {
|
|
688
|
+
if (Array.isArray(i) && i[0] === '()' && typeof i[1] === 'string' && Array.isArray(i[2]) && i[2][0] === '=' && isDestructPattern(i[2][1])) {
|
|
689
|
+
if (rest.length === 0 && inits.length === 1) return [';', [op, i[1]], prep(i[2])]
|
|
690
|
+
err('destructuring assignment after declaration must be a separate statement')
|
|
691
|
+
}
|
|
692
|
+
|
|
595
693
|
if (!Array.isArray(i) || i[0] !== '=') { rest.push(i); continue }
|
|
596
694
|
const [, name, init] = i, normed = prep(init)
|
|
597
695
|
|
|
@@ -611,13 +709,18 @@ function prepDecl(op, ...inits) {
|
|
|
611
709
|
|
|
612
710
|
if (!defFunc(name, normed)) {
|
|
613
711
|
let declName = name
|
|
614
|
-
// Block scope: rename if shadowing an outer declaration
|
|
615
|
-
|
|
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))) {
|
|
616
718
|
declName = `${name}${T}${ctx.func.uniq++}`
|
|
617
719
|
scopes[scopes.length - 1].set(name, declName)
|
|
618
720
|
} else if (typeof name === 'string' && scopes.length > 0) {
|
|
619
721
|
scopes[scopes.length - 1].set(name, name)
|
|
620
722
|
}
|
|
723
|
+
if (typeof declName === 'string' && fnNames) fnNames.add(declName)
|
|
621
724
|
// Track const for reassignment checks — only module-scope consts (depth 0)
|
|
622
725
|
if (typeof declName === 'string' && depth === 0) {
|
|
623
726
|
if (ctx.module.currentPrefix) {
|
|
@@ -629,10 +732,20 @@ function prepDecl(op, ...inits) {
|
|
|
629
732
|
ctx.scope.consts.add(declName)
|
|
630
733
|
if (Array.isArray(normed) && normed[0] === 'str' && typeof normed[1] === 'string')
|
|
631
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)
|
|
632
737
|
} else if (op === 'let' && ctx.scope.consts?.has(declName)) {
|
|
633
738
|
ctx.scope.consts.delete(declName)
|
|
634
739
|
ctx.scope.constStrs?.delete(declName)
|
|
740
|
+
ctx.scope.shapeStrArrays?.delete(declName)
|
|
635
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])
|
|
748
|
+
recordGlobalValueFact(declName, normed)
|
|
636
749
|
}
|
|
637
750
|
// Track object schemas (after prefix so schema is keyed to final name)
|
|
638
751
|
if (typeof declName === 'string' && Array.isArray(normed) && normed[0] === '{}' && normed.length > 1) {
|
|
@@ -662,8 +775,7 @@ function prepDecl(op, ...inits) {
|
|
|
662
775
|
const handlers = {
|
|
663
776
|
// Spread operator: [...expr] in arrays, f(...args) in calls, {...obj} in objects
|
|
664
777
|
'...'(expr) {
|
|
665
|
-
|
|
666
|
-
includeModule('array')
|
|
778
|
+
includeForArrayLiteral()
|
|
667
779
|
return ['...', prep(expr)]
|
|
668
780
|
},
|
|
669
781
|
|
|
@@ -673,6 +785,7 @@ const handlers = {
|
|
|
673
785
|
'await': () => err('async/await not supported: WASM is synchronous'),
|
|
674
786
|
'class': () => err('class not supported: use object literals'),
|
|
675
787
|
'yield': () => err('generators not supported: use loops'),
|
|
788
|
+
'debugger': () => null,
|
|
676
789
|
'delete': () => err('delete not supported: object shape is fixed'),
|
|
677
790
|
'in'(key, obj) { return ['in', prep(key), prep(obj)] },
|
|
678
791
|
'instanceof': () => err('instanceof not supported: use typeof'),
|
|
@@ -686,6 +799,9 @@ const handlers = {
|
|
|
686
799
|
// Destructuring assignment: [a, ...r] = expr or ({x: a} = expr)
|
|
687
800
|
// Distinguishing from index assignment: destructuring patterns have exactly one payload node.
|
|
688
801
|
if (isDestructPattern(lhs) && lhs.length === 2) {
|
|
802
|
+
const scalar = scalarArrayDestruct(lhs, rhs)
|
|
803
|
+
if (scalar) return scalar
|
|
804
|
+
|
|
689
805
|
const normed = prep(rhs)
|
|
690
806
|
const tmp = `${T}d${ctx.func.uniq++}`
|
|
691
807
|
const decls = [['=', tmp, normed]]
|
|
@@ -708,9 +824,9 @@ const handlers = {
|
|
|
708
824
|
}
|
|
709
825
|
// Function property assignment: fn.prop = arrow → extract as top-level function fn$prop
|
|
710
826
|
if (depth === 0 && Array.isArray(lhs) && lhs[0] === '.' && typeof lhs[1] === 'string'
|
|
711
|
-
&&
|
|
827
|
+
&& hasFunc(lhs[1]) && Array.isArray(rhs) && rhs[0] === '=>') {
|
|
712
828
|
const name = `${lhs[1]}$${lhs[2]}`
|
|
713
|
-
if (defFunc(name, prep(rhs))) return
|
|
829
|
+
if (defFunc(name, prep(rhs))) return ['=', prep(lhs), name]
|
|
714
830
|
}
|
|
715
831
|
return ['=', prep(lhs), prep(rhs)]
|
|
716
832
|
},
|
|
@@ -736,16 +852,12 @@ const handlers = {
|
|
|
736
852
|
},
|
|
737
853
|
'throw'(expr) { return ['throw', prep(expr)] },
|
|
738
854
|
|
|
739
|
-
// Template literal: [``, part, ...] →
|
|
740
|
-
// First node is always a string (empty if template starts with ${...}) so concat dispatches correctly.
|
|
855
|
+
// Template literal: [``, part, ...] → fused single-allocation string concat.
|
|
741
856
|
'`'(...parts) {
|
|
742
|
-
|
|
857
|
+
includeForStringValue()
|
|
743
858
|
const nodes = parts.map(p =>
|
|
744
859
|
Array.isArray(p) && p[0] == null && typeof p[1] === 'string' ? ['str', p[1]] : prep(p))
|
|
745
|
-
|
|
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])
|
|
860
|
+
return ['strcat', ...nodes]
|
|
749
861
|
},
|
|
750
862
|
|
|
751
863
|
// Tagged template: tag`a${x}b` → tag(['a','b'], x)
|
|
@@ -781,7 +893,7 @@ const handlers = {
|
|
|
781
893
|
if (hostMod && Array.isArray(specifiers) && specifiers[0] === '{}') {
|
|
782
894
|
const inner = specifiers[1]
|
|
783
895
|
if (inner != null) {
|
|
784
|
-
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)
|
|
785
897
|
const builtinItems = []
|
|
786
898
|
for (const item of items) {
|
|
787
899
|
const name = typeof item === 'string' ? item : item[1]
|
|
@@ -794,7 +906,7 @@ const handlers = {
|
|
|
794
906
|
}
|
|
795
907
|
}
|
|
796
908
|
if (builtinItems.length === 0) return null
|
|
797
|
-
if (!
|
|
909
|
+
if (!hasModule(mod)) {
|
|
798
910
|
const name = typeof builtinItems[0] === 'string' ? builtinItems[0] : builtinItems[0][1]
|
|
799
911
|
err(`'${name}' not declared in host module '${mod}'`)
|
|
800
912
|
}
|
|
@@ -805,7 +917,7 @@ const handlers = {
|
|
|
805
917
|
}
|
|
806
918
|
|
|
807
919
|
// Tier 1: Built-in module
|
|
808
|
-
if (
|
|
920
|
+
if (hasModule(mod)) {
|
|
809
921
|
includeModule(mod)
|
|
810
922
|
const bind = (name, alias) => {
|
|
811
923
|
const key = mod + '.' + name
|
|
@@ -819,7 +931,7 @@ const handlers = {
|
|
|
819
931
|
if (Array.isArray(remaining) && remaining[0] === '{}') {
|
|
820
932
|
const inner = remaining[1]
|
|
821
933
|
if (inner == null) return null
|
|
822
|
-
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)
|
|
823
935
|
for (const item of items)
|
|
824
936
|
if (typeof item === 'string') bind(item)
|
|
825
937
|
else if (Array.isArray(item) && item[0] === 'as') bind(item[1], item[2])
|
|
@@ -850,7 +962,7 @@ const handlers = {
|
|
|
850
962
|
if (Array.isArray(specifiers) && specifiers[0] === '{}') {
|
|
851
963
|
const inner = specifiers[1]
|
|
852
964
|
if (inner == null) return null
|
|
853
|
-
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)
|
|
854
966
|
for (const item of items) {
|
|
855
967
|
const name = typeof item === 'string' ? item : item[1]
|
|
856
968
|
const alias = typeof item === 'string' ? item : item[2]
|
|
@@ -867,7 +979,7 @@ const handlers = {
|
|
|
867
979
|
if (Array.isArray(specifiers) && specifiers[0] === '{}') {
|
|
868
980
|
const inner = specifiers[1]
|
|
869
981
|
if (inner == null) return null
|
|
870
|
-
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)
|
|
871
983
|
for (const item of items) {
|
|
872
984
|
const name = typeof item === 'string' ? item : item[1]
|
|
873
985
|
const alias = typeof item === 'string' ? item : item[2]
|
|
@@ -909,6 +1021,34 @@ const handlers = {
|
|
|
909
1021
|
for (const i of decl.slice(1))
|
|
910
1022
|
if (Array.isArray(i) && i[0] === '=' && typeof i[1] === 'string')
|
|
911
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
|
+
}
|
|
912
1052
|
// export { name1, name2 as alias } → register named exports
|
|
913
1053
|
if (Array.isArray(decl) && decl[0] === '{}') {
|
|
914
1054
|
const inner = decl[1]
|
|
@@ -930,7 +1070,7 @@ const handlers = {
|
|
|
930
1070
|
if (Array.isArray(decl) && decl[0] === 'default') {
|
|
931
1071
|
const val = decl[1]
|
|
932
1072
|
// export default name → export existing name as 'default'
|
|
933
|
-
if (typeof val === 'string' && (
|
|
1073
|
+
if (typeof val === 'string' && (hasFunc(val) || ctx.scope.globals.has(val))) {
|
|
934
1074
|
ctx.func.exports['default'] = val // alias
|
|
935
1075
|
return null
|
|
936
1076
|
}
|
|
@@ -949,13 +1089,14 @@ const handlers = {
|
|
|
949
1089
|
|
|
950
1090
|
// Arrow: don't prep params. Track depth for nested function detection.
|
|
951
1091
|
'=>': (params, body) => {
|
|
952
|
-
if (depth > 0) {
|
|
1092
|
+
if (depth > 0) { includeForCallableValue() }
|
|
953
1093
|
const raw = extractParams(params)
|
|
954
1094
|
const fnScope = new Map()
|
|
955
1095
|
for (const n of collectParamNames(raw)) fnScope.set(n, n)
|
|
956
1096
|
|
|
957
1097
|
depth++
|
|
958
1098
|
scopes.push(fnScope)
|
|
1099
|
+
funcLocalNames.push(new Set(collectParamNames(raw)))
|
|
959
1100
|
|
|
960
1101
|
const nextParams = []
|
|
961
1102
|
const bodyPrefix = []
|
|
@@ -988,6 +1129,7 @@ const handlers = {
|
|
|
988
1129
|
const inner = nextParams.length === 0 ? null : nextParams.length === 1 ? nextParams[0] : [',', ...nextParams]
|
|
989
1130
|
const result = ['=>', Array.isArray(params) && params[0] === '()' ? ['()', inner] : inner, preparedBody]
|
|
990
1131
|
scopes.pop()
|
|
1132
|
+
funcLocalNames.pop()
|
|
991
1133
|
depth--
|
|
992
1134
|
return result
|
|
993
1135
|
},
|
|
@@ -1019,7 +1161,9 @@ const handlers = {
|
|
|
1019
1161
|
},
|
|
1020
1162
|
// Boolean literals NaN-box as f64 — typeof at runtime returns 'number'. Fold here so the JS-spec value survives.
|
|
1021
1163
|
'typeof'(a) {
|
|
1022
|
-
if (Array.isArray(a) && a[0] == null && typeof a[1] === 'boolean') {
|
|
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] }
|
|
1023
1167
|
return ['typeof', prep(a)]
|
|
1024
1168
|
},
|
|
1025
1169
|
|
|
@@ -1028,7 +1172,7 @@ const handlers = {
|
|
|
1028
1172
|
if (b === undefined) {
|
|
1029
1173
|
const na = prep(a)
|
|
1030
1174
|
if (isLit(na) && typeof na[1] === 'number') return na
|
|
1031
|
-
|
|
1175
|
+
includeForNumericCoercion()
|
|
1032
1176
|
return ['u+', na]
|
|
1033
1177
|
}
|
|
1034
1178
|
return ['+', prep(a), prep(b)]
|
|
@@ -1060,36 +1204,43 @@ const handlers = {
|
|
|
1060
1204
|
return ['//', pattern, flags]
|
|
1061
1205
|
},
|
|
1062
1206
|
|
|
1063
|
-
|
|
1064
|
-
'**'(a, b) { includeModule('math'); return ['**', prep(a), prep(b)] },
|
|
1207
|
+
'**'(a, b) { return ['**', prep(a), prep(b)] },
|
|
1065
1208
|
|
|
1066
1209
|
// Function call or grouping parens
|
|
1067
1210
|
'()'(callee, ...args) {
|
|
1068
1211
|
// Grouping: (expr) → ['()', expr] with no args. Call: f() → ['()', 'f', null] with null arg.
|
|
1069
1212
|
if (args.length === 0) return prep(callee)
|
|
1070
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
|
+
|
|
1071
1222
|
const hasRealArgs = args.some(a => a != null)
|
|
1072
1223
|
|
|
1073
1224
|
if (typeof callee === 'string') {
|
|
1074
1225
|
if (PROHIBITED[callee]) err(PROHIBITED[callee])
|
|
1075
1226
|
if (CTORS.includes(callee)) return handlers['new'](['()', callee, ...args])
|
|
1076
1227
|
|
|
1077
|
-
|
|
1078
|
-
if (mods) {
|
|
1079
|
-
includeMods(...mods)
|
|
1228
|
+
if (includeForNamedCall(callee)) {
|
|
1080
1229
|
if (callee === 'BigInt64Array' || callee === 'BigUint64Array') {
|
|
1081
1230
|
return ['()', callee, ...args.filter(a => a != null).map(prep)]
|
|
1082
1231
|
}
|
|
1083
1232
|
}
|
|
1084
1233
|
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
|
1238
|
+
else if (resolved && hasFunc(resolved)) callee = resolved
|
|
1088
1239
|
else if (resolved && !resolved.includes('.')) {
|
|
1089
|
-
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)
|
|
1090
1241
|
}
|
|
1091
1242
|
else if (depth > 0 && !resolved && !ctx.func.exports[callee] && !ctx.module.imports.some(i => i[3]?.[1] === `$${callee}`)) {
|
|
1092
|
-
|
|
1243
|
+
includeForCallableValue()
|
|
1093
1244
|
}
|
|
1094
1245
|
} else if (Array.isArray(callee) && callee[0] === '.') {
|
|
1095
1246
|
const [, obj, prop] = callee
|
|
@@ -1099,29 +1250,37 @@ const handlers = {
|
|
|
1099
1250
|
const alias = `${obj}$${prop}`
|
|
1100
1251
|
addHostImport(obj, prop, alias, spec)
|
|
1101
1252
|
callee = alias
|
|
1102
|
-
} else if (key &&
|
|
1103
|
-
includeMods(...CALL_MODULES[key])
|
|
1253
|
+
} else if (key && includeForNamedCall(key)) {
|
|
1104
1254
|
callee = key
|
|
1105
|
-
} else if (
|
|
1106
|
-
includeMods(...GENERIC_METHOD_MODULES[prop])
|
|
1255
|
+
} else if (includeForGenericMethod(prop)) {
|
|
1107
1256
|
callee = prep(callee)
|
|
1108
1257
|
} else {
|
|
1109
1258
|
const mod = ctx.scope.chain[obj]
|
|
1110
|
-
if (typeof obj === 'string' && mod && !mod.includes('.') &&
|
|
1259
|
+
if (typeof obj === 'string' && mod && !mod.includes('.') && hasModule(mod)) {
|
|
1111
1260
|
callee = (includeModule(mod), mod + '.' + prop)
|
|
1112
1261
|
} else {
|
|
1113
1262
|
callee = prep(callee)
|
|
1114
1263
|
}
|
|
1115
1264
|
}
|
|
1116
1265
|
} else {
|
|
1117
|
-
|
|
1266
|
+
includeForCallableValue()
|
|
1118
1267
|
callee = prep(callee)
|
|
1119
1268
|
}
|
|
1120
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
|
+
}
|
|
1121
1280
|
const preppedArgs = args.filter(a => a != null).map(prep)
|
|
1122
1281
|
for (const a of preppedArgs) {
|
|
1123
|
-
if (typeof a === 'string' &&
|
|
1124
|
-
|
|
1282
|
+
if (typeof a === 'string' && hasFunc(a)) {
|
|
1283
|
+
includeForCallableValue(); break
|
|
1125
1284
|
}
|
|
1126
1285
|
}
|
|
1127
1286
|
const result = ['()', callee, ...preppedArgs]
|
|
@@ -1135,13 +1294,13 @@ const handlers = {
|
|
|
1135
1294
|
'[]'(...args) {
|
|
1136
1295
|
if (args.length === 1) {
|
|
1137
1296
|
const inner = args[0]
|
|
1138
|
-
|
|
1297
|
+
includeForArrayLiteral()
|
|
1139
1298
|
if (inner == null) return ['[']
|
|
1140
1299
|
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
1300
|
return ['[', prep(inner)]
|
|
1142
1301
|
}
|
|
1143
1302
|
if (typeof args[0] === 'string' && ctx.module.namespaces?.[args[0]]) {
|
|
1144
|
-
|
|
1303
|
+
includeForStringOnly()
|
|
1145
1304
|
const key = prep(args[1])
|
|
1146
1305
|
const exports = [...ctx.module.namespaces[args[0]].entries()]
|
|
1147
1306
|
let fallback = [, undefined]
|
|
@@ -1151,7 +1310,7 @@ const handlers = {
|
|
|
1151
1310
|
}
|
|
1152
1311
|
return fallback
|
|
1153
1312
|
}
|
|
1154
|
-
|
|
1313
|
+
includeForArrayAccess()
|
|
1155
1314
|
return ['[]', prep(args[0]), prep(args[1])]
|
|
1156
1315
|
},
|
|
1157
1316
|
|
|
@@ -1174,17 +1333,40 @@ const handlers = {
|
|
|
1174
1333
|
return result
|
|
1175
1334
|
}
|
|
1176
1335
|
|
|
1177
|
-
|
|
1336
|
+
includeForObjectLiteral()
|
|
1178
1337
|
if (inner == null) return ['{}']
|
|
1179
1338
|
// Process properties: shorthand 'x' → [':', 'x', 'x'], or [':', key, val] → prep val only
|
|
1180
1339
|
const prop = p => {
|
|
1181
1340
|
if (typeof p === 'string') return [':', p, prep(p)]
|
|
1182
|
-
if (Array.isArray(p) && p[0] === ':')
|
|
1341
|
+
if (Array.isArray(p) && p[0] === ':') {
|
|
1342
|
+
const key = typeof p[1] === 'string' ? p[1] : staticPropertyKey(p[1])
|
|
1343
|
+
if (key == null) err('computed property name not supported for fixed-shape object: use a compile-time string/number key')
|
|
1344
|
+
return [':', key, prep(p[2])]
|
|
1345
|
+
}
|
|
1183
1346
|
return prep(p)
|
|
1184
1347
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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]
|
|
1188
1370
|
// Register schema so property access works for function params (duck typing)
|
|
1189
1371
|
const props = result.slice(1).filter(p => Array.isArray(p) && p[0] === ':').map(p => p[1])
|
|
1190
1372
|
if (props.length && ctx.schema.register) ctx.schema.register(props)
|
|
@@ -1198,15 +1380,19 @@ const handlers = {
|
|
|
1198
1380
|
if (Array.isArray(head) && head[0] === ';') {
|
|
1199
1381
|
let [, init, cond, step] = head
|
|
1200
1382
|
// Hoist .length / .size / .byteLength from for-condition:
|
|
1201
|
-
// `i < arr.length` → `let __len = arr.length; ... i < __len`
|
|
1202
|
-
//
|
|
1203
|
-
//
|
|
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.
|
|
1204
1390
|
if (cond && Array.isArray(cond) && (cond[0] === '<' || cond[0] === '<=' || cond[0] === '>' || cond[0] === '>=')) {
|
|
1205
1391
|
const lenExpr = cond[0] === '<' || cond[0] === '<=' ? cond[2] : cond[1]
|
|
1206
1392
|
if (Array.isArray(lenExpr) && lenExpr[0] === '.' &&
|
|
1207
1393
|
(lenExpr[2] === 'length' || lenExpr[2] === 'size' || lenExpr[2] === 'byteLength')) {
|
|
1208
1394
|
const lenVar = `${T}len${ctx.func.uniq++}`
|
|
1209
|
-
const lenDecl = ['let', ['=', lenVar, lenExpr]]
|
|
1395
|
+
const lenDecl = ['let', ['=', lenVar, ['|', lenExpr, [, 0]]]]
|
|
1210
1396
|
init = init ? [';', init, lenDecl] : lenDecl
|
|
1211
1397
|
if (cond[0] === '<' || cond[0] === '<=') cond = [cond[0], cond[1], lenVar]
|
|
1212
1398
|
else cond = [cond[0], lenVar, cond[2]]
|
|
@@ -1223,9 +1409,12 @@ const handlers = {
|
|
|
1223
1409
|
const lenVar = `${T}len${ctx.func.uniq++}`
|
|
1224
1410
|
const trivial = typeof src === 'string'
|
|
1225
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]]
|
|
1226
1415
|
const decls = trivial
|
|
1227
|
-
? ['let', ['=', idx, [, 0]], ['=', lenVar,
|
|
1228
|
-
: ['let', ['=', arrVar, src], ['=', idx, [, 0]], ['=', lenVar,
|
|
1416
|
+
? ['let', ['=', idx, [, 0]], ['=', lenVar, lenE]]
|
|
1417
|
+
: ['let', ['=', arrVar, src], ['=', idx, [, 0]], ['=', lenVar, lenE]]
|
|
1229
1418
|
const cond = ['<', idx, lenVar]
|
|
1230
1419
|
const step = ['++', idx]
|
|
1231
1420
|
const inner = [';', ['let', ['=', varName, ['[]', arrVar, idx]]], body]
|
|
@@ -1233,14 +1422,14 @@ const handlers = {
|
|
|
1233
1422
|
} else if (Array.isArray(head) && head[0] === 'in') {
|
|
1234
1423
|
// for (let k in obj) → unroll at compile time when schema known, else HASH runtime iteration
|
|
1235
1424
|
const [, decl, src] = head
|
|
1236
|
-
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
|
|
1237
1426
|
const srcName = typeof src === 'string' ? (ctx.scope.chain[src] || src) : null
|
|
1238
|
-
const sid = typeof srcName === 'string'
|
|
1427
|
+
const sid = typeof srcName === 'string' ? ctx.schema.vars.get(srcName) : null
|
|
1239
1428
|
if (sid != null) {
|
|
1240
1429
|
// Known schema → compile-time unrolling with string keys
|
|
1241
1430
|
const keys = ctx.schema.list[sid]
|
|
1242
1431
|
if (!keys || !keys.length) { scopes.pop(); return null }
|
|
1243
|
-
|
|
1432
|
+
includeForKnownKeyIteration()
|
|
1244
1433
|
const stmts = []
|
|
1245
1434
|
for (let i = 0; i < keys.length; i++) {
|
|
1246
1435
|
stmts.push(i === 0
|
|
@@ -1251,7 +1440,7 @@ const handlers = {
|
|
|
1251
1440
|
r = prep([';', ...stmts])
|
|
1252
1441
|
} else {
|
|
1253
1442
|
// Dynamic object → HASH runtime iteration
|
|
1254
|
-
|
|
1443
|
+
includeForRuntimeKeyIteration()
|
|
1255
1444
|
r = ['for-in', varName, prep(src), prep(body)]
|
|
1256
1445
|
}
|
|
1257
1446
|
} else {
|
|
@@ -1263,24 +1452,22 @@ const handlers = {
|
|
|
1263
1452
|
|
|
1264
1453
|
// Property access - resolve namespaces or object/array properties
|
|
1265
1454
|
'.'(obj, prop) {
|
|
1455
|
+
if (prop === 'caller' || prop === 'callee') err('`.caller` and `.callee` are prohibited: deprecated stack introspection')
|
|
1456
|
+
if (prop === 'url' && isImportMeta(obj)) return staticString(importMetaUrl())
|
|
1266
1457
|
const mod = ctx.scope.chain[obj]
|
|
1267
1458
|
// 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('.') &&
|
|
1269
|
-
|
|
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
|
+
}
|
|
1270
1465
|
// Source module namespace: import * as X → X.prop resolved to mangled name
|
|
1271
1466
|
if (typeof obj === 'string' && ctx.module.namespaces?.[obj]) {
|
|
1272
1467
|
const mangled = ctx.module.namespaces[obj].get(prop)
|
|
1273
1468
|
if (mangled) return mangled
|
|
1274
1469
|
}
|
|
1275
|
-
|
|
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')
|
|
1470
|
+
includeForProperty(prop)
|
|
1284
1471
|
return ['.', prep(obj), prop]
|
|
1285
1472
|
},
|
|
1286
1473
|
|
|
@@ -1292,20 +1479,21 @@ const handlers = {
|
|
|
1292
1479
|
if (ctorArgs.length === 1 && Array.isArray(ctorArgs[0]) && ctorArgs[0][0] === ',')
|
|
1293
1480
|
ctorArgs = ctorArgs[0].slice(1)
|
|
1294
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
|
+
|
|
1295
1491
|
// Wrap multi-arg ctor arg lists back into a single comma-group — the '()' op
|
|
1296
1492
|
// expects callArgs as a single element (possibly comma-grouped).
|
|
1297
1493
|
const wrapArgs = (args) => args.length === 0 ? [null]
|
|
1298
1494
|
: args.length === 1 ? [prep(args[0])]
|
|
1299
1495
|
: [[',', ...args.map(prep)]]
|
|
1300
|
-
|
|
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')
|
|
1496
|
+
if (includeForRuntimeCtor(name)) {
|
|
1309
1497
|
return ['()', `new.${name}`, ...wrapArgs(ctorArgs)]
|
|
1310
1498
|
}
|
|
1311
1499
|
|
|
@@ -1317,37 +1505,6 @@ const handlers = {
|
|
|
1317
1505
|
}
|
|
1318
1506
|
}
|
|
1319
1507
|
|
|
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
1508
|
/** Merge source schemas into target via Object.assign for compile-time schema inference. */
|
|
1352
1509
|
function inferAssignSchema(callNode) {
|
|
1353
1510
|
// After prep, args may be comma-grouped: ['()', callee, [',', target, s1, s2]]
|
|
@@ -1421,6 +1578,7 @@ function defFunc(name, node) {
|
|
|
1421
1578
|
const funcInfo = { name, body, exported, sig, ...(hasDefaults && { defaults }) }
|
|
1422
1579
|
if (hasRest.length) funcInfo.rest = hasRest[0] // track rest param name
|
|
1423
1580
|
ctx.func.list.push(funcInfo)
|
|
1581
|
+
ctx.func.names.add(name)
|
|
1424
1582
|
return true
|
|
1425
1583
|
}
|
|
1426
1584
|
|
|
@@ -1493,6 +1651,19 @@ function prepareModule(specifier, source) {
|
|
|
1493
1651
|
|
|
1494
1652
|
// Collect exports: rename exported funcs with prefix
|
|
1495
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
|
+
}
|
|
1496
1667
|
for (const name of Object.keys(ctx.func.exports)) {
|
|
1497
1668
|
const val = ctx.func.exports[name]
|
|
1498
1669
|
// Default export alias: export default existingName → map 'default' to that name's mangled form
|
|
@@ -1500,19 +1671,29 @@ function prepareModule(specifier, source) {
|
|
|
1500
1671
|
// Will resolve after all named exports are mangled
|
|
1501
1672
|
continue
|
|
1502
1673
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
if (
|
|
1514
|
-
|
|
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
|
|
1515
1695
|
}
|
|
1696
|
+
exportLocal(name, name)
|
|
1516
1697
|
}
|
|
1517
1698
|
// Resolve default export alias after named exports are mangled
|
|
1518
1699
|
if (typeof ctx.func.exports['default'] === 'string') {
|
|
@@ -1525,7 +1706,7 @@ function prepareModule(specifier, source) {
|
|
|
1525
1706
|
const mangled = `${prefix}$${alias}`
|
|
1526
1707
|
moduleExports.set('default', mangled)
|
|
1527
1708
|
const func = ctx.func.list.find(f => f.name === alias)
|
|
1528
|
-
if (func) func
|
|
1709
|
+
if (func) renameFunc(func, mangled)
|
|
1529
1710
|
if (ctx.scope.globals.has(alias)) {
|
|
1530
1711
|
const wat = ctx.scope.globals.get(alias).replace(`$${alias}`, `$${mangled}`)
|
|
1531
1712
|
ctx.scope.globals.delete(alias)
|
|
@@ -1544,7 +1725,7 @@ function prepareModule(specifier, source) {
|
|
|
1544
1725
|
if (func.name.includes('__') && func.name.includes('$')) continue
|
|
1545
1726
|
const mangled = `${prefix}$${func.name}`
|
|
1546
1727
|
moduleExports.set(func.name, mangled)
|
|
1547
|
-
func
|
|
1728
|
+
renameFunc(func, mangled)
|
|
1548
1729
|
}
|
|
1549
1730
|
|
|
1550
1731
|
// Add mangled non-exported globals to moduleExports for walk renaming
|
|
@@ -1584,6 +1765,7 @@ function prepareModule(specifier, source) {
|
|
|
1584
1765
|
if (moduleInit) {
|
|
1585
1766
|
if (!ctx.module.moduleInits) ctx.module.moduleInits = []
|
|
1586
1767
|
ctx.module.moduleInits.push(moduleInit)
|
|
1768
|
+
recordModuleInitFacts(moduleInit)
|
|
1587
1769
|
}
|
|
1588
1770
|
|
|
1589
1771
|
// Restore caller state
|