xbintsc 0.2.4 → 0.2.6

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 (124) hide show
  1. package/dist/src/ast/declarations.d.ts +20 -1
  2. package/dist/src/ast/expressions.d.ts +6 -2
  3. package/dist/src/ast/kinds.d.ts +6 -1
  4. package/dist/src/ast/kinds.js +5 -0
  5. package/dist/src/ast/kinds.js.map +1 -1
  6. package/dist/src/ast/statements.d.ts +2 -1
  7. package/dist/src/binder/binder.js +73 -9
  8. package/dist/src/binder/binder.js.map +1 -1
  9. package/dist/src/cli/main.js +2 -4
  10. package/dist/src/cli/main.js.map +1 -1
  11. package/dist/src/codegen/generator/calls.d.ts +1 -1
  12. package/dist/src/codegen/generator/calls.js +57 -14
  13. package/dist/src/codegen/generator/calls.js.map +1 -1
  14. package/dist/src/codegen/generator/context.d.ts +1 -1
  15. package/dist/src/codegen/generator/expressions.js +39 -3
  16. package/dist/src/codegen/generator/expressions.js.map +1 -1
  17. package/dist/src/codegen/generator/module.js +73 -7
  18. package/dist/src/codegen/generator/module.js.map +1 -1
  19. package/dist/src/codegen/generator/state.js +16 -0
  20. package/dist/src/codegen/generator/state.js.map +1 -1
  21. package/dist/src/codegen/generator/statements.d.ts +4 -1
  22. package/dist/src/codegen/generator/statements.js +141 -10
  23. package/dist/src/codegen/generator/statements.js.map +1 -1
  24. package/dist/src/codegen/generator/tables.d.ts +3 -0
  25. package/dist/src/codegen/generator/tables.js +13 -0
  26. package/dist/src/codegen/generator/tables.js.map +1 -1
  27. package/dist/src/codegen/values.d.ts +18 -11
  28. package/dist/src/codegen/values.js +92 -15
  29. package/dist/src/codegen/values.js.map +1 -1
  30. package/dist/src/diagnostics/source.d.ts +1 -1
  31. package/dist/src/diagnostics/source.js +12 -7
  32. package/dist/src/diagnostics/source.js.map +1 -1
  33. package/dist/src/driver/compiler.d.ts +5 -0
  34. package/dist/src/driver/compiler.js +42 -2
  35. package/dist/src/driver/compiler.js.map +1 -1
  36. package/dist/src/driver/modules.js +29 -4
  37. package/dist/src/driver/modules.js.map +1 -1
  38. package/dist/src/extensions/node/child_process/index.d.ts +7 -0
  39. package/dist/src/extensions/node/child_process/index.js +19 -0
  40. package/dist/src/extensions/node/child_process/index.js.map +1 -0
  41. package/dist/src/extensions/node/crypto/index.d.ts +6 -0
  42. package/dist/src/extensions/node/crypto/index.js +18 -0
  43. package/dist/src/extensions/node/crypto/index.js.map +1 -0
  44. package/dist/src/extensions/node/index.js +6 -0
  45. package/dist/src/extensions/node/index.js.map +1 -1
  46. package/dist/src/extensions/node/module.d.ts +9 -1
  47. package/dist/src/extensions/node/module.js +14 -1
  48. package/dist/src/extensions/node/module.js.map +1 -1
  49. package/dist/src/extensions/node/url/index.d.ts +7 -0
  50. package/dist/src/extensions/node/url/index.js +21 -0
  51. package/dist/src/extensions/node/url/index.js.map +1 -0
  52. package/dist/src/lexer/scanner.js +4 -5
  53. package/dist/src/lexer/scanner.js.map +1 -1
  54. package/dist/src/lexer/token.d.ts +10 -0
  55. package/dist/src/lexer/token.js +69 -0
  56. package/dist/src/lexer/token.js.map +1 -1
  57. package/dist/src/parser/context.js +4 -16
  58. package/dist/src/parser/context.js.map +1 -1
  59. package/dist/src/parser/declarations.d.ts +2 -0
  60. package/dist/src/parser/declarations.js +146 -33
  61. package/dist/src/parser/declarations.js.map +1 -1
  62. package/dist/src/parser/expressions.js +20 -3
  63. package/dist/src/parser/expressions.js.map +1 -1
  64. package/dist/src/parser/helpers.js +2 -0
  65. package/dist/src/parser/helpers.js.map +1 -1
  66. package/dist/src/parser/modules.js +7 -2
  67. package/dist/src/parser/modules.js.map +1 -1
  68. package/dist/src/parser/statements.js +28 -3
  69. package/dist/src/parser/statements.js.map +1 -1
  70. package/dist/src/parser/types.js +5 -0
  71. package/dist/src/parser/types.js.map +1 -1
  72. package/dist/tests/codegen/llvm.test.js +16 -0
  73. package/dist/tests/codegen/llvm.test.js.map +1 -1
  74. package/dist/tests/driver/compiler.test.js +13 -1
  75. package/dist/tests/driver/compiler.test.js.map +1 -1
  76. package/dist/tests/e2e/compile.test.js +57 -0
  77. package/dist/tests/e2e/compile.test.js.map +1 -1
  78. package/dist/tests/parser/parser.test.js +53 -0
  79. package/dist/tests/parser/parser.test.js.map +1 -1
  80. package/package.json +1 -1
  81. package/runtime/ext_node/child_process/child_process.c +213 -0
  82. package/runtime/ext_node/crypto/crypto.c +177 -0
  83. package/runtime/ext_node/process/process.c +37 -3
  84. package/runtime/ext_node/url/url.c +85 -0
  85. package/runtime/rt.h +20 -0
  86. package/runtime/rt_internal.h +11 -0
  87. package/runtime/xt_builtins.c +23 -0
  88. package/runtime/xt_containers.c +136 -10
  89. package/runtime/xt_io.c +53 -0
  90. package/runtime/xt_regex.h +16 -6
  91. package/runtime/xt_stdlib.c +11 -2
  92. package/runtime/xt_stdlib2.c +250 -13
  93. package/runtime/xt_values.c +2 -1
  94. package/src/ast/declarations.ts +26 -1
  95. package/src/ast/expressions.ts +7 -2
  96. package/src/ast/kinds.ts +5 -0
  97. package/src/ast/statements.ts +2 -1
  98. package/src/binder/binder.ts +75 -9
  99. package/src/cli/main.ts +2 -3
  100. package/src/codegen/generator/calls.ts +59 -16
  101. package/src/codegen/generator/context.ts +1 -1
  102. package/src/codegen/generator/expressions.ts +39 -3
  103. package/src/codegen/generator/module.ts +79 -7
  104. package/src/codegen/generator/state.ts +16 -0
  105. package/src/codegen/generator/statements.ts +139 -11
  106. package/src/codegen/generator/tables.ts +14 -0
  107. package/src/codegen/values.ts +85 -16
  108. package/src/diagnostics/source.ts +12 -7
  109. package/src/driver/compiler.ts +44 -2
  110. package/src/driver/modules.ts +28 -3
  111. package/src/extensions/node/child_process/index.ts +24 -0
  112. package/src/extensions/node/crypto/index.ts +23 -0
  113. package/src/extensions/node/index.ts +6 -0
  114. package/src/extensions/node/module.ts +13 -1
  115. package/src/extensions/node/url/index.ts +26 -0
  116. package/src/lexer/scanner.ts +4 -4
  117. package/src/lexer/token.ts +73 -0
  118. package/src/parser/context.ts +4 -15
  119. package/src/parser/declarations.ts +140 -29
  120. package/src/parser/expressions.ts +20 -4
  121. package/src/parser/helpers.ts +2 -0
  122. package/src/parser/modules.ts +7 -2
  123. package/src/parser/statements.ts +27 -3
  124. package/src/parser/types.ts +5 -0
@@ -4,15 +4,19 @@
4
4
 
5
5
  import {
6
6
  SyntaxKind,
7
+ type ArrayBindingPattern,
8
+ type BindingName,
7
9
  type Block,
8
10
  type CaseClause,
9
11
  type DoStatement,
12
+ type EnumDeclaration,
10
13
  type Expression,
11
14
  type ForInStatement,
12
15
  type ForOfStatement,
13
16
  type ForStatement,
14
17
  type Identifier,
15
18
  type IfStatement,
19
+ type ObjectBindingPattern,
16
20
  type ReturnStatement,
17
21
  type Statement,
18
22
  type SwitchStatement,
@@ -23,12 +27,16 @@ import {
23
27
  type WhileStatement,
24
28
  } from "../../ast/nodes.js";
25
29
  import { i64, numberLiteral, XT_UNDEFINED } from "../values.js";
30
+ import { propertyNameText } from "./tables.js";
26
31
  import type { Generator } from "./generator.js";
27
32
 
28
33
  export interface StatementMethods {
29
34
  emitStatements(this: Generator, statements: readonly Statement[]): void;
30
35
  emitStatement(this: Generator, statement: Statement): void;
31
36
  emitVariableStatement(this: Generator, statement: VariableStatement): void;
37
+ emitBindingPattern(this: Generator, name: BindingName, value: string): void;
38
+ emitBindingDefault(this: Generator, value: string, initializer: Expression): string;
39
+ emitEnum(this: Generator, statement: EnumDeclaration): void;
32
40
  emitIf(this: Generator, statement: IfStatement): void;
33
41
  emitWhile(this: Generator, statement: WhileStatement): void;
34
42
  emitDo(this: Generator, statement: DoStatement): void;
@@ -125,6 +133,9 @@ export const statementMethods: StatementMethods = {
125
133
  if (declaration) this.emitStatement(declaration);
126
134
  return;
127
135
  }
136
+ case SyntaxKind.EnumDeclaration:
137
+ this.emitEnum(statement as EnumDeclaration);
138
+ return;
128
139
  default:
129
140
  this.unsupported(statement, "statement");
130
141
  }
@@ -132,16 +143,130 @@ export const statementMethods: StatementMethods = {
132
143
 
133
144
  emitVariableStatement(statement: VariableStatement): void {
134
145
  for (const declaration of statement.declarationList.declarations) {
135
- const symbol = this.binding.symbolOfDeclaration.get(declaration);
136
- if (!symbol) continue;
137
- if (this.current.slots.has(symbol.id)) continue; // hoisted `var`
138
- const initial = declaration.initializer
139
- ? this.emitExpression(declaration.initializer)
140
- : i64(XT_UNDEFINED);
141
- this.declareSlot(symbol, initial);
146
+ if (declaration.name.kind === SyntaxKind.Identifier) {
147
+ const symbol = this.binding.symbolOfDeclaration.get(declaration);
148
+ if (!symbol) {
149
+ if (declaration.initializer) this.emitExpression(declaration.initializer);
150
+ continue;
151
+ }
152
+ if (this.current.slots.has(symbol.id)) {
153
+ // Hoisted `var` (or an already-declared slot): keep evaluating the
154
+ // initializer for its side effects, but do not redeclare the slot.
155
+ if (declaration.initializer) this.emitExpression(declaration.initializer);
156
+ continue;
157
+ }
158
+ if (symbol.boxed) {
159
+ // A captured binding lives in a box. Create the box *before*
160
+ // evaluating the initializer so a self-referencing closure
161
+ // (`const f = () => f()`) captures the box, not a copy of the
162
+ // still-uninitialised value.
163
+ this.declareSlot(symbol, i64(XT_UNDEFINED));
164
+ if (declaration.initializer) this.writeSlot(symbol, this.emitExpression(declaration.initializer));
165
+ continue;
166
+ }
167
+ const initial = declaration.initializer ? this.emitExpression(declaration.initializer) : i64(XT_UNDEFINED);
168
+ this.declareSlot(symbol, initial);
169
+ } else {
170
+ const initial = declaration.initializer ? this.emitExpression(declaration.initializer) : i64(XT_UNDEFINED);
171
+ this.emitBindingPattern(declaration.name, initial);
172
+ }
173
+ }
174
+ },
175
+
176
+ /** Lower a destructuring binding pattern, declaring each bound identifier. */
177
+ emitBindingPattern(name: BindingName, value: string): void {
178
+ if (name.kind === SyntaxKind.Identifier) {
179
+ const symbol = this.binding.symbolOfDeclaration.get(name);
180
+ if (!symbol) return;
181
+ if (this.current.slots.has(symbol.id)) this.writeSlot(symbol, value);
182
+ else this.declareSlot(symbol, value);
183
+ return;
184
+ }
185
+ if (name.kind === SyntaxKind.ArrayBindingPattern) {
186
+ const pattern = name as ArrayBindingPattern;
187
+ for (let index = 0; index < pattern.elements.length; index++) {
188
+ const element = pattern.elements[index];
189
+ if (!element) continue;
190
+ let elementValue: string;
191
+ if (element.dotDotDotToken) {
192
+ const startPtr = this.alloca();
193
+ this.emit(` store i64 ${numberLiteral(index)}, i64* ${startPtr}`);
194
+ const sliceName = this.stringValue("slice");
195
+ elementValue = this.reg();
196
+ this.emit(` ${elementValue} = call i64 @xt_call_method(i64 ${value}, i64 ${sliceName}, i32 1, i64* ${startPtr})`);
197
+ } else {
198
+ elementValue = this.reg();
199
+ this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${numberLiteral(index)})`);
200
+ }
201
+ if (element.initializer) elementValue = this.emitBindingDefault(elementValue, element.initializer);
202
+ this.emitBindingPattern(element.name, elementValue);
203
+ }
204
+ return;
205
+ }
206
+ const pattern = name as ObjectBindingPattern;
207
+ for (const element of pattern.elements) {
208
+ const key = element.propertyName ?? (element.name.kind === SyntaxKind.Identifier ? element.name : undefined);
209
+ if (element.dotDotDotToken) {
210
+ this.unsupported(element, "object rest destructuring");
211
+ continue;
212
+ }
213
+ const keyValue = this.stringValue(key ? propertyNameText(key) : "undefined");
214
+ let elementValue = this.reg();
215
+ this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${keyValue})`);
216
+ if (element.initializer) elementValue = this.emitBindingDefault(elementValue, element.initializer);
217
+ this.emitBindingPattern(element.name, elementValue);
142
218
  }
143
219
  },
144
220
 
221
+ /** `x = fallback` in a binding pattern: only apply when `x` is `undefined`. */
222
+ emitBindingDefault(value: string, initializer: Expression): string {
223
+ const fallback = this.emitExpression(initializer);
224
+ const isUndefined = this.reg();
225
+ this.emit(` ${isUndefined} = call i64 @xt_seq(i64 ${value}, i64 ${i64(XT_UNDEFINED)})`);
226
+ const truthy = this.reg();
227
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${isUndefined})`);
228
+ const condition = this.reg();
229
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
230
+ const result = this.reg();
231
+ this.emit(` ${result} = select i1 ${condition}, i64 ${fallback}, i64 ${value}`);
232
+ return result;
233
+ },
234
+
235
+ /**
236
+ * `enum E { A, B }` (and `const enum`) lowers to a runtime object with both
237
+ * forward (`E.A`) and reverse (`E[0] === "A"`) numeric mappings.
238
+ */
239
+ emitEnum(statement: EnumDeclaration): void {
240
+ const symbol = this.binding.symbolOfDeclaration.get(statement);
241
+ const object = this.reg();
242
+ this.emit(` ${object} = call i64 @xt_object_new()`);
243
+ let next = 0;
244
+ for (const member of statement.members) {
245
+ const name = propertyNameText(member.name);
246
+ const key = this.stringValue(name);
247
+ let value: string;
248
+ if (member.initializer) {
249
+ value = this.emitExpression(member.initializer);
250
+ if (member.initializer.kind === SyntaxKind.NumericLiteral) {
251
+ const numeric = (member.initializer as { value?: number }).value ?? 0;
252
+ next = numeric + 1;
253
+ } else {
254
+ next++;
255
+ }
256
+ } else {
257
+ value = numberLiteral(next);
258
+ next++;
259
+ }
260
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
261
+ // Reverse mapping (only meaningful for numeric members).
262
+ if (!member.initializer || member.initializer.kind === SyntaxKind.NumericLiteral) {
263
+ const reverseKey = this.stringValue(name);
264
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${value}, i64 ${reverseKey})`);
265
+ }
266
+ }
267
+ if (symbol) this.declareSlot(symbol, object);
268
+ },
269
+
145
270
  emitIf(statement: IfStatement): void {
146
271
  const thenLabel = this.label("if.then");
147
272
  const elseLabel = statement.elseStatement ? this.label("if.else") : undefined;
@@ -178,7 +303,6 @@ export const statementMethods: StatementMethods = {
178
303
  const nonzero = this.reg();
179
304
  this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
180
305
  this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
181
-
182
306
  this.startBlock(bodyLabel);
183
307
  this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
184
308
  this.emitStatement(statement.statement);
@@ -260,7 +384,7 @@ export const statementMethods: StatementMethods = {
260
384
  const indexPtr = this.alloca();
261
385
  this.emit(` store i64 ${numberLiteral(0)}, i64* ${indexPtr}`);
262
386
  const lengthValue = this.reg();
263
- this.emit(` ${lengthValue} = call i64 @xt_array_length(i64 ${iterable})`);
387
+ this.emit(` ${lengthValue} = call i64 @xt_iter_length(i64 ${iterable})`);
264
388
 
265
389
  const condLabel = this.label("forof.cond");
266
390
  const bodyLabel = this.label("forof.body");
@@ -281,7 +405,7 @@ export const statementMethods: StatementMethods = {
281
405
 
282
406
  this.startBlock(bodyLabel);
283
407
  const element = this.reg();
284
- this.emit(` ${element} = call i64 @xt_get(i64 ${iterable}, i64 ${index})`);
408
+ this.emit(` ${element} = call i64 @xt_iter_value(i64 ${iterable}, i64 ${index})`);
285
409
  this.bindLoopVariable(statement.initializer, element);
286
410
  this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
287
411
  this.emitStatement(statement.statement);
@@ -300,7 +424,11 @@ export const statementMethods: StatementMethods = {
300
424
  if (initializer.kind === SyntaxKind.VariableDeclarationList) {
301
425
  const declaration = initializer.declarations[0];
302
426
  if (!declaration) return;
303
- const symbol = this.binding.symbolOfDeclaration.get(declaration);
427
+ if (declaration.name.kind !== SyntaxKind.Identifier) {
428
+ this.emitBindingPattern(declaration.name, value);
429
+ return;
430
+ }
431
+ const symbol = this.binding.symbolOfDeclaration.get(declaration) ?? this.binding.symbolOfDeclaration.get(declaration.name);
304
432
  if (symbol) {
305
433
  if (this.current.slots.has(symbol.id)) this.writeSlot(symbol, value);
306
434
  else this.declareSlot(symbol, value);
@@ -79,6 +79,18 @@ export const GLOBAL_FUNCTIONS: Record<string, string> = {
79
79
  Boolean: "xt_boolean_ctor",
80
80
  };
81
81
 
82
+ /** Global functions that are also usable as first-class values, each mapped to
83
+ * a runtime trampoline with the closure ABI. */
84
+ export const BUILTIN_FUNCTION_VALUES: Record<string, string> = {
85
+ parseInt: "xt_builtin_value_parseInt",
86
+ parseFloat: "xt_builtin_value_parseFloat",
87
+ isNaN: "xt_builtin_value_isNaN",
88
+ isFinite: "xt_builtin_value_isFinite",
89
+ Number: "xt_builtin_value_number",
90
+ String: "xt_builtin_value_string",
91
+ Boolean: "xt_builtin_value_boolean",
92
+ };
93
+
82
94
  /** Global namespaces whose static methods map to runtime dispatchers. */
83
95
  export const NAMESPACE_STATICS: Record<string, string> = {
84
96
  Math: "xt_math_call",
@@ -107,6 +119,7 @@ export const NAMESPACE_STATICS: Record<string, string> = {
107
119
  /** Namespace identifiers whose property access maps to a runtime getter. */
108
120
  export const NAMESPACE_PROPERTIES: Record<string, string> = {
109
121
  process: "xt_process_get",
122
+ "import.meta": "xt_import_meta",
110
123
  };
111
124
 
112
125
  /** Global constructors called as `new X(...)` (all have signature `(i32, i64*)`). */
@@ -116,6 +129,7 @@ export const CTOR_FUNCTIONS: Record<string, string> = {
116
129
  Set: "xt_set_ctor",
117
130
  Date: "xt_date_ctor",
118
131
  RegExp: "xt_regexp_ctor",
132
+ Error: "xt_error_ctor",
119
133
  Promise: "xt_promise_ctor",
120
134
  Buffer: "xt_buffer_ctor",
121
135
  Readable: "xt_readable_ctor",
@@ -5,32 +5,101 @@
5
5
  * value uses a tag in the high 16 bits plus a 48-bit payload. The compiler
6
6
  * emits these constants directly into LLVM IR, so this module (together with
7
7
  * runtime/rt.h) is the single source of truth for the representation.
8
+ *
9
+ * The constants are written as pre-computed signed 64-bit decimal literals
10
+ * rather than BigInt expressions, and `numberLiteral` derives the raw bits of a
11
+ * double with plain arithmetic. That keeps this module free of BigInt and
12
+ * typed-array APIs so the compiler can be compiled by itself on every platform.
8
13
  */
9
14
 
10
- export const XT_TAG_MASK = 0xffff000000000000n;
11
- export const XT_NUMBER_MASK = 0xfff8000000000000n;
15
+ /** `0xffff000000000000` as a signed i64. */
16
+ export const XT_TAG_MASK = "-281474976710656";
17
+ /** `0xfff8000000000000` as a signed i64. */
18
+ export const XT_NUMBER_MASK = "-2251799813685248";
12
19
 
13
- export const XT_UNDEFINED = 0xfff8000000000000n;
14
- export const XT_NULL = 0xfff9000000000000n;
15
- export const XT_FALSE = 0xfffa000000000000n;
16
- export const XT_TRUE = 0xfffb000000000000n;
17
- export const XT_STRING = 0xfffc000000000000n;
18
- export const XT_OBJECT = 0xfffd000000000000n;
19
- export const XT_ARRAY = 0xfffe000000000000n;
20
- export const XT_FUNCTION = 0xffff000000000000n;
20
+ export const XT_UNDEFINED = "-2251799813685248"; // 0xfff8000000000000
21
+ export const XT_NULL = "-1970324836974592"; // 0xfff9000000000000
22
+ export const XT_FALSE = "-1688849860263936"; // 0xfffa000000000000
23
+ export const XT_TRUE = "-1407374883553280"; // 0xfffb000000000000
24
+ export const XT_STRING = "-1125899906842624"; // 0xfffc000000000000
25
+ export const XT_OBJECT = "-844424930131968"; // 0xfffd000000000000
26
+ export const XT_ARRAY = "-562949953421312"; // 0xfffe000000000000
27
+ export const XT_FUNCTION = "-281474976710656"; // 0xffff000000000000
21
28
 
22
29
  /** Render a 64-bit constant the way LLVM expects signed i64 literals. */
23
- export function i64(value: bigint): string {
24
- return BigInt.asIntN(64, value).toString();
30
+ export function i64(value: string): string {
31
+ return value;
25
32
  }
26
33
 
27
34
  /** The raw 64 bits of an IEEE-754 double, as a signed i64 literal. */
28
35
  export function numberLiteral(value: number): string {
29
- const view = new DataView(new ArrayBuffer(8));
30
- view.setFloat64(0, value);
31
- return i64(view.getBigInt64(0));
36
+ if (value !== value) return "9221120237041090560"; // 0x7ff8000000000000 (NaN)
37
+ const negative = value < 0 || (value === 0 && 1 / value < 0);
38
+ const abs = negative ? -value : value;
39
+ let high: number;
40
+ let low: number;
41
+ if (abs === 0) {
42
+ high = 0;
43
+ low = 0;
44
+ } else if (abs === Infinity) {
45
+ high = 0x7ff00000;
46
+ low = 0;
47
+ } else {
48
+ let exponent = Math.floor(Math.log2(abs));
49
+ while (exponent < 1023 && Math.pow(2, exponent + 1) <= abs) exponent++;
50
+ while (exponent > -1022 && Math.pow(2, exponent) > abs) exponent--;
51
+ let mantissa = Math.round((abs / Math.pow(2, exponent) - 1) * 4503599627370496);
52
+ let biased = exponent + 1023;
53
+ if (biased <= 0) {
54
+ mantissa = Math.round((abs / Math.pow(2, -1022)) * 4503599627370496);
55
+ biased = 0;
56
+ } else if (mantissa >= 4503599627370496) {
57
+ mantissa = 0;
58
+ biased += 1;
59
+ }
60
+ high = biased * 0x100000 + Math.floor(mantissa / 0x100000000);
61
+ low = mantissa % 0x100000000;
62
+ }
63
+ if (negative) high += 0x80000000;
64
+ return signedDecimal(high >>> 0, low >>> 0);
32
65
  }
33
66
 
34
67
  export function booleanLiteral(value: boolean): string {
35
- return i64(value ? XT_TRUE : XT_FALSE);
68
+ return value ? XT_TRUE : XT_FALSE;
69
+ }
70
+
71
+ /** Two's-complement (high, low) 32-bit halves as a signed decimal string. */
72
+ function signedDecimal(high: number, low: number): string {
73
+ if (high >= 0x80000000) {
74
+ let h = (~high) >>> 0;
75
+ let l = (~low) >>> 0;
76
+ l = (l + 1) >>> 0;
77
+ if (l === 0) h = (h + 1) >>> 0;
78
+ return "-" + unsignedDecimal(h, l);
79
+ }
80
+ return unsignedDecimal(high, low);
81
+ }
82
+
83
+ /** Unsigned 64-bit `high * 2^32 + low` as a decimal string. */
84
+ function unsignedDecimal(high: number, low: number): string {
85
+ const digits: number[] = [];
86
+ let h = high;
87
+ if (h === 0) digits.push(0);
88
+ while (h > 0) {
89
+ digits.push(h % 10);
90
+ h = Math.floor(h / 10);
91
+ }
92
+ let carry = low;
93
+ for (let i = 0; i < digits.length; i++) {
94
+ const current = digits[i]! * 4294967296 + carry;
95
+ digits[i] = current % 10;
96
+ carry = Math.floor(current / 10);
97
+ }
98
+ while (carry > 0) {
99
+ digits.push(carry % 10);
100
+ carry = Math.floor(carry / 10);
101
+ }
102
+ let out = "";
103
+ for (let i = digits.length - 1; i >= 0; i--) out += digits[i]!;
104
+ return out;
36
105
  }
@@ -80,15 +80,20 @@ export class SourceFile {
80
80
  * and stable across platforms which is exactly what the incremental cache
81
81
  * needs. Collision resistance is not a security property here.
82
82
  */
83
- export function hashText(text: string, seed = 0xcbf29ce484222325n): string {
84
- let hash = seed;
85
- const prime = 0x100000001b3n;
86
- const mask = 0xffffffffffffffffn;
83
+ export function hashText(text: string, seed = 0): string {
84
+ // Two independent 32-bit FNV-1a lanes give a wide, stable fingerprint
85
+ // without relying on BigInt, which the compiler cannot assume at runtime.
86
+ let a = (0xcbf29ce4 ^ seed) >>> 0;
87
+ let b = (0x84222325 ^ seed) >>> 0;
88
+ const prime = 0x01000193;
87
89
  for (let i = 0; i < text.length; i++) {
88
- hash ^= BigInt(text.charCodeAt(i));
89
- hash = (hash * prime) & mask;
90
+ const code = text.charCodeAt(i);
91
+ a = Math.imul(a ^ (code & 0xff), prime) >>> 0;
92
+ a = Math.imul(a ^ (code >>> 8), prime) >>> 0;
93
+ b = Math.imul(b ^ (code >>> 8), prime) >>> 0;
94
+ b = Math.imul(b ^ (code & 0xff), prime) >>> 0;
90
95
  }
91
- return hash.toString(16).padStart(16, "0");
96
+ return (a >>> 0).toString(16).padStart(8, "0") + (b >>> 0).toString(16).padStart(8, "0");
92
97
  }
93
98
 
94
99
  /** Combine several hashes/strings into one stable fingerprint. */
@@ -7,7 +7,7 @@
7
7
  * nothing relevant changed.
8
8
  */
9
9
 
10
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
11
11
  import { basename, dirname, extname, join, resolve } from "node:path";
12
12
  import { DiagnosticBag, type Diagnostic } from "../diagnostics/diagnostic.js";
13
13
  import { SourceFile } from "../diagnostics/source.js";
@@ -73,6 +73,35 @@ export function compileString(source: string, fileName = "input.ts", extensions?
73
73
  return { ir, diagnostics: diagnostics.diagnostics };
74
74
  }
75
75
 
76
+ /**
77
+ * Compile a file to LLVM IR, bundling every reachable relative module first so
78
+ * that imports from other TypeScript sources resolve exactly like `build`.
79
+ */
80
+ export function compileEntry(entryPath: string, extensions?: ExtensionRegistry): CompileStringResult {
81
+ const absoluteEntry = resolve(entryPath);
82
+ const sourceText = readFileSync(absoluteEntry, "utf8");
83
+ const file = new SourceFile(absoluteEntry, sourceText);
84
+ const diagnostics = new DiagnosticBag();
85
+ const parser = new Parser(file, diagnostics);
86
+ let sourceFile = parser.parseSourceFile();
87
+ const isModule = sourceFile.statements.some(
88
+ (statement) =>
89
+ statement.kind === SyntaxKind.ImportDeclaration ||
90
+ statement.kind === SyntaxKind.ExportDeclaration ||
91
+ statement.kind === SyntaxKind.ExportAssignment,
92
+ );
93
+ if (isModule) {
94
+ const bundled = bundleModules(absoluteEntry, diagnostics);
95
+ if (bundled) sourceFile = bundled.sourceFile;
96
+ }
97
+ const registry = extensions ?? createDefaultRegistry();
98
+ const { ir } = generate(sourceFile, diagnostics, {
99
+ builtins: registry.builtins(),
100
+ modules: registry.modules(),
101
+ });
102
+ return { ir, diagnostics: diagnostics.diagnostics };
103
+ }
104
+
76
105
  function executableName(entry: string, outDir: string, explicit: string | undefined): string {
77
106
  if (explicit) return resolve(explicit);
78
107
  const base = basename(entry, extname(entry));
@@ -217,9 +246,22 @@ function ensureRuntimeObjects(
217
246
  cacheDir: string,
218
247
  registry: ExtensionRegistry,
219
248
  ): RuntimeObjects {
249
+ /* Runtime objects depend on the shared headers (rt.h/rt_internal.h/...),
250
+ so a header change must invalidate every cached object. Hash them all. */
251
+ const headerParts: string[] = [];
252
+ const collectHeaders = (dir: string): void => {
253
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
254
+ const full = join(dir, entry.name);
255
+ if (entry.isDirectory()) collectHeaders(full);
256
+ else if (entry.name.endsWith(".h")) headerParts.push(readFileSync(full, "utf8"));
257
+ }
258
+ };
259
+ collectHeaders(runtimeDir);
260
+ const headerStamp = hashParts(headerParts);
261
+
220
262
  const compileOne = (sourcePath: string, tag: string): string => {
221
263
  const body = readFileSync(sourcePath, "utf8");
222
- const key = hashParts(["runtime", COMPILER_VERSION, tag, body, clang, process.platform]);
264
+ const key = hashParts(["runtime", COMPILER_VERSION, tag, headerStamp, body, clang, process.platform]);
223
265
  const objectPath = join(cacheDir, `${tag}-${key}.o`);
224
266
  if (!existsSync(objectPath)) {
225
267
  compileC(runner, clang, sourcePath, objectPath, runtimeDir);
@@ -65,6 +65,18 @@ function isExternalSpecifier(specifier: string): boolean {
65
65
 
66
66
  function resolveModule(fromDir: string, specifier: string): string | undefined {
67
67
  const base = resolve(fromDir, specifier);
68
+ // TypeScript sources are imported using their emitted `.js` extension
69
+ // (`import ... from "./foo.js"`), so map the extension back to the source
70
+ // file before probing the usual suffixes.
71
+ const candidates = [base];
72
+ const jsExtension = /\.(?:m|c)?jsx?$/.exec(base);
73
+ if (jsExtension) {
74
+ const stem = base.slice(0, jsExtension.index);
75
+ candidates.push(`${stem}.ts`, `${stem}.tsx`, `${stem}.mts`, `${stem}.cts`);
76
+ }
77
+ for (const candidate of candidates) {
78
+ if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
79
+ }
68
80
  for (const suffix of RESOLVE_SUFFIXES) {
69
81
  const candidate = base + suffix;
70
82
  if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
@@ -86,8 +98,10 @@ function declarationName(node: Node): Identifier | undefined {
86
98
  case SyntaxKind.TypeAliasDeclaration:
87
99
  case SyntaxKind.ModuleDeclaration:
88
100
  return (node as { name?: Identifier }).name;
89
- case SyntaxKind.VariableDeclaration:
90
- return (node as VariableDeclaration).name;
101
+ case SyntaxKind.VariableDeclaration: {
102
+ const name = (node as VariableDeclaration).name;
103
+ return name.kind === SyntaxKind.Identifier ? name : undefined;
104
+ }
91
105
  default:
92
106
  return undefined;
93
107
  }
@@ -349,7 +363,7 @@ function exportedNames(statement: Statement): string[] {
349
363
  const names: string[] = [];
350
364
  if (statement.kind === SyntaxKind.VariableStatement) {
351
365
  for (const declaration of (statement as VariableStatement).declarationList.declarations) {
352
- if (declaration.name) names.push(declaration.name.text);
366
+ collectBindingIdentifiers(declaration.name, names);
353
367
  }
354
368
  } else {
355
369
  const name = declarationName(statement);
@@ -358,6 +372,17 @@ function exportedNames(statement: Statement): string[] {
358
372
  return names;
359
373
  }
360
374
 
375
+ function collectBindingIdentifiers(name: import("../ast/declarations.js").BindingName, out: string[]): void {
376
+ if (name.kind === SyntaxKind.Identifier) {
377
+ out.push(name.text);
378
+ return;
379
+ }
380
+ const elements = name.kind === SyntaxKind.ArrayBindingPattern ? name.elements : name.elements;
381
+ for (const element of elements) {
382
+ if (element) collectBindingIdentifiers(element.name, out);
383
+ }
384
+ }
385
+
361
386
  /** Turn `export default expr` into a synthetic `const` declaration. */
362
387
  function exportAssignmentToStatement(record: ModuleRecord, assignment: ExportAssignment): Statement {
363
388
  const name: Identifier = {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Node's `child_process` module: `spawnSync(command, args, options)`, backed by
3
+ * `runtime/ext_node/child_process`. Captures stdout/stderr as UTF-8 strings and
4
+ * honours `options.cwd`, which is all the compiler's toolchain wrapper needs.
5
+ */
6
+
7
+ import type { NodeModule } from "../module.js";
8
+ import { resolveFrom } from "../module.js";
9
+ import type { BuiltinFunction, ModuleExports } from "../../registry.js";
10
+
11
+ const builtins: Record<string, BuiltinFunction> = {
12
+ spawnSync: { symbol: "xt_child_process_spawn_sync" },
13
+ };
14
+
15
+ const exports: ModuleExports = {
16
+ spawnSync: { symbol: "xt_child_process_spawn_sync" },
17
+ };
18
+
19
+ export const childProcessModule: NodeModule = {
20
+ name: "child_process",
21
+ runtimeSources: () => [resolveFrom(import.meta.url, "../../../../runtime/ext_node/child_process/child_process.c")],
22
+ builtins: () => builtins,
23
+ exports: () => exports,
24
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Node's `crypto` module: `createHash(...).update(...).digest("hex")`, backed
3
+ * by a self-contained SHA-256 in `runtime/ext_node/crypto`.
4
+ */
5
+
6
+ import type { NodeModule } from "../module.js";
7
+ import { resolveFrom } from "../module.js";
8
+ import type { BuiltinFunction, ModuleExports } from "../../registry.js";
9
+
10
+ const builtins: Record<string, BuiltinFunction> = {
11
+ createHash: { symbol: "xt_crypto_create_hash" },
12
+ };
13
+
14
+ const exports: ModuleExports = {
15
+ createHash: { symbol: "xt_crypto_create_hash" },
16
+ };
17
+
18
+ export const cryptoModule: NodeModule = {
19
+ name: "crypto",
20
+ runtimeSources: () => [resolveFrom(import.meta.url, "../../../../runtime/ext_node/crypto/crypto.c")],
21
+ builtins: () => builtins,
22
+ exports: () => exports,
23
+ };
@@ -22,6 +22,9 @@ import { streamModule } from "./stream/index.js";
22
22
  import { netModule } from "./net/index.js";
23
23
  import { dgramModule } from "./dgram/index.js";
24
24
  import { httpModule } from "./http/index.js";
25
+ import { urlModule } from "./url/index.js";
26
+ import { cryptoModule } from "./crypto/index.js";
27
+ import { childProcessModule } from "./child_process/index.js";
25
28
 
26
29
  /** Every Node module the extension currently provides. */
27
30
  const modules: readonly NodeModule[] = [
@@ -35,6 +38,9 @@ const modules: readonly NodeModule[] = [
35
38
  netModule,
36
39
  dgramModule,
37
40
  httpModule,
41
+ urlModule,
42
+ cryptoModule,
43
+ childProcessModule,
38
44
  ];
39
45
 
40
46
  export const nodeExtension: Extension = {
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { dirname, resolve } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
+ import { findRuntimeDir } from "../../driver/paths.js";
10
11
  import type { BuiltinFunction, ModuleExports } from "../registry.js";
11
12
 
12
13
  export interface NodeModule {
@@ -26,7 +27,18 @@ export interface NodeModule {
26
27
  exports?(): ModuleExports;
27
28
  }
28
29
 
29
- /** Resolve a path relative to the folder of the module that calls this. */
30
+ /**
31
+ * Resolve a C runtime source that is written relative to an extension module
32
+ * (e.g. `../../../../runtime/ext_node/fs/read_file.c`).
33
+ *
34
+ * When running from source the page is resolved against the calling module's
35
+ * own URL, but inside a compiled xbintsc binary `import.meta.url` points at the
36
+ * executable, so the module-relative form no longer resolves. Anchor on the
37
+ * discovered runtime directory instead, which is correct in both worlds.
38
+ */
30
39
  export function resolveFrom(importMetaUrl: string, relative: string): string {
40
+ const marker = "runtime/";
41
+ const index = relative.indexOf(marker);
42
+ if (index >= 0) return resolve(findRuntimeDir(), relative.slice(index + marker.length));
31
43
  return resolve(dirname(fileURLToPath(importMetaUrl)), relative);
32
44
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Node's `url` module: `pathToFileURL` / `fileURLToPath`, backed by
3
+ * `runtime/ext_node/url`. These are the helpers the compiler uses to relate
4
+ * `import.meta.url`, `process.argv[1]` and filesystem paths.
5
+ */
6
+
7
+ import type { NodeModule } from "../module.js";
8
+ import { resolveFrom } from "../module.js";
9
+ import type { BuiltinFunction, ModuleExports } from "../../registry.js";
10
+
11
+ const builtins: Record<string, BuiltinFunction> = {
12
+ pathToFileURL: { symbol: "xt_url_path_to_file_url" },
13
+ fileURLToPath: { symbol: "xt_url_file_url_to_path" },
14
+ };
15
+
16
+ const exports: ModuleExports = {
17
+ pathToFileURL: { symbol: "xt_url_path_to_file_url" },
18
+ fileURLToPath: { symbol: "xt_url_file_url_to_path" },
19
+ };
20
+
21
+ export const urlModule: NodeModule = {
22
+ name: "url",
23
+ runtimeSources: () => [resolveFrom(import.meta.url, "../../../../runtime/ext_node/url/url.c")],
24
+ builtins: () => builtins,
25
+ exports: () => exports,
26
+ };