@amritk/nish-x86_64-linux 0.20.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.
- package/INSTALL.md +22 -10
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +2 -1
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +44 -0
- package/runtime/nish.h +75 -2
- package/runtime/nish.mjs +23 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +1 -1
- package/runtime/runtime-host.c +1 -1
- package/runtime/runtime-os.c +1 -1
- package/runtime/runtime-parallel.c +1 -1
- package/runtime/runtime-simd.c +231 -7
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +0 -11
- package/runtime/runtime.c +190 -6
- package/runtime/shim.mjs +6 -985
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +261 -53
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +31 -11
- package/std/base64.ts +116 -0
- package/std/bytes.ts +256 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/hmac.ts +138 -2
- package/std/crypto/x509.ts +11 -122
- package/std/json-tokenizer.ts +2998 -57
- package/std/json-value.ts +22 -3
- package/std/json.ts +83 -1
- package/std/math.ts +608 -0
- package/std/net/http1-server.ts +85 -8
- package/std/net/http3-server.ts +50 -12
- package/std/net/postgres.ts +1875 -0
- package/std/net/quic-listener.ts +20 -4
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The strings of the core: an Nish string is its UTF-8 bytes, so every length
|
|
3
|
+
* and offset here counts bytes. Only the web's `TextEncoder` and `TextDecoder`
|
|
4
|
+
* are used, and the decoder keeps a leading byte-order mark, as a cut through
|
|
5
|
+
* the bytes must. Decoding is lossy where native is not: bytes that are not
|
|
6
|
+
* UTF-8, or a cut through a character, decode to U+FFFD, whose three bytes
|
|
7
|
+
* then change the string's length.
|
|
8
|
+
*/
|
|
9
|
+
import { panicIndex, panicSlice, toIndex } from "./checks.mjs";
|
|
10
|
+
|
|
11
|
+
const ENCODER = new TextEncoder();
|
|
12
|
+
const DECODER = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
13
|
+
|
|
14
|
+
/** The string `bytesOf` encoded last, and its bytes. */
|
|
15
|
+
let lastString = "";
|
|
16
|
+
let lastBytes = new Uint8Array(0);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The UTF-8 bytes of `s`, which is what an Nish string holds. The last string
|
|
20
|
+
* encoded is kept with its bytes, so a loop of `s.charCodeAt(i)` over one
|
|
21
|
+
* string encodes it once rather than once per byte. Nothing here writes to
|
|
22
|
+
* the bytes it is handed.
|
|
23
|
+
*/
|
|
24
|
+
function bytesOf(s) {
|
|
25
|
+
if (s !== lastString) {
|
|
26
|
+
lastBytes = ENCODER.encode(s);
|
|
27
|
+
lastString = s;
|
|
28
|
+
}
|
|
29
|
+
return lastBytes;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `s.length`: UTF-8 byte length for strings (arrays keep their own `.length`).
|
|
34
|
+
* The engine's encoder outruns a count in JavaScript, and the loop condition
|
|
35
|
+
* `i < s.length` then leaves the bytes for the `s.charCodeAt(i)` it guards.
|
|
36
|
+
*/
|
|
37
|
+
export function strLen(x) {
|
|
38
|
+
return typeof x === "string" ? bytesOf(x).length : x.length;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `s?.length` in a `?.` chain: `undefined` when the chain has stopped at a
|
|
43
|
+
* `null` or `undefined` link, as `?.` answers, and the byte length otherwise.
|
|
44
|
+
* The chain is its argument, so it is evaluated once and its short circuit
|
|
45
|
+
* kept.
|
|
46
|
+
*/
|
|
47
|
+
export function strLenOpt(x) {
|
|
48
|
+
return x === null || x === undefined ? undefined : strLen(x);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** `s.charCodeAt(i)`: the byte at `i`, bounds-checked as `a[i]` is (JavaScript answers NaN). */
|
|
52
|
+
export function charCodeAt(s, i) {
|
|
53
|
+
const bytes = bytesOf(s);
|
|
54
|
+
const k = toIndex(i);
|
|
55
|
+
if (!(k >= 0 && k < bytes.length)) panicIndex(k, bytes.length);
|
|
56
|
+
return bytes[k];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** `s.substring(a, b)`: JavaScript's clamp and swap, over byte offsets. */
|
|
60
|
+
export function substring(s, a, b) {
|
|
61
|
+
const bytes = bytesOf(s);
|
|
62
|
+
const clamp = (v) => Math.max(0, Math.min(toIndex(v), bytes.length));
|
|
63
|
+
const from = clamp(a);
|
|
64
|
+
const to = b === undefined ? bytes.length : clamp(b);
|
|
65
|
+
return DECODER.decode(bytes.subarray(Math.min(from, to), Math.max(from, to)));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `s.slice(a, b)`: the bytes of `[a, b)` with no clamp, `b` defaulting to the
|
|
70
|
+
* byte length (WP15 §4). Out of range panics rather than clamping, so the two
|
|
71
|
+
* compares the native code emits are reproduced here rather than JavaScript's
|
|
72
|
+
* negative-from-the-end rule.
|
|
73
|
+
*/
|
|
74
|
+
export function slice(s, a, b) {
|
|
75
|
+
const bytes = bytesOf(s);
|
|
76
|
+
const from = toIndex(a);
|
|
77
|
+
const to = b === undefined ? bytes.length : toIndex(b);
|
|
78
|
+
if (!(from >= 0 && from <= to && to <= bytes.length)) panicSlice(from, to, bytes.length);
|
|
79
|
+
return DECODER.decode(bytes.subarray(from, to));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Whether `needle` sits in `bytes` at byte offset `at`. */
|
|
83
|
+
function bytesAt(bytes, at, needle) {
|
|
84
|
+
if (!(at >= 0 && at + needle.length <= bytes.length)) return false;
|
|
85
|
+
for (let j = 0; j < needle.length; j++) {
|
|
86
|
+
if (bytes[at + j] !== needle[j]) return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* `s.indexOf(sub, from)`: the first *byte* offset at or after `from`, or -1.
|
|
93
|
+
* `from` is converted as JavaScript's `ToIntegerOrInfinity` converts it (NaN
|
|
94
|
+
* is 0) and clamped into `[0, len]`, so an empty `sub` is found at `from`.
|
|
95
|
+
* The engine's `indexOf` over the bytes finds each candidate for `sub`'s
|
|
96
|
+
* first byte, and only those are compared in full.
|
|
97
|
+
*/
|
|
98
|
+
export function indexOf(s, sub, from) {
|
|
99
|
+
const haystack = bytesOf(s);
|
|
100
|
+
const at = from === undefined ? 0 : toIndex(from);
|
|
101
|
+
const start = Number.isNaN(at) ? 0 : Math.max(0, Math.min(at, haystack.length));
|
|
102
|
+
if (sub.length === 0) return start;
|
|
103
|
+
const needle = ENCODER.encode(sub);
|
|
104
|
+
const last = haystack.length - needle.length;
|
|
105
|
+
for (let i = haystack.indexOf(needle[0], start); i >= 0 && i <= last; i = haystack.indexOf(needle[0], i + 1)) {
|
|
106
|
+
if (bytesAt(haystack, i, needle)) return i;
|
|
107
|
+
}
|
|
108
|
+
return -1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** `nish_str_at`: whether `sub`'s bytes sit at byte offset `at`. */
|
|
112
|
+
function occursAt(s, at, sub) {
|
|
113
|
+
return bytesAt(bytesOf(s), at, ENCODER.encode(sub));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function startsWith(s, sub) {
|
|
117
|
+
return occursAt(s, 0, sub);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function endsWith(s, sub) {
|
|
121
|
+
const bytes = bytesOf(s);
|
|
122
|
+
const needle = ENCODER.encode(sub);
|
|
123
|
+
return bytesAt(bytes, bytes.length - needle.length, needle);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* `String.fromCharCode(c)`: the one-unit string of `c & 0xFF`. Under 128 that
|
|
128
|
+
* is the one byte native builds; from 128 it is the Latin-1 character, two
|
|
129
|
+
* UTF-8 bytes where native holds one (LANGUAGE.md's C row).
|
|
130
|
+
*/
|
|
131
|
+
export function fromCharCode(code) {
|
|
132
|
+
return String.fromCharCode(toIndex(code) & 0xff);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* `String.fromBytes(bytes, start, end)`: the bytes of `[start, end)`, one
|
|
137
|
+
* code unit each as `fromCharCode` builds them, checked as `s.slice`'s range
|
|
138
|
+
* is and failing with its panic. The copy into a `Uint8Array` reduces each
|
|
139
|
+
* element to a byte as a `Buffer` store does, and the string is built a chunk
|
|
140
|
+
* at a time so that no call passes more arguments than the engine takes.
|
|
141
|
+
*/
|
|
142
|
+
export function fromBytes(bytes, start, end) {
|
|
143
|
+
const from = toIndex(start);
|
|
144
|
+
const to = toIndex(end);
|
|
145
|
+
if (!(from >= 0 && from <= to && to <= bytes.length)) panicSlice(from, to, bytes.length);
|
|
146
|
+
const out = new Uint8Array(to - from);
|
|
147
|
+
for (let i = from; i < to; i++) out[i - from] = bytes[i];
|
|
148
|
+
let text = "";
|
|
149
|
+
for (let i = 0; i < out.length; i += 8192) {
|
|
150
|
+
text += String.fromCharCode.apply(null, out.subarray(i, i + 8192));
|
|
151
|
+
}
|
|
152
|
+
return text;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* `String.fromBytes(bytes, start, end)` as `--emit-ts` writes it: the bytes of
|
|
157
|
+
* `[start, end)` decoded as UTF-8, which is the string a native program holds,
|
|
158
|
+
* checked as `fromBytes`'s range is. A sequence that is not UTF-8 decodes to
|
|
159
|
+
* U+FFFD where native keeps the bytes. `fromBytes` above stays the
|
|
160
|
+
* one-unit-per-byte reading the prelude's programs are pinned to.
|
|
161
|
+
*/
|
|
162
|
+
export function utf8FromBytes(bytes, start, end) {
|
|
163
|
+
const from = toIndex(start);
|
|
164
|
+
const to = toIndex(end);
|
|
165
|
+
if (!(from >= 0 && from <= to && to <= bytes.length)) panicSlice(from, to, bytes.length);
|
|
166
|
+
const out = new Uint8Array(to - from);
|
|
167
|
+
for (let i = from; i < to; i++) out[i - from] = bytes[i];
|
|
168
|
+
return DECODER.decode(out);
|
|
169
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/runtime`: the host-agnostic core (runtime/core.mjs). Pair it with
|
|
3
|
+
* `nish/runtime/node` or `nish/runtime/browser`, which install a host.
|
|
4
|
+
*/
|
|
5
|
+
export * from "./core/arrays.js";
|
|
6
|
+
export * from "./core/host.js";
|
|
7
|
+
export * from "./core/numbers.js";
|
|
8
|
+
export * from "./core/result.js";
|
|
9
|
+
export * from "./core/strings.js";
|
package/runtime/core.mjs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nish builtins, host-agnostic (WP13 differential testing, WP33 R3).
|
|
3
|
+
*
|
|
4
|
+
* The differential runner (tests/differential/run.js) rewrites an Nish
|
|
5
|
+
* program into plain JavaScript and runs it under Node with this module as
|
|
6
|
+
* `__nish` (`runtime/shim.mjs`, which is this core with `runtime/node.mjs`
|
|
7
|
+
* installed as its host). Every helper here reproduces the *runtime* semantics the compiled
|
|
8
|
+
* binary has (runtime/runtime.c and runtime/runtime-os.c, the system-call half,
|
|
9
|
+
* plus the intrinsics in docs/wp7-runtime.md) where JavaScript's own semantics
|
|
10
|
+
* differ:
|
|
11
|
+
*
|
|
12
|
+
* - `number` is a 32-bit integer in the default mode (`--number-mode i32`),
|
|
13
|
+
* `i64` is a 64-bit integer, both wrapping; JS has doubles and BigInt.
|
|
14
|
+
* - `u8`/`u16`/`u32`/`u64` are unsigned and wrapping, and JavaScript has no
|
|
15
|
+
* unsigned integers at all: the narrow three live in a `number` masked
|
|
16
|
+
* back into range (`& 0xFF`, `& 0xFFFF`, `>>> 0`) and `u64` is a BigInt
|
|
17
|
+
* kept in range with `BigInt.asUintN(64, x)`.
|
|
18
|
+
* - `f32` is a 32-bit float and JavaScript has only doubles, so every `f32`
|
|
19
|
+
* result is rounded with `Math.fround`.
|
|
20
|
+
* - `s.length` is the UTF-8 byte length, and so is every index the string
|
|
21
|
+
* methods take or return: `charCodeAt` yields a byte (and bounds-checks
|
|
22
|
+
* instead of returning `NaN`), `substring` cuts on byte offsets, and
|
|
23
|
+
* `indexOf` answers with one. `slice` cuts on byte offsets too and panics
|
|
24
|
+
* on a range the string does not contain, where JavaScript would clamp and
|
|
25
|
+
* read a negative offset from the end. `String.fromCharCode` builds a
|
|
26
|
+
* one-byte string from the low 8 bits, and `String.fromBytes` one such
|
|
27
|
+
* string per byte of its range.
|
|
28
|
+
* - `a[i]` is bounds-checked: out of range prints
|
|
29
|
+
* `index out of range: <i> >= <len>` to stderr and exits 1, and so is
|
|
30
|
+
* `a.pop()` on an empty array, which has no `undefined` to return.
|
|
31
|
+
* - `toI32/toI64` from f64 saturate (NaN -> 0), integer conversions wrap.
|
|
32
|
+
* - `console.log(x)` never prints the `n` suffix of an i64 and writes
|
|
33
|
+
* synchronously so `process.exit` cannot lose output.
|
|
34
|
+
* - file I/O errors print `nish: cannot read <path>` and exit 1.
|
|
35
|
+
* - a directory listing is sorted by UTF-8 bytes, which is `strcmp`'s order
|
|
36
|
+
* and not `Array#sort`'s UTF-16 one; `monotonicNanos` reads a clock whose
|
|
37
|
+
* origin is arbitrary, so two readings can agree with a native run and a
|
|
38
|
+
* single reading never can.
|
|
39
|
+
* - `process.argv[0]` is the program (the script here, the executable
|
|
40
|
+
* natively); `parseInt` is base 10 only and saturates into i32 (0 for no
|
|
41
|
+
* digits); `parseFloat`/`Number` accept ASCII whitespace, decimal forms,
|
|
42
|
+
* `Infinity`, and a `0x` hex prefix (parseFloat too, unlike JS).
|
|
43
|
+
*
|
|
44
|
+
* The core names no `node:` module and neither `process` nor `Buffer`: what it
|
|
45
|
+
* needs of the world goes through the host `runtime/core/host.mjs` holds, so
|
|
46
|
+
* another adapter can stand where `runtime/node.mjs` stands. A check in
|
|
47
|
+
* `tests/run.js` (`runtime-hosts`) holds it to that.
|
|
48
|
+
*
|
|
49
|
+
* The rewrite rules that call these helpers are listed in docs/wp13-differential.md.
|
|
50
|
+
*/
|
|
51
|
+
export * from "./core/arrays.mjs";
|
|
52
|
+
export * from "./core/host.mjs";
|
|
53
|
+
export * from "./core/numbers.mjs";
|
|
54
|
+
export * from "./core/result.mjs";
|
|
55
|
+
export * from "./core/strings.mjs";
|
package/runtime/nish.d.ts
CHANGED
|
@@ -171,6 +171,8 @@ declare function toF64(x: number | boolean): f64;
|
|
|
171
171
|
/** Reinterpret the 64 bits of an `f64`, never convert the value. */
|
|
172
172
|
declare function f64ToBits(x: f64): i64;
|
|
173
173
|
declare function bitsToF64(bits: i64): f64;
|
|
174
|
+
/** Reinterpret the 32 bits of an `f32`, never convert the value. */
|
|
175
|
+
declare function f32ToBits(x: f32): i32;
|
|
174
176
|
|
|
175
177
|
// ---- Constant time (WP34 N6) ------------------------------------------------
|
|
176
178
|
|
|
@@ -380,6 +382,33 @@ declare function pollRemove(loop: i32, fd: i32): i32;
|
|
|
380
382
|
/** Wait up to `timeoutMs` (negative: forever); pairs `ready[2k]` token, `ready[2k + 1]` events (4: hang-up or error). */
|
|
381
383
|
declare function pollWait(loop: i32, ready: i32[], timeoutMs: i32): i32;
|
|
382
384
|
|
|
385
|
+
// ---- `nish:term` ------------------------------------------------------------------
|
|
386
|
+
//
|
|
387
|
+
// stdin without blocking and the terminal it may be: the frame clock and the
|
|
388
|
+
// keys of a terminal UI, with no `net` capability. -11 is "nothing is
|
|
389
|
+
// waiting" and -25 "not a terminal" on every platform; any other failure is
|
|
390
|
+
// the host's `-errno`. Under WASI the reading works, `readStdin` waiting for
|
|
391
|
+
// its bytes (ask `waitStdin` first), and the terminal answers -25. Under Node `isTerminal` and `terminalSize` answer and the other four
|
|
392
|
+
// throw.
|
|
393
|
+
|
|
394
|
+
/** Up to `len` bytes of stdin into `buf` from `off`, never blocking: the count, 0 at end of stream, or -11 when nothing is waiting. */
|
|
395
|
+
declare function readStdin(buf: u8[], off: i32, len: i32): i32;
|
|
396
|
+
/** Wait up to `timeoutMs` (negative: forever) for stdin to be readable (its end and an error count): `true` when it is, `false` when the time passed. */
|
|
397
|
+
declare function waitStdin(timeoutMs: i32): boolean;
|
|
398
|
+
/** Whether descriptor `fd` is a terminal (`isatty`). */
|
|
399
|
+
declare function isTerminal(fd: i32): boolean;
|
|
400
|
+
/** `[columns, rows]` of the terminal on stdout, else stderr; `[-25, 0]` when neither is one, `[0, 0]` when it has no size set. */
|
|
401
|
+
declare function terminalSize(): i32[];
|
|
402
|
+
/**
|
|
403
|
+
* Raw mode on stdin's terminal, or the mode it had back: 0, -25 when stdin is
|
|
404
|
+
* not a terminal. No echo, no line editing; Ctrl-C stays SIGINT and "\n"
|
|
405
|
+
* still starts a line. The terminal is put back at exit, on a panic and on
|
|
406
|
+
* every signal that ends the program, SIGKILL aside.
|
|
407
|
+
*/
|
|
408
|
+
declare function setRawMode(on: boolean): i32;
|
|
409
|
+
/** Bytes the runtime writes to the terminal on stdout at exit, on a panic and on every signal that ends the program (SIGKILL aside), before termios is restored. Replaces the previous string; "" clears it. 0, -25 when stdout is not a terminal, -7 (E2BIG) past NISH_TERM_RESTORE_MAX bytes. The cap is 4096 bytes. */
|
|
410
|
+
declare function setTerminalRestore(bytes: string): i32;
|
|
411
|
+
|
|
383
412
|
// ---- `Date` and `crypto` (WP34 N3) -----------------------------------------------
|
|
384
413
|
//
|
|
385
414
|
// `lib.es2022` already declares `Date`, whose `now()` is Nish's one member of it,
|
|
@@ -535,6 +564,21 @@ declare module "nish:io" {
|
|
|
535
564
|
export function panic(message: string): never;
|
|
536
565
|
}
|
|
537
566
|
|
|
567
|
+
declare module "nish:term" {
|
|
568
|
+
/** Up to `len` bytes of stdin into `buf` from `off`, never blocking: the count, 0 at end of stream, or -11 when nothing is waiting. */
|
|
569
|
+
export function readStdin(buf: u8[], off: i32, len: i32): i32;
|
|
570
|
+
/** Wait up to `timeoutMs` (negative: forever) for stdin to be readable (its end and an error count): `true` when it is, `false` when the time passed. */
|
|
571
|
+
export function waitStdin(timeoutMs: i32): boolean;
|
|
572
|
+
/** Whether descriptor `fd` is a terminal (`isatty`). */
|
|
573
|
+
export function isTerminal(fd: i32): boolean;
|
|
574
|
+
/** `[columns, rows]` of the terminal on stdout, else stderr; `[-25, 0]` when neither is one, `[0, 0]` when it has no size set. */
|
|
575
|
+
export function terminalSize(): i32[];
|
|
576
|
+
/** Raw mode on stdin's terminal (0), or the mode it had back; -25 when stdin is not a terminal. Restored at exit, on a panic and on every signal that ends the program, SIGKILL aside. */
|
|
577
|
+
export function setRawMode(on: boolean): i32;
|
|
578
|
+
/** Bytes the runtime writes to the terminal on stdout at exit, on a panic and on every signal that ends the program (SIGKILL aside), before termios is restored. Replaces the previous string; "" clears it. 0, -25 when stdout is not a terminal, -7 (E2BIG) past NISH_TERM_RESTORE_MAX bytes. */
|
|
579
|
+
export function setTerminalRestore(bytes: string): i32;
|
|
580
|
+
}
|
|
581
|
+
|
|
538
582
|
// `nish:secret` has source behind it, `std/secret.ts`, but a program imports it
|
|
539
583
|
// by this name, so `tsc` reads these declarations rather than that file. The
|
|
540
584
|
// class is declared with a private constructor and a private field: `tsc` then
|
package/runtime/nish.h
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* functions, the directory and subprocess calls, `nish_getenv`, the monotonic
|
|
10
10
|
* clock, `nish_platform` / `nish_arch` — and runtime-parallel.c, runtime-host.c,
|
|
11
11
|
* runtime-net.c, runtime-simd.c (the byte searches that read a vector at a
|
|
12
|
-
* time)
|
|
12
|
+
* time), runtime-aes.c (AES-GCM on the CPU's AES instructions) and
|
|
13
|
+
* runtime-term.c (stdin and the terminal, `nish:term`) split off
|
|
13
14
|
* the same way since. They are separate so that each carries its own measured
|
|
14
15
|
* code-size ceiling (docs/wp7-runtime.md, "Runtime additions and budget"); a
|
|
15
16
|
* host links all of them, which `scripts/build.sh` does for you when handed
|
|
@@ -154,6 +155,22 @@ int64_t nish_str_index_of_from(const nish_str *s, const nish_str *sub, int64_t f
|
|
|
154
155
|
set outside 1..16 bytes is answered by the scalar path rather than refused,
|
|
155
156
|
but only 1..16 is the contract. Variable-time: never call it on a secret. */
|
|
156
157
|
int64_t nish_str_index_of_any(const nish_str *s, const nish_str *set, int64_t from);
|
|
158
|
+
/* WP38 S6a, the byte-class runs of `nish/bytes`: the first index i in
|
|
159
|
+
[from, to) whose byte p[i] is outside the class, or `to`. `p` is an array's
|
|
160
|
+
data and the caller has clamped 0 <= from <= to <= its length, so no byte at
|
|
161
|
+
or past `to` is read. `skip_blank` steps over JSON's four blanks (space, tab,
|
|
162
|
+
newline, carriage return) and `skip_digits` over 0-9. `skip_json_text` steps
|
|
163
|
+
over printable ASCII other than `"` and `\`, and over every well-formed
|
|
164
|
+
UTF-8 sequence (RFC 3629) that ends at or before `to`; a malformed or cut-off
|
|
165
|
+
sequence stops the run at its lead byte. `skip_json_plain` steps over every
|
|
166
|
+
byte from 0x20 up other than `"` and `\` -- what a JSON writer copies without
|
|
167
|
+
an escape -- with no UTF-8 check. They live in runtime/runtime-bytes.c and
|
|
168
|
+
choose their path as `nish_str_index_of_any` does. Variable-time: never call
|
|
169
|
+
them on a secret. */
|
|
170
|
+
int64_t nish_bytes_skip_blank(const uint8_t *p, int64_t from, int64_t to);
|
|
171
|
+
int64_t nish_bytes_skip_digits(const uint8_t *p, int64_t from, int64_t to);
|
|
172
|
+
int64_t nish_bytes_skip_json_text(const uint8_t *p, int64_t from, int64_t to);
|
|
173
|
+
int64_t nish_bytes_skip_json_plain(const uint8_t *p, int64_t from, int64_t to);
|
|
157
174
|
/* `console.log(s)`: one write(2) of the bytes plus a newline to stdout. */
|
|
158
175
|
void nish_print(const nish_str *s);
|
|
159
176
|
/* `console.error` and the newline-free `write` / `writeError`: fd 1 or 2. */
|
|
@@ -170,6 +187,13 @@ void nish_panic_claim(void);
|
|
|
170
187
|
#else
|
|
171
188
|
#define nish_panic_claim() ((void)0)
|
|
172
189
|
#endif
|
|
190
|
+
/* How a panic that skips atexit ends: `_exit(1)`, after `nish_fatal_hook`
|
|
191
|
+
when it is set (runtime.c). The hook is the terminal's restore while
|
|
192
|
+
`setRawMode(true)` holds or `setTerminalRestore` bytes wait
|
|
193
|
+
(runtime-term.c), and null otherwise. Under `-DNISH_FUZZ_ABORT`
|
|
194
|
+
(scripts/fuzz.sh) it and `nish_panic` call `abort()`. */
|
|
195
|
+
__attribute__((noreturn)) void nish_fatal_exit(void);
|
|
196
|
+
extern void (*nish_fatal_hook)(void);
|
|
173
197
|
/* Number to string, as `${n}` does: decimal for integers, shortest round-trip (JS Number#toString) for f64. */
|
|
174
198
|
nish_str *nish_str_from_i32(int32_t v);
|
|
175
199
|
nish_str *nish_str_from_f64(double v);
|
|
@@ -244,11 +268,28 @@ typedef struct nish_array { uint64_t len; uint64_t cap; char *data; } nish_array
|
|
|
244
268
|
* `elem_size` is `sizeof` one element, so for a record element type it is
|
|
245
269
|
* `sizeof(struct Point)` and the block is a C array of them. */
|
|
246
270
|
nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len);
|
|
271
|
+
/* `nish/regex`'s match-start search: the first i >= from with i + depth <=
|
|
272
|
+
len where every s[i + j], j < depth, is in position j's byte set, or -1.
|
|
273
|
+
Position j's set is written twice: as the 32 bytes of masks from 32 * j,
|
|
274
|
+
where byte b is in it when bit (b >> 4) & 7 of byte (b & 15) | (b >> 7) << 4
|
|
275
|
+
is set, and as the 256 bytes from 32 * depth + 256 * j, where it is in it
|
|
276
|
+
when byte b of them is not 0; the two must agree. The vector paths look for
|
|
277
|
+
the width positions from off first, in the first form, and test the others
|
|
278
|
+
in the second; the answer does not depend on them. from is already in
|
|
279
|
+
[0, len], depth is 1..8, width is 1..4, off + width <= depth and masks
|
|
280
|
+
holds at least 288 * depth bytes. It lives
|
|
281
|
+
in runtime/runtime-simd.c and picks its path as nish_str_index_of_any does.
|
|
282
|
+
Variable-time: never call it on a secret. */
|
|
283
|
+
int64_t nish_regex_scan(const nish_str *s, const nish_array *masks, int64_t depth, int64_t off, int64_t width,
|
|
284
|
+
int64_t from);
|
|
247
285
|
/* No array grows past 2^31 - 1 elements and no string past 2^31 - 1 bytes:
|
|
248
286
|
* `nish_array_grow`, `nish_alloc_array` and `nish_str_concat` fail as an
|
|
249
287
|
* allocation does (`nish: out of memory`, exit 1; a trap on wasm) rather than
|
|
250
288
|
* make one, because under --number-mode i32 its `length` would read back
|
|
251
|
-
* negative (docs/security/codegen.md K1-6, docs/security/runtime.md RT-2).
|
|
289
|
+
* negative (docs/security/codegen.md K1-6, docs/security/runtime.md RT-2).
|
|
290
|
+
* A host that hands an array in keeps the same bound: the N-API shim refuses a
|
|
291
|
+
* longer typed array, and a C host must not pass a `len` past it
|
|
292
|
+
* (docs/security/bytes.md, BYTES-1). */
|
|
252
293
|
/* The cold paths compiled code calls: `push` when len == cap, a failed bounds
|
|
253
294
|
* check. `nish_array_grow` moves `cap * elem_size` bytes into a fresh arena
|
|
254
295
|
* block and writes `data`/`cap`, so for a record element type it relocates the
|
|
@@ -315,6 +356,13 @@ void nish_panic_slice(int64_t start, int64_t end, int64_t len);
|
|
|
315
356
|
extern nish_array *nish_argv;
|
|
316
357
|
void nish_argv_init(int32_t argc, char **argv);
|
|
317
358
|
|
|
359
|
+
/* The 128-bit SipHash-1-3 key of every `Map` and `Set` string hash
|
|
360
|
+
* (docs/security/json.md, JSON-13). A constructor the compiled module
|
|
361
|
+
* registers fills it from `getentropy` before `main`, once per process, and
|
|
362
|
+
* sets bit 0 of word 0 so a filled key is never all zero; nothing writes it
|
|
363
|
+
* afterwards. A host must not change it while any table exists. */
|
|
364
|
+
extern uint64_t nish_hash_key[2];
|
|
365
|
+
|
|
318
366
|
/* ---- Directories and subprocesses (WP14 D4) -----------------------------
|
|
319
367
|
* The calls a self-hosted driver needs to find its inputs and link its own
|
|
320
368
|
* output. Every one of them answers a value instead of exiting, exactly as
|
|
@@ -541,6 +589,31 @@ int32_t nish_poll_modify(int32_t loop, int32_t fd, int32_t events, int32_t token
|
|
|
541
589
|
int32_t nish_poll_remove(int32_t loop, int32_t fd);
|
|
542
590
|
int32_t nish_poll_wait(int32_t loop, nish_array *ready, int32_t timeout_ms);
|
|
543
591
|
|
|
592
|
+
/* ---- The terminal, runtime/runtime-term.c, `nish:term` -------------------
|
|
593
|
+
*
|
|
594
|
+
* `nish_read_stdin(buf, off, len)`: at most `len` bytes of stdin into `buf`
|
|
595
|
+
* from `off`, without blocking: the count, 0 at the end of the stream, -11
|
|
596
|
+
* when nothing is waiting, or another `-errno`. `nish_wait_stdin(timeout_ms)`:
|
|
597
|
+
* whether stdin became readable before `timeout_ms` passed (negative waits
|
|
598
|
+
* for ever). `nish_is_terminal(fd)`: `isatty`. `nish_terminal_size()`: a fresh
|
|
599
|
+
* `i32[]` of `[columns, rows]`, or `[-errno, 0]`, -25 for "not a terminal".
|
|
600
|
+
* `nish_set_raw_mode(on)`: 0, -25 when stdin is not a terminal, or `-errno`;
|
|
601
|
+
* the saved termios comes back on `false`, at exit, on a panic and on every
|
|
602
|
+
* signal left at its default that ends the program, SIGKILL aside (the list is
|
|
603
|
+
* runtime-term.c's). -11 and -25 are those
|
|
604
|
+
* numbers on WASI too, where the three TTY calls answer "not a terminal".
|
|
605
|
+
* `nish_set_terminal_restore(bytes)`: keeps up to `NISH_TERM_RESTORE_MAX`
|
|
606
|
+
* bytes to write to stdout, once, at exit, on a panic and on each of those
|
|
607
|
+
* signals, before the termios comes back; "" keeps none. 0, -7 past the cap,
|
|
608
|
+
* -25 when stdout is not a terminal (always, for bytes, on WASI). */
|
|
609
|
+
#define NISH_TERM_RESTORE_MAX 4096
|
|
610
|
+
int32_t nish_read_stdin(nish_array *buf, int64_t off, int64_t len);
|
|
611
|
+
bool nish_wait_stdin(int32_t timeout_ms);
|
|
612
|
+
bool nish_is_terminal(int32_t fd);
|
|
613
|
+
nish_array *nish_terminal_size(void);
|
|
614
|
+
int32_t nish_set_raw_mode(bool on);
|
|
615
|
+
int32_t nish_set_terminal_restore(const nish_str *bytes);
|
|
616
|
+
|
|
544
617
|
/* String to number (WP7), ASCII whitespace only. mode 0 is `parseFloat`
|
|
545
618
|
* (longest JS decimal literal or `Infinity`, else NaN), mode 1 is `Number`
|
|
546
619
|
* (the whole string, trimmed; blank is 0; `0x` hex accepted, as in JS),
|
package/runtime/nish.mjs
CHANGED
|
@@ -52,7 +52,10 @@
|
|
|
52
52
|
*
|
|
53
53
|
* Everything here delegates to `runtime/shim.mjs`, the module the differential
|
|
54
54
|
* harness already uses, so the two cannot drift apart: a semantic fixed there
|
|
55
|
-
* is fixed here in the same commit.
|
|
55
|
+
* is fixed here in the same commit. The shim is itself two layers: the
|
|
56
|
+
* host-agnostic core (`runtime/core.mjs`) and the Node host
|
|
57
|
+
* (`runtime/node.mjs`) it installs, so this module adds only what is Node's to
|
|
58
|
+
* add — the globals, the module hooks and `process.argv`.
|
|
56
59
|
*/
|
|
57
60
|
import { registerHooks } from "node:module";
|
|
58
61
|
import * as shim from "./shim.mjs";
|
|
@@ -131,6 +134,17 @@ for (const name of [
|
|
|
131
134
|
provide(name, () => shim.noNetReading(name));
|
|
132
135
|
}
|
|
133
136
|
|
|
137
|
+
// `nish:term`. Whether a descriptor is a terminal and how big stdout's is have
|
|
138
|
+
// Node's answers; reading stdin, waiting for it and raw mode throw, each naming
|
|
139
|
+
// itself, for the net functions' reason, and restore bytes throw for their own
|
|
140
|
+
// (core/host.mjs).
|
|
141
|
+
provide("isTerminal", shim.isTerminal);
|
|
142
|
+
provide("terminalSize", shim.terminalSize);
|
|
143
|
+
for (const name of ["readStdin", "waitStdin", "setRawMode"]) {
|
|
144
|
+
provide(name, () => shim.noTermReading(name));
|
|
145
|
+
}
|
|
146
|
+
provide("setTerminalRestore", shim.setTerminalRestore);
|
|
147
|
+
|
|
134
148
|
// `crypto.getRandomValues(bytes)` (WP34 N3). Node has the global, but it
|
|
135
149
|
// takes only a typed array and a `u8[]` is a plain `Array` here, so the one
|
|
136
150
|
// method is replaced on Node's own `crypto` object by one that fills a plain
|
|
@@ -179,6 +193,7 @@ provide("toU32", (x) => shim.convert(x, "f64", "u32"));
|
|
|
179
193
|
provide("toU64", (x) => shim.convert(x, "f64", "u64"));
|
|
180
194
|
provide("f64ToBits", shim.f64ToBits);
|
|
181
195
|
provide("bitsToF64", shim.bitsToF64);
|
|
196
|
+
provide("f32ToBits", shim.f32ToBits);
|
|
182
197
|
|
|
183
198
|
// Constant time (WP34 N6). Pure functions of their operands, so the answers
|
|
184
199
|
// agree with a native run; the timing does not, and is not claimed here.
|
|
@@ -274,11 +289,13 @@ provide("arena", shim.arena);
|
|
|
274
289
|
// `nish:secret` is answered here as well, because the standard library imports
|
|
275
290
|
// it (`std/crypto/p256.ts` and its neighbours): its names are
|
|
276
291
|
// `runtime/shim.mjs`'s, where a `Secret` is a plain wrapper and `wipe`
|
|
277
|
-
// zero-fills it. `nish:unsafe`
|
|
278
|
-
// globals installed above: its functions are globals of this module either
|
|
292
|
+
// zero-fills it. `nish:unsafe` and `nish:term` are each answered by a module
|
|
293
|
+
// that hands back the globals installed above: its functions are globals of this module either
|
|
279
294
|
// way, and the import is what the compiler requires a program to write.
|
|
280
295
|
const UNSAFE_EXPORTS = ["uncheckedGet", "uncheckedSet", "wrappingAdd", "wrappingSub", "wrappingMul"];
|
|
281
296
|
const unsafeSource = UNSAFE_EXPORTS.map((name) => `export const ${name} = globalThis.${name};`).join("\n");
|
|
297
|
+
const TERM_EXPORTS = ["readStdin", "waitStdin", "isTerminal", "terminalSize", "setRawMode", "setTerminalRestore"];
|
|
298
|
+
const termSource = TERM_EXPORTS.map((name) => `export const ${name} = globalThis.${name};`).join("\n");
|
|
282
299
|
registerHooks({
|
|
283
300
|
resolve: (specifier, context, next) => {
|
|
284
301
|
if (specifier === "nish:secret") {
|
|
@@ -287,6 +304,9 @@ registerHooks({
|
|
|
287
304
|
if (specifier === "nish:unsafe") {
|
|
288
305
|
return { url: `data:text/javascript,${encodeURIComponent(unsafeSource)}`, shortCircuit: true };
|
|
289
306
|
}
|
|
307
|
+
if (specifier === "nish:term") {
|
|
308
|
+
return { url: `data:text/javascript,${encodeURIComponent(termSource)}`, shortCircuit: true };
|
|
309
|
+
}
|
|
290
310
|
return specifier.startsWith("nish/")
|
|
291
311
|
? next(new URL(`../std/${specifier.slice("nish/".length)}.ts`, import.meta.url).href, context)
|
|
292
312
|
: next(specifier, context);
|