xbintsc 0.2.4 → 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.
- package/dist/src/ast/declarations.d.ts +20 -1
- package/dist/src/ast/kinds.d.ts +5 -1
- package/dist/src/ast/kinds.js +4 -0
- package/dist/src/ast/kinds.js.map +1 -1
- package/dist/src/ast/statements.d.ts +2 -1
- package/dist/src/binder/binder.js +44 -9
- package/dist/src/binder/binder.js.map +1 -1
- package/dist/src/cli/main.js +2 -4
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/expressions.js +25 -0
- package/dist/src/codegen/generator/expressions.js.map +1 -1
- package/dist/src/codegen/generator/module.js +16 -5
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/state.js +1 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements.d.ts +4 -1
- package/dist/src/codegen/generator/statements.js +121 -7
- package/dist/src/codegen/generator/statements.js.map +1 -1
- package/dist/src/codegen/generator/tables.js +1 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/compiler.d.ts +5 -0
- package/dist/src/driver/compiler.js +26 -0
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/modules.js +29 -4
- package/dist/src/driver/modules.js.map +1 -1
- package/dist/src/lexer/token.d.ts +10 -0
- package/dist/src/lexer/token.js +69 -0
- package/dist/src/lexer/token.js.map +1 -1
- package/dist/src/parser/context.js +4 -16
- package/dist/src/parser/context.js.map +1 -1
- package/dist/src/parser/declarations.d.ts +2 -0
- package/dist/src/parser/declarations.js +136 -24
- package/dist/src/parser/declarations.js.map +1 -1
- package/dist/src/parser/expressions.js +19 -2
- package/dist/src/parser/expressions.js.map +1 -1
- package/dist/src/parser/helpers.js +2 -0
- package/dist/src/parser/helpers.js.map +1 -1
- package/dist/src/parser/modules.js +7 -2
- package/dist/src/parser/modules.js.map +1 -1
- package/dist/src/parser/statements.js +28 -3
- package/dist/src/parser/statements.js.map +1 -1
- package/dist/src/parser/types.js +5 -0
- package/dist/src/parser/types.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +16 -0
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/driver/compiler.test.js +13 -1
- package/dist/tests/driver/compiler.test.js.map +1 -1
- package/dist/tests/e2e/compile.test.js +21 -0
- package/dist/tests/e2e/compile.test.js.map +1 -1
- package/dist/tests/parser/parser.test.js +53 -0
- package/dist/tests/parser/parser.test.js.map +1 -1
- package/package.json +1 -1
- package/runtime/xt_stdlib2.c +11 -0
- package/src/ast/declarations.ts +26 -1
- package/src/ast/kinds.ts +4 -0
- package/src/ast/statements.ts +2 -1
- package/src/binder/binder.ts +46 -9
- package/src/cli/main.ts +2 -3
- package/src/codegen/generator/expressions.ts +25 -0
- package/src/codegen/generator/module.ts +19 -6
- package/src/codegen/generator/state.ts +1 -0
- package/src/codegen/generator/statements.ts +118 -5
- package/src/codegen/generator/tables.ts +1 -0
- package/src/driver/compiler.ts +29 -0
- package/src/driver/modules.ts +28 -3
- package/src/lexer/token.ts +73 -0
- package/src/parser/context.ts +4 -15
- package/src/parser/declarations.ts +130 -22
- package/src/parser/expressions.ts +19 -3
- package/src/parser/helpers.ts +2 -0
- package/src/parser/modules.ts +7 -2
- package/src/parser/statements.ts +27 -3
- package/src/parser/types.ts +5 -0
package/src/ast/declarations.ts
CHANGED
|
@@ -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:
|
|
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
|
}
|
package/src/ast/statements.ts
CHANGED
|
@@ -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:
|
|
34
|
+
readonly name: BindingName;
|
|
34
35
|
readonly exclamation: boolean;
|
|
35
36
|
readonly type?: TypeNode;
|
|
36
37
|
readonly initializer?: Expression;
|
package/src/binder/binder.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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,
|
|
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 } =
|
|
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 =
|
|
44
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
package/src/driver/compiler.ts
CHANGED
|
@@ -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));
|
package/src/driver/modules.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 = {
|
package/src/lexer/token.ts
CHANGED
|
@@ -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
|
+
}
|