xbintsc 0.3.25 → 0.3.46

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 (187) hide show
  1. package/README.md +39 -3
  2. package/dist/src/cli/main.js +124 -30
  3. package/dist/src/cli/main.js.map +1 -1
  4. package/dist/src/codegen/generator/calls/access.js +8 -1
  5. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  6. package/dist/src/codegen/generator/module.js +10 -0
  7. package/dist/src/codegen/generator/module.js.map +1 -1
  8. package/dist/src/codegen/generator/state.js +24 -0
  9. package/dist/src/codegen/generator/state.js.map +1 -1
  10. package/dist/src/codegen/generator/tables.d.ts +6 -0
  11. package/dist/src/codegen/generator/tables.js +38 -0
  12. package/dist/src/codegen/generator/tables.js.map +1 -1
  13. package/dist/src/driver/bundler/commonjs.d.ts +52 -0
  14. package/dist/src/driver/bundler/commonjs.js +493 -0
  15. package/dist/src/driver/bundler/commonjs.js.map +1 -0
  16. package/dist/src/driver/bundler/graph.js +14 -0
  17. package/dist/src/driver/bundler/graph.js.map +1 -1
  18. package/dist/src/driver/bundler/merge.js +14 -0
  19. package/dist/src/driver/bundler/merge.js.map +1 -1
  20. package/dist/src/driver/bundler/types.d.ts +8 -0
  21. package/dist/src/driver/compiler.d.ts +8 -1
  22. package/dist/src/driver/compiler.js +73 -5
  23. package/dist/src/driver/compiler.js.map +1 -1
  24. package/dist/src/driver/config.d.ts +53 -0
  25. package/dist/src/driver/config.js +141 -0
  26. package/dist/src/driver/config.js.map +1 -0
  27. package/dist/src/driver/icon.d.ts +62 -0
  28. package/dist/src/driver/icon.js +151 -0
  29. package/dist/src/driver/icon.js.map +1 -0
  30. package/dist/src/driver/index.d.ts +4 -0
  31. package/dist/src/driver/index.js +4 -0
  32. package/dist/src/driver/index.js.map +1 -1
  33. package/dist/src/driver/mac-bundle.d.ts +41 -0
  34. package/dist/src/driver/mac-bundle.js +123 -0
  35. package/dist/src/driver/mac-bundle.js.map +1 -0
  36. package/dist/src/driver/win-icon.d.ts +27 -0
  37. package/dist/src/driver/win-icon.js +80 -0
  38. package/dist/src/driver/win-icon.js.map +1 -0
  39. package/dist/src/parser/statements.js +17 -4
  40. package/dist/src/parser/statements.js.map +1 -1
  41. package/dist/tests/cli/main.test.js +46 -1
  42. package/dist/tests/cli/main.test.js.map +1 -1
  43. package/dist/tests/driver/config.test.d.ts +1 -0
  44. package/dist/tests/driver/config.test.js +124 -0
  45. package/dist/tests/driver/config.test.js.map +1 -0
  46. package/dist/tests/driver/icon.test.d.ts +1 -0
  47. package/dist/tests/driver/icon.test.js +421 -0
  48. package/dist/tests/driver/icon.test.js.map +1 -0
  49. package/dist/tests/driver/modules.test.js +109 -0
  50. package/dist/tests/driver/modules.test.js.map +1 -1
  51. package/dist/tests/e2e/fetch.test.d.ts +12 -0
  52. package/dist/tests/e2e/fetch.test.js +272 -0
  53. package/dist/tests/e2e/fetch.test.js.map +1 -0
  54. package/dist/tests/e2e/gc.test.d.ts +1 -0
  55. package/dist/tests/e2e/gc.test.js +168 -0
  56. package/dist/tests/e2e/gc.test.js.map +1 -0
  57. package/dist/tests/e2e/gui-dom.test.d.ts +5 -0
  58. package/dist/tests/e2e/gui-dom.test.js +102 -0
  59. package/dist/tests/e2e/gui-dom.test.js.map +1 -0
  60. package/dist/tests/e2e/gui-events.test.d.ts +5 -0
  61. package/dist/tests/e2e/gui-events.test.js +60 -0
  62. package/dist/tests/e2e/gui-events.test.js.map +1 -0
  63. package/dist/tests/e2e/gui-helpers.d.ts +31 -0
  64. package/dist/tests/e2e/gui-helpers.js +74 -0
  65. package/dist/tests/e2e/gui-helpers.js.map +1 -0
  66. package/dist/tests/e2e/gui-layout.test.d.ts +4 -0
  67. package/dist/tests/e2e/gui-layout.test.js +88 -0
  68. package/dist/tests/e2e/gui-layout.test.js.map +1 -0
  69. package/dist/tests/e2e/gui-lifecycle.test.d.ts +5 -0
  70. package/dist/tests/e2e/gui-lifecycle.test.js +58 -0
  71. package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -0
  72. package/dist/tests/e2e/gui-paint.test.d.ts +5 -0
  73. package/dist/tests/e2e/gui-paint.test.js +111 -0
  74. package/dist/tests/e2e/gui-paint.test.js.map +1 -0
  75. package/dist/tests/e2e/gui-script.test.d.ts +5 -0
  76. package/dist/tests/e2e/gui-script.test.js +78 -0
  77. package/dist/tests/e2e/gui-script.test.js.map +1 -0
  78. package/dist/tests/e2e/gui-style.test.d.ts +5 -0
  79. package/dist/tests/e2e/gui-style.test.js +175 -0
  80. package/dist/tests/e2e/gui-style.test.js.map +1 -0
  81. package/dist/tests/e2e/harness.d.ts +2 -0
  82. package/dist/tests/e2e/harness.js +1 -0
  83. package/dist/tests/e2e/harness.js.map +1 -1
  84. package/dist/tests/e2e/icon.test.d.ts +7 -0
  85. package/dist/tests/e2e/icon.test.js +80 -0
  86. package/dist/tests/e2e/icon.test.js.map +1 -0
  87. package/dist/tests/e2e/npm-modules.test.d.ts +4 -1
  88. package/dist/tests/e2e/npm-modules.test.js +47 -1
  89. package/dist/tests/e2e/npm-modules.test.js.map +1 -1
  90. package/dist/tests/e2e/runtime-coverage-typed-array.test.d.ts +9 -0
  91. package/dist/tests/e2e/runtime-coverage-typed-array.test.js +140 -0
  92. package/dist/tests/e2e/runtime-coverage-typed-array.test.js.map +1 -0
  93. package/dist/tests/parser/statements.test.js +11 -0
  94. package/dist/tests/parser/statements.test.js.map +1 -1
  95. package/package.json +3 -2
  96. package/runtime/ext_gui/css.cpp +4 -517
  97. package/runtime/ext_gui/css_internal.h +27 -0
  98. package/runtime/ext_gui/css_match.cpp +195 -0
  99. package/runtime/ext_gui/css_selector.cpp +207 -0
  100. package/runtime/ext_gui/css_sheet.cpp +129 -0
  101. package/runtime/ext_gui/dom.cpp +7 -403
  102. package/runtime/ext_gui/dom_api.cpp +63 -1146
  103. package/runtime/ext_gui/dom_api_document.cpp +96 -0
  104. package/runtime/ext_gui/dom_api_events.cpp +123 -0
  105. package/runtime/ext_gui/dom_api_internal.h +135 -0
  106. package/runtime/ext_gui/dom_api_node.cpp +329 -0
  107. package/runtime/ext_gui/dom_api_node_read.cpp +294 -0
  108. package/runtime/ext_gui/dom_api_proto.cpp +135 -0
  109. package/runtime/ext_gui/dom_api_style.cpp +192 -0
  110. package/runtime/ext_gui/dom_internal.h +39 -0
  111. package/runtime/ext_gui/dom_parse.cpp +348 -0
  112. package/runtime/ext_gui/dom_serialize.cpp +54 -0
  113. package/runtime/ext_gui/gui.cpp +56 -286
  114. package/runtime/ext_gui/gui_events.cpp +294 -0
  115. package/runtime/ext_gui/gui_internal.h +29 -0
  116. package/runtime/ext_gui/layout.cpp +6 -597
  117. package/runtime/ext_gui/layout_flex.cpp +200 -0
  118. package/runtime/ext_gui/layout_flow.cpp +191 -0
  119. package/runtime/ext_gui/layout_flow_internal.h +149 -0
  120. package/runtime/ext_gui/layout_inline.cpp +181 -0
  121. package/runtime/ext_gui/layout_internal.h +20 -0
  122. package/runtime/ext_gui/renderer.cpp +15 -679
  123. package/runtime/ext_gui/renderer_image.cpp +233 -0
  124. package/runtime/ext_gui/renderer_internal.h +112 -0
  125. package/runtime/ext_gui/renderer_shaders.h +108 -0
  126. package/runtime/ext_gui/renderer_text.cpp +342 -0
  127. package/runtime/ext_gui/style.cpp +12 -660
  128. package/runtime/ext_gui/style_internal.h +33 -0
  129. package/runtime/ext_gui/style_print.cpp +113 -0
  130. package/runtime/ext_gui/style_ua.cpp +98 -0
  131. package/runtime/ext_gui/style_values.cpp +468 -0
  132. package/runtime/ext_gui/window.cpp +1 -0
  133. package/runtime/ext_node/buffer/parts/prototype.inc +1 -0
  134. package/runtime/ext_node/dgram/dgram.c +1 -0
  135. package/runtime/ext_node/events/events.c +1 -0
  136. package/runtime/ext_node/fs/fs_ops.c +10 -25
  137. package/runtime/ext_node/fs/glob.c +13 -30
  138. package/runtime/ext_node/fs/promises.c +1 -0
  139. package/runtime/ext_node/http/parts/prototypes.inc +5 -0
  140. package/runtime/ext_node/net/parts/prototypes.inc +2 -0
  141. package/runtime/ext_node/process/process.c +9 -7
  142. package/runtime/ext_node/stream/stream.c +1 -0
  143. package/runtime/ext_node/util/util.c +6 -6
  144. package/runtime/rt.h +16 -0
  145. package/runtime/rt_internal.h +67 -2
  146. package/runtime/xt_alloc.c +442 -5
  147. package/runtime/xt_builtins.c +1 -0
  148. package/runtime/xt_containers/access.inc +32 -1
  149. package/runtime/xt_fetch/buffer.inc +64 -0
  150. package/runtime/xt_fetch/headers.inc +312 -0
  151. package/runtime/xt_fetch/request.inc +386 -0
  152. package/runtime/xt_fetch/response.inc +107 -0
  153. package/runtime/xt_fetch/socket.inc +72 -0
  154. package/runtime/xt_fetch/url.inc +108 -0
  155. package/runtime/xt_fetch.c +39 -0
  156. package/runtime/xt_generator.c +95 -1
  157. package/runtime/xt_loop.c +35 -1
  158. package/runtime/xt_promise.c +46 -0
  159. package/runtime/xt_stdlib2/error.inc +1 -0
  160. package/runtime/xt_stdlib2/regexp-match.inc +8 -7
  161. package/runtime/xt_stdlib2/set.inc +1 -1
  162. package/runtime/xt_symbol.c +2 -0
  163. package/runtime/xt_typed_array/construction.inc +142 -0
  164. package/runtime/xt_typed_array/elements.inc +92 -0
  165. package/runtime/xt_typed_array/methods.inc +329 -0
  166. package/runtime/xt_typed_array.c +72 -0
  167. package/runtime/xt_values/number-format.inc +26 -0
  168. package/scripts/check-file-length.ts +85 -0
  169. package/src/cli/main.ts +137 -29
  170. package/src/codegen/generator/calls/access.ts +9 -1
  171. package/src/codegen/generator/module.ts +10 -0
  172. package/src/codegen/generator/state.ts +24 -0
  173. package/src/codegen/generator/tables.ts +40 -0
  174. package/src/driver/bundler/commonjs.ts +553 -0
  175. package/src/driver/bundler/graph.ts +14 -0
  176. package/src/driver/bundler/merge.ts +15 -0
  177. package/src/driver/bundler/types.ts +8 -0
  178. package/src/driver/compiler.ts +83 -5
  179. package/src/driver/config.ts +173 -0
  180. package/src/driver/icon.ts +184 -0
  181. package/src/driver/index.ts +30 -0
  182. package/src/driver/mac-bundle.ts +156 -0
  183. package/src/driver/win-icon.ts +96 -0
  184. package/src/parser/statements.ts +17 -4
  185. package/dist/tests/e2e/gui.test.d.ts +0 -12
  186. package/dist/tests/e2e/gui.test.js +0 -702
  187. package/dist/tests/e2e/gui.test.js.map +0 -1
@@ -0,0 +1,85 @@
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
+ interface Offense {
42
+ file: string;
43
+ lines: number;
44
+ }
45
+
46
+ function countLines(path: string): number {
47
+ const content = readFileSync(path, "utf8");
48
+ if (content.length === 0) return 0;
49
+ // A trailing newline terminates the last line instead of starting a new one.
50
+ const lines = content.split("\n");
51
+ return content.endsWith("\n") ? lines.length - 1 : lines.length;
52
+ }
53
+
54
+ function collect(dir: string, offenses: Offense[]): void {
55
+ for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
56
+ const full = join(dir, entry.name);
57
+ if (entry.isDirectory()) {
58
+ if (IGNORED_DIRECTORIES.has(entry.name)) continue;
59
+ // `runtime/lib` holds prebuilt archives, never sources.
60
+ if (relative(root, full) === join("runtime", "lib")) continue;
61
+ collect(full, offenses);
62
+ continue;
63
+ }
64
+ if (!entry.isFile()) continue;
65
+ const dot = entry.name.lastIndexOf(".");
66
+ if (dot < 0 || !CODE_EXTENSIONS.has(entry.name.slice(dot))) continue;
67
+ const file = relative(root, full);
68
+ if (VENDORED_FILES.has(file)) continue;
69
+ const lines = countLines(full);
70
+ if (lines > MAX_LINES) offenses.push({ file, lines });
71
+ }
72
+ }
73
+
74
+ const offenses: Offense[] = [];
75
+ collect(root, offenses);
76
+
77
+ if (offenses.length > 0) {
78
+ console.error(`xbintsc: code files must stay under ${MAX_LINES} lines - split them by responsibility:`);
79
+ for (const offense of offenses.sort((a, b) => b.lines - a.lines)) {
80
+ console.error(` ${offense.file}: ${offense.lines} lines`);
81
+ }
82
+ process.exit(1);
83
+ }
84
+
85
+ console.log(`xbintsc: all code files are within ${MAX_LINES} lines`);
package/src/cli/main.ts CHANGED
@@ -14,6 +14,7 @@
14
14
 
15
15
  import { spawnSync } from "node:child_process";
16
16
  import { readFileSync } from "node:fs";
17
+ import { dirname, resolve } from "node:path";
17
18
  import { pathToFileURL } from "node:url";
18
19
  import { formatDiagnostic, type Diagnostic } from "../diagnostics/diagnostic.js";
19
20
  import { SourceFile } from "../diagnostics/source.js";
@@ -25,6 +26,15 @@ import { realRunner } from "../driver/toolchain.js";
25
26
  import { createDefaultRegistry, type ExtensionRegistry } from "../extensions/registry.js";
26
27
  import { bundledExtensions } from "../extensions/catalog.js";
27
28
  import { nativeExtensionFromManifest } from "../extensions/native.js";
29
+ import { resolveResourceCompiler } from "../driver/win-icon.js";
30
+ import {
31
+ CONFIG_FILE_NAME,
32
+ findProjectConfig,
33
+ loadProjectConfig,
34
+ resolveConfigPaths,
35
+ type AppConfig,
36
+ type ProjectConfig,
37
+ } from "../driver/config.js";
28
38
 
29
39
  export interface CliIo {
30
40
  readonly stdout: (text: string) => void;
@@ -43,7 +53,18 @@ interface ParsedArgs {
43
53
  readonly passthrough: string[];
44
54
  }
45
55
 
46
- const VALUE_FLAGS = new Set(["output", "out", "emit", "optimize", "ext", "ext-native"]);
56
+ const VALUE_FLAGS = new Set([
57
+ "output",
58
+ "out",
59
+ "emit",
60
+ "optimize",
61
+ "ext",
62
+ "ext-native",
63
+ "config",
64
+ "icon",
65
+ "app-id",
66
+ "app-name",
67
+ ]);
47
68
 
48
69
  function parseArgs(argv: readonly string[]): ParsedArgs {
49
70
  const positionals: string[] = [];
@@ -95,29 +116,22 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
95
116
  return { command: positionals.shift(), positionals, flags, passthrough };
96
117
  }
97
118
 
98
- function buildRegistry(flags: Map<string, string | boolean>): ExtensionRegistry {
119
+ function buildRegistry(extNames: readonly string[], nativeManifests: readonly string[]): ExtensionRegistry {
99
120
  const registry = createDefaultRegistry();
100
- const requested = flags.get("ext");
101
- if (typeof requested === "string") {
102
- const bundled = bundledExtensions();
103
- for (const name of requested.split(",").map((n) => n.trim()).filter(Boolean)) {
104
- const extension = bundled.find((candidate) => candidate.name === name);
105
- if (!extension) throw new Error(`Unknown extension '${name}'`);
106
- registry.register(extension);
107
- }
121
+ const bundled = bundledExtensions();
122
+ for (const name of extNames) {
123
+ const extension = bundled.find((candidate) => candidate.name === name);
124
+ if (!extension) throw new Error(`Unknown extension '${name}'`);
125
+ registry.register(extension);
108
126
  }
109
- // Each `--ext-native` value is a manifest path describing a pre-built C++ or
110
- // Rust library; several manifests may be comma separated.
111
- const native = flags.get("ext-native");
112
- if (typeof native === "string") {
113
- for (const manifest of native.split(",").map((n) => n.trim()).filter(Boolean)) {
114
- registry.register(nativeExtensionFromManifest(manifest));
115
- }
127
+ // Each manifest describes a pre-built C++ or Rust library.
128
+ for (const manifest of nativeManifests) {
129
+ registry.register(nativeExtensionFromManifest(manifest));
116
130
  }
117
131
  // Hint the extensions that ship with xbintsc but were not enabled, so a
118
132
  // missing `import` of a known module points at the flag that enables it
119
133
  // (`pass --ext node`) instead of failing later with a confusing error.
120
- for (const extension of bundledExtensions()) {
134
+ for (const extension of bundled) {
121
135
  if (!registry.has(extension.name)) registry.hintExtension(extension);
122
136
  }
123
137
  return registry;
@@ -131,6 +145,11 @@ function printDiagnostics(diagnostics: readonly Diagnostic[], fileName: string,
131
145
  source = undefined;
132
146
  }
133
147
  for (const diagnostic of diagnostics) {
148
+ // Driver-level diagnostics (icon, config, …) have no source location.
149
+ if (diagnostic.fileName === undefined && diagnostic.start === undefined) {
150
+ io.stderr(`error TS${diagnostic.code}: ${diagnostic.message}\n`);
151
+ continue;
152
+ }
134
153
  io.stderr(formatDiagnostic(diagnostic, source) + "\n");
135
154
  }
136
155
  }
@@ -153,6 +172,13 @@ Options:
153
172
  --ext <names> Comma separated extensions (e.g. node)
154
173
  --ext-native <m> Register a C++/Rust extension from a JSON manifest
155
174
  (comma separated for several)
175
+ --config <path> Use a project config (default: auto-detect
176
+ ${CONFIG_FILE_NAME})
177
+ --no-config Do not read any project config
178
+ --icon <path> Embed an application icon (PNG/ICO/ICNS)
179
+ --bundle macOS: also produce a .app bundle
180
+ --app-name <name> Bundle / display name
181
+ --app-id <id> macOS bundle identifier (e.g. com.example.demo)
156
182
  --force Ignore the incremental cache
157
183
  --verbose Print progress information
158
184
  `;
@@ -190,9 +216,70 @@ function doctor(io: CliIo): number {
190
216
  } catch {
191
217
  io.stdout("runtime lib: unavailable\n");
192
218
  }
219
+
220
+ try {
221
+ const toolchain = resolveToolchain();
222
+ const resource = resolveResourceCompiler(realRunner, toolchain.clang);
223
+ io.stdout(`icon res : ${resource ? `${resource.tool} (${resource.kind})` : "none (.exe icon disabled)"}\n`);
224
+ } catch {
225
+ io.stdout("icon res : unknown\n");
226
+ }
193
227
  return 0;
194
228
  }
195
229
 
230
+ type ConfigResolution =
231
+ | { readonly ok: true; readonly config: ProjectConfig }
232
+ | { readonly ok: false; readonly error: string };
233
+
234
+ /**
235
+ * Resolve the project config for a command: `--config <path>` selects one
236
+ * explicitly, `--no-config` disables discovery, otherwise we walk up from the
237
+ * entry file (or the cwd) looking for {@link CONFIG_FILE_NAME}.
238
+ */
239
+ function resolveProjectConfig(
240
+ flags: Map<string, string | boolean>,
241
+ cliEntry: string | undefined,
242
+ ): ConfigResolution {
243
+ try {
244
+ const explicit = flags.get("config");
245
+ if (typeof explicit === "string") {
246
+ return { ok: true, config: resolveConfigPaths(loadProjectConfig(explicit)) };
247
+ }
248
+ if (flags.has("no-config")) return { ok: true, config: {} };
249
+ const startDir = cliEntry ? dirname(resolve(cliEntry)) : process.cwd();
250
+ const found = findProjectConfig(startDir);
251
+ if (!found) return { ok: true, config: {} };
252
+ return { ok: true, config: resolveConfigPaths(loadProjectConfig(found)) };
253
+ } catch (error) {
254
+ return { ok: false, error: (error as Error).message };
255
+ }
256
+ }
257
+
258
+ /** Merge `app.*` config with the CLI overrides (`--icon`, `--bundle`, …). */
259
+ function mergeAppConfig(flags: Map<string, string | boolean>, config: ProjectConfig): AppConfig {
260
+ const base = config.app ?? {};
261
+ const name = typeof flags.get("app-name") === "string" ? (flags.get("app-name") as string) : base.name;
262
+ const bundleId = typeof flags.get("app-id") === "string" ? (flags.get("app-id") as string) : base.bundleId;
263
+ const bundle = flags.has("bundle") ? true : base.bundle;
264
+ const icon = typeof flags.get("icon") === "string" ? (flags.get("icon") as string) : base.icon;
265
+ return {
266
+ ...(name !== undefined ? { name } : {}),
267
+ ...(bundleId !== undefined ? { bundleId } : {}),
268
+ ...(bundle !== undefined ? { bundle } : {}),
269
+ ...(icon !== undefined ? { icon } : {}),
270
+ };
271
+ }
272
+
273
+ /** Split a comma-separated CLI flag into a trimmed, non-empty list. */
274
+ function listFlag(flags: Map<string, string | boolean>, name: string): string[] | undefined {
275
+ const value = flags.get(name);
276
+ if (typeof value !== "string") return undefined;
277
+ return value
278
+ .split(",")
279
+ .map((entry) => entry.trim())
280
+ .filter(Boolean);
281
+ }
282
+
196
283
  export function run(argv: readonly string[], io: CliIo = defaultIo): number {
197
284
  const args = parseArgs(argv);
198
285
  const command = args.command;
@@ -212,12 +299,20 @@ export function run(argv: readonly string[], io: CliIo = defaultIo): number {
212
299
  }
213
300
 
214
301
  if (command === "emit") {
215
- const entry = args.positionals[0];
302
+ const cliEntry = args.positionals[0];
303
+ const resolved = resolveProjectConfig(args.flags, cliEntry);
304
+ if (!resolved.ok) {
305
+ io.stderr(`xbintsc: ${resolved.error}\n`);
306
+ return 1;
307
+ }
308
+ const entry = cliEntry ?? resolved.config.entry;
216
309
  if (!entry) {
217
- io.stderr("xbintsc: emit requires a source file\n");
310
+ io.stderr(`xbintsc: emit requires a source file (or 'entry' in ${CONFIG_FILE_NAME})\n`);
218
311
  return 1;
219
312
  }
220
- const registry = buildRegistry(args.flags);
313
+ const extNames = listFlag(args.flags, "ext") ?? resolved.config.extensions ?? [];
314
+ const nativeManifests = listFlag(args.flags, "ext-native") ?? resolved.config.extNative ?? [];
315
+ const registry = buildRegistry(extNames, nativeManifests);
221
316
  const { ir, diagnostics } = compileEntry(entry, registry);
222
317
  if (diagnostics.some((d) => d.category === "error")) {
223
318
  printDiagnostics(diagnostics, entry, io);
@@ -228,21 +323,33 @@ export function run(argv: readonly string[], io: CliIo = defaultIo): number {
228
323
  }
229
324
 
230
325
  if (command === "build" || command === "run") {
231
- const entry = args.positionals[0];
326
+ const cliEntry = args.positionals[0];
327
+ const resolved = resolveProjectConfig(args.flags, cliEntry);
328
+ if (!resolved.ok) {
329
+ io.stderr(`xbintsc: ${resolved.error}\n`);
330
+ return 1;
331
+ }
332
+ const config = resolved.config;
333
+ const entry = cliEntry ?? config.entry;
232
334
  if (!entry) {
233
- io.stderr(`xbintsc: ${command} requires a source file\n`);
335
+ io.stderr(`xbintsc: ${command} requires a source file (or 'entry' in ${CONFIG_FILE_NAME})\n`);
234
336
  return 1;
235
337
  }
236
338
  const emit = (args.flags.get("emit") as EmitKind | undefined) ?? "exe";
237
- const optimize = (args.flags.get("optimize") as string | undefined) ?? "2";
339
+ const optimize = (args.flags.get("optimize") as string | undefined) ?? config.optimize ?? "2";
340
+ const extNames = listFlag(args.flags, "ext") ?? config.extensions ?? [];
341
+ const nativeManifests = listFlag(args.flags, "ext-native") ?? config.extNative ?? [];
342
+ const registry = buildRegistry(extNames, nativeManifests);
343
+
238
344
  const result = build(entry, {
239
- output: typeof args.flags.get("output") === "string" ? (args.flags.get("output") as string) : undefined,
240
- outDir: typeof args.flags.get("out") === "string" ? (args.flags.get("out") as string) : undefined,
345
+ output: typeof args.flags.get("output") === "string" ? (args.flags.get("output") as string) : config.output,
346
+ outDir: typeof args.flags.get("out") === "string" ? (args.flags.get("out") as string) : config.outDir,
241
347
  emit,
242
348
  optimize: optimize as "0" | "1" | "2" | "3",
243
- force: Boolean(args.flags.get("force")),
349
+ force: Boolean(args.flags.get("force")) || config.force === true,
244
350
  verbose: Boolean(args.flags.get("verbose")),
245
- extensions: buildRegistry(args.flags),
351
+ extensions: registry,
352
+ app: mergeAppConfig(args.flags, config),
246
353
  });
247
354
 
248
355
  if (result.diagnostics.some((d) => d.category === "error")) {
@@ -251,7 +358,8 @@ export function run(argv: readonly string[], io: CliIo = defaultIo): number {
251
358
  }
252
359
 
253
360
  if (command === "build") {
254
- io.stdout(`xbintsc: wrote ${result.outputPath}${result.cached ? " (cached)" : ""}\n`);
361
+ const bundle = result.bundlePath ? ` (bundle: ${result.bundlePath})` : "";
362
+ io.stdout(`xbintsc: wrote ${result.outputPath}${result.cached ? " (cached)" : ""}${bundle}\n`);
255
363
  return 0;
256
364
  }
257
365
 
@@ -13,7 +13,7 @@ import {
13
13
  type PropertyAccessExpression,
14
14
  } from "../../../ast/nodes.js";
15
15
  import { i64, numberLiteral, XT_TRUE, XT_UNDEFINED } from "../../values.js";
16
- import { MATH_CONSTANTS, NAMESPACE_PROPERTIES, NUMBER_CONSTANTS } from "../tables.js";
16
+ import { MATH_CONSTANTS, NAMESPACE_PROPERTIES, NUMBER_CONSTANTS, TYPED_ARRAYS } from "../tables.js";
17
17
  import type { Generator } from "../generator.js";
18
18
 
19
19
  export interface AccessCallMethods {
@@ -121,6 +121,14 @@ export const accessCallMethods: AccessCallMethods = {
121
121
  return result;
122
122
  }
123
123
  }
124
+ if (
125
+ node.expression.kind === SyntaxKind.Identifier &&
126
+ !this.binding.symbolOfIdentifier.get(node.expression as Identifier) &&
127
+ node.name.text === "BYTES_PER_ELEMENT"
128
+ ) {
129
+ const typed = TYPED_ARRAYS[(node.expression as Identifier).text];
130
+ if (typed) return numberLiteral(typed.bytes);
131
+ }
124
132
  const object = this.emitExpression(node.expression);
125
133
  return this.emitPropertyGet(object, node.name.text);
126
134
  },
@@ -227,9 +227,19 @@ export const moduleMethods: ModuleMethods = {
227
227
 
228
228
  emitMain(): void {
229
229
  const moduleName = this.functionName(this.binding.moduleFunction);
230
+ // Module bindings and class constructors live in globals; register them as
231
+ // GC roots before the module body runs so a collection cannot free them.
232
+ const roots = [...this.classGlobals.values(), ...this.moduleGlobals.values()];
233
+ const rootLines = roots.map((name) => ` call void @xt_gc_add_root(i64* ${name})`);
230
234
  this.functions.push(
231
235
  [
232
236
  "define i32 @main(i32 %argc, i8** %argv) {",
237
+ " %stackbase = alloca i64",
238
+ " %stackbase.ptr = bitcast i64* %stackbase to i8*",
239
+ " call void @xt_gc_set_stack_base(i8* %stackbase.ptr)",
240
+ " call void @xt_gc_init()",
241
+ ...rootLines,
242
+ " call void @xt_gc_arm()",
233
243
  " call void @xt_set_program_args(i32 %argc, i8** %argv)",
234
244
  ` %result = call i64 @${moduleName}(i64 ${i64(XT_UNDEFINED)}, i64 ${i64(XT_UNDEFINED)}, i32 0, i64* null)`,
235
245
  " call void @xt_drain_microtasks()",
@@ -103,6 +103,10 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
103
103
  "declare i64 @xt_await(i64)",
104
104
  "declare void @xt_drain_microtasks()",
105
105
  "declare void @xt_run_event_loop()",
106
+ "declare void @xt_gc_init()",
107
+ "declare void @xt_gc_set_stack_base(i8*)",
108
+ "declare void @xt_gc_arm()",
109
+ "declare void @xt_gc_add_root(i64*)",
106
110
  "declare i64 @xt_promise_resolve(i64)",
107
111
  "declare i64 @xt_promise_reject(i64)",
108
112
  "declare i64 @xt_promise_ctor(i32, i64*)",
@@ -116,6 +120,24 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
116
120
  "declare i64 @xt_os_static(i64, i32, i64*)",
117
121
  "declare i64 @xt_buffer_static(i64, i32, i64*)",
118
122
  "declare i64 @xt_buffer_ctor(i32, i64*)",
123
+ "declare i64 @xt_ta_u8_ctor(i32, i64*)",
124
+ "declare i64 @xt_ta_u8_static(i64, i32, i64*)",
125
+ "declare i64 @xt_ta_i8_ctor(i32, i64*)",
126
+ "declare i64 @xt_ta_i8_static(i64, i32, i64*)",
127
+ "declare i64 @xt_ta_u8c_ctor(i32, i64*)",
128
+ "declare i64 @xt_ta_u8c_static(i64, i32, i64*)",
129
+ "declare i64 @xt_ta_u16_ctor(i32, i64*)",
130
+ "declare i64 @xt_ta_u16_static(i64, i32, i64*)",
131
+ "declare i64 @xt_ta_i16_ctor(i32, i64*)",
132
+ "declare i64 @xt_ta_i16_static(i64, i32, i64*)",
133
+ "declare i64 @xt_ta_u32_ctor(i32, i64*)",
134
+ "declare i64 @xt_ta_u32_static(i64, i32, i64*)",
135
+ "declare i64 @xt_ta_i32_ctor(i32, i64*)",
136
+ "declare i64 @xt_ta_i32_static(i64, i32, i64*)",
137
+ "declare i64 @xt_ta_f32_ctor(i32, i64*)",
138
+ "declare i64 @xt_ta_f32_static(i64, i32, i64*)",
139
+ "declare i64 @xt_ta_f64_ctor(i32, i64*)",
140
+ "declare i64 @xt_ta_f64_static(i64, i32, i64*)",
119
141
  "declare i64 @xt_stream_static(i64, i32, i64*)",
120
142
  "declare i64 @xt_readable_ctor(i32, i64*)",
121
143
  "declare i64 @xt_writable_ctor(i32, i64*)",
@@ -219,6 +241,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
219
241
  "declare i64 @xt_clear_timeout(i32, i64*)",
220
242
  "declare i64 @xt_set_interval(i32, i64*)",
221
243
  "declare i64 @xt_clear_interval(i32, i64*)",
244
+ "declare i64 @xt_fetch(i32, i64*)",
222
245
  "declare i64 @xt_number_ctor(i32, i64*)",
223
246
  "declare i64 @xt_bigint_ctor(i32, i64*)",
224
247
  "declare i64 @xt_bigint_static(i64, i32, i64*)",
@@ -254,6 +277,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
254
277
  "declare i64 @xt_builtin_value_clearTimeout(i64, i64, i32, i64*)",
255
278
  "declare i64 @xt_builtin_value_setInterval(i64, i64, i32, i64*)",
256
279
  "declare i64 @xt_builtin_value_clearInterval(i64, i64, i32, i64*)",
280
+ "declare i64 @xt_builtin_value_fetch(i64, i64, i32, i64*)",
257
281
  "declare i64 @xt_builtin_value_number(i64, i64, i32, i64*)",
258
282
  "declare i64 @xt_builtin_value_string(i64, i64, i32, i64*)",
259
283
  "declare i64 @xt_builtin_value_boolean(i64, i64, i32, i64*)",
@@ -127,6 +127,7 @@ export const GLOBAL_FUNCTIONS: Record<string, string> = {
127
127
  clearTimeout: "xt_clear_timeout",
128
128
  setInterval: "xt_set_interval",
129
129
  clearInterval: "xt_clear_interval",
130
+ fetch: "xt_fetch",
130
131
  Number: "xt_number_ctor",
131
132
  String: "xt_string_ctor",
132
133
  Boolean: "xt_boolean_ctor",
@@ -150,6 +151,7 @@ export const BUILTIN_FUNCTION_VALUES: Record<string, string> = {
150
151
  clearTimeout: "xt_builtin_value_clearTimeout",
151
152
  setInterval: "xt_builtin_value_setInterval",
152
153
  clearInterval: "xt_builtin_value_clearInterval",
154
+ fetch: "xt_builtin_value_fetch",
153
155
  Number: "xt_builtin_value_number",
154
156
  String: "xt_builtin_value_string",
155
157
  Boolean: "xt_builtin_value_boolean",
@@ -157,6 +159,42 @@ export const BUILTIN_FUNCTION_VALUES: Record<string, string> = {
157
159
  Symbol: "xt_builtin_value_symbol",
158
160
  };
159
161
 
162
+ /** Typed-array globals: constructor, static dispatcher and element width. */
163
+ export const TYPED_ARRAYS: Record<string, { ctor: string; static: string; bytes: number }> = {
164
+ Uint8Array: { ctor: "xt_ta_u8_ctor", static: "xt_ta_u8_static", bytes: 1 },
165
+ Int8Array: { ctor: "xt_ta_i8_ctor", static: "xt_ta_i8_static", bytes: 1 },
166
+ Uint8ClampedArray: { ctor: "xt_ta_u8c_ctor", static: "xt_ta_u8c_static", bytes: 1 },
167
+ Uint16Array: { ctor: "xt_ta_u16_ctor", static: "xt_ta_u16_static", bytes: 2 },
168
+ Int16Array: { ctor: "xt_ta_i16_ctor", static: "xt_ta_i16_static", bytes: 2 },
169
+ Uint32Array: { ctor: "xt_ta_u32_ctor", static: "xt_ta_u32_static", bytes: 4 },
170
+ Int32Array: { ctor: "xt_ta_i32_ctor", static: "xt_ta_i32_static", bytes: 4 },
171
+ Float32Array: { ctor: "xt_ta_f32_ctor", static: "xt_ta_f32_static", bytes: 4 },
172
+ Float64Array: { ctor: "xt_ta_f64_ctor", static: "xt_ta_f64_static", bytes: 8 },
173
+ };
174
+
175
+ const TYPED_ARRAY_CTORS: Record<string, string> = {
176
+ Uint8Array: "xt_ta_u8_ctor",
177
+ Int8Array: "xt_ta_i8_ctor",
178
+ Uint8ClampedArray: "xt_ta_u8c_ctor",
179
+ Uint16Array: "xt_ta_u16_ctor",
180
+ Int16Array: "xt_ta_i16_ctor",
181
+ Uint32Array: "xt_ta_u32_ctor",
182
+ Int32Array: "xt_ta_i32_ctor",
183
+ Float32Array: "xt_ta_f32_ctor",
184
+ Float64Array: "xt_ta_f64_ctor",
185
+ };
186
+ const TYPED_ARRAY_STATICS: Record<string, string> = {
187
+ Uint8Array: "xt_ta_u8_static",
188
+ Int8Array: "xt_ta_i8_static",
189
+ Uint8ClampedArray: "xt_ta_u8c_static",
190
+ Uint16Array: "xt_ta_u16_static",
191
+ Int16Array: "xt_ta_i16_static",
192
+ Uint32Array: "xt_ta_u32_static",
193
+ Int32Array: "xt_ta_i32_static",
194
+ Float32Array: "xt_ta_f32_static",
195
+ Float64Array: "xt_ta_f64_static",
196
+ };
197
+
160
198
  /** Global namespaces whose static methods map to runtime dispatchers. */
161
199
  export const NAMESPACE_STATICS: Record<string, string> = {
162
200
  Math: "xt_math_call",
@@ -186,6 +224,7 @@ export const NAMESPACE_STATICS: Record<string, string> = {
186
224
  util: "xt_util_static",
187
225
  querystring: "xt_querystring_static",
188
226
  assert: "xt_assert_static",
227
+ ...TYPED_ARRAY_STATICS,
189
228
  };
190
229
 
191
230
  /** Namespace identifiers whose property access maps to a runtime getter. */
@@ -213,6 +252,7 @@ export const CTOR_FUNCTIONS: Record<string, string> = {
213
252
  Server: "xt_net_server_ctor",
214
253
  EventEmitter: "xt_event_emitter_ctor",
215
254
  ...ERROR_CONSTRUCTORS,
255
+ ...TYPED_ARRAY_CTORS,
216
256
  };
217
257
 
218
258
  export const BUILTIN_METHODS = new Set<string>([