@jalvin/compiler 2.0.43 → 2.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/codegen.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,8 +98,10 @@ class CodeGenerator {
99
98
  opts;
100
99
  hasComponents = false;
101
100
  runtimeSymbolsNeeded = new Set();
102
- /** Names of component functions — used to detect component calls and emit them as JSX */
103
- componentNames = new Set();
101
+ /** Names of true Jalvin components — used to detect functional calls */
102
+ userComponentNames = new Set();
103
+ /** Names of all potential components (including UI primitives) — used to detect props-object calls */
104
+ allComponentLikeNames = new Set();
104
105
  /**
105
106
  * Param names for components resolved from imported local .jalvin files.
106
107
  * Maps component name → ordered list of param names.
@@ -108,6 +109,8 @@ class CodeGenerator {
108
109
  componentParamNames = new Map();
109
110
  /** True when the program contains `import @jalvin/ui.*` — used to detect UI primitive calls */
110
111
  hasUiStarImport = false;
112
+ /** Names of symbols explicitly imported from @jalvin/ui */
113
+ uiNamedImports = new Set();
111
114
  /** Operator overload resolutions from the type checker */
112
115
  operatorOverloadMap = new Map();
113
116
  /** Type map from the type checker */
@@ -131,6 +134,7 @@ class CodeGenerator {
131
134
  * Maps module specifier (e.g. "src/views") → sorted list of exported names.
132
135
  */
133
136
  localStarExports = new Map();
137
+ isInClass = false;
134
138
  constructor(opts = {}) {
135
139
  this.opts = { ...exports.DEFAULT_CODEGEN_OPTIONS, ...opts };
136
140
  }
@@ -141,34 +145,40 @@ class CodeGenerator {
141
145
  this.typeMap = typeMap;
142
146
  // Pre-scan for components (local declarations OR @jalvin/ui imports)
143
147
  this.hasComponents = program.declarations.some((d) => d.kind === "ComponentDecl" ||
144
- (d.kind === "ClassDecl" && d.body?.members.some((m) => m.kind === "ComponentDecl"))) || program.imports.some((imp) => imp.path[0] === "@jalvin" && imp.path[1] === "ui");
148
+ (d.kind === "ClassDecl" && d.body?.members.some((m) => m.kind === "ComponentDecl"))) || program.imports.some((imp) => imp.path[0] === "@jalvin" && imp.path[1] === "ui")
149
+ || this.containsJsx(program);
145
150
  // Collect component names for component call detection
146
- this.componentNames = new Set();
151
+ this.userComponentNames = new Set();
152
+ this.allComponentLikeNames = new Set();
147
153
  this.componentParamNames = new Map();
148
154
  for (const decl of program.declarations) {
149
155
  if (decl.kind === "ComponentDecl") {
150
- this.componentNames.add(decl.name);
156
+ this.userComponentNames.add(decl.name);
157
+ this.allComponentLikeNames.add(decl.name);
151
158
  this.componentParamNames.set(decl.name, decl.params.map((p) => p.name));
152
159
  }
153
160
  if (decl.kind === "ClassDecl" && decl.body) {
154
161
  for (const m of decl.body.members) {
155
162
  if (m.kind === "ComponentDecl") {
156
- this.componentNames.add(m.name);
163
+ this.userComponentNames.add(m.name);
164
+ this.allComponentLikeNames.add(m.name);
157
165
  this.componentParamNames.set(m.name, m.params.map((p) => p.name));
158
166
  }
159
167
  }
160
168
  }
161
169
  }
162
170
  this.hasUiStarImport = false;
171
+ this.uiNamedImports = new Set();
163
172
  for (const imp of program.imports) {
164
- // Named imports from @jalvin/ui are treated as potential component functions.
165
- // Hooks (e.g. useIsHovered) are filtered out at call-site by the type-based guard below.
166
- if (imp.path[0] === "@jalvin" && imp.path[1] === "ui" && !imp.star) {
167
- this.componentNames.add(imp.path[imp.path.length - 1]);
168
- }
169
- // Star import from @jalvin/ui — UI primitives won't be in componentNames
170
- if (imp.path[0] === "@jalvin" && imp.path[1] === "ui" && imp.star) {
171
- this.hasUiStarImport = true;
173
+ if (imp.path[0] === "@jalvin" && imp.path[1] === "ui") {
174
+ if (imp.star) {
175
+ this.hasUiStarImport = true;
176
+ }
177
+ else {
178
+ const name = imp.path[imp.path.length - 1];
179
+ this.uiNamedImports.add(name);
180
+ this.allComponentLikeNames.add(name);
181
+ }
172
182
  }
173
183
  }
174
184
  // Resolve component fun symbols from locally imported .jalvin files
@@ -202,14 +212,42 @@ class CodeGenerator {
202
212
  return {
203
213
  code,
204
214
  lineMap: this.w.lineMap,
205
- isJsx: this.hasComponents,
215
+ hasComponents: this.hasComponents,
216
+ };
217
+ }
218
+ /** Walk AST to see if it contains any JsxElement nodes */
219
+ containsJsx(program) {
220
+ let found = false;
221
+ const walk = (val) => {
222
+ if (found || !val || typeof val !== "object")
223
+ return;
224
+ if (Array.isArray(val)) {
225
+ for (const item of val)
226
+ walk(item);
227
+ return;
228
+ }
229
+ const obj = val;
230
+ if (obj["kind"] === "JsxElement") {
231
+ found = true;
232
+ return;
233
+ }
234
+ for (const propVal of Object.values(obj)) {
235
+ walk(propVal);
236
+ }
206
237
  };
238
+ for (const decl of program.declarations)
239
+ walk(decl);
240
+ return found;
207
241
  }
208
242
  // ── Preamble & imports ─────────────────────────────────────────────────────
209
243
  buildPreamble() {
210
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("// ─────────────────────────────────────────────────────────────────────────────");
211
249
  if (this.hasComponents) {
212
- lines.push('import React from "react";');
250
+ this.runtimeSymbolsNeeded.add("jalvinCreateElement");
213
251
  }
214
252
  // Emit compiler-injected runtime symbols, but only those NOT already covered
215
253
  // by a star-import's named-import expansion (to avoid duplicate imports).
@@ -226,19 +264,6 @@ class CodeGenerator {
226
264
  const sourceRoot = this.opts.sourceRoot ?? process.cwd();
227
265
  // Re-emit user imports as ES imports
228
266
  for (const imp of program.imports) {
229
- // Build module specifier.
230
- //
231
- // Scoped packages (@org/pkg.Symbol): symbol is an export from the package,
232
- // strip the last segment.
233
- // import @jalvin/ui.Column → import { Column } from "@jalvin/ui"
234
- // import @jalvin/runtime.* → import * as runtime from "@jalvin/runtime"
235
- //
236
- // Local imports (a.b.C): check whether the preceding segments already
237
- // resolve to a file on disk.
238
- // import src.models.Rotation → src/models/Rotation.ts does NOT exist
239
- // → import { Rotation } from "src/models/Rotation"
240
- // import src.models.css.Css → src/models/css.ts DOES exist
241
- // → import { Css } from "src/models/css"
242
267
  const isScoped = imp.path[0].startsWith("@");
243
268
  let moduleSpecifier;
244
269
  if (imp.star) {
@@ -253,35 +278,28 @@ class CodeGenerator {
253
278
  moduleSpecifier = moduleParts[0] + "/" + moduleParts.slice(1).join("/");
254
279
  }
255
280
  else {
256
- // Non-scoped import: prefer local-file convention unless this looks like
257
- // an installed npm package path (e.g. cubing/twisty.TwistyPlayer).
281
+ // Non-scoped import: prefer local-file convention
258
282
  const precedingParts = imp.path.slice(0, -1);
259
283
  const precedingRelPath = precedingParts.join("/");
260
284
  const fileExts = [".ts", ".tsx", ".jalvin"];
261
285
  const precedingIsFile = precedingParts.length > 0 && fileExts.some((ext) => fs.existsSync(nodePath.join(sourceRoot, precedingRelPath + ext)));
262
286
  const looksLikeNpmPackage = this.isLikelyNodeModuleImport(imp.path, sourceRoot);
263
287
  moduleSpecifier = precedingIsFile
264
- ? precedingRelPath // src.models.css.Css → "src/models/css"
288
+ ? precedingRelPath
265
289
  : looksLikeNpmPackage
266
- ? precedingRelPath // cubing.twisty.TwistyPlayer → "cubing/twisty"
267
- : imp.path.join("/"); // src.models.Rotation → "src/models/Rotation"
290
+ ? precedingRelPath
291
+ : imp.path.join("/");
268
292
  }
269
293
  if (imp.star && isScoped && this.externalStarCandidates.size > 0) {
270
- // Named-import expansion of a scoped star import.
271
- // Emit explicit named imports for each external symbol used in the file so
272
- // that symbols like `ViewModel`, `mutableStateOf` are directly in scope.
273
294
  const symbols = [...this.externalStarCandidates].sort();
274
295
  this.w.writeIndentedLine(`import { ${symbols.join(", ")} } from "${moduleSpecifier}";`);
275
296
  this.handledStarImportModules.add(moduleSpecifier);
276
297
  }
277
298
  else if (imp.star && !isScoped && this.localStarExports.has(moduleSpecifier)) {
278
- // Local wildcard import: import src.module.* — expand to named imports
279
- // so all exported symbols are directly in scope (no namespace qualifier needed).
280
299
  const symbols = this.localStarExports.get(moduleSpecifier).slice().sort();
281
300
  this.w.writeIndentedLine(`import { ${symbols.join(", ")} } from "${moduleSpecifier}";`);
282
301
  }
283
302
  else if (imp.star) {
284
- // Fallback: namespace import (multiple scoped star imports or no candidates).
285
303
  this.w.writeIndentedLine(`import * as ${imp.path[imp.path.length - 1]} from "${moduleSpecifier}";`);
286
304
  }
287
305
  else if (imp.alias) {
@@ -295,11 +313,6 @@ class CodeGenerator {
295
313
  if (program.imports.length > 0)
296
314
  this.w.writeLine();
297
315
  }
298
- /**
299
- * Heuristic for non-scoped imports: if the first path segment resolves to an
300
- * installed package in node_modules, treat the import as npm/ESM and strip
301
- * the trailing symbol name from the module specifier.
302
- */
303
316
  isLikelyNodeModuleImport(pathParts, sourceRoot) {
304
317
  if (pathParts.length < 2)
305
318
  return false;
@@ -317,14 +330,12 @@ class CodeGenerator {
317
330
  return false;
318
331
  }
319
332
  // ── Top-level declarations ─────────────────────────────────────────────────
320
- /** Emit a @deprecated JSDoc block if the declaration has @Nuked. */
321
333
  emitAnnotations(mods) {
322
334
  for (const ann of mods.annotations) {
323
335
  if (ann.name === "Nuked") {
324
336
  const reason = ann.args ? ` ${ann.args.replace(/^["']|["']$/g, "")}` : "";
325
337
  this.w.writeIndentedLine(`/** @deprecated${reason} */`);
326
338
  }
327
- // Other annotations are silently ignored for now (pass-through model)
328
339
  }
329
340
  }
330
341
  emitTopLevelDecl(decl) {
@@ -379,7 +390,6 @@ class CodeGenerator {
379
390
  ? `: ${this.emitTypeRef(decl.returnType)}`
380
391
  : "";
381
392
  if (!decl.body) {
382
- // Abstract / interface method
383
393
  this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType};`);
384
394
  return;
385
395
  }
@@ -396,7 +406,6 @@ class CodeGenerator {
396
406
  this.w.writeIndentedLine(`}`);
397
407
  }
398
408
  else {
399
- // Expression body
400
409
  if (memberOf) {
401
410
  this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
402
411
  }
@@ -437,21 +446,19 @@ class CodeGenerator {
437
446
  this.w.writeIndentedLine(`}`);
438
447
  }
439
448
  }
440
- // ── component declarations → React function components ────────────────────
449
+ // ── component declarations → Functional components ────────────────────
441
450
  emitComponentDecl(decl) {
442
451
  this.emitAnnotations(decl.modifiers);
443
452
  this.hasComponents = true;
444
- const vis = this.exportPrefix(decl.modifiers);
445
- // "children" is passed as the second positional argument by emitComponentCall,
446
- // so it must NOT be destructured from the props object.
447
- const propsParams = decl.params.filter((p) => p.name !== "children");
453
+ const vis = this.isInClass ? this.visibilityPrefix(decl.modifiers) : this.exportPrefix(decl.modifiers);
448
454
  const hasChildren = decl.params.some((p) => p.name === "children");
455
+ const propsParams = decl.params.filter((p) => p.name !== "children");
449
456
  // Props interface
450
- if (decl.params.length > 0) {
457
+ if (!this.isInClass && propsParams.length > 0) {
451
458
  this.w.writeIndentedLine(`interface ${decl.name}Props {`);
452
459
  this.w.pushIndent();
453
- for (const p of decl.params) {
454
- const optional = p.defaultValue || p.name === "children" ? "?" : "";
460
+ for (const p of propsParams) {
461
+ const optional = p.defaultValue ? "?" : "";
455
462
  const typeStr = this.opts.emitTypes ? this.emitTypeRef(p.type) : "any";
456
463
  this.w.writeIndentedLine(`readonly ${p.name}${optional}: ${typeStr};`);
457
464
  }
@@ -459,21 +466,32 @@ class CodeGenerator {
459
466
  this.w.writeIndentedLine(`}`);
460
467
  this.w.writeLine();
461
468
  }
462
- const propsDestructure = decl.params.length > 0
463
- ? `{ ${decl.params.map((p) => p.name + (p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "")).join(", ")} }: ${decl.name}Props`
464
- : "";
465
- this.w.writeIndentedLine(`${vis}function ${decl.name}(${propsDestructure}) {`);
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} {`);
466
490
  this.w.pushIndent();
467
491
  this.emitComponentBlock(decl.body);
468
492
  this.w.popIndent();
469
493
  this.w.writeIndentedLine(`}`);
470
494
  }
471
- /**
472
- * Emit the body of a `component fun` block.
473
- * The last statement is emitted in tail position — if it is an expression,
474
- * an if/else chain, or a when block, the innermost UI call is implicitly
475
- * returned (implicit return of the last expression).
476
- */
477
495
  emitComponentBlock(block) {
478
496
  const stmts = block.statements;
479
497
  for (let i = 0; i < stmts.length; i++) {
@@ -486,10 +504,8 @@ class CodeGenerator {
486
504
  }
487
505
  }
488
506
  }
489
- /** Emit a statement in tail position, adding implicit return where applicable. */
490
507
  emitTailStmt(stmt) {
491
508
  if (stmt.kind === "ExprStmt") {
492
- // Implicit return: last expression in a component block
493
509
  this.w.writeIndentedLine(`return ${this.emitExpr(stmt.expr)};`);
494
510
  }
495
511
  else if (stmt.kind === "IfStmt") {
@@ -502,7 +518,6 @@ class CodeGenerator {
502
518
  this.emitStmt(stmt);
503
519
  }
504
520
  }
505
- /** Emit a block in tail position, treating its last statement as a tail expression. */
506
521
  emitTailBlock(block) {
507
522
  const stmts = block.statements;
508
523
  for (let i = 0; i < stmts.length; i++) {
@@ -515,7 +530,6 @@ class CodeGenerator {
515
530
  }
516
531
  }
517
532
  }
518
- /** Like emitIfStmt but with tail-return applied recursively to each branch body. */
519
533
  emitIfStmtWithTailReturn(stmt) {
520
534
  this.w.writeIndentedLine(`if (${this.emitExpr(stmt.condition)}) {`);
521
535
  this.w.pushIndent();
@@ -538,7 +552,6 @@ class CodeGenerator {
538
552
  this.w.writeIndentedLine(`}`);
539
553
  }
540
554
  }
541
- /** Like emitWhenStmt but with tail-return applied recursively to each branch body. */
542
555
  emitWhenStmtWithTailReturn(stmt) {
543
556
  const subjectVar = stmt.subject ? "__when_subject__" : null;
544
557
  if (stmt.subject) {
@@ -575,15 +588,12 @@ class CodeGenerator {
575
588
  const superTypes = this.emitSuperTypesStr(decl.superTypes);
576
589
  this.w.writeIndentedLine(`${vis}${abstract}class ${decl.name}${typeParams}${superTypes} {`);
577
590
  this.w.pushIndent();
578
- // The first supertype's delegateArgs become the `super(args)` call inside the constructor.
579
591
  const superDelegateArgs = decl.superTypes[0]?.delegateArgs ?? null;
580
592
  if (decl.primaryConstructor) {
581
593
  const initBlocks = decl.body?.members.filter((m) => m.kind === "InitBlock") ?? [];
582
594
  this.emitPrimaryConstructorProps(decl.primaryConstructor.params, initBlocks, superDelegateArgs);
583
595
  }
584
596
  else if (superDelegateArgs !== null) {
585
- // No primary constructor but the supertype has explicit delegation args —
586
- // emit a minimal constructor that forwards them to super().
587
597
  const superArgsStr = superDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
588
598
  this.w.writeIndentedLine(`constructor() {`);
589
599
  this.w.pushIndent();
@@ -606,24 +616,20 @@ class CodeGenerator {
606
616
  const props = decl.primaryConstructor.params;
607
617
  this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams}${superTypes} {`);
608
618
  this.w.pushIndent();
609
- // Discriminant for sealed class sub-types
610
619
  if (kindName) {
611
620
  this.w.writeIndentedLine(`readonly __kind = "${kindName}" as const;`);
612
621
  }
613
- // Constructor params → readonly properties
614
622
  for (const p of props) {
615
623
  const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
616
624
  this.w.writeIndentedLine(`readonly ${p.name}${t};`);
617
625
  }
618
626
  this.w.writeLine();
619
- // Constructor
620
627
  const ctorParams = props.map((p) => {
621
628
  const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
622
629
  return `${p.name}${t}`;
623
630
  }).join(", ");
624
631
  this.w.writeIndentedLine(`constructor(${ctorParams}) {`);
625
632
  this.w.pushIndent();
626
- // Emit super() with actual delegation args (or empty call if delegateArgs is []).
627
633
  const dataSuperDelegateArgs = decl.superTypes[0]?.delegateArgs ?? null;
628
634
  if (dataSuperDelegateArgs !== null) {
629
635
  const superArgsStr = dataSuperDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
@@ -635,7 +641,6 @@ class CodeGenerator {
635
641
  this.w.popIndent();
636
642
  this.w.writeIndentedLine(`}`);
637
643
  this.w.writeLine();
638
- // copy()
639
644
  const copyParams = props.map((p) => {
640
645
  const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
641
646
  return `${p.name}${t} = this.${p.name}`;
@@ -647,7 +652,6 @@ class CodeGenerator {
647
652
  this.w.popIndent();
648
653
  this.w.writeIndentedLine(`}`);
649
654
  this.w.writeLine();
650
- // equals()
651
655
  this.runtimeSymbolsNeeded.add("jalvinEquals");
652
656
  const eqChecks = props.map((p) => `jalvinEquals(this.${p.name}, other.${p.name})`).join(" && ") || "true";
653
657
  this.w.writeIndentedLine(`equals(other: unknown): boolean {`);
@@ -657,7 +661,6 @@ class CodeGenerator {
657
661
  this.w.popIndent();
658
662
  this.w.writeIndentedLine(`}`);
659
663
  this.w.writeLine();
660
- // toString()
661
664
  const toStringParts = props.map((p) => `${p.name}=\${this.${p.name}}`).join(", ");
662
665
  this.w.writeIndentedLine(`toString(): string {`);
663
666
  this.w.pushIndent();
@@ -665,7 +668,6 @@ class CodeGenerator {
665
668
  this.w.popIndent();
666
669
  this.w.writeIndentedLine(`}`);
667
670
  this.w.writeLine();
668
- // hashCode() — djb2
669
671
  this.w.writeIndentedLine(`hashCode(): number {`);
670
672
  this.w.pushIndent();
671
673
  this.w.writeIndentedLine(`let h = 17;`);
@@ -701,7 +703,6 @@ class CodeGenerator {
701
703
  }
702
704
  }
703
705
  }
704
- // Emit the abstract base class (only non-subtype members in the body)
705
706
  this.w.writeIndentedLine(`${vis}abstract class ${decl.name}${typeParams} {`);
706
707
  this.w.pushIndent();
707
708
  this.w.writeIndentedLine(`abstract readonly __kind: string;`);
@@ -714,7 +715,6 @@ class CodeGenerator {
714
715
  this.w.writeLine();
715
716
  if (subDecls.length === 0)
716
717
  return;
717
- // Emit non-object sub-declarations at module level (before namespace)
718
718
  for (const sub of subDecls) {
719
719
  if (sub.kind === "DataClassDecl") {
720
720
  this.emitDataClassDecl(sub, sub.name);
@@ -725,12 +725,10 @@ class CodeGenerator {
725
725
  this.w.writeLine();
726
726
  }
727
727
  }
728
- // Emit namespace merging so SealedClass.SubType is accessible
729
728
  this.w.writeIndentedLine(`${vis}namespace ${decl.name} {`);
730
729
  this.w.pushIndent();
731
730
  for (const sub of subDecls) {
732
731
  if (sub.kind === "ObjectDecl") {
733
- // Singleton object — emit inline in namespace with __kind discriminant
734
732
  const superStr = sub.superTypes.length > 0
735
733
  ? this.emitSuperTypesStr(sub.superTypes)
736
734
  : ` extends ${decl.name}`;
@@ -746,7 +744,6 @@ class CodeGenerator {
746
744
  this.w.writeIndentedLine(`export const ${sub.name} = new (class${superStr} { ${membersStr} })();`);
747
745
  }
748
746
  else {
749
- // DataClassDecl / ClassDecl — already emitted at module level, re-export
750
747
  this.w.writeIndentedLine(`export { ${sub.name} };`);
751
748
  }
752
749
  }
@@ -757,12 +754,8 @@ class CodeGenerator {
757
754
  emitEnumClassDecl(decl) {
758
755
  const vis = this.exportPrefix(decl.modifiers);
759
756
  const typeParams = this.emitTypeParamsStr(decl.typeParams);
760
- // Emit the enum as a TypeScript class with static singleton instances.
761
- // This preserves the `EnumClass.ENTRY` access pattern and allows
762
- // methods/properties to be attached to entries.
763
757
  this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams} {`);
764
758
  this.w.pushIndent();
765
- // Private constructor so entries are the only instances
766
759
  if (decl.primaryConstructor && decl.primaryConstructor.params.length > 0) {
767
760
  const ctorParams = decl.primaryConstructor.params
768
761
  .map((p) => {
@@ -777,7 +770,6 @@ class CodeGenerator {
777
770
  this.w.writeIndentedLine(`private constructor(readonly name: string, readonly ordinal: number) {}`);
778
771
  }
779
772
  this.w.writeLine();
780
- // Static entry instances
781
773
  for (let i = 0; i < decl.entries.length; i++) {
782
774
  const entry = decl.entries[i];
783
775
  const args = entry.args.length > 0
@@ -787,7 +779,6 @@ class CodeGenerator {
787
779
  }
788
780
  if (decl.entries.length > 0) {
789
781
  this.w.writeLine();
790
- // values() helper
791
782
  const entryList = decl.entries.map((e) => `${decl.name}.${e.name}`).join(", ");
792
783
  this.w.writeIndentedLine(`static values(): ${decl.name}[] { return [${entryList}]; }`);
793
784
  this.w.writeIndentedLine(`static valueOf(name: string): ${decl.name} {`);
@@ -805,14 +796,12 @@ class CodeGenerator {
805
796
  this.w.popIndent();
806
797
  this.w.writeIndentedLine(`}`);
807
798
  }
808
- // ── destructuring declaration — val (a, b) = expr ─────────────────────────
809
799
  emitDestructuringDecl(decl, _memberOf) {
810
800
  const kw = decl.mutable ? "let" : "const";
811
801
  const names = decl.names.map((n) => n ?? "_").join(", ");
812
802
  const init = this.emitExpr(decl.initializer);
813
803
  this.w.writeIndentedLine(`${kw} [${names}] = ${init};`);
814
804
  }
815
- // ── interface ──────────────────────────────────────────────────────────────
816
805
  emitInterfaceDecl(decl) {
817
806
  this.emitAnnotations(decl.modifiers);
818
807
  const vis = this.exportPrefix(decl.modifiers);
@@ -830,12 +819,9 @@ class CodeGenerator {
830
819
  this.w.popIndent();
831
820
  this.w.writeIndentedLine(`}`);
832
821
  }
833
- // ── object declaration → singleton ────────────────────────────────────────
834
822
  emitObjectDecl(decl) {
835
- if (!decl.name) {
836
- // Anonymous object — emitted inline
823
+ if (!decl.name)
837
824
  return;
838
- }
839
825
  this.emitAnnotations(decl.modifiers);
840
826
  const vis = this.exportPrefix(decl.modifiers);
841
827
  const superTypes = this.emitSuperTypesStr(decl.superTypes);
@@ -845,16 +831,11 @@ class CodeGenerator {
845
831
  this.w.popIndent();
846
832
  this.w.writeIndentedLine(`})();`);
847
833
  }
848
- // ── type alias ─────────────────────────────────────────────────────────────
849
834
  emitTypeAliasDecl(decl) {
850
835
  const vis = this.exportPrefix(decl.modifiers);
851
836
  const typeParams = this.emitTypeParamsStr(decl.typeParams);
852
837
  this.w.writeIndentedLine(`${vis}type ${decl.name}${typeParams} = ${this.emitTypeRef(decl.type)};`);
853
838
  }
854
- // ── extension functions ────────────────────────────────────────────────────
855
- // Emitted as standalone free functions with receiver as explicit first param.
856
- // For class types, also patched onto the prototype for dot-call syntax.
857
- // For primitive types, call sites are rewritten via `primitiveExtensions`.
858
839
  emitExtensionFunDecl(decl) {
859
840
  const isSuspend = AST.isSuspend(decl.modifiers);
860
841
  const asyncKw = isSuspend ? "async " : "";
@@ -865,9 +846,6 @@ class CodeGenerator {
865
846
  ? `: ${this.emitTypeRef(decl.returnType)}`
866
847
  : "";
867
848
  const fnName = `__ext_${receiverType.replace(/[^a-zA-Z0-9_]/g, "_")}_${decl.name}`;
868
- // Emit as a free function: receiver is the first explicit parameter `$receiver`.
869
- // Inside the function body, `this` is rebound to `$receiver` so Jalvin's
870
- // `this.prop` references work correctly.
871
849
  this.w.writeIndentedLine(`${asyncKw}function ${fnName}${typeParams}($receiver: ${receiverType}${params ? ", " + params : ""})${retType} {`);
872
850
  this.w.pushIndent();
873
851
  if (decl.body.kind === "Block") {
@@ -886,14 +864,12 @@ class CodeGenerator {
886
864
  const simpleReceiverName = this.receiverClassName(decl.receiver);
887
865
  if (simpleReceiverName) {
888
866
  if (PRIMITIVE_TYPES.has(simpleReceiverName)) {
889
- // Register for call-site rewriting — cannot patch primitive prototypes
890
867
  if (!this.primitiveExtensions.has(simpleReceiverName)) {
891
868
  this.primitiveExtensions.set(simpleReceiverName, new Map());
892
869
  }
893
870
  this.primitiveExtensions.get(simpleReceiverName).set(decl.name, fnName);
894
871
  }
895
872
  else {
896
- // Class types — prototype monkey-patch for dot-call syntax
897
873
  this.w.writeIndentedLine(`// Extension: ${receiverType}.${decl.name}`);
898
874
  this.w.writeIndentedLine(`(${simpleReceiverName}.prototype as any).${decl.name} = function(this: ${receiverType}, ...args: unknown[]) { return (${fnName} as Function)(this, ...args); };`);
899
875
  }
@@ -906,11 +882,6 @@ class CodeGenerator {
906
882
  return ref.base.name[ref.base.name.length - 1] ?? null;
907
883
  return null;
908
884
  }
909
- /**
910
- * Maps a JType to the primitive receiver type name used as a key
911
- * in `primitiveExtensions` (e.g. JType `{ tag: "string" }` → `"String"`).
912
- * Returns null for non-primitive / unknown types.
913
- */
914
885
  jTypeToReceiverName(type) {
915
886
  const tagToReceiverName = {
916
887
  string: "String",
@@ -930,19 +901,16 @@ class CodeGenerator {
930
901
  m.name === "invoke" &&
931
902
  m.modifiers.modifiers.includes("operator"));
932
903
  }
933
- // ── property declaration ───────────────────────────────────────────────────
934
904
  emitPropertyDecl(decl, member, isLocal = false) {
935
905
  this.emitAnnotations(decl.modifiers);
936
906
  const vis = member ? this.visibilityPrefix(decl.modifiers) : isLocal ? "" : this.exportPrefix(decl.modifiers);
937
907
  const isConst = decl.modifiers.modifiers.includes("const");
938
908
  const isLateinit = decl.modifiers.modifiers.includes("lateinit");
939
- // `const val` → `const` at module level; inside classes treated as `static readonly`
940
909
  const kw = member
941
910
  ? (decl.mutable ? "" : "readonly ")
942
911
  : (isConst ? "const " : decl.mutable ? "let " : "const ");
943
912
  const type = decl.type && this.opts.emitTypes ? `: ${this.emitTypeRef(decl.type)}` : "";
944
913
  if (decl.delegate) {
945
- // Delegated property — wrap in a getter/setter pair using the delegate
946
914
  this.runtimeSymbolsNeeded.add("delegate");
947
915
  const delegateExpr = this.emitExpr(decl.delegate);
948
916
  if (member) {
@@ -952,28 +920,22 @@ class CodeGenerator {
952
920
  }
953
921
  }
954
922
  else {
955
- // Non-member delegate: resolve via delegate().getValue() so the identifier
956
- // holds the delegated value, not the raw delegate wrapper object.
957
923
  this.w.writeIndentedLine(`${kw}${decl.name}${type} = delegate(${delegateExpr}, "${decl.name}", null).getValue();`);
958
924
  }
959
925
  return;
960
926
  }
961
- const init = decl.initializer ? ` = ${this.emitExpr(decl.initializer)}` : (isLateinit ? "" : "");
927
+ const init = decl.initializer ? ` = ${this.emitExpr(decl.initializer)}` : "";
962
928
  if (member && !decl.getter) {
963
- // Skip backing field when a getter is defined — TypeScript forbids both
964
- // `readonly name: T` and `get name()` with the same name in a class.
965
929
  const modStr = isConst ? "static readonly " : decl.mutable ? "" : "readonly ";
966
930
  this.w.writeIndentedLine(`${vis}${modStr}${decl.name}${type}${init};`);
967
931
  }
968
932
  else if (!member) {
969
933
  this.w.writeIndentedLine(`${vis}${kw}${decl.name}${type}${init};`);
970
934
  }
971
- if (decl.getter) {
935
+ if (decl.getter)
972
936
  this.emitPropertyAccessor("get", decl.name, decl.getter, member, type);
973
- }
974
- if (decl.setter) {
937
+ if (decl.setter)
975
938
  this.emitPropertyAccessor("set", decl.name, decl.setter, member, type);
976
- }
977
939
  }
978
940
  emitPropertyAccessor(kind, name, acc, member, type) {
979
941
  const vis = this.visibilityPrefix(acc.modifiers);
@@ -993,14 +955,19 @@ class CodeGenerator {
993
955
  this.w.popIndent();
994
956
  this.w.writeIndentedLine(`}`);
995
957
  }
996
- // ── class body ─────────────────────────────────────────────────────────────
997
958
  emitClassBody(body) {
998
- for (const member of body.members) {
999
- // InitBlocks are emitted inside the constructor by emitPrimaryConstructorProps
1000
- if (member.kind === "InitBlock")
1001
- continue;
1002
- this.emitClassMember(member);
1003
- 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;
1004
971
  }
1005
972
  }
1006
973
  emitClassMember(member) {
@@ -1032,7 +999,6 @@ class CodeGenerator {
1032
999
  case "CompanionObject":
1033
1000
  this.emitCompanionObject(member);
1034
1001
  break;
1035
- case "InitBlock": /* handled in constructor */ break;
1036
1002
  case "SecondaryConstructor":
1037
1003
  this.emitSecondaryConstructor(member);
1038
1004
  break;
@@ -1049,16 +1015,13 @@ class CodeGenerator {
1049
1015
  this.w.writeIndentedLine(`${ro}${p.name}${type};`);
1050
1016
  }
1051
1017
  }
1052
- // Constructor
1053
1018
  const ctorParams = params.map((p) => {
1054
- const ro = p.propertyKind === "val" ? "readonly " : p.propertyKind === "var" ? "" : "";
1055
1019
  const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
1056
1020
  const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
1057
1021
  return `${p.name}${type}${def}`;
1058
1022
  });
1059
1023
  this.w.writeIndentedLine(`constructor(${ctorParams.join(", ")}) {`);
1060
1024
  this.w.pushIndent();
1061
- // super() call must be the first statement if there is a supertype.
1062
1025
  if (superDelegateArgs !== null && superDelegateArgs !== undefined) {
1063
1026
  const superArgsStr = superDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
1064
1027
  this.w.writeIndentedLine(`super(${superArgsStr});`);
@@ -1068,17 +1031,13 @@ class CodeGenerator {
1068
1031
  this.w.writeIndentedLine(`this.${p.name} = ${p.name};`);
1069
1032
  }
1070
1033
  }
1071
- // Emit init{} blocks inside the constructor, in declaration order
1072
- for (const ib of (initBlocks ?? [])) {
1034
+ for (const ib of (initBlocks ?? []))
1073
1035
  this.emitBlock(ib.body);
1074
- }
1075
1036
  this.w.popIndent();
1076
1037
  this.w.writeIndentedLine(`}`);
1077
1038
  this.w.writeLine();
1078
1039
  }
1079
1040
  emitCompanionObject(co) {
1080
- // Emit companion object members as `static` members of the outer class so that
1081
- // `MyClass.factoryMethod()` works directly (standard semantics).
1082
1041
  for (const member of co.body.members) {
1083
1042
  if (member.kind === "FunDecl") {
1084
1043
  const vis = this.visibilityPrefix(member.modifiers);
@@ -1123,11 +1082,9 @@ class CodeGenerator {
1123
1082
  this.w.popIndent();
1124
1083
  this.w.writeIndentedLine(`}`);
1125
1084
  }
1126
- // ── Statements ─────────────────────────────────────────────────────────────
1127
1085
  emitBlock(block) {
1128
- for (const stmt of block.statements) {
1086
+ for (const stmt of block.statements)
1129
1087
  this.emitStmt(stmt);
1130
- }
1131
1088
  }
1132
1089
  emitStmt(stmt) {
1133
1090
  switch (stmt.kind) {
@@ -1216,8 +1173,6 @@ class CodeGenerator {
1216
1173
  }
1217
1174
  }
1218
1175
  emitWhenStmt(stmt) {
1219
- // when(subject) { is Foo -> ... else -> ... }
1220
- // Compiles to a series of if/else-if chains
1221
1176
  const subjectVar = stmt.subject ? "__when_subject__" : null;
1222
1177
  if (stmt.subject) {
1223
1178
  const binding = stmt.subject.binding ?? subjectVar;
@@ -1234,12 +1189,10 @@ class CodeGenerator {
1234
1189
  }
1235
1190
  first = false;
1236
1191
  this.w.pushIndent();
1237
- if (branch.body.kind === "Block") {
1192
+ if (branch.body.kind === "Block")
1238
1193
  this.emitBlock(branch.body);
1239
- }
1240
- else {
1194
+ else
1241
1195
  this.w.writeIndentedLine(`${this.emitExpr(branch.body)};`);
1242
- }
1243
1196
  this.w.popIndent();
1244
1197
  }
1245
1198
  this.w.writeIndentedLine(`}`);
@@ -1247,8 +1200,6 @@ class CodeGenerator {
1247
1200
  emitWhenCondition(cond, subject) {
1248
1201
  switch (cond.kind) {
1249
1202
  case "WhenIsCondition": {
1250
- // Qualified name (e.g. UiState.Loading) → use __kind discriminant
1251
- // Single name (e.g. BibiError) → use instanceof
1252
1203
  const isQualified = cond.type.kind === "SimpleTypeRef" && cond.type.name.length > 1;
1253
1204
  const check = isQualified
1254
1205
  ? `(${subject} as any).__kind === "${cond.type.kind === "SimpleTypeRef" ? cond.type.name[cond.type.name.length - 1] : ""}"`
@@ -1278,7 +1229,6 @@ class CodeGenerator {
1278
1229
  }
1279
1230
  else {
1280
1231
  const { key, value } = stmt.binding;
1281
- // Use .entries() for JS Map (Map<K,V>), Object.entries() for plain objects
1282
1232
  const iterableType = this.typeMap.get(stmt.iterable);
1283
1233
  const isMap = iterableType?.tag === "class" && iterableType.name === "Map";
1284
1234
  const entries = isMap ? `${iter}.entries()` : `Object.entries(${iter})`;
@@ -1297,9 +1247,6 @@ class CodeGenerator {
1297
1247
  for (const c of stmt.catches) {
1298
1248
  this.w.writeIndentedLine(`} catch (${c.name}: unknown) {`);
1299
1249
  this.w.pushIndent();
1300
- // Narrow type — always emit the guard so catch clauses are type-safe
1301
- // regardless of the emitTypes option (which controls explicit type annotations,
1302
- // not runtime type checks).
1303
1250
  this.w.writeIndentedLine(`if (!(${c.name} instanceof ${this.emitTypeRef(c.type)})) throw ${c.name};`);
1304
1251
  this.emitBlock(c.body);
1305
1252
  this.w.popIndent();
@@ -1312,7 +1259,6 @@ class CodeGenerator {
1312
1259
  }
1313
1260
  this.w.writeIndentedLine(`}`);
1314
1261
  }
1315
- // ── Expressions ────────────────────────────────────────────────────────────
1316
1262
  emitExpr(expr) {
1317
1263
  switch (expr.kind) {
1318
1264
  case "IntLiteralExpr": return String(expr.value);
@@ -1322,17 +1268,12 @@ class CodeGenerator {
1322
1268
  case "BooleanLiteralExpr": return String(expr.value);
1323
1269
  case "NullLiteralExpr": return "null";
1324
1270
  case "StringLiteralExpr":
1325
- return expr.raw
1326
- ? `\`${expr.value}\``
1327
- : JSON.stringify(expr.value);
1271
+ return expr.raw ? `\`${expr.value}\`` : JSON.stringify(expr.value);
1328
1272
  case "StringTemplateExpr":
1329
1273
  return this.emitStringTemplate(expr);
1330
1274
  case "NameExpr":
1331
- // Ensure Int/Long companion objects are imported from the runtime
1332
- if (expr.name === "Int" || expr.name === "Long") {
1275
+ if (expr.name === "Int" || expr.name === "Long")
1333
1276
  this.runtimeSymbolsNeeded.add(expr.name);
1334
- }
1335
- // Unit singleton emits as undefined (void semantics)
1336
1277
  if (expr.name === "Unit")
1337
1278
  return "undefined";
1338
1279
  return expr.name;
@@ -1348,34 +1289,23 @@ class CodeGenerator {
1348
1289
  if (opMethod) {
1349
1290
  const l = this.emitExpr(expr.left);
1350
1291
  const r = this.emitExpr(expr.right);
1351
- // `compareTo` overloads: wrap result in a comparison against 0
1352
- if (opMethod === "compareTo") {
1353
- const cmpOp = expr.op;
1354
- return `(${l}.compareTo(${r}) ${cmpOp} 0)`;
1355
- }
1356
- // `contains` overloads: `element in collection` → `collection.contains(element)`
1357
- if (opMethod === "contains") {
1292
+ if (opMethod === "compareTo")
1293
+ return `(${l}.compareTo(${r}) ${expr.op} 0)`;
1294
+ if (opMethod === "contains")
1358
1295
  return `${r}.contains(${l})`;
1359
- }
1360
- if (opMethod === "!contains") {
1296
+ if (opMethod === "!contains")
1361
1297
  return `!${r}.contains(${l})`;
1362
- }
1363
- // Generic operator overload: emit as method call `left.method(right)`
1364
1298
  return `${l}.${opMethod}(${r})`;
1365
1299
  }
1366
- // `==` → structural equality, `!=` → negation
1367
1300
  if (expr.op === "==" || expr.op === "!=") {
1368
1301
  this.runtimeSymbolsNeeded.add("jalvinEquals");
1369
1302
  const eq = `jalvinEquals(${this.emitExpr(expr.left)}, ${this.emitExpr(expr.right)})`;
1370
1303
  return expr.op === "==" ? eq : `!${eq}`;
1371
1304
  }
1372
- // `in` / `!in` without a user-defined operator: use JS `in` / array includes
1373
- if (expr.op === "in") {
1305
+ if (expr.op === "in")
1374
1306
  return `${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)})`;
1375
- }
1376
- if (expr.op === "!in") {
1307
+ if (expr.op === "!in")
1377
1308
  return `!(${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)}))`;
1378
- }
1379
1309
  return `(${this.emitExpr(expr.left)} ${this.binaryOpStr(expr.op)} ${this.emitExpr(expr.right)})`;
1380
1310
  }
1381
1311
  case "AssignExpr":
@@ -1386,124 +1316,81 @@ class CodeGenerator {
1386
1316
  return expr.prefix
1387
1317
  ? `${expr.op}${this.emitExpr(expr.target)}`
1388
1318
  : `${this.emitExpr(expr.target)}${expr.op}`;
1389
- case "MemberExpr":
1390
- return `${this.emitExpr(expr.target)}.${expr.member}`;
1391
- case "SafeMemberExpr":
1392
- return `${this.emitExpr(expr.target)}?.${expr.member}`;
1393
- case "IndexExpr":
1394
- 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)}]`;
1395
1322
  case "NotNullExpr":
1396
- // Runtime check
1397
1323
  this.runtimeSymbolsNeeded.add("notNull");
1398
1324
  return `notNull(${this.emitExpr(expr.expr)})`;
1399
1325
  case "SafeCallExpr": {
1400
- // x?.() — safe invocation of a nullable function value
1401
1326
  const callee = this.emitExpr(expr.callee);
1402
- const restArgs = [];
1403
- for (const a of expr.args) {
1404
- restArgs.push(a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1405
- }
1327
+ const restArgs = expr.args.map((a) => a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1406
1328
  if (expr.trailingLambda)
1407
1329
  restArgs.push(this.emitLambdaExpr(expr.trailingLambda));
1408
1330
  return `${callee}?.(${restArgs.join(", ")})`;
1409
1331
  }
1410
- case "ElvisExpr": {
1411
- // left ?? right (null coalescing)
1412
- return `(${this.emitExpr(expr.left)} ?? ${this.emitExpr(expr.right)})`;
1413
- }
1414
- case "CallExpr":
1415
- return this.emitCallExpr(expr);
1416
- case "LambdaExpr":
1417
- return this.emitLambdaExpr(expr);
1418
- case "IfExpr":
1419
- return this.emitIfExpr(expr);
1420
- case "WhenExpr":
1421
- return this.emitWhenExpr(expr);
1422
- case "TryCatchExpr":
1423
- return this.emitTryCatchExpr(expr);
1424
- case "TypeCheckExpr":
1425
- return `${expr.negated ? "!(" : ""}${this.emitExpr(expr.expr)} instanceof ${this.emitTypeRef(expr.type)}${expr.negated ? ")" : ""}`;
1426
- case "TypeCastExpr":
1427
- // Unsafe cast — emit as `(expr as Type)` which is stripped at runtime
1428
- return `(${this.emitExpr(expr.expr)} as unknown as ${this.emitTypeRef(expr.type)})`;
1429
- 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":
1430
1341
  this.runtimeSymbolsNeeded.add("safeCast");
1431
1342
  return `safeCast(${this.emitExpr(expr.expr)}, ${this.emitTypeRef(expr.type)})`;
1432
- }
1433
- case "RangeExpr": {
1343
+ case "RangeExpr":
1434
1344
  this.runtimeSymbolsNeeded.add("range");
1435
1345
  return `range(${this.emitExpr(expr.from)}, ${this.emitExpr(expr.to)}, ${expr.inclusive})`;
1436
- }
1437
1346
  case "LaunchExpr": {
1438
- // fire-and-forget → void IIFE
1439
1347
  const stmts = this.captureBlock(expr.body);
1440
1348
  return `(async () => { ${stmts} })()`;
1441
1349
  }
1442
1350
  case "AsyncExpr": {
1443
- // returns Promise<T>
1444
1351
  const stmts = this.captureBlock(expr.body);
1445
1352
  return `(async () => { ${stmts} })()`;
1446
1353
  }
1447
- case "CollectionLiteralExpr":
1448
- return this.emitCollectionLiteral(expr);
1449
- case "ObjectExpr":
1450
- return this.emitObjectExpr(expr);
1354
+ case "CollectionLiteralExpr": return this.emitCollectionLiteral(expr);
1355
+ case "ObjectExpr": return this.emitObjectExpr(expr);
1451
1356
  case "ReturnExpr": {
1452
1357
  const val = expr.value ? ` ${this.emitExpr(expr.value)}` : "";
1453
1358
  return `((() => { return${val}; })())`;
1454
1359
  }
1455
1360
  case "BreakExpr": return "undefined /* break */";
1456
1361
  case "ContinueExpr": return "undefined /* continue */";
1457
- case "JsxElement":
1458
- return this.emitJsxElement(expr);
1459
- default:
1460
- return "undefined";
1362
+ case "JsxElement": return this.emitJsxElement(expr);
1363
+ default: return "undefined";
1461
1364
  }
1462
1365
  }
1463
- // ── JSX ──────────────────────────────────────────────────────────────────
1464
1366
  emitJsxElement(expr) {
1367
+ this.runtimeSymbolsNeeded.add("jalvinCreateElement");
1465
1368
  const attrsStr = expr.attrs.length > 0
1466
- ? " " + expr.attrs.map((a) => this.emitJsxAttr(a)).join(" ")
1467
- : "";
1468
- if (expr.children.length === 0) {
1469
- return `<${expr.tag}${attrsStr} />`;
1470
- }
1471
- const children = expr.children.map((c) => this.emitJsxChild(c)).join("");
1472
- 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})`;
1473
1375
  }
1474
1376
  emitJsxAttr(attr) {
1475
- // Map HTML attribute names to React prop names
1476
- const name = attr.name === "class" ? "className"
1477
- : attr.name === "for" ? "htmlFor"
1478
- : attr.name;
1479
- if (attr.value === null)
1480
- return name;
1481
- if (typeof attr.value === "string")
1482
- return `${name}="${attr.value}"`;
1483
- 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}`;
1484
1380
  }
1485
1381
  emitJsxChild(child) {
1486
1382
  switch (child.kind) {
1487
1383
  case "JsxElement": return this.emitJsxElement(child);
1488
- case "JsxExprChild": return `{${this.emitExpr(child.expr)}}`;
1489
- case "JsxTextChild": return child.text;
1384
+ case "JsxExprChild": return this.emitExpr(child.expr);
1385
+ case "JsxTextChild": return `document.createTextNode(${JSON.stringify(child.text)})`;
1490
1386
  }
1491
1387
  }
1492
1388
  emitStringTemplate(expr) {
1493
- const inner = expr.parts.map((p) => {
1494
- if (p.kind === "LiteralPart") {
1495
- return p.value.replace(/`/g, "\\`").replace(/\\/g, "\\\\");
1496
- }
1497
- return `\${${this.emitExpr(p.expr)}}`;
1498
- }).join("");
1389
+ const inner = expr.parts.map((p) => p.kind === "LiteralPart" ? p.value.replace(/`/g, "\\`").replace(/\\/g, "\\\\") : `\${${this.emitExpr(p.expr)}}`).join("");
1499
1390
  return `\`${inner}\``;
1500
1391
  }
1501
1392
  emitCallExpr(expr) {
1502
- // Rewrite infix numeric extension calls that JS numbers don't natively have:
1503
- // `a.downTo(b)` → `downTo(a, b)` `a.until(b)` → `range(a, b, false)`
1504
- // `range.step(n)` → `step(range, n)`
1505
- if (expr.callee.kind === "MemberExpr" &&
1506
- (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")) {
1507
1394
  const obj = this.emitExpr(expr.callee.target);
1508
1395
  const arg = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "0";
1509
1396
  if (expr.callee.member === "downTo") {
@@ -1519,51 +1406,39 @@ class CodeGenerator {
1519
1406
  return `step(${obj}, ${arg})`;
1520
1407
  }
1521
1408
  }
1522
- // Rewrite calls on primitive-receiver extension functions.
1523
- // `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
1524
1409
  if (expr.callee.kind === "MemberExpr") {
1525
1410
  const scopeMember = expr.callee.member;
1526
- // Scope function call rewriting: `x.let { ... }` → `let_(x, ...)`
1527
- // `x.apply { ... }` `apply(x, function(this:any) { ... })`
1528
- if (scopeMember === "let" || scopeMember === "also" || scopeMember === "apply" || scopeMember === "run" || scopeMember === "takeIf" || scopeMember === "takeUnless") {
1529
- const receiver = this.emitExpr(expr.callee.target);
1530
- const lambda = expr.trailingLambda ??
1531
- (expr.args.length === 1 && expr.args[0].value.kind === "LambdaExpr"
1532
- ? expr.args[0].value
1533
- : null);
1534
- if (lambda) {
1535
- if (scopeMember === "let") {
1536
- this.runtimeSymbolsNeeded.add("let_");
1537
- return `let_(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1538
- }
1539
- if (scopeMember === "also") {
1540
- this.runtimeSymbolsNeeded.add("also");
1541
- return `also(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1542
- }
1543
- if (scopeMember === "apply") {
1544
- this.runtimeSymbolsNeeded.add("apply");
1545
- const body = this.captureBlockStatements(lambda.body);
1546
- return `apply(${receiver}, function(this: any) { ${body} })`;
1547
- }
1548
- if (scopeMember === "run") {
1549
- this.runtimeSymbolsNeeded.add("run_");
1550
- const body = this.captureBlockStatements(lambda.body);
1551
- return `run_(${receiver}, function(this: any) { ${body} })`;
1552
- }
1553
- if (scopeMember === "takeIf") {
1554
- this.runtimeSymbolsNeeded.add("takeIf");
1555
- return `takeIf(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1556
- }
1557
- if (scopeMember === "takeUnless") {
1558
- this.runtimeSymbolsNeeded.add("takeUnless");
1559
- return `takeUnless(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1560
- }
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)})`;
1561
1439
  }
1562
1440
  }
1563
- // `with(x) { ... }` is a free function handled via seedBuiltins; but rewrite if emitted as member
1564
1441
  }
1565
- // Rewrite calls on primitive-receiver extension functions.
1566
- // `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
1567
1442
  if (expr.callee.kind === "MemberExpr") {
1568
1443
  const receiverType = this.typeMap.get(expr.callee.target);
1569
1444
  const memberName = expr.callee.member;
@@ -1583,105 +1458,69 @@ class CodeGenerator {
1583
1458
  }
1584
1459
  }
1585
1460
  const callee = this.emitExpr(expr.callee);
1586
- const typeArgs = expr.typeArgs.length > 0
1587
- ? `<${expr.typeArgs.map((t) => t.star ? "*" : this.emitTypeRef(t.type)).join(", ")}>`
1588
- : "";
1589
- // Rewrite top-level `with(obj) { ... }` → `with_(obj, function(this: any) { ... })`
1590
- if (expr.callee.kind === "NameExpr" && expr.callee.name === "with" &&
1591
- (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"))) {
1592
1463
  const obj = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "undefined";
1593
- const lambda = expr.trailingLambda ??
1594
- expr.args[1].value;
1464
+ const lambda = expr.trailingLambda ?? expr.args[1].value;
1595
1465
  this.runtimeSymbolsNeeded.add("with_");
1596
1466
  const body = this.captureBlockStatements(lambda.body);
1597
1467
  return `with_(${obj}, function(this: any) { ${body} })`;
1598
1468
  }
1599
- // Rewrite top-level `run { ... }` (no receiver) just call the lambda
1600
- if (expr.callee.kind === "NameExpr" && expr.callee.name === "run" &&
1601
- expr.args.length === 0 && expr.trailingLambda) {
1469
+ if (expr.callee.kind === "NameExpr" && expr.callee.name === "run" && expr.args.length === 0 && expr.trailingLambda) {
1602
1470
  return `(${this.emitLambdaExpr(expr.trailingLambda)})()`;
1603
1471
  }
1604
- // Handle named arguments by reordering them to match positional parameters
1605
1472
  const calleeType = this.typeMap.get(expr.callee);
1606
- // Component call: Column(modifier = ...) { ... } → Column({ modifier: ... }, [children])
1607
- // Also catches star-imported @jalvin/ui primitives (Row, Button, etc.) whose type is T_UNKNOWN
1608
- //
1609
- // Skip the component path when the typechecker resolved a func type with explicit paramNames.
1610
- // That indicates a regular function (fun) or a built-in hook — both use positional calling.
1611
- // component fun declarations intentionally omit paramNames from their func type.
1612
1473
  const isKnownPositionalFunc = calleeType?.tag === "func" && calleeType.paramNames !== undefined;
1613
- if (!isKnownPositionalFunc && expr.callee.kind === "NameExpr" && (this.componentNames.has(expr.callee.name) ||
1614
- (this.hasUiStarImport && (!calleeType || calleeType.tag === "unknown") && /^[A-Z]/.test(expr.callee.name)))) {
1615
- 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);
1616
1480
  }
1481
+ if (!isKnownPositionalFunc && isComponentLike)
1482
+ return this.emitComponentCall(expr);
1617
1483
  let finalArgs = [];
1618
1484
  if (calleeType && calleeType.tag === "func" && calleeType.paramNames) {
1619
1485
  const paramNames = calleeType.paramNames;
1620
1486
  const argsByName = new Map();
1621
1487
  const positionalArgs = [];
1622
1488
  for (const arg of expr.args) {
1623
- if (arg.name) {
1489
+ if (arg.name)
1624
1490
  argsByName.set(arg.name, arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
1625
- }
1626
- else {
1491
+ else
1627
1492
  positionalArgs.push(arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
1628
- }
1629
1493
  }
1630
- // Reconstruct args based on paramNames
1631
1494
  for (let i = 0; i < paramNames.length; i++) {
1632
1495
  const name = paramNames[i];
1633
- if (argsByName.has(name)) {
1496
+ if (argsByName.has(name))
1634
1497
  finalArgs.push(argsByName.get(name));
1635
- }
1636
- else if (i < positionalArgs.length) {
1498
+ else if (i < positionalArgs.length)
1637
1499
  finalArgs.push(positionalArgs[i]);
1638
- }
1639
- else {
1640
- // Missing argument - JS default value logic will handle it if we pass undefined
1500
+ else
1641
1501
  finalArgs.push("undefined");
1642
- }
1643
1502
  }
1644
- // Handle trailing positional args if any
1645
- if (positionalArgs.length > paramNames.length) {
1503
+ if (positionalArgs.length > paramNames.length)
1646
1504
  finalArgs.push(...positionalArgs.slice(paramNames.length));
1647
- }
1648
1505
  }
1649
1506
  else {
1650
- // Fallback for types we don't know param names for (like constructors or any)
1651
- finalArgs = expr.args.map((a) => {
1652
- return a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value);
1653
- });
1507
+ finalArgs = expr.args.map((a) => a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1654
1508
  }
1655
- if (expr.trailingLambda) {
1509
+ if (expr.trailingLambda)
1656
1510
  finalArgs.push(this.emitLambdaExpr(expr.trailingLambda));
1657
- }
1658
- // If the callee is a class instance (not a constructor / function), emit `.invoke(args)`
1659
- if (calleeType &&
1660
- calleeType.tag === "class" &&
1661
- calleeType.decl &&
1662
- this.classHasInvokeOperator(calleeType.decl)) {
1511
+ if (calleeType && calleeType.tag === "class" && calleeType.decl && this.classHasInvokeOperator(calleeType.decl)) {
1663
1512
  return `${callee}.invoke(${finalArgs.join(", ")})`;
1664
1513
  }
1665
- // Constructor calls: class names start with uppercase by convention
1666
- if (this.isConstructorCall(expr.callee)) {
1514
+ if (this.isConstructorCall(expr.callee))
1667
1515
  return `new ${callee}${typeArgs}(${finalArgs.join(", ")})`;
1668
- }
1669
1516
  return `${callee}${typeArgs}(${finalArgs.join(", ")})`;
1670
1517
  }
1671
- /**
1672
- * For each non-scoped local import (e.g. `import src.views.MoveCounterView.MoveCounter`),
1673
- * attempt to load and parse the corresponding .jalvin source file. If the named symbol is
1674
- * a `component fun`, register it in `componentNames` and record its param names so that
1675
- * call sites can emit the correct props-object invocation.
1676
- */
1677
1518
  resolveLocalComponentImports(program, sourceRoot) {
1678
1519
  this.localStarExports.clear();
1679
1520
  for (const imp of program.imports) {
1680
- // Skip scoped packages (@org/pkg)
1681
1521
  if (imp.path[0]?.startsWith("@"))
1682
1522
  continue;
1683
1523
  if (imp.star) {
1684
- // Local wildcard import: import src.module.* — load all exports from src/module.jalvin
1685
1524
  if (imp.path.length < 1)
1686
1525
  continue;
1687
1526
  const candidatePath = nodePath.join(sourceRoot, imp.path.join("/") + ".jalvin");
@@ -1704,21 +1543,27 @@ class CodeGenerator {
1704
1543
  continue;
1705
1544
  exportedNames.push(name);
1706
1545
  if (d.kind === "ComponentDecl") {
1707
- this.componentNames.add(name);
1546
+ this.userComponentNames.add(name);
1547
+ this.allComponentLikeNames.add(name);
1708
1548
  this.componentParamNames.set(name, d.params.map((p) => p.name));
1709
1549
  this.hasComponents = true;
1710
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
+ }
1711
1560
  }
1712
- if (exportedNames.length > 0) {
1561
+ if (exportedNames.length > 0)
1713
1562
  this.localStarExports.set(moduleSpecifier, exportedNames);
1714
- }
1715
- }
1716
- catch {
1717
- // Silently skip if the file cannot be read or parsed
1718
1563
  }
1564
+ catch { }
1719
1565
  continue;
1720
1566
  }
1721
- // Named import: last path segment is the symbol, preceding segments form the file path
1722
1567
  if (imp.path.length < 2)
1723
1568
  continue;
1724
1569
  const rawSymbolName = imp.path[imp.path.length - 1];
@@ -1736,574 +1581,237 @@ class CodeGenerator {
1736
1581
  const ast = (0, parser_js_1.parse)(tokens, candidatePath, diag, source);
1737
1582
  if (diag.hasErrors)
1738
1583
  continue;
1739
- // Look for a top-level ComponentDecl matching the imported symbol name
1740
- const compDecl = ast.declarations.find((d) => d.kind === "ComponentDecl" && d.name === rawSymbolName);
1741
- if (compDecl) {
1742
- this.componentNames.add(symbolName);
1743
- this.componentParamNames.set(symbolName, compDecl.params.map((p) => p.name));
1584
+ const decl = ast.declarations.find((d) => ((d.kind === "ComponentDecl" || d.kind === "ClassDecl") && d.name === rawSymbolName));
1585
+ if (decl?.kind === "ComponentDecl") {
1586
+ this.userComponentNames.add(symbolName);
1587
+ this.allComponentLikeNames.add(symbolName);
1588
+ this.componentParamNames.set(symbolName, decl.params.map((p) => p.name));
1744
1589
  this.hasComponents = true;
1745
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
+ }
1746
1601
  }
1747
- catch {
1748
- // Silently skip if the file cannot be read or parsed
1749
- }
1602
+ catch { }
1750
1603
  }
1751
1604
  }
1752
- /** Returns true if a call expression's callee looks like a class constructor.
1753
- * In Jalvin, class names always start with an uppercase letter. */
1754
1605
  isConstructorCall(calleeExpr) {
1755
1606
  if (calleeExpr.kind === "NameExpr") {
1756
- // Names in componentNames are factory functions (UI primitives, components), not constructors
1757
- if (this.componentNames.has(calleeExpr.name))
1607
+ if (this.allComponentLikeNames.has(calleeExpr.name))
1758
1608
  return false;
1759
- // Class names start with uppercase; function names start with lowercase
1760
1609
  return /^[A-Z]/.test(calleeExpr.name);
1761
1610
  }
1762
1611
  if (calleeExpr.kind === "MemberExpr") {
1763
- // e.g. UiState.Success(data), Discount.Percentage(0.1)
1612
+ if (this.allComponentLikeNames.has(calleeExpr.member))
1613
+ return false;
1764
1614
  return /^[A-Z]/.test(calleeExpr.member);
1765
1615
  }
1766
1616
  return false;
1767
1617
  }
1768
- // ── Component call → props object ──────────────────────────────────────
1769
- /** Emit `Column(modifier = ...) { ... }` as `React.createElement(Column, { modifier: ... }, [children])` */
1770
1618
  emitComponentCall(expr) {
1771
- 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;
1772
1621
  const calleeType = this.typeMap.get(expr.callee);
1773
- // Param names from type checker (for same-file components) or resolved from imported files
1774
- const paramNames = (calleeType?.tag === "func" ? calleeType.paramNames : undefined) ??
1775
- this.componentParamNames.get(tag);
1776
- // Build props object from named/positional args
1622
+ const paramNames = (calleeType?.tag === "func" ? calleeType.paramNames : undefined) ?? (tagName ? this.componentParamNames.get(tagName) : undefined);
1777
1623
  const props = [];
1778
1624
  for (let i = 0; i < expr.args.length; i++) {
1779
1625
  const arg = expr.args[i];
1780
- // Prop name: explicit named arg > known param name > NameExpr variable name (shorthand)
1781
- const propName = arg.name ??
1782
- paramNames?.[i] ??
1783
- (arg.value.kind === "NameExpr" ? arg.value.name : undefined);
1626
+ const propName = arg.name ?? paramNames?.[i] ?? (arg.value.kind === "NameExpr" ? arg.value.name : undefined);
1784
1627
  if (!propName)
1785
1628
  continue;
1786
1629
  const val = this.emitExpr(arg.value);
1787
- // Use JS shorthand `{ key }` when key and value are the same identifier
1788
1630
  props.push(propName === val ? propName : `${propName}: ${val}`);
1789
1631
  }
1790
1632
  const propsStr = props.length > 0 ? `{ ${props.join(", ")} }` : "{}";
1791
1633
  if (expr.trailingLambda) {
1792
1634
  const children = this.emitLambdaBodyAsDomChildren(expr.trailingLambda);
1793
- return `React.createElement(${tag}, ${propsStr}, [${children}])`;
1635
+ return `${callee}(${propsStr}, [${children}])`;
1794
1636
  }
1795
- return `React.createElement(${tag}, ${propsStr})`;
1637
+ return `${callee}(${propsStr})`;
1796
1638
  }
1797
- /** Collect each expression statement in a trailing-lambda body as DOM children.
1798
- * For-loop statements are emitted as spread IIFEs so their dynamic output is preserved. */
1799
1639
  emitLambdaBodyAsDomChildren(lambda) {
1800
1640
  const parts = [];
1801
1641
  for (const stmt of lambda.body) {
1802
1642
  if (stmt.kind === "ExprStmt") {
1803
1643
  parts.push(this.emitExpr(stmt.expr));
1804
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
+ }
1805
1657
  else if (stmt.kind === "ForStmt") {
1806
1658
  const s = stmt;
1807
1659
  const iter = this.emitExpr(s.iterable);
1808
- let bindingStr;
1809
- if (typeof s.binding === "string") {
1810
- bindingStr = `const ${s.binding}`;
1811
- }
1812
- else if (s.binding.kind === "TupleDestructure") {
1813
- const names = s.binding.names.map((n) => n ?? "_").join(", ");
1814
- bindingStr = `const [${names}]`;
1815
- }
1816
- else {
1817
- bindingStr = `const [${s.binding.key}, ${s.binding.value}]`;
1818
- }
1819
- const innerExprs = s.body.statements
1820
- .filter((inner) => inner.kind === "ExprStmt")
1821
- .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));
1822
1662
  if (innerExprs.length > 0) {
1823
1663
  const pushes = innerExprs.map((e) => `__c.push(${e});`).join(" ");
1824
1664
  parts.push(`...(() => { const __c: any[] = []; for (${bindingStr} of ${iter}) { ${pushes} } return __c; })()`);
1825
1665
  }
1826
1666
  }
1827
- // Other statement kinds (if, while, etc.) are not renderable as UI children inline
1828
1667
  }
1829
1668
  return parts.join(", ");
1830
1669
  }
1831
1670
  emitLambdaExpr(expr) {
1832
- // If no explicit params, emit `it` as the single implicit parameter
1833
- // (Convention for single-argument lambdas)
1834
- const params = expr.params.length === 0
1835
- ? "it"
1836
- : expr.params.map((p) => p.name ?? "_").join(", ");
1837
- const body = expr.body;
1838
- if (body.length === 1 && body[0].kind === "ExprStmt") {
1839
- return `(${params}) => ${this.emitExpr(body[0].expr)}`;
1840
- }
1841
- 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(" ")} }`;
1842
1675
  }
1843
1676
  emitIfExpr(expr) {
1844
1677
  const cond = this.emitExpr(expr.condition);
1845
- const thenStr = expr.then.kind === "Block"
1846
- ? `(() => { ${this.captureBlockStatements(expr.then.statements)} })()`
1847
- : this.emitExpr(expr.then);
1848
- const elseExpr = expr.else;
1849
- const elseStr = elseExpr.kind === "Block"
1850
- ? `(() => { ${this.captureBlockStatements(elseExpr.statements)} })()`
1851
- : elseExpr.kind === "IfExpr"
1852
- ? this.emitIfExpr(elseExpr)
1853
- : 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);
1854
1680
  return `(${cond} ? ${thenStr} : ${elseStr})`;
1855
1681
  }
1856
1682
  emitWhenExpr(expr) {
1857
- // Compile as an IIFE with if/else chain
1858
1683
  const parts = [];
1859
1684
  const subject = expr.subject ? `const __s = ${this.emitExpr(expr.subject.expr)};` : "";
1860
1685
  const subjectRef = expr.subject ? (expr.subject.binding ?? "__s") : "";
1861
1686
  for (const branch of expr.branches) {
1862
- if (branch.isElse) {
1863
- const body = branch.body.kind === "Block"
1864
- ? this.captureBlock(branch.body)
1865
- : `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)
1866
1689
  parts.push(`{ ${body} }`);
1867
- }
1868
- else {
1869
- const cond = branch.conditions.map((c) => this.emitWhenCondition(c, subjectRef)).join(" || ");
1870
- const body = branch.body.kind === "Block"
1871
- ? this.captureBlock(branch.body)
1872
- : `return ${this.emitExpr(branch.body)};`;
1873
- parts.push(`if (${cond}) { ${body} }`);
1874
- }
1690
+ else
1691
+ parts.push(`if (${branch.conditions.map((c) => this.emitWhenCondition(c, subjectRef)).join(" || ")}) { ${body} }`);
1875
1692
  }
1876
1693
  return `(() => { ${subject} ${parts.join(" else ")} })()`;
1877
1694
  }
1878
1695
  emitTryCatchExpr(expr) {
1879
1696
  const body = this.captureBlock(expr.body);
1880
- const catches = expr.catches.map((c) => {
1881
- const cb = this.captureBlock(c.body);
1882
- return `catch (${c.name}) { ${cb} }`;
1883
- }).join(" ");
1697
+ const catches = expr.catches.map((c) => `catch (${c.name}) { ${this.captureBlock(c.body)} }`).join(" ");
1884
1698
  const fin = expr.finally ? `finally { ${this.captureBlock(expr.finally)} }` : "";
1885
1699
  return `(() => { try { ${body} } ${catches} ${fin} })()`;
1886
1700
  }
1887
1701
  emitCollectionLiteral(expr) {
1888
- if (expr.collectionKind === "map") {
1889
- const entries = expr.elements.map((e) => {
1890
- if ("kind" in e && e.kind === "MapEntry") {
1891
- return `[${this.emitExpr(e.key)}, ${this.emitExpr(e.value)}]`;
1892
- }
1893
- return "null";
1894
- });
1895
- return `new Map([${entries.join(", ")}])`;
1896
- }
1897
- if (expr.collectionKind === "set") {
1898
- const items = expr.elements.map((e) => this.emitExpr(e));
1899
- return `new Set([${items.join(", ")}])`;
1900
- }
1901
- const items = expr.elements.map((e) => this.emitExpr(e));
1902
- 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(", ")}]`;
1903
1707
  }
1904
1708
  emitObjectExpr(expr) {
1905
1709
  const superType = expr.superTypes[0];
1906
1710
  const ext = superType ? ` extends ${this.emitTypeRef(superType.type)}` : "";
1907
- const members = expr.body.members.map((m) => this.captureClassMember(m)).join(" ");
1908
- return `(new (class${ext} { ${members} })())`;
1909
- }
1910
- // ── capture helpers (emit to temp string) ─────────────────────────────────
1911
- captureBlock(block) {
1912
- return block.statements.map((s) => this.captureStmt(s)).join(" ");
1913
- }
1914
- captureBlockStatements(stmts) {
1915
- return stmts.map((s) => this.captureStmt(s)).join(" ");
1916
- }
1917
- captureStmt(stmt) {
1918
- const saved = this.w;
1919
- const tmp = new Writer();
1920
- this.w = tmp;
1921
- this.emitStmt(stmt);
1922
- this.w = saved;
1923
- return tmp.output.trim();
1924
- }
1925
- captureClassMember(member) {
1926
- const saved = this.w;
1927
- const tmp = new Writer();
1928
- this.w = tmp;
1929
- this.emitClassMember(member);
1930
- this.w = saved;
1931
- return tmp.output.trim();
1932
- }
1933
- // ── 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(); }
1934
1717
  emitTypeRef(ref) {
1935
1718
  switch (ref.kind) {
1936
- case "SimpleTypeRef": {
1937
- const name = ref.name.join(".");
1938
- return PRIMITIVE_TYPE_MAP[name] ?? name;
1939
- }
1940
- case "NullableTypeRef":
1941
- return `${this.emitTypeRef(ref.base)} | null | undefined`;
1942
- case "GenericTypeRef": {
1943
- const base = ref.base.name.join(".");
1944
- const mapped = GENERIC_TYPE_MAP[base] ?? base;
1945
- const args = ref.args.map((a) => a.star ? "any" : a.type ? this.emitTypeRef(a.type) : "unknown");
1946
- return `${mapped}<${args.join(", ")}>`;
1947
- }
1948
- case "FunctionTypeRef": {
1949
- const params = ref.params.map((p, i) => `p${i}: ${this.emitTypeRef(p)}`).join(", ");
1950
- const ret = this.emitTypeRef(ref.returnType);
1951
- return `(${params}) => ${ret}`;
1952
- }
1953
- case "StarProjection":
1954
- return "any";
1955
- }
1956
- }
1957
- // ── Utility helpers ────────────────────────────────────────────────────────
1958
- emitTypeParamsStr(params) {
1959
- if (params.length === 0)
1960
- return "";
1961
- const ps = params.map((p) => {
1962
- const bound = p.upperBound ? ` extends ${this.emitTypeRef(p.upperBound)}` : "";
1963
- return `${p.name}${bound}`;
1964
- });
1965
- return `<${ps.join(", ")}>`;
1966
- }
1967
- emitParamsStr(params) {
1968
- return params.map((p) => {
1969
- const spread = p.vararg ? "..." : "";
1970
- // vararg params are rest params in TS: `...name: T[]`
1971
- const type = this.opts.emitTypes
1972
- ? `: ${this.emitTypeRef(p.type)}${p.vararg ? "[]" : ""}`
1973
- : "";
1974
- const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
1975
- return `${spread}${p.name}${type}${def}`;
1976
- }).join(", ");
1977
- }
1978
- emitComponentPropsStr(params) {
1979
- if (params.length === 0)
1980
- return "";
1981
- return params.map((p) => {
1982
- const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
1983
- return `${p.name}${type}`;
1984
- }).join(", ");
1985
- }
1986
- emitSuperTypesStr(superTypes) {
1987
- if (superTypes.length === 0)
1988
- return "";
1989
- const parts = [];
1990
- let first = true;
1991
- for (const s of superTypes) {
1992
- // TypeScript does NOT allow constructor arguments in the `extends` clause.
1993
- // Delegation args are passed via `super(args)` inside the constructor.
1994
- if (first) {
1995
- parts.push(` extends ${this.emitTypeRef(s.type)}`);
1996
- first = false;
1997
- }
1998
- else {
1999
- parts.push(` implements ${this.emitTypeRef(s.type)}`);
2000
- }
2001
- }
2002
- return parts.join("");
2003
- }
2004
- exportPrefix(mods) {
2005
- if (mods.visibility === "private" || mods.visibility === "internal")
2006
- return "";
2007
- return "export ";
2008
- }
2009
- visibilityPrefix(mods) {
2010
- switch (mods.visibility) {
2011
- case "private": return "private ";
2012
- case "protected": return "protected ";
2013
- case "internal": return "/* internal */ ";
2014
- default: return "";
2015
- }
2016
- }
2017
- unaryOpStr(op) {
2018
- if (op === "not")
2019
- return "!";
2020
- return op;
2021
- }
2022
- binaryOpStr(op) {
2023
- switch (op) {
2024
- case "and": return "&&";
2025
- case "or": return "||";
2026
- case "xor": return "^";
2027
- case "shl": return "<<";
2028
- case "shr": return ">>";
2029
- case "ushr": return ">>>";
2030
- // === / !== are JS reference equality (Jalvin's triple-equals)
2031
- case "===": return "===";
2032
- case "!==": return "!==";
2033
- case "..": return "/* .. */ +"; // handled by range()
2034
- case "..<": return "/* ..< */ +";
2035
- default: return op;
2036
- }
2037
- }
2038
- // ── AST name walkers (for wildcard import resolution) ─────────────────────
2039
- /**
2040
- * Walk the entire program AST and collect all identifier names that are
2041
- * REFERENCED (i.e., used) in the code: NameExpr values and the first name
2042
- * component of SimpleTypeRef / GenericTypeRef nodes.
2043
- *
2044
- * Jalvin primitive type names (String, Boolean, …) that map to TS primitives
2045
- * are excluded from TypeRef collection since they never need an import.
2046
- */
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
+ } }
2047
1748
  gatherReferencedNames(program) {
2048
1749
  const names = new Set();
2049
1750
  const visited = new WeakSet();
2050
1751
  const walk = (val) => {
2051
- if (!val || typeof val !== "object")
1752
+ if (!val || typeof val !== "object" || visited.has(val))
2052
1753
  return;
1754
+ visited.add(val);
2053
1755
  if (Array.isArray(val)) {
2054
- for (const item of val)
2055
- walk(item);
1756
+ val.forEach(walk);
2056
1757
  return;
2057
1758
  }
2058
1759
  const obj = val;
2059
- if (visited.has(obj))
2060
- return;
2061
- visited.add(obj);
2062
- const kind = obj["kind"];
2063
- if (kind === "NameExpr") {
2064
- const name = obj["name"];
2065
- if (typeof name === "string")
2066
- names.add(name);
2067
- return; // leaf node
2068
- }
2069
- if (kind === "SimpleTypeRef") {
2070
- const nameArr = obj["name"];
2071
- if (Array.isArray(nameArr) && nameArr.length > 0) {
2072
- const first = nameArr[0];
2073
- // Skip Jalvin primitive type names — they're erased to TS primitives.
2074
- if (!(first in PRIMITIVE_TYPE_MAP))
2075
- names.add(first);
2076
- }
1760
+ if (obj.kind === "NameExpr") {
1761
+ if (typeof obj.name === "string")
1762
+ names.add(obj.name);
2077
1763
  return;
2078
1764
  }
2079
- if (kind === "GenericTypeRef") {
2080
- const base = obj["base"];
2081
- const nameArr = base?.["name"];
2082
- if (Array.isArray(nameArr) && nameArr.length > 0) {
2083
- const first = nameArr[0];
2084
- if (!(first in PRIMITIVE_TYPE_MAP))
2085
- names.add(first);
2086
- }
2087
- // 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;
2088
1769
  }
2089
- for (const propVal of Object.values(obj)) {
2090
- 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]);
2091
1773
  }
1774
+ Object.values(obj).forEach(walk);
2092
1775
  };
2093
- for (const decl of program.declarations)
2094
- walk(decl);
1776
+ program.declarations.forEach(walk);
2095
1777
  return names;
2096
1778
  }
2097
- /**
2098
- * Walk the entire program AST and collect all names that are LOCALLY DEFINED:
2099
- * top-level declaration names, non-star import aliases, function/lambda
2100
- * parameters, local val/var bindings, for-loop variables, catch-clause names,
2101
- * when-subject bindings, and type parameter names.
2102
- */
2103
1779
  gatherAllLocalBindings(program) {
2104
1780
  const names = new Set();
2105
1781
  const visited = new WeakSet();
2106
- // Top-level declaration names
2107
- for (const decl of program.declarations) {
2108
- const d = decl;
2109
- if (typeof d.name === "string" && d.name)
2110
- names.add(d.name);
2111
- }
2112
- // Non-star import aliases
2113
- for (const imp of program.imports) {
2114
- if (!imp.star) {
2115
- const name = imp.alias ?? imp.path[imp.path.length - 1];
2116
- if (name)
2117
- names.add(name);
2118
- }
2119
- }
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]); });
2120
1786
  const walk = (val) => {
2121
- if (!val || typeof val !== "object")
1787
+ if (!val || typeof val !== "object" || visited.has(val))
2122
1788
  return;
1789
+ visited.add(val);
2123
1790
  if (Array.isArray(val)) {
2124
- for (const item of val)
2125
- walk(item);
1791
+ val.forEach(walk);
2126
1792
  return;
2127
1793
  }
2128
1794
  const obj = val;
2129
- if (visited.has(obj))
2130
- return;
2131
- visited.add(obj);
2132
- const kind = obj["kind"];
2133
- // Collect bound names at their declaration sites
2134
- if (kind === "PropertyDecl" || kind === "DestructuringDecl") {
2135
- if (typeof obj["name"] === "string")
2136
- names.add(obj["name"]);
2137
- const nms = obj["names"];
2138
- if (Array.isArray(nms)) {
2139
- for (const n of nms) {
2140
- if (typeof n === "string")
2141
- names.add(n);
2142
- }
2143
- }
2144
- }
2145
- if (kind === "FunDecl" || kind === "ExtensionFunDecl" ||
2146
- kind === "ComponentDecl" || kind === "ClassDecl" ||
2147
- kind === "DataClassDecl" || kind === "SealedClassDecl" ||
2148
- kind === "EnumClassDecl" || kind === "InterfaceDecl" ||
2149
- kind === "ObjectDecl" || kind === "TypeAliasDecl") {
2150
- if (typeof obj["name"] === "string" && obj["name"])
2151
- names.add(obj["name"]);
2152
- // Type parameters
2153
- const typeParams = obj["typeParams"];
2154
- if (Array.isArray(typeParams)) {
2155
- for (const tp of typeParams) {
2156
- if (tp.name)
2157
- names.add(tp.name);
2158
- }
2159
- }
2160
- // Function/method parameters
2161
- const params = obj["params"];
2162
- if (Array.isArray(params)) {
2163
- for (const p of params) {
2164
- if (p.name)
2165
- names.add(p.name);
2166
- }
2167
- }
2168
- }
2169
- if (kind === "LambdaExpr") {
2170
- const params = obj["params"];
2171
- if (Array.isArray(params)) {
2172
- for (const p of params) {
2173
- if (p.name)
2174
- names.add(p.name);
2175
- }
2176
- }
2177
- }
2178
- if (kind === "ForStmt") {
2179
- const binding = obj["binding"];
2180
- if (typeof binding === "string") {
2181
- names.add(binding);
2182
- }
2183
- else if (binding && typeof binding === "object") {
2184
- const b = binding;
2185
- // TupleDestructure or MapDestructure
2186
- if (Array.isArray(b["names"])) {
2187
- for (const n of b["names"]) {
2188
- if (typeof n === "string")
2189
- names.add(n);
2190
- }
2191
- }
2192
- if (typeof b["key"] === "string")
2193
- names.add(b["key"]);
2194
- if (typeof b["value"] === "string")
2195
- names.add(b["value"]);
2196
- }
2197
- }
2198
- if (kind === "TryCatchStmt") {
2199
- const catches = obj["catches"];
2200
- if (Array.isArray(catches)) {
2201
- for (const c of catches) {
2202
- if (c.name)
2203
- names.add(c.name);
2204
- }
2205
- }
2206
- }
2207
- if (kind === "WhenStmt" || kind === "WhenExpr") {
2208
- const subject = obj["subject"];
2209
- if (typeof subject?.["binding"] === "string")
2210
- names.add(subject["binding"]);
2211
- }
2212
- if (kind === "SecondaryConstructor") {
2213
- const params = obj["params"];
2214
- if (Array.isArray(params)) {
2215
- for (const p of params) {
2216
- if (p.name)
2217
- names.add(p.name);
2218
- }
2219
- }
2220
- }
2221
- for (const propVal of Object.values(obj)) {
2222
- walk(propVal);
2223
- }
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);
2224
1806
  };
2225
- for (const decl of program.declarations)
2226
- walk(decl);
1807
+ program.declarations.forEach(walk);
2227
1808
  return names;
2228
1809
  }
2229
1810
  }
2230
1811
  exports.CodeGenerator = CodeGenerator;
2231
- // ---------------------------------------------------------------------------
2232
- // Type name mappings (Jalvin TypeScript)
2233
- // ---------------------------------------------------------------------------
2234
- const PRIMITIVE_TYPE_MAP = {
2235
- Int: "number",
2236
- Long: "bigint",
2237
- Float: "number",
2238
- Double: "number",
2239
- Boolean: "boolean",
2240
- String: "string",
2241
- Char: "string",
2242
- Byte: "number",
2243
- Short: "number",
2244
- Unit: "void",
2245
- Any: "unknown",
2246
- Nothing: "never",
2247
- };
2248
- const GENERIC_TYPE_MAP = {
2249
- List: "ReadonlyArray",
2250
- MutableList: "Array",
2251
- Set: "ReadonlySet",
2252
- MutableSet: "Set",
2253
- Map: "ReadonlyMap",
2254
- MutableMap: "Map",
2255
- Array: "Array",
2256
- Pair: "[", // handled specially
2257
- Triple: "[",
2258
- Deferred: "Promise",
2259
- StateFlow: "StateFlow",
2260
- MutableStateFlow: "MutableStateFlow",
2261
- Flow: "AsyncIterable",
2262
- };
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" };
2263
1814
  const PRIMITIVE_TYPES = new Set(Object.keys(PRIMITIVE_TYPE_MAP));
2264
- // ---------------------------------------------------------------------------
2265
- // Well-known JavaScript / TypeScript global names that must never be emitted
2266
- // as named imports from a wildcard-imported package.
2267
- // ---------------------------------------------------------------------------
2268
- const JS_GLOBAL_NAMES = new Set([
2269
- // JS built-in constructors and objects
2270
- "Array", "Map", "Set", "Object", "String", "Number", "Boolean",
2271
- "Promise", "Error", "TypeError", "RangeError", "SyntaxError", "URIError",
2272
- "EvalError", "ReferenceError",
2273
- "console", "Math", "Date", "JSON", "RegExp", "Symbol", "BigInt",
2274
- "Proxy", "Reflect", "globalThis", "Atomics", "SharedArrayBuffer",
2275
- "ArrayBuffer", "DataView", "Int8Array", "Uint8Array", "Uint8ClampedArray",
2276
- "Int16Array", "Uint16Array", "Int32Array", "Uint32Array",
2277
- "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array",
2278
- // Global functions
2279
- "isNaN", "isFinite", "parseInt", "parseFloat", "eval",
2280
- "encodeURI", "decodeURI", "encodeURIComponent", "decodeURIComponent",
2281
- // Browser/Node globals
2282
- "setTimeout", "clearTimeout", "setInterval", "clearInterval",
2283
- "queueMicrotask", "requestAnimationFrame", "cancelAnimationFrame",
2284
- "fetch", "URL", "URLSearchParams", "AbortController", "AbortSignal",
2285
- "EventTarget", "Event", "CustomEvent", "FormData", "Headers",
2286
- "Request", "Response", "Blob", "File", "FileReader",
2287
- "Worker", "SharedWorker", "WebSocket",
2288
- "ReadableStream", "WritableStream", "TransformStream",
2289
- "document", "window", "navigator", "location", "history", "screen",
2290
- "performance", "crypto", "indexedDB", "localStorage", "sessionStorage",
2291
- "confirm", "alert", "prompt",
2292
- "process", "Buffer", "global", "require", "module", "exports", "__dirname", "__filename",
2293
- // Special identifiers
2294
- "undefined", "null", "NaN", "Infinity",
2295
- "it", "this", "super", "arguments", "new", "class", "function",
2296
- // TypeScript primitive type names
2297
- "any", "unknown", "never", "void", "string", "number", "boolean", "bigint",
2298
- "object", "symbol",
2299
- // Jalvin type names that map to TS primitives (never need import)
2300
- "String", "Boolean", "Any", "Nothing", "Unit", "Char", "Byte", "Short",
2301
- "Float", "Double",
2302
- ]);
2303
- // ---------------------------------------------------------------------------
2304
- // Public helper
2305
- // ---------------------------------------------------------------------------
2306
- function generate(program, opts, operatorOverloads, typeMap) {
2307
- return new CodeGenerator(opts).generate(program, operatorOverloads, typeMap);
2308
- }
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); }
2309
1817
  //# sourceMappingURL=codegen.js.map