xbintsc 0.3.3 → 0.3.5

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 (215) hide show
  1. package/dist/src/ast/expressions.d.ts +8 -0
  2. package/dist/src/binder/binder.js +1 -0
  3. package/dist/src/binder/binder.js.map +1 -1
  4. package/dist/src/binder/references.d.ts +1 -1
  5. package/dist/src/binder/references.js +34 -5
  6. package/dist/src/binder/references.js.map +1 -1
  7. package/dist/src/binder/types.d.ts +4 -0
  8. package/dist/src/binder/types.js.map +1 -1
  9. package/dist/src/cli/main.js.map +1 -1
  10. package/dist/src/codegen/generator/calls/access.d.ts +9 -1
  11. package/dist/src/codegen/generator/calls/access.js +146 -13
  12. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  13. package/dist/src/codegen/generator/calls/closures.d.ts +1 -0
  14. package/dist/src/codegen/generator/calls/closures.js +25 -0
  15. package/dist/src/codegen/generator/calls/closures.js.map +1 -1
  16. package/dist/src/codegen/generator/calls/invocation.js +12 -1
  17. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  18. package/dist/src/codegen/generator/calls/literals.js +12 -4
  19. package/dist/src/codegen/generator/calls/literals.js.map +1 -1
  20. package/dist/src/codegen/generator/calls/super.js +4 -7
  21. package/dist/src/codegen/generator/calls/super.js.map +1 -1
  22. package/dist/src/codegen/generator/context.d.ts +21 -2
  23. package/dist/src/codegen/generator/context.js +42 -5
  24. package/dist/src/codegen/generator/context.js.map +1 -1
  25. package/dist/src/codegen/generator/expressions/assignment.js +1 -3
  26. package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
  27. package/dist/src/codegen/generator/expressions/primary.d.ts +3 -1
  28. package/dist/src/codegen/generator/expressions/primary.js +99 -1
  29. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  30. package/dist/src/codegen/generator/generator.js.map +1 -1
  31. package/dist/src/codegen/generator/module.js +25 -15
  32. package/dist/src/codegen/generator/module.js.map +1 -1
  33. package/dist/src/codegen/generator/state.d.ts +15 -0
  34. package/dist/src/codegen/generator/state.js +28 -1
  35. package/dist/src/codegen/generator/state.js.map +1 -1
  36. package/dist/src/codegen/generator/statements/control-flow.d.ts +3 -1
  37. package/dist/src/codegen/generator/statements/control-flow.js +85 -0
  38. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  39. package/dist/src/codegen/generator/statements/dispatch.js +15 -6
  40. package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
  41. package/dist/src/codegen/generator/statements/loops.d.ts +3 -0
  42. package/dist/src/codegen/generator/statements/loops.js +25 -11
  43. package/dist/src/codegen/generator/statements/loops.js.map +1 -1
  44. package/dist/src/codegen/generator/tables.d.ts +7 -0
  45. package/dist/src/codegen/generator/tables.js +41 -1
  46. package/dist/src/codegen/generator/tables.js.map +1 -1
  47. package/dist/src/driver/compiler.d.ts +1 -1
  48. package/dist/src/driver/compiler.js +33 -1
  49. package/dist/src/driver/compiler.js.map +1 -1
  50. package/dist/src/driver/modules.d.ts +4 -3
  51. package/dist/src/driver/modules.js +61 -4
  52. package/dist/src/driver/modules.js.map +1 -1
  53. package/dist/src/driver/toolchain.js +1 -1
  54. package/dist/src/driver/toolchain.js.map +1 -1
  55. package/dist/src/lexer/scanner.js.map +1 -1
  56. package/dist/src/parser/declarations/classes.js +4 -0
  57. package/dist/src/parser/declarations/classes.js.map +1 -1
  58. package/dist/src/parser/declarations/type-members.js.map +1 -1
  59. package/dist/src/parser/expressions.d.ts +2 -1
  60. package/dist/src/parser/expressions.js +10 -1
  61. package/dist/src/parser/expressions.js.map +1 -1
  62. package/dist/src/parser/helpers.js +2 -2
  63. package/dist/src/parser/helpers.js.map +1 -1
  64. package/dist/src/parser/literals.js +79 -6
  65. package/dist/src/parser/literals.js.map +1 -1
  66. package/dist/src/parser/modules.js.map +1 -1
  67. package/dist/src/parser/parser.js +0 -1
  68. package/dist/src/parser/parser.js.map +1 -1
  69. package/dist/src/parser/statements.d.ts +1 -0
  70. package/dist/src/parser/statements.js +13 -1
  71. package/dist/src/parser/statements.js.map +1 -1
  72. package/dist/src/parser/types.js +2 -2
  73. package/dist/src/parser/types.js.map +1 -1
  74. package/dist/tests/ast/factory.test.d.ts +1 -0
  75. package/dist/tests/ast/factory.test.js +35 -0
  76. package/dist/tests/ast/factory.test.js.map +1 -0
  77. package/dist/tests/ast/visitor.test.d.ts +1 -0
  78. package/dist/tests/ast/visitor.test.js +53 -0
  79. package/dist/tests/ast/visitor.test.js.map +1 -0
  80. package/dist/tests/cli/main.test.js +109 -9
  81. package/dist/tests/cli/main.test.js.map +1 -1
  82. package/dist/tests/codegen/closures.test.d.ts +1 -0
  83. package/dist/tests/codegen/closures.test.js +32 -0
  84. package/dist/tests/codegen/closures.test.js.map +1 -0
  85. package/dist/tests/codegen/expressions.test.d.ts +1 -0
  86. package/dist/tests/codegen/expressions.test.js +149 -0
  87. package/dist/tests/codegen/expressions.test.js.map +1 -0
  88. package/dist/tests/codegen/llvm.test.js +14 -0
  89. package/dist/tests/codegen/llvm.test.js.map +1 -1
  90. package/dist/tests/codegen/values.test.d.ts +1 -0
  91. package/dist/tests/codegen/values.test.js +48 -0
  92. package/dist/tests/codegen/values.test.js.map +1 -0
  93. package/dist/tests/diagnostics/diagnostic.test.d.ts +1 -0
  94. package/dist/tests/diagnostics/diagnostic.test.js +117 -0
  95. package/dist/tests/diagnostics/diagnostic.test.js.map +1 -0
  96. package/dist/tests/diagnostics/source.test.d.ts +1 -0
  97. package/dist/tests/diagnostics/source.test.js +58 -0
  98. package/dist/tests/diagnostics/source.test.js.map +1 -0
  99. package/dist/tests/driver/modules.test.d.ts +1 -0
  100. package/dist/tests/driver/modules.test.js +140 -0
  101. package/dist/tests/driver/modules.test.js.map +1 -0
  102. package/dist/tests/driver/paths.test.js +17 -1
  103. package/dist/tests/driver/paths.test.js.map +1 -1
  104. package/dist/tests/driver/runtime-lib.test.d.ts +1 -0
  105. package/dist/tests/driver/runtime-lib.test.js +19 -0
  106. package/dist/tests/driver/runtime-lib.test.js.map +1 -0
  107. package/dist/tests/driver/toolchain-provider.test.js +91 -0
  108. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  109. package/dist/tests/driver/toolchain.test.js +11 -1
  110. package/dist/tests/driver/toolchain.test.js.map +1 -1
  111. package/dist/tests/e2e/compile-node.test.js +14 -1
  112. package/dist/tests/e2e/compile-node.test.js.map +1 -1
  113. package/dist/tests/e2e/compile.test.js +465 -2
  114. package/dist/tests/e2e/compile.test.js.map +1 -1
  115. package/dist/tests/e2e/differential.test.d.ts +7 -0
  116. package/dist/tests/e2e/differential.test.js +581 -0
  117. package/dist/tests/e2e/differential.test.js.map +1 -0
  118. package/dist/tests/e2e/harness.d.ts +21 -0
  119. package/dist/tests/e2e/harness.js +26 -0
  120. package/dist/tests/e2e/harness.js.map +1 -1
  121. package/dist/tests/extensions/node.test.d.ts +1 -0
  122. package/dist/tests/extensions/node.test.js +32 -0
  123. package/dist/tests/extensions/node.test.js.map +1 -0
  124. package/dist/tests/lexer/identifiers.test.d.ts +1 -0
  125. package/dist/tests/lexer/identifiers.test.js +13 -0
  126. package/dist/tests/lexer/identifiers.test.js.map +1 -0
  127. package/dist/tests/lexer/numbers.test.d.ts +1 -0
  128. package/dist/tests/lexer/numbers.test.js +23 -0
  129. package/dist/tests/lexer/numbers.test.js.map +1 -0
  130. package/dist/tests/lexer/strings.test.d.ts +1 -0
  131. package/dist/tests/lexer/strings.test.js +72 -0
  132. package/dist/tests/lexer/strings.test.js.map +1 -0
  133. package/dist/tests/parser/declarations.test.d.ts +1 -0
  134. package/dist/tests/parser/declarations.test.js +70 -0
  135. package/dist/tests/parser/declarations.test.js.map +1 -0
  136. package/dist/tests/parser/expressions.test.d.ts +1 -0
  137. package/dist/tests/parser/expressions.test.js +47 -0
  138. package/dist/tests/parser/expressions.test.js.map +1 -0
  139. package/dist/tests/parser/helpers.test.d.ts +1 -0
  140. package/dist/tests/parser/helpers.test.js +58 -0
  141. package/dist/tests/parser/helpers.test.js.map +1 -0
  142. package/dist/tests/parser/literals.test.d.ts +1 -0
  143. package/dist/tests/parser/literals.test.js +70 -0
  144. package/dist/tests/parser/literals.test.js.map +1 -0
  145. package/dist/tests/parser/module-syntax.test.d.ts +1 -0
  146. package/dist/tests/parser/module-syntax.test.js +91 -0
  147. package/dist/tests/parser/module-syntax.test.js.map +1 -0
  148. package/dist/tests/parser/statements.test.d.ts +1 -0
  149. package/dist/tests/parser/statements.test.js +85 -0
  150. package/dist/tests/parser/statements.test.js.map +1 -0
  151. package/dist/tests/parser/type-members.test.d.ts +1 -0
  152. package/dist/tests/parser/type-members.test.js +86 -0
  153. package/dist/tests/parser/type-members.test.js.map +1 -0
  154. package/dist/tests/parser/types.test.d.ts +1 -0
  155. package/dist/tests/parser/types.test.js +143 -0
  156. package/dist/tests/parser/types.test.js.map +1 -0
  157. package/package.json +6 -1
  158. package/runtime/ext_node/fs/fs_ops.c +36 -1
  159. package/runtime/rt.h +17 -0
  160. package/runtime/rt_internal.h +88 -2
  161. package/runtime/xt_builtins.c +174 -6
  162. package/runtime/xt_containers.c +234 -42
  163. package/runtime/xt_generator.c +304 -0
  164. package/runtime/xt_io.c +170 -30
  165. package/runtime/xt_promise.c +6 -1
  166. package/runtime/xt_regex.h +105 -35
  167. package/runtime/xt_stdlib/dispatch.inc +255 -4
  168. package/runtime/xt_stdlib/strings.inc +17 -13
  169. package/runtime/xt_stdlib2/arrays.inc +126 -19
  170. package/runtime/xt_stdlib2/date.inc +9 -2
  171. package/runtime/xt_stdlib2/error.inc +162 -12
  172. package/runtime/xt_stdlib2/helpers.inc +4 -0
  173. package/runtime/xt_stdlib2/json.inc +19 -6
  174. package/runtime/xt_stdlib2/number-string-statics.inc +1 -1
  175. package/runtime/xt_stdlib2/numbers.inc +4 -5
  176. package/runtime/xt_stdlib2/objects.inc +29 -0
  177. package/runtime/xt_stdlib2/regexp-match.inc +293 -51
  178. package/runtime/xt_stdlib2/set.inc +58 -2
  179. package/runtime/xt_stdlib2/strings.inc +38 -0
  180. package/runtime/xt_symbol.c +149 -0
  181. package/runtime/xt_values.c +221 -15
  182. package/scripts/build-runtime.ts +1 -1
  183. package/src/ast/expressions.ts +8 -0
  184. package/src/binder/binder.ts +1 -1
  185. package/src/binder/references.ts +47 -4
  186. package/src/binder/types.ts +4 -0
  187. package/src/cli/main.ts +1 -2
  188. package/src/codegen/generator/calls/access.ts +158 -13
  189. package/src/codegen/generator/calls/closures.ts +24 -0
  190. package/src/codegen/generator/calls/invocation.ts +11 -1
  191. package/src/codegen/generator/calls/literals.ts +10 -4
  192. package/src/codegen/generator/calls/super.ts +6 -9
  193. package/src/codegen/generator/context.ts +44 -7
  194. package/src/codegen/generator/expressions/assignment.ts +1 -3
  195. package/src/codegen/generator/expressions/primary.ts +108 -2
  196. package/src/codegen/generator/generator.ts +0 -1
  197. package/src/codegen/generator/module.ts +24 -18
  198. package/src/codegen/generator/state.ts +44 -1
  199. package/src/codegen/generator/statements/control-flow.ts +86 -0
  200. package/src/codegen/generator/statements/dispatch.ts +18 -6
  201. package/src/codegen/generator/statements/loops.ts +29 -11
  202. package/src/codegen/generator/tables.ts +44 -1
  203. package/src/driver/compiler.ts +32 -2
  204. package/src/driver/modules.ts +64 -4
  205. package/src/driver/toolchain.ts +1 -1
  206. package/src/lexer/scanner.ts +0 -1
  207. package/src/parser/declarations/classes.ts +4 -0
  208. package/src/parser/declarations/type-members.ts +0 -2
  209. package/src/parser/expressions.ts +13 -1
  210. package/src/parser/helpers.ts +2 -2
  211. package/src/parser/literals.ts +82 -7
  212. package/src/parser/modules.ts +0 -1
  213. package/src/parser/parser.ts +0 -1
  214. package/src/parser/statements.ts +14 -0
  215. package/src/parser/types.ts +2 -2
@@ -9,7 +9,6 @@ import {
9
9
  type ArrowFunction,
10
10
  type Block,
11
11
  type Expression,
12
- type Identifier,
13
12
  type Parameter,
14
13
  } from "../../ast/nodes.js";
15
14
  import type { ClassInfo, FunctionInfo, SymbolInfo } from "../../binder/binder.js";
@@ -94,7 +93,9 @@ export const moduleMethods: ModuleMethods = {
94
93
  label: 0,
95
94
  terminated: false,
96
95
  loops: [],
96
+ pendingLabels: [],
97
97
  tryFrames: [],
98
+ finallyStack: [],
98
99
  escapePointers: [],
99
100
  usesTry: false,
100
101
  };
@@ -157,11 +158,8 @@ export const moduleMethods: ModuleMethods = {
157
158
  if (fn.isConstructor && fn.classInfo?.parentExpression) {
158
159
  const explicitBody = (fn.node as { body?: Block }).body;
159
160
  const parent = fn.classInfo.parentExpression;
160
- const unboundBuiltin =
161
- parent.kind === SyntaxKind.Identifier &&
162
- (parent as Identifier).text === "Error" &&
163
- !this.binding.symbolOfIdentifier.get(parent as Identifier);
164
- if (!explicitBody && !unboundBuiltin) {
161
+ const errorName = this.errorFamilyName(parent);
162
+ if (!explicitBody && !errorName) {
165
163
  const parentValue = this.emitExpression(parent);
166
164
  const parentProto = this.reg();
167
165
  this.emit(` ${parentProto} = call i64 @xt_function_get_prototype(i64 ${parentValue})`);
@@ -170,10 +168,12 @@ export const moduleMethods: ModuleMethods = {
170
168
  this.emit(` ${parentCtor} = call i64 @xt_get(i64 ${parentProto}, i64 ${ctorKey})`);
171
169
  const superThis = this.emitThis();
172
170
  this.emit(` call i64 @xt_call_with_this(i64 ${parentCtor}, i64 ${superThis}, i32 %argc, i64* %argv)`);
173
- } else if (!explicitBody && unboundBuiltin) {
174
- // `extends Error` with no explicit constructor: initialise message/name.
171
+ } else if (!explicitBody && errorName) {
172
+ // `extends Error` / `extends TypeError` with no explicit constructor:
173
+ // initialise the instance's name and message from the arguments.
175
174
  const superThis = this.emitThis();
176
- this.emit(` call i64 @xt_error_init(i64 ${superThis}, i32 %argc, i64* %argv)`);
175
+ const nameValue = this.stringValue(errorName);
176
+ this.emit(` call i64 @xt_error_init(i64 ${superThis}, i64 ${nameValue}, i32 %argc, i64* %argv)`);
177
177
  }
178
178
  }
179
179
 
@@ -274,14 +274,16 @@ export const moduleMethods: ModuleMethods = {
274
274
  const proto = this.reg();
275
275
  this.emit(` ${proto} = call i64 @xt_object_new()`);
276
276
  if (classInfo.parentExpression) {
277
- // `extends Error` (and other host builtins) have no runtime class value;
278
- // the subclass still works, it simply does not inherit a prototype.
277
+ // `extends Error` / `extends TypeError` / other host builtins: chain the
278
+ // subclass prototype onto the builtin's prototype so `instanceof` works.
279
279
  const parent = classInfo.parentExpression;
280
- const unboundBuiltin =
281
- parent.kind === SyntaxKind.Identifier &&
282
- (parent as Identifier).text === "Error" &&
283
- !this.binding.symbolOfIdentifier.get(parent as Identifier);
284
- if (!unboundBuiltin) {
280
+ const errorName = this.errorFamilyName(parent);
281
+ if (errorName) {
282
+ const nameValue = this.stringValue(errorName);
283
+ const parentProto = this.reg();
284
+ this.emit(` ${parentProto} = call i64 @xt_error_prototype(i64 ${nameValue})`);
285
+ this.emit(` call i64 @xt_object_set_prototype(i64 ${proto}, i64 ${parentProto})`);
286
+ } else {
285
287
  const parentValue = this.emitExpression(parent);
286
288
  const parentProto = this.reg();
287
289
  this.emit(` ${parentProto} = call i64 @xt_function_get_prototype(i64 ${parentValue})`);
@@ -290,7 +292,9 @@ export const moduleMethods: ModuleMethods = {
290
292
  }
291
293
  for (const method of classInfo.methods) {
292
294
  const fnValue = this.emitClosureValue(method);
293
- const key = this.stringValue(method.name);
295
+ const key = method.computedKey
296
+ ? this.emitExpression(method.computedKey)
297
+ : this.stringValue(method.name);
294
298
  const accessor = (method.node as { accessor?: "get" | "set" }).accessor;
295
299
  if (accessor === "get") {
296
300
  this.emit(` call i64 @xt_object_define_getter(i64 ${proto}, i64 ${key}, i64 ${fnValue})`);
@@ -306,7 +310,9 @@ export const moduleMethods: ModuleMethods = {
306
310
  this.emit(` call i64 @xt_set(i64 ${proto}, i64 ${ctorKey}, i64 ${ctor})`);
307
311
  for (const method of classInfo.statics) {
308
312
  const fnValue = this.emitClosureValue(method);
309
- const key = this.stringValue(method.name);
313
+ const key = method.computedKey
314
+ ? this.emitExpression(method.computedKey)
315
+ : this.stringValue(method.name);
310
316
  this.emit(` call i64 @xt_set(i64 ${ctor}, i64 ${key}, i64 ${fnValue})`);
311
317
  }
312
318
  for (const field of classInfo.fields) {
@@ -5,6 +5,7 @@
5
5
  import type { FunctionInfo } from "../../binder/binder.js";
6
6
  import type { BindResult } from "../../binder/binder.js";
7
7
  import type { BuiltinFunction, ExtensionModule } from "../../extensions/registry.js";
8
+ import type { Block } from "../../ast/nodes.js";
8
9
 
9
10
  export interface Slot {
10
11
  /** Register holding the `i64*` to the storage. */
@@ -12,11 +13,23 @@ export interface Slot {
12
13
  readonly boxed: boolean;
13
14
  }
14
15
 
16
+ /**
17
+ * One enclosing `try`/`finally` region, used to run the `finally` block when an
18
+ * abrupt completion (`return`/`break`/`continue`) exits it.
19
+ */
20
+ export interface FinallyContext {
21
+ /** Number of active `try` frames just before this region's frame. */
22
+ readonly frameDepth: number;
23
+ readonly block: Block;
24
+ }
25
+
15
26
  export interface LoopLabels {
16
27
  readonly breakLabel: string;
17
28
  readonly continueLabel: string;
18
29
  /** Number of enclosing `try` frames when the loop was entered. */
19
30
  readonly tryDepth?: number;
31
+ /** Labels attached to this loop (outermost first), for `break`/`continue`. */
32
+ readonly labels?: readonly string[];
20
33
  }
21
34
 
22
35
  export interface FunctionState {
@@ -28,7 +41,10 @@ export interface FunctionState {
28
41
  label: number;
29
42
  terminated: boolean;
30
43
  readonly loops: LoopLabels[];
44
+ /** Labels on enclosing `LabeledStatement`s, consumed by the next loop. */
45
+ readonly pendingLabels: string[];
31
46
  readonly tryFrames: string[];
47
+ readonly finallyStack: FinallyContext[];
32
48
  readonly escapePointers: string[];
33
49
  /** Alloca holding the `this` binding for the current function. */
34
50
  thisPtr?: string;
@@ -57,6 +73,8 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
57
73
  "declare i64 @xt_string_from_cstr(i8*)",
58
74
  "declare i64 @xt_arg(i32, i64*, i32)",
59
75
  "declare i64 @xt_closure_new(i8*, i32, i64*)",
76
+ "declare i64 @xt_function_set_metadata(i64, i64, i32)",
77
+ "declare i64 @xt_function_set_generator(i64)",
60
78
  "declare i64 @xt_closure_call(i64, i32, i64*)",
61
79
  "declare i64 @xt_closure_env(i64, i32)",
62
80
  "declare i64 @xt_call_with_this(i64, i64, i32, i64*)",
@@ -108,7 +126,16 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
108
126
  "declare i64 @xt_date_ctor(i32, i64*)",
109
127
  "declare i64 @xt_regexp_ctor(i32, i64*)",
110
128
  "declare i64 @xt_error_ctor(i32, i64*)",
111
- "declare i64 @xt_error_init(i64, i32, i64*)",
129
+ "declare i64 @xt_type_error_ctor(i32, i64*)",
130
+ "declare i64 @xt_range_error_ctor(i32, i64*)",
131
+ "declare i64 @xt_syntax_error_ctor(i32, i64*)",
132
+ "declare i64 @xt_reference_error_ctor(i32, i64*)",
133
+ "declare i64 @xt_eval_error_ctor(i32, i64*)",
134
+ "declare i64 @xt_uri_error_ctor(i32, i64*)",
135
+ "declare i64 @xt_aggregate_error_ctor(i32, i64*)",
136
+ "declare i64 @xt_error_constructor(i64)",
137
+ "declare i64 @xt_error_prototype(i64)",
138
+ "declare i64 @xt_error_init(i64, i64, i32, i64*)",
112
139
  "declare i64 @xt_json_parse(i32, i64*)",
113
140
  "declare i64 @xt_json_stringify(i32, i64*)",
114
141
  "declare i64 @xt_object_from_entries(i64)",
@@ -169,6 +196,10 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
169
196
  "declare i64 @xt_parse_float(i32, i64*)",
170
197
  "declare i64 @xt_is_nan(i32, i64*)",
171
198
  "declare i64 @xt_is_finite(i32, i64*)",
199
+ "declare i64 @xt_encode_uri_component(i32, i64*)",
200
+ "declare i64 @xt_encode_uri(i32, i64*)",
201
+ "declare i64 @xt_decode_uri_component(i32, i64*)",
202
+ "declare i64 @xt_decode_uri(i32, i64*)",
172
203
  "declare i64 @xt_number_ctor(i32, i64*)",
173
204
  "declare i64 @xt_bigint_ctor(i32, i64*)",
174
205
  "declare i64 @xt_bigint_static(i64, i32, i64*)",
@@ -185,17 +216,29 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
185
216
  "declare i64 @xt_array_length(i64)",
186
217
  "declare i64 @xt_array_spread(i64, i64)",
187
218
  "declare i64 @xt_iter_length(i64)",
219
+ "declare i64 @xt_iter_open(i64)",
220
+ "declare i64 @xt_iter_has(i64, i64)",
188
221
  "declare i64 @xt_iter_value(i64, i64)",
222
+ "declare i64 @xt_yield(i64)",
223
+ "declare i64 @xt_yield_star(i64)",
189
224
  "declare i32 @xt_array_size(i64)",
190
225
  "declare i64* @xt_array_items(i64)",
191
226
  "declare i64 @xt_builtin_value_parseInt(i64, i64, i32, i64*)",
192
227
  "declare i64 @xt_builtin_value_parseFloat(i64, i64, i32, i64*)",
193
228
  "declare i64 @xt_builtin_value_isNaN(i64, i64, i32, i64*)",
194
229
  "declare i64 @xt_builtin_value_isFinite(i64, i64, i32, i64*)",
230
+ "declare i64 @xt_builtin_value_encodeURIComponent(i64, i64, i32, i64*)",
231
+ "declare i64 @xt_builtin_value_encodeURI(i64, i64, i32, i64*)",
232
+ "declare i64 @xt_builtin_value_decodeURIComponent(i64, i64, i32, i64*)",
233
+ "declare i64 @xt_builtin_value_decodeURI(i64, i64, i32, i64*)",
195
234
  "declare i64 @xt_builtin_value_number(i64, i64, i32, i64*)",
196
235
  "declare i64 @xt_builtin_value_string(i64, i64, i32, i64*)",
197
236
  "declare i64 @xt_builtin_value_boolean(i64, i64, i32, i64*)",
198
237
  "declare i64 @xt_builtin_value_bigint(i64, i64, i32, i64*)",
238
+ "declare i64 @xt_builtin_value_symbol(i64, i64, i32, i64*)",
239
+ "declare i64 @xt_symbol(i32, i64*)",
240
+ "declare i64 @xt_symbol_static(i64, i32, i64*)",
241
+ "declare i64 @xt_symbol_get(i64)",
199
242
  "declare i64 @xt_get(i64, i64)",
200
243
  "declare i64 @xt_set(i64, i64, i64)",
201
244
  "declare i64 @xt_box_new(i64)",
@@ -7,7 +7,9 @@ import {
7
7
  type CaseClause,
8
8
  type Identifier,
9
9
  type IfStatement,
10
+ type LabeledStatement,
10
11
  type ReturnStatement,
12
+ type Statement,
11
13
  type SwitchStatement,
12
14
  type TryStatement,
13
15
  } from "../../../ast/nodes.js";
@@ -16,14 +18,54 @@ import type { Generator } from "../generator.js";
16
18
 
17
19
  export interface ControlFlowStatementMethods {
18
20
  emitIf(this: Generator, statement: IfStatement): void;
21
+ emitLabeled(this: Generator, statement: LabeledStatement): void;
19
22
  emitSwitch(this: Generator, statement: SwitchStatement): void;
20
23
  emitTry(this: Generator, statement: TryStatement): void;
21
24
  bindCatchVariable(this: Generator, variable: Identifier | undefined, value: string): void;
22
25
  emitReturn(this: Generator, statement: ReturnStatement): void;
23
26
  popTryFramesTo(this: Generator, depth: number): void;
27
+ runFinallysBeforeExit(this: Generator, targetTryDepth: number): void;
24
28
  }
25
29
 
26
30
  export const controlFlowStatementMethods: ControlFlowStatementMethods = {
31
+ /**
32
+ * `label: statement`. A label on a loop is attached to that loop's break and
33
+ * continue targets; a label on any other statement becomes a breakable
34
+ * region so `break label` can jump to its end.
35
+ */
36
+ emitLabeled(statement: LabeledStatement): void {
37
+ const labels: string[] = [];
38
+ let current: Statement = statement;
39
+ while (current.kind === SyntaxKind.LabeledStatement) {
40
+ labels.push((current as LabeledStatement).label.text);
41
+ current = (current as LabeledStatement).statement;
42
+ }
43
+ const isLoop =
44
+ current.kind === SyntaxKind.WhileStatement ||
45
+ current.kind === SyntaxKind.DoStatement ||
46
+ current.kind === SyntaxKind.ForStatement ||
47
+ current.kind === SyntaxKind.ForOfStatement ||
48
+ current.kind === SyntaxKind.ForInStatement;
49
+ if (isLoop) {
50
+ this.current.pendingLabels.push(...labels);
51
+ this.emitStatement(current);
52
+ /* The loop consumed them; guard against a loop kind that did not. */
53
+ this.current.pendingLabels.length = 0;
54
+ return;
55
+ }
56
+ const endLabel = this.label("label.end");
57
+ this.current.loops.push({
58
+ breakLabel: endLabel,
59
+ continueLabel: endLabel,
60
+ labels,
61
+ tryDepth: this.current.tryFrames.length,
62
+ });
63
+ this.emitStatement(current);
64
+ this.current.loops.pop();
65
+ if (!this.current.terminated) this.terminate(`br label %${endLabel}`);
66
+ this.startBlock(endLabel);
67
+ },
68
+
27
69
  emitIf(statement: IfStatement): void {
28
70
  const thenLabel = this.label("if.then");
29
71
  const elseLabel = statement.elseStatement ? this.label("if.else") : undefined;
@@ -115,6 +157,10 @@ export const controlFlowStatementMethods: ControlFlowStatementMethods = {
115
157
  const finallyLabel = statement.finallyBlock ? this.label("try.finally") : undefined;
116
158
  const endLabel = this.label("try.end");
117
159
  const hasCatch = !!statement.catchClause;
160
+ const finallyContext =
161
+ statement.finallyBlock && finallyLabel
162
+ ? { frameDepth: this.current.tryFrames.length, block: statement.finallyBlock }
163
+ : undefined;
118
164
 
119
165
  const frame = this.reg();
120
166
  this.emit(` ${frame} = call i8* @xt_try_enter()`);
@@ -136,7 +182,9 @@ export const controlFlowStatementMethods: ControlFlowStatementMethods = {
136
182
  // Normal completion of the try block.
137
183
  this.startBlock(tryLabel);
138
184
  this.current.tryFrames.push(frameSlot);
185
+ if (finallyContext) this.current.finallyStack.push(finallyContext);
139
186
  this.emitStatements(statement.tryBlock.statements);
187
+ if (finallyContext) this.current.finallyStack.pop();
140
188
  this.current.tryFrames.pop();
141
189
  if (!this.current.terminated) {
142
190
  const currentFrame = this.reg();
@@ -153,7 +201,9 @@ export const controlFlowStatementMethods: ControlFlowStatementMethods = {
153
201
  this.emit(` ${exception} = call i64 @xt_try_exception(i8* ${currentFrame})`);
154
202
  this.emit(` call void @xt_try_leave(i8* ${currentFrame})`);
155
203
  this.bindCatchVariable(statement.catchClause!.variable, exception);
204
+ if (finallyContext) this.current.finallyStack.push(finallyContext);
156
205
  this.emitStatements(statement.catchClause!.block.statements);
206
+ if (finallyContext) this.current.finallyStack.pop();
157
207
  if (!this.current.terminated) this.terminate(`br label %${finallyLabel ?? endLabel}`);
158
208
  } else {
159
209
  // Without a catch clause the exception is remembered, then rethrown
@@ -209,6 +259,8 @@ export const controlFlowStatementMethods: ControlFlowStatementMethods = {
209
259
  emitReturn(statement: ReturnStatement): void {
210
260
  let value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
211
261
  if (this.current.fn.isAsync) value = this.wrapAsync(value);
262
+ this.runFinallysBeforeExit(0);
263
+ if (this.current.terminated) return;
212
264
  this.popTryFramesTo(0);
213
265
  this.terminate(`ret i64 ${value}`);
214
266
  },
@@ -222,4 +274,38 @@ export const controlFlowStatementMethods: ControlFlowStatementMethods = {
222
274
  this.emit(` call void @xt_try_leave(i8* ${frame})`);
223
275
  }
224
276
  },
277
+
278
+ /**
279
+ * Run the `finally` blocks of every region whose frame is above
280
+ * `targetTryDepth`, innermost first, so an abrupt completion
281
+ * (`return`/`break`/`continue`) observes JavaScript's guarantee that each
282
+ * `finally` runs before control leaves its `try`.
283
+ *
284
+ * The blocks are emitted inline on the exiting path (the shared `finally`
285
+ * block still serves normal and exceptional completion). While a block runs,
286
+ * only the *outer* regions stay on {@link FunctionState.finallyStack}, so a
287
+ * `return` written inside `finally` overrides the pending completion without
288
+ * re-entering the same block. The stack is restored afterwards because the
289
+ * emission is path-local: sibling branches must still see every region.
290
+ */
291
+ runFinallysBeforeExit(targetTryDepth: number): void {
292
+ const saved = this.current.finallyStack.slice();
293
+ const restore = (): void => {
294
+ this.current.finallyStack.length = 0;
295
+ this.current.finallyStack.push(...saved);
296
+ };
297
+ for (let index = saved.length - 1; index >= 0; index--) {
298
+ const context = saved[index]!;
299
+ if (context.frameDepth < targetTryDepth) break;
300
+ this.current.finallyStack.length = 0;
301
+ this.current.finallyStack.push(...saved.slice(0, index));
302
+ this.popTryFramesTo(context.frameDepth);
303
+ this.emitStatements(context.block.statements);
304
+ if (this.current.terminated) {
305
+ restore();
306
+ return;
307
+ }
308
+ }
309
+ restore();
310
+ },
225
311
  };
@@ -7,6 +7,8 @@
7
7
  import {
8
8
  SyntaxKind,
9
9
  type Block,
10
+ type BreakStatement,
11
+ type ContinueStatement,
10
12
  type DoStatement,
11
13
  type EnumDeclaration,
12
14
  type Expression,
@@ -14,6 +16,7 @@ import {
14
16
  type ForOfStatement,
15
17
  type ForStatement,
16
18
  type IfStatement,
19
+ type LabeledStatement,
17
20
  type ReturnStatement,
18
21
  type Statement,
19
22
  type SwitchStatement,
@@ -63,6 +66,9 @@ export const statementDispatchMethods: StatementDispatchMethods = {
63
66
  case SyntaxKind.IfStatement:
64
67
  this.emitIf(statement as IfStatement);
65
68
  return;
69
+ case SyntaxKind.LabeledStatement:
70
+ this.emitLabeled(statement as LabeledStatement);
71
+ return;
66
72
  case SyntaxKind.WhileStatement:
67
73
  this.emitWhile(statement as WhileStatement);
68
74
  return;
@@ -86,18 +92,24 @@ export const statementDispatchMethods: StatementDispatchMethods = {
86
92
  this.emitReturn(statement as ReturnStatement);
87
93
  return;
88
94
  case SyntaxKind.BreakStatement: {
89
- const loop = this.current.loops[this.current.loops.length - 1];
95
+ const loop = this.findLoop((statement as BreakStatement).label?.text);
90
96
  if (loop) {
91
- this.popTryFramesTo(loop.tryDepth ?? 0);
92
- this.terminate(`br label %${loop.breakLabel}`);
97
+ this.runFinallysBeforeExit(loop.tryDepth ?? 0);
98
+ if (!this.current.terminated) {
99
+ this.popTryFramesTo(loop.tryDepth ?? 0);
100
+ this.terminate(`br label %${loop.breakLabel}`);
101
+ }
93
102
  }
94
103
  return;
95
104
  }
96
105
  case SyntaxKind.ContinueStatement: {
97
- const loop = this.current.loops[this.current.loops.length - 1];
106
+ const loop = this.findLoop((statement as ContinueStatement).label?.text);
98
107
  if (loop) {
99
- this.popTryFramesTo(loop.tryDepth ?? 0);
100
- this.terminate(`br label %${loop.continueLabel}`);
108
+ this.runFinallysBeforeExit(loop.tryDepth ?? 0);
109
+ if (!this.current.terminated) {
110
+ this.popTryFramesTo(loop.tryDepth ?? 0);
111
+ this.terminate(`br label %${loop.continueLabel}`);
112
+ }
101
113
  }
102
114
  return;
103
115
  }
@@ -14,6 +14,7 @@ import {
14
14
  } from "../../../ast/nodes.js";
15
15
  import { numberLiteral } from "../../values.js";
16
16
  import type { Generator } from "../generator.js";
17
+ import type { LoopLabels } from "../state.js";
17
18
 
18
19
  export interface LoopStatementMethods {
19
20
  emitWhile(this: Generator, statement: WhileStatement): void;
@@ -21,9 +22,25 @@ export interface LoopStatementMethods {
21
22
  emitFor(this: Generator, statement: ForStatement): void;
22
23
  emitForOf(this: Generator, statement: ForOfStatement | ForInStatement): void;
23
24
  bindLoopVariable(this: Generator, initializer: VariableDeclarationList | Expression, value: string): void;
25
+ takePendingLabels(this: Generator): string[];
26
+ findLoop(this: Generator, label: string | undefined): LoopLabels | undefined;
24
27
  }
25
28
 
26
29
  export const loopStatementMethods: LoopStatementMethods = {
30
+ /** Consume labels accumulated by enclosing `LabeledStatement`s. */
31
+ takePendingLabels(): string[] {
32
+ return this.current.pendingLabels.splice(0, this.current.pendingLabels.length);
33
+ },
34
+
35
+ /** Innermost loop, or the innermost loop carrying `label`. */
36
+ findLoop(label: string | undefined) {
37
+ for (let i = this.current.loops.length - 1; i >= 0; i--) {
38
+ const loop = this.current.loops[i]!;
39
+ if (!label || loop.labels?.includes(label)) return loop;
40
+ }
41
+ return undefined;
42
+ },
43
+
27
44
  emitWhile(statement: WhileStatement): void {
28
45
  const condLabel = this.label("while.cond");
29
46
  const bodyLabel = this.label("while.body");
@@ -37,7 +54,7 @@ export const loopStatementMethods: LoopStatementMethods = {
37
54
  this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
38
55
  this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
39
56
  this.startBlock(bodyLabel);
40
- this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
57
+ this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length, labels: this.takePendingLabels() });
41
58
  this.emitStatement(statement.statement);
42
59
  this.current.loops.pop();
43
60
  if (!this.current.terminated) this.terminate(`br label %${condLabel}`);
@@ -50,7 +67,7 @@ export const loopStatementMethods: LoopStatementMethods = {
50
67
  const endLabel = this.label("do.end");
51
68
  this.terminate(`br label %${bodyLabel}`);
52
69
  this.startBlock(bodyLabel);
53
- this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length });
70
+ this.current.loops.push({ breakLabel: endLabel, continueLabel: condLabel, tryDepth: this.current.tryFrames.length, labels: this.takePendingLabels() });
54
71
  this.emitStatement(statement.statement);
55
72
  this.current.loops.pop();
56
73
  if (!this.current.terminated) this.terminate(`br label %${condLabel}`);
@@ -96,7 +113,7 @@ export const loopStatementMethods: LoopStatementMethods = {
96
113
  }
97
114
 
98
115
  this.startBlock(bodyLabel);
99
- this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
116
+ this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length, labels: this.takePendingLabels() });
100
117
  this.emitStatement(statement.statement);
101
118
  this.current.loops.pop();
102
119
  if (!this.current.terminated) this.terminate(`br label %${updateLabel}`);
@@ -112,12 +129,12 @@ export const loopStatementMethods: LoopStatementMethods = {
112
129
  const source = this.emitExpression(statement.expression);
113
130
  const isForIn = statement.kind === SyntaxKind.ForInStatement;
114
131
  // `for...in` iterates the enumerable keys (indices become strings);
115
- // `for...of` iterates the values at each index.
116
- const iterable = isForIn ? this.runtimeCall("xt_object_keys", [source]) : source;
132
+ // `for...of` resolves the iteration protocol once, then walks it.
133
+ const iterable = isForIn
134
+ ? this.runtimeCall("xt_object_keys", [source])
135
+ : this.runtimeCall("xt_iter_open", [source]);
117
136
  const indexPtr = this.alloca();
118
137
  this.emit(` store i64 ${numberLiteral(0)}, i64* ${indexPtr}`);
119
- const lengthValue = this.reg();
120
- this.emit(` ${lengthValue} = call i64 @xt_iter_length(i64 ${iterable})`);
121
138
 
122
139
  const condLabel = this.label("forof.cond");
123
140
  const bodyLabel = this.label("forof.body");
@@ -128,10 +145,11 @@ export const loopStatementMethods: LoopStatementMethods = {
128
145
  this.startBlock(condLabel);
129
146
  const index = this.reg();
130
147
  this.emit(` ${index} = load i64, i64* ${indexPtr}`);
131
- const inRange = this.reg();
132
- this.emit(` ${inRange} = call i64 @xt_lt(i64 ${index}, i64 ${lengthValue})`);
148
+ /* `xt_iter_has` covers generators too, which cannot be sized up front. */
149
+ const hasValue = this.reg();
150
+ this.emit(` ${hasValue} = call i64 @xt_iter_has(i64 ${iterable}, i64 ${index})`);
133
151
  const truthy = this.reg();
134
- this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${inRange})`);
152
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${hasValue})`);
135
153
  const nonzero = this.reg();
136
154
  this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
137
155
  this.terminate(`br i1 ${nonzero}, label %${bodyLabel}, label %${endLabel}`);
@@ -140,7 +158,7 @@ export const loopStatementMethods: LoopStatementMethods = {
140
158
  const element = this.reg();
141
159
  this.emit(` ${element} = call i64 @xt_iter_value(i64 ${iterable}, i64 ${index})`);
142
160
  this.bindLoopVariable(statement.initializer, element);
143
- this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length });
161
+ this.current.loops.push({ breakLabel: endLabel, continueLabel: updateLabel, tryDepth: this.current.tryFrames.length, labels: this.takePendingLabels() });
144
162
  this.emitStatement(statement.statement);
145
163
  this.current.loops.pop();
146
164
  if (!this.current.terminated) this.terminate(`br label %${updateLabel}`);
@@ -69,15 +69,51 @@ export const MATH_CONSTANTS: Record<string, number> = {
69
69
  SQRT1_2: Math.SQRT1_2,
70
70
  };
71
71
 
72
+ /** Read-only numeric constants exposed on the global `Number` namespace. */
73
+ export const NUMBER_CONSTANTS = new Set<string>([
74
+ "MAX_SAFE_INTEGER",
75
+ "MIN_SAFE_INTEGER",
76
+ "MAX_VALUE",
77
+ "MIN_VALUE",
78
+ "EPSILON",
79
+ "POSITIVE_INFINITY",
80
+ "NEGATIVE_INFINITY",
81
+ "NaN",
82
+ ]);
83
+
84
+ /** The Error family. Each name is a first-class constructor: usable with `new`,
85
+ * as a value (`instanceof`, `typeof`) and as a superclass (`extends TypeError`).
86
+ * All map to runtime constructors with signature `(i32, i64*)`. */
87
+ export const ERROR_CONSTRUCTORS: Record<string, string> = {
88
+ Error: "xt_error_ctor",
89
+ TypeError: "xt_type_error_ctor",
90
+ RangeError: "xt_range_error_ctor",
91
+ SyntaxError: "xt_syntax_error_ctor",
92
+ ReferenceError: "xt_reference_error_ctor",
93
+ EvalError: "xt_eval_error_ctor",
94
+ URIError: "xt_uri_error_ctor",
95
+ AggregateError: "xt_aggregate_error_ctor",
96
+ };
97
+
98
+ export function isErrorFamily(name: string): boolean {
99
+ return ERROR_CONSTRUCTORS[name] !== undefined;
100
+ }
101
+
72
102
  export const GLOBAL_FUNCTIONS: Record<string, string> = {
73
103
  parseInt: "xt_parse_int",
74
104
  parseFloat: "xt_parse_float",
75
105
  isNaN: "xt_is_nan",
76
106
  isFinite: "xt_is_finite",
107
+ encodeURIComponent: "xt_encode_uri_component",
108
+ encodeURI: "xt_encode_uri",
109
+ decodeURIComponent: "xt_decode_uri_component",
110
+ decodeURI: "xt_decode_uri",
77
111
  Number: "xt_number_ctor",
78
112
  String: "xt_string_ctor",
79
113
  Boolean: "xt_boolean_ctor",
80
114
  BigInt: "xt_bigint_ctor",
115
+ Symbol: "xt_symbol",
116
+ ...ERROR_CONSTRUCTORS,
81
117
  };
82
118
 
83
119
  /** Global functions that are also usable as first-class values, each mapped to
@@ -87,10 +123,15 @@ export const BUILTIN_FUNCTION_VALUES: Record<string, string> = {
87
123
  parseFloat: "xt_builtin_value_parseFloat",
88
124
  isNaN: "xt_builtin_value_isNaN",
89
125
  isFinite: "xt_builtin_value_isFinite",
126
+ encodeURIComponent: "xt_builtin_value_encodeURIComponent",
127
+ encodeURI: "xt_builtin_value_encodeURI",
128
+ decodeURIComponent: "xt_builtin_value_decodeURIComponent",
129
+ decodeURI: "xt_builtin_value_decodeURI",
90
130
  Number: "xt_builtin_value_number",
91
131
  String: "xt_builtin_value_string",
92
132
  Boolean: "xt_builtin_value_boolean",
93
133
  BigInt: "xt_builtin_value_bigint",
134
+ Symbol: "xt_builtin_value_symbol",
94
135
  };
95
136
 
96
137
  /** Global namespaces whose static methods map to runtime dispatchers. */
@@ -102,6 +143,7 @@ export const NAMESPACE_STATICS: Record<string, string> = {
102
143
  Number: "xt_number_static",
103
144
  String: "xt_string_static",
104
145
  BigInt: "xt_bigint_static",
146
+ Symbol: "xt_symbol_static",
105
147
  Date: "xt_date_static",
106
148
  Promise: "xt_promise_static",
107
149
  path: "xt_path_static",
@@ -126,6 +168,7 @@ export const NAMESPACE_STATICS: Record<string, string> = {
126
168
  export const NAMESPACE_PROPERTIES: Record<string, string> = {
127
169
  process: "xt_process_get",
128
170
  "import.meta": "xt_import_meta",
171
+ Symbol: "xt_symbol_get",
129
172
  };
130
173
 
131
174
  /** Global constructors called as `new X(...)` (all have signature `(i32, i64*)`). */
@@ -135,7 +178,6 @@ export const CTOR_FUNCTIONS: Record<string, string> = {
135
178
  Set: "xt_set_ctor",
136
179
  Date: "xt_date_ctor",
137
180
  RegExp: "xt_regexp_ctor",
138
- Error: "xt_error_ctor",
139
181
  Promise: "xt_promise_ctor",
140
182
  Buffer: "xt_buffer_ctor",
141
183
  Readable: "xt_readable_ctor",
@@ -146,6 +188,7 @@ export const CTOR_FUNCTIONS: Record<string, string> = {
146
188
  Socket: "xt_net_socket_ctor",
147
189
  Server: "xt_net_server_ctor",
148
190
  EventEmitter: "xt_event_emitter_ctor",
191
+ ...ERROR_CONSTRUCTORS,
149
192
  };
150
193
 
151
194
  export const BUILTIN_METHODS = new Set<string>([
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
11
- import { basename, dirname, extname, join, resolve } from "node:path";
11
+ import { basename, extname, join, resolve } from "node:path";
12
12
  import { DiagnosticBag, type Diagnostic } from "../diagnostics/diagnostic.js";
13
13
  import { SourceFile } from "../diagnostics/source.js";
14
14
  import { Parser } from "../parser/parser.js";
@@ -153,6 +153,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
153
153
  }
154
154
  }
155
155
 
156
+ const runtimeDir = findRuntimeDir();
156
157
  const cacheKey = hashParts([
157
158
  COMPILER_VERSION,
158
159
  cacheText,
@@ -160,6 +161,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
160
161
  optimize,
161
162
  process.platform,
162
163
  registry.all().map((e) => e.name).join(","),
164
+ runtimeFingerprint(runtimeDir),
163
165
  ]);
164
166
 
165
167
  const cache = new BuildCache(cacheDir);
@@ -207,7 +209,6 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
207
209
  return { outputPath, irPath, cached: false, diagnostics: [], ir };
208
210
  }
209
211
 
210
- const runtimeDir = findRuntimeDir();
211
212
  const { runtimeObjects, extensionObjects } = ensureRuntimeObjects(
212
213
  runner,
213
214
  clang,
@@ -251,10 +252,39 @@ export const RUNTIME_SOURCES = [
251
252
  "xt_stdlib2.c",
252
253
  "xt_promise.c",
253
254
  "xt_loop.c",
255
+ "xt_generator.c",
256
+ "xt_symbol.c",
254
257
  "xt_builtins.c",
255
258
  "xt_io.c",
256
259
  ] as const;
257
260
 
261
+ /**
262
+ * Fingerprint every runtime artifact that can change a linked executable: the
263
+ * C sources, shared headers and `#include`d `.inc` fragments plus the shipped
264
+ * prebuilt archives. It feeds the executable cache key, so a runtime change
265
+ * invalidates a cached binary even when the entry source is untouched — the
266
+ * per-object cache cannot express that, because the freshness check runs before
267
+ * any compilation.
268
+ */
269
+ function runtimeFingerprint(runtimeDir: string): string {
270
+ if (!existsSync(runtimeDir)) return "";
271
+ const parts: string[] = [];
272
+ const walk = (dir: string): void => {
273
+ for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
274
+ const full = join(dir, entry.name);
275
+ if (entry.isDirectory()) {
276
+ walk(full);
277
+ } else if (/\.(c|h|inc)$/.test(entry.name)) {
278
+ parts.push(entry.name, readFileSync(full, "utf8"));
279
+ } else {
280
+ parts.push(entry.name, readFileSync(full).toString("base64"));
281
+ }
282
+ }
283
+ };
284
+ walk(runtimeDir);
285
+ return hashParts(parts);
286
+ }
287
+
258
288
  /**
259
289
  * Compile the core runtime and every extension source, reusing cached object
260
290
  * files keyed on the C source hash. Returns the object paths to link.