xbintsc 0.3.15 → 0.3.16

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.
Files changed (156) hide show
  1. package/dist/src/cli/main.js +4 -4
  2. package/dist/src/cli/main.js.map +1 -1
  3. package/dist/src/codegen/generator/state.js +8 -0
  4. package/dist/src/codegen/generator/state.js.map +1 -1
  5. package/dist/src/codegen/generator/tables.js +8 -0
  6. package/dist/src/codegen/generator/tables.js.map +1 -1
  7. package/dist/src/driver/bundler/graph.d.ts +8 -0
  8. package/dist/src/driver/bundler/graph.js +85 -0
  9. package/dist/src/driver/bundler/graph.js.map +1 -0
  10. package/dist/src/driver/bundler/merge.d.ts +11 -0
  11. package/dist/src/driver/bundler/merge.js +288 -0
  12. package/dist/src/driver/bundler/merge.js.map +1 -0
  13. package/dist/src/driver/bundler/resolve.d.ts +12 -0
  14. package/dist/src/driver/bundler/resolve.js +246 -0
  15. package/dist/src/driver/bundler/resolve.js.map +1 -0
  16. package/dist/src/driver/bundler/symbols.d.ts +15 -0
  17. package/dist/src/driver/bundler/symbols.js +51 -0
  18. package/dist/src/driver/bundler/symbols.js.map +1 -0
  19. package/dist/src/driver/bundler/types.d.ts +36 -0
  20. package/dist/src/driver/bundler/types.js +5 -0
  21. package/dist/src/driver/bundler/types.js.map +1 -0
  22. package/dist/src/driver/modules.d.ts +9 -16
  23. package/dist/src/driver/modules.js +8 -644
  24. package/dist/src/driver/modules.js.map +1 -1
  25. package/dist/src/extensions/catalog.js +2 -1
  26. package/dist/src/extensions/catalog.js.map +1 -1
  27. package/dist/src/extensions/gui/index.d.ts +24 -0
  28. package/dist/src/extensions/gui/index.js +101 -0
  29. package/dist/src/extensions/gui/index.js.map +1 -0
  30. package/dist/tests/e2e/compile-basics.test.d.ts +4 -0
  31. package/dist/tests/e2e/compile-basics.test.js +134 -0
  32. package/dist/tests/e2e/compile-basics.test.js.map +1 -0
  33. package/dist/tests/e2e/compile-builtins.test.d.ts +4 -0
  34. package/dist/tests/e2e/compile-builtins.test.js +191 -0
  35. package/dist/tests/e2e/compile-builtins.test.js.map +1 -0
  36. package/dist/tests/e2e/compile-control-flow.test.d.ts +4 -0
  37. package/dist/tests/e2e/compile-control-flow.test.js +190 -0
  38. package/dist/tests/e2e/compile-control-flow.test.js.map +1 -0
  39. package/dist/tests/e2e/compile-modules.test.d.ts +4 -0
  40. package/dist/tests/e2e/compile-modules.test.js +48 -0
  41. package/dist/tests/e2e/compile-modules.test.js.map +1 -0
  42. package/dist/tests/e2e/compile-objects-classes.test.d.ts +4 -0
  43. package/dist/tests/e2e/compile-objects-classes.test.js +362 -0
  44. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -0
  45. package/dist/tests/e2e/differential-helpers.d.ts +12 -0
  46. package/dist/tests/e2e/differential-helpers.js +16 -0
  47. package/dist/tests/e2e/differential-helpers.js.map +1 -0
  48. package/dist/tests/e2e/differential-language.test.d.ts +4 -0
  49. package/dist/tests/e2e/{differential.test.js → differential-language.test.js} +5 -287
  50. package/dist/tests/e2e/differential-language.test.js.map +1 -0
  51. package/dist/tests/e2e/differential-symbols.test.d.ts +4 -0
  52. package/dist/tests/e2e/differential-symbols.test.js +92 -0
  53. package/dist/tests/e2e/differential-symbols.test.js.map +1 -0
  54. package/dist/tests/e2e/differential-values.test.d.ts +4 -0
  55. package/dist/tests/e2e/differential-values.test.js +197 -0
  56. package/dist/tests/e2e/differential-values.test.js.map +1 -0
  57. package/dist/tests/e2e/gui.test.d.ts +12 -0
  58. package/dist/tests/e2e/gui.test.js +493 -0
  59. package/dist/tests/e2e/gui.test.js.map +1 -0
  60. package/dist/tests/e2e/runtime-coverage-buffer.test.d.ts +4 -0
  61. package/dist/tests/e2e/runtime-coverage-buffer.test.js +97 -0
  62. package/dist/tests/e2e/runtime-coverage-buffer.test.js.map +1 -0
  63. package/dist/tests/e2e/runtime-coverage-globals.test.d.ts +4 -0
  64. package/dist/tests/e2e/runtime-coverage-globals.test.js +149 -0
  65. package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -0
  66. package/dist/tests/e2e/runtime-coverage-node-modules.test.d.ts +4 -0
  67. package/dist/tests/e2e/runtime-coverage-node-modules.test.js +177 -0
  68. package/dist/tests/e2e/runtime-coverage-node-modules.test.js.map +1 -0
  69. package/dist/tests/e2e/runtime-coverage-streams.test.d.ts +4 -0
  70. package/dist/tests/e2e/runtime-coverage-streams.test.js +161 -0
  71. package/dist/tests/e2e/runtime-coverage-streams.test.js.map +1 -0
  72. package/dist/tests/e2e/runtime-coverage-system.test.d.ts +4 -0
  73. package/dist/tests/e2e/runtime-coverage-system.test.js +91 -0
  74. package/dist/tests/e2e/runtime-coverage-system.test.js.map +1 -0
  75. package/dist/tests/e2e/timers.test.d.ts +7 -0
  76. package/dist/tests/e2e/timers.test.js +63 -0
  77. package/dist/tests/e2e/timers.test.js.map +1 -0
  78. package/package.json +2 -1
  79. package/runtime/ext_gui/css.cpp +743 -0
  80. package/runtime/ext_gui/css.h +98 -0
  81. package/runtime/ext_gui/document.cpp +264 -0
  82. package/runtime/ext_gui/document.h +89 -0
  83. package/runtime/ext_gui/dom.cpp +472 -0
  84. package/runtime/ext_gui/dom.h +71 -0
  85. package/runtime/ext_gui/gui.cpp +457 -0
  86. package/runtime/ext_gui/gui_engine.h +81 -0
  87. package/runtime/ext_gui/image.cpp +125 -0
  88. package/runtime/ext_gui/image.h +42 -0
  89. package/runtime/ext_gui/layout.cpp +748 -0
  90. package/runtime/ext_gui/layout.h +93 -0
  91. package/runtime/ext_gui/paint.cpp +137 -0
  92. package/runtime/ext_gui/paint.h +75 -0
  93. package/runtime/ext_gui/renderer.cpp +959 -0
  94. package/runtime/ext_gui/renderer.h +68 -0
  95. package/runtime/ext_gui/stb_image.h +7988 -0
  96. package/runtime/ext_gui/style.cpp +771 -0
  97. package/runtime/ext_gui/style.h +117 -0
  98. package/runtime/ext_gui/text.cpp +378 -0
  99. package/runtime/ext_gui/text.h +78 -0
  100. package/runtime/ext_gui/window.cpp +405 -0
  101. package/runtime/ext_node/fs/fs_common.h +13 -527
  102. package/runtime/ext_node/fs/parts/buffer-bridge.inc +6 -0
  103. package/runtime/ext_node/fs/parts/builtins.inc +69 -0
  104. package/runtime/ext_node/fs/parts/dir.inc +47 -0
  105. package/runtime/ext_node/fs/parts/encoding.inc +186 -0
  106. package/runtime/ext_node/fs/parts/errors.inc +66 -0
  107. package/runtime/ext_node/fs/parts/flags.inc +39 -0
  108. package/runtime/ext_node/fs/parts/platform.inc +86 -0
  109. package/runtime/ext_node/fs/parts/promisify.inc +15 -0
  110. package/runtime/ext_node/fs/parts/time.inc +9 -0
  111. package/runtime/rt.h +33 -0
  112. package/runtime/xt_builtins.c +4 -0
  113. package/runtime/xt_containers/access.inc +168 -0
  114. package/runtime/xt_containers/arrays.inc +108 -0
  115. package/runtime/xt_containers/boxes.inc +11 -0
  116. package/runtime/xt_containers/objects.inc +326 -0
  117. package/runtime/xt_containers.c +8 -618
  118. package/runtime/xt_io/base64.inc +87 -0
  119. package/runtime/xt_io/console.inc +119 -0
  120. package/runtime/xt_io/exception.inc +142 -0
  121. package/runtime/xt_io/inspect.inc +212 -0
  122. package/runtime/xt_io.c +8 -553
  123. package/runtime/xt_loop.c +262 -24
  124. package/runtime/xt_regex/api.inc +58 -0
  125. package/runtime/xt_regex/helpers.inc +68 -0
  126. package/runtime/xt_regex/matcher.inc +248 -0
  127. package/runtime/xt_regex/parser.inc +236 -0
  128. package/runtime/xt_regex/types.inc +58 -0
  129. package/runtime/xt_regex.h +9 -672
  130. package/runtime/xt_values/arithmetic.inc +150 -0
  131. package/runtime/xt_values/comparison.inc +112 -0
  132. package/runtime/xt_values/constructors.inc +41 -0
  133. package/runtime/xt_values/conversions.inc +115 -0
  134. package/runtime/xt_values/number-format.inc +180 -0
  135. package/runtime/xt_values/strings.inc +22 -0
  136. package/runtime/xt_values.c +10 -626
  137. package/scripts/build-gui.ts +317 -0
  138. package/src/cli/main.ts +4 -3
  139. package/src/codegen/generator/state.ts +8 -0
  140. package/src/codegen/generator/tables.ts +8 -0
  141. package/src/driver/bundler/graph.ts +95 -0
  142. package/src/driver/bundler/merge.ts +312 -0
  143. package/src/driver/bundler/resolve.ts +240 -0
  144. package/src/driver/bundler/symbols.ts +55 -0
  145. package/src/driver/bundler/types.ts +37 -0
  146. package/src/driver/modules.ts +9 -693
  147. package/src/extensions/catalog.ts +2 -1
  148. package/src/extensions/gui/index.ts +107 -0
  149. package/dist/tests/e2e/compile.test.d.ts +0 -8
  150. package/dist/tests/e2e/compile.test.js +0 -893
  151. package/dist/tests/e2e/compile.test.js.map +0 -1
  152. package/dist/tests/e2e/differential.test.d.ts +0 -7
  153. package/dist/tests/e2e/differential.test.js.map +0 -1
  154. package/dist/tests/e2e/runtime-coverage.test.d.ts +0 -11
  155. package/dist/tests/e2e/runtime-coverage.test.js +0 -646
  156. package/dist/tests/e2e/runtime-coverage.test.js.map +0 -1
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build the GUI extension archive:
4
+ *
5
+ * runtime/lib/<os>-<arch>/gui.a
6
+ *
7
+ * The archive bundles the engine (`runtime/ext_gui/*.cpp`) together with a
8
+ * statically built SDL3, so a released compiler links GUI programs with no
9
+ * external dependency beyond the OS frameworks declared by
10
+ * `src/extensions/gui/index.ts`.
11
+ *
12
+ * Steps:
13
+ * 1. fetch the pinned SDL3 source into `vendor/SDL` (gitignored),
14
+ * 2. build `libSDL3.a` with CMake,
15
+ * 2b. fetch + build static FreeType and HarfBuzz for text,
16
+ * 3. compile the engine translation units,
17
+ * 4. merge everything into `gui.a` (libtool on macOS, `ar -M` elsewhere;
18
+ * `gui.lib` on Windows, built from MSVC-compatible COFF objects).
19
+ *
20
+ * Environment:
21
+ * xbintsc_CXX C++ compiler override (default: clang++/c++/g++)
22
+ * xbintsc_CMAKE CMake override (default: a vendored CMake, then `cmake`)
23
+ * xbintsc_AR archiver override (default: llvm-ar, then ar)
24
+ * SDL3_TAG SDL3 git tag to check out (default: release-3.2.10)
25
+ * FREETYPE_VERSION FreeType version (default: 2.13.3)
26
+ * HARFBUZZ_VERSION HarfBuzz version (default: 10.1.0)
27
+ *
28
+ * On Windows the engine is compiled with `clang++` against the MSVC ABI (run
29
+ * from a Visual Studio developer prompt so `INCLUDE`/`LIB` are set) and merged
30
+ * into a COFF `gui.lib` with `llvm-ar`.
31
+ */
32
+
33
+ import { existsSync, mkdirSync, readdirSync, renameSync, rmSync } from "node:fs";
34
+ import { spawnSync, type SpawnSyncReturns } from "node:child_process";
35
+ import { dirname, join, resolve } from "node:path";
36
+ import { fileURLToPath } from "node:url";
37
+ import { platformSlug } from "../src/driver/paths.js";
38
+
39
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
40
+ const runtimeDir = join(root, "runtime");
41
+ const engineDir = join(runtimeDir, "ext_gui");
42
+ const vendorDir = join(root, "vendor");
43
+ const sdlSrc = join(vendorDir, "SDL");
44
+ const sdlBuild = join(sdlSrc, "build");
45
+ const freetypeSrc = join(vendorDir, "freetype");
46
+ const freetypeBuild = join(vendorDir, "freetype-build");
47
+ const harfbuzzSrc = join(vendorDir, "harfbuzz");
48
+ const harfbuzzBuild = join(vendorDir, "harfbuzz-build");
49
+ const objDir = join(root, "build", "gui-obj");
50
+ const outDir = join(runtimeDir, "lib", platformSlug());
51
+
52
+ const isWindows = process.platform === "win32";
53
+ const objectSuffix = isWindows ? ".obj" : ".o";
54
+ const output = join(outDir, isWindows ? "gui.lib" : "gui.a");
55
+
56
+ const SDL_TAG = process.env.SDL3_TAG || "release-3.2.10";
57
+ const FREETYPE_VERSION = process.env.FREETYPE_VERSION || "2.13.3";
58
+ const HARFBUZZ_VERSION = process.env.HARFBUZZ_VERSION || "10.1.0";
59
+ const FREETYPE_URL = `https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.xz`;
60
+ const HARFBUZZ_URL = `https://github.com/harfbuzz/harfbuzz/releases/download/${HARFBUZZ_VERSION}/harfbuzz-${HARFBUZZ_VERSION}.tar.xz`;
61
+
62
+ function fail(message: string): never {
63
+ console.error(`xbintsc: ${message}`);
64
+ process.exit(1);
65
+ }
66
+
67
+ function run(
68
+ command: string,
69
+ args: readonly string[],
70
+ options: { input?: string; quietStderr?: boolean } = {},
71
+ ): SpawnSyncReturns<string> {
72
+ const quiet = options.quietStderr === true;
73
+ const result = spawnSync(command, args, {
74
+ stdio: options.input !== undefined ? ["pipe", "inherit", "inherit"] : quiet ? ["ignore", "inherit", "pipe"] : "inherit",
75
+ encoding: "utf8",
76
+ input: options.input,
77
+ });
78
+ if (result.status !== 0) {
79
+ if (quiet && result.stderr) process.stderr.write(result.stderr);
80
+ fail(`${command} ${args.join(" ")} failed with status ${result.status ?? "?"}`);
81
+ }
82
+ return result;
83
+ }
84
+
85
+ function isRunnable(candidate: string): boolean {
86
+ return !spawnSync(candidate, ["--version"], { encoding: "utf8" }).error;
87
+ }
88
+
89
+ function firstAvailable(candidates: readonly (string | undefined)[]): string | undefined {
90
+ for (const candidate of candidates) {
91
+ if (candidate && isRunnable(candidate)) return candidate;
92
+ }
93
+ return undefined;
94
+ }
95
+
96
+ /** Vendored CMake from `vendor/tools`, then `cmake` on PATH. */
97
+ function findCmake(): string {
98
+ const candidates: string[] = [];
99
+ if (process.env.xbintsc_CMAKE) candidates.push(process.env.xbintsc_CMAKE);
100
+ if (existsSync(join(vendorDir, "tools"))) {
101
+ for (const entry of readdirSync(join(vendorDir, "tools"))) {
102
+ if (!entry.startsWith("cmake-")) continue;
103
+ candidates.push(join(vendorDir, "tools", entry, "CMake.app", "Contents", "bin", "cmake"));
104
+ candidates.push(join(vendorDir, "tools", entry, "bin", "cmake"));
105
+ }
106
+ }
107
+ candidates.push("cmake");
108
+ const cmake = firstAvailable(candidates);
109
+ if (!cmake) fail("no CMake found; install it or set xbintsc_CMAKE");
110
+ return cmake;
111
+ }
112
+
113
+ function findCxx(): string {
114
+ const cxx = firstAvailable([process.env.xbintsc_CXX, "clang++", "c++", "g++"]);
115
+ if (!cxx) fail("no C++ compiler found; install clang++ or set xbintsc_CXX");
116
+ return cxx;
117
+ }
118
+
119
+ function findArchiver(): string {
120
+ const ar = firstAvailable([process.env.xbintsc_AR, "llvm-ar", "ar"]);
121
+ if (!ar) fail("no archiver found; set xbintsc_AR to llvm-ar or ar");
122
+ return ar;
123
+ }
124
+
125
+ /** First of `candidates` that exists on disk (CMake generators place static
126
+ * libraries either at the build root or under `Release/`). */
127
+ function firstExisting(candidates: readonly string[]): string | undefined {
128
+ for (const candidate of candidates) {
129
+ if (existsSync(candidate)) return candidate;
130
+ }
131
+ return undefined;
132
+ }
133
+
134
+ /** Locate a library CMake produced, accepting the POSIX and MSVC names and
135
+ * falling back to a version-suffixed MSVC name (e.g. `freetype-2.13.3.lib`). */
136
+ function builtLibrary(buildDir: string, names: readonly string[], pattern?: RegExp): string | undefined {
137
+ const candidates: string[] = [];
138
+ for (const name of names) {
139
+ candidates.push(join(buildDir, name));
140
+ candidates.push(join(buildDir, "Release", name));
141
+ candidates.push(join(buildDir, "Debug", name));
142
+ }
143
+ const exact = firstExisting(candidates);
144
+ if (exact || !pattern) return exact;
145
+ for (const dir of [buildDir, join(buildDir, "Release"), join(buildDir, "Debug")]) {
146
+ if (!existsSync(dir)) continue;
147
+ for (const entry of readdirSync(dir)) {
148
+ if (pattern.test(entry)) return join(dir, entry);
149
+ }
150
+ }
151
+ return undefined;
152
+ }
153
+
154
+ function sdlArchive(): string | undefined {
155
+ return builtLibrary(sdlBuild, ["libSDL3.a", "SDL3-static.lib", "SDL3.lib"], /^SDL3[-\w.]*\.lib$/);
156
+ }
157
+ function freetypeArchive(): string | undefined {
158
+ return builtLibrary(freetypeBuild, ["libfreetype.a", "freetype.lib"], /^freetype[-\d.]*\.lib$/);
159
+ }
160
+ function harfbuzzArchive(): string | undefined {
161
+ return builtLibrary(harfbuzzBuild, ["libharfbuzz.a", "harfbuzz.lib"], /^harfbuzz[-\d.]*\.lib$/);
162
+ }
163
+
164
+ /* -- 1. SDL3 source ------------------------------------------------------- */
165
+
166
+ if (!existsSync(join(sdlSrc, "CMakeLists.txt"))) {
167
+ console.log(`xbintsc: fetching SDL3 ${SDL_TAG} into vendor/SDL`);
168
+ mkdirSync(vendorDir, { recursive: true });
169
+ run("git", ["clone", "--depth", "1", "--branch", SDL_TAG, "https://github.com/libsdl-org/SDL.git", sdlSrc]);
170
+ }
171
+
172
+ /* -- 2. static SDL3 ------------------------------------------------------- */
173
+
174
+ const existingSdlArchive = sdlArchive();
175
+ if (!existingSdlArchive) {
176
+ const cmake = findCmake();
177
+ console.log(`xbintsc: building static SDL3 with ${cmake}`);
178
+ const configureArgs = [
179
+ "-S", sdlSrc,
180
+ "-B", sdlBuild,
181
+ "-DCMAKE_BUILD_TYPE=Release",
182
+ "-DSDL_SHARED=OFF",
183
+ "-DSDL_STATIC=ON",
184
+ "-DSDL_TESTS=OFF",
185
+ "-DSDL_EXAMPLES=OFF",
186
+ ];
187
+ run(cmake, configureArgs);
188
+ run(cmake, ["--build", sdlBuild, "--config", "Release", "--target", "SDL3-static", "--parallel"]);
189
+ } else {
190
+ console.log(`xbintsc: reusing existing ${existingSdlArchive}`);
191
+ }
192
+
193
+ /* -- 2b. FreeType + HarfBuzz (text) --------------------------------------- */
194
+
195
+ /** Download a release tarball into `vendor/` and extract it under `name`.
196
+ * Release tarballs are used (not git clones) because they ship generated
197
+ * sources, so no ragel/autotools step is needed. */
198
+ function ensureTarballSource(name: string, url: string, extractedName: string): string {
199
+ const destination = join(vendorDir, name);
200
+ if (existsSync(join(destination, "CMakeLists.txt"))) return destination;
201
+ const tarball = join(vendorDir, `${name}.tar.xz`);
202
+ console.log(`xbintsc: fetching ${name} from ${url}`);
203
+ mkdirSync(vendorDir, { recursive: true });
204
+ run("curl", ["-sL", "--fail", "-o", tarball, url]);
205
+ run("tar", ["-xf", tarball, "-C", vendorDir]);
206
+ const extracted = join(vendorDir, extractedName);
207
+ if (!existsSync(extracted)) fail(`expected ${extractedName} after extracting ${name}`);
208
+ rmSync(destination, { recursive: true, force: true });
209
+ renameSync(extracted, destination);
210
+ rmSync(tarball, { force: true });
211
+ return destination;
212
+ }
213
+
214
+ ensureTarballSource("freetype", FREETYPE_URL, `freetype-${FREETYPE_VERSION}`);
215
+ ensureTarballSource("harfbuzz", HARFBUZZ_URL, `harfbuzz-${HARFBUZZ_VERSION}`);
216
+
217
+ if (!existsSync(freetypeArchive() ?? "")) {
218
+ const cmake = findCmake();
219
+ console.log("xbintsc: building static FreeType");
220
+ run(cmake, [
221
+ "-S", freetypeSrc,
222
+ "-B", freetypeBuild,
223
+ "-DCMAKE_BUILD_TYPE=Release",
224
+ "-DBUILD_SHARED_LIBS=OFF",
225
+ /* We only need TrueType/OpenType outlines; skip the optional codecs. */
226
+ "-DFT_DISABLE_ZLIB=TRUE",
227
+ "-DFT_DISABLE_BZIP2=TRUE",
228
+ "-DFT_DISABLE_PNG=TRUE",
229
+ "-DFT_DISABLE_HARFBUZZ=TRUE",
230
+ "-DFT_DISABLE_BROTLI=TRUE",
231
+ ]);
232
+ run(cmake, ["--build", freetypeBuild, "--config", "Release", "--parallel"]);
233
+ } else {
234
+ console.log(`xbintsc: reusing existing ${freetypeArchive()}`);
235
+ }
236
+
237
+ if (!existsSync(harfbuzzArchive() ?? "")) {
238
+ const cmake = findCmake();
239
+ console.log("xbintsc: building static HarfBuzz");
240
+ run(cmake, [
241
+ "-S", harfbuzzSrc,
242
+ "-B", harfbuzzBuild,
243
+ "-DCMAKE_BUILD_TYPE=Release",
244
+ "-DBUILD_SHARED_LIBS=OFF",
245
+ "-DHB_BUILD_UTILS=OFF",
246
+ "-DHB_BUILD_TESTS=OFF",
247
+ "-DHB_BUILD_SUBSET=OFF",
248
+ "-DHB_HAVE_FREETYPE=OFF",
249
+ "-DHB_HAVE_GLIB=OFF",
250
+ "-DHB_HAVE_ICU=OFF",
251
+ "-DHB_HAVE_GRAPHITE2=OFF",
252
+ ]);
253
+ run(cmake, ["--build", harfbuzzBuild, "--config", "Release", "--parallel"]);
254
+ } else {
255
+ console.log(`xbintsc: reusing existing ${harfbuzzArchive()}`);
256
+ }
257
+
258
+ /* -- 3. engine objects ---------------------------------------------------- */
259
+
260
+ const cxx = findCxx();
261
+ mkdirSync(objDir, { recursive: true });
262
+ const includeFlags = [
263
+ "-I", runtimeDir,
264
+ "-I", join(sdlSrc, "include"),
265
+ "-I", join(freetypeSrc, "include"),
266
+ "-I", join(freetypeBuild, "include"),
267
+ "-I", join(harfbuzzSrc, "src"),
268
+ ];
269
+ const sources = readdirSync(engineDir)
270
+ .filter((entry) => entry.endsWith(".cpp"))
271
+ .sort();
272
+
273
+ const objects: string[] = [];
274
+ const cxxFlags = ["-std=c++17", "-O2", "-Wall", "-Wextra", ...includeFlags];
275
+ /* The MSVC ABI rejects `-fPIC`; position independence is the default elsewhere. */
276
+ if (!isWindows) cxxFlags.splice(3, 0, "-fPIC");
277
+ for (const source of sources) {
278
+ const object = join(objDir, source.replace(/\.cpp$/, objectSuffix));
279
+ console.log(`xbintsc: compiling ${source}`);
280
+ run(cxx, [...cxxFlags, "-c", join(engineDir, source), "-o", object]);
281
+ objects.push(object);
282
+ }
283
+
284
+ /* -- 4. merge into gui.a / gui.lib ---------------------------------------- */
285
+
286
+ const sdl = sdlArchive();
287
+ const freetype = freetypeArchive();
288
+ const harfbuzz = harfbuzzArchive();
289
+ if (!sdl || !freetype || !harfbuzz) {
290
+ fail("one of the SDL3/FreeType/HarfBuzz archives was not found after building");
291
+ }
292
+ const libraries = [sdl, freetype, harfbuzz];
293
+
294
+ mkdirSync(outDir, { recursive: true });
295
+ rmSync(output, { force: true });
296
+ if (process.platform === "darwin") {
297
+ /* libtool warns about the empty object files SDL3's archive carries; hide it
298
+ * unless the merge actually fails. */
299
+ run("libtool", ["-static", "-o", output, ...objects, ...libraries], {
300
+ quietStderr: true,
301
+ });
302
+ } else {
303
+ /* `ar -M` (GNU/LLVM) merges archives through an MRI script. Windows paths are
304
+ * normalised to `/` so the MRI parser does not read `\` as an escape. */
305
+ const mri = (path: string): string => path.replace(/\\/g, "/");
306
+ const ar = findArchiver();
307
+ const script = [
308
+ `create ${mri(output)}`,
309
+ ...objects.map((object) => `addmod ${mri(object)}`),
310
+ ...libraries.map((library) => `addlib ${mri(library)}`),
311
+ "save",
312
+ "end",
313
+ ].join("\n");
314
+ run(ar, ["-M"], { input: script });
315
+ }
316
+
317
+ console.log(`xbintsc: wrote ${output}`);
package/src/cli/main.ts CHANGED
@@ -24,7 +24,6 @@ import { resolveToolchain } from "../driver/toolchain-provider.js";
24
24
  import { realRunner } from "../driver/toolchain.js";
25
25
  import { createDefaultRegistry, type ExtensionRegistry } from "../extensions/registry.js";
26
26
  import { bundledExtensions } from "../extensions/catalog.js";
27
- import { nodeExtension } from "../extensions/node/index.js";
28
27
  import { nativeExtensionFromManifest } from "../extensions/native.js";
29
28
 
30
29
  export interface CliIo {
@@ -100,9 +99,11 @@ function buildRegistry(flags: Map<string, string | boolean>): ExtensionRegistry
100
99
  const registry = createDefaultRegistry();
101
100
  const requested = flags.get("ext");
102
101
  if (typeof requested === "string") {
102
+ const bundled = bundledExtensions();
103
103
  for (const name of requested.split(",").map((n) => n.trim()).filter(Boolean)) {
104
- if (name === "node") registry.register(nodeExtension);
105
- else throw new Error(`Unknown extension '${name}'`);
104
+ const extension = bundled.find((candidate) => candidate.name === name);
105
+ if (!extension) throw new Error(`Unknown extension '${name}'`);
106
+ registry.register(extension);
106
107
  }
107
108
  }
108
109
  // Each `--ext-native` value is a manifest path describing a pre-built C++ or
@@ -215,6 +215,10 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
215
215
  "declare i64 @xt_decode_uri(i32, i64*)",
216
216
  "declare i64 @xt_btoa(i32, i64*)",
217
217
  "declare i64 @xt_atob(i32, i64*)",
218
+ "declare i64 @xt_set_timeout(i32, i64*)",
219
+ "declare i64 @xt_clear_timeout(i32, i64*)",
220
+ "declare i64 @xt_set_interval(i32, i64*)",
221
+ "declare i64 @xt_clear_interval(i32, i64*)",
218
222
  "declare i64 @xt_number_ctor(i32, i64*)",
219
223
  "declare i64 @xt_bigint_ctor(i32, i64*)",
220
224
  "declare i64 @xt_bigint_static(i64, i32, i64*)",
@@ -246,6 +250,10 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
246
250
  "declare i64 @xt_builtin_value_encodeURI(i64, i64, i32, i64*)",
247
251
  "declare i64 @xt_builtin_value_decodeURIComponent(i64, i64, i32, i64*)",
248
252
  "declare i64 @xt_builtin_value_decodeURI(i64, i64, i32, i64*)",
253
+ "declare i64 @xt_builtin_value_setTimeout(i64, i64, i32, i64*)",
254
+ "declare i64 @xt_builtin_value_clearTimeout(i64, i64, i32, i64*)",
255
+ "declare i64 @xt_builtin_value_setInterval(i64, i64, i32, i64*)",
256
+ "declare i64 @xt_builtin_value_clearInterval(i64, i64, i32, i64*)",
249
257
  "declare i64 @xt_builtin_value_number(i64, i64, i32, i64*)",
250
258
  "declare i64 @xt_builtin_value_string(i64, i64, i32, i64*)",
251
259
  "declare i64 @xt_builtin_value_boolean(i64, i64, i32, i64*)",
@@ -123,6 +123,10 @@ export const GLOBAL_FUNCTIONS: Record<string, string> = {
123
123
  decodeURI: "xt_decode_uri",
124
124
  btoa: "xt_btoa",
125
125
  atob: "xt_atob",
126
+ setTimeout: "xt_set_timeout",
127
+ clearTimeout: "xt_clear_timeout",
128
+ setInterval: "xt_set_interval",
129
+ clearInterval: "xt_clear_interval",
126
130
  Number: "xt_number_ctor",
127
131
  String: "xt_string_ctor",
128
132
  Boolean: "xt_boolean_ctor",
@@ -142,6 +146,10 @@ export const BUILTIN_FUNCTION_VALUES: Record<string, string> = {
142
146
  encodeURI: "xt_builtin_value_encodeURI",
143
147
  decodeURIComponent: "xt_builtin_value_decodeURIComponent",
144
148
  decodeURI: "xt_builtin_value_decodeURI",
149
+ setTimeout: "xt_builtin_value_setTimeout",
150
+ clearTimeout: "xt_builtin_value_clearTimeout",
151
+ setInterval: "xt_builtin_value_setInterval",
152
+ clearInterval: "xt_builtin_value_clearInterval",
145
153
  Number: "xt_builtin_value_number",
146
154
  String: "xt_builtin_value_string",
147
155
  Boolean: "xt_builtin_value_boolean",
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Import-graph loading: parse and bind the entry module and every module it
3
+ * reaches, in dependency order.
4
+ */
5
+
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { dirname } from "node:path";
8
+ import { SyntaxKind, type ExportDeclaration, type ImportDeclaration, type Statement } from "../../ast/nodes.js";
9
+ import { bind } from "../../binder/binder.js";
10
+ import { DiagnosticBag, DiagnosticCode } from "../../diagnostics/diagnostic.js";
11
+ import { SourceFile } from "../../diagnostics/source.js";
12
+ import { Parser } from "../../parser/parser.js";
13
+ import { classifyDependency } from "./resolve.js";
14
+ import type { ModuleRecord } from "./types.js";
15
+
16
+ function moduleSpecifierOf(statement: Statement): string | undefined {
17
+ if (statement.kind === SyntaxKind.ImportDeclaration) {
18
+ return (statement as ImportDeclaration).moduleSpecifier.value;
19
+ }
20
+ if (statement.kind === SyntaxKind.ExportDeclaration) {
21
+ const specifier = (statement as ExportDeclaration).moduleSpecifier;
22
+ if (specifier) return specifier.value;
23
+ }
24
+ return undefined;
25
+ }
26
+
27
+ /** Load, parse and bind the entry module and every module it imports. */
28
+ export function loadGraph(
29
+ entryPath: string,
30
+ diagnostics: DiagnosticBag,
31
+ externalSpecifiers: ReadonlySet<string>,
32
+ ): ModuleRecord[] | undefined {
33
+ const records = new Map<string, ModuleRecord>();
34
+ const order: ModuleRecord[] = [];
35
+ const visiting = new Set<string>();
36
+ let counter = 0;
37
+ let failed = false;
38
+
39
+ const load = (path: string): ModuleRecord | undefined => {
40
+ const existing = records.get(path);
41
+ if (existing) return existing;
42
+ if (visiting.has(path)) {
43
+ diagnostics.error(DiagnosticCode.CodegenError, `Circular import detected involving '${path}'`);
44
+ failed = true;
45
+ return undefined;
46
+ }
47
+ if (!existsSync(path)) {
48
+ diagnostics.error(DiagnosticCode.CodegenError, `Cannot find module '${path}'`);
49
+ failed = true;
50
+ return undefined;
51
+ }
52
+ visiting.add(path);
53
+ const text = readFileSync(path, "utf8");
54
+ const file = new SourceFile(path, text);
55
+ const parser = new Parser(file, diagnostics);
56
+ const sourceFile = parser.parseSourceFile();
57
+ const bindResult = bind(sourceFile);
58
+ const record: ModuleRecord = {
59
+ path,
60
+ text,
61
+ sourceFile,
62
+ bind: bindResult,
63
+ prefix: `m${counter++}_`,
64
+ exports: new Map(),
65
+ originalNames: new Map(),
66
+ finalNames: new Map(),
67
+ };
68
+ records.set(path, record);
69
+
70
+ for (const statement of sourceFile.statements) {
71
+ const specifier = moduleSpecifierOf(statement);
72
+ if (!specifier) continue;
73
+ const dependency = classifyDependency(dirname(path), specifier, externalSpecifiers);
74
+ if (dependency.kind === "external") continue;
75
+ if (dependency.kind === "missing") {
76
+ diagnostics.error(
77
+ DiagnosticCode.CodegenError,
78
+ `Cannot resolve module '${specifier}' from '${path}'`,
79
+ statement,
80
+ path,
81
+ );
82
+ failed = true;
83
+ continue;
84
+ }
85
+ load(dependency.path);
86
+ }
87
+ visiting.delete(path);
88
+ order.push(record);
89
+ return record;
90
+ };
91
+
92
+ load(entryPath);
93
+ if (failed) return undefined;
94
+ return order;
95
+ }