xbintsc 0.3.79 → 0.3.87

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 (201) hide show
  1. package/README.md +13 -5
  2. package/README.zh-CN.md +13 -6
  3. package/dist/src/ast/common.d.ts +23 -0
  4. package/dist/src/cli/main.js +68 -24
  5. package/dist/src/cli/main.js.map +1 -1
  6. package/dist/src/codegen/generator/calls/invocation.js +5 -1
  7. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  8. package/dist/src/codegen/generator/context.d.ts +50 -0
  9. package/dist/src/codegen/generator/context.js +124 -13
  10. package/dist/src/codegen/generator/context.js.map +1 -1
  11. package/dist/src/codegen/generator/debug.d.ts +33 -0
  12. package/dist/src/codegen/generator/debug.js +97 -0
  13. package/dist/src/codegen/generator/debug.js.map +1 -0
  14. package/dist/src/codegen/generator/expressions/assignment.js +9 -5
  15. package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
  16. package/dist/src/codegen/generator/expressions/operators.js +35 -9
  17. package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
  18. package/dist/src/codegen/generator/expressions/primary.js +5 -2
  19. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  20. package/dist/src/codegen/generator/generator.d.ts +3 -1
  21. package/dist/src/codegen/generator/generator.js +3 -1
  22. package/dist/src/codegen/generator/generator.js.map +1 -1
  23. package/dist/src/codegen/generator/module.d.ts +1 -0
  24. package/dist/src/codegen/generator/module.js +76 -6
  25. package/dist/src/codegen/generator/module.js.map +1 -1
  26. package/dist/src/codegen/generator/numeric.d.ts +26 -0
  27. package/dist/src/codegen/generator/numeric.js +127 -0
  28. package/dist/src/codegen/generator/numeric.js.map +1 -0
  29. package/dist/src/codegen/generator/state.d.ts +17 -0
  30. package/dist/src/codegen/generator/state.js +5 -0
  31. package/dist/src/codegen/generator/state.js.map +1 -1
  32. package/dist/src/codegen/generator/statements/control-flow.js +1 -1
  33. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  34. package/dist/src/codegen/generator/statements/dispatch.js +3 -0
  35. package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
  36. package/dist/src/codegen/generator/statements/variables.js +8 -2
  37. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  38. package/dist/src/codegen/generator/tables.d.ts +7 -0
  39. package/dist/src/codegen/generator/tables.js +25 -0
  40. package/dist/src/codegen/generator/tables.js.map +1 -1
  41. package/dist/src/driver/bundler/merge.js +41 -5
  42. package/dist/src/driver/bundler/merge.js.map +1 -1
  43. package/dist/src/driver/compiler.d.ts +22 -3
  44. package/dist/src/driver/compiler.js +49 -58
  45. package/dist/src/driver/compiler.js.map +1 -1
  46. package/dist/src/driver/fingerprint.d.ts +27 -0
  47. package/dist/src/driver/fingerprint.js +88 -0
  48. package/dist/src/driver/fingerprint.js.map +1 -0
  49. package/dist/src/driver/toolchain-provider.d.ts +9 -2
  50. package/dist/src/driver/toolchain-provider.js +15 -15
  51. package/dist/src/driver/toolchain-provider.js.map +1 -1
  52. package/dist/src/driver/toolchain.d.ts +58 -1
  53. package/dist/src/driver/toolchain.js +169 -22
  54. package/dist/src/driver/toolchain.js.map +1 -1
  55. package/dist/src/extensions/gui/index.js +10 -2
  56. package/dist/src/extensions/gui/index.js.map +1 -1
  57. package/dist/src/parser/expressions.js +20 -1
  58. package/dist/src/parser/expressions.js.map +1 -1
  59. package/dist/tests/cli/main.test.js +41 -1
  60. package/dist/tests/cli/main.test.js.map +1 -1
  61. package/dist/tests/codegen/expressions.test.js +14 -3
  62. package/dist/tests/codegen/expressions.test.js.map +1 -1
  63. package/dist/tests/codegen/llvm.test.js +23 -0
  64. package/dist/tests/codegen/llvm.test.js.map +1 -1
  65. package/dist/tests/driver/toolchain-provider.test.js +49 -2
  66. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  67. package/dist/tests/driver/toolchain.test.js +62 -3
  68. package/dist/tests/driver/toolchain.test.js.map +1 -1
  69. package/dist/tests/e2e/child-process.test.d.ts +10 -0
  70. package/dist/tests/e2e/child-process.test.js +59 -0
  71. package/dist/tests/e2e/child-process.test.js.map +1 -0
  72. package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
  73. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  74. package/dist/tests/e2e/error-stack.test.d.ts +9 -0
  75. package/dist/tests/e2e/error-stack.test.js +94 -0
  76. package/dist/tests/e2e/error-stack.test.js.map +1 -0
  77. package/dist/tests/e2e/gui-example-svg.test.d.ts +21 -0
  78. package/dist/tests/e2e/gui-example-svg.test.js +105 -0
  79. package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
  80. package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
  81. package/dist/tests/e2e/gui-transform.test.js +198 -0
  82. package/dist/tests/e2e/gui-transform.test.js.map +1 -0
  83. package/dist/tests/e2e/string-concat.test.d.ts +9 -0
  84. package/dist/tests/e2e/string-concat.test.js +102 -0
  85. package/dist/tests/e2e/string-concat.test.js.map +1 -0
  86. package/dist/tests/helpers.d.ts +9 -0
  87. package/dist/tests/helpers.js +26 -3
  88. package/dist/tests/helpers.js.map +1 -1
  89. package/dist/tests/parser/expressions.test.js +7 -0
  90. package/dist/tests/parser/expressions.test.js.map +1 -1
  91. package/dist/tests/version.test.d.ts +1 -0
  92. package/dist/tests/version.test.js +24 -0
  93. package/dist/tests/version.test.js.map +1 -0
  94. package/doc/DESIGN.md +20 -12
  95. package/doc/ai/cli.md +7 -0
  96. package/doc/ai/contributing.md +7 -1
  97. package/doc/ai/extensions.md +5 -1
  98. package/doc/ai/language-support.md +4 -4
  99. package/doc/ai/troubleshooting.md +39 -2
  100. package/doc/ai/zh-CN/cli.md +2 -0
  101. package/doc/ai/zh-CN/contributing.md +6 -2
  102. package/doc/ai/zh-CN/extensions.md +5 -1
  103. package/doc/ai/zh-CN/language-support.md +4 -4
  104. package/doc/ai/zh-CN/troubleshooting.md +35 -2
  105. package/doc/gui/animation.md +53 -2
  106. package/doc/gui/api.md +1 -1
  107. package/doc/gui/dom.md +6 -1
  108. package/doc/gui/input.md +4 -1
  109. package/doc/gui/known-issues.md +16 -4
  110. package/doc/gui/layout.md +36 -0
  111. package/doc/gui/rendering.md +15 -0
  112. package/doc/gui.md +32 -3
  113. package/doc/implemented.md +4 -4
  114. package/doc/requirements.md +13 -4
  115. package/doc/unimplemented.md +11 -10
  116. package/doc/zh-CN/DESIGN.md +15 -9
  117. package/doc/zh-CN/gui/animation.md +49 -2
  118. package/doc/zh-CN/gui/api.md +1 -1
  119. package/doc/zh-CN/gui/dom.md +6 -1
  120. package/doc/zh-CN/gui/input.md +2 -0
  121. package/doc/zh-CN/gui/known-issues.md +12 -2
  122. package/doc/zh-CN/gui/layout.md +31 -0
  123. package/doc/zh-CN/gui/rendering.md +11 -0
  124. package/doc/zh-CN/gui.md +28 -3
  125. package/doc/zh-CN/implemented.md +4 -4
  126. package/doc/zh-CN/requirements.md +12 -3
  127. package/doc/zh-CN/unimplemented.md +10 -9
  128. package/package.json +3 -1
  129. package/runtime/ext_gui/document.cpp +21 -3
  130. package/runtime/ext_gui/document.h +4 -1
  131. package/runtime/ext_gui/dom_api_node.cpp +6 -4
  132. package/runtime/ext_gui/layout.cpp +78 -1
  133. package/runtime/ext_gui/layout.h +21 -0
  134. package/runtime/ext_gui/paint.cpp +48 -15
  135. package/runtime/ext_gui/paint.h +8 -2
  136. package/runtime/ext_gui/renderer.cpp +4 -2
  137. package/runtime/ext_gui/renderer_image.cpp +2 -1
  138. package/runtime/ext_gui/renderer_internal.h +6 -1
  139. package/runtime/ext_gui/renderer_text.cpp +37 -15
  140. package/runtime/ext_gui/style.h +10 -0
  141. package/runtime/ext_gui/style_print.cpp +6 -0
  142. package/runtime/ext_gui/style_values.cpp +43 -0
  143. package/runtime/ext_gui/transform.cpp +412 -0
  144. package/runtime/ext_gui/transform.h +112 -0
  145. package/runtime/ext_gui/window.cpp +6 -4
  146. package/runtime/ext_node/buffer/parts/construction.inc +2 -2
  147. package/runtime/ext_node/buffer/parts/methods.inc +1 -1
  148. package/runtime/ext_node/buffer/parts/search.inc +1 -1
  149. package/runtime/ext_node/child_process/child_process.c +25 -0
  150. package/runtime/ext_node/fs/fd_ops.c +1 -1
  151. package/runtime/rt.h +17 -1
  152. package/runtime/rt_internal.h +47 -4
  153. package/runtime/xt_alloc.c +14 -2
  154. package/runtime/xt_bigint/conversions.inc +1 -1
  155. package/runtime/xt_builtins.c +136 -14
  156. package/runtime/xt_containers/access.inc +17 -16
  157. package/runtime/xt_containers/objects.inc +4 -3
  158. package/runtime/xt_io/exception.inc +171 -2
  159. package/runtime/xt_io/inspect.inc +15 -13
  160. package/runtime/xt_io.c +5 -0
  161. package/runtime/xt_stdlib/arrays.inc +2 -2
  162. package/runtime/xt_stdlib/dispatch.inc +2 -2
  163. package/runtime/xt_stdlib/strings.inc +33 -21
  164. package/runtime/xt_stdlib2/array-statics.inc +2 -1
  165. package/runtime/xt_stdlib2/arrays.inc +1 -1
  166. package/runtime/xt_stdlib2/error.inc +22 -6
  167. package/runtime/xt_stdlib2/json.inc +3 -2
  168. package/runtime/xt_stdlib2/number-string-statics.inc +3 -1
  169. package/runtime/xt_stdlib2/regexp-match.inc +48 -33
  170. package/runtime/xt_stdlib2/strings.inc +23 -14
  171. package/runtime/xt_symbol.c +1 -1
  172. package/runtime/xt_values/comparison.inc +2 -2
  173. package/runtime/xt_values/constructors.inc +10 -5
  174. package/runtime/xt_values/conversions.inc +4 -3
  175. package/runtime/xt_values/strings.inc +78 -4
  176. package/scripts/bench.ts +172 -0
  177. package/scripts/build-runtime.ts +2 -1
  178. package/scripts/check-version.ts +53 -0
  179. package/src/ast/common.ts +23 -0
  180. package/src/cli/main.ts +68 -26
  181. package/src/codegen/generator/calls/invocation.ts +5 -0
  182. package/src/codegen/generator/context.ts +129 -18
  183. package/src/codegen/generator/debug.ts +132 -0
  184. package/src/codegen/generator/expressions/assignment.ts +9 -5
  185. package/src/codegen/generator/expressions/operators.ts +47 -8
  186. package/src/codegen/generator/expressions/primary.ts +5 -2
  187. package/src/codegen/generator/generator.ts +18 -2
  188. package/src/codegen/generator/module.ts +78 -6
  189. package/src/codegen/generator/numeric.ts +158 -0
  190. package/src/codegen/generator/state.ts +22 -0
  191. package/src/codegen/generator/statements/control-flow.ts +1 -1
  192. package/src/codegen/generator/statements/dispatch.ts +3 -0
  193. package/src/codegen/generator/statements/variables.ts +8 -2
  194. package/src/codegen/generator/tables.ts +26 -0
  195. package/src/driver/bundler/merge.ts +39 -5
  196. package/src/driver/compiler.ts +68 -56
  197. package/src/driver/fingerprint.ts +85 -0
  198. package/src/driver/toolchain-provider.ts +17 -16
  199. package/src/driver/toolchain.ts +210 -21
  200. package/src/extensions/gui/index.ts +10 -2
  201. package/src/parser/expressions.ts +20 -1
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build and time a small, fixed benchmark suite.
4
+ *
5
+ * The numbers are wall-clock times on the current machine, so they are only
6
+ * useful for comparing revisions of the compiler (or the same revision against
7
+ * Node): run `npm run bench` before and after a change to the runtime or
8
+ * codegen. Each program is compiled with the normal driver (cache bypassed) and
9
+ * executed several times; the table reports the fastest run to reduce noise.
10
+ *
11
+ * Usage:
12
+ * npm run bench # every benchmark, 3 runs, with Node for scale
13
+ * npm run bench -- --runs 5 # more runs (minimum is reported)
14
+ * npm run bench -- --filter prop
15
+ * npm run bench -- --no-node # skip the Node comparison
16
+ */
17
+
18
+ import { spawnSync } from "node:child_process";
19
+ import { mkdirSync, statSync, writeFileSync } from "node:fs";
20
+ import { dirname, join, resolve } from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+ import { build } from "../src/driver/index.js";
23
+
24
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
25
+ const outDir = join(root, "build", "bench");
26
+ const cacheDir = join(root, "build", "bench-cache");
27
+
28
+ interface Benchmark {
29
+ readonly name: string;
30
+ /** Plain JavaScript so the same source can run under Node. */
31
+ readonly source: string;
32
+ }
33
+
34
+ const BENCHMARKS: readonly Benchmark[] = [
35
+ {
36
+ name: "numeric",
37
+ source: `
38
+ let total = 0;
39
+ for (let i = 0; i < 50000000; i++) total = total + i;
40
+ console.log(total);
41
+ `,
42
+ },
43
+ {
44
+ name: "property",
45
+ source: `
46
+ const object = { x: 0 };
47
+ for (let i = 0; i < 5000000; i++) object.x = object.x + 1;
48
+ console.log(object.x);
49
+ `,
50
+ },
51
+ {
52
+ name: "array",
53
+ source: `
54
+ const values = [];
55
+ for (let i = 0; i < 3000000; i++) values[i] = i;
56
+ let total = 0;
57
+ for (let i = 0; i < 3000000; i++) total = total + values[i];
58
+ console.log(total);
59
+ `,
60
+ },
61
+ {
62
+ name: "string-concat",
63
+ source: `
64
+ let text = "";
65
+ for (let i = 0; i < 100000; i++) text = text + "a";
66
+ console.log(text.length);
67
+ `,
68
+ },
69
+ ];
70
+
71
+ interface Options {
72
+ readonly runs: number;
73
+ readonly filter?: string;
74
+ readonly node: boolean;
75
+ }
76
+
77
+ function parseOptions(argv: readonly string[]): Options {
78
+ let runs = 3;
79
+ let filter: string | undefined;
80
+ let node = true;
81
+ for (let index = 0; index < argv.length; index++) {
82
+ const arg = argv[index]!;
83
+ if (arg === "--no-node") node = false;
84
+ else if (arg === "--runs") runs = Math.max(1, Number(argv[++index]) || 1);
85
+ else if (arg.startsWith("--runs=")) runs = Math.max(1, Number(arg.slice("--runs=".length)) || 1);
86
+ else if (arg === "--filter") filter = argv[++index];
87
+ else if (arg.startsWith("--filter=")) filter = arg.slice("--filter=".length);
88
+ else if (arg === "--help" || arg === "-h") {
89
+ process.stdout.write(
90
+ "usage: npm run bench [-- --runs N] [--filter <substr>] [--no-node]\n",
91
+ );
92
+ process.exit(0);
93
+ }
94
+ }
95
+ return { runs, filter, node };
96
+ }
97
+
98
+ function minTimeMs(runs: number, execute: () => void): number {
99
+ let best = Number.POSITIVE_INFINITY;
100
+ for (let run = 0; run < runs; run++) {
101
+ const start = process.hrtime.bigint();
102
+ execute();
103
+ const elapsed = Number(process.hrtime.bigint() - start) / 1e6;
104
+ if (elapsed < best) best = elapsed;
105
+ }
106
+ return best;
107
+ }
108
+
109
+ function formatMs(value: number | undefined): string {
110
+ return value === undefined ? "-" : `${value.toFixed(1)} ms`;
111
+ }
112
+
113
+ function main(): number {
114
+ const options = parseOptions(process.argv.slice(2));
115
+ mkdirSync(outDir, { recursive: true });
116
+ mkdirSync(cacheDir, { recursive: true });
117
+ const benchmarks = BENCHMARKS.filter(
118
+ (benchmark) => options.filter === undefined || benchmark.name.includes(options.filter),
119
+ );
120
+ if (benchmarks.length === 0) {
121
+ process.stderr.write(`xbintsc: no benchmark matches '${options.filter}'\n`);
122
+ return 1;
123
+ }
124
+
125
+ const rows: { name: string; ours?: number; node?: number; bytes?: number }[] = [];
126
+ for (const benchmark of benchmarks) {
127
+ const entry = join(outDir, `${benchmark.name}.ts`);
128
+ writeFileSync(entry, benchmark.source);
129
+ const result = build(entry, {
130
+ emit: "exe",
131
+ outDir: join(outDir, "bin"),
132
+ cacheDir,
133
+ force: true,
134
+ });
135
+ const errors = result.diagnostics.filter((diagnostic) => diagnostic.category === "error");
136
+ if (errors.length > 0) {
137
+ for (const error of errors) process.stderr.write(`xbintsc: ${error.message}\n`);
138
+ return 1;
139
+ }
140
+ const bytes = statSync(result.outputPath).size;
141
+ const ours = minTimeMs(options.runs, () => {
142
+ const executed = spawnSync(result.outputPath, [], { stdio: "ignore" });
143
+ if (executed.status !== 0) throw new Error(`${benchmark.name} exited with ${executed.status}`);
144
+ });
145
+
146
+ let nodeTime: number | undefined;
147
+ if (options.node) {
148
+ const script = join(outDir, `${benchmark.name}.mjs`);
149
+ writeFileSync(script, benchmark.source);
150
+ nodeTime = minTimeMs(options.runs, () => {
151
+ const executed = spawnSync(process.execPath, [script], { stdio: "ignore" });
152
+ if (executed.status !== 0) throw new Error(`${benchmark.name} (node) exited with ${executed.status}`);
153
+ });
154
+ }
155
+ rows.push({ name: benchmark.name, ours, node: nodeTime, bytes });
156
+ }
157
+
158
+ const header = ["benchmark", "xbintsc", "node", "size"];
159
+ process.stdout.write(
160
+ `${header[0]!.padEnd(16)}${header[1]!.padStart(12)}${header[2]!.padStart(12)}${header[3]!.padStart(12)}\n`,
161
+ );
162
+ for (const row of rows) {
163
+ const size = row.bytes === undefined ? "-" : `${Math.round(row.bytes / 1024)} KB`;
164
+ process.stdout.write(
165
+ `${row.name.padEnd(16)}${formatMs(row.ours).padStart(12)}${formatMs(row.node).padStart(12)}${size.padStart(12)}\n`,
166
+ );
167
+ }
168
+ process.stdout.write(`\nfastest of ${options.runs} run(s) on ${process.platform}-${process.arch}\n`);
169
+ return 0;
170
+ }
171
+
172
+ process.exit(main());
@@ -21,6 +21,7 @@ import { fileURLToPath } from "node:url";
21
21
  import { RUNTIME_SOURCES } from "../src/driver/compiler.js";
22
22
  import { platformSlug } from "../src/driver/paths.js";
23
23
  import { resolveToolchain } from "../src/driver/toolchain-provider.js";
24
+ import { RUNTIME_CFLAGS } from "../src/driver/toolchain.js";
24
25
  import { nodeExtension } from "../src/extensions/node/index.js";
25
26
 
26
27
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -72,7 +73,7 @@ function compile(sources: readonly string[], extension: string): string[] {
72
73
  const object = join(objDir, `${extension}_${index}_${basename(source, ".c")}.o`);
73
74
  const result = spawnSync(
74
75
  clang,
75
- ["-O2", "-Wall", "-Wextra", "-D_CRT_SECURE_NO_WARNINGS", "-c", source, "-o", object, `-I${runtimeDir}`],
76
+ [...RUNTIME_CFLAGS, "-c", source, "-o", object, `-I${runtimeDir}`],
76
77
  { stdio: "inherit", env: childEnv },
77
78
  );
78
79
  if (result.status !== 0) {
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Verify that `COMPILER_VERSION` (reported by `xbintsc version` / `doctor` and
4
+ * embedded in the incremental cache key) matches the version in `package.json`
5
+ * (which names the release archives).
6
+ *
7
+ * Release automation runs this before tagging. A mismatch means an upgraded
8
+ * compiler can reuse binaries produced by the previous one while presenting the
9
+ * wrong version to bug reports.
10
+ */
11
+
12
+ import { readFileSync } from "node:fs";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { fileURLToPath, pathToFileURL } from "node:url";
15
+
16
+ /** Read the version literal from the compiler source. */
17
+ export function compilerVersionOf(root: string): string {
18
+ const text = readFileSync(join(root, "src", "driver", "compiler.ts"), "utf8");
19
+ const match = /export const COMPILER_VERSION = "([^"]+)"/.exec(text);
20
+ if (!match) throw new Error("COMPILER_VERSION is not declared in src/driver/compiler.ts");
21
+ return match[1]!;
22
+ }
23
+
24
+ /** Read the published version from package.json. */
25
+ export function packageVersionOf(root: string): string {
26
+ const parsed = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { version?: string };
27
+ if (!parsed.version) throw new Error("package.json has no version field");
28
+ return parsed.version;
29
+ }
30
+
31
+ /** Returns a human-readable mismatch message, or `undefined` when in sync. */
32
+ export function checkCompilerVersion(root: string): string | undefined {
33
+ const packageVersion = packageVersionOf(root);
34
+ const compilerVersion = compilerVersionOf(root);
35
+ if (packageVersion === compilerVersion) return undefined;
36
+ return (
37
+ `COMPILER_VERSION is "${compilerVersion}" but package.json is "${packageVersion}".\n` +
38
+ 'Update COMPILER_VERSION in src/driver/compiler.ts (it keys the incremental cache), or fix package.json.'
39
+ );
40
+ }
41
+
42
+ const invokedDirectly =
43
+ process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
44
+
45
+ if (invokedDirectly) {
46
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
47
+ const mismatch = checkCompilerVersion(root);
48
+ if (mismatch) {
49
+ process.stderr.write(`xbintsc: version mismatch\n${mismatch}\n`);
50
+ process.exit(1);
51
+ }
52
+ process.stdout.write(`xbintsc: version ${compilerVersionOf(root)} matches package.json\n`);
53
+ }
package/src/ast/common.ts CHANGED
@@ -207,4 +207,27 @@ export interface SourceFileNode extends Node {
207
207
  readonly statements: Statement[];
208
208
  readonly fileName: string;
209
209
  readonly text: string;
210
+ /**
211
+ * For a bundled program: the original module each node was parsed from.
212
+ *
213
+ * The bundler merges every module into one `SourceFile`, but the nodes keep
214
+ * their module-local `start`/`end` offsets. Diagnostics must therefore report
215
+ * the module a node came from, not the entry file, or a cross-module error
216
+ * shows the wrong file and a nonsensical excerpt. Absent for a single-file
217
+ * compile, where `fileName` is the only source.
218
+ */
219
+ readonly origins?: Map<object, string>;
220
+ /**
221
+ * For a bundled program: the original text of every module, keyed like
222
+ * {@link origins}. A node's `start` is relative to its module's text, so
223
+ * computing a frame's line number needs that text rather than the merged
224
+ * concatenation in `text`.
225
+ */
226
+ readonly moduleTexts?: Map<string, string>;
227
+ /**
228
+ * Bundler-generated symbol prefixes (`m0_`, `m1_`, …). Display names (stack
229
+ * frames, symbol suffixes) strip them so a bundled `fail()` still shows as
230
+ * `fail`, not `m1_fail`.
231
+ */
232
+ readonly modulePrefixes?: readonly string[];
210
233
  }
package/src/cli/main.ts CHANGED
@@ -18,11 +18,11 @@ import { dirname, resolve } from "node:path";
18
18
  import { pathToFileURL } from "node:url";
19
19
  import { formatDiagnostic, type Diagnostic } from "../diagnostics/diagnostic.js";
20
20
  import { SourceFile } from "../diagnostics/source.js";
21
- import { build, compileEntry, COMPILER_VERSION, type EmitKind } from "../driver/compiler.js";
21
+ import { build, compileEntry, COMPILER_VERSION, type EmitKind, type PhaseTiming } from "../driver/compiler.js";
22
22
  import { findRuntimeDir, platformSlug } from "../driver/paths.js";
23
23
  import { runtimeLibDir } from "../driver/runtime-lib.js";
24
- import { resolveToolchain } from "../driver/toolchain-provider.js";
25
- import { realRunner, ToolchainError } from "../driver/toolchain.js";
24
+ import { resolveToolchain, type ResolvedToolchain } from "../driver/toolchain-provider.js";
25
+ import { MINIMUM_CLANG_MAJOR, probeClang, realRunner, ToolchainError } from "../driver/toolchain.js";
26
26
  import {
27
27
  DOC_CLI,
28
28
  DOC_TROUBLESHOOTING,
@@ -154,23 +154,39 @@ function buildRegistry(extNames: readonly string[], nativeManifests: readonly st
154
154
  }
155
155
 
156
156
  function printDiagnostics(diagnostics: readonly Diagnostic[], fileName: string, io: CliIo): void {
157
- let source: SourceFile | undefined;
158
- try {
159
- source = new SourceFile(fileName, readFileSync(fileName, "utf8"));
160
- } catch {
161
- source = undefined;
162
- }
157
+ /* A diagnostic can come from any module in a bundled program, so load (and
158
+ * cache) the source of the file it names instead of always using the entry:
159
+ * reusing the entry's text is what used to print another file's line with a
160
+ * caret under unrelated code. */
161
+ const sources = new Map<string, SourceFile | undefined>();
162
+ const sourceFor = (name: string): SourceFile | undefined => {
163
+ if (!sources.has(name)) {
164
+ try {
165
+ sources.set(name, new SourceFile(name, readFileSync(name, "utf8")));
166
+ } catch {
167
+ sources.set(name, undefined);
168
+ }
169
+ }
170
+ return sources.get(name);
171
+ };
163
172
  for (const diagnostic of diagnostics) {
164
173
  // Driver-level diagnostics (icon, config, …) have no source location.
165
174
  if (diagnostic.fileName === undefined && diagnostic.start === undefined) {
166
175
  io.stderr(`error TS${diagnostic.code}: ${diagnostic.message}\n`);
167
176
  continue;
168
177
  }
178
+ const source = sourceFor(diagnostic.fileName ?? fileName);
169
179
  io.stderr(formatDiagnostic(diagnostic, source) + "\n");
170
180
  }
171
181
  for (const hint of documentationHints(diagnostics)) emitHint(io, hint);
172
182
  }
173
183
 
184
+ /** Print the phases collected by `--time`. */
185
+ function printTimings(timings: readonly PhaseTiming[], io: CliIo): void {
186
+ const text = timings.map((timing) => `${timing.phase} ${timing.ms}ms`).join(", ");
187
+ io.stdout(`xbintsc: timing (${text})\n`);
188
+ }
189
+
174
190
  /** Print one `hint:` line unless the caller asked for plain output. */
175
191
  function emitHint(io: CliIo, line: string): void {
176
192
  if (hintsDisabled) return;
@@ -237,6 +253,8 @@ Options:
237
253
  --app-id <id> macOS bundle identifier (e.g. com.example.demo)
238
254
  --force Ignore the incremental cache
239
255
  --verbose Print progress information
256
+ --time Print per-phase build timings
257
+ --debug Emit debug info (DWARF/CodeView) and keep frame pointers
240
258
  --no-hints Do not append the "hint: … see <doc>" line to failures
241
259
 
242
260
  xbintsc compiles a subset of TypeScript to a native binary. Types are erased
@@ -247,26 +265,47 @@ Docs: doc/ai/README.md (task guide) - doc/ai/language-support.md (subset) -
247
265
  doc/ai/troubleshooting.md (failures) - llms.txt (index of every document)
248
266
  `;
249
267
 
250
- function firstLine(text: string): string {
251
- const index = text.indexOf("\n");
252
- return (index === -1 ? text : text.slice(0, index)).trim();
253
- }
254
-
255
- /** Report the resolved toolchain and runtime locations. */
268
+ /**
269
+ * Report the resolved toolchain and runtime locations.
270
+ *
271
+ * Returns non-zero when the resolved compiler cannot build anything (missing,
272
+ * or older than {@link MINIMUM_CLANG_MAJOR}), so CI and scripts can use it as a
273
+ * preflight instead of discovering the problem halfway through a build.
274
+ */
256
275
  function doctor(io: CliIo): number {
257
276
  io.stdout(`xbintsc ${COMPILER_VERSION}\n`);
258
277
  io.stdout(`platform : ${platformSlug()}\n`);
259
278
 
279
+ let toolchain: ResolvedToolchain | undefined;
280
+ let usable = false;
260
281
  try {
261
- const toolchain = resolveToolchain();
262
- const version = realRunner.run(toolchain.clang, ["--version"], { env: toolchain.env }).stdout;
282
+ toolchain = resolveToolchain();
283
+ const probe = probeClang(realRunner, toolchain.clang, toolchain.env);
263
284
  io.stdout(`toolchain : ${toolchain.clang} (${toolchain.source})\n`);
264
- io.stdout(`compiler : ${firstLine(version) || "unknown"}\n`);
265
- } catch {
266
- io.stdout("toolchain : not found\n");
267
- io.stdout(" install one of: Xcode Command Line Tools (`xcode-select --install`) on macOS,\n");
268
- io.stdout(" clang+lld (apt/dnf/pacman) on Linux, or LLVM + Visual Studio C++ build tools on Windows,\n");
269
- io.stdout(" then re-run; or set xbintsc_CLANG to a clang binary. See doc/requirements.md.\n");
285
+ io.stdout(`compiler : ${probe.summary || "unknown"}\n`);
286
+ const version = toolchain.version ?? probe.version;
287
+ if (!version) {
288
+ // An explicit wrapper need not print a clang-style version; let it run.
289
+ usable = true;
290
+ io.stdout(`status : version unknown (requires clang >= ${MINIMUM_CLANG_MAJOR})\n`);
291
+ } else if (version.major >= MINIMUM_CLANG_MAJOR) {
292
+ usable = true;
293
+ io.stdout(`status : ok (requires clang >= ${MINIMUM_CLANG_MAJOR})\n`);
294
+ } else {
295
+ io.stdout(
296
+ `status : too old: clang ${version.major}.${version.minor}.${version.patch} ` +
297
+ `(requires >= ${MINIMUM_CLANG_MAJOR})\n`,
298
+ );
299
+ io.stdout(" set xbintsc_CLANG to a newer clang; on Windows Visual Studio ships one under\n");
300
+ io.stdout(" <VS install>\\VC\\Tools\\Llvm\\x64\\bin\\clang.exe. See doc/requirements.md.\n");
301
+ }
302
+ } catch (error) {
303
+ const message = error instanceof Error ? error.message : String(error);
304
+ io.stdout("toolchain : not usable\n");
305
+ for (const line of message.split("\n")) {
306
+ if (line.trim().length > 0) io.stdout(` ${line.trim()}\n`);
307
+ }
308
+ io.stdout(" See doc/requirements.md for the per-platform install steps, or set xbintsc_CLANG.\n");
270
309
  }
271
310
 
272
311
  try {
@@ -282,13 +321,13 @@ function doctor(io: CliIo): number {
282
321
  }
283
322
 
284
323
  try {
285
- const toolchain = resolveToolchain();
324
+ if (!toolchain) throw new Error("no toolchain");
286
325
  const resource = resolveResourceCompiler(realRunner, toolchain.clang);
287
326
  io.stdout(`icon res : ${resource ? `${resource.tool} (${resource.kind})` : "none (.exe icon disabled)"}\n`);
288
327
  } catch {
289
328
  io.stdout("icon res : unknown\n");
290
329
  }
291
- return 0;
330
+ return usable ? 0 : 1;
292
331
  }
293
332
 
294
333
  type ConfigResolution =
@@ -388,7 +427,7 @@ function dispatch(argv: readonly string[], io: CliIo): number {
388
427
  const extNames = listFlag(args.flags, "ext") ?? resolved.config.extensions ?? [];
389
428
  const nativeManifests = listFlag(args.flags, "ext-native") ?? resolved.config.extNative ?? [];
390
429
  const registry = buildRegistry(extNames, nativeManifests);
391
- const { ir, diagnostics } = compileEntry(entry, registry);
430
+ const { ir, diagnostics } = compileEntry(entry, registry, { debug: Boolean(args.flags.get("debug")) });
392
431
  if (diagnostics.some((d) => d.category === "error")) {
393
432
  printDiagnostics(diagnostics, entry, io);
394
433
  return 1;
@@ -423,6 +462,7 @@ function dispatch(argv: readonly string[], io: CliIo): number {
423
462
  verbose: Boolean(args.flags.get("verbose")),
424
463
  extensions: registry,
425
464
  app: mergeAppConfig(args.flags, config),
465
+ debug: Boolean(args.flags.get("debug")),
426
466
  });
427
467
 
428
468
  if (result.diagnostics.some((d) => d.category === "error")) {
@@ -430,6 +470,8 @@ function dispatch(argv: readonly string[], io: CliIo): number {
430
470
  return 1;
431
471
  }
432
472
 
473
+ if (args.flags.has("time") && result.timings) printTimings(result.timings, io);
474
+
433
475
  if (command === "build") {
434
476
  const bundle = result.bundlePath ? ` (bundle: ${result.bundlePath})` : "";
435
477
  io.stdout(`xbintsc: wrote ${result.outputPath}${result.cached ? " (cached)" : ""}${bundle}\n`);
@@ -19,6 +19,7 @@ import {
19
19
  BUILTIN_METHODS,
20
20
  CONSOLE_METHODS,
21
21
  CTOR_FUNCTIONS,
22
+ ERROR_CONSTRUCTORS,
22
23
  GLOBAL_FUNCTIONS,
23
24
  MATH_FUNCTIONS,
24
25
  NAMESPACE_STATICS,
@@ -142,6 +143,10 @@ export const invocationCallMethods: InvocationCallMethods = {
142
143
  }
143
144
  const ctor = !symbol ? CTOR_FUNCTIONS[(callee as Identifier).text] : undefined;
144
145
  if (ctor) {
146
+ // `Error("x")` without `new`: point the innermost frame at the call.
147
+ if (ERROR_CONSTRUCTORS[(callee as Identifier).text]) {
148
+ this.emit(` call void @xt_stack_set_line(i32 ${this.lineOf(node)})`);
149
+ }
145
150
  const args = this.emitArguments(node.arguments);
146
151
  const result = this.reg();
147
152
  this.emit(` ${result} = call i64 @${ctor}(i32 ${args.argc}, i64* ${args.ptr})`);