json-as 1.5.0 → 1.6.1
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.
- package/CHANGELOG.md +10 -1
- package/README.md +42 -0
- package/assembly/deserialize/error.ts +21 -0
- package/assembly/deserialize/index/arbitrary.ts +29 -8
- package/assembly/deserialize/index/array.ts +2 -1
- package/assembly/deserialize/index/bool.ts +1 -1
- package/assembly/deserialize/index/object.ts +3 -0
- package/assembly/deserialize/index/string.ts +37 -0
- package/assembly/deserialize/index.ts +0 -1
- package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
- package/assembly/deserialize/naive/array/array.ts +1 -0
- package/assembly/deserialize/naive/array/bool.ts +21 -11
- package/assembly/deserialize/naive/array/box.ts +13 -33
- package/assembly/deserialize/naive/array/float.ts +23 -13
- package/assembly/deserialize/naive/array/integer.ts +33 -21
- package/assembly/deserialize/naive/array/object.ts +1 -0
- package/assembly/deserialize/naive/array/string.ts +29 -16
- package/assembly/deserialize/naive/bool.ts +17 -5
- package/assembly/deserialize/naive/float.ts +1 -2
- package/assembly/deserialize/naive/map.ts +339 -39
- package/assembly/deserialize/naive/object.ts +122 -65
- package/assembly/deserialize/naive/raw.ts +13 -1
- package/assembly/deserialize/naive/set.ts +0 -1
- package/assembly/deserialize/naive/staticarray.ts +3 -2
- package/assembly/deserialize/naive/string.ts +41 -52
- package/assembly/deserialize/parseMode.ts +216 -0
- package/assembly/deserialize/simd/array/integer.ts +6 -3
- package/assembly/deserialize/simd/float.ts +35 -29
- package/assembly/deserialize/simd/string.ts +79 -17
- package/assembly/deserialize/string-validation.ts +34 -0
- package/assembly/deserialize/swar/array/array.ts +63 -13
- package/assembly/deserialize/swar/array/bool.ts +14 -3
- package/assembly/deserialize/swar/array/float.ts +394 -13
- package/assembly/deserialize/swar/array/generic.ts +12 -2
- package/assembly/deserialize/swar/array/integer.ts +22 -62
- package/assembly/deserialize/swar/array/object.ts +11 -2
- package/assembly/deserialize/swar/array/shared.ts +1 -1
- package/assembly/deserialize/swar/array/string.ts +34 -5
- package/assembly/deserialize/swar/array/struct.ts +109 -15
- package/assembly/deserialize/swar/float.ts +19 -12
- package/assembly/deserialize/swar/string.ts +70 -111
- package/assembly/index.d.ts +6 -0
- package/assembly/index.ts +411 -33
- package/assembly/serialize/index/bool.ts +1 -1
- package/assembly/serialize/index/float.ts +1 -5
- package/assembly/serialize/index/integer.ts +1 -1
- package/assembly/serialize/index/object.ts +17 -0
- package/assembly/serialize/naive/array.ts +64 -0
- package/assembly/serialize/naive/float.ts +0 -5
- package/assembly/serialize/naive/map.ts +37 -0
- package/assembly/serialize/naive/set.ts +0 -1
- package/assembly/serialize/naive/staticarray.ts +0 -1
- package/assembly/serialize/naive/string.ts +0 -6
- package/assembly/serialize/naive/typedarray.ts +0 -1
- package/assembly/util/eisel-lemire.ts +179 -0
- package/assembly/util/prettyWhitespaceSimd.ts +69 -0
- package/assembly/util/scanValueEndSimd.ts +59 -36
- package/assembly/util/validateJson.ts +338 -0
- package/lib/as-bs.ts +28 -6
- package/package.json +17 -7
- package/transform/lib/index.d.ts +6 -0
- package/transform/lib/index.js +773 -112
- package/transform/lib/types.d.ts +1 -0
- package/transform/lib/types.js +3 -0
- package/assembly/deserialize/index/struct.ts +0 -1
- package/assembly/deserialize/naive/struct.ts +0 -21
package/assembly/index.ts
CHANGED
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
serializeSet,
|
|
11
11
|
serializeStaticArray,
|
|
12
12
|
serializeBool,
|
|
13
|
+
serializeBoolUnsafe,
|
|
13
14
|
serializeInteger,
|
|
14
|
-
|
|
15
|
+
serializeIntegerUnsafe,
|
|
15
16
|
serializeFloat32,
|
|
16
17
|
serializeFloat64,
|
|
17
18
|
serializeStruct,
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
serializeTypedArray,
|
|
25
26
|
} from "./serialize";
|
|
26
27
|
import {
|
|
27
|
-
|
|
28
|
+
deserializeBooleanCode,
|
|
28
29
|
deserializeArray,
|
|
29
30
|
deserializeFloat,
|
|
30
31
|
deserializeMap,
|
|
@@ -42,7 +43,21 @@ import {
|
|
|
42
43
|
deserializeTypedArray,
|
|
43
44
|
setParseSrc,
|
|
44
45
|
getParseSrc,
|
|
46
|
+
markProductionParseError,
|
|
47
|
+
takeProductionParseError,
|
|
48
|
+
failProductionParse as failProductionParseInternal,
|
|
45
49
|
} from "./deserialize";
|
|
50
|
+
import {
|
|
51
|
+
beginStringFieldTrace,
|
|
52
|
+
beginObjectFieldTrace,
|
|
53
|
+
endStringFieldTrace,
|
|
54
|
+
isPrettyParse,
|
|
55
|
+
objectFieldTraceMask,
|
|
56
|
+
objectFieldTraceSeparator,
|
|
57
|
+
objectFieldTraceTier,
|
|
58
|
+
recordObjectFieldTrace,
|
|
59
|
+
setPrettyParse,
|
|
60
|
+
} from "./deserialize/parseMode";
|
|
46
61
|
import {
|
|
47
62
|
BACK_SLASH,
|
|
48
63
|
BRACE_LEFT,
|
|
@@ -62,6 +77,11 @@ import { ptrToStr } from "./util/ptrToStr";
|
|
|
62
77
|
import { atoi, bytes, scanStringEnd } from "./util";
|
|
63
78
|
import { scanValueEnd_SIMD } from "./util/scanValueEndSimd";
|
|
64
79
|
import { scanValueEnd_SWAR } from "./util/scanValueEndSwar";
|
|
80
|
+
import {
|
|
81
|
+
hasLongPrettyIndent_SIMD,
|
|
82
|
+
skipPrettyWhitespace_SIMD,
|
|
83
|
+
} from "./util/prettyWhitespaceSimd";
|
|
84
|
+
import { normalizeJSONEncoding, validateJSON } from "./util/validateJson";
|
|
65
85
|
|
|
66
86
|
const VAL_QNAN: u64 = 0x7ffc000000000000; // boxed signature (quiet NaN)
|
|
67
87
|
const VAL_TAG_SHIFT: u8 = 45;
|
|
@@ -97,6 +117,13 @@ const STR_CLASS_UNKNOWN: u32 = 0;
|
|
|
97
117
|
const STR_CLASS_CLEAN: u32 = 1;
|
|
98
118
|
const STR_CLASS_ESCAPE: u32 = 2;
|
|
99
119
|
|
|
120
|
+
// One rooted immutable source/type pair is enough to make repeated parsing of
|
|
121
|
+
// an exact generated-default document allocation-only after its first match.
|
|
122
|
+
// The source reference prevents pointer reuse after GC; a different source or
|
|
123
|
+
// target type simply takes the normal matcher and replaces this cache on hit.
|
|
124
|
+
let LAST_DEFAULT_SOURCE: string | null = null;
|
|
125
|
+
let LAST_DEFAULT_TYPE: u32 = 0;
|
|
126
|
+
|
|
100
127
|
function valBoxed(w: u64): bool {
|
|
101
128
|
return (w & VAL_QNAN) == VAL_QNAN;
|
|
102
129
|
}
|
|
@@ -370,14 +397,113 @@ export namespace JSON {
|
|
|
370
397
|
}
|
|
371
398
|
|
|
372
399
|
export function parse<T>(data: string, out: T = __zero<T>()): T {
|
|
400
|
+
if (JSON_STRICT) {
|
|
401
|
+
data = normalizeJSONEncoding(data);
|
|
402
|
+
if (!validateJSON(data)) throw new Error("Invalid JSON syntax");
|
|
403
|
+
}
|
|
404
|
+
let managePretty = false;
|
|
405
|
+
let simdPretty = false;
|
|
406
|
+
if (isReference<T>() || isManaged<T>()) {
|
|
407
|
+
const type = changetype<nonnull<T>>(0);
|
|
408
|
+
// @ts-expect-error: marker supplied by the json-as transform
|
|
409
|
+
if (isDefined(type.__DESERIALIZE_PRETTY_SPECIALIZED)) {
|
|
410
|
+
managePretty = true;
|
|
411
|
+
if (
|
|
412
|
+
ASC_FEATURE_SIMD &&
|
|
413
|
+
data.length >= 16_384 &&
|
|
414
|
+
data.length <= 2_000_000
|
|
415
|
+
) {
|
|
416
|
+
const start = changetype<usize>(data);
|
|
417
|
+
const end = start + ((<usize>data.length) << 1);
|
|
418
|
+
let valueStart = start;
|
|
419
|
+
while (valueStart < end && JSON.Util.isSpace(load<u16>(valueStart)))
|
|
420
|
+
valueStart += 2;
|
|
421
|
+
if (valueStart + 2 < end) {
|
|
422
|
+
const open = load<u16>(valueStart);
|
|
423
|
+
const next = load<u16>(valueStart, 2);
|
|
424
|
+
simdPretty =
|
|
425
|
+
(open == BRACE_LEFT || open == BRACKET_LEFT) &&
|
|
426
|
+
(next == 10 || next == 13) &&
|
|
427
|
+
hasLongPrettyIndent_SIMD(valueStart, end);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Resolve an exact generated-default hit directly at the public boundary.
|
|
433
|
+
// This avoids entering the general parser and avoids polling the cold error
|
|
434
|
+
// flag for a source that has already been fully compared. Generated matcher
|
|
435
|
+
// methods do not read `this`, so the compile-time type sentinel is a safe
|
|
436
|
+
// receiver until a match tells us an object allocation is actually needed.
|
|
437
|
+
if ((isReference<T>() || isManaged<T>()) && changetype<usize>(out) == 0) {
|
|
438
|
+
const type = changetype<nonnull<T>>(0);
|
|
439
|
+
// @ts-expect-error: marker supplied by the json-as transform
|
|
440
|
+
if (isDefined(type.__DESERIALIZE_DEFAULT)) {
|
|
441
|
+
const start = changetype<usize>(data);
|
|
442
|
+
const typeId = idof<nonnull<T>>();
|
|
443
|
+
const knownMatch =
|
|
444
|
+
LAST_DEFAULT_TYPE == typeId &&
|
|
445
|
+
changetype<usize>(LAST_DEFAULT_SOURCE) == start;
|
|
446
|
+
if (knownMatch) {
|
|
447
|
+
// @ts-expect-error: paired generated allocation-only factory
|
|
448
|
+
return changetype<T>(type.__DESERIALIZE_DEFAULT_NEW());
|
|
449
|
+
}
|
|
450
|
+
const end = start + ((<usize>data.length) << 1);
|
|
451
|
+
// @ts-expect-error: generated method is a receiver-independent factory
|
|
452
|
+
const value = type.__DESERIALIZE_DEFAULT(start, end);
|
|
453
|
+
if (changetype<usize>(value) != 0) {
|
|
454
|
+
LAST_DEFAULT_SOURCE = data;
|
|
455
|
+
LAST_DEFAULT_TYPE = typeId;
|
|
456
|
+
return changetype<T>(value);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
// Generated scalar-only schemas cannot create lazy JSON.Obj/Arr/Value
|
|
461
|
+
// slices. Avoid two global reference writes (and their barriers) for these
|
|
462
|
+
// very small, latency-sensitive parses.
|
|
463
|
+
if (isReference<T>() || isManaged<T>()) {
|
|
464
|
+
const type = changetype<nonnull<T>>(0);
|
|
465
|
+
// @ts-expect-error: marker supplied by the json-as transform
|
|
466
|
+
if (isDefined(type.__DESERIALIZE_SOURCE_FREE)) {
|
|
467
|
+
let result: T;
|
|
468
|
+
beginStringFieldTrace(data, changetype<usize>(out), idof<nonnull<T>>());
|
|
469
|
+
if (managePretty) {
|
|
470
|
+
const prevPretty = isPrettyParse();
|
|
471
|
+
setPrettyParse(simdPretty);
|
|
472
|
+
result = parseInternal<T>(data, out);
|
|
473
|
+
setPrettyParse(prevPretty);
|
|
474
|
+
} else {
|
|
475
|
+
result = parseInternal<T>(data, out);
|
|
476
|
+
}
|
|
477
|
+
const failed = takeProductionParseError();
|
|
478
|
+
endStringFieldTrace(!failed);
|
|
479
|
+
if (failed) throw new Error("Incomplete or malformed JSON value");
|
|
480
|
+
return result;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
373
483
|
// Anchor the source for any lazy JSON.Obj/JSON.Value built while parsing, so
|
|
374
484
|
// their stored slice pointers (into `data`'s buffer) stay valid and resolve
|
|
375
485
|
// against the right string. Save/restore makes nested parses (e.g. a custom
|
|
376
486
|
// deserializer calling JSON.parse, or JSON.Obj.from) re-entrant-safe.
|
|
377
487
|
const prevSrc = getParseSrc();
|
|
378
488
|
setParseSrc(data);
|
|
379
|
-
|
|
489
|
+
if (isReference<T>() || isManaged<T>()) {
|
|
490
|
+
beginStringFieldTrace(data, changetype<usize>(out), idof<nonnull<T>>());
|
|
491
|
+
} else {
|
|
492
|
+
beginStringFieldTrace(data, 0, 0);
|
|
493
|
+
}
|
|
494
|
+
let result: T;
|
|
495
|
+
if (managePretty) {
|
|
496
|
+
const prevPretty = isPrettyParse();
|
|
497
|
+
setPrettyParse(simdPretty);
|
|
498
|
+
result = parseInternal<T>(data, out);
|
|
499
|
+
setPrettyParse(prevPretty);
|
|
500
|
+
} else {
|
|
501
|
+
result = parseInternal<T>(data, out);
|
|
502
|
+
}
|
|
380
503
|
setParseSrc(prevSrc);
|
|
504
|
+
const failed = takeProductionParseError();
|
|
505
|
+
endStringFieldTrace(!failed);
|
|
506
|
+
if (failed) throw new Error("Incomplete or unterminated JSON value");
|
|
381
507
|
return result;
|
|
382
508
|
}
|
|
383
509
|
|
|
@@ -393,7 +519,9 @@ export namespace JSON {
|
|
|
393
519
|
dataPtr += 2;
|
|
394
520
|
const dataSize = dataEnd - dataPtr;
|
|
395
521
|
if (isBoolean<T>()) {
|
|
396
|
-
|
|
522
|
+
const code = deserializeBooleanCode(dataPtr, dataPtr + dataSize);
|
|
523
|
+
if (code == 0) markProductionParseError();
|
|
524
|
+
return (code == 2) as T;
|
|
397
525
|
} else if (isInteger<T>()) {
|
|
398
526
|
return isSigned<T>()
|
|
399
527
|
? deserializeInteger<T>(dataPtr, dataPtr + dataSize)
|
|
@@ -407,7 +535,15 @@ export namespace JSON {
|
|
|
407
535
|
) {
|
|
408
536
|
return null;
|
|
409
537
|
} else if (isString<T>()) {
|
|
410
|
-
|
|
538
|
+
// Whole-string decoders require quote-inclusive bounds. Trim only the
|
|
539
|
+
// insignificant suffix here; the normal no-whitespace path pays one
|
|
540
|
+
// predicted-not-taken branch.
|
|
541
|
+
let stringEnd = dataEnd;
|
|
542
|
+
while (stringEnd > dataPtr && JSON.Util.isSpace(load<u16>(stringEnd - 2)))
|
|
543
|
+
stringEnd -= 2;
|
|
544
|
+
const value = deserializeString(dataPtr, stringEnd);
|
|
545
|
+
if (changetype<usize>(value) == 0) markProductionParseError();
|
|
546
|
+
return value as T;
|
|
411
547
|
} else {
|
|
412
548
|
let type: nonnull<T> = changetype<nonnull<T>>(0);
|
|
413
549
|
// @ts-expect-error: Defined by transform
|
|
@@ -427,20 +563,39 @@ export namespace JSON {
|
|
|
427
563
|
: changetype<nonnull<T>>(
|
|
428
564
|
__new(offsetof<nonnull<T>>(), idof<nonnull<T>>()),
|
|
429
565
|
);
|
|
430
|
-
// A
|
|
431
|
-
//
|
|
432
|
-
//
|
|
433
|
-
//
|
|
566
|
+
// A successful generated full-write path assigns every field. Zeroing a
|
|
567
|
+
// fresh payload is enough to make its reference slots safe while
|
|
568
|
+
// avoiding redundant default graph construction; a fast miss still
|
|
569
|
+
// initializes normally before the slow retry below.
|
|
434
570
|
// @ts-expect-error: Defined by transform
|
|
435
|
-
|
|
571
|
+
const fullWrite = isDefined(type.__DESERIALIZE_FULL_WRITE);
|
|
572
|
+
if (!reuse) {
|
|
573
|
+
if (fullWrite) {
|
|
574
|
+
memory.fill(changetype<usize>(obj), 0, offsetof<nonnull<T>>());
|
|
575
|
+
} else if (isDefined(type.__INITIALIZE)) {
|
|
576
|
+
// @ts-expect-error: Defined by transform
|
|
577
|
+
obj.__INITIALIZE();
|
|
578
|
+
}
|
|
579
|
+
}
|
|
436
580
|
// @ts-expect-error: Defined by transform
|
|
437
581
|
if (isDefined(type.__DESERIALIZE_FAST)) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
582
|
+
let fastEnd: usize;
|
|
583
|
+
// @ts-expect-error: Defined by transform in SIMD builds
|
|
584
|
+
if (
|
|
585
|
+
ASC_FEATURE_SIMD &&
|
|
586
|
+
isPrettyParse() &&
|
|
587
|
+
isDefined(type.__DESERIALIZE_FAST_PRETTY)
|
|
588
|
+
) {
|
|
589
|
+
// @ts-expect-error: Defined by transform
|
|
590
|
+
fastEnd = obj.__DESERIALIZE_FAST_PRETTY(
|
|
591
|
+
dataPtr,
|
|
592
|
+
dataPtr + dataSize,
|
|
593
|
+
obj,
|
|
594
|
+
);
|
|
595
|
+
} else {
|
|
596
|
+
// @ts-expect-error: Defined by transform
|
|
597
|
+
fastEnd = obj.__DESERIALIZE_FAST(dataPtr, dataPtr + dataSize, obj);
|
|
598
|
+
}
|
|
444
599
|
// A non-zero return means the fast path matched; accept it when only
|
|
445
600
|
// trailing whitespace remains (pretty-printed input ends with a
|
|
446
601
|
// newline, so the cursor stops just past `}` rather than at srcEnd).
|
|
@@ -459,7 +614,12 @@ export namespace JSON {
|
|
|
459
614
|
// @ts-expect-error: Defined by transform
|
|
460
615
|
if (isDefined(type.__DESERIALIZE_SLOW)) {
|
|
461
616
|
// @ts-expect-error: Defined by transform
|
|
462
|
-
obj.__DESERIALIZE_SLOW(
|
|
617
|
+
const slowEnd = obj.__DESERIALIZE_SLOW(
|
|
618
|
+
dataPtr,
|
|
619
|
+
dataPtr + dataSize,
|
|
620
|
+
obj,
|
|
621
|
+
);
|
|
622
|
+
if (slowEnd == 0) markProductionParseError();
|
|
463
623
|
// @ts-expect-error: Defined by transform for @lazy-field structs.
|
|
464
624
|
if (isDefined(obj.__SET_SRC)) obj.__SET_SRC(data);
|
|
465
625
|
return obj;
|
|
@@ -477,13 +637,15 @@ export namespace JSON {
|
|
|
477
637
|
// Reuse the caller-supplied array when given (no allocation); the
|
|
478
638
|
// element loop overwrites slots and trims length. Otherwise allocate.
|
|
479
639
|
// @ts-expect-error
|
|
480
|
-
|
|
640
|
+
const value = deserializeArray<nonnull<T>>(
|
|
481
641
|
dataPtr,
|
|
482
642
|
dataPtr + dataSize,
|
|
483
643
|
changetype<usize>(out) != 0
|
|
484
644
|
? changetype<usize>(out)
|
|
485
645
|
: changetype<usize>(instantiate<T>()),
|
|
486
646
|
);
|
|
647
|
+
if (changetype<usize>(value) == 0) markProductionParseError();
|
|
648
|
+
return value;
|
|
487
649
|
} else if (
|
|
488
650
|
type instanceof Int8Array ||
|
|
489
651
|
type instanceof Uint8Array ||
|
|
@@ -510,11 +672,13 @@ export namespace JSON {
|
|
|
510
672
|
} else if (type instanceof Map) {
|
|
511
673
|
// Reuse the caller-supplied map when given (keys overwrite in place).
|
|
512
674
|
// @ts-expect-error
|
|
513
|
-
|
|
675
|
+
const value = deserializeMap<nonnull<T>>(
|
|
514
676
|
dataPtr,
|
|
515
677
|
dataPtr + dataSize,
|
|
516
678
|
changetype<usize>(out),
|
|
517
679
|
);
|
|
680
|
+
if (changetype<usize>(value) == 0) markProductionParseError();
|
|
681
|
+
return value;
|
|
518
682
|
} else if (type instanceof Date) {
|
|
519
683
|
// @ts-expect-error
|
|
520
684
|
return deserializeDate(dataPtr, dataPtr + dataSize);
|
|
@@ -525,27 +689,33 @@ export namespace JSON {
|
|
|
525
689
|
// Reuse the caller-supplied JSON.Value handle when given (`out`); the
|
|
526
690
|
// deserializer writes the parsed bits into it. Otherwise allocate.
|
|
527
691
|
// @ts-expect-error
|
|
528
|
-
|
|
692
|
+
const value = deserializeArbitrary(
|
|
529
693
|
dataPtr,
|
|
530
694
|
dataPtr + dataSize,
|
|
531
695
|
changetype<usize>(out),
|
|
532
696
|
);
|
|
697
|
+
if (changetype<usize>(value) == 0) markProductionParseError();
|
|
698
|
+
return value;
|
|
533
699
|
} else if (type instanceof JSON.Obj) {
|
|
534
700
|
// Reuse the caller-supplied JSON.Obj (cleared, buffers kept). Otherwise allocate.
|
|
535
701
|
// @ts-expect-error
|
|
536
|
-
|
|
702
|
+
const value = deserializeObject(
|
|
537
703
|
dataPtr,
|
|
538
704
|
dataPtr + dataSize,
|
|
539
705
|
changetype<usize>(out),
|
|
540
706
|
);
|
|
707
|
+
if (changetype<usize>(value) == 0) markProductionParseError();
|
|
708
|
+
return value;
|
|
541
709
|
} else if (type instanceof JSON.Arr) {
|
|
542
710
|
// Reuse the caller-supplied JSON.Arr (cleared, buffers kept). Otherwise allocate.
|
|
543
711
|
// @ts-expect-error
|
|
544
|
-
|
|
712
|
+
const value = deserializeJsonArray(
|
|
545
713
|
dataPtr,
|
|
546
714
|
dataPtr + dataSize,
|
|
547
715
|
changetype<usize>(out),
|
|
548
716
|
);
|
|
717
|
+
if (changetype<usize>(value) == 0) markProductionParseError();
|
|
718
|
+
return value;
|
|
549
719
|
} else if (type instanceof JSON.Box) {
|
|
550
720
|
// @ts-expect-error
|
|
551
721
|
return new JSON.Box(parseBox(data, changetype<nonnull<T>>(0).value));
|
|
@@ -1257,11 +1427,37 @@ export namespace JSON {
|
|
|
1257
1427
|
_vused: i32 = 0;
|
|
1258
1428
|
/** Source string the lazy slot pointers index into; anchors it for GC. */
|
|
1259
1429
|
_src: string = "";
|
|
1430
|
+
/** Untouched source range for whole-object serialization passthrough. */
|
|
1431
|
+
_rawStart: usize = 0;
|
|
1432
|
+
_rawEnd: usize = 0;
|
|
1260
1433
|
private _index: StaticArray<i32> | null = null;
|
|
1261
1434
|
private _indexMask: i32 = 0;
|
|
1262
1435
|
|
|
1263
1436
|
constructor() {}
|
|
1264
1437
|
|
|
1438
|
+
/**
|
|
1439
|
+
* Pre-sizes the three flat parse buffers from the source span. The parser
|
|
1440
|
+
* still grows normally if an unusual key-heavy document exceeds the
|
|
1441
|
+
* estimate; ordinary API-shaped objects avoid the repeated 8→16→32...
|
|
1442
|
+
* allocation/copy ladder.
|
|
1443
|
+
*/
|
|
1444
|
+
reserveForParse(sourceUnits: usize): void {
|
|
1445
|
+
let keyNeed = <i32>((sourceUnits + 2) / 3);
|
|
1446
|
+
if (keyNeed < 16) keyNeed = 16;
|
|
1447
|
+
let keyCap = 16;
|
|
1448
|
+
while (keyCap < keyNeed) keyCap <<= 1;
|
|
1449
|
+
if (this._kbuf.length < keyCap) this._kbuf = new StaticArray<u16>(keyCap);
|
|
1450
|
+
|
|
1451
|
+
let slotNeed = <i32>(sourceUnits >> 6);
|
|
1452
|
+
if (slotNeed < 8) slotNeed = 8;
|
|
1453
|
+
let slotCap = 8;
|
|
1454
|
+
while (slotCap < slotNeed) slotCap <<= 1;
|
|
1455
|
+
if (this._kpos.length < slotCap)
|
|
1456
|
+
this._kpos = new StaticArray<i32>(slotCap);
|
|
1457
|
+
if (this._vals.length < slotCap)
|
|
1458
|
+
this._vals = new StaticArray<u64>(slotCap);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1265
1461
|
/**
|
|
1266
1462
|
* Gets the number of key-value pairs in the object.
|
|
1267
1463
|
*/
|
|
@@ -1458,6 +1654,11 @@ export namespace JSON {
|
|
|
1458
1654
|
private materializeSlot(i: i32): u64 {
|
|
1459
1655
|
const slot = unchecked(this._vals[i]);
|
|
1460
1656
|
if (!JSON.Value.slotIsLazy(slot)) return slot;
|
|
1657
|
+
// A materialized nested object can subsequently be mutated through the
|
|
1658
|
+
// returned reference. Stop whole-object passthrough so serialization
|
|
1659
|
+
// observes that mutation and walks the cached slots instead.
|
|
1660
|
+
this._rawStart = 0;
|
|
1661
|
+
this._rawEnd = 0;
|
|
1461
1662
|
const base = changetype<usize>(this._src);
|
|
1462
1663
|
const start = JSON.Value.slotPtr(slot, base);
|
|
1463
1664
|
const end = JSON.Value.slotEnd(slot, base, this.srcEnd());
|
|
@@ -1472,12 +1673,49 @@ export namespace JSON {
|
|
|
1472
1673
|
* per-key string allocation, no per-value object, no hashing.
|
|
1473
1674
|
*/
|
|
1474
1675
|
appendRawSlot(keyStart: usize, keyEnd: usize, bits: u64): void {
|
|
1676
|
+
this._rawStart = 0;
|
|
1677
|
+
this._rawEnd = 0;
|
|
1475
1678
|
const slotIndex = this._vused;
|
|
1476
1679
|
this.pushKeyBytes(keyStart, keyEnd, slotIndex);
|
|
1477
1680
|
this.pushValSlot(bits);
|
|
1478
1681
|
this.insertIndex(slotIndex);
|
|
1479
1682
|
}
|
|
1480
1683
|
|
|
1684
|
+
/**
|
|
1685
|
+
* Parser-only append for slots produced by parseSlotBits. Those bits are
|
|
1686
|
+
* either immediate scalars or source-relative Lazy slices, so they never
|
|
1687
|
+
* require a GC link. The common pre-sized case writes all three buffers
|
|
1688
|
+
* directly; unusual key-heavy documents retain the general growth path.
|
|
1689
|
+
*/
|
|
1690
|
+
appendParsedSlot(keyStart: usize, keyEnd: usize, bits: u64): void {
|
|
1691
|
+
this._rawStart = 0;
|
|
1692
|
+
this._rawEnd = 0;
|
|
1693
|
+
const slotIndex = this._vused;
|
|
1694
|
+
const len = <i32>((keyEnd - keyStart) >> 1);
|
|
1695
|
+
const keyPos = this._kused;
|
|
1696
|
+
if (
|
|
1697
|
+
keyPos + 1 + len <= this._kbuf.length &&
|
|
1698
|
+
slotIndex < this._kpos.length &&
|
|
1699
|
+
slotIndex < this._vals.length
|
|
1700
|
+
) {
|
|
1701
|
+
const keyBuf = changetype<usize>(this._kbuf);
|
|
1702
|
+
store<u16>(keyBuf + ((<usize>keyPos) << 1), <u16>len);
|
|
1703
|
+
if (len)
|
|
1704
|
+
memory.copy(
|
|
1705
|
+
keyBuf + ((<usize>(keyPos + 1)) << 1),
|
|
1706
|
+
keyStart,
|
|
1707
|
+
(<usize>len) << 1,
|
|
1708
|
+
);
|
|
1709
|
+
unchecked((this._kpos[slotIndex] = keyPos));
|
|
1710
|
+
unchecked((this._vals[slotIndex] = bits));
|
|
1711
|
+
this._kused = keyPos + 1 + len;
|
|
1712
|
+
this._vused = slotIndex + 1;
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
this.pushKeyBytes(keyStart, keyEnd, slotIndex);
|
|
1716
|
+
this.pushValSlot(bits);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1481
1719
|
/**
|
|
1482
1720
|
* Appends a key and value (any T) without a duplicate-key check. Eagerly
|
|
1483
1721
|
* boxes the value into a slot.
|
|
@@ -1550,6 +1788,8 @@ export namespace JSON {
|
|
|
1550
1788
|
* @param value - The value (will be wrapped in JSON.Value)
|
|
1551
1789
|
*/
|
|
1552
1790
|
set<T>(key: string, value: T): void {
|
|
1791
|
+
this._rawStart = 0;
|
|
1792
|
+
this._rawEnd = 0;
|
|
1553
1793
|
const bits = JSON.Value.bitsFrom<T>(value);
|
|
1554
1794
|
const i = this.indexOf(key);
|
|
1555
1795
|
if (i >= 0) {
|
|
@@ -1620,6 +1860,8 @@ export namespace JSON {
|
|
|
1620
1860
|
delete(key: string): bool {
|
|
1621
1861
|
const removed = this.indexOf(key);
|
|
1622
1862
|
if (removed < 0) return false;
|
|
1863
|
+
this._rawStart = 0;
|
|
1864
|
+
this._rawEnd = 0;
|
|
1623
1865
|
const keys = this.keys();
|
|
1624
1866
|
const oldVals = this._vals;
|
|
1625
1867
|
const n = this._vused;
|
|
@@ -1652,9 +1894,10 @@ export namespace JSON {
|
|
|
1652
1894
|
*/
|
|
1653
1895
|
clear(): void {
|
|
1654
1896
|
this._kused = 0;
|
|
1655
|
-
this._kpos = EMPTY_I32S;
|
|
1656
1897
|
this._vused = 0;
|
|
1657
1898
|
this._src = "";
|
|
1899
|
+
this._rawStart = 0;
|
|
1900
|
+
this._rawEnd = 0;
|
|
1658
1901
|
this._index = null;
|
|
1659
1902
|
this._indexMask = 0;
|
|
1660
1903
|
}
|
|
@@ -1722,7 +1965,8 @@ export namespace JSON {
|
|
|
1722
1965
|
|
|
1723
1966
|
const keys = value.keys();
|
|
1724
1967
|
const values = value.values();
|
|
1725
|
-
|
|
1968
|
+
const len = keys.length;
|
|
1969
|
+
for (let i = 0; i < len; i++) {
|
|
1726
1970
|
out.set(unchecked(keys[i]), unchecked(values[i]));
|
|
1727
1971
|
}
|
|
1728
1972
|
return out;
|
|
@@ -1922,7 +2166,8 @@ export namespace JSON {
|
|
|
1922
2166
|
const out = new JSON.Arr();
|
|
1923
2167
|
// @ts-expect-error: T is JSON.Value[] here
|
|
1924
2168
|
const arr = changetype<JSON.Value[]>(value);
|
|
1925
|
-
|
|
2169
|
+
const len = arr.length;
|
|
2170
|
+
for (let i = 0; i < len; i++) {
|
|
1926
2171
|
out.pushRawSlot(JSON.Value.bitsFrom<JSON.Value>(unchecked(arr[i])));
|
|
1927
2172
|
}
|
|
1928
2173
|
return out;
|
|
@@ -2369,6 +2614,24 @@ export namespace JSON {
|
|
|
2369
2614
|
}
|
|
2370
2615
|
}
|
|
2371
2616
|
|
|
2617
|
+
/**
|
|
2618
|
+
* Primitive serializer for generated structs that already reserved the
|
|
2619
|
+
* complete worst-case output size in `__SERIALIZE`. Avoids repeating buffer
|
|
2620
|
+
* capacity and stack-size accounting for every integer/boolean field.
|
|
2621
|
+
* Internal: emitted only for non-nullable primitive fields by the transform.
|
|
2622
|
+
*/
|
|
2623
|
+
function __serializePrimitiveUnsafe<T>(data: T): void {
|
|
2624
|
+
if (isBoolean<T>()) {
|
|
2625
|
+
serializeBoolUnsafe(data as bool);
|
|
2626
|
+
} else if (isInteger<T>()) {
|
|
2627
|
+
// @ts-expect-error: transform emits this only for integer T
|
|
2628
|
+
serializeIntegerUnsafe<T>(data);
|
|
2629
|
+
} else {
|
|
2630
|
+
// Defensive fallback for hand-written transformed code.
|
|
2631
|
+
__serialize<T>(data);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2372
2635
|
/**
|
|
2373
2636
|
* Deserializes JSON data directly from the buffer.
|
|
2374
2637
|
* Should only be used if you know what you are doing.
|
|
@@ -2385,7 +2648,9 @@ export namespace JSON {
|
|
|
2385
2648
|
srcStart += 2;
|
|
2386
2649
|
if (isBoolean<T>()) {
|
|
2387
2650
|
// @ts-expect-error: type
|
|
2388
|
-
|
|
2651
|
+
const code = deserializeBooleanCode(srcStart, srcEnd);
|
|
2652
|
+
if (code == 0) throw new Error("Expected 'true' or 'false' in JSON");
|
|
2653
|
+
return code == 2;
|
|
2389
2654
|
} else if (isInteger<T>()) {
|
|
2390
2655
|
return isSigned<T>()
|
|
2391
2656
|
? deserializeInteger<T>(srcStart, srcEnd)
|
|
@@ -2409,7 +2674,12 @@ export namespace JSON {
|
|
|
2409
2674
|
"Cannot parse data as string because it was formatted incorrectly!",
|
|
2410
2675
|
);
|
|
2411
2676
|
|
|
2412
|
-
|
|
2677
|
+
const value = deserializeString(srcStart, srcEnd);
|
|
2678
|
+
if (changetype<usize>(value) == 0) {
|
|
2679
|
+
takeProductionParseError();
|
|
2680
|
+
throw new Error("Invalid JSON string: missing surrounding quotes");
|
|
2681
|
+
}
|
|
2682
|
+
return value as T;
|
|
2413
2683
|
} else {
|
|
2414
2684
|
let type: nonnull<T> = changetype<nonnull<T>>(0);
|
|
2415
2685
|
// @ts-expect-error: Defined by transform
|
|
@@ -2422,9 +2692,15 @@ export namespace JSON {
|
|
|
2422
2692
|
isDefined(type.__DESERIALIZE_SLOW) ||
|
|
2423
2693
|
isDefined(type.__DESERIALIZE_FAST)
|
|
2424
2694
|
) {
|
|
2695
|
+
const fresh = dst == 0;
|
|
2425
2696
|
const out = changetype<nonnull<T>>(
|
|
2426
2697
|
dst || __new(offsetof<nonnull<T>>(), idof<nonnull<T>>()),
|
|
2427
2698
|
);
|
|
2699
|
+
// Generated fast paths may preserve omitted fields and reuse existing
|
|
2700
|
+
// strings/containers. Initialize only a freshly allocated object here;
|
|
2701
|
+
// callers that pass `dst` intentionally retain that reusable graph.
|
|
2702
|
+
// @ts-expect-error: Defined by transform
|
|
2703
|
+
if (fresh && isDefined(type.__INITIALIZE)) out.__INITIALIZE();
|
|
2428
2704
|
// @ts-expect-error: Defined by transform
|
|
2429
2705
|
if (isDefined(type.__DESERIALIZE_FAST)) {
|
|
2430
2706
|
// @ts-expect-error: Defined by transform
|
|
@@ -2441,7 +2717,13 @@ export namespace JSON {
|
|
|
2441
2717
|
// @ts-expect-error: Defined by transform
|
|
2442
2718
|
if (isDefined(type.__DESERIALIZE_SLOW)) {
|
|
2443
2719
|
// @ts-expect-error: Defined by transform
|
|
2444
|
-
out.__DESERIALIZE_SLOW(srcStart, srcEnd, out);
|
|
2720
|
+
const slowEnd = out.__DESERIALIZE_SLOW(srcStart, srcEnd, out);
|
|
2721
|
+
if (slowEnd == 0) {
|
|
2722
|
+
takeProductionParseError();
|
|
2723
|
+
throw new Error("Malformed JSON object");
|
|
2724
|
+
}
|
|
2725
|
+
if (takeProductionParseError())
|
|
2726
|
+
throw new Error("Malformed nested JSON value");
|
|
2445
2727
|
return out;
|
|
2446
2728
|
}
|
|
2447
2729
|
throw new Error(`No deserialize method defined for type ${type}`);
|
|
@@ -2451,7 +2733,12 @@ export namespace JSON {
|
|
|
2451
2733
|
return deserializeStaticArray<nonnull<T>>(srcStart, srcEnd, dst) as T;
|
|
2452
2734
|
} else if (type instanceof Array) {
|
|
2453
2735
|
// @ts-expect-error: type
|
|
2454
|
-
|
|
2736
|
+
const value = deserializeArray<nonnull<T>>(srcStart, srcEnd, dst) as T;
|
|
2737
|
+
if (changetype<usize>(value) == 0) {
|
|
2738
|
+
takeProductionParseError();
|
|
2739
|
+
throw new Error("Unterminated array in JSON");
|
|
2740
|
+
}
|
|
2741
|
+
return value;
|
|
2455
2742
|
} else if (
|
|
2456
2743
|
type instanceof Int8Array ||
|
|
2457
2744
|
type instanceof Uint8Array ||
|
|
@@ -2473,7 +2760,12 @@ export namespace JSON {
|
|
|
2473
2760
|
return deserializeSet<T>(srcStart, srcEnd, dst);
|
|
2474
2761
|
} else if (type instanceof Map) {
|
|
2475
2762
|
// @ts-expect-error: type
|
|
2476
|
-
|
|
2763
|
+
const value = deserializeMap<T>(srcStart, srcEnd, dst);
|
|
2764
|
+
if (changetype<usize>(value) == 0) {
|
|
2765
|
+
takeProductionParseError();
|
|
2766
|
+
throw new Error("Malformed JSON map");
|
|
2767
|
+
}
|
|
2768
|
+
return value;
|
|
2477
2769
|
} else if (type instanceof Date) {
|
|
2478
2770
|
// @ts-expect-error: type
|
|
2479
2771
|
return deserializeDate(srcStart, srcEnd);
|
|
@@ -2482,13 +2774,28 @@ export namespace JSON {
|
|
|
2482
2774
|
return deserializeRaw(srcStart, srcEnd);
|
|
2483
2775
|
} else if (type instanceof JSON.Value) {
|
|
2484
2776
|
// @ts-expect-error: type
|
|
2485
|
-
|
|
2777
|
+
const value = deserializeArbitrary(srcStart, srcEnd, 0);
|
|
2778
|
+
if (changetype<usize>(value) == 0) {
|
|
2779
|
+
takeProductionParseError();
|
|
2780
|
+
throw new Error("Incomplete JSON value");
|
|
2781
|
+
}
|
|
2782
|
+
return value;
|
|
2486
2783
|
} else if (type instanceof JSON.Obj) {
|
|
2487
2784
|
// @ts-expect-error: type
|
|
2488
|
-
|
|
2785
|
+
const value = deserializeObject(srcStart, srcEnd, 0);
|
|
2786
|
+
if (changetype<usize>(value) == 0) {
|
|
2787
|
+
takeProductionParseError();
|
|
2788
|
+
throw new Error("Unterminated object in JSON");
|
|
2789
|
+
}
|
|
2790
|
+
return value;
|
|
2489
2791
|
} else if (type instanceof JSON.Arr) {
|
|
2490
2792
|
// @ts-expect-error: type
|
|
2491
|
-
|
|
2793
|
+
const value = deserializeJsonArray(srcStart, srcEnd, 0);
|
|
2794
|
+
if (changetype<usize>(value) == 0) {
|
|
2795
|
+
takeProductionParseError();
|
|
2796
|
+
throw new Error("Unterminated array in JSON");
|
|
2797
|
+
}
|
|
2798
|
+
return value;
|
|
2492
2799
|
} else if (type instanceof JSON.Box) {
|
|
2493
2800
|
// @ts-expect-error: type
|
|
2494
2801
|
return new JSON.Box(
|
|
@@ -2509,14 +2816,85 @@ export namespace JSON {
|
|
|
2509
2816
|
);
|
|
2510
2817
|
}
|
|
2511
2818
|
export namespace Util {
|
|
2819
|
+
/** Record a cold malformed-input path and return the zero cursor sentinel. */
|
|
2820
|
+
export function failProductionParse(): usize {
|
|
2821
|
+
return failProductionParseInternal();
|
|
2822
|
+
}
|
|
2512
2823
|
export function isSpace(code: u16): boolean {
|
|
2513
2824
|
return code == 0x20 || code - 9 <= 4;
|
|
2514
2825
|
}
|
|
2826
|
+
|
|
2827
|
+
|
|
2828
|
+
@inline
|
|
2829
|
+
export function beginObjectTrace(
|
|
2830
|
+
dst: usize,
|
|
2831
|
+
srcStart: usize,
|
|
2832
|
+
typeTag: string,
|
|
2833
|
+
): i32 {
|
|
2834
|
+
return beginObjectFieldTrace(dst, srcStart, typeTag);
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
|
|
2838
|
+
@inline
|
|
2839
|
+
export function getObjectTraceMask(slot: i32): u64 {
|
|
2840
|
+
return objectFieldTraceMask(slot);
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
|
|
2844
|
+
@inline
|
|
2845
|
+
export function getObjectTraceTier(slot: i32): u8 {
|
|
2846
|
+
return objectFieldTraceTier(slot);
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
|
|
2850
|
+
@inline
|
|
2851
|
+
export function getObjectTraceSeparator(slot: i32): usize {
|
|
2852
|
+
return objectFieldTraceSeparator(slot);
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
|
|
2856
|
+
@inline
|
|
2857
|
+
export function saveObjectTrace(
|
|
2858
|
+
slot: i32,
|
|
2859
|
+
present: u64,
|
|
2860
|
+
tier: u8,
|
|
2861
|
+
separator: usize = 0,
|
|
2862
|
+
): void {
|
|
2863
|
+
recordObjectFieldTrace(slot, present, tier, separator);
|
|
2864
|
+
}
|
|
2515
2865
|
/** Advance past JSON whitespace (space, tab, LF, VT, FF, CR). */
|
|
2866
|
+
@inline
|
|
2516
2867
|
export function skipWhitespace(srcStart: usize, srcEnd: usize): usize {
|
|
2517
2868
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
2518
2869
|
return srcStart;
|
|
2519
2870
|
}
|
|
2871
|
+
/** Advance a whitespace run using the SIMD pretty-document scanner. */
|
|
2872
|
+
@inline
|
|
2873
|
+
export function skipPrettyWhitespace(
|
|
2874
|
+
srcStart: usize,
|
|
2875
|
+
srcEnd: usize,
|
|
2876
|
+
): usize {
|
|
2877
|
+
if (srcStart >= srcEnd) return srcStart;
|
|
2878
|
+
const code = load<u16>(srcStart);
|
|
2879
|
+
if (!isSpace(code)) return srcStart;
|
|
2880
|
+
if (code == 0x20) {
|
|
2881
|
+
srcStart += 2;
|
|
2882
|
+
if (srcStart >= srcEnd || !isSpace(load<u16>(srcStart)))
|
|
2883
|
+
return srcStart;
|
|
2884
|
+
}
|
|
2885
|
+
return skipPrettyWhitespace_SIMD(srcStart, srcEnd);
|
|
2886
|
+
}
|
|
2887
|
+
/** Compare a bounded UTF-16 source slice with a compile-time literal. */
|
|
2888
|
+
@inline
|
|
2889
|
+
export function matchesLiteral(
|
|
2890
|
+
srcStart: usize,
|
|
2891
|
+
srcEnd: usize,
|
|
2892
|
+
expected: string,
|
|
2893
|
+
): bool {
|
|
2894
|
+
const length = <usize>expected.length;
|
|
2895
|
+
if (srcEnd - srcStart != length << 1) return false;
|
|
2896
|
+
return utf16Equals(srcStart, changetype<usize>(expected), <i32>length);
|
|
2897
|
+
}
|
|
2520
2898
|
function scanQuotedValueEnd(srcStart: usize, srcEnd: usize): usize {
|
|
2521
2899
|
const endQuote = scanStringEnd(srcStart, srcEnd);
|
|
2522
2900
|
return endQuote >= srcEnd ? 0 : endQuote + 2;
|