xbintsc 0.1.0
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/LICENSE +21 -0
- package/README.md +191 -0
- package/bin/xbintsc.js +26 -0
- package/dist/src/ast/factory.d.ts +11 -0
- package/dist/src/ast/factory.js +36 -0
- package/dist/src/ast/factory.js.map +1 -0
- package/dist/src/ast/nodes.d.ts +848 -0
- package/dist/src/ast/nodes.js +247 -0
- package/dist/src/ast/nodes.js.map +1 -0
- package/dist/src/ast/visitor.d.ts +18 -0
- package/dist/src/ast/visitor.js +52 -0
- package/dist/src/ast/visitor.js.map +1 -0
- package/dist/src/binder/binder.d.ts +82 -0
- package/dist/src/binder/binder.js +443 -0
- package/dist/src/binder/binder.js.map +1 -0
- package/dist/src/cli/main.d.ts +16 -0
- package/dist/src/cli/main.js +191 -0
- package/dist/src/cli/main.js.map +1 -0
- package/dist/src/codegen/llvm.d.ts +31 -0
- package/dist/src/codegen/llvm.js +1178 -0
- package/dist/src/codegen/llvm.js.map +1 -0
- package/dist/src/codegen/values.d.ts +23 -0
- package/dist/src/codegen/values.js +32 -0
- package/dist/src/codegen/values.js.map +1 -0
- package/dist/src/diagnostics/diagnostic.d.ts +69 -0
- package/dist/src/diagnostics/diagnostic.js +104 -0
- package/dist/src/diagnostics/diagnostic.js.map +1 -0
- package/dist/src/diagnostics/source.d.ts +43 -0
- package/dist/src/diagnostics/source.js +73 -0
- package/dist/src/diagnostics/source.js.map +1 -0
- package/dist/src/driver/cache.d.ts +37 -0
- package/dist/src/driver/cache.js +82 -0
- package/dist/src/driver/cache.js.map +1 -0
- package/dist/src/driver/compiler.d.ts +41 -0
- package/dist/src/driver/compiler.js +128 -0
- package/dist/src/driver/compiler.js.map +1 -0
- package/dist/src/driver/index.d.ts +4 -0
- package/dist/src/driver/index.js +5 -0
- package/dist/src/driver/index.js.map +1 -0
- package/dist/src/driver/paths.d.ts +4 -0
- package/dist/src/driver/paths.js +22 -0
- package/dist/src/driver/paths.js.map +1 -0
- package/dist/src/driver/toolchain.d.ts +43 -0
- package/dist/src/driver/toolchain.js +76 -0
- package/dist/src/driver/toolchain.js.map +1 -0
- package/dist/src/extensions/node.d.ts +9 -0
- package/dist/src/extensions/node.js +20 -0
- package/dist/src/extensions/node.js.map +1 -0
- package/dist/src/extensions/registry.d.ts +45 -0
- package/dist/src/extensions/registry.js +76 -0
- package/dist/src/extensions/registry.js.map +1 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lexer/scanner.d.ts +48 -0
- package/dist/src/lexer/scanner.js +650 -0
- package/dist/src/lexer/scanner.js.map +1 -0
- package/dist/src/lexer/token.d.ts +170 -0
- package/dist/src/lexer/token.js +250 -0
- package/dist/src/lexer/token.js.map +1 -0
- package/dist/src/parser/parser.d.ts +127 -0
- package/dist/src/parser/parser.js +2334 -0
- package/dist/src/parser/parser.js.map +1 -0
- package/dist/tests/binder/binder.test.d.ts +1 -0
- package/dist/tests/binder/binder.test.js +89 -0
- package/dist/tests/binder/binder.test.js.map +1 -0
- package/dist/tests/cli/main.test.d.ts +1 -0
- package/dist/tests/cli/main.test.js +68 -0
- package/dist/tests/cli/main.test.js.map +1 -0
- package/dist/tests/cli/process.test.d.ts +9 -0
- package/dist/tests/cli/process.test.js +57 -0
- package/dist/tests/cli/process.test.js.map +1 -0
- package/dist/tests/codegen/llvm.test.d.ts +1 -0
- package/dist/tests/codegen/llvm.test.js +93 -0
- package/dist/tests/codegen/llvm.test.js.map +1 -0
- package/dist/tests/driver/cache.test.d.ts +1 -0
- package/dist/tests/driver/cache.test.js +66 -0
- package/dist/tests/driver/cache.test.js.map +1 -0
- package/dist/tests/driver/compiler.test.d.ts +1 -0
- package/dist/tests/driver/compiler.test.js +80 -0
- package/dist/tests/driver/compiler.test.js.map +1 -0
- package/dist/tests/driver/toolchain.test.d.ts +1 -0
- package/dist/tests/driver/toolchain.test.js +60 -0
- package/dist/tests/driver/toolchain.test.js.map +1 -0
- package/dist/tests/e2e/compile.test.d.ts +5 -0
- package/dist/tests/e2e/compile.test.js +93 -0
- package/dist/tests/e2e/compile.test.js.map +1 -0
- package/dist/tests/extensions/registry.test.d.ts +1 -0
- package/dist/tests/extensions/registry.test.js +47 -0
- package/dist/tests/extensions/registry.test.js.map +1 -0
- package/dist/tests/helpers.d.ts +23 -0
- package/dist/tests/helpers.js +41 -0
- package/dist/tests/helpers.js.map +1 -0
- package/dist/tests/lexer/scanner.test.d.ts +1 -0
- package/dist/tests/lexer/scanner.test.js +95 -0
- package/dist/tests/lexer/scanner.test.js.map +1 -0
- package/dist/tests/parser/parser.test.d.ts +1 -0
- package/dist/tests/parser/parser.test.js +137 -0
- package/dist/tests/parser/parser.test.js.map +1 -0
- package/package.json +49 -0
- package/runtime/ext_node.c +42 -0
- package/runtime/rt.h +177 -0
- package/runtime/xt_runtime.c +682 -0
- package/scripts/build-runtime.mjs +34 -0
- package/src/ast/factory.ts +37 -0
- package/src/ast/nodes.ts +1111 -0
- package/src/ast/visitor.ts +60 -0
- package/src/binder/binder.ts +549 -0
- package/src/cli/main.ts +210 -0
- package/src/codegen/llvm.ts +1324 -0
- package/src/codegen/values.ts +36 -0
- package/src/diagnostics/diagnostic.ts +132 -0
- package/src/diagnostics/source.ts +97 -0
- package/src/driver/cache.ts +96 -0
- package/src/driver/compiler.ts +197 -0
- package/src/driver/index.ts +4 -0
- package/src/driver/paths.ts +24 -0
- package/src/driver/toolchain.ts +103 -0
- package/src/extensions/node.ts +23 -0
- package/src/extensions/registry.ts +101 -0
- package/src/index.ts +33 -0
- package/src/lexer/scanner.ts +650 -0
- package/src/lexer/token.ts +266 -0
- package/src/parser/parser.ts +2396 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { SyntaxKind, type Node } from "./nodes.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A generic, allocation-light AST visitor. The parser, binder and checker all
|
|
5
|
+
* traverse the tree; centralizing the "what are my children" logic avoids the
|
|
6
|
+
* classic bug where a new node kind is added but one traversal forgets it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type VisitorFn<T extends Node = Node> = (node: T) => void;
|
|
10
|
+
|
|
11
|
+
export interface Visitor {
|
|
12
|
+
[SyntaxKind.Identifier]?: VisitorFn;
|
|
13
|
+
[key: number]: VisitorFn | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Return the direct children of a node for generic traversal. */
|
|
17
|
+
export function getChildren(node: Node): Node[] {
|
|
18
|
+
const children: Node[] = [];
|
|
19
|
+
const push = (n: unknown): void => {
|
|
20
|
+
if (n && typeof n === "object" && "kind" in (n as Node)) children.push(n as Node);
|
|
21
|
+
};
|
|
22
|
+
const pushAll = (list: readonly unknown[] | undefined): void => {
|
|
23
|
+
if (!list) return;
|
|
24
|
+
for (const item of list) push(item);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const n = node as unknown as Record<string, unknown>;
|
|
28
|
+
for (const key of Object.keys(n)) {
|
|
29
|
+
if (key === "kind" || key === "start" || key === "end" || key === "text" || key === "value") continue;
|
|
30
|
+
const value = n[key];
|
|
31
|
+
if (Array.isArray(value)) pushAll(value);
|
|
32
|
+
else if (value && typeof value === "object" && "kind" in (value as object)) push(value);
|
|
33
|
+
}
|
|
34
|
+
return children;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Depth-first pre-order traversal. */
|
|
38
|
+
export function forEachChild(node: Node, visit: (child: Node) => void): void {
|
|
39
|
+
for (const child of getChildren(node)) visit(child);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function walk(node: Node, visit: VisitorFn): void {
|
|
43
|
+
visit(node);
|
|
44
|
+
for (const child of getChildren(node)) walk(child, visit);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Find the innermost node whose range contains `offset`. */
|
|
48
|
+
export function nodeAtPosition(root: Node, offset: number): Node | undefined {
|
|
49
|
+
let current: Node | undefined = root;
|
|
50
|
+
let result: Node | undefined;
|
|
51
|
+
while (current) {
|
|
52
|
+
const children: Node[] = getChildren(current).filter((c: Node) => c.start <= offset && offset < c.end);
|
|
53
|
+
if (children.length === 0) break;
|
|
54
|
+
// Prefer the smallest (innermost) child.
|
|
55
|
+
children.sort((a: Node, b: Node) => a.end - a.start - (b.end - b.start));
|
|
56
|
+
current = children[0];
|
|
57
|
+
result = current;
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The binder walks the AST once and answers two questions the code generator
|
|
3
|
+
* and checker depend on:
|
|
4
|
+
*
|
|
5
|
+
* 1. Which declaration does every identifier refer to? (scopes + symbols)
|
|
6
|
+
* 2. Which variables are captured by nested functions? (closure conversion)
|
|
7
|
+
*
|
|
8
|
+
* The result is deliberately plain data — maps and records — so it can be
|
|
9
|
+
* cached, serialized for the incremental build, and unit tested in isolation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
SyntaxKind,
|
|
14
|
+
type ArrowFunction,
|
|
15
|
+
type Block,
|
|
16
|
+
type FunctionDeclaration,
|
|
17
|
+
type FunctionExpression,
|
|
18
|
+
type Identifier,
|
|
19
|
+
type Node,
|
|
20
|
+
type SourceFileNode,
|
|
21
|
+
type Statement,
|
|
22
|
+
type Expression,
|
|
23
|
+
type VariableDeclarationList,
|
|
24
|
+
type VariableStatement,
|
|
25
|
+
type ImportDeclaration,
|
|
26
|
+
type ExportDeclaration,
|
|
27
|
+
type CatchClause,
|
|
28
|
+
type ForStatement,
|
|
29
|
+
type ForOfStatement,
|
|
30
|
+
type ForInStatement,
|
|
31
|
+
} from "../ast/nodes.js";
|
|
32
|
+
|
|
33
|
+
export enum SymbolKind {
|
|
34
|
+
Var = "var",
|
|
35
|
+
Let = "let",
|
|
36
|
+
Const = "const",
|
|
37
|
+
Function = "function",
|
|
38
|
+
Parameter = "parameter",
|
|
39
|
+
Class = "class",
|
|
40
|
+
Interface = "interface",
|
|
41
|
+
TypeAlias = "type",
|
|
42
|
+
Enum = "enum",
|
|
43
|
+
Import = "import",
|
|
44
|
+
Namespace = "namespace",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SymbolInfo {
|
|
48
|
+
readonly id: number;
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly kind: SymbolKind;
|
|
51
|
+
/** False for `const` bindings and imports. */
|
|
52
|
+
readonly mutable: boolean;
|
|
53
|
+
/** The function this symbol belongs to. */
|
|
54
|
+
readonly fn: FunctionInfo;
|
|
55
|
+
readonly declarations: Node[];
|
|
56
|
+
readonly references: Identifier[];
|
|
57
|
+
/** Set when referenced from a nested function. */
|
|
58
|
+
captured: boolean;
|
|
59
|
+
/** Assigned after construction once we know it is captured. */
|
|
60
|
+
boxed: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type FunctionNode = SourceFileNode | FunctionDeclaration | FunctionExpression | ArrowFunction;
|
|
64
|
+
|
|
65
|
+
export interface FunctionInfo {
|
|
66
|
+
readonly id: number;
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly node: FunctionNode;
|
|
69
|
+
readonly parent?: FunctionInfo;
|
|
70
|
+
readonly scope: Scope;
|
|
71
|
+
readonly params: SymbolInfo[];
|
|
72
|
+
/** Every binding declared directly in this function (params + locals). */
|
|
73
|
+
readonly locals: SymbolInfo[];
|
|
74
|
+
/** Outer bindings referenced by this function or any descendant. */
|
|
75
|
+
readonly captures: SymbolInfo[];
|
|
76
|
+
readonly captureIndex: Map<number, number>;
|
|
77
|
+
readonly isModule: boolean;
|
|
78
|
+
readonly isArrow: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export enum ScopeKind {
|
|
82
|
+
Module = "module",
|
|
83
|
+
Function = "function",
|
|
84
|
+
Block = "block",
|
|
85
|
+
For = "for",
|
|
86
|
+
Catch = "catch",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface Scope {
|
|
90
|
+
readonly kind: ScopeKind;
|
|
91
|
+
readonly parent?: Scope;
|
|
92
|
+
readonly fn: FunctionInfo;
|
|
93
|
+
readonly symbols: Map<string, SymbolInfo>;
|
|
94
|
+
/** The syntax node that introduced the scope, when there is one. */
|
|
95
|
+
readonly node?: Node;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface BindResult {
|
|
99
|
+
readonly moduleFunction: FunctionInfo;
|
|
100
|
+
readonly functions: FunctionInfo[];
|
|
101
|
+
/** Maps a function-like AST node to the FunctionInfo the binder built for it. */
|
|
102
|
+
readonly functionOfNode: Map<Node, FunctionInfo>;
|
|
103
|
+
readonly symbolOfDeclaration: Map<Node, SymbolInfo>;
|
|
104
|
+
readonly symbolOfIdentifier: Map<Identifier, SymbolInfo>;
|
|
105
|
+
readonly scopes: Map<Node, Scope>;
|
|
106
|
+
/** Identifier references that could not be resolved in user code. */
|
|
107
|
+
readonly unresolved: Identifier[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface MutableFunction extends FunctionInfo {
|
|
111
|
+
locals: SymbolInfo[];
|
|
112
|
+
captures: SymbolInfo[];
|
|
113
|
+
captureIndex: Map<number, number>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function bind(sourceFile: SourceFileNode): BindResult {
|
|
117
|
+
const binder = new Binder(sourceFile);
|
|
118
|
+
return binder.bind();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class Binder {
|
|
122
|
+
private readonly functions: FunctionInfo[] = [];
|
|
123
|
+
private readonly symbolOfDeclaration = new Map<Node, SymbolInfo>();
|
|
124
|
+
private readonly symbolOfIdentifier = new Map<Identifier, SymbolInfo>();
|
|
125
|
+
private readonly scopes = new Map<Node, Scope>();
|
|
126
|
+
private readonly unresolved: Identifier[] = [];
|
|
127
|
+
private nextSymbolId = 1;
|
|
128
|
+
private nextFunctionId = 1;
|
|
129
|
+
private current!: MutableFunction;
|
|
130
|
+
|
|
131
|
+
constructor(private readonly sourceFile: SourceFileNode) {}
|
|
132
|
+
|
|
133
|
+
bind(): BindResult {
|
|
134
|
+
this.current = this.createFunction("(module)", this.sourceFile, undefined, true, false);
|
|
135
|
+
const scope = this.createScope(ScopeKind.Module, this.sourceFile, undefined);
|
|
136
|
+
(this.current as { scope: Scope }).scope = scope;
|
|
137
|
+
this.scopes.set(this.sourceFile, scope);
|
|
138
|
+
|
|
139
|
+
this.predeclareStatements(this.sourceFile.statements, scope);
|
|
140
|
+
this.collectFunctionScoped(this.sourceFile.statements, this.current);
|
|
141
|
+
this.bindStatements(this.sourceFile.statements, scope);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
moduleFunction: this.current,
|
|
145
|
+
functions: this.functions,
|
|
146
|
+
functionOfNode: new Map(this.functions.map((fn) => [fn.node as Node, fn])),
|
|
147
|
+
symbolOfDeclaration: this.symbolOfDeclaration,
|
|
148
|
+
symbolOfIdentifier: this.symbolOfIdentifier,
|
|
149
|
+
scopes: this.scopes,
|
|
150
|
+
unresolved: this.unresolved,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// -- construction helpers ------------------------------------------------
|
|
155
|
+
|
|
156
|
+
private createFunction(
|
|
157
|
+
name: string,
|
|
158
|
+
node: FunctionNode,
|
|
159
|
+
parent: MutableFunction | undefined,
|
|
160
|
+
isModule: boolean,
|
|
161
|
+
isArrow: boolean,
|
|
162
|
+
): MutableFunction {
|
|
163
|
+
const fn: MutableFunction = {
|
|
164
|
+
id: this.nextFunctionId++,
|
|
165
|
+
name,
|
|
166
|
+
node,
|
|
167
|
+
parent,
|
|
168
|
+
scope: undefined as unknown as Scope,
|
|
169
|
+
params: [],
|
|
170
|
+
locals: [],
|
|
171
|
+
captures: [],
|
|
172
|
+
captureIndex: new Map(),
|
|
173
|
+
isModule,
|
|
174
|
+
isArrow,
|
|
175
|
+
};
|
|
176
|
+
this.functions.push(fn);
|
|
177
|
+
return fn;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private createScope(kind: ScopeKind, node: Node | undefined, parent: Scope | undefined): Scope {
|
|
181
|
+
return { kind, node, parent, fn: this.current, symbols: new Map() };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private declare(
|
|
185
|
+
name: string,
|
|
186
|
+
kind: SymbolKind,
|
|
187
|
+
node: Node,
|
|
188
|
+
scope: Scope,
|
|
189
|
+
mutable: boolean,
|
|
190
|
+
): SymbolInfo {
|
|
191
|
+
const existing = scope.symbols.get(name);
|
|
192
|
+
if (existing) {
|
|
193
|
+
// `var` redeclarations and duplicate functions merge into one symbol.
|
|
194
|
+
if (
|
|
195
|
+
(kind === SymbolKind.Var && existing.kind === SymbolKind.Var) ||
|
|
196
|
+
(kind === SymbolKind.Function && existing.kind === SymbolKind.Function)
|
|
197
|
+
) {
|
|
198
|
+
(existing.declarations as Node[]).push(node);
|
|
199
|
+
}
|
|
200
|
+
this.symbolOfDeclaration.set(node, existing);
|
|
201
|
+
return existing;
|
|
202
|
+
}
|
|
203
|
+
const symbol: SymbolInfo = {
|
|
204
|
+
id: this.nextSymbolId++,
|
|
205
|
+
name,
|
|
206
|
+
kind,
|
|
207
|
+
mutable,
|
|
208
|
+
fn: this.current,
|
|
209
|
+
declarations: [node],
|
|
210
|
+
references: [],
|
|
211
|
+
captured: false,
|
|
212
|
+
boxed: false,
|
|
213
|
+
};
|
|
214
|
+
scope.symbols.set(name, symbol);
|
|
215
|
+
this.symbolOfDeclaration.set(node, symbol);
|
|
216
|
+
if (scope.fn === this.current) this.current.locals.push(symbol);
|
|
217
|
+
return symbol;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// -- declaration passes --------------------------------------------------
|
|
221
|
+
|
|
222
|
+
/** Pre-declare block-scoped bindings that appear directly in `statements`. */
|
|
223
|
+
private predeclareStatements(statements: readonly Statement[], scope: Scope): void {
|
|
224
|
+
for (const statement of statements) {
|
|
225
|
+
switch (statement.kind) {
|
|
226
|
+
case SyntaxKind.VariableStatement: {
|
|
227
|
+
this.predeclareVariableList((statement as VariableStatement).declarationList, scope);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case SyntaxKind.FunctionDeclaration:
|
|
231
|
+
this.declare((statement as FunctionDeclaration).name?.text ?? "(anonymous)", SymbolKind.Function, statement, scope, true);
|
|
232
|
+
break;
|
|
233
|
+
case SyntaxKind.ClassDeclaration:
|
|
234
|
+
this.declare((statement as { name: Identifier }).name.text, SymbolKind.Class, statement, scope, false);
|
|
235
|
+
break;
|
|
236
|
+
case SyntaxKind.InterfaceDeclaration:
|
|
237
|
+
this.declare((statement as { name: Identifier }).name.text, SymbolKind.Interface, statement, scope, false);
|
|
238
|
+
break;
|
|
239
|
+
case SyntaxKind.TypeAliasDeclaration:
|
|
240
|
+
this.declare((statement as { name: Identifier }).name.text, SymbolKind.TypeAlias, statement, scope, false);
|
|
241
|
+
break;
|
|
242
|
+
case SyntaxKind.EnumDeclaration:
|
|
243
|
+
this.declare((statement as { name: Identifier }).name.text, SymbolKind.Enum, statement, scope, true);
|
|
244
|
+
break;
|
|
245
|
+
case SyntaxKind.ImportDeclaration:
|
|
246
|
+
this.predeclareImport(statement as ImportDeclaration, scope);
|
|
247
|
+
break;
|
|
248
|
+
case SyntaxKind.ExportDeclaration: {
|
|
249
|
+
const exported = statement as ExportDeclaration;
|
|
250
|
+
if (exported.exportClause && exported.exportClause.kind === SyntaxKind.NamedExports) {
|
|
251
|
+
for (const specifier of exported.exportClause.elements) {
|
|
252
|
+
if (specifier.propertyName) this.reference(specifier.propertyName, scope);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
case SyntaxKind.ModuleDeclaration:
|
|
258
|
+
this.declare((statement as { name: Identifier }).name.text, SymbolKind.Namespace, statement, scope, false);
|
|
259
|
+
break;
|
|
260
|
+
default:
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private predeclareVariableList(list: VariableDeclarationList, scope: Scope): void {
|
|
267
|
+
const kind =
|
|
268
|
+
list.declarationKind === "const"
|
|
269
|
+
? SymbolKind.Const
|
|
270
|
+
: list.declarationKind === "var"
|
|
271
|
+
? SymbolKind.Var
|
|
272
|
+
: SymbolKind.Let;
|
|
273
|
+
for (const declaration of list.declarations) {
|
|
274
|
+
this.declare(declaration.name.text, kind, declaration, scope, kind !== SymbolKind.Const);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private predeclareImport(node: ImportDeclaration, scope: Scope): void {
|
|
279
|
+
const clause = node.importClause;
|
|
280
|
+
if (!clause) return;
|
|
281
|
+
if (clause.name) this.declare(clause.name.text, SymbolKind.Import, clause.name, scope, false);
|
|
282
|
+
const bindings = clause.namedBindings;
|
|
283
|
+
if (bindings && bindings.kind === SyntaxKind.NamedImports) {
|
|
284
|
+
for (const specifier of bindings.elements) {
|
|
285
|
+
this.declare(specifier.name.text, SymbolKind.Import, specifier.name, scope, false);
|
|
286
|
+
}
|
|
287
|
+
} else if (bindings && bindings.kind === SyntaxKind.NamespaceImport) {
|
|
288
|
+
this.declare(bindings.name.text, SymbolKind.Import, bindings.name, scope, false);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Collect `var` and function declarations from nested blocks into the
|
|
294
|
+
* enclosing function scope, without crossing function boundaries.
|
|
295
|
+
*/
|
|
296
|
+
private collectFunctionScoped(statements: readonly Statement[], fn: MutableFunction): void {
|
|
297
|
+
const visit = (node: Node): void => {
|
|
298
|
+
switch (node.kind) {
|
|
299
|
+
case SyntaxKind.FunctionDeclaration:
|
|
300
|
+
case SyntaxKind.FunctionExpression:
|
|
301
|
+
case SyntaxKind.ArrowFunction:
|
|
302
|
+
case SyntaxKind.ClassDeclaration:
|
|
303
|
+
case SyntaxKind.ClassExpression:
|
|
304
|
+
return; // opaque, except we still want the declaration name handled at its site
|
|
305
|
+
case SyntaxKind.VariableStatement: {
|
|
306
|
+
const decl = node as VariableStatement;
|
|
307
|
+
if (decl.declarationList.declarationKind === "var") {
|
|
308
|
+
for (const d of decl.declarationList.declarations) {
|
|
309
|
+
const existing = fn.scope.symbols.get(d.name.text);
|
|
310
|
+
if (!existing) this.declare(d.name.text, SymbolKind.Var, d, fn.scope, true);
|
|
311
|
+
else this.symbolOfDeclaration.set(d, existing);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
default:
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
for (const child of childNodes(node)) visit(child);
|
|
320
|
+
};
|
|
321
|
+
for (const statement of statements) {
|
|
322
|
+
if (
|
|
323
|
+
statement.kind === SyntaxKind.FunctionDeclaration ||
|
|
324
|
+
statement.kind === SyntaxKind.ClassDeclaration
|
|
325
|
+
) {
|
|
326
|
+
const name = (statement as { name: Identifier }).name.text;
|
|
327
|
+
if (!fn.scope.symbols.has(name)) {
|
|
328
|
+
this.declare(name, statement.kind === SyntaxKind.FunctionDeclaration ? SymbolKind.Function : SymbolKind.Class, statement, fn.scope, statement.kind === SyntaxKind.FunctionDeclaration);
|
|
329
|
+
}
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
visit(statement);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// -- reference pass ------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
private bindStatements(statements: readonly Statement[], scope: Scope): void {
|
|
339
|
+
for (const statement of statements) this.bindNode(statement, scope);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private bindBlock(block: Block, scope: Scope): void {
|
|
343
|
+
const blockScope = this.createScope(ScopeKind.Block, block, scope);
|
|
344
|
+
this.scopes.set(block, blockScope);
|
|
345
|
+
this.predeclareStatements(block.statements, blockScope);
|
|
346
|
+
this.bindStatements(block.statements, blockScope);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private bindNode(node: Node | undefined, scope: Scope): void {
|
|
350
|
+
if (!node) return;
|
|
351
|
+
switch (node.kind) {
|
|
352
|
+
case SyntaxKind.Block:
|
|
353
|
+
this.bindBlock(node as Block, scope);
|
|
354
|
+
return;
|
|
355
|
+
case SyntaxKind.VariableStatement:
|
|
356
|
+
this.bindVariableList((node as VariableStatement).declarationList, scope);
|
|
357
|
+
return;
|
|
358
|
+
case SyntaxKind.FunctionDeclaration: {
|
|
359
|
+
const fn = node as FunctionDeclaration;
|
|
360
|
+
const symbol = this.symbolOfDeclaration.get(fn);
|
|
361
|
+
if (symbol) (symbol.declarations as Node[]).length; // keep declaration mapping warm
|
|
362
|
+
this.bindFunction(fn, scope, symbol);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
case SyntaxKind.FunctionExpression:
|
|
366
|
+
case SyntaxKind.ArrowFunction:
|
|
367
|
+
this.bindFunction(node as FunctionExpression | ArrowFunction, scope, undefined);
|
|
368
|
+
return;
|
|
369
|
+
case SyntaxKind.Identifier:
|
|
370
|
+
this.reference(node as Identifier, scope);
|
|
371
|
+
return;
|
|
372
|
+
case SyntaxKind.InterfaceDeclaration:
|
|
373
|
+
case SyntaxKind.TypeAliasDeclaration:
|
|
374
|
+
// Type-only: skip the body entirely.
|
|
375
|
+
return;
|
|
376
|
+
case SyntaxKind.ImportDeclaration:
|
|
377
|
+
return;
|
|
378
|
+
case SyntaxKind.ExportDeclaration: {
|
|
379
|
+
const exported = node as ExportDeclaration;
|
|
380
|
+
if (exported.exportClause && exported.exportClause.kind === SyntaxKind.NamedExports) {
|
|
381
|
+
for (const specifier of exported.exportClause.elements) {
|
|
382
|
+
if (specifier.propertyName) this.reference(specifier.propertyName, scope);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
case SyntaxKind.ExportAssignment:
|
|
388
|
+
this.bindNode((node as unknown as { expression: Expression }).expression, scope);
|
|
389
|
+
return;
|
|
390
|
+
case SyntaxKind.ForStatement: {
|
|
391
|
+
const forNode = node as ForStatement;
|
|
392
|
+
const forScope = this.createScope(ScopeKind.For, forNode, scope);
|
|
393
|
+
this.scopes.set(forNode, forScope);
|
|
394
|
+
if (forNode.initializer) {
|
|
395
|
+
if (forNode.initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
396
|
+
this.predeclareVariableList(forNode.initializer as VariableDeclarationList, forScope);
|
|
397
|
+
this.bindVariableList(forNode.initializer as VariableDeclarationList, forScope);
|
|
398
|
+
} else {
|
|
399
|
+
this.bindNode(forNode.initializer, forScope);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
this.bindNode(forNode.condition, forScope);
|
|
403
|
+
this.bindNode(forNode.incrementor, forScope);
|
|
404
|
+
this.bindNode(forNode.statement, forScope);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
case SyntaxKind.ForOfStatement:
|
|
408
|
+
case SyntaxKind.ForInStatement: {
|
|
409
|
+
const loop = node as ForOfStatement | ForInStatement;
|
|
410
|
+
const loopScope = this.createScope(ScopeKind.For, loop, scope);
|
|
411
|
+
this.scopes.set(loop, loopScope);
|
|
412
|
+
if (loop.initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
413
|
+
this.predeclareVariableList(loop.initializer, loopScope);
|
|
414
|
+
this.bindVariableList(loop.initializer, loopScope);
|
|
415
|
+
} else {
|
|
416
|
+
this.bindNode(loop.initializer, loopScope);
|
|
417
|
+
}
|
|
418
|
+
this.bindNode(loop.expression, loopScope);
|
|
419
|
+
this.bindNode(loop.statement, loopScope);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
case SyntaxKind.CatchClause: {
|
|
423
|
+
const clause = node as CatchClause;
|
|
424
|
+
const catchScope = this.createScope(ScopeKind.Catch, clause, scope);
|
|
425
|
+
this.scopes.set(clause, catchScope);
|
|
426
|
+
if (clause.variable) this.declare(clause.variable.text, SymbolKind.Let, clause.variable, catchScope, true);
|
|
427
|
+
this.bindBlock(clause.block, catchScope);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
default:
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
for (const child of childNodes(node)) this.bindNode(child, scope);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private bindVariableList(list: VariableDeclarationList, scope: Scope): void {
|
|
437
|
+
for (const declaration of list.declarations) {
|
|
438
|
+
const symbol = this.symbolOfDeclaration.get(declaration);
|
|
439
|
+
if (declaration.name) this.symbolOfDeclaration.set(declaration.name, symbol ?? this.symbolOfDeclaration.get(declaration) as SymbolInfo);
|
|
440
|
+
this.bindNode(declaration.initializer, scope);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private bindFunction(
|
|
445
|
+
node: FunctionDeclaration | FunctionExpression | ArrowFunction,
|
|
446
|
+
parentScope: Scope,
|
|
447
|
+
symbol: SymbolInfo | undefined,
|
|
448
|
+
): void {
|
|
449
|
+
const name =
|
|
450
|
+
node.kind === SyntaxKind.FunctionDeclaration
|
|
451
|
+
? (node as FunctionDeclaration).name?.text ?? "(anonymous)"
|
|
452
|
+
: node.kind === SyntaxKind.FunctionExpression
|
|
453
|
+
? (node as FunctionExpression).name?.text ?? "(anonymous)"
|
|
454
|
+
: "(arrow)";
|
|
455
|
+
const parentFn = this.current;
|
|
456
|
+
const fn = this.createFunction(name, node, parentFn, false, node.kind === SyntaxKind.ArrowFunction);
|
|
457
|
+
this.current = fn;
|
|
458
|
+
|
|
459
|
+
const scope = this.createScope(ScopeKind.Function, node, parentScope);
|
|
460
|
+
(fn as { scope: Scope }).scope = scope;
|
|
461
|
+
this.scopes.set(node, scope);
|
|
462
|
+
|
|
463
|
+
// Parameters live in the function scope.
|
|
464
|
+
const params: SymbolInfo[] = [];
|
|
465
|
+
for (const parameter of node.parameters) {
|
|
466
|
+
const paramSymbol = this.declare(parameter.name.text, SymbolKind.Parameter, parameter, scope, true);
|
|
467
|
+
params.push(paramSymbol);
|
|
468
|
+
if (parameter.initializer) this.bindNode(parameter.initializer, scope);
|
|
469
|
+
}
|
|
470
|
+
(fn as { params: SymbolInfo[] }).params = params;
|
|
471
|
+
|
|
472
|
+
// Initialize a named function expression's own name inside its scope.
|
|
473
|
+
if (node.kind === SyntaxKind.FunctionExpression && (node as FunctionExpression).name) {
|
|
474
|
+
const selfName = (node as FunctionExpression).name!;
|
|
475
|
+
this.declare(selfName.text, SymbolKind.Const, selfName, scope, false);
|
|
476
|
+
}
|
|
477
|
+
if (symbol) this.symbolOfDeclaration.set(node, symbol);
|
|
478
|
+
|
|
479
|
+
const body = node.body as unknown as Block | Expression | undefined;
|
|
480
|
+
if (body) {
|
|
481
|
+
this.collectFunctionScoped([body.kind === SyntaxKind.Block ? body : ({ kind: SyntaxKind.Block, statements: [], start: body.start, end: body.end } as Block)], fn);
|
|
482
|
+
if (body.kind === SyntaxKind.Block) {
|
|
483
|
+
this.predeclareStatements(body.statements, scope);
|
|
484
|
+
this.bindStatements(body.statements, scope);
|
|
485
|
+
} else {
|
|
486
|
+
this.bindNode(body, scope);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
this.current = parentFn;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private reference(identifier: Identifier, scope: Scope): void {
|
|
494
|
+
let current: Scope | undefined = scope;
|
|
495
|
+
while (current) {
|
|
496
|
+
const symbol = current.symbols.get(identifier.text);
|
|
497
|
+
if (symbol) {
|
|
498
|
+
(symbol.references as Identifier[]).push(identifier);
|
|
499
|
+
this.symbolOfIdentifier.set(identifier, symbol);
|
|
500
|
+
// Functions, classes and type-only names are resolved to their
|
|
501
|
+
// declaration sites by codegen; they are never captured as values.
|
|
502
|
+
if (symbol.fn !== this.current && isCapturable(symbol.kind)) this.markCaptured(symbol);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
current = current.parent;
|
|
506
|
+
}
|
|
507
|
+
this.unresolved.push(identifier);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/** Record that `symbol` escapes into `this.current`, threading it through every intermediate closure. */
|
|
511
|
+
private markCaptured(symbol: SymbolInfo): void {
|
|
512
|
+
symbol.captured = true;
|
|
513
|
+
symbol.boxed = true;
|
|
514
|
+
let fn: MutableFunction | undefined = this.current;
|
|
515
|
+
while (fn && fn !== symbol.fn) {
|
|
516
|
+
if (!fn.captureIndex.has(symbol.id)) {
|
|
517
|
+
fn.captureIndex.set(symbol.id, fn.captures.length);
|
|
518
|
+
fn.captures.push(symbol);
|
|
519
|
+
}
|
|
520
|
+
fn = fn.parent as MutableFunction | undefined;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** Direct AST children, tolerant of both arrays and single nodes. */
|
|
526
|
+
function childNodes(node: Node): Node[] {
|
|
527
|
+
const result: Node[] = [];
|
|
528
|
+
const n = node as unknown as Record<string, unknown>;
|
|
529
|
+
for (const key of Object.keys(n)) {
|
|
530
|
+
if (key === "kind" || key === "start" || key === "end") continue;
|
|
531
|
+
const value = n[key];
|
|
532
|
+
if (Array.isArray(value)) {
|
|
533
|
+
for (const item of value) if (item && typeof item === "object" && "kind" in (item as object)) result.push(item as Node);
|
|
534
|
+
} else if (value && typeof value === "object" && "kind" in (value as object)) {
|
|
535
|
+
result.push(value as Node);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** Only value bindings participate in closure capture. */
|
|
542
|
+
function isCapturable(kind: SymbolKind): boolean {
|
|
543
|
+
return (
|
|
544
|
+
kind === SymbolKind.Var ||
|
|
545
|
+
kind === SymbolKind.Let ||
|
|
546
|
+
kind === SymbolKind.Const ||
|
|
547
|
+
kind === SymbolKind.Parameter
|
|
548
|
+
);
|
|
549
|
+
}
|