jz 0.0.0 → 0.1.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.
@@ -0,0 +1,605 @@
1
+ /**
2
+ * Number module — toString, toFixed, toPrecision, toExponential, String().
3
+ *
4
+ * Core: __ftoa(f64, precision, mode) → f64 (NaN-boxed string pointer).
5
+ * Modes: 0=default (shortest repr), 1=fixed (toFixed).
6
+ * Uses integer-based digit extraction to avoid float drift.
7
+ * Static string table at address 0 for NaN, Infinity, etc.
8
+ *
9
+ * @module number
10
+ */
11
+
12
+ import { emit, typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, valTypeOf, VAL, temp, tempI32, tempI64 } from '../src/compile.js'
13
+ import { inc, PTR } from '../src/ctx.js'
14
+
15
+ export default (ctx) => {
16
+ Object.assign(ctx.core.stdlibDeps, {
17
+ __mkstr: ['__alloc'],
18
+ __ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str'],
19
+ __toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
20
+ __to_num: ['__char_at', '__str_byteLen', '__pow10'],
21
+ __parseInt: ['__char_at', '__str_byteLen'],
22
+ })
23
+
24
+
25
+ // __pow10(n: i32) → f64 — compute 10^n via loop
26
+ ctx.core.stdlib['__pow10'] = `(func $__pow10 (param $n i32) (result f64)
27
+ (local $r f64)
28
+ (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)))
34
+ (local.get $r))`
35
+
36
+ // __itoa(val: i32, buf: i32) → i32 (digit count). Writes decimal digits to buf.
37
+ ctx.core.stdlib['__itoa'] = `(func $__itoa (param $val i32) (param $buf i32) (result i32)
38
+ (local $len i32) (local $i i32) (local $j i32) (local $tmp i32)
39
+ (if (i32.eqz (local.get $val))
40
+ (then (i32.store8 (local.get $buf) (i32.const 48)) (return (i32.const 1))))
41
+ (local.set $tmp (local.get $val))
42
+ (block $d (loop $l
43
+ (br_if $d (i32.eqz (local.get $tmp)))
44
+ (i32.store8 (i32.add (local.get $buf) (local.get $len))
45
+ (i32.add (i32.const 48) (i32.rem_u (local.get $tmp) (i32.const 10))))
46
+ (local.set $tmp (i32.div_u (local.get $tmp) (i32.const 10)))
47
+ (local.set $len (i32.add (local.get $len) (i32.const 1)))
48
+ (br $l)))
49
+ ;; Reverse
50
+ (local.set $j (i32.sub (local.get $len) (i32.const 1)))
51
+ (block $rd (loop $rl
52
+ (br_if $rd (i32.ge_s (local.get $i) (local.get $j)))
53
+ (local.set $tmp (i32.load8_u (i32.add (local.get $buf) (local.get $i))))
54
+ (i32.store8 (i32.add (local.get $buf) (local.get $i))
55
+ (i32.load8_u (i32.add (local.get $buf) (local.get $j))))
56
+ (i32.store8 (i32.add (local.get $buf) (local.get $j)) (local.get $tmp))
57
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
58
+ (local.set $j (i32.sub (local.get $j) (i32.const 1)))
59
+ (br $rl)))
60
+ (local.get $len))`
61
+
62
+ // __mkstr(buf: i32, len: i32) → f64 — copy scratch buffer to heap string.
63
+ // Hot (~60M calls in watr self-host via __ftoa). bulk memory.copy is ~10× faster than
64
+ // a hand-rolled byte loop (wasm2c lowers it to memcpy under PGO+LTO).
65
+ ctx.core.stdlib['__mkstr'] = `(func $__mkstr (param $buf i32) (param $len i32) (result f64)
66
+ (local $off i32)
67
+ (local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
68
+ (i32.store (local.get $off) (local.get $len))
69
+ (local.set $off (i32.add (local.get $off) (i32.const 4)))
70
+ (memory.copy (local.get $off) (local.get $buf) (local.get $len))
71
+ (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
72
+
73
+ // __ftoa(val: f64, prec: i32, mode: i32) → f64 (NaN-boxed string)
74
+ // mode 0: default (shortest repr, strip trailing zeros)
75
+ // mode 1: fixed (exactly prec decimal places)
76
+ // Uses integer-scaled digit extraction to avoid float drift.
77
+ ctx.core.stdlib['__ftoa'] = `(func $__ftoa (param $val f64) (param $prec i32) (param $mode i32) (result f64)
78
+ (local $buf i32) (local $pos i32) (local $neg i32)
79
+ (local $abs f64) (local $scale f64) (local $scaled f64)
80
+ (local $int i32) (local $frac i32) (local $ilen i32) (local $flen i32)
81
+ (local $i i32) (local $j i32)
82
+ ;; Special values
83
+ (if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
84
+ (if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
85
+ (if (f64.eq (local.get $val) (f64.const -inf)) (then (return (call $__static_str (i32.const 2)))))
86
+ (local.set $buf (call $__alloc (i32.const 40)))
87
+ ;; Sign
88
+ (if (f64.lt (local.get $val) (f64.const 0))
89
+ (then (local.set $neg (i32.const 1)) (local.set $val (f64.neg (local.get $val)))))
90
+ (if (i32.and (f64.eq (local.get $val) (f64.const 0)) (local.get $neg))
91
+ (then (local.set $neg (i32.const 0))))
92
+ (if (local.get $neg)
93
+ (then (i32.store8 (local.get $buf) (i32.const 45))
94
+ (local.set $pos (i32.const 1))))
95
+ ;; Default mode: auto-select precision (up to 9 digits, must fit i32 when scaled)
96
+ (if (i32.eqz (local.get $mode))
97
+ (then (local.set $prec (i32.const 9))))
98
+ ;; Round and scale to integer: scaled = nearest(val * 10^prec)
99
+ (local.set $scale (call $__pow10 (local.get $prec)))
100
+ (local.set $scaled (f64.nearest (f64.mul (local.get $val) (local.get $scale))))
101
+ ;; If scaled doesn't fit i32, reduce precision until it does (min prec=0)
102
+ (block $fit (loop $fitl
103
+ (br_if $fit (f64.lt (local.get $scaled) (f64.const 2147483648)))
104
+ (br_if $fit (i32.le_s (local.get $prec) (i32.const 0)))
105
+ (local.set $prec (i32.sub (local.get $prec) (i32.const 1)))
106
+ (local.set $scale (call $__pow10 (local.get $prec)))
107
+ (local.set $scaled (f64.nearest (f64.mul (local.get $val) (local.get $scale))))
108
+ (br $fitl)))
109
+ ;; Split: int = scaled / scale, frac = scaled % scale
110
+ (if (f64.lt (local.get $scaled) (f64.const 2147483648))
111
+ (then
112
+ (local.set $int (i32.trunc_f64_u (f64.div (local.get $scaled) (local.get $scale))))
113
+ (local.set $frac (i32.trunc_f64_u (f64.sub (local.get $scaled)
114
+ (f64.mul (f64.convert_i32_u (local.get $int)) (local.get $scale))))))
115
+ (else
116
+ (local.set $int (i32.const 0))
117
+ (local.set $frac (i32.const 0))
118
+ (local.set $prec (i32.const 0))
119
+ (local.set $abs (f64.trunc (local.get $val)))
120
+ ;; Write large integer digits reversed
121
+ (local.set $ilen (local.get $pos))
122
+ (block $ld (loop $ll
123
+ (br_if $ld (f64.lt (local.get $abs) (f64.const 1)))
124
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos))
125
+ (i32.add (i32.const 48) (i32.trunc_f64_u (f64.sub (local.get $abs)
126
+ (f64.mul (f64.trunc (f64.div (local.get $abs) (f64.const 10))) (f64.const 10))))))
127
+ (local.set $abs (f64.trunc (f64.div (local.get $abs) (f64.const 10))))
128
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
129
+ (br $ll)))
130
+ ;; Reverse
131
+ (local.set $i (local.get $ilen)) (local.set $j (i32.sub (local.get $pos) (i32.const 1)))
132
+ (block $rd (loop $rl
133
+ (br_if $rd (i32.ge_s (local.get $i) (local.get $j)))
134
+ (local.set $int (i32.load8_u (i32.add (local.get $buf) (local.get $i))))
135
+ (i32.store8 (i32.add (local.get $buf) (local.get $i))
136
+ (i32.load8_u (i32.add (local.get $buf) (local.get $j))))
137
+ (i32.store8 (i32.add (local.get $buf) (local.get $j)) (local.get $int))
138
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
139
+ (local.set $j (i32.sub (local.get $j) (i32.const 1)))
140
+ (br $rl)))
141
+ (return (call $__mkstr (local.get $buf) (local.get $pos)))))
142
+ ;; Write integer part
143
+ (local.set $ilen (call $__itoa (local.get $int) (i32.add (local.get $buf) (local.get $pos))))
144
+ (local.set $pos (i32.add (local.get $pos) (local.get $ilen)))
145
+ ;; Write fractional part: extract digits from $frac by dividing by 10^(prec-1), 10^(prec-2), ...
146
+ (if (i32.gt_s (local.get $prec) (i32.const 0))
147
+ (then
148
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 46))
149
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
150
+ (local.set $i (i32.sub (local.get $prec) (i32.const 1)))
151
+ (block $fd (loop $fl
152
+ (br_if $fd (i32.lt_s (local.get $i) (i32.const 0)))
153
+ (local.set $j (i32.div_u (local.get $frac) (i32.trunc_f64_u (call $__pow10 (local.get $i)))))
154
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos))
155
+ (i32.add (i32.const 48) (i32.rem_u (local.get $j) (i32.const 10))))
156
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
157
+ (local.set $i (i32.sub (local.get $i) (i32.const 1)))
158
+ (br $fl)))))
159
+ ;; Default mode: strip trailing zeros and dot — only when a fractional part was emitted.
160
+ ;; Gating on $prec>0 prevents stripping zeros from the integer part (e.g. 1079623680 → 107962368)
161
+ ;; for values where auto-fit reduced prec to 0 because the scaled integer wouldn't fit i32.
162
+ (if (i32.and (i32.eqz (local.get $mode)) (i32.gt_s (local.get $prec) (i32.const 0)))
163
+ (then
164
+ (block $sd (loop $sl
165
+ (br_if $sd (i32.le_s (local.get $pos) (i32.const 0)))
166
+ (br_if $sd (i32.ne (i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1)))) (i32.const 48)))
167
+ (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))
168
+ (br $sl)))
169
+ (if (i32.and (i32.gt_s (local.get $pos) (i32.const 0))
170
+ (i32.eq (i32.load8_u (i32.add (local.get $buf) (i32.sub (local.get $pos) (i32.const 1)))) (i32.const 46)))
171
+ (then (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))))))
172
+ (call $__mkstr (local.get $buf) (local.get $pos)))`
173
+
174
+ // __toExp(val: f64, prec: i32) → f64 (NaN-boxed string)
175
+ // Format: [-]d.ddd...e[+/-]dd — integer-based digit extraction
176
+ ctx.core.stdlib['__toExp'] = `(func $__toExp (param $val f64) (param $prec i32) (result f64)
177
+ (local $buf i32) (local $pos i32) (local $neg i32) (local $exp i32)
178
+ (local $len i32) (local $i i32) (local $j i32)
179
+ (local $mantissa f64) (local $scale f64)
180
+ (if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
181
+ (if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
182
+ (if (f64.eq (local.get $val) (f64.const -inf)) (then (return (call $__static_str (i32.const 2)))))
183
+ (local.set $buf (call $__alloc (i32.const 32)))
184
+ ;; Sign
185
+ (if (f64.lt (local.get $val) (f64.const 0))
186
+ (then (local.set $neg (i32.const 1)) (local.set $val (f64.neg (local.get $val)))))
187
+ (if (i32.and (f64.eq (local.get $val) (f64.const 0)) (local.get $neg))
188
+ (then (local.set $neg (i32.const 0))))
189
+ (if (local.get $neg)
190
+ (then (i32.store8 (local.get $buf) (i32.const 45))
191
+ (local.set $pos (i32.const 1))))
192
+ ;; Normalize: 1 <= val < 10
193
+ (if (f64.gt (local.get $val) (f64.const 0))
194
+ (then
195
+ (block $d1 (loop $l1
196
+ (br_if $d1 (f64.lt (local.get $val) (f64.const 10)))
197
+ (local.set $val (f64.div (local.get $val) (f64.const 10)))
198
+ (local.set $exp (i32.add (local.get $exp) (i32.const 1)))
199
+ (br $l1)))
200
+ (block $d2 (loop $l2
201
+ (br_if $d2 (f64.ge (local.get $val) (f64.const 1)))
202
+ (local.set $val (f64.mul (local.get $val) (f64.const 10)))
203
+ (local.set $exp (i32.sub (local.get $exp) (i32.const 1)))
204
+ (br $l2)))))
205
+ ;; Scale to integer mantissa: nearest(val * 10^prec)
206
+ (local.set $scale (call $__pow10 (local.get $prec)))
207
+ (local.set $mantissa (f64.nearest (f64.mul (local.get $val) (local.get $scale))))
208
+ ;; Rounding overflow (e.g. 9.95 → 1000 when prec=1, scale=10)
209
+ (if (f64.ge (local.get $mantissa) (f64.mul (f64.const 10) (local.get $scale)))
210
+ (then
211
+ (local.set $mantissa (f64.div (local.get $mantissa) (f64.const 10)))
212
+ (local.set $exp (i32.add (local.get $exp) (i32.const 1)))))
213
+ ;; Write mantissa digits via itoa
214
+ (local.set $len (call $__itoa (i32.trunc_f64_u (local.get $mantissa)) (i32.add (local.get $buf) (local.get $pos))))
215
+ ;; Insert '.' after first digit
216
+ (if (i32.gt_s (local.get $prec) (i32.const 0))
217
+ (then
218
+ (local.set $i (local.get $len))
219
+ (block $md (loop $ml
220
+ (br_if $md (i32.le_s (local.get $i) (i32.const 1)))
221
+ (i32.store8 (i32.add (local.get $buf) (i32.add (local.get $pos) (local.get $i)))
222
+ (i32.load8_u (i32.add (local.get $buf) (i32.add (local.get $pos) (i32.sub (local.get $i) (i32.const 1))))))
223
+ (local.set $i (i32.sub (local.get $i) (i32.const 1)))
224
+ (br $ml)))
225
+ (i32.store8 (i32.add (local.get $buf) (i32.add (local.get $pos) (i32.const 1))) (i32.const 46))
226
+ (local.set $pos (i32.add (local.get $pos) (i32.add (local.get $len) (i32.const 1)))))
227
+ (else (local.set $pos (i32.add (local.get $pos) (local.get $len)))))
228
+ ;; Write 'e', sign, exponent
229
+ (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 101))
230
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
231
+ (if (i32.lt_s (local.get $exp) (i32.const 0))
232
+ (then (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 45))
233
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))
234
+ (local.set $exp (i32.sub (i32.const 0) (local.get $exp))))
235
+ (else (i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 43))
236
+ (local.set $pos (i32.add (local.get $pos) (i32.const 1)))))
237
+ (local.set $pos (i32.add (local.get $pos) (call $__itoa (local.get $exp) (i32.add (local.get $buf) (local.get $pos)))))
238
+ (call $__mkstr (local.get $buf) (local.get $pos)))`
239
+
240
+ // __static_str(id: i32) → f64 — create heap string from data segment
241
+ // 0=NaN 1=Infinity 2=-Infinity 3=true 4=false 5=null 6=undefined 7=[Array] 8=[Object]
242
+ ctx.core.stdlib['__static_str'] = `(func $__static_str (param $id i32) (result f64)
243
+ (local $src i32) (local $len i32)
244
+ (local.set $src (i32.const 0)) (local.set $len (i32.const 0))
245
+ (if (i32.eqz (local.get $id)) (then (local.set $len (i32.const 3))))
246
+ (if (i32.eq (local.get $id) (i32.const 1)) (then (local.set $src (i32.const 3)) (local.set $len (i32.const 8))))
247
+ (if (i32.eq (local.get $id) (i32.const 2)) (then (local.set $src (i32.const 11)) (local.set $len (i32.const 9))))
248
+ (if (i32.eq (local.get $id) (i32.const 3)) (then (local.set $src (i32.const 20)) (local.set $len (i32.const 4))))
249
+ (if (i32.eq (local.get $id) (i32.const 4)) (then (local.set $src (i32.const 24)) (local.set $len (i32.const 5))))
250
+ (if (i32.eq (local.get $id) (i32.const 5)) (then (local.set $src (i32.const 29)) (local.set $len (i32.const 4))))
251
+ (if (i32.eq (local.get $id) (i32.const 6)) (then (local.set $src (i32.const 33)) (local.set $len (i32.const 9))))
252
+ (if (i32.eq (local.get $id) (i32.const 7)) (then (local.set $src (i32.const 42)) (local.set $len (i32.const 7))))
253
+ (if (i32.eq (local.get $id) (i32.const 8)) (then (local.set $src (i32.const 49)) (local.set $len (i32.const 8))))
254
+ (call $__mkstr (local.get $src) (local.get $len)))`
255
+
256
+ // R: Static strings seeded at address 0. Compile.js strips if __static_str unused.
257
+ // 0=NaN 1=Infinity 2=-Infinity 3=true 4=false 5=null 6=undefined 7=[Array] 8=[Object]
258
+ const staticStr = 'NaNInfinity-Infinitytruefalsenullundefined[Array][Object]'
259
+ ctx.runtime.staticDataLen = staticStr.length
260
+ ctx.runtime.data = (ctx.runtime.data || '') + staticStr
261
+
262
+ // === Number constants ===
263
+
264
+ ctx.core.emit['Number.MAX_SAFE_INTEGER'] = () => typed(['f64.const', 9007199254740991], 'f64')
265
+ ctx.core.emit['Number.MIN_SAFE_INTEGER'] = () => typed(['f64.const', -9007199254740991], 'f64')
266
+ ctx.core.emit['Number.EPSILON'] = () => typed(['f64.const', 2.220446049250313e-16], 'f64')
267
+ ctx.core.emit['Number.MAX_VALUE'] = () => typed(['f64.const', 1.7976931348623157e+308], 'f64')
268
+ ctx.core.emit['Number.MIN_VALUE'] = () => typed(['f64.const', 5e-324], 'f64')
269
+ ctx.core.emit['Number.POSITIVE_INFINITY'] = () => typed(['f64.const', Infinity], 'f64')
270
+ ctx.core.emit['Number.NEGATIVE_INFINITY'] = () => typed(['f64.const', -Infinity], 'f64')
271
+ ctx.core.emit['Number.NaN'] = () => typed(['f64.const', NaN], 'f64')
272
+
273
+ // === Number static methods ===
274
+
275
+ const emitIsNaN = (x) => {
276
+ const v = asF64(emit(x))
277
+ const t = temp('t')
278
+ return typed(['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]], 'i32')
279
+ }
280
+
281
+ const emitIsFinite = (x) => {
282
+ const v = asF64(emit(x))
283
+ const t = temp('t')
284
+ return typed(['i32.and',
285
+ ['f64.eq', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
286
+ ['f64.lt', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', Infinity]]], 'i32')
287
+ }
288
+
289
+ ctx.core.emit['Number.isNaN'] = emitIsNaN
290
+ ctx.core.emit['Number.isFinite'] = emitIsFinite
291
+
292
+ // Global isNaN/isFinite — coerce string→number first (unlike Number.isNaN/isFinite)
293
+ ctx.core.emit['isNaN'] = (x) => {
294
+ inc('__to_num')
295
+ const v = asF64(emit(x))
296
+ const t = temp('t')
297
+ return typed(['f64.ne',
298
+ ['local.tee', `$${t}`, ['call', '$__to_num', v]],
299
+ ['local.get', `$${t}`]], 'i32')
300
+ }
301
+ ctx.core.emit['isFinite'] = (x) => {
302
+ inc('__to_num')
303
+ const v = asF64(emit(x))
304
+ const t = temp('t')
305
+ return typed(['i32.and',
306
+ ['f64.eq', ['local.tee', `$${t}`, ['call', '$__to_num', v]], ['local.get', `$${t}`]],
307
+ ['f64.lt', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', Infinity]]], 'i32')
308
+ }
309
+
310
+ ctx.core.emit['Number.isInteger'] = (x) => {
311
+ const v = asF64(emit(x))
312
+ const t = temp('t')
313
+ return typed(['i32.and',
314
+ ['i32.and',
315
+ ['f64.eq', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
316
+ ['f64.lt', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', Infinity]]],
317
+ ['f64.eq', ['local.get', `$${t}`], ['f64.trunc', ['local.get', `$${t}`]]]], 'i32')
318
+ }
319
+
320
+ // parseInt(str, radix) — parse string to integer
321
+ ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str f64) (param $radix i32) (result f64)
322
+ (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)
324
+ ;; 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)))
330
+ (then (return (f64.const nan))))
331
+ (local.set $off (call $__ptr_offset (local.get $str)))
332
+ (local.set $len (call $__str_byteLen (local.get $str)))
333
+ (local.set $i (i32.const 0))
334
+ ;; Skip whitespace
335
+ (block $ws (loop $wsl
336
+ (br_if $ws (i32.ge_s (local.get $i) (local.get $len)))
337
+ (br_if $ws (i32.gt_s (call $__char_at (local.get $str) (local.get $i)) (i32.const 32)))
338
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
339
+ (br $wsl)))
340
+ ;; Sign
341
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
342
+ (i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 45)))
343
+ (then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
344
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
345
+ (i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 43)))
346
+ (then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
347
+ ;; 0x prefix → radix 16
348
+ (if (i32.and (i32.eqz (local.get $radix))
349
+ (i32.and (i32.le_s (i32.add (local.get $i) (i32.const 1)) (local.get $len))
350
+ (i32.and (i32.eq (call $__char_at (local.get $str) (local.get $i)) (i32.const 48))
351
+ (i32.or (i32.eq (call $__char_at (local.get $str) (i32.add (local.get $i) (i32.const 1))) (i32.const 120))
352
+ (i32.eq (call $__char_at (local.get $str) (i32.add (local.get $i) (i32.const 1))) (i32.const 88))))))
353
+ (then (local.set $radix (i32.const 16)) (local.set $i (i32.add (local.get $i) (i32.const 2)))))
354
+ (if (i32.eqz (local.get $radix)) (then (local.set $radix (i32.const 10))))
355
+ ;; Parse digits
356
+ (local.set $result (f64.const 0))
357
+ (block $done (loop $lp
358
+ (br_if $done (i32.ge_s (local.get $i) (local.get $len)))
359
+ (local.set $c (call $__char_at (local.get $str) (local.get $i)))
360
+ ;; Digit value
361
+ (local.set $digit (i32.const -1))
362
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 48)) (i32.le_s (local.get $c) (i32.const 57)))
363
+ (then (local.set $digit (i32.sub (local.get $c) (i32.const 48)))))
364
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 97)) (i32.le_s (local.get $c) (i32.const 122)))
365
+ (then (local.set $digit (i32.sub (local.get $c) (i32.const 87)))))
366
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 65)) (i32.le_s (local.get $c) (i32.const 90)))
367
+ (then (local.set $digit (i32.sub (local.get $c) (i32.const 55)))))
368
+ (br_if $done (i32.or (i32.lt_s (local.get $digit) (i32.const 0)) (i32.ge_s (local.get $digit) (local.get $radix))))
369
+ (local.set $seen (i32.const 1))
370
+ (local.set $result (f64.add (f64.mul (local.get $result) (f64.convert_i32_s (local.get $radix))) (f64.convert_i32_s (local.get $digit))))
371
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
372
+ (br $lp)))
373
+ ;; No digits consumed → NaN
374
+ (if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
375
+ (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
376
+
377
+ ctx.core.stdlib['__to_num'] = `(func $__to_num (param $v f64) (result f64)
378
+ (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))))
384
+ (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))))
390
+ (local.set $len (call $__str_byteLen (local.get $v)))
391
+ ;; Skip leading whitespace.
392
+ (block $ws (loop $wsl
393
+ (br_if $ws (i32.ge_s (local.get $i) (local.get $len)))
394
+ (br_if $ws (i32.gt_s (call $__char_at (local.get $v) (local.get $i)) (i32.const 32)))
395
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
396
+ (br $wsl)))
397
+ ;; Sign.
398
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
399
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
400
+ (then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
401
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
402
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
403
+ (then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
404
+ ;; 0x prefix → hex parse and early return
405
+ (if (i32.and
406
+ (i32.le_s (i32.add (local.get $i) (i32.const 1)) (local.get $len))
407
+ (i32.and (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 48))
408
+ (i32.or (i32.eq (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 120))
409
+ (i32.eq (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 88)))))
410
+ (then
411
+ (local.set $i (i32.add (local.get $i) (i32.const 2)))
412
+ (block $hexDone (loop $hexLoop
413
+ (br_if $hexDone (i32.ge_s (local.get $i) (local.get $len)))
414
+ (local.set $c (call $__char_at (local.get $v) (local.get $i)))
415
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 48)) (i32.le_s (local.get $c) (i32.const 57)))
416
+ (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 48)))))
417
+ (local.set $seen (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $hexLoop)))
418
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 97)) (i32.le_s (local.get $c) (i32.const 102)))
419
+ (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 87)))))
420
+ (local.set $seen (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $hexLoop)))
421
+ (if (i32.and (i32.ge_s (local.get $c) (i32.const 65)) (i32.le_s (local.get $c) (i32.const 70)))
422
+ (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
+ (local.set $seen (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $hexLoop)))))
424
+ (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)))
428
+ (local.set $c (call $__char_at (local.get $v) (local.get $i)))
429
+ (br_if $intDone
430
+ (i32.or
431
+ (i32.lt_s (local.get $c) (i32.const 48))
432
+ (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
+ (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))))
453
+ (local.set $result
454
+ (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))))
464
+ ;; Scientific notation.
465
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
466
+ (i32.or
467
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 101))
468
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 69))))
469
+ (then
470
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
471
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
472
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
473
+ (then (local.set $expNeg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
474
+ (if (i32.and (i32.lt_s (local.get $i) (local.get $len))
475
+ (i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
476
+ (then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
477
+ (block $expDone (loop $expLoop
478
+ (br_if $expDone (i32.ge_s (local.get $i) (local.get $len)))
479
+ (local.set $c (call $__char_at (local.get $v) (local.get $i)))
480
+ (br_if $expDone
481
+ (i32.or
482
+ (i32.lt_s (local.get $c) (i32.const 48))
483
+ (i32.gt_s (local.get $c) (i32.const 57))))
484
+ (local.set $exp
485
+ (i32.add
486
+ (i32.mul (local.get $exp) (i32.const 10))
487
+ (i32.sub (local.get $c) (i32.const 48))))
488
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
489
+ (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))))))))
493
+ (if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
494
+
495
+ ctx.core.emit['Number.parseInt'] = (x, radix) => {
496
+ inc('__parseInt')
497
+ return typed(['call', '$__parseInt', asF64(emit(x)), radix ? asI32(emit(radix)) : ['i32.const', 0]], 'f64')
498
+ }
499
+ ctx.core.emit['parseInt'] = ctx.core.emit['Number.parseInt']
500
+ ctx.core.emit['Number.parseFloat'] = (x) => {
501
+ inc('__to_num')
502
+ return typed(['call', '$__to_num', asF64(emit(x))], 'f64')
503
+ }
504
+ ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']
505
+
506
+ // Boolean(x) → truthiness (non-zero → 1, zero → 0)
507
+ ctx.core.emit['Boolean'] = (x) => {
508
+ inc('__is_truthy')
509
+ const v = asF64(emit(x))
510
+ return typed(['if', ['result', 'f64'], ['call', '$__is_truthy', v], ['then', ['f64.const', 1]], ['else', ['f64.const', 0]]], 'f64')
511
+ }
512
+
513
+ // === Instance method emitters ===
514
+
515
+ ctx.core.emit['.number:toString'] = (n) => {
516
+ inc('__ftoa')
517
+ return typed(['call', '$__ftoa', asF64(emit(n)), ['i32.const', 0], ['i32.const', 0]], 'f64')
518
+ }
519
+
520
+ ctx.core.emit['.number:toFixed'] = (n, d) => {
521
+ inc('__ftoa')
522
+ return typed(['call', '$__ftoa', asF64(emit(n)), asI32(emit(d || [, 0])), ['i32.const', 1]], 'f64')
523
+ }
524
+
525
+ ctx.core.emit['.number:toExponential'] = (n, d) => {
526
+ inc('__toExp')
527
+ return typed(['call', '$__toExp', asF64(emit(n)), asI32(emit(d || [, 0]))], 'f64')
528
+ }
529
+
530
+ ctx.core.emit['.number:toPrecision'] = (n, p) => {
531
+ inc('__ftoa', '__toExp')
532
+ const val = temp('pv'), t = temp('tp'), exp = tempI32('te'), pr = tempI32('pp')
533
+ return typed(['block', ['result', 'f64'],
534
+ ['local.set', `$${val}`, asF64(emit(n))],
535
+ ['local.set', `$${pr}`, asI32(emit(p))],
536
+ ['local.set', `$${t}`, ['f64.abs', ['local.get', `$${val}`]]],
537
+ ['local.set', `$${exp}`, ['i32.const', 0]],
538
+ ['if', ['f64.gt', ['local.get', `$${t}`], ['f64.const', 0]],
539
+ ['then',
540
+ ['block', '$d1', ['loop', '$l1',
541
+ ['br_if', '$d1', ['f64.lt', ['local.get', `$${t}`], ['f64.const', 10]]],
542
+ ['local.set', `$${t}`, ['f64.div', ['local.get', `$${t}`], ['f64.const', 10]]],
543
+ ['local.set', `$${exp}`, ['i32.add', ['local.get', `$${exp}`], ['i32.const', 1]]],
544
+ ['br', '$l1']]],
545
+ ['block', '$d2', ['loop', '$l2',
546
+ ['br_if', '$d2', ['f64.ge', ['local.get', `$${t}`], ['f64.const', 1]]],
547
+ ['local.set', `$${t}`, ['f64.mul', ['local.get', `$${t}`], ['f64.const', 10]]],
548
+ ['local.set', `$${exp}`, ['i32.sub', ['local.get', `$${exp}`], ['i32.const', 1]]],
549
+ ['br', '$l2']]]]],
550
+ ['if', ['result', 'f64'],
551
+ ['i32.or',
552
+ ['i32.lt_s', ['local.get', `$${exp}`], ['i32.const', -6]],
553
+ ['i32.ge_s', ['local.get', `$${exp}`], ['local.get', `$${pr}`]]],
554
+ ['then', ['call', '$__toExp', ['local.get', `$${val}`], ['i32.sub', ['local.get', `$${pr}`], ['i32.const', 1]]]],
555
+ ['else', ['call', '$__ftoa', ['local.get', `$${val}`],
556
+ ['i32.sub', ['i32.sub', ['local.get', `$${pr}`], ['i32.const', 1]], ['local.get', `$${exp}`]],
557
+ ['i32.const', 1]]]]], 'f64')
558
+ }
559
+
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
+ // Number(x) — identity for numbers, i64→f64 conversion for BigInt
567
+ ctx.core.emit['Number'] = (x) => {
568
+ if (valTypeOf(x) === VAL.BIGINT)
569
+ return typed(['f64.convert_i64_s', asI64(emit(x))], 'f64')
570
+ inc('__to_num')
571
+ return typed(['call', '$__to_num', asF64(emit(x))], 'f64')
572
+ }
573
+
574
+ // BigInt(x) — f64→i64 conversion (reinterpret as BigInt-as-f64).
575
+ // For number input: truncate directly. For string / unknown: first coerce via __to_num
576
+ // (handles both decimal and hex string parse), then truncate.
577
+ ctx.core.emit['BigInt'] = (x) => {
578
+ const vt = valTypeOf(x)
579
+ if (vt === VAL.BIGINT) return emit(x)
580
+ if (vt === VAL.NUMBER)
581
+ 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')
585
+ }
586
+
587
+ // BigInt.asIntN(bits, bigint) — truncate to signed N-bit
588
+ ctx.core.emit['BigInt.asIntN'] = (bits, val) => {
589
+ const vbits = asI32(emit(bits)), vval = asI64(emit(val))
590
+ // (val << (64 - bits)) >> (64 - bits) — arithmetic shift for sign extension
591
+ const shift = typed(['i64.sub', ['i64.const', 64], ['i64.extend_i32_s', vbits]], 'i64')
592
+ const t = tempI64('bi')
593
+ return typed(['f64.reinterpret_i64', ['block', ['result', 'i64'],
594
+ ['local.set', `$${t}`, shift],
595
+ ['i64.shr_s', ['i64.shl', vval, ['local.get', `$${t}`]], ['local.get', `$${t}`]]]], 'f64')
596
+ }
597
+
598
+ // BigInt.asUintN(bits, bigint) — truncate to unsigned N-bit
599
+ ctx.core.emit['BigInt.asUintN'] = (bits, val) => {
600
+ const vbits = asI32(emit(bits)), vval = asI64(emit(val))
601
+ // val & ((1 << bits) - 1)
602
+ return typed(['f64.reinterpret_i64',
603
+ ['i64.and', vval, ['i64.sub', ['i64.shl', ['i64.const', 1], ['i64.extend_i32_s', vbits]], ['i64.const', 1]]]], 'f64')
604
+ }
605
+ }