@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 +1 -1
- package/dist/analysis/typeAnalyzer.js +57 -14
- package/dist/ast/symbols.js +57 -15
- package/dist/cli-utils/args.js +2 -2
- package/dist/cli-utils/spinner.js +1 -1
- package/dist/cli.js +8 -8
- package/dist/core/codegen/class-handlers.js +2 -2
- package/dist/core/codegen/control-flow-handlers.js +140 -52
- package/dist/core/codegen/declaration-handlers.js +38 -13
- package/dist/core/codegen/expression-handlers.js +39 -4
- package/dist/core/codegen/function-handlers.js +42 -59
- package/dist/core/codegen/helpers.js +171 -33
- package/dist/core/codegen/index.js +10 -6
- package/dist/core/codegen/literal-handlers.js +1 -1
- package/dist/core/codegen/statement-handlers.js +81 -38
- package/dist/core/codegen/visitor.js +8 -8
- package/dist/core/parser.js +2 -2
- package/dist/index.js +6 -6
- package/package.json +1 -1
- package/src/prelude/utils/access.hpp +2 -2
- 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
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] ??
|
|
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] ??
|
|
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] ??
|
|
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] ??
|
|
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] ??
|
|
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] ??
|
|
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] ??
|
|
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] ??
|
|
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] ??
|
|
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 &
|
|
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] ??
|
|
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)) &&
|
package/dist/ast/symbols.js
CHANGED
|
@@ -1,32 +1,74 @@
|
|
|
1
|
-
export var
|
|
2
|
-
(function (
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
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
|
}
|
package/dist/cli-utils/args.js
CHANGED
|
@@ -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;
|
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(
|
|
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 {
|
|
3
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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
|
+
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
|
-
|
|
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`;
|