@ugo-studio/jspp 0.1.4 → 0.1.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/dist/analysis/scope.js +17 -0
- package/dist/analysis/typeAnalyzer.js +7 -1
- package/dist/ast/symbols.js +29 -0
- package/dist/ast/types.js +0 -6
- package/dist/cli-utils/args.js +57 -0
- package/dist/cli-utils/colors.js +9 -0
- package/dist/cli-utils/file-utils.js +20 -0
- package/dist/cli-utils/spinner.js +55 -0
- package/dist/cli.js +105 -30
- package/dist/core/codegen/class-handlers.js +10 -6
- package/dist/core/codegen/control-flow-handlers.js +31 -21
- package/dist/core/codegen/declaration-handlers.js +10 -6
- package/dist/core/codegen/expression-handlers.js +202 -60
- package/dist/core/codegen/function-handlers.js +179 -70
- package/dist/core/codegen/helpers.js +107 -17
- package/dist/core/codegen/index.js +9 -8
- package/dist/core/codegen/literal-handlers.js +15 -6
- package/dist/core/codegen/statement-handlers.js +67 -53
- package/dist/core/codegen/visitor.js +3 -1
- package/package.json +1 -1
- package/src/prelude/any_value.hpp +195 -342
- package/src/prelude/any_value_access.hpp +78 -30
- package/src/prelude/any_value_defines.hpp +74 -35
- package/src/prelude/any_value_helpers.hpp +73 -180
- package/src/prelude/exception.hpp +1 -0
- package/src/prelude/exception_helpers.hpp +4 -4
- package/src/prelude/index.hpp +9 -2
- package/src/prelude/library/array.hpp +190 -0
- package/src/prelude/library/console.hpp +6 -5
- package/src/prelude/library/error.hpp +10 -8
- package/src/prelude/library/function.hpp +10 -0
- package/src/prelude/library/global.hpp +20 -0
- package/src/prelude/library/math.hpp +308 -0
- package/src/prelude/library/object.hpp +288 -0
- package/src/prelude/library/performance.hpp +1 -1
- package/src/prelude/library/process.hpp +39 -0
- package/src/prelude/library/promise.hpp +53 -43
- package/src/prelude/library/symbol.hpp +45 -57
- package/src/prelude/library/timer.hpp +6 -6
- package/src/prelude/types.hpp +48 -0
- package/src/prelude/utils/access.hpp +182 -11
- package/src/prelude/utils/assignment_operators.hpp +99 -0
- package/src/prelude/utils/log_any_value/array.hpp +8 -8
- package/src/prelude/utils/log_any_value/function.hpp +6 -4
- package/src/prelude/utils/log_any_value/object.hpp +41 -24
- package/src/prelude/utils/log_any_value/primitives.hpp +3 -1
- package/src/prelude/utils/operators.hpp +750 -274
- package/src/prelude/utils/well_known_symbols.hpp +12 -0
- package/src/prelude/values/array.hpp +8 -6
- package/src/prelude/values/descriptors.hpp +2 -2
- package/src/prelude/values/function.hpp +71 -62
- package/src/prelude/values/helpers/array.hpp +64 -28
- package/src/prelude/values/helpers/function.hpp +77 -92
- package/src/prelude/values/helpers/iterator.hpp +3 -3
- package/src/prelude/values/helpers/object.hpp +54 -9
- package/src/prelude/values/helpers/promise.hpp +3 -3
- package/src/prelude/values/iterator.hpp +1 -1
- package/src/prelude/values/object.hpp +10 -3
- package/src/prelude/values/promise.hpp +3 -3
- package/src/prelude/values/prototypes/array.hpp +851 -12
- package/src/prelude/values/prototypes/function.hpp +2 -2
- package/src/prelude/values/prototypes/iterator.hpp +5 -5
- package/src/prelude/values/prototypes/number.hpp +153 -0
- package/src/prelude/values/prototypes/object.hpp +2 -2
- package/src/prelude/values/prototypes/promise.hpp +40 -30
- package/src/prelude/values/prototypes/string.hpp +28 -28
- package/src/prelude/values/prototypes/symbol.hpp +20 -3
- package/src/prelude/values/shape.hpp +52 -0
package/dist/analysis/scope.js
CHANGED
|
@@ -14,12 +14,17 @@ export const BUILTIN_OBJECTS = new Set([
|
|
|
14
14
|
{ name: "performance", isConst: false },
|
|
15
15
|
{ name: "global", isConst: false },
|
|
16
16
|
{ name: "globalThis", isConst: false },
|
|
17
|
+
{ name: "process", isConst: false },
|
|
17
18
|
{ name: "Error", isConst: false },
|
|
18
19
|
{ name: "Promise", isConst: false },
|
|
20
|
+
{ name: "Function", isConst: false },
|
|
19
21
|
{ name: "setTimeout", isConst: false },
|
|
20
22
|
{ name: "clearTimeout", isConst: false },
|
|
21
23
|
{ name: "setInterval", isConst: false },
|
|
22
24
|
{ name: "clearInterval", isConst: false },
|
|
25
|
+
{ name: "Array", isConst: false },
|
|
26
|
+
{ name: "Object", isConst: false },
|
|
27
|
+
{ name: "Math", isConst: false },
|
|
23
28
|
]);
|
|
24
29
|
// Represents a single scope (e.g., a function body or a block statement)
|
|
25
30
|
export class Scope {
|
|
@@ -79,11 +84,23 @@ export class ScopeManager {
|
|
|
79
84
|
}
|
|
80
85
|
// Defines a variable in the current scope.
|
|
81
86
|
define(name, type) {
|
|
87
|
+
// if (name === "named" || name === "letVal") console.log("Defining", name, "in scope. isBuiltin:", type.isBuiltin, " type:", type.type);
|
|
82
88
|
if (this.reservedKeywords.has(name) && !type.isBuiltin) {
|
|
83
89
|
throw new Error(`SyntaxError: Unexpected reserved word "${name}"`);
|
|
84
90
|
}
|
|
85
91
|
this.currentScope.define(name, type);
|
|
86
92
|
}
|
|
93
|
+
// Defines a `var` variable (hoisted to function or global scope).
|
|
94
|
+
defineVar(name, type) {
|
|
95
|
+
if (this.reservedKeywords.has(name) && !type.isBuiltin) {
|
|
96
|
+
throw new Error(`SyntaxError: Unexpected reserved word "${name}"`);
|
|
97
|
+
}
|
|
98
|
+
let scope = this.currentScope;
|
|
99
|
+
while (scope.parent && scope.ownerFunction === scope.parent.ownerFunction) {
|
|
100
|
+
scope = scope.parent;
|
|
101
|
+
}
|
|
102
|
+
scope.define(name, type);
|
|
103
|
+
}
|
|
87
104
|
// Looks up a variable's type information from the current scope upwards.
|
|
88
105
|
lookup(name) {
|
|
89
106
|
const scope = this.currentScope.findScopeFor(name);
|
|
@@ -362,6 +362,7 @@ export class TypeAnalyzer {
|
|
|
362
362
|
enter: (node) => {
|
|
363
363
|
if (ts.isVariableDeclaration(node)) {
|
|
364
364
|
const name = node.name.getText();
|
|
365
|
+
const isBlockScoped = (node.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
365
366
|
const isConst = (node.parent.flags & ts.NodeFlags.Const) !== 0;
|
|
366
367
|
let type = "auto";
|
|
367
368
|
let needsHeap = false;
|
|
@@ -381,7 +382,12 @@ export class TypeAnalyzer {
|
|
|
381
382
|
isConst,
|
|
382
383
|
needsHeapAllocation: needsHeap,
|
|
383
384
|
};
|
|
384
|
-
|
|
385
|
+
if (isBlockScoped) {
|
|
386
|
+
this.scopeManager.define(name, typeInfo);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
this.scopeManager.defineVar(name, typeInfo);
|
|
390
|
+
}
|
|
385
391
|
}
|
|
386
392
|
},
|
|
387
393
|
},
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export var DeclaredSymbolType;
|
|
2
|
+
(function (DeclaredSymbolType) {
|
|
3
|
+
DeclaredSymbolType["letOrConst"] = "letOrConst";
|
|
4
|
+
DeclaredSymbolType["function"] = "function";
|
|
5
|
+
DeclaredSymbolType["var"] = "var";
|
|
6
|
+
})(DeclaredSymbolType || (DeclaredSymbolType = {}));
|
|
7
|
+
export class DeclaredSymbols {
|
|
8
|
+
symbols;
|
|
9
|
+
constructor(...m) {
|
|
10
|
+
this.symbols = new Map();
|
|
11
|
+
m.forEach((ds) => ds.symbols.forEach((v, k) => this.symbols.set(k, v)));
|
|
12
|
+
}
|
|
13
|
+
has(name) {
|
|
14
|
+
return this.symbols.has(name);
|
|
15
|
+
}
|
|
16
|
+
get(name) {
|
|
17
|
+
return this.symbols.get(name);
|
|
18
|
+
}
|
|
19
|
+
set(name, value) {
|
|
20
|
+
return this.symbols.set(name, value);
|
|
21
|
+
}
|
|
22
|
+
update(name, update) {
|
|
23
|
+
const oldValue = this.get(name);
|
|
24
|
+
if (oldValue) {
|
|
25
|
+
const newValue = { ...oldValue, ...update };
|
|
26
|
+
return this.symbols.set(name, newValue);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/ast/types.js
CHANGED
|
@@ -1,7 +1 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
|
-
export var DeclaredSymbolType;
|
|
3
|
-
(function (DeclaredSymbolType) {
|
|
4
|
-
DeclaredSymbolType["letOrConst"] = "letOrConst";
|
|
5
|
-
DeclaredSymbolType["function"] = "function";
|
|
6
|
-
DeclaredSymbolType["var"] = "var";
|
|
7
|
-
})(DeclaredSymbolType || (DeclaredSymbolType = {}));
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { COLORS } from "./colors";
|
|
3
|
+
export function parseArgs(rawArgs) {
|
|
4
|
+
let jsFilePathArg = null;
|
|
5
|
+
let isRelease = false;
|
|
6
|
+
let keepCpp = false;
|
|
7
|
+
let outputExePath = null;
|
|
8
|
+
let scriptArgs = [];
|
|
9
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
10
|
+
const arg = rawArgs[i];
|
|
11
|
+
if (!arg)
|
|
12
|
+
continue;
|
|
13
|
+
if (arg === "--") {
|
|
14
|
+
scriptArgs = rawArgs.slice(i + 1);
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
if (arg === "--release") {
|
|
18
|
+
isRelease = true;
|
|
19
|
+
}
|
|
20
|
+
else if (arg === "--keep-cpp") {
|
|
21
|
+
keepCpp = true;
|
|
22
|
+
}
|
|
23
|
+
else if (arg === "-o" || arg === "--output") {
|
|
24
|
+
if (i + 1 < rawArgs.length) {
|
|
25
|
+
outputExePath = rawArgs[i + 1] ?? null;
|
|
26
|
+
i++;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.error(`${COLORS.red}Error: --output requires a file path argument.${COLORS.reset}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (arg.startsWith("-")) {
|
|
34
|
+
console.warn(`${COLORS.yellow}Warning: Unknown argument '${arg}'${COLORS.reset}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
if (jsFilePathArg) {
|
|
38
|
+
console.error(`${COLORS.red}Error: Multiple input files specified.${COLORS.reset}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
jsFilePathArg = arg;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!jsFilePathArg) {
|
|
45
|
+
console.log(`${COLORS.bold}Usage:${COLORS.reset} jspp <path-to-js-file> [--release] [--keep-cpp] [-o <output-path>] [-- <args...>]`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
jsFilePath: path.resolve(process.cwd(), jsFilePathArg),
|
|
50
|
+
isRelease,
|
|
51
|
+
keepCpp,
|
|
52
|
+
outputExePath: outputExePath
|
|
53
|
+
? path.resolve(process.cwd(), outputExePath)
|
|
54
|
+
: null,
|
|
55
|
+
scriptArgs,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export async function getLatestMtime(dirPath) {
|
|
4
|
+
let maxMtime = 0;
|
|
5
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
6
|
+
for (const entry of entries) {
|
|
7
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
8
|
+
if (entry.isDirectory()) {
|
|
9
|
+
const nestedMtime = await getLatestMtime(fullPath);
|
|
10
|
+
if (nestedMtime > maxMtime)
|
|
11
|
+
maxMtime = nestedMtime;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
const stats = await fs.stat(fullPath);
|
|
15
|
+
if (stats.mtimeMs > maxMtime)
|
|
16
|
+
maxMtime = stats.mtimeMs;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return maxMtime;
|
|
20
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { COLORS } from "./colors";
|
|
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
|
@@ -1,63 +1,138 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import pkg from "../package.json";
|
|
5
|
+
import { parseArgs } from "./cli-utils/args";
|
|
6
|
+
import { COLORS } from "./cli-utils/colors";
|
|
7
|
+
import { getLatestMtime } from "./cli-utils/file-utils";
|
|
8
|
+
import { Spinner } from "./cli-utils/spinner";
|
|
4
9
|
import { Interpreter } from "./index";
|
|
5
10
|
async function main() {
|
|
6
|
-
const
|
|
7
|
-
if (args.length === 0) {
|
|
8
|
-
console.log("Usage: jspp <path-to-js-file>");
|
|
9
|
-
process.exit(1);
|
|
10
|
-
}
|
|
11
|
-
const jsFilePath = path.resolve(process.cwd(), args[0]);
|
|
11
|
+
const { jsFilePath, isRelease, keepCpp, outputExePath, scriptArgs } = parseArgs(process.argv.slice(2));
|
|
12
12
|
const jsFileName = path.basename(jsFilePath, ".js");
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const
|
|
13
|
+
const sourceDir = path.dirname(jsFilePath);
|
|
14
|
+
// Intermediate C++ file goes alongside the source JS file
|
|
15
|
+
const cppFilePath = path.join(sourceDir, `${jsFileName}.cpp`);
|
|
16
|
+
// Determine output executable path
|
|
17
|
+
let exeFilePath;
|
|
18
|
+
if (outputExePath) {
|
|
19
|
+
exeFilePath = outputExePath;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
23
|
+
exeFilePath = path.join(sourceDir, `${jsFileName}${ext}`);
|
|
24
|
+
}
|
|
25
|
+
// Mode Configuration
|
|
26
|
+
const mode = isRelease ? "release" : "debug";
|
|
27
|
+
console.log(`${COLORS.bold}JSPP Compiler${COLORS.reset} ${COLORS.dim}v${pkg.version}${COLORS.reset}`);
|
|
28
|
+
console.log(`Mode: ${isRelease ? COLORS.green : COLORS.yellow}${mode.toUpperCase()}${COLORS.reset}\n`);
|
|
29
|
+
const flags = isRelease
|
|
30
|
+
? ["-O3", "-DNDEBUG", "-Wa,-mbig-obj"]
|
|
31
|
+
: ["-O0", "-Wa,-mbig-obj"];
|
|
32
|
+
const pchDir = path.resolve(process.cwd(), "prelude-build", mode);
|
|
33
|
+
const spinner = new Spinner("Initializing...");
|
|
16
34
|
try {
|
|
35
|
+
spinner.start();
|
|
36
|
+
// 1. Interpreter Phase
|
|
37
|
+
spinner.update(`Reading ${path.basename(jsFilePath)}...`);
|
|
17
38
|
const jsCode = await fs.readFile(jsFilePath, "utf-8");
|
|
39
|
+
spinner.update("Transpiling to C++...");
|
|
18
40
|
const interpreter = new Interpreter();
|
|
19
41
|
const { cppCode, preludePath } = interpreter.interpret(jsCode);
|
|
20
|
-
|
|
21
|
-
await fs.mkdir(
|
|
42
|
+
// Ensure directory for cpp file exists (should exist as it's source dir, but for safety if we change logic)
|
|
43
|
+
await fs.mkdir(path.dirname(cppFilePath), { recursive: true });
|
|
22
44
|
await fs.writeFile(cppFilePath, cppCode);
|
|
23
|
-
|
|
24
|
-
|
|
45
|
+
spinner.succeed(`Generated ${COLORS.dim}${path.basename(cppFilePath)}${COLORS.reset}`);
|
|
46
|
+
// 2. Precompiled Header Check
|
|
47
|
+
spinner.text = "Checking precompiled headers...";
|
|
48
|
+
spinner.start();
|
|
49
|
+
const pchFile = path.join(pchDir, "index.hpp.gch");
|
|
50
|
+
let shouldRebuild = false;
|
|
51
|
+
try {
|
|
52
|
+
const pchStats = await fs.stat(pchFile);
|
|
53
|
+
const sourceMtime = await getLatestMtime(preludePath);
|
|
54
|
+
if (sourceMtime > pchStats.mtimeMs) {
|
|
55
|
+
shouldRebuild = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
shouldRebuild = true;
|
|
60
|
+
}
|
|
61
|
+
if (shouldRebuild) {
|
|
62
|
+
spinner.update("Rebuilding precompiled headers (this may take a while)...");
|
|
63
|
+
// Use spawn (async) instead of spawnSync to keep spinner alive
|
|
64
|
+
const rebuild = Bun.spawn({
|
|
65
|
+
cmd: ["bun", "run", "scripts/precompile-headers.ts"],
|
|
66
|
+
stdout: "pipe", // pipe to hide output unless error, or handle differently
|
|
67
|
+
stderr: "pipe",
|
|
68
|
+
});
|
|
69
|
+
const exitCode = await rebuild.exited;
|
|
70
|
+
if (exitCode !== 0) {
|
|
71
|
+
const stderr = await new Response(rebuild.stderr).text();
|
|
72
|
+
spinner.fail("Failed to rebuild precompiled headers");
|
|
73
|
+
console.error(stderr);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
spinner.succeed("Precompiled headers updated");
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
spinner.succeed("Precompiled headers up-to-date");
|
|
80
|
+
}
|
|
81
|
+
// 3. Compilation Phase
|
|
82
|
+
spinner.text = `Compiling binary...`;
|
|
83
|
+
spinner.start();
|
|
84
|
+
// Ensure output directory exists
|
|
85
|
+
await fs.mkdir(path.dirname(exeFilePath), { recursive: true });
|
|
86
|
+
const compile = Bun.spawn({
|
|
25
87
|
cmd: [
|
|
26
88
|
"g++",
|
|
27
89
|
"-std=c++23",
|
|
90
|
+
...flags,
|
|
28
91
|
cppFilePath,
|
|
29
92
|
"-o",
|
|
30
93
|
exeFilePath,
|
|
31
94
|
"-I",
|
|
95
|
+
pchDir,
|
|
96
|
+
"-I",
|
|
32
97
|
preludePath,
|
|
33
|
-
"-O3",
|
|
34
|
-
"-DNDEBUG",
|
|
35
|
-
// "-include",
|
|
36
|
-
// path.join(process.cwd(), "prelude-build", "index.hpp"),
|
|
37
98
|
],
|
|
38
|
-
stdout: "
|
|
39
|
-
stderr: "
|
|
99
|
+
stdout: "pipe",
|
|
100
|
+
stderr: "pipe",
|
|
40
101
|
});
|
|
41
|
-
|
|
42
|
-
|
|
102
|
+
const compileExitCode = await compile.exited;
|
|
103
|
+
if (compileExitCode !== 0) {
|
|
104
|
+
const stderr = await new Response(compile.stderr).text();
|
|
105
|
+
spinner.fail(`Compilation failed`);
|
|
106
|
+
console.error(stderr);
|
|
43
107
|
process.exit(1);
|
|
44
108
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
109
|
+
spinner.succeed(`Compiled to ${COLORS.green}${COLORS.bold}${path.basename(exeFilePath)}${COLORS.reset}`);
|
|
110
|
+
// Clean up C++ file if not requested to keep
|
|
111
|
+
if (!keepCpp) {
|
|
112
|
+
try {
|
|
113
|
+
await fs.unlink(cppFilePath);
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
// Ignore error if file cannot be deleted
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// 4. Execution Phase
|
|
120
|
+
console.log(`\n${COLORS.cyan}--- Running Output ---${COLORS.reset}`);
|
|
121
|
+
const run = Bun.spawn({
|
|
122
|
+
cmd: [exeFilePath, ...scriptArgs],
|
|
49
123
|
stdout: "inherit",
|
|
50
124
|
stderr: "inherit",
|
|
51
125
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
126
|
+
const runExitCode = await run.exited;
|
|
127
|
+
console.log(`${COLORS.cyan}----------------------${COLORS.reset}\n`);
|
|
128
|
+
if (runExitCode !== 0) {
|
|
129
|
+
console.error(`${COLORS.red}Execution failed with exit code ${runExitCode}${COLORS.reset}`);
|
|
55
130
|
process.exit(1);
|
|
56
131
|
}
|
|
57
|
-
console.log(`Successfully ran ${exeFilePath}`);
|
|
58
132
|
}
|
|
59
133
|
catch (error) {
|
|
60
|
-
|
|
134
|
+
spinner.fail("An unexpected error occurred");
|
|
135
|
+
console.error(error);
|
|
61
136
|
process.exit(1);
|
|
62
137
|
}
|
|
63
138
|
}
|
|
@@ -20,7 +20,7 @@ export function visitClassDeclaration(node, context) {
|
|
|
20
20
|
const scope = this.getScopeForNode(extendsExpr);
|
|
21
21
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(extendsExpr.getText(), scope);
|
|
22
22
|
if (typeInfo) {
|
|
23
|
-
parentName = this.getDerefCode(parentName, this.getJsVarName(extendsExpr), typeInfo);
|
|
23
|
+
parentName = this.getDerefCode(parentName, this.getJsVarName(extendsExpr), context, typeInfo);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -42,16 +42,16 @@ export function visitClassDeclaration(node, context) {
|
|
|
42
42
|
// Default constructor
|
|
43
43
|
if (parentName) {
|
|
44
44
|
constructorLambda =
|
|
45
|
-
`jspp::AnyValue::make_class([=](const jspp::AnyValue& ${this.globalThisVar},
|
|
45
|
+
`jspp::AnyValue::make_class([=](const jspp::AnyValue& ${this.globalThisVar}, std::span<const jspp::AnyValue> args) mutable -> jspp::AnyValue {
|
|
46
46
|
auto __parent = ${parentName};
|
|
47
|
-
__parent.
|
|
48
|
-
return jspp::
|
|
47
|
+
__parent.call(${this.globalThisVar}, args, "super");
|
|
48
|
+
return jspp::Constants::UNDEFINED;
|
|
49
49
|
}, "${className}")`;
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
52
|
constructorLambda =
|
|
53
|
-
`jspp::AnyValue::make_class([=](const jspp::AnyValue& ${this.globalThisVar},
|
|
54
|
-
return jspp::
|
|
53
|
+
`jspp::AnyValue::make_class([=](const jspp::AnyValue& ${this.globalThisVar}, std::span<const jspp::AnyValue> args) mutable -> jspp::AnyValue {
|
|
54
|
+
return jspp::Constants::UNDEFINED;
|
|
55
55
|
}, "${className}")`;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -63,6 +63,10 @@ export function visitClassDeclaration(node, context) {
|
|
|
63
63
|
code +=
|
|
64
64
|
`${this.indent()}(*${className}).get_own_property("prototype").set_prototype(${parentName}.get_own_property("prototype"));\n`;
|
|
65
65
|
}
|
|
66
|
+
else {
|
|
67
|
+
code +=
|
|
68
|
+
`${this.indent()}(*${className}).get_own_property("prototype").set_prototype(::Object.get_own_property("prototype"));\n`;
|
|
69
|
+
}
|
|
66
70
|
// Members
|
|
67
71
|
for (const member of node.members) {
|
|
68
72
|
if (ts.isMethodDeclaration(member)) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { DeclaredSymbols, DeclaredSymbolType } from "../../ast/symbols";
|
|
2
3
|
import { CodeGenerator } from "./";
|
|
3
4
|
export function visitForStatement(node, context) {
|
|
4
5
|
const forStmt = node;
|
|
@@ -10,6 +11,10 @@ export function visitForStatement(node, context) {
|
|
|
10
11
|
this.indentationLevel++; // Enter a new scope for the for loop
|
|
11
12
|
// Handle initializer
|
|
12
13
|
let initializerCode = "";
|
|
14
|
+
let conditionContext = {
|
|
15
|
+
...context,
|
|
16
|
+
topLevelScopeSymbols: this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.localScopeSymbols),
|
|
17
|
+
};
|
|
13
18
|
if (forStmt.initializer) {
|
|
14
19
|
if (ts.isVariableDeclarationList(forStmt.initializer)) {
|
|
15
20
|
const varDeclList = forStmt.initializer;
|
|
@@ -22,10 +27,15 @@ export function visitForStatement(node, context) {
|
|
|
22
27
|
const name = decl.name.getText();
|
|
23
28
|
const initValue = decl.initializer
|
|
24
29
|
? this.visit(decl.initializer, context)
|
|
25
|
-
: "jspp::
|
|
30
|
+
: "jspp::Constants::UNDEFINED";
|
|
26
31
|
const scope = this.getScopeForNode(decl);
|
|
27
32
|
const typeInfo = this.typeAnalyzer.scopeManager
|
|
28
33
|
.lookupFromScope(name, scope);
|
|
34
|
+
conditionContext.localScopeSymbols = new DeclaredSymbols();
|
|
35
|
+
conditionContext.localScopeSymbols.set(name, {
|
|
36
|
+
type: DeclaredSymbolType.letOrConst,
|
|
37
|
+
checkedIfUninitialized: true,
|
|
38
|
+
});
|
|
29
39
|
if (typeInfo.needsHeapAllocation) {
|
|
30
40
|
initializerCode =
|
|
31
41
|
`auto ${name} = std::make_shared<jspp::AnyValue>(${initValue})`;
|
|
@@ -49,7 +59,7 @@ export function visitForStatement(node, context) {
|
|
|
49
59
|
}
|
|
50
60
|
code += `${this.indent()}for (${initializerCode}; `;
|
|
51
61
|
if (forStmt.condition) {
|
|
52
|
-
code += `(${this.visit(forStmt.condition,
|
|
62
|
+
code += `is_truthy(${this.visit(forStmt.condition, conditionContext)})`;
|
|
53
63
|
}
|
|
54
64
|
code += "; ";
|
|
55
65
|
if (forStmt.incrementor) {
|
|
@@ -105,12 +115,12 @@ export function visitForInStatement(node, context) {
|
|
|
105
115
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(varName, scope);
|
|
106
116
|
if (typeInfo.needsHeapAllocation) {
|
|
107
117
|
code +=
|
|
108
|
-
`${this.indent()}auto ${varName} = std::make_shared<jspp::AnyValue>(jspp::
|
|
118
|
+
`${this.indent()}auto ${varName} = std::make_shared<jspp::AnyValue>(jspp::Constants::UNDEFINED);\n`;
|
|
109
119
|
assignmentTarget = `*${varName}`;
|
|
110
120
|
}
|
|
111
121
|
else {
|
|
112
122
|
code +=
|
|
113
|
-
`${this.indent()}jspp::AnyValue ${varName} = jspp::
|
|
123
|
+
`${this.indent()}jspp::AnyValue ${varName} = jspp::Constants::UNDEFINED;\n`;
|
|
114
124
|
assignmentTarget = varName;
|
|
115
125
|
}
|
|
116
126
|
}
|
|
@@ -129,7 +139,7 @@ export function visitForInStatement(node, context) {
|
|
|
129
139
|
if (ts.isIdentifier(expr)) {
|
|
130
140
|
const scope = this.getScopeForNode(expr);
|
|
131
141
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(expr.getText(), scope);
|
|
132
|
-
derefExpr = this.getDerefCode(exprText, this.getJsVarName(expr), typeInfo);
|
|
142
|
+
derefExpr = this.getDerefCode(exprText, this.getJsVarName(expr), context, typeInfo);
|
|
133
143
|
}
|
|
134
144
|
const keysVar = this.generateUniqueName("__keys_", new Set([varName]));
|
|
135
145
|
code +=
|
|
@@ -177,12 +187,12 @@ export function visitForOfStatement(node, context) {
|
|
|
177
187
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(elemName, scope);
|
|
178
188
|
if (typeInfo.needsHeapAllocation) {
|
|
179
189
|
code +=
|
|
180
|
-
`${this.indent()}auto ${elemName} = std::make_shared<jspp::AnyValue>(jspp::
|
|
190
|
+
`${this.indent()}auto ${elemName} = std::make_shared<jspp::AnyValue>(jspp::Constants::UNDEFINED);\n`;
|
|
181
191
|
assignmentTarget = `*${elemName}`;
|
|
182
192
|
}
|
|
183
193
|
else {
|
|
184
194
|
code +=
|
|
185
|
-
`${this.indent()}jspp::AnyValue ${elemName} = jspp::
|
|
195
|
+
`${this.indent()}jspp::AnyValue ${elemName} = jspp::Constants::UNDEFINED;\n`;
|
|
186
196
|
assignmentTarget = elemName;
|
|
187
197
|
}
|
|
188
198
|
}
|
|
@@ -201,7 +211,7 @@ export function visitForOfStatement(node, context) {
|
|
|
201
211
|
const scope = this.getScopeForNode(forOf.expression);
|
|
202
212
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(forOf.expression.getText(), scope);
|
|
203
213
|
const varName = this.getJsVarName(forOf.expression);
|
|
204
|
-
derefIterable = this.getDerefCode(iterableExpr, varName, typeInfo);
|
|
214
|
+
derefIterable = this.getDerefCode(iterableExpr, varName, context, typeInfo);
|
|
205
215
|
}
|
|
206
216
|
const declaredSymbols = this.getDeclaredSymbols(forOf.statement);
|
|
207
217
|
const iterableRef = this.generateUniqueName("__iter_ref", declaredSymbols);
|
|
@@ -213,11 +223,11 @@ export function visitForOfStatement(node, context) {
|
|
|
213
223
|
code +=
|
|
214
224
|
`${this.indent()}auto ${iterator} = jspp::Access::get_object_value_iterator(${iterableRef}, ${varName});\n`;
|
|
215
225
|
code +=
|
|
216
|
-
`${this.indent()}auto ${nextFunc} = ${iterator}.get_own_property("next")
|
|
226
|
+
`${this.indent()}auto ${nextFunc} = ${iterator}.get_own_property("next");\n`;
|
|
217
227
|
code +=
|
|
218
|
-
`${this.indent()}auto ${nextRes} = ${nextFunc}
|
|
228
|
+
`${this.indent()}auto ${nextRes} = ${nextFunc}.call(${iterator}, {}, "next");\n`;
|
|
219
229
|
code +=
|
|
220
|
-
`${this.indent()}while (
|
|
230
|
+
`${this.indent()}while (!is_truthy(${nextRes}.get_own_property("done"))) {\n`;
|
|
221
231
|
this.indentationLevel++;
|
|
222
232
|
code +=
|
|
223
233
|
`${this.indent()}${assignmentTarget} = ${nextRes}.get_own_property("value");\n`;
|
|
@@ -230,7 +240,7 @@ export function visitForOfStatement(node, context) {
|
|
|
230
240
|
code += `${this.indent()}${context.currentLabel}_continue:;\n`;
|
|
231
241
|
}
|
|
232
242
|
code +=
|
|
233
|
-
`${this.indent()}${nextRes} = ${nextFunc}
|
|
243
|
+
`${this.indent()}${nextRes} = ${nextFunc}.call(${iterator}, {}, "next");\n`;
|
|
234
244
|
this.indentationLevel--;
|
|
235
245
|
code += `${this.indent()}}\n`;
|
|
236
246
|
this.indentationLevel--; // Exit the scope for the for-of loop
|
|
@@ -248,7 +258,7 @@ export function visitWhileStatement(node, context) {
|
|
|
248
258
|
const conditionText = condition.kind === ts.SyntaxKind.TrueKeyword ||
|
|
249
259
|
condition.kind === ts.SyntaxKind.FalseKeyword
|
|
250
260
|
? condition.getText()
|
|
251
|
-
: `(${this.visit(condition, context)})
|
|
261
|
+
: `is_truthy(${this.visit(condition, context)})`;
|
|
252
262
|
let code = "";
|
|
253
263
|
if (context.currentLabel) {
|
|
254
264
|
code += `${this.indent()}${context.currentLabel}: {\n`;
|
|
@@ -288,7 +298,7 @@ export function visitWhileStatement(node, context) {
|
|
|
288
298
|
}
|
|
289
299
|
export function visitDoStatement(node, context) {
|
|
290
300
|
const condition = node.expression;
|
|
291
|
-
const conditionText = `(${this.visit(condition, context)})
|
|
301
|
+
const conditionText = `is_truthy(${this.visit(condition, context)})`;
|
|
292
302
|
let code = "";
|
|
293
303
|
if (context.currentLabel) {
|
|
294
304
|
code += `${this.indent()}${context.currentLabel}: {\n`;
|
|
@@ -346,7 +356,7 @@ export function visitSwitchStatement(node, context) {
|
|
|
346
356
|
`${this.indent()}const jspp::AnyValue ${switchValueVar} = ${expressionCode};\n`;
|
|
347
357
|
code += `${this.indent()}bool ${fallthroughVar} = false;\n`;
|
|
348
358
|
// Hoist variable declarations
|
|
349
|
-
const hoistedSymbols = new
|
|
359
|
+
const hoistedSymbols = new DeclaredSymbols();
|
|
350
360
|
for (const clause of switchStmt.caseBlock.clauses) {
|
|
351
361
|
if (ts.isCaseClause(clause) || ts.isDefaultClause(clause)) {
|
|
352
362
|
for (const stmt of clause.statements) {
|
|
@@ -363,7 +373,7 @@ export function visitSwitchStatement(node, context) {
|
|
|
363
373
|
}
|
|
364
374
|
}
|
|
365
375
|
// Prepare scope symbols for the switch block
|
|
366
|
-
const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.
|
|
376
|
+
const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.localScopeSymbols);
|
|
367
377
|
let firstIf = true;
|
|
368
378
|
for (const clause of switchStmt.caseBlock.clauses) {
|
|
369
379
|
if (ts.isCaseClause(clause)) {
|
|
@@ -374,13 +384,13 @@ export function visitSwitchStatement(node, context) {
|
|
|
374
384
|
let condition = "";
|
|
375
385
|
if (firstIf) {
|
|
376
386
|
condition =
|
|
377
|
-
`(${fallthroughVar} || ${switchValueVar}
|
|
387
|
+
`(${fallthroughVar} || jspp::is_strictly_equal_to_primitive(${switchValueVar}, ${caseExprCode}))`;
|
|
378
388
|
code += `${this.indent()}if ${condition} {\n`;
|
|
379
389
|
firstIf = false;
|
|
380
390
|
}
|
|
381
391
|
else {
|
|
382
392
|
condition =
|
|
383
|
-
`(${fallthroughVar} || ${switchValueVar}
|
|
393
|
+
`(${fallthroughVar} || jspp::is_strictly_equal_to_primitive(${switchValueVar}, ${caseExprCode}))`;
|
|
384
394
|
code += `${this.indent()}if ${condition} {\n`;
|
|
385
395
|
}
|
|
386
396
|
this.indentationLevel++;
|
|
@@ -392,7 +402,7 @@ export function visitSwitchStatement(node, context) {
|
|
|
392
402
|
const contextForFunction = {
|
|
393
403
|
...context,
|
|
394
404
|
topLevelScopeSymbols,
|
|
395
|
-
|
|
405
|
+
localScopeSymbols: hoistedSymbols,
|
|
396
406
|
};
|
|
397
407
|
const lambda = this.generateLambda(stmt, contextForFunction, { isAssignment: true });
|
|
398
408
|
code += `${this.indent()}*${funcName} = ${lambda};\n`;
|
|
@@ -404,7 +414,7 @@ export function visitSwitchStatement(node, context) {
|
|
|
404
414
|
switchBreakLabel,
|
|
405
415
|
currentLabel: undefined, // Clear currentLabel for nested visits
|
|
406
416
|
topLevelScopeSymbols,
|
|
407
|
-
|
|
417
|
+
localScopeSymbols: hoistedSymbols,
|
|
408
418
|
derefBeforeAssignment: true,
|
|
409
419
|
isAssignmentOnly: ts.isVariableStatement(stmt),
|
|
410
420
|
});
|
|
@@ -431,7 +441,7 @@ export function visitSwitchStatement(node, context) {
|
|
|
431
441
|
switchBreakLabel,
|
|
432
442
|
currentLabel: undefined, // Clear currentLabel for nested visits
|
|
433
443
|
topLevelScopeSymbols,
|
|
434
|
-
|
|
444
|
+
localScopeSymbols: hoistedSymbols,
|
|
435
445
|
derefBeforeAssignment: true,
|
|
436
446
|
isAssignmentOnly: ts.isVariableStatement(stmt),
|
|
437
447
|
});
|