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
@@ -14,16 +14,18 @@ import {
14
14
  import { isSpace, scanStringEnd } from "../../util";
15
15
  import { deserializeFloat } from "../index/float";
16
16
  import { deserializeString } from "../index/string";
17
+ import {
18
+ failProductionParse,
19
+ markProductionParseError,
20
+ takeProductionParseError,
21
+ } from "../error";
17
22
 
18
23
  // "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;
24
+ const TRUE_WORD: u64 = 28429475166421108;
25
+ // "alse" - the tail of "false", read at +2 so the leading 'f' is skipped.
26
+ const ALSE_WORD: u64 = 28429466576093281;
24
27
  // "null" as a u64 of UTF-16 code units (LE).
25
- // @ts-ignore: inline
26
- @inline const NULL_WORD: u64 = 30399761348886638;
28
+ const NULL_WORD: u64 = 30399761348886638;
27
29
 
28
30
  // End offset (just past the value) of the most recent parseValue() call. The
29
31
  // recursive-descent parser reports each value's end through this single cursor
@@ -32,30 +34,170 @@ import { deserializeString } from "../index/string";
32
34
  // parseValue() runs, so recursion never clobbers a still-needed value.
33
35
  let parseValueEnd: usize = 0;
34
36
 
37
+ // Source string for the parse currently in flight. When non-empty, nested
38
+ // objects/arrays are deferred: instead of being recursively materialized, each
39
+ // is stored as a lazy JSON.Value holding its raw slice + this anchor (see
40
+ // JSON.Value.fromSlice / JSON.Types.Lazy). Set at the top of JSON.parse (and in
41
+ // JSON.Value.materialize), both of which save/restore it for re-entrancy, so any
42
+ // lazy value built during a parse points into that parse's own source buffer.
43
+ let parseSrc: string = "";
44
+ export function setParseSrc(s: string): void {
45
+ parseSrc = s;
46
+ }
47
+ export function getParseSrc(): string {
48
+ return parseSrc;
49
+ }
50
+ export { markProductionParseError, takeProductionParseError };
51
+
35
52
  export function deserializeObject(
36
53
  srcStart: usize,
37
54
  srcEnd: usize,
38
55
  dst: usize,
39
56
  ): JSON.Obj {
40
- const out = changetype<JSON.Obj>(dst || changetype<usize>(new JSON.Obj()));
57
+ const reuse = dst != 0;
58
+ const out = changetype<JSON.Obj>(
59
+ reuse ? dst : changetype<usize>(new JSON.Obj()),
60
+ );
61
+ // Reuse path (`JSON.parse<JSON.Obj>(data, out)`): empty the handle first,
62
+ // keeping its buffer capacity, so we overwrite rather than append stale data.
63
+ if (reuse) out.clear();
64
+
65
+ while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
66
+
67
+ if (
68
+ srcEnd == srcStart ||
69
+ load<u16>(srcStart) != BRACE_LEFT ||
70
+ load<u16>(srcEnd - 2) != BRACE_RIGHT
71
+ ) {
72
+ failProductionParse();
73
+ return changetype<JSON.Obj>(0);
74
+ }
75
+
76
+ out.reserveForParse((srcEnd - srcStart) >> 1);
77
+ return parseObjectBody(out, srcStart + 2, srcEnd) != 0
78
+ ? out
79
+ : changetype<JSON.Obj>(0);
80
+ }
81
+
82
+ export function deserializeJsonArray(
83
+ srcStart: usize,
84
+ srcEnd: usize,
85
+ dst: usize,
86
+ ): JSON.Arr {
87
+ const reuse = dst != 0;
88
+ const out = changetype<JSON.Arr>(
89
+ reuse ? dst : changetype<usize>(new JSON.Arr()),
90
+ );
91
+ if (reuse) out.clear();
41
92
 
42
93
  while (srcEnd > srcStart && isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;
43
94
 
44
- if (srcEnd == srcStart)
45
- throw new Error("Input string had zero length or was all whitespace");
46
- if (load<u16>(srcStart) != BRACE_LEFT)
47
- throw new Error(
48
- "Expected '{' at start of object at position " +
49
- (srcEnd - srcStart).toString(),
50
- );
51
- if (load<u16>(srcEnd - 2) != BRACE_RIGHT)
52
- throw new Error(
53
- "Expected '}' at end of object at position " +
54
- (srcEnd - srcStart).toString(),
55
- );
56
-
57
- parseObjectBody(out, srcStart + 2, srcEnd);
58
- return out;
95
+ if (
96
+ srcEnd == srcStart ||
97
+ load<u16>(srcStart) != BRACKET_LEFT ||
98
+ load<u16>(srcEnd - 2) != BRACKET_RIGHT
99
+ ) {
100
+ failProductionParse();
101
+ return changetype<JSON.Arr>(0);
102
+ }
103
+
104
+ return parseArrayBodySlots(out, srcStart + 2, srcEnd) != 0
105
+ ? out
106
+ : changetype<JSON.Arr>(0);
107
+ }
108
+
109
+ /**
110
+ * Parses array elements starting at `srcStart` (just past the opening `[`) into
111
+ * a JSON.Arr's NaN-boxed value slots, returning the offset just after the
112
+ * matching `]`. Mirrors {@link parseObjectBody} without keys.
113
+ */
114
+ export function parseArrayBodySlots(
115
+ out: JSON.Arr,
116
+ srcStart: usize,
117
+ srcEnd: usize,
118
+ ): usize {
119
+ out._src = parseSrc;
120
+ while (srcStart < srcEnd) {
121
+ const code = load<u16>(srcStart);
122
+ if (isSpace(code) || code == COMMA) {
123
+ srcStart += 2;
124
+ continue;
125
+ }
126
+ if (code == BRACKET_RIGHT) return srcStart + 2;
127
+ if (parseSrc.length != 0) {
128
+ out.pushRawSlot(parseSlotBits(srcStart, srcEnd));
129
+ } else {
130
+ const value = parseValue(srcStart, srcEnd);
131
+ if (parseValueEnd == 0) return 0;
132
+ out.pushRawSlot(JSON.Value.bitsFrom<JSON.Value>(value));
133
+ }
134
+ if (parseValueEnd == 0) return 0;
135
+ srcStart = parseValueEnd;
136
+ }
137
+ return 0;
138
+ }
139
+
140
+ /**
141
+ * Parses a single object-member value into a NaN-boxed value slot, setting
142
+ * {@link parseValueEnd} to the offset just past it. Strings and composites are
143
+ * deferred (a `valBox(Lazy, startPtr)` slice the object parses on first access);
144
+ * numbers, booleans and null are parsed eagerly inline. Requires {@link parseSrc}
145
+ * to be set (the caller guards), since a lazy slot points into it.
146
+ */
147
+ function parseSlotBits(srcStart: usize, srcEnd: usize): u64 {
148
+ if (srcStart >= srcEnd) {
149
+ parseValueEnd = 0;
150
+ markProductionParseError();
151
+ return JSON.Value.nullBits();
152
+ }
153
+ const code = load<u16>(srcStart);
154
+ if (code == QUOTE || code == BRACE_LEFT || code == BRACKET_LEFT) {
155
+ const end = JSON.Util.scanValueEnd<JSON.Value>(srcStart, srcEnd);
156
+ if (end == 0) {
157
+ parseValueEnd = 0;
158
+ markProductionParseError();
159
+ return JSON.Value.nullBits();
160
+ }
161
+ parseValueEnd = end;
162
+ return JSON.Value.lazyBits(changetype<usize>(parseSrc), srcStart, end);
163
+ } else if (code - 48 <= 9 || code == 45) {
164
+ let p = srcStart + 2;
165
+ while (p < srcEnd) {
166
+ const c = load<u16>(p);
167
+ if (c == COMMA || c == BRACKET_RIGHT || c == BRACE_RIGHT || isSpace(c))
168
+ break;
169
+ p += 2;
170
+ }
171
+ parseValueEnd = p;
172
+ return JSON.Value.f64Bits(deserializeFloat<f64>(srcStart, p));
173
+ } else if (code == CHAR_T) {
174
+ if (srcEnd - srcStart < 8 || load<u64>(srcStart) != TRUE_WORD) {
175
+ parseValueEnd = 0;
176
+ markProductionParseError();
177
+ return JSON.Value.nullBits();
178
+ }
179
+ parseValueEnd = srcStart + 8;
180
+ return JSON.Value.boolBits(true);
181
+ } else if (code == CHAR_F) {
182
+ if (srcEnd - srcStart < 10 || load<u64>(srcStart, 2) != ALSE_WORD) {
183
+ parseValueEnd = 0;
184
+ markProductionParseError();
185
+ return JSON.Value.nullBits();
186
+ }
187
+ parseValueEnd = srcStart + 10;
188
+ return JSON.Value.boolBits(false);
189
+ } else if (code == CHAR_N) {
190
+ if (srcEnd - srcStart < 8 || load<u64>(srcStart) != NULL_WORD) {
191
+ parseValueEnd = 0;
192
+ markProductionParseError();
193
+ return JSON.Value.nullBits();
194
+ }
195
+ parseValueEnd = srcStart + 8;
196
+ return JSON.Value.nullBits();
197
+ }
198
+ parseValueEnd = 0;
199
+ markProductionParseError();
200
+ return JSON.Value.nullBits();
59
201
  }
60
202
 
61
203
  /**
@@ -70,19 +212,34 @@ export function lastValueEnd(): usize {
70
212
  }
71
213
 
72
214
  export function parseValue(srcStart: usize, srcEnd: usize): JSON.Value {
215
+ if (srcStart >= srcEnd) {
216
+ parseValueEnd = 0;
217
+ markProductionParseError();
218
+ return changetype<JSON.Value>(0);
219
+ }
73
220
  const code = load<u16>(srcStart);
74
221
  if (code == QUOTE) {
75
222
  const end = scanStringEnd(srcStart, srcEnd);
76
- if (end >= srcEnd) throw new Error("Unterminated string in JSON");
223
+ if (end >= srcEnd) {
224
+ parseValueEnd = 0;
225
+ markProductionParseError();
226
+ return changetype<JSON.Value>(0);
227
+ }
77
228
  parseValueEnd = end + 2;
78
- return JSON.Value.from(deserializeString(srcStart, end + 2));
229
+ const value = deserializeString(srcStart, end + 2);
230
+ if (changetype<usize>(value) == 0) {
231
+ parseValueEnd = 0;
232
+ markProductionParseError();
233
+ return changetype<JSON.Value>(0);
234
+ }
235
+ return JSON.Value.from(value);
79
236
  } else if (code == BRACE_LEFT) {
80
237
  const obj = new JSON.Obj();
81
238
  parseValueEnd = parseObjectBody(obj, srcStart + 2, srcEnd);
82
239
  return JSON.Value.from(obj);
83
240
  } else if (code == BRACKET_LEFT) {
84
- const arr = instantiate<JSON.Value[]>();
85
- parseValueEnd = parseArrayBody(arr, srcStart + 2, srcEnd);
241
+ const arr = new JSON.Arr();
242
+ parseValueEnd = parseArrayBodySlots(arr, srcStart + 2, srcEnd);
86
243
  return JSON.Value.from(arr);
87
244
  } else if (code - 48 <= 9 || code == 45) {
88
245
  let p = srcStart + 2;
@@ -95,24 +252,33 @@ export function parseValue(srcStart: usize, srcEnd: usize): JSON.Value {
95
252
  parseValueEnd = p;
96
253
  return JSON.Value.from(deserializeFloat<f64>(srcStart, p));
97
254
  } else if (code == CHAR_T) {
98
- if (load<u64>(srcStart) != TRUE_WORD)
99
- throw new Error("Expected 'true' in JSON");
255
+ if (srcEnd - srcStart < 8 || load<u64>(srcStart) != TRUE_WORD) {
256
+ parseValueEnd = 0;
257
+ markProductionParseError();
258
+ return changetype<JSON.Value>(0);
259
+ }
100
260
  parseValueEnd = srcStart + 8;
101
261
  return JSON.Value.from(true);
102
262
  } else if (code == CHAR_F) {
103
- if (load<u64>(srcStart, 2) != ALSE_WORD)
104
- throw new Error("Expected 'false' in JSON");
263
+ if (srcEnd - srcStart < 10 || load<u64>(srcStart, 2) != ALSE_WORD) {
264
+ parseValueEnd = 0;
265
+ markProductionParseError();
266
+ return changetype<JSON.Value>(0);
267
+ }
105
268
  parseValueEnd = srcStart + 10;
106
269
  return JSON.Value.from(false);
107
270
  } else if (code == CHAR_N) {
108
- if (load<u64>(srcStart) != NULL_WORD)
109
- throw new Error("Expected 'null' in JSON");
271
+ if (srcEnd - srcStart < 8 || load<u64>(srcStart) != NULL_WORD) {
272
+ parseValueEnd = 0;
273
+ markProductionParseError();
274
+ return changetype<JSON.Value>(0);
275
+ }
110
276
  parseValueEnd = srcStart + 8;
111
277
  return JSON.Value.from<usize>(0);
112
278
  }
113
- throw new Error(
114
- "Unexpected character in JSON '" + String.fromCharCode(code) + "'",
115
- );
279
+ parseValueEnd = 0;
280
+ markProductionParseError();
281
+ return changetype<JSON.Value>(0);
116
282
  }
117
283
 
118
284
  /**
@@ -132,10 +298,27 @@ export function parseArrayBody(
132
298
  continue;
133
299
  }
134
300
  if (code == BRACKET_RIGHT) return srcStart + 2;
135
- out.push(parseValue(srcStart, srcEnd));
136
- srcStart = parseValueEnd;
301
+ if (
302
+ (code == BRACE_LEFT || code == BRACKET_LEFT || code == QUOTE) &&
303
+ parseSrc.length != 0
304
+ ) {
305
+ // Defer strings and composites (the allocating shapes): store the raw
306
+ // slice, parse on first access. Cheap primitives stay eager below.
307
+ const end = JSON.Util.scanValueEnd<JSON.Value>(srcStart, srcEnd);
308
+ if (end == 0) {
309
+ markProductionParseError();
310
+ return 0;
311
+ }
312
+ out.push(JSON.Value.fromSlice(srcStart, end, parseSrc));
313
+ srcStart = end;
314
+ } else {
315
+ const value = parseValue(srcStart, srcEnd);
316
+ if (parseValueEnd == 0) return 0;
317
+ out.push(value);
318
+ srcStart = parseValueEnd;
319
+ }
137
320
  }
138
- return srcEnd;
321
+ return 0;
139
322
  }
140
323
 
141
324
  /**
@@ -148,6 +331,12 @@ export function parseObjectBody(
148
331
  srcStart: usize,
149
332
  srcEnd: usize,
150
333
  ): usize {
334
+ const objectStart = srcStart - 2;
335
+ // Anchor the source for this object's deferred value slots (start pointers
336
+ // into it). Set here so every caller - deserializeObject, the JSON.Obj[]
337
+ // array path, parseValue, the map path - gets it. Empty off the parse path,
338
+ // where no lazy slots are produced.
339
+ out._src = parseSrc;
151
340
  while (srcStart < srcEnd) {
152
341
  let code = load<u16>(srcStart);
153
342
 
@@ -156,37 +345,43 @@ export function parseObjectBody(
156
345
  srcStart += 2;
157
346
  continue;
158
347
  }
159
- if (code == BRACE_RIGHT) return srcStart + 2;
348
+ if (code == BRACE_RIGHT) {
349
+ const objectEnd = srcStart + 2;
350
+ out._rawStart = objectStart;
351
+ out._rawEnd = objectEnd;
352
+ return objectEnd;
353
+ }
160
354
 
161
355
  // --- key ---
162
- if (code != QUOTE)
163
- throw new Error(
164
- "Unexpected character in JSON object '" +
165
- String.fromCharCode(code) +
166
- "' at position " +
167
- (srcEnd - srcStart).toString(),
168
- );
356
+ if (code != QUOTE) return failProductionParse();
169
357
  const keyStart = srcStart + 2;
170
358
  srcStart = scanStringEnd(srcStart, srcEnd);
171
- if (srcStart >= srcEnd)
172
- throw new Error("Unterminated string in JSON object");
359
+ if (srcStart >= srcEnd) return failProductionParse();
173
360
  const keyEnd = srcStart;
174
361
  srcStart += 2;
175
362
 
176
363
  // --- colon ---
177
364
  while (srcStart < srcEnd && isSpace((code = load<u16>(srcStart))))
178
365
  srcStart += 2;
179
- if (srcStart >= srcEnd || code != COLON)
180
- throw new Error(
181
- "Expected ':' after key at position " + (srcEnd - srcStart).toString(),
182
- );
366
+ if (srcStart >= srcEnd || code != COLON) return failProductionParse();
183
367
  srcStart += 2;
184
368
 
185
369
  // --- value ---
186
370
  while (srcStart < srcEnd && isSpace((code = load<u16>(srcStart))))
187
371
  srcStart += 2;
188
- out.appendRaw(keyStart, keyEnd, parseValue(srcStart, srcEnd));
372
+ if (srcStart >= srcEnd) return failProductionParse();
373
+ if (parseSrc.length != 0) {
374
+ // Parsing: store a NaN-boxed slot directly (strings/composites deferred,
375
+ // scalars eager) - no per-value JSON.Value object.
376
+ out.appendParsedSlot(keyStart, keyEnd, parseSlotBits(srcStart, srcEnd));
377
+ if (parseValueEnd == 0) return 0;
378
+ } else {
379
+ // Off the parse path (no source anchor): box eagerly.
380
+ const value = parseValue(srcStart, srcEnd);
381
+ if (parseValueEnd == 0) return 0;
382
+ out.appendRaw(keyStart, keyEnd, value);
383
+ }
189
384
  srcStart = parseValueEnd;
190
385
  }
191
- return srcEnd;
386
+ return 0;
192
387
  }
@@ -1,10 +1,18 @@
1
1
  import { JSON } from "../..";
2
2
  import { ptrToStr } from "../../util/ptrToStr";
3
3
 
4
- // @ts-ignore: inline
5
- @inline export function deserializeRaw(
4
+ export function deserializeRaw(
6
5
  srcStart: usize,
7
6
  srcEnd: usize,
7
+ dst: usize = 0,
8
8
  ): JSON.Raw {
9
+ const size = srcEnd - srcStart;
10
+ if (dst) {
11
+ const out = changetype<JSON.Raw>(dst);
12
+ const data = changetype<string>(__renew(changetype<usize>(out.data), size));
13
+ memory.copy(changetype<usize>(data), srcStart, size);
14
+ out.data = data;
15
+ return out;
16
+ }
9
17
  return JSON.Raw.from(ptrToStr(srcStart, srcEnd));
10
18
  }
@@ -63,7 +63,6 @@ function deserializeSetDirect<T extends Set<any>>(
63
63
  if (load<u16>(srcStart) == BRACKET_RIGHT) return srcStart + 2;
64
64
 
65
65
  while (srcStart < srcEnd) {
66
- while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
67
66
  const code = load<u16>(srcStart);
68
67
 
69
68
  // @ts-ignore: type
@@ -156,8 +155,7 @@ export function deserializeSet<T extends Set<any>>(
156
155
  return out;
157
156
  }
158
157
 
159
- // @ts-expect-error: Decorator valid here
160
- @inline function deserializeSetBody<T extends Set<any>>(
158
+ function deserializeSetBody<T extends Set<any>>(
161
159
  srcStart: usize,
162
160
  srcEnd: usize,
163
161
  out: T,
@@ -166,8 +164,7 @@ export function deserializeSet<T extends Set<any>>(
166
164
  return deserializeSetDirect<T>(srcStart, srcEnd, changetype<nonnull<T>>(out));
167
165
  }
168
166
 
169
- // @ts-expect-error: Decorator valid here
170
- @inline export function deserializeSetField<T extends Set<any>>(
167
+ export function deserializeSetField<T extends Set<any>>(
171
168
  srcStart: usize,
172
169
  srcEnd: usize,
173
170
  dstObj: usize,
@@ -21,7 +21,8 @@ function materializeStaticArray<T extends StaticArray<any>>(
21
21
  src: valueof<T>[],
22
22
  dst: usize,
23
23
  ): T {
24
- const byteLength = <usize>src.length * sizeof<valueof<T>>();
24
+ const len = src.length;
25
+ const byteLength = <usize>len * sizeof<valueof<T>>();
25
26
  let out = dst;
26
27
 
27
28
  if (!out) {
@@ -31,7 +32,7 @@ function materializeStaticArray<T extends StaticArray<any>>(
31
32
  }
32
33
 
33
34
  const typed = changetype<T>(out);
34
- for (let i = 0; i < src.length; i++) {
35
+ for (let i = 0; i < len; i++) {
35
36
  unchecked((typed[i] = unchecked(src[i])));
36
37
  }
37
38
  return typed;
@@ -112,8 +113,7 @@ export function deserializeStaticArray<T extends StaticArray<any>>(
112
113
  throw new Error("Could not parse static array of type " + nameof<T>() + "!");
113
114
  }
114
115
 
115
-
116
- @inline export function deserializeStaticArrayField<T extends StaticArray<any>>(
116
+ export function deserializeStaticArrayField<T extends StaticArray<any>>(
117
117
  srcStart: usize,
118
118
  srcEnd: usize,
119
119
  dstObj: usize,
@@ -3,16 +3,16 @@ import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
3
3
  import { __heap_base } from "memory";
4
4
  import { BACK_SLASH, QUOTE } from "../../custom/chars";
5
5
  import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
6
+ import { markProductionParseError } from "../error";
7
+ import { isValidStringEscape } from "../string-validation";
6
8
 
7
- // @ts-ignore: inline
8
- @inline function hexDigit(c: u16): u32 {
9
+ function hexDigit(c: u16): u32 {
9
10
  if (c <= 0x39) return c - 0x30; // '0'-'9'
10
11
  if (c <= 0x46) return c - 0x37; // 'A'-'F'
11
12
  return c - 0x57; // 'a'-'f'
12
13
  }
13
14
 
14
- // @ts-ignore: inline
15
- @inline function hex4ToU16(srcStart: usize): u16 {
15
+ function hex4ToU16(srcStart: usize): u16 {
16
16
  return <u16>(
17
17
  ((hexDigit(load<u16>(srcStart)) << 12) |
18
18
  (hexDigit(load<u16>(srcStart, 2)) << 8) |
@@ -21,47 +21,10 @@ import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
21
21
  );
22
22
  }
23
23
 
24
- // @ts-ignore: inline
25
- @inline function isHexDigit(c: u16): bool {
26
- return (
27
- (c >= 0x30 && c <= 0x39) ||
28
- (c >= 0x41 && c <= 0x46) ||
29
- (c >= 0x61 && c <= 0x66)
30
- );
31
- }
32
-
33
- // Strict RFC 8259 check for the char following a backslash, at [escPtr, srcEnd).
34
- // Legal escapes: " \ / b f n r t and \uXXXX (4 hex digits). Throws otherwise:
35
- // unknown escape letter, a trailing backslash, or a short / non-hex \u.
36
- function validateEscape(escPtr: usize, srcEnd: usize): void {
37
- if (escPtr >= srcEnd)
38
- throw new Error("Invalid JSON string: incomplete escape");
39
- const code = load<u16>(escPtr);
40
- if (code == 0x75) {
41
- // \uXXXX
42
- if (escPtr + 10 > srcEnd)
43
- throw new Error("Invalid JSON string: incomplete \\u escape");
44
- if (
45
- !isHexDigit(load<u16>(escPtr, 2)) ||
46
- !isHexDigit(load<u16>(escPtr, 4)) ||
47
- !isHexDigit(load<u16>(escPtr, 6)) ||
48
- !isHexDigit(load<u16>(escPtr, 8))
49
- )
50
- throw new Error("Invalid JSON string: \\u escape needs 4 hex digits");
51
- return;
52
- }
53
- // short escapes: " \ / b f n r t
54
- if (
55
- code != 0x22 &&
56
- code != 0x5c &&
57
- code != 0x2f &&
58
- code != 0x62 &&
59
- code != 0x66 &&
60
- code != 0x6e &&
61
- code != 0x72 &&
62
- code != 0x74
63
- )
64
- throw new Error("Invalid JSON string: illegal escape");
24
+ // RFC 8259 check for the char following a backslash, at [escPtr, srcEnd).
25
+ // Legal escapes: " \ / b f n r t and \uXXXX (4 hex digits).
26
+ function validateEscape(escPtr: usize, srcEnd: usize): bool {
27
+ return isValidStringEscape(escPtr - 2, srcEnd);
65
28
  }
66
29
 
67
30
  export function deserializeString_NAIVE(
@@ -75,8 +38,10 @@ export function deserializeString_NAIVE(
75
38
  srcEnd - srcStart < 4 ||
76
39
  load<u16>(srcStart) != QUOTE ||
77
40
  load<u16>(srcEnd - 2) != QUOTE
78
- )
79
- throw new Error("Invalid JSON string: missing surrounding quotes");
41
+ ) {
42
+ markProductionParseError();
43
+ return changetype<string>(0);
44
+ }
80
45
  // Strip quotes
81
46
  srcStart += 2;
82
47
  srcEnd -= 2;
@@ -91,13 +56,20 @@ export function deserializeString_NAIVE(
91
56
  // Early exit
92
57
  if (block !== 0x5c) {
93
58
  // RFC 8259: literal control chars (U+0000..U+001F) must be escaped.
94
- if (block < 0x20)
95
- throw new Error("Invalid JSON string: unescaped control character");
59
+ if (block < 0x20) {
60
+ bs.offset = bs.buffer + outStart;
61
+ markProductionParseError();
62
+ return changetype<string>(0);
63
+ }
96
64
  bs.offset += 2;
97
65
  continue;
98
66
  }
99
67
 
100
- validateEscape(srcStart, srcEnd);
68
+ if (!validateEscape(srcStart, srcEnd)) {
69
+ bs.offset = bs.buffer + outStart;
70
+ markProductionParseError();
71
+ return changetype<string>(0);
72
+ }
101
73
  const code = load<u16>(srcStart);
102
74
  if (code !== 0x75) {
103
75
  // Short escapes (\n \t \" \\)
@@ -148,7 +120,7 @@ function writeStringToField(
148
120
  // Escape-bearing tail of the field parse: the clean prefix [payloadStart,
149
121
  // escPos) is bulk-copied into the scratch buffer, then escapes are decoded into
150
122
  // 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`.
123
+ // actually present - the common escape-free case never touches `bs`.
152
124
  function deserializeEscapedStringField_NAIVE(
153
125
  payloadStart: usize,
154
126
  escPos: usize,
@@ -194,27 +166,41 @@ function deserializeEscapedStringField_NAIVE(
194
166
  bs.offset += 2;
195
167
  }
196
168
 
197
- abort("Expected closing quote");
169
+ markProductionParseError();
198
170
  return 0;
199
171
  }
200
172
 
201
173
  // NOT @inline: this is a loop-bearing scanner called per string field. As an
202
174
  // always-inline entry it gets inlined into every field call site inside the
203
175
  // @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.
176
+ // schemas (~118s on the `large` bench). Kept as a single shared function - one
177
+ // call per field - matching the non-inline SWAR/SIMD field deserializers.
206
178
  export function deserializeStringField_NAIVE<T extends string | null>(
207
179
  srcStart: usize,
208
180
  srcEnd: usize,
209
181
  dstObj: usize,
210
182
  dstOffset: usize = 0,
211
183
  ): usize {
212
- const dstFieldPtr = dstObj + dstOffset;
213
- if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE)
214
- abort("Expected leading quote");
184
+ if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE) {
185
+ markProductionParseError();
186
+ return 0;
187
+ }
188
+ return deserializeStringFieldTrusted_NAIVE(
189
+ srcStart + 2,
190
+ srcEnd,
191
+ dstObj,
192
+ dstOffset,
193
+ );
194
+ }
215
195
 
216
- const payloadStart = srcStart + 2;
217
- srcStart = payloadStart;
196
+ export function deserializeStringFieldTrusted_NAIVE(
197
+ payloadStart: usize,
198
+ srcEnd: usize,
199
+ dstObj: usize,
200
+ dstOffset: usize = 0,
201
+ ): usize {
202
+ const dstFieldPtr = dstObj + dstOffset;
203
+ let srcStart = payloadStart;
218
204
 
219
205
  // Scan for the closing quote without touching the scratch buffer. For the
220
206
  // common escape-free case the bytes are a verbatim slice of the source, so we
@@ -241,6 +227,6 @@ export function deserializeStringField_NAIVE<T extends string | null>(
241
227
  srcStart += 2;
242
228
  }
243
229
 
244
- abort("Expected closing quote");
230
+ markProductionParseError();
245
231
  return 0;
246
232
  }
@@ -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 {