@ball-lang/compiler 1.66.1 → 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/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { BallCompiler, compile, compileModule } from "./compiler.ts";
|
|
2
|
-
export type { CompileOptions, CompileModuleOptions } from "./compiler.ts";
|
|
1
|
+
export { BallCompiler, compile, compileLibrary, compileModule } from "./compiler.ts";
|
|
2
|
+
export type { CompileOptions, CompileLibraryOptions, CompileModuleOptions } from "./compiler.ts";
|
|
3
3
|
export { TS_RUNTIME_PREAMBLE } from "./preamble.ts";
|
|
4
4
|
export type * from "./types.ts";
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACrF,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,mBAAmB,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAErF,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ball-lang/compiler",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.67.0",
|
|
4
4
|
"description": "Ball → TypeScript compiler. Consumes a Ball protobuf Program and emits idiomatic TypeScript via ts-morph. The canonical TS compiler for Ball — lives in TS land so TS syntax knowledge doesn't leak into other languages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/src/compiler.ts
CHANGED
|
@@ -34,6 +34,14 @@ export interface CompileOptions {
|
|
|
34
34
|
fileName?: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** Options for {@link BallCompiler.compileLibrary}. */
|
|
38
|
+
export interface CompileLibraryOptions {
|
|
39
|
+
/** Include the runtime preamble at the top of the output. Default true. */
|
|
40
|
+
includePreamble?: boolean;
|
|
41
|
+
/** Output file path hint (affects ts-morph's internal resolution). */
|
|
42
|
+
fileName?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
interface CtorParam {
|
|
38
46
|
name: string;
|
|
39
47
|
isThis: boolean;
|
|
@@ -164,162 +172,7 @@ export class BallCompiler {
|
|
|
164
172
|
);
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
|
|
168
|
-
const userModules: Module[] = [];
|
|
169
|
-
let usesStdMemory = false;
|
|
170
|
-
for (const mod of this.program.modules ?? []) {
|
|
171
|
-
const fns = mod.functions ?? [];
|
|
172
|
-
const allBase = fns.length > 0 && fns.every((f: FunctionDef) => f.isBase);
|
|
173
|
-
if (allBase) {
|
|
174
|
-
if (mod.name === "std_memory") usesStdMemory = true;
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
userModules.push(mod);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ── Linear memory runtime preamble ──
|
|
181
|
-
// If the program imports `std_memory` (linear memory simulation), inject
|
|
182
|
-
// the runtime variables backing the `ByteData`/`Endian` shims already
|
|
183
|
-
// defined in the (always-included) runtime preamble. Mirrors the Dart
|
|
184
|
-
// compiler's conditional injection (dart/compiler/lib/compiler.dart,
|
|
185
|
-
// "Linear memory runtime preamble") — only emitted when actually used.
|
|
186
|
-
if (usesStdMemory) {
|
|
187
|
-
sf.addStatements(
|
|
188
|
-
"// Ball linear memory runtime\n" +
|
|
189
|
-
"const _ballMemory = new ByteData(65536);\n" +
|
|
190
|
-
"let _ballHeapPtr = 0;\n" +
|
|
191
|
-
"const _ballStackFrames: number[] = [];\n" +
|
|
192
|
-
"let _ballStackPtr = 65536;\n",
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Seed the function-name + typeDef lookup tables from ALL user modules.
|
|
197
|
-
this.allFunctionNames = new Set(
|
|
198
|
-
userModules.flatMap((m) => (m.functions ?? []).map((f: FunctionDef) => f.name)),
|
|
199
|
-
);
|
|
200
|
-
this.asyncFnNames = new Set(
|
|
201
|
-
userModules.flatMap((m) =>
|
|
202
|
-
(m.functions ?? [])
|
|
203
|
-
.filter((f: FunctionDef) => f.metadata?.["is_async"] === true)
|
|
204
|
-
.map((f: FunctionDef) => f.name),
|
|
205
|
-
),
|
|
206
|
-
);
|
|
207
|
-
this.typeDefByName = new Map(
|
|
208
|
-
userModules.flatMap((m) =>
|
|
209
|
-
(m.typeDefs ?? []).map((td) => [td.name, td] as [string, TypeDefinition]),
|
|
210
|
-
),
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
// Group functions by their enclosing class (if any) — matches the
|
|
214
|
-
// `<typeDef.name>.<member>` naming convention from the encoder.
|
|
215
|
-
const classMembers = new Map<string, FunctionDef[]>();
|
|
216
|
-
const freeFunctions: FunctionDef[] = [];
|
|
217
|
-
for (const mod of userModules) {
|
|
218
|
-
for (const fn of mod.functions ?? []) {
|
|
219
|
-
if (fn.isBase) continue;
|
|
220
|
-
if (fn.name === this.program.entryFunction) continue;
|
|
221
|
-
const enclosing = this.enclosingTypeName(fn.name);
|
|
222
|
-
if (enclosing) {
|
|
223
|
-
const list = classMembers.get(enclosing) ?? [];
|
|
224
|
-
list.push(fn);
|
|
225
|
-
classMembers.set(enclosing, list);
|
|
226
|
-
} else {
|
|
227
|
-
freeFunctions.push(fn);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Typedefs → TsTypeAlias (from all user modules).
|
|
233
|
-
for (const mod of userModules) {
|
|
234
|
-
for (const ta of mod.typeAliases ?? []) {
|
|
235
|
-
sf.addTypeAlias({
|
|
236
|
-
name: ta.name,
|
|
237
|
-
type: this.dartTypeToTs(ta.targetType),
|
|
238
|
-
isExported: true,
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Classes.
|
|
244
|
-
// BallObject / BallMap / BallList are runtime container types supplied by
|
|
245
|
-
// the preamble (a Ball instance is a plain-object-like BallObject, a map a
|
|
246
|
-
// plain object, a list a plain array). Skip emitting their class bodies —
|
|
247
|
-
// the encoder's versions reference the inherited `entries` field via bare
|
|
248
|
-
// identifiers and take named ctor args the emitter can't reproduce; the
|
|
249
|
-
// hand-written preamble versions are the source of truth.
|
|
250
|
-
const _runtimeContainerTypes = new Set(["BallObject", "BallMap", "BallList"]);
|
|
251
|
-
const allTypeDefs = userModules.flatMap((m) => m.typeDefs ?? []);
|
|
252
|
-
for (const td of allTypeDefs) {
|
|
253
|
-
if (_runtimeContainerTypes.has(classTsName(td.name))) continue;
|
|
254
|
-
// Collect members for this class, including mixin members.
|
|
255
|
-
let members = [...(classMembers.get(td.name) ?? [])];
|
|
256
|
-
const tdMeta: Struct = td.metadata ?? {};
|
|
257
|
-
const mixins = Array.isArray(tdMeta["mixins"]) ? tdMeta["mixins"] as string[] : [];
|
|
258
|
-
if (mixins.length > 0) {
|
|
259
|
-
// Collect the set of method short names already defined on this class
|
|
260
|
-
const ownShortNames = new Set(members.map((m) => memberShortName(m.name)));
|
|
261
|
-
for (const mixinName of mixins) {
|
|
262
|
-
// Find the mixin typeDef name — try both plain and module-qualified
|
|
263
|
-
let mixinTdName: string | undefined;
|
|
264
|
-
for (const [tdName] of this.typeDefByName) {
|
|
265
|
-
if (classTsName(tdName) === mixinName || tdName === mixinName || tdName.endsWith(":" + mixinName)) {
|
|
266
|
-
mixinTdName = tdName;
|
|
267
|
-
break;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
if (!mixinTdName) continue;
|
|
271
|
-
const mixinMembers = classMembers.get(mixinTdName) ?? [];
|
|
272
|
-
for (const mm of mixinMembers) {
|
|
273
|
-
const shortName = memberShortName(mm.name);
|
|
274
|
-
// Only include mixin methods not already defined on this class
|
|
275
|
-
if (!ownShortNames.has(shortName)) {
|
|
276
|
-
members.push(mm);
|
|
277
|
-
ownShortNames.add(shortName);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
this.emitClass(sf, td, members);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Enums declared in Module.enums[] (google.protobuf.EnumDescriptorProto,
|
|
286
|
-
// proto3 JSON: `{name, value: [{name, number}]}`). Both encoders emit
|
|
287
|
-
// enum declarations here (the Dart encoder with module-qualified names
|
|
288
|
-
// like "main:Color"); dropping them left `Color.red` references dangling
|
|
289
|
-
// in the compiled output (#120).
|
|
290
|
-
const emittedTypeNames = new Set(allTypeDefs.map((td) => classTsName(td.name)));
|
|
291
|
-
for (const mod of userModules) {
|
|
292
|
-
for (const en of mod.enums ?? []) {
|
|
293
|
-
const tsName = classTsName(en.name);
|
|
294
|
-
// A typeDef of the same name already produced a class declaration.
|
|
295
|
-
if (emittedTypeNames.has(tsName)) continue;
|
|
296
|
-
emittedTypeNames.add(tsName);
|
|
297
|
-
const entries = (en.value ?? []).map((v, i) => ({
|
|
298
|
-
name: v.name,
|
|
299
|
-
index: typeof v.number === "number" ? v.number : i,
|
|
300
|
-
}));
|
|
301
|
-
this.emitEnumClass(sf, tsName, entries);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Top-level variables (kind == 'top_level_variable') emit as
|
|
306
|
-
// `const <name> = <body>;` before free functions.
|
|
307
|
-
for (const fn of freeFunctions.filter(
|
|
308
|
-
(f) => (f.metadata as any)?.kind === "top_level_variable",
|
|
309
|
-
)) {
|
|
310
|
-
const name = sanitize(fn.name);
|
|
311
|
-
const body = fn.body ? this.captureInto(() => {
|
|
312
|
-
this.writeln(`return ${this.expr(fn.body!)};`);
|
|
313
|
-
}) : "undefined";
|
|
314
|
-
sf.addStatements(`let ${name} = (() => { ${body} })();`);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Free top-level functions (exclude top-level variables).
|
|
318
|
-
for (const fn of freeFunctions.filter(
|
|
319
|
-
(f) => (f.metadata as any)?.kind !== "top_level_variable",
|
|
320
|
-
)) {
|
|
321
|
-
this.emitFreeFunction(sf, fn);
|
|
322
|
-
}
|
|
175
|
+
this.emitDeclarations(sf, false);
|
|
323
176
|
|
|
324
177
|
// Entry function as `main()` + immediate call.
|
|
325
178
|
const entryFn = entryMod.functions.find(
|
|
@@ -2350,13 +2203,249 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
2350
2203
|
return includePreamble ? TS_RUNTIME_PREAMBLE + "\n" + body : body;
|
|
2351
2204
|
}
|
|
2352
2205
|
|
|
2206
|
+
/**
|
|
2207
|
+
* Compile the whole Program to a TypeScript LIBRARY — no assumed entry
|
|
2208
|
+
* point, no synthesized invocation, every top-level declaration exported.
|
|
2209
|
+
*
|
|
2210
|
+
* The TS sibling of `rust/compiler`'s `compile_library`, `go/compiler`'s
|
|
2211
|
+
* `CompileLibrary`, `python/compiler`'s `compile_library`, C#'s
|
|
2212
|
+
* entry-optional `Compile` and Dart's `DartCompiler.compileModule`. Use it
|
|
2213
|
+
* for code that was never written to be run as a program — the third-party
|
|
2214
|
+
* library files the coverage study measures (issue #536), or any Ball
|
|
2215
|
+
* Program consumed as a module.
|
|
2216
|
+
*
|
|
2217
|
+
* Three things it deliberately does differently from `compile()`:
|
|
2218
|
+
*
|
|
2219
|
+
* 1. It NEVER looks up `program.entryFunction`. `@ball-lang/encoder`
|
|
2220
|
+
* defaults that field to `"main"` for every file it encodes, so
|
|
2221
|
+
* `compile()` appends a zero-arg `main();` to any library that merely
|
|
2222
|
+
* happens to declare `main(argv: string[])` — a wrong-arity call that
|
|
2223
|
+
* throws at run time. Here such a declaration is an ordinary function.
|
|
2224
|
+
* 2. It does not require an entry module to exist. "No entry" is the normal
|
|
2225
|
+
* case for a library, not an error, so every non-base module present is
|
|
2226
|
+
* compiled.
|
|
2227
|
+
* 3. It exports STRUCTURALLY, through ts-morph's `isExported`, never by
|
|
2228
|
+
* rewriting the formatted output text afterwards the way `compileModule`
|
|
2229
|
+
* does. A regex matched against emitted source rots silently the moment
|
|
2230
|
+
* the emitter's formatting shifts (see the #489/#499 note further down
|
|
2231
|
+
* this file for a pass that did exactly that).
|
|
2232
|
+
*
|
|
2233
|
+
* It also emits none of `compile()`'s engine-specific post-processing — not
|
|
2234
|
+
* the `__isUnknownFnError` helper, not the `_evalCall`/`_Scope` patches.
|
|
2235
|
+
* Those exist to repair the ONE self-hosted engine `compile()` produces; a
|
|
2236
|
+
* library compile of somebody else's code must not gain declarations its
|
|
2237
|
+
* source never had, or a round-trip can never reach a fixpoint.
|
|
2238
|
+
*/
|
|
2239
|
+
compileLibrary(options: CompileLibraryOptions = {}): string {
|
|
2240
|
+
const { includePreamble = true, fileName = "library.ts" } = options;
|
|
2241
|
+
const project = new Project({
|
|
2242
|
+
useInMemoryFileSystem: true,
|
|
2243
|
+
compilerOptions: { target: 99 /* ESNext */ },
|
|
2244
|
+
});
|
|
2245
|
+
const sf = project.createSourceFile(fileName, "", { overwrite: true });
|
|
2246
|
+
|
|
2247
|
+
this.emitDeclarations(sf, true);
|
|
2248
|
+
|
|
2249
|
+
sf.formatText({ indentSize: 2, convertTabsToSpaces: true });
|
|
2250
|
+
const body = sf.getFullText();
|
|
2251
|
+
return includePreamble ? TS_RUNTIME_PREAMBLE + "\n" + body : body;
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2353
2254
|
// ───────────────────────── Declarations ────────────────────────────
|
|
2354
2255
|
|
|
2256
|
+
/**
|
|
2257
|
+
* Emit every declaration of the program's non-base modules into `sf`.
|
|
2258
|
+
*
|
|
2259
|
+
* Shared verbatim by `compile()` (whole-program, `library = false`) and
|
|
2260
|
+
* `compileLibrary()` (`library = true`). The two differ in exactly two
|
|
2261
|
+
* places, both threaded through `library`:
|
|
2262
|
+
*
|
|
2263
|
+
* - the entry function is skipped here in program mode (`compile()`
|
|
2264
|
+
* emits it last, renamed to `main`, followed by its invocation) but is
|
|
2265
|
+
* an ordinary declaration in library mode;
|
|
2266
|
+
* - top-level functions and variables carry `export` in library mode.
|
|
2267
|
+
* Classes, enum classes and type aliases are already exported in both.
|
|
2268
|
+
*
|
|
2269
|
+
* Everything else — module collection, the linear-memory preamble, the
|
|
2270
|
+
* name/typeDef lookup tables, class/mixin grouping, typedefs, enums,
|
|
2271
|
+
* top-level variables and free functions — is ONE implementation, so a
|
|
2272
|
+
* fix to either caller lands in both.
|
|
2273
|
+
*/
|
|
2274
|
+
private emitDeclarations(
|
|
2275
|
+
sf: ReturnType<Project["createSourceFile"]>,
|
|
2276
|
+
library: boolean,
|
|
2277
|
+
): void {
|
|
2278
|
+
// Collect ALL non-base modules (entry + user library modules).
|
|
2279
|
+
const userModules: Module[] = [];
|
|
2280
|
+
let usesStdMemory = false;
|
|
2281
|
+
for (const mod of this.program.modules ?? []) {
|
|
2282
|
+
const fns = mod.functions ?? [];
|
|
2283
|
+
const allBase = fns.length > 0 && fns.every((f: FunctionDef) => f.isBase);
|
|
2284
|
+
if (allBase) {
|
|
2285
|
+
if (mod.name === "std_memory") usesStdMemory = true;
|
|
2286
|
+
continue;
|
|
2287
|
+
}
|
|
2288
|
+
userModules.push(mod);
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
// ── Linear memory runtime preamble ──
|
|
2292
|
+
// If the program imports `std_memory` (linear memory simulation), inject
|
|
2293
|
+
// the runtime variables backing the `ByteData`/`Endian` shims already
|
|
2294
|
+
// defined in the (always-included) runtime preamble. Mirrors the Dart
|
|
2295
|
+
// compiler's conditional injection (dart/compiler/lib/compiler.dart,
|
|
2296
|
+
// "Linear memory runtime preamble") — only emitted when actually used.
|
|
2297
|
+
if (usesStdMemory) {
|
|
2298
|
+
sf.addStatements(
|
|
2299
|
+
"// Ball linear memory runtime\n" +
|
|
2300
|
+
"const _ballMemory = new ByteData(65536);\n" +
|
|
2301
|
+
"let _ballHeapPtr = 0;\n" +
|
|
2302
|
+
"const _ballStackFrames: number[] = [];\n" +
|
|
2303
|
+
"let _ballStackPtr = 65536;\n",
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
// Seed the function-name + typeDef lookup tables from ALL user modules.
|
|
2308
|
+
this.allFunctionNames = new Set(
|
|
2309
|
+
userModules.flatMap((m) => (m.functions ?? []).map((f: FunctionDef) => f.name)),
|
|
2310
|
+
);
|
|
2311
|
+
this.asyncFnNames = new Set(
|
|
2312
|
+
userModules.flatMap((m) =>
|
|
2313
|
+
(m.functions ?? [])
|
|
2314
|
+
.filter((f: FunctionDef) => f.metadata?.["is_async"] === true)
|
|
2315
|
+
.map((f: FunctionDef) => f.name),
|
|
2316
|
+
),
|
|
2317
|
+
);
|
|
2318
|
+
this.typeDefByName = new Map(
|
|
2319
|
+
userModules.flatMap((m) =>
|
|
2320
|
+
(m.typeDefs ?? []).map((td) => [td.name, td] as [string, TypeDefinition]),
|
|
2321
|
+
),
|
|
2322
|
+
);
|
|
2323
|
+
|
|
2324
|
+
// Group functions by their enclosing class (if any) — matches the
|
|
2325
|
+
// `<typeDef.name>.<member>` naming convention from the encoder.
|
|
2326
|
+
const classMembers = new Map<string, FunctionDef[]>();
|
|
2327
|
+
const freeFunctions: FunctionDef[] = [];
|
|
2328
|
+
for (const mod of userModules) {
|
|
2329
|
+
for (const fn of mod.functions ?? []) {
|
|
2330
|
+
if (fn.isBase) continue;
|
|
2331
|
+
// In library mode the entry function is NOT special: a top-level
|
|
2332
|
+
// declaration that merely SHARES program.entryFunction's name (the
|
|
2333
|
+
// ts/encoder default is literally "main") compiles as an ordinary
|
|
2334
|
+
// function, and nothing invokes it.
|
|
2335
|
+
if (!library && fn.name === this.program.entryFunction) continue;
|
|
2336
|
+
const enclosing = this.enclosingTypeName(fn.name);
|
|
2337
|
+
if (enclosing) {
|
|
2338
|
+
const list = classMembers.get(enclosing) ?? [];
|
|
2339
|
+
list.push(fn);
|
|
2340
|
+
classMembers.set(enclosing, list);
|
|
2341
|
+
} else {
|
|
2342
|
+
freeFunctions.push(fn);
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// Typedefs → TsTypeAlias (from all user modules).
|
|
2348
|
+
for (const mod of userModules) {
|
|
2349
|
+
for (const ta of mod.typeAliases ?? []) {
|
|
2350
|
+
sf.addTypeAlias({
|
|
2351
|
+
name: ta.name,
|
|
2352
|
+
type: this.dartTypeToTs(ta.targetType),
|
|
2353
|
+
isExported: true,
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// Classes.
|
|
2359
|
+
// BallObject / BallMap / BallList are runtime container types supplied by
|
|
2360
|
+
// the preamble (a Ball instance is a plain-object-like BallObject, a map a
|
|
2361
|
+
// plain object, a list a plain array). Skip emitting their class bodies —
|
|
2362
|
+
// the encoder's versions reference the inherited `entries` field via bare
|
|
2363
|
+
// identifiers and take named ctor args the emitter can't reproduce; the
|
|
2364
|
+
// hand-written preamble versions are the source of truth.
|
|
2365
|
+
const _runtimeContainerTypes = new Set(["BallObject", "BallMap", "BallList"]);
|
|
2366
|
+
const allTypeDefs = userModules.flatMap((m) => m.typeDefs ?? []);
|
|
2367
|
+
for (const td of allTypeDefs) {
|
|
2368
|
+
if (_runtimeContainerTypes.has(classTsName(td.name))) continue;
|
|
2369
|
+
// Collect members for this class, including mixin members.
|
|
2370
|
+
let members = [...(classMembers.get(td.name) ?? [])];
|
|
2371
|
+
const tdMeta: Struct = td.metadata ?? {};
|
|
2372
|
+
const mixins = Array.isArray(tdMeta["mixins"]) ? tdMeta["mixins"] as string[] : [];
|
|
2373
|
+
if (mixins.length > 0) {
|
|
2374
|
+
// Collect the set of method short names already defined on this class
|
|
2375
|
+
const ownShortNames = new Set(members.map((m) => memberShortName(m.name)));
|
|
2376
|
+
for (const mixinName of mixins) {
|
|
2377
|
+
// Find the mixin typeDef name — try both plain and module-qualified
|
|
2378
|
+
let mixinTdName: string | undefined;
|
|
2379
|
+
for (const [tdName] of this.typeDefByName) {
|
|
2380
|
+
if (classTsName(tdName) === mixinName || tdName === mixinName || tdName.endsWith(":" + mixinName)) {
|
|
2381
|
+
mixinTdName = tdName;
|
|
2382
|
+
break;
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
if (!mixinTdName) continue;
|
|
2386
|
+
const mixinMembers = classMembers.get(mixinTdName) ?? [];
|
|
2387
|
+
for (const mm of mixinMembers) {
|
|
2388
|
+
const shortName = memberShortName(mm.name);
|
|
2389
|
+
// Only include mixin methods not already defined on this class
|
|
2390
|
+
if (!ownShortNames.has(shortName)) {
|
|
2391
|
+
members.push(mm);
|
|
2392
|
+
ownShortNames.add(shortName);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
this.emitClass(sf, td, members);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// Enums declared in Module.enums[] (google.protobuf.EnumDescriptorProto,
|
|
2401
|
+
// proto3 JSON: `{name, value: [{name, number}]}`). Both encoders emit
|
|
2402
|
+
// enum declarations here (the Dart encoder with module-qualified names
|
|
2403
|
+
// like "main:Color"); dropping them left `Color.red` references dangling
|
|
2404
|
+
// in the compiled output (#120).
|
|
2405
|
+
const emittedTypeNames = new Set(allTypeDefs.map((td) => classTsName(td.name)));
|
|
2406
|
+
for (const mod of userModules) {
|
|
2407
|
+
for (const en of mod.enums ?? []) {
|
|
2408
|
+
const tsName = classTsName(en.name);
|
|
2409
|
+
// A typeDef of the same name already produced a class declaration.
|
|
2410
|
+
if (emittedTypeNames.has(tsName)) continue;
|
|
2411
|
+
emittedTypeNames.add(tsName);
|
|
2412
|
+
const entries = (en.value ?? []).map((v, i) => ({
|
|
2413
|
+
name: v.name,
|
|
2414
|
+
index: typeof v.number === "number" ? v.number : i,
|
|
2415
|
+
}));
|
|
2416
|
+
this.emitEnumClass(sf, tsName, entries);
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
// Top-level variables (kind == 'top_level_variable') emit as
|
|
2421
|
+
// `const <name> = <body>;` before free functions.
|
|
2422
|
+
for (const fn of freeFunctions.filter(
|
|
2423
|
+
(f) => (f.metadata as any)?.kind === "top_level_variable",
|
|
2424
|
+
)) {
|
|
2425
|
+
const name = sanitize(fn.name);
|
|
2426
|
+
const body = fn.body ? this.captureInto(() => {
|
|
2427
|
+
this.writeln(`return ${this.expr(fn.body!)};`);
|
|
2428
|
+
}) : "undefined";
|
|
2429
|
+
sf.addStatements(
|
|
2430
|
+
`${library ? "export " : ""}let ${name} = (() => { ${body} })();`,
|
|
2431
|
+
);
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// Free top-level functions (exclude top-level variables).
|
|
2435
|
+
for (const fn of freeFunctions.filter(
|
|
2436
|
+
(f) => (f.metadata as any)?.kind !== "top_level_variable",
|
|
2437
|
+
)) {
|
|
2438
|
+
this.emitFreeFunction(sf, fn, undefined, undefined, library);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2355
2442
|
private emitFreeFunction(
|
|
2356
2443
|
sf: ReturnType<Project["createSourceFile"]>,
|
|
2357
2444
|
fn: FunctionDef,
|
|
2358
2445
|
forceName?: string,
|
|
2359
2446
|
forceAsync?: boolean,
|
|
2447
|
+
/** Emit `export function …` (library mode). Default false. */
|
|
2448
|
+
isExported = false,
|
|
2360
2449
|
): void {
|
|
2361
2450
|
const params = extractParams(fn);
|
|
2362
2451
|
const name = forceName ?? sanitize(fn.name);
|
|
@@ -2381,6 +2470,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
2381
2470
|
sf.addFunction({
|
|
2382
2471
|
kind: StructureKind.Function,
|
|
2383
2472
|
name,
|
|
2473
|
+
isExported,
|
|
2384
2474
|
isAsync: false,
|
|
2385
2475
|
isGenerator: false,
|
|
2386
2476
|
parameters: params.map((p) => ({ name: sanitize(p), type: "any" })),
|
|
@@ -2391,6 +2481,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
2391
2481
|
sf.addFunction({
|
|
2392
2482
|
kind: StructureKind.Function,
|
|
2393
2483
|
name,
|
|
2484
|
+
isExported,
|
|
2394
2485
|
isAsync: isAsync && !isGenerator,
|
|
2395
2486
|
isGenerator,
|
|
2396
2487
|
parameters: params.map((p) => ({ name: sanitize(p), type: "any" })),
|
|
@@ -7022,6 +7113,20 @@ export function compile(program: Program, options?: CompileOptions): string {
|
|
|
7022
7113
|
return new BallCompiler(program).compile(options);
|
|
7023
7114
|
}
|
|
7024
7115
|
|
|
7116
|
+
/**
|
|
7117
|
+
* Convenience: compile a Program as a LIBRARY — no assumed entry point, no
|
|
7118
|
+
* synthesized invocation, every top-level declaration exported.
|
|
7119
|
+
*
|
|
7120
|
+
* See {@link BallCompiler.compileLibrary} for why this is a separate primitive
|
|
7121
|
+
* rather than a flag on `compile()`.
|
|
7122
|
+
*/
|
|
7123
|
+
export function compileLibrary(
|
|
7124
|
+
program: Program,
|
|
7125
|
+
options?: CompileLibraryOptions,
|
|
7126
|
+
): string {
|
|
7127
|
+
return new BallCompiler(program).compileLibrary(options);
|
|
7128
|
+
}
|
|
7129
|
+
|
|
7025
7130
|
// ══════════════════════════════════════════════════���═════════════════════════
|
|
7026
7131
|
// Library / Module compilation (no main, exported symbols)
|
|
7027
7132
|
// ══════════════════════════════════════════════��═════════════════════════════
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { BallCompiler, compile, compileModule } from "./compiler.ts";
|
|
2
|
-
export type { CompileOptions, CompileModuleOptions } from "./compiler.ts";
|
|
1
|
+
export { BallCompiler, compile, compileLibrary, compileModule } from "./compiler.ts";
|
|
2
|
+
export type { CompileOptions, CompileLibraryOptions, CompileModuleOptions } from "./compiler.ts";
|
|
3
3
|
export { TS_RUNTIME_PREAMBLE } from "./preamble.ts";
|
|
4
4
|
export type * from "./types.ts";
|