xbintsc 0.1.3 → 0.1.8

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 (142) hide show
  1. package/README.md +14 -13
  2. package/dist/src/ast/common.d.ts +33 -0
  3. package/dist/src/ast/common.js +7 -0
  4. package/dist/src/ast/common.js.map +1 -0
  5. package/dist/src/ast/declarations.d.ts +110 -0
  6. package/dist/src/ast/declarations.js +5 -0
  7. package/dist/src/ast/declarations.js.map +1 -0
  8. package/dist/src/ast/expressions.d.ts +219 -0
  9. package/dist/src/ast/expressions.js +6 -0
  10. package/dist/src/ast/expressions.js.map +1 -0
  11. package/dist/src/ast/kinds.d.ts +160 -0
  12. package/dist/src/ast/kinds.js +171 -0
  13. package/dist/src/ast/kinds.js.map +1 -0
  14. package/dist/src/ast/modules.d.ts +61 -0
  15. package/dist/src/ast/modules.js +5 -0
  16. package/dist/src/ast/modules.js.map +1 -0
  17. package/dist/src/ast/nodes.d.ts +11 -839
  18. package/dist/src/ast/nodes.js +11 -237
  19. package/dist/src/ast/nodes.js.map +1 -1
  20. package/dist/src/ast/operators.d.ts +71 -0
  21. package/dist/src/ast/operators.js +77 -0
  22. package/dist/src/ast/operators.js.map +1 -0
  23. package/dist/src/ast/statements.d.ts +120 -0
  24. package/dist/src/ast/statements.js +5 -0
  25. package/dist/src/ast/statements.js.map +1 -0
  26. package/dist/src/ast/types.d.ts +123 -0
  27. package/dist/src/ast/types.js +6 -0
  28. package/dist/src/ast/types.js.map +1 -0
  29. package/dist/src/codegen/generator/calls.d.ts +22 -0
  30. package/dist/src/codegen/generator/calls.js +301 -0
  31. package/dist/src/codegen/generator/calls.js.map +1 -0
  32. package/dist/src/codegen/generator/context.d.ts +45 -0
  33. package/dist/src/codegen/generator/context.js +128 -0
  34. package/dist/src/codegen/generator/context.js.map +1 -0
  35. package/dist/src/codegen/generator/expressions.d.ts +22 -0
  36. package/dist/src/codegen/generator/expressions.js +383 -0
  37. package/dist/src/codegen/generator/expressions.js.map +1 -0
  38. package/dist/src/codegen/generator/generator.d.ts +22 -0
  39. package/dist/src/codegen/generator/generator.js +21 -0
  40. package/dist/src/codegen/generator/generator.js.map +1 -0
  41. package/dist/src/codegen/generator/module.d.ts +15 -0
  42. package/dist/src/codegen/generator/module.js +114 -0
  43. package/dist/src/codegen/generator/module.js.map +1 -0
  44. package/dist/src/codegen/generator/state.d.ts +40 -0
  45. package/dist/src/codegen/generator/state.js +84 -0
  46. package/dist/src/codegen/generator/state.js.map +1 -0
  47. package/dist/src/codegen/generator/statements.d.ts +22 -0
  48. package/dist/src/codegen/generator/statements.js +425 -0
  49. package/dist/src/codegen/generator/statements.js.map +1 -0
  50. package/dist/src/codegen/generator/tables.d.ts +19 -0
  51. package/dist/src/codegen/generator/tables.js +153 -0
  52. package/dist/src/codegen/generator/tables.js.map +1 -0
  53. package/dist/src/codegen/llvm.d.ts +4 -13
  54. package/dist/src/codegen/llvm.js +4 -1159
  55. package/dist/src/codegen/llvm.js.map +1 -1
  56. package/dist/src/driver/compiler.js +13 -4
  57. package/dist/src/driver/compiler.js.map +1 -1
  58. package/dist/src/lexer/char.d.ts +78 -0
  59. package/dist/src/lexer/char.js +106 -0
  60. package/dist/src/lexer/char.js.map +1 -0
  61. package/dist/src/lexer/scanner.js +1 -98
  62. package/dist/src/lexer/scanner.js.map +1 -1
  63. package/dist/src/parser/context.d.ts +58 -0
  64. package/dist/src/parser/context.js +174 -0
  65. package/dist/src/parser/context.js.map +1 -0
  66. package/dist/src/parser/declarations.d.ts +26 -0
  67. package/dist/src/parser/declarations.js +461 -0
  68. package/dist/src/parser/declarations.js.map +1 -0
  69. package/dist/src/parser/expressions.d.ts +27 -0
  70. package/dist/src/parser/expressions.js +437 -0
  71. package/dist/src/parser/expressions.js.map +1 -0
  72. package/dist/src/parser/helpers.d.ts +7 -0
  73. package/dist/src/parser/helpers.js +36 -0
  74. package/dist/src/parser/helpers.js.map +1 -0
  75. package/dist/src/parser/literals.d.ts +16 -0
  76. package/dist/src/parser/literals.js +193 -0
  77. package/dist/src/parser/literals.js.map +1 -0
  78. package/dist/src/parser/modules.d.ts +12 -0
  79. package/dist/src/parser/modules.js +175 -0
  80. package/dist/src/parser/modules.js.map +1 -0
  81. package/dist/src/parser/operators.d.ts +12 -0
  82. package/dist/src/parser/operators.js +172 -0
  83. package/dist/src/parser/operators.js.map +1 -0
  84. package/dist/src/parser/parser.d.ts +17 -122
  85. package/dist/src/parser/parser.js +18 -2307
  86. package/dist/src/parser/parser.js.map +1 -1
  87. package/dist/src/parser/speculation.d.ts +6 -0
  88. package/dist/src/parser/speculation.js +10 -0
  89. package/dist/src/parser/speculation.js.map +1 -0
  90. package/dist/src/parser/statements.d.ts +28 -0
  91. package/dist/src/parser/statements.js +402 -0
  92. package/dist/src/parser/statements.js.map +1 -0
  93. package/dist/src/parser/types.d.ts +21 -0
  94. package/dist/src/parser/types.js +338 -0
  95. package/dist/src/parser/types.js.map +1 -0
  96. package/dist/tests/e2e/compile.test.js +122 -2
  97. package/dist/tests/e2e/compile.test.js.map +1 -1
  98. package/dist/tests/parser/parser.test.js +9 -1
  99. package/dist/tests/parser/parser.test.js.map +1 -1
  100. package/package.json +1 -1
  101. package/runtime/rt.h +32 -0
  102. package/runtime/rt_internal.h +106 -0
  103. package/runtime/xt_alloc.c +38 -0
  104. package/runtime/xt_builtins.c +192 -0
  105. package/runtime/xt_containers.c +225 -0
  106. package/runtime/xt_io.c +133 -0
  107. package/runtime/xt_stdlib.c +502 -0
  108. package/runtime/xt_values.c +291 -0
  109. package/scripts/build-runtime.mjs +8 -1
  110. package/src/ast/common.ts +210 -0
  111. package/src/ast/declarations.ts +130 -0
  112. package/src/ast/expressions.ts +275 -0
  113. package/src/ast/kinds.ts +176 -0
  114. package/src/ast/modules.ts +73 -0
  115. package/src/ast/nodes.ts +11 -1101
  116. package/src/ast/operators.ts +76 -0
  117. package/src/ast/statements.ts +148 -0
  118. package/src/ast/types.ts +173 -0
  119. package/src/codegen/generator/calls.ts +355 -0
  120. package/src/codegen/generator/context.ts +156 -0
  121. package/src/codegen/generator/expressions.ts +431 -0
  122. package/src/codegen/generator/generator.ts +35 -0
  123. package/src/codegen/generator/module.ts +139 -0
  124. package/src/codegen/generator/state.ts +126 -0
  125. package/src/codegen/generator/statements.ts +474 -0
  126. package/src/codegen/generator/tables.ts +171 -0
  127. package/src/codegen/llvm.ts +4 -1305
  128. package/src/driver/compiler.ts +17 -5
  129. package/src/lexer/char.ts +116 -0
  130. package/src/lexer/scanner.ts +9 -109
  131. package/src/parser/context.ts +196 -0
  132. package/src/parser/declarations.ts +502 -0
  133. package/src/parser/expressions.ts +465 -0
  134. package/src/parser/helpers.ts +42 -0
  135. package/src/parser/literals.ts +207 -0
  136. package/src/parser/modules.ts +194 -0
  137. package/src/parser/operators.ts +181 -0
  138. package/src/parser/parser.ts +35 -2369
  139. package/src/parser/speculation.ts +9 -0
  140. package/src/parser/statements.ts +457 -0
  141. package/src/parser/types.ts +365 -0
  142. package/runtime/xt_runtime.c +0 -682
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Expression lowering: operators, assignments, conditionals, templates and
3
+ * identifier resolution.
4
+ */
5
+
6
+ import {
7
+ SyntaxKind,
8
+ type ArrayLiteralExpression,
9
+ type ArrowFunction,
10
+ type BinaryExpression,
11
+ type CallExpression,
12
+ type ConditionalExpression,
13
+ type DeleteExpression,
14
+ type ElementAccessExpression,
15
+ type Expression,
16
+ type FunctionExpression,
17
+ type Identifier,
18
+ type ObjectLiteralExpression,
19
+ type PostfixUnaryExpression,
20
+ type PrefixUnaryExpression,
21
+ type PropertyAccessExpression,
22
+ type TemplateLiteral,
23
+ } from "../../ast/nodes.js";
24
+ import {
25
+ AssignmentOperator,
26
+ BinaryOperator,
27
+ PrefixUnaryOperator,
28
+ PostfixUnaryOperator,
29
+ } from "../../ast/nodes.js";
30
+ import { SymbolKind, type SymbolInfo } from "../../binder/binder.js";
31
+ import { DiagnosticCode } from "../../diagnostics/diagnostic.js";
32
+ import { i64, numberLiteral, XT_FALSE, XT_NULL, XT_TRUE, XT_UNDEFINED } from "../values.js";
33
+ import { BINARY_RUNTIME, compoundToBinary, isAssignmentOperator } from "./tables.js";
34
+ import type { Generator } from "./generator.js";
35
+
36
+ export interface ExpressionMethods {
37
+ emitExpression(this: Generator, node: Expression): string;
38
+ emitIdentifier(this: Generator, identifier: Identifier): string;
39
+ emitFunctionValue(this: Generator, symbol: SymbolInfo): string;
40
+ emitTemplate(this: Generator, node: TemplateLiteral): string;
41
+ emitBinaryOrAssignment(this: Generator, node: BinaryExpression): string;
42
+ emitShortCircuit(this: Generator, node: BinaryExpression, kind: "and" | "or" | "nullish"): string;
43
+ emitConditional(this: Generator, node: ConditionalExpression): string;
44
+ emitPrefix(this: Generator, node: PrefixUnaryExpression): string;
45
+ emitPostfix(this: Generator, node: PostfixUnaryExpression): string;
46
+ emitAssignment(this: Generator, node: BinaryExpression): string;
47
+ emitLogicalAssignment(this: Generator, node: BinaryExpression): string;
48
+ emitAssignmentTarget(this: Generator, target: Expression, value: string): void;
49
+ }
50
+
51
+ export const expressionMethods: ExpressionMethods = {
52
+ emitExpression(node: Expression): string {
53
+ switch (node.kind) {
54
+ case SyntaxKind.Identifier:
55
+ return this.emitIdentifier(node as Identifier);
56
+ case SyntaxKind.NumericLiteral: {
57
+ const value = (node as { value: number }).value;
58
+ return numberLiteral(value);
59
+ }
60
+ case SyntaxKind.BigIntLiteral:
61
+ return numberLiteral(Number((node as { value: bigint }).value));
62
+ case SyntaxKind.StringLiteral:
63
+ return this.stringValue((node as { value: string }).value);
64
+ case SyntaxKind.NoSubstitutionTemplateLiteral:
65
+ return this.stringValue((node as { value: string }).value);
66
+ case SyntaxKind.TemplateLiteral:
67
+ return this.emitTemplate(node as TemplateLiteral);
68
+ case SyntaxKind.TrueKeyword:
69
+ return i64(XT_TRUE);
70
+ case SyntaxKind.FalseKeyword:
71
+ return i64(XT_FALSE);
72
+ case SyntaxKind.NullKeyword:
73
+ return i64(XT_NULL);
74
+ case SyntaxKind.UndefinedKeyword:
75
+ return i64(XT_UNDEFINED);
76
+ case SyntaxKind.ParenthesizedExpression:
77
+ return this.emitExpression((node as { expression: Expression }).expression);
78
+ case SyntaxKind.AsExpression:
79
+ case SyntaxKind.SatisfiesExpression:
80
+ case SyntaxKind.NonNullExpression:
81
+ return this.emitExpression((node as { expression: Expression }).expression);
82
+ case SyntaxKind.DeleteExpression:
83
+ return this.emitDelete(node as DeleteExpression);
84
+ case SyntaxKind.BinaryExpression:
85
+ return this.emitBinaryOrAssignment(node as BinaryExpression);
86
+ case SyntaxKind.PrefixUnaryExpression:
87
+ return this.emitPrefix(node as PrefixUnaryExpression);
88
+ case SyntaxKind.PostfixUnaryExpression:
89
+ return this.emitPostfix(node as PostfixUnaryExpression);
90
+ case SyntaxKind.ConditionalExpression:
91
+ return this.emitConditional(node as ConditionalExpression);
92
+ case SyntaxKind.CallExpression:
93
+ return this.emitCall(node as CallExpression);
94
+ case SyntaxKind.PropertyAccessExpression:
95
+ return this.emitPropertyAccess(node as PropertyAccessExpression);
96
+ case SyntaxKind.ElementAccessExpression:
97
+ return this.emitElementAccess(node as ElementAccessExpression);
98
+ case SyntaxKind.ArrayLiteralExpression:
99
+ return this.emitArrayLiteral(node as ArrayLiteralExpression);
100
+ case SyntaxKind.ObjectLiteralExpression:
101
+ return this.emitObjectLiteral(node as ObjectLiteralExpression);
102
+ case SyntaxKind.ArrowFunction:
103
+ case SyntaxKind.FunctionExpression:
104
+ return this.emitClosure(node as ArrowFunction | FunctionExpression);
105
+ default:
106
+ this.unsupported(node, "expression");
107
+ return i64(XT_UNDEFINED);
108
+ }
109
+ },
110
+
111
+ emitIdentifier(identifier: Identifier): string {
112
+ const symbol = this.binding.symbolOfIdentifier.get(identifier);
113
+ if (symbol) {
114
+ if (symbol.kind === SymbolKind.Function && !this.current.slots.has(symbol.id)) {
115
+ return this.emitFunctionValue(symbol);
116
+ }
117
+ return this.readSlot(symbol);
118
+ }
119
+ switch (identifier.text) {
120
+ case "undefined":
121
+ return i64(XT_UNDEFINED);
122
+ case "NaN":
123
+ return numberLiteral(NaN);
124
+ case "Infinity":
125
+ return numberLiteral(Infinity);
126
+ case "console":
127
+ return i64(XT_UNDEFINED);
128
+ case "arguments": {
129
+ const argc = this.reg();
130
+ this.emit(` ${argc} = load i32, i32* %saved.argc`);
131
+ const argv = this.reg();
132
+ this.emit(` ${argv} = load i64*, i64** %saved.argv`);
133
+ const rest = this.reg();
134
+ this.emit(` ${rest} = call i64 @xt_rest_args(i32 ${argc}, i64* ${argv}, i32 0)`);
135
+ return rest;
136
+ }
137
+ default:
138
+ this.diagnostics.error(
139
+ DiagnosticCode.CannotFindName,
140
+ `Cannot find name '${identifier.text}'`,
141
+ identifier,
142
+ this.sourceFile.fileName,
143
+ );
144
+ return i64(XT_UNDEFINED);
145
+ }
146
+ },
147
+
148
+ emitFunctionValue(symbol: SymbolInfo): string {
149
+ const declaration = symbol.declarations[0];
150
+ const fn = declaration ? this.binding.functionOfNode.get(declaration) : undefined;
151
+ if (!fn) return i64(XT_UNDEFINED);
152
+ const cast = this.reg();
153
+ this.emit(` ${cast} = bitcast i64 (i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
154
+ const closure = this.reg();
155
+ this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 0, i64* null)`);
156
+ return closure;
157
+ },
158
+
159
+ emitTemplate(node: TemplateLiteral): string {
160
+ let accumulator = this.stringValue(node.head);
161
+ for (const span of node.spans) {
162
+ const expression = this.emitExpression(span.expression);
163
+ const text = this.reg();
164
+ this.emit(` ${text} = call i64 @xt_to_string(i64 ${expression})`);
165
+ const joined = this.reg();
166
+ this.emit(` ${joined} = call i64 @xt_add(i64 ${accumulator}, i64 ${text})`);
167
+ accumulator = joined;
168
+ if (span.literal.length > 0) {
169
+ const literal = this.stringValue(span.literal);
170
+ const withLiteral = this.reg();
171
+ this.emit(` ${withLiteral} = call i64 @xt_add(i64 ${accumulator}, i64 ${literal})`);
172
+ accumulator = withLiteral;
173
+ }
174
+ }
175
+ return accumulator;
176
+ },
177
+
178
+ emitBinaryOrAssignment(node: BinaryExpression): string {
179
+ const operator = node.operator as string;
180
+ if (isAssignmentOperator(operator)) {
181
+ return this.emitAssignment(node);
182
+ }
183
+ switch (operator) {
184
+ case BinaryOperator.AmpersandAmpersand:
185
+ return this.emitShortCircuit(node, "and");
186
+ case BinaryOperator.BarBar:
187
+ return this.emitShortCircuit(node, "or");
188
+ case BinaryOperator.QuestionQuestion:
189
+ return this.emitShortCircuit(node, "nullish");
190
+ case BinaryOperator.Comma: {
191
+ this.emitExpression(node.left);
192
+ return this.emitExpression(node.right);
193
+ }
194
+ default:
195
+ break;
196
+ }
197
+ const left = this.emitExpression(node.left);
198
+ const right = this.emitExpression(node.right);
199
+ const runtime = BINARY_RUNTIME[operator];
200
+ if (!runtime) {
201
+ this.unsupported(node, `operator '${operator}'`);
202
+ return i64(XT_UNDEFINED);
203
+ }
204
+ const result = this.reg();
205
+ this.emit(` ${result} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
206
+ return result;
207
+ },
208
+
209
+ emitShortCircuit(node: BinaryExpression, kind: "and" | "or" | "nullish"): string {
210
+ const result = this.alloca();
211
+ const left = this.emitExpression(node.left);
212
+ this.emit(` store i64 ${left}, i64* ${result}`);
213
+ const rightLabel = this.label("sc.right");
214
+ const endLabel = this.label("sc.end");
215
+ let condition: string;
216
+ if (kind === "and") {
217
+ const truthy = this.reg();
218
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${left})`);
219
+ condition = this.reg();
220
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
221
+ } else if (kind === "or") {
222
+ const truthy = this.reg();
223
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${left})`);
224
+ const isTruthy = this.reg();
225
+ this.emit(` ${isTruthy} = icmp ne i32 ${truthy}, 0`);
226
+ condition = this.reg();
227
+ this.emit(` ${condition} = xor i1 ${isTruthy}, true`);
228
+ } else {
229
+ const nullish = this.reg();
230
+ this.emit(` ${nullish} = call i64 @xt_is_nullish(i64 ${left})`);
231
+ const truthy = this.reg();
232
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${nullish})`);
233
+ condition = this.reg();
234
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
235
+ }
236
+ this.terminate(`br i1 ${condition}, label %${rightLabel}, label %${endLabel}`);
237
+ this.startBlock(rightLabel);
238
+ const right = this.emitExpression(node.right);
239
+ this.emit(` store i64 ${right}, i64* ${result}`);
240
+ this.terminate(`br label %${endLabel}`);
241
+ this.startBlock(endLabel);
242
+ const value = this.reg();
243
+ this.emit(` ${value} = load i64, i64* ${result}`);
244
+ return value;
245
+ },
246
+
247
+ emitConditional(node: ConditionalExpression): string {
248
+ const result = this.alloca();
249
+ const condition = this.emitExpression(node.condition);
250
+ const truthy = this.reg();
251
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${condition})`);
252
+ const nonzero = this.reg();
253
+ this.emit(` ${nonzero} = icmp ne i32 ${truthy}, 0`);
254
+ const trueLabel = this.label("cond.true");
255
+ const falseLabel = this.label("cond.false");
256
+ const endLabel = this.label("cond.end");
257
+ this.terminate(`br i1 ${nonzero}, label %${trueLabel}, label %${falseLabel}`);
258
+ this.startBlock(trueLabel);
259
+ const whenTrue = this.emitExpression(node.whenTrue);
260
+ this.emit(` store i64 ${whenTrue}, i64* ${result}`);
261
+ this.terminate(`br label %${endLabel}`);
262
+ this.startBlock(falseLabel);
263
+ const whenFalse = this.emitExpression(node.whenFalse);
264
+ this.emit(` store i64 ${whenFalse}, i64* ${result}`);
265
+ this.terminate(`br label %${endLabel}`);
266
+ this.startBlock(endLabel);
267
+ const value = this.reg();
268
+ this.emit(` ${value} = load i64, i64* ${result}`);
269
+ return value;
270
+ },
271
+
272
+ emitPrefix(node: PrefixUnaryExpression): string {
273
+ switch (node.operator) {
274
+ case PrefixUnaryOperator.Minus: {
275
+ const operand = this.emitExpression(node.operand);
276
+ const result = this.reg();
277
+ this.emit(` ${result} = call i64 @xt_neg(i64 ${operand})`);
278
+ return result;
279
+ }
280
+ case PrefixUnaryOperator.Plus: {
281
+ const operand = this.emitExpression(node.operand);
282
+ const result = this.reg();
283
+ this.emit(` ${result} = call i64 @xt_pos(i64 ${operand})`);
284
+ return result;
285
+ }
286
+ case PrefixUnaryOperator.Exclamation: {
287
+ const operand = this.emitExpression(node.operand);
288
+ const result = this.reg();
289
+ this.emit(` ${result} = call i64 @xt_not(i64 ${operand})`);
290
+ return result;
291
+ }
292
+ case PrefixUnaryOperator.Tilde: {
293
+ const operand = this.emitExpression(node.operand);
294
+ const result = this.reg();
295
+ this.emit(` ${result} = call i64 @xt_bit_not(i64 ${operand})`);
296
+ return result;
297
+ }
298
+ case PrefixUnaryOperator.TypeOf: {
299
+ const operand = this.emitExpression(node.operand);
300
+ const result = this.reg();
301
+ this.emit(` ${result} = call i64 @xt_typeof(i64 ${operand})`);
302
+ return result;
303
+ }
304
+ case PrefixUnaryOperator.Void:
305
+ if (node.operand) this.emitExpression(node.operand);
306
+ return i64(XT_UNDEFINED);
307
+ case PrefixUnaryOperator.PlusPlus:
308
+ case PrefixUnaryOperator.MinusMinus: {
309
+ const one = numberLiteral(1);
310
+ const current = this.emitExpression(node.operand);
311
+ const fn = node.operator === PrefixUnaryOperator.PlusPlus ? "xt_add" : "xt_sub";
312
+ const next = this.reg();
313
+ this.emit(` ${next} = call i64 @${fn}(i64 ${current}, i64 ${one})`);
314
+ this.emitAssignmentTarget(node.operand, next);
315
+ return next;
316
+ }
317
+ default:
318
+ this.unsupported(node, `unary operator '${node.operator}'`);
319
+ return i64(XT_UNDEFINED);
320
+ }
321
+ },
322
+
323
+ emitPostfix(node: PostfixUnaryExpression): string {
324
+ const one = numberLiteral(1);
325
+ const current = this.emitExpression(node.operand);
326
+ const fn = node.operator === PostfixUnaryOperator.PlusPlus ? "xt_add" : "xt_sub";
327
+ const next = this.reg();
328
+ this.emit(` ${next} = call i64 @${fn}(i64 ${current}, i64 ${one})`);
329
+ this.emitAssignmentTarget(node.operand, next);
330
+ return current;
331
+ },
332
+
333
+ emitAssignment(node: BinaryExpression): string {
334
+ const operator = node.operator as unknown as AssignmentOperator;
335
+ const target = node.left;
336
+ let value: string;
337
+ if (operator === AssignmentOperator.Assign) {
338
+ value = this.emitExpression(node.right);
339
+ } else if (
340
+ operator === AssignmentOperator.AmpersandAmpersandAssign ||
341
+ operator === AssignmentOperator.BarBarAssign ||
342
+ operator === AssignmentOperator.QuestionQuestionAssign
343
+ ) {
344
+ value = this.emitLogicalAssignment(node);
345
+ } else {
346
+ const current = this.emitExpression(target);
347
+ const right = this.emitExpression(node.right);
348
+ const binaryOperator = compoundToBinary(operator);
349
+ const runtime = BINARY_RUNTIME[binaryOperator];
350
+ if (!runtime) {
351
+ this.unsupported(node, `assignment operator '${operator}'`);
352
+ return i64(XT_UNDEFINED);
353
+ }
354
+ const result = this.reg();
355
+ this.emit(` ${result} = call i64 @${runtime}(i64 ${current}, i64 ${right})`);
356
+ value = result;
357
+ }
358
+ this.emitAssignmentTarget(target, value);
359
+ return value;
360
+ },
361
+
362
+ emitLogicalAssignment(node: BinaryExpression): string {
363
+ const operator = node.operator as unknown as AssignmentOperator;
364
+ const target = node.left;
365
+ const current = this.emitExpression(target);
366
+ const result = this.alloca();
367
+ this.emit(` store i64 ${current}, i64* ${result}`);
368
+ const rightLabel = this.label("assign.right");
369
+ const endLabel = this.label("assign.end");
370
+ let condition: string;
371
+ if (operator === AssignmentOperator.AmpersandAmpersandAssign) {
372
+ const truthy = this.reg();
373
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${current})`);
374
+ condition = this.reg();
375
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
376
+ } else if (operator === AssignmentOperator.BarBarAssign) {
377
+ const truthy = this.reg();
378
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${current})`);
379
+ const isTruthy = this.reg();
380
+ this.emit(` ${isTruthy} = icmp ne i32 ${truthy}, 0`);
381
+ condition = this.reg();
382
+ this.emit(` ${condition} = xor i1 ${isTruthy}, true`);
383
+ } else {
384
+ const nullish = this.reg();
385
+ this.emit(` ${nullish} = call i64 @xt_is_nullish(i64 ${current})`);
386
+ const truthy = this.reg();
387
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${nullish})`);
388
+ condition = this.reg();
389
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
390
+ }
391
+ this.terminate(`br i1 ${condition}, label %${rightLabel}, label %${endLabel}`);
392
+ this.startBlock(rightLabel);
393
+ const right = this.emitExpression(node.right);
394
+ this.emit(` store i64 ${right}, i64* ${result}`);
395
+ this.terminate(`br label %${endLabel}`);
396
+ this.startBlock(endLabel);
397
+ const value = this.reg();
398
+ this.emit(` ${value} = load i64, i64* ${result}`);
399
+ this.emitAssignmentTarget(target, value);
400
+ return value;
401
+ },
402
+
403
+ emitAssignmentTarget(target: Expression, value: string): void {
404
+ switch (target.kind) {
405
+ case SyntaxKind.Identifier: {
406
+ const symbol = this.binding.symbolOfIdentifier.get(target as Identifier);
407
+ if (symbol) this.writeSlot(symbol, value);
408
+ return;
409
+ }
410
+ case SyntaxKind.PropertyAccessExpression: {
411
+ const access = target as PropertyAccessExpression;
412
+ const object = this.emitExpression(access.expression);
413
+ const key = this.stringValue(access.name.text);
414
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
415
+ return;
416
+ }
417
+ case SyntaxKind.ElementAccessExpression: {
418
+ const access = target as ElementAccessExpression;
419
+ const object = this.emitExpression(access.expression);
420
+ const key = this.emitExpression(access.argumentExpression);
421
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
422
+ return;
423
+ }
424
+ case SyntaxKind.ParenthesizedExpression:
425
+ this.emitAssignmentTarget((target as { expression: Expression }).expression, value);
426
+ return;
427
+ default:
428
+ this.unsupported(target, "assignment target");
429
+ }
430
+ },
431
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Assembles the LLVM IR generator from its method groups.
3
+ *
4
+ * The implementation is split across `context` (shared state and primitives)
5
+ * and several method-group modules. Each group contributes an interface plus a
6
+ * plain object of methods; they are merged onto the prototype here so the
7
+ * groups can freely call one another through `this`.
8
+ */
9
+
10
+ import type { SourceFileNode } from "../../ast/nodes.js";
11
+ import type { DiagnosticBag } from "../../diagnostics/diagnostic.js";
12
+ import { GeneratorContext } from "./context.js";
13
+ import { moduleMethods, type ModuleMethods } from "./module.js";
14
+ import { statementMethods, type StatementMethods } from "./statements.js";
15
+ import { expressionMethods, type ExpressionMethods } from "./expressions.js";
16
+ import { callMethods, type CallMethods } from "./calls.js";
17
+ import type { CodegenOptions, CodegenResult } from "./state.js";
18
+
19
+ export class Generator extends GeneratorContext {}
20
+
21
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
22
+ export interface Generator extends ModuleMethods, StatementMethods, ExpressionMethods, CallMethods {}
23
+
24
+ Object.assign(Generator.prototype, moduleMethods, statementMethods, expressionMethods, callMethods);
25
+
26
+ export type { CodegenOptions, CodegenResult } from "./state.js";
27
+
28
+ export function generate(
29
+ sourceFile: SourceFileNode,
30
+ diagnostics: DiagnosticBag,
31
+ options: CodegenOptions = {},
32
+ ): CodegenResult {
33
+ const generator = new Generator(sourceFile, diagnostics, options);
34
+ return { ir: generator.run(), binding: generator.binding };
35
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Module-level generation: emitting functions, the entry point and the overall
3
+ * IR text, plus default-parameter initialization.
4
+ */
5
+
6
+ import {
7
+ SyntaxKind,
8
+ type ArrowFunction,
9
+ type Block,
10
+ type Expression,
11
+ type Parameter,
12
+ } from "../../ast/nodes.js";
13
+ import type { FunctionInfo, SymbolInfo } from "../../binder/binder.js";
14
+ import { i64, XT_UNDEFINED } from "../values.js";
15
+ import { RUNTIME_DECLARATIONS, type FunctionState } from "./state.js";
16
+ import type { Generator } from "./generator.js";
17
+
18
+ export interface ModuleMethods {
19
+ run(this: Generator): string;
20
+ emitFunction(this: Generator, fn: FunctionInfo): void;
21
+ emitMain(this: Generator): void;
22
+ functionName(this: Generator, fn: FunctionInfo): string;
23
+ emitDefaultParameter(this: Generator, symbol: SymbolInfo, value: string, initializer: Expression): void;
24
+ }
25
+
26
+ export const moduleMethods: ModuleMethods = {
27
+ run(): string {
28
+ for (const fn of this.binding.functions) this.emitFunction(fn);
29
+ this.emitMain();
30
+ const header = ["; ModuleID = 'xbintsc'", "source_filename = \"" + this.sourceFile.fileName + "\"", ""];
31
+ return [...header, ...RUNTIME_DECLARATIONS, ...this.extraDeclarations, "", ...this.globals, "", ...this.functions, ""].join("\n");
32
+ },
33
+
34
+ // -- module level --------------------------------------------------------
35
+
36
+ emitFunction(fn: FunctionInfo): void {
37
+ const name = this.functionName(fn);
38
+ const state: FunctionState = {
39
+ fn,
40
+ buffer: [],
41
+ allocas: [],
42
+ slots: new Map(),
43
+ reg: 0,
44
+ label: 0,
45
+ terminated: false,
46
+ loops: [],
47
+ tryFrames: [],
48
+ escapePointers: [],
49
+ usesTry: false,
50
+ };
51
+ this.current = state;
52
+
53
+ const header = `define i64 @${name}(i64 %env, i32 %argc, i64* %argv) {`;
54
+ this.emit(`%saved.env = alloca i64`);
55
+ this.emit(`store i64 %env, i64* %saved.env`);
56
+ this.emit(`%saved.argc = alloca i32`);
57
+ this.emit(`store i32 %argc, i32* %saved.argc`);
58
+ this.emit(`%saved.argv = alloca i64*`);
59
+ this.emit(`store i64* %argv, i64** %saved.argv`);
60
+
61
+ // Parameters.
62
+ const parameterNodes = (fn.node as { parameters?: Parameter[] }).parameters ?? [];
63
+ for (let index = 0; index < fn.params.length; index++) {
64
+ const symbol = fn.params[index]!;
65
+ const parameter = parameterNodes[index];
66
+ if (parameter?.dotDotDotToken) {
67
+ const rest = this.reg();
68
+ this.emit(` ${rest} = call i64 @xt_rest_args(i32 %argc, i64* %argv, i32 ${index})`);
69
+ this.declareSlot(symbol, rest);
70
+ continue;
71
+ }
72
+ const value = this.reg();
73
+ this.emit(` ${value} = call i64 @xt_arg(i32 %argc, i64* %argv, i32 ${index})`);
74
+ this.declareSlot(symbol, value);
75
+ if (parameter?.initializer) this.emitDefaultParameter(symbol, value, parameter.initializer);
76
+ }
77
+
78
+ // Captures threaded through the environment.
79
+ for (const symbol of fn.captures) {
80
+ const index = fn.captureIndex.get(symbol.id)!;
81
+ const value = this.reg();
82
+ this.emit(` ${value} = call i64 @xt_closure_env(i64 %env, i32 ${index})`);
83
+ this.defineCaptureSlot(symbol, value);
84
+ }
85
+
86
+ // Body.
87
+ if (fn.node.kind === SyntaxKind.SourceFile) {
88
+ this.emitStatements(this.sourceFile.statements);
89
+ } else if (fn.node.kind === SyntaxKind.ArrowFunction && (fn.node as ArrowFunction).body.kind !== SyntaxKind.Block) {
90
+ const value = this.emitExpression((fn.node as ArrowFunction).body as Expression);
91
+ this.terminate(`ret i64 ${value}`);
92
+ } else {
93
+ const body = (fn.node as { body: Block }).body;
94
+ this.emitStatements(body.statements);
95
+ }
96
+
97
+ if (!this.current.terminated) this.terminate(`ret i64 ${i64(XT_UNDEFINED)}`);
98
+
99
+ const escapes = state.usesTry
100
+ ? state.escapePointers.map((ptr) => ` call void asm sideeffect "", "r"(i64* ${ptr})`)
101
+ : [];
102
+ const lines = [...state.allocas.map((a) => ` ${a}`), ...escapes, ...state.buffer];
103
+ this.functions.push([header, ...lines, "}", ""].join("\n"));
104
+ },
105
+
106
+ emitMain(): void {
107
+ const moduleName = this.functionName(this.binding.moduleFunction);
108
+ this.functions.push(
109
+ [
110
+ "define i32 @main(i32 %argc, i8** %argv) {",
111
+ ` %result = call i64 @${moduleName}(i64 ${i64(XT_UNDEFINED)}, i32 0, i64* null)`,
112
+ " ret i32 0",
113
+ "}",
114
+ "",
115
+ ].join("\n"),
116
+ );
117
+ },
118
+
119
+ functionName(fn: FunctionInfo): string {
120
+ return fn.isModule ? "xt_module" : `xt_fn_${fn.id}`;
121
+ },
122
+
123
+ emitDefaultParameter(symbol: SymbolInfo, value: string, initializer: Expression): void {
124
+ const isUndefined = this.reg();
125
+ this.emit(` ${isUndefined} = call i64 @xt_seq(i64 ${value}, i64 ${i64(XT_UNDEFINED)})`);
126
+ const truthy = this.reg();
127
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${isUndefined})`);
128
+ const condition = this.reg();
129
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
130
+ const applyLabel = this.label("param.default");
131
+ const endLabel = this.label("param.end");
132
+ this.terminate(`br i1 ${condition}, label %${applyLabel}, label %${endLabel}`);
133
+ this.startBlock(applyLabel);
134
+ const fallback = this.emitExpression(initializer);
135
+ this.writeSlot(symbol, fallback);
136
+ this.terminate(`br label %${endLabel}`);
137
+ this.startBlock(endLabel);
138
+ },
139
+ };