@ugo-studio/jspp 0.1.3 → 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/README.md +2 -2
- package/dist/analysis/scope.js +33 -4
- package/dist/analysis/typeAnalyzer.js +260 -21
- package/dist/ast/symbols.js +29 -0
- 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 -31
- package/dist/core/codegen/class-handlers.js +131 -0
- package/dist/core/codegen/control-flow-handlers.js +474 -0
- package/dist/core/codegen/declaration-handlers.js +36 -15
- package/dist/core/codegen/expression-handlers.js +579 -125
- package/dist/core/codegen/function-handlers.js +222 -37
- package/dist/core/codegen/helpers.js +158 -4
- package/dist/core/codegen/index.js +20 -8
- package/dist/core/codegen/literal-handlers.js +18 -6
- package/dist/core/codegen/statement-handlers.js +171 -228
- package/dist/core/codegen/visitor.js +31 -3
- package/package.json +3 -3
- package/src/prelude/any_value.hpp +510 -633
- package/src/prelude/any_value_access.hpp +151 -0
- package/src/prelude/any_value_defines.hpp +190 -0
- package/src/prelude/any_value_helpers.hpp +139 -225
- package/src/prelude/exception.hpp +32 -0
- package/src/prelude/exception_helpers.hpp +49 -0
- package/src/prelude/index.hpp +25 -9
- package/src/prelude/library/array.hpp +190 -0
- package/src/prelude/library/console.hpp +14 -13
- package/src/prelude/library/error.hpp +113 -0
- package/src/prelude/library/function.hpp +10 -0
- package/src/prelude/library/global.hpp +35 -4
- package/src/prelude/library/math.hpp +308 -0
- package/src/prelude/library/object.hpp +288 -0
- package/src/prelude/library/performance.hpp +2 -2
- package/src/prelude/library/process.hpp +39 -0
- package/src/prelude/library/promise.hpp +131 -0
- package/src/prelude/library/symbol.hpp +46 -59
- package/src/prelude/library/timer.hpp +92 -0
- package/src/prelude/scheduler.hpp +145 -0
- package/src/prelude/types.hpp +58 -1
- package/src/prelude/utils/access.hpp +345 -0
- package/src/prelude/utils/assignment_operators.hpp +99 -0
- package/src/prelude/utils/log_any_value/array.hpp +245 -0
- package/src/prelude/utils/log_any_value/config.hpp +32 -0
- package/src/prelude/utils/log_any_value/function.hpp +39 -0
- package/src/prelude/utils/log_any_value/fwd.hpp +15 -0
- package/src/prelude/utils/log_any_value/helpers.hpp +62 -0
- package/src/prelude/utils/log_any_value/log_any_value.hpp +94 -0
- package/src/prelude/utils/log_any_value/object.hpp +136 -0
- package/src/prelude/utils/log_any_value/primitives.hpp +43 -0
- package/src/prelude/utils/operators.hpp +751 -0
- package/src/prelude/utils/well_known_symbols.hpp +25 -0
- package/src/prelude/values/array.hpp +10 -7
- package/src/prelude/{descriptors.hpp → values/descriptors.hpp} +2 -2
- package/src/prelude/values/function.hpp +85 -51
- package/src/prelude/values/helpers/array.hpp +80 -35
- package/src/prelude/values/helpers/function.hpp +110 -77
- package/src/prelude/values/helpers/iterator.hpp +16 -10
- package/src/prelude/values/helpers/object.hpp +85 -10
- package/src/prelude/values/helpers/promise.hpp +181 -0
- package/src/prelude/values/helpers/string.hpp +3 -3
- package/src/prelude/values/helpers/symbol.hpp +2 -2
- package/src/prelude/values/iterator.hpp +14 -6
- package/src/prelude/values/object.hpp +14 -3
- package/src/prelude/values/promise.hpp +73 -0
- package/src/prelude/values/prototypes/array.hpp +855 -16
- package/src/prelude/values/prototypes/function.hpp +4 -4
- package/src/prelude/values/prototypes/iterator.hpp +11 -10
- package/src/prelude/values/prototypes/number.hpp +153 -0
- package/src/prelude/values/prototypes/object.hpp +26 -0
- package/src/prelude/values/prototypes/promise.hpp +134 -0
- package/src/prelude/values/prototypes/string.hpp +29 -29
- package/src/prelude/values/prototypes/symbol.hpp +22 -3
- package/src/prelude/values/shape.hpp +52 -0
- package/src/prelude/values/string.hpp +1 -1
- package/src/prelude/values/symbol.hpp +1 -1
- package/src/prelude/access.hpp +0 -91
- package/src/prelude/error.hpp +0 -31
- package/src/prelude/error_helpers.hpp +0 -59
- package/src/prelude/log_string.hpp +0 -407
- package/src/prelude/operators.hpp +0 -256
- package/src/prelude/well_known_symbols.hpp +0 -14
package/dist/cli.js
CHANGED
|
@@ -1,64 +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
|
-
console.time(`Generated C++ code ${cppFilePath}...`);
|
|
20
41
|
const { cppCode, preludePath } = interpreter.interpret(jsCode);
|
|
21
|
-
|
|
22
|
-
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 });
|
|
23
44
|
await fs.writeFile(cppFilePath, cppCode);
|
|
24
|
-
|
|
25
|
-
|
|
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({
|
|
26
87
|
cmd: [
|
|
27
88
|
"g++",
|
|
28
89
|
"-std=c++23",
|
|
90
|
+
...flags,
|
|
29
91
|
cppFilePath,
|
|
30
92
|
"-o",
|
|
31
93
|
exeFilePath,
|
|
32
94
|
"-I",
|
|
95
|
+
pchDir,
|
|
96
|
+
"-I",
|
|
33
97
|
preludePath,
|
|
34
|
-
"-O3",
|
|
35
|
-
"-DNDEBUG",
|
|
36
|
-
// "-include",
|
|
37
|
-
// path.join(process.cwd(), "prelude-build", "index.hpp"),
|
|
38
98
|
],
|
|
39
|
-
stdout: "
|
|
40
|
-
stderr: "
|
|
99
|
+
stdout: "pipe",
|
|
100
|
+
stderr: "pipe",
|
|
41
101
|
});
|
|
42
|
-
|
|
43
|
-
|
|
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);
|
|
44
107
|
process.exit(1);
|
|
45
108
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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],
|
|
50
123
|
stdout: "inherit",
|
|
51
124
|
stderr: "inherit",
|
|
52
125
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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}`);
|
|
56
130
|
process.exit(1);
|
|
57
131
|
}
|
|
58
|
-
console.log(`Successfully ran ${exeFilePath}`);
|
|
59
132
|
}
|
|
60
133
|
catch (error) {
|
|
61
|
-
|
|
134
|
+
spinner.fail("An unexpected error occurred");
|
|
135
|
+
console.error(error);
|
|
62
136
|
process.exit(1);
|
|
63
137
|
}
|
|
64
138
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { CodeGenerator } from "./";
|
|
3
|
+
import { visitObjectPropertyName } from "./expression-handlers";
|
|
4
|
+
export function visitClassDeclaration(node, context) {
|
|
5
|
+
const className = node.name.getText();
|
|
6
|
+
// Check extends
|
|
7
|
+
let extendsExpr = null;
|
|
8
|
+
if (node.heritageClauses) {
|
|
9
|
+
for (const clause of node.heritageClauses) {
|
|
10
|
+
if (clause.token === ts.SyntaxKind.ExtendsKeyword &&
|
|
11
|
+
clause.types.length > 0) {
|
|
12
|
+
extendsExpr = clause.types[0].expression;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
let parentName = "";
|
|
17
|
+
if (extendsExpr) {
|
|
18
|
+
parentName = this.visit(extendsExpr, context);
|
|
19
|
+
if (ts.isIdentifier(extendsExpr)) {
|
|
20
|
+
const scope = this.getScopeForNode(extendsExpr);
|
|
21
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(extendsExpr.getText(), scope);
|
|
22
|
+
if (typeInfo) {
|
|
23
|
+
parentName = this.getDerefCode(parentName, this.getJsVarName(extendsExpr), context, typeInfo);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const classContext = {
|
|
28
|
+
...context,
|
|
29
|
+
superClassVar: parentName,
|
|
30
|
+
};
|
|
31
|
+
// 1. Constructor
|
|
32
|
+
const constructor = node.members.find(ts.isConstructorDeclaration);
|
|
33
|
+
let constructorLambda = "";
|
|
34
|
+
if (constructor) {
|
|
35
|
+
constructorLambda = this.generateLambda(constructor, {
|
|
36
|
+
...classContext,
|
|
37
|
+
isInsideFunction: true,
|
|
38
|
+
lambdaName: className,
|
|
39
|
+
}, { isClass: true });
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Default constructor
|
|
43
|
+
if (parentName) {
|
|
44
|
+
constructorLambda =
|
|
45
|
+
`jspp::AnyValue::make_class([=](const jspp::AnyValue& ${this.globalThisVar}, std::span<const jspp::AnyValue> args) mutable -> jspp::AnyValue {
|
|
46
|
+
auto __parent = ${parentName};
|
|
47
|
+
__parent.call(${this.globalThisVar}, args, "super");
|
|
48
|
+
return jspp::Constants::UNDEFINED;
|
|
49
|
+
}, "${className}")`;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
constructorLambda =
|
|
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
|
+
}, "${className}")`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
let code = `${this.indent()}*${className} = ${constructorLambda};\n`;
|
|
59
|
+
// Set prototype of class (static inheritance) and prototype object (instance inheritance)
|
|
60
|
+
if (parentName) {
|
|
61
|
+
code +=
|
|
62
|
+
`${this.indent()}(*${className}).set_prototype(${parentName});\n`;
|
|
63
|
+
code +=
|
|
64
|
+
`${this.indent()}(*${className}).get_own_property("prototype").set_prototype(${parentName}.get_own_property("prototype"));\n`;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
code +=
|
|
68
|
+
`${this.indent()}(*${className}).get_own_property("prototype").set_prototype(::Object.get_own_property("prototype"));\n`;
|
|
69
|
+
}
|
|
70
|
+
// Members
|
|
71
|
+
for (const member of node.members) {
|
|
72
|
+
if (ts.isMethodDeclaration(member)) {
|
|
73
|
+
const methodName = visitObjectPropertyName.call(this, member.name, {
|
|
74
|
+
...context,
|
|
75
|
+
isObjectLiteralExpression: true, // Reuse this flag to handle computed properties
|
|
76
|
+
});
|
|
77
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
78
|
+
const methodLambda = this.generateLambda(member, {
|
|
79
|
+
...classContext,
|
|
80
|
+
isInsideFunction: true,
|
|
81
|
+
});
|
|
82
|
+
if (isStatic) {
|
|
83
|
+
code +=
|
|
84
|
+
`${this.indent()}(*${className}).set_own_property(${methodName}, ${methodLambda});\n`;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
code +=
|
|
88
|
+
`${this.indent()}(*${className}).get_own_property("prototype").set_own_property(${methodName}, ${methodLambda});\n`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (ts.isGetAccessor(member)) {
|
|
92
|
+
const methodName = visitObjectPropertyName.call(this, member.name, {
|
|
93
|
+
...context,
|
|
94
|
+
isObjectLiteralExpression: true,
|
|
95
|
+
});
|
|
96
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
97
|
+
const lambda = this.generateLambda(member, {
|
|
98
|
+
...classContext,
|
|
99
|
+
isInsideFunction: true,
|
|
100
|
+
});
|
|
101
|
+
if (isStatic) {
|
|
102
|
+
code +=
|
|
103
|
+
`${this.indent()}(*${className}).define_getter(${methodName}, ${lambda});\n`;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
code +=
|
|
107
|
+
`${this.indent()}(*${className}).get_own_property("prototype").define_getter(${methodName}, ${lambda});\n`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (ts.isSetAccessor(member)) {
|
|
111
|
+
const methodName = visitObjectPropertyName.call(this, member.name, {
|
|
112
|
+
...context,
|
|
113
|
+
isObjectLiteralExpression: true,
|
|
114
|
+
});
|
|
115
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
116
|
+
const lambda = this.generateLambda(member, {
|
|
117
|
+
...classContext,
|
|
118
|
+
isInsideFunction: true,
|
|
119
|
+
});
|
|
120
|
+
if (isStatic) {
|
|
121
|
+
code +=
|
|
122
|
+
`${this.indent()}(*${className}).define_setter(${methodName}, ${lambda});\n`;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
code +=
|
|
126
|
+
`${this.indent()}(*${className}).get_own_property("prototype").define_setter(${methodName}, ${lambda});\n`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return code;
|
|
131
|
+
}
|