@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/net/url.ts ADDED
@@ -0,0 +1,571 @@
1
+ /**
2
+ * `nish/net/url` — the request target's path and query, split and
3
+ * percent-decoded in place, and its query string read as key/value pairs.
4
+ *
5
+ * A handler that routes on a path or reads `?name=…` from a query otherwise
6
+ * writes its own byte scan, and each such scan decides differently what `%zz`,
7
+ * `%00`, `+` or a hundred thousand `&` mean. This module decides once:
8
+ *
9
+ * import { UrlQuery, UrlQueryLimits, URL_DECODE_FORM, URL_QUERY_PAIR, percentDecode, urlQueryFind, urlQueryStart } from "nish/net/url";
10
+ *
11
+ * const q: i32 = urlQueryStart(target, 0, len) // -1: no `?`, so no query
12
+ * if (q >= 0) {
13
+ * const query: UrlQuery = new UrlQuery(new UrlQueryLimits())
14
+ * query.reset(target, q + 1, len - q - 1)
15
+ * if (urlQueryFind(query, "name") === URL_QUERY_PAIR) {
16
+ * const n: i32 = percentDecode(target, query.valueStart, query.valueLength, out, 0, URL_DECODE_FORM)
17
+ * if (n >= 0) {
18
+ * // out[0, n) is the value
19
+ * } // else n is a URL_DECODE_ERR_* code: a peer's `%zz`, never read out
20
+ * } // else no such key, or URL_QUERY_ERR_PAIRS past the cap
21
+ * }
22
+ *
23
+ * **The byte API allocates nothing.** `urlQueryStart`, `percentDecode`,
24
+ * `UrlPathSegments` and `UrlQuery` read a window `bytes[off, off + len)` and
25
+ * answer offsets into it, or write into the caller's array; `reset` reuses an
26
+ * iterator, so a server that holds one per connection allocates nothing per
27
+ * request. No byte outside the window is read. A window outside its array, or
28
+ * an `at` outside `out`, is a bug in the caller and panics; nothing a peer
29
+ * sends does.
30
+ *
31
+ * **Decoding is strict unless asked otherwise.** With no flags,
32
+ * `percentDecode` refuses a `%` not followed by two hex digits
33
+ * (`URL_DECODE_ERR_ESCAPE`) and `%00` (`URL_DECODE_ERR_NUL`), and copies `+`
34
+ * as it stands. `URL_DECODE_FORM` reads `+` as a space, as
35
+ * `application/x-www-form-urlencoded` does; `URL_DECODE_LENIENT` copies a
36
+ * malformed escape as it stands, as the WHATWG URL Standard's percent-decode
37
+ * and `URLSearchParams` do; `URL_DECODE_ALLOW_NUL` decodes `%00` to a 0 byte.
38
+ * For valid UTF-8, `URLSearchParams` is `URL_DECODE_FORM | URL_DECODE_LENIENT |
39
+ * URL_DECODE_ALLOW_NUL`; it replaces an invalid sequence with U+FFFD.
40
+ *
41
+ * **Decoded bytes are bytes.** The byte API does not check that what it
42
+ * decodes is UTF-8, where `decodeURIComponent` throws on `%FF`: a path segment
43
+ * or a value may be binary, and whatever reads it as text checks it.
44
+ * `urlDecode` and `urlQueryGet`, which answer a string, refuse anything that is
45
+ * not valid UTF-8.
46
+ *
47
+ * **A path is split before it is decoded.** `UrlPathSegments` splits on the
48
+ * raw `/` only, and `percentDecodePathSegment` decodes one segment, so `%2F`
49
+ * is a 0x2F byte inside its segment and never starts a new one: `/a%2Fb/c` is
50
+ * the two segments `a/b` and `c`. `+` is literal in a path.
51
+ *
52
+ * **A query yields at most `UrlQueryLimits.maxPairs` pairs** (100 by default),
53
+ * and past it `next()` answers `URL_QUERY_ERR_PAIRS` and stays spent. Empty
54
+ * pairs (`a&&b`, a leading or trailing `&`) are skipped and not counted, as
55
+ * `URLSearchParams` skips them.
56
+ *
57
+ * **A fragment is not part of a request target** (RFC 9112 §3.2), so nothing
58
+ * here looks for a `#`: in a target that holds one, it is a byte of the path
59
+ * or of the last value.
60
+ *
61
+ * The record of what this module refuses and why is `docs/security/url.md`.
62
+ */
63
+
64
+ /** `+` is a space, as `application/x-www-form-urlencoded` writes one. */
65
+ export const URL_DECODE_FORM: i32 = 1
66
+ /** A `%` not followed by two hex digits is copied as it stands rather than refused (WHATWG percent-decode). */
67
+ export const URL_DECODE_LENIENT: i32 = 2
68
+ /** `%00` decodes to a 0 byte rather than being refused. */
69
+ export const URL_DECODE_ALLOW_NUL: i32 = 4
70
+
71
+ /** A `%` not followed by two hex digits: `%zz`, or `%4` or `%` at the end of the window. */
72
+ export const URL_DECODE_ERR_ESCAPE: i32 = -1
73
+ /** `%00` without `URL_DECODE_ALLOW_NUL`. */
74
+ export const URL_DECODE_ERR_NUL: i32 = -2
75
+ /** `out` has no room for the next byte; nothing is written at or past `out.length`. */
76
+ export const URL_DECODE_ERR_SPACE: i32 = -3
77
+
78
+ /** `next()` found a pair; the iterator's windows bound it. */
79
+ export const URL_QUERY_PAIR: i32 = 1
80
+ /** The window holds no more pairs; every later `next()` answers the same. */
81
+ export const URL_QUERY_END: i32 = 0
82
+ /** The query holds more than `maxPairs` pairs; sticky in `failed`. */
83
+ export const URL_QUERY_ERR_PAIRS: i32 = -4
84
+
85
+ /** The default for `UrlQueryLimits.maxPairs`. */
86
+ export const URL_DEFAULT_MAX_PAIRS: i32 = 100
87
+
88
+ const URL_SPACE: i32 = 32
89
+ const URL_PERCENT: i32 = 37
90
+ const URL_AMPERSAND: i32 = 38
91
+ const URL_PLUS: i32 = 43
92
+ const URL_SLASH: i32 = 47
93
+ const URL_EQUALS: i32 = 61
94
+ const URL_QUESTION: i32 = 63
95
+
96
+ /** Panics unless `bytes[off, off + len)` lies inside `bytes`. */
97
+ const urlCheckWindow = (bytes: u8[], off: i32, len: i32, who: string): void => {
98
+ const size: i32 = toI32(bytes.length)
99
+ if (off < 0 || len < 0 || off > size || len > size - off) {
100
+ panic(who + ": the window is outside the array")
101
+ }
102
+ }
103
+
104
+ /** The value of one hex digit, either case, or -1. */
105
+ const urlHexValue = (b: i32): i32 => {
106
+ if (b >= 48 && b <= 57) {
107
+ return b - 48
108
+ }
109
+ if (b >= 65 && b <= 70) {
110
+ return b - 55
111
+ }
112
+ if (b >= 97 && b <= 102) {
113
+ return b - 87
114
+ }
115
+ return -1
116
+ }
117
+
118
+ /** The byte the escape at `bytes[i]` (a `%`) stands for, or -1 when two hex digits do not follow it before `end`. */
119
+ const urlEscapeAt = (bytes: u8[], i: i32, end: i32): i32 => {
120
+ if (end - i < 3) {
121
+ return -1
122
+ }
123
+ const hi: i32 = urlHexValue(toI32(bytes[i + 1]))
124
+ const lo: i32 = urlHexValue(toI32(bytes[i + 2]))
125
+ if (hi < 0 || lo < 0) {
126
+ return -1
127
+ }
128
+ return hi * 16 + lo
129
+ }
130
+
131
+ /**
132
+ * `percentDecode`'s loop, shared with `percentDecodeErrorAt` so the two agree
133
+ * on where an error is: with `wantPos` it answers the input offset of the
134
+ * error, or -1 when there is none, rather than the count or the code.
135
+ */
136
+ const urlDecodeRun = (
137
+ src: u8[],
138
+ off: i32,
139
+ len: i32,
140
+ out: u8[],
141
+ at: i32,
142
+ flags: i32,
143
+ wantPos: boolean
144
+ ): i32 => {
145
+ urlCheckWindow(src, off, len, "percentDecode")
146
+ const size: i32 = toI32(out.length)
147
+ if (at < 0 || at > size) {
148
+ panic("percentDecode: at is outside out")
149
+ }
150
+ const form: boolean = (flags & URL_DECODE_FORM) !== 0
151
+ const lenient: boolean = (flags & URL_DECODE_LENIENT) !== 0
152
+ const allowNul: boolean = (flags & URL_DECODE_ALLOW_NUL) !== 0
153
+ const end: i32 = off + len
154
+ let i: i32 = off
155
+ let w: i32 = at
156
+ const n: i32 = toI32(src.length)
157
+ while (i >= 0 && i < end && i < n) {
158
+ let b: i32 = toI32(src[i])
159
+ let step: i32 = 1
160
+ if (b === URL_PERCENT) {
161
+ const e: i32 = urlEscapeAt(src, i, end)
162
+ if (e >= 0) {
163
+ if (e === 0 && !allowNul) {
164
+ return wantPos ? i : URL_DECODE_ERR_NUL
165
+ }
166
+ b = e
167
+ step = 3
168
+ } else if (!lenient) {
169
+ return wantPos ? i : URL_DECODE_ERR_ESCAPE
170
+ }
171
+ } else if (form && b === URL_PLUS) {
172
+ b = URL_SPACE
173
+ }
174
+ if (w >= size) {
175
+ return wantPos ? i : URL_DECODE_ERR_SPACE
176
+ }
177
+ out[w] = toU8(b)
178
+ w += 1
179
+ i += step
180
+ }
181
+ return wantPos ? -1 : w - at
182
+ }
183
+
184
+ /**
185
+ * The index of the first `?` in `bytes[off, off + len)`, or -1 when there is
186
+ * none. The path is `[off, q)` and the query `(q, off + len)`; nothing is
187
+ * copied. A `#` is not looked for: a request target holds no fragment.
188
+ */
189
+ export const urlQueryStart = (bytes: u8[], off: i32, len: i32): i32 => {
190
+ urlCheckWindow(bytes, off, len, "urlQueryStart")
191
+ const end: i32 = off + len
192
+ const n: i32 = toI32(bytes.length)
193
+ let i: i32 = off
194
+ while (i >= 0 && i < end && i < n) {
195
+ if (toI32(bytes[i]) === URL_QUESTION) {
196
+ return i
197
+ }
198
+ i += 1
199
+ }
200
+ return -1
201
+ }
202
+
203
+ /**
204
+ * Percent-decodes `src[off, off + len)` into `out` from `out[at]` on, and
205
+ * answers the number of bytes written, or a negative `URL_DECODE_ERR_*` code;
206
+ * `flags` is a sum of `URL_DECODE_*` (0 is strict: malformed escapes and `%00`
207
+ * refused, `+` kept). Decoding never makes the input longer, so `len` bytes
208
+ * of room always suffice; with less, `URL_DECODE_ERR_SPACE` is answered before
209
+ * any write at or past `out.length`. On an error the bytes before it have been
210
+ * written. What is decoded is not checked as UTF-8.
211
+ */
212
+ export const percentDecode = (src: u8[], off: i32, len: i32, out: u8[], at: i32, flags: i32): i32 =>
213
+ urlDecodeRun(src, off, len, out, at, flags, false)
214
+
215
+ /**
216
+ * The offset in `src` of the byte `percentDecode` with the same arguments
217
+ * fails at — the `%` of a malformed escape or of `%00`, or the first byte
218
+ * `out` has no room for — or -1 when it does not fail. It writes what
219
+ * `percentDecode` would have written before the error.
220
+ */
221
+ export const percentDecodeErrorAt = (src: u8[], off: i32, len: i32, out: u8[], at: i32, flags: i32): i32 =>
222
+ urlDecodeRun(src, off, len, out, at, flags, true)
223
+
224
+ /**
225
+ * `percentDecode` for one path segment: `+` is literal whatever `flags` say,
226
+ * and `%2F` is a 0x2F byte in the segment. Split the path with
227
+ * `UrlPathSegments` first, then decode each segment.
228
+ */
229
+ export const percentDecodePathSegment = (
230
+ src: u8[],
231
+ off: i32,
232
+ len: i32,
233
+ out: u8[],
234
+ at: i32,
235
+ flags: i32
236
+ ): i32 => urlDecodeRun(src, off, len, out, at, (flags | URL_DECODE_FORM) ^ URL_DECODE_FORM, false)
237
+
238
+ /**
239
+ * The segments of a path, split on the raw `/`. A leading `/` starts the
240
+ * path rather than an empty segment, so `/a//b/` is `a`, ``, `b`, ``, `/` is
241
+ * one empty segment and an empty window has none. Each segment's raw bytes
242
+ * are `bytes[start, start + length)`; decode one with
243
+ * `percentDecodePathSegment`.
244
+ */
245
+ export class UrlPathSegments {
246
+ bytes: u8[]
247
+ /** The current segment's first byte. */
248
+ start: i32 = 0
249
+ /** The current segment's length in raw bytes. */
250
+ length: i32 = 0
251
+ pos: i32 = 0
252
+ end: i32 = 0
253
+ done: boolean = true
254
+
255
+ /** An iterator over nothing until `reset`. */
256
+ constructor() {
257
+ this.bytes = new Array<u8>(0)
258
+ }
259
+
260
+ /** Starts over on the path `bytes[off, off + len)`; panics when the window is outside the array. */
261
+ reset(bytes: u8[], off: i32, len: i32): void {
262
+ urlCheckWindow(bytes, off, len, "UrlPathSegments")
263
+ this.bytes = bytes
264
+ this.pos = off
265
+ this.end = off + len
266
+ this.start = off
267
+ this.length = 0
268
+ this.done = len === 0
269
+ if (len > 0 && toI32(bytes[off]) === URL_SLASH) {
270
+ this.pos = off + 1
271
+ }
272
+ }
273
+
274
+ /** Moves to the next segment and answers true, or answers false when the path has no more. */
275
+ next(): boolean {
276
+ if (this.done) {
277
+ return false
278
+ }
279
+ const bytes: u8[] = this.bytes
280
+ let j: i32 = this.pos
281
+ const n: i32 = toI32(bytes.length)
282
+ while (j >= 0 && j < this.end && j < n && toI32(bytes[j]) !== URL_SLASH) {
283
+ j += 1
284
+ }
285
+ this.start = this.pos
286
+ this.length = j - this.pos
287
+ if (j < this.end) {
288
+ this.pos = j + 1
289
+ } else {
290
+ this.done = true
291
+ }
292
+ return true
293
+ }
294
+ }
295
+
296
+ /**
297
+ * What a `UrlQuery` refuses to yield. The iterator copies the number when it
298
+ * is made; a negative cap is read as zero, so every query with a pair fails.
299
+ */
300
+ export class UrlQueryLimits {
301
+ /** The most pairs one query yields, `URL_DEFAULT_MAX_PAIRS` (a field starts at a literal); past it `next()` answers `URL_QUERY_ERR_PAIRS` and stays spent. */
302
+ maxPairs: i32 = 100
303
+ }
304
+
305
+ /**
306
+ * The pairs of a query string, one per `next()`, in order. The window is the
307
+ * query without its `?`. `&` separates pairs and the first `=` in a pair ends
308
+ * its key: `a&b=&c=d=e` is `a` with no value, `b` with an empty one and `c`
309
+ * with `d=e`. A key's raw bytes are `bytes[keyStart, keyStart + keyLength)`
310
+ * and a value's `bytes[valueStart, valueStart + valueLength)`; decode them
311
+ * with `percentDecode` and `URL_DECODE_FORM`.
312
+ */
313
+ export class UrlQuery {
314
+ bytes: u8[]
315
+ keyStart: i32 = 0
316
+ keyLength: i32 = 0
317
+ valueStart: i32 = 0
318
+ valueLength: i32 = 0
319
+ /** 0, or the `URL_QUERY_ERR_*` code every later `next()` answers. */
320
+ failed: i32 = 0
321
+ maxPairs: i32
322
+ pos: i32 = 0
323
+ end: i32 = 0
324
+ count: i32 = 0
325
+ /** False for a pair written without `=` (`a`), true for `a=` with `valueLength` 0. */
326
+ hasValue: boolean = false
327
+
328
+ /** An iterator over nothing until `reset`, with the cap in `limits`. */
329
+ constructor(limits: UrlQueryLimits) {
330
+ this.bytes = new Array<u8>(0)
331
+ this.maxPairs = limits.maxPairs < 0 ? 0 : limits.maxPairs
332
+ }
333
+
334
+ /** Starts over on the query `bytes[off, off + len)`, clearing `failed`; panics when the window is outside the array. */
335
+ reset(bytes: u8[], off: i32, len: i32): void {
336
+ urlCheckWindow(bytes, off, len, "UrlQuery")
337
+ this.bytes = bytes
338
+ this.pos = off
339
+ this.end = off + len
340
+ this.count = 0
341
+ this.failed = 0
342
+ this.keyStart = off
343
+ this.keyLength = 0
344
+ this.valueStart = off
345
+ this.valueLength = 0
346
+ this.hasValue = false
347
+ }
348
+
349
+ /** `URL_QUERY_PAIR` with the windows set to the next pair, `URL_QUERY_END`, or `failed`. */
350
+ next(): i32 {
351
+ if (this.failed !== 0) {
352
+ return this.failed
353
+ }
354
+ const bytes: u8[] = this.bytes
355
+ const end: i32 = this.end
356
+ let i: i32 = this.pos
357
+ const n: i32 = toI32(bytes.length)
358
+ while (i >= 0 && i < end && i < n && toI32(bytes[i]) === URL_AMPERSAND) {
359
+ i += 1
360
+ }
361
+ this.pos = i
362
+ if (i >= end) {
363
+ return URL_QUERY_END
364
+ }
365
+ if (this.count >= this.maxPairs) {
366
+ this.failed = URL_QUERY_ERR_PAIRS
367
+ return this.failed
368
+ }
369
+ let eq: i32 = -1
370
+ while (i >= 0 && i < end && i < n) {
371
+ const b: i32 = toI32(bytes[i])
372
+ if (b === URL_AMPERSAND) {
373
+ break
374
+ }
375
+ if (b === URL_EQUALS && eq < 0) {
376
+ eq = i
377
+ }
378
+ i += 1
379
+ }
380
+ this.keyStart = this.pos
381
+ if (eq < 0) {
382
+ this.keyLength = i - this.pos
383
+ this.valueStart = i
384
+ this.valueLength = 0
385
+ this.hasValue = false
386
+ } else {
387
+ this.keyLength = eq - this.pos
388
+ this.valueStart = eq + 1
389
+ this.valueLength = i - eq - 1
390
+ this.hasValue = true
391
+ }
392
+ this.pos = i
393
+ this.count += 1
394
+ return URL_QUERY_PAIR
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Whether `bytes[off, off + len)`, decoded strictly in form mode, is `name`'s
400
+ * bytes. Nothing is written: a malformed escape or `%00` in the key matches
401
+ * no name.
402
+ */
403
+ const urlKeyEquals = (bytes: u8[], off: i32, len: i32, name: string): boolean => {
404
+ const n: i32 = toI32(name.length)
405
+ const end: i32 = off + len
406
+ let i: i32 = off
407
+ let o: i32 = 0
408
+ const size: i32 = toI32(bytes.length)
409
+ while (i >= 0 && i < end && i < size) {
410
+ let b: i32 = toI32(bytes[i])
411
+ let step: i32 = 1
412
+ if (b === URL_PERCENT) {
413
+ b = urlEscapeAt(bytes, i, end)
414
+ if (b <= 0) {
415
+ return false
416
+ }
417
+ step = 3
418
+ } else if (b === URL_PLUS) {
419
+ b = URL_SPACE
420
+ }
421
+ if (o >= n || b !== toI32(name.charCodeAt(o))) {
422
+ return false
423
+ }
424
+ o += 1
425
+ i += step
426
+ }
427
+ return o === n
428
+ }
429
+
430
+ /**
431
+ * Moves `q` on to the first of its remaining pairs whose key, decoded in form
432
+ * mode, equals `name`, and answers `URL_QUERY_PAIR` with `q`'s windows on it;
433
+ * or `URL_QUERY_END`, or the error `next()` answered. Nothing is allocated.
434
+ * `?na%6De=x` holds the key `name`; a key with a malformed escape or `%00`
435
+ * matches nothing. Call it again for the next pair with the same key.
436
+ * `name` is ASCII: natively it is compared with the key's bytes, under Node
437
+ * with its UTF-16 code units, so a non-ASCII name answers differently.
438
+ */
439
+ export const urlQueryFind = (q: UrlQuery, name: string): i32 => {
440
+ let k: i32 = q.next()
441
+ while (k === URL_QUERY_PAIR) {
442
+ if (urlKeyEquals(q.bytes, q.keyStart, q.keyLength, name)) {
443
+ return k
444
+ }
445
+ k = q.next()
446
+ }
447
+ return k
448
+ }
449
+
450
+ /** Whether `buf[off, off + len)` is well-formed UTF-8 (RFC 3629: no overlong form, no surrogate, nothing past U+10FFFF, nothing cut short). */
451
+ const urlIsUtf8 = (buf: u8[], off: i32, len: i32): boolean => {
452
+ const end: i32 = off + len
453
+ let i: i32 = off
454
+ const n: i32 = toI32(buf.length)
455
+ while (i >= 0 && i < end && i < n) {
456
+ const b: i32 = toI32(buf[i])
457
+ if (b < 0x80) {
458
+ i += 1
459
+ continue
460
+ }
461
+ let need: i32 = 0
462
+ let lo: i32 = 0x80
463
+ let hi: i32 = 0xbf
464
+ if (b >= 0xc2 && b <= 0xdf) {
465
+ need = 1
466
+ } else if (b === 0xe0) {
467
+ need = 2
468
+ lo = 0xa0
469
+ } else if (b === 0xed) {
470
+ need = 2
471
+ hi = 0x9f
472
+ } else if (b >= 0xe1 && b <= 0xef) {
473
+ need = 2
474
+ } else if (b === 0xf0) {
475
+ need = 3
476
+ lo = 0x90
477
+ } else if (b === 0xf4) {
478
+ need = 3
479
+ hi = 0x8f
480
+ } else if (b >= 0xf1 && b <= 0xf3) {
481
+ need = 3
482
+ } else {
483
+ return false
484
+ }
485
+ if (end - i - 1 < need || i + 1 >= n) {
486
+ return false
487
+ }
488
+ const second: i32 = toI32(buf[i + 1])
489
+ if (second < lo || second > hi) {
490
+ return false
491
+ }
492
+ let k: i32 = 2
493
+ while (k <= need) {
494
+ const c: i32 = toI32(buf[i + k])
495
+ if (c < 0x80 || c > 0xbf) {
496
+ return false
497
+ }
498
+ k += 1
499
+ }
500
+ i += need + 1
501
+ }
502
+ return true
503
+ }
504
+
505
+ /** `s`'s bytes, or null when it holds a code unit past 0xFF (only possible under Node, where a string is UTF-16). */
506
+ const urlBytesOf = (s: string): u8[] | null => {
507
+ const n: i32 = toI32(s.length)
508
+ const bytes: u8[] = new Array<u8>(n)
509
+ let i: i32 = 0
510
+ while (i < n && i < toI32(bytes.length)) {
511
+ const c: i32 = toI32(s.charCodeAt(i))
512
+ if (c > 0xff) {
513
+ return null
514
+ }
515
+ bytes[i] = toU8(c)
516
+ i += 1
517
+ }
518
+ return bytes
519
+ }
520
+
521
+ /** `percentDecode` of `src[off, off + len)` as a new string, or null on an error or when the result is not UTF-8. */
522
+ const urlDecodeString = (src: u8[], off: i32, len: i32, flags: i32): string | null => {
523
+ const out: u8[] = new Array<u8>(len)
524
+ const n: i32 = percentDecode(src, off, len, out, 0, flags)
525
+ if (n < 0 || !urlIsUtf8(out, 0, n)) {
526
+ return null
527
+ }
528
+ return String.fromBytes(out, 0, n)
529
+ }
530
+
531
+ /**
532
+ * `s` percent-decoded with `flags`, or null when `percentDecode` refuses it
533
+ * or what it decodes to is not valid UTF-8, which is where
534
+ * `decodeURIComponent` throws. `s` is expected to be ASCII, as a URL on the
535
+ * wire is. It allocates its answer.
536
+ */
537
+ export const urlDecode = (s: string, flags: i32): string | null => {
538
+ const bytes: u8[] | null = urlBytesOf(s)
539
+ if (bytes === null) {
540
+ return null
541
+ }
542
+ return urlDecodeString(bytes, 0, toI32(bytes.length), flags)
543
+ }
544
+
545
+ /**
546
+ * The first value for `name` in the query of the request target `target`,
547
+ * form-decoded strictly: `""` for a key written without `=`, as
548
+ * `URLSearchParams.get` answers, and null when the target has no query, the
549
+ * key is absent, the value is malformed or not UTF-8, or the key comes after
550
+ * `URL_DEFAULT_MAX_PAIRS` pairs. It allocates its answer.
551
+ */
552
+ export const urlQueryGet = (target: string, name: string): string | null => {
553
+ const bytes: u8[] | null = urlBytesOf(target)
554
+ if (bytes === null) {
555
+ return null
556
+ }
557
+ const len: i32 = toI32(bytes.length)
558
+ const q: i32 = urlQueryStart(bytes, 0, len)
559
+ if (q < 0) {
560
+ return null
561
+ }
562
+ const query: UrlQuery = new UrlQuery(new UrlQueryLimits())
563
+ query.reset(bytes, q + 1, len - q - 1)
564
+ if (urlQueryFind(query, name) !== URL_QUERY_PAIR) {
565
+ return null
566
+ }
567
+ if (!query.hasValue) {
568
+ return ""
569
+ }
570
+ return urlDecodeString(bytes, query.valueStart, query.valueLength, URL_DECODE_FORM)
571
+ }
package/std/random.ts ADDED
@@ -0,0 +1,94 @@
1
+ /**
2
+ * `std/random` — a seeded generator of pseudo-random numbers whose every
3
+ * answer is the same natively and under Node.
4
+ *
5
+ * import { Rng } from "nish/random";
6
+ *
7
+ * const r = new Rng(42);
8
+ * const die: i32 = 1 + (r.nextI32() >>> 1) % 6;
9
+ * const u: f64 = r.nextF64(); // in [0, 1)
10
+ *
11
+ * The state is one 32-bit integer. Each step adds the odd constant
12
+ * 0x9E3779B9 (2^32 divided by the golden ratio) to it, wrapping, so the
13
+ * states walk all 2^32 values before any repeats, whatever the seed — 0
14
+ * included. Each answer is MurmurHash3's 32-bit finaliser applied to the new
15
+ * state (Austin Appleby's `fmix32`; MurmurHash3 is public domain, and these
16
+ * are its constants): two multiplies and three shifted exclusive ors that mix
17
+ * every bit of the state into every bit of the answer, so seeds 1, 2 and 3
18
+ * answer three unrelated sequences. That pairing of a Weyl sequence with a
19
+ * mixing function is the shape of SplitMix (Steele, Lea and Flood, "Fast
20
+ * splittable pseudorandom number generators", OOPSLA 2014), on 32 bits.
21
+ *
22
+ * **The same bits under Node.** The two operations that wrap are spelled
23
+ * `wrappingAdd` and `wrappingMul` from `nish:unsafe`, which define the
24
+ * two's-complement wrap natively and answer `(a + b) | 0` and `Math.imul(a, b)`
25
+ * under Node (docs/LANGUAGE.md, "nish:unsafe"); every shift has a constant
26
+ * count, and `^` and `>>> 16` answer the same 32 bits in JavaScript as in the
27
+ * compiled program. `nextF64` builds its double from integers below 2^53, so
28
+ * its products and sums are exact both ways. The program a test prints under
29
+ * Node is the one it prints compiled (docs/RUN_UNDER_NODE.md).
30
+ *
31
+ * It is not for cryptography: a key or a nonce comes from
32
+ * `crypto.getRandomValues`.
33
+ *
34
+ * An `Rng` is an ordinary object, so a parallel body or a task that makes one
35
+ * of its own may step it as it likes: what a function allocates is its own
36
+ * memory (docs/LANGUAGE.md, "Data parallelism"). One shared by several tasks
37
+ * would be a write their caller can see, and is refused as any such write is;
38
+ * give each task its own, seeded from its index.
39
+ */
40
+ import { wrappingAdd, wrappingMul } from "nish:unsafe"
41
+
42
+ /** 0x9E3779B9 as an `i32`: the Weyl increment, odd, so the walk visits every state. */
43
+ const GOLDEN: i32 = -1640531527
44
+
45
+ /** 0x85EBCA6B as an `i32`: MurmurHash3's first finaliser multiplier (public domain). */
46
+ const MIX_1: i32 = -2048144789
47
+
48
+ /** 0xC2B2AE35 as an `i32`: MurmurHash3's second finaliser multiplier (public domain). */
49
+ const MIX_2: i32 = -1028477387
50
+
51
+ /** 2^26, the weight of the high draw in `nextF64`'s 53 bits. */
52
+ const TWO_26: f64 = 67108864.0
53
+
54
+ /** 2^53, which scales a 53-bit integer into [0, 1). */
55
+ const TWO_53: f64 = 9007199254740992.0
56
+
57
+ /** MurmurHash3's `fmix32`: every bit of `h` into every bit of the answer, a bijection on 32 bits. */
58
+ const mix = (h: i32): i32 => {
59
+ let x: i32 = h
60
+ x = x ^ (x >>> 16)
61
+ x = wrappingMul(x, MIX_1)
62
+ x = x ^ (x >>> 13)
63
+ x = wrappingMul(x, MIX_2)
64
+ x = x ^ (x >>> 16)
65
+ return x
66
+ }
67
+
68
+ /** A generator of pseudo-random numbers: a Weyl sequence from its seed, each state mixed. */
69
+ export class Rng {
70
+ /** Where the walk is: the seed plus the increment times the steps taken, wrapped. */
71
+ state: i32
72
+
73
+ /** A generator whose sequence `seed` decides: two made from one seed answer the same numbers. */
74
+ constructor(seed: i32) {
75
+ this.state = seed
76
+ }
77
+
78
+ /** The next 32 bits, as an `i32`: over 2^32 steps each value is answered exactly once. */
79
+ nextI32(): i32 {
80
+ this.state = wrappingAdd(this.state, GOLDEN)
81
+ return mix(this.state)
82
+ }
83
+
84
+ /**
85
+ * The next number in [0, 1), from two steps: the high 27 bits of one and
86
+ * the high 26 of the next make a 53-bit integer, which a double holds
87
+ * exactly, scaled by 2^-53.
88
+ */
89
+ nextF64(): f64 {
90
+ const hi: i32 = this.nextI32() >>> 5
91
+ const lo: i32 = this.nextI32() >>> 6
92
+ return (toF64(hi) * TWO_26 + toF64(lo)) / TWO_53
93
+ }
94
+ }