@jalvin/compiler 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1624 @@
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 AST = __importStar(require("./ast.js"));
54
+ // ---------------------------------------------------------------------------
55
+ // Code writer — thin abstraction over string concatenation that tracks
56
+ // indentation and emits source-map line hints.
57
+ // ---------------------------------------------------------------------------
58
+ class Writer {
59
+ buf = "";
60
+ indent = 0;
61
+ INDENT = " ";
62
+ /** line mapping: output line → input line (1-based) */
63
+ lineMap = [];
64
+ currentLine = 1;
65
+ write(text) {
66
+ this.buf += text;
67
+ }
68
+ writeLine(text = "") {
69
+ this.buf += text + "\n";
70
+ this.lineMap.push(this.currentLine);
71
+ this.currentLine++;
72
+ }
73
+ writeIndented(text) {
74
+ this.buf += this.INDENT.repeat(this.indent) + text;
75
+ }
76
+ writeIndentedLine(text = "") {
77
+ this.buf += this.INDENT.repeat(this.indent) + text + "\n";
78
+ this.lineMap.push(this.currentLine);
79
+ this.currentLine++;
80
+ }
81
+ pushIndent() { this.indent++; }
82
+ popIndent() { this.indent = Math.max(0, this.indent - 1); }
83
+ get output() { return this.buf; }
84
+ setSourceLine(line) { this.currentLine = line + 1; }
85
+ }
86
+ exports.DEFAULT_CODEGEN_OPTIONS = {
87
+ jsx: false,
88
+ module: "esm",
89
+ emitTypes: false,
90
+ runtimeImport: "@jalvin/runtime",
91
+ };
92
+ class CodeGenerator {
93
+ w = new Writer();
94
+ opts;
95
+ hasComponents = false;
96
+ runtimeSymbolsNeeded = new Set();
97
+ /** Operator overload resolutions from the type checker */
98
+ operatorOverloadMap = new Map();
99
+ /** Type map from the type checker */
100
+ typeMap = new Map();
101
+ /**
102
+ * Registry of extension functions declared on primitive receiver types.
103
+ * Maps receiverTypeName → Map<methodName, generatedFnName>
104
+ * Populated during top-level emit; used at call sites for rewriting.
105
+ */
106
+ primitiveExtensions = new Map();
107
+ constructor(opts = {}) {
108
+ this.opts = { ...exports.DEFAULT_CODEGEN_OPTIONS, ...opts };
109
+ }
110
+ generate(program, operatorOverloads, typeMap) {
111
+ if (operatorOverloads)
112
+ this.operatorOverloadMap = operatorOverloads;
113
+ if (typeMap)
114
+ this.typeMap = typeMap;
115
+ // Pre-scan for components
116
+ this.hasComponents = program.declarations.some((d) => d.kind === "ComponentDecl" ||
117
+ (d.kind === "ClassDecl" && d.body?.members.some((m) => m.kind === "ComponentDecl")));
118
+ // Emit header
119
+ this.emitHeader(program);
120
+ // Declarations
121
+ for (const decl of program.declarations) {
122
+ this.emitTopLevelDecl(decl);
123
+ this.w.writeLine();
124
+ }
125
+ // Patch in runtime import if needed
126
+ const preamble = this.buildPreamble();
127
+ const code = preamble + this.w.output;
128
+ return {
129
+ code,
130
+ lineMap: this.w.lineMap,
131
+ isJsx: this.hasComponents,
132
+ };
133
+ }
134
+ // ── Preamble & imports ─────────────────────────────────────────────────────
135
+ buildPreamble() {
136
+ const lines = [];
137
+ if (this.hasComponents) {
138
+ lines.push(`import React from "react";`);
139
+ }
140
+ const needed = [...this.runtimeSymbolsNeeded];
141
+ if (needed.length > 0) {
142
+ lines.push(`import { ${needed.sort().join(", ")} } from "${this.opts.runtimeImport}";`);
143
+ }
144
+ return lines.length > 0 ? lines.join("\n") + "\n\n" : "";
145
+ }
146
+ emitHeader(program) {
147
+ // Re-emit user imports as ES imports
148
+ for (const imp of program.imports) {
149
+ // Correctly handle scoped packages: if path starts with @, preserve it
150
+ const path = imp.path[0].startsWith("@")
151
+ ? imp.path[0] + "/" + imp.path.slice(1).join("/")
152
+ : imp.path.join("/");
153
+ if (imp.star) {
154
+ this.w.writeIndentedLine(`import * as ${imp.path[imp.path.length - 1]} from "${path}";`);
155
+ }
156
+ else if (imp.alias) {
157
+ const named = imp.path[imp.path.length - 1];
158
+ this.w.writeIndentedLine(`import { ${named} as ${imp.alias} } from "${path}";`);
159
+ }
160
+ else {
161
+ this.w.writeIndentedLine(`import { ${imp.path[imp.path.length - 1]} } from "${path}";`);
162
+ }
163
+ }
164
+ if (program.imports.length > 0)
165
+ this.w.writeLine();
166
+ }
167
+ // ── Top-level declarations ─────────────────────────────────────────────────
168
+ /** Emit a @deprecated JSDoc block if the declaration has @Nuked. */
169
+ emitAnnotations(mods) {
170
+ for (const ann of mods.annotations) {
171
+ if (ann.name === "Nuked") {
172
+ const reason = ann.args ? ` ${ann.args.replace(/^["']|["']$/g, "")}` : "";
173
+ this.w.writeIndentedLine(`/** @deprecated${reason} */`);
174
+ }
175
+ // Other annotations are silently ignored for now (pass-through model)
176
+ }
177
+ }
178
+ emitTopLevelDecl(decl) {
179
+ switch (decl.kind) {
180
+ case "FunDecl":
181
+ this.emitFunDecl(decl, false);
182
+ break;
183
+ case "ComponentDecl":
184
+ this.emitComponentDecl(decl);
185
+ break;
186
+ case "ClassDecl":
187
+ this.emitClassDecl(decl);
188
+ break;
189
+ case "DataClassDecl":
190
+ this.emitDataClassDecl(decl);
191
+ break;
192
+ case "SealedClassDecl":
193
+ this.emitSealedClassDecl(decl);
194
+ break;
195
+ case "EnumClassDecl":
196
+ this.emitEnumClassDecl(decl);
197
+ break;
198
+ case "InterfaceDecl":
199
+ this.emitInterfaceDecl(decl);
200
+ break;
201
+ case "ObjectDecl":
202
+ this.emitObjectDecl(decl);
203
+ break;
204
+ case "TypeAliasDecl":
205
+ this.emitTypeAliasDecl(decl);
206
+ break;
207
+ case "PropertyDecl":
208
+ this.emitPropertyDecl(decl, false);
209
+ break;
210
+ case "ExtensionFunDecl":
211
+ this.emitExtensionFunDecl(decl);
212
+ break;
213
+ case "DestructuringDecl":
214
+ this.emitDestructuringDecl(decl, false);
215
+ break;
216
+ }
217
+ }
218
+ // ── function declarations ──────────────────────────────────────────────────
219
+ emitFunDecl(decl, memberOf) {
220
+ this.emitAnnotations(decl.modifiers);
221
+ const isSuspend = AST.isSuspend(decl.modifiers);
222
+ const vis = memberOf ? this.visibilityPrefix(decl.modifiers) : this.exportPrefix(decl.modifiers);
223
+ const asyncKw = isSuspend ? "async " : "";
224
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
225
+ const params = this.emitParamsStr(decl.params);
226
+ const retType = decl.returnType && this.opts.emitTypes
227
+ ? `: ${this.emitTypeRef(decl.returnType)}`
228
+ : "";
229
+ if (!decl.body) {
230
+ // Abstract / interface method
231
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType};`);
232
+ return;
233
+ }
234
+ if (decl.body.kind === "Block") {
235
+ if (memberOf) {
236
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
237
+ }
238
+ else {
239
+ this.w.writeIndentedLine(`${vis}${asyncKw}function ${decl.name}${typeParams}(${params})${retType} {`);
240
+ }
241
+ this.w.pushIndent();
242
+ this.emitBlock(decl.body);
243
+ this.w.popIndent();
244
+ this.w.writeIndentedLine(`}`);
245
+ }
246
+ else {
247
+ // Expression body
248
+ if (memberOf) {
249
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
250
+ }
251
+ else {
252
+ this.w.writeIndentedLine(`${vis}${asyncKw}function ${decl.name}${typeParams}(${params})${retType} {`);
253
+ }
254
+ this.w.pushIndent();
255
+ this.w.writeIndentedLine(`return ${this.emitExpr(decl.body)};`);
256
+ this.w.popIndent();
257
+ this.w.writeIndentedLine(`}`);
258
+ }
259
+ }
260
+ emitMemberFunDecl(decl) {
261
+ const isSuspend = AST.isSuspend(decl.modifiers);
262
+ const vis = this.visibilityPrefix(decl.modifiers);
263
+ const asyncKw = isSuspend ? "async " : "";
264
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
265
+ const params = this.emitParamsStr(decl.params);
266
+ const retType = decl.returnType && this.opts.emitTypes
267
+ ? `: ${this.emitTypeRef(decl.returnType)}`
268
+ : "";
269
+ if (!decl.body) {
270
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType};`);
271
+ return;
272
+ }
273
+ if (decl.body.kind === "Block") {
274
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
275
+ this.w.pushIndent();
276
+ this.emitBlock(decl.body);
277
+ this.w.popIndent();
278
+ this.w.writeIndentedLine(`}`);
279
+ }
280
+ else {
281
+ this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
282
+ this.w.pushIndent();
283
+ this.w.writeIndentedLine(`return ${this.emitExpr(decl.body)};`);
284
+ this.w.popIndent();
285
+ this.w.writeIndentedLine(`}`);
286
+ }
287
+ }
288
+ // ── component declarations → React function components ────────────────────
289
+ emitComponentDecl(decl) {
290
+ this.emitAnnotations(decl.modifiers);
291
+ this.hasComponents = true;
292
+ const vis = this.exportPrefix(decl.modifiers);
293
+ const params = this.emitComponentPropsStr(decl.params);
294
+ // Props interface
295
+ if (decl.params.length > 0) {
296
+ this.w.writeIndentedLine(`interface ${decl.name}Props {`);
297
+ this.w.pushIndent();
298
+ for (const p of decl.params) {
299
+ const optional = p.defaultValue ? "?" : "";
300
+ const typeStr = this.opts.emitTypes ? this.emitTypeRef(p.type) : "any";
301
+ this.w.writeIndentedLine(`readonly ${p.name}${optional}: ${typeStr};`);
302
+ }
303
+ this.w.popIndent();
304
+ this.w.writeIndentedLine(`}`);
305
+ this.w.writeLine();
306
+ }
307
+ const propsParam = decl.params.length > 0
308
+ ? `{ ${decl.params.map((p) => p.name + (p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "")).join(", ")} }: ${decl.name}Props`
309
+ : "";
310
+ this.w.writeIndentedLine(`${vis}function ${decl.name}(${propsParam}): React.ReactElement | null {`);
311
+ this.w.pushIndent();
312
+ this.emitBlock(decl.body);
313
+ this.w.popIndent();
314
+ this.w.writeIndentedLine(`}`);
315
+ }
316
+ // ── regular class ──────────────────────────────────────────────────────────
317
+ emitClassDecl(decl) {
318
+ this.emitAnnotations(decl.modifiers);
319
+ const vis = this.exportPrefix(decl.modifiers);
320
+ const abstract = decl.modifiers.modifiers.includes("abstract") ? "abstract " : "";
321
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
322
+ const superTypes = this.emitSuperTypesStr(decl.superTypes);
323
+ this.w.writeIndentedLine(`${vis}${abstract}class ${decl.name}${typeParams}${superTypes} {`);
324
+ this.w.pushIndent();
325
+ if (decl.primaryConstructor) {
326
+ const initBlocks = decl.body?.members.filter((m) => m.kind === "InitBlock") ?? [];
327
+ this.emitPrimaryConstructorProps(decl.primaryConstructor.params, initBlocks);
328
+ }
329
+ if (decl.body) {
330
+ this.emitClassBody(decl.body);
331
+ }
332
+ this.w.popIndent();
333
+ this.w.writeIndentedLine(`}`);
334
+ }
335
+ // ── data class → class with copy/equals/toString ──────────────────────────
336
+ emitDataClassDecl(decl, kindName) {
337
+ this.emitAnnotations(decl.modifiers);
338
+ const vis = this.exportPrefix(decl.modifiers);
339
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
340
+ const superTypes = this.emitSuperTypesStr(decl.superTypes);
341
+ const props = decl.primaryConstructor.params;
342
+ this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams}${superTypes} {`);
343
+ this.w.pushIndent();
344
+ // Discriminant for sealed class sub-types
345
+ if (kindName) {
346
+ this.w.writeIndentedLine(`readonly __kind = "${kindName}" as const;`);
347
+ }
348
+ // Constructor params → readonly properties
349
+ for (const p of props) {
350
+ const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
351
+ this.w.writeIndentedLine(`readonly ${p.name}${t};`);
352
+ }
353
+ this.w.writeLine();
354
+ // Constructor
355
+ const ctorParams = props.map((p) => {
356
+ const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
357
+ return `${p.name}${t}`;
358
+ }).join(", ");
359
+ this.w.writeIndentedLine(`constructor(${ctorParams}) {`);
360
+ this.w.pushIndent();
361
+ if (decl.superTypes.length > 0)
362
+ this.w.writeIndentedLine(`super();`);
363
+ for (const p of props) {
364
+ this.w.writeIndentedLine(`this.${p.name} = ${p.name};`);
365
+ }
366
+ this.w.popIndent();
367
+ this.w.writeIndentedLine(`}`);
368
+ this.w.writeLine();
369
+ // copy()
370
+ const copyParams = props.map((p) => {
371
+ const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
372
+ return `${p.name}${t} = this.${p.name}`;
373
+ }).join(", ");
374
+ const copyArgs = props.map((p) => p.name).join(", ");
375
+ this.w.writeIndentedLine(`copy(${copyParams}): ${decl.name} {`);
376
+ this.w.pushIndent();
377
+ this.w.writeIndentedLine(`return new ${decl.name}(${copyArgs});`);
378
+ this.w.popIndent();
379
+ this.w.writeIndentedLine(`}`);
380
+ this.w.writeLine();
381
+ // equals()
382
+ this.runtimeSymbolsNeeded.add("jalvinEquals");
383
+ const eqChecks = props.map((p) => `jalvinEquals(this.${p.name}, other.${p.name})`).join(" && ") || "true";
384
+ this.w.writeIndentedLine(`equals(other: unknown): boolean {`);
385
+ this.w.pushIndent();
386
+ this.w.writeIndentedLine(`if (!(other instanceof ${decl.name})) return false;`);
387
+ this.w.writeIndentedLine(`return ${eqChecks};`);
388
+ this.w.popIndent();
389
+ this.w.writeIndentedLine(`}`);
390
+ this.w.writeLine();
391
+ // toString()
392
+ const toStringParts = props.map((p) => `${p.name}=\${this.${p.name}}`).join(", ");
393
+ this.w.writeIndentedLine(`toString(): string {`);
394
+ this.w.pushIndent();
395
+ this.w.writeIndentedLine(`return \`${decl.name}(${toStringParts})\`;`);
396
+ this.w.popIndent();
397
+ this.w.writeIndentedLine(`}`);
398
+ this.w.writeLine();
399
+ // hashCode() — djb2
400
+ this.w.writeIndentedLine(`hashCode(): number {`);
401
+ this.w.pushIndent();
402
+ this.w.writeIndentedLine(`let h = 17;`);
403
+ for (const p of props) {
404
+ this.w.writeIndentedLine(`h = h * 31 + String(this.${p.name}).split("").reduce((a, c) => (a * 31 + c.charCodeAt(0)) >>> 0, 0);`);
405
+ }
406
+ this.w.writeIndentedLine(`return h >>> 0;`);
407
+ this.w.popIndent();
408
+ this.w.writeIndentedLine(`}`);
409
+ if (decl.body) {
410
+ this.w.writeLine();
411
+ this.emitClassBody(decl.body);
412
+ }
413
+ this.w.popIndent();
414
+ this.w.writeIndentedLine(`}`);
415
+ }
416
+ // ── sealed class → TS abstract class + namespace merging ──────────────────
417
+ emitSealedClassDecl(decl) {
418
+ this.emitAnnotations(decl.modifiers);
419
+ const vis = this.exportPrefix(decl.modifiers);
420
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
421
+ const subDecls = [];
422
+ const otherMembers = [];
423
+ if (decl.body) {
424
+ for (const member of decl.body.members) {
425
+ if (member.kind === "ClassDecl" ||
426
+ member.kind === "DataClassDecl" ||
427
+ member.kind === "ObjectDecl") {
428
+ subDecls.push(member);
429
+ }
430
+ else if (member.kind !== "InitBlock") {
431
+ otherMembers.push(member);
432
+ }
433
+ }
434
+ }
435
+ // Emit the abstract base class (only non-subtype members in the body)
436
+ this.w.writeIndentedLine(`${vis}abstract class ${decl.name}${typeParams} {`);
437
+ this.w.pushIndent();
438
+ this.w.writeIndentedLine(`abstract readonly __kind: string;`);
439
+ for (const member of otherMembers) {
440
+ this.emitClassMember(member);
441
+ this.w.writeLine();
442
+ }
443
+ this.w.popIndent();
444
+ this.w.writeIndentedLine(`}`);
445
+ this.w.writeLine();
446
+ if (subDecls.length === 0)
447
+ return;
448
+ // Emit non-object sub-declarations at module level (before namespace)
449
+ for (const sub of subDecls) {
450
+ if (sub.kind === "DataClassDecl") {
451
+ this.emitDataClassDecl(sub, sub.name);
452
+ this.w.writeLine();
453
+ }
454
+ else if (sub.kind === "ClassDecl") {
455
+ this.emitClassDecl(sub);
456
+ this.w.writeLine();
457
+ }
458
+ }
459
+ // Emit namespace merging so SealedClass.SubType is accessible
460
+ this.w.writeIndentedLine(`${vis}namespace ${decl.name} {`);
461
+ this.w.pushIndent();
462
+ for (const sub of subDecls) {
463
+ if (sub.kind === "ObjectDecl") {
464
+ // Singleton object — emit inline in namespace with __kind discriminant
465
+ const superStr = sub.superTypes.length > 0
466
+ ? this.emitSuperTypesStr(sub.superTypes)
467
+ : ` extends ${decl.name}`;
468
+ const membersArr = [];
469
+ membersArr.push(`readonly __kind = "${sub.name}" as const;`);
470
+ if (sub.body) {
471
+ for (const m of sub.body.members) {
472
+ if (m.kind !== "InitBlock")
473
+ membersArr.push(this.captureClassMember(m));
474
+ }
475
+ }
476
+ const membersStr = membersArr.join(" ");
477
+ this.w.writeIndentedLine(`export const ${sub.name} = new (class${superStr} { ${membersStr} })();`);
478
+ }
479
+ else {
480
+ // DataClassDecl / ClassDecl — already emitted at module level, re-export
481
+ this.w.writeIndentedLine(`export { ${sub.name} };`);
482
+ }
483
+ }
484
+ this.w.popIndent();
485
+ this.w.writeIndentedLine(`}`);
486
+ }
487
+ // ── enum class → TypeScript const enum + class ────────────────────────────
488
+ emitEnumClassDecl(decl) {
489
+ const vis = this.exportPrefix(decl.modifiers);
490
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
491
+ // Emit the enum as a TypeScript class with static singleton instances.
492
+ // This preserves the `EnumClass.ENTRY` access pattern and allows
493
+ // methods/properties to be attached to entries.
494
+ this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams} {`);
495
+ this.w.pushIndent();
496
+ // Private constructor so entries are the only instances
497
+ if (decl.primaryConstructor && decl.primaryConstructor.params.length > 0) {
498
+ const ctorParams = decl.primaryConstructor.params
499
+ .map((p) => {
500
+ const ro = p.propertyKind === "val" ? "readonly " : "";
501
+ const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
502
+ return `${ro}${p.name}${type}`;
503
+ })
504
+ .join(", ");
505
+ this.w.writeIndentedLine(`private constructor(${ctorParams}) {}`);
506
+ }
507
+ else {
508
+ this.w.writeIndentedLine(`private constructor(readonly name: string, readonly ordinal: number) {}`);
509
+ }
510
+ this.w.writeLine();
511
+ // Static entry instances
512
+ for (let i = 0; i < decl.entries.length; i++) {
513
+ const entry = decl.entries[i];
514
+ const args = entry.args.length > 0
515
+ ? entry.args.map((a) => this.emitExpr(a.value)).join(", ")
516
+ : `"${entry.name}", ${i}`;
517
+ this.w.writeIndentedLine(`static readonly ${entry.name} = new ${decl.name}(${args});`);
518
+ }
519
+ if (decl.entries.length > 0) {
520
+ this.w.writeLine();
521
+ // values() helper
522
+ const entryList = decl.entries.map((e) => `${decl.name}.${e.name}`).join(", ");
523
+ this.w.writeIndentedLine(`static values(): ${decl.name}[] { return [${entryList}]; }`);
524
+ this.w.writeIndentedLine(`static valueOf(name: string): ${decl.name} {`);
525
+ this.w.pushIndent();
526
+ this.w.writeIndentedLine(`const v = ${decl.name}.values().find(e => e.name === name);`);
527
+ this.w.writeIndentedLine(`if (!v) throw new Error(\`No enum constant ${decl.name}.\${name}\`);`);
528
+ this.w.writeIndentedLine(`return v;`);
529
+ this.w.popIndent();
530
+ this.w.writeIndentedLine(`}`);
531
+ }
532
+ if (decl.body) {
533
+ this.w.writeLine();
534
+ this.emitClassBody(decl.body);
535
+ }
536
+ this.w.popIndent();
537
+ this.w.writeIndentedLine(`}`);
538
+ }
539
+ // ── destructuring declaration — val (a, b) = expr ─────────────────────────
540
+ emitDestructuringDecl(decl, _memberOf) {
541
+ const kw = decl.mutable ? "let" : "const";
542
+ const names = decl.names.map((n) => n ?? "_").join(", ");
543
+ const init = this.emitExpr(decl.initializer);
544
+ this.w.writeIndentedLine(`${kw} [${names}] = ${init};`);
545
+ }
546
+ // ── interface ──────────────────────────────────────────────────────────────
547
+ emitInterfaceDecl(decl) {
548
+ this.emitAnnotations(decl.modifiers);
549
+ const vis = this.exportPrefix(decl.modifiers);
550
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
551
+ const superTypes = decl.superTypes.length > 0
552
+ ? ` extends ${decl.superTypes.map((s) => this.emitTypeRef(s.type)).join(", ")}`
553
+ : "";
554
+ this.w.writeIndentedLine(`${vis}interface ${decl.name}${typeParams}${superTypes} {`);
555
+ this.w.pushIndent();
556
+ if (decl.body) {
557
+ for (const member of decl.body.members) {
558
+ this.emitClassMember(member);
559
+ }
560
+ }
561
+ this.w.popIndent();
562
+ this.w.writeIndentedLine(`}`);
563
+ }
564
+ // ── object declaration → singleton ────────────────────────────────────────
565
+ emitObjectDecl(decl) {
566
+ if (!decl.name) {
567
+ // Anonymous object — emitted inline
568
+ return;
569
+ }
570
+ this.emitAnnotations(decl.modifiers);
571
+ const vis = this.exportPrefix(decl.modifiers);
572
+ const superTypes = this.emitSuperTypesStr(decl.superTypes);
573
+ this.w.writeIndentedLine(`${vis}const ${decl.name} = new (class${superTypes} {`);
574
+ this.w.pushIndent();
575
+ this.emitClassBody(decl.body);
576
+ this.w.popIndent();
577
+ this.w.writeIndentedLine(`})();`);
578
+ }
579
+ // ── type alias ─────────────────────────────────────────────────────────────
580
+ emitTypeAliasDecl(decl) {
581
+ const vis = this.exportPrefix(decl.modifiers);
582
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
583
+ this.w.writeIndentedLine(`${vis}type ${decl.name}${typeParams} = ${this.emitTypeRef(decl.type)};`);
584
+ }
585
+ // ── extension functions ────────────────────────────────────────────────────
586
+ // Emitted as standalone free functions with receiver as explicit first param.
587
+ // For class types, also patched onto the prototype for dot-call syntax.
588
+ // For primitive types, call sites are rewritten via `primitiveExtensions`.
589
+ emitExtensionFunDecl(decl) {
590
+ const isSuspend = AST.isSuspend(decl.modifiers);
591
+ const asyncKw = isSuspend ? "async " : "";
592
+ const receiverType = this.emitTypeRef(decl.receiver);
593
+ const typeParams = this.emitTypeParamsStr(decl.typeParams);
594
+ const params = this.emitParamsStr(decl.params);
595
+ const retType = decl.returnType && this.opts.emitTypes
596
+ ? `: ${this.emitTypeRef(decl.returnType)}`
597
+ : "";
598
+ const fnName = `__ext_${receiverType.replace(/[^a-zA-Z0-9_]/g, "_")}_${decl.name}`;
599
+ // Emit as a free function: receiver is the first explicit parameter `$receiver`.
600
+ // Inside the function body, `this` is rebound to `$receiver` so Jalvin's
601
+ // `this.prop` references work correctly.
602
+ this.w.writeIndentedLine(`${asyncKw}function ${fnName}${typeParams}($receiver: ${receiverType}${params ? ", " + params : ""})${retType} {`);
603
+ this.w.pushIndent();
604
+ if (decl.body.kind === "Block") {
605
+ this.w.writeIndentedLine(`return (function(this: ${receiverType})${retType} {`);
606
+ this.w.pushIndent();
607
+ this.emitBlock(decl.body);
608
+ this.w.popIndent();
609
+ this.w.writeIndentedLine(`}).call($receiver);`);
610
+ }
611
+ else {
612
+ this.w.writeIndentedLine(`return (function(this: ${receiverType})${retType} { return ${this.emitExpr(decl.body)}; }).call($receiver);`);
613
+ }
614
+ this.w.popIndent();
615
+ this.w.writeIndentedLine(`}`);
616
+ this.w.writeLine();
617
+ const simpleReceiverName = this.receiverClassName(decl.receiver);
618
+ if (simpleReceiverName) {
619
+ if (PRIMITIVE_TYPES.has(simpleReceiverName)) {
620
+ // Register for call-site rewriting — cannot patch primitive prototypes
621
+ if (!this.primitiveExtensions.has(simpleReceiverName)) {
622
+ this.primitiveExtensions.set(simpleReceiverName, new Map());
623
+ }
624
+ this.primitiveExtensions.get(simpleReceiverName).set(decl.name, fnName);
625
+ }
626
+ else {
627
+ // Class types — prototype monkey-patch for dot-call syntax
628
+ this.w.writeIndentedLine(`// Extension: ${receiverType}.${decl.name}`);
629
+ this.w.writeIndentedLine(`(${simpleReceiverName}.prototype as any).${decl.name} = function(this: ${receiverType}, ...args: unknown[]) { return (${fnName} as Function)(this, ...args); };`);
630
+ }
631
+ }
632
+ }
633
+ receiverClassName(ref) {
634
+ if (ref.kind === "SimpleTypeRef")
635
+ return ref.name[ref.name.length - 1] ?? null;
636
+ if (ref.kind === "GenericTypeRef")
637
+ return ref.base.name[ref.base.name.length - 1] ?? null;
638
+ return null;
639
+ }
640
+ /**
641
+ * Maps a JType to the primitive receiver type name used as a key
642
+ * in `primitiveExtensions` (e.g. JType `{ tag: "string" }` → `"String"`).
643
+ * Returns null for non-primitive / unknown types.
644
+ */
645
+ jTypeToReceiverName(type) {
646
+ const tagToReceiverName = {
647
+ string: "String",
648
+ int: "Int",
649
+ long: "Long",
650
+ float: "Float",
651
+ double: "Double",
652
+ boolean: "Boolean",
653
+ char: "Char",
654
+ };
655
+ return tagToReceiverName[type.tag] ?? null;
656
+ }
657
+ classHasInvokeOperator(decl) {
658
+ if (!decl.body)
659
+ return false;
660
+ return decl.body.members.some((m) => m.kind === "FunDecl" &&
661
+ m.name === "invoke" &&
662
+ m.modifiers.modifiers.includes("operator"));
663
+ }
664
+ // ── property declaration ───────────────────────────────────────────────────
665
+ emitPropertyDecl(decl, member) {
666
+ this.emitAnnotations(decl.modifiers);
667
+ const vis = member ? this.visibilityPrefix(decl.modifiers) : this.exportPrefix(decl.modifiers);
668
+ const isConst = decl.modifiers.modifiers.includes("const");
669
+ const isLateinit = decl.modifiers.modifiers.includes("lateinit");
670
+ // `const val` → `const` at module level; inside classes treated as `static readonly`
671
+ const kw = member
672
+ ? (decl.mutable ? "" : "readonly ")
673
+ : (isConst ? "const " : decl.mutable ? "let " : "const ");
674
+ const type = decl.type && this.opts.emitTypes ? `: ${this.emitTypeRef(decl.type)}` : "";
675
+ if (decl.delegate) {
676
+ // Delegated property — wrap in a getter/setter pair using the delegate
677
+ this.runtimeSymbolsNeeded.add("delegate");
678
+ const delegateExpr = this.emitExpr(decl.delegate);
679
+ if (member) {
680
+ this.w.writeIndentedLine(`${vis}get ${decl.name}()${type} { return delegate(${delegateExpr}, "${decl.name}", this).getValue(); }`);
681
+ if (decl.mutable) {
682
+ this.w.writeIndentedLine(`${vis}set ${decl.name}(v: any) { delegate(${delegateExpr}, "${decl.name}", this).setValue(v); }`);
683
+ }
684
+ }
685
+ else {
686
+ this.w.writeIndentedLine(`${kw}${decl.name}${type} = ${delegateExpr};`);
687
+ }
688
+ return;
689
+ }
690
+ const init = decl.initializer ? ` = ${this.emitExpr(decl.initializer)}` : (isLateinit ? "" : "");
691
+ if (member) {
692
+ const modStr = isConst ? "static readonly " : decl.mutable ? "" : "readonly ";
693
+ this.w.writeIndentedLine(`${vis}${modStr}${decl.name}${type}${init};`);
694
+ }
695
+ else {
696
+ this.w.writeIndentedLine(`${vis}${kw}${decl.name}${type}${init};`);
697
+ }
698
+ if (decl.getter) {
699
+ this.emitPropertyAccessor("get", decl.name, decl.getter, member, type);
700
+ }
701
+ if (decl.setter) {
702
+ this.emitPropertyAccessor("set", decl.name, decl.setter, member, type);
703
+ }
704
+ }
705
+ emitPropertyAccessor(kind, name, acc, member, type) {
706
+ const retOrParam = kind === "get" ? type : `(value: any)`;
707
+ const vis = this.visibilityPrefix(acc.modifiers);
708
+ this.w.writeIndentedLine(`${vis}${kind} ${name}()${retOrParam} {`);
709
+ this.w.pushIndent();
710
+ if (acc.body) {
711
+ if (acc.body.kind === "Block")
712
+ this.emitBlock(acc.body);
713
+ else
714
+ this.w.writeIndentedLine(`return ${this.emitExpr(acc.body)};`);
715
+ }
716
+ this.w.popIndent();
717
+ this.w.writeIndentedLine(`}`);
718
+ }
719
+ // ── class body ─────────────────────────────────────────────────────────────
720
+ emitClassBody(body) {
721
+ for (const member of body.members) {
722
+ // InitBlocks are emitted inside the constructor by emitPrimaryConstructorProps
723
+ if (member.kind === "InitBlock")
724
+ continue;
725
+ this.emitClassMember(member);
726
+ this.w.writeLine();
727
+ }
728
+ }
729
+ emitClassMember(member) {
730
+ switch (member.kind) {
731
+ case "FunDecl":
732
+ this.emitMemberFunDecl(member);
733
+ break;
734
+ case "ComponentDecl":
735
+ this.emitComponentDecl(member);
736
+ break;
737
+ case "PropertyDecl":
738
+ this.emitPropertyDecl(member, true);
739
+ break;
740
+ case "ClassDecl":
741
+ this.emitClassDecl(member);
742
+ break;
743
+ case "DataClassDecl":
744
+ this.emitDataClassDecl(member);
745
+ break;
746
+ case "SealedClassDecl":
747
+ this.emitSealedClassDecl(member);
748
+ break;
749
+ case "EnumClassDecl":
750
+ this.emitEnumClassDecl(member);
751
+ break;
752
+ case "ObjectDecl":
753
+ this.emitObjectDecl(member);
754
+ break;
755
+ case "CompanionObject":
756
+ this.emitCompanionObject(member);
757
+ break;
758
+ case "InitBlock": /* handled in constructor */ break;
759
+ case "SecondaryConstructor":
760
+ this.emitSecondaryConstructor(member);
761
+ break;
762
+ case "ExtensionFunDecl":
763
+ this.emitExtensionFunDecl(member);
764
+ break;
765
+ }
766
+ }
767
+ emitPrimaryConstructorProps(params, initBlocks) {
768
+ for (const p of params) {
769
+ if (p.propertyKind) {
770
+ const ro = p.propertyKind === "val" ? "readonly " : "";
771
+ const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
772
+ this.w.writeIndentedLine(`${ro}${p.name}${type};`);
773
+ }
774
+ }
775
+ // Constructor
776
+ const ctorParams = params.map((p) => {
777
+ const ro = p.propertyKind === "val" ? "readonly " : p.propertyKind === "var" ? "" : "";
778
+ const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
779
+ const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
780
+ return `${p.name}${type}${def}`;
781
+ });
782
+ this.w.writeIndentedLine(`constructor(${ctorParams.join(", ")}) {`);
783
+ this.w.pushIndent();
784
+ for (const p of params) {
785
+ if (p.propertyKind) {
786
+ this.w.writeIndentedLine(`this.${p.name} = ${p.name};`);
787
+ }
788
+ }
789
+ // Emit init{} blocks inside the constructor, in declaration order
790
+ for (const ib of (initBlocks ?? [])) {
791
+ this.emitBlock(ib.body);
792
+ }
793
+ this.w.popIndent();
794
+ this.w.writeIndentedLine(`}`);
795
+ this.w.writeLine();
796
+ }
797
+ emitCompanionObject(co) {
798
+ // Emit companion object members as `static` members of the outer class so that
799
+ // `MyClass.factoryMethod()` works directly (standard semantics).
800
+ for (const member of co.body.members) {
801
+ if (member.kind === "FunDecl") {
802
+ const vis = this.visibilityPrefix(member.modifiers);
803
+ const params = this.emitParamsStr(member.params);
804
+ const retType = member.returnType && this.opts.emitTypes
805
+ ? `: ${this.emitTypeRef(member.returnType)}` : "";
806
+ const asyncKw = AST.isSuspend(member.modifiers) ? "async " : "";
807
+ if (member.body) {
808
+ if (member.body.kind === "Block") {
809
+ this.w.writeIndentedLine(`${vis}static ${asyncKw}${member.name}(${params})${retType} {`);
810
+ this.w.pushIndent();
811
+ this.emitBlock(member.body);
812
+ this.w.popIndent();
813
+ this.w.writeIndentedLine(`}`);
814
+ }
815
+ else {
816
+ this.w.writeIndentedLine(`${vis}static ${asyncKw}${member.name}(${params})${retType} { return ${this.emitExpr(member.body)}; }`);
817
+ }
818
+ }
819
+ else {
820
+ this.w.writeIndentedLine(`${vis}static abstract ${member.name}(${params})${retType};`);
821
+ }
822
+ }
823
+ else if (member.kind === "PropertyDecl") {
824
+ const vis = this.visibilityPrefix(member.modifiers);
825
+ const kw = member.mutable ? "" : "readonly ";
826
+ const typeAnn = member.type && this.opts.emitTypes ? `: ${this.emitTypeRef(member.type)}` : "";
827
+ const init = member.initializer ? ` = ${this.emitExpr(member.initializer)}` : "";
828
+ this.w.writeIndentedLine(`${vis}static ${kw}${member.name}${typeAnn}${init};`);
829
+ }
830
+ }
831
+ }
832
+ emitSecondaryConstructor(ctor) {
833
+ const params = this.emitParamsStr(ctor.params);
834
+ this.w.writeIndentedLine(`constructor(${params}) {`);
835
+ this.w.pushIndent();
836
+ if (ctor.delegation) {
837
+ const args = ctor.delegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
838
+ this.w.writeIndentedLine(`${ctor.delegation}(${args});`);
839
+ }
840
+ this.emitBlock(ctor.body);
841
+ this.w.popIndent();
842
+ this.w.writeIndentedLine(`}`);
843
+ }
844
+ // ── Statements ─────────────────────────────────────────────────────────────
845
+ emitBlock(block) {
846
+ for (const stmt of block.statements) {
847
+ this.emitStmt(stmt);
848
+ }
849
+ }
850
+ emitStmt(stmt) {
851
+ switch (stmt.kind) {
852
+ case "Block":
853
+ this.w.writeIndentedLine(`{`);
854
+ this.w.pushIndent();
855
+ this.emitBlock(stmt);
856
+ this.w.popIndent();
857
+ this.w.writeIndentedLine(`}`);
858
+ break;
859
+ case "PropertyDecl":
860
+ this.emitPropertyDecl(stmt, false);
861
+ break;
862
+ case "DestructuringDecl":
863
+ this.emitDestructuringDecl(stmt, false);
864
+ break;
865
+ case "ExprStmt":
866
+ this.w.writeIndentedLine(`${this.emitExpr(stmt.expr)};`);
867
+ break;
868
+ case "ReturnStmt": {
869
+ const val = stmt.value ? ` ${this.emitExpr(stmt.value)}` : "";
870
+ this.w.writeIndentedLine(`return${val};`);
871
+ break;
872
+ }
873
+ case "ThrowStmt":
874
+ this.w.writeIndentedLine(`throw ${this.emitExpr(stmt.value)};`);
875
+ break;
876
+ case "BreakStmt":
877
+ this.w.writeIndentedLine(stmt.label ? `break ${stmt.label};` : `break;`);
878
+ break;
879
+ case "ContinueStmt":
880
+ this.w.writeIndentedLine(stmt.label ? `continue ${stmt.label};` : `continue;`);
881
+ break;
882
+ case "IfStmt":
883
+ this.emitIfStmt(stmt);
884
+ break;
885
+ case "WhenStmt":
886
+ this.emitWhenStmt(stmt);
887
+ break;
888
+ case "ForStmt":
889
+ this.emitForStmt(stmt);
890
+ break;
891
+ case "WhileStmt":
892
+ this.w.writeIndentedLine(`while (${this.emitExpr(stmt.condition)}) {`);
893
+ this.w.pushIndent();
894
+ this.emitBlock(stmt.body);
895
+ this.w.popIndent();
896
+ this.w.writeIndentedLine(`}`);
897
+ break;
898
+ case "DoWhileStmt":
899
+ this.w.writeIndentedLine(`do {`);
900
+ this.w.pushIndent();
901
+ this.emitBlock(stmt.body);
902
+ this.w.popIndent();
903
+ this.w.writeIndentedLine(`} while (${this.emitExpr(stmt.condition)});`);
904
+ break;
905
+ case "TryCatchStmt":
906
+ this.emitTryCatch(stmt);
907
+ break;
908
+ case "LabeledStmt":
909
+ this.w.writeIndentedLine(`${stmt.label}:`);
910
+ this.emitStmt(stmt.body);
911
+ break;
912
+ }
913
+ }
914
+ emitIfStmt(stmt) {
915
+ this.w.writeIndentedLine(`if (${this.emitExpr(stmt.condition)}) {`);
916
+ this.w.pushIndent();
917
+ this.emitBlock(stmt.then);
918
+ this.w.popIndent();
919
+ if (stmt.else) {
920
+ if (stmt.else.kind === "IfStmt") {
921
+ this.w.writeIndented(`} else `);
922
+ this.emitIfStmt(stmt.else);
923
+ }
924
+ else {
925
+ this.w.writeIndentedLine(`} else {`);
926
+ this.w.pushIndent();
927
+ this.emitBlock(stmt.else);
928
+ this.w.popIndent();
929
+ this.w.writeIndentedLine(`}`);
930
+ }
931
+ }
932
+ else {
933
+ this.w.writeIndentedLine(`}`);
934
+ }
935
+ }
936
+ emitWhenStmt(stmt) {
937
+ // when(subject) { is Foo -> ... else -> ... }
938
+ // Compiles to a series of if/else-if chains
939
+ const subjectVar = stmt.subject ? "__when_subject__" : null;
940
+ if (stmt.subject) {
941
+ const binding = stmt.subject.binding ?? subjectVar;
942
+ this.w.writeIndentedLine(`const ${binding} = ${this.emitExpr(stmt.subject.expr)};`);
943
+ }
944
+ let first = true;
945
+ for (const branch of stmt.branches) {
946
+ if (branch.isElse) {
947
+ this.w.writeIndentedLine(first ? `{` : `} else {`);
948
+ }
949
+ else {
950
+ const cond = branch.conditions.map((c) => this.emitWhenCondition(c, subjectVar ?? "")).join(" || ");
951
+ this.w.writeIndentedLine(first ? `if (${cond}) {` : `} else if (${cond}) {`);
952
+ }
953
+ first = false;
954
+ this.w.pushIndent();
955
+ if (branch.body.kind === "Block") {
956
+ this.emitBlock(branch.body);
957
+ }
958
+ else {
959
+ this.w.writeIndentedLine(`${this.emitExpr(branch.body)};`);
960
+ }
961
+ this.w.popIndent();
962
+ }
963
+ this.w.writeIndentedLine(`}`);
964
+ }
965
+ emitWhenCondition(cond, subject) {
966
+ switch (cond.kind) {
967
+ case "WhenIsCondition": {
968
+ // Qualified name (e.g. UiState.Loading) → use __kind discriminant
969
+ // Single name (e.g. BibiError) → use instanceof
970
+ const isQualified = cond.type.kind === "SimpleTypeRef" && cond.type.name.length > 1;
971
+ const check = isQualified
972
+ ? `(${subject} as any).__kind === "${cond.type.kind === "SimpleTypeRef" ? cond.type.name[cond.type.name.length - 1] : ""}"`
973
+ : `${subject} instanceof ${this.emitTypeRef(cond.type)}`;
974
+ return cond.negated ? `!(${check})` : check;
975
+ }
976
+ case "WhenInCondition": {
977
+ const check = `(${this.emitExpr(cond.expr)}).includes(${subject})`;
978
+ return cond.negated ? `!(${check})` : check;
979
+ }
980
+ case "WhenExprCondition":
981
+ return subject
982
+ ? `${subject} === ${this.emitExpr(cond.expr)}`
983
+ : this.emitExpr(cond.expr);
984
+ }
985
+ }
986
+ emitForStmt(stmt) {
987
+ const iter = this.emitExpr(stmt.iterable);
988
+ if (typeof stmt.binding === "string") {
989
+ this.w.writeIndentedLine(`for (const ${stmt.binding} of ${iter}) {`);
990
+ }
991
+ else if (stmt.binding.kind === "TupleDestructure") {
992
+ const names = stmt.binding.names.map((n) => n ?? "_").join(", ");
993
+ this.w.writeIndentedLine(`for (const [${names}] of ${iter}) {`);
994
+ }
995
+ else {
996
+ const { key, value } = stmt.binding;
997
+ // Use .entries() for JS Map (Map<K,V>), Object.entries() for plain objects
998
+ const iterableType = this.typeMap.get(stmt.iterable);
999
+ const isMap = iterableType?.tag === "class" && iterableType.name === "Map";
1000
+ const entries = isMap ? `${iter}.entries()` : `Object.entries(${iter})`;
1001
+ this.w.writeIndentedLine(`for (const [${key}, ${value}] of ${entries}) {`);
1002
+ }
1003
+ this.w.pushIndent();
1004
+ this.emitBlock(stmt.body);
1005
+ this.w.popIndent();
1006
+ this.w.writeIndentedLine(`}`);
1007
+ }
1008
+ emitTryCatch(stmt) {
1009
+ this.w.writeIndentedLine(`try {`);
1010
+ this.w.pushIndent();
1011
+ this.emitBlock(stmt.body);
1012
+ this.w.popIndent();
1013
+ for (const c of stmt.catches) {
1014
+ this.w.writeIndentedLine(`} catch (${c.name}: unknown) {`);
1015
+ this.w.pushIndent();
1016
+ // Narrow type
1017
+ if (this.opts.emitTypes) {
1018
+ this.w.writeIndentedLine(`if (!(${c.name} instanceof ${this.emitTypeRef(c.type)})) throw ${c.name};`);
1019
+ }
1020
+ this.emitBlock(c.body);
1021
+ this.w.popIndent();
1022
+ }
1023
+ if (stmt.finally) {
1024
+ this.w.writeIndentedLine(`} finally {`);
1025
+ this.w.pushIndent();
1026
+ this.emitBlock(stmt.finally);
1027
+ this.w.popIndent();
1028
+ }
1029
+ this.w.writeIndentedLine(`}`);
1030
+ }
1031
+ // ── Expressions ────────────────────────────────────────────────────────────
1032
+ emitExpr(expr) {
1033
+ switch (expr.kind) {
1034
+ case "IntLiteralExpr": return String(expr.value);
1035
+ case "LongLiteralExpr": return `BigInt(${expr.value})`;
1036
+ case "FloatLiteralExpr":
1037
+ case "DoubleLiteralExpr": return String(expr.value);
1038
+ case "BooleanLiteralExpr": return String(expr.value);
1039
+ case "NullLiteralExpr": return "null";
1040
+ case "StringLiteralExpr":
1041
+ return expr.raw
1042
+ ? `\`${expr.value}\``
1043
+ : JSON.stringify(expr.value);
1044
+ case "StringTemplateExpr":
1045
+ return this.emitStringTemplate(expr);
1046
+ case "NameExpr":
1047
+ // Ensure Int/Long companion objects are imported from the runtime
1048
+ if (expr.name === "Int" || expr.name === "Long") {
1049
+ this.runtimeSymbolsNeeded.add(expr.name);
1050
+ }
1051
+ return expr.name;
1052
+ case "ThisExpr": return "this";
1053
+ case "SuperExpr": return "super";
1054
+ case "ParenExpr": return `(${this.emitExpr(expr.expr)})`;
1055
+ case "UnaryExpr":
1056
+ return expr.prefix
1057
+ ? `${this.unaryOpStr(expr.op)}${this.emitExpr(expr.operand)}`
1058
+ : `${this.emitExpr(expr.operand)}${this.unaryOpStr(expr.op)}`;
1059
+ case "BinaryExpr": {
1060
+ const opMethod = this.operatorOverloadMap.get(expr);
1061
+ if (opMethod) {
1062
+ const l = this.emitExpr(expr.left);
1063
+ const r = this.emitExpr(expr.right);
1064
+ // `compareTo` overloads: wrap result in a comparison against 0
1065
+ if (opMethod === "compareTo") {
1066
+ const cmpOp = expr.op;
1067
+ return `(${l}.compareTo(${r}) ${cmpOp} 0)`;
1068
+ }
1069
+ // `contains` overloads: `element in collection` → `collection.contains(element)`
1070
+ if (opMethod === "contains") {
1071
+ return `${r}.contains(${l})`;
1072
+ }
1073
+ if (opMethod === "!contains") {
1074
+ return `!${r}.contains(${l})`;
1075
+ }
1076
+ // Generic operator overload: emit as method call `left.method(right)`
1077
+ return `${l}.${opMethod}(${r})`;
1078
+ }
1079
+ // `==` → structural equality, `!=` → negation
1080
+ if (expr.op === "==" || expr.op === "!=") {
1081
+ this.runtimeSymbolsNeeded.add("jalvinEquals");
1082
+ const eq = `jalvinEquals(${this.emitExpr(expr.left)}, ${this.emitExpr(expr.right)})`;
1083
+ return expr.op === "==" ? eq : `!${eq}`;
1084
+ }
1085
+ // `in` / `!in` without a user-defined operator: use JS `in` / array includes
1086
+ if (expr.op === "in") {
1087
+ return `${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)})`;
1088
+ }
1089
+ if (expr.op === "!in") {
1090
+ return `!(${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)}))`;
1091
+ }
1092
+ return `(${this.emitExpr(expr.left)} ${this.binaryOpStr(expr.op)} ${this.emitExpr(expr.right)})`;
1093
+ }
1094
+ case "AssignExpr":
1095
+ return `${this.emitExpr(expr.target)} = ${this.emitExpr(expr.value)}`;
1096
+ case "CompoundAssignExpr":
1097
+ return `${this.emitExpr(expr.target)} ${expr.op} ${this.emitExpr(expr.value)}`;
1098
+ case "IncrDecrExpr":
1099
+ return expr.prefix
1100
+ ? `${expr.op}${this.emitExpr(expr.target)}`
1101
+ : `${this.emitExpr(expr.target)}${expr.op}`;
1102
+ case "MemberExpr":
1103
+ return `${this.emitExpr(expr.target)}.${expr.member}`;
1104
+ case "SafeMemberExpr":
1105
+ return `${this.emitExpr(expr.target)}?.${expr.member}`;
1106
+ case "IndexExpr":
1107
+ return `${this.emitExpr(expr.target)}[${this.emitExpr(expr.index)}]`;
1108
+ case "NotNullExpr":
1109
+ // Runtime check
1110
+ this.runtimeSymbolsNeeded.add("notNull");
1111
+ return `notNull(${this.emitExpr(expr.expr)})`;
1112
+ case "ElvisExpr": {
1113
+ // left ?? right (null coalescing)
1114
+ return `(${this.emitExpr(expr.left)} ?? ${this.emitExpr(expr.right)})`;
1115
+ }
1116
+ case "CallExpr":
1117
+ return this.emitCallExpr(expr);
1118
+ case "LambdaExpr":
1119
+ return this.emitLambdaExpr(expr);
1120
+ case "IfExpr":
1121
+ return this.emitIfExpr(expr);
1122
+ case "WhenExpr":
1123
+ return this.emitWhenExpr(expr);
1124
+ case "TryCatchExpr":
1125
+ return this.emitTryCatchExpr(expr);
1126
+ case "TypeCheckExpr":
1127
+ return `${expr.negated ? "!(" : ""}${this.emitExpr(expr.expr)} instanceof ${this.emitTypeRef(expr.type)}${expr.negated ? ")" : ""}`;
1128
+ case "TypeCastExpr":
1129
+ // Unsafe cast — emit as `(expr as Type)` which is stripped at runtime
1130
+ return `(${this.emitExpr(expr.expr)} as unknown as ${this.emitTypeRef(expr.type)})`;
1131
+ case "SafeCastExpr": {
1132
+ this.runtimeSymbolsNeeded.add("safeCast");
1133
+ return `safeCast(${this.emitExpr(expr.expr)}, ${this.emitTypeRef(expr.type)})`;
1134
+ }
1135
+ case "RangeExpr": {
1136
+ this.runtimeSymbolsNeeded.add("range");
1137
+ return `range(${this.emitExpr(expr.from)}, ${this.emitExpr(expr.to)}, ${expr.inclusive})`;
1138
+ }
1139
+ case "LaunchExpr": {
1140
+ // fire-and-forget → void IIFE
1141
+ const stmts = this.captureBlock(expr.body);
1142
+ return `(async () => { ${stmts} })()`;
1143
+ }
1144
+ case "AsyncExpr": {
1145
+ // returns Promise<T>
1146
+ const stmts = this.captureBlock(expr.body);
1147
+ return `(async () => { ${stmts} })()`;
1148
+ }
1149
+ case "CollectionLiteralExpr":
1150
+ return this.emitCollectionLiteral(expr);
1151
+ case "ObjectExpr":
1152
+ return this.emitObjectExpr(expr);
1153
+ case "ReturnExpr": {
1154
+ const val = expr.value ? ` ${this.emitExpr(expr.value)}` : "";
1155
+ return `((() => { return${val}; })())`;
1156
+ }
1157
+ case "BreakExpr": return "undefined /* break */";
1158
+ case "ContinueExpr": return "undefined /* continue */";
1159
+ case "JsxElement":
1160
+ return this.emitJsxElement(expr);
1161
+ default:
1162
+ return "undefined";
1163
+ }
1164
+ }
1165
+ // ── JSX ──────────────────────────────────────────────────────────────────
1166
+ emitJsxElement(expr) {
1167
+ const attrsStr = expr.attrs.length > 0
1168
+ ? " " + expr.attrs.map((a) => this.emitJsxAttr(a)).join(" ")
1169
+ : "";
1170
+ if (expr.children.length === 0) {
1171
+ return `<${expr.tag}${attrsStr} />`;
1172
+ }
1173
+ const children = expr.children.map((c) => this.emitJsxChild(c)).join("");
1174
+ return `<${expr.tag}${attrsStr}>${children}</${expr.tag}>`;
1175
+ }
1176
+ emitJsxAttr(attr) {
1177
+ // Map HTML attribute names to React prop names
1178
+ const name = attr.name === "class" ? "className"
1179
+ : attr.name === "for" ? "htmlFor"
1180
+ : attr.name;
1181
+ if (attr.value === null)
1182
+ return name;
1183
+ if (typeof attr.value === "string")
1184
+ return `${name}="${attr.value}"`;
1185
+ return `${name}={${this.emitExpr(attr.value)}}`;
1186
+ }
1187
+ emitJsxChild(child) {
1188
+ switch (child.kind) {
1189
+ case "JsxElement": return this.emitJsxElement(child);
1190
+ case "JsxExprChild": return `{${this.emitExpr(child.expr)}}`;
1191
+ case "JsxTextChild": return child.text;
1192
+ }
1193
+ }
1194
+ emitStringTemplate(expr) {
1195
+ const inner = expr.parts.map((p) => {
1196
+ if (p.kind === "LiteralPart") {
1197
+ return p.value.replace(/`/g, "\\`").replace(/\\/g, "\\\\");
1198
+ }
1199
+ return `\${${this.emitExpr(p.expr)}}`;
1200
+ }).join("");
1201
+ return `\`${inner}\``;
1202
+ }
1203
+ emitCallExpr(expr) {
1204
+ // Rewrite infix numeric extension calls that JS numbers don't natively have:
1205
+ // `a.downTo(b)` → `downTo(a, b)` `a.until(b)` → `range(a, b, false)`
1206
+ // `range.step(n)` → `step(range, n)`
1207
+ if (expr.callee.kind === "MemberExpr" &&
1208
+ (expr.callee.member === "downTo" || expr.callee.member === "until" || expr.callee.member === "step")) {
1209
+ const obj = this.emitExpr(expr.callee.target);
1210
+ const arg = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "0";
1211
+ if (expr.callee.member === "downTo") {
1212
+ this.runtimeSymbolsNeeded.add("downTo");
1213
+ return `downTo(${obj}, ${arg})`;
1214
+ }
1215
+ if (expr.callee.member === "until") {
1216
+ this.runtimeSymbolsNeeded.add("range");
1217
+ return `range(${obj}, ${arg}, false)`;
1218
+ }
1219
+ if (expr.callee.member === "step") {
1220
+ this.runtimeSymbolsNeeded.add("step");
1221
+ return `step(${obj}, ${arg})`;
1222
+ }
1223
+ }
1224
+ // Rewrite calls on primitive-receiver extension functions.
1225
+ // `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
1226
+ if (expr.callee.kind === "MemberExpr") {
1227
+ const scopeMember = expr.callee.member;
1228
+ // Scope function call rewriting: `x.let { ... }` → `let_(x, ...)`
1229
+ // `x.apply { ... }` → `apply(x, function(this:any) { ... })`
1230
+ if (scopeMember === "let" || scopeMember === "also" || scopeMember === "apply" || scopeMember === "run" || scopeMember === "takeIf" || scopeMember === "takeUnless") {
1231
+ const receiver = this.emitExpr(expr.callee.target);
1232
+ const lambda = expr.trailingLambda ??
1233
+ (expr.args.length === 1 && expr.args[0].value.kind === "LambdaExpr"
1234
+ ? expr.args[0].value
1235
+ : null);
1236
+ if (lambda) {
1237
+ if (scopeMember === "let") {
1238
+ this.runtimeSymbolsNeeded.add("let_");
1239
+ return `let_(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1240
+ }
1241
+ if (scopeMember === "also") {
1242
+ this.runtimeSymbolsNeeded.add("also");
1243
+ return `also(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1244
+ }
1245
+ if (scopeMember === "apply") {
1246
+ this.runtimeSymbolsNeeded.add("apply");
1247
+ const body = this.captureBlockStatements(lambda.body);
1248
+ return `apply(${receiver}, function(this: any) { ${body} })`;
1249
+ }
1250
+ if (scopeMember === "run") {
1251
+ this.runtimeSymbolsNeeded.add("run_");
1252
+ const body = this.captureBlockStatements(lambda.body);
1253
+ return `run_(${receiver}, function(this: any) { ${body} })`;
1254
+ }
1255
+ if (scopeMember === "takeIf") {
1256
+ this.runtimeSymbolsNeeded.add("takeIf");
1257
+ return `takeIf(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1258
+ }
1259
+ if (scopeMember === "takeUnless") {
1260
+ this.runtimeSymbolsNeeded.add("takeUnless");
1261
+ return `takeUnless(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1262
+ }
1263
+ }
1264
+ }
1265
+ // `with(x) { ... }` is a free function handled via seedBuiltins; but rewrite if emitted as member
1266
+ }
1267
+ // Rewrite calls on primitive-receiver extension functions.
1268
+ // `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
1269
+ if (expr.callee.kind === "MemberExpr") {
1270
+ const receiverType = this.typeMap.get(expr.callee.target);
1271
+ const memberName = expr.callee.member;
1272
+ if (receiverType) {
1273
+ const tsTypeName = this.jTypeToReceiverName(receiverType);
1274
+ if (tsTypeName) {
1275
+ const extMap = this.primitiveExtensions.get(tsTypeName);
1276
+ if (extMap?.has(memberName)) {
1277
+ const fnName = extMap.get(memberName);
1278
+ const receiver = this.emitExpr(expr.callee.target);
1279
+ const restArgs = expr.args.map((a) => a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1280
+ if (expr.trailingLambda)
1281
+ restArgs.push(this.emitLambdaExpr(expr.trailingLambda));
1282
+ return `${fnName}(${[receiver, ...restArgs].join(", ")})`;
1283
+ }
1284
+ }
1285
+ }
1286
+ }
1287
+ const callee = this.emitExpr(expr.callee);
1288
+ const typeArgs = expr.typeArgs.length > 0
1289
+ ? `<${expr.typeArgs.map((t) => t.star ? "*" : this.emitTypeRef(t.type)).join(", ")}>`
1290
+ : "";
1291
+ // Rewrite top-level `with(obj) { ... }` → `with_(obj, function(this: any) { ... })`
1292
+ if (expr.callee.kind === "NameExpr" && expr.callee.name === "with" &&
1293
+ (expr.trailingLambda || (expr.args.length === 2 && expr.args[1].value.kind === "LambdaExpr"))) {
1294
+ const obj = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "undefined";
1295
+ const lambda = expr.trailingLambda ??
1296
+ expr.args[1].value;
1297
+ this.runtimeSymbolsNeeded.add("with_");
1298
+ const body = this.captureBlockStatements(lambda.body);
1299
+ return `with_(${obj}, function(this: any) { ${body} })`;
1300
+ }
1301
+ // Rewrite top-level `run { ... }` (no receiver) — just call the lambda
1302
+ if (expr.callee.kind === "NameExpr" && expr.callee.name === "run" &&
1303
+ expr.args.length === 0 && expr.trailingLambda) {
1304
+ return `(${this.emitLambdaExpr(expr.trailingLambda)})()`;
1305
+ }
1306
+ // Handle named arguments by reordering them to match positional parameters
1307
+ const calleeType = this.typeMap.get(expr.callee);
1308
+ let finalArgs = [];
1309
+ if (calleeType && calleeType.tag === "func" && calleeType.paramNames) {
1310
+ const paramNames = calleeType.paramNames;
1311
+ const argsByName = new Map();
1312
+ const positionalArgs = [];
1313
+ for (const arg of expr.args) {
1314
+ if (arg.name) {
1315
+ argsByName.set(arg.name, arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
1316
+ }
1317
+ else {
1318
+ positionalArgs.push(arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
1319
+ }
1320
+ }
1321
+ // Reconstruct args based on paramNames
1322
+ for (let i = 0; i < paramNames.length; i++) {
1323
+ const name = paramNames[i];
1324
+ if (argsByName.has(name)) {
1325
+ finalArgs.push(argsByName.get(name));
1326
+ }
1327
+ else if (i < positionalArgs.length) {
1328
+ finalArgs.push(positionalArgs[i]);
1329
+ }
1330
+ else {
1331
+ // Missing argument - JS default value logic will handle it if we pass undefined
1332
+ finalArgs.push("undefined");
1333
+ }
1334
+ }
1335
+ // Handle trailing positional args if any
1336
+ if (positionalArgs.length > paramNames.length) {
1337
+ finalArgs.push(...positionalArgs.slice(paramNames.length));
1338
+ }
1339
+ }
1340
+ else {
1341
+ // Fallback for types we don't know param names for (like constructors or any)
1342
+ finalArgs = expr.args.map((a) => {
1343
+ return a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value);
1344
+ });
1345
+ }
1346
+ if (expr.trailingLambda) {
1347
+ finalArgs.push(this.emitLambdaExpr(expr.trailingLambda));
1348
+ }
1349
+ // If the callee is a class instance (not a constructor / function), emit `.invoke(args)`
1350
+ if (calleeType &&
1351
+ calleeType.tag === "class" &&
1352
+ calleeType.decl &&
1353
+ this.classHasInvokeOperator(calleeType.decl)) {
1354
+ return `${callee}.invoke(${finalArgs.join(", ")})`;
1355
+ }
1356
+ // Constructor calls: class names start with uppercase by convention
1357
+ if (this.isConstructorCall(expr.callee)) {
1358
+ return `new ${callee}${typeArgs}(${finalArgs.join(", ")})`;
1359
+ }
1360
+ return `${callee}${typeArgs}(${finalArgs.join(", ")})`;
1361
+ }
1362
+ /** Returns true if a call expression's callee looks like a class constructor.
1363
+ * In Jalvin, class names always start with an uppercase letter. */
1364
+ isConstructorCall(calleeExpr) {
1365
+ if (calleeExpr.kind === "NameExpr") {
1366
+ // Class names start with uppercase; function names start with lowercase
1367
+ return /^[A-Z]/.test(calleeExpr.name);
1368
+ }
1369
+ if (calleeExpr.kind === "MemberExpr") {
1370
+ // e.g. UiState.Success(data), Discount.Percentage(0.1)
1371
+ return /^[A-Z]/.test(calleeExpr.member);
1372
+ }
1373
+ return false;
1374
+ }
1375
+ emitLambdaExpr(expr) {
1376
+ // If no explicit params, emit `it` as the single implicit parameter
1377
+ // (Convention for single-argument lambdas)
1378
+ const params = expr.params.length === 0
1379
+ ? "it"
1380
+ : expr.params.map((p) => p.name ?? "_").join(", ");
1381
+ const body = expr.body;
1382
+ if (body.length === 1 && body[0].kind === "ExprStmt") {
1383
+ return `(${params}) => ${this.emitExpr(body[0].expr)}`;
1384
+ }
1385
+ return `(${params}) => { ${body.map((s) => this.captureStmt(s)).join(" ")} }`;
1386
+ }
1387
+ emitIfExpr(expr) {
1388
+ const cond = this.emitExpr(expr.condition);
1389
+ const thenStr = expr.then.kind === "Block"
1390
+ ? `(() => { ${this.captureBlockStatements(expr.then.statements)} })()`
1391
+ : this.emitExpr(expr.then);
1392
+ const elseExpr = expr.else;
1393
+ const elseStr = elseExpr.kind === "Block"
1394
+ ? `(() => { ${this.captureBlockStatements(elseExpr.statements)} })()`
1395
+ : elseExpr.kind === "IfExpr"
1396
+ ? this.emitIfExpr(elseExpr)
1397
+ : this.emitExpr(elseExpr);
1398
+ return `(${cond} ? ${thenStr} : ${elseStr})`;
1399
+ }
1400
+ emitWhenExpr(expr) {
1401
+ // Compile as an IIFE with if/else chain
1402
+ const parts = [];
1403
+ const subject = expr.subject ? `const __s = ${this.emitExpr(expr.subject.expr)};` : "";
1404
+ const subjectRef = expr.subject ? (expr.subject.binding ?? "__s") : "";
1405
+ for (const branch of expr.branches) {
1406
+ if (branch.isElse) {
1407
+ const body = branch.body.kind === "Block"
1408
+ ? this.captureBlock(branch.body)
1409
+ : `return ${this.emitExpr(branch.body)};`;
1410
+ parts.push(`{ ${body} }`);
1411
+ }
1412
+ else {
1413
+ const cond = branch.conditions.map((c) => this.emitWhenCondition(c, subjectRef)).join(" || ");
1414
+ const body = branch.body.kind === "Block"
1415
+ ? this.captureBlock(branch.body)
1416
+ : `return ${this.emitExpr(branch.body)};`;
1417
+ parts.push(`if (${cond}) { ${body} }`);
1418
+ }
1419
+ }
1420
+ return `(() => { ${subject} ${parts.join(" else ")} })()`;
1421
+ }
1422
+ emitTryCatchExpr(expr) {
1423
+ const body = this.captureBlock(expr.body);
1424
+ const catches = expr.catches.map((c) => {
1425
+ const cb = this.captureBlock(c.body);
1426
+ return `catch (${c.name}) { ${cb} }`;
1427
+ }).join(" ");
1428
+ const fin = expr.finally ? `finally { ${this.captureBlock(expr.finally)} }` : "";
1429
+ return `(() => { try { ${body} } ${catches} ${fin} })()`;
1430
+ }
1431
+ emitCollectionLiteral(expr) {
1432
+ if (expr.collectionKind === "map") {
1433
+ const entries = expr.elements.map((e) => {
1434
+ if ("kind" in e && e.kind === "MapEntry") {
1435
+ return `[${this.emitExpr(e.key)}, ${this.emitExpr(e.value)}]`;
1436
+ }
1437
+ return "null";
1438
+ });
1439
+ return `new Map([${entries.join(", ")}])`;
1440
+ }
1441
+ if (expr.collectionKind === "set") {
1442
+ const items = expr.elements.map((e) => this.emitExpr(e));
1443
+ return `new Set([${items.join(", ")}])`;
1444
+ }
1445
+ const items = expr.elements.map((e) => this.emitExpr(e));
1446
+ return `[${items.join(", ")}]`;
1447
+ }
1448
+ emitObjectExpr(expr) {
1449
+ const superType = expr.superTypes[0];
1450
+ const ext = superType ? ` extends ${this.emitTypeRef(superType.type)}` : "";
1451
+ const members = expr.body.members.map((m) => this.captureClassMember(m)).join(" ");
1452
+ return `(new (class${ext} { ${members} })())`;
1453
+ }
1454
+ // ── capture helpers (emit to temp string) ─────────────────────────────────
1455
+ captureBlock(block) {
1456
+ return block.statements.map((s) => this.captureStmt(s)).join(" ");
1457
+ }
1458
+ captureBlockStatements(stmts) {
1459
+ return stmts.map((s) => this.captureStmt(s)).join(" ");
1460
+ }
1461
+ captureStmt(stmt) {
1462
+ const saved = this.w;
1463
+ const tmp = new Writer();
1464
+ this.w = tmp;
1465
+ this.emitStmt(stmt);
1466
+ this.w = saved;
1467
+ return tmp.output.trim();
1468
+ }
1469
+ captureClassMember(member) {
1470
+ const saved = this.w;
1471
+ const tmp = new Writer();
1472
+ this.w = tmp;
1473
+ this.emitClassMember(member);
1474
+ this.w = saved;
1475
+ return tmp.output.trim();
1476
+ }
1477
+ // ── Type reference emission ────────────────────────────────────────────────
1478
+ emitTypeRef(ref) {
1479
+ switch (ref.kind) {
1480
+ case "SimpleTypeRef": {
1481
+ const name = ref.name.join(".");
1482
+ return PRIMITIVE_TYPE_MAP[name] ?? name;
1483
+ }
1484
+ case "NullableTypeRef":
1485
+ return `${this.emitTypeRef(ref.base)} | null | undefined`;
1486
+ case "GenericTypeRef": {
1487
+ const base = ref.base.name.join(".");
1488
+ const mapped = GENERIC_TYPE_MAP[base] ?? base;
1489
+ const args = ref.args.map((a) => a.star ? "any" : a.type ? this.emitTypeRef(a.type) : "unknown");
1490
+ return `${mapped}<${args.join(", ")}>`;
1491
+ }
1492
+ case "FunctionTypeRef": {
1493
+ const params = ref.params.map((p, i) => `p${i}: ${this.emitTypeRef(p)}`).join(", ");
1494
+ const ret = this.emitTypeRef(ref.returnType);
1495
+ return `(${params}) => ${ret}`;
1496
+ }
1497
+ case "StarProjection":
1498
+ return "any";
1499
+ }
1500
+ }
1501
+ // ── Utility helpers ────────────────────────────────────────────────────────
1502
+ emitTypeParamsStr(params) {
1503
+ if (params.length === 0)
1504
+ return "";
1505
+ const ps = params.map((p) => {
1506
+ const bound = p.upperBound ? ` extends ${this.emitTypeRef(p.upperBound)}` : "";
1507
+ return `${p.name}${bound}`;
1508
+ });
1509
+ return `<${ps.join(", ")}>`;
1510
+ }
1511
+ emitParamsStr(params) {
1512
+ return params.map((p) => {
1513
+ const spread = p.vararg ? "..." : "";
1514
+ // vararg params are rest params in TS: `...name: T[]`
1515
+ const type = this.opts.emitTypes
1516
+ ? `: ${this.emitTypeRef(p.type)}${p.vararg ? "[]" : ""}`
1517
+ : "";
1518
+ const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
1519
+ return `${spread}${p.name}${type}${def}`;
1520
+ }).join(", ");
1521
+ }
1522
+ emitComponentPropsStr(params) {
1523
+ if (params.length === 0)
1524
+ return "";
1525
+ return params.map((p) => {
1526
+ const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
1527
+ return `${p.name}${type}`;
1528
+ }).join(", ");
1529
+ }
1530
+ emitSuperTypesStr(superTypes) {
1531
+ if (superTypes.length === 0)
1532
+ return "";
1533
+ const parts = [];
1534
+ let first = true;
1535
+ for (const s of superTypes) {
1536
+ const args = s.delegateArgs
1537
+ ? `(${s.delegateArgs.map((a) => this.emitExpr(a.value)).join(", ")})`
1538
+ : "";
1539
+ if (first) {
1540
+ parts.push(` extends ${this.emitTypeRef(s.type)}${args}`);
1541
+ first = false;
1542
+ }
1543
+ else {
1544
+ parts.push(` implements ${this.emitTypeRef(s.type)}`);
1545
+ }
1546
+ }
1547
+ return parts.join("");
1548
+ }
1549
+ exportPrefix(mods) {
1550
+ if (mods.visibility === "private" || mods.visibility === "internal")
1551
+ return "";
1552
+ return "export ";
1553
+ }
1554
+ visibilityPrefix(mods) {
1555
+ switch (mods.visibility) {
1556
+ case "private": return "private ";
1557
+ case "protected": return "protected ";
1558
+ case "internal": return "/* internal */ ";
1559
+ default: return "";
1560
+ }
1561
+ }
1562
+ unaryOpStr(op) {
1563
+ if (op === "not")
1564
+ return "!";
1565
+ return op;
1566
+ }
1567
+ binaryOpStr(op) {
1568
+ switch (op) {
1569
+ case "and": return "&&";
1570
+ case "or": return "||";
1571
+ case "xor": return "^";
1572
+ case "shl": return "<<";
1573
+ case "shr": return ">>";
1574
+ case "ushr": return ">>>";
1575
+ // === / !== are JS reference equality (Jalvin's triple-equals)
1576
+ case "===": return "===";
1577
+ case "!==": return "!==";
1578
+ case "..": return "/* .. */ +"; // handled by range()
1579
+ case "..<": return "/* ..< */ +";
1580
+ default: return op;
1581
+ }
1582
+ }
1583
+ }
1584
+ exports.CodeGenerator = CodeGenerator;
1585
+ // ---------------------------------------------------------------------------
1586
+ // Type name mappings (Jalvin → TypeScript)
1587
+ // ---------------------------------------------------------------------------
1588
+ const PRIMITIVE_TYPE_MAP = {
1589
+ Int: "number",
1590
+ Long: "bigint",
1591
+ Float: "number",
1592
+ Double: "number",
1593
+ Boolean: "boolean",
1594
+ String: "string",
1595
+ Char: "string",
1596
+ Byte: "number",
1597
+ Short: "number",
1598
+ Unit: "void",
1599
+ Any: "unknown",
1600
+ Nothing: "never",
1601
+ };
1602
+ const GENERIC_TYPE_MAP = {
1603
+ List: "ReadonlyArray",
1604
+ MutableList: "Array",
1605
+ Set: "ReadonlySet",
1606
+ MutableSet: "Set",
1607
+ Map: "ReadonlyMap",
1608
+ MutableMap: "Map",
1609
+ Array: "Array",
1610
+ Pair: "[", // handled specially
1611
+ Triple: "[",
1612
+ Deferred: "Promise",
1613
+ StateFlow: "StateFlow",
1614
+ MutableStateFlow: "MutableStateFlow",
1615
+ Flow: "AsyncIterable",
1616
+ };
1617
+ const PRIMITIVE_TYPES = new Set(Object.keys(PRIMITIVE_TYPE_MAP));
1618
+ // ---------------------------------------------------------------------------
1619
+ // Public helper
1620
+ // ---------------------------------------------------------------------------
1621
+ function generate(program, opts, operatorOverloads, typeMap) {
1622
+ return new CodeGenerator(opts).generate(program, operatorOverloads, typeMap);
1623
+ }
1624
+ //# sourceMappingURL=codegen.js.map