@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 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.
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"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"}
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
- // Collect ALL non-base modules (entry + user library modules).
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
- emitFreeFunction(sf, fn, forceName, forceAsync) {
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
  *