@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/AI.md
ADDED
|
@@ -0,0 +1,1076 @@
|
|
|
1
|
+
# Writing Nish: the rules card
|
|
2
|
+
|
|
3
|
+
This is the **whole language, stated as rules, for a model that has to write
|
|
4
|
+
Nish and get it accepted on the first try**. It is a digest, not the authority:
|
|
5
|
+
[`LANGUAGE.md`](LANGUAGE.md) is normative and settles every disagreement, and
|
|
6
|
+
[`IR_COOKBOOK.md`](IR_COOKBOOK.md) shows the exact IR each construct lowers to.
|
|
7
|
+
This page exists because LANGUAGE.md is 2,300 lines written to be *read*, and
|
|
8
|
+
what an author needs is the rules in one pass with the traps first.
|
|
9
|
+
|
|
10
|
+
> Working **on the compiler** rather than **in the language**? That is a
|
|
11
|
+
> different document: [`AGENTS.md`](../AGENTS.md) and [`.claude/`](../.claude/).
|
|
12
|
+
|
|
13
|
+
Nish is a strictly static subset of TypeScript compiled ahead of time to LLVM
|
|
14
|
+
IR. Every Nish program is legal TypeScript *syntax* — it is parsed by the
|
|
15
|
+
official TypeScript parser — and with
|
|
16
|
+
[`runtime/nish.d.ts`](../runtime/nish.d.ts) on the include path it type-checks
|
|
17
|
+
under `tsc --strict` too. That is the trap this page exists for: **your
|
|
18
|
+
TypeScript instincts produce programs that parse, that `tsc` accepts, and that
|
|
19
|
+
`nish` rejects.** The subset is much smaller than it looks.
|
|
20
|
+
|
|
21
|
+
Contents: [The loop](#the-loop-compile-read-the-code-fix) ·
|
|
22
|
+
[A whole program](#a-whole-program) ·
|
|
23
|
+
[What you must unlearn](#what-you-must-unlearn) · [Types](#types) ·
|
|
24
|
+
[Failure](#failure-result-not-exceptions) · [Absence](#absence-nullable-types) ·
|
|
25
|
+
[Declarations](#declarations) · [Generics](#generic-functions) ·
|
|
26
|
+
[Calling C](#calling-c) · [Statements](#statements) ·
|
|
27
|
+
[Expressions](#expressions) · [The builtins, in full](#the-builtins-in-full) ·
|
|
28
|
+
[Memory](#memory) · [Recipes](#recipes-for-what-is-missing) ·
|
|
29
|
+
[Before you say it compiles](#before-you-say-it-compiles)
|
|
30
|
+
|
|
31
|
+
## The loop: compile, read the code, fix
|
|
32
|
+
|
|
33
|
+
Do not reason about whether a program is legal. Ask the compiler — it is fast,
|
|
34
|
+
and it is the only authority.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
nish program.ts --json # one JSON object per diagnostic, on stdout
|
|
38
|
+
nish program.ts -o out.ll # emit LLVM IR
|
|
39
|
+
nish program.ts --link prog # build a native binary (needs clang)
|
|
40
|
+
nish --help # the full flag list, stdout, exit 0
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`--json` is the surface to automate against. One flat object per line on
|
|
44
|
+
stdout, nothing on stderr, every field 1-based with `endLine`/`endColumn`
|
|
45
|
+
exclusive:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{"file":"p.ts","line":3,"column":17,"endLine":3,"endColumn":20,"severity":"error","code":"NL2249","message":"Unknown method `map` on i32[] (supported: push, pop, indexOf, join)"}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- **Key on `code`, never on `message`.** A code is a promise: `NL2249` means
|
|
52
|
+
the same rule next release. The prose may improve; the code may not.
|
|
53
|
+
- **`severity`** is `"error"` or `"performance"`. A performance warning never
|
|
54
|
+
changes the exit code — it is advice, not a rejection.
|
|
55
|
+
- **Bands**: `NL1xxx` the Phase 0 forbidden-syntax sweep, `NL2xxx` the checker,
|
|
56
|
+
`NL3xxx` the driver and modules, `NL4xxx` the interop sidecars, `NL9xxx`
|
|
57
|
+
performance, `NL0001` syntax, `NL0002` toolchain, `NL0003` internal,
|
|
58
|
+
`NL0000` a diagnostic with no rule yet.
|
|
59
|
+
- **Exit codes**: `0` ok, `1` the program was rejected, `2` usage, `3` the C
|
|
60
|
+
toolchain is unusable, `70` an internal compiler error — that last one is a
|
|
61
|
+
bug in `nish`, not in your program, and is worth reporting.
|
|
62
|
+
- Every failure is a `--json` object, toolchain and internal errors included,
|
|
63
|
+
so you never have to parse stderr to find out why a run failed.
|
|
64
|
+
|
|
65
|
+
Two more surfaces worth knowing: `nish --emit-ast f.ts` prints what was parsed
|
|
66
|
+
and `nish --emit-checked f.ts` prints the side tables the emitter reads. Both
|
|
67
|
+
are one node per line, which is to say diffable.
|
|
68
|
+
|
|
69
|
+
## A whole program
|
|
70
|
+
|
|
71
|
+
```ts nish:ok
|
|
72
|
+
const gcd = (a: i32, b: i32): i32 => {
|
|
73
|
+
let x = a;
|
|
74
|
+
let y = b;
|
|
75
|
+
while (y !== 0) {
|
|
76
|
+
const t = y;
|
|
77
|
+
y = x % y;
|
|
78
|
+
x = t;
|
|
79
|
+
}
|
|
80
|
+
return x;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const main = (): i32 => {
|
|
84
|
+
console.log(`gcd = ${gcd(1071, 462)}`);
|
|
85
|
+
return 0;
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
That is the shape: top-level `const` arrows, an exported `main`, explicit types
|
|
90
|
+
everywhere, a template literal instead of `String(n)`.
|
|
91
|
+
|
|
92
|
+
- A module is one `.ts` file. At the top level it may hold **only**
|
|
93
|
+
declarations: `const` arrows (functions), `class`, `interface`, module
|
|
94
|
+
`const`s, `type` aliases, numeric `enum`s, and `import`s. There is no
|
|
95
|
+
top-level code, so there is no initialisation order to think about.
|
|
96
|
+
- `export const main` lives in the entry module only, takes no parameters, and
|
|
97
|
+
returns `void` or an `i32`-lowered `number` — the process exit code. Under
|
|
98
|
+
`--number-mode f64`, declare it `main(): i32`.
|
|
99
|
+
- `number` is `i32` by default and `f64` under `--number-mode f64`. Write the
|
|
100
|
+
explicit width (`i32`, `f64`) when you mean one; it reads the same in both
|
|
101
|
+
modes.
|
|
102
|
+
|
|
103
|
+
## What you must unlearn
|
|
104
|
+
|
|
105
|
+
Every row is something a TypeScript-trained model writes by reflex and Nish
|
|
106
|
+
rejects. This table is the highest-value part of the page.
|
|
107
|
+
|
|
108
|
+
| Your reflex | What happens | Write instead |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| `if (xs.length)` | `Condition must be boolean … (Nish has no truthiness)` | `if (xs.length !== 0)` |
|
|
111
|
+
| `x == y` | `Loose equality is forbidden` | `x === y` |
|
|
112
|
+
| `throw new Error(m)` | `` `throw` is forbidden `` | `return Err(m)`, or `panic(m)` to end the process |
|
|
113
|
+
| `try { … } catch { … }` | `` `try`/`catch`/`finally` is forbidden `` | `Result<T, E>` and `isErr()` |
|
|
114
|
+
| `xs.map(f)`, `.filter`, `.reduce`, `.forEach`, `.slice`, `.sort`, `.shift` | `` Unknown method `map` on i32[] (supported: push, pop, indexOf, join) `` | a `for` loop; there are no function values to pass |
|
|
115
|
+
| `s.toUpperCase()`, `s.split()`, `s.trim()`, `s.replace()` | `` Unknown method … on string `` | index bytes with `charCodeAt` / `substring` |
|
|
116
|
+
| `a?.b`, `a ?? b` | forbidden | `if (a !== null)` first |
|
|
117
|
+
| `x as T`, `<T>x`, `x!` | `Unsupported expression in Phase 1: AsExpression` | there are no casts; `implements` is the only widening |
|
|
118
|
+
| `any`, `unknown` | forbidden | name the real type |
|
|
119
|
+
| `undefined` | forbidden | `null`, with a `T \| null` type |
|
|
120
|
+
| `let total = 0` at the top level | `` Top-level `let` is not supported `` | a module `const`, or a local |
|
|
121
|
+
| a callback: `xs.forEach(f)`, `(cb: (n: i32) => i32)` | `` Unsupported type `(n: i32) => i32` `` | there are **no function values**; inline the body or write a loop |
|
|
122
|
+
| `type Pair<T>` (a generic alias) | `` expected `=`, found `<` `` (a syntax error) | a generic **function**, **class**, **interface** and **method** all work — see below; an alias renames a type that already exists, so it has nothing to specialise |
|
|
123
|
+
| `constructor<T>(x: T)` | a syntax error: `a constructor cannot have type parameters` | put the parameter on the class (`class Box<T>`), or on a method |
|
|
124
|
+
| `h.get<i32>(7)` (type argument at a method call) | `Type arguments are not written at a call site in Nish` | `h.get(7)` — a generic method infers like a generic function |
|
|
125
|
+
| `<T>(p: T) => p.x` (a member of a type parameter) | `` Cannot read `x` of `T`: an unconstrained type parameter has no members `` | `<T extends Point>(p: T) => p.x`, where `Point` is a class or interface that declares `x` |
|
|
126
|
+
| `identity<i32>(7)` (type argument at a call) | `Type arguments are not written at a call site in Nish` | `identity(7)` — `T` is inferred from the arguments |
|
|
127
|
+
| `async` / `await` / `Promise` | forbidden (no event loop) | the I/O builtins are synchronous |
|
|
128
|
+
| `class B extends A` | `` `extends` is not supported: Nish has no inheritance `` | repeat the fields and `implements` an interface |
|
|
129
|
+
| `static` members, getters/setters | not supported | module `const`s and plain methods |
|
|
130
|
+
| `type Pair = { a: i32 }` (inline object type) | `` Unsupported type `{ a: i32 }` `` | declare an `interface` |
|
|
131
|
+
| `A \| B` unions | `` Union types other than `T \| null` are forbidden `` | one type, or an `interface` prefix |
|
|
132
|
+
| `String(n)`, `n.toString()` | `` Unknown function `String` `` / `` Unknown method `toString` on i32 `` | `` `${n}` `` |
|
|
133
|
+
| `xs.length = 0` | `` Cannot assign to `length` of i32[] (array length is read-only; use `push`) `` | build a new array |
|
|
134
|
+
| `for (const k in o)` | `Unsupported statement in Phase 1: ForInStatement` | `for (const x of xs)` over an array |
|
|
135
|
+
| `import { readFileSync } from "fs"` | `` Cannot find package `fs` `` — a bare specifier is a **package name**, so it is looked for in `node_modules`; one that is installed but has no `nish` condition is `` Package `fs` has no Nish entry point `` | `readFileSync` is a global; no import needed (or `import { readFileSync } from "nish:fs"`) |
|
|
136
|
+
| `export default f` | `` `export default` / `export =` are not supported `` | `export const f = …` |
|
|
137
|
+
| `export type T = …`, `export enum K` | cannot be exported | declare the alias/enum in each module that needs it |
|
|
138
|
+
| `new Date()`, `Date.now()` | `` Unknown builtin `Date.now` `` | `monotonicNanos()` for elapsed time; there is no wall clock and no calendar |
|
|
139
|
+
| `JSON.parse`, `RegExp`, `Map`, `Set`, `Promise` | unknown / forbidden | none of these exist; write them or restructure |
|
|
140
|
+
| `let x = 5; x = "s"` | `Cannot initialize …` | types never change and never convert implicitly |
|
|
141
|
+
|
|
142
|
+
A worked pair. This is the single most common rejection:
|
|
143
|
+
|
|
144
|
+
```ts nish:err-body NL2188
|
|
145
|
+
const xs = [1, 2, 3];
|
|
146
|
+
if (xs.length) {
|
|
147
|
+
console.log("non-empty");
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```ts nish:ok-body
|
|
152
|
+
const xs = [1, 2, 3];
|
|
153
|
+
if (xs.length !== 0) {
|
|
154
|
+
console.log("non-empty");
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Types
|
|
159
|
+
|
|
160
|
+
Every type is exactly one LLVM first-class type. No boxing, no runtime tag, no
|
|
161
|
+
structural subtyping, **no implicit conversion of any kind**: two values are
|
|
162
|
+
compatible only when their types are identical.
|
|
163
|
+
|
|
164
|
+
| Type | Is | Notes |
|
|
165
|
+
| --- | --- | --- |
|
|
166
|
+
| `number` | `i32`, or `f64` under `--number-mode f64` | the mode's default width |
|
|
167
|
+
| `i32`, `i64` | signed integers | signed overflow is undefined; `--wrapping` wraps |
|
|
168
|
+
| `u8`, `u16`, `u32`, `u64` | unsigned integers | arithmetic **always** wraps; `>>` is logical |
|
|
169
|
+
| `f32`, `f64` | IEEE-754 | `f32` is never the lowering of `number` |
|
|
170
|
+
| `boolean` | `i1` | no truthiness anywhere |
|
|
171
|
+
| `string` | immutable UTF-8 bytes | `length` is the **byte** length |
|
|
172
|
+
| `T[]`, `Array<T>` | one element type, bounds-checked | `readonly T[]` refuses every write |
|
|
173
|
+
| `Int32Array`, `Float32Array`, `Float64Array`, `BigInt64Array` | aliases of `i32[]`, `f32[]`, `f64[]`, `i64[]` | not distinct types |
|
|
174
|
+
| `class C`, `interface I` | a struct, fields in declaration order | no header, no vtable |
|
|
175
|
+
| `enum K` | a **distinct** type represented as `i32` | never interchangeable with `i32` |
|
|
176
|
+
| `T \| null` | `T` a class, interface, array, or string | a scalar can never be nullable |
|
|
177
|
+
| `Result<T, E>` | the only way to report failure | `E` may not be `void` |
|
|
178
|
+
| `void` | return type only | |
|
|
179
|
+
|
|
180
|
+
**Mixing widths is an error, always.** `f32 + f64`, `i64 + number`, even
|
|
181
|
+
`u32 + i32` (same LLVM type, different signedness) are rejected. Convert
|
|
182
|
+
explicitly: `toI32` `toI64` `toU8` `toU16` `toU32` `toU64` `toF32` `toF64`.
|
|
183
|
+
|
|
184
|
+
```ts nish:err NL2231
|
|
185
|
+
const bad = (a: i32, b: i64): i64 => {
|
|
186
|
+
return a + b;
|
|
187
|
+
};
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```ts nish:ok
|
|
191
|
+
const good = (a: i32, b: i64): i64 => {
|
|
192
|
+
return toI64(a) + b;
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**`integer` is reserved, not a type.** It is held for ranged integers
|
|
197
|
+
(`integer<Lo, Hi>`), which the compiler does not build yet, so writing it as a
|
|
198
|
+
type is refused. Declaring a type alias, enum, class, interface or function
|
|
199
|
+
named `integer` is refused too. Use `i32`. A value named `integer` (a local
|
|
200
|
+
or a module constant) is fine.
|
|
201
|
+
|
|
202
|
+
```ts nish:err NL2333
|
|
203
|
+
const clamp = (x: integer): i32 => x;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```ts nish:err NL2332
|
|
207
|
+
class integer {
|
|
208
|
+
value: i32 = 0;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Numeric literals take their type from the immediate context
|
|
213
|
+
|
|
214
|
+
A literal is the mode's default (`i32`, or `f64` in f64 mode) **unless its
|
|
215
|
+
immediate context demands another numeric type**, and the contexts that do are
|
|
216
|
+
a closed list: an annotated initializer, a `return`, an argument to a user
|
|
217
|
+
*function* or to a constructor, the other operand of a binary operator whose
|
|
218
|
+
type is already known, `Math.min` / `Math.max`, the f64-only `Math.*` and
|
|
219
|
+
`toF64` / `toF32`, `process.exit`, `Number`, an element of an array literal
|
|
220
|
+
that itself has a `T[]` context, a class field's literal initializer, a field
|
|
221
|
+
or element assignment target, a ternary arm taking the conditional's own
|
|
222
|
+
context, and `push` / `indexOf` through a receiver that is a **plain name**.
|
|
223
|
+
|
|
224
|
+
Everything else leaves the literal at the default, however obvious the intent.
|
|
225
|
+
The three that catch you out: an **object-literal property**, a `Result`
|
|
226
|
+
payload, and a **method's** argument.
|
|
227
|
+
|
|
228
|
+
```ts nish:err NL2269
|
|
229
|
+
interface Pixel { r: u8; g: u8; b: u8; }
|
|
230
|
+
|
|
231
|
+
export const main = (): i32 => {
|
|
232
|
+
const p: Pixel = { r: 255, g: 0, b: 0 };
|
|
233
|
+
console.log(`${p.r}`);
|
|
234
|
+
return 0;
|
|
235
|
+
};
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
```ts nish:ok
|
|
239
|
+
interface Pixel { r: u8; g: u8; b: u8; }
|
|
240
|
+
|
|
241
|
+
export const main = (): i32 => {
|
|
242
|
+
const p: Pixel = { r: toU8(255), g: toU8(0), b: toU8(0) };
|
|
243
|
+
console.log(`${p.r}`);
|
|
244
|
+
return 0;
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The same rule refuses `const b: u8 = 1 + 2` — that is a sum of two `i32`
|
|
249
|
+
literals, not a literal in a `u8` context. Name the value first, or convert.
|
|
250
|
+
|
|
251
|
+
## Failure: `Result`, not exceptions
|
|
252
|
+
|
|
253
|
+
There is no `throw`, no `try`, and no unwinding anywhere in the language. A
|
|
254
|
+
function that can fail says so in its return type.
|
|
255
|
+
|
|
256
|
+
```ts nish:ok
|
|
257
|
+
const half = (n: i32): Result<i32, string> => {
|
|
258
|
+
if (n % 2 !== 0) {
|
|
259
|
+
return Err("odd");
|
|
260
|
+
}
|
|
261
|
+
return Ok(n / 2);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const quarter = (n: i32): Result<i32, string> => {
|
|
265
|
+
const h = half(n).orReturn(); // Rust's `?`: propagates the error
|
|
266
|
+
return half(h);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export const main = (): i32 => {
|
|
270
|
+
const outcome = quarter(8);
|
|
271
|
+
if (outcome.isErr()) {
|
|
272
|
+
console.error(outcome.error);
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
console.log(`${outcome.value}`);
|
|
276
|
+
return 0;
|
|
277
|
+
};
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The surface: `Ok(v)` / `Ok()`, `Err(e)`, `r.isOk()`, `r.isErr()`, `r.ok`,
|
|
281
|
+
`r.value`, `r.error`, `r.orReturn()`, `r.unwrapOr(d)`, `r.expect(msg)`. There
|
|
282
|
+
is deliberately no `unwrap()` — `expect` insists on a reason — and no
|
|
283
|
+
`map`/`andThen`, which would need function values.
|
|
284
|
+
|
|
285
|
+
**Three rules, and they are the ones that catch you out:**
|
|
286
|
+
|
|
287
|
+
1. **A `Result` cannot be dropped.** A call answering one may not stand as an
|
|
288
|
+
expression statement, and a local holding one must be read at least once.
|
|
289
|
+
Passing it on — as an argument, as a `return` — counts as reading it.
|
|
290
|
+
2. **The error arm comes first.** `r.value` is legal only where the checker
|
|
291
|
+
proved `isOk()`; `r.error` only where it proved `isErr()`. There is no
|
|
292
|
+
spelling that reaches the success payload without deciding what happens to
|
|
293
|
+
the failure.
|
|
294
|
+
3. **Propagation is contagious.** `r.orReturn()` is legal only inside a
|
|
295
|
+
function that itself returns a `Result` whose error arm accepts `E`. There
|
|
296
|
+
is no implicit error conversion — convert by hand with
|
|
297
|
+
`if (r.isErr()) { return Err(…); }`.
|
|
298
|
+
|
|
299
|
+
```ts nish:err NL2044
|
|
300
|
+
const half = (n: i32): Result<i32, string> => {
|
|
301
|
+
return Ok(n / 2);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export const main = (): i32 => {
|
|
305
|
+
const r = half(8);
|
|
306
|
+
console.log(`${r.value}`);
|
|
307
|
+
return 0;
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Narrowing follows the same engine as `T | null` below: it applies to a
|
|
312
|
+
**variable**, never a property path; it ends at any assignment to that
|
|
313
|
+
variable; and it is dropped before a loop that assigns it. `if (r.isOk()) A
|
|
314
|
+
else B` narrows in `A`, and after the `if` when `B` cannot fall through.
|
|
315
|
+
|
|
316
|
+
`panic(message)` is the other ending: message to stderr, exit 1. It is for an
|
|
317
|
+
invariant that cannot hold, not for a failure a caller should handle. It
|
|
318
|
+
terminates control flow, so a non-`void` function may end with it.
|
|
319
|
+
|
|
320
|
+
## Absence: nullable types
|
|
321
|
+
|
|
322
|
+
`T | null` is available for `T` a class, interface, array, or string — never a
|
|
323
|
+
scalar, which has no null value. It is the same pointer with `null` as one more
|
|
324
|
+
value, so nothing is boxed.
|
|
325
|
+
|
|
326
|
+
```ts nish:ok
|
|
327
|
+
class Node {
|
|
328
|
+
value: i32;
|
|
329
|
+
next: Node | null = null;
|
|
330
|
+
constructor(value: i32) {
|
|
331
|
+
this.value = value;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const total = (head: Node | null): i32 => {
|
|
336
|
+
let sum = 0;
|
|
337
|
+
let cur = head;
|
|
338
|
+
while (cur !== null) {
|
|
339
|
+
sum = sum + cur.value;
|
|
340
|
+
cur = cur.next;
|
|
341
|
+
}
|
|
342
|
+
return sum;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
export const main = (): i32 => {
|
|
346
|
+
const a = new Node(1);
|
|
347
|
+
a.next = new Node(2);
|
|
348
|
+
console.log(`${total(a)}`);
|
|
349
|
+
return 0;
|
|
350
|
+
};
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
- Reading anything off an un-narrowed nullable is an error: narrow with
|
|
354
|
+
`!== null` first. `?.` and `??` are forbidden outright.
|
|
355
|
+
- Narrowing applies to a **local or parameter**, never a property path. `if
|
|
356
|
+
(n.next !== null) n.next.v` is rejected — copy into a local first.
|
|
357
|
+
- A narrowing ends at any assignment to the variable, and is dropped before a
|
|
358
|
+
loop whose body, condition or update assigns it.
|
|
359
|
+
- Two nullables cannot be compared with each other; compare each with `null`.
|
|
360
|
+
- `new Array<T | null>(n)` is allowed: the zero fill *is* `null`.
|
|
361
|
+
|
|
362
|
+
## Declarations
|
|
363
|
+
|
|
364
|
+
### Functions
|
|
365
|
+
|
|
366
|
+
```ts nish:ok
|
|
367
|
+
const add = (a: i32, b: i32): i32 => {
|
|
368
|
+
return a + b;
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const double = (n: i32): i32 => n * 2; // a concise body is that one `return`
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
- A function is a **module-level `const` bound to an arrow**. `let` is
|
|
375
|
+
rejected; annotating the `const` is rejected (the annotation would be a
|
|
376
|
+
function type, and those are forbidden). The `function` keyword is accepted
|
|
377
|
+
as the legacy spelling and compiles to identical IR.
|
|
378
|
+
- **Every parameter and the return type must be annotated.**
|
|
379
|
+
- **A function is not a value.** `const alias = double` is
|
|
380
|
+
`` Unknown identifier `double` ``. No callbacks, no function types, ever.
|
|
381
|
+
- **Parameters are immutable**: `p = 1`, `p++`, `p += 1` are all rejected. Copy
|
|
382
|
+
into a `let` first.
|
|
383
|
+
- A non-`void` function must return on every path.
|
|
384
|
+
- Rejected: generators, `async`, destructured / rest / optional / default
|
|
385
|
+
parameters, overloads, nested function declarations. Type parameters *are*
|
|
386
|
+
allowed — see [Generic functions](#generic-functions).
|
|
387
|
+
|
|
388
|
+
### Generic functions
|
|
389
|
+
|
|
390
|
+
A function may declare type parameters, and **each instantiation is compiled to
|
|
391
|
+
its own specialised function** — no boxing, no dictionary, no runtime type
|
|
392
|
+
information. The IR is what somebody would have written by hand at that type.
|
|
393
|
+
|
|
394
|
+
```ts nish:ok
|
|
395
|
+
const identity = <T>(x: T): T => x;
|
|
396
|
+
|
|
397
|
+
export const main = (): i32 => {
|
|
398
|
+
console.log(identity(7));
|
|
399
|
+
console.log(identity("hi"));
|
|
400
|
+
return 0;
|
|
401
|
+
};
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
- **The type argument is inferred from the arguments and is never written at a
|
|
405
|
+
call site.** `identity<i32>(7)` is
|
|
406
|
+
`Type arguments are not written at a call site in Nish`. Inference matches the
|
|
407
|
+
shape of each declared parameter against its argument — `T` against `i32`,
|
|
408
|
+
`T[]` against `i32[]`, `Result<T, string>` against `Result<i32, string>` —
|
|
409
|
+
left to right, first binding wins.
|
|
410
|
+
- **A type parameter that appears in no parameter is an error at the
|
|
411
|
+
declaration**, because it could never be inferred.
|
|
412
|
+
- **Every other rule still applies inside.** A type parameter is not a hole to
|
|
413
|
+
smuggle something through: each instantiation is checked with `T` bound to a
|
|
414
|
+
concrete type.
|
|
415
|
+
- **A template may be exported and instantiated from another module.** Each
|
|
416
|
+
instantiation is defined once, in the module that declares the template, and
|
|
417
|
+
`declare`d everywhere else — so one imported name becomes one symbol per
|
|
418
|
+
distinct type-argument tuple, and two modules asking for the same tuple share
|
|
419
|
+
the one definition. A type argument may be a class the declaring module has
|
|
420
|
+
never heard of. A template that is not exported cannot be imported, and an
|
|
421
|
+
imported one may be renamed (`import { identity as id }`) without moving the
|
|
422
|
+
symbol. A type-argument list on an imported name that is *not* a template is
|
|
423
|
+
`` `Point` in `./lib` takes no type arguments ``.
|
|
424
|
+
- **A type parameter has no members unless it is constrained.** Passing a `T`
|
|
425
|
+
on, returning it, storing it and comparing it are fine; `p.x`, `p.x = 1` and
|
|
426
|
+
`p.m()` through a `T` are `` Cannot read `x` of `T`: an unconstrained type
|
|
427
|
+
parameter has no members ``, even when every call passes a type with an `x`.
|
|
428
|
+
It is where the value came from that counts — a local copied from a `T`, an
|
|
429
|
+
element of a `T[]` and a narrowed `T | null` are all a `T` — never its type.
|
|
430
|
+
|
|
431
|
+
```ts nish:err NL2329
|
|
432
|
+
interface Point { x: i32; y: i32; }
|
|
433
|
+
|
|
434
|
+
const getX = <T>(p: T): i32 => p.x;
|
|
435
|
+
|
|
436
|
+
export const main = (): i32 => {
|
|
437
|
+
const p: Point = { x: 1, y: 2 };
|
|
438
|
+
return getX(p);
|
|
439
|
+
};
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
- **`<T extends Shape>` gives `T` exactly `Shape`'s members**: its fields, and
|
|
443
|
+
its methods when `Shape` is a class. A constraint is a declared class or
|
|
444
|
+
interface (or `Container<i32>`, with its arguments written out) — never a
|
|
445
|
+
scalar, `string`, an array or another type parameter. Each instantiation still
|
|
446
|
+
reads its own struct directly, with no vtable.
|
|
447
|
+
- **A type argument must satisfy the constraint**: be `Shape` itself, or a
|
|
448
|
+
class that declares `implements Shape`. Matching field names are not enough.
|
|
449
|
+
A class constraint is satisfied by that class alone. The refusal is at the
|
|
450
|
+
call: `` `T` of `areaOf` requires `T extends Shape`, and `i32` does not
|
|
451
|
+
implement it ``.
|
|
452
|
+
- **A constraint is a declaration, not a name.** A `Shape` your module declares
|
|
453
|
+
is not the `Shape` another module's `<T extends Shape>` names, even with the
|
|
454
|
+
same fields, so implementing your own is refused the same way; import the
|
|
455
|
+
template's `Shape` instead.
|
|
456
|
+
|
|
457
|
+
```ts nish:ok
|
|
458
|
+
interface Shape { area: i32; }
|
|
459
|
+
|
|
460
|
+
class Circle implements Shape {
|
|
461
|
+
area: i32;
|
|
462
|
+
radius: i32;
|
|
463
|
+
constructor(r: i32) { this.area = 3 * r * r; this.radius = r; }
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const areaOf = <T extends Shape>(s: T): i32 => s.area; // s.radius would be refused
|
|
467
|
+
|
|
468
|
+
export const main = (): i32 => areaOf(new Circle(2)) - 12;
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
- **Not supported yet**: a default type argument (`<T = string>`) and type
|
|
472
|
+
parameters on a **constructor**, each with its own message, and a generic
|
|
473
|
+
**type alias**, which is only the syntax error `` expected `=`, found `<` ``.
|
|
474
|
+
A generic `main` is refused. There are no multiple bounds (`T extends A & B`)
|
|
475
|
+
and no bound that mentions another parameter.
|
|
476
|
+
- **`$` may not appear in a function, class or interface name** — it is what
|
|
477
|
+
separates a generic's name from its type arguments in the emitted symbol.
|
|
478
|
+
- **Read `identity<i32>` in a message, `identity$i32` in the IR.** Diagnostics,
|
|
479
|
+
`--emit-checked` and the `-g` debug names spell an instantiation as written
|
|
480
|
+
(`identity<Box<i32>>`, `Box<i32>.get`); symbols and `%struct` names are
|
|
481
|
+
mangled (`@identity$$Box$i32`, `%struct.Box$i32`).
|
|
482
|
+
- **A host calls an exported instantiation as `nish_gen_…`**: `identity<i32>`
|
|
483
|
+
is `nish_gen_identity_i32` in the C header, the `.d.ts`, the wasm loader and
|
|
484
|
+
the N-API addon, and `identity<i32[]>` is `nish_gen_identity_arr_i32`. There
|
|
485
|
+
is no `identity` to call — only the instantiations the program makes.
|
|
486
|
+
- **With a sidecar flag, an exported generic must be instantiated.** Nothing
|
|
487
|
+
instantiated means no symbol, so `--emit-header`, `--emit-dts` and
|
|
488
|
+
`--emit-napi` refuse it (NL4007) rather than leave it out; call it somewhere,
|
|
489
|
+
or do not export it. The same flags refuse two names that are one C
|
|
490
|
+
identifier, such as a method `Point.shifted` and a function `Point_shifted`
|
|
491
|
+
(NL4008).
|
|
492
|
+
|
|
493
|
+
```ts nish:err NL4007 --emit-dts build/docs-ai/nl4007.d.ts
|
|
494
|
+
export const identity = <T>(x: T): T => x; // exported, never called
|
|
495
|
+
|
|
496
|
+
export const main = (): i32 => 0;
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Generic classes and interfaces
|
|
500
|
+
|
|
501
|
+
A `class` or an `interface` may take type parameters too, and an instantiation
|
|
502
|
+
is **an ordinary struct**: `Box<i32>` is `%struct.Box$i32` with `Box`'s fields
|
|
503
|
+
at `T = i32`, and everything the language does to a struct works on it.
|
|
504
|
+
|
|
505
|
+
```ts nish:ok
|
|
506
|
+
class Box<T> {
|
|
507
|
+
value: T;
|
|
508
|
+
constructor(v: T) { this.value = v; }
|
|
509
|
+
get(): T { return this.value; }
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
interface Pair<A, B> { first: A; second: B; }
|
|
513
|
+
|
|
514
|
+
export const main = (): i32 => {
|
|
515
|
+
const b = new Box<i32>(7);
|
|
516
|
+
const p: Pair<i32, string> = { first: 1, second: "hi" };
|
|
517
|
+
console.log(p.second);
|
|
518
|
+
return b.get();
|
|
519
|
+
};
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
- **Write the type arguments out.** `Box` on its own is not a type — there is
|
|
523
|
+
no layout until `T` is bound — so it is `` `Box` is generic: it must be
|
|
524
|
+
written with its type arguments ``. The two positions that take them are an
|
|
525
|
+
annotation (`const b: Box<i32>`) and `new` (`new Box<i32>(7)`); a *call*
|
|
526
|
+
infers instead. A class that is not generic takes none.
|
|
527
|
+
- **A type argument may be anything a type may be**, including another
|
|
528
|
+
instantiation: `Box<Point>`, `Box<Box<i32>>`, `Box<i32[]>`,
|
|
529
|
+
`Box<Result<i32, string>>`.
|
|
530
|
+
- **`implements` may name an instantiation**: `class Box<T> implements
|
|
531
|
+
Container<T>` is checked per instantiation by the usual field-prefix rule.
|
|
532
|
+
- **A field may not grow its own type argument.** `class Nest<T> { inner:
|
|
533
|
+
Nest<T[]> | null }` is `` Monomorphising `Nest` would not terminate ``;
|
|
534
|
+
`Nest<T>` and a type that mentions no parameter are both fine.
|
|
535
|
+
- **A type parameter takes no type arguments of its own.** `T` is whatever the
|
|
536
|
+
instantiation bound it to, so `const y: T<i32>` is
|
|
537
|
+
`` Unsupported type reference `T<i32>` ``.
|
|
538
|
+
- **A template obeys every rule a declared class obeys**: `declare class
|
|
539
|
+
Box<T>`, `export default class Box<T>`, `abstract class Box<T>` and
|
|
540
|
+
`interface Box<T> extends Base` are refused in the words their non-generic
|
|
541
|
+
spellings are refused in.
|
|
542
|
+
- **A template claims its name.** `class Box<T>` is a declaration of `Box`, so
|
|
543
|
+
a function, constant, alias, enum or second class of that name is refused
|
|
544
|
+
whichever was written first: `` `Box` is already declared in this module ``,
|
|
545
|
+
or `` Duplicate declaration of `Box` `` for a second class.
|
|
546
|
+
- **Two modules may not both declare a generic class of one name** once both
|
|
547
|
+
instantiate it: `%struct.Box$i32` is program-wide, so
|
|
548
|
+
`` Generic class `Holder` is also declared in helper.ts ``.
|
|
549
|
+
- **A generic class or interface may be imported**, and the rule is a generic
|
|
550
|
+
function's: `%struct.Box$i32` and every `@Box$i32.*` symbol are defined once,
|
|
551
|
+
by the module that declares `Box<T>`, and `declare`d by every module that
|
|
552
|
+
holds one. Unlike a declared class it may be renamed on import, because the
|
|
553
|
+
name that crosses the ABI is the template's. Named without its type arguments
|
|
554
|
+
it is `` `Crate` is generic: it must be written with its type arguments ``.
|
|
555
|
+
- **A class or interface may constrain its parameters** — `class Holder<T
|
|
556
|
+
extends Shape>` — by the rules a function's follow: its methods may read
|
|
557
|
+
`this.item.area` through a field of type `T`, and `new Holder<Point>` or an
|
|
558
|
+
annotation `Holder<Point>` is refused unless `Point` satisfies `Shape`.
|
|
559
|
+
|
|
560
|
+
### Generic methods
|
|
561
|
+
|
|
562
|
+
A method — of a generic class or not — may declare its own type parameters.
|
|
563
|
+
They are inferred from the arguments like a generic function's, and each
|
|
564
|
+
(receiver, method type arguments) pair is its own `define`:
|
|
565
|
+
`@Chooser.pick$i32`, `@Box$i32.keep$str`.
|
|
566
|
+
|
|
567
|
+
```ts nish:ok
|
|
568
|
+
class Chooser {
|
|
569
|
+
flip: boolean = false;
|
|
570
|
+
pick<T>(a: T, b: T): T { return this.flip ? b : a; }
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
class Box<T> {
|
|
574
|
+
value: T;
|
|
575
|
+
constructor(v: T) { this.value = v; }
|
|
576
|
+
keep<U>(other: U): T { return this.value; }
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export const main = (): i32 => {
|
|
580
|
+
const c = new Chooser();
|
|
581
|
+
const word: string = c.pick("left", "right");
|
|
582
|
+
console.log(word);
|
|
583
|
+
return c.pick(0, 1) + new Box<i32>(7).keep("seven") - 7;
|
|
584
|
+
};
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
- **Never write the type arguments at the call**: `c.pick<i32>(1, 2)` is
|
|
588
|
+
`` Type arguments are not written at a call site ``. A method type parameter
|
|
589
|
+
that no parameter mentions is `` Cannot infer `U` for `Holder.make` ``.
|
|
590
|
+
- **Do not reuse a class parameter's name**: `class Box<T> { map<T>(…) }` is
|
|
591
|
+
`` Type parameter `T` of `Box.map` shadows `Box`'s own `T` `` (NL2331) —
|
|
592
|
+
call the method's `U`.
|
|
593
|
+
- **Constrain it like a function's** — `apply<U extends Shape>(u: U)` reads
|
|
594
|
+
`u.area` — but the constraint may not mention a type parameter, the class's
|
|
595
|
+
included.
|
|
596
|
+
- **A constructor takes none**; its class's are written after `new`.
|
|
597
|
+
|
|
598
|
+
```ts nish:err NL2331
|
|
599
|
+
class Box<T> {
|
|
600
|
+
value: T;
|
|
601
|
+
constructor(v: T) { this.value = v; }
|
|
602
|
+
map<T>(other: T): T { return other; } // shadows Box's T: call it U
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export const main = (): i32 => new Box<i32>(1).value;
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Calling C
|
|
609
|
+
|
|
610
|
+
`declare function name(params): T;` declares a C function this program calls but
|
|
611
|
+
does not define. The symbol is the identifier, unmangled, and the call is an
|
|
612
|
+
ordinary call.
|
|
613
|
+
|
|
614
|
+
```ts nish:ok
|
|
615
|
+
declare function abs(n: i32): i32;
|
|
616
|
+
|
|
617
|
+
export const main = (): i32 => {
|
|
618
|
+
console.log(`${abs(-5)}`);
|
|
619
|
+
return 0;
|
|
620
|
+
};
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
- **Every position must be a scalar** — the integer and float widths, `boolean`,
|
|
624
|
+
`void`. A `string`, array, class or interface is rejected. That is not style:
|
|
625
|
+
a scalar boundary has no pointer for a foreign function to capture or free,
|
|
626
|
+
which is what lets the escape analysis stay correct without knowing anything
|
|
627
|
+
about the callee.
|
|
628
|
+
- **No body, and it cannot be `export`ed.**
|
|
629
|
+
- **It costs the caller its attributes.** Nothing about a body the compiler
|
|
630
|
+
cannot see is provable, so a function that calls C loses `readnone` and
|
|
631
|
+
`willreturn`, and so does everything above it in the call graph. A program
|
|
632
|
+
that calls C is no longer one the compiler can reason about end to end — that
|
|
633
|
+
trade *is* the feature.
|
|
634
|
+
|
|
635
|
+
### Module constants
|
|
636
|
+
|
|
637
|
+
```ts nish:ok
|
|
638
|
+
const TOKEN_NAME: i32 = 1;
|
|
639
|
+
const TOKEN_END: i32 = TOKEN_NAME + 1;
|
|
640
|
+
export const PROMPT: string = "> ";
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
A top-level `const` names a value the compiler already knows. **No symbol is
|
|
644
|
+
emitted and no initialiser runs** — every use lowers to the value itself.
|
|
645
|
+
|
|
646
|
+
- The **type annotation is required**, and so is an initialiser.
|
|
647
|
+
- The type must be `i32`, `i64`, `f64`, `number`, `boolean`, or `string`. An
|
|
648
|
+
array, class or interface constant would need an allocation, and there is no
|
|
649
|
+
code to run it.
|
|
650
|
+
- The initialiser is literals, other constants (declared anywhere, or
|
|
651
|
+
imported), and arithmetic over them. A call, a `new`, a local — anything else
|
|
652
|
+
— is rejected.
|
|
653
|
+
- Folding uses the language's own arithmetic, so an overflowing fold is an
|
|
654
|
+
error rather than a wrap, and `1 / 0` in a constant is refused at compile
|
|
655
|
+
time.
|
|
656
|
+
|
|
657
|
+
### Classes and interfaces
|
|
658
|
+
|
|
659
|
+
```ts nish:ok
|
|
660
|
+
interface Shape { width: i32; height: i32; }
|
|
661
|
+
|
|
662
|
+
class Rect implements Shape {
|
|
663
|
+
width: i32;
|
|
664
|
+
height: i32;
|
|
665
|
+
label: string = "r";
|
|
666
|
+
|
|
667
|
+
constructor(width: i32, height: i32) {
|
|
668
|
+
this.width = width;
|
|
669
|
+
this.height = height;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
area(): i32 {
|
|
673
|
+
return this.width * this.height;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const describe = (s: Shape): i32 => s.width + s.height;
|
|
678
|
+
|
|
679
|
+
export const main = (): i32 => {
|
|
680
|
+
const r = new Rect(3, 4);
|
|
681
|
+
console.log(`${r.area()} ${describe(r)}`);
|
|
682
|
+
return 0;
|
|
683
|
+
};
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
- Fields need annotations; an initializer must be a **literal** of the field's
|
|
687
|
+
type. Layout is declaration order, exactly as clang lays out the same C
|
|
688
|
+
struct.
|
|
689
|
+
- At most one constructor, no parameter properties, no overloads. **Definite
|
|
690
|
+
assignment** is checked syntactically: after the constructor returns every
|
|
691
|
+
field holds a value, and reading `this.f` before every field is assigned is
|
|
692
|
+
an error.
|
|
693
|
+
- Methods need a body and an explicit return type. `readonly` fields may be
|
|
694
|
+
assigned only as `this.f = v` in their own class's constructor.
|
|
695
|
+
- `===` / `!==` on two values of one class is pointer identity; `<` and friends
|
|
696
|
+
are rejected.
|
|
697
|
+
- **No inheritance.** `extends` and `super` are rejected in every spelling.
|
|
698
|
+
- An **interface** is a struct with fields only — no methods, no `extends`, no
|
|
699
|
+
`new`.
|
|
700
|
+
- **`class C implements I` requires `I`'s fields to be `C`'s first fields**, in
|
|
701
|
+
order, with identical types; `C` may declare more after them. A `C` then
|
|
702
|
+
converts to an `I` wherever one is expected. **This is the only widening in
|
|
703
|
+
the language**, and nothing converts back — no downcast, no `instanceof`.
|
|
704
|
+
|
|
705
|
+
### An array of records is one contiguous block
|
|
706
|
+
|
|
707
|
+
`Point[]` where `Point` is a class or interface stores the records themselves,
|
|
708
|
+
back to back — not an array of pointers. So `ps[i]` is an *interior* pointer,
|
|
709
|
+
and `push` may move the whole block to grow it. **Holding an element across a
|
|
710
|
+
`push` on the same array is therefore refused**, because the write would land
|
|
711
|
+
in the old storage:
|
|
712
|
+
|
|
713
|
+
```ts nish:err NL2290
|
|
714
|
+
interface Point { x: i32; }
|
|
715
|
+
|
|
716
|
+
export const main = (): i32 => {
|
|
717
|
+
const ps: Point[] = [{ x: 1 }];
|
|
718
|
+
const p = ps[0];
|
|
719
|
+
ps.push({ x: 2 });
|
|
720
|
+
p.x = 3;
|
|
721
|
+
return 0;
|
|
722
|
+
};
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
Index again after the `push` instead of holding the element across it:
|
|
726
|
+
|
|
727
|
+
```ts nish:ok
|
|
728
|
+
interface Point { x: i32; }
|
|
729
|
+
|
|
730
|
+
export const main = (): i32 => {
|
|
731
|
+
const ps: Point[] = [{ x: 1 }];
|
|
732
|
+
ps.push({ x: 2 });
|
|
733
|
+
ps[0].x = 3;
|
|
734
|
+
console.log(`${ps[0].x}`);
|
|
735
|
+
return 0;
|
|
736
|
+
};
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Object literals
|
|
740
|
+
|
|
741
|
+
An object literal takes its type from context — a variable annotation, the
|
|
742
|
+
enclosing function's return type, the parameter it is passed to, the field it
|
|
743
|
+
is assigned to, or a field of an enclosing literal. It must set every field
|
|
744
|
+
exactly once, with plain identifier keys.
|
|
745
|
+
|
|
746
|
+
```ts nish:ok
|
|
747
|
+
interface Pair { first: i32; second: i32; }
|
|
748
|
+
|
|
749
|
+
const swap = (p: Pair): Pair => ({ first: p.second, second: p.first });
|
|
750
|
+
|
|
751
|
+
export const main = (): i32 => {
|
|
752
|
+
const p: Pair = { first: 1, second: 2 };
|
|
753
|
+
console.log(`${swap(p).first}`);
|
|
754
|
+
return 0;
|
|
755
|
+
};
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
The context reaches a nested literal and a `null`, and stops there: a numeric
|
|
759
|
+
literal in a property keeps the mode's default type (above), and `[]` has no
|
|
760
|
+
element type at all in that position.
|
|
761
|
+
|
|
762
|
+
### Type aliases and enums
|
|
763
|
+
|
|
764
|
+
```ts nish:ok
|
|
765
|
+
type Byte = u8;
|
|
766
|
+
type Bytes = Byte[];
|
|
767
|
+
|
|
768
|
+
enum Kind {
|
|
769
|
+
If = 1,
|
|
770
|
+
While = 2,
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const name = (k: Kind): string => {
|
|
774
|
+
switch (k) {
|
|
775
|
+
case Kind.If:
|
|
776
|
+
return "if";
|
|
777
|
+
default:
|
|
778
|
+
return "while";
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
export const main = (): i32 => {
|
|
783
|
+
console.log(name(Kind.If));
|
|
784
|
+
return 0;
|
|
785
|
+
};
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
- A `type` alias is a second name for an existing type, **not a type of its
|
|
789
|
+
own** — the same program with and without its aliases emits byte-identical
|
|
790
|
+
IR. A generic *alias* is still forbidden even though a generic function,
|
|
791
|
+
class and interface are not; built-in names may not be aliased.
|
|
792
|
+
- A numeric `enum` is a **distinct type** represented as `i32`, and that is the
|
|
793
|
+
point: `Kind` and `i32` never convert in either direction. Members must be
|
|
794
|
+
numeric literals. `===` and `!==` are the **only** operators — no arithmetic,
|
|
795
|
+
no bitwise, so **bit flags stay `i32` module constants**.
|
|
796
|
+
- **Neither can be exported.** Both are module-local; declare them in every
|
|
797
|
+
module that needs them. A class or interface is what crosses a module
|
|
798
|
+
boundary.
|
|
799
|
+
- Both are top-level only.
|
|
800
|
+
|
|
801
|
+
### Modules
|
|
802
|
+
|
|
803
|
+
- `export` goes on `const` (function or constant), `class`, and `interface`
|
|
804
|
+
declarations. No `export default`, no `export { … }`, no `export *`.
|
|
805
|
+
- The only import form is a **named import**:
|
|
806
|
+
`import { square, cube as pow3 } from "./math"`. `.ts` is optional. Default
|
|
807
|
+
imports, namespace imports, side-effect imports and type-only imports are all
|
|
808
|
+
rejected.
|
|
809
|
+
- A specifier is a relative path (`./x`, `../x`), a builtin module (`nish:fs`),
|
|
810
|
+
a standard-library module (`nish/text`, `nish/pair`), or a **package name** (`hash`,
|
|
811
|
+
`@scope/hash`, with a subpath after it if you want one).
|
|
812
|
+
- **A package is resolved through `node_modules` and compiled from source.**
|
|
813
|
+
The package's `package.json` must offer the file under the `nish` export
|
|
814
|
+
condition — `{"exports": {".": {"nish": "./src/index.ts"}}}` — and the mode
|
|
815
|
+
gets a spelling of its own, `nish-i32` / `nish-f64`, for source that is only
|
|
816
|
+
correct under one `--number-mode`. The mode-qualified condition wins wherever
|
|
817
|
+
the package declares it, so a manifest carrying both never compiles the wrong
|
|
818
|
+
one of the two. A package without that condition is
|
|
819
|
+
`` Package `lodash` has no Nish entry point ``, which is what an ordinary npm
|
|
820
|
+
package gets: there is nothing to compile in a `.js` file. One that offers
|
|
821
|
+
only the other mode is `` supports Nish in f64 mode only ``, one whose
|
|
822
|
+
`"engines": {"nish": ">=X.Y.Z"}` is above this compiler `` needs a newer
|
|
823
|
+
compiler ``, and a `package.json` that is not JSON names its line and
|
|
824
|
+
column — each with its own `--json` code (`NL3017`–`NL3021`).
|
|
825
|
+
- Functions may be renamed on import; classes and interfaces may not — the type
|
|
826
|
+
name is part of the ABI.
|
|
827
|
+
- Import cycles are allowed; a shared dependency is compiled once.
|
|
828
|
+
- **A function name is unique across the whole program**, exported or not.
|
|
829
|
+
- An `export`ed function is an external C-ABI symbol; every other function gets
|
|
830
|
+
`internal` linkage so LLVM may inline or drop it.
|
|
831
|
+
|
|
832
|
+
## Statements
|
|
833
|
+
|
|
834
|
+
- **Conditions must be `boolean`.** There is no truthiness, in `if`, `while`,
|
|
835
|
+
`for`, the ternary, `&&` or `||`.
|
|
836
|
+
- `let` / `const` need an initializer and take their type from it; an
|
|
837
|
+
annotation must match **exactly**. `const` freezes the binding, not the
|
|
838
|
+
contents: `xs[0] = 1` and `xs.push(1)` on a `const xs` are fine.
|
|
839
|
+
- `var` is forbidden. Destructuring is not supported.
|
|
840
|
+
- `for (init; cond; update)` with every clause optional, and
|
|
841
|
+
`for (const x of xs)` over an **array** only — `x` gets the element type and
|
|
842
|
+
must not be annotated. The array's `length` is re-read each iteration, so a
|
|
843
|
+
`push` inside the body extends the loop.
|
|
844
|
+
- `switch` takes an **integer or enum** discriminant; every `case` label is an
|
|
845
|
+
integer constant expression of that type. **There is no implicit
|
|
846
|
+
fallthrough** — a clause with statements ends in `break`, `return`,
|
|
847
|
+
`continue` or `process.exit`, unless it is the last. An *empty* clause does
|
|
848
|
+
fall through, which is how `case 1: case 2:` shares a body. A clause cannot
|
|
849
|
+
declare a variable directly; wrap its body in a block.
|
|
850
|
+
- `break` / `continue` are unlabelled only; labeled statements are forbidden.
|
|
851
|
+
- `process.exit(code)` and `panic(msg)` **terminate control flow**, so a
|
|
852
|
+
non-`void` function may end with either.
|
|
853
|
+
- **Unreachable code is an error**, not a warning: anything after a `return`,
|
|
854
|
+
`break`, `continue`, `process.exit`, an `if` whose branches all return, or an
|
|
855
|
+
infinite loop.
|
|
856
|
+
|
|
857
|
+
```ts nish:err NL2166
|
|
858
|
+
const classify = (n: i32): string => {
|
|
859
|
+
switch (n) {
|
|
860
|
+
case 0:
|
|
861
|
+
console.log("zero"); // no `break`: a clause with statements must end in one
|
|
862
|
+
case 1:
|
|
863
|
+
return "one";
|
|
864
|
+
default:
|
|
865
|
+
return "many";
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
export const main = (): i32 => {
|
|
870
|
+
console.log(classify(0));
|
|
871
|
+
return 0;
|
|
872
|
+
};
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
## Expressions
|
|
876
|
+
|
|
877
|
+
- Arithmetic `+ - * / %` takes **two numbers of one type**. `+` on two strings
|
|
878
|
+
concatenates; `string + number` is rejected (use a template literal).
|
|
879
|
+
- **Integer division is checked**, Rust-style: a zero divisor, or `MIN / -1`,
|
|
880
|
+
prints to stderr and exits 1. Float division is never checked.
|
|
881
|
+
- Bitwise `& | ^ ~` and shifts `<< >> >>>` take **integers of one type**.
|
|
882
|
+
`>>` is arithmetic on a signed type and logical on an unsigned one; the shift
|
|
883
|
+
count is masked to the operand width.
|
|
884
|
+
- `=== !==` on any two values of the same type: integers and booleans by value,
|
|
885
|
+
`f64` by `fcmp` (so `x !== x` is true for `NaN`), **strings by content**,
|
|
886
|
+
arrays and objects by **identity**.
|
|
887
|
+
- `< <= > >=` on two numbers of one type and nothing else — not booleans, not
|
|
888
|
+
strings, not structs.
|
|
889
|
+
- `++` / `--` work on **numeric mutable locals only**, not fields or elements.
|
|
890
|
+
- Compound assignment `+= -= *= /= %=` requires a numeric target (so `+=` never
|
|
891
|
+
concatenates strings); the bitwise forms need an integer target.
|
|
892
|
+
- **Forbidden**: `,` `??` `?.` `in` `instanceof` `typeof` `delete` `void expr`
|
|
893
|
+
`==` `!=` `**` unary `+`.
|
|
894
|
+
- Element access `a[i]` needs an array and a numeric index, and is
|
|
895
|
+
bounds-checked (negative indices fail too). String-keyed access is forbidden.
|
|
896
|
+
The check comes off where the compiler proves the index in range — a loop
|
|
897
|
+
condition `i < xs.length`, or `i < h.xs.length` on a field path — as long as
|
|
898
|
+
nothing between the test and the access calls a function, stores to a field
|
|
899
|
+
the path names, or reassigns its root.
|
|
900
|
+
- Array literals need one element type; `[]` needs a contextual `T[]`. Holes
|
|
901
|
+
and spread are not supported.
|
|
902
|
+
- Template literals accept holes of type `string`, `number`, `i64`, `f64` or
|
|
903
|
+
`boolean` — this is how you convert a number to a string.
|
|
904
|
+
|
|
905
|
+
## The builtins, in full
|
|
906
|
+
|
|
907
|
+
No import is needed; these are resolved by name, and a user function of the
|
|
908
|
+
same bare name shadows the builtin. **This list is exhaustive** — anything not
|
|
909
|
+
on it does not exist, and inventing a method is the most common way to write a
|
|
910
|
+
program that does not compile.
|
|
911
|
+
|
|
912
|
+
**Output.** `console.log(x)` and `console.error(x)` take exactly one argument
|
|
913
|
+
of `string | number | i64 | f64 | boolean` and are **statement position only**.
|
|
914
|
+
`write(s)` / `writeError(s)` take a `string` and add no newline.
|
|
915
|
+
`panic(message)` writes to stderr and exits 1.
|
|
916
|
+
|
|
917
|
+
**`Math`.** `sqrt` `floor` `ceil` `trunc` `sin` `cos` `exp` `log` (all `f64`
|
|
918
|
+
argument, `f64` result), `pow(x, y)`, `round(x)`, `abs(x)` (any numeric type),
|
|
919
|
+
`min(a, b)` / `max(a, b)` (exactly two, one type), `random()`, and the
|
|
920
|
+
constants `Math.PI` / `Math.E`. The f64-only ones reject an `i32`: write
|
|
921
|
+
`Math.sqrt(toF64(n))`.
|
|
922
|
+
|
|
923
|
+
**Conversions.** `toI32` `toI64` `toU8` `toU16` `toU32` `toU64` `toF32`
|
|
924
|
+
`toF64`; `f64ToBits(x)` / `bitsToF64(b)` reinterpret rather than convert;
|
|
925
|
+
`Number(x)`, `parseInt(s)` (base 10, `i32`, no `NaN` — no digits give `0`),
|
|
926
|
+
`parseFloat(s)`.
|
|
927
|
+
|
|
928
|
+
**Arrays.** `a.length` (read-only), `a.push(v)`, `a.pop()` (panics when empty —
|
|
929
|
+
there is no `undefined` to return), `a.indexOf(v)`, `a.join(sep)` — **`join` is
|
|
930
|
+
`string[]` only**. That is every array method there is.
|
|
931
|
+
|
|
932
|
+
**Strings.** `s.length` (**bytes**), `s.charCodeAt(i)` (the byte, bounds-checked),
|
|
933
|
+
`s.substring(start[, end])`, `s.slice(start[, end])`, `s.indexOf(sub)`,
|
|
934
|
+
`s.startsWith(sub)`, `s.endsWith(sub)`, and `String.fromCharCode(c)`. That is
|
|
935
|
+
every string method there is. `substring` **clamps** an out-of-range offset the
|
|
936
|
+
way JavaScript does; `slice` instead **panics** unless
|
|
937
|
+
`0 <= start <= end <= s.length`, and is the faster of the two when you have
|
|
938
|
+
already established the range. Every offset is a **byte** offset, so cutting a multi-byte character
|
|
939
|
+
in half is possible.
|
|
940
|
+
|
|
941
|
+
**Process.** `process.exit(code)`, `process.argv` (a read-only `string[]`;
|
|
942
|
+
`argv[0]` is the program path, one index earlier than Node),
|
|
943
|
+
`process.platform`, `process.arch`.
|
|
944
|
+
|
|
945
|
+
**Files and the system.** `readFileSync(path)` (exits 1 on failure),
|
|
946
|
+
`readFileSyncOrNull(path)` (`string | null`), `writeFileSync(path, data)`,
|
|
947
|
+
`appendFileSync(path, data)`, `mkdirSync(path)` (one level, `boolean`),
|
|
948
|
+
`isDirectorySync(path)`, `readdirSync(path)` (`string[] | null`, sorted by
|
|
949
|
+
bytes, no `.`/`..`), `spawnSync(argv)`, `spawnSyncTo(argv, outPath, errPath)`,
|
|
950
|
+
`getenv(name)` (`string | null` — unset and empty are different answers),
|
|
951
|
+
`realpathSync(path)` (`string | null`; symbolic links resolved, absolute, and
|
|
952
|
+
`null` when it does not resolve),
|
|
953
|
+
`monotonicNanos()` (`i64`; elapsed time only, **there is no wall clock and no
|
|
954
|
+
`Date`**).
|
|
955
|
+
|
|
956
|
+
**Arena.** `Arena.mark()`, `Arena.release(m)`, `Arena.reset()`, `Arena.used()`
|
|
957
|
+
— see below.
|
|
958
|
+
|
|
959
|
+
## Memory
|
|
960
|
+
|
|
961
|
+
There is **no garbage collector and no `free`**, and nothing about this changes
|
|
962
|
+
what a program computes — only where memory lives. You do not have to manage
|
|
963
|
+
it, and mostly you should not try:
|
|
964
|
+
|
|
965
|
+
1. **Stack allocation** for a `new`, object literal, array literal or
|
|
966
|
+
`new Array<T>(literal)` whose value provably does not outlive its function.
|
|
967
|
+
2. **Automatic arena scopes** for a function whose temporaries all die with it:
|
|
968
|
+
a mark on entry, a release before every `ret`.
|
|
969
|
+
3. **A reclaim at the call site** for a function that returns a string.
|
|
970
|
+
4. **Explicit control** with the `Arena` builtins, for code that manages
|
|
971
|
+
batches itself.
|
|
972
|
+
|
|
973
|
+
Everything else is bumped from the arena, which is released when `main`
|
|
974
|
+
returns. **Safety rule**: `Arena.release` / `Arena.reset` while any object,
|
|
975
|
+
array or string allocated after the mark is still referenced is undefined
|
|
976
|
+
behaviour. Reach for them only when you are deliberately managing a batch.
|
|
977
|
+
|
|
978
|
+
## Recipes for what is missing
|
|
979
|
+
|
|
980
|
+
**Iterate and transform** — there is no `map`, and no function to pass it:
|
|
981
|
+
|
|
982
|
+
```ts nish:ok-body
|
|
983
|
+
const xs: i32[] = [1, 2, 3];
|
|
984
|
+
const doubled: i32[] = [];
|
|
985
|
+
for (const x of xs) {
|
|
986
|
+
doubled.push(x * 2);
|
|
987
|
+
}
|
|
988
|
+
console.log(`${doubled.length}`);
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
**Convert a number to a string** — there is no `toString` and no `String()`:
|
|
992
|
+
|
|
993
|
+
```ts nish:ok-body
|
|
994
|
+
const n = 42;
|
|
995
|
+
const s = `${n}`;
|
|
996
|
+
console.log(s);
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
**Return two values** — there are no tuples: declare an interface.
|
|
1000
|
+
|
|
1001
|
+
```ts nish:ok
|
|
1002
|
+
interface Split { head: string; rest: string; }
|
|
1003
|
+
|
|
1004
|
+
const cut = (s: string, at: i32): Split => ({
|
|
1005
|
+
head: s.substring(0, at),
|
|
1006
|
+
rest: s.substring(at),
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
export const main = (): i32 => {
|
|
1010
|
+
console.log(cut("hello", 2).rest);
|
|
1011
|
+
return 0;
|
|
1012
|
+
};
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
When the two values need no names of their own, `import { Pair } from "nish/pair"`
|
|
1016
|
+
and return `Pair<A, B>`: an object literal `{ first, second }` at the return,
|
|
1017
|
+
exactly as above. It is for returning two values, not storing them — a struct
|
|
1018
|
+
kept in an array or a field wants named fields.
|
|
1019
|
+
|
|
1020
|
+
**A map or a set** — neither exists. Use parallel arrays with a linear scan, or
|
|
1021
|
+
an array indexed by a small integer key, and write the lookup out.
|
|
1022
|
+
|
|
1023
|
+
**Shared behaviour over several shapes** — there is no inheritance and no
|
|
1024
|
+
dispatch. Give each class the interface's fields **first**, `implements` it,
|
|
1025
|
+
and write a free function over the interface:
|
|
1026
|
+
|
|
1027
|
+
```ts nish:ok
|
|
1028
|
+
interface Sized { width: i32; height: i32; }
|
|
1029
|
+
|
|
1030
|
+
class Photo implements Sized {
|
|
1031
|
+
width: i32;
|
|
1032
|
+
height: i32;
|
|
1033
|
+
path: string = "";
|
|
1034
|
+
constructor(width: i32, height: i32) {
|
|
1035
|
+
this.width = width;
|
|
1036
|
+
this.height = height;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
const area = (s: Sized): i32 => s.width * s.height;
|
|
1041
|
+
|
|
1042
|
+
export const main = (): i32 => {
|
|
1043
|
+
console.log(`${area(new Photo(3, 4))}`);
|
|
1044
|
+
return 0;
|
|
1045
|
+
};
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
**Optional parameters** — there are none. Write two functions with different
|
|
1049
|
+
names, or take the value and document the sentinel.
|
|
1050
|
+
|
|
1051
|
+
**A wall clock, regex, JSON, threads of your own** — not in the language. Say
|
|
1052
|
+
so rather than emitting code that cannot compile.
|
|
1053
|
+
|
|
1054
|
+
## Before you say it compiles
|
|
1055
|
+
|
|
1056
|
+
Run it. `nish file.ts --json` is one command and it is the only proof.
|
|
1057
|
+
|
|
1058
|
+
1. Did you **run the compiler**? Nothing else counts.
|
|
1059
|
+
2. Are all conditions `boolean` — no `if (x)` on a number, string or array?
|
|
1060
|
+
3. Did you invent a method? Check it against
|
|
1061
|
+
[the builtins list](#the-builtins-in-full).
|
|
1062
|
+
4. Is every parameter, return type, field and module constant annotated?
|
|
1063
|
+
5. Does every numeric type match exactly — no `i32` meeting `i64`, no literal
|
|
1064
|
+
in an object-literal property or a method argument expecting a width?
|
|
1065
|
+
6. Is every `Result` read, and is every `.value` behind an `isOk()`?
|
|
1066
|
+
7. Is every nullable narrowed with `!== null` before it is touched?
|
|
1067
|
+
8. Did you use `throw`, `try`, `any`, `undefined`, `?.`, `??`, a cast, a
|
|
1068
|
+
callback, a generic *alias*, or `extends`?
|
|
1069
|
+
9. If you are adding to this repository: `npm run check` and `npm test` green,
|
|
1070
|
+
and a new construct ships a golden `.ll`, an `llvm-as` pass, a native round
|
|
1071
|
+
trip, a negative test, its `LANGUAGE.md` rule and cookbook entry, and a
|
|
1072
|
+
`CHANGELOG.md` line.
|
|
1073
|
+
|
|
1074
|
+
Every `ts nish:ok` and `ts nish:err` block on this page is compiled by
|
|
1075
|
+
`npm test`, so an example that has gone stale is a failing test rather than a
|
|
1076
|
+
program that no longer works.
|