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/src/ctx.js CHANGED
@@ -109,6 +109,7 @@ export function reset(proto, globals) {
109
109
  resolvedModules: new Map(),
110
110
  moduleStack: [],
111
111
  moduleInits: [],
112
+ initFacts: null,
112
113
  currentPrefix: null,
113
114
  }
114
115
 
package/src/emit.js CHANGED
@@ -1877,20 +1877,30 @@ export const emitter = {
1877
1877
  const combined = reconstructArgsWithSpreads(parsed.normal, parsed.spreads)
1878
1878
  const arrayIR = buildArrayWithSpreads(combined)
1879
1879
  const propRead = typed(['call', '$__dyn_get_expr', ['local.get', `$${objTmp}`], asF64(emit(['str', method]))], 'f64')
1880
+ const propTmp = `${T}mprop${ctx.func.uniq++}`
1881
+ ctx.func.locals.set(propTmp, 'f64')
1880
1882
  if (usesDynProps(vt)) {
1881
- inc('__dyn_get_expr')
1883
+ inc('__dyn_get_expr', '__ptr_type')
1882
1884
  return typed(['block', ['result', 'f64'],
1883
1885
  ['local.set', `$${objTmp}`, asF64(emit(obj))],
1884
- ctx.closure.call(propRead, [arrayIR], true)], 'f64')
1886
+ ['local.set', `$${propTmp}`, propRead],
1887
+ ['if', ['result', 'f64'],
1888
+ ['i32.eq', ['call', '$__ptr_type', ['local.get', `$${propTmp}`]], ['i32.const', PTR.CLOSURE]],
1889
+ ['then', ctx.closure.call(typed(['local.get', `$${propTmp}`], 'f64'), [arrayIR], true)],
1890
+ ['else', undefExpr()]]], 'f64')
1885
1891
  }
1886
- inc('__dyn_get_expr', '__ext_call')
1892
+ inc('__dyn_get_expr', '__ext_call', '__ptr_type')
1887
1893
  ctx.features.external = true
1888
1894
  return typed(['block', ['result', 'f64'],
1889
1895
  ['local.set', `$${objTmp}`, asF64(emit(obj))],
1896
+ ['local.set', `$${propTmp}`, propRead],
1890
1897
  ['if', ['result', 'f64'],
1891
- ['i32.eq', ['call', '$__ptr_type', ['local.get', `$${objTmp}`]], ['i32.const', PTR.EXTERNAL]],
1892
- ['then', ['call', '$__ext_call', ['local.get', `$${objTmp}`], asF64(emit(['str', method])), arrayIR]],
1893
- ['else', ctx.closure.call(propRead, [arrayIR], true)]]], 'f64')
1898
+ ['i32.eq', ['call', '$__ptr_type', ['local.get', `$${propTmp}`]], ['i32.const', PTR.CLOSURE]],
1899
+ ['then', ctx.closure.call(typed(['local.get', `$${propTmp}`], 'f64'), [arrayIR], true)],
1900
+ ['else', ['if', ['result', 'f64'],
1901
+ ['i32.eq', ['call', '$__ptr_type', ['local.get', `$${objTmp}`]], ['i32.const', PTR.EXTERNAL]],
1902
+ ['then', ['call', '$__ext_call', ['local.get', `$${objTmp}`], asF64(emit(['str', method])), arrayIR]],
1903
+ ['else', undefExpr()]]]]], 'f64')
1894
1904
  }
1895
1905
 
1896
1906
  // Unknown callee - assume external method
package/src/host.js CHANGED
@@ -435,36 +435,31 @@ export const wrap = (memSrc, inst) => {
435
435
  return exports
436
436
  }
437
437
 
438
- /**
439
- * Compile, instantiate, and wrap exports (with WASI + rest-param support).
440
- * `compile` is the jz.compile function (injected to avoid importing the compiler core).
441
- */
442
- export const instantiate = (compile, code, opts = {}) => {
443
- const extMap = [null]
444
- let mem = null
438
+ const prepareInterop = (opts) => {
439
+ const state = { extMap: [null], mem: null }
445
440
  opts._interp = opts._interp || {}
446
441
  opts._interp.__ext_prop = (objPtr, propPtr) => {
447
- const obj = extMap[offset(objPtr)]
448
- const prop = mem.read(propPtr)
449
- return mem.wrapVal(typeof obj[prop] === 'function' ? obj[prop].bind(obj) : obj[prop])
442
+ const obj = state.extMap[offset(objPtr)]
443
+ const prop = state.mem.read(propPtr)
444
+ return state.mem.wrapVal(typeof obj[prop] === 'function' ? obj[prop].bind(obj) : obj[prop])
450
445
  }
451
446
  opts._interp.__ext_has = (objPtr, propPtr) => {
452
- return (mem.read(propPtr) in extMap[offset(objPtr)]) ? 1 : 0
447
+ return (state.mem.read(propPtr) in state.extMap[offset(objPtr)]) ? 1 : 0
453
448
  }
454
449
  opts._interp.__ext_set = (objPtr, propPtr, valPtr) => {
455
- extMap[offset(objPtr)][mem.read(propPtr)] = mem.read(valPtr)
450
+ state.extMap[offset(objPtr)][state.mem.read(propPtr)] = state.mem.read(valPtr)
456
451
  return 1
457
452
  }
458
453
  opts._interp.__ext_call = (objPtr, propPtr, argsPtr) => {
459
- const obj = extMap[offset(objPtr)]
460
- const prop = mem.read(propPtr)
461
- const args = mem.read(argsPtr)
462
- return mem.wrapVal(obj[prop].apply(obj, args))
454
+ const obj = state.extMap[offset(objPtr)]
455
+ const prop = state.mem.read(propPtr)
456
+ const args = state.mem.read(argsPtr)
457
+ return state.mem.wrapVal(obj[prop].apply(obj, args))
463
458
  }
459
+ return state
460
+ }
464
461
 
465
- const wasm = compile(code, opts)
466
- opts.extMap = extMap
467
- const mod = new WebAssembly.Module(wasm)
462
+ const buildImports = (mod, opts, state) => {
468
463
  const needsWasi = WebAssembly.Module.imports(mod).some(i => i.module === 'wasi_snapshot_preview1')
469
464
  const imports = needsWasi ? wasi(opts) : {}
470
465
  if (opts._interp) imports.env = { ...imports.env, ...opts._interp }
@@ -477,8 +472,8 @@ export const instantiate = (compile, code, opts = {}) => {
477
472
  const fn = typeof spec === 'function' ? spec : (spec && typeof spec === 'object' ? spec.fn : null)
478
473
  if (typeof fn === 'function')
479
474
  imports[modName][name] = (...args) => {
480
- const ret = fn.call(fns, ...args.map(a => mem ? mem.read(a) : a))
481
- return mem ? mem.wrapVal(ret) : coerce(ret)
475
+ const ret = fn.call(fns, ...args.map(a => state.mem ? state.mem.read(a) : a))
476
+ return state.mem ? state.mem.wrapVal(ret) : coerce(ret)
482
477
  }
483
478
  }
484
479
  }
@@ -495,13 +490,15 @@ export const instantiate = (compile, code, opts = {}) => {
495
490
  const host = globalThis[imp.name]
496
491
  if (host !== undefined) {
497
492
  if (!imports.env) imports.env = {}
498
- let id = extMap.indexOf(host); if (id === -1) { id = extMap.length; extMap.push(host) }
493
+ let id = state.extMap.indexOf(host); if (id === -1) { id = state.extMap.length; state.extMap.push(host) }
499
494
  imports.env[imp.name] = new WebAssembly.Global({ value: 'f64', mutable: true }, ptr(11, 0, id))
500
495
  }
501
496
  }
502
497
  }
503
- const hasImports = Object.keys(imports).some(k => k !== '_setMemory')
504
- const inst = new WebAssembly.Instance(mod, hasImports ? imports : undefined)
498
+ return { imports, needsWasi }
499
+ }
500
+
501
+ const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
505
502
  if (needsWasi) imports._setMemory(inst.exports.memory)
506
503
 
507
504
  // Drive WASM timer queue via JS scheduling (non-blocking)
@@ -517,8 +514,23 @@ export const instantiate = (compile, code, opts = {}) => {
517
514
 
518
515
  // For shared memory, resolve memory from import; for own memory, from export
519
516
  const rawMemory = opts.memory || inst.exports.memory
520
- const memSrc = { module: mod, instance: inst, exports: { ...inst.exports, memory: rawMemory }, extMap }
517
+ const memSrc = { module: mod, instance: inst, exports: { ...inst.exports, memory: rawMemory }, extMap: state.extMap }
521
518
  const enhanced = memory(memSrc)
522
- mem = enhanced
519
+ state.mem = enhanced
523
520
  return { exports: wrap(memSrc), memory: enhanced, instance: inst, module: mod }
524
521
  }
522
+
523
+ /**
524
+ * Compile, instantiate, and wrap exports (with WASI + rest-param support).
525
+ * `compile` is the jz.compile function (injected to avoid importing the compiler core).
526
+ */
527
+ export const instantiate = (compile, code, opts = {}) => {
528
+ const state = prepareInterop(opts)
529
+ const wasm = compile(code, opts)
530
+ opts.extMap = state.extMap
531
+ const mod = new WebAssembly.Module(wasm)
532
+ const { imports, needsWasi } = buildImports(mod, opts, state)
533
+ const hasImports = Object.keys(imports).some(k => k !== '_setMemory')
534
+ const inst = new WebAssembly.Instance(mod, hasImports ? imports : undefined)
535
+ return finishInstantiation(mod, inst, imports, needsWasi, opts, state)
536
+ }
package/src/jzify.js CHANGED
@@ -122,21 +122,33 @@ function paramList(params) {
122
122
  }
123
123
 
124
124
  function lowerArguments(params, body) {
125
- if (!usesArguments(body)) return [params, body]
125
+ if (!usesArguments(params) && !usesArguments(body)) return [params, body]
126
126
  const name = `\uE001arg${argsIdx++}`
127
- const items = paramList(params)
128
- items.push(['...', name])
129
- const inner = items.length === 1 ? items[0] : [',', ...items]
130
- return [['()', inner], renameArguments(body, name)]
127
+ const decls = []
128
+ for (const [idx, param] of paramList(params).entries()) {
129
+ if (Array.isArray(param) && param[0] === '...') {
130
+ decls.push(['=', param[1], ['()', ['.', name, 'slice'], [null, idx]]])
131
+ continue
132
+ }
133
+ if (Array.isArray(param) && param[0] === '=') {
134
+ decls.push(['=', param[1], ['??', ['[]', name, [null, idx]], renameArguments(param[2], name)]])
135
+ continue
136
+ }
137
+ decls.push(['=', param, ['[]', name, [null, idx]]])
138
+ }
139
+ const renamed = renameArguments(body, name)
140
+ return [['()', ['...', name]], decls.length ? [';', ['let', ...decls], renamed] : renamed]
131
141
  }
132
142
 
143
+ const arrowParams = params => Array.isArray(params) && params[0] === '()' ? params : ['()', params]
144
+
133
145
  const handlers = {
134
146
  // Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
135
147
  '()'(callee, ...rest) {
136
148
  if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
137
149
  const [, name, params, body] = callee[1]
138
150
  const [p2, b2] = lowerArguments(params, body)
139
- return [';', ['let', ['=', name, ['=>', p2, wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
151
+ return [';', ['let', ['=', name, ['=>', arrowParams(p2), wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
140
152
  }
141
153
  },
142
154
 
@@ -148,7 +160,14 @@ const handlers = {
148
160
  return arrow
149
161
  },
150
162
 
151
- 'var'(...args) { return ['let', ...args.map(transform)] },
163
+ 'var'(...args) {
164
+ // for-in/for-of: ['var', ['in', 'k', obj]] → ['in', ['let', 'k'], obj]
165
+ if (args.length === 1 && Array.isArray(args[0]) && (args[0][0] === 'in' || args[0][0] === 'of')) {
166
+ const [, name, src] = args[0]
167
+ return [args[0][0], ['let', typeof name === 'string' ? name : transform(name)], transform(src)]
168
+ }
169
+ return ['let', ...args.map(transform)]
170
+ },
152
171
 
153
172
  '='(lhs, rhs) {
154
173
  // var assignment: ['=', ['var', name], init] → let
@@ -205,16 +224,14 @@ const handlers = {
205
224
  return ['===', ['typeof', t], [null, 'object']]
206
225
  },
207
226
 
208
- // do { body } while (cond) → for (let _once = true; _once || cond; _once = false) body
209
- // Avoids body duplication; matches JS continue semantics (continue runs cond, not body).
227
+ // do { body } while (cond) → let _once = true; while (_once || cond) { _once = false; body }
228
+ // Avoids body duplication and preserves continue: `continue` jumps back to the
229
+ // while condition after the one-shot flag has been cleared.
210
230
  'do'(body, cond) {
211
231
  const flag = `do${doIdx++}`
212
- return ['for',
213
- [';',
214
- ['let', ['=', flag, [null, true]]],
215
- ['||', flag, transform(cond)],
216
- ['=', flag, [null, false]]],
217
- transform(body)]
232
+ return [';',
233
+ ['let', ['=', flag, [null, true]]],
234
+ ['while', ['||', flag, transform(cond)], ['{}', [';', ['=', flag, [null, false]], transform(body)]]]]
218
235
  },
219
236
 
220
237
  // Block body: recurse as scope for hoisting
@@ -316,6 +333,9 @@ export function codegen(node, depth = 0) {
316
333
  const [head, body] = a
317
334
  if (Array.isArray(head) && (head[0] === 'of' || head[0] === 'in'))
318
335
  return 'for (' + codegen(head[1]) + ' ' + head[0] + ' ' + codegen(head[2]) + ') ' + codegen(body, depth)
336
+ // ['let'/'const', ['in'/'of', name, obj]] — subscript wraps var→let around in/of
337
+ if (Array.isArray(head) && (head[0] === 'let' || head[0] === 'const') && Array.isArray(head[1]) && (head[1][0] === 'in' || head[1][0] === 'of'))
338
+ return 'for (' + head[0] + ' ' + codegen(head[1][1]) + ' ' + head[1][0] + ' ' + codegen(head[1][2]) + ') ' + codegen(body, depth)
319
339
  return 'for (' + codegen(head) + ') ' + codegen(body, depth)
320
340
  }
321
341
  return 'for (' + (codegen(a[0]) || '') + '; ' + (codegen(a[1]) || '') + '; ' + (codegen(a[2]) || '') + ') ' + codegen(a[3], depth)
@@ -324,7 +344,11 @@ export function codegen(node, depth = 0) {
324
344
  if (op === 'throw') return 'throw ' + codegen(a[0])
325
345
  if (op === 'break') return 'break'
326
346
  if (op === 'continue') return 'continue'
327
- if (op === 'catch') return 'try ' + codegen(a[0], depth) + ' catch (' + a[1] + ') ' + codegen(a[2], depth)
347
+ // catch with optional binding: ['catch', tryBlock, catchBody] or ['catch', tryBlock, paramName, catchBody]
348
+ if (op === 'catch') {
349
+ if (a.length === 3) return 'try ' + codegen(a[0], depth) + ' catch (' + a[1] + ') ' + codegen(a[2], depth)
350
+ return 'try ' + codegen(a[0], depth) + ' catch ' + codegen(a[1], depth)
351
+ }
328
352
 
329
353
  // Arrow
330
354
  if (op === '=>') {
@@ -358,8 +382,11 @@ export function codegen(node, depth = 0) {
358
382
 
359
383
  // Comma
360
384
  if (op === ',') return a.map(x => codegen(x)).join(', ')
361
- // Template literal
362
- if (op === '`') return '`' + a.map((p, i) => i % 2 ? '${' + codegen(p) + '}' : (p?.[1] ?? '')).join('') + '`'
385
+ // Template literal: alternating string/expr parts. String parts are [null, "str"], expr parts are AST nodes.
386
+ if (op === '`') return '`' + a.map(p => {
387
+ if (Array.isArray(p) && p[0] == null && typeof p[1] === 'string') return p[1].replace(/[`\\$]/g, c => '\\' + c)
388
+ return '${' + codegen(p) + '}'
389
+ }).join('') + '`'
363
390
 
364
391
  // Spread
365
392
  if (op === '...') return '...' + codegen(a[0])
package/src/key.js ADDED
@@ -0,0 +1,73 @@
1
+ /** Static property-key evaluation for computed member names. */
2
+
3
+ const NO_VALUE = Symbol('no-static-property-key')
4
+
5
+ export function staticPropertyKey(node) {
6
+ const value = staticValue(node)
7
+ return value === NO_VALUE ? null : String(value)
8
+ }
9
+
10
+ function staticValue(node) {
11
+ if (node === undefined) return undefined
12
+ if (node === null || typeof node === 'number' || typeof node === 'string' || typeof node === 'boolean') return node
13
+ if (!Array.isArray(node)) return NO_VALUE
14
+
15
+ const [op, ...args] = node
16
+ if (op == null) return args.length ? args[0] : undefined
17
+ if (op === 'str') return args[0]
18
+ if (op === '[]' && args.length === 1) return staticValue(args[0])
19
+ if (op === '()' && args[0] === 'String' && args.length === 2) {
20
+ const value = staticValue(args[1])
21
+ return value === NO_VALUE ? NO_VALUE : String(value)
22
+ }
23
+ if (op === '()' && args[0] === 'Number' && args.length === 2) {
24
+ const value = staticValue(args[1])
25
+ return value === NO_VALUE ? NO_VALUE : Number(value)
26
+ }
27
+ if (op === '?:' || op === '?') {
28
+ const cond = staticValue(args[0])
29
+ return cond === NO_VALUE ? NO_VALUE : staticValue(cond ? args[1] : args[2])
30
+ }
31
+ if (op === '&&' || op === '||') {
32
+ const left = staticValue(args[0])
33
+ if (left === NO_VALUE) return NO_VALUE
34
+ return op === '&&' ? (left ? staticValue(args[1]) : left) : (left ? left : staticValue(args[1]))
35
+ }
36
+ if (op === '??') {
37
+ const left = staticValue(args[0])
38
+ return left === NO_VALUE ? NO_VALUE : left == null ? staticValue(args[1]) : left
39
+ }
40
+
41
+ if (args.length === 1) {
42
+ const value = staticValue(args[0])
43
+ if (value === NO_VALUE) return NO_VALUE
44
+ if (op === 'u+') return +value
45
+ if (op === 'u-') return -value
46
+ if (op === '!') return !value
47
+ if (op === '~') return ~value
48
+ return NO_VALUE
49
+ }
50
+
51
+ if (args.length === 2) {
52
+ const left = staticValue(args[0])
53
+ const right = staticValue(args[1])
54
+ if (left === NO_VALUE || right === NO_VALUE) return NO_VALUE
55
+ switch (op) {
56
+ case '+': return typeof left === 'string' || typeof right === 'string' ? String(left) + String(right) : Number(left) + Number(right)
57
+ case '-': return Number(left) - Number(right)
58
+ case '*': return Number(left) * Number(right)
59
+ case '/': return Number(left) / Number(right)
60
+ case '%': return Number(left) % Number(right)
61
+ case '**': return Number(left) ** Number(right)
62
+ case '&': return Number(left) & Number(right)
63
+ case '|': return Number(left) | Number(right)
64
+ case '^': return Number(left) ^ Number(right)
65
+ case '<<': return Number(left) << Number(right)
66
+ case '>>': return Number(left) >> Number(right)
67
+ case '>>>': return Number(left) >>> Number(right)
68
+ default: return NO_VALUE
69
+ }
70
+ }
71
+
72
+ return NO_VALUE
73
+ }