json-as 1.3.9 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -19
- package/README.md +120 -21
- package/assembly/custom/chars.ts +39 -78
- package/assembly/deserialize/index/arbitrary.ts +28 -10
- package/assembly/deserialize/index/float.ts +2 -4
- package/assembly/deserialize/index/integer.ts +2 -4
- package/assembly/deserialize/index/object.ts +6 -1
- package/assembly/deserialize/index/string.ts +2 -7
- package/assembly/deserialize/index/unsigned.ts +2 -4
- 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 +2 -7
- package/assembly/deserialize/naive/array/map.ts +10 -14
- package/assembly/deserialize/naive/array/object.ts +10 -14
- package/assembly/deserialize/naive/array/struct.ts +19 -1
- package/assembly/deserialize/naive/bool.ts +1 -5
- package/assembly/deserialize/naive/date.ts +1 -2
- package/assembly/deserialize/naive/float.ts +4 -11
- package/assembly/deserialize/naive/integer.ts +2 -4
- package/assembly/deserialize/naive/map.ts +42 -205
- package/assembly/deserialize/naive/object.ts +291 -174
- package/assembly/deserialize/naive/raw.ts +1 -5
- package/assembly/deserialize/naive/set.ts +3 -6
- package/assembly/deserialize/naive/staticarray.ts +2 -4
- package/assembly/deserialize/naive/string.ts +68 -24
- package/assembly/deserialize/naive/typedarray.ts +1 -2
- package/assembly/deserialize/naive/unsigned.ts +2 -4
- package/assembly/deserialize/simd/array/integer.ts +5 -13
- package/assembly/deserialize/simd/float.ts +5 -12
- package/assembly/deserialize/simd/integer.ts +6 -15
- package/assembly/deserialize/simd/string.ts +21 -43
- package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
- package/assembly/deserialize/swar/array/array.ts +2 -4
- package/assembly/deserialize/swar/array/bool.ts +2 -4
- package/assembly/deserialize/swar/array/box.ts +1 -2
- package/assembly/deserialize/swar/array/float.ts +8 -21
- package/assembly/deserialize/swar/array/generic.ts +2 -4
- package/assembly/deserialize/swar/array/integer.ts +13 -27
- package/assembly/deserialize/swar/array/map.ts +1 -2
- package/assembly/deserialize/swar/array/object.ts +2 -4
- package/assembly/deserialize/swar/array/raw.ts +1 -2
- package/assembly/deserialize/swar/array/shared.ts +9 -21
- package/assembly/deserialize/swar/array/string.ts +4 -10
- package/assembly/deserialize/swar/array/struct.ts +3 -9
- package/assembly/deserialize/swar/array.ts +1 -3
- package/assembly/deserialize/swar/float.ts +7 -17
- package/assembly/deserialize/swar/integer.ts +6 -15
- package/assembly/deserialize/swar/string.ts +40 -54
- package/assembly/deserialize/swar/typedarray.ts +4 -4
- package/assembly/index.d.ts +259 -21
- package/assembly/index.ts +1704 -266
- package/assembly/serialize/index/arbitrary.ts +70 -4
- package/assembly/serialize/index/jsonarray.ts +51 -0
- package/assembly/serialize/index/object.ts +39 -14
- package/assembly/serialize/index/string.ts +1 -2
- package/assembly/serialize/index/typedarray.ts +1 -2
- package/assembly/serialize/index.ts +1 -0
- package/assembly/serialize/naive/array.ts +23 -34
- package/assembly/serialize/naive/bool.ts +0 -1
- package/assembly/serialize/naive/float.ts +16 -25
- package/assembly/serialize/naive/integer.ts +1 -5
- package/assembly/serialize/naive/raw.ts +1 -2
- package/assembly/serialize/naive/set.ts +0 -4
- package/assembly/serialize/naive/staticarray.ts +0 -5
- package/assembly/serialize/naive/string.ts +11 -7
- package/assembly/serialize/naive/typedarray.ts +0 -6
- package/assembly/serialize/simd/string.ts +1 -3
- package/assembly/serialize/swar/string.ts +2 -4
- package/assembly/util/atoi-fast.ts +4 -14
- package/assembly/util/atoi.ts +1 -2
- package/assembly/util/bytes.ts +1 -2
- package/assembly/util/idofd.ts +1 -2
- package/assembly/util/isSpace.ts +1 -2
- package/assembly/util/itoa-fast.ts +9 -15
- package/assembly/util/nextPowerOf2.ts +1 -2
- package/assembly/util/parsefloat-fast.ts +4 -7
- package/assembly/util/ptrToStr.ts +1 -2
- package/assembly/util/scanValueEnd.ts +1 -2
- package/assembly/util/scanValueEndSimd.ts +198 -0
- package/assembly/util/scanValueEndSwar.ts +184 -0
- package/assembly/util/scientific.ts +8 -14
- package/assembly/util/simd-int.ts +4 -8
- package/assembly/util/snp.ts +2 -7
- package/assembly/util/stringScan.ts +2 -4
- package/assembly/util/swar-int.ts +8 -16
- package/assembly/util/swar.ts +2 -4
- package/lib/as-bs.ts +57 -42
- package/package.json +27 -10
- 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 +617 -326
- 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 +4 -2
- package/transform/lib/types.js +5 -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/assembly/util/dragonbox-cache.ts +0 -445
- package/assembly/util/dragonbox.ts +0 -660
- 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
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
serializeFloat64,
|
|
17
17
|
serializeStruct,
|
|
18
18
|
serializeObject,
|
|
19
|
+
serializeJsonArray,
|
|
19
20
|
serializeRaw,
|
|
20
21
|
serializeString,
|
|
21
22
|
serializeArrayBufferUnsafe,
|
|
@@ -34,12 +35,16 @@ import {
|
|
|
34
35
|
deserializeStaticArray,
|
|
35
36
|
deserializeArbitrary,
|
|
36
37
|
deserializeObject,
|
|
38
|
+
deserializeJsonArray,
|
|
37
39
|
deserializeRaw,
|
|
38
40
|
deserializeString,
|
|
39
41
|
deserializeArrayBuffer,
|
|
40
42
|
deserializeTypedArray,
|
|
43
|
+
setParseSrc,
|
|
44
|
+
getParseSrc,
|
|
41
45
|
} from "./deserialize";
|
|
42
46
|
import {
|
|
47
|
+
BACK_SLASH,
|
|
43
48
|
BRACE_LEFT,
|
|
44
49
|
BRACE_RIGHT,
|
|
45
50
|
BRACKET_LEFT,
|
|
@@ -52,20 +57,181 @@ import {
|
|
|
52
57
|
FALSE_WORD_U64,
|
|
53
58
|
} from "./custom/chars";
|
|
54
59
|
import { itoa_buffered } from "util/number";
|
|
55
|
-
import {
|
|
56
|
-
dragonbox_f32_buffered,
|
|
57
|
-
dragonbox_f64_buffered,
|
|
58
|
-
} from "./util/dragonbox";
|
|
60
|
+
import { dtoa_buffered, ftoa_buffered } from "xjb-as";
|
|
59
61
|
import { ptrToStr } from "./util/ptrToStr";
|
|
60
62
|
import { atoi, bytes, scanStringEnd } from "./util";
|
|
63
|
+
import { scanValueEnd_SIMD } from "./util/scanValueEndSimd";
|
|
64
|
+
import { scanValueEnd_SWAR } from "./util/scanValueEndSwar";
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
const VAL_QNAN: u64 = 0x7ffc000000000000; // boxed signature (quiet NaN)
|
|
67
|
+
const VAL_TAG_SHIFT: u8 = 45;
|
|
68
|
+
const VAL_PAYLOAD_MASK: u64 = 0x00001fffffffffff; // low 45 bits
|
|
69
|
+
const VAL_PTR_MASK: u64 = 0xffffffff; // wasm32 pointer
|
|
70
|
+
const VAL_BOX64: u64 = 0x8000000000000000; // sign bit: 64-bit int spilled to heap
|
|
71
|
+
const VAL_NULL: u64 = VAL_QNAN; // tag 0 (Null), payload 0
|
|
72
|
+
const VAL_I64_LIMIT: i64 = 17592186044416; // 2^44 - inline range is [-2^44, 2^44)
|
|
73
|
+
const VAL_U64_LIMIT: u64 = 35184372088832; // 2^45 - inline range is [0, 2^45)
|
|
74
|
+
|
|
75
|
+
// Lazy value-slot payload layout (45-bit box payload), see JSON.Value.lazyBits.
|
|
76
|
+
// Compact form (bit 44 = 0): a source-relative start offset (23 bits) + the
|
|
77
|
+
// value's length (21 bits), both in UTF-16 units. Offset-heavy on purpose:
|
|
78
|
+
// object/array fields are usually small while the document can be large, so
|
|
79
|
+
// offset overflow (a field late in a big doc) is the realistic trigger, not a
|
|
80
|
+
// single giant field - so the offset field gets the wider range. bit 44 flags
|
|
81
|
+
// the absolute (scan-on-demand) fallback for a source/value past those ranges.
|
|
82
|
+
// bits [0..22] offset (<=~16MB src) · [23..43] length (<=~4MB val) · 44 abs
|
|
83
|
+
const LZ_OFF_BITS: u64 = 23;
|
|
84
|
+
const LZ_OFF_MASK: u64 = 0x7fffff; // (1 << 23) - 1
|
|
85
|
+
const LZ_LEN_MASK: u64 = 0x1fffff; // (1 << 21) - 1
|
|
86
|
+
const LZ_ABS_FLAG: u64 = 0x100000000000; // 1 << 44
|
|
87
|
+
|
|
88
|
+
// A materialized String box only uses the low 32 payload bits for its pointer,
|
|
89
|
+
// so two spare bits [32..33] cache the value's serialize-escape class. This lets
|
|
90
|
+
// re-serializing a dynamic string skip the per-char escape scan - a clean string
|
|
91
|
+
// emits via a single memcpy. `valPtr` masks bit 32+ off, so the pointer (and GC)
|
|
92
|
+
// are unaffected.
|
|
93
|
+
// 0 = unclassified · 1 = clean (no escaping -> memcpy) · 2 = needs escaping
|
|
94
|
+
const VAL_STR_CLASS_SHIFT: u64 = 32;
|
|
95
|
+
const VAL_STR_CLASS_MASK: u64 = 0x0000000300000000; // bits [32..33]
|
|
96
|
+
const STR_CLASS_UNKNOWN: u32 = 0;
|
|
97
|
+
const STR_CLASS_CLEAN: u32 = 1;
|
|
98
|
+
const STR_CLASS_ESCAPE: u32 = 2;
|
|
99
|
+
|
|
100
|
+
function valBoxed(w: u64): bool {
|
|
101
|
+
return (w & VAL_QNAN) == VAL_QNAN;
|
|
102
|
+
}
|
|
103
|
+
function valTag(w: u64): u32 {
|
|
104
|
+
return <u32>((w >> VAL_TAG_SHIFT) & 0x1f);
|
|
105
|
+
}
|
|
106
|
+
function valPayload(w: u64): u64 {
|
|
107
|
+
return w & VAL_PAYLOAD_MASK;
|
|
108
|
+
}
|
|
109
|
+
function valPtr(w: u64): usize {
|
|
110
|
+
return <usize>(w & VAL_PTR_MASK);
|
|
111
|
+
}
|
|
112
|
+
function valBox(tag: u32, payload: u64): u64 {
|
|
113
|
+
return (
|
|
114
|
+
VAL_QNAN | ((<u64>tag) << VAL_TAG_SHIFT) | (payload & VAL_PAYLOAD_MASK)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function valLazy(w: u64): bool {
|
|
118
|
+
return valBoxed(w) && valTag(w) == JSON.Types.Lazy;
|
|
119
|
+
}
|
|
120
|
+
function valIntTag<T>(): u32 {
|
|
121
|
+
if (sizeof<T>() == 1) return isSigned<T>() ? JSON.Types.I8 : JSON.Types.U8;
|
|
122
|
+
if (sizeof<T>() == 2) return isSigned<T>() ? JSON.Types.I16 : JSON.Types.U16;
|
|
123
|
+
if (sizeof<T>() == 4) return isSigned<T>() ? JSON.Types.I32 : JSON.Types.U32;
|
|
124
|
+
return isSigned<T>() ? JSON.Types.I64 : JSON.Types.U64;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function hashUtf16(ptr: usize, len: i32): u32 {
|
|
128
|
+
let h: u32 = 2166136261;
|
|
129
|
+
for (let i = 0; i < len; i++) {
|
|
130
|
+
h ^= <u32>load<u16>(ptr + ((<usize>i) << 1));
|
|
131
|
+
h *= 16777619;
|
|
132
|
+
}
|
|
133
|
+
h ^= <u32>len;
|
|
134
|
+
h *= 16777619;
|
|
135
|
+
return h;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// v128 path: compare 8 code units (16 bytes) per step, then a u64 step (4) and
|
|
139
|
+
// a scalar tail. Each load is bounded by `len`, so it never reads past either
|
|
140
|
+
// key. Only reachable (and thus only compiled) when the SIMD feature is on, so
|
|
141
|
+
// the intrinsics don't break the naive/swar builds.
|
|
142
|
+
function utf16Equals_SIMD(ptrA: usize, ptrB: usize, len: i32): bool {
|
|
143
|
+
let i = 0;
|
|
144
|
+
for (; i + 8 <= len; i += 8) {
|
|
145
|
+
const off = (<usize>i) << 1;
|
|
146
|
+
if (v128.any_true(v128.xor(v128.load(ptrA + off), v128.load(ptrB + off))))
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
for (; i + 4 <= len; i += 4) {
|
|
150
|
+
const off = (<usize>i) << 1;
|
|
151
|
+
if (load<u64>(ptrA + off) != load<u64>(ptrB + off)) return false;
|
|
152
|
+
}
|
|
153
|
+
for (; i < len; i++) {
|
|
154
|
+
const off = (<usize>i) << 1;
|
|
155
|
+
if (load<u16>(ptrA + off) != load<u16>(ptrB + off)) return false;
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function utf16Equals(ptrA: usize, ptrB: usize, len: i32): bool {
|
|
161
|
+
if (ASC_FEATURE_SIMD) return utf16Equals_SIMD(ptrA, ptrB, len);
|
|
162
|
+
// Scalar: 4 code units (one u64) per step, scalar tail. Bounded by `len`.
|
|
163
|
+
let i = 0;
|
|
164
|
+
for (; i + 4 <= len; i += 4) {
|
|
165
|
+
const off = (<usize>i) << 1;
|
|
166
|
+
if (load<u64>(ptrA + off) != load<u64>(ptrB + off)) return false;
|
|
167
|
+
}
|
|
168
|
+
for (; i < len; i++) {
|
|
169
|
+
const off = (<usize>i) << 1;
|
|
170
|
+
if (load<u16>(ptrA + off) != load<u16>(ptrB + off)) return false;
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Shared zero-length sentinel for JSON.Obj's key buffer, so an empty object
|
|
176
|
+
// allocates no key storage until its first key is inserted. Never mutated.
|
|
177
|
+
// @ts-expect-error: Decorator valid here
|
|
178
|
+
@lazy const EMPTY_KEYS: StaticArray<u16> = new StaticArray<u16>(0);
|
|
179
|
+
|
|
180
|
+
// Shared zero-length sentinel for JSON.Obj's key-position/index buffers.
|
|
181
|
+
// Never mutated.
|
|
65
182
|
// @ts-expect-error: Decorator valid here
|
|
66
|
-
@
|
|
183
|
+
@lazy const EMPTY_I32S: StaticArray<i32> = new StaticArray<i32>(0);
|
|
184
|
+
|
|
185
|
+
// Shared zero-length sentinel for JSON.Obj's value-slot buffer, so an empty
|
|
186
|
+
// object allocates no slot storage until its first value. Never mutated.
|
|
187
|
+
// @ts-expect-error: Decorator valid here
|
|
188
|
+
@lazy const EMPTY_VALS: StaticArray<u64> = new StaticArray<u64>(0);
|
|
189
|
+
|
|
190
|
+
// A JSON.Obj with at most this many keys resolves lookups by linear scan and
|
|
191
|
+
// never allocates/hashes a key index. Most JSON objects are small and dynamic
|
|
192
|
+
// access touches only a few keys, so the O(n) index build a hash table needs is
|
|
193
|
+
// pure overhead below this size. Above it, the hash index amortizes.
|
|
194
|
+
const OBJ_LINEAR_MAX: i32 = 6;
|
|
195
|
+
|
|
196
|
+
// Deferred-value record for a lazy JSON.Value (see JSON.Types.Lazy). `lz` packs
|
|
197
|
+
// the unparsed source slice as (sliceStart << 32) | sliceEnd - the same encoding
|
|
198
|
+
// the transform uses for @lazy struct fields. `src` is the GC anchor that keeps
|
|
199
|
+
// the source string (and therefore the slice pointers, which index into its
|
|
200
|
+
// UTF-16 buffer) alive until the value is materialized. Managed so `src` is
|
|
201
|
+
// traced automatically; a JSON.Value's __visit traces the LazyRef itself.
|
|
202
|
+
// @ts-expect-error: decorators allowed here
|
|
203
|
+
@final
|
|
204
|
+
class LazyRef {
|
|
205
|
+
lz: u64 = 0;
|
|
206
|
+
src: string = "";
|
|
207
|
+
}
|
|
67
208
|
|
|
68
209
|
export namespace JSON {
|
|
210
|
+
/**
|
|
211
|
+
* On-demand field marker. `JSON.Lazy<T>` is structurally just `T` (a no-op
|
|
212
|
+
* type alias), so a field declared `JSON.Lazy<T>` is typed and accessed
|
|
213
|
+
* exactly like `T`. The transform detects the annotation and defers that
|
|
214
|
+
* field: its raw JSON slice is stored at parse time and parsed into `T` on
|
|
215
|
+
* first access (a generated get accessor).
|
|
216
|
+
*/
|
|
217
|
+
export type Lazy<T> = T;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Whether a lazy slot's value is JSON null - for `@omitnull` on lazy fields,
|
|
221
|
+
* without forcing materialization. The slot encodes the state: `u64.MAX_VALUE`
|
|
222
|
+
* = materialized (null iff the value pointer is 0), `0` = absent (null), any
|
|
223
|
+
* other value = a not-yet-parsed slice range (null iff it is literally `null`).
|
|
224
|
+
* @param valPtr pointer of the materialized value (0 when null)
|
|
225
|
+
* @param lz the packed slot
|
|
226
|
+
*/
|
|
227
|
+
export function __lazyIsNull(valPtr: usize, lz: u64): bool {
|
|
228
|
+
if (lz == u64.MAX_VALUE) return valPtr == 0;
|
|
229
|
+
if (lz == 0) return true;
|
|
230
|
+
const hi = <usize>(lz >>> 32);
|
|
231
|
+
// raw slice of length 4 (8 bytes) equal to the UTF-16 word "null"
|
|
232
|
+
return <usize>(<u32>lz) - hi == 8 && load<u64>(hi) == 0x006c006c0075006e;
|
|
233
|
+
}
|
|
234
|
+
|
|
69
235
|
/**
|
|
70
236
|
* Memory management utilities for the JSON serialization buffer.
|
|
71
237
|
*/
|
|
@@ -93,11 +259,7 @@ export namespace JSON {
|
|
|
93
259
|
* @param data T
|
|
94
260
|
* @returns string
|
|
95
261
|
*/
|
|
96
|
-
|
|
97
|
-
@inline export function stringify<T>(
|
|
98
|
-
data: T,
|
|
99
|
-
out: string | null = null,
|
|
100
|
-
): string {
|
|
262
|
+
export function stringify<T>(data: T, out: string | null = null): string {
|
|
101
263
|
if (isBoolean<T>()) {
|
|
102
264
|
if (out) {
|
|
103
265
|
if (<bool>data == true) {
|
|
@@ -137,13 +299,14 @@ export namespace JSON {
|
|
|
137
299
|
return data.toString();
|
|
138
300
|
} else if (isFloat<T>(data)) {
|
|
139
301
|
out = out
|
|
140
|
-
? changetype<string>(__renew(changetype<usize>(out),
|
|
141
|
-
: changetype<string>(__new(
|
|
302
|
+
? changetype<string>(__renew(changetype<usize>(out), 128))
|
|
303
|
+
: changetype<string>(__new(128, idof<string>()));
|
|
304
|
+
const startPtr = changetype<usize>(out);
|
|
142
305
|
const bytes =
|
|
143
306
|
(sizeof<T>() == 4
|
|
144
|
-
?
|
|
145
|
-
:
|
|
146
|
-
return changetype<string>(__renew(
|
|
307
|
+
? ftoa_buffered(startPtr, <f32>data)
|
|
308
|
+
: dtoa_buffered(startPtr, <f64>data)) << 1;
|
|
309
|
+
return changetype<string>(__renew(startPtr, bytes));
|
|
147
310
|
} else if (isNullable<T>() && changetype<usize>(data) == <usize>0) {
|
|
148
311
|
if (out) {
|
|
149
312
|
out = changetype<string>(__renew(changetype<usize>(out), 8));
|
|
@@ -153,17 +316,17 @@ export namespace JSON {
|
|
|
153
316
|
return NULL_WORD;
|
|
154
317
|
} else if (isString<nonnull<T>>()) {
|
|
155
318
|
serializeString(data as string);
|
|
156
|
-
return bs.out<string>();
|
|
319
|
+
return out ? bs.outTo<string>(changetype<usize>(out)) : bs.out<string>();
|
|
157
320
|
// @ts-expect-error: Defined by transform
|
|
158
321
|
} else if (isDefined(data.__SERIALIZE_CUSTOM)) {
|
|
159
322
|
// @ts-expect-error: Defined by transform
|
|
160
323
|
data.__SERIALIZE_CUSTOM();
|
|
161
|
-
return bs.out<string>();
|
|
324
|
+
return out ? bs.outTo<string>(changetype<usize>(out)) : bs.out<string>();
|
|
162
325
|
// @ts-expect-error: Defined by transform
|
|
163
326
|
} else if (isDefined(data.__SERIALIZE)) {
|
|
164
327
|
// @ts-expect-error: Defined by transform
|
|
165
|
-
|
|
166
|
-
return bs.out<string>();
|
|
328
|
+
data.__SERIALIZE(changetype<usize>(data));
|
|
329
|
+
return out ? bs.outTo<string>(changetype<usize>(out)) : bs.out<string>();
|
|
167
330
|
} else if (data instanceof Date) {
|
|
168
331
|
out = out
|
|
169
332
|
? changetype<string>(__renew(changetype<usize>(out), 52))
|
|
@@ -179,7 +342,7 @@ export namespace JSON {
|
|
|
179
342
|
return changetype<string>(out);
|
|
180
343
|
} else {
|
|
181
344
|
serializeReference<T>(data);
|
|
182
|
-
return bs.out<string>();
|
|
345
|
+
return out ? bs.outTo<string>(changetype<usize>(out)) : bs.out<string>();
|
|
183
346
|
}
|
|
184
347
|
}
|
|
185
348
|
|
|
@@ -188,17 +351,43 @@ export namespace JSON {
|
|
|
188
351
|
* ```js
|
|
189
352
|
* JSON.parse<T>(data)
|
|
190
353
|
* ```
|
|
354
|
+
* Pass an existing object as `out` to deserialize into it, reusing its
|
|
355
|
+
* allocations (symmetric with `stringify<T>(data, out)`). On the fast path the
|
|
356
|
+
* per-field reuse logic (nested structs reused as `dst`, strings `__renew`d in
|
|
357
|
+
* place when sizes match, arrays keeping capacity) makes a steady-state
|
|
358
|
+
* re-parse of the same shape allocate ~nothing after the first call.
|
|
191
359
|
* @param data string
|
|
360
|
+
* @param out optional existing object to reuse (structs/composites only)
|
|
192
361
|
* @returns T
|
|
193
362
|
*/
|
|
194
|
-
//
|
|
195
|
-
|
|
363
|
+
// A type-correct "zero" for any T: null pointer for references, 0/false for
|
|
364
|
+
// value types. `changetype<T>(0)` alone fails for bool/f64 (size mismatch),
|
|
365
|
+
// so branch on isReference at compile time.
|
|
366
|
+
function __zero<T>(): T {
|
|
367
|
+
// @ts-ignore: compile-time intrinsic
|
|
368
|
+
if (isReference<T>() || isManaged<T>()) return changetype<T>(0);
|
|
369
|
+
return <T>0;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function parse<T>(data: string, out: T = __zero<T>()): T {
|
|
373
|
+
// Anchor the source for any lazy JSON.Obj/JSON.Value built while parsing, so
|
|
374
|
+
// their stored slice pointers (into `data`'s buffer) stay valid and resolve
|
|
375
|
+
// against the right string. Save/restore makes nested parses (e.g. a custom
|
|
376
|
+
// deserializer calling JSON.parse, or JSON.Obj.from) re-entrant-safe.
|
|
377
|
+
const prevSrc = getParseSrc();
|
|
378
|
+
setParseSrc(data);
|
|
379
|
+
const result = parseInternal<T>(data, out);
|
|
380
|
+
setParseSrc(prevSrc);
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function parseInternal<T>(data: string, out: T = __zero<T>()): T {
|
|
196
385
|
let dataPtr = changetype<usize>(data);
|
|
197
386
|
const dataEnd = dataPtr + bytes(data);
|
|
198
387
|
// Entry point skips leading whitespace: every deserialize handler may then
|
|
199
388
|
// assume srcStart points at the first non-whitespace char. Handlers must
|
|
200
389
|
// NOT re-skip leading whitespace themselves. (Trailing whitespace is left
|
|
201
|
-
// intact
|
|
390
|
+
// intact - scalars stop at the value end, composites self-trim, and
|
|
202
391
|
// JSON.Raw intentionally preserves trailing bytes.)
|
|
203
392
|
while (dataPtr < dataEnd && JSON.Util.isSpace(load<u16>(dataPtr)))
|
|
204
393
|
dataPtr += 2;
|
|
@@ -223,24 +412,34 @@ export namespace JSON {
|
|
|
223
412
|
let type: nonnull<T> = changetype<nonnull<T>>(0);
|
|
224
413
|
// @ts-expect-error: Defined by transform
|
|
225
414
|
if (isDefined(type.__DESERIALIZE_CUSTOM)) {
|
|
226
|
-
const
|
|
415
|
+
const obj = changetype<nonnull<T>>(0);
|
|
227
416
|
// @ts-expect-error
|
|
228
|
-
return
|
|
417
|
+
return obj.__DESERIALIZE_CUSTOM(data);
|
|
229
418
|
// @ts-expect-error: Defined by transform
|
|
230
419
|
} else if (
|
|
231
420
|
isDefined(type.__DESERIALIZE_SLOW) ||
|
|
232
421
|
isDefined(type.__DESERIALIZE_FAST)
|
|
233
422
|
) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
423
|
+
// Reuse the caller-supplied `out` graph when given; otherwise allocate.
|
|
424
|
+
const reuse = changetype<usize>(out) != 0;
|
|
425
|
+
const obj = reuse
|
|
426
|
+
? changetype<nonnull<T>>(changetype<usize>(out))
|
|
427
|
+
: changetype<nonnull<T>>(
|
|
428
|
+
__new(offsetof<nonnull<T>>(), idof<nonnull<T>>()),
|
|
429
|
+
);
|
|
430
|
+
// A freshly allocated object holds uninitialized fields (__new does not
|
|
431
|
+
// zero). The fast path writes fields in place and may leave some
|
|
432
|
+
// unwritten (@optional / skip-unknown), so it must run against defaults,
|
|
433
|
+
// not garbage. A reused graph is already initialized - skip it.
|
|
434
|
+
// @ts-expect-error: Defined by transform
|
|
435
|
+
if (!reuse && isDefined(type.__INITIALIZE)) obj.__INITIALIZE();
|
|
237
436
|
// @ts-expect-error: Defined by transform
|
|
238
437
|
if (isDefined(type.__DESERIALIZE_FAST)) {
|
|
239
438
|
// @ts-expect-error: Defined by transform
|
|
240
|
-
const fastEnd =
|
|
439
|
+
const fastEnd = obj.__DESERIALIZE_FAST(
|
|
241
440
|
dataPtr,
|
|
242
441
|
dataPtr + dataSize,
|
|
243
|
-
|
|
442
|
+
obj,
|
|
244
443
|
);
|
|
245
444
|
// A non-zero return means the fast path matched; accept it when only
|
|
246
445
|
// trailing whitespace remains (pretty-printed input ends with a
|
|
@@ -249,31 +448,41 @@ export namespace JSON {
|
|
|
249
448
|
fastEnd != 0 &&
|
|
250
449
|
JSON.Util.skipWhitespace(fastEnd, dataPtr + dataSize) ==
|
|
251
450
|
dataPtr + dataSize
|
|
252
|
-
)
|
|
253
|
-
|
|
451
|
+
) {
|
|
452
|
+
// @ts-expect-error: Defined by transform for @lazy-field structs -
|
|
453
|
+
// pins the source so stored slice ranges stay valid.
|
|
454
|
+
if (isDefined(obj.__SET_SRC)) obj.__SET_SRC(data);
|
|
455
|
+
return obj;
|
|
456
|
+
}
|
|
254
457
|
}
|
|
255
|
-
if (isDefined(type.__INITIALIZE))
|
|
458
|
+
if (isDefined(type.__INITIALIZE)) obj.__INITIALIZE();
|
|
256
459
|
// @ts-expect-error: Defined by transform
|
|
257
460
|
if (isDefined(type.__DESERIALIZE_SLOW)) {
|
|
258
461
|
// @ts-expect-error: Defined by transform
|
|
259
|
-
|
|
260
|
-
|
|
462
|
+
obj.__DESERIALIZE_SLOW(dataPtr, dataPtr + dataSize, obj);
|
|
463
|
+
// @ts-expect-error: Defined by transform for @lazy-field structs.
|
|
464
|
+
if (isDefined(obj.__SET_SRC)) obj.__SET_SRC(data);
|
|
465
|
+
return obj;
|
|
261
466
|
}
|
|
262
467
|
throw new Error(`No deserialize method defined for type ${type}`);
|
|
263
468
|
}
|
|
264
469
|
if (type instanceof StaticArray) {
|
|
265
470
|
// @ts-expect-error
|
|
266
|
-
return
|
|
267
|
-
|
|
471
|
+
return deserializeStaticArray<nonnull<T>>(
|
|
472
|
+
dataPtr,
|
|
473
|
+
dataPtr + dataSize,
|
|
474
|
+
0,
|
|
268
475
|
);
|
|
269
476
|
} else if (type instanceof Array) {
|
|
477
|
+
// Reuse the caller-supplied array when given (no allocation); the
|
|
478
|
+
// element loop overwrites slots and trims length. Otherwise allocate.
|
|
270
479
|
// @ts-expect-error
|
|
271
|
-
return
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
changetype<usize>(
|
|
276
|
-
|
|
480
|
+
return deserializeArray<nonnull<T>>(
|
|
481
|
+
dataPtr,
|
|
482
|
+
dataPtr + dataSize,
|
|
483
|
+
changetype<usize>(out) != 0
|
|
484
|
+
? changetype<usize>(out)
|
|
485
|
+
: changetype<usize>(instantiate<T>()),
|
|
277
486
|
);
|
|
278
487
|
} else if (
|
|
279
488
|
type instanceof Int8Array ||
|
|
@@ -297,13 +506,14 @@ export namespace JSON {
|
|
|
297
506
|
return deserializeArrayBuffer(dataPtr, dataPtr + dataSize, 0) as T;
|
|
298
507
|
} else if (type instanceof Set) {
|
|
299
508
|
// @ts-expect-error
|
|
300
|
-
return
|
|
301
|
-
deserializeSet<nonnull<T>>(dataPtr, dataPtr + dataSize, 0),
|
|
302
|
-
);
|
|
509
|
+
return deserializeSet<nonnull<T>>(dataPtr, dataPtr + dataSize, 0);
|
|
303
510
|
} else if (type instanceof Map) {
|
|
511
|
+
// Reuse the caller-supplied map when given (keys overwrite in place).
|
|
304
512
|
// @ts-expect-error
|
|
305
|
-
return
|
|
306
|
-
|
|
513
|
+
return deserializeMap<nonnull<T>>(
|
|
514
|
+
dataPtr,
|
|
515
|
+
dataPtr + dataSize,
|
|
516
|
+
changetype<usize>(out),
|
|
307
517
|
);
|
|
308
518
|
} else if (type instanceof Date) {
|
|
309
519
|
// @ts-expect-error
|
|
@@ -312,13 +522,30 @@ export namespace JSON {
|
|
|
312
522
|
// @ts-expect-error: type
|
|
313
523
|
return deserializeRaw(dataPtr, dataPtr + dataSize);
|
|
314
524
|
} else if (type instanceof JSON.Value) {
|
|
525
|
+
// Reuse the caller-supplied JSON.Value handle when given (`out`); the
|
|
526
|
+
// deserializer writes the parsed bits into it. Otherwise allocate.
|
|
315
527
|
// @ts-expect-error
|
|
316
|
-
return
|
|
317
|
-
|
|
528
|
+
return deserializeArbitrary(
|
|
529
|
+
dataPtr,
|
|
530
|
+
dataPtr + dataSize,
|
|
531
|
+
changetype<usize>(out),
|
|
318
532
|
);
|
|
319
533
|
} else if (type instanceof JSON.Obj) {
|
|
534
|
+
// Reuse the caller-supplied JSON.Obj (cleared, buffers kept). Otherwise allocate.
|
|
320
535
|
// @ts-expect-error
|
|
321
|
-
return
|
|
536
|
+
return deserializeObject(
|
|
537
|
+
dataPtr,
|
|
538
|
+
dataPtr + dataSize,
|
|
539
|
+
changetype<usize>(out),
|
|
540
|
+
);
|
|
541
|
+
} else if (type instanceof JSON.Arr) {
|
|
542
|
+
// Reuse the caller-supplied JSON.Arr (cleared, buffers kept). Otherwise allocate.
|
|
543
|
+
// @ts-expect-error
|
|
544
|
+
return deserializeJsonArray(
|
|
545
|
+
dataPtr,
|
|
546
|
+
dataPtr + dataSize,
|
|
547
|
+
changetype<usize>(out),
|
|
548
|
+
);
|
|
322
549
|
} else if (type instanceof JSON.Box) {
|
|
323
550
|
// @ts-expect-error
|
|
324
551
|
return new JSON.Box(parseBox(data, changetype<nonnull<T>>(0).value));
|
|
@@ -345,47 +572,33 @@ export namespace JSON {
|
|
|
345
572
|
*/
|
|
346
573
|
export namespace Types {
|
|
347
574
|
/** Represents a null value */
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
@inline export const I8: u16 = 6;
|
|
362
|
-
// @ts-expect-error
|
|
363
|
-
@inline export const I16: u16 = 7;
|
|
364
|
-
// @ts-expect-error
|
|
365
|
-
@inline export const I32: u16 = 8;
|
|
366
|
-
// @ts-expect-error
|
|
367
|
-
@inline export const I64: u16 = 9;
|
|
368
|
-
// @ts-expect-error
|
|
369
|
-
@inline export const F32: u16 = 10;
|
|
370
|
-
// @ts-expect-error
|
|
371
|
-
@inline export const F64: u16 = 11;
|
|
372
|
-
// @ts-expect-error
|
|
373
|
-
@inline export const Bool: u16 = 12;
|
|
575
|
+
export const Null: u16 = 0;
|
|
576
|
+
export const Raw: u16 = 1;
|
|
577
|
+
export const U8: u16 = 2;
|
|
578
|
+
export const U16: u16 = 3;
|
|
579
|
+
export const U32: u16 = 4;
|
|
580
|
+
export const U64: u16 = 5;
|
|
581
|
+
export const I8: u16 = 6;
|
|
582
|
+
export const I16: u16 = 7;
|
|
583
|
+
export const I32: u16 = 8;
|
|
584
|
+
export const I64: u16 = 9;
|
|
585
|
+
export const F32: u16 = 10;
|
|
586
|
+
export const F64: u16 = 11;
|
|
587
|
+
export const Bool: u16 = 12;
|
|
374
588
|
// Managed
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
@inline export const ArrayBuffer: u16 = 19;
|
|
589
|
+
export const String: u16 = 13;
|
|
590
|
+
export const Object: u16 = 14;
|
|
591
|
+
export const Array: u16 = 15;
|
|
592
|
+
export const Map: u16 = 16;
|
|
593
|
+
export const Struct: u16 = 17;
|
|
594
|
+
export const TypedArray: u16 = 18;
|
|
595
|
+
export const ArrayBuffer: u16 = 19;
|
|
596
|
+
/**
|
|
597
|
+
* Internal: a not-yet-materialized value holding a raw source slice
|
|
598
|
+
* (see LazyRef). Never returned by `JSON.Value.type` - accessing the value
|
|
599
|
+
* materializes it first, so callers only ever observe the concrete type.
|
|
600
|
+
*/
|
|
601
|
+
export const Lazy: u16 = 20;
|
|
389
602
|
}
|
|
390
603
|
|
|
391
604
|
/**
|
|
@@ -434,7 +647,7 @@ export namespace JSON {
|
|
|
434
647
|
* @param data - A valid JSON string
|
|
435
648
|
* @returns A new Raw instance
|
|
436
649
|
*/
|
|
437
|
-
|
|
650
|
+
static from(data: string): JSON.Raw {
|
|
438
651
|
return new JSON.Raw(data);
|
|
439
652
|
}
|
|
440
653
|
}
|
|
@@ -458,29 +671,44 @@ export namespace JSON {
|
|
|
458
671
|
* ```
|
|
459
672
|
*/
|
|
460
673
|
// @ts-expect-error: decorators allowed here
|
|
461
|
-
@final
|
|
462
|
-
export class Value {
|
|
674
|
+
@final export class Value {
|
|
463
675
|
/** Map of struct type IDs to their serialization function indices */
|
|
464
676
|
@lazy static METHODS: Map<u32, u32> = new Map<u32, u32>();
|
|
465
677
|
|
|
466
|
-
/**
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
/** Internal storage for the value (8 bytes, can hold any primitive or pointer) */
|
|
470
|
-
private storage: u64;
|
|
678
|
+
/** NaN-boxed word holding both the type tag and the value (8 bytes). */
|
|
679
|
+
private bits: u64;
|
|
471
680
|
|
|
472
681
|
private constructor() {
|
|
473
682
|
unreachable();
|
|
474
683
|
}
|
|
475
684
|
|
|
685
|
+
/**
|
|
686
|
+
* The runtime type identifier (see JSON.Types), decoded from the boxed word.
|
|
687
|
+
* Struct values report `idof<T>() + JSON.Types.Struct`, recovered from the
|
|
688
|
+
* stored object's runtime header.
|
|
689
|
+
*/
|
|
690
|
+
get type(): u16 {
|
|
691
|
+
this.materialize();
|
|
692
|
+
const w = this.bits;
|
|
693
|
+
if (!valBoxed(w)) return JSON.Types.F64;
|
|
694
|
+
const tag = valTag(w);
|
|
695
|
+
if (tag == JSON.Types.Struct) {
|
|
696
|
+
const rtId = changetype<OBJECT>(valPtr(w) - TOTAL_OVERHEAD).rtId;
|
|
697
|
+
return <u16>rtId + JSON.Types.Struct;
|
|
698
|
+
}
|
|
699
|
+
return <u16>tag;
|
|
700
|
+
}
|
|
701
|
+
|
|
476
702
|
/**
|
|
477
703
|
* Creates an JSON.Value instance with no set value.
|
|
478
704
|
* @returns An instance of JSON.Value.
|
|
479
705
|
*/
|
|
480
|
-
|
|
481
|
-
|
|
706
|
+
static empty(): JSON.Value {
|
|
707
|
+
const out = changetype<JSON.Value>(
|
|
482
708
|
__new(offsetof<JSON.Value>(), idof<JSON.Value>()),
|
|
483
709
|
);
|
|
710
|
+
out.bits = VAL_NULL;
|
|
711
|
+
return out;
|
|
484
712
|
}
|
|
485
713
|
|
|
486
714
|
/**
|
|
@@ -488,7 +716,7 @@ export namespace JSON {
|
|
|
488
716
|
* @param value - The value to be encapsulated.
|
|
489
717
|
* @returns An instance of JSON.Value.
|
|
490
718
|
*/
|
|
491
|
-
|
|
719
|
+
static from<T>(value: T): JSON.Value {
|
|
492
720
|
if (value instanceof JSON.Value) return value;
|
|
493
721
|
const out = changetype<JSON.Value>(
|
|
494
722
|
__new(offsetof<JSON.Value>(), idof<JSON.Value>()),
|
|
@@ -497,12 +725,248 @@ export namespace JSON {
|
|
|
497
725
|
return out;
|
|
498
726
|
}
|
|
499
727
|
|
|
728
|
+
/**
|
|
729
|
+
* Creates a lazy JSON.Value wrapping the unparsed source slice
|
|
730
|
+
* `[sliceStart, sliceEnd)` (UTF-16 byte pointers into `src`). The slice is
|
|
731
|
+
* parsed into a concrete value on first access (see `materialize`); until
|
|
732
|
+
* then it serializes by passing those bytes through verbatim. `src` anchors
|
|
733
|
+
* the source string so the slice pointers stay valid. Internal - produced by
|
|
734
|
+
* the dynamic deserializers, never called by user code.
|
|
735
|
+
*/
|
|
736
|
+
static fromSlice(
|
|
737
|
+
sliceStart: usize,
|
|
738
|
+
sliceEnd: usize,
|
|
739
|
+
src: string,
|
|
740
|
+
): JSON.Value {
|
|
741
|
+
const ref = new LazyRef();
|
|
742
|
+
ref.lz = ((<u64>sliceStart) << 32) | (<u64>(<u32>sliceEnd));
|
|
743
|
+
ref.src = src;
|
|
744
|
+
const out = changetype<JSON.Value>(
|
|
745
|
+
__new(offsetof<JSON.Value>(), idof<JSON.Value>()),
|
|
746
|
+
);
|
|
747
|
+
out.bits = valBox(JSON.Types.Lazy, <u64>changetype<usize>(ref));
|
|
748
|
+
return out;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Copy `src`'s NaN-boxed bits into the already-allocated value at `dst`,
|
|
753
|
+
* applying the itcms write barrier for any managed payload (mirrors
|
|
754
|
+
* `__visit`'s tracing rule: tags >= String carry a pointer, as do
|
|
755
|
+
* heap-spilled 64-bit ints). Lets `JSON.parse<JSON.Value>(data, out)` reuse
|
|
756
|
+
* the caller's handle without materializing `src`.
|
|
757
|
+
*/
|
|
758
|
+
@unsafe static __adoptInto(dst: usize, src: JSON.Value): JSON.Value {
|
|
759
|
+
const target = changetype<JSON.Value>(dst);
|
|
760
|
+
const bits = src.bits;
|
|
761
|
+
target.bits = bits;
|
|
762
|
+
if (valBoxed(bits)) {
|
|
763
|
+
const tag = valTag(bits);
|
|
764
|
+
if (
|
|
765
|
+
tag >= JSON.Types.String ||
|
|
766
|
+
((tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
|
|
767
|
+
(bits & VAL_BOX64) != 0)
|
|
768
|
+
) {
|
|
769
|
+
__link(dst, valPtr(bits), false);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return target;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Parses a deferred (lazy) value into a concrete one in place, replacing the
|
|
777
|
+
* boxed slice with the real boxed value (cached for subsequent reads). The
|
|
778
|
+
* deferred shapes are strings, objects and arrays; a string materializes to
|
|
779
|
+
* a `string`, while a composite's own nested deferred children stay lazy -
|
|
780
|
+
* one level is peeled per access. A no-op for already-materialized values.
|
|
781
|
+
* Never allocates during GC (not called from `__visit`).
|
|
782
|
+
*/
|
|
783
|
+
private materialize(): void {
|
|
784
|
+
const w = this.bits;
|
|
785
|
+
if (!valLazy(w)) return;
|
|
786
|
+
const ref = changetype<LazyRef>(valPtr(w));
|
|
787
|
+
const lz = ref.lz;
|
|
788
|
+
this.bits = JSON.Value.parseSliceBits(
|
|
789
|
+
<usize>(lz >>> 32),
|
|
790
|
+
<usize>(<u32>lz),
|
|
791
|
+
ref.src,
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Parses the raw slice `[start, end)` (the allocating shapes only: string,
|
|
797
|
+
* object, array) into a concrete value and returns its NaN-boxed bits. A
|
|
798
|
+
* composite's own nested string/composite children stay lazy - one level is
|
|
799
|
+
* peeled. Shared by `JSON.Value.materialize` (standalone lazy values) and
|
|
800
|
+
* `JSON.Obj`'s value-slot materialization.
|
|
801
|
+
*/
|
|
802
|
+
static parseSliceBits(start: usize, end: usize, src: string): u64 {
|
|
803
|
+
const first = load<u16>(start);
|
|
804
|
+
if (first == 0x22 /* '"' */) {
|
|
805
|
+
// A string anchors no children, so no source pinning is needed.
|
|
806
|
+
return valBox(
|
|
807
|
+
JSON.Types.String,
|
|
808
|
+
<u64>changetype<usize>(deserializeString(start, end)),
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
// Pin the same source for the one level we peel so its children defer too.
|
|
812
|
+
const prev = getParseSrc();
|
|
813
|
+
setParseSrc(src);
|
|
814
|
+
let bits: u64;
|
|
815
|
+
if (first == 0x7b /* '{' */) {
|
|
816
|
+
bits = valBox(
|
|
817
|
+
JSON.Types.Object,
|
|
818
|
+
<u64>changetype<usize>(deserializeObject(start, end, 0)),
|
|
819
|
+
);
|
|
820
|
+
} else {
|
|
821
|
+
bits = valBox(
|
|
822
|
+
JSON.Types.Array,
|
|
823
|
+
<u64>changetype<usize>(deserializeJsonArray(start, end, 0)),
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
setParseSrc(prev);
|
|
827
|
+
return bits;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Internal: the packed slice `(start << 32) | end` if this value is still a
|
|
832
|
+
* deferred slice, else 0. Lets the serializer pass raw bytes through without
|
|
833
|
+
* forcing materialization. `start` is a non-zero pointer, so a real slice is
|
|
834
|
+
* never 0.
|
|
835
|
+
*/
|
|
836
|
+
__lazySlice(): u64 {
|
|
837
|
+
const w = this.bits;
|
|
838
|
+
if (!valLazy(w)) return 0;
|
|
839
|
+
return changetype<LazyRef>(valPtr(w)).lz;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* The cached serialize-escape class of a materialized String value: 0 = not
|
|
844
|
+
* yet classified, 1 = clean (no chars need escaping, so it serializes via a
|
|
845
|
+
* single memcpy), 2 = needs escaping. Stored in two spare payload bits so the
|
|
846
|
+
* scan is paid once and reused. Only meaningful for String-tagged values.
|
|
847
|
+
*/
|
|
848
|
+
__strClass(): u32 {
|
|
849
|
+
return <u32>((this.bits >> VAL_STR_CLASS_SHIFT) & 3);
|
|
850
|
+
}
|
|
851
|
+
/** Records the serialize-escape class on this String value (see __strClass). */
|
|
852
|
+
__setStrClass(c: u32): void {
|
|
853
|
+
this.bits =
|
|
854
|
+
(this.bits & ~VAL_STR_CLASS_MASK) | ((<u64>c) << VAL_STR_CLASS_SHIFT);
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Raw boxed bits. Lets the JSON.Obj/JSON.Arr serializers read back a class
|
|
858
|
+
* the serializer cached on a transient value (see `serializeArbitrary`) and
|
|
859
|
+
* persist it into their flat u64 slot, so re-serializing reuses it.
|
|
860
|
+
*/
|
|
861
|
+
__bits(): u64 {
|
|
862
|
+
return this.bits;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// --- value-slot helpers (JSON.Obj stores values as flat NaN-boxed u64 ---
|
|
866
|
+
// slots instead of heap JSON.Value objects; these build/inspect/decode the
|
|
867
|
+
// raw bits without allocating, while keeping the box layout encapsulated).
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Bits for a deferred slot. The 45-bit payload has two forms (bit 44 selects):
|
|
871
|
+
*
|
|
872
|
+
* compact (bit 44 = 0): the value's start *offset* and *length*, both in
|
|
873
|
+
* UTF-16 units relative to the source base - `(length << 22) | offset`.
|
|
874
|
+
* Gives the exact end with no scan for any value inside a source up to
|
|
875
|
+
* 2^22 units (8 MB) whose own length is also < 8 MB. This is the common
|
|
876
|
+
* case, and storing a relative offset (vs an absolute pointer) is also
|
|
877
|
+
* GC-relocation-safe.
|
|
878
|
+
*
|
|
879
|
+
* absolute (bit 44 = 1): the absolute start pointer in the low 32 bits;
|
|
880
|
+
* the end is scanned on demand. Fallback for a source or value past the
|
|
881
|
+
* 8 MB field range - rare, and correct (it scans from the value start;
|
|
882
|
+
* scanning a composite cannot safely resume mid-value).
|
|
883
|
+
*/
|
|
884
|
+
static lazyBits(srcBase: usize, start: usize, end: usize): u64 {
|
|
885
|
+
const offset = <u64>((start - srcBase) >> 1);
|
|
886
|
+
const length = <u64>((end - start) >> 1);
|
|
887
|
+
let payload: u64;
|
|
888
|
+
if (offset <= LZ_OFF_MASK && length <= LZ_LEN_MASK) {
|
|
889
|
+
payload = (length << LZ_OFF_BITS) | offset;
|
|
890
|
+
} else {
|
|
891
|
+
payload = LZ_ABS_FLAG | ((<u64>start) & VAL_PTR_MASK);
|
|
892
|
+
}
|
|
893
|
+
return valBox(JSON.Types.Lazy, payload);
|
|
894
|
+
}
|
|
895
|
+
/** The value-end pointer of a lazy slot - from the packed length, or scanned. */
|
|
896
|
+
static slotEnd(w: u64, srcBase: usize, srcEnd: usize): usize {
|
|
897
|
+
const p = valPayload(w);
|
|
898
|
+
if (p & LZ_ABS_FLAG) {
|
|
899
|
+
return JSON.Util.scanValueEnd<JSON.Value>(
|
|
900
|
+
<usize>(p & VAL_PTR_MASK),
|
|
901
|
+
srcEnd,
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
const start = srcBase + ((<usize>(p & LZ_OFF_MASK)) << 1);
|
|
905
|
+
const length = <usize>((p >> LZ_OFF_BITS) & LZ_LEN_MASK);
|
|
906
|
+
return start + (length << 1);
|
|
907
|
+
}
|
|
908
|
+
/** Bits for a JSON null. */
|
|
909
|
+
static nullBits(): u64 {
|
|
910
|
+
return VAL_NULL;
|
|
911
|
+
}
|
|
912
|
+
/** Bits for a boolean. */
|
|
913
|
+
static boolBits(b: bool): u64 {
|
|
914
|
+
return valBox(JSON.Types.Bool, b ? 1 : 0);
|
|
915
|
+
}
|
|
916
|
+
/** Bits for an f64 (raw IEEE-754, NaN canonicalized off the box signature). */
|
|
917
|
+
static f64Bits(v: f64): u64 {
|
|
918
|
+
return isNaN(v) ? 0x7ff8000000000000 : reinterpret<u64>(v);
|
|
919
|
+
}
|
|
920
|
+
/** Whether a slot is still a deferred (start-pointer) slice. */
|
|
921
|
+
static slotIsLazy(w: u64): bool {
|
|
922
|
+
return valLazy(w);
|
|
923
|
+
}
|
|
924
|
+
/** The start pointer held by a lazy slot (compact offset or absolute). */
|
|
925
|
+
static slotPtr(w: u64, srcBase: usize): usize {
|
|
926
|
+
const p = valPayload(w);
|
|
927
|
+
if (p & LZ_ABS_FLAG) return <usize>(p & VAL_PTR_MASK);
|
|
928
|
+
return srcBase + ((<usize>(p & LZ_OFF_MASK)) << 1);
|
|
929
|
+
}
|
|
930
|
+
/** Wraps raw bits in a JSON.Value (eager scalar / materialized reference). */
|
|
931
|
+
static fromBits(w: u64): JSON.Value {
|
|
932
|
+
const out = changetype<JSON.Value>(
|
|
933
|
+
__new(offsetof<JSON.Value>(), idof<JSON.Value>()),
|
|
934
|
+
);
|
|
935
|
+
out.bits = w;
|
|
936
|
+
return out;
|
|
937
|
+
}
|
|
938
|
+
/** Concrete NaN-boxed bits for a value of type T (materializing if lazy). */
|
|
939
|
+
static bitsFrom<T>(value: T): u64 {
|
|
940
|
+
const v = JSON.Value.from<T>(value);
|
|
941
|
+
v.materialize();
|
|
942
|
+
return v.bits;
|
|
943
|
+
}
|
|
944
|
+
/** Decodes NaN-boxed bits into T (the body of the instance `get<T>`). */
|
|
945
|
+
static decodeBits<T>(w: u64): T {
|
|
946
|
+
if (isFloat<T>()) {
|
|
947
|
+
if (sizeof<T>() == 4) return <T>reinterpret<f32>(<u32>valPayload(w));
|
|
948
|
+
return <T>reinterpret<f64>(w);
|
|
949
|
+
} else if (isInteger<T>()) {
|
|
950
|
+
if (sizeof<T>() == 8) {
|
|
951
|
+
if (w & VAL_BOX64) return load<T>(valPtr(w));
|
|
952
|
+
if (isSigned<T>()) return <T>((<i64>(valPayload(w) << 19)) >> 19);
|
|
953
|
+
return <T>valPayload(w);
|
|
954
|
+
}
|
|
955
|
+
return <T>valPayload(w);
|
|
956
|
+
} else if (isBoolean<T>()) {
|
|
957
|
+
return <T>valPayload(w);
|
|
958
|
+
} else if (isReference<T>()) {
|
|
959
|
+
return changetype<T>(valPtr(w));
|
|
960
|
+
}
|
|
961
|
+
return unreachable();
|
|
962
|
+
}
|
|
963
|
+
|
|
500
964
|
/**
|
|
501
965
|
* Gets the type of a given value as a JSON.Types enum.
|
|
502
966
|
* @param value - any
|
|
503
967
|
* @returns JSON.Types
|
|
504
968
|
*/
|
|
505
|
-
|
|
969
|
+
getType<T>(value: T): JSON.Types {
|
|
506
970
|
if (isNullable<T>() && changetype<usize>(value) === 0)
|
|
507
971
|
return JSON.Types.Null;
|
|
508
972
|
if (isBoolean<T>()) return JSON.Types.Bool;
|
|
@@ -549,42 +1013,45 @@ export namespace JSON {
|
|
|
549
1013
|
if (value instanceof Map) return JSON.Types.Map;
|
|
550
1014
|
if (value instanceof JSON.Raw) return JSON.Types.Raw;
|
|
551
1015
|
if (value instanceof JSON.Obj) return JSON.Types.Object;
|
|
1016
|
+
if (value instanceof JSON.Arr) return JSON.Types.Array;
|
|
552
1017
|
return JSON.Types.Null;
|
|
553
1018
|
}
|
|
554
1019
|
/**
|
|
555
1020
|
* Sets the value of the JSON.Value instance.
|
|
556
1021
|
* @param value - The value to be set.
|
|
557
1022
|
*/
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
if (
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
else if (
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
1023
|
+
set<T>(value: T): void {
|
|
1024
|
+
if (value instanceof JSON.Box) {
|
|
1025
|
+
this.set(value.value);
|
|
1026
|
+
} else if (isBoolean<T>()) {
|
|
1027
|
+
this.bits = valBox(JSON.Types.Bool, value ? 1 : 0);
|
|
1028
|
+
} else if (isInteger<T>() && nameof<T>() == "usize") {
|
|
1029
|
+
// A `usize` of 0 is the null sentinel (see deserializeArbitrary);
|
|
1030
|
+
// any other usize is an ordinary 32-bit unsigned integer.
|
|
1031
|
+
this.bits = value ? valBox(valIntTag<T>(), <u64>value) : VAL_NULL;
|
|
1032
|
+
} else if (isFloat<T>()) {
|
|
1033
|
+
if (sizeof<T>() == 4) {
|
|
1034
|
+
this.bits = valBox(JSON.Types.F32, <u64>reinterpret<u32>(<f32>value));
|
|
1035
|
+
} else {
|
|
1036
|
+
const f = <f64>value;
|
|
1037
|
+
// Canonicalize NaN so it never collides with the box signature.
|
|
1038
|
+
this.bits = isNaN(f) ? 0x7ff8000000000000 : reinterpret<u64>(f);
|
|
1039
|
+
}
|
|
1040
|
+
} else if (isInteger<T>()) {
|
|
1041
|
+
if (sizeof<T>() == 8) this.setWide<T>(value);
|
|
1042
|
+
else this.bits = valBox(valIntTag<T>(), <u64>value);
|
|
1043
|
+
} else if (isNullable<T>() && changetype<usize>(value) === 0) {
|
|
1044
|
+
this.bits = VAL_NULL;
|
|
1045
|
+
} else if (isString<T>()) {
|
|
1046
|
+
this.bits = valBox(JSON.Types.String, <u64>changetype<usize>(value));
|
|
1047
|
+
} else if (value instanceof JSON.Raw) {
|
|
1048
|
+
this.bits = valBox(JSON.Types.Raw, <u64>changetype<usize>(value));
|
|
1049
|
+
// @ts-expect-error: supplied by transform
|
|
1050
|
+
} else if (isDefined(value.__SERIALIZE) && isManaged<T>(value)) {
|
|
580
1051
|
// @ts-expect-error
|
|
581
1052
|
if (!JSON.Value.METHODS.has(idof<T>()))
|
|
582
1053
|
JSON.Value.METHODS.set(idof<T>(), value.__SERIALIZE.index);
|
|
583
|
-
|
|
584
|
-
changetype<usize>(this),
|
|
585
|
-
changetype<usize>(value),
|
|
586
|
-
STORAGE,
|
|
587
|
-
);
|
|
1054
|
+
this.bits = valBox(JSON.Types.Struct, <u64>changetype<usize>(value));
|
|
588
1055
|
} else if (
|
|
589
1056
|
value instanceof Int8Array ||
|
|
590
1057
|
value instanceof Uint8Array ||
|
|
@@ -596,20 +1063,59 @@ export namespace JSON {
|
|
|
596
1063
|
value instanceof Int64Array ||
|
|
597
1064
|
value instanceof Uint64Array ||
|
|
598
1065
|
value instanceof Float32Array ||
|
|
599
|
-
value instanceof Float64Array
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
1066
|
+
value instanceof Float64Array
|
|
1067
|
+
) {
|
|
1068
|
+
this.bits = valBox(
|
|
1069
|
+
JSON.Types.TypedArray,
|
|
1070
|
+
<u64>changetype<usize>(value),
|
|
1071
|
+
);
|
|
1072
|
+
} else if (value instanceof ArrayBuffer) {
|
|
1073
|
+
this.bits = valBox(
|
|
1074
|
+
JSON.Types.ArrayBuffer,
|
|
1075
|
+
<u64>changetype<usize>(value),
|
|
1076
|
+
);
|
|
1077
|
+
} else if (value instanceof Map) {
|
|
604
1078
|
if (idof<T>() !== idof<Map<string, JSON.Value>>()) {
|
|
605
1079
|
abort("Maps must be of type Map<string, JSON.Value>!");
|
|
606
1080
|
}
|
|
607
|
-
|
|
1081
|
+
this.bits = valBox(JSON.Types.Map, <u64>changetype<usize>(value));
|
|
608
1082
|
} else if (value instanceof JSON.Obj) {
|
|
609
|
-
|
|
1083
|
+
this.bits = valBox(JSON.Types.Object, <u64>changetype<usize>(value));
|
|
1084
|
+
} else if (value instanceof JSON.Arr) {
|
|
1085
|
+
this.bits = valBox(JSON.Types.Array, <u64>changetype<usize>(value));
|
|
610
1086
|
// @ts-expect-error
|
|
611
1087
|
} else if (isArray<T>() && idof<valueof<T>>() == idof<JSON.Value>()) {
|
|
612
|
-
|
|
1088
|
+
// A JSON.Value[] is converted to the buffer-backed JSON.Arr form, so
|
|
1089
|
+
// the Array tag always boxes a JSON.Arr.
|
|
1090
|
+
this.bits = valBox(
|
|
1091
|
+
JSON.Types.Array,
|
|
1092
|
+
<u64>changetype<usize>(JSON.Arr.from<T>(value)),
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/** Encodes a 64-bit integer, spilling to the heap when it exceeds the payload. */
|
|
1098
|
+
private setWide<T>(value: T): void {
|
|
1099
|
+
if (isSigned<T>()) {
|
|
1100
|
+
const v = <i64>value;
|
|
1101
|
+
if (v >= -VAL_I64_LIMIT && v < VAL_I64_LIMIT) {
|
|
1102
|
+
this.bits = valBox(JSON.Types.I64, <u64>v);
|
|
1103
|
+
} else {
|
|
1104
|
+
const box = new StaticArray<u64>(1);
|
|
1105
|
+
unchecked((box[0] = <u64>v));
|
|
1106
|
+
this.bits =
|
|
1107
|
+
valBox(JSON.Types.I64, <u64>changetype<usize>(box)) | VAL_BOX64;
|
|
1108
|
+
}
|
|
1109
|
+
} else {
|
|
1110
|
+
const v = <u64>value;
|
|
1111
|
+
if (v < VAL_U64_LIMIT) {
|
|
1112
|
+
this.bits = valBox(JSON.Types.U64, v);
|
|
1113
|
+
} else {
|
|
1114
|
+
const box = new StaticArray<u64>(1);
|
|
1115
|
+
unchecked((box[0] = v));
|
|
1116
|
+
this.bits =
|
|
1117
|
+
valBox(JSON.Types.U64, <u64>changetype<usize>(box)) | VAL_BOX64;
|
|
1118
|
+
}
|
|
613
1119
|
}
|
|
614
1120
|
}
|
|
615
1121
|
|
|
@@ -617,8 +1123,9 @@ export namespace JSON {
|
|
|
617
1123
|
* Gets the value of the JSON.Value instance.
|
|
618
1124
|
* @returns The encapsulated value.
|
|
619
1125
|
*/
|
|
620
|
-
|
|
621
|
-
|
|
1126
|
+
get<T>(): T {
|
|
1127
|
+
this.materialize();
|
|
1128
|
+
return JSON.Value.decodeBits<T>(this.bits);
|
|
622
1129
|
}
|
|
623
1130
|
|
|
624
1131
|
/**
|
|
@@ -626,8 +1133,8 @@ export namespace JSON {
|
|
|
626
1133
|
* Alias for .get<T>()
|
|
627
1134
|
* @returns The encapsulated value.
|
|
628
1135
|
*/
|
|
629
|
-
|
|
630
|
-
return
|
|
1136
|
+
as<T>(): T {
|
|
1137
|
+
return this.get<T>();
|
|
631
1138
|
}
|
|
632
1139
|
|
|
633
1140
|
/**
|
|
@@ -635,7 +1142,8 @@ export namespace JSON {
|
|
|
635
1142
|
* Alias for .get<T>()
|
|
636
1143
|
* @returns The encapsulated value.
|
|
637
1144
|
*/
|
|
638
|
-
|
|
1145
|
+
asBox<T>(): Box<T> | null {
|
|
1146
|
+
this.materialize();
|
|
639
1147
|
if (this.type === JSON.Types.Null) return null;
|
|
640
1148
|
return changetype<Box<T>>(JSON.Box.fromValue<T>(this));
|
|
641
1149
|
}
|
|
@@ -645,6 +1153,7 @@ export namespace JSON {
|
|
|
645
1153
|
* @returns The string representation of the JSON.Value.
|
|
646
1154
|
*/
|
|
647
1155
|
toString(): string {
|
|
1156
|
+
this.materialize();
|
|
648
1157
|
switch (this.type) {
|
|
649
1158
|
case JSON.Types.Null:
|
|
650
1159
|
return "null";
|
|
@@ -676,19 +1185,7 @@ export namespace JSON {
|
|
|
676
1185
|
return this.get<JSON.Raw>().toString();
|
|
677
1186
|
}
|
|
678
1187
|
case JSON.Types.Array: {
|
|
679
|
-
|
|
680
|
-
if (!arr.length) return "[]";
|
|
681
|
-
let out = "[";
|
|
682
|
-
const end = arr.length - 1;
|
|
683
|
-
for (let i = 0; i < end; i++) {
|
|
684
|
-
const element = unchecked(arr[i]);
|
|
685
|
-
out += element.toString() + ",";
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const element = unchecked(arr[end]);
|
|
689
|
-
out += element.toString() + "]";
|
|
690
|
-
|
|
691
|
-
return out.toString();
|
|
1188
|
+
return JSON.stringify(this.get<JSON.Arr>());
|
|
692
1189
|
}
|
|
693
1190
|
case JSON.Types.TypedArray:
|
|
694
1191
|
case JSON.Types.ArrayBuffer: {
|
|
@@ -709,8 +1206,26 @@ export namespace JSON {
|
|
|
709
1206
|
|
|
710
1207
|
|
|
711
1208
|
@unsafe private __visit(cookie: u32): void {
|
|
712
|
-
|
|
713
|
-
|
|
1209
|
+
const w = this.bits;
|
|
1210
|
+
if (!valBoxed(w)) return; // raw f64 holds no reference
|
|
1211
|
+
const tag = valTag(w);
|
|
1212
|
+
// A deferred value carries a LazyRef pointer; tracing it keeps the LazyRef
|
|
1213
|
+
// (and, transitively, its `src` anchor) alive. Must precede the
|
|
1214
|
+
// `tag >= String` branch since Lazy(20) would otherwise fall into it.
|
|
1215
|
+
// Trace-only - never materialize here (no allocation during GC).
|
|
1216
|
+
if (tag == JSON.Types.Lazy) {
|
|
1217
|
+
__visit(valPtr(w), cookie);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
// String(13)..ArrayBuffer(19) and Struct all carry a managed pointer;
|
|
1221
|
+
// Raw(1) is intentionally not traced (matches prior behavior).
|
|
1222
|
+
if (tag >= JSON.Types.String) {
|
|
1223
|
+
__visit(valPtr(w), cookie);
|
|
1224
|
+
} else if (
|
|
1225
|
+
(tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
|
|
1226
|
+
w & VAL_BOX64
|
|
1227
|
+
) {
|
|
1228
|
+
__visit(valPtr(w), cookie); // heap-spilled 64-bit int
|
|
714
1229
|
}
|
|
715
1230
|
}
|
|
716
1231
|
}
|
|
@@ -734,36 +1249,358 @@ export namespace JSON {
|
|
|
734
1249
|
* console.log(JSON.stringify(obj)); // {"key":"value","count":42}
|
|
735
1250
|
* ```
|
|
736
1251
|
*/
|
|
737
|
-
export class Obj {
|
|
738
|
-
|
|
739
|
-
|
|
1252
|
+
@final export class Obj {
|
|
1253
|
+
_kbuf: StaticArray<u16> = EMPTY_KEYS;
|
|
1254
|
+
_kused: i32 = 0;
|
|
1255
|
+
_kpos: StaticArray<i32> = EMPTY_I32S;
|
|
1256
|
+
_vals: StaticArray<u64> = EMPTY_VALS;
|
|
1257
|
+
_vused: i32 = 0;
|
|
1258
|
+
/** Source string the lazy slot pointers index into; anchors it for GC. */
|
|
1259
|
+
_src: string = "";
|
|
1260
|
+
private _index: StaticArray<i32> | null = null;
|
|
1261
|
+
private _indexMask: i32 = 0;
|
|
740
1262
|
|
|
741
1263
|
constructor() {}
|
|
742
1264
|
|
|
743
1265
|
/**
|
|
744
1266
|
* Gets the number of key-value pairs in the object.
|
|
745
1267
|
*/
|
|
746
|
-
|
|
747
|
-
return this.
|
|
1268
|
+
get size(): i32 {
|
|
1269
|
+
return this._vused;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/** Grows the key buffer to hold at least `need` code units. */
|
|
1273
|
+
private ensureKeyCap(need: i32): void {
|
|
1274
|
+
const cap = this._kbuf.length;
|
|
1275
|
+
if (cap >= need) return;
|
|
1276
|
+
let n = cap ? cap : 16;
|
|
1277
|
+
while (n < need) n <<= 1;
|
|
1278
|
+
const nb = new StaticArray<u16>(n);
|
|
1279
|
+
if (this._kused)
|
|
1280
|
+
memory.copy(
|
|
1281
|
+
changetype<usize>(nb),
|
|
1282
|
+
changetype<usize>(this._kbuf),
|
|
1283
|
+
(<usize>this._kused) << 1,
|
|
1284
|
+
);
|
|
1285
|
+
this._kbuf = nb;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/** Grows the key-position buffer to hold at least `need` entries. */
|
|
1289
|
+
private ensureKeyPosCap(need: i32): void {
|
|
1290
|
+
const cap = this._kpos.length;
|
|
1291
|
+
if (cap >= need) return;
|
|
1292
|
+
let n = cap ? cap : 8;
|
|
1293
|
+
while (n < need) n <<= 1;
|
|
1294
|
+
const nb = new StaticArray<i32>(n);
|
|
1295
|
+
if (this._vused)
|
|
1296
|
+
memory.copy(
|
|
1297
|
+
changetype<usize>(nb),
|
|
1298
|
+
changetype<usize>(this._kpos),
|
|
1299
|
+
(<usize>this._vused) << 2,
|
|
1300
|
+
);
|
|
1301
|
+
this._kpos = nb;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/** Appends a length-prefixed key (from a source memory range). */
|
|
1305
|
+
private pushKeyBytes(keyStart: usize, keyEnd: usize, slotIndex: i32): void {
|
|
1306
|
+
const len = <i32>((keyEnd - keyStart) >> 1);
|
|
1307
|
+
const pos = this._kused;
|
|
1308
|
+
this.ensureKeyCap(pos + 1 + len);
|
|
1309
|
+
this.ensureKeyPosCap(slotIndex + 1);
|
|
1310
|
+
const buf = changetype<usize>(this._kbuf);
|
|
1311
|
+
store<u16>(buf + ((<usize>pos) << 1), <u16>len);
|
|
1312
|
+
if (len)
|
|
1313
|
+
memory.copy(
|
|
1314
|
+
buf + ((<usize>(pos + 1)) << 1),
|
|
1315
|
+
keyStart,
|
|
1316
|
+
(<usize>len) << 1,
|
|
1317
|
+
);
|
|
1318
|
+
unchecked((this._kpos[slotIndex] = pos));
|
|
1319
|
+
this._kused = pos + 1 + len;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/** Materializes a key string from `len` code units starting at slot `at`. */
|
|
1323
|
+
private makeKey(at: i32, len: i32): string {
|
|
1324
|
+
const out = changetype<string>(__new((<usize>len) << 1, idof<string>()));
|
|
1325
|
+
if (len)
|
|
1326
|
+
memory.copy(
|
|
1327
|
+
changetype<usize>(out),
|
|
1328
|
+
changetype<usize>(this._kbuf) + ((<usize>at) << 1),
|
|
1329
|
+
(<usize>len) << 1,
|
|
1330
|
+
);
|
|
1331
|
+
return out;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/** Grows the value-slot buffer to hold at least `need` slots. */
|
|
1335
|
+
private ensureValCap(need: i32): void {
|
|
1336
|
+
const cap = this._vals.length;
|
|
1337
|
+
if (cap >= need) return;
|
|
1338
|
+
let n = cap ? cap : 8;
|
|
1339
|
+
while (n < need) n <<= 1;
|
|
1340
|
+
const nb = new StaticArray<u64>(n);
|
|
1341
|
+
if (this._vused)
|
|
1342
|
+
memory.copy(
|
|
1343
|
+
changetype<usize>(nb),
|
|
1344
|
+
changetype<usize>(this._vals),
|
|
1345
|
+
(<usize>this._vused) << 3,
|
|
1346
|
+
);
|
|
1347
|
+
this._vals = nb;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/** Writes a slot and, if it carries a managed pointer, links it for the GC. */
|
|
1351
|
+
private storeSlot(i: i32, bits: u64): void {
|
|
1352
|
+
unchecked((this._vals[i] = bits));
|
|
1353
|
+
if (valBoxed(bits)) {
|
|
1354
|
+
const tag = valTag(bits);
|
|
1355
|
+
// Lazy slots hold an interior `_src` pointer (anchored by the _src
|
|
1356
|
+
// field), not an owned object - never link those.
|
|
1357
|
+
if (
|
|
1358
|
+
tag != JSON.Types.Lazy &&
|
|
1359
|
+
(tag >= JSON.Types.String ||
|
|
1360
|
+
((tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
|
|
1361
|
+
bits & VAL_BOX64))
|
|
1362
|
+
) {
|
|
1363
|
+
__link(changetype<usize>(this), valPtr(bits), false);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/** Appends a value slot (raw NaN-boxed bits). */
|
|
1369
|
+
private pushValSlot(bits: u64): void {
|
|
1370
|
+
const pos = this._vused;
|
|
1371
|
+
this.ensureValCap(pos + 1);
|
|
1372
|
+
this._vused = pos + 1;
|
|
1373
|
+
this.storeSlot(pos, bits);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/** End pointer of the source buffer (upper bound for scanning a lazy slot). */
|
|
1377
|
+
private srcEnd(): usize {
|
|
1378
|
+
return changetype<usize>(this._src) + ((<usize>this._src.length) << 1);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/** Compares a lookup key against the stored key bytes for slot `i`. */
|
|
1382
|
+
private keyEquals(i: i32, key: string): bool {
|
|
1383
|
+
const pos = unchecked(this._kpos[i]);
|
|
1384
|
+
const buf = changetype<usize>(this._kbuf) + ((<usize>pos) << 1);
|
|
1385
|
+
const len = <i32>load<u16>(buf);
|
|
1386
|
+
if (len != <i32>key.length) return false;
|
|
1387
|
+
return utf16Equals(changetype<usize>(key), buf + 2, len);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/** Compares two stored key slots without materializing strings. */
|
|
1391
|
+
private slotEqualsSlot(a: i32, b: i32): bool {
|
|
1392
|
+
const posa = unchecked(this._kpos[a]);
|
|
1393
|
+
const bufA = changetype<usize>(this._kbuf) + ((<usize>posa) << 1);
|
|
1394
|
+
const lenA = <i32>load<u16>(bufA);
|
|
1395
|
+
const posb = unchecked(this._kpos[b]);
|
|
1396
|
+
const bufB = changetype<usize>(this._kbuf) + ((<usize>posb) << 1);
|
|
1397
|
+
const lenB = <i32>load<u16>(bufB);
|
|
1398
|
+
if (lenA != lenB) return false;
|
|
1399
|
+
return utf16Equals(bufA + 2, bufB + 2, lenA);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/** Hashes the stored key for slot `i`. */
|
|
1403
|
+
private keyHashAt(i: i32): u32 {
|
|
1404
|
+
const pos = unchecked(this._kpos[i]);
|
|
1405
|
+
const buf = changetype<usize>(this._kbuf) + ((<usize>pos) << 1);
|
|
1406
|
+
const len = <i32>load<u16>(buf);
|
|
1407
|
+
return hashUtf16(buf + 2, len);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/** Resolves a key to its slot index, or -1 if absent. */
|
|
1411
|
+
private indexOf(key: string): i32 {
|
|
1412
|
+
const n = this._vused;
|
|
1413
|
+
const keyLen = <i32>key.length;
|
|
1414
|
+
// Small objects: scan linearly and never build a hash index. The keys are
|
|
1415
|
+
// packed contiguously in _kbuf and the length prefix rejects mismatches
|
|
1416
|
+
// before any byte compare. Scan from the end so a duplicate key resolves
|
|
1417
|
+
// to its LAST occurrence - JSON last-value-wins, matching buildIndex()
|
|
1418
|
+
// (which overwrites the slot on a collision) for objects above the
|
|
1419
|
+
// threshold; a forward scan would return the first and disagree.
|
|
1420
|
+
if (n <= OBJ_LINEAR_MAX) {
|
|
1421
|
+
const kbuf = changetype<usize>(this._kbuf);
|
|
1422
|
+
const keyPtr = changetype<usize>(key);
|
|
1423
|
+
const kpos = this._kpos;
|
|
1424
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
1425
|
+
const buf = kbuf + ((<usize>unchecked(kpos[i])) << 1);
|
|
1426
|
+
if (
|
|
1427
|
+
<i32>load<u16>(buf) == keyLen &&
|
|
1428
|
+
utf16Equals(keyPtr, buf + 2, keyLen)
|
|
1429
|
+
)
|
|
1430
|
+
return i;
|
|
1431
|
+
}
|
|
1432
|
+
return -1;
|
|
1433
|
+
}
|
|
1434
|
+
const idx = this.buildIndex();
|
|
1435
|
+
const mask = this._indexMask;
|
|
1436
|
+
if (mask == 0) {
|
|
1437
|
+
const entry = unchecked(idx[0]);
|
|
1438
|
+
return entry != 0 && this.keyEquals(entry - 1, key) ? entry - 1 : -1;
|
|
1439
|
+
}
|
|
1440
|
+
let slot = <i32>(
|
|
1441
|
+
(hashUtf16(changetype<usize>(key), <i32>key.length) & (<u32>mask))
|
|
1442
|
+
);
|
|
1443
|
+
const start = slot;
|
|
1444
|
+
while (true) {
|
|
1445
|
+
const entry = unchecked(idx[slot]);
|
|
1446
|
+
if (entry == 0) return -1;
|
|
1447
|
+
const i = entry - 1;
|
|
1448
|
+
if (this.keyEquals(i, key)) return i;
|
|
1449
|
+
slot = (slot + 1) & mask;
|
|
1450
|
+
// A correctly-maintained table stays below full load, so an empty slot
|
|
1451
|
+
// is found first. This wrap check is a safety net against spinning
|
|
1452
|
+
// forever should that invariant ever be violated.
|
|
1453
|
+
if (slot == start) return -1;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/** Parses a lazy slot in place, caching the concrete box, and returns it. */
|
|
1458
|
+
private materializeSlot(i: i32): u64 {
|
|
1459
|
+
const slot = unchecked(this._vals[i]);
|
|
1460
|
+
if (!JSON.Value.slotIsLazy(slot)) return slot;
|
|
1461
|
+
const base = changetype<usize>(this._src);
|
|
1462
|
+
const start = JSON.Value.slotPtr(slot, base);
|
|
1463
|
+
const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
|
|
1464
|
+
const bits = JSON.Value.parseSliceBits(start, end, this._src);
|
|
1465
|
+
this.storeSlot(i, bits);
|
|
1466
|
+
return bits;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* Appends a key (from a source memory range) and a precomputed NaN-boxed
|
|
1471
|
+
* value slot without a duplicate-key check. Used by the deserializer - no
|
|
1472
|
+
* per-key string allocation, no per-value object, no hashing.
|
|
1473
|
+
*/
|
|
1474
|
+
appendRawSlot(keyStart: usize, keyEnd: usize, bits: u64): void {
|
|
1475
|
+
const slotIndex = this._vused;
|
|
1476
|
+
this.pushKeyBytes(keyStart, keyEnd, slotIndex);
|
|
1477
|
+
this.pushValSlot(bits);
|
|
1478
|
+
this.insertIndex(slotIndex);
|
|
748
1479
|
}
|
|
749
1480
|
|
|
750
1481
|
/**
|
|
751
|
-
*
|
|
1482
|
+
* Appends a key and value (any T) without a duplicate-key check. Eagerly
|
|
1483
|
+
* boxes the value into a slot.
|
|
1484
|
+
*/
|
|
1485
|
+
appendRaw<T>(keyStart: usize, keyEnd: usize, value: T): void {
|
|
1486
|
+
this.appendRawSlot(keyStart, keyEnd, JSON.Value.bitsFrom<T>(value));
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/** Inserts a single slot into an already-built index. */
|
|
1490
|
+
private insertIndex(slotIndex: i32): void {
|
|
1491
|
+
const idx = this._index;
|
|
1492
|
+
if (idx === null) return;
|
|
1493
|
+
const mask = this._indexMask;
|
|
1494
|
+
let slot = <i32>(this.keyHashAt(slotIndex) & (<u32>mask));
|
|
1495
|
+
const start = slot;
|
|
1496
|
+
while (unchecked(idx[slot]) != 0) {
|
|
1497
|
+
const entry = unchecked(idx[slot]) - 1;
|
|
1498
|
+
if (this.slotEqualsSlot(entry, slotIndex)) {
|
|
1499
|
+
unchecked((idx[slot] = slotIndex + 1));
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
slot = (slot + 1) & mask;
|
|
1503
|
+
if (slot == start) {
|
|
1504
|
+
this._index = null;
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
unchecked((idx[slot] = slotIndex + 1));
|
|
1509
|
+
// buildIndex() sizes the table at >2x the entry count (load < 0.5), but it
|
|
1510
|
+
// only runs lazily; appends since then go through here without resizing.
|
|
1511
|
+
// Once we cross half load, drop the index so the next access rebuilds it at
|
|
1512
|
+
// double capacity — this keeps an empty slot available for every probe.
|
|
1513
|
+
// Without it a small (e.g. cap-2) table fills and indexOf() spins forever.
|
|
1514
|
+
if ((slotIndex + 1) << 1 > mask + 1) this._index = null;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
/** Builds (once) and returns the lazy key -> position index. */
|
|
1518
|
+
private buildIndex(): StaticArray<i32> {
|
|
1519
|
+
let idx = this._index;
|
|
1520
|
+
if (idx === null) {
|
|
1521
|
+
const used = this._vused;
|
|
1522
|
+
let cap = 2;
|
|
1523
|
+
while (cap <= used << 1) cap <<= 1;
|
|
1524
|
+
idx = new StaticArray<i32>(cap);
|
|
1525
|
+
const mask = cap - 1;
|
|
1526
|
+
for (let i = 0; i < used; i++) {
|
|
1527
|
+
const keyPos = unchecked(this._kpos[i]);
|
|
1528
|
+
const buf = changetype<usize>(this._kbuf) + ((<usize>keyPos) << 1);
|
|
1529
|
+
const len = <i32>load<u16>(buf);
|
|
1530
|
+
let slot = <i32>(hashUtf16(buf + 2, len) & (<u32>mask));
|
|
1531
|
+
while (unchecked(idx[slot]) != 0) {
|
|
1532
|
+
const entry = unchecked(idx[slot]) - 1;
|
|
1533
|
+
if (this.slotEqualsSlot(entry, i)) {
|
|
1534
|
+
unchecked((idx[slot] = i + 1));
|
|
1535
|
+
break;
|
|
1536
|
+
}
|
|
1537
|
+
slot = (slot + 1) & mask;
|
|
1538
|
+
}
|
|
1539
|
+
if (unchecked(idx[slot]) == 0) unchecked((idx[slot] = i + 1));
|
|
1540
|
+
}
|
|
1541
|
+
this._index = idx;
|
|
1542
|
+
this._indexMask = mask;
|
|
1543
|
+
}
|
|
1544
|
+
return idx;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* Sets a key-value pair in the object, overwriting any existing value.
|
|
752
1549
|
* @param key - The string key
|
|
753
1550
|
* @param value - The value (will be wrapped in JSON.Value)
|
|
754
1551
|
*/
|
|
755
|
-
|
|
756
|
-
|
|
1552
|
+
set<T>(key: string, value: T): void {
|
|
1553
|
+
const bits = JSON.Value.bitsFrom<T>(value);
|
|
1554
|
+
const i = this.indexOf(key);
|
|
1555
|
+
if (i >= 0) {
|
|
1556
|
+
this.storeSlot(i, bits);
|
|
1557
|
+
} else {
|
|
1558
|
+
const slotIndex = this._vused;
|
|
1559
|
+
this.pushKeyBytes(
|
|
1560
|
+
changetype<usize>(key),
|
|
1561
|
+
changetype<usize>(key) + ((<usize>key.length) << 1),
|
|
1562
|
+
slotIndex,
|
|
1563
|
+
);
|
|
1564
|
+
this.pushValSlot(bits);
|
|
1565
|
+
this.insertIndex(slotIndex);
|
|
1566
|
+
}
|
|
757
1567
|
}
|
|
758
1568
|
|
|
759
1569
|
/**
|
|
760
|
-
* Gets a value by key.
|
|
1570
|
+
* Gets a value by key as a JSON.Value (dynamic access).
|
|
761
1571
|
* @param key - The key to look up
|
|
762
1572
|
* @returns The JSON.Value or null if not found
|
|
763
1573
|
*/
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1574
|
+
get(key: string): JSON.Value | null {
|
|
1575
|
+
const i = this.indexOf(key);
|
|
1576
|
+
if (i < 0) return null;
|
|
1577
|
+
const slot = unchecked(this._vals[i]);
|
|
1578
|
+
if (JSON.Value.slotIsLazy(slot)) {
|
|
1579
|
+
// Hand back a self-contained lazy value (its own slice + anchor) so it
|
|
1580
|
+
// can materialize independently of this object.
|
|
1581
|
+
const base = changetype<usize>(this._src);
|
|
1582
|
+
const start = JSON.Value.slotPtr(slot, base);
|
|
1583
|
+
const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
|
|
1584
|
+
return JSON.Value.fromSlice(start, end, this._src);
|
|
1585
|
+
}
|
|
1586
|
+
return JSON.Value.fromBits(slot);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/**
|
|
1590
|
+
* Gets a value by key directly as `T`, with no intermediate JSON.Value
|
|
1591
|
+
* allocation. A deferred slot is parsed (and cached) on first access; an
|
|
1592
|
+
* absent key returns the type's default (null / 0 / false).
|
|
1593
|
+
*
|
|
1594
|
+
* Named `getAs` rather than `get<T>` because AssemblyScript has no method
|
|
1595
|
+
* overloading - `get(key)` (dynamic) and a typed `get` can't share a name.
|
|
1596
|
+
* @param key - The key to look up
|
|
1597
|
+
*/
|
|
1598
|
+
getAs<T>(key: string): T {
|
|
1599
|
+
const i = this.indexOf(key);
|
|
1600
|
+
if (i < 0) return __zero<T>();
|
|
1601
|
+
let slot = unchecked(this._vals[i]);
|
|
1602
|
+
if (JSON.Value.slotIsLazy(slot)) slot = this.materializeSlot(i);
|
|
1603
|
+
return JSON.Value.decodeBits<T>(slot);
|
|
767
1604
|
}
|
|
768
1605
|
|
|
769
1606
|
/**
|
|
@@ -771,8 +1608,8 @@ export namespace JSON {
|
|
|
771
1608
|
* @param key - The key to check
|
|
772
1609
|
* @returns true if the key exists
|
|
773
1610
|
*/
|
|
774
|
-
|
|
775
|
-
return this.
|
|
1611
|
+
has(key: string): bool {
|
|
1612
|
+
return this.indexOf(key) >= 0;
|
|
776
1613
|
}
|
|
777
1614
|
|
|
778
1615
|
/**
|
|
@@ -780,31 +1617,93 @@ export namespace JSON {
|
|
|
780
1617
|
* @param key - The key to delete
|
|
781
1618
|
* @returns true if the key was found and deleted
|
|
782
1619
|
*/
|
|
783
|
-
|
|
784
|
-
|
|
1620
|
+
delete(key: string): bool {
|
|
1621
|
+
const removed = this.indexOf(key);
|
|
1622
|
+
if (removed < 0) return false;
|
|
1623
|
+
const keys = this.keys();
|
|
1624
|
+
const oldVals = this._vals;
|
|
1625
|
+
const n = this._vused;
|
|
1626
|
+
this._kbuf = EMPTY_KEYS;
|
|
1627
|
+
this._kused = 0;
|
|
1628
|
+
this._kpos = EMPTY_I32S;
|
|
1629
|
+
this._vals = EMPTY_VALS;
|
|
1630
|
+
this._vused = 0;
|
|
1631
|
+
for (let j = 0; j < n; j++) {
|
|
1632
|
+
if (j == removed) continue;
|
|
1633
|
+
const k = unchecked(keys[j]);
|
|
1634
|
+
this.pushKeyBytes(
|
|
1635
|
+
changetype<usize>(k),
|
|
1636
|
+
changetype<usize>(k) + ((<usize>k.length) << 1),
|
|
1637
|
+
this._vused,
|
|
1638
|
+
);
|
|
1639
|
+
this.pushValSlot(unchecked(oldVals[j]));
|
|
1640
|
+
}
|
|
1641
|
+
this._index = null;
|
|
1642
|
+
this._indexMask = 0;
|
|
1643
|
+
return true;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/**
|
|
1647
|
+
* Removes all entries. Backing key/value buffer capacity is kept so a
|
|
1648
|
+
* subsequent parse or insert reuses it without re-allocating - this is what
|
|
1649
|
+
* makes `JSON.parse<JSON.Obj>(data, out)` allocation-light. `__visit` only
|
|
1650
|
+
* traces `[0, _vused)`, so resetting the used counters drops the old slots
|
|
1651
|
+
* from GC tracing safely.
|
|
1652
|
+
*/
|
|
1653
|
+
clear(): void {
|
|
1654
|
+
this._kused = 0;
|
|
1655
|
+
this._kpos = EMPTY_I32S;
|
|
1656
|
+
this._vused = 0;
|
|
1657
|
+
this._src = "";
|
|
1658
|
+
this._index = null;
|
|
1659
|
+
this._indexMask = 0;
|
|
785
1660
|
}
|
|
786
1661
|
|
|
787
1662
|
/**
|
|
788
1663
|
* Gets all keys in the object.
|
|
789
|
-
* @returns Array of string keys
|
|
1664
|
+
* @returns Array of string keys (in insertion order)
|
|
790
1665
|
*/
|
|
791
|
-
|
|
792
|
-
|
|
1666
|
+
keys(): string[] {
|
|
1667
|
+
const out = new Array<string>(this._vused);
|
|
1668
|
+
const buf = changetype<usize>(this._kbuf);
|
|
1669
|
+
const used = this._kused;
|
|
1670
|
+
let pos = 0;
|
|
1671
|
+
let i = 0;
|
|
1672
|
+
while (pos < used) {
|
|
1673
|
+
const len = <i32>load<u16>(buf + ((<usize>pos) << 1));
|
|
1674
|
+
unchecked((out[i++] = this.makeKey(pos + 1, len)));
|
|
1675
|
+
pos += 1 + len;
|
|
1676
|
+
}
|
|
1677
|
+
return out;
|
|
793
1678
|
}
|
|
794
1679
|
|
|
795
1680
|
/**
|
|
796
1681
|
* Gets all values in the object.
|
|
797
|
-
* @returns Array of JSON.Value instances
|
|
1682
|
+
* @returns Array of JSON.Value instances (in insertion order)
|
|
798
1683
|
*/
|
|
799
|
-
|
|
800
|
-
|
|
1684
|
+
values(): JSON.Value[] {
|
|
1685
|
+
const n = this._vused;
|
|
1686
|
+
const out = new Array<JSON.Value>(n);
|
|
1687
|
+
const base = changetype<usize>(this._src);
|
|
1688
|
+
const srcEnd = this.srcEnd();
|
|
1689
|
+
for (let i = 0; i < n; i++) {
|
|
1690
|
+
const slot = unchecked(this._vals[i]);
|
|
1691
|
+
if (JSON.Value.slotIsLazy(slot)) {
|
|
1692
|
+
const start = JSON.Value.slotPtr(slot, base);
|
|
1693
|
+
const end = JSON.Value.slotEnd(slot, base, srcEnd);
|
|
1694
|
+
unchecked((out[i] = JSON.Value.fromSlice(start, end, this._src)));
|
|
1695
|
+
} else {
|
|
1696
|
+
unchecked((out[i] = JSON.Value.fromBits(slot)));
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
return out;
|
|
801
1700
|
}
|
|
802
1701
|
|
|
803
1702
|
/**
|
|
804
1703
|
* Serializes the object to a JSON string.
|
|
805
1704
|
* @returns JSON string representation
|
|
806
1705
|
*/
|
|
807
|
-
|
|
1706
|
+
toString(): string {
|
|
808
1707
|
return JSON.stringify(this);
|
|
809
1708
|
}
|
|
810
1709
|
|
|
@@ -813,7 +1712,7 @@ export namespace JSON {
|
|
|
813
1712
|
* @param value - The value to convert
|
|
814
1713
|
* @returns A new JSON.Obj instance
|
|
815
1714
|
*/
|
|
816
|
-
|
|
1715
|
+
static from<T>(value: T): JSON.Obj {
|
|
817
1716
|
if (value instanceof JSON.Obj) return value;
|
|
818
1717
|
if (value instanceof Map) {
|
|
819
1718
|
const out = new JSON.Obj();
|
|
@@ -837,6 +1736,537 @@ export namespace JSON {
|
|
|
837
1736
|
}
|
|
838
1737
|
return parsed.get<JSON.Obj>();
|
|
839
1738
|
}
|
|
1739
|
+
|
|
1740
|
+
|
|
1741
|
+
@unsafe private __visit(cookie: u32): void {
|
|
1742
|
+
__visit(changetype<usize>(this._kbuf), cookie);
|
|
1743
|
+
__visit(changetype<usize>(this._kpos), cookie);
|
|
1744
|
+
__visit(changetype<usize>(this._vals), cookie);
|
|
1745
|
+
__visit(changetype<usize>(this._src), cookie);
|
|
1746
|
+
__visit(changetype<usize>(this._index), cookie); // null-safe in rt
|
|
1747
|
+
const vals = this._vals;
|
|
1748
|
+
const n = this._vused;
|
|
1749
|
+
for (let i = 0; i < n; i++) {
|
|
1750
|
+
const w = unchecked(vals[i]);
|
|
1751
|
+
if (!valBoxed(w)) continue; // raw f64, no reference
|
|
1752
|
+
const tag = valTag(w);
|
|
1753
|
+
if (tag == JSON.Types.Lazy) continue; // interior _src pointer
|
|
1754
|
+
if (tag >= JSON.Types.String) {
|
|
1755
|
+
__visit(valPtr(w), cookie);
|
|
1756
|
+
} else if (
|
|
1757
|
+
(tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
|
|
1758
|
+
w & VAL_BOX64
|
|
1759
|
+
) {
|
|
1760
|
+
__visit(valPtr(w), cookie); // heap-spilled 64-bit int
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/**
|
|
1767
|
+
* Dynamic JSON array with JSON.Value elements, backed by the same flat
|
|
1768
|
+
* NaN-boxed u64 slot buffer as JSON.Obj (no per-element JSON.Value objects).
|
|
1769
|
+
* Deferred string/array/object elements are parsed on first access; untouched
|
|
1770
|
+
* elements serialize straight from their source bytes.
|
|
1771
|
+
*
|
|
1772
|
+
* Index with `arr.at(i)` (returns a JSON.Value) or read typed via `getAs<T>(i)`.
|
|
1773
|
+
*
|
|
1774
|
+
* @example
|
|
1775
|
+
* ```typescript
|
|
1776
|
+
* const arr = JSON.parse<JSON.Arr>('[1,"two",[3,4]]');
|
|
1777
|
+
* arr.at(0).get<f64>(); // 1
|
|
1778
|
+
* arr.getAs<string>(1); // "two"
|
|
1779
|
+
* arr.getAs<JSON.Arr>(2).at(0); // 3
|
|
1780
|
+
* ```
|
|
1781
|
+
*/
|
|
1782
|
+
@final export class Arr {
|
|
1783
|
+
// Same flat slot model as JSON.Obj, without the key buffer. See JSON.Obj for
|
|
1784
|
+
// the slot encoding, the custom `__visit`, and the `__link` write barrier.
|
|
1785
|
+
_vals: StaticArray<u64> = EMPTY_VALS;
|
|
1786
|
+
_vused: i32 = 0;
|
|
1787
|
+
/** Source string the lazy slot pointers index into; anchors it for GC. */
|
|
1788
|
+
_src: string = "";
|
|
1789
|
+
|
|
1790
|
+
constructor() {}
|
|
1791
|
+
|
|
1792
|
+
/** Number of elements. */
|
|
1793
|
+
get length(): i32 {
|
|
1794
|
+
return this._vused;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* Removes all elements, keeping the value-slot buffer capacity so a
|
|
1799
|
+
* subsequent parse or push reuses it - powers `JSON.parse<JSON.Arr>(data,
|
|
1800
|
+
* out)`. `__visit` only traces `[0, _vused)`, so this is GC-safe.
|
|
1801
|
+
*/
|
|
1802
|
+
clear(): void {
|
|
1803
|
+
this._vused = 0;
|
|
1804
|
+
this._src = "";
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
/** Grows the value-slot buffer to hold at least `need` slots. */
|
|
1808
|
+
private ensureValCap(need: i32): void {
|
|
1809
|
+
const cap = this._vals.length;
|
|
1810
|
+
if (cap >= need) return;
|
|
1811
|
+
let n = cap ? cap : 8;
|
|
1812
|
+
while (n < need) n <<= 1;
|
|
1813
|
+
const nb = new StaticArray<u64>(n);
|
|
1814
|
+
if (this._vused)
|
|
1815
|
+
memory.copy(
|
|
1816
|
+
changetype<usize>(nb),
|
|
1817
|
+
changetype<usize>(this._vals),
|
|
1818
|
+
(<usize>this._vused) << 3,
|
|
1819
|
+
);
|
|
1820
|
+
this._vals = nb;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
/** Writes a slot and, if it carries a managed pointer, links it for the GC. */
|
|
1824
|
+
private storeSlot(i: i32, bits: u64): void {
|
|
1825
|
+
unchecked((this._vals[i] = bits));
|
|
1826
|
+
if (valBoxed(bits)) {
|
|
1827
|
+
const tag = valTag(bits);
|
|
1828
|
+
if (
|
|
1829
|
+
tag != JSON.Types.Lazy &&
|
|
1830
|
+
(tag >= JSON.Types.String ||
|
|
1831
|
+
((tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
|
|
1832
|
+
bits & VAL_BOX64))
|
|
1833
|
+
) {
|
|
1834
|
+
__link(changetype<usize>(this), valPtr(bits), false);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
/** Appends a value slot (raw NaN-boxed bits). Deserializer entry point. */
|
|
1840
|
+
pushRawSlot(bits: u64): void {
|
|
1841
|
+
const pos = this._vused;
|
|
1842
|
+
this.ensureValCap(pos + 1);
|
|
1843
|
+
this._vused = pos + 1;
|
|
1844
|
+
this.storeSlot(pos, bits);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
/** End pointer of the source buffer (upper bound for scanning a lazy slot). */
|
|
1848
|
+
private srcEnd(): usize {
|
|
1849
|
+
return changetype<usize>(this._src) + ((<usize>this._src.length) << 1);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
/** Parses a lazy slot in place, caching the concrete box, and returns it. */
|
|
1853
|
+
private materializeSlot(i: i32): u64 {
|
|
1854
|
+
const slot = unchecked(this._vals[i]);
|
|
1855
|
+
if (!JSON.Value.slotIsLazy(slot)) return slot;
|
|
1856
|
+
const base = changetype<usize>(this._src);
|
|
1857
|
+
const start = JSON.Value.slotPtr(slot, base);
|
|
1858
|
+
const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
|
|
1859
|
+
const bits = JSON.Value.parseSliceBits(start, end, this._src);
|
|
1860
|
+
this.storeSlot(i, bits);
|
|
1861
|
+
return bits;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
/** Element access as a JSON.Value: `arr.at(i)`. */
|
|
1865
|
+
at(index: i32): JSON.Value {
|
|
1866
|
+
if (<u32>index >= <u32>this._vused) throw new Error("Index out of range");
|
|
1867
|
+
const slot = unchecked(this._vals[index]);
|
|
1868
|
+
if (JSON.Value.slotIsLazy(slot)) {
|
|
1869
|
+
const base = changetype<usize>(this._src);
|
|
1870
|
+
const start = JSON.Value.slotPtr(slot, base);
|
|
1871
|
+
const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
|
|
1872
|
+
return JSON.Value.fromSlice(start, end, this._src);
|
|
1873
|
+
}
|
|
1874
|
+
return JSON.Value.fromBits(slot);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/**
|
|
1878
|
+
* Reads element `i` directly as `T`, with no intermediate JSON.Value
|
|
1879
|
+
* allocation. A deferred slot is parsed (and cached) on first access.
|
|
1880
|
+
* @param i - element index
|
|
1881
|
+
*/
|
|
1882
|
+
getAs<T>(i: i32): T {
|
|
1883
|
+
let slot = unchecked(this._vals[i]);
|
|
1884
|
+
if (JSON.Value.slotIsLazy(slot)) slot = this.materializeSlot(i);
|
|
1885
|
+
return JSON.Value.decodeBits<T>(slot);
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
/** Appends a value (any T), eagerly boxed into a slot. */
|
|
1889
|
+
push<T>(value: T): void {
|
|
1890
|
+
this.pushRawSlot(JSON.Value.bitsFrom<T>(value));
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
/** Overwrites element `i`. */
|
|
1894
|
+
set<T>(i: i32, value: T): void {
|
|
1895
|
+
this.storeSlot(i, JSON.Value.bitsFrom<T>(value));
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
/**
|
|
1899
|
+
* Bounds-checked element access via `arr[i]` - returns a `JSON.Value`
|
|
1900
|
+
* (allocating), mirroring `at(i)`. For an allocation-free typed read use
|
|
1901
|
+
* `getAs<T>(i)`.
|
|
1902
|
+
*/
|
|
1903
|
+
@operator("[]") private __get(index: i32): JSON.Value {
|
|
1904
|
+
return this.at(index);
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
/** Element assignment via `arr[i] = value` (any `JSON.Value`). */
|
|
1908
|
+
@operator("[]=") private __set(index: i32, value: JSON.Value): void {
|
|
1909
|
+
this.set<JSON.Value>(index, value);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
/** Serializes the array to a JSON string. */
|
|
1913
|
+
toString(): string {
|
|
1914
|
+
return JSON.stringify(this);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
/** Creates a JSON.Arr from a JSON.Value[] (or returns an existing one). */
|
|
1918
|
+
static from<T>(value: T): JSON.Arr {
|
|
1919
|
+
if (value instanceof JSON.Arr) return value;
|
|
1920
|
+
// @ts-expect-error: handled by the isArray guard below
|
|
1921
|
+
if (isArray<T>() && idof<valueof<T>>() == idof<JSON.Value>()) {
|
|
1922
|
+
const out = new JSON.Arr();
|
|
1923
|
+
// @ts-expect-error: T is JSON.Value[] here
|
|
1924
|
+
const arr = changetype<JSON.Value[]>(value);
|
|
1925
|
+
for (let i = 0; i < arr.length; i++) {
|
|
1926
|
+
out.pushRawSlot(JSON.Value.bitsFrom<JSON.Value>(unchecked(arr[i])));
|
|
1927
|
+
}
|
|
1928
|
+
return out;
|
|
1929
|
+
}
|
|
1930
|
+
throw new Error("JSON.Arr.from expects a JSON.Value[]");
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// ---- Array-like API: slot-optimized ports of the AssemblyScript stdlib ----
|
|
1934
|
+
// Slot-shuffling ops (reverse/fill/copyWithin/pop/shift/unshift/slice/splice)
|
|
1935
|
+
// move the raw u64 slots directly - no per-element JSON.Value - and keep any
|
|
1936
|
+
// managed pointers linked to the same owner. New single-source results share
|
|
1937
|
+
// `_src`, so copied deferred ranges stay lazy. Callbacks get a JSON.Value view
|
|
1938
|
+
// of each element via `at(i)`.
|
|
1939
|
+
|
|
1940
|
+
/** Concrete, source-independent bits for slot `i` (resolves a deferred slot
|
|
1941
|
+
* without caching it back into this array - used by cross-source ops). */
|
|
1942
|
+
private resolvedBits(i: i32): u64 {
|
|
1943
|
+
const slot = unchecked(this._vals[i]);
|
|
1944
|
+
if (!JSON.Value.slotIsLazy(slot)) return slot;
|
|
1945
|
+
const base = changetype<usize>(this._src);
|
|
1946
|
+
const start = JSON.Value.slotPtr(slot, base);
|
|
1947
|
+
const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
|
|
1948
|
+
return JSON.Value.parseSliceBits(start, end, this._src);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
/** Appends `count` raw slots from `this[from..]` into a fresh `dst`, sharing
|
|
1952
|
+
* `_src` so copied deferred ranges still resolve. */
|
|
1953
|
+
private copyInto(dst: JSON.Arr, from: i32, count: i32): void {
|
|
1954
|
+
if (count <= 0) return;
|
|
1955
|
+
dst._src = this._src;
|
|
1956
|
+
const at = dst._vused;
|
|
1957
|
+
dst.ensureValCap(at + count);
|
|
1958
|
+
for (let k = 0; k < count; k++)
|
|
1959
|
+
dst.storeSlot(at + k, unchecked(this._vals[from + k]));
|
|
1960
|
+
dst._vused = at + count;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
/** Truncates (drops the tail) or extends (pads with `null`) the array. */
|
|
1964
|
+
set length(newLength: i32) {
|
|
1965
|
+
if (newLength < 0) throw new Error("Invalid array length");
|
|
1966
|
+
const used = this._vused;
|
|
1967
|
+
if (newLength <= used) {
|
|
1968
|
+
this._vused = newLength;
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
this.ensureValCap(newLength);
|
|
1972
|
+
for (let i = used; i < newLength; i++)
|
|
1973
|
+
unchecked((this._vals[i] = JSON.Value.nullBits()));
|
|
1974
|
+
this._vused = newLength;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
/** Removes and returns the last element. */
|
|
1978
|
+
pop(): JSON.Value {
|
|
1979
|
+
const n = this._vused;
|
|
1980
|
+
if (n == 0) throw new Error("pop from empty JSON.Arr");
|
|
1981
|
+
const v = this.at(n - 1);
|
|
1982
|
+
this._vused = n - 1;
|
|
1983
|
+
return v;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
/** Removes and returns the first element, shifting the rest down. */
|
|
1987
|
+
shift(): JSON.Value {
|
|
1988
|
+
const n = this._vused;
|
|
1989
|
+
if (n == 0) throw new Error("shift from empty JSON.Arr");
|
|
1990
|
+
const v = this.at(0);
|
|
1991
|
+
const base = changetype<usize>(this._vals);
|
|
1992
|
+
memory.copy(base, base + 8, (<usize>(n - 1)) << 3);
|
|
1993
|
+
this._vused = n - 1;
|
|
1994
|
+
return v;
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
/** Prepends `value`, shifting existing elements up. Returns the new length. */
|
|
1998
|
+
unshift<T>(value: T): i32 {
|
|
1999
|
+
const n = this._vused;
|
|
2000
|
+
this.ensureValCap(n + 1);
|
|
2001
|
+
const base = changetype<usize>(this._vals);
|
|
2002
|
+
if (n) memory.copy(base + 8, base, (<usize>n) << 3);
|
|
2003
|
+
this._vused = n + 1;
|
|
2004
|
+
this.storeSlot(0, JSON.Value.bitsFrom<T>(value));
|
|
2005
|
+
return n + 1;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
/** Reverses the elements in place (slot swap). */
|
|
2009
|
+
reverse(): JSON.Arr {
|
|
2010
|
+
const vals = this._vals;
|
|
2011
|
+
let lo = 0;
|
|
2012
|
+
let hi = this._vused - 1;
|
|
2013
|
+
while (lo < hi) {
|
|
2014
|
+
const t = unchecked(vals[lo]);
|
|
2015
|
+
unchecked((vals[lo] = unchecked(vals[hi])));
|
|
2016
|
+
unchecked((vals[hi] = t));
|
|
2017
|
+
lo++;
|
|
2018
|
+
hi--;
|
|
2019
|
+
}
|
|
2020
|
+
return this;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/** Fills `[start, end)` with `value`. */
|
|
2024
|
+
fill<T>(value: T, start: i32 = 0, end: i32 = i32.MAX_VALUE): JSON.Arr {
|
|
2025
|
+
const n = this._vused;
|
|
2026
|
+
let s = start < 0 ? max(n + start, 0) : min(start, n);
|
|
2027
|
+
const e = end < 0 ? max(n + end, 0) : min(end, n);
|
|
2028
|
+
const bits = JSON.Value.bitsFrom<T>(value);
|
|
2029
|
+
for (; s < e; s++) this.storeSlot(s, bits);
|
|
2030
|
+
return this;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
/** Copies the slot block `[start, end)` to `target`, in place. */
|
|
2034
|
+
copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): JSON.Arr {
|
|
2035
|
+
const n = this._vused;
|
|
2036
|
+
const t = target < 0 ? max(n + target, 0) : min(target, n);
|
|
2037
|
+
const s = start < 0 ? max(n + start, 0) : min(start, n);
|
|
2038
|
+
const e = end < 0 ? max(n + end, 0) : min(end, n);
|
|
2039
|
+
const count = min(e - s, n - t);
|
|
2040
|
+
if (count > 0) {
|
|
2041
|
+
const base = changetype<usize>(this._vals);
|
|
2042
|
+
memory.copy(
|
|
2043
|
+
base + ((<usize>t) << 3),
|
|
2044
|
+
base + ((<usize>s) << 3),
|
|
2045
|
+
(<usize>count) << 3,
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2048
|
+
return this;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
/** Returns a new JSON.Arr with the elements in `[start, end)` (lazy-preserving). */
|
|
2052
|
+
slice(start: i32 = 0, end: i32 = i32.MAX_VALUE): JSON.Arr {
|
|
2053
|
+
const n = this._vused;
|
|
2054
|
+
const s = start < 0 ? max(n + start, 0) : min(start, n);
|
|
2055
|
+
const e = end < 0 ? max(n + end, 0) : min(end, n);
|
|
2056
|
+
const out = new JSON.Arr();
|
|
2057
|
+
this.copyInto(out, s, e - s);
|
|
2058
|
+
return out;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
/** Removes `deleteCount` elements at `start`; returns them as a new JSON.Arr. */
|
|
2062
|
+
splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): JSON.Arr {
|
|
2063
|
+
const n = this._vused;
|
|
2064
|
+
const s = start < 0 ? max(n + start, 0) : min(start, n);
|
|
2065
|
+
const d = max(min(deleteCount, n - s), 0);
|
|
2066
|
+
const removed = new JSON.Arr();
|
|
2067
|
+
this.copyInto(removed, s, d);
|
|
2068
|
+
const tail = n - (s + d);
|
|
2069
|
+
if (tail > 0) {
|
|
2070
|
+
const base = changetype<usize>(this._vals);
|
|
2071
|
+
memory.copy(
|
|
2072
|
+
base + ((<usize>s) << 3),
|
|
2073
|
+
base + ((<usize>(s + d)) << 3),
|
|
2074
|
+
(<usize>tail) << 3,
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
2077
|
+
this._vused = n - d;
|
|
2078
|
+
return removed;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
/** Returns a new JSON.Arr = this followed by `other` (resolves deferred slots
|
|
2082
|
+
* since the two sources can't share one `_src`). */
|
|
2083
|
+
concat(other: JSON.Arr): JSON.Arr {
|
|
2084
|
+
const out = new JSON.Arr();
|
|
2085
|
+
const a = this._vused;
|
|
2086
|
+
const b = other._vused;
|
|
2087
|
+
out.ensureValCap(a + b);
|
|
2088
|
+
for (let i = 0; i < a; i++) out.pushRawSlot(this.resolvedBits(i));
|
|
2089
|
+
for (let i = 0; i < b; i++) out.pushRawSlot(other.resolvedBits(i));
|
|
2090
|
+
return out;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
/** First index of `value` (typed compare via getAs<T>), or -1. */
|
|
2094
|
+
indexOf<T>(value: T, fromIndex: i32 = 0): i32 {
|
|
2095
|
+
const n = this._vused;
|
|
2096
|
+
let i = fromIndex < 0 ? max(n + fromIndex, 0) : fromIndex;
|
|
2097
|
+
for (; i < n; i++) if (this.getAs<T>(i) == value) return i;
|
|
2098
|
+
return -1;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
/** Last index of `value`, searching backwards, or -1. */
|
|
2102
|
+
lastIndexOf<T>(value: T, fromIndex: i32 = i32.MAX_VALUE): i32 {
|
|
2103
|
+
const n = this._vused;
|
|
2104
|
+
let i = fromIndex < 0 ? n + fromIndex : min(fromIndex, n - 1);
|
|
2105
|
+
for (; i >= 0; i--) if (this.getAs<T>(i) == value) return i;
|
|
2106
|
+
return -1;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
/** Whether `value` is present. */
|
|
2110
|
+
includes<T>(value: T, fromIndex: i32 = 0): bool {
|
|
2111
|
+
return this.indexOf<T>(value, fromIndex) >= 0;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
/** Calls `fn` for each element. */
|
|
2115
|
+
forEach(
|
|
2116
|
+
fn: (value: JSON.Value, index: i32, array: JSON.Arr) => void,
|
|
2117
|
+
): void {
|
|
2118
|
+
for (let i = 0, n = this._vused; i < n; i++) fn(this.at(i), i, this);
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
/** New JSON.Arr of `fn`'s results. */
|
|
2122
|
+
map(
|
|
2123
|
+
fn: (value: JSON.Value, index: i32, array: JSON.Arr) => JSON.Value,
|
|
2124
|
+
): JSON.Arr {
|
|
2125
|
+
const n = this._vused;
|
|
2126
|
+
const out = new JSON.Arr();
|
|
2127
|
+
out.ensureValCap(n);
|
|
2128
|
+
for (let i = 0; i < n; i++) out.push<JSON.Value>(fn(this.at(i), i, this));
|
|
2129
|
+
return out;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
/** New JSON.Arr of elements passing `fn` (lazy-preserving). */
|
|
2133
|
+
filter(
|
|
2134
|
+
fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool,
|
|
2135
|
+
): JSON.Arr {
|
|
2136
|
+
const out = new JSON.Arr();
|
|
2137
|
+
for (let i = 0, n = this._vused; i < n; i++)
|
|
2138
|
+
if (fn(this.at(i), i, this)) this.copyInto(out, i, 1);
|
|
2139
|
+
return out;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
/** First element passing `fn`, or `null`. */
|
|
2143
|
+
find(
|
|
2144
|
+
fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool,
|
|
2145
|
+
): JSON.Value | null {
|
|
2146
|
+
for (let i = 0, n = this._vused; i < n; i++) {
|
|
2147
|
+
const v = this.at(i);
|
|
2148
|
+
if (fn(v, i, this)) return v;
|
|
2149
|
+
}
|
|
2150
|
+
return null;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
/** Index of the first element passing `fn`, or -1. */
|
|
2154
|
+
findIndex(
|
|
2155
|
+
fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool,
|
|
2156
|
+
): i32 {
|
|
2157
|
+
for (let i = 0, n = this._vused; i < n; i++)
|
|
2158
|
+
if (fn(this.at(i), i, this)) return i;
|
|
2159
|
+
return -1;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
/** Last element passing `fn`, or `null`. */
|
|
2163
|
+
findLast(
|
|
2164
|
+
fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool,
|
|
2165
|
+
): JSON.Value | null {
|
|
2166
|
+
for (let i = this._vused - 1; i >= 0; i--) {
|
|
2167
|
+
const v = this.at(i);
|
|
2168
|
+
if (fn(v, i, this)) return v;
|
|
2169
|
+
}
|
|
2170
|
+
return null;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
/** Index of the last element passing `fn`, or -1. */
|
|
2174
|
+
findLastIndex(
|
|
2175
|
+
fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool,
|
|
2176
|
+
): i32 {
|
|
2177
|
+
for (let i = this._vused - 1; i >= 0; i--)
|
|
2178
|
+
if (fn(this.at(i), i, this)) return i;
|
|
2179
|
+
return -1;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/** Whether every element passes `fn`. */
|
|
2183
|
+
every(fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool): bool {
|
|
2184
|
+
for (let i = 0, n = this._vused; i < n; i++)
|
|
2185
|
+
if (!fn(this.at(i), i, this)) return false;
|
|
2186
|
+
return true;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
/** Whether any element passes `fn`. */
|
|
2190
|
+
some(fn: (value: JSON.Value, index: i32, array: JSON.Arr) => bool): bool {
|
|
2191
|
+
for (let i = 0, n = this._vused; i < n; i++)
|
|
2192
|
+
if (fn(this.at(i), i, this)) return true;
|
|
2193
|
+
return false;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
/** Left fold. */
|
|
2197
|
+
reduce<U>(
|
|
2198
|
+
fn: (acc: U, value: JSON.Value, index: i32, array: JSON.Arr) => U,
|
|
2199
|
+
initialValue: U,
|
|
2200
|
+
): U {
|
|
2201
|
+
let acc = initialValue;
|
|
2202
|
+
for (let i = 0, n = this._vused; i < n; i++)
|
|
2203
|
+
acc = fn(acc, this.at(i), i, this);
|
|
2204
|
+
return acc;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
/** Right fold. */
|
|
2208
|
+
reduceRight<U>(
|
|
2209
|
+
fn: (acc: U, value: JSON.Value, index: i32, array: JSON.Arr) => U,
|
|
2210
|
+
initialValue: U,
|
|
2211
|
+
): U {
|
|
2212
|
+
let acc = initialValue;
|
|
2213
|
+
for (let i = this._vused - 1; i >= 0; i--)
|
|
2214
|
+
acc = fn(acc, this.at(i), i, this);
|
|
2215
|
+
return acc;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
/** Sorts in place by `comparator(a, b)` (materializes for comparison). */
|
|
2219
|
+
sort(comparator: (a: JSON.Value, b: JSON.Value) => i32): JSON.Arr {
|
|
2220
|
+
const n = this._vused;
|
|
2221
|
+
if (n < 2) return this;
|
|
2222
|
+
const view = new Array<JSON.Value>(n);
|
|
2223
|
+
for (let i = 0; i < n; i++) unchecked((view[i] = this.at(i)));
|
|
2224
|
+
view.sort(comparator);
|
|
2225
|
+
for (let i = 0; i < n; i++)
|
|
2226
|
+
this.storeSlot(i, JSON.Value.bitsFrom<JSON.Value>(unchecked(view[i])));
|
|
2227
|
+
return this;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
/** JS-parity element string: strings unquoted, null -> "", everything else
|
|
2231
|
+
* via ES-exact JSON (numbers drop a trailing `.0`). */
|
|
2232
|
+
private elemStr(i: i32): string {
|
|
2233
|
+
const v = this.at(i);
|
|
2234
|
+
const t = v.type;
|
|
2235
|
+
if (t == JSON.Types.String) return v.get<string>();
|
|
2236
|
+
if (t == JSON.Types.Null) return "";
|
|
2237
|
+
return JSON.stringify(v);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
/** Joins the elements with `separator` (JS `Array#join` semantics). */
|
|
2241
|
+
join(separator: string = ","): string {
|
|
2242
|
+
const n = this._vused;
|
|
2243
|
+
if (n == 0) return "";
|
|
2244
|
+
let out = this.elemStr(0);
|
|
2245
|
+
for (let i = 1; i < n; i++) out += separator + this.elemStr(i);
|
|
2246
|
+
return out;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
// See JSON.Obj.__visit - same custom GC visitor for the slot buffer.
|
|
2250
|
+
@unsafe private __visit(cookie: u32): void {
|
|
2251
|
+
__visit(changetype<usize>(this._vals), cookie);
|
|
2252
|
+
__visit(changetype<usize>(this._src), cookie);
|
|
2253
|
+
const vals = this._vals;
|
|
2254
|
+
const n = this._vused;
|
|
2255
|
+
for (let i = 0; i < n; i++) {
|
|
2256
|
+
const w = unchecked(vals[i]);
|
|
2257
|
+
if (!valBoxed(w)) continue;
|
|
2258
|
+
const tag = valTag(w);
|
|
2259
|
+
if (tag == JSON.Types.Lazy) continue;
|
|
2260
|
+
if (tag >= JSON.Types.String) {
|
|
2261
|
+
__visit(valPtr(w), cookie);
|
|
2262
|
+
} else if (
|
|
2263
|
+
(tag == JSON.Types.U64 || tag == JSON.Types.I64) &&
|
|
2264
|
+
w & VAL_BOX64
|
|
2265
|
+
) {
|
|
2266
|
+
__visit(valPtr(w), cookie);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
840
2270
|
}
|
|
841
2271
|
/**
|
|
842
2272
|
* Box for primitive types
|
|
@@ -851,7 +2281,7 @@ export namespace JSON {
|
|
|
851
2281
|
* @param value T
|
|
852
2282
|
* @returns this
|
|
853
2283
|
*/
|
|
854
|
-
|
|
2284
|
+
set(value: T): Box<T> {
|
|
855
2285
|
this.value = value;
|
|
856
2286
|
return this;
|
|
857
2287
|
}
|
|
@@ -866,7 +2296,7 @@ export namespace JSON {
|
|
|
866
2296
|
* @param from T
|
|
867
2297
|
* @returns Box<T> | null
|
|
868
2298
|
*/
|
|
869
|
-
|
|
2299
|
+
static fromValue<T>(value: JSON.Value): Box<T> | null {
|
|
870
2300
|
if (!(value instanceof JSON.Value))
|
|
871
2301
|
throw new Error("value must be of type JSON.Value");
|
|
872
2302
|
if (value.type === JSON.Types.Null) return null;
|
|
@@ -885,7 +2315,7 @@ export namespace JSON {
|
|
|
885
2315
|
* @param from T
|
|
886
2316
|
* @returns Box<T>
|
|
887
2317
|
*/
|
|
888
|
-
|
|
2318
|
+
static from<T>(value: T): Box<T> {
|
|
889
2319
|
return new Box(value);
|
|
890
2320
|
}
|
|
891
2321
|
toString(): string {
|
|
@@ -933,7 +2363,7 @@ export namespace JSON {
|
|
|
933
2363
|
serializeStruct(changetype<nonnull<T>>(data));
|
|
934
2364
|
} else if (data instanceof Date) {
|
|
935
2365
|
// @ts-expect-error
|
|
936
|
-
|
|
2366
|
+
serializeDate(changetype<nonnull<T>>(data));
|
|
937
2367
|
} else {
|
|
938
2368
|
serializeReference<T>(data);
|
|
939
2369
|
}
|
|
@@ -950,7 +2380,7 @@ export namespace JSON {
|
|
|
950
2380
|
function __deserialize<T>(srcStart: usize, srcEnd: usize, dst: usize = 0): T {
|
|
951
2381
|
// Skip leading whitespace once here so every handler below may assume
|
|
952
2382
|
// srcStart is at the first non-whitespace char. (Trailing whitespace is
|
|
953
|
-
// left intact
|
|
2383
|
+
// left intact - composites self-trim and JSON.Raw preserves it.)
|
|
954
2384
|
while (srcStart < srcEnd && JSON.Util.isSpace(load<u16>(srcStart)))
|
|
955
2385
|
srcStart += 2;
|
|
956
2386
|
if (isBoolean<T>()) {
|
|
@@ -962,6 +2392,17 @@ export namespace JSON {
|
|
|
962
2392
|
: deserializeUnsigned<T>(srcStart, srcEnd);
|
|
963
2393
|
} else if (isFloat<T>()) {
|
|
964
2394
|
return deserializeFloat<T>(srcStart, srcEnd);
|
|
2395
|
+
} else if (
|
|
2396
|
+
isNullable<T>() &&
|
|
2397
|
+
srcEnd - srcStart == 8 &&
|
|
2398
|
+
load<u64>(srcStart) == NULL_WORD_U64
|
|
2399
|
+
) {
|
|
2400
|
+
// A `null` literal must be matched before the string branch: a nullable
|
|
2401
|
+
// string (`string | null`) reports `isString<T>() == true`, so without
|
|
2402
|
+
// this `null` would be (mis)handled as a quoted string and abort. Mirrors
|
|
2403
|
+
// the same-ordered check in `parseInternal`. Reached by lazy-field
|
|
2404
|
+
// materialization, which routes every slot value through `__deserialize`.
|
|
2405
|
+
return null;
|
|
965
2406
|
} else if (isString<T>()) {
|
|
966
2407
|
if (srcEnd - srcStart < 4)
|
|
967
2408
|
throw new Error(
|
|
@@ -969,12 +2410,6 @@ export namespace JSON {
|
|
|
969
2410
|
);
|
|
970
2411
|
|
|
971
2412
|
return deserializeString(srcStart, srcEnd) as T;
|
|
972
|
-
} else if (
|
|
973
|
-
isNullable<T>() &&
|
|
974
|
-
srcEnd - srcStart == 8 &&
|
|
975
|
-
load<u64>(srcStart) == NULL_WORD_U64
|
|
976
|
-
) {
|
|
977
|
-
return null;
|
|
978
2413
|
} else {
|
|
979
2414
|
let type: nonnull<T> = changetype<nonnull<T>>(0);
|
|
980
2415
|
// @ts-expect-error: Defined by transform
|
|
@@ -1013,10 +2448,10 @@ export namespace JSON {
|
|
|
1013
2448
|
}
|
|
1014
2449
|
if (type instanceof StaticArray) {
|
|
1015
2450
|
// @ts-expect-error: type
|
|
1016
|
-
return deserializeStaticArray<T
|
|
2451
|
+
return deserializeStaticArray<nonnull<T>>(srcStart, srcEnd, dst) as T;
|
|
1017
2452
|
} else if (type instanceof Array) {
|
|
1018
2453
|
// @ts-expect-error: type
|
|
1019
|
-
return deserializeArray<T
|
|
2454
|
+
return deserializeArray<nonnull<T>>(srcStart, srcEnd, dst) as T;
|
|
1020
2455
|
} else if (
|
|
1021
2456
|
type instanceof Int8Array ||
|
|
1022
2457
|
type instanceof Uint8Array ||
|
|
@@ -1051,6 +2486,9 @@ export namespace JSON {
|
|
|
1051
2486
|
} else if (type instanceof JSON.Obj) {
|
|
1052
2487
|
// @ts-expect-error: type
|
|
1053
2488
|
return deserializeObject(srcStart, srcEnd, 0);
|
|
2489
|
+
} else if (type instanceof JSON.Arr) {
|
|
2490
|
+
// @ts-expect-error: type
|
|
2491
|
+
return deserializeJsonArray(srcStart, srcEnd, 0);
|
|
1054
2492
|
} else if (type instanceof JSON.Box) {
|
|
1055
2493
|
// @ts-expect-error: type
|
|
1056
2494
|
return new JSON.Box(
|
|
@@ -1071,72 +2509,80 @@ export namespace JSON {
|
|
|
1071
2509
|
);
|
|
1072
2510
|
}
|
|
1073
2511
|
export namespace Util {
|
|
1074
|
-
|
|
1075
|
-
@inline export function isSpace(code: u16): boolean {
|
|
2512
|
+
export function isSpace(code: u16): boolean {
|
|
1076
2513
|
return code == 0x20 || code - 9 <= 4;
|
|
1077
2514
|
}
|
|
1078
2515
|
/** Advance past JSON whitespace (space, tab, LF, VT, FF, CR). */
|
|
1079
|
-
|
|
1080
|
-
@inline export function skipWhitespace(
|
|
1081
|
-
srcStart: usize,
|
|
1082
|
-
srcEnd: usize,
|
|
1083
|
-
): usize {
|
|
2516
|
+
export function skipWhitespace(srcStart: usize, srcEnd: usize): usize {
|
|
1084
2517
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
1085
2518
|
return srcStart;
|
|
1086
2519
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
): usize {
|
|
1092
|
-
|
|
1093
|
-
let ptr = srcStart;
|
|
1094
|
-
while (ptr < srcEnd && isSpace(load<u16>(ptr))) ptr += 2;
|
|
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
|
-
|
|
2520
|
+
function scanQuotedValueEnd(srcStart: usize, srcEnd: usize): usize {
|
|
2521
|
+
const endQuote = scanStringEnd(srcStart, srcEnd);
|
|
2522
|
+
return endQuote >= srcEnd ? 0 : endQuote + 2;
|
|
2523
|
+
}
|
|
2524
|
+
function scanCompositeValueEnd(srcStart: usize, srcEnd: usize): usize {
|
|
2525
|
+
let depth: i32 = 1;
|
|
2526
|
+
let ptr = srcStart + 2;
|
|
1124
2527
|
while (ptr < srcEnd) {
|
|
1125
2528
|
const code = load<u16>(ptr);
|
|
2529
|
+
if (code == QUOTE) {
|
|
2530
|
+
ptr = scanQuotedValueEnd(ptr, srcEnd);
|
|
2531
|
+
if (!ptr) return 0;
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
if (code == BRACE_LEFT || code == BRACKET_LEFT) {
|
|
2535
|
+
depth++;
|
|
2536
|
+
} else if (code == BRACE_RIGHT || code == BRACKET_RIGHT) {
|
|
2537
|
+
if (--depth == 0) return ptr + 2;
|
|
2538
|
+
}
|
|
2539
|
+
ptr += 2;
|
|
2540
|
+
}
|
|
2541
|
+
return 0;
|
|
2542
|
+
}
|
|
2543
|
+
function scanScalarValueEnd(srcStart: usize, srcEnd: usize): usize {
|
|
2544
|
+
while (srcStart < srcEnd) {
|
|
2545
|
+
const code = load<u16>(srcStart);
|
|
1126
2546
|
if (
|
|
1127
2547
|
code == COMMA ||
|
|
1128
2548
|
code == BRACKET_RIGHT ||
|
|
1129
2549
|
code == BRACE_RIGHT ||
|
|
1130
2550
|
isSpace(code)
|
|
1131
2551
|
)
|
|
1132
|
-
return
|
|
1133
|
-
|
|
2552
|
+
return srcStart;
|
|
2553
|
+
srcStart += 2;
|
|
1134
2554
|
}
|
|
1135
2555
|
|
|
1136
|
-
return
|
|
2556
|
+
return srcStart;
|
|
1137
2557
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
2558
|
+
export function scanValueEnd<T = JSON.Value>(
|
|
2559
|
+
srcStart: usize,
|
|
2560
|
+
srcEnd: usize,
|
|
2561
|
+
): usize {
|
|
2562
|
+
if (srcStart >= srcEnd) return 0;
|
|
2563
|
+
let ptr = skipWhitespace(srcStart, srcEnd);
|
|
2564
|
+
if (ptr >= srcEnd) return 0;
|
|
2565
|
+
|
|
2566
|
+
if (ASC_FEATURE_SIMD) return scanValueEnd_SIMD<T>(ptr, srcEnd);
|
|
2567
|
+
if (JSON_MODE == JSONMode.SWAR) return scanValueEnd_SWAR<T>(ptr, srcEnd);
|
|
2568
|
+
|
|
2569
|
+
const first = load<u16>(ptr);
|
|
2570
|
+
if (isString<nonnull<T>>() && first == QUOTE)
|
|
2571
|
+
return scanQuotedValueEnd(ptr, srcEnd);
|
|
2572
|
+
if (isArray<nonnull<T>>() && first == BRACKET_LEFT)
|
|
2573
|
+
return scanCompositeValueEnd(ptr, srcEnd);
|
|
2574
|
+
if (
|
|
2575
|
+
(isManaged<nonnull<T>>() || isReference<nonnull<T>>()) &&
|
|
2576
|
+
first == BRACE_LEFT
|
|
2577
|
+
)
|
|
2578
|
+
return scanCompositeValueEnd(ptr, srcEnd);
|
|
2579
|
+
|
|
2580
|
+
if (first == QUOTE) return scanQuotedValueEnd(ptr, srcEnd);
|
|
2581
|
+
if (first == BRACE_LEFT || first == BRACKET_LEFT)
|
|
2582
|
+
return scanCompositeValueEnd(ptr, srcEnd);
|
|
2583
|
+
return scanScalarValueEnd(ptr, srcEnd);
|
|
2584
|
+
}
|
|
2585
|
+
export function ptrToStr(start: usize, end: usize): string {
|
|
1140
2586
|
const size = end - start;
|
|
1141
2587
|
const out = __new(size, idof<string>());
|
|
1142
2588
|
memory.copy(out, start, size);
|
|
@@ -1154,11 +2600,7 @@ export namespace JSON {
|
|
|
1154
2600
|
* @param out - string | null
|
|
1155
2601
|
* @returns - string
|
|
1156
2602
|
*/
|
|
1157
|
-
|
|
1158
|
-
@inline export function stringify<T>(
|
|
1159
|
-
data: T,
|
|
1160
|
-
out: string | null = null,
|
|
1161
|
-
): string {
|
|
2603
|
+
export function stringify<T>(data: T, out: string | null = null): string {
|
|
1162
2604
|
bs.saveState();
|
|
1163
2605
|
JSON.__serialize<T>(data);
|
|
1164
2606
|
const result = bs.cpyOut<string>();
|
|
@@ -1177,8 +2619,7 @@ export namespace JSON {
|
|
|
1177
2619
|
* @param data - string
|
|
1178
2620
|
* @returns - T
|
|
1179
2621
|
*/
|
|
1180
|
-
|
|
1181
|
-
@inline export function parse<T>(data: string): T {
|
|
2622
|
+
export function parse<T>(data: string): T {
|
|
1182
2623
|
bs.saveState();
|
|
1183
2624
|
const result = JSON.parse<T>(data);
|
|
1184
2625
|
bs.loadState();
|
|
@@ -1193,8 +2634,7 @@ export namespace JSON {
|
|
|
1193
2634
|
* and `Date` fast paths are handled by the callers (which have buffer-reuse
|
|
1194
2635
|
* optimizations); everything else routes here so the dispatch chain lives once.
|
|
1195
2636
|
*/
|
|
1196
|
-
|
|
1197
|
-
@inline function serializeReference<T>(data: T): void {
|
|
2637
|
+
function serializeReference<T>(data: T): void {
|
|
1198
2638
|
if (data instanceof Array) {
|
|
1199
2639
|
// @ts-expect-error
|
|
1200
2640
|
serializeArray(changetype<nonnull<T>>(data));
|
|
@@ -1241,6 +2681,8 @@ export namespace JSON {
|
|
|
1241
2681
|
serializeArbitrary(data);
|
|
1242
2682
|
} else if (data instanceof JSON.Obj) {
|
|
1243
2683
|
serializeObject(data);
|
|
2684
|
+
} else if (data instanceof JSON.Arr) {
|
|
2685
|
+
serializeJsonArray(data);
|
|
1244
2686
|
} else if (data instanceof JSON.Box) {
|
|
1245
2687
|
JSON.__serialize(data.value);
|
|
1246
2688
|
} else {
|
|
@@ -1258,12 +2700,11 @@ export enum JSONMode {
|
|
|
1258
2700
|
NAIVE = 2,
|
|
1259
2701
|
}
|
|
1260
2702
|
|
|
1261
|
-
|
|
1262
|
-
@inline function parseBox<T>(data: string, ty: T): T {
|
|
2703
|
+
function parseBox<T>(data: string, ty: T): T {
|
|
1263
2704
|
return JSON.parse<T>(data);
|
|
1264
2705
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
2706
|
+
|
|
2707
|
+
function deserializeBox<T>(
|
|
1267
2708
|
srcStart: usize,
|
|
1268
2709
|
srcEnd: usize,
|
|
1269
2710
|
dst: usize,
|
|
@@ -1272,16 +2713,13 @@ export enum JSONMode {
|
|
|
1272
2713
|
return JSON.__deserialize<T>(srcStart, srcEnd, dst);
|
|
1273
2714
|
}
|
|
1274
2715
|
|
|
1275
|
-
|
|
1276
|
-
@inline export function toRaw(data: string): JSON.Raw {
|
|
2716
|
+
export function toRaw(data: string): JSON.Raw {
|
|
1277
2717
|
return new JSON.Raw(data);
|
|
1278
2718
|
}
|
|
1279
|
-
|
|
1280
|
-
@inline export function fromRaw(data: JSON.Raw): string {
|
|
2719
|
+
export function fromRaw(data: JSON.Raw): string {
|
|
1281
2720
|
return data.data;
|
|
1282
2721
|
}
|
|
1283
2722
|
|
|
1284
|
-
|
|
1285
|
-
@inline export function toBox<T>(data: T): JSON.Box<T> {
|
|
2723
|
+
export function toBox<T>(data: T): JSON.Box<T> {
|
|
1286
2724
|
return new JSON.Box<T>(data);
|
|
1287
2725
|
}
|