@amritk/nish 0.12.0 → 0.13.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/README.md +25 -23
- package/bin/launcher.js +45 -41
- package/bin/packaging.js +39 -33
- package/docs/AI.md +95 -10
- package/docs/INSTALL.md +13 -13
- package/package.json +9 -6
- package/runtime/nish.d.ts +20 -1
- package/runtime/nish.h +31 -11
- package/runtime/nish.mjs +3 -3
- package/runtime/{runtime_os.c → runtime-os.c} +1 -1
- package/runtime/{runtime_parallel.c → runtime-parallel.c} +112 -4
- package/runtime/{runtime_wasm.c → runtime-wasm.c} +6 -1
- package/runtime/runtime.c +5 -5
- package/runtime/shim.mjs +6 -6
- package/scripts/bootstrap.sh +29 -29
- package/scripts/build.sh +14 -9
- package/scripts/changelog-gen.mjs +260 -192
- package/scripts/ci-profile.mjs +80 -68
- package/scripts/codes-registry.js +25 -13
- package/scripts/gen-diagnostic-codes.mjs +86 -69
- package/scripts/nish-compiler.sh +2 -0
- package/scripts/platform-package.mjs +26 -26
- package/scripts/postinstall.mjs +46 -36
- package/scripts/size-report.sh +3 -3
- package/scripts/smoke.sh +1 -1
- package/std/README.md +2 -2
- package/std/collections.ts +194 -188
- package/std/json.ts +136 -136
- package/std/map.ts +9 -7
- package/std/pair.ts +2 -2
- package/std/testing.ts +67 -67
- package/std/text.ts +54 -54
- package/std/threads.ts +126 -38
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# Nish
|
|
4
4
|
|
|
5
|
+
**Native Instruction Static Host AKA Turbo Typescript. A fast subset of typescript without the encumbrance of javascript.**
|
|
6
|
+
|
|
5
7
|
**An ahead-of-time compiler for a strictly static subset of TypeScript — LLVM IR in the middle, native binaries at the end. No interpreter, no garbage collector, nothing to ship beside the executable.**
|
|
6
8
|
|
|
7
9
|

|
|
@@ -22,12 +24,12 @@ Nish parses source with its own lexer and parser, rejects everything dynamic
|
|
|
22
24
|
(`any`, prototypes, `eval`, exceptions, a garbage collector), and emits
|
|
23
25
|
textual LLVM IR (`.ll`). LLVM's own toolchain (`clang` / `llc`) then optimises
|
|
24
26
|
and produces native binaries for x86_64, ARM64, or WebAssembly. The compiler
|
|
25
|
-
is itself written in Nish, in `
|
|
27
|
+
is itself written in Nish, in `src/`, and ships as a native binary with no
|
|
26
28
|
runtime dependency.
|
|
27
29
|
|
|
28
30
|
```
|
|
29
31
|
TypeScript source ──▶ AST ──▶ validator + checker ──▶ LLVM IR (.ll) ──▶ clang/llc ──▶ native binary
|
|
30
|
-
(
|
|
32
|
+
(src/parser.ts) (src/checker.ts) (src/emit*.ts) + runtime/runtime.c + runtime-os.c
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
If it compiles, every value has one fixed, known memory layout; binaries are
|
|
@@ -73,7 +75,7 @@ curl -fsSL https://raw.githubusercontent.com/amritk/nish/main/install.sh | sh
|
|
|
73
75
|
> `bash scripts/fetch-seed.sh`, `npm run build`, then `build/nish ...` wherever this README says `nish`.
|
|
74
76
|
> The build is a bootstrap: the previous release's `nish` (the seed, fetched
|
|
75
77
|
> by `scripts/fetch-seed.sh`, or named with `NISH_BOOTSTRAP=<path>`) compiles
|
|
76
|
-
> `
|
|
78
|
+
> `src/`, and that compiler compiles `src/` again into `build/nish`.
|
|
77
79
|
|
|
78
80
|
`hello.ts`:
|
|
79
81
|
|
|
@@ -210,7 +212,7 @@ dies with its frame, Rust refuses to compile it, Go falls back to the garbage
|
|
|
210
212
|
collector, and Zig hands the question back to you and an allocator. Nish
|
|
211
213
|
leaves the value in the arena, where it stays until `main` returns.
|
|
212
214
|
|
|
213
|
-
So [`
|
|
215
|
+
So [`src/escape.ts`](src/escape.ts) and the whole-program fact
|
|
214
216
|
fixpoint are optimisations and nothing else: a refusal costs memory and never
|
|
215
217
|
correctness, and no program is rejected for a lifetime reason. The compiler
|
|
216
218
|
says so out loud where the cost is real: assigning an allocation to a local
|
|
@@ -345,7 +347,7 @@ safe. `-o`, `--link`, `--target`, the `--emit-*` sidecars and
|
|
|
345
347
|
errors there, and performance warnings are not printed, because stderr belongs
|
|
346
348
|
to the program.
|
|
347
349
|
|
|
348
|
-
Without `--link`, build the IR yourself: `clang add.ll examples/main.c runtime/runtime.c runtime/
|
|
350
|
+
Without `--link`, build the IR yourself: `clang add.ll examples/main.c runtime/runtime.c runtime/runtime-os.c -o app`
|
|
349
351
|
(the `overriding the module target triple` warning is harmless: the IR is
|
|
350
352
|
target-neutral unless you pass `--target`; `-Wno-override-module` silences
|
|
351
353
|
it), or step by step with `llvm-as`, `llc -O2 -filetype=obj`, and
|
|
@@ -363,7 +365,7 @@ with `--target aarch64-unknown-linux-gnu` / `wasm32-wasi` plus
|
|
|
363
365
|
Rust-class output is the goal: no GC, no embedded engine, aliasing and
|
|
364
366
|
purity facts handed to LLVM up front, and a link step that strips everything
|
|
365
367
|
unused. `examples/add.ts` + `examples/main.c` + the two runtime translation units
|
|
366
|
-
(`runtime/runtime.c` and `runtime/
|
|
368
|
+
(`runtime/runtime.c` and `runtime/runtime-os.c`), x86_64 Linux, glibc
|
|
367
369
|
dynamically linked (`npm run size-report`):
|
|
368
370
|
|
|
369
371
|
| Profile | Bytes | What it does |
|
|
@@ -380,7 +382,7 @@ core does not grow every time the language reaches further into the operating
|
|
|
380
382
|
system: `runtime.c` is the 3,480 bytes every program touches (one chunked bump
|
|
381
383
|
arena with O(1) reset and mark/release, strings, JavaScript-exact number
|
|
382
384
|
formatting, string parsing, `Math.random`, `process.argv`, array growth, the
|
|
383
|
-
panic paths), and `
|
|
385
|
+
panic paths), and `runtime-os.c` the 1,190 bytes that wrap a system call (exit,
|
|
384
386
|
files, directories, subprocesses, the environment, the clock) — see
|
|
385
387
|
[docs/wp7-runtime.md](docs/wp7-runtime.md#runtime-additions-and-budget) for both
|
|
386
388
|
budgets and the reasoning. `Math.*` calls are LLVM intrinsics, so pure functions
|
|
@@ -422,7 +424,7 @@ The supported direction is Node importing Nish:
|
|
|
422
424
|
|
|
423
425
|
- `scripts/build.sh --profile wasm` produces a module `WebAssembly.instantiate`
|
|
424
426
|
loads directly (`examples/node-host.mjs`); exports use the plain C ABI.
|
|
425
|
-
Add `runtime/
|
|
427
|
+
Add `runtime/runtime-wasm.c` (arena + arrays, no libc) when a function
|
|
426
428
|
takes or returns an array.
|
|
427
429
|
- `--emit-napi` + `scripts/build.sh --profile napi` build a `.node` addon
|
|
428
430
|
with argument type checks (`examples/node-addon.mjs`).
|
|
@@ -446,11 +448,11 @@ Details: [docs/wp8-interop.md](docs/wp8-interop.md).
|
|
|
446
448
|
|
|
447
449
|
## The compiler in a browser
|
|
448
450
|
|
|
449
|
-
`
|
|
451
|
+
`src/` is an Nish program, so the compiler compiles itself to
|
|
450
452
|
WebAssembly like any other one:
|
|
451
453
|
|
|
452
454
|
```bash
|
|
453
|
-
nish
|
|
455
|
+
nish src/compile.ts --link web/nish.wasm --profile wasi
|
|
454
456
|
node web/compile.mjs web/nish.wasm examples/add.ts # the IR, from a Web Worker
|
|
455
457
|
```
|
|
456
458
|
|
|
@@ -464,17 +466,17 @@ refused there. [web/README.md](web/README.md) has the rest.
|
|
|
464
466
|
|
|
465
467
|
## Self-hosting
|
|
466
468
|
|
|
467
|
-
`
|
|
469
|
+
`src/` is the compiler, and the only one: lexer, parser, checker and emitter,
|
|
468
470
|
written in Nish, with no `typescript` package underneath. It compiles its own
|
|
469
471
|
source to a fixed point:
|
|
470
472
|
|
|
471
473
|
```
|
|
472
|
-
IR(stage1,
|
|
474
|
+
IR(stage1, src/) == IR(stage2, src/) byte for byte, and stage3 == stage2
|
|
473
475
|
```
|
|
474
476
|
|
|
475
477
|
The chain starts from a seed, the previous release's `nish` binary, the way
|
|
476
|
-
Rust and Go build themselves: stage1 is `
|
|
477
|
-
`
|
|
478
|
+
Rust and Go build themselves: stage1 is `src/` built by the seed, stage2 is
|
|
479
|
+
`src/` built by stage1 and is what gets installed, and stage3, built by
|
|
478
480
|
stage2, has to reproduce stage2 file for file. `scripts/bootstrap.sh --verify`
|
|
479
481
|
asserts both equalities, and `npm test` re-proves them on every run. Compiling
|
|
480
482
|
the whole compiler costs it **91 ms and 86 MB**; the TypeScript compiler it
|
|
@@ -486,15 +488,15 @@ npm run build # seed -> stage1 -> stage2 = build/nish
|
|
|
486
488
|
build/nish hello.ts --link hello # -o, --link, --profile, its own directories
|
|
487
489
|
```
|
|
488
490
|
|
|
489
|
-
Because the seed is the last release, `
|
|
490
|
-
source what that release compiles. A new construct is implemented in `
|
|
491
|
-
and becomes usable inside `
|
|
491
|
+
Because the seed is the last release, `src/` may only *use* in its own
|
|
492
|
+
source what that release compiles. A new construct is implemented in `src/`
|
|
493
|
+
and becomes usable inside `src/` from the next release on — the rolling
|
|
492
494
|
freeze CI's `bootstrap` job checks. Until R6 there was a second compiler, the
|
|
493
|
-
TypeScript one
|
|
494
|
-
oracle each `
|
|
495
|
+
TypeScript one, stage0, which seeded every bootstrap and served as the
|
|
496
|
+
oracle each phase of `src/` was compared against; it was deleted once the
|
|
495
497
|
self-hosted compiler did everything it did
|
|
496
498
|
([wp19](docs/wp19-stage0-retirement.md)).
|
|
497
|
-
Details, and the subset `
|
|
499
|
+
Details, and the subset `src/` is written in, are in
|
|
498
500
|
[docs/wp14-selfhost.md](docs/wp14-selfhost.md).
|
|
499
501
|
|
|
500
502
|
---
|
|
@@ -511,7 +513,7 @@ and the IR any of them lowers to are all still free to change.
|
|
|
511
513
|
| M2 "Data" | classes and interfaces, arrays, runtime and intrinsics | done |
|
|
512
514
|
| M3 "Rust parity" | interop, memory strategy (stack allocation, arena scopes, `T \| null`), benchmarks with `--target`/`--nsw`/PGO, differential testing against Node | done |
|
|
513
515
|
| M4 "1.0" | frozen language reference, tagged release | next |
|
|
514
|
-
| M5 "Self-hosting" | `
|
|
516
|
+
| M5 "Self-hosting" | `src/`: the compiler, written in Nish, compiling itself | done |
|
|
515
517
|
|
|
516
518
|
Not in the language yet, in the order they are likely to land: optional
|
|
517
519
|
reference counting for objects that must outlive an arena reset, and dynamic
|
|
@@ -542,7 +544,7 @@ node tests/run.js locals # only cases whose name contains "locals"
|
|
|
542
544
|
npm run test:update # write missing .ll goldens for new cases
|
|
543
545
|
npm run test:diff # every whole program natively and under Node (runtime/shim.mjs), compared byte for byte
|
|
544
546
|
node tests/differential/fuzz.js --stage1 --count 200 # random integer programs, the released seed's IR against HEAD's; prints the seed
|
|
545
|
-
npm run check # tsc --noEmit: an ambient type-check of
|
|
547
|
+
npm run check # tsc --noEmit: an ambient type-check of src/, std/ and tests/nish/
|
|
546
548
|
npm run lint # Biome style lint (advisory, never a compile gate)
|
|
547
549
|
npm run smoke # build and run every example with a main
|
|
548
550
|
scripts/bootstrap.sh --verify # the whole chain, with IR(stage1) == IR(stage2) and stage3 == stage2 asserted
|
|
@@ -555,7 +557,7 @@ the add-a-construct checklist, ABI guard tests) and the conventions in
|
|
|
555
557
|
[docs/MASTER_PLAN.md §7](docs/MASTER_PLAN.md#7-conventions-for-every-agent):
|
|
556
558
|
every construct ships with a golden `.ll`, an `llvm-as` pass, a native round
|
|
557
559
|
trip, a negative test, and its LANGUAGE.md and cookbook entries; no attribute
|
|
558
|
-
without a proof; layout changes touch `
|
|
560
|
+
without a proof; layout changes touch `src/runtime.ts` and `runtime/runtime.c` together.
|
|
559
561
|
CI runs the suite on Ubuntu and macOS with LLVM 18
|
|
560
562
|
([docs/wp10-ci.md](docs/wp10-ci.md)). The documentation index is
|
|
561
563
|
[docs/README.md](docs/README.md). Coding guidelines for contributors and
|
package/bin/launcher.js
CHANGED
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
*
|
|
17
17
|
* **A platform with no prebuilt binary is now an error rather than a fallback,
|
|
18
18
|
* and that is a decision rather than an oversight.** Until 0.6.0 this file
|
|
19
|
-
* imported `dist/index.js` -- the TypeScript compiler built from `src/` -- so
|
|
19
|
+
* imported `dist/index.js` -- the TypeScript compiler built from stage0's `src/` -- so
|
|
20
20
|
* musl, FreeBSD and 32-bit anything got a working compiler that happened to be
|
|
21
|
-
* slower. `src/` was deleted in R6 (`docs/wp19-stage0-retirement.md`), so there
|
|
21
|
+
* slower. stage0's `src/` was deleted in R6 (`docs/wp19-stage0-retirement.md`), so there
|
|
22
22
|
* is no second compiler in the package to reach for, and the honest answer is
|
|
23
23
|
* the one below: name the platforms a release carries, say there is nothing to
|
|
24
|
-
* fall back to, and exit non-zero. The alternative -- shipping `
|
|
24
|
+
* fall back to, and exit non-zero. The alternative -- shipping `src/` and
|
|
25
25
|
* bootstrapping on the user's machine -- was priced in wp12 and turned down,
|
|
26
26
|
* and quietly doing nothing was never on the table: a command that exits 0
|
|
27
27
|
* having compiled nothing is worse than one that refuses.
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
* `bin/` rather than the old `dist/` for the same reason: the command may not
|
|
30
30
|
* be a build artifact of the compiler it installs.
|
|
31
31
|
*/
|
|
32
|
-
import { spawnSync } from "node:child_process"
|
|
33
|
-
import fs from "node:fs"
|
|
34
|
-
import path from "node:path"
|
|
35
|
-
import { createRequire } from "node:module"
|
|
36
|
-
import { assetFor, noCompilerMessage, platformPackageName } from "./packaging.js"
|
|
32
|
+
import { spawnSync } from "node:child_process"
|
|
33
|
+
import fs from "node:fs"
|
|
34
|
+
import path from "node:path"
|
|
35
|
+
import { createRequire } from "node:module"
|
|
36
|
+
import { assetFor, noCompilerMessage, platformPackageName } from "./packaging.js"
|
|
37
37
|
|
|
38
38
|
/** Package root: bin/launcher.js -> `..`, the directory `package.json` sits in. */
|
|
39
|
-
const PKG_ROOT = path.resolve(import.meta.dirname, "..")
|
|
39
|
+
const PKG_ROOT = path.resolve(import.meta.dirname, "..")
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Exit 3, the toolchain code.
|
|
@@ -54,17 +54,17 @@ const PKG_ROOT = path.resolve(import.meta.dirname, "..");
|
|
|
54
54
|
* below is machine-readable. `scripts/postinstall.mjs`'s generated shim answers
|
|
55
55
|
* 3 for its own version of this, so all three spell one situation the same way.
|
|
56
56
|
*/
|
|
57
|
-
const NO_COMPILER = 3
|
|
57
|
+
const NO_COMPILER = 3
|
|
58
58
|
|
|
59
59
|
/** This package's own name, or `null` when its `package.json` cannot be read. */
|
|
60
60
|
const packageName = () => {
|
|
61
61
|
try {
|
|
62
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"))
|
|
63
|
-
return typeof pkg.name === "string" ? pkg.name : null
|
|
62
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"))
|
|
63
|
+
return typeof pkg.name === "string" ? pkg.name : null
|
|
64
64
|
} catch {
|
|
65
|
-
return null
|
|
65
|
+
return null
|
|
66
66
|
}
|
|
67
|
-
}
|
|
67
|
+
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Whether this invocation asked for machine-readable output.
|
|
@@ -75,7 +75,7 @@ const packageName = () => {
|
|
|
75
75
|
* failure the `--json` contract covers (AGENTS.md, "Machine-readable
|
|
76
76
|
* surfaces").
|
|
77
77
|
*/
|
|
78
|
-
const wantsJson = () => process.argv.slice(2).includes("--json")
|
|
78
|
+
const wantsJson = () => process.argv.slice(2).includes("--json")
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Write and exit, without losing what was written.
|
|
@@ -87,9 +87,9 @@ const wantsJson = () => process.argv.slice(2).includes("--json");
|
|
|
87
87
|
* there is nothing after it to run.
|
|
88
88
|
*/
|
|
89
89
|
const writeAndExit = (stream, text, status) => {
|
|
90
|
-
stream.write(text)
|
|
91
|
-
process.exitCode = status
|
|
92
|
-
}
|
|
90
|
+
stream.write(text)
|
|
91
|
+
process.exitCode = status
|
|
92
|
+
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* Where the native compiler for this machine is, or `null` when this machine
|
|
@@ -103,26 +103,30 @@ const writeAndExit = (stream, text, status) => {
|
|
|
103
103
|
* `build.sh`, the C runtime and the standard library one level up from itself
|
|
104
104
|
* exactly as it does when unpacked by hand.
|
|
105
105
|
*/
|
|
106
|
-
const nativeCompiler = (
|
|
107
|
-
const name = packageName()
|
|
108
|
-
if (name === null)
|
|
106
|
+
const nativeCompiler = (assetName) => {
|
|
107
|
+
const name = packageName()
|
|
108
|
+
if (name === null) {
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
109
111
|
try {
|
|
110
|
-
const manifest = createRequire(import.meta.url).resolve(
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
const manifest = createRequire(import.meta.url).resolve(
|
|
113
|
+
`${platformPackageName(name, assetName)}/package.json`
|
|
114
|
+
)
|
|
115
|
+
const binary = path.join(path.dirname(manifest), "bin", "nish")
|
|
116
|
+
return fs.existsSync(binary) ? binary : null
|
|
113
117
|
} catch {
|
|
114
118
|
// Not installed. On a supported platform that is a partial install; on an
|
|
115
119
|
// unsupported one npm skipped the entry on purpose. `refuse` tells the two
|
|
116
120
|
// apart, because the advice differs.
|
|
117
|
-
return null
|
|
121
|
+
return null
|
|
118
122
|
}
|
|
119
|
-
}
|
|
123
|
+
}
|
|
120
124
|
|
|
121
125
|
/**
|
|
122
126
|
* Say why there is no compiler to run, and stop.
|
|
123
127
|
*
|
|
124
128
|
* Under `--json` that is one object on stdout and **nothing on stderr**, which
|
|
125
|
-
* is the shape `
|
|
129
|
+
* is the shape `src/compile.ts`'s own `reportToolchainFailure` uses for the
|
|
126
130
|
* same code: stdout carries objects and nothing else, so a tool reading it does
|
|
127
131
|
* not have to strip a human report out of the stream. Otherwise it is the
|
|
128
132
|
* report on stderr. Either way the status is the same.
|
|
@@ -137,50 +141,50 @@ const refuse = (unstartable) => {
|
|
|
137
141
|
// none: it sends the user to install something that does not exist.
|
|
138
142
|
packageName: packageName(),
|
|
139
143
|
unstartable,
|
|
140
|
-
})
|
|
144
|
+
})
|
|
141
145
|
if (wantsJson()) {
|
|
142
146
|
writeAndExit(
|
|
143
147
|
process.stdout,
|
|
144
148
|
`${JSON.stringify({ severity: "error", code: why.code, message: why.summary })}\n`,
|
|
145
149
|
NO_COMPILER
|
|
146
|
-
)
|
|
150
|
+
)
|
|
147
151
|
} else {
|
|
148
|
-
writeAndExit(process.stderr, why.report, NO_COMPILER)
|
|
152
|
+
writeAndExit(process.stderr, why.report, NO_COMPILER)
|
|
149
153
|
}
|
|
150
|
-
}
|
|
154
|
+
}
|
|
151
155
|
|
|
152
|
-
const asset = assetFor(process.platform, process.arch)
|
|
156
|
+
const asset = assetFor(process.platform, process.arch)
|
|
153
157
|
// No binary for this machine, and none coming: musl, FreeBSD, 32-bit anything.
|
|
154
158
|
// `refuse` sets an exit code rather than exiting, so each of these returns
|
|
155
159
|
// before the next line runs: the sequence below is written as a chain for that
|
|
156
160
|
// reason and `process.exitCode` is what carries the status out.
|
|
157
161
|
if (asset === null) {
|
|
158
|
-
refuse(null)
|
|
162
|
+
refuse(null)
|
|
159
163
|
} else {
|
|
160
|
-
const binary = nativeCompiler(asset)
|
|
164
|
+
const binary = nativeCompiler(asset)
|
|
161
165
|
if (binary === null) {
|
|
162
166
|
// A binary exists for this platform and this install does not have it.
|
|
163
|
-
refuse(null)
|
|
167
|
+
refuse(null)
|
|
164
168
|
} else {
|
|
165
|
-
handOver(binary)
|
|
169
|
+
handOver(binary)
|
|
166
170
|
}
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
/** Run the native compiler and give the caller back exactly what it answered. */
|
|
170
174
|
function handOver(binary) {
|
|
171
|
-
const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" })
|
|
175
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" })
|
|
172
176
|
if (result.error !== undefined && result.error !== null) {
|
|
173
177
|
// Installed and will not start -- a broken or partial install rather than an
|
|
174
178
|
// unsupported platform. There is nothing to fall back to, so this is a
|
|
175
179
|
// refusal with the reason in it rather than a warning above a slower compile.
|
|
176
|
-
refuse({ binary, reason: result.error.message })
|
|
177
|
-
|
|
180
|
+
refuse({ binary, reason: result.error.message })
|
|
181
|
+
} else if (result.signal !== null) {
|
|
178
182
|
// Re-raise rather than translating to an exit code, so that a crash or an
|
|
179
183
|
// interrupt reaches the shell as the signal it was. `process.exitCode`
|
|
180
184
|
// cannot express one, and a wrapper that turned SIGINT into exit 130 would
|
|
181
185
|
// make `nish` the one command in a pipeline that did.
|
|
182
|
-
process.kill(process.pid, result.signal)
|
|
186
|
+
process.kill(process.pid, result.signal)
|
|
183
187
|
} else {
|
|
184
|
-
process.exitCode = result.status ?? 0
|
|
188
|
+
process.exitCode = result.status ?? 0
|
|
185
189
|
}
|
|
186
190
|
}
|
package/bin/packaging.js
CHANGED
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
* trusting the two lists to stay equal.
|
|
22
22
|
*
|
|
23
23
|
* **This is plain JavaScript under `bin/`, and that is deliberate.** It used
|
|
24
|
-
* to be `src/packaging.ts`, compiled into `dist/` by `tsc` -- which made the
|
|
24
|
+
* to be stage0's `src/packaging.ts`, compiled into `dist/` by `tsc` -- which made the
|
|
25
25
|
* command a build artifact of the compiler it is supposed to install. `bin/`
|
|
26
26
|
* is what the tarball carries and what `bin.nish` points into, so the launcher
|
|
27
27
|
* and its platform table now live where they ship, need no build step, and
|
|
28
|
-
* survived the deletion of `src/` (`docs/wp19-stage0-retirement.md` R6). The
|
|
28
|
+
* survived the deletion of stage0's `src/` (`docs/wp19-stage0-retirement.md` R6). The
|
|
29
29
|
* types they lose are not much of a loss for two string maps; what they gain is
|
|
30
30
|
* that `npm pack` ships the same bytes this repository runs.
|
|
31
31
|
*/
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
const ARCH_BY_CPU = {
|
|
41
41
|
x64: "x86_64",
|
|
42
42
|
arm64: "aarch64",
|
|
43
|
-
}
|
|
43
|
+
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* The operating-system half. It is an identity map today, because node's
|
|
@@ -52,7 +52,7 @@ const ARCH_BY_CPU = {
|
|
|
52
52
|
const OS_BY_PLATFORM = {
|
|
53
53
|
linux: "linux",
|
|
54
54
|
darwin: "darwin",
|
|
55
|
-
}
|
|
55
|
+
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Every asset a release actually attaches, sorted, in `seed-targets.json`'s
|
|
@@ -68,7 +68,7 @@ const OS_BY_PLATFORM = {
|
|
|
68
68
|
* compares the two, so a literal is exactly as gated as a derivation and says
|
|
69
69
|
* only what is true.
|
|
70
70
|
*/
|
|
71
|
-
export const SUPPORTED_ASSETS = ["aarch64-darwin", "aarch64-linux", "x86_64-darwin", "x86_64-linux"]
|
|
71
|
+
export const SUPPORTED_ASSETS = ["aarch64-darwin", "aarch64-linux", "x86_64-darwin", "x86_64-linux"]
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* The `asset` for a node platform/arch pair, or `null` when this project
|
|
@@ -81,23 +81,29 @@ export const SUPPORTED_ASSETS = ["aarch64-darwin", "aarch64-linux", "x86_64-darw
|
|
|
81
81
|
* exits non-zero (`docs/wp12-release.md`, "Which compiler the package ships").
|
|
82
82
|
*/
|
|
83
83
|
export const assetFor = (platform, arch) => {
|
|
84
|
-
const os = OS_BY_PLATFORM[platform]
|
|
85
|
-
const cpu = ARCH_BY_CPU[arch]
|
|
86
|
-
if (os === undefined || cpu === undefined)
|
|
87
|
-
|
|
88
|
-
}
|
|
84
|
+
const os = OS_BY_PLATFORM[platform]
|
|
85
|
+
const cpu = ARCH_BY_CPU[arch]
|
|
86
|
+
if (os === undefined || cpu === undefined) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
return `${cpu}-${os}`
|
|
90
|
+
}
|
|
89
91
|
|
|
90
92
|
/** The inverse, for the generator and the tests: an `asset` back to npm's pair. */
|
|
91
93
|
export const targetForAsset = (asset) => {
|
|
92
|
-
const dash = asset.indexOf("-")
|
|
93
|
-
if (dash < 0)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
const dash = asset.indexOf("-")
|
|
95
|
+
if (dash < 0) {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
const arch = asset.slice(0, dash)
|
|
99
|
+
const os = asset.slice(dash + 1)
|
|
100
|
+
const cpu = Object.keys(ARCH_BY_CPU).find((key) => ARCH_BY_CPU[key] === arch)
|
|
101
|
+
const platform = Object.keys(OS_BY_PLATFORM).find((key) => OS_BY_PLATFORM[key] === os)
|
|
102
|
+
if (cpu === undefined || platform === undefined) {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
return { asset, os: platform, cpu }
|
|
106
|
+
}
|
|
101
107
|
|
|
102
108
|
/**
|
|
103
109
|
* The package holding the binary for one asset, derived from the main
|
|
@@ -108,22 +114,22 @@ export const targetForAsset = (asset) => {
|
|
|
108
114
|
* is scoped -- docs/wp12-release.md "The npm name"), and a list of five literal
|
|
109
115
|
* names would be five places to edit if it ever moves again.
|
|
110
116
|
*/
|
|
111
|
-
export const platformPackageName = (packageName, asset) => `${packageName}-${asset}
|
|
117
|
+
export const platformPackageName = (packageName, asset) => `${packageName}-${asset}`
|
|
112
118
|
|
|
113
119
|
/**
|
|
114
120
|
* The diagnostic code every refusal here carries: `NL0002`, the toolchain code.
|
|
115
121
|
*
|
|
116
|
-
* Not a new code, and that is the point. `
|
|
122
|
+
* Not a new code, and that is the point. `src/codes.ts` defines `NL0002` as
|
|
117
123
|
* "the toolchain `--link` needs could not be used (exit 3)", and a prebuilt
|
|
118
124
|
* compiler that is absent or will not start is the same class of failure seen
|
|
119
125
|
* one step earlier: no source position, nothing wrong with the program, and the
|
|
120
126
|
* thing that could not be run is a binary rather than the input. Reusing it
|
|
121
127
|
* keeps the launcher out of the diagnostic registry entirely -- a code minted
|
|
122
|
-
* here would be one `
|
|
123
|
-
* never print, and `tests/
|
|
128
|
+
* here would be one `src/codes.ts` mirrors for a message the compiler can
|
|
129
|
+
* never print, and `tests/diagnostic-coverage.js` would then want a case
|
|
124
130
|
* provoking a rule that does not exist.
|
|
125
131
|
*/
|
|
126
|
-
export const NO_COMPILER_CODE = "NL0002"
|
|
132
|
+
export const NO_COMPILER_CODE = "NL0002"
|
|
127
133
|
|
|
128
134
|
/**
|
|
129
135
|
* Why there is no compiler to run, and what to do about it: the one-line
|
|
@@ -160,13 +166,13 @@ export const NO_COMPILER_CODE = "NL0002";
|
|
|
160
166
|
* refused wants to know whether the list is the whole list. It is.
|
|
161
167
|
*/
|
|
162
168
|
export const noCompilerMessage = ({ platform, arch, packageName, unstartable = null }) => {
|
|
163
|
-
const asset = assetFor(platform, arch)
|
|
164
|
-
const published = SUPPORTED_ASSETS.join(" ")
|
|
169
|
+
const asset = assetFor(platform, arch)
|
|
170
|
+
const published = SUPPORTED_ASSETS.join(" ")
|
|
165
171
|
const preamble =
|
|
166
172
|
" This package installs a prebuilt native compiler. One is published for:\n" +
|
|
167
173
|
` ${published}\n\n` +
|
|
168
174
|
" There is no compiler inside the package to fall back to, and nothing is\n" +
|
|
169
|
-
" compiled on your machine on any path.\n"
|
|
175
|
+
" compiled on your machine on any path.\n"
|
|
170
176
|
if (asset === null) {
|
|
171
177
|
return {
|
|
172
178
|
code: NO_COMPILER_CODE,
|
|
@@ -182,7 +188,7 @@ export const noCompilerMessage = ({ platform, arch, packageName, unstartable = n
|
|
|
182
188
|
" NISH_BOOTSTRAP=<a released nish that runs here> scripts/bootstrap.sh\n" +
|
|
183
189
|
" docs/INSTALL.md has the detail, including what to do when no released\n" +
|
|
184
190
|
" binary runs on this platform at all.\n",
|
|
185
|
-
}
|
|
191
|
+
}
|
|
186
192
|
}
|
|
187
193
|
if (unstartable !== null) {
|
|
188
194
|
return {
|
|
@@ -195,14 +201,14 @@ export const noCompilerMessage = ({ platform, arch, packageName, unstartable = n
|
|
|
195
201
|
`nish: ${unstartable.binary} could not be started (${unstartable.reason})\n\n${preamble}\n` +
|
|
196
202
|
" That is a broken or partly written install rather than an unsupported\n" +
|
|
197
203
|
" platform. Reinstall the package, or run `npm rebuild` if the tree moved.\n",
|
|
198
|
-
}
|
|
204
|
+
}
|
|
199
205
|
}
|
|
200
206
|
// A `packageName` of `null` means this package's own `package.json` could not
|
|
201
207
|
// be read, which is a broken install and not the moment to guess: naming the
|
|
202
208
|
// wrong package sends the user to install something that does not exist, so
|
|
203
209
|
// the sentence loses the name instead of inventing one.
|
|
204
|
-
const named = packageName === null ? null : platformPackageName(packageName, asset)
|
|
205
|
-
const pkg = named === null ? `the platform package for ${asset}` : named
|
|
210
|
+
const named = packageName === null ? null : platformPackageName(packageName, asset)
|
|
211
|
+
const pkg = named === null ? `the platform package for ${asset}` : named
|
|
206
212
|
return {
|
|
207
213
|
code: NO_COMPILER_CODE,
|
|
208
214
|
summary:
|
|
@@ -216,5 +222,5 @@ export const noCompilerMessage = ({ platform, arch, packageName, unstartable = n
|
|
|
216
222
|
" `--no-optional`, a lockfile without the platform packages, or a registry that\n" +
|
|
217
223
|
" does not carry them yet. Reinstall the package, or install the pair by hand as\n" +
|
|
218
224
|
" docs/INSTALL.md shows.\n",
|
|
219
|
-
}
|
|
220
|
-
}
|
|
225
|
+
}
|
|
226
|
+
}
|
package/docs/AI.md
CHANGED
|
@@ -198,16 +198,42 @@ const good = (a: i32, b: i64): i64 => {
|
|
|
198
198
|
};
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
**`integer
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
**`integer<Lo, Hi>` is an `i32` that stays in `[Lo, Hi]`.** The bounds are two
|
|
202
|
+
integer literals (a sign allowed) inside `i32`, and it is an `i32` everywhere
|
|
203
|
+
the machine can see. Putting a value *into* one is checked: an `i32` or
|
|
204
|
+
another range is compared once and the program panics (exit 1) outside the
|
|
205
|
+
range, a literal outside it is a compile error, and a literal inside it or a
|
|
206
|
+
narrower range costs nothing. So does a value a loop condition or a guard
|
|
207
|
+
already bounds (`b` below), and `toI32` of a `u8`; a check left inside a loop
|
|
208
|
+
is a performance warning (NL9013) naming the guard that removes it. A range
|
|
209
|
+
bounds an index too: `integer<0, 255>`, like `u8`, needs only a length guard of
|
|
210
|
+
256. Taking one *out* is free: every operator reads
|
|
211
|
+
it as `i32`, a `const` keeps the range and a `let` widens to `i32`. A `u8` is
|
|
212
|
+
not an `integer<0, 255>` (convert with `toI32`), and a `declare function` may
|
|
213
|
+
not mention a range. Put the range on the value you use, not on a loop
|
|
214
|
+
counter, which has to leave the range to end the loop.
|
|
215
|
+
|
|
216
|
+
```ts nish:ok
|
|
217
|
+
const getByte = (buf: u8[], i: integer<0, 255>): u8 => buf[i];
|
|
218
|
+
|
|
219
|
+
const sumAll = (buf: u8[]): i32 => {
|
|
220
|
+
let sum = 0;
|
|
221
|
+
for (let i = 0; i < 256 && i < buf.length; i++) {
|
|
222
|
+
const b: integer<0, 255> = i;
|
|
223
|
+
sum = sum + toI32(getByte(buf, b));
|
|
224
|
+
}
|
|
225
|
+
return sum;
|
|
226
|
+
};
|
|
209
227
|
```
|
|
210
228
|
|
|
229
|
+
```ts nish:err-body NL2384
|
|
230
|
+
const digit: integer<0, 9> = 12;
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Declaring a type alias, enum, class, interface or function named `integer` is
|
|
234
|
+
refused, because the name is the type. A value named `integer` (a local or a
|
|
235
|
+
module constant) is fine.
|
|
236
|
+
|
|
211
237
|
```ts nish:err NL2332
|
|
212
238
|
class integer {
|
|
213
239
|
value: i32 = 0;
|
|
@@ -317,6 +343,8 @@ Narrowing follows the same engine as `T | null` below: it applies to a
|
|
|
317
343
|
**variable**, never a property path; it ends at any assignment to that
|
|
318
344
|
variable; and it is dropped before a loop that assigns it. `if (r.isOk()) A
|
|
319
345
|
else B` narrows in `A`, and after the `if` when `B` cannot fall through.
|
|
346
|
+
The proof stays with `r`: `const y = r`, `c ? r : q` and `[r]` are plain
|
|
347
|
+
`Result`s, so test `y` itself before `y.value`.
|
|
320
348
|
|
|
321
349
|
`panic(message)` is the other ending: message to stderr, exit 1. It is for an
|
|
322
350
|
invariant that cannot hold, not for a failure a caller should handle. It
|
|
@@ -360,8 +388,9 @@ export const main = (): i32 => {
|
|
|
360
388
|
`Map.get` (see [Map and Set](#map-and-set)).
|
|
361
389
|
- Narrowing applies to a **local or parameter**, never a property path. `if
|
|
362
390
|
(n.next !== null) n.next.v` is rejected — copy into a local first.
|
|
363
|
-
- A narrowing ends at any assignment to the variable,
|
|
364
|
-
|
|
391
|
+
- A narrowing ends at any assignment to the variable, including one in an
|
|
392
|
+
earlier operand of the same `&&` / `||` chain, and is dropped before a loop
|
|
393
|
+
whose body, condition or update assigns it.
|
|
365
394
|
- Two nullables cannot be compared with each other; compare each with `null`.
|
|
366
395
|
- `new Array<T | null>(n)` is allowed: the zero fill *is* `null`.
|
|
367
396
|
|
|
@@ -723,6 +752,62 @@ import { parallelReduce } from "nish/threads";
|
|
|
723
752
|
export const main = (): i32 => parallelReduce([1, 2, 3], (a, b) => a + b, 1); // `+` folds from 0
|
|
724
753
|
```
|
|
725
754
|
|
|
755
|
+
### Scoped tasks: `using s = scope()`
|
|
756
|
+
|
|
757
|
+
Different functions at once, one thread each: open a scope with `using`, give
|
|
758
|
+
it tasks with `spawn(entry, arg, dst, at)`, and every task has run and stored
|
|
759
|
+
`entry(arg)` into `dst[at]` when the block ends. (A plain block, for the same
|
|
760
|
+
reason as the one above: `tests/link/thread_scope_basic` compiles it.)
|
|
761
|
+
|
|
762
|
+
```ts
|
|
763
|
+
import { scope } from "nish/threads";
|
|
764
|
+
|
|
765
|
+
const sumOf = (xs: f64[]): f64 => xs[0] + xs[1];
|
|
766
|
+
const square = (n: i32): i32 => n * n;
|
|
767
|
+
|
|
768
|
+
export const main = (): i32 => {
|
|
769
|
+
const xs: f64[] = [1.5, 2.5];
|
|
770
|
+
const sums: f64[] = [0.0];
|
|
771
|
+
const squares: i32[] = [0];
|
|
772
|
+
{
|
|
773
|
+
using s = scope();
|
|
774
|
+
s.spawn(sumOf, xs, sums, 0);
|
|
775
|
+
s.spawn(square, 12, squares, 0);
|
|
776
|
+
} // joined here, on every exit
|
|
777
|
+
console.log(`${sums[0]} ${squares[0]}`); // 4 144
|
|
778
|
+
return 0;
|
|
779
|
+
};
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
- **The tasks run when the block ends**, together, and each answer is stored
|
|
783
|
+
after the last one finishes. So read a destination after the block. A
|
|
784
|
+
destination is a `const` bound to a fresh array (`[0, 0]`, `new Array`) that
|
|
785
|
+
you only index; never pass it on. Between the first `spawn` and the block's
|
|
786
|
+
end, don't read a destination, and don't write memory a task could read (a
|
|
787
|
+
store into an argument, a call that writes through its argument).
|
|
788
|
+
- **`using` takes only `scope()`**, and `scope()` only comes from `using`.
|
|
789
|
+
The scope is only ever the receiver of a `spawn` statement: never pass it,
|
|
790
|
+
store it or return it.
|
|
791
|
+
- **The task is a named top-level function**, not an arrow, and it follows the
|
|
792
|
+
data-parallel rules: it writes nothing anybody else can see, answers a number,
|
|
793
|
+
a `boolean` or an enum, and leaves `Arena` alone. A task cannot open a scope
|
|
794
|
+
of its own or call `parallelMapInto`.
|
|
795
|
+
- **Any argument will do** — an array, an object — because nothing writes
|
|
796
|
+
memory while the tasks run. There is no thread count: one task, one thread.
|
|
797
|
+
|
|
798
|
+
```ts nish:err NL2388
|
|
799
|
+
import { scope } from "nish/threads";
|
|
800
|
+
|
|
801
|
+
const one = (n: i32): i32 => n + 1;
|
|
802
|
+
|
|
803
|
+
export const main = (): i32 => {
|
|
804
|
+
const out: i32[] = [0];
|
|
805
|
+
const s = scope(); // a scope must be introduced by `using`
|
|
806
|
+
s.spawn(one, 1, out, 0);
|
|
807
|
+
return out[0];
|
|
808
|
+
};
|
|
809
|
+
```
|
|
810
|
+
|
|
726
811
|
### Calling C
|
|
727
812
|
|
|
728
813
|
`declare function name(params): T;` declares a C function this program calls but
|