jz 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/module/math.js CHANGED
@@ -13,7 +13,8 @@
13
13
  * @module math
14
14
  */
15
15
 
16
- import { emit, typed, asF64, asI32, temp, arrayLoop } from '../src/compile.js'
16
+ import { typed, asF64, asI32, temp, arrayLoop } from '../src/ir.js'
17
+ import { emit } from '../src/emit.js'
17
18
  import { inc } from '../src/ctx.js'
18
19
  import { repOf } from '../src/analyze.js'
19
20
 
@@ -59,13 +60,21 @@ export default (ctx) => {
59
60
  ctx.core.emit['math.ceil'] = a => fInt('f64.ceil', a)
60
61
  ctx.core.emit['math.trunc'] = a => fInt('f64.trunc', a)
61
62
  ctx.core.emit['math.min'] = (a, b, ...rest) => {
63
+ if (a === undefined) return typed(['f64.const', Infinity], 'f64')
62
64
  // Spread: Math.min(...arr) — iterate array to find min
63
65
  if (!b && Array.isArray(a) && a[0] === '...') return emitArrayReduce('f64.min', a[1], Infinity)
64
- return f2('f64.min', a, b)
66
+ if (b === undefined) return typed(['f64.min', asF64(emit(a)), ['f64.const', Infinity]], 'f64')
67
+ let r = f2('f64.min', a, b)
68
+ for (const x of rest) r = typed(['f64.min', r, asF64(emit(x))], 'f64')
69
+ return r
65
70
  }
66
71
  ctx.core.emit['math.max'] = (a, b, ...rest) => {
72
+ if (a === undefined) return typed(['f64.const', -Infinity], 'f64')
67
73
  if (!b && Array.isArray(a) && a[0] === '...') return emitArrayReduce('f64.max', a[1], -Infinity)
68
- return f2('f64.max', a, b)
74
+ if (b === undefined) return typed(['f64.max', asF64(emit(a)), ['f64.const', -Infinity]], 'f64')
75
+ let r = f2('f64.max', a, b)
76
+ for (const x of rest) r = typed(['f64.max', r, asF64(emit(x))], 'f64')
77
+ return r
69
78
  }
70
79
  ctx.core.emit['math.round'] = a => fInt('f64.nearest', a)
71
80
  ctx.core.emit['math.fround'] = a => typed(['f64.promote_f32', ['f32.demote_f64', asF64(emit(a))]], 'f64')
@@ -106,7 +115,16 @@ export default (ctx) => {
106
115
  ctx.core.emit['math.pow'] = (a, b) => callDeps(['math.exp', 'math.log', 'math.pow'], 'math.pow', a, b)
107
116
  ctx.core.emit['**'] = ctx.core.emit['math.pow']
108
117
  ctx.core.emit['math.cbrt'] = a => callDeps(['math.exp', 'math.log', 'math.pow', 'math.cbrt'], 'math.cbrt', a)
109
- ctx.core.emit['math.hypot'] = (a, b) => call('math.hypot', a, b)
118
+ ctx.core.emit['math.hypot'] = (a, b, ...rest) => {
119
+ if (a === undefined) return typed(['f64.const', 0], 'f64')
120
+ if (b === undefined) return f('f64.abs', a)
121
+ let r = call('math.hypot', a, b)
122
+ for (const x of rest) {
123
+ inc('math.hypot')
124
+ r = typed(['call', '$math.hypot', r, asF64(emit(x))], 'f64')
125
+ }
126
+ return r
127
+ }
110
128
 
111
129
  // Integer/bit operations: return i32 directly. Consumers `asF64`-rebox at
112
130
  // store/return boundaries; consumers staying in i32 (bit chains, i32 locals)
@@ -169,6 +187,7 @@ export default (ctx) => {
169
187
 
170
188
  ctx.core.stdlib['math.exp'] = `(func $math.exp (param $x f64) (result f64)
171
189
  (local $k i32) (local $t f64) (local $t2 f64) (local $result f64) (local $pow2 f64)
190
+ (if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
172
191
  (if (result f64) (f64.gt (local.get $x) (f64.const 709.0)) (then (f64.const 1.7976931348623157e+308)) (else
173
192
  (if (result f64) (f64.lt (local.get $x) (f64.const -745.0)) (then (f64.const 0.0)) (else
174
193
  (local.set $k (i32.trunc_f64_s (f64.div (local.get $x) (f64.const ${Math.LN2}))))
@@ -246,42 +265,64 @@ export default (ctx) => {
246
265
 
247
266
  ctx.core.stdlib['math.pow'] = `(func $math.pow (param $x f64) (param $y f64) (result f64)
248
267
  (local $result f64) (local $n i32) (local $neg_base i32) (local $abs_x f64)
249
- (if (result f64) (f64.eq (local.get $y) (f64.const 0.0))
250
- (then (f64.const 1.0))
251
- (else (if (result f64) (f64.eq (local.get $x) (f64.const 0.0))
252
- (then (f64.const 0.0))
253
- (else (if (result f64) (f64.eq (local.get $x) (f64.const 1.0))
254
- (then (f64.const 1.0))
255
- (else (if (result f64) (f64.eq (local.get $y) (f64.const 1.0))
256
- (then (local.get $x))
257
- (else
258
- (if (result f64)
259
- (i32.and
260
- (f64.eq (f64.nearest (local.get $y)) (local.get $y))
261
- (f64.le (f64.abs (local.get $y)) (f64.const 100.0)))
262
- (then
263
- (local.set $abs_x (f64.abs (local.get $x)))
264
- (local.set $neg_base (i32.and (f64.lt (local.get $x) (f64.const 0.0))
265
- (i32.and (i32.trunc_f64_s (local.get $y)) (i32.const 1))))
266
- (local.set $n (i32.trunc_f64_s (f64.abs (local.get $y))))
267
- (local.set $result (f64.const 1.0))
268
- (block $done
269
- (loop $loop
270
- (br_if $done (i32.le_s (local.get $n) (i32.const 0)))
271
- (if (i32.and (local.get $n) (i32.const 1))
272
- (then (local.set $result (f64.mul (local.get $result) (local.get $abs_x)))))
273
- (local.set $abs_x (f64.mul (local.get $abs_x) (local.get $abs_x)))
274
- (local.set $n (i32.shr_s (local.get $n) (i32.const 1)))
275
- (br $loop)))
276
- (if (f64.lt (local.get $y) (f64.const 0.0))
277
- (then (local.set $result (f64.div (f64.const 1.0) (local.get $result)))))
278
- (if (local.get $neg_base)
279
- (then (local.set $result (f64.neg (local.get $result)))))
280
- (local.get $result))
281
- (else
282
- (if (result f64) (f64.lt (local.get $x) (f64.const 0.0))
283
- (then (f64.const 0.0))
284
- (else (call $math.exp (f64.mul (local.get $y) (call $math.log (local.get $x)))))))))))))))))`
268
+ ;; y == 0 -> 1 (covers pow(NaN,0), pow(±0,0), pow(±Inf,0))
269
+ (if (f64.eq (local.get $y) (f64.const 0.0)) (then (return (f64.const 1.0))))
270
+ ;; y is NaN -> NaN
271
+ (if (f64.ne (local.get $y) (local.get $y)) (then (return (local.get $y))))
272
+ ;; x is NaN -> NaN
273
+ (if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $x))))
274
+ ;; y is ±Infinity
275
+ (if (f64.eq (f64.abs (local.get $y)) (f64.const inf))
276
+ (then
277
+ (local.set $abs_x (f64.abs (local.get $x)))
278
+ (if (f64.eq (local.get $abs_x) (f64.const 1.0))
279
+ (then (return (f64.div (f64.const 0.0) (f64.const 0.0)))))
280
+ (if (i32.eq (f64.gt (local.get $abs_x) (f64.const 1.0))
281
+ (f64.gt (local.get $y) (f64.const 0.0)))
282
+ (then (return (f64.const inf)))
283
+ (else (return (f64.const 0.0))))))
284
+ ;; x == 1 -> 1 (after y=±Inf check, so 1**Inf already returned NaN)
285
+ (if (f64.eq (local.get $x) (f64.const 1.0)) (then (return (f64.const 1.0))))
286
+ ;; y == 1 -> x (preserves -0 for (-0)**1)
287
+ (if (f64.eq (local.get $y) (f64.const 1.0)) (then (return (local.get $x))))
288
+ ;; integer fast path: y integer in i32 range. Binary exponentiation is
289
+ ;; O(log |n|) so the bound only matters for i32.trunc_f64_s safety.
290
+ ;; Also covers ±Infinity x: abs_x stays Inf through the loop, 1/Inf=0,
291
+ ;; with neg_base (x<0 && odd y) producing -0 — required for (-Inf)**-odd.
292
+ ;; Runs before the x==0 fallback so (-0)**oddInt correctly returns ∓0/∓Inf.
293
+ (if (i32.and
294
+ (f64.eq (f64.nearest (local.get $y)) (local.get $y))
295
+ (f64.lt (f64.abs (local.get $y)) (f64.const 2147483648.0)))
296
+ (then
297
+ (local.set $abs_x (f64.abs (local.get $x)))
298
+ ;; copysign(1, x) gives -1 for any x with sign bit set (incl. -0); f64.lt picks that up.
299
+ (local.set $neg_base (i32.and (f64.lt (f64.copysign (f64.const 1.0) (local.get $x)) (f64.const 0.0))
300
+ (i32.and (i32.trunc_f64_s (local.get $y)) (i32.const 1))))
301
+ (local.set $n (i32.trunc_f64_s (f64.abs (local.get $y))))
302
+ (local.set $result (f64.const 1.0))
303
+ (block $done
304
+ (loop $loop
305
+ (br_if $done (i32.le_s (local.get $n) (i32.const 0)))
306
+ (if (i32.and (local.get $n) (i32.const 1))
307
+ (then (local.set $result (f64.mul (local.get $result) (local.get $abs_x)))))
308
+ (local.set $abs_x (f64.mul (local.get $abs_x) (local.get $abs_x)))
309
+ (local.set $n (i32.shr_s (local.get $n) (i32.const 1)))
310
+ (br $loop)))
311
+ (if (f64.lt (local.get $y) (f64.const 0.0))
312
+ (then (local.set $result (f64.div (f64.const 1.0) (local.get $result)))))
313
+ (if (local.get $neg_base)
314
+ (then (local.set $result (f64.neg (local.get $result)))))
315
+ (return (local.get $result))))
316
+ ;; x == 0 with non-integer y -> y<0 ? Infinity : 0 (sign-of-zero only matters for integer y, handled above)
317
+ (if (f64.eq (local.get $x) (f64.const 0.0))
318
+ (then
319
+ (if (f64.lt (local.get $y) (f64.const 0.0))
320
+ (then (return (f64.const inf)))
321
+ (else (return (f64.const 0.0))))))
322
+ ;; x < 0, non-integer finite y -> NaN
323
+ (if (f64.lt (local.get $x) (f64.const 0.0))
324
+ (then (return (f64.div (f64.const 0.0) (f64.const 0.0)))))
325
+ (call $math.exp (f64.mul (local.get $y) (call $math.log (local.get $x)))))`
285
326
 
286
327
  ctx.core.stdlib['math.atan'] = `(func $math.atan (param $x f64) (result f64)
287
328
  (local $x2 f64) (local $abs_x f64) (local $reduced f64)
package/module/number.js CHANGED
@@ -9,28 +9,48 @@
9
9
  * @module number
10
10
  */
11
11
 
12
- import { emit, typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, valTypeOf, VAL, temp, tempI32, tempI64 } from '../src/compile.js'
12
+ import { typed, asF64, asI32, asI64, toNumF64, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64 } from '../src/ir.js'
13
+ import { emit, isReassigned } from '../src/emit.js'
14
+ import { valTypeOf, VAL } from '../src/analyze.js'
13
15
  import { inc, PTR } from '../src/ctx.js'
14
16
 
15
17
  export default (ctx) => {
16
18
  Object.assign(ctx.core.stdlibDeps, {
17
19
  __mkstr: ['__alloc'],
18
- __ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str'],
20
+ __ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str', '__toExp'],
19
21
  __toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
20
- __to_num: ['__char_at', '__str_byteLen', '__pow10'],
22
+ __to_num: ['__char_at', '__str_byteLen', '__pow10', '__to_str'],
23
+ __to_bigint: ['__char_at', '__str_byteLen'],
21
24
  __parseInt: ['__char_at', '__str_byteLen'],
22
25
  })
23
26
 
24
27
 
25
- // __pow10(n: i32) → f64 — compute 10^n via loop
28
+ // __pow10(n: i32) → f64 — compute 10^n via binary decomposition.
29
+ // Naive iterative `r *= 10` accumulates O(n) ULPs of rounding drift —
30
+ // 1e308 came out 1 ULP low, breaking parseFloat round-trip at the f64 edge.
31
+ // Bit-decomposition multiplies at most 9 precomputed powers (10^1 .. 10^256),
32
+ // so accumulated error stays at O(log n) ULPs.
26
33
  ctx.core.stdlib['__pow10'] = `(func $__pow10 (param $n i32) (result f64)
27
34
  (local $r f64)
28
35
  (local.set $r (f64.const 1))
29
- (block $d (loop $l
30
- (br_if $d (i32.le_s (local.get $n) (i32.const 0)))
31
- (local.set $r (f64.mul (local.get $r) (f64.const 10)))
32
- (local.set $n (i32.sub (local.get $n) (i32.const 1)))
33
- (br $l)))
36
+ (if (i32.and (local.get $n) (i32.const 1))
37
+ (then (local.set $r (f64.mul (local.get $r) (f64.const 10)))))
38
+ (if (i32.and (local.get $n) (i32.const 2))
39
+ (then (local.set $r (f64.mul (local.get $r) (f64.const 100)))))
40
+ (if (i32.and (local.get $n) (i32.const 4))
41
+ (then (local.set $r (f64.mul (local.get $r) (f64.const 10000)))))
42
+ (if (i32.and (local.get $n) (i32.const 8))
43
+ (then (local.set $r (f64.mul (local.get $r) (f64.const 1e8)))))
44
+ (if (i32.and (local.get $n) (i32.const 16))
45
+ (then (local.set $r (f64.mul (local.get $r) (f64.const 1e16)))))
46
+ (if (i32.and (local.get $n) (i32.const 32))
47
+ (then (local.set $r (f64.mul (local.get $r) (f64.const 1e32)))))
48
+ (if (i32.and (local.get $n) (i32.const 64))
49
+ (then (local.set $r (f64.mul (local.get $r) (f64.const 1e64)))))
50
+ (if (i32.and (local.get $n) (i32.const 128))
51
+ (then (local.set $r (f64.mul (local.get $r) (f64.const 1e128)))))
52
+ (if (i32.and (local.get $n) (i32.const 256))
53
+ (then (local.set $r (f64.mul (local.get $r) (f64.const 1e256)))))
34
54
  (local.get $r))`
35
55
 
36
56
  // __itoa(val: i32, buf: i32) → i32 (digit count). Writes decimal digits to buf.
@@ -83,6 +103,17 @@ export default (ctx) => {
83
103
  (if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
84
104
  (if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
85
105
  (if (f64.eq (local.get $val) (f64.const -inf)) (then (return (call $__static_str (i32.const 2)))))
106
+ ;; ES spec: |x| >= 1e21 or 0 < |x| < 1e-6 → exponential notation (default mode only).
107
+ ;; Cap prec at 9: mantissa fits i32, avoiding trunc_f64_u trap. Fewer digits than
108
+ ;; ECMAScript shortest-repr ideal, but valid output for very large/small numbers.
109
+ (if (i32.eqz (local.get $mode))
110
+ (then
111
+ (if (f64.ge (f64.abs (local.get $val)) (f64.const 1e21))
112
+ (then (return (call $__toExp (local.get $val) (i32.const 9)))))
113
+ (if (i32.and
114
+ (f64.gt (f64.abs (local.get $val)) (f64.const 0))
115
+ (f64.lt (f64.abs (local.get $val)) (f64.const 1e-6)))
116
+ (then (return (call $__toExp (local.get $val) (i32.const 9)))))))
86
117
  (local.set $buf (call $__alloc (i32.const 40)))
87
118
  ;; Sign
88
119
  (if (f64.lt (local.get $val) (f64.const 0))
@@ -291,19 +322,17 @@ export default (ctx) => {
291
322
 
292
323
  // Global isNaN/isFinite — coerce string→number first (unlike Number.isNaN/isFinite)
293
324
  ctx.core.emit['isNaN'] = (x) => {
294
- inc('__to_num')
295
- const v = asF64(emit(x))
325
+ const v = toNumF64(x, emit(x))
296
326
  const t = temp('t')
297
327
  return typed(['f64.ne',
298
- ['local.tee', `$${t}`, ['call', '$__to_num', v]],
328
+ ['local.tee', `$${t}`, v],
299
329
  ['local.get', `$${t}`]], 'i32')
300
330
  }
301
331
  ctx.core.emit['isFinite'] = (x) => {
302
- inc('__to_num')
303
- const v = asF64(emit(x))
332
+ const v = toNumF64(x, emit(x))
304
333
  const t = temp('t')
305
334
  return typed(['i32.and',
306
- ['f64.eq', ['local.tee', `$${t}`, ['call', '$__to_num', v]], ['local.get', `$${t}`]],
335
+ ['f64.eq', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
307
336
  ['f64.lt', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', Infinity]]], 'i32')
308
337
  }
309
338
 
@@ -318,15 +347,14 @@ export default (ctx) => {
318
347
  }
319
348
 
320
349
  // parseInt(str, radix) — parse string to integer
321
- ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str f64) (param $radix i32) (result f64)
350
+ ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str i64) (param $radix i32) (result f64)
322
351
  (local $off i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
323
- (local $result f64) (local $digit i32) (local $seen i32)
352
+ (local $result f64) (local $digit i32) (local $seen i32) (local $f f64)
353
+ (local.set $f (f64.reinterpret_i64 (local.get $str)))
324
354
  ;; If input is a number, just truncate
325
- (if (f64.eq (local.get $str) (local.get $str)) (then (return (f64.trunc (local.get $str)))))
326
- ;; If NaN-boxed but not a string type (4=heap,5=SSO) → return NaN
327
- (if (i32.and
328
- (i32.ne (call $__ptr_type (local.get $str)) (i32.const 4))
329
- (i32.ne (call $__ptr_type (local.get $str)) (i32.const 5)))
355
+ (if (f64.eq (local.get $f) (local.get $f)) (then (return (f64.trunc (local.get $f)))))
356
+ ;; If NaN-boxed but not a string → return NaN
357
+ (if (i32.ne (call $__ptr_type (local.get $str)) (i32.const 4))
330
358
  (then (return (f64.const nan))))
331
359
  (local.set $off (call $__ptr_offset (local.get $str)))
332
360
  (local.set $len (call $__str_byteLen (local.get $str)))
@@ -374,19 +402,25 @@ export default (ctx) => {
374
402
  (if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
375
403
  (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
376
404
 
377
- ctx.core.stdlib['__to_num'] = `(func $__to_num (param $v f64) (result f64)
405
+ ctx.core.stdlib['__to_num'] = `(func $__to_num (param $v i64) (result f64)
378
406
  (local $t i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
379
- (local $seen i32) (local $exp i32) (local $expNeg i32)
380
- (local $result f64) (local $scale f64)
381
- (if (f64.eq (local.get $v) (local.get $v)) (then (return (local.get $v))))
382
- (if (i64.eq (i64.reinterpret_f64 (local.get $v)) (i64.const ${NULL_NAN})) (then (return (f64.const 0))))
383
- (if (i64.eq (i64.reinterpret_f64 (local.get $v)) (i64.const ${UNDEF_NAN})) (then (return (f64.const nan))))
407
+ (local $seen i32) (local $exp i32) (local $expNeg i32) (local $expDigits i32)
408
+ (local $dot i32) (local $sigDigits i32) (local $decExp i32) (local $dropped i32) (local $round i32)
409
+ (local $wsEnd i32)
410
+ (local $result f64) (local $f f64)
411
+ (local.set $f (f64.reinterpret_i64 (local.get $v)))
412
+ (if (f64.eq (local.get $f) (local.get $f)) (then (return (local.get $f))))
413
+ (if (i64.eq (local.get $v) (i64.const ${NULL_NAN})) (then (return (f64.const 0))))
414
+ (if (i64.eq (local.get $v) (i64.const ${UNDEF_NAN})) (then (return (f64.const nan))))
384
415
  (local.set $t (call $__ptr_type (local.get $v)))
385
- (if (i32.eqz
386
- (i32.or
387
- (i32.eq (local.get $t) (i32.const ${PTR.STRING}))
388
- (i32.eq (local.get $t) (i32.const ${PTR.SSO}))))
389
- (then (return (f64.const nan))))
416
+ ;; Non-string values go through ToString per JS spec, then re-check the
417
+ ;; type in case ToString itself returned a non-string sentinel.
418
+ (if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
419
+ (then
420
+ (local.set $v (call $__to_str (local.get $v)))
421
+ (local.set $t (call $__ptr_type (local.get $v)))
422
+ (if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
423
+ (then (return (f64.const nan))))))
390
424
  (local.set $len (call $__str_byteLen (local.get $v)))
391
425
  ;; Skip leading whitespace.
392
426
  (block $ws (loop $wsl
@@ -394,6 +428,7 @@ export default (ctx) => {
394
428
  (br_if $ws (i32.gt_s (call $__char_at (local.get $v) (local.get $i)) (i32.const 32)))
395
429
  (local.set $i (i32.add (local.get $i) (i32.const 1)))
396
430
  (br $wsl)))
431
+ (local.set $wsEnd (local.get $i))
397
432
  ;; Sign.
398
433
  (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
399
434
  (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
@@ -422,45 +457,51 @@ export default (ctx) => {
422
457
  (then (local.set $result (f64.add (f64.mul (local.get $result) (f64.const 16)) (f64.convert_i32_s (i32.sub (local.get $c) (i32.const 55)))))
423
458
  (local.set $seen (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $hexLoop)))))
424
459
  (return (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))))
425
- ;; Integer part.
426
- (block $intDone (loop $intLoop
427
- (br_if $intDone (i32.ge_s (local.get $i) (local.get $len)))
460
+ ;; Decimal significand. Keep 17 significant decimal digits, track the
461
+ ;; base-10 exponent for skipped digits, and round once before pow10 scaling.
462
+ (block $numDone (loop $numLoop
463
+ (br_if $numDone (i32.ge_s (local.get $i) (local.get $len)))
428
464
  (local.set $c (call $__char_at (local.get $v) (local.get $i)))
429
- (br_if $intDone
465
+ (if (i32.and (i32.eq (local.get $c) (i32.const 46)) (i32.eqz (local.get $dot)))
466
+ (then
467
+ (local.set $dot (i32.const 1))
468
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
469
+ (br $numLoop)))
470
+ (br_if $numDone
430
471
  (i32.or
431
472
  (i32.lt_s (local.get $c) (i32.const 48))
432
473
  (i32.gt_s (local.get $c) (i32.const 57))))
433
- (local.set $result
434
- (f64.add
435
- (f64.mul (local.get $result) (f64.const 10))
436
- (f64.convert_i32_s (i32.sub (local.get $c) (i32.const 48)))))
437
474
  (local.set $seen (i32.const 1))
438
- (local.set $i (i32.add (local.get $i) (i32.const 1)))
439
- (br $intLoop)))
440
- ;; Fractional part.
441
- (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
442
- (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 46)))
443
- (then
444
- (local.set $i (i32.add (local.get $i) (i32.const 1)))
445
- (local.set $scale (f64.const 0.1))
446
- (block $fracDone (loop $fracLoop
447
- (br_if $fracDone (i32.ge_s (local.get $i) (local.get $len)))
448
- (local.set $c (call $__char_at (local.get $v) (local.get $i)))
449
- (br_if $fracDone
450
- (i32.or
451
- (i32.lt_s (local.get $c) (i32.const 48))
452
- (i32.gt_s (local.get $c) (i32.const 57))))
475
+ (local.set $c (i32.sub (local.get $c) (i32.const 48)))
476
+ (if (i32.and (i32.eqz (local.get $sigDigits)) (i32.eqz (local.get $c)))
477
+ (then
478
+ (if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1)))))
479
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
480
+ (br $numLoop)))
481
+ (if (i32.lt_s (local.get $sigDigits)
482
+ (if (result i32) (local.get $dot) (then (i32.const 16)) (else (i32.const 17))))
483
+ (then
453
484
  (local.set $result
454
485
  (f64.add
455
- (local.get $result)
456
- (f64.mul
457
- (f64.convert_i32_s (i32.sub (local.get $c) (i32.const 48)))
458
- (local.get $scale))))
459
- (local.set $scale (f64.mul (local.get $scale) (f64.const 0.1)))
460
- (local.set $seen (i32.const 1))
461
- (local.set $i (i32.add (local.get $i) (i32.const 1)))
462
- (br $fracLoop)))))
463
- (if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
486
+ (f64.mul (local.get $result) (f64.const 10))
487
+ (f64.convert_i32_s (local.get $c))))
488
+ (local.set $sigDigits (i32.add (local.get $sigDigits) (i32.const 1)))
489
+ (if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1))))))
490
+ (else
491
+ (if (i32.eqz (local.get $dropped))
492
+ (then (if (i32.ge_s (local.get $c) (i32.const 5)) (then (local.set $round (i32.const 1))))))
493
+ (local.set $dropped (i32.const 1))
494
+ (if (i32.eqz (local.get $dot)) (then (local.set $decExp (i32.add (local.get $decExp) (i32.const 1)))))))
495
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
496
+ (br $numLoop)))
497
+ ;; No digits seen: empty/whitespace-only string coerces to 0; non-numeric
498
+ ;; content (sign-only, "abc", etc.) coerces to NaN.
499
+ (if (i32.eqz (local.get $seen))
500
+ (then
501
+ (if (i32.ge_s (local.get $wsEnd) (local.get $len))
502
+ (then (return (f64.const 0))))
503
+ (return (f64.const nan))))
504
+ (if (local.get $round) (then (local.set $result (f64.add (local.get $result) (f64.const 1)))))
464
505
  ;; Scientific notation.
465
506
  (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
466
507
  (i32.or
@@ -485,28 +526,88 @@ export default (ctx) => {
485
526
  (i32.add
486
527
  (i32.mul (local.get $exp) (i32.const 10))
487
528
  (i32.sub (local.get $c) (i32.const 48))))
529
+ (local.set $expDigits (i32.add (local.get $expDigits) (i32.const 1)))
488
530
  (local.set $i (i32.add (local.get $i) (i32.const 1)))
489
531
  (br $expLoop)))
490
- (if (local.get $expNeg)
491
- (then (local.set $result (f64.div (local.get $result) (call $__pow10 (local.get $exp)))))
492
- (else (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $exp))))))))
532
+ (if (local.get $expDigits)
533
+ (then
534
+ (if (local.get $expNeg)
535
+ (then (local.set $decExp (i32.sub (local.get $decExp) (local.get $exp))))
536
+ (else (local.set $decExp (i32.add (local.get $decExp) (local.get $exp)))))))))
537
+ (if (i32.gt_s (local.get $decExp) (i32.const 0))
538
+ (then (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $decExp))))))
539
+ (if (i32.lt_s (local.get $decExp) (i32.const 0))
540
+ (then (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const 0) (local.get $decExp)))))))
493
541
  (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
494
542
 
543
+ ctx.core.stdlib['__to_bigint'] = `(func $__to_bigint (param $v i64) (result f64)
544
+ (local $t i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
545
+ (local $radix i32) (local $digit i32) (local $seen i32) (local $result i64) (local $f f64)
546
+ (local.set $f (f64.reinterpret_i64 (local.get $v)))
547
+ (if (f64.eq (local.get $f) (local.get $f))
548
+ (then (return (f64.reinterpret_i64 (i64.trunc_sat_f64_s (local.get $f))))))
549
+ (local.set $t (call $__ptr_type (local.get $v)))
550
+ (if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
551
+ (then (return (f64.reinterpret_i64 (i64.const 0)))))
552
+ (local.set $len (call $__str_byteLen (local.get $v)))
553
+ (block $ws (loop $wsl
554
+ (br_if $ws (i32.ge_s (local.get $i) (local.get $len)))
555
+ (br_if $ws (i32.gt_s (call $__char_at (local.get $v) (local.get $i)) (i32.const 32)))
556
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
557
+ (br $wsl)))
558
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
559
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
560
+ (then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
561
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
562
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
563
+ (then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
564
+ (local.set $radix (i32.const 10))
565
+ (if (i32.and
566
+ (i32.lt_s (i32.add (local.get $i) (i32.const 1)) (local.get $len))
567
+ (i32.and (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 48))
568
+ (i32.or (i32.eq (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 120))
569
+ (i32.eq (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 88)))))
570
+ (then (local.set $radix (i32.const 16)) (local.set $i (i32.add (local.get $i) (i32.const 2)))))
571
+ (block $done (loop $lp
572
+ (br_if $done (i32.ge_s (local.get $i) (local.get $len)))
573
+ (local.set $c (call $__char_at (local.get $v) (local.get $i)))
574
+ (local.set $digit (i32.const -1))
575
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 48)) (i32.le_s (local.get $c) (i32.const 57)))
576
+ (then (local.set $digit (i32.sub (local.get $c) (i32.const 48)))))
577
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 97)) (i32.le_s (local.get $c) (i32.const 122)))
578
+ (then (local.set $digit (i32.sub (local.get $c) (i32.const 87)))))
579
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 65)) (i32.le_s (local.get $c) (i32.const 90)))
580
+ (then (local.set $digit (i32.sub (local.get $c) (i32.const 55)))))
581
+ (br_if $done (i32.or (i32.lt_s (local.get $digit) (i32.const 0)) (i32.ge_s (local.get $digit) (local.get $radix))))
582
+ (local.set $seen (i32.const 1))
583
+ (local.set $result
584
+ (i64.add
585
+ (i64.mul (local.get $result) (i64.extend_i32_s (local.get $radix)))
586
+ (i64.extend_i32_s (local.get $digit))))
587
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
588
+ (br $lp)))
589
+ (if (i32.eqz (local.get $seen)) (then (return (f64.reinterpret_i64 (i64.const 0)))))
590
+ (f64.reinterpret_i64
591
+ (if (result i64) (local.get $neg)
592
+ (then (i64.sub (i64.const 0) (local.get $result)))
593
+ (else (local.get $result)))))`
594
+
495
595
  ctx.core.emit['Number.parseInt'] = (x, radix) => {
496
596
  inc('__parseInt')
497
- return typed(['call', '$__parseInt', asF64(emit(x)), radix ? asI32(emit(radix)) : ['i32.const', 0]], 'f64')
597
+ return typed(['call', '$__parseInt', asI64(emit(x)), radix ? asI32(emit(radix)) : ['i32.const', 0]], 'f64')
498
598
  }
499
599
  ctx.core.emit['parseInt'] = ctx.core.emit['Number.parseInt']
500
600
  ctx.core.emit['Number.parseFloat'] = (x) => {
501
601
  inc('__to_num')
502
- return typed(['call', '$__to_num', asF64(emit(x))], 'f64')
602
+ return typed(['call', '$__to_num', asI64(emit(x))], 'f64')
503
603
  }
504
604
  ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']
505
605
 
506
606
  // Boolean(x) → truthiness (non-zero → 1, zero → 0)
507
607
  ctx.core.emit['Boolean'] = (x) => {
608
+ if (x === undefined) return typed(['f64.const', 0], 'f64')
508
609
  inc('__is_truthy')
509
- const v = asF64(emit(x))
610
+ const v = asI64(emit(x))
510
611
  return typed(['if', ['result', 'f64'], ['call', '$__is_truthy', v], ['then', ['f64.const', 1]], ['else', ['f64.const', 0]]], 'f64')
511
612
  }
512
613
 
@@ -557,18 +658,12 @@ export default (ctx) => {
557
658
  ['i32.const', 1]]]]], 'f64')
558
659
  }
559
660
 
560
- ctx.core.emit['String'] = (x) => {
561
- inc('__ftoa')
562
- if (Array.isArray(x) && x[0] === 'str') return emit(x)
563
- return typed(['call', '$__ftoa', asF64(emit(x)), ['i32.const', 0], ['i32.const', 0]], 'f64')
564
- }
565
-
566
661
  // Number(x) — identity for numbers, i64→f64 conversion for BigInt
567
662
  ctx.core.emit['Number'] = (x) => {
663
+ if (x === undefined) return typed(['f64.const', 0], 'f64')
568
664
  if (valTypeOf(x) === VAL.BIGINT)
569
665
  return typed(['f64.convert_i64_s', asI64(emit(x))], 'f64')
570
- inc('__to_num')
571
- return typed(['call', '$__to_num', asF64(emit(x))], 'f64')
666
+ return toNumF64(x, emit(x))
572
667
  }
573
668
 
574
669
  // BigInt(x) — f64→i64 conversion (reinterpret as BigInt-as-f64).
@@ -576,12 +671,23 @@ export default (ctx) => {
576
671
  // (handles both decimal and hex string parse), then truncate.
577
672
  ctx.core.emit['BigInt'] = (x) => {
578
673
  const vt = valTypeOf(x)
579
- if (vt === VAL.BIGINT) return emit(x)
674
+ if (vt === VAL.BIGINT) {
675
+ if (typeof x === 'bigint' || (typeof x === 'string' && !isReassigned(ctx.func.body, x))) return emit(x)
676
+ inc('__to_bigint', '__ptr_type')
677
+ const t = temp('bi')
678
+ return typed(['block', ['result', 'f64'],
679
+ ['local.set', `$${t}`, asF64(emit(x))],
680
+ ['if', ['result', 'f64'], ['f64.eq', ['local.get', `$${t}`], ['local.get', `$${t}`]],
681
+ ['then', ['f64.reinterpret_i64', ['i64.trunc_sat_f64_s', ['local.get', `$${t}`]]]],
682
+ ['else', ['if', ['result', 'f64'],
683
+ ['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]], ['i32.const', PTR.STRING]],
684
+ ['then', ['call', '$__to_bigint', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
685
+ ['else', ['local.get', `$${t}`]]]]]], 'f64')
686
+ }
580
687
  if (vt === VAL.NUMBER)
581
688
  return typed(['f64.reinterpret_i64', ['i64.trunc_sat_f64_s', asF64(emit(x))]], 'f64')
582
- inc('__to_num')
583
- return typed(['f64.reinterpret_i64',
584
- ['i64.trunc_sat_f64_s', ['call', '$__to_num', asF64(emit(x))]]], 'f64')
689
+ inc('__to_bigint')
690
+ return typed(['call', '$__to_bigint', asI64(emit(x))], 'f64')
585
691
  }
586
692
 
587
693
  // BigInt.asIntN(bits, bigint) — truncate to signed N-bit