@amritk/nish-x86_64-linux 0.18.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 +215 -18
- 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 +4 -4
- package/runtime/runtime-wasm.c +5 -3
- package/runtime/runtime.c +69 -13
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +223 -22
- package/std/README.md +81 -51
- 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 +932 -61
- 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 +1120 -34
- 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 +211 -31
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +88 -7
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- 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 +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/threads.ts +438 -14
package/std/json.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `std/json` — the value of a field, or of several, of one flat JSON object
|
|
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
|
-
* compiled into 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.
|
|
@@ -56,6 +58,50 @@
|
|
|
56
58
|
* calls it keeps its per-pass arena release, as one calling `jsonField` does;
|
|
57
59
|
* `jsonFields`' own comment says when that holds.
|
|
58
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
|
+
*
|
|
59
105
|
* Every offset here is a **byte** offset and every width is spelled, with each
|
|
60
106
|
* length and byte a builtin answers read through `toI32` — `.length` and
|
|
61
107
|
* `charCodeAt` answer `number`, which is `f64` under `--number-mode f64`, so the
|
|
@@ -123,19 +169,74 @@ const jsonSkipBlank = (text: string, from: i32): i32 => {
|
|
|
123
169
|
return i
|
|
124
170
|
}
|
|
125
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
|
+
|
|
126
222
|
/**
|
|
127
223
|
* The index just past the string literal whose opening quote is at `at`, or `-1`
|
|
128
224
|
* when the quote is never closed. A backslash takes the byte after it with it,
|
|
129
225
|
* which is all a scanner needs to know about escapes: `\"` cannot end the
|
|
130
|
-
* 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.
|
|
131
230
|
*/
|
|
132
231
|
const jsonEndOfString = (text: string, at: i32): i32 => {
|
|
133
232
|
const length: i32 = toI32(text.length)
|
|
134
233
|
if (at < 0) {
|
|
135
234
|
return -1
|
|
136
235
|
}
|
|
236
|
+
const stop: i32 = at + 1 + JSON_INLINE_SCAN < length ? at + 1 + JSON_INLINE_SCAN : length
|
|
137
237
|
let i: i32 = at + 1
|
|
138
|
-
|
|
238
|
+
// `i < length` follows from `i < stop`, and is there for the bounds proof.
|
|
239
|
+
while (i < stop && i < length) {
|
|
139
240
|
const code: i32 = toI32(text.charCodeAt(i))
|
|
140
241
|
if (code === JSON_BACKSLASH) {
|
|
141
242
|
i += 2
|
|
@@ -146,7 +247,11 @@ const jsonEndOfString = (text: string, at: i32): i32 => {
|
|
|
146
247
|
}
|
|
147
248
|
i += 1
|
|
148
249
|
}
|
|
149
|
-
|
|
250
|
+
if (i >= length) {
|
|
251
|
+
return -1
|
|
252
|
+
}
|
|
253
|
+
const quote: i32 = jsonClosingQuote(text, i)
|
|
254
|
+
return quote < 0 ? -1 : quote + 1
|
|
150
255
|
}
|
|
151
256
|
|
|
152
257
|
/**
|
|
@@ -159,10 +264,13 @@ const jsonEndOfString = (text: string, at: i32): i32 => {
|
|
|
159
264
|
* ends at the first byte that cannot be part of it, which is the comma, the
|
|
160
265
|
* closer or the blank that follows.
|
|
161
266
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* 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.
|
|
166
274
|
*/
|
|
167
275
|
const jsonEndOfValue = (text: string, at: i32): i32 => {
|
|
168
276
|
const length: i32 = toI32(text.length)
|
|
@@ -170,30 +278,47 @@ const jsonEndOfValue = (text: string, at: i32): i32 => {
|
|
|
170
278
|
return -1
|
|
171
279
|
}
|
|
172
280
|
const first: i32 = toI32(text.charCodeAt(at))
|
|
173
|
-
if (first === JSON_QUOTE) {
|
|
174
|
-
return jsonEndOfString(text, at)
|
|
175
|
-
}
|
|
176
|
-
if (first === JSON_OPEN_BRACE || first === JSON_OPEN_BRACKET) {
|
|
281
|
+
if (first === JSON_QUOTE || first === JSON_OPEN_BRACE || first === JSON_OPEN_BRACKET) {
|
|
177
282
|
let depth: i32 = 0
|
|
178
|
-
let inString: boolean = false
|
|
179
283
|
let i: i32 = at
|
|
180
|
-
|
|
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) {
|
|
181
289
|
const code: i32 = toI32(text.charCodeAt(i))
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
305
|
+
}
|
|
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
|
+
}
|
|
187
314
|
}
|
|
188
|
-
if (
|
|
189
|
-
|
|
315
|
+
if (depth === 0) {
|
|
316
|
+
return close + 1
|
|
190
317
|
}
|
|
191
|
-
i
|
|
318
|
+
i = close + 1
|
|
192
319
|
continue
|
|
193
320
|
}
|
|
194
|
-
if (code ===
|
|
195
|
-
inString = true
|
|
196
|
-
} else if (code === JSON_OPEN_BRACE || code === JSON_OPEN_BRACKET) {
|
|
321
|
+
if (code === JSON_OPEN_BRACE || code === JSON_OPEN_BRACKET) {
|
|
197
322
|
depth += 1
|
|
198
323
|
} else if (code === JSON_CLOSE_BRACE || code === JSON_CLOSE_BRACKET) {
|
|
199
324
|
depth -= 1
|
|
@@ -317,7 +442,7 @@ const jsonUtf8Byte = (code: i32, width: i32, k: i32): i32 => {
|
|
|
317
442
|
* reading of anything else.
|
|
318
443
|
*/
|
|
319
444
|
const jsonUnescape = (text: string, at: i32, end: i32): string => {
|
|
320
|
-
// Both callers pass the inside of a literal
|
|
445
|
+
// Both callers pass the inside of a literal the scanners found, so
|
|
321
446
|
// `0 <= at` and `end <= text.length` always hold. Saying so here is what
|
|
322
447
|
// proves every read below in range and drops the `substring` clamps.
|
|
323
448
|
if (at < 0 || end > toI32(text.length)) {
|
|
@@ -656,3 +781,964 @@ export const jsonFields = (object: string, names: string[]): (string | null)[] =
|
|
|
656
781
|
}
|
|
657
782
|
return values
|
|
658
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
|
|
982
|
+
}
|
|
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)
|
|
987
|
+
}
|
|
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()
|
|
1744
|
+
}
|