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