@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.
- package/INSTALL.md +58 -28
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +3 -2
- 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 +99 -5
- package/runtime/nish.h +289 -19
- package/runtime/nish.mjs +39 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +8 -7
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +214 -16
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +235 -11
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +5 -14
- package/runtime/runtime.c +257 -17
- package/runtime/shim.mjs +6 -915
- 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 +437 -28
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +108 -58
- package/std/base64.ts +116 -0
- package/std/bytes.ts +419 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +382 -54
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +20 -131
- package/std/json-tokenizer.ts +4237 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1182 -0
- package/std/json.ts +1202 -34
- package/std/math.ts +608 -0
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +501 -62
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +261 -43
- package/std/net/http3.ts +187 -28
- package/std/net/postgres.ts +1875 -0
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +108 -11
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- 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/threads.ts +438 -14
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/runtime/shim.mjs
CHANGED
|
@@ -1,918 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Nish builtins for Node
|
|
3
|
-
*
|
|
4
|
-
* The differential runner
|
|
5
|
-
*
|
|
6
|
-
* `__nish`. Every helper here reproduces the *runtime* semantics the compiled
|
|
7
|
-
* binary has (runtime/runtime.c and runtime/runtime-os.c, the system-call half,
|
|
8
|
-
* plus the intrinsics in docs/wp7-runtime.md) where JavaScript's own semantics
|
|
9
|
-
* differ:
|
|
10
|
-
*
|
|
11
|
-
* - `number` is a 32-bit integer in the default mode (`--number-mode i32`),
|
|
12
|
-
* `i64` is a 64-bit integer, both wrapping; JS has doubles and BigInt.
|
|
13
|
-
* - `u8`/`u16`/`u32`/`u64` are unsigned and wrapping, and JavaScript has no
|
|
14
|
-
* unsigned integers at all: the narrow three live in a `number` masked
|
|
15
|
-
* back into range (`& 0xFF`, `& 0xFFFF`, `>>> 0`) and `u64` is a BigInt
|
|
16
|
-
* kept in range with `BigInt.asUintN(64, x)`.
|
|
17
|
-
* - `f32` is a 32-bit float and JavaScript has only doubles, so every `f32`
|
|
18
|
-
* result is rounded with `Math.fround`.
|
|
19
|
-
* - `s.length` is the UTF-8 byte length, and so is every index the string
|
|
20
|
-
* methods take or return: `charCodeAt` yields a byte (and bounds-checks
|
|
21
|
-
* instead of returning `NaN`), `substring` cuts on byte offsets, and
|
|
22
|
-
* `indexOf` answers with one. `slice` cuts on byte offsets too and panics
|
|
23
|
-
* on a range the string does not contain, where JavaScript would clamp and
|
|
24
|
-
* read a negative offset from the end. `String.fromCharCode` builds a
|
|
25
|
-
* one-byte string from the low 8 bits.
|
|
26
|
-
* - `a[i]` is bounds-checked: out of range prints
|
|
27
|
-
* `index out of range: <i> >= <len>` to stderr and exits 1, and so is
|
|
28
|
-
* `a.pop()` on an empty array, which has no `undefined` to return.
|
|
29
|
-
* - `toI32/toI64` from f64 saturate (NaN -> 0), integer conversions wrap.
|
|
30
|
-
* - `console.log(x)` never prints the `n` suffix of an i64 and writes
|
|
31
|
-
* synchronously so `process.exit` cannot lose output.
|
|
32
|
-
* - file I/O errors print `nish: cannot read <path>` and exit 1.
|
|
33
|
-
* - a directory listing is sorted by UTF-8 bytes, which is `strcmp`'s order
|
|
34
|
-
* and not `Array#sort`'s UTF-16 one; `monotonicNanos` reads a clock whose
|
|
35
|
-
* origin is arbitrary, so two readings can agree with a native run and a
|
|
36
|
-
* single reading never can.
|
|
37
|
-
* - `process.argv[0]` is the program (the script here, the executable
|
|
38
|
-
* natively); `parseInt` is base 10 only and saturates into i32 (0 for no
|
|
39
|
-
* digits); `parseFloat`/`Number` accept ASCII whitespace, decimal forms,
|
|
40
|
-
* `Infinity`, and a `0x` hex prefix (parseFloat too, unlike JS).
|
|
41
|
-
*
|
|
42
|
-
* The rewrite rules that call these helpers are listed in docs/wp13-differential.md.
|
|
2
|
+
* Nish builtins for Node: the host-agnostic core (`runtime/core.mjs`, whose
|
|
3
|
+
* header lists the semantics) with the Node host (`runtime/node.mjs`)
|
|
4
|
+
* installed. The differential runner and `runtime/nish.mjs` import this module,
|
|
5
|
+
* and the frozen rewrites name its exports.
|
|
43
6
|
*/
|
|
44
|
-
import
|
|
45
|
-
import { webcrypto } from "node:crypto";
|
|
46
|
-
import fs from "node:fs";
|
|
47
|
-
import os from "node:os";
|
|
7
|
+
import "./node.mjs";
|
|
48
8
|
|
|
49
|
-
|
|
50
|
-
const I32_MAX = 2147483647;
|
|
51
|
-
const I64_MIN = -(1n << 63n);
|
|
52
|
-
const I64_MAX = (1n << 63n) - 1n;
|
|
53
|
-
|
|
54
|
-
/** `trunc i64 -> i32` (wrap) for BigInt, `llvm.fptosi.sat.i32.f64` for numbers. */
|
|
55
|
-
export function toI32(x) {
|
|
56
|
-
if (typeof x === "bigint") return Number(BigInt.asIntN(32, x));
|
|
57
|
-
if (Number.isNaN(x)) return 0;
|
|
58
|
-
if (x >= I32_MAX) return I32_MAX;
|
|
59
|
-
if (x <= I32_MIN) return I32_MIN;
|
|
60
|
-
return Math.trunc(x) | 0;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** `sext i32 -> i64` for int32 numbers, `llvm.fptosi.sat.i64.f64` for doubles. */
|
|
64
|
-
export function toI64(x) {
|
|
65
|
-
if (typeof x === "bigint") return x;
|
|
66
|
-
if (Number.isNaN(x)) return 0n;
|
|
67
|
-
if (x >= 9223372036854775808) return I64_MAX;
|
|
68
|
-
if (x <= -9223372036854775808) return I64_MIN;
|
|
69
|
-
return BigInt(Math.trunc(x));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** `sitofp` for both integer widths; a double is returned unchanged. */
|
|
73
|
-
export function toF64(x) {
|
|
74
|
-
return typeof x === "bigint" ? Number(x) : x;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* `f64ToBits` / `bitsToF64`: reinterpret the 64 bits, never convert the value.
|
|
79
|
-
* The compiler lowers each to one `bitcast`; here it is a one-element
|
|
80
|
-
* DataView, which is the only way JavaScript lets you see a double's bits.
|
|
81
|
-
* The result is signed, matching the i64 the compiler produces.
|
|
82
|
-
*/
|
|
83
|
-
const BITS = new DataView(new ArrayBuffer(8));
|
|
84
|
-
|
|
85
|
-
export function f64ToBits(x) {
|
|
86
|
-
BITS.setFloat64(0, x);
|
|
87
|
-
return BigInt.asIntN(64, BITS.getBigUint64(0));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function bitsToF64(b) {
|
|
91
|
-
BITS.setBigInt64(0, BigInt.asIntN(64, b));
|
|
92
|
-
return BITS.getFloat64(0);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* `ctSelect` / `ctEq` (WP34 N6). A `u32` is a `number` here and a `u64` a
|
|
97
|
-
* BigInt, so the operands' kind picks the width, and a mix of the two — which
|
|
98
|
-
* the native checker refuses, and which a `u64` written as a bare literal is
|
|
99
|
-
* under an unrewritten run — throws the `TypeError` BigInt arithmetic throws
|
|
100
|
-
* rather than comparing a number with a BigInt and answering zero. JavaScript's
|
|
101
|
-
* `&` reads a `number` as a signed 32-bit integer, so each answer is put back in
|
|
102
|
-
* range with `>>> 0` or `asUintN(64, ...)`. These branch: only the native
|
|
103
|
-
* lowering promises constant time.
|
|
104
|
-
*/
|
|
105
|
-
function ctWide(name, first, second, third) {
|
|
106
|
-
const wide = typeof first === "bigint";
|
|
107
|
-
if ((typeof second === "bigint") !== wide || (typeof third === "bigint") !== wide) {
|
|
108
|
-
throw new TypeError(`${name}: cannot mix a u64 (BigInt) with a u32 (number)`);
|
|
109
|
-
}
|
|
110
|
-
return wide;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const U64_ONES = (1n << 64n) - 1n;
|
|
114
|
-
|
|
115
|
-
export function ctSelect(mask, a, b) {
|
|
116
|
-
if (ctWide("ctSelect", mask, a, b)) return wrapU64((a & mask) | (b & ~mask));
|
|
117
|
-
return ((a & mask) | (b & ~mask)) >>> 0;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function ctEq(a, b) {
|
|
121
|
-
// `ctEq` has two operands, so the second one stands in for the third.
|
|
122
|
-
if (ctWide("ctEq", a, b, b)) return wrapU64(a ^ b) === 0n ? U64_ONES : 0n;
|
|
123
|
-
return (a ^ b) === 0 ? 0xffffffff : 0;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* `nish:secret` (docs/LANGUAGE.md, "Secrets"). Natively the compiler holds a
|
|
128
|
-
* `Secret` to its rules and `wipe` is a volatile store of zeros; here the rules
|
|
129
|
-
* have already been checked by the compiler or by nobody, so a `Secret` is a
|
|
130
|
-
* plain wrapper and `wipe` zero-fills what it holds. `runtime/nish.mjs`
|
|
131
|
-
* resolves the `nish:secret` specifier to this module, so these are its
|
|
132
|
-
* exports by name. A zero-fill under Node promises nothing about copies the
|
|
133
|
-
* engine made, which is the native wipe's whole point and is not claimed here.
|
|
134
|
-
*/
|
|
135
|
-
export class Secret {
|
|
136
|
-
constructor(value) {
|
|
137
|
-
this.value = value;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function secret(value) {
|
|
142
|
-
return new Secret(value);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export function expose(s, f) {
|
|
146
|
-
return f(s.value);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function exposeWith(s, arg, f) {
|
|
150
|
-
return f(s.value, arg);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** Zero an array of integers, or every integer field of a record: `0` for a number, `0n` for an `i64` or `u64`. */
|
|
154
|
-
function zeroFill(target) {
|
|
155
|
-
if (Array.isArray(target) || ArrayBuffer.isView(target)) {
|
|
156
|
-
for (let i = 0; i < target.length; i++) {
|
|
157
|
-
target[i] = typeof target[i] === "bigint" ? 0n : 0;
|
|
158
|
-
}
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
for (const key of Object.keys(target)) {
|
|
162
|
-
const field = target[key];
|
|
163
|
-
if (typeof field === "bigint") target[key] = 0n;
|
|
164
|
-
else if (typeof field === "number") target[key] = 0;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export function wipe(target) {
|
|
169
|
-
zeroFill(target instanceof Secret ? target.value : target);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** Wrap a BigInt to the i64 range: every i64 `+ - * /` and unary minus goes through here. */
|
|
173
|
-
export function wrapI64(x) {
|
|
174
|
-
return BigInt.asIntN(64, x);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* The i64 shifts. BigInt has no `>>>` and neither of its shifts masks the
|
|
179
|
-
* count, so all three do here what `codegen/emit/bitwise.ts` emits: mask the
|
|
180
|
-
* count to 6 bits, then shift. `lshrI64` reads the operand as unsigned before
|
|
181
|
-
* shifting and hands back the signed reading of the result, which is `lshr`.
|
|
182
|
-
*/
|
|
183
|
-
export function shlI64(a, b) {
|
|
184
|
-
return BigInt.asIntN(64, a << (b & 63n));
|
|
185
|
-
}
|
|
186
|
-
export function ashrI64(a, b) {
|
|
187
|
-
return a >> (b & 63n);
|
|
188
|
-
}
|
|
189
|
-
export function lshrI64(a, b) {
|
|
190
|
-
return BigInt.asIntN(64, BigInt.asUintN(64, a) >> (b & 63n));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** `llvm.abs.i64(x, false)`: the minimum value wraps to itself. */
|
|
194
|
-
export function absI64(x) {
|
|
195
|
-
return BigInt.asIntN(64, x < 0n ? -x : x);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** `llvm.smin.i64` / `llvm.smax.i64` (Math.min/max reject BigInt). */
|
|
199
|
-
export function minI64(a, b) {
|
|
200
|
-
return a < b ? a : b;
|
|
201
|
-
}
|
|
202
|
-
export function maxI64(a, b) {
|
|
203
|
-
return a > b ? a : b;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// ---- Unsigned integers (WP15) -------------------------------------------------------
|
|
207
|
-
//
|
|
208
|
-
// JavaScript has no unsigned integer type, so every unsigned result is masked
|
|
209
|
-
// back into its width right where the compiled code would have relied on the
|
|
210
|
-
// LLVM type. u8/u16/u32 fit a `number` exactly (2^32 - 1 < 2^53); u64 is a
|
|
211
|
-
// BigInt, so it goes through `wrapU64` the way i64 goes through `wrapI64`.
|
|
212
|
-
|
|
213
|
-
/** Wrap into the u64 range: every u64 `+ - * /`, unary minus and shift ends here. */
|
|
214
|
-
export function wrapU64(x) {
|
|
215
|
-
return BigInt.asUintN(64, x);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* The u64 shifts. A u64 is already held as a non-negative BigInt, so its `>>`
|
|
220
|
-
* *is* the logical shift; what these add over the operator is the count mask
|
|
221
|
-
* (BigInt does not mask) and the wrap back into the unsigned range.
|
|
222
|
-
*/
|
|
223
|
-
export function shlU64(a, b) {
|
|
224
|
-
return BigInt.asUintN(64, a << (b & 63n));
|
|
225
|
-
}
|
|
226
|
-
export function lshrU64(a, b) {
|
|
227
|
-
return BigInt.asUintN(64, a) >> (b & 63n);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* The widths, as the rewriter names them. `bits` drives every mask and
|
|
232
|
-
* `BigInt.asIntN`/`asUintN` call; `big` says whether the value is a BigInt
|
|
233
|
-
* (i64/u64) or a `number` in JavaScript.
|
|
234
|
-
*/
|
|
235
|
-
const INT_KINDS = {
|
|
236
|
-
i32: { bits: 32, signed: true, big: false },
|
|
237
|
-
i64: { bits: 64, signed: true, big: true },
|
|
238
|
-
u8: { bits: 8, signed: false, big: false },
|
|
239
|
-
u16: { bits: 16, signed: false, big: false },
|
|
240
|
-
u32: { bits: 32, signed: false, big: false },
|
|
241
|
-
u64: { bits: 64, signed: false, big: true },
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
/** `llvm.umin`/`umax` on u64 (`Math.min`/`max` reject BigInt); the u32-and-below widths use Math. */
|
|
245
|
-
export function minU64(a, b) {
|
|
246
|
-
return a < b ? a : b;
|
|
247
|
-
}
|
|
248
|
-
export function maxU64(a, b) {
|
|
249
|
-
return a > b ? a : b;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Every numeric conversion whose source or target is unsigned (`toU8`,
|
|
254
|
-
* `toU32(i)`, `toI32(u)`, ...), as one function taking the kind names the
|
|
255
|
-
* checker recorded. Doing it through BigInt is what makes the whole matrix
|
|
256
|
-
* exact in one place: an integer source is turned into its true mathematical
|
|
257
|
-
* value, then `asIntN`/`asUintN` performs the sign-extend, zero-extend or
|
|
258
|
-
* truncate that the target's width and signedness call for — which is exactly
|
|
259
|
-
* what `sext`/`zext`/`trunc` do natively.
|
|
260
|
-
*/
|
|
261
|
-
export function convert(x, from, to) {
|
|
262
|
-
const num = typeof x === "bigint" ? Number(x) : x;
|
|
263
|
-
if (to === "f64") return num; // sitofp / uitofp / fpext
|
|
264
|
-
if (to === "f32") return Math.fround(num); // ... / fptrunc, then rounded to a float
|
|
265
|
-
const target = INT_KINDS[to];
|
|
266
|
-
const lo = target.signed ? -(1n << BigInt(target.bits - 1)) : 0n;
|
|
267
|
-
const hi = target.signed ? (1n << BigInt(target.bits - 1)) - 1n : (1n << BigInt(target.bits)) - 1n;
|
|
268
|
-
let exact;
|
|
269
|
-
if (INT_KINDS[from] === undefined) {
|
|
270
|
-
// A float source (f32 or f64) goes through the saturating intrinsics: NaN
|
|
271
|
-
// is 0 and out-of-range values clamp. The bounds are BigInt because
|
|
272
|
-
// 2^64 - 1 has no exact `number`.
|
|
273
|
-
if (Number.isNaN(x)) exact = 0n;
|
|
274
|
-
else if (x <= Number(lo)) exact = lo;
|
|
275
|
-
else if (x >= Number(hi)) exact = hi;
|
|
276
|
-
else exact = BigInt(Math.trunc(x));
|
|
277
|
-
} else {
|
|
278
|
-
exact = BigInt(x); // sext/zext/trunc, below
|
|
279
|
-
}
|
|
280
|
-
const wrapped = target.signed ? BigInt.asIntN(target.bits, exact) : BigInt.asUintN(target.bits, exact);
|
|
281
|
-
return target.big ? wrapped : Number(wrapped);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/** `s.length`: UTF-8 byte length for strings (arrays keep their own `.length`). */
|
|
285
|
-
export function strLen(x) {
|
|
286
|
-
return typeof x === "string" ? Buffer.byteLength(x, "utf8") : x.length;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/** The UTF-8 bytes of `s`, which is what an Nish string holds. */
|
|
290
|
-
function bytesOf(s) {
|
|
291
|
-
return Buffer.from(s, "utf8");
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/** `s.charCodeAt(i)`: the byte at `i`, bounds-checked as `a[i]` is (JavaScript answers NaN). */
|
|
295
|
-
export function charCodeAt(s, i) {
|
|
296
|
-
const bytes = bytesOf(s);
|
|
297
|
-
const k = toIndex(i);
|
|
298
|
-
if (!(k >= 0 && k < bytes.length)) panicIndex(k, bytes.length);
|
|
299
|
-
return bytes[k];
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/** `s.substring(a, b)`: JavaScript's clamp and swap, over byte offsets. */
|
|
303
|
-
export function substring(s, a, b) {
|
|
304
|
-
const bytes = bytesOf(s);
|
|
305
|
-
const clamp = (v) => Math.max(0, Math.min(toIndex(v), bytes.length));
|
|
306
|
-
const from = clamp(a);
|
|
307
|
-
const to = b === undefined ? bytes.length : clamp(b);
|
|
308
|
-
return bytes.subarray(Math.min(from, to), Math.max(from, to)).toString("utf8");
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* `s.slice(a, b)`: the bytes of `[a, b)` with no clamp, `b` defaulting to the
|
|
313
|
-
* byte length (WP15 §4). Out of range panics rather than clamping, so the two
|
|
314
|
-
* compares the native code emits are reproduced here rather than JavaScript's
|
|
315
|
-
* negative-from-the-end rule.
|
|
316
|
-
*/
|
|
317
|
-
export function slice(s, a, b) {
|
|
318
|
-
const bytes = bytesOf(s);
|
|
319
|
-
const from = toIndex(a);
|
|
320
|
-
const to = b === undefined ? bytes.length : toIndex(b);
|
|
321
|
-
if (!(from >= 0 && from <= to && to <= bytes.length)) panicSlice(from, to, bytes.length);
|
|
322
|
-
return bytes.subarray(from, to).toString("utf8");
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/** `s.indexOf(sub)`: the first *byte* offset, or -1. */
|
|
326
|
-
export function indexOf(s, sub) {
|
|
327
|
-
return bytesOf(s).indexOf(bytesOf(sub));
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/** `nish_str_at`: whether `sub`'s bytes sit at byte offset `at`. */
|
|
331
|
-
function occursAt(s, at, sub) {
|
|
332
|
-
const bytes = bytesOf(s);
|
|
333
|
-
const needle = bytesOf(sub);
|
|
334
|
-
return at >= 0 && at + needle.length <= bytes.length && bytes.subarray(at, at + needle.length).equals(needle);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export function startsWith(s, sub) {
|
|
338
|
-
return occursAt(s, 0, sub);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
export function endsWith(s, sub) {
|
|
342
|
-
return occursAt(s, bytesOf(s).length - bytesOf(sub).length, sub);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/** `String.fromCharCode(c)`: the one-byte string of `c & 0xFF`. */
|
|
346
|
-
export function fromCharCode(code) {
|
|
347
|
-
return Buffer.from([toIndex(code) & 0xff]).toString("latin1");
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/** `console.log(x)`: `String(x)` (no `n` suffix for i64) plus a newline, written synchronously. */
|
|
351
|
-
export function log(x) {
|
|
352
|
-
fs.writeSync(1, `${String(x)}\n`);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/** `console.error(x)`: the same, on stderr. */
|
|
356
|
-
export function error(x) {
|
|
357
|
-
fs.writeSync(2, `${String(x)}\n`);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/** `write(s)` / `writeError(s)`: the bytes as they are, no trailing newline. */
|
|
361
|
-
export function write(s) {
|
|
362
|
-
fs.writeSync(1, s);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
export function writeError(s) {
|
|
366
|
-
fs.writeSync(2, s);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/** `panic(message)`: the message on stderr, then exit 1. */
|
|
370
|
-
export function panic(message) {
|
|
371
|
-
fs.writeSync(2, `${message}\n`);
|
|
372
|
-
process.exit(1);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// ---- Result (WP16) ---------------------------------------------------------
|
|
376
|
-
//
|
|
377
|
-
// Natively a `Result` is a two-arm struct in the arena; here it is an ordinary
|
|
378
|
-
// object with the same three names, so `r.ok`, `r.value` and `r.error` in the
|
|
379
|
-
// rewritten program mean what they mean in the compiled one. Only `orReturn`
|
|
380
|
-
// needs help: it returns from the *enclosing* function, which no expression in
|
|
381
|
-
// JavaScript can do, so it throws a sentinel that `rewrite.js` catches in a
|
|
382
|
-
// wrapper around every body that contains one.
|
|
383
|
-
|
|
384
|
-
class Propagate {
|
|
385
|
-
constructor(error) {
|
|
386
|
-
this.error = error;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
class NishResult {
|
|
391
|
-
constructor(ok, value, error) {
|
|
392
|
-
this.ok = ok;
|
|
393
|
-
if (ok) this.value = value;
|
|
394
|
-
else this.error = error;
|
|
395
|
-
}
|
|
396
|
-
isOk() {
|
|
397
|
-
return this.ok;
|
|
398
|
-
}
|
|
399
|
-
isErr() {
|
|
400
|
-
return !this.ok;
|
|
401
|
-
}
|
|
402
|
-
unwrapOr(fallback) {
|
|
403
|
-
return this.ok ? this.value : fallback;
|
|
404
|
-
}
|
|
405
|
-
expect(message) {
|
|
406
|
-
if (this.ok) return this.value;
|
|
407
|
-
panic(message);
|
|
408
|
-
}
|
|
409
|
-
orReturn() {
|
|
410
|
-
if (this.ok) return this.value;
|
|
411
|
-
throw new Propagate(this.error);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/** `Ok(v)`; `Ok()` on a `Result<void, E>` carries nothing. */
|
|
416
|
-
export function Ok(value) {
|
|
417
|
-
return new NishResult(true, value, undefined);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/** `Err(e)`. */
|
|
421
|
-
export function Err(error) {
|
|
422
|
-
return new NishResult(false, undefined, error);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* The `catch` half of `orReturn`: re-raise anything that is not a propagation,
|
|
427
|
-
* and answer the `Err` the enclosing function should return otherwise. The
|
|
428
|
-
* rewriter emits `return __nish.caught(e)` and nothing else, so a genuine
|
|
429
|
-
* runtime error still reaches Node unchanged.
|
|
430
|
-
*/
|
|
431
|
-
export function caught(thrown) {
|
|
432
|
-
if (thrown instanceof Propagate) return new NishResult(false, undefined, thrown.error);
|
|
433
|
-
throw thrown;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/** Index conversion as in the compiler: `sext` from i32, `fptosi` (truncation) from f64. */
|
|
437
|
-
function toIndex(i) {
|
|
438
|
-
return typeof i === "bigint" ? Number(i) : Math.trunc(i);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function panicIndex(i, len) {
|
|
442
|
-
fs.writeSync(2, `index out of range: ${i} >= ${len}\n`);
|
|
443
|
-
process.exit(1);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/** `nish_panic_slice`: the failed range check of `s.slice(start, end)`; the offsets print signed. */
|
|
447
|
-
function panicSlice(start, end, len) {
|
|
448
|
-
fs.writeSync(2, `slice out of range: [${start}, ${end}) of length ${len}\n`);
|
|
449
|
-
process.exit(1);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/** `a[i]` read with the WP4 bounds check (negative indices fail like the unsigned compare does). */
|
|
453
|
-
export function idx(a, i) {
|
|
454
|
-
const k = toIndex(i);
|
|
455
|
-
if (!(k >= 0 && k < a.length)) panicIndex(k, a.length);
|
|
456
|
-
return a[k];
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/** `a.pop()`: the last element, or the bounds panic — Nish has no `undefined` to return. */
|
|
460
|
-
export function pop(a) {
|
|
461
|
-
if (a.length === 0) panicIndex(0, 0);
|
|
462
|
-
return a.pop();
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/** `a[i] = v`: evaluates `a`, `i`, `v`, then checks and stores; yields `v`. */
|
|
466
|
-
export function setIdx(a, i, v) {
|
|
467
|
-
const k = toIndex(i);
|
|
468
|
-
if (!(k >= 0 && k < a.length)) panicIndex(k, a.length);
|
|
469
|
-
a[k] = v;
|
|
470
|
-
return v;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/** `a[i] op= v`: evaluates `a`, `i`, checks, loads, then `f(old)` computes the new element. */
|
|
474
|
-
export function updIdx(a, i, f) {
|
|
475
|
-
const k = toIndex(i);
|
|
476
|
-
if (!(k >= 0 && k < a.length)) panicIndex(k, a.length);
|
|
477
|
-
const v = f(a[k]);
|
|
478
|
-
a[k] = v;
|
|
479
|
-
return v;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* `dst.set(src, offset)` (WP34 N2): `TypedArray.prototype.set`'s copy on the
|
|
484
|
-
* plain array a `u8[]` is here. The source is copied first, so a self-copy or
|
|
485
|
-
* an overlapping one reads what was there before, as `memmove` does natively;
|
|
486
|
-
* a range past the end fails with the native panic and its words, where a
|
|
487
|
-
* typed array would throw a `RangeError` for the same offsets.
|
|
488
|
-
*/
|
|
489
|
-
export function arraySet(dst, src, offset) {
|
|
490
|
-
// `ToIntegerOrInfinity`: NaN is 0, as `llvm.fptosi.sat` makes it natively.
|
|
491
|
-
// `Math.trunc` rather than `toIndex`, which converts a bigint: an `i64` or
|
|
492
|
-
// `u64` offset is a bigint here, and it throws the `TypeError` the typed
|
|
493
|
-
// array and `Array.prototype.fill` throw for one, instead of being rounded
|
|
494
|
-
// to the nearest double past 2^53 (docs/RUN_UNDER_NODE.md).
|
|
495
|
-
const at = offset === undefined ? 0 : Math.trunc(offset) || 0;
|
|
496
|
-
const end = at + src.length;
|
|
497
|
-
if (!(at >= 0 && end <= dst.length)) panicSlice(at, end, dst.length);
|
|
498
|
-
// Two plain arrays overlap only when they are one array, which is the one
|
|
499
|
-
// case that must read the source before writing it.
|
|
500
|
-
const from = src === dst ? src.slice() : src;
|
|
501
|
-
for (let i = 0; i < from.length; i++) dst[at + i] = from[i];
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/** `new Array<T>(n)`: `n` zero-filled elements (`0`, `0n`, or `false`). */
|
|
505
|
-
export function newArray(n, zero) {
|
|
506
|
-
return new Array(toIndex(n)).fill(zero);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/** `process.exit(code)`: libc `exit` truncates to 8 bits exactly like Node does. */
|
|
510
|
-
export function exit(code) {
|
|
511
|
-
process.exit(toIndex(code));
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
function ioFail(verb, path) {
|
|
516
|
-
fs.writeSync(2, `nish: cannot ${verb} ${path}\n`);
|
|
517
|
-
process.exit(1);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
export function readFileSync(path) {
|
|
521
|
-
try {
|
|
522
|
-
return fs.readFileSync(path, "utf8");
|
|
523
|
-
} catch {
|
|
524
|
-
return ioFail("read", path);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/** `readFileSyncOrNull(path)`: null instead of exiting, so the program decides. */
|
|
529
|
-
export function readFileSyncOrNull(path) {
|
|
530
|
-
try {
|
|
531
|
-
return fs.readFileSync(path, "utf8");
|
|
532
|
-
} catch {
|
|
533
|
-
return null;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/** `readFileBytesSync(path)` (WP34 N2): the bytes as a plain array of numbers, or null. */
|
|
538
|
-
export function readFileBytesSync(path) {
|
|
539
|
-
try {
|
|
540
|
-
return Array.from(fs.readFileSync(path));
|
|
541
|
-
} catch {
|
|
542
|
-
return null;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* The flags `runtime-os.c` opens a file it writes with, `O_NOFOLLOW` included
|
|
548
|
-
* (docs/security/runtime.md, RT-4): a symbolic link as the last component of
|
|
549
|
-
* the path is refused rather than followed, so `writeFileSync`,
|
|
550
|
-
* `appendFileSync` and `spawnSyncTo` fail here where they fail natively. Node's
|
|
551
|
-
* `"w"` and `"a"` follow the link. A link among the directories on the way is
|
|
552
|
-
* still followed, as it is natively. Windows has no `O_NOFOLLOW`, and there the
|
|
553
|
-
* flag is 0.
|
|
554
|
-
*/
|
|
555
|
-
const { O_WRONLY, O_CREAT, O_TRUNC, O_APPEND, O_NOFOLLOW = 0 } = fs.constants;
|
|
556
|
-
const WRITE_FLAGS = O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW;
|
|
557
|
-
const APPEND_FLAGS = O_WRONLY | O_CREAT | O_APPEND | O_NOFOLLOW;
|
|
558
|
-
|
|
559
|
-
export function writeFileSync(path, data) {
|
|
560
|
-
try {
|
|
561
|
-
fs.writeFileSync(path, data, { encoding: "utf8", flag: WRITE_FLAGS });
|
|
562
|
-
} catch {
|
|
563
|
-
ioFail("write", path);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
export function appendFileSync(path, data) {
|
|
568
|
-
try {
|
|
569
|
-
fs.appendFileSync(path, data, { encoding: "utf8", flag: APPEND_FLAGS });
|
|
570
|
-
} catch {
|
|
571
|
-
ioFail("write", path);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* `mkdirSync(path)` (WP14 D4): one directory, not recursive, answering whether
|
|
577
|
-
* a directory is there afterwards. Node throws where the runtime answers
|
|
578
|
-
* false, and `EEXIST` on a plain file is a failure here as it is there, so the
|
|
579
|
-
* `statSync` decides rather than the exception.
|
|
580
|
-
*/
|
|
581
|
-
export function mkdirSync(path) {
|
|
582
|
-
try {
|
|
583
|
-
fs.mkdirSync(path);
|
|
584
|
-
return true;
|
|
585
|
-
} catch {
|
|
586
|
-
try {
|
|
587
|
-
return fs.statSync(path).isDirectory();
|
|
588
|
-
} catch {
|
|
589
|
-
return false;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
/**
|
|
595
|
-
* `isDirectorySync(path)` (WP14 §7a): one stat, and a boolean out of it rather
|
|
596
|
-
* than an exception, which is what the runtime's `stat` answers too.
|
|
597
|
-
*/
|
|
598
|
-
export function isDirectorySync(path) {
|
|
599
|
-
try {
|
|
600
|
-
return fs.statSync(path).isDirectory();
|
|
601
|
-
} catch {
|
|
602
|
-
return false;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* `readdirSync(path)`: the entries, sorted ascending by bytes, or `null` when
|
|
608
|
-
* the directory cannot be read. Node throws where the runtime answers a value,
|
|
609
|
-
* so the `catch` is what makes the two agree, and a directory that exists and
|
|
610
|
-
* is empty answers an empty array on both sides. Node's readdir never yields
|
|
611
|
-
* `.` or `..` — the pair `runtime-os.c` skips explicitly — so there is nothing
|
|
612
|
-
* to filter out here.
|
|
613
|
-
*
|
|
614
|
-
* The sort is the semantic point. `runtime-os.c` orders the names with
|
|
615
|
-
* `strcmp`, which compares UTF-8 bytes, and `Array#sort` compares UTF-16 code
|
|
616
|
-
* units. The two agree on ASCII names and part company above the BMP, where a
|
|
617
|
-
* surrogate pair sorts below `U+E000`..`U+FFFF` in UTF-16 and above them in
|
|
618
|
-
* UTF-8. So the comparison is over the encoded bytes, which is the native order
|
|
619
|
-
* exactly rather than the native order for the names that happen to be ASCII.
|
|
620
|
-
*/
|
|
621
|
-
export function readdirSync(path) {
|
|
622
|
-
let names;
|
|
623
|
-
try {
|
|
624
|
-
names = fs.readdirSync(path);
|
|
625
|
-
} catch {
|
|
626
|
-
return null;
|
|
627
|
-
}
|
|
628
|
-
return names.sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* `realpathSync(path)` (WP19 §5a item 4): `path` with its symbolic links
|
|
633
|
-
* resolved, or `null` when it does not resolve.
|
|
634
|
-
*
|
|
635
|
-
* `fs.realpathSync` throws where the native `realpath` answers NULL — a
|
|
636
|
-
* missing path, a loop, a component that is not a directory — so the `catch`
|
|
637
|
-
* is what makes the two runtimes agree, exactly as it does for `readdirSync`
|
|
638
|
-
* above. Node's own answer is already absolute and already normalised, which
|
|
639
|
-
* is what POSIX `realpath` guarantees, so nothing here has to normalise it.
|
|
640
|
-
*/
|
|
641
|
-
export function realpathSync(path) {
|
|
642
|
-
try {
|
|
643
|
-
return fs.realpathSync(path);
|
|
644
|
-
} catch {
|
|
645
|
-
return null;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* `process.platform` / `process.arch` (WP14 §7a). Node's spellings are the
|
|
651
|
-
* ones `runtime-os.c` answers with, so on any machine this compiler has a
|
|
652
|
-
* triple for the two runtimes give the same string; elsewhere the native build
|
|
653
|
-
* says `unknown` where Node names the platform, which is the one place they
|
|
654
|
-
* part.
|
|
655
|
-
*/
|
|
656
|
-
export function platform() {
|
|
657
|
-
return process.platform;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
export function arch() {
|
|
661
|
-
return process.arch;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* `getenv(name)` (WP19 R1): the value, or `null` when the variable is unset.
|
|
666
|
-
* Node answers `undefined` there and the language has no `undefined`, so the
|
|
667
|
-
* `??` is what makes the two runtimes agree; an empty value stays an empty
|
|
668
|
-
* string on both sides, because `CC=` is set and `CC` unset is not.
|
|
669
|
-
*/
|
|
670
|
-
export function getenv(name) {
|
|
671
|
-
return process.env[name] ?? null;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* `spawnSync(argv)` and `spawnSyncTo(argv, out, err)` (WP14 D4), which are one
|
|
676
|
-
* run with its streams answered differently: the child's exit status, 128 + n
|
|
677
|
-
* when signal n killed it, -1 for an empty vector or a program that would not
|
|
678
|
-
* start. `runtime-os.c` puts one `static nish_spawn_impl` behind both builtins
|
|
679
|
-
* for the same reason this module puts one function behind both helpers — the
|
|
680
|
-
* argument vector, the wait and the signal convention are written once and
|
|
681
|
-
* cannot drift between the two.
|
|
682
|
-
*
|
|
683
|
-
* `out` and `err` are paths for the child's stdout and stderr, and an **empty**
|
|
684
|
-
* string leaves that stream inherited. Each file is created or truncated at
|
|
685
|
-
* 0644 with `WRITE_FLAGS`, which is what the runtime's file actions ask for:
|
|
686
|
-
* `O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW`, so a symbolic link at the path
|
|
687
|
-
* is refused and the answer is -1, on both sides. The descriptors opened here are
|
|
688
|
-
* closed again whichever way the child went; natively the child opens them and
|
|
689
|
-
* its exit drops them.
|
|
690
|
-
*/
|
|
691
|
-
function spawnImpl(argv, out, err) {
|
|
692
|
-
if (argv.length === 0) return -1;
|
|
693
|
-
const opened = [];
|
|
694
|
-
const stream = (target) => {
|
|
695
|
-
if (target.length === 0) return "inherit";
|
|
696
|
-
const fd = fs.openSync(target, WRITE_FLAGS, 0o644);
|
|
697
|
-
opened.push(fd);
|
|
698
|
-
return fd;
|
|
699
|
-
};
|
|
700
|
-
try {
|
|
701
|
-
const r = child_process.spawnSync(argv[0], argv.slice(1), {
|
|
702
|
-
stdio: ["inherit", stream(out), stream(err)],
|
|
703
|
-
});
|
|
704
|
-
if (r.error !== undefined) return -1;
|
|
705
|
-
if (r.signal !== null && r.signal !== undefined) return 128 + (os.constants.signals[r.signal] ?? 0);
|
|
706
|
-
return r.status === null ? -1 : r.status;
|
|
707
|
-
} catch {
|
|
708
|
-
// A path that cannot be opened is, natively, a file action the child could
|
|
709
|
-
// not perform, and `posix_spawnp` reports that through its return value:
|
|
710
|
-
// -1, with no child having run. Opening the second path is what can fail
|
|
711
|
-
// after the first file was already created, so the truncation a caller can
|
|
712
|
-
// observe happens on both sides.
|
|
713
|
-
return -1;
|
|
714
|
-
} finally {
|
|
715
|
-
for (const fd of opened) fs.closeSync(fd);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
/** `spawnSync(argv)`: the child inherits this process's streams, as it does natively. */
|
|
720
|
-
export function spawnSync(argv) {
|
|
721
|
-
return spawnImpl(argv, "", "");
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/** `spawnSyncTo(argv, stdoutPath, stderrPath)`: the same run with a stream sent to a file. */
|
|
725
|
-
export function spawnSyncTo(argv, out, err) {
|
|
726
|
-
return spawnImpl(argv, out, err);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* `monotonicNanos()`: `process.hrtime.bigint()`, a monotonic clock in
|
|
731
|
-
* nanoseconds (`CLOCK_MONOTONIC` on every platform that has it, which is the
|
|
732
|
-
* one `runtime-os.c` reads). The value is a BigInt because that is how an `i64`
|
|
733
|
-
* is held on this side.
|
|
734
|
-
*
|
|
735
|
-
* No rewrite can make a *reading* agree with a native run: both origins are
|
|
736
|
-
* arbitrary and neither is the other's. Only the difference between two reads
|
|
737
|
-
* means anything, so a differential program may compare two readings and must
|
|
738
|
-
* never print one.
|
|
739
|
-
*/
|
|
740
|
-
export function monotonicNanos() {
|
|
741
|
-
return process.hrtime.bigint();
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// ---- The host (WP34 N3) ------------------------------------------------------
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* `statMtimeSync(path)`: Node's `mtimeMs` for the path, or NaN when it cannot
|
|
748
|
-
* be stat'd, which is the native answer too. `mtimeMs` is the same arithmetic
|
|
749
|
-
* `runtime-host.c` does, so the two print the same digits, fraction and all.
|
|
750
|
-
*/
|
|
751
|
-
export function statMtimeSync(path) {
|
|
752
|
-
const st = fs.statSync(path, { throwIfNoEntry: false });
|
|
753
|
-
if (st === undefined) {
|
|
754
|
-
return Number.NaN;
|
|
755
|
-
}
|
|
756
|
-
return st.mtimeMs;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
/**
|
|
760
|
-
* The RT-9 owner checks (#386): `lstatOwnerModeSync(path)` is the owner's uid
|
|
761
|
-
* in the high 32 bits and `st_mode` in the low 32 of one `lstat`, so a
|
|
762
|
-
* symbolic link answers for itself, or -1 when the path does not resolve or
|
|
763
|
-
* holds a NUL; `geteuid()` is the effective uid; `isExecutableSync(path)` is
|
|
764
|
-
* `access(path, X_OK)`. Each answers what `runtime-host.c` answers, the two
|
|
765
|
-
* `i64`s as BigInts because that is how an `i64` is held on this side. Node's
|
|
766
|
-
* `fs` throws on a NUL in a path where the runtime names nothing, so the
|
|
767
|
-
* `catch` is what makes the two agree there too.
|
|
768
|
-
*/
|
|
769
|
-
export function lstatOwnerModeSync(path) {
|
|
770
|
-
try {
|
|
771
|
-
const st = fs.lstatSync(path);
|
|
772
|
-
return BigInt.asIntN(64, (BigInt(st.uid >>> 0) << 32n) | BigInt(st.mode >>> 0));
|
|
773
|
-
} catch {
|
|
774
|
-
return -1n;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
export function geteuid() {
|
|
779
|
-
return BigInt(process.geteuid());
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
export function isExecutableSync(path) {
|
|
783
|
-
try {
|
|
784
|
-
fs.accessSync(path, fs.constants.X_OK);
|
|
785
|
-
return true;
|
|
786
|
-
} catch {
|
|
787
|
-
return false;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
/**
|
|
792
|
-
* `secureZero(bytes)` (#385): every element set to zero, which is what the
|
|
793
|
-
* native `nish_wipe` leaves. Nothing here can drop a store to an array the
|
|
794
|
-
* caller still holds, so a loop is the whole of it. What it cannot promise is
|
|
795
|
-
* what the native build does: a collector that moved the array may have left a
|
|
796
|
-
* copy of the old bytes behind, and JavaScript has no way to reach it.
|
|
797
|
-
*/
|
|
798
|
-
export function secureZero(bytes) {
|
|
799
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
800
|
-
bytes[i] = 0;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
/**
|
|
805
|
-
* Node's own fill, taken before `runtime/nish.mjs` puts the one below in its
|
|
806
|
-
* place on the same object: `webcrypto` is the global `crypto`.
|
|
807
|
-
*/
|
|
808
|
-
const webRandom = webcrypto.getRandomValues.bind(webcrypto);
|
|
809
|
-
|
|
810
|
-
/**
|
|
811
|
-
* `crypto.getRandomValues(bytes)` for the plain array a `u8[]` is here. Node's
|
|
812
|
-
* own takes only a typed array, so the bytes are drawn into one and copied
|
|
813
|
-
* across. More than 65,536 fails with the native panic and its words, where
|
|
814
|
-
* Node would throw a `QuotaExceededError`: the exit status is 1 either way. A
|
|
815
|
-
* typed array goes straight through.
|
|
816
|
-
*/
|
|
817
|
-
export function getRandomValues(bytes) {
|
|
818
|
-
if (!Array.isArray(bytes)) {
|
|
819
|
-
return webRandom(bytes);
|
|
820
|
-
}
|
|
821
|
-
if (bytes.length > 65536) {
|
|
822
|
-
panic(`crypto.getRandomValues: ${bytes.length} bytes asked for, and one call fills at most 65536`);
|
|
823
|
-
}
|
|
824
|
-
const drawn = webRandom(new Uint8Array(bytes.length));
|
|
825
|
-
for (let i = 0; i < drawn.length; i++) {
|
|
826
|
-
bytes[i] = drawn[i];
|
|
827
|
-
}
|
|
828
|
-
return bytes;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
/**
|
|
832
|
-
* `signalFd()` and `readSignal(fd)` have no faithful reading under Node, and
|
|
833
|
-
* these say so rather than answer something else. Node delivers a signal to
|
|
834
|
-
* its event loop (`process.on("SIGTERM")`), and a blocking read keeps the loop
|
|
835
|
-
* from ever running, so no synchronous function here can learn that one
|
|
836
|
-
* arrived. docs/wp33-round-trip.md §3.5 has the row and the translation.
|
|
837
|
-
*/
|
|
838
|
-
export function signalFd() {
|
|
839
|
-
throw new Error(
|
|
840
|
-
"signalFd has no synchronous reading under Node: a signal reaches the event loop, which a blocking readSignal never returns to (docs/wp33-round-trip.md)"
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
export function readSignal() {
|
|
845
|
-
return signalFd();
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
/**
|
|
849
|
-
* The `nish:net` functions (WP34 N5) have no faithful reading under Node
|
|
850
|
-
* either, for `signalFd`'s reason: Node's sockets are asynchronous only, and a
|
|
851
|
-
* socket becomes readable, writable or connected only to the event loop, which
|
|
852
|
-
* a program that owns its loop and spins or blocks in it never returns to. So
|
|
853
|
-
* each throws, naming itself, rather than answer -11 forever; `nish.mjs`
|
|
854
|
-
* installs one per name.
|
|
855
|
-
* docs/wp33-round-trip.md is the note that asks for one stated answer.
|
|
856
|
-
*/
|
|
857
|
-
export function noNetReading(name) {
|
|
858
|
-
throw new Error(
|
|
859
|
-
`\`${name}\` has no synchronous reading under Node: a socket is ready only to the event loop, which a program that owns its loop never returns to (docs/wp33-round-trip.md)`
|
|
860
|
-
);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
/** `process.argv`: index 0 is the program (the script here, the executable natively), then the arguments. */
|
|
864
|
-
export function argv() {
|
|
865
|
-
return process.argv.slice(1);
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
const SPACES = "[ \\t\\n\\v\\f\\r]*";
|
|
869
|
-
/** What `nish_parse_number` recognises: strtod's decimal and hex-integer forms, or an exact `Infinity`. */
|
|
870
|
-
const LITERAL = new RegExp(`^${SPACES}([+-]?)(Infinity|0[xX][0-9a-fA-F]+|(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?)`);
|
|
871
|
-
const BLANK = new RegExp(`^${SPACES}$`);
|
|
872
|
-
const WHOLE = new RegExp(`${LITERAL.source}${SPACES}$`);
|
|
873
|
-
|
|
874
|
-
/** The value of a `LITERAL` match: hex goes through parseInt(16), and `-` applies afterwards as in strtod. */
|
|
875
|
-
function literalValue(m) {
|
|
876
|
-
const magnitude = /^0[xX]/.test(m[2]) ? Number.parseInt(m[2], 16) : Number(m[2]);
|
|
877
|
-
return m[1] === "-" ? -magnitude : magnitude;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
/** `parseInt(s)`: strtoll base 10 (ASCII whitespace, sign, digits; 0 without digits) saturated into i32. */
|
|
881
|
-
export function parseInt(s) {
|
|
882
|
-
const m = new RegExp(`^${SPACES}([+-]?\\d+)`).exec(s);
|
|
883
|
-
return m ? toI32(Number(m[1])) : 0;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
/** `parseFloat(s)`: the longest literal after ASCII whitespace, else NaN (`0x1A` is 26, as strtod reads it). */
|
|
887
|
-
export function parseFloat(s) {
|
|
888
|
-
const m = LITERAL.exec(s);
|
|
889
|
-
return m ? literalValue(m) : NaN;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
/** `Number(x)`: a string must be one literal bar surrounding ASCII whitespace (blank is 0); others convert numerically. */
|
|
893
|
-
export function number(x) {
|
|
894
|
-
if (typeof x === "bigint") return Number(x);
|
|
895
|
-
if (typeof x === "boolean") return x ? 1 : 0;
|
|
896
|
-
if (typeof x !== "string") return x;
|
|
897
|
-
const m = WHOLE.exec(x);
|
|
898
|
-
if (m) return literalValue(m);
|
|
899
|
-
return BLANK.test(x) ? 0 : NaN;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// Arena introspection has no JS counterpart: the stubs keep programs that only
|
|
903
|
-
// compare `Arena.used()` before/after (a "stayed flat" check) in agreement, while
|
|
904
|
-
// programs that print raw byte counts are listed as known differences.
|
|
905
|
-
//
|
|
906
|
-
// `nish --threads` makes the native arena thread-local (WP20 T0) and nothing
|
|
907
|
-
// here moves with it: a rewritten program runs on the one thread Node gives it,
|
|
908
|
-
// so "the arena of the calling thread" and "the arena" are the same object, and
|
|
909
|
-
// these stubs answer for both. If T1 ever lands a spawn the rewrite can reach,
|
|
910
|
-
// that is when this file grows a second arena to keep count of.
|
|
911
|
-
export function arenaUsed() { return 0; }
|
|
912
|
-
export function arenaMark() { return 0; }
|
|
913
|
-
export function arenaRelease() {}
|
|
914
|
-
export function arenaReset() {}
|
|
915
|
-
/** `using a = arena()`: a disposable whose disposal does nothing, because there is no arena to release. */
|
|
916
|
-
export function arena() {
|
|
917
|
-
return { [Symbol.dispose]() {} };
|
|
918
|
-
}
|
|
9
|
+
export * from "./core.mjs";
|