xbintsc 0.1.6 → 0.1.9
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/README.md +7 -4
- package/dist/src/ast/common.d.ts +33 -0
- package/dist/src/ast/common.js +7 -0
- package/dist/src/ast/common.js.map +1 -0
- package/dist/src/ast/declarations.d.ts +110 -0
- package/dist/src/ast/declarations.js +5 -0
- package/dist/src/ast/declarations.js.map +1 -0
- package/dist/src/ast/expressions.d.ts +219 -0
- package/dist/src/ast/expressions.js +6 -0
- package/dist/src/ast/expressions.js.map +1 -0
- package/dist/src/ast/kinds.d.ts +160 -0
- package/dist/src/ast/kinds.js +171 -0
- package/dist/src/ast/kinds.js.map +1 -0
- package/dist/src/ast/modules.d.ts +61 -0
- package/dist/src/ast/modules.js +5 -0
- package/dist/src/ast/modules.js.map +1 -0
- package/dist/src/ast/nodes.d.ts +11 -839
- package/dist/src/ast/nodes.js +11 -237
- package/dist/src/ast/nodes.js.map +1 -1
- package/dist/src/ast/operators.d.ts +71 -0
- package/dist/src/ast/operators.js +77 -0
- package/dist/src/ast/operators.js.map +1 -0
- package/dist/src/ast/statements.d.ts +120 -0
- package/dist/src/ast/statements.js +5 -0
- package/dist/src/ast/statements.js.map +1 -0
- package/dist/src/ast/types.d.ts +123 -0
- package/dist/src/ast/types.js +6 -0
- package/dist/src/ast/types.js.map +1 -0
- package/dist/src/binder/binder.d.ts +35 -1
- package/dist/src/binder/binder.js +138 -0
- package/dist/src/binder/binder.js.map +1 -1
- package/dist/src/codegen/generator/calls.d.ts +26 -0
- package/dist/src/codegen/generator/calls.js +373 -0
- package/dist/src/codegen/generator/calls.js.map +1 -0
- package/dist/src/codegen/generator/context.d.ts +47 -0
- package/dist/src/codegen/generator/context.js +164 -0
- package/dist/src/codegen/generator/context.js.map +1 -0
- package/dist/src/codegen/generator/expressions.d.ts +25 -0
- package/dist/src/codegen/generator/expressions.js +454 -0
- package/dist/src/codegen/generator/expressions.js.map +1 -0
- package/dist/src/codegen/generator/generator.d.ts +22 -0
- package/dist/src/codegen/generator/generator.js +21 -0
- package/dist/src/codegen/generator/generator.js.map +1 -0
- package/dist/src/codegen/generator/module.d.ts +19 -0
- package/dist/src/codegen/generator/module.js +214 -0
- package/dist/src/codegen/generator/module.js.map +1 -0
- package/dist/src/codegen/generator/state.d.ts +42 -0
- package/dist/src/codegen/generator/state.js +125 -0
- package/dist/src/codegen/generator/state.js.map +1 -0
- package/dist/src/codegen/generator/statements.d.ts +22 -0
- package/dist/src/codegen/generator/statements.js +433 -0
- package/dist/src/codegen/generator/statements.js.map +1 -0
- package/dist/src/codegen/generator/tables.d.ts +23 -0
- package/dist/src/codegen/generator/tables.js +186 -0
- package/dist/src/codegen/generator/tables.js.map +1 -0
- package/dist/src/codegen/llvm.d.ts +4 -13
- package/dist/src/codegen/llvm.js +4 -1537
- package/dist/src/codegen/llvm.js.map +1 -1
- package/dist/src/driver/compiler.js +36 -11
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/modules.d.ts +24 -0
- package/dist/src/driver/modules.js +347 -0
- package/dist/src/driver/modules.js.map +1 -0
- package/dist/src/lexer/char.d.ts +78 -0
- package/dist/src/lexer/char.js +106 -0
- package/dist/src/lexer/char.js.map +1 -0
- package/dist/src/lexer/scanner.js +1 -98
- package/dist/src/lexer/scanner.js.map +1 -1
- package/dist/src/parser/context.d.ts +58 -0
- package/dist/src/parser/context.js +174 -0
- package/dist/src/parser/context.js.map +1 -0
- package/dist/src/parser/declarations.d.ts +26 -0
- package/dist/src/parser/declarations.js +461 -0
- package/dist/src/parser/declarations.js.map +1 -0
- package/dist/src/parser/expressions.d.ts +27 -0
- package/dist/src/parser/expressions.js +440 -0
- package/dist/src/parser/expressions.js.map +1 -0
- package/dist/src/parser/helpers.d.ts +7 -0
- package/dist/src/parser/helpers.js +36 -0
- package/dist/src/parser/helpers.js.map +1 -0
- package/dist/src/parser/literals.d.ts +16 -0
- package/dist/src/parser/literals.js +193 -0
- package/dist/src/parser/literals.js.map +1 -0
- package/dist/src/parser/modules.d.ts +12 -0
- package/dist/src/parser/modules.js +175 -0
- package/dist/src/parser/modules.js.map +1 -0
- package/dist/src/parser/operators.d.ts +12 -0
- package/dist/src/parser/operators.js +172 -0
- package/dist/src/parser/operators.js.map +1 -0
- package/dist/src/parser/parser.d.ts +17 -122
- package/dist/src/parser/parser.js +18 -2311
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/parser/speculation.d.ts +6 -0
- package/dist/src/parser/speculation.js +10 -0
- package/dist/src/parser/speculation.js.map +1 -0
- package/dist/src/parser/statements.d.ts +28 -0
- package/dist/src/parser/statements.js +402 -0
- package/dist/src/parser/statements.js.map +1 -0
- package/dist/src/parser/types.d.ts +21 -0
- package/dist/src/parser/types.js +338 -0
- package/dist/src/parser/types.js.map +1 -0
- package/dist/tests/codegen/llvm.test.js +1 -1
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/e2e/compile.test.js +86 -0
- package/dist/tests/e2e/compile.test.js.map +1 -1
- package/package.json +1 -1
- package/runtime/rt.h +55 -4
- package/runtime/rt_internal.h +169 -0
- package/runtime/xt_alloc.c +38 -0
- package/runtime/xt_builtins.c +238 -0
- package/runtime/xt_containers.c +312 -0
- package/runtime/xt_io.c +246 -0
- package/runtime/xt_promise.c +292 -0
- package/runtime/xt_stdlib.c +556 -0
- package/runtime/xt_stdlib2.c +1043 -0
- package/runtime/xt_values.c +291 -0
- package/scripts/build-runtime.mjs +8 -1
- package/src/ast/common.ts +210 -0
- package/src/ast/declarations.ts +130 -0
- package/src/ast/expressions.ts +275 -0
- package/src/ast/kinds.ts +176 -0
- package/src/ast/modules.ts +73 -0
- package/src/ast/nodes.ts +11 -1101
- package/src/ast/operators.ts +76 -0
- package/src/ast/statements.ts +148 -0
- package/src/ast/types.ts +173 -0
- package/src/binder/binder.ts +196 -0
- package/src/codegen/generator/calls.ts +433 -0
- package/src/codegen/generator/context.ts +190 -0
- package/src/codegen/generator/expressions.ts +508 -0
- package/src/codegen/generator/generator.ts +35 -0
- package/src/codegen/generator/module.ts +244 -0
- package/src/codegen/generator/state.ts +169 -0
- package/src/codegen/generator/statements.ts +480 -0
- package/src/codegen/generator/tables.ts +206 -0
- package/src/codegen/llvm.ts +4 -1720
- package/src/driver/compiler.ts +45 -12
- package/src/driver/modules.ts +377 -0
- package/src/lexer/char.ts +116 -0
- package/src/lexer/scanner.ts +9 -109
- package/src/parser/context.ts +196 -0
- package/src/parser/declarations.ts +502 -0
- package/src/parser/expressions.ts +468 -0
- package/src/parser/helpers.ts +42 -0
- package/src/parser/literals.ts +207 -0
- package/src/parser/modules.ts +194 -0
- package/src/parser/operators.ts +181 -0
- package/src/parser/parser.ts +35 -2373
- package/src/parser/speculation.ts +9 -0
- package/src/parser/statements.ts +457 -0
- package/src/parser/types.ts +365 -0
- package/runtime/xt_runtime.c +0 -1372
package/src/driver/compiler.ts
CHANGED
|
@@ -13,6 +13,8 @@ import { DiagnosticBag, type Diagnostic } from "../diagnostics/diagnostic.js";
|
|
|
13
13
|
import { SourceFile } from "../diagnostics/source.js";
|
|
14
14
|
import { Parser } from "../parser/parser.js";
|
|
15
15
|
import { generate } from "../codegen/llvm.js";
|
|
16
|
+
import { SyntaxKind } from "../ast/nodes.js";
|
|
17
|
+
import { bundleModules } from "./modules.js";
|
|
16
18
|
import { createDefaultRegistry, type ExtensionRegistry } from "../extensions/registry.js";
|
|
17
19
|
import { BuildCache, hashParts } from "./cache.js";
|
|
18
20
|
import { findRuntimeDir } from "./paths.js";
|
|
@@ -94,9 +96,31 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
94
96
|
? resolve(options.output ?? join(outDir, baseName + ".o"))
|
|
95
97
|
: executableName(absoluteEntry, outDir, options.output);
|
|
96
98
|
|
|
99
|
+
// -- front end -----------------------------------------------------------
|
|
100
|
+
const file = new SourceFile(absoluteEntry, sourceText);
|
|
101
|
+
const diagnostics = new DiagnosticBag();
|
|
102
|
+
const parser = new Parser(file, diagnostics);
|
|
103
|
+
let sourceFile = parser.parseSourceFile();
|
|
104
|
+
|
|
105
|
+
// Lower `import`/`export` by merging every reachable module into one file.
|
|
106
|
+
const isModule = sourceFile.statements.some(
|
|
107
|
+
(statement) =>
|
|
108
|
+
statement.kind === SyntaxKind.ImportDeclaration ||
|
|
109
|
+
statement.kind === SyntaxKind.ExportDeclaration ||
|
|
110
|
+
statement.kind === SyntaxKind.ExportAssignment,
|
|
111
|
+
);
|
|
112
|
+
let cacheText = sourceText;
|
|
113
|
+
if (isModule) {
|
|
114
|
+
const bundled = bundleModules(absoluteEntry, diagnostics);
|
|
115
|
+
if (bundled) {
|
|
116
|
+
sourceFile = bundled.sourceFile;
|
|
117
|
+
cacheText = bundled.text;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
97
121
|
const cacheKey = hashParts([
|
|
98
122
|
COMPILER_VERSION,
|
|
99
|
-
|
|
123
|
+
cacheText,
|
|
100
124
|
emit,
|
|
101
125
|
optimize,
|
|
102
126
|
process.platform,
|
|
@@ -105,15 +129,10 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
105
129
|
|
|
106
130
|
const cache = new BuildCache(cacheDir);
|
|
107
131
|
const outputs = [outputPath];
|
|
108
|
-
if (!options.force && cache.isFresh(cacheKey, outputs)) {
|
|
132
|
+
if (!options.force && !diagnostics.hasErrors && cache.isFresh(cacheKey, outputs)) {
|
|
109
133
|
return { outputPath, cached: true, diagnostics: [] };
|
|
110
134
|
}
|
|
111
135
|
|
|
112
|
-
// -- front end -----------------------------------------------------------
|
|
113
|
-
const file = new SourceFile(absoluteEntry, sourceText);
|
|
114
|
-
const diagnostics = new DiagnosticBag();
|
|
115
|
-
const parser = new Parser(file, diagnostics);
|
|
116
|
-
const sourceFile = parser.parseSourceFile();
|
|
117
136
|
const { ir } = generate(sourceFile, diagnostics, { builtins: registry.builtins() });
|
|
118
137
|
|
|
119
138
|
if (options.verbose) {
|
|
@@ -139,7 +158,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
139
158
|
const runtimeDir = findRuntimeDir();
|
|
140
159
|
mkdirSync(cacheDir, { recursive: true });
|
|
141
160
|
|
|
142
|
-
const {
|
|
161
|
+
const { runtimeObjects, extensionObjects } = ensureRuntimeObjects(runner, clang, runtimeDir, cacheDir, registry);
|
|
143
162
|
|
|
144
163
|
const objectPath = emit === "obj" ? outputPath : resolve(outDir, baseName + ".o");
|
|
145
164
|
compileIr(runner, { clang, irPath, objectPath, optimize });
|
|
@@ -152,7 +171,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
152
171
|
|
|
153
172
|
link(runner, {
|
|
154
173
|
clang,
|
|
155
|
-
objectPaths: [objectPath,
|
|
174
|
+
objectPaths: [objectPath, ...runtimeObjects, ...extensionObjects],
|
|
156
175
|
outputPath,
|
|
157
176
|
linkerFlags: [...(process.platform === "win32" ? [] : ["-lm"]), ...registry.linkerFlags()],
|
|
158
177
|
optimize,
|
|
@@ -164,10 +183,22 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
164
183
|
}
|
|
165
184
|
|
|
166
185
|
interface RuntimeObjects {
|
|
167
|
-
readonly
|
|
186
|
+
readonly runtimeObjects: readonly string[];
|
|
168
187
|
readonly extensionObjects: readonly string[];
|
|
169
188
|
}
|
|
170
189
|
|
|
190
|
+
/** Core runtime translation units (each compiled and cached independently). */
|
|
191
|
+
const RUNTIME_SOURCES = [
|
|
192
|
+
"xt_alloc.c",
|
|
193
|
+
"xt_values.c",
|
|
194
|
+
"xt_containers.c",
|
|
195
|
+
"xt_stdlib.c",
|
|
196
|
+
"xt_stdlib2.c",
|
|
197
|
+
"xt_promise.c",
|
|
198
|
+
"xt_builtins.c",
|
|
199
|
+
"xt_io.c",
|
|
200
|
+
] as const;
|
|
201
|
+
|
|
171
202
|
/**
|
|
172
203
|
* Compile the core runtime and every extension source, reusing cached object
|
|
173
204
|
* files keyed on the C source hash. Returns the object paths to link.
|
|
@@ -189,9 +220,11 @@ function ensureRuntimeObjects(
|
|
|
189
220
|
return objectPath;
|
|
190
221
|
};
|
|
191
222
|
|
|
192
|
-
const
|
|
223
|
+
const runtimeObjects = RUNTIME_SOURCES.map((source) =>
|
|
224
|
+
compileOne(join(runtimeDir, source), basename(source, ".c")),
|
|
225
|
+
);
|
|
193
226
|
const extensionObjects = registry
|
|
194
227
|
.runtimeSources()
|
|
195
228
|
.map((sourcePath, index) => compileOne(sourcePath, `ext_${index}_${basename(sourcePath, ".c")}`));
|
|
196
|
-
return {
|
|
229
|
+
return { runtimeObjects, extensionObjects };
|
|
197
230
|
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-level module bundler.
|
|
3
|
+
*
|
|
4
|
+
* xbintsc does not run a linker for modules, so `import`/`export` are lowered
|
|
5
|
+
* at the driver by merging every reachable module into a single source file.
|
|
6
|
+
* Each module's top-level bindings are renamed to a globally unique name and
|
|
7
|
+
* imported names are rewritten to the (renamed) exported binding.
|
|
8
|
+
*
|
|
9
|
+
* This is intentionally simple: it supports named imports/exports, `export
|
|
10
|
+
* default` for declarations, and `export ... from`. Namespace imports and
|
|
11
|
+
* `export *` are approximated, and circular graphs are reported as an error.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
15
|
+
import { dirname, resolve } from "node:path";
|
|
16
|
+
import {
|
|
17
|
+
ModifierKind,
|
|
18
|
+
SyntaxKind,
|
|
19
|
+
type ExportAssignment,
|
|
20
|
+
type ExportDeclaration,
|
|
21
|
+
type Identifier,
|
|
22
|
+
type ImportDeclaration,
|
|
23
|
+
type Node,
|
|
24
|
+
type SourceFileNode,
|
|
25
|
+
type Statement,
|
|
26
|
+
type VariableDeclaration,
|
|
27
|
+
type VariableDeclarationList,
|
|
28
|
+
type VariableStatement,
|
|
29
|
+
} from "../ast/nodes.js";
|
|
30
|
+
import { bind, SymbolKind, type BindResult, type SymbolInfo } from "../binder/binder.js";
|
|
31
|
+
import { DiagnosticBag, DiagnosticCode } from "../diagnostics/diagnostic.js";
|
|
32
|
+
import { SourceFile } from "../diagnostics/source.js";
|
|
33
|
+
import { Parser } from "../parser/parser.js";
|
|
34
|
+
|
|
35
|
+
interface ModuleRecord {
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly text: string;
|
|
38
|
+
readonly sourceFile: SourceFileNode;
|
|
39
|
+
readonly bind: BindResult;
|
|
40
|
+
readonly prefix: string;
|
|
41
|
+
/** exported name -> final (renamed) local name */
|
|
42
|
+
readonly exports: Map<string, string>;
|
|
43
|
+
/** original local name of every top-level symbol */
|
|
44
|
+
readonly originalNames: Map<number, string>;
|
|
45
|
+
/** final name of every top-level symbol */
|
|
46
|
+
readonly finalNames: Map<number, string>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface BundleResult {
|
|
50
|
+
readonly sourceFile: SourceFileNode;
|
|
51
|
+
readonly text: string;
|
|
52
|
+
readonly moduleCount: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".mts", ".cts", "/index.ts", "/index.tsx"];
|
|
56
|
+
|
|
57
|
+
function resolveModule(fromDir: string, specifier: string): string | undefined {
|
|
58
|
+
const base = resolve(fromDir, specifier);
|
|
59
|
+
for (const suffix of RESOLVE_SUFFIXES) {
|
|
60
|
+
const candidate = base + suffix;
|
|
61
|
+
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hasModifier(node: Node, kind: ModifierKind): boolean {
|
|
67
|
+
const modifiers = (node as { modifiers?: { modifierKind: ModifierKind }[] }).modifiers;
|
|
68
|
+
return !!modifiers && modifiers.some((modifier) => modifier.modifierKind === kind);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function declarationName(node: Node): Identifier | undefined {
|
|
72
|
+
switch (node.kind) {
|
|
73
|
+
case SyntaxKind.FunctionDeclaration:
|
|
74
|
+
case SyntaxKind.ClassDeclaration:
|
|
75
|
+
case SyntaxKind.EnumDeclaration:
|
|
76
|
+
case SyntaxKind.InterfaceDeclaration:
|
|
77
|
+
case SyntaxKind.TypeAliasDeclaration:
|
|
78
|
+
case SyntaxKind.ModuleDeclaration:
|
|
79
|
+
return (node as { name?: Identifier }).name;
|
|
80
|
+
case SyntaxKind.VariableDeclaration:
|
|
81
|
+
return (node as VariableDeclaration).name;
|
|
82
|
+
default:
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Load, parse and bind the entry module and every module it imports. */
|
|
88
|
+
function loadGraph(entryPath: string, diagnostics: DiagnosticBag): ModuleRecord[] | undefined {
|
|
89
|
+
const records = new Map<string, ModuleRecord>();
|
|
90
|
+
const order: ModuleRecord[] = [];
|
|
91
|
+
const visiting = new Set<string>();
|
|
92
|
+
let counter = 0;
|
|
93
|
+
let failed = false;
|
|
94
|
+
|
|
95
|
+
const load = (path: string): ModuleRecord | undefined => {
|
|
96
|
+
const existing = records.get(path);
|
|
97
|
+
if (existing) return existing;
|
|
98
|
+
if (visiting.has(path)) {
|
|
99
|
+
diagnostics.error(DiagnosticCode.CodegenError, `Circular import detected involving '${path}'`);
|
|
100
|
+
failed = true;
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
if (!existsSync(path)) {
|
|
104
|
+
diagnostics.error(DiagnosticCode.CodegenError, `Cannot find module '${path}'`);
|
|
105
|
+
failed = true;
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
visiting.add(path);
|
|
109
|
+
const text = readFileSync(path, "utf8");
|
|
110
|
+
const file = new SourceFile(path, text);
|
|
111
|
+
const parser = new Parser(file, diagnostics);
|
|
112
|
+
const sourceFile = parser.parseSourceFile();
|
|
113
|
+
const bindResult = bind(sourceFile);
|
|
114
|
+
const record: ModuleRecord = {
|
|
115
|
+
path,
|
|
116
|
+
text,
|
|
117
|
+
sourceFile,
|
|
118
|
+
bind: bindResult,
|
|
119
|
+
prefix: `m${counter++}_`,
|
|
120
|
+
exports: new Map(),
|
|
121
|
+
originalNames: new Map(),
|
|
122
|
+
finalNames: new Map(),
|
|
123
|
+
};
|
|
124
|
+
records.set(path, record);
|
|
125
|
+
|
|
126
|
+
for (const statement of sourceFile.statements) {
|
|
127
|
+
const specifier = moduleSpecifierOf(statement);
|
|
128
|
+
if (!specifier) continue;
|
|
129
|
+
const dependency = resolveModule(dirname(path), specifier);
|
|
130
|
+
if (!dependency) {
|
|
131
|
+
diagnostics.error(
|
|
132
|
+
DiagnosticCode.CodegenError,
|
|
133
|
+
`Cannot resolve module '${specifier}' from '${path}'`,
|
|
134
|
+
statement,
|
|
135
|
+
path,
|
|
136
|
+
);
|
|
137
|
+
failed = true;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
load(dependency);
|
|
141
|
+
}
|
|
142
|
+
visiting.delete(path);
|
|
143
|
+
order.push(record);
|
|
144
|
+
return record;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
load(entryPath);
|
|
148
|
+
if (failed) return undefined;
|
|
149
|
+
return order;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function moduleSpecifierOf(statement: Statement): string | undefined {
|
|
153
|
+
if (statement.kind === SyntaxKind.ImportDeclaration) {
|
|
154
|
+
return (statement as ImportDeclaration).moduleSpecifier.value;
|
|
155
|
+
}
|
|
156
|
+
if (statement.kind === SyntaxKind.ExportDeclaration) {
|
|
157
|
+
const specifier = (statement as ExportDeclaration).moduleSpecifier;
|
|
158
|
+
if (specifier) return specifier.value;
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Rewrite a symbol's declaration and every reference to `finalName`. */
|
|
164
|
+
function renameSymbol(symbol: SymbolInfo, finalName: string): void {
|
|
165
|
+
for (const declaration of symbol.declarations) {
|
|
166
|
+
const name = declarationName(declaration);
|
|
167
|
+
if (name) (name as { text: string }).text = finalName;
|
|
168
|
+
}
|
|
169
|
+
for (const reference of symbol.references) (reference as { text: string }).text = finalName;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Rewrite just the references of a symbol (used for imported names). */
|
|
173
|
+
function renameReferences(symbol: SymbolInfo, finalName: string): void {
|
|
174
|
+
for (const reference of symbol.references) (reference as { text: string }).text = finalName;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function moduleScope(record: ModuleRecord) {
|
|
178
|
+
return record.bind.scopes.get(record.sourceFile);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function topLevelSymbols(record: ModuleRecord): SymbolInfo[] {
|
|
182
|
+
const scope = moduleScope(record);
|
|
183
|
+
if (!scope) return [];
|
|
184
|
+
return [...scope.symbols.values()];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Merge every module's statements into a single file. Dependencies come first
|
|
189
|
+
* (post-order over the import graph), the entry module last.
|
|
190
|
+
*/
|
|
191
|
+
export function bundleModules(entryPath: string, diagnostics: DiagnosticBag): BundleResult | undefined {
|
|
192
|
+
const records = loadGraph(entryPath, diagnostics);
|
|
193
|
+
if (!records) return undefined;
|
|
194
|
+
|
|
195
|
+
const byPath = new Map(records.map((record) => [record.path, record]));
|
|
196
|
+
const dependencyOf = (record: ModuleRecord, specifier: string): ModuleRecord | undefined => {
|
|
197
|
+
const resolved = resolveModule(dirname(record.path), specifier);
|
|
198
|
+
return resolved ? byPath.get(resolved) : undefined;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Phase 1: record every top-level binding and pick a unique final name.
|
|
202
|
+
for (const record of records) {
|
|
203
|
+
for (const symbol of topLevelSymbols(record)) {
|
|
204
|
+
record.originalNames.set(symbol.id, symbol.name);
|
|
205
|
+
if (symbol.kind === SymbolKind.Import) continue;
|
|
206
|
+
record.finalNames.set(symbol.id, record.prefix + symbol.name);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Phase 2: collect each module's exported names.
|
|
211
|
+
for (const record of records) {
|
|
212
|
+
const scope = moduleScope(record);
|
|
213
|
+
if (!scope) continue;
|
|
214
|
+
const symbolByName = scope.symbols;
|
|
215
|
+
for (const statement of record.sourceFile.statements) {
|
|
216
|
+
if (statement.kind === SyntaxKind.ExportDeclaration) {
|
|
217
|
+
const declaration = statement as ExportDeclaration;
|
|
218
|
+
if (declaration.exportClause && declaration.exportClause.kind === SyntaxKind.NamedExports) {
|
|
219
|
+
for (const specifier of declaration.exportClause.elements) {
|
|
220
|
+
const localName = specifier.propertyName?.text ?? specifier.name.text;
|
|
221
|
+
const exportedName = specifier.name.text;
|
|
222
|
+
if (declaration.moduleSpecifier) {
|
|
223
|
+
const dependency = dependencyOf(record, declaration.moduleSpecifier.value);
|
|
224
|
+
const target = dependency?.exports.get(localName);
|
|
225
|
+
if (target) record.exports.set(exportedName, target);
|
|
226
|
+
} else {
|
|
227
|
+
const symbol = symbolByName.get(localName);
|
|
228
|
+
if (symbol) {
|
|
229
|
+
const finalName = record.finalNames.get(symbol.id) ?? symbol.name;
|
|
230
|
+
record.exports.set(exportedName, finalName);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} else if (!declaration.exportClause && declaration.moduleSpecifier) {
|
|
235
|
+
// `export * from "..."`: copy every re-exported name.
|
|
236
|
+
const dependency = dependencyOf(record, declaration.moduleSpecifier.value);
|
|
237
|
+
if (dependency) for (const [name, value] of dependency.exports) record.exports.set(name, value);
|
|
238
|
+
}
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (statement.kind === SyntaxKind.ExportAssignment) {
|
|
242
|
+
record.exports.set("default", `${record.prefix}default`);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (hasModifier(statement, ModifierKind.Default)) {
|
|
246
|
+
const name = declarationName(statement) ?? declarationName((statement as { declaration?: Node }).declaration ?? statement);
|
|
247
|
+
if (name) {
|
|
248
|
+
const symbol = symbolByName.get(name.text);
|
|
249
|
+
if (symbol) record.exports.set("default", record.finalNames.get(symbol.id) ?? symbol.name);
|
|
250
|
+
}
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (hasModifier(statement, ModifierKind.Export)) {
|
|
254
|
+
const names = exportedNames(statement);
|
|
255
|
+
for (const name of names) {
|
|
256
|
+
const symbol = symbolByName.get(name);
|
|
257
|
+
if (symbol) record.exports.set(name, record.finalNames.get(symbol.id) ?? symbol.name);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Phase 3: rewrite imported names and record re-exports.
|
|
264
|
+
for (const record of records) {
|
|
265
|
+
const scope = moduleScope(record);
|
|
266
|
+
if (!scope) continue;
|
|
267
|
+
for (const statement of record.sourceFile.statements) {
|
|
268
|
+
if (statement.kind !== SyntaxKind.ImportDeclaration) continue;
|
|
269
|
+
const declaration = statement as ImportDeclaration;
|
|
270
|
+
const dependency = dependencyOf(record, declaration.moduleSpecifier.value);
|
|
271
|
+
const clause = declaration.importClause;
|
|
272
|
+
if (!clause || !dependency) continue;
|
|
273
|
+
if (clause.name) {
|
|
274
|
+
const target = dependency.exports.get("default") ?? dependency.exports.get("default");
|
|
275
|
+
const local = scope.symbols.get(clause.name.text);
|
|
276
|
+
if (target && local) renameReferences(local, target);
|
|
277
|
+
}
|
|
278
|
+
const bindings = clause.namedBindings;
|
|
279
|
+
if (bindings && bindings.kind === SyntaxKind.NamedImports) {
|
|
280
|
+
for (const specifier of bindings.elements) {
|
|
281
|
+
const importedName = specifier.propertyName?.text ?? specifier.name.text;
|
|
282
|
+
const target = dependency.exports.get(importedName);
|
|
283
|
+
const local = scope.symbols.get(specifier.name.text);
|
|
284
|
+
if (target && local) renameReferences(local, target);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Phase 4: concatenate statements, dropping imports and pure export wrappers.
|
|
291
|
+
const merged: Statement[] = [];
|
|
292
|
+
const first = records[records.length - 1]!;
|
|
293
|
+
for (const record of records) {
|
|
294
|
+
for (const statement of record.sourceFile.statements) {
|
|
295
|
+
if (statement.kind === SyntaxKind.ImportDeclaration) continue;
|
|
296
|
+
if (statement.kind === SyntaxKind.ExportDeclaration) {
|
|
297
|
+
const declaration = statement as ExportDeclaration;
|
|
298
|
+
const inner = (declaration as unknown as { declaration?: Statement }).declaration;
|
|
299
|
+
if (inner) merged.push(inner);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (statement.kind === SyntaxKind.ExportAssignment) {
|
|
303
|
+
const assignment = statement as ExportAssignment;
|
|
304
|
+
merged.push(exportAssignmentToStatement(record, assignment));
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
merged.push(statement);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Phase 5: rename every top-level binding (declaration + references) to its
|
|
312
|
+
// unique final name, now that import references have been rewritten.
|
|
313
|
+
for (const record of records) {
|
|
314
|
+
for (const symbol of topLevelSymbols(record)) {
|
|
315
|
+
if (symbol.kind === SymbolKind.Import) continue;
|
|
316
|
+
const finalName = record.finalNames.get(symbol.id);
|
|
317
|
+
if (finalName) renameSymbol(symbol, finalName);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const sourceFile: SourceFileNode = {
|
|
322
|
+
kind: SyntaxKind.SourceFile,
|
|
323
|
+
statements: merged,
|
|
324
|
+
fileName: first.path,
|
|
325
|
+
text: records.map((record) => record.text).join("\n"),
|
|
326
|
+
start: 0,
|
|
327
|
+
end: 0,
|
|
328
|
+
};
|
|
329
|
+
return { sourceFile, text: sourceFile.text, moduleCount: records.length };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function exportedNames(statement: Statement): string[] {
|
|
333
|
+
const names: string[] = [];
|
|
334
|
+
if (statement.kind === SyntaxKind.VariableStatement) {
|
|
335
|
+
for (const declaration of (statement as VariableStatement).declarationList.declarations) {
|
|
336
|
+
if (declaration.name) names.push(declaration.name.text);
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
const name = declarationName(statement);
|
|
340
|
+
if (name) names.push(name.text);
|
|
341
|
+
}
|
|
342
|
+
return names;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Turn `export default expr` into a synthetic `const` declaration. */
|
|
346
|
+
function exportAssignmentToStatement(record: ModuleRecord, assignment: ExportAssignment): Statement {
|
|
347
|
+
const name: Identifier = {
|
|
348
|
+
kind: SyntaxKind.Identifier,
|
|
349
|
+
text: `${record.prefix}default`,
|
|
350
|
+
start: assignment.start,
|
|
351
|
+
end: assignment.start,
|
|
352
|
+
};
|
|
353
|
+
record.exports.set("default", name.text);
|
|
354
|
+
const declaration: VariableDeclaration = {
|
|
355
|
+
kind: SyntaxKind.VariableDeclaration,
|
|
356
|
+
name,
|
|
357
|
+
exclamation: false,
|
|
358
|
+
initializer: assignment.expression,
|
|
359
|
+
start: assignment.start,
|
|
360
|
+
end: assignment.end,
|
|
361
|
+
};
|
|
362
|
+
const list: VariableDeclarationList = {
|
|
363
|
+
kind: SyntaxKind.VariableDeclarationList,
|
|
364
|
+
declarationKind: "const" as const,
|
|
365
|
+
declarations: [declaration],
|
|
366
|
+
start: assignment.start,
|
|
367
|
+
end: assignment.end,
|
|
368
|
+
};
|
|
369
|
+
const statement: VariableStatement = {
|
|
370
|
+
kind: SyntaxKind.VariableStatement,
|
|
371
|
+
declarationList: list,
|
|
372
|
+
modifiers: [],
|
|
373
|
+
start: assignment.start,
|
|
374
|
+
end: assignment.end,
|
|
375
|
+
};
|
|
376
|
+
return statement;
|
|
377
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character classification used by the scanner.
|
|
3
|
+
*
|
|
4
|
+
* Keeping the code-point constants and the `is*` predicates in their own module
|
|
5
|
+
* keeps the scanner focused on tokenization and makes the low-level rules easy
|
|
6
|
+
* to test in isolation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const enum Char {
|
|
10
|
+
Null = 0,
|
|
11
|
+
Tab = 9,
|
|
12
|
+
LineFeed = 10,
|
|
13
|
+
VerticalTab = 11,
|
|
14
|
+
FormFeed = 12,
|
|
15
|
+
CarriageReturn = 13,
|
|
16
|
+
Space = 32,
|
|
17
|
+
Exclamation = 33,
|
|
18
|
+
DoubleQuote = 34,
|
|
19
|
+
Hash = 35,
|
|
20
|
+
Dollar = 36,
|
|
21
|
+
Percent = 37,
|
|
22
|
+
Ampersand = 38,
|
|
23
|
+
SingleQuote = 39,
|
|
24
|
+
OpenParen = 40,
|
|
25
|
+
CloseParen = 41,
|
|
26
|
+
Asterisk = 42,
|
|
27
|
+
Plus = 43,
|
|
28
|
+
Comma = 44,
|
|
29
|
+
Minus = 45,
|
|
30
|
+
Dot = 46,
|
|
31
|
+
Slash = 47,
|
|
32
|
+
Zero = 48,
|
|
33
|
+
Nine = 57,
|
|
34
|
+
Colon = 58,
|
|
35
|
+
Semicolon = 59,
|
|
36
|
+
LessThan = 60,
|
|
37
|
+
Equals = 61,
|
|
38
|
+
GreaterThan = 62,
|
|
39
|
+
Question = 63,
|
|
40
|
+
At = 64,
|
|
41
|
+
UpperA = 65,
|
|
42
|
+
UpperB = 66,
|
|
43
|
+
UpperE = 69,
|
|
44
|
+
UpperF = 70,
|
|
45
|
+
UpperO = 79,
|
|
46
|
+
UpperX = 88,
|
|
47
|
+
UpperZ = 90,
|
|
48
|
+
OpenBracket = 91,
|
|
49
|
+
Backslash = 92,
|
|
50
|
+
CloseBracket = 93,
|
|
51
|
+
Caret = 94,
|
|
52
|
+
Underscore = 95,
|
|
53
|
+
Backtick = 96,
|
|
54
|
+
LowerA = 97,
|
|
55
|
+
LowerB = 98,
|
|
56
|
+
LowerE = 101,
|
|
57
|
+
LowerF = 102,
|
|
58
|
+
LowerN = 110,
|
|
59
|
+
LowerO = 111,
|
|
60
|
+
LowerR = 114,
|
|
61
|
+
LowerT = 116,
|
|
62
|
+
LowerU = 117,
|
|
63
|
+
LowerX = 120,
|
|
64
|
+
LowerZ = 122,
|
|
65
|
+
OpenBrace = 123,
|
|
66
|
+
Bar = 124,
|
|
67
|
+
CloseBrace = 125,
|
|
68
|
+
Tilde = 126,
|
|
69
|
+
NonBreakingSpace = 0xa0,
|
|
70
|
+
LineSeparator = 0x2028,
|
|
71
|
+
ParagraphSeparator = 0x2029,
|
|
72
|
+
ByteOrderMark = 0xfeff,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isLineBreak(ch: number): boolean {
|
|
76
|
+
return ch === Char.LineFeed || ch === Char.CarriageReturn || ch === Char.LineSeparator || ch === Char.ParagraphSeparator;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const WHITESPACE_UNICODE = new Set<number>([
|
|
80
|
+
0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a, 0x202f, 0x205f,
|
|
81
|
+
0x3000,
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
export function isWhiteSpace(ch: number): boolean {
|
|
85
|
+
return (
|
|
86
|
+
ch === Char.Space ||
|
|
87
|
+
ch === Char.Tab ||
|
|
88
|
+
ch === Char.VerticalTab ||
|
|
89
|
+
ch === Char.FormFeed ||
|
|
90
|
+
ch === Char.NonBreakingSpace ||
|
|
91
|
+
ch === Char.ByteOrderMark ||
|
|
92
|
+
(ch > 0x1680 && WHITESPACE_UNICODE.has(ch))
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function isDigit(ch: number): boolean {
|
|
97
|
+
return ch >= Char.Zero && ch <= Char.Nine;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function isHexDigit(ch: number): boolean {
|
|
101
|
+
return isDigit(ch) || (ch >= Char.LowerA && ch <= Char.LowerF) || (ch >= Char.UpperA && ch <= Char.UpperF);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function isIdentifierStart(ch: number): boolean {
|
|
105
|
+
return (
|
|
106
|
+
(ch >= Char.LowerA && ch <= Char.LowerZ) ||
|
|
107
|
+
(ch >= Char.UpperA && ch <= Char.UpperZ) ||
|
|
108
|
+
ch === Char.Dollar ||
|
|
109
|
+
ch === Char.Underscore ||
|
|
110
|
+
ch > 0x7f
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function isIdentifierPart(ch: number): boolean {
|
|
115
|
+
return isIdentifierStart(ch) || isDigit(ch) || ch === Char.Backslash || ch > 0x7f;
|
|
116
|
+
}
|