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.
- package/CHANGELOG.md +54 -29
- package/README.md +126 -33
- package/assembly/custom/chars.ts +39 -78
- package/assembly/deserialize/error.ts +21 -0
- package/assembly/deserialize/index/arbitrary.ts +50 -11
- package/assembly/deserialize/index/array.ts +2 -1
- package/assembly/deserialize/index/bool.ts +1 -1
- package/assembly/deserialize/index/float.ts +2 -4
- package/assembly/deserialize/index/integer.ts +2 -4
- package/assembly/deserialize/index/object.ts +9 -1
- package/assembly/deserialize/index/string.ts +39 -7
- package/assembly/deserialize/index/unsigned.ts +2 -4
- 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 +34 -22
- package/assembly/deserialize/naive/array/map.ts +1 -1
- package/assembly/deserialize/naive/array/object.ts +2 -1
- package/assembly/deserialize/naive/array/string.ts +29 -16
- package/assembly/deserialize/naive/array/struct.ts +19 -1
- package/assembly/deserialize/naive/bool.ts +18 -10
- package/assembly/deserialize/naive/date.ts +1 -2
- package/assembly/deserialize/naive/float.ts +3 -9
- package/assembly/deserialize/naive/integer.ts +1 -2
- package/assembly/deserialize/naive/map.ts +342 -43
- package/assembly/deserialize/naive/object.ts +250 -55
- package/assembly/deserialize/naive/raw.ts +10 -2
- package/assembly/deserialize/naive/set.ts +2 -5
- package/assembly/deserialize/naive/staticarray.ts +4 -4
- package/assembly/deserialize/naive/string.ts +46 -60
- package/assembly/deserialize/naive/unsigned.ts +1 -2
- package/assembly/deserialize/parseMode.ts +216 -0
- package/assembly/deserialize/simd/array/integer.ts +8 -10
- package/assembly/deserialize/simd/float.ts +38 -34
- package/assembly/deserialize/simd/integer.ts +2 -7
- package/assembly/deserialize/simd/string.ts +84 -39
- package/assembly/deserialize/string-validation.ts +34 -0
- package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
- package/assembly/deserialize/swar/array/array.ts +64 -15
- package/assembly/deserialize/swar/array/bool.ts +15 -5
- package/assembly/deserialize/swar/array/box.ts +1 -2
- package/assembly/deserialize/swar/array/float.ts +399 -30
- package/assembly/deserialize/swar/array/generic.ts +13 -4
- package/assembly/deserialize/swar/array/integer.ts +29 -78
- package/assembly/deserialize/swar/array/map.ts +1 -2
- package/assembly/deserialize/swar/array/object.ts +12 -4
- package/assembly/deserialize/swar/array/raw.ts +1 -2
- package/assembly/deserialize/swar/array/shared.ts +7 -14
- package/assembly/deserialize/swar/array/string.ts +37 -13
- package/assembly/deserialize/swar/array/struct.ts +109 -21
- package/assembly/deserialize/swar/array.ts +1 -3
- package/assembly/deserialize/swar/float.ts +23 -21
- package/assembly/deserialize/swar/integer.ts +2 -7
- package/assembly/deserialize/swar/string.ts +81 -124
- package/assembly/deserialize/swar/typedarray.ts +4 -4
- package/assembly/index.d.ts +35 -24
- package/assembly/index.ts +1775 -281
- package/assembly/serialize/index/arbitrary.ts +70 -4
- 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/jsonarray.ts +51 -0
- package/assembly/serialize/index/object.ts +42 -3
- package/assembly/serialize/index/string.ts +1 -2
- package/assembly/serialize/index/typedarray.ts +1 -2
- package/assembly/serialize/index.ts +1 -0
- package/assembly/serialize/naive/array.ts +87 -34
- package/assembly/serialize/naive/bool.ts +0 -1
- package/assembly/serialize/naive/float.ts +15 -29
- package/assembly/serialize/naive/integer.ts +1 -5
- package/assembly/serialize/naive/map.ts +37 -0
- package/assembly/serialize/naive/raw.ts +1 -2
- package/assembly/serialize/naive/set.ts +0 -5
- package/assembly/serialize/naive/staticarray.ts +0 -6
- package/assembly/serialize/naive/string.ts +1 -10
- package/assembly/serialize/naive/typedarray.ts +0 -7
- package/assembly/serialize/simd/string.ts +1 -3
- package/assembly/serialize/swar/string.ts +1 -2
- package/assembly/util/atoi-fast.ts +4 -14
- package/assembly/util/bytes.ts +1 -2
- package/assembly/util/eisel-lemire.ts +179 -0
- package/assembly/util/idofd.ts +1 -2
- package/assembly/util/isSpace.ts +1 -2
- package/assembly/util/itoa-fast.ts +6 -9
- package/assembly/util/nextPowerOf2.ts +1 -2
- package/assembly/util/parsefloat-fast.ts +3 -5
- package/assembly/util/prettyWhitespaceSimd.ts +69 -0
- package/assembly/util/ptrToStr.ts +1 -2
- package/assembly/util/scanValueEndSimd.ts +83 -22
- package/assembly/util/scanValueEndSwar.ts +67 -25
- package/assembly/util/scientific.ts +5 -8
- package/assembly/util/snp.ts +1 -2
- package/assembly/util/swar-int.ts +5 -10
- package/assembly/util/swar.ts +2 -4
- package/assembly/util/validateJson.ts +338 -0
- package/lib/as-bs.ts +51 -51
- package/package.json +25 -8
- package/transform/lib/index.d.ts +6 -0
- package/transform/lib/index.js +874 -171
- package/transform/lib/types.d.ts +3 -1
- package/transform/lib/types.js +6 -0
- package/assembly/deserialize/index/struct.ts +0 -1
- package/assembly/deserialize/naive/struct.ts +0 -21
- package/assembly/util/dragonbox-cache.ts +0 -445
- 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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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)
|
|
223
|
+
if (end >= srcEnd) {
|
|
224
|
+
parseValueEnd = 0;
|
|
225
|
+
markProductionParseError();
|
|
226
|
+
return changetype<JSON.Value>(0);
|
|
227
|
+
}
|
|
77
228
|
parseValueEnd = end + 2;
|
|
78
|
-
|
|
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 =
|
|
85
|
-
parseValueEnd =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
|
386
|
+
return 0;
|
|
192
387
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { JSON } from "../..";
|
|
2
2
|
import { ptrToStr } from "../../util/ptrToStr";
|
|
3
3
|
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 <
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
205
|
-
// call per field
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
230
|
+
markProductionParseError();
|
|
245
231
|
return 0;
|
|
246
232
|
}
|