@ugo-studio/jspp 0.3.0 → 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.
Files changed (127) hide show
  1. package/LICENSE +25 -25
  2. package/README.md +20 -12
  3. package/dist/cli/args.js +22 -0
  4. package/dist/cli/compiler.js +53 -0
  5. package/dist/cli/index.js +43 -107
  6. package/dist/cli/pch.js +71 -0
  7. package/dist/cli/runner.js +23 -0
  8. package/dist/cli/spinner.js +27 -11
  9. package/dist/cli/transpiler.js +20 -0
  10. package/dist/cli/utils.js +59 -0
  11. package/dist/cli/wasm.js +70 -0
  12. package/dist/index.js +17 -6
  13. package/dist/{analysis → interpreter/analysis}/scope.js +38 -3
  14. package/dist/{analysis → interpreter/analysis}/typeAnalyzer.js +563 -28
  15. package/dist/{core → interpreter/core}/codegen/class-handlers.js +1 -1
  16. package/dist/{core → interpreter/core}/codegen/control-flow-handlers.js +12 -11
  17. package/dist/{core → interpreter/core}/codegen/declaration-handlers.js +28 -9
  18. package/dist/{core → interpreter/core}/codegen/destructuring-handlers.js +9 -4
  19. package/dist/{core → interpreter/core}/codegen/expression-handlers.js +82 -88
  20. package/dist/{core → interpreter/core}/codegen/function-handlers.js +159 -46
  21. package/dist/{core → interpreter/core}/codegen/helpers.js +170 -25
  22. package/dist/interpreter/core/codegen/index.js +156 -0
  23. package/dist/{core → interpreter/core}/codegen/literal-handlers.js +9 -0
  24. package/dist/{core → interpreter/core}/codegen/statement-handlers.js +47 -7
  25. package/package.json +6 -4
  26. package/scripts/precompile-headers.ts +293 -50
  27. package/scripts/setup-compiler.ts +63 -63
  28. package/scripts/setup-emsdk.ts +114 -0
  29. package/src/prelude/any_value.cpp +888 -0
  30. package/src/prelude/any_value.hpp +29 -24
  31. package/src/prelude/{exception_helpers.hpp → exception.cpp} +53 -53
  32. package/src/prelude/exception.hpp +27 -27
  33. package/src/prelude/iterator_instantiations.hpp +10 -0
  34. package/src/prelude/{index.hpp → jspp.hpp} +13 -17
  35. package/src/prelude/library/array.cpp +191 -0
  36. package/src/prelude/library/array.hpp +5 -178
  37. package/src/prelude/library/boolean.cpp +30 -0
  38. package/src/prelude/library/boolean.hpp +14 -0
  39. package/src/prelude/library/console.cpp +125 -0
  40. package/src/prelude/library/console.hpp +9 -97
  41. package/src/prelude/library/error.cpp +100 -0
  42. package/src/prelude/library/error.hpp +8 -108
  43. package/src/prelude/library/function.cpp +69 -0
  44. package/src/prelude/library/function.hpp +6 -5
  45. package/src/prelude/library/global.cpp +98 -0
  46. package/src/prelude/library/global.hpp +12 -28
  47. package/src/prelude/library/global_usings.hpp +15 -0
  48. package/src/prelude/library/math.cpp +261 -0
  49. package/src/prelude/library/math.hpp +8 -288
  50. package/src/prelude/library/object.cpp +379 -0
  51. package/src/prelude/library/object.hpp +5 -267
  52. package/src/prelude/library/performance.cpp +21 -0
  53. package/src/prelude/library/performance.hpp +5 -20
  54. package/src/prelude/library/process.cpp +38 -0
  55. package/src/prelude/library/process.hpp +3 -31
  56. package/src/prelude/library/promise.cpp +131 -0
  57. package/src/prelude/library/promise.hpp +5 -116
  58. package/src/prelude/library/symbol.cpp +56 -0
  59. package/src/prelude/library/symbol.hpp +5 -46
  60. package/src/prelude/library/timer.cpp +88 -0
  61. package/src/prelude/library/timer.hpp +11 -87
  62. package/src/prelude/runtime.cpp +19 -0
  63. package/src/prelude/types.hpp +26 -20
  64. package/src/prelude/utils/access.hpp +123 -32
  65. package/src/prelude/utils/assignment_operators.hpp +119 -99
  66. package/src/prelude/utils/log_any_value/array.hpp +61 -40
  67. package/src/prelude/utils/log_any_value/function.hpp +39 -39
  68. package/src/prelude/utils/log_any_value/log_any_value.hpp +1 -1
  69. package/src/prelude/utils/log_any_value/object.hpp +60 -3
  70. package/src/prelude/utils/log_any_value/primitives.hpp +1 -1
  71. package/src/prelude/utils/operators.hpp +109 -94
  72. package/src/prelude/utils/operators_native.hpp +349 -0
  73. package/src/prelude/utils/well_known_symbols.hpp +24 -24
  74. package/src/prelude/values/array.cpp +1399 -0
  75. package/src/prelude/values/array.hpp +4 -0
  76. package/src/prelude/values/async_iterator.cpp +251 -0
  77. package/src/prelude/values/async_iterator.hpp +60 -32
  78. package/src/prelude/values/boolean.cpp +64 -0
  79. package/src/prelude/values/function.cpp +262 -0
  80. package/src/prelude/values/function.hpp +10 -30
  81. package/src/prelude/values/iterator.cpp +309 -0
  82. package/src/prelude/values/iterator.hpp +33 -64
  83. package/src/prelude/values/number.cpp +221 -0
  84. package/src/prelude/values/object.cpp +200 -0
  85. package/src/prelude/values/object.hpp +4 -0
  86. package/src/prelude/values/promise.cpp +479 -0
  87. package/src/prelude/values/promise.hpp +9 -2
  88. package/src/prelude/values/prototypes/array.hpp +46 -1348
  89. package/src/prelude/values/prototypes/async_iterator.hpp +19 -61
  90. package/src/prelude/values/prototypes/boolean.hpp +24 -0
  91. package/src/prelude/values/prototypes/function.hpp +7 -46
  92. package/src/prelude/values/prototypes/iterator.hpp +15 -191
  93. package/src/prelude/values/prototypes/number.hpp +30 -210
  94. package/src/prelude/values/prototypes/object.hpp +7 -23
  95. package/src/prelude/values/prototypes/promise.hpp +8 -186
  96. package/src/prelude/values/prototypes/string.hpp +28 -553
  97. package/src/prelude/values/prototypes/symbol.hpp +9 -70
  98. package/src/prelude/values/shape.hpp +52 -52
  99. package/src/prelude/values/string.cpp +485 -0
  100. package/src/prelude/values/symbol.cpp +89 -0
  101. package/src/prelude/values/symbol.hpp +101 -101
  102. package/dist/cli/file-utils.js +0 -20
  103. package/dist/cli-utils/args.js +0 -59
  104. package/dist/cli-utils/colors.js +0 -9
  105. package/dist/cli-utils/file-utils.js +0 -20
  106. package/dist/cli-utils/spinner.js +0 -55
  107. package/dist/cli.js +0 -153
  108. package/dist/core/codegen/index.js +0 -86
  109. package/src/prelude/any_value_access.hpp +0 -170
  110. package/src/prelude/any_value_defines.hpp +0 -190
  111. package/src/prelude/any_value_helpers.hpp +0 -374
  112. package/src/prelude/utils/operators_primitive.hpp +0 -337
  113. package/src/prelude/values/helpers/array.hpp +0 -199
  114. package/src/prelude/values/helpers/async_iterator.hpp +0 -275
  115. package/src/prelude/values/helpers/function.hpp +0 -109
  116. package/src/prelude/values/helpers/iterator.hpp +0 -145
  117. package/src/prelude/values/helpers/object.hpp +0 -104
  118. package/src/prelude/values/helpers/promise.hpp +0 -254
  119. package/src/prelude/values/helpers/string.hpp +0 -37
  120. package/src/prelude/values/helpers/symbol.hpp +0 -21
  121. /package/dist/{ast → interpreter/ast}/symbols.js +0 -0
  122. /package/dist/{ast → interpreter/ast}/types.js +0 -0
  123. /package/dist/{core → interpreter/core}/codegen/visitor.js +0 -0
  124. /package/dist/{core → interpreter/core}/constants.js +0 -0
  125. /package/dist/{core → interpreter/core}/error.js +0 -0
  126. /package/dist/{core → interpreter/core}/parser.js +0 -0
  127. /package/dist/{core → interpreter/core}/traverser.js +0 -0
package/LICENSE CHANGED
@@ -1,25 +1,25 @@
1
- # License
2
-
3
- JSPP is licensed under the [MIT License](#mit-license).
4
-
5
- ## MIT License
6
-
7
- Copyright (c) 2025 Emmanuel
8
-
9
- Permission is hereby granted, free of charge, to any person obtaining a copy
10
- of this software and associated documentation files (the "Software"), to deal
11
- in the Software without restriction, including without limitation the rights
12
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
- copies of the Software, and to permit persons to whom the Software is
14
- furnished to do so, subject to the following conditions:
15
-
16
- The above copyright notice and this permission notice shall be included in all
17
- copies or substantial portions of the Software.
18
-
19
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
- SOFTWARE.
1
+ # License
2
+
3
+ JSPP is licensed under the [MIT License](#mit-license).
4
+
5
+ ## MIT License
6
+
7
+ Copyright (c) 2025 Emmanuel
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
package/README.md CHANGED
@@ -11,7 +11,11 @@ The primary goal of this project is to achieve a near-perfect translation of Jav
11
11
 
12
12
  JavaScript is flexible and dynamic; C++ is performant and type-safe. JSPP aims to offer the best of both worlds. By transpiling JS/TS to C++, we can potentially run JavaScript logic in environments where C++ is native, with significant performance gains and opportunities for low-level interoperability.
13
13
 
14
- This project serves as a deep dive into compiler design, language semantics, and the expressive power of modern C++. The current implementation translates an entire JavaScript/TypeScript file into a single C++ `main` function, cleverly using `std::shared_ptr<std::any>` (and custom wrappers) to replicate JavaScript's dynamic typing, garbage-collection-like memory management, and complex features like closures.
14
+ This project serves as a deep dive into compiler design, language semantics, and the expressive power of modern C++. The architecture is designed for performance, utilizing:
15
+ - **Fast Runtime Library:** Core JavaScript logic is implemented in a static C++ library (`libjspp.a`), precompiled for speed.
16
+ - **Precompiled Headers (PCH):** Common headers are precompiled to drastically reduce the front-end parsing time of the C++ compiler.
17
+ - **NaN-Boxing:** An efficient 64-bit value representation (NaN-boxing) is used to replicate JavaScript's dynamic typing with minimal overhead.
18
+ - **Modern C++23:** Leverages the latest language features, including coroutines for `async/await` and generators.
15
19
 
16
20
  ## Features
17
21
 
@@ -67,8 +71,10 @@ To contribute to JSPP or run its test suite, follow these steps:
67
71
  ### Prerequisites
68
72
 
69
73
  - **Bun:** This project uses [Bun](https://bun.sh/) for package management, script execution, and testing.
70
- - **C++ Compiler:** A compiler with support for C++23 is required. This project is tested with `g++`.
71
- - `g++` (MinGW on Windows, or available via build-essentials on Linux)
74
+ - **C++ Compiler:** A compiler with support for C++23 is required (e.g., `g++` 13+ or `clang` 17+).
75
+ - **Windows:** [MSYS2](https://www.msys2.org/) with `mingw-w64-x86_64-gcc` is recommended.
76
+ - **Linux:** `g++-14` or equivalent.
77
+ - **macOS:** `brew install gcc`.
72
78
 
73
79
  ### Setup
74
80
 
@@ -76,10 +82,11 @@ To contribute to JSPP or run its test suite, follow these steps:
76
82
  ```sh
77
83
  git clone https://github.com/ugo-studio/jspp.git
78
84
  ```
79
- 2. Install dependencies:
85
+ 2. Install dependencies and build the runtime:
80
86
  ```sh
81
87
  bun install
82
88
  ```
89
+ *Note: The `postinstall` script will automatically check for your C++ compiler and precompile the runtime headers and library.*
83
90
 
84
91
  ## Usage
85
92
 
@@ -99,11 +106,9 @@ jspp my-code/test.ts
99
106
 
100
107
  The transpiled C++ file and executable will be generated in the same directory as the input file and cleaned up after execution (unless `--keep-cpp` is used).
101
108
 
102
- You can also run the test suite:
109
+ ### Timing and Reports
103
110
 
104
- ```sh
105
- bun test
106
- ```
111
+ In debug mode (default), JSPP provides a compilation time report using GCC's `-ftime-report`. This helps track the performance of the transpilation and compilation phases.
107
112
 
108
113
  ## Roadmap
109
114
 
@@ -115,7 +120,7 @@ This project is ambitious, and there is a long and exciting road ahead. Here is
115
120
 
116
121
  This phase focuses on building a solid foundation that correctly models JavaScript's core runtime behavior.
117
122
 
118
- - [x] Dynamic Variables & Primitives
123
+ - [x] Dynamic Variables & Primitives (NaN-boxing)
119
124
  - [x] Function Declarations & Arrow Functions
120
125
  - [x] Correct Hoisting for Variables and Functions
121
126
  - [x] Closures & Lexical Scoping
@@ -139,16 +144,19 @@ This phase broadens the range of supported JavaScript syntax and features.
139
144
 
140
145
  This phase focuses on building out the standard library and enabling modular code.
141
146
 
142
- - [ ] **JS Standard Library:** Implementation of common built-in objects. (Currently supports: `Math`, `Symbol`, `Error`, `String`, `Array`, `Object`, `Timer`). **Pending:** `Date`, `Temporal`, `Map`, `Set`, `JSON`, etc.
143
- - [x] **Asynchronous Operations:** Event loop, `Promise`, `async/await`, and timers (`setTimeout`).
147
+ - [x] **JS Standard Library:** Core implementation of `Math`, `Symbol`, `Error`, `String`, `Array`, `Object`, `Timer`.
148
+ - [ ] **Expanded Library:** `Date`, `Temporal`, `Map`, `Set`, `JSON`, `RegExp`.
149
+ - [x] **Asynchronous Operations:** Event loop, `Promise`, `async/await`.
144
150
  - [ ] **Module System:** Support for `import` and `export` to transpile multi-file projects.
145
151
 
146
152
  ### **Phase 4: Optimization & Advanced Features**
147
153
 
148
154
  With a feature-complete transpiler, the focus will shift to performance and advanced capabilities.
149
155
 
156
+ - [x] **Architecture Optimization:** Static library runtime and Precompiled Headers.
150
157
  - [ ] **Performance Benchmarking:** Create a suite to compare transpiled C++ performance against V8.
151
- - [ ] **C++ Interoperability:** Define a clear API for calling C++ functions from the transpiled JavaScript and vice-versa.
158
+ - [ ] **Linker Optimization:** Support for `LLD` or `Mold` linkers.
159
+ - [ ] **C++ Interoperability:** Define a clear API for calling C++ functions from JavaScript.
152
160
 
153
161
  ## Contributing
154
162
 
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,128 +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";
10
- import { getLatestMtime } from "./file-utils.js";
8
+ import { compileCpp } from "./compiler.js";
9
+ import { checkAndRebuildPCH } from "./pch.js";
10
+ import { runOutput } from "./runner.js";
11
11
  import { Spinner } from "./spinner.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"] : ["-O0"];
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, "index.hpp.gch");
55
- let shouldRebuild = false;
56
- try {
57
- const pchStats = await fs.stat(pchFile);
58
- const sourceMtime = await getLatestMtime(preludePath);
59
- if (sourceMtime > pchStats.mtimeMs) {
60
- shouldRebuild = true;
61
- }
62
- }
63
- catch (e) {
64
- shouldRebuild = true;
65
- }
66
- if (shouldRebuild) {
67
- spinner.update("Rebuilding precompiled headers (this may take a while)...");
68
- // Use spawn (async) instead of spawnSync to keep spinner alive
69
- const rebuild = spawn("bun", [
70
- "run",
71
- "scripts/precompile-headers.ts",
72
- ], {
73
- cwd: pkgDir,
74
- stdio: ["ignore", "pipe", "pipe"],
75
- });
76
- const stderrChunks = [];
77
- if (rebuild.stderr) {
78
- rebuild.stderr.on("data", (chunk) => stderrChunks.push(chunk));
79
- }
80
- const exitCode = await new Promise((resolve) => {
81
- rebuild.on("close", (code) => resolve(code ?? 1));
82
- });
83
- if (exitCode !== 0) {
84
- const stderr = Buffer.concat(stderrChunks).toString();
85
- spinner.fail("Failed to rebuild precompiled headers");
86
- console.error(stderr);
87
- process.exit(1);
88
- }
89
- spinner.succeed("Precompiled headers updated");
90
- }
91
- else {
92
- spinner.succeed("Precompiled headers");
93
- }
66
+ await checkAndRebuildPCH(pkgDir, pchDir, mode, preludePath, emsdkEnv, spinner);
94
67
  // 3. Compilation Phase
95
- spinner.text = `Compiling binary...`;
96
- spinner.start();
97
- // Ensure output directory exists
98
- await fs.mkdir(path.dirname(exeFilePath), { recursive: true });
99
- const compile = spawn("g++", [
100
- "-std=c++23",
101
- ...flags,
102
- cppFilePath,
103
- "-o",
104
- exeFilePath,
105
- "-I",
106
- pchDir,
107
- "-I",
108
- preludePath,
109
- ], {
110
- stdio: ["ignore", "pipe", "pipe"],
111
- });
112
- const compileStderrChunks = [];
113
- if (compile.stderr) {
114
- 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);
115
72
  }
116
- const compileExitCode = await new Promise((resolve) => {
117
- compile.on("close", (code) => resolve(code ?? 1));
118
- });
119
- if (compileExitCode !== 0) {
120
- const stderr = Buffer.concat(compileStderrChunks).toString();
121
- spinner.fail(`Compilation failed`);
122
- console.error(stderr);
123
- process.exit(1);
124
- }
125
- spinner.succeed(`Compiled to ${COLORS.green}${COLORS.bold}${path.basename(exeFilePath)}${COLORS.reset}`);
126
73
  // Clean up C++ file if not requested to keep
127
74
  if (!keepCpp) {
128
75
  try {
@@ -133,18 +80,7 @@ async function main() {
133
80
  }
134
81
  }
135
82
  // 4. Execution Phase
136
- console.log(`\n${COLORS.cyan}--- Running Output ---${COLORS.reset}`);
137
- const run = spawn(exeFilePath, scriptArgs, {
138
- stdio: "inherit",
139
- });
140
- const runExitCode = await new Promise((resolve) => {
141
- run.on("close", (code) => resolve(code ?? 1));
142
- });
143
- console.log(`${COLORS.cyan}----------------------${COLORS.reset}\n`);
144
- if (runExitCode !== 0) {
145
- console.error(`${COLORS.red}Execution failed with exit code ${runExitCode}${COLORS.reset}`);
146
- process.exit(1);
147
- }
83
+ await runOutput(exeFilePath, scriptArgs, isWasm);
148
84
  }
149
85
  catch (error) {
150
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
+ }
@@ -0,0 +1,59 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ export async function getLatestMtime(dirPath, filter) {
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, filter);
10
+ if (nestedMtime > maxMtime)
11
+ maxMtime = nestedMtime;
12
+ }
13
+ else {
14
+ if (filter && !filter(entry.name))
15
+ continue;
16
+ const stats = await fs.stat(fullPath);
17
+ if (stats.mtimeMs > maxMtime)
18
+ maxMtime = stats.mtimeMs;
19
+ }
20
+ }
21
+ return maxMtime;
22
+ }
23
+ /**
24
+ * Converts milliseconds to a single-unit, human-readable decimal format.
25
+ * @param ms - The time in milliseconds
26
+ * @returns A formatted string (e.g., "2.52s", "1.5h")
27
+ */
28
+ export function msToHumanReadable(ms) {
29
+ if (ms < 0) {
30
+ throw new Error("Time cannot be negative");
31
+ }
32
+ if (ms === 0) {
33
+ return "0ms";
34
+ }
35
+ const MS_PER_SECOND = 1000;
36
+ const MS_PER_MINUTE = 60 * MS_PER_SECOND;
37
+ const MS_PER_HOUR = 60 * MS_PER_MINUTE;
38
+ const MS_PER_DAY = 24 * MS_PER_HOUR;
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(/\.$/, "");
45
+ };
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";
57
+ }
58
+ return formatValue(ms) + "ms";
59
+ }