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.
Files changed (66) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/README.md +42 -0
  3. package/assembly/deserialize/error.ts +21 -0
  4. package/assembly/deserialize/index/arbitrary.ts +29 -8
  5. package/assembly/deserialize/index/array.ts +2 -1
  6. package/assembly/deserialize/index/bool.ts +1 -1
  7. package/assembly/deserialize/index/object.ts +3 -0
  8. package/assembly/deserialize/index/string.ts +37 -0
  9. package/assembly/deserialize/index.ts +0 -1
  10. package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
  11. package/assembly/deserialize/naive/array/array.ts +1 -0
  12. package/assembly/deserialize/naive/array/bool.ts +21 -11
  13. package/assembly/deserialize/naive/array/box.ts +13 -33
  14. package/assembly/deserialize/naive/array/float.ts +23 -13
  15. package/assembly/deserialize/naive/array/integer.ts +33 -21
  16. package/assembly/deserialize/naive/array/object.ts +1 -0
  17. package/assembly/deserialize/naive/array/string.ts +29 -16
  18. package/assembly/deserialize/naive/bool.ts +17 -5
  19. package/assembly/deserialize/naive/float.ts +1 -2
  20. package/assembly/deserialize/naive/map.ts +339 -39
  21. package/assembly/deserialize/naive/object.ts +122 -65
  22. package/assembly/deserialize/naive/raw.ts +13 -1
  23. package/assembly/deserialize/naive/set.ts +0 -1
  24. package/assembly/deserialize/naive/staticarray.ts +3 -2
  25. package/assembly/deserialize/naive/string.ts +41 -52
  26. package/assembly/deserialize/parseMode.ts +216 -0
  27. package/assembly/deserialize/simd/array/integer.ts +6 -3
  28. package/assembly/deserialize/simd/float.ts +35 -29
  29. package/assembly/deserialize/simd/string.ts +79 -17
  30. package/assembly/deserialize/string-validation.ts +34 -0
  31. package/assembly/deserialize/swar/array/array.ts +63 -13
  32. package/assembly/deserialize/swar/array/bool.ts +14 -3
  33. package/assembly/deserialize/swar/array/float.ts +394 -13
  34. package/assembly/deserialize/swar/array/generic.ts +12 -2
  35. package/assembly/deserialize/swar/array/integer.ts +22 -62
  36. package/assembly/deserialize/swar/array/object.ts +11 -2
  37. package/assembly/deserialize/swar/array/shared.ts +1 -1
  38. package/assembly/deserialize/swar/array/string.ts +34 -5
  39. package/assembly/deserialize/swar/array/struct.ts +109 -15
  40. package/assembly/deserialize/swar/float.ts +19 -12
  41. package/assembly/deserialize/swar/string.ts +70 -111
  42. package/assembly/index.d.ts +6 -0
  43. package/assembly/index.ts +411 -33
  44. package/assembly/serialize/index/bool.ts +1 -1
  45. package/assembly/serialize/index/float.ts +1 -5
  46. package/assembly/serialize/index/integer.ts +1 -1
  47. package/assembly/serialize/index/object.ts +17 -0
  48. package/assembly/serialize/naive/array.ts +64 -0
  49. package/assembly/serialize/naive/float.ts +0 -5
  50. package/assembly/serialize/naive/map.ts +37 -0
  51. package/assembly/serialize/naive/set.ts +0 -1
  52. package/assembly/serialize/naive/staticarray.ts +0 -1
  53. package/assembly/serialize/naive/string.ts +0 -6
  54. package/assembly/serialize/naive/typedarray.ts +0 -1
  55. package/assembly/util/eisel-lemire.ts +179 -0
  56. package/assembly/util/prettyWhitespaceSimd.ts +69 -0
  57. package/assembly/util/scanValueEndSimd.ts +59 -36
  58. package/assembly/util/validateJson.ts +338 -0
  59. package/lib/as-bs.ts +28 -6
  60. package/package.json +17 -7
  61. package/transform/lib/index.d.ts +6 -0
  62. package/transform/lib/index.js +773 -112
  63. package/transform/lib/types.d.ts +1 -0
  64. package/transform/lib/types.js +3 -0
  65. package/assembly/deserialize/index/struct.ts +0 -1
  66. package/assembly/deserialize/naive/struct.ts +0 -21
@@ -2,6 +2,27 @@ import { JSON } from "../../..";
2
2
  import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
3
3
  import { deserializeFloatArrayBody } from "./float";
4
4
  import { ensureArrayField, scanValueEnd, skipWhitespace } from "./shared";
5
+ import { skipPrettyWhitespace_SIMD } from "../../../util/prettyWhitespaceSimd";
6
+
7
+
8
+ @inline
9
+ function skipNestedArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
10
+ const code = load<u16>(srcStart);
11
+ if (ASC_FEATURE_SIMD && code == 10) {
12
+ // Common JSON.stringify(..., null, 2) shape at Canada's coordinate depth:
13
+ // LF + twelve spaces + the next nested array. Two fixed compares avoid a
14
+ // general whitespace mask, bitmask, and ctz on every coordinate pair.
15
+ if (
16
+ srcStart + 28 <= srcEnd &&
17
+ !v128.any_true(v128.xor(load<v128>(srcStart, 2), i16x8.splat(0x20))) &&
18
+ load<u64>(srcStart, 18) == 0x0020_0020_0020_0020 &&
19
+ load<u16>(srcStart, 26) == BRACKET_LEFT
20
+ )
21
+ return srcStart + 26;
22
+ return skipPrettyWhitespace_SIMD(srcStart, srcEnd);
23
+ }
24
+ return skipWhitespace(srcStart, srcEnd);
25
+ }
5
26
 
6
27
  export function deserializeArrayArrayBody<T extends unknown[][]>(
7
28
  srcStart: usize,
@@ -9,11 +30,21 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
9
30
  out: T,
10
31
  ): usize {
11
32
  let index = 0;
33
+ const reusableLength = load<i32>(
34
+ changetype<usize>(out),
35
+ offsetof<T>("length_"),
36
+ );
37
+ const reusableDataStart = out.dataStart;
38
+ const elementSize = sizeof<valueof<T>>();
12
39
 
13
40
  do {
14
41
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
15
42
  srcStart += 2;
16
- srcStart = skipWhitespace(srcStart, srcEnd);
43
+ if (srcStart < srcEnd) {
44
+ const code = load<u16>(srcStart);
45
+ if (code != BRACKET_LEFT)
46
+ srcStart = skipNestedArrayWhitespace(srcStart, srcEnd);
47
+ }
17
48
  if (srcStart >= srcEnd) break;
18
49
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
19
50
  out.length = 0;
@@ -23,8 +54,10 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
23
54
  while (srcStart < srcEnd) {
24
55
  if (isFloat<valueof<valueof<T>>>()) {
25
56
  let value: valueof<T>;
26
- if (index < out.length) {
27
- value = unchecked(out[index]);
57
+ if (index < reusableLength) {
58
+ value = load<valueof<T>>(
59
+ reusableDataStart + <usize>index * elementSize,
60
+ );
28
61
  } else {
29
62
  value = changetype<valueof<T>>(instantiate<valueof<T>>());
30
63
  out.push(value);
@@ -37,8 +70,10 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
37
70
  if (!srcStart || srcStart >= srcEnd) break;
38
71
  } else if (isArray<valueof<valueof<T>>>()) {
39
72
  let value: valueof<T>;
40
- if (index < out.length) {
41
- value = unchecked(out[index]);
73
+ if (index < reusableLength) {
74
+ value = load<valueof<T>>(
75
+ reusableDataStart + <usize>index * elementSize,
76
+ );
42
77
  } else {
43
78
  value = changetype<valueof<T>>(instantiate<valueof<T>>());
44
79
  out.push(value);
@@ -54,24 +89,39 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
54
89
  if (!valueEnd || valueEnd <= srcStart) break;
55
90
 
56
91
  let valuePtr: usize = 0;
57
- if (index < out.length) {
58
- valuePtr = changetype<usize>(unchecked(out[index]));
92
+ if (index < reusableLength) {
93
+ valuePtr = changetype<usize>(
94
+ load<valueof<T>>(reusableDataStart + <usize>index * elementSize),
95
+ );
59
96
  }
60
97
  const value = JSON.__deserialize<valueof<T>>(
61
98
  srcStart,
62
99
  valueEnd,
63
100
  valuePtr,
64
101
  );
65
- if (index < out.length) unchecked((out[index] = value));
66
- else out.push(value);
102
+ if (index < reusableLength) {
103
+ store<valueof<T>>(
104
+ reusableDataStart + <usize>index * elementSize,
105
+ value,
106
+ );
107
+ } else {
108
+ out.push(value);
109
+ }
67
110
  srcStart = valueEnd;
68
111
  }
69
112
 
70
- srcStart = skipWhitespace(srcStart, srcEnd);
71
- const code = load<u16>(srcStart);
113
+ let code = load<u16>(srcStart);
114
+ if (code != COMMA && code != BRACKET_RIGHT) {
115
+ srcStart = skipWhitespace(srcStart, srcEnd);
116
+ code = load<u16>(srcStart);
117
+ }
72
118
  if (code == COMMA) {
73
119
  srcStart += 2;
74
- srcStart = skipWhitespace(srcStart, srcEnd);
120
+ if (srcStart < srcEnd) {
121
+ code = load<u16>(srcStart);
122
+ if (code != BRACKET_LEFT)
123
+ srcStart = skipNestedArrayWhitespace(srcStart, srcEnd);
124
+ }
75
125
  index++;
76
126
  continue;
77
127
  }
@@ -80,7 +130,7 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
80
130
  // correct (the array is being reused with the same shape, e.g.
81
131
  // canada's geometry rings across repeated parses).
82
132
  const nextLen = index + 1;
83
- if (out.length != nextLen) out.length = nextLen;
133
+ if (reusableLength != nextLen) out.length = nextLen;
84
134
  return srcStart + 2;
85
135
  }
86
136
  break;
@@ -7,6 +7,7 @@ import {
7
7
  } from "../../../custom/chars";
8
8
  import { isSpace } from "../../../util";
9
9
  import { ensureArrayElementSlot, ensureArrayField } from "./shared";
10
+ import { markProductionParseError } from "../../error";
10
11
 
11
12
  function deserializeBooleanArrayBody<T extends boolean[]>(
12
13
  srcStart: usize,
@@ -14,6 +15,12 @@ function deserializeBooleanArrayBody<T extends boolean[]>(
14
15
  out: T,
15
16
  ): usize {
16
17
  let index = 0;
18
+ const reusableLength = load<i32>(
19
+ changetype<usize>(out),
20
+ offsetof<T>("length_"),
21
+ );
22
+ const reusableDataStart = out.dataStart;
23
+ const elementSize = sizeof<valueof<T>>();
17
24
 
18
25
  do {
19
26
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
@@ -26,7 +33,10 @@ function deserializeBooleanArrayBody<T extends boolean[]>(
26
33
  }
27
34
 
28
35
  while (srcStart < srcEnd) {
29
- const slot = ensureArrayElementSlot<T>(out, index);
36
+ const slot =
37
+ index < reusableLength
38
+ ? reusableDataStart + <usize>index * elementSize
39
+ : ensureArrayElementSlot<T>(out, index);
30
40
  const block = load<u64>(srcStart);
31
41
  if (block == TRUE_WORD_U64) {
32
42
  store<valueof<T>>(slot, true);
@@ -49,14 +59,15 @@ function deserializeBooleanArrayBody<T extends boolean[]>(
49
59
  }
50
60
  if (code == BRACKET_RIGHT) {
51
61
  const nextLen = index + 1;
52
- if (out.length != nextLen) out.length = nextLen;
62
+ if (reusableLength != nextLen) out.length = nextLen;
53
63
  return srcStart + 2;
54
64
  }
55
65
  break;
56
66
  }
57
67
  } while (false);
58
68
 
59
- throw new Error("Failed to parse JSON!");
69
+ markProductionParseError();
70
+ return 0;
60
71
  }
61
72
 
62
73
  export function deserializeBooleanArrayField<T extends boolean[]>(
@@ -4,14 +4,24 @@ 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
20
  function skipFloatArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
12
21
  while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
13
22
  return srcStart;
14
23
  }
24
+
15
25
  function fallbackStore<E>(origStart: usize, end: usize, slot: usize): void {
16
26
  const s = ptrToStr(origStart, end);
17
27
  if (sizeof<E>() == sizeof<f32>()) {
@@ -21,6 +31,228 @@ function fallbackStore<E>(origStart: usize, end: usize, slot: usize): void {
21
31
  }
22
32
  }
23
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
+
24
256
  /**
25
257
  * Inline single-pass Lemire-style float element parser for arrays.
26
258
  *
@@ -55,37 +287,128 @@ export function parseFloatElementSWAR<E>(
55
287
  let firstDigit = <u32>code - 48;
56
288
  if (firstDigit > 9) return 0;
57
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
+
58
381
  // Integer mantissa: scalar (most JSON integers are 1-3 digits).
59
382
  let mantissa: u64 = 0;
60
- let intDigits: i32 = 0;
383
+ const intStart = p;
61
384
  while (p < srcEnd) {
62
385
  const d = <u32>load<u16>(p) - 48;
63
386
  if (d > 9) break;
64
387
  mantissa = mantissa * 10 + <u64>d;
65
- intDigits++;
66
388
  p += 2;
67
389
  }
390
+ const intDigits = <i32>((p - intStart) >> 1);
68
391
 
69
392
  // Fractional mantissa: parse4 SWAR stride + scalar tail. Same u64
70
393
  // accumulator as the integer part - exponent compensates for fracDigits.
71
- let fracDigits: i32 = 0;
394
+ let fracStart = p;
72
395
  if (p < srcEnd && load<u16>(p) == 46) {
73
396
  p += 2;
397
+ fracStart = p;
74
398
  while (p + 6 < srcEnd) {
75
399
  const parsed = parse4Digits_PairMul(load<u64>(p));
76
400
  if (parsed == U32.MAX_VALUE) break;
77
401
  mantissa = mantissa * 10_000 + <u64>parsed;
78
- fracDigits += 4;
79
402
  p += 8;
80
403
  }
81
404
  while (p < srcEnd) {
82
405
  const d = <u32>load<u16>(p) - 48;
83
406
  if (d > 9) break;
84
407
  mantissa = mantissa * 10 + <u64>d;
85
- fracDigits++;
86
408
  p += 2;
87
409
  }
88
410
  }
411
+ const fracDigits = <i32>((p - fracStart) >> 1);
89
412
 
90
413
  const mantDigits = intDigits + fracDigits;
91
414
  let exponent: i32 = -fracDigits;
@@ -149,10 +472,12 @@ export function parseFloatElementSWAR<E>(
149
472
  result /= loadPow10(<u32>-exponent);
150
473
  }
151
474
  } else if (mantDigits <= 19) {
152
- // Mantissa fits in u64 but the fast-path constraints don't hold. Call
153
- // `scientific` directly with our already-parsed mantissa+exp, skipping
154
- // the `ptrToStr` allocation + strtod re-parse.
155
- 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);
156
481
  } else {
157
482
  // >19 mantissa digits - beyond u64 capacity, may need strtod's sticky-bit
158
483
  // pattern. Hand off to f*.parse on the float's substring.
@@ -254,12 +579,19 @@ export function deserializeFloatArray_SWAR<T extends number[]>(
254
579
  * `[lon,lat]` would over-allocate megabytes of f64 capacity. Instead we
255
580
  * use `ensureArrayElementSlot`'s grow-or-reuse strategy.
256
581
  */
582
+ @inline
257
583
  export function deserializeFloatArrayBody<T extends number[]>(
258
584
  srcStart: usize,
259
585
  srcEnd: usize,
260
586
  out: T,
261
587
  ): usize {
262
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>>();
263
595
 
264
596
  do {
265
597
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
@@ -271,8 +603,56 @@ export function deserializeFloatArrayBody<T extends number[]>(
271
603
  return srcStart + 2;
272
604
  }
273
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
+
274
651
  while (srcStart < srcEnd) {
275
- const slot = ensureArrayElementSlot<T>(out, index);
652
+ const slot =
653
+ index < reusableLength
654
+ ? reusableDataStart + <usize>index * elementSize
655
+ : ensureArrayElementSlot<T>(out, index);
276
656
  let next = parseFloatElementSWAR<valueof<T>>(srcStart, srcEnd, slot);
277
657
  if (!next) {
278
658
  next = deserializeFloatField_NAIVE<valueof<T>>(srcStart, srcEnd, slot);
@@ -296,14 +676,15 @@ export function deserializeFloatArrayBody<T extends number[]>(
296
676
  // pairs across thousands of geometries) the length is unchanged
297
677
  // and the AS runtime's `ensureCapacity` call is pure overhead.
298
678
  const nextLen = index + 1;
299
- if (out.length != nextLen) out.length = nextLen;
679
+ if (reusableLength != nextLen) out.length = nextLen;
300
680
  return srcStart + 2;
301
681
  }
302
682
  break;
303
683
  }
304
684
  } while (false);
305
685
 
306
- throw new Error("Failed to parse JSON!");
686
+ markProductionParseError();
687
+ return 0;
307
688
  }
308
689
  export function deserializeFloatArrayField<T extends number[]>(
309
690
  srcStart: usize,