@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.
Files changed (59) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +215 -18
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +4 -4
  14. package/runtime/runtime-wasm.c +5 -3
  15. package/runtime/runtime.c +69 -13
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +223 -22
  18. package/std/README.md +81 -51
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +932 -61
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1120 -34
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +211 -31
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-listener.ts +88 -7
  45. package/std/net/quic-packet.ts +243 -82
  46. package/std/net/quic-recovery.ts +361 -30
  47. package/std/net/quic-stream.ts +424 -24
  48. package/std/net/quic.ts +519 -167
  49. package/std/net/router.ts +676 -0
  50. package/std/net/tls/codec.ts +426 -27
  51. package/std/net/tls/record-server.ts +478 -132
  52. package/std/net/tls/record.ts +133 -42
  53. package/std/net/tls/schedule.ts +464 -55
  54. package/std/net/tls/ticket.ts +610 -0
  55. package/std/net/tls-tcp.ts +196 -53
  56. package/std/net/tls.ts +1018 -260
  57. package/std/net/url.ts +571 -0
  58. package/std/random.ts +94 -0
  59. 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
- * It exists because the compiler's own machine-readable surface is JSON: under
5
- * `--json` every diagnostic is one flat object on a line of stdout, whose `code`
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 this is a **reader, not a parser**, and the difference is worth stating:
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
- * There are two exported functions, and few on purpose. A `std/` module is
30
- * compiled into the program that imports it and the symbol namespace is flat
31
- * (`std/README.md`), so every name here is a name its importer cannot use: the
32
- * helpers are `json`-prefixed and the readers are the only exports. Splitting a
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
- while (i < length) {
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
- return -1
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
- * The string skip inside an object is a flag in the one loop rather than a call
163
- * to `jsonEndOfString` that moves the cursor to wherever it answers: a cursor
164
- * that only ever steps forward keeps the lower bound the bounds proof needs,
165
- * and one assigned a callee's answer does not.
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
- while (i < length) {
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 (inString) {
183
- // `jsonEndOfString`'s rule: a backslash takes the byte after it along.
184
- if (code === JSON_BACKSLASH) {
185
- i += 2
186
- continue
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 (code === JSON_QUOTE) {
189
- inString = false
315
+ if (depth === 0) {
316
+ return close + 1
190
317
  }
191
- i += 1
318
+ i = close + 1
192
319
  continue
193
320
  }
194
- if (code === JSON_QUOTE) {
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 `jsonEndOfString` found, so
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
+ }