@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.
Files changed (64) 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 +44 -61
  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 +63 -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 -2
  38. package/src/prelude/utils/assignment_operators.hpp +40 -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 +87 -87
  42. package/src/prelude/utils/operators_native.hpp +349 -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/number.cpp +137 -92
  47. package/src/prelude/values/object.cpp +163 -122
  48. package/src/prelude/values/prototypes/boolean.hpp +24 -0
  49. package/src/prelude/values/prototypes/number.hpp +8 -1
  50. package/dist/cli/file-utils.js +0 -20
  51. package/dist/cli-utils/args.js +0 -59
  52. package/dist/cli-utils/colors.js +0 -9
  53. package/dist/cli-utils/file-utils.js +0 -20
  54. package/dist/cli-utils/spinner.js +0 -55
  55. package/dist/cli.js +0 -153
  56. package/dist/core/codegen/index.js +0 -88
  57. package/src/prelude/utils/operators_primitive.hpp +0 -337
  58. /package/dist/{ast → interpreter/ast}/symbols.js +0 -0
  59. /package/dist/{ast → interpreter/ast}/types.js +0 -0
  60. /package/dist/{core → interpreter/core}/codegen/visitor.js +0 -0
  61. /package/dist/{core → interpreter/core}/constants.js +0 -0
  62. /package/dist/{core → interpreter/core}/error.js +0 -0
  63. /package/dist/{core → interpreter/core}/parser.js +0 -0
  64. /package/dist/{core → interpreter/core}/traverser.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import ts from "typescript";
2
2
  import { BUILTIN_OBJECTS, Scope } from "../../analysis/scope.js";
3
- import { DeclarationType, DeclaredSymbols } from "../../ast/symbols.js";
3
+ import { DeclarationType, DeclaredSymbols, } from "../../ast/symbols.js";
4
4
  import { CompilerError } from "../error.js";
5
5
  import { CodeGenerator } from "./index.js";
6
6
  /**
@@ -250,7 +250,7 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
250
250
  : ts.isEnumDeclaration(decl)
251
251
  ? DeclarationType.enum
252
252
  : DeclarationType.var;
253
- const hoistName = (nameNode) => {
253
+ const hoistName = (nameNode, isFromDestructuring = false) => {
254
254
  if (ts.isIdentifier(nameNode)) {
255
255
  const name = nameNode.text;
256
256
  if (hoistedSymbols.has(name)) {
@@ -269,6 +269,10 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
269
269
  }
270
270
  else {
271
271
  // Add the symbol to the hoisted symbols
272
+ hoistedSymbols.add(name, {
273
+ type: declType,
274
+ });
275
+ // Get and update symbol features
272
276
  if (ts.isFunctionDeclaration(decl) ||
273
277
  (ts.isVariableDeclaration(decl) && decl.initializer &&
274
278
  nameNode === decl.name &&
@@ -278,22 +282,23 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
278
282
  const funcExpr = ts.isVariableDeclaration(decl)
279
283
  ? decl.initializer
280
284
  : decl;
281
- const isAsync = this.isAsyncFunction(funcExpr);
282
- const isGenerator = this.isGeneratorFunction(funcExpr);
283
- hoistedSymbols.add(name, {
284
- type: declType,
285
- features: { isAsync, isGenerator },
285
+ // Update features
286
+ hoistedSymbols.update(name, {
287
+ features: {
288
+ isAsync: this.isAsyncFunction(funcExpr),
289
+ isGenerator: this.isGeneratorFunction(funcExpr),
290
+ },
286
291
  });
287
292
  // Don't hoist declarations not used as a variable
288
293
  // They will be called with their native lambda/value
289
294
  if (!this.isDeclarationUsedAsValue(decl, scopeNode) &&
290
295
  !this.isDeclarationUsedBeforeInitialization(name, scopeNode)) {
296
+ hoistedSymbols.update(name, {
297
+ checks: { skippedHoisting: true },
298
+ });
291
299
  return "";
292
300
  }
293
301
  }
294
- else {
295
- hoistedSymbols.add(name, { type: declType });
296
- }
297
302
  }
298
303
  const scope = this.getScopeForNode(decl);
299
304
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
@@ -304,6 +309,13 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
304
309
  return `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initializer});\n`;
305
310
  }
306
311
  else {
312
+ if ((isLet || isConst) &&
313
+ !this.isDeclarationUsedBeforeInitialization(name, scopeNode) && !isFromDestructuring) {
314
+ hoistedSymbols.update(name, {
315
+ checks: { skippedHoisting: true },
316
+ });
317
+ return "";
318
+ }
307
319
  return `${this.indent()}jspp::AnyValue ${name} = ${initializer};\n`;
308
320
  }
309
321
  }
@@ -311,7 +323,7 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
311
323
  let code = "";
312
324
  nameNode.elements.forEach((element) => {
313
325
  if (ts.isBindingElement(element)) {
314
- code += hoistName(element.name);
326
+ code += hoistName(element.name, true);
315
327
  }
316
328
  });
317
329
  return code;
@@ -715,6 +727,33 @@ export function isVariableUsedWithoutDeclaration(name, root) {
715
727
  visit(root);
716
728
  return isUsedIllegally;
717
729
  }
730
+ /**
731
+ * Checks if a node (usually a SourceFile) contains any top-level await expressions.
732
+ * This determines if the module function should be a coroutine.
733
+ */
734
+ export function needsTopLevelAwait(node) {
735
+ let found = false;
736
+ const visitor = (n) => {
737
+ if (found)
738
+ return;
739
+ if (ts.isAwaitExpression(n)) {
740
+ found = true;
741
+ return;
742
+ }
743
+ if (ts.isForOfStatement(n) && n.awaitModifier) {
744
+ found = true;
745
+ return;
746
+ }
747
+ // Nested functions/classes have their own context
748
+ if (ts.isFunctionLike(n) || ts.isClassLike(n) ||
749
+ ts.isModuleDeclaration(n)) {
750
+ return;
751
+ }
752
+ ts.forEachChild(n, visitor);
753
+ };
754
+ ts.forEachChild(node, visitor);
755
+ return found;
756
+ }
718
757
  /**
719
758
  * Validates and filters function parameters, checking for illegal "this"
720
759
  * and correctly positioned rest parameters.
@@ -744,3 +783,32 @@ export function validateFunctionParams(parameters) {
744
783
  return p;
745
784
  }) || [];
746
785
  }
786
+ /**
787
+ * Traverses the AST upwards from the given node to find the nearest enclosing
788
+ * `FunctionDeclaration` node that contains a `ReturnStatement` ancestor.
789
+ *
790
+ * This function is useful for determining the function context in which a return
791
+ * statement appears. It starts from the provided node and walks up the parent
792
+ * chain, tracking whether a return statement has been encountered. Once a
793
+ * function declaration is found after encountering a return statement, it is returned.
794
+ *
795
+ * @param node - The starting TypeScript AST node.
796
+ * @returns The enclosing `ts.FunctionDeclaration` node if found, otherwise `undefined`.
797
+ */
798
+ export function findEnclosingFunctionDeclarationFromReturnStatement(node) {
799
+ let current = node;
800
+ let foundReturn = false;
801
+ while (current) {
802
+ if (ts.isFunctionDeclaration(current)) {
803
+ if (foundReturn) {
804
+ return current;
805
+ }
806
+ break;
807
+ }
808
+ if (ts.isReturnStatement(current)) {
809
+ foundReturn = true;
810
+ }
811
+ current = current.parent;
812
+ }
813
+ return undefined;
814
+ }
@@ -0,0 +1,156 @@
1
+ import ts from "typescript";
2
+ import { DeclaredSymbols } from "../../ast/symbols.js";
3
+ import { generateDestructuring } from "./destructuring-handlers.js";
4
+ import { generateLambdaComponents, generateNativeLambda, generateWrappedLambda, } from "./function-handlers.js";
5
+ import { escapeString, findEnclosingFunctionDeclarationFromReturnStatement, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isDeclarationCalledAsFunction, isDeclarationUsedAsValue, isDeclarationUsedBeforeInitialization, isGeneratorFunction, isVariableUsedWithoutDeclaration, markSymbolAsInitialized, needsTopLevelAwait, prepareScopeSymbolsForVisit, validateFunctionParams, } from "./helpers.js";
6
+ import { visit } from "./visitor.js";
7
+ export class CodeGenerator {
8
+ indentationLevel = 0;
9
+ typeAnalyzer;
10
+ isTypescript = false;
11
+ moduleFunctionName;
12
+ globalThisVar;
13
+ uniqueNameCounter = 0;
14
+ isWasm = false;
15
+ wasmExports = [];
16
+ // visitor
17
+ visit = visit;
18
+ // helpers
19
+ getDeclaredSymbols = getDeclaredSymbols;
20
+ generateUniqueName = generateUniqueName;
21
+ generateUniqueExceptionName = generateUniqueExceptionName;
22
+ hoistDeclaration = hoistDeclaration;
23
+ getScopeForNode = getScopeForNode;
24
+ indent = indent;
25
+ escapeString = escapeString;
26
+ getJsVarName = getJsVarName;
27
+ getDerefCode = getDerefCode;
28
+ getReturnCommand = getReturnCommand;
29
+ isBuiltinObject = isBuiltinObject;
30
+ isGeneratorFunction = isGeneratorFunction;
31
+ isAsyncFunction = isAsyncFunction;
32
+ prepareScopeSymbolsForVisit = prepareScopeSymbolsForVisit;
33
+ markSymbolAsInitialized = markSymbolAsInitialized;
34
+ isDeclarationCalledAsFunction = isDeclarationCalledAsFunction;
35
+ isDeclarationUsedAsValue = isDeclarationUsedAsValue;
36
+ isDeclarationUsedBeforeInitialization = isDeclarationUsedBeforeInitialization;
37
+ isVariableUsedWithoutDeclaration = isVariableUsedWithoutDeclaration;
38
+ validateFunctionParams = validateFunctionParams;
39
+ generateDestructuring = generateDestructuring;
40
+ findEnclosingFunctionDeclarationFromReturnStatement = findEnclosingFunctionDeclarationFromReturnStatement;
41
+ // function handlers
42
+ generateLambdaComponents = generateLambdaComponents;
43
+ generateNativeLambda = generateNativeLambda;
44
+ generateWrappedLambda = generateWrappedLambda;
45
+ /**
46
+ * Main entry point for the code generation process.
47
+ */
48
+ generate(ast, analyzer, isTypescript, isWasm = false) {
49
+ this.typeAnalyzer = analyzer;
50
+ this.isTypescript = isTypescript;
51
+ this.isWasm = isWasm;
52
+ this.wasmExports = [];
53
+ this.moduleFunctionName = this.generateUniqueName("__module_entry_point_", this.getDeclaredSymbols(ast));
54
+ this.globalThisVar = this.generateUniqueName("__this_val__", this.getDeclaredSymbols(ast));
55
+ const isAsyncModule = needsTopLevelAwait(ast);
56
+ const moduleReturnType = isAsyncModule
57
+ ? "jspp::JsPromise"
58
+ : "jspp::AnyValue";
59
+ const moduleReturnCommand = isAsyncModule ? "co_return" : "return";
60
+ let declarations = `#include "jspp.hpp"\n#include "library/global_usings.hpp"\n`;
61
+ if (isWasm) {
62
+ declarations += `#include <emscripten.h>\n`;
63
+ }
64
+ declarations += `\n`;
65
+ // module function code
66
+ let moduleCode = `${moduleReturnType} ${this.moduleFunctionName}() {\n`;
67
+ this.indentationLevel++;
68
+ moduleCode +=
69
+ `${this.indent()}jspp::AnyValue ${this.globalThisVar} = global;\n`;
70
+ const context = {
71
+ currentScopeNode: ast,
72
+ isMainContext: true,
73
+ isInsideFunction: true,
74
+ isFunctionBody: true,
75
+ isInsideAsyncFunction: isAsyncModule,
76
+ globalScopeSymbols: new DeclaredSymbols(),
77
+ localScopeSymbols: new DeclaredSymbols(),
78
+ };
79
+ const generatedBody = this.visit(ast, context);
80
+ moduleCode += generatedBody;
81
+ moduleCode +=
82
+ `${this.indent()}${moduleReturnCommand} jspp::Constants::UNDEFINED;\n`;
83
+ this.indentationLevel--;
84
+ moduleCode += "}\n\n";
85
+ // Wasm Exports
86
+ let wasmGlobalPointers = "";
87
+ let wasmWrappers = "";
88
+ if (isWasm) {
89
+ for (const exp of this.wasmExports) {
90
+ const paramTypes = exp.params.map((_, i) => `jspp::AnyValue`);
91
+ const paramList = paramTypes.length > 0
92
+ ? `, ${paramTypes.join(", ")}`
93
+ : "";
94
+ const pointerName = `__wasm_export_ptr_${exp.jsName}`;
95
+ wasmGlobalPointers +=
96
+ `std::function<jspp::AnyValue(jspp::AnyValue${paramList})> ${pointerName} = nullptr;\n`;
97
+ const wrapperParamList = exp.params.map((_, i) => `double p${i}`).join(", ");
98
+ const callArgsList = exp.params.map((_, i) => `jspp::AnyValue::make_number(p${i})`);
99
+ const callArgs = callArgsList.length > 0
100
+ ? `, ${callArgsList.join(", ")}`
101
+ : "";
102
+ wasmWrappers += `extern "C" EMSCRIPTEN_KEEPALIVE\n`;
103
+ wasmWrappers +=
104
+ `double wasm_export_${exp.jsName}(${wrapperParamList}) {\n`;
105
+ wasmWrappers += ` if (!${pointerName}) return 0;\n`;
106
+ wasmWrappers +=
107
+ ` auto res = ${pointerName}(global${callArgs});\n`;
108
+ wasmWrappers +=
109
+ ` return jspp::Operators_Private::ToNumber(res);\n`;
110
+ wasmWrappers += `}\n\n`;
111
+ }
112
+ }
113
+ // main function code
114
+ let mainCode = "int main(int argc, char** argv) {\n";
115
+ this.indentationLevel++;
116
+ mainCode += `${this.indent()}try {\n`;
117
+ this.indentationLevel++;
118
+ mainCode += `${this.indent()}jspp::initialize_runtime();\n`;
119
+ mainCode += `${this.indent()}jspp::setup_process_argv(argc, argv);\n`;
120
+ if (isAsyncModule) {
121
+ mainCode +=
122
+ `${this.indent()}auto p = ${this.moduleFunctionName}();\n`;
123
+ mainCode +=
124
+ `${this.indent()}p.then(nullptr, [](jspp::AnyValue err) {\n`;
125
+ this.indentationLevel++;
126
+ mainCode +=
127
+ `${this.indent()}auto error = std::make_shared<jspp::AnyValue>(err);\n`;
128
+ this.indentationLevel++;
129
+ mainCode +=
130
+ `${this.indent()}console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
131
+ mainCode += `${this.indent()}std::exit(1);\n`;
132
+ this.indentationLevel--;
133
+ mainCode += `${this.indent()}});\n`;
134
+ }
135
+ else {
136
+ mainCode += `${this.indent()}${this.moduleFunctionName}();\n`;
137
+ }
138
+ mainCode += `${this.indent()}jspp::Scheduler::instance().run();\n`;
139
+ this.indentationLevel--;
140
+ mainCode += `${this.indent()}} catch (const std::exception& ex) {\n`;
141
+ this.indentationLevel++;
142
+ mainCode +=
143
+ `${this.indent()}auto error = std::make_shared<jspp::AnyValue>(jspp::Exception::exception_to_any_value(ex));\n`;
144
+ this.indentationLevel++;
145
+ mainCode +=
146
+ `${this.indent()}console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
147
+ mainCode += `${this.indent()}return 1;\n`;
148
+ this.indentationLevel--;
149
+ mainCode += `${this.indent()}}\n`;
150
+ mainCode += `${this.indent()}return 0;\n`;
151
+ this.indentationLevel--;
152
+ mainCode += `}`;
153
+ return declarations + wasmGlobalPointers + wasmWrappers + moduleCode +
154
+ mainCode;
155
+ }
156
+ }
@@ -11,6 +11,15 @@ export function visitIdentifier(node) {
11
11
  return node.text;
12
12
  }
13
13
  export function visitNumericLiteral(node) {
14
+ const getOuterExpr = (n) => !ts.isParenthesizedExpression(n) &&
15
+ n.kind !== ts.SyntaxKind.FirstLiteralToken
16
+ ? n
17
+ : getOuterExpr(n.parent);
18
+ const outerExpr = getOuterExpr(node);
19
+ if (!ts.isPropertyAccessExpression(outerExpr) &&
20
+ !ts.isElementAccessExpression(outerExpr)) {
21
+ return this.escapeString(node.text);
22
+ }
14
23
  if (node.text === "0" || (node.text.startsWith("0.") &&
15
24
  !node.text.substring(2).split("").some((c) => c !== "0")))
16
25
  return "jspp::Constants::ZERO";
@@ -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.2",
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",
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
 
@@ -133,18 +146,32 @@ async function findCppFiles(dir: string): Promise<string[]> {
133
146
  }
134
147
 
135
148
  async function runCommand(cmd: string, args: string[]): Promise<boolean> {
149
+ // console.log(`${COLORS.dim}> ${cmd} ${args.join(" ")}${COLORS.reset}`);
136
150
  return new Promise((resolve) => {
137
- const proc = spawn(cmd, args, { stdio: "ignore" });
151
+ const proc = spawn(cmd, args, {
152
+ stdio: "inherit",
153
+ shell: process.platform === "win32",
154
+ });
138
155
  proc.on("close", (code) => resolve(code === 0));
139
156
  });
140
157
  }
141
158
 
142
159
  async function precompileHeaders() {
143
- console.log(
144
- `${COLORS.bold}${COLORS.cyan}JSPP: Precompiling headers and runtime...${COLORS.reset}\n`,
145
- );
146
-
147
160
  const force = process.argv.includes("--force");
161
+ const jsppCliIsParent = process.argv.includes("--jspp-cli-is-parent");
162
+ const silent = process.argv.includes("--silent");
163
+
164
+ const modeArgIdx = process.argv.indexOf("--mode");
165
+ const targetMode = modeArgIdx !== -1
166
+ ? process.argv[modeArgIdx + 1]
167
+ : undefined;
168
+
169
+ if (!jsppCliIsParent && !silent) {
170
+ console.log(
171
+ `${COLORS.bold}${COLORS.cyan}JSPP: Precompiling headers and runtime...${COLORS.reset}\n`,
172
+ );
173
+ }
174
+
148
175
  try {
149
176
  await fs.mkdir(PRECOMPILED_HEADER_BASE_DIR, { recursive: true });
150
177
 
@@ -154,13 +181,15 @@ async function precompileHeaders() {
154
181
  );
155
182
 
156
183
  for (const mode of MODES) {
184
+ if (targetMode && mode.name !== targetMode) continue;
185
+
157
186
  const modeDir = path.join(PRECOMPILED_HEADER_BASE_DIR, mode.name);
158
187
  const headerPath = path.join(modeDir, "jspp.hpp");
159
188
  const gchPath = path.join(modeDir, "jspp.hpp.gch");
160
189
 
161
190
  const modeLabel = `[${mode.name.toUpperCase()}]`;
162
191
  const spinner = new Spinner(`${modeLabel} Checking headers...`);
163
- spinner.start();
192
+ if (!silent) spinner.start();
164
193
 
165
194
  await fs.mkdir(modeDir, { recursive: true });
166
195
 
@@ -179,6 +208,7 @@ async function precompileHeaders() {
179
208
  }
180
209
 
181
210
  if (shouldBuildGch) {
211
+ if (silent) spinner.start();
182
212
  spinner.update(`${modeLabel} Compiling header...`);
183
213
  await fs.copyFile(
184
214
  path.join(PRELUDE_DIR, "jspp.hpp"),
@@ -186,7 +216,7 @@ async function precompileHeaders() {
186
216
  );
187
217
 
188
218
  const tempGchPath = `${gchPath}.tmp`;
189
- const success = await runCommand("g++", [
219
+ const success = await runCommand(mode.compiler, [
190
220
  "-x",
191
221
  "c++-header",
192
222
  "-std=c++23",
@@ -209,7 +239,9 @@ async function precompileHeaders() {
209
239
  gchRebuilt = true;
210
240
  spinner.succeed(`${modeLabel} PCH Success.`);
211
241
  } else {
212
- spinner.succeed(`${modeLabel} Headers are up-to-date.`);
242
+ if (!silent) {
243
+ spinner.succeed(`${modeLabel} Headers are up-to-date.`);
244
+ }
213
245
  }
214
246
 
215
247
  // --- Incremental Compilation of .cpp files ---
@@ -217,12 +249,16 @@ async function precompileHeaders() {
217
249
  const objFiles: string[] = [];
218
250
  let anyObjRebuilt = false;
219
251
 
220
- const gchMtime = (await fs.stat(gchPath)).mtimeMs;
252
+ // We need gchPath to exist for next check, if it doesn't we probably have a problem or it's a first run
253
+ let gchMtime = 0;
254
+ try {
255
+ gchMtime = (await fs.stat(gchPath)).mtimeMs;
256
+ } catch (e) {}
221
257
 
222
258
  const libSpinner = new Spinner(
223
259
  `${modeLabel} Checking runtime library...`,
224
260
  );
225
- libSpinner.start();
261
+ if (!silent) libSpinner.start();
226
262
 
227
263
  for (let idx = 0; idx < cppFiles.length; idx++) {
228
264
  const cppFile = cppFiles[idx];
@@ -251,12 +287,13 @@ async function precompileHeaders() {
251
287
  }
252
288
 
253
289
  if (shouldCompile) {
290
+ if (silent && !libSpinner["interval"]) libSpinner.start();
254
291
  libSpinner.update(
255
292
  `${modeLabel} Compiling ${relativePath} ${COLORS.dim}[${
256
293
  idx + 1
257
294
  }/${cppFiles.length}]${COLORS.reset}`,
258
295
  );
259
- const success = await runCommand("g++", [
296
+ const success = await runCommand(mode.compiler, [
260
297
  "-c",
261
298
  "-std=c++23",
262
299
  ...mode.flags,
@@ -290,10 +327,11 @@ async function precompileHeaders() {
290
327
  }
291
328
 
292
329
  if (shouldArchive) {
330
+ if (silent && !libSpinner["interval"]) libSpinner.start();
293
331
  libSpinner.update(`${modeLabel} Updating runtime library...`);
294
332
  const tempLibPath = `${libPath}.tmp`;
295
333
 
296
- const success = await runCommand("ar", [
334
+ const success = await runCommand(mode.archiver, [
297
335
  "rcs",
298
336
  tempLibPath,
299
337
  ...objFiles,
@@ -309,14 +347,20 @@ async function precompileHeaders() {
309
347
  await fs.rename(tempLibPath, libPath);
310
348
  libSpinner.succeed(`${modeLabel} Runtime Library Success.`);
311
349
  } else {
312
- libSpinner.succeed(
313
- `${modeLabel} Runtime library is up-to-date.`,
314
- );
350
+ if (!silent) {
351
+ libSpinner.succeed(
352
+ `${modeLabel} Runtime library is up-to-date.`,
353
+ );
354
+ } else {
355
+ libSpinner.stop();
356
+ }
315
357
  }
316
358
  }
317
- console.log(
318
- `\n${COLORS.bold}${COLORS.green}JSPP: Environment ready.${COLORS.reset}\n`,
319
- );
359
+ if (!jsppCliIsParent && !silent) {
360
+ console.log(
361
+ `\n${COLORS.bold}${COLORS.green}JSPP: Environment ready.${COLORS.reset}\n`,
362
+ );
363
+ }
320
364
  } catch (error: any) {
321
365
  console.error(`${COLORS.red}Error: ${error.message}${COLORS.reset}`);
322
366
  process.exit(1);