@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
@@ -52,6 +52,21 @@ export function visitSourceFile(node, context) {
52
52
  // Mark before further visits
53
53
  this.markSymbolAsInitialized(funcName, contextForFunctions.globalScopeSymbols, contextForFunctions.localScopeSymbols);
54
54
  this.markSymbolAsInitialized(funcName, globalScopeSymbols, localScopeSymbols);
55
+ // Check for @export comment
56
+ let exported = false;
57
+ if (this.isWasm) {
58
+ const fullText = sourceFile.text;
59
+ const commentRanges = ts.getLeadingCommentRanges(fullText, stmt.pos);
60
+ if (commentRanges) {
61
+ for (const range of commentRanges) {
62
+ const comment = fullText.substring(range.pos, range.end);
63
+ if (comment.includes("@export")) {
64
+ exported = true;
65
+ break;
66
+ }
67
+ }
68
+ }
69
+ }
55
70
  // Update features in the symbol registry
56
71
  const nativeName = this.generateUniqueName(`__${funcName}_native_`, hoistedSymbols);
57
72
  const argumentKeywordIsUsed = this.isVariableUsedWithoutDeclaration("arguments", stmt.body);
@@ -72,9 +87,18 @@ export function visitSourceFile(node, context) {
72
87
  noTypeSignature: true,
73
88
  });
74
89
  // Generate native lambda
75
- if (this.isDeclarationCalledAsFunction(stmt, node)) {
90
+ if (this.isDeclarationCalledAsFunction(stmt, node) || exported) {
76
91
  const nativeLambda = this.generateNativeLambda(lambdaComps);
77
92
  code += `${this.indent()}auto ${nativeName} = ${nativeLambda};\n`;
93
+ if (exported) {
94
+ this.wasmExports.push({
95
+ jsName: funcName,
96
+ nativeName,
97
+ params: Array.from(stmt.parameters),
98
+ });
99
+ code +=
100
+ `${this.indent()}__wasm_export_ptr_${funcName} = ${nativeName};\n`;
101
+ }
78
102
  }
79
103
  // Generate AnyValue wrapped lamda
80
104
  if (this.isDeclarationUsedAsValue(stmt, node) ||
@@ -799,6 +823,20 @@ export function visitReturnStatement(node, context) {
799
823
  !typeInfo.isBuiltin) {
800
824
  finalExpr = this.getDerefCode(exprText, this.getJsVarName(expr), context, typeInfo);
801
825
  }
826
+ const exprReturnType = this.typeAnalyzer.inferNodeReturnType(expr);
827
+ if (exprReturnType === "number" &&
828
+ context.isInsideNativeLambda &&
829
+ context.isInsideFunction) {
830
+ const funcDecl = this
831
+ .findEnclosingFunctionDeclarationFromReturnStatement(expr);
832
+ if (funcDecl) {
833
+ const funcReturnType = this.typeAnalyzer
834
+ .inferFunctionReturnType(funcDecl);
835
+ if (funcReturnType === "number") {
836
+ finalExpr = `${finalExpr}.as_double()`;
837
+ }
838
+ }
839
+ }
802
840
  }
803
841
  return `${this.indent()}${returnCmd} ${finalExpr};\n`;
804
842
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ugo-studio/jspp",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "A modern transpiler that converts JavaScript code into high-performance, standard C++23.",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.ts",
@@ -14,14 +14,16 @@
14
14
  "scripts"
15
15
  ],
16
16
  "scripts": {
17
- "postinstall": "bun run scripts/setup-compiler.ts && bun run scripts/precompile-headers.ts",
17
+ "postinstall": "bun run scripts/setup-compiler.ts && bun run scripts/setup-emsdk.ts && bun run scripts/precompile-headers.ts --mode debug",
18
18
  "dev": "bun run src/cli/index.ts",
19
19
  "typecheck": "tsc --noEmit",
20
- "precompile": "bun run scripts/precompile-headers.ts",
21
20
  "test": "bun test",
22
21
  "build": "tsc",
23
22
  "prepack": "bun run build",
24
- "publish:npm": "npm publish --access=public"
23
+ "publish:npm": "npm publish --access=public",
24
+ "script:setup-compiler": "bun run scripts/setup-compiler.ts",
25
+ "script:setup-emsdk": "bun run scripts/setup-emsdk.ts",
26
+ "script:precompile-headers": "bun run scripts/precompile-headers.ts"
25
27
  },
26
28
  "devDependencies": {
27
29
  "@types/bun": "latest"
@@ -1,4 +1,4 @@
1
- import { spawn, spawnSync } from "child_process";
1
+ import { spawn } from "child_process";
2
2
  import fs from "fs/promises";
3
3
  import path from "path";
4
4
 
@@ -87,10 +87,23 @@ const MODES = [
87
87
  {
88
88
  name: "debug",
89
89
  flags: ["-Og"],
90
+ linkerFlags: [],
91
+ compiler: "g++",
92
+ archiver: "ar",
90
93
  },
91
94
  {
92
95
  name: "release",
93
96
  flags: ["-O3", "-DNDEBUG"],
97
+ linkerFlags: [],
98
+ compiler: "g++",
99
+ archiver: "ar",
100
+ },
101
+ {
102
+ name: "wasm",
103
+ flags: ["-O3", "-DNDEBUG"],
104
+ linkerFlags: ["-sASYNCIFY", "-sALLOW_MEMORY_GROWTH=1"],
105
+ compiler: "em++",
106
+ archiver: "emar",
94
107
  },
95
108
  ];
96
109
 
@@ -99,6 +112,14 @@ if (process.platform === "win32") {
99
112
  MODES[1].flags.push("-Wa,-mbig-obj");
100
113
  }
101
114
 
115
+ const pkgDir = path.dirname(import.meta.dirname);
116
+ const emsdkEnv = {
117
+ ...process.env,
118
+ PATH: `${path.join(pkgDir, ".emsdk")}${path.delimiter}${
119
+ path.join(pkgDir, ".emsdk", "upstream", "emscripten")
120
+ }${path.delimiter}${process.env.PATH}`,
121
+ };
122
+
102
123
  async function getLatestMtime(
103
124
  dirPath: string,
104
125
  filter?: (name: string) => boolean,
@@ -134,17 +155,31 @@ async function findCppFiles(dir: string): Promise<string[]> {
134
155
 
135
156
  async function runCommand(cmd: string, args: string[]): Promise<boolean> {
136
157
  return new Promise((resolve) => {
137
- const proc = spawn(cmd, args, { stdio: "ignore" });
158
+ const proc = spawn(cmd, args, {
159
+ stdio: "inherit",
160
+ shell: process.platform === "win32",
161
+ env: emsdkEnv,
162
+ });
138
163
  proc.on("close", (code) => resolve(code === 0));
139
164
  });
140
165
  }
141
166
 
142
167
  async function precompileHeaders() {
143
- console.log(
144
- `${COLORS.bold}${COLORS.cyan}JSPP: Precompiling headers and runtime...${COLORS.reset}\n`,
145
- );
146
-
147
168
  const force = process.argv.includes("--force");
169
+ const jsppCliIsParent = process.argv.includes("--jspp-cli-is-parent");
170
+ const silent = process.argv.includes("--silent");
171
+
172
+ const modeArgIdx = process.argv.indexOf("--mode");
173
+ const targetMode = modeArgIdx !== -1
174
+ ? process.argv[modeArgIdx + 1]
175
+ : undefined;
176
+
177
+ if (!jsppCliIsParent && !silent) {
178
+ console.log(
179
+ `${COLORS.bold}${COLORS.cyan}JSPP: Precompiling headers and runtime...${COLORS.reset}\n`,
180
+ );
181
+ }
182
+
148
183
  try {
149
184
  await fs.mkdir(PRECOMPILED_HEADER_BASE_DIR, { recursive: true });
150
185
 
@@ -154,13 +189,15 @@ async function precompileHeaders() {
154
189
  );
155
190
 
156
191
  for (const mode of MODES) {
192
+ if (targetMode && mode.name !== targetMode) continue;
193
+
157
194
  const modeDir = path.join(PRECOMPILED_HEADER_BASE_DIR, mode.name);
158
195
  const headerPath = path.join(modeDir, "jspp.hpp");
159
196
  const gchPath = path.join(modeDir, "jspp.hpp.gch");
160
197
 
161
198
  const modeLabel = `[${mode.name.toUpperCase()}]`;
162
199
  const spinner = new Spinner(`${modeLabel} Checking headers...`);
163
- spinner.start();
200
+ if (!silent) spinner.start();
164
201
 
165
202
  await fs.mkdir(modeDir, { recursive: true });
166
203
 
@@ -179,6 +216,7 @@ async function precompileHeaders() {
179
216
  }
180
217
 
181
218
  if (shouldBuildGch) {
219
+ if (silent) spinner.start();
182
220
  spinner.update(`${modeLabel} Compiling header...`);
183
221
  await fs.copyFile(
184
222
  path.join(PRELUDE_DIR, "jspp.hpp"),
@@ -186,7 +224,7 @@ async function precompileHeaders() {
186
224
  );
187
225
 
188
226
  const tempGchPath = `${gchPath}.tmp`;
189
- const success = await runCommand("g++", [
227
+ const success = await runCommand(mode.compiler, [
190
228
  "-x",
191
229
  "c++-header",
192
230
  "-std=c++23",
@@ -209,7 +247,9 @@ async function precompileHeaders() {
209
247
  gchRebuilt = true;
210
248
  spinner.succeed(`${modeLabel} PCH Success.`);
211
249
  } else {
212
- spinner.succeed(`${modeLabel} Headers are up-to-date.`);
250
+ if (!silent) {
251
+ spinner.succeed(`${modeLabel} Headers are up-to-date.`);
252
+ }
213
253
  }
214
254
 
215
255
  // --- Incremental Compilation of .cpp files ---
@@ -217,12 +257,16 @@ async function precompileHeaders() {
217
257
  const objFiles: string[] = [];
218
258
  let anyObjRebuilt = false;
219
259
 
220
- const gchMtime = (await fs.stat(gchPath)).mtimeMs;
260
+ // We need gchPath to exist for next check, if it doesn't we probably have a problem or it's a first run
261
+ let gchMtime = 0;
262
+ try {
263
+ gchMtime = (await fs.stat(gchPath)).mtimeMs;
264
+ } catch (e) {}
221
265
 
222
266
  const libSpinner = new Spinner(
223
267
  `${modeLabel} Checking runtime library...`,
224
268
  );
225
- libSpinner.start();
269
+ if (!silent) libSpinner.start();
226
270
 
227
271
  for (let idx = 0; idx < cppFiles.length; idx++) {
228
272
  const cppFile = cppFiles[idx];
@@ -251,12 +295,13 @@ async function precompileHeaders() {
251
295
  }
252
296
 
253
297
  if (shouldCompile) {
298
+ if (silent && !libSpinner["interval"]) libSpinner.start();
254
299
  libSpinner.update(
255
300
  `${modeLabel} Compiling ${relativePath} ${COLORS.dim}[${
256
301
  idx + 1
257
302
  }/${cppFiles.length}]${COLORS.reset}`,
258
303
  );
259
- const success = await runCommand("g++", [
304
+ const success = await runCommand(mode.compiler, [
260
305
  "-c",
261
306
  "-std=c++23",
262
307
  ...mode.flags,
@@ -290,10 +335,11 @@ async function precompileHeaders() {
290
335
  }
291
336
 
292
337
  if (shouldArchive) {
338
+ if (silent && !libSpinner["interval"]) libSpinner.start();
293
339
  libSpinner.update(`${modeLabel} Updating runtime library...`);
294
340
  const tempLibPath = `${libPath}.tmp`;
295
341
 
296
- const success = await runCommand("ar", [
342
+ const success = await runCommand(mode.archiver, [
297
343
  "rcs",
298
344
  tempLibPath,
299
345
  ...objFiles,
@@ -309,14 +355,20 @@ async function precompileHeaders() {
309
355
  await fs.rename(tempLibPath, libPath);
310
356
  libSpinner.succeed(`${modeLabel} Runtime Library Success.`);
311
357
  } else {
312
- libSpinner.succeed(
313
- `${modeLabel} Runtime library is up-to-date.`,
314
- );
358
+ if (!silent) {
359
+ libSpinner.succeed(
360
+ `${modeLabel} Runtime library is up-to-date.`,
361
+ );
362
+ } else {
363
+ libSpinner.stop();
364
+ }
315
365
  }
316
366
  }
317
- console.log(
318
- `\n${COLORS.bold}${COLORS.green}JSPP: Environment ready.${COLORS.reset}\n`,
319
- );
367
+ if (!jsppCliIsParent && !silent) {
368
+ console.log(
369
+ `\n${COLORS.bold}${COLORS.green}JSPP: Environment ready.${COLORS.reset}\n`,
370
+ );
371
+ }
320
372
  } catch (error: any) {
321
373
  console.error(`${COLORS.red}Error: ${error.message}${COLORS.reset}`);
322
374
  process.exit(1);
@@ -0,0 +1,114 @@
1
+ import { spawnSync } from "child_process";
2
+ import fs from "fs";
3
+ import { platform } from "os";
4
+ import path from "path";
5
+
6
+ const COLORS = {
7
+ reset: "\x1b[0m",
8
+ green: "\x1b[32m",
9
+ yellow: "\x1b[33m",
10
+ red: "\x1b[31m",
11
+ cyan: "\x1b[36m",
12
+ bold: "\x1b[1m",
13
+ };
14
+
15
+ const EMSDK_DIR = path.resolve(process.cwd(), ".emsdk");
16
+ const isWin = platform() === "win32";
17
+ const emsdkCmd = isWin ? "emsdk.bat" : "./emsdk";
18
+
19
+ function checkEmcc(): boolean {
20
+ try {
21
+ const result = spawnSync("emcc", ["--version"], { encoding: "utf8" });
22
+ return result.status === 0;
23
+ } catch (e) {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ function runCommand(
29
+ cmd: string,
30
+ args: string[],
31
+ cwd: string = process.cwd(),
32
+ ): boolean {
33
+ console.log(`${COLORS.cyan}> ${cmd} ${args.join(" ")}${COLORS.reset}`);
34
+ const result = spawnSync(cmd, args, {
35
+ cwd,
36
+ stdio: "inherit",
37
+ shell: isWin,
38
+ });
39
+ return result.status === 0;
40
+ }
41
+
42
+ async function setup() {
43
+ console.log(
44
+ `${COLORS.bold}${COLORS.cyan}JSPP: Setting up Emscripten SDK for Wasm support...${COLORS.reset}`,
45
+ );
46
+
47
+ if (checkEmcc()) {
48
+ console.log(
49
+ `${COLORS.green}✔ Emscripten (emcc) found in PATH.${COLORS.reset}`,
50
+ );
51
+ return;
52
+ }
53
+
54
+ if (fs.existsSync(path.join(EMSDK_DIR, emsdkCmd))) {
55
+ console.log(
56
+ `${COLORS.yellow}ℹ Local EMSDK found in .emsdk directory.${COLORS.reset}`,
57
+ );
58
+ } else {
59
+ console.log(
60
+ `${COLORS.yellow}ℹ EMSDK not found. Cloning from GitHub...${COLORS.reset}`,
61
+ );
62
+ if (
63
+ !runCommand("git", [
64
+ "clone",
65
+ "https://github.com/emscripten-core/emsdk.git",
66
+ ".emsdk",
67
+ ])
68
+ ) {
69
+ console.error(
70
+ `${COLORS.red}Error: Failed to clone EMSDK repository. Make sure 'git' is installed.${COLORS.reset}`,
71
+ );
72
+ return;
73
+ }
74
+ }
75
+
76
+ console.log(
77
+ `${COLORS.cyan}Installing latest Emscripten SDK...${COLORS.reset}`,
78
+ );
79
+ if (!runCommand(emsdkCmd, ["install", "latest"], EMSDK_DIR)) {
80
+ console.error(
81
+ `${COLORS.red}Error: Failed to install Emscripten SDK.${COLORS.reset}`,
82
+ );
83
+ return;
84
+ }
85
+
86
+ console.log(
87
+ `${COLORS.cyan}Activating latest Emscripten SDK...${COLORS.reset}`,
88
+ );
89
+ if (
90
+ !runCommand(emsdkCmd, ["activate", "latest", "--permanent"], EMSDK_DIR)
91
+ ) {
92
+ console.error(
93
+ `${COLORS.red}Error: Failed to activate Emscripten SDK.${COLORS.reset}`,
94
+ );
95
+ return;
96
+ }
97
+
98
+ console.log(
99
+ `\n${COLORS.green}✔ Emscripten SDK setup complete.${COLORS.reset}`,
100
+ );
101
+ console.log(
102
+ `${COLORS.yellow}Note: To use emcc in your current terminal, you may need to run:${COLORS.reset}`,
103
+ );
104
+ if (isWin) {
105
+ console.log(`${COLORS.bold} .emsdk\\emsdk_env.bat${COLORS.reset}`);
106
+ } else {
107
+ console.log(
108
+ `${COLORS.bold} source .emsdk/emsdk_env.sh${COLORS.reset}`,
109
+ );
110
+ }
111
+ console.log("");
112
+ }
113
+
114
+ setup();