jz 0.1.1 → 0.2.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/module/math.js CHANGED
@@ -60,13 +60,21 @@ export default (ctx) => {
60
60
  ctx.core.emit['math.ceil'] = a => fInt('f64.ceil', a)
61
61
  ctx.core.emit['math.trunc'] = a => fInt('f64.trunc', a)
62
62
  ctx.core.emit['math.min'] = (a, b, ...rest) => {
63
+ if (a === undefined) return typed(['f64.const', Infinity], 'f64')
63
64
  // Spread: Math.min(...arr) — iterate array to find min
64
65
  if (!b && Array.isArray(a) && a[0] === '...') return emitArrayReduce('f64.min', a[1], Infinity)
65
- 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
66
70
  }
67
71
  ctx.core.emit['math.max'] = (a, b, ...rest) => {
72
+ if (a === undefined) return typed(['f64.const', -Infinity], 'f64')
68
73
  if (!b && Array.isArray(a) && a[0] === '...') return emitArrayReduce('f64.max', a[1], -Infinity)
69
- 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
70
78
  }
71
79
  ctx.core.emit['math.round'] = a => fInt('f64.nearest', a)
72
80
  ctx.core.emit['math.fround'] = a => typed(['f64.promote_f32', ['f32.demote_f64', asF64(emit(a))]], 'f64')
@@ -107,7 +115,16 @@ export default (ctx) => {
107
115
  ctx.core.emit['math.pow'] = (a, b) => callDeps(['math.exp', 'math.log', 'math.pow'], 'math.pow', a, b)
108
116
  ctx.core.emit['**'] = ctx.core.emit['math.pow']
109
117
  ctx.core.emit['math.cbrt'] = a => callDeps(['math.exp', 'math.log', 'math.pow', 'math.cbrt'], 'math.cbrt', a)
110
- 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
+ }
111
128
 
112
129
  // Integer/bit operations: return i32 directly. Consumers `asF64`-rebox at
113
130
  // store/return boundaries; consumers staying in i32 (bit chains, i32 locals)
@@ -170,6 +187,7 @@ export default (ctx) => {
170
187
 
171
188
  ctx.core.stdlib['math.exp'] = `(func $math.exp (param $x f64) (result f64)
172
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))))
173
191
  (if (result f64) (f64.gt (local.get $x) (f64.const 709.0)) (then (f64.const 1.7976931348623157e+308)) (else
174
192
  (if (result f64) (f64.lt (local.get $x) (f64.const -745.0)) (then (f64.const 0.0)) (else
175
193
  (local.set $k (i32.trunc_f64_s (f64.div (local.get $x) (f64.const ${Math.LN2}))))
@@ -247,42 +265,64 @@ export default (ctx) => {
247
265
 
248
266
  ctx.core.stdlib['math.pow'] = `(func $math.pow (param $x f64) (param $y f64) (result f64)
249
267
  (local $result f64) (local $n i32) (local $neg_base i32) (local $abs_x f64)
250
- (if (result f64) (f64.eq (local.get $y) (f64.const 0.0))
251
- (then (f64.const 1.0))
252
- (else (if (result f64) (f64.eq (local.get $x) (f64.const 0.0))
253
- (then (f64.const 0.0))
254
- (else (if (result f64) (f64.eq (local.get $x) (f64.const 1.0))
255
- (then (f64.const 1.0))
256
- (else (if (result f64) (f64.eq (local.get $y) (f64.const 1.0))
257
- (then (local.get $x))
258
- (else
259
- (if (result f64)
260
- (i32.and
261
- (f64.eq (f64.nearest (local.get $y)) (local.get $y))
262
- (f64.le (f64.abs (local.get $y)) (f64.const 100.0)))
263
- (then
264
- (local.set $abs_x (f64.abs (local.get $x)))
265
- (local.set $neg_base (i32.and (f64.lt (local.get $x) (f64.const 0.0))
266
- (i32.and (i32.trunc_f64_s (local.get $y)) (i32.const 1))))
267
- (local.set $n (i32.trunc_f64_s (f64.abs (local.get $y))))
268
- (local.set $result (f64.const 1.0))
269
- (block $done
270
- (loop $loop
271
- (br_if $done (i32.le_s (local.get $n) (i32.const 0)))
272
- (if (i32.and (local.get $n) (i32.const 1))
273
- (then (local.set $result (f64.mul (local.get $result) (local.get $abs_x)))))
274
- (local.set $abs_x (f64.mul (local.get $abs_x) (local.get $abs_x)))
275
- (local.set $n (i32.shr_s (local.get $n) (i32.const 1)))
276
- (br $loop)))
277
- (if (f64.lt (local.get $y) (f64.const 0.0))
278
- (then (local.set $result (f64.div (f64.const 1.0) (local.get $result)))))
279
- (if (local.get $neg_base)
280
- (then (local.set $result (f64.neg (local.get $result)))))
281
- (local.get $result))
282
- (else
283
- (if (result f64) (f64.lt (local.get $x) (f64.const 0.0))
284
- (then (f64.const 0.0))
285
- (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)))))`
286
326
 
287
327
  ctx.core.stdlib['math.atan'] = `(func $math.atan (param $x f64) (result f64)
288
328
  (local $x2 f64) (local $abs_x f64) (local $reduced f64)
package/module/number.js CHANGED
@@ -9,30 +9,48 @@
9
9
  * @module number
10
10
  */
11
11
 
12
- import { typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64 } from '../src/ir.js'
13
- import { emit } from '../src/emit.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
14
  import { valTypeOf, VAL } from '../src/analyze.js'
15
15
  import { inc, PTR } from '../src/ctx.js'
16
16
 
17
17
  export default (ctx) => {
18
18
  Object.assign(ctx.core.stdlibDeps, {
19
19
  __mkstr: ['__alloc'],
20
- __ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str'],
20
+ __ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str', '__toExp'],
21
21
  __toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
22
- __to_num: ['__char_at', '__str_byteLen', '__pow10'],
22
+ __to_num: ['__char_at', '__str_byteLen', '__pow10', '__to_str'],
23
+ __to_bigint: ['__char_at', '__str_byteLen'],
23
24
  __parseInt: ['__char_at', '__str_byteLen'],
24
25
  })
25
26
 
26
27
 
27
- // __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.
28
33
  ctx.core.stdlib['__pow10'] = `(func $__pow10 (param $n i32) (result f64)
29
34
  (local $r f64)
30
35
  (local.set $r (f64.const 1))
31
- (block $d (loop $l
32
- (br_if $d (i32.le_s (local.get $n) (i32.const 0)))
33
- (local.set $r (f64.mul (local.get $r) (f64.const 10)))
34
- (local.set $n (i32.sub (local.get $n) (i32.const 1)))
35
- (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)))))
36
54
  (local.get $r))`
37
55
 
38
56
  // __itoa(val: i32, buf: i32) → i32 (digit count). Writes decimal digits to buf.
@@ -85,6 +103,17 @@ export default (ctx) => {
85
103
  (if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
86
104
  (if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
87
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)))))))
88
117
  (local.set $buf (call $__alloc (i32.const 40)))
89
118
  ;; Sign
90
119
  (if (f64.lt (local.get $val) (f64.const 0))
@@ -293,19 +322,17 @@ export default (ctx) => {
293
322
 
294
323
  // Global isNaN/isFinite — coerce string→number first (unlike Number.isNaN/isFinite)
295
324
  ctx.core.emit['isNaN'] = (x) => {
296
- inc('__to_num')
297
- const v = asF64(emit(x))
325
+ const v = toNumF64(x, emit(x))
298
326
  const t = temp('t')
299
327
  return typed(['f64.ne',
300
- ['local.tee', `$${t}`, ['call', '$__to_num', v]],
328
+ ['local.tee', `$${t}`, v],
301
329
  ['local.get', `$${t}`]], 'i32')
302
330
  }
303
331
  ctx.core.emit['isFinite'] = (x) => {
304
- inc('__to_num')
305
- const v = asF64(emit(x))
332
+ const v = toNumF64(x, emit(x))
306
333
  const t = temp('t')
307
334
  return typed(['i32.and',
308
- ['f64.eq', ['local.tee', `$${t}`, ['call', '$__to_num', v]], ['local.get', `$${t}`]],
335
+ ['f64.eq', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
309
336
  ['f64.lt', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', Infinity]]], 'i32')
310
337
  }
311
338
 
@@ -320,15 +347,14 @@ export default (ctx) => {
320
347
  }
321
348
 
322
349
  // parseInt(str, radix) — parse string to integer
323
- 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)
324
351
  (local $off i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
325
- (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)))
326
354
  ;; If input is a number, just truncate
327
- (if (f64.eq (local.get $str) (local.get $str)) (then (return (f64.trunc (local.get $str)))))
328
- ;; If NaN-boxed but not a string type (4=heap,5=SSO) → return NaN
329
- (if (i32.and
330
- (i32.ne (call $__ptr_type (local.get $str)) (i32.const 4))
331
- (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))
332
358
  (then (return (f64.const nan))))
333
359
  (local.set $off (call $__ptr_offset (local.get $str)))
334
360
  (local.set $len (call $__str_byteLen (local.get $str)))
@@ -376,19 +402,25 @@ export default (ctx) => {
376
402
  (if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
377
403
  (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
378
404
 
379
- 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)
380
406
  (local $t i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
381
- (local $seen i32) (local $exp i32) (local $expNeg i32)
382
- (local $result f64) (local $scale f64)
383
- (if (f64.eq (local.get $v) (local.get $v)) (then (return (local.get $v))))
384
- (if (i64.eq (i64.reinterpret_f64 (local.get $v)) (i64.const ${NULL_NAN})) (then (return (f64.const 0))))
385
- (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))))
386
415
  (local.set $t (call $__ptr_type (local.get $v)))
387
- (if (i32.eqz
388
- (i32.or
389
- (i32.eq (local.get $t) (i32.const ${PTR.STRING}))
390
- (i32.eq (local.get $t) (i32.const ${PTR.SSO}))))
391
- (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))))))
392
424
  (local.set $len (call $__str_byteLen (local.get $v)))
393
425
  ;; Skip leading whitespace.
394
426
  (block $ws (loop $wsl
@@ -396,6 +428,7 @@ export default (ctx) => {
396
428
  (br_if $ws (i32.gt_s (call $__char_at (local.get $v) (local.get $i)) (i32.const 32)))
397
429
  (local.set $i (i32.add (local.get $i) (i32.const 1)))
398
430
  (br $wsl)))
431
+ (local.set $wsEnd (local.get $i))
399
432
  ;; Sign.
400
433
  (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
401
434
  (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
@@ -424,45 +457,51 @@ export default (ctx) => {
424
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)))))
425
458
  (local.set $seen (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $hexLoop)))))
426
459
  (return (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))))
427
- ;; Integer part.
428
- (block $intDone (loop $intLoop
429
- (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)))
430
464
  (local.set $c (call $__char_at (local.get $v) (local.get $i)))
431
- (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
432
471
  (i32.or
433
472
  (i32.lt_s (local.get $c) (i32.const 48))
434
473
  (i32.gt_s (local.get $c) (i32.const 57))))
435
- (local.set $result
436
- (f64.add
437
- (f64.mul (local.get $result) (f64.const 10))
438
- (f64.convert_i32_s (i32.sub (local.get $c) (i32.const 48)))))
439
474
  (local.set $seen (i32.const 1))
440
- (local.set $i (i32.add (local.get $i) (i32.const 1)))
441
- (br $intLoop)))
442
- ;; Fractional part.
443
- (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
444
- (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 46)))
445
- (then
446
- (local.set $i (i32.add (local.get $i) (i32.const 1)))
447
- (local.set $scale (f64.const 0.1))
448
- (block $fracDone (loop $fracLoop
449
- (br_if $fracDone (i32.ge_s (local.get $i) (local.get $len)))
450
- (local.set $c (call $__char_at (local.get $v) (local.get $i)))
451
- (br_if $fracDone
452
- (i32.or
453
- (i32.lt_s (local.get $c) (i32.const 48))
454
- (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
455
484
  (local.set $result
456
485
  (f64.add
457
- (local.get $result)
458
- (f64.mul
459
- (f64.convert_i32_s (i32.sub (local.get $c) (i32.const 48)))
460
- (local.get $scale))))
461
- (local.set $scale (f64.mul (local.get $scale) (f64.const 0.1)))
462
- (local.set $seen (i32.const 1))
463
- (local.set $i (i32.add (local.get $i) (i32.const 1)))
464
- (br $fracLoop)))))
465
- (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)))))
466
505
  ;; Scientific notation.
467
506
  (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
468
507
  (i32.or
@@ -487,28 +526,88 @@ export default (ctx) => {
487
526
  (i32.add
488
527
  (i32.mul (local.get $exp) (i32.const 10))
489
528
  (i32.sub (local.get $c) (i32.const 48))))
529
+ (local.set $expDigits (i32.add (local.get $expDigits) (i32.const 1)))
490
530
  (local.set $i (i32.add (local.get $i) (i32.const 1)))
491
531
  (br $expLoop)))
492
- (if (local.get $expNeg)
493
- (then (local.set $result (f64.div (local.get $result) (call $__pow10 (local.get $exp)))))
494
- (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)))))))
495
541
  (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
496
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
+
497
595
  ctx.core.emit['Number.parseInt'] = (x, radix) => {
498
596
  inc('__parseInt')
499
- 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')
500
598
  }
501
599
  ctx.core.emit['parseInt'] = ctx.core.emit['Number.parseInt']
502
600
  ctx.core.emit['Number.parseFloat'] = (x) => {
503
601
  inc('__to_num')
504
- return typed(['call', '$__to_num', asF64(emit(x))], 'f64')
602
+ return typed(['call', '$__to_num', asI64(emit(x))], 'f64')
505
603
  }
506
604
  ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']
507
605
 
508
606
  // Boolean(x) → truthiness (non-zero → 1, zero → 0)
509
607
  ctx.core.emit['Boolean'] = (x) => {
608
+ if (x === undefined) return typed(['f64.const', 0], 'f64')
510
609
  inc('__is_truthy')
511
- const v = asF64(emit(x))
610
+ const v = asI64(emit(x))
512
611
  return typed(['if', ['result', 'f64'], ['call', '$__is_truthy', v], ['then', ['f64.const', 1]], ['else', ['f64.const', 0]]], 'f64')
513
612
  }
514
613
 
@@ -561,10 +660,10 @@ export default (ctx) => {
561
660
 
562
661
  // Number(x) — identity for numbers, i64→f64 conversion for BigInt
563
662
  ctx.core.emit['Number'] = (x) => {
663
+ if (x === undefined) return typed(['f64.const', 0], 'f64')
564
664
  if (valTypeOf(x) === VAL.BIGINT)
565
665
  return typed(['f64.convert_i64_s', asI64(emit(x))], 'f64')
566
- inc('__to_num')
567
- return typed(['call', '$__to_num', asF64(emit(x))], 'f64')
666
+ return toNumF64(x, emit(x))
568
667
  }
569
668
 
570
669
  // BigInt(x) — f64→i64 conversion (reinterpret as BigInt-as-f64).
@@ -572,12 +671,23 @@ export default (ctx) => {
572
671
  // (handles both decimal and hex string parse), then truncate.
573
672
  ctx.core.emit['BigInt'] = (x) => {
574
673
  const vt = valTypeOf(x)
575
- 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
+ }
576
687
  if (vt === VAL.NUMBER)
577
688
  return typed(['f64.reinterpret_i64', ['i64.trunc_sat_f64_s', asF64(emit(x))]], 'f64')
578
- inc('__to_num')
579
- return typed(['f64.reinterpret_i64',
580
- ['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')
581
691
  }
582
692
 
583
693
  // BigInt.asIntN(bits, bigint) — truncate to signed N-bit