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
@@ -5,6 +5,9 @@ import { QUOTE } from "../../custom/chars";
5
5
  import { BACK_SLASH } from "../../custom/chars";
6
6
  import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
7
7
  import { hex4_to_u16_swar } from "../../util/swar";
8
+ import { markProductionParseError } from "../error";
9
+ import { isValidStringEscape } from "../string-validation";
10
+ import { probeStringFieldTrace, recordStringFieldTrace } from "../parseMode";
8
11
 
9
12
  // @ts-expect-error: @lazy is a valid decorator
10
13
  @lazy const SPLAT_5C = i16x8.splat(0x5c); // \
@@ -63,13 +66,8 @@ import { hex4_to_u16_swar } from "../../util/swar";
63
66
  * @param dst buffer to write to
64
67
  * @returns number of bytes written
65
68
  */
66
- // @ts-expect-error: @inline is a valid decorator
67
- @inline function copyStringFromSource_SIMD(
68
- srcStart: usize,
69
- byteLength: usize,
70
- ): string {
69
+ function copyStringFromSource_SIMD(srcStart: usize, byteLength: usize): string {
71
70
  if (byteLength == 0) return changetype<string>("");
72
- // @ts-expect-error: __new is a runtime builtin
73
71
  const out = __new(byteLength, idof<string>());
74
72
  memory.copy(out, srcStart, byteLength);
75
73
  return changetype<string>(out);
@@ -106,11 +104,7 @@ function writeStringToField_SIMD(
106
104
  // handling (no closing-quote search). Same HYBRID strategy as the field path
107
105
  // (see deserializeEscapedStringField_SIMD): escape blocks use a free
108
106
  // whole-block v128 store for the plain prefix; clean runs stream the first
109
- // block then bulk-memcpy the remainder. Output is sliced out of `bs` scratch.
110
- //
111
- // Deliberately NOT @inline: cold escape path. Inlining the nested-loop v128
112
- // body at every call site explodes `--converge` compile time on large schemas
113
- // for no runtime gain (one call per escaped string).
107
+
114
108
  function deserializeEscapedString_SIMD(
115
109
  payloadStart: usize,
116
110
  escapeStart: usize,
@@ -125,7 +119,7 @@ function deserializeEscapedString_SIMD(
125
119
  }
126
120
 
127
121
  let srcStart = escapeStart;
128
- const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
122
+ const srcEnd16 = srcEnd - 16;
129
123
 
130
124
  while (srcStart <= srcEnd16) {
131
125
  const block = load<v128>(srcStart);
@@ -159,6 +153,11 @@ function deserializeEscapedString_SIMD(
159
153
  const laneIdx = usize(ctz(mask) << 1);
160
154
  bs.offset += laneIdx;
161
155
  const srcIdx = srcStart + laneIdx;
156
+ if (!isValidStringEscape(srcIdx, srcEnd)) {
157
+ bs.offset = bs.buffer + outStart;
158
+ markProductionParseError();
159
+ return changetype<string>(0);
160
+ }
162
161
  const code = load<u16>(srcIdx, 2);
163
162
  if (code !== 0x75) {
164
163
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
@@ -180,6 +179,11 @@ function deserializeEscapedString_SIMD(
180
179
  srcStart += 2;
181
180
  continue;
182
181
  }
182
+ if (!isValidStringEscape(srcStart, srcEnd)) {
183
+ bs.offset = bs.buffer + outStart;
184
+ markProductionParseError();
185
+ return changetype<string>(0);
186
+ }
183
187
  const code = load<u16>(srcStart, 2);
184
188
  if (code !== 0x75) {
185
189
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
@@ -254,18 +258,14 @@ export function deserializeString_SIMD(srcStart: usize, srcEnd: usize): string {
254
258
  // reused `bs` scratch buffer, then written once via writeStringToField_SIMD.
255
259
  //
256
260
  // Strategy (validated against run-copy and pure-stream variants across escape
257
- // densities see __benches__/custom/simd-string-deser-variants-h2h):
261
+ // densities - see __benches__/custom/simd-string-deser-variants-h2h):
258
262
  // * Escape-bearing block: a single whole-block v128 store copies the plain
259
263
  // prefix for free; then the escape is decoded scalar.
260
264
  // * Clean block: stream the first one cheaply, but if the clean run keeps
261
- // going, switch to one bulk memory.copy for the remainder bandwidth-
265
+ // going, switch to one bulk memory.copy for the remainder - bandwidth-
262
266
  // optimal on long sparse runs, avoiding a per-block-store cliff on large
263
267
  // inputs. This dominates both alternatives: stream-cheap on dense escapes,
264
- // memcpy-fast on long runs.
265
- //
266
- // Deliberately NOT @inline: cold escape path. As an @inline it was inlined into
267
- // deserializeStringField_SIMD at every struct string-field site, exploding
268
- // `--converge` compile time ~24x on large schemas (4s → 99s) for no runtime
268
+
269
269
  // gain. The hot no-escape scan + writeStringToField stay inline in the caller.
270
270
  function deserializeEscapedStringField_SIMD(
271
271
  payloadStart: usize,
@@ -275,16 +275,23 @@ function deserializeEscapedStringField_SIMD(
275
275
  ): usize {
276
276
  const prefixLen = <u32>(escapeStart - payloadStart);
277
277
  bs.offset = bs.buffer;
278
- bs.ensureSize(<u32>(srcEnd - payloadStart) + 16); // +16 slack for overcopy
278
+ // The containing parser may pass document-end bounds. Reserve according to
279
+ // decoded output rather than retaining the entire remaining document.
280
+ bs.ensureSize(prefixLen + 1024);
279
281
  if (prefixLen != 0) {
280
282
  memory.copy(bs.buffer, payloadStart, prefixLen);
281
283
  bs.offset += prefixLen;
282
284
  }
283
285
 
284
286
  let srcStart = escapeStart;
285
- const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
287
+ const srcEnd16 = srcEnd - 16;
288
+ let scratchEnd = bs.buffer + bs.bufferSize - 16;
286
289
 
287
290
  while (srcStart <= srcEnd16) {
291
+ if (bs.offset > scratchEnd) {
292
+ bs.ensureSize(1024);
293
+ scratchEnd = bs.buffer + bs.bufferSize - 16;
294
+ }
288
295
  const block = load<v128>(srcStart);
289
296
  const mask = i16x8.bitmask(
290
297
  v128.or(i16x8.eq(block, SPLAT_5C), i16x8.eq(block, SPLAT_22)),
@@ -315,6 +322,10 @@ function deserializeEscapedStringField_SIMD(
315
322
  srcStart += 16;
316
323
  }
317
324
  const runLen = <u32>(srcStart - runStart);
325
+ if (bs.offset + runLen > scratchEnd) {
326
+ bs.ensureSize(runLen + 1024);
327
+ scratchEnd = bs.buffer + bs.bufferSize - 16;
328
+ }
318
329
  memory.copy(bs.offset, runStart, runLen);
319
330
  bs.offset += runLen;
320
331
  }
@@ -338,6 +349,11 @@ function deserializeEscapedStringField_SIMD(
338
349
  return srcIdx + 2;
339
350
  }
340
351
 
352
+ if (!isValidStringEscape(srcIdx, srcEnd)) {
353
+ bs.offset = bs.buffer;
354
+ markProductionParseError();
355
+ return 0;
356
+ }
341
357
  const code = load<u16>(srcIdx, 2);
342
358
  if (code !== 0x75) {
343
359
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
@@ -351,6 +367,7 @@ function deserializeEscapedStringField_SIMD(
351
367
  }
352
368
 
353
369
  // scalar tail (< 16 bytes remaining): emit chars directly.
370
+ bs.ensureSize(<u32>(srcEnd - srcStart) + 2);
354
371
  while (srcStart < srcEnd) {
355
372
  const char = load<u16>(srcStart);
356
373
  if (char == QUOTE) {
@@ -369,6 +386,11 @@ function deserializeEscapedStringField_SIMD(
369
386
  continue;
370
387
  }
371
388
 
389
+ if (!isValidStringEscape(srcStart, srcEnd)) {
390
+ bs.offset = bs.buffer;
391
+ markProductionParseError();
392
+ return 0;
393
+ }
372
394
  const code = load<u16>(srcStart, 2);
373
395
  if (code !== 0x75) {
374
396
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
@@ -382,27 +404,42 @@ function deserializeEscapedStringField_SIMD(
382
404
  }
383
405
 
384
406
  bs.offset = bs.buffer;
385
- abort("Unterminated string literal");
386
- return srcStart;
407
+ markProductionParseError();
408
+ return 0;
387
409
  }
388
410
 
389
- // NOT @inline: as an @inline entry, binaryen inlined the (loop-bearing) escaped
390
- // scanner into every per-field copy, exploding `large` SIMD compile ~24x
391
- // (4s→99s, 221KB→555KB wasm). Kept as a single shared function — matches
392
- // deserializeStringField_SWAR (also non-inline) and costs only one call/field.
393
411
  export function deserializeStringField_SIMD<T extends string | null>(
394
412
  srcStart: usize,
395
413
  srcEnd: usize,
396
414
  dstObj: usize,
397
415
  dstOffset: usize = 0,
398
416
  ): usize {
399
- const dstFieldPtr = dstObj + dstOffset;
400
- if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE)
401
- abort("Expected leading quote");
417
+ if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE) {
418
+ markProductionParseError();
419
+ return 0;
420
+ }
421
+ return deserializeStringFieldTrusted_SIMD(
422
+ srcStart + 2,
423
+ srcEnd,
424
+ dstObj,
425
+ dstOffset,
426
+ );
427
+ }
402
428
 
403
- const payloadStart = srcStart + 2;
404
- const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
405
- srcStart = payloadStart;
429
+ // Generated compact-object paths have already matched the key and validated
430
+ // the opening quote. Enter at the payload so that proof replaces (rather than
431
+ // duplicates) the public field decoder's framing load.
432
+ export function deserializeStringFieldTrusted_SIMD(
433
+ payloadStart: usize,
434
+ srcEnd: usize,
435
+ dstObj: usize,
436
+ dstOffset: usize = 0,
437
+ ): usize {
438
+ const dstFieldPtr = dstObj + dstOffset;
439
+ const cachedEnd = probeStringFieldTrace(dstFieldPtr, payloadStart);
440
+ if (cachedEnd != 0) return cachedEnd;
441
+ const srcEnd16 = srcEnd - 16;
442
+ let srcStart = payloadStart;
406
443
 
407
444
  while (srcStart <= srcEnd16) {
408
445
  const block = load<v128>(srcStart);
@@ -423,15 +460,19 @@ export function deserializeStringField_SIMD<T extends string | null>(
423
460
  payloadStart,
424
461
  <u32>(srcIdx - payloadStart),
425
462
  );
426
- return srcIdx + 2;
463
+ const next = srcIdx + 2;
464
+ recordStringFieldTrace(dstFieldPtr, payloadStart, next);
465
+ return next;
427
466
  }
428
467
  // backslash → vectorized escaped scan (no more SWAR fallback)
429
- return deserializeEscapedStringField_SIMD(
468
+ const next = deserializeEscapedStringField_SIMD(
430
469
  payloadStart,
431
470
  srcIdx,
432
471
  srcEnd,
433
472
  dstFieldPtr,
434
473
  );
474
+ if (next != 0) recordStringFieldTrace(dstFieldPtr, payloadStart, next);
475
+ return next;
435
476
  }
436
477
 
437
478
  while (srcStart < srcEnd) {
@@ -442,19 +483,23 @@ export function deserializeStringField_SIMD<T extends string | null>(
442
483
  payloadStart,
443
484
  <u32>(srcStart - payloadStart),
444
485
  );
445
- return srcStart + 2;
486
+ const next = srcStart + 2;
487
+ recordStringFieldTrace(dstFieldPtr, payloadStart, next);
488
+ return next;
446
489
  }
447
490
  if (char == BACK_SLASH) {
448
- return deserializeEscapedStringField_SIMD(
491
+ const next = deserializeEscapedStringField_SIMD(
449
492
  payloadStart,
450
493
  srcStart,
451
494
  srcEnd,
452
495
  dstFieldPtr,
453
496
  );
497
+ if (next != 0) recordStringFieldTrace(dstFieldPtr, payloadStart, next);
498
+ return next;
454
499
  }
455
500
  srcStart += 2;
456
501
  }
457
502
 
458
- abort("Unterminated string literal");
459
- return srcStart;
503
+ markProductionParseError();
504
+ return 0;
460
505
  }
@@ -0,0 +1,34 @@
1
+
2
+ @inline
3
+ function isHexDigit(c: u16): bool {
4
+ return (
5
+ (c >= 0x30 && c <= 0x39) ||
6
+ (c >= 0x41 && c <= 0x46) ||
7
+ (c >= 0x61 && c <= 0x66)
8
+ );
9
+ }
10
+
11
+ /** Validate one escape beginning at its backslash without scanning plain text. */
12
+ export function isValidStringEscape(srcStart: usize, srcEnd: usize): bool {
13
+ if (srcStart + 4 > srcEnd) return false;
14
+ const code = load<u16>(srcStart, 2);
15
+ if (code == 0x75) {
16
+ return (
17
+ srcStart + 12 <= srcEnd &&
18
+ isHexDigit(load<u16>(srcStart, 4)) &&
19
+ isHexDigit(load<u16>(srcStart, 6)) &&
20
+ isHexDigit(load<u16>(srcStart, 8)) &&
21
+ isHexDigit(load<u16>(srcStart, 10))
22
+ );
23
+ }
24
+ return (
25
+ code == 0x22 ||
26
+ code == 0x5c ||
27
+ code == 0x2f ||
28
+ code == 0x62 ||
29
+ code == 0x66 ||
30
+ code == 0x6e ||
31
+ code == 0x72 ||
32
+ code == 0x74
33
+ );
34
+ }
@@ -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 deserializeArbitraryArrayField(
5
+ export function deserializeArbitraryArrayField(
7
6
  srcStart: usize,
8
7
  srcEnd: usize,
9
8
  fieldPtr: usize,
@@ -2,6 +2,27 @@ import { JSON } from "../../..";
2
2
  import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
3
3
  import { deserializeFloatArrayBody } from "./float";
4
4
  import { ensureArrayField, scanValueEnd, skipWhitespace } from "./shared";
5
+ import { skipPrettyWhitespace_SIMD } from "../../../util/prettyWhitespaceSimd";
6
+
7
+
8
+ @inline
9
+ function skipNestedArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
10
+ const code = load<u16>(srcStart);
11
+ if (ASC_FEATURE_SIMD && code == 10) {
12
+ // Common JSON.stringify(..., null, 2) shape at Canada's coordinate depth:
13
+ // LF + twelve spaces + the next nested array. Two fixed compares avoid a
14
+ // general whitespace mask, bitmask, and ctz on every coordinate pair.
15
+ if (
16
+ srcStart + 28 <= srcEnd &&
17
+ !v128.any_true(v128.xor(load<v128>(srcStart, 2), i16x8.splat(0x20))) &&
18
+ load<u64>(srcStart, 18) == 0x0020_0020_0020_0020 &&
19
+ load<u16>(srcStart, 26) == BRACKET_LEFT
20
+ )
21
+ return srcStart + 26;
22
+ return skipPrettyWhitespace_SIMD(srcStart, srcEnd);
23
+ }
24
+ return skipWhitespace(srcStart, srcEnd);
25
+ }
5
26
 
6
27
  export function deserializeArrayArrayBody<T extends unknown[][]>(
7
28
  srcStart: usize,
@@ -9,11 +30,21 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
9
30
  out: T,
10
31
  ): usize {
11
32
  let index = 0;
33
+ const reusableLength = load<i32>(
34
+ changetype<usize>(out),
35
+ offsetof<T>("length_"),
36
+ );
37
+ const reusableDataStart = out.dataStart;
38
+ const elementSize = sizeof<valueof<T>>();
12
39
 
13
40
  do {
14
41
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
15
42
  srcStart += 2;
16
- srcStart = skipWhitespace(srcStart, srcEnd);
43
+ if (srcStart < srcEnd) {
44
+ const code = load<u16>(srcStart);
45
+ if (code != BRACKET_LEFT)
46
+ srcStart = skipNestedArrayWhitespace(srcStart, srcEnd);
47
+ }
17
48
  if (srcStart >= srcEnd) break;
18
49
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
19
50
  out.length = 0;
@@ -23,8 +54,10 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
23
54
  while (srcStart < srcEnd) {
24
55
  if (isFloat<valueof<valueof<T>>>()) {
25
56
  let value: valueof<T>;
26
- if (index < out.length) {
27
- value = unchecked(out[index]);
57
+ if (index < reusableLength) {
58
+ value = load<valueof<T>>(
59
+ reusableDataStart + <usize>index * elementSize,
60
+ );
28
61
  } else {
29
62
  value = changetype<valueof<T>>(instantiate<valueof<T>>());
30
63
  out.push(value);
@@ -37,8 +70,10 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
37
70
  if (!srcStart || srcStart >= srcEnd) break;
38
71
  } else if (isArray<valueof<valueof<T>>>()) {
39
72
  let value: valueof<T>;
40
- if (index < out.length) {
41
- value = unchecked(out[index]);
73
+ if (index < reusableLength) {
74
+ value = load<valueof<T>>(
75
+ reusableDataStart + <usize>index * elementSize,
76
+ );
42
77
  } else {
43
78
  value = changetype<valueof<T>>(instantiate<valueof<T>>());
44
79
  out.push(value);
@@ -54,24 +89,39 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
54
89
  if (!valueEnd || valueEnd <= srcStart) break;
55
90
 
56
91
  let valuePtr: usize = 0;
57
- if (index < out.length) {
58
- valuePtr = changetype<usize>(unchecked(out[index]));
92
+ if (index < reusableLength) {
93
+ valuePtr = changetype<usize>(
94
+ load<valueof<T>>(reusableDataStart + <usize>index * elementSize),
95
+ );
59
96
  }
60
97
  const value = JSON.__deserialize<valueof<T>>(
61
98
  srcStart,
62
99
  valueEnd,
63
100
  valuePtr,
64
101
  );
65
- if (index < out.length) unchecked((out[index] = value));
66
- else out.push(value);
102
+ if (index < reusableLength) {
103
+ store<valueof<T>>(
104
+ reusableDataStart + <usize>index * elementSize,
105
+ value,
106
+ );
107
+ } else {
108
+ out.push(value);
109
+ }
67
110
  srcStart = valueEnd;
68
111
  }
69
112
 
70
- srcStart = skipWhitespace(srcStart, srcEnd);
71
- const code = load<u16>(srcStart);
113
+ let code = load<u16>(srcStart);
114
+ if (code != COMMA && code != BRACKET_RIGHT) {
115
+ srcStart = skipWhitespace(srcStart, srcEnd);
116
+ code = load<u16>(srcStart);
117
+ }
72
118
  if (code == COMMA) {
73
119
  srcStart += 2;
74
- srcStart = skipWhitespace(srcStart, srcEnd);
120
+ if (srcStart < srcEnd) {
121
+ code = load<u16>(srcStart);
122
+ if (code != BRACKET_LEFT)
123
+ srcStart = skipNestedArrayWhitespace(srcStart, srcEnd);
124
+ }
75
125
  index++;
76
126
  continue;
77
127
  }
@@ -80,7 +130,7 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
80
130
  // correct (the array is being reused with the same shape, e.g.
81
131
  // canada's geometry rings across repeated parses).
82
132
  const nextLen = index + 1;
83
- if (out.length != nextLen) out.length = nextLen;
133
+ if (reusableLength != nextLen) out.length = nextLen;
84
134
  return srcStart + 2;
85
135
  }
86
136
  break;
@@ -90,8 +140,7 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
90
140
  throw new Error("Failed to parse JSON!");
91
141
  }
92
142
 
93
-
94
- @inline export function deserializeArrayArrayField<T extends unknown[][]>(
143
+ export function deserializeArrayArrayField<T extends unknown[][]>(
95
144
  srcStart: usize,
96
145
  srcEnd: usize,
97
146
  fieldPtr: usize,
@@ -7,6 +7,7 @@ import {
7
7
  } from "../../../custom/chars";
8
8
  import { isSpace } from "../../../util";
9
9
  import { ensureArrayElementSlot, ensureArrayField } from "./shared";
10
+ import { markProductionParseError } from "../../error";
10
11
 
11
12
  function deserializeBooleanArrayBody<T extends boolean[]>(
12
13
  srcStart: usize,
@@ -14,6 +15,12 @@ function deserializeBooleanArrayBody<T extends boolean[]>(
14
15
  out: T,
15
16
  ): usize {
16
17
  let index = 0;
18
+ const reusableLength = load<i32>(
19
+ changetype<usize>(out),
20
+ offsetof<T>("length_"),
21
+ );
22
+ const reusableDataStart = out.dataStart;
23
+ const elementSize = sizeof<valueof<T>>();
17
24
 
18
25
  do {
19
26
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
@@ -26,7 +33,10 @@ function deserializeBooleanArrayBody<T extends boolean[]>(
26
33
  }
27
34
 
28
35
  while (srcStart < srcEnd) {
29
- const slot = ensureArrayElementSlot<T>(out, index);
36
+ const slot =
37
+ index < reusableLength
38
+ ? reusableDataStart + <usize>index * elementSize
39
+ : ensureArrayElementSlot<T>(out, index);
30
40
  const block = load<u64>(srcStart);
31
41
  if (block == TRUE_WORD_U64) {
32
42
  store<valueof<T>>(slot, true);
@@ -49,18 +59,18 @@ function deserializeBooleanArrayBody<T extends boolean[]>(
49
59
  }
50
60
  if (code == BRACKET_RIGHT) {
51
61
  const nextLen = index + 1;
52
- if (out.length != nextLen) out.length = nextLen;
62
+ if (reusableLength != nextLen) out.length = nextLen;
53
63
  return srcStart + 2;
54
64
  }
55
65
  break;
56
66
  }
57
67
  } while (false);
58
68
 
59
- throw new Error("Failed to parse JSON!");
69
+ markProductionParseError();
70
+ return 0;
60
71
  }
61
72
 
62
-
63
- @inline export function deserializeBooleanArrayField<T extends boolean[]>(
73
+ export function deserializeBooleanArrayField<T extends boolean[]>(
64
74
  srcStart: usize,
65
75
  srcEnd: usize,
66
76
  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 deserializeBoxArrayField<T extends JSON.Box<any>[]>(
5
+ export function deserializeBoxArrayField<T extends JSON.Box<any>[]>(
7
6
  srcStart: usize,
8
7
  srcEnd: usize,
9
8
  fieldPtr: usize,