@ugo-studio/jspp 0.1.5 → 0.1.7
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/LICENSE +25 -21
- package/README.md +2 -16
- package/dist/ast/symbols.js +3 -0
- package/dist/cli.js +7 -4
- package/dist/core/codegen/control-flow-handlers.js +28 -9
- package/dist/core/codegen/expression-handlers.js +7 -4
- package/dist/core/codegen/function-handlers.js +45 -27
- package/dist/core/codegen/helpers.js +24 -17
- package/dist/core/codegen/index.js +16 -9
- package/dist/core/codegen/statement-handlers.js +242 -58
- package/package.json +2 -1
- package/src/prelude/any_value.hpp +27 -1
- package/src/prelude/any_value_access.hpp +161 -122
- package/src/prelude/any_value_helpers.hpp +2 -0
- package/src/prelude/index.hpp +3 -0
- package/src/prelude/library/promise.hpp +6 -14
- package/src/prelude/types.hpp +6 -0
- package/src/prelude/utils/access.hpp +35 -2
- package/src/prelude/values/array.hpp +2 -2
- package/src/prelude/values/async_iterator.hpp +79 -0
- package/src/prelude/values/function.hpp +2 -1
- package/src/prelude/values/helpers/array.hpp +3 -0
- package/src/prelude/values/helpers/async_iterator.hpp +275 -0
- package/src/prelude/values/helpers/function.hpp +4 -0
- package/src/prelude/values/helpers/promise.hpp +10 -3
- package/src/prelude/values/promise.hpp +4 -8
- package/src/prelude/values/prototypes/async_iterator.hpp +50 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
copies
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# JSPP (JavaScript++)
|
|
2
2
|
|
|
3
|
-
[](https://github.com/ugo-studio/jspp/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
6
|
**JSPP is a modern, experimental transpiler that converts JavaScript code into high-performance, standard C++23.**
|
|
@@ -33,24 +33,10 @@ JSPP reserves certain keywords to avoid conflicts with the generated C++ code an
|
|
|
33
33
|
|
|
34
34
|
- `std`: Reserved to prevent conflicts with the C++ standard library namespace.
|
|
35
35
|
- `jspp`: Reserved for internal use by the JSPP transpiler.
|
|
36
|
-
- `co_yield`: Reserved to prevent conflicts with the c++
|
|
37
|
-
- `co_return`: Reserved to prevent conflicts with the c++ generator functions.
|
|
36
|
+
- `co_yield`,`co_return`,`co_await`: Reserved to prevent conflicts with the c++ couroutine keywords.
|
|
38
37
|
|
|
39
38
|
Using these keywords as variable names will result in a `SyntaxError`.
|
|
40
39
|
|
|
41
|
-
## How It Works
|
|
42
|
-
|
|
43
|
-
The transpilation process is a classic three-stage pipeline:
|
|
44
|
-
|
|
45
|
-
1. **Parsing:** The incoming JavaScript code is parsed into an Abstract Syntax Tree (AST) using the powerful TypeScript compiler API. This gives us a structured, traversable representation of the code.
|
|
46
|
-
|
|
47
|
-
2. **Analysis:** The `TypeAnalyzer` traverses the AST. While it doesn't perform traditional static type checking, it plays a crucial role in understanding the code's structure. It identifies scopes (global, function, block) and detects when variables are "captured" by closures.
|
|
48
|
-
|
|
49
|
-
3. **Code Generation:** The `CodeGenerator` performs a final traversal of the AST. It translates each node into its C++ equivalent.
|
|
50
|
-
- All variables are declared as `std::shared_ptr<AnyValue>` (where `AnyValue` uses a [Tagged Union](https://en.wikipedia.org/wiki/Tagged_union) method for managing dynamic types like JavaScript). This approach elegantly mimics JavaScript's dynamic types and reference-based memory model.
|
|
51
|
-
- Closures are implemented as C++ lambdas that capture `shared_ptr`s by value, ensuring variable lifetimes are correctly extended beyond their original scope.
|
|
52
|
-
- The entire script is wrapped into a single `main` function, with hoisting logic carefully replicated to ensure correct execution order.
|
|
53
|
-
|
|
54
40
|
## Installation
|
|
55
41
|
|
|
56
42
|
To use JSPP as a command-line tool, install it globally via npm:
|
package/dist/ast/symbols.js
CHANGED
package/dist/cli.js
CHANGED
|
@@ -27,8 +27,11 @@ async function main() {
|
|
|
27
27
|
console.log(`${COLORS.bold}JSPP Compiler${COLORS.reset} ${COLORS.dim}v${pkg.version}${COLORS.reset}`);
|
|
28
28
|
console.log(`Mode: ${isRelease ? COLORS.green : COLORS.yellow}${mode.toUpperCase()}${COLORS.reset}\n`);
|
|
29
29
|
const flags = isRelease
|
|
30
|
-
? ["-O3", "-DNDEBUG"
|
|
31
|
-
: ["-O0"
|
|
30
|
+
? ["-O3", "-DNDEBUG"]
|
|
31
|
+
: ["-O0"];
|
|
32
|
+
if (process.platform === "win32") {
|
|
33
|
+
flags.push("-Wa,-mbig-obj");
|
|
34
|
+
}
|
|
32
35
|
const pchDir = path.resolve(process.cwd(), "prelude-build", mode);
|
|
33
36
|
const spinner = new Spinner("Initializing...");
|
|
34
37
|
try {
|
|
@@ -42,7 +45,7 @@ async function main() {
|
|
|
42
45
|
// Ensure directory for cpp file exists (should exist as it's source dir, but for safety if we change logic)
|
|
43
46
|
await fs.mkdir(path.dirname(cppFilePath), { recursive: true });
|
|
44
47
|
await fs.writeFile(cppFilePath, cppCode);
|
|
45
|
-
spinner.succeed(`Generated
|
|
48
|
+
spinner.succeed(`Generated cpp`);
|
|
46
49
|
// 2. Precompiled Header Check
|
|
47
50
|
spinner.text = "Checking precompiled headers...";
|
|
48
51
|
spinner.start();
|
|
@@ -76,7 +79,7 @@ async function main() {
|
|
|
76
79
|
spinner.succeed("Precompiled headers updated");
|
|
77
80
|
}
|
|
78
81
|
else {
|
|
79
|
-
spinner.succeed("Precompiled headers
|
|
82
|
+
spinner.succeed("Precompiled headers");
|
|
80
83
|
}
|
|
81
84
|
// 3. Compilation Phase
|
|
82
85
|
spinner.text = `Compiling binary...`;
|
|
@@ -70,7 +70,7 @@ export function visitForStatement(node, context) {
|
|
|
70
70
|
...context,
|
|
71
71
|
currentLabel: undefined,
|
|
72
72
|
isFunctionBody: false,
|
|
73
|
-
});
|
|
73
|
+
}).trim();
|
|
74
74
|
if (ts.isBlock(node.statement)) {
|
|
75
75
|
let blockContent = statementCode.substring(1, statementCode.length - 2); // remove curly braces
|
|
76
76
|
if (context.currentLabel) {
|
|
@@ -218,14 +218,27 @@ export function visitForOfStatement(node, context) {
|
|
|
218
218
|
const iterator = this.generateUniqueName("__iter", declaredSymbols);
|
|
219
219
|
const nextFunc = this.generateUniqueName("__next_func", declaredSymbols);
|
|
220
220
|
const nextRes = this.generateUniqueName("__next_res", declaredSymbols);
|
|
221
|
+
const isAwait = forOf.awaitModifier !== undefined;
|
|
221
222
|
const varName = this.getJsVarName(forOf.expression);
|
|
222
223
|
code += `${this.indent()}auto ${iterableRef} = ${derefIterable};\n`;
|
|
223
|
-
|
|
224
|
-
|
|
224
|
+
if (isAwait) {
|
|
225
|
+
code +=
|
|
226
|
+
`${this.indent()}auto ${iterator} = jspp::Access::get_async_object_value_iterator(${iterableRef}, ${varName});\n`;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
code +=
|
|
230
|
+
`${this.indent()}auto ${iterator} = jspp::Access::get_object_value_iterator(${iterableRef}, ${varName});\n`;
|
|
231
|
+
}
|
|
225
232
|
code +=
|
|
226
233
|
`${this.indent()}auto ${nextFunc} = ${iterator}.get_own_property("next");\n`;
|
|
227
|
-
|
|
228
|
-
|
|
234
|
+
if (isAwait) {
|
|
235
|
+
code +=
|
|
236
|
+
`${this.indent()}auto ${nextRes} = co_await ${nextFunc}.call(${iterator}, {}, "next");\n`;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
code +=
|
|
240
|
+
`${this.indent()}auto ${nextRes} = ${nextFunc}.call(${iterator}, {}, "next");\n`;
|
|
241
|
+
}
|
|
229
242
|
code +=
|
|
230
243
|
`${this.indent()}while (!is_truthy(${nextRes}.get_own_property("done"))) {\n`;
|
|
231
244
|
this.indentationLevel++;
|
|
@@ -239,8 +252,14 @@ export function visitForOfStatement(node, context) {
|
|
|
239
252
|
if (context.currentLabel) {
|
|
240
253
|
code += `${this.indent()}${context.currentLabel}_continue:;\n`;
|
|
241
254
|
}
|
|
242
|
-
|
|
243
|
-
|
|
255
|
+
if (isAwait) {
|
|
256
|
+
code +=
|
|
257
|
+
`${this.indent()}${nextRes} = co_await ${nextFunc}.call(${iterator}, {}, "next");\n`;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
code +=
|
|
261
|
+
`${this.indent()}${nextRes} = ${nextFunc}.call(${iterator}, {}, "next");\n`;
|
|
262
|
+
}
|
|
244
263
|
this.indentationLevel--;
|
|
245
264
|
code += `${this.indent()}}\n`;
|
|
246
265
|
this.indentationLevel--; // Exit the scope for the for-of loop
|
|
@@ -269,7 +288,7 @@ export function visitWhileStatement(node, context) {
|
|
|
269
288
|
...context,
|
|
270
289
|
currentLabel: undefined,
|
|
271
290
|
isFunctionBody: false,
|
|
272
|
-
});
|
|
291
|
+
}).trim();
|
|
273
292
|
if (ts.isBlock(node.statement)) {
|
|
274
293
|
let blockContent = statementCode.substring(1, statementCode.length - 2); // remove curly braces
|
|
275
294
|
if (context.currentLabel) {
|
|
@@ -309,7 +328,7 @@ export function visitDoStatement(node, context) {
|
|
|
309
328
|
...context,
|
|
310
329
|
currentLabel: undefined,
|
|
311
330
|
isFunctionBody: false,
|
|
312
|
-
});
|
|
331
|
+
}).trim();
|
|
313
332
|
if (ts.isBlock(node.statement)) {
|
|
314
333
|
let blockContent = statementCode.substring(1, statementCode.length - 2); // remove curly braces
|
|
315
334
|
if (context.currentLabel) {
|
|
@@ -28,8 +28,9 @@ export function visitObjectPropertyName(node, context) {
|
|
|
28
28
|
export function visitObjectLiteralExpression(node, context) {
|
|
29
29
|
const obj = node;
|
|
30
30
|
const objVar = this.generateUniqueName("__obj_", this.getDeclaredSymbols(node));
|
|
31
|
-
let code = `([&]() {
|
|
32
|
-
|
|
31
|
+
let code = `([&]() {\n`;
|
|
32
|
+
code +=
|
|
33
|
+
`${this.indent()} auto ${objVar} = jspp::AnyValue::make_object_with_proto({}, ::Object.get_own_property("prototype"));\n`;
|
|
33
34
|
this.indentationLevel++;
|
|
34
35
|
for (const prop of obj.properties) {
|
|
35
36
|
if (ts.isPropertyAssignment(prop)) {
|
|
@@ -101,6 +102,8 @@ ${this.indent()} auto ${objVar} = jspp::AnyValue::make_object_with_proto({}, ::
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
this.indentationLevel--;
|
|
105
|
+
// code +=
|
|
106
|
+
// `${this.indent()} ${returnCmd} ${objVar};\n${this.indent()}} )() ))`;
|
|
104
107
|
code += `${this.indent()} return ${objVar};\n${this.indent()}})()`;
|
|
105
108
|
return code;
|
|
106
109
|
}
|
|
@@ -593,7 +596,7 @@ export function visitCallExpression(node, context) {
|
|
|
593
596
|
if (callExpr.questionDotToken) {
|
|
594
597
|
return `jspp::Access::optional_call(${derefObj}.get_own_property("${propName}"), ${derefObj}, ${argsSpan}, "${this.escapeString(propName)}")`;
|
|
595
598
|
}
|
|
596
|
-
return
|
|
599
|
+
return `${derefObj}.call_own_property("${propName}", ${argsSpan})`;
|
|
597
600
|
}
|
|
598
601
|
// Handle obj[method]() -> pass obj as 'this'
|
|
599
602
|
if (ts.isElementAccessExpression(callee)) {
|
|
@@ -628,7 +631,7 @@ export function visitCallExpression(node, context) {
|
|
|
628
631
|
if (callExpr.questionDotToken) {
|
|
629
632
|
return `jspp::Access::optional_call(${derefObj}.get_own_property(${argText}), ${derefObj}, ${argsSpan})`;
|
|
630
633
|
}
|
|
631
|
-
return
|
|
634
|
+
return `${derefObj}.call_own_property(${argText}, ${argsSpan})`;
|
|
632
635
|
}
|
|
633
636
|
const calleeCode = this.visit(callee, context);
|
|
634
637
|
let derefCallee = calleeCode;
|
|
@@ -13,9 +13,11 @@ export function generateLambda(node, context, options) {
|
|
|
13
13
|
isInsideGeneratorFunction: isInsideGeneratorFunction,
|
|
14
14
|
isInsideAsyncFunction: isInsideAsyncFunction,
|
|
15
15
|
});
|
|
16
|
-
const funcReturnType = isInsideGeneratorFunction
|
|
17
|
-
? "jspp::
|
|
18
|
-
: (
|
|
16
|
+
const funcReturnType = (isInsideGeneratorFunction && isInsideAsyncFunction)
|
|
17
|
+
? "jspp::JsAsyncIterator<jspp::AnyValue>"
|
|
18
|
+
: (isInsideGeneratorFunction
|
|
19
|
+
? "jspp::JsIterator<jspp::AnyValue>"
|
|
20
|
+
: (isInsideAsyncFunction ? "jspp::JsPromise" : "jspp::AnyValue"));
|
|
19
21
|
const isArrow = ts.isArrowFunction(node);
|
|
20
22
|
// Lambda arguments are ALWAYS const references/spans to avoid copy overhead for normal functions.
|
|
21
23
|
// For generators/async, we manually copy them inside the body.
|
|
@@ -34,8 +36,10 @@ export function generateLambda(node, context, options) {
|
|
|
34
36
|
topLevelScopeSymbols,
|
|
35
37
|
localScopeSymbols: new DeclaredSymbols(),
|
|
36
38
|
superClassVar: context.superClassVar,
|
|
39
|
+
isInsideGeneratorFunction: isInsideGeneratorFunction,
|
|
40
|
+
isInsideAsyncFunction: isInsideAsyncFunction,
|
|
37
41
|
};
|
|
38
|
-
// Name of 'this' and 'args' to be used in the body.
|
|
42
|
+
// Name of 'this' and 'args' to be used in the body.
|
|
39
43
|
// If generator/async, we use the copied versions.
|
|
40
44
|
let bodyThisVar = this.globalThisVar;
|
|
41
45
|
let bodyArgsVar = argsName;
|
|
@@ -45,40 +49,42 @@ export function generateLambda(node, context, options) {
|
|
|
45
49
|
const thisCopy = this.generateUniqueName("__this_copy", declaredSymbols);
|
|
46
50
|
const argsCopy = this.generateUniqueName("__args_copy", declaredSymbols);
|
|
47
51
|
if (!isArrow) {
|
|
48
|
-
preamble +=
|
|
52
|
+
preamble +=
|
|
53
|
+
`${this.indent()}jspp::AnyValue ${thisCopy} = ${this.globalThisVar};\n`;
|
|
49
54
|
bodyThisVar = thisCopy;
|
|
50
55
|
}
|
|
51
|
-
preamble +=
|
|
56
|
+
preamble +=
|
|
57
|
+
`${this.indent()}std::vector<jspp::AnyValue> ${argsCopy}(${argsName}.begin(), ${argsName}.end());\n`;
|
|
52
58
|
bodyArgsVar = argsCopy;
|
|
53
|
-
// Update visitContext to use the new 'this' variable if needed?
|
|
54
|
-
// CodeGenerator.globalThisVar is a member of the class, so we can't easily change it for the recursive visit
|
|
59
|
+
// Update visitContext to use the new 'this' variable if needed?
|
|
60
|
+
// CodeGenerator.globalThisVar is a member of the class, so we can't easily change it for the recursive visit
|
|
55
61
|
// without changing the class state or passing it down.
|
|
56
|
-
// BUT: visitThisKeyword uses `this.globalThisVar`.
|
|
62
|
+
// BUT: visitThisKeyword uses `this.globalThisVar`.
|
|
57
63
|
// We need to temporarily swap `this.globalThisVar` or handle it.
|
|
58
64
|
// Actually, `this.globalThisVar` is set once in `generate`.
|
|
59
65
|
// Wait, `visitThisKeyword` uses `this.globalThisVar`.
|
|
60
66
|
// If we change the variable name for 'this', we must ensure `visitThisKeyword` generates the correct name.
|
|
61
67
|
// `generateLambda` does NOT create a new CodeGenerator instance, so `this.globalThisVar` is the global one.
|
|
62
|
-
//
|
|
68
|
+
//
|
|
63
69
|
// We need to support shadowing `globalThisVar` for the duration of the visit.
|
|
64
|
-
// The current `CodeGenerator` doesn't seem to support changing `globalThisVar` recursively easily
|
|
70
|
+
// The current `CodeGenerator` doesn't seem to support changing `globalThisVar` recursively easily
|
|
65
71
|
// because it's a property of the class, not `VisitContext`.
|
|
66
|
-
//
|
|
72
|
+
//
|
|
67
73
|
// However, `visitFunctionDeclaration` etc don't update `globalThisVar`.
|
|
68
74
|
// `visitThisKeyword` just returns `this.globalThisVar`.
|
|
69
|
-
//
|
|
75
|
+
//
|
|
70
76
|
// If we are inside a function, `this` should refer to the function's `this`.
|
|
71
77
|
// `this.globalThisVar` is generated in `generate()`: `__this_val__...`.
|
|
72
78
|
// And `generateLambda` uses `this.globalThisVar` as the parameter name.
|
|
73
|
-
//
|
|
79
|
+
//
|
|
74
80
|
// So, if we copy it to `__this_copy`, `visitThisKeyword` will still print `__this_val__...`.
|
|
75
81
|
// This is BAD for generators because `__this_val__...` is a reference that dies.
|
|
76
|
-
//
|
|
82
|
+
//
|
|
77
83
|
// FIX: We must reuse the SAME name for the local copy, and shadow the parameter.
|
|
78
84
|
// But we can't declare a variable with the same name as the parameter in the same scope.
|
|
79
|
-
//
|
|
85
|
+
//
|
|
80
86
|
// We can rename the PARAMETER to something else, and declare the local variable with `this.globalThisVar`.
|
|
81
|
-
//
|
|
87
|
+
//
|
|
82
88
|
}
|
|
83
89
|
// Adjust parameter names for generator/async to allow shadowing/copying
|
|
84
90
|
let finalThisParamName = isArrow ? "" : this.globalThisVar;
|
|
@@ -93,16 +99,19 @@ export function generateLambda(node, context, options) {
|
|
|
93
99
|
? "const jspp::AnyValue&"
|
|
94
100
|
: `${paramThisType} ${finalThisParamName}`;
|
|
95
101
|
// Re-construct lambda header with potentially new param names
|
|
96
|
-
lambda =
|
|
102
|
+
lambda =
|
|
103
|
+
`${capture}(${thisArgParamFinal}, ${paramArgsType} ${finalArgsParamName}) mutable -> ${funcReturnType} `;
|
|
97
104
|
// Regenerate preamble
|
|
98
105
|
preamble = "";
|
|
99
106
|
if (isInsideGeneratorFunction || isInsideAsyncFunction) {
|
|
100
107
|
if (!isArrow) {
|
|
101
|
-
preamble +=
|
|
108
|
+
preamble +=
|
|
109
|
+
`${this.indent()}jspp::AnyValue ${this.globalThisVar} = ${finalThisParamName};\n`;
|
|
102
110
|
}
|
|
103
|
-
preamble +=
|
|
111
|
+
preamble +=
|
|
112
|
+
`${this.indent()}std::vector<jspp::AnyValue> ${argsName}(${finalArgsParamName}.begin(), ${finalArgsParamName}.end());\n`;
|
|
104
113
|
}
|
|
105
|
-
// Now 'argsName' refers to the vector (if copied) or the span (if not).
|
|
114
|
+
// Now 'argsName' refers to the vector (if copied) or the span (if not).
|
|
106
115
|
// And 'this.globalThisVar' refers to the copy (if copied) or the param (if not).
|
|
107
116
|
// So subsequent code using `argsName` and `visit` (using `globalThisVar`) works correctly.
|
|
108
117
|
const paramExtractor = (parameters) => {
|
|
@@ -168,7 +177,7 @@ export function generateLambda(node, context, options) {
|
|
|
168
177
|
if (ts.isBlock(node.body)) {
|
|
169
178
|
// Hoist var declarations in the function body
|
|
170
179
|
const varDecls = collectFunctionScopedDeclarations(node.body);
|
|
171
|
-
varDecls.forEach(decl => {
|
|
180
|
+
varDecls.forEach((decl) => {
|
|
172
181
|
preamble += this.hoistDeclaration(decl, visitContext.localScopeSymbols);
|
|
173
182
|
});
|
|
174
183
|
this.indentationLevel++;
|
|
@@ -183,7 +192,8 @@ export function generateLambda(node, context, options) {
|
|
|
183
192
|
isInsideAsyncFunction: isInsideAsyncFunction,
|
|
184
193
|
});
|
|
185
194
|
// The block visitor already adds braces, so we need to inject the preamble and param extraction.
|
|
186
|
-
lambda += "{\n" + preamble + paramExtraction +
|
|
195
|
+
lambda += "{\n" + preamble + paramExtraction +
|
|
196
|
+
blockContent.trimStart().substring(2);
|
|
187
197
|
}
|
|
188
198
|
else {
|
|
189
199
|
lambda += "{\n";
|
|
@@ -210,10 +220,18 @@ export function generateLambda(node, context, options) {
|
|
|
210
220
|
let method = "";
|
|
211
221
|
// Handle generator function
|
|
212
222
|
if (isInsideGeneratorFunction) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
if (isInsideAsyncFunction) {
|
|
224
|
+
signature =
|
|
225
|
+
"jspp::JsAsyncIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
226
|
+
callable = `std::function<${signature}>(${lambda})`;
|
|
227
|
+
method = `jspp::AnyValue::make_async_generator`;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
signature =
|
|
231
|
+
"jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
232
|
+
callable = `std::function<${signature}>(${lambda})`;
|
|
233
|
+
method = `jspp::AnyValue::make_generator`;
|
|
234
|
+
}
|
|
217
235
|
} // Handle async function
|
|
218
236
|
else if (isInsideAsyncFunction) {
|
|
219
237
|
signature =
|
|
@@ -34,22 +34,20 @@ export function getDeclaredSymbols(node) {
|
|
|
34
34
|
return symbols;
|
|
35
35
|
}
|
|
36
36
|
export function generateUniqueName(prefix, ...namesToAvoid) {
|
|
37
|
-
let name = `${prefix}${this.
|
|
37
|
+
let name = `${prefix}${this.uniqueNameCounter}`;
|
|
38
38
|
while (namesToAvoid.some((names) => names.has(name))) {
|
|
39
|
-
this.
|
|
40
|
-
name = `${prefix}${this.
|
|
39
|
+
this.uniqueNameCounter++;
|
|
40
|
+
name = `${prefix}${this.uniqueNameCounter}`;
|
|
41
41
|
}
|
|
42
|
-
this.
|
|
42
|
+
this.uniqueNameCounter++;
|
|
43
43
|
return name;
|
|
44
44
|
}
|
|
45
|
-
export function generateUniqueExceptionName(
|
|
46
|
-
let
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
exceptionName = `__caught_exception_${this.exceptionCounter}`;
|
|
45
|
+
export function generateUniqueExceptionName(exceptionNameToAvoid, ...otherNamesToAvoid) {
|
|
46
|
+
let prefix = `__caught_exception_`;
|
|
47
|
+
if (exceptionNameToAvoid) {
|
|
48
|
+
prefix += exceptionNameToAvoid;
|
|
50
49
|
}
|
|
51
|
-
this.
|
|
52
|
-
return exceptionName;
|
|
50
|
+
return this.generateUniqueName(prefix, ...otherNamesToAvoid);
|
|
53
51
|
}
|
|
54
52
|
export function getScopeForNode(node) {
|
|
55
53
|
let current = node;
|
|
@@ -189,14 +187,16 @@ export function collectFunctionScopedDeclarations(node) {
|
|
|
189
187
|
const decls = [];
|
|
190
188
|
function visit(n) {
|
|
191
189
|
if (ts.isVariableStatement(n)) {
|
|
192
|
-
const isLetOrConst = (n.declarationList.flags &
|
|
190
|
+
const isLetOrConst = (n.declarationList.flags &
|
|
191
|
+
(ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
193
192
|
if (!isLetOrConst) {
|
|
194
193
|
decls.push(...n.declarationList.declarations);
|
|
195
194
|
}
|
|
196
195
|
}
|
|
197
196
|
else if (ts.isForStatement(n)) {
|
|
198
197
|
if (n.initializer && ts.isVariableDeclarationList(n.initializer)) {
|
|
199
|
-
const isLetOrConst = (n.initializer.flags &
|
|
198
|
+
const isLetOrConst = (n.initializer.flags &
|
|
199
|
+
(ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
200
200
|
if (!isLetOrConst) {
|
|
201
201
|
decls.push(...n.initializer.declarations);
|
|
202
202
|
}
|
|
@@ -204,7 +204,8 @@ export function collectFunctionScopedDeclarations(node) {
|
|
|
204
204
|
}
|
|
205
205
|
else if (ts.isForInStatement(n)) {
|
|
206
206
|
if (ts.isVariableDeclarationList(n.initializer)) {
|
|
207
|
-
const isLetOrConst = (n.initializer.flags &
|
|
207
|
+
const isLetOrConst = (n.initializer.flags &
|
|
208
|
+
(ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
208
209
|
if (!isLetOrConst) {
|
|
209
210
|
decls.push(...n.initializer.declarations);
|
|
210
211
|
}
|
|
@@ -212,14 +213,19 @@ export function collectFunctionScopedDeclarations(node) {
|
|
|
212
213
|
}
|
|
213
214
|
else if (ts.isForOfStatement(n)) {
|
|
214
215
|
if (ts.isVariableDeclarationList(n.initializer)) {
|
|
215
|
-
const isLetOrConst = (n.initializer.flags &
|
|
216
|
+
const isLetOrConst = (n.initializer.flags &
|
|
217
|
+
(ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
216
218
|
if (!isLetOrConst) {
|
|
217
219
|
decls.push(...n.initializer.declarations);
|
|
218
220
|
}
|
|
219
221
|
}
|
|
220
222
|
}
|
|
221
223
|
// Stop recursion at function boundaries (but not the root node if it is a function)
|
|
222
|
-
if (n !== node &&
|
|
224
|
+
if (n !== node &&
|
|
225
|
+
(ts.isFunctionDeclaration(n) || ts.isFunctionExpression(n) ||
|
|
226
|
+
ts.isArrowFunction(n) || ts.isMethodDeclaration(n) ||
|
|
227
|
+
ts.isGetAccessor(n) || ts.isSetAccessor(n) ||
|
|
228
|
+
ts.isClassDeclaration(n))) {
|
|
223
229
|
return;
|
|
224
230
|
}
|
|
225
231
|
ts.forEachChild(n, visit);
|
|
@@ -231,7 +237,8 @@ export function collectBlockScopedDeclarations(statements) {
|
|
|
231
237
|
const decls = [];
|
|
232
238
|
for (const stmt of statements) {
|
|
233
239
|
if (ts.isVariableStatement(stmt)) {
|
|
234
|
-
const isLetOrConst = (stmt.declarationList.flags &
|
|
240
|
+
const isLetOrConst = (stmt.declarationList.flags &
|
|
241
|
+
(ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
235
242
|
if (isLetOrConst) {
|
|
236
243
|
decls.push(...stmt.declarationList.declarations);
|
|
237
244
|
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { TypeAnalyzer } from "../../analysis/typeAnalyzer";
|
|
2
1
|
import { DeclaredSymbols } from "../../ast/symbols";
|
|
3
2
|
import { generateLambda } from "./function-handlers";
|
|
4
3
|
import { escapeString, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isGeneratorFunction, markSymbolAsChecked, prepareScopeSymbolsForVisit, } from "./helpers";
|
|
5
4
|
import { visit } from "./visitor";
|
|
6
|
-
const
|
|
5
|
+
const MODULE_NAME = "__main_function__";
|
|
7
6
|
export class CodeGenerator {
|
|
8
7
|
indentationLevel = 0;
|
|
9
8
|
typeAnalyzer;
|
|
10
9
|
globalThisVar;
|
|
11
|
-
|
|
10
|
+
uniqueNameCounter = 0;
|
|
12
11
|
// visitor
|
|
13
12
|
visit = visit;
|
|
14
13
|
// helpers
|
|
@@ -36,7 +35,7 @@ export class CodeGenerator {
|
|
|
36
35
|
this.typeAnalyzer = analyzer;
|
|
37
36
|
this.globalThisVar = this.generateUniqueName("__this_val__", this.getDeclaredSymbols(ast));
|
|
38
37
|
const declarations = `#include "index.hpp"\n\n`;
|
|
39
|
-
let containerCode = `jspp::
|
|
38
|
+
let containerCode = `jspp::JsPromise ${MODULE_NAME}() {\n`;
|
|
40
39
|
this.indentationLevel++;
|
|
41
40
|
containerCode +=
|
|
42
41
|
`${this.indent()}jspp::AnyValue ${this.globalThisVar} = global;\n`;
|
|
@@ -44,23 +43,31 @@ export class CodeGenerator {
|
|
|
44
43
|
isMainContext: true,
|
|
45
44
|
isInsideFunction: true,
|
|
46
45
|
isFunctionBody: true,
|
|
46
|
+
isInsideAsyncFunction: true,
|
|
47
47
|
topLevelScopeSymbols: new DeclaredSymbols(),
|
|
48
48
|
localScopeSymbols: new DeclaredSymbols(),
|
|
49
49
|
});
|
|
50
50
|
this.indentationLevel--;
|
|
51
|
-
containerCode += "
|
|
51
|
+
containerCode += " co_return jspp::Constants::UNDEFINED;\n";
|
|
52
52
|
containerCode += "}\n\n";
|
|
53
53
|
let mainCode = "int main(int argc, char** argv) {\n";
|
|
54
54
|
mainCode += ` try {\n`;
|
|
55
55
|
mainCode += ` jspp::setup_process_argv(argc, argv);\n`;
|
|
56
|
-
mainCode += ` ${
|
|
56
|
+
mainCode += ` auto p = ${MODULE_NAME}();\n`;
|
|
57
|
+
mainCode += ` p.then(nullptr, [](const jspp::AnyValue& err) {\n`;
|
|
58
|
+
mainCode +=
|
|
59
|
+
` auto error = std::make_shared<jspp::AnyValue>(err);\n`;
|
|
60
|
+
mainCode +=
|
|
61
|
+
` console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
|
|
62
|
+
mainCode += ` std::exit(1);\n`;
|
|
63
|
+
mainCode += ` });\n`;
|
|
57
64
|
mainCode += ` jspp::Scheduler::instance().run();\n`;
|
|
58
65
|
mainCode += ` } catch (const std::exception& ex) {\n`;
|
|
59
66
|
mainCode +=
|
|
60
|
-
" auto error = std::make_shared<jspp::AnyValue>(jspp::Exception::exception_to_any_value(ex));\n{\n";
|
|
67
|
+
" auto error = std::make_shared<jspp::AnyValue>(jspp::Exception::exception_to_any_value(ex));\n {\n";
|
|
61
68
|
mainCode +=
|
|
62
|
-
`
|
|
63
|
-
mainCode += `
|
|
69
|
+
` console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
|
|
70
|
+
mainCode += ` return 1;\n }\n`;
|
|
64
71
|
mainCode += ` }\n`;
|
|
65
72
|
mainCode += " return 0;\n}";
|
|
66
73
|
return declarations + containerCode + mainCode;
|