json-as 1.3.9 → 1.4.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/CHANGELOG.md +20 -0
- package/README.md +49 -1
- package/assembly/deserialize/index/arbitrary.ts +2 -2
- package/assembly/deserialize/naive/array/arbitrary.ts +3 -136
- package/assembly/deserialize/naive/array/array.ts +30 -1
- package/assembly/deserialize/naive/array/integer.ts +1 -6
- package/assembly/deserialize/naive/array/map.ts +10 -14
- package/assembly/deserialize/naive/array/object.ts +10 -14
- package/assembly/deserialize/naive/float.ts +2 -4
- package/assembly/deserialize/naive/integer.ts +1 -2
- package/assembly/deserialize/naive/map.ts +40 -202
- package/assembly/deserialize/naive/object.ts +153 -174
- package/assembly/deserialize/naive/set.ts +1 -2
- package/assembly/deserialize/naive/staticarray.ts +1 -2
- package/assembly/deserialize/naive/string.ts +65 -18
- package/assembly/deserialize/naive/typedarray.ts +1 -2
- package/assembly/deserialize/naive/unsigned.ts +1 -2
- package/assembly/deserialize/simd/array/integer.ts +3 -6
- package/assembly/deserialize/simd/float.ts +2 -7
- package/assembly/deserialize/simd/integer.ts +4 -8
- package/assembly/deserialize/simd/string.ts +16 -21
- package/assembly/deserialize/swar/array/array.ts +1 -2
- package/assembly/deserialize/swar/array/bool.ts +1 -2
- package/assembly/deserialize/swar/array/float.ts +2 -3
- package/assembly/deserialize/swar/array/generic.ts +1 -2
- package/assembly/deserialize/swar/array/integer.ts +6 -11
- package/assembly/deserialize/swar/array/object.ts +1 -2
- package/assembly/deserialize/swar/array/shared.ts +3 -8
- package/assembly/deserialize/swar/array/string.ts +1 -2
- package/assembly/deserialize/swar/array/struct.ts +1 -1
- package/assembly/deserialize/swar/float.ts +3 -8
- package/assembly/deserialize/swar/integer.ts +4 -8
- package/assembly/deserialize/swar/string.ts +29 -41
- package/assembly/index.d.ts +248 -15
- package/assembly/index.ts +468 -146
- package/assembly/serialize/index/object.ts +18 -15
- package/assembly/serialize/naive/string.ts +9 -2
- package/assembly/serialize/swar/string.ts +1 -2
- package/assembly/util/atoi.ts +1 -2
- package/assembly/util/dragonbox.ts +0 -8
- package/assembly/util/itoa-fast.ts +3 -6
- package/assembly/util/parsefloat-fast.ts +1 -2
- package/assembly/util/scanValueEnd.ts +1 -2
- package/assembly/util/scanValueEndSimd.ts +160 -0
- package/assembly/util/scanValueEndSwar.ts +142 -0
- package/assembly/util/scientific.ts +3 -6
- package/assembly/util/simd-int.ts +4 -8
- package/assembly/util/snp.ts +1 -5
- package/assembly/util/stringScan.ts +2 -4
- package/assembly/util/swar-int.ts +3 -6
- package/lib/as-bs.ts +37 -0
- package/package.json +14 -4
- package/transform/lib/builder.d.ts +0 -1
- package/transform/lib/builder.js +0 -1
- package/transform/lib/index.d.ts +0 -1
- package/transform/lib/index.js +535 -288
- package/transform/lib/linkers/alias.d.ts +0 -1
- package/transform/lib/linkers/alias.js +0 -1
- package/transform/lib/linkers/custom.d.ts +0 -1
- package/transform/lib/linkers/custom.js +0 -1
- package/transform/lib/linkers/imports.d.ts +0 -1
- package/transform/lib/linkers/imports.js +0 -1
- package/transform/lib/types.d.ts +3 -2
- package/transform/lib/types.js +2 -1
- package/transform/lib/util.d.ts +0 -1
- package/transform/lib/util.js +0 -1
- package/transform/lib/visitor.d.ts +0 -1
- package/transform/lib/visitor.js +0 -1
- package/transform/lib/builder.d.ts.map +0 -1
- package/transform/lib/builder.js.map +0 -1
- package/transform/lib/index.d.ts.map +0 -1
- package/transform/lib/index.js.map +0 -1
- package/transform/lib/linkers/alias.d.ts.map +0 -1
- package/transform/lib/linkers/alias.js.map +0 -1
- package/transform/lib/linkers/custom.d.ts.map +0 -1
- package/transform/lib/linkers/custom.js.map +0 -1
- package/transform/lib/linkers/imports.d.ts.map +0 -1
- package/transform/lib/linkers/imports.js.map +0 -1
- package/transform/lib/types.d.ts.map +0 -1
- package/transform/lib/types.js.map +0 -1
- package/transform/lib/util.d.ts.map +0 -1
- package/transform/lib/util.js.map +0 -1
- package/transform/lib/visitor.d.ts.map +0 -1
- package/transform/lib/visitor.js.map +0 -1
package/assembly/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
deserializeTypedArray,
|
|
41
41
|
} from "./deserialize";
|
|
42
42
|
import {
|
|
43
|
+
BACK_SLASH,
|
|
43
44
|
BRACE_LEFT,
|
|
44
45
|
BRACE_RIGHT,
|
|
45
46
|
BRACKET_LEFT,
|
|
@@ -58,14 +59,102 @@ import {
|
|
|
58
59
|
} from "./util/dragonbox";
|
|
59
60
|
import { ptrToStr } from "./util/ptrToStr";
|
|
60
61
|
import { atoi, bytes, scanStringEnd } from "./util";
|
|
62
|
+
import { scanValueEnd_SIMD } from "./util/scanValueEndSimd";
|
|
63
|
+
import { scanValueEnd_SWAR } from "./util/scanValueEndSwar";
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
// --- NaN-boxing encoding for JSON.Value ----------------------------------
|
|
66
|
+
// JSON.Value packs its type tag and payload into a single 8-byte word.
|
|
67
|
+
// Real f64 values are stored as their raw IEEE-754 bits; every other type is
|
|
68
|
+
// encoded inside a quiet-NaN ("boxed") word: a 5-bit tag (the JSON.Types id,
|
|
69
|
+
// or Struct for @json classes) plus a 45-bit payload holding a 32-bit pointer
|
|
70
|
+
// or a small scalar. 64-bit ints that don't fit the payload are spilled to a
|
|
71
|
+
// heap StaticArray<u64> referenced by the payload pointer and flagged in the
|
|
72
|
+
// sign bit. Only quiet-NaN doubles whose top two mantissa bits are both set
|
|
73
|
+
// collide with the box signature; hardware NaN is 0x7FF8.. so finite/Inf/NaN
|
|
74
|
+
// doubles all pass through untouched.
|
|
75
|
+
//
|
|
76
|
+
// The managed reference packed into the word is traced by JSON.Value.__visit;
|
|
77
|
+
// AssemblyScript only wires that hook for library-declared classes, so the
|
|
78
|
+
// json-as transform marks this source as a library source (see afterParse).
|
|
79
|
+
// @ts-expect-error: Decorator valid here
|
|
80
|
+
@inline const VAL_QNAN: u64 = 0x7ffc000000000000; // boxed signature (quiet NaN)
|
|
81
|
+
// @ts-expect-error: Decorator valid here
|
|
82
|
+
@inline const VAL_TAG_SHIFT: u8 = 45;
|
|
83
|
+
// @ts-expect-error: Decorator valid here
|
|
84
|
+
@inline const VAL_PAYLOAD_MASK: u64 = 0x00001fffffffffff; // low 45 bits
|
|
85
|
+
// @ts-expect-error: Decorator valid here
|
|
86
|
+
@inline const VAL_PTR_MASK: u64 = 0xffffffff; // wasm32 pointer
|
|
87
|
+
// @ts-expect-error: Decorator valid here
|
|
88
|
+
@inline const VAL_BOX64: u64 = 0x8000000000000000; // sign bit: 64-bit int spilled to heap
|
|
89
|
+
// @ts-expect-error: Decorator valid here
|
|
90
|
+
@inline const VAL_NULL: u64 = VAL_QNAN; // tag 0 (Null), payload 0
|
|
91
|
+
// @ts-expect-error: Decorator valid here
|
|
92
|
+
@inline const VAL_I64_LIMIT: i64 = 17592186044416; // 2^44 — inline range is [-2^44, 2^44)
|
|
93
|
+
// @ts-expect-error: Decorator valid here
|
|
94
|
+
@inline const VAL_U64_LIMIT: u64 = 35184372088832; // 2^45 — inline range is [0, 2^45)
|
|
95
|
+
|
|
96
|
+
// @ts-expect-error: Decorator valid here
|
|
97
|
+
@inline function valBoxed(w: u64): bool {
|
|
98
|
+
return (w & VAL_QNAN) == VAL_QNAN;
|
|
99
|
+
}
|
|
100
|
+
// @ts-expect-error: Decorator valid here
|
|
101
|
+
@inline function valTag(w: u64): u32 {
|
|
102
|
+
return <u32>((w >> VAL_TAG_SHIFT) & 0x1f);
|
|
103
|
+
}
|
|
104
|
+
// @ts-expect-error: Decorator valid here
|
|
105
|
+
@inline function valPayload(w: u64): u64 {
|
|
106
|
+
return w & VAL_PAYLOAD_MASK;
|
|
107
|
+
}
|
|
108
|
+
// @ts-expect-error: Decorator valid here
|
|
109
|
+
@inline function valPtr(w: u64): usize {
|
|
110
|
+
return <usize>(w & VAL_PTR_MASK);
|
|
111
|
+
}
|
|
65
112
|
// @ts-expect-error: Decorator valid here
|
|
66
|
-
@inline
|
|
113
|
+
@inline function valBox(tag: u32, payload: u64): u64 {
|
|
114
|
+
return (
|
|
115
|
+
VAL_QNAN | ((<u64>tag) << VAL_TAG_SHIFT) | (payload & VAL_PAYLOAD_MASK)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
// @ts-expect-error: Decorator valid here
|
|
119
|
+
@inline function valIntTag<T>(): u32 {
|
|
120
|
+
if (sizeof<T>() == 1) return isSigned<T>() ? JSON.Types.I8 : JSON.Types.U8;
|
|
121
|
+
if (sizeof<T>() == 2) return isSigned<T>() ? JSON.Types.I16 : JSON.Types.U16;
|
|
122
|
+
if (sizeof<T>() == 4) return isSigned<T>() ? JSON.Types.I32 : JSON.Types.U32;
|
|
123
|
+
return isSigned<T>() ? JSON.Types.I64 : JSON.Types.U64;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Shared zero-length sentinel for JSON.Obj's key buffer, so an empty object
|
|
127
|
+
// allocates no key storage until its first key is inserted. Never mutated.
|
|
128
|
+
// @ts-expect-error: Decorator valid here
|
|
129
|
+
@lazy const EMPTY_KEYS: StaticArray<u16> = new StaticArray<u16>(0);
|
|
67
130
|
|
|
68
131
|
export namespace JSON {
|
|
132
|
+
/**
|
|
133
|
+
* On-demand field marker. `JSON.Lazy<T>` is structurally just `T` (a no-op
|
|
134
|
+
* type alias), so a field declared `JSON.Lazy<T>` is typed and accessed
|
|
135
|
+
* exactly like `T`. The transform detects the annotation and defers that
|
|
136
|
+
* field: its raw JSON slice is stored at parse time and parsed into `T` on
|
|
137
|
+
* first access (a generated get accessor).
|
|
138
|
+
*/
|
|
139
|
+
export type Lazy<T> = T;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Whether a lazy slot's value is JSON null — for `@omitnull` on lazy fields,
|
|
143
|
+
* without forcing materialization. The slot encodes the state: `u64.MAX_VALUE`
|
|
144
|
+
* = materialized (null iff the value pointer is 0), `0` = absent (null), any
|
|
145
|
+
* other value = a not-yet-parsed slice range (null iff it is literally `null`).
|
|
146
|
+
* @param valPtr pointer of the materialized value (0 when null)
|
|
147
|
+
* @param lz the packed slot
|
|
148
|
+
*/
|
|
149
|
+
// @ts-expect-error: inline
|
|
150
|
+
@inline export function __lazyIsNull(valPtr: usize, lz: u64): bool {
|
|
151
|
+
if (lz == u64.MAX_VALUE) return valPtr == 0;
|
|
152
|
+
if (lz == 0) return true;
|
|
153
|
+
const hi = <usize>(lz >>> 32);
|
|
154
|
+
// raw slice of length 4 (8 bytes) equal to the UTF-16 word "null"
|
|
155
|
+
return <usize>(<u32>lz) - hi == 8 && load<u64>(hi) == 0x006c006c0075006e;
|
|
156
|
+
}
|
|
157
|
+
|
|
69
158
|
/**
|
|
70
159
|
* Memory management utilities for the JSON serialization buffer.
|
|
71
160
|
*/
|
|
@@ -153,17 +242,17 @@ export namespace JSON {
|
|
|
153
242
|
return NULL_WORD;
|
|
154
243
|
} else if (isString<nonnull<T>>()) {
|
|
155
244
|
serializeString(data as string);
|
|
156
|
-
return bs.out<string>();
|
|
245
|
+
return out ? bs.outTo<string>(changetype<usize>(out)) : bs.out<string>();
|
|
157
246
|
// @ts-expect-error: Defined by transform
|
|
158
247
|
} else if (isDefined(data.__SERIALIZE_CUSTOM)) {
|
|
159
248
|
// @ts-expect-error: Defined by transform
|
|
160
249
|
data.__SERIALIZE_CUSTOM();
|
|
161
|
-
return bs.out<string>();
|
|
250
|
+
return out ? bs.outTo<string>(changetype<usize>(out)) : bs.out<string>();
|
|
162
251
|
// @ts-expect-error: Defined by transform
|
|
163
252
|
} else if (isDefined(data.__SERIALIZE)) {
|
|
164
253
|
// @ts-expect-error: Defined by transform
|
|
165
|
-
|
|
166
|
-
return bs.out<string>();
|
|
254
|
+
data.__SERIALIZE(changetype<usize>(data));
|
|
255
|
+
return out ? bs.outTo<string>(changetype<usize>(out)) : bs.out<string>();
|
|
167
256
|
} else if (data instanceof Date) {
|
|
168
257
|
out = out
|
|
169
258
|
? changetype<string>(__renew(changetype<usize>(out), 52))
|
|
@@ -179,7 +268,7 @@ export namespace JSON {
|
|
|
179
268
|
return changetype<string>(out);
|
|
180
269
|
} else {
|
|
181
270
|
serializeReference<T>(data);
|
|
182
|
-
return bs.out<string>();
|
|
271
|
+
return out ? bs.outTo<string>(changetype<usize>(out)) : bs.out<string>();
|
|
183
272
|
}
|
|
184
273
|
}
|
|
185
274
|
|
|
@@ -188,11 +277,27 @@ export namespace JSON {
|
|
|
188
277
|
* ```js
|
|
189
278
|
* JSON.parse<T>(data)
|
|
190
279
|
* ```
|
|
280
|
+
* Pass an existing object as `out` to deserialize into it, reusing its
|
|
281
|
+
* allocations (symmetric with `stringify<T>(data, out)`). On the fast path the
|
|
282
|
+
* per-field reuse logic (nested structs reused as `dst`, strings `__renew`d in
|
|
283
|
+
* place when sizes match, arrays keeping capacity) makes a steady-state
|
|
284
|
+
* re-parse of the same shape allocate ~nothing after the first call.
|
|
191
285
|
* @param data string
|
|
286
|
+
* @param out optional existing object to reuse (structs/composites only)
|
|
192
287
|
* @returns T
|
|
193
288
|
*/
|
|
289
|
+
// A type-correct "zero" for any T: null pointer for references, 0/false for
|
|
290
|
+
// value types. `changetype<T>(0)` alone fails for bool/f64 (size mismatch),
|
|
291
|
+
// so branch on isReference at compile time.
|
|
292
|
+
// @ts-ignore: inline
|
|
293
|
+
@inline function __zero<T>(): T {
|
|
294
|
+
// @ts-ignore: compile-time intrinsic
|
|
295
|
+
if (isReference<T>() || isManaged<T>()) return changetype<T>(0);
|
|
296
|
+
return <T>0;
|
|
297
|
+
}
|
|
298
|
+
|
|
194
299
|
// @ts-expect-error: inline
|
|
195
|
-
@inline export function parse<T>(data: string): T {
|
|
300
|
+
@inline export function parse<T>(data: string, out: T = __zero<T>()): T {
|
|
196
301
|
let dataPtr = changetype<usize>(data);
|
|
197
302
|
const dataEnd = dataPtr + bytes(data);
|
|
198
303
|
// Entry point skips leading whitespace: every deserialize handler may then
|
|
@@ -223,24 +328,27 @@ export namespace JSON {
|
|
|
223
328
|
let type: nonnull<T> = changetype<nonnull<T>>(0);
|
|
224
329
|
// @ts-expect-error: Defined by transform
|
|
225
330
|
if (isDefined(type.__DESERIALIZE_CUSTOM)) {
|
|
226
|
-
const
|
|
331
|
+
const obj = changetype<nonnull<T>>(0);
|
|
227
332
|
// @ts-expect-error
|
|
228
|
-
return
|
|
333
|
+
return obj.__DESERIALIZE_CUSTOM(data);
|
|
229
334
|
// @ts-expect-error: Defined by transform
|
|
230
335
|
} else if (
|
|
231
336
|
isDefined(type.__DESERIALIZE_SLOW) ||
|
|
232
337
|
isDefined(type.__DESERIALIZE_FAST)
|
|
233
338
|
) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
339
|
+
// Reuse the caller-supplied `out` graph when given; otherwise allocate.
|
|
340
|
+
const obj = changetype<usize>(out)
|
|
341
|
+
? changetype<nonnull<T>>(changetype<usize>(out))
|
|
342
|
+
: changetype<nonnull<T>>(
|
|
343
|
+
__new(offsetof<nonnull<T>>(), idof<nonnull<T>>()),
|
|
344
|
+
);
|
|
237
345
|
// @ts-expect-error: Defined by transform
|
|
238
346
|
if (isDefined(type.__DESERIALIZE_FAST)) {
|
|
239
347
|
// @ts-expect-error: Defined by transform
|
|
240
|
-
const fastEnd =
|
|
348
|
+
const fastEnd = obj.__DESERIALIZE_FAST(
|
|
241
349
|
dataPtr,
|
|
242
350
|
dataPtr + dataSize,
|
|
243
|
-
|
|
351
|
+
obj,
|
|
244
352
|
);
|
|
245
353
|
// A non-zero return means the fast path matched; accept it when only
|
|
246
354
|
// trailing whitespace remains (pretty-printed input ends with a
|
|
@@ -249,31 +357,37 @@ export namespace JSON {
|
|
|
249
357
|
fastEnd != 0 &&
|
|
250
358
|
JSON.Util.skipWhitespace(fastEnd, dataPtr + dataSize) ==
|
|
251
359
|
dataPtr + dataSize
|
|
252
|
-
)
|
|
253
|
-
|
|
360
|
+
) {
|
|
361
|
+
// @ts-expect-error: Defined by transform for @lazy-field structs —
|
|
362
|
+
// pins the source so stored slice ranges stay valid.
|
|
363
|
+
if (isDefined(obj.__SET_SRC)) obj.__SET_SRC(data);
|
|
364
|
+
return obj;
|
|
365
|
+
}
|
|
254
366
|
}
|
|
255
|
-
if (isDefined(type.__INITIALIZE))
|
|
367
|
+
if (isDefined(type.__INITIALIZE)) obj.__INITIALIZE();
|
|
256
368
|
// @ts-expect-error: Defined by transform
|
|
257
369
|
if (isDefined(type.__DESERIALIZE_SLOW)) {
|
|
258
370
|
// @ts-expect-error: Defined by transform
|
|
259
|
-
|
|
260
|
-
|
|
371
|
+
obj.__DESERIALIZE_SLOW(dataPtr, dataPtr + dataSize, obj);
|
|
372
|
+
// @ts-expect-error: Defined by transform for @lazy-field structs.
|
|
373
|
+
if (isDefined(obj.__SET_SRC)) obj.__SET_SRC(data);
|
|
374
|
+
return obj;
|
|
261
375
|
}
|
|
262
376
|
throw new Error(`No deserialize method defined for type ${type}`);
|
|
263
377
|
}
|
|
264
378
|
if (type instanceof StaticArray) {
|
|
265
379
|
// @ts-expect-error
|
|
266
|
-
return
|
|
267
|
-
|
|
380
|
+
return deserializeStaticArray<nonnull<T>>(
|
|
381
|
+
dataPtr,
|
|
382
|
+
dataPtr + dataSize,
|
|
383
|
+
0,
|
|
268
384
|
);
|
|
269
385
|
} else if (type instanceof Array) {
|
|
270
386
|
// @ts-expect-error
|
|
271
|
-
return
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
changetype<usize>(instantiate<T>()),
|
|
276
|
-
),
|
|
387
|
+
return deserializeArray<nonnull<T>>(
|
|
388
|
+
dataPtr,
|
|
389
|
+
dataPtr + dataSize,
|
|
390
|
+
changetype<usize>(instantiate<T>()),
|
|
277
391
|
);
|
|
278
392
|
} else if (
|
|
279
393
|
type instanceof Int8Array ||
|
|
@@ -297,14 +411,10 @@ export namespace JSON {
|
|
|
297
411
|
return deserializeArrayBuffer(dataPtr, dataPtr + dataSize, 0) as T;
|
|
298
412
|
} else if (type instanceof Set) {
|
|
299
413
|
// @ts-expect-error
|
|
300
|
-
return
|
|
301
|
-
deserializeSet<nonnull<T>>(dataPtr, dataPtr + dataSize, 0),
|
|
302
|
-
);
|
|
414
|
+
return deserializeSet<nonnull<T>>(dataPtr, dataPtr + dataSize, 0);
|
|
303
415
|
} else if (type instanceof Map) {
|
|
304
416
|
// @ts-expect-error
|
|
305
|
-
return
|
|
306
|
-
deserializeMap<nonnull<T>>(dataPtr, dataPtr + dataSize, 0),
|
|
307
|
-
);
|
|
417
|
+
return deserializeMap<nonnull<T>>(dataPtr, dataPtr + dataSize, 0);
|
|
308
418
|
} else if (type instanceof Date) {
|
|
309
419
|
// @ts-expect-error
|
|
310
420
|
return deserializeDate(dataPtr, dataPtr + dataSize);
|
|
@@ -313,12 +423,10 @@ export namespace JSON {
|
|
|
313
423
|
return deserializeRaw(dataPtr, dataPtr + dataSize);
|
|
314
424
|
} else if (type instanceof JSON.Value) {
|
|
315
425
|
// @ts-expect-error
|
|
316
|
-
return
|
|
317
|
-
deserializeArbitrary(dataPtr, dataPtr + dataSize, 0),
|
|
318
|
-
);
|
|
426
|
+
return deserializeArbitrary(dataPtr, dataPtr + dataSize, 0);
|
|
319
427
|
} else if (type instanceof JSON.Obj) {
|
|
320
428
|
// @ts-expect-error
|
|
321
|
-
return
|
|
429
|
+
return deserializeObject(dataPtr, dataPtr + dataSize, 0);
|
|
322
430
|
} else if (type instanceof JSON.Box) {
|
|
323
431
|
// @ts-expect-error
|
|
324
432
|
return new JSON.Box(parseBox(data, changetype<nonnull<T>>(0).value));
|
|
@@ -463,24 +571,39 @@ export namespace JSON {
|
|
|
463
571
|
/** Map of struct type IDs to their serialization function indices */
|
|
464
572
|
@lazy static METHODS: Map<u32, u32> = new Map<u32, u32>();
|
|
465
573
|
|
|
466
|
-
/**
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
/** Internal storage for the value (8 bytes, can hold any primitive or pointer) */
|
|
470
|
-
private storage: u64;
|
|
574
|
+
/** NaN-boxed word holding both the type tag and the value (8 bytes). */
|
|
575
|
+
private bits: u64;
|
|
471
576
|
|
|
472
577
|
private constructor() {
|
|
473
578
|
unreachable();
|
|
474
579
|
}
|
|
475
580
|
|
|
581
|
+
/**
|
|
582
|
+
* The runtime type identifier (see JSON.Types), decoded from the boxed word.
|
|
583
|
+
* Struct values report `idof<T>() + JSON.Types.Struct`, recovered from the
|
|
584
|
+
* stored object's runtime header.
|
|
585
|
+
*/
|
|
586
|
+
get type(): u16 {
|
|
587
|
+
const w = this.bits;
|
|
588
|
+
if (!valBoxed(w)) return JSON.Types.F64;
|
|
589
|
+
const tag = valTag(w);
|
|
590
|
+
if (tag == JSON.Types.Struct) {
|
|
591
|
+
const rtId = changetype<OBJECT>(valPtr(w) - TOTAL_OVERHEAD).rtId;
|
|
592
|
+
return <u16>rtId + JSON.Types.Struct;
|
|
593
|
+
}
|
|
594
|
+
return <u16>tag;
|
|
595
|
+
}
|
|
596
|
+
|
|
476
597
|
/**
|
|
477
598
|
* Creates an JSON.Value instance with no set value.
|
|
478
599
|
* @returns An instance of JSON.Value.
|
|
479
600
|
*/
|
|
480
601
|
@inline static empty(): JSON.Value {
|
|
481
|
-
|
|
602
|
+
const out = changetype<JSON.Value>(
|
|
482
603
|
__new(offsetof<JSON.Value>(), idof<JSON.Value>()),
|
|
483
604
|
);
|
|
605
|
+
out.bits = VAL_NULL;
|
|
606
|
+
return out;
|
|
484
607
|
}
|
|
485
608
|
|
|
486
609
|
/**
|
|
@@ -556,35 +679,37 @@ export namespace JSON {
|
|
|
556
679
|
* @param value - The value to be set.
|
|
557
680
|
*/
|
|
558
681
|
@inline set<T>(value: T): void {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
if (
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
else if (
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
682
|
+
if (value instanceof JSON.Box) {
|
|
683
|
+
this.set(value.value);
|
|
684
|
+
} else if (isBoolean<T>()) {
|
|
685
|
+
this.bits = valBox(JSON.Types.Bool, value ? 1 : 0);
|
|
686
|
+
} else if (isInteger<T>() && nameof<T>() == "usize") {
|
|
687
|
+
// A `usize` of 0 is the null sentinel (see deserializeArbitrary);
|
|
688
|
+
// any other usize is an ordinary 32-bit unsigned integer.
|
|
689
|
+
this.bits = value ? valBox(valIntTag<T>(), <u64>value) : VAL_NULL;
|
|
690
|
+
} else if (isFloat<T>()) {
|
|
691
|
+
if (sizeof<T>() == 4) {
|
|
692
|
+
this.bits = valBox(JSON.Types.F32, <u64>reinterpret<u32>(<f32>value));
|
|
693
|
+
} else {
|
|
694
|
+
const f = <f64>value;
|
|
695
|
+
// Canonicalize NaN so it never collides with the box signature.
|
|
696
|
+
this.bits = isNaN(f) ? 0x7ff8000000000000 : reinterpret<u64>(f);
|
|
697
|
+
}
|
|
698
|
+
} else if (isInteger<T>()) {
|
|
699
|
+
if (sizeof<T>() == 8) this.setWide<T>(value);
|
|
700
|
+
else this.bits = valBox(valIntTag<T>(), <u64>value);
|
|
701
|
+
} else if (isNullable<T>() && changetype<usize>(value) === 0) {
|
|
702
|
+
this.bits = VAL_NULL;
|
|
703
|
+
} else if (isString<T>()) {
|
|
704
|
+
this.bits = valBox(JSON.Types.String, <u64>changetype<usize>(value));
|
|
705
|
+
} else if (value instanceof JSON.Raw) {
|
|
706
|
+
this.bits = valBox(JSON.Types.Raw, <u64>changetype<usize>(value));
|
|
707
|
+
// @ts-expect-error: supplied by transform
|
|
708
|
+
} else if (isDefined(value.__SERIALIZE) && isManaged<T>(value)) {
|
|
580
709
|
// @ts-expect-error
|
|
581
710
|
if (!JSON.Value.METHODS.has(idof<T>()))
|
|
582
711
|
JSON.Value.METHODS.set(idof<T>(), value.__SERIALIZE.index);
|
|
583
|
-
|
|
584
|
-
changetype<usize>(this),
|
|
585
|
-
changetype<usize>(value),
|
|
586
|
-
STORAGE,
|
|
587
|
-
);
|
|
712
|
+
this.bits = valBox(JSON.Types.Struct, <u64>changetype<usize>(value));
|
|
588
713
|
} else if (
|
|
589
714
|
value instanceof Int8Array ||
|
|
590
715
|
value instanceof Uint8Array ||
|
|
@@ -596,20 +721,52 @@ export namespace JSON {
|
|
|
596
721
|
value instanceof Int64Array ||
|
|
597
722
|
value instanceof Uint64Array ||
|
|
598
723
|
value instanceof Float32Array ||
|
|
599
|
-
value instanceof Float64Array
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
724
|
+
value instanceof Float64Array
|
|
725
|
+
) {
|
|
726
|
+
this.bits = valBox(
|
|
727
|
+
JSON.Types.TypedArray,
|
|
728
|
+
<u64>changetype<usize>(value),
|
|
729
|
+
);
|
|
730
|
+
} else if (value instanceof ArrayBuffer) {
|
|
731
|
+
this.bits = valBox(
|
|
732
|
+
JSON.Types.ArrayBuffer,
|
|
733
|
+
<u64>changetype<usize>(value),
|
|
734
|
+
);
|
|
735
|
+
} else if (value instanceof Map) {
|
|
604
736
|
if (idof<T>() !== idof<Map<string, JSON.Value>>()) {
|
|
605
737
|
abort("Maps must be of type Map<string, JSON.Value>!");
|
|
606
738
|
}
|
|
607
|
-
|
|
739
|
+
this.bits = valBox(JSON.Types.Map, <u64>changetype<usize>(value));
|
|
608
740
|
} else if (value instanceof JSON.Obj) {
|
|
609
|
-
|
|
741
|
+
this.bits = valBox(JSON.Types.Object, <u64>changetype<usize>(value));
|
|
610
742
|
// @ts-expect-error
|
|
611
743
|
} else if (isArray<T>() && idof<valueof<T>>() == idof<JSON.Value>()) {
|
|
612
|
-
|
|
744
|
+
this.bits = valBox(JSON.Types.Array, <u64>changetype<usize>(value));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** Encodes a 64-bit integer, spilling to the heap when it exceeds the payload. */
|
|
749
|
+
private setWide<T>(value: T): void {
|
|
750
|
+
if (isSigned<T>()) {
|
|
751
|
+
const v = <i64>value;
|
|
752
|
+
if (v >= -VAL_I64_LIMIT && v < VAL_I64_LIMIT) {
|
|
753
|
+
this.bits = valBox(JSON.Types.I64, <u64>v);
|
|
754
|
+
} else {
|
|
755
|
+
const box = new StaticArray<u64>(1);
|
|
756
|
+
unchecked((box[0] = <u64>v));
|
|
757
|
+
this.bits =
|
|
758
|
+
valBox(JSON.Types.I64, <u64>changetype<usize>(box)) | VAL_BOX64;
|
|
759
|
+
}
|
|
760
|
+
} else {
|
|
761
|
+
const v = <u64>value;
|
|
762
|
+
if (v < VAL_U64_LIMIT) {
|
|
763
|
+
this.bits = valBox(JSON.Types.U64, v);
|
|
764
|
+
} else {
|
|
765
|
+
const box = new StaticArray<u64>(1);
|
|
766
|
+
unchecked((box[0] = v));
|
|
767
|
+
this.bits =
|
|
768
|
+
valBox(JSON.Types.U64, <u64>changetype<usize>(box)) | VAL_BOX64;
|
|
769
|
+
}
|
|
613
770
|
}
|
|
614
771
|
}
|
|
615
772
|
|
|
@@ -618,7 +775,23 @@ export namespace JSON {
|
|
|
618
775
|
* @returns The encapsulated value.
|
|
619
776
|
*/
|
|
620
777
|
@inline get<T>(): T {
|
|
621
|
-
|
|
778
|
+
const w = this.bits;
|
|
779
|
+
if (isFloat<T>()) {
|
|
780
|
+
if (sizeof<T>() == 4) return <T>reinterpret<f32>(<u32>valPayload(w));
|
|
781
|
+
return <T>reinterpret<f64>(w);
|
|
782
|
+
} else if (isInteger<T>()) {
|
|
783
|
+
if (sizeof<T>() == 8) {
|
|
784
|
+
if (w & VAL_BOX64) return load<T>(valPtr(w));
|
|
785
|
+
if (isSigned<T>()) return <T>((<i64>(valPayload(w) << 19)) >> 19);
|
|
786
|
+
return <T>valPayload(w);
|
|
787
|
+
}
|
|
788
|
+
return <T>valPayload(w);
|
|
789
|
+
} else if (isBoolean<T>()) {
|
|
790
|
+
return <T>valPayload(w);
|
|
791
|
+
} else if (isReference<T>()) {
|
|
792
|
+
return changetype<T>(valPtr(w));
|
|
793
|
+
}
|
|
794
|
+
return unreachable();
|
|
622
795
|
}
|
|
623
796
|
|
|
624
797
|
/**
|
|
@@ -627,7 +800,7 @@ export namespace JSON {
|
|
|
627
800
|
* @returns The encapsulated value.
|
|
628
801
|
*/
|
|
629
802
|
@inline as<T>(): T {
|
|
630
|
-
return
|
|
803
|
+
return this.get<T>();
|
|
631
804
|
}
|
|
632
805
|
|
|
633
806
|
/**
|
|
@@ -709,8 +882,18 @@ export namespace JSON {
|
|
|
709
882
|
|
|
710
883
|
|
|
711
884
|
@unsafe private __visit(cookie: u32): void {
|
|
712
|
-
|
|
713
|
-
|
|
885
|
+
const w = this.bits;
|
|
886
|
+
if (!valBoxed(w)) return; // raw f64 holds no reference
|
|
887
|
+
const tag = valTag(w);
|
|
888
|
+
// String(13)..ArrayBuffer(19) and Struct all carry a managed pointer;
|
|
889
|
+
// Raw(1) is intentionally not traced (matches prior behavior).
|
|
890
|
+
if (tag >= JSON.Types.String) {
|
|
891
|
+
__visit(valPtr(w), cookie);
|
|
892
|
+
} else if (
|
|
893
|
+
(tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
|
|
894
|
+
w & VAL_BOX64
|
|
895
|
+
) {
|
|
896
|
+
__visit(valPtr(w), cookie); // heap-spilled 64-bit int
|
|
714
897
|
}
|
|
715
898
|
}
|
|
716
899
|
}
|
|
@@ -735,8 +918,16 @@ export namespace JSON {
|
|
|
735
918
|
* ```
|
|
736
919
|
*/
|
|
737
920
|
export class Obj {
|
|
738
|
-
|
|
739
|
-
|
|
921
|
+
// Keys are packed into one growable buffer, each prefixed by a u16 length
|
|
922
|
+
// (UTF-16 code units), instead of allocating a heap string per key. Values
|
|
923
|
+
// are a parallel array. A key -> position index is built lazily on the
|
|
924
|
+
// first keyed access (never during parsing). Per-object allocation count
|
|
925
|
+
// matches the previous Map-based storage, while deserialization avoids the
|
|
926
|
+
// per-key string allocation and hashing entirely.
|
|
927
|
+
_kbuf: StaticArray<u16> = EMPTY_KEYS;
|
|
928
|
+
_kused: i32 = 0;
|
|
929
|
+
_vals: JSON.Value[] = [];
|
|
930
|
+
private _index: Map<string, i32> | null = null;
|
|
740
931
|
|
|
741
932
|
constructor() {}
|
|
742
933
|
|
|
@@ -744,16 +935,106 @@ export namespace JSON {
|
|
|
744
935
|
* Gets the number of key-value pairs in the object.
|
|
745
936
|
*/
|
|
746
937
|
@inline get size(): i32 {
|
|
747
|
-
return this.
|
|
938
|
+
return this._vals.length;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/** Grows the key buffer to hold at least `need` code units. */
|
|
942
|
+
private ensureKeyCap(need: i32): void {
|
|
943
|
+
const cap = this._kbuf.length;
|
|
944
|
+
if (cap >= need) return;
|
|
945
|
+
let n = cap ? cap : 16;
|
|
946
|
+
while (n < need) n <<= 1;
|
|
947
|
+
const nb = new StaticArray<u16>(n);
|
|
948
|
+
if (this._kused)
|
|
949
|
+
memory.copy(
|
|
950
|
+
changetype<usize>(nb),
|
|
951
|
+
changetype<usize>(this._kbuf),
|
|
952
|
+
(<usize>this._kused) << 1,
|
|
953
|
+
);
|
|
954
|
+
this._kbuf = nb;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/** Appends a length-prefixed key (from a source memory range). */
|
|
958
|
+
private pushKeyBytes(keyStart: usize, keyEnd: usize): void {
|
|
959
|
+
const len = <i32>((keyEnd - keyStart) >> 1);
|
|
960
|
+
const pos = this._kused;
|
|
961
|
+
this.ensureKeyCap(pos + 1 + len);
|
|
962
|
+
const buf = changetype<usize>(this._kbuf);
|
|
963
|
+
store<u16>(buf + ((<usize>pos) << 1), <u16>len);
|
|
964
|
+
if (len)
|
|
965
|
+
memory.copy(
|
|
966
|
+
buf + ((<usize>(pos + 1)) << 1),
|
|
967
|
+
keyStart,
|
|
968
|
+
(<usize>len) << 1,
|
|
969
|
+
);
|
|
970
|
+
this._kused = pos + 1 + len;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/** Materializes a key string from `len` code units starting at slot `at`. */
|
|
974
|
+
private makeKey(at: i32, len: i32): string {
|
|
975
|
+
const out = changetype<string>(__new((<usize>len) << 1, idof<string>()));
|
|
976
|
+
if (len)
|
|
977
|
+
memory.copy(
|
|
978
|
+
changetype<usize>(out),
|
|
979
|
+
changetype<usize>(this._kbuf) + ((<usize>at) << 1),
|
|
980
|
+
(<usize>len) << 1,
|
|
981
|
+
);
|
|
982
|
+
return out;
|
|
748
983
|
}
|
|
749
984
|
|
|
750
985
|
/**
|
|
751
|
-
*
|
|
986
|
+
* Appends a key (from a source memory range) and value without a
|
|
987
|
+
* duplicate-key check. Used by the deserializer — no per-key string
|
|
988
|
+
* allocation, no hashing.
|
|
989
|
+
*/
|
|
990
|
+
appendRaw<T>(keyStart: usize, keyEnd: usize, value: T): void {
|
|
991
|
+
this.pushKeyBytes(keyStart, keyEnd);
|
|
992
|
+
this._vals.push(JSON.Value.from<T>(value));
|
|
993
|
+
const idx = this._index;
|
|
994
|
+
if (idx !== null) {
|
|
995
|
+
const len = <i32>((keyEnd - keyStart) >> 1);
|
|
996
|
+
const k = changetype<string>(__new((<usize>len) << 1, idof<string>()));
|
|
997
|
+
if (len) memory.copy(changetype<usize>(k), keyStart, (<usize>len) << 1);
|
|
998
|
+
idx.set(k, this._vals.length - 1);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/** Builds (once) and returns the lazy key -> position index. */
|
|
1003
|
+
private buildIndex(): Map<string, i32> {
|
|
1004
|
+
let idx = this._index;
|
|
1005
|
+
if (idx === null) {
|
|
1006
|
+
idx = new Map<string, i32>();
|
|
1007
|
+
const buf = changetype<usize>(this._kbuf);
|
|
1008
|
+
const used = this._kused;
|
|
1009
|
+
let pos = 0;
|
|
1010
|
+
let i = 0;
|
|
1011
|
+
while (pos < used) {
|
|
1012
|
+
const len = <i32>load<u16>(buf + ((<usize>pos) << 1));
|
|
1013
|
+
idx.set(this.makeKey(pos + 1, len), i++);
|
|
1014
|
+
pos += 1 + len;
|
|
1015
|
+
}
|
|
1016
|
+
this._index = idx;
|
|
1017
|
+
}
|
|
1018
|
+
return idx;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Sets a key-value pair in the object, overwriting any existing value.
|
|
752
1023
|
* @param key - The string key
|
|
753
1024
|
* @param value - The value (will be wrapped in JSON.Value)
|
|
754
1025
|
*/
|
|
755
|
-
|
|
756
|
-
this.
|
|
1026
|
+
set<T>(key: string, value: T): void {
|
|
1027
|
+
const idx = this.buildIndex();
|
|
1028
|
+
if (idx.has(key)) {
|
|
1029
|
+
unchecked((this._vals[idx.get(key)] = JSON.Value.from<T>(value)));
|
|
1030
|
+
} else {
|
|
1031
|
+
this.pushKeyBytes(
|
|
1032
|
+
changetype<usize>(key),
|
|
1033
|
+
changetype<usize>(key) + ((<usize>key.length) << 1),
|
|
1034
|
+
);
|
|
1035
|
+
this._vals.push(JSON.Value.from<T>(value));
|
|
1036
|
+
idx.set(key, this._vals.length - 1);
|
|
1037
|
+
}
|
|
757
1038
|
}
|
|
758
1039
|
|
|
759
1040
|
/**
|
|
@@ -762,8 +1043,8 @@ export namespace JSON {
|
|
|
762
1043
|
* @returns The JSON.Value or null if not found
|
|
763
1044
|
*/
|
|
764
1045
|
@inline get(key: string): JSON.Value | null {
|
|
765
|
-
|
|
766
|
-
return this.
|
|
1046
|
+
const idx = this.buildIndex();
|
|
1047
|
+
return idx.has(key) ? unchecked(this._vals[idx.get(key)]) : null;
|
|
767
1048
|
}
|
|
768
1049
|
|
|
769
1050
|
/**
|
|
@@ -772,7 +1053,7 @@ export namespace JSON {
|
|
|
772
1053
|
* @returns true if the key exists
|
|
773
1054
|
*/
|
|
774
1055
|
@inline has(key: string): bool {
|
|
775
|
-
return this.
|
|
1056
|
+
return this.buildIndex().has(key);
|
|
776
1057
|
}
|
|
777
1058
|
|
|
778
1059
|
/**
|
|
@@ -780,24 +1061,53 @@ export namespace JSON {
|
|
|
780
1061
|
* @param key - The key to delete
|
|
781
1062
|
* @returns true if the key was found and deleted
|
|
782
1063
|
*/
|
|
783
|
-
|
|
784
|
-
|
|
1064
|
+
delete(key: string): bool {
|
|
1065
|
+
const idx = this.buildIndex();
|
|
1066
|
+
if (!idx.has(key)) return false;
|
|
1067
|
+
const removed = idx.get(key);
|
|
1068
|
+
const keys = this.keys();
|
|
1069
|
+
const vals = this._vals;
|
|
1070
|
+
this._kbuf = EMPTY_KEYS;
|
|
1071
|
+
this._kused = 0;
|
|
1072
|
+
const newVals = new Array<JSON.Value>();
|
|
1073
|
+
for (let j = 0; j < keys.length; j++) {
|
|
1074
|
+
if (j == removed) continue;
|
|
1075
|
+
const k = unchecked(keys[j]);
|
|
1076
|
+
this.pushKeyBytes(
|
|
1077
|
+
changetype<usize>(k),
|
|
1078
|
+
changetype<usize>(k) + ((<usize>k.length) << 1),
|
|
1079
|
+
);
|
|
1080
|
+
newVals.push(unchecked(vals[j]));
|
|
1081
|
+
}
|
|
1082
|
+
this._vals = newVals;
|
|
1083
|
+
this._index = null;
|
|
1084
|
+
return true;
|
|
785
1085
|
}
|
|
786
1086
|
|
|
787
1087
|
/**
|
|
788
1088
|
* Gets all keys in the object.
|
|
789
|
-
* @returns Array of string keys
|
|
1089
|
+
* @returns Array of string keys (in insertion order)
|
|
790
1090
|
*/
|
|
791
|
-
|
|
792
|
-
|
|
1091
|
+
keys(): string[] {
|
|
1092
|
+
const out = new Array<string>(this._vals.length);
|
|
1093
|
+
const buf = changetype<usize>(this._kbuf);
|
|
1094
|
+
const used = this._kused;
|
|
1095
|
+
let pos = 0;
|
|
1096
|
+
let i = 0;
|
|
1097
|
+
while (pos < used) {
|
|
1098
|
+
const len = <i32>load<u16>(buf + ((<usize>pos) << 1));
|
|
1099
|
+
unchecked((out[i++] = this.makeKey(pos + 1, len)));
|
|
1100
|
+
pos += 1 + len;
|
|
1101
|
+
}
|
|
1102
|
+
return out;
|
|
793
1103
|
}
|
|
794
1104
|
|
|
795
1105
|
/**
|
|
796
1106
|
* Gets all values in the object.
|
|
797
|
-
* @returns Array of JSON.Value instances
|
|
1107
|
+
* @returns Array of JSON.Value instances (in insertion order)
|
|
798
1108
|
*/
|
|
799
1109
|
@inline values(): JSON.Value[] {
|
|
800
|
-
return this.
|
|
1110
|
+
return this._vals.slice();
|
|
801
1111
|
}
|
|
802
1112
|
|
|
803
1113
|
/**
|
|
@@ -813,7 +1123,7 @@ export namespace JSON {
|
|
|
813
1123
|
* @param value - The value to convert
|
|
814
1124
|
* @returns A new JSON.Obj instance
|
|
815
1125
|
*/
|
|
816
|
-
|
|
1126
|
+
static from<T>(value: T): JSON.Obj {
|
|
817
1127
|
if (value instanceof JSON.Obj) return value;
|
|
818
1128
|
if (value instanceof Map) {
|
|
819
1129
|
const out = new JSON.Obj();
|
|
@@ -933,7 +1243,7 @@ export namespace JSON {
|
|
|
933
1243
|
serializeStruct(changetype<nonnull<T>>(data));
|
|
934
1244
|
} else if (data instanceof Date) {
|
|
935
1245
|
// @ts-expect-error
|
|
936
|
-
|
|
1246
|
+
serializeDate(changetype<nonnull<T>>(data));
|
|
937
1247
|
} else {
|
|
938
1248
|
serializeReference<T>(data);
|
|
939
1249
|
}
|
|
@@ -1085,55 +1395,71 @@ export namespace JSON {
|
|
|
1085
1395
|
return srcStart;
|
|
1086
1396
|
}
|
|
1087
1397
|
// @ts-expect-error: decorator
|
|
1088
|
-
@inline
|
|
1089
|
-
srcStart
|
|
1090
|
-
srcEnd:
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
let
|
|
1094
|
-
|
|
1095
|
-
if (ptr >= srcEnd) return 0;
|
|
1096
|
-
const first = load<u16>(ptr);
|
|
1097
|
-
|
|
1098
|
-
if (first == QUOTE) {
|
|
1099
|
-
const endQuote = scanStringEnd(ptr, srcEnd);
|
|
1100
|
-
return endQuote >= srcEnd ? 0 : endQuote + 2;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
if (first == BRACE_LEFT || first == BRACKET_LEFT) {
|
|
1104
|
-
let depth: i32 = 1;
|
|
1105
|
-
ptr += 2;
|
|
1106
|
-
while (ptr < srcEnd) {
|
|
1107
|
-
const code = load<u16>(ptr);
|
|
1108
|
-
if (code == QUOTE) {
|
|
1109
|
-
const endQuote = scanStringEnd(ptr, srcEnd);
|
|
1110
|
-
if (endQuote >= srcEnd) return 0;
|
|
1111
|
-
ptr = endQuote + 2;
|
|
1112
|
-
continue;
|
|
1113
|
-
}
|
|
1114
|
-
if (code == BRACE_LEFT || code == BRACKET_LEFT) {
|
|
1115
|
-
depth++;
|
|
1116
|
-
} else if (code == BRACE_RIGHT || code == BRACKET_RIGHT) {
|
|
1117
|
-
if (--depth == 0) return ptr + 2;
|
|
1118
|
-
}
|
|
1119
|
-
ptr += 2;
|
|
1120
|
-
}
|
|
1121
|
-
return 0;
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1398
|
+
@inline function scanQuotedValueEnd(srcStart: usize, srcEnd: usize): usize {
|
|
1399
|
+
const endQuote = scanStringEnd(srcStart, srcEnd);
|
|
1400
|
+
return endQuote >= srcEnd ? 0 : endQuote + 2;
|
|
1401
|
+
}
|
|
1402
|
+
function scanCompositeValueEnd(srcStart: usize, srcEnd: usize): usize {
|
|
1403
|
+
let depth: i32 = 1;
|
|
1404
|
+
let ptr = srcStart + 2;
|
|
1124
1405
|
while (ptr < srcEnd) {
|
|
1125
1406
|
const code = load<u16>(ptr);
|
|
1407
|
+
if (code == QUOTE) {
|
|
1408
|
+
ptr = scanQuotedValueEnd(ptr, srcEnd);
|
|
1409
|
+
if (!ptr) return 0;
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1412
|
+
if (code == BRACE_LEFT || code == BRACKET_LEFT) {
|
|
1413
|
+
depth++;
|
|
1414
|
+
} else if (code == BRACE_RIGHT || code == BRACKET_RIGHT) {
|
|
1415
|
+
if (--depth == 0) return ptr + 2;
|
|
1416
|
+
}
|
|
1417
|
+
ptr += 2;
|
|
1418
|
+
}
|
|
1419
|
+
return 0;
|
|
1420
|
+
}
|
|
1421
|
+
function scanScalarValueEnd(srcStart: usize, srcEnd: usize): usize {
|
|
1422
|
+
while (srcStart < srcEnd) {
|
|
1423
|
+
const code = load<u16>(srcStart);
|
|
1126
1424
|
if (
|
|
1127
1425
|
code == COMMA ||
|
|
1128
1426
|
code == BRACKET_RIGHT ||
|
|
1129
1427
|
code == BRACE_RIGHT ||
|
|
1130
1428
|
isSpace(code)
|
|
1131
1429
|
)
|
|
1132
|
-
return
|
|
1133
|
-
|
|
1430
|
+
return srcStart;
|
|
1431
|
+
srcStart += 2;
|
|
1134
1432
|
}
|
|
1135
1433
|
|
|
1136
|
-
return
|
|
1434
|
+
return srcStart;
|
|
1435
|
+
}
|
|
1436
|
+
// @ts-expect-error: decorator
|
|
1437
|
+
@inline export function scanValueEnd<T = JSON.Value>(
|
|
1438
|
+
srcStart: usize,
|
|
1439
|
+
srcEnd: usize,
|
|
1440
|
+
): usize {
|
|
1441
|
+
if (srcStart >= srcEnd) return 0;
|
|
1442
|
+
let ptr = skipWhitespace(srcStart, srcEnd);
|
|
1443
|
+
if (ptr >= srcEnd) return 0;
|
|
1444
|
+
|
|
1445
|
+
if (ASC_FEATURE_SIMD) return scanValueEnd_SIMD<T>(ptr, srcEnd);
|
|
1446
|
+
if (JSON_MODE == JSONMode.SWAR) return scanValueEnd_SWAR<T>(ptr, srcEnd);
|
|
1447
|
+
|
|
1448
|
+
const first = load<u16>(ptr);
|
|
1449
|
+
if (isString<nonnull<T>>() && first == QUOTE)
|
|
1450
|
+
return scanQuotedValueEnd(ptr, srcEnd);
|
|
1451
|
+
if (isArray<nonnull<T>>() && first == BRACKET_LEFT)
|
|
1452
|
+
return scanCompositeValueEnd(ptr, srcEnd);
|
|
1453
|
+
if (
|
|
1454
|
+
(isManaged<nonnull<T>>() || isReference<nonnull<T>>()) &&
|
|
1455
|
+
first == BRACE_LEFT
|
|
1456
|
+
)
|
|
1457
|
+
return scanCompositeValueEnd(ptr, srcEnd);
|
|
1458
|
+
|
|
1459
|
+
if (first == QUOTE) return scanQuotedValueEnd(ptr, srcEnd);
|
|
1460
|
+
if (first == BRACE_LEFT || first == BRACKET_LEFT)
|
|
1461
|
+
return scanCompositeValueEnd(ptr, srcEnd);
|
|
1462
|
+
return scanScalarValueEnd(ptr, srcEnd);
|
|
1137
1463
|
}
|
|
1138
1464
|
// @ts-expect-error: decorator
|
|
1139
1465
|
@inline export function ptrToStr(start: usize, end: usize): string {
|
|
@@ -1154,11 +1480,7 @@ export namespace JSON {
|
|
|
1154
1480
|
* @param out - string | null
|
|
1155
1481
|
* @returns - string
|
|
1156
1482
|
*/
|
|
1157
|
-
|
|
1158
|
-
@inline export function stringify<T>(
|
|
1159
|
-
data: T,
|
|
1160
|
-
out: string | null = null,
|
|
1161
|
-
): string {
|
|
1483
|
+
export function stringify<T>(data: T, out: string | null = null): string {
|
|
1162
1484
|
bs.saveState();
|
|
1163
1485
|
JSON.__serialize<T>(data);
|
|
1164
1486
|
const result = bs.cpyOut<string>();
|