xbintsc 0.3.35 → 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 +31 -3
- 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/module.js +10 -0
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/state.js +4 -0
- package/dist/src/codegen/generator/state.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/e2e/gc.test.d.ts +1 -0
- package/dist/tests/e2e/gc.test.js +168 -0
- package/dist/tests/e2e/gc.test.js.map +1 -0
- package/dist/tests/e2e/harness.d.ts +2 -0
- package/dist/tests/e2e/harness.js +1 -0
- package/dist/tests/e2e/harness.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 +6 -2
- package/runtime/ext_gui/dom_api_proto.cpp +5 -0
- 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/runtime/ext_gui/window.cpp +1 -0
- package/runtime/ext_node/buffer/parts/prototype.inc +1 -0
- package/runtime/ext_node/dgram/dgram.c +1 -0
- package/runtime/ext_node/events/events.c +1 -0
- package/runtime/ext_node/fs/fs_ops.c +10 -25
- package/runtime/ext_node/fs/glob.c +13 -30
- package/runtime/ext_node/fs/promises.c +1 -0
- package/runtime/ext_node/http/parts/prototypes.inc +5 -0
- package/runtime/ext_node/net/parts/prototypes.inc +2 -0
- package/runtime/ext_node/process/process.c +9 -7
- package/runtime/ext_node/stream/stream.c +1 -0
- package/runtime/ext_node/util/util.c +6 -6
- package/runtime/rt.h +10 -0
- package/runtime/rt_internal.h +63 -2
- package/runtime/xt_alloc.c +442 -5
- package/runtime/xt_generator.c +95 -1
- package/runtime/xt_loop.c +35 -1
- package/runtime/xt_promise.c +46 -0
- package/runtime/xt_stdlib2/error.inc +1 -0
- package/runtime/xt_stdlib2/regexp-match.inc +8 -7
- package/runtime/xt_symbol.c +2 -0
- package/runtime/xt_typed_array/construction.inc +142 -0
- package/runtime/xt_typed_array/elements.inc +92 -0
- package/runtime/xt_typed_array/methods.inc +329 -0
- package/runtime/xt_typed_array.c +6 -548
- package/runtime/xt_values/number-format.inc +26 -0
- package/scripts/build-gui-shaders.mjs +204 -0
- package/scripts/build-gui.ts +35 -0
- package/scripts/check-file-length.ts +89 -0
- package/src/cli/hints.ts +194 -0
- package/src/cli/main.ts +82 -9
- package/src/codegen/generator/module.ts +10 -0
- package/src/codegen/generator/state.ts +4 -0
- 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
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Compile the GUI engine's shaders ahead of time and embed them in
|
|
4
|
+
* `runtime/ext_gui/renderer_shaders_data.h`.
|
|
5
|
+
*
|
|
6
|
+
* SDL_GPU cannot compile GLSL or HLSL at runtime: each backend consumes its own
|
|
7
|
+
* binary format. The engine therefore ships:
|
|
8
|
+
*
|
|
9
|
+
* - SPIR-V, for Vulkan, compiled from `runtime/ext_gui/spirv/*.{vert,frag}`
|
|
10
|
+
* with `glslc` (from the Vulkan SDK); and
|
|
11
|
+
* - DXIL, for Direct3D 12, compiled from `runtime/ext_gui/shaders.hlsl` with
|
|
12
|
+
* `dxc` (inside the Windows SDK).
|
|
13
|
+
*
|
|
14
|
+
* Metal needs no blob: SDL compiles the MSL literals in `renderer_shaders.h` at
|
|
15
|
+
* runtime.
|
|
16
|
+
*
|
|
17
|
+
* The generated header is committed so a machine without either compiler (Linux,
|
|
18
|
+
* macOS CI) still builds the engine; only the corresponding backend is
|
|
19
|
+
* unavailable there.
|
|
20
|
+
*
|
|
21
|
+
* Environment:
|
|
22
|
+
* xbintsc_GLSLC explicit `glslc` path (default: newest `C:\VulkanSDK\*`)
|
|
23
|
+
* xbintsc_DXC explicit `dxc` path (default: newest Windows SDK)
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* node scripts/build-gui-shaders.mjs # regenerate the header
|
|
27
|
+
* node scripts/build-gui-shaders.mjs --check # fail if it is out of date
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
31
|
+
import { spawnSync } from "node:child_process";
|
|
32
|
+
import { dirname, join, resolve } from "node:path";
|
|
33
|
+
import { fileURLToPath } from "node:url";
|
|
34
|
+
|
|
35
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
36
|
+
const engineDir = join(root, "runtime", "ext_gui");
|
|
37
|
+
const glslDir = join(engineDir, "spirv");
|
|
38
|
+
const hlsl = join(engineDir, "shaders.hlsl");
|
|
39
|
+
const header = join(engineDir, "renderer_shaders_data.h");
|
|
40
|
+
const scratch = join(root, "build", "gui-shaders");
|
|
41
|
+
|
|
42
|
+
/** SPIR-V programs: GLSL source file and the symbol to emit. */
|
|
43
|
+
const SPIRV_PROGRAMS = [
|
|
44
|
+
{ source: "fill.vert", symbol: "kFillSpirvVs" },
|
|
45
|
+
{ source: "fill.frag", symbol: "kFillSpirvFs" },
|
|
46
|
+
{ source: "quad.vert", symbol: "kTextSpirvVs" },
|
|
47
|
+
{ source: "text.frag", symbol: "kTextSpirvFs" },
|
|
48
|
+
{ source: "image.frag", symbol: "kImageSpirvFs" },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/** DXIL programs: HLSL entry point, stage and the symbol to emit. */
|
|
52
|
+
const DXIL_PROGRAMS = [
|
|
53
|
+
{ target: "vs_6_0", entry: "vs_main", symbol: "kFillDxilVs" },
|
|
54
|
+
{ target: "ps_6_0", entry: "fs_main", symbol: "kFillDxilFs" },
|
|
55
|
+
{ target: "vs_6_0", entry: "vs_text", symbol: "kTextDxilVs" },
|
|
56
|
+
{ target: "ps_6_0", entry: "fs_text", symbol: "kTextDxilFs" },
|
|
57
|
+
{ target: "ps_6_0", entry: "fs_image", symbol: "kImageDxilFs" },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
function fail(message) {
|
|
61
|
+
console.error(`xbintsc: ${message}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Newest version-suffixed directory under `base`, or undefined. */
|
|
66
|
+
function newestVersionDir(base, isVersion) {
|
|
67
|
+
if (!existsSync(base)) return undefined;
|
|
68
|
+
const versions = readdirSync(base)
|
|
69
|
+
.filter(isVersion)
|
|
70
|
+
.sort((a, b) => {
|
|
71
|
+
const pa = a.split(".").map(Number);
|
|
72
|
+
const pb = b.split(".").map(Number);
|
|
73
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
|
|
74
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
75
|
+
if (diff !== 0) return diff;
|
|
76
|
+
}
|
|
77
|
+
return 0;
|
|
78
|
+
})
|
|
79
|
+
.reverse();
|
|
80
|
+
return { base, versions };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Locate `glslc`: the explicit override, then the newest installed Vulkan SDK. */
|
|
84
|
+
function findGlslc() {
|
|
85
|
+
if (process.env.xbintsc_GLSLC) return process.env.xbintsc_GLSLC;
|
|
86
|
+
const found = newestVersionDir("C:\\VulkanSDK", (name) => /^\d+\.\d+\.\d+\.\d+$/.test(name));
|
|
87
|
+
if (!found) return undefined;
|
|
88
|
+
for (const version of found.versions) {
|
|
89
|
+
const bin = join(found.base, version, "Bin");
|
|
90
|
+
for (const name of ["glslc.exe", "glslc"]) {
|
|
91
|
+
if (existsSync(join(bin, name))) return join(bin, name);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Locate `dxc`: the explicit override, then the newest Windows SDK. */
|
|
98
|
+
function findDxc() {
|
|
99
|
+
if (process.env.xbintsc_DXC) return process.env.xbintsc_DXC;
|
|
100
|
+
const found = newestVersionDir("C:\\Program Files (x86)\\Windows Kits\\10\\bin", (name) =>
|
|
101
|
+
/^\d+\.\d+\.\d+\.\d+$/.test(name),
|
|
102
|
+
);
|
|
103
|
+
if (!found) return undefined;
|
|
104
|
+
for (const version of found.versions) {
|
|
105
|
+
for (const arch of ["x64", "arm64"]) {
|
|
106
|
+
const candidate = join(found.base, version, arch, "dxc.exe");
|
|
107
|
+
if (existsSync(candidate)) return candidate;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Compile `args` with `tool`, failing with its output on a non-zero exit. */
|
|
114
|
+
function compile(tool, args, label) {
|
|
115
|
+
const result = spawnSync(tool, args, { encoding: "utf8" });
|
|
116
|
+
if (result.status !== 0) {
|
|
117
|
+
fail(`${label} failed:\n${result.stderr ?? ""}${result.stdout ?? ""}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Render one blob as a C++ array initializer, 16 bytes per line. */
|
|
122
|
+
function renderArray(symbol, bytes) {
|
|
123
|
+
const lines = [];
|
|
124
|
+
for (let i = 0; i < bytes.length; i += 16) {
|
|
125
|
+
lines.push(" " + Array.from(bytes.subarray(i, i + 16)).join(", ") + ",");
|
|
126
|
+
}
|
|
127
|
+
return [
|
|
128
|
+
`inline constexpr unsigned char ${symbol}[] = {`,
|
|
129
|
+
...lines,
|
|
130
|
+
"};",
|
|
131
|
+
`inline constexpr size_t ${symbol}Size = sizeof(${symbol});`,
|
|
132
|
+
].join("\n");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
mkdirSync(scratch, { recursive: true });
|
|
136
|
+
|
|
137
|
+
const blobs = [];
|
|
138
|
+
|
|
139
|
+
const glslc = findGlslc();
|
|
140
|
+
if (glslc === undefined) {
|
|
141
|
+
fail(
|
|
142
|
+
"no glslc found; install the Vulkan SDK (https://vulkan.lunarg.com) or set " +
|
|
143
|
+
`xbintsc_GLSLC.\nThe committed ${header} was not regenerated.`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
for (const program of SPIRV_PROGRAMS) {
|
|
147
|
+
const output = join(scratch, `${program.symbol}.spv`);
|
|
148
|
+
compile(
|
|
149
|
+
glslc,
|
|
150
|
+
["-c", join(glslDir, program.source), "-o", output],
|
|
151
|
+
`glslc ${program.source}`,
|
|
152
|
+
);
|
|
153
|
+
blobs.push({ symbol: program.symbol, bytes: readFileSync(output) });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const dxc = findDxc();
|
|
157
|
+
if (dxc === undefined) {
|
|
158
|
+
fail(
|
|
159
|
+
"no dxc found; install the Windows SDK or set xbintsc_DXC.\n" +
|
|
160
|
+
`The committed ${header} was not regenerated.`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
for (const program of DXIL_PROGRAMS) {
|
|
164
|
+
const output = join(scratch, `${program.symbol}.dxil`);
|
|
165
|
+
compile(
|
|
166
|
+
dxc,
|
|
167
|
+
["-T", program.target, "-E", program.entry, "-Fo", output, hlsl],
|
|
168
|
+
`dxc ${program.target} ${program.entry}`,
|
|
169
|
+
);
|
|
170
|
+
blobs.push({ symbol: program.symbol, bytes: readFileSync(output) });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
174
|
+
|
|
175
|
+
const generated = [
|
|
176
|
+
"/*",
|
|
177
|
+
" * Generated by `scripts/build-gui-shaders.mjs` — do not edit.",
|
|
178
|
+
" *",
|
|
179
|
+
" * SPIR-V blobs (Vulkan) compiled from `runtime/ext_gui/spirv/` with glslc, and",
|
|
180
|
+
" * DXIL blobs (Direct3D 12) compiled from `runtime/ext_gui/shaders.hlsl` with dxc.",
|
|
181
|
+
" * The Metal path uses the MSL literals in `renderer_shaders.h` directly.",
|
|
182
|
+
" */",
|
|
183
|
+
"#ifndef XT_GUI_RENDERER_SHADERS_DATA_H",
|
|
184
|
+
"#define XT_GUI_RENDERER_SHADERS_DATA_H",
|
|
185
|
+
"",
|
|
186
|
+
"#include <stddef.h>",
|
|
187
|
+
"",
|
|
188
|
+
...blobs.map((blob) => renderArray(blob.symbol, blob.bytes)),
|
|
189
|
+
"",
|
|
190
|
+
"#endif /* XT_GUI_RENDERER_SHADERS_DATA_H */",
|
|
191
|
+
"",
|
|
192
|
+
].join("\n");
|
|
193
|
+
|
|
194
|
+
if (process.argv.includes("--check")) {
|
|
195
|
+
const current = existsSync(header) ? readFileSync(header, "utf8") : "";
|
|
196
|
+
if (current !== generated) {
|
|
197
|
+
fail(`${header} is out of date; run \`node scripts/build-gui-shaders.mjs\``);
|
|
198
|
+
}
|
|
199
|
+
console.log(`xbintsc: ${header} is up to date`);
|
|
200
|
+
} else {
|
|
201
|
+
writeFileSync(header, generated);
|
|
202
|
+
const total = blobs.reduce((sum, blob) => sum + blob.bytes.length, 0);
|
|
203
|
+
console.log(`xbintsc: wrote ${header} (${blobs.length} blobs, ${total} bytes)`);
|
|
204
|
+
}
|
package/scripts/build-gui.ts
CHANGED
|
@@ -53,6 +53,32 @@ const isWindows = process.platform === "win32";
|
|
|
53
53
|
const objectSuffix = isWindows ? ".obj" : ".o";
|
|
54
54
|
const output = join(outDir, isWindows ? "gui.lib" : "gui.a");
|
|
55
55
|
|
|
56
|
+
/* CMake generator and ABI settings used for every vendored library. Ninja is
|
|
57
|
+
* selected on Windows because the Visual Studio generator imports the
|
|
58
|
+
* machine-wide MSBuild/vcpkg integration, whose `LIBPATH` shadows the Windows
|
|
59
|
+
* SDK and makes the compiler probe fail with
|
|
60
|
+
* `LNK1104: cannot open file 'ucrtd.lib'`.
|
|
61
|
+
*
|
|
62
|
+
* `CMAKE_MSVC_RUNTIME_LIBRARY` is pinned to the *static* CRT (`MultiThreaded`),
|
|
63
|
+
* which is clang's Windows default (`-defaultlib:libcmt`) and what xbintsc uses
|
|
64
|
+
* for its own runtime objects. Building the vendored libraries against the DLL
|
|
65
|
+
* CRT instead fails the final link with
|
|
66
|
+
* `LNK2038: mismatch detected for 'RuntimeLibrary'`. The variable only takes
|
|
67
|
+
* effect when policy CMP0091 is NEW, but FreeType and HarfBuzz declare
|
|
68
|
+
* `cmake_minimum_required` below 3.15, which defaults the policy to OLD and
|
|
69
|
+
* silently ignores it — hence `CMAKE_POLICY_DEFAULT_CMP0091`. The C/C++ compilers
|
|
70
|
+
* are pinned to the same clang the engine is compiled with, so every object in
|
|
71
|
+
* `gui.a` shares one ABI and optimization behaviour. */
|
|
72
|
+
const cmakeGeneratorArgs: readonly string[] = isWindows
|
|
73
|
+
? [
|
|
74
|
+
"-G", "Ninja",
|
|
75
|
+
"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded",
|
|
76
|
+
"-DCMAKE_POLICY_DEFAULT_CMP0091=NEW",
|
|
77
|
+
"-DCMAKE_C_COMPILER=clang",
|
|
78
|
+
"-DCMAKE_CXX_COMPILER=clang++",
|
|
79
|
+
]
|
|
80
|
+
: [];
|
|
81
|
+
|
|
56
82
|
const SDL_TAG = process.env.SDL3_TAG || "release-3.2.10";
|
|
57
83
|
const FREETYPE_VERSION = process.env.FREETYPE_VERSION || "2.13.3";
|
|
58
84
|
const HARFBUZZ_VERSION = process.env.HARFBUZZ_VERSION || "10.1.0";
|
|
@@ -178,6 +204,7 @@ if (!existingSdlArchive) {
|
|
|
178
204
|
const configureArgs = [
|
|
179
205
|
"-S", sdlSrc,
|
|
180
206
|
"-B", sdlBuild,
|
|
207
|
+
...cmakeGeneratorArgs,
|
|
181
208
|
"-DCMAKE_BUILD_TYPE=Release",
|
|
182
209
|
"-DSDL_SHARED=OFF",
|
|
183
210
|
"-DSDL_STATIC=ON",
|
|
@@ -220,6 +247,7 @@ if (!existsSync(freetypeArchive() ?? "")) {
|
|
|
220
247
|
run(cmake, [
|
|
221
248
|
"-S", freetypeSrc,
|
|
222
249
|
"-B", freetypeBuild,
|
|
250
|
+
...cmakeGeneratorArgs,
|
|
223
251
|
"-DCMAKE_BUILD_TYPE=Release",
|
|
224
252
|
"-DBUILD_SHARED_LIBS=OFF",
|
|
225
253
|
/* We only need TrueType/OpenType outlines; skip the optional codecs. */
|
|
@@ -240,6 +268,7 @@ if (!existsSync(harfbuzzArchive() ?? "")) {
|
|
|
240
268
|
run(cmake, [
|
|
241
269
|
"-S", harfbuzzSrc,
|
|
242
270
|
"-B", harfbuzzBuild,
|
|
271
|
+
...cmakeGeneratorArgs,
|
|
243
272
|
"-DCMAKE_BUILD_TYPE=Release",
|
|
244
273
|
"-DBUILD_SHARED_LIBS=OFF",
|
|
245
274
|
"-DHB_BUILD_UTILS=OFF",
|
|
@@ -272,6 +301,12 @@ const sources = readdirSync(engineDir)
|
|
|
272
301
|
|
|
273
302
|
const objects: string[] = [];
|
|
274
303
|
const cxxFlags = ["-std=c++17", "-O2", "-Wall", "-Wextra", ...includeFlags];
|
|
304
|
+
/* The engine is C++ and the MSVC C++ runtime must match the static C CRT that
|
|
305
|
+
* clang links by default, so let every engine object record `libcpmt` as a
|
|
306
|
+
* default library. The extension itself only contributes the DLL import library
|
|
307
|
+
* (`-lmsvcprt`, `src/extensions/gui/index.ts`), which is inconsistent with that
|
|
308
|
+
* CRT and makes the final link fail with `LNK2038`/unresolved `__imp_*`. */
|
|
309
|
+
if (isWindows) cxxFlags.push("-Xclang", "--dependent-lib=libcpmt");
|
|
275
310
|
/* The MSVC ABI rejects `-fPIC`; position independence is the default elsewhere. */
|
|
276
311
|
if (!isWindows) cxxFlags.splice(3, 0, "-fPIC");
|
|
277
312
|
for (const source of sources) {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Enforce the per-file line budget for code ESLint cannot parse.
|
|
4
|
+
*
|
|
5
|
+
* ESLint's `max-lines` rule already caps the TypeScript/JavaScript sources at
|
|
6
|
+
* 600 lines (see `eslint.config.js`). This script applies the same budget to
|
|
7
|
+
* the runtime's C, C++, Rust and `#include`d `.inc` fragments plus shell
|
|
8
|
+
* scripts, so *every* code file stays focused and small.
|
|
9
|
+
*
|
|
10
|
+
* Vendored third-party sources that must ship verbatim (notably the
|
|
11
|
+
* single-header `stb_image.h`) are exempt: they are not ours to split.
|
|
12
|
+
*
|
|
13
|
+
* Run via `npm run lint` (or `tsx scripts/check-file-length.ts`).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
17
|
+
import { join, relative, resolve } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
const root = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
|
|
21
|
+
|
|
22
|
+
/** Maximum number of lines allowed in a single code file. */
|
|
23
|
+
const MAX_LINES = 600;
|
|
24
|
+
|
|
25
|
+
const CODE_EXTENSIONS = new Set([".c", ".h", ".cc", ".cpp", ".hpp", ".rs", ".inc", ".sh"]);
|
|
26
|
+
|
|
27
|
+
const IGNORED_DIRECTORIES = new Set([
|
|
28
|
+
".git",
|
|
29
|
+
"build",
|
|
30
|
+
"coverage",
|
|
31
|
+
"dist",
|
|
32
|
+
"node_modules",
|
|
33
|
+
"scratch",
|
|
34
|
+
"target",
|
|
35
|
+
"vendor",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
/** Vendored third-party sources that ship verbatim and are not ours to split. */
|
|
39
|
+
const VENDORED_FILES = new Set([join("runtime", "ext_gui", "stb_image.h")]);
|
|
40
|
+
|
|
41
|
+
/** Machine-generated files: they are reproduced from their sources by a script,
|
|
42
|
+
* so their size is not a sign of a module that needs splitting. */
|
|
43
|
+
const GENERATED_FILES = new Set([join("runtime", "ext_gui", "renderer_shaders_data.h")]);
|
|
44
|
+
|
|
45
|
+
interface Offense {
|
|
46
|
+
file: string;
|
|
47
|
+
lines: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function countLines(path: string): number {
|
|
51
|
+
const content = readFileSync(path, "utf8");
|
|
52
|
+
if (content.length === 0) return 0;
|
|
53
|
+
// A trailing newline terminates the last line instead of starting a new one.
|
|
54
|
+
const lines = content.split("\n");
|
|
55
|
+
return content.endsWith("\n") ? lines.length - 1 : lines.length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function collect(dir: string, offenses: Offense[]): void {
|
|
59
|
+
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
60
|
+
const full = join(dir, entry.name);
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
if (IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
63
|
+
// `runtime/lib` holds prebuilt archives, never sources.
|
|
64
|
+
if (relative(root, full) === join("runtime", "lib")) continue;
|
|
65
|
+
collect(full, offenses);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!entry.isFile()) continue;
|
|
69
|
+
const dot = entry.name.lastIndexOf(".");
|
|
70
|
+
if (dot < 0 || !CODE_EXTENSIONS.has(entry.name.slice(dot))) continue;
|
|
71
|
+
const file = relative(root, full);
|
|
72
|
+
if (VENDORED_FILES.has(file) || GENERATED_FILES.has(file)) continue;
|
|
73
|
+
const lines = countLines(full);
|
|
74
|
+
if (lines > MAX_LINES) offenses.push({ file, lines });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const offenses: Offense[] = [];
|
|
79
|
+
collect(root, offenses);
|
|
80
|
+
|
|
81
|
+
if (offenses.length > 0) {
|
|
82
|
+
console.error(`xbintsc: code files must stay under ${MAX_LINES} lines - split them by responsibility:`);
|
|
83
|
+
for (const offense of offenses.sort((a, b) => b.lines - a.lines)) {
|
|
84
|
+
console.error(` ${offense.file}: ${offense.lines} lines`);
|
|
85
|
+
}
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(`xbintsc: all code files are within ${MAX_LINES} lines`);
|
package/src/cli/hints.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation pointers for diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* Every failure printed by the CLI ends with a `hint:` line naming the document
|
|
5
|
+
* that explains what to do next. The messages already say what is wrong; these
|
|
6
|
+
* lines say where to read more, which is what keeps an agent (or a person) from
|
|
7
|
+
* re-deriving the language subset one failed build at a time.
|
|
8
|
+
*
|
|
9
|
+
* The paths are relative to the repository root, so they stay clickable in
|
|
10
|
+
* GitHub's rendered output and on a terminal that links paths.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { DiagnosticCode, type Diagnostic } from "../diagnostics/diagnostic.js";
|
|
14
|
+
|
|
15
|
+
/** Where a reader should go next. */
|
|
16
|
+
export interface DocumentationPointer {
|
|
17
|
+
/** Message printed after `hint:`. */
|
|
18
|
+
readonly text: string;
|
|
19
|
+
/** Repository-relative path to the document. */
|
|
20
|
+
readonly path: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const DOC_LANGUAGE_SUPPORT = "doc/ai/language-support.md";
|
|
24
|
+
export const DOC_EXTENSIONS = "doc/ai/extensions.md";
|
|
25
|
+
export const DOC_TROUBLESHOOTING = "doc/ai/troubleshooting.md";
|
|
26
|
+
export const DOC_CLI = "doc/ai/cli.md";
|
|
27
|
+
export const DOC_REQUIREMENTS = "doc/requirements.md";
|
|
28
|
+
|
|
29
|
+
const CHECKER_HINT: DocumentationPointer = {
|
|
30
|
+
text: "the compiler supports a subset of TypeScript; check what is implemented before rewriting",
|
|
31
|
+
path: DOC_LANGUAGE_SUPPORT,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const REQUIREMENT_HINT: DocumentationPointer = {
|
|
35
|
+
text: "xbintsc needs clang 16 or newer; run `xbintsc doctor` to see the resolved toolchain",
|
|
36
|
+
path: DOC_REQUIREMENTS,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Hint for a clang failure: a missing or too-old clang has a specific answer. */
|
|
40
|
+
export function toolchainPointer(message: string): DocumentationPointer {
|
|
41
|
+
const usesTypedPointers = /defined with type '\[.*i8\]\*' but expected 'i8\*'/.test(message);
|
|
42
|
+
if (usesTypedPointers || /No C compiler found/i.test(message)) return REQUIREMENT_HINT;
|
|
43
|
+
return {
|
|
44
|
+
text: "read the clang output above, then the toolchain section of the troubleshooting guide",
|
|
45
|
+
path: DOC_TROUBLESHOOTING,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A thrown failure the CLI can name, in table order. */
|
|
50
|
+
export interface ThrownFailure {
|
|
51
|
+
/** Tested against the thrown message. */
|
|
52
|
+
readonly match: RegExp;
|
|
53
|
+
readonly code: DiagnosticCode;
|
|
54
|
+
readonly pointer: DocumentationPointer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Failures the CLI reports as a message instead of a diagnostic. Matching on the
|
|
59
|
+
* text is what lets each one reach the document that actually explains it —
|
|
60
|
+
* a missing manifest and an unknown extension are both "cannot read something",
|
|
61
|
+
* but only one of them is answered by the CLI reference.
|
|
62
|
+
*/
|
|
63
|
+
const THROWN_FAILURES: readonly ThrownFailure[] = [
|
|
64
|
+
{
|
|
65
|
+
match: /native extension manifest/i,
|
|
66
|
+
code: DiagnosticCode.IOError,
|
|
67
|
+
pointer: {
|
|
68
|
+
text: "check the manifest path and its JSON; objects are relative to the manifest file",
|
|
69
|
+
path: DOC_EXTENSIONS,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
match: /Unknown extension/i,
|
|
74
|
+
code: DiagnosticCode.IOError,
|
|
75
|
+
pointer: {
|
|
76
|
+
text: "run `xbintsc help` for the flag, and see the built-in extensions this build ships",
|
|
77
|
+
path: DOC_EXTENSIONS,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
match: /config|JSON/i,
|
|
82
|
+
code: DiagnosticCode.IOError,
|
|
83
|
+
pointer: {
|
|
84
|
+
text: "check the project config path and its fields",
|
|
85
|
+
path: DOC_CLI,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
/** The failure a thrown error's message describes, if any. */
|
|
91
|
+
export function thrownFailure(message: string): ThrownFailure | undefined {
|
|
92
|
+
return THROWN_FAILURES.find((failure) => failure.match.test(message));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The completion for an unclassified thrown error: the toolchain's own wording
|
|
97
|
+
* is the only thing worth directing a reader to.
|
|
98
|
+
*/
|
|
99
|
+
export const UNEXPECTED_FAILURE_HINT: DocumentationPointer = {
|
|
100
|
+
text: "this failure has no dedicated guide; check the resolved toolchain and the source paths",
|
|
101
|
+
path: DOC_TROUBLESHOOTING,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The document that explains a diagnostic, or `undefined` for internal errors
|
|
106
|
+
* that no reader can act on.
|
|
107
|
+
*/
|
|
108
|
+
export function documentationFor(diagnostic: Diagnostic): DocumentationPointer | undefined {
|
|
109
|
+
switch (diagnostic.code) {
|
|
110
|
+
// Lexer, parser and binder report a precise location; the guidance is the
|
|
111
|
+
// same subset reference.
|
|
112
|
+
case DiagnosticCode.UnterminatedString:
|
|
113
|
+
case DiagnosticCode.UnterminatedTemplate:
|
|
114
|
+
case DiagnosticCode.UnterminatedComment:
|
|
115
|
+
case DiagnosticCode.InvalidCharacter:
|
|
116
|
+
case DiagnosticCode.InvalidNumber:
|
|
117
|
+
case DiagnosticCode.InvalidEscape:
|
|
118
|
+
case DiagnosticCode.UnexpectedToken:
|
|
119
|
+
case DiagnosticCode.ExpectedToken:
|
|
120
|
+
case DiagnosticCode.ExpectedIdentifier:
|
|
121
|
+
case DiagnosticCode.UnexpectedEof:
|
|
122
|
+
case DiagnosticCode.InvalidAssignmentTarget:
|
|
123
|
+
case DiagnosticCode.DuplicateDefault:
|
|
124
|
+
case DiagnosticCode.InvalidTypeSyntax:
|
|
125
|
+
case DiagnosticCode.DuplicateIdentifier:
|
|
126
|
+
case DiagnosticCode.CannotFindName:
|
|
127
|
+
case DiagnosticCode.IllegalRedeclaration:
|
|
128
|
+
case DiagnosticCode.CannotAssignToConst:
|
|
129
|
+
case DiagnosticCode.UnsupportedFeature:
|
|
130
|
+
return CHECKER_HINT;
|
|
131
|
+
|
|
132
|
+
// Every codegen and bundler error carries its own message; the subset page
|
|
133
|
+
// is what separates "you wrote unsupported code" from "you wrote a bug".
|
|
134
|
+
case DiagnosticCode.CodegenError:
|
|
135
|
+
return CHECKER_HINT;
|
|
136
|
+
|
|
137
|
+
case DiagnosticCode.ModuleNotFound:
|
|
138
|
+
return {
|
|
139
|
+
text: "Node modules need the `node` extension (`--ext node`); third-party npm packages are not supported",
|
|
140
|
+
path: DOC_EXTENSIONS,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
case DiagnosticCode.IOError:
|
|
144
|
+
return {
|
|
145
|
+
text: "check the path and the project config; paths in xbintsc.config.json resolve against that file",
|
|
146
|
+
path: DOC_TROUBLESHOOTING,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
case DiagnosticCode.ToolchainError:
|
|
150
|
+
return toolchainPointer(diagnostic.message);
|
|
151
|
+
|
|
152
|
+
case DiagnosticCode.IncrementalCacheError:
|
|
153
|
+
return {
|
|
154
|
+
text: "the incremental cache could not be trusted; retry with `--force` or remove the cache directory",
|
|
155
|
+
path: DOC_CLI,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// The checker codes are defined but never emitted today.
|
|
159
|
+
case DiagnosticCode.TypeMismatch:
|
|
160
|
+
case DiagnosticCode.NotCallable:
|
|
161
|
+
case DiagnosticCode.PropertyNotFound:
|
|
162
|
+
case DiagnosticCode.ArgumentCountMismatch:
|
|
163
|
+
return CHECKER_HINT;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Render one `hint:` line, or `undefined` when the code has no document. */
|
|
168
|
+
export function formatDocumentationHint(diagnostic: Diagnostic): string | undefined {
|
|
169
|
+
const pointer = documentationFor(diagnostic);
|
|
170
|
+
if (!pointer) return undefined;
|
|
171
|
+
return `hint: ${pointer.text} — see ${pointer.path}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* One hint line per failing diagnostic, in first-seen order and without
|
|
176
|
+
* repeating a document the reader has already been sent to.
|
|
177
|
+
*/
|
|
178
|
+
export function documentationHints(diagnostics: readonly Diagnostic[]): string[] {
|
|
179
|
+
const lines: string[] = [];
|
|
180
|
+
const seen = new Set<string>();
|
|
181
|
+
for (const diagnostic of diagnostics) {
|
|
182
|
+
if (diagnostic.category !== "error") continue;
|
|
183
|
+
const line = formatDocumentationHint(diagnostic);
|
|
184
|
+
if (line === undefined || seen.has(line)) continue;
|
|
185
|
+
seen.add(line);
|
|
186
|
+
lines.push(line);
|
|
187
|
+
}
|
|
188
|
+
return lines;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Render one hint line for an already-classified thrown failure. */
|
|
192
|
+
export function formatThrownHint(pointer: DocumentationPointer): string {
|
|
193
|
+
return `hint: ${pointer.text} — see ${pointer.path}`;
|
|
194
|
+
}
|