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
package/lib/as-bs.ts CHANGED
@@ -40,15 +40,13 @@ export namespace bs {
40
40
  * Using bit shifts for efficiency: alpha = 1/8, so (1 - alpha) = 7/8
41
41
  * @param newSize - The new size to incorporate into the average
42
42
  */
43
- // @ts-expect-error: @inline is a valid decorator
44
- @inline function updateTypicalSize(newSize: usize): void {
43
+ function updateTypicalSize(newSize: usize): void {
45
44
  // EMA: typicalSize = (newSize >> 3) + typicalSize - (typicalSize >> 3)
46
45
  // Simplified: typicalSize += (newSize - typicalSize) >> 3
47
46
  typicalSize += (newSize - typicalSize) >> EMA_ALPHA_SHIFT;
48
47
  }
49
48
 
50
- // @ts-expect-error: @inline is a valid decorator
51
- @inline function renewBuffer(newSize: usize): void {
49
+ function renewBuffer(newSize: usize): void {
52
50
  const oldPtr = buffer;
53
51
  const relOffset = offset - oldPtr;
54
52
  const newPtr = heap.realloc(oldPtr, newSize);
@@ -57,8 +55,7 @@ export namespace bs {
57
55
  bufferSize = newSize;
58
56
  }
59
57
 
60
- // @ts-expect-error: @inline is a valid decorator
61
- @inline function reserve(requiredSize: usize, extra: usize): void {
58
+ function reserve(requiredSize: usize, extra: usize): void {
62
59
  if (requiredSize <= bufferSize) return;
63
60
  // Grow aggressively (2x) to minimize realloc frequency in hot serialization paths.
64
61
  let next = bufferSize << 1;
@@ -67,8 +64,7 @@ export namespace bs {
67
64
  renewBuffer(next);
68
65
  }
69
66
 
70
- // @ts-expect-error: @inline is a valid decorator
71
- @inline function finalizeDynamicOutput(len: usize): void {
67
+ function finalizeDynamicOutput(len: usize): void {
72
68
  counter += 1;
73
69
  updateTypicalSize(len);
74
70
  if ((counter & SHRINK_EVERY_N_MASK) == 0 && bufferSize > typicalSize << 2) {
@@ -83,8 +79,7 @@ export namespace bs {
83
79
  /**
84
80
  * Stores the state of the buffer, allowing further changes to be reset
85
81
  */
86
- // @ts-expect-error: @inline is a valid decorator
87
- @inline export function saveState(): void {
82
+ export function saveState(): void {
88
83
  pauseOffsets.push(offset - buffer);
89
84
  pauseStackSizes.push(stackSize);
90
85
  }
@@ -93,8 +88,7 @@ export namespace bs {
93
88
  * Resets the buffer to the state it was in when `pause()` was called.
94
89
  * This allows for changes made after the pause to be discarded.
95
90
  */
96
- // @ts-expect-error: @inline is a valid decorator
97
- @inline export function loadState(): void {
91
+ export function loadState(): void {
98
92
  const length = pauseOffsets.length;
99
93
  if (length == 0) return;
100
94
  const index = length - 1;
@@ -109,8 +103,7 @@ export namespace bs {
109
103
  * serialize/deserialize op mid-flight: a partial run can leave `offset`
110
104
  * advanced and the pause stacks non-empty, which would corrupt the next op.
111
105
  */
112
- // @ts-expect-error: @inline is a valid decorator
113
- @inline export function reset(): void {
106
+ export function reset(): void {
114
107
  offset = buffer;
115
108
  stackSize = 0;
116
109
  pauseOffsets.length = 0;
@@ -122,8 +115,7 @@ export namespace bs {
122
115
  * If necessary, reallocates the buffer to the exact new size.
123
116
  * @param size - The size to propose.
124
117
  */
125
- // @ts-expect-error: @inline is a valid decorator
126
- @inline export function ensureSize(size: u32): void {
118
+ export function ensureSize(size: u32): void {
127
119
  reserve(offset - buffer + usize(size), MIN_BUFFER_SIZE);
128
120
  }
129
121
 
@@ -132,8 +124,7 @@ export namespace bs {
132
124
  * If necessary, reallocates the buffer to the exact new size.
133
125
  * @param size - The size to propose.w
134
126
  */
135
- // @ts-expect-error: @inline is a valid decorator
136
- @inline export function proposeSize(size: u32): void {
127
+ export function proposeSize(size: u32): void {
137
128
  stackSize += size;
138
129
  reserve(stackSize, 0);
139
130
  }
@@ -143,8 +134,7 @@ export namespace bs {
143
134
  * If necessary, reallocates the buffer to the exact new size.
144
135
  * @param size - The size to grow by.
145
136
  */
146
- // @ts-expect-error: @inline is a valid decorator
147
- @inline export function growSize(size: u32): void {
137
+ export function growSize(size: u32): void {
148
138
  stackSize += size;
149
139
  reserve(stackSize, MIN_BUFFER_SIZE);
150
140
  }
@@ -153,8 +143,7 @@ export namespace bs {
153
143
  * Resizes the buffer to the specified size.
154
144
  * @param newSize - The new buffer size.
155
145
  */
156
- // @ts-expect-error: @inline is a valid decorator
157
- @inline export function resize(newSize: u32): void {
146
+ export function resize(newSize: u32): void {
158
147
  const oldPtr = buffer;
159
148
  const relOffset = offset - oldPtr;
160
149
  const newPtr = heap.realloc(buffer, newSize);
@@ -168,8 +157,7 @@ export namespace bs {
168
157
  * finalization. Keeps enough capacity for the recent typical output size while
169
158
  * releasing clearly excess memory.
170
159
  */
171
- // @ts-expect-error: @inline is a valid decorator
172
- @inline export function shrink(): void {
160
+ export function shrink(): void {
173
161
  let next = typicalSize << 1;
174
162
  if (next < MIN_BUFFER_SIZE) next = MIN_BUFFER_SIZE;
175
163
  if (bufferSize > next) {
@@ -184,8 +172,7 @@ export namespace bs {
184
172
  * Copies the buffer's content to a new object of a specified type. Does not shrink the buffer.
185
173
  * @returns The new object containing the buffer's content.
186
174
  */
187
- // @ts-expect-error: @inline is a valid decorator
188
- @inline export function cpyOut<T>(): T {
175
+ export function cpyOut<T>(): T {
189
176
  if (pauseOffsets.length == 0) {
190
177
  const len = offset - buffer;
191
178
  // @ts-expect-error: __new is a runtime builtin
@@ -214,8 +201,7 @@ export namespace bs {
214
201
  * Note: this restores only `offset`. Deserialization paths do not currently depend on
215
202
  * `stackSize`, which is tracked for serialization growth heuristics.
216
203
  */
217
- // @ts-expect-error: @inline is a valid decorator
218
- @inline export function sliceOut<T>(start: usize): T {
204
+ export function sliceOut<T>(start: usize): T {
219
205
  const sliceStart = buffer + start;
220
206
  const len = offset - sliceStart;
221
207
  // @ts-expect-error: __new is a runtime builtin
@@ -229,8 +215,7 @@ export namespace bs {
229
215
  * Copies the slice starting at a caller-provided relative buffer offset into a string field
230
216
  * and restores `offset` back to that slice start.
231
217
  */
232
- // @ts-expect-error: @inline is a valid decorator
233
- @inline export function toField(start: usize, dstFieldPtr: usize): void {
218
+ export function toField(start: usize, dstFieldPtr: usize): void {
234
219
  const sliceStart = buffer + start;
235
220
  const byteLength = <u32>(offset - sliceStart);
236
221
  if (byteLength == 0) {
@@ -265,8 +250,7 @@ export namespace bs {
265
250
  * adaptive buffer management - shrinks buffer when consistently oversized.
266
251
  * @returns The new object containing the buffer's content.
267
252
  */
268
- // @ts-expect-error: @inline is a valid decorator
269
- @inline export function out<T>(): T {
253
+ export function out<T>(): T {
270
254
  let out: usize;
271
255
  if (cacheOutput === 0) {
272
256
  const len = offset - buffer;
@@ -296,8 +280,7 @@ export namespace bs {
296
280
  * can pass an empty/uninitialized target on the first call. Mirrors the reuse
297
281
  * policy of `toField`, but for the top-level return value.
298
282
  */
299
- // @ts-expect-error: @inline is a valid decorator
300
- @inline export function outTo<T>(target: usize): T {
283
+ export function outTo<T>(target: usize): T {
301
284
  if (target < __heap_base) return out<T>();
302
285
  let len: usize;
303
286
  let src: usize;
@@ -309,7 +292,7 @@ export namespace bs {
309
292
  src = cacheOutput;
310
293
  }
311
294
  let dst = target;
312
- if (changetype<OBJECT>(target - TOTAL_OVERHEAD).rtSize != len) {
295
+ if (changetype<OBJECT>(target - TOTAL_OVERHEAD).rtSize != <u32>len) {
313
296
  // @ts-expect-error: __renew is a runtime builtin
314
297
  dst = __renew(target, len);
315
298
  }
@@ -371,12 +354,9 @@ export namespace bs {
371
354
  * avoiding re-serialization of previously seen strings.
372
355
  */
373
356
  export namespace sc {
374
- // @ts-expect-error: @inline is a valid decorator
375
- @inline export const ENTRY_KEY = offsetof<sc.Entry>("key");
376
- // @ts-expect-error: @inline is a valid decorator
377
- @inline export const ENTRY_PTR = offsetof<sc.Entry>("ptr");
378
- // @ts-expect-error: @inline is a valid decorator
379
- @inline export const ENTRY_LEN = offsetof<sc.Entry>("len");
357
+ export const ENTRY_KEY = offsetof<sc.Entry>("key");
358
+ export const ENTRY_PTR = offsetof<sc.Entry>("ptr");
359
+ export const ENTRY_LEN = offsetof<sc.Entry>("len");
380
360
 
381
361
  // @ts-expect-error: JSON_CACHE may not be defined. If so, it will default to false.
382
362
  export const CACHE_ENABLED: bool = isDefined(JSON_CACHE) ? JSON_CACHE : false;
@@ -425,8 +405,7 @@ export namespace sc {
425
405
  * Uses pointer address shifted right by 4 bits (aligned to 16-byte boundaries)
426
406
  * masked to fit within cache size.
427
407
  */
428
- // @ts-expect-error: @inline is a valid decorator
429
- @inline
408
+
430
409
  export function indexFor(ptr: usize): usize {
431
410
  return (ptr >> 4) & CACHE_MASK;
432
411
  }
@@ -437,8 +416,7 @@ export namespace sc {
437
416
  * @param key - The string pointer to look up
438
417
  * @returns true if cache hit, false if cache miss
439
418
  */
440
- // @ts-expect-error: @inline is a valid decorator
441
- @inline
419
+
442
420
  export function tryEmitCached(key: usize): bool {
443
421
  const e = unchecked(entries[indexFor(key)]);
444
422
  if (e.key == key) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "json-as",
3
- "version": "1.4.0",
4
- "author": "Jairus Tanaka",
3
+ "version": "1.5.0",
4
+ "author": "Jairus Tanaka <me@jairus.dev>",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/JairusSW/json-as.git"
@@ -11,15 +11,15 @@
11
11
  "devDependencies": {
12
12
  "@assemblyscript/wasi-shim": "^0.1.0",
13
13
  "@eslint/js": "^10.0.1",
14
- "@types/node": "^25.9.1",
14
+ "@types/node": "^25.9.2",
15
15
  "as-heap-analyzer": "^1.2.0",
16
16
  "as-test": "^1.6.0",
17
- "assemblyscript": "^0.28.17",
17
+ "assemblyscript": "^0.28.18",
18
18
  "assemblyscript-json": "^1.1.0",
19
19
  "assemblyscript-prettier": "^3.0.4",
20
20
  "chartjs-node-canvas": "^5.0.0",
21
21
  "chartjs-plugin-datalabels": "^2.2.0",
22
- "eslint": "^10.4.0",
22
+ "eslint": "^10.4.1",
23
23
  "fast-json-parse": "^1.0.3",
24
24
  "fast-json-stringify": "^6.4.0",
25
25
  "husky": "^9.1.7",
@@ -29,7 +29,7 @@
29
29
  "tinybench": "^6.0.2",
30
30
  "try-as": "^1.1.4",
31
31
  "typescript": "^6.0.3",
32
- "typescript-eslint": "^8.60.0"
32
+ "typescript-eslint": "^8.60.1"
33
33
  },
34
34
  "bugs": {
35
35
  "url": "https://github.com/JairusSW/json-as/issues"
@@ -98,6 +98,7 @@
98
98
  "test:ci": "ast test --parallel --clean --enable try-as",
99
99
  "test:coverage": "ast test --enable coverage --enable try-as",
100
100
  "bench": "bash -c 'bash ./scripts/run-bench.as.sh \"$@\" && { arg=\"${1:-}\"; if [ -z \"$arg\" ] || [ \"${arg#custom/}\" = \"$arg\" ]; then bash ./scripts/run-bench.js.sh \"$@\"; fi; } && bash ./scripts/build-charts.sh' --",
101
+ "bench:all": "bash ./scripts/bench-all.sh",
101
102
  "bench:as": "bash ./scripts/run-bench.as.sh",
102
103
  "bench:js": "bash ./scripts/run-bench.js.sh",
103
104
  "charts": "bun run charts:build && bun run charts:serve",
@@ -115,6 +116,9 @@
115
116
  "run:playground": "wasmtime ./build/playground.wasm",
116
117
  "run:pg": "npm run run:playground",
117
118
  "playground": "npm run build:playground && npm run run:playground",
119
+ "build:playground:tmp": "asc assembly/playground.tmp.ts -o ./build/playground.tmp.wasm --textFile ./build/playground.tmp.wat -O3 --noAssert --uncheckedBehavior always --runtime incremental --enable bulk-memory --enable simd --use JSON_MODE=1 --exportStart start --exportRuntime",
120
+ "playground:tmp": "npm run build:playground:tmp && v8 --no-liftoff --module ./bench/runners/assemblyscript.js -- playground.tmp.wasm",
121
+ "pg:tmp": "npm run playground:tmp",
118
122
  "play": "npm run playground",
119
123
  "pg": "npm run playground",
120
124
  "bench:wasmer": "wasmer ./build/bench.wasm --llvm",
@@ -132,5 +136,8 @@
132
136
  "prepare": "husky"
133
137
  },
134
138
  "type": "module",
135
- "types": "assembly/index.ts"
139
+ "types": "assembly/index.ts",
140
+ "dependencies": {
141
+ "xjb-as": "^0.1.0"
142
+ }
136
143
  }
@@ -310,6 +310,7 @@ export class JSONTransform extends Visitor {
310
310
  }) ??
311
311
  false));
312
312
  let __hasLazy = false;
313
+ const __lazyValInits = new Map();
313
314
  for (let i = node.members.length - 1; i >= 0; i--) {
314
315
  const fd = node.members[i];
315
316
  if (fd.kind !== NodeKind.FieldDeclaration ||
@@ -340,7 +341,7 @@ export class JSONTransform extends Visitor {
340
341
  continue;
341
342
  if (hasCustomSerde)
342
343
  throwError("Lazy fields (@lazy / JSON.Lazy<T> / @json({ lazy })) are not supported " +
343
- "on a class with a custom @serializer/@deserializer the custom methods " +
344
+ "on a class with a custom @serializer/@deserializer - the custom methods " +
344
345
  "bypass the generated (de)serializer, so the deferred slot is never filled. " +
345
346
  "Remove the lazy marker or the custom (de)serializer.", fd.range);
346
347
  const fname = fd.name.text;
@@ -356,6 +357,13 @@ export class JSONTransform extends Visitor {
356
357
  : "null";
357
358
  const fdInit = fd.initializer;
358
359
  const fieldDefault = fdInit ? toString(fdInit) : null;
360
+ const refDefault = fieldDefault != null
361
+ ? fieldDefault
362
+ : !storesScalar && baseT == T && !baseT.startsWith("StaticArray<")
363
+ ? baseT == "string" || baseT == "String"
364
+ ? '""'
365
+ : `new ${baseT}()`
366
+ : null;
359
367
  __hasLazy = true;
360
368
  const omitIfDeco = decos?.find((d) => d.name.text === "omitif");
361
369
  lazyInner.set("__" + fname + "_lz", {
@@ -397,8 +405,8 @@ export class JSONTransform extends Visitor {
397
405
  ` this.__${fname}_lz = ((<u64>0xffffffff) << 32) | ${encVal("value")};\n}`,
398
406
  ]
399
407
  : [
400
- `@alias(${key}) private __${fname}_lz: u64 = ${fieldDefault != null ? "u64.MAX_VALUE" : "0"};`,
401
- `private __${fname}_val: ${valueType} = ${fieldDefault ?? valueDefault};`,
408
+ `@alias(${key}) private __${fname}_lz: u64 = ${refDefault != null ? "u64.MAX_VALUE" : "0"};`,
409
+ `private __${fname}_val: ${valueType} = ${refDefault ?? valueDefault};`,
402
410
  `get ${fname}(): ${T} {\n` +
403
411
  ` const __lz = this.__${fname}_lz;\n` +
404
412
  ` if (__lz != 0 && __lz != u64.MAX_VALUE) {\n` +
@@ -411,6 +419,9 @@ export class JSONTransform extends Visitor {
411
419
  ` this.__${fname}_lz = u64.MAX_VALUE;\n}`,
412
420
  ]).map((src) => SimpleParser.parseClassMember(src, node));
413
421
  node.members.splice(i, 1, ...lowered);
422
+ if (!packScalar && refDefault != null) {
423
+ __lazyValInits.set(`__${fname}_lz`, ` store<${valueType}>(changetype<usize>(this), ${refDefault}, offsetof<this>(${JSON.stringify(`__${fname}_val`)}));\n`);
424
+ }
414
425
  }
415
426
  if (__hasLazy) {
416
427
  node.members.push(SimpleParser.parseClassMember(`private __src: string = "";`, node), SimpleParser.parseClassMember(`__SET_SRC(s: string): void { this.__src = s; }`, node));
@@ -569,7 +580,7 @@ export class JSONTransform extends Visitor {
569
580
  else if (isString(type) || isPrimitive(type)) {
570
581
  return types;
571
582
  }
572
- else if (["JSON.Box", "JSON.Obj", "JSON.Value", "JSON.Raw"].includes(type)) {
583
+ else if (["JSON.Box", "JSON.Obj", "JSON.Arr", "JSON.Value", "JSON.Raw"].includes(type)) {
573
584
  return types;
574
585
  }
575
586
  else if (node.isGeneric &&
@@ -665,9 +676,9 @@ export class JSONTransform extends Visitor {
665
676
  this.visitedClasses.add(fullClassPath);
666
677
  const requestedFastPath = USE_FAST_PATH;
667
678
  let SERIALIZE = "__SERIALIZE(ptr: usize): void {\n";
668
- let INITIALIZE = "@inline __INITIALIZE(): this {\n";
679
+ let INITIALIZE = " __INITIALIZE(): this {\n";
669
680
  let DESERIALIZE = "__DESERIALIZE_SLOW<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): usize {\n";
670
- let DESERIALIZE_FAST = "@inline __DESERIALIZE_FAST<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): usize {\n";
681
+ let DESERIALIZE_FAST = "__DESERIALIZE_FAST<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): usize {\n";
671
682
  let DESERIALIZE_CUSTOM = "";
672
683
  let SERIALIZE_CUSTOM = "";
673
684
  if (DEBUG > 0)
@@ -773,8 +784,7 @@ export class JSONTransform extends Visitor {
773
784
  if (!deserializer.decorators.some((v) => v.name.text == "inline")) {
774
785
  deserializer.decorators.push(Node.createDecorator(Node.createIdentifierExpression("inline", deserializer.range), null, deserializer.range));
775
786
  }
776
- DESERIALIZE_CUSTOM +=
777
- " @inline __DESERIALIZE_CUSTOM(data: string): this {\n";
787
+ DESERIALIZE_CUSTOM += " __DESERIALIZE_CUSTOM(data: string): this {\n";
778
788
  DESERIALIZE_CUSTOM +=
779
789
  " return this." + deserializer.name.text + "(data);\n";
780
790
  DESERIALIZE_CUSTOM += " }\n";
@@ -841,6 +851,10 @@ export class JSONTransform extends Visitor {
841
851
  this.schema.static = false;
842
852
  break;
843
853
  }
854
+ case "optional": {
855
+ mem.flags.set(PropertyFlags.Optional, null);
856
+ break;
857
+ }
844
858
  case "omitnull": {
845
859
  if (isPrimitive(type)) {
846
860
  throwError("@omitnull cannot be used on primitive types!", member.range);
@@ -857,11 +871,11 @@ export class JSONTransform extends Visitor {
857
871
  }
858
872
  this.schema.members.push(mem);
859
873
  }
860
- if (!this.schema.static)
861
- this.schema.members = sortMembers(this.schema.members);
874
+ this.schema.members = sortMembers(this.schema.members);
862
875
  const hasOmitIfMembers = this.schema.members.some((v) => v.flags.has(PropertyFlags.OmitIf));
863
876
  const hasOmitNullMembers = this.schema.members.some((v) => v.flags.has(PropertyFlags.OmitNull));
864
- const hasOptionalMembers = hasOmitIfMembers || hasOmitNullMembers;
877
+ const hasExplicitOptionalMembers = this.schema.members.some((v) => v.flags.has(PropertyFlags.Optional));
878
+ const hasOptionalMembers = hasOmitIfMembers || hasOmitNullMembers || hasExplicitOptionalMembers;
865
879
  const hasLazyMembers = this.schema.members.some((v) => v.flags.has(PropertyFlags.Lazy));
866
880
  const supportsFastOptionalPath = requestedFastPath && hasOptionalMembers;
867
881
  const hasTypeParams = !!node.typeParameters && node.typeParameters.length > 0;
@@ -942,12 +956,10 @@ export class JSONTransform extends Visitor {
942
956
  const aliasName = JSON.stringify(member.alias || member.name);
943
957
  const realName = member.name;
944
958
  if (member.value) {
945
- if (member.value != "null" &&
946
- member.value != "0" &&
947
- member.value != "0.0" &&
948
- member.value != "false") {
949
- INITIALIZE += ` store<${member.type}>(changetype<usize>(this), ${member.value}, offsetof<this>(${JSON.stringify(member.name)}));\n`;
950
- }
959
+ INITIALIZE += ` store<${member.type}>(changetype<usize>(this), ${member.value}, offsetof<this>(${JSON.stringify(member.name)}));\n`;
960
+ const __valInit = __lazyValInits.get(member.name);
961
+ if (__valInit)
962
+ INITIALIZE += __valInit;
951
963
  }
952
964
  else if (member.generic) {
953
965
  INITIALIZE += ` if (isManaged<nonnull<${member.type}>>() || isReference<nonnull<${member.type}>>()) {\n`;
@@ -974,6 +986,9 @@ export class JSONTransform extends Visitor {
974
986
  INITIALIZE += ` store<${member.type}>(changetype<usize>(this), "", offsetof<this>(${JSON.stringify(member.name)}));\n`;
975
987
  }
976
988
  }
989
+ else {
990
+ INITIALIZE += ` store<${member.type}>(changetype<usize>(this), null, offsetof<this>(${JSON.stringify(member.name)}));\n`;
991
+ }
977
992
  const SIMD_ENABLED = this.program.options.hasFeature(16);
978
993
  if (!isRegular &&
979
994
  !member.flags.has(PropertyFlags.OmitIf) &&
@@ -1116,7 +1131,7 @@ export class JSONTransform extends Visitor {
1116
1131
  type.startsWith("JSON.Box<") ||
1117
1132
  isEnum(type, this.sources.get(this.schema.node.range.source), this.parser))
1118
1133
  sortedMembers.number.push(member);
1119
- else if (isArray(type))
1134
+ else if (isArray(type) || type == "JSON.Arr" || type == "Arr")
1120
1135
  sortedMembers.array.push(member);
1121
1136
  else
1122
1137
  sortedMembers.object.push(member);
@@ -1337,9 +1352,18 @@ export class JSONTransform extends Visitor {
1337
1352
  out.push(` if (changetype<usize>(value) == 0) {`);
1338
1353
  out.push(` value = changetype<${resolvedType}>(__new(offsetof<nonnull<${resolvedType}>>(), idof<nonnull<${resolvedType}>>()));`);
1339
1354
  out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
1355
+ out.push(` changetype<nonnull<${resolvedType}>>(value).__INITIALIZE();`);
1340
1356
  out.push(" }");
1341
- out.push(` ${srcPtr} = changetype<nonnull<${resolvedType}>>(value).__DESERIALIZE_FAST<${resolvedType}>(${valuePtr}, srcEnd, value);`);
1342
- out.push(` if (!${srcPtr}) break;`);
1357
+ out.push(` const __fe = changetype<nonnull<${resolvedType}>>(value).__DESERIALIZE_FAST<${resolvedType}>(${valuePtr}, srcEnd, value);`);
1358
+ out.push(` if (__fe) {`);
1359
+ out.push(` ${srcPtr} = __fe;`);
1360
+ out.push(` } else {`);
1361
+ out.push(` const __ve = JSON.Util.scanValueEnd<${resolvedType}>(${valuePtr}, srcEnd);`);
1362
+ out.push(` if (!__ve) break;`);
1363
+ out.push(` changetype<nonnull<${resolvedType}>>(value).__INITIALIZE();`);
1364
+ out.push(` changetype<nonnull<${resolvedType}>>(value).__DESERIALIZE_SLOW<${resolvedType}>(${valuePtr}, __ve, value);`);
1365
+ out.push(` ${srcPtr} = __ve;`);
1366
+ out.push(` }`);
1343
1367
  if (member.node.type.isNullable) {
1344
1368
  out.push(" }");
1345
1369
  }
@@ -1454,13 +1478,24 @@ export class JSONTransform extends Visitor {
1454
1478
  out.push(" if (changetype<usize>(item) == 0) {");
1455
1479
  out.push(` item = changetype<${valueType}>(__new(offsetof<nonnull<${valueType}>>(), idof<nonnull<${valueType}>>()));`);
1456
1480
  out.push(" unchecked((value[index] = item));");
1481
+ out.push(` changetype<nonnull<${valueType}>>(item).__INITIALIZE();`);
1457
1482
  out.push(" }");
1458
1483
  out.push(" } else {");
1459
1484
  out.push(` item = changetype<${valueType}>(__new(offsetof<nonnull<${valueType}>>(), idof<nonnull<${valueType}>>()));`);
1460
1485
  out.push(" value.push(item);");
1486
+ out.push(` changetype<nonnull<${valueType}>>(item).__INITIALIZE();`);
1461
1487
  out.push(" }");
1462
- out.push(` ${srcPtr} = changetype<nonnull<${valueType}>>(item).__DESERIALIZE_FAST<${valueType}>(${srcPtr}, srcEnd, item);`);
1463
- out.push(` if (!${srcPtr}) break;`);
1488
+ out.push(` const __es = ${srcPtr};`);
1489
+ out.push(` const __ee = changetype<nonnull<${valueType}>>(item).__DESERIALIZE_FAST<${valueType}>(${srcPtr}, srcEnd, item);`);
1490
+ out.push(` if (__ee) {`);
1491
+ out.push(` ${srcPtr} = __ee;`);
1492
+ out.push(` } else {`);
1493
+ out.push(` const __ve = JSON.Util.scanValueEnd<${valueType}>(__es, srcEnd);`);
1494
+ out.push(` if (!__ve) break;`);
1495
+ out.push(` changetype<nonnull<${valueType}>>(item).__INITIALIZE();`);
1496
+ out.push(` changetype<nonnull<${valueType}>>(item).__DESERIALIZE_SLOW<${valueType}>(__es, __ve, item);`);
1497
+ out.push(` ${srcPtr} = __ve;`);
1498
+ out.push(` }`);
1464
1499
  out.push(" index++;");
1465
1500
  out.push(` ${srcPtr} = JSON.Util.skipWhitespace(${srcPtr}, srcEnd);`);
1466
1501
  out.push(` const code = load<u16>(${srcPtr});`);
@@ -1481,7 +1516,7 @@ export class JSONTransform extends Visitor {
1481
1516
  out.push("}");
1482
1517
  return out;
1483
1518
  }
1484
- out.push(` ${srcPtr} = __deserializeArrayField_SWAR<${resolvedType}>(${valuePtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
1519
+ out.push(` ${srcPtr} = __deserializeArrayField_SWAR<nonnull<${resolvedType}>>(${valuePtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
1485
1520
  out.push(` if (!${srcPtr}) break;`);
1486
1521
  if (member.node.type.isNullable) {
1487
1522
  out.push(" }");
@@ -1502,6 +1537,7 @@ export class JSONTransform extends Visitor {
1502
1537
  }
1503
1538
  else if (resolvedType == "JSON.Value" ||
1504
1539
  resolvedType == "JSON.Obj" ||
1540
+ resolvedType == "JSON.Arr" ||
1505
1541
  isEnum(resolvedType, this.sources.get(this.schema.node.range.source), this.parser)) {
1506
1542
  out.push("break;");
1507
1543
  }
@@ -1531,29 +1567,8 @@ export class JSONTransform extends Visitor {
1531
1567
  }
1532
1568
  return calls;
1533
1569
  };
1534
- const chunkFastBlocksOptional = (blocks, tag, callIndent, needsKp) => {
1535
- if (blocks.length <= FAST_CHUNK_SIZE)
1536
- return blocks.join("");
1537
- let calls = "";
1538
- for (let c = 0; c < blocks.length; c += FAST_CHUNK_SIZE) {
1539
- const name = `__DESERIALIZE_FAST_${tag}_${fastChunkId++}`;
1540
- const body = blocks
1541
- .slice(c, c + FAST_CHUNK_SIZE)
1542
- .join("")
1543
- .replace(/\bbreak;/g, "return 0;");
1544
- fastChunkMethods.push(`${name}(srcStart: usize, srcEnd: usize, dst: usize, seenAny: bool): u64 {\n` +
1545
- (needsKp ? " let kp: usize = 0;\n" : "") +
1546
- `${body}\n` +
1547
- ` return (<u64>srcStart) | ((<u64>(seenAny ? 1 : 0)) << 32);\n}`);
1548
- calls +=
1549
- `${callIndent}{\n` +
1550
- `${callIndent} const __r = this.${name}(srcStart, srcEnd, dst, seenAny);\n` +
1551
- `${callIndent} if (__r == 0) break;\n` +
1552
- `${callIndent} srcStart = <usize>(<u32>__r);\n` +
1553
- `${callIndent} seenAny = (<u32>(__r >>> 32)) != 0;\n` +
1554
- `${callIndent}}\n`;
1555
- }
1556
- return calls;
1570
+ const chunkFastBlocksOptional = (blocks, _tag, _callIndent, _needsKp) => {
1571
+ return blocks.join("");
1557
1572
  };
1558
1573
  DESERIALIZE_FAST += indent + "const start = srcStart;\n";
1559
1574
  DESERIALIZE_FAST += indent + "const dst = changetype<usize>(out);\n";
@@ -2520,9 +2535,9 @@ export class JSONTransform extends Visitor {
2520
2535
  .find((s) => s.name == name) || null);
2521
2536
  }
2522
2537
  generateEmptyMethods(node) {
2523
- const SERIALIZE_EMPTY = "@inline __SERIALIZE(ptr: usize): void {\n bs.proposeSize(4);\n store<u32>(bs.offset, 8192123);\n bs.offset += 4;\n}";
2524
- const INITIALIZE_EMPTY = "@inline __INITIALIZE(): this {\n return this;\n}";
2525
- const DESERIALIZE_SLOW_EMPTY = "@inline __DESERIALIZE_SLOW<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): usize {\n return srcEnd;\n}";
2538
+ const SERIALIZE_EMPTY = "__SERIALIZE(ptr: usize): void {\n bs.proposeSize(4);\n store<u32>(bs.offset, 8192123);\n bs.offset += 4;\n}";
2539
+ const INITIALIZE_EMPTY = "__INITIALIZE(): this {\n return this;\n}";
2540
+ const DESERIALIZE_SLOW_EMPTY = "__DESERIALIZE_SLOW<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): usize {\n return srcEnd;\n}";
2526
2541
  if (DEBUG > 0) {
2527
2542
  console.log(SERIALIZE_EMPTY);
2528
2543
  console.log(INITIALIZE_EMPTY);
@@ -2698,9 +2713,11 @@ export class JSONTransform extends Visitor {
2698
2713
  "Date",
2699
2714
  "JSON.Value",
2700
2715
  "JSON.Obj",
2716
+ "JSON.Arr",
2701
2717
  "JSON.Raw",
2702
2718
  "Value",
2703
2719
  "Obj",
2720
+ "Arr",
2704
2721
  "Raw",
2705
2722
  ...this.schemas
2706
2723
  .get(this.schema.node.range.source.internalPath)
@@ -2729,6 +2746,8 @@ var JSONMode;
2729
2746
  JSONMode[JSONMode["NAIVE"] = 2] = "NAIVE";
2730
2747
  })(JSONMode || (JSONMode = {}));
2731
2748
  let MODE = JSONMode.SWAR;
2749
+ let MODE_TEXT = "SWAR";
2750
+ const STAGES = process.env["JSON_STAGES"] !== undefined;
2732
2751
  export default class Transformer extends Transform {
2733
2752
  afterInitialize(program) {
2734
2753
  if (program.options.hasFeature(16))
@@ -2749,6 +2768,21 @@ export default class Transformer extends Transform {
2749
2768
  }
2750
2769
  }
2751
2770
  }
2771
+ switch (MODE) {
2772
+ case JSONMode.SWAR:
2773
+ MODE_TEXT = "SWAR";
2774
+ break;
2775
+ case JSONMode.SIMD:
2776
+ MODE_TEXT = "SIMD";
2777
+ break;
2778
+ case JSONMode.NAIVE:
2779
+ MODE_TEXT = "NAIVE";
2780
+ break;
2781
+ }
2782
+ if (STAGES)
2783
+ console.log("[transform]: Finished initializing transformer in " +
2784
+ MODE_TEXT +
2785
+ " mode");
2752
2786
  program.registerConstantInteger("JSON_MODE", Type.i32, i64_new(MODE));
2753
2787
  if (JSON_CACHE_CONFIG.enabled) {
2754
2788
  program.registerConstantInteger("JSON_CACHE", Type.bool, i64_one);
@@ -2757,6 +2791,8 @@ export default class Transformer extends Transform {
2757
2791
  }
2758
2792
  afterParse(parser) {
2759
2793
  const transformer = new JSONTransform();
2794
+ if (STAGES)
2795
+ console.log("[transform]: Walking AST and generating schemas");
2760
2796
  const sources = parser.sources
2761
2797
  .filter((source) => {
2762
2798
  const p = source.internalPath;
@@ -2812,23 +2848,12 @@ export default class Transformer extends Transform {
2812
2848
  source.sourceKind = 2;
2813
2849
  }
2814
2850
  }
2851
+ if (STAGES)
2852
+ console.log("[transform]: Finished generating " +
2853
+ transformer.schemas.size +
2854
+ " schemas");
2815
2855
  }
2816
2856
  }
2817
- function sortMembers(members) {
2818
- return members.sort((a, b) => {
2819
- const aMove = a.flags.has(PropertyFlags.OmitIf) || a.flags.has(PropertyFlags.OmitNull);
2820
- const bMove = b.flags.has(PropertyFlags.OmitIf) || b.flags.has(PropertyFlags.OmitNull);
2821
- if (aMove && !bMove) {
2822
- return -1;
2823
- }
2824
- else if (!aMove && bMove) {
2825
- return 1;
2826
- }
2827
- else {
2828
- return 0;
2829
- }
2830
- });
2831
- }
2832
2857
  function toU16(data, offset = 0) {
2833
2858
  return data.charCodeAt(offset + 0);
2834
2859
  }
@@ -3011,6 +3036,8 @@ function lazyTypeCost(type, source, parser) {
3011
3036
  base === "Value" ||
3012
3037
  base === "JSON.Obj" ||
3013
3038
  base === "Obj" ||
3039
+ base === "JSON.Arr" ||
3040
+ base === "Arr" ||
3014
3041
  base === "JSON.Raw" ||
3015
3042
  base === "Raw")
3016
3043
  return 15;
@@ -3076,6 +3103,8 @@ function estimatedSerializedByteSize(type, source, parser) {
3076
3103
  }
3077
3104
  else if (baseType == "JSON.Obj" ||
3078
3105
  baseType == "Obj" ||
3106
+ baseType == "JSON.Arr" ||
3107
+ baseType == "Arr" ||
3079
3108
  baseType == "JSON.Raw" ||
3080
3109
  baseType == "Raw" ||
3081
3110
  baseType == "JSON.Value" ||
@@ -3134,6 +3163,21 @@ export function stripNull(type) {
3134
3163
  }
3135
3164
  return type;
3136
3165
  }
3166
+ function sortMembers(members) {
3167
+ return members.sort((a, b) => {
3168
+ const aMove = a.flags.has(PropertyFlags.OmitIf) || a.flags.has(PropertyFlags.OmitNull);
3169
+ const bMove = b.flags.has(PropertyFlags.OmitIf) || b.flags.has(PropertyFlags.OmitNull);
3170
+ if (aMove && !bMove) {
3171
+ return -1;
3172
+ }
3173
+ else if (!aMove && bMove) {
3174
+ return 1;
3175
+ }
3176
+ else {
3177
+ return 0;
3178
+ }
3179
+ });
3180
+ }
3137
3181
  function getComparison(data) {
3138
3182
  switch (data.length << 1) {
3139
3183
  case 2: {
@@ -6,7 +6,8 @@ export declare enum PropertyFlags {
6
6
  OmitIf = 1,
7
7
  Raw = 2,
8
8
  Custom = 3,
9
- Lazy = 4
9
+ Lazy = 4,
10
+ Optional = 5
10
11
  }
11
12
  export declare class Property {
12
13
  name: string;