xbintsc 0.2.3 → 0.2.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 (73) hide show
  1. package/dist/src/ast/declarations.d.ts +20 -1
  2. package/dist/src/ast/kinds.d.ts +5 -1
  3. package/dist/src/ast/kinds.js +4 -0
  4. package/dist/src/ast/kinds.js.map +1 -1
  5. package/dist/src/ast/statements.d.ts +2 -1
  6. package/dist/src/binder/binder.js +44 -9
  7. package/dist/src/binder/binder.js.map +1 -1
  8. package/dist/src/cli/main.js +2 -4
  9. package/dist/src/cli/main.js.map +1 -1
  10. package/dist/src/codegen/generator/expressions.js +25 -0
  11. package/dist/src/codegen/generator/expressions.js.map +1 -1
  12. package/dist/src/codegen/generator/module.js +16 -5
  13. package/dist/src/codegen/generator/module.js.map +1 -1
  14. package/dist/src/codegen/generator/state.js +1 -0
  15. package/dist/src/codegen/generator/state.js.map +1 -1
  16. package/dist/src/codegen/generator/statements.d.ts +4 -1
  17. package/dist/src/codegen/generator/statements.js +121 -7
  18. package/dist/src/codegen/generator/statements.js.map +1 -1
  19. package/dist/src/codegen/generator/tables.js +1 -0
  20. package/dist/src/codegen/generator/tables.js.map +1 -1
  21. package/dist/src/driver/compiler.d.ts +5 -0
  22. package/dist/src/driver/compiler.js +26 -0
  23. package/dist/src/driver/compiler.js.map +1 -1
  24. package/dist/src/driver/modules.js +29 -4
  25. package/dist/src/driver/modules.js.map +1 -1
  26. package/dist/src/lexer/token.d.ts +10 -0
  27. package/dist/src/lexer/token.js +69 -0
  28. package/dist/src/lexer/token.js.map +1 -1
  29. package/dist/src/parser/context.js +4 -16
  30. package/dist/src/parser/context.js.map +1 -1
  31. package/dist/src/parser/declarations.d.ts +2 -0
  32. package/dist/src/parser/declarations.js +136 -24
  33. package/dist/src/parser/declarations.js.map +1 -1
  34. package/dist/src/parser/expressions.js +19 -2
  35. package/dist/src/parser/expressions.js.map +1 -1
  36. package/dist/src/parser/helpers.js +2 -0
  37. package/dist/src/parser/helpers.js.map +1 -1
  38. package/dist/src/parser/modules.js +7 -2
  39. package/dist/src/parser/modules.js.map +1 -1
  40. package/dist/src/parser/statements.js +28 -3
  41. package/dist/src/parser/statements.js.map +1 -1
  42. package/dist/src/parser/types.js +5 -0
  43. package/dist/src/parser/types.js.map +1 -1
  44. package/dist/tests/codegen/llvm.test.js +16 -0
  45. package/dist/tests/codegen/llvm.test.js.map +1 -1
  46. package/dist/tests/driver/compiler.test.js +13 -1
  47. package/dist/tests/driver/compiler.test.js.map +1 -1
  48. package/dist/tests/e2e/compile.test.js +21 -0
  49. package/dist/tests/e2e/compile.test.js.map +1 -1
  50. package/dist/tests/parser/parser.test.js +53 -0
  51. package/dist/tests/parser/parser.test.js.map +1 -1
  52. package/package.json +1 -1
  53. package/runtime/xt_stdlib2.c +11 -0
  54. package/src/ast/declarations.ts +26 -1
  55. package/src/ast/kinds.ts +4 -0
  56. package/src/ast/statements.ts +2 -1
  57. package/src/binder/binder.ts +46 -9
  58. package/src/cli/main.ts +2 -3
  59. package/src/codegen/generator/expressions.ts +25 -0
  60. package/src/codegen/generator/module.ts +19 -6
  61. package/src/codegen/generator/state.ts +1 -0
  62. package/src/codegen/generator/statements.ts +118 -5
  63. package/src/codegen/generator/tables.ts +1 -0
  64. package/src/driver/compiler.ts +29 -0
  65. package/src/driver/modules.ts +28 -3
  66. package/src/lexer/token.ts +73 -0
  67. package/src/parser/context.ts +4 -15
  68. package/src/parser/declarations.ts +130 -22
  69. package/src/parser/expressions.ts +19 -3
  70. package/src/parser/helpers.ts +2 -0
  71. package/src/parser/modules.ts +7 -2
  72. package/src/parser/statements.ts +27 -3
  73. package/src/parser/types.ts +5 -0
@@ -14,7 +14,7 @@ import type { TypeElement } from "./types.js";
14
14
 
15
15
  export interface Parameter extends Node {
16
16
  readonly kind: SyntaxKind.Parameter;
17
- readonly name: Identifier;
17
+ readonly name: BindingName;
18
18
  readonly modifiers: Modifier[];
19
19
  readonly dotDotDotToken: boolean;
20
20
  readonly questionToken: boolean;
@@ -22,6 +22,29 @@ export interface Parameter extends Node {
22
22
  readonly initializer?: Expression;
23
23
  }
24
24
 
25
+ // -- binding patterns (destructuring) --------------------------------------
26
+
27
+ export type BindingName = Identifier | ArrayBindingPattern | ObjectBindingPattern;
28
+
29
+ export interface BindingElement extends Node {
30
+ readonly kind: SyntaxKind.BindingElement;
31
+ readonly name: BindingName;
32
+ /** For `{ a: b }` the `a`; absent for shorthand and array patterns. */
33
+ readonly propertyName?: PropertyName;
34
+ readonly dotDotDotToken: boolean;
35
+ readonly initializer?: Expression;
36
+ }
37
+
38
+ export interface ArrayBindingPattern extends Node {
39
+ readonly kind: SyntaxKind.ArrayBindingPattern;
40
+ readonly elements: (BindingElement | undefined)[];
41
+ }
42
+
43
+ export interface ObjectBindingPattern extends Node {
44
+ readonly kind: SyntaxKind.ObjectBindingPattern;
45
+ readonly elements: BindingElement[];
46
+ }
47
+
25
48
  export interface TypeParameterDeclaration extends Node {
26
49
  readonly kind: SyntaxKind.TypeParameterDeclaration;
27
50
  readonly name: Identifier;
@@ -74,6 +97,8 @@ export interface MethodDeclaration extends Node {
74
97
  readonly body?: Block;
75
98
  readonly flags: NodeFlags;
76
99
  readonly optional: boolean;
100
+ /** Present for `get x()` / `set x(v)` accessors. */
101
+ readonly accessor?: "get" | "set";
77
102
  }
78
103
 
79
104
  export interface ConstructorDeclaration extends Node {
package/src/ast/kinds.ts CHANGED
@@ -59,6 +59,9 @@ export const enum SyntaxKind {
59
59
  AwaitExpression = 43,
60
60
  YieldExpression = 44,
61
61
  TaggedTemplateExpression = 45,
62
+ ArrayBindingPattern = 46,
63
+ ObjectBindingPattern = 47,
64
+ BindingElement = 48,
62
65
 
63
66
  // Statements
64
67
  ExpressionStatement = 60,
@@ -173,4 +176,5 @@ export const enum ModifierKind {
173
176
  Static = "static",
174
177
  Readonly = "readonly",
175
178
  Async = "async",
179
+ Const = "const",
176
180
  }
@@ -5,6 +5,7 @@
5
5
  import { SyntaxKind } from "./kinds.js";
6
6
  import type { Expression, Modifier, Node, Statement, TypeNode } from "./common.js";
7
7
  import type { Identifier } from "./expressions.js";
8
+ import type { BindingName } from "./declarations.js";
8
9
 
9
10
  // ---------------------------------------------------------------------------
10
11
  // Statements
@@ -30,7 +31,7 @@ export interface DebuggerStatement extends Node {
30
31
 
31
32
  export interface VariableDeclaration extends Node {
32
33
  readonly kind: SyntaxKind.VariableDeclaration;
33
- readonly name: Identifier;
34
+ readonly name: BindingName;
34
35
  readonly exclamation: boolean;
35
36
  readonly type?: TypeNode;
36
37
  readonly initializer?: Expression;
@@ -13,6 +13,7 @@ import { NodeFlags, ModifierKind } from "../ast/kinds.js";
13
13
  import {
14
14
  SyntaxKind,
15
15
  type ArrowFunction,
16
+ type BindingName,
16
17
  type Block,
17
18
  type ClassDeclaration,
18
19
  type ClassExpression,
@@ -244,6 +245,27 @@ class Binder {
244
245
  return { kind, node, parent, fn: this.current, symbols: new Map() };
245
246
  }
246
247
 
248
+ /** Declare every identifier introduced by a (possibly destructured) binding. */
249
+ private declareBinding(
250
+ name: BindingName,
251
+ kind: SymbolKind,
252
+ declaration: Node,
253
+ scope: Scope,
254
+ mutable: boolean,
255
+ ): SymbolInfo | undefined {
256
+ if (name.kind === SyntaxKind.Identifier) {
257
+ return this.declare(name.text, kind, declaration, scope, mutable);
258
+ }
259
+ let first: SymbolInfo | undefined;
260
+ const pattern = name as import("../ast/declarations.js").ArrayBindingPattern | import("../ast/declarations.js").ObjectBindingPattern;
261
+ for (const element of pattern.elements) {
262
+ if (!element) continue;
263
+ const symbol = this.declareBinding(element.name, kind, element.name, scope, mutable);
264
+ if (!first) first = symbol;
265
+ }
266
+ return first;
267
+ }
268
+
247
269
  private declare(
248
270
  name: string,
249
271
  kind: SymbolKind,
@@ -334,7 +356,7 @@ class Binder {
334
356
  ? SymbolKind.Var
335
357
  : SymbolKind.Let;
336
358
  for (const declaration of list.declarations) {
337
- this.declare(declaration.name.text, kind, declaration, scope, kind !== SymbolKind.Const);
359
+ this.declareBinding(declaration.name, kind, declaration, scope, kind !== SymbolKind.Const);
338
360
  }
339
361
  }
340
362
 
@@ -369,9 +391,9 @@ class Binder {
369
391
  const decl = node as VariableStatement;
370
392
  if (decl.declarationList.declarationKind === "var") {
371
393
  for (const d of decl.declarationList.declarations) {
372
- const existing = fn.scope.symbols.get(d.name.text);
373
- if (!existing) this.declare(d.name.text, SymbolKind.Var, d, fn.scope, true);
374
- else this.symbolOfDeclaration.set(d, existing);
394
+ const existing = d.name.kind === SyntaxKind.Identifier ? fn.scope.symbols.get(d.name.text) : undefined;
395
+ if (!existing) this.declareBinding(d.name, SymbolKind.Var, d, fn.scope, true);
396
+ else if (d.name.kind === SyntaxKind.Identifier) this.symbolOfDeclaration.set(d, existing);
375
397
  }
376
398
  }
377
399
  return;
@@ -506,11 +528,26 @@ class Binder {
506
528
  private bindVariableList(list: VariableDeclarationList, scope: Scope): void {
507
529
  for (const declaration of list.declarations) {
508
530
  const symbol = this.symbolOfDeclaration.get(declaration);
509
- if (declaration.name) this.symbolOfDeclaration.set(declaration.name, symbol ?? this.symbolOfDeclaration.get(declaration) as SymbolInfo);
531
+ if (declaration.name.kind === SyntaxKind.Identifier) {
532
+ this.symbolOfDeclaration.set(declaration.name, symbol ?? this.symbolOfDeclaration.get(declaration) as SymbolInfo);
533
+ } else {
534
+ this.bindBindingPattern(declaration.name, scope);
535
+ }
510
536
  this.bindNode(declaration.initializer, scope);
511
537
  }
512
538
  }
513
539
 
540
+ /** Bind default-value expressions inside a binding pattern. */
541
+ private bindBindingPattern(name: BindingName, scope: Scope): void {
542
+ if (name.kind === SyntaxKind.Identifier) return;
543
+ const pattern = name as import("../ast/declarations.js").ArrayBindingPattern | import("../ast/declarations.js").ObjectBindingPattern;
544
+ for (const element of pattern.elements) {
545
+ if (!element) continue;
546
+ if (element.initializer) this.bindNode(element.initializer, scope);
547
+ this.bindBindingPattern(element.name, scope);
548
+ }
549
+ }
550
+
514
551
  private bindFunction(
515
552
  node: FunctionDeclaration | FunctionExpression | ArrowFunction,
516
553
  parentScope: Scope,
@@ -533,8 +570,8 @@ class Binder {
533
570
  // Parameters live in the function scope.
534
571
  const params: SymbolInfo[] = [];
535
572
  for (const parameter of node.parameters) {
536
- const paramSymbol = this.declare(parameter.name.text, SymbolKind.Parameter, parameter, scope, true);
537
- params.push(paramSymbol);
573
+ const paramSymbol = this.declareBinding(parameter.name, SymbolKind.Parameter, parameter, scope, true);
574
+ if (paramSymbol) params.push(paramSymbol);
538
575
  if (parameter.initializer) this.bindNode(parameter.initializer, scope);
539
576
  }
540
577
  (fn as { params: SymbolInfo[] }).params = params;
@@ -651,8 +688,8 @@ class Binder {
651
688
 
652
689
  const params: SymbolInfo[] = [];
653
690
  for (const parameter of parameters) {
654
- const paramSymbol = this.declare(parameter.name.text, SymbolKind.Parameter, parameter, scope, true);
655
- params.push(paramSymbol);
691
+ const paramSymbol = this.declareBinding(parameter.name, SymbolKind.Parameter, parameter, scope, true);
692
+ if (paramSymbol) params.push(paramSymbol);
656
693
  if (parameter.initializer) this.bindNode(parameter.initializer, scope);
657
694
  }
658
695
  (fn as { params: SymbolInfo[] }).params = params;
package/src/cli/main.ts CHANGED
@@ -16,7 +16,7 @@ import { resolve } from "node:path";
16
16
  import { pathToFileURL } from "node:url";
17
17
  import { DiagnosticBag, formatDiagnostic, type Diagnostic } from "../diagnostics/diagnostic.js";
18
18
  import { SourceFile } from "../diagnostics/source.js";
19
- import { build, compileString, COMPILER_VERSION, type EmitKind } from "../driver/compiler.js";
19
+ import { build, compileEntry, COMPILER_VERSION, type EmitKind } from "../driver/compiler.js";
20
20
  import { createDefaultRegistry, type ExtensionRegistry } from "../extensions/registry.js";
21
21
  import { nodeExtension } from "../extensions/node/index.js";
22
22
 
@@ -152,9 +152,8 @@ export function run(argv: readonly string[], io: CliIo = defaultIo): number {
152
152
  io.stderr("xbintsc: emit requires a source file\n");
153
153
  return 1;
154
154
  }
155
- const source = readFileSync(resolve(entry), "utf8");
156
155
  const registry = buildRegistry(args.flags);
157
- const { ir, diagnostics } = compileString(source, entry, registry);
156
+ const { ir, diagnostics } = compileEntry(entry, registry);
158
157
  if (diagnostics.some((d) => d.category === "error")) {
159
158
  printDiagnostics(diagnostics, entry, io);
160
159
  return 1;
@@ -69,6 +69,22 @@ export const expressionMethods: ExpressionMethods = {
69
69
  return this.stringValue((node as { value: string }).value);
70
70
  case SyntaxKind.NoSubstitutionTemplateLiteral:
71
71
  return this.stringValue((node as { value: string }).value);
72
+ case SyntaxKind.RegularExpressionLiteral: {
73
+ const regex = node as { pattern: string; flags: string };
74
+ const pattern = this.stringValue(regex.pattern);
75
+ const flags = this.stringValue(regex.flags);
76
+ const ptr = `%regex${this.current.allocas.length}`;
77
+ this.current.allocas.push(`${ptr} = alloca i64, i32 2`);
78
+ const slot0 = this.reg();
79
+ this.emit(` ${slot0} = getelementptr i64, i64* ${ptr}, i32 0`);
80
+ this.emit(` store i64 ${pattern}, i64* ${slot0}`);
81
+ const slot1 = this.reg();
82
+ this.emit(` ${slot1} = getelementptr i64, i64* ${ptr}, i32 1`);
83
+ this.emit(` store i64 ${flags}, i64* ${slot1}`);
84
+ const result = this.reg();
85
+ this.emit(` ${result} = call i64 @xt_regexp_ctor(i32 2, i64* ${ptr})`);
86
+ return result;
87
+ }
72
88
  case SyntaxKind.TemplateLiteral:
73
89
  return this.emitTemplate(node as TemplateLiteral);
74
90
  case SyntaxKind.TrueKeyword:
@@ -87,6 +103,15 @@ export const expressionMethods: ExpressionMethods = {
87
103
  return this.emitExpression((node as { expression: Expression }).expression);
88
104
  case SyntaxKind.DeleteExpression:
89
105
  return this.emitDelete(node as DeleteExpression);
106
+ case SyntaxKind.TypeOfExpression: {
107
+ const operand = this.emitExpression((node as { expression: Expression }).expression);
108
+ const result = this.reg();
109
+ this.emit(` ${result} = call i64 @xt_typeof(i64 ${operand})`);
110
+ return result;
111
+ }
112
+ case SyntaxKind.VoidExpression:
113
+ this.emitExpression((node as { expression: Expression }).expression);
114
+ return i64(XT_UNDEFINED);
90
115
  case SyntaxKind.ThisKeyword:
91
116
  return this.emitThis();
92
117
  case SyntaxKind.NewExpression:
@@ -8,6 +8,7 @@ import {
8
8
  type ArrowFunction,
9
9
  type Block,
10
10
  type Expression,
11
+ type Identifier,
11
12
  type Parameter,
12
13
  } from "../../ast/nodes.js";
13
14
  import type { ClassInfo, FunctionInfo, SymbolInfo } from "../../binder/binder.js";
@@ -40,8 +41,11 @@ export const moduleMethods: ModuleMethods = {
40
41
  const moduleScope = this.binding.scopes.get(this.sourceFile);
41
42
  if (moduleScope) {
42
43
  for (const symbol of moduleScope.symbols.values()) {
43
- const isVariable = symbol.kind === SymbolKind.Var || symbol.kind === SymbolKind.Let || symbol.kind === SymbolKind.Const;
44
- if (!isVariable || !symbol.boxed || !symbol.fn.isModule) continue;
44
+ const isVariable =
45
+ symbol.kind === SymbolKind.Var || symbol.kind === SymbolKind.Let || symbol.kind === SymbolKind.Const;
46
+ const isEnum = symbol.kind === SymbolKind.Enum;
47
+ if ((!isVariable && !isEnum) || !symbol.fn?.isModule) continue;
48
+ if (isVariable && !symbol.boxed) continue;
45
49
  const globalName = `@g.${symbol.id}`;
46
50
  this.moduleGlobals.set(symbol.id, globalName);
47
51
  this.globals.push(`${globalName} = internal global i64 ${i64(XT_UNDEFINED)}`);
@@ -199,10 +203,19 @@ export const moduleMethods: ModuleMethods = {
199
203
  const proto = this.reg();
200
204
  this.emit(` ${proto} = call i64 @xt_object_new()`);
201
205
  if (classInfo.parentExpression) {
202
- const parent = this.emitExpression(classInfo.parentExpression);
203
- const parentProto = this.reg();
204
- this.emit(` ${parentProto} = call i64 @xt_function_get_prototype(i64 ${parent})`);
205
- this.emit(` call i64 @xt_object_set_prototype(i64 ${proto}, i64 ${parentProto})`);
206
+ // `extends Error` (and other host builtins) have no runtime class value;
207
+ // the subclass still works, it simply does not inherit a prototype.
208
+ const parent = classInfo.parentExpression;
209
+ const unboundBuiltin =
210
+ parent.kind === SyntaxKind.Identifier &&
211
+ (parent as Identifier).text === "Error" &&
212
+ !this.binding.symbolOfIdentifier.get(parent as Identifier);
213
+ if (!unboundBuiltin) {
214
+ const parentValue = this.emitExpression(parent);
215
+ const parentProto = this.reg();
216
+ this.emit(` ${parentProto} = call i64 @xt_function_get_prototype(i64 ${parentValue})`);
217
+ this.emit(` call i64 @xt_object_set_prototype(i64 ${proto}, i64 ${parentProto})`);
218
+ }
206
219
  }
207
220
  for (const method of classInfo.methods) {
208
221
  const fnValue = this.emitClosureValue(method);
@@ -101,6 +101,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
101
101
  "declare i64 @xt_set_ctor(i32, i64*)",
102
102
  "declare i64 @xt_date_ctor(i32, i64*)",
103
103
  "declare i64 @xt_regexp_ctor(i32, i64*)",
104
+ "declare i64 @xt_error_ctor(i32, i64*)",
104
105
  "declare i64 @xt_json_parse(i32, i64*)",
105
106
  "declare i64 @xt_json_stringify(i32, i64*)",
106
107
  "declare i64 @xt_object_from_entries(i64)",
@@ -4,15 +4,19 @@
4
4
 
5
5
  import {
6
6
  SyntaxKind,
7
+ type ArrayBindingPattern,
8
+ type BindingName,
7
9
  type Block,
8
10
  type CaseClause,
9
11
  type DoStatement,
12
+ type EnumDeclaration,
10
13
  type Expression,
11
14
  type ForInStatement,
12
15
  type ForOfStatement,
13
16
  type ForStatement,
14
17
  type Identifier,
15
18
  type IfStatement,
19
+ type ObjectBindingPattern,
16
20
  type ReturnStatement,
17
21
  type Statement,
18
22
  type SwitchStatement,
@@ -23,12 +27,16 @@ import {
23
27
  type WhileStatement,
24
28
  } from "../../ast/nodes.js";
25
29
  import { i64, numberLiteral, XT_UNDEFINED } from "../values.js";
30
+ import { propertyNameText } from "./tables.js";
26
31
  import type { Generator } from "./generator.js";
27
32
 
28
33
  export interface StatementMethods {
29
34
  emitStatements(this: Generator, statements: readonly Statement[]): void;
30
35
  emitStatement(this: Generator, statement: Statement): void;
31
36
  emitVariableStatement(this: Generator, statement: VariableStatement): void;
37
+ emitBindingPattern(this: Generator, name: BindingName, value: string): void;
38
+ emitBindingDefault(this: Generator, value: string, initializer: Expression): string;
39
+ emitEnum(this: Generator, statement: EnumDeclaration): void;
32
40
  emitIf(this: Generator, statement: IfStatement): void;
33
41
  emitWhile(this: Generator, statement: WhileStatement): void;
34
42
  emitDo(this: Generator, statement: DoStatement): void;
@@ -125,6 +133,9 @@ export const statementMethods: StatementMethods = {
125
133
  if (declaration) this.emitStatement(declaration);
126
134
  return;
127
135
  }
136
+ case SyntaxKind.EnumDeclaration:
137
+ this.emitEnum(statement as EnumDeclaration);
138
+ return;
128
139
  default:
129
140
  this.unsupported(statement, "statement");
130
141
  }
@@ -132,16 +143,114 @@ export const statementMethods: StatementMethods = {
132
143
 
133
144
  emitVariableStatement(statement: VariableStatement): void {
134
145
  for (const declaration of statement.declarationList.declarations) {
135
- const symbol = this.binding.symbolOfDeclaration.get(declaration);
136
- if (!symbol) continue;
137
- if (this.current.slots.has(symbol.id)) continue; // hoisted `var`
138
146
  const initial = declaration.initializer
139
147
  ? this.emitExpression(declaration.initializer)
140
148
  : i64(XT_UNDEFINED);
141
- this.declareSlot(symbol, initial);
149
+ if (declaration.name.kind === SyntaxKind.Identifier) {
150
+ const symbol = this.binding.symbolOfDeclaration.get(declaration);
151
+ if (!symbol) continue;
152
+ if (this.current.slots.has(symbol.id)) continue; // hoisted `var`
153
+ this.declareSlot(symbol, initial);
154
+ } else {
155
+ this.emitBindingPattern(declaration.name, initial);
156
+ }
157
+ }
158
+ },
159
+
160
+ /** Lower a destructuring binding pattern, declaring each bound identifier. */
161
+ emitBindingPattern(name: BindingName, value: string): void {
162
+ if (name.kind === SyntaxKind.Identifier) {
163
+ const symbol = this.binding.symbolOfDeclaration.get(name);
164
+ if (!symbol) return;
165
+ if (this.current.slots.has(symbol.id)) this.writeSlot(symbol, value);
166
+ else this.declareSlot(symbol, value);
167
+ return;
168
+ }
169
+ if (name.kind === SyntaxKind.ArrayBindingPattern) {
170
+ const pattern = name as ArrayBindingPattern;
171
+ for (let index = 0; index < pattern.elements.length; index++) {
172
+ const element = pattern.elements[index];
173
+ if (!element) continue;
174
+ let elementValue: string;
175
+ if (element.dotDotDotToken) {
176
+ const startPtr = this.alloca();
177
+ this.emit(` store i64 ${numberLiteral(index)}, i64* ${startPtr}`);
178
+ const sliceName = this.stringValue("slice");
179
+ elementValue = this.reg();
180
+ this.emit(` ${elementValue} = call i64 @xt_call_method(i64 ${value}, i64 ${sliceName}, i32 1, i64* ${startPtr})`);
181
+ } else {
182
+ elementValue = this.reg();
183
+ this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${numberLiteral(index)})`);
184
+ }
185
+ if (element.initializer) elementValue = this.emitBindingDefault(elementValue, element.initializer);
186
+ this.emitBindingPattern(element.name, elementValue);
187
+ }
188
+ return;
189
+ }
190
+ const pattern = name as ObjectBindingPattern;
191
+ for (const element of pattern.elements) {
192
+ const key = element.propertyName ?? (element.name.kind === SyntaxKind.Identifier ? element.name : undefined);
193
+ if (element.dotDotDotToken) {
194
+ this.unsupported(element, "object rest destructuring");
195
+ continue;
196
+ }
197
+ const keyValue = this.stringValue(key ? propertyNameText(key) : "undefined");
198
+ let elementValue = this.reg();
199
+ this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${keyValue})`);
200
+ if (element.initializer) elementValue = this.emitBindingDefault(elementValue, element.initializer);
201
+ this.emitBindingPattern(element.name, elementValue);
142
202
  }
143
203
  },
144
204
 
205
+ /** `x = fallback` in a binding pattern: only apply when `x` is `undefined`. */
206
+ emitBindingDefault(value: string, initializer: Expression): string {
207
+ const fallback = this.emitExpression(initializer);
208
+ const isUndefined = this.reg();
209
+ this.emit(` ${isUndefined} = call i64 @xt_seq(i64 ${value}, i64 ${i64(XT_UNDEFINED)})`);
210
+ const truthy = this.reg();
211
+ this.emit(` ${truthy} = call i32 @xt_truthy(i64 ${isUndefined})`);
212
+ const condition = this.reg();
213
+ this.emit(` ${condition} = icmp ne i32 ${truthy}, 0`);
214
+ const result = this.reg();
215
+ this.emit(` ${result} = select i1 ${condition}, i64 ${fallback}, i64 ${value}`);
216
+ return result;
217
+ },
218
+
219
+ /**
220
+ * `enum E { A, B }` (and `const enum`) lowers to a runtime object with both
221
+ * forward (`E.A`) and reverse (`E[0] === "A"`) numeric mappings.
222
+ */
223
+ emitEnum(statement: EnumDeclaration): void {
224
+ const symbol = this.binding.symbolOfDeclaration.get(statement);
225
+ const object = this.reg();
226
+ this.emit(` ${object} = call i64 @xt_object_new()`);
227
+ let next = 0;
228
+ for (const member of statement.members) {
229
+ const name = propertyNameText(member.name);
230
+ const key = this.stringValue(name);
231
+ let value: string;
232
+ if (member.initializer) {
233
+ value = this.emitExpression(member.initializer);
234
+ if (member.initializer.kind === SyntaxKind.NumericLiteral) {
235
+ const numeric = (member.initializer as { value?: number }).value ?? 0;
236
+ next = numeric + 1;
237
+ } else {
238
+ next++;
239
+ }
240
+ } else {
241
+ value = numberLiteral(next);
242
+ next++;
243
+ }
244
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${key}, i64 ${value})`);
245
+ // Reverse mapping (only meaningful for numeric members).
246
+ if (!member.initializer || member.initializer.kind === SyntaxKind.NumericLiteral) {
247
+ const reverseKey = this.stringValue(name);
248
+ this.emit(` call i64 @xt_set(i64 ${object}, i64 ${value}, i64 ${reverseKey})`);
249
+ }
250
+ }
251
+ if (symbol) this.declareSlot(symbol, object);
252
+ },
253
+
145
254
  emitIf(statement: IfStatement): void {
146
255
  const thenLabel = this.label("if.then");
147
256
  const elseLabel = statement.elseStatement ? this.label("if.else") : undefined;
@@ -300,7 +409,11 @@ export const statementMethods: StatementMethods = {
300
409
  if (initializer.kind === SyntaxKind.VariableDeclarationList) {
301
410
  const declaration = initializer.declarations[0];
302
411
  if (!declaration) return;
303
- const symbol = this.binding.symbolOfDeclaration.get(declaration);
412
+ if (declaration.name.kind !== SyntaxKind.Identifier) {
413
+ this.emitBindingPattern(declaration.name, value);
414
+ return;
415
+ }
416
+ const symbol = this.binding.symbolOfDeclaration.get(declaration) ?? this.binding.symbolOfDeclaration.get(declaration.name);
304
417
  if (symbol) {
305
418
  if (this.current.slots.has(symbol.id)) this.writeSlot(symbol, value);
306
419
  else this.declareSlot(symbol, value);
@@ -116,6 +116,7 @@ export const CTOR_FUNCTIONS: Record<string, string> = {
116
116
  Set: "xt_set_ctor",
117
117
  Date: "xt_date_ctor",
118
118
  RegExp: "xt_regexp_ctor",
119
+ Error: "xt_error_ctor",
119
120
  Promise: "xt_promise_ctor",
120
121
  Buffer: "xt_buffer_ctor",
121
122
  Readable: "xt_readable_ctor",
@@ -73,6 +73,35 @@ export function compileString(source: string, fileName = "input.ts", extensions?
73
73
  return { ir, diagnostics: diagnostics.diagnostics };
74
74
  }
75
75
 
76
+ /**
77
+ * Compile a file to LLVM IR, bundling every reachable relative module first so
78
+ * that imports from other TypeScript sources resolve exactly like `build`.
79
+ */
80
+ export function compileEntry(entryPath: string, extensions?: ExtensionRegistry): CompileStringResult {
81
+ const absoluteEntry = resolve(entryPath);
82
+ const sourceText = readFileSync(absoluteEntry, "utf8");
83
+ const file = new SourceFile(absoluteEntry, sourceText);
84
+ const diagnostics = new DiagnosticBag();
85
+ const parser = new Parser(file, diagnostics);
86
+ let sourceFile = parser.parseSourceFile();
87
+ const isModule = sourceFile.statements.some(
88
+ (statement) =>
89
+ statement.kind === SyntaxKind.ImportDeclaration ||
90
+ statement.kind === SyntaxKind.ExportDeclaration ||
91
+ statement.kind === SyntaxKind.ExportAssignment,
92
+ );
93
+ if (isModule) {
94
+ const bundled = bundleModules(absoluteEntry, diagnostics);
95
+ if (bundled) sourceFile = bundled.sourceFile;
96
+ }
97
+ const registry = extensions ?? createDefaultRegistry();
98
+ const { ir } = generate(sourceFile, diagnostics, {
99
+ builtins: registry.builtins(),
100
+ modules: registry.modules(),
101
+ });
102
+ return { ir, diagnostics: diagnostics.diagnostics };
103
+ }
104
+
76
105
  function executableName(entry: string, outDir: string, explicit: string | undefined): string {
77
106
  if (explicit) return resolve(explicit);
78
107
  const base = basename(entry, extname(entry));
@@ -65,6 +65,18 @@ function isExternalSpecifier(specifier: string): boolean {
65
65
 
66
66
  function resolveModule(fromDir: string, specifier: string): string | undefined {
67
67
  const base = resolve(fromDir, specifier);
68
+ // TypeScript sources are imported using their emitted `.js` extension
69
+ // (`import ... from "./foo.js"`), so map the extension back to the source
70
+ // file before probing the usual suffixes.
71
+ const candidates = [base];
72
+ const jsExtension = /\.(?:m|c)?jsx?$/.exec(base);
73
+ if (jsExtension) {
74
+ const stem = base.slice(0, jsExtension.index);
75
+ candidates.push(`${stem}.ts`, `${stem}.tsx`, `${stem}.mts`, `${stem}.cts`);
76
+ }
77
+ for (const candidate of candidates) {
78
+ if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
79
+ }
68
80
  for (const suffix of RESOLVE_SUFFIXES) {
69
81
  const candidate = base + suffix;
70
82
  if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
@@ -86,8 +98,10 @@ function declarationName(node: Node): Identifier | undefined {
86
98
  case SyntaxKind.TypeAliasDeclaration:
87
99
  case SyntaxKind.ModuleDeclaration:
88
100
  return (node as { name?: Identifier }).name;
89
- case SyntaxKind.VariableDeclaration:
90
- return (node as VariableDeclaration).name;
101
+ case SyntaxKind.VariableDeclaration: {
102
+ const name = (node as VariableDeclaration).name;
103
+ return name.kind === SyntaxKind.Identifier ? name : undefined;
104
+ }
91
105
  default:
92
106
  return undefined;
93
107
  }
@@ -349,7 +363,7 @@ function exportedNames(statement: Statement): string[] {
349
363
  const names: string[] = [];
350
364
  if (statement.kind === SyntaxKind.VariableStatement) {
351
365
  for (const declaration of (statement as VariableStatement).declarationList.declarations) {
352
- if (declaration.name) names.push(declaration.name.text);
366
+ collectBindingIdentifiers(declaration.name, names);
353
367
  }
354
368
  } else {
355
369
  const name = declarationName(statement);
@@ -358,6 +372,17 @@ function exportedNames(statement: Statement): string[] {
358
372
  return names;
359
373
  }
360
374
 
375
+ function collectBindingIdentifiers(name: import("../ast/declarations.js").BindingName, out: string[]): void {
376
+ if (name.kind === SyntaxKind.Identifier) {
377
+ out.push(name.text);
378
+ return;
379
+ }
380
+ const elements = name.kind === SyntaxKind.ArrayBindingPattern ? name.elements : name.elements;
381
+ for (const element of elements) {
382
+ if (element) collectBindingIdentifiers(element.name, out);
383
+ }
384
+ }
385
+
361
386
  /** Turn `export default expr` into a synthetic `const` declaration. */
362
387
  function exportAssignmentToStatement(record: ModuleRecord, assignment: ExportAssignment): Statement {
363
388
  const name: Identifier = {
@@ -264,3 +264,76 @@ export const CONTEXTUAL_KEYWORDS: ReadonlySet<TokenKind> = new Set([
264
264
  export function isOfKeyword(token: Token): boolean {
265
265
  return token.kind === TokenKind.Identifier && token.text === "of";
266
266
  }
267
+
268
+ /** Every keyword token kind, derived from the scanner's keyword table. */
269
+ export const KEYWORD_KINDS: ReadonlySet<TokenKind> = new Set(KEYWORDS.values());
270
+
271
+ /**
272
+ * True for the hard/contextual keywords the scanner emits. Property names and
273
+ * member access allow any of these (`obj.default`, `{ type: 1 }`), so the
274
+ * parser consults this instead of maintaining a hand-written allow list.
275
+ */
276
+ export function isKeywordKind(kind: TokenKind): boolean {
277
+ return KEYWORD_KINDS.has(kind);
278
+ }
279
+
280
+ /**
281
+ * Keywords that can never be used as a plain binding/identifier. Everything
282
+ * else the scanner recognizes is a TypeScript contextual keyword (`type`,
283
+ * `async`, `module`, `namespace`, `readonly`, …) and may legally name a
284
+ * variable, parameter or property.
285
+ */
286
+ const RESERVED_KEYWORDS: ReadonlySet<TokenKind> = new Set([
287
+ TokenKind.BreakKeyword,
288
+ TokenKind.CaseKeyword,
289
+ TokenKind.CatchKeyword,
290
+ TokenKind.ClassKeyword,
291
+ TokenKind.ConstKeyword,
292
+ TokenKind.ContinueKeyword,
293
+ TokenKind.DebuggerKeyword,
294
+ TokenKind.DefaultKeyword,
295
+ TokenKind.DeleteKeyword,
296
+ TokenKind.DoKeyword,
297
+ TokenKind.ElseKeyword,
298
+ TokenKind.EnumKeyword,
299
+ TokenKind.ExportKeyword,
300
+ TokenKind.ExtendsKeyword,
301
+ TokenKind.FalseKeyword,
302
+ TokenKind.FinallyKeyword,
303
+ TokenKind.ForKeyword,
304
+ TokenKind.FunctionKeyword,
305
+ TokenKind.IfKeyword,
306
+ TokenKind.ImportKeyword,
307
+ TokenKind.InKeyword,
308
+ TokenKind.InstanceOfKeyword,
309
+ TokenKind.NewKeyword,
310
+ TokenKind.NullKeyword,
311
+ TokenKind.ReturnKeyword,
312
+ TokenKind.SuperKeyword,
313
+ TokenKind.SwitchKeyword,
314
+ TokenKind.ThisKeyword,
315
+ TokenKind.ThrowKeyword,
316
+ TokenKind.TrueKeyword,
317
+ TokenKind.TryKeyword,
318
+ TokenKind.TypeOfKeyword,
319
+ TokenKind.VarKeyword,
320
+ TokenKind.VoidKeyword,
321
+ TokenKind.WhileKeyword,
322
+ TokenKind.WithKeyword,
323
+ // Strict-mode / module reserved words.
324
+ TokenKind.LetKeyword,
325
+ TokenKind.StaticKeyword,
326
+ TokenKind.YieldKeyword,
327
+ TokenKind.AwaitKeyword,
328
+ TokenKind.ImplementsKeyword,
329
+ TokenKind.InterfaceKeyword,
330
+ TokenKind.PackageKeyword,
331
+ TokenKind.PrivateKeyword,
332
+ TokenKind.ProtectedKeyword,
333
+ TokenKind.PublicKeyword,
334
+ ]);
335
+
336
+ /** True when the scanner token may be used as an identifier/binding name. */
337
+ export function isIdentifierNameToken(kind: TokenKind): boolean {
338
+ return kind === TokenKind.Identifier || (isKeywordKind(kind) && !RESERVED_KEYWORDS.has(kind));
339
+ }