@ugo-studio/jspp 0.3.1 → 0.3.3

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.
Files changed (65) hide show
  1. package/dist/cli/args.js +22 -0
  2. package/dist/cli/compiler.js +53 -0
  3. package/dist/cli/index.js +43 -117
  4. package/dist/cli/pch.js +71 -0
  5. package/dist/cli/runner.js +23 -0
  6. package/dist/cli/spinner.js +27 -11
  7. package/dist/cli/transpiler.js +20 -0
  8. package/dist/cli/utils.js +25 -27
  9. package/dist/cli/wasm.js +70 -0
  10. package/dist/index.js +17 -6
  11. package/dist/{analysis → interpreter/analysis}/scope.js +34 -1
  12. package/dist/{analysis → interpreter/analysis}/typeAnalyzer.js +542 -3
  13. package/dist/{core → interpreter/core}/codegen/class-handlers.js +1 -1
  14. package/dist/{core → interpreter/core}/codegen/control-flow-handlers.js +2 -2
  15. package/dist/{core → interpreter/core}/codegen/declaration-handlers.js +21 -9
  16. package/dist/{core → interpreter/core}/codegen/destructuring-handlers.js +3 -3
  17. package/dist/{core → interpreter/core}/codegen/expression-handlers.js +136 -82
  18. package/dist/{core → interpreter/core}/codegen/function-handlers.js +108 -61
  19. package/dist/{core → interpreter/core}/codegen/helpers.js +79 -11
  20. package/dist/interpreter/core/codegen/index.js +156 -0
  21. package/dist/{core → interpreter/core}/codegen/literal-handlers.js +9 -0
  22. package/dist/{core → interpreter/core}/codegen/statement-handlers.js +39 -1
  23. package/package.json +6 -4
  24. package/scripts/precompile-headers.ts +71 -19
  25. package/scripts/setup-emsdk.ts +114 -0
  26. package/src/prelude/any_value.cpp +851 -599
  27. package/src/prelude/any_value.hpp +28 -30
  28. package/src/prelude/jspp.hpp +3 -1
  29. package/src/prelude/library/boolean.cpp +30 -0
  30. package/src/prelude/library/boolean.hpp +14 -0
  31. package/src/prelude/library/error.cpp +2 -2
  32. package/src/prelude/library/global.cpp +2 -0
  33. package/src/prelude/library/math.cpp +46 -43
  34. package/src/prelude/library/math.hpp +2 -0
  35. package/src/prelude/library/object.cpp +1 -1
  36. package/src/prelude/types.hpp +10 -9
  37. package/src/prelude/utils/access.hpp +2 -36
  38. package/src/prelude/utils/assignment_operators.hpp +136 -20
  39. package/src/prelude/utils/log_any_value/log_any_value.hpp +1 -1
  40. package/src/prelude/utils/log_any_value/primitives.hpp +1 -1
  41. package/src/prelude/utils/operators.hpp +123 -88
  42. package/src/prelude/utils/operators_native.hpp +360 -0
  43. package/src/prelude/utils/well_known_symbols.hpp +13 -13
  44. package/src/prelude/values/array.cpp +3 -3
  45. package/src/prelude/values/boolean.cpp +64 -0
  46. package/src/prelude/values/iterator.cpp +262 -210
  47. package/src/prelude/values/number.cpp +137 -92
  48. package/src/prelude/values/object.cpp +163 -122
  49. package/src/prelude/values/prototypes/boolean.hpp +24 -0
  50. package/src/prelude/values/prototypes/number.hpp +8 -1
  51. package/dist/cli/file-utils.js +0 -20
  52. package/dist/cli-utils/args.js +0 -59
  53. package/dist/cli-utils/colors.js +0 -9
  54. package/dist/cli-utils/file-utils.js +0 -20
  55. package/dist/cli-utils/spinner.js +0 -55
  56. package/dist/cli.js +0 -153
  57. package/dist/core/codegen/index.js +0 -88
  58. package/src/prelude/utils/operators_primitive.hpp +0 -337
  59. /package/dist/{ast → interpreter/ast}/symbols.js +0 -0
  60. /package/dist/{ast → interpreter/ast}/types.js +0 -0
  61. /package/dist/{core → interpreter/core}/codegen/visitor.js +0 -0
  62. /package/dist/{core → interpreter/core}/constants.js +0 -0
  63. /package/dist/{core → interpreter/core}/error.js +0 -0
  64. /package/dist/{core → interpreter/core}/parser.js +0 -0
  65. /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 { getLatestMtime, msToHumanReadable } from "./utils.js";
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
- const cppFilePath = path.join(sourceDir, `${jsFileName}.cpp`);
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
- const ext = process.platform === "win32" ? ".exe" : "";
27
- exeFilePath = path.join(sourceDir, `${jsFileName}${ext}`);
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", "-ftime-report"];
34
- if (process.platform === "win32") {
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. Interpreter Phase
42
- spinner.update(`Reading ${path.basename(jsFilePath)}...`);
43
- const jsCode = await fs.readFile(jsFilePath, "utf-8");
44
- spinner.update("Transpiling to C++...");
45
- const interpreter = new Interpreter();
46
- const { cppCode, preludePath } = interpreter.interpret(jsCode, jsFilePath);
47
- // Ensure directory for cpp file exists (should exist as it's source dir, but for safety if we change logic)
48
- await fs.mkdir(path.dirname(cppFilePath), { recursive: true });
49
- await fs.writeFile(cppFilePath, cppCode);
50
- spinner.succeed(`Generated cpp`);
63
+ // 1. Transpilation Phase
64
+ const { preludePath, wasmExports } = await transpile(jsFilePath, cppFilePath, target, spinner);
51
65
  // 2. Precompiled Header Check
52
- spinner.text = "Checking precompiled headers...";
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
- spinner.text = `Compiling binary...`;
97
- spinner.start();
98
- // Ensure output directory exists
99
- await fs.mkdir(path.dirname(exeFilePath), { recursive: true });
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
- console.log(`\n${COLORS.cyan}--- Running Output ---${COLORS.reset}`);
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) {
@@ -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
+ }
@@ -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.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();
16
+ this.startInterval();
22
17
  }
23
- stop(symbol = "", color = COLORS.reset) {
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
- process.stdout.write(`${color}${symbol} ${COLORS.reset} ${this.text}\n`);
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 hours, 1 minute, 4 seconds")
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 "0 seconds";
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
- // Calculate each unit
39
- const days = Math.floor(ms / MS_PER_DAY);
40
- const hours = Math.floor((ms % MS_PER_DAY) / MS_PER_HOUR);
41
- const minutes = Math.floor((ms % MS_PER_HOUR) / MS_PER_MINUTE);
42
- const seconds = Math.floor((ms % MS_PER_MINUTE) / MS_PER_SECOND);
43
- // Array to hold the formatted parts
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
- addPart(days, "day");
52
- addPart(hours, "hour");
53
- addPart(minutes, "minute");
54
- addPart(seconds, "second");
55
- // If the time was less than 1 second, it will be empty
56
- if (parts.length === 0) {
57
- return `${ms} millisecond${ms > 1 ? "s" : ""}`;
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
- // Join the parts with a comma and space
60
- return parts.join(", ");
58
+ return formatValue(ms) + "ms";
61
59
  }
@@ -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
+ }