jz 0.4.0 → 0.5.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/README.md +275 -255
- package/cli.js +8 -51
- package/index.js +166 -31
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +181 -71
- package/module/collection.js +222 -8
- package/module/console.js +1 -1
- package/module/core.js +251 -118
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +361 -55
- package/module/math.js +551 -128
- package/module/number.js +390 -227
- package/module/object.js +252 -34
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +540 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +10 -7
- package/src/abi/array.js +89 -0
- package/src/abi/index.js +35 -0
- package/src/abi/number.js +137 -0
- package/src/abi/object.js +57 -0
- package/src/abi/string.js +529 -0
- package/src/analyze.js +1870 -485
- package/src/assemble.js +27 -12
- package/src/autoload.js +13 -1
- package/src/compile.js +446 -179
- package/src/ctx.js +85 -18
- package/src/emit.js +662 -196
- package/src/infer.js +548 -0
- package/src/ir.js +291 -23
- package/src/jzify.js +786 -94
- package/src/narrow.js +433 -251
- package/src/optimize.js +126 -122
- package/src/plan.js +703 -34
- package/src/prepare.js +822 -150
- package/src/resolve.js +85 -0
- package/src/vectorize.js +99 -18
- package/src/ast.js +0 -160
- package/src/auto-config.js +0 -120
- package/src/fuse.js +0 -159
package/module/number.js
CHANGED
|
@@ -9,19 +9,139 @@
|
|
|
9
9
|
* @module number
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { typed, asF64, asI32, asI64, toNumF64, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64 } from '../src/ir.js'
|
|
13
|
-
import { emit } from '../src/emit.js'
|
|
14
|
-
import { isReassigned } from '../src/
|
|
15
|
-
import { valTypeOf, VAL } from '../src/analyze.js'
|
|
12
|
+
import { typed, asF64, asI32, asI64, toI32, toNumF64, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64 } from '../src/ir.js'
|
|
13
|
+
import { emit, emitBoolStr } from '../src/emit.js'
|
|
14
|
+
import { isReassigned, valTypeOf, VAL } from '../src/analyze.js'
|
|
16
15
|
import { inc, PTR } from '../src/ctx.js'
|
|
17
16
|
|
|
17
|
+
// ─── Shared decimal-number parsing fragments ────────────────────────────────
|
|
18
|
+
// `__to_num` (Number coercion) and `__parseFloat` both scan a StrDecimalLiteral
|
|
19
|
+
// significand + ExponentPart, and that scan was verbatim-identical between them
|
|
20
|
+
// — a "fix the same bug twice" hazard (commit 652ba5f patched both copies).
|
|
21
|
+
// These named fragments are the common core, spliced into both bodies; the
|
|
22
|
+
// produced WASM is unchanged, the source now has one place to fix.
|
|
23
|
+
// Required locals (every consumer declares them):
|
|
24
|
+
// $v i64 · $i $len $c $dot $seen $sigDigits $decExp $dropped $round
|
|
25
|
+
// $exp $expNeg $expDigits $sbase i32 · $mant i64 · $result f64
|
|
26
|
+
|
|
27
|
+
// In-bounds byte read for a confirmed string `$v`. `__char_at` is ~95 WASM
|
|
28
|
+
// instructions — too large for V8 to inline — so a scan that calls it per
|
|
29
|
+
// char pays a real call plus a redundant SSO/view/bounds dispatch every step.
|
|
30
|
+
// A non-SSO string (the common case: source slices, heap strings) keeps its
|
|
31
|
+
// bytes contiguous at `$v & 0xFFFFFFFF` (`$sbase`); the read collapses to one
|
|
32
|
+
// `i32.load8_u`. The SSO test is loop-invariant — V8 hoists it — and the SSO
|
|
33
|
+
// arm still routes through `__char_at` (its bytes are packed in the pointer).
|
|
34
|
+
// Callers MUST declare `$sbase i32`, set it once after `$v` is final and a
|
|
35
|
+
// confirmed string, and only pass indices proven `< $len` (`__char_at` would
|
|
36
|
+
// otherwise return its OOB 0; the inline load has no such guard).
|
|
37
|
+
const SBASE_INIT = '(local.set $sbase (i32.wrap_i64 (i64.and (local.get $v) (i64.const 4294967295))))'
|
|
38
|
+
const chAt = idx => `(if (result i32)
|
|
39
|
+
(i64.eqz (i64.and (local.get $v) (i64.const 0x0000400000000000)))
|
|
40
|
+
(then (i32.load8_u (i32.add (local.get $sbase) ${idx})))
|
|
41
|
+
(else (call $__char_at (local.get $v) ${idx})))`
|
|
42
|
+
|
|
43
|
+
// chAt for reads NOT dominated by a `$i < $len` guard. `i32.and` does not
|
|
44
|
+
// short-circuit, so `(i32.and (lt_s $i $len) (… chAt …))` would still run the
|
|
45
|
+
// unguarded inline load when `$i == $len`. chAtSafe restores `__char_at`'s
|
|
46
|
+
// total contract (0 out of bounds), so such a site drops the now-redundant
|
|
47
|
+
// outer guard and compares chAtSafe directly against the wanted byte.
|
|
48
|
+
const chAtSafe = idx => `(if (result i32)
|
|
49
|
+
(i32.lt_s ${idx} (local.get $len))
|
|
50
|
+
(then ${chAt(idx)})
|
|
51
|
+
(else (i32.const 0)))`
|
|
52
|
+
|
|
53
|
+
// 18-significant-digit significand → $mant; $decExp tracks the base-10 exponent
|
|
54
|
+
// of dropped/fractional digits; $round defers a single round-up.
|
|
55
|
+
const DEC_SIGNIFICAND = `
|
|
56
|
+
(block $numDone (loop $numLoop
|
|
57
|
+
(br_if $numDone (i32.ge_s (local.get $i) (local.get $len)))
|
|
58
|
+
(local.set $c ${chAt('(local.get $i)')})
|
|
59
|
+
(if (i32.and (i32.eq (local.get $c) (i32.const 46)) (i32.eqz (local.get $dot)))
|
|
60
|
+
(then
|
|
61
|
+
(local.set $dot (i32.const 1))
|
|
62
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
63
|
+
(br $numLoop)))
|
|
64
|
+
(br_if $numDone
|
|
65
|
+
(i32.or
|
|
66
|
+
(i32.lt_s (local.get $c) (i32.const 48))
|
|
67
|
+
(i32.gt_s (local.get $c) (i32.const 57))))
|
|
68
|
+
(local.set $seen (i32.const 1))
|
|
69
|
+
(local.set $c (i32.sub (local.get $c) (i32.const 48)))
|
|
70
|
+
(if (i32.and (i32.eqz (local.get $sigDigits)) (i32.eqz (local.get $c)))
|
|
71
|
+
(then
|
|
72
|
+
(if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1)))))
|
|
73
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
74
|
+
(br $numLoop)))
|
|
75
|
+
;; Accumulate the significand in an i64 (exact to 18 decimal digits,
|
|
76
|
+
;; since 10^18 < 2^63) and convert to f64 once at the end — a single
|
|
77
|
+
;; correctly-rounded i64->f64 step instead of lossy per-digit f64 math.
|
|
78
|
+
(if (i32.lt_s (local.get $sigDigits) (i32.const 18))
|
|
79
|
+
(then
|
|
80
|
+
(local.set $mant
|
|
81
|
+
(i64.add
|
|
82
|
+
(i64.mul (local.get $mant) (i64.const 10))
|
|
83
|
+
(i64.extend_i32_s (local.get $c))))
|
|
84
|
+
(local.set $sigDigits (i32.add (local.get $sigDigits) (i32.const 1)))
|
|
85
|
+
(if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1))))))
|
|
86
|
+
(else
|
|
87
|
+
(if (i32.eqz (local.get $dropped))
|
|
88
|
+
(then (if (i32.ge_s (local.get $c) (i32.const 5)) (then (local.set $round (i32.const 1))))))
|
|
89
|
+
(local.set $dropped (i32.const 1))
|
|
90
|
+
(if (i32.eqz (local.get $dot)) (then (local.set $decExp (i32.add (local.get $decExp) (i32.const 1)))))))
|
|
91
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
92
|
+
(br $numLoop)))`
|
|
93
|
+
|
|
94
|
+
// No significant digit seen → NaN; apply the deferred round; $mant → $result.
|
|
95
|
+
const FINISH_SIGNIFICAND = `
|
|
96
|
+
(if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
|
|
97
|
+
(if (local.get $round) (then (local.set $mant (i64.add (local.get $mant) (i64.const 1)))))
|
|
98
|
+
(local.set $result (f64.convert_i64_u (local.get $mant)))`
|
|
99
|
+
|
|
100
|
+
// ExponentPart scan: 'e'/'E' + optional sign + digits → $exp / $expDigits.
|
|
101
|
+
// `tail` runs inside the e/E branch — Number rejects an empty exponent ("1e")
|
|
102
|
+
// as NaN, parseFloat ignores it, so each caller passes its own resolution.
|
|
103
|
+
const sciExponent = (tail) => `
|
|
104
|
+
(local.set $c ${chAtSafe('(local.get $i)')})
|
|
105
|
+
(if (i32.or
|
|
106
|
+
(i32.eq (local.get $c) (i32.const 101))
|
|
107
|
+
(i32.eq (local.get $c) (i32.const 69)))
|
|
108
|
+
(then
|
|
109
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
110
|
+
(if (i32.eq ${chAtSafe('(local.get $i)')} (i32.const 45))
|
|
111
|
+
(then (local.set $expNeg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
112
|
+
(if (i32.eq ${chAtSafe('(local.get $i)')} (i32.const 43))
|
|
113
|
+
(then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
114
|
+
(block $expDone (loop $expLoop
|
|
115
|
+
(br_if $expDone (i32.ge_s (local.get $i) (local.get $len)))
|
|
116
|
+
(local.set $c ${chAt('(local.get $i)')})
|
|
117
|
+
(br_if $expDone
|
|
118
|
+
(i32.or
|
|
119
|
+
(i32.lt_s (local.get $c) (i32.const 48))
|
|
120
|
+
(i32.gt_s (local.get $c) (i32.const 57))))
|
|
121
|
+
(local.set $exp
|
|
122
|
+
(i32.add
|
|
123
|
+
(i32.mul (local.get $exp) (i32.const 10))
|
|
124
|
+
(i32.sub (local.get $c) (i32.const 48))))
|
|
125
|
+
(local.set $expDigits (i32.add (local.get $expDigits) (i32.const 1)))
|
|
126
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
127
|
+
(br $expLoop)))
|
|
128
|
+
${tail}))`
|
|
129
|
+
|
|
130
|
+
// Apply the accumulated base-10 exponent to $result via __pow10.
|
|
131
|
+
const POW10_SCALE = `
|
|
132
|
+
(if (i32.gt_s (local.get $decExp) (i32.const 0))
|
|
133
|
+
(then (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $decExp))))))
|
|
134
|
+
(if (i32.lt_s (local.get $decExp) (i32.const 0))
|
|
135
|
+
(then (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const 0) (local.get $decExp)))))))`
|
|
136
|
+
|
|
18
137
|
export default (ctx) => {
|
|
19
138
|
Object.assign(ctx.core.stdlibDeps, {
|
|
20
139
|
__mkstr: ['__alloc'],
|
|
21
140
|
__ftoa: ['__itoa', '__pow10', '__mkstr', '__static_str', '__toExp'],
|
|
22
141
|
__toExp: ['__itoa', '__pow10', '__mkstr', '__static_str'],
|
|
23
|
-
__to_num: ['__char_at', '__str_byteLen', '__pow10', '__to_str'],
|
|
24
|
-
|
|
142
|
+
__to_num: ['__char_at', '__str_byteLen', '__pow10', '__to_str', '__skipws', '__ptr_aux'],
|
|
143
|
+
__skipws: ['__char_at', '__strws'],
|
|
144
|
+
__to_bigint: ['__char_at', '__str_byteLen', '__num_to_bigint'],
|
|
25
145
|
__parseInt: ['__char_at', '__str_byteLen'],
|
|
26
146
|
__parseFloat: ['__char_at', '__str_byteLen', '__pow10', '__to_str'],
|
|
27
147
|
})
|
|
@@ -34,6 +154,9 @@ export default (ctx) => {
|
|
|
34
154
|
// so accumulated error stays at O(log n) ULPs.
|
|
35
155
|
ctx.core.stdlib['__pow10'] = `(func $__pow10 (param $n i32) (result f64)
|
|
36
156
|
(local $r f64)
|
|
157
|
+
;; 10^309 already overflows f64 (max ~1.8e308); short-circuit so callers
|
|
158
|
+
;; get Infinity rather than the truncated product of a 9-bit decomposition.
|
|
159
|
+
(if (i32.ge_s (local.get $n) (i32.const 309)) (then (return (f64.const inf))))
|
|
37
160
|
(local.set $r (f64.const 1))
|
|
38
161
|
(if (i32.and (local.get $n) (i32.const 1))
|
|
39
162
|
(then (local.set $r (f64.mul (local.get $r) (f64.const 10)))))
|
|
@@ -106,16 +229,16 @@ export default (ctx) => {
|
|
|
106
229
|
(if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
|
|
107
230
|
(if (f64.eq (local.get $val) (f64.const -inf)) (then (return (call $__static_str (i32.const 2)))))
|
|
108
231
|
;; ES spec: |x| >= 1e21 or 0 < |x| < 1e-6 → exponential notation (default mode only).
|
|
109
|
-
;;
|
|
110
|
-
;; ECMAScript shortest-repr ideal, but valid output
|
|
232
|
+
;; __toExp clamps the digit count so its scaled mantissa fits an unsigned i32.
|
|
233
|
+
;; Fewer digits than ECMAScript shortest-repr ideal, but valid output.
|
|
111
234
|
(if (i32.eqz (local.get $mode))
|
|
112
235
|
(then
|
|
113
236
|
(if (f64.ge (f64.abs (local.get $val)) (f64.const 1e21))
|
|
114
|
-
(then (return (call $__toExp (local.get $val) (i32.const
|
|
237
|
+
(then (return (call $__toExp (local.get $val) (i32.const 8) (i32.const 1)))))
|
|
115
238
|
(if (i32.and
|
|
116
239
|
(f64.gt (f64.abs (local.get $val)) (f64.const 0))
|
|
117
240
|
(f64.lt (f64.abs (local.get $val)) (f64.const 1e-6)))
|
|
118
|
-
(then (return (call $__toExp (local.get $val) (i32.const
|
|
241
|
+
(then (return (call $__toExp (local.get $val) (i32.const 8) (i32.const 1)))))))
|
|
119
242
|
(local.set $buf (call $__alloc (i32.const 40)))
|
|
120
243
|
;; Sign
|
|
121
244
|
(if (f64.lt (local.get $val) (f64.const 0))
|
|
@@ -204,15 +327,22 @@ export default (ctx) => {
|
|
|
204
327
|
(then (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))))))
|
|
205
328
|
(call $__mkstr (local.get $buf) (local.get $pos)))`
|
|
206
329
|
|
|
207
|
-
// __toExp(val: f64, prec: i32) → f64 (NaN-boxed string)
|
|
208
|
-
// Format: [-]d.ddd...e[+/-]dd — integer-based digit extraction
|
|
209
|
-
|
|
330
|
+
// __toExp(val: f64, prec: i32, strip: i32) → f64 (NaN-boxed string)
|
|
331
|
+
// Format: [-]d.ddd...e[+/-]dd — integer-based digit extraction.
|
|
332
|
+
// strip=1 drops trailing fractional zeros (default ToString); strip=0 keeps
|
|
333
|
+
// the exact prec digits (toExponential/toPrecision need a fixed digit count).
|
|
334
|
+
ctx.core.stdlib['__toExp'] = `(func $__toExp (param $val f64) (param $prec i32) (param $strip i32) (result f64)
|
|
210
335
|
(local $buf i32) (local $pos i32) (local $neg i32) (local $exp i32)
|
|
211
336
|
(local $len i32) (local $i i32) (local $j i32)
|
|
212
337
|
(local $mantissa f64) (local $scale f64)
|
|
213
338
|
(if (f64.ne (local.get $val) (local.get $val)) (then (return (call $__static_str (i32.const 0)))))
|
|
214
339
|
(if (f64.eq (local.get $val) (f64.const inf)) (then (return (call $__static_str (i32.const 1)))))
|
|
215
340
|
(if (f64.eq (local.get $val) (f64.const -inf)) (then (return (call $__static_str (i32.const 2)))))
|
|
341
|
+
;; The scaled mantissa is (prec+1) digits; cap prec at 8 so it stays below
|
|
342
|
+
;; 2^32 (10^9 < 2^32 < 10^10), otherwise i32.trunc_f64_u below traps with
|
|
343
|
+
;; "float unrepresentable in integer range" — e.g. 7.5e-151 normalizes to
|
|
344
|
+
;; 7.5 and 7.5*10^9 already overflows an unsigned i32.
|
|
345
|
+
(if (i32.gt_s (local.get $prec) (i32.const 8)) (then (local.set $prec (i32.const 8))))
|
|
216
346
|
(local.set $buf (call $__alloc (i32.const 32)))
|
|
217
347
|
;; Sign
|
|
218
348
|
(if (f64.lt (local.get $val) (f64.const 0))
|
|
@@ -258,6 +388,16 @@ export default (ctx) => {
|
|
|
258
388
|
(i32.store8 (i32.add (local.get $buf) (i32.add (local.get $pos) (i32.const 1))) (i32.const 46))
|
|
259
389
|
(local.set $pos (i32.add (local.get $pos) (i32.add (local.get $len) (i32.const 1)))))
|
|
260
390
|
(else (local.set $pos (i32.add (local.get $pos) (local.get $len)))))
|
|
391
|
+
;; Shortest form: drop trailing zeros (and a bare '.') from the mantissa.
|
|
392
|
+
;; The leading digit is always 1-9, so the walk-back stops at the '.' at worst.
|
|
393
|
+
(if (i32.and (local.get $strip) (i32.gt_s (local.get $prec) (i32.const 0)))
|
|
394
|
+
(then
|
|
395
|
+
(block $sz (loop $szl
|
|
396
|
+
(br_if $sz (i32.ne (i32.load8_u (i32.sub (i32.add (local.get $buf) (local.get $pos)) (i32.const 1))) (i32.const 48)))
|
|
397
|
+
(local.set $pos (i32.sub (local.get $pos) (i32.const 1)))
|
|
398
|
+
(br $szl)))
|
|
399
|
+
(if (i32.eq (i32.load8_u (i32.sub (i32.add (local.get $buf) (local.get $pos)) (i32.const 1))) (i32.const 46))
|
|
400
|
+
(then (local.set $pos (i32.sub (local.get $pos) (i32.const 1)))))))
|
|
261
401
|
;; Write 'e', sign, exponent
|
|
262
402
|
(i32.store8 (i32.add (local.get $buf) (local.get $pos)) (i32.const 101))
|
|
263
403
|
(local.set $pos (i32.add (local.get $pos) (i32.const 1)))
|
|
@@ -348,6 +488,17 @@ export default (ctx) => {
|
|
|
348
488
|
['f64.eq', ['local.get', `$${t}`], ['f64.trunc', ['local.get', `$${t}`]]]], 'i32')
|
|
349
489
|
}
|
|
350
490
|
|
|
491
|
+
// Number.isSafeInteger(x): integer AND |x| ≤ 2^53 − 1.
|
|
492
|
+
ctx.core.emit['Number.isSafeInteger'] = (x) => {
|
|
493
|
+
const v = asF64(emit(x))
|
|
494
|
+
const t = temp('t')
|
|
495
|
+
return typed(['i32.and',
|
|
496
|
+
['i32.and',
|
|
497
|
+
['f64.eq', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
|
|
498
|
+
['f64.eq', ['local.get', `$${t}`], ['f64.trunc', ['local.get', `$${t}`]]]],
|
|
499
|
+
['f64.le', ['f64.abs', ['local.get', `$${t}`]], ['f64.const', 9007199254740991]]], 'i32')
|
|
500
|
+
}
|
|
501
|
+
|
|
351
502
|
ctx.core.stdlib['__parseInt'] = `(func $__parseInt (param $str i64) (param $radix i32) (result f64)
|
|
352
503
|
(local $off i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
|
|
353
504
|
(local $result f64) (local $digit i32) (local $seen i32) (local $f f64)
|
|
@@ -403,17 +554,72 @@ export default (ctx) => {
|
|
|
403
554
|
(if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
|
|
404
555
|
(if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
|
|
405
556
|
|
|
557
|
+
// __strws(c: i32) → i32 — ECMA StrWhiteSpace predicate. Covers TAB..CR, SP,
|
|
558
|
+
// NBSP, BOM, LS/PS, and every Unicode Space_Separator. U+180E is *not*
|
|
559
|
+
// whitespace (declassified in Unicode 6.3).
|
|
560
|
+
ctx.core.stdlib['__strws'] = `(func $__strws (param $c i32) (result i32)
|
|
561
|
+
(i32.or
|
|
562
|
+
(i32.or
|
|
563
|
+
(i32.and (i32.ge_s (local.get $c) (i32.const 9)) (i32.le_s (local.get $c) (i32.const 13)))
|
|
564
|
+
(i32.or (i32.eq (local.get $c) (i32.const 32)) (i32.eq (local.get $c) (i32.const 160))))
|
|
565
|
+
(i32.or
|
|
566
|
+
(i32.or
|
|
567
|
+
(i32.eq (local.get $c) (i32.const 0x1680))
|
|
568
|
+
(i32.and (i32.ge_s (local.get $c) (i32.const 0x2000)) (i32.le_s (local.get $c) (i32.const 0x200a))))
|
|
569
|
+
(i32.or
|
|
570
|
+
(i32.or (i32.eq (local.get $c) (i32.const 0x2028)) (i32.eq (local.get $c) (i32.const 0x2029)))
|
|
571
|
+
(i32.or
|
|
572
|
+
(i32.or (i32.eq (local.get $c) (i32.const 0x202f)) (i32.eq (local.get $c) (i32.const 0x205f)))
|
|
573
|
+
(i32.or (i32.eq (local.get $c) (i32.const 0x3000)) (i32.eq (local.get $c) (i32.const 0xfeff))))))))`
|
|
574
|
+
|
|
575
|
+
// __skipws(v, i, len) → i32 — advance the byte index i past any run of
|
|
576
|
+
// StrWhiteSpace. Strings are UTF-8, so each step decodes one scalar: every
|
|
577
|
+
// whitespace code point is ≤ U+FEFF (≤ 3 bytes), and a 4-byte lead is never
|
|
578
|
+
// whitespace, so it (and any non-space scalar) ends the run.
|
|
579
|
+
ctx.core.stdlib['__skipws'] = `(func $__skipws (param $v i64) (param $i i32) (param $len i32) (result i32)
|
|
580
|
+
(local $b i32) (local $cp i32) (local $n i32) (local $sbase i32)
|
|
581
|
+
${SBASE_INIT}
|
|
582
|
+
(block $done (loop $l
|
|
583
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $len)))
|
|
584
|
+
(local.set $b ${chAt('(local.get $i)')})
|
|
585
|
+
(if (i32.lt_u (local.get $b) (i32.const 0x80))
|
|
586
|
+
(then (local.set $cp (local.get $b)) (local.set $n (i32.const 1)))
|
|
587
|
+
(else (if (i32.lt_u (local.get $b) (i32.const 0xe0))
|
|
588
|
+
(then
|
|
589
|
+
(local.set $n (i32.const 2))
|
|
590
|
+
(local.set $cp (i32.or
|
|
591
|
+
(i32.shl (i32.and (local.get $b) (i32.const 0x1f)) (i32.const 6))
|
|
592
|
+
(i32.and (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 0x3f)))))
|
|
593
|
+
(else (if (i32.lt_u (local.get $b) (i32.const 0xf0))
|
|
594
|
+
(then
|
|
595
|
+
(local.set $n (i32.const 3))
|
|
596
|
+
(local.set $cp (i32.or (i32.or
|
|
597
|
+
(i32.shl (i32.and (local.get $b) (i32.const 0x0f)) (i32.const 12))
|
|
598
|
+
(i32.shl (i32.and (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 0x3f)) (i32.const 6)))
|
|
599
|
+
(i32.and (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 2))) (i32.const 0x3f)))))
|
|
600
|
+
(else (return (local.get $i))))))))
|
|
601
|
+
(br_if $done (i32.eqz (call $__strws (local.get $cp))))
|
|
602
|
+
(local.set $i (i32.add (local.get $i) (local.get $n)))
|
|
603
|
+
(br $l)))
|
|
604
|
+
(local.get $i))`
|
|
605
|
+
|
|
406
606
|
ctx.core.stdlib['__to_num'] = `(func $__to_num (param $v i64) (result f64)
|
|
407
607
|
(local $t i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
|
|
408
608
|
(local $seen i32) (local $exp i32) (local $expNeg i32) (local $expDigits i32)
|
|
409
609
|
(local $dot i32) (local $sigDigits i32) (local $decExp i32) (local $dropped i32) (local $round i32)
|
|
410
|
-
(local $
|
|
411
|
-
(local $result f64) (local $f f64)
|
|
610
|
+
(local $radix i32) (local $digit i32) (local $sbase i32)
|
|
611
|
+
(local $result f64) (local $f f64) (local $mant i64)
|
|
412
612
|
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
413
613
|
(if (f64.eq (local.get $f) (local.get $f)) (then (return (local.get $f))))
|
|
414
614
|
(if (i64.eq (local.get $v) (i64.const ${NULL_NAN})) (then (return (f64.const 0))))
|
|
415
615
|
(if (i64.eq (local.get $v) (i64.const ${UNDEF_NAN})) (then (return (f64.const nan))))
|
|
416
616
|
(local.set $t (call $__ptr_type (local.get $v)))
|
|
617
|
+
;; ToNumber(Symbol) is a TypeError. A Symbol is an ATOM (type 0) with a user
|
|
618
|
+
;; atom-id (>= 16); null/undefined returned above, and a bare NaN carries
|
|
619
|
+
;; aux 0, so type==0 && aux>=16 uniquely identifies a Symbol.
|
|
620
|
+
(if (i32.and (i32.eqz (local.get $t))
|
|
621
|
+
(i32.ge_u (call $__ptr_aux (local.get $v)) (i32.const 16)))
|
|
622
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
417
623
|
;; Non-string values go through ToString per JS spec, then re-check the
|
|
418
624
|
;; type in case ToString itself returned a non-string sentinel.
|
|
419
625
|
(if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
|
|
@@ -423,154 +629,160 @@ export default (ctx) => {
|
|
|
423
629
|
(if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
|
|
424
630
|
(then (return (f64.const nan))))))
|
|
425
631
|
(local.set $len (call $__str_byteLen (local.get $v)))
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
(local.set $wsEnd (local.get $i))
|
|
433
|
-
;; Sign.
|
|
434
|
-
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
435
|
-
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
|
|
436
|
-
(then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
437
|
-
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
438
|
-
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
|
|
439
|
-
(then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
440
|
-
;; 0x prefix → hex parse and early return
|
|
632
|
+
${SBASE_INIT}
|
|
633
|
+
;; Trim leading whitespace. An empty / all-whitespace string is +0.
|
|
634
|
+
(local.set $i (call $__skipws (local.get $v) (i32.const 0) (local.get $len)))
|
|
635
|
+
(if (i32.ge_s (local.get $i) (local.get $len)) (then (return (f64.const 0))))
|
|
636
|
+
;; NonDecimalIntegerLiteral (0x / 0o / 0b). Per the grammar no sign may
|
|
637
|
+
;; precede the prefix, so it is matched before sign consumption.
|
|
441
638
|
(if (i32.and
|
|
442
|
-
(i32.
|
|
443
|
-
(i32.
|
|
444
|
-
|
|
445
|
-
|
|
639
|
+
(i32.lt_s (i32.add (local.get $i) (i32.const 1)) (local.get $len))
|
|
640
|
+
(i32.eq ${chAt('(local.get $i)')} (i32.const 48)))
|
|
641
|
+
(then
|
|
642
|
+
(local.set $c ${chAt('(i32.add (local.get $i) (i32.const 1))')})
|
|
643
|
+
(if (i32.or (i32.eq (local.get $c) (i32.const 120)) (i32.eq (local.get $c) (i32.const 88)))
|
|
644
|
+
(then (local.set $radix (i32.const 16))))
|
|
645
|
+
(if (i32.or (i32.eq (local.get $c) (i32.const 111)) (i32.eq (local.get $c) (i32.const 79)))
|
|
646
|
+
(then (local.set $radix (i32.const 8))))
|
|
647
|
+
(if (i32.or (i32.eq (local.get $c) (i32.const 98)) (i32.eq (local.get $c) (i32.const 66)))
|
|
648
|
+
(then (local.set $radix (i32.const 2))))))
|
|
649
|
+
(if (local.get $radix)
|
|
446
650
|
(then
|
|
447
651
|
(local.set $i (i32.add (local.get $i) (i32.const 2)))
|
|
448
|
-
(block $
|
|
449
|
-
(br_if $
|
|
450
|
-
(local.set $c
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
(
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
(br_if $numDone (i32.ge_s (local.get $i) (local.get $len)))
|
|
465
|
-
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
466
|
-
(if (i32.and (i32.eq (local.get $c) (i32.const 46)) (i32.eqz (local.get $dot)))
|
|
467
|
-
(then
|
|
468
|
-
(local.set $dot (i32.const 1))
|
|
469
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
470
|
-
(br $numLoop)))
|
|
471
|
-
(br_if $numDone
|
|
472
|
-
(i32.or
|
|
473
|
-
(i32.lt_s (local.get $c) (i32.const 48))
|
|
474
|
-
(i32.gt_s (local.get $c) (i32.const 57))))
|
|
475
|
-
(local.set $seen (i32.const 1))
|
|
476
|
-
(local.set $c (i32.sub (local.get $c) (i32.const 48)))
|
|
477
|
-
(if (i32.and (i32.eqz (local.get $sigDigits)) (i32.eqz (local.get $c)))
|
|
478
|
-
(then
|
|
479
|
-
(if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1)))))
|
|
652
|
+
(block $ndDone (loop $ndLoop
|
|
653
|
+
(br_if $ndDone (i32.ge_s (local.get $i) (local.get $len)))
|
|
654
|
+
(local.set $c ${chAt('(local.get $i)')})
|
|
655
|
+
;; Decode digit; 99 sentinel for any non-[0-9a-fA-F] char so the
|
|
656
|
+
;; unsigned ">= radix" test rejects it and any out-of-base digit.
|
|
657
|
+
(local.set $digit
|
|
658
|
+
(if (result i32) (i32.and (i32.ge_s (local.get $c) (i32.const 48)) (i32.le_s (local.get $c) (i32.const 57)))
|
|
659
|
+
(then (i32.sub (local.get $c) (i32.const 48)))
|
|
660
|
+
(else (if (result i32) (i32.and (i32.ge_s (local.get $c) (i32.const 97)) (i32.le_s (local.get $c) (i32.const 102)))
|
|
661
|
+
(then (i32.sub (local.get $c) (i32.const 87)))
|
|
662
|
+
(else (if (result i32) (i32.and (i32.ge_s (local.get $c) (i32.const 65)) (i32.le_s (local.get $c) (i32.const 70)))
|
|
663
|
+
(then (i32.sub (local.get $c) (i32.const 55)))
|
|
664
|
+
(else (i32.const 99))))))))
|
|
665
|
+
(br_if $ndDone (i32.ge_u (local.get $digit) (local.get $radix)))
|
|
666
|
+
(local.set $result (f64.add (f64.mul (local.get $result) (f64.convert_i32_s (local.get $radix))) (f64.convert_i32_s (local.get $digit))))
|
|
667
|
+
(local.set $seen (i32.const 1))
|
|
480
668
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
481
|
-
(br $
|
|
482
|
-
|
|
483
|
-
(if (
|
|
484
|
-
(
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
497
|
-
(br $numLoop)))
|
|
498
|
-
;; No digits seen: empty/whitespace-only string coerces to 0; non-numeric
|
|
499
|
-
;; content (sign-only, "abc", etc.) coerces to NaN.
|
|
500
|
-
(if (i32.eqz (local.get $seen))
|
|
669
|
+
(br $ndLoop)))
|
|
670
|
+
;; No digits, or trailing non-whitespace ("0b1.0", "0xg") → NaN.
|
|
671
|
+
(if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
|
|
672
|
+
(local.set $i (call $__skipws (local.get $v) (local.get $i) (local.get $len)))
|
|
673
|
+
(if (i32.lt_s (local.get $i) (local.get $len)) (then (return (f64.const nan))))
|
|
674
|
+
(return (local.get $result))))
|
|
675
|
+
;; Sign (StrDecimalLiteral only).
|
|
676
|
+
(if (i32.eq ${chAt('(local.get $i)')} (i32.const 45))
|
|
677
|
+
(then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
678
|
+
(if (i32.eq ${chAtSafe('(local.get $i)')} (i32.const 43))
|
|
679
|
+
(then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
680
|
+
;; "Infinity" — the only non-numeric token ToNumber accepts. The 8 letters
|
|
681
|
+
;; are packed little-endian in one i64; any mismatch, short input, or
|
|
682
|
+
;; trailing non-whitespace makes the whole string NaN.
|
|
683
|
+
(if (i32.eq ${chAtSafe('(local.get $i)')} (i32.const 73))
|
|
501
684
|
(then
|
|
502
|
-
(
|
|
503
|
-
(
|
|
685
|
+
(block $infBad
|
|
686
|
+
(local.set $digit (i32.const 0))
|
|
687
|
+
(loop $infl
|
|
688
|
+
(if (i32.lt_s (local.get $digit) (i32.const 8))
|
|
689
|
+
(then
|
|
690
|
+
(br_if $infBad (i32.ge_s (i32.add (local.get $i) (local.get $digit)) (local.get $len)))
|
|
691
|
+
(br_if $infBad (i32.ne
|
|
692
|
+
${chAt('(i32.add (local.get $i) (local.get $digit))')}
|
|
693
|
+
(i32.and (i32.wrap_i64 (i64.shr_u (i64.const 0x7974696e69666e49)
|
|
694
|
+
(i64.extend_i32_u (i32.shl (local.get $digit) (i32.const 3))))) (i32.const 255))))
|
|
695
|
+
(local.set $digit (i32.add (local.get $digit) (i32.const 1)))
|
|
696
|
+
(br $infl))))
|
|
697
|
+
(local.set $i (call $__skipws (local.get $v) (i32.add (local.get $i) (i32.const 8)) (local.get $len)))
|
|
698
|
+
(br_if $infBad (i32.lt_s (local.get $i) (local.get $len)))
|
|
699
|
+
(return (if (result f64) (local.get $neg) (then (f64.const -inf)) (else (f64.const inf)))))
|
|
504
700
|
(return (f64.const nan))))
|
|
505
|
-
|
|
506
|
-
;;
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
(then (local.set $
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
522
|
-
(br_if $expDone
|
|
523
|
-
(i32.or
|
|
524
|
-
(i32.lt_s (local.get $c) (i32.const 48))
|
|
525
|
-
(i32.gt_s (local.get $c) (i32.const 57))))
|
|
526
|
-
(local.set $exp
|
|
527
|
-
(i32.add
|
|
528
|
-
(i32.mul (local.get $exp) (i32.const 10))
|
|
529
|
-
(i32.sub (local.get $c) (i32.const 48))))
|
|
530
|
-
(local.set $expDigits (i32.add (local.get $expDigits) (i32.const 1)))
|
|
531
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
532
|
-
(br $expLoop)))
|
|
533
|
-
(if (local.get $expDigits)
|
|
534
|
-
(then
|
|
535
|
-
(if (local.get $expNeg)
|
|
536
|
-
(then (local.set $decExp (i32.sub (local.get $decExp) (local.get $exp))))
|
|
537
|
-
(else (local.set $decExp (i32.add (local.get $decExp) (local.get $exp)))))))))
|
|
538
|
-
(if (i32.gt_s (local.get $decExp) (i32.const 0))
|
|
539
|
-
(then (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $decExp))))))
|
|
540
|
-
(if (i32.lt_s (local.get $decExp) (i32.const 0))
|
|
541
|
-
(then (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const 0) (local.get $decExp)))))))
|
|
701
|
+
;; Decimal significand. Keep 18 significant decimal digits, track the
|
|
702
|
+
;; base-10 exponent for skipped digits, and round once before pow10 scaling.
|
|
703
|
+
${DEC_SIGNIFICAND}
|
|
704
|
+
;; No digits — the literal was a bare sign or stray text ("abc", "+") → NaN.
|
|
705
|
+
;; (Empty / all-whitespace strings already returned +0 above.)
|
|
706
|
+
${FINISH_SIGNIFICAND}
|
|
707
|
+
;; Scientific notation. 'e'/'E' commits to an ExponentPart — at least one
|
|
708
|
+
;; digit must follow ("1e", "5e+" are NaN).
|
|
709
|
+
${sciExponent(`(if (i32.eqz (local.get $expDigits)) (then (return (f64.const nan))))
|
|
710
|
+
(if (local.get $expNeg)
|
|
711
|
+
(then (local.set $decExp (i32.sub (local.get $decExp) (local.get $exp))))
|
|
712
|
+
(else (local.set $decExp (i32.add (local.get $decExp) (local.get $exp)))))`)}
|
|
713
|
+
;; Reject trailing non-whitespace ("5px", numeric separators "1_0", …).
|
|
714
|
+
(local.set $i (call $__skipws (local.get $v) (local.get $i) (local.get $len)))
|
|
715
|
+
(if (i32.lt_s (local.get $i) (local.get $len)) (then (return (f64.const nan))))
|
|
716
|
+
${POW10_SCALE}
|
|
542
717
|
(if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
|
|
543
718
|
|
|
719
|
+
// NumberToBigInt: a RangeError unless n is an integral Number — finite and
|
|
720
|
+
// equal to its own truncation. NaN fails the f64.eq integrality test;
|
|
721
|
+
// ±Infinity fails the finite test. Non-integers (1.1, .5, …) fail integrality.
|
|
722
|
+
// The throw rides $__jz_err so `assert.throws`/try-catch observe it.
|
|
723
|
+
ctx.core.stdlib['__num_to_bigint'] = `(func $__num_to_bigint (param $n f64) (result f64)
|
|
724
|
+
(if (i32.eqz (i32.and
|
|
725
|
+
(f64.eq (local.get $n) (f64.trunc (local.get $n)))
|
|
726
|
+
(f64.lt (f64.abs (local.get $n)) (f64.const inf))))
|
|
727
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
728
|
+
(f64.reinterpret_i64 (i64.trunc_sat_f64_s (local.get $n))))`
|
|
729
|
+
|
|
730
|
+
// StringToBigInt: strict — the whole trimmed string must be a single integer
|
|
731
|
+
// literal, else a SyntaxError ($__jz_err). Unlike parseInt this does NOT stop
|
|
732
|
+
// at the first bad char: `BigInt("10n")` and `BigInt("000 12")` throw. Empty
|
|
733
|
+
// or all-whitespace strings parse to 0n. Radix prefixes 0b/0o/0x (case-
|
|
734
|
+
// insensitive) are recognised only when no sign precedes them, so `-0x1`
|
|
735
|
+
// surfaces its `x` as an invalid decimal digit and throws, as the spec wants.
|
|
736
|
+
// (jz's BigInt is i64-backed, so values past 2^63 wrap — out of these tests'
|
|
737
|
+
// range.)
|
|
544
738
|
ctx.core.stdlib['__to_bigint'] = `(func $__to_bigint (param $v i64) (result f64)
|
|
545
|
-
(local $t i32) (local $len i32) (local $i i32) (local $
|
|
546
|
-
(local $
|
|
739
|
+
(local $t i32) (local $len i32) (local $i i32) (local $end i32) (local $c i32)
|
|
740
|
+
(local $neg i32) (local $sign i32) (local $radix i32) (local $digit i32)
|
|
741
|
+
(local $seen i32) (local $result i64) (local $f f64)
|
|
547
742
|
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
548
743
|
(if (f64.eq (local.get $f) (local.get $f))
|
|
549
|
-
(then (return (
|
|
744
|
+
(then (return (call $__num_to_bigint (local.get $f)))))
|
|
550
745
|
(local.set $t (call $__ptr_type (local.get $v)))
|
|
551
746
|
(if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
|
|
552
747
|
(then (return (f64.reinterpret_i64 (i64.const 0)))))
|
|
553
748
|
(local.set $len (call $__str_byteLen (local.get $v)))
|
|
749
|
+
(local.set $end (local.get $len))
|
|
750
|
+
;; Trim leading whitespace (any byte <= 32).
|
|
554
751
|
(block $ws (loop $wsl
|
|
555
|
-
(br_if $ws (i32.ge_s (local.get $i) (local.get $
|
|
752
|
+
(br_if $ws (i32.ge_s (local.get $i) (local.get $end)))
|
|
556
753
|
(br_if $ws (i32.gt_s (call $__char_at (local.get $v) (local.get $i)) (i32.const 32)))
|
|
557
754
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
558
755
|
(br $wsl)))
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
(
|
|
562
|
-
|
|
563
|
-
(
|
|
564
|
-
(
|
|
756
|
+
;; Trim trailing whitespace.
|
|
757
|
+
(block $te (loop $tel
|
|
758
|
+
(br_if $te (i32.le_s (local.get $end) (local.get $i)))
|
|
759
|
+
(br_if $te (i32.gt_s (call $__char_at (local.get $v) (i32.sub (local.get $end) (i32.const 1))) (i32.const 32)))
|
|
760
|
+
(local.set $end (i32.sub (local.get $end) (i32.const 1)))
|
|
761
|
+
(br $tel)))
|
|
762
|
+
;; Empty / all-whitespace string → 0n.
|
|
763
|
+
(if (i32.ge_s (local.get $i) (local.get $end))
|
|
764
|
+
(then (return (f64.reinterpret_i64 (i64.const 0)))))
|
|
765
|
+
;; Optional single leading sign — decimal literals only.
|
|
766
|
+
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
767
|
+
(if (i32.eq (local.get $c) (i32.const 45))
|
|
768
|
+
(then (local.set $neg (i32.const 1)) (local.set $sign (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1))))
|
|
769
|
+
(else (if (i32.eq (local.get $c) (i32.const 43))
|
|
770
|
+
(then (local.set $sign (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))))
|
|
565
771
|
(local.set $radix (i32.const 10))
|
|
566
|
-
(
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
772
|
+
;; Radix prefix 0b/0o/0x (case-insensitive) — not allowed after a sign.
|
|
773
|
+
(if (i32.and (i32.eqz (local.get $sign))
|
|
774
|
+
(i32.and (i32.lt_s (i32.add (local.get $i) (i32.const 1)) (local.get $end))
|
|
775
|
+
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 48))))
|
|
776
|
+
(then
|
|
777
|
+
(local.set $c (i32.or (call $__char_at (local.get $v) (i32.add (local.get $i) (i32.const 1))) (i32.const 0x20)))
|
|
778
|
+
(if (i32.eq (local.get $c) (i32.const 98)) (then (local.set $radix (i32.const 2))))
|
|
779
|
+
(if (i32.eq (local.get $c) (i32.const 111)) (then (local.set $radix (i32.const 8))))
|
|
780
|
+
(if (i32.eq (local.get $c) (i32.const 120)) (then (local.set $radix (i32.const 16))))
|
|
781
|
+
(if (i32.ne (local.get $radix) (i32.const 10))
|
|
782
|
+
(then (local.set $i (i32.add (local.get $i) (i32.const 2)))))))
|
|
783
|
+
;; Strict scan — every remaining char must be a valid radix digit.
|
|
572
784
|
(block $done (loop $lp
|
|
573
|
-
(br_if $done (i32.ge_s (local.get $i) (local.get $
|
|
785
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $end)))
|
|
574
786
|
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
575
787
|
(local.set $digit (i32.const -1))
|
|
576
788
|
(if (i32.and (i32.ge_s (local.get $c) (i32.const 48)) (i32.le_s (local.get $c) (i32.const 57)))
|
|
@@ -579,7 +791,8 @@ export default (ctx) => {
|
|
|
579
791
|
(then (local.set $digit (i32.sub (local.get $c) (i32.const 87)))))
|
|
580
792
|
(if (i32.and (i32.ge_s (local.get $c) (i32.const 65)) (i32.le_s (local.get $c) (i32.const 90)))
|
|
581
793
|
(then (local.set $digit (i32.sub (local.get $c) (i32.const 55)))))
|
|
582
|
-
(
|
|
794
|
+
(if (i32.or (i32.lt_s (local.get $digit) (i32.const 0)) (i32.ge_s (local.get $digit) (local.get $radix)))
|
|
795
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
583
796
|
(local.set $seen (i32.const 1))
|
|
584
797
|
(local.set $result
|
|
585
798
|
(i64.add
|
|
@@ -587,7 +800,8 @@ export default (ctx) => {
|
|
|
587
800
|
(i64.extend_i32_s (local.get $digit))))
|
|
588
801
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
589
802
|
(br $lp)))
|
|
590
|
-
|
|
803
|
+
;; A sign or radix prefix with no digits ("-", "0x", "0b") is a SyntaxError.
|
|
804
|
+
(if (i32.eqz (local.get $seen)) (then (throw $__jz_err (f64.const 0))))
|
|
591
805
|
(f64.reinterpret_i64
|
|
592
806
|
(if (result i64) (local.get $neg)
|
|
593
807
|
(then (i64.sub (i64.const 0) (local.get $result)))
|
|
@@ -597,7 +811,7 @@ export default (ctx) => {
|
|
|
597
811
|
(local $t i32) (local $len i32) (local $i i32) (local $c i32) (local $neg i32)
|
|
598
812
|
(local $seen i32) (local $exp i32) (local $expNeg i32) (local $expDigits i32)
|
|
599
813
|
(local $dot i32) (local $sigDigits i32) (local $decExp i32) (local $dropped i32) (local $round i32)
|
|
600
|
-
(local $result f64) (local $f f64)
|
|
814
|
+
(local $result f64) (local $f f64) (local $mant i64) (local $sbase i32)
|
|
601
815
|
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
602
816
|
(if (f64.eq (local.get $f) (local.get $f)) (then (return (local.get $f))))
|
|
603
817
|
(local.set $t (call $__ptr_type (local.get $v)))
|
|
@@ -611,99 +825,43 @@ export default (ctx) => {
|
|
|
611
825
|
(if (i32.ne (local.get $t) (i32.const ${PTR.STRING}))
|
|
612
826
|
(then (return (f64.const nan))))))
|
|
613
827
|
(local.set $len (call $__str_byteLen (local.get $v)))
|
|
828
|
+
${SBASE_INIT}
|
|
614
829
|
;; Skip leading whitespace.
|
|
615
830
|
(block $ws (loop $wsl
|
|
616
831
|
(br_if $ws (i32.ge_s (local.get $i) (local.get $len)))
|
|
617
|
-
(br_if $ws (i32.gt_s
|
|
832
|
+
(br_if $ws (i32.gt_s ${chAt('(local.get $i)')} (i32.const 32)))
|
|
618
833
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
619
834
|
(br $wsl)))
|
|
620
835
|
;; Sign.
|
|
621
|
-
(if (i32.
|
|
622
|
-
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
|
|
836
|
+
(if (i32.eq ${chAtSafe('(local.get $i)')} (i32.const 45))
|
|
623
837
|
(then (local.set $neg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
624
|
-
(if (i32.
|
|
625
|
-
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
|
|
838
|
+
(if (i32.eq ${chAtSafe('(local.get $i)')} (i32.const 43))
|
|
626
839
|
(then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
627
|
-
;; Decimal significand. Keep
|
|
840
|
+
;; Decimal significand. Keep 18 significant decimal digits, track the
|
|
628
841
|
;; base-10 exponent for skipped digits, and round once before pow10 scaling.
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
632
|
-
(if (i32.and (i32.eq (local.get $c) (i32.const 46)) (i32.eqz (local.get $dot)))
|
|
633
|
-
(then
|
|
634
|
-
(local.set $dot (i32.const 1))
|
|
635
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
636
|
-
(br $numLoop)))
|
|
637
|
-
(br_if $numDone
|
|
638
|
-
(i32.or
|
|
639
|
-
(i32.lt_s (local.get $c) (i32.const 48))
|
|
640
|
-
(i32.gt_s (local.get $c) (i32.const 57))))
|
|
641
|
-
(local.set $seen (i32.const 1))
|
|
642
|
-
(local.set $c (i32.sub (local.get $c) (i32.const 48)))
|
|
643
|
-
(if (i32.and (i32.eqz (local.get $sigDigits)) (i32.eqz (local.get $c)))
|
|
644
|
-
(then
|
|
645
|
-
(if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1)))))
|
|
646
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
647
|
-
(br $numLoop)))
|
|
648
|
-
(if (i32.lt_s (local.get $sigDigits)
|
|
649
|
-
(if (result i32) (local.get $dot) (then (i32.const 16)) (else (i32.const 17))))
|
|
650
|
-
(then
|
|
651
|
-
(local.set $result
|
|
652
|
-
(f64.add
|
|
653
|
-
(f64.mul (local.get $result) (f64.const 10))
|
|
654
|
-
(f64.convert_i32_s (local.get $c))))
|
|
655
|
-
(local.set $sigDigits (i32.add (local.get $sigDigits) (i32.const 1)))
|
|
656
|
-
(if (local.get $dot) (then (local.set $decExp (i32.sub (local.get $decExp) (i32.const 1))))))
|
|
657
|
-
(else
|
|
658
|
-
(if (i32.eqz (local.get $dropped))
|
|
659
|
-
(then (if (i32.ge_s (local.get $c) (i32.const 5)) (then (local.set $round (i32.const 1))))))
|
|
660
|
-
(local.set $dropped (i32.const 1))
|
|
661
|
-
(if (i32.eqz (local.get $dot)) (then (local.set $decExp (i32.add (local.get $decExp) (i32.const 1)))))))
|
|
662
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
663
|
-
(br $numLoop)))
|
|
664
|
-
(if (i32.eqz (local.get $seen)) (then (return (f64.const nan))))
|
|
665
|
-
(if (local.get $round) (then (local.set $result (f64.add (local.get $result) (f64.const 1)))))
|
|
842
|
+
${DEC_SIGNIFICAND}
|
|
843
|
+
${FINISH_SIGNIFICAND}
|
|
666
844
|
;; Scientific notation.
|
|
667
|
-
(if (
|
|
668
|
-
(i32.or
|
|
669
|
-
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 101))
|
|
670
|
-
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 69))))
|
|
671
|
-
(then
|
|
672
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
673
|
-
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
674
|
-
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 45)))
|
|
675
|
-
(then (local.set $expNeg (i32.const 1)) (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
676
|
-
(if (i32.and (i32.lt_s (local.get $i) (local.get $len))
|
|
677
|
-
(i32.eq (call $__char_at (local.get $v) (local.get $i)) (i32.const 43)))
|
|
678
|
-
(then (local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
679
|
-
(block $expDone (loop $expLoop
|
|
680
|
-
(br_if $expDone (i32.ge_s (local.get $i) (local.get $len)))
|
|
681
|
-
(local.set $c (call $__char_at (local.get $v) (local.get $i)))
|
|
682
|
-
(br_if $expDone
|
|
683
|
-
(i32.or
|
|
684
|
-
(i32.lt_s (local.get $c) (i32.const 48))
|
|
685
|
-
(i32.gt_s (local.get $c) (i32.const 57))))
|
|
686
|
-
(local.set $exp
|
|
687
|
-
(i32.add
|
|
688
|
-
(i32.mul (local.get $exp) (i32.const 10))
|
|
689
|
-
(i32.sub (local.get $c) (i32.const 48))))
|
|
690
|
-
(local.set $expDigits (i32.add (local.get $expDigits) (i32.const 1)))
|
|
691
|
-
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
692
|
-
(br $expLoop)))
|
|
693
|
-
(if (local.get $expDigits)
|
|
845
|
+
${sciExponent(`(if (local.get $expDigits)
|
|
694
846
|
(then
|
|
695
847
|
(if (local.get $expNeg)
|
|
696
848
|
(then (local.set $decExp (i32.sub (local.get $decExp) (local.get $exp))))
|
|
697
|
-
(else (local.set $decExp (i32.add (local.get $decExp) (local.get $exp))))))))
|
|
698
|
-
|
|
699
|
-
(then (local.set $result (f64.mul (local.get $result) (call $__pow10 (local.get $decExp))))))
|
|
700
|
-
(if (i32.lt_s (local.get $decExp) (i32.const 0))
|
|
701
|
-
(then (local.set $result (f64.div (local.get $result) (call $__pow10 (i32.sub (i32.const 0) (local.get $decExp)))))))
|
|
849
|
+
(else (local.set $decExp (i32.add (local.get $decExp) (local.get $exp)))))))`)}
|
|
850
|
+
${POW10_SCALE}
|
|
702
851
|
(if (result f64) (local.get $neg) (then (f64.neg (local.get $result))) (else (local.get $result))))`
|
|
703
852
|
|
|
853
|
+
// ToString(arg) for the string-input builtins. A statically-known boolean must
|
|
854
|
+
// render as "true"/"false" (spec step 1: ToString) before parsing — otherwise
|
|
855
|
+
// its 0/1 carrier bits are fed to the parser as if a string pointer. Other types
|
|
856
|
+
// (string already, number/object ToPrimitive) stay out of scope per the runner.
|
|
857
|
+
const strInputI64 = (x) => valTypeOf(x) === VAL.BOOL ? asI64(emitBoolStr(x)) : asI64(emit(x))
|
|
858
|
+
|
|
704
859
|
ctx.core.emit['Number.parseInt'] = (x, radix) => {
|
|
705
860
|
needParseInt()
|
|
706
|
-
|
|
861
|
+
// Radix is coerced ToNumber then ToInt32 (modular wrap; NaN/±∞→0) per spec —
|
|
862
|
+
// a raw trunc would turn a string radix into garbage and Infinity into maxint.
|
|
863
|
+
const radixIR = radix == null ? ['i32.const', 0] : toI32(toNumF64(radix, emit(radix)))
|
|
864
|
+
return typed(['call', '$__parseInt', strInputI64(x), radixIR], 'f64')
|
|
707
865
|
}
|
|
708
866
|
ctx.core.emit['parseInt'] = ctx.core.emit['Number.parseInt']
|
|
709
867
|
const addImportOnce = (ctx, mod, name, fn) => {
|
|
@@ -718,10 +876,10 @@ export default (ctx) => {
|
|
|
718
876
|
ctx.core.emit['Number.parseFloat'] = (x) => {
|
|
719
877
|
if (ctx.transform.host === 'wasi') {
|
|
720
878
|
inc('__parseFloat')
|
|
721
|
-
return typed(['call', '$__parseFloat',
|
|
879
|
+
return typed(['call', '$__parseFloat', strInputI64(x)], 'f64')
|
|
722
880
|
}
|
|
723
881
|
needParseFloat()
|
|
724
|
-
return typed(['call', '$__parseFloat',
|
|
882
|
+
return typed(['call', '$__parseFloat', strInputI64(x)], 'f64')
|
|
725
883
|
}
|
|
726
884
|
ctx.core.emit['parseFloat'] = ctx.core.emit['Number.parseFloat']
|
|
727
885
|
|
|
@@ -747,7 +905,7 @@ export default (ctx) => {
|
|
|
747
905
|
|
|
748
906
|
ctx.core.emit['.number:toExponential'] = (n, d) => {
|
|
749
907
|
inc('__toExp')
|
|
750
|
-
return typed(['call', '$__toExp', asF64(emit(n)), asI32(emit(d || [, 0]))], 'f64')
|
|
908
|
+
return typed(['call', '$__toExp', asF64(emit(n)), asI32(emit(d || [, 0])), ['i32.const', 0]], 'f64')
|
|
751
909
|
}
|
|
752
910
|
|
|
753
911
|
ctx.core.emit['.number:toPrecision'] = (n, p) => {
|
|
@@ -774,7 +932,7 @@ export default (ctx) => {
|
|
|
774
932
|
['i32.or',
|
|
775
933
|
['i32.lt_s', ['local.get', `$${exp}`], ['i32.const', -6]],
|
|
776
934
|
['i32.ge_s', ['local.get', `$${exp}`], ['local.get', `$${pr}`]]],
|
|
777
|
-
['then', ['call', '$__toExp', ['local.get', `$${val}`], ['i32.sub', ['local.get', `$${pr}`], ['i32.const', 1]]]],
|
|
935
|
+
['then', ['call', '$__toExp', ['local.get', `$${val}`], ['i32.sub', ['local.get', `$${pr}`], ['i32.const', 1]], ['i32.const', 0]]],
|
|
778
936
|
['else', ['call', '$__ftoa', ['local.get', `$${val}`],
|
|
779
937
|
['i32.sub', ['i32.sub', ['local.get', `$${pr}`], ['i32.const', 1]], ['local.get', `$${exp}`]],
|
|
780
938
|
['i32.const', 1]]]]], 'f64')
|
|
@@ -792,6 +950,9 @@ export default (ctx) => {
|
|
|
792
950
|
// For number input: truncate directly. For string / unknown: first coerce via __to_num
|
|
793
951
|
// (handles both decimal and hex string parse), then truncate.
|
|
794
952
|
ctx.core.emit['BigInt'] = (x) => {
|
|
953
|
+
// Every BigInt() path can fault: a non-integral Number is a RangeError and
|
|
954
|
+
// a malformed String is a SyntaxError, both raised via $__jz_err.
|
|
955
|
+
ctx.runtime.throws = true
|
|
795
956
|
const vt = valTypeOf(x)
|
|
796
957
|
if (vt === VAL.BIGINT) {
|
|
797
958
|
if (typeof x === 'bigint' || (typeof x === 'string' && !isReassigned(ctx.func.body, x))) return emit(x)
|
|
@@ -806,8 +967,10 @@ export default (ctx) => {
|
|
|
806
967
|
['then', ['call', '$__to_bigint', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
807
968
|
['else', ['local.get', `$${t}`]]]]]], 'f64')
|
|
808
969
|
}
|
|
809
|
-
if (vt === VAL.NUMBER)
|
|
810
|
-
|
|
970
|
+
if (vt === VAL.NUMBER) {
|
|
971
|
+
inc('__num_to_bigint')
|
|
972
|
+
return typed(['call', '$__num_to_bigint', asF64(emit(x))], 'f64')
|
|
973
|
+
}
|
|
811
974
|
inc('__to_bigint')
|
|
812
975
|
return typed(['call', '$__to_bigint', asI64(emit(x))], 'f64')
|
|
813
976
|
}
|