@amritk/nish 0.10.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/LICENSE +21 -0
- package/README.md +553 -0
- package/bin/launcher.js +186 -0
- package/bin/nish +23 -0
- package/bin/packaging.js +220 -0
- package/docs/AI.md +1076 -0
- package/docs/INSTALL.md +468 -0
- package/llms.txt +49 -0
- package/package.json +87 -0
- package/runtime/nish.d.ts +290 -0
- package/runtime/nish.h +340 -0
- package/runtime/nish.mjs +143 -0
- package/runtime/runtime.c +1184 -0
- package/runtime/runtime_os.c +351 -0
- package/runtime/runtime_parallel.c +156 -0
- package/runtime/runtime_wasm.c +99 -0
- package/runtime/shim.mjs +672 -0
- package/scripts/bootstrap.sh +357 -0
- package/scripts/build.sh +279 -0
- package/scripts/changelog-gen.mjs +528 -0
- package/scripts/changelog-section.sh +28 -0
- package/scripts/ci-profile.mjs +187 -0
- package/scripts/codes-registry.js +73 -0
- package/scripts/gen-diagnostic-codes.mjs +199 -0
- package/scripts/gen-pow5-tables.py +45 -0
- package/scripts/nish-compiler.sh +17 -0
- package/scripts/platform-package.mjs +91 -0
- package/scripts/postinstall.mjs +133 -0
- package/scripts/size-report.sh +72 -0
- package/scripts/smoke.sh +94 -0
- package/scripts/verify-binaries.sh +213 -0
- package/std/README.md +185 -0
- package/std/json.ts +402 -0
- package/std/pair.ts +28 -0
- package/std/testing.ts +347 -0
- package/std/text.ts +193 -0
package/docs/INSTALL.md
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# Installing nish
|
|
2
|
+
|
|
3
|
+
`nish` compiles a static subset of TypeScript to LLVM IR and, with
|
|
4
|
+
`--link`, to a native binary. The compiler is itself a native binary, written
|
|
5
|
+
in Nish (`self/`), and needs nothing to run; the `--link` step (and anything
|
|
6
|
+
else that turns `.ll` into machine code) needs an LLVM toolchain.
|
|
7
|
+
|
|
8
|
+
## 1. Prerequisites
|
|
9
|
+
|
|
10
|
+
- **Node.js 22.18 or newer** (22 is what CI uses), for the npm route and for
|
|
11
|
+
building from a checkout — the launcher, the postinstall step and the test
|
|
12
|
+
harness are Node scripts. It is the version where Node strips TypeScript
|
|
13
|
+
types without a flag, which `docs/RUN_UNDER_NODE.md` relies on. The
|
|
14
|
+
`install.sh` route below needs no Node at all.
|
|
15
|
+
- **clang** (LLVM 18 recommended) and **lld**, for `--link`. Without them
|
|
16
|
+
`nish` still writes the `.ll` files and exits 3 with the install
|
|
17
|
+
command for your platform when you ask for `--link`.
|
|
18
|
+
|
|
19
|
+
### Ubuntu / Debian
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
sudo apt-get update
|
|
23
|
+
sudo apt-get install -y clang-18 lld-18 llvm-18
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Debian ships versioned binaries (`clang-18`, `ld.lld-18`, ...). Either point
|
|
27
|
+
`nish` at the versioned compiler with `CC=clang-18`, or expose the plain
|
|
28
|
+
names on `PATH`:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
mkdir -p ~/.local/llvm-bin
|
|
32
|
+
for t in clang clang++ llc llvm-as opt ld.lld wasm-ld; do
|
|
33
|
+
ln -sf "/usr/bin/$t-18" ~/.local/llvm-bin/$t
|
|
34
|
+
done
|
|
35
|
+
export PATH=~/.local/llvm-bin:$PATH # add to your shell profile
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
(`sudo apt-get install -y clang lld llvm` also works if your release's default
|
|
39
|
+
LLVM is 15 or newer.)
|
|
40
|
+
|
|
41
|
+
### Fedora
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
sudo dnf install clang lld llvm
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### macOS
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
brew install llvm@18
|
|
51
|
+
export PATH="$(brew --prefix llvm@18)/bin:$PATH" # add to your shell profile
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Xcode's `clang` (`xcode-select --install`) also works for native binaries;
|
|
55
|
+
the Homebrew LLVM is needed for the `wasm` and `wasi` profiles (`wasm-ld`).
|
|
56
|
+
|
|
57
|
+
### WASI (optional, for `--profile wasi`)
|
|
58
|
+
|
|
59
|
+
The `wasi` profile links the runtime against wasi-libc so that whole programs
|
|
60
|
+
(strings, `console.log`, files, `process.argv`) run under any WASI host. It
|
|
61
|
+
needs a WASI sysroot and compiler-rt's wasm32 builtins next to your clang:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Ubuntu / Debian: the packaged sysroot lands in /usr/lib/wasi-sysroot
|
|
65
|
+
sudo apt-get install -y wasi-libc libclang-rt-18-dev-wasm32
|
|
66
|
+
|
|
67
|
+
# Any platform: wasi-sdk's sysroot and builtins tarballs (versions that match your clang)
|
|
68
|
+
curl -L -o - https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sysroot-24.0.tar.gz | tar -xz -C /opt
|
|
69
|
+
curl -L -o - https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/libclang_rt.builtins-wasm32-wasi-24.0.tar.gz | tar -xz -C /opt
|
|
70
|
+
export WASI_SYSROOT=/opt/wasi-sysroot-24.0 # add to your shell profile
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`scripts/build.sh` looks for the sysroot in `WASI_SYSROOT`,
|
|
74
|
+
`/usr/lib/wasi-sysroot`, `/opt/wasi-sdk/share/wasi-sysroot` and
|
|
75
|
+
`/usr/share/wasi-sysroot`, and for `libclang_rt.builtins-wasm32.a` in clang's
|
|
76
|
+
resource directory, next to the sysroot (as the tarball above unpacks it),
|
|
77
|
+
in `<sysroot>/lib/wasm32-wasi/`, or at `WASI_BUILTINS=<file>`. Then:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
nish examples/argv.ts --link build/argv.wasm --profile wasi
|
|
81
|
+
node examples/wasi-host.mjs build/argv.wasm 3 4 five # or: wasmtime build/argv.wasm 3 4 five
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Node's built-in `node:wasi` runs the module (argv, stdout, exit code and the
|
|
85
|
+
working directory behave as natively); `npm test` prints
|
|
86
|
+
`skipped: no WASI sysroot` and moves on when none is installed.
|
|
87
|
+
|
|
88
|
+
### Windows
|
|
89
|
+
|
|
90
|
+
Windows is not supported natively yet: `scripts/build.sh` is a bash script
|
|
91
|
+
and the runtime is built with clang on a POSIX toolchain. Use
|
|
92
|
+
[WSL](https://learn.microsoft.com/windows/wsl/install) with Ubuntu and follow
|
|
93
|
+
the Ubuntu steps inside it.
|
|
94
|
+
|
|
95
|
+
## 2. Install the compiler
|
|
96
|
+
|
|
97
|
+
**Two ways in, and they install the same compiler.** Neither compiles anything
|
|
98
|
+
on your machine: the binary was built, `--verify`d and smoke-tested on hardware
|
|
99
|
+
of its own architecture by the release workflow before the release existed, and
|
|
100
|
+
installing is a download and an unpack.
|
|
101
|
+
|
|
102
|
+
Through npm, which is also how an Nish *program* resolves its dependencies
|
|
103
|
+
([wp21-packages.md](wp21-packages.md)), so this is the one to pick if you want
|
|
104
|
+
the compiler pinned per project in a `package.json`:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm install -g @amritk/nish
|
|
108
|
+
nish --version
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Or without npm and without node at all:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
curl -fsSL https://raw.githubusercontent.com/amritk/nish/main/install.sh | sh
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
That unpacks into `~/.nish` and prints the one line to add to your shell
|
|
118
|
+
profile. On a platform it has no binary for it says so and names the four it
|
|
119
|
+
does build — the npm route covers exactly the same four and no more, because
|
|
120
|
+
since 0.6.0 there is no compiler inside that package to fall back to either.
|
|
121
|
+
|
|
122
|
+
**Upgrading is running it again.** There is no separate command: it installs
|
|
123
|
+
over an existing install, says `upgrading nish 0.3.0 to 0.4.0`, and says
|
|
124
|
+
`nothing to do` when the version already matches.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
curl -fsSL https://raw.githubusercontent.com/amritk/nish/main/install.sh | sh # latest
|
|
128
|
+
curl -fsSL https://raw.githubusercontent.com/amritk/nish/main/install.sh | sh -s 0.4.0 # a version
|
|
129
|
+
sh install.sh --dir /opt/nish # somewhere other than ~/.nish (or NISH_INSTALL)
|
|
130
|
+
sh install.sh --force # reinstall the version already there
|
|
131
|
+
sh install.sh --uninstall # remove it
|
|
132
|
+
sh install.sh --help # all of the above
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
An upgrade cannot leave you without a compiler. The new one is unpacked beside
|
|
136
|
+
the old, checked that it runs *and* that it reports the version that was asked
|
|
137
|
+
for — which catches a truncated download, a tarball built for another
|
|
138
|
+
architecture, and an asset whose name does not match what is inside it — and
|
|
139
|
+
only then swapped in. If any of that fails the install you had is untouched.
|
|
140
|
+
|
|
141
|
+
The npm route installs the **native** compiler, and it is a download rather than a
|
|
142
|
+
build: nothing is compiled on your machine. The package declares one
|
|
143
|
+
`nish-<os>-<arch>` package per supported platform as an `optionalDependencies`
|
|
144
|
+
entry with `os` and `cpu` set, so npm fetches exactly the one that matches and
|
|
145
|
+
skips the rest. Each of those carries the self-hosted compiler — `self/`
|
|
146
|
+
compiled by itself — already built, `--verify`d and smoke-tested on a machine
|
|
147
|
+
of its own architecture by the release workflow.
|
|
148
|
+
|
|
149
|
+
A postinstall step then puts that binary on your PATH directly, so `nish` is
|
|
150
|
+
the compiler rather than a node script that starts it — 3.2 ms per invocation
|
|
151
|
+
instead of 94 ms, measured. If you install with `npm ci --ignore-scripts`, or
|
|
152
|
+
in a sandbox that disables scripts, the step does not run and `nish` is a small
|
|
153
|
+
node launcher that spawns the same binary: the same compiler and the same
|
|
154
|
+
answers, about 91 ms slower to start. Nothing else changes, and the install
|
|
155
|
+
never fails over it.
|
|
156
|
+
|
|
157
|
+
> [!IMPORTANT]
|
|
158
|
+
> **Unpacking a release tarball onto your `PATH` is not an install.** The
|
|
159
|
+
> compiler resolves `scripts/build.sh` and `runtime/` from `argv[0]`'s
|
|
160
|
+
> directory, and a bare `nish` found on `PATH` has no directory in it — so it
|
|
161
|
+
> looks in `./..` and every `--link` fails against whatever your working
|
|
162
|
+
> directory happens to be, while `--version` and `-o` keep working. Both
|
|
163
|
+
> installers above handle it by running the binary through a one-line `exec` of
|
|
164
|
+
> an absolute path. If you unpack a tarball by hand, invoke it by path
|
|
165
|
+
> (`nish-<version>-<asset>/bin/nish`) or write that wrapper yourself. The
|
|
166
|
+
> underlying defect is [wp19 §5a](wp19-stage0-retirement.md#5a-what-r6-is-waiting-on)
|
|
167
|
+
> item 4.
|
|
168
|
+
|
|
169
|
+
**Do not install `nish`.** That name on the public registry has belonged to
|
|
170
|
+
`stdarg`'s "A Node.js Interactive shell" since February 2014 — versions 0.0.0
|
|
171
|
+
and 0.0.1, both deprecated by their author, nothing published since. **This
|
|
172
|
+
compiler is `@amritk/nish`** (decided 2026-09-19,
|
|
173
|
+
[docs/wp12-release.md](wp12-release.md#the-npm-name)), and the command it
|
|
174
|
+
installs is still `nish` — the package name and the command are two different
|
|
175
|
+
strings, and that is the one thing the scope costs.
|
|
176
|
+
|
|
177
|
+
**On a platform this project attaches no binary for, `npm install` gets you a
|
|
178
|
+
command that refuses.** The table below is the whole list, so musl, FreeBSD and
|
|
179
|
+
32-bit anything are outside it, and outside it `nish` prints what happened,
|
|
180
|
+
names the four platforms that do have a binary, and exits 3:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
$ nish --version
|
|
184
|
+
nish: no prebuilt compiler for freebsd/x64
|
|
185
|
+
|
|
186
|
+
This package installs a prebuilt native compiler. One is published for:
|
|
187
|
+
aarch64-darwin aarch64-linux x86_64-darwin x86_64-linux
|
|
188
|
+
|
|
189
|
+
There is no compiler inside the package to fall back to, and nothing is
|
|
190
|
+
compiled on your machine on any path.
|
|
191
|
+
|
|
192
|
+
To get a compiler for this machine, build one from a released `nish` in a
|
|
193
|
+
checkout of the repository:
|
|
194
|
+
NISH_BOOTSTRAP=<a released nish that runs here> scripts/bootstrap.sh
|
|
195
|
+
docs/INSTALL.md has the detail, including what to do when no released
|
|
196
|
+
binary runs on this platform at all.
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Until 0.6.0 it ran the TypeScript compiler that shipped in the same package
|
|
200
|
+
instead — the same compiler by every test here, about eight times slower, and
|
|
201
|
+
no C toolchain needed. That compiler was `src/`, which was deleted in R6
|
|
202
|
+
([wp19](wp19-stage0-retirement.md)), so there is nothing left in the package to
|
|
203
|
+
fall back to. The cost is stated where the rest of that deletion's costs are,
|
|
204
|
+
in [wp19 §6](wp19-stage0-retirement.md#6-what-retirement-costs-stated-plainly),
|
|
205
|
+
and it is a real one: **musl, FreeBSD and 32-bit platforms have no `npm
|
|
206
|
+
install` route to a compiler any more.**
|
|
207
|
+
|
|
208
|
+
What is left for those platforms is the bootstrap, and it needs one thing this
|
|
209
|
+
page cannot hand you: a compiler that already runs on the machine. The seed does
|
|
210
|
+
not have to be *for* the machine — it has to run *on* it — so the practical
|
|
211
|
+
routes are
|
|
212
|
+
|
|
213
|
+
- a glibc container or host to build in, when the target is musl. Build
|
|
214
|
+
`build/nish` there with `NISH_BOOTSTRAP=<released nish> scripts/bootstrap.sh`,
|
|
215
|
+
and then **emit the IR in the container and link it on the musl host**:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# in the glibc container, where build/nish runs
|
|
219
|
+
build/nish app.ts -o app.ll
|
|
220
|
+
# on the musl host, with its own clang and the runtime from this repository
|
|
221
|
+
clang app.ll runtime/runtime.c runtime/runtime_os.c -lm -o app
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`--link`ing inside the container is the mistake to avoid: it shells out to the
|
|
225
|
+
container's `clang` and hands you a glibc binary, which is the one thing the
|
|
226
|
+
musl host cannot run. The split works because the compiler's output is
|
|
227
|
+
target-neutral text — the C toolchain that compiles `runtime.c` and links is
|
|
228
|
+
what decides the libc, not the compiler that wrote the IR;
|
|
229
|
+
- `--profile wasi`, which compiles the compiler itself into a single WASI module
|
|
230
|
+
that runs anywhere Node does (`web/compile.mjs` drives one). This is not a
|
|
231
|
+
shipped artefact and there is no supported install path for it;
|
|
232
|
+
- a target this project does build, if one will do.
|
|
233
|
+
|
|
234
|
+
If you need one of these to be a first-class install, that is the conversation
|
|
235
|
+
to have on the issue tracker rather than a workaround to discover here.
|
|
236
|
+
|
|
237
|
+
**Nothing is published to the registry yet.** The installer is built and
|
|
238
|
+
tested, and what is left is a person publishing a release under it, which
|
|
239
|
+
[docs/wp12-release.md](wp12-release.md#release-procedure) step 4 is about.
|
|
240
|
+
Until that happens, install from a release.
|
|
241
|
+
|
|
242
|
+
From a release tarball on GitHub — the same package `npm publish` would upload,
|
|
243
|
+
carried by the release instead of the registry. The `Release` workflow attaches
|
|
244
|
+
the npm tarball to the release it builds for a `v*` tag. `npm pack` names it
|
|
245
|
+
after `package.json#name`, so it is `amritk-nish-<version>.tgz` from 0.4.0 on
|
|
246
|
+
and `nish-<version>.tgz` for the releases cut before the scope was taken — the
|
|
247
|
+
example below is one of those:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
curl -LO https://github.com/amritk/nish/releases/download/v0.2.0/nish-0.2.0.tgz
|
|
251
|
+
npm install -g ./nish-0.2.0.tgz
|
|
252
|
+
nish --version
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Installed on its own like that, the package finds no platform package next to
|
|
256
|
+
it and refuses — the same refusal as above, but for the other reason: the binary
|
|
257
|
+
was never fetched rather than none existing for your machine. The message says
|
|
258
|
+
which of the two it is, because the remedy differs. **So install the pair**: a
|
|
259
|
+
release from 0.4.0 on attaches the platform packages beside the npm tarball,
|
|
260
|
+
under the name `npm pack` gave them.
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
base=https://github.com/amritk/nish/releases/download/v0.4.0
|
|
264
|
+
curl -LO $base/amritk-nish-0.4.0.tgz
|
|
265
|
+
curl -LO $base/amritk-nish-x86_64-linux-0.4.0.tgz # the row matching your machine
|
|
266
|
+
npm install -g ./amritk-nish-0.4.0.tgz ./amritk-nish-x86_64-linux-0.4.0.tgz
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
As a native compiler, which needs no Node at all. A release also attaches the
|
|
270
|
+
self-hosted compiler — the binary `self/` produces by compiling itself — one
|
|
271
|
+
per supported platform, from the version named in the last column:
|
|
272
|
+
|
|
273
|
+
| Asset | For | Attached from |
|
|
274
|
+
| --- | --- | --- |
|
|
275
|
+
| `nish-<version>-x86_64-linux.tar.gz` | Linux on Intel or AMD | v0.1.1 |
|
|
276
|
+
| `nish-<version>-aarch64-linux.tar.gz` | Linux on ARM | v0.4.0 |
|
|
277
|
+
| `nish-<version>-x86_64-darwin.tar.gz` | macOS on Intel | v0.4.0 |
|
|
278
|
+
| `nish-<version>-aarch64-darwin.tar.gz` | macOS on Apple Silicon | v0.4.0 |
|
|
279
|
+
|
|
280
|
+
Each is built and smoke-tested on a machine of its own architecture rather than
|
|
281
|
+
cross-compiled, so the one you take has compiled and run two programs before it
|
|
282
|
+
reached you — a one-module one and a two-module one, from a directory unrelated
|
|
283
|
+
to the machine that built it.
|
|
284
|
+
|
|
285
|
+
The last column is there because a release is a past event and a workflow is
|
|
286
|
+
not. `release.yml` builds all four, but a release already published cannot grow
|
|
287
|
+
an asset: **v0.2.0, the current release, attaches `x86_64-linux` only**. Those
|
|
288
|
+
versions are not prose — they are `attachedSince` in
|
|
289
|
+
[`.github/seed-targets.json`](https://github.com/amritk/nish/blob/main/.github/seed-targets.json),
|
|
290
|
+
the same file the release workflow builds its matrix from, so this table and
|
|
291
|
+
the assets cannot drift apart without a test failing.
|
|
292
|
+
|
|
293
|
+
**The three new rows say v0.4.0 rather than v0.3.0 on purpose.** The workflow
|
|
294
|
+
builds all four now, but nothing in this repository has ever run on macOS or on
|
|
295
|
+
ARM Linux, and a row that has never run is not something to put in front of a
|
|
296
|
+
release: `release` needs the whole matrix, so one red row blocks the publish.
|
|
297
|
+
They wait for a release after the run that exercises them. The two macOS rows
|
|
298
|
+
wait on a second thing as well — the bootstrap's `stage3 == stage2` check does
|
|
299
|
+
not hold as a raw byte comparison under `ld64`, and which bytes actually differ
|
|
300
|
+
has not been measured on real hardware, so a darwin row may turn up work rather
|
|
301
|
+
than a green tick. `scripts/verify-binaries.sh`'s header is the long version.
|
|
302
|
+
|
|
303
|
+
Check [the releases page](https://github.com/amritk/nish/releases/latest) for
|
|
304
|
+
what a given version actually carries; on a platform whose row has not shipped
|
|
305
|
+
yet, take the npm package above, or build from source below.
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
# pick the row above that matches `uname -s` and `uname -m`
|
|
309
|
+
curl -LO https://github.com/amritk/nish/releases/download/v0.2.0/nish-0.2.0-x86_64-linux.tar.gz
|
|
310
|
+
tar -xzf nish-0.2.0-x86_64-linux.tar.gz
|
|
311
|
+
nish-0.2.0-x86_64-linux/bin/nish --version
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Both URLs name the version rather than using GitHub's version-neutral
|
|
315
|
+
`/releases/latest/download/` form, because that form needs the asset's exact
|
|
316
|
+
file name and every asset name carries the version in it. **v0.2.0 is the
|
|
317
|
+
current release**; v0.1.1 before it was the first one with a binary attached.
|
|
318
|
+
The `v0.1.0` tag exists but has no release behind it and no assets, so every
|
|
319
|
+
`v0.1.0` download URL is a 404 — the tag was pushed by a workflow, and GitHub
|
|
320
|
+
raises no event for that, so nothing ever built it
|
|
321
|
+
([docs/wp12-release.md](wp12-release.md#release-procedure) step 2). When a newer
|
|
322
|
+
release exists, take its version from
|
|
323
|
+
[the releases page](https://github.com/amritk/nish/releases/latest).
|
|
324
|
+
|
|
325
|
+
Unpack it and run `bin/nish` from wherever you like; put that on `PATH` if you
|
|
326
|
+
want it there. Keep the directory intact rather than moving the binary out of
|
|
327
|
+
it: `--link` runs `scripts/build.sh` and compiles the C runtime
|
|
328
|
+
(`runtime/runtime.c` and `runtime/runtime_os.c`, the system-call half), and the
|
|
329
|
+
compiler finds all of them relative to its own location — `bin/nish` alone in a
|
|
330
|
+
directory can still emit IR with `-o`, but `--link` will tell you it cannot
|
|
331
|
+
find `scripts/build.sh`.
|
|
332
|
+
|
|
333
|
+
It still needs `clang` and `lld` on `PATH` for `--link` (§1), because linking
|
|
334
|
+
is the C toolchain's job, not the compiler's; what it does not need is Node.
|
|
335
|
+
x86_64 Linux is the only platform built today — on anything else, take the
|
|
336
|
+
`.tgz` above or build from a checkout.
|
|
337
|
+
|
|
338
|
+
From a checkout:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
git clone https://github.com/amritk/nish.git
|
|
342
|
+
cd nish
|
|
343
|
+
npm ci
|
|
344
|
+
bash scripts/fetch-seed.sh # the last release, into build/seed/
|
|
345
|
+
npm run build # seed -> stage1 -> stage2 = build/nish
|
|
346
|
+
build/nish hello.ts --link hello
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The build is a bootstrap, the way Rust and Go build themselves: the compiler
|
|
350
|
+
is written in Nish, so something has to compile it first, and that is the
|
|
351
|
+
previous release's `nish` binary — the seed. `scripts/fetch-seed.sh` downloads
|
|
352
|
+
it into `build/seed/`; `NISH_BOOTSTRAP=<path>` names one you already have and
|
|
353
|
+
skips the download. §2a has what the build does with it.
|
|
354
|
+
|
|
355
|
+
The published package ships `bin/` (the `nish` command, which is a launcher),
|
|
356
|
+
`runtime/` (the C runtime — two translation units and their header),
|
|
357
|
+
`scripts/build.sh` (the link pipeline) and `std/` (the standard library). The
|
|
358
|
+
compiler itself is not in it: it arrives as one prebuilt
|
|
359
|
+
`@amritk/nish-<asset>` package per platform, and `bin/nish` hands over to
|
|
360
|
+
whichever one npm installed. `nish` locates the runtime, the script and `std/`
|
|
361
|
+
relative to its own install directory, so a global install works from any
|
|
362
|
+
working directory — and the binary runs from inside its own platform package
|
|
363
|
+
for exactly that reason.
|
|
364
|
+
|
|
365
|
+
Building the IR yourself rather than through `--link` means naming the runtime
|
|
366
|
+
on the `clang` line, and it is two files:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
clang app.ll runtime/runtime.c runtime/runtime_os.c -lm -o app
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
`runtime.c` is the half every program touches — the arena, strings, arrays,
|
|
373
|
+
number formatting, the panics — and `runtime_os.c` is the half that wraps the
|
|
374
|
+
system calls: files, directories, subprocesses, `getenv`, the monotonic clock.
|
|
375
|
+
They are separate so that each carries its own measured size ceiling
|
|
376
|
+
([docs/wp7-runtime.md](wp7-runtime.md)); nothing in the core calls into the
|
|
377
|
+
system-call half, so an older line that names `runtime.c` alone still links a
|
|
378
|
+
program that reads no files and spawns nothing. `scripts/build.sh` compiles
|
|
379
|
+
`runtime_os.c` beside any `runtime.c` it is handed, so a build that goes
|
|
380
|
+
through it — every `--link`, and every `--profile` recipe in these documents —
|
|
381
|
+
needs to name only the one.
|
|
382
|
+
|
|
383
|
+
## 2a. What `npm run build` does
|
|
384
|
+
|
|
385
|
+
`self/` is the compiler, written in Nish, and it compiles itself
|
|
386
|
+
([docs/wp14-selfhost.md](wp14-selfhost.md)). From a checkout, with clang on
|
|
387
|
+
`PATH`, `npm run build` runs `scripts/bootstrap.sh` with the seed from §2:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
npm run build # build/nish
|
|
391
|
+
NISH_BOOTSTRAP=~/.nish/bin/nish npm run build # the same, seeded by an installed nish
|
|
392
|
+
scripts/bootstrap.sh --verify # and assert the fixed point
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
The seed builds stage1, stage1 builds stage2, and stage2 is installed as
|
|
396
|
+
`build/nish`. `--verify` also builds stage3 and asserts
|
|
397
|
+
`IR(stage1) == IR(stage2)` and `stage3 == stage2` byte for byte; the seed's
|
|
398
|
+
own IR is only reported beside them, because a codegen change since the
|
|
399
|
+
release is expected to move it. `--stages 1` stops one link sooner.
|
|
400
|
+
|
|
401
|
+
`build/nish` is then the compiler you run: it takes the same `-o`, `--link`
|
|
402
|
+
and `--profile` spellings as an installed `nish`, because it is the same
|
|
403
|
+
program, and makes every directory in the way of the IR, a sidecar or the
|
|
404
|
+
binary itself. It looks for `scripts/build.sh` and the two `runtime/*.c` files
|
|
405
|
+
one level up from wherever it was invoked, then in the working directory, so
|
|
406
|
+
it wants a checkout or an installed package around it the way `nish` does.
|
|
407
|
+
|
|
408
|
+
Because the seed is the last release, `self/` may only *use* in its own
|
|
409
|
+
source the constructs that release compiles. A new construct is implemented
|
|
410
|
+
in `self/` and becomes usable inside `self/` from the next release on; CI's
|
|
411
|
+
`bootstrap` job is what checks that the released seed still builds stage1.
|
|
412
|
+
Until R6 the seed was a TypeScript compiler in `src/`, run under Node; it was
|
|
413
|
+
deleted once the native one answered every flag it did
|
|
414
|
+
([wp19](wp19-stage0-retirement.md)).
|
|
415
|
+
|
|
416
|
+
## 3. Hello world
|
|
417
|
+
|
|
418
|
+
Create `hello.ts`:
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
export const main = (): number => {
|
|
422
|
+
console.log("hello from Nish");
|
|
423
|
+
return 0;
|
|
424
|
+
};
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
`export const main` is the process entry; its return value is the exit code
|
|
428
|
+
(`main(): void` exits 0). Compile and link it:
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
nish hello.ts --link hello
|
|
432
|
+
# wrote hello.ll
|
|
433
|
+
# linked hello: 5104 bytes (speed)
|
|
434
|
+
./hello
|
|
435
|
+
# hello from Nish
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
`hello.ll` is the LLVM IR, kept next to the binary. To only get the IR:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
nish hello.ts -o hello.ll
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Other build profiles: `--profile size` (smallest binary), `--profile debug`
|
|
445
|
+
(no optimisation, symbols kept). Run `nish --help` for every flag, and
|
|
446
|
+
see the [README](../README.md) for the language subset.
|
|
447
|
+
|
|
448
|
+
## 4. Exit codes
|
|
449
|
+
|
|
450
|
+
| Code | Meaning |
|
|
451
|
+
| ---: | --- |
|
|
452
|
+
| 0 | success |
|
|
453
|
+
| 1 | the program was rejected: compile error (`file:line:col: error: ...`), missing input file, or an `-o` layout that does not fit the module count |
|
|
454
|
+
| 2 | usage error: unknown flag, missing argument, no input files |
|
|
455
|
+
| 3 | toolchain error: `--link` found no `clang` (`CC` overrides), or `scripts/build.sh` failed (its output is shown; the `.ll` files are still written) — or the `nish` command found no prebuilt compiler for this platform, or one that would not start. Under `--json` all of these are one `NL0002` object |
|
|
456
|
+
| 70 | internal compiler error: an unexpected exception. Please report it at <https://github.com/amritk/nish/issues> with the input and command line; `NISH_DEBUG=1` prints the stack trace |
|
|
457
|
+
|
|
458
|
+
## Troubleshooting
|
|
459
|
+
|
|
460
|
+
- `--link: no usable C compiler found` -- install clang as above, or set
|
|
461
|
+
`CC=/path/to/clang` (for example `CC=clang-18` on Debian).
|
|
462
|
+
- `warning: overriding the module target triple` from clang -- harmless: the
|
|
463
|
+
IR is target-neutral and clang fills in the host triple.
|
|
464
|
+
- `the wasm profile needs wasm-ld` -- install `lld` (`lld-18` on Debian,
|
|
465
|
+
bundled with Homebrew `llvm@18`).
|
|
466
|
+
- `the wasi profile needs a WASI sysroot` / `needs compiler-rt's wasm32
|
|
467
|
+
builtins` -- install them as in the WASI section above, or point
|
|
468
|
+
`WASI_SYSROOT` (a directory) and `WASI_BUILTINS` (the `.a` file) at them.
|
package/llms.txt
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Nish
|
|
2
|
+
|
|
3
|
+
> Nish is an ahead-of-time compiler from a strictly static subset of TypeScript
|
|
4
|
+
> to LLVM IR. Every Nish program is legal TypeScript syntax and type-checks
|
|
5
|
+
> under `tsc --strict`, but the subset is far smaller than TypeScript: no `any`,
|
|
6
|
+
> no `throw`, no callbacks or function values, no inheritance, no truthiness,
|
|
7
|
+
> and no implicit conversion of any kind. Generics are monomorphised: each
|
|
8
|
+
> instantiation is its own function or struct. Failure is a `Result<T, E>` the
|
|
9
|
+
> checker will not let you drop; absence is `T | null` you must narrow. There is
|
|
10
|
+
> no garbage collector — allocation is placed at compile time.
|
|
11
|
+
|
|
12
|
+
If you are **writing a Nish program**, read `docs/AI.md` first. It is the whole
|
|
13
|
+
language as rules, written for one pass, with the TypeScript reflexes that get
|
|
14
|
+
rejected listed before anything else. If you are **working on the compiler**,
|
|
15
|
+
read `AGENTS.md` and `.claude/orientation.md` instead.
|
|
16
|
+
|
|
17
|
+
Verify, never guess: `nish program.ts --json` prints one flat JSON object per
|
|
18
|
+
diagnostic on stdout and is the only authority on whether a program is legal.
|
|
19
|
+
Key on the stable `code` field (`NL2249`), never on the prose in `message`.
|
|
20
|
+
Exit codes: 0 ok, 1 the program was rejected, 2 usage, 3 the C toolchain or the
|
|
21
|
+
prebuilt compiler could not be run (one NL0002 object under --json), 70 a bug in
|
|
22
|
+
the compiler.
|
|
23
|
+
|
|
24
|
+
## Writing Nish
|
|
25
|
+
|
|
26
|
+
- [Rules card](https://raw.githubusercontent.com/amritk/nish/main/docs/AI.md): the whole language as rules, sized for one read — the traps first, then types, `Result`, nullables, declarations, statements, the complete builtin inventory, and recipes for what is missing. Ships in the npm package as `docs/AI.md`.
|
|
27
|
+
- [Language reference](https://raw.githubusercontent.com/amritk/nish/main/docs/LANGUAGE.md): normative and exhaustive. Every rule cites the test that pins it and quotes the exact rejection message. Settles any disagreement with the rules card.
|
|
28
|
+
- [Ambient declarations](https://raw.githubusercontent.com/amritk/nish/main/runtime/nish.d.ts): reference this from `tsconfig.json` and `tsc --strict`, your editor and your language server accept `Result<T, E>`, `i32` and the rest. `nish` is still the authority — `tsc` cannot see the flow rules.
|
|
29
|
+
- [Standard library](https://raw.githubusercontent.com/amritk/nish/main/std/README.md): `std/testing`, a suite a program drives to check itself and answer an exit code; `std/text`, the `split` / `trim` / `replace` the language does not have; `std/json`, the value of one field of one flat JSON object — enough to read this compiler's own `--json` output. Source rather than a built library, imported by relative path, and shipped in the npm package under `std/`.
|
|
30
|
+
- [FAQ](https://raw.githubusercontent.com/amritk/nish/main/docs/FAQ.md): why no `any`, why `i32`, how to get JavaScript numbers, why no GC, overflow and `--wrapping`, what errors look like.
|
|
31
|
+
- [Install](https://raw.githubusercontent.com/amritk/nish/main/docs/INSTALL.md): prerequisites per OS, hello world, exit codes, troubleshooting.
|
|
32
|
+
|
|
33
|
+
## How it compiles
|
|
34
|
+
|
|
35
|
+
- [IR cookbook](https://raw.githubusercontent.com/amritk/nish/main/docs/IR_COOKBOOK.md): for every construct, the smallest snippet and the exact LLVM IR it compiles to today.
|
|
36
|
+
- [Architecture](https://raw.githubusercontent.com/amritk/nish/main/docs/ARCHITECTURE.md): the pipeline, side tables, how to add a construct, the ABI contracts, attribute soundness rules, the test harness.
|
|
37
|
+
- [Benchmarks](https://raw.githubusercontent.com/amritk/nish/main/docs/BENCHMARKS.md): wall time, binary size and peak memory against C and Rust, with the exact build commands.
|
|
38
|
+
|
|
39
|
+
## Working on the compiler
|
|
40
|
+
|
|
41
|
+
- [Agent guide](https://raw.githubusercontent.com/amritk/nish/main/AGENTS.md): the repository's rules for AI agents — the machine-readable surfaces, how to trust a test run, the house rules.
|
|
42
|
+
- [Orientation](https://raw.githubusercontent.com/amritk/nish/main/.claude/orientation.md): the two compilers, the code map, the commands, the seven things that are always true.
|
|
43
|
+
- [Documentation index](https://raw.githubusercontent.com/amritk/nish/main/docs/README.md): every reference and design note, one line each.
|
|
44
|
+
|
|
45
|
+
## Optional
|
|
46
|
+
|
|
47
|
+
- [Master plan](https://raw.githubusercontent.com/amritk/nish/main/docs/MASTER_PLAN.md): vision, design rules, work packages, contributor conventions.
|
|
48
|
+
- [Self-hosting](https://raw.githubusercontent.com/amritk/nish/main/docs/wp14-selfhost.md): the compiler written in its own language, and the oracles that keep the two in step.
|
|
49
|
+
- [Changelog](https://raw.githubusercontent.com/amritk/nish/main/CHANGELOG.md): generated from the commits; what changed in each release.
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amritk/nish",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Nish: an ahead-of-time compiler from a static subset of TypeScript to LLVM IR",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
"./package.json": "./package.json",
|
|
9
|
+
"./runtime/*": "./runtime/*",
|
|
10
|
+
"./scripts/*": "./scripts/*",
|
|
11
|
+
"./*": "./std/*.ts"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"nish": "bin/nish"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"runtime",
|
|
19
|
+
"scripts",
|
|
20
|
+
"!scripts/arrow-verify.mjs",
|
|
21
|
+
"!scripts/arrowify.mjs",
|
|
22
|
+
"!scripts/fetch-seed.sh",
|
|
23
|
+
"!scripts/check-pr-body.mjs",
|
|
24
|
+
"!scripts/check-pr-body.test.mjs",
|
|
25
|
+
"!scripts/changelog-gen.test.mjs",
|
|
26
|
+
"std",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"llms.txt",
|
|
30
|
+
"docs/AI.md",
|
|
31
|
+
"docs/INSTALL.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "bash scripts/bootstrap.sh",
|
|
35
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
36
|
+
"bootstrap": "bash scripts/bootstrap.sh",
|
|
37
|
+
"compile": "build/nish",
|
|
38
|
+
"example": "build/nish examples/add.ts -o build/add.ll && cat build/add.ll",
|
|
39
|
+
"test": "node tests/run.js",
|
|
40
|
+
"size-report": "build/nish examples/add.ts -o build/add.ll && bash scripts/size-report.sh build/add.ll examples/main.c",
|
|
41
|
+
"smoke": "bash scripts/smoke.sh",
|
|
42
|
+
"check": "tsc -p tsconfig.json",
|
|
43
|
+
"test:update": "UPDATE_GOLDENS=1 node tests/run.js",
|
|
44
|
+
"test:diff": "node tests/differential/run.js",
|
|
45
|
+
"test:nish": "build/nish tests/nish/run.ts -o build/nish-runner.ir/ --link build/nish-runner && build/nish-runner",
|
|
46
|
+
"test:cli": "build/nish tests/nish/cli.ts -o build/nish-cli.ir/ --link build/nish-cli && build/nish-cli",
|
|
47
|
+
"test:node": "node tests/differential/unmodified.js",
|
|
48
|
+
"lint": "biome check --formatter-enabled=false .",
|
|
49
|
+
"format": "biome format --write .",
|
|
50
|
+
"prepublishOnly": "npm run check && npm test",
|
|
51
|
+
"changelog": "node scripts/changelog-gen.mjs"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=22.18.0"
|
|
55
|
+
},
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "git+https://github.com/amritk/nish.git"
|
|
59
|
+
},
|
|
60
|
+
"bugs": {
|
|
61
|
+
"url": "https://github.com/amritk/nish/issues"
|
|
62
|
+
},
|
|
63
|
+
"homepage": "https://github.com/amritk/nish#readme",
|
|
64
|
+
"keywords": [
|
|
65
|
+
"typescript",
|
|
66
|
+
"compiler",
|
|
67
|
+
"llvm",
|
|
68
|
+
"llvm-ir",
|
|
69
|
+
"ahead-of-time",
|
|
70
|
+
"aot",
|
|
71
|
+
"native",
|
|
72
|
+
"wasm",
|
|
73
|
+
"webassembly",
|
|
74
|
+
"nish"
|
|
75
|
+
],
|
|
76
|
+
"optionalDependencies": {
|
|
77
|
+
"@amritk/nish-x86_64-linux": "0.10.0",
|
|
78
|
+
"@amritk/nish-aarch64-linux": "0.10.0",
|
|
79
|
+
"@amritk/nish-aarch64-darwin": "0.10.0",
|
|
80
|
+
"@amritk/nish-x86_64-darwin": "0.10.0"
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@biomejs/biome": "2.5.12",
|
|
84
|
+
"@types/node": "^22.0.0",
|
|
85
|
+
"typescript": "^5.6.0"
|
|
86
|
+
}
|
|
87
|
+
}
|