@ugo-studio/jspp 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "child_process";
3
+ import fs from "fs/promises";
4
+ import path from "path";
5
+ import pkg from "../../package.json" with { type: "json" };
6
+ import { Interpreter } from "../index.js";
7
+ import { CompilerError } from "../core/error.js";
8
+ import { parseArgs } from "./args.js";
9
+ import { COLORS } from "./colors.js";
10
+ import { getLatestMtime } from "./file-utils.js";
11
+ import { Spinner } from "./spinner.js";
12
+ const pkgDir = path.dirname(path.dirname(import.meta.dirname));
13
+ async function main() {
14
+ const { jsFilePath, isRelease, keepCpp, outputExePath, scriptArgs } = parseArgs(process.argv.slice(2));
15
+ const ext = path.extname(jsFilePath);
16
+ const jsFileName = path.basename(jsFilePath, ext);
17
+ const sourceDir = path.dirname(jsFilePath);
18
+ // Intermediate C++ file goes alongside the source JS file
19
+ const cppFilePath = path.join(sourceDir, `${jsFileName}.cpp`);
20
+ // Determine output executable path
21
+ let exeFilePath;
22
+ if (outputExePath) {
23
+ exeFilePath = outputExePath;
24
+ }
25
+ else {
26
+ const ext = process.platform === "win32" ? ".exe" : "";
27
+ exeFilePath = path.join(sourceDir, `${jsFileName}${ext}`);
28
+ }
29
+ // Mode Configuration
30
+ const mode = isRelease ? "release" : "debug";
31
+ console.log(`${COLORS.bold}JSPP Compiler${COLORS.reset} ${COLORS.dim}v${pkg.version}${COLORS.reset}`);
32
+ console.log(`Mode: ${isRelease ? COLORS.green : COLORS.yellow}${mode.toUpperCase()}${COLORS.reset}\n`);
33
+ const flags = isRelease ? ["-O3", "-DNDEBUG"] : ["-O0"];
34
+ if (process.platform === "win32") {
35
+ flags.push("-Wa,-mbig-obj");
36
+ }
37
+ const pchDir = path.resolve(pkgDir, "prelude-build", mode);
38
+ const spinner = new Spinner("Initializing...");
39
+ try {
40
+ spinner.start();
41
+ // 1. Interpreter Phase
42
+ spinner.update(`Reading ${path.basename(jsFilePath)}...`);
43
+ const jsCode = await fs.readFile(jsFilePath, "utf-8");
44
+ spinner.update("Transpiling to C++...");
45
+ const interpreter = new Interpreter();
46
+ const { cppCode, preludePath } = interpreter.interpret(jsCode, jsFilePath);
47
+ // Ensure directory for cpp file exists (should exist as it's source dir, but for safety if we change logic)
48
+ await fs.mkdir(path.dirname(cppFilePath), { recursive: true });
49
+ await fs.writeFile(cppFilePath, cppCode);
50
+ spinner.succeed(`Generated cpp`);
51
+ // 2. Precompiled Header Check
52
+ spinner.text = "Checking precompiled headers...";
53
+ spinner.start();
54
+ const pchFile = path.join(pchDir, "index.hpp.gch");
55
+ let shouldRebuild = false;
56
+ try {
57
+ const pchStats = await fs.stat(pchFile);
58
+ const sourceMtime = await getLatestMtime(preludePath);
59
+ if (sourceMtime > pchStats.mtimeMs) {
60
+ shouldRebuild = true;
61
+ }
62
+ }
63
+ catch (e) {
64
+ shouldRebuild = true;
65
+ }
66
+ if (shouldRebuild) {
67
+ spinner.update("Rebuilding precompiled headers (this may take a while)...");
68
+ // Use spawn (async) instead of spawnSync to keep spinner alive
69
+ const rebuild = spawn("bun", [
70
+ "run",
71
+ "scripts/precompile-headers.ts",
72
+ ], {
73
+ cwd: pkgDir,
74
+ stdio: ["ignore", "pipe", "pipe"],
75
+ });
76
+ const stderrChunks = [];
77
+ if (rebuild.stderr) {
78
+ rebuild.stderr.on("data", (chunk) => stderrChunks.push(chunk));
79
+ }
80
+ const exitCode = await new Promise((resolve) => {
81
+ rebuild.on("close", (code) => resolve(code ?? 1));
82
+ });
83
+ if (exitCode !== 0) {
84
+ const stderr = Buffer.concat(stderrChunks).toString();
85
+ spinner.fail("Failed to rebuild precompiled headers");
86
+ console.error(stderr);
87
+ process.exit(1);
88
+ }
89
+ spinner.succeed("Precompiled headers updated");
90
+ }
91
+ else {
92
+ spinner.succeed("Precompiled headers");
93
+ }
94
+ // 3. Compilation Phase
95
+ spinner.text = `Compiling binary...`;
96
+ spinner.start();
97
+ // Ensure output directory exists
98
+ await fs.mkdir(path.dirname(exeFilePath), { recursive: true });
99
+ const compile = spawn("g++", [
100
+ "-std=c++23",
101
+ ...flags,
102
+ cppFilePath,
103
+ "-o",
104
+ exeFilePath,
105
+ "-I",
106
+ pchDir,
107
+ "-I",
108
+ preludePath,
109
+ ], {
110
+ stdio: ["ignore", "pipe", "pipe"],
111
+ });
112
+ const compileStderrChunks = [];
113
+ if (compile.stderr) {
114
+ compile.stderr.on("data", (chunk) => compileStderrChunks.push(chunk));
115
+ }
116
+ const compileExitCode = await new Promise((resolve) => {
117
+ compile.on("close", (code) => resolve(code ?? 1));
118
+ });
119
+ if (compileExitCode !== 0) {
120
+ const stderr = Buffer.concat(compileStderrChunks).toString();
121
+ spinner.fail(`Compilation failed`);
122
+ console.error(stderr);
123
+ process.exit(1);
124
+ }
125
+ spinner.succeed(`Compiled to ${COLORS.green}${COLORS.bold}${path.basename(exeFilePath)}${COLORS.reset}`);
126
+ // Clean up C++ file if not requested to keep
127
+ if (!keepCpp) {
128
+ try {
129
+ await fs.unlink(cppFilePath);
130
+ }
131
+ catch (e) {
132
+ // Ignore error if file cannot be deleted
133
+ }
134
+ }
135
+ // 4. Execution Phase
136
+ console.log(`\n${COLORS.cyan}--- Running Output ---${COLORS.reset}`);
137
+ const run = spawn(exeFilePath, scriptArgs, {
138
+ stdio: "inherit",
139
+ });
140
+ const runExitCode = await new Promise((resolve) => {
141
+ run.on("close", (code) => resolve(code ?? 1));
142
+ });
143
+ console.log(`${COLORS.cyan}----------------------${COLORS.reset}\n`);
144
+ if (runExitCode !== 0) {
145
+ console.error(`${COLORS.red}Execution failed with exit code ${runExitCode}${COLORS.reset}`);
146
+ process.exit(1);
147
+ }
148
+ }
149
+ catch (error) {
150
+ if (error instanceof CompilerError) {
151
+ spinner.fail("Compilation failed");
152
+ console.error(error.getFormattedError());
153
+ process.exit(1);
154
+ }
155
+ spinner.fail("An unexpected error occurred");
156
+ console.error(error);
157
+ process.exit(1);
158
+ }
159
+ }
160
+ main();
@@ -0,0 +1,55 @@
1
+ import { COLORS } from "./colors.js";
2
+ export class Spinner {
3
+ frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
4
+ interval = null;
5
+ frameIndex = 0;
6
+ text;
7
+ constructor(text) {
8
+ this.text = text;
9
+ }
10
+ start() {
11
+ process.stdout.write("\x1b[?25l"); // Hide cursor
12
+ this.frameIndex = 0;
13
+ this.render();
14
+ this.interval = setInterval(() => {
15
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
16
+ this.render();
17
+ }, 80);
18
+ }
19
+ update(text) {
20
+ this.text = text;
21
+ this.render();
22
+ }
23
+ stop(symbol = "", color = COLORS.reset) {
24
+ if (this.interval) {
25
+ clearInterval(this.interval);
26
+ this.interval = null;
27
+ }
28
+ this.clearLine();
29
+ process.stdout.write(`${color}${symbol} ${COLORS.reset} ${this.text}\n`);
30
+ process.stdout.write("\x1b[?25h"); // Show cursor
31
+ }
32
+ succeed(text) {
33
+ if (text)
34
+ this.text = text;
35
+ this.stop("✔", COLORS.green);
36
+ }
37
+ fail(text) {
38
+ if (text)
39
+ this.text = text;
40
+ this.stop("✖", COLORS.red);
41
+ }
42
+ info(text) {
43
+ if (text)
44
+ this.text = text;
45
+ this.stop("ℹ", COLORS.cyan);
46
+ }
47
+ render() {
48
+ this.clearLine();
49
+ const frame = this.frames[this.frameIndex];
50
+ process.stdout.write(`${COLORS.cyan}${frame} ${COLORS.reset} ${this.text}`);
51
+ }
52
+ clearLine() {
53
+ process.stdout.write("\r\x1b[K");
54
+ }
55
+ }
package/dist/cli.js CHANGED
@@ -41,7 +41,7 @@ async function main() {
41
41
  const jsCode = await fs.readFile(jsFilePath, "utf-8");
42
42
  spinner.update("Transpiling to C++...");
43
43
  const interpreter = new Interpreter();
44
- const { cppCode, preludePath } = interpreter.interpret(jsCode);
44
+ const { cppCode, preludePath } = interpreter.interpret(jsCode, jsFilePath);
45
45
  // Ensure directory for cpp file exists (should exist as it's source dir, but for safety if we change logic)
46
46
  await fs.mkdir(path.dirname(cppFilePath), { recursive: true });
47
47
  await fs.writeFile(cppFilePath, cppCode);
@@ -1,5 +1,5 @@
1
1
  import ts from "typescript";
2
- import { DeclaredSymbols, DeclaredSymbolType } from "../../ast/symbols.js";
2
+ import { DeclarationType, DeclaredSymbols } from "../../ast/symbols.js";
3
3
  import { CodeGenerator } from "./index.js";
4
4
  export function visitForStatement(node, context) {
5
5
  const forStmt = node;
@@ -11,9 +11,14 @@ export function visitForStatement(node, context) {
11
11
  this.indentationLevel++; // Enter a new scope for the for loop
12
12
  // Handle initializer
13
13
  let initializerCode = "";
14
- let conditionContext = {
14
+ const conditionContext = {
15
15
  ...context,
16
- topLevelScopeSymbols: this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.localScopeSymbols),
16
+ globalScopeSymbols: this.prepareScopeSymbolsForVisit(context.globalScopeSymbols, context.localScopeSymbols),
17
+ };
18
+ const statementContext = {
19
+ ...context,
20
+ currentLabel: undefined,
21
+ isFunctionBody: false,
17
22
  };
18
23
  if (forStmt.initializer) {
19
24
  if (ts.isVariableDeclarationList(forStmt.initializer)) {
@@ -32,9 +37,18 @@ export function visitForStatement(node, context) {
32
37
  const typeInfo = this.typeAnalyzer.scopeManager
33
38
  .lookupFromScope(name, scope);
34
39
  conditionContext.localScopeSymbols = new DeclaredSymbols();
35
- conditionContext.localScopeSymbols.set(name, {
36
- type: DeclaredSymbolType.letOrConst,
37
- checkedIfUninitialized: true,
40
+ const declType = (varDeclList.flags &
41
+ (ts.NodeFlags.Let)) !==
42
+ 0
43
+ ? DeclarationType.let
44
+ : DeclarationType.const;
45
+ conditionContext.localScopeSymbols.add(name, {
46
+ type: declType,
47
+ checks: { initialized: true },
48
+ });
49
+ statementContext.localScopeSymbols.add(name, {
50
+ type: declType,
51
+ checks: { initialized: true },
38
52
  });
39
53
  if (typeInfo.needsHeapAllocation) {
40
54
  initializerCode =
@@ -59,18 +73,15 @@ export function visitForStatement(node, context) {
59
73
  }
60
74
  code += `${this.indent()}for (${initializerCode}; `;
61
75
  if (forStmt.condition) {
62
- code += `is_truthy(${this.visit(forStmt.condition, conditionContext)})`;
76
+ code += `jspp::is_truthy(${this.visit(forStmt.condition, conditionContext)})`;
63
77
  }
64
78
  code += "; ";
65
79
  if (forStmt.incrementor) {
66
80
  code += this.visit(forStmt.incrementor, context);
67
81
  }
68
82
  code += ") ";
69
- const statementCode = this.visit(forStmt.statement, {
70
- ...context,
71
- currentLabel: undefined,
72
- isFunctionBody: false,
73
- }).trim();
83
+ const statementCode = this.visit(forStmt.statement, statementContext)
84
+ .trim();
74
85
  if (ts.isBlock(node.statement)) {
75
86
  let blockContent = statementCode.substring(1, statementCode.length - 2); // remove curly braces
76
87
  if (context.currentLabel) {
@@ -240,7 +251,7 @@ export function visitForOfStatement(node, context) {
240
251
  `${this.indent()}auto ${nextRes} = ${nextFunc}.call(${iterator}, {}, "next");\n`;
241
252
  }
242
253
  code +=
243
- `${this.indent()}while (!is_truthy(${nextRes}.get_own_property("done"))) {\n`;
254
+ `${this.indent()}while (!jspp::is_truthy(${nextRes}.get_own_property("done"))) {\n`;
244
255
  this.indentationLevel++;
245
256
  code +=
246
257
  `${this.indent()}${assignmentTarget} = ${nextRes}.get_own_property("value");\n`;
@@ -277,7 +288,7 @@ export function visitWhileStatement(node, context) {
277
288
  const conditionText = condition.kind === ts.SyntaxKind.TrueKeyword ||
278
289
  condition.kind === ts.SyntaxKind.FalseKeyword
279
290
  ? condition.getText()
280
- : `is_truthy(${this.visit(condition, context)})`;
291
+ : `jspp::is_truthy(${this.visit(condition, context)})`;
281
292
  let code = "";
282
293
  if (context.currentLabel) {
283
294
  code += `${this.indent()}${context.currentLabel}: {\n`;
@@ -317,7 +328,7 @@ export function visitWhileStatement(node, context) {
317
328
  }
318
329
  export function visitDoStatement(node, context) {
319
330
  const condition = node.expression;
320
- const conditionText = `is_truthy(${this.visit(condition, context)})`;
331
+ const conditionText = `jspp::is_truthy(${this.visit(condition, context)})`;
321
332
  let code = "";
322
333
  if (context.currentLabel) {
323
334
  code += `${this.indent()}${context.currentLabel}: {\n`;
@@ -358,6 +369,7 @@ export function visitDoStatement(node, context) {
358
369
  }
359
370
  export function visitSwitchStatement(node, context) {
360
371
  const switchStmt = node;
372
+ context.currentScopeNode = node; // Update scope node
361
373
  let code = "";
362
374
  const declaredSymbols = this.getDeclaredSymbols(switchStmt.caseBlock);
363
375
  const switchBreakLabel = this.generateUniqueName("__switch_break_", declaredSymbols);
@@ -374,25 +386,75 @@ export function visitSwitchStatement(node, context) {
374
386
  code +=
375
387
  `${this.indent()}const jspp::AnyValue ${switchValueVar} = ${expressionCode};\n`;
376
388
  code += `${this.indent()}bool ${fallthroughVar} = false;\n`;
377
- // Hoist variable declarations
378
- const hoistedSymbols = new DeclaredSymbols();
389
+ // Collect declarations from all clauses
390
+ const funcDecls = [];
391
+ const classDecls = [];
392
+ const blockScopedDecls = [];
379
393
  for (const clause of switchStmt.caseBlock.clauses) {
380
- if (ts.isCaseClause(clause) || ts.isDefaultClause(clause)) {
381
- for (const stmt of clause.statements) {
382
- if (ts.isVariableStatement(stmt)) {
383
- const varDecls = stmt.declarationList.declarations;
384
- for (const decl of varDecls) {
385
- code += this.hoistDeclaration(decl, hoistedSymbols);
386
- }
387
- }
388
- else if (ts.isFunctionDeclaration(stmt)) {
389
- code += this.hoistDeclaration(stmt, hoistedSymbols);
394
+ for (const stmt of clause.statements) {
395
+ if (ts.isFunctionDeclaration(stmt)) {
396
+ funcDecls.push(stmt);
397
+ }
398
+ else if (ts.isClassDeclaration(stmt)) {
399
+ classDecls.push(stmt);
400
+ }
401
+ else if (ts.isVariableStatement(stmt)) {
402
+ const isLetOrConst = (stmt.declarationList.flags &
403
+ (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
404
+ if (isLetOrConst) {
405
+ blockScopedDecls.push(...stmt.declarationList.declarations);
390
406
  }
391
407
  }
392
408
  }
393
409
  }
394
- // Prepare scope symbols for the switch block
395
- const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.localScopeSymbols);
410
+ const hoistedSymbols = new DeclaredSymbols();
411
+ // 1. Hoist function declarations
412
+ funcDecls.forEach((func) => {
413
+ code += this.hoistDeclaration(func, hoistedSymbols, node);
414
+ });
415
+ // 2. Hoist class declarations
416
+ classDecls.forEach((cls) => {
417
+ code += this.hoistDeclaration(cls, hoistedSymbols, node);
418
+ });
419
+ // 3. Hoist variable declarations (let/const only)
420
+ blockScopedDecls.forEach((decl) => {
421
+ code += this.hoistDeclaration(decl, hoistedSymbols, node);
422
+ });
423
+ // Compile symbols for other statements
424
+ const globalScopeSymbols = this.prepareScopeSymbolsForVisit(context.globalScopeSymbols, context.localScopeSymbols);
425
+ const localScopeSymbols = new DeclaredSymbols(hoistedSymbols);
426
+ // 4. Assign hoisted functions (Optimization)
427
+ const contextForFunctions = {
428
+ ...context,
429
+ localScopeSymbols: new DeclaredSymbols(context.localScopeSymbols, hoistedSymbols),
430
+ };
431
+ funcDecls.forEach((stmt) => {
432
+ const funcName = stmt.name?.getText();
433
+ if (!funcName)
434
+ return;
435
+ const symbol = hoistedSymbols.get(funcName);
436
+ if (!symbol)
437
+ return;
438
+ // Mark initialized
439
+ this.markSymbolAsInitialized(funcName, contextForFunctions.globalScopeSymbols, contextForFunctions.localScopeSymbols);
440
+ this.markSymbolAsInitialized(funcName, globalScopeSymbols, localScopeSymbols);
441
+ // Generate native name
442
+ const nativeName = this.generateUniqueName(`__${funcName}_native_`, hoistedSymbols);
443
+ hoistedSymbols.update(funcName, { func: { nativeName } });
444
+ // Generate lambda
445
+ const lambda = this.generateLambda(stmt, contextForFunctions, {
446
+ isAssignment: true,
447
+ generateOnlyLambda: true,
448
+ nativeName,
449
+ });
450
+ code += `${this.indent()}auto ${nativeName} = ${lambda};\n`;
451
+ // Generate AnyValue wrapper
452
+ if (this.isFunctionUsedAsValue(stmt, node) ||
453
+ this.isFunctionUsedBeforeDeclaration(funcName, node)) {
454
+ const fullExpression = this.generateFullLambdaExpression(stmt, contextForFunctions, nativeName, { isAssignment: true, noTypeSignature: true });
455
+ code += `${this.indent()}*${funcName} = ${fullExpression};\n`;
456
+ }
457
+ });
396
458
  let firstIf = true;
397
459
  for (const clause of switchStmt.caseBlock.clauses) {
398
460
  if (ts.isCaseClause(clause)) {
@@ -416,15 +478,23 @@ export function visitSwitchStatement(node, context) {
416
478
  code += `${this.indent()}${fallthroughVar} = true;\n`;
417
479
  for (const stmt of clause.statements) {
418
480
  if (ts.isFunctionDeclaration(stmt)) {
419
- const funcName = stmt.name?.getText();
420
- if (funcName) {
421
- const contextForFunction = {
422
- ...context,
423
- topLevelScopeSymbols,
424
- localScopeSymbols: hoistedSymbols,
425
- };
426
- const lambda = this.generateLambda(stmt, contextForFunction, { isAssignment: true });
427
- code += `${this.indent()}*${funcName} = ${lambda};\n`;
481
+ // Already handled
482
+ }
483
+ else if (ts.isVariableStatement(stmt)) {
484
+ const isLetOrConst = (stmt.declarationList.flags &
485
+ (ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
486
+ 0;
487
+ const contextForVisit = {
488
+ ...context,
489
+ switchBreakLabel,
490
+ currentLabel: undefined,
491
+ globalScopeSymbols,
492
+ localScopeSymbols,
493
+ isAssignmentOnly: !isLetOrConst,
494
+ };
495
+ const assignments = this.visit(stmt.declarationList, contextForVisit);
496
+ if (assignments) {
497
+ code += `${this.indent()}${assignments};\n`;
428
498
  }
429
499
  }
430
500
  else {
@@ -432,10 +502,8 @@ export function visitSwitchStatement(node, context) {
432
502
  ...context,
433
503
  switchBreakLabel,
434
504
  currentLabel: undefined, // Clear currentLabel for nested visits
435
- topLevelScopeSymbols,
436
- localScopeSymbols: hoistedSymbols,
437
- derefBeforeAssignment: true,
438
- isAssignmentOnly: ts.isVariableStatement(stmt),
505
+ globalScopeSymbols,
506
+ localScopeSymbols,
439
507
  });
440
508
  }
441
509
  }
@@ -455,15 +523,35 @@ export function visitSwitchStatement(node, context) {
455
523
  }
456
524
  this.indentationLevel++;
457
525
  for (const stmt of clause.statements) {
458
- code += this.visit(stmt, {
459
- ...context,
460
- switchBreakLabel,
461
- currentLabel: undefined, // Clear currentLabel for nested visits
462
- topLevelScopeSymbols,
463
- localScopeSymbols: hoistedSymbols,
464
- derefBeforeAssignment: true,
465
- isAssignmentOnly: ts.isVariableStatement(stmt),
466
- });
526
+ if (ts.isFunctionDeclaration(stmt)) {
527
+ // Already handled
528
+ }
529
+ else if (ts.isVariableStatement(stmt)) {
530
+ const isLetOrConst = (stmt.declarationList.flags &
531
+ (ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
532
+ 0;
533
+ const contextForVisit = {
534
+ ...context,
535
+ switchBreakLabel,
536
+ currentLabel: undefined,
537
+ globalScopeSymbols,
538
+ localScopeSymbols,
539
+ isAssignmentOnly: !isLetOrConst,
540
+ };
541
+ const assignments = this.visit(stmt.declarationList, contextForVisit);
542
+ if (assignments) {
543
+ code += `${this.indent()}${assignments};\n`;
544
+ }
545
+ }
546
+ else {
547
+ code += this.visit(stmt, {
548
+ ...context,
549
+ switchBreakLabel,
550
+ currentLabel: undefined, // Clear currentLabel for nested visits
551
+ globalScopeSymbols,
552
+ localScopeSymbols,
553
+ });
554
+ }
467
555
  }
468
556
  this.indentationLevel--;
469
557
  code += `${this.indent()}}\n`;
@@ -1,4 +1,5 @@
1
1
  import ts from "typescript";
2
+ import { CompilerError } from "../error.js";
2
3
  import { CodeGenerator } from "./index.js";
3
4
  export function visitVariableDeclarationList(node, context) {
4
5
  return node.declarations
@@ -12,27 +13,50 @@ export function visitVariableDeclaration(node, context) {
12
13
  const scope = this.getScopeForNode(varDecl);
13
14
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
14
15
  // Mark the symbol as checked
15
- this.markSymbolAsChecked(name, context.topLevelScopeSymbols, context.localScopeSymbols);
16
+ this.markSymbolAsInitialized(name, context.globalScopeSymbols, context.localScopeSymbols);
17
+ let nativeLambdaCode = "";
16
18
  let initializer = "";
19
+ let shouldSkipDeref = false;
17
20
  if (varDecl.initializer) {
18
21
  const initExpr = varDecl.initializer;
19
- const initContext = {
20
- ...context,
21
- lambdaName: ts.isArrowFunction(initExpr) ? name : undefined, // Pass the variable name for arrow functions
22
- };
23
22
  let initText = ts.isNumericLiteral(initExpr)
24
23
  ? initExpr.getText()
25
- : this.visit(initExpr, initContext);
24
+ : this.visit(initExpr, context);
26
25
  if (ts.isIdentifier(initExpr)) {
27
26
  const initScope = this.getScopeForNode(initExpr);
28
27
  const initTypeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(initExpr.text, initScope);
29
28
  const varName = this.getJsVarName(initExpr);
29
+ // Check if both target and initializer are heap allocated
30
+ if (typeInfo.needsHeapAllocation &&
31
+ initTypeInfo?.needsHeapAllocation) {
32
+ shouldSkipDeref = true;
33
+ }
30
34
  if (initTypeInfo &&
31
35
  !initTypeInfo.isParameter &&
32
- !initTypeInfo.isBuiltin) {
33
- initText = this.getDerefCode(initText, varName, initContext, initTypeInfo);
36
+ !initTypeInfo.isBuiltin &&
37
+ !shouldSkipDeref) {
38
+ initText = this.getDerefCode(initText, varName, context, initTypeInfo);
34
39
  }
35
40
  }
41
+ else if (ts.isArrowFunction(initExpr)) {
42
+ const initContext = {
43
+ ...context,
44
+ lambdaName: name, // Use the variable name as function name
45
+ };
46
+ // Generate and update self name
47
+ const nativeName = this.generateUniqueName(`__${name}_native_`, context.localScopeSymbols, context.globalScopeSymbols);
48
+ context.localScopeSymbols.update(name, { func: { nativeName } });
49
+ // Generate lambda
50
+ const lambda = this.generateLambda(initExpr, initContext, {
51
+ isAssignment: true,
52
+ generateOnlyLambda: true,
53
+ nativeName,
54
+ });
55
+ nativeLambdaCode =
56
+ `auto ${nativeName} = ${lambda};\n${this.indent()}`;
57
+ // Generate AnyValue wrapper
58
+ initText = this.generateFullLambdaExpression(initExpr, initContext, nativeName, { isAssignment: true, noTypeSignature: true });
59
+ }
36
60
  initializer = " = " + initText;
37
61
  }
38
62
  const isLetOrConst = (varDecl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
@@ -40,19 +64,28 @@ export function visitVariableDeclaration(node, context) {
40
64
  (!context.localScopeSymbols.has(name));
41
65
  const assignmentTarget = shouldDeref
42
66
  ? this.getDerefCode(name, name, context, typeInfo)
43
- : (typeInfo.needsHeapAllocation ? `*${name}` : name);
67
+ : (typeInfo.needsHeapAllocation && !shouldSkipDeref
68
+ ? `*${name}`
69
+ : name);
44
70
  if (isLetOrConst) {
45
71
  // If there's no initializer, it should be assigned undefined.
46
72
  if (!initializer) {
47
- return `${assignmentTarget} = jspp::Constants::UNDEFINED`;
73
+ // Constant variables must be initialized
74
+ const isConst = (varDecl.parent.flags & (ts.NodeFlags.Const)) !== 0;
75
+ if (isConst) {
76
+ throw new CompilerError(`The constant "${name}" must be initialized`, varDecl, "SyntaxError");
77
+ }
78
+ else {
79
+ return `${nativeLambdaCode}${assignmentTarget} = jspp::Constants::UNDEFINED`;
80
+ }
48
81
  }
49
- return `${assignmentTarget}${initializer}`;
82
+ return `${nativeLambdaCode}${assignmentTarget}${initializer}`;
50
83
  }
51
84
  // For 'var', it's a bit more complex.
52
85
  if (context.isAssignmentOnly) {
53
86
  if (!initializer)
54
87
  return "";
55
- return `${assignmentTarget}${initializer}`;
88
+ return `${nativeLambdaCode}${assignmentTarget}${initializer}`;
56
89
  }
57
90
  else {
58
91
  // This case should not be hit with the new hoisting logic,