json-as 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +54 -29
  2. package/README.md +126 -33
  3. package/assembly/custom/chars.ts +39 -78
  4. package/assembly/deserialize/error.ts +21 -0
  5. package/assembly/deserialize/index/arbitrary.ts +50 -11
  6. package/assembly/deserialize/index/array.ts +2 -1
  7. package/assembly/deserialize/index/bool.ts +1 -1
  8. package/assembly/deserialize/index/float.ts +2 -4
  9. package/assembly/deserialize/index/integer.ts +2 -4
  10. package/assembly/deserialize/index/object.ts +9 -1
  11. package/assembly/deserialize/index/string.ts +39 -7
  12. package/assembly/deserialize/index/unsigned.ts +2 -4
  13. package/assembly/deserialize/index.ts +0 -1
  14. package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
  15. package/assembly/deserialize/naive/array/array.ts +1 -0
  16. package/assembly/deserialize/naive/array/bool.ts +21 -11
  17. package/assembly/deserialize/naive/array/box.ts +13 -33
  18. package/assembly/deserialize/naive/array/float.ts +23 -13
  19. package/assembly/deserialize/naive/array/integer.ts +34 -22
  20. package/assembly/deserialize/naive/array/map.ts +1 -1
  21. package/assembly/deserialize/naive/array/object.ts +2 -1
  22. package/assembly/deserialize/naive/array/string.ts +29 -16
  23. package/assembly/deserialize/naive/array/struct.ts +19 -1
  24. package/assembly/deserialize/naive/bool.ts +18 -10
  25. package/assembly/deserialize/naive/date.ts +1 -2
  26. package/assembly/deserialize/naive/float.ts +3 -9
  27. package/assembly/deserialize/naive/integer.ts +1 -2
  28. package/assembly/deserialize/naive/map.ts +342 -43
  29. package/assembly/deserialize/naive/object.ts +250 -55
  30. package/assembly/deserialize/naive/raw.ts +10 -2
  31. package/assembly/deserialize/naive/set.ts +2 -5
  32. package/assembly/deserialize/naive/staticarray.ts +4 -4
  33. package/assembly/deserialize/naive/string.ts +46 -60
  34. package/assembly/deserialize/naive/unsigned.ts +1 -2
  35. package/assembly/deserialize/parseMode.ts +216 -0
  36. package/assembly/deserialize/simd/array/integer.ts +8 -10
  37. package/assembly/deserialize/simd/float.ts +38 -34
  38. package/assembly/deserialize/simd/integer.ts +2 -7
  39. package/assembly/deserialize/simd/string.ts +84 -39
  40. package/assembly/deserialize/string-validation.ts +34 -0
  41. package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
  42. package/assembly/deserialize/swar/array/array.ts +64 -15
  43. package/assembly/deserialize/swar/array/bool.ts +15 -5
  44. package/assembly/deserialize/swar/array/box.ts +1 -2
  45. package/assembly/deserialize/swar/array/float.ts +399 -30
  46. package/assembly/deserialize/swar/array/generic.ts +13 -4
  47. package/assembly/deserialize/swar/array/integer.ts +29 -78
  48. package/assembly/deserialize/swar/array/map.ts +1 -2
  49. package/assembly/deserialize/swar/array/object.ts +12 -4
  50. package/assembly/deserialize/swar/array/raw.ts +1 -2
  51. package/assembly/deserialize/swar/array/shared.ts +7 -14
  52. package/assembly/deserialize/swar/array/string.ts +37 -13
  53. package/assembly/deserialize/swar/array/struct.ts +109 -21
  54. package/assembly/deserialize/swar/array.ts +1 -3
  55. package/assembly/deserialize/swar/float.ts +23 -21
  56. package/assembly/deserialize/swar/integer.ts +2 -7
  57. package/assembly/deserialize/swar/string.ts +81 -124
  58. package/assembly/deserialize/swar/typedarray.ts +4 -4
  59. package/assembly/index.d.ts +35 -24
  60. package/assembly/index.ts +1775 -281
  61. package/assembly/serialize/index/arbitrary.ts +70 -4
  62. package/assembly/serialize/index/bool.ts +1 -1
  63. package/assembly/serialize/index/float.ts +1 -5
  64. package/assembly/serialize/index/integer.ts +1 -1
  65. package/assembly/serialize/index/jsonarray.ts +51 -0
  66. package/assembly/serialize/index/object.ts +42 -3
  67. package/assembly/serialize/index/string.ts +1 -2
  68. package/assembly/serialize/index/typedarray.ts +1 -2
  69. package/assembly/serialize/index.ts +1 -0
  70. package/assembly/serialize/naive/array.ts +87 -34
  71. package/assembly/serialize/naive/bool.ts +0 -1
  72. package/assembly/serialize/naive/float.ts +15 -29
  73. package/assembly/serialize/naive/integer.ts +1 -5
  74. package/assembly/serialize/naive/map.ts +37 -0
  75. package/assembly/serialize/naive/raw.ts +1 -2
  76. package/assembly/serialize/naive/set.ts +0 -5
  77. package/assembly/serialize/naive/staticarray.ts +0 -6
  78. package/assembly/serialize/naive/string.ts +1 -10
  79. package/assembly/serialize/naive/typedarray.ts +0 -7
  80. package/assembly/serialize/simd/string.ts +1 -3
  81. package/assembly/serialize/swar/string.ts +1 -2
  82. package/assembly/util/atoi-fast.ts +4 -14
  83. package/assembly/util/bytes.ts +1 -2
  84. package/assembly/util/eisel-lemire.ts +179 -0
  85. package/assembly/util/idofd.ts +1 -2
  86. package/assembly/util/isSpace.ts +1 -2
  87. package/assembly/util/itoa-fast.ts +6 -9
  88. package/assembly/util/nextPowerOf2.ts +1 -2
  89. package/assembly/util/parsefloat-fast.ts +3 -5
  90. package/assembly/util/prettyWhitespaceSimd.ts +69 -0
  91. package/assembly/util/ptrToStr.ts +1 -2
  92. package/assembly/util/scanValueEndSimd.ts +83 -22
  93. package/assembly/util/scanValueEndSwar.ts +67 -25
  94. package/assembly/util/scientific.ts +5 -8
  95. package/assembly/util/snp.ts +1 -2
  96. package/assembly/util/swar-int.ts +5 -10
  97. package/assembly/util/swar.ts +2 -4
  98. package/assembly/util/validateJson.ts +338 -0
  99. package/lib/as-bs.ts +51 -51
  100. package/package.json +25 -8
  101. package/transform/lib/index.d.ts +6 -0
  102. package/transform/lib/index.js +874 -171
  103. package/transform/lib/types.d.ts +3 -1
  104. package/transform/lib/types.js +6 -0
  105. package/assembly/deserialize/index/struct.ts +0 -1
  106. package/assembly/deserialize/naive/struct.ts +0 -21
  107. package/assembly/util/dragonbox-cache.ts +0 -445
  108. package/assembly/util/dragonbox.ts +0 -652
@@ -4,25 +4,25 @@ import { deserializeFloatField_NAIVE } from "../../naive/float";
4
4
  import { deserializeFloatArray_NAIVE } from "../../naive/array/float";
5
5
  import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
6
6
  import { ensureArrayElementSlot, ensureArrayField } from "./shared";
7
- import { parse4Digits_PairMul } from "../../../util/swar-int";
7
+ import {
8
+ parse4Digits_PairMul,
9
+ parse8Digits_PairMul,
10
+ } from "../../../util/swar-int";
8
11
  import { loadPow10, MAX_EXACT_MANTISSA, MAX_EXACT_POW10 } from "../float";
9
12
  import { isSpace } from "../../../util";
13
+ import {
14
+ eiselLemire22,
15
+ eiselLemireMinus14,
16
+ eiselLemireMinus14_54Bit,
17
+ } from "../../../util/eisel-lemire";
18
+ import { markProductionParseError } from "../../error";
10
19
 
11
-
12
- @inline function skipFloatArrayWhitespace(
13
- srcStart: usize,
14
- srcEnd: usize,
15
- ): usize {
20
+ function skipFloatArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
16
21
  while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
17
22
  return srcStart;
18
23
  }
19
24
 
20
- // @ts-ignore: inline
21
- @inline function fallbackStore<E>(
22
- origStart: usize,
23
- end: usize,
24
- slot: usize,
25
- ): void {
25
+ function fallbackStore<E>(origStart: usize, end: usize, slot: usize): void {
26
26
  const s = ptrToStr(origStart, end);
27
27
  if (sizeof<E>() == sizeof<f32>()) {
28
28
  store<f32>(slot, f32.parse(s));
@@ -31,6 +31,228 @@ import { isSpace } from "../../../util";
31
31
  }
32
32
  }
33
33
 
34
+ // Cold companion to the 14-digit GeoJSON hot path. Keeping the less common
35
+ // fixed widths out of `parseFloatElementSWAR` preserves its compact branch
36
+ // layout while avoiding the generic digit loops for the remaining coordinate
37
+ // shapes seen in real GeoJSON payloads.
38
+ function parseGeoAlternateFixed<E>(
39
+ p: usize,
40
+ srcEnd: usize,
41
+ slot: usize,
42
+ negative: bool,
43
+ firstDigit: u32,
44
+ ): usize {
45
+ if (p + 20 >= srcEnd) return 0;
46
+ let intDigits = 0;
47
+ if (load<u16>(p, 4) == 46) {
48
+ intDigits = 2;
49
+ } else if (load<u16>(p, 6) == 46) {
50
+ intDigits = 3;
51
+ } else {
52
+ return 0;
53
+ }
54
+
55
+ const frac = p + <usize>((intDigits + 1) << 1);
56
+ let fracDigits = 0;
57
+ let term = frac + 12;
58
+ let termCode = load<u16>(term);
59
+ if (termCode == COMMA || termCode == BRACKET_RIGHT || isSpace(termCode)) {
60
+ fracDigits = 6;
61
+ } else {
62
+ term = frac + 30;
63
+ if (term >= srcEnd) return 0;
64
+ termCode = load<u16>(term);
65
+ if (termCode == COMMA || termCode == BRACKET_RIGHT || isSpace(termCode)) {
66
+ fracDigits = 15;
67
+ } else {
68
+ term = frac + 26;
69
+ termCode = load<u16>(term);
70
+ if (termCode == COMMA || termCode == BRACKET_RIGHT || isSpace(termCode)) {
71
+ fracDigits = 13;
72
+ } else {
73
+ return 0;
74
+ }
75
+ }
76
+ }
77
+
78
+ const d1 = <u32>load<u16>(p, 2) - 48;
79
+ const d2 = intDigits == 3 ? <u32>load<u16>(p, 4) - 48 : <u32>0;
80
+ if (d1 > 9 || d2 > 9) return 0;
81
+ const whole =
82
+ intDigits == 2
83
+ ? <u64>(firstDigit * 10 + d1)
84
+ : <u64>(firstDigit * 100 + d1 * 10 + d2);
85
+
86
+ let mantissa: u64;
87
+ if (fracDigits == 6) {
88
+ const a = parse4Digits_PairMul(load<u64>(frac));
89
+ const d4 = <u32>load<u16>(frac, 8) - 48;
90
+ const d5 = <u32>load<u16>(frac, 10) - 48;
91
+ if (a == U32.MAX_VALUE || d4 > 9 || d5 > 9) return 0;
92
+ mantissa = whole * 1_000_000 + <u64>a * 100 + <u64>(d4 * 10 + d5);
93
+ } else {
94
+ const a = parse8Digits_PairMul(load<u64>(frac), load<u64>(frac, 8));
95
+ const c = parse4Digits_PairMul(load<u64>(frac, 16));
96
+ const d12 = <u32>load<u16>(frac, 24) - 48;
97
+ if (a == U32.MAX_VALUE || c == U32.MAX_VALUE || d12 > 9) return 0;
98
+ if (fracDigits == 13) {
99
+ mantissa =
100
+ whole * 10_000_000_000_000 + <u64>a * 100_000 + <u64>c * 10 + <u64>d12;
101
+ } else {
102
+ const d13 = <u32>load<u16>(frac, 26) - 48;
103
+ const d14 = <u32>load<u16>(frac, 28) - 48;
104
+ if (d13 > 9 || d14 > 9) return 0;
105
+ mantissa =
106
+ whole * 1_000_000_000_000_000 +
107
+ <u64>a * 10_000_000 +
108
+ <u64>c * 1_000 +
109
+ <u64>(d12 * 100 + d13 * 10 + d14);
110
+ }
111
+ }
112
+
113
+ let result: f64;
114
+ if (fracDigits == 6) {
115
+ result = <f64>mantissa / loadPow10(6);
116
+ } else if (fracDigits == 15) {
117
+ result = eiselLemire22(mantissa, -15);
118
+ } else {
119
+ result =
120
+ mantissa <= MAX_EXACT_MANTISSA
121
+ ? <f64>mantissa / loadPow10(13)
122
+ : eiselLemire22(mantissa, -13);
123
+ }
124
+ if (negative) result = -result;
125
+ if (sizeof<E>() == sizeof<f32>()) {
126
+ store<f32>(slot, <f32>result);
127
+ } else {
128
+ store<f64>(slot, result);
129
+ }
130
+ return term;
131
+ }
132
+
133
+ function parseFixed14Pair<E>(
134
+ srcStart: usize,
135
+ srcEnd: usize,
136
+ dataStart: usize,
137
+ ): usize {
138
+ let p0 = srcStart;
139
+ let negative0 = false;
140
+ if (load<u16>(p0) == 45) {
141
+ negative0 = true;
142
+ p0 += 2;
143
+ }
144
+ if (p0 + 36 >= srcEnd) return 0;
145
+ const first0 = <u32>load<u16>(p0) - 48;
146
+ if (first0 > 9) return 0;
147
+ let intDigits0 = 0;
148
+ if (load<u16>(p0, 4) == 46) {
149
+ intDigits0 = 2;
150
+ } else if (load<u16>(p0, 6) == 46) {
151
+ intDigits0 = 3;
152
+ } else {
153
+ return 0;
154
+ }
155
+ const frac0 = p0 + <usize>((intDigits0 + 1) << 1);
156
+ const term0 = frac0 + 28;
157
+ if (load<u16>(term0) != COMMA) return 0;
158
+
159
+ let p1 = term0 + 2;
160
+ if (load<u16>(p1) == 32) p1 += 2;
161
+ let negative1 = false;
162
+ if (load<u16>(p1) == 45) {
163
+ negative1 = true;
164
+ p1 += 2;
165
+ }
166
+ if (p1 + 36 >= srcEnd) return 0;
167
+ const first1 = <u32>load<u16>(p1) - 48;
168
+ if (first1 > 9) return 0;
169
+ let intDigits1 = 0;
170
+ if (load<u16>(p1, 4) == 46) {
171
+ intDigits1 = 2;
172
+ } else if (load<u16>(p1, 6) == 46) {
173
+ intDigits1 = 3;
174
+ } else {
175
+ return 0;
176
+ }
177
+ const frac1 = p1 + <usize>((intDigits1 + 1) << 1);
178
+ const term1 = frac1 + 28;
179
+ if (load<u16>(term1) != BRACKET_RIGHT) return 0;
180
+
181
+ const a0 = parse8Digits_PairMul(load<u64>(frac0), load<u64>(frac0, 8));
182
+ const c0 = parse4Digits_PairMul(load<u64>(frac0, 16));
183
+ const d012 = <u32>load<u16>(frac0, 24) - 48;
184
+ const d013 = <u32>load<u16>(frac0, 26) - 48;
185
+ const d01 = <u32>load<u16>(p0, 2) - 48;
186
+ const d02 = intDigits0 == 3 ? <u32>load<u16>(p0, 4) - 48 : <u32>0;
187
+ if (
188
+ a0 == U32.MAX_VALUE ||
189
+ c0 == U32.MAX_VALUE ||
190
+ d012 > 9 ||
191
+ d013 > 9 ||
192
+ d01 > 9 ||
193
+ d02 > 9
194
+ )
195
+ return 0;
196
+
197
+ const a1 = parse8Digits_PairMul(load<u64>(frac1), load<u64>(frac1, 8));
198
+ const c1 = parse4Digits_PairMul(load<u64>(frac1, 16));
199
+ const d112 = <u32>load<u16>(frac1, 24) - 48;
200
+ const d113 = <u32>load<u16>(frac1, 26) - 48;
201
+ const d11 = <u32>load<u16>(p1, 2) - 48;
202
+ const d12 = intDigits1 == 3 ? <u32>load<u16>(p1, 4) - 48 : <u32>0;
203
+ if (
204
+ a1 == U32.MAX_VALUE ||
205
+ c1 == U32.MAX_VALUE ||
206
+ d112 > 9 ||
207
+ d113 > 9 ||
208
+ d11 > 9 ||
209
+ d12 > 9
210
+ )
211
+ return 0;
212
+
213
+ const whole0 =
214
+ intDigits0 == 2
215
+ ? <u64>(first0 * 10 + d01)
216
+ : <u64>(first0 * 100 + d01 * 10 + d02);
217
+ const whole1 =
218
+ intDigits1 == 2
219
+ ? <u64>(first1 * 10 + d11)
220
+ : <u64>(first1 * 100 + d11 * 10 + d12);
221
+ const mantissa0 =
222
+ whole0 * 100_000_000_000_000 +
223
+ <u64>a0 * 1_000_000 +
224
+ <u64>c0 * 100 +
225
+ <u64>(d012 * 10 + d013);
226
+ const mantissa1 =
227
+ whole1 * 100_000_000_000_000 +
228
+ <u64>a1 * 1_000_000 +
229
+ <u64>c1 * 100 +
230
+ <u64>(d112 * 10 + d113);
231
+
232
+ let value0 =
233
+ mantissa0 <= MAX_EXACT_MANTISSA
234
+ ? <f64>mantissa0 / loadPow10(14)
235
+ : mantissa0 < (<u64>1) << 54
236
+ ? eiselLemireMinus14_54Bit(mantissa0)
237
+ : eiselLemireMinus14(mantissa0);
238
+ let value1 =
239
+ mantissa1 <= MAX_EXACT_MANTISSA
240
+ ? <f64>mantissa1 / loadPow10(14)
241
+ : mantissa1 < (<u64>1) << 54
242
+ ? eiselLemireMinus14_54Bit(mantissa1)
243
+ : eiselLemireMinus14(mantissa1);
244
+ if (negative0) value0 = -value0;
245
+ if (negative1) value1 = -value1;
246
+ if (sizeof<E>() == sizeof<f32>()) {
247
+ store<f32>(dataStart, <f32>value0);
248
+ store<f32>(dataStart + sizeof<E>(), <f32>value1);
249
+ } else {
250
+ store<f64>(dataStart, value0);
251
+ store<f64>(dataStart + sizeof<E>(), value1);
252
+ }
253
+ return term1 + 2;
254
+ }
255
+
34
256
  /**
35
257
  * Inline single-pass Lemire-style float element parser for arrays.
36
258
  *
@@ -65,37 +287,128 @@ export function parseFloatElementSWAR<E>(
65
287
  let firstDigit = <u32>code - 48;
66
288
  if (firstDigit > 9) return 0;
67
289
 
290
+ // GeoJSON coordinates overwhelmingly use one of these two fixed layouts:
291
+ // `DD.ffffffffffffff` or `DDD.ffffffffffffff`. Recognize them before the
292
+ // generic scan so the common case avoids four loop exits (including the
293
+ // guaranteed failing parse4 probe at the fraction terminator). Each parse4
294
+ // still validates its lanes; malformed or exponent-bearing values fall
295
+ // through unchanged to the fully general parser below.
296
+ if (ASC_FEATURE_SIMD && p + 36 < srcEnd) {
297
+ let intDigitsFast = 0;
298
+ if (load<u16>(p, 4) == 46) {
299
+ intDigitsFast = 2;
300
+ } else if (load<u16>(p, 6) == 46) {
301
+ intDigitsFast = 3;
302
+ }
303
+ if (intDigitsFast != 0) {
304
+ const frac = p + <usize>((intDigitsFast + 1) << 1);
305
+ const term = frac + 28;
306
+ const termCode = load<u16>(term);
307
+ if (termCode == COMMA || termCode == BRACKET_RIGHT || isSpace(termCode)) {
308
+ const a = parse8Digits_PairMul(load<u64>(frac), load<u64>(frac, 8));
309
+ const c = parse4Digits_PairMul(load<u64>(frac, 16));
310
+ const d12 = <u32>load<u16>(frac, 24) - 48;
311
+ const d13 = <u32>load<u16>(frac, 26) - 48;
312
+ const d1 = <u32>load<u16>(p, 2) - 48;
313
+ const d2 = intDigitsFast == 3 ? <u32>load<u16>(p, 4) - 48 : <u32>0;
314
+ if (
315
+ a != U32.MAX_VALUE &&
316
+ c != U32.MAX_VALUE &&
317
+ d1 <= 9 &&
318
+ d2 <= 9 &&
319
+ d12 <= 9 &&
320
+ d13 <= 9
321
+ ) {
322
+ const whole =
323
+ intDigitsFast == 2
324
+ ? <u64>(firstDigit * 10 + d1)
325
+ : <u64>(firstDigit * 100 + d1 * 10 + d2);
326
+ const mantissa =
327
+ whole * 100_000_000_000_000 +
328
+ <u64>a * 1_000_000 +
329
+ <u64>c * 100 +
330
+ <u64>(d12 * 10 + d13);
331
+ let result =
332
+ mantissa <= MAX_EXACT_MANTISSA
333
+ ? <f64>mantissa / loadPow10(14)
334
+ : mantissa < (<u64>1) << 54
335
+ ? eiselLemireMinus14_54Bit(mantissa)
336
+ : eiselLemireMinus14(mantissa);
337
+ if (negative) result = -result;
338
+ if (sizeof<E>() == sizeof<f32>()) {
339
+ store<f32>(slot, <f32>result);
340
+ } else {
341
+ store<f64>(slot, result);
342
+ }
343
+ return term;
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ if (ASC_FEATURE_SIMD) {
350
+ const alternateEnd = parseGeoAlternateFixed<E>(
351
+ p,
352
+ srcEnd,
353
+ slot,
354
+ negative,
355
+ firstDigit,
356
+ );
357
+ if (alternateEnd) return alternateEnd;
358
+ }
359
+
360
+ return parseFloatElementGeneric<E>(origStart, srcEnd, slot);
361
+ }
362
+
363
+ function parseFloatElementGeneric<E>(
364
+ srcStart: usize,
365
+ srcEnd: usize,
366
+ slot: usize,
367
+ ): usize {
368
+ const origStart = srcStart;
369
+ let p = srcStart;
370
+ let negative = false;
371
+ let code = load<u16>(p);
372
+ if (code == 45) {
373
+ negative = true;
374
+ p += 2;
375
+ if (p >= srcEnd) return 0;
376
+ code = load<u16>(p);
377
+ }
378
+ const firstDigit = <u32>code - 48;
379
+ if (firstDigit > 9) return 0;
380
+
68
381
  // Integer mantissa: scalar (most JSON integers are 1-3 digits).
69
382
  let mantissa: u64 = 0;
70
- let intDigits: i32 = 0;
383
+ const intStart = p;
71
384
  while (p < srcEnd) {
72
385
  const d = <u32>load<u16>(p) - 48;
73
386
  if (d > 9) break;
74
387
  mantissa = mantissa * 10 + <u64>d;
75
- intDigits++;
76
388
  p += 2;
77
389
  }
390
+ const intDigits = <i32>((p - intStart) >> 1);
78
391
 
79
392
  // Fractional mantissa: parse4 SWAR stride + scalar tail. Same u64
80
- // accumulator as the integer part exponent compensates for fracDigits.
81
- let fracDigits: i32 = 0;
393
+ // accumulator as the integer part - exponent compensates for fracDigits.
394
+ let fracStart = p;
82
395
  if (p < srcEnd && load<u16>(p) == 46) {
83
396
  p += 2;
397
+ fracStart = p;
84
398
  while (p + 6 < srcEnd) {
85
399
  const parsed = parse4Digits_PairMul(load<u64>(p));
86
400
  if (parsed == U32.MAX_VALUE) break;
87
401
  mantissa = mantissa * 10_000 + <u64>parsed;
88
- fracDigits += 4;
89
402
  p += 8;
90
403
  }
91
404
  while (p < srcEnd) {
92
405
  const d = <u32>load<u16>(p) - 48;
93
406
  if (d > 9) break;
94
407
  mantissa = mantissa * 10 + <u64>d;
95
- fracDigits++;
96
408
  p += 2;
97
409
  }
98
410
  }
411
+ const fracDigits = <i32>((p - fracStart) >> 1);
99
412
 
100
413
  const mantDigits = intDigits + fracDigits;
101
414
  let exponent: i32 = -fracDigits;
@@ -159,12 +472,14 @@ export function parseFloatElementSWAR<E>(
159
472
  result /= loadPow10(<u32>-exponent);
160
473
  }
161
474
  } else if (mantDigits <= 19) {
162
- // Mantissa fits in u64 but the fast-path constraints don't hold. Call
163
- // `scientific` directly with our already-parsed mantissa+exp, skipping
164
- // the `ptrToStr` allocation + strtod re-parse.
165
- result = scientific(mantissa, exponent);
475
+ // Eisel-Lemire handles the overwhelmingly common medium exponent range
476
+ // without the u64 division/modulo in `scientific`'s scaledown path.
477
+ result =
478
+ exponent >= -22 && exponent <= 22
479
+ ? eiselLemire22(mantissa, exponent)
480
+ : scientific(mantissa, exponent);
166
481
  } else {
167
- // >19 mantissa digits beyond u64 capacity, may need strtod's sticky-bit
482
+ // >19 mantissa digits - beyond u64 capacity, may need strtod's sticky-bit
168
483
  // pattern. Hand off to f*.parse on the float's substring.
169
484
  fallbackStore<E>(origStart, p, slot);
170
485
  return p;
@@ -255,7 +570,7 @@ export function deserializeFloatArray_SWAR<T extends number[]>(
255
570
  }
256
571
 
257
572
  /**
258
- * Field/into variant parses `[..]` into the existing `out` array and
573
+ * Field/into variant - parses `[..]` into the existing `out` array and
259
574
  * returns the cursor past the closing `]`.
260
575
  *
261
576
  * Worst-case pre-sizing (`(srcEnd - srcStart) >> 2`) used by the top-level
@@ -264,12 +579,19 @@ export function deserializeFloatArray_SWAR<T extends number[]>(
264
579
  * `[lon,lat]` would over-allocate megabytes of f64 capacity. Instead we
265
580
  * use `ensureArrayElementSlot`'s grow-or-reuse strategy.
266
581
  */
582
+ @inline
267
583
  export function deserializeFloatArrayBody<T extends number[]>(
268
584
  srcStart: usize,
269
585
  srcEnd: usize,
270
586
  out: T,
271
587
  ): usize {
272
588
  let index = 0;
589
+ const reusableLength = load<i32>(
590
+ changetype<usize>(out),
591
+ offsetof<T>("length_"),
592
+ );
593
+ const reusableDataStart = out.dataStart;
594
+ const elementSize = sizeof<valueof<T>>();
273
595
 
274
596
  do {
275
597
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
@@ -281,8 +603,56 @@ export function deserializeFloatArrayBody<T extends number[]>(
281
603
  return srcStart + 2;
282
604
  }
283
605
 
606
+ // GeoJSON's innermost coordinate arrays are overwhelmingly reused
607
+ // `[longitude,latitude]` pairs. Unroll that stable-shape case so neither
608
+ // element pays the generic index/capacity selection or loop backedge. A
609
+ // mismatch restores the first-value cursor and falls through unchanged.
610
+ if (ASC_FEATURE_SIMD && reusableLength == 2) {
611
+ const firstStart = srcStart;
612
+ const pairEnd = parseFixed14Pair<valueof<T>>(
613
+ srcStart,
614
+ srcEnd,
615
+ reusableDataStart,
616
+ );
617
+ if (pairEnd) return pairEnd;
618
+ let next = parseFloatElementSWAR<valueof<T>>(
619
+ srcStart,
620
+ srcEnd,
621
+ reusableDataStart,
622
+ );
623
+ if (next) {
624
+ srcStart = next;
625
+ if (srcStart < srcEnd && load<u16>(srcStart) != COMMA)
626
+ srcStart = skipFloatArrayWhitespace(srcStart, srcEnd);
627
+ if (srcStart < srcEnd && load<u16>(srcStart) == COMMA) {
628
+ srcStart += 2;
629
+ if (srcStart < srcEnd && load<u16>(srcStart) == 32) {
630
+ srcStart += 2;
631
+ } else {
632
+ srcStart = skipFloatArrayWhitespace(srcStart, srcEnd);
633
+ }
634
+ next = parseFloatElementSWAR<valueof<T>>(
635
+ srcStart,
636
+ srcEnd,
637
+ reusableDataStart + elementSize,
638
+ );
639
+ if (next) {
640
+ srcStart = next;
641
+ if (srcStart < srcEnd && load<u16>(srcStart) != BRACKET_RIGHT)
642
+ srcStart = skipFloatArrayWhitespace(srcStart, srcEnd);
643
+ if (srcStart < srcEnd && load<u16>(srcStart) == BRACKET_RIGHT)
644
+ return srcStart + 2;
645
+ }
646
+ }
647
+ }
648
+ srcStart = firstStart;
649
+ }
650
+
284
651
  while (srcStart < srcEnd) {
285
- const slot = ensureArrayElementSlot<T>(out, index);
652
+ const slot =
653
+ index < reusableLength
654
+ ? reusableDataStart + <usize>index * elementSize
655
+ : ensureArrayElementSlot<T>(out, index);
286
656
  let next = parseFloatElementSWAR<valueof<T>>(srcStart, srcEnd, slot);
287
657
  if (!next) {
288
658
  next = deserializeFloatField_NAIVE<valueof<T>>(srcStart, srcEnd, slot);
@@ -306,18 +676,17 @@ export function deserializeFloatArrayBody<T extends number[]>(
306
676
  // pairs across thousands of geometries) the length is unchanged
307
677
  // and the AS runtime's `ensureCapacity` call is pure overhead.
308
678
  const nextLen = index + 1;
309
- if (out.length != nextLen) out.length = nextLen;
679
+ if (reusableLength != nextLen) out.length = nextLen;
310
680
  return srcStart + 2;
311
681
  }
312
682
  break;
313
683
  }
314
684
  } while (false);
315
685
 
316
- throw new Error("Failed to parse JSON!");
686
+ markProductionParseError();
687
+ return 0;
317
688
  }
318
-
319
-
320
- @inline export function deserializeFloatArrayField<T extends number[]>(
689
+ export function deserializeFloatArrayField<T extends number[]>(
321
690
  srcStart: usize,
322
691
  srcEnd: usize,
323
692
  fieldPtr: usize,
@@ -20,12 +20,22 @@ export function deserializeGenericArrayBody<T extends unknown[]>(
20
20
  if (load<u16>(srcStart) == BRACKET_RIGHT) return srcStart + 2;
21
21
 
22
22
  let index = 0;
23
+ const reusableLength = load<i32>(
24
+ changetype<usize>(out),
25
+ offsetof<T>("length_"),
26
+ );
27
+ const reusableDataStart = out.dataStart;
28
+ const elementSize = sizeof<valueof<T>>();
23
29
 
24
30
  while (srcStart < srcEnd) {
25
31
  const valueEnd = scanValueEnd(srcStart, srcEnd);
26
32
  if (!valueEnd || valueEnd <= srcStart) break;
27
33
 
28
- const slot = ensureArrayElementSlot<T>(out, index++);
34
+ const slot =
35
+ index < reusableLength
36
+ ? reusableDataStart + <usize>index * elementSize
37
+ : ensureArrayElementSlot<T>(out, index);
38
+ index++;
29
39
  store<valueof<T>>(slot, JSON.__deserialize<valueof<T>>(srcStart, valueEnd));
30
40
  srcStart = valueEnd;
31
41
 
@@ -38,7 +48,7 @@ export function deserializeGenericArrayBody<T extends unknown[]>(
38
48
  continue;
39
49
  }
40
50
  if (code == BRACKET_RIGHT) {
41
- out.length = index;
51
+ if (reusableLength != index) out.length = index;
42
52
  return srcStart + 2;
43
53
  }
44
54
  break;
@@ -47,8 +57,7 @@ export function deserializeGenericArrayBody<T extends unknown[]>(
47
57
  throw new Error("Failed to parse JSON!");
48
58
  }
49
59
 
50
-
51
- @inline export function deserializeGenericArrayField<T extends unknown[]>(
60
+ export function deserializeGenericArrayField<T extends unknown[]>(
52
61
  srcStart: usize,
53
62
  srcEnd: usize,
54
63
  fieldPtr: usize,