@jalvin/compiler 2.0.32 → 2.0.33

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.
Files changed (53) hide show
  1. package/dist/codegen/index.d.ts +179 -0
  2. package/dist/codegen/index.d.ts.map +1 -0
  3. package/dist/codegen/index.js +2207 -0
  4. package/dist/codegen/index.js.map +1 -0
  5. package/dist/codegen_freestanding_c.d.ts +9 -0
  6. package/dist/codegen_freestanding_c.d.ts.map +1 -0
  7. package/dist/codegen_freestanding_c.js +321 -0
  8. package/dist/codegen_freestanding_c.js.map +1 -0
  9. package/dist/diagnostics.d.ts +2 -0
  10. package/dist/diagnostics.d.ts.map +1 -1
  11. package/dist/diagnostics.js +3 -1
  12. package/dist/diagnostics.js.map +1 -1
  13. package/dist/lexer/chars.d.ts +5 -0
  14. package/dist/lexer/chars.d.ts.map +1 -0
  15. package/dist/lexer/chars.js +20 -0
  16. package/dist/lexer/chars.js.map +1 -0
  17. package/dist/lexer/index.d.ts +37 -0
  18. package/dist/lexer/index.d.ts.map +1 -0
  19. package/dist/lexer/index.js +630 -0
  20. package/dist/lexer/index.js.map +1 -0
  21. package/dist/lexer/tokens.d.ts +135 -0
  22. package/dist/lexer/tokens.d.ts.map +1 -0
  23. package/dist/lexer/tokens.js +89 -0
  24. package/dist/lexer/tokens.js.map +1 -0
  25. package/dist/parser/constants.d.ts +3 -0
  26. package/dist/parser/constants.d.ts.map +1 -0
  27. package/dist/parser/constants.js +7 -0
  28. package/dist/parser/constants.js.map +1 -0
  29. package/dist/parser/helpers.d.ts +2 -0
  30. package/dist/parser/helpers.d.ts.map +1 -0
  31. package/dist/parser/helpers.js +9 -0
  32. package/dist/parser/helpers.js.map +1 -0
  33. package/dist/parser/index.d.ts +118 -0
  34. package/dist/parser/index.d.ts.map +1 -0
  35. package/dist/parser/index.js +2387 -0
  36. package/dist/parser/index.js.map +1 -0
  37. package/dist/typechecker/globals.d.ts +2 -0
  38. package/dist/typechecker/globals.d.ts.map +1 -0
  39. package/dist/typechecker/globals.js +15 -0
  40. package/dist/typechecker/globals.js.map +1 -0
  41. package/dist/typechecker/index.d.ts +146 -0
  42. package/dist/typechecker/index.d.ts.map +1 -0
  43. package/dist/typechecker/index.js +2288 -0
  44. package/dist/typechecker/index.js.map +1 -0
  45. package/dist/typechecker/types.d.ts +66 -0
  46. package/dist/typechecker/types.d.ts.map +1 -0
  47. package/dist/typechecker/types.js +39 -0
  48. package/dist/typechecker/types.js.map +1 -0
  49. package/dist/typechecker.d.ts +12 -0
  50. package/dist/typechecker.d.ts.map +1 -1
  51. package/dist/typechecker.js +243 -149
  52. package/dist/typechecker.js.map +1 -1
  53. package/package.json +10 -15
@@ -0,0 +1,2207 @@
1
+ "use strict";
2
+ // ─────────────────────────────────────────────────────────────────────────────
3
+ // Jalvin Code Generator — AST → TypeScript/TSX
4
+ //
5
+ // Design principles:
6
+ // • component declarations → React functional components (TSX)
7
+ // • data class → class with auto-generated copy(), equals(), toString()
8
+ // • sealed class → TypeScript discriminated union + base class
9
+ // • extension functions → module-level functions with receiver as first param,
10
+ // augmented onto the prototype via declaration merging
11
+ // • launch {} → void-returning async IIFE (fire-and-forget)
12
+ // • async {} → Promise<T>-returning async IIFE
13
+ // • suspend fun → async function
14
+ // • Bibi(...) → runtime Bibi() helper (HTTP client)
15
+ // • StateFlow / ViewModel → @jalvin/runtime types
16
+ // ─────────────────────────────────────────────────────────────────────────────
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.CodeGenerator = exports.DEFAULT_CODEGEN_OPTIONS = void 0;
52
+ exports.generate = generate;
53
+ const fs = __importStar(require("node:fs"));
54
+ const nodePath = __importStar(require("node:path"));
55
+ const AST = __importStar(require("../ast.js"));
56
+ const diagnostics_js_1 = require("../diagnostics.js");
57
+ const lexer_js_1 = require("../lexer.js");
58
+ const parser_js_1 = require("../parser.js");
59
+ // ---------------------------------------------------------------------------
60
+ // Code writer — thin abstraction over string concatenation that tracks
61
+ // indentation and emits source-map line hints.
62
+ // ---------------------------------------------------------------------------
63
+ class Writer {
64
+ buf = "";
65
+ indent = 0;
66
+ INDENT = " ";
67
+ /** line mapping: output line → input line (1-based) */
68
+ lineMap = [];
69
+ currentLine = 1;
70
+ write(text) {
71
+ this.buf += text;
72
+ }
73
+ writeLine(text = "") {
74
+ this.buf += text + "\n";
75
+ this.lineMap.push(this.currentLine);
76
+ this.currentLine++;
77
+ }
78
+ writeIndented(text) {
79
+ this.buf += this.INDENT.repeat(this.indent) + text;
80
+ }
81
+ writeIndentedLine(text = "") {
82
+ this.buf += this.INDENT.repeat(this.indent) + text + "\n";
83
+ this.lineMap.push(this.currentLine);
84
+ this.currentLine++;
85
+ }
86
+ pushIndent() { this.indent++; }
87
+ popIndent() { this.indent = Math.max(0, this.indent - 1); }
88
+ get output() { return this.buf; }
89
+ setSourceLine(line) { this.currentLine = line + 1; }
90
+ }
91
+ exports.DEFAULT_CODEGEN_OPTIONS = {
92
+ jsx: false,
93
+ module: "esm",
94
+ emitTypes: false,
95
+ runtimeImport: "@jalvin/runtime",
96
+ };
97
+ class CodeGenerator {
98
+ w = new Writer();
99
+ opts;
100
+ hasComponents = false;
101
+ runtimeSymbolsNeeded = new Set();
102
+ /** Names of component functions — used to detect Compose-style calls and emit them as JSX */
103
+ componentNames = new Set();
104
+ /**
105
+ * Param names for components resolved from imported local .jalvin files.
106
+ * Maps component name → ordered list of param names.
107
+ */
108
+ componentParamNames = new Map();
109
+ /** True when the program contains `import @jalvin/ui.*` — used to detect UI primitive calls */
110
+ hasUiStarImport = false;
111
+ /** Operator overload resolutions from the type checker */
112
+ operatorOverloadMap = new Map();
113
+ /** Type map from the type checker */
114
+ typeMap = new Map();
115
+ /**
116
+ * Registry of extension functions declared on primitive receiver types.
117
+ * Maps receiverTypeName → Map<methodName, generatedFnName>
118
+ * Populated during top-level emit; used at call sites for rewriting.
119
+ */
120
+ primitiveExtensions = new Map();
121
+ /**
122
+ * When a scoped star import exists (e.g. import @jalvin/runtime.*), contains the
123
+ * set of external symbol names to emit as named imports from that package instead
124
+ * of a namespace import. Empty if namespace-import behavior is used.
125
+ */
126
+ externalStarCandidates = new Set();
127
+ /** Module specifiers whose symbols are already covered by a named star-import expansion. */
128
+ handledStarImportModules = new Set();
129
+ constructor(opts = {}) {
130
+ this.opts = { ...exports.DEFAULT_CODEGEN_OPTIONS, ...opts };
131
+ }
132
+ generate(program, operatorOverloads, typeMap) {
133
+ if (operatorOverloads)
134
+ this.operatorOverloadMap = operatorOverloads;
135
+ if (typeMap)
136
+ this.typeMap = typeMap;
137
+ // Pre-scan for components (local declarations OR @jalvin/ui imports)
138
+ this.hasComponents = program.declarations.some((d) => d.kind === "ComponentDecl" ||
139
+ (d.kind === "ClassDecl" && d.body?.members.some((m) => m.kind === "ComponentDecl"))) || program.imports.some((imp) => imp.path[0] === "@jalvin" && imp.path[1] === "ui");
140
+ // Collect component names for Compose-style call detection
141
+ this.componentNames = new Set();
142
+ this.componentParamNames = new Map();
143
+ for (const decl of program.declarations) {
144
+ if (decl.kind === "ComponentDecl")
145
+ this.componentNames.add(decl.name);
146
+ if (decl.kind === "ClassDecl" && decl.body) {
147
+ for (const m of decl.body.members) {
148
+ if (m.kind === "ComponentDecl")
149
+ this.componentNames.add(m.name);
150
+ }
151
+ }
152
+ }
153
+ this.hasUiStarImport = false;
154
+ for (const imp of program.imports) {
155
+ // Named imports from @jalvin/ui are component functions
156
+ if (imp.path[0] === "@jalvin" && imp.path[1] === "ui" && !imp.star) {
157
+ this.componentNames.add(imp.path[imp.path.length - 1]);
158
+ }
159
+ // Star import from @jalvin/ui — UI primitives won't be in componentNames
160
+ if (imp.path[0] === "@jalvin" && imp.path[1] === "ui" && imp.star) {
161
+ this.hasUiStarImport = true;
162
+ }
163
+ }
164
+ // Resolve component fun symbols from locally imported .jalvin files
165
+ const sourceRoot = this.opts.sourceRoot ?? process.cwd();
166
+ this.resolveLocalComponentImports(program, sourceRoot);
167
+ // Pre-compute external star-import candidates (before emitting anything)
168
+ this.externalStarCandidates = new Set();
169
+ this.handledStarImportModules = new Set();
170
+ const scopedStarImports = program.imports.filter((imp) => imp.star && imp.path[0].startsWith("@"));
171
+ if (scopedStarImports.length === 1) {
172
+ // Only resolve when there's exactly one scoped star import — with multiple
173
+ // star imports we can't safely split external names across packages.
174
+ const referencedNames = this.gatherReferencedNames(program);
175
+ const localBindings = this.gatherAllLocalBindings(program);
176
+ for (const name of referencedNames) {
177
+ if (!localBindings.has(name) && !JS_GLOBAL_NAMES.has(name)) {
178
+ this.externalStarCandidates.add(name);
179
+ }
180
+ }
181
+ }
182
+ // Emit header
183
+ this.emitHeader(program);
184
+ // Declarations
185
+ for (const decl of program.declarations) {
186
+ this.emitTopLevelDecl(decl);
187
+ this.w.writeLine();
188
+ }
189
+ // Patch in runtime import if needed
190
+ const preamble = this.buildPreamble();
191
+ const code = preamble + this.w.output;
192
+ return {
193
+ code,
194
+ lineMap: this.w.lineMap,
195
+ isJsx: false, // DOM-based emit — no JSX
196
+ };
197
+ }
198
+ // ── Preamble & imports ─────────────────────────────────────────────────────
199
+ buildPreamble() {
200
+ const lines = [];
201
+ // No React import — @jalvin/ui is DOM-based.
202
+ // Emit compiler-injected runtime symbols, but only those NOT already covered
203
+ // by a star-import's named-import expansion (to avoid duplicate imports).
204
+ const alreadyCovered = this.handledStarImportModules.has(this.opts.runtimeImport)
205
+ ? this.externalStarCandidates
206
+ : new Set();
207
+ const needed = [...this.runtimeSymbolsNeeded].filter((s) => !alreadyCovered.has(s));
208
+ if (needed.length > 0) {
209
+ lines.push(`import { ${needed.sort().join(", ")} } from "${this.opts.runtimeImport}";`);
210
+ }
211
+ return lines.length > 0 ? lines.join("\n") + "\n\n" : "";
212
+ }
213
+ emitHeader(program) {
214
+ const sourceRoot = this.opts.sourceRoot ?? process.cwd();
215
+ // Re-emit user imports as ES imports
216
+ for (const imp of program.imports) {
217
+ // Build module specifier.
218
+ //
219
+ // Scoped packages (@org/pkg.Symbol): symbol is an export from the package,
220
+ // strip the last segment.
221
+ // import @jalvin/ui.Column → import { Column } from "@jalvin/ui"
222
+ // import @jalvin/runtime.* → import * as runtime from "@jalvin/runtime"
223
+ //
224
+ // Local imports (a.b.C): check whether the preceding segments already
225
+ // resolve to a file on disk.
226
+ // import src.models.Rotation → src/models/Rotation.ts does NOT exist
227
+ // → import { Rotation } from "src/models/Rotation"
228
+ // import src.models.css.Css → src/models/css.ts DOES exist
229
+ // → import { Css } from "src/models/css"
230
+ const isScoped = imp.path[0].startsWith("@");
231
+ let moduleSpecifier;
232
+ if (imp.star) {
233
+ // Star import — the entire path is the module.
234
+ moduleSpecifier = isScoped
235
+ ? imp.path[0] + "/" + imp.path.slice(1).join("/")
236
+ : imp.path.join("/");
237
+ }
238
+ else if (isScoped) {
239
+ // @org/pkg.Symbol → strip symbol from path.
240
+ const moduleParts = imp.path.slice(0, -1);
241
+ moduleSpecifier = moduleParts[0] + "/" + moduleParts.slice(1).join("/");
242
+ }
243
+ else {
244
+ // Non-scoped import: prefer local-file convention unless this looks like
245
+ // an installed npm package path (e.g. cubing/twisty.TwistyPlayer).
246
+ const precedingParts = imp.path.slice(0, -1);
247
+ const precedingRelPath = precedingParts.join("/");
248
+ const fileExts = [".ts", ".tsx", ".jalvin"];
249
+ const precedingIsFile = precedingParts.length > 0 && fileExts.some((ext) => fs.existsSync(nodePath.join(sourceRoot, precedingRelPath + ext)));
250
+ const looksLikeNpmPackage = this.isLikelyNodeModuleImport(imp.path, sourceRoot);
251
+ moduleSpecifier = precedingIsFile
252
+ ? precedingRelPath // src.models.css.Css → "src/models/css"
253
+ : looksLikeNpmPackage
254
+ ? precedingRelPath // cubing.twisty.TwistyPlayer → "cubing/twisty"
255
+ : imp.path.join("/"); // src.models.Rotation → "src/models/Rotation"
256
+ }
257
+ if (imp.star && isScoped && this.externalStarCandidates.size > 0) {
258
+ // Named-import expansion of a scoped star import.
259
+ // Emit explicit named imports for each external symbol used in the file so
260
+ // that symbols like `ViewModel`, `mutableStateOf` are directly in scope.
261
+ const symbols = [...this.externalStarCandidates].sort();
262
+ this.w.writeIndentedLine(`import { ${symbols.join(", ")} } from "${moduleSpecifier}";`);
263
+ this.handledStarImportModules.add(moduleSpecifier);
264
+ }
265
+ else if (imp.star) {
266
+ // Fallback: namespace import (multiple scoped star imports or no candidates).
267
+ this.w.writeIndentedLine(`import * as ${imp.path[imp.path.length - 1]} from "${moduleSpecifier}";`);
268
+ }
269
+ else if (imp.alias) {
270
+ const named = imp.path[imp.path.length - 1];
271
+ this.w.writeIndentedLine(`import { ${named} as ${imp.alias} } from "${moduleSpecifier}";`);
272
+ }
273
+ else {
274
+ this.w.writeIndentedLine(`import { ${imp.path[imp.path.length - 1]} } from "${moduleSpecifier}";`);
275
+ }
276
+ }
277
+ if (program.imports.length > 0)
278
+ this.w.writeLine();
279
+ }
280
+ /**
281
+ * Heuristic for non-scoped imports: if the first path segment resolves to an
282
+ * installed package in node_modules, treat the import as npm/ESM and strip
283
+ * the trailing symbol name from the module specifier.
284
+ */
285
+ isLikelyNodeModuleImport(pathParts, sourceRoot) {
286
+ if (pathParts.length < 2)
287
+ return false;
288
+ const packageName = pathParts[0];
289
+ let dir = sourceRoot;
290
+ while (true) {
291
+ const packageJson = nodePath.join(dir, "node_modules", packageName, "package.json");
292
+ if (fs.existsSync(packageJson))
293
+ return true;
294
+ const parent = nodePath.dirname(dir);
295
+ if (parent === dir)
296
+ break;
297
+ dir = parent;
298
+ }
299
+ return false;
300
+ }
301
+ // ── Top-level declarations ─────────────────────────────────────────────────
302
+ /** Emit a @deprecated JSDoc block if the declaration has @Nuked. */
303
+ emitAnnotations(mods) {
304
+ for (const ann of mods.annotations) {
305
+ if (ann.name === "Nuked") {
306
+ const reason = ann.args ? ` ${ann.args.replace(/^["']|["']$/g, "")}` : "";
307
+ this.w.writeIndentedLine(`/** @deprecated${reason} */`);
308
+ }
309
+ // Other annotations are silently ignored for now (pass-through model)
310
+ }
311
+ }
312
+ emitTopLevelDecl(decl) {
313
+ switch (decl.kind) {
314
+ case "FunDecl":
315
+ this.emitFunDecl(decl, false);
316
+ break;
317
+ case "ComponentDecl":
318
+ this.emitComponentDecl(decl);
319
+ break;
320
+ case "ClassDecl":
321
+ this.emitClassDecl(decl);
322
+ break;
323
+ case "DataClassDecl":
324
+ this.emitDataClassDecl(decl);
325
+ break;
326
+ case "SealedClassDecl":
327
+ this.emitSealedClassDecl(decl);
328
+ break;
329
+ case "EnumClassDecl":
330
+ this.emitEnumClassDecl(decl);
331
+ break;
332
+ case "InterfaceDecl":
333
+ this.emitInterfaceDecl(decl);
334
+ break;
335
+ case "ObjectDecl":
336
+ this.emitObjectDecl(decl);
337
+ break;
338
+ case "TypeAliasDecl":
339
+ this.emitTypeAliasDecl(decl);
340
+ break;
341
+ case "PropertyDecl":
342
+ this.emitPropertyDecl(decl, false);
343
+ break;
344
+ case "ExtensionFunDecl":
345
+ this.emitExtensionFunDecl(decl);
346
+ break;
347
+ case "DestructuringDecl":
348
+ this.emitDestructuringDecl(decl, false);
349
+ break;
350
+ }
351
+ }
352
+ // ── function declarations ──────────────────────────────────────────────────
353
+ emitFunDecl(decl, memberOf) {
354
+ this.emitAnnotations(decl.modifiers);
355
+ const isSuspend = AST.isSuspend(decl.modifiers);
356
+ const vis = memberOf ? this.visibilityPrefix(decl.modifiers) : this.exportPrefix(decl.modifiers);
357
+ const asyncKw = isSuspend ? "async " : "";
358
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
359
+ const params = this.emitParamsStr(decl.params);
360
+ const retType = decl.returnType && this.opts.emitTypes
361
+ ? `: ${this.emitTypeRef(decl.returnType)}`
362
+ : "";
363
+ if (!decl.body) {
364
+ // Abstract / interface method
365
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType};`);
366
+ return;
367
+ }
368
+ if (decl.body.kind === "Block") {
369
+ if (memberOf) {
370
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
371
+ }
372
+ else {
373
+ this.w.writeIndentedLine(`${vis}${asyncKw}function ${decl.name}${typeParams}(${params})${retType} {`);
374
+ }
375
+ this.w.pushIndent();
376
+ this.emitBlock(decl.body);
377
+ this.w.popIndent();
378
+ this.w.writeIndentedLine(`}`);
379
+ }
380
+ else {
381
+ // Expression body
382
+ if (memberOf) {
383
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
384
+ }
385
+ else {
386
+ this.w.writeIndentedLine(`${vis}${asyncKw}function ${decl.name}${typeParams}(${params})${retType} {`);
387
+ }
388
+ this.w.pushIndent();
389
+ this.w.writeIndentedLine(`return ${this.emitExpr(decl.body)};`);
390
+ this.w.popIndent();
391
+ this.w.writeIndentedLine(`}`);
392
+ }
393
+ }
394
+ emitMemberFunDecl(decl) {
395
+ const isSuspend = AST.isSuspend(decl.modifiers);
396
+ const vis = this.visibilityPrefix(decl.modifiers);
397
+ const asyncKw = isSuspend ? "async " : "";
398
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
399
+ const params = this.emitParamsStr(decl.params);
400
+ const retType = decl.returnType && this.opts.emitTypes
401
+ ? `: ${this.emitTypeRef(decl.returnType)}`
402
+ : "";
403
+ if (!decl.body) {
404
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType};`);
405
+ return;
406
+ }
407
+ if (decl.body.kind === "Block") {
408
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
409
+ this.w.pushIndent();
410
+ this.emitBlock(decl.body);
411
+ this.w.popIndent();
412
+ this.w.writeIndentedLine(`}`);
413
+ }
414
+ else {
415
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
416
+ this.w.pushIndent();
417
+ this.w.writeIndentedLine(`return ${this.emitExpr(decl.body)};`);
418
+ this.w.popIndent();
419
+ this.w.writeIndentedLine(`}`);
420
+ }
421
+ }
422
+ // ── component declarations → React function components ────────────────────
423
+ emitComponentDecl(decl) {
424
+ this.emitAnnotations(decl.modifiers);
425
+ this.hasComponents = true;
426
+ const vis = this.exportPrefix(decl.modifiers);
427
+ const params = this.emitComponentPropsStr(decl.params);
428
+ // Props interface
429
+ if (decl.params.length > 0) {
430
+ this.w.writeIndentedLine(`interface ${decl.name}Props {`);
431
+ this.w.pushIndent();
432
+ for (const p of decl.params) {
433
+ const optional = p.defaultValue ? "?" : "";
434
+ const typeStr = this.opts.emitTypes ? this.emitTypeRef(p.type) : "any";
435
+ this.w.writeIndentedLine(`readonly ${p.name}${optional}: ${typeStr};`);
436
+ }
437
+ this.w.popIndent();
438
+ this.w.writeIndentedLine(`}`);
439
+ this.w.writeLine();
440
+ }
441
+ const propsParam = decl.params.length > 0
442
+ ? `{ ${decl.params.map((p) => p.name + (p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "")).join(", ")} }: ${decl.name}Props`
443
+ : "";
444
+ this.w.writeIndentedLine(`${vis}function ${decl.name}(${propsParam}) {`);
445
+ this.w.pushIndent();
446
+ this.emitComponentBlock(decl.body);
447
+ this.w.popIndent();
448
+ this.w.writeIndentedLine(`}`);
449
+ }
450
+ /**
451
+ * Emit the body of a `component fun` block.
452
+ * The last statement is emitted in tail position — if it is an expression,
453
+ * an if/else chain, or a when block, the innermost UI call is implicitly
454
+ * returned (Kotlin-style implicit return of the last expression).
455
+ */
456
+ emitComponentBlock(block) {
457
+ const stmts = block.statements;
458
+ for (let i = 0; i < stmts.length; i++) {
459
+ const stmt = stmts[i];
460
+ if (i === stmts.length - 1) {
461
+ this.emitTailStmt(stmt);
462
+ }
463
+ else {
464
+ this.emitStmt(stmt);
465
+ }
466
+ }
467
+ }
468
+ /** Emit a statement in tail position, adding implicit return where applicable. */
469
+ emitTailStmt(stmt) {
470
+ if (stmt.kind === "ExprStmt") {
471
+ // Implicit return: last expression in a component block
472
+ this.w.writeIndentedLine(`return ${this.emitExpr(stmt.expr)};`);
473
+ }
474
+ else if (stmt.kind === "IfStmt") {
475
+ this.emitIfStmtWithTailReturn(stmt);
476
+ }
477
+ else if (stmt.kind === "WhenStmt") {
478
+ this.emitWhenStmtWithTailReturn(stmt);
479
+ }
480
+ else {
481
+ this.emitStmt(stmt);
482
+ }
483
+ }
484
+ /** Emit a block in tail position, treating its last statement as a tail expression. */
485
+ emitTailBlock(block) {
486
+ const stmts = block.statements;
487
+ for (let i = 0; i < stmts.length; i++) {
488
+ const stmt = stmts[i];
489
+ if (i === stmts.length - 1) {
490
+ this.emitTailStmt(stmt);
491
+ }
492
+ else {
493
+ this.emitStmt(stmt);
494
+ }
495
+ }
496
+ }
497
+ /** Like emitIfStmt but with tail-return applied recursively to each branch body. */
498
+ emitIfStmtWithTailReturn(stmt) {
499
+ this.w.writeIndentedLine(`if (${this.emitExpr(stmt.condition)}) {`);
500
+ this.w.pushIndent();
501
+ this.emitTailBlock(stmt.then);
502
+ this.w.popIndent();
503
+ if (stmt.else) {
504
+ if (stmt.else.kind === "IfStmt") {
505
+ this.w.writeIndented(`} else `);
506
+ this.emitIfStmtWithTailReturn(stmt.else);
507
+ }
508
+ else {
509
+ this.w.writeIndentedLine(`} else {`);
510
+ this.w.pushIndent();
511
+ this.emitTailBlock(stmt.else);
512
+ this.w.popIndent();
513
+ this.w.writeIndentedLine(`}`);
514
+ }
515
+ }
516
+ else {
517
+ this.w.writeIndentedLine(`}`);
518
+ }
519
+ }
520
+ /** Like emitWhenStmt but with tail-return applied recursively to each branch body. */
521
+ emitWhenStmtWithTailReturn(stmt) {
522
+ const subjectVar = stmt.subject ? "__when_subject__" : null;
523
+ if (stmt.subject) {
524
+ const binding = stmt.subject.binding ?? subjectVar;
525
+ this.w.writeIndentedLine(`const ${binding} = ${this.emitExpr(stmt.subject.expr)};`);
526
+ }
527
+ let first = true;
528
+ for (const branch of stmt.branches) {
529
+ if (branch.isElse) {
530
+ this.w.writeIndentedLine(first ? `{` : `} else {`);
531
+ }
532
+ else {
533
+ const cond = branch.conditions.map((c) => this.emitWhenCondition(c, subjectVar ?? "")).join(" || ");
534
+ this.w.writeIndentedLine(first ? `if (${cond}) {` : `} else if (${cond}) {`);
535
+ }
536
+ first = false;
537
+ this.w.pushIndent();
538
+ if (branch.body.kind === "Block") {
539
+ this.emitTailBlock(branch.body);
540
+ }
541
+ else {
542
+ this.w.writeIndentedLine(`return ${this.emitExpr(branch.body)};`);
543
+ }
544
+ this.w.popIndent();
545
+ }
546
+ this.w.writeIndentedLine(`}`);
547
+ }
548
+ // ── regular class ──────────────────────────────────────────────────────────
549
+ emitClassDecl(decl) {
550
+ this.emitAnnotations(decl.modifiers);
551
+ const vis = this.exportPrefix(decl.modifiers);
552
+ const abstract = decl.modifiers.modifiers.includes("abstract") ? "abstract " : "";
553
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
554
+ const superTypes = this.emitSuperTypesStr(decl.superTypes);
555
+ this.w.writeIndentedLine(`${vis}${abstract}class ${decl.name}${typeParams}${superTypes} {`);
556
+ this.w.pushIndent();
557
+ // The first supertype's delegateArgs become the `super(args)` call inside the constructor.
558
+ const superDelegateArgs = decl.superTypes[0]?.delegateArgs ?? null;
559
+ if (decl.primaryConstructor) {
560
+ const initBlocks = decl.body?.members.filter((m) => m.kind === "InitBlock") ?? [];
561
+ this.emitPrimaryConstructorProps(decl.primaryConstructor.params, initBlocks, superDelegateArgs);
562
+ }
563
+ else if (superDelegateArgs !== null) {
564
+ // No primary constructor but the supertype has explicit delegation args —
565
+ // emit a minimal constructor that forwards them to super().
566
+ const superArgsStr = superDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
567
+ this.w.writeIndentedLine(`constructor() {`);
568
+ this.w.pushIndent();
569
+ this.w.writeIndentedLine(`super(${superArgsStr});`);
570
+ this.w.popIndent();
571
+ this.w.writeIndentedLine(`}`);
572
+ }
573
+ if (decl.body) {
574
+ this.emitClassBody(decl.body);
575
+ }
576
+ this.w.popIndent();
577
+ this.w.writeIndentedLine(`}`);
578
+ }
579
+ // ── data class → class with copy/equals/toString ──────────────────────────
580
+ emitDataClassDecl(decl, kindName) {
581
+ this.emitAnnotations(decl.modifiers);
582
+ const vis = this.exportPrefix(decl.modifiers);
583
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
584
+ const superTypes = this.emitSuperTypesStr(decl.superTypes);
585
+ const props = decl.primaryConstructor.params;
586
+ this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams}${superTypes} {`);
587
+ this.w.pushIndent();
588
+ // Discriminant for sealed class sub-types
589
+ if (kindName) {
590
+ this.w.writeIndentedLine(`readonly __kind = "${kindName}" as const;`);
591
+ }
592
+ // Constructor params → readonly properties
593
+ for (const p of props) {
594
+ const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
595
+ this.w.writeIndentedLine(`readonly ${p.name}${t};`);
596
+ }
597
+ this.w.writeLine();
598
+ // Constructor
599
+ const ctorParams = props.map((p) => {
600
+ const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
601
+ return `${p.name}${t}`;
602
+ }).join(", ");
603
+ this.w.writeIndentedLine(`constructor(${ctorParams}) {`);
604
+ this.w.pushIndent();
605
+ // Emit super() with actual delegation args (or empty call if delegateArgs is []).
606
+ const dataSuperDelegateArgs = decl.superTypes[0]?.delegateArgs ?? null;
607
+ if (dataSuperDelegateArgs !== null) {
608
+ const superArgsStr = dataSuperDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
609
+ this.w.writeIndentedLine(`super(${superArgsStr});`);
610
+ }
611
+ for (const p of props) {
612
+ this.w.writeIndentedLine(`this.${p.name} = ${p.name};`);
613
+ }
614
+ this.w.popIndent();
615
+ this.w.writeIndentedLine(`}`);
616
+ this.w.writeLine();
617
+ // copy()
618
+ const copyParams = props.map((p) => {
619
+ const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
620
+ return `${p.name}${t} = this.${p.name}`;
621
+ }).join(", ");
622
+ const copyArgs = props.map((p) => p.name).join(", ");
623
+ this.w.writeIndentedLine(`copy(${copyParams}): ${decl.name} {`);
624
+ this.w.pushIndent();
625
+ this.w.writeIndentedLine(`return new ${decl.name}(${copyArgs});`);
626
+ this.w.popIndent();
627
+ this.w.writeIndentedLine(`}`);
628
+ this.w.writeLine();
629
+ // equals()
630
+ this.runtimeSymbolsNeeded.add("jalvinEquals");
631
+ const eqChecks = props.map((p) => `jalvinEquals(this.${p.name}, other.${p.name})`).join(" && ") || "true";
632
+ this.w.writeIndentedLine(`equals(other: unknown): boolean {`);
633
+ this.w.pushIndent();
634
+ this.w.writeIndentedLine(`if (!(other instanceof ${decl.name})) return false;`);
635
+ this.w.writeIndentedLine(`return ${eqChecks};`);
636
+ this.w.popIndent();
637
+ this.w.writeIndentedLine(`}`);
638
+ this.w.writeLine();
639
+ // toString()
640
+ const toStringParts = props.map((p) => `${p.name}=\${this.${p.name}}`).join(", ");
641
+ this.w.writeIndentedLine(`toString(): string {`);
642
+ this.w.pushIndent();
643
+ this.w.writeIndentedLine(`return \`${decl.name}(${toStringParts})\`;`);
644
+ this.w.popIndent();
645
+ this.w.writeIndentedLine(`}`);
646
+ this.w.writeLine();
647
+ // hashCode() — djb2
648
+ this.w.writeIndentedLine(`hashCode(): number {`);
649
+ this.w.pushIndent();
650
+ this.w.writeIndentedLine(`let h = 17;`);
651
+ for (const p of props) {
652
+ this.w.writeIndentedLine(`h = h * 31 + String(this.${p.name}).split("").reduce((a, c) => (a * 31 + c.charCodeAt(0)) >>> 0, 0);`);
653
+ }
654
+ this.w.writeIndentedLine(`return h >>> 0;`);
655
+ this.w.popIndent();
656
+ this.w.writeIndentedLine(`}`);
657
+ if (decl.body) {
658
+ this.w.writeLine();
659
+ this.emitClassBody(decl.body);
660
+ }
661
+ this.w.popIndent();
662
+ this.w.writeIndentedLine(`}`);
663
+ }
664
+ // ── sealed class → TS abstract class + namespace merging ──────────────────
665
+ emitSealedClassDecl(decl) {
666
+ this.emitAnnotations(decl.modifiers);
667
+ const vis = this.exportPrefix(decl.modifiers);
668
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
669
+ const subDecls = [];
670
+ const otherMembers = [];
671
+ if (decl.body) {
672
+ for (const member of decl.body.members) {
673
+ if (member.kind === "ClassDecl" ||
674
+ member.kind === "DataClassDecl" ||
675
+ member.kind === "ObjectDecl") {
676
+ subDecls.push(member);
677
+ }
678
+ else if (member.kind !== "InitBlock") {
679
+ otherMembers.push(member);
680
+ }
681
+ }
682
+ }
683
+ // Emit the abstract base class (only non-subtype members in the body)
684
+ this.w.writeIndentedLine(`${vis}abstract class ${decl.name}${typeParams} {`);
685
+ this.w.pushIndent();
686
+ this.w.writeIndentedLine(`abstract readonly __kind: string;`);
687
+ for (const member of otherMembers) {
688
+ this.emitClassMember(member);
689
+ this.w.writeLine();
690
+ }
691
+ this.w.popIndent();
692
+ this.w.writeIndentedLine(`}`);
693
+ this.w.writeLine();
694
+ if (subDecls.length === 0)
695
+ return;
696
+ // Emit non-object sub-declarations at module level (before namespace)
697
+ for (const sub of subDecls) {
698
+ if (sub.kind === "DataClassDecl") {
699
+ this.emitDataClassDecl(sub, sub.name);
700
+ this.w.writeLine();
701
+ }
702
+ else if (sub.kind === "ClassDecl") {
703
+ this.emitClassDecl(sub);
704
+ this.w.writeLine();
705
+ }
706
+ }
707
+ // Emit namespace merging so SealedClass.SubType is accessible
708
+ this.w.writeIndentedLine(`${vis}namespace ${decl.name} {`);
709
+ this.w.pushIndent();
710
+ for (const sub of subDecls) {
711
+ if (sub.kind === "ObjectDecl") {
712
+ // Singleton object — emit inline in namespace with __kind discriminant
713
+ const superStr = sub.superTypes.length > 0
714
+ ? this.emitSuperTypesStr(sub.superTypes)
715
+ : ` extends ${decl.name}`;
716
+ const membersArr = [];
717
+ membersArr.push(`readonly __kind = "${sub.name}" as const;`);
718
+ if (sub.body) {
719
+ for (const m of sub.body.members) {
720
+ if (m.kind !== "InitBlock")
721
+ membersArr.push(this.captureClassMember(m));
722
+ }
723
+ }
724
+ const membersStr = membersArr.join(" ");
725
+ this.w.writeIndentedLine(`export const ${sub.name} = new (class${superStr} { ${membersStr} })();`);
726
+ }
727
+ else {
728
+ // DataClassDecl / ClassDecl — already emitted at module level, re-export
729
+ this.w.writeIndentedLine(`export { ${sub.name} };`);
730
+ }
731
+ }
732
+ this.w.popIndent();
733
+ this.w.writeIndentedLine(`}`);
734
+ }
735
+ // ── enum class → TypeScript const enum + class ────────────────────────────
736
+ emitEnumClassDecl(decl) {
737
+ const vis = this.exportPrefix(decl.modifiers);
738
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
739
+ // Emit the enum as a TypeScript class with static singleton instances.
740
+ // This preserves the `EnumClass.ENTRY` access pattern and allows
741
+ // methods/properties to be attached to entries.
742
+ this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams} {`);
743
+ this.w.pushIndent();
744
+ // Private constructor so entries are the only instances
745
+ if (decl.primaryConstructor && decl.primaryConstructor.params.length > 0) {
746
+ const ctorParams = decl.primaryConstructor.params
747
+ .map((p) => {
748
+ const ro = p.propertyKind === "val" ? "readonly " : "";
749
+ const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
750
+ return `${ro}${p.name}${type}`;
751
+ })
752
+ .join(", ");
753
+ this.w.writeIndentedLine(`private constructor(${ctorParams}) {}`);
754
+ }
755
+ else {
756
+ this.w.writeIndentedLine(`private constructor(readonly name: string, readonly ordinal: number) {}`);
757
+ }
758
+ this.w.writeLine();
759
+ // Static entry instances
760
+ for (let i = 0; i < decl.entries.length; i++) {
761
+ const entry = decl.entries[i];
762
+ const args = entry.args.length > 0
763
+ ? entry.args.map((a) => this.emitExpr(a.value)).join(", ")
764
+ : `"${entry.name}", ${i}`;
765
+ this.w.writeIndentedLine(`static readonly ${entry.name} = new ${decl.name}(${args});`);
766
+ }
767
+ if (decl.entries.length > 0) {
768
+ this.w.writeLine();
769
+ // values() helper
770
+ const entryList = decl.entries.map((e) => `${decl.name}.${e.name}`).join(", ");
771
+ this.w.writeIndentedLine(`static values(): ${decl.name}[] { return [${entryList}]; }`);
772
+ this.w.writeIndentedLine(`static valueOf(name: string): ${decl.name} {`);
773
+ this.w.pushIndent();
774
+ this.w.writeIndentedLine(`const v = ${decl.name}.values().find(e => e.name === name);`);
775
+ this.w.writeIndentedLine(`if (!v) throw new Error(\`No enum constant ${decl.name}.\${name}\`);`);
776
+ this.w.writeIndentedLine(`return v;`);
777
+ this.w.popIndent();
778
+ this.w.writeIndentedLine(`}`);
779
+ }
780
+ if (decl.body) {
781
+ this.w.writeLine();
782
+ this.emitClassBody(decl.body);
783
+ }
784
+ this.w.popIndent();
785
+ this.w.writeIndentedLine(`}`);
786
+ }
787
+ // ── destructuring declaration — val (a, b) = expr ─────────────────────────
788
+ emitDestructuringDecl(decl, _memberOf) {
789
+ const kw = decl.mutable ? "let" : "const";
790
+ const names = decl.names.map((n) => n ?? "_").join(", ");
791
+ const init = this.emitExpr(decl.initializer);
792
+ this.w.writeIndentedLine(`${kw} [${names}] = ${init};`);
793
+ }
794
+ // ── interface ──────────────────────────────────────────────────────────────
795
+ emitInterfaceDecl(decl) {
796
+ this.emitAnnotations(decl.modifiers);
797
+ const vis = this.exportPrefix(decl.modifiers);
798
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
799
+ const superTypes = decl.superTypes.length > 0
800
+ ? ` extends ${decl.superTypes.map((s) => this.emitTypeRef(s.type)).join(", ")}`
801
+ : "";
802
+ this.w.writeIndentedLine(`${vis}interface ${decl.name}${typeParams}${superTypes} {`);
803
+ this.w.pushIndent();
804
+ if (decl.body) {
805
+ for (const member of decl.body.members) {
806
+ this.emitClassMember(member);
807
+ }
808
+ }
809
+ this.w.popIndent();
810
+ this.w.writeIndentedLine(`}`);
811
+ }
812
+ // ── object declaration → singleton ────────────────────────────────────────
813
+ emitObjectDecl(decl) {
814
+ if (!decl.name) {
815
+ // Anonymous object — emitted inline
816
+ return;
817
+ }
818
+ this.emitAnnotations(decl.modifiers);
819
+ const vis = this.exportPrefix(decl.modifiers);
820
+ const superTypes = this.emitSuperTypesStr(decl.superTypes);
821
+ this.w.writeIndentedLine(`${vis}const ${decl.name} = new (class${superTypes} {`);
822
+ this.w.pushIndent();
823
+ this.emitClassBody(decl.body);
824
+ this.w.popIndent();
825
+ this.w.writeIndentedLine(`})();`);
826
+ }
827
+ // ── type alias ─────────────────────────────────────────────────────────────
828
+ emitTypeAliasDecl(decl) {
829
+ const vis = this.exportPrefix(decl.modifiers);
830
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
831
+ this.w.writeIndentedLine(`${vis}type ${decl.name}${typeParams} = ${this.emitTypeRef(decl.type)};`);
832
+ }
833
+ // ── extension functions ────────────────────────────────────────────────────
834
+ // Emitted as standalone free functions with receiver as explicit first param.
835
+ // For class types, also patched onto the prototype for dot-call syntax.
836
+ // For primitive types, call sites are rewritten via `primitiveExtensions`.
837
+ emitExtensionFunDecl(decl) {
838
+ const isSuspend = AST.isSuspend(decl.modifiers);
839
+ const asyncKw = isSuspend ? "async " : "";
840
+ const receiverType = this.emitTypeRef(decl.receiver);
841
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
842
+ const params = this.emitParamsStr(decl.params);
843
+ const retType = decl.returnType && this.opts.emitTypes
844
+ ? `: ${this.emitTypeRef(decl.returnType)}`
845
+ : "";
846
+ const fnName = `__ext_${receiverType.replace(/[^a-zA-Z0-9_]/g, "_")}_${decl.name}`;
847
+ // Emit as a free function: receiver is the first explicit parameter `$receiver`.
848
+ // Inside the function body, `this` is rebound to `$receiver` so Jalvin's
849
+ // `this.prop` references work correctly.
850
+ this.w.writeIndentedLine(`${asyncKw}function ${fnName}${typeParams}($receiver: ${receiverType}${params ? ", " + params : ""})${retType} {`);
851
+ this.w.pushIndent();
852
+ if (decl.body.kind === "Block") {
853
+ this.w.writeIndentedLine(`return (function(this: ${receiverType})${retType} {`);
854
+ this.w.pushIndent();
855
+ this.emitBlock(decl.body);
856
+ this.w.popIndent();
857
+ this.w.writeIndentedLine(`}).call($receiver);`);
858
+ }
859
+ else {
860
+ this.w.writeIndentedLine(`return (function(this: ${receiverType})${retType} { return ${this.emitExpr(decl.body)}; }).call($receiver);`);
861
+ }
862
+ this.w.popIndent();
863
+ this.w.writeIndentedLine(`}`);
864
+ this.w.writeLine();
865
+ const simpleReceiverName = this.receiverClassName(decl.receiver);
866
+ if (simpleReceiverName) {
867
+ if (PRIMITIVE_TYPES.has(simpleReceiverName)) {
868
+ // Register for call-site rewriting — cannot patch primitive prototypes
869
+ if (!this.primitiveExtensions.has(simpleReceiverName)) {
870
+ this.primitiveExtensions.set(simpleReceiverName, new Map());
871
+ }
872
+ this.primitiveExtensions.get(simpleReceiverName).set(decl.name, fnName);
873
+ }
874
+ else {
875
+ // Class types — prototype monkey-patch for dot-call syntax
876
+ this.w.writeIndentedLine(`// Extension: ${receiverType}.${decl.name}`);
877
+ this.w.writeIndentedLine(`(${simpleReceiverName}.prototype as any).${decl.name} = function(this: ${receiverType}, ...args: unknown[]) { return (${fnName} as Function)(this, ...args); };`);
878
+ }
879
+ }
880
+ }
881
+ receiverClassName(ref) {
882
+ if (ref.kind === "SimpleTypeRef")
883
+ return ref.name[ref.name.length - 1] ?? null;
884
+ if (ref.kind === "GenericTypeRef")
885
+ return ref.base.name[ref.base.name.length - 1] ?? null;
886
+ return null;
887
+ }
888
+ /**
889
+ * Maps a JType to the primitive receiver type name used as a key
890
+ * in `primitiveExtensions` (e.g. JType `{ tag: "string" }` → `"String"`).
891
+ * Returns null for non-primitive / unknown types.
892
+ */
893
+ jTypeToReceiverName(type) {
894
+ const tagToReceiverName = {
895
+ string: "String",
896
+ int: "Int",
897
+ long: "Long",
898
+ float: "Float",
899
+ double: "Double",
900
+ boolean: "Boolean",
901
+ char: "Char",
902
+ };
903
+ return tagToReceiverName[type.tag] ?? null;
904
+ }
905
+ classHasInvokeOperator(decl) {
906
+ if (!decl.body)
907
+ return false;
908
+ return decl.body.members.some((m) => m.kind === "FunDecl" &&
909
+ m.name === "invoke" &&
910
+ m.modifiers.modifiers.includes("operator"));
911
+ }
912
+ // ── property declaration ───────────────────────────────────────────────────
913
+ emitPropertyDecl(decl, member, isLocal = false) {
914
+ this.emitAnnotations(decl.modifiers);
915
+ const vis = member ? this.visibilityPrefix(decl.modifiers) : isLocal ? "" : this.exportPrefix(decl.modifiers);
916
+ const isConst = decl.modifiers.modifiers.includes("const");
917
+ const isLateinit = decl.modifiers.modifiers.includes("lateinit");
918
+ // `const val` → `const` at module level; inside classes treated as `static readonly`
919
+ const kw = member
920
+ ? (decl.mutable ? "" : "readonly ")
921
+ : (isConst ? "const " : decl.mutable ? "let " : "const ");
922
+ const type = decl.type && this.opts.emitTypes ? `: ${this.emitTypeRef(decl.type)}` : "";
923
+ if (decl.delegate) {
924
+ // Delegated property — wrap in a getter/setter pair using the delegate
925
+ this.runtimeSymbolsNeeded.add("delegate");
926
+ const delegateExpr = this.emitExpr(decl.delegate);
927
+ if (member) {
928
+ this.w.writeIndentedLine(`${vis}get ${decl.name}()${type} { return delegate(${delegateExpr}, "${decl.name}", this).getValue(); }`);
929
+ if (decl.mutable) {
930
+ this.w.writeIndentedLine(`${vis}set ${decl.name}(v: any) { delegate(${delegateExpr}, "${decl.name}", this).setValue(v); }`);
931
+ }
932
+ }
933
+ else {
934
+ this.w.writeIndentedLine(`${kw}${decl.name}${type} = ${delegateExpr};`);
935
+ }
936
+ return;
937
+ }
938
+ const init = decl.initializer ? ` = ${this.emitExpr(decl.initializer)}` : (isLateinit ? "" : "");
939
+ if (member) {
940
+ const modStr = isConst ? "static readonly " : decl.mutable ? "" : "readonly ";
941
+ this.w.writeIndentedLine(`${vis}${modStr}${decl.name}${type}${init};`);
942
+ }
943
+ else {
944
+ this.w.writeIndentedLine(`${vis}${kw}${decl.name}${type}${init};`);
945
+ }
946
+ if (decl.getter) {
947
+ this.emitPropertyAccessor("get", decl.name, decl.getter, member, type);
948
+ }
949
+ if (decl.setter) {
950
+ this.emitPropertyAccessor("set", decl.name, decl.setter, member, type);
951
+ }
952
+ }
953
+ emitPropertyAccessor(kind, name, acc, member, type) {
954
+ const retOrParam = kind === "get" ? type : `(value: any)`;
955
+ const vis = this.visibilityPrefix(acc.modifiers);
956
+ this.w.writeIndentedLine(`${vis}${kind} ${name}()${retOrParam} {`);
957
+ this.w.pushIndent();
958
+ if (acc.body) {
959
+ if (acc.body.kind === "Block")
960
+ this.emitBlock(acc.body);
961
+ else
962
+ this.w.writeIndentedLine(`return ${this.emitExpr(acc.body)};`);
963
+ }
964
+ this.w.popIndent();
965
+ this.w.writeIndentedLine(`}`);
966
+ }
967
+ // ── class body ─────────────────────────────────────────────────────────────
968
+ emitClassBody(body) {
969
+ for (const member of body.members) {
970
+ // InitBlocks are emitted inside the constructor by emitPrimaryConstructorProps
971
+ if (member.kind === "InitBlock")
972
+ continue;
973
+ this.emitClassMember(member);
974
+ this.w.writeLine();
975
+ }
976
+ }
977
+ emitClassMember(member) {
978
+ switch (member.kind) {
979
+ case "FunDecl":
980
+ this.emitMemberFunDecl(member);
981
+ break;
982
+ case "ComponentDecl":
983
+ this.emitComponentDecl(member);
984
+ break;
985
+ case "PropertyDecl":
986
+ this.emitPropertyDecl(member, true);
987
+ break;
988
+ case "ClassDecl":
989
+ this.emitClassDecl(member);
990
+ break;
991
+ case "DataClassDecl":
992
+ this.emitDataClassDecl(member);
993
+ break;
994
+ case "SealedClassDecl":
995
+ this.emitSealedClassDecl(member);
996
+ break;
997
+ case "EnumClassDecl":
998
+ this.emitEnumClassDecl(member);
999
+ break;
1000
+ case "ObjectDecl":
1001
+ this.emitObjectDecl(member);
1002
+ break;
1003
+ case "CompanionObject":
1004
+ this.emitCompanionObject(member);
1005
+ break;
1006
+ case "InitBlock": /* handled in constructor */ break;
1007
+ case "SecondaryConstructor":
1008
+ this.emitSecondaryConstructor(member);
1009
+ break;
1010
+ case "ExtensionFunDecl":
1011
+ this.emitExtensionFunDecl(member);
1012
+ break;
1013
+ }
1014
+ }
1015
+ emitPrimaryConstructorProps(params, initBlocks, superDelegateArgs) {
1016
+ for (const p of params) {
1017
+ if (p.propertyKind) {
1018
+ const ro = p.propertyKind === "val" ? "readonly " : "";
1019
+ const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
1020
+ this.w.writeIndentedLine(`${ro}${p.name}${type};`);
1021
+ }
1022
+ }
1023
+ // Constructor
1024
+ const ctorParams = params.map((p) => {
1025
+ const ro = p.propertyKind === "val" ? "readonly " : p.propertyKind === "var" ? "" : "";
1026
+ const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
1027
+ const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
1028
+ return `${p.name}${type}${def}`;
1029
+ });
1030
+ this.w.writeIndentedLine(`constructor(${ctorParams.join(", ")}) {`);
1031
+ this.w.pushIndent();
1032
+ // super() call must be the first statement if there is a supertype.
1033
+ if (superDelegateArgs !== null && superDelegateArgs !== undefined) {
1034
+ const superArgsStr = superDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
1035
+ this.w.writeIndentedLine(`super(${superArgsStr});`);
1036
+ }
1037
+ for (const p of params) {
1038
+ if (p.propertyKind) {
1039
+ this.w.writeIndentedLine(`this.${p.name} = ${p.name};`);
1040
+ }
1041
+ }
1042
+ // Emit init{} blocks inside the constructor, in declaration order
1043
+ for (const ib of (initBlocks ?? [])) {
1044
+ this.emitBlock(ib.body);
1045
+ }
1046
+ this.w.popIndent();
1047
+ this.w.writeIndentedLine(`}`);
1048
+ this.w.writeLine();
1049
+ }
1050
+ emitCompanionObject(co) {
1051
+ // Emit companion object members as `static` members of the outer class so that
1052
+ // `MyClass.factoryMethod()` works directly (standard semantics).
1053
+ for (const member of co.body.members) {
1054
+ if (member.kind === "FunDecl") {
1055
+ const vis = this.visibilityPrefix(member.modifiers);
1056
+ const params = this.emitParamsStr(member.params);
1057
+ const retType = member.returnType && this.opts.emitTypes
1058
+ ? `: ${this.emitTypeRef(member.returnType)}` : "";
1059
+ const asyncKw = AST.isSuspend(member.modifiers) ? "async " : "";
1060
+ if (member.body) {
1061
+ if (member.body.kind === "Block") {
1062
+ this.w.writeIndentedLine(`${vis}static ${asyncKw}${member.name}(${params})${retType} {`);
1063
+ this.w.pushIndent();
1064
+ this.emitBlock(member.body);
1065
+ this.w.popIndent();
1066
+ this.w.writeIndentedLine(`}`);
1067
+ }
1068
+ else {
1069
+ this.w.writeIndentedLine(`${vis}static ${asyncKw}${member.name}(${params})${retType} { return ${this.emitExpr(member.body)}; }`);
1070
+ }
1071
+ }
1072
+ else {
1073
+ this.w.writeIndentedLine(`${vis}static abstract ${member.name}(${params})${retType};`);
1074
+ }
1075
+ }
1076
+ else if (member.kind === "PropertyDecl") {
1077
+ const vis = this.visibilityPrefix(member.modifiers);
1078
+ const kw = member.mutable ? "" : "readonly ";
1079
+ const typeAnn = member.type && this.opts.emitTypes ? `: ${this.emitTypeRef(member.type)}` : "";
1080
+ const init = member.initializer ? ` = ${this.emitExpr(member.initializer)}` : "";
1081
+ this.w.writeIndentedLine(`${vis}static ${kw}${member.name}${typeAnn}${init};`);
1082
+ }
1083
+ }
1084
+ }
1085
+ emitSecondaryConstructor(ctor) {
1086
+ const params = this.emitParamsStr(ctor.params);
1087
+ this.w.writeIndentedLine(`constructor(${params}) {`);
1088
+ this.w.pushIndent();
1089
+ if (ctor.delegation) {
1090
+ const args = ctor.delegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
1091
+ this.w.writeIndentedLine(`${ctor.delegation}(${args});`);
1092
+ }
1093
+ this.emitBlock(ctor.body);
1094
+ this.w.popIndent();
1095
+ this.w.writeIndentedLine(`}`);
1096
+ }
1097
+ // ── Statements ─────────────────────────────────────────────────────────────
1098
+ emitBlock(block) {
1099
+ for (const stmt of block.statements) {
1100
+ this.emitStmt(stmt);
1101
+ }
1102
+ }
1103
+ emitStmt(stmt) {
1104
+ switch (stmt.kind) {
1105
+ case "Block":
1106
+ this.w.writeIndentedLine(`{`);
1107
+ this.w.pushIndent();
1108
+ this.emitBlock(stmt);
1109
+ this.w.popIndent();
1110
+ this.w.writeIndentedLine(`}`);
1111
+ break;
1112
+ case "PropertyDecl":
1113
+ this.emitPropertyDecl(stmt, false, true);
1114
+ break;
1115
+ case "DestructuringDecl":
1116
+ this.emitDestructuringDecl(stmt, false);
1117
+ break;
1118
+ case "ExprStmt":
1119
+ this.w.writeIndentedLine(`${this.emitExpr(stmt.expr)};`);
1120
+ break;
1121
+ case "ReturnStmt": {
1122
+ const val = stmt.value ? ` ${this.emitExpr(stmt.value)}` : "";
1123
+ this.w.writeIndentedLine(`return${val};`);
1124
+ break;
1125
+ }
1126
+ case "ThrowStmt":
1127
+ this.w.writeIndentedLine(`throw ${this.emitExpr(stmt.value)};`);
1128
+ break;
1129
+ case "BreakStmt":
1130
+ this.w.writeIndentedLine(stmt.label ? `break ${stmt.label};` : `break;`);
1131
+ break;
1132
+ case "ContinueStmt":
1133
+ this.w.writeIndentedLine(stmt.label ? `continue ${stmt.label};` : `continue;`);
1134
+ break;
1135
+ case "IfStmt":
1136
+ this.emitIfStmt(stmt);
1137
+ break;
1138
+ case "WhenStmt":
1139
+ this.emitWhenStmt(stmt);
1140
+ break;
1141
+ case "ForStmt":
1142
+ this.emitForStmt(stmt);
1143
+ break;
1144
+ case "WhileStmt":
1145
+ this.w.writeIndentedLine(`while (${this.emitExpr(stmt.condition)}) {`);
1146
+ this.w.pushIndent();
1147
+ this.emitBlock(stmt.body);
1148
+ this.w.popIndent();
1149
+ this.w.writeIndentedLine(`}`);
1150
+ break;
1151
+ case "DoWhileStmt":
1152
+ this.w.writeIndentedLine(`do {`);
1153
+ this.w.pushIndent();
1154
+ this.emitBlock(stmt.body);
1155
+ this.w.popIndent();
1156
+ this.w.writeIndentedLine(`} while (${this.emitExpr(stmt.condition)});`);
1157
+ break;
1158
+ case "TryCatchStmt":
1159
+ this.emitTryCatch(stmt);
1160
+ break;
1161
+ case "LabeledStmt":
1162
+ this.w.writeIndentedLine(`${stmt.label}:`);
1163
+ this.emitStmt(stmt.body);
1164
+ break;
1165
+ }
1166
+ }
1167
+ emitIfStmt(stmt) {
1168
+ this.w.writeIndentedLine(`if (${this.emitExpr(stmt.condition)}) {`);
1169
+ this.w.pushIndent();
1170
+ this.emitBlock(stmt.then);
1171
+ this.w.popIndent();
1172
+ if (stmt.else) {
1173
+ if (stmt.else.kind === "IfStmt") {
1174
+ this.w.writeIndented(`} else `);
1175
+ this.emitIfStmt(stmt.else);
1176
+ }
1177
+ else {
1178
+ this.w.writeIndentedLine(`} else {`);
1179
+ this.w.pushIndent();
1180
+ this.emitBlock(stmt.else);
1181
+ this.w.popIndent();
1182
+ this.w.writeIndentedLine(`}`);
1183
+ }
1184
+ }
1185
+ else {
1186
+ this.w.writeIndentedLine(`}`);
1187
+ }
1188
+ }
1189
+ emitWhenStmt(stmt) {
1190
+ // when(subject) { is Foo -> ... else -> ... }
1191
+ // Compiles to a series of if/else-if chains
1192
+ const subjectVar = stmt.subject ? "__when_subject__" : null;
1193
+ if (stmt.subject) {
1194
+ const binding = stmt.subject.binding ?? subjectVar;
1195
+ this.w.writeIndentedLine(`const ${binding} = ${this.emitExpr(stmt.subject.expr)};`);
1196
+ }
1197
+ let first = true;
1198
+ for (const branch of stmt.branches) {
1199
+ if (branch.isElse) {
1200
+ this.w.writeIndentedLine(first ? `{` : `} else {`);
1201
+ }
1202
+ else {
1203
+ const cond = branch.conditions.map((c) => this.emitWhenCondition(c, subjectVar ?? "")).join(" || ");
1204
+ this.w.writeIndentedLine(first ? `if (${cond}) {` : `} else if (${cond}) {`);
1205
+ }
1206
+ first = false;
1207
+ this.w.pushIndent();
1208
+ if (branch.body.kind === "Block") {
1209
+ this.emitBlock(branch.body);
1210
+ }
1211
+ else {
1212
+ this.w.writeIndentedLine(`${this.emitExpr(branch.body)};`);
1213
+ }
1214
+ this.w.popIndent();
1215
+ }
1216
+ this.w.writeIndentedLine(`}`);
1217
+ }
1218
+ emitWhenCondition(cond, subject) {
1219
+ switch (cond.kind) {
1220
+ case "WhenIsCondition": {
1221
+ // Qualified name (e.g. UiState.Loading) → use __kind discriminant
1222
+ // Single name (e.g. BibiError) → use instanceof
1223
+ const isQualified = cond.type.kind === "SimpleTypeRef" && cond.type.name.length > 1;
1224
+ const check = isQualified
1225
+ ? `(${subject} as any).__kind === "${cond.type.kind === "SimpleTypeRef" ? cond.type.name[cond.type.name.length - 1] : ""}"`
1226
+ : `${subject} instanceof ${this.emitTypeRef(cond.type)}`;
1227
+ return cond.negated ? `!(${check})` : check;
1228
+ }
1229
+ case "WhenInCondition": {
1230
+ const check = `(${this.emitExpr(cond.expr)}).includes(${subject})`;
1231
+ return cond.negated ? `!(${check})` : check;
1232
+ }
1233
+ case "WhenExprCondition":
1234
+ return subject
1235
+ ? `${subject} === ${this.emitExpr(cond.expr)}`
1236
+ : this.emitExpr(cond.expr);
1237
+ }
1238
+ }
1239
+ emitForStmt(stmt) {
1240
+ const iter = this.emitExpr(stmt.iterable);
1241
+ if (typeof stmt.binding === "string") {
1242
+ this.w.writeIndentedLine(`for (const ${stmt.binding} of ${iter}) {`);
1243
+ }
1244
+ else if (stmt.binding.kind === "TupleDestructure") {
1245
+ const names = stmt.binding.names.map((n) => n ?? "_").join(", ");
1246
+ this.w.writeIndentedLine(`for (const [${names}] of ${iter}) {`);
1247
+ }
1248
+ else {
1249
+ const { key, value } = stmt.binding;
1250
+ // Use .entries() for JS Map (Map<K,V>), Object.entries() for plain objects
1251
+ const iterableType = this.typeMap.get(stmt.iterable);
1252
+ const isMap = iterableType?.tag === "class" && iterableType.name === "Map";
1253
+ const entries = isMap ? `${iter}.entries()` : `Object.entries(${iter})`;
1254
+ this.w.writeIndentedLine(`for (const [${key}, ${value}] of ${entries}) {`);
1255
+ }
1256
+ this.w.pushIndent();
1257
+ this.emitBlock(stmt.body);
1258
+ this.w.popIndent();
1259
+ this.w.writeIndentedLine(`}`);
1260
+ }
1261
+ emitTryCatch(stmt) {
1262
+ this.w.writeIndentedLine(`try {`);
1263
+ this.w.pushIndent();
1264
+ this.emitBlock(stmt.body);
1265
+ this.w.popIndent();
1266
+ for (const c of stmt.catches) {
1267
+ this.w.writeIndentedLine(`} catch (${c.name}: unknown) {`);
1268
+ this.w.pushIndent();
1269
+ // Narrow type
1270
+ if (this.opts.emitTypes) {
1271
+ this.w.writeIndentedLine(`if (!(${c.name} instanceof ${this.emitTypeRef(c.type)})) throw ${c.name};`);
1272
+ }
1273
+ this.emitBlock(c.body);
1274
+ this.w.popIndent();
1275
+ }
1276
+ if (stmt.finally) {
1277
+ this.w.writeIndentedLine(`} finally {`);
1278
+ this.w.pushIndent();
1279
+ this.emitBlock(stmt.finally);
1280
+ this.w.popIndent();
1281
+ }
1282
+ this.w.writeIndentedLine(`}`);
1283
+ }
1284
+ // ── Expressions ────────────────────────────────────────────────────────────
1285
+ emitExpr(expr) {
1286
+ switch (expr.kind) {
1287
+ case "IntLiteralExpr": return String(expr.value);
1288
+ case "LongLiteralExpr": return `BigInt(${expr.value})`;
1289
+ case "FloatLiteralExpr":
1290
+ case "DoubleLiteralExpr": return String(expr.value);
1291
+ case "BooleanLiteralExpr": return String(expr.value);
1292
+ case "NullLiteralExpr": return "null";
1293
+ case "StringLiteralExpr":
1294
+ return expr.raw
1295
+ ? `\`${expr.value}\``
1296
+ : JSON.stringify(expr.value);
1297
+ case "StringTemplateExpr":
1298
+ return this.emitStringTemplate(expr);
1299
+ case "NameExpr":
1300
+ // Ensure Int/Long companion objects are imported from the runtime
1301
+ if (expr.name === "Int" || expr.name === "Long") {
1302
+ this.runtimeSymbolsNeeded.add(expr.name);
1303
+ }
1304
+ // Unit singleton emits as undefined (matches Kotlin/JVM void semantics)
1305
+ if (expr.name === "Unit")
1306
+ return "undefined";
1307
+ return expr.name;
1308
+ case "ThisExpr": return "this";
1309
+ case "SuperExpr": return "super";
1310
+ case "ParenExpr": return `(${this.emitExpr(expr.expr)})`;
1311
+ case "UnaryExpr":
1312
+ return expr.prefix
1313
+ ? `${this.unaryOpStr(expr.op)}${this.emitExpr(expr.operand)}`
1314
+ : `${this.emitExpr(expr.operand)}${this.unaryOpStr(expr.op)}`;
1315
+ case "BinaryExpr": {
1316
+ const opMethod = this.operatorOverloadMap.get(expr);
1317
+ if (opMethod) {
1318
+ const l = this.emitExpr(expr.left);
1319
+ const r = this.emitExpr(expr.right);
1320
+ // `compareTo` overloads: wrap result in a comparison against 0
1321
+ if (opMethod === "compareTo") {
1322
+ const cmpOp = expr.op;
1323
+ return `(${l}.compareTo(${r}) ${cmpOp} 0)`;
1324
+ }
1325
+ // `contains` overloads: `element in collection` → `collection.contains(element)`
1326
+ if (opMethod === "contains") {
1327
+ return `${r}.contains(${l})`;
1328
+ }
1329
+ if (opMethod === "!contains") {
1330
+ return `!${r}.contains(${l})`;
1331
+ }
1332
+ // Generic operator overload: emit as method call `left.method(right)`
1333
+ return `${l}.${opMethod}(${r})`;
1334
+ }
1335
+ // `==` → structural equality, `!=` → negation
1336
+ if (expr.op === "==" || expr.op === "!=") {
1337
+ this.runtimeSymbolsNeeded.add("jalvinEquals");
1338
+ const eq = `jalvinEquals(${this.emitExpr(expr.left)}, ${this.emitExpr(expr.right)})`;
1339
+ return expr.op === "==" ? eq : `!${eq}`;
1340
+ }
1341
+ // `in` / `!in` without a user-defined operator: use JS `in` / array includes
1342
+ if (expr.op === "in") {
1343
+ return `${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)})`;
1344
+ }
1345
+ if (expr.op === "!in") {
1346
+ return `!(${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)}))`;
1347
+ }
1348
+ return `(${this.emitExpr(expr.left)} ${this.binaryOpStr(expr.op)} ${this.emitExpr(expr.right)})`;
1349
+ }
1350
+ case "AssignExpr":
1351
+ return `${this.emitExpr(expr.target)} = ${this.emitExpr(expr.value)}`;
1352
+ case "CompoundAssignExpr":
1353
+ return `${this.emitExpr(expr.target)} ${expr.op} ${this.emitExpr(expr.value)}`;
1354
+ case "IncrDecrExpr":
1355
+ return expr.prefix
1356
+ ? `${expr.op}${this.emitExpr(expr.target)}`
1357
+ : `${this.emitExpr(expr.target)}${expr.op}`;
1358
+ case "MemberExpr":
1359
+ return `${this.emitExpr(expr.target)}.${expr.member}`;
1360
+ case "SafeMemberExpr":
1361
+ return `${this.emitExpr(expr.target)}?.${expr.member}`;
1362
+ case "IndexExpr":
1363
+ return `${this.emitExpr(expr.target)}[${this.emitExpr(expr.index)}]`;
1364
+ case "NotNullExpr":
1365
+ // Runtime check
1366
+ this.runtimeSymbolsNeeded.add("notNull");
1367
+ return `notNull(${this.emitExpr(expr.expr)})`;
1368
+ case "SafeCallExpr": {
1369
+ // x?.() — safe invocation of a nullable function value
1370
+ const callee = this.emitExpr(expr.callee);
1371
+ const restArgs = [];
1372
+ for (const a of expr.args) {
1373
+ restArgs.push(a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1374
+ }
1375
+ if (expr.trailingLambda)
1376
+ restArgs.push(this.emitLambdaExpr(expr.trailingLambda));
1377
+ return `${callee}?.(${restArgs.join(", ")})`;
1378
+ }
1379
+ case "ElvisExpr": {
1380
+ // left ?? right (null coalescing)
1381
+ return `(${this.emitExpr(expr.left)} ?? ${this.emitExpr(expr.right)})`;
1382
+ }
1383
+ case "CallExpr":
1384
+ return this.emitCallExpr(expr);
1385
+ case "LambdaExpr":
1386
+ return this.emitLambdaExpr(expr);
1387
+ case "IfExpr":
1388
+ return this.emitIfExpr(expr);
1389
+ case "WhenExpr":
1390
+ return this.emitWhenExpr(expr);
1391
+ case "TryCatchExpr":
1392
+ return this.emitTryCatchExpr(expr);
1393
+ case "TypeCheckExpr":
1394
+ return `${expr.negated ? "!(" : ""}${this.emitExpr(expr.expr)} instanceof ${this.emitTypeRef(expr.type)}${expr.negated ? ")" : ""}`;
1395
+ case "TypeCastExpr":
1396
+ // Unsafe cast — emit as `(expr as Type)` which is stripped at runtime
1397
+ return `(${this.emitExpr(expr.expr)} as unknown as ${this.emitTypeRef(expr.type)})`;
1398
+ case "SafeCastExpr": {
1399
+ this.runtimeSymbolsNeeded.add("safeCast");
1400
+ return `safeCast(${this.emitExpr(expr.expr)}, ${this.emitTypeRef(expr.type)})`;
1401
+ }
1402
+ case "RangeExpr": {
1403
+ this.runtimeSymbolsNeeded.add("range");
1404
+ return `range(${this.emitExpr(expr.from)}, ${this.emitExpr(expr.to)}, ${expr.inclusive})`;
1405
+ }
1406
+ case "LaunchExpr": {
1407
+ // fire-and-forget → void IIFE
1408
+ const stmts = this.captureBlock(expr.body);
1409
+ return `(async () => { ${stmts} })()`;
1410
+ }
1411
+ case "AsyncExpr": {
1412
+ // returns Promise<T>
1413
+ const stmts = this.captureBlock(expr.body);
1414
+ return `(async () => { ${stmts} })()`;
1415
+ }
1416
+ case "CollectionLiteralExpr":
1417
+ return this.emitCollectionLiteral(expr);
1418
+ case "ObjectExpr":
1419
+ return this.emitObjectExpr(expr);
1420
+ case "ReturnExpr": {
1421
+ const val = expr.value ? ` ${this.emitExpr(expr.value)}` : "";
1422
+ return `((() => { return${val}; })())`;
1423
+ }
1424
+ case "BreakExpr": return "undefined /* break */";
1425
+ case "ContinueExpr": return "undefined /* continue */";
1426
+ case "JsxElement":
1427
+ return this.emitJsxElement(expr);
1428
+ default:
1429
+ return "undefined";
1430
+ }
1431
+ }
1432
+ // ── JSX ──────────────────────────────────────────────────────────────────
1433
+ emitJsxElement(expr) {
1434
+ const attrsStr = expr.attrs.length > 0
1435
+ ? " " + expr.attrs.map((a) => this.emitJsxAttr(a)).join(" ")
1436
+ : "";
1437
+ if (expr.children.length === 0) {
1438
+ return `<${expr.tag}${attrsStr} />`;
1439
+ }
1440
+ const children = expr.children.map((c) => this.emitJsxChild(c)).join("");
1441
+ return `<${expr.tag}${attrsStr}>${children}</${expr.tag}>`;
1442
+ }
1443
+ emitJsxAttr(attr) {
1444
+ // Map HTML attribute names to React prop names
1445
+ const name = attr.name === "class" ? "className"
1446
+ : attr.name === "for" ? "htmlFor"
1447
+ : attr.name;
1448
+ if (attr.value === null)
1449
+ return name;
1450
+ if (typeof attr.value === "string")
1451
+ return `${name}="${attr.value}"`;
1452
+ return `${name}={${this.emitExpr(attr.value)}}`;
1453
+ }
1454
+ emitJsxChild(child) {
1455
+ switch (child.kind) {
1456
+ case "JsxElement": return this.emitJsxElement(child);
1457
+ case "JsxExprChild": return `{${this.emitExpr(child.expr)}}`;
1458
+ case "JsxTextChild": return child.text;
1459
+ }
1460
+ }
1461
+ emitStringTemplate(expr) {
1462
+ const inner = expr.parts.map((p) => {
1463
+ if (p.kind === "LiteralPart") {
1464
+ return p.value.replace(/`/g, "\\`").replace(/\\/g, "\\\\");
1465
+ }
1466
+ return `\${${this.emitExpr(p.expr)}}`;
1467
+ }).join("");
1468
+ return `\`${inner}\``;
1469
+ }
1470
+ emitCallExpr(expr) {
1471
+ // Rewrite infix numeric extension calls that JS numbers don't natively have:
1472
+ // `a.downTo(b)` → `downTo(a, b)` `a.until(b)` → `range(a, b, false)`
1473
+ // `range.step(n)` → `step(range, n)`
1474
+ if (expr.callee.kind === "MemberExpr" &&
1475
+ (expr.callee.member === "downTo" || expr.callee.member === "until" || expr.callee.member === "step")) {
1476
+ const obj = this.emitExpr(expr.callee.target);
1477
+ const arg = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "0";
1478
+ if (expr.callee.member === "downTo") {
1479
+ this.runtimeSymbolsNeeded.add("downTo");
1480
+ return `downTo(${obj}, ${arg})`;
1481
+ }
1482
+ if (expr.callee.member === "until") {
1483
+ this.runtimeSymbolsNeeded.add("range");
1484
+ return `range(${obj}, ${arg}, false)`;
1485
+ }
1486
+ if (expr.callee.member === "step") {
1487
+ this.runtimeSymbolsNeeded.add("step");
1488
+ return `step(${obj}, ${arg})`;
1489
+ }
1490
+ }
1491
+ // Rewrite calls on primitive-receiver extension functions.
1492
+ // `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
1493
+ if (expr.callee.kind === "MemberExpr") {
1494
+ const scopeMember = expr.callee.member;
1495
+ // Scope function call rewriting: `x.let { ... }` → `let_(x, ...)`
1496
+ // `x.apply { ... }` → `apply(x, function(this:any) { ... })`
1497
+ if (scopeMember === "let" || scopeMember === "also" || scopeMember === "apply" || scopeMember === "run" || scopeMember === "takeIf" || scopeMember === "takeUnless") {
1498
+ const receiver = this.emitExpr(expr.callee.target);
1499
+ const lambda = expr.trailingLambda ??
1500
+ (expr.args.length === 1 && expr.args[0].value.kind === "LambdaExpr"
1501
+ ? expr.args[0].value
1502
+ : null);
1503
+ if (lambda) {
1504
+ if (scopeMember === "let") {
1505
+ this.runtimeSymbolsNeeded.add("let_");
1506
+ return `let_(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1507
+ }
1508
+ if (scopeMember === "also") {
1509
+ this.runtimeSymbolsNeeded.add("also");
1510
+ return `also(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1511
+ }
1512
+ if (scopeMember === "apply") {
1513
+ this.runtimeSymbolsNeeded.add("apply");
1514
+ const body = this.captureBlockStatements(lambda.body);
1515
+ return `apply(${receiver}, function(this: any) { ${body} })`;
1516
+ }
1517
+ if (scopeMember === "run") {
1518
+ this.runtimeSymbolsNeeded.add("run_");
1519
+ const body = this.captureBlockStatements(lambda.body);
1520
+ return `run_(${receiver}, function(this: any) { ${body} })`;
1521
+ }
1522
+ if (scopeMember === "takeIf") {
1523
+ this.runtimeSymbolsNeeded.add("takeIf");
1524
+ return `takeIf(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1525
+ }
1526
+ if (scopeMember === "takeUnless") {
1527
+ this.runtimeSymbolsNeeded.add("takeUnless");
1528
+ return `takeUnless(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1529
+ }
1530
+ }
1531
+ }
1532
+ // `with(x) { ... }` is a free function handled via seedBuiltins; but rewrite if emitted as member
1533
+ }
1534
+ // Rewrite calls on primitive-receiver extension functions.
1535
+ // `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
1536
+ if (expr.callee.kind === "MemberExpr") {
1537
+ const receiverType = this.typeMap.get(expr.callee.target);
1538
+ const memberName = expr.callee.member;
1539
+ if (receiverType) {
1540
+ const tsTypeName = this.jTypeToReceiverName(receiverType);
1541
+ if (tsTypeName) {
1542
+ const extMap = this.primitiveExtensions.get(tsTypeName);
1543
+ if (extMap?.has(memberName)) {
1544
+ const fnName = extMap.get(memberName);
1545
+ const receiver = this.emitExpr(expr.callee.target);
1546
+ const restArgs = expr.args.map((a) => a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1547
+ if (expr.trailingLambda)
1548
+ restArgs.push(this.emitLambdaExpr(expr.trailingLambda));
1549
+ return `${fnName}(${[receiver, ...restArgs].join(", ")})`;
1550
+ }
1551
+ }
1552
+ }
1553
+ }
1554
+ const callee = this.emitExpr(expr.callee);
1555
+ const typeArgs = expr.typeArgs.length > 0
1556
+ ? `<${expr.typeArgs.map((t) => t.star ? "*" : this.emitTypeRef(t.type)).join(", ")}>`
1557
+ : "";
1558
+ // Rewrite top-level `with(obj) { ... }` → `with_(obj, function(this: any) { ... })`
1559
+ if (expr.callee.kind === "NameExpr" && expr.callee.name === "with" &&
1560
+ (expr.trailingLambda || (expr.args.length === 2 && expr.args[1].value.kind === "LambdaExpr"))) {
1561
+ const obj = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "undefined";
1562
+ const lambda = expr.trailingLambda ??
1563
+ expr.args[1].value;
1564
+ this.runtimeSymbolsNeeded.add("with_");
1565
+ const body = this.captureBlockStatements(lambda.body);
1566
+ return `with_(${obj}, function(this: any) { ${body} })`;
1567
+ }
1568
+ // Rewrite top-level `run { ... }` (no receiver) — just call the lambda
1569
+ if (expr.callee.kind === "NameExpr" && expr.callee.name === "run" &&
1570
+ expr.args.length === 0 && expr.trailingLambda) {
1571
+ return `(${this.emitLambdaExpr(expr.trailingLambda)})()`;
1572
+ }
1573
+ // Handle named arguments by reordering them to match positional parameters
1574
+ const calleeType = this.typeMap.get(expr.callee);
1575
+ // Compose-style component call: Column(modifier = ...) { ... } → Column({ modifier: ... }, [children])
1576
+ // Also catches star-imported @jalvin/ui primitives (Row, Button, etc.) whose type is T_UNKNOWN
1577
+ if (expr.callee.kind === "NameExpr" && (this.componentNames.has(expr.callee.name) ||
1578
+ (this.hasUiStarImport && (!calleeType || calleeType.tag === "unknown") && /^[A-Z]/.test(expr.callee.name)))) {
1579
+ return this.emitComposeCallAsDom(expr);
1580
+ }
1581
+ let finalArgs = [];
1582
+ if (calleeType && calleeType.tag === "func" && calleeType.paramNames) {
1583
+ const paramNames = calleeType.paramNames;
1584
+ const argsByName = new Map();
1585
+ const positionalArgs = [];
1586
+ for (const arg of expr.args) {
1587
+ if (arg.name) {
1588
+ argsByName.set(arg.name, arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
1589
+ }
1590
+ else {
1591
+ positionalArgs.push(arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
1592
+ }
1593
+ }
1594
+ // Reconstruct args based on paramNames
1595
+ for (let i = 0; i < paramNames.length; i++) {
1596
+ const name = paramNames[i];
1597
+ if (argsByName.has(name)) {
1598
+ finalArgs.push(argsByName.get(name));
1599
+ }
1600
+ else if (i < positionalArgs.length) {
1601
+ finalArgs.push(positionalArgs[i]);
1602
+ }
1603
+ else {
1604
+ // Missing argument - JS default value logic will handle it if we pass undefined
1605
+ finalArgs.push("undefined");
1606
+ }
1607
+ }
1608
+ // Handle trailing positional args if any
1609
+ if (positionalArgs.length > paramNames.length) {
1610
+ finalArgs.push(...positionalArgs.slice(paramNames.length));
1611
+ }
1612
+ }
1613
+ else {
1614
+ // Fallback for types we don't know param names for (like constructors or any)
1615
+ finalArgs = expr.args.map((a) => {
1616
+ return a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value);
1617
+ });
1618
+ }
1619
+ if (expr.trailingLambda) {
1620
+ finalArgs.push(this.emitLambdaExpr(expr.trailingLambda));
1621
+ }
1622
+ // If the callee is a class instance (not a constructor / function), emit `.invoke(args)`
1623
+ if (calleeType &&
1624
+ calleeType.tag === "class" &&
1625
+ calleeType.decl &&
1626
+ this.classHasInvokeOperator(calleeType.decl)) {
1627
+ return `${callee}.invoke(${finalArgs.join(", ")})`;
1628
+ }
1629
+ // Constructor calls: class names start with uppercase by convention
1630
+ if (this.isConstructorCall(expr.callee)) {
1631
+ return `new ${callee}${typeArgs}(${finalArgs.join(", ")})`;
1632
+ }
1633
+ return `${callee}${typeArgs}(${finalArgs.join(", ")})`;
1634
+ }
1635
+ /**
1636
+ * For each non-scoped local import (e.g. `import src.views.MoveCounterView.MoveCounter`),
1637
+ * attempt to load and parse the corresponding .jalvin source file. If the named symbol is
1638
+ * a `component fun`, register it in `componentNames` and record its param names so that
1639
+ * call sites can emit the correct props-object invocation.
1640
+ */
1641
+ resolveLocalComponentImports(program, sourceRoot) {
1642
+ for (const imp of program.imports) {
1643
+ // Skip scoped packages (@org/pkg) and star imports
1644
+ if (imp.star || imp.path[0]?.startsWith("@"))
1645
+ continue;
1646
+ // Need at least 2 path segments: the last is the symbol, the rest form the file path
1647
+ if (imp.path.length < 2)
1648
+ continue;
1649
+ const rawSymbolName = imp.path[imp.path.length - 1];
1650
+ const symbolName = imp.alias ?? rawSymbolName;
1651
+ const precedingParts = imp.path.slice(0, -1);
1652
+ const candidatePath = nodePath.join(sourceRoot, precedingParts.join("/") + ".jalvin");
1653
+ if (!fs.existsSync(candidatePath))
1654
+ continue;
1655
+ try {
1656
+ const source = fs.readFileSync(candidatePath, "utf8");
1657
+ const diag = new diagnostics_js_1.DiagnosticBag();
1658
+ const tokens = (0, lexer_js_1.lex)(source, candidatePath, diag);
1659
+ if (diag.hasErrors)
1660
+ continue;
1661
+ const ast = (0, parser_js_1.parse)(tokens, candidatePath, diag, source);
1662
+ if (diag.hasErrors)
1663
+ continue;
1664
+ // Look for a top-level ComponentDecl matching the imported symbol name
1665
+ const compDecl = ast.declarations.find((d) => d.kind === "ComponentDecl" && d.name === rawSymbolName);
1666
+ if (compDecl) {
1667
+ this.componentNames.add(symbolName);
1668
+ this.componentParamNames.set(symbolName, compDecl.params.map((p) => p.name));
1669
+ this.hasComponents = true;
1670
+ }
1671
+ }
1672
+ catch {
1673
+ // Silently skip if the file cannot be read or parsed
1674
+ }
1675
+ }
1676
+ }
1677
+ /** Returns true if a call expression's callee looks like a class constructor.
1678
+ * In Jalvin, class names always start with an uppercase letter. */
1679
+ isConstructorCall(calleeExpr) {
1680
+ if (calleeExpr.kind === "NameExpr") {
1681
+ // Names in componentNames are factory functions (UI primitives, components), not constructors
1682
+ if (this.componentNames.has(calleeExpr.name))
1683
+ return false;
1684
+ // Class names start with uppercase; function names start with lowercase
1685
+ return /^[A-Z]/.test(calleeExpr.name);
1686
+ }
1687
+ if (calleeExpr.kind === "MemberExpr") {
1688
+ // e.g. UiState.Success(data), Discount.Percentage(0.1)
1689
+ return /^[A-Z]/.test(calleeExpr.member);
1690
+ }
1691
+ return false;
1692
+ }
1693
+ // ── Compose-style component call → DOM ──────────────────────────────────────
1694
+ /** Emit `Column(modifier = ...) { ... }` as `Column({ modifier: ... }, [children])` */
1695
+ emitComposeCallAsDom(expr) {
1696
+ const tag = expr.callee.name;
1697
+ const calleeType = this.typeMap.get(expr.callee);
1698
+ // Param names from type checker (for same-file components) or resolved from imported files
1699
+ const paramNames = (calleeType?.tag === "func" ? calleeType.paramNames : undefined) ??
1700
+ this.componentParamNames.get(tag);
1701
+ // Build props object from named/positional args
1702
+ const props = [];
1703
+ for (let i = 0; i < expr.args.length; i++) {
1704
+ const arg = expr.args[i];
1705
+ // Prop name: explicit named arg > known param name > NameExpr variable name (shorthand)
1706
+ const propName = arg.name ??
1707
+ paramNames?.[i] ??
1708
+ (arg.value.kind === "NameExpr" ? arg.value.name : undefined);
1709
+ if (!propName)
1710
+ continue;
1711
+ const val = this.emitExpr(arg.value);
1712
+ // Use JS shorthand `{ key }` when key and value are the same identifier
1713
+ props.push(propName === val ? propName : `${propName}: ${val}`);
1714
+ }
1715
+ const propsStr = props.length > 0 ? `{ ${props.join(", ")} }` : "{}";
1716
+ if (expr.trailingLambda) {
1717
+ const children = this.emitLambdaBodyAsDomChildren(expr.trailingLambda);
1718
+ return children ? `${tag}(${propsStr}, [${children}])` : `${tag}(${propsStr})`;
1719
+ }
1720
+ return `${tag}(${propsStr})`;
1721
+ }
1722
+ /** Collect each expression statement in a trailing-lambda body as DOM children. */
1723
+ emitLambdaBodyAsDomChildren(lambda) {
1724
+ return lambda.body
1725
+ .filter((stmt) => stmt.kind === "ExprStmt")
1726
+ .map((stmt) => this.emitExpr(stmt.expr))
1727
+ .join(", ");
1728
+ }
1729
+ emitLambdaExpr(expr) {
1730
+ // If no explicit params, emit `it` as the single implicit parameter
1731
+ // (Convention for single-argument lambdas)
1732
+ const params = expr.params.length === 0
1733
+ ? "it"
1734
+ : expr.params.map((p) => p.name ?? "_").join(", ");
1735
+ const body = expr.body;
1736
+ if (body.length === 1 && body[0].kind === "ExprStmt") {
1737
+ return `(${params}) => ${this.emitExpr(body[0].expr)}`;
1738
+ }
1739
+ return `(${params}) => { ${body.map((s) => this.captureStmt(s)).join(" ")} }`;
1740
+ }
1741
+ emitIfExpr(expr) {
1742
+ const cond = this.emitExpr(expr.condition);
1743
+ const thenStr = expr.then.kind === "Block"
1744
+ ? `(() => { ${this.captureBlockStatements(expr.then.statements)} })()`
1745
+ : this.emitExpr(expr.then);
1746
+ const elseExpr = expr.else;
1747
+ const elseStr = elseExpr.kind === "Block"
1748
+ ? `(() => { ${this.captureBlockStatements(elseExpr.statements)} })()`
1749
+ : elseExpr.kind === "IfExpr"
1750
+ ? this.emitIfExpr(elseExpr)
1751
+ : this.emitExpr(elseExpr);
1752
+ return `(${cond} ? ${thenStr} : ${elseStr})`;
1753
+ }
1754
+ emitWhenExpr(expr) {
1755
+ // Compile as an IIFE with if/else chain
1756
+ const parts = [];
1757
+ const subject = expr.subject ? `const __s = ${this.emitExpr(expr.subject.expr)};` : "";
1758
+ const subjectRef = expr.subject ? (expr.subject.binding ?? "__s") : "";
1759
+ for (const branch of expr.branches) {
1760
+ if (branch.isElse) {
1761
+ const body = branch.body.kind === "Block"
1762
+ ? this.captureBlock(branch.body)
1763
+ : `return ${this.emitExpr(branch.body)};`;
1764
+ parts.push(`{ ${body} }`);
1765
+ }
1766
+ else {
1767
+ const cond = branch.conditions.map((c) => this.emitWhenCondition(c, subjectRef)).join(" || ");
1768
+ const body = branch.body.kind === "Block"
1769
+ ? this.captureBlock(branch.body)
1770
+ : `return ${this.emitExpr(branch.body)};`;
1771
+ parts.push(`if (${cond}) { ${body} }`);
1772
+ }
1773
+ }
1774
+ return `(() => { ${subject} ${parts.join(" else ")} })()`;
1775
+ }
1776
+ emitTryCatchExpr(expr) {
1777
+ const body = this.captureBlock(expr.body);
1778
+ const catches = expr.catches.map((c) => {
1779
+ const cb = this.captureBlock(c.body);
1780
+ return `catch (${c.name}) { ${cb} }`;
1781
+ }).join(" ");
1782
+ const fin = expr.finally ? `finally { ${this.captureBlock(expr.finally)} }` : "";
1783
+ return `(() => { try { ${body} } ${catches} ${fin} })()`;
1784
+ }
1785
+ emitCollectionLiteral(expr) {
1786
+ if (expr.collectionKind === "map") {
1787
+ const entries = expr.elements.map((e) => {
1788
+ if ("kind" in e && e.kind === "MapEntry") {
1789
+ return `[${this.emitExpr(e.key)}, ${this.emitExpr(e.value)}]`;
1790
+ }
1791
+ return "null";
1792
+ });
1793
+ return `new Map([${entries.join(", ")}])`;
1794
+ }
1795
+ if (expr.collectionKind === "set") {
1796
+ const items = expr.elements.map((e) => this.emitExpr(e));
1797
+ return `new Set([${items.join(", ")}])`;
1798
+ }
1799
+ const items = expr.elements.map((e) => this.emitExpr(e));
1800
+ return `[${items.join(", ")}]`;
1801
+ }
1802
+ emitObjectExpr(expr) {
1803
+ const superType = expr.superTypes[0];
1804
+ const ext = superType ? ` extends ${this.emitTypeRef(superType.type)}` : "";
1805
+ const members = expr.body.members.map((m) => this.captureClassMember(m)).join(" ");
1806
+ return `(new (class${ext} { ${members} })())`;
1807
+ }
1808
+ // ── capture helpers (emit to temp string) ─────────────────────────────────
1809
+ captureBlock(block) {
1810
+ return block.statements.map((s) => this.captureStmt(s)).join(" ");
1811
+ }
1812
+ captureBlockStatements(stmts) {
1813
+ return stmts.map((s) => this.captureStmt(s)).join(" ");
1814
+ }
1815
+ captureStmt(stmt) {
1816
+ const saved = this.w;
1817
+ const tmp = new Writer();
1818
+ this.w = tmp;
1819
+ this.emitStmt(stmt);
1820
+ this.w = saved;
1821
+ return tmp.output.trim();
1822
+ }
1823
+ captureClassMember(member) {
1824
+ const saved = this.w;
1825
+ const tmp = new Writer();
1826
+ this.w = tmp;
1827
+ this.emitClassMember(member);
1828
+ this.w = saved;
1829
+ return tmp.output.trim();
1830
+ }
1831
+ // ── Type reference emission ────────────────────────────────────────────────
1832
+ emitTypeRef(ref) {
1833
+ switch (ref.kind) {
1834
+ case "SimpleTypeRef": {
1835
+ const name = ref.name.join(".");
1836
+ return PRIMITIVE_TYPE_MAP[name] ?? name;
1837
+ }
1838
+ case "NullableTypeRef":
1839
+ return `${this.emitTypeRef(ref.base)} | null | undefined`;
1840
+ case "GenericTypeRef": {
1841
+ const base = ref.base.name.join(".");
1842
+ const mapped = GENERIC_TYPE_MAP[base] ?? base;
1843
+ const args = ref.args.map((a) => a.star ? "any" : a.type ? this.emitTypeRef(a.type) : "unknown");
1844
+ return `${mapped}<${args.join(", ")}>`;
1845
+ }
1846
+ case "FunctionTypeRef": {
1847
+ const params = ref.params.map((p, i) => `p${i}: ${this.emitTypeRef(p)}`).join(", ");
1848
+ const ret = this.emitTypeRef(ref.returnType);
1849
+ return `(${params}) => ${ret}`;
1850
+ }
1851
+ case "StarProjection":
1852
+ return "any";
1853
+ }
1854
+ }
1855
+ // ── Utility helpers ────────────────────────────────────────────────────────
1856
+ emitTypeParamsStr(params) {
1857
+ if (params.length === 0)
1858
+ return "";
1859
+ const ps = params.map((p) => {
1860
+ const bound = p.upperBound ? ` extends ${this.emitTypeRef(p.upperBound)}` : "";
1861
+ return `${p.name}${bound}`;
1862
+ });
1863
+ return `<${ps.join(", ")}>`;
1864
+ }
1865
+ emitParamsStr(params) {
1866
+ return params.map((p) => {
1867
+ const spread = p.vararg ? "..." : "";
1868
+ // vararg params are rest params in TS: `...name: T[]`
1869
+ const type = this.opts.emitTypes
1870
+ ? `: ${this.emitTypeRef(p.type)}${p.vararg ? "[]" : ""}`
1871
+ : "";
1872
+ const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
1873
+ return `${spread}${p.name}${type}${def}`;
1874
+ }).join(", ");
1875
+ }
1876
+ emitComponentPropsStr(params) {
1877
+ if (params.length === 0)
1878
+ return "";
1879
+ return params.map((p) => {
1880
+ const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
1881
+ return `${p.name}${type}`;
1882
+ }).join(", ");
1883
+ }
1884
+ emitSuperTypesStr(superTypes) {
1885
+ if (superTypes.length === 0)
1886
+ return "";
1887
+ const parts = [];
1888
+ let first = true;
1889
+ for (const s of superTypes) {
1890
+ // TypeScript does NOT allow constructor arguments in the `extends` clause.
1891
+ // Delegation args are passed via `super(args)` inside the constructor.
1892
+ if (first) {
1893
+ parts.push(` extends ${this.emitTypeRef(s.type)}`);
1894
+ first = false;
1895
+ }
1896
+ else {
1897
+ parts.push(` implements ${this.emitTypeRef(s.type)}`);
1898
+ }
1899
+ }
1900
+ return parts.join("");
1901
+ }
1902
+ exportPrefix(mods) {
1903
+ if (mods.visibility === "private" || mods.visibility === "internal")
1904
+ return "";
1905
+ return "export ";
1906
+ }
1907
+ visibilityPrefix(mods) {
1908
+ switch (mods.visibility) {
1909
+ case "private": return "private ";
1910
+ case "protected": return "protected ";
1911
+ case "internal": return "/* internal */ ";
1912
+ default: return "";
1913
+ }
1914
+ }
1915
+ unaryOpStr(op) {
1916
+ if (op === "not")
1917
+ return "!";
1918
+ return op;
1919
+ }
1920
+ binaryOpStr(op) {
1921
+ switch (op) {
1922
+ case "and": return "&&";
1923
+ case "or": return "||";
1924
+ case "xor": return "^";
1925
+ case "shl": return "<<";
1926
+ case "shr": return ">>";
1927
+ case "ushr": return ">>>";
1928
+ // === / !== are JS reference equality (Jalvin's triple-equals)
1929
+ case "===": return "===";
1930
+ case "!==": return "!==";
1931
+ case "..": return "/* .. */ +"; // handled by range()
1932
+ case "..<": return "/* ..< */ +";
1933
+ default: return op;
1934
+ }
1935
+ }
1936
+ // ── AST name walkers (for wildcard import resolution) ─────────────────────
1937
+ /**
1938
+ * Walk the entire program AST and collect all identifier names that are
1939
+ * REFERENCED (i.e., used) in the code: NameExpr values and the first name
1940
+ * component of SimpleTypeRef / GenericTypeRef nodes.
1941
+ *
1942
+ * Jalvin primitive type names (String, Boolean, …) that map to TS primitives
1943
+ * are excluded from TypeRef collection since they never need an import.
1944
+ */
1945
+ gatherReferencedNames(program) {
1946
+ const names = new Set();
1947
+ const visited = new WeakSet();
1948
+ const walk = (val) => {
1949
+ if (!val || typeof val !== "object")
1950
+ return;
1951
+ if (Array.isArray(val)) {
1952
+ for (const item of val)
1953
+ walk(item);
1954
+ return;
1955
+ }
1956
+ const obj = val;
1957
+ if (visited.has(obj))
1958
+ return;
1959
+ visited.add(obj);
1960
+ const kind = obj["kind"];
1961
+ if (kind === "NameExpr") {
1962
+ const name = obj["name"];
1963
+ if (typeof name === "string")
1964
+ names.add(name);
1965
+ return; // leaf node
1966
+ }
1967
+ if (kind === "SimpleTypeRef") {
1968
+ const nameArr = obj["name"];
1969
+ if (Array.isArray(nameArr) && nameArr.length > 0) {
1970
+ const first = nameArr[0];
1971
+ // Skip Jalvin primitive type names — they're erased to TS primitives.
1972
+ if (!(first in PRIMITIVE_TYPE_MAP))
1973
+ names.add(first);
1974
+ }
1975
+ return;
1976
+ }
1977
+ if (kind === "GenericTypeRef") {
1978
+ const base = obj["base"];
1979
+ const nameArr = base?.["name"];
1980
+ if (Array.isArray(nameArr) && nameArr.length > 0) {
1981
+ const first = nameArr[0];
1982
+ if (!(first in PRIMITIVE_TYPE_MAP))
1983
+ names.add(first);
1984
+ }
1985
+ // Fall through to recurse into type arguments
1986
+ }
1987
+ for (const propVal of Object.values(obj)) {
1988
+ walk(propVal);
1989
+ }
1990
+ };
1991
+ for (const decl of program.declarations)
1992
+ walk(decl);
1993
+ return names;
1994
+ }
1995
+ /**
1996
+ * Walk the entire program AST and collect all names that are LOCALLY DEFINED:
1997
+ * top-level declaration names, non-star import aliases, function/lambda
1998
+ * parameters, local val/var bindings, for-loop variables, catch-clause names,
1999
+ * when-subject bindings, and type parameter names.
2000
+ */
2001
+ gatherAllLocalBindings(program) {
2002
+ const names = new Set();
2003
+ const visited = new WeakSet();
2004
+ // Top-level declaration names
2005
+ for (const decl of program.declarations) {
2006
+ const d = decl;
2007
+ if (typeof d.name === "string" && d.name)
2008
+ names.add(d.name);
2009
+ }
2010
+ // Non-star import aliases
2011
+ for (const imp of program.imports) {
2012
+ if (!imp.star) {
2013
+ const name = imp.alias ?? imp.path[imp.path.length - 1];
2014
+ if (name)
2015
+ names.add(name);
2016
+ }
2017
+ }
2018
+ const walk = (val) => {
2019
+ if (!val || typeof val !== "object")
2020
+ return;
2021
+ if (Array.isArray(val)) {
2022
+ for (const item of val)
2023
+ walk(item);
2024
+ return;
2025
+ }
2026
+ const obj = val;
2027
+ if (visited.has(obj))
2028
+ return;
2029
+ visited.add(obj);
2030
+ const kind = obj["kind"];
2031
+ // Collect bound names at their declaration sites
2032
+ if (kind === "PropertyDecl" || kind === "DestructuringDecl") {
2033
+ if (typeof obj["name"] === "string")
2034
+ names.add(obj["name"]);
2035
+ const nms = obj["names"];
2036
+ if (Array.isArray(nms)) {
2037
+ for (const n of nms) {
2038
+ if (typeof n === "string")
2039
+ names.add(n);
2040
+ }
2041
+ }
2042
+ }
2043
+ if (kind === "FunDecl" || kind === "ExtensionFunDecl" ||
2044
+ kind === "ComponentDecl" || kind === "ClassDecl" ||
2045
+ kind === "DataClassDecl" || kind === "SealedClassDecl" ||
2046
+ kind === "EnumClassDecl" || kind === "InterfaceDecl" ||
2047
+ kind === "ObjectDecl" || kind === "TypeAliasDecl") {
2048
+ if (typeof obj["name"] === "string" && obj["name"])
2049
+ names.add(obj["name"]);
2050
+ // Type parameters
2051
+ const typeParams = obj["typeParams"];
2052
+ if (Array.isArray(typeParams)) {
2053
+ for (const tp of typeParams) {
2054
+ if (tp.name)
2055
+ names.add(tp.name);
2056
+ }
2057
+ }
2058
+ // Function/method parameters
2059
+ const params = obj["params"];
2060
+ if (Array.isArray(params)) {
2061
+ for (const p of params) {
2062
+ if (p.name)
2063
+ names.add(p.name);
2064
+ }
2065
+ }
2066
+ }
2067
+ if (kind === "LambdaExpr") {
2068
+ const params = obj["params"];
2069
+ if (Array.isArray(params)) {
2070
+ for (const p of params) {
2071
+ if (p.name)
2072
+ names.add(p.name);
2073
+ }
2074
+ }
2075
+ }
2076
+ if (kind === "ForStmt") {
2077
+ const binding = obj["binding"];
2078
+ if (typeof binding === "string") {
2079
+ names.add(binding);
2080
+ }
2081
+ else if (binding && typeof binding === "object") {
2082
+ const b = binding;
2083
+ // TupleDestructure or MapDestructure
2084
+ if (Array.isArray(b["names"])) {
2085
+ for (const n of b["names"]) {
2086
+ if (typeof n === "string")
2087
+ names.add(n);
2088
+ }
2089
+ }
2090
+ if (typeof b["key"] === "string")
2091
+ names.add(b["key"]);
2092
+ if (typeof b["value"] === "string")
2093
+ names.add(b["value"]);
2094
+ }
2095
+ }
2096
+ if (kind === "TryCatchStmt") {
2097
+ const catches = obj["catches"];
2098
+ if (Array.isArray(catches)) {
2099
+ for (const c of catches) {
2100
+ if (c.name)
2101
+ names.add(c.name);
2102
+ }
2103
+ }
2104
+ }
2105
+ if (kind === "WhenStmt" || kind === "WhenExpr") {
2106
+ const subject = obj["subject"];
2107
+ if (typeof subject?.["binding"] === "string")
2108
+ names.add(subject["binding"]);
2109
+ }
2110
+ if (kind === "SecondaryConstructor") {
2111
+ const params = obj["params"];
2112
+ if (Array.isArray(params)) {
2113
+ for (const p of params) {
2114
+ if (p.name)
2115
+ names.add(p.name);
2116
+ }
2117
+ }
2118
+ }
2119
+ for (const propVal of Object.values(obj)) {
2120
+ walk(propVal);
2121
+ }
2122
+ };
2123
+ for (const decl of program.declarations)
2124
+ walk(decl);
2125
+ return names;
2126
+ }
2127
+ }
2128
+ exports.CodeGenerator = CodeGenerator;
2129
+ // ---------------------------------------------------------------------------
2130
+ // Type name mappings (Jalvin → TypeScript)
2131
+ // ---------------------------------------------------------------------------
2132
+ const PRIMITIVE_TYPE_MAP = {
2133
+ Int: "number",
2134
+ Long: "bigint",
2135
+ Float: "number",
2136
+ Double: "number",
2137
+ Boolean: "boolean",
2138
+ String: "string",
2139
+ Char: "string",
2140
+ Byte: "number",
2141
+ Short: "number",
2142
+ Unit: "void",
2143
+ Any: "unknown",
2144
+ Nothing: "never",
2145
+ };
2146
+ const GENERIC_TYPE_MAP = {
2147
+ List: "ReadonlyArray",
2148
+ MutableList: "Array",
2149
+ Set: "ReadonlySet",
2150
+ MutableSet: "Set",
2151
+ Map: "ReadonlyMap",
2152
+ MutableMap: "Map",
2153
+ Array: "Array",
2154
+ Pair: "[", // handled specially
2155
+ Triple: "[",
2156
+ Deferred: "Promise",
2157
+ StateFlow: "StateFlow",
2158
+ MutableStateFlow: "MutableStateFlow",
2159
+ Flow: "AsyncIterable",
2160
+ };
2161
+ const PRIMITIVE_TYPES = new Set(Object.keys(PRIMITIVE_TYPE_MAP));
2162
+ // ---------------------------------------------------------------------------
2163
+ // Well-known JavaScript / TypeScript global names that must never be emitted
2164
+ // as named imports from a wildcard-imported package.
2165
+ // ---------------------------------------------------------------------------
2166
+ const JS_GLOBAL_NAMES = new Set([
2167
+ // JS built-in constructors and objects
2168
+ "Array", "Map", "Set", "Object", "String", "Number", "Boolean",
2169
+ "Promise", "Error", "TypeError", "RangeError", "SyntaxError", "URIError",
2170
+ "EvalError", "ReferenceError",
2171
+ "console", "Math", "Date", "JSON", "RegExp", "Symbol", "BigInt",
2172
+ "Proxy", "Reflect", "globalThis", "Atomics", "SharedArrayBuffer",
2173
+ "ArrayBuffer", "DataView", "Int8Array", "Uint8Array", "Uint8ClampedArray",
2174
+ "Int16Array", "Uint16Array", "Int32Array", "Uint32Array",
2175
+ "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array",
2176
+ // Global functions
2177
+ "isNaN", "isFinite", "parseInt", "parseFloat", "eval",
2178
+ "encodeURI", "decodeURI", "encodeURIComponent", "decodeURIComponent",
2179
+ // Browser/Node globals
2180
+ "setTimeout", "clearTimeout", "setInterval", "clearInterval",
2181
+ "queueMicrotask", "requestAnimationFrame", "cancelAnimationFrame",
2182
+ "fetch", "URL", "URLSearchParams", "AbortController", "AbortSignal",
2183
+ "EventTarget", "Event", "CustomEvent", "FormData", "Headers",
2184
+ "Request", "Response", "Blob", "File", "FileReader",
2185
+ "Worker", "SharedWorker", "WebSocket",
2186
+ "ReadableStream", "WritableStream", "TransformStream",
2187
+ "document", "window", "navigator", "location", "history", "screen",
2188
+ "performance", "crypto", "indexedDB", "localStorage", "sessionStorage",
2189
+ "confirm", "alert", "prompt",
2190
+ "process", "Buffer", "global", "require", "module", "exports", "__dirname", "__filename",
2191
+ // Special identifiers
2192
+ "undefined", "null", "NaN", "Infinity",
2193
+ "it", "this", "super", "arguments", "new", "class", "function",
2194
+ // TypeScript primitive type names
2195
+ "any", "unknown", "never", "void", "string", "number", "boolean", "bigint",
2196
+ "object", "symbol",
2197
+ // Jalvin type names that map to TS primitives (never need import)
2198
+ "String", "Boolean", "Any", "Nothing", "Unit", "Char", "Byte", "Short",
2199
+ "Float", "Double",
2200
+ ]);
2201
+ // ---------------------------------------------------------------------------
2202
+ // Public helper
2203
+ // ---------------------------------------------------------------------------
2204
+ function generate(program, opts, operatorOverloads, typeMap) {
2205
+ return new CodeGenerator(opts).generate(program, operatorOverloads, typeMap);
2206
+ }
2207
+ //# sourceMappingURL=index.js.map