@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
package/std/json.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `std/json` — the value of
|
|
2
|
+
* `std/json` — the value of a field, or of several, of one flat JSON object;
|
|
3
|
+
* and `JsonWriter`, which writes any JSON value into a byte buffer.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
* `--json` every diagnostic is one flat object on a line of
|
|
6
|
-
* is a stable rule identifier (`AGENTS.md`, and `docs/wp12-release.md` for the
|
|
5
|
+
* The reader exists because the compiler's own machine-readable surface is
|
|
6
|
+
* JSON: under `--json` every diagnostic is one flat object on a line of
|
|
7
|
+
* stdout, whose `code` is a stable rule identifier (`AGENTS.md`, and `docs/wp12-release.md` for the
|
|
7
8
|
* exit-code bands). A Nish program that reads that surface — a wrapper, an
|
|
8
9
|
* editor plug-in, or `tests/nish/cli.ts`, which pins the contract — needs the
|
|
9
10
|
* value of a named field and nothing else, and the language has no `JSON.parse`
|
|
10
11
|
* to give it.
|
|
11
12
|
*
|
|
12
|
-
* So
|
|
13
|
+
* So the reading half is a **reader, not a parser**, and the difference is
|
|
14
|
+
* worth stating:
|
|
13
15
|
*
|
|
14
16
|
* - It answers **text**. A string field comes back with its escapes decoded
|
|
15
17
|
* and without its quotes; a number, a `true` or a `null` comes back as the
|
|
@@ -26,10 +28,10 @@
|
|
|
26
28
|
* whether the whole line was well-formed is asking a question this does not
|
|
27
29
|
* answer.
|
|
28
30
|
*
|
|
29
|
-
*
|
|
30
|
-
* the program that imports it and the symbol namespace is
|
|
31
|
-
* (`std/README.md`), so every name here is a name its importer cannot
|
|
32
|
-
*
|
|
31
|
+
* The reader is two exported functions, and few on purpose. A `std/` module
|
|
32
|
+
* is compiled into the program that imports it and the symbol namespace is
|
|
33
|
+
* flat (`std/README.md`), so every name here is a name its importer cannot
|
|
34
|
+
* use: every helper is `json`-prefixed or a method of `JsonWriter`. Splitting a
|
|
33
35
|
* stream into lines and picking the ones that start with `{` is two calls the
|
|
34
36
|
* caller already has — `splitLines` from `std/text` and `startsWith` — and
|
|
35
37
|
* duplicating them here would cost more names than it saves.
|
|
@@ -46,6 +48,60 @@
|
|
|
46
48
|
* }
|
|
47
49
|
* }
|
|
48
50
|
*
|
|
51
|
+
* `jsonField` scans from the start of the line on every call, so a caller that
|
|
52
|
+
* wants three fields of each line reads each line three times. `jsonFields`
|
|
53
|
+
* reads it once: `jsonFields(line, ["code", "line", "message"])` answers an
|
|
54
|
+
* array whose slot `k` is exactly what `jsonField(line, names[k])` answers —
|
|
55
|
+
* absent, first-of-a-duplicate and malformed input included — because the two
|
|
56
|
+
* share the step that reads one member (`jsonReadMember`) and stop at the same
|
|
57
|
+
* one. Its array goes back to the caller with the values in it, so a loop that
|
|
58
|
+
* calls it keeps its per-pass arena release, as one calling `jsonField` does;
|
|
59
|
+
* `jsonFields`' own comment says when that holds.
|
|
60
|
+
*
|
|
61
|
+
* **The writer** is for the other direction: a server's response body
|
|
62
|
+
* (WP34 N11, whose first caller is a TechEmpower `/json` entry writing
|
|
63
|
+
* `{"message":"Hello, World!"}` on every request). `JsonWriter` writes into a
|
|
64
|
+
* `u8[]` the caller owns and reuses, puts in every comma and colon itself,
|
|
65
|
+
* and allocates nothing per call except an `f64`'s digits, which it gives back
|
|
66
|
+
* before the call returns. What it writes is byte for byte what
|
|
67
|
+
* `JSON.stringify` writes for the same value: the seven short escapes and
|
|
68
|
+
* `\u00xx` below `0x20`, `/` and `0x7f` as themselves, integers as their
|
|
69
|
+
* digits, an `f64` as `String(v)` spells it with `-0` as `0`, and NaN and the
|
|
70
|
+
* infinities as `null`. `jsonStringifyString` is the same string rule
|
|
71
|
+
* answered as a string.
|
|
72
|
+
*
|
|
73
|
+
* **Two modes**, both off by default and both kept across `reset`, so a
|
|
74
|
+
* server sets them once. `setIndent(n)` and `setIndentTab()` write what
|
|
75
|
+
* `JSON.stringify(v, null, n)` and `JSON.stringify(v, null, "\t")` write:
|
|
76
|
+
* one member or element a line, `": "` after a key, an empty `{}` or `[]` on
|
|
77
|
+
* one line, and `n` clamped to 10. The indent is one byte stored as many times
|
|
78
|
+
* as the depth asks, so the indented text allocates no more than the minified
|
|
79
|
+
* one, and a `raw` fragment is copied as it is, not re-indented.
|
|
80
|
+
* `setStrict(true)` makes NaN and the infinities, which JSON cannot
|
|
81
|
+
* spell, the sticky `JSON_WRITE_NON_FINITE` instead of `null`: for a caller
|
|
82
|
+
* to whom a `null` where a number was due would be a silently corrupt record.
|
|
83
|
+
*
|
|
84
|
+
* **Misuse is a sticky code**, as in `nish/net/hpack`: never a panic, and no
|
|
85
|
+
* `Result` to carry on the hot path. The first call that cannot be honoured
|
|
86
|
+
* sets `failed` to a negative `JSON_WRITE_*` code — a value where a key is
|
|
87
|
+
* due, a key where a value is due, an unbalanced or mismatched `end*`,
|
|
88
|
+
* nesting past `JSON_WRITER_MAX_DEPTH`, a buffer with no room, a second
|
|
89
|
+
* top-level value, a bad `raw` window or `start`, a non-finite `f64` in strict
|
|
90
|
+
* mode — and every later call does nothing. A call that fails writes nothing
|
|
91
|
+
* past `buf.length` (a full buffer is checked before the bytes go in, never
|
|
92
|
+
* by the bounds check), so the caller writes the whole value, reads `failed`
|
|
93
|
+
* once at the end, and grows the buffer and retries or answers a 500. `complete()` says whether one
|
|
94
|
+
* whole value was written with every container closed.
|
|
95
|
+
*
|
|
96
|
+
* **A string is its bytes** (WP33 class C, `docs/wp33-round-trip.md` §3.2,
|
|
97
|
+
* the first row). The writer escapes by byte and copies every byte at or
|
|
98
|
+
* above `0x80`, so UTF-8 text comes out as the UTF-8 that `JSON.stringify`'s
|
|
99
|
+
* answer encodes to, and a string that is not UTF-8 — a lone `0xFF` — comes
|
|
100
|
+
* out raw, where no Node string could hold it in the first place. Under Node
|
|
101
|
+
* the same source reads UTF-16 units, which agree on ASCII and only there;
|
|
102
|
+
* `tests/link/std_json_writer` pins both edges natively, and its `node.ts`
|
|
103
|
+
* is the ASCII half that `tests/run.js` holds to `JSON.stringify` under Node.
|
|
104
|
+
*
|
|
49
105
|
* Every offset here is a **byte** offset and every width is spelled, with each
|
|
50
106
|
* length and byte a builtin answers read through `toI32` — `.length` and
|
|
51
107
|
* `charCodeAt` answer `number`, which is `f64` under `--number-mode f64`, so the
|
|
@@ -53,14 +109,17 @@
|
|
|
53
109
|
* (`docs/wp26-stdlib.md` §4).
|
|
54
110
|
*/
|
|
55
111
|
|
|
112
|
+
const JSON_BACKSPACE: i32 = 8
|
|
56
113
|
const JSON_TAB: i32 = 9
|
|
57
114
|
const JSON_NEWLINE: i32 = 10
|
|
115
|
+
const JSON_FORM_FEED: i32 = 12
|
|
58
116
|
const JSON_CARRIAGE_RETURN: i32 = 13
|
|
59
117
|
const JSON_SPACE: i32 = 32
|
|
60
118
|
const JSON_QUOTE: i32 = 34
|
|
61
119
|
const JSON_COMMA: i32 = 44
|
|
62
120
|
const JSON_COLON: i32 = 58
|
|
63
121
|
const JSON_BACKSLASH: i32 = 92
|
|
122
|
+
const JSON_LETTER_U: i32 = 117
|
|
64
123
|
const JSON_OPEN_BRACKET: i32 = 91
|
|
65
124
|
const JSON_CLOSE_BRACKET: i32 = 93
|
|
66
125
|
const JSON_OPEN_BRACE: i32 = 123
|
|
@@ -110,19 +169,74 @@ const jsonSkipBlank = (text: string, from: i32): i32 => {
|
|
|
110
169
|
return i
|
|
111
170
|
}
|
|
112
171
|
|
|
172
|
+
/**
|
|
173
|
+
* How many bytes of a string body the scanners read one at a time before they
|
|
174
|
+
* hand the rest to `jsonClosingQuote`. A key or a short value ends inside
|
|
175
|
+
* them, and for those a call into the runtime costs more than the bytes do.
|
|
176
|
+
*/
|
|
177
|
+
const JSON_INLINE_SCAN: i32 = 16
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The index of the closing quote of a string body that has run on past the
|
|
181
|
+
* bytes the scanners read one at a time, searching from `start`, a byte no
|
|
182
|
+
* backslash has taken; `-1` when no quote closes it.
|
|
183
|
+
*
|
|
184
|
+
* The body is not walked: `indexOf` jumps to the next quote, which is one
|
|
185
|
+
* `memchr` in the runtime, and the run of backslashes just before that quote
|
|
186
|
+
* says whether it is escaped. The byte before the run is not a backslash — at
|
|
187
|
+
* the latest it is the opening quote — so the run starts where an escape can:
|
|
188
|
+
* an odd run ends in a backslash that takes this quote, and an even one is
|
|
189
|
+
* pairs of `\\` that leave it alone. The run may reach back past `start`, and
|
|
190
|
+
* that changes nothing: what lies before `start` ends on a byte no escape took,
|
|
191
|
+
* so any backslashes there are whole pairs. An escaped quote moves the search
|
|
192
|
+
* one byte past it, and the next run's count stops at that quote, so every
|
|
193
|
+
* backslash is counted once.
|
|
194
|
+
*
|
|
195
|
+
* The byte loops stay in the scanners rather than in here: most strings end
|
|
196
|
+
* in them and never make this call.
|
|
197
|
+
*/
|
|
198
|
+
const jsonClosingQuote = (text: string, start: i32): i32 => {
|
|
199
|
+
const length: i32 = toI32(text.length)
|
|
200
|
+
if (start < 0) {
|
|
201
|
+
return -1
|
|
202
|
+
}
|
|
203
|
+
let quote: i32 = toI32(text.indexOf('"', start))
|
|
204
|
+
// `quote >= start` is false for the `-1` of a miss.
|
|
205
|
+
while (quote >= start && quote < length) {
|
|
206
|
+
// The count stops at the opening quote at the latest, so `j >= 0` always
|
|
207
|
+
// holds, and `j < length` does because `j` starts below `quote`; both are
|
|
208
|
+
// spelled out because the bounds proof reads neither from a decrement of a
|
|
209
|
+
// callee's answer.
|
|
210
|
+
let j: i32 = quote - 1
|
|
211
|
+
while (j >= 0 && j < length && toI32(text.charCodeAt(j)) === JSON_BACKSLASH) {
|
|
212
|
+
j -= 1
|
|
213
|
+
}
|
|
214
|
+
if (((quote - 1 - j) & 1) === 0) {
|
|
215
|
+
return quote
|
|
216
|
+
}
|
|
217
|
+
quote = toI32(text.indexOf('"', quote + 1))
|
|
218
|
+
}
|
|
219
|
+
return -1
|
|
220
|
+
}
|
|
221
|
+
|
|
113
222
|
/**
|
|
114
223
|
* The index just past the string literal whose opening quote is at `at`, or `-1`
|
|
115
224
|
* when the quote is never closed. A backslash takes the byte after it with it,
|
|
116
225
|
* which is all a scanner needs to know about escapes: `\"` cannot end the
|
|
117
|
-
* literal and `\\` cannot make the next quote an escape.
|
|
226
|
+
* literal and `\\` cannot make the next quote an escape. The first
|
|
227
|
+
* `JSON_INLINE_SCAN` bytes are read here and the rest searched by
|
|
228
|
+
* `jsonClosingQuote`; the step over an escape can take the cursor one past
|
|
229
|
+
* them, which is still a byte no backslash has taken.
|
|
118
230
|
*/
|
|
119
231
|
const jsonEndOfString = (text: string, at: i32): i32 => {
|
|
120
232
|
const length: i32 = toI32(text.length)
|
|
121
233
|
if (at < 0) {
|
|
122
234
|
return -1
|
|
123
235
|
}
|
|
236
|
+
const stop: i32 = at + 1 + JSON_INLINE_SCAN < length ? at + 1 + JSON_INLINE_SCAN : length
|
|
124
237
|
let i: i32 = at + 1
|
|
125
|
-
|
|
238
|
+
// `i < length` follows from `i < stop`, and is there for the bounds proof.
|
|
239
|
+
while (i < stop && i < length) {
|
|
126
240
|
const code: i32 = toI32(text.charCodeAt(i))
|
|
127
241
|
if (code === JSON_BACKSLASH) {
|
|
128
242
|
i += 2
|
|
@@ -133,7 +247,11 @@ const jsonEndOfString = (text: string, at: i32): i32 => {
|
|
|
133
247
|
}
|
|
134
248
|
i += 1
|
|
135
249
|
}
|
|
136
|
-
|
|
250
|
+
if (i >= length) {
|
|
251
|
+
return -1
|
|
252
|
+
}
|
|
253
|
+
const quote: i32 = jsonClosingQuote(text, i)
|
|
254
|
+
return quote < 0 ? -1 : quote + 1
|
|
137
255
|
}
|
|
138
256
|
|
|
139
257
|
/**
|
|
@@ -146,10 +264,13 @@ const jsonEndOfString = (text: string, at: i32): i32 => {
|
|
|
146
264
|
* ends at the first byte that cannot be part of it, which is the comma, the
|
|
147
265
|
* closer or the blank that follows.
|
|
148
266
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* and
|
|
267
|
+
* A string value goes through the same loop as a string inside an object, and
|
|
268
|
+
* ends where its closing quote leaves the loop at depth zero. Each string is
|
|
269
|
+
* read as `jsonEndOfString` reads one, its first `JSON_INLINE_SCAN` bytes a
|
|
270
|
+
* byte at a time and the rest searched by `jsonClosingQuote`, and the byte loop
|
|
271
|
+
* is written out here rather than called: a call to `jsonEndOfString` from
|
|
272
|
+
* a second site is one LLVM stops inlining into `jsonReadMember`, and a call
|
|
273
|
+
* per string is what a line of short strings feels most.
|
|
153
274
|
*/
|
|
154
275
|
const jsonEndOfValue = (text: string, at: i32): i32 => {
|
|
155
276
|
const length: i32 = toI32(text.length)
|
|
@@ -157,30 +278,47 @@ const jsonEndOfValue = (text: string, at: i32): i32 => {
|
|
|
157
278
|
return -1
|
|
158
279
|
}
|
|
159
280
|
const first: i32 = toI32(text.charCodeAt(at))
|
|
160
|
-
if (first === JSON_QUOTE) {
|
|
161
|
-
return jsonEndOfString(text, at)
|
|
162
|
-
}
|
|
163
|
-
if (first === JSON_OPEN_BRACE || first === JSON_OPEN_BRACKET) {
|
|
281
|
+
if (first === JSON_QUOTE || first === JSON_OPEN_BRACE || first === JSON_OPEN_BRACKET) {
|
|
164
282
|
let depth: i32 = 0
|
|
165
|
-
let inString: boolean = false
|
|
166
283
|
let i: i32 = at
|
|
167
|
-
|
|
284
|
+
// `i >= 0` always holds — `i` starts at `at` and only moves forward — and
|
|
285
|
+
// is in the test because a long string moves `i` to where
|
|
286
|
+
// `jsonClosingQuote` answers, and a cursor assigned a callee's answer is
|
|
287
|
+
// one the bounds proof stops following.
|
|
288
|
+
while (i >= 0 && i < length) {
|
|
168
289
|
const code: i32 = toI32(text.charCodeAt(i))
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
290
|
+
if (code === JSON_QUOTE) {
|
|
291
|
+
const stop: i32 = i + 1 + JSON_INLINE_SCAN < length ? i + 1 + JSON_INLINE_SCAN : length
|
|
292
|
+
let j: i32 = i + 1
|
|
293
|
+
let close: i32 = -1
|
|
294
|
+
while (j < stop && j < length) {
|
|
295
|
+
const inner: i32 = toI32(text.charCodeAt(j))
|
|
296
|
+
if (inner === JSON_BACKSLASH) {
|
|
297
|
+
j += 2
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
if (inner === JSON_QUOTE) {
|
|
301
|
+
close = j
|
|
302
|
+
break
|
|
303
|
+
}
|
|
304
|
+
j += 1
|
|
174
305
|
}
|
|
175
|
-
if (
|
|
176
|
-
|
|
306
|
+
if (close < 0) {
|
|
307
|
+
if (j >= length) {
|
|
308
|
+
return -1
|
|
309
|
+
}
|
|
310
|
+
close = jsonClosingQuote(text, j)
|
|
311
|
+
if (close < 0) {
|
|
312
|
+
return -1
|
|
313
|
+
}
|
|
177
314
|
}
|
|
178
|
-
|
|
315
|
+
if (depth === 0) {
|
|
316
|
+
return close + 1
|
|
317
|
+
}
|
|
318
|
+
i = close + 1
|
|
179
319
|
continue
|
|
180
320
|
}
|
|
181
|
-
if (code ===
|
|
182
|
-
inString = true
|
|
183
|
-
} else if (code === JSON_OPEN_BRACE || code === JSON_OPEN_BRACKET) {
|
|
321
|
+
if (code === JSON_OPEN_BRACE || code === JSON_OPEN_BRACKET) {
|
|
184
322
|
depth += 1
|
|
185
323
|
} else if (code === JSON_CLOSE_BRACE || code === JSON_CLOSE_BRACKET) {
|
|
186
324
|
depth -= 1
|
|
@@ -261,6 +399,37 @@ const jsonUtf8 = (code: i32): string => {
|
|
|
261
399
|
return `${lead}${middle}${String.fromCharCode(JSON_UTF8_CONTINUATION + (code & JSON_UTF8_LOW_SIX))}`
|
|
262
400
|
}
|
|
263
401
|
|
|
402
|
+
/** The number of bytes `jsonUtf8(code)` answers. */
|
|
403
|
+
const jsonUtf8Width = (code: i32): i32 => {
|
|
404
|
+
if (code < JSON_UTF8_TWO_BYTE_FLOOR) {
|
|
405
|
+
return 1
|
|
406
|
+
}
|
|
407
|
+
if (code < JSON_UTF8_THREE_BYTE_FLOOR) {
|
|
408
|
+
return 2
|
|
409
|
+
}
|
|
410
|
+
return 3
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Byte `k` of the `width` bytes `jsonUtf8(code)` answers: its arithmetic, one
|
|
415
|
+
* byte at a time, for a caller that compares the bytes rather than keeps them.
|
|
416
|
+
*/
|
|
417
|
+
const jsonUtf8Byte = (code: i32, width: i32, k: i32): i32 => {
|
|
418
|
+
if (width === 1) {
|
|
419
|
+
return code
|
|
420
|
+
}
|
|
421
|
+
if (k === 0) {
|
|
422
|
+
if (width === 2) {
|
|
423
|
+
return JSON_UTF8_TWO_BYTE_LEAD + (code >> 6)
|
|
424
|
+
}
|
|
425
|
+
return JSON_UTF8_THREE_BYTE_LEAD + (code >> 12)
|
|
426
|
+
}
|
|
427
|
+
if (k === width - 1) {
|
|
428
|
+
return JSON_UTF8_CONTINUATION + (code & JSON_UTF8_LOW_SIX)
|
|
429
|
+
}
|
|
430
|
+
return JSON_UTF8_CONTINUATION + ((code >> 6) & JSON_UTF8_LOW_SIX)
|
|
431
|
+
}
|
|
432
|
+
|
|
264
433
|
/**
|
|
265
434
|
* The bytes of `text[at..end)` with the JSON escapes decoded — the body of a
|
|
266
435
|
* string literal, without its quotes.
|
|
@@ -273,7 +442,7 @@ const jsonUtf8 = (code: i32): string => {
|
|
|
273
442
|
* reading of anything else.
|
|
274
443
|
*/
|
|
275
444
|
const jsonUnescape = (text: string, at: i32, end: i32): string => {
|
|
276
|
-
// Both callers pass the inside of a literal
|
|
445
|
+
// Both callers pass the inside of a literal the scanners found, so
|
|
277
446
|
// `0 <= at` and `end <= text.length` always hold. Saying so here is what
|
|
278
447
|
// proves every read below in range and drops the `substring` clamps.
|
|
279
448
|
if (at < 0 || end > toI32(text.length)) {
|
|
@@ -343,6 +512,190 @@ const jsonUnescape = (text: string, at: i32, end: i32): string => {
|
|
|
343
512
|
return parts.join("")
|
|
344
513
|
}
|
|
345
514
|
|
|
515
|
+
/** Whether byte `j` of `name` is `code`; an index past either end is not. */
|
|
516
|
+
const jsonNameHas = (name: string, j: i32, code: i32): boolean =>
|
|
517
|
+
j >= 0 && j < toI32(name.length) && toI32(name.charCodeAt(j)) === code
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Whether `jsonUnescape(text, at, end)` would answer `name`, decided without
|
|
521
|
+
* building it.
|
|
522
|
+
*
|
|
523
|
+
* `jsonField` and `jsonFields` ask this of every key they walk past, and almost
|
|
524
|
+
* every answer is no, so the question is answered the way it is asked: the literal and `name`
|
|
525
|
+
* are walked together, each escape decoded where it stands, and the first byte
|
|
526
|
+
* that differs ends the walk. Every escape reads as `jsonUnescape` reads it —
|
|
527
|
+
* the short forms, a `\u` as the bytes `jsonUtf8` would build, a broken `\u`
|
|
528
|
+
* as the two bytes `\u`, a backslash with no letter after it as itself, and
|
|
529
|
+
* any other escape as the byte it escapes — so a key matches here exactly when
|
|
530
|
+
* its decoded text would.
|
|
531
|
+
*/
|
|
532
|
+
const jsonKeyEquals = (text: string, at: i32, end: i32, name: string): boolean => {
|
|
533
|
+
// `jsonUnescape`'s guard, for the same reason: every caller passes the
|
|
534
|
+
// inside of a key `jsonReadMember` found with `jsonEndOfString`.
|
|
535
|
+
if (at < 0 || end > toI32(text.length)) {
|
|
536
|
+
return false
|
|
537
|
+
}
|
|
538
|
+
let i: i32 = at
|
|
539
|
+
let j: i32 = 0
|
|
540
|
+
while (i < end) {
|
|
541
|
+
const code: i32 = toI32(text.charCodeAt(i))
|
|
542
|
+
if (code !== JSON_BACKSLASH) {
|
|
543
|
+
if (!jsonNameHas(name, j, code)) {
|
|
544
|
+
return false
|
|
545
|
+
}
|
|
546
|
+
i += 1
|
|
547
|
+
j += 1
|
|
548
|
+
continue
|
|
549
|
+
}
|
|
550
|
+
const letter: i32 = i + 1 < end ? toI32(text.charCodeAt(i + 1)) : 0
|
|
551
|
+
if (letter === JSON_LETTER_U) {
|
|
552
|
+
const point: i32 = jsonHex4(text, i + 2, end)
|
|
553
|
+
if (point < 0) {
|
|
554
|
+
if (!jsonNameHas(name, j, JSON_BACKSLASH) || !jsonNameHas(name, j + 1, JSON_LETTER_U)) {
|
|
555
|
+
return false
|
|
556
|
+
}
|
|
557
|
+
i += 2
|
|
558
|
+
j += 2
|
|
559
|
+
continue
|
|
560
|
+
}
|
|
561
|
+
const width: i32 = jsonUtf8Width(point)
|
|
562
|
+
let k: i32 = 0
|
|
563
|
+
while (k < width) {
|
|
564
|
+
if (!jsonNameHas(name, j + k, jsonUtf8Byte(point, width, k))) {
|
|
565
|
+
return false
|
|
566
|
+
}
|
|
567
|
+
k += 1
|
|
568
|
+
}
|
|
569
|
+
i += 6
|
|
570
|
+
j += width
|
|
571
|
+
continue
|
|
572
|
+
}
|
|
573
|
+
// Every other escape decodes to one byte.
|
|
574
|
+
let decoded: i32 = letter
|
|
575
|
+
if (letter === 110) {
|
|
576
|
+
decoded = JSON_NEWLINE
|
|
577
|
+
} else if (letter === 116) {
|
|
578
|
+
decoded = JSON_TAB
|
|
579
|
+
} else if (letter === 114) {
|
|
580
|
+
decoded = JSON_CARRIAGE_RETURN
|
|
581
|
+
} else if (letter === 98) {
|
|
582
|
+
decoded = JSON_BACKSPACE
|
|
583
|
+
} else if (letter === 102) {
|
|
584
|
+
decoded = JSON_FORM_FEED
|
|
585
|
+
} else if (letter === 0) {
|
|
586
|
+
decoded = JSON_BACKSLASH
|
|
587
|
+
}
|
|
588
|
+
if (!jsonNameHas(name, j, decoded)) {
|
|
589
|
+
return false
|
|
590
|
+
}
|
|
591
|
+
// A backslash with no letter after it is one byte of the literal and every
|
|
592
|
+
// other escape two. The steps are constants rather than a variable because
|
|
593
|
+
// a cursor moved by a variable is one the bounds proof stops following.
|
|
594
|
+
if (letter === 0) {
|
|
595
|
+
i += 1
|
|
596
|
+
} else {
|
|
597
|
+
i += 2
|
|
598
|
+
}
|
|
599
|
+
j += 1
|
|
600
|
+
}
|
|
601
|
+
return j === toI32(name.length)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Where one member of an object lies, as `jsonReadMember` found it: the inside
|
|
606
|
+
* of its key literal, its value, and the opening quote of the key after it.
|
|
607
|
+
*
|
|
608
|
+
* It is a class so that one step can answer the four offsets together, and
|
|
609
|
+
* each reader allocates exactly one, which never leaves the reader: that is
|
|
610
|
+
* what keeps it a stack slot rather than an arena allocation.
|
|
611
|
+
*/
|
|
612
|
+
class JsonMember {
|
|
613
|
+
keyAt: i32 = 0
|
|
614
|
+
keyEnd: i32 = 0
|
|
615
|
+
valueAt: i32 = 0
|
|
616
|
+
valueEnd: i32 = 0
|
|
617
|
+
/** The opening quote of the next key, or `-1` when no comma follows the value. */
|
|
618
|
+
next: i32 = -1
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* The opening quote of the first key of `object`, or `-1` when `object` does
|
|
623
|
+
* not start, past any blanks, with a `{`.
|
|
624
|
+
*/
|
|
625
|
+
const jsonFirstKey = (object: string): i32 => {
|
|
626
|
+
const length: i32 = toI32(object.length)
|
|
627
|
+
const i: i32 = jsonSkipBlank(object, 0)
|
|
628
|
+
if (i >= length || toI32(object.charCodeAt(i)) !== JSON_OPEN_BRACE) {
|
|
629
|
+
return -1
|
|
630
|
+
}
|
|
631
|
+
return jsonSkipBlank(object, i + 1)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Reads the member whose key's opening quote is at `at` into `member`, and
|
|
636
|
+
* answers whether there was one.
|
|
637
|
+
*
|
|
638
|
+
* This is the per-key step `jsonField` and `jsonFields` share, so they cannot
|
|
639
|
+
* disagree about where a member ends: `false` for anything that is not a key,
|
|
640
|
+
* a colon and a whole value in that order, which is the point at which both
|
|
641
|
+
* stop. A value need not be followed by a comma — a truncated object still
|
|
642
|
+
* answers the fields before the cut — so a missing one only sets `next` to
|
|
643
|
+
* `-1`, and the read that follows answers `false`.
|
|
644
|
+
*/
|
|
645
|
+
const jsonReadMember = (object: string, at: i32, member: JsonMember): boolean => {
|
|
646
|
+
const length: i32 = toI32(object.length)
|
|
647
|
+
// `jsonSkipBlank` never answers a negative index for a non-negative one, but
|
|
648
|
+
// the bounds proof does not look inside a callee, so every cursor it answers
|
|
649
|
+
// is tested for `i < 0` beside `i >= length`: the pair is what lets each read
|
|
650
|
+
// below go without a check.
|
|
651
|
+
if (at < 0 || at >= length || toI32(object.charCodeAt(at)) !== JSON_QUOTE) {
|
|
652
|
+
return false
|
|
653
|
+
}
|
|
654
|
+
const keyEnd: i32 = jsonEndOfString(object, at)
|
|
655
|
+
if (keyEnd < 0) {
|
|
656
|
+
return false
|
|
657
|
+
}
|
|
658
|
+
const colon: i32 = jsonSkipBlank(object, keyEnd)
|
|
659
|
+
if (colon < 0 || colon >= length || toI32(object.charCodeAt(colon)) !== JSON_COLON) {
|
|
660
|
+
return false
|
|
661
|
+
}
|
|
662
|
+
const valueAt: i32 = jsonSkipBlank(object, colon + 1)
|
|
663
|
+
const valueEnd: i32 = jsonEndOfValue(object, valueAt)
|
|
664
|
+
if (valueEnd < 0) {
|
|
665
|
+
return false
|
|
666
|
+
}
|
|
667
|
+
member.keyAt = at + 1
|
|
668
|
+
member.keyEnd = keyEnd - 1
|
|
669
|
+
member.valueAt = valueAt
|
|
670
|
+
member.valueEnd = valueEnd
|
|
671
|
+
const comma: i32 = jsonSkipBlank(object, valueEnd)
|
|
672
|
+
if (comma < 0 || comma >= length || toI32(object.charCodeAt(comma)) !== JSON_COMMA) {
|
|
673
|
+
member.next = -1
|
|
674
|
+
} else {
|
|
675
|
+
member.next = jsonSkipBlank(object, comma + 1)
|
|
676
|
+
}
|
|
677
|
+
return true
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* The text a member's value answers: a string unquoted and unescaped, anything
|
|
682
|
+
* else as the bytes it was written with.
|
|
683
|
+
*/
|
|
684
|
+
const jsonValueText = (object: string, member: JsonMember): string => {
|
|
685
|
+
const valueAt: i32 = member.valueAt
|
|
686
|
+
const valueEnd: i32 = member.valueEnd
|
|
687
|
+
// `jsonReadMember` only fills in a value `jsonEndOfValue` ended, which starts
|
|
688
|
+
// inside the object and never ends past it, so this changes no answer: it is
|
|
689
|
+
// the range the value's first byte and its `substring` are proved in.
|
|
690
|
+
if (valueAt < 0 || valueAt >= valueEnd || valueEnd > toI32(object.length)) {
|
|
691
|
+
return ""
|
|
692
|
+
}
|
|
693
|
+
if (toI32(object.charCodeAt(valueAt)) === JSON_QUOTE) {
|
|
694
|
+
return jsonUnescape(object, valueAt + 1, valueEnd - 1)
|
|
695
|
+
}
|
|
696
|
+
return object.substring(valueAt, valueEnd)
|
|
697
|
+
}
|
|
698
|
+
|
|
346
699
|
/**
|
|
347
700
|
* The value of `name` in `object`, or `null` when the object does not have that
|
|
348
701
|
* field.
|
|
@@ -358,45 +711,1034 @@ const jsonUnescape = (text: string, at: i32, end: i32): string => {
|
|
|
358
711
|
* and it is the shape the compiler's `--json` line has.
|
|
359
712
|
*/
|
|
360
713
|
export const jsonField = (object: string, name: string): string | null => {
|
|
361
|
-
const
|
|
362
|
-
let
|
|
363
|
-
|
|
364
|
-
|
|
714
|
+
const member: JsonMember = new JsonMember()
|
|
715
|
+
let at: i32 = jsonFirstKey(object)
|
|
716
|
+
while (jsonReadMember(object, at, member)) {
|
|
717
|
+
// The key is compared where it stands rather than unescaped into a string
|
|
718
|
+
// first: every key before the one asked for is a no, and a no should not
|
|
719
|
+
// cost an allocation.
|
|
720
|
+
if (jsonKeyEquals(object, member.keyAt, member.keyEnd, name)) {
|
|
721
|
+
return jsonValueText(object, member)
|
|
722
|
+
}
|
|
723
|
+
at = member.next
|
|
365
724
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
725
|
+
return null
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* The value of every one of `names` in `object`, in the order of `names`: slot
|
|
730
|
+
* `k` is exactly what `jsonField(object, names[k])` answers.
|
|
731
|
+
*
|
|
732
|
+
* It is for a caller that wants several fields of one line — a diagnostic's
|
|
733
|
+
* `code`, `line` and `message` — and would otherwise scan the line once per
|
|
734
|
+
* field. This scans it once, left to right, and stops as soon as every name has
|
|
735
|
+
* answered: a speed path and nothing more, since a slot is filled only once
|
|
736
|
+
* and reading on could change no answer. Each slot keeps the **first** field of its name, as `jsonField`
|
|
737
|
+
* does; a name asked for twice answers in both slots; and a malformed object
|
|
738
|
+
* answers in each slot what `jsonField` answers for that name, because both
|
|
739
|
+
* stop at the same member.
|
|
740
|
+
*
|
|
741
|
+
* The answer is a fresh array and the only allocation besides the values. Each
|
|
742
|
+
* value is stored into it by a plain `values[k] = text` statement, and the
|
|
743
|
+
* array is only stored into, tested and returned, so the values travel with
|
|
744
|
+
* the array: the call lets nothing else out (LANGUAGE.md, "Memory model", a
|
|
745
|
+
* value stored into an array that is returned with it). A loop calling this
|
|
746
|
+
* keeps its per-pass release on a line older than the pass, as `jsonField`'s
|
|
747
|
+
* does, and a `using a = arena()` block may hold the call. On a line built in
|
|
748
|
+
* the pass neither reader keeps the release (LANGUAGE.md, "Memory model").
|
|
749
|
+
* Reading `values` back, passing it to a function, or storing it anywhere
|
|
750
|
+
* before the return would undo this; `tests/link/std_json` pins the flat
|
|
751
|
+
* arena and the `using` block.
|
|
752
|
+
*/
|
|
753
|
+
export const jsonFields = (object: string, names: string[]): (string | null)[] => {
|
|
754
|
+
const count: i32 = toI32(names.length)
|
|
755
|
+
const values: (string | null)[] = new Array<string | null>(count)
|
|
756
|
+
const member: JsonMember = new JsonMember()
|
|
757
|
+
let missing: i32 = count
|
|
758
|
+
let at: i32 = jsonFirstKey(object)
|
|
759
|
+
while (missing > 0 && jsonReadMember(object, at, member)) {
|
|
760
|
+
// A value is built once per member however many names it answers, and
|
|
761
|
+
// only when one of them does.
|
|
762
|
+
let text: string | null = null
|
|
763
|
+
let k: i32 = 0
|
|
764
|
+
// Bounded by both lengths, which are the same, because the bounds proof
|
|
765
|
+
// follows each array by its own length and not by `count`.
|
|
766
|
+
while (k < toI32(values.length) && k < toI32(names.length)) {
|
|
767
|
+
if (values[k] === null && jsonKeyEquals(object, member.keyAt, member.keyEnd, names[k])) {
|
|
768
|
+
if (text === null) {
|
|
769
|
+
text = jsonValueText(object, member)
|
|
770
|
+
}
|
|
771
|
+
// Always true: the loop's own test. A call stands between it and the
|
|
772
|
+
// store, and a call is where the bounds proof forgets a length.
|
|
773
|
+
if (k < toI32(values.length)) {
|
|
774
|
+
values[k] = text
|
|
775
|
+
}
|
|
776
|
+
missing -= 1
|
|
392
777
|
}
|
|
393
|
-
|
|
778
|
+
k += 1
|
|
394
779
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
780
|
+
at = member.next
|
|
781
|
+
}
|
|
782
|
+
return values
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// ---- The writer ---------------------------------------------------------------
|
|
786
|
+
|
|
787
|
+
/** `JsonWriter.failed` while every call so far has been a well-formed write that fitted. */
|
|
788
|
+
export const JSON_WRITE_OK: i32 = 0
|
|
789
|
+
/** A value, an opener or a `raw` fragment, written in an object where a key is due. */
|
|
790
|
+
export const JSON_WRITE_KEY_EXPECTED: i32 = -1
|
|
791
|
+
/** `key` outside an object or straight after another key, or `endObject` straight after a key. */
|
|
792
|
+
export const JSON_WRITE_VALUE_EXPECTED: i32 = -2
|
|
793
|
+
/** `endObject` or `endArray` with no container open, or closing the other kind. */
|
|
794
|
+
export const JSON_WRITE_UNBALANCED: i32 = -3
|
|
795
|
+
/** A `beginObject` or `beginArray` that would nest deeper than `JSON_WRITER_MAX_DEPTH`. */
|
|
796
|
+
export const JSON_WRITE_TOO_DEEP: i32 = -4
|
|
797
|
+
/** A write that does not fit between the cursor and `buf.length`; nothing past `buf.length` is touched. */
|
|
798
|
+
export const JSON_WRITE_FULL: i32 = -5
|
|
799
|
+
/** A second value at the top level, after the first was written whole. */
|
|
800
|
+
export const JSON_WRITE_DONE: i32 = -6
|
|
801
|
+
/** A `raw` window outside its array, or a `start` outside the writer's buffer. */
|
|
802
|
+
export const JSON_WRITE_BAD_WINDOW: i32 = -7
|
|
803
|
+
/** An `f64` that is NaN or an infinity, written by a writer `setStrict(true)` made strict. */
|
|
804
|
+
export const JSON_WRITE_NON_FINITE: i32 = -8
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* How deep a writer nests objects and arrays. The kind of each open container
|
|
808
|
+
* is one bit of two `i32` fields inside the writer, so a nesting past it is
|
|
809
|
+
* `JSON_WRITE_TOO_DEEP` rather than a stack that grows.
|
|
810
|
+
*/
|
|
811
|
+
export const JSON_WRITER_MAX_DEPTH: i32 = 64
|
|
812
|
+
/** The lowest bit, spelled `i32` so that `<<` has an `i32` on its left under `--number-mode f64` too. */
|
|
813
|
+
const JSON_WRITER_BIT: i32 = 1
|
|
814
|
+
/** The shift that moves a word's lowest bit to its highest, and its highest to its lowest with `>>>`. */
|
|
815
|
+
const JSON_WRITER_TOP_BIT: i32 = 31
|
|
816
|
+
/**
|
|
817
|
+
* The widest indent, in spaces: `JSON.stringify` clamps its `space` argument
|
|
818
|
+
* to 10, so a wider one writes what 10 writes. At `JSON_WRITER_MAX_DEPTH` it
|
|
819
|
+
* puts 640 bytes before a line's value, which is the most a line break costs.
|
|
820
|
+
*/
|
|
821
|
+
const JSON_WRITER_MAX_INDENT: i32 = 10
|
|
822
|
+
|
|
823
|
+
const JSON_SLASH_B: i32 = 98
|
|
824
|
+
const JSON_SLASH_F: i32 = 102
|
|
825
|
+
const JSON_SLASH_N: i32 = 110
|
|
826
|
+
const JSON_SLASH_R: i32 = 114
|
|
827
|
+
const JSON_SLASH_T: i32 = 116
|
|
828
|
+
const JSON_DIGIT_ZERO: i32 = 48
|
|
829
|
+
const JSON_MINUS: i32 = 45
|
|
830
|
+
const JSON_DECIMAL_BASE: i32 = 10
|
|
831
|
+
const JSON_LOWER_A: i32 = 97
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* The letter of the short escape for `code` — `"`, `\`, backspace, form feed,
|
|
835
|
+
* newline, carriage return or tab — or `0` when it has none.
|
|
836
|
+
*/
|
|
837
|
+
const jsonShortEscape = (code: i32): i32 => {
|
|
838
|
+
if (code === JSON_QUOTE || code === JSON_BACKSLASH) {
|
|
839
|
+
return code
|
|
840
|
+
}
|
|
841
|
+
if (code === JSON_NEWLINE) {
|
|
842
|
+
return JSON_SLASH_N
|
|
843
|
+
}
|
|
844
|
+
if (code === JSON_TAB) {
|
|
845
|
+
return JSON_SLASH_T
|
|
846
|
+
}
|
|
847
|
+
if (code === JSON_CARRIAGE_RETURN) {
|
|
848
|
+
return JSON_SLASH_R
|
|
849
|
+
}
|
|
850
|
+
if (code === JSON_BACKSPACE) {
|
|
851
|
+
return JSON_SLASH_B
|
|
852
|
+
}
|
|
853
|
+
if (code === JSON_FORM_FEED) {
|
|
854
|
+
return JSON_SLASH_F
|
|
855
|
+
}
|
|
856
|
+
return 0
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/** One lower-case hex digit, as `JSON.stringify` writes the last two of a `\u00xx`. */
|
|
860
|
+
const jsonHexByte = (nibble: i32): i32 =>
|
|
861
|
+
nibble < JSON_DECIMAL_BASE ? JSON_DIGIT_ZERO + nibble : JSON_LOWER_A + nibble - JSON_DECIMAL_BASE
|
|
862
|
+
|
|
863
|
+
/** Whether byte `code` of a string goes inside the quotes as itself: JSON escapes only `"`, `\` and the bytes below a space. */
|
|
864
|
+
const jsonIsPlainByte = (code: i32): boolean =>
|
|
865
|
+
code >= JSON_SPACE && code !== JSON_QUOTE && code !== JSON_BACKSLASH
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* How many bytes byte `code` of a string takes inside the quotes: itself, a
|
|
869
|
+
* two-byte short escape, or the six bytes of `\u00xx`.
|
|
870
|
+
*/
|
|
871
|
+
const jsonEscapedWidth = (code: i32): i32 => {
|
|
872
|
+
if (jsonIsPlainByte(code)) {
|
|
873
|
+
return 1
|
|
874
|
+
}
|
|
875
|
+
return jsonShortEscape(code) !== 0 ? 2 : 6
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* A JSON text written into a byte buffer the caller owns: `buf[start ..)`,
|
|
880
|
+
* up to `buf.length`, with `end` the cursor. Every comma and colon is the
|
|
881
|
+
* writer's, so the caller says only what the value is:
|
|
882
|
+
*
|
|
883
|
+
* const w = new JsonWriter(out, 0)
|
|
884
|
+
* w.beginObject()
|
|
885
|
+
* w.key("message")
|
|
886
|
+
* w.string("Hello, World!")
|
|
887
|
+
* w.endObject()
|
|
888
|
+
* if (w.complete()) {
|
|
889
|
+
* // out[0 .. w.end) is {"message":"Hello, World!"}
|
|
890
|
+
* }
|
|
891
|
+
*
|
|
892
|
+
* No call allocates except `f64` (whose digits are a string the call
|
|
893
|
+
* releases before it returns) and `text`: a writer reset onto the same buffer
|
|
894
|
+
* for each request keeps `Arena.used()` flat, which `tests/link/std_json_writer`
|
|
895
|
+
* measures over 10,000 writes.
|
|
896
|
+
*
|
|
897
|
+
* Misuse is a sticky code, not a panic and not a `Result` (the module header
|
|
898
|
+
* has the policy). Once `failed` is negative every later call does nothing, so
|
|
899
|
+
* a caller writes the whole value and reads `failed` once at the end.
|
|
900
|
+
*/
|
|
901
|
+
export class JsonWriter {
|
|
902
|
+
buf: u8[]
|
|
903
|
+
start: i32 = 0
|
|
904
|
+
/** The cursor: the bytes written are `buf[start .. end)`. */
|
|
905
|
+
end: i32 = 0
|
|
906
|
+
/** `JSON_WRITE_OK`, or the first `JSON_WRITE_*` code a call hit. */
|
|
907
|
+
failed: i32 = 0
|
|
908
|
+
/** How many containers are open. */
|
|
909
|
+
depth: i32 = 0
|
|
910
|
+
/**
|
|
911
|
+
* The kinds of the open containers, one bit each, 1 for an object, as a
|
|
912
|
+
* stack 64 bits deep: the innermost is bit 0 of `objectsLow`, an opener
|
|
913
|
+
* shifts the pair left by one and a closer shifts it back. So the check
|
|
914
|
+
* before every value — is the innermost container an object? — is one
|
|
915
|
+
* `and` of one word, and costs the minified text nothing a cached copy
|
|
916
|
+
* would save. Two `i32` words rather than an `i64` or an array, so that a
|
|
917
|
+
* writer allocates nothing and the module still runs under Node, where an
|
|
918
|
+
* `i64` is a BigInt.
|
|
919
|
+
*/
|
|
920
|
+
objectsLow: i32 = 0
|
|
921
|
+
objectsHigh: i32 = 0
|
|
922
|
+
/**
|
|
923
|
+
* What goes before the next key or element of the innermost container,
|
|
924
|
+
* one `JSON_WRITER_SEP_*`: nothing, a comma, and in an indented text a
|
|
925
|
+
* line break or a comma and a line break. The minified text tests it only
|
|
926
|
+
* where it would test a "comma due" flag, against 0 and 1, so the indented
|
|
927
|
+
* states cost it nothing; bit 1 is the indent and every step keeps it.
|
|
928
|
+
*/
|
|
929
|
+
sep: i32 = 0
|
|
930
|
+
/**
|
|
931
|
+
* How many `indentByte`s each level of nesting puts at the start of a line,
|
|
932
|
+
* or `0` for the minified text. A mode, not a part of the text: `reset`
|
|
933
|
+
* keeps it, so a writer set up once writes every text the same way.
|
|
934
|
+
*/
|
|
935
|
+
indentWidth: i32 = 0
|
|
936
|
+
/** The byte an indent is made of: a space, or a tab after `setIndentTab`. */
|
|
937
|
+
indentByte: i32 = 32
|
|
938
|
+
/** Whether a key has been written in the innermost object and its value is due. */
|
|
939
|
+
afterKey: boolean = false
|
|
940
|
+
/** Whether the top-level value has been written whole. */
|
|
941
|
+
done: boolean = false
|
|
942
|
+
/** Whether a NaN or infinite `f64` is `JSON_WRITE_NON_FINITE` rather than `null`. Kept by `reset`. */
|
|
943
|
+
strict: boolean = false
|
|
944
|
+
|
|
945
|
+
constructor(buf: u8[], start: i32) {
|
|
946
|
+
this.buf = buf
|
|
947
|
+
this.reset(buf, start)
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Starts a new text at `buf[start ..)`, forgetting everything the last one
|
|
952
|
+
* wrote and every error it hit: what a server calls once per response, on
|
|
953
|
+
* the buffer it reuses. A `start` outside `buf` is `JSON_WRITE_BAD_WINDOW`.
|
|
954
|
+
*/
|
|
955
|
+
reset(buf: u8[], start: i32): void {
|
|
956
|
+
this.buf = buf
|
|
957
|
+
this.start = start
|
|
958
|
+
this.end = start
|
|
959
|
+
this.depth = 0
|
|
960
|
+
this.objectsLow = 0
|
|
961
|
+
this.objectsHigh = 0
|
|
962
|
+
this.sep = this.indentWidth !== 0 ? JSON_WRITER_SEP_INDENTED : JSON_WRITER_SEP_NONE
|
|
963
|
+
this.afterKey = false
|
|
964
|
+
this.done = false
|
|
965
|
+
this.failed = start < 0 || start > toI32(buf.length) ? JSON_WRITE_BAD_WINDOW : JSON_WRITE_OK
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Indents the text by `spaces` spaces a level, as `JSON.stringify(v, null,
|
|
970
|
+
* spaces)` does: each member and element on a line of its own, one space
|
|
971
|
+
* after a key's colon, and an empty `{}` or `[]` left on one line. `spaces`
|
|
972
|
+
* is clamped to 10 as `JSON.stringify` clamps it, and below 1 is the
|
|
973
|
+
* minified text, the default. A mode that `reset` keeps; set it before the
|
|
974
|
+
* text, since a change in the middle of one applies from the next byte.
|
|
975
|
+
*/
|
|
976
|
+
setIndent(spaces: i32): void {
|
|
977
|
+
let width: i32 = spaces
|
|
978
|
+
if (width < 0) {
|
|
979
|
+
width = 0
|
|
980
|
+
} else if (width > JSON_WRITER_MAX_INDENT) {
|
|
981
|
+
width = JSON_WRITER_MAX_INDENT
|
|
398
982
|
}
|
|
399
|
-
|
|
983
|
+
this.indentWidth = width
|
|
984
|
+
this.indentByte = JSON_SPACE
|
|
985
|
+
this.sep =
|
|
986
|
+
(this.sep & JSON_WRITER_SEP_COMMA) | (width !== 0 ? JSON_WRITER_SEP_INDENTED : JSON_WRITER_SEP_NONE)
|
|
400
987
|
}
|
|
401
|
-
|
|
988
|
+
|
|
989
|
+
/** Indents the text by one tab a level, as `JSON.stringify(v, null, "\t")` does. */
|
|
990
|
+
setIndentTab(): void {
|
|
991
|
+
this.indentWidth = 1
|
|
992
|
+
this.indentByte = JSON_TAB
|
|
993
|
+
this.sep = this.sep | JSON_WRITER_SEP_INDENTED
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Whether an `f64` that is NaN or an infinity fails the text with
|
|
998
|
+
* `JSON_WRITE_NON_FINITE`, writing nothing, rather than being written as
|
|
999
|
+
* the `null` `JSON.stringify` writes. Off by default; a mode that `reset`
|
|
1000
|
+
* keeps. Strict is for a caller to whom a `null` where a number was due is
|
|
1001
|
+
* a corrupt record rather than a missing one.
|
|
1002
|
+
*/
|
|
1003
|
+
setStrict(strict: boolean): void {
|
|
1004
|
+
this.strict = strict
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/** How many bytes have been written since `reset`. */
|
|
1008
|
+
written(): i32 {
|
|
1009
|
+
return this.end - this.start
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/** Whether one whole value has been written, every container closed, and nothing failed. */
|
|
1013
|
+
complete(): boolean {
|
|
1014
|
+
return this.failed === JSON_WRITE_OK && this.done
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* The bytes written so far, `buf[start .. end)`, as a string. A convenience
|
|
1019
|
+
* that allocates a byte at a time, for a caller that wants text rather than
|
|
1020
|
+
* bytes; a server sends `buf` itself.
|
|
1021
|
+
*/
|
|
1022
|
+
text(): string {
|
|
1023
|
+
const buf: u8[] = this.buf
|
|
1024
|
+
const parts: string[] = []
|
|
1025
|
+
for (let k: i32 = this.start; k >= 0 && k < this.end && k < toI32(buf.length); k += 1) {
|
|
1026
|
+
parts.push(String.fromCharCode(toI32(buf[k])))
|
|
1027
|
+
}
|
|
1028
|
+
return parts.join("")
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
beginObject(): void {
|
|
1032
|
+
jsonWriterOpen(this, true, JSON_OPEN_BRACE)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
endObject(): void {
|
|
1036
|
+
if (this.sep === JSON_WRITER_SEP_COMMA_BREAK) {
|
|
1037
|
+
jsonWriterCloseIndented(this, true, JSON_CLOSE_BRACE)
|
|
1038
|
+
} else {
|
|
1039
|
+
jsonWriterClose(this, true, JSON_CLOSE_BRACE)
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
beginArray(): void {
|
|
1044
|
+
jsonWriterOpen(this, false, JSON_OPEN_BRACKET)
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
endArray(): void {
|
|
1048
|
+
if (this.sep === JSON_WRITER_SEP_COMMA_BREAK) {
|
|
1049
|
+
jsonWriterCloseIndented(this, false, JSON_CLOSE_BRACKET)
|
|
1050
|
+
} else {
|
|
1051
|
+
jsonWriterClose(this, false, JSON_CLOSE_BRACKET)
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/** The next member's key, with the comma before it and the colon after it. */
|
|
1056
|
+
key(name: string): void {
|
|
1057
|
+
if (this.failed !== JSON_WRITE_OK) {
|
|
1058
|
+
return
|
|
1059
|
+
}
|
|
1060
|
+
if (this.depth === 0 && this.done) {
|
|
1061
|
+
this.failed = JSON_WRITE_DONE
|
|
1062
|
+
return
|
|
1063
|
+
}
|
|
1064
|
+
if (!jsonWriterInObject(this) || this.afterKey) {
|
|
1065
|
+
this.failed = JSON_WRITE_VALUE_EXPECTED
|
|
1066
|
+
return
|
|
1067
|
+
}
|
|
1068
|
+
const sep: i32 = this.sep
|
|
1069
|
+
if (sep !== JSON_WRITER_SEP_NONE) {
|
|
1070
|
+
if (sep !== JSON_WRITER_SEP_COMMA) {
|
|
1071
|
+
jsonWriterIndented(this, this.depth, name)
|
|
1072
|
+
return
|
|
1073
|
+
}
|
|
1074
|
+
if (!jsonWriterPutByte(this, JSON_COMMA)) {
|
|
1075
|
+
return
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
if (jsonWriterPutString(this, name) && jsonWriterPutByte(this, JSON_COLON)) {
|
|
1079
|
+
this.afterKey = true
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/** `s` quoted and escaped as `JSON.stringify` escapes it; every other byte goes through as itself. */
|
|
1084
|
+
string(s: string): void {
|
|
1085
|
+
if (jsonWriterValueDue(this) && jsonWriterPutString(this, s)) {
|
|
1086
|
+
jsonWriterValueDone(this)
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* `key` for a name held as bytes, `bytes[off .. off + len)`, escaped exactly
|
|
1092
|
+
* as `key` escapes a string of those bytes, and allocating nothing: what a
|
|
1093
|
+
* caller that keeps its text in a byte buffer, such as `nish/json-value`'s
|
|
1094
|
+
* `JsonDocument`, writes without making a string per key. A window outside
|
|
1095
|
+
* `bytes` is `JSON_WRITE_BAD_WINDOW`, checked once, before anything is
|
|
1096
|
+
* written.
|
|
1097
|
+
*/
|
|
1098
|
+
keyBytes(bytes: u8[], off: i32, len: i32): void {
|
|
1099
|
+
if (this.failed !== JSON_WRITE_OK) {
|
|
1100
|
+
return
|
|
1101
|
+
}
|
|
1102
|
+
if (!jsonWriterWindowOk(this, bytes, off, len)) {
|
|
1103
|
+
return
|
|
1104
|
+
}
|
|
1105
|
+
if (this.depth === 0 && this.done) {
|
|
1106
|
+
this.failed = JSON_WRITE_DONE
|
|
1107
|
+
return
|
|
1108
|
+
}
|
|
1109
|
+
if (!jsonWriterInObject(this) || this.afterKey) {
|
|
1110
|
+
this.failed = JSON_WRITE_VALUE_EXPECTED
|
|
1111
|
+
return
|
|
1112
|
+
}
|
|
1113
|
+
const sep: i32 = this.sep
|
|
1114
|
+
const indented: boolean = sep !== JSON_WRITER_SEP_NONE && sep !== JSON_WRITER_SEP_COMMA
|
|
1115
|
+
if (indented) {
|
|
1116
|
+
if (!jsonWriterBreakBeforeKey(this)) {
|
|
1117
|
+
return
|
|
1118
|
+
}
|
|
1119
|
+
} else if (sep === JSON_WRITER_SEP_COMMA && !jsonWriterPutByte(this, JSON_COMMA)) {
|
|
1120
|
+
return
|
|
1121
|
+
}
|
|
1122
|
+
if (
|
|
1123
|
+
jsonWriterPutBytes(this, bytes, off, len) &&
|
|
1124
|
+
jsonWriterPutByte(this, JSON_COLON) &&
|
|
1125
|
+
(!indented || jsonWriterPutByte(this, JSON_SPACE))
|
|
1126
|
+
) {
|
|
1127
|
+
this.afterKey = true
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* `string` for text held as bytes, `bytes[off .. off + len)`: the same
|
|
1133
|
+
* quotes and escapes, byte for byte, and no allocation. A window outside
|
|
1134
|
+
* `bytes` is `JSON_WRITE_BAD_WINDOW`, checked once, before anything is
|
|
1135
|
+
* written.
|
|
1136
|
+
*/
|
|
1137
|
+
stringBytes(bytes: u8[], off: i32, len: i32): void {
|
|
1138
|
+
if (this.failed !== JSON_WRITE_OK) {
|
|
1139
|
+
return
|
|
1140
|
+
}
|
|
1141
|
+
if (!jsonWriterWindowOk(this, bytes, off, len)) {
|
|
1142
|
+
return
|
|
1143
|
+
}
|
|
1144
|
+
if (jsonWriterValueDue(this) && jsonWriterPutBytes(this, bytes, off, len)) {
|
|
1145
|
+
jsonWriterValueDone(this)
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
i32(v: i32): void {
|
|
1150
|
+
if (jsonWriterValueDue(this) && jsonWriterPutDigits(this, v, -1)) {
|
|
1151
|
+
jsonWriterValueDone(this)
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* One past `i32`'s range is ten digits, so the last digit is split off in
|
|
1157
|
+
* `u32` and the nine before it are an `i32`'s.
|
|
1158
|
+
*/
|
|
1159
|
+
u32(v: u32): void {
|
|
1160
|
+
if (!jsonWriterValueDue(this)) {
|
|
1161
|
+
return
|
|
1162
|
+
}
|
|
1163
|
+
// The largest `u32` whose digits fit an `i32`; a module constant cannot be a `u32`.
|
|
1164
|
+
const i32Max: u32 = 2147483647
|
|
1165
|
+
let written: boolean = false
|
|
1166
|
+
if (v <= i32Max) {
|
|
1167
|
+
written = jsonWriterPutDigits(this, toI32(v), -1)
|
|
1168
|
+
} else {
|
|
1169
|
+
const ten: u32 = 10
|
|
1170
|
+
const last: u32 = v % ten
|
|
1171
|
+
written = jsonWriterPutDigits(this, toI32((v - last) / ten), toI32(last))
|
|
1172
|
+
}
|
|
1173
|
+
if (written) {
|
|
1174
|
+
jsonWriterValueDone(this)
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/** The digits of `v`, the minimum included: the arithmetic stays on the negative side, where it fits. */
|
|
1179
|
+
i64(v: i64): void {
|
|
1180
|
+
if (jsonWriterValueDue(this) && jsonWriterPutDigits64(this, v)) {
|
|
1181
|
+
jsonWriterValueDone(this)
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* `v` as `String(v)` spells it, which is `JSON.stringify`'s rule: the
|
|
1187
|
+
* shortest digits that read back as `v`, `-0` as `0`, and `null` for NaN
|
|
1188
|
+
* and both infinities, which JSON cannot spell — or, once `setStrict(true)`,
|
|
1189
|
+
* `JSON_WRITE_NON_FINITE` for those three, with nothing written.
|
|
1190
|
+
*/
|
|
1191
|
+
f64(v: f64): void {
|
|
1192
|
+
// `v - v` is `0` for every finite `v`, and NaN for NaN and both infinities.
|
|
1193
|
+
if (v - v !== toF64(0)) {
|
|
1194
|
+
if (!this.strict) {
|
|
1195
|
+
this.nullValue()
|
|
1196
|
+
} else if (jsonWriterValueAllowed(this)) {
|
|
1197
|
+
this.failed = JSON_WRITE_NON_FINITE
|
|
1198
|
+
}
|
|
1199
|
+
} else if (jsonWriterValueDue(this) && jsonWriterPutF64(this, v)) {
|
|
1200
|
+
jsonWriterValueDone(this)
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
boolean(v: boolean): void {
|
|
1205
|
+
if (jsonWriterValueDue(this) && jsonWriterPutText(this, v ? "true" : "false")) {
|
|
1206
|
+
jsonWriterValueDone(this)
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
/** `null`. The method is not named `null`, which the parser refuses as a member name. */
|
|
1211
|
+
nullValue(): void {
|
|
1212
|
+
if (jsonWriterValueDue(this) && jsonWriterPutText(this, "null")) {
|
|
1213
|
+
jsonWriterValueDone(this)
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* `bytes[off .. off + len)`, copied as it is and counted as one value: a
|
|
1219
|
+
* fragment the caller encoded already, such as a cached object. Nothing in
|
|
1220
|
+
* it is checked, so a fragment that is not one whole JSON value makes a
|
|
1221
|
+
* text that is not JSON either. The bytes are copied front to back, so
|
|
1222
|
+
* `bytes` is not the writer's own buffer at a window the copy overtakes.
|
|
1223
|
+
* In an indented text the fragment goes in where a value would, after its
|
|
1224
|
+
* line break and indent, but its own bytes are not re-indented: an object
|
|
1225
|
+
* fragment stays on one line, where `JSON.stringify` would spread it out.
|
|
1226
|
+
*/
|
|
1227
|
+
raw(bytes: u8[], off: i32, len: i32): void {
|
|
1228
|
+
if (this.failed !== JSON_WRITE_OK) {
|
|
1229
|
+
return
|
|
1230
|
+
}
|
|
1231
|
+
if (off < 0 || len < 0 || off > toI32(bytes.length) - len) {
|
|
1232
|
+
this.failed = JSON_WRITE_BAD_WINDOW
|
|
1233
|
+
return
|
|
1234
|
+
}
|
|
1235
|
+
if (!jsonWriterValueDue(this)) {
|
|
1236
|
+
return
|
|
1237
|
+
}
|
|
1238
|
+
const buf: u8[] = this.buf
|
|
1239
|
+
const at: i32 = this.end
|
|
1240
|
+
if (at < 0 || at > toI32(buf.length) - len) {
|
|
1241
|
+
this.failed = JSON_WRITE_FULL
|
|
1242
|
+
return
|
|
1243
|
+
}
|
|
1244
|
+
for (let k: i32 = 0; k < len && off + k < toI32(bytes.length) && at + k < toI32(buf.length); k += 1) {
|
|
1245
|
+
if (off + k >= 0 && at + k >= 0) {
|
|
1246
|
+
buf[at + k] = bytes[off + k]
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
this.end = at + len
|
|
1250
|
+
jsonWriterValueDone(this)
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/** Whether the innermost open container is an object; `false` at the top level. */
|
|
1255
|
+
const jsonWriterInObject = (w: JsonWriter): boolean => (w.objectsLow & JSON_WRITER_BIT) !== 0
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Whether a value may be written now, with the comma before it written when
|
|
1259
|
+
* one is due; `false`, with `failed` set, when the writer has failed, a key
|
|
1260
|
+
* is due, or the top-level value is already whole.
|
|
1261
|
+
*/
|
|
1262
|
+
const jsonWriterValueDue = (w: JsonWriter): boolean => {
|
|
1263
|
+
if (w.failed !== JSON_WRITE_OK) {
|
|
1264
|
+
return false
|
|
1265
|
+
}
|
|
1266
|
+
if (w.depth === 0) {
|
|
1267
|
+
if (w.done) {
|
|
1268
|
+
w.failed = JSON_WRITE_DONE
|
|
1269
|
+
return false
|
|
1270
|
+
}
|
|
1271
|
+
return true
|
|
1272
|
+
}
|
|
1273
|
+
if (w.afterKey) {
|
|
1274
|
+
w.afterKey = false
|
|
1275
|
+
return true
|
|
1276
|
+
}
|
|
1277
|
+
if (jsonWriterInObject(w)) {
|
|
1278
|
+
w.failed = JSON_WRITE_KEY_EXPECTED
|
|
1279
|
+
return false
|
|
1280
|
+
}
|
|
1281
|
+
// A comma is the common case, every element but the first, so it is
|
|
1282
|
+
// tested first and costs the one compare a "comma due" flag would.
|
|
1283
|
+
const sep: i32 = w.sep
|
|
1284
|
+
if (sep === JSON_WRITER_SEP_COMMA) {
|
|
1285
|
+
return jsonWriterPutByte(w, JSON_COMMA)
|
|
1286
|
+
}
|
|
1287
|
+
return sep === JSON_WRITER_SEP_NONE || jsonWriterIndented(w, w.depth, "")
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* `jsonWriterValueDue`'s checks without its writes: whether a value may be
|
|
1292
|
+
* written now, with `failed` set to the misuse when not. Strict `f64` asks
|
|
1293
|
+
* it before it fails a non-finite value, so a value where a key is due, or
|
|
1294
|
+
* after the top-level value, keeps the code it has for every other value:
|
|
1295
|
+
* the first misuse wins, and a non-finite value is reported only when it is
|
|
1296
|
+
* the first thing wrong.
|
|
1297
|
+
*/
|
|
1298
|
+
const jsonWriterValueAllowed = (w: JsonWriter): boolean => {
|
|
1299
|
+
if (w.failed !== JSON_WRITE_OK) {
|
|
1300
|
+
return false
|
|
1301
|
+
}
|
|
1302
|
+
if (w.depth === 0 && w.done) {
|
|
1303
|
+
w.failed = JSON_WRITE_DONE
|
|
1304
|
+
return false
|
|
1305
|
+
}
|
|
1306
|
+
if (w.depth !== 0 && jsonWriterInObject(w) && !w.afterKey) {
|
|
1307
|
+
w.failed = JSON_WRITE_KEY_EXPECTED
|
|
1308
|
+
return false
|
|
1309
|
+
}
|
|
1310
|
+
return true
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/** After a whole value: the next element needs a comma, and at the top level the text is done. */
|
|
1314
|
+
const jsonWriterValueDone = (w: JsonWriter): void => {
|
|
1315
|
+
w.sep = w.sep | JSON_WRITER_SEP_COMMA
|
|
1316
|
+
if (w.depth === 0) {
|
|
1317
|
+
w.done = true
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/** `beginObject` and `beginArray`: the opener, and a slot for the new container. */
|
|
1322
|
+
const jsonWriterOpen = (w: JsonWriter, object: boolean, opener: i32): void => {
|
|
1323
|
+
if (w.failed === JSON_WRITE_OK && w.depth >= JSON_WRITER_MAX_DEPTH) {
|
|
1324
|
+
w.failed = JSON_WRITE_TOO_DEEP
|
|
1325
|
+
return
|
|
1326
|
+
}
|
|
1327
|
+
if (!jsonWriterValueDue(w) || !jsonWriterPutByte(w, opener)) {
|
|
1328
|
+
return
|
|
1329
|
+
}
|
|
1330
|
+
const low: i32 = w.objectsLow
|
|
1331
|
+
w.objectsHigh = (w.objectsHigh << JSON_WRITER_BIT) | (low >>> JSON_WRITER_TOP_BIT)
|
|
1332
|
+
w.objectsLow = (low << JSON_WRITER_BIT) | (object ? JSON_WRITER_BIT : 0)
|
|
1333
|
+
w.depth = w.depth + 1
|
|
1334
|
+
w.sep = w.sep & JSON_WRITER_SEP_INDENTED
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
/** `endObject` and `endArray`: the closer, when the innermost container is of that kind and not waiting on a value. */
|
|
1338
|
+
const jsonWriterClose = (w: JsonWriter, object: boolean, closer: i32): void => {
|
|
1339
|
+
if (w.failed !== JSON_WRITE_OK) {
|
|
1340
|
+
return
|
|
1341
|
+
}
|
|
1342
|
+
if (w.depth === 0 || jsonWriterInObject(w) !== object) {
|
|
1343
|
+
w.failed = JSON_WRITE_UNBALANCED
|
|
1344
|
+
return
|
|
1345
|
+
}
|
|
1346
|
+
if (w.afterKey) {
|
|
1347
|
+
w.failed = JSON_WRITE_VALUE_EXPECTED
|
|
1348
|
+
return
|
|
1349
|
+
}
|
|
1350
|
+
if (jsonWriterPutByte(w, closer)) {
|
|
1351
|
+
w.depth -= 1
|
|
1352
|
+
const high: i32 = w.objectsHigh
|
|
1353
|
+
w.objectsLow = (w.objectsLow >>> JSON_WRITER_BIT) | ((high & JSON_WRITER_BIT) << JSON_WRITER_TOP_BIT)
|
|
1354
|
+
w.objectsHigh = high >>> JSON_WRITER_BIT
|
|
1355
|
+
jsonWriterValueDone(w)
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
/**
|
|
1360
|
+
* `endObject` and `endArray` for an indented container that holds anything,
|
|
1361
|
+
* the one state whose closer goes on a line of its own: the line break one
|
|
1362
|
+
* level out, after the checks `jsonWriterClose` makes, which it then makes
|
|
1363
|
+
* again before it writes the closer. A separate entry, chosen by the `sep`
|
|
1364
|
+
* test `end*` makes, rather than a branch inside `jsonWriterClose`, which
|
|
1365
|
+
* stays `main`'s code and small enough for LLVM to inline into every `end*`.
|
|
1366
|
+
*/
|
|
1367
|
+
const jsonWriterCloseIndented = (w: JsonWriter, object: boolean, closer: i32): void => {
|
|
1368
|
+
if (
|
|
1369
|
+
w.failed === JSON_WRITE_OK &&
|
|
1370
|
+
w.depth !== 0 &&
|
|
1371
|
+
jsonWriterInObject(w) === object &&
|
|
1372
|
+
!w.afterKey &&
|
|
1373
|
+
!jsonWriterIndented(w, w.depth - 1, "")
|
|
1374
|
+
) {
|
|
1375
|
+
return
|
|
1376
|
+
}
|
|
1377
|
+
jsonWriterClose(w, object, closer)
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/** One byte at the cursor, or `JSON_WRITE_FULL` when the buffer has no room for it. */
|
|
1381
|
+
const jsonWriterPutByte = (w: JsonWriter, code: i32): boolean => {
|
|
1382
|
+
const buf: u8[] = w.buf
|
|
1383
|
+
const at: i32 = w.end
|
|
1384
|
+
if (at < 0 || at >= toI32(buf.length)) {
|
|
1385
|
+
w.failed = JSON_WRITE_FULL
|
|
1386
|
+
return false
|
|
1387
|
+
}
|
|
1388
|
+
buf[at] = toU8(code)
|
|
1389
|
+
w.end = at + 1
|
|
1390
|
+
return true
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/** `JsonWriter.sep` when nothing goes before the next key or element: the first in a minified container. */
|
|
1394
|
+
const JSON_WRITER_SEP_NONE: i32 = 0
|
|
1395
|
+
/** A comma goes first: the minified text after a key's value or an element. */
|
|
1396
|
+
const JSON_WRITER_SEP_COMMA: i32 = 1
|
|
1397
|
+
/** The indent bit, set in every state of an indented text; alone, a line break goes first. */
|
|
1398
|
+
const JSON_WRITER_SEP_INDENTED: i32 = 2
|
|
1399
|
+
/**
|
|
1400
|
+
* A comma and a line break go first: an indented container after it holds
|
|
1401
|
+
* anything, which is also when its closer goes on a line of its own, since
|
|
1402
|
+
* an empty `{}` or `[]` closes on the line it opened on.
|
|
1403
|
+
*/
|
|
1404
|
+
const JSON_WRITER_SEP_COMMA_BREAK: i32 = 3
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* Everything the indented text writes that the minified one does not: at
|
|
1408
|
+
* `level` — the container's own depth before an element or a key, one less
|
|
1409
|
+
* before its closer — the comma when one is due (never before a closer), a
|
|
1410
|
+
* line break and the indent, then, before a key, the key `name` and `": "`.
|
|
1411
|
+
* It answers whether the caller goes on to write its own bytes: yes for an
|
|
1412
|
+
* element or a closer, no for a key, whose bytes are all written here.
|
|
1413
|
+
*
|
|
1414
|
+
* It is one function, reached only from the indented `JsonWriter.sep`
|
|
1415
|
+
* states, and the minified text never calls it: `key`, `jsonWriterValueDue`
|
|
1416
|
+
* and `jsonWriterClose` test `sep` where they tested a "comma due" flag.
|
|
1417
|
+
* Whether it is placing a closer or a key is read from `level` and the
|
|
1418
|
+
* innermost container rather than passed as a constant, so LLVM cannot fold
|
|
1419
|
+
* the other cases away at a call and inline what is left there, which would
|
|
1420
|
+
* grow those callers past what LLVM inlines into theirs and slow every
|
|
1421
|
+
* minified write.
|
|
1422
|
+
*
|
|
1423
|
+
* The line break and its indent go in whole or, with `JSON_WRITE_FULL`, not
|
|
1424
|
+
* at all; a comma before them may already be written when they do not fit,
|
|
1425
|
+
* as a comma before a minified key may, so a full buffer always leaves a
|
|
1426
|
+
* prefix of the text. The indent is `indentByte` stored `indentWidth *
|
|
1427
|
+
* level` times with one `fill`, so a break allocates nothing however deep it
|
|
1428
|
+
* is.
|
|
1429
|
+
*/
|
|
1430
|
+
const jsonWriterIndented = (w: JsonWriter, level: i32, name: string): boolean => {
|
|
1431
|
+
const closing: boolean = level !== w.depth
|
|
1432
|
+
if (!closing && w.sep === JSON_WRITER_SEP_COMMA_BREAK && !jsonWriterPutByte(w, JSON_COMMA)) {
|
|
1433
|
+
return false
|
|
1434
|
+
}
|
|
1435
|
+
const buf: u8[] = w.buf
|
|
1436
|
+
const at: i32 = w.end
|
|
1437
|
+
const stop: i32 = at + 1 + w.indentWidth * level
|
|
1438
|
+
if (at < 0 || stop > toI32(buf.length)) {
|
|
1439
|
+
w.failed = JSON_WRITE_FULL
|
|
1440
|
+
return false
|
|
1441
|
+
}
|
|
1442
|
+
buf[at] = toU8(JSON_NEWLINE)
|
|
1443
|
+
buf.fill(toU8(w.indentByte), at + 1, stop)
|
|
1444
|
+
w.end = stop
|
|
1445
|
+
// A key is the one step that is neither a closer nor an array's element.
|
|
1446
|
+
if (closing || !jsonWriterInObject(w)) {
|
|
1447
|
+
return true
|
|
1448
|
+
}
|
|
1449
|
+
if (jsonWriterPutString(w, name) && jsonWriterPutByte(w, JSON_COLON) && jsonWriterPutByte(w, JSON_SPACE)) {
|
|
1450
|
+
w.afterKey = true
|
|
1451
|
+
}
|
|
1452
|
+
return false
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/** Whether `bytes[off .. off + len)` lies inside `bytes`, for `keyBytes` and `stringBytes`; `JSON_WRITE_BAD_WINDOW` when not. */
|
|
1456
|
+
const jsonWriterWindowOk = (w: JsonWriter, bytes: u8[], off: i32, len: i32): boolean => {
|
|
1457
|
+
if (off < 0 || len < 0 || off > toI32(bytes.length) - len) {
|
|
1458
|
+
w.failed = JSON_WRITE_BAD_WINDOW
|
|
1459
|
+
return false
|
|
1460
|
+
}
|
|
1461
|
+
return true
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/**
|
|
1465
|
+
* The comma when one is due, a line break and the indent before a key of an
|
|
1466
|
+
* indented object: `jsonWriterIndented`'s first half, for `keyBytes`, whose
|
|
1467
|
+
* name is bytes rather than the string that function writes. It is a
|
|
1468
|
+
* function of its own rather than a parameter of that one so that the
|
|
1469
|
+
* string path, which every `key` takes, is left exactly as it was. Answers
|
|
1470
|
+
* whether the key may follow; all of it or, with `JSON_WRITE_FULL`, none of
|
|
1471
|
+
* the break.
|
|
1472
|
+
*/
|
|
1473
|
+
const jsonWriterBreakBeforeKey = (w: JsonWriter): boolean => {
|
|
1474
|
+
if (w.sep === JSON_WRITER_SEP_COMMA_BREAK && !jsonWriterPutByte(w, JSON_COMMA)) {
|
|
1475
|
+
return false
|
|
1476
|
+
}
|
|
1477
|
+
const buf: u8[] = w.buf
|
|
1478
|
+
const at: i32 = w.end
|
|
1479
|
+
const stop: i32 = at + 1 + w.indentWidth * w.depth
|
|
1480
|
+
if (at < 0 || stop > toI32(buf.length)) {
|
|
1481
|
+
w.failed = JSON_WRITE_FULL
|
|
1482
|
+
return false
|
|
1483
|
+
}
|
|
1484
|
+
buf[at] = toU8(JSON_NEWLINE)
|
|
1485
|
+
buf.fill(toU8(w.indentByte), at + 1, stop)
|
|
1486
|
+
w.end = stop
|
|
1487
|
+
return true
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* The bytes of `text`, which needs no escaping, at the cursor; all of them or,
|
|
1492
|
+
* with `JSON_WRITE_FULL`, none.
|
|
1493
|
+
*/
|
|
1494
|
+
const jsonWriterPutText = (w: JsonWriter, text: string): boolean => {
|
|
1495
|
+
const buf: u8[] = w.buf
|
|
1496
|
+
const n: i32 = toI32(text.length)
|
|
1497
|
+
const at: i32 = w.end
|
|
1498
|
+
if (at < 0 || at > toI32(buf.length) - n) {
|
|
1499
|
+
w.failed = JSON_WRITE_FULL
|
|
1500
|
+
return false
|
|
1501
|
+
}
|
|
1502
|
+
for (let k: i32 = 0; k < n && at + k < toI32(buf.length); k += 1) {
|
|
1503
|
+
if (at + k >= 0) {
|
|
1504
|
+
buf[at + k] = toU8(text.charCodeAt(k))
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
w.end = at + n
|
|
1508
|
+
return true
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* `s` as a JSON string at the cursor: the quotes, the seven short escapes,
|
|
1513
|
+
* `\u00xx` for the other bytes below `0x20`, and every other byte as itself.
|
|
1514
|
+
* That is `jsonQuote`'s rule in `src/strings.ts`, and `JSON.stringify`'s on a
|
|
1515
|
+
* string that is ASCII. A byte at or above `0x80` is copied, so UTF-8 comes
|
|
1516
|
+
* out as the UTF-8 `JSON.stringify` makes once it is encoded, and a byte that
|
|
1517
|
+
* is not UTF-8 at all comes out raw (the module header says why).
|
|
1518
|
+
*
|
|
1519
|
+
* The cursor is a local and is stored back only when the whole string fits,
|
|
1520
|
+
* so a `JSON_WRITE_FULL` moves nothing.
|
|
1521
|
+
*/
|
|
1522
|
+
const jsonWriterPutString = (w: JsonWriter, s: string): boolean => {
|
|
1523
|
+
const buf: u8[] = w.buf
|
|
1524
|
+
const cap: i32 = toI32(buf.length)
|
|
1525
|
+
const n: i32 = toI32(s.length)
|
|
1526
|
+
let at: i32 = w.end
|
|
1527
|
+
if (at < 0 || at >= cap) {
|
|
1528
|
+
w.failed = JSON_WRITE_FULL
|
|
1529
|
+
return false
|
|
1530
|
+
}
|
|
1531
|
+
buf[at] = toU8(JSON_QUOTE)
|
|
1532
|
+
at += 1
|
|
1533
|
+
for (let i: i32 = 0; i < n; i += 1) {
|
|
1534
|
+
const code: i32 = toI32(s.charCodeAt(i))
|
|
1535
|
+
// A plain byte is almost every byte, and costs one test of the room.
|
|
1536
|
+
// Each branch moves the cursor by a constant, which is what lets the
|
|
1537
|
+
// bounds proof keep `at >= 0` from one byte to the next.
|
|
1538
|
+
if (jsonIsPlainByte(code)) {
|
|
1539
|
+
if (at >= cap) {
|
|
1540
|
+
w.failed = JSON_WRITE_FULL
|
|
1541
|
+
return false
|
|
1542
|
+
}
|
|
1543
|
+
buf[at] = toU8(code)
|
|
1544
|
+
at += 1
|
|
1545
|
+
continue
|
|
1546
|
+
}
|
|
1547
|
+
const letter: i32 = jsonShortEscape(code)
|
|
1548
|
+
if (letter !== 0) {
|
|
1549
|
+
if (at < 0 || at + 1 >= cap) {
|
|
1550
|
+
w.failed = JSON_WRITE_FULL
|
|
1551
|
+
return false
|
|
1552
|
+
}
|
|
1553
|
+
buf[at] = toU8(JSON_BACKSLASH)
|
|
1554
|
+
buf[at + 1] = toU8(letter)
|
|
1555
|
+
at += 2
|
|
1556
|
+
continue
|
|
1557
|
+
}
|
|
1558
|
+
if (at < 0 || at + 5 >= cap) {
|
|
1559
|
+
w.failed = JSON_WRITE_FULL
|
|
1560
|
+
return false
|
|
1561
|
+
}
|
|
1562
|
+
buf[at] = toU8(JSON_BACKSLASH)
|
|
1563
|
+
buf[at + 1] = toU8(JSON_LETTER_U)
|
|
1564
|
+
buf[at + 2] = toU8(JSON_DIGIT_ZERO)
|
|
1565
|
+
buf[at + 3] = toU8(JSON_DIGIT_ZERO)
|
|
1566
|
+
buf[at + 4] = toU8(jsonHexByte(code >> 4))
|
|
1567
|
+
buf[at + 5] = toU8(jsonHexByte(code & 15))
|
|
1568
|
+
at += 6
|
|
1569
|
+
}
|
|
1570
|
+
if (at >= cap) {
|
|
1571
|
+
w.failed = JSON_WRITE_FULL
|
|
1572
|
+
return false
|
|
1573
|
+
}
|
|
1574
|
+
buf[at] = toU8(JSON_QUOTE)
|
|
1575
|
+
w.end = at + 1
|
|
1576
|
+
return true
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
/**
|
|
1580
|
+
* `jsonWriterPutString` over `bytes[off .. off + len)`, a window the caller
|
|
1581
|
+
* has checked: the same quotes and escapes, from the same three helpers, so
|
|
1582
|
+
* the two write the same bytes for the same text. The loop is its own rather
|
|
1583
|
+
* than shared with the string one, so that `string` and `key` keep the code
|
|
1584
|
+
* they had.
|
|
1585
|
+
*/
|
|
1586
|
+
const jsonWriterPutBytes = (w: JsonWriter, bytes: u8[], off: i32, len: i32): boolean => {
|
|
1587
|
+
const buf: u8[] = w.buf
|
|
1588
|
+
const cap: i32 = toI32(buf.length)
|
|
1589
|
+
let at: i32 = w.end
|
|
1590
|
+
if (at < 0 || at >= cap) {
|
|
1591
|
+
w.failed = JSON_WRITE_FULL
|
|
1592
|
+
return false
|
|
1593
|
+
}
|
|
1594
|
+
buf[at] = toU8(JSON_QUOTE)
|
|
1595
|
+
at += 1
|
|
1596
|
+
const stop: i32 = off + len
|
|
1597
|
+
for (let i: i32 = off; i >= 0 && i < stop && i < toI32(bytes.length); i += 1) {
|
|
1598
|
+
const code: i32 = toI32(bytes[i])
|
|
1599
|
+
if (jsonIsPlainByte(code)) {
|
|
1600
|
+
if (at >= cap) {
|
|
1601
|
+
w.failed = JSON_WRITE_FULL
|
|
1602
|
+
return false
|
|
1603
|
+
}
|
|
1604
|
+
buf[at] = toU8(code)
|
|
1605
|
+
at += 1
|
|
1606
|
+
continue
|
|
1607
|
+
}
|
|
1608
|
+
const letter: i32 = jsonShortEscape(code)
|
|
1609
|
+
if (letter !== 0) {
|
|
1610
|
+
if (at < 0 || at + 1 >= cap) {
|
|
1611
|
+
w.failed = JSON_WRITE_FULL
|
|
1612
|
+
return false
|
|
1613
|
+
}
|
|
1614
|
+
buf[at] = toU8(JSON_BACKSLASH)
|
|
1615
|
+
buf[at + 1] = toU8(letter)
|
|
1616
|
+
at += 2
|
|
1617
|
+
continue
|
|
1618
|
+
}
|
|
1619
|
+
if (at < 0 || at + 5 >= cap) {
|
|
1620
|
+
w.failed = JSON_WRITE_FULL
|
|
1621
|
+
return false
|
|
1622
|
+
}
|
|
1623
|
+
buf[at] = toU8(JSON_BACKSLASH)
|
|
1624
|
+
buf[at + 1] = toU8(JSON_LETTER_U)
|
|
1625
|
+
buf[at + 2] = toU8(JSON_DIGIT_ZERO)
|
|
1626
|
+
buf[at + 3] = toU8(JSON_DIGIT_ZERO)
|
|
1627
|
+
buf[at + 4] = toU8(jsonHexByte(code >> 4))
|
|
1628
|
+
buf[at + 5] = toU8(jsonHexByte(code & 15))
|
|
1629
|
+
at += 6
|
|
1630
|
+
}
|
|
1631
|
+
if (at >= cap) {
|
|
1632
|
+
w.failed = JSON_WRITE_FULL
|
|
1633
|
+
return false
|
|
1634
|
+
}
|
|
1635
|
+
buf[at] = toU8(JSON_QUOTE)
|
|
1636
|
+
w.end = at + 1
|
|
1637
|
+
return true
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
/**
|
|
1641
|
+
* The decimal digits of `v`, then `suffix` as one more digit when it is not
|
|
1642
|
+
* negative, at the cursor; all of them or, with `JSON_WRITE_FULL`, none.
|
|
1643
|
+
*
|
|
1644
|
+
* The digits are counted first and then written right to left, straight into
|
|
1645
|
+
* the buffer. A negative `v` is never negated, so `-2147483648` needs no case
|
|
1646
|
+
* of its own: each step takes the remainder, which is zero or negative, and
|
|
1647
|
+
* divides what is left exactly. The exact division is also what keeps the
|
|
1648
|
+
* step the same under Node, where `/` does not truncate.
|
|
1649
|
+
*/
|
|
1650
|
+
const jsonWriterPutDigits = (w: JsonWriter, v: i32, suffix: i32): boolean => {
|
|
1651
|
+
let digits: i32 = 1
|
|
1652
|
+
let rest: i32 = v
|
|
1653
|
+
while (rest <= -JSON_DECIMAL_BASE || rest >= JSON_DECIMAL_BASE) {
|
|
1654
|
+
rest = (rest - (rest % JSON_DECIMAL_BASE)) / JSON_DECIMAL_BASE
|
|
1655
|
+
digits += 1
|
|
1656
|
+
}
|
|
1657
|
+
const sign: i32 = v < 0 ? 1 : 0
|
|
1658
|
+
const extra: i32 = suffix >= 0 ? 1 : 0
|
|
1659
|
+
const buf: u8[] = w.buf
|
|
1660
|
+
const at: i32 = w.end
|
|
1661
|
+
const width: i32 = sign + digits + extra
|
|
1662
|
+
if (at < 0 || at > toI32(buf.length) - width) {
|
|
1663
|
+
w.failed = JSON_WRITE_FULL
|
|
1664
|
+
return false
|
|
1665
|
+
}
|
|
1666
|
+
if (sign === 1) {
|
|
1667
|
+
buf[at] = toU8(JSON_MINUS)
|
|
1668
|
+
}
|
|
1669
|
+
if (extra === 1 && at + width - 1 < toI32(buf.length) && at + width - 1 >= 0) {
|
|
1670
|
+
buf[at + width - 1] = toU8(JSON_DIGIT_ZERO + suffix)
|
|
1671
|
+
}
|
|
1672
|
+
rest = v
|
|
1673
|
+
for (let k: i32 = at + sign + digits - 1; k >= at + sign && k >= 0 && k < toI32(buf.length); k -= 1) {
|
|
1674
|
+
const digit: i32 = rest % JSON_DECIMAL_BASE
|
|
1675
|
+
buf[k] = toU8(JSON_DIGIT_ZERO + (digit < 0 ? -digit : digit))
|
|
1676
|
+
rest = (rest - digit) / JSON_DECIMAL_BASE
|
|
1677
|
+
}
|
|
1678
|
+
w.end = at + width
|
|
1679
|
+
return true
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
/** `jsonWriterPutDigits` in 64 bits, for `i64`, whose minimum likewise needs no case of its own. */
|
|
1683
|
+
const jsonWriterPutDigits64 = (w: JsonWriter, v: i64): boolean => {
|
|
1684
|
+
// Built with `toI64`: under Node an `i64` is a BigInt, and a bare `10` would
|
|
1685
|
+
// stay a number that `%` and `/` refuse to mix with one.
|
|
1686
|
+
const ten: i64 = toI64(10)
|
|
1687
|
+
let digits: i32 = 1
|
|
1688
|
+
let rest: i64 = v
|
|
1689
|
+
while (rest <= -ten || rest >= ten) {
|
|
1690
|
+
rest = rest / ten
|
|
1691
|
+
digits += 1
|
|
1692
|
+
}
|
|
1693
|
+
const sign: i32 = v < 0 ? 1 : 0
|
|
1694
|
+
const buf: u8[] = w.buf
|
|
1695
|
+
const at: i32 = w.end
|
|
1696
|
+
const width: i32 = sign + digits
|
|
1697
|
+
if (at < 0 || at > toI32(buf.length) - width) {
|
|
1698
|
+
w.failed = JSON_WRITE_FULL
|
|
1699
|
+
return false
|
|
1700
|
+
}
|
|
1701
|
+
if (sign === 1) {
|
|
1702
|
+
buf[at] = toU8(JSON_MINUS)
|
|
1703
|
+
}
|
|
1704
|
+
rest = v
|
|
1705
|
+
for (let k: i32 = at + width - 1; k >= at + sign && k >= 0 && k < toI32(buf.length); k -= 1) {
|
|
1706
|
+
const digit: i32 = toI32(rest % ten)
|
|
1707
|
+
buf[k] = toU8(JSON_DIGIT_ZERO + (digit < 0 ? -digit : digit))
|
|
1708
|
+
rest = rest / ten
|
|
1709
|
+
}
|
|
1710
|
+
w.end = at + width
|
|
1711
|
+
return true
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
/**
|
|
1715
|
+
* The digits `${v}` spells for a finite `v`, at the cursor. They are a string
|
|
1716
|
+
* the runtime builds, and nothing holds it once its bytes are copied, so the
|
|
1717
|
+
* function's automatic arena scope (LANGUAGE.md, "Memory model", item 2)
|
|
1718
|
+
* gives it back before the call returns: a loop of `f64` writes keeps
|
|
1719
|
+
* `Arena.used()` flat like every other write, and `tests/link/std_json_writer`
|
|
1720
|
+
* measures it. The scope is the compiler's rather than a `using a = arena()`
|
|
1721
|
+
* block because this module also runs under Node, which `tests/run.js` holds
|
|
1722
|
+
* to `JSON.stringify`, and Node 22 does not parse `using`.
|
|
1723
|
+
*/
|
|
1724
|
+
const jsonWriterPutF64 = (w: JsonWriter, v: f64): boolean => jsonWriterPutText(w, `${v}`)
|
|
1725
|
+
|
|
1726
|
+
/**
|
|
1727
|
+
* `s` as `JSON.stringify(s)` writes it — quoted, with the escapes
|
|
1728
|
+
* `JsonWriter.string` writes — as a string. A thin layer on the writer: it
|
|
1729
|
+
* sizes a buffer to fit exactly, writes into it and reads it back. A string
|
|
1730
|
+
* with nothing to escape skips the writer and is only quoted.
|
|
1731
|
+
*/
|
|
1732
|
+
export const jsonStringifyString = (s: string): string => {
|
|
1733
|
+
const n: i32 = toI32(s.length)
|
|
1734
|
+
let width: i32 = 2
|
|
1735
|
+
for (let i: i32 = 0; i < n; i += 1) {
|
|
1736
|
+
width += jsonEscapedWidth(toI32(s.charCodeAt(i)))
|
|
1737
|
+
}
|
|
1738
|
+
if (width === n + 2) {
|
|
1739
|
+
return `"${s}"`
|
|
1740
|
+
}
|
|
1741
|
+
const w: JsonWriter = new JsonWriter(new Array<u8>(width), 0)
|
|
1742
|
+
w.string(s)
|
|
1743
|
+
return w.text()
|
|
402
1744
|
}
|