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.
Files changed (86) hide show
  1. package/CHANGELOG.md +50 -29
  2. package/README.md +84 -33
  3. package/assembly/custom/chars.ts +39 -78
  4. package/assembly/deserialize/index/arbitrary.ts +26 -8
  5. package/assembly/deserialize/index/float.ts +2 -4
  6. package/assembly/deserialize/index/integer.ts +2 -4
  7. package/assembly/deserialize/index/object.ts +6 -1
  8. package/assembly/deserialize/index/string.ts +2 -7
  9. package/assembly/deserialize/index/unsigned.ts +2 -4
  10. package/assembly/deserialize/naive/array/integer.ts +1 -1
  11. package/assembly/deserialize/naive/array/map.ts +1 -1
  12. package/assembly/deserialize/naive/array/object.ts +1 -1
  13. package/assembly/deserialize/naive/array/struct.ts +19 -1
  14. package/assembly/deserialize/naive/bool.ts +1 -5
  15. package/assembly/deserialize/naive/date.ts +1 -2
  16. package/assembly/deserialize/naive/float.ts +2 -7
  17. package/assembly/deserialize/naive/integer.ts +1 -2
  18. package/assembly/deserialize/naive/map.ts +5 -6
  19. package/assembly/deserialize/naive/object.ts +151 -13
  20. package/assembly/deserialize/naive/raw.ts +1 -5
  21. package/assembly/deserialize/naive/set.ts +2 -4
  22. package/assembly/deserialize/naive/staticarray.ts +1 -2
  23. package/assembly/deserialize/naive/string.ts +6 -9
  24. package/assembly/deserialize/naive/unsigned.ts +1 -2
  25. package/assembly/deserialize/simd/array/integer.ts +2 -7
  26. package/assembly/deserialize/simd/float.ts +3 -5
  27. package/assembly/deserialize/simd/integer.ts +2 -7
  28. package/assembly/deserialize/simd/string.ts +5 -22
  29. package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
  30. package/assembly/deserialize/swar/array/array.ts +1 -2
  31. package/assembly/deserialize/swar/array/bool.ts +1 -2
  32. package/assembly/deserialize/swar/array/box.ts +1 -2
  33. package/assembly/deserialize/swar/array/float.ts +6 -18
  34. package/assembly/deserialize/swar/array/generic.ts +1 -2
  35. package/assembly/deserialize/swar/array/integer.ts +7 -16
  36. package/assembly/deserialize/swar/array/map.ts +1 -2
  37. package/assembly/deserialize/swar/array/object.ts +1 -2
  38. package/assembly/deserialize/swar/array/raw.ts +1 -2
  39. package/assembly/deserialize/swar/array/shared.ts +6 -13
  40. package/assembly/deserialize/swar/array/string.ts +3 -8
  41. package/assembly/deserialize/swar/array/struct.ts +2 -8
  42. package/assembly/deserialize/swar/array.ts +1 -3
  43. package/assembly/deserialize/swar/float.ts +4 -9
  44. package/assembly/deserialize/swar/integer.ts +2 -7
  45. package/assembly/deserialize/swar/string.ts +13 -15
  46. package/assembly/deserialize/swar/typedarray.ts +4 -4
  47. package/assembly/index.d.ts +29 -24
  48. package/assembly/index.ts +1362 -246
  49. package/assembly/serialize/index/arbitrary.ts +70 -4
  50. package/assembly/serialize/index/jsonarray.ts +51 -0
  51. package/assembly/serialize/index/object.ts +25 -3
  52. package/assembly/serialize/index/string.ts +1 -2
  53. package/assembly/serialize/index/typedarray.ts +1 -2
  54. package/assembly/serialize/index.ts +1 -0
  55. package/assembly/serialize/naive/array.ts +23 -34
  56. package/assembly/serialize/naive/bool.ts +0 -1
  57. package/assembly/serialize/naive/float.ts +16 -25
  58. package/assembly/serialize/naive/integer.ts +1 -5
  59. package/assembly/serialize/naive/raw.ts +1 -2
  60. package/assembly/serialize/naive/set.ts +0 -4
  61. package/assembly/serialize/naive/staticarray.ts +0 -5
  62. package/assembly/serialize/naive/string.ts +2 -5
  63. package/assembly/serialize/naive/typedarray.ts +0 -6
  64. package/assembly/serialize/simd/string.ts +1 -3
  65. package/assembly/serialize/swar/string.ts +1 -2
  66. package/assembly/util/atoi-fast.ts +4 -14
  67. package/assembly/util/bytes.ts +1 -2
  68. package/assembly/util/idofd.ts +1 -2
  69. package/assembly/util/isSpace.ts +1 -2
  70. package/assembly/util/itoa-fast.ts +6 -9
  71. package/assembly/util/nextPowerOf2.ts +1 -2
  72. package/assembly/util/parsefloat-fast.ts +3 -5
  73. package/assembly/util/ptrToStr.ts +1 -2
  74. package/assembly/util/scanValueEndSimd.ts +54 -16
  75. package/assembly/util/scanValueEndSwar.ts +67 -25
  76. package/assembly/util/scientific.ts +5 -8
  77. package/assembly/util/snp.ts +1 -2
  78. package/assembly/util/swar-int.ts +5 -10
  79. package/assembly/util/swar.ts +2 -4
  80. package/lib/as-bs.ts +23 -45
  81. package/package.json +14 -7
  82. package/transform/lib/index.js +108 -64
  83. package/transform/lib/types.d.ts +2 -1
  84. package/transform/lib/types.js +3 -0
  85. package/assembly/util/dragonbox-cache.ts +0 -445
  86. 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 (firstChar == QUOTE) {
16
- return JSON.Value.from(deserializeString(srcStart, srcEnd));
17
- } else if (firstChar == BRACE_LEFT) {
18
- return JSON.Value.from(deserializeObject(srcStart, srcEnd, 0));
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
- // @ts-ignore: inline
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
- // @ts-ignore: inline
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
- // @ts-expect-error: @inline is a valid decorator
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
- // @ts-expect-error: @inline is a valid decorator
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,
@@ -1 +1,6 @@
1
- export { deserializeObject } from "../naive/object";
1
+ export {
2
+ deserializeObject,
3
+ deserializeJsonArray,
4
+ setParseSrc,
5
+ getParseSrc,
6
+ } from "../naive/object";
@@ -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
- // @ts-expect-error: @inline is a valid decorator
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
- // @ts-expect-error: @inline is a valid decorator
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 no leading zeros, fraction,
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 no separate scan to find the closing brace.
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 no separate scan to find the closing brace.
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
- out.push(JSON.__deserialize<valueof<T>>(lastIndex, (srcStart += 2)));
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
- // @ts-ignore: inline
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
- // @ts-ignore: inline
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
- // @ts-ignore: inline
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
- // @ts-ignore: inline
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,
@@ -1,7 +1,6 @@
1
1
  import { atoi } from "../../util/atoi";
2
2
 
3
- // @ts-ignore: inline
4
- @inline export function deserializeInteger_NAIVE<T extends number>(
3
+ export function deserializeInteger_NAIVE<T extends number>(
5
4
  srcStart: usize,
6
5
  srcEnd: usize,
7
6
  ): T {
@@ -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
- // @ts-ignore: Decorator is valid here
14
- @inline function deserializeMapKey<T>(start: usize, end: usize): T {
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 valueof<T> is JSON.Value in this branch
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
- // @ts-ignore: Decorator is valid here
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 clear it before repopulating. Fresh maps
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
- // @ts-ignore: inline
20
- @inline const TRUE_WORD: u64 = 28429475166421108;
21
- // "alse" — the tail of "false", read at +2 so the leading 'f' is skipped.
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
- // @ts-ignore: inline
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 out = changetype<JSON.Obj>(dst || changetype<usize>(new JSON.Obj()));
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 = instantiate<JSON.Value[]>();
85
- parseValueEnd = parseArrayBody(arr, srcStart + 2, srcEnd);
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
- out.push(parseValue(srcStart, srcEnd));
136
- srcStart = parseValueEnd;
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
- out.appendRaw(keyStart, keyEnd, parseValue(srcStart, srcEnd));
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
- // @ts-ignore: inline
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
- // @ts-expect-error: Decorator valid here
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
- // @ts-expect-error: Decorator valid here
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
- // @ts-ignore: inline
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
- // @ts-ignore: inline
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
- // @ts-ignore: inline
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 the common escape-free case never touches `bs`.
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 one
205
- // call per field matching the non-inline SWAR/SIMD field deserializers.
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,
@@ -1,7 +1,6 @@
1
1
  import { atoi } from "../../util/atoi";
2
2
 
3
- // @ts-ignore: inline
4
- @inline export function deserializeUnsigned_NAIVE<T extends number>(
3
+ export function deserializeUnsigned_NAIVE<T extends number>(
5
4
  srcStart: usize,
6
5
  srcEnd: usize,
7
6
  ): T {
@@ -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
- // @ts-expect-error: decorators valid here
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
- // @ts-expect-error: decorators valid here
62
- @inline function storeUnsignedInteger<T extends number[]>(
57
+ function storeUnsignedInteger<T extends number[]>(
63
58
  slot: usize,
64
59
  value: u64,
65
60
  ): void {