@ball-lang/compiler 1.66.2 → 1.67.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/README.md +4 -0
- package/dist/compiler.d.ts +68 -0
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +224 -140
- package/dist/compiler.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler.ts +261 -156
- package/src/index.ts +2 -2
package/README.md
CHANGED
|
@@ -58,6 +58,10 @@ console.log(tsSource); // emitted TypeScript
|
|
|
58
58
|
## API
|
|
59
59
|
|
|
60
60
|
- `compile(program: Program, options?: CompileOptions): string` — compile a whole program to TypeScript source.
|
|
61
|
+
- `compileLibrary(program: Program, options?: CompileLibraryOptions): string` —
|
|
62
|
+
compile a whole Program as a **library**: no assumed entry point, no
|
|
63
|
+
synthesized invocation, every top-level declaration exported. The analog of
|
|
64
|
+
`compile_library`/`CompileLibrary` in the Rust/Go/Python compilers.
|
|
61
65
|
- `compileModule(module: Module, options?: CompileModuleOptions): string` — compile a single module.
|
|
62
66
|
- `BallCompiler` — the underlying class, if you need finer control.
|
|
63
67
|
- `TS_RUNTIME_PREAMBLE` — the Dart-flavored runtime polyfill preamble injected at the top of compiled output.
|
package/dist/compiler.d.ts
CHANGED
|
@@ -5,6 +5,13 @@ export interface CompileOptions {
|
|
|
5
5
|
/** Output file path hint (affects ts-morph's internal resolution). */
|
|
6
6
|
fileName?: string;
|
|
7
7
|
}
|
|
8
|
+
/** Options for {@link BallCompiler.compileLibrary}. */
|
|
9
|
+
export interface CompileLibraryOptions {
|
|
10
|
+
/** Include the runtime preamble at the top of the output. Default true. */
|
|
11
|
+
includePreamble?: boolean;
|
|
12
|
+
/** Output file path hint (affects ts-morph's internal resolution). */
|
|
13
|
+
fileName?: string;
|
|
14
|
+
}
|
|
8
15
|
export declare class BallCompiler {
|
|
9
16
|
private readonly program;
|
|
10
17
|
/** Buffer that _emit* functions write into. */
|
|
@@ -86,6 +93,59 @@ export declare class BallCompiler {
|
|
|
86
93
|
constructor(program: Program);
|
|
87
94
|
/** Compile to TS source. */
|
|
88
95
|
compile(options?: CompileOptions): string;
|
|
96
|
+
/**
|
|
97
|
+
* Compile the whole Program to a TypeScript LIBRARY — no assumed entry
|
|
98
|
+
* point, no synthesized invocation, every top-level declaration exported.
|
|
99
|
+
*
|
|
100
|
+
* The TS sibling of `rust/compiler`'s `compile_library`, `go/compiler`'s
|
|
101
|
+
* `CompileLibrary`, `python/compiler`'s `compile_library`, C#'s
|
|
102
|
+
* entry-optional `Compile` and Dart's `DartCompiler.compileModule`. Use it
|
|
103
|
+
* for code that was never written to be run as a program — the third-party
|
|
104
|
+
* library files the coverage study measures (issue #536), or any Ball
|
|
105
|
+
* Program consumed as a module.
|
|
106
|
+
*
|
|
107
|
+
* Three things it deliberately does differently from `compile()`:
|
|
108
|
+
*
|
|
109
|
+
* 1. It NEVER looks up `program.entryFunction`. `@ball-lang/encoder`
|
|
110
|
+
* defaults that field to `"main"` for every file it encodes, so
|
|
111
|
+
* `compile()` appends a zero-arg `main();` to any library that merely
|
|
112
|
+
* happens to declare `main(argv: string[])` — a wrong-arity call that
|
|
113
|
+
* throws at run time. Here such a declaration is an ordinary function.
|
|
114
|
+
* 2. It does not require an entry module to exist. "No entry" is the normal
|
|
115
|
+
* case for a library, not an error, so every non-base module present is
|
|
116
|
+
* compiled.
|
|
117
|
+
* 3. It exports STRUCTURALLY, through ts-morph's `isExported`, never by
|
|
118
|
+
* rewriting the formatted output text afterwards the way `compileModule`
|
|
119
|
+
* does. A regex matched against emitted source rots silently the moment
|
|
120
|
+
* the emitter's formatting shifts (see the #489/#499 note further down
|
|
121
|
+
* this file for a pass that did exactly that).
|
|
122
|
+
*
|
|
123
|
+
* It also emits none of `compile()`'s engine-specific post-processing — not
|
|
124
|
+
* the `__isUnknownFnError` helper, not the `_evalCall`/`_Scope` patches.
|
|
125
|
+
* Those exist to repair the ONE self-hosted engine `compile()` produces; a
|
|
126
|
+
* library compile of somebody else's code must not gain declarations its
|
|
127
|
+
* source never had, or a round-trip can never reach a fixpoint.
|
|
128
|
+
*/
|
|
129
|
+
compileLibrary(options?: CompileLibraryOptions): string;
|
|
130
|
+
/**
|
|
131
|
+
* Emit every declaration of the program's non-base modules into `sf`.
|
|
132
|
+
*
|
|
133
|
+
* Shared verbatim by `compile()` (whole-program, `library = false`) and
|
|
134
|
+
* `compileLibrary()` (`library = true`). The two differ in exactly two
|
|
135
|
+
* places, both threaded through `library`:
|
|
136
|
+
*
|
|
137
|
+
* - the entry function is skipped here in program mode (`compile()`
|
|
138
|
+
* emits it last, renamed to `main`, followed by its invocation) but is
|
|
139
|
+
* an ordinary declaration in library mode;
|
|
140
|
+
* - top-level functions and variables carry `export` in library mode.
|
|
141
|
+
* Classes, enum classes and type aliases are already exported in both.
|
|
142
|
+
*
|
|
143
|
+
* Everything else — module collection, the linear-memory preamble, the
|
|
144
|
+
* name/typeDef lookup tables, class/mixin grouping, typedefs, enums,
|
|
145
|
+
* top-level variables and free functions — is ONE implementation, so a
|
|
146
|
+
* fix to either caller lands in both.
|
|
147
|
+
*/
|
|
148
|
+
private emitDeclarations;
|
|
89
149
|
private emitFreeFunction;
|
|
90
150
|
/** Wrap captured statements in an IIFE, choosing the right kind:
|
|
91
151
|
* - yield present → `yield* (function* () { ... })()`
|
|
@@ -295,6 +355,14 @@ export declare class BallCompiler {
|
|
|
295
355
|
}
|
|
296
356
|
/** Convenience: compile a Program directly. */
|
|
297
357
|
export declare function compile(program: Program, options?: CompileOptions): string;
|
|
358
|
+
/**
|
|
359
|
+
* Convenience: compile a Program as a LIBRARY — no assumed entry point, no
|
|
360
|
+
* synthesized invocation, every top-level declaration exported.
|
|
361
|
+
*
|
|
362
|
+
* See {@link BallCompiler.compileLibrary} for why this is a separate primitive
|
|
363
|
+
* rather than a flag on `compile()`.
|
|
364
|
+
*/
|
|
365
|
+
export declare function compileLibrary(program: Program, options?: CompileLibraryOptions): string;
|
|
298
366
|
export interface CompileModuleOptions {
|
|
299
367
|
/** Include the runtime preamble at the top of the output. Default true. */
|
|
300
368
|
includePreamble?: boolean;
|
package/dist/compiler.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAMV,MAAM,EACN,OAAO,EAIR,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAQD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC,+CAA+C;IAC/C,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,KAAK,CAAK;IAElB,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAE/C,sEAAsE;IACtE,OAAO,CAAC,YAAY,CAA0B;IAE9C,mEAAmE;IACnE,OAAO,CAAC,kBAAkB,CAA0B;IAEpD,mEAAmE;IACnE,OAAO,CAAC,mBAAmB,CAA0B;IAErD,0EAA0E;IAC1E,OAAO,CAAC,uBAAuB,CAA0B;IAEzD;0EACsE;IACtE,OAAO,CAAC,uBAAuB,CAA0B;IAEzD;;;;OAIG;IACH,OAAO,CAAC,uBAAuB,CAA0B;IAEzD,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB,CAAqB;IAE7C,8EAA8E;IAC9E,OAAO,CAAC,sBAAsB,CAA0B;IAExD,yEAAyE;IACzE,OAAO,CAAC,gBAAgB,CAA0B;IAElD,4DAA4D;IAC5D,OAAO,CAAC,aAAa,CAA0C;IAE/D;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAA0B;IAEnD;;;;;OAKG;IACH,OAAO,CAAC,WAAW,CAA6B;IAEhD;;;;;;;;;;OAUG;IACH,OAAO,CAAC,gBAAgB,CAAgB;IAExC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB,CAKhB;IAER;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAK;IAErB,6EAA6E;IAC7E,OAAO,CAAC,SAAS,CAAK;gBAEV,OAAO,EAAE,OAAO;IAI5B,4BAA4B;IAC5B,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM;
|
|
1
|
+
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"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"}
|
package/dist/compiler.js
CHANGED
|
@@ -107,145 +107,7 @@ export class BallCompiler {
|
|
|
107
107
|
if (!entryMod) {
|
|
108
108
|
throw new Error(`Entry module "${this.program.entryModule}" not found`);
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
const userModules = [];
|
|
112
|
-
let usesStdMemory = false;
|
|
113
|
-
for (const mod of this.program.modules ?? []) {
|
|
114
|
-
const fns = mod.functions ?? [];
|
|
115
|
-
const allBase = fns.length > 0 && fns.every((f) => f.isBase);
|
|
116
|
-
if (allBase) {
|
|
117
|
-
if (mod.name === "std_memory")
|
|
118
|
-
usesStdMemory = true;
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
userModules.push(mod);
|
|
122
|
-
}
|
|
123
|
-
// ── Linear memory runtime preamble ──
|
|
124
|
-
// If the program imports `std_memory` (linear memory simulation), inject
|
|
125
|
-
// the runtime variables backing the `ByteData`/`Endian` shims already
|
|
126
|
-
// defined in the (always-included) runtime preamble. Mirrors the Dart
|
|
127
|
-
// compiler's conditional injection (dart/compiler/lib/compiler.dart,
|
|
128
|
-
// "Linear memory runtime preamble") — only emitted when actually used.
|
|
129
|
-
if (usesStdMemory) {
|
|
130
|
-
sf.addStatements("// Ball linear memory runtime\n" +
|
|
131
|
-
"const _ballMemory = new ByteData(65536);\n" +
|
|
132
|
-
"let _ballHeapPtr = 0;\n" +
|
|
133
|
-
"const _ballStackFrames: number[] = [];\n" +
|
|
134
|
-
"let _ballStackPtr = 65536;\n");
|
|
135
|
-
}
|
|
136
|
-
// Seed the function-name + typeDef lookup tables from ALL user modules.
|
|
137
|
-
this.allFunctionNames = new Set(userModules.flatMap((m) => (m.functions ?? []).map((f) => f.name)));
|
|
138
|
-
this.asyncFnNames = new Set(userModules.flatMap((m) => (m.functions ?? [])
|
|
139
|
-
.filter((f) => f.metadata?.["is_async"] === true)
|
|
140
|
-
.map((f) => f.name)));
|
|
141
|
-
this.typeDefByName = new Map(userModules.flatMap((m) => (m.typeDefs ?? []).map((td) => [td.name, td])));
|
|
142
|
-
// Group functions by their enclosing class (if any) — matches the
|
|
143
|
-
// `<typeDef.name>.<member>` naming convention from the encoder.
|
|
144
|
-
const classMembers = new Map();
|
|
145
|
-
const freeFunctions = [];
|
|
146
|
-
for (const mod of userModules) {
|
|
147
|
-
for (const fn of mod.functions ?? []) {
|
|
148
|
-
if (fn.isBase)
|
|
149
|
-
continue;
|
|
150
|
-
if (fn.name === this.program.entryFunction)
|
|
151
|
-
continue;
|
|
152
|
-
const enclosing = this.enclosingTypeName(fn.name);
|
|
153
|
-
if (enclosing) {
|
|
154
|
-
const list = classMembers.get(enclosing) ?? [];
|
|
155
|
-
list.push(fn);
|
|
156
|
-
classMembers.set(enclosing, list);
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
freeFunctions.push(fn);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// Typedefs → TsTypeAlias (from all user modules).
|
|
164
|
-
for (const mod of userModules) {
|
|
165
|
-
for (const ta of mod.typeAliases ?? []) {
|
|
166
|
-
sf.addTypeAlias({
|
|
167
|
-
name: ta.name,
|
|
168
|
-
type: this.dartTypeToTs(ta.targetType),
|
|
169
|
-
isExported: true,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// Classes.
|
|
174
|
-
// BallObject / BallMap / BallList are runtime container types supplied by
|
|
175
|
-
// the preamble (a Ball instance is a plain-object-like BallObject, a map a
|
|
176
|
-
// plain object, a list a plain array). Skip emitting their class bodies —
|
|
177
|
-
// the encoder's versions reference the inherited `entries` field via bare
|
|
178
|
-
// identifiers and take named ctor args the emitter can't reproduce; the
|
|
179
|
-
// hand-written preamble versions are the source of truth.
|
|
180
|
-
const _runtimeContainerTypes = new Set(["BallObject", "BallMap", "BallList"]);
|
|
181
|
-
const allTypeDefs = userModules.flatMap((m) => m.typeDefs ?? []);
|
|
182
|
-
for (const td of allTypeDefs) {
|
|
183
|
-
if (_runtimeContainerTypes.has(classTsName(td.name)))
|
|
184
|
-
continue;
|
|
185
|
-
// Collect members for this class, including mixin members.
|
|
186
|
-
let members = [...(classMembers.get(td.name) ?? [])];
|
|
187
|
-
const tdMeta = td.metadata ?? {};
|
|
188
|
-
const mixins = Array.isArray(tdMeta["mixins"]) ? tdMeta["mixins"] : [];
|
|
189
|
-
if (mixins.length > 0) {
|
|
190
|
-
// Collect the set of method short names already defined on this class
|
|
191
|
-
const ownShortNames = new Set(members.map((m) => memberShortName(m.name)));
|
|
192
|
-
for (const mixinName of mixins) {
|
|
193
|
-
// Find the mixin typeDef name — try both plain and module-qualified
|
|
194
|
-
let mixinTdName;
|
|
195
|
-
for (const [tdName] of this.typeDefByName) {
|
|
196
|
-
if (classTsName(tdName) === mixinName || tdName === mixinName || tdName.endsWith(":" + mixinName)) {
|
|
197
|
-
mixinTdName = tdName;
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (!mixinTdName)
|
|
202
|
-
continue;
|
|
203
|
-
const mixinMembers = classMembers.get(mixinTdName) ?? [];
|
|
204
|
-
for (const mm of mixinMembers) {
|
|
205
|
-
const shortName = memberShortName(mm.name);
|
|
206
|
-
// Only include mixin methods not already defined on this class
|
|
207
|
-
if (!ownShortNames.has(shortName)) {
|
|
208
|
-
members.push(mm);
|
|
209
|
-
ownShortNames.add(shortName);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
this.emitClass(sf, td, members);
|
|
215
|
-
}
|
|
216
|
-
// Enums declared in Module.enums[] (google.protobuf.EnumDescriptorProto,
|
|
217
|
-
// proto3 JSON: `{name, value: [{name, number}]}`). Both encoders emit
|
|
218
|
-
// enum declarations here (the Dart encoder with module-qualified names
|
|
219
|
-
// like "main:Color"); dropping them left `Color.red` references dangling
|
|
220
|
-
// in the compiled output (#120).
|
|
221
|
-
const emittedTypeNames = new Set(allTypeDefs.map((td) => classTsName(td.name)));
|
|
222
|
-
for (const mod of userModules) {
|
|
223
|
-
for (const en of mod.enums ?? []) {
|
|
224
|
-
const tsName = classTsName(en.name);
|
|
225
|
-
// A typeDef of the same name already produced a class declaration.
|
|
226
|
-
if (emittedTypeNames.has(tsName))
|
|
227
|
-
continue;
|
|
228
|
-
emittedTypeNames.add(tsName);
|
|
229
|
-
const entries = (en.value ?? []).map((v, i) => ({
|
|
230
|
-
name: v.name,
|
|
231
|
-
index: typeof v.number === "number" ? v.number : i,
|
|
232
|
-
}));
|
|
233
|
-
this.emitEnumClass(sf, tsName, entries);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
// Top-level variables (kind == 'top_level_variable') emit as
|
|
237
|
-
// `const <name> = <body>;` before free functions.
|
|
238
|
-
for (const fn of freeFunctions.filter((f) => f.metadata?.kind === "top_level_variable")) {
|
|
239
|
-
const name = sanitize(fn.name);
|
|
240
|
-
const body = fn.body ? this.captureInto(() => {
|
|
241
|
-
this.writeln(`return ${this.expr(fn.body)};`);
|
|
242
|
-
}) : "undefined";
|
|
243
|
-
sf.addStatements(`let ${name} = (() => { ${body} })();`);
|
|
244
|
-
}
|
|
245
|
-
// Free top-level functions (exclude top-level variables).
|
|
246
|
-
for (const fn of freeFunctions.filter((f) => f.metadata?.kind !== "top_level_variable")) {
|
|
247
|
-
this.emitFreeFunction(sf, fn);
|
|
248
|
-
}
|
|
110
|
+
this.emitDeclarations(sf, false);
|
|
249
111
|
// Entry function as `main()` + immediate call.
|
|
250
112
|
const entryFn = entryMod.functions.find((f) => f.name === this.program.entryFunction);
|
|
251
113
|
if (entryFn) {
|
|
@@ -1956,8 +1818,218 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
1956
1818
|
body = unknownFnHelper + body;
|
|
1957
1819
|
return includePreamble ? TS_RUNTIME_PREAMBLE + "\n" + body : body;
|
|
1958
1820
|
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Compile the whole Program to a TypeScript LIBRARY — no assumed entry
|
|
1823
|
+
* point, no synthesized invocation, every top-level declaration exported.
|
|
1824
|
+
*
|
|
1825
|
+
* The TS sibling of `rust/compiler`'s `compile_library`, `go/compiler`'s
|
|
1826
|
+
* `CompileLibrary`, `python/compiler`'s `compile_library`, C#'s
|
|
1827
|
+
* entry-optional `Compile` and Dart's `DartCompiler.compileModule`. Use it
|
|
1828
|
+
* for code that was never written to be run as a program — the third-party
|
|
1829
|
+
* library files the coverage study measures (issue #536), or any Ball
|
|
1830
|
+
* Program consumed as a module.
|
|
1831
|
+
*
|
|
1832
|
+
* Three things it deliberately does differently from `compile()`:
|
|
1833
|
+
*
|
|
1834
|
+
* 1. It NEVER looks up `program.entryFunction`. `@ball-lang/encoder`
|
|
1835
|
+
* defaults that field to `"main"` for every file it encodes, so
|
|
1836
|
+
* `compile()` appends a zero-arg `main();` to any library that merely
|
|
1837
|
+
* happens to declare `main(argv: string[])` — a wrong-arity call that
|
|
1838
|
+
* throws at run time. Here such a declaration is an ordinary function.
|
|
1839
|
+
* 2. It does not require an entry module to exist. "No entry" is the normal
|
|
1840
|
+
* case for a library, not an error, so every non-base module present is
|
|
1841
|
+
* compiled.
|
|
1842
|
+
* 3. It exports STRUCTURALLY, through ts-morph's `isExported`, never by
|
|
1843
|
+
* rewriting the formatted output text afterwards the way `compileModule`
|
|
1844
|
+
* does. A regex matched against emitted source rots silently the moment
|
|
1845
|
+
* the emitter's formatting shifts (see the #489/#499 note further down
|
|
1846
|
+
* this file for a pass that did exactly that).
|
|
1847
|
+
*
|
|
1848
|
+
* It also emits none of `compile()`'s engine-specific post-processing — not
|
|
1849
|
+
* the `__isUnknownFnError` helper, not the `_evalCall`/`_Scope` patches.
|
|
1850
|
+
* Those exist to repair the ONE self-hosted engine `compile()` produces; a
|
|
1851
|
+
* library compile of somebody else's code must not gain declarations its
|
|
1852
|
+
* source never had, or a round-trip can never reach a fixpoint.
|
|
1853
|
+
*/
|
|
1854
|
+
compileLibrary(options = {}) {
|
|
1855
|
+
const { includePreamble = true, fileName = "library.ts" } = options;
|
|
1856
|
+
const project = new Project({
|
|
1857
|
+
useInMemoryFileSystem: true,
|
|
1858
|
+
compilerOptions: { target: 99 /* ESNext */ },
|
|
1859
|
+
});
|
|
1860
|
+
const sf = project.createSourceFile(fileName, "", { overwrite: true });
|
|
1861
|
+
this.emitDeclarations(sf, true);
|
|
1862
|
+
sf.formatText({ indentSize: 2, convertTabsToSpaces: true });
|
|
1863
|
+
const body = sf.getFullText();
|
|
1864
|
+
return includePreamble ? TS_RUNTIME_PREAMBLE + "\n" + body : body;
|
|
1865
|
+
}
|
|
1959
1866
|
// ───────────────────────── Declarations ────────────────────────────
|
|
1960
|
-
|
|
1867
|
+
/**
|
|
1868
|
+
* Emit every declaration of the program's non-base modules into `sf`.
|
|
1869
|
+
*
|
|
1870
|
+
* Shared verbatim by `compile()` (whole-program, `library = false`) and
|
|
1871
|
+
* `compileLibrary()` (`library = true`). The two differ in exactly two
|
|
1872
|
+
* places, both threaded through `library`:
|
|
1873
|
+
*
|
|
1874
|
+
* - the entry function is skipped here in program mode (`compile()`
|
|
1875
|
+
* emits it last, renamed to `main`, followed by its invocation) but is
|
|
1876
|
+
* an ordinary declaration in library mode;
|
|
1877
|
+
* - top-level functions and variables carry `export` in library mode.
|
|
1878
|
+
* Classes, enum classes and type aliases are already exported in both.
|
|
1879
|
+
*
|
|
1880
|
+
* Everything else — module collection, the linear-memory preamble, the
|
|
1881
|
+
* name/typeDef lookup tables, class/mixin grouping, typedefs, enums,
|
|
1882
|
+
* top-level variables and free functions — is ONE implementation, so a
|
|
1883
|
+
* fix to either caller lands in both.
|
|
1884
|
+
*/
|
|
1885
|
+
emitDeclarations(sf, library) {
|
|
1886
|
+
// Collect ALL non-base modules (entry + user library modules).
|
|
1887
|
+
const userModules = [];
|
|
1888
|
+
let usesStdMemory = false;
|
|
1889
|
+
for (const mod of this.program.modules ?? []) {
|
|
1890
|
+
const fns = mod.functions ?? [];
|
|
1891
|
+
const allBase = fns.length > 0 && fns.every((f) => f.isBase);
|
|
1892
|
+
if (allBase) {
|
|
1893
|
+
if (mod.name === "std_memory")
|
|
1894
|
+
usesStdMemory = true;
|
|
1895
|
+
continue;
|
|
1896
|
+
}
|
|
1897
|
+
userModules.push(mod);
|
|
1898
|
+
}
|
|
1899
|
+
// ── Linear memory runtime preamble ──
|
|
1900
|
+
// If the program imports `std_memory` (linear memory simulation), inject
|
|
1901
|
+
// the runtime variables backing the `ByteData`/`Endian` shims already
|
|
1902
|
+
// defined in the (always-included) runtime preamble. Mirrors the Dart
|
|
1903
|
+
// compiler's conditional injection (dart/compiler/lib/compiler.dart,
|
|
1904
|
+
// "Linear memory runtime preamble") — only emitted when actually used.
|
|
1905
|
+
if (usesStdMemory) {
|
|
1906
|
+
sf.addStatements("// Ball linear memory runtime\n" +
|
|
1907
|
+
"const _ballMemory = new ByteData(65536);\n" +
|
|
1908
|
+
"let _ballHeapPtr = 0;\n" +
|
|
1909
|
+
"const _ballStackFrames: number[] = [];\n" +
|
|
1910
|
+
"let _ballStackPtr = 65536;\n");
|
|
1911
|
+
}
|
|
1912
|
+
// Seed the function-name + typeDef lookup tables from ALL user modules.
|
|
1913
|
+
this.allFunctionNames = new Set(userModules.flatMap((m) => (m.functions ?? []).map((f) => f.name)));
|
|
1914
|
+
this.asyncFnNames = new Set(userModules.flatMap((m) => (m.functions ?? [])
|
|
1915
|
+
.filter((f) => f.metadata?.["is_async"] === true)
|
|
1916
|
+
.map((f) => f.name)));
|
|
1917
|
+
this.typeDefByName = new Map(userModules.flatMap((m) => (m.typeDefs ?? []).map((td) => [td.name, td])));
|
|
1918
|
+
// Group functions by their enclosing class (if any) — matches the
|
|
1919
|
+
// `<typeDef.name>.<member>` naming convention from the encoder.
|
|
1920
|
+
const classMembers = new Map();
|
|
1921
|
+
const freeFunctions = [];
|
|
1922
|
+
for (const mod of userModules) {
|
|
1923
|
+
for (const fn of mod.functions ?? []) {
|
|
1924
|
+
if (fn.isBase)
|
|
1925
|
+
continue;
|
|
1926
|
+
// In library mode the entry function is NOT special: a top-level
|
|
1927
|
+
// declaration that merely SHARES program.entryFunction's name (the
|
|
1928
|
+
// ts/encoder default is literally "main") compiles as an ordinary
|
|
1929
|
+
// function, and nothing invokes it.
|
|
1930
|
+
if (!library && fn.name === this.program.entryFunction)
|
|
1931
|
+
continue;
|
|
1932
|
+
const enclosing = this.enclosingTypeName(fn.name);
|
|
1933
|
+
if (enclosing) {
|
|
1934
|
+
const list = classMembers.get(enclosing) ?? [];
|
|
1935
|
+
list.push(fn);
|
|
1936
|
+
classMembers.set(enclosing, list);
|
|
1937
|
+
}
|
|
1938
|
+
else {
|
|
1939
|
+
freeFunctions.push(fn);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
// Typedefs → TsTypeAlias (from all user modules).
|
|
1944
|
+
for (const mod of userModules) {
|
|
1945
|
+
for (const ta of mod.typeAliases ?? []) {
|
|
1946
|
+
sf.addTypeAlias({
|
|
1947
|
+
name: ta.name,
|
|
1948
|
+
type: this.dartTypeToTs(ta.targetType),
|
|
1949
|
+
isExported: true,
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
// Classes.
|
|
1954
|
+
// BallObject / BallMap / BallList are runtime container types supplied by
|
|
1955
|
+
// the preamble (a Ball instance is a plain-object-like BallObject, a map a
|
|
1956
|
+
// plain object, a list a plain array). Skip emitting their class bodies —
|
|
1957
|
+
// the encoder's versions reference the inherited `entries` field via bare
|
|
1958
|
+
// identifiers and take named ctor args the emitter can't reproduce; the
|
|
1959
|
+
// hand-written preamble versions are the source of truth.
|
|
1960
|
+
const _runtimeContainerTypes = new Set(["BallObject", "BallMap", "BallList"]);
|
|
1961
|
+
const allTypeDefs = userModules.flatMap((m) => m.typeDefs ?? []);
|
|
1962
|
+
for (const td of allTypeDefs) {
|
|
1963
|
+
if (_runtimeContainerTypes.has(classTsName(td.name)))
|
|
1964
|
+
continue;
|
|
1965
|
+
// Collect members for this class, including mixin members.
|
|
1966
|
+
let members = [...(classMembers.get(td.name) ?? [])];
|
|
1967
|
+
const tdMeta = td.metadata ?? {};
|
|
1968
|
+
const mixins = Array.isArray(tdMeta["mixins"]) ? tdMeta["mixins"] : [];
|
|
1969
|
+
if (mixins.length > 0) {
|
|
1970
|
+
// Collect the set of method short names already defined on this class
|
|
1971
|
+
const ownShortNames = new Set(members.map((m) => memberShortName(m.name)));
|
|
1972
|
+
for (const mixinName of mixins) {
|
|
1973
|
+
// Find the mixin typeDef name — try both plain and module-qualified
|
|
1974
|
+
let mixinTdName;
|
|
1975
|
+
for (const [tdName] of this.typeDefByName) {
|
|
1976
|
+
if (classTsName(tdName) === mixinName || tdName === mixinName || tdName.endsWith(":" + mixinName)) {
|
|
1977
|
+
mixinTdName = tdName;
|
|
1978
|
+
break;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
if (!mixinTdName)
|
|
1982
|
+
continue;
|
|
1983
|
+
const mixinMembers = classMembers.get(mixinTdName) ?? [];
|
|
1984
|
+
for (const mm of mixinMembers) {
|
|
1985
|
+
const shortName = memberShortName(mm.name);
|
|
1986
|
+
// Only include mixin methods not already defined on this class
|
|
1987
|
+
if (!ownShortNames.has(shortName)) {
|
|
1988
|
+
members.push(mm);
|
|
1989
|
+
ownShortNames.add(shortName);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
this.emitClass(sf, td, members);
|
|
1995
|
+
}
|
|
1996
|
+
// Enums declared in Module.enums[] (google.protobuf.EnumDescriptorProto,
|
|
1997
|
+
// proto3 JSON: `{name, value: [{name, number}]}`). Both encoders emit
|
|
1998
|
+
// enum declarations here (the Dart encoder with module-qualified names
|
|
1999
|
+
// like "main:Color"); dropping them left `Color.red` references dangling
|
|
2000
|
+
// in the compiled output (#120).
|
|
2001
|
+
const emittedTypeNames = new Set(allTypeDefs.map((td) => classTsName(td.name)));
|
|
2002
|
+
for (const mod of userModules) {
|
|
2003
|
+
for (const en of mod.enums ?? []) {
|
|
2004
|
+
const tsName = classTsName(en.name);
|
|
2005
|
+
// A typeDef of the same name already produced a class declaration.
|
|
2006
|
+
if (emittedTypeNames.has(tsName))
|
|
2007
|
+
continue;
|
|
2008
|
+
emittedTypeNames.add(tsName);
|
|
2009
|
+
const entries = (en.value ?? []).map((v, i) => ({
|
|
2010
|
+
name: v.name,
|
|
2011
|
+
index: typeof v.number === "number" ? v.number : i,
|
|
2012
|
+
}));
|
|
2013
|
+
this.emitEnumClass(sf, tsName, entries);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
// Top-level variables (kind == 'top_level_variable') emit as
|
|
2017
|
+
// `const <name> = <body>;` before free functions.
|
|
2018
|
+
for (const fn of freeFunctions.filter((f) => f.metadata?.kind === "top_level_variable")) {
|
|
2019
|
+
const name = sanitize(fn.name);
|
|
2020
|
+
const body = fn.body ? this.captureInto(() => {
|
|
2021
|
+
this.writeln(`return ${this.expr(fn.body)};`);
|
|
2022
|
+
}) : "undefined";
|
|
2023
|
+
sf.addStatements(`${library ? "export " : ""}let ${name} = (() => { ${body} })();`);
|
|
2024
|
+
}
|
|
2025
|
+
// Free top-level functions (exclude top-level variables).
|
|
2026
|
+
for (const fn of freeFunctions.filter((f) => f.metadata?.kind !== "top_level_variable")) {
|
|
2027
|
+
this.emitFreeFunction(sf, fn, undefined, undefined, library);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
emitFreeFunction(sf, fn, forceName, forceAsync,
|
|
2031
|
+
/** Emit `export function …` (library mode). Default false. */
|
|
2032
|
+
isExported = false) {
|
|
1961
2033
|
const params = extractParams(fn);
|
|
1962
2034
|
const name = forceName ?? sanitize(fn.name);
|
|
1963
2035
|
const needsInputAlias = params.length === 1 && params[0] !== "input";
|
|
@@ -1982,6 +2054,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
1982
2054
|
sf.addFunction({
|
|
1983
2055
|
kind: StructureKind.Function,
|
|
1984
2056
|
name,
|
|
2057
|
+
isExported,
|
|
1985
2058
|
isAsync: false,
|
|
1986
2059
|
isGenerator: false,
|
|
1987
2060
|
parameters: params.map((p) => ({ name: sanitize(p), type: "any" })),
|
|
@@ -1993,6 +2066,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
1993
2066
|
sf.addFunction({
|
|
1994
2067
|
kind: StructureKind.Function,
|
|
1995
2068
|
name,
|
|
2069
|
+
isExported,
|
|
1996
2070
|
isAsync: isAsync && !isGenerator,
|
|
1997
2071
|
isGenerator,
|
|
1998
2072
|
parameters: params.map((p) => ({ name: sanitize(p), type: "any" })),
|
|
@@ -6662,6 +6736,16 @@ function sanitize(name) {
|
|
|
6662
6736
|
export function compile(program, options) {
|
|
6663
6737
|
return new BallCompiler(program).compile(options);
|
|
6664
6738
|
}
|
|
6739
|
+
/**
|
|
6740
|
+
* Convenience: compile a Program as a LIBRARY — no assumed entry point, no
|
|
6741
|
+
* synthesized invocation, every top-level declaration exported.
|
|
6742
|
+
*
|
|
6743
|
+
* See {@link BallCompiler.compileLibrary} for why this is a separate primitive
|
|
6744
|
+
* rather than a flag on `compile()`.
|
|
6745
|
+
*/
|
|
6746
|
+
export function compileLibrary(program, options) {
|
|
6747
|
+
return new BallCompiler(program).compileLibrary(options);
|
|
6748
|
+
}
|
|
6665
6749
|
/**
|
|
6666
6750
|
* Compile a Ball Module (not a Program) to a TypeScript ESM library.
|
|
6667
6751
|
*
|