jz 0.8.0 → 0.9.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.
Files changed (73) hide show
  1. package/README.md +56 -9
  2. package/bench/README.md +121 -50
  3. package/bench/bench.svg +27 -27
  4. package/cli.js +3 -1
  5. package/dist/interop.js +1 -1
  6. package/dist/jz.js +7541 -6178
  7. package/index.js +165 -74
  8. package/interop.js +189 -17
  9. package/jzify/arguments.js +32 -1
  10. package/jzify/async.js +409 -0
  11. package/jzify/classes.js +136 -18
  12. package/jzify/generators.js +639 -0
  13. package/jzify/hoist-vars.js +73 -1
  14. package/jzify/index.js +123 -4
  15. package/jzify/names.js +1 -0
  16. package/jzify/transform.js +239 -1
  17. package/layout.js +48 -3
  18. package/module/array.js +318 -43
  19. package/module/atomics.js +144 -0
  20. package/module/collection.js +1429 -155
  21. package/module/core.js +431 -40
  22. package/module/date.js +142 -120
  23. package/module/fs.js +144 -0
  24. package/module/function.js +6 -3
  25. package/module/index.js +4 -1
  26. package/module/json.js +270 -49
  27. package/module/math.js +1032 -145
  28. package/module/number.js +532 -163
  29. package/module/object.js +353 -95
  30. package/module/regex.js +157 -7
  31. package/module/schema.js +100 -2
  32. package/module/string.js +428 -93
  33. package/module/typedarray.js +264 -72
  34. package/module/web.js +36 -0
  35. package/package.json +5 -5
  36. package/src/abi/string.js +82 -11
  37. package/src/ast.js +11 -5
  38. package/src/autoload.js +28 -1
  39. package/src/bridge.js +7 -2
  40. package/src/compile/analyze-scans.js +22 -7
  41. package/src/compile/analyze.js +136 -30
  42. package/src/compile/dyn-closure-tables.js +277 -0
  43. package/src/compile/emit-assign.js +281 -15
  44. package/src/compile/emit.js +1055 -70
  45. package/src/compile/index.js +277 -29
  46. package/src/compile/infer.js +42 -4
  47. package/src/compile/inplace-store.js +329 -0
  48. package/src/compile/loop-recurrence.js +167 -0
  49. package/src/compile/narrow.js +555 -18
  50. package/src/compile/peel-stencil.js +5 -0
  51. package/src/compile/plan/index.js +45 -3
  52. package/src/compile/plan/inline.js +8 -1
  53. package/src/compile/plan/literals.js +39 -0
  54. package/src/compile/plan/scope.js +430 -23
  55. package/src/compile/program-facts.js +732 -38
  56. package/src/ctx.js +64 -9
  57. package/src/helper-counters.js +7 -1
  58. package/src/ir.js +113 -5
  59. package/src/kind-traits.js +66 -3
  60. package/src/kind.js +84 -12
  61. package/src/op-policy.js +7 -4
  62. package/src/optimize/index.js +1179 -704
  63. package/src/optimize/recurse.js +2 -2
  64. package/src/optimize/vectorize.js +982 -67
  65. package/src/prepare/index.js +809 -67
  66. package/src/prepare/math-kernel.js +331 -0
  67. package/src/prepare/pre-eval.js +714 -0
  68. package/src/snapshot.js +194 -0
  69. package/src/type.js +1170 -56
  70. package/src/wat/assemble.js +403 -65
  71. package/src/wat/codegen.js +74 -14
  72. package/transform.js +113 -4
  73. package/wasi.js +3 -0
@@ -5,8 +5,21 @@
5
5
 
6
6
  import { JZ_BLOCK_OPS } from '../src/ast.js'
7
7
 
8
+ // A `'[]'`-tagged node is ambiguous pre-prepare(): an array literal/destructure
9
+ // pattern (`[a, b]` → `['[]', commaSeqOrSingleElem]`, length ≤ 2 — empty `[]`
10
+ // is length 1, one element is length 2, 2+ elements comma-wrap into that one
11
+ // slot) and an element-access expression (`arr[i]` → `['[]', receiver, index]`,
12
+ // ALWAYS exactly length 3 — two separate args, never comma-wrapped) share the
13
+ // tag until prepare() splits them into `'['` (literal) vs `'[]'` (access).
14
+ // Length disambiguates: without it, `arr[i] = v` misclassifies as a
15
+ // destructuring assignment and gets walked as a pattern (`arr`/`i` treated as
16
+ // binding targets) instead of falling through to the plain-assignment path.
17
+ // Native jzify happens to reconstruct byte-identical IR either way for this
18
+ // shape (both the pattern-walk and the generic-transform fallback are no-ops
19
+ // on a receiver-name + literal-index pair) — masking it there; the self-hosted
20
+ // kernel exercises the (wrong) pattern-walk's own compiled path and diverges.
8
21
  export const isDestructurePat = p =>
9
- Array.isArray(p) && (p[0] === '[]' || p[0] === '{}' || (p[0] === '=' && isDestructurePat(p[1])))
22
+ Array.isArray(p) && ((p[0] === '[]' && p.length !== 3) || p[0] === '{}' || (p[0] === '=' && isDestructurePat(p[1])))
10
23
 
11
24
  export function hoistVars(node, names) {
12
25
  if (node == null || !Array.isArray(node)) return node
@@ -28,6 +41,10 @@ export function hoistVars(node, names) {
28
41
  if (Array.isArray(lhs) && lhs[0] === 'var' && typeof lhs[1] === 'string' && lhs.length === 2) {
29
42
  names.add(lhs[1])
30
43
  lhs = lhs[1]
44
+ } else if (Array.isArray(lhs) && lhs[0] === 'var' && lhs.length === 2 && isDestructurePat(lhs[1])) {
45
+ // `for (var [a, b] of …)` — hoist the pattern's bindings, keep an
46
+ // assignment-form head (the generic var branch DROPPED the declarator).
47
+ lhs = hoistVarPattern(lhs[1], names)
31
48
  } else {
32
49
  lhs = hoistVars(lhs, names)
33
50
  }
@@ -53,6 +70,12 @@ export function hoistVars(node, names) {
53
70
  (head[1][0] === 'in' || head[1][0] === 'of') && typeof head[1][1] === 'string') {
54
71
  names.add(head[1][1])
55
72
  h2 = [head[1][0], head[1][1], hoistVars(head[1][2], names)]
73
+ } else if (Array.isArray(head) && head[0] === 'var' && Array.isArray(head[1]) &&
74
+ (head[1][0] === 'in' || head[1][0] === 'of') && isDestructurePat(head[1][1])) {
75
+ // `for (var [a, b] of …)` / `for (var {x} in …)` — hoist the pattern's
76
+ // bindings, keep an assignment-form head (a silently DROPPED head here
77
+ // was the "'y' is not in scope" class).
78
+ h2 = [head[1][0], hoistVarPattern(head[1][1], names), hoistVars(head[1][2], names)]
56
79
  } else if (Array.isArray(head) && head[0] === ';') {
57
80
  h2 = [';']
58
81
  for (let i = 1; i < head.length; i++) h2.push(hoistVars(head[i], names))
@@ -61,6 +84,33 @@ export function hoistVars(node, names) {
61
84
  }
62
85
  return ['for', h2, hoistVars(node[2], names)]
63
86
  }
87
+ // `export var x = 1, y` — the var splits into a hoisted `let` + in-place
88
+ // assignment, so the `export` keyword can't stay on the declarator
89
+ // (`export x = 1` is not JS). Assignments stay in place; the export moves
90
+ // to a clause: `x = 1; export { x, y }` — live bindings, same semantics.
91
+ // A function-valued declarator instead keeps decl+init together in place
92
+ // (`export let f = fn`) so the compiler sees a function binding, not a
93
+ // closure-valued global — mirrors the `;`-handler's arrow-in-place rule.
94
+ if (op === 'export' && Array.isArray(node[1]) && node[1][0] === 'var') {
95
+ const isFnInit = v => Array.isArray(v) && (v[0] === '=>' || v[0] === 'function')
96
+ const stmts = [], exported = []
97
+ for (let i = 1; i < node[1].length; i++) {
98
+ const d = node[1][i]
99
+ if (typeof d === 'string') { names.add(d); exported.push(d) }
100
+ else if (Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string') {
101
+ if (isFnInit(d[2])) { stmts.push(['export', ['let', ['=', d[1], hoistVars(d[2], names)]]]); continue }
102
+ names.add(d[1]); exported.push(d[1])
103
+ stmts.push(['=', d[1], hoistVars(d[2], names)])
104
+ } else if (Array.isArray(d) && d[0] === '=' && isDestructurePat(d[1])) {
105
+ const local = new Set()
106
+ stmts.push(['=', hoistVarPattern(d[1], local), hoistVars(d[2], names)])
107
+ for (const n of local) { names.add(n); exported.push(n) }
108
+ }
109
+ }
110
+ if (exported.length) stmts.push(['export', ['{}', exported.length === 1 ? exported[0] : [',', ...exported]]])
111
+ if (!stmts.length) return null
112
+ return stmts.length === 1 ? stmts[0] : [';', ...stmts]
113
+ }
64
114
  if (op === 'var') {
65
115
  const decls = []
66
116
  for (let i = 1; i < node.length; i++) {
@@ -69,6 +119,13 @@ export function hoistVars(node, names) {
69
119
  if (Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string') {
70
120
  names.add(d[1])
71
121
  decls.push(['=', d[1], hoistVars(d[2], names)])
122
+ continue
123
+ }
124
+ // `var [a, b] = src` / `var {x} = src` — hoist every pattern binding,
125
+ // keep the declarator as an assignment-form destructure (a silently
126
+ // DROPPED declarator here was the "'b' is not in scope" class).
127
+ if (Array.isArray(d) && d[0] === '=' && isDestructurePat(d[1])) {
128
+ decls.push(['=', hoistVarPattern(d[1], names), hoistVars(d[2], names)])
72
129
  }
73
130
  }
74
131
  if (decls.length === 0) return null
@@ -128,6 +185,21 @@ export function hoistPattern(node, names) {
128
185
  return hoistVars(node, names)
129
186
  }
130
187
 
188
+ // `var`-declared pattern: every bare leaf in binding position is a hoisted
189
+ // name (the declarator itself becomes a plain assignment). let/const patterns
190
+ // keep hoistPattern above — their names are declared by the surviving `let`,
191
+ // so collecting them here would predeclare duplicates.
192
+ function hoistVarPattern(node, names) {
193
+ if (typeof node === 'string') { names.add(node); return node }
194
+ if (node == null || !Array.isArray(node)) return node
195
+ const op = node[0]
196
+ if (op === '=') return ['=', hoistVarPattern(node[1], names), hoistVars(node[2], names)]
197
+ if (op === ':') return [':', hoistVars(node[1], names), hoistVarPattern(node[2], names)]
198
+ if (op === '...') return ['...', hoistVarPattern(node[1], names)]
199
+ if (op === '[]' || op === '{}' || op === ',') return [op, ...node.slice(1).map(n => hoistVarPattern(n, names))]
200
+ return hoistVars(node, names)
201
+ }
202
+
131
203
  export function prependDecls(body, names) {
132
204
  const decl = ['let', ...names]
133
205
  if (Array.isArray(body) && body[0] === ';') return [';', decl, ...body.slice(1)]
package/jzify/index.js CHANGED
@@ -8,13 +8,16 @@
8
8
  */
9
9
 
10
10
  import { JZIFY_CLASS_ERRORS as JC } from '../src/op-policy.js'
11
+ import { parse } from '../src/parse.js'
12
+ import { createAsyncLowering, ASYNC_RUNTIME, ASYNC_GEN_RUNTIME } from './async.js'
11
13
  import { createNames } from './names.js'
12
14
  import { foldStaticExportHelpers, foldStaticBundlerHelpers, canonicalizeObjectIdioms } from './bundler.js'
13
15
  import { createSwitchLowering, normalizeCaseBody } from './switch.js'
14
- import { createClassLowering } from './classes.js'
16
+ import { createClassLowering, foldPseudoClassical } from './classes.js'
15
17
  import { hoistVars, prependDecls } from './hoist-vars.js'
16
18
  import { createArgumentsLowering } from './arguments.js'
17
- import { createTransform } from './transform.js'
19
+ import { createTransform, bindGenerators } from './transform.js'
20
+ import { createGeneratorLowering, ITER_HELPERS_RUNTIME, ITER_ARR_RUNTIME } from './generators.js'
18
21
 
19
22
  const names = createNames()
20
23
  const { lowerArguments, transformPattern, bindTransform } = createArgumentsLowering(names)
@@ -34,9 +37,72 @@ let transform, transformScope
34
37
  }))
35
38
  bindTransform(transform)
36
39
 
37
- ;({ lowerClass, lowerObjectLiteralThis, lowerArrayConstructor } = createClassLowering({ transform, names, JC }))
40
+ const constStrings = new Map()
41
+ ;({ lowerClass, lowerObjectLiteralThis, lowerArrayConstructor } = createClassLowering({ transform, names, JC, constStrings }))
42
+ const generatorNames = new Set()
43
+ // Program mints iterator objects (generators anywhere, hand-rolled `next()`
44
+ // members, `[Symbol.iterator]` methods) — gates the for-of protocol fork so
45
+ // programs without iterator producers compile byte-identically.
46
+ const iterProto = { on: false }
47
+ const genErr = (msg) => { throw new Error('jzify: ' + msg) }
48
+ const { lowerGenerator, desugarForOfGenerator, desugarForOfProtocol, unwindChain, fuseTerminal, fusedLoop, isTerminal } = createGeneratorLowering({ transform, err: genErr, generatorNames, genTemp: (t) => names.genTemp(t), iterProto })
49
+ const { lowerAsync, lowerAsyncGen, noteAsync, asyncUsed, agenUsed, resetAsync } = createAsyncLowering({ genTemp: (t) => names.genTemp(t), err: genErr })
50
+ bindGenerators({ lowerGenerator, desugarForOfGenerator, desugarForOfProtocol, lowerAsync, lowerAsyncGen, noteAsync, generatorNames, iterProto, unwindChain, fuseTerminal, fusedLoop, isTerminal })
38
51
  transformSwitch = createSwitchLowering(transform, names)
39
52
 
53
+ // Spread normalization for iterator values — injected only when a spread site
54
+ // wrapped in __drain (iterProto.drain). Arrays/strings/Sets/Maps pass through
55
+ // untouched (the existing spread machinery owns them); machines and
56
+ // @@iterator providers materialize.
57
+ const ITER_RUNTIME = `
58
+ let __it_drain = (v) => {
59
+ if (v == null) return v
60
+ if (typeof v === 'object' && v['@@iterator'] != null) v = v['@@iterator']()
61
+ if (typeof v !== 'object' || v.next == null) return v
62
+ let r = v.next(), a = []
63
+ while (!r.done) { a.push(r.value); r = v.next() }
64
+ return a
65
+ }
66
+ `
67
+
68
+ const isSymbolWellKnown = (n, which) => Array.isArray(n) && n[0] === '.' && n[1] === 'Symbol' && n[2] === which
69
+ const WELL_KNOWN = { iterator: '@@iterator', dispose: '@@dispose', asyncIterator: '@@asyncIterator' }
70
+ // Iterator-helper method names (ES2025) — a CALL of one of these on any
71
+ // receiver, in a program that mints iterators, gates decorated generator
72
+ // objects (__it_mk). Fusable chains still fuse; this covers value positions.
73
+ const ITER_HELPER_NAMES = new Set(['map', 'filter', 'take', 'drop', 'flatMap',
74
+ 'toArray', 'reduce', 'forEach', 'some', 'every', 'find'])
75
+ // Entry walk, two jobs in one pass:
76
+ // 1. Canonicalize well-known-symbol shapes to reserved literal props — a
77
+ // fixed-shape object has no symbol slots, so `[Symbol.iterator]` becomes
78
+ // the '@@iterator' prop in BOTH key position (computed member/method) and
79
+ // access position (`x[Symbol.iterator]`).
80
+ // 2. Detect iterator producers for the protocol-fork gate, and helper-method
81
+ // use / `instanceof Iterator` for the decorated-iterator gate.
82
+ function canonSymbols(node) {
83
+ if (!Array.isArray(node)) return node
84
+ const [op] = node
85
+ if (op === 'function*') iterProto.on = true
86
+ if (op === ':' && (node[1] === 'next' || node[1] === '@@iterator') &&
87
+ Array.isArray(node[2]) && (node[2][0] === '=>' || node[2][0] === 'function' || node[2][0] === 'function*'))
88
+ iterProto.on = true
89
+ if (op === '()' && Array.isArray(node[1]) && node[1][0] === '.' && ITER_HELPER_NAMES.has(node[1][2]))
90
+ iterProto.helpers = true
91
+ if (op === 'instanceof' && node[2] === 'Iterator') iterProto.helpers = true
92
+ // computed key: [':', ['[]', Symbol.X], value]
93
+ if (op === ':' && Array.isArray(node[1]) && node[1][0] === '[]' && node[1].length === 2) {
94
+ for (const [k, prop] of Object.entries(WELL_KNOWN))
95
+ if (isSymbolWellKnown(node[1][1], k)) { node[1] = prop; if (prop === '@@iterator') iterProto.on = true }
96
+ }
97
+ // access: ['[]', obj, Symbol.X] → ['.', obj, '@@X']
98
+ if (op === '[]' && node.length === 3) {
99
+ for (const [k, prop] of Object.entries(WELL_KNOWN))
100
+ if (isSymbolWellKnown(node[2], k)) { node[0] = '.'; node[2] = prop }
101
+ }
102
+ for (let i = 1; i < node.length; i++) canonSymbols(node[i])
103
+ return node
104
+ }
105
+
40
106
  /**
41
107
  * Transform AST in-place. Returns transformed AST.
42
108
  * @param {Array} ast - subscript/jessie parsed AST
@@ -44,8 +110,61 @@ transformSwitch = createSwitchLowering(transform, names)
44
110
  */
45
111
  export default function jzify(ast) {
46
112
  names.reset()
113
+ // Module-scope `const K = 'str'` bindings — lets class lowering fold computed
114
+ // member names `[K]() {}` (const guarantees the binding never changes).
115
+ constStrings.clear()
116
+ generatorNames.clear()
117
+ iterProto.on = false
118
+ iterProto.helpers = false
119
+ iterProto.helpersUsed = false
120
+ iterProto.arr = false
121
+ iterProto.fromUsed = false
122
+ ast = canonSymbols(ast)
123
+ if (Array.isArray(ast)) {
124
+ const stmts = ast[0] === ';' ? ast.slice(1) : [ast]
125
+ for (const st of stmts) {
126
+ if (Array.isArray(st) && st[0] === 'function*' && typeof st[1] === 'string' && st[1]) generatorNames.add(st[1])
127
+ if (Array.isArray(st) && st[0] === 'export' && Array.isArray(st[1]) && st[1][0] === 'function*' && st[1][1]) generatorNames.add(st[1][1])
128
+ if (!Array.isArray(st) || st[0] !== 'const') continue
129
+ for (let i = 1; i < st.length; i++) {
130
+ const d = st[i]
131
+ if (Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string' &&
132
+ Array.isArray(d[2]) && d[2][0] == null && typeof d[2][1] === 'string')
133
+ constStrings.set(d[1], d[2][1])
134
+ }
135
+ }
136
+ }
47
137
  const hoisted = new Set()
48
138
  ast = hoistVars(ast, hoisted)
49
139
  if (hoisted.size) ast = prependDecls(ast, hoisted)
50
- return foldStaticBundlerHelpers(foldStaticExportHelpers(canonicalizeObjectIdioms(transformScope(ast))))
140
+ if (Array.isArray(ast) && ast[0] === ';') ast = [';', ...foldPseudoClassical(ast.slice(1))]
141
+ resetAsync()
142
+ iterProto.drain = false
143
+ let out = transformScope(ast)
144
+ const prepend = (src) => {
145
+ // runtimes ride the same well-known-symbol canonicalization as user code
146
+ // ([Symbol.iterator] → '@@iterator' in key/access/assign positions) — the
147
+ // dot-canonical form keeps writes and prehashed dot-reads on ONE path.
148
+ const rt = transformScope(canonSymbols(parse(src)))
149
+ const rtStmts = Array.isArray(rt) && rt[0] === ';' ? rt.slice(1) : [rt]
150
+ const outStmts = Array.isArray(out) && out[0] === ';' ? out.slice(1) : [out]
151
+ out = [';', ...rtStmts, ...outStmts]
152
+ }
153
+ // spread-of-iterator sites wrapped in __drain → splice the helper
154
+ // (pass-through for arrays/strings; materializes machines/providers).
155
+ if (iterProto.drain) prepend(ITER_RUNTIME)
156
+ // Array.from-over-iterators sites → splice __it_arr (materialize/copy).
157
+ if (iterProto.arr) prepend(ITER_ARR_RUNTIME)
158
+ // helper-bearing generator mints (__it_mk) or literal-receiver
159
+ // [Symbol.iterator]() mints (__it_from) → splice the decorated-iterator
160
+ // factory. Programs without value-position helpers never take this shape.
161
+ if (iterProto.helpersUsed || iterProto.fromUsed) prepend(ITER_HELPERS_RUNTIME)
162
+ // async generators → splice the tagged-yield driver (before the promise
163
+ // runtime prepend so it lands AFTER it in module order — it calls __p_*).
164
+ if (agenUsed()) prepend(ASYNC_GEN_RUNTIME)
165
+ // async somewhere in the program → splice the plain-jz promise runtime
166
+ // (microtask queue, __async_run driver, promise shape + boundary readers)
167
+ // ahead of user code. Sync programs never reach this — byte-identical.
168
+ if (asyncUsed()) prepend(ASYNC_RUNTIME)
169
+ return foldStaticBundlerHelpers(foldStaticExportHelpers(canonicalizeObjectIdioms(out)))
51
170
  }
package/jzify/names.js CHANGED
@@ -33,5 +33,6 @@ export function createNames() {
33
33
  classStatic: () => `\uE003class${staticClassIdx++}`,
34
34
  classSuperArg: i => `\uE003superArg${classIdx}_${i}`,
35
35
  classSuper: i => `\uE003super${classIdx}_${i}`,
36
+ genTemp: (tag) => `\uE004${tag}${swIdx++}`,
36
37
  }
37
38
  }
@@ -73,6 +73,9 @@ const arrowParams = params => Array.isArray(params) && params[0] === '()' ? para
73
73
  * @param {() => Function} opts.lowerObjectLiteralThis
74
74
  * @param {() => Function} opts.lowerArrayConstructor
75
75
  */
76
+ let _gen = null
77
+ export const bindGenerators = (g) => { _gen = g }
78
+
76
79
  export function createTransform(opts) {
77
80
  const { names, lowerArguments, transformPattern, normalizeCaseBody, transformSwitch } = opts
78
81
  const lowerClass = (...a) => opts.lowerClass()(...a)
@@ -103,13 +106,42 @@ export function createTransform(opts) {
103
106
  return decl
104
107
  }
105
108
 
109
+ // `using x = res` (ERM): bind, resolve [Symbol.dispose] up front (TypeError
110
+ // if absent on a non-null resource — spec checks at binding), then wrap the
111
+ // REST of the scope in try/finally calling it. Multiple resources nest —
112
+ // LIFO disposal falls out of the nesting. Divergence (documented): if both
113
+ // the body and a dispose throw, the dispose error propagates (no
114
+ // SuppressedError aggregation).
115
+ function lowerUsing(declarators, remaining) {
116
+ const NULL = [null, null]
117
+ const dispose = (name) => ['if', ['!=', name, NULL], ['()', ['.', name, '@@dispose'], null]]
118
+ let inner = remaining.length ? transformScope([';', ...remaining]) : null
119
+ for (let k = declarators.length - 1; k >= 0; k--) {
120
+ const [, name, init] = declarators[k]
121
+ inner = [';',
122
+ ['let', ['=', name, transform(init)]],
123
+ ['if', ['&&', ['!=', name, NULL], ['==', ['.', name, '@@dispose'], NULL]],
124
+ ['throw', [null, 'using: value has no [Symbol.dispose]() method']]],
125
+ ['try', inner ?? ['{}', null], ['finally', dispose(name)]],
126
+ ]
127
+ }
128
+ return inner
129
+ }
130
+
106
131
  function transformScope(node) {
107
132
  if (!Array.isArray(node)) return transform(node)
108
133
 
109
134
  const [op, ...args] = node
110
135
 
111
136
  if (op === 'function' && args[0]) return hoistFnDecl(...args)
137
+ if (op === 'function*' && args[0] && _gen)
138
+ return ['const', ['=', args[0], _gen.lowerGenerator(args[1], args[2])]]
139
+ if (op === 'async' && Array.isArray(args[0]) && args[0][0] === 'function' && args[0][1] && _gen?.lowerAsync)
140
+ return ['const', ['=', args[0][1], transform(_gen.lowerAsync(args[0][2], args[0][3]))]]
141
+ if (op === 'async' && Array.isArray(args[0]) && args[0][0] === 'function*' && args[0][1] && _gen?.lowerAsyncGen)
142
+ return ['const', ['=', args[0][1], transform(_gen.lowerAsyncGen(args[0][2], args[0][3]))]]
112
143
  if (op === 'class' && args[0]) return ['let', ['=', args[0], lowerClass(...args)]]
144
+ if (op === 'using') return lowerUsing(args, [])
113
145
 
114
146
  if (op === ';') {
115
147
  const hoisted = [], rest = []
@@ -119,10 +151,41 @@ export function createTransform(opts) {
119
151
  hoisted.push(hoistFnDecl(stmt[1], stmt[2], stmt[3]))
120
152
  continue
121
153
  }
154
+ if (Array.isArray(stmt) && stmt[0] === 'function*' && stmt[1] && _gen) {
155
+ hoisted.push(['const', ['=', stmt[1], _gen.lowerGenerator(stmt[2], stmt[3])]])
156
+ continue
157
+ }
158
+ // async function DECLARATION — hoists like any function declaration.
159
+ if (Array.isArray(stmt) && stmt[0] === 'async' && Array.isArray(stmt[1]) &&
160
+ stmt[1][0] === 'function' && stmt[1][1] && _gen?.lowerAsync) {
161
+ hoisted.push(['const', ['=', stmt[1][1], transform(_gen.lowerAsync(stmt[1][2], stmt[1][3]))]])
162
+ continue
163
+ }
164
+ // async GENERATOR declaration — same hoisting, tagged-yield machine.
165
+ if (Array.isArray(stmt) && stmt[0] === 'async' && Array.isArray(stmt[1]) &&
166
+ stmt[1][0] === 'function*' && stmt[1][1] && _gen?.lowerAsyncGen) {
167
+ hoisted.push(['const', ['=', stmt[1][1], transform(_gen.lowerAsyncGen(stmt[1][2], stmt[1][3]))]])
168
+ continue
169
+ }
122
170
  if (Array.isArray(stmt) && stmt[0] === 'class' && stmt[1]) {
123
171
  rest.push(['let', ['=', stmt[1], lowerClass(stmt[1], stmt[2], stmt[3])]])
124
172
  continue
125
173
  }
174
+ // `using` consumes the REST of the scope into its try body (disposal
175
+ // runs at scope exit however the scope exits).
176
+ if (Array.isArray(stmt) && stmt[0] === 'using') {
177
+ rest.push(lowerUsing(stmt.slice(1), args.slice(i + 1)))
178
+ break
179
+ }
180
+ // Labeled BLOCK in statement position (`lbl: { … }`): unambiguous here —
181
+ // a ':' STATEMENT can't be an object prop. '{}' stays out of
182
+ // LABEL_BODY_OPS (the expression-context disambiguator), so literal
183
+ // props `k: {…}` never label.
184
+ if (Array.isArray(stmt) && stmt[0] === ':' && typeof stmt[1] === 'string' &&
185
+ Array.isArray(stmt[2]) && stmt[2][0] === '{}') {
186
+ rest.push(['label', stmt[1], transform(stmt[2])])
187
+ continue
188
+ }
126
189
  const t = transform(stmt)
127
190
  if (t == null) continue
128
191
  if (Array.isArray(t) && t[0] === 'const' && t._hoisted) {
@@ -154,12 +217,98 @@ export function createTransform(opts) {
154
217
  return transform(node)
155
218
  }
156
219
 
220
+ // Promise statics → the injected plain-jz runtime helpers.
221
+ const P_STATIC = {
222
+ resolve: '__p_resolve', reject: '__p_reject', all: '__p_all', race: '__p_race',
223
+ allSettled: '__p_allSettled', any: '__p_any', try: '__p_try', withResolvers: '__p_withResolvers',
224
+ }
225
+
226
+ // Spread of a possibly-iterator value (iterator-minting programs only):
227
+ // `...E` → `...__drain(E)` — pass-through for arrays/strings, materializes
228
+ // machines/@@iterator providers. Array literals skip (statically safe).
229
+ const wrapSpreadDrain = (e) => {
230
+ if (!_gen?.iterProto?.on) return null
231
+ const v = e[1]
232
+ if (Array.isArray(v) && (v[0] === '[]' || v[0] == null)) return null
233
+ _gen.iterProto.drain = true
234
+ return ['...', ['()', '__it_drain', transform(v)]]
235
+ }
236
+ const wrapArg = (a) => (Array.isArray(a) && a[0] === '...' && wrapSpreadDrain(a)) || transform(a)
237
+
157
238
  const handlers = {
239
+ // async function/arrow → (...aa) => __async_run((function* …)(...aa))
240
+ 'async'(inner) {
241
+ if (!_gen?.lowerAsync || !Array.isArray(inner)) return
242
+ if (inner[0] === 'function*') return transform(_gen.lowerAsyncGen(inner[2], inner[3]))
243
+ if (inner[0] === 'function') return transform(_gen.lowerAsync(inner[2], inner[3]))
244
+ if (inner[0] === '=>') {
245
+ const params = Array.isArray(inner[1]) && inner[1][0] === '()' ? inner[1][1] : inner[1]
246
+ return transform(_gen.lowerAsync(params, inner[2]))
247
+ }
248
+ // `async function () {}()` — the parser binds the CALL inside the async
249
+ // wrapper; lower the callee, keep the call.
250
+ if (inner[0] === '()' && Array.isArray(inner[1]) && (inner[1][0] === 'function' || inner[1][0] === '=>'))
251
+ return transform(['()', ['async', inner[1]], ...inner.slice(2)])
252
+ },
253
+
158
254
  '()'(callee, ...rest) {
255
+ // Promise API rides the async runtime: new Promise(fn) arrives here as a
256
+ // plain call (the `new` handler unwraps unknown ctors), statics by name.
257
+ if (_gen?.noteAsync) {
258
+ if (callee === 'Promise' && rest.length) {
259
+ _gen.noteAsync()
260
+ return ['()', '__p_exec', ...rest.map(a => a == null ? a : transform(a))]
261
+ }
262
+ if (Array.isArray(callee) && callee[0] === '.' && callee[1] === 'Promise' && P_STATIC[callee[2]]) {
263
+ _gen.noteAsync()
264
+ return ['()', P_STATIC[callee[2]], ...rest.map(a => a == null ? a : transform(a))]
265
+ }
266
+ }
267
+ // Terminal iterator helper (toArray/reduce/forEach/some/every/find) on a
268
+ // chain rooted at a known generator call → fused IIFE loop.
269
+ if (_gen && Array.isArray(callee) && callee[0] === '.') {
270
+ const chain = _gen.unwindChain(['()', callee, ...rest])
271
+ if (chain && chain.stages.length && _gen.isTerminal(chain.stages[chain.stages.length - 1].h)) {
272
+ const fused = _gen.fuseTerminal(chain, names.genTemp)
273
+ if (fused) return transform(fused)
274
+ }
275
+ }
159
276
  if (callee === 'Array') {
160
277
  const lit = lowerArrayConstructor(rest[0])
161
278
  if (lit) return lit
162
279
  }
280
+ // `[…literal][Symbol.iterator]()` / `'s'[Symbol.iterator]()` — mint a
281
+ // decorated indexed iterator (array/string values as iterators). Literal
282
+ // receivers only: name/expression receivers keep the plain dot-call
283
+ // (machines answer it themselves; the protocol desugar's own probe-call
284
+ // must not re-enter here).
285
+ if (_gen && Array.isArray(callee) && callee[0] === '.' && callee[2] === '@@iterator' &&
286
+ rest.every(a => a == null) && Array.isArray(callee[1]) &&
287
+ ((callee[1][0] === '[]' && callee[1].length === 2) || (callee[1][0] == null && typeof callee[1][1] === 'string'))) {
288
+ _gen.iterProto.fromUsed = true
289
+ return ['()', '__it_from', transform(callee[1])]
290
+ }
291
+ // Array.from over iterator values (iterator-minting programs only):
292
+ // protocol values materialize via __it_arr; arrays copy; array-likes
293
+ // build by length. `Array.from(x, fn)` maps the materialized array.
294
+ if (_gen?.iterProto?.on && Array.isArray(callee) && callee[0] === '.' &&
295
+ callee[1] === 'Array' && callee[2] === 'from' && rest.length === 1) {
296
+ _gen.iterProto.arr = true
297
+ const args = Array.isArray(rest[0]) && rest[0][0] === ',' ? rest[0].slice(1) : [rest[0]]
298
+ const drained = ['()', '__it_arr', transform(args[0])]
299
+ if (args.length >= 2) return ['()', ['.', drained, 'map'], transform(args[1])]
300
+ return drained
301
+ }
302
+ // spread ARG of a possibly-iterator value → __drain (iterator programs only)
303
+ if (_gen?.iterProto?.on && rest.length === 1 && Array.isArray(rest[0])) {
304
+ const args = rest[0]
305
+ if (args[0] === ',' && args.slice(1).some(x => Array.isArray(x) && x[0] === '...'))
306
+ return ['()', transform(callee), [',', ...args.slice(1).map(wrapArg)]]
307
+ if (args[0] === '...') {
308
+ const w = wrapSpreadDrain(args)
309
+ if (w) return ['()', transform(callee), w]
310
+ }
311
+ }
163
312
  if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
164
313
  const [, name, params, body] = callee[1]
165
314
  const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
@@ -217,6 +366,43 @@ export function createTransform(opts) {
217
366
  return ['let', ...args.map(transform)]
218
367
  },
219
368
 
369
+ 'function*'(name, params, body) {
370
+ // Expression form (`let g = function* () {…}`). Named statement forms are
371
+ // hoisted in transformScope like plain function declarations.
372
+ if (!_gen) return
373
+ return _gen.lowerGenerator(params, body)
374
+ },
375
+
376
+ '[]'(payload, idx) {
377
+ // 2-arg form is INDEXING (obj[key]) — not ours. The 1-arg form is the
378
+ // array LITERAL: rewrite a spread of a generator / helper chain into a
379
+ // spread of the FUSED toArray (a plain array) — the existing
380
+ // array-spread machinery takes it from there. In an iterator-minting
381
+ // program, any OTHER spread of a non-literal wraps in __drain (returns
382
+ // the value untouched unless it's an iterator — then materializes it).
383
+ if (!_gen || idx !== undefined) return
384
+ const rewrite = (e) => {
385
+ if (!Array.isArray(e) || e[0] !== '...') return null
386
+ if (Array.isArray(e[1])) {
387
+ const chain = _gen.unwindChain(e[1])
388
+ if (chain && (!chain.stages.length || !_gen.isTerminal(chain.stages[chain.stages.length - 1].h))) {
389
+ const fused = _gen.fuseTerminal({ root: chain.root, stages: [...chain.stages, { h: 'toArray', args: [] }] }, names.genTemp)
390
+ if (fused) return ['...', transform(fused)]
391
+ }
392
+ }
393
+ return wrapSpreadDrain(e)
394
+ }
395
+ if (payload === undefined) return
396
+ if (Array.isArray(payload) && payload[0] === ',') {
397
+ let hit = false
398
+ const mapped = payload.slice(1).map((e) => { const r = rewrite(e); if (r) hit = true; return r ?? transform(e) })
399
+ if (hit) return ['[]', [',', ...mapped]]
400
+ return
401
+ }
402
+ const one = rewrite(payload)
403
+ if (one) return ['[]', one]
404
+ },
405
+
220
406
  ':'(label, body) {
221
407
  if (typeof label === 'string' && Array.isArray(body) && LABEL_BODY_OPS.has(body[0]))
222
408
  return ['label', label, transform(body)]
@@ -246,6 +432,17 @@ export function createTransform(opts) {
246
432
  if (lit) return lit
247
433
  }
248
434
  const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
435
+ // SharedArrayBuffer canonicalizes to ArrayBuffer: within one module the
436
+ // buffer IS jz's linear memory — sharedness is a jz.pool linking concern
437
+ // (sharedMemory: true), not a per-buffer object property. Atomics accept
438
+ // proven Int32Array views either way. (`new X(args)` parses with the
439
+ // args INSIDE the ctor call node — rename the callee, keep the rest.)
440
+ if (name === 'SharedArrayBuffer') {
441
+ const ctor2 = Array.isArray(ctor) && ctor[0] === '()'
442
+ ? ['()', 'ArrayBuffer', ...ctor.slice(2).map(transform)]
443
+ : 'ArrayBuffer'
444
+ return ['new', ctor2, ...cargs.map(transform)]
445
+ }
249
446
  // Preserve `new` for native constructors the compiler resolves under its own
250
447
  // `new` handler (prepare/index.js): typed arrays/Array/RegExp need the `new`
251
448
  // form, and `new URL(rel, import.meta.url)` lowers to a static href string there.
@@ -256,8 +453,21 @@ export function createTransform(opts) {
256
453
  },
257
454
 
258
455
  'instanceof'(val, ctor) {
456
+ // promise-shape probe — promises are fixed-shape objects, no ctor chain
457
+ if (ctor === 'Promise' && _gen?.noteAsync) {
458
+ _gen.noteAsync()
459
+ const t0 = transform(val)
460
+ return ['&&', ['!=', t0, [null, null]], ['==', ['.', t0, '__p'], [null, 1]]]
461
+ }
462
+ // iterator-shape probe — anything driving the protocol (a callable next)
463
+ // is an Iterator to jz; arrays/strings probe false (no `next` prop).
464
+ if (ctor === 'Iterator' && _gen) {
465
+ const t0 = transform(val)
466
+ return ['&&', ['===', ['typeof', t0], [null, 'object']], ['!=', ['.', t0, 'next'], [null, null]]]
467
+ }
259
468
  const t = transform(val)
260
- const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
469
+ let name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
470
+ if (name === 'SharedArrayBuffer') name = 'ArrayBuffer' // same canonicalization as `new`
261
471
  const fold = staticInstanceofFold(val, name)
262
472
  if (fold != null) return [null, fold]
263
473
  if (typeof name === 'string' && ERROR_INSTANCEOF.has(name)) {
@@ -288,6 +498,34 @@ export function createTransform(opts) {
288
498
  // (`for (; i < n; i++)` ran zero/garbage iterations). for-of/for-in heads aren't
289
499
  // `;`-lists, so they pass through transform unchanged.
290
500
  'for'(head, body) {
501
+ // for-of over a KNOWN generator call → while-next desugar (the generator
502
+ // object is plain closures + a fixed-shape result record).
503
+ if (_gen && Array.isArray(head) && head[0] === 'of' &&
504
+ Array.isArray(head[2]) && head[2][0] === '()' &&
505
+ typeof head[2][1] === 'string' && _gen.generatorNames.has(head[2][1]))
506
+ return transform(_gen.desugarForOfGenerator(head[1], transform(head[2]), body, names.genTemp))
507
+ // for-of over an iterator-HELPER CHAIN rooted at a known generator call
508
+ // → one fused while-next loop (map/filter/take/drop compose in place).
509
+ if (_gen && Array.isArray(head) && head[0] === 'of' && Array.isArray(head[2])) {
510
+ const chain = _gen.unwindChain(head[2])
511
+ if (chain && chain.stages.length && chain.stages.every(st => !_gen.isTerminal(st.h))) {
512
+ const name = Array.isArray(head[1]) ? head[1][1] : head[1]
513
+ const bodyStmts = Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
514
+ const x = names.genTemp('gx')
515
+ const fused = _gen.fusedLoop(chain.root, chain.stages, names.genTemp, x,
516
+ () => [['let', ['=', name, x]], ...bodyStmts])
517
+ return transform(fused)
518
+ }
519
+ }
520
+ // 'of-idx' — the protocol fork's array arm: plain indexed for-of, no re-fork.
521
+ if (Array.isArray(head) && head[0] === 'of-idx') {
522
+ const t = transform(['of', head[1], head[2]])
523
+ return ['for', t, transform(body)]
524
+ }
525
+ // for-of over an UNKNOWN source in an iterator-minting program → runtime
526
+ // protocol fork (probe once, drive next() lazily, else indexed path).
527
+ if (_gen && _gen.iterProto?.on && Array.isArray(head) && head[0] === 'of')
528
+ return transform(_gen.desugarForOfProtocol(head[1], head[2], body, names.genTemp))
291
529
  if (Array.isArray(head) && head[0] === ';')
292
530
  return ['for', [';', ...head.slice(1).map(s => s == null ? s : transform(s))], transform(body)]
293
531
  return ['for', transform(head), transform(body)]