@amritk/nish-x86_64-linux 0.18.0 → 0.21.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 (104) hide show
  1. package/INSTALL.md +58 -28
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2759 -0
  4. package/package.json +3 -2
  5. package/runtime/browser.d.ts +14 -0
  6. package/runtime/browser.mjs +88 -0
  7. package/runtime/core/arrays.d.ts +18 -0
  8. package/runtime/core/arrays.mjs +125 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +70 -0
  11. package/runtime/core/host.mjs +311 -0
  12. package/runtime/core/numbers.d.ts +53 -0
  13. package/runtime/core/numbers.mjs +316 -0
  14. package/runtime/core/result.d.ts +41 -0
  15. package/runtime/core/result.mjs +127 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +99 -5
  22. package/runtime/nish.h +289 -19
  23. package/runtime/nish.mjs +39 -3
  24. package/runtime/node.mjs +383 -0
  25. package/runtime/runtime-aes.c +490 -0
  26. package/runtime/runtime-bytes.c +401 -0
  27. package/runtime/runtime-cross.c +366 -0
  28. package/runtime/runtime-host.c +8 -7
  29. package/runtime/runtime-net.c +93 -34
  30. package/runtime/runtime-os.c +214 -16
  31. package/runtime/runtime-parallel.c +590 -107
  32. package/runtime/runtime-simd.c +235 -11
  33. package/runtime/runtime-term.c +504 -0
  34. package/runtime/runtime-wasm.c +5 -14
  35. package/runtime/runtime.c +257 -17
  36. package/runtime/shim.mjs +6 -915
  37. package/runtime/sqlite/LICENSE.md +85 -0
  38. package/runtime/sqlite/README.md +45 -0
  39. package/runtime/sqlite/nish-sqlite.c +359 -0
  40. package/runtime/sqlite/sqlite3.c +271131 -0
  41. package/runtime/sqlite/sqlite3.h +14447 -0
  42. package/scripts/build.sh +437 -28
  43. package/std/LICENSE-fast-float +23 -0
  44. package/std/LICENSE-fdlibm +8 -0
  45. package/std/README.md +108 -58
  46. package/std/base64.ts +116 -0
  47. package/std/bytes.ts +419 -0
  48. package/std/cli.ts +328 -0
  49. package/std/collections.ts +6 -5
  50. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  51. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  52. package/std/crypto/aes.ts +238 -76
  53. package/std/crypto/base64url.ts +125 -0
  54. package/std/crypto/bcrypt.ts +760 -0
  55. package/std/crypto/chacha20poly1305.ts +43 -9
  56. package/std/crypto/ed25519.ts +840 -0
  57. package/std/crypto/hkdf.ts +304 -12
  58. package/std/crypto/hmac.ts +382 -54
  59. package/std/crypto/p256.ts +932 -61
  60. package/std/crypto/x25519.ts +55 -37
  61. package/std/crypto/x509-random.ts +71 -0
  62. package/std/crypto/x509.ts +20 -131
  63. package/std/json-tokenizer.ts +4237 -0
  64. package/std/json-typed.ts +1014 -0
  65. package/std/json-value.ts +1182 -0
  66. package/std/json.ts +1202 -34
  67. package/std/math.ts +608 -0
  68. package/std/net/buffer-pool.ts +151 -0
  69. package/std/net/hpack.ts +416 -112
  70. package/std/net/http-fields.ts +93 -0
  71. package/std/net/http1-server.ts +501 -62
  72. package/std/net/http1.ts +31 -6
  73. package/std/net/http2-tls.ts +112 -24
  74. package/std/net/http2.ts +333 -75
  75. package/std/net/http3-server.ts +261 -43
  76. package/std/net/http3.ts +187 -28
  77. package/std/net/postgres.ts +1875 -0
  78. package/std/net/prefork.ts +412 -0
  79. package/std/net/quic-listener.ts +108 -11
  80. package/std/net/quic-packet.ts +243 -82
  81. package/std/net/quic-recovery.ts +361 -30
  82. package/std/net/quic-stream.ts +424 -24
  83. package/std/net/quic.ts +519 -167
  84. package/std/net/router.ts +676 -0
  85. package/std/net/tls/codec.ts +426 -27
  86. package/std/net/tls/record-server.ts +478 -132
  87. package/std/net/tls/record.ts +133 -42
  88. package/std/net/tls/schedule.ts +464 -55
  89. package/std/net/tls/ticket.ts +610 -0
  90. package/std/net/tls-tcp.ts +196 -53
  91. package/std/net/tls.ts +1018 -260
  92. package/std/net/url.ts +571 -0
  93. package/std/random.ts +94 -0
  94. package/std/regex.ts +4274 -0
  95. package/std/sqlite.ts +391 -0
  96. package/std/term-io.ts +978 -0
  97. package/std/term-pixels.ts +874 -0
  98. package/std/term-probe.ts +459 -0
  99. package/std/term-rate.ts +993 -0
  100. package/std/term.ts +2453 -0
  101. package/std/text.ts +90 -7
  102. package/std/threads.ts +438 -14
  103. package/std/yaml-lines.ts +141 -0
  104. package/std/yaml.ts +4675 -0
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
@@ -63,6 +109,8 @@
63
109
  * (`docs/wp26-stdlib.md` §4).
64
110
  */
65
111
 
112
+ import { copyBytes, skipJsonPlain } from "nish/bytes"
113
+
66
114
  const JSON_BACKSPACE: i32 = 8
67
115
  const JSON_TAB: i32 = 9
68
116
  const JSON_NEWLINE: i32 = 10
@@ -123,19 +171,74 @@ const jsonSkipBlank = (text: string, from: i32): i32 => {
123
171
  return i
124
172
  }
125
173
 
174
+ /**
175
+ * How many bytes of a string body the scanners read one at a time before they
176
+ * hand the rest to `jsonClosingQuote`. A key or a short value ends inside
177
+ * them, and for those a call into the runtime costs more than the bytes do.
178
+ */
179
+ const JSON_INLINE_SCAN: i32 = 16
180
+
181
+ /**
182
+ * The index of the closing quote of a string body that has run on past the
183
+ * bytes the scanners read one at a time, searching from `start`, a byte no
184
+ * backslash has taken; `-1` when no quote closes it.
185
+ *
186
+ * The body is not walked: `indexOf` jumps to the next quote, which is one
187
+ * `memchr` in the runtime, and the run of backslashes just before that quote
188
+ * says whether it is escaped. The byte before the run is not a backslash — at
189
+ * the latest it is the opening quote — so the run starts where an escape can:
190
+ * an odd run ends in a backslash that takes this quote, and an even one is
191
+ * pairs of `\\` that leave it alone. The run may reach back past `start`, and
192
+ * that changes nothing: what lies before `start` ends on a byte no escape took,
193
+ * so any backslashes there are whole pairs. An escaped quote moves the search
194
+ * one byte past it, and the next run's count stops at that quote, so every
195
+ * backslash is counted once.
196
+ *
197
+ * The byte loops stay in the scanners rather than in here: most strings end
198
+ * in them and never make this call.
199
+ */
200
+ const jsonClosingQuote = (text: string, start: i32): i32 => {
201
+ const length: i32 = toI32(text.length)
202
+ if (start < 0) {
203
+ return -1
204
+ }
205
+ let quote: i32 = toI32(text.indexOf('"', start))
206
+ // `quote >= start` is false for the `-1` of a miss.
207
+ while (quote >= start && quote < length) {
208
+ // The count stops at the opening quote at the latest, so `j >= 0` always
209
+ // holds, and `j < length` does because `j` starts below `quote`; both are
210
+ // spelled out because the bounds proof reads neither from a decrement of a
211
+ // callee's answer.
212
+ let j: i32 = quote - 1
213
+ while (j >= 0 && j < length && toI32(text.charCodeAt(j)) === JSON_BACKSLASH) {
214
+ j -= 1
215
+ }
216
+ if (((quote - 1 - j) & 1) === 0) {
217
+ return quote
218
+ }
219
+ quote = toI32(text.indexOf('"', quote + 1))
220
+ }
221
+ return -1
222
+ }
223
+
126
224
  /**
127
225
  * The index just past the string literal whose opening quote is at `at`, or `-1`
128
226
  * when the quote is never closed. A backslash takes the byte after it with it,
129
227
  * which is all a scanner needs to know about escapes: `\"` cannot end the
130
- * literal and `\\` cannot make the next quote an escape.
228
+ * literal and `\\` cannot make the next quote an escape. The first
229
+ * `JSON_INLINE_SCAN` bytes are read here and the rest searched by
230
+ * `jsonClosingQuote`; the step over an escape can take the cursor one past
231
+ * them, which is still a byte no backslash has taken.
131
232
  */
132
233
  const jsonEndOfString = (text: string, at: i32): i32 => {
133
234
  const length: i32 = toI32(text.length)
134
235
  if (at < 0) {
135
236
  return -1
136
237
  }
238
+ const stop: i32 = at + 1 + JSON_INLINE_SCAN < length ? at + 1 + JSON_INLINE_SCAN : length
137
239
  let i: i32 = at + 1
138
- while (i < length) {
240
+ // `i < length` follows from `i < stop`, and is there for the bounds proof.
241
+ while (i < stop && i < length) {
139
242
  const code: i32 = toI32(text.charCodeAt(i))
140
243
  if (code === JSON_BACKSLASH) {
141
244
  i += 2
@@ -146,7 +249,11 @@ const jsonEndOfString = (text: string, at: i32): i32 => {
146
249
  }
147
250
  i += 1
148
251
  }
149
- return -1
252
+ if (i >= length) {
253
+ return -1
254
+ }
255
+ const quote: i32 = jsonClosingQuote(text, i)
256
+ return quote < 0 ? -1 : quote + 1
150
257
  }
151
258
 
152
259
  /**
@@ -159,10 +266,13 @@ const jsonEndOfString = (text: string, at: i32): i32 => {
159
266
  * ends at the first byte that cannot be part of it, which is the comma, the
160
267
  * closer or the blank that follows.
161
268
  *
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.
269
+ * A string value goes through the same loop as a string inside an object, and
270
+ * ends where its closing quote leaves the loop at depth zero. Each string is
271
+ * read as `jsonEndOfString` reads one, its first `JSON_INLINE_SCAN` bytes a
272
+ * byte at a time and the rest searched by `jsonClosingQuote`, and the byte loop
273
+ * is written out here rather than called: a call to `jsonEndOfString` from
274
+ * a second site is one LLVM stops inlining into `jsonReadMember`, and a call
275
+ * per string is what a line of short strings feels most.
166
276
  */
167
277
  const jsonEndOfValue = (text: string, at: i32): i32 => {
168
278
  const length: i32 = toI32(text.length)
@@ -170,30 +280,47 @@ const jsonEndOfValue = (text: string, at: i32): i32 => {
170
280
  return -1
171
281
  }
172
282
  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) {
283
+ if (first === JSON_QUOTE || first === JSON_OPEN_BRACE || first === JSON_OPEN_BRACKET) {
177
284
  let depth: i32 = 0
178
- let inString: boolean = false
179
285
  let i: i32 = at
180
- while (i < length) {
286
+ // `i >= 0` always holds — `i` starts at `at` and only moves forward — and
287
+ // is in the test because a long string moves `i` to where
288
+ // `jsonClosingQuote` answers, and a cursor assigned a callee's answer is
289
+ // one the bounds proof stops following.
290
+ while (i >= 0 && i < length) {
181
291
  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
292
+ if (code === JSON_QUOTE) {
293
+ const stop: i32 = i + 1 + JSON_INLINE_SCAN < length ? i + 1 + JSON_INLINE_SCAN : length
294
+ let j: i32 = i + 1
295
+ let close: i32 = -1
296
+ while (j < stop && j < length) {
297
+ const inner: i32 = toI32(text.charCodeAt(j))
298
+ if (inner === JSON_BACKSLASH) {
299
+ j += 2
300
+ continue
301
+ }
302
+ if (inner === JSON_QUOTE) {
303
+ close = j
304
+ break
305
+ }
306
+ j += 1
187
307
  }
188
- if (code === JSON_QUOTE) {
189
- inString = false
308
+ if (close < 0) {
309
+ if (j >= length) {
310
+ return -1
311
+ }
312
+ close = jsonClosingQuote(text, j)
313
+ if (close < 0) {
314
+ return -1
315
+ }
190
316
  }
191
- i += 1
317
+ if (depth === 0) {
318
+ return close + 1
319
+ }
320
+ i = close + 1
192
321
  continue
193
322
  }
194
- if (code === JSON_QUOTE) {
195
- inString = true
196
- } else if (code === JSON_OPEN_BRACE || code === JSON_OPEN_BRACKET) {
323
+ if (code === JSON_OPEN_BRACE || code === JSON_OPEN_BRACKET) {
197
324
  depth += 1
198
325
  } else if (code === JSON_CLOSE_BRACE || code === JSON_CLOSE_BRACKET) {
199
326
  depth -= 1
@@ -317,7 +444,7 @@ const jsonUtf8Byte = (code: i32, width: i32, k: i32): i32 => {
317
444
  * reading of anything else.
318
445
  */
319
446
  const jsonUnescape = (text: string, at: i32, end: i32): string => {
320
- // Both callers pass the inside of a literal `jsonEndOfString` found, so
447
+ // Both callers pass the inside of a literal the scanners found, so
321
448
  // `0 <= at` and `end <= text.length` always hold. Saying so here is what
322
449
  // proves every read below in range and drops the `substring` clamps.
323
450
  if (at < 0 || end > toI32(text.length)) {
@@ -656,3 +783,1044 @@ export const jsonFields = (object: string, names: string[]): (string | null)[] =
656
783
  }
657
784
  return values
658
785
  }
786
+
787
+ // ---- The writer ---------------------------------------------------------------
788
+
789
+ /** `JsonWriter.failed` while every call so far has been a well-formed write that fitted. */
790
+ export const JSON_WRITE_OK: i32 = 0
791
+ /** A value, an opener or a `raw` fragment, written in an object where a key is due. */
792
+ export const JSON_WRITE_KEY_EXPECTED: i32 = -1
793
+ /** `key` outside an object or straight after another key, or `endObject` straight after a key. */
794
+ export const JSON_WRITE_VALUE_EXPECTED: i32 = -2
795
+ /** `endObject` or `endArray` with no container open, or closing the other kind. */
796
+ export const JSON_WRITE_UNBALANCED: i32 = -3
797
+ /** A `beginObject` or `beginArray` that would nest deeper than `JSON_WRITER_MAX_DEPTH`. */
798
+ export const JSON_WRITE_TOO_DEEP: i32 = -4
799
+ /** A write that does not fit between the cursor and `buf.length`; nothing past `buf.length` is touched. */
800
+ export const JSON_WRITE_FULL: i32 = -5
801
+ /** A second value at the top level, after the first was written whole. */
802
+ export const JSON_WRITE_DONE: i32 = -6
803
+ /** A `raw` window outside its array, or a `start` outside the writer's buffer. */
804
+ export const JSON_WRITE_BAD_WINDOW: i32 = -7
805
+ /** An `f64` that is NaN or an infinity, written by a writer `setStrict(true)` made strict. */
806
+ export const JSON_WRITE_NON_FINITE: i32 = -8
807
+
808
+ /**
809
+ * How deep a writer nests objects and arrays. The kind of each open container
810
+ * is one bit of two `i32` fields inside the writer, so a nesting past it is
811
+ * `JSON_WRITE_TOO_DEEP` rather than a stack that grows.
812
+ */
813
+ export const JSON_WRITER_MAX_DEPTH: i32 = 64
814
+ /** The lowest bit, spelled `i32` so that `<<` has an `i32` on its left under `--number-mode f64` too. */
815
+ const JSON_WRITER_BIT: i32 = 1
816
+ /** The shift that moves a word's lowest bit to its highest, and its highest to its lowest with `>>>`. */
817
+ const JSON_WRITER_TOP_BIT: i32 = 31
818
+ /**
819
+ * The widest indent, in spaces: `JSON.stringify` clamps its `space` argument
820
+ * to 10, so a wider one writes what 10 writes. At `JSON_WRITER_MAX_DEPTH` it
821
+ * puts 640 bytes before a line's value, which is the most a line break costs.
822
+ */
823
+ const JSON_WRITER_MAX_INDENT: i32 = 10
824
+
825
+ const JSON_SLASH_B: i32 = 98
826
+ const JSON_SLASH_F: i32 = 102
827
+ const JSON_SLASH_N: i32 = 110
828
+ const JSON_SLASH_R: i32 = 114
829
+ const JSON_SLASH_T: i32 = 116
830
+ const JSON_DIGIT_ZERO: i32 = 48
831
+ const JSON_MINUS: i32 = 45
832
+ const JSON_DECIMAL_BASE: i32 = 10
833
+ const JSON_LOWER_A: i32 = 97
834
+
835
+ /**
836
+ * The letter of the short escape for `code` — `"`, `\`, backspace, form feed,
837
+ * newline, carriage return or tab — or `0` when it has none.
838
+ */
839
+ const jsonShortEscape = (code: i32): i32 => {
840
+ if (code === JSON_QUOTE || code === JSON_BACKSLASH) {
841
+ return code
842
+ }
843
+ if (code === JSON_NEWLINE) {
844
+ return JSON_SLASH_N
845
+ }
846
+ if (code === JSON_TAB) {
847
+ return JSON_SLASH_T
848
+ }
849
+ if (code === JSON_CARRIAGE_RETURN) {
850
+ return JSON_SLASH_R
851
+ }
852
+ if (code === JSON_BACKSPACE) {
853
+ return JSON_SLASH_B
854
+ }
855
+ if (code === JSON_FORM_FEED) {
856
+ return JSON_SLASH_F
857
+ }
858
+ return 0
859
+ }
860
+
861
+ /** One lower-case hex digit, as `JSON.stringify` writes the last two of a `\u00xx`. */
862
+ const jsonHexByte = (nibble: i32): i32 =>
863
+ nibble < JSON_DECIMAL_BASE ? JSON_DIGIT_ZERO + nibble : JSON_LOWER_A + nibble - JSON_DECIMAL_BASE
864
+
865
+ /** Whether byte `code` of a string goes inside the quotes as itself: JSON escapes only `"`, `\` and the bytes below a space. */
866
+ const jsonIsPlainByte = (code: i32): boolean =>
867
+ code >= JSON_SPACE && code !== JSON_QUOTE && code !== JSON_BACKSLASH
868
+
869
+ /**
870
+ * How many bytes byte `code` of a string takes inside the quotes: itself, a
871
+ * two-byte short escape, or the six bytes of `\u00xx`.
872
+ */
873
+ const jsonEscapedWidth = (code: i32): i32 => {
874
+ if (jsonIsPlainByte(code)) {
875
+ return 1
876
+ }
877
+ return jsonShortEscape(code) !== 0 ? 2 : 6
878
+ }
879
+
880
+ /**
881
+ * A JSON text written into a byte buffer the caller owns: `buf[start ..)`,
882
+ * up to `buf.length`, with `end` the cursor. Every comma and colon is the
883
+ * writer's, so the caller says only what the value is:
884
+ *
885
+ * const w = new JsonWriter(out, 0)
886
+ * w.beginObject()
887
+ * w.key("message")
888
+ * w.string("Hello, World!")
889
+ * w.endObject()
890
+ * if (w.complete()) {
891
+ * // out[0 .. w.end) is {"message":"Hello, World!"}
892
+ * }
893
+ *
894
+ * No call allocates except `f64` (whose digits are a string the call
895
+ * releases before it returns) and `text`: a writer reset onto the same buffer
896
+ * for each request keeps `Arena.used()` flat, which `tests/link/std_json_writer`
897
+ * measures over 10,000 writes.
898
+ *
899
+ * Misuse is a sticky code, not a panic and not a `Result` (the module header
900
+ * has the policy). Once `failed` is negative every later call does nothing, so
901
+ * a caller writes the whole value and reads `failed` once at the end.
902
+ */
903
+ export class JsonWriter {
904
+ buf: u8[]
905
+ start: i32 = 0
906
+ /** The cursor: the bytes written are `buf[start .. end)`. */
907
+ end: i32 = 0
908
+ /** `JSON_WRITE_OK`, or the first `JSON_WRITE_*` code a call hit. */
909
+ failed: i32 = 0
910
+ /** How many containers are open. */
911
+ depth: i32 = 0
912
+ /**
913
+ * The kinds of the open containers, one bit each, 1 for an object, as a
914
+ * stack 64 bits deep: the innermost is bit 0 of `objectsLow`, an opener
915
+ * shifts the pair left by one and a closer shifts it back. So the check
916
+ * before every value — is the innermost container an object? — is one
917
+ * `and` of one word, and costs the minified text nothing a cached copy
918
+ * would save. Two `i32` words rather than an `i64` or an array, so that a
919
+ * writer allocates nothing and the module still runs under Node, where an
920
+ * `i64` is a BigInt.
921
+ */
922
+ objectsLow: i32 = 0
923
+ objectsHigh: i32 = 0
924
+ /**
925
+ * What goes before the next key or element of the innermost container,
926
+ * one `JSON_WRITER_SEP_*`: nothing, a comma, and in an indented text a
927
+ * line break or a comma and a line break. The minified text tests it only
928
+ * where it would test a "comma due" flag, against 0 and 1, so the indented
929
+ * states cost it nothing; bit 1 is the indent and every step keeps it.
930
+ */
931
+ sep: i32 = 0
932
+ /**
933
+ * How many `indentByte`s each level of nesting puts at the start of a line,
934
+ * or `0` for the minified text. A mode, not a part of the text: `reset`
935
+ * keeps it, so a writer set up once writes every text the same way.
936
+ */
937
+ indentWidth: i32 = 0
938
+ /** The byte an indent is made of: a space, or a tab after `setIndentTab`. */
939
+ indentByte: i32 = 32
940
+ /** Whether a key has been written in the innermost object and its value is due. */
941
+ afterKey: boolean = false
942
+ /** Whether the top-level value has been written whole. */
943
+ done: boolean = false
944
+ /** Whether a NaN or infinite `f64` is `JSON_WRITE_NON_FINITE` rather than `null`. Kept by `reset`. */
945
+ strict: boolean = false
946
+
947
+ constructor(buf: u8[], start: i32) {
948
+ this.buf = buf
949
+ this.reset(buf, start)
950
+ }
951
+
952
+ /**
953
+ * Starts a new text at `buf[start ..)`, forgetting everything the last one
954
+ * wrote and every error it hit: what a server calls once per response, on
955
+ * the buffer it reuses. A `start` outside `buf` is `JSON_WRITE_BAD_WINDOW`.
956
+ */
957
+ reset(buf: u8[], start: i32): void {
958
+ this.buf = buf
959
+ this.start = start
960
+ this.end = start
961
+ this.depth = 0
962
+ this.objectsLow = 0
963
+ this.objectsHigh = 0
964
+ this.sep = this.indentWidth !== 0 ? JSON_WRITER_SEP_INDENTED : JSON_WRITER_SEP_NONE
965
+ this.afterKey = false
966
+ this.done = false
967
+ this.failed = start < 0 || start > toI32(buf.length) ? JSON_WRITE_BAD_WINDOW : JSON_WRITE_OK
968
+ }
969
+
970
+ /**
971
+ * Indents the text by `spaces` spaces a level, as `JSON.stringify(v, null,
972
+ * spaces)` does: each member and element on a line of its own, one space
973
+ * after a key's colon, and an empty `{}` or `[]` left on one line. `spaces`
974
+ * is clamped to 10 as `JSON.stringify` clamps it, and below 1 is the
975
+ * minified text, the default. A mode that `reset` keeps; set it before the
976
+ * text, since a change in the middle of one applies from the next byte.
977
+ */
978
+ setIndent(spaces: i32): void {
979
+ let width: i32 = spaces
980
+ if (width < 0) {
981
+ width = 0
982
+ } else if (width > JSON_WRITER_MAX_INDENT) {
983
+ width = JSON_WRITER_MAX_INDENT
984
+ }
985
+ this.indentWidth = width
986
+ this.indentByte = JSON_SPACE
987
+ this.sep =
988
+ (this.sep & JSON_WRITER_SEP_COMMA) | (width !== 0 ? JSON_WRITER_SEP_INDENTED : JSON_WRITER_SEP_NONE)
989
+ }
990
+
991
+ /** Indents the text by one tab a level, as `JSON.stringify(v, null, "\t")` does. */
992
+ setIndentTab(): void {
993
+ this.indentWidth = 1
994
+ this.indentByte = JSON_TAB
995
+ this.sep = this.sep | JSON_WRITER_SEP_INDENTED
996
+ }
997
+
998
+ /**
999
+ * Whether an `f64` that is NaN or an infinity fails the text with
1000
+ * `JSON_WRITE_NON_FINITE`, writing nothing, rather than being written as
1001
+ * the `null` `JSON.stringify` writes. Off by default; a mode that `reset`
1002
+ * keeps. Strict is for a caller to whom a `null` where a number was due is
1003
+ * a corrupt record rather than a missing one.
1004
+ */
1005
+ setStrict(strict: boolean): void {
1006
+ this.strict = strict
1007
+ }
1008
+
1009
+ /** How many bytes have been written since `reset`. */
1010
+ written(): i32 {
1011
+ return this.end - this.start
1012
+ }
1013
+
1014
+ /** Whether one whole value has been written, every container closed, and nothing failed. */
1015
+ complete(): boolean {
1016
+ return this.failed === JSON_WRITE_OK && this.done
1017
+ }
1018
+
1019
+ /**
1020
+ * The bytes written so far, `buf[start .. end)`, as a string. A convenience
1021
+ * that allocates a byte at a time, for a caller that wants text rather than
1022
+ * bytes; a server sends `buf` itself.
1023
+ */
1024
+ text(): string {
1025
+ const buf: u8[] = this.buf
1026
+ const parts: string[] = []
1027
+ for (let k: i32 = this.start; k >= 0 && k < this.end && k < toI32(buf.length); k += 1) {
1028
+ parts.push(String.fromCharCode(toI32(buf[k])))
1029
+ }
1030
+ return parts.join("")
1031
+ }
1032
+
1033
+ beginObject(): void {
1034
+ jsonWriterOpen(this, true, JSON_OPEN_BRACE)
1035
+ }
1036
+
1037
+ endObject(): void {
1038
+ if (this.sep === JSON_WRITER_SEP_COMMA_BREAK) {
1039
+ jsonWriterCloseIndented(this, true, JSON_CLOSE_BRACE)
1040
+ } else {
1041
+ jsonWriterClose(this, true, JSON_CLOSE_BRACE)
1042
+ }
1043
+ }
1044
+
1045
+ beginArray(): void {
1046
+ jsonWriterOpen(this, false, JSON_OPEN_BRACKET)
1047
+ }
1048
+
1049
+ endArray(): void {
1050
+ if (this.sep === JSON_WRITER_SEP_COMMA_BREAK) {
1051
+ jsonWriterCloseIndented(this, false, JSON_CLOSE_BRACKET)
1052
+ } else {
1053
+ jsonWriterClose(this, false, JSON_CLOSE_BRACKET)
1054
+ }
1055
+ }
1056
+
1057
+ /** The next member's key, with the comma before it and the colon after it. */
1058
+ key(name: string): void {
1059
+ if (this.failed !== JSON_WRITE_OK) {
1060
+ return
1061
+ }
1062
+ if (this.depth === 0 && this.done) {
1063
+ this.failed = JSON_WRITE_DONE
1064
+ return
1065
+ }
1066
+ if (!jsonWriterInObject(this) || this.afterKey) {
1067
+ this.failed = JSON_WRITE_VALUE_EXPECTED
1068
+ return
1069
+ }
1070
+ const sep: i32 = this.sep
1071
+ if (sep !== JSON_WRITER_SEP_NONE) {
1072
+ if (sep !== JSON_WRITER_SEP_COMMA) {
1073
+ jsonWriterIndented(this, this.depth, name)
1074
+ return
1075
+ }
1076
+ if (!jsonWriterPutByte(this, JSON_COMMA)) {
1077
+ return
1078
+ }
1079
+ }
1080
+ if (jsonWriterPutString(this, name) && jsonWriterPutByte(this, JSON_COLON)) {
1081
+ this.afterKey = true
1082
+ }
1083
+ }
1084
+
1085
+ /** `s` quoted and escaped as `JSON.stringify` escapes it; every other byte goes through as itself. */
1086
+ string(s: string): void {
1087
+ if (jsonWriterValueDue(this) && jsonWriterPutString(this, s)) {
1088
+ jsonWriterValueDone(this)
1089
+ }
1090
+ }
1091
+
1092
+ /**
1093
+ * `key` for a name held as bytes, `bytes[off .. off + len)`, escaped exactly
1094
+ * as `key` escapes a string of those bytes, and allocating nothing: what a
1095
+ * caller that keeps its text in a byte buffer, such as `nish/json-value`'s
1096
+ * `JsonDocument`, writes without making a string per key. A window outside
1097
+ * `bytes` is `JSON_WRITE_BAD_WINDOW`, checked once, before anything is
1098
+ * written.
1099
+ */
1100
+ keyBytes(bytes: u8[], off: i32, len: i32): void {
1101
+ if (this.failed !== JSON_WRITE_OK) {
1102
+ return
1103
+ }
1104
+ if (!jsonWriterWindowOk(this, bytes, off, len)) {
1105
+ return
1106
+ }
1107
+ if (this.depth === 0 && this.done) {
1108
+ this.failed = JSON_WRITE_DONE
1109
+ return
1110
+ }
1111
+ if (!jsonWriterInObject(this) || this.afterKey) {
1112
+ this.failed = JSON_WRITE_VALUE_EXPECTED
1113
+ return
1114
+ }
1115
+ const sep: i32 = this.sep
1116
+ const indented: boolean = sep !== JSON_WRITER_SEP_NONE && sep !== JSON_WRITER_SEP_COMMA
1117
+ if (indented) {
1118
+ if (!jsonWriterBreakBeforeKey(this)) {
1119
+ return
1120
+ }
1121
+ } else if (sep === JSON_WRITER_SEP_COMMA && !jsonWriterPutByte(this, JSON_COMMA)) {
1122
+ return
1123
+ }
1124
+ if (
1125
+ jsonWriterPutBytes(this, bytes, off, len) &&
1126
+ jsonWriterPutByte(this, JSON_COLON) &&
1127
+ (!indented || jsonWriterPutByte(this, JSON_SPACE))
1128
+ ) {
1129
+ this.afterKey = true
1130
+ }
1131
+ }
1132
+
1133
+ /**
1134
+ * `string` for text held as bytes, `bytes[off .. off + len)`: the same
1135
+ * quotes and escapes, byte for byte, and no allocation. A window outside
1136
+ * `bytes` is `JSON_WRITE_BAD_WINDOW`, checked once, before anything is
1137
+ * written.
1138
+ */
1139
+ stringBytes(bytes: u8[], off: i32, len: i32): void {
1140
+ if (this.failed !== JSON_WRITE_OK) {
1141
+ return
1142
+ }
1143
+ if (!jsonWriterWindowOk(this, bytes, off, len)) {
1144
+ return
1145
+ }
1146
+ if (jsonWriterValueDue(this) && jsonWriterPutBytes(this, bytes, off, len)) {
1147
+ jsonWriterValueDone(this)
1148
+ }
1149
+ }
1150
+
1151
+ i32(v: i32): void {
1152
+ if (jsonWriterValueDue(this) && jsonWriterPutDigits(this, v, -1)) {
1153
+ jsonWriterValueDone(this)
1154
+ }
1155
+ }
1156
+
1157
+ /**
1158
+ * One past `i32`'s range is ten digits, so the last digit is split off in
1159
+ * `u32` and the nine before it are an `i32`'s.
1160
+ */
1161
+ u32(v: u32): void {
1162
+ if (!jsonWriterValueDue(this)) {
1163
+ return
1164
+ }
1165
+ // The largest `u32` whose digits fit an `i32`; a module constant cannot be a `u32`.
1166
+ const i32Max: u32 = 2147483647
1167
+ let written: boolean = false
1168
+ if (v <= i32Max) {
1169
+ written = jsonWriterPutDigits(this, toI32(v), -1)
1170
+ } else {
1171
+ const ten: u32 = 10
1172
+ const last: u32 = v % ten
1173
+ written = jsonWriterPutDigits(this, toI32((v - last) / ten), toI32(last))
1174
+ }
1175
+ if (written) {
1176
+ jsonWriterValueDone(this)
1177
+ }
1178
+ }
1179
+
1180
+ /** The digits of `v`, the minimum included: the arithmetic stays on the negative side, where it fits. */
1181
+ i64(v: i64): void {
1182
+ if (jsonWriterValueDue(this) && jsonWriterPutDigits64(this, v)) {
1183
+ jsonWriterValueDone(this)
1184
+ }
1185
+ }
1186
+
1187
+ /**
1188
+ * `v` as `String(v)` spells it, which is `JSON.stringify`'s rule: the
1189
+ * shortest digits that read back as `v`, `-0` as `0`, and `null` for NaN
1190
+ * and both infinities, which JSON cannot spell — or, once `setStrict(true)`,
1191
+ * `JSON_WRITE_NON_FINITE` for those three, with nothing written.
1192
+ */
1193
+ f64(v: f64): void {
1194
+ // `v - v` is `0` for every finite `v`, and NaN for NaN and both infinities.
1195
+ if (v - v !== toF64(0)) {
1196
+ if (!this.strict) {
1197
+ this.nullValue()
1198
+ } else if (jsonWriterValueAllowed(this)) {
1199
+ this.failed = JSON_WRITE_NON_FINITE
1200
+ }
1201
+ } else if (jsonWriterValueDue(this) && jsonWriterPutF64(this, v)) {
1202
+ jsonWriterValueDone(this)
1203
+ }
1204
+ }
1205
+
1206
+ boolean(v: boolean): void {
1207
+ if (jsonWriterValueDue(this) && jsonWriterPutText(this, v ? "true" : "false")) {
1208
+ jsonWriterValueDone(this)
1209
+ }
1210
+ }
1211
+
1212
+ /** `null`. The method is not named `null`, which the parser refuses as a member name. */
1213
+ nullValue(): void {
1214
+ if (jsonWriterValueDue(this) && jsonWriterPutText(this, "null")) {
1215
+ jsonWriterValueDone(this)
1216
+ }
1217
+ }
1218
+
1219
+ /**
1220
+ * `bytes[off .. off + len)`, copied as it is and counted as one value: a
1221
+ * fragment the caller encoded already, such as a cached object. Nothing in
1222
+ * it is checked, so a fragment that is not one whole JSON value makes a
1223
+ * text that is not JSON either. The bytes are copied front to back, so
1224
+ * `bytes` is not the writer's own buffer at a window the copy overtakes.
1225
+ * In an indented text the fragment goes in where a value would, after its
1226
+ * line break and indent, but its own bytes are not re-indented: an object
1227
+ * fragment stays on one line, where `JSON.stringify` would spread it out.
1228
+ */
1229
+ raw(bytes: u8[], off: i32, len: i32): void {
1230
+ if (this.failed !== JSON_WRITE_OK) {
1231
+ return
1232
+ }
1233
+ if (off < 0 || len < 0 || off > toI32(bytes.length) - len) {
1234
+ this.failed = JSON_WRITE_BAD_WINDOW
1235
+ return
1236
+ }
1237
+ if (!jsonWriterValueDue(this)) {
1238
+ return
1239
+ }
1240
+ const buf: u8[] = this.buf
1241
+ const at: i32 = this.end
1242
+ if (at < 0 || at > toI32(buf.length) - len) {
1243
+ this.failed = JSON_WRITE_FULL
1244
+ return
1245
+ }
1246
+ for (let k: i32 = 0; k < len && off + k < toI32(bytes.length) && at + k < toI32(buf.length); k += 1) {
1247
+ if (off + k >= 0 && at + k >= 0) {
1248
+ buf[at + k] = bytes[off + k]
1249
+ }
1250
+ }
1251
+ this.end = at + len
1252
+ jsonWriterValueDone(this)
1253
+ }
1254
+ }
1255
+
1256
+ /** Whether the innermost open container is an object; `false` at the top level. */
1257
+ const jsonWriterInObject = (w: JsonWriter): boolean => (w.objectsLow & JSON_WRITER_BIT) !== 0
1258
+
1259
+ /**
1260
+ * Whether a value may be written now, with the comma before it written when
1261
+ * one is due; `false`, with `failed` set, when the writer has failed, a key
1262
+ * is due, or the top-level value is already whole.
1263
+ */
1264
+ const jsonWriterValueDue = (w: JsonWriter): boolean => {
1265
+ if (w.failed !== JSON_WRITE_OK) {
1266
+ return false
1267
+ }
1268
+ if (w.depth === 0) {
1269
+ if (w.done) {
1270
+ w.failed = JSON_WRITE_DONE
1271
+ return false
1272
+ }
1273
+ return true
1274
+ }
1275
+ if (w.afterKey) {
1276
+ w.afterKey = false
1277
+ return true
1278
+ }
1279
+ if (jsonWriterInObject(w)) {
1280
+ w.failed = JSON_WRITE_KEY_EXPECTED
1281
+ return false
1282
+ }
1283
+ // A comma is the common case, every element but the first, so it is
1284
+ // tested first and costs the one compare a "comma due" flag would.
1285
+ const sep: i32 = w.sep
1286
+ if (sep === JSON_WRITER_SEP_COMMA) {
1287
+ return jsonWriterPutByte(w, JSON_COMMA)
1288
+ }
1289
+ return sep === JSON_WRITER_SEP_NONE || jsonWriterIndented(w, w.depth, "")
1290
+ }
1291
+
1292
+ /**
1293
+ * `jsonWriterValueDue`'s checks without its writes: whether a value may be
1294
+ * written now, with `failed` set to the misuse when not. Strict `f64` asks
1295
+ * it before it fails a non-finite value, so a value where a key is due, or
1296
+ * after the top-level value, keeps the code it has for every other value:
1297
+ * the first misuse wins, and a non-finite value is reported only when it is
1298
+ * the first thing wrong.
1299
+ */
1300
+ const jsonWriterValueAllowed = (w: JsonWriter): boolean => {
1301
+ if (w.failed !== JSON_WRITE_OK) {
1302
+ return false
1303
+ }
1304
+ if (w.depth === 0 && w.done) {
1305
+ w.failed = JSON_WRITE_DONE
1306
+ return false
1307
+ }
1308
+ if (w.depth !== 0 && jsonWriterInObject(w) && !w.afterKey) {
1309
+ w.failed = JSON_WRITE_KEY_EXPECTED
1310
+ return false
1311
+ }
1312
+ return true
1313
+ }
1314
+
1315
+ /** After a whole value: the next element needs a comma, and at the top level the text is done. */
1316
+ const jsonWriterValueDone = (w: JsonWriter): void => {
1317
+ w.sep = w.sep | JSON_WRITER_SEP_COMMA
1318
+ if (w.depth === 0) {
1319
+ w.done = true
1320
+ }
1321
+ }
1322
+
1323
+ /** `beginObject` and `beginArray`: the opener, and a slot for the new container. */
1324
+ const jsonWriterOpen = (w: JsonWriter, object: boolean, opener: i32): void => {
1325
+ if (w.failed === JSON_WRITE_OK && w.depth >= JSON_WRITER_MAX_DEPTH) {
1326
+ w.failed = JSON_WRITE_TOO_DEEP
1327
+ return
1328
+ }
1329
+ if (!jsonWriterValueDue(w) || !jsonWriterPutByte(w, opener)) {
1330
+ return
1331
+ }
1332
+ const low: i32 = w.objectsLow
1333
+ w.objectsHigh = (w.objectsHigh << JSON_WRITER_BIT) | (low >>> JSON_WRITER_TOP_BIT)
1334
+ w.objectsLow = (low << JSON_WRITER_BIT) | (object ? JSON_WRITER_BIT : 0)
1335
+ w.depth = w.depth + 1
1336
+ w.sep = w.sep & JSON_WRITER_SEP_INDENTED
1337
+ }
1338
+
1339
+ /** `endObject` and `endArray`: the closer, when the innermost container is of that kind and not waiting on a value. */
1340
+ const jsonWriterClose = (w: JsonWriter, object: boolean, closer: i32): void => {
1341
+ if (w.failed !== JSON_WRITE_OK) {
1342
+ return
1343
+ }
1344
+ if (w.depth === 0 || jsonWriterInObject(w) !== object) {
1345
+ w.failed = JSON_WRITE_UNBALANCED
1346
+ return
1347
+ }
1348
+ if (w.afterKey) {
1349
+ w.failed = JSON_WRITE_VALUE_EXPECTED
1350
+ return
1351
+ }
1352
+ if (jsonWriterPutByte(w, closer)) {
1353
+ w.depth -= 1
1354
+ const high: i32 = w.objectsHigh
1355
+ w.objectsLow = (w.objectsLow >>> JSON_WRITER_BIT) | ((high & JSON_WRITER_BIT) << JSON_WRITER_TOP_BIT)
1356
+ w.objectsHigh = high >>> JSON_WRITER_BIT
1357
+ jsonWriterValueDone(w)
1358
+ }
1359
+ }
1360
+
1361
+ /**
1362
+ * `endObject` and `endArray` for an indented container that holds anything,
1363
+ * the one state whose closer goes on a line of its own: the line break one
1364
+ * level out, after the checks `jsonWriterClose` makes, which it then makes
1365
+ * again before it writes the closer. A separate entry, chosen by the `sep`
1366
+ * test `end*` makes, rather than a branch inside `jsonWriterClose`, which
1367
+ * stays `main`'s code and small enough for LLVM to inline into every `end*`.
1368
+ */
1369
+ const jsonWriterCloseIndented = (w: JsonWriter, object: boolean, closer: i32): void => {
1370
+ if (
1371
+ w.failed === JSON_WRITE_OK &&
1372
+ w.depth !== 0 &&
1373
+ jsonWriterInObject(w) === object &&
1374
+ !w.afterKey &&
1375
+ !jsonWriterIndented(w, w.depth - 1, "")
1376
+ ) {
1377
+ return
1378
+ }
1379
+ jsonWriterClose(w, object, closer)
1380
+ }
1381
+
1382
+ /** One byte at the cursor, or `JSON_WRITE_FULL` when the buffer has no room for it. */
1383
+ const jsonWriterPutByte = (w: JsonWriter, code: i32): boolean => {
1384
+ const buf: u8[] = w.buf
1385
+ const at: i32 = w.end
1386
+ if (at < 0 || at >= toI32(buf.length)) {
1387
+ w.failed = JSON_WRITE_FULL
1388
+ return false
1389
+ }
1390
+ buf[at] = toU8(code)
1391
+ w.end = at + 1
1392
+ return true
1393
+ }
1394
+
1395
+ /** `JsonWriter.sep` when nothing goes before the next key or element: the first in a minified container. */
1396
+ const JSON_WRITER_SEP_NONE: i32 = 0
1397
+ /** A comma goes first: the minified text after a key's value or an element. */
1398
+ const JSON_WRITER_SEP_COMMA: i32 = 1
1399
+ /** The indent bit, set in every state of an indented text; alone, a line break goes first. */
1400
+ const JSON_WRITER_SEP_INDENTED: i32 = 2
1401
+ /**
1402
+ * A comma and a line break go first: an indented container after it holds
1403
+ * anything, which is also when its closer goes on a line of its own, since
1404
+ * an empty `{}` or `[]` closes on the line it opened on.
1405
+ */
1406
+ const JSON_WRITER_SEP_COMMA_BREAK: i32 = 3
1407
+
1408
+ /**
1409
+ * Everything the indented text writes that the minified one does not: at
1410
+ * `level` — the container's own depth before an element or a key, one less
1411
+ * before its closer — the comma when one is due (never before a closer), a
1412
+ * line break and the indent, then, before a key, the key `name` and `": "`.
1413
+ * It answers whether the caller goes on to write its own bytes: yes for an
1414
+ * element or a closer, no for a key, whose bytes are all written here.
1415
+ *
1416
+ * It is one function, reached only from the indented `JsonWriter.sep`
1417
+ * states, and the minified text never calls it: `key`, `jsonWriterValueDue`
1418
+ * and `jsonWriterClose` test `sep` where they tested a "comma due" flag.
1419
+ * Whether it is placing a closer or a key is read from `level` and the
1420
+ * innermost container rather than passed as a constant, so LLVM cannot fold
1421
+ * the other cases away at a call and inline what is left there, which would
1422
+ * grow those callers past what LLVM inlines into theirs and slow every
1423
+ * minified write.
1424
+ *
1425
+ * The line break and its indent go in whole or, with `JSON_WRITE_FULL`, not
1426
+ * at all; a comma before them may already be written when they do not fit,
1427
+ * as a comma before a minified key may, so a full buffer always leaves a
1428
+ * prefix of the text. The indent is `indentByte` stored `indentWidth *
1429
+ * level` times with one `fill`, so a break allocates nothing however deep it
1430
+ * is.
1431
+ */
1432
+ const jsonWriterIndented = (w: JsonWriter, level: i32, name: string): boolean => {
1433
+ const closing: boolean = level !== w.depth
1434
+ if (!closing && w.sep === JSON_WRITER_SEP_COMMA_BREAK && !jsonWriterPutByte(w, JSON_COMMA)) {
1435
+ return false
1436
+ }
1437
+ const buf: u8[] = w.buf
1438
+ const at: i32 = w.end
1439
+ const stop: i32 = at + 1 + w.indentWidth * level
1440
+ if (at < 0 || stop > toI32(buf.length)) {
1441
+ w.failed = JSON_WRITE_FULL
1442
+ return false
1443
+ }
1444
+ buf[at] = toU8(JSON_NEWLINE)
1445
+ buf.fill(toU8(w.indentByte), at + 1, stop)
1446
+ w.end = stop
1447
+ // A key is the one step that is neither a closer nor an array's element.
1448
+ if (closing || !jsonWriterInObject(w)) {
1449
+ return true
1450
+ }
1451
+ if (jsonWriterPutString(w, name) && jsonWriterPutByte(w, JSON_COLON) && jsonWriterPutByte(w, JSON_SPACE)) {
1452
+ w.afterKey = true
1453
+ }
1454
+ return false
1455
+ }
1456
+
1457
+ /** Whether `bytes[off .. off + len)` lies inside `bytes`, for `keyBytes` and `stringBytes`; `JSON_WRITE_BAD_WINDOW` when not. */
1458
+ const jsonWriterWindowOk = (w: JsonWriter, bytes: u8[], off: i32, len: i32): boolean => {
1459
+ if (off < 0 || len < 0 || off > toI32(bytes.length) - len) {
1460
+ w.failed = JSON_WRITE_BAD_WINDOW
1461
+ return false
1462
+ }
1463
+ return true
1464
+ }
1465
+
1466
+ /**
1467
+ * The comma when one is due, a line break and the indent before a key of an
1468
+ * indented object: `jsonWriterIndented`'s first half, for `keyBytes`, whose
1469
+ * name is bytes rather than the string that function writes. It is a
1470
+ * function of its own rather than a parameter of that one so that the
1471
+ * string path, which every `key` takes, is left exactly as it was. Answers
1472
+ * whether the key may follow; all of it or, with `JSON_WRITE_FULL`, none of
1473
+ * the break.
1474
+ */
1475
+ const jsonWriterBreakBeforeKey = (w: JsonWriter): boolean => {
1476
+ if (w.sep === JSON_WRITER_SEP_COMMA_BREAK && !jsonWriterPutByte(w, JSON_COMMA)) {
1477
+ return false
1478
+ }
1479
+ const buf: u8[] = w.buf
1480
+ const at: i32 = w.end
1481
+ const stop: i32 = at + 1 + w.indentWidth * w.depth
1482
+ if (at < 0 || stop > toI32(buf.length)) {
1483
+ w.failed = JSON_WRITE_FULL
1484
+ return false
1485
+ }
1486
+ buf[at] = toU8(JSON_NEWLINE)
1487
+ buf.fill(toU8(w.indentByte), at + 1, stop)
1488
+ w.end = stop
1489
+ return true
1490
+ }
1491
+
1492
+ /**
1493
+ * The bytes of `text`, which needs no escaping, at the cursor; all of them or,
1494
+ * with `JSON_WRITE_FULL`, none.
1495
+ */
1496
+ const jsonWriterPutText = (w: JsonWriter, text: string): boolean => {
1497
+ const buf: u8[] = w.buf
1498
+ const n: i32 = toI32(text.length)
1499
+ const at: i32 = w.end
1500
+ if (at < 0 || at > toI32(buf.length) - n) {
1501
+ w.failed = JSON_WRITE_FULL
1502
+ return false
1503
+ }
1504
+ for (let k: i32 = 0; k < n && at + k < toI32(buf.length); k += 1) {
1505
+ if (at + k >= 0) {
1506
+ buf[at + k] = toU8(text.charCodeAt(k))
1507
+ }
1508
+ }
1509
+ w.end = at + n
1510
+ return true
1511
+ }
1512
+
1513
+ /**
1514
+ * `s` as a JSON string at the cursor: the quotes, the seven short escapes,
1515
+ * `\u00xx` for the other bytes below `0x20`, and every other byte as itself.
1516
+ * That is `jsonQuote`'s rule in `src/strings.ts`, and `JSON.stringify`'s on a
1517
+ * string that is ASCII. A byte at or above `0x80` is copied, so UTF-8 comes
1518
+ * out as the UTF-8 `JSON.stringify` makes once it is encoded, and a byte that
1519
+ * is not UTF-8 at all comes out raw (the module header says why).
1520
+ *
1521
+ * The cursor is a local and is stored back only when the whole string fits,
1522
+ * so a `JSON_WRITE_FULL` moves nothing.
1523
+ */
1524
+ const jsonWriterPutString = (w: JsonWriter, s: string): boolean => {
1525
+ const buf: u8[] = w.buf
1526
+ const cap: i32 = toI32(buf.length)
1527
+ const n: i32 = toI32(s.length)
1528
+ let at: i32 = w.end
1529
+ if (at < 0 || at >= cap) {
1530
+ w.failed = JSON_WRITE_FULL
1531
+ return false
1532
+ }
1533
+ buf[at] = toU8(JSON_QUOTE)
1534
+ at += 1
1535
+ for (let i: i32 = 0; i < n; i += 1) {
1536
+ const code: i32 = toI32(s.charCodeAt(i))
1537
+ // A plain byte is almost every byte, and costs one test of the room.
1538
+ // Each branch moves the cursor by a constant, which is what lets the
1539
+ // bounds proof keep `at >= 0` from one byte to the next.
1540
+ if (jsonIsPlainByte(code)) {
1541
+ if (at >= cap) {
1542
+ w.failed = JSON_WRITE_FULL
1543
+ return false
1544
+ }
1545
+ buf[at] = toU8(code)
1546
+ at += 1
1547
+ continue
1548
+ }
1549
+ const letter: i32 = jsonShortEscape(code)
1550
+ if (letter !== 0) {
1551
+ if (at < 0 || at + 1 >= cap) {
1552
+ w.failed = JSON_WRITE_FULL
1553
+ return false
1554
+ }
1555
+ buf[at] = toU8(JSON_BACKSLASH)
1556
+ buf[at + 1] = toU8(letter)
1557
+ at += 2
1558
+ continue
1559
+ }
1560
+ if (at < 0 || at + 5 >= cap) {
1561
+ w.failed = JSON_WRITE_FULL
1562
+ return false
1563
+ }
1564
+ buf[at] = toU8(JSON_BACKSLASH)
1565
+ buf[at + 1] = toU8(JSON_LETTER_U)
1566
+ buf[at + 2] = toU8(JSON_DIGIT_ZERO)
1567
+ buf[at + 3] = toU8(JSON_DIGIT_ZERO)
1568
+ buf[at + 4] = toU8(jsonHexByte(code >> 4))
1569
+ buf[at + 5] = toU8(jsonHexByte(code & 15))
1570
+ at += 6
1571
+ }
1572
+ if (at >= cap) {
1573
+ w.failed = JSON_WRITE_FULL
1574
+ return false
1575
+ }
1576
+ buf[at] = toU8(JSON_QUOTE)
1577
+ w.end = at + 1
1578
+ return true
1579
+ }
1580
+
1581
+ /**
1582
+ * The shortest window `jsonWriterPutBytes` hands to `skipJsonPlain` and
1583
+ * `copyBytes`; a shorter one keeps the byte loop, where the two calls cost
1584
+ * more than the bytes they would save (docs/wp38-simd.md, "JsonWriter's byte
1585
+ * writers").
1586
+ */
1587
+ const JSON_PLAIN_RUN_MIN: i32 = 16
1588
+
1589
+ /**
1590
+ * `jsonWriterPutString` over `bytes[off .. off + len)`, a window the caller
1591
+ * has checked: the same quotes and escapes, from the same three helpers, so
1592
+ * the two write the same bytes for the same text. The loop is its own rather
1593
+ * than shared with the string one, so that `string` and `key` keep the code
1594
+ * they had. A window of `JSON_PLAIN_RUN_MIN` bytes or more goes to
1595
+ * `jsonWriterPutRuns` instead.
1596
+ */
1597
+ const jsonWriterPutBytes = (w: JsonWriter, bytes: u8[], off: i32, len: i32): boolean => {
1598
+ if (len >= JSON_PLAIN_RUN_MIN) {
1599
+ return jsonWriterPutRuns(w, bytes, off, len)
1600
+ }
1601
+ const buf: u8[] = w.buf
1602
+ const cap: i32 = toI32(buf.length)
1603
+ let at: i32 = w.end
1604
+ if (at < 0 || at >= cap) {
1605
+ w.failed = JSON_WRITE_FULL
1606
+ return false
1607
+ }
1608
+ buf[at] = toU8(JSON_QUOTE)
1609
+ at += 1
1610
+ const stop: i32 = off + len
1611
+ for (let i: i32 = off; i >= 0 && i < stop && i < toI32(bytes.length); i += 1) {
1612
+ const code: i32 = toI32(bytes[i])
1613
+ if (jsonIsPlainByte(code)) {
1614
+ if (at >= cap) {
1615
+ w.failed = JSON_WRITE_FULL
1616
+ return false
1617
+ }
1618
+ buf[at] = toU8(code)
1619
+ at += 1
1620
+ continue
1621
+ }
1622
+ const letter: i32 = jsonShortEscape(code)
1623
+ if (letter !== 0) {
1624
+ if (at < 0 || at + 1 >= cap) {
1625
+ w.failed = JSON_WRITE_FULL
1626
+ return false
1627
+ }
1628
+ buf[at] = toU8(JSON_BACKSLASH)
1629
+ buf[at + 1] = toU8(letter)
1630
+ at += 2
1631
+ continue
1632
+ }
1633
+ if (at < 0 || at + 5 >= cap) {
1634
+ w.failed = JSON_WRITE_FULL
1635
+ return false
1636
+ }
1637
+ buf[at] = toU8(JSON_BACKSLASH)
1638
+ buf[at + 1] = toU8(JSON_LETTER_U)
1639
+ buf[at + 2] = toU8(JSON_DIGIT_ZERO)
1640
+ buf[at + 3] = toU8(JSON_DIGIT_ZERO)
1641
+ buf[at + 4] = toU8(jsonHexByte(code >> 4))
1642
+ buf[at + 5] = toU8(jsonHexByte(code & 15))
1643
+ at += 6
1644
+ }
1645
+ if (at >= cap) {
1646
+ w.failed = JSON_WRITE_FULL
1647
+ return false
1648
+ }
1649
+ buf[at] = toU8(JSON_QUOTE)
1650
+ w.end = at + 1
1651
+ return true
1652
+ }
1653
+
1654
+ /**
1655
+ * `jsonWriterPutBytes` a run at a time: `skipJsonPlain` finds where the bytes
1656
+ * that need no escape end, `copyBytes` moves them in one copy, and only the
1657
+ * byte the walk stopped at is escaped. The same bytes as the byte loop, the
1658
+ * same all-or-nothing on a full buffer. Variable-time, as the loop is: this
1659
+ * writes text, never key material.
1660
+ */
1661
+ const jsonWriterPutRuns = (w: JsonWriter, bytes: u8[], off: i32, len: i32): boolean => {
1662
+ const buf: u8[] = w.buf
1663
+ const cap: i32 = toI32(buf.length)
1664
+ let at: i32 = w.end
1665
+ if (at < 0 || at >= cap) {
1666
+ w.failed = JSON_WRITE_FULL
1667
+ return false
1668
+ }
1669
+ buf[at] = toU8(JSON_QUOTE)
1670
+ at += 1
1671
+ const stop: i32 = off + len
1672
+ let i: i32 = off
1673
+ while (i >= 0 && i < stop && i < toI32(bytes.length)) {
1674
+ const run: i32 = skipJsonPlain(bytes, i, stop)
1675
+ const count: i32 = run - i
1676
+ if (count > 0) {
1677
+ if (at > cap - count) {
1678
+ w.failed = JSON_WRITE_FULL
1679
+ return false
1680
+ }
1681
+ copyBytes(buf, at, bytes, i, count)
1682
+ at += count
1683
+ i = run
1684
+ }
1685
+ if (i < 0 || i >= stop || i >= toI32(bytes.length)) {
1686
+ break
1687
+ }
1688
+ const code: i32 = toI32(bytes[i])
1689
+ i += 1
1690
+ const letter: i32 = jsonShortEscape(code)
1691
+ if (letter !== 0) {
1692
+ if (at < 0 || at + 1 >= cap) {
1693
+ w.failed = JSON_WRITE_FULL
1694
+ return false
1695
+ }
1696
+ buf[at] = toU8(JSON_BACKSLASH)
1697
+ buf[at + 1] = toU8(letter)
1698
+ at += 2
1699
+ continue
1700
+ }
1701
+ if (at < 0 || at + 5 >= cap) {
1702
+ w.failed = JSON_WRITE_FULL
1703
+ return false
1704
+ }
1705
+ buf[at] = toU8(JSON_BACKSLASH)
1706
+ buf[at + 1] = toU8(JSON_LETTER_U)
1707
+ buf[at + 2] = toU8(JSON_DIGIT_ZERO)
1708
+ buf[at + 3] = toU8(JSON_DIGIT_ZERO)
1709
+ buf[at + 4] = toU8(jsonHexByte(code >> 4))
1710
+ buf[at + 5] = toU8(jsonHexByte(code & 15))
1711
+ at += 6
1712
+ }
1713
+ if (at >= cap) {
1714
+ w.failed = JSON_WRITE_FULL
1715
+ return false
1716
+ }
1717
+ buf[at] = toU8(JSON_QUOTE)
1718
+ w.end = at + 1
1719
+ return true
1720
+ }
1721
+
1722
+ /**
1723
+ * The decimal digits of `v`, then `suffix` as one more digit when it is not
1724
+ * negative, at the cursor; all of them or, with `JSON_WRITE_FULL`, none.
1725
+ *
1726
+ * The digits are counted first and then written right to left, straight into
1727
+ * the buffer. A negative `v` is never negated, so `-2147483648` needs no case
1728
+ * of its own: each step takes the remainder, which is zero or negative, and
1729
+ * divides what is left exactly. The exact division is also what keeps the
1730
+ * step the same under Node, where `/` does not truncate.
1731
+ */
1732
+ const jsonWriterPutDigits = (w: JsonWriter, v: i32, suffix: i32): boolean => {
1733
+ let digits: i32 = 1
1734
+ let rest: i32 = v
1735
+ while (rest <= -JSON_DECIMAL_BASE || rest >= JSON_DECIMAL_BASE) {
1736
+ rest = (rest - (rest % JSON_DECIMAL_BASE)) / JSON_DECIMAL_BASE
1737
+ digits += 1
1738
+ }
1739
+ const sign: i32 = v < 0 ? 1 : 0
1740
+ const extra: i32 = suffix >= 0 ? 1 : 0
1741
+ const buf: u8[] = w.buf
1742
+ const at: i32 = w.end
1743
+ const width: i32 = sign + digits + extra
1744
+ if (at < 0 || at > toI32(buf.length) - width) {
1745
+ w.failed = JSON_WRITE_FULL
1746
+ return false
1747
+ }
1748
+ if (sign === 1) {
1749
+ buf[at] = toU8(JSON_MINUS)
1750
+ }
1751
+ if (extra === 1 && at + width - 1 < toI32(buf.length) && at + width - 1 >= 0) {
1752
+ buf[at + width - 1] = toU8(JSON_DIGIT_ZERO + suffix)
1753
+ }
1754
+ rest = v
1755
+ for (let k: i32 = at + sign + digits - 1; k >= at + sign && k >= 0 && k < toI32(buf.length); k -= 1) {
1756
+ const digit: i32 = rest % JSON_DECIMAL_BASE
1757
+ buf[k] = toU8(JSON_DIGIT_ZERO + (digit < 0 ? -digit : digit))
1758
+ rest = (rest - digit) / JSON_DECIMAL_BASE
1759
+ }
1760
+ w.end = at + width
1761
+ return true
1762
+ }
1763
+
1764
+ /** `jsonWriterPutDigits` in 64 bits, for `i64`, whose minimum likewise needs no case of its own. */
1765
+ const jsonWriterPutDigits64 = (w: JsonWriter, v: i64): boolean => {
1766
+ // Built with `toI64`: under Node an `i64` is a BigInt, and a bare `10` would
1767
+ // stay a number that `%` and `/` refuse to mix with one.
1768
+ const ten: i64 = toI64(10)
1769
+ let digits: i32 = 1
1770
+ let rest: i64 = v
1771
+ while (rest <= -ten || rest >= ten) {
1772
+ rest = rest / ten
1773
+ digits += 1
1774
+ }
1775
+ const sign: i32 = v < 0 ? 1 : 0
1776
+ const buf: u8[] = w.buf
1777
+ const at: i32 = w.end
1778
+ const width: i32 = sign + digits
1779
+ if (at < 0 || at > toI32(buf.length) - width) {
1780
+ w.failed = JSON_WRITE_FULL
1781
+ return false
1782
+ }
1783
+ if (sign === 1) {
1784
+ buf[at] = toU8(JSON_MINUS)
1785
+ }
1786
+ rest = v
1787
+ for (let k: i32 = at + width - 1; k >= at + sign && k >= 0 && k < toI32(buf.length); k -= 1) {
1788
+ const digit: i32 = toI32(rest % ten)
1789
+ buf[k] = toU8(JSON_DIGIT_ZERO + (digit < 0 ? -digit : digit))
1790
+ rest = rest / ten
1791
+ }
1792
+ w.end = at + width
1793
+ return true
1794
+ }
1795
+
1796
+ /**
1797
+ * The digits `${v}` spells for a finite `v`, at the cursor. They are a string
1798
+ * the runtime builds, and nothing holds it once its bytes are copied, so the
1799
+ * function's automatic arena scope (LANGUAGE.md, "Memory model", item 2)
1800
+ * gives it back before the call returns: a loop of `f64` writes keeps
1801
+ * `Arena.used()` flat like every other write, and `tests/link/std_json_writer`
1802
+ * measures it. The scope is the compiler's rather than a `using a = arena()`
1803
+ * block because this module also runs under Node, which `tests/run.js` holds
1804
+ * to `JSON.stringify`, and Node 22 does not parse `using`.
1805
+ */
1806
+ const jsonWriterPutF64 = (w: JsonWriter, v: f64): boolean => jsonWriterPutText(w, `${v}`)
1807
+
1808
+ /**
1809
+ * `s` as `JSON.stringify(s)` writes it — quoted, with the escapes
1810
+ * `JsonWriter.string` writes — as a string. A thin layer on the writer: it
1811
+ * sizes a buffer to fit exactly, writes into it and reads it back. A string
1812
+ * with nothing to escape skips the writer and is only quoted.
1813
+ */
1814
+ export const jsonStringifyString = (s: string): string => {
1815
+ const n: i32 = toI32(s.length)
1816
+ let width: i32 = 2
1817
+ for (let i: i32 = 0; i < n; i += 1) {
1818
+ width += jsonEscapedWidth(toI32(s.charCodeAt(i)))
1819
+ }
1820
+ if (width === n + 2) {
1821
+ return `"${s}"`
1822
+ }
1823
+ const w: JsonWriter = new JsonWriter(new Array<u8>(width), 0)
1824
+ w.string(s)
1825
+ return w.text()
1826
+ }