xbintsc 0.3.46 → 0.3.49
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/AGENTS.md +95 -0
- package/README.md +25 -0
- package/README.zh-CN.md +23 -0
- package/dist/src/cli/hints.d.ts +54 -0
- package/dist/src/cli/hints.js +165 -0
- package/dist/src/cli/hints.js.map +1 -0
- package/dist/src/cli/main.js +73 -9
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +26 -0
- package/dist/src/codegen/generator/tables.js +64 -12
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/diagnostics/source-text.d.ts +22 -0
- package/dist/src/diagnostics/source-text.js +76 -0
- package/dist/src/diagnostics/source-text.js.map +1 -0
- package/dist/src/driver/bundler/graph.js +2 -1
- package/dist/src/driver/bundler/graph.js.map +1 -1
- package/dist/src/driver/compiler.js +3 -2
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/lexer/scanner/strings.js +16 -3
- package/dist/src/lexer/scanner/strings.js.map +1 -1
- package/dist/tests/cli/hints.test.d.ts +9 -0
- package/dist/tests/cli/hints.test.js +143 -0
- package/dist/tests/cli/hints.test.js.map +1 -0
- package/dist/tests/cli/main.test.js +6 -4
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +17 -2
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/helpers.js +3 -2
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/lexer/strings.test.js +14 -2
- package/dist/tests/lexer/strings.test.js.map +1 -1
- package/doc/DESIGN.md +117 -0
- package/doc/ai/README.md +63 -0
- package/doc/ai/build-recipe.md +137 -0
- package/doc/ai/cli.md +142 -0
- package/doc/ai/contributing.md +196 -0
- package/doc/ai/extensions.md +148 -0
- package/doc/ai/language-support.md +152 -0
- package/doc/ai/troubleshooting.md +163 -0
- package/doc/ai/zh-CN/README.md +56 -0
- package/doc/ai/zh-CN/build-recipe.md +132 -0
- package/doc/ai/zh-CN/cli.md +127 -0
- package/doc/ai/zh-CN/contributing.md +173 -0
- package/doc/ai/zh-CN/extensions.md +139 -0
- package/doc/ai/zh-CN/language-support.md +147 -0
- package/doc/ai/zh-CN/troubleshooting.md +150 -0
- package/doc/gui-scripts.md +350 -0
- package/doc/gui.md +646 -0
- package/doc/icon.md +265 -0
- package/doc/implemented.md +373 -0
- package/doc/node-implemented.md +588 -0
- package/doc/node-unimplemented.md +167 -0
- package/doc/post/announce.md +43 -0
- package/doc/requirements.md +145 -0
- package/doc/unimplemented.md +286 -0
- package/doc/xbintsc.config.schema.json +67 -0
- package/doc/zh-CN/DESIGN.md +104 -0
- package/doc/zh-CN/gui-scripts.md +329 -0
- package/doc/zh-CN/gui.md +588 -0
- package/doc/zh-CN/icon.md +241 -0
- package/doc/zh-CN/implemented.md +365 -0
- package/doc/zh-CN/node-implemented.md +533 -0
- package/doc/zh-CN/node-unimplemented.md +141 -0
- package/doc/zh-CN/plan-require-node-modules.md +284 -0
- package/doc/zh-CN/post/announce.md +47 -0
- package/doc/zh-CN/requirements.md +134 -0
- package/doc/zh-CN/unimplemented.md +247 -0
- package/llms.txt +45 -0
- package/package.json +4 -1
- package/runtime/ext_gui/gui.cpp +3 -1
- package/runtime/ext_gui/renderer.cpp +13 -11
- package/runtime/ext_gui/renderer_image.cpp +12 -8
- package/runtime/ext_gui/renderer_shaders.h +131 -4
- package/runtime/ext_gui/renderer_shaders_data.h +1809 -0
- package/runtime/ext_gui/renderer_text.cpp +12 -8
- package/runtime/ext_gui/shaders.hlsl +98 -0
- package/runtime/ext_gui/spirv/fill.frag +19 -0
- package/runtime/ext_gui/spirv/fill.vert +42 -0
- package/runtime/ext_gui/spirv/image.frag +16 -0
- package/runtime/ext_gui/spirv/quad.vert +30 -0
- package/runtime/ext_gui/spirv/text.frag +16 -0
- package/scripts/build-gui-shaders.mjs +204 -0
- package/scripts/build-gui.ts +35 -0
- package/scripts/check-file-length.ts +5 -1
- package/src/cli/hints.ts +194 -0
- package/src/cli/main.ts +82 -9
- package/src/codegen/generator/tables.ts +60 -14
- package/src/diagnostics/source-text.ts +78 -0
- package/src/driver/bundler/graph.ts +2 -1
- package/src/driver/compiler.ts +3 -2
- package/src/lexer/scanner/strings.ts +16 -3
package/doc/ai/cli.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# CLI and configuration reference
|
|
2
|
+
|
|
3
|
+
The authoritative `--help` text is the `HELP` constant in
|
|
4
|
+
[../../src/cli/main.ts](../../src/cli/main.ts). This page adds the semantics that
|
|
5
|
+
the help text leaves implicit.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
| Command | Purpose | Needs clang? |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `xbintsc build <file.ts>` | Compile to `exe` (default), `obj` or `ir` | yes, except `--emit ir` |
|
|
12
|
+
| `xbintsc run <file.ts> [-- args]` | Compile to an executable and execute it | yes (`--emit exe` only) |
|
|
13
|
+
| `xbintsc emit <file.ts>` | Print LLVM IR to stdout, write nothing | no |
|
|
14
|
+
| `xbintsc doctor` | Report the resolved toolchain, runtime and icon tools | only for the probe |
|
|
15
|
+
| `xbintsc version` | Print the version | no |
|
|
16
|
+
| `xbintsc help` | Print the help text | no |
|
|
17
|
+
|
|
18
|
+
Any command also accepts `--help`, which prints the help text and exits 0. An
|
|
19
|
+
unknown command prints an error plus the help text and exits 1.
|
|
20
|
+
|
|
21
|
+
## Options
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
-o, --output <path> Explicit output path (overrides --out and the config)
|
|
25
|
+
--out <dir> Output directory (default: build/)
|
|
26
|
+
--emit <kind> exe | obj | ir (default: exe)
|
|
27
|
+
-O0 .. -O3 Optimization level passed to clang (default: -O2)
|
|
28
|
+
--ext <names> Enable bundled extensions, comma separated (e.g. node,gui)
|
|
29
|
+
--ext-native <m> Register a C++/Rust extension from a JSON manifest
|
|
30
|
+
(comma separated for several)
|
|
31
|
+
--config <path> Use this project config instead of discovering one
|
|
32
|
+
--no-config Do not read any project config
|
|
33
|
+
--icon <path> Embed an application icon (PNG/ICO/ICNS)
|
|
34
|
+
--bundle macOS: also produce a <name>.app bundle
|
|
35
|
+
--app-name <name> Bundle / display name
|
|
36
|
+
--app-id <id> macOS bundle identifier (e.g. com.example.demo)
|
|
37
|
+
--force Ignore the incremental cache
|
|
38
|
+
--verbose Print progress information
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Parsing details that occasionally surprise:
|
|
42
|
+
|
|
43
|
+
- `--flag=value` and `--flag value` are both accepted.
|
|
44
|
+
- `-O`, `-O1`, `-O2`, `-O3` all work; a bare `-O` means `-O2`.
|
|
45
|
+
- `--` ends option parsing: everything after it belongs to the program under
|
|
46
|
+
`run`, or is a positional for the other commands.
|
|
47
|
+
- `-o` consumes the next argument unconditionally; the other value flags leave
|
|
48
|
+
the value unset when the next argument starts with `-`.
|
|
49
|
+
|
|
50
|
+
## Project config
|
|
51
|
+
|
|
52
|
+
`xbintsc.config.json` is discovered by walking **up** from the entry file's
|
|
53
|
+
directory (or from the current directory when no entry was given). Every path in
|
|
54
|
+
the file resolves relative to the config file, and any CLI flag overrides the
|
|
55
|
+
matching field. `--no-config` skips discovery entirely.
|
|
56
|
+
|
|
57
|
+
| Field | Type | Meaning |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `entry` | string | Entry TypeScript file, so `xbintsc build` needs no positional |
|
|
60
|
+
| `outDir` | string | Output directory (default `build/`) |
|
|
61
|
+
| `output` | string | Explicit output path, overriding `outDir` |
|
|
62
|
+
| `optimize` | `"0"|"1"|"2"|"3"` | Optimization level |
|
|
63
|
+
| `extensions` | string[] | Bundled extensions to enable, e.g. `["node"]` |
|
|
64
|
+
| `extNative` | string[] | Native extension manifest paths |
|
|
65
|
+
| `force` | boolean | Ignore the incremental cache |
|
|
66
|
+
| `app.name` / `app.icon` / `app.bundle` / `app.bundleId` | | Application metadata ([icon.md](../icon.md)) |
|
|
67
|
+
|
|
68
|
+
JSON schema: [xbintsc.config.schema.json](../xbintsc.config.schema.json). An
|
|
69
|
+
unreadable or malformed config fails the build with `invalid JSON` or
|
|
70
|
+
`Unable to read` rather than silently falling back to defaults.
|
|
71
|
+
|
|
72
|
+
## Environment variables
|
|
73
|
+
|
|
74
|
+
| Variable | Effect |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `xbintsc_CLANG` | Use this clang instead of resolving one from `PATH` |
|
|
77
|
+
| `xbintsc_LINKER_ARGS` | Extra linker flags (e.g. `-fuse-ld=lld`) |
|
|
78
|
+
| `xbintsc_CACHE_DIR` | Object-cache directory (default: `.xbintsc`) |
|
|
79
|
+
| `xbintsc_PREFER_PREBUILT` | `0` forces the runtime to be compiled from source |
|
|
80
|
+
| `xbintsc_BINARY` | Native compiler binary the `bin/` launcher should run |
|
|
81
|
+
|
|
82
|
+
## Incremental compilation
|
|
83
|
+
|
|
84
|
+
Each build is keyed on the entry source hash, the compiler version, the build
|
|
85
|
+
options, the platform and the active extension set
|
|
86
|
+
([../../src/driver/cache.ts](../../src/driver/cache.ts)). If every recorded
|
|
87
|
+
output still exists, the build returns immediately and prints `(cached)`. The C
|
|
88
|
+
runtime and extension sources are cached the same way; objects live in
|
|
89
|
+
`.xbintsc/` (or `xbintsc_CACHE_DIR`).
|
|
90
|
+
|
|
91
|
+
`--force` (or `force: true`) bypasses the freshness check. If a build ever
|
|
92
|
+
returns stale output, that key is the first thing to inspect — and note that the
|
|
93
|
+
object cache key deliberately excludes compiler flags, which is why the runtime
|
|
94
|
+
coverage script uses a separate cache directory.
|
|
95
|
+
|
|
96
|
+
## Exit codes
|
|
97
|
+
|
|
98
|
+
- `0` — success (for `run`: the program exited 0).
|
|
99
|
+
- `1` — diagnostics, a usage error, or a failure in the compiler itself.
|
|
100
|
+
- `run` otherwise propagates the program's own exit status.
|
|
101
|
+
|
|
102
|
+
## Diagnostics
|
|
103
|
+
|
|
104
|
+
Errors are rendered as `file:line:col - error TS<code>: <message>` with a source
|
|
105
|
+
excerpt and caret underline. Codes are grouped by stage
|
|
106
|
+
([../../src/diagnostics/diagnostic.ts](../../src/diagnostics/diagnostic.ts)):
|
|
107
|
+
|
|
108
|
+
| Range | Stage |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| TS1xxx | lexer |
|
|
111
|
+
| TS2xxx | parser |
|
|
112
|
+
| TS3xxx | binder |
|
|
113
|
+
| TS4xxx | checker (`TS4005` is the `UnsupportedFeature` code) |
|
|
114
|
+
| TS5xxx | codegen |
|
|
115
|
+
| TS6xxx | driver: `TS6001` module not found, `TS6002` IO, `TS6003` toolchain, `TS6004` cache |
|
|
116
|
+
|
|
117
|
+
The CLI appends a `hint:` line pointing at the relevant document (for example
|
|
118
|
+
[language-support.md](./language-support.md) for `TS4005`, or
|
|
119
|
+
[troubleshooting.md](./troubleshooting.md) for `TS6001`/`TS6003`). Toolchain and
|
|
120
|
+
IO failures throw instead of producing a diagnostic, so the CLI reports those as
|
|
121
|
+
`xbintsc: <message>` plus the same style of hint.
|
|
122
|
+
|
|
123
|
+
## Programmatic API
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { build, compileString } from "xbintsc";
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- `compileString(source, fileName?, extensions?)` → `{ ir, diagnostics }`.
|
|
130
|
+
Pure IR generation: no filesystem, no clang.
|
|
131
|
+
- `build(entryPath, options?)` → `BuildResult`. `options` accepts `output`,
|
|
132
|
+
`outDir`, `emit`, `optimize`, `force`, `verbose`, `extensions` (a registry),
|
|
133
|
+
`app`, `clang`, `preferPrebuilt`.
|
|
134
|
+
- `canonicalize` of results: `{ outputPath, irPath?, cached, diagnostics, ir?,
|
|
135
|
+
bundlePath? }`.
|
|
136
|
+
|
|
137
|
+
`build` reports source-level problems through `diagnostics` — always check
|
|
138
|
+
`diagnostics.some((d) => d.category === "error")` before using the artifact — but
|
|
139
|
+
**throws** `ToolchainError` when clang itself fails
|
|
140
|
+
([../../src/driver/toolchain.ts](../../src/driver/toolchain.ts)). The
|
|
141
|
+
`xbintsc/driver` subpath additionally exports the cache, toolchain resolution,
|
|
142
|
+
config loader, icon and macOS-bundle helpers.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Contributing to the compiler
|
|
2
|
+
|
|
3
|
+
Use this page after [../AGENTS.md](../../AGENTS.md), which covers the layout, the
|
|
4
|
+
build gate and the rules. This one covers the workflow details that are easy to
|
|
5
|
+
get wrong.
|
|
6
|
+
|
|
7
|
+
## Set up
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
npm run typecheck # tsc --noEmit
|
|
12
|
+
npm run lint # eslint + the 600-line file budget
|
|
13
|
+
npm test # unit + end-to-end
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Node.js ≥ 22 is required to run the compiler from source, and a **clang 16+**
|
|
17
|
+
toolchain is required for anything that links a binary (see
|
|
18
|
+
[troubleshooting.md](./troubleshooting.md)). `xbintsc emit` needs neither clang
|
|
19
|
+
nor the runtime library, which makes it the fastest inner loop.
|
|
20
|
+
|
|
21
|
+
Optional but recommended — install the repository hook:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
git config core.hooksPath .githooks
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`.githooks/pre-commit` **bumps the patch version on every commit** and folds
|
|
28
|
+
`package.json` + `package-lock.json` into that commit (`npm version patch
|
|
29
|
+
--no-git-tag-version`). Expect a version bump in your commits; do not countermand
|
|
30
|
+
it. Release tags are created by `release.yml`, never by the hook.
|
|
31
|
+
|
|
32
|
+
## The test suites
|
|
33
|
+
|
|
34
|
+
| Command | Scope |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `npm test` | everything: per-module unit tests plus `tests/e2e` |
|
|
37
|
+
| `npm run test:e2e` | only the compile-and-run tests |
|
|
38
|
+
| `npm run test:watch` | vitest in watch mode |
|
|
39
|
+
| `npm run coverage` | V8 coverage of the TypeScript compiler (`src/`) |
|
|
40
|
+
| `npm run coverage:runtime` | LLVM coverage of the C runtime (`runtime/`) |
|
|
41
|
+
|
|
42
|
+
Unit tests live one directory per module under `tests/` (`lexer`, `parser`,
|
|
43
|
+
`binder`, `codegen`, `driver`, `extensions`, `cli`, `e2e`) and mirror the source
|
|
44
|
+
tree. Edge-case tests belong next to the module they cover.
|
|
45
|
+
|
|
46
|
+
`tests/e2e/` compiles real programs and runs the resulting binaries, including a
|
|
47
|
+
**differential harness** (`tests/e2e/differential-*.test.ts`) that executes the
|
|
48
|
+
same source through both xbintsc and Node and compares output byte-for-byte. That
|
|
49
|
+
harness is the strongest tool in the repository: when you touch semantics, add a
|
|
50
|
+
case there rather than asserting a hand-written expected string. Tests that need
|
|
51
|
+
clang skip themselves when it is unavailable (`hasClang()` in
|
|
52
|
+
[tests/helpers.ts](../../tests/helpers.ts)) — do not turn that into a silent
|
|
53
|
+
pass.
|
|
54
|
+
|
|
55
|
+
## Two invariants you must not break
|
|
56
|
+
|
|
57
|
+
### 1. The self-hosting fixpoint
|
|
58
|
+
|
|
59
|
+
CI (`self-host` job in [.github/workflows/ci.yml](../../.github/workflows/ci.yml))
|
|
60
|
+
compiles the compiler with itself and requires the emitted IR to be identical
|
|
61
|
+
across generations:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
source --emit--> ref.ll
|
|
65
|
+
source --build--> gen1 binary
|
|
66
|
+
gen1 --emit--> gen2.ll # must equal ref.ll
|
|
67
|
+
gen1 --build--> gen2 binary
|
|
68
|
+
gen2 --emit--> gen3.ll # must equal ref.ll
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
By consequence: **any change that alters emitted IR must still be reproducible by
|
|
72
|
+
the compiler being changed**, and the IR must be byte-for-byte deterministic —
|
|
73
|
+
iteration order, generated symbol names and numbering included. Never introduce
|
|
74
|
+
nondeterminism (a `Map`/`Set` iteration over insertion-ordered data you did not
|
|
75
|
+
control, a timestamp, a filesystem-order dependency) into codegen.
|
|
76
|
+
|
|
77
|
+
A second consequence is the string model: the compiler's own source text has to
|
|
78
|
+
mean the same thing in every generation. A compiled string is UTF-8 bytes, so the
|
|
79
|
+
self-hosted `readFileSync` (`--ext node`) hands the scanner the bytes of a file,
|
|
80
|
+
while Node hands it the same file already decoded — read source files through
|
|
81
|
+
`decodeUtf8` ([src/diagnostics/utf8.ts](../../src/diagnostics/utf8.ts)) and never
|
|
82
|
+
assume a code unit is a byte or a character. Getting this wrong shows up as a
|
|
83
|
+
non-ASCII literal (`—` in a hint string) coming out re-encoded in `gen2.ll`, which
|
|
84
|
+
is why the emitted IR is compared byte-for-byte rather than line-by-line.
|
|
85
|
+
|
|
86
|
+
Verify locally:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx tsx src/cli/main.ts emit src/cli/main.ts --ext node > scratch/ref.ll
|
|
90
|
+
npx tsx src/cli/main.ts build src/cli/main.ts --ext node --out scratch/self --force
|
|
91
|
+
./scratch/self/main emit src/cli/main.ts --ext node > scratch/gen2.ll # .exe on Windows
|
|
92
|
+
diff scratch/ref.ll scratch/gen2.ll
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2. The runtime ABI and value model
|
|
96
|
+
|
|
97
|
+
- Every compiled function uses
|
|
98
|
+
`xt_value fn(xt_value env, int32_t argc, xt_value *argv)` — direct calls and
|
|
99
|
+
closure calls share this one path, and `env` threads captured variables
|
|
100
|
+
through boxes.
|
|
101
|
+
- Every value is a single 64-bit word: doubles unboxed, everything else a tagged
|
|
102
|
+
pointer (16-bit tag + 48-bit payload). The representation is defined **once**
|
|
103
|
+
in [../../src/codegen/values.ts](../../src/codegen/values.ts) and
|
|
104
|
+
[../../runtime/rt.h](../../runtime/rt.h); change both together or not at all.
|
|
105
|
+
- Awkward JS semantics (`+` coercion, relational comparison, property access,
|
|
106
|
+
inspection) are delegated to `@xt_*` runtime calls rather than inlined.
|
|
107
|
+
- The GC is a non-moving mark-sweep collector: new heap objects go through
|
|
108
|
+
`xt_alloc`, and a value must be reachable from an explicit root slot, a
|
|
109
|
+
registered root provider (the event loop, the microtask queue) or the
|
|
110
|
+
conservative C-stack scan while it is live. A value held only in a C local that
|
|
111
|
+
is not on the scanned stack is a bug.
|
|
112
|
+
|
|
113
|
+
## Changing the runtime
|
|
114
|
+
|
|
115
|
+
`runtime/` is C, split by function across translation units (`xt_alloc.c`,
|
|
116
|
+
`xt_values.c`, `xt_containers.c`, `xt_stdlib.c`, …) sharing
|
|
117
|
+
`runtime/rt_internal.h`. The runtime must be rebuilt for C changes to take
|
|
118
|
+
effect:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm run runtime # tsx scripts/build-runtime.ts -> runtime/lib/<os>-<arch>/
|
|
122
|
+
npm run runtime:clean # drop build/runtime-obj and runtime/lib
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Add a new C file only together with the place that lists the runtime sources
|
|
126
|
+
(`RUNTIME_SOURCES` in [../../src/driver/compiler.ts](../../src/driver/compiler.ts)),
|
|
127
|
+
because that list decides what gets compiled and linked. Extension C sources are
|
|
128
|
+
declared by the extension itself.
|
|
129
|
+
|
|
130
|
+
## Adding or extending an extension
|
|
131
|
+
|
|
132
|
+
An extension is a plain object ([../../src/extensions/registry.ts](../../src/extensions/registry.ts))
|
|
133
|
+
contributing `runtimeSources()`, `modules()` / `builtins()`, and optionally
|
|
134
|
+
`nativeObjects()` or `assetLoaders()`. The Node extension is one folder per
|
|
135
|
+
module, pairing its TypeScript exports with the C sources that implement them:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
src/extensions/node/fs/index.ts runtime/ext_node/fs/read_file.c
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Adding a Node module therefore means dropping a folder in each place and
|
|
142
|
+
registering it in the module list; the core compiler never changes. If your
|
|
143
|
+
feature is optional or platform-specific, it belongs in an extension rather than
|
|
144
|
+
in `src/codegen`. Bundled extensions are listed in
|
|
145
|
+
[../../src/extensions/catalog.ts](../../src/extensions/catalog.ts) — an extension
|
|
146
|
+
listed there but not enabled produces the actionable `pass --ext <name>` hint, so
|
|
147
|
+
add modules to the list even when the feature is off by default.
|
|
148
|
+
|
|
149
|
+
Extension authoring from C++/Rust without touching the compiler is documented in
|
|
150
|
+
[../../examples/extensions/README.md](../../examples/extensions/README.md).
|
|
151
|
+
|
|
152
|
+
## Adding a diagnostic
|
|
153
|
+
|
|
154
|
+
1. Add a stable code to the enum grouped by pipeline stage in
|
|
155
|
+
[../../src/diagnostics/diagnostic.ts](../../src/diagnostics/diagnostic.ts)
|
|
156
|
+
(1xxx lexer, 2xxx parser, 3xxx binder, 4xxx checker, 5xxx codegen, 6xxx
|
|
157
|
+
driver). Codes are a public interface — never renumber one.
|
|
158
|
+
2. Emit it with the most specific range you have; `formatDiagnostic` renders the
|
|
159
|
+
file, line, column, excerpt and caret from it.
|
|
160
|
+
3. Make the message actionable, in the style of the existing ones: say what is
|
|
161
|
+
wrong *and* what to do (`pass --ext node`, `use an ESM import`, …). If a
|
|
162
|
+
reader needs more, the CLI hint layer maps codes to a document — extend that
|
|
163
|
+
map in [../../src/cli/main.ts](../../src/cli/main.ts) when you add a code.
|
|
164
|
+
4. Cover it in `tests/diagnostics` or the module's own test.
|
|
165
|
+
|
|
166
|
+
## Documentation is part of the change
|
|
167
|
+
|
|
168
|
+
A feature is not done until the docs match:
|
|
169
|
+
|
|
170
|
+
| Document | Update when |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| [../implemented.md](../implemented.md) | a feature starts working |
|
|
173
|
+
| [../unimplemented.md](../unimplemented.md) | a limitation appears or is lifted |
|
|
174
|
+
| [../node-implemented.md](../node-implemented.md) / [../node-unimplemented.md](../node-unimplemented.md) | Node module coverage changes |
|
|
175
|
+
| [language-support.md](./language-support.md) | the AI-facing summary of the above changes |
|
|
176
|
+
| [../requirements.md](../requirements.md) | the toolchain or platform requirements change |
|
|
177
|
+
| [cli.md](./cli.md) | a flag, config field or environment variable changes |
|
|
178
|
+
|
|
179
|
+
Keep the Chinese translation (`doc/zh-CN/`, `doc/ai/zh-CN/`) in step with the
|
|
180
|
+
English source; a half-translated page is worse than none because it silently
|
|
181
|
+
goes stale.
|
|
182
|
+
|
|
183
|
+
## Repository hygiene
|
|
184
|
+
|
|
185
|
+
- **600 lines per code file**, enforced by ESLint (`max-lines`) for TS/JS and by
|
|
186
|
+
[scripts/check-file-length.ts](../../scripts/check-file-length.ts) for C, C++,
|
|
187
|
+
Rust, `.inc` and shell files. Split by responsibility instead of growing a
|
|
188
|
+
file; vendored sources are exempt.
|
|
189
|
+
- **No `any`**; `prefer-const` and `eqeqeq` are errors. The
|
|
190
|
+
declaration-merging pattern in the parser/generator is deliberately allowed.
|
|
191
|
+
- **Public API changes** go through [../../src/index.ts](../../src/index.ts) and
|
|
192
|
+
must keep the `xbintsc` / `xbintsc/driver` export maps in
|
|
193
|
+
[../../package.json](../../package.json) valid.
|
|
194
|
+
- `npm run package-release` assembles the per-platform archives into
|
|
195
|
+
`dist/release/`; the release workflow owns tagging, so never push a tag by
|
|
196
|
+
hand.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Extensions: Node modules, GUI, C++/Rust libraries
|
|
2
|
+
|
|
3
|
+
The compiler core is platform-agnostic. Anything platform-specific — Node's
|
|
4
|
+
`fs`, an HTML/CSS renderer, your own C++ library — arrives as an **extension**
|
|
5
|
+
that contributes module bindings plus the C/C++ sources or objects to link.
|
|
6
|
+
|
|
7
|
+
## The rule that trips people up
|
|
8
|
+
|
|
9
|
+
**An import of an extension-provided module fails until the extension is
|
|
10
|
+
enabled.** xbintsc knows the module exists and says exactly what to pass:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
$ xbintsc run app.ts # app.ts: import { readFileSync } from "fs";
|
|
14
|
+
error TS6001: module 'fs' is provided by the 'node' extension; pass --ext node
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
xbintsc run app.ts --ext node
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Enable several at once with a comma-separated list: `--ext node,gui`. In a
|
|
22
|
+
project, put them in `xbintsc.config.json` so no flag is needed:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{ "entry": "src/app.ts", "outDir": "build", "extensions": ["node"] }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Bundled extensions
|
|
29
|
+
|
|
30
|
+
### `node` — Node built-in modules
|
|
31
|
+
|
|
32
|
+
Enable with `--ext node`. A module is importable by its bare name or the
|
|
33
|
+
`node:` prefix (`import { readFileSync } from "node:fs"`), and both spellings
|
|
34
|
+
resolve to the same implementation.
|
|
35
|
+
|
|
36
|
+
| Module | Notes |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `fs` | **synchronous API only** (`readFileSync`, `writeFileSync`, …); `fs/promises` is separate |
|
|
39
|
+
| `fs/promises` | promise-based file APIs |
|
|
40
|
+
| `path` | also hooks namespace dispatch, so `path.join(...)` works |
|
|
41
|
+
| `os` | |
|
|
42
|
+
| `process` | `process.cwd()`, `argv`, `env`, …; namespace dispatch too |
|
|
43
|
+
| `buffer` | |
|
|
44
|
+
| `crypto` | |
|
|
45
|
+
| `stream`, `stream/promises` | |
|
|
46
|
+
| `events` | |
|
|
47
|
+
| `net`, `dgram`, `http` | sockets and servers on the xbintsc event loop (see the async caveat below) |
|
|
48
|
+
| `child_process` | |
|
|
49
|
+
| `worker_threads` | |
|
|
50
|
+
| `util`, `querystring`, `url`, `assert`, `test`, `zlib` | |
|
|
51
|
+
|
|
52
|
+
Coverage per module (exact functions and options) is in
|
|
53
|
+
[../node-implemented.md](../node-implemented.md); what is missing is in
|
|
54
|
+
[../node-unimplemented.md](../node-unimplemented.md).
|
|
55
|
+
|
|
56
|
+
Remember the runtime model before building on `net`/`http`: the event loop runs
|
|
57
|
+
**after** the program body, and `async`/`await` is a synchronous microtask
|
|
58
|
+
model. See [language-support.md](./language-support.md).
|
|
59
|
+
|
|
60
|
+
### `gui` — HTML/CSS window
|
|
61
|
+
|
|
62
|
+
Enable with `--ext gui` and import the `gui` module:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { createWindow, run } from "gui";
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
It is a self-contained GPU-accelerated renderer (own HTML parser, CSS cascade,
|
|
69
|
+
layout and compositor) — not a system WebView. It ships as a per-platform
|
|
70
|
+
prebuilt `gui.a`/`gui.lib` because it is C++. If that archive is missing for
|
|
71
|
+
your platform, the build fails with an actionable message; design and roadmap
|
|
72
|
+
live in [../gui.md](../gui.md), and the scripting surface in
|
|
73
|
+
[../gui-scripts.md](../gui-scripts.md).
|
|
74
|
+
|
|
75
|
+
## Third-party npm packages
|
|
76
|
+
|
|
77
|
+
Bare specifiers that no enabled extension claims are looked up in `node_modules`
|
|
78
|
+
and bundled **as source**. A package that is not plain ESM TypeScript/JavaScript
|
|
79
|
+
— or that relies on CommonJS, `require`, `__dirname`, or circular dependencies —
|
|
80
|
+
will not work. `require()` is rejected with a hint to convert to `import`.
|
|
81
|
+
|
|
82
|
+
Practical consequence: for a Node program, enabling `--ext node` is usually the
|
|
83
|
+
right answer; reach for an npm dependency only when you must.
|
|
84
|
+
|
|
85
|
+
## Native extensions (C++ / Rust, no compiler changes)
|
|
86
|
+
|
|
87
|
+
Any code that exposes `extern "C"` entry points with the runtime ABI can be
|
|
88
|
+
linked in:
|
|
89
|
+
|
|
90
|
+
```c
|
|
91
|
+
xt_value my_fn(int32_t argc, xt_value *argv);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Build it with an **external** toolchain (clang++ or cargo), describe the
|
|
95
|
+
artifacts in a JSON manifest, and pass the manifest:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
xbintsc build demo.ts --ext-native ./xbintsc.manifest.json
|
|
99
|
+
xbintsc build demo.ts --ext-native a.json,b.json # several
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```jsonc
|
|
103
|
+
{
|
|
104
|
+
"name": "mathx-cpp", // required, unique
|
|
105
|
+
"objects": ["build/mathx.o"], // .o / .a / .lib, relative to this file
|
|
106
|
+
"linkerFlagsByPlatform": { // C++/Rust runtimes
|
|
107
|
+
"linux": ["-lstdc++", "-lm"],
|
|
108
|
+
"darwin": ["-lc++"],
|
|
109
|
+
"win32": ["-lmsvcprt"]
|
|
110
|
+
},
|
|
111
|
+
"builtins": { "cppClamp": { "symbol": "mathx_clamp" } }, // no import needed
|
|
112
|
+
"modules": {
|
|
113
|
+
"mathx": { "exports": { "add": { "symbol": "mathx_add" } } }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`import { add } from "mathx"` then lowers to the native symbol exactly like a C
|
|
119
|
+
runtime binding; `builtins` are globally callable without importing.
|
|
120
|
+
|
|
121
|
+
Authoring helpers: `runtime/xt_ext.h` (C/C++) and `runtime/xt_ext.rs` (Rust) —
|
|
122
|
+
they wrap the argument and value helpers. Key ABI facts: values are 64-bit
|
|
123
|
+
NaN-boxed words; strings are **not** NUL-terminated, so pair `xt_string_data`
|
|
124
|
+
with `xt_string_length_value`; the runtime never moves or frees memory it handed
|
|
125
|
+
out. Build with the same clang/ABI that xbintsc resolves (`xbintsc doctor`
|
|
126
|
+
prints it), and on Windows build inside an **x64/ARM64 Native Tools Command
|
|
127
|
+
Prompt**.
|
|
128
|
+
|
|
129
|
+
Working projects to copy: [`examples/extensions/cpp`](../../examples/extensions/cpp)
|
|
130
|
+
and [`examples/extensions/rust`](../../examples/extensions/rust); the full guide
|
|
131
|
+
is [../../examples/extensions/README.md](../../examples/extensions/README.md).
|
|
132
|
+
|
|
133
|
+
## Programmatic registration
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { build, createDefaultRegistry, nativeExtensionFromManifest, nodeExtension } from "xbintsc";
|
|
137
|
+
|
|
138
|
+
const extensions = createDefaultRegistry()
|
|
139
|
+
.register(nodeExtension)
|
|
140
|
+
.register(nativeExtensionFromManifest("./xbintsc.manifest.json"));
|
|
141
|
+
|
|
142
|
+
build("demo.ts", { extensions });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The registry is also how a host tool hints at a disabled extension, which is what
|
|
146
|
+
produces the "pass `--ext node`" message instead of a confusing downstream error
|
|
147
|
+
([../../src/extensions/catalog.ts](../../src/extensions/catalog.ts)). The list of
|
|
148
|
+
bundled extensions is `bundledExtensions()`.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Language support: what compiles, what behaves differently
|
|
2
|
+
|
|
3
|
+
xbintsc compiles a **practical subset** of TypeScript. Use this page to decide
|
|
4
|
+
what you can write; follow the links for exact wording.
|
|
5
|
+
|
|
6
|
+
Canonical detail (always current, much longer):
|
|
7
|
+
|
|
8
|
+
- [../implemented.md](../implemented.md) — everything that works, by compiler stage
|
|
9
|
+
- [../unimplemented.md](../unimplemented.md) — unsupported syntax, deviations, quick reference
|
|
10
|
+
- [../node-implemented.md](../node-implemented.md) / [../node-unimplemented.md](../node-unimplemented.md) — the Node extension
|
|
11
|
+
|
|
12
|
+
> Chinese: [../zh-CN/implemented.md](../zh-CN/implemented.md), [../zh-CN/unimplemented.md](../zh-CN/unimplemented.md)
|
|
13
|
+
|
|
14
|
+
## Legend
|
|
15
|
+
|
|
16
|
+
| Mark | Meaning |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| ✅ | Works as in Node/TypeScript |
|
|
19
|
+
| ⚠️ | Works but deviates from the standard |
|
|
20
|
+
| 🚫 | Rejected — parse error or `UnsupportedFeature` |
|
|
21
|
+
| — | Parsed and erased, with no runtime effect |
|
|
22
|
+
|
|
23
|
+
## Statements and declarations
|
|
24
|
+
|
|
25
|
+
| Feature | Status | Notes |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `var` / `let` / `const`, blocks, `if`, `for`, `for...of`, `for...in`, `while`, `do...while` | ✅ | `for...in` enumerates own keys only (no prototype chain) |
|
|
28
|
+
| `switch`, `break`, `continue`, labels | ✅ | labeled `break`/`continue` included |
|
|
29
|
+
| `try` / `catch` / `finally`, `throw` | ✅ | `finally` runs on early `return`/`break`/`continue` |
|
|
30
|
+
| `function`, arrow functions, default/rest parameters | ✅ | |
|
|
31
|
+
| `return`, `throw` as expressions' operands | ✅ | |
|
|
32
|
+
| `namespace` / `module` declaration | 🚫 | parse ✓, codegen ✗ |
|
|
33
|
+
| `with`, `debugger` | 🚫 | not supported |
|
|
34
|
+
| Top-level `await` | 🚫 | wrap it in an `async` function |
|
|
35
|
+
|
|
36
|
+
## Expressions and operators
|
|
37
|
+
|
|
38
|
+
| Feature | Status | Notes |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| Arithmetic, bitwise, logical, comparison, assignment operators | ✅ | ES `ToPrimitive` / `+` coercion match Node |
|
|
41
|
+
| `===` / `!==` / `==` / `!=` | ✅ | loose equality matches Node |
|
|
42
|
+
| Optional chaining `?.`, nullish `??`, logical assignment `??=` | ✅ | whole-chain short-circuit implemented |
|
|
43
|
+
| Template literals, tagged templates | ✅ | tagged templates have `raw` + `String.raw` |
|
|
44
|
+
| Spread / rest in calls, arrays and object literals | ✅ | |
|
|
45
|
+
| Destructuring (bindings, parameters, nested) | ✅ | defaults + rest supported |
|
|
46
|
+
| `delete`, `in`, `instanceof`, `typeof` | ✅ | |
|
|
47
|
+
| Comma operator, `void` | ✅ | |
|
|
48
|
+
| `new.target` | 🚫 | not implemented |
|
|
49
|
+
| `import.meta` | 🚫 | parsed but has no value |
|
|
50
|
+
| `super` | ⚠️ | single-level inheritance correct; depth > 1 may be inaccurate |
|
|
51
|
+
|
|
52
|
+
## Functions, classes, objects
|
|
53
|
+
|
|
54
|
+
| Feature | Status | Notes |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| Closures (capture by reference through boxes) | ✅ | one ABI for direct and closure calls |
|
|
57
|
+
| `this`, method calls, arrow lexical `this` | ✅ | arrow functions get their own `arguments`, unlike JS |
|
|
58
|
+
| `call` / `apply` / `bind`, `fn.name` / `fn.length` | ✅ | a bound closure does not track partial-argument `length` |
|
|
59
|
+
| First-class built-in methods (`arr.map` as a value) | ✅ | exposed as *unbound* method values; a detached call throws like JS |
|
|
60
|
+
| `fn.toString()` | ⚠️ | returns `function name() { [native code] }`, not the source text |
|
|
61
|
+
| Classes: fields, methods, statics, getters/setters, `extends`/`super`, `instanceof` | ✅ | |
|
|
62
|
+
| Constructor parameter properties `constructor(public x: T)` | ✅ | |
|
|
63
|
+
| `#private` fields / methods / statics | ⚠️ | stored under a literal `#x` key; no access enforcement; parent/child name collisions may alias |
|
|
64
|
+
| `enum` / `const enum` | ✅ | forward + reverse mapping |
|
|
65
|
+
| `private` / `protected` / `public` / `readonly` / `abstract` / `implements` | — | erased, no access control |
|
|
66
|
+
| Generators `function*`, `yield`, `yield*` | ✅ | `async` generators are not supported |
|
|
67
|
+
| `arguments` object | ✅ | implicit; arrows see their own parameters |
|
|
68
|
+
| Argument-count validation | 🚫 | never checked, though `fn.length` reports declared arity |
|
|
69
|
+
|
|
70
|
+
## Async, Promises, event loop
|
|
71
|
+
|
|
72
|
+
| Feature | Status | Notes |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| `async` / `await`, `Promise`, `Promise.all` | ⚠️ | **synchronous microtask model** — `await` on a settled promise continues synchronously |
|
|
75
|
+
| `setTimeout` and callbacks | ⚠️ | the event loop runs **after** the program body, so a promise settled from a timer cannot be awaited |
|
|
76
|
+
| Sockets / servers (`http`, `net`, `dgram`) | ⚠️ | same model: callbacks run on the event loop after the program body |
|
|
77
|
+
| A real async event loop, worker threads | 🚫 | not implemented |
|
|
78
|
+
|
|
79
|
+
## Types: parsed, then erased
|
|
80
|
+
|
|
81
|
+
**No type checking happens.** Type syntax is parsed into the AST and erased
|
|
82
|
+
during binding/codegen:
|
|
83
|
+
|
|
84
|
+
| Construct | Status |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| Type annotations, return types, type aliases, interfaces | — parsed, erased, never checked |
|
|
87
|
+
| Generic type parameters and constraints | — no runtime instantiation |
|
|
88
|
+
| `as`, `satisfies`, non-null `!` | — erased, no assertion semantics |
|
|
89
|
+
| Optional-chaining type narrowing | 🚫 |
|
|
90
|
+
| Diagnostics `TS4001`–`TS4004` (`TypeMismatch`, `NotCallable`, `PropertyNotFound`, `ArgumentCountMismatch`) | 🚫 defined but never emitted |
|
|
91
|
+
|
|
92
|
+
A type error in your source is therefore not a compile error — only runtime
|
|
93
|
+
behaviour is checked. Write the runtime code you mean.
|
|
94
|
+
|
|
95
|
+
## Standard library
|
|
96
|
+
|
|
97
|
+
Implemented: `Math`, `JSON`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`/`WeakSet`,
|
|
98
|
+
`Symbol`, `Error` family, `BigInt`, `Array`/`String`/`Number`/`Object` methods,
|
|
99
|
+
typed arrays, `String.prototype.match`/`split`/`replace` with capture groups,
|
|
100
|
+
immutable array helpers (`toReversed`, `toSorted`, `toSpliced`, `with`), global
|
|
101
|
+
URI functions.
|
|
102
|
+
|
|
103
|
+
| Missing or different | Status |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `String.prototype.normalize`, `structuredClone` | 🚫 |
|
|
106
|
+
| `Error.stack` capture | 🚫 (not captured) |
|
|
107
|
+
| `Object.getPrototypeOf({})` | ⚠️ returns `undefined`, not `Object.prototype` |
|
|
108
|
+
| Global RegExp `lastIndex` | ⚠️ `test`/`exec` ignore it for `/g` and `/y` |
|
|
109
|
+
| Sparse array holes, array out-of-bounds | ⚠️ holes are not tracked distinctly; out-of-bounds reads give `undefined` |
|
|
110
|
+
|
|
111
|
+
See section 3 of [../unimplemented.md](../unimplemented.md) for the full list.
|
|
112
|
+
|
|
113
|
+
## Modules
|
|
114
|
+
|
|
115
|
+
| Feature | Status | Notes |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| ESM `import` / `export` | ✅ | |
|
|
118
|
+
| Relative multi-file bundling | ✅ | a `./helper.js` specifier resolves to `helper.ts` |
|
|
119
|
+
| `import * as ns`, default and named imports | ✅ | |
|
|
120
|
+
| Node built-in modules | ✅ | only with `--ext node` — see [extensions.md](./extensions.md) |
|
|
121
|
+
| Bare third-party npm packages | 🚫 | `node_modules` ESM packages are bundled as source only when no extension claims the specifier |
|
|
122
|
+
| `require()` / CommonJS | 🚫 | rejected with a hint to use `import` |
|
|
123
|
+
| Circular dependencies | 🚫 | |
|
|
124
|
+
| Live bindings | ⚠️ | imports and namespace members are snapshots |
|
|
125
|
+
| `import.meta`, `__dirname`, `__filename` | 🚫 | |
|
|
126
|
+
|
|
127
|
+
## Strings, numbers, memory
|
|
128
|
+
|
|
129
|
+
| Item | Behaviour |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| `String.prototype.length` | ⚠️ **UTF-8 bytes**, not UTF-16 units: `"é".length === 1`, `"😀".length === 4`; `codePointAt` differs accordingly |
|
|
132
|
+
| Number formatting and coercion | ✅ matches Node (`toFixed`, `toPrecision`, hex/octal/binary parsing) |
|
|
133
|
+
| `BigInt` | ✅ implemented |
|
|
134
|
+
| Garbage collection | ✅ non-moving mark-sweep; explicit roots plus a conservative C-stack scan; single-threaded and stop-the-world; no weak references |
|
|
135
|
+
| Threads | 🚫 single-threaded; `worker_threads` exists in the Node extension only |
|
|
136
|
+
|
|
137
|
+
## Platform
|
|
138
|
+
|
|
139
|
+
Builds target macOS, Linux and Windows on x64 and arm64. On Windows the target
|
|
140
|
+
is the **MSVC ABI**, so clang needs the MSVC/SDK environment — see
|
|
141
|
+
[../requirements.md](../requirements.md). clang **16 or newer** is required.
|
|
142
|
+
|
|
143
|
+
## Before you write a large program
|
|
144
|
+
|
|
145
|
+
1. Skim the table for the features you plan to use; open
|
|
146
|
+
[../unimplemented.md](../unimplemented.md) for anything marked ⚠️ or 🚫.
|
|
147
|
+
2. Prototype with `xbintsc emit app.ts` — the cheapest way to learn whether a
|
|
148
|
+
construct is supported, since it needs no clang.
|
|
149
|
+
3. Run the real thing with `xbintsc run app.ts`. If a build fails, go to
|
|
150
|
+
[troubleshooting.md](./troubleshooting.md).
|
|
151
|
+
4. When exact Node behaviour matters, run the same program under Node and
|
|
152
|
+
compare output; the project's own test suite does this differential check.
|