@amritk/nish-x86_64-linux 0.17.0 → 0.20.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 (61) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +231 -20
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +203 -0
  14. package/runtime/runtime-wasm.c +16 -3
  15. package/runtime/runtime.c +89 -23
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +225 -20
  18. package/std/README.md +86 -55
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +1376 -42
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1412 -70
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +231 -45
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-conn-params.ts +86 -16
  45. package/std/net/quic-listener.ts +576 -219
  46. package/std/net/quic-packet.ts +443 -58
  47. package/std/net/quic-recovery.ts +361 -30
  48. package/std/net/quic-stream.ts +434 -28
  49. package/std/net/quic.ts +922 -206
  50. package/std/net/router.ts +676 -0
  51. package/std/net/tls/codec.ts +426 -27
  52. package/std/net/tls/record-server.ts +478 -132
  53. package/std/net/tls/record.ts +133 -42
  54. package/std/net/tls/schedule.ts +464 -55
  55. package/std/net/tls/ticket.ts +610 -0
  56. package/std/net/tls-tcp.ts +204 -57
  57. package/std/net/tls.ts +1132 -305
  58. package/std/net/url.ts +571 -0
  59. package/std/random.ts +94 -0
  60. package/std/text.ts +77 -0
  61. package/std/threads.ts +438 -14
@@ -0,0 +1,1296 @@
1
+ /**
2
+ * `nish/json-tokenizer` — a validating, streaming tokenizer for RFC 8259 JSON
3
+ * held in a window of a `u8[]`.
4
+ *
5
+ * `std/json`'s `jsonField` reads one field of one flat object and validates
6
+ * nothing. A server that takes JSON from a peer needs the opposite: every byte
7
+ * checked against the grammar, nothing allocated per request, and a hard limit
8
+ * on everything an attacker controls. That is this module.
9
+ *
10
+ * import { JsonLimits, JsonTokenizer, JSON_TOK_END, JSON_TOK_KEY } from "nish/json-tokenizer";
11
+ *
12
+ * const t = new JsonTokenizer(body, 0, toI32(body.length), new JsonLimits());
13
+ * let k: i32 = t.next();
14
+ * while (k > 0) {
15
+ * if (k === JSON_TOK_KEY && t.keyEquals("message")) {
16
+ * // t.next() is the value; t.stringInto(out, 0) copies it out
17
+ * }
18
+ * k = t.next();
19
+ * }
20
+ * // k === JSON_TOK_END: the window held one whole, valid JSON text
21
+ * // k < 0: a JSON_TOK_ERR_* code, also in t.failed, at byte t.errorAt
22
+ *
23
+ * **One token per `next()`.** It answers a `JSON_TOK_*` kind — an object or
24
+ * array opening or closing, a key, a string, a number, `true`, `false` or
25
+ * `null` — then `JSON_TOK_END` once the top-level value is complete and only
26
+ * blanks follow it, and `JSON_TOK_END` again on every later call.
27
+ * `tokStart` and `tokEnd` bound the token's bytes in the source (a string's
28
+ * include its quotes), and `depth` is the number of containers open after it.
29
+ * Structure is checked as the tokens go by: a comma or colon out of place, a
30
+ * closer that does not match its opener, a second top-level value, or anything
31
+ * but blanks after the first, is an error at the byte where it stands.
32
+ *
33
+ * **Errors are sticky.** The first error sets `failed` to a negative
34
+ * `JSON_TOK_ERR_*` code and `errorAt` to the byte offset it was found at, and
35
+ * every later `next()` answers the same code without reading anything, as
36
+ * `nish/net/hpack`'s decoder does. Nothing an input can hold panics, and no
37
+ * byte outside `src[off, off + len)` is ever read. `reset` starts over on a new
38
+ * window with the same limits and the same depth stack, so a server holds one
39
+ * tokenizer per connection and allocates nothing per request.
40
+ *
41
+ * **Strings are checked as UTF-8 and decoded on request.** A string may hold
42
+ * any well-formed UTF-8 (RFC 3629: no overlong form, no surrogate code point,
43
+ * nothing past U+10FFFF, no sequence cut short), no raw byte below 0x20, and
44
+ * the escapes of RFC 8259 §7. A `\u` escape of a high surrogate must be
45
+ * followed at once by one of a low surrogate, and a low surrogate must follow
46
+ * a high one: a lone surrogate escape is an error. RFC 8259 allows one, but it
47
+ * names no Unicode character, decodes to no valid UTF-8, and is what a peer
48
+ * smuggles past a check done on the decoded bytes, so this module takes the
49
+ * stricter reading (`docs/security/json.md`, "Lone surrogates"). Nothing is decoded until
50
+ * the caller asks: `stringInto` writes the decoded bytes of the current key
51
+ * or string into the caller's array, and `keyEquals` compares them with a
52
+ * string without writing them anywhere.
53
+ *
54
+ * **Numbers are checked against the grammar and parsed on request.**
55
+ * `numberF64` is correctly rounded: a number of at most 15 significant digits
56
+ * whose decimal exponent is within ±22 is one exact multiply or divide of two
57
+ * exact doubles (Clinger's fast path), and any other goes to the runtime's
58
+ * `parseFloat`, which is `strtod`, from a function whose own arena scope
59
+ * releases the copy it reads, so it leaves the arena where it was. `isInteger` and `numberI64` read a number
60
+ * written without a fraction or an exponent that fits in an `i64`, and
61
+ * `isU64` and `numberU64` one written without a sign either that fits in a
62
+ * `u64`.
63
+ *
64
+ * **Runs are read in bulk.** A string's plain bytes, the blanks between
65
+ * tokens and a number's digits are each stepped over by a loop that reads
66
+ * `src` directly, bounded by the window's end (or a string's or number's
67
+ * limit, which is never past it) and by the array's length, so a run stops
68
+ * exactly where a read through `jsonTokByteAt` would have answered `-1`.
69
+ * The readers of a number already scanned, and `stringInto`'s copy of a
70
+ * string with no escape, read `src` the same way; every other byte goes
71
+ * through `jsonTokByteAt`.
72
+ *
73
+ * **Duplicate keys are tokens.** `{"a":1,"a":2}` is two keys; which one wins,
74
+ * or whether a duplicate is an error, is the decision of whatever reads the
75
+ * tokens (`docs/security/json.md`, JSON-3).
76
+ *
77
+ * **The limits** are `JsonLimits`: nesting depth, bytes in one string, bytes
78
+ * in one number and bytes in the whole window. Each has its own error code and
79
+ * a default sized for a request body, and each is checked before the work it
80
+ * bounds, so a depth bomb costs one byte of stack per level allowed and an
81
+ * over-long string is refused at the first byte past the limit.
82
+ *
83
+ * Every name here is `json`-prefixed, because a `std/` module is compiled into
84
+ * the program that imports it and the symbol namespace is flat
85
+ * (`std/README.md`). Every offset is a byte offset, and every constant is an
86
+ * `i32`, so the module is the same under `--number-mode f64`
87
+ * (`docs/wp26-stdlib.md` §4). Written from RFC 8259 and RFC 3629, not ported
88
+ * from another implementation.
89
+ */
90
+
91
+ /** The top-level value is complete and only blanks follow it. Answered again on every later call. */
92
+ export const JSON_TOK_END: i32 = 0
93
+ /** `{`. */
94
+ export const JSON_TOK_OBJECT_BEGIN: i32 = 1
95
+ /** `}`. */
96
+ export const JSON_TOK_OBJECT_END: i32 = 2
97
+ /** `[`. */
98
+ export const JSON_TOK_ARRAY_BEGIN: i32 = 3
99
+ /** `]`. */
100
+ export const JSON_TOK_ARRAY_END: i32 = 4
101
+ /** A member's name: a string followed by its colon. The value is the next token. */
102
+ export const JSON_TOK_KEY: i32 = 5
103
+ /** A string value. */
104
+ export const JSON_TOK_STRING: i32 = 6
105
+ /** A number value. */
106
+ export const JSON_TOK_NUMBER: i32 = 7
107
+ /** `true`. */
108
+ export const JSON_TOK_TRUE: i32 = 8
109
+ /** `false`. */
110
+ export const JSON_TOK_FALSE: i32 = 9
111
+ /** `null`. */
112
+ export const JSON_TOK_NULL: i32 = 10
113
+
114
+ /** A byte that cannot stand where it stands: `[1,]`, `{"a" 1}`, `[1}`, `[01]`, `+1`, `.5`, `nulx`, a byte outside a string that is not ASCII. */
115
+ export const JSON_TOK_ERR_SYNTAX: i32 = -1
116
+ /** The window ended inside a value: an empty window, `[1,`, `{"a":`, an unclosed string. */
117
+ export const JSON_TOK_ERR_TRUNCATED: i32 = -2
118
+ /** Something other than blanks after the top-level value: `[] []`, `1 2`, a trailing NUL. */
119
+ export const JSON_TOK_ERR_TRAILING: i32 = -3
120
+ /** A string holds a byte sequence that is not UTF-8: an overlong form, a surrogate, a code point past U+10FFFF, a sequence cut short by a byte that does not continue it, a stray continuation byte. */
121
+ export const JSON_TOK_ERR_UTF8: i32 = -4
122
+ /** A backslash not followed by one of `"\/bfnrt`, or a `\u` not followed by four hex digits. */
123
+ export const JSON_TOK_ERR_ESCAPE: i32 = -5
124
+ /** A `\u` escape of a surrogate that is not one half of a high-then-low pair. */
125
+ export const JSON_TOK_ERR_SURROGATE: i32 = -6
126
+ /** A raw byte below 0x20 inside a string, which RFC 8259 §7 requires to be escaped. */
127
+ export const JSON_TOK_ERR_CONTROL: i32 = -7
128
+ /** A number outside the RFC 8259 §6 grammar: `-a`, `1.a`, `1ex`, `-Infinity`. A number the window cuts short (`1.`, `-`) is `JSON_TOK_ERR_TRUNCATED`. */
129
+ export const JSON_TOK_ERR_NUMBER: i32 = -8
130
+ /** An opening bracket or brace past `JsonLimits.maxDepth` open containers. */
131
+ export const JSON_TOK_ERR_DEPTH: i32 = -9
132
+ /** A string, or a key, longer than `JsonLimits.maxStringBytes` bytes between its quotes. */
133
+ export const JSON_TOK_ERR_STRING_LIMIT: i32 = -10
134
+ /** A number longer than `JsonLimits.maxNumberBytes` bytes. */
135
+ export const JSON_TOK_ERR_NUMBER_LIMIT: i32 = -11
136
+ /** A window longer than `JsonLimits.maxTotalBytes` bytes. Answered by the first `next()`, before anything is read. */
137
+ export const JSON_TOK_ERR_TOTAL_LIMIT: i32 = -12
138
+ /** `off` and `len` do not describe a window inside `src`. Answered by the first `next()`. */
139
+ export const JSON_TOK_ERR_WINDOW: i32 = -13
140
+
141
+ /**
142
+ * The default limits, sized for a request body. A depth of 128 is serde_json's
143
+ * recursion limit and deeper than any document a person writes; a string may
144
+ * be a mebibyte; 256 bytes hold every double in its shortest form many times
145
+ * over; and a body of 16 MiB is past what an HTTP server should buffer anyway.
146
+ */
147
+ export const JSON_DEFAULT_MAX_DEPTH: i32 = 128
148
+ export const JSON_DEFAULT_MAX_STRING_BYTES: i32 = 1048576
149
+ export const JSON_DEFAULT_MAX_NUMBER_BYTES: i32 = 256
150
+ export const JSON_DEFAULT_MAX_TOTAL_BYTES: i32 = 16777216
151
+
152
+ /**
153
+ * What a `JsonTokenizer` refuses to read, each with its own error code. The
154
+ * tokenizer copies the four numbers when it is made, so changing an object
155
+ * afterwards changes nothing for a tokenizer already made from it. A negative
156
+ * limit is read as zero: with a depth of zero only a scalar top-level value
157
+ * is accepted, with a string limit of zero only `""`.
158
+ */
159
+ export class JsonLimits {
160
+ /** Containers open at once; the tokenizer allocates one byte of stack per level, once. */
161
+ maxDepth: i32
162
+ /** Raw bytes between a string's quotes, escapes counted as written. */
163
+ maxStringBytes: i32
164
+ /** Bytes in one number, sign and exponent included. */
165
+ maxNumberBytes: i32
166
+ /** Bytes in the window handed to the constructor or to `reset`. */
167
+ maxTotalBytes: i32
168
+
169
+ /** The defaults, `JSON_DEFAULT_*`. */
170
+ constructor() {
171
+ this.maxDepth = JSON_DEFAULT_MAX_DEPTH
172
+ this.maxStringBytes = JSON_DEFAULT_MAX_STRING_BYTES
173
+ this.maxNumberBytes = JSON_DEFAULT_MAX_NUMBER_BYTES
174
+ this.maxTotalBytes = JSON_DEFAULT_MAX_TOTAL_BYTES
175
+ }
176
+ }
177
+
178
+ /** What `next()` expects at the cursor. */
179
+ const JSON_TOK_WANT_VALUE: i32 = 0
180
+ const JSON_TOK_WANT_VALUE_OR_CLOSE: i32 = 1
181
+ const JSON_TOK_WANT_KEY_OR_CLOSE: i32 = 2
182
+ const JSON_TOK_WANT_COLON: i32 = 3
183
+ const JSON_TOK_WANT_COMMA_OR_CLOSE: i32 = 4
184
+ const JSON_TOK_WANT_NOTHING: i32 = 5
185
+
186
+ /** What the depth stack holds for an open container. */
187
+ const JSON_TOK_IN_OBJECT: i32 = 1
188
+ const JSON_TOK_IN_ARRAY: i32 = 2
189
+
190
+ const JSON_TOK_TAB: i32 = 9
191
+ const JSON_TOK_NEWLINE: i32 = 10
192
+ const JSON_TOK_CARRIAGE_RETURN: i32 = 13
193
+ const JSON_TOK_SPACE: i32 = 32
194
+ const JSON_TOK_QUOTE: i32 = 34
195
+ const JSON_TOK_PLUS: i32 = 43
196
+ const JSON_TOK_COMMA: i32 = 44
197
+ const JSON_TOK_MINUS: i32 = 45
198
+ const JSON_TOK_DOT: i32 = 46
199
+ const JSON_TOK_SLASH: i32 = 47
200
+ const JSON_TOK_DIGIT_0: i32 = 48
201
+ const JSON_TOK_DIGIT_1: i32 = 49
202
+ const JSON_TOK_DIGIT_9: i32 = 57
203
+ const JSON_TOK_COLON: i32 = 58
204
+ const JSON_TOK_UPPER_A: i32 = 65
205
+ const JSON_TOK_UPPER_E: i32 = 69
206
+ const JSON_TOK_UPPER_F: i32 = 70
207
+ const JSON_TOK_OPEN_BRACKET: i32 = 91
208
+ const JSON_TOK_BACKSLASH: i32 = 92
209
+ const JSON_TOK_CLOSE_BRACKET: i32 = 93
210
+ const JSON_TOK_LOWER_A: i32 = 97
211
+ const JSON_TOK_LOWER_B: i32 = 98
212
+ const JSON_TOK_LOWER_E: i32 = 101
213
+ const JSON_TOK_LOWER_F: i32 = 102
214
+ const JSON_TOK_LOWER_N: i32 = 110
215
+ const JSON_TOK_LOWER_R: i32 = 114
216
+ const JSON_TOK_LOWER_T: i32 = 116
217
+ const JSON_TOK_LOWER_U: i32 = 117
218
+ const JSON_TOK_OPEN_BRACE: i32 = 123
219
+ const JSON_TOK_CLOSE_BRACE: i32 = 125
220
+
221
+ /** The bytes the short escapes stand for: backspace, form feed, newline, carriage return, tab. */
222
+ const JSON_TOK_BACKSPACE: i32 = 8
223
+ const JSON_TOK_FORM_FEED: i32 = 12
224
+
225
+ /** The bytes and code points UTF-8 is checked and built against (RFC 3629 §3, §4). */
226
+ const JSON_TOK_ASCII_END: i32 = 128
227
+ const JSON_TOK_CONTINUATION_LOW: i32 = 128
228
+ const JSON_TOK_CONTINUATION_HIGH: i32 = 191
229
+ const JSON_TOK_LEAD_TWO_LOW: i32 = 194
230
+ const JSON_TOK_LEAD_TWO_HIGH: i32 = 223
231
+ const JSON_TOK_LEAD_E0: i32 = 224
232
+ const JSON_TOK_LEAD_ED: i32 = 237
233
+ const JSON_TOK_LEAD_THREE_HIGH: i32 = 239
234
+ const JSON_TOK_LEAD_F0: i32 = 240
235
+ const JSON_TOK_LEAD_F4: i32 = 244
236
+ const JSON_TOK_E0_SECOND_LOW: i32 = 160
237
+ const JSON_TOK_ED_SECOND_HIGH: i32 = 159
238
+ const JSON_TOK_F0_SECOND_LOW: i32 = 144
239
+ const JSON_TOK_F4_SECOND_HIGH: i32 = 143
240
+ const JSON_TOK_TWO_BYTE_LEAD: i32 = 192
241
+ const JSON_TOK_THREE_BYTE_LEAD: i32 = 224
242
+ const JSON_TOK_FOUR_BYTE_LEAD: i32 = 240
243
+ const JSON_TOK_LOW_SIX: i32 = 63
244
+ const JSON_TOK_CONTINUATION_BITS: i32 = 6
245
+ const JSON_TOK_TWO_BYTE_FLOOR: i32 = 128
246
+ const JSON_TOK_THREE_BYTE_FLOOR: i32 = 2048
247
+ const JSON_TOK_FOUR_BYTE_FLOOR: i32 = 65536
248
+ const JSON_TOK_HIGH_SURROGATE: i32 = 55296
249
+ const JSON_TOK_LOW_SURROGATE: i32 = 56320
250
+ const JSON_TOK_SURROGATE_END: i32 = 57344
251
+ const JSON_TOK_HEX_BASE: i32 = 16
252
+ const JSON_TOK_TEN: i32 = 10
253
+
254
+ /** Clinger's fast path: at most 15 significant digits, so the mantissa is an exact double, and 10^22, the largest exact power of ten. */
255
+ const JSON_TOK_FAST_DIGITS: i32 = 15
256
+ const JSON_TOK_FAST_EXPONENT: i32 = 22
257
+ /** Where an exponent's digits stop being added up: past it, the fast path is out of reach and `parseFloat` reads the text. */
258
+ const JSON_TOK_EXPONENT_CAP: i32 = 100000
259
+
260
+ /**
261
+ * The furthest a window may end. The scan reads up to twelve bytes past a
262
+ * position it has checked against the end, so a window that ends within 64
263
+ * bytes of 2^31 - 1 is refused rather than let an offset overflow. Reaching
264
+ * it takes an array of 2 GiB, so no test does.
265
+ */
266
+ const JSON_TOK_END_CEILING: i32 = 2147483583
267
+
268
+ /** The byte at `i`, or `-1` when `i` is outside `[0, end)` or outside `src`. */
269
+ const jsonTokByteAt = (src: u8[], i: i32, end: i32): i32 => {
270
+ if (i < 0 || i >= end || i >= toI32(src.length)) {
271
+ return -1
272
+ }
273
+ return toI32(src[i])
274
+ }
275
+
276
+ /** The four bytes RFC 8259 §2 allows between tokens. */
277
+ const jsonTokIsBlank = (b: i32): boolean =>
278
+ b === JSON_TOK_SPACE || b === JSON_TOK_TAB || b === JSON_TOK_NEWLINE || b === JSON_TOK_CARRIAGE_RETURN
279
+
280
+ const jsonTokIsDigit = (b: i32): boolean => b >= JSON_TOK_DIGIT_0 && b <= JSON_TOK_DIGIT_9
281
+
282
+ /**
283
+ * A byte a string's scan steps over without a second look: printable ASCII
284
+ * that is neither the closing quote nor a backslash.
285
+ */
286
+ const jsonTokIsPlain = (b: i32): boolean =>
287
+ b >= JSON_TOK_SPACE && b < JSON_TOK_ASCII_END && b !== JSON_TOK_QUOTE && b !== JSON_TOK_BACKSLASH
288
+
289
+ /**
290
+ * The first index at or after `i` that is not a blank, or `end`. The run is
291
+ * read straight out of `src`, bounded by both `end` and the array's length,
292
+ * so it stops wherever `jsonTokByteAt` would have answered `-1`.
293
+ */
294
+ const jsonTokSkipBlank = (src: u8[], i: i32, end: i32): i32 => {
295
+ if (i < 0) {
296
+ return i
297
+ }
298
+ const n: i32 = toI32(src.length)
299
+ let j: i32 = i
300
+ while (j < end && j < n && jsonTokIsBlank(toI32(src[j]))) {
301
+ j += 1
302
+ }
303
+ return j
304
+ }
305
+
306
+ /** The first index at or after `i` that is not a digit, or `end`, read as `jsonTokSkipBlank` reads its run. */
307
+ const jsonTokSkipDigits = (src: u8[], i: i32, end: i32): i32 => {
308
+ if (i < 0) {
309
+ return i
310
+ }
311
+ const n: i32 = toI32(src.length)
312
+ let j: i32 = i
313
+ while (j < end && j < n && jsonTokIsDigit(toI32(src[j]))) {
314
+ j += 1
315
+ }
316
+ return j
317
+ }
318
+
319
+ /** The first index at or after `i` that is not a plain string byte, or `limit`, read as `jsonTokSkipBlank` reads its run. */
320
+ const jsonTokSkipPlain = (src: u8[], i: i32, limit: i32): i32 => {
321
+ if (i < 0) {
322
+ return i
323
+ }
324
+ const n: i32 = toI32(src.length)
325
+ let j: i32 = i
326
+ while (j < limit && j < n && jsonTokIsPlain(toI32(src[j]))) {
327
+ j += 1
328
+ }
329
+ return j
330
+ }
331
+
332
+ /** The value of one hex digit, or `-1`. */
333
+ const jsonTokHexDigit = (b: i32): i32 => {
334
+ if (jsonTokIsDigit(b)) {
335
+ return b - JSON_TOK_DIGIT_0
336
+ }
337
+ if (b >= JSON_TOK_LOWER_A && b <= JSON_TOK_LOWER_F) {
338
+ return b - JSON_TOK_LOWER_A + JSON_TOK_TEN
339
+ }
340
+ if (b >= JSON_TOK_UPPER_A && b <= JSON_TOK_UPPER_F) {
341
+ return b - JSON_TOK_UPPER_A + JSON_TOK_TEN
342
+ }
343
+ return -1
344
+ }
345
+
346
+ /**
347
+ * The four hex digits at `at` as one value; `JSON_TOK_ERR_TRUNCATED` when the
348
+ * window ends before the fourth, and `JSON_TOK_ERR_ESCAPE` when one is not a
349
+ * hex digit.
350
+ */
351
+ const jsonTokHex4 = (src: u8[], at: i32, end: i32): i32 => {
352
+ let value: i32 = 0
353
+ let k: i32 = 0
354
+ while (k < 4) {
355
+ const b: i32 = jsonTokByteAt(src, at + k, end)
356
+ if (b < 0) {
357
+ return JSON_TOK_ERR_TRUNCATED
358
+ }
359
+ const digit: i32 = jsonTokHexDigit(b)
360
+ if (digit < 0) {
361
+ return JSON_TOK_ERR_ESCAPE
362
+ }
363
+ value = value * JSON_TOK_HEX_BASE + digit
364
+ k += 1
365
+ }
366
+ return value
367
+ }
368
+
369
+ /** Whether byte `b`, a lead or a continuation, lies between `low` and `high`, inclusive; `-1`, past the window, never does. */
370
+ const jsonTokInRange = (b: i32, low: i32, high: i32): boolean => b >= low && b <= high
371
+
372
+ /**
373
+ * The length of the UTF-8 sequence whose lead byte, at `at`, is not ASCII, or
374
+ * `JSON_TOK_ERR_UTF8`. RFC 3629 §4's table, row by row: the second byte's
375
+ * range depends on the lead, which is what refuses an overlong form (`C0`,
376
+ * `C1`, `E0 80`–`E0 9F`, `F0 80`–`F0 8F`), a surrogate (`ED A0`–`ED BF`) and a
377
+ * code point past U+10FFFF (`F4 90` and up, `F5`–`FF`). A sequence that is
378
+ * well formed as far as the window goes and is cut short by its end is
379
+ * `JSON_TOK_ERR_TRUNCATED`, as any other value the window cuts short is.
380
+ */
381
+ const jsonTokUtf8Length = (src: u8[], at: i32, end: i32): i32 => {
382
+ const lead: i32 = jsonTokByteAt(src, at, end)
383
+ const second: i32 = jsonTokByteAt(src, at + 1, end)
384
+ let low: i32 = JSON_TOK_CONTINUATION_LOW
385
+ let high: i32 = JSON_TOK_CONTINUATION_HIGH
386
+ let length: i32 = 0
387
+ if (jsonTokInRange(lead, JSON_TOK_LEAD_TWO_LOW, JSON_TOK_LEAD_TWO_HIGH)) {
388
+ length = 2
389
+ } else if (jsonTokInRange(lead, JSON_TOK_LEAD_E0, JSON_TOK_LEAD_THREE_HIGH)) {
390
+ length = 3
391
+ if (lead === JSON_TOK_LEAD_E0) {
392
+ low = JSON_TOK_E0_SECOND_LOW
393
+ } else if (lead === JSON_TOK_LEAD_ED) {
394
+ high = JSON_TOK_ED_SECOND_HIGH
395
+ }
396
+ } else if (jsonTokInRange(lead, JSON_TOK_LEAD_F0, JSON_TOK_LEAD_F4)) {
397
+ length = 4
398
+ if (lead === JSON_TOK_LEAD_F0) {
399
+ low = JSON_TOK_F0_SECOND_LOW
400
+ } else if (lead === JSON_TOK_LEAD_F4) {
401
+ high = JSON_TOK_F4_SECOND_HIGH
402
+ }
403
+ } else {
404
+ return JSON_TOK_ERR_UTF8
405
+ }
406
+ if (!jsonTokInRange(second, low, high)) {
407
+ return second < 0 ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_UTF8
408
+ }
409
+ let k: i32 = 2
410
+ while (k < length) {
411
+ const next: i32 = jsonTokByteAt(src, at + k, end)
412
+ if (!jsonTokInRange(next, JSON_TOK_CONTINUATION_LOW, JSON_TOK_CONTINUATION_HIGH)) {
413
+ return next < 0 ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_UTF8
414
+ }
415
+ k += 1
416
+ }
417
+ return length
418
+ }
419
+
420
+ /** How many bytes code point `cp` takes in UTF-8. */
421
+ const jsonTokUtf8Width = (cp: i32): i32 => {
422
+ if (cp < JSON_TOK_TWO_BYTE_FLOOR) {
423
+ return 1
424
+ }
425
+ if (cp < JSON_TOK_THREE_BYTE_FLOOR) {
426
+ return 2
427
+ }
428
+ return cp < JSON_TOK_FOUR_BYTE_FLOOR ? 3 : 4
429
+ }
430
+
431
+ /** Byte `k` of code point `cp`'s `width`-byte UTF-8 form. */
432
+ const jsonTokUtf8Byte = (cp: i32, width: i32, k: i32): i32 => {
433
+ if (width === 1) {
434
+ return cp
435
+ }
436
+ const shift: i32 = (width - 1 - k) * JSON_TOK_CONTINUATION_BITS
437
+ const bits: i32 = (cp >> shift) & JSON_TOK_LOW_SIX
438
+ if (k > 0) {
439
+ return JSON_TOK_CONTINUATION_LOW | bits
440
+ }
441
+ if (width === 2) {
442
+ return JSON_TOK_TWO_BYTE_LEAD | (cp >> 6)
443
+ }
444
+ return width === 3 ? JSON_TOK_THREE_BYTE_LEAD | (cp >> 12) : JSON_TOK_FOUR_BYTE_LEAD | (cp >> 18)
445
+ }
446
+
447
+ /** The byte a short escape's letter stands for, or `-1` when the letter is not one of `"\/bfnrt`. */
448
+ const jsonTokShortEscape = (b: i32): i32 => {
449
+ switch (b) {
450
+ case JSON_TOK_QUOTE:
451
+ return JSON_TOK_QUOTE
452
+ case JSON_TOK_BACKSLASH:
453
+ return JSON_TOK_BACKSLASH
454
+ case JSON_TOK_SLASH:
455
+ return JSON_TOK_SLASH
456
+ case JSON_TOK_LOWER_B:
457
+ return JSON_TOK_BACKSPACE
458
+ case JSON_TOK_LOWER_F:
459
+ return JSON_TOK_FORM_FEED
460
+ case JSON_TOK_LOWER_N:
461
+ return JSON_TOK_NEWLINE
462
+ case JSON_TOK_LOWER_R:
463
+ return JSON_TOK_CARRIAGE_RETURN
464
+ case JSON_TOK_LOWER_T:
465
+ return JSON_TOK_TAB
466
+ default:
467
+ return -1
468
+ }
469
+ }
470
+
471
+ /**
472
+ * How many raw bytes the escape at `at` takes, given the code point it decodes
473
+ * to: 12 for a surrogate pair, the only escape past U+FFFF, 6 for any other
474
+ * `\u`, 2 for a short one.
475
+ */
476
+ const jsonTokEscapeLength = (src: u8[], at: i32, end: i32, cp: i32): i32 => {
477
+ if (cp >= JSON_TOK_FOUR_BYTE_FLOOR) {
478
+ return 12
479
+ }
480
+ return jsonTokByteAt(src, at + 1, end) === JSON_TOK_LOWER_U ? 6 : 2
481
+ }
482
+
483
+ /**
484
+ * The tokenizer. One per connection or per document; `reset` reuses it, and
485
+ * nothing it does after the constructor allocates, except `numberF64`'s
486
+ * `parseFloat` path, which releases what it allocated before it returns.
487
+ *
488
+ * The fields a caller reads are `depth`, `kind`, `tokStart`, `tokEnd`,
489
+ * `failed` and `errorAt`. The rest are the cursor and what the scan of the
490
+ * current token found, and are read through the methods.
491
+ */
492
+ export class JsonTokenizer {
493
+ src: u8[]
494
+ /** The integral number `integerScan` read last, negated. */
495
+ integerValue: i64
496
+ /** The integral number `unsignedScan` read last. */
497
+ unsignedValue: u64
498
+ /** One byte per open container, `JSON_TOK_IN_OBJECT` or `JSON_TOK_IN_ARRAY`, `maxDepth` long. */
499
+ stack: u8[]
500
+ /** The window: bytes `[off, end)` of `src`. */
501
+ off: i32
502
+ end: i32
503
+ /** The next byte `next()` reads. */
504
+ pos: i32
505
+ maxDepth: i32
506
+ maxStringBytes: i32
507
+ maxNumberBytes: i32
508
+ maxTotalBytes: i32
509
+ /** Containers open after the current token. */
510
+ depth: i32
511
+ /** What `next()` expects at `pos`. */
512
+ want: i32
513
+ /** The current token's kind, the last `next()` answered; `JSON_TOK_END` before the first. */
514
+ kind: i32
515
+ /** The current token's bytes, `src[tokStart, tokEnd)`; a string's and a key's include their quotes. */
516
+ tokStart: i32
517
+ tokEnd: i32
518
+ /** The sticky error, a negative `JSON_TOK_ERR_*` code; `0` until one. */
519
+ failed: i32
520
+ /**
521
+ * The byte offset in `src` the error was found at, and `-1` until one. Read
522
+ * it only once `failed` is set: a `JSON_TOK_ERR_WINDOW` records the `off`
523
+ * the caller passed, which may itself be negative or past the end of `src`.
524
+ */
525
+ errorAt: i32
526
+ /** The current string's or key's length once decoded; shorter than its raw length exactly when it holds an escape. */
527
+ stringBytes: i32
528
+ /** Whether the current number has neither a fraction nor an exponent. */
529
+ integral: boolean
530
+
531
+ constructor(src: u8[], off: i32, len: i32, limits: JsonLimits) {
532
+ this.maxDepth = limits.maxDepth < 0 ? 0 : limits.maxDepth
533
+ this.maxStringBytes = limits.maxStringBytes < 0 ? 0 : limits.maxStringBytes
534
+ this.maxNumberBytes = limits.maxNumberBytes < 0 ? 0 : limits.maxNumberBytes
535
+ this.maxTotalBytes = limits.maxTotalBytes < 0 ? 0 : limits.maxTotalBytes
536
+ this.stack = new Array<u8>(this.maxDepth)
537
+ this.src = src
538
+ this.off = 0
539
+ this.end = 0
540
+ this.pos = 0
541
+ this.depth = 0
542
+ this.want = JSON_TOK_WANT_VALUE
543
+ this.kind = JSON_TOK_END
544
+ this.tokStart = 0
545
+ this.tokEnd = 0
546
+ this.failed = 0
547
+ this.errorAt = -1
548
+ this.stringBytes = 0
549
+ this.integral = false
550
+ this.integerValue = toI64(0)
551
+ this.unsignedValue = toU64(0)
552
+ this.reset(src, off, len)
553
+ }
554
+
555
+ /**
556
+ * Starts over on `src[off, off + len)`, keeping the limits and the depth
557
+ * stack, and clearing any error. A window outside `src` or longer than the
558
+ * total limit is refused here, and the first `next()` answers it.
559
+ */
560
+ reset(src: u8[], off: i32, len: i32): void {
561
+ this.src = src
562
+ this.depth = 0
563
+ this.want = JSON_TOK_WANT_VALUE
564
+ this.kind = JSON_TOK_END
565
+ this.tokStart = off
566
+ this.tokEnd = off
567
+ this.failed = 0
568
+ this.errorAt = -1
569
+ this.stringBytes = 0
570
+ this.integral = false
571
+ // The second test is only reached when the first proves `off + len` fits.
572
+ if (off < 0 || len < 0 || off > toI32(src.length) - len || off + len > JSON_TOK_END_CEILING) {
573
+ this.off = 0
574
+ this.end = 0
575
+ this.pos = 0
576
+ this.fail(JSON_TOK_ERR_WINDOW, off)
577
+ return
578
+ }
579
+ this.off = off
580
+ this.end = off + len
581
+ this.pos = off
582
+ if (len > this.maxTotalBytes) {
583
+ this.fail(JSON_TOK_ERR_TOTAL_LIMIT, off + this.maxTotalBytes)
584
+ }
585
+ }
586
+
587
+ /** Records the first error and answers it. */
588
+ fail(code: i32, at: i32): i32 {
589
+ if (this.failed === 0) {
590
+ this.failed = code
591
+ this.errorAt = at
592
+ }
593
+ return this.failed
594
+ }
595
+
596
+ /** The next token's kind, `JSON_TOK_END`, or a negative error, which is sticky. */
597
+ next(): i32 {
598
+ if (this.failed !== 0) {
599
+ return this.failed
600
+ }
601
+ const src: u8[] = this.src
602
+ const end: i32 = this.end
603
+ const at: i32 = jsonTokSkipBlank(src, this.pos, end)
604
+ const b: i32 = jsonTokByteAt(src, at, end)
605
+ switch (this.want) {
606
+ case JSON_TOK_WANT_NOTHING:
607
+ if (b < 0) {
608
+ this.pos = at
609
+ this.kind = JSON_TOK_END
610
+ return JSON_TOK_END
611
+ }
612
+ return this.fail(JSON_TOK_ERR_TRAILING, at)
613
+ case JSON_TOK_WANT_COLON:
614
+ if (b === JSON_TOK_COLON) {
615
+ return this.value(jsonTokSkipBlank(src, at + 1, end))
616
+ }
617
+ return this.fail(b < 0 ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_SYNTAX, at)
618
+ case JSON_TOK_WANT_KEY_OR_CLOSE:
619
+ if (b === JSON_TOK_CLOSE_BRACE) {
620
+ return this.close(at, JSON_TOK_IN_OBJECT)
621
+ }
622
+ return this.key(at)
623
+ case JSON_TOK_WANT_VALUE_OR_CLOSE:
624
+ if (b === JSON_TOK_CLOSE_BRACKET) {
625
+ return this.close(at, JSON_TOK_IN_ARRAY)
626
+ }
627
+ return this.value(at)
628
+ case JSON_TOK_WANT_COMMA_OR_CLOSE:
629
+ if (b === JSON_TOK_COMMA) {
630
+ const after: i32 = jsonTokSkipBlank(src, at + 1, end)
631
+ if (this.top() === JSON_TOK_IN_OBJECT) {
632
+ return this.key(after)
633
+ }
634
+ return this.value(after)
635
+ }
636
+ if (b === JSON_TOK_CLOSE_BRACE) {
637
+ return this.close(at, JSON_TOK_IN_OBJECT)
638
+ }
639
+ if (b === JSON_TOK_CLOSE_BRACKET) {
640
+ return this.close(at, JSON_TOK_IN_ARRAY)
641
+ }
642
+ return this.fail(b < 0 ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_SYNTAX, at)
643
+ default:
644
+ return this.value(at)
645
+ }
646
+ }
647
+
648
+ /** The kind of the innermost open container, or `0` at the top level. */
649
+ top(): i32 {
650
+ const d: i32 = this.depth
651
+ if (d <= 0 || d > toI32(this.stack.length)) {
652
+ return 0
653
+ }
654
+ return toI32(this.stack[d - 1])
655
+ }
656
+
657
+ /** Records the token `src[start, stop)` of kind `k` and what may follow it. */
658
+ emit(k: i32, start: i32, stop: i32): i32 {
659
+ this.kind = k
660
+ this.tokStart = start
661
+ this.tokEnd = stop
662
+ this.pos = stop
663
+ if (k === JSON_TOK_KEY) {
664
+ this.want = JSON_TOK_WANT_COLON
665
+ } else if (k === JSON_TOK_OBJECT_BEGIN) {
666
+ this.want = JSON_TOK_WANT_KEY_OR_CLOSE
667
+ } else if (k === JSON_TOK_ARRAY_BEGIN) {
668
+ this.want = JSON_TOK_WANT_VALUE_OR_CLOSE
669
+ } else {
670
+ this.want = this.depth === 0 ? JSON_TOK_WANT_NOTHING : JSON_TOK_WANT_COMMA_OR_CLOSE
671
+ }
672
+ return k
673
+ }
674
+
675
+ /** A closer at `at`, which must match the innermost container, `inside`. */
676
+ close(at: i32, inside: i32): i32 {
677
+ if (this.top() !== inside) {
678
+ return this.fail(JSON_TOK_ERR_SYNTAX, at)
679
+ }
680
+ this.depth -= 1
681
+ return this.emit(inside === JSON_TOK_IN_OBJECT ? JSON_TOK_OBJECT_END : JSON_TOK_ARRAY_END, at, at + 1)
682
+ }
683
+
684
+ /** An opener at `at`, refused past the depth limit. */
685
+ open(at: i32, inside: i32): i32 {
686
+ const d: i32 = this.depth
687
+ if (d >= this.maxDepth || d < 0 || d >= toI32(this.stack.length)) {
688
+ return this.fail(JSON_TOK_ERR_DEPTH, at)
689
+ }
690
+ this.stack[d] = toU8(inside)
691
+ this.depth = d + 1
692
+ return this.emit(inside === JSON_TOK_IN_OBJECT ? JSON_TOK_OBJECT_BEGIN : JSON_TOK_ARRAY_BEGIN, at, at + 1)
693
+ }
694
+
695
+ /** A key at `at`: a string, whose colon the next call reads. */
696
+ key(at: i32): i32 {
697
+ const b: i32 = jsonTokByteAt(this.src, at, this.end)
698
+ if (b === JSON_TOK_QUOTE) {
699
+ return this.string(at, JSON_TOK_KEY)
700
+ }
701
+ return this.fail(b < 0 ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_SYNTAX, at)
702
+ }
703
+
704
+ /** A value at `at`. */
705
+ value(at: i32): i32 {
706
+ const src: u8[] = this.src
707
+ const end: i32 = this.end
708
+ const b: i32 = jsonTokByteAt(src, at, end)
709
+ if (b === JSON_TOK_QUOTE) {
710
+ return this.string(at, JSON_TOK_STRING)
711
+ }
712
+ if (b === JSON_TOK_MINUS || jsonTokIsDigit(b)) {
713
+ return this.number(at)
714
+ }
715
+ switch (b) {
716
+ case JSON_TOK_OPEN_BRACE:
717
+ return this.open(at, JSON_TOK_IN_OBJECT)
718
+ case JSON_TOK_OPEN_BRACKET:
719
+ return this.open(at, JSON_TOK_IN_ARRAY)
720
+ case JSON_TOK_LOWER_T:
721
+ return this.literal(at, "true", JSON_TOK_TRUE)
722
+ case JSON_TOK_LOWER_F:
723
+ return this.literal(at, "false", JSON_TOK_FALSE)
724
+ case JSON_TOK_LOWER_N:
725
+ return this.literal(at, "null", JSON_TOK_NULL)
726
+ default:
727
+ return this.fail(b < 0 ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_SYNTAX, at)
728
+ }
729
+ }
730
+
731
+ /** `true`, `false` or `null` at `at`; a window that ends inside the word is truncated. */
732
+ literal(at: i32, word: string, k: i32): i32 {
733
+ const n: i32 = toI32(word.length)
734
+ let matched: i32 = 0
735
+ while (
736
+ matched < n &&
737
+ jsonTokByteAt(this.src, at + matched, this.end) === toI32(word.charCodeAt(matched))
738
+ ) {
739
+ matched += 1
740
+ }
741
+ if (matched === n) {
742
+ return this.emit(k, at, at + n)
743
+ }
744
+ return this.fail(at + matched >= this.end ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_SYNTAX, at + matched)
745
+ }
746
+
747
+ /**
748
+ * A number at `at`, to RFC 8259 §6: `-`? then `0` or a digit run not
749
+ * starting with `0`, then `.` and digits, then `e` or `E`, a sign and
750
+ * digits. What follows it is the next call's to judge, so `01` is a `0`
751
+ * and then an error: `JSON_TOK_ERR_SYNTAX` inside a container (`[01]`),
752
+ * `JSON_TOK_ERR_TRAILING` at the top level. `1x` is a `1` and then the same.
753
+ *
754
+ * Nothing past the limit is read: the scan stops one byte past it, and a
755
+ * number that reaches that byte is refused for its length, whatever the
756
+ * grammar would have said of the rest.
757
+ */
758
+ number(at: i32): i32 {
759
+ const src: u8[] = this.src
760
+ const end: i32 = this.maxNumberBytes >= this.end - at ? this.end : at + this.maxNumberBytes + 1
761
+ let i: i32 = at
762
+ if (jsonTokByteAt(src, i, end) === JSON_TOK_MINUS) {
763
+ i += 1
764
+ }
765
+ const first: i32 = jsonTokByteAt(src, i, end)
766
+ if (first === JSON_TOK_DIGIT_0) {
767
+ i += 1
768
+ } else if (first >= JSON_TOK_DIGIT_1 && first <= JSON_TOK_DIGIT_9) {
769
+ i = jsonTokSkipDigits(src, i + 1, end)
770
+ } else {
771
+ return this.numberRefused(first < 0 ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_NUMBER, at, i)
772
+ }
773
+ let integral = true
774
+ if (jsonTokByteAt(src, i, end) === JSON_TOK_DOT) {
775
+ integral = false
776
+ i += 1
777
+ const digit: i32 = jsonTokByteAt(src, i, end)
778
+ if (!jsonTokIsDigit(digit)) {
779
+ return this.numberRefused(digit < 0 ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_NUMBER, at, i)
780
+ }
781
+ i = jsonTokSkipDigits(src, i + 1, end)
782
+ }
783
+ const e: i32 = jsonTokByteAt(src, i, end)
784
+ if (e === JSON_TOK_LOWER_E || e === JSON_TOK_UPPER_E) {
785
+ integral = false
786
+ i += 1
787
+ const sign: i32 = jsonTokByteAt(src, i, end)
788
+ if (sign === JSON_TOK_PLUS || sign === JSON_TOK_MINUS) {
789
+ i += 1
790
+ }
791
+ const digit: i32 = jsonTokByteAt(src, i, end)
792
+ if (!jsonTokIsDigit(digit)) {
793
+ return this.numberRefused(digit < 0 ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_NUMBER, at, i)
794
+ }
795
+ i = jsonTokSkipDigits(src, i + 1, end)
796
+ }
797
+ if (i - at > this.maxNumberBytes) {
798
+ return this.fail(JSON_TOK_ERR_NUMBER_LIMIT, at + this.maxNumberBytes)
799
+ }
800
+ this.integral = integral
801
+ return this.emit(JSON_TOK_NUMBER, at, i)
802
+ }
803
+
804
+ /** `code` at `i`, unless the number starting at `at` has run past its limit, which is the error then. */
805
+ numberRefused(code: i32, at: i32, i: i32): i32 {
806
+ if (i - at > this.maxNumberBytes) {
807
+ return this.fail(JSON_TOK_ERR_NUMBER_LIMIT, at + this.maxNumberBytes)
808
+ }
809
+ return this.fail(code, i)
810
+ }
811
+
812
+ /**
813
+ * A string at `at`, its opening quote, as a key or a value: checked to the
814
+ * closing quote and measured once decoded, decoding nothing. The length
815
+ * limit counts raw bytes and is checked at every byte, so a string past it
816
+ * is refused where it crosses the limit, not where it ends.
817
+ */
818
+ string(at: i32, k: i32): i32 {
819
+ const src: u8[] = this.src
820
+ const end: i32 = this.end
821
+ // Where the string would pass its limit, or `end` when it cannot pass it
822
+ // before the window ends, which also keeps the sum from overflowing.
823
+ const room: i32 = end - at - 1
824
+ const limit: i32 = this.maxStringBytes >= room ? end : at + 1 + this.maxStringBytes
825
+ let i: i32 = at + 1
826
+ let decoded: i32 = 0
827
+ while (true) {
828
+ // A run of plain bytes is stepped over in one loop. Each byte of it is
829
+ // one the checks below would pass with `decoded += 1; i += 1`, and the
830
+ // run stops short of `limit`, so the first byte it leaves is judged by
831
+ // those checks exactly as before.
832
+ const run: i32 = i
833
+ i = jsonTokSkipPlain(src, i, limit)
834
+ decoded += i - run
835
+ const b: i32 = jsonTokByteAt(src, i, end)
836
+ if (b === JSON_TOK_QUOTE) {
837
+ break
838
+ }
839
+ if (i >= limit) {
840
+ return this.fail(b < 0 ? JSON_TOK_ERR_TRUNCATED : JSON_TOK_ERR_STRING_LIMIT, i)
841
+ }
842
+ if (b < 0) {
843
+ return this.fail(JSON_TOK_ERR_TRUNCATED, i)
844
+ }
845
+ if (b < JSON_TOK_SPACE) {
846
+ return this.fail(JSON_TOK_ERR_CONTROL, i)
847
+ }
848
+ if (b === JSON_TOK_BACKSLASH) {
849
+ const cp: i32 = this.escapeCodePoint(i)
850
+ if (cp < 0) {
851
+ return cp
852
+ }
853
+ decoded += jsonTokUtf8Width(cp)
854
+ i += jsonTokEscapeLength(src, i, end, cp)
855
+ } else if (b < JSON_TOK_ASCII_END) {
856
+ decoded += 1
857
+ i += 1
858
+ } else {
859
+ const width: i32 = jsonTokUtf8Length(src, i, end)
860
+ if (width < 0) {
861
+ return this.fail(width, i)
862
+ }
863
+ decoded += width
864
+ i += width
865
+ }
866
+ }
867
+ if (i > limit) {
868
+ return this.fail(JSON_TOK_ERR_STRING_LIMIT, limit)
869
+ }
870
+ this.stringBytes = decoded
871
+ return this.emit(k, at, i + 1)
872
+ }
873
+
874
+ /**
875
+ * The code point of the escape whose backslash is at `at`, a surrogate pair
876
+ * joined into one, or a negative error, recorded. Used both to check a
877
+ * string and, on one already checked, to decode it.
878
+ */
879
+ escapeCodePoint(at: i32): i32 {
880
+ const src: u8[] = this.src
881
+ const end: i32 = this.end
882
+ const letter: i32 = jsonTokByteAt(src, at + 1, end)
883
+ if (letter < 0) {
884
+ return this.fail(JSON_TOK_ERR_TRUNCATED, at + 1)
885
+ }
886
+ if (letter !== JSON_TOK_LOWER_U) {
887
+ const short: i32 = jsonTokShortEscape(letter)
888
+ return short < 0 ? this.fail(JSON_TOK_ERR_ESCAPE, at + 1) : short
889
+ }
890
+ const unit: i32 = jsonTokHex4(src, at + 2, end)
891
+ if (unit < 0) {
892
+ return this.fail(unit, at + 2)
893
+ }
894
+ if (unit < JSON_TOK_HIGH_SURROGATE || unit >= JSON_TOK_SURROGATE_END) {
895
+ return unit
896
+ }
897
+ if (unit >= JSON_TOK_LOW_SURROGATE) {
898
+ return this.fail(JSON_TOK_ERR_SURROGATE, at)
899
+ }
900
+ const slash: i32 = jsonTokByteAt(src, at + 6, end)
901
+ const u: i32 = jsonTokByteAt(src, at + 7, end)
902
+ // A window that ends where the second half should start may only be cut short.
903
+ if (slash < 0 || (slash === JSON_TOK_BACKSLASH && u < 0)) {
904
+ return this.fail(JSON_TOK_ERR_TRUNCATED, slash < 0 ? at + 6 : at + 7)
905
+ }
906
+ if (slash !== JSON_TOK_BACKSLASH || u !== JSON_TOK_LOWER_U) {
907
+ return this.fail(JSON_TOK_ERR_SURROGATE, at)
908
+ }
909
+ const low: i32 = jsonTokHex4(src, at + 8, end)
910
+ if (low < 0) {
911
+ return this.fail(low, at + 8)
912
+ }
913
+ if (low < JSON_TOK_LOW_SURROGATE || low >= JSON_TOK_SURROGATE_END) {
914
+ return this.fail(JSON_TOK_ERR_SURROGATE, at)
915
+ }
916
+ return (
917
+ JSON_TOK_FOUR_BYTE_FLOOR + ((unit - JSON_TOK_HIGH_SURROGATE) << 10) + (low - JSON_TOK_LOW_SURROGATE)
918
+ )
919
+ }
920
+
921
+ /** Whether the current token is a key or a string. */
922
+ isText(): boolean {
923
+ return this.kind === JSON_TOK_KEY || this.kind === JSON_TOK_STRING
924
+ }
925
+
926
+ /**
927
+ * Writes the current key's or string's decoded bytes to `out` from `at`,
928
+ * and answers how many; `-1`, writing nothing, when the current token is
929
+ * neither or `out` has fewer than `stringLength()` bytes from `at`.
930
+ */
931
+ stringInto(out: u8[], at: i32): i32 {
932
+ const n: i32 = this.stringBytes
933
+ if (!this.isText() || at < 0 || at > toI32(out.length) - n) {
934
+ return -1
935
+ }
936
+ const src: u8[] = this.src
937
+ const end: i32 = this.tokEnd - 1
938
+ let i: i32 = this.tokStart + 1
939
+ let o: i32 = at
940
+ if (n === end - i) {
941
+ // No escape (see `keyEquals`): the bytes are copied as they are. Both
942
+ // cursors only climb, so their floors are tested once, before the loop.
943
+ if (i >= 0 && o >= 0) {
944
+ const srcLength: i32 = toI32(src.length)
945
+ const outLength: i32 = toI32(out.length)
946
+ while (i < end && i < srcLength && o < outLength) {
947
+ out[o] = src[i]
948
+ o += 1
949
+ i += 1
950
+ }
951
+ }
952
+ return o - at
953
+ }
954
+ while (i < end) {
955
+ const b: i32 = jsonTokByteAt(src, i, end)
956
+ if (b !== JSON_TOK_BACKSLASH) {
957
+ if (o >= 0 && o < toI32(out.length)) {
958
+ out[o] = toU8(b)
959
+ }
960
+ o += 1
961
+ i += 1
962
+ continue
963
+ }
964
+ const cp: i32 = this.escapeCodePoint(i)
965
+ const width: i32 = jsonTokUtf8Width(cp)
966
+ let k: i32 = 0
967
+ while (k < width) {
968
+ if (o >= 0 && o < toI32(out.length)) {
969
+ out[o] = toU8(jsonTokUtf8Byte(cp, width, k))
970
+ }
971
+ o += 1
972
+ k += 1
973
+ }
974
+ i += jsonTokEscapeLength(src, i, end, cp)
975
+ }
976
+ return o - at
977
+ }
978
+
979
+ /** The current key's or string's length once decoded, or `-1` when the token is neither. */
980
+ stringLength(): i32 {
981
+ return this.isText() ? this.stringBytes : -1
982
+ }
983
+
984
+ /**
985
+ * Whether the current key or string, decoded, is exactly `name`'s bytes;
986
+ * `false` when the token is neither. Nothing is allocated or written.
987
+ * `name.length` and `charCodeAt` are bytes natively. Under Node they are
988
+ * UTF-16 code units, compared one by one against the decoded UTF-8 bytes:
989
+ * an ASCII name answers the same both ways, a genuine non-ASCII name does
990
+ * not match there, and a name whose code units 0x80 to 0xFF spell those
991
+ * bytes does (`"é"` matches a key `"é"`). That is the bytes against code
992
+ * units divergence `docs/wp33-round-trip.md` §3.2 records.
993
+ */
994
+ keyEquals(name: string): boolean {
995
+ const n: i32 = toI32(name.length)
996
+ if (!this.isText() || n !== this.stringBytes) {
997
+ return false
998
+ }
999
+ const src: u8[] = this.src
1000
+ const end: i32 = this.tokEnd - 1
1001
+ let i: i32 = this.tokStart + 1
1002
+ let o: i32 = 0
1003
+ // Every escape decodes to fewer bytes than it is written with, so a string
1004
+ // whose decoded length is its raw length has none.
1005
+ if (n === this.tokEnd - this.tokStart - 2) {
1006
+ while (o < n) {
1007
+ if (jsonTokByteAt(src, i + o, end) !== toI32(name.charCodeAt(o))) {
1008
+ return false
1009
+ }
1010
+ o += 1
1011
+ }
1012
+ return true
1013
+ }
1014
+ while (i < end) {
1015
+ const b: i32 = jsonTokByteAt(src, i, end)
1016
+ if (b !== JSON_TOK_BACKSLASH) {
1017
+ if (o >= n || b !== toI32(name.charCodeAt(o))) {
1018
+ return false
1019
+ }
1020
+ o += 1
1021
+ i += 1
1022
+ continue
1023
+ }
1024
+ const cp: i32 = this.escapeCodePoint(i)
1025
+ const width: i32 = jsonTokUtf8Width(cp)
1026
+ let k: i32 = 0
1027
+ while (k < width) {
1028
+ if (o >= n || jsonTokUtf8Byte(cp, width, k) !== toI32(name.charCodeAt(o))) {
1029
+ return false
1030
+ }
1031
+ o += 1
1032
+ k += 1
1033
+ }
1034
+ i += jsonTokEscapeLength(src, i, end, cp)
1035
+ }
1036
+ return o === n
1037
+ }
1038
+
1039
+ /**
1040
+ * Whether the current token is a number written without a fraction or an
1041
+ * exponent whose value fits in an `i64`: `-0` and `9223372036854775807`
1042
+ * are, `1.0`, `1e2` and `9223372036854775808` are not.
1043
+ */
1044
+ isInteger(): boolean {
1045
+ return this.kind === JSON_TOK_NUMBER && this.integral && this.integerScan()
1046
+ }
1047
+
1048
+ /**
1049
+ * The current number as an `i64` when `isInteger()` holds, and `0`
1050
+ * otherwise. Read it after checking `isInteger()`: a `0` alone does not say
1051
+ * which.
1052
+ */
1053
+ numberI64(): i64 {
1054
+ if (!this.isInteger()) {
1055
+ return toI64(0)
1056
+ }
1057
+ const src: u8[] = this.src
1058
+ const negated: i64 = this.integerValue
1059
+ return jsonTokByteAt(src, this.tokStart, this.tokEnd) === JSON_TOK_MINUS ? negated : -negated
1060
+ }
1061
+
1062
+ /**
1063
+ * Reads the integral number's digits into `integerValue`, negated, and
1064
+ * answers whether its magnitude fits `i64` for its sign. Accumulating a
1065
+ * negative number reaches -2^63, where a positive one stops at 2^63 - 1, so
1066
+ * the bound for a positive number is one less.
1067
+ */
1068
+ integerScan(): boolean {
1069
+ const src: u8[] = this.src
1070
+ const end: i32 = this.tokEnd
1071
+ const negative: boolean = jsonTokByteAt(src, this.tokStart, end) === JSON_TOK_MINUS
1072
+ // Every `i64` here comes from `toI64`, which is a BigInt under Node, where a
1073
+ // bare literal would be a double and the two do not mix.
1074
+ // -2^63 has no literal (NL2055): -2^31 shifted up 32 bits.
1075
+ const min: i64 = toI64(-2147483648) << toI64(32)
1076
+ const ten: i64 = toI64(10)
1077
+ const bound: i64 = negative ? min : min + toI64(1)
1078
+ let acc: i64 = toI64(0)
1079
+ let i: i32 = negative ? this.tokStart + 1 : this.tokStart
1080
+ const n: i32 = toI32(src.length)
1081
+ if (i < 0 || end > n) {
1082
+ return false
1083
+ }
1084
+ while (i < end && i < n) {
1085
+ const digit: i64 = toI64(toI32(src[i]) - JSON_TOK_DIGIT_0)
1086
+ if (acc < (bound + digit) / ten) {
1087
+ return false
1088
+ }
1089
+ acc = acc * ten - digit
1090
+ i += 1
1091
+ }
1092
+ this.integerValue = acc
1093
+ return true
1094
+ }
1095
+
1096
+ /**
1097
+ * Whether the current token is a number written in digits alone, with no
1098
+ * sign, fraction or exponent, whose value fits in a `u64`: `0` and
1099
+ * `18446744073709551615` are; `-0`, `-1`, `1.0`, `1e2` and
1100
+ * `18446744073709551616` are not.
1101
+ */
1102
+ isU64(): boolean {
1103
+ return this.kind === JSON_TOK_NUMBER && this.integral && this.unsignedScan()
1104
+ }
1105
+
1106
+ /**
1107
+ * The current number as a `u64` when `isU64()` holds, and `0` otherwise.
1108
+ * Read it after checking `isU64()`: a `0` alone does not say which.
1109
+ */
1110
+ numberU64(): u64 {
1111
+ return this.isU64() ? this.unsignedValue : toU64(0)
1112
+ }
1113
+
1114
+ /**
1115
+ * Reads the integral number's digits into `unsignedValue` and answers
1116
+ * whether it is unsigned and fits a `u64`. RFC 8259 allows no leading zero,
1117
+ * so a number of up to 19 digits is below 10^19 and always fits, and one of
1118
+ * 21 or more never does. One of 20 fits exactly when it is a `1` and then
1119
+ * 19 digits no greater than 2^64 - 1's last 19, 8446744073709551615, so the
1120
+ * sum never wraps and needs no divide.
1121
+ */
1122
+ unsignedScan(): boolean {
1123
+ const src: u8[] = this.src
1124
+ const end: i32 = this.tokEnd
1125
+ const n: i32 = toI32(src.length)
1126
+ let i: i32 = this.tokStart
1127
+ const digits: i32 = end - i
1128
+ if (i < 0 || end > n || digits < 1 || digits > 20 || i >= n) {
1129
+ return false
1130
+ }
1131
+ if (toI32(src[i]) === JSON_TOK_MINUS) {
1132
+ return false
1133
+ }
1134
+ const wide: boolean = digits === 20
1135
+ if (wide) {
1136
+ if (toI32(src[i]) !== JSON_TOK_DIGIT_1) {
1137
+ return false
1138
+ }
1139
+ i += 1
1140
+ }
1141
+ // Every `u64` here comes from `toU64`, a BigInt under Node, as `integerScan`'s `i64`s do.
1142
+ const ten: u64 = toU64(10)
1143
+ let acc: u64 = toU64(0)
1144
+ while (i < end && i < n) {
1145
+ acc = acc * ten + toU64(toI32(src[i]) - JSON_TOK_DIGIT_0)
1146
+ i += 1
1147
+ }
1148
+ if (wide) {
1149
+ // 8446744073709551615 and 10^19, built from parts that fit an `i32`.
1150
+ const million: u64 = toU64(1000000)
1151
+ const tail: u64 = (toU64(8446744) * million + toU64(73709)) * million + toU64(551615)
1152
+ if (acc > tail) {
1153
+ return false
1154
+ }
1155
+ const billion: u64 = toU64(1000000000)
1156
+ acc = acc + billion * billion * ten
1157
+ }
1158
+ this.unsignedValue = acc
1159
+ return true
1160
+ }
1161
+
1162
+ /**
1163
+ * The current number as the nearest `f64`, ties to even, as `JSON.parse`
1164
+ * reads it: `-0` is negative zero, a magnitude past the largest double is
1165
+ * an infinity and one below the smallest is zero. `0` when the current
1166
+ * token is not a number.
1167
+ */
1168
+ numberF64(): f64 {
1169
+ if (this.kind !== JSON_TOK_NUMBER) {
1170
+ return 0
1171
+ }
1172
+ const src: u8[] = this.src
1173
+ const start: i32 = this.tokStart
1174
+ const end: i32 = this.tokEnd
1175
+ const negative: boolean = jsonTokByteAt(src, start, end) === JSON_TOK_MINUS
1176
+ // At most 15 digits are accumulated, so the mantissa is an exact double.
1177
+ const ten: f64 = 10
1178
+ let mantissa: f64 = 0
1179
+ let digits: i32 = 0
1180
+ let scale: i32 = 0
1181
+ let afterDot = false
1182
+ let i: i32 = negative ? start + 1 : start
1183
+ if (i < 0 || end > toI32(src.length)) {
1184
+ return this.numberSlow()
1185
+ }
1186
+ // The length is read in each test rather than hoisted: the call to
1187
+ // `numberSlow` in the loop would otherwise end what a hoisted copy proves.
1188
+ while (i < end && i < toI32(src.length)) {
1189
+ const b: i32 = toI32(src[i])
1190
+ if (b === JSON_TOK_DOT) {
1191
+ afterDot = true
1192
+ } else if (jsonTokIsDigit(b)) {
1193
+ if (digits > 0 || b !== JSON_TOK_DIGIT_0) {
1194
+ digits += 1
1195
+ if (digits <= JSON_TOK_FAST_DIGITS) {
1196
+ mantissa = mantissa * ten + toF64(b - JSON_TOK_DIGIT_0)
1197
+ }
1198
+ }
1199
+ if (afterDot) {
1200
+ scale -= 1
1201
+ }
1202
+ if (digits > JSON_TOK_FAST_DIGITS) {
1203
+ return this.numberSlow()
1204
+ }
1205
+ } else {
1206
+ break
1207
+ }
1208
+ i += 1
1209
+ }
1210
+ if (i < end) {
1211
+ // The exponent: `i` is at the `e` or `E`.
1212
+ i += 1
1213
+ const sign: i32 = jsonTokByteAt(src, i, end)
1214
+ const minus: boolean = sign === JSON_TOK_MINUS
1215
+ if (minus || sign === JSON_TOK_PLUS) {
1216
+ i += 1
1217
+ }
1218
+ let exponent: i32 = 0
1219
+ while (i < end && i < toI32(src.length) && exponent < JSON_TOK_EXPONENT_CAP) {
1220
+ exponent = exponent * JSON_TOK_TEN + toI32(src[i]) - JSON_TOK_DIGIT_0
1221
+ i += 1
1222
+ }
1223
+ if (i < end) {
1224
+ return this.numberSlow()
1225
+ }
1226
+ scale += minus ? -exponent : exponent
1227
+ }
1228
+ let value: f64 = mantissa
1229
+ if (mantissa !== 0) {
1230
+ if (scale < -JSON_TOK_FAST_EXPONENT || scale > JSON_TOK_FAST_EXPONENT) {
1231
+ return this.numberSlow()
1232
+ }
1233
+ const power: f64 = jsonTokPowerOfTen(scale < 0 ? -scale : scale)
1234
+ value = scale < 0 ? value / power : value * power
1235
+ }
1236
+ return negative ? -value : value
1237
+ }
1238
+
1239
+ /**
1240
+ * The current number through `parseFloat`, which is `strtod` and correctly
1241
+ * rounded, for the numbers Clinger's fast path cannot take. The text is
1242
+ * copied into a string first, in one allocation by `String.fromBytes`. The
1243
+ * function answers a number, so its own arena scope releases the copy when
1244
+ * it returns (docs/LANGUAGE.md, "Memory model", item 2), as
1245
+ * `JsonWriter.f64`'s digits are released. A `using a = arena()` block would
1246
+ * say the same thing, but Node 22 cannot parse `using`, and this module also
1247
+ * runs under Node. A token outside `src` can only mean the caller shortened
1248
+ * the array after `next()` read it, and answers `0` rather than panic.
1249
+ */
1250
+ numberSlow(): f64 {
1251
+ const src: u8[] = this.src
1252
+ const start: i32 = this.tokStart
1253
+ const end: i32 = this.tokEnd
1254
+ if (start < 0 || start > end || end > toI32(src.length)) {
1255
+ return 0
1256
+ }
1257
+ return parseFloat(String.fromBytes(src, start, end))
1258
+ }
1259
+
1260
+ /**
1261
+ * Steps over the value the current token starts, so that the next `next()`
1262
+ * answers what follows it. After a key, that is the key's value; after an
1263
+ * opener, everything up to and including its closer; after a scalar or a
1264
+ * closer, nothing. Answers the kind of the last token it read (the closer,
1265
+ * or the scalar), or a negative error.
1266
+ */
1267
+ skipValue(): i32 {
1268
+ if (this.failed !== 0) {
1269
+ return this.failed
1270
+ }
1271
+ let k: i32 = this.kind
1272
+ if (k === JSON_TOK_KEY) {
1273
+ k = this.next()
1274
+ }
1275
+ if (k !== JSON_TOK_OBJECT_BEGIN && k !== JSON_TOK_ARRAY_BEGIN) {
1276
+ return k
1277
+ }
1278
+ const target: i32 = this.depth - 1
1279
+ while (this.depth > target && k >= 0) {
1280
+ k = this.next()
1281
+ }
1282
+ return k
1283
+ }
1284
+ }
1285
+
1286
+ /** 10^n as an exact double, for `n` from 0 to 22. */
1287
+ const jsonTokPowerOfTen = (n: i32): f64 => {
1288
+ const ten: f64 = 10
1289
+ let p: f64 = 1
1290
+ let k: i32 = 0
1291
+ while (k < n) {
1292
+ p = p * ten
1293
+ k += 1
1294
+ }
1295
+ return p
1296
+ }