@ugo-studio/jspp 0.3.1 → 0.3.2
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/cli/args.js +22 -0
- package/dist/cli/compiler.js +53 -0
- package/dist/cli/index.js +43 -117
- package/dist/cli/pch.js +71 -0
- package/dist/cli/runner.js +23 -0
- package/dist/cli/spinner.js +27 -11
- package/dist/cli/transpiler.js +20 -0
- package/dist/cli/utils.js +25 -27
- package/dist/cli/wasm.js +70 -0
- package/dist/index.js +17 -6
- package/dist/{analysis → interpreter/analysis}/scope.js +34 -1
- package/dist/{analysis → interpreter/analysis}/typeAnalyzer.js +542 -3
- package/dist/{core → interpreter/core}/codegen/class-handlers.js +1 -1
- package/dist/{core → interpreter/core}/codegen/control-flow-handlers.js +2 -2
- package/dist/{core → interpreter/core}/codegen/declaration-handlers.js +21 -9
- package/dist/{core → interpreter/core}/codegen/destructuring-handlers.js +3 -3
- package/dist/{core → interpreter/core}/codegen/expression-handlers.js +44 -61
- package/dist/{core → interpreter/core}/codegen/function-handlers.js +108 -61
- package/dist/{core → interpreter/core}/codegen/helpers.js +79 -11
- package/dist/interpreter/core/codegen/index.js +156 -0
- package/dist/{core → interpreter/core}/codegen/literal-handlers.js +9 -0
- package/dist/{core → interpreter/core}/codegen/statement-handlers.js +39 -1
- package/package.json +6 -4
- package/scripts/precompile-headers.ts +63 -19
- package/scripts/setup-emsdk.ts +114 -0
- package/src/prelude/any_value.cpp +851 -599
- package/src/prelude/any_value.hpp +28 -30
- package/src/prelude/jspp.hpp +3 -1
- package/src/prelude/library/boolean.cpp +30 -0
- package/src/prelude/library/boolean.hpp +14 -0
- package/src/prelude/library/error.cpp +2 -2
- package/src/prelude/library/global.cpp +2 -0
- package/src/prelude/library/math.cpp +46 -43
- package/src/prelude/library/math.hpp +2 -0
- package/src/prelude/library/object.cpp +1 -1
- package/src/prelude/types.hpp +10 -9
- package/src/prelude/utils/access.hpp +2 -2
- package/src/prelude/utils/assignment_operators.hpp +40 -20
- package/src/prelude/utils/log_any_value/log_any_value.hpp +1 -1
- package/src/prelude/utils/log_any_value/primitives.hpp +1 -1
- package/src/prelude/utils/operators.hpp +87 -87
- package/src/prelude/utils/operators_native.hpp +349 -0
- package/src/prelude/utils/well_known_symbols.hpp +13 -13
- package/src/prelude/values/array.cpp +3 -3
- package/src/prelude/values/boolean.cpp +64 -0
- package/src/prelude/values/number.cpp +137 -92
- package/src/prelude/values/object.cpp +163 -122
- package/src/prelude/values/prototypes/boolean.hpp +24 -0
- package/src/prelude/values/prototypes/number.hpp +8 -1
- package/dist/cli/file-utils.js +0 -20
- package/dist/cli-utils/args.js +0 -59
- package/dist/cli-utils/colors.js +0 -9
- package/dist/cli-utils/file-utils.js +0 -20
- package/dist/cli-utils/spinner.js +0 -55
- package/dist/cli.js +0 -153
- package/dist/core/codegen/index.js +0 -88
- package/src/prelude/utils/operators_primitive.hpp +0 -337
- /package/dist/{ast → interpreter/ast}/symbols.js +0 -0
- /package/dist/{ast → interpreter/ast}/types.js +0 -0
- /package/dist/{core → interpreter/core}/codegen/visitor.js +0 -0
- /package/dist/{core → interpreter/core}/constants.js +0 -0
- /package/dist/{core → interpreter/core}/error.js +0 -0
- /package/dist/{core → interpreter/core}/parser.js +0 -0
- /package/dist/{core → interpreter/core}/traverser.js +0 -0
package/dist/cli/args.js
CHANGED
|
@@ -7,6 +7,7 @@ export function parseArgs(rawArgs) {
|
|
|
7
7
|
let keepCpp = false;
|
|
8
8
|
let outputExePath = null;
|
|
9
9
|
let scriptArgs = [];
|
|
10
|
+
let target = "native";
|
|
10
11
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
11
12
|
const arg = rawArgs[i];
|
|
12
13
|
if (!arg)
|
|
@@ -21,6 +22,26 @@ export function parseArgs(rawArgs) {
|
|
|
21
22
|
else if (arg === "--keep-cpp") {
|
|
22
23
|
keepCpp = true;
|
|
23
24
|
}
|
|
25
|
+
else if (arg === "-t" || arg === "--target") {
|
|
26
|
+
if (i + 1 < rawArgs.length) {
|
|
27
|
+
const targetValue = rawArgs[i + 1]?.toLowerCase();
|
|
28
|
+
if (targetValue === "wasm") {
|
|
29
|
+
target = "wasm";
|
|
30
|
+
}
|
|
31
|
+
else if (targetValue === "native") {
|
|
32
|
+
target = "native";
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.error(`${COLORS.red}Error: Invalid target '${targetValue}'. Supported targets: native, wasm.${COLORS.reset}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.error(`${COLORS.red}Error: --target requires a value (native or wasm).${COLORS.reset}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
24
45
|
else if (arg === "-o" || arg === "--output") {
|
|
25
46
|
if (i + 1 < rawArgs.length) {
|
|
26
47
|
outputExePath = rawArgs[i + 1] ?? null;
|
|
@@ -55,5 +76,6 @@ export function parseArgs(rawArgs) {
|
|
|
55
76
|
? path.resolve(process.cwd(), outputExePath)
|
|
56
77
|
: null,
|
|
57
78
|
scriptArgs,
|
|
79
|
+
target,
|
|
58
80
|
};
|
|
59
81
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { COLORS } from "./colors.js";
|
|
5
|
+
import { Spinner } from "./spinner.js";
|
|
6
|
+
import { msToHumanReadable } from "./utils.js";
|
|
7
|
+
export async function compileCpp(cppFilePath, exeFilePath, pchDir, preludePath, isWasm, flags, emsdkEnv, spinner) {
|
|
8
|
+
spinner.text = `Compiling binary...`;
|
|
9
|
+
spinner.start();
|
|
10
|
+
// Ensure output directory exists
|
|
11
|
+
await fs.mkdir(path.dirname(exeFilePath), { recursive: true });
|
|
12
|
+
const runtimeLibPath = path.join(pchDir, "libjspp.a");
|
|
13
|
+
const compiler = isWasm ? "em++" : "g++";
|
|
14
|
+
const compileArgs = [
|
|
15
|
+
"-std=c++23",
|
|
16
|
+
...flags,
|
|
17
|
+
"-include",
|
|
18
|
+
"jspp.hpp",
|
|
19
|
+
cppFilePath,
|
|
20
|
+
runtimeLibPath,
|
|
21
|
+
"-o",
|
|
22
|
+
exeFilePath,
|
|
23
|
+
"-I",
|
|
24
|
+
pchDir,
|
|
25
|
+
"-I",
|
|
26
|
+
preludePath,
|
|
27
|
+
];
|
|
28
|
+
if (!isWasm) {
|
|
29
|
+
compileArgs.splice(1, 0, "-Winvalid-pch");
|
|
30
|
+
}
|
|
31
|
+
const compileStartTime = performance.now();
|
|
32
|
+
const compile = spawn(compiler, compileArgs, {
|
|
33
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
34
|
+
env: emsdkEnv,
|
|
35
|
+
shell: process.platform === "win32",
|
|
36
|
+
});
|
|
37
|
+
const compileStderrChunks = [];
|
|
38
|
+
if (compile.stderr) {
|
|
39
|
+
compile.stderr.on("data", (chunk) => compileStderrChunks.push(chunk));
|
|
40
|
+
}
|
|
41
|
+
const compileExitCode = await new Promise((resolve) => {
|
|
42
|
+
compile.on("close", (code) => resolve(code ?? 1));
|
|
43
|
+
});
|
|
44
|
+
if (compileExitCode !== 0) {
|
|
45
|
+
const stderr = Buffer.concat(compileStderrChunks).toString();
|
|
46
|
+
spinner.fail(`Compilation failed`);
|
|
47
|
+
console.error(stderr);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const compileEndTime = performance.now();
|
|
51
|
+
const compileTime = msToHumanReadable(compileEndTime - compileStartTime);
|
|
52
|
+
spinner.succeed(`Compiled to ${COLORS.green}${COLORS.bold}${path.basename(exeFilePath)}${COLORS.reset} ${COLORS.dim}[${compileTime}]${COLORS.reset}`);
|
|
53
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,138 +1,75 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "child_process";
|
|
3
2
|
import fs from "fs/promises";
|
|
4
3
|
import path from "path";
|
|
5
4
|
import pkg from "../../package.json" with { type: "json" };
|
|
6
|
-
import { CompilerError } from "../core/error.js";
|
|
7
|
-
import { Interpreter } from "../index.js";
|
|
5
|
+
import { CompilerError } from "../interpreter/core/error.js";
|
|
8
6
|
import { parseArgs } from "./args.js";
|
|
9
7
|
import { COLORS } from "./colors.js";
|
|
8
|
+
import { compileCpp } from "./compiler.js";
|
|
9
|
+
import { checkAndRebuildPCH } from "./pch.js";
|
|
10
|
+
import { runOutput } from "./runner.js";
|
|
10
11
|
import { Spinner } from "./spinner.js";
|
|
11
|
-
import {
|
|
12
|
+
import { transpile } from "./transpiler.js";
|
|
13
|
+
import { postProcessWasm, setupEmsdk } from "./wasm.js";
|
|
12
14
|
const pkgDir = path.dirname(path.dirname(import.meta.dirname));
|
|
15
|
+
const emsdkEnv = {
|
|
16
|
+
...process.env,
|
|
17
|
+
PATH: `${path.join(pkgDir, ".emsdk")}${path.delimiter}${path.join(pkgDir, ".emsdk", "upstream", "emscripten")}${path.delimiter}${process.env.PATH}`,
|
|
18
|
+
};
|
|
13
19
|
async function main() {
|
|
14
|
-
const { jsFilePath, isRelease, keepCpp, outputExePath, scriptArgs } = parseArgs(process.argv.slice(2));
|
|
20
|
+
const { jsFilePath, isRelease, keepCpp, outputExePath, scriptArgs, target, } = parseArgs(process.argv.slice(2));
|
|
21
|
+
const isWasm = target === "wasm";
|
|
15
22
|
const ext = path.extname(jsFilePath);
|
|
16
23
|
const jsFileName = path.basename(jsFilePath, ext);
|
|
17
24
|
const sourceDir = path.dirname(jsFilePath);
|
|
18
|
-
// Intermediate C++ file goes alongside the source JS file
|
|
19
|
-
|
|
25
|
+
// Intermediate C++ file goes alongside the source JS file if `--output` is not set
|
|
26
|
+
let cppFilePath = path.join(sourceDir, `${jsFileName}.cpp`);
|
|
20
27
|
// Determine output executable path
|
|
21
28
|
let exeFilePath;
|
|
22
29
|
if (outputExePath) {
|
|
23
30
|
exeFilePath = outputExePath;
|
|
31
|
+
cppFilePath = path.join(path.dirname(outputExePath), `${jsFileName}.cpp`);
|
|
24
32
|
}
|
|
25
33
|
else {
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
if (isWasm) {
|
|
35
|
+
exeFilePath = path.join(sourceDir, `${jsFileName}.js`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
39
|
+
exeFilePath = path.join(sourceDir, `${jsFileName}${ext}`);
|
|
40
|
+
}
|
|
28
41
|
}
|
|
29
42
|
// Mode Configuration
|
|
30
|
-
const mode = isRelease ? "release" : "debug";
|
|
43
|
+
const mode = isWasm ? "wasm" : (isRelease ? "release" : "debug");
|
|
44
|
+
const modeNote = isRelease
|
|
45
|
+
? `${COLORS.dim}(optimized)${COLORS.reset}`
|
|
46
|
+
: `${COLORS.dim}(debug)${COLORS.reset}\n${COLORS.dim}NOTE: Use "--release" for an optimized build for production${COLORS.reset}`;
|
|
31
47
|
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"] : ["-Og"
|
|
34
|
-
if (
|
|
48
|
+
console.log(`Target: ${isWasm ? COLORS.cyan : COLORS.green}${target.toUpperCase()}${COLORS.reset} | Mode: ${isRelease ? COLORS.green : COLORS.yellow}${mode.toUpperCase()}${COLORS.reset} ${modeNote}\n`);
|
|
49
|
+
const flags = isRelease ? ["-O3", "-DNDEBUG"] : ["-Og"];
|
|
50
|
+
if (isWasm) {
|
|
51
|
+
flags.push("-sASYNCIFY", "-sALLOW_MEMORY_GROWTH=1", "-sWASM=1", "-sEXPORT_ES6=1", "-sMODULARIZE=1", "-sEXPORT_NAME=jsppModule");
|
|
52
|
+
}
|
|
53
|
+
else if (process.platform === "win32") {
|
|
35
54
|
flags.push("-Wa,-mbig-obj");
|
|
36
55
|
}
|
|
37
56
|
const pchDir = path.resolve(pkgDir, "prelude-build", mode);
|
|
38
57
|
const spinner = new Spinner("Initializing...");
|
|
39
58
|
try {
|
|
59
|
+
if (isWasm) {
|
|
60
|
+
await setupEmsdk(pkgDir, spinner);
|
|
61
|
+
}
|
|
40
62
|
spinner.start();
|
|
41
|
-
// 1.
|
|
42
|
-
|
|
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`);
|
|
63
|
+
// 1. Transpilation Phase
|
|
64
|
+
const { preludePath, wasmExports } = await transpile(jsFilePath, cppFilePath, target, spinner);
|
|
51
65
|
// 2. Precompiled Header Check
|
|
52
|
-
|
|
53
|
-
spinner.start();
|
|
54
|
-
const pchFile = path.join(pchDir, "jspp.hpp.gch");
|
|
55
|
-
const runtimeLibPath = path.join(pchDir, "libjspp.a");
|
|
56
|
-
let shouldRebuild = false;
|
|
57
|
-
try {
|
|
58
|
-
const pchStats = await fs.stat(pchFile);
|
|
59
|
-
const sourceMtime = await getLatestMtime(preludePath);
|
|
60
|
-
if (sourceMtime > pchStats.mtimeMs) {
|
|
61
|
-
shouldRebuild = true;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
catch (e) {
|
|
65
|
-
shouldRebuild = true;
|
|
66
|
-
}
|
|
67
|
-
if (shouldRebuild) {
|
|
68
|
-
spinner.update("Rebuilding precompiled headers (this may take a while)...");
|
|
69
|
-
// Use spawn (async) instead of spawnSync to keep spinner alive
|
|
70
|
-
const rebuild = spawn("bun", [
|
|
71
|
-
"run",
|
|
72
|
-
"scripts/precompile-headers.ts",
|
|
73
|
-
], {
|
|
74
|
-
cwd: pkgDir,
|
|
75
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
76
|
-
});
|
|
77
|
-
const stderrChunks = [];
|
|
78
|
-
if (rebuild.stderr) {
|
|
79
|
-
rebuild.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
80
|
-
}
|
|
81
|
-
const exitCode = await new Promise((resolve) => {
|
|
82
|
-
rebuild.on("close", (code) => resolve(code ?? 1));
|
|
83
|
-
});
|
|
84
|
-
if (exitCode !== 0) {
|
|
85
|
-
const stderr = Buffer.concat(stderrChunks).toString();
|
|
86
|
-
spinner.fail("Failed to rebuild precompiled headers");
|
|
87
|
-
console.error(stderr);
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
spinner.succeed("Precompiled headers updated");
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
spinner.succeed("Precompiled headers");
|
|
94
|
-
}
|
|
66
|
+
await checkAndRebuildPCH(pkgDir, pchDir, mode, preludePath, emsdkEnv, spinner);
|
|
95
67
|
// 3. Compilation Phase
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const compileStartTime = performance.now();
|
|
101
|
-
const compile = spawn("g++", [
|
|
102
|
-
"-std=c++23",
|
|
103
|
-
...flags,
|
|
104
|
-
"-Winvalid-pch",
|
|
105
|
-
"-include",
|
|
106
|
-
"jspp.hpp",
|
|
107
|
-
cppFilePath,
|
|
108
|
-
runtimeLibPath,
|
|
109
|
-
"-o",
|
|
110
|
-
exeFilePath,
|
|
111
|
-
"-I",
|
|
112
|
-
pchDir,
|
|
113
|
-
"-I",
|
|
114
|
-
preludePath,
|
|
115
|
-
], {
|
|
116
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
117
|
-
});
|
|
118
|
-
const compileStderrChunks = [];
|
|
119
|
-
if (compile.stderr) {
|
|
120
|
-
compile.stderr.on("data", (chunk) => compileStderrChunks.push(chunk));
|
|
68
|
+
await compileCpp(cppFilePath, exeFilePath, pchDir, preludePath, isWasm, flags, emsdkEnv, spinner);
|
|
69
|
+
// 3.5 Post-processing for Wasm (Exports)
|
|
70
|
+
if (isWasm) {
|
|
71
|
+
await postProcessWasm(exeFilePath, wasmExports);
|
|
121
72
|
}
|
|
122
|
-
const compileExitCode = await new Promise((resolve) => {
|
|
123
|
-
compile.on("close", (code) => resolve(code ?? 1));
|
|
124
|
-
});
|
|
125
|
-
if (compileExitCode !== 0) {
|
|
126
|
-
const stderr = Buffer.concat(compileStderrChunks).toString();
|
|
127
|
-
spinner.fail(`Compilation failed`);
|
|
128
|
-
console.error(stderr);
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
const compileEndTime = performance.now();
|
|
132
|
-
const compileTime = msToHumanReadable(compileEndTime - compileStartTime);
|
|
133
|
-
spinner.succeed(`Compiled to ${COLORS.green}${COLORS.bold}${path.basename(exeFilePath)}${COLORS.reset} in ${COLORS.dim}${COLORS.bold}${compileTime}${COLORS.reset}`);
|
|
134
|
-
// const stderr = Buffer.concat(compileStderrChunks).toString();
|
|
135
|
-
// console.log(stderr);
|
|
136
73
|
// Clean up C++ file if not requested to keep
|
|
137
74
|
if (!keepCpp) {
|
|
138
75
|
try {
|
|
@@ -143,18 +80,7 @@ async function main() {
|
|
|
143
80
|
}
|
|
144
81
|
}
|
|
145
82
|
// 4. Execution Phase
|
|
146
|
-
|
|
147
|
-
const run = spawn(exeFilePath, scriptArgs, {
|
|
148
|
-
stdio: "inherit",
|
|
149
|
-
});
|
|
150
|
-
const runExitCode = await new Promise((resolve) => {
|
|
151
|
-
run.on("close", (code) => resolve(code ?? 1));
|
|
152
|
-
});
|
|
153
|
-
console.log(`${COLORS.cyan}----------------------${COLORS.reset}\n`);
|
|
154
|
-
if (runExitCode !== 0) {
|
|
155
|
-
console.error(`${COLORS.red}Execution failed with exit code ${runExitCode}${COLORS.reset}`);
|
|
156
|
-
process.exit(1);
|
|
157
|
-
}
|
|
83
|
+
await runOutput(exeFilePath, scriptArgs, isWasm);
|
|
158
84
|
}
|
|
159
85
|
catch (error) {
|
|
160
86
|
if (error instanceof CompilerError) {
|
package/dist/cli/pch.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { COLORS } from "./colors.js";
|
|
5
|
+
import { Spinner } from "./spinner.js";
|
|
6
|
+
import { getLatestMtime, msToHumanReadable } from "./utils.js";
|
|
7
|
+
export async function checkAndRebuildPCH(pkgDir, pchDir, mode, preludePath, emsdkEnv, spinner) {
|
|
8
|
+
spinner.text = "Checking precompiled headers...";
|
|
9
|
+
spinner.start();
|
|
10
|
+
const pchFile = path.join(pchDir, "jspp.hpp.gch");
|
|
11
|
+
const runtimeLibPath = path.join(pchDir, "libjspp.a");
|
|
12
|
+
let shouldRebuildPCH = false;
|
|
13
|
+
try {
|
|
14
|
+
const pchStats = await fs.stat(pchFile);
|
|
15
|
+
const libStats = await fs.stat(runtimeLibPath);
|
|
16
|
+
// 1. Check if any header is newer than the PCH
|
|
17
|
+
const latestHeaderMtime = await getLatestMtime(preludePath, (name) => name.endsWith(".hpp") || name.endsWith(".h"));
|
|
18
|
+
// 2. Check if any CPP file is newer than the library
|
|
19
|
+
const latestCppMtime = await getLatestMtime(preludePath, (name) => name.endsWith(".cpp"));
|
|
20
|
+
if (latestHeaderMtime > pchStats.mtimeMs ||
|
|
21
|
+
latestCppMtime > libStats.mtimeMs ||
|
|
22
|
+
pchStats.mtimeMs > libStats.mtimeMs) {
|
|
23
|
+
shouldRebuildPCH = true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
shouldRebuildPCH = true;
|
|
28
|
+
}
|
|
29
|
+
if (shouldRebuildPCH) {
|
|
30
|
+
spinner.update("Rebuilding precompiled headers (this may take a while)...");
|
|
31
|
+
const pchStartTime = performance.now();
|
|
32
|
+
const rebuild = spawn("bun", [
|
|
33
|
+
"run",
|
|
34
|
+
"scripts/precompile-headers.ts",
|
|
35
|
+
"--jspp-cli-is-parent",
|
|
36
|
+
"--mode",
|
|
37
|
+
mode,
|
|
38
|
+
], {
|
|
39
|
+
cwd: pkgDir,
|
|
40
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
41
|
+
env: emsdkEnv,
|
|
42
|
+
});
|
|
43
|
+
if (rebuild.stdout) {
|
|
44
|
+
rebuild.stdout.on("data", (chunk) => {
|
|
45
|
+
spinner.pause();
|
|
46
|
+
process.stdout.write(chunk);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const stderrChunks = [];
|
|
50
|
+
if (rebuild.stderr) {
|
|
51
|
+
rebuild.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
52
|
+
}
|
|
53
|
+
const exitCode = await new Promise((resolve) => {
|
|
54
|
+
rebuild.on("close", (code) => {
|
|
55
|
+
spinner.resume();
|
|
56
|
+
resolve(code ?? 1);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
if (exitCode !== 0) {
|
|
60
|
+
const stderr = Buffer.concat(stderrChunks).toString();
|
|
61
|
+
spinner.fail("Failed to rebuild precompiled headers");
|
|
62
|
+
console.error(stderr);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const pchTime = msToHumanReadable(performance.now() - pchStartTime);
|
|
66
|
+
spinner.succeed(`Precompiled headers updated ${COLORS.dim}[${pchTime}]${COLORS.reset}`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
spinner.stop();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { COLORS } from "./colors.js";
|
|
4
|
+
export async function runOutput(exeFilePath, scriptArgs, isWasm) {
|
|
5
|
+
if (isWasm) {
|
|
6
|
+
console.log(`\n${COLORS.cyan}Compilation finished. To run the output, use node or a browser:${COLORS.reset}`);
|
|
7
|
+
console.log(`${COLORS.bold} node ${path.basename(exeFilePath)}${COLORS.reset}\n`);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
console.log(`\n${COLORS.cyan}--- Running Output ---${COLORS.reset}`);
|
|
11
|
+
const run = spawn(exeFilePath, scriptArgs, {
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
});
|
|
14
|
+
const runExitCode = await new Promise((resolve) => {
|
|
15
|
+
run.on("close", (code) => resolve(code ?? 1));
|
|
16
|
+
});
|
|
17
|
+
console.log(`${COLORS.cyan}----------------------${COLORS.reset}\n`);
|
|
18
|
+
if (runExitCode !== 0) {
|
|
19
|
+
console.error(`${COLORS.red}Execution failed with exit code ${runExitCode}${COLORS.reset}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
package/dist/cli/spinner.js
CHANGED
|
@@ -1,32 +1,42 @@
|
|
|
1
1
|
import { COLORS } from "./colors.js";
|
|
2
2
|
export class Spinner {
|
|
3
3
|
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
4
|
-
interval = null;
|
|
4
|
+
interval = null; // Note: You may need NodeJS.Timeout depending on your environment
|
|
5
5
|
frameIndex = 0;
|
|
6
6
|
text;
|
|
7
7
|
constructor(text) {
|
|
8
8
|
this.text = text;
|
|
9
9
|
}
|
|
10
10
|
start() {
|
|
11
|
+
if (this.interval)
|
|
12
|
+
return; // Prevent multiple intervals
|
|
11
13
|
process.stdout.write("\x1b[?25l"); // Hide cursor
|
|
12
14
|
this.frameIndex = 0;
|
|
13
15
|
this.render();
|
|
14
|
-
this.
|
|
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();
|
|
16
|
+
this.startInterval();
|
|
22
17
|
}
|
|
23
|
-
|
|
18
|
+
pause() {
|
|
24
19
|
if (this.interval) {
|
|
25
20
|
clearInterval(this.interval);
|
|
26
21
|
this.interval = null;
|
|
27
22
|
}
|
|
23
|
+
}
|
|
24
|
+
resume() {
|
|
25
|
+
if (this.interval)
|
|
26
|
+
return; // Already running
|
|
27
|
+
process.stdout.write("\x1b[?25l"); // Ensure cursor stays hidden
|
|
28
|
+
this.startInterval();
|
|
29
|
+
}
|
|
30
|
+
update(text) {
|
|
31
|
+
this.text = text;
|
|
32
|
+
this.render();
|
|
33
|
+
}
|
|
34
|
+
stop(symbol, color = COLORS.reset) {
|
|
35
|
+
this.pause(); // Reuse pause logic to clear the interval
|
|
28
36
|
this.clearLine();
|
|
29
|
-
|
|
37
|
+
if (symbol) {
|
|
38
|
+
process.stdout.write(`${color}${symbol} ${COLORS.reset} ${this.text}\n`);
|
|
39
|
+
}
|
|
30
40
|
process.stdout.write("\x1b[?25h"); // Show cursor
|
|
31
41
|
}
|
|
32
42
|
succeed(text) {
|
|
@@ -44,6 +54,12 @@ export class Spinner {
|
|
|
44
54
|
this.text = text;
|
|
45
55
|
this.stop("ℹ", COLORS.cyan);
|
|
46
56
|
}
|
|
57
|
+
startInterval() {
|
|
58
|
+
this.interval = setInterval(() => {
|
|
59
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
60
|
+
this.render();
|
|
61
|
+
}, 80);
|
|
62
|
+
}
|
|
47
63
|
render() {
|
|
48
64
|
this.clearLine();
|
|
49
65
|
const frame = this.frames[this.frameIndex];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { Interpreter } from "../index.js";
|
|
4
|
+
import { COLORS } from "./colors.js";
|
|
5
|
+
import { Spinner } from "./spinner.js";
|
|
6
|
+
import { msToHumanReadable } from "./utils.js";
|
|
7
|
+
export async function transpile(jsFilePath, cppFilePath, target, spinner) {
|
|
8
|
+
spinner.update(`Reading ${path.basename(jsFilePath)}...`);
|
|
9
|
+
const jsCode = await fs.readFile(jsFilePath, "utf-8");
|
|
10
|
+
spinner.update("Transpiling to C++...");
|
|
11
|
+
const transpileStartTime = performance.now();
|
|
12
|
+
const interpreter = new Interpreter();
|
|
13
|
+
const { cppCode, preludePath, wasmExports } = interpreter.interpret(jsCode, jsFilePath, target);
|
|
14
|
+
const transpileTime = msToHumanReadable(performance.now() - transpileStartTime);
|
|
15
|
+
// Ensure directory for cpp file exists
|
|
16
|
+
await fs.mkdir(path.dirname(cppFilePath), { recursive: true });
|
|
17
|
+
await fs.writeFile(cppFilePath, cppCode);
|
|
18
|
+
spinner.succeed(`Generated cpp ${COLORS.dim}[${transpileTime}]${COLORS.reset}`);
|
|
19
|
+
return { preludePath, wasmExports };
|
|
20
|
+
}
|
package/dist/cli/utils.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
|
-
export async function getLatestMtime(dirPath) {
|
|
3
|
+
export async function getLatestMtime(dirPath, filter) {
|
|
4
4
|
let maxMtime = 0;
|
|
5
5
|
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
6
6
|
for (const entry of entries) {
|
|
7
7
|
const fullPath = path.join(dirPath, entry.name);
|
|
8
8
|
if (entry.isDirectory()) {
|
|
9
|
-
const nestedMtime = await getLatestMtime(fullPath);
|
|
9
|
+
const nestedMtime = await getLatestMtime(fullPath, filter);
|
|
10
10
|
if (nestedMtime > maxMtime)
|
|
11
11
|
maxMtime = nestedMtime;
|
|
12
12
|
}
|
|
13
13
|
else {
|
|
14
|
+
if (filter && !filter(entry.name))
|
|
15
|
+
continue;
|
|
14
16
|
const stats = await fs.stat(fullPath);
|
|
15
17
|
if (stats.mtimeMs > maxMtime)
|
|
16
18
|
maxMtime = stats.mtimeMs;
|
|
@@ -19,43 +21,39 @@ export async function getLatestMtime(dirPath) {
|
|
|
19
21
|
return maxMtime;
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
|
-
* Converts milliseconds to a human-readable format.
|
|
24
|
+
* Converts milliseconds to a single-unit, human-readable decimal format.
|
|
23
25
|
* @param ms - The time in milliseconds
|
|
24
|
-
* @returns A formatted string (e.g., "2
|
|
26
|
+
* @returns A formatted string (e.g., "2.52s", "1.5h")
|
|
25
27
|
*/
|
|
26
28
|
export function msToHumanReadable(ms) {
|
|
27
29
|
if (ms < 0) {
|
|
28
30
|
throw new Error("Time cannot be negative");
|
|
29
31
|
}
|
|
30
32
|
if (ms === 0) {
|
|
31
|
-
return "
|
|
33
|
+
return "0ms";
|
|
32
34
|
}
|
|
33
|
-
// Define time constants in milliseconds
|
|
34
35
|
const MS_PER_SECOND = 1000;
|
|
35
36
|
const MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
36
37
|
const MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
37
38
|
const MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const parts = [];
|
|
45
|
-
// Helper function to handle pluralization
|
|
46
|
-
const addPart = (value, unit) => {
|
|
47
|
-
if (value > 0) {
|
|
48
|
-
parts.push(`${value} ${unit}${value > 1 ? "s" : ""}`);
|
|
49
|
-
}
|
|
39
|
+
// Helper to safely truncate to 2 decimal places without rounding up
|
|
40
|
+
const formatValue = (val) => {
|
|
41
|
+
// toFixed(3) resolves float math errors, slice(0,-1) enforces truncation (e.g. 2.528 -> 2.52)
|
|
42
|
+
let str = val.toFixed(3).slice(0, -1);
|
|
43
|
+
// Remove trailing zeroes and lingering decimal points (e.g., "2.00" -> "2")
|
|
44
|
+
return str.replace(/0+$/, "").replace(/\.$/, "");
|
|
50
45
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
if (ms >= MS_PER_DAY) {
|
|
47
|
+
return formatValue(ms / MS_PER_DAY) + "d";
|
|
48
|
+
}
|
|
49
|
+
if (ms >= MS_PER_HOUR) {
|
|
50
|
+
return formatValue(ms / MS_PER_HOUR) + "h";
|
|
51
|
+
}
|
|
52
|
+
if (ms >= MS_PER_MINUTE) {
|
|
53
|
+
return formatValue(ms / MS_PER_MINUTE) + "m";
|
|
54
|
+
}
|
|
55
|
+
if (ms >= MS_PER_SECOND) {
|
|
56
|
+
return formatValue(ms / MS_PER_SECOND) + "s";
|
|
58
57
|
}
|
|
59
|
-
|
|
60
|
-
return parts.join(", ");
|
|
58
|
+
return formatValue(ms) + "ms";
|
|
61
59
|
}
|
package/dist/cli/wasm.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { Spinner } from "./spinner.js";
|
|
5
|
+
export async function setupEmsdk(pkgDir, spinner) {
|
|
6
|
+
spinner.text = "Checking Emscripten SDK...";
|
|
7
|
+
spinner.start();
|
|
8
|
+
// Ensure emsdk is set up
|
|
9
|
+
const setupEmsdk = spawn("bun", ["run", "scripts/setup-emsdk.ts"], {
|
|
10
|
+
cwd: pkgDir,
|
|
11
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12
|
+
});
|
|
13
|
+
const setupExitCode = await new Promise((resolve) => {
|
|
14
|
+
setupEmsdk.on("close", (code) => resolve(code ?? 1));
|
|
15
|
+
});
|
|
16
|
+
if (setupExitCode !== 0) {
|
|
17
|
+
spinner.fail("Emscripten SDK setup failed");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
spinner.stop();
|
|
21
|
+
}
|
|
22
|
+
export async function postProcessWasm(exeFilePath, wasmExports) {
|
|
23
|
+
const outputDir = path.dirname(exeFilePath);
|
|
24
|
+
const outputBaseName = path.basename(exeFilePath, ".js");
|
|
25
|
+
// Generate .d.ts
|
|
26
|
+
let dtsContent = "";
|
|
27
|
+
for (const exp of wasmExports) {
|
|
28
|
+
const params = exp.params.map((p) => `${p.name}: ${p.type}`)
|
|
29
|
+
.join(", ");
|
|
30
|
+
dtsContent +=
|
|
31
|
+
`export declare function wasm_export_${exp.jsName}(${params}): ${exp.returnType};\n`;
|
|
32
|
+
}
|
|
33
|
+
dtsContent += `export declare const Module: Promise<any>;\n`;
|
|
34
|
+
dtsContent += `export declare const load: () => typeof Module;\n`;
|
|
35
|
+
dtsContent +=
|
|
36
|
+
`declare const jsppModule: (options?: any) => Promise<any>;\n`;
|
|
37
|
+
dtsContent += `export default jsppModule;\n`;
|
|
38
|
+
const dtsPath = path.join(outputDir, `${outputBaseName}.d.ts`);
|
|
39
|
+
await fs.writeFile(dtsPath, dtsContent);
|
|
40
|
+
// Append exports to .js glue code
|
|
41
|
+
let jsGlue = await fs.readFile(exeFilePath, "utf-8");
|
|
42
|
+
// Emscripten with -sEXPORT_ES6=1 adds this at the end
|
|
43
|
+
const defaultExportStr = "export default jsppModule;";
|
|
44
|
+
if (jsGlue.trim().endsWith(defaultExportStr)) {
|
|
45
|
+
jsGlue = jsGlue.trim().substring(0, jsGlue.trim().length - defaultExportStr.length);
|
|
46
|
+
}
|
|
47
|
+
jsGlue += `\n\n// JSPP Named Exports\n`;
|
|
48
|
+
jsGlue += `let _notLoaded = true;\n`;
|
|
49
|
+
jsGlue += `let _resolve = () => {};\n`;
|
|
50
|
+
jsGlue += `let _reject = () => {};\n`;
|
|
51
|
+
jsGlue += `let _instance;\n`;
|
|
52
|
+
jsGlue +=
|
|
53
|
+
`const _getinstance=()=>{if(!_instance)throw new Error("Module not loaded");return _instance}\n`;
|
|
54
|
+
jsGlue +=
|
|
55
|
+
`export const Module = new Promise((res,rej)=>{_resolve=res;_reject=rej});\n`;
|
|
56
|
+
jsGlue += `export const load = async () => {\n`;
|
|
57
|
+
jsGlue += ` if (_notLoaded) {\n`;
|
|
58
|
+
jsGlue += ` _notLoaded = false;\n`;
|
|
59
|
+
jsGlue +=
|
|
60
|
+
` jsppModule().then(m=>{_instance=m;_resolve(m)}).catch(e=>{_notLoaded=true;_reject(e)});\n`;
|
|
61
|
+
jsGlue += ` }\n`;
|
|
62
|
+
jsGlue += ` return Module;\n`;
|
|
63
|
+
jsGlue += `};\n`;
|
|
64
|
+
for (const exp of wasmExports) {
|
|
65
|
+
jsGlue +=
|
|
66
|
+
`export const wasm_export_${exp.jsName} = (...args) => _getinstance()._wasm_export_${exp.jsName}(...args);\n`;
|
|
67
|
+
}
|
|
68
|
+
jsGlue += `export default jsppModule;\n`;
|
|
69
|
+
await fs.writeFile(exeFilePath, jsGlue);
|
|
70
|
+
}
|