@ugo-studio/jspp 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/args.js +22 -0
- package/dist/cli/compiler.js +53 -0
- package/dist/cli/index.js +43 -117
- package/dist/cli/pch.js +71 -0
- package/dist/cli/runner.js +23 -0
- package/dist/cli/spinner.js +27 -11
- package/dist/cli/transpiler.js +20 -0
- package/dist/cli/utils.js +25 -27
- package/dist/cli/wasm.js +70 -0
- package/dist/index.js +17 -6
- package/dist/{analysis → interpreter/analysis}/scope.js +34 -1
- package/dist/{analysis → interpreter/analysis}/typeAnalyzer.js +542 -3
- package/dist/{core → interpreter/core}/codegen/class-handlers.js +1 -1
- package/dist/{core → interpreter/core}/codegen/control-flow-handlers.js +2 -2
- package/dist/{core → interpreter/core}/codegen/declaration-handlers.js +21 -9
- package/dist/{core → interpreter/core}/codegen/destructuring-handlers.js +3 -3
- package/dist/{core → interpreter/core}/codegen/expression-handlers.js +44 -61
- package/dist/{core → interpreter/core}/codegen/function-handlers.js +108 -61
- package/dist/{core → interpreter/core}/codegen/helpers.js +79 -11
- package/dist/interpreter/core/codegen/index.js +156 -0
- package/dist/{core → interpreter/core}/codegen/literal-handlers.js +9 -0
- package/dist/{core → interpreter/core}/codegen/statement-handlers.js +39 -1
- package/package.json +6 -4
- package/scripts/precompile-headers.ts +63 -19
- package/scripts/setup-emsdk.ts +114 -0
- package/src/prelude/any_value.cpp +851 -599
- package/src/prelude/any_value.hpp +28 -30
- package/src/prelude/jspp.hpp +3 -1
- package/src/prelude/library/boolean.cpp +30 -0
- package/src/prelude/library/boolean.hpp +14 -0
- package/src/prelude/library/error.cpp +2 -2
- package/src/prelude/library/global.cpp +2 -0
- package/src/prelude/library/math.cpp +46 -43
- package/src/prelude/library/math.hpp +2 -0
- package/src/prelude/library/object.cpp +1 -1
- package/src/prelude/types.hpp +10 -9
- package/src/prelude/utils/access.hpp +2 -2
- package/src/prelude/utils/assignment_operators.hpp +40 -20
- package/src/prelude/utils/log_any_value/log_any_value.hpp +1 -1
- package/src/prelude/utils/log_any_value/primitives.hpp +1 -1
- package/src/prelude/utils/operators.hpp +87 -87
- package/src/prelude/utils/operators_native.hpp +349 -0
- package/src/prelude/utils/well_known_symbols.hpp +13 -13
- package/src/prelude/values/array.cpp +3 -3
- package/src/prelude/values/boolean.cpp +64 -0
- package/src/prelude/values/number.cpp +137 -92
- package/src/prelude/values/object.cpp +163 -122
- package/src/prelude/values/prototypes/boolean.hpp +24 -0
- package/src/prelude/values/prototypes/number.hpp +8 -1
- package/dist/cli/file-utils.js +0 -20
- package/dist/cli-utils/args.js +0 -59
- package/dist/cli-utils/colors.js +0 -9
- package/dist/cli-utils/file-utils.js +0 -20
- package/dist/cli-utils/spinner.js +0 -55
- package/dist/cli.js +0 -153
- package/dist/core/codegen/index.js +0 -88
- package/src/prelude/utils/operators_primitive.hpp +0 -337
- /package/dist/{ast → interpreter/ast}/symbols.js +0 -0
- /package/dist/{ast → interpreter/ast}/types.js +0 -0
- /package/dist/{core → interpreter/core}/codegen/visitor.js +0 -0
- /package/dist/{core → interpreter/core}/constants.js +0 -0
- /package/dist/{core → interpreter/core}/error.js +0 -0
- /package/dist/{core → interpreter/core}/parser.js +0 -0
- /package/dist/{core → interpreter/core}/traverser.js +0 -0
|
@@ -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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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.
|
|
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
|
|
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, {
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
313
|
-
|
|
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
|
-
|
|
318
|
-
|
|
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);
|