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.
package/module/json.js ADDED
@@ -0,0 +1,504 @@
1
+ /**
2
+ * JSON module — JSON.stringify and JSON.parse.
3
+ *
4
+ * stringify: recursive type-dispatch → string assembly in scratch buffer.
5
+ * parse: recursive descent parser using globals for input position.
6
+ * Objects parsed as Map (dynamic keys). Arrays as standard jz arrays.
7
+ *
8
+ * @module json
9
+ */
10
+
11
+ import { emit, typed, asF64, T, temp, nullExpr, allocPtr, slotAddr } from '../src/compile.js'
12
+ import { err, inc, PTR } from '../src/ctx.js'
13
+ import { strHashLiteral } from './collection.js'
14
+
15
+ function jsonConstString(ctx, expr) {
16
+ if (Array.isArray(expr) && expr[0] === 'str' && typeof expr[1] === 'string') return expr[1]
17
+ if (Array.isArray(expr) && expr[0] == null && typeof expr[1] === 'string') return expr[1]
18
+ if (typeof expr === 'string') return ctx.scope.constStrs?.get(expr) ?? null
19
+ return null
20
+ }
21
+
22
+ function hashCapFor(n) {
23
+ let cap = 8
24
+ const need = Math.max(1, Math.ceil(n * 4 / 3))
25
+ while (cap < need) cap <<= 1
26
+ return cap
27
+ }
28
+
29
+ export default (ctx) => {
30
+ Object.assign(ctx.core.stdlibDeps, {
31
+ __stringify: ['__json_val', '__jput', '__jput_str', '__jput_num', '__mkstr'],
32
+ __json_val: ['__ptr_type', '__len', '__ptr_offset', '__jput', '__jput_num', '__jput_str', '__json_hash', '__json_obj'],
33
+ __json_hash: ['__ptr_offset', '__jput', '__jput_str', '__json_val'],
34
+ __json_obj: ['__ptr_offset', '__ptr_aux', '__len', '__jput', '__jput_str', '__json_val'],
35
+ __jput_num: ['__ftoa'],
36
+ __jput_str: ['__char_at', '__str_byteLen'],
37
+ __jp: ['__jp_val', '__jp_str', '__jp_num', '__jp_arr', '__jp_obj', '__jp_peek', '__jp_adv', '__jp_ws'],
38
+ __jp_str: ['__sso_char', '__char_at', '__str_byteLen'],
39
+ __jp_num: ['__pow10'],
40
+ __jp_arr: ['__jp_val'],
41
+ __jp_obj: ['__jp_val', '__hash_new', '__hash_set_local'],
42
+ })
43
+
44
+ function emitJsonConstValue(v) {
45
+ if (v == null) return asF64(emit(nullExpr))
46
+ if (typeof v === 'number') return asF64(emit(v))
47
+ if (typeof v === 'string') return asF64(emit(['str', v]))
48
+ if (typeof v === 'boolean') return asF64(emit(v ? 1 : 0))
49
+ if (Array.isArray(v)) {
50
+ const a = allocPtr({ type: PTR.ARRAY, len: v.length, cap: Math.max(v.length, 4), tag: 'jarr' })
51
+ const body = [a.init]
52
+ for (let i = 0; i < v.length; i++) body.push(['f64.store', slotAddr(a.local, i), emitJsonConstValue(v[i])])
53
+ body.push(a.ptr)
54
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
55
+ }
56
+ if (typeof v === 'object') {
57
+ const keys = Object.keys(v)
58
+ const obj = allocPtr({ type: PTR.HASH, len: 0, cap: hashCapFor(keys.length), stride: 24, tag: 'jhash' })
59
+ const h = temp('jhash')
60
+ const body = [obj.init, ['local.set', `$${h}`, obj.ptr]]
61
+ for (const k of keys) {
62
+ body.push(['local.set', `$${h}`,
63
+ ['call', '$__hash_set_local_h', ['local.get', `$${h}`], asF64(emit(['str', k])), ['i32.const', strHashLiteral(k)], emitJsonConstValue(v[k])]])
64
+ }
65
+ body.push(['local.get', `$${h}`])
66
+ if (keys.length) inc('__hash_set_local_h')
67
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
68
+ }
69
+ return asF64(emit(nullExpr))
70
+ }
71
+
72
+
73
+ // === JSON.stringify ===
74
+
75
+ // Scratch buffer approach: __json_buf is a growable output buffer.
76
+ // Functions append bytes to it, __json_pos tracks current write position.
77
+
78
+ ctx.scope.globals.set('__jbuf', '(global $__jbuf (mut i32) (i32.const 0))')
79
+ ctx.scope.globals.set('__jpos', '(global $__jpos (mut i32) (i32.const 0))')
80
+ ctx.scope.globals.set('__jcap', '(global $__jcap (mut i32) (i32.const 0))')
81
+ ctx.scope.globals.set('__schema_tbl', '(global $__schema_tbl (mut i32) (i32.const 0))')
82
+
83
+ // __jput(byte: i32) — append one byte to output buffer
84
+ ctx.core.stdlib['__jput'] = `(func $__jput (param $b i32)
85
+ (local $new i32)
86
+ (if (i32.ge_s (global.get $__jpos) (global.get $__jcap))
87
+ (then
88
+ (global.set $__jcap (i32.shl (i32.add (global.get $__jcap) (i32.const 1)) (i32.const 1)))
89
+ (local.set $new (call $__alloc (global.get $__jcap)))
90
+ (memory.copy (local.get $new) (global.get $__jbuf) (global.get $__jpos))
91
+ (global.set $__jbuf (local.get $new))))
92
+ (i32.store8 (i32.add (global.get $__jbuf) (global.get $__jpos)) (local.get $b))
93
+ (global.set $__jpos (i32.add (global.get $__jpos) (i32.const 1))))`
94
+
95
+ // __jput_str(ptr: f64) — append string chars (without quotes) to buffer
96
+ ctx.core.stdlib['__jput_str'] = `(func $__jput_str (param $ptr f64)
97
+ (local $len i32) (local $i i32) (local $ch i32)
98
+ (local.set $len (call $__str_byteLen (local.get $ptr)))
99
+ (local.set $i (i32.const 0))
100
+ (block $d (loop $l
101
+ (br_if $d (i32.ge_s (local.get $i) (local.get $len)))
102
+ (local.set $ch (call $__char_at (local.get $ptr) (local.get $i)))
103
+ ;; Escape special JSON chars
104
+ (if (i32.le_u (local.get $ch) (i32.const 13))
105
+ (then
106
+ (if (i32.eq (local.get $ch) (i32.const 10)) (then (call $__jput (i32.const 92)) (call $__jput (i32.const 110)))
107
+ (else (if (i32.eq (local.get $ch) (i32.const 13)) (then (call $__jput (i32.const 92)) (call $__jput (i32.const 114)))
108
+ (else (if (i32.eq (local.get $ch) (i32.const 9)) (then (call $__jput (i32.const 92)) (call $__jput (i32.const 116)))
109
+ (else (if (i32.eq (local.get $ch) (i32.const 8)) (then (call $__jput (i32.const 92)) (call $__jput (i32.const 98)))
110
+ (else (if (i32.eq (local.get $ch) (i32.const 12)) (then (call $__jput (i32.const 92)) (call $__jput (i32.const 102)))
111
+ (else (call $__jput (local.get $ch)))))))))))))
112
+ (else
113
+ (if (i32.eq (local.get $ch) (i32.const 34)) (then (call $__jput (i32.const 92)) (call $__jput (i32.const 34)))
114
+ (else (if (i32.eq (local.get $ch) (i32.const 92)) (then (call $__jput (i32.const 92)) (call $__jput (i32.const 92)))
115
+ (else (call $__jput (local.get $ch))))))))
116
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
117
+ (br $l))))`
118
+
119
+ // __jput_num(val: f64) — convert number to string, append bytes to buffer
120
+ ctx.core.stdlib['__jput_num'] = `(func $__jput_num (param $val f64)
121
+ (call $__jput_str (call $__ftoa (local.get $val) (i32.const 0) (i32.const 0))))`
122
+
123
+ // __json_val(val: f64) — stringify any value, append to buffer
124
+ ctx.core.stdlib['__json_val'] = `(func $__json_val (param $val f64)
125
+ (local $type i32) (local $len i32) (local $i i32) (local $off i32)
126
+ ;; Number (not NaN) — but Infinity must be null per JSON spec
127
+ (if (f64.eq (local.get $val) (local.get $val))
128
+ (then
129
+ (if (f64.eq (f64.abs (local.get $val)) (f64.const inf))
130
+ (then
131
+ (call $__jput (i32.const 110)) (call $__jput (i32.const 117))
132
+ (call $__jput (i32.const 108)) (call $__jput (i32.const 108)) (return)))
133
+ (call $__jput_num (local.get $val)) (return)))
134
+ ;; NaN-boxed pointer
135
+ (local.set $type (call $__ptr_type (local.get $val)))
136
+ ;; Plain NaN (type=0) → null
137
+ (if (i32.eqz (local.get $type))
138
+ (then
139
+ (call $__jput (i32.const 110)) (call $__jput (i32.const 117))
140
+ (call $__jput (i32.const 108)) (call $__jput (i32.const 108)) (return)))
141
+ ;; String
142
+ (if (i32.or (i32.eq (local.get $type) (i32.const ${PTR.STRING}))
143
+ (i32.eq (local.get $type) (i32.const ${PTR.SSO})))
144
+ (then
145
+ (call $__jput (i32.const 34))
146
+ (call $__jput_str (local.get $val))
147
+ (call $__jput (i32.const 34)) (return)))
148
+ ;; Array
149
+ (if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
150
+ (then
151
+ (call $__jput (i32.const 91)) ;; [
152
+ (local.set $len (call $__len (local.get $val)))
153
+ (local.set $off (call $__ptr_offset (local.get $val)))
154
+ (local.set $i (i32.const 0))
155
+ (block $d (loop $l
156
+ (br_if $d (i32.ge_s (local.get $i) (local.get $len)))
157
+ (if (local.get $i) (then (call $__jput (i32.const 44)))) ;; ,
158
+ (call $__json_val (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
159
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
160
+ (br $l)))
161
+ (call $__jput (i32.const 93)) ;; ]
162
+ (return)))
163
+ ;; HASH/MAP — iterate entries: {"key":val,...}
164
+ (if (i32.or (i32.eq (local.get $type) (i32.const ${PTR.HASH}))
165
+ (i32.eq (local.get $type) (i32.const ${PTR.MAP})))
166
+ (then (call $__json_hash (local.get $val)) (return)))
167
+ ;; OBJECT — schema-based: iterate props with schema name table
168
+ (if (i32.eq (local.get $type) (i32.const ${PTR.OBJECT}))
169
+ (then (call $__json_obj (local.get $val)) (return)))
170
+ ;; Unknown type → null
171
+ (call $__jput (i32.const 110)) (call $__jput (i32.const 117))
172
+ (call $__jput (i32.const 108)) (call $__jput (i32.const 108)))`
173
+
174
+ // __json_hash(val: f64) — stringify HASH/MAP: iterate slots, emit {"key":val,...}
175
+ // Slot layout: 24 bytes each — [hash:f64][key:f64][val:f64]. Empty slots have hash==0.
176
+ ctx.core.stdlib['__json_hash'] = `(func $__json_hash (param $val f64)
177
+ (local $off i32) (local $cap i32) (local $i i32) (local $slot i32) (local $first i32)
178
+ (local.set $off (call $__ptr_offset (local.get $val)))
179
+ (local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
180
+ (local.set $first (i32.const 1))
181
+ (call $__jput (i32.const 123))
182
+ (block $d (loop $l
183
+ (br_if $d (i32.ge_s (local.get $i) (local.get $cap)))
184
+ (local.set $slot (i32.add (local.get $off) (i32.mul (local.get $i) (i32.const 24))))
185
+ (if (f64.ne (f64.load (local.get $slot)) (f64.const 0))
186
+ (then
187
+ (if (i32.eqz (local.get $first))
188
+ (then (call $__jput (i32.const 44))))
189
+ (local.set $first (i32.const 0))
190
+ (call $__jput (i32.const 34))
191
+ (call $__jput_str (f64.load (i32.add (local.get $slot) (i32.const 8))))
192
+ (call $__jput (i32.const 34))
193
+ (call $__jput (i32.const 58))
194
+ (call $__json_val (f64.load (i32.add (local.get $slot) (i32.const 16))))))
195
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
196
+ (br $l)))
197
+ (call $__jput (i32.const 125)))`
198
+
199
+ // __json_obj(val: f64) — stringify OBJECT using runtime schema name table.
200
+ // Schema name table: global $__schema_tbl → array of f64 pointers.
201
+ // schema_tbl[schemaId * 8] = f64 pointer to jz Array of key name strings.
202
+ // Object props are sequential f64 at ptr_offset, indexed same as schema.
203
+ ctx.core.stdlib['__json_obj'] = `(func $__json_obj (param $val f64)
204
+ (local $off i32) (local $sid i32) (local $keys i32) (local $nkeys i32)
205
+ (local $i i32) (local $koff i32)
206
+ (local.set $off (call $__ptr_offset (local.get $val)))
207
+ (local.set $sid (call $__ptr_aux (local.get $val)))
208
+ ;; Load keys array from schema table: schema_tbl + sid * 8
209
+ (local.set $keys (call $__ptr_offset
210
+ (f64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))))
211
+ (local.set $nkeys (call $__len
212
+ (f64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $sid) (i32.const 3))))))
213
+ (local.set $koff (local.get $keys))
214
+ (call $__jput (i32.const 123))
215
+ (block $d (loop $l
216
+ (br_if $d (i32.ge_s (local.get $i) (local.get $nkeys)))
217
+ (if (local.get $i) (then (call $__jput (i32.const 44))))
218
+ (call $__jput (i32.const 34))
219
+ (call $__jput_str (f64.load (i32.add (local.get $koff) (i32.shl (local.get $i) (i32.const 3)))))
220
+ (call $__jput (i32.const 34))
221
+ (call $__jput (i32.const 58))
222
+ (call $__json_val (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
223
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
224
+ (br $l)))
225
+ (call $__jput (i32.const 125)))`
226
+
227
+ // __stringify(val: f64) → f64 (NaN-boxed string)
228
+ ctx.core.stdlib['__stringify'] = `(func $__stringify (param $val f64) (result f64)
229
+ ;; Reset output buffer
230
+ (global.set $__jbuf (call $__alloc (i32.const 256)))
231
+ (global.set $__jpos (i32.const 0))
232
+ (global.set $__jcap (i32.const 256))
233
+ (call $__json_val (local.get $val))
234
+ ;; Create string from buffer
235
+ (call $__mkstr (global.get $__jbuf) (global.get $__jpos)))`
236
+
237
+ // === JSON.parse ===
238
+
239
+ ctx.scope.globals.set('__jpstr', '(global $__jpstr (mut i32) (i32.const 0))') // input string offset
240
+ ctx.scope.globals.set('__jplen', '(global $__jplen (mut i32) (i32.const 0))') // input length
241
+ ctx.scope.globals.set('__jppos', '(global $__jppos (mut i32) (i32.const 0))') // current parse position
242
+
243
+ // Sentinel-driven peek: __jp copies input to a scratch buffer with 0xFF bytes
244
+ // appended past the end. i32.load8_s sign-extends, so the sentinel reads as -1
245
+ // — exactly the EOF value all callers already test for. Bounds check and
246
+ // function-call overhead both gone; ~50 calls/parse char in well-formed JSON.
247
+ ctx.core.stdlib['__jp_peek'] = `(func $__jp_peek (result i32)
248
+ (i32.load8_s (i32.add (global.get $__jpstr) (global.get $__jppos))))`
249
+
250
+ ctx.core.stdlib['__jp_adv'] = `(func $__jp_adv (param $n i32)
251
+ (global.set $__jppos (i32.add (global.get $__jppos) (local.get $n))))`
252
+
253
+ ctx.core.stdlib['__jp_ws'] = `(func $__jp_ws
254
+ (local $ch i32)
255
+ (block $d (loop $l
256
+ (local.set $ch (call $__jp_peek))
257
+ (br_if $d (i32.and (i32.ne (local.get $ch) (i32.const 32))
258
+ (i32.and (i32.ne (local.get $ch) (i32.const 9))
259
+ (i32.and (i32.ne (local.get $ch) (i32.const 10))
260
+ (i32.ne (local.get $ch) (i32.const 13))))))
261
+ (call $__jp_adv (i32.const 1))
262
+ (br $l))))`
263
+
264
+ // Parse string (after opening " consumed). Two-phase: scan to closing quote
265
+ // tracking whether all chars are simple ASCII (no escapes, no high-bit), then
266
+ // either pack into SSO (≤4 simple chars) or heap-alloc + escape-decode.
267
+ ctx.core.stdlib['__jp_str'] = `(func $__jp_str (result f64)
268
+ (local $start i32) (local $ch i32) (local $len i32) (local $off i32) (local $i i32) (local $simple i32) (local $sso i32)
269
+ (local.set $start (global.get $__jppos))
270
+ (local.set $simple (i32.const 1))
271
+ (block $d (loop $l
272
+ (local.set $ch (call $__jp_peek))
273
+ (br_if $d (i32.eq (local.get $ch) (i32.const 34)))
274
+ (br_if $d (i32.eq (local.get $ch) (i32.const -1)))
275
+ ;; Mark non-simple: escape (\\=92) or non-ASCII (load8_s gives <0 for byte≥128).
276
+ (if (i32.or (i32.eq (local.get $ch) (i32.const 92)) (i32.lt_s (local.get $ch) (i32.const 0)))
277
+ (then (local.set $simple (i32.const 0))))
278
+ (if (i32.eq (local.get $ch) (i32.const 92))
279
+ (then (call $__jp_adv (i32.const 2)))
280
+ (else (call $__jp_adv (i32.const 1))))
281
+ (br $l)))
282
+ (local.set $len (i32.sub (global.get $__jppos) (local.get $start)))
283
+ (call $__jp_adv (i32.const 1)) ;; skip "
284
+ ;; SSO fast path: ≤4 ASCII chars, no escapes — pack bytes into the offset slot,
285
+ ;; skip alloc + memcopy entirely. The dominant case for object keys (id/kind/meta/bias).
286
+ (if (i32.and (local.get $simple) (i32.le_u (local.get $len) (i32.const 4)))
287
+ (then
288
+ (local.set $i (i32.const 0))
289
+ (block $sd (loop $sl
290
+ (br_if $sd (i32.ge_s (local.get $i) (local.get $len)))
291
+ (local.set $sso
292
+ (i32.or (local.get $sso)
293
+ (i32.shl (i32.load8_u (i32.add (i32.add (global.get $__jpstr) (local.get $start)) (local.get $i)))
294
+ (i32.shl (local.get $i) (i32.const 3)))))
295
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
296
+ (br $sl)))
297
+ (return (call $__mkptr (i32.const ${PTR.SSO}) (local.get $len) (local.get $sso)))))
298
+ ;; Simple STRING fast path: no escapes, len > 4 — bulk memcpy from parse buffer,
299
+ ;; skip rewind + per-byte escape-decode loop. Hits 5+ char keys without escapes.
300
+ (if (local.get $simple)
301
+ (then
302
+ (local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
303
+ (local.set $off (i32.add (local.get $off) (i32.const 4)))
304
+ (i32.store (i32.sub (local.get $off) (i32.const 4)) (local.get $len))
305
+ (memory.copy (local.get $off) (i32.add (global.get $__jpstr) (local.get $start)) (local.get $len))
306
+ (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))))
307
+ ;; Copy chars to new string (handles escapes inline)
308
+ (local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
309
+ (local.set $off (i32.add (local.get $off) (i32.const 4)))
310
+ (local.set $i (i32.const 0))
311
+ (global.set $__jppos (local.get $start)) ;; rewind to re-scan
312
+ (local.set $len (i32.const 0)) ;; actual output length
313
+ (block $d2 (loop $l2
314
+ (local.set $ch (call $__jp_peek))
315
+ (br_if $d2 (i32.eq (local.get $ch) (i32.const 34)))
316
+ (br_if $d2 (i32.eq (local.get $ch) (i32.const -1)))
317
+ (if (i32.eq (local.get $ch) (i32.const 92))
318
+ (then
319
+ (call $__jp_adv (i32.const 1))
320
+ (local.set $ch (call $__jp_peek))
321
+ (call $__jp_adv (i32.const 1))
322
+ ;; Decode escape: n→10 t→9 r→13 b→8 f→12, else literal
323
+ (if (i32.eq (local.get $ch) (i32.const 110)) (then (local.set $ch (i32.const 10))))
324
+ (if (i32.eq (local.get $ch) (i32.const 116)) (then (local.set $ch (i32.const 9))))
325
+ (if (i32.eq (local.get $ch) (i32.const 114)) (then (local.set $ch (i32.const 13))))
326
+ (if (i32.eq (local.get $ch) (i32.const 98)) (then (local.set $ch (i32.const 8))))
327
+ (if (i32.eq (local.get $ch) (i32.const 102)) (then (local.set $ch (i32.const 12)))))
328
+ (else (call $__jp_adv (i32.const 1))))
329
+ (i32.store8 (i32.add (local.get $off) (local.get $len)) (local.get $ch))
330
+ (local.set $len (i32.add (local.get $len) (i32.const 1)))
331
+ (br $l2)))
332
+ (call $__jp_adv (i32.const 1)) ;; skip closing "
333
+ ;; Store actual length in header
334
+ (i32.store (i32.sub (local.get $off) (i32.const 4)) (local.get $len))
335
+ (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
336
+
337
+ // Parse number
338
+ ctx.core.stdlib['__jp_num'] = `(func $__jp_num (result f64)
339
+ (local $neg i32) (local $val f64) (local $scale f64) (local $ch i32)
340
+ (local $exp i32) (local $expNeg i32)
341
+ (if (i32.eq (call $__jp_peek) (i32.const 45))
342
+ (then (local.set $neg (i32.const 1)) (call $__jp_adv (i32.const 1))))
343
+ (block $d (loop $l
344
+ (local.set $ch (call $__jp_peek))
345
+ (br_if $d (i32.or (i32.lt_s (local.get $ch) (i32.const 48)) (i32.gt_s (local.get $ch) (i32.const 57))))
346
+ (local.set $val (f64.add (f64.mul (local.get $val) (f64.const 10))
347
+ (f64.convert_i32_s (i32.sub (local.get $ch) (i32.const 48)))))
348
+ (call $__jp_adv (i32.const 1)) (br $l)))
349
+ (if (i32.eq (call $__jp_peek) (i32.const 46))
350
+ (then
351
+ (call $__jp_adv (i32.const 1))
352
+ (local.set $scale (f64.const 0.1))
353
+ (block $fd (loop $fl
354
+ (local.set $ch (call $__jp_peek))
355
+ (br_if $fd (i32.or (i32.lt_s (local.get $ch) (i32.const 48)) (i32.gt_s (local.get $ch) (i32.const 57))))
356
+ (local.set $val (f64.add (local.get $val)
357
+ (f64.mul (local.get $scale) (f64.convert_i32_s (i32.sub (local.get $ch) (i32.const 48))))))
358
+ (local.set $scale (f64.mul (local.get $scale) (f64.const 0.1)))
359
+ (call $__jp_adv (i32.const 1)) (br $fl)))))
360
+ (if (i32.or (i32.eq (call $__jp_peek) (i32.const 101)) (i32.eq (call $__jp_peek) (i32.const 69)))
361
+ (then
362
+ (call $__jp_adv (i32.const 1))
363
+ (if (i32.eq (call $__jp_peek) (i32.const 45))
364
+ (then (local.set $expNeg (i32.const 1)) (call $__jp_adv (i32.const 1)))
365
+ (else (if (i32.eq (call $__jp_peek) (i32.const 43))
366
+ (then (call $__jp_adv (i32.const 1))))))
367
+ (block $ed (loop $el
368
+ (local.set $ch (call $__jp_peek))
369
+ (br_if $ed (i32.or (i32.lt_s (local.get $ch) (i32.const 48)) (i32.gt_s (local.get $ch) (i32.const 57))))
370
+ (local.set $exp (i32.add (i32.mul (local.get $exp) (i32.const 10)) (i32.sub (local.get $ch) (i32.const 48))))
371
+ (call $__jp_adv (i32.const 1)) (br $el)))
372
+ (if (local.get $expNeg) (then (local.set $exp (i32.sub (i32.const 0) (local.get $exp)))))
373
+ (local.set $val (f64.mul (local.get $val) (call $__pow10
374
+ (if (result i32) (i32.lt_s (local.get $exp) (i32.const 0))
375
+ (then (i32.const 0)) (else (local.get $exp))))))
376
+ (if (i32.lt_s (local.get $exp) (i32.const 0))
377
+ (then (local.set $val (f64.div (local.get $val) (call $__pow10 (i32.sub (i32.const 0) (local.get $exp)))))))))
378
+ (if (result f64) (local.get $neg) (then (f64.neg (local.get $val))) (else (local.get $val))))`
379
+
380
+ // Parse array
381
+ ctx.core.stdlib['__jp_arr'] = `(func $__jp_arr (result f64)
382
+ (local $ptr i32) (local $len i32) (local $cap i32) (local $new i32) (local $ch i32)
383
+ (local.set $cap (i32.const 8))
384
+ (local.set $ptr (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $cap) (i32.const 3)))))
385
+ (local.set $ptr (i32.add (local.get $ptr) (i32.const 8)))
386
+ (call $__jp_ws)
387
+ (if (i32.eq (call $__jp_peek) (i32.const 93))
388
+ (then (call $__jp_adv (i32.const 1))
389
+ (i32.store (i32.sub (local.get $ptr) (i32.const 8)) (i32.const 0))
390
+ (i32.store (i32.sub (local.get $ptr) (i32.const 4)) (local.get $cap))
391
+ (return (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $ptr)))))
392
+ (block $d (loop $l
393
+ (call $__jp_ws)
394
+ ;; Grow if needed
395
+ (if (i32.ge_s (local.get $len) (local.get $cap))
396
+ (then
397
+ (local.set $new (call $__alloc (i32.add (i32.const 8) (i32.shl (i32.shl (local.get $cap) (i32.const 1)) (i32.const 3)))))
398
+ (local.set $new (i32.add (local.get $new) (i32.const 8)))
399
+ (memory.copy (local.get $new) (local.get $ptr) (i32.shl (local.get $len) (i32.const 3)))
400
+ (local.set $cap (i32.shl (local.get $cap) (i32.const 1)))
401
+ (local.set $ptr (local.get $new))))
402
+ (f64.store (i32.add (local.get $ptr) (i32.shl (local.get $len) (i32.const 3))) (call $__jp_val))
403
+ (local.set $len (i32.add (local.get $len) (i32.const 1)))
404
+ (call $__jp_ws)
405
+ (local.set $ch (call $__jp_peek))
406
+ (br_if $d (i32.eq (local.get $ch) (i32.const 93)))
407
+ (if (i32.eq (local.get $ch) (i32.const 44)) (then (call $__jp_adv (i32.const 1))))
408
+ (br $l)))
409
+ (call $__jp_adv (i32.const 1))
410
+ (i32.store (i32.sub (local.get $ptr) (i32.const 8)) (local.get $len))
411
+ (i32.store (i32.sub (local.get $ptr) (i32.const 4)) (local.get $cap))
412
+ (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $ptr)))`
413
+
414
+ // Parse object → HASH (dynamic string-keyed object)
415
+ ctx.core.stdlib['__jp_obj'] = `(func $__jp_obj (result f64)
416
+ (local $obj f64) (local $key f64) (local $ch i32)
417
+ (local.set $obj (call $__hash_new))
418
+ (call $__jp_ws)
419
+ (if (i32.eq (call $__jp_peek) (i32.const 125))
420
+ (then (call $__jp_adv (i32.const 1)) (return (local.get $obj))))
421
+ (block $d (loop $l
422
+ (call $__jp_ws)
423
+ (if (i32.eq (call $__jp_peek) (i32.const 34))
424
+ (then (call $__jp_adv (i32.const 1))))
425
+ (local.set $key (call $__jp_str))
426
+ (call $__jp_ws)
427
+ (if (i32.eq (call $__jp_peek) (i32.const 58))
428
+ (then (call $__jp_adv (i32.const 1))))
429
+ (call $__jp_ws)
430
+ (local.set $obj (call $__hash_set_local (local.get $obj) (local.get $key) (call $__jp_val)))
431
+ (call $__jp_ws)
432
+ (local.set $ch (call $__jp_peek))
433
+ (br_if $d (i32.eq (local.get $ch) (i32.const 125)))
434
+ (if (i32.eq (local.get $ch) (i32.const 44)) (then (call $__jp_adv (i32.const 1))))
435
+ (br $l)))
436
+ (call $__jp_adv (i32.const 1))
437
+ (local.get $obj))`
438
+
439
+ // Main value dispatcher
440
+ ctx.core.stdlib['__jp_val'] = `(func $__jp_val (result f64)
441
+ (local $ch i32)
442
+ (call $__jp_ws)
443
+ (local.set $ch (call $__jp_peek))
444
+ (if (i32.eq (local.get $ch) (i32.const 34))
445
+ (then (call $__jp_adv (i32.const 1)) (return (call $__jp_str))))
446
+ (if (i32.eq (local.get $ch) (i32.const 91))
447
+ (then (call $__jp_adv (i32.const 1)) (return (call $__jp_arr))))
448
+ (if (i32.eq (local.get $ch) (i32.const 123))
449
+ (then (call $__jp_adv (i32.const 1)) (return (call $__jp_obj))))
450
+ (if (i32.or (i32.and (i32.ge_s (local.get $ch) (i32.const 48)) (i32.le_s (local.get $ch) (i32.const 57)))
451
+ (i32.eq (local.get $ch) (i32.const 45)))
452
+ (then (return (call $__jp_num))))
453
+ (if (i32.eq (local.get $ch) (i32.const 116))
454
+ (then (call $__jp_adv (i32.const 4)) (return (f64.const 1))))
455
+ (if (i32.eq (local.get $ch) (i32.const 102))
456
+ (then (call $__jp_adv (i32.const 5)) (return (f64.const 0))))
457
+ (if (i32.eq (local.get $ch) (i32.const 110))
458
+ (then (call $__jp_adv (i32.const 4)) (return (f64.const 0))))
459
+ (f64.const 0))`
460
+
461
+ // Entry point — copies input to a scratch buffer with 0xFF sentinel padding
462
+ // past the end so __jp_peek can omit its bounds check. Pad is 8 bytes so any
463
+ // overshoot from speculative peek/adv on malformed input still hits sentinel,
464
+ // not unallocated memory.
465
+ ctx.core.stdlib['__jp'] = `(func $__jp (param $str f64) (result f64)
466
+ (local $len i32) (local $buf i32) (local $i i32)
467
+ (local.set $len (call $__str_byteLen (local.get $str)))
468
+ (local.set $buf (call $__alloc (i32.add (local.get $len) (i32.const 8))))
469
+ ;; Pre-fill 8 sentinel bytes at end (writes overlapping a 64-bit slot).
470
+ (i64.store (i32.add (local.get $buf) (local.get $len)) (i64.const -1))
471
+ ;; SSO: byte-by-byte via __sso_char; STRING: bulk memcpy from string offset.
472
+ (if (i32.eq (call $__ptr_type (local.get $str)) (i32.const ${PTR.SSO}))
473
+ (then
474
+ (local.set $i (i32.const 0))
475
+ (block $d (loop $l
476
+ (br_if $d (i32.ge_s (local.get $i) (local.get $len)))
477
+ (i32.store8 (i32.add (local.get $buf) (local.get $i))
478
+ (call $__sso_char (local.get $str) (local.get $i)))
479
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
480
+ (br $l))))
481
+ (else
482
+ (memory.copy (local.get $buf) (call $__ptr_offset (local.get $str)) (local.get $len))))
483
+ (global.set $__jpstr (local.get $buf))
484
+ (global.set $__jplen (local.get $len))
485
+ (global.set $__jppos (i32.const 0))
486
+ (call $__jp_val))`
487
+
488
+ // === Emitters ===
489
+
490
+ ctx.core.emit['JSON.stringify'] = (x) => {
491
+ inc('__stringify')
492
+ return typed(['call', '$__stringify', asF64(emit(x))], 'f64')
493
+ }
494
+
495
+ ctx.core.emit['JSON.parse'] = (x) => {
496
+ const src = jsonConstString(ctx, x)
497
+ if (src != null) {
498
+ try { return emitJsonConstValue(JSON.parse(src)) }
499
+ catch { /* fall through to runtime parser for invalid JSON so runtime behavior stays unchanged */ }
500
+ }
501
+ inc('__jp')
502
+ return typed(['call', '$__jp', asF64(emit(x))], 'f64')
503
+ }
504
+ }