jz 0.2.0 → 0.3.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 +8 -28
- package/cli.js +19 -2
- package/index.js +14 -4
- package/module/array.js +66 -9
- package/module/collection.js +68 -35
- package/module/core.js +65 -19
- package/module/date.js +104 -7
- package/module/function.js +2 -1
- package/module/json.js +66 -8
- package/module/number.js +127 -5
- package/module/object.js +88 -1
- package/module/string.js +8 -2
- package/module/typedarray.js +7 -1
- package/package.json +3 -3
- package/src/analyze.js +18 -6
- package/src/assemble.js +544 -0
- package/src/ast.js +160 -0
- package/src/auto-config.js +120 -0
- package/src/autoload.js +6 -3
- package/src/compile.js +22 -620
- package/src/ctx.js +33 -5
- package/src/emit.js +97 -165
- package/src/host.js +12 -0
- package/src/ir.js +19 -0
- package/src/jzify.js +306 -7
- package/src/narrow.js +2 -1
- package/src/optimize.js +298 -24
- package/src/plan.js +258 -37
- package/src/prepare.js +23 -2
- package/src/vectorize.js +3 -12
package/src/jzify.js
CHANGED
|
@@ -26,11 +26,12 @@ export default function jzify(ast) {
|
|
|
26
26
|
swIdx = 0
|
|
27
27
|
argsIdx = 0
|
|
28
28
|
doIdx = 0
|
|
29
|
+
classIdx = 0
|
|
29
30
|
// Hoist module-level vars: any `var x` inside nested blocks bubbles up.
|
|
30
31
|
const names = new Set()
|
|
31
32
|
ast = hoistVars(ast, names)
|
|
32
33
|
if (names.size) ast = prependDecls(ast, names)
|
|
33
|
-
return transformScope(ast)
|
|
34
|
+
return foldStaticExportHelpers(transformScope(ast))
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/**
|
|
@@ -70,12 +71,20 @@ function hoistVars(node, names) {
|
|
|
70
71
|
}
|
|
71
72
|
return [op, lhs, hoistVars(node[2], names)]
|
|
72
73
|
}
|
|
74
|
+
if (op === '=' && Array.isArray(node[1]) && node[1][0] === 'var' && typeof node[1][1] === 'string' && node[1].length === 2) {
|
|
75
|
+
names.add(node[1][1])
|
|
76
|
+
return ['=', node[1][1], hoistVars(node[2], names)]
|
|
77
|
+
}
|
|
73
78
|
// For-head `;` is positional (init; cond; update), not a statement sequence.
|
|
74
79
|
// Recurse into each slot but never filter nulls — empty slots are valid.
|
|
75
80
|
if (op === 'for') {
|
|
76
81
|
const head = node[1]
|
|
77
82
|
let h2
|
|
78
|
-
if (Array.isArray(head) && head[0] === '
|
|
83
|
+
if (Array.isArray(head) && head[0] === 'var' && Array.isArray(head[1]) &&
|
|
84
|
+
(head[1][0] === 'in' || head[1][0] === 'of') && typeof head[1][1] === 'string') {
|
|
85
|
+
names.add(head[1][1])
|
|
86
|
+
h2 = [head[1][0], head[1][1], hoistVars(head[1][2], names)]
|
|
87
|
+
} else if (Array.isArray(head) && head[0] === ';') {
|
|
79
88
|
h2 = [';']
|
|
80
89
|
for (let i = 1; i < head.length; i++) h2.push(hoistVars(head[i], names))
|
|
81
90
|
} else {
|
|
@@ -144,16 +153,43 @@ function transformScope(node) {
|
|
|
144
153
|
|
|
145
154
|
// Single named function-statement at scope position: hoist as const arrow
|
|
146
155
|
if (op === 'function' && args[0]) return hoistFnDecl(...args)
|
|
156
|
+
// Single statement-form class declaration: bind the factory (no hoisting — classes are TDZ)
|
|
157
|
+
if (op === 'class' && args[0]) return ['let', ['=', args[0], lowerClass(...args)]]
|
|
147
158
|
|
|
148
159
|
// Statement sequence: collect hoisted functions
|
|
149
160
|
if (op === ';') {
|
|
150
161
|
const hoisted = [], rest = []
|
|
151
|
-
for (
|
|
162
|
+
for (let i = 0; i < args.length; i++) {
|
|
163
|
+
const stmt = args[i]
|
|
164
|
+
// Workaround for subscript parser ASI bug: multiline named IIFE
|
|
165
|
+
// `(function name(){...})();` is parsed as two statements when there are
|
|
166
|
+
// newlines inside the function body. Reconstruct the single-statement IIFE
|
|
167
|
+
// so the () handler can desugar it correctly.
|
|
168
|
+
if (Array.isArray(stmt) && stmt[0] === '()' &&
|
|
169
|
+
Array.isArray(stmt[1]) && stmt[1][0] === 'function' && stmt[1][1] &&
|
|
170
|
+
i + 1 < args.length && Array.isArray(args[i + 1]) && args[i + 1][0] === '()') {
|
|
171
|
+
const merged = ['()', ['()', stmt[1]], args[i + 1][1] ?? null]
|
|
172
|
+
const t = transform(merged)
|
|
173
|
+
if (t != null) {
|
|
174
|
+
if (Array.isArray(t) && t[0] === ';') {
|
|
175
|
+
for (const s of t.slice(1)) { if (s != null) rest.push(s) }
|
|
176
|
+
} else {
|
|
177
|
+
rest.push(t)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
i++
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
152
183
|
// Statement-form named function declaration: hoist directly (skip expression handler)
|
|
153
184
|
if (Array.isArray(stmt) && stmt[0] === 'function' && stmt[1]) {
|
|
154
185
|
hoisted.push(hoistFnDecl(stmt[1], stmt[2], stmt[3]))
|
|
155
186
|
continue
|
|
156
187
|
}
|
|
188
|
+
// Statement-form class declaration: bind the factory in place (not hoisted — TDZ)
|
|
189
|
+
if (Array.isArray(stmt) && stmt[0] === 'class' && stmt[1]) {
|
|
190
|
+
rest.push(['let', ['=', stmt[1], lowerClass(stmt[1], stmt[2], stmt[3])]])
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
157
193
|
const t = transform(stmt)
|
|
158
194
|
if (t == null) continue
|
|
159
195
|
// Hoist function declarations to top of scope
|
|
@@ -236,8 +272,14 @@ function paramList(params) {
|
|
|
236
272
|
return [params]
|
|
237
273
|
}
|
|
238
274
|
|
|
275
|
+
// Destructuring pattern as a parameter — `[a,b]` / `{a,b}` (optionally with a
|
|
276
|
+
// default). Plain `=` defaults and `...rest` are handled natively by emit, so
|
|
277
|
+
// they don't by themselves force lowering.
|
|
278
|
+
const isDestructurePat = p => Array.isArray(p) && (p[0] === '[]' || p[0] === '{}' || (p[0] === '=' && isDestructurePat(p[1])))
|
|
279
|
+
|
|
239
280
|
function lowerArguments(params, body) {
|
|
240
|
-
|
|
281
|
+
const paramsNeedLowering = paramList(params).some(isDestructurePat)
|
|
282
|
+
if (!paramsNeedLowering && !usesArguments(params) && !usesArguments(body)) return [params, body]
|
|
241
283
|
const name = `\uE001arg${argsIdx++}`
|
|
242
284
|
const decls = []
|
|
243
285
|
for (const [idx, param] of paramList(params).entries()) {
|
|
@@ -252,11 +294,119 @@ function lowerArguments(params, body) {
|
|
|
252
294
|
decls.push(['=', param, ['[]', name, [null, idx]]])
|
|
253
295
|
}
|
|
254
296
|
const renamed = renameArguments(body, name)
|
|
255
|
-
return [['()', ['...', name]], decls.length ? ['
|
|
297
|
+
return [['()', ['...', name]], decls.length ? prependParamDecls(['let', ...decls], renamed) : renamed]
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function prependParamDecls(decl, body) {
|
|
301
|
+
if (Array.isArray(body) && body[0] === '{}') {
|
|
302
|
+
const inner = body[1]
|
|
303
|
+
if (Array.isArray(inner) && inner[0] === ';') return ['{}', [';', decl, ...inner.slice(1)]]
|
|
304
|
+
if (inner == null) return ['{}', decl]
|
|
305
|
+
return ['{}', [';', decl, inner]]
|
|
306
|
+
}
|
|
307
|
+
if (Array.isArray(body) && (body[0] === ';' || body[0] === 'return')) return [';', decl, body]
|
|
308
|
+
return ['{}', [';', decl, ['return', body]]]
|
|
256
309
|
}
|
|
257
310
|
|
|
258
311
|
const arrowParams = params => Array.isArray(params) && params[0] === '()' ? params : ['()', params]
|
|
259
312
|
|
|
313
|
+
// === class lowering ===
|
|
314
|
+
//
|
|
315
|
+
// A class is lowered to a factory arrow. Instance state is a plain object;
|
|
316
|
+
// methods are per-instance arrows capturing it (so `obj.m()` keeps working
|
|
317
|
+
// without a separate `this` argument); `this` is renamed to that object;
|
|
318
|
+
// `new C(a)` is already turned into `C(a)` by the `new` handler.
|
|
319
|
+
//
|
|
320
|
+
// class Point { x = 0; y; constructor(a,b){ this.x = a; this.y = b }
|
|
321
|
+
// dist(){ return Math.hypot(this.x, this.y) } }
|
|
322
|
+
// →
|
|
323
|
+
// let Point = (a, b) => {
|
|
324
|
+
// let selfN = { x: undefined, y: undefined,
|
|
325
|
+
// dist: () => Math.hypot(selfN.x, selfN.y) }
|
|
326
|
+
// selfN.x = 0 // field initializers, in declaration order
|
|
327
|
+
// selfN.x = a // then the constructor body
|
|
328
|
+
// selfN.y = b
|
|
329
|
+
// return selfN
|
|
330
|
+
// }
|
|
331
|
+
//
|
|
332
|
+
// Out of scope for now (rejected with a clear message): `extends`/`super`,
|
|
333
|
+
// `static` members, getters/setters, computed/private-via-`#` member names are
|
|
334
|
+
// kept as the literal key string `#name` (jz allows it).
|
|
335
|
+
let classIdx = 0
|
|
336
|
+
|
|
337
|
+
const classBodyItems = (body) =>
|
|
338
|
+
body == null ? [] : Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
|
|
339
|
+
|
|
340
|
+
// Rename `this` → `to`, not crossing into a nested `function`/`class` (those
|
|
341
|
+
// rebind `this`); arrows inherit `this`, so they are crossed. Property *names*
|
|
342
|
+
// (`obj.this`, `{this: …}` value-side only) are left alone.
|
|
343
|
+
function renameThis(node, to) {
|
|
344
|
+
if (node === 'this') return to
|
|
345
|
+
if (!Array.isArray(node)) return node
|
|
346
|
+
if (node[0] === 'function' || node[0] === 'class') return node
|
|
347
|
+
if (node[0] === '.' || node[0] === '?.') return [node[0], renameThis(node[1], to), node[2]]
|
|
348
|
+
if (node[0] === ':') return [node[0], node[1], renameThis(node[2], to)]
|
|
349
|
+
return node.map(n => renameThis(n, to))
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function jzifyError(msg) { throw new Error(`jzify: ${msg}`) }
|
|
353
|
+
|
|
354
|
+
function lowerClass(name, heritage, body) {
|
|
355
|
+
if (heritage != null) jzifyError('`class … extends …` is not supported yet — flatten the hierarchy or compose explicitly')
|
|
356
|
+
let ctorParams = null, ctorBody = null
|
|
357
|
+
const methods = [], fields = []
|
|
358
|
+
for (const it of classBodyItems(body)) {
|
|
359
|
+
if (typeof it === 'string') { fields.push([it, null]); continue } // bare `x;`
|
|
360
|
+
if (!Array.isArray(it)) continue
|
|
361
|
+
if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === '=>') {
|
|
362
|
+
if (typeof it[1] !== 'string') jzifyError('computed class member names are not supported')
|
|
363
|
+
if (it[1] === 'constructor') { ctorParams = it[2][1]; ctorBody = it[2][2] }
|
|
364
|
+
else methods.push([it[1], it[2][1], it[2][2]])
|
|
365
|
+
continue
|
|
366
|
+
}
|
|
367
|
+
if (it[0] === '=') {
|
|
368
|
+
const lhs = it[1]
|
|
369
|
+
if (Array.isArray(lhs) && lhs[0] === 'static') jzifyError('`static` class members are not supported yet')
|
|
370
|
+
if (typeof lhs !== 'string') jzifyError('computed/destructured class fields are not supported')
|
|
371
|
+
fields.push([lhs, it[2]])
|
|
372
|
+
continue
|
|
373
|
+
}
|
|
374
|
+
if (it[0] === 'get' || it[0] === 'set') jzifyError('class getters/setters are not supported — jz objects have no accessors')
|
|
375
|
+
if (it[0] === 'static') jzifyError('`static` class members are not supported yet')
|
|
376
|
+
jzifyError(`unsupported class member ${JSON.stringify(it).slice(0, 60)}`)
|
|
377
|
+
}
|
|
378
|
+
const self = `self${classIdx++}`
|
|
379
|
+
const UNDEF = [] // jessie's node for `undefined`
|
|
380
|
+
// A class member body from jessie is a bare statement / `;`-sequence — wrap it
|
|
381
|
+
// in a `{}` block so the `=>` handler treats it as a function body, not an
|
|
382
|
+
// expression (an unwrapped `;`-seq arrow body produces malformed IR).
|
|
383
|
+
const block = b => Array.isArray(b) && b[0] === '{}' ? b : ['{}', b]
|
|
384
|
+
const usesThis = n => n === 'this' || (Array.isArray(n) && n[0] !== 'function' && n[0] !== 'class' && n.some(usesThis))
|
|
385
|
+
// Object literal: every declared field (its initializer inline when it doesn't
|
|
386
|
+
// touch `this`, else `undefined` and assigned below), every method as its
|
|
387
|
+
// self-capturing arrow. Declaring all fields up front fixes the object shape.
|
|
388
|
+
const litProps = [], deferred = []
|
|
389
|
+
for (const [fname, init] of fields) {
|
|
390
|
+
if (init != null && !usesThis(init)) litProps.push([':', fname, transform(init)])
|
|
391
|
+
else { litProps.push([':', fname, UNDEF]); if (init != null) deferred.push([fname, init]) }
|
|
392
|
+
}
|
|
393
|
+
for (const [mname, mparams, mbody] of methods)
|
|
394
|
+
litProps.push([':', mname, transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
|
|
395
|
+
const lit = ['{}', litProps.length === 0 ? null : litProps.length === 1 ? litProps[0] : [',', ...litProps]]
|
|
396
|
+
const stmts = [['let', ['=', self, lit]]]
|
|
397
|
+
// `this`-dependent field initializers run, in declaration order, before the ctor.
|
|
398
|
+
for (const [fname, init] of deferred)
|
|
399
|
+
stmts.push(['=', ['.', self, fname], transform(renameThis(init, self))])
|
|
400
|
+
if (ctorBody != null) {
|
|
401
|
+
let cb = transform(renameThis(ctorBody, self))
|
|
402
|
+
if (Array.isArray(cb) && cb[0] === '{}') cb = cb[1]
|
|
403
|
+
if (Array.isArray(cb) && cb[0] === ';') stmts.push(...cb.slice(1).filter(s => s != null))
|
|
404
|
+
else if (cb != null) stmts.push(cb)
|
|
405
|
+
}
|
|
406
|
+
stmts.push(['return', self])
|
|
407
|
+
return ['=>', arrowParams(ctorParams ?? ['()', null]), ['{}', [';', ...stmts]]]
|
|
408
|
+
}
|
|
409
|
+
|
|
260
410
|
const handlers = {
|
|
261
411
|
// Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
|
|
262
412
|
'()'(callee, ...rest) {
|
|
@@ -283,6 +433,16 @@ const handlers = {
|
|
|
283
433
|
return arrow
|
|
284
434
|
},
|
|
285
435
|
|
|
436
|
+
'=>'(params, body) {
|
|
437
|
+
const [p2, b2] = lowerArguments(params, body)
|
|
438
|
+
return ['=>', p2, transform(b2)]
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
// Class in expression position → its factory arrow. (A named class
|
|
442
|
+
// expression's own inner binding is dropped — rare; statement-form
|
|
443
|
+
// `class C {}` is handled by transformScope, which keeps the binding.)
|
|
444
|
+
'class'(name, heritage, body) { return lowerClass(name, heritage, body) },
|
|
445
|
+
|
|
286
446
|
// `var` is hoisted away before transform reaches here. If one slips through
|
|
287
447
|
// (e.g. raw subscript output without going via jzify entry/wrapArrowBody),
|
|
288
448
|
// fall back to treating it as `let`.
|
|
@@ -326,10 +486,15 @@ const handlers = {
|
|
|
326
486
|
|
|
327
487
|
// new → call (keep TypedArrays)
|
|
328
488
|
'new'(ctor, ...cargs) {
|
|
489
|
+
if (Array.isArray(ctor) && ctor[0] === '()' && Array.isArray(ctor[1]) && ctor[1][0] === '.') {
|
|
490
|
+
return ['()', ['.', transform(['new', ctor[1][1]]), ctor[1][2]], ...ctor.slice(2).map(transform)]
|
|
491
|
+
}
|
|
329
492
|
const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
|
|
330
|
-
if (typeof name === 'string' && TYPED_ARRAYS.has(name)) return ['new', transform(ctor), ...cargs.map(transform)]
|
|
493
|
+
if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array')) return ['new', transform(ctor), ...cargs.map(transform)]
|
|
331
494
|
if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
|
|
332
|
-
|
|
495
|
+
// `new C(a)` → `C(a)`; `new C` (no parens) → `C()` — a 2-element `['()', X]`
|
|
496
|
+
// is grouping parens, so a no-arg call needs the explicit `null` arg slot.
|
|
497
|
+
return ['()', transform(ctor), ...(cargs.length ? cargs.map(transform) : [null])]
|
|
333
498
|
},
|
|
334
499
|
|
|
335
500
|
// instanceof → typeof / Array.isArray (jzify allows what strict mode prohibits)
|
|
@@ -363,10 +528,17 @@ const handlers = {
|
|
|
363
528
|
if (Array.isArray(inner) && inner[0] === 'function' && inner[1]) {
|
|
364
529
|
return ['export', hoistFnDecl(inner[1], inner[2], inner[3])]
|
|
365
530
|
}
|
|
531
|
+
// `export class C {}` → `export let C = factory`; named class keeps its binding.
|
|
532
|
+
if (Array.isArray(inner) && inner[0] === 'class' && inner[1]) {
|
|
533
|
+
return ['export', ['let', ['=', inner[1], lowerClass(inner[1], inner[2], inner[3])]]]
|
|
534
|
+
}
|
|
366
535
|
if (Array.isArray(inner) && inner[0] === 'default' && Array.isArray(inner[1]) && inner[1][0] === 'function' && inner[1][1]) {
|
|
367
536
|
const decl = hoistFnDecl(inner[1][1], inner[1][2], inner[1][3])
|
|
368
537
|
return [';', decl, ['export', ['default', inner[1][1]]]]
|
|
369
538
|
}
|
|
539
|
+
if (Array.isArray(inner) && inner[0] === 'default' && Array.isArray(inner[1]) && inner[1][0] === 'class' && inner[1][1]) {
|
|
540
|
+
return [';', ['let', ['=', inner[1][1], lowerClass(inner[1][1], inner[1][2], inner[1][3])]], ['export', ['default', inner[1][1]]]]
|
|
541
|
+
}
|
|
370
542
|
return ['export', transform(inner)]
|
|
371
543
|
},
|
|
372
544
|
}
|
|
@@ -380,6 +552,133 @@ function transform(node) {
|
|
|
380
552
|
return (h && h(...args)) ?? (h ? [op, ...args.map(transform)] : [op, ...args.map(transform)])
|
|
381
553
|
}
|
|
382
554
|
|
|
555
|
+
// Esbuild emits a small ESM helper:
|
|
556
|
+
//
|
|
557
|
+
// var __defProp = Object.defineProperty;
|
|
558
|
+
// var __export = (target, all) => {
|
|
559
|
+
// for (var name in all)
|
|
560
|
+
// __defProp(target, name, { get: all[name], enumerable: true });
|
|
561
|
+
// };
|
|
562
|
+
// __export(src_exports, { default: () => value });
|
|
563
|
+
// use(src_exports.default);
|
|
564
|
+
//
|
|
565
|
+
// Full descriptor/prototype semantics are outside JZ's fixed-shape object model.
|
|
566
|
+
// This pass instead recognizes the static helper pattern and rewrites reads of
|
|
567
|
+
// the synthetic export object to the real binding.
|
|
568
|
+
function foldStaticExportHelpers(ast) {
|
|
569
|
+
const body = astSeq(ast)
|
|
570
|
+
if (!body) return ast
|
|
571
|
+
|
|
572
|
+
const defPropAliases = new Set()
|
|
573
|
+
for (const stmt of body) {
|
|
574
|
+
if (Array.isArray(stmt) && stmt[0] === '=' && typeof stmt[1] === 'string' && isObjectDefineProperty(stmt[2]))
|
|
575
|
+
defPropAliases.add(stmt[1])
|
|
576
|
+
}
|
|
577
|
+
if (!defPropAliases.size) return ast
|
|
578
|
+
|
|
579
|
+
const helperNames = new Set()
|
|
580
|
+
for (const stmt of body) {
|
|
581
|
+
if (Array.isArray(stmt) && stmt[0] === '=' && typeof stmt[1] === 'string' &&
|
|
582
|
+
Array.isArray(stmt[2]) && stmt[2][0] === '=>' && containsDefinePropertyCall(stmt[2], defPropAliases))
|
|
583
|
+
helperNames.add(stmt[1])
|
|
584
|
+
}
|
|
585
|
+
if (!helperNames.size) return ast
|
|
586
|
+
|
|
587
|
+
const rewrites = new Map()
|
|
588
|
+
const removable = new Set()
|
|
589
|
+
for (const stmt of body) {
|
|
590
|
+
const ex = staticExportCall(stmt, helperNames)
|
|
591
|
+
if (!ex) continue
|
|
592
|
+
for (const [key, value] of ex.props) rewrites.set(`${ex.target}.${key}`, value)
|
|
593
|
+
removable.add(stmt)
|
|
594
|
+
}
|
|
595
|
+
if (!rewrites.size) return ast
|
|
596
|
+
|
|
597
|
+
const rewritten = body
|
|
598
|
+
.filter(stmt => !removable.has(stmt) && !isDefPropAliasAssign(stmt, defPropAliases) && !isExportHelperAssign(stmt, helperNames))
|
|
599
|
+
.map(stmt => replaceStaticExportReads(stmt, rewrites))
|
|
600
|
+
return rewritten.length === 0 ? null : rewritten.length === 1 ? rewritten[0] : [';', ...rewritten]
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function astSeq(ast) {
|
|
604
|
+
if (!Array.isArray(ast)) return null
|
|
605
|
+
return ast[0] === ';' ? ast.slice(1).filter(Boolean) : [ast]
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function isObjectDefineProperty(node) {
|
|
609
|
+
return Array.isArray(node) && node[0] === '.' && node[1] === 'Object' && node[2] === 'defineProperty'
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function isDefPropAliasAssign(stmt, aliases) {
|
|
613
|
+
return Array.isArray(stmt) && stmt[0] === '=' && aliases.has(stmt[1]) && isObjectDefineProperty(stmt[2])
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function isExportHelperAssign(stmt, helpers) {
|
|
617
|
+
return Array.isArray(stmt) && stmt[0] === '=' && helpers.has(stmt[1])
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function containsDefinePropertyCall(node, aliases) {
|
|
621
|
+
if (!Array.isArray(node)) return false
|
|
622
|
+
if (node[0] === '()' && (aliases.has(node[1]) || isObjectDefineProperty(node[1]))) return true
|
|
623
|
+
for (let i = 1; i < node.length; i++) if (containsDefinePropertyCall(node[i], aliases)) return true
|
|
624
|
+
return false
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function staticExportCall(stmt, helpers) {
|
|
628
|
+
if (!Array.isArray(stmt) || stmt[0] !== '()' || !helpers.has(stmt[1])) return null
|
|
629
|
+
const args = callArgs(stmt.slice(2))
|
|
630
|
+
if (args.length !== 2 || typeof args[0] !== 'string') return null
|
|
631
|
+
const props = objectProps(args[1])
|
|
632
|
+
if (!props) return null
|
|
633
|
+
const out = []
|
|
634
|
+
for (const prop of props) {
|
|
635
|
+
if (!Array.isArray(prop) || prop[0] !== ':' || typeof prop[1] !== 'string') return null
|
|
636
|
+
const value = getterReturnExpr(prop[2])
|
|
637
|
+
if (!value) return null
|
|
638
|
+
out.push([prop[1], value])
|
|
639
|
+
}
|
|
640
|
+
return { target: args[0], props: out }
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function callArgs(args) {
|
|
644
|
+
if (args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',') return args[0].slice(1)
|
|
645
|
+
return args.filter(a => a != null)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function objectProps(node) {
|
|
649
|
+
if (!Array.isArray(node) || node[0] !== '{}') return null
|
|
650
|
+
const body = node[1]
|
|
651
|
+
if (body == null) return []
|
|
652
|
+
if (Array.isArray(body) && body[0] === ',') return body.slice(1)
|
|
653
|
+
return [body]
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function getterReturnExpr(node) {
|
|
657
|
+
if (!Array.isArray(node) || node[0] !== '=>') return null
|
|
658
|
+
const params = paramList(node[1])
|
|
659
|
+
if (params.length !== 0) return null
|
|
660
|
+
const body = node[2]
|
|
661
|
+
if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === 'return') return body[1][1]
|
|
662
|
+
if (Array.isArray(body) && body[0] === 'return') return body[1]
|
|
663
|
+
return body
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function replaceStaticExportReads(node, rewrites) {
|
|
667
|
+
if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
|
|
668
|
+
if ((node[0] === '.' || node[0] === '?.') && typeof node[1] === 'string' && typeof node[2] === 'string') {
|
|
669
|
+
const value = rewrites.get(`${node[1]}.${node[2]}`)
|
|
670
|
+
if (value) return cloneAst(value)
|
|
671
|
+
}
|
|
672
|
+
if (node[0] === ':') return [node[0], node[1], replaceStaticExportReads(node[2], rewrites)]
|
|
673
|
+
return node.map((part, i) => i === 0 ? part : replaceStaticExportReads(part, rewrites))
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function cloneAst(node) {
|
|
677
|
+
if (node == null || typeof node !== 'object') return node
|
|
678
|
+
if (!Array.isArray(node)) return node
|
|
679
|
+
return node.map(cloneAst)
|
|
680
|
+
}
|
|
681
|
+
|
|
383
682
|
/** Transform switch statement to if/else chain. */
|
|
384
683
|
let swIdx = 0
|
|
385
684
|
function transformSwitch(discriminant, cases) {
|
package/src/narrow.js
CHANGED
|
@@ -389,7 +389,8 @@ export default function narrowSignatures(programFacts, ast) {
|
|
|
389
389
|
// Safety:
|
|
390
390
|
// - exclude ARRAY (forwards on realloc — f64 NaN-box is a stable identity) and
|
|
391
391
|
// STRING (SSO vs heap dual encoding depends on ptr-type bits we'd drop).
|
|
392
|
-
// - exclude CLOSURE
|
|
392
|
+
// - exclude CLOSURE (aux carries funcIdx, needed for call_indirect) and TYPED
|
|
393
|
+
// (aux carries element-type, handled separately by applyTypedPointerParamAbi).
|
|
393
394
|
// - exclude params with defaults (nullish sentinel needs the f64 NaN space).
|
|
394
395
|
// - exclude rest position (array pack/unpack stays f64).
|
|
395
396
|
applyPointerParamAbi(paramReps, valueUsed)
|