json-as 1.4.0 → 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 +50 -29
- package/README.md +84 -33
- package/assembly/custom/chars.ts +39 -78
- package/assembly/deserialize/index/arbitrary.ts +26 -8
- 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/integer.ts +1 -1
- package/assembly/deserialize/naive/array/map.ts +1 -1
- package/assembly/deserialize/naive/array/object.ts +1 -1
- 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 +2 -7
- package/assembly/deserialize/naive/integer.ts +1 -2
- package/assembly/deserialize/naive/map.ts +5 -6
- package/assembly/deserialize/naive/object.ts +151 -13
- package/assembly/deserialize/naive/raw.ts +1 -5
- package/assembly/deserialize/naive/set.ts +2 -4
- package/assembly/deserialize/naive/staticarray.ts +1 -2
- package/assembly/deserialize/naive/string.ts +6 -9
- package/assembly/deserialize/naive/unsigned.ts +1 -2
- package/assembly/deserialize/simd/array/integer.ts +2 -7
- package/assembly/deserialize/simd/float.ts +3 -5
- package/assembly/deserialize/simd/integer.ts +2 -7
- package/assembly/deserialize/simd/string.ts +5 -22
- package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
- package/assembly/deserialize/swar/array/array.ts +1 -2
- package/assembly/deserialize/swar/array/bool.ts +1 -2
- package/assembly/deserialize/swar/array/box.ts +1 -2
- package/assembly/deserialize/swar/array/float.ts +6 -18
- package/assembly/deserialize/swar/array/generic.ts +1 -2
- package/assembly/deserialize/swar/array/integer.ts +7 -16
- package/assembly/deserialize/swar/array/map.ts +1 -2
- package/assembly/deserialize/swar/array/object.ts +1 -2
- package/assembly/deserialize/swar/array/raw.ts +1 -2
- package/assembly/deserialize/swar/array/shared.ts +6 -13
- package/assembly/deserialize/swar/array/string.ts +3 -8
- package/assembly/deserialize/swar/array/struct.ts +2 -8
- package/assembly/deserialize/swar/array.ts +1 -3
- package/assembly/deserialize/swar/float.ts +4 -9
- package/assembly/deserialize/swar/integer.ts +2 -7
- package/assembly/deserialize/swar/string.ts +13 -15
- package/assembly/deserialize/swar/typedarray.ts +4 -4
- package/assembly/index.d.ts +29 -24
- package/assembly/index.ts +1362 -246
- package/assembly/serialize/index/arbitrary.ts +70 -4
- package/assembly/serialize/index/jsonarray.ts +51 -0
- package/assembly/serialize/index/object.ts +25 -3
- 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 +2 -5
- package/assembly/serialize/naive/typedarray.ts +0 -6
- package/assembly/serialize/simd/string.ts +1 -3
- package/assembly/serialize/swar/string.ts +1 -2
- package/assembly/util/atoi-fast.ts +4 -14
- 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 +6 -9
- package/assembly/util/nextPowerOf2.ts +1 -2
- package/assembly/util/parsefloat-fast.ts +3 -5
- package/assembly/util/ptrToStr.ts +1 -2
- package/assembly/util/scanValueEndSimd.ts +54 -16
- package/assembly/util/scanValueEndSwar.ts +67 -25
- package/assembly/util/scientific.ts +5 -8
- package/assembly/util/snp.ts +1 -2
- package/assembly/util/swar-int.ts +5 -10
- package/assembly/util/swar.ts +2 -4
- package/lib/as-bs.ts +23 -45
- package/package.json +14 -7
- package/transform/lib/index.js +108 -64
- package/transform/lib/types.d.ts +2 -1
- package/transform/lib/types.js +3 -0
- package/assembly/util/dragonbox-cache.ts +0 -445
- package/assembly/util/dragonbox.ts +0 -652
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { JSON } from "../..";
|
|
2
|
-
import { deserializeArray } from "./array";
|
|
3
2
|
import { deserializeBoolean } from "./bool";
|
|
4
3
|
import { deserializeFloat } from "./float";
|
|
5
|
-
import { deserializeObject } from "./object";
|
|
4
|
+
import { deserializeObject, deserializeJsonArray, getParseSrc } from "./object";
|
|
6
5
|
import { deserializeString } from "./string";
|
|
7
6
|
import { BRACE_LEFT, BRACKET_LEFT, CHAR_N, QUOTE } from "../../custom/chars";
|
|
8
7
|
|
|
@@ -11,15 +10,34 @@ export function deserializeArbitrary(
|
|
|
11
10
|
srcEnd: usize,
|
|
12
11
|
dst: usize,
|
|
13
12
|
): JSON.Value {
|
|
13
|
+
const v = parseArbitraryValue(srcStart, srcEnd);
|
|
14
|
+
// Reuse path (`JSON.parse<JSON.Value>(data, out)`): write the parsed bits into
|
|
15
|
+
// the caller's handle (with the GC barrier for any managed payload).
|
|
16
|
+
return dst != 0 ? JSON.Value.__adoptInto(dst, v) : v;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseArbitraryValue(srcStart: usize, srcEnd: usize): JSON.Value {
|
|
14
20
|
const firstChar = load<u16>(srcStart);
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
if (
|
|
22
|
+
firstChar == QUOTE ||
|
|
23
|
+
firstChar == BRACE_LEFT ||
|
|
24
|
+
firstChar == BRACKET_LEFT
|
|
25
|
+
) {
|
|
26
|
+
// Lazy by default: when a parse is in flight (source anchor present), defer
|
|
27
|
+
// strings and composites (the allocating shapes) - store the exact raw slice
|
|
28
|
+
// and materialize on first access. Cheap primitives stay eager below.
|
|
29
|
+
const src = getParseSrc();
|
|
30
|
+
if (src.length != 0) {
|
|
31
|
+
const end = JSON.Util.scanValueEnd<JSON.Value>(srcStart, srcEnd);
|
|
32
|
+
return JSON.Value.fromSlice(srcStart, end, src);
|
|
33
|
+
}
|
|
34
|
+
if (firstChar == QUOTE)
|
|
35
|
+
return JSON.Value.from(deserializeString(srcStart, srcEnd));
|
|
36
|
+
return firstChar == BRACE_LEFT
|
|
37
|
+
? JSON.Value.from(deserializeObject(srcStart, srcEnd, 0))
|
|
38
|
+
: JSON.Value.from(deserializeJsonArray(srcStart, srcEnd, 0));
|
|
19
39
|
} else if (firstChar - 48 <= 9 || firstChar == 45) {
|
|
20
40
|
return JSON.Value.from(deserializeFloat<f64>(srcStart, srcEnd));
|
|
21
|
-
} else if (firstChar == BRACKET_LEFT) {
|
|
22
|
-
return JSON.Value.from(deserializeArray<JSON.Value[]>(srcStart, srcEnd, 0));
|
|
23
41
|
} else if (firstChar == 116 || firstChar == 102) {
|
|
24
42
|
return JSON.Value.from(deserializeBoolean(srcStart, srcEnd));
|
|
25
43
|
} else if (firstChar == CHAR_N) {
|
|
@@ -12,8 +12,7 @@ import {
|
|
|
12
12
|
deserializeFloatField_SIMD,
|
|
13
13
|
} from "../simd/float";
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
@inline export function deserializeFloat<T>(srcStart: usize, srcEnd: usize): T {
|
|
15
|
+
export function deserializeFloat<T>(srcStart: usize, srcEnd: usize): T {
|
|
17
16
|
if (JSON_MODE == JSONMode.SIMD) {
|
|
18
17
|
return deserializeFloat_SIMD<T>(srcStart, srcEnd);
|
|
19
18
|
} else if (JSON_MODE == JSONMode.NAIVE) {
|
|
@@ -23,8 +22,7 @@ import {
|
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
@inline export function deserializeFloatField<T extends number>(
|
|
25
|
+
export function deserializeFloatField<T extends number>(
|
|
28
26
|
srcStart: usize,
|
|
29
27
|
srcEnd: usize,
|
|
30
28
|
dstObj: usize,
|
|
@@ -21,8 +21,7 @@ import {
|
|
|
21
21
|
* @param srcEnd Pointer just past the last code unit.
|
|
22
22
|
* @returns The parsed value, truncated to `T`.
|
|
23
23
|
*/
|
|
24
|
-
|
|
25
|
-
@inline export function deserializeInteger<T extends number>(
|
|
24
|
+
export function deserializeInteger<T extends number>(
|
|
26
25
|
srcStart: usize,
|
|
27
26
|
srcEnd: usize,
|
|
28
27
|
): T {
|
|
@@ -46,8 +45,7 @@ import {
|
|
|
46
45
|
* @param dstOffset Byte offset of the field within `dstObj`.
|
|
47
46
|
* @returns The source position immediately after the last digit consumed.
|
|
48
47
|
*/
|
|
49
|
-
|
|
50
|
-
@inline export function deserializeIntegerField<T extends number>(
|
|
48
|
+
export function deserializeIntegerField<T extends number>(
|
|
51
49
|
srcStart: usize,
|
|
52
50
|
srcEnd: usize,
|
|
53
51
|
dstObj: usize,
|
|
@@ -12,11 +12,7 @@ import {
|
|
|
12
12
|
deserializeStringField_SWAR,
|
|
13
13
|
} from "../swar/string";
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
@inline export function deserializeString(
|
|
17
|
-
srcStart: usize,
|
|
18
|
-
srcEnd: usize,
|
|
19
|
-
): string {
|
|
15
|
+
export function deserializeString(srcStart: usize, srcEnd: usize): string {
|
|
20
16
|
if (JSON_MODE == JSONMode.SIMD) {
|
|
21
17
|
return deserializeString_SIMD(srcStart, srcEnd);
|
|
22
18
|
} else if (JSON_MODE == JSONMode.NAIVE) {
|
|
@@ -26,8 +22,7 @@ import {
|
|
|
26
22
|
}
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
@inline export function deserializeStringField<T extends string | null>(
|
|
25
|
+
export function deserializeStringField<T extends string | null>(
|
|
31
26
|
srcStart: usize,
|
|
32
27
|
srcEnd: usize,
|
|
33
28
|
dstObj: usize,
|
|
@@ -21,8 +21,7 @@ import {
|
|
|
21
21
|
* @param srcEnd Pointer just past the last code unit.
|
|
22
22
|
* @returns The parsed value, truncated to `T`.
|
|
23
23
|
*/
|
|
24
|
-
|
|
25
|
-
@inline export function deserializeUnsigned<T extends number>(
|
|
24
|
+
export function deserializeUnsigned<T extends number>(
|
|
26
25
|
srcStart: usize,
|
|
27
26
|
srcEnd: usize,
|
|
28
27
|
): T {
|
|
@@ -46,8 +45,7 @@ import {
|
|
|
46
45
|
* @param dstOffset Byte offset of the field within `dstObj`.
|
|
47
46
|
* @returns The source position immediately after the last digit consumed.
|
|
48
47
|
*/
|
|
49
|
-
|
|
50
|
-
@inline export function deserializeUnsignedField<T extends number>(
|
|
48
|
+
export function deserializeUnsignedField<T extends number>(
|
|
51
49
|
srcStart: usize,
|
|
52
50
|
srcEnd: usize,
|
|
53
51
|
dstObj: usize,
|
|
@@ -2,7 +2,7 @@ import { atoi, isSpace } from "../../../util";
|
|
|
2
2
|
import { COMMA, BRACKET_LEFT, BRACKET_RIGHT } from "../../../custom/chars";
|
|
3
3
|
|
|
4
4
|
// Strict RFC 8259 integer-token check over [start, end): optional minus (signed
|
|
5
|
-
// types only), then a lone `0` or [1-9] digits
|
|
5
|
+
// types only), then a lone `0` or [1-9] digits - no leading zeros, fraction,
|
|
6
6
|
// exponent, or trailing garbage. Throws otherwise.
|
|
7
7
|
function validateJSONInteger(start: usize, end: usize, signed: bool): void {
|
|
8
8
|
let ptr = start;
|
|
@@ -29,7 +29,7 @@ export function deserializeMapArray<T extends Map<any, any>[]>(
|
|
|
29
29
|
);
|
|
30
30
|
|
|
31
31
|
// Each `{...}` map element is parsed in a single pass via deserializeMapBody,
|
|
32
|
-
// which reports where it ended
|
|
32
|
+
// which reports where it ended - no separate scan to find the closing brace.
|
|
33
33
|
while (srcStart < srcEnd) {
|
|
34
34
|
if (load<u16>(srcStart) == BRACE_LEFT) {
|
|
35
35
|
const m = instantiate<valueof<T>>();
|
|
@@ -29,7 +29,7 @@ export function deserializeObjectArray<T extends unknown[]>(
|
|
|
29
29
|
);
|
|
30
30
|
|
|
31
31
|
// Each `{...}` element is parsed in a single pass via parseObjectBody, which
|
|
32
|
-
// reports where it ended
|
|
32
|
+
// reports where it ended - no separate scan to find the closing brace.
|
|
33
33
|
while (srcStart < srcEnd) {
|
|
34
34
|
if (load<u16>(srcStart) == BRACE_LEFT) {
|
|
35
35
|
const obj = new JSON.Obj();
|
|
@@ -34,14 +34,32 @@ export function deserializeStructArray<T extends unknown[]>(
|
|
|
34
34
|
(srcEnd - srcStart).toString(),
|
|
35
35
|
);
|
|
36
36
|
|
|
37
|
+
// Reuse existing element slots when `dst` already holds elements (no per-call
|
|
38
|
+
// allocation on a reused array); fall back to push for fresh/extra slots and
|
|
39
|
+
// trim any leftovers from a previous, longer parse.
|
|
40
|
+
let index = 0;
|
|
37
41
|
while (srcStart < srcEnd) {
|
|
38
42
|
const code = load<u16>(srcStart);
|
|
39
43
|
if (code == BRACE_LEFT && depth++ == 0) {
|
|
40
44
|
lastIndex = srcStart;
|
|
41
45
|
} else if (code == BRACE_RIGHT && --depth == 0) {
|
|
42
|
-
|
|
46
|
+
const valueEnd = (srcStart += 2);
|
|
47
|
+
if (<usize>index < <usize>out.length) {
|
|
48
|
+
const slot = changetype<usize>(unchecked(out[index]));
|
|
49
|
+
if (slot != 0) {
|
|
50
|
+
JSON.__deserialize<valueof<T>>(lastIndex, valueEnd, slot);
|
|
51
|
+
} else {
|
|
52
|
+
unchecked(
|
|
53
|
+
(out[index] = JSON.__deserialize<valueof<T>>(lastIndex, valueEnd)),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
out.push(JSON.__deserialize<valueof<T>>(lastIndex, valueEnd));
|
|
58
|
+
}
|
|
59
|
+
index++;
|
|
43
60
|
}
|
|
44
61
|
srcStart += 2;
|
|
45
62
|
}
|
|
63
|
+
if (<usize>index < <usize>out.length) out.length = index;
|
|
46
64
|
return out;
|
|
47
65
|
}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
@inline export function deserializeBoolean(
|
|
3
|
-
srcStart: usize,
|
|
4
|
-
srcEnd: usize,
|
|
5
|
-
): boolean {
|
|
1
|
+
export function deserializeBoolean(srcStart: usize, srcEnd: usize): boolean {
|
|
6
2
|
const block = load<u64>(srcStart);
|
|
7
3
|
if (block == 28429475166421108) return true;
|
|
8
4
|
else if (block == 32370086184550502 && load<u16>(srcStart, 8) == 101)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ptrToStr } from "../../util/ptrToStr";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
@inline export function deserializeDate(srcStart: usize, srcEnd: usize): Date {
|
|
3
|
+
export function deserializeDate(srcStart: usize, srcEnd: usize): Date {
|
|
5
4
|
// Use AssemblyScript's date parser
|
|
6
5
|
const d = Date.fromString(ptrToStr(srcStart + 2, srcEnd - 2));
|
|
7
6
|
|
|
@@ -61,11 +61,7 @@ function validateJSONNumber(srcStart: usize, srcEnd: usize): void {
|
|
|
61
61
|
if (ptr != end) throw new Error("Invalid JSON number: trailing characters");
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
@inline export function deserializeFloat_NAIVE<T>(
|
|
66
|
-
srcStart: usize,
|
|
67
|
-
srcEnd: usize,
|
|
68
|
-
): T {
|
|
64
|
+
export function deserializeFloat_NAIVE<T>(srcStart: usize, srcEnd: usize): T {
|
|
69
65
|
validateJSONNumber(srcStart, srcEnd);
|
|
70
66
|
// @ts-ignore
|
|
71
67
|
const type: T = 0;
|
|
@@ -113,8 +109,7 @@ function scanFloatEnd(srcStart: usize, srcEnd: usize): usize {
|
|
|
113
109
|
return ptr;
|
|
114
110
|
}
|
|
115
111
|
|
|
116
|
-
|
|
117
|
-
@inline export function deserializeFloatField_NAIVE<T extends number>(
|
|
112
|
+
export function deserializeFloatField_NAIVE<T extends number>(
|
|
118
113
|
srcStart: usize,
|
|
119
114
|
srcEnd: usize,
|
|
120
115
|
dstObj: usize,
|
|
@@ -10,8 +10,8 @@ import { isSpace, scanStringEnd } from "../../util";
|
|
|
10
10
|
import { scanValueEnd } from "../../util/scanValueEnd";
|
|
11
11
|
import { lastValueEnd, parseValue } from "./object";
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
@
|
|
13
|
+
function deserializeMapKey<T>(start: usize, end: usize): T {
|
|
14
|
+
// @ts-expect-error: exists
|
|
15
15
|
const keyText = JSON.__deserialize<string>(start - 2, end + 2);
|
|
16
16
|
if (isString<T>()) return changetype<T>(keyText);
|
|
17
17
|
return JSON.parse<T>(keyText);
|
|
@@ -85,7 +85,7 @@ export function deserializeMapBody<T extends Map<any, any>>(
|
|
|
85
85
|
|
|
86
86
|
if (isReference<valueof<T>>() && arbitraryValue) {
|
|
87
87
|
const val = parseValue(srcStart, srcEnd);
|
|
88
|
-
// @ts-ignore: type
|
|
88
|
+
// @ts-ignore: type - valueof<T> is JSON.Value in this branch
|
|
89
89
|
changetype<nonnull<T>>(out).set(
|
|
90
90
|
deserializeMapKey<indexof<T>>(keyStart, keyEnd),
|
|
91
91
|
changetype<valueof<T>>(changetype<usize>(val)),
|
|
@@ -117,8 +117,7 @@ export function deserializeMapBody<T extends Map<any, any>>(
|
|
|
117
117
|
throw new Error("Failed to parse JSON!");
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
@inline export function deserializeMapField<T extends Map<any, any>>(
|
|
120
|
+
export function deserializeMapField<T extends Map<any, any>>(
|
|
122
121
|
srcStart: usize,
|
|
123
122
|
srcEnd: usize,
|
|
124
123
|
dstObj: usize,
|
|
@@ -130,7 +129,7 @@ export function deserializeMapBody<T extends Map<any, any>>(
|
|
|
130
129
|
out = changetype<T>(instantiate<T>());
|
|
131
130
|
store<T>(fieldPtr, out);
|
|
132
131
|
} else {
|
|
133
|
-
// Reusing an existing field map
|
|
132
|
+
// Reusing an existing field map - clear it before repopulating. Fresh maps
|
|
134
133
|
// (deserializeMap / deserializeMapArray) skip this.
|
|
135
134
|
changetype<nonnull<T>>(out).clear();
|
|
136
135
|
}
|
|
@@ -16,14 +16,11 @@ import { deserializeFloat } from "../index/float";
|
|
|
16
16
|
import { deserializeString } from "../index/string";
|
|
17
17
|
|
|
18
18
|
// "true" as a u64 of UTF-16 code units (LE).
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// @ts-ignore: inline
|
|
23
|
-
@inline const ALSE_WORD: u64 = 28429466576093281;
|
|
19
|
+
const TRUE_WORD: u64 = 28429475166421108;
|
|
20
|
+
// "alse" - the tail of "false", read at +2 so the leading 'f' is skipped.
|
|
21
|
+
const ALSE_WORD: u64 = 28429466576093281;
|
|
24
22
|
// "null" as a u64 of UTF-16 code units (LE).
|
|
25
|
-
|
|
26
|
-
@inline const NULL_WORD: u64 = 30399761348886638;
|
|
23
|
+
const NULL_WORD: u64 = 30399761348886638;
|
|
27
24
|
|
|
28
25
|
// End offset (just past the value) of the most recent parseValue() call. The
|
|
29
26
|
// recursive-descent parser reports each value's end through this single cursor
|
|
@@ -32,12 +29,32 @@ import { deserializeString } from "../index/string";
|
|
|
32
29
|
// parseValue() runs, so recursion never clobbers a still-needed value.
|
|
33
30
|
let parseValueEnd: usize = 0;
|
|
34
31
|
|
|
32
|
+
// Source string for the parse currently in flight. When non-empty, nested
|
|
33
|
+
// objects/arrays are deferred: instead of being recursively materialized, each
|
|
34
|
+
// is stored as a lazy JSON.Value holding its raw slice + this anchor (see
|
|
35
|
+
// JSON.Value.fromSlice / JSON.Types.Lazy). Set at the top of JSON.parse (and in
|
|
36
|
+
// JSON.Value.materialize), both of which save/restore it for re-entrancy, so any
|
|
37
|
+
// lazy value built during a parse points into that parse's own source buffer.
|
|
38
|
+
let parseSrc: string = "";
|
|
39
|
+
export function setParseSrc(s: string): void {
|
|
40
|
+
parseSrc = s;
|
|
41
|
+
}
|
|
42
|
+
export function getParseSrc(): string {
|
|
43
|
+
return parseSrc;
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
export function deserializeObject(
|
|
36
47
|
srcStart: usize,
|
|
37
48
|
srcEnd: usize,
|
|
38
49
|
dst: usize,
|
|
39
50
|
): JSON.Obj {
|
|
40
|
-
const
|
|
51
|
+
const reuse = dst != 0;
|
|
52
|
+
const out = changetype<JSON.Obj>(
|
|
53
|
+
reuse ? dst : changetype<usize>(new JSON.Obj()),
|
|
54
|
+
);
|
|
55
|
+
// Reuse path (`JSON.parse<JSON.Obj>(data, out)`): empty the handle first,
|
|
56
|
+
// keeping its buffer capacity, so we overwrite rather than append stale data.
|
|
57
|
+
if (reuse) out.clear();
|
|
41
58
|
|
|
42
59
|
while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
|
|
43
60
|
|
|
@@ -58,6 +75,104 @@ export function deserializeObject(
|
|
|
58
75
|
return out;
|
|
59
76
|
}
|
|
60
77
|
|
|
78
|
+
export function deserializeJsonArray(
|
|
79
|
+
srcStart: usize,
|
|
80
|
+
srcEnd: usize,
|
|
81
|
+
dst: usize,
|
|
82
|
+
): JSON.Arr {
|
|
83
|
+
const reuse = dst != 0;
|
|
84
|
+
const out = changetype<JSON.Arr>(
|
|
85
|
+
reuse ? dst : changetype<usize>(new JSON.Arr()),
|
|
86
|
+
);
|
|
87
|
+
if (reuse) out.clear();
|
|
88
|
+
|
|
89
|
+
while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
|
|
90
|
+
|
|
91
|
+
if (srcEnd == srcStart)
|
|
92
|
+
throw new Error("Input string had zero length or was all whitespace");
|
|
93
|
+
if (load<u16>(srcStart) != BRACKET_LEFT)
|
|
94
|
+
throw new Error("Expected '[' at start of array");
|
|
95
|
+
if (load<u16>(srcEnd - 2) != BRACKET_RIGHT)
|
|
96
|
+
throw new Error("Expected ']' at end of array");
|
|
97
|
+
|
|
98
|
+
parseArrayBodySlots(out, srcStart + 2, srcEnd);
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parses array elements starting at `srcStart` (just past the opening `[`) into
|
|
104
|
+
* a JSON.Arr's NaN-boxed value slots, returning the offset just after the
|
|
105
|
+
* matching `]`. Mirrors {@link parseObjectBody} without keys.
|
|
106
|
+
*/
|
|
107
|
+
export function parseArrayBodySlots(
|
|
108
|
+
out: JSON.Arr,
|
|
109
|
+
srcStart: usize,
|
|
110
|
+
srcEnd: usize,
|
|
111
|
+
): usize {
|
|
112
|
+
out._src = parseSrc;
|
|
113
|
+
while (srcStart < srcEnd) {
|
|
114
|
+
const code = load<u16>(srcStart);
|
|
115
|
+
if (isSpace(code) || code == COMMA) {
|
|
116
|
+
srcStart += 2;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (code == BRACKET_RIGHT) return srcStart + 2;
|
|
120
|
+
if (parseSrc.length != 0) {
|
|
121
|
+
out.pushRawSlot(parseSlotBits(srcStart, srcEnd));
|
|
122
|
+
} else {
|
|
123
|
+
out.pushRawSlot(
|
|
124
|
+
JSON.Value.bitsFrom<JSON.Value>(parseValue(srcStart, srcEnd)),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
srcStart = parseValueEnd;
|
|
128
|
+
}
|
|
129
|
+
return srcEnd;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Parses a single object-member value into a NaN-boxed value slot, setting
|
|
134
|
+
* {@link parseValueEnd} to the offset just past it. Strings and composites are
|
|
135
|
+
* deferred (a `valBox(Lazy, startPtr)` slice the object parses on first access);
|
|
136
|
+
* numbers, booleans and null are parsed eagerly inline. Requires {@link parseSrc}
|
|
137
|
+
* to be set (the caller guards), since a lazy slot points into it.
|
|
138
|
+
*/
|
|
139
|
+
function parseSlotBits(srcStart: usize, srcEnd: usize): u64 {
|
|
140
|
+
const code = load<u16>(srcStart);
|
|
141
|
+
if (code == QUOTE || code == BRACE_LEFT || code == BRACKET_LEFT) {
|
|
142
|
+
const end = JSON.Util.scanValueEnd<JSON.Value>(srcStart, srcEnd);
|
|
143
|
+
parseValueEnd = end;
|
|
144
|
+
return JSON.Value.lazyBits(changetype<usize>(parseSrc), srcStart, end);
|
|
145
|
+
} else if (code - 48 <= 9 || code == 45) {
|
|
146
|
+
let p = srcStart + 2;
|
|
147
|
+
while (p < srcEnd) {
|
|
148
|
+
const c = load<u16>(p);
|
|
149
|
+
if (c == COMMA || c == BRACKET_RIGHT || c == BRACE_RIGHT || isSpace(c))
|
|
150
|
+
break;
|
|
151
|
+
p += 2;
|
|
152
|
+
}
|
|
153
|
+
parseValueEnd = p;
|
|
154
|
+
return JSON.Value.f64Bits(deserializeFloat<f64>(srcStart, p));
|
|
155
|
+
} else if (code == CHAR_T) {
|
|
156
|
+
if (load<u64>(srcStart) != TRUE_WORD)
|
|
157
|
+
throw new Error("Expected 'true' in JSON");
|
|
158
|
+
parseValueEnd = srcStart + 8;
|
|
159
|
+
return JSON.Value.boolBits(true);
|
|
160
|
+
} else if (code == CHAR_F) {
|
|
161
|
+
if (load<u64>(srcStart, 2) != ALSE_WORD)
|
|
162
|
+
throw new Error("Expected 'false' in JSON");
|
|
163
|
+
parseValueEnd = srcStart + 10;
|
|
164
|
+
return JSON.Value.boolBits(false);
|
|
165
|
+
} else if (code == CHAR_N) {
|
|
166
|
+
if (load<u64>(srcStart) != NULL_WORD)
|
|
167
|
+
throw new Error("Expected 'null' in JSON");
|
|
168
|
+
parseValueEnd = srcStart + 8;
|
|
169
|
+
return JSON.Value.nullBits();
|
|
170
|
+
}
|
|
171
|
+
throw new Error(
|
|
172
|
+
"Unexpected character in JSON '" + String.fromCharCode(code) + "'",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
61
176
|
/**
|
|
62
177
|
* Parses a single JSON value whose first character is at `srcStart` (`srcEnd`
|
|
63
178
|
* is an upper bound). Returns the value and sets {@link parseValueEnd} to the
|
|
@@ -81,8 +196,8 @@ export function parseValue(srcStart: usize, srcEnd: usize): JSON.Value {
|
|
|
81
196
|
parseValueEnd = parseObjectBody(obj, srcStart + 2, srcEnd);
|
|
82
197
|
return JSON.Value.from(obj);
|
|
83
198
|
} else if (code == BRACKET_LEFT) {
|
|
84
|
-
const arr =
|
|
85
|
-
parseValueEnd =
|
|
199
|
+
const arr = new JSON.Arr();
|
|
200
|
+
parseValueEnd = parseArrayBodySlots(arr, srcStart + 2, srcEnd);
|
|
86
201
|
return JSON.Value.from(arr);
|
|
87
202
|
} else if (code - 48 <= 9 || code == 45) {
|
|
88
203
|
let p = srcStart + 2;
|
|
@@ -132,8 +247,19 @@ export function parseArrayBody(
|
|
|
132
247
|
continue;
|
|
133
248
|
}
|
|
134
249
|
if (code == BRACKET_RIGHT) return srcStart + 2;
|
|
135
|
-
|
|
136
|
-
|
|
250
|
+
if (
|
|
251
|
+
(code == BRACE_LEFT || code == BRACKET_LEFT || code == QUOTE) &&
|
|
252
|
+
parseSrc.length != 0
|
|
253
|
+
) {
|
|
254
|
+
// Defer strings and composites (the allocating shapes): store the raw
|
|
255
|
+
// slice, parse on first access. Cheap primitives stay eager below.
|
|
256
|
+
const end = JSON.Util.scanValueEnd<JSON.Value>(srcStart, srcEnd);
|
|
257
|
+
out.push(JSON.Value.fromSlice(srcStart, end, parseSrc));
|
|
258
|
+
srcStart = end;
|
|
259
|
+
} else {
|
|
260
|
+
out.push(parseValue(srcStart, srcEnd));
|
|
261
|
+
srcStart = parseValueEnd;
|
|
262
|
+
}
|
|
137
263
|
}
|
|
138
264
|
return srcEnd;
|
|
139
265
|
}
|
|
@@ -148,6 +274,11 @@ export function parseObjectBody(
|
|
|
148
274
|
srcStart: usize,
|
|
149
275
|
srcEnd: usize,
|
|
150
276
|
): usize {
|
|
277
|
+
// Anchor the source for this object's deferred value slots (start pointers
|
|
278
|
+
// into it). Set here so every caller - deserializeObject, the JSON.Obj[]
|
|
279
|
+
// array path, parseValue, the map path - gets it. Empty off the parse path,
|
|
280
|
+
// where no lazy slots are produced.
|
|
281
|
+
out._src = parseSrc;
|
|
151
282
|
while (srcStart < srcEnd) {
|
|
152
283
|
let code = load<u16>(srcStart);
|
|
153
284
|
|
|
@@ -185,7 +316,14 @@ export function parseObjectBody(
|
|
|
185
316
|
// --- value ---
|
|
186
317
|
while (srcStart < srcEnd && isSpace((code = load<u16>(srcStart))))
|
|
187
318
|
srcStart += 2;
|
|
188
|
-
|
|
319
|
+
if (parseSrc.length != 0) {
|
|
320
|
+
// Parsing: store a NaN-boxed slot directly (strings/composites deferred,
|
|
321
|
+
// scalars eager) - no per-value JSON.Value object.
|
|
322
|
+
out.appendRawSlot(keyStart, keyEnd, parseSlotBits(srcStart, srcEnd));
|
|
323
|
+
} else {
|
|
324
|
+
// Off the parse path (no source anchor): box eagerly.
|
|
325
|
+
out.appendRaw(keyStart, keyEnd, parseValue(srcStart, srcEnd));
|
|
326
|
+
}
|
|
189
327
|
srcStart = parseValueEnd;
|
|
190
328
|
}
|
|
191
329
|
return srcEnd;
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { JSON } from "../..";
|
|
2
2
|
import { ptrToStr } from "../../util/ptrToStr";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
@inline export function deserializeRaw(
|
|
6
|
-
srcStart: usize,
|
|
7
|
-
srcEnd: usize,
|
|
8
|
-
): JSON.Raw {
|
|
4
|
+
export function deserializeRaw(srcStart: usize, srcEnd: usize): JSON.Raw {
|
|
9
5
|
return JSON.Raw.from(ptrToStr(srcStart, srcEnd));
|
|
10
6
|
}
|
|
@@ -156,8 +156,7 @@ export function deserializeSet<T extends Set<any>>(
|
|
|
156
156
|
return out;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
@inline function deserializeSetBody<T extends Set<any>>(
|
|
159
|
+
function deserializeSetBody<T extends Set<any>>(
|
|
161
160
|
srcStart: usize,
|
|
162
161
|
srcEnd: usize,
|
|
163
162
|
out: T,
|
|
@@ -166,8 +165,7 @@ export function deserializeSet<T extends Set<any>>(
|
|
|
166
165
|
return deserializeSetDirect<T>(srcStart, srcEnd, changetype<nonnull<T>>(out));
|
|
167
166
|
}
|
|
168
167
|
|
|
169
|
-
|
|
170
|
-
@inline export function deserializeSetField<T extends Set<any>>(
|
|
168
|
+
export function deserializeSetField<T extends Set<any>>(
|
|
171
169
|
srcStart: usize,
|
|
172
170
|
srcEnd: usize,
|
|
173
171
|
dstObj: usize,
|
|
@@ -112,8 +112,7 @@ export function deserializeStaticArray<T extends StaticArray<any>>(
|
|
|
112
112
|
throw new Error("Could not parse static array of type " + nameof<T>() + "!");
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
@inline export function deserializeStaticArrayField<T extends StaticArray<any>>(
|
|
115
|
+
export function deserializeStaticArrayField<T extends StaticArray<any>>(
|
|
117
116
|
srcStart: usize,
|
|
118
117
|
srcEnd: usize,
|
|
119
118
|
dstObj: usize,
|
|
@@ -4,15 +4,13 @@ import { __heap_base } from "memory";
|
|
|
4
4
|
import { BACK_SLASH, QUOTE } from "../../custom/chars";
|
|
5
5
|
import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
@inline function hexDigit(c: u16): u32 {
|
|
7
|
+
function hexDigit(c: u16): u32 {
|
|
9
8
|
if (c <= 0x39) return c - 0x30; // '0'-'9'
|
|
10
9
|
if (c <= 0x46) return c - 0x37; // 'A'-'F'
|
|
11
10
|
return c - 0x57; // 'a'-'f'
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
@inline function hex4ToU16(srcStart: usize): u16 {
|
|
13
|
+
function hex4ToU16(srcStart: usize): u16 {
|
|
16
14
|
return <u16>(
|
|
17
15
|
((hexDigit(load<u16>(srcStart)) << 12) |
|
|
18
16
|
(hexDigit(load<u16>(srcStart, 2)) << 8) |
|
|
@@ -21,8 +19,7 @@ import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
|
|
|
21
19
|
);
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
@inline function isHexDigit(c: u16): bool {
|
|
22
|
+
function isHexDigit(c: u16): bool {
|
|
26
23
|
return (
|
|
27
24
|
(c >= 0x30 && c <= 0x39) ||
|
|
28
25
|
(c >= 0x41 && c <= 0x46) ||
|
|
@@ -148,7 +145,7 @@ function writeStringToField(
|
|
|
148
145
|
// Escape-bearing tail of the field parse: the clean prefix [payloadStart,
|
|
149
146
|
// escPos) is bulk-copied into the scratch buffer, then escapes are decoded into
|
|
150
147
|
// it, and the result is written to the field. Only reached when a backslash is
|
|
151
|
-
// actually present
|
|
148
|
+
// actually present - the common escape-free case never touches `bs`.
|
|
152
149
|
function deserializeEscapedStringField_NAIVE(
|
|
153
150
|
payloadStart: usize,
|
|
154
151
|
escPos: usize,
|
|
@@ -201,8 +198,8 @@ function deserializeEscapedStringField_NAIVE(
|
|
|
201
198
|
// NOT @inline: this is a loop-bearing scanner called per string field. As an
|
|
202
199
|
// always-inline entry it gets inlined into every field call site inside the
|
|
203
200
|
// @inline __DESERIALIZE_FAST, exploding binaryen's optimize phase on large
|
|
204
|
-
// schemas (~118s on the `large` bench). Kept as a single shared function
|
|
205
|
-
// call per field
|
|
201
|
+
// schemas (~118s on the `large` bench). Kept as a single shared function - one
|
|
202
|
+
// call per field - matching the non-inline SWAR/SIMD field deserializers.
|
|
206
203
|
export function deserializeStringField_NAIVE<T extends string | null>(
|
|
207
204
|
srcStart: usize,
|
|
208
205
|
srcEnd: usize,
|
|
@@ -40,11 +40,7 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
|
|
|
40
40
|
// @ts-expect-error: decorators valid here
|
|
41
41
|
@lazy const PAIR_WEIGHTS_100_1 = i16x8(100, 1, 100, 1, 0, 0, 0, 0);
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
@inline function storeSignedInteger<T extends number[]>(
|
|
45
|
-
slot: usize,
|
|
46
|
-
value: i64,
|
|
47
|
-
): void {
|
|
43
|
+
function storeSignedInteger<T extends number[]>(slot: usize, value: i64): void {
|
|
48
44
|
if (sizeof<valueof<T>>() == sizeof<i8>()) {
|
|
49
45
|
store<i8>(slot, <i8>value);
|
|
50
46
|
} else if (sizeof<valueof<T>>() == sizeof<i16>()) {
|
|
@@ -58,8 +54,7 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
|
|
|
58
54
|
}
|
|
59
55
|
}
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
@inline function storeUnsignedInteger<T extends number[]>(
|
|
57
|
+
function storeUnsignedInteger<T extends number[]>(
|
|
63
58
|
slot: usize,
|
|
64
59
|
value: u64,
|
|
65
60
|
): void {
|