@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.
Files changed (56) hide show
  1. package/dist/codegen.d.ts +4 -56
  2. package/dist/codegen.d.ts.map +1 -1
  3. package/dist/codegen.js +297 -884
  4. package/dist/codegen.js.map +1 -1
  5. package/dist/index.d.ts +1 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -4
  8. package/dist/index.js.map +1 -1
  9. package/dist/typechecker.d.ts.map +1 -1
  10. package/dist/typechecker.js +1 -3
  11. package/dist/typechecker.js.map +1 -1
  12. package/package.json +1 -1
  13. package/dist/codegen/index.d.ts +0 -179
  14. package/dist/codegen/index.d.ts.map +0 -1
  15. package/dist/codegen/index.js +0 -2207
  16. package/dist/codegen/index.js.map +0 -1
  17. package/dist/codegen_freestanding_c.d.ts +0 -9
  18. package/dist/codegen_freestanding_c.d.ts.map +0 -1
  19. package/dist/codegen_freestanding_c.js +0 -321
  20. package/dist/codegen_freestanding_c.js.map +0 -1
  21. package/dist/lexer/chars.d.ts +0 -5
  22. package/dist/lexer/chars.d.ts.map +0 -1
  23. package/dist/lexer/chars.js +0 -20
  24. package/dist/lexer/chars.js.map +0 -1
  25. package/dist/lexer/index.d.ts +0 -37
  26. package/dist/lexer/index.d.ts.map +0 -1
  27. package/dist/lexer/index.js +0 -630
  28. package/dist/lexer/index.js.map +0 -1
  29. package/dist/lexer/tokens.d.ts +0 -135
  30. package/dist/lexer/tokens.d.ts.map +0 -1
  31. package/dist/lexer/tokens.js +0 -89
  32. package/dist/lexer/tokens.js.map +0 -1
  33. package/dist/parser/constants.d.ts +0 -3
  34. package/dist/parser/constants.d.ts.map +0 -1
  35. package/dist/parser/constants.js +0 -7
  36. package/dist/parser/constants.js.map +0 -1
  37. package/dist/parser/helpers.d.ts +0 -2
  38. package/dist/parser/helpers.d.ts.map +0 -1
  39. package/dist/parser/helpers.js +0 -9
  40. package/dist/parser/helpers.js.map +0 -1
  41. package/dist/parser/index.d.ts +0 -118
  42. package/dist/parser/index.d.ts.map +0 -1
  43. package/dist/parser/index.js +0 -2387
  44. package/dist/parser/index.js.map +0 -1
  45. package/dist/typechecker/globals.d.ts +0 -2
  46. package/dist/typechecker/globals.d.ts.map +0 -1
  47. package/dist/typechecker/globals.js +0 -15
  48. package/dist/typechecker/globals.js.map +0 -1
  49. package/dist/typechecker/index.d.ts +0 -146
  50. package/dist/typechecker/index.d.ts.map +0 -1
  51. package/dist/typechecker/index.js +0 -2288
  52. package/dist/typechecker/index.js.map +0 -1
  53. package/dist/typechecker/types.d.ts +0 -66
  54. package/dist/typechecker/types.d.ts.map +0 -1
  55. package/dist/typechecker/types.js +0 -39
  56. 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/TSX
3
+ // Jalvin Code Generator — AST → TypeScript
4
4
  //
5
5
  // Design principles:
6
- // • component declarations → React functional components (TSX)
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 emit React.createElement boundaries */
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
- isJsx: this.hasComponents,
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
- lines.push('import React from "react";');
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 unless this looks like
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 // src.models.css.Css → "src/models/css"
288
+ ? precedingRelPath
303
289
  : looksLikeNpmPackage
304
- ? precedingRelPath // cubing.twisty.TwistyPlayer → "cubing/twisty"
305
- : imp.path.join("/"); // src.models.Rotation → "src/models/Rotation"
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 → React function components ────────────────────
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
- if (this.isJsxMode) {
485
- // React.createElement format: children are part of props destructuring
486
- // Props interface
487
- if (decl.params.length > 0) {
488
- this.w.writeIndentedLine(`interface ${decl.name}Props {`);
489
- this.w.pushIndent();
490
- for (const p of decl.params) {
491
- const optional = p.defaultValue || p.name === "children" ? "?" : "";
492
- const typeStr = this.opts.emitTypes ? this.emitTypeRef(p.type) : "any";
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.writeIndentedLine(`${vis}function ${decl.name}(${signature}) {`);
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)}` : (isLateinit ? "" : "");
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
- for (const member of body.members) {
1067
- // InitBlocks are emitted inside the constructor by emitPrimaryConstructorProps
1068
- if (member.kind === "InitBlock")
1069
- continue;
1070
- this.emitClassMember(member);
1071
- this.w.writeLine();
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
- // Emit init{} blocks inside the constructor, in declaration order
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
- // Ensure Int/Long companion objects are imported from the runtime
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
- // `compareTo` overloads: wrap result in a comparison against 0
1420
- if (opMethod === "compareTo") {
1421
- const cmpOp = expr.op;
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
- // `in` / `!in` without a user-defined operator: use JS `in` / array includes
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
- return `${this.emitExpr(expr.target)}.${expr.member}`;
1459
- case "SafeMemberExpr":
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
- // left ?? right (null coalescing)
1480
- return `(${this.emitExpr(expr.left)} ?? ${this.emitExpr(expr.right)})`;
1481
- }
1482
- case "CallExpr":
1483
- return this.emitCallExpr(expr);
1484
- case "LambdaExpr":
1485
- return this.emitLambdaExpr(expr);
1486
- case "IfExpr":
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
- return this.emitCollectionLiteral(expr);
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
- return this.emitJsxElement(expr);
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
- if (expr.children.length === 0) {
1537
- return `<${expr.tag}${attrsStr} />`;
1538
- }
1539
- const children = expr.children.map((c) => this.emitJsxChild(c)).join("");
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
- // Map HTML attribute names to React prop names
1544
- const name = attr.name === "class" ? "className"
1545
- : attr.name === "for" ? "htmlFor"
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 `{${this.emitExpr(child.expr)}}`;
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
- // Rewrite infix numeric extension calls that JS numbers don't natively have:
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
- // Scope function call rewriting: `x.let { ... }` → `let_(x, ...)`
1595
- // `x.apply { ... }` `apply(x, function(this:any) { ... })`
1596
- if (scopeMember === "let" || scopeMember === "also" || scopeMember === "apply" || scopeMember === "run" || scopeMember === "takeIf" || scopeMember === "takeUnless") {
1597
- const receiver = this.emitExpr(expr.callee.target);
1598
- const lambda = expr.trailingLambda ??
1599
- (expr.args.length === 1 && expr.args[0].value.kind === "LambdaExpr"
1600
- ? expr.args[0].value
1601
- : null);
1602
- if (lambda) {
1603
- if (scopeMember === "let") {
1604
- this.runtimeSymbolsNeeded.add("let_");
1605
- return `let_(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1606
- }
1607
- if (scopeMember === "also") {
1608
- this.runtimeSymbolsNeeded.add("also");
1609
- return `also(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1610
- }
1611
- if (scopeMember === "apply") {
1612
- this.runtimeSymbolsNeeded.add("apply");
1613
- const body = this.captureBlockStatements(lambda.body);
1614
- return `apply(${receiver}, function(this: any) { ${body} })`;
1615
- }
1616
- if (scopeMember === "run") {
1617
- this.runtimeSymbolsNeeded.add("run_");
1618
- const body = this.captureBlockStatements(lambda.body);
1619
- return `run_(${receiver}, function(this: any) { ${body} })`;
1620
- }
1621
- if (scopeMember === "takeIf") {
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
- ? `<${expr.typeArgs.map((t) => t.star ? "*" : this.emitTypeRef(t.type)).join(", ")}>`
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
- // Rewrite top-level `run { ... }` (no receiver) just call the lambda
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
- if (!isKnownPositionalFunc && expr.callee.kind === "NameExpr" && (this.allComponentLikeNames.has(expr.callee.name) ||
1682
- (this.hasUiStarImport && (!calleeType || calleeType.tag === "unknown") && /^[A-Z]/.test(expr.callee.name)))) {
1683
- return this.emitComponentCall(expr);
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
- // Handle trailing positional args if any
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
- // Fallback for types we don't know param names for (like constructors or any)
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
- // Constructor calls: class names start with uppercase by convention
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
- // Look for a top-level ComponentDecl matching the imported symbol name
1809
- const compDecl = ast.declarations.find((d) => d.kind === "ComponentDecl" && d.name === rawSymbolName);
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, compDecl.params.map((p) => p.name));
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
- // e.g. UiState.Success(data), Discount.Percentage(0.1)
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 tag = expr.callee.name;
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
- // Param names from type checker (for same-file components) or resolved from imported files
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
- // Prop name: explicit named arg > known param name > NameExpr variable name (shorthand)
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 (this.isJsxMode && this.isTrueComponent(tag)) {
1876
- // React.createElement format for JSX output (True Components only)
1877
- if (expr.trailingLambda) {
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
- if (typeof s.binding === "string") {
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
- // If no explicit params, emit `it` as the single implicit parameter
1928
- // (Convention for single-argument lambdas)
1929
- const params = expr.params.length === 0
1930
- ? "it"
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
- ? `(() => { ${this.captureBlockStatements(expr.then.statements)} })()`
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
- if (branch.isElse) {
1958
- const body = branch.body.kind === "Block"
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
- else {
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
- const entries = expr.elements.map((e) => {
1985
- if ("kind" in e && e.kind === "MapEntry") {
1986
- return `[${this.emitExpr(e.key)}, ${this.emitExpr(e.value)}]`;
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
- const members = expr.body.members.map((m) => this.captureClassMember(m)).join(" ");
2003
- return `(new (class${ext} { ${members} })())`;
2004
- }
2005
- // ── capture helpers (emit to temp string) ─────────────────────────────────
2006
- captureBlock(block) {
2007
- return block.statements.map((s) => this.captureStmt(s)).join(" ");
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
- const name = ref.name.join(".");
2033
- return PRIMITIVE_TYPE_MAP[name] ?? name;
2034
- }
2035
- case "NullableTypeRef":
2036
- return `${this.emitTypeRef(ref.base)} | null | undefined`;
2037
- case "GenericTypeRef": {
2038
- const base = ref.base.name.join(".");
2039
- const mapped = GENERIC_TYPE_MAP[base] ?? base;
2040
- const args = ref.args.map((a) => a.star ? "any" : a.type ? this.emitTypeRef(a.type) : "unknown");
2041
- return `${mapped}<${args.join(", ")}>`;
2042
- }
2043
- case "FunctionTypeRef": {
2044
- const params = ref.params.map((p, i) => `p${i}: ${this.emitTypeRef(p)}`).join(", ");
2045
- const ret = this.emitTypeRef(ref.returnType);
2046
- return `(${params}) => ${ret}`;
2047
- }
2048
- case "StarProjection":
2049
- return "any";
2050
- }
2051
- }
2052
- // ── Utility helpers ────────────────────────────────────────────────────────
2053
- emitTypeParamsStr(params) {
2054
- if (params.length === 0)
2055
- return "";
2056
- const ps = params.map((p) => {
2057
- const bound = p.upperBound ? ` extends ${this.emitTypeRef(p.upperBound)}` : "";
2058
- return `${p.name}${bound}`;
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
- for (const item of val)
2150
- walk(item);
1756
+ val.forEach(walk);
2151
1757
  return;
2152
1758
  }
2153
1759
  const obj = val;
2154
- if (visited.has(obj))
2155
- return;
2156
- visited.add(obj);
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 === "GenericTypeRef") {
2175
- const base = obj["base"];
2176
- const nameArr = base?.["name"];
2177
- if (Array.isArray(nameArr) && nameArr.length > 0) {
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
- for (const propVal of Object.values(obj)) {
2185
- walk(propVal);
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
- for (const decl of program.declarations)
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
- // Top-level declaration names
2202
- for (const decl of program.declarations) {
2203
- const d = decl;
2204
- if (typeof d.name === "string" && d.name)
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
- for (const item of val)
2220
- walk(item);
1791
+ val.forEach(walk);
2221
1792
  return;
2222
1793
  }
2223
1794
  const obj = val;
2224
- if (visited.has(obj))
2225
- return;
2226
- visited.add(obj);
2227
- const kind = obj["kind"];
2228
- // Collect bound names at their declaration sites
2229
- if (kind === "PropertyDecl" || kind === "DestructuringDecl") {
2230
- if (typeof obj["name"] === "string")
2231
- names.add(obj["name"]);
2232
- const nms = obj["names"];
2233
- if (Array.isArray(nms)) {
2234
- for (const n of nms) {
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
- for (const decl of program.declarations)
2321
- walk(decl);
1807
+ program.declarations.forEach(walk);
2322
1808
  return names;
2323
1809
  }
2324
1810
  }
2325
1811
  exports.CodeGenerator = CodeGenerator;
2326
- // ---------------------------------------------------------------------------
2327
- // Type name mappings (Jalvin TypeScript)
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
- // Well-known JavaScript / TypeScript global names that must never be emitted
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