@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.
- package/README.md +51 -36
- package/dist/analysis/scope.js +7 -0
- package/dist/analysis/typeAnalyzer.js +58 -5
- package/dist/ast/symbols.js +58 -15
- package/dist/cli/args.js +59 -0
- package/dist/cli/colors.js +9 -0
- package/dist/cli/file-utils.js +20 -0
- package/dist/cli/index.js +160 -0
- package/dist/cli/spinner.js +55 -0
- package/dist/cli.js +1 -1
- package/dist/core/codegen/control-flow-handlers.js +139 -51
- package/dist/core/codegen/declaration-handlers.js +45 -12
- package/dist/core/codegen/expression-handlers.js +139 -48
- package/dist/core/codegen/function-handlers.js +53 -59
- package/dist/core/codegen/helpers.js +196 -32
- package/dist/core/codegen/index.js +15 -9
- package/dist/core/codegen/statement-handlers.js +178 -63
- package/dist/core/codegen/visitor.js +22 -2
- package/dist/core/constants.js +16 -0
- package/dist/core/error.js +58 -0
- package/dist/core/parser.js +2 -2
- package/dist/index.js +6 -3
- package/package.json +3 -3
- package/src/prelude/scheduler.hpp +144 -144
- package/src/prelude/utils/access.hpp +2 -2
- package/src/prelude/utils/log_any_value/object.hpp +12 -10
- package/src/prelude/utils/log_any_value/primitives.hpp +7 -0
- package/src/prelude/utils/operators.hpp +4 -6
- package/src/prelude/values/prototypes/function.hpp +18 -0
|
@@ -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 {
|
|
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
|
-
|
|
14
|
+
const conditionContext = {
|
|
15
15
|
...context,
|
|
16
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
378
|
-
const
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
395
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
436
|
-
localScopeSymbols
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|