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
@@ -5,13 +5,15 @@
5
5
 
6
6
  import {
7
7
  SyntaxKind,
8
+ type CallExpression,
8
9
  type DeleteExpression,
9
10
  type ElementAccessExpression,
11
+ type Expression,
10
12
  type Identifier,
11
13
  type PropertyAccessExpression,
12
14
  } from "../../../ast/nodes.js";
13
15
  import { i64, numberLiteral, XT_TRUE, XT_UNDEFINED } from "../../values.js";
14
- import { MATH_CONSTANTS, NAMESPACE_PROPERTIES } from "../tables.js";
16
+ import { MATH_CONSTANTS, NAMESPACE_PROPERTIES, NUMBER_CONSTANTS } from "../tables.js";
15
17
  import type { Generator } from "../generator.js";
16
18
 
17
19
  export interface AccessCallMethods {
@@ -19,6 +21,14 @@ export interface AccessCallMethods {
19
21
  emitPropertyAccess(this: Generator, node: PropertyAccessExpression): string;
20
22
  emitElementAccess(this: Generator, node: ElementAccessExpression): string;
21
23
  emitOptional(this: Generator, objectValue: string, compute: () => string): string;
24
+ /** True when `node` is (part of) an optional chain: a `?.` appears anywhere
25
+ * along its member/call spine. Parentheses end the chain. */
26
+ isOptionalChain(this: Generator, node: Expression): boolean;
27
+ emitOptionalChain(this: Generator, node: Expression): string;
28
+ emitChainValue(this: Generator, node: Expression, endLabel: string): string;
29
+ emitChainReceiver(this: Generator, node: Expression, endLabel: string): string;
30
+ guardOptional(this: Generator, value: string, endLabel: string): void;
31
+ emitPropertyGet(this: Generator, object: string, name: string): string;
22
32
  }
23
33
 
24
34
  export const accessCallMethods: AccessCallMethods = {
@@ -59,6 +69,17 @@ export const accessCallMethods: AccessCallMethods = {
59
69
  ) {
60
70
  return numberLiteral(MATH_CONSTANTS[node.name.text]!);
61
71
  }
72
+ if (
73
+ node.expression.kind === SyntaxKind.Identifier &&
74
+ (node.expression as Identifier).text === "Number" &&
75
+ NUMBER_CONSTANTS.has(node.name.text) &&
76
+ !this.binding.symbolOfIdentifier.get(node.expression as Identifier)
77
+ ) {
78
+ const key = this.stringValue(node.name.text);
79
+ const result = this.reg();
80
+ this.emit(` ${result} = call i64 @xt_number_static(i64 ${key}, i32 0, i64* null)`);
81
+ return result;
82
+ }
62
83
  if (
63
84
  node.expression.kind === SyntaxKind.Identifier &&
64
85
  !this.binding.symbolOfIdentifier.get(node.expression as Identifier)
@@ -86,18 +107,7 @@ export const accessCallMethods: AccessCallMethods = {
86
107
  }
87
108
  }
88
109
  const object = this.emitExpression(node.expression);
89
- const access = (): string => {
90
- if (node.name.text === "length") {
91
- const result = this.reg();
92
- this.emit(` ${result} = call i64 @xt_array_length(i64 ${object})`);
93
- return result;
94
- }
95
- const key = this.stringValue(node.name.text);
96
- const result = this.reg();
97
- this.emit(` ${result} = call i64 @xt_get(i64 ${object}, i64 ${key})`);
98
- return result;
99
- };
100
- return node.optional ? this.emitOptional(object, access) : access();
110
+ return this.emitPropertyGet(object, node.name.text);
101
111
  },
102
112
 
103
113
  emitElementAccess(node: ElementAccessExpression): string {
@@ -111,6 +121,141 @@ export const accessCallMethods: AccessCallMethods = {
111
121
  return node.optional ? this.emitOptional(object, access) : access();
112
122
  },
113
123
 
124
+ /** Property read that works on any receiver value (`length` is special-cased
125
+ * because it is meaningful for arrays and strings). */
126
+ emitPropertyGet(object: string, name: string): string {
127
+ if (name === "length") {
128
+ const result = this.reg();
129
+ this.emit(` ${result} = call i64 @xt_array_length(i64 ${object})`);
130
+ return result;
131
+ }
132
+ const key = this.stringValue(name);
133
+ const result = this.reg();
134
+ this.emit(` ${result} = call i64 @xt_get(i64 ${object}, i64 ${key})`);
135
+ return result;
136
+ },
137
+
138
+ /*
139
+ * Optional chaining is a *whole-chain* short circuit in JavaScript:
140
+ * `a?.b.c()` evaluates to `undefined` when `a` is nullish, without reading
141
+ * `.b`/`.c` or calling anything. The parser records `optional` on the
142
+ * individual access node, so we detect the chain here, evaluate it once and
143
+ * branch to a shared end label whenever any `?.` guard fails. The result slot
144
+ * defaults to `undefined` and is overwritten only on the non-short-circuited
145
+ * path.
146
+ */
147
+ isOptionalChain(node: Expression): boolean {
148
+ let current: Expression = node;
149
+ for (;;) {
150
+ if (current.kind === SyntaxKind.PropertyAccessExpression || current.kind === SyntaxKind.ElementAccessExpression) {
151
+ const access = current as PropertyAccessExpression | ElementAccessExpression;
152
+ if (access.optional) return true;
153
+ current = access.expression;
154
+ } else if (current.kind === SyntaxKind.CallExpression) {
155
+ const call = current as CallExpression;
156
+ if (call.optional) return true;
157
+ current = call.expression;
158
+ } else {
159
+ return false;
160
+ }
161
+ }
162
+ },
163
+
164
+ emitOptionalChain(node: Expression): string {
165
+ const result = this.alloca();
166
+ this.emit(` store i64 ${i64(XT_UNDEFINED)}, i64* ${result}`);
167
+ const endLabel = this.label("optchain.end");
168
+ const value = this.emitChainValue(node, endLabel);
169
+ this.emit(` store i64 ${value}, i64* ${result}`);
170
+ this.terminate(`br label %${endLabel}`);
171
+ this.startBlock(endLabel);
172
+ const merged = this.reg();
173
+ this.emit(` ${merged} = load i64, i64* ${result}`);
174
+ return merged;
175
+ },
176
+
177
+ emitChainValue(node: Expression, endLabel: string): string {
178
+ if (node.kind === SyntaxKind.PropertyAccessExpression) {
179
+ const access = node as PropertyAccessExpression;
180
+ const object = this.emitChainReceiver(access.expression, endLabel);
181
+ if (access.optional) this.guardOptional(object, endLabel);
182
+ return this.emitPropertyGet(object, access.name.text);
183
+ }
184
+ if (node.kind === SyntaxKind.ElementAccessExpression) {
185
+ const access = node as ElementAccessExpression;
186
+ const object = this.emitChainReceiver(access.expression, endLabel);
187
+ if (access.optional) this.guardOptional(object, endLabel);
188
+ const key = this.emitExpression(access.argumentExpression);
189
+ return this.runtimeCall("xt_get", [object, key]);
190
+ }
191
+ if (node.kind === SyntaxKind.CallExpression) {
192
+ const call = node as CallExpression;
193
+ const callee = call.expression;
194
+ /* `a.b?.()` / `a[k]?.()`: evaluate the callee, then invoke it with the
195
+ * receiver as `this` so user methods and first-class built-in method
196
+ * values both bind correctly. */
197
+ if (call.optional) {
198
+ if (callee.kind === SyntaxKind.PropertyAccessExpression || callee.kind === SyntaxKind.ElementAccessExpression) {
199
+ const access = callee as PropertyAccessExpression | ElementAccessExpression;
200
+ const object = this.emitChainReceiver(access.expression, endLabel);
201
+ if (access.optional) this.guardOptional(object, endLabel);
202
+ const calleeValue =
203
+ access.kind === SyntaxKind.PropertyAccessExpression
204
+ ? this.emitPropertyGet(object, (access as PropertyAccessExpression).name.text)
205
+ : this.runtimeCall("xt_get", [object, this.emitExpression((access as ElementAccessExpression).argumentExpression)]);
206
+ this.guardOptional(calleeValue, endLabel);
207
+ const args = this.emitArguments(call.arguments);
208
+ const out = this.reg();
209
+ this.emit(` ${out} = call i64 @xt_call_with_this(i64 ${calleeValue}, i64 ${object}, i32 ${args.argc}, i64* ${args.ptr})`);
210
+ return out;
211
+ }
212
+ const calleeValue = this.emitChainReceiver(callee, endLabel);
213
+ this.guardOptional(calleeValue, endLabel);
214
+ const args = this.emitArguments(call.arguments);
215
+ const out = this.reg();
216
+ this.emit(` ${out} = call i64 @xt_closure_call(i64 ${calleeValue}, i32 ${args.argc}, i64* ${args.ptr})`);
217
+ return out;
218
+ }
219
+ /* `a?.b(...)` / `a?.[k](...)`: guard the receiver, then dispatch. */
220
+ if (callee.kind === SyntaxKind.PropertyAccessExpression || callee.kind === SyntaxKind.ElementAccessExpression) {
221
+ const access = callee as PropertyAccessExpression | ElementAccessExpression;
222
+ const object = this.emitChainReceiver(access.expression, endLabel);
223
+ if (access.optional) this.guardOptional(object, endLabel);
224
+ const name =
225
+ access.kind === SyntaxKind.PropertyAccessExpression
226
+ ? this.stringValue((access as PropertyAccessExpression).name.text)
227
+ : this.emitExpression((access as ElementAccessExpression).argumentExpression);
228
+ const args = this.emitArguments(call.arguments);
229
+ const out = this.reg();
230
+ this.emit(` ${out} = call i64 @xt_call_method(i64 ${object}, i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`);
231
+ return out;
232
+ }
233
+ const calleeValue = this.emitChainReceiver(callee, endLabel);
234
+ const args = this.emitArguments(call.arguments);
235
+ const out = this.reg();
236
+ this.emit(` ${out} = call i64 @xt_closure_call(i64 ${calleeValue}, i32 ${args.argc}, i64* ${args.ptr})`);
237
+ return out;
238
+ }
239
+ return this.emitExpression(node);
240
+ },
241
+
242
+ emitChainReceiver(node: Expression, endLabel: string): string {
243
+ return this.isOptionalChain(node) ? this.emitChainValue(node, endLabel) : this.emitExpression(node);
244
+ },
245
+
246
+ /** Branch to `endLabel` (result stays `undefined`) when `value` is nullish. */
247
+ guardOptional(value: string, endLabel: string): void {
248
+ const nullish = this.reg();
249
+ this.emit(` ${nullish} = call i64 @xt_is_nullish(i64 ${value})`);
250
+ const truthy = this.reg();
251
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${nullish})`);
252
+ const condition = this.reg();
253
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
254
+ const cont = this.label("optchain.cont");
255
+ this.terminate(`br i1 ${condition}, label %${endLabel}, label %${cont}`);
256
+ this.startBlock(cont);
257
+ },
258
+
114
259
  /**
115
260
  * Evaluate `object` once and, when it is neither `null` nor `undefined`, run
116
261
  * `compute` to produce the value; otherwise short-circuit to `undefined`.
@@ -10,6 +10,7 @@ import type { Generator } from "../generator.js";
10
10
  export interface ClosureCallMethods {
11
11
  emitClosure(this: Generator, node: ArrowFunction | FunctionExpression): string;
12
12
  emitClosureValue(this: Generator, fn: FunctionInfo): string;
13
+ emitFunctionMetadata(this: Generator, closure: string, fn: FunctionInfo): void;
13
14
  }
14
15
 
15
16
  export const closureCallMethods: ClosureCallMethods = {
@@ -55,6 +56,29 @@ export const closureCallMethods: ClosureCallMethods = {
55
56
  this.emit(` ${cast} = bitcast i64 (i64, i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
56
57
  const closure = this.reg();
57
58
  this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 ${count}, i64* ${envPtr})`);
59
+ this.emitFunctionMetadata(closure, fn);
58
60
  return closure;
59
61
  },
62
+
63
+ /**
64
+ * Attach the JavaScript `name` and `length` (arity) of a closure. Arity
65
+ * counts parameters before the first default / rest parameter, matching the
66
+ * specification.
67
+ */
68
+ emitFunctionMetadata(closure: string, fn: FunctionInfo): void {
69
+ const parameters =
70
+ (fn.node as { parameters?: readonly { initializer?: unknown; dotDotDotToken?: boolean }[] })
71
+ .parameters ?? [];
72
+ let arity = 0;
73
+ for (const parameter of parameters) {
74
+ if (parameter.initializer || parameter.dotDotDotToken) break;
75
+ arity++;
76
+ }
77
+ let name = fn.name;
78
+ if (name === "(anonymous)" || name === "(arrow)") name = "";
79
+ if (fn.isConstructor && fn.classInfo) name = fn.classInfo.name;
80
+ const nameValue = this.stringValue(name);
81
+ this.emit(` call i64 @xt_function_set_metadata(i64 ${closure}, i64 ${nameValue}, i32 ${arity})`);
82
+ if (fn.isGenerator) this.emit(` call i64 @xt_function_set_generator(i64 ${closure})`);
83
+ },
60
84
  };
@@ -100,7 +100,9 @@ export const invocationCallMethods: InvocationCallMethods = {
100
100
  if (symbol && symbol.kind === SymbolKind.Function) {
101
101
  const declaration = symbol.declarations[0];
102
102
  const fn = declaration ? this.binding.functionOfNode.get(declaration) : undefined;
103
- if (fn) {
103
+ /* Generator functions are not called directly: the call must create a
104
+ * suspended generator, so fall through to the closure-call path. */
105
+ if (fn && !fn.isGenerator) {
104
106
  const args = this.emitArguments(node.arguments);
105
107
  const result = this.reg();
106
108
  this.emit(
@@ -204,6 +206,14 @@ export const invocationCallMethods: InvocationCallMethods = {
204
206
  return result;
205
207
  }
206
208
  }
209
+ // Modules with no namespace dispatcher (`fs`, `child_process`, ...): lower
210
+ // `import fs from "node:fs"; fs.readFileSync(...)` through the module's
211
+ // named exports, exactly like a named import of `readFileSync`.
212
+ const moduleExports = this.moduleExportsOfSymbol(targetSymbol);
213
+ if (moduleExports) {
214
+ const exported = moduleExports[method];
215
+ if (exported) return this.emitModuleExport(node, exported);
216
+ }
207
217
  }
208
218
 
209
219
  void BUILTIN_METHODS;
@@ -47,14 +47,20 @@ export const literalCallMethods: LiteralCallMethods = {
47
47
  const nameNode = property.name;
48
48
  let key: string;
49
49
  if (nameNode.kind === SyntaxKind.ComputedPropertyName) {
50
- const keyValue = this.emitExpression((nameNode as { expression: Expression }).expression);
51
- key = this.reg();
52
- this.emit(` ${key} = call i64 @xt_to_string(i64 ${keyValue})`);
50
+ // Property keys may be symbols; `xt_set` normalises with
51
+ // `xt_to_property_key`.
52
+ key = this.emitExpression((nameNode as { expression: Expression }).expression);
53
53
  } else {
54
54
  key = this.stringValue(propertyNameText(nameNode));
55
55
  }
56
56
  const value = this.emitExpression(property.initializer);
57
- this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
57
+ if (property.accessor === "get") {
58
+ this.emit(` call i64 @xt_object_define_getter(i64 ${object}, i64 ${key}, i64 ${value})`);
59
+ } else if (property.accessor === "set") {
60
+ this.emit(` call i64 @xt_object_define_setter(i64 ${object}, i64 ${key}, i64 ${value})`);
61
+ } else {
62
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
63
+ }
58
64
  } else if (property.kind === SyntaxKind.ShorthandPropertyAssignment) {
59
65
  if (property.initializer) {
60
66
  // `{ a = 1 }` is only legal as a destructuring target, not a value.
@@ -3,9 +3,7 @@
3
3
  */
4
4
 
5
5
  import {
6
- SyntaxKind,
7
6
  type CallExpression,
8
- type Identifier,
9
7
  type PropertyAccessExpression,
10
8
  } from "../../../ast/nodes.js";
11
9
  import type { Generator } from "../generator.js";
@@ -20,15 +18,14 @@ export const superCallMethods: SuperCallMethods = {
20
18
  const thisValue = this.emitThis();
21
19
  const classInfo = this.current.fn.classInfo;
22
20
  const parent = classInfo?.parentExpression;
23
- const unboundBuiltin =
24
- parent !== undefined &&
25
- parent.kind === SyntaxKind.Identifier &&
26
- (parent as Identifier).text === "Error" &&
27
- !this.binding.symbolOfIdentifier.get(parent as Identifier);
28
- if (unboundBuiltin) {
21
+ const errorName = parent ? this.errorFamilyName(parent) : undefined;
22
+ if (errorName) {
29
23
  const args = this.emitArguments(node.arguments);
24
+ const nameValue = this.stringValue(errorName);
30
25
  const result = this.reg();
31
- this.emit(` ${result} = call i64 @xt_error_init(i64 ${thisValue}, i32 ${args.argc}, i64* ${args.ptr})`);
26
+ this.emit(
27
+ ` ${result} = call i64 @xt_error_init(i64 ${thisValue}, i64 ${nameValue}, i32 ${args.argc}, i64* ${args.ptr})`,
28
+ );
32
29
  return result;
33
30
  }
34
31
  const proto = this.reg();
@@ -6,18 +6,19 @@
6
6
  * helpers every group relies on.
7
7
  */
8
8
 
9
- import { bind, SymbolKind, type BindResult, type ClassInfo, type SymbolInfo } from "../../binder/binder.js";
9
+ import { bind, SymbolKind, type BindResult, type SymbolInfo } from "../../binder/binder.js";
10
10
  import type { DiagnosticBag } from "../../diagnostics/diagnostic.js";
11
11
  import { DiagnosticCode } from "../../diagnostics/diagnostic.js";
12
- import { SyntaxKind, type ImportDeclaration, type SourceFileNode, type Node } from "../../ast/nodes.js";
12
+ import { SyntaxKind, type Identifier, type ImportDeclaration, type Node, type SourceFileNode } from "../../ast/nodes.js";
13
13
  import { i64, XT_UNDEFINED } from "../values.js";
14
14
  import type {
15
15
  BuiltinFunction,
16
16
  ExtensionModule,
17
17
  ModuleExport,
18
+ ModuleExports,
18
19
  } from "../../extensions/registry.js";
19
20
  import type { CodegenOptions, FunctionState } from "./state.js";
20
- import { escapeBytes, kindName, utf8Bytes } from "./tables.js";
21
+ import { escapeBytes, isErrorFamily, kindName, utf8Bytes } from "./tables.js";
21
22
 
22
23
  export class GeneratorContext {
23
24
  readonly binding: BindResult;
@@ -29,6 +30,13 @@ export class GeneratorContext {
29
30
  readonly importExports = new Map<number, ModuleExport>();
30
31
  /** Imported symbol id -> the namespace it aliases (e.g. `path`). */
31
32
  readonly importNamespaces = new Map<number, string>();
33
+ /**
34
+ * Imported symbol id -> the named exports of the module it aliases, for
35
+ * modules that do not expose a runtime namespace dispatcher (`fs`,
36
+ * `child_process`, `crypto`, ...). Lets `import fs from "node:fs"` lower
37
+ * `fs.readFileSync(...)` straight to the module's runtime symbol.
38
+ */
39
+ readonly importModuleExports = new Map<number, ModuleExports>();
32
40
  readonly globals: string[] = [];
33
41
  readonly functions: string[] = [];
34
42
  readonly strings = new Map<string, { label: string; length: number }>();
@@ -61,9 +69,9 @@ export class GeneratorContext {
61
69
  if (!module) continue;
62
70
  const clause = declaration.importClause;
63
71
  if (!clause) continue;
64
- if (clause.name && module.namespace) {
72
+ if (clause.name) {
65
73
  const symbol = this.binding.symbolOfDeclaration.get(clause.name);
66
- if (symbol) this.importNamespaces.set(symbol.id, module.namespace);
74
+ if (symbol) this.bindModuleAlias(symbol, module);
67
75
  }
68
76
  const bindings = clause.namedBindings;
69
77
  if (bindings && bindings.kind === SyntaxKind.NamedImports) {
@@ -73,19 +81,37 @@ export class GeneratorContext {
73
81
  const symbol = this.binding.symbolOfDeclaration.get(specifier.name);
74
82
  if (symbol && exported) this.importExports.set(symbol.id, exported);
75
83
  }
76
- } else if (bindings && bindings.kind === SyntaxKind.NamespaceImport && module.namespace) {
84
+ } else if (bindings && bindings.kind === SyntaxKind.NamespaceImport) {
77
85
  const symbol = this.binding.symbolOfDeclaration.get(bindings.name);
78
- if (symbol) this.importNamespaces.set(symbol.id, module.namespace);
86
+ if (symbol) this.bindModuleAlias(symbol, module);
79
87
  }
80
88
  }
81
89
  }
82
90
 
91
+ /**
92
+ * Bind a default/namespace import to either the module's runtime namespace
93
+ * dispatcher (when it has one) or its named exports (when it does not).
94
+ */
95
+ private bindModuleAlias(symbol: SymbolInfo, module: ExtensionModule): void {
96
+ if (module.namespace) {
97
+ this.importNamespaces.set(symbol.id, module.namespace);
98
+ } else if (module.exports) {
99
+ this.importModuleExports.set(symbol.id, module.exports);
100
+ }
101
+ }
102
+
83
103
  /** Namespace name an imported alias refers to, if any. */
84
104
  namespaceOfSymbol(symbol: SymbolInfo | undefined): string | undefined {
85
105
  if (!symbol || symbol.kind !== SymbolKind.Import) return undefined;
86
106
  return this.importNamespaces.get(symbol.id);
87
107
  }
88
108
 
109
+ /** Named exports a default/namespace import aliases, if any. */
110
+ moduleExportsOfSymbol(symbol: SymbolInfo | undefined): ModuleExports | undefined {
111
+ if (!symbol || symbol.kind !== SymbolKind.Import) return undefined;
112
+ return this.importModuleExports.get(symbol.id);
113
+ }
114
+
89
115
  // -- emission primitives -------------------------------------------------
90
116
 
91
117
  emit(line: string): void {
@@ -210,6 +236,17 @@ export class GeneratorContext {
210
236
 
211
237
  // -- string pool ---------------------------------------------------------
212
238
 
239
+ /**
240
+ * The name of the builtin Error-family constructor an expression refers to
241
+ * (`extends TypeError`), or `undefined` for a user class / shadowed name.
242
+ */
243
+ errorFamilyName(expression: Node): string | undefined {
244
+ if (expression.kind !== SyntaxKind.Identifier) return undefined;
245
+ const identifier = expression as Identifier;
246
+ if (this.binding.symbolOfIdentifier.get(identifier)) return undefined;
247
+ return isErrorFamily(identifier.text) ? identifier.text : undefined;
248
+ }
249
+
213
250
  stringValue(text: string): string {
214
251
  const entry = this.internString(text);
215
252
  const result = this.reg();
@@ -196,9 +196,7 @@ export const assignmentExpressionMethods: AssignmentExpressionMethods = {
196
196
  const assignment = property as PropertyAssignment;
197
197
  let key: string;
198
198
  if (assignment.name.kind === SyntaxKind.ComputedPropertyName) {
199
- const keyValue = this.emitExpression((assignment.name as { expression: Expression }).expression);
200
- key = this.reg();
201
- this.emit(` ${key} = call i64 @xt_to_string(i64 ${keyValue})`);
199
+ key = this.emitExpression((assignment.name as { expression: Expression }).expression);
202
200
  } else {
203
201
  key = this.stringValue(propertyNameText(assignment.name));
204
202
  }
@@ -9,7 +9,6 @@ import {
9
9
  type AwaitExpression,
10
10
  type BinaryExpression,
11
11
  type CallExpression,
12
- type ClassExpression,
13
12
  type ConditionalExpression,
14
13
  type DeleteExpression,
15
14
  type ElementAccessExpression,
@@ -21,13 +20,15 @@ import {
21
20
  type PostfixUnaryExpression,
22
21
  type PrefixUnaryExpression,
23
22
  type PropertyAccessExpression,
23
+ type TaggedTemplateExpression,
24
24
  type TemplateLiteral,
25
+ type YieldExpression,
25
26
  type ArrayLiteralExpression,
26
27
  } from "../../../ast/nodes.js";
27
28
  import { SymbolKind, type SymbolInfo } from "../../../binder/binder.js";
28
29
  import { DiagnosticCode } from "../../../diagnostics/diagnostic.js";
29
30
  import { i64, numberLiteral, XT_FALSE, XT_NULL, XT_TRUE, XT_UNDEFINED } from "../../values.js";
30
- import { BUILTIN_FUNCTION_VALUES, CTOR_FUNCTIONS } from "../tables.js";
31
+ import { BUILTIN_FUNCTION_VALUES, CTOR_FUNCTIONS, ERROR_CONSTRUCTORS } from "../tables.js";
31
32
  import type { Generator } from "../generator.js";
32
33
 
33
34
  export interface PrimaryExpressionMethods {
@@ -35,13 +36,18 @@ export interface PrimaryExpressionMethods {
35
36
  emitIdentifier(this: Generator, identifier: Identifier): string;
36
37
  emitFunctionValue(this: Generator, symbol: SymbolInfo): string;
37
38
  emitTemplate(this: Generator, node: TemplateLiteral): string;
39
+ emitTaggedTemplate(this: Generator, node: TaggedTemplateExpression): string;
38
40
  emitThis(this: Generator): string;
39
41
  emitNew(this: Generator, node: NewExpression): string;
40
42
  emitAwait(this: Generator, node: AwaitExpression): string;
43
+ emitYield(this: Generator, node: YieldExpression): string;
41
44
  }
42
45
 
43
46
  export const primaryExpressionMethods: PrimaryExpressionMethods = {
44
47
  emitExpression(node: Expression): string {
48
+ /* An optional chain must be lowered as a unit so `?.` short-circuits the
49
+ entire chain (`a?.b.c()`), not just the guarded member. */
50
+ if (this.isOptionalChain(node)) return this.emitOptionalChain(node);
45
51
  switch (node.kind) {
46
52
  case SyntaxKind.Identifier:
47
53
  return this.emitIdentifier(node as Identifier);
@@ -77,6 +83,8 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
77
83
  }
78
84
  case SyntaxKind.TemplateLiteral:
79
85
  return this.emitTemplate(node as TemplateLiteral);
86
+ case SyntaxKind.TaggedTemplateExpression:
87
+ return this.emitTaggedTemplate(node as TaggedTemplateExpression);
80
88
  case SyntaxKind.TrueKeyword:
81
89
  return i64(XT_TRUE);
82
90
  case SyntaxKind.FalseKeyword:
@@ -108,6 +116,8 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
108
116
  return this.emitNew(node as NewExpression);
109
117
  case SyntaxKind.AwaitExpression:
110
118
  return this.emitAwait(node as AwaitExpression);
119
+ case SyntaxKind.YieldExpression:
120
+ return this.emitYield(node as YieldExpression);
111
121
  case SyntaxKind.ClassExpression: {
112
122
  const info = this.binding.classOfNode.get(node);
113
123
  if (!info) {
@@ -198,6 +208,13 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
198
208
  this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 0, i64* null)`);
199
209
  return closure;
200
210
  }
211
+ const errorCtor = ERROR_CONSTRUCTORS[identifier.text];
212
+ if (errorCtor) {
213
+ // A first-class Error-family constructor (`instanceof TypeError`,
214
+ // `typeof RangeError`, `class X extends Error`, ...).
215
+ const nameValue = this.stringValue(identifier.text);
216
+ return this.runtimeCall("xt_error_constructor", [nameValue]);
217
+ }
201
218
  this.diagnostics.error(
202
219
  DiagnosticCode.CannotFindName,
203
220
  `Cannot find name '${identifier.text}'`,
@@ -217,6 +234,7 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
217
234
  this.emit(` ${cast} = bitcast i64 (i64, i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
218
235
  const closure = this.reg();
219
236
  this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 0, i64* null)`);
237
+ this.emitFunctionMetadata(closure, fn);
220
238
  return closure;
221
239
  },
222
240
 
@@ -234,6 +252,19 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
234
252
  return result;
235
253
  },
236
254
 
255
+ emitYield(node: YieldExpression): string {
256
+ if (node.delegate) {
257
+ const delegate = this.emitExpression(node.expression as Expression);
258
+ const result = this.reg();
259
+ this.emit(` ${result} = call i64 @xt_yield_star(i64 ${delegate})`);
260
+ return result;
261
+ }
262
+ const value = node.expression ? this.emitExpression(node.expression) : i64(XT_UNDEFINED);
263
+ const result = this.reg();
264
+ this.emit(` ${result} = call i64 @xt_yield(i64 ${value})`);
265
+ return result;
266
+ },
267
+
237
268
  emitNew(node: NewExpression): string {
238
269
  const callee = node.expression;
239
270
  if (callee.kind === SyntaxKind.Identifier) {
@@ -301,4 +332,79 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
301
332
  }
302
333
  return accumulator;
303
334
  },
335
+
336
+ /**
337
+ * Lower a tagged template: build the (cooked) strings array with a `.raw`
338
+ * sibling and call the tag with it followed by the interpolation values.
339
+ * `String.raw` is the one namespace tag special-cased by the runtime.
340
+ */
341
+ emitTaggedTemplate(node: TaggedTemplateExpression): string {
342
+ const template = node.template;
343
+ let cooked: string[];
344
+ let raw: string[];
345
+ let expressions: readonly Expression[];
346
+ if (template.kind === SyntaxKind.NoSubstitutionTemplateLiteral) {
347
+ cooked = [template.value];
348
+ raw = [template.raw ?? template.value];
349
+ expressions = [];
350
+ } else {
351
+ const literal = template as TemplateLiteral;
352
+ cooked = [literal.head, ...literal.spans.map((span) => span.literal)];
353
+ raw = [literal.raw ?? literal.head, ...literal.spans.map((span) => span.raw ?? span.literal)];
354
+ expressions = literal.spans.map((span) => span.expression);
355
+ }
356
+
357
+ const makeArray = (items: string[]): string => {
358
+ const array = this.reg();
359
+ this.emit(` ${array} = call i64 @xt_array_new(i32 0, i64* null)`);
360
+ for (const item of items) {
361
+ const value = this.stringValue(item);
362
+ this.emit(` call i64 @xt_array_push(i64 ${array}, i64 ${value})`);
363
+ }
364
+ return array;
365
+ };
366
+ const strings = makeArray(cooked);
367
+ const rawArray = makeArray(raw);
368
+ const rawKey = this.stringValue("raw");
369
+ this.emit(` call i64 @xt_set(i64 ${strings}, i64 ${rawKey}, i64 ${rawArray})`);
370
+
371
+ const argc = String(expressions.length + 1);
372
+ const ptr = `%args${this.current.allocas.length}`;
373
+ this.current.allocas.push(`${ptr} = alloca i64, i32 ${expressions.length + 1}`);
374
+ const first = this.reg();
375
+ this.emit(` ${first} = getelementptr i64, i64* ${ptr}, i32 0`);
376
+ this.emit(` store i64 ${strings}, i64* ${first}`);
377
+ for (let index = 0; index < expressions.length; index++) {
378
+ const value = this.emitExpression(expressions[index]!);
379
+ const slot = this.reg();
380
+ this.emit(` ${slot} = getelementptr i64, i64* ${ptr}, i32 ${index + 1}`);
381
+ this.emit(` store i64 ${value}, i64* ${slot}`);
382
+ }
383
+
384
+ const tag = node.tag;
385
+ if (tag.kind === SyntaxKind.PropertyAccessExpression) {
386
+ const access = tag as PropertyAccessExpression;
387
+ const owner = access.expression;
388
+ if (
389
+ owner.kind === SyntaxKind.Identifier &&
390
+ (owner as Identifier).text === "String" &&
391
+ access.name.text === "raw" &&
392
+ !this.binding.symbolOfIdentifier.get(owner as Identifier)
393
+ ) {
394
+ const key = this.stringValue("raw");
395
+ const result = this.reg();
396
+ this.emit(` ${result} = call i64 @xt_string_static(i64 ${key}, i32 ${argc}, i64* ${ptr})`);
397
+ return result;
398
+ }
399
+ const object = this.emitExpression(owner);
400
+ const name = this.stringValue(access.name.text);
401
+ const result = this.reg();
402
+ this.emit(` ${result} = call i64 @xt_call_method(i64 ${object}, i64 ${name}, i32 ${argc}, i64* ${ptr})`);
403
+ return result;
404
+ }
405
+ const tagValue = this.emitExpression(tag);
406
+ const result = this.reg();
407
+ this.emit(` ${result} = call i64 @xt_closure_call(i64 ${tagValue}, i32 ${argc}, i64* ${ptr})`);
408
+ return result;
409
+ },
304
410
  };
@@ -18,7 +18,6 @@ import type { CodegenOptions, CodegenResult } from "./state.js";
18
18
 
19
19
  export class Generator extends GeneratorContext {}
20
20
 
21
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
22
21
  export interface Generator extends ModuleMethods, StatementMethods, ExpressionMethods, CallMethods {}
23
22
 
24
23
  Object.assign(Generator.prototype, moduleMethods, statementMethods, expressionMethods, callMethods);