@ugo-studio/jspp 0.2.3 → 0.2.5
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/README.md +1 -1
- package/dist/analysis/typeAnalyzer.js +57 -14
- package/dist/ast/symbols.js +57 -15
- package/dist/cli-utils/args.js +2 -2
- package/dist/cli-utils/spinner.js +1 -1
- package/dist/cli.js +8 -8
- package/dist/core/codegen/class-handlers.js +2 -2
- package/dist/core/codegen/control-flow-handlers.js +140 -52
- package/dist/core/codegen/declaration-handlers.js +38 -13
- package/dist/core/codegen/expression-handlers.js +39 -4
- package/dist/core/codegen/function-handlers.js +42 -59
- package/dist/core/codegen/helpers.js +171 -33
- package/dist/core/codegen/index.js +10 -6
- package/dist/core/codegen/literal-handlers.js +1 -1
- package/dist/core/codegen/statement-handlers.js +81 -38
- package/dist/core/codegen/visitor.js +8 -8
- package/dist/core/parser.js +2 -2
- package/dist/index.js +6 -6
- package/package.json +1 -1
- package/src/prelude/utils/access.hpp +2 -2
- package/src/prelude/utils/log_any_value/primitives.hpp +7 -0
- package/src/prelude/utils/operators.hpp +4 -6
- package/src/prelude/values/prototypes/function.hpp +18 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { CodeGenerator } from "./";
|
|
2
|
+
import { CodeGenerator } from "./index.js";
|
|
3
3
|
export function visitVariableDeclarationList(node, context) {
|
|
4
4
|
return node.declarations
|
|
5
5
|
.map((d) => this.visit(d, context))
|
|
@@ -12,27 +12,50 @@ export function visitVariableDeclaration(node, context) {
|
|
|
12
12
|
const scope = this.getScopeForNode(varDecl);
|
|
13
13
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
|
|
14
14
|
// Mark the symbol as checked
|
|
15
|
-
this.
|
|
15
|
+
this.markSymbolAsInitialized(name, context.globalScopeSymbols, context.localScopeSymbols);
|
|
16
|
+
let nativeLambdaCode = "";
|
|
16
17
|
let initializer = "";
|
|
18
|
+
let shouldSkipDeref = false;
|
|
17
19
|
if (varDecl.initializer) {
|
|
18
20
|
const initExpr = varDecl.initializer;
|
|
19
|
-
const initContext = {
|
|
20
|
-
...context,
|
|
21
|
-
lambdaName: ts.isArrowFunction(initExpr) ? name : undefined, // Pass the variable name for arrow functions
|
|
22
|
-
};
|
|
23
21
|
let initText = ts.isNumericLiteral(initExpr)
|
|
24
22
|
? initExpr.getText()
|
|
25
|
-
: this.visit(initExpr,
|
|
23
|
+
: this.visit(initExpr, context);
|
|
26
24
|
if (ts.isIdentifier(initExpr)) {
|
|
27
25
|
const initScope = this.getScopeForNode(initExpr);
|
|
28
26
|
const initTypeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(initExpr.text, initScope);
|
|
29
27
|
const varName = this.getJsVarName(initExpr);
|
|
28
|
+
// Check if both target and initializer are heap allocated
|
|
29
|
+
if (typeInfo.needsHeapAllocation &&
|
|
30
|
+
initTypeInfo?.needsHeapAllocation) {
|
|
31
|
+
shouldSkipDeref = true;
|
|
32
|
+
}
|
|
30
33
|
if (initTypeInfo &&
|
|
31
34
|
!initTypeInfo.isParameter &&
|
|
32
|
-
!initTypeInfo.isBuiltin
|
|
33
|
-
|
|
35
|
+
!initTypeInfo.isBuiltin &&
|
|
36
|
+
!shouldSkipDeref) {
|
|
37
|
+
initText = this.getDerefCode(initText, varName, context, initTypeInfo);
|
|
34
38
|
}
|
|
35
39
|
}
|
|
40
|
+
else if (ts.isArrowFunction(initExpr)) {
|
|
41
|
+
const initContext = {
|
|
42
|
+
...context,
|
|
43
|
+
lambdaName: name, // Use the variable name as function name
|
|
44
|
+
};
|
|
45
|
+
// Generate and update self name
|
|
46
|
+
const nativeName = this.generateUniqueName(`__${name}_native_`, context.localScopeSymbols, context.globalScopeSymbols);
|
|
47
|
+
context.localScopeSymbols.update(name, { func: { nativeName } });
|
|
48
|
+
// Generate lambda
|
|
49
|
+
const lambda = this.generateLambda(initExpr, initContext, {
|
|
50
|
+
isAssignment: true,
|
|
51
|
+
generateOnlyLambda: true,
|
|
52
|
+
nativeName,
|
|
53
|
+
});
|
|
54
|
+
nativeLambdaCode =
|
|
55
|
+
`auto ${nativeName} = ${lambda};\n${this.indent()}`;
|
|
56
|
+
// Generate AnyValue wrapper
|
|
57
|
+
initText = this.generateFullLambdaExpression(initExpr, initContext, nativeName, { isAssignment: true, noTypeSignature: true });
|
|
58
|
+
}
|
|
36
59
|
initializer = " = " + initText;
|
|
37
60
|
}
|
|
38
61
|
const isLetOrConst = (varDecl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
@@ -40,19 +63,21 @@ export function visitVariableDeclaration(node, context) {
|
|
|
40
63
|
(!context.localScopeSymbols.has(name));
|
|
41
64
|
const assignmentTarget = shouldDeref
|
|
42
65
|
? this.getDerefCode(name, name, context, typeInfo)
|
|
43
|
-
: (typeInfo.needsHeapAllocation
|
|
66
|
+
: (typeInfo.needsHeapAllocation && !shouldSkipDeref
|
|
67
|
+
? `*${name}`
|
|
68
|
+
: name);
|
|
44
69
|
if (isLetOrConst) {
|
|
45
70
|
// If there's no initializer, it should be assigned undefined.
|
|
46
71
|
if (!initializer) {
|
|
47
|
-
return `${assignmentTarget} = jspp::Constants::UNDEFINED`;
|
|
72
|
+
return `${nativeLambdaCode}${assignmentTarget} = jspp::Constants::UNDEFINED`;
|
|
48
73
|
}
|
|
49
|
-
return `${assignmentTarget}${initializer}`;
|
|
74
|
+
return `${nativeLambdaCode}${assignmentTarget}${initializer}`;
|
|
50
75
|
}
|
|
51
76
|
// For 'var', it's a bit more complex.
|
|
52
77
|
if (context.isAssignmentOnly) {
|
|
53
78
|
if (!initializer)
|
|
54
79
|
return "";
|
|
55
|
-
return `${assignmentTarget}${initializer}`;
|
|
80
|
+
return `${nativeLambdaCode}${assignmentTarget}${initializer}`;
|
|
56
81
|
}
|
|
57
82
|
else {
|
|
58
83
|
// This case should not be hit with the new hoisting logic,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { CodeGenerator } from "./";
|
|
2
|
+
import { CodeGenerator } from "./index.js";
|
|
3
3
|
export function visitObjectPropertyName(node, context) {
|
|
4
4
|
if (ts.isNumericLiteral(node)) {
|
|
5
5
|
return context.isBracketNotationPropertyAccess
|
|
@@ -28,6 +28,13 @@ 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
|
+
if (!obj.properties.some((prop) => ts.isPropertyAssignment(prop) ||
|
|
32
|
+
ts.isShorthandPropertyAssignment(prop) ||
|
|
33
|
+
ts.isMethodDeclaration(prop) || ts.isGetAccessor(prop) ||
|
|
34
|
+
ts.isSetAccessor(prop))) {
|
|
35
|
+
// Empty object
|
|
36
|
+
return `jspp::AnyValue::make_object_with_proto({}, ::Object.get_own_property("prototype"))`;
|
|
37
|
+
}
|
|
31
38
|
let code = `([&]() {\n`;
|
|
32
39
|
code +=
|
|
33
40
|
`${this.indent()} auto ${objVar} = jspp::AnyValue::make_object_with_proto({}, ::Object.get_own_property("prototype"));\n`;
|
|
@@ -102,8 +109,6 @@ export function visitObjectLiteralExpression(node, context) {
|
|
|
102
109
|
}
|
|
103
110
|
}
|
|
104
111
|
this.indentationLevel--;
|
|
105
|
-
// code +=
|
|
106
|
-
// `${this.indent()} ${returnCmd} ${objVar};\n${this.indent()}} )() ))`;
|
|
107
112
|
code += `${this.indent()} return ${objVar};\n${this.indent()}})()`;
|
|
108
113
|
return code;
|
|
109
114
|
}
|
|
@@ -439,6 +444,21 @@ export function visitBinaryExpression(node, context) {
|
|
|
439
444
|
const target = context.derefBeforeAssignment
|
|
440
445
|
? this.getDerefCode(leftText, leftText, context, typeInfo)
|
|
441
446
|
: (typeInfo.needsHeapAllocation ? `*${leftText}` : leftText);
|
|
447
|
+
// Update scope symbols on variable re-assignment
|
|
448
|
+
if (ts.isIdentifier(binExpr.left)) {
|
|
449
|
+
if (!ts.isFunctionDeclaration(binExpr.right)) {
|
|
450
|
+
if (context.localScopeSymbols.has(binExpr.left.text)) {
|
|
451
|
+
context.localScopeSymbols.update(binExpr.left.text, {
|
|
452
|
+
func: null,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
else if (context.globalScopeSymbols.has(binExpr.left.text)) {
|
|
456
|
+
context.globalScopeSymbols.update(binExpr.left.text, {
|
|
457
|
+
func: null,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
442
462
|
return `${target} ${op} ${rightText}`;
|
|
443
463
|
}
|
|
444
464
|
const leftText = this.visit(binExpr.left, context);
|
|
@@ -535,7 +555,7 @@ export function visitConditionalExpression(node, context) {
|
|
|
535
555
|
...context,
|
|
536
556
|
isFunctionBody: false,
|
|
537
557
|
});
|
|
538
|
-
return `is_truthy(${condition}) ? ${whenTrueStmt} : ${whenFalseStmt}`;
|
|
558
|
+
return `jspp::is_truthy(${condition}) ? ${whenTrueStmt} : ${whenFalseStmt}`;
|
|
539
559
|
}
|
|
540
560
|
export function visitCallExpression(node, context) {
|
|
541
561
|
const callExpr = node;
|
|
@@ -645,6 +665,21 @@ export function visitCallExpression(node, context) {
|
|
|
645
665
|
derefCallee = calleeCode;
|
|
646
666
|
}
|
|
647
667
|
else if (typeInfo) {
|
|
668
|
+
const name = callee.getText();
|
|
669
|
+
const symbol = context.localScopeSymbols.get(name) ??
|
|
670
|
+
context.globalScopeSymbols.get(name);
|
|
671
|
+
// Optimization: Direct lambda call
|
|
672
|
+
if (symbol && symbol.func?.nativeName) {
|
|
673
|
+
const callExpr = `${symbol.func.nativeName}(jspp::Constants::UNDEFINED, ${argsSpan})`;
|
|
674
|
+
if (symbol.func.isGenerator) {
|
|
675
|
+
if (symbol.func.isAsync) {
|
|
676
|
+
return `jspp::AnyValue::from_async_iterator(${callExpr})`;
|
|
677
|
+
}
|
|
678
|
+
return `jspp::AnyValue::from_iterator(${callExpr})`;
|
|
679
|
+
}
|
|
680
|
+
return callExpr;
|
|
681
|
+
}
|
|
682
|
+
// AnyValue function call
|
|
648
683
|
derefCallee = this.getDerefCode(calleeCode, this.getJsVarName(callee), context, typeInfo);
|
|
649
684
|
}
|
|
650
685
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { DeclaredSymbols } from "../../ast/symbols";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { DeclaredSymbols } from "../../ast/symbols.js";
|
|
3
|
+
import { collectFunctionScopedDeclarations } from "./helpers.js";
|
|
4
|
+
import { CodeGenerator } from "./index.js";
|
|
5
5
|
export function generateLambda(node, context, options) {
|
|
6
|
-
const isAssignment = options?.isAssignment || false;
|
|
7
6
|
const capture = options?.capture || "[=]";
|
|
7
|
+
const nativeName = options?.nativeName;
|
|
8
8
|
const declaredSymbols = this.getDeclaredSymbols(node);
|
|
9
|
-
const argsName = this.generateUniqueName("__args_", declaredSymbols);
|
|
9
|
+
const argsName = this.generateUniqueName("__args_", declaredSymbols, context.globalScopeSymbols, context.localScopeSymbols);
|
|
10
10
|
const isInsideGeneratorFunction = this.isGeneratorFunction(node);
|
|
11
11
|
const isInsideAsyncFunction = this.isAsyncFunction(node);
|
|
12
12
|
const returnCmd = this.getReturnCommand({
|
|
@@ -23,26 +23,25 @@ export function generateLambda(node, context, options) {
|
|
|
23
23
|
// For generators/async, we manually copy them inside the body.
|
|
24
24
|
const paramThisType = "const jspp::AnyValue&";
|
|
25
25
|
const paramArgsType = "std::span<const jspp::AnyValue>";
|
|
26
|
+
const selfParamPart = nativeName ? `this auto&& ${nativeName}, ` : "";
|
|
27
|
+
const mutablePart = nativeName ? "" : "mutable ";
|
|
26
28
|
const thisArgParam = isArrow
|
|
27
29
|
? "const jspp::AnyValue&" // Arrow functions use captured 'this' or are never generators in this parser context
|
|
28
30
|
: `${paramThisType} ${this.globalThisVar}`;
|
|
29
|
-
let lambda = `${capture}(${thisArgParam}, ${paramArgsType} ${argsName})
|
|
30
|
-
const
|
|
31
|
+
let lambda = `${capture}(${selfParamPart}${thisArgParam}, ${paramArgsType} ${argsName}) ${mutablePart}-> ${funcReturnType} `;
|
|
32
|
+
const globalScopeSymbols = this.prepareScopeSymbolsForVisit(context.globalScopeSymbols, context.localScopeSymbols);
|
|
31
33
|
const visitContext = {
|
|
34
|
+
currentScopeNode: context.currentScopeNode,
|
|
32
35
|
isMainContext: false,
|
|
33
36
|
isInsideFunction: true,
|
|
34
37
|
isFunctionBody: false,
|
|
35
38
|
lambdaName: undefined,
|
|
36
|
-
|
|
39
|
+
globalScopeSymbols,
|
|
37
40
|
localScopeSymbols: new DeclaredSymbols(),
|
|
38
41
|
superClassVar: context.superClassVar,
|
|
39
42
|
isInsideGeneratorFunction: isInsideGeneratorFunction,
|
|
40
43
|
isInsideAsyncFunction: isInsideAsyncFunction,
|
|
41
44
|
};
|
|
42
|
-
// Name of 'this' and 'args' to be used in the body.
|
|
43
|
-
// If generator/async, we use the copied versions.
|
|
44
|
-
let bodyThisVar = this.globalThisVar;
|
|
45
|
-
let bodyArgsVar = argsName;
|
|
46
45
|
// Preamble to copy arguments for coroutines
|
|
47
46
|
let preamble = "";
|
|
48
47
|
if (isInsideGeneratorFunction || isInsideAsyncFunction) {
|
|
@@ -51,40 +50,9 @@ export function generateLambda(node, context, options) {
|
|
|
51
50
|
if (!isArrow) {
|
|
52
51
|
preamble +=
|
|
53
52
|
`${this.indent()}jspp::AnyValue ${thisCopy} = ${this.globalThisVar};\n`;
|
|
54
|
-
bodyThisVar = thisCopy;
|
|
55
53
|
}
|
|
56
54
|
preamble +=
|
|
57
55
|
`${this.indent()}std::vector<jspp::AnyValue> ${argsCopy}(${argsName}.begin(), ${argsName}.end());\n`;
|
|
58
|
-
bodyArgsVar = argsCopy;
|
|
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
|
|
61
|
-
// without changing the class state or passing it down.
|
|
62
|
-
// BUT: visitThisKeyword uses `this.globalThisVar`.
|
|
63
|
-
// We need to temporarily swap `this.globalThisVar` or handle it.
|
|
64
|
-
// Actually, `this.globalThisVar` is set once in `generate`.
|
|
65
|
-
// Wait, `visitThisKeyword` uses `this.globalThisVar`.
|
|
66
|
-
// If we change the variable name for 'this', we must ensure `visitThisKeyword` generates the correct name.
|
|
67
|
-
// `generateLambda` does NOT create a new CodeGenerator instance, so `this.globalThisVar` is the global one.
|
|
68
|
-
//
|
|
69
|
-
// We need to support shadowing `globalThisVar` for the duration of the visit.
|
|
70
|
-
// The current `CodeGenerator` doesn't seem to support changing `globalThisVar` recursively easily
|
|
71
|
-
// because it's a property of the class, not `VisitContext`.
|
|
72
|
-
//
|
|
73
|
-
// However, `visitFunctionDeclaration` etc don't update `globalThisVar`.
|
|
74
|
-
// `visitThisKeyword` just returns `this.globalThisVar`.
|
|
75
|
-
//
|
|
76
|
-
// If we are inside a function, `this` should refer to the function's `this`.
|
|
77
|
-
// `this.globalThisVar` is generated in `generate()`: `__this_val__...`.
|
|
78
|
-
// And `generateLambda` uses `this.globalThisVar` as the parameter name.
|
|
79
|
-
//
|
|
80
|
-
// So, if we copy it to `__this_copy`, `visitThisKeyword` will still print `__this_val__...`.
|
|
81
|
-
// This is BAD for generators because `__this_val__...` is a reference that dies.
|
|
82
|
-
//
|
|
83
|
-
// FIX: We must reuse the SAME name for the local copy, and shadow the parameter.
|
|
84
|
-
// But we can't declare a variable with the same name as the parameter in the same scope.
|
|
85
|
-
//
|
|
86
|
-
// We can rename the PARAMETER to something else, and declare the local variable with `this.globalThisVar`.
|
|
87
|
-
//
|
|
88
56
|
}
|
|
89
57
|
// Adjust parameter names for generator/async to allow shadowing/copying
|
|
90
58
|
let finalThisParamName = isArrow ? "" : this.globalThisVar;
|
|
@@ -100,7 +68,7 @@ export function generateLambda(node, context, options) {
|
|
|
100
68
|
: `${paramThisType} ${finalThisParamName}`;
|
|
101
69
|
// Re-construct lambda header with potentially new param names
|
|
102
70
|
lambda =
|
|
103
|
-
`${capture}(${thisArgParamFinal}, ${paramArgsType} ${finalArgsParamName})
|
|
71
|
+
`${capture}(${selfParamPart}${thisArgParamFinal}, ${paramArgsType} ${finalArgsParamName}) ${mutablePart}-> ${funcReturnType} `;
|
|
104
72
|
// Regenerate preamble
|
|
105
73
|
preamble = "";
|
|
106
74
|
if (isInsideGeneratorFunction || isInsideAsyncFunction) {
|
|
@@ -178,7 +146,7 @@ export function generateLambda(node, context, options) {
|
|
|
178
146
|
// Hoist var declarations in the function body
|
|
179
147
|
const varDecls = collectFunctionScopedDeclarations(node.body);
|
|
180
148
|
varDecls.forEach((decl) => {
|
|
181
|
-
preamble += this.hoistDeclaration(decl, visitContext.localScopeSymbols);
|
|
149
|
+
preamble += this.hoistDeclaration(decl, visitContext.localScopeSymbols, node.body);
|
|
182
150
|
});
|
|
183
151
|
this.indentationLevel++;
|
|
184
152
|
const paramExtraction = paramExtractor(node.parameters);
|
|
@@ -215,34 +183,49 @@ export function generateLambda(node, context, options) {
|
|
|
215
183
|
else {
|
|
216
184
|
lambda += `{ ${preamble} ${returnCmd} jspp::Constants::UNDEFINED; }\n`;
|
|
217
185
|
}
|
|
218
|
-
|
|
219
|
-
|
|
186
|
+
// Return only lambda if required
|
|
187
|
+
if (options?.generateOnlyLambda) {
|
|
188
|
+
return lambda.trimEnd();
|
|
189
|
+
}
|
|
190
|
+
return this.generateFullLambdaExpression(node, context, lambda, options);
|
|
191
|
+
}
|
|
192
|
+
export function generateFullLambdaExpression(node, context, lambda, options) {
|
|
193
|
+
const isAssignment = options?.isAssignment || false;
|
|
194
|
+
const noTypeSignature = options?.noTypeSignature || false;
|
|
195
|
+
const isInsideGeneratorFunction = this.isGeneratorFunction(node);
|
|
196
|
+
const isInsideAsyncFunction = this.isAsyncFunction(node);
|
|
197
|
+
const isArrow = ts.isArrowFunction(node);
|
|
198
|
+
let callable = lambda;
|
|
220
199
|
let method = "";
|
|
221
200
|
// Handle generator function
|
|
222
201
|
if (isInsideGeneratorFunction) {
|
|
223
202
|
if (isInsideAsyncFunction) {
|
|
224
|
-
|
|
225
|
-
"jspp::JsAsyncIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
226
|
-
|
|
203
|
+
if (!noTypeSignature) {
|
|
204
|
+
const signature = "jspp::JsAsyncIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
205
|
+
callable = `std::function<${signature}>(${lambda})`;
|
|
206
|
+
}
|
|
227
207
|
method = `jspp::AnyValue::make_async_generator`;
|
|
228
208
|
}
|
|
229
209
|
else {
|
|
230
|
-
|
|
231
|
-
"jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
232
|
-
|
|
210
|
+
if (!noTypeSignature) {
|
|
211
|
+
const signature = "jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
212
|
+
callable = `std::function<${signature}>(${lambda})`;
|
|
213
|
+
}
|
|
233
214
|
method = `jspp::AnyValue::make_generator`;
|
|
234
215
|
}
|
|
235
216
|
} // Handle async function
|
|
236
217
|
else if (isInsideAsyncFunction) {
|
|
237
|
-
|
|
238
|
-
"jspp::JsPromise(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
239
|
-
|
|
218
|
+
if (!noTypeSignature) {
|
|
219
|
+
const signature = "jspp::JsPromise(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
220
|
+
callable = `std::function<${signature}>(${lambda})`;
|
|
221
|
+
}
|
|
240
222
|
method = `jspp::AnyValue::make_async_function`;
|
|
241
223
|
} // Handle normal function
|
|
242
224
|
else {
|
|
243
|
-
|
|
244
|
-
`jspp::AnyValue(const jspp::AnyValue&, std::span<const jspp::AnyValue>)`;
|
|
245
|
-
|
|
225
|
+
if (!noTypeSignature) {
|
|
226
|
+
const signature = `jspp::AnyValue(const jspp::AnyValue&, std::span<const jspp::AnyValue>)`;
|
|
227
|
+
callable = `std::function<${signature}>(${lambda})`;
|
|
228
|
+
}
|
|
246
229
|
if (options?.isClass) {
|
|
247
230
|
method = `jspp::AnyValue::make_class`;
|
|
248
231
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { BUILTIN_OBJECTS, Scope } from "../../analysis/scope";
|
|
3
|
-
import {
|
|
4
|
-
import { CodeGenerator } from "./";
|
|
2
|
+
import { BUILTIN_OBJECTS, Scope } from "../../analysis/scope.js";
|
|
3
|
+
import { DeclarationType, DeclaredSymbols } from "../../ast/symbols.js";
|
|
4
|
+
import { CodeGenerator } from "./index.js";
|
|
5
5
|
export function isBuiltinObject(node) {
|
|
6
6
|
return BUILTIN_OBJECTS.values().some((obj) => obj.name === node.text);
|
|
7
7
|
}
|
|
@@ -73,7 +73,8 @@ export function escapeString(str) {
|
|
|
73
73
|
.replace(/"/g, '\\"')
|
|
74
74
|
.replace(/\n/g, "\\n")
|
|
75
75
|
.replace(/\r/g, "\\r")
|
|
76
|
-
.replace(/\t/g, "\\t")
|
|
76
|
+
.replace(/\t/g, "\\t")
|
|
77
|
+
.replace(/\?/g, "\\?");
|
|
77
78
|
}
|
|
78
79
|
export function getJsVarName(node) {
|
|
79
80
|
return `"${node.text}"`;
|
|
@@ -86,13 +87,13 @@ export function getDerefCode(nodeText, varName, context, typeInfo) {
|
|
|
86
87
|
varName = varName + '"';
|
|
87
88
|
const symbolName = varName.slice(1).slice(0, -1);
|
|
88
89
|
const symbol = context.localScopeSymbols.get(symbolName) ??
|
|
89
|
-
context.
|
|
90
|
-
const
|
|
90
|
+
context.globalScopeSymbols.get(symbolName);
|
|
91
|
+
const isInitialized = symbol?.checked.initialized ||
|
|
91
92
|
false;
|
|
92
93
|
// Mark the symbol as checked
|
|
93
|
-
this.
|
|
94
|
+
this.markSymbolAsInitialized(symbolName, context.globalScopeSymbols, context.localScopeSymbols);
|
|
94
95
|
// Apply deref code
|
|
95
|
-
if (
|
|
96
|
+
if (isInitialized) {
|
|
96
97
|
if (typeInfo && typeInfo.needsHeapAllocation) {
|
|
97
98
|
return `(*${nodeText})`;
|
|
98
99
|
}
|
|
@@ -105,15 +106,15 @@ export function getDerefCode(nodeText, varName, context, typeInfo) {
|
|
|
105
106
|
return `jspp::Access::deref_stack(${nodeText}, ${varName})`;
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
|
-
export function
|
|
109
|
+
export function markSymbolAsInitialized(name, topLevel, local) {
|
|
109
110
|
if (topLevel.has(name)) {
|
|
110
111
|
topLevel.update(name, {
|
|
111
|
-
|
|
112
|
+
checked: { initialized: true },
|
|
112
113
|
});
|
|
113
114
|
}
|
|
114
115
|
else if (local.has(name)) {
|
|
115
116
|
local.update(name, {
|
|
116
|
-
|
|
117
|
+
checked: { initialized: true },
|
|
117
118
|
});
|
|
118
119
|
}
|
|
119
120
|
}
|
|
@@ -122,39 +123,58 @@ export function getReturnCommand(context) {
|
|
|
122
123
|
? "co_return"
|
|
123
124
|
: "return";
|
|
124
125
|
}
|
|
125
|
-
export function hoistDeclaration(decl, hoistedSymbols) {
|
|
126
|
+
export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
|
|
126
127
|
const name = decl.name?.getText();
|
|
127
128
|
if (!name) {
|
|
128
129
|
return `/* Unknown declaration name: ${ts.SyntaxKind[decl.kind]} */`;
|
|
129
130
|
}
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
const isLet = (decl.parent.flags & (ts.NodeFlags.Let)) !== 0;
|
|
132
|
+
const isConst = (decl.parent.flags & (ts.NodeFlags.Const)) !== 0;
|
|
133
|
+
const declType = isLet
|
|
134
|
+
? DeclarationType.let
|
|
135
|
+
: isConst
|
|
136
|
+
? DeclarationType.const
|
|
137
|
+
: ts.isFunctionDeclaration(decl)
|
|
138
|
+
? DeclarationType.function
|
|
139
|
+
: ts.isClassDeclaration(decl)
|
|
140
|
+
? DeclarationType.class
|
|
141
|
+
: DeclarationType.var;
|
|
140
142
|
if (hoistedSymbols.has(name)) {
|
|
141
|
-
const
|
|
142
|
-
// Don't allow multiple declaration of `
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
const existingSymbol = hoistedSymbols.get(name);
|
|
144
|
+
// Don't allow multiple declaration of `let`,`const`,`function` or `class` variables
|
|
145
|
+
if (existingSymbol?.type === DeclarationType.let ||
|
|
146
|
+
existingSymbol?.type === DeclarationType.const ||
|
|
147
|
+
existingSymbol?.type === DeclarationType.function ||
|
|
148
|
+
existingSymbol?.type === DeclarationType.class ||
|
|
149
|
+
existingSymbol?.type !== declType) {
|
|
150
|
+
throw new SyntaxError(`Identifier '${name}' has already been declared.\n\n${" ".repeat(6) + decl.getText()}\n`);
|
|
147
151
|
}
|
|
148
152
|
// `var` variables can be declared multiple times
|
|
149
153
|
return "";
|
|
150
154
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
else {
|
|
156
|
+
// Add the symbol to the hoisted symbols
|
|
157
|
+
if (declType === DeclarationType.function) {
|
|
158
|
+
const isAsync = this.isAsyncFunction(decl);
|
|
159
|
+
const isGenerator = this.isGeneratorFunction(decl);
|
|
160
|
+
hoistedSymbols.add(name, {
|
|
161
|
+
type: declType,
|
|
162
|
+
func: { isAsync, isGenerator },
|
|
163
|
+
});
|
|
164
|
+
// Don't hoist functions not used as a variable
|
|
165
|
+
// they will be called with their native lambdas
|
|
166
|
+
if (!this.isFunctionUsedAsValue(decl, scopeNode) &&
|
|
167
|
+
!this.isFunctionUsedBeforeDeclaration(name, scopeNode)) {
|
|
168
|
+
return "";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
hoistedSymbols.add(name, { type: declType });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
155
175
|
const scope = this.getScopeForNode(decl);
|
|
156
176
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
|
|
157
|
-
const initializer =
|
|
177
|
+
const initializer = isLet || isConst || ts.isClassDeclaration(decl)
|
|
158
178
|
? "jspp::Constants::UNINITIALIZED"
|
|
159
179
|
: "jspp::Constants::UNDEFINED";
|
|
160
180
|
if (typeInfo.needsHeapAllocation) {
|
|
@@ -246,3 +266,121 @@ export function collectBlockScopedDeclarations(statements) {
|
|
|
246
266
|
}
|
|
247
267
|
return decls;
|
|
248
268
|
}
|
|
269
|
+
export function isFunctionUsedAsValue(decl, root) {
|
|
270
|
+
const funcName = decl.name?.getText();
|
|
271
|
+
if (!funcName)
|
|
272
|
+
return false;
|
|
273
|
+
let isUsed = false;
|
|
274
|
+
const visitor = (node) => {
|
|
275
|
+
if (isUsed)
|
|
276
|
+
return;
|
|
277
|
+
if (ts.isIdentifier(node) && node.text === funcName) {
|
|
278
|
+
const scope = this.getScopeForNode(node);
|
|
279
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(node.text, scope);
|
|
280
|
+
if (typeInfo?.declaration === decl) {
|
|
281
|
+
const parent = node.parent;
|
|
282
|
+
// Ignore declarations where the identifier is the name being declared
|
|
283
|
+
if ((ts.isFunctionDeclaration(parent) ||
|
|
284
|
+
ts.isVariableDeclaration(parent) ||
|
|
285
|
+
ts.isClassDeclaration(parent) ||
|
|
286
|
+
ts.isMethodDeclaration(parent) ||
|
|
287
|
+
ts.isParameter(parent) ||
|
|
288
|
+
ts.isImportSpecifier(parent)) &&
|
|
289
|
+
parent.name === node) {
|
|
290
|
+
// Declaration, do nothing
|
|
291
|
+
} // Ignore property names (e.g. obj.funcName)
|
|
292
|
+
else if ((ts.isPropertyAccessExpression(parent) &&
|
|
293
|
+
parent.name === node) ||
|
|
294
|
+
(ts.isPropertyAssignment(parent) && parent.name === node)) {
|
|
295
|
+
// Property name, do nothing
|
|
296
|
+
} // Ignore direct calls (e.g. funcName())
|
|
297
|
+
else if (ts.isCallExpression(parent) && parent.expression === node) {
|
|
298
|
+
// Call, do nothing
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
// Used as a value
|
|
302
|
+
isUsed = true;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (!isUsed) {
|
|
307
|
+
ts.forEachChild(node, visitor);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
ts.forEachChild(root, visitor);
|
|
311
|
+
return isUsed;
|
|
312
|
+
}
|
|
313
|
+
export function isFunctionUsedBeforeDeclaration(funcName, root) {
|
|
314
|
+
let declPos = -1;
|
|
315
|
+
let foundDecl = false;
|
|
316
|
+
// Helper to find the function declaration position
|
|
317
|
+
function findDecl(node) {
|
|
318
|
+
if (foundDecl)
|
|
319
|
+
return;
|
|
320
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === funcName) {
|
|
321
|
+
declPos = node.getStart();
|
|
322
|
+
foundDecl = true;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
ts.forEachChild(node, findDecl);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
findDecl(root);
|
|
329
|
+
// If not declared in this scope (or at least not found), assume it's not a local forward-ref issue
|
|
330
|
+
if (!foundDecl)
|
|
331
|
+
return false;
|
|
332
|
+
let isUsedBefore = false;
|
|
333
|
+
function visit(node) {
|
|
334
|
+
if (isUsedBefore)
|
|
335
|
+
return;
|
|
336
|
+
if (ts.isIdentifier(node) && node.text === funcName) {
|
|
337
|
+
const parent = node.parent;
|
|
338
|
+
// Ignore declarations where the identifier is the name being declared
|
|
339
|
+
if ((ts.isFunctionDeclaration(parent) ||
|
|
340
|
+
ts.isVariableDeclaration(parent) ||
|
|
341
|
+
ts.isClassDeclaration(parent) ||
|
|
342
|
+
ts.isMethodDeclaration(parent) ||
|
|
343
|
+
ts.isParameter(parent) ||
|
|
344
|
+
ts.isImportSpecifier(parent)) &&
|
|
345
|
+
parent.name === node) {
|
|
346
|
+
// Declaration (or shadowing), do nothing
|
|
347
|
+
} // Ignore property names (e.g. obj.funcName)
|
|
348
|
+
else if ((ts.isPropertyAccessExpression(parent) &&
|
|
349
|
+
parent.name === node) ||
|
|
350
|
+
(ts.isPropertyAssignment(parent) && parent.name === node)) {
|
|
351
|
+
// Property name, do nothing
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
// It is a usage (call or value usage)
|
|
355
|
+
const usagePos = node.getStart();
|
|
356
|
+
// Check 1: Lexically before
|
|
357
|
+
if (usagePos < declPos) {
|
|
358
|
+
isUsedBefore = true;
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
// Check 2: Inside a function declared before
|
|
362
|
+
let current = parent;
|
|
363
|
+
while (current && current !== root) {
|
|
364
|
+
if (ts.isFunctionDeclaration(current) ||
|
|
365
|
+
ts.isMethodDeclaration(current) ||
|
|
366
|
+
ts.isArrowFunction(current) ||
|
|
367
|
+
ts.isFunctionExpression(current) ||
|
|
368
|
+
ts.isGetAccessor(current) ||
|
|
369
|
+
ts.isSetAccessor(current) ||
|
|
370
|
+
ts.isConstructorDeclaration(current)) {
|
|
371
|
+
if (current.getStart() < declPos) {
|
|
372
|
+
isUsedBefore = true;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
// Once we hit a function boundary, we stop bubbling up for this usage.
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
current = current.parent;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
ts.forEachChild(node, visit);
|
|
383
|
+
}
|
|
384
|
+
visit(root);
|
|
385
|
+
return isUsedBefore;
|
|
386
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { DeclaredSymbols } from "../../ast/symbols";
|
|
2
|
-
import { generateLambda } from "./function-handlers";
|
|
3
|
-
import { escapeString, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isGeneratorFunction,
|
|
4
|
-
import { visit } from "./visitor";
|
|
1
|
+
import { DeclaredSymbols } from "../../ast/symbols.js";
|
|
2
|
+
import { generateFullLambdaExpression, generateLambda, } from "./function-handlers.js";
|
|
3
|
+
import { escapeString, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isFunctionUsedAsValue, isFunctionUsedBeforeDeclaration, isGeneratorFunction, markSymbolAsInitialized, prepareScopeSymbolsForVisit, } from "./helpers.js";
|
|
4
|
+
import { visit } from "./visitor.js";
|
|
5
5
|
const MODULE_NAME = "__main_function__";
|
|
6
6
|
export class CodeGenerator {
|
|
7
7
|
indentationLevel = 0;
|
|
@@ -25,9 +25,12 @@ export class CodeGenerator {
|
|
|
25
25
|
isGeneratorFunction = isGeneratorFunction;
|
|
26
26
|
isAsyncFunction = isAsyncFunction;
|
|
27
27
|
prepareScopeSymbolsForVisit = prepareScopeSymbolsForVisit;
|
|
28
|
-
|
|
28
|
+
markSymbolAsInitialized = markSymbolAsInitialized;
|
|
29
|
+
isFunctionUsedAsValue = isFunctionUsedAsValue;
|
|
30
|
+
isFunctionUsedBeforeDeclaration = isFunctionUsedBeforeDeclaration;
|
|
29
31
|
// function handlers
|
|
30
32
|
generateLambda = generateLambda;
|
|
33
|
+
generateFullLambdaExpression = generateFullLambdaExpression;
|
|
31
34
|
/**
|
|
32
35
|
* Main entry point for the code generation process.
|
|
33
36
|
*/
|
|
@@ -40,11 +43,12 @@ export class CodeGenerator {
|
|
|
40
43
|
containerCode +=
|
|
41
44
|
`${this.indent()}jspp::AnyValue ${this.globalThisVar} = global;\n`;
|
|
42
45
|
containerCode += this.visit(ast, {
|
|
46
|
+
currentScopeNode: ast,
|
|
43
47
|
isMainContext: true,
|
|
44
48
|
isInsideFunction: true,
|
|
45
49
|
isFunctionBody: true,
|
|
46
50
|
isInsideAsyncFunction: true,
|
|
47
|
-
|
|
51
|
+
globalScopeSymbols: new DeclaredSymbols(),
|
|
48
52
|
localScopeSymbols: new DeclaredSymbols(),
|
|
49
53
|
});
|
|
50
54
|
this.indentationLevel--;
|