@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.
- package/dist/ast.d.ts +720 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +58 -0
- package/dist/ast.js.map +1 -0
- package/dist/codegen.d.ts +106 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +1624 -0
- package/dist/codegen.js.map +1 -0
- package/dist/diagnostics.d.ts +61 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +104 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer.d.ts +157 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +677 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser.d.ts +114 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +2180 -0
- package/dist/parser.js.map +1 -0
- package/dist/typechecker.d.ts +205 -0
- package/dist/typechecker.d.ts.map +1 -0
- package/dist/typechecker.js +2196 -0
- package/dist/typechecker.js.map +1 -0
- package/package.json +35 -0
package/dist/codegen.js
ADDED
|
@@ -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
|