@jalvin/compiler 2.0.37 → 2.0.39
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/codegen.d.ts +4 -56
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +297 -884
- package/dist/codegen.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/typechecker.d.ts.map +1 -1
- package/dist/typechecker.js +1 -3
- package/dist/typechecker.js.map +1 -1
- package/package.json +1 -1
- package/dist/codegen/index.d.ts +0 -179
- package/dist/codegen/index.d.ts.map +0 -1
- package/dist/codegen/index.js +0 -2207
- package/dist/codegen/index.js.map +0 -1
- package/dist/codegen_freestanding_c.d.ts +0 -9
- package/dist/codegen_freestanding_c.d.ts.map +0 -1
- package/dist/codegen_freestanding_c.js +0 -321
- package/dist/codegen_freestanding_c.js.map +0 -1
- package/dist/lexer/chars.d.ts +0 -5
- package/dist/lexer/chars.d.ts.map +0 -1
- package/dist/lexer/chars.js +0 -20
- package/dist/lexer/chars.js.map +0 -1
- package/dist/lexer/index.d.ts +0 -37
- package/dist/lexer/index.d.ts.map +0 -1
- package/dist/lexer/index.js +0 -630
- package/dist/lexer/index.js.map +0 -1
- package/dist/lexer/tokens.d.ts +0 -135
- package/dist/lexer/tokens.d.ts.map +0 -1
- package/dist/lexer/tokens.js +0 -89
- package/dist/lexer/tokens.js.map +0 -1
- package/dist/parser/constants.d.ts +0 -3
- package/dist/parser/constants.d.ts.map +0 -1
- package/dist/parser/constants.js +0 -7
- package/dist/parser/constants.js.map +0 -1
- package/dist/parser/helpers.d.ts +0 -2
- package/dist/parser/helpers.d.ts.map +0 -1
- package/dist/parser/helpers.js +0 -9
- package/dist/parser/helpers.js.map +0 -1
- package/dist/parser/index.d.ts +0 -118
- package/dist/parser/index.d.ts.map +0 -1
- package/dist/parser/index.js +0 -2387
- package/dist/parser/index.js.map +0 -1
- package/dist/typechecker/globals.d.ts +0 -2
- package/dist/typechecker/globals.d.ts.map +0 -1
- package/dist/typechecker/globals.js +0 -15
- package/dist/typechecker/globals.js.map +0 -1
- package/dist/typechecker/index.d.ts +0 -146
- package/dist/typechecker/index.d.ts.map +0 -1
- package/dist/typechecker/index.js +0 -2288
- package/dist/typechecker/index.js.map +0 -1
- package/dist/typechecker/types.d.ts +0 -66
- package/dist/typechecker/types.d.ts.map +0 -1
- package/dist/typechecker/types.js +0 -39
- package/dist/typechecker/types.js.map +0 -1
package/dist/codegen.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
-
// Jalvin Code Generator — AST → TypeScript
|
|
3
|
+
// Jalvin Code Generator — AST → TypeScript
|
|
4
4
|
//
|
|
5
5
|
// Design principles:
|
|
6
|
-
// • component declarations →
|
|
6
|
+
// • component declarations → Functional components (standard TS)
|
|
7
7
|
// • data class → class with auto-generated copy(), equals(), toString()
|
|
8
8
|
// • sealed class → TypeScript discriminated union + base class
|
|
9
9
|
// • extension functions → module-level functions with receiver as first param,
|
|
@@ -89,7 +89,6 @@ class Writer {
|
|
|
89
89
|
setSourceLine(line) { this.currentLine = line + 1; }
|
|
90
90
|
}
|
|
91
91
|
exports.DEFAULT_CODEGEN_OPTIONS = {
|
|
92
|
-
jsx: false,
|
|
93
92
|
module: "esm",
|
|
94
93
|
emitTypes: false,
|
|
95
94
|
runtimeImport: "@jalvin/runtime",
|
|
@@ -99,7 +98,7 @@ class CodeGenerator {
|
|
|
99
98
|
opts;
|
|
100
99
|
hasComponents = false;
|
|
101
100
|
runtimeSymbolsNeeded = new Set();
|
|
102
|
-
/** Names of true Jalvin components — used to
|
|
101
|
+
/** Names of true Jalvin components — used to detect functional calls */
|
|
103
102
|
userComponentNames = new Set();
|
|
104
103
|
/** Names of all potential components (including UI primitives) — used to detect props-object calls */
|
|
105
104
|
allComponentLikeNames = new Set();
|
|
@@ -135,13 +134,10 @@ class CodeGenerator {
|
|
|
135
134
|
* Maps module specifier (e.g. "src/views") → sorted list of exported names.
|
|
136
135
|
*/
|
|
137
136
|
localStarExports = new Map();
|
|
137
|
+
isInClass = false;
|
|
138
138
|
constructor(opts = {}) {
|
|
139
139
|
this.opts = { ...exports.DEFAULT_CODEGEN_OPTIONS, ...opts };
|
|
140
140
|
}
|
|
141
|
-
/** Returns true if the output should be treated as JSX (React components) */
|
|
142
|
-
get isJsxMode() {
|
|
143
|
-
return this.opts.jsx || this.hasComponents;
|
|
144
|
-
}
|
|
145
141
|
generate(program, operatorOverloads, typeMap) {
|
|
146
142
|
if (operatorOverloads)
|
|
147
143
|
this.operatorOverloadMap = operatorOverloads;
|
|
@@ -216,7 +212,7 @@ class CodeGenerator {
|
|
|
216
212
|
return {
|
|
217
213
|
code,
|
|
218
214
|
lineMap: this.w.lineMap,
|
|
219
|
-
|
|
215
|
+
hasComponents: this.hasComponents,
|
|
220
216
|
};
|
|
221
217
|
}
|
|
222
218
|
/** Walk AST to see if it contains any JsxElement nodes */
|
|
@@ -246,8 +242,12 @@ class CodeGenerator {
|
|
|
246
242
|
// ── Preamble & imports ─────────────────────────────────────────────────────
|
|
247
243
|
buildPreamble() {
|
|
248
244
|
const lines = [];
|
|
245
|
+
lines.push("// ─────────────────────────────────────────────────────────────────────────────");
|
|
246
|
+
lines.push("// JALVIN COMPILER V5 - PURE VANILLA - NO REACT ALLOWED");
|
|
247
|
+
lines.push(`// BUILD ID: ${Date.now()}`);
|
|
248
|
+
lines.push("// ─────────────────────────────────────────────────────────────────────────────");
|
|
249
249
|
if (this.hasComponents) {
|
|
250
|
-
|
|
250
|
+
this.runtimeSymbolsNeeded.add("jalvinCreateElement");
|
|
251
251
|
}
|
|
252
252
|
// Emit compiler-injected runtime symbols, but only those NOT already covered
|
|
253
253
|
// by a star-import's named-import expansion (to avoid duplicate imports).
|
|
@@ -264,19 +264,6 @@ class CodeGenerator {
|
|
|
264
264
|
const sourceRoot = this.opts.sourceRoot ?? process.cwd();
|
|
265
265
|
// Re-emit user imports as ES imports
|
|
266
266
|
for (const imp of program.imports) {
|
|
267
|
-
// Build module specifier.
|
|
268
|
-
//
|
|
269
|
-
// Scoped packages (@org/pkg.Symbol): symbol is an export from the package,
|
|
270
|
-
// strip the last segment.
|
|
271
|
-
// import @jalvin/ui.Column → import { Column } from "@jalvin/ui"
|
|
272
|
-
// import @jalvin/runtime.* → import * as runtime from "@jalvin/runtime"
|
|
273
|
-
//
|
|
274
|
-
// Local imports (a.b.C): check whether the preceding segments already
|
|
275
|
-
// resolve to a file on disk.
|
|
276
|
-
// import src.models.Rotation → src/models/Rotation.ts does NOT exist
|
|
277
|
-
// → import { Rotation } from "src/models/Rotation"
|
|
278
|
-
// import src.models.css.Css → src/models/css.ts DOES exist
|
|
279
|
-
// → import { Css } from "src/models/css"
|
|
280
267
|
const isScoped = imp.path[0].startsWith("@");
|
|
281
268
|
let moduleSpecifier;
|
|
282
269
|
if (imp.star) {
|
|
@@ -291,35 +278,28 @@ class CodeGenerator {
|
|
|
291
278
|
moduleSpecifier = moduleParts[0] + "/" + moduleParts.slice(1).join("/");
|
|
292
279
|
}
|
|
293
280
|
else {
|
|
294
|
-
// Non-scoped import: prefer local-file convention
|
|
295
|
-
// an installed npm package path (e.g. cubing/twisty.TwistyPlayer).
|
|
281
|
+
// Non-scoped import: prefer local-file convention
|
|
296
282
|
const precedingParts = imp.path.slice(0, -1);
|
|
297
283
|
const precedingRelPath = precedingParts.join("/");
|
|
298
284
|
const fileExts = [".ts", ".tsx", ".jalvin"];
|
|
299
285
|
const precedingIsFile = precedingParts.length > 0 && fileExts.some((ext) => fs.existsSync(nodePath.join(sourceRoot, precedingRelPath + ext)));
|
|
300
286
|
const looksLikeNpmPackage = this.isLikelyNodeModuleImport(imp.path, sourceRoot);
|
|
301
287
|
moduleSpecifier = precedingIsFile
|
|
302
|
-
? precedingRelPath
|
|
288
|
+
? precedingRelPath
|
|
303
289
|
: looksLikeNpmPackage
|
|
304
|
-
? precedingRelPath
|
|
305
|
-
: imp.path.join("/");
|
|
290
|
+
? precedingRelPath
|
|
291
|
+
: imp.path.join("/");
|
|
306
292
|
}
|
|
307
293
|
if (imp.star && isScoped && this.externalStarCandidates.size > 0) {
|
|
308
|
-
// Named-import expansion of a scoped star import.
|
|
309
|
-
// Emit explicit named imports for each external symbol used in the file so
|
|
310
|
-
// that symbols like `ViewModel`, `mutableStateOf` are directly in scope.
|
|
311
294
|
const symbols = [...this.externalStarCandidates].sort();
|
|
312
295
|
this.w.writeIndentedLine(`import { ${symbols.join(", ")} } from "${moduleSpecifier}";`);
|
|
313
296
|
this.handledStarImportModules.add(moduleSpecifier);
|
|
314
297
|
}
|
|
315
298
|
else if (imp.star && !isScoped && this.localStarExports.has(moduleSpecifier)) {
|
|
316
|
-
// Local wildcard import: import src.module.* — expand to named imports
|
|
317
|
-
// so all exported symbols are directly in scope (no namespace qualifier needed).
|
|
318
299
|
const symbols = this.localStarExports.get(moduleSpecifier).slice().sort();
|
|
319
300
|
this.w.writeIndentedLine(`import { ${symbols.join(", ")} } from "${moduleSpecifier}";`);
|
|
320
301
|
}
|
|
321
302
|
else if (imp.star) {
|
|
322
|
-
// Fallback: namespace import (multiple scoped star imports or no candidates).
|
|
323
303
|
this.w.writeIndentedLine(`import * as ${imp.path[imp.path.length - 1]} from "${moduleSpecifier}";`);
|
|
324
304
|
}
|
|
325
305
|
else if (imp.alias) {
|
|
@@ -333,11 +313,6 @@ class CodeGenerator {
|
|
|
333
313
|
if (program.imports.length > 0)
|
|
334
314
|
this.w.writeLine();
|
|
335
315
|
}
|
|
336
|
-
/**
|
|
337
|
-
* Heuristic for non-scoped imports: if the first path segment resolves to an
|
|
338
|
-
* installed package in node_modules, treat the import as npm/ESM and strip
|
|
339
|
-
* the trailing symbol name from the module specifier.
|
|
340
|
-
*/
|
|
341
316
|
isLikelyNodeModuleImport(pathParts, sourceRoot) {
|
|
342
317
|
if (pathParts.length < 2)
|
|
343
318
|
return false;
|
|
@@ -355,14 +330,12 @@ class CodeGenerator {
|
|
|
355
330
|
return false;
|
|
356
331
|
}
|
|
357
332
|
// ── Top-level declarations ─────────────────────────────────────────────────
|
|
358
|
-
/** Emit a @deprecated JSDoc block if the declaration has @Nuked. */
|
|
359
333
|
emitAnnotations(mods) {
|
|
360
334
|
for (const ann of mods.annotations) {
|
|
361
335
|
if (ann.name === "Nuked") {
|
|
362
336
|
const reason = ann.args ? ` ${ann.args.replace(/^["']|["']$/g, "")}` : "";
|
|
363
337
|
this.w.writeIndentedLine(`/** @deprecated${reason} */`);
|
|
364
338
|
}
|
|
365
|
-
// Other annotations are silently ignored for now (pass-through model)
|
|
366
339
|
}
|
|
367
340
|
}
|
|
368
341
|
emitTopLevelDecl(decl) {
|
|
@@ -417,7 +390,6 @@ class CodeGenerator {
|
|
|
417
390
|
? `: ${this.emitTypeRef(decl.returnType)}`
|
|
418
391
|
: "";
|
|
419
392
|
if (!decl.body) {
|
|
420
|
-
// Abstract / interface method
|
|
421
393
|
this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType};`);
|
|
422
394
|
return;
|
|
423
395
|
}
|
|
@@ -434,7 +406,6 @@ class CodeGenerator {
|
|
|
434
406
|
this.w.writeIndentedLine(`}`);
|
|
435
407
|
}
|
|
436
408
|
else {
|
|
437
|
-
// Expression body
|
|
438
409
|
if (memberOf) {
|
|
439
410
|
this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
|
|
440
411
|
}
|
|
@@ -475,73 +446,52 @@ class CodeGenerator {
|
|
|
475
446
|
this.w.writeIndentedLine(`}`);
|
|
476
447
|
}
|
|
477
448
|
}
|
|
478
|
-
// ── component declarations →
|
|
449
|
+
// ── component declarations → Functional components ────────────────────
|
|
479
450
|
emitComponentDecl(decl) {
|
|
480
451
|
this.emitAnnotations(decl.modifiers);
|
|
481
452
|
this.hasComponents = true;
|
|
482
|
-
const vis = this.exportPrefix(decl.modifiers);
|
|
453
|
+
const vis = this.isInClass ? this.visibilityPrefix(decl.modifiers) : this.exportPrefix(decl.modifiers);
|
|
483
454
|
const hasChildren = decl.params.some((p) => p.name === "children");
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
this.w.writeIndentedLine(`readonly ${p.name}${optional}: ${typeStr};`);
|
|
494
|
-
}
|
|
495
|
-
this.w.popIndent();
|
|
496
|
-
this.w.writeIndentedLine(`}`);
|
|
497
|
-
this.w.writeLine();
|
|
498
|
-
}
|
|
499
|
-
const propsDestructure = decl.params.length > 0
|
|
500
|
-
? `{ ${decl.params.map((p) => p.name + (p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "")).join(", ")} }: ${decl.name}Props`
|
|
501
|
-
: "";
|
|
502
|
-
this.w.writeIndentedLine(`${vis}function ${decl.name}(${propsDestructure}) {`);
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
// Direct function call format: children are separate positional parameter
|
|
506
|
-
const propsParams = decl.params.filter((p) => p.name !== "children");
|
|
507
|
-
// Props interface
|
|
508
|
-
if (propsParams.length > 0) {
|
|
509
|
-
this.w.writeIndentedLine(`interface ${decl.name}Props {`);
|
|
510
|
-
this.w.pushIndent();
|
|
511
|
-
for (const p of propsParams) {
|
|
512
|
-
const optional = p.defaultValue ? "?" : "";
|
|
513
|
-
const typeStr = this.opts.emitTypes ? this.emitTypeRef(p.type) : "any";
|
|
514
|
-
this.w.writeIndentedLine(`readonly ${p.name}${optional}: ${typeStr};`);
|
|
515
|
-
}
|
|
516
|
-
this.w.popIndent();
|
|
517
|
-
this.w.writeIndentedLine(`}`);
|
|
518
|
-
this.w.writeLine();
|
|
519
|
-
}
|
|
520
|
-
// Build the function signature
|
|
521
|
-
let signature = "";
|
|
522
|
-
if (propsParams.length > 0) {
|
|
523
|
-
signature = `{ ${propsParams.map((p) => p.name + (p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "")).join(", ")} }: ${decl.name}Props`;
|
|
524
|
-
}
|
|
525
|
-
else if (hasChildren) {
|
|
526
|
-
// Children-only component needs {} as first param to absorb the empty props object from call site
|
|
527
|
-
signature = "{}";
|
|
528
|
-
}
|
|
529
|
-
if (hasChildren) {
|
|
530
|
-
signature += signature ? ", children?: any" : "children?: any";
|
|
455
|
+
const propsParams = decl.params.filter((p) => p.name !== "children");
|
|
456
|
+
// Props interface
|
|
457
|
+
if (!this.isInClass && propsParams.length > 0) {
|
|
458
|
+
this.w.writeIndentedLine(`interface ${decl.name}Props {`);
|
|
459
|
+
this.w.pushIndent();
|
|
460
|
+
for (const p of propsParams) {
|
|
461
|
+
const optional = p.defaultValue ? "?" : "";
|
|
462
|
+
const typeStr = this.opts.emitTypes ? this.emitTypeRef(p.type) : "any";
|
|
463
|
+
this.w.writeIndentedLine(`readonly ${p.name}${optional}: ${typeStr};`);
|
|
531
464
|
}
|
|
532
|
-
this.w.
|
|
465
|
+
this.w.popIndent();
|
|
466
|
+
this.w.writeIndentedLine(`}`);
|
|
467
|
+
this.w.writeLine();
|
|
533
468
|
}
|
|
469
|
+
const propsType = this.isInClass
|
|
470
|
+
? `{ ${propsParams.map(p => {
|
|
471
|
+
const optional = p.defaultValue ? "?" : "";
|
|
472
|
+
const typeStr = this.opts.emitTypes ? this.emitTypeRef(p.type) : "any";
|
|
473
|
+
return `readonly ${p.name}${optional}: ${typeStr}`;
|
|
474
|
+
}).join("; ")} }`
|
|
475
|
+
: `${decl.name}Props`;
|
|
476
|
+
let signature = "";
|
|
477
|
+
if (propsParams.length > 0) {
|
|
478
|
+
signature = `{ ${propsParams.map((p) => p.name + (p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "")).join(", ")} }: ${propsType}`;
|
|
479
|
+
}
|
|
480
|
+
else if (hasChildren) {
|
|
481
|
+
signature = "{}";
|
|
482
|
+
}
|
|
483
|
+
if (hasChildren) {
|
|
484
|
+
signature += signature ? ", children?: any" : "children?: any";
|
|
485
|
+
}
|
|
486
|
+
const sig = this.isInClass
|
|
487
|
+
? `${decl.name}(${signature})`
|
|
488
|
+
: `function ${decl.name}(${signature})`;
|
|
489
|
+
this.w.writeIndentedLine(`${vis}${sig} {`);
|
|
534
490
|
this.w.pushIndent();
|
|
535
491
|
this.emitComponentBlock(decl.body);
|
|
536
492
|
this.w.popIndent();
|
|
537
493
|
this.w.writeIndentedLine(`}`);
|
|
538
494
|
}
|
|
539
|
-
/**
|
|
540
|
-
* Emit the body of a `component fun` block.
|
|
541
|
-
* The last statement is emitted in tail position — if it is an expression,
|
|
542
|
-
* an if/else chain, or a when block, the innermost UI call is implicitly
|
|
543
|
-
* returned (implicit return of the last expression).
|
|
544
|
-
*/
|
|
545
495
|
emitComponentBlock(block) {
|
|
546
496
|
const stmts = block.statements;
|
|
547
497
|
for (let i = 0; i < stmts.length; i++) {
|
|
@@ -554,10 +504,8 @@ class CodeGenerator {
|
|
|
554
504
|
}
|
|
555
505
|
}
|
|
556
506
|
}
|
|
557
|
-
/** Emit a statement in tail position, adding implicit return where applicable. */
|
|
558
507
|
emitTailStmt(stmt) {
|
|
559
508
|
if (stmt.kind === "ExprStmt") {
|
|
560
|
-
// Implicit return: last expression in a component block
|
|
561
509
|
this.w.writeIndentedLine(`return ${this.emitExpr(stmt.expr)};`);
|
|
562
510
|
}
|
|
563
511
|
else if (stmt.kind === "IfStmt") {
|
|
@@ -570,7 +518,6 @@ class CodeGenerator {
|
|
|
570
518
|
this.emitStmt(stmt);
|
|
571
519
|
}
|
|
572
520
|
}
|
|
573
|
-
/** Emit a block in tail position, treating its last statement as a tail expression. */
|
|
574
521
|
emitTailBlock(block) {
|
|
575
522
|
const stmts = block.statements;
|
|
576
523
|
for (let i = 0; i < stmts.length; i++) {
|
|
@@ -583,7 +530,6 @@ class CodeGenerator {
|
|
|
583
530
|
}
|
|
584
531
|
}
|
|
585
532
|
}
|
|
586
|
-
/** Like emitIfStmt but with tail-return applied recursively to each branch body. */
|
|
587
533
|
emitIfStmtWithTailReturn(stmt) {
|
|
588
534
|
this.w.writeIndentedLine(`if (${this.emitExpr(stmt.condition)}) {`);
|
|
589
535
|
this.w.pushIndent();
|
|
@@ -606,7 +552,6 @@ class CodeGenerator {
|
|
|
606
552
|
this.w.writeIndentedLine(`}`);
|
|
607
553
|
}
|
|
608
554
|
}
|
|
609
|
-
/** Like emitWhenStmt but with tail-return applied recursively to each branch body. */
|
|
610
555
|
emitWhenStmtWithTailReturn(stmt) {
|
|
611
556
|
const subjectVar = stmt.subject ? "__when_subject__" : null;
|
|
612
557
|
if (stmt.subject) {
|
|
@@ -643,15 +588,12 @@ class CodeGenerator {
|
|
|
643
588
|
const superTypes = this.emitSuperTypesStr(decl.superTypes);
|
|
644
589
|
this.w.writeIndentedLine(`${vis}${abstract}class ${decl.name}${typeParams}${superTypes} {`);
|
|
645
590
|
this.w.pushIndent();
|
|
646
|
-
// The first supertype's delegateArgs become the `super(args)` call inside the constructor.
|
|
647
591
|
const superDelegateArgs = decl.superTypes[0]?.delegateArgs ?? null;
|
|
648
592
|
if (decl.primaryConstructor) {
|
|
649
593
|
const initBlocks = decl.body?.members.filter((m) => m.kind === "InitBlock") ?? [];
|
|
650
594
|
this.emitPrimaryConstructorProps(decl.primaryConstructor.params, initBlocks, superDelegateArgs);
|
|
651
595
|
}
|
|
652
596
|
else if (superDelegateArgs !== null) {
|
|
653
|
-
// No primary constructor but the supertype has explicit delegation args —
|
|
654
|
-
// emit a minimal constructor that forwards them to super().
|
|
655
597
|
const superArgsStr = superDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
|
|
656
598
|
this.w.writeIndentedLine(`constructor() {`);
|
|
657
599
|
this.w.pushIndent();
|
|
@@ -674,24 +616,20 @@ class CodeGenerator {
|
|
|
674
616
|
const props = decl.primaryConstructor.params;
|
|
675
617
|
this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams}${superTypes} {`);
|
|
676
618
|
this.w.pushIndent();
|
|
677
|
-
// Discriminant for sealed class sub-types
|
|
678
619
|
if (kindName) {
|
|
679
620
|
this.w.writeIndentedLine(`readonly __kind = "${kindName}" as const;`);
|
|
680
621
|
}
|
|
681
|
-
// Constructor params → readonly properties
|
|
682
622
|
for (const p of props) {
|
|
683
623
|
const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
|
|
684
624
|
this.w.writeIndentedLine(`readonly ${p.name}${t};`);
|
|
685
625
|
}
|
|
686
626
|
this.w.writeLine();
|
|
687
|
-
// Constructor
|
|
688
627
|
const ctorParams = props.map((p) => {
|
|
689
628
|
const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
|
|
690
629
|
return `${p.name}${t}`;
|
|
691
630
|
}).join(", ");
|
|
692
631
|
this.w.writeIndentedLine(`constructor(${ctorParams}) {`);
|
|
693
632
|
this.w.pushIndent();
|
|
694
|
-
// Emit super() with actual delegation args (or empty call if delegateArgs is []).
|
|
695
633
|
const dataSuperDelegateArgs = decl.superTypes[0]?.delegateArgs ?? null;
|
|
696
634
|
if (dataSuperDelegateArgs !== null) {
|
|
697
635
|
const superArgsStr = dataSuperDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
|
|
@@ -703,7 +641,6 @@ class CodeGenerator {
|
|
|
703
641
|
this.w.popIndent();
|
|
704
642
|
this.w.writeIndentedLine(`}`);
|
|
705
643
|
this.w.writeLine();
|
|
706
|
-
// copy()
|
|
707
644
|
const copyParams = props.map((p) => {
|
|
708
645
|
const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
|
|
709
646
|
return `${p.name}${t} = this.${p.name}`;
|
|
@@ -715,7 +652,6 @@ class CodeGenerator {
|
|
|
715
652
|
this.w.popIndent();
|
|
716
653
|
this.w.writeIndentedLine(`}`);
|
|
717
654
|
this.w.writeLine();
|
|
718
|
-
// equals()
|
|
719
655
|
this.runtimeSymbolsNeeded.add("jalvinEquals");
|
|
720
656
|
const eqChecks = props.map((p) => `jalvinEquals(this.${p.name}, other.${p.name})`).join(" && ") || "true";
|
|
721
657
|
this.w.writeIndentedLine(`equals(other: unknown): boolean {`);
|
|
@@ -725,7 +661,6 @@ class CodeGenerator {
|
|
|
725
661
|
this.w.popIndent();
|
|
726
662
|
this.w.writeIndentedLine(`}`);
|
|
727
663
|
this.w.writeLine();
|
|
728
|
-
// toString()
|
|
729
664
|
const toStringParts = props.map((p) => `${p.name}=\${this.${p.name}}`).join(", ");
|
|
730
665
|
this.w.writeIndentedLine(`toString(): string {`);
|
|
731
666
|
this.w.pushIndent();
|
|
@@ -733,7 +668,6 @@ class CodeGenerator {
|
|
|
733
668
|
this.w.popIndent();
|
|
734
669
|
this.w.writeIndentedLine(`}`);
|
|
735
670
|
this.w.writeLine();
|
|
736
|
-
// hashCode() — djb2
|
|
737
671
|
this.w.writeIndentedLine(`hashCode(): number {`);
|
|
738
672
|
this.w.pushIndent();
|
|
739
673
|
this.w.writeIndentedLine(`let h = 17;`);
|
|
@@ -769,7 +703,6 @@ class CodeGenerator {
|
|
|
769
703
|
}
|
|
770
704
|
}
|
|
771
705
|
}
|
|
772
|
-
// Emit the abstract base class (only non-subtype members in the body)
|
|
773
706
|
this.w.writeIndentedLine(`${vis}abstract class ${decl.name}${typeParams} {`);
|
|
774
707
|
this.w.pushIndent();
|
|
775
708
|
this.w.writeIndentedLine(`abstract readonly __kind: string;`);
|
|
@@ -782,7 +715,6 @@ class CodeGenerator {
|
|
|
782
715
|
this.w.writeLine();
|
|
783
716
|
if (subDecls.length === 0)
|
|
784
717
|
return;
|
|
785
|
-
// Emit non-object sub-declarations at module level (before namespace)
|
|
786
718
|
for (const sub of subDecls) {
|
|
787
719
|
if (sub.kind === "DataClassDecl") {
|
|
788
720
|
this.emitDataClassDecl(sub, sub.name);
|
|
@@ -793,12 +725,10 @@ class CodeGenerator {
|
|
|
793
725
|
this.w.writeLine();
|
|
794
726
|
}
|
|
795
727
|
}
|
|
796
|
-
// Emit namespace merging so SealedClass.SubType is accessible
|
|
797
728
|
this.w.writeIndentedLine(`${vis}namespace ${decl.name} {`);
|
|
798
729
|
this.w.pushIndent();
|
|
799
730
|
for (const sub of subDecls) {
|
|
800
731
|
if (sub.kind === "ObjectDecl") {
|
|
801
|
-
// Singleton object — emit inline in namespace with __kind discriminant
|
|
802
732
|
const superStr = sub.superTypes.length > 0
|
|
803
733
|
? this.emitSuperTypesStr(sub.superTypes)
|
|
804
734
|
: ` extends ${decl.name}`;
|
|
@@ -814,7 +744,6 @@ class CodeGenerator {
|
|
|
814
744
|
this.w.writeIndentedLine(`export const ${sub.name} = new (class${superStr} { ${membersStr} })();`);
|
|
815
745
|
}
|
|
816
746
|
else {
|
|
817
|
-
// DataClassDecl / ClassDecl — already emitted at module level, re-export
|
|
818
747
|
this.w.writeIndentedLine(`export { ${sub.name} };`);
|
|
819
748
|
}
|
|
820
749
|
}
|
|
@@ -825,12 +754,8 @@ class CodeGenerator {
|
|
|
825
754
|
emitEnumClassDecl(decl) {
|
|
826
755
|
const vis = this.exportPrefix(decl.modifiers);
|
|
827
756
|
const typeParams = this.emitTypeParamsStr(decl.typeParams);
|
|
828
|
-
// Emit the enum as a TypeScript class with static singleton instances.
|
|
829
|
-
// This preserves the `EnumClass.ENTRY` access pattern and allows
|
|
830
|
-
// methods/properties to be attached to entries.
|
|
831
757
|
this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams} {`);
|
|
832
758
|
this.w.pushIndent();
|
|
833
|
-
// Private constructor so entries are the only instances
|
|
834
759
|
if (decl.primaryConstructor && decl.primaryConstructor.params.length > 0) {
|
|
835
760
|
const ctorParams = decl.primaryConstructor.params
|
|
836
761
|
.map((p) => {
|
|
@@ -845,7 +770,6 @@ class CodeGenerator {
|
|
|
845
770
|
this.w.writeIndentedLine(`private constructor(readonly name: string, readonly ordinal: number) {}`);
|
|
846
771
|
}
|
|
847
772
|
this.w.writeLine();
|
|
848
|
-
// Static entry instances
|
|
849
773
|
for (let i = 0; i < decl.entries.length; i++) {
|
|
850
774
|
const entry = decl.entries[i];
|
|
851
775
|
const args = entry.args.length > 0
|
|
@@ -855,7 +779,6 @@ class CodeGenerator {
|
|
|
855
779
|
}
|
|
856
780
|
if (decl.entries.length > 0) {
|
|
857
781
|
this.w.writeLine();
|
|
858
|
-
// values() helper
|
|
859
782
|
const entryList = decl.entries.map((e) => `${decl.name}.${e.name}`).join(", ");
|
|
860
783
|
this.w.writeIndentedLine(`static values(): ${decl.name}[] { return [${entryList}]; }`);
|
|
861
784
|
this.w.writeIndentedLine(`static valueOf(name: string): ${decl.name} {`);
|
|
@@ -873,14 +796,12 @@ class CodeGenerator {
|
|
|
873
796
|
this.w.popIndent();
|
|
874
797
|
this.w.writeIndentedLine(`}`);
|
|
875
798
|
}
|
|
876
|
-
// ── destructuring declaration — val (a, b) = expr ─────────────────────────
|
|
877
799
|
emitDestructuringDecl(decl, _memberOf) {
|
|
878
800
|
const kw = decl.mutable ? "let" : "const";
|
|
879
801
|
const names = decl.names.map((n) => n ?? "_").join(", ");
|
|
880
802
|
const init = this.emitExpr(decl.initializer);
|
|
881
803
|
this.w.writeIndentedLine(`${kw} [${names}] = ${init};`);
|
|
882
804
|
}
|
|
883
|
-
// ── interface ──────────────────────────────────────────────────────────────
|
|
884
805
|
emitInterfaceDecl(decl) {
|
|
885
806
|
this.emitAnnotations(decl.modifiers);
|
|
886
807
|
const vis = this.exportPrefix(decl.modifiers);
|
|
@@ -898,12 +819,9 @@ class CodeGenerator {
|
|
|
898
819
|
this.w.popIndent();
|
|
899
820
|
this.w.writeIndentedLine(`}`);
|
|
900
821
|
}
|
|
901
|
-
// ── object declaration → singleton ────────────────────────────────────────
|
|
902
822
|
emitObjectDecl(decl) {
|
|
903
|
-
if (!decl.name)
|
|
904
|
-
// Anonymous object — emitted inline
|
|
823
|
+
if (!decl.name)
|
|
905
824
|
return;
|
|
906
|
-
}
|
|
907
825
|
this.emitAnnotations(decl.modifiers);
|
|
908
826
|
const vis = this.exportPrefix(decl.modifiers);
|
|
909
827
|
const superTypes = this.emitSuperTypesStr(decl.superTypes);
|
|
@@ -913,16 +831,11 @@ class CodeGenerator {
|
|
|
913
831
|
this.w.popIndent();
|
|
914
832
|
this.w.writeIndentedLine(`})();`);
|
|
915
833
|
}
|
|
916
|
-
// ── type alias ─────────────────────────────────────────────────────────────
|
|
917
834
|
emitTypeAliasDecl(decl) {
|
|
918
835
|
const vis = this.exportPrefix(decl.modifiers);
|
|
919
836
|
const typeParams = this.emitTypeParamsStr(decl.typeParams);
|
|
920
837
|
this.w.writeIndentedLine(`${vis}type ${decl.name}${typeParams} = ${this.emitTypeRef(decl.type)};`);
|
|
921
838
|
}
|
|
922
|
-
// ── extension functions ────────────────────────────────────────────────────
|
|
923
|
-
// Emitted as standalone free functions with receiver as explicit first param.
|
|
924
|
-
// For class types, also patched onto the prototype for dot-call syntax.
|
|
925
|
-
// For primitive types, call sites are rewritten via `primitiveExtensions`.
|
|
926
839
|
emitExtensionFunDecl(decl) {
|
|
927
840
|
const isSuspend = AST.isSuspend(decl.modifiers);
|
|
928
841
|
const asyncKw = isSuspend ? "async " : "";
|
|
@@ -933,9 +846,6 @@ class CodeGenerator {
|
|
|
933
846
|
? `: ${this.emitTypeRef(decl.returnType)}`
|
|
934
847
|
: "";
|
|
935
848
|
const fnName = `__ext_${receiverType.replace(/[^a-zA-Z0-9_]/g, "_")}_${decl.name}`;
|
|
936
|
-
// Emit as a free function: receiver is the first explicit parameter `$receiver`.
|
|
937
|
-
// Inside the function body, `this` is rebound to `$receiver` so Jalvin's
|
|
938
|
-
// `this.prop` references work correctly.
|
|
939
849
|
this.w.writeIndentedLine(`${asyncKw}function ${fnName}${typeParams}($receiver: ${receiverType}${params ? ", " + params : ""})${retType} {`);
|
|
940
850
|
this.w.pushIndent();
|
|
941
851
|
if (decl.body.kind === "Block") {
|
|
@@ -954,14 +864,12 @@ class CodeGenerator {
|
|
|
954
864
|
const simpleReceiverName = this.receiverClassName(decl.receiver);
|
|
955
865
|
if (simpleReceiverName) {
|
|
956
866
|
if (PRIMITIVE_TYPES.has(simpleReceiverName)) {
|
|
957
|
-
// Register for call-site rewriting — cannot patch primitive prototypes
|
|
958
867
|
if (!this.primitiveExtensions.has(simpleReceiverName)) {
|
|
959
868
|
this.primitiveExtensions.set(simpleReceiverName, new Map());
|
|
960
869
|
}
|
|
961
870
|
this.primitiveExtensions.get(simpleReceiverName).set(decl.name, fnName);
|
|
962
871
|
}
|
|
963
872
|
else {
|
|
964
|
-
// Class types — prototype monkey-patch for dot-call syntax
|
|
965
873
|
this.w.writeIndentedLine(`// Extension: ${receiverType}.${decl.name}`);
|
|
966
874
|
this.w.writeIndentedLine(`(${simpleReceiverName}.prototype as any).${decl.name} = function(this: ${receiverType}, ...args: unknown[]) { return (${fnName} as Function)(this, ...args); };`);
|
|
967
875
|
}
|
|
@@ -974,11 +882,6 @@ class CodeGenerator {
|
|
|
974
882
|
return ref.base.name[ref.base.name.length - 1] ?? null;
|
|
975
883
|
return null;
|
|
976
884
|
}
|
|
977
|
-
/**
|
|
978
|
-
* Maps a JType to the primitive receiver type name used as a key
|
|
979
|
-
* in `primitiveExtensions` (e.g. JType `{ tag: "string" }` → `"String"`).
|
|
980
|
-
* Returns null for non-primitive / unknown types.
|
|
981
|
-
*/
|
|
982
885
|
jTypeToReceiverName(type) {
|
|
983
886
|
const tagToReceiverName = {
|
|
984
887
|
string: "String",
|
|
@@ -998,19 +901,16 @@ class CodeGenerator {
|
|
|
998
901
|
m.name === "invoke" &&
|
|
999
902
|
m.modifiers.modifiers.includes("operator"));
|
|
1000
903
|
}
|
|
1001
|
-
// ── property declaration ───────────────────────────────────────────────────
|
|
1002
904
|
emitPropertyDecl(decl, member, isLocal = false) {
|
|
1003
905
|
this.emitAnnotations(decl.modifiers);
|
|
1004
906
|
const vis = member ? this.visibilityPrefix(decl.modifiers) : isLocal ? "" : this.exportPrefix(decl.modifiers);
|
|
1005
907
|
const isConst = decl.modifiers.modifiers.includes("const");
|
|
1006
908
|
const isLateinit = decl.modifiers.modifiers.includes("lateinit");
|
|
1007
|
-
// `const val` → `const` at module level; inside classes treated as `static readonly`
|
|
1008
909
|
const kw = member
|
|
1009
910
|
? (decl.mutable ? "" : "readonly ")
|
|
1010
911
|
: (isConst ? "const " : decl.mutable ? "let " : "const ");
|
|
1011
912
|
const type = decl.type && this.opts.emitTypes ? `: ${this.emitTypeRef(decl.type)}` : "";
|
|
1012
913
|
if (decl.delegate) {
|
|
1013
|
-
// Delegated property — wrap in a getter/setter pair using the delegate
|
|
1014
914
|
this.runtimeSymbolsNeeded.add("delegate");
|
|
1015
915
|
const delegateExpr = this.emitExpr(decl.delegate);
|
|
1016
916
|
if (member) {
|
|
@@ -1020,28 +920,22 @@ class CodeGenerator {
|
|
|
1020
920
|
}
|
|
1021
921
|
}
|
|
1022
922
|
else {
|
|
1023
|
-
// Non-member delegate: resolve via delegate().getValue() so the identifier
|
|
1024
|
-
// holds the delegated value, not the raw delegate wrapper object.
|
|
1025
923
|
this.w.writeIndentedLine(`${kw}${decl.name}${type} = delegate(${delegateExpr}, "${decl.name}", null).getValue();`);
|
|
1026
924
|
}
|
|
1027
925
|
return;
|
|
1028
926
|
}
|
|
1029
|
-
const init = decl.initializer ? ` = ${this.emitExpr(decl.initializer)}` :
|
|
927
|
+
const init = decl.initializer ? ` = ${this.emitExpr(decl.initializer)}` : "";
|
|
1030
928
|
if (member && !decl.getter) {
|
|
1031
|
-
// Skip backing field when a getter is defined — TypeScript forbids both
|
|
1032
|
-
// `readonly name: T` and `get name()` with the same name in a class.
|
|
1033
929
|
const modStr = isConst ? "static readonly " : decl.mutable ? "" : "readonly ";
|
|
1034
930
|
this.w.writeIndentedLine(`${vis}${modStr}${decl.name}${type}${init};`);
|
|
1035
931
|
}
|
|
1036
932
|
else if (!member) {
|
|
1037
933
|
this.w.writeIndentedLine(`${vis}${kw}${decl.name}${type}${init};`);
|
|
1038
934
|
}
|
|
1039
|
-
if (decl.getter)
|
|
935
|
+
if (decl.getter)
|
|
1040
936
|
this.emitPropertyAccessor("get", decl.name, decl.getter, member, type);
|
|
1041
|
-
|
|
1042
|
-
if (decl.setter) {
|
|
937
|
+
if (decl.setter)
|
|
1043
938
|
this.emitPropertyAccessor("set", decl.name, decl.setter, member, type);
|
|
1044
|
-
}
|
|
1045
939
|
}
|
|
1046
940
|
emitPropertyAccessor(kind, name, acc, member, type) {
|
|
1047
941
|
const vis = this.visibilityPrefix(acc.modifiers);
|
|
@@ -1061,14 +955,19 @@ class CodeGenerator {
|
|
|
1061
955
|
this.w.popIndent();
|
|
1062
956
|
this.w.writeIndentedLine(`}`);
|
|
1063
957
|
}
|
|
1064
|
-
// ── class body ─────────────────────────────────────────────────────────────
|
|
1065
958
|
emitClassBody(body) {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
959
|
+
const old = this.isInClass;
|
|
960
|
+
this.isInClass = true;
|
|
961
|
+
try {
|
|
962
|
+
for (const member of body.members) {
|
|
963
|
+
if (member.kind === "InitBlock")
|
|
964
|
+
continue;
|
|
965
|
+
this.emitClassMember(member);
|
|
966
|
+
this.w.writeLine();
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
finally {
|
|
970
|
+
this.isInClass = old;
|
|
1072
971
|
}
|
|
1073
972
|
}
|
|
1074
973
|
emitClassMember(member) {
|
|
@@ -1100,7 +999,6 @@ class CodeGenerator {
|
|
|
1100
999
|
case "CompanionObject":
|
|
1101
1000
|
this.emitCompanionObject(member);
|
|
1102
1001
|
break;
|
|
1103
|
-
case "InitBlock": /* handled in constructor */ break;
|
|
1104
1002
|
case "SecondaryConstructor":
|
|
1105
1003
|
this.emitSecondaryConstructor(member);
|
|
1106
1004
|
break;
|
|
@@ -1117,16 +1015,13 @@ class CodeGenerator {
|
|
|
1117
1015
|
this.w.writeIndentedLine(`${ro}${p.name}${type};`);
|
|
1118
1016
|
}
|
|
1119
1017
|
}
|
|
1120
|
-
// Constructor
|
|
1121
1018
|
const ctorParams = params.map((p) => {
|
|
1122
|
-
const ro = p.propertyKind === "val" ? "readonly " : p.propertyKind === "var" ? "" : "";
|
|
1123
1019
|
const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
|
|
1124
1020
|
const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
|
|
1125
1021
|
return `${p.name}${type}${def}`;
|
|
1126
1022
|
});
|
|
1127
1023
|
this.w.writeIndentedLine(`constructor(${ctorParams.join(", ")}) {`);
|
|
1128
1024
|
this.w.pushIndent();
|
|
1129
|
-
// super() call must be the first statement if there is a supertype.
|
|
1130
1025
|
if (superDelegateArgs !== null && superDelegateArgs !== undefined) {
|
|
1131
1026
|
const superArgsStr = superDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
|
|
1132
1027
|
this.w.writeIndentedLine(`super(${superArgsStr});`);
|
|
@@ -1136,17 +1031,13 @@ class CodeGenerator {
|
|
|
1136
1031
|
this.w.writeIndentedLine(`this.${p.name} = ${p.name};`);
|
|
1137
1032
|
}
|
|
1138
1033
|
}
|
|
1139
|
-
|
|
1140
|
-
for (const ib of (initBlocks ?? [])) {
|
|
1034
|
+
for (const ib of (initBlocks ?? []))
|
|
1141
1035
|
this.emitBlock(ib.body);
|
|
1142
|
-
}
|
|
1143
1036
|
this.w.popIndent();
|
|
1144
1037
|
this.w.writeIndentedLine(`}`);
|
|
1145
1038
|
this.w.writeLine();
|
|
1146
1039
|
}
|
|
1147
1040
|
emitCompanionObject(co) {
|
|
1148
|
-
// Emit companion object members as `static` members of the outer class so that
|
|
1149
|
-
// `MyClass.factoryMethod()` works directly (standard semantics).
|
|
1150
1041
|
for (const member of co.body.members) {
|
|
1151
1042
|
if (member.kind === "FunDecl") {
|
|
1152
1043
|
const vis = this.visibilityPrefix(member.modifiers);
|
|
@@ -1191,11 +1082,9 @@ class CodeGenerator {
|
|
|
1191
1082
|
this.w.popIndent();
|
|
1192
1083
|
this.w.writeIndentedLine(`}`);
|
|
1193
1084
|
}
|
|
1194
|
-
// ── Statements ─────────────────────────────────────────────────────────────
|
|
1195
1085
|
emitBlock(block) {
|
|
1196
|
-
for (const stmt of block.statements)
|
|
1086
|
+
for (const stmt of block.statements)
|
|
1197
1087
|
this.emitStmt(stmt);
|
|
1198
|
-
}
|
|
1199
1088
|
}
|
|
1200
1089
|
emitStmt(stmt) {
|
|
1201
1090
|
switch (stmt.kind) {
|
|
@@ -1284,8 +1173,6 @@ class CodeGenerator {
|
|
|
1284
1173
|
}
|
|
1285
1174
|
}
|
|
1286
1175
|
emitWhenStmt(stmt) {
|
|
1287
|
-
// when(subject) { is Foo -> ... else -> ... }
|
|
1288
|
-
// Compiles to a series of if/else-if chains
|
|
1289
1176
|
const subjectVar = stmt.subject ? "__when_subject__" : null;
|
|
1290
1177
|
if (stmt.subject) {
|
|
1291
1178
|
const binding = stmt.subject.binding ?? subjectVar;
|
|
@@ -1302,12 +1189,10 @@ class CodeGenerator {
|
|
|
1302
1189
|
}
|
|
1303
1190
|
first = false;
|
|
1304
1191
|
this.w.pushIndent();
|
|
1305
|
-
if (branch.body.kind === "Block")
|
|
1192
|
+
if (branch.body.kind === "Block")
|
|
1306
1193
|
this.emitBlock(branch.body);
|
|
1307
|
-
|
|
1308
|
-
else {
|
|
1194
|
+
else
|
|
1309
1195
|
this.w.writeIndentedLine(`${this.emitExpr(branch.body)};`);
|
|
1310
|
-
}
|
|
1311
1196
|
this.w.popIndent();
|
|
1312
1197
|
}
|
|
1313
1198
|
this.w.writeIndentedLine(`}`);
|
|
@@ -1315,8 +1200,6 @@ class CodeGenerator {
|
|
|
1315
1200
|
emitWhenCondition(cond, subject) {
|
|
1316
1201
|
switch (cond.kind) {
|
|
1317
1202
|
case "WhenIsCondition": {
|
|
1318
|
-
// Qualified name (e.g. UiState.Loading) → use __kind discriminant
|
|
1319
|
-
// Single name (e.g. BibiError) → use instanceof
|
|
1320
1203
|
const isQualified = cond.type.kind === "SimpleTypeRef" && cond.type.name.length > 1;
|
|
1321
1204
|
const check = isQualified
|
|
1322
1205
|
? `(${subject} as any).__kind === "${cond.type.kind === "SimpleTypeRef" ? cond.type.name[cond.type.name.length - 1] : ""}"`
|
|
@@ -1346,7 +1229,6 @@ class CodeGenerator {
|
|
|
1346
1229
|
}
|
|
1347
1230
|
else {
|
|
1348
1231
|
const { key, value } = stmt.binding;
|
|
1349
|
-
// Use .entries() for JS Map (Map<K,V>), Object.entries() for plain objects
|
|
1350
1232
|
const iterableType = this.typeMap.get(stmt.iterable);
|
|
1351
1233
|
const isMap = iterableType?.tag === "class" && iterableType.name === "Map";
|
|
1352
1234
|
const entries = isMap ? `${iter}.entries()` : `Object.entries(${iter})`;
|
|
@@ -1365,9 +1247,6 @@ class CodeGenerator {
|
|
|
1365
1247
|
for (const c of stmt.catches) {
|
|
1366
1248
|
this.w.writeIndentedLine(`} catch (${c.name}: unknown) {`);
|
|
1367
1249
|
this.w.pushIndent();
|
|
1368
|
-
// Narrow type — always emit the guard so catch clauses are type-safe
|
|
1369
|
-
// regardless of the emitTypes option (which controls explicit type annotations,
|
|
1370
|
-
// not runtime type checks).
|
|
1371
1250
|
this.w.writeIndentedLine(`if (!(${c.name} instanceof ${this.emitTypeRef(c.type)})) throw ${c.name};`);
|
|
1372
1251
|
this.emitBlock(c.body);
|
|
1373
1252
|
this.w.popIndent();
|
|
@@ -1380,7 +1259,6 @@ class CodeGenerator {
|
|
|
1380
1259
|
}
|
|
1381
1260
|
this.w.writeIndentedLine(`}`);
|
|
1382
1261
|
}
|
|
1383
|
-
// ── Expressions ────────────────────────────────────────────────────────────
|
|
1384
1262
|
emitExpr(expr) {
|
|
1385
1263
|
switch (expr.kind) {
|
|
1386
1264
|
case "IntLiteralExpr": return String(expr.value);
|
|
@@ -1390,17 +1268,12 @@ class CodeGenerator {
|
|
|
1390
1268
|
case "BooleanLiteralExpr": return String(expr.value);
|
|
1391
1269
|
case "NullLiteralExpr": return "null";
|
|
1392
1270
|
case "StringLiteralExpr":
|
|
1393
|
-
return expr.raw
|
|
1394
|
-
? `\`${expr.value}\``
|
|
1395
|
-
: JSON.stringify(expr.value);
|
|
1271
|
+
return expr.raw ? `\`${expr.value}\`` : JSON.stringify(expr.value);
|
|
1396
1272
|
case "StringTemplateExpr":
|
|
1397
1273
|
return this.emitStringTemplate(expr);
|
|
1398
1274
|
case "NameExpr":
|
|
1399
|
-
|
|
1400
|
-
if (expr.name === "Int" || expr.name === "Long") {
|
|
1275
|
+
if (expr.name === "Int" || expr.name === "Long")
|
|
1401
1276
|
this.runtimeSymbolsNeeded.add(expr.name);
|
|
1402
|
-
}
|
|
1403
|
-
// Unit singleton emits as undefined (void semantics)
|
|
1404
1277
|
if (expr.name === "Unit")
|
|
1405
1278
|
return "undefined";
|
|
1406
1279
|
return expr.name;
|
|
@@ -1416,34 +1289,23 @@ class CodeGenerator {
|
|
|
1416
1289
|
if (opMethod) {
|
|
1417
1290
|
const l = this.emitExpr(expr.left);
|
|
1418
1291
|
const r = this.emitExpr(expr.right);
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
return `(${l}.compareTo(${r}) ${cmpOp} 0)`;
|
|
1423
|
-
}
|
|
1424
|
-
// `contains` overloads: `element in collection` → `collection.contains(element)`
|
|
1425
|
-
if (opMethod === "contains") {
|
|
1292
|
+
if (opMethod === "compareTo")
|
|
1293
|
+
return `(${l}.compareTo(${r}) ${expr.op} 0)`;
|
|
1294
|
+
if (opMethod === "contains")
|
|
1426
1295
|
return `${r}.contains(${l})`;
|
|
1427
|
-
|
|
1428
|
-
if (opMethod === "!contains") {
|
|
1296
|
+
if (opMethod === "!contains")
|
|
1429
1297
|
return `!${r}.contains(${l})`;
|
|
1430
|
-
}
|
|
1431
|
-
// Generic operator overload: emit as method call `left.method(right)`
|
|
1432
1298
|
return `${l}.${opMethod}(${r})`;
|
|
1433
1299
|
}
|
|
1434
|
-
// `==` → structural equality, `!=` → negation
|
|
1435
1300
|
if (expr.op === "==" || expr.op === "!=") {
|
|
1436
1301
|
this.runtimeSymbolsNeeded.add("jalvinEquals");
|
|
1437
1302
|
const eq = `jalvinEquals(${this.emitExpr(expr.left)}, ${this.emitExpr(expr.right)})`;
|
|
1438
1303
|
return expr.op === "==" ? eq : `!${eq}`;
|
|
1439
1304
|
}
|
|
1440
|
-
|
|
1441
|
-
if (expr.op === "in") {
|
|
1305
|
+
if (expr.op === "in")
|
|
1442
1306
|
return `${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)})`;
|
|
1443
|
-
|
|
1444
|
-
if (expr.op === "!in") {
|
|
1307
|
+
if (expr.op === "!in")
|
|
1445
1308
|
return `!(${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)}))`;
|
|
1446
|
-
}
|
|
1447
1309
|
return `(${this.emitExpr(expr.left)} ${this.binaryOpStr(expr.op)} ${this.emitExpr(expr.right)})`;
|
|
1448
1310
|
}
|
|
1449
1311
|
case "AssignExpr":
|
|
@@ -1454,124 +1316,81 @@ class CodeGenerator {
|
|
|
1454
1316
|
return expr.prefix
|
|
1455
1317
|
? `${expr.op}${this.emitExpr(expr.target)}`
|
|
1456
1318
|
: `${this.emitExpr(expr.target)}${expr.op}`;
|
|
1457
|
-
case "MemberExpr":
|
|
1458
|
-
|
|
1459
|
-
case "
|
|
1460
|
-
return `${this.emitExpr(expr.target)}?.${expr.member}`;
|
|
1461
|
-
case "IndexExpr":
|
|
1462
|
-
return `${this.emitExpr(expr.target)}[${this.emitExpr(expr.index)}]`;
|
|
1319
|
+
case "MemberExpr": return `${this.emitExpr(expr.target)}.${expr.member}`;
|
|
1320
|
+
case "SafeMemberExpr": return `${this.emitExpr(expr.target)}?.${expr.member}`;
|
|
1321
|
+
case "IndexExpr": return `${this.emitExpr(expr.target)}[${this.emitExpr(expr.index)}]`;
|
|
1463
1322
|
case "NotNullExpr":
|
|
1464
|
-
// Runtime check
|
|
1465
1323
|
this.runtimeSymbolsNeeded.add("notNull");
|
|
1466
1324
|
return `notNull(${this.emitExpr(expr.expr)})`;
|
|
1467
1325
|
case "SafeCallExpr": {
|
|
1468
|
-
// x?.() — safe invocation of a nullable function value
|
|
1469
1326
|
const callee = this.emitExpr(expr.callee);
|
|
1470
|
-
const restArgs =
|
|
1471
|
-
for (const a of expr.args) {
|
|
1472
|
-
restArgs.push(a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
|
|
1473
|
-
}
|
|
1327
|
+
const restArgs = expr.args.map((a) => a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
|
|
1474
1328
|
if (expr.trailingLambda)
|
|
1475
1329
|
restArgs.push(this.emitLambdaExpr(expr.trailingLambda));
|
|
1476
1330
|
return `${callee}?.(${restArgs.join(", ")})`;
|
|
1477
1331
|
}
|
|
1478
|
-
case "ElvisExpr": {
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
case "
|
|
1483
|
-
|
|
1484
|
-
case "
|
|
1485
|
-
|
|
1486
|
-
case "
|
|
1487
|
-
return this.emitIfExpr(expr);
|
|
1488
|
-
case "WhenExpr":
|
|
1489
|
-
return this.emitWhenExpr(expr);
|
|
1490
|
-
case "TryCatchExpr":
|
|
1491
|
-
return this.emitTryCatchExpr(expr);
|
|
1492
|
-
case "TypeCheckExpr":
|
|
1493
|
-
return `${expr.negated ? "!(" : ""}${this.emitExpr(expr.expr)} instanceof ${this.emitTypeRef(expr.type)}${expr.negated ? ")" : ""}`;
|
|
1494
|
-
case "TypeCastExpr":
|
|
1495
|
-
// Unsafe cast — emit as `(expr as Type)` which is stripped at runtime
|
|
1496
|
-
return `(${this.emitExpr(expr.expr)} as unknown as ${this.emitTypeRef(expr.type)})`;
|
|
1497
|
-
case "SafeCastExpr": {
|
|
1332
|
+
case "ElvisExpr": return `(${this.emitExpr(expr.left)} ?? ${this.emitExpr(expr.right)})`;
|
|
1333
|
+
case "CallExpr": return this.emitCallExpr(expr);
|
|
1334
|
+
case "LambdaExpr": return this.emitLambdaExpr(expr);
|
|
1335
|
+
case "IfExpr": return this.emitIfExpr(expr);
|
|
1336
|
+
case "WhenExpr": return this.emitWhenExpr(expr);
|
|
1337
|
+
case "TryCatchExpr": return this.emitTryCatchExpr(expr);
|
|
1338
|
+
case "TypeCheckExpr": return `${expr.negated ? "!(" : ""}${this.emitExpr(expr.expr)} instanceof ${this.emitTypeRef(expr.type)}${expr.negated ? ")" : ""}`;
|
|
1339
|
+
case "TypeCastExpr": return `(${this.emitExpr(expr.expr)} as unknown as ${this.emitTypeRef(expr.type)})`;
|
|
1340
|
+
case "SafeCastExpr":
|
|
1498
1341
|
this.runtimeSymbolsNeeded.add("safeCast");
|
|
1499
1342
|
return `safeCast(${this.emitExpr(expr.expr)}, ${this.emitTypeRef(expr.type)})`;
|
|
1500
|
-
|
|
1501
|
-
case "RangeExpr": {
|
|
1343
|
+
case "RangeExpr":
|
|
1502
1344
|
this.runtimeSymbolsNeeded.add("range");
|
|
1503
1345
|
return `range(${this.emitExpr(expr.from)}, ${this.emitExpr(expr.to)}, ${expr.inclusive})`;
|
|
1504
|
-
}
|
|
1505
1346
|
case "LaunchExpr": {
|
|
1506
|
-
// fire-and-forget → void IIFE
|
|
1507
1347
|
const stmts = this.captureBlock(expr.body);
|
|
1508
1348
|
return `(async () => { ${stmts} })()`;
|
|
1509
1349
|
}
|
|
1510
1350
|
case "AsyncExpr": {
|
|
1511
|
-
// returns Promise<T>
|
|
1512
1351
|
const stmts = this.captureBlock(expr.body);
|
|
1513
1352
|
return `(async () => { ${stmts} })()`;
|
|
1514
1353
|
}
|
|
1515
|
-
case "CollectionLiteralExpr":
|
|
1516
|
-
|
|
1517
|
-
case "ObjectExpr":
|
|
1518
|
-
return this.emitObjectExpr(expr);
|
|
1354
|
+
case "CollectionLiteralExpr": return this.emitCollectionLiteral(expr);
|
|
1355
|
+
case "ObjectExpr": return this.emitObjectExpr(expr);
|
|
1519
1356
|
case "ReturnExpr": {
|
|
1520
1357
|
const val = expr.value ? ` ${this.emitExpr(expr.value)}` : "";
|
|
1521
1358
|
return `((() => { return${val}; })())`;
|
|
1522
1359
|
}
|
|
1523
1360
|
case "BreakExpr": return "undefined /* break */";
|
|
1524
1361
|
case "ContinueExpr": return "undefined /* continue */";
|
|
1525
|
-
case "JsxElement":
|
|
1526
|
-
|
|
1527
|
-
default:
|
|
1528
|
-
return "undefined";
|
|
1362
|
+
case "JsxElement": return this.emitJsxElement(expr);
|
|
1363
|
+
default: return "undefined";
|
|
1529
1364
|
}
|
|
1530
1365
|
}
|
|
1531
|
-
// ── JSX ──────────────────────────────────────────────────────────────────
|
|
1532
1366
|
emitJsxElement(expr) {
|
|
1367
|
+
this.runtimeSymbolsNeeded.add("jalvinCreateElement");
|
|
1533
1368
|
const attrsStr = expr.attrs.length > 0
|
|
1534
|
-
? " " + expr.attrs.map((a) => this.emitJsxAttr(a)).join(" ")
|
|
1535
|
-
: "";
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
return `<${expr.tag}${attrsStr}>${children}</${expr.tag}>`;
|
|
1369
|
+
? "{ " + expr.attrs.map((a) => this.emitJsxAttr(a)).join(", ") + " }"
|
|
1370
|
+
: "{}";
|
|
1371
|
+
const childrenArr = expr.children.length > 0
|
|
1372
|
+
? "[" + expr.children.map((c) => this.emitJsxChild(c)).join(", ") + "]"
|
|
1373
|
+
: "[]";
|
|
1374
|
+
return `jalvinCreateElement(${JSON.stringify(expr.tag)}, ${attrsStr}, ${childrenArr})`;
|
|
1541
1375
|
}
|
|
1542
1376
|
emitJsxAttr(attr) {
|
|
1543
|
-
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
: attr.name;
|
|
1547
|
-
if (attr.value === null)
|
|
1548
|
-
return name;
|
|
1549
|
-
if (typeof attr.value === "string")
|
|
1550
|
-
return `${name}="${attr.value}"`;
|
|
1551
|
-
return `${name}={${this.emitExpr(attr.value)}}`;
|
|
1377
|
+
const name = attr.name === "class" ? "className" : attr.name === "for" ? "htmlFor" : attr.name;
|
|
1378
|
+
const val = attr.value === null ? "true" : typeof attr.value === "string" ? JSON.stringify(attr.value) : this.emitExpr(attr.value);
|
|
1379
|
+
return `${name}: ${val}`;
|
|
1552
1380
|
}
|
|
1553
1381
|
emitJsxChild(child) {
|
|
1554
1382
|
switch (child.kind) {
|
|
1555
1383
|
case "JsxElement": return this.emitJsxElement(child);
|
|
1556
|
-
case "JsxExprChild": return
|
|
1557
|
-
case "JsxTextChild": return child.text
|
|
1384
|
+
case "JsxExprChild": return this.emitExpr(child.expr);
|
|
1385
|
+
case "JsxTextChild": return `document.createTextNode(${JSON.stringify(child.text)})`;
|
|
1558
1386
|
}
|
|
1559
1387
|
}
|
|
1560
1388
|
emitStringTemplate(expr) {
|
|
1561
|
-
const inner = expr.parts.map((p) => {
|
|
1562
|
-
if (p.kind === "LiteralPart") {
|
|
1563
|
-
return p.value.replace(/`/g, "\\`").replace(/\\/g, "\\\\");
|
|
1564
|
-
}
|
|
1565
|
-
return `\${${this.emitExpr(p.expr)}}`;
|
|
1566
|
-
}).join("");
|
|
1389
|
+
const inner = expr.parts.map((p) => p.kind === "LiteralPart" ? p.value.replace(/`/g, "\\`").replace(/\\/g, "\\\\") : `\${${this.emitExpr(p.expr)}}`).join("");
|
|
1567
1390
|
return `\`${inner}\``;
|
|
1568
1391
|
}
|
|
1569
1392
|
emitCallExpr(expr) {
|
|
1570
|
-
|
|
1571
|
-
// `a.downTo(b)` → `downTo(a, b)` `a.until(b)` → `range(a, b, false)`
|
|
1572
|
-
// `range.step(n)` → `step(range, n)`
|
|
1573
|
-
if (expr.callee.kind === "MemberExpr" &&
|
|
1574
|
-
(expr.callee.member === "downTo" || expr.callee.member === "until" || expr.callee.member === "step")) {
|
|
1393
|
+
if (expr.callee.kind === "MemberExpr" && (expr.callee.member === "downTo" || expr.callee.member === "until" || expr.callee.member === "step")) {
|
|
1575
1394
|
const obj = this.emitExpr(expr.callee.target);
|
|
1576
1395
|
const arg = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "0";
|
|
1577
1396
|
if (expr.callee.member === "downTo") {
|
|
@@ -1587,51 +1406,39 @@ class CodeGenerator {
|
|
|
1587
1406
|
return `step(${obj}, ${arg})`;
|
|
1588
1407
|
}
|
|
1589
1408
|
}
|
|
1590
|
-
// Rewrite calls on primitive-receiver extension functions.
|
|
1591
|
-
// `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
|
|
1592
1409
|
if (expr.callee.kind === "MemberExpr") {
|
|
1593
1410
|
const scopeMember = expr.callee.member;
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
if (
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
(
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
this.runtimeSymbolsNeeded.add("takeIf");
|
|
1623
|
-
return `takeIf(${receiver}, ${this.emitLambdaExpr(lambda)})`;
|
|
1624
|
-
}
|
|
1625
|
-
if (scopeMember === "takeUnless") {
|
|
1626
|
-
this.runtimeSymbolsNeeded.add("takeUnless");
|
|
1627
|
-
return `takeUnless(${receiver}, ${this.emitLambdaExpr(lambda)})`;
|
|
1628
|
-
}
|
|
1411
|
+
const receiver = this.emitExpr(expr.callee.target);
|
|
1412
|
+
const lambda = expr.trailingLambda ?? (expr.args.length === 1 && expr.args[0].value.kind === "LambdaExpr" ? expr.args[0].value : null);
|
|
1413
|
+
if (lambda) {
|
|
1414
|
+
if (scopeMember === "let") {
|
|
1415
|
+
this.runtimeSymbolsNeeded.add("let_");
|
|
1416
|
+
return `let_(${receiver}, ${this.emitLambdaExpr(lambda)})`;
|
|
1417
|
+
}
|
|
1418
|
+
if (scopeMember === "also") {
|
|
1419
|
+
this.runtimeSymbolsNeeded.add("also");
|
|
1420
|
+
return `also(${receiver}, ${this.emitLambdaExpr(lambda)})`;
|
|
1421
|
+
}
|
|
1422
|
+
if (scopeMember === "apply") {
|
|
1423
|
+
this.runtimeSymbolsNeeded.add("apply");
|
|
1424
|
+
const body = this.captureBlockStatements(lambda.body);
|
|
1425
|
+
return `apply(${receiver}, function(this: any) { ${body} })`;
|
|
1426
|
+
}
|
|
1427
|
+
if (scopeMember === "run") {
|
|
1428
|
+
this.runtimeSymbolsNeeded.add("run_");
|
|
1429
|
+
const body = this.captureBlockStatements(lambda.body);
|
|
1430
|
+
return `run_(${receiver}, function(this: any) { ${body} })`;
|
|
1431
|
+
}
|
|
1432
|
+
if (scopeMember === "takeIf") {
|
|
1433
|
+
this.runtimeSymbolsNeeded.add("takeIf");
|
|
1434
|
+
return `takeIf(${receiver}, ${this.emitLambdaExpr(lambda)})`;
|
|
1435
|
+
}
|
|
1436
|
+
if (scopeMember === "takeUnless") {
|
|
1437
|
+
this.runtimeSymbolsNeeded.add("takeUnless");
|
|
1438
|
+
return `takeUnless(${receiver}, ${this.emitLambdaExpr(lambda)})`;
|
|
1629
1439
|
}
|
|
1630
1440
|
}
|
|
1631
|
-
// `with(x) { ... }` is a free function handled via seedBuiltins; but rewrite if emitted as member
|
|
1632
1441
|
}
|
|
1633
|
-
// Rewrite calls on primitive-receiver extension functions.
|
|
1634
|
-
// `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
|
|
1635
1442
|
if (expr.callee.kind === "MemberExpr") {
|
|
1636
1443
|
const receiverType = this.typeMap.get(expr.callee.target);
|
|
1637
1444
|
const memberName = expr.callee.member;
|
|
@@ -1651,105 +1458,69 @@ class CodeGenerator {
|
|
|
1651
1458
|
}
|
|
1652
1459
|
}
|
|
1653
1460
|
const callee = this.emitExpr(expr.callee);
|
|
1654
|
-
const typeArgs = expr.typeArgs.length > 0
|
|
1655
|
-
|
|
1656
|
-
: "";
|
|
1657
|
-
// Rewrite top-level `with(obj) { ... }` → `with_(obj, function(this: any) { ... })`
|
|
1658
|
-
if (expr.callee.kind === "NameExpr" && expr.callee.name === "with" &&
|
|
1659
|
-
(expr.trailingLambda || (expr.args.length === 2 && expr.args[1].value.kind === "LambdaExpr"))) {
|
|
1461
|
+
const typeArgs = expr.typeArgs.length > 0 ? `<${expr.typeArgs.map((t) => t.star ? "*" : this.emitTypeRef(t.type)).join(", ")}>` : "";
|
|
1462
|
+
if (expr.callee.kind === "NameExpr" && expr.callee.name === "with" && (expr.trailingLambda || (expr.args.length === 2 && expr.args[1].value.kind === "LambdaExpr"))) {
|
|
1660
1463
|
const obj = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "undefined";
|
|
1661
|
-
const lambda = expr.trailingLambda ??
|
|
1662
|
-
expr.args[1].value;
|
|
1464
|
+
const lambda = expr.trailingLambda ?? expr.args[1].value;
|
|
1663
1465
|
this.runtimeSymbolsNeeded.add("with_");
|
|
1664
1466
|
const body = this.captureBlockStatements(lambda.body);
|
|
1665
1467
|
return `with_(${obj}, function(this: any) { ${body} })`;
|
|
1666
1468
|
}
|
|
1667
|
-
|
|
1668
|
-
if (expr.callee.kind === "NameExpr" && expr.callee.name === "run" &&
|
|
1669
|
-
expr.args.length === 0 && expr.trailingLambda) {
|
|
1469
|
+
if (expr.callee.kind === "NameExpr" && expr.callee.name === "run" && expr.args.length === 0 && expr.trailingLambda) {
|
|
1670
1470
|
return `(${this.emitLambdaExpr(expr.trailingLambda)})()`;
|
|
1671
1471
|
}
|
|
1672
|
-
// Handle named arguments by reordering them to match positional parameters
|
|
1673
1472
|
const calleeType = this.typeMap.get(expr.callee);
|
|
1674
|
-
// Component call: Column(modifier = ...) { ... } → Column({ modifier: ... }, [children])
|
|
1675
|
-
// Also catches star-imported @jalvin/ui primitives (Row, Button, etc.) whose type is T_UNKNOWN
|
|
1676
|
-
//
|
|
1677
|
-
// Skip the component path when the typechecker resolved a func type with explicit paramNames.
|
|
1678
|
-
// That indicates a regular function (fun) or a built-in hook — both use positional calling.
|
|
1679
|
-
// component fun declarations intentionally omit paramNames from their func type.
|
|
1680
1473
|
const isKnownPositionalFunc = calleeType?.tag === "func" && calleeType.paramNames !== undefined;
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1474
|
+
let isComponentLike = false;
|
|
1475
|
+
if (expr.callee.kind === "NameExpr") {
|
|
1476
|
+
isComponentLike = this.allComponentLikeNames.has(expr.callee.name) || (this.hasUiStarImport && (!calleeType || calleeType.tag === "unknown") && /^[A-Z]/.test(expr.callee.name));
|
|
1477
|
+
}
|
|
1478
|
+
else if (expr.callee.kind === "MemberExpr" || expr.callee.kind === "SafeMemberExpr") {
|
|
1479
|
+
isComponentLike = this.allComponentLikeNames.has(expr.callee.member);
|
|
1684
1480
|
}
|
|
1481
|
+
if (!isKnownPositionalFunc && isComponentLike)
|
|
1482
|
+
return this.emitComponentCall(expr);
|
|
1685
1483
|
let finalArgs = [];
|
|
1686
1484
|
if (calleeType && calleeType.tag === "func" && calleeType.paramNames) {
|
|
1687
1485
|
const paramNames = calleeType.paramNames;
|
|
1688
1486
|
const argsByName = new Map();
|
|
1689
1487
|
const positionalArgs = [];
|
|
1690
1488
|
for (const arg of expr.args) {
|
|
1691
|
-
if (arg.name)
|
|
1489
|
+
if (arg.name)
|
|
1692
1490
|
argsByName.set(arg.name, arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
|
|
1693
|
-
|
|
1694
|
-
else {
|
|
1491
|
+
else
|
|
1695
1492
|
positionalArgs.push(arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
|
|
1696
|
-
}
|
|
1697
1493
|
}
|
|
1698
|
-
// Reconstruct args based on paramNames
|
|
1699
1494
|
for (let i = 0; i < paramNames.length; i++) {
|
|
1700
1495
|
const name = paramNames[i];
|
|
1701
|
-
if (argsByName.has(name))
|
|
1496
|
+
if (argsByName.has(name))
|
|
1702
1497
|
finalArgs.push(argsByName.get(name));
|
|
1703
|
-
|
|
1704
|
-
else if (i < positionalArgs.length) {
|
|
1498
|
+
else if (i < positionalArgs.length)
|
|
1705
1499
|
finalArgs.push(positionalArgs[i]);
|
|
1706
|
-
|
|
1707
|
-
else {
|
|
1708
|
-
// Missing argument - JS default value logic will handle it if we pass undefined
|
|
1500
|
+
else
|
|
1709
1501
|
finalArgs.push("undefined");
|
|
1710
|
-
}
|
|
1711
1502
|
}
|
|
1712
|
-
|
|
1713
|
-
if (positionalArgs.length > paramNames.length) {
|
|
1503
|
+
if (positionalArgs.length > paramNames.length)
|
|
1714
1504
|
finalArgs.push(...positionalArgs.slice(paramNames.length));
|
|
1715
|
-
}
|
|
1716
1505
|
}
|
|
1717
1506
|
else {
|
|
1718
|
-
|
|
1719
|
-
finalArgs = expr.args.map((a) => {
|
|
1720
|
-
return a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value);
|
|
1721
|
-
});
|
|
1507
|
+
finalArgs = expr.args.map((a) => a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
|
|
1722
1508
|
}
|
|
1723
|
-
if (expr.trailingLambda)
|
|
1509
|
+
if (expr.trailingLambda)
|
|
1724
1510
|
finalArgs.push(this.emitLambdaExpr(expr.trailingLambda));
|
|
1725
|
-
|
|
1726
|
-
// If the callee is a class instance (not a constructor / function), emit `.invoke(args)`
|
|
1727
|
-
if (calleeType &&
|
|
1728
|
-
calleeType.tag === "class" &&
|
|
1729
|
-
calleeType.decl &&
|
|
1730
|
-
this.classHasInvokeOperator(calleeType.decl)) {
|
|
1511
|
+
if (calleeType && calleeType.tag === "class" && calleeType.decl && this.classHasInvokeOperator(calleeType.decl)) {
|
|
1731
1512
|
return `${callee}.invoke(${finalArgs.join(", ")})`;
|
|
1732
1513
|
}
|
|
1733
|
-
|
|
1734
|
-
if (this.isConstructorCall(expr.callee)) {
|
|
1514
|
+
if (this.isConstructorCall(expr.callee))
|
|
1735
1515
|
return `new ${callee}${typeArgs}(${finalArgs.join(", ")})`;
|
|
1736
|
-
}
|
|
1737
1516
|
return `${callee}${typeArgs}(${finalArgs.join(", ")})`;
|
|
1738
1517
|
}
|
|
1739
|
-
/**
|
|
1740
|
-
* For each non-scoped local import (e.g. `import src.views.MoveCounterView.MoveCounter`),
|
|
1741
|
-
* attempt to load and parse the corresponding .jalvin source file. If the named symbol is
|
|
1742
|
-
* a `component fun`, register it in `userComponentNames` and record its param names so that
|
|
1743
|
-
* call sites can emit the correct props-object invocation.
|
|
1744
|
-
*/
|
|
1745
1518
|
resolveLocalComponentImports(program, sourceRoot) {
|
|
1746
1519
|
this.localStarExports.clear();
|
|
1747
1520
|
for (const imp of program.imports) {
|
|
1748
|
-
// Skip scoped packages (@org/pkg)
|
|
1749
1521
|
if (imp.path[0]?.startsWith("@"))
|
|
1750
1522
|
continue;
|
|
1751
1523
|
if (imp.star) {
|
|
1752
|
-
// Local wildcard import: import src.module.* — load all exports from src/module.jalvin
|
|
1753
1524
|
if (imp.path.length < 1)
|
|
1754
1525
|
continue;
|
|
1755
1526
|
const candidatePath = nodePath.join(sourceRoot, imp.path.join("/") + ".jalvin");
|
|
@@ -1777,17 +1548,22 @@ class CodeGenerator {
|
|
|
1777
1548
|
this.componentParamNames.set(name, d.params.map((p) => p.name));
|
|
1778
1549
|
this.hasComponents = true;
|
|
1779
1550
|
}
|
|
1551
|
+
if (d.kind === "ClassDecl" && d.body) {
|
|
1552
|
+
for (const m of d.body.members) {
|
|
1553
|
+
if (m.kind === "ComponentDecl") {
|
|
1554
|
+
this.allComponentLikeNames.add(m.name);
|
|
1555
|
+
this.componentParamNames.set(m.name, m.params.map((p) => p.name));
|
|
1556
|
+
this.hasComponents = true;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1780
1560
|
}
|
|
1781
|
-
if (exportedNames.length > 0)
|
|
1561
|
+
if (exportedNames.length > 0)
|
|
1782
1562
|
this.localStarExports.set(moduleSpecifier, exportedNames);
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
catch {
|
|
1786
|
-
// Silently skip if the file cannot be read or parsed
|
|
1787
1563
|
}
|
|
1564
|
+
catch { }
|
|
1788
1565
|
continue;
|
|
1789
1566
|
}
|
|
1790
|
-
// Named import: last path segment is the symbol, preceding segments form the file path
|
|
1791
1567
|
if (imp.path.length < 2)
|
|
1792
1568
|
continue;
|
|
1793
1569
|
const rawSymbolName = imp.path[imp.path.length - 1];
|
|
@@ -1805,600 +1581,237 @@ class CodeGenerator {
|
|
|
1805
1581
|
const ast = (0, parser_js_1.parse)(tokens, candidatePath, diag, source);
|
|
1806
1582
|
if (diag.hasErrors)
|
|
1807
1583
|
continue;
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
if (compDecl) {
|
|
1584
|
+
const decl = ast.declarations.find((d) => ((d.kind === "ComponentDecl" || d.kind === "ClassDecl") && d.name === rawSymbolName));
|
|
1585
|
+
if (decl?.kind === "ComponentDecl") {
|
|
1811
1586
|
this.userComponentNames.add(symbolName);
|
|
1812
1587
|
this.allComponentLikeNames.add(symbolName);
|
|
1813
|
-
this.componentParamNames.set(symbolName,
|
|
1588
|
+
this.componentParamNames.set(symbolName, decl.params.map((p) => p.name));
|
|
1814
1589
|
this.hasComponents = true;
|
|
1815
1590
|
}
|
|
1591
|
+
else if (decl?.kind === "ClassDecl" && decl.body) {
|
|
1592
|
+
for (const m of decl.body.members) {
|
|
1593
|
+
if (m.kind === "ComponentDecl") {
|
|
1594
|
+
this.userComponentNames.add(m.name);
|
|
1595
|
+
this.allComponentLikeNames.add(m.name);
|
|
1596
|
+
this.componentParamNames.set(m.name, m.params.map((p) => p.name));
|
|
1597
|
+
this.hasComponents = true;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1816
1601
|
}
|
|
1817
|
-
catch {
|
|
1818
|
-
// Silently skip if the file cannot be read or parsed
|
|
1819
|
-
}
|
|
1602
|
+
catch { }
|
|
1820
1603
|
}
|
|
1821
1604
|
}
|
|
1822
|
-
/** Returns true if a call expression's callee looks like a class constructor.
|
|
1823
|
-
* In Jalvin, class names always start with an uppercase letter. */
|
|
1824
1605
|
isConstructorCall(calleeExpr) {
|
|
1825
1606
|
if (calleeExpr.kind === "NameExpr") {
|
|
1826
|
-
// Names in allComponentLikeNames are factory functions (UI primitives, components), not constructors
|
|
1827
1607
|
if (this.allComponentLikeNames.has(calleeExpr.name))
|
|
1828
1608
|
return false;
|
|
1829
|
-
// Class names start with uppercase; function names start with lowercase
|
|
1830
1609
|
return /^[A-Z]/.test(calleeExpr.name);
|
|
1831
1610
|
}
|
|
1832
1611
|
if (calleeExpr.kind === "MemberExpr") {
|
|
1833
|
-
|
|
1612
|
+
if (this.allComponentLikeNames.has(calleeExpr.member))
|
|
1613
|
+
return false;
|
|
1834
1614
|
return /^[A-Z]/.test(calleeExpr.member);
|
|
1835
1615
|
}
|
|
1836
1616
|
return false;
|
|
1837
1617
|
}
|
|
1838
|
-
/** Returns true if a name refers to a "true" Jalvin component that needs a React boundary */
|
|
1839
|
-
isTrueComponent(name) {
|
|
1840
|
-
// 1. Locally declared or imported .jalvin components
|
|
1841
|
-
if (this.userComponentNames.has(name))
|
|
1842
|
-
return true;
|
|
1843
|
-
// 2. Named imports from @jalvin/ui are considered UI primitives (functions)
|
|
1844
|
-
// unless they were also identified as components.
|
|
1845
|
-
// For now, everything in @jalvin/ui is treated as a UI primitive (function call)
|
|
1846
|
-
// to maintain compatibility with the (props, children) signature.
|
|
1847
|
-
if (this.uiNamedImports.has(name))
|
|
1848
|
-
return false;
|
|
1849
|
-
// 3. Fallback for star-imported @jalvin/ui
|
|
1850
|
-
return false;
|
|
1851
|
-
}
|
|
1852
|
-
// ── Component call → props object ──────────────────────────────────────
|
|
1853
|
-
/** Emit `Column(modifier = ...) { ... }` as either direct calls or React.createElement depending on jsx option */
|
|
1854
1618
|
emitComponentCall(expr) {
|
|
1855
|
-
const
|
|
1619
|
+
const callee = this.emitExpr(expr.callee);
|
|
1620
|
+
const tagName = expr.callee.kind === "NameExpr" ? expr.callee.name : expr.callee.member;
|
|
1856
1621
|
const calleeType = this.typeMap.get(expr.callee);
|
|
1857
|
-
|
|
1858
|
-
const paramNames = (calleeType?.tag === "func" ? calleeType.paramNames : undefined) ??
|
|
1859
|
-
this.componentParamNames.get(tag);
|
|
1860
|
-
// Build props object from named/positional args
|
|
1622
|
+
const paramNames = (calleeType?.tag === "func" ? calleeType.paramNames : undefined) ?? (tagName ? this.componentParamNames.get(tagName) : undefined);
|
|
1861
1623
|
const props = [];
|
|
1862
1624
|
for (let i = 0; i < expr.args.length; i++) {
|
|
1863
1625
|
const arg = expr.args[i];
|
|
1864
|
-
|
|
1865
|
-
const propName = arg.name ??
|
|
1866
|
-
paramNames?.[i] ??
|
|
1867
|
-
(arg.value.kind === "NameExpr" ? arg.value.name : undefined);
|
|
1626
|
+
const propName = arg.name ?? paramNames?.[i] ?? (arg.value.kind === "NameExpr" ? arg.value.name : undefined);
|
|
1868
1627
|
if (!propName)
|
|
1869
1628
|
continue;
|
|
1870
1629
|
const val = this.emitExpr(arg.value);
|
|
1871
|
-
// Use JS shorthand `{ key }` when key and value are the same identifier
|
|
1872
1630
|
props.push(propName === val ? propName : `${propName}: ${val}`);
|
|
1873
1631
|
}
|
|
1874
1632
|
const propsStr = props.length > 0 ? `{ ${props.join(", ")} }` : "{}";
|
|
1875
|
-
if (
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
const children = this.emitLambdaBodyAsDomChildren(expr.trailingLambda);
|
|
1879
|
-
return `React.createElement(${tag}, ${propsStr}, [${children}])`;
|
|
1880
|
-
}
|
|
1881
|
-
return `React.createElement(${tag}, ${propsStr})`;
|
|
1882
|
-
}
|
|
1883
|
-
else {
|
|
1884
|
-
// Direct function call format for non-JSX output OR UI Primitives
|
|
1885
|
-
if (expr.trailingLambda) {
|
|
1886
|
-
const children = this.emitLambdaBodyAsDomChildren(expr.trailingLambda);
|
|
1887
|
-
return `${tag}(${propsStr}, [${children}])`;
|
|
1888
|
-
}
|
|
1889
|
-
return `${tag}(${propsStr})`;
|
|
1633
|
+
if (expr.trailingLambda) {
|
|
1634
|
+
const children = this.emitLambdaBodyAsDomChildren(expr.trailingLambda);
|
|
1635
|
+
return `${callee}(${propsStr}, [${children}])`;
|
|
1890
1636
|
}
|
|
1637
|
+
return `${callee}(${propsStr})`;
|
|
1891
1638
|
}
|
|
1892
|
-
/** Collect each expression statement in a trailing-lambda body as DOM children.
|
|
1893
|
-
* For-loop statements are emitted as spread IIFEs so their dynamic output is preserved. */
|
|
1894
1639
|
emitLambdaBodyAsDomChildren(lambda) {
|
|
1895
1640
|
const parts = [];
|
|
1896
1641
|
for (const stmt of lambda.body) {
|
|
1897
1642
|
if (stmt.kind === "ExprStmt") {
|
|
1898
1643
|
parts.push(this.emitExpr(stmt.expr));
|
|
1899
1644
|
}
|
|
1645
|
+
else if (stmt.kind === "IfStmt") {
|
|
1646
|
+
const s = stmt;
|
|
1647
|
+
const cond = this.emitExpr(s.condition);
|
|
1648
|
+
const thenExprs = s.then.statements.filter((st) => st.kind === "ExprStmt").map((st) => this.emitExpr(st.expr));
|
|
1649
|
+
const elseExprs = s.else && s.else.kind === "Block" ? s.else.statements.filter((st) => st.kind === "ExprStmt").map((st) => this.emitExpr(st.expr)) : [];
|
|
1650
|
+
const thenStr = thenExprs.length > 0 ? `...(${cond} ? [${thenExprs.join(", ")}] : [])` : "";
|
|
1651
|
+
const elseStr = elseExprs.length > 0 ? `...(!(${cond}) ? [${elseExprs.join(", ")}] : [])` : "";
|
|
1652
|
+
if (thenStr)
|
|
1653
|
+
parts.push(thenStr);
|
|
1654
|
+
if (elseStr)
|
|
1655
|
+
parts.push(elseStr);
|
|
1656
|
+
}
|
|
1900
1657
|
else if (stmt.kind === "ForStmt") {
|
|
1901
1658
|
const s = stmt;
|
|
1902
1659
|
const iter = this.emitExpr(s.iterable);
|
|
1903
|
-
let bindingStr
|
|
1904
|
-
|
|
1905
|
-
bindingStr = `const ${s.binding}`;
|
|
1906
|
-
}
|
|
1907
|
-
else if (s.binding.kind === "TupleDestructure") {
|
|
1908
|
-
const names = s.binding.names.map((n) => n ?? "_").join(", ");
|
|
1909
|
-
bindingStr = `const [${names}]`;
|
|
1910
|
-
}
|
|
1911
|
-
else {
|
|
1912
|
-
bindingStr = `const [${s.binding.key}, ${s.binding.value}]`;
|
|
1913
|
-
}
|
|
1914
|
-
const innerExprs = s.body.statements
|
|
1915
|
-
.filter((inner) => inner.kind === "ExprStmt")
|
|
1916
|
-
.map((inner) => this.emitExpr(inner.expr));
|
|
1660
|
+
let bindingStr = typeof s.binding === "string" ? `const ${s.binding}` : s.binding.kind === "TupleDestructure" ? `const [${s.binding.names.map((n) => n ?? "_").join(", ")}]` : `const [${s.binding.key}, ${s.binding.value}]`;
|
|
1661
|
+
const innerExprs = s.body.statements.filter((inner) => inner.kind === "ExprStmt").map((inner) => this.emitExpr(inner.expr));
|
|
1917
1662
|
if (innerExprs.length > 0) {
|
|
1918
1663
|
const pushes = innerExprs.map((e) => `__c.push(${e});`).join(" ");
|
|
1919
1664
|
parts.push(`...(() => { const __c: any[] = []; for (${bindingStr} of ${iter}) { ${pushes} } return __c; })()`);
|
|
1920
1665
|
}
|
|
1921
1666
|
}
|
|
1922
|
-
// Other statement kinds (if, while, etc.) are not renderable as UI children inline
|
|
1923
1667
|
}
|
|
1924
1668
|
return parts.join(", ");
|
|
1925
1669
|
}
|
|
1926
1670
|
emitLambdaExpr(expr) {
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
: expr.params.map((p) => p.name ?? "_").join(", ");
|
|
1932
|
-
const body = expr.body;
|
|
1933
|
-
if (body.length === 1 && body[0].kind === "ExprStmt") {
|
|
1934
|
-
return `(${params}) => ${this.emitExpr(body[0].expr)}`;
|
|
1935
|
-
}
|
|
1936
|
-
return `(${params}) => { ${body.map((s) => this.captureStmt(s)).join(" ")} }`;
|
|
1671
|
+
const params = expr.params.length === 0 ? "it" : expr.params.map((p) => p.name ?? "_").join(", ");
|
|
1672
|
+
if (expr.body.length === 1 && expr.body[0].kind === "ExprStmt")
|
|
1673
|
+
return `(${params}) => ${this.emitExpr(expr.body[0].expr)}`;
|
|
1674
|
+
return `(${params}) => { ${expr.body.map((s) => this.captureStmt(s)).join(" ")} }`;
|
|
1937
1675
|
}
|
|
1938
1676
|
emitIfExpr(expr) {
|
|
1939
1677
|
const cond = this.emitExpr(expr.condition);
|
|
1940
|
-
const thenStr = expr.then.kind === "Block"
|
|
1941
|
-
|
|
1942
|
-
: this.emitExpr(expr.then);
|
|
1943
|
-
const elseExpr = expr.else;
|
|
1944
|
-
const elseStr = elseExpr.kind === "Block"
|
|
1945
|
-
? `(() => { ${this.captureBlockStatements(elseExpr.statements)} })()`
|
|
1946
|
-
: elseExpr.kind === "IfExpr"
|
|
1947
|
-
? this.emitIfExpr(elseExpr)
|
|
1948
|
-
: this.emitExpr(elseExpr);
|
|
1678
|
+
const thenStr = expr.then.kind === "Block" ? `(() => { ${this.captureBlockStatements(expr.then.statements)} })()` : this.emitExpr(expr.then);
|
|
1679
|
+
const elseStr = expr.else.kind === "Block" ? `(() => { ${this.captureBlockStatements(expr.else.statements)} })()` : expr.else.kind === "IfExpr" ? this.emitIfExpr(expr.else) : this.emitExpr(expr.else);
|
|
1949
1680
|
return `(${cond} ? ${thenStr} : ${elseStr})`;
|
|
1950
1681
|
}
|
|
1951
1682
|
emitWhenExpr(expr) {
|
|
1952
|
-
// Compile as an IIFE with if/else chain
|
|
1953
1683
|
const parts = [];
|
|
1954
1684
|
const subject = expr.subject ? `const __s = ${this.emitExpr(expr.subject.expr)};` : "";
|
|
1955
1685
|
const subjectRef = expr.subject ? (expr.subject.binding ?? "__s") : "";
|
|
1956
1686
|
for (const branch of expr.branches) {
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
? this.captureBlock(branch.body)
|
|
1960
|
-
: `return ${this.emitExpr(branch.body)};`;
|
|
1687
|
+
const body = branch.body.kind === "Block" ? this.captureBlock(branch.body) : `return ${this.emitExpr(branch.body)};`;
|
|
1688
|
+
if (branch.isElse)
|
|
1961
1689
|
parts.push(`{ ${body} }`);
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
const cond = branch.conditions.map((c) => this.emitWhenCondition(c, subjectRef)).join(" || ");
|
|
1965
|
-
const body = branch.body.kind === "Block"
|
|
1966
|
-
? this.captureBlock(branch.body)
|
|
1967
|
-
: `return ${this.emitExpr(branch.body)};`;
|
|
1968
|
-
parts.push(`if (${cond}) { ${body} }`);
|
|
1969
|
-
}
|
|
1690
|
+
else
|
|
1691
|
+
parts.push(`if (${branch.conditions.map((c) => this.emitWhenCondition(c, subjectRef)).join(" || ")}) { ${body} }`);
|
|
1970
1692
|
}
|
|
1971
1693
|
return `(() => { ${subject} ${parts.join(" else ")} })()`;
|
|
1972
1694
|
}
|
|
1973
1695
|
emitTryCatchExpr(expr) {
|
|
1974
1696
|
const body = this.captureBlock(expr.body);
|
|
1975
|
-
const catches = expr.catches.map((c) => {
|
|
1976
|
-
const cb = this.captureBlock(c.body);
|
|
1977
|
-
return `catch (${c.name}) { ${cb} }`;
|
|
1978
|
-
}).join(" ");
|
|
1697
|
+
const catches = expr.catches.map((c) => `catch (${c.name}) { ${this.captureBlock(c.body)} }`).join(" ");
|
|
1979
1698
|
const fin = expr.finally ? `finally { ${this.captureBlock(expr.finally)} }` : "";
|
|
1980
1699
|
return `(() => { try { ${body} } ${catches} ${fin} })()`;
|
|
1981
1700
|
}
|
|
1982
1701
|
emitCollectionLiteral(expr) {
|
|
1983
|
-
if (expr.collectionKind === "map")
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
return "null";
|
|
1989
|
-
});
|
|
1990
|
-
return `new Map([${entries.join(", ")}])`;
|
|
1991
|
-
}
|
|
1992
|
-
if (expr.collectionKind === "set") {
|
|
1993
|
-
const items = expr.elements.map((e) => this.emitExpr(e));
|
|
1994
|
-
return `new Set([${items.join(", ")}])`;
|
|
1995
|
-
}
|
|
1996
|
-
const items = expr.elements.map((e) => this.emitExpr(e));
|
|
1997
|
-
return `[${items.join(", ")}]`;
|
|
1702
|
+
if (expr.collectionKind === "map")
|
|
1703
|
+
return `new Map([${expr.elements.map((e) => ("kind" in e && e.kind === "MapEntry") ? `[${this.emitExpr(e.key)}, ${this.emitExpr(e.value)}]` : "null").join(", ")}])`;
|
|
1704
|
+
if (expr.collectionKind === "set")
|
|
1705
|
+
return `new Set([${expr.elements.map((e) => this.emitExpr(e)).join(", ")}])`;
|
|
1706
|
+
return `[${expr.elements.map((e) => this.emitExpr(e)).join(", ")}]`;
|
|
1998
1707
|
}
|
|
1999
1708
|
emitObjectExpr(expr) {
|
|
2000
1709
|
const superType = expr.superTypes[0];
|
|
2001
1710
|
const ext = superType ? ` extends ${this.emitTypeRef(superType.type)}` : "";
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
}
|
|
2009
|
-
captureBlockStatements(stmts) {
|
|
2010
|
-
return stmts.map((s) => this.captureStmt(s)).join(" ");
|
|
2011
|
-
}
|
|
2012
|
-
captureStmt(stmt) {
|
|
2013
|
-
const saved = this.w;
|
|
2014
|
-
const tmp = new Writer();
|
|
2015
|
-
this.w = tmp;
|
|
2016
|
-
this.emitStmt(stmt);
|
|
2017
|
-
this.w = saved;
|
|
2018
|
-
return tmp.output.trim();
|
|
2019
|
-
}
|
|
2020
|
-
captureClassMember(member) {
|
|
2021
|
-
const saved = this.w;
|
|
2022
|
-
const tmp = new Writer();
|
|
2023
|
-
this.w = tmp;
|
|
2024
|
-
this.emitClassMember(member);
|
|
2025
|
-
this.w = saved;
|
|
2026
|
-
return tmp.output.trim();
|
|
2027
|
-
}
|
|
2028
|
-
// ── Type reference emission ────────────────────────────────────────────────
|
|
1711
|
+
return `(new (class${ext} { ${expr.body.members.map((m) => this.captureClassMember(m)).join(" ")} })())`;
|
|
1712
|
+
}
|
|
1713
|
+
captureBlock(block) { return block.statements.map((s) => this.captureStmt(s)).join(" "); }
|
|
1714
|
+
captureBlockStatements(stmts) { return stmts.map((s) => this.captureStmt(s)).join(" "); }
|
|
1715
|
+
captureStmt(stmt) { const saved = this.w; const tmp = new Writer(); this.w = tmp; this.emitStmt(stmt); this.w = saved; return tmp.output.trim(); }
|
|
1716
|
+
captureClassMember(member) { const saved = this.w; const tmp = new Writer(); this.w = tmp; this.emitClassMember(member); this.w = saved; return tmp.output.trim(); }
|
|
2029
1717
|
emitTypeRef(ref) {
|
|
2030
1718
|
switch (ref.kind) {
|
|
2031
|
-
case "SimpleTypeRef":
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
}
|
|
2035
|
-
case "
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
return `<${ps.join(", ")}>`;
|
|
2061
|
-
}
|
|
2062
|
-
emitParamsStr(params) {
|
|
2063
|
-
return params.map((p) => {
|
|
2064
|
-
const spread = p.vararg ? "..." : "";
|
|
2065
|
-
// vararg params are rest params in TS: `...name: T[]`
|
|
2066
|
-
const type = this.opts.emitTypes
|
|
2067
|
-
? `: ${this.emitTypeRef(p.type)}${p.vararg ? "[]" : ""}`
|
|
2068
|
-
: "";
|
|
2069
|
-
const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
|
|
2070
|
-
return `${spread}${p.name}${type}${def}`;
|
|
2071
|
-
}).join(", ");
|
|
2072
|
-
}
|
|
2073
|
-
emitComponentPropsStr(params) {
|
|
2074
|
-
if (params.length === 0)
|
|
2075
|
-
return "";
|
|
2076
|
-
return params.map((p) => {
|
|
2077
|
-
const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
|
|
2078
|
-
return `${p.name}${type}`;
|
|
2079
|
-
}).join(", ");
|
|
2080
|
-
}
|
|
2081
|
-
emitSuperTypesStr(superTypes) {
|
|
2082
|
-
if (superTypes.length === 0)
|
|
2083
|
-
return "";
|
|
2084
|
-
const parts = [];
|
|
2085
|
-
let first = true;
|
|
2086
|
-
for (const s of superTypes) {
|
|
2087
|
-
// TypeScript does NOT allow constructor arguments in the `extends` clause.
|
|
2088
|
-
// Delegation args are passed via `super(args)` inside the constructor.
|
|
2089
|
-
if (first) {
|
|
2090
|
-
parts.push(` extends ${this.emitTypeRef(s.type)}`);
|
|
2091
|
-
first = false;
|
|
2092
|
-
}
|
|
2093
|
-
else {
|
|
2094
|
-
parts.push(` implements ${this.emitTypeRef(s.type)}`);
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
return parts.join("");
|
|
2098
|
-
}
|
|
2099
|
-
exportPrefix(mods) {
|
|
2100
|
-
if (mods.visibility === "private" || mods.visibility === "internal")
|
|
2101
|
-
return "";
|
|
2102
|
-
return "export ";
|
|
2103
|
-
}
|
|
2104
|
-
visibilityPrefix(mods) {
|
|
2105
|
-
switch (mods.visibility) {
|
|
2106
|
-
case "private": return "private ";
|
|
2107
|
-
case "protected": return "protected ";
|
|
2108
|
-
case "internal": return "/* internal */ ";
|
|
2109
|
-
default: return "";
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
unaryOpStr(op) {
|
|
2113
|
-
if (op === "not")
|
|
2114
|
-
return "!";
|
|
2115
|
-
return op;
|
|
2116
|
-
}
|
|
2117
|
-
binaryOpStr(op) {
|
|
2118
|
-
switch (op) {
|
|
2119
|
-
case "and": return "&&";
|
|
2120
|
-
case "or": return "||";
|
|
2121
|
-
case "xor": return "^";
|
|
2122
|
-
case "shl": return "<<";
|
|
2123
|
-
case "shr": return ">>";
|
|
2124
|
-
case "ushr": return ">>>";
|
|
2125
|
-
// === / !== are JS reference equality (Jalvin's triple-equals)
|
|
2126
|
-
case "===": return "===";
|
|
2127
|
-
case "!==": return "!==";
|
|
2128
|
-
case "..": return "/* .. */ +"; // handled by range()
|
|
2129
|
-
case "..<": return "/* ..< */ +";
|
|
2130
|
-
default: return op;
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
// ── AST name walkers (for wildcard import resolution) ─────────────────────
|
|
2134
|
-
/**
|
|
2135
|
-
* Walk the entire program AST and collect all identifier names that are
|
|
2136
|
-
* REFERENCED (i.e., used) in the code: NameExpr values and the first name
|
|
2137
|
-
* component of SimpleTypeRef / GenericTypeRef nodes.
|
|
2138
|
-
*
|
|
2139
|
-
* Jalvin primitive type names (String, Boolean, …) that map to TS primitives
|
|
2140
|
-
* are excluded from TypeRef collection since they never need an import.
|
|
2141
|
-
*/
|
|
1719
|
+
case "SimpleTypeRef": return PRIMITIVE_TYPE_MAP[ref.name.join(".")] ?? ref.name.join(".");
|
|
1720
|
+
case "NullableTypeRef": return `${this.emitTypeRef(ref.base)} | null | undefined`;
|
|
1721
|
+
case "GenericTypeRef": return `${GENERIC_TYPE_MAP[ref.base.name.join(".")] ?? ref.base.name.join(".")}<${ref.args.map((a) => a.star ? "any" : a.type ? this.emitTypeRef(a.type) : "unknown").join(", ")}>`;
|
|
1722
|
+
case "FunctionTypeRef": return `(${ref.params.map((p, i) => `p${i}: ${this.emitTypeRef(p)}`).join(", ")}) => ${this.emitTypeRef(ref.returnType)}`;
|
|
1723
|
+
case "StarProjection": return "any";
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
emitTypeParamsStr(params) { return params.length === 0 ? "" : `<${params.map((p) => `${p.name}${p.upperBound ? ` extends ${this.emitTypeRef(p.upperBound)}` : ""}`).join(", ")}>`; }
|
|
1727
|
+
emitParamsStr(params) { return params.map((p) => `${p.vararg ? "..." : ""}${p.name}${this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}${p.vararg ? "[]" : ""}` : ""}${p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : ""}`).join(", "); }
|
|
1728
|
+
emitSuperTypesStr(superTypes) { return superTypes.length === 0 ? "" : superTypes.map((s, i) => i === 0 ? ` extends ${this.emitTypeRef(s.type)}` : ` implements ${this.emitTypeRef(s.type)}`).join(""); }
|
|
1729
|
+
exportPrefix(mods) { return (mods.visibility === "private" || mods.visibility === "internal") ? "" : "export "; }
|
|
1730
|
+
visibilityPrefix(mods) { switch (mods.visibility) {
|
|
1731
|
+
case "private": return "private ";
|
|
1732
|
+
case "protected": return "protected ";
|
|
1733
|
+
case "internal": return "/* internal */ ";
|
|
1734
|
+
default: return "";
|
|
1735
|
+
} }
|
|
1736
|
+
unaryOpStr(op) { return op === "not" ? "!" : op; }
|
|
1737
|
+
binaryOpStr(op) { switch (op) {
|
|
1738
|
+
case "and": return "&&";
|
|
1739
|
+
case "or": return "||";
|
|
1740
|
+
case "xor": return "^";
|
|
1741
|
+
case "shl": return "<<";
|
|
1742
|
+
case "shr": return ">>";
|
|
1743
|
+
case "ushr": return ">>>";
|
|
1744
|
+
case "===": return "===";
|
|
1745
|
+
case "!==": return "!==";
|
|
1746
|
+
default: return op;
|
|
1747
|
+
} }
|
|
2142
1748
|
gatherReferencedNames(program) {
|
|
2143
1749
|
const names = new Set();
|
|
2144
1750
|
const visited = new WeakSet();
|
|
2145
1751
|
const walk = (val) => {
|
|
2146
|
-
if (!val || typeof val !== "object")
|
|
1752
|
+
if (!val || typeof val !== "object" || visited.has(val))
|
|
2147
1753
|
return;
|
|
1754
|
+
visited.add(val);
|
|
2148
1755
|
if (Array.isArray(val)) {
|
|
2149
|
-
|
|
2150
|
-
walk(item);
|
|
1756
|
+
val.forEach(walk);
|
|
2151
1757
|
return;
|
|
2152
1758
|
}
|
|
2153
1759
|
const obj = val;
|
|
2154
|
-
if (
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
const kind = obj["kind"];
|
|
2158
|
-
if (kind === "NameExpr") {
|
|
2159
|
-
const name = obj["name"];
|
|
2160
|
-
if (typeof name === "string")
|
|
2161
|
-
names.add(name);
|
|
2162
|
-
return; // leaf node
|
|
2163
|
-
}
|
|
2164
|
-
if (kind === "SimpleTypeRef") {
|
|
2165
|
-
const nameArr = obj["name"];
|
|
2166
|
-
if (Array.isArray(nameArr) && nameArr.length > 0) {
|
|
2167
|
-
const first = nameArr[0];
|
|
2168
|
-
// Skip Jalvin primitive type names — they're erased to TS primitives.
|
|
2169
|
-
if (!(first in PRIMITIVE_TYPE_MAP))
|
|
2170
|
-
names.add(first);
|
|
2171
|
-
}
|
|
1760
|
+
if (obj.kind === "NameExpr") {
|
|
1761
|
+
if (typeof obj.name === "string")
|
|
1762
|
+
names.add(obj.name);
|
|
2172
1763
|
return;
|
|
2173
1764
|
}
|
|
2174
|
-
if (kind === "
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
const first = nameArr[0];
|
|
2179
|
-
if (!(first in PRIMITIVE_TYPE_MAP))
|
|
2180
|
-
names.add(first);
|
|
2181
|
-
}
|
|
2182
|
-
// Fall through to recurse into type arguments
|
|
1765
|
+
if (obj.kind === "SimpleTypeRef") {
|
|
1766
|
+
if (obj.name?.[0] && !(obj.name[0] in PRIMITIVE_TYPE_MAP))
|
|
1767
|
+
names.add(obj.name[0]);
|
|
1768
|
+
return;
|
|
2183
1769
|
}
|
|
2184
|
-
|
|
2185
|
-
|
|
1770
|
+
if (obj.kind === "GenericTypeRef") {
|
|
1771
|
+
if (obj.base?.name?.[0] && !(obj.base.name[0] in PRIMITIVE_TYPE_MAP))
|
|
1772
|
+
names.add(obj.base.name[0]);
|
|
2186
1773
|
}
|
|
1774
|
+
Object.values(obj).forEach(walk);
|
|
2187
1775
|
};
|
|
2188
|
-
|
|
2189
|
-
walk(decl);
|
|
1776
|
+
program.declarations.forEach(walk);
|
|
2190
1777
|
return names;
|
|
2191
1778
|
}
|
|
2192
|
-
/**
|
|
2193
|
-
* Walk the entire program AST and collect all names that are LOCALLY DEFINED:
|
|
2194
|
-
* top-level declaration names, non-star import aliases, function/lambda
|
|
2195
|
-
* parameters, local val/var bindings, for-loop variables, catch-clause names,
|
|
2196
|
-
* when-subject bindings, and type parameter names.
|
|
2197
|
-
*/
|
|
2198
1779
|
gatherAllLocalBindings(program) {
|
|
2199
1780
|
const names = new Set();
|
|
2200
1781
|
const visited = new WeakSet();
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
names.add(d.name);
|
|
2206
|
-
}
|
|
2207
|
-
// Non-star import aliases
|
|
2208
|
-
for (const imp of program.imports) {
|
|
2209
|
-
if (!imp.star) {
|
|
2210
|
-
const name = imp.alias ?? imp.path[imp.path.length - 1];
|
|
2211
|
-
if (name)
|
|
2212
|
-
names.add(name);
|
|
2213
|
-
}
|
|
2214
|
-
}
|
|
1782
|
+
program.declarations.forEach((d) => { if (d.name)
|
|
1783
|
+
names.add(d.name); });
|
|
1784
|
+
program.imports.forEach((i) => { if (!i.star)
|
|
1785
|
+
names.add(i.alias ?? i.path[i.path.length - 1]); });
|
|
2215
1786
|
const walk = (val) => {
|
|
2216
|
-
if (!val || typeof val !== "object")
|
|
1787
|
+
if (!val || typeof val !== "object" || visited.has(val))
|
|
2217
1788
|
return;
|
|
1789
|
+
visited.add(val);
|
|
2218
1790
|
if (Array.isArray(val)) {
|
|
2219
|
-
|
|
2220
|
-
walk(item);
|
|
1791
|
+
val.forEach(walk);
|
|
2221
1792
|
return;
|
|
2222
1793
|
}
|
|
2223
1794
|
const obj = val;
|
|
2224
|
-
if (
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
if (typeof n === "string")
|
|
2236
|
-
names.add(n);
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
if (kind === "FunDecl" || kind === "ExtensionFunDecl" ||
|
|
2241
|
-
kind === "ComponentDecl" || kind === "ClassDecl" ||
|
|
2242
|
-
kind === "DataClassDecl" || kind === "SealedClassDecl" ||
|
|
2243
|
-
kind === "EnumClassDecl" || kind === "InterfaceDecl" ||
|
|
2244
|
-
kind === "ObjectDecl" || kind === "TypeAliasDecl") {
|
|
2245
|
-
if (typeof obj["name"] === "string" && obj["name"])
|
|
2246
|
-
names.add(obj["name"]);
|
|
2247
|
-
// Type parameters
|
|
2248
|
-
const typeParams = obj["typeParams"];
|
|
2249
|
-
if (Array.isArray(typeParams)) {
|
|
2250
|
-
for (const tp of typeParams) {
|
|
2251
|
-
if (tp.name)
|
|
2252
|
-
names.add(tp.name);
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
// Function/method parameters
|
|
2256
|
-
const params = obj["params"];
|
|
2257
|
-
if (Array.isArray(params)) {
|
|
2258
|
-
for (const p of params) {
|
|
2259
|
-
if (p.name)
|
|
2260
|
-
names.add(p.name);
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
if (kind === "LambdaExpr") {
|
|
2265
|
-
const params = obj["params"];
|
|
2266
|
-
if (Array.isArray(params)) {
|
|
2267
|
-
for (const p of params) {
|
|
2268
|
-
if (p.name)
|
|
2269
|
-
names.add(p.name);
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
if (kind === "ForStmt") {
|
|
2274
|
-
const binding = obj["binding"];
|
|
2275
|
-
if (typeof binding === "string") {
|
|
2276
|
-
names.add(binding);
|
|
2277
|
-
}
|
|
2278
|
-
else if (binding && typeof binding === "object") {
|
|
2279
|
-
const b = binding;
|
|
2280
|
-
// TupleDestructure or MapDestructure
|
|
2281
|
-
if (Array.isArray(b["names"])) {
|
|
2282
|
-
for (const n of b["names"]) {
|
|
2283
|
-
if (typeof n === "string")
|
|
2284
|
-
names.add(n);
|
|
2285
|
-
}
|
|
2286
|
-
}
|
|
2287
|
-
if (typeof b["key"] === "string")
|
|
2288
|
-
names.add(b["key"]);
|
|
2289
|
-
if (typeof b["value"] === "string")
|
|
2290
|
-
names.add(b["value"]);
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
if (kind === "TryCatchStmt") {
|
|
2294
|
-
const catches = obj["catches"];
|
|
2295
|
-
if (Array.isArray(catches)) {
|
|
2296
|
-
for (const c of catches) {
|
|
2297
|
-
if (c.name)
|
|
2298
|
-
names.add(c.name);
|
|
2299
|
-
}
|
|
2300
|
-
}
|
|
2301
|
-
}
|
|
2302
|
-
if (kind === "WhenStmt" || kind === "WhenExpr") {
|
|
2303
|
-
const subject = obj["subject"];
|
|
2304
|
-
if (typeof subject?.["binding"] === "string")
|
|
2305
|
-
names.add(subject["binding"]);
|
|
2306
|
-
}
|
|
2307
|
-
if (kind === "SecondaryConstructor") {
|
|
2308
|
-
const params = obj["params"];
|
|
2309
|
-
if (Array.isArray(params)) {
|
|
2310
|
-
for (const p of params) {
|
|
2311
|
-
if (p.name)
|
|
2312
|
-
names.add(p.name);
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
}
|
|
2316
|
-
for (const propVal of Object.values(obj)) {
|
|
2317
|
-
walk(propVal);
|
|
2318
|
-
}
|
|
1795
|
+
if (["PropertyDecl", "DestructuringDecl", "FunDecl", "ExtensionFunDecl", "ComponentDecl", "ClassDecl", "DataClassDecl", "SealedClassDecl", "EnumClassDecl", "InterfaceDecl", "ObjectDecl", "TypeAliasDecl", "LambdaExpr"].includes(obj.kind) && obj.name)
|
|
1796
|
+
names.add(obj.name);
|
|
1797
|
+
if (obj.typeParams)
|
|
1798
|
+
obj.typeParams.forEach((tp) => names.add(tp.name));
|
|
1799
|
+
if (obj.params)
|
|
1800
|
+
obj.params.forEach((p) => names.add(p.name));
|
|
1801
|
+
if (obj.kind === "ForStmt" && typeof obj.binding === "string")
|
|
1802
|
+
names.add(obj.binding);
|
|
1803
|
+
if (obj.kind === "TryCatchStmt" && obj.catches)
|
|
1804
|
+
obj.catches.forEach((c) => names.add(c.name));
|
|
1805
|
+
Object.values(obj).forEach(walk);
|
|
2319
1806
|
};
|
|
2320
|
-
|
|
2321
|
-
walk(decl);
|
|
1807
|
+
program.declarations.forEach(walk);
|
|
2322
1808
|
return names;
|
|
2323
1809
|
}
|
|
2324
1810
|
}
|
|
2325
1811
|
exports.CodeGenerator = CodeGenerator;
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
// ---------------------------------------------------------------------------
|
|
2329
|
-
const PRIMITIVE_TYPE_MAP = {
|
|
2330
|
-
Int: "number",
|
|
2331
|
-
Long: "bigint",
|
|
2332
|
-
Float: "number",
|
|
2333
|
-
Double: "number",
|
|
2334
|
-
Boolean: "boolean",
|
|
2335
|
-
String: "string",
|
|
2336
|
-
Char: "string",
|
|
2337
|
-
Byte: "number",
|
|
2338
|
-
Short: "number",
|
|
2339
|
-
Unit: "void",
|
|
2340
|
-
Any: "unknown",
|
|
2341
|
-
Nothing: "never",
|
|
2342
|
-
};
|
|
2343
|
-
const GENERIC_TYPE_MAP = {
|
|
2344
|
-
List: "ReadonlyArray",
|
|
2345
|
-
MutableList: "Array",
|
|
2346
|
-
Set: "ReadonlySet",
|
|
2347
|
-
MutableSet: "Set",
|
|
2348
|
-
Map: "ReadonlyMap",
|
|
2349
|
-
MutableMap: "Map",
|
|
2350
|
-
Array: "Array",
|
|
2351
|
-
Pair: "[", // handled specially
|
|
2352
|
-
Triple: "[",
|
|
2353
|
-
Deferred: "Promise",
|
|
2354
|
-
StateFlow: "StateFlow",
|
|
2355
|
-
MutableStateFlow: "MutableStateFlow",
|
|
2356
|
-
Flow: "AsyncIterable",
|
|
2357
|
-
};
|
|
1812
|
+
const PRIMITIVE_TYPE_MAP = { Int: "number", Long: "bigint", Float: "number", Double: "number", Boolean: "boolean", String: "string", Char: "string", Byte: "number", Short: "number", Unit: "void", Any: "unknown", Nothing: "never" };
|
|
1813
|
+
const GENERIC_TYPE_MAP = { List: "ReadonlyArray", MutableList: "Array", Set: "ReadonlySet", MutableSet: "Set", Map: "ReadonlyMap", MutableMap: "Map", Array: "Array", Deferred: "Promise", StateFlow: "StateFlow", MutableStateFlow: "MutableStateFlow", Flow: "AsyncIterable" };
|
|
2358
1814
|
const PRIMITIVE_TYPES = new Set(Object.keys(PRIMITIVE_TYPE_MAP));
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
// as named imports from a wildcard-imported package.
|
|
2362
|
-
// ---------------------------------------------------------------------------
|
|
2363
|
-
const JS_GLOBAL_NAMES = new Set([
|
|
2364
|
-
// JS built-in constructors and objects
|
|
2365
|
-
"Array", "Map", "Set", "Object", "String", "Number", "Boolean",
|
|
2366
|
-
"Promise", "Error", "TypeError", "RangeError", "SyntaxError", "URIError",
|
|
2367
|
-
"EvalError", "ReferenceError",
|
|
2368
|
-
"console", "Math", "Date", "JSON", "RegExp", "Symbol", "BigInt",
|
|
2369
|
-
"Proxy", "Reflect", "globalThis", "Atomics", "SharedArrayBuffer",
|
|
2370
|
-
"ArrayBuffer", "DataView", "Int8Array", "Uint8Array", "Uint8ClampedArray",
|
|
2371
|
-
"Int16Array", "Uint16Array", "Int32Array", "Uint32Array",
|
|
2372
|
-
"Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array",
|
|
2373
|
-
// Global functions
|
|
2374
|
-
"isNaN", "isFinite", "parseInt", "parseFloat", "eval",
|
|
2375
|
-
"encodeURI", "decodeURI", "encodeURIComponent", "decodeURIComponent",
|
|
2376
|
-
// Browser/Node globals
|
|
2377
|
-
"setTimeout", "clearTimeout", "setInterval", "clearInterval",
|
|
2378
|
-
"queueMicrotask", "requestAnimationFrame", "cancelAnimationFrame",
|
|
2379
|
-
"fetch", "URL", "URLSearchParams", "AbortController", "AbortSignal",
|
|
2380
|
-
"EventTarget", "Event", "CustomEvent", "FormData", "Headers",
|
|
2381
|
-
"Request", "Response", "Blob", "File", "FileReader",
|
|
2382
|
-
"Worker", "SharedWorker", "WebSocket",
|
|
2383
|
-
"ReadableStream", "WritableStream", "TransformStream",
|
|
2384
|
-
"document", "window", "navigator", "location", "history", "screen",
|
|
2385
|
-
"performance", "crypto", "indexedDB", "localStorage", "sessionStorage",
|
|
2386
|
-
"confirm", "alert", "prompt",
|
|
2387
|
-
"process", "Buffer", "global", "require", "module", "exports", "__dirname", "__filename",
|
|
2388
|
-
// Special identifiers
|
|
2389
|
-
"undefined", "null", "NaN", "Infinity",
|
|
2390
|
-
"it", "this", "super", "arguments", "new", "class", "function",
|
|
2391
|
-
// TypeScript primitive type names
|
|
2392
|
-
"any", "unknown", "never", "void", "string", "number", "boolean", "bigint",
|
|
2393
|
-
"object", "symbol",
|
|
2394
|
-
// Jalvin type names that map to TS primitives (never need import)
|
|
2395
|
-
"String", "Boolean", "Any", "Nothing", "Unit", "Char", "Byte", "Short",
|
|
2396
|
-
"Float", "Double",
|
|
2397
|
-
]);
|
|
2398
|
-
// ---------------------------------------------------------------------------
|
|
2399
|
-
// Public helper
|
|
2400
|
-
// ---------------------------------------------------------------------------
|
|
2401
|
-
function generate(program, opts, operatorOverloads, typeMap) {
|
|
2402
|
-
return new CodeGenerator(opts).generate(program, operatorOverloads, typeMap);
|
|
2403
|
-
}
|
|
1815
|
+
const JS_GLOBAL_NAMES = new Set(["Array", "Map", "Set", "Object", "String", "Number", "Boolean", "Promise", "Error", "console", "Math", "Date", "JSON", "setTimeout", "setInterval", "fetch", "document", "window", "undefined", "null", "NaN", "Infinity", "it", "this", "super", "any", "unknown", "never", "void"]);
|
|
1816
|
+
function generate(program, opts, operatorOverloads, typeMap) { return new CodeGenerator(opts).generate(program, operatorOverloads, typeMap); }
|
|
2404
1817
|
//# sourceMappingURL=codegen.js.map
|