@amritk/nish 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/docs/AI.md +41 -2
- package/docs/INSTALL.md +16 -0
- package/package.json +7 -6
- package/runtime/nish.d.ts +2 -0
- package/runtime/nish.mjs +13 -0
- package/scripts/ci-profile.mjs +3 -2
- package/std/README.md +14 -9
- package/std/collections.ts +39 -2
- package/std/map.ts +37 -0
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ export const main = (): number => {
|
|
|
88
88
|
nish hello.ts --link hello # writes hello.ll, then builds hello with clang -O3 -flto
|
|
89
89
|
./hello # hello from Nish
|
|
90
90
|
nish hello.ts -o hello.ll # IR only
|
|
91
|
+
nish run hello.ts # build into a cache and run it: a script, still native
|
|
91
92
|
```
|
|
92
93
|
|
|
93
94
|
The IR is readable as is. `examples/add.ts` compiles to:
|
|
@@ -275,6 +276,7 @@ buys is the output: no GC, no runtime, and the sizes under
|
|
|
275
276
|
|
|
276
277
|
```
|
|
277
278
|
nish <entry.ts> [more.ts ...] [options]
|
|
279
|
+
nish run [options] <file.ts> [args ...]
|
|
278
280
|
nish --version | --help
|
|
279
281
|
-o, --output <file.ll> output path for a single module (default: <input>.ll)
|
|
280
282
|
-o, --output <dir>/ output directory: one <dir>/<module>.ll per module
|
|
@@ -327,6 +329,22 @@ with `2`. `-g` adds a DWARF line table and variables to the IR so
|
|
|
327
329
|
Multi-file programs: `nish examples/multi/main.ts --link build/multi && ./build/multi; echo $?`
|
|
328
330
|
prints `49`.
|
|
329
331
|
|
|
332
|
+
**`nish run` is the scripting shape.** `nish run tool.ts a b` compiles
|
|
333
|
+
`tool.ts`, links it into a cache, and starts it with `a b` as its arguments.
|
|
334
|
+
Its stdin, stdout and stderr are the program's, and its exit status is the
|
|
335
|
+
program's once it starts (`128 + n` for a signal). The options before the file
|
|
336
|
+
are the compiler's (`--number-mode f64`, `--profile speed`, `-g`), and
|
|
337
|
+
everything after it is the program's, `--help` included. Nothing is
|
|
338
|
+
interpreted: every run compiles, which takes milliseconds, and links only when
|
|
339
|
+
the IR, the recipe or the runtime is new, so the first run of an edit pays for
|
|
340
|
+
one `--profile debug` link (the default here, and the fast one) and every run
|
|
341
|
+
after that starts the cached binary. The cache is `$XDG_CACHE_HOME/nish/run`,
|
|
342
|
+
or `~/.cache/nish/run`. Each entry is one program, so `rm -rf` of it is always
|
|
343
|
+
safe. `-o`, `--link`, `--target`, the `--emit-*` sidecars and
|
|
344
|
+
`--profile wasi` write something a run keeps to itself, so they are usage
|
|
345
|
+
errors there, and performance warnings are not printed, because stderr belongs
|
|
346
|
+
to the program.
|
|
347
|
+
|
|
330
348
|
Without `--link`, build the IR yourself: `clang add.ll examples/main.c runtime/runtime.c runtime/runtime_os.c -o app`
|
|
331
349
|
(the `overriding the module target triple` warning is harmless: the IR is
|
|
332
350
|
target-neutral unless you pass `--target`; `-Wno-override-module` silences
|
package/docs/AI.md
CHANGED
|
@@ -37,6 +37,7 @@ and it is the only authority.
|
|
|
37
37
|
nish program.ts --json # one JSON object per diagnostic, on stdout
|
|
38
38
|
nish program.ts -o out.ll # emit LLVM IR
|
|
39
39
|
nish program.ts --link prog # build a native binary (needs clang)
|
|
40
|
+
nish run program.ts a b # build into a cache, then run it with `a b`
|
|
40
41
|
nish --help # the full flag list, stdout, exit 0
|
|
41
42
|
```
|
|
42
43
|
|
|
@@ -58,7 +59,9 @@ exclusive:
|
|
|
58
59
|
`NL0000` a diagnostic with no rule yet.
|
|
59
60
|
- **Exit codes**: `0` ok, `1` the program was rejected, `2` usage, `3` the C
|
|
60
61
|
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
|
+
bug in `nish`, not in your program, and is worth reporting. `nish run`
|
|
63
|
+
answers the same codes until the program starts, and the program's own
|
|
64
|
+
status after that.
|
|
62
65
|
- Every failure is a `--json` object, toolchain and internal errors included,
|
|
63
66
|
so you never have to parse stderr to find out why a run failed.
|
|
64
67
|
|
|
@@ -675,7 +678,7 @@ export const main = (): i32 => {
|
|
|
675
678
|
threads as the array is long enough for. `f` is a [function parameter](#function-parameters).
|
|
676
679
|
Importing the module compiles the program with `--threads`; there is nothing
|
|
677
680
|
else to switch on and no thread count to choose. (A plain block: a program
|
|
678
|
-
importing
|
|
681
|
+
importing `nish/threads` writes two `.ll` files, so this one is compiled by
|
|
679
682
|
`tests/link/par_map` and `tests/link/par_reduce` instead.)
|
|
680
683
|
|
|
681
684
|
```ts
|
|
@@ -952,6 +955,11 @@ export const main = (): i32 => {
|
|
|
952
955
|
annotation must match **exactly**. `const` freezes the binding, not the
|
|
953
956
|
contents: `xs[0] = 1` and `xs.push(1)` on a `const xs` are fine.
|
|
954
957
|
- `var` is forbidden. Destructuring is not supported.
|
|
958
|
+
- **Semicolons are optional**, where TypeScript would insert one: at a line
|
|
959
|
+
break, before `}` and at the end of the file. It is TypeScript's rule, so a
|
|
960
|
+
line starting with `(` or `[` continues the one before it, and `return`
|
|
961
|
+
followed by a line break returns nothing. Here, the value left on the next
|
|
962
|
+
line is an unreachable-code error rather than a silent bug.
|
|
955
963
|
- `for (init; cond; update)` with every clause optional, and
|
|
956
964
|
`for (const x of xs)` over an **array** only — `x` gets the element type and
|
|
957
965
|
must not be annotated. The array's `length` is re-read each iteration, so a
|
|
@@ -1159,6 +1167,37 @@ for (const e of m) {
|
|
|
1159
1167
|
}
|
|
1160
1168
|
```
|
|
1161
1169
|
|
|
1170
|
+
Asking about one key twice costs one probe when the second call writes
|
|
1171
|
+
through the first: `m.set(k, E)` whose `E` reads `m.get(k)` or `m.has(k)`
|
|
1172
|
+
once, and `if (m.has(k)) { m.set(k, E); … }`, `if (!m.has(k)) { m.set(k, E); … }`
|
|
1173
|
+
or `if (!s.has(x)) { s.add(x); … }` with the write first in the branch. The
|
|
1174
|
+
receiver and key must be a local, a parameter or a `this.<field>` path (the key
|
|
1175
|
+
may be a literal), spelled the same in both calls, and nothing in between may
|
|
1176
|
+
call anything, allocate (`new`, a literal array or object, a template, a
|
|
1177
|
+
string `+`) or assign. Anything else still compiles, as one probe per call.
|
|
1178
|
+
`nish/map` adds `getOrInsert(m, k, v)` — the value of `k`, or `v` after
|
|
1179
|
+
inserting it — and `reserve(m, n)`, which sizes the table for `n` entries; both
|
|
1180
|
+
are one module with the program and run unchanged under Node.
|
|
1181
|
+
|
|
1182
|
+
```ts nish:ok
|
|
1183
|
+
import { getOrInsert, reserve } from "nish/map";
|
|
1184
|
+
|
|
1185
|
+
export const main = (): i32 => {
|
|
1186
|
+
const words = ["to", "be", "or", "not", "to", "be"];
|
|
1187
|
+
const counts = new Map<string, i32>();
|
|
1188
|
+
reserve(counts, words.length);
|
|
1189
|
+
for (const w of words) {
|
|
1190
|
+
counts.set(w, (counts.get(w) ?? 0) + 1); // one probe per word
|
|
1191
|
+
}
|
|
1192
|
+
const first = new Map<string, i32>();
|
|
1193
|
+
for (let i = 0; i < words.length; i++) {
|
|
1194
|
+
getOrInsert(first, words[i], i); // one probe; inserts only the first time
|
|
1195
|
+
}
|
|
1196
|
+
console.log(`${counts.get("to") ?? 0} ${first.get("be") ?? -1}`); // 2 1
|
|
1197
|
+
return 0;
|
|
1198
|
+
};
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1162
1201
|
## Memory
|
|
1163
1202
|
|
|
1164
1203
|
There is **no garbage collector and no `free`**, and nothing about this changes
|
package/docs/INSTALL.md
CHANGED
|
@@ -446,6 +446,21 @@ Other build profiles: `--profile size` (smallest binary), `--profile debug`
|
|
|
446
446
|
(no optimisation, symbols kept). Run `nish --help` for every flag, and
|
|
447
447
|
see the [README](../README.md) for the language subset.
|
|
448
448
|
|
|
449
|
+
To use a program as a script rather than keep the binary, run it:
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
nish run hello.ts
|
|
453
|
+
# hello from Nish
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
`nish run [options] <file.ts> [args ...]` compiles the file, links it into
|
|
457
|
+
`$XDG_CACHE_HOME/nish/run` (or `~/.cache/nish/run`) when that program has not
|
|
458
|
+
been linked before, and starts it with the arguments after the file. The first
|
|
459
|
+
run of a new edit pays for one link, and later runs start the cached binary.
|
|
460
|
+
It needs clang only for that link. It needs `HOME` or `XDG_CACHE_HOME` set,
|
|
461
|
+
and refuses the run without them rather than keep a binary in a shared
|
|
462
|
+
directory.
|
|
463
|
+
|
|
449
464
|
## 4. Exit codes
|
|
450
465
|
|
|
451
466
|
| Code | Meaning |
|
|
@@ -454,6 +469,7 @@ see the [README](../README.md) for the language subset.
|
|
|
454
469
|
| 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 |
|
|
455
470
|
| 2 | usage error: unknown flag, missing argument, no input files |
|
|
456
471
|
| 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 |
|
|
472
|
+
| | `nish run` answers these codes until the program starts, and the program's own exit status (`128 + n` for a signal) after that. With neither `HOME` nor `XDG_CACHE_HOME` set it answers 3 |
|
|
457
473
|
| 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 |
|
|
458
474
|
|
|
459
475
|
## Troubleshooting
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amritk/nish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Nish: an ahead-of-time compiler from a static subset of TypeScript to LLVM IR",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"!scripts/arrow-verify.mjs",
|
|
21
21
|
"!scripts/arrowify.mjs",
|
|
22
22
|
"!scripts/fetch-seed.sh",
|
|
23
|
+
"!scripts/check-filenames.mjs",
|
|
23
24
|
"!scripts/check-pr-body.mjs",
|
|
24
25
|
"!scripts/check-pr-body.test.mjs",
|
|
25
26
|
"!scripts/changelog-gen.test.mjs",
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"test:nish": "build/nish tests/nish/run.ts -o build/nish-runner.ir/ --link build/nish-runner && build/nish-runner",
|
|
46
47
|
"test:cli": "build/nish tests/nish/cli.ts -o build/nish-cli.ir/ --link build/nish-cli && build/nish-cli",
|
|
47
48
|
"test:node": "node tests/differential/unmodified.js",
|
|
48
|
-
"lint": "biome check --formatter-enabled=false .",
|
|
49
|
+
"lint": "node scripts/check-filenames.mjs --advisory && biome check --formatter-enabled=false .",
|
|
49
50
|
"format": "biome format --write .",
|
|
50
51
|
"prepublishOnly": "npm run check && npm test",
|
|
51
52
|
"changelog": "node scripts/changelog-gen.mjs"
|
|
@@ -74,10 +75,10 @@
|
|
|
74
75
|
"nish"
|
|
75
76
|
],
|
|
76
77
|
"optionalDependencies": {
|
|
77
|
-
"@amritk/nish-x86_64-linux": "0.
|
|
78
|
-
"@amritk/nish-aarch64-linux": "0.
|
|
79
|
-
"@amritk/nish-aarch64-darwin": "0.
|
|
80
|
-
"@amritk/nish-x86_64-darwin": "0.
|
|
78
|
+
"@amritk/nish-x86_64-linux": "0.12.0",
|
|
79
|
+
"@amritk/nish-aarch64-linux": "0.12.0",
|
|
80
|
+
"@amritk/nish-aarch64-darwin": "0.12.0",
|
|
81
|
+
"@amritk/nish-x86_64-darwin": "0.12.0"
|
|
81
82
|
},
|
|
82
83
|
"devDependencies": {
|
|
83
84
|
"@biomejs/biome": "2.5.12",
|
package/runtime/nish.d.ts
CHANGED
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
* only as the iterable of a `for...of`, and `get`'s `V | undefined` only as a
|
|
35
35
|
* `const`'s initialiser, the left of `??` or an operand of `=== undefined`,
|
|
36
36
|
* none of which `tsc` can know (docs/LANGUAGE.md -> `Map` and `Set`).
|
|
37
|
+
* `nish/map`'s `reserve` and `getOrInsert` are not declared here either: they
|
|
38
|
+
* are ordinary source, `std/map.ts`, which `tsconfig.json` maps `nish/*` to.
|
|
37
39
|
*/
|
|
38
40
|
|
|
39
41
|
// ---- Numeric widths (docs/LANGUAGE.md -> Types) ------------------------------
|
package/runtime/nish.mjs
CHANGED
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
* harness already uses, so the two cannot drift apart: a semantic fixed there
|
|
55
55
|
* is fixed here in the same commit.
|
|
56
56
|
*/
|
|
57
|
+
import { registerHooks } from "node:module";
|
|
57
58
|
import * as shim from "./shim.mjs";
|
|
58
59
|
|
|
59
60
|
/** Install `value` as a global unless the program declared its own. */
|
|
@@ -141,3 +142,15 @@ provide("Arena", {
|
|
|
141
142
|
reset: shim.arenaReset,
|
|
142
143
|
used: shim.arenaUsed,
|
|
143
144
|
});
|
|
145
|
+
|
|
146
|
+
// The standard library. A program imports it as `nish/<module>`, which the
|
|
147
|
+
// compiler resolves to `std/<module>.ts` beside itself; this does the same for
|
|
148
|
+
// Node, relative to this file rather than to the program, so the same
|
|
149
|
+
// specifier works from any directory. Only the `nish/` package is answered
|
|
150
|
+
// here, and every other specifier goes to Node as it was written.
|
|
151
|
+
registerHooks({
|
|
152
|
+
resolve: (specifier, context, next) =>
|
|
153
|
+
specifier.startsWith("nish/")
|
|
154
|
+
? next(new URL(`../std/${specifier.slice("nish/".length)}.ts`, import.meta.url).href, context)
|
|
155
|
+
: next(specifier, context),
|
|
156
|
+
});
|
package/scripts/ci-profile.mjs
CHANGED
|
@@ -67,10 +67,11 @@ const takeLine = (line) => {
|
|
|
67
67
|
|
|
68
68
|
const onData = (data) => {
|
|
69
69
|
buffered += data;
|
|
70
|
-
let i;
|
|
71
|
-
while (
|
|
70
|
+
let i = buffered.indexOf("\n");
|
|
71
|
+
while (i >= 0) {
|
|
72
72
|
takeLine(buffered.slice(0, i));
|
|
73
73
|
buffered = buffered.slice(i + 1);
|
|
74
|
+
i = buffered.indexOf("\n");
|
|
74
75
|
}
|
|
75
76
|
};
|
|
76
77
|
|
package/std/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# `std/` — the standard library
|
|
2
2
|
|
|
3
3
|
Nish modules written in Nish, for Nish programs to import. There is no magic
|
|
4
|
-
here and — with
|
|
5
|
-
the compiler knows about: a module in this directory is an ordinary Nish source file, compiled as part of
|
|
4
|
+
here and — with three exceptions, `threads.ts`, `collections.ts` and `map.ts` —
|
|
5
|
+
nothing the compiler knows about: a module in this directory is an ordinary Nish source file, compiled as part of
|
|
6
6
|
whatever program imports it, and subject to the same rules as `examples/` or
|
|
7
7
|
`self/` ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md) is the style guide).
|
|
8
8
|
|
|
@@ -12,7 +12,8 @@ whatever program imports it, and subject to the same rules as `examples/` or
|
|
|
12
12
|
| [`text.ts`](./text.ts) | the string operations a program would otherwise write inline: `splitLines`, `splitWhitespace`, `trim` and its halves, `contains`, `replaceAll`, and `firstDifference` over two arrays of lines |
|
|
13
13
|
| [`json.ts`](./json.ts) | `jsonField(object, name)`: the value of one field of one flat JSON object, which is the shape the compiler's own `--json` diagnostics have. A reader and not a parser — it answers text, answers `null` for a field that is not there, and does not validate |
|
|
14
14
|
| [`pair.ts`](./pair.ts) | `Pair<A, B>`: an interface with `first` and `second`, for a function that answers two values from one call. A type and nothing else — the caller writes an object literal at the return — and for returning two values rather than storing them side by side |
|
|
15
|
-
| [`collections.ts`](./collections.ts) | the global `Map<K, V>` and `Set<T>`: insertion-ordered tables whose buckets carry a hash fingerprint beside the entry index and whose entries keep their full hash, so every `get`, `set`, `add`, `has` and `delete` is one probe. `get` is not a method here: its `V | undefined` never crosses a call, so the compiler lowers it to `probe` and, where the key was found, `valueAt`. Nor are `keys()` and `values()`: an iterator is not a value, so a `for...of` over one is lowered to `walkOpen`, `walkNext`, `keyAt` or `valueAt`, and `walkClose`, and a count of live walks defers compaction until no loop is walking the table. A program never imports it: naming `Map` or `Set` loads it, and the compiler emits what a module uses of it into that module ([`docs/wp32-map.md`](../docs/wp32-map.md), [`docs/LANGUAGE.md`](../docs/LANGUAGE.md#map-and-set)). Its `hashKey` and `
|
|
15
|
+
| [`collections.ts`](./collections.ts) | the global `Map<K, V>` and `Set<T>`: insertion-ordered tables whose buckets carry a hash fingerprint beside the entry index and whose entries keep their full hash, so every `get`, `set`, `add`, `has` and `delete` is one probe. `get` is not a method here: its `V | undefined` never crosses a call, so the compiler lowers it to `probe` and, where the key was found, `valueAt`. Nor are `keys()` and `values()`: an iterator is not a value, so a `for...of` over one is lowered to `walkOpen`, `walkNext`, `keyAt` or `valueAt`, and `walkClose`, and a count of live walks defers compaction until no loop is walking the table. A program never imports it: naming `Map` or `Set` loads it, and the compiler emits what a module uses of it into that module ([`docs/wp32-map.md`](../docs/wp32-map.md), [`docs/LANGUAGE.md`](../docs/LANGUAGE.md#map-and-set)). Its `hashKey`, `sameKey` and `storedKey` are lowered by the compiler per key type |
|
|
16
|
+
| [`map.ts`](./map.ts) | `reserve(m, n)` and `getOrInsert(m, k, v)` for the global `Map`. Their bodies are the meaning, and what runs under Node: `reserve` does nothing, and `getOrInsert` is a `get`, and a `set` of `v` when the key was missing. Natively the compiler lowers every call in place — `reserve` to the table's `reserveSlots`, which grows the buckets once so that `n` entries fit without a rebuild, and `getOrInsert` to one `probe` and a `valueAt` or an `insertAt` through its answer — so, like `collections.ts`, it writes no `.ll` of its own ([`docs/wp32-map.md`](../docs/wp32-map.md) §9.2, [`docs/LANGUAGE.md`](../docs/LANGUAGE.md#map-and-set)) |
|
|
16
17
|
| [`threads.ts`](./threads.ts) | `parallelMapInto(src, dst, f)` and `parallelReduce(src, f, identity)`: a function over every element of an array, on as many threads as the length is worth. Its bodies are the sequential meaning, which is what runs under Node; the compiler recognises the two templates by module and name, lowers the one loop in each onto `nish_parallel_range`, holds the function to the rules that make that safe, and compiles an importing program with `--threads` ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#data-parallelism-nishthreads)). `tests/link/par_*` are its programs |
|
|
17
18
|
|
|
18
19
|
## How a program imports it
|
|
@@ -64,12 +65,16 @@ reasons that are only true of it:
|
|
|
64
65
|
package and is versioned with it, so "the `std/` beside this binary" is not a
|
|
65
66
|
guess a resolver makes — it is the only `std/` that can be correct for the
|
|
66
67
|
compiler reading it. No version can be skewed against it.
|
|
67
|
-
- It **is** what Node
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
`
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
- It **is** what Node and `tsc` resolve. The package is published as
|
|
69
|
+
`@amritk/nish`, so a bare `nish/text` is not a package self-reference on its
|
|
70
|
+
own; `runtime/nish.mjs`, the prelude a program runs under Node with
|
|
71
|
+
(`node --experimental-strip-types --import ./runtime/nish.mjs`), resolves
|
|
72
|
+
`nish/<module>` to `std/<module>.ts` beside itself, and the repository's
|
|
73
|
+
`tsconfig.json` maps `nish/*` to `./std/*`, which is what gives `tsc` and an
|
|
74
|
+
editor go-to-definition into the real source. `package.json` still declares
|
|
75
|
+
`"./*": "./std/*.ts"` in `exports`, so `@amritk/nish/text` reaches the same
|
|
76
|
+
file. The compiler short-circuits to that answer rather than walking
|
|
77
|
+
`node_modules` to reach it.
|
|
73
78
|
|
|
74
79
|
So this is WP21's first slice rather than a detour around it: the spelling is
|
|
75
80
|
the one WP21 specifies, and what is still missing is resolution for specifiers
|
package/std/collections.ts
CHANGED
|
@@ -394,7 +394,7 @@ export class Map<K, V> {
|
|
|
394
394
|
this.rebuild();
|
|
395
395
|
bucket = -1;
|
|
396
396
|
}
|
|
397
|
-
this.entryKeys.push(key);
|
|
397
|
+
this.entryKeys.push(storedKey(key));
|
|
398
398
|
this.entryValues.push(value);
|
|
399
399
|
this.entryHashes.push(h);
|
|
400
400
|
this.live = this.live + 1;
|
|
@@ -427,6 +427,33 @@ export class Map<K, V> {
|
|
|
427
427
|
this.mask = toI32(slots.length) - 1;
|
|
428
428
|
refile(slots, this.entryHashes);
|
|
429
429
|
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* `reserve(m, n)` from `nish/map` (docs/wp32-map.md §9.2): grow the bucket
|
|
433
|
+
* table until `n` entries, dead ones included, fit under the load bound, so
|
|
434
|
+
* that the inserts up to `n` rebuild nothing. It only ever grows the buckets
|
|
435
|
+
* and re-files them from the stored hashes, so no entry moves and it is as
|
|
436
|
+
* safe under a walk as the doubling a walk already allows (§6.2). A count
|
|
437
|
+
* that is not positive, or not a number, does nothing, as `reserve` does
|
|
438
|
+
* under Node; one past the entry cap is the cap.
|
|
439
|
+
*/
|
|
440
|
+
reserveSlots(n: number): void {
|
|
441
|
+
if (!(n > 0)) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const want: i32 = n >= 16777215 ? INDEX_CAP : toI32(n);
|
|
445
|
+
const have = toI32(this.slots.length);
|
|
446
|
+
let size = have;
|
|
447
|
+
while (want * 4 > size * 3) {
|
|
448
|
+
size = size * 2;
|
|
449
|
+
}
|
|
450
|
+
if (size > have) {
|
|
451
|
+
const slots = new Array<u32>(size);
|
|
452
|
+
this.slots = slots;
|
|
453
|
+
this.mask = size - 1;
|
|
454
|
+
refile(slots, this.entryHashes);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
430
457
|
}
|
|
431
458
|
|
|
432
459
|
/** A set of keys in insertion order: `Map`'s table with no values, and the same probe. */
|
|
@@ -519,7 +546,7 @@ export class Set<T> {
|
|
|
519
546
|
this.rebuild();
|
|
520
547
|
bucket = -1;
|
|
521
548
|
}
|
|
522
|
-
this.entryKeys.push(key);
|
|
549
|
+
this.entryKeys.push(storedKey(key));
|
|
523
550
|
this.entryHashes.push(h);
|
|
524
551
|
this.live = this.live + 1;
|
|
525
552
|
this.size = this.size + 1;
|
|
@@ -546,3 +573,13 @@ export class Set<T> {
|
|
|
546
573
|
refile(slots, this.entryHashes);
|
|
547
574
|
}
|
|
548
575
|
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* The key as an entry stores it: a float's -0 becomes +0, as ECMA-262's
|
|
579
|
+
* `Map.prototype.set` and `Set.prototype.add` store it, so a walk over the
|
|
580
|
+
* keys yields +0 whichever zero was inserted. Lowered in place, like `hashKey`
|
|
581
|
+
* and `sameKey`: `fadd` of +0 for a float, and nothing at all for every other
|
|
582
|
+
* key. It sits last in the file so that adding it moved no line a `-g` build
|
|
583
|
+
* of an earlier function records.
|
|
584
|
+
*/
|
|
585
|
+
const storedKey = <K>(key: K): K => key;
|
package/std/map.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/map` — two operations on the global `Map` that JavaScript's does not
|
|
3
|
+
* have, written so that a program using them still runs unmodified under Node
|
|
4
|
+
* (docs/wp32-map.md §9.2).
|
|
5
|
+
*
|
|
6
|
+
* The bodies below are the meaning, and they are what runs under Node:
|
|
7
|
+
* `reserve` does nothing, and `getOrInsert` is a `get`, and a `set` when the
|
|
8
|
+
* key was missing. Natively neither body is ever called. The compiler lowers
|
|
9
|
+
* every call in place, as it lowers `hashKey` and `sameKey`: `reserve` is the
|
|
10
|
+
* table's `reserveSlots`, which grows the buckets so that `n` entries fit
|
|
11
|
+
* without a rebuild, and `getOrInsert` is one `probe` of the key, then the
|
|
12
|
+
* value it found or an insert through the empty bucket the probe stopped at,
|
|
13
|
+
* with the hash it already computed. So this module writes no `.ll` of its
|
|
14
|
+
* own, and a one-file program that imports it is still one module.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Make room in `m` for `n` entries in all, so that inserting up to `n` never
|
|
19
|
+
* grows the table. Only the speed of a program depends on it: natively it
|
|
20
|
+
* presizes the bucket table, and under Node, which has no such call, it does
|
|
21
|
+
* nothing. A count that is not positive does nothing either way.
|
|
22
|
+
*/
|
|
23
|
+
export const reserve = <K, V>(m: Map<K, V>, n: number): void => {};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The value of `key` in `m`; or, when `key` is missing, `value`, after
|
|
27
|
+
* setting `key` to it. One hash and one probe natively, whichever it was.
|
|
28
|
+
* `value` is evaluated either way, as every argument is.
|
|
29
|
+
*/
|
|
30
|
+
export const getOrInsert = <K, V>(m: Map<K, V>, key: K, value: V): V => {
|
|
31
|
+
const found = m.get(key);
|
|
32
|
+
if (found !== undefined) {
|
|
33
|
+
return found;
|
|
34
|
+
}
|
|
35
|
+
m.set(key, value);
|
|
36
|
+
return value;
|
|
37
|
+
};
|