@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/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
@@ -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;AACrE,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,mBAAmB,YAAY,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
@@ -1,3 +1,3 @@
1
- export { BallCompiler, compile, compileModule } from "./compiler.js";
1
+ export { BallCompiler, compile, compileLibrary, compileModule } from "./compiler.js";
2
2
  export { TS_RUNTIME_PREAMBLE } from "./preamble.js";
3
3
  //# sourceMappingURL=index.js.map
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;AAErE,OAAO,EAAE,mBAAmB,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.66.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
- // Collect ALL non-base modules (entry + user library modules).
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";