@ugo-studio/jspp 0.2.3 → 0.2.5

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/README.md CHANGED
@@ -52,7 +52,7 @@ To contribute to JSPP or run its test suite, follow these steps:
52
52
  ### Prerequisites
53
53
 
54
54
  - **Node.js:** This project uses Node.js for package management and script execution.
55
- - [Install Node.js](https://nodejs.org/)
55
+ - [Install Node.js >= v20.0.0](https://nodejs.org/)
56
56
  - **C++ Compiler:** A compiler with support for C++23 is required. This project is tested with `g++`.
57
57
  - `g++` (MinGW on Windows, or available via build-essentials on Linux)
58
58
 
@@ -1,7 +1,7 @@
1
1
  import * as ts from "typescript";
2
- import { isBuiltinObject } from "../core/codegen/helpers";
3
- import { Traverser } from "../core/traverser";
4
- import { Scope, ScopeManager } from "./scope";
2
+ import { isBuiltinObject } from "../core/codegen/helpers.js";
3
+ import { Traverser } from "../core/traverser.js";
4
+ import { Scope, ScopeManager } from "./scope.js";
5
5
  export class TypeAnalyzer {
6
6
  traverser = new Traverser();
7
7
  scopeManager = new ScopeManager();
@@ -19,7 +19,8 @@ export class TypeAnalyzer {
19
19
  const definingScope = this.scopeManager.currentScope
20
20
  .findScopeFor(name);
21
21
  if (definingScope) {
22
- const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
22
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
23
+ null;
23
24
  const definingFunc = definingScope.ownerFunction;
24
25
  if (definingFunc !== currentFuncNode) {
25
26
  const typeInfo = this.scopeManager.lookup(name);
@@ -34,7 +35,8 @@ export class TypeAnalyzer {
34
35
  // Enter new scope for any block-like structure
35
36
  Block: {
36
37
  enter: (node, parent) => {
37
- const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
38
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
39
+ null;
38
40
  this.scopeManager.enterScope(currentFuncNode);
39
41
  this.nodeToScope.set(node, this.scopeManager.currentScope);
40
42
  if (parent && ts.isCatchClause(parent) &&
@@ -57,7 +59,8 @@ export class TypeAnalyzer {
57
59
  ForStatement: {
58
60
  enter: (node) => {
59
61
  this.loopDepth++;
60
- const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
62
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
63
+ null;
61
64
  this.scopeManager.enterScope(currentFuncNode);
62
65
  this.nodeToScope.set(node, this.scopeManager.currentScope);
63
66
  },
@@ -69,7 +72,8 @@ export class TypeAnalyzer {
69
72
  ForOfStatement: {
70
73
  enter: (node) => {
71
74
  this.loopDepth++;
72
- const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
75
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
76
+ null;
73
77
  this.scopeManager.enterScope(currentFuncNode);
74
78
  this.nodeToScope.set(node, this.scopeManager.currentScope);
75
79
  },
@@ -81,7 +85,8 @@ export class TypeAnalyzer {
81
85
  ForInStatement: {
82
86
  enter: (node) => {
83
87
  this.loopDepth++;
84
- const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
88
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
89
+ null;
85
90
  this.scopeManager.enterScope(currentFuncNode);
86
91
  this.nodeToScope.set(node, this.scopeManager.currentScope);
87
92
  const forIn = node;
@@ -112,7 +117,8 @@ export class TypeAnalyzer {
112
117
  WhileStatement: {
113
118
  enter: (node) => {
114
119
  this.loopDepth++;
115
- const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
120
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
121
+ null;
116
122
  this.scopeManager.enterScope(currentFuncNode);
117
123
  this.nodeToScope.set(node, this.scopeManager.currentScope);
118
124
  },
@@ -124,7 +130,8 @@ export class TypeAnalyzer {
124
130
  DoStatement: {
125
131
  enter: (node) => {
126
132
  this.loopDepth++;
127
- const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
133
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
134
+ null;
128
135
  this.scopeManager.enterScope(currentFuncNode);
129
136
  this.nodeToScope.set(node, this.scopeManager.currentScope);
130
137
  },
@@ -136,7 +143,8 @@ export class TypeAnalyzer {
136
143
  SwitchStatement: {
137
144
  enter: (node) => {
138
145
  this.switchDepth++;
139
- const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
146
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
147
+ null;
140
148
  this.scopeManager.enterScope(currentFuncNode);
141
149
  this.nodeToScope.set(node, this.scopeManager.currentScope);
142
150
  },
@@ -197,6 +205,15 @@ export class TypeAnalyzer {
197
205
  this.functionTypeInfo.set(node, funcType);
198
206
  this.scopeManager.enterScope(node);
199
207
  this.nodeToScope.set(node, this.scopeManager.currentScope);
208
+ // Catch invalid parameters
209
+ node.parameters.forEach((p) => {
210
+ if (p.getText() == "this") {
211
+ const sourceFile = node.getSourceFile();
212
+ const { line, character } = sourceFile
213
+ .getLineAndCharacterOfPosition(p.getStart());
214
+ throw new SyntaxError(`Cannot use 'this' as a parameter name.\n\n${" ".repeat(6)}at ${sourceFile.fileName}:${line + 1}:${character + 1}\n`);
215
+ }
216
+ });
200
217
  // Define parameters in the new scope
201
218
  node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
202
219
  type: "auto",
@@ -230,6 +247,15 @@ export class TypeAnalyzer {
230
247
  if (node.name) {
231
248
  this.scopeManager.define(node.name.getText(), funcType);
232
249
  }
250
+ // Catch invalid parameters
251
+ node.parameters.forEach((p) => {
252
+ if (p.getText() == "this") {
253
+ const sourceFile = node.getSourceFile();
254
+ const { line, character } = sourceFile
255
+ .getLineAndCharacterOfPosition(p.getStart());
256
+ throw new SyntaxError(`Cannot use 'this' as a parameter name.\n\n${" ".repeat(6)}at ${sourceFile.fileName}:${line + 1}:${character + 1}\n`);
257
+ }
258
+ });
233
259
  // Define parameters in the new scope
234
260
  node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
235
261
  type: "auto",
@@ -262,6 +288,15 @@ export class TypeAnalyzer {
262
288
  this.scopeManager.define(funcName, funcType);
263
289
  this.functionTypeInfo.set(node, funcType);
264
290
  }
291
+ // Catch invalid parameters
292
+ node.parameters.forEach((p) => {
293
+ if (p.getText() == "this") {
294
+ const sourceFile = node.getSourceFile();
295
+ const { line, character } = sourceFile
296
+ .getLineAndCharacterOfPosition(p.getStart());
297
+ throw new SyntaxError(`Cannot use 'this' as a parameter name.\n\n${" ".repeat(6)}at ${sourceFile.fileName}:${line + 1}:${character + 1}\n`);
298
+ }
299
+ });
265
300
  this.scopeManager.enterScope(node);
266
301
  this.nodeToScope.set(node, this.scopeManager.currentScope);
267
302
  // Define parameters in the new scope
@@ -292,7 +327,8 @@ export class TypeAnalyzer {
292
327
  };
293
328
  this.scopeManager.define(name, typeInfo);
294
329
  }
295
- const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
330
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
331
+ null;
296
332
  this.scopeManager.enterScope(currentFuncNode);
297
333
  this.nodeToScope.set(node, this.scopeManager.currentScope);
298
334
  },
@@ -362,7 +398,8 @@ export class TypeAnalyzer {
362
398
  enter: (node) => {
363
399
  if (ts.isVariableDeclaration(node)) {
364
400
  const name = node.name.getText();
365
- const isBlockScoped = (node.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
401
+ const isBlockScoped = (node.parent.flags &
402
+ (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
366
403
  const isConst = (node.parent.flags & ts.NodeFlags.Const) !== 0;
367
404
  let type = "auto";
368
405
  let needsHeap = false;
@@ -375,6 +412,11 @@ export class TypeAnalyzer {
375
412
  type = "function";
376
413
  needsHeap = true;
377
414
  }
415
+ else if (ts.isIdentifier(node.initializer)) {
416
+ const typeInfo = this.scopeManager.lookup(node.initializer.text);
417
+ needsHeap = typeInfo?.needsHeapAllocation ??
418
+ false;
419
+ }
378
420
  }
379
421
  const typeInfo = {
380
422
  type,
@@ -396,7 +438,8 @@ export class TypeAnalyzer {
396
438
  if (ts.isIdentifier(node)) {
397
439
  if (isBuiltinObject.call(this, node))
398
440
  return;
399
- const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
441
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
442
+ null;
400
443
  if (currentFuncNode &&
401
444
  (ts.isFunctionDeclaration(currentFuncNode) ||
402
445
  ts.isFunctionExpression(currentFuncNode)) &&
@@ -1,32 +1,74 @@
1
- export var DeclaredSymbolType;
2
- (function (DeclaredSymbolType) {
3
- DeclaredSymbolType["letOrConst"] = "letOrConst";
4
- DeclaredSymbolType["function"] = "function";
5
- DeclaredSymbolType["var"] = "var";
6
- })(DeclaredSymbolType || (DeclaredSymbolType = {}));
1
+ export var DeclarationType;
2
+ (function (DeclarationType) {
3
+ DeclarationType["var"] = "var";
4
+ DeclarationType["let"] = "let";
5
+ DeclarationType["const"] = "const";
6
+ DeclarationType["function"] = "function";
7
+ DeclarationType["class"] = "class";
8
+ })(DeclarationType || (DeclarationType = {}));
9
+ export class DeclaredSymbol {
10
+ type;
11
+ checked;
12
+ func;
13
+ constructor(type) {
14
+ this.type = type;
15
+ this.checked = {
16
+ initialized: false,
17
+ };
18
+ this.func = null;
19
+ }
20
+ get isMutable() {
21
+ return this.type === DeclarationType.let ||
22
+ this.type === DeclarationType.var;
23
+ }
24
+ updateChecked(update) {
25
+ this.checked = {
26
+ ...this.checked,
27
+ ...update,
28
+ };
29
+ }
30
+ updateFunc(update) {
31
+ this.func = update
32
+ ? {
33
+ ...this.func,
34
+ ...update,
35
+ }
36
+ : null;
37
+ }
38
+ }
7
39
  export class DeclaredSymbols {
8
40
  symbols;
9
41
  constructor(...m) {
10
42
  this.symbols = new Map();
11
43
  m.forEach((ds) => ds.symbols.forEach((v, k) => this.symbols.set(k, v)));
12
44
  }
45
+ get names() {
46
+ return new Set(this.symbols.keys());
47
+ }
13
48
  has(name) {
14
49
  return this.symbols.has(name);
15
50
  }
16
51
  get(name) {
17
52
  return this.symbols.get(name);
18
53
  }
19
- set(name, value) {
20
- return this.symbols.set(name, value);
54
+ add(name, value) {
55
+ const sym = new DeclaredSymbol(value.type);
56
+ if (value.checked !== undefined)
57
+ sym.updateChecked(value.checked);
58
+ if (value.func !== undefined)
59
+ sym.updateFunc(value.func);
60
+ return this.symbols.set(name, sym);
21
61
  }
22
62
  update(name, update) {
23
- const oldValue = this.get(name);
24
- if (oldValue) {
25
- const newValue = { ...oldValue, ...update };
26
- return this.symbols.set(name, newValue);
63
+ const sym = this.get(name);
64
+ if (sym) {
65
+ if (update.type !== undefined)
66
+ sym.type = update.type;
67
+ if (update.checked !== undefined)
68
+ sym.updateChecked(update.checked);
69
+ if (update.func !== undefined)
70
+ sym.updateFunc(update.func);
71
+ return this.symbols.set(name, sym);
27
72
  }
28
73
  }
29
- toSet() {
30
- return new Set(this.symbols.keys());
31
- }
32
74
  }
@@ -1,6 +1,6 @@
1
1
  import path from "path";
2
- import pkg from "../../package.json";
3
- import { COLORS } from "./colors";
2
+ import pkg from "../../package.json" with { type: "json" };
3
+ import { COLORS } from "./colors.js";
4
4
  export function parseArgs(rawArgs) {
5
5
  let jsFilePathArg = null;
6
6
  let isRelease = false;
@@ -1,4 +1,4 @@
1
- import { COLORS } from "./colors";
1
+ import { COLORS } from "./colors.js";
2
2
  export class Spinner {
3
3
  frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
4
4
  interval = null;
package/dist/cli.js CHANGED
@@ -2,13 +2,13 @@
2
2
  import { spawn } from "child_process";
3
3
  import fs from "fs/promises";
4
4
  import path from "path";
5
- import pkg from "../package.json";
6
- import { parseArgs } from "./cli-utils/args";
7
- import { COLORS } from "./cli-utils/colors";
8
- import { getLatestMtime } from "./cli-utils/file-utils";
9
- import { Spinner } from "./cli-utils/spinner";
10
- import { Interpreter } from "./index";
11
- const pkgDir = path.dirname(__dirname);
5
+ import pkg from "../package.json" with { type: "json" };
6
+ import { parseArgs } from "./cli-utils/args.js";
7
+ import { COLORS } from "./cli-utils/colors.js";
8
+ import { getLatestMtime } from "./cli-utils/file-utils.js";
9
+ import { Spinner } from "./cli-utils/spinner.js";
10
+ import { Interpreter } from "./index.js";
11
+ const pkgDir = path.dirname(import.meta.dirname);
12
12
  async function main() {
13
13
  const { jsFilePath, isRelease, keepCpp, outputExePath, scriptArgs } = parseArgs(process.argv.slice(2));
14
14
  const jsFileName = path.basename(jsFilePath, ".js");
@@ -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,6 +1,6 @@
1
1
  import ts from "typescript";
2
- import { CodeGenerator } from "./";
3
- import { visitObjectPropertyName } from "./expression-handlers";
2
+ import { visitObjectPropertyName } from "./expression-handlers.js";
3
+ import { CodeGenerator } from "./index.js";
4
4
  export function visitClassDeclaration(node, context) {
5
5
  const className = node.name.getText();
6
6
  // Check extends
@@ -1,6 +1,6 @@
1
1
  import ts from "typescript";
2
- import { DeclaredSymbols, DeclaredSymbolType } from "../../ast/symbols";
3
- import { CodeGenerator } from "./";
2
+ import { DeclarationType, DeclaredSymbols } from "../../ast/symbols.js";
3
+ import { CodeGenerator } from "./index.js";
4
4
  export function visitForStatement(node, context) {
5
5
  const forStmt = node;
6
6
  let code = "";
@@ -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
+ checked: { initialized: true },
48
+ });
49
+ statementContext.localScopeSymbols.add(name, {
50
+ type: declType,
51
+ checked: { 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`;