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