json-as 1.4.0 → 1.6.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 (108) hide show
  1. package/CHANGELOG.md +54 -29
  2. package/README.md +126 -33
  3. package/assembly/custom/chars.ts +39 -78
  4. package/assembly/deserialize/error.ts +21 -0
  5. package/assembly/deserialize/index/arbitrary.ts +50 -11
  6. package/assembly/deserialize/index/array.ts +2 -1
  7. package/assembly/deserialize/index/bool.ts +1 -1
  8. package/assembly/deserialize/index/float.ts +2 -4
  9. package/assembly/deserialize/index/integer.ts +2 -4
  10. package/assembly/deserialize/index/object.ts +9 -1
  11. package/assembly/deserialize/index/string.ts +39 -7
  12. package/assembly/deserialize/index/unsigned.ts +2 -4
  13. package/assembly/deserialize/index.ts +0 -1
  14. package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
  15. package/assembly/deserialize/naive/array/array.ts +1 -0
  16. package/assembly/deserialize/naive/array/bool.ts +21 -11
  17. package/assembly/deserialize/naive/array/box.ts +13 -33
  18. package/assembly/deserialize/naive/array/float.ts +23 -13
  19. package/assembly/deserialize/naive/array/integer.ts +34 -22
  20. package/assembly/deserialize/naive/array/map.ts +1 -1
  21. package/assembly/deserialize/naive/array/object.ts +2 -1
  22. package/assembly/deserialize/naive/array/string.ts +29 -16
  23. package/assembly/deserialize/naive/array/struct.ts +19 -1
  24. package/assembly/deserialize/naive/bool.ts +18 -10
  25. package/assembly/deserialize/naive/date.ts +1 -2
  26. package/assembly/deserialize/naive/float.ts +3 -9
  27. package/assembly/deserialize/naive/integer.ts +1 -2
  28. package/assembly/deserialize/naive/map.ts +342 -43
  29. package/assembly/deserialize/naive/object.ts +250 -55
  30. package/assembly/deserialize/naive/raw.ts +10 -2
  31. package/assembly/deserialize/naive/set.ts +2 -5
  32. package/assembly/deserialize/naive/staticarray.ts +4 -4
  33. package/assembly/deserialize/naive/string.ts +46 -60
  34. package/assembly/deserialize/naive/unsigned.ts +1 -2
  35. package/assembly/deserialize/parseMode.ts +216 -0
  36. package/assembly/deserialize/simd/array/integer.ts +8 -10
  37. package/assembly/deserialize/simd/float.ts +38 -34
  38. package/assembly/deserialize/simd/integer.ts +2 -7
  39. package/assembly/deserialize/simd/string.ts +84 -39
  40. package/assembly/deserialize/string-validation.ts +34 -0
  41. package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
  42. package/assembly/deserialize/swar/array/array.ts +64 -15
  43. package/assembly/deserialize/swar/array/bool.ts +15 -5
  44. package/assembly/deserialize/swar/array/box.ts +1 -2
  45. package/assembly/deserialize/swar/array/float.ts +399 -30
  46. package/assembly/deserialize/swar/array/generic.ts +13 -4
  47. package/assembly/deserialize/swar/array/integer.ts +29 -78
  48. package/assembly/deserialize/swar/array/map.ts +1 -2
  49. package/assembly/deserialize/swar/array/object.ts +12 -4
  50. package/assembly/deserialize/swar/array/raw.ts +1 -2
  51. package/assembly/deserialize/swar/array/shared.ts +7 -14
  52. package/assembly/deserialize/swar/array/string.ts +37 -13
  53. package/assembly/deserialize/swar/array/struct.ts +109 -21
  54. package/assembly/deserialize/swar/array.ts +1 -3
  55. package/assembly/deserialize/swar/float.ts +23 -21
  56. package/assembly/deserialize/swar/integer.ts +2 -7
  57. package/assembly/deserialize/swar/string.ts +81 -124
  58. package/assembly/deserialize/swar/typedarray.ts +4 -4
  59. package/assembly/index.d.ts +35 -24
  60. package/assembly/index.ts +1775 -281
  61. package/assembly/serialize/index/arbitrary.ts +70 -4
  62. package/assembly/serialize/index/bool.ts +1 -1
  63. package/assembly/serialize/index/float.ts +1 -5
  64. package/assembly/serialize/index/integer.ts +1 -1
  65. package/assembly/serialize/index/jsonarray.ts +51 -0
  66. package/assembly/serialize/index/object.ts +42 -3
  67. package/assembly/serialize/index/string.ts +1 -2
  68. package/assembly/serialize/index/typedarray.ts +1 -2
  69. package/assembly/serialize/index.ts +1 -0
  70. package/assembly/serialize/naive/array.ts +87 -34
  71. package/assembly/serialize/naive/bool.ts +0 -1
  72. package/assembly/serialize/naive/float.ts +15 -29
  73. package/assembly/serialize/naive/integer.ts +1 -5
  74. package/assembly/serialize/naive/map.ts +37 -0
  75. package/assembly/serialize/naive/raw.ts +1 -2
  76. package/assembly/serialize/naive/set.ts +0 -5
  77. package/assembly/serialize/naive/staticarray.ts +0 -6
  78. package/assembly/serialize/naive/string.ts +1 -10
  79. package/assembly/serialize/naive/typedarray.ts +0 -7
  80. package/assembly/serialize/simd/string.ts +1 -3
  81. package/assembly/serialize/swar/string.ts +1 -2
  82. package/assembly/util/atoi-fast.ts +4 -14
  83. package/assembly/util/bytes.ts +1 -2
  84. package/assembly/util/eisel-lemire.ts +179 -0
  85. package/assembly/util/idofd.ts +1 -2
  86. package/assembly/util/isSpace.ts +1 -2
  87. package/assembly/util/itoa-fast.ts +6 -9
  88. package/assembly/util/nextPowerOf2.ts +1 -2
  89. package/assembly/util/parsefloat-fast.ts +3 -5
  90. package/assembly/util/prettyWhitespaceSimd.ts +69 -0
  91. package/assembly/util/ptrToStr.ts +1 -2
  92. package/assembly/util/scanValueEndSimd.ts +83 -22
  93. package/assembly/util/scanValueEndSwar.ts +67 -25
  94. package/assembly/util/scientific.ts +5 -8
  95. package/assembly/util/snp.ts +1 -2
  96. package/assembly/util/swar-int.ts +5 -10
  97. package/assembly/util/swar.ts +2 -4
  98. package/assembly/util/validateJson.ts +338 -0
  99. package/lib/as-bs.ts +51 -51
  100. package/package.json +25 -8
  101. package/transform/lib/index.d.ts +6 -0
  102. package/transform/lib/index.js +874 -171
  103. package/transform/lib/types.d.ts +3 -1
  104. package/transform/lib/types.js +6 -0
  105. package/assembly/deserialize/index/struct.ts +0 -1
  106. package/assembly/deserialize/naive/struct.ts +0 -21
  107. package/assembly/util/dragonbox-cache.ts +0 -445
  108. package/assembly/util/dragonbox.ts +0 -652
@@ -1,18 +1,16 @@
1
1
  import { deserializeIntegerArray_NAIVE } from "../../naive/array/integer";
2
2
  import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
3
3
  import { isSpace } from "../../../util";
4
+ import { markProductionParseError } from "../../error";
4
5
  import { ensureArrayElementSlot, ensureArrayField } from "./shared";
5
6
  import { parse4Digits_PairMul } from "../../../util/swar-int";
6
7
 
7
8
  // Store helpers parameterised on the element type `E` directly, so they
8
9
  // serve both `Array<E>` and `TypedArray<E>` callers. The integer-array
9
10
  // callers below all pass `valueof<T>` and AS folds the resulting tower of
10
- // `sizeof<E>` comparisons at compile time same codegen as the prior
11
+ // `sizeof<E>` comparisons at compile time - same codegen as the prior
11
12
  // `T extends number[]` version, but reusable from `swar/typedarray.ts`.
12
- @inline function storeSignedIntegerE<E extends number>(
13
- slot: usize,
14
- value: i64,
15
- ): void {
13
+ function storeSignedIntegerE<E extends number>(slot: usize, value: i64): void {
16
14
  if (sizeof<E>() == sizeof<i8>()) {
17
15
  store<i8>(slot, <i8>value);
18
16
  } else if (sizeof<E>() == sizeof<i16>()) {
@@ -26,8 +24,7 @@ import { parse4Digits_PairMul } from "../../../util/swar-int";
26
24
  }
27
25
  }
28
26
 
29
-
30
- @inline function storeUnsignedIntegerE<E extends number>(
27
+ function storeUnsignedIntegerE<E extends number>(
31
28
  slot: usize,
32
29
  value: u64,
33
30
  ): void {
@@ -44,7 +41,7 @@ import { parse4Digits_PairMul } from "../../../util/swar-int";
44
41
  }
45
42
  }
46
43
 
47
- // The four parse helpers below take a `slot` pointer (`writePtr`) and store
44
+ // The parse helpers below take a `slot` pointer (`writePtr`) and store
48
45
  // the value directly via `store<valueof<T>>(slot, ...)`. The outer dispatcher
49
46
  // owns the array's `out.length = maxElements` pre-allocation and the
50
47
  // `writePtr` advance, so the per-element `Array.push` capacity check and
@@ -53,57 +50,6 @@ import { parse4Digits_PairMul } from "../../../util/swar-int";
53
50
  // Parsers are also E-parameterised so they're shareable with
54
51
  // `swar/typedarray.ts`. The body is byte-identical to the prior version
55
52
  // modulo s/valueof<T>/E/.
56
- export function parseSignedIntegerScalar<E extends number>(
57
- srcStart: usize,
58
- srcEnd: usize,
59
- slot: usize,
60
- ): usize {
61
- let negative = false;
62
- let code = load<u16>(srcStart);
63
- if (code == 45) {
64
- negative = true;
65
- srcStart += 2;
66
- if (srcStart >= srcEnd) return 0;
67
- code = load<u16>(srcStart);
68
- }
69
-
70
- let digit = <u32>code - 48;
71
- if (digit > 9) return 0;
72
-
73
- let value: u64 = digit;
74
- srcStart += 2;
75
- while (srcStart < srcEnd) {
76
- digit = <u32>load<u16>(srcStart) - 48;
77
- if (digit > 9) break;
78
- value = value * 10 + digit;
79
- srcStart += 2;
80
- }
81
-
82
- storeSignedIntegerE<E>(slot, negative ? -(<i64>value) : <i64>value);
83
- return srcStart;
84
- }
85
-
86
- export function parseUnsignedIntegerScalar<E extends number>(
87
- srcStart: usize,
88
- srcEnd: usize,
89
- slot: usize,
90
- ): usize {
91
- let digit = <u32>load<u16>(srcStart) - 48;
92
- if (digit > 9) return 0;
93
-
94
- let value: u64 = digit;
95
- srcStart += 2;
96
- while (srcStart < srcEnd) {
97
- digit = <u32>load<u16>(srcStart) - 48;
98
- if (digit > 9) break;
99
- value = value * 10 + digit;
100
- srcStart += 2;
101
- }
102
-
103
- storeUnsignedIntegerE<E>(slot, value);
104
- return srcStart;
105
- }
106
-
107
53
  export function parseSignedIntegerSWAR<E extends number>(
108
54
  srcStart: usize,
109
55
  srcEnd: usize,
@@ -211,11 +157,7 @@ export function parseUnsignedIntegerSWAR<E extends number>(
211
157
  return srcStart;
212
158
  }
213
159
 
214
-
215
- @inline function skipIntegerArrayWhitespace(
216
- srcStart: usize,
217
- srcEnd: usize,
218
- ): usize {
160
+ function skipIntegerArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
219
161
  while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) {
220
162
  srcStart += 2;
221
163
  }
@@ -235,7 +177,8 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
235
177
 
236
178
  out.length = 0;
237
179
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) {
238
- throw new Error("Failed to parse JSON!");
180
+ markProductionParseError();
181
+ return changetype<T>(0);
239
182
  }
240
183
 
241
184
  srcStart += 2;
@@ -303,7 +246,8 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
303
246
  break;
304
247
  }
305
248
 
306
- throw new Error("Failed to parse JSON!");
249
+ markProductionParseError();
250
+ return changetype<T>(0);
307
251
  }
308
252
 
309
253
  function deserializeIntegerArrayImpl<T extends number[]>(
@@ -316,7 +260,10 @@ function deserializeIntegerArrayImpl<T extends number[]>(
316
260
  dst || changetype<usize>(instantiate<T>()),
317
261
  );
318
262
  const originalSrcStart = srcStart;
319
- const reusableLength = out.length;
263
+ const reusableLength = load<i32>(
264
+ changetype<usize>(out),
265
+ offsetof<T>("length_"),
266
+ );
320
267
 
321
268
  if (useSWAR && reusableLength != 0) {
322
269
  const dataStart = out.dataStart;
@@ -456,9 +403,11 @@ function deserializeIntegerArrayImpl<T extends number[]>(
456
403
 
457
404
  if (isSigned<valueof<T>>()) {
458
405
  while (srcStart < srcEnd) {
459
- const next = useSWAR
460
- ? parseSignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, writePtr)
461
- : parseSignedIntegerScalar<valueof<T>>(srcStart, srcEnd, writePtr);
406
+ const next = parseSignedIntegerSWAR<valueof<T>>(
407
+ srcStart,
408
+ srcEnd,
409
+ writePtr,
410
+ );
462
411
  if (!next) break;
463
412
  writePtr += elementSize;
464
413
  srcStart = next;
@@ -477,9 +426,11 @@ function deserializeIntegerArrayImpl<T extends number[]>(
477
426
  }
478
427
  } else {
479
428
  while (srcStart < srcEnd) {
480
- const next = useSWAR
481
- ? parseUnsignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, writePtr)
482
- : parseUnsignedIntegerScalar<valueof<T>>(srcStart, srcEnd, writePtr);
429
+ const next = parseUnsignedIntegerSWAR<valueof<T>>(
430
+ srcStart,
431
+ srcEnd,
432
+ writePtr,
433
+ );
483
434
  if (!next) break;
484
435
  writePtr += elementSize;
485
436
  srcStart = next;
@@ -661,7 +612,7 @@ function deserializeIntegerArrayBody<T extends number[]>(
661
612
  const slot = ensureArrayElementSlot<T>(out, index);
662
613
  // Inline the array-optimized SWAR parser directly. The top-level
663
614
  // (`deserializeIntegerArrayImpl`) and the typed-array path
664
- // (`swar/typedarray.ts`) already call these having the field path
615
+ // (`swar/typedarray.ts`) already call these - having the field path
665
616
  // call them too means the per-element parser is identical across all
666
617
  // three call sites (parse4 + scalar fold for both signed and
667
618
  // unsigned, narrow-lane special case for i8/u8/i16/u16).
@@ -669,7 +620,7 @@ function deserializeIntegerArrayBody<T extends number[]>(
669
620
  // Why not `deserializeUnsignedField_SWAR` (which uses parse8 + scalar)?
670
621
  // That tuning targets the struct-single-field path where the digit
671
622
  // run is one aligned token. In an array, mixed element widths cause
672
- // parse8 to fail-and-retry at element boundaries see u32-64mib's
623
+ // parse8 to fail-and-retry at element boundaries - see u32-64mib's
673
624
  // 23% regression when parse8 was tried in the array path.
674
625
  srcStart = isSigned<valueof<T>>()
675
626
  ? parseSignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, slot)
@@ -694,11 +645,11 @@ function deserializeIntegerArrayBody<T extends number[]>(
694
645
  }
695
646
  } while (false);
696
647
 
697
- throw new Error("Failed to parse JSON!");
648
+ markProductionParseError();
649
+ return 0;
698
650
  }
699
651
 
700
-
701
- @inline export function deserializeIntegerArrayField<T extends number[]>(
652
+ export function deserializeIntegerArrayField<T extends number[]>(
702
653
  srcStart: usize,
703
654
  srcEnd: usize,
704
655
  fieldPtr: usize,
@@ -1,8 +1,7 @@
1
1
  import { deserializeGenericArrayBody } from "./generic";
2
2
  import { ensureArrayField } from "./shared";
3
3
 
4
-
5
- @inline export function deserializeMapArrayField<T extends Map<any, any>[]>(
4
+ export function deserializeMapArrayField<T extends Map<any, any>[]>(
6
5
  srcStart: usize,
7
6
  srcEnd: usize,
8
7
  fieldPtr: usize,
@@ -13,6 +13,12 @@ function deserializeObjectArrayBody<T extends unknown[]>(
13
13
  out: T,
14
14
  ): usize {
15
15
  let index = 0;
16
+ const reusableLength = load<i32>(
17
+ changetype<usize>(out),
18
+ offsetof<T>("length_"),
19
+ );
20
+ const reusableDataStart = out.dataStart;
21
+ const elementSize = sizeof<valueof<T>>();
16
22
 
17
23
  do {
18
24
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
@@ -25,7 +31,10 @@ function deserializeObjectArrayBody<T extends unknown[]>(
25
31
  }
26
32
 
27
33
  while (srcStart < srcEnd) {
28
- const slot = ensureArrayElementSlot<T>(out, index);
34
+ const slot =
35
+ index < reusableLength
36
+ ? reusableDataStart + <usize>index * elementSize
37
+ : ensureArrayElementSlot<T>(out, index);
29
38
  const valueStart = srcStart;
30
39
  const valueEnd = scanValueEnd(valueStart, srcEnd);
31
40
  if (!valueEnd) break;
@@ -48,7 +57,7 @@ function deserializeObjectArrayBody<T extends unknown[]>(
48
57
  }
49
58
  if (code == BRACKET_RIGHT) {
50
59
  const nextLen = index + 1;
51
- if (out.length != nextLen) out.length = nextLen;
60
+ if (reusableLength != nextLen) out.length = nextLen;
52
61
  return srcStart + 2;
53
62
  }
54
63
  break;
@@ -58,8 +67,7 @@ function deserializeObjectArrayBody<T extends unknown[]>(
58
67
  throw new Error("Failed to parse JSON!");
59
68
  }
60
69
 
61
-
62
- @inline export function deserializeObjectArrayField<T extends unknown[]>(
70
+ export function deserializeObjectArrayField<T extends unknown[]>(
63
71
  srcStart: usize,
64
72
  srcEnd: usize,
65
73
  fieldPtr: usize,
@@ -2,8 +2,7 @@ import { JSON } from "../../..";
2
2
  import { deserializeGenericArrayBody } from "./generic";
3
3
  import { ensureArrayField } from "./shared";
4
4
 
5
-
6
- @inline export function deserializeRawArrayField(
5
+ export function deserializeRawArrayField(
7
6
  srcStart: usize,
8
7
  srcEnd: usize,
9
8
  fieldPtr: usize,
@@ -10,16 +10,12 @@ import {
10
10
  import { isSpace } from "../../../util";
11
11
 
12
12
  /** Advance past JSON whitespace (space, tab, LF, CR). */
13
- // @ts-expect-error: @inline is a valid decorator
14
- @inline export function skipWhitespace(srcStart: usize, srcEnd: usize): usize {
13
+ export function skipWhitespace(srcStart: usize, srcEnd: usize): usize {
15
14
  while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
16
15
  return srcStart;
17
16
  }
18
17
 
19
-
20
- @inline export function ensureArrayField<T extends Array<any>>(
21
- fieldPtr: usize,
22
- ): T {
18
+ export function ensureArrayField<T extends Array<any>>(fieldPtr: usize): T {
23
19
  let out = load<T>(fieldPtr);
24
20
  if (!changetype<usize>(out)) {
25
21
  out = changetype<T>(instantiate<T>());
@@ -28,8 +24,7 @@ import { isSpace } from "../../../util";
28
24
  return out;
29
25
  }
30
26
 
31
-
32
- @inline export function ensureArrayFieldAt<T extends Array<any>>(
27
+ export function ensureArrayFieldAt<T extends Array<any>>(
33
28
  dstObj: usize,
34
29
  dstOffset: usize,
35
30
  ): T {
@@ -41,8 +36,7 @@ import { isSpace } from "../../../util";
41
36
  return out;
42
37
  }
43
38
 
44
-
45
- @inline function backslashOrQuoteMask(block: u64): u64 {
39
+ function backslashOrQuoteMask(block: u64): u64 {
46
40
  const b = block ^ 0x005c_005c_005c_005c;
47
41
  const q = block ^ 0x0022_0022_0022_0022;
48
42
  return (
@@ -51,8 +45,7 @@ import { isSpace } from "../../../util";
51
45
  );
52
46
  }
53
47
 
54
-
55
- @inline export function ensureArrayElementSlot<T extends Array<any>>(
48
+ export function ensureArrayElementSlot<T extends Array<any>>(
56
49
  out: T,
57
50
  index: i32,
58
51
  ): usize {
@@ -60,7 +53,7 @@ import { isSpace } from "../../../util";
60
53
  if (out.length < nextLength) {
61
54
  // Grow via `push`, not `out.length = nextLength`. AS's `length=`
62
55
  // setter calls `ensureCapacity(canGrow=false)` which reallocates to
63
- // *exactly* the requested size fine for a one-shot resize, but
56
+ // *exactly* the requested size - fine for a one-shot resize, but
64
57
  // catastrophic in the per-element loop (every push triggers a full
65
58
  // copy of the array, giving O(N²) growth cost). `push` goes through
66
59
  // `canGrow=true`, doubling capacity geometrically as needed.
@@ -79,7 +72,7 @@ import { isSpace } from "../../../util";
79
72
 
80
73
  export function scanQuotedValueEnd_SWAR(srcStart: usize, srcEnd: usize): usize {
81
74
  srcStart += 2;
82
- const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
75
+ const srcEnd8 = srcEnd - 8;
83
76
 
84
77
  while (srcStart <= srcEnd8) {
85
78
  let mask = backslashOrQuoteMask(load<u64>(srcStart));
@@ -7,12 +7,9 @@ import {
7
7
  import { isSpace } from "../../../util";
8
8
  import { ensureArrayElementSlot, ensureArrayField } from "./shared";
9
9
  import { deserializeStringField_SWAR } from "../string";
10
+ import { markProductionParseError } from "../../error";
10
11
 
11
-
12
- @inline function skipStringArrayWhitespace(
13
- srcStart: usize,
14
- srcEnd: usize,
15
- ): usize {
12
+ function skipStringArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
16
13
  while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
17
14
  return srcStart;
18
15
  }
@@ -23,6 +20,15 @@ function deserializeStringArrayBody<T extends string[]>(
23
20
  out: T,
24
21
  ): usize {
25
22
  let index = 0;
23
+ // Reused struct fields normally retain their array backing store. Cache the
24
+ // original slot range so the common path avoids an Array.length load and
25
+ // growth branch for every string.
26
+ const reusableLength = load<i32>(
27
+ changetype<usize>(out),
28
+ offsetof<T>("length_"),
29
+ );
30
+ const reusableDataStart = out.dataStart;
31
+ const elementSize = sizeof<valueof<T>>();
26
32
 
27
33
  do {
28
34
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
@@ -35,13 +41,16 @@ function deserializeStringArrayBody<T extends string[]>(
35
41
  }
36
42
 
37
43
  while (srcStart < srcEnd) {
38
- const slot = ensureArrayElementSlot<T>(out, index);
44
+ const slot =
45
+ index < reusableLength
46
+ ? reusableDataStart + <usize>index * elementSize
47
+ : ensureArrayElementSlot<T>(out, index);
39
48
  // Null fast path for `(string | null)[]`. "null" is 4 UTF-16 chars
40
49
  // = 8 bytes, exactly one u64 compare. Store 0 (the AS null reference)
41
50
  // and skip 8 bytes.
42
51
  //
43
52
  // We accept null tokens unconditionally rather than gating on
44
- // `isNullable<valueof<T>>()` AS's `valueof` of a nullable-array
53
+ // `isNullable<valueof<T>>()` - AS's `valueof` of a nullable-array
45
54
  // element type doesn't always preserve the nullable marker through
46
55
  // the dispatcher's `<T>` cast, so the gate would mis-fire for the
47
56
  // very case it's meant to handle. The runtime cost on plain
@@ -70,18 +79,18 @@ function deserializeStringArrayBody<T extends string[]>(
70
79
  }
71
80
  if (code == BRACKET_RIGHT) {
72
81
  const nextLen = index + 1;
73
- if (out.length != nextLen) out.length = nextLen;
82
+ if (reusableLength != nextLen) out.length = nextLen;
74
83
  return srcStart + 2;
75
84
  }
76
85
  break;
77
86
  }
78
87
  } while (false);
79
88
 
80
- throw new Error("Failed to parse JSON!");
89
+ markProductionParseError();
90
+ return 0;
81
91
  }
82
92
 
83
-
84
- @inline export function deserializeStringArrayField<T extends string[]>(
93
+ export function deserializeStringArrayField<T extends string[]>(
85
94
  srcStart: usize,
86
95
  srcEnd: usize,
87
96
  fieldPtr: usize,
@@ -111,6 +120,11 @@ export function deserializeStringArray_SWAR<T extends string[]>(
111
120
  dst || changetype<usize>(instantiate<T>()),
112
121
  );
113
122
 
123
+ // Top-level exact-range entry. Field arrays use the cursor-returning body
124
+ // above, so trimming here preserves valid document whitespace without
125
+ // touching the hot generated-struct path.
126
+ while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
127
+
114
128
  // Worst-case sizing: shortest possible element is `""` followed by `,` =
115
129
  // 6 UTF-16 bytes, so `(srcLen + 5) / 6` upper-bounds the count. Clamp to
116
130
  // AS's BLOCK_MAXSIZE / sizeof<string ref> = (1<<28) - 4 elements; payloads
@@ -131,11 +145,16 @@ export function deserializeStringArray_SWAR<T extends string[]>(
131
145
  // Caller guarantees srcStart is at the opening `[`.
132
146
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) {
133
147
  out.length = 0;
134
- return out;
148
+ markProductionParseError();
149
+ return changetype<T>(0);
135
150
  }
136
151
  srcStart += 2;
137
152
  srcStart = skipStringArrayWhitespace(srcStart, srcEnd);
138
153
  if (srcStart < srcEnd && load<u16>(srcStart) == BRACKET_RIGHT) {
154
+ if (srcStart + 2 != srcEnd) {
155
+ markProductionParseError();
156
+ return changetype<T>(0);
157
+ }
139
158
  out.length = 0;
140
159
  return out;
141
160
  }
@@ -164,6 +183,10 @@ export function deserializeStringArray_SWAR<T extends string[]>(
164
183
  continue;
165
184
  }
166
185
  if (code == BRACKET_RIGHT) {
186
+ if (srcStart + 2 != srcEnd) {
187
+ markProductionParseError();
188
+ return changetype<T>(0);
189
+ }
167
190
  const finalLen = i32(<usize>(writePtr - dataStart) / elementSize);
168
191
  if (out.length != finalLen) out.length = finalLen;
169
192
  return out;
@@ -171,5 +194,6 @@ export function deserializeStringArray_SWAR<T extends string[]>(
171
194
  break;
172
195
  }
173
196
 
174
- throw new Error("Failed to parse JSON!");
197
+ markProductionParseError();
198
+ return changetype<T>(0);
175
199
  }
@@ -1,12 +1,33 @@
1
- import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
1
+ import {
2
+ BRACE_LEFT,
3
+ BRACKET_LEFT,
4
+ BRACKET_RIGHT,
5
+ COMMA,
6
+ } from "../../../custom/chars";
2
7
  import { isSpace } from "../../../util";
3
- import { ensureArrayElementSlot, ensureArrayField } from "./shared";
8
+ import {
9
+ ensureArrayElementSlot,
10
+ ensureArrayField,
11
+ scanValueEnd,
12
+ } from "./shared";
13
+ import { markProductionParseError } from "../../error";
4
14
 
5
15
 
6
- @inline function skipStructArrayWhitespace(
7
- srcStart: usize,
8
- srcEnd: usize,
9
- ): usize {
16
+ @inline
17
+ function skipStructArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
18
+ // JSON.stringify(..., null, 2) at a root struct-array boundary emits exactly
19
+ // LF + two spaces + the next object (or LF + closing bracket). Keep that
20
+ // overwhelmingly common shape to two scalar loads; nested/foreign layouts
21
+ // retain the fully general fallback below.
22
+ if (
23
+ ASC_FEATURE_SIMD &&
24
+ srcStart + 8 <= srcEnd &&
25
+ load<u16>(srcStart) == 10 &&
26
+ load<u32>(srcStart, 2) == 0x0020_0020
27
+ ) {
28
+ const next = load<u16>(srcStart, 6);
29
+ if (next == BRACE_LEFT || next == BRACKET_RIGHT) return srcStart + 6;
30
+ }
10
31
  while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
11
32
  return srcStart;
12
33
  }
@@ -32,11 +53,22 @@ function deserializeStructArrayBody<T extends unknown[]>(
32
53
  out: T,
33
54
  ): usize {
34
55
  let index = 0;
56
+ const reusableLength = load<i32>(
57
+ changetype<usize>(out),
58
+ offsetof<T>("length_"),
59
+ );
60
+ const reusableDataStart = out.dataStart;
61
+ const elementSize = sizeof<valueof<T>>();
62
+ let pretty = false;
35
63
 
36
64
  do {
37
65
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
38
66
  srcStart += 2;
39
- srcStart = skipStructArrayWhitespace(srcStart, srcEnd);
67
+ if (srcStart < srcEnd && load<u16>(srcStart) != BRACE_LEFT) {
68
+ const code = load<u16>(srcStart);
69
+ pretty = ASC_FEATURE_SIMD && (code == 10 || code == 13);
70
+ srcStart = skipStructArrayWhitespace(srcStart, srcEnd);
71
+ }
40
72
  if (srcStart >= srcEnd) break;
41
73
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
42
74
  out.length = 0;
@@ -44,7 +76,10 @@ function deserializeStructArrayBody<T extends unknown[]>(
44
76
  }
45
77
 
46
78
  while (srcStart < srcEnd) {
47
- const slot = ensureArrayElementSlot<T>(out, index);
79
+ const slot =
80
+ index < reusableLength
81
+ ? reusableDataStart + <usize>index * elementSize
82
+ : ensureArrayElementSlot<T>(out, index);
48
83
  let value = load<valueof<T>>(slot);
49
84
  if (changetype<usize>(value) == 0) {
50
85
  value = changetype<valueof<T>>(
@@ -58,31 +93,70 @@ function deserializeStructArrayBody<T extends unknown[]>(
58
93
  store<valueof<T>>(slot, value);
59
94
  }
60
95
 
96
+ const valueStart = srcStart;
61
97
  let next: usize = 0;
62
98
  if (
63
99
  // @ts-ignore: supplied by transform
64
100
  isDefined(changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST)
65
101
  ) {
102
+ // Omit the explicit type arg so AS infers it from `value` — passing
103
+ // `<valueof<T>>` explicitly collides when the element type is itself
104
+ // generic (e.g. GenericTest<string>) because AS resolves `T` against
105
+ // the element class's own generic rather than this function's `T`.
66
106
  // @ts-ignore: supplied by transform
67
- next = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST<
68
- valueof<T>
69
- >(srcStart, srcEnd, value);
107
+ // @ts-ignore: supplied by transform in SIMD builds
108
+ if (
109
+ pretty &&
110
+ isDefined(
111
+ changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST_PRETTY,
112
+ )
113
+ ) {
114
+ // @ts-ignore: supplied by transform
115
+ next = changetype<nonnull<valueof<T>>>(
116
+ value,
117
+ ).__DESERIALIZE_FAST_PRETTY(srcStart, srcEnd, value);
118
+ } else {
119
+ next = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST(
120
+ srcStart,
121
+ srcEnd,
122
+ value,
123
+ );
124
+ }
70
125
  }
71
126
  if (!next) {
127
+ // __DESERIALIZE_SLOW requires srcEnd to point just past the closing
128
+ // `}` of *this* element (it asserts srcEnd-2 == `}`). Scan the value
129
+ // boundary first, then re-initialize the object so any partial state
130
+ // from the failed FAST attempt is cleared.
131
+ const valueEnd = scanValueEnd(valueStart, srcEnd);
132
+ if (!valueEnd) break;
72
133
  // @ts-ignore: supplied by transform
73
- next = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_SLOW<
74
- valueof<T>
75
- >(srcStart, srcEnd, value);
134
+ if (isDefined(changetype<nonnull<valueof<T>>>(value).__INITIALIZE)) {
135
+ // @ts-ignore: supplied by transform
136
+ changetype<nonnull<valueof<T>>>(value).__INITIALIZE();
137
+ }
138
+ // @ts-ignore: supplied by transform
139
+ changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_SLOW(
140
+ valueStart,
141
+ valueEnd,
142
+ value,
143
+ );
144
+ next = valueEnd;
76
145
  }
77
146
  if (!next) break;
78
147
  srcStart = next;
79
- srcStart = skipStructArrayWhitespace(srcStart, srcEnd);
80
148
  if (srcStart >= srcEnd) break;
81
149
 
82
- const code = load<u16>(srcStart);
150
+ let code = load<u16>(srcStart);
151
+ if (code != COMMA && code != BRACKET_RIGHT) {
152
+ srcStart = skipStructArrayWhitespace(srcStart, srcEnd);
153
+ if (srcStart >= srcEnd) break;
154
+ code = load<u16>(srcStart);
155
+ }
83
156
  if (code == COMMA) {
84
157
  srcStart += 2;
85
- srcStart = skipStructArrayWhitespace(srcStart, srcEnd);
158
+ if (srcStart < srcEnd && load<u16>(srcStart) != BRACE_LEFT)
159
+ srcStart = skipStructArrayWhitespace(srcStart, srcEnd);
86
160
  index++;
87
161
  continue;
88
162
  }
@@ -91,18 +165,32 @@ function deserializeStructArrayBody<T extends unknown[]>(
91
165
  // right length (e.g. canada-style geometry rings whose count
92
166
  // matches a previous parse).
93
167
  const nextLen = index + 1;
94
- if (out.length != nextLen) out.length = nextLen;
168
+ if (reusableLength != nextLen) out.length = nextLen;
95
169
  return srcStart + 2;
96
170
  }
97
171
  break;
98
172
  }
99
173
  } while (false);
100
174
 
101
- throw new Error("Failed to parse JSON!");
175
+ markProductionParseError();
176
+ return 0;
177
+ }
178
+ export function deserializeStructArray_SWAR<T extends unknown[]>(
179
+ srcStart: usize,
180
+ srcEnd: usize,
181
+ dst: usize,
182
+ ): T {
183
+ while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
184
+ const out = changetype<nonnull<T>>(
185
+ dst || changetype<usize>(instantiate<T>()),
186
+ );
187
+ const end = deserializeStructArrayBody<T>(srcStart, srcEnd, out);
188
+ if (end == srcEnd) return out;
189
+ markProductionParseError();
190
+ return changetype<T>(0);
102
191
  }
103
192
 
104
-
105
- @inline export function deserializeStructArrayField<T extends unknown[]>(
193
+ export function deserializeStructArrayField<T extends unknown[]>(
106
194
  srcStart: usize,
107
195
  srcEnd: usize,
108
196
  fieldPtr: usize,
@@ -13,9 +13,7 @@ import { deserializeStringArrayField } from "./array/string";
13
13
  import { deserializeStructArrayField } from "./array/struct";
14
14
 
15
15
  export { deserializeArrayField as deserializeArrayField_SWAR };
16
-
17
-
18
- @inline export function deserializeArrayField<T extends unknown[]>(
16
+ export function deserializeArrayField<T extends unknown[]>(
19
17
  srcStart: usize,
20
18
  srcEnd: usize,
21
19
  dstObj: usize,