@amritk/nish-x86_64-linux 0.20.0 → 0.22.1

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 (74) hide show
  1. package/INSTALL.md +52 -10
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2777 -0
  4. package/package.json +2 -1
  5. package/runtime/browser.d.ts +6 -0
  6. package/runtime/browser.mjs +77 -0
  7. package/runtime/core/arrays.d.ts +36 -0
  8. package/runtime/core/arrays.mjs +153 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +80 -0
  11. package/runtime/core/host.mjs +325 -0
  12. package/runtime/core/numbers.d.ts +66 -0
  13. package/runtime/core/numbers.mjs +343 -0
  14. package/runtime/core/result.d.ts +45 -0
  15. package/runtime/core/result.mjs +140 -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 +46 -2
  22. package/runtime/nish.h +75 -2
  23. package/runtime/nish.mjs +23 -3
  24. package/runtime/node.mjs +386 -0
  25. package/runtime/runtime-bytes.c +401 -0
  26. package/runtime/runtime-cross.c +1 -1
  27. package/runtime/runtime-host.c +1 -1
  28. package/runtime/runtime-net.c +12 -1
  29. package/runtime/runtime-os.c +1 -1
  30. package/runtime/runtime-parallel.c +1 -1
  31. package/runtime/runtime-simd.c +231 -7
  32. package/runtime/runtime-term.c +504 -0
  33. package/runtime/runtime-wasm.c +0 -11
  34. package/runtime/runtime.c +190 -6
  35. package/runtime/shim.mjs +6 -985
  36. package/runtime/sqlite/LICENSE.md +85 -0
  37. package/runtime/sqlite/README.md +45 -0
  38. package/runtime/sqlite/nish-sqlite.c +359 -0
  39. package/runtime/sqlite/sqlite3.c +271131 -0
  40. package/runtime/sqlite/sqlite3.h +14447 -0
  41. package/scripts/build.sh +311 -59
  42. package/std/LICENSE-fast-float +23 -0
  43. package/std/LICENSE-fdlibm +8 -0
  44. package/std/README.md +32 -12
  45. package/std/base64.ts +116 -0
  46. package/std/bytes.ts +256 -0
  47. package/std/cli.ts +344 -0
  48. package/std/collections.ts +6 -5
  49. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  50. package/std/crypto/base64url.ts +125 -0
  51. package/std/crypto/bcrypt.ts +1557 -0
  52. package/std/crypto/hmac.ts +138 -2
  53. package/std/crypto/x509.ts +11 -122
  54. package/std/json-tokenizer.ts +2998 -57
  55. package/std/json-value.ts +22 -3
  56. package/std/json.ts +87 -8
  57. package/std/math.ts +608 -0
  58. package/std/net/http1-server.ts +282 -40
  59. package/std/net/http1.ts +40 -21
  60. package/std/net/http3-server.ts +50 -12
  61. package/std/net/postgres.ts +2294 -0
  62. package/std/net/prefork.ts +23 -3
  63. package/std/net/quic-listener.ts +20 -4
  64. package/std/random.ts +66 -4
  65. package/std/regex.ts +4274 -0
  66. package/std/sqlite.ts +391 -0
  67. package/std/term-io.ts +978 -0
  68. package/std/term-pixels.ts +874 -0
  69. package/std/term-probe.ts +459 -0
  70. package/std/term-rate.ts +993 -0
  71. package/std/term.ts +2453 -0
  72. package/std/text.ts +90 -7
  73. package/std/yaml-lines.ts +141 -0
  74. package/std/yaml.ts +4675 -0
@@ -0,0 +1,386 @@
1
+ /**
2
+ * The Node host (WP33 R3): importing this module installs, into
3
+ * `runtime/core/host.mjs`, the output, exit, arguments and host-bound builtins
4
+ * Node gives a rewritten program. Output is written with `fs.writeSync`, so
5
+ * `process.exit` cannot lose it. What each builtin answers, and where it parts
6
+ * from the native runtime (runtime/runtime-os.c, runtime/runtime-host.c), is
7
+ * written beside it.
8
+ */
9
+ import child_process from "node:child_process";
10
+ import fs from "node:fs";
11
+ import os from "node:os";
12
+ import tty from "node:tty";
13
+ import { installHost } from "./core/host.mjs";
14
+
15
+ function ioFail(verb, path) {
16
+ fs.writeSync(2, `nish: cannot ${verb} ${path}\n`);
17
+ process.exit(1);
18
+ }
19
+
20
+ function readFileSync(path) {
21
+ try {
22
+ return fs.readFileSync(path, "utf8");
23
+ } catch {
24
+ return ioFail("read", path);
25
+ }
26
+ }
27
+
28
+ /** `readFileSyncOrNull(path)`: null instead of exiting, so the program decides. */
29
+ function readFileSyncOrNull(path) {
30
+ try {
31
+ return fs.readFileSync(path, "utf8");
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ /** `readFileBytesSync(path)` (WP34 N2): the bytes as a plain array of numbers, or null. */
38
+ function readFileBytesSync(path) {
39
+ try {
40
+ return Array.from(fs.readFileSync(path));
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * The flags `runtime-os.c` opens a file it writes with, `O_NOFOLLOW` included
48
+ * (docs/security/runtime.md, RT-4): a symbolic link as the last component of
49
+ * the path is refused rather than followed, so `writeFileSync`,
50
+ * `appendFileSync` and `spawnSyncTo` fail here where they fail natively. Node's
51
+ * `"w"` and `"a"` follow the link. A link among the directories on the way is
52
+ * still followed, as it is natively. Windows has no `O_NOFOLLOW`, and there the
53
+ * flag is 0.
54
+ */
55
+ const { O_WRONLY, O_CREAT, O_TRUNC, O_APPEND, O_NOFOLLOW = 0 } = fs.constants;
56
+ const WRITE_FLAGS = O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW;
57
+ const APPEND_FLAGS = O_WRONLY | O_CREAT | O_APPEND | O_NOFOLLOW;
58
+
59
+ function writeFileSync(path, data) {
60
+ try {
61
+ fs.writeFileSync(path, data, { encoding: "utf8", flag: WRITE_FLAGS });
62
+ } catch {
63
+ ioFail("write", path);
64
+ }
65
+ }
66
+
67
+ function appendFileSync(path, data) {
68
+ try {
69
+ fs.appendFileSync(path, data, { encoding: "utf8", flag: APPEND_FLAGS });
70
+ } catch {
71
+ ioFail("write", path);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * `mkdirSync(path)` (WP14 D4): one directory, not recursive, answering whether
77
+ * a directory is there afterwards. Node throws where the runtime answers
78
+ * false, and `EEXIST` on a plain file is a failure here as it is there, so the
79
+ * `statSync` decides rather than the exception.
80
+ */
81
+ function mkdirSync(path) {
82
+ try {
83
+ fs.mkdirSync(path);
84
+ return true;
85
+ } catch {
86
+ try {
87
+ return fs.statSync(path).isDirectory();
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * `isDirectorySync(path)` (WP14 §7a): one stat, and a boolean out of it rather
96
+ * than an exception, which is what the runtime's `stat` answers too.
97
+ */
98
+ function isDirectorySync(path) {
99
+ try {
100
+ return fs.statSync(path).isDirectory();
101
+ } catch {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * `readdirSync(path)`: the entries, sorted ascending by bytes, or `null` when
108
+ * the directory cannot be read. Node throws where the runtime answers a value,
109
+ * so the `catch` is what makes the two agree, and a directory that exists and
110
+ * is empty answers an empty array on both sides. Node's readdir never yields
111
+ * `.` or `..` — the pair `runtime-os.c` skips explicitly — so there is nothing
112
+ * to filter out here.
113
+ *
114
+ * The sort is the semantic point. `runtime-os.c` orders the names with
115
+ * `strcmp`, which compares UTF-8 bytes, and `Array#sort` compares UTF-16 code
116
+ * units. The two agree on ASCII names and part company above the BMP, where a
117
+ * surrogate pair sorts below `U+E000`..`U+FFFF` in UTF-16 and above them in
118
+ * UTF-8. So the comparison is over the encoded bytes, which is the native order
119
+ * exactly rather than the native order for the names that happen to be ASCII.
120
+ */
121
+ function readdirSync(path) {
122
+ let names;
123
+ try {
124
+ names = fs.readdirSync(path);
125
+ } catch {
126
+ return null;
127
+ }
128
+ return names.sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
129
+ }
130
+
131
+ /**
132
+ * `realpathSync(path)` (WP19 §5a item 4): `path` with its symbolic links
133
+ * resolved, or `null` when it does not resolve.
134
+ *
135
+ * `fs.realpathSync` throws where the native `realpath` answers NULL — a
136
+ * missing path, a loop, a component that is not a directory — so the `catch`
137
+ * is what makes the two runtimes agree, exactly as it does for `readdirSync`
138
+ * above. Node's own answer is already absolute and already normalised, which
139
+ * is what POSIX `realpath` guarantees, so nothing here has to normalise it.
140
+ */
141
+ function realpathSync(path) {
142
+ try {
143
+ return fs.realpathSync(path);
144
+ } catch {
145
+ return null;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * `process.platform` / `process.arch` (WP14 §7a). Node's spellings are the
151
+ * ones `runtime-os.c` answers with, so on any machine this compiler has a
152
+ * triple for the two runtimes give the same string; elsewhere the native build
153
+ * says `unknown` where Node names the platform, which is the one place they
154
+ * part.
155
+ */
156
+ function platform() {
157
+ return process.platform;
158
+ }
159
+
160
+ function arch() {
161
+ return process.arch;
162
+ }
163
+
164
+ /**
165
+ * `getenv(name)` (WP19 R1): the value, or `null` when the variable is unset.
166
+ * Node answers `undefined` there and the language has no `undefined`, so the
167
+ * `??` is what makes the two runtimes agree; an empty value stays an empty
168
+ * string on both sides, because `CC=` is set and `CC` unset is not.
169
+ */
170
+ function getenv(name) {
171
+ return process.env[name] ?? null;
172
+ }
173
+
174
+ /**
175
+ * `spawnSync(argv)` and `spawnSyncTo(argv, out, err)` (WP14 D4), which are one
176
+ * run with its streams answered differently: the child's exit status, 128 + n
177
+ * when signal n killed it, -1 for an empty vector or a program that would not
178
+ * start. `runtime-os.c` puts one `static nish_spawn_impl` behind both builtins
179
+ * for the same reason this module puts one function behind both helpers — the
180
+ * argument vector, the wait and the signal convention are written once and
181
+ * cannot drift between the two.
182
+ *
183
+ * `out` and `err` are paths for the child's stdout and stderr, and an **empty**
184
+ * string leaves that stream inherited. Each file is created or truncated at
185
+ * 0644 with `WRITE_FLAGS`, which is what the runtime's file actions ask for:
186
+ * `O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW`, so a symbolic link at the path
187
+ * is refused and the answer is -1, on both sides. The descriptors opened here are
188
+ * closed again whichever way the child went; natively the child opens them and
189
+ * its exit drops them.
190
+ */
191
+ function spawnImpl(argv, out, err) {
192
+ if (argv.length === 0) return -1;
193
+ const opened = [];
194
+ const stream = (target) => {
195
+ if (target.length === 0) return "inherit";
196
+ const fd = fs.openSync(target, WRITE_FLAGS, 0o644);
197
+ opened.push(fd);
198
+ return fd;
199
+ };
200
+ try {
201
+ const r = child_process.spawnSync(argv[0], argv.slice(1), {
202
+ stdio: ["inherit", stream(out), stream(err)],
203
+ });
204
+ if (r.error !== undefined) return -1;
205
+ if (r.signal !== null && r.signal !== undefined) return 128 + (os.constants.signals[r.signal] ?? 0);
206
+ return r.status === null ? -1 : r.status;
207
+ } catch {
208
+ // A path that cannot be opened is, natively, a file action the child could
209
+ // not perform, and `posix_spawnp` reports that through its return value:
210
+ // -1, with no child having run. Opening the second path is what can fail
211
+ // after the first file was already created, so the truncation a caller can
212
+ // observe happens on both sides.
213
+ return -1;
214
+ } finally {
215
+ for (const fd of opened) fs.closeSync(fd);
216
+ }
217
+ }
218
+
219
+ /** `spawnSync(argv)`: the child inherits this process's streams, as it does natively. */
220
+ function spawnSync(argv) {
221
+ return spawnImpl(argv, "", "");
222
+ }
223
+
224
+ /** `spawnSyncTo(argv, stdoutPath, stderrPath)`: the same run with a stream sent to a file. */
225
+ function spawnSyncTo(argv, out, err) {
226
+ return spawnImpl(argv, out, err);
227
+ }
228
+
229
+ /**
230
+ * `kill(pid, sig)`: 0, or a negative errno, as `runtime-os.c`'s `nish_kill`
231
+ * answers. Only 0, 2, 9 and 15 are sent and a pid of 0 or below is refused,
232
+ * both -22, before Node is asked; Node's error then names its errno, which
233
+ * `os.constants.errno` turns back into the number (-3 for no such process).
234
+ */
235
+ function kill(pid, sig) {
236
+ if (pid <= 0 || !(sig === 0 || sig === 2 || sig === 9 || sig === 15)) return -22;
237
+ try {
238
+ process.kill(pid, sig);
239
+ return 0;
240
+ } catch (e) {
241
+ return -(os.constants.errno[e.code] ?? 22);
242
+ }
243
+ }
244
+
245
+ /**
246
+ * `availableParallelism()`: Node's own count, which reads the affinity mask as
247
+ * the native one does, lowered by a positive `NISH_NUM_THREADS` the way the
248
+ * native count is. The native count also reads a cgroup v2 CPU quota, which
249
+ * Node does not, so inside a container with a quota the two may differ; both
250
+ * are at least 1.
251
+ */
252
+ function availableParallelism() {
253
+ const n = os.availableParallelism();
254
+ const want = /^[0-9]+$/.test(process.env.NISH_NUM_THREADS ?? "") ? Number(process.env.NISH_NUM_THREADS) : 0;
255
+ return want > 0 && want < n ? want : n;
256
+ }
257
+
258
+ /** `totalMemory()`: `os.totalmem()`, as a BigInt because an `i64` is held as one here. */
259
+ function totalMemory() {
260
+ return BigInt(os.totalmem());
261
+ }
262
+
263
+ /**
264
+ * `monotonicNanos()`: `process.hrtime.bigint()`, a monotonic clock in
265
+ * nanoseconds (`CLOCK_MONOTONIC` on every platform that has it, which is the
266
+ * one `runtime-os.c` reads). The value is a BigInt because that is how an `i64`
267
+ * is held on this side.
268
+ *
269
+ * No rewrite can make a *reading* agree with a native run: both origins are
270
+ * arbitrary and neither is the other's. Only the difference between two reads
271
+ * means anything, so a differential program may compare two readings and must
272
+ * never print one.
273
+ */
274
+ function monotonicNanos() {
275
+ return process.hrtime.bigint();
276
+ }
277
+
278
+ /**
279
+ * `statMtimeSync(path)`: Node's `mtimeMs` for the path, or NaN when it cannot
280
+ * be stat'd, which is the native answer too. `mtimeMs` is the same arithmetic
281
+ * `runtime-host.c` does, so the two print the same digits, fraction and all.
282
+ */
283
+ function statMtimeSync(path) {
284
+ const st = fs.statSync(path, { throwIfNoEntry: false });
285
+ if (st === undefined) {
286
+ return Number.NaN;
287
+ }
288
+ return st.mtimeMs;
289
+ }
290
+
291
+ /**
292
+ * The RT-9 owner checks (#386): `lstatOwnerModeSync(path)` is the owner's uid
293
+ * in the high 32 bits and `st_mode` in the low 32 of one `lstat`, so a
294
+ * symbolic link answers for itself, or -1 when the path does not resolve or
295
+ * holds a NUL; `geteuid()` is the effective uid; `isExecutableSync(path)` is
296
+ * `access(path, X_OK)`. Each answers what `runtime-host.c` answers, the two
297
+ * `i64`s as BigInts because that is how an `i64` is held on this side. Node's
298
+ * `fs` throws on a NUL in a path where the runtime names nothing, so the
299
+ * `catch` is what makes the two agree there too.
300
+ */
301
+ function lstatOwnerModeSync(path) {
302
+ try {
303
+ const st = fs.lstatSync(path);
304
+ return BigInt.asIntN(64, (BigInt(st.uid >>> 0) << 32n) | BigInt(st.mode >>> 0));
305
+ } catch {
306
+ return -1n;
307
+ }
308
+ }
309
+
310
+ function geteuid() {
311
+ return BigInt(process.geteuid());
312
+ }
313
+
314
+ function isExecutableSync(path) {
315
+ try {
316
+ fs.accessSync(path, fs.constants.X_OK);
317
+ return true;
318
+ } catch {
319
+ return false;
320
+ }
321
+ }
322
+
323
+ /**
324
+ * `isTerminal(fd)` and `terminalSize()` (`nish:term`): whether a descriptor is
325
+ * a terminal, and the size Node already keeps for stdout's, or stderr's (a
326
+ * `tty.ReadStream` keeps none, so stdin has no size here),
327
+ * `[-25, 0]` when neither is one, as `runtime-term.c` answers.
328
+ */
329
+ function isTerminal(fd) {
330
+ return tty.isatty(fd);
331
+ }
332
+
333
+ function terminalSize() {
334
+ for (const stream of [process.stdout, process.stderr]) {
335
+ if (stream.isTTY && stream.columns !== undefined) return [stream.columns, stream.rows];
336
+ }
337
+ return [-25, 0];
338
+ }
339
+
340
+ /** The host-bound builtins by name, for the host's `call`. */
341
+ const CALLS = {
342
+ readFileSync,
343
+ readFileSyncOrNull,
344
+ readFileBytesSync,
345
+ writeFileSync,
346
+ appendFileSync,
347
+ mkdirSync,
348
+ isDirectorySync,
349
+ readdirSync,
350
+ realpathSync,
351
+ platform,
352
+ arch,
353
+ getenv,
354
+ spawnSync,
355
+ spawnSyncTo,
356
+ kill,
357
+ availableParallelism,
358
+ totalMemory,
359
+ monotonicNanos,
360
+ statMtimeSync,
361
+ lstatOwnerModeSync,
362
+ geteuid,
363
+ isExecutableSync,
364
+ isTerminal,
365
+ terminalSize,
366
+ };
367
+
368
+ // A rewritten program prints most values with Node's own `console.log`, which
369
+ // queues a write to a pipe the reader has not drained, and `process.exit`
370
+ // drops what is queued: under load the tail of the output was lost with exit 0
371
+ // (`corpus/bit_fnv1a` under the emit-ts differential). Blocking writes make it
372
+ // as synchronous as `writeOut`.
373
+ for (const stream of [process.stdout, process.stderr]) {
374
+ stream._handle?.setBlocking?.(true);
375
+ }
376
+
377
+ installHost({
378
+ writeOut: (s) => fs.writeSync(1, s),
379
+ writeErr: (s) => fs.writeSync(2, s),
380
+ exit: (code) => process.exit(code),
381
+ // Index 0 is the program on both sides: the executable natively, the entry
382
+ // script here, which is one index later than Node's own `argv`. The global
383
+ // is left as Node set it; the output reads `argv()` (`src/emit-ts-records.ts`).
384
+ argv: () => process.argv.slice(1),
385
+ call: (name, args) => CALLS[name](...args),
386
+ });