@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.
- package/INSTALL.md +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +231 -20
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +16 -3
- package/runtime/runtime.c +89 -23
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +225 -20
- package/std/README.md +86 -55
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +1376 -42
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1412 -70
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +231 -45
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-conn-params.ts +86 -16
- package/std/net/quic-listener.ts +576 -219
- package/std/net/quic-packet.ts +443 -58
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +434 -28
- package/std/net/quic.ts +922 -206
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +204 -57
- package/std/net/tls.ts +1132 -305
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/text.ts +77 -0
- 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
|
+
}
|