@ugo-studio/jspp 0.1.2 → 0.1.4
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 +5 -3
- package/dist/analysis/scope.js +38 -15
- package/dist/analysis/typeAnalyzer.js +257 -23
- package/dist/ast/types.js +6 -0
- package/dist/cli.js +3 -4
- package/dist/core/codegen/class-handlers.js +127 -0
- package/dist/core/codegen/control-flow-handlers.js +464 -0
- package/dist/core/codegen/declaration-handlers.js +31 -14
- package/dist/core/codegen/expression-handlers.js +432 -116
- package/dist/core/codegen/function-handlers.js +110 -13
- package/dist/core/codegen/helpers.js +76 -8
- package/dist/core/codegen/index.js +18 -5
- package/dist/core/codegen/literal-handlers.js +3 -0
- package/dist/core/codegen/statement-handlers.js +152 -186
- package/dist/core/codegen/visitor.js +35 -3
- package/package.json +3 -3
- package/src/prelude/any_value.hpp +658 -734
- package/src/prelude/any_value_access.hpp +103 -0
- package/src/prelude/any_value_defines.hpp +151 -0
- package/src/prelude/any_value_helpers.hpp +246 -0
- package/src/prelude/exception.hpp +31 -0
- package/src/prelude/exception_helpers.hpp +49 -0
- package/src/prelude/index.hpp +35 -12
- package/src/prelude/library/console.hpp +20 -20
- package/src/prelude/library/error.hpp +111 -0
- package/src/prelude/library/global.hpp +15 -4
- package/src/prelude/library/performance.hpp +25 -0
- package/src/prelude/library/promise.hpp +121 -0
- package/src/prelude/library/symbol.hpp +60 -4
- package/src/prelude/library/timer.hpp +92 -0
- package/src/prelude/scheduler.hpp +145 -0
- package/src/prelude/types.hpp +33 -6
- package/src/prelude/utils/access.hpp +174 -0
- package/src/prelude/utils/log_any_value/array.hpp +245 -0
- package/src/prelude/utils/log_any_value/config.hpp +32 -0
- package/src/prelude/utils/log_any_value/function.hpp +37 -0
- package/src/prelude/utils/log_any_value/fwd.hpp +15 -0
- package/src/prelude/utils/log_any_value/helpers.hpp +62 -0
- package/src/prelude/utils/log_any_value/log_any_value.hpp +94 -0
- package/src/prelude/utils/log_any_value/object.hpp +119 -0
- package/src/prelude/utils/log_any_value/primitives.hpp +41 -0
- package/src/prelude/{operators.hpp → utils/operators.hpp} +31 -12
- package/src/prelude/utils/well_known_symbols.hpp +13 -0
- package/src/prelude/values/array.hpp +5 -2
- package/src/prelude/{descriptors.hpp → values/descriptors.hpp} +2 -2
- package/src/prelude/values/function.hpp +76 -19
- package/src/prelude/values/{operators → helpers}/array.hpp +30 -14
- package/src/prelude/values/helpers/function.hpp +125 -0
- package/src/prelude/values/helpers/iterator.hpp +107 -0
- package/src/prelude/values/helpers/object.hpp +64 -0
- package/src/prelude/values/helpers/promise.hpp +181 -0
- package/src/prelude/values/helpers/string.hpp +50 -0
- package/src/prelude/values/helpers/symbol.hpp +23 -0
- package/src/prelude/values/iterator.hpp +96 -0
- package/src/prelude/values/object.hpp +8 -3
- package/src/prelude/values/promise.hpp +73 -0
- package/src/prelude/values/prototypes/array.hpp +23 -16
- package/src/prelude/values/prototypes/function.hpp +26 -0
- package/src/prelude/values/prototypes/iterator.hpp +58 -0
- package/src/prelude/values/prototypes/object.hpp +26 -0
- package/src/prelude/values/prototypes/promise.hpp +124 -0
- package/src/prelude/values/prototypes/string.hpp +366 -357
- package/src/prelude/values/prototypes/symbol.hpp +41 -0
- package/src/prelude/values/string.hpp +25 -0
- package/src/prelude/values/symbol.hpp +102 -0
- package/src/prelude/access.hpp +0 -86
- package/src/prelude/error.hpp +0 -31
- package/src/prelude/error_helpers.hpp +0 -59
- package/src/prelude/log_string.hpp +0 -403
- package/src/prelude/values/operators/function.hpp +0 -34
- package/src/prelude/values/operators/object.hpp +0 -34
- package/src/prelude/well_known_symbols.hpp +0 -10
package/README.md
CHANGED
|
@@ -33,6 +33,8 @@ 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++ generator functions.
|
|
37
|
+
- `co_return`: Reserved to prevent conflicts with the c++ generator functions.
|
|
36
38
|
|
|
37
39
|
Using these keywords as variable names will result in a `SyntaxError`.
|
|
38
40
|
|
|
@@ -45,7 +47,7 @@ The transpilation process is a classic three-stage pipeline:
|
|
|
45
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.
|
|
46
48
|
|
|
47
49
|
3. **Code Generation:** The `CodeGenerator` performs a final traversal of the AST. It translates each node into its C++ equivalent.
|
|
48
|
-
- All variables are declared as `std::shared_ptr<AnyValue>` (where `AnyValue` uses a [Tagged Union](https://en.wikipedia.org/wiki/Tagged_union)
|
|
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.
|
|
49
51
|
- Closures are implemented as C++ lambdas that capture `shared_ptr`s by value, ensuring variable lifetimes are correctly extended beyond their original scope.
|
|
50
52
|
- The entire script is wrapped into a single `main` function, with hoisting logic carefully replicated to ensure correct execution order.
|
|
51
53
|
|
|
@@ -130,15 +132,15 @@ This phase will broaden the range of supported JavaScript syntax and features.
|
|
|
130
132
|
- [x] **Objects:** Literals, property access (dot and bracket notation), methods.
|
|
131
133
|
- [x] **Arrays:** Literals, indexing, and core methods (`.push`, `.pop`, `.length`, etc.).
|
|
132
134
|
- [x] **Operators:** Full suite of arithmetic, logical, and comparison operators.
|
|
133
|
-
- [
|
|
135
|
+
- [x] **Control Flow:** tenary operators, `for` loops, `while` loops, `switch`.
|
|
134
136
|
|
|
135
137
|
### **Phase 3: Interoperability & Standard Library**
|
|
136
138
|
|
|
137
139
|
This phase will focus on building out the standard library and enabling modular code.
|
|
138
140
|
|
|
139
141
|
- [ ] **JS Standard Library:** Implementation of common built-in objects and functions (`Math`, `Date`, `String.prototype.*`, `Array.prototype.*`).
|
|
142
|
+
- [x] **Asynchronous Operations:** A C++ implementation of the JavaScript event loop, `Promise`, and `async/await`.
|
|
140
143
|
- [ ] **Module System:** Support for `import` and `export` to transpile multi-file projects.
|
|
141
|
-
- [ ] **Asynchronous Operations:** A C++ implementation of the JavaScript event loop, `Promise`, and `async/await`.
|
|
142
144
|
|
|
143
145
|
### **Phase 4: Optimization & Advanced Features**
|
|
144
146
|
|
package/dist/analysis/scope.js
CHANGED
|
@@ -1,10 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
export const RESERVED_KEYWORDS = new Set([
|
|
3
|
+
"jspp",
|
|
4
|
+
"std",
|
|
5
|
+
"co_yield",
|
|
6
|
+
"co_return",
|
|
7
|
+
"co_await",
|
|
8
|
+
]);
|
|
9
|
+
export const BUILTIN_OBJECTS = new Set([
|
|
10
|
+
{ name: "undefined", isConst: true },
|
|
11
|
+
{ name: "null", isConst: true },
|
|
12
|
+
{ name: "Symbol", isConst: false },
|
|
13
|
+
{ name: "console", isConst: false },
|
|
14
|
+
{ name: "performance", isConst: false },
|
|
15
|
+
{ name: "global", isConst: false },
|
|
16
|
+
{ name: "globalThis", isConst: false },
|
|
17
|
+
{ name: "Error", isConst: false },
|
|
18
|
+
{ name: "Promise", isConst: false },
|
|
19
|
+
{ name: "setTimeout", isConst: false },
|
|
20
|
+
{ name: "clearTimeout", isConst: false },
|
|
21
|
+
{ name: "setInterval", isConst: false },
|
|
22
|
+
{ name: "clearInterval", isConst: false },
|
|
23
|
+
]);
|
|
2
24
|
// Represents a single scope (e.g., a function body or a block statement)
|
|
3
25
|
export class Scope {
|
|
4
26
|
parent;
|
|
27
|
+
ownerFunction;
|
|
5
28
|
symbols = new Map();
|
|
6
|
-
|
|
29
|
+
children = [];
|
|
30
|
+
constructor(parent, ownerFunction) {
|
|
7
31
|
this.parent = parent;
|
|
32
|
+
this.ownerFunction = ownerFunction;
|
|
8
33
|
}
|
|
9
34
|
// Defines a variable in this scope.
|
|
10
35
|
define(name, type) {
|
|
@@ -28,23 +53,21 @@ export class ScopeManager {
|
|
|
28
53
|
allScopes = []; // Array to store all created scopes
|
|
29
54
|
reservedKeywords = new Set(RESERVED_KEYWORDS);
|
|
30
55
|
constructor() {
|
|
31
|
-
const rootScope = new Scope(null); // The global scope
|
|
56
|
+
const rootScope = new Scope(null, null); // The global scope
|
|
32
57
|
this.currentScope = rootScope;
|
|
33
58
|
this.allScopes.push(rootScope); // Add the root scope to our list
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
isConst: true,
|
|
42
|
-
isBuiltin: true,
|
|
43
|
-
});
|
|
59
|
+
for (const { name, isConst } of BUILTIN_OBJECTS) {
|
|
60
|
+
this.define(name, {
|
|
61
|
+
type: name,
|
|
62
|
+
isConst: isConst,
|
|
63
|
+
isBuiltin: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
44
66
|
}
|
|
45
67
|
// Enters a new, nested scope.
|
|
46
|
-
enterScope() {
|
|
47
|
-
const newScope = new Scope(this.currentScope);
|
|
68
|
+
enterScope(ownerFunction) {
|
|
69
|
+
const newScope = new Scope(this.currentScope, ownerFunction);
|
|
70
|
+
this.currentScope.children.push(newScope);
|
|
48
71
|
this.currentScope = newScope;
|
|
49
72
|
this.allScopes.push(newScope); // Add every new scope to the list
|
|
50
73
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
|
+
import { isBuiltinObject } from "../core/codegen/helpers";
|
|
2
3
|
import { Traverser } from "../core/traverser";
|
|
3
4
|
import { Scope, ScopeManager } from "./scope";
|
|
4
5
|
export class TypeAnalyzer {
|
|
@@ -7,13 +8,34 @@ export class TypeAnalyzer {
|
|
|
7
8
|
functionTypeInfo = new Map();
|
|
8
9
|
functionStack = [];
|
|
9
10
|
nodeToScope = new Map();
|
|
11
|
+
labelStack = [];
|
|
12
|
+
loopDepth = 0;
|
|
13
|
+
switchDepth = 0;
|
|
10
14
|
analyze(ast) {
|
|
11
15
|
this.nodeToScope.set(ast, this.scopeManager.currentScope);
|
|
16
|
+
const crossScopeModificationVisitor = (node) => {
|
|
17
|
+
if (ts.isIdentifier(node)) {
|
|
18
|
+
const name = node.getText();
|
|
19
|
+
const definingScope = this.scopeManager.currentScope
|
|
20
|
+
.findScopeFor(name);
|
|
21
|
+
if (definingScope) {
|
|
22
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
23
|
+
const definingFunc = definingScope.ownerFunction;
|
|
24
|
+
if (definingFunc !== currentFuncNode) {
|
|
25
|
+
const typeInfo = this.scopeManager.lookup(name);
|
|
26
|
+
if (typeInfo) {
|
|
27
|
+
typeInfo.needsHeapAllocation = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
12
33
|
const visitor = {
|
|
13
34
|
// Enter new scope for any block-like structure
|
|
14
35
|
Block: {
|
|
15
36
|
enter: (node, parent) => {
|
|
16
|
-
this.
|
|
37
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
38
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
17
39
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
18
40
|
if (parent && ts.isCatchClause(parent) &&
|
|
19
41
|
parent.variableDeclaration) {
|
|
@@ -34,28 +56,41 @@ export class TypeAnalyzer {
|
|
|
34
56
|
},
|
|
35
57
|
ForStatement: {
|
|
36
58
|
enter: (node) => {
|
|
37
|
-
this.
|
|
59
|
+
this.loopDepth++;
|
|
60
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
61
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
38
62
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
39
63
|
},
|
|
40
|
-
exit: () =>
|
|
64
|
+
exit: () => {
|
|
65
|
+
this.loopDepth--;
|
|
66
|
+
this.scopeManager.exitScope();
|
|
67
|
+
},
|
|
41
68
|
},
|
|
42
69
|
ForOfStatement: {
|
|
43
70
|
enter: (node) => {
|
|
44
|
-
this.
|
|
71
|
+
this.loopDepth++;
|
|
72
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
73
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
45
74
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
46
75
|
},
|
|
47
|
-
exit: () =>
|
|
76
|
+
exit: () => {
|
|
77
|
+
this.loopDepth--;
|
|
78
|
+
this.scopeManager.exitScope();
|
|
79
|
+
},
|
|
48
80
|
},
|
|
49
81
|
ForInStatement: {
|
|
50
82
|
enter: (node) => {
|
|
51
|
-
this.
|
|
83
|
+
this.loopDepth++;
|
|
84
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
85
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
52
86
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
53
87
|
const forIn = node;
|
|
54
88
|
if (ts.isVariableDeclarationList(forIn.initializer)) {
|
|
55
89
|
const varDecl = forIn.initializer.declarations[0];
|
|
56
90
|
if (varDecl) {
|
|
57
91
|
const name = varDecl.name.getText();
|
|
58
|
-
const isConst = (varDecl.parent.flags & ts.NodeFlags.Const) !==
|
|
92
|
+
const isConst = (varDecl.parent.flags & ts.NodeFlags.Const) !==
|
|
93
|
+
0;
|
|
59
94
|
const typeInfo = {
|
|
60
95
|
type: "string", // Keys are always strings
|
|
61
96
|
declaration: varDecl,
|
|
@@ -69,7 +104,87 @@ export class TypeAnalyzer {
|
|
|
69
104
|
// The generator will handle assigning to it.
|
|
70
105
|
}
|
|
71
106
|
},
|
|
72
|
-
exit: () =>
|
|
107
|
+
exit: () => {
|
|
108
|
+
this.loopDepth--;
|
|
109
|
+
this.scopeManager.exitScope();
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
WhileStatement: {
|
|
113
|
+
enter: (node) => {
|
|
114
|
+
this.loopDepth++;
|
|
115
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
116
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
117
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
118
|
+
},
|
|
119
|
+
exit: () => {
|
|
120
|
+
this.loopDepth--;
|
|
121
|
+
this.scopeManager.exitScope();
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
DoStatement: {
|
|
125
|
+
enter: (node) => {
|
|
126
|
+
this.loopDepth++;
|
|
127
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
128
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
129
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
130
|
+
},
|
|
131
|
+
exit: () => {
|
|
132
|
+
this.loopDepth--;
|
|
133
|
+
this.scopeManager.exitScope();
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
SwitchStatement: {
|
|
137
|
+
enter: (node) => {
|
|
138
|
+
this.switchDepth++;
|
|
139
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
140
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
141
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
142
|
+
},
|
|
143
|
+
exit: () => {
|
|
144
|
+
this.switchDepth--;
|
|
145
|
+
this.scopeManager.exitScope();
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
LabeledStatement: {
|
|
149
|
+
enter: (node) => {
|
|
150
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
151
|
+
this.labelStack.push(node.label.text);
|
|
152
|
+
},
|
|
153
|
+
exit: () => {
|
|
154
|
+
this.labelStack.pop();
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
BreakStatement: {
|
|
158
|
+
enter: (node) => {
|
|
159
|
+
const breakNode = node;
|
|
160
|
+
if (breakNode.label) {
|
|
161
|
+
if (!this.labelStack.includes(breakNode.label.text)) {
|
|
162
|
+
throw new Error(`SyntaxError: Undefined label '${breakNode.label.text}'`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
if (this.loopDepth === 0 && this.switchDepth === 0) {
|
|
167
|
+
throw new Error("SyntaxError: Unlabeled break must be inside an iteration or switch statement");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
ContinueStatement: {
|
|
173
|
+
enter: (node) => {
|
|
174
|
+
const continueNode = node;
|
|
175
|
+
if (continueNode.label) {
|
|
176
|
+
if (!this.labelStack.includes(continueNode.label.text)) {
|
|
177
|
+
throw new Error(`SyntaxError: Undefined label '${continueNode.label.text}'`);
|
|
178
|
+
}
|
|
179
|
+
// Also need to check if the label belongs to a loop, but that's harder here.
|
|
180
|
+
// The TS checker should handle this. We'll assume for now it does.
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
if (this.loopDepth === 0) {
|
|
184
|
+
throw new Error("SyntaxError: Unlabeled continue must be inside an iteration statement");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
73
188
|
},
|
|
74
189
|
ArrowFunction: {
|
|
75
190
|
enter: (node) => {
|
|
@@ -80,7 +195,7 @@ export class TypeAnalyzer {
|
|
|
80
195
|
captures: new Map(),
|
|
81
196
|
};
|
|
82
197
|
this.functionTypeInfo.set(node, funcType);
|
|
83
|
-
this.scopeManager.enterScope();
|
|
198
|
+
this.scopeManager.enterScope(node);
|
|
84
199
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
85
200
|
// Define parameters in the new scope
|
|
86
201
|
node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
|
|
@@ -106,9 +221,10 @@ export class TypeAnalyzer {
|
|
|
106
221
|
isClosure: false,
|
|
107
222
|
captures: new Map(),
|
|
108
223
|
declaration: node,
|
|
224
|
+
needsHeapAllocation: true, // Added: Functions are always heap-allocated
|
|
109
225
|
};
|
|
110
226
|
this.functionTypeInfo.set(node, funcType);
|
|
111
|
-
this.scopeManager.enterScope();
|
|
227
|
+
this.scopeManager.enterScope(node);
|
|
112
228
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
113
229
|
// If the function expression is named, define the name within its own scope for recursion.
|
|
114
230
|
if (node.name) {
|
|
@@ -141,11 +257,12 @@ export class TypeAnalyzer {
|
|
|
141
257
|
isClosure: false,
|
|
142
258
|
captures: new Map(),
|
|
143
259
|
declaration: node,
|
|
260
|
+
needsHeapAllocation: true,
|
|
144
261
|
};
|
|
145
262
|
this.scopeManager.define(funcName, funcType);
|
|
146
263
|
this.functionTypeInfo.set(node, funcType);
|
|
147
264
|
}
|
|
148
|
-
this.scopeManager.enterScope();
|
|
265
|
+
this.scopeManager.enterScope(node);
|
|
149
266
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
150
267
|
// Define parameters in the new scope
|
|
151
268
|
node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
|
|
@@ -163,21 +280,106 @@ export class TypeAnalyzer {
|
|
|
163
280
|
this.scopeManager.exitScope();
|
|
164
281
|
},
|
|
165
282
|
},
|
|
283
|
+
ClassDeclaration: {
|
|
284
|
+
enter: (node) => {
|
|
285
|
+
const classNode = node;
|
|
286
|
+
if (classNode.name) {
|
|
287
|
+
const name = classNode.name.getText();
|
|
288
|
+
const typeInfo = {
|
|
289
|
+
type: "function", // Classes are functions
|
|
290
|
+
declaration: classNode,
|
|
291
|
+
needsHeapAllocation: true,
|
|
292
|
+
};
|
|
293
|
+
this.scopeManager.define(name, typeInfo);
|
|
294
|
+
}
|
|
295
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
296
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
297
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
298
|
+
},
|
|
299
|
+
exit: () => {
|
|
300
|
+
this.scopeManager.exitScope();
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
MethodDeclaration: {
|
|
304
|
+
enter: (node) => {
|
|
305
|
+
if (ts.isMethodDeclaration(node)) {
|
|
306
|
+
const funcType = {
|
|
307
|
+
type: "function",
|
|
308
|
+
isClosure: false,
|
|
309
|
+
captures: new Map(),
|
|
310
|
+
declaration: node,
|
|
311
|
+
needsHeapAllocation: true,
|
|
312
|
+
};
|
|
313
|
+
// Methods don't need to be defined in scope by name generally,
|
|
314
|
+
// but we need to track them for captures.
|
|
315
|
+
this.functionTypeInfo.set(node, funcType);
|
|
316
|
+
this.scopeManager.enterScope(node);
|
|
317
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
318
|
+
node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
|
|
319
|
+
type: "auto",
|
|
320
|
+
isParameter: true,
|
|
321
|
+
declaration: p,
|
|
322
|
+
}));
|
|
323
|
+
this.functionStack.push(node);
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
exit: (node) => {
|
|
327
|
+
if (ts.isMethodDeclaration(node)) {
|
|
328
|
+
this.functionStack.pop();
|
|
329
|
+
}
|
|
330
|
+
this.scopeManager.exitScope();
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
Constructor: {
|
|
334
|
+
enter: (node) => {
|
|
335
|
+
if (ts.isConstructorDeclaration(node)) {
|
|
336
|
+
const funcType = {
|
|
337
|
+
type: "function",
|
|
338
|
+
isClosure: false,
|
|
339
|
+
captures: new Map(),
|
|
340
|
+
declaration: node,
|
|
341
|
+
needsHeapAllocation: true,
|
|
342
|
+
};
|
|
343
|
+
this.functionTypeInfo.set(node, funcType);
|
|
344
|
+
this.scopeManager.enterScope(node);
|
|
345
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
346
|
+
node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
|
|
347
|
+
type: "auto",
|
|
348
|
+
isParameter: true,
|
|
349
|
+
declaration: p,
|
|
350
|
+
}));
|
|
351
|
+
this.functionStack.push(node); // Constructor acts like a function
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
exit: (node) => {
|
|
355
|
+
if (ts.isConstructorDeclaration(node)) {
|
|
356
|
+
this.functionStack.pop();
|
|
357
|
+
}
|
|
358
|
+
this.scopeManager.exitScope();
|
|
359
|
+
},
|
|
360
|
+
},
|
|
166
361
|
VariableDeclaration: {
|
|
167
362
|
enter: (node) => {
|
|
168
363
|
if (ts.isVariableDeclaration(node)) {
|
|
169
364
|
const name = node.name.getText();
|
|
170
365
|
const isConst = (node.parent.flags & ts.NodeFlags.Const) !== 0;
|
|
171
366
|
let type = "auto";
|
|
367
|
+
let needsHeap = false;
|
|
172
368
|
if (node.initializer) {
|
|
173
369
|
if (ts.isArrayLiteralExpression(node.initializer)) {
|
|
174
370
|
type = "array";
|
|
175
371
|
}
|
|
372
|
+
else if (ts.isArrowFunction(node.initializer) ||
|
|
373
|
+
ts.isFunctionExpression(node.initializer)) {
|
|
374
|
+
type = "function";
|
|
375
|
+
needsHeap = true;
|
|
376
|
+
}
|
|
176
377
|
}
|
|
177
378
|
const typeInfo = {
|
|
178
379
|
type,
|
|
179
380
|
declaration: node,
|
|
180
381
|
isConst,
|
|
382
|
+
needsHeapAllocation: needsHeap,
|
|
181
383
|
};
|
|
182
384
|
this.scopeManager.define(name, typeInfo);
|
|
183
385
|
}
|
|
@@ -186,10 +388,9 @@ export class TypeAnalyzer {
|
|
|
186
388
|
Identifier: {
|
|
187
389
|
enter: (node, parent) => {
|
|
188
390
|
if (ts.isIdentifier(node)) {
|
|
189
|
-
if (
|
|
391
|
+
if (isBuiltinObject.call(this, node))
|
|
190
392
|
return;
|
|
191
|
-
|
|
192
|
-
const currentFuncNode = this.functionStack[this.functionStack.length - 1];
|
|
393
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
193
394
|
if (currentFuncNode &&
|
|
194
395
|
(ts.isFunctionDeclaration(currentFuncNode) ||
|
|
195
396
|
ts.isFunctionExpression(currentFuncNode)) &&
|
|
@@ -200,17 +401,19 @@ export class TypeAnalyzer {
|
|
|
200
401
|
// And see if it belongs to an outer scope
|
|
201
402
|
const definingScope = this.scopeManager.currentScope
|
|
202
403
|
.findScopeFor(node.text);
|
|
203
|
-
if (definingScope
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (currentFuncNode) {
|
|
404
|
+
if (definingScope) {
|
|
405
|
+
const definingFunc = definingScope.ownerFunction;
|
|
406
|
+
if (definingFunc !== currentFuncNode) {
|
|
407
|
+
// This is a capture!
|
|
208
408
|
const type = this.scopeManager.lookup(node.text);
|
|
209
409
|
if (type) {
|
|
210
|
-
|
|
211
|
-
if (
|
|
212
|
-
info
|
|
213
|
-
info
|
|
410
|
+
type.needsHeapAllocation = true;
|
|
411
|
+
if (currentFuncNode) {
|
|
412
|
+
const info = this.functionTypeInfo.get(currentFuncNode);
|
|
413
|
+
if (info) {
|
|
414
|
+
info.isClosure = true;
|
|
415
|
+
info.captures?.set(node.text, type);
|
|
416
|
+
}
|
|
214
417
|
}
|
|
215
418
|
}
|
|
216
419
|
}
|
|
@@ -218,6 +421,37 @@ export class TypeAnalyzer {
|
|
|
218
421
|
}
|
|
219
422
|
},
|
|
220
423
|
},
|
|
424
|
+
BinaryExpression: {
|
|
425
|
+
enter: (node) => {
|
|
426
|
+
if (ts.isBinaryExpression(node)) {
|
|
427
|
+
const isAssignment = node.operatorToken.kind >=
|
|
428
|
+
ts.SyntaxKind.FirstAssignment &&
|
|
429
|
+
node.operatorToken.kind <=
|
|
430
|
+
ts.SyntaxKind.LastAssignment;
|
|
431
|
+
if (isAssignment) {
|
|
432
|
+
crossScopeModificationVisitor(node.left);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
PostfixUnaryExpression: {
|
|
438
|
+
enter: (node) => {
|
|
439
|
+
if (ts.isPostfixUnaryExpression(node)) {
|
|
440
|
+
crossScopeModificationVisitor(node.operand);
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
PrefixUnaryExpression: {
|
|
445
|
+
enter: (node) => {
|
|
446
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
447
|
+
const op = node.operator;
|
|
448
|
+
if (op === ts.SyntaxKind.PlusPlusToken ||
|
|
449
|
+
op === ts.SyntaxKind.MinusMinusToken) {
|
|
450
|
+
crossScopeModificationVisitor(node.operand);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
},
|
|
221
455
|
};
|
|
222
456
|
this.traverser.traverse(ast, visitor);
|
|
223
457
|
}
|
package/dist/ast/types.js
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
|
+
export var DeclaredSymbolType;
|
|
3
|
+
(function (DeclaredSymbolType) {
|
|
4
|
+
DeclaredSymbolType["letOrConst"] = "letOrConst";
|
|
5
|
+
DeclaredSymbolType["function"] = "function";
|
|
6
|
+
DeclaredSymbolType["var"] = "var";
|
|
7
|
+
})(DeclaredSymbolType || (DeclaredSymbolType = {}));
|
package/dist/cli.js
CHANGED
|
@@ -16,22 +16,21 @@ async function main() {
|
|
|
16
16
|
try {
|
|
17
17
|
const jsCode = await fs.readFile(jsFilePath, "utf-8");
|
|
18
18
|
const interpreter = new Interpreter();
|
|
19
|
-
console.time(`Generated C++ code ${cppFilePath}...`);
|
|
20
19
|
const { cppCode, preludePath } = interpreter.interpret(jsCode);
|
|
21
|
-
console.
|
|
20
|
+
console.log(`Generated C++ code ${cppFilePath}...`);
|
|
22
21
|
await fs.mkdir(outputDir, { recursive: true });
|
|
23
22
|
await fs.writeFile(cppFilePath, cppCode);
|
|
24
23
|
console.log(`Compiling ${cppFilePath}...`);
|
|
25
24
|
const compile = Bun.spawnSync({
|
|
26
25
|
cmd: [
|
|
27
26
|
"g++",
|
|
28
|
-
"-std=c++
|
|
27
|
+
"-std=c++23",
|
|
29
28
|
cppFilePath,
|
|
30
29
|
"-o",
|
|
31
30
|
exeFilePath,
|
|
32
31
|
"-I",
|
|
33
32
|
preludePath,
|
|
34
|
-
"-
|
|
33
|
+
"-O3",
|
|
35
34
|
"-DNDEBUG",
|
|
36
35
|
// "-include",
|
|
37
36
|
// path.join(process.cwd(), "prelude-build", "index.hpp"),
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { CodeGenerator } from "./";
|
|
3
|
+
import { visitObjectPropertyName } from "./expression-handlers";
|
|
4
|
+
export function visitClassDeclaration(node, context) {
|
|
5
|
+
const className = node.name.getText();
|
|
6
|
+
// Check extends
|
|
7
|
+
let extendsExpr = null;
|
|
8
|
+
if (node.heritageClauses) {
|
|
9
|
+
for (const clause of node.heritageClauses) {
|
|
10
|
+
if (clause.token === ts.SyntaxKind.ExtendsKeyword &&
|
|
11
|
+
clause.types.length > 0) {
|
|
12
|
+
extendsExpr = clause.types[0].expression;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
let parentName = "";
|
|
17
|
+
if (extendsExpr) {
|
|
18
|
+
parentName = this.visit(extendsExpr, context);
|
|
19
|
+
if (ts.isIdentifier(extendsExpr)) {
|
|
20
|
+
const scope = this.getScopeForNode(extendsExpr);
|
|
21
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(extendsExpr.getText(), scope);
|
|
22
|
+
if (typeInfo) {
|
|
23
|
+
parentName = this.getDerefCode(parentName, this.getJsVarName(extendsExpr), typeInfo);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const classContext = {
|
|
28
|
+
...context,
|
|
29
|
+
superClassVar: parentName,
|
|
30
|
+
};
|
|
31
|
+
// 1. Constructor
|
|
32
|
+
const constructor = node.members.find(ts.isConstructorDeclaration);
|
|
33
|
+
let constructorLambda = "";
|
|
34
|
+
if (constructor) {
|
|
35
|
+
constructorLambda = this.generateLambda(constructor, {
|
|
36
|
+
...classContext,
|
|
37
|
+
isInsideFunction: true,
|
|
38
|
+
lambdaName: className,
|
|
39
|
+
}, { isClass: true });
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Default constructor
|
|
43
|
+
if (parentName) {
|
|
44
|
+
constructorLambda =
|
|
45
|
+
`jspp::AnyValue::make_class([=](const jspp::AnyValue& ${this.globalThisVar}, const std::vector<jspp::AnyValue>& args) mutable -> jspp::AnyValue {
|
|
46
|
+
auto __parent = ${parentName};
|
|
47
|
+
__parent.as_function("super")->call(${this.globalThisVar}, args);
|
|
48
|
+
return jspp::AnyValue::make_undefined();
|
|
49
|
+
}, "${className}")`;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
constructorLambda =
|
|
53
|
+
`jspp::AnyValue::make_class([=](const jspp::AnyValue& ${this.globalThisVar}, const std::vector<jspp::AnyValue>& args) mutable -> jspp::AnyValue {
|
|
54
|
+
return jspp::AnyValue::make_undefined();
|
|
55
|
+
}, "${className}")`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
let code = `${this.indent()}*${className} = ${constructorLambda};\n`;
|
|
59
|
+
// Set prototype of class (static inheritance) and prototype object (instance inheritance)
|
|
60
|
+
if (parentName) {
|
|
61
|
+
code +=
|
|
62
|
+
`${this.indent()}(*${className}).set_prototype(${parentName});\n`;
|
|
63
|
+
code +=
|
|
64
|
+
`${this.indent()}(*${className}).get_own_property("prototype").set_prototype(${parentName}.get_own_property("prototype"));\n`;
|
|
65
|
+
}
|
|
66
|
+
// Members
|
|
67
|
+
for (const member of node.members) {
|
|
68
|
+
if (ts.isMethodDeclaration(member)) {
|
|
69
|
+
const methodName = visitObjectPropertyName.call(this, member.name, {
|
|
70
|
+
...context,
|
|
71
|
+
isObjectLiteralExpression: true, // Reuse this flag to handle computed properties
|
|
72
|
+
});
|
|
73
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
74
|
+
const methodLambda = this.generateLambda(member, {
|
|
75
|
+
...classContext,
|
|
76
|
+
isInsideFunction: true,
|
|
77
|
+
});
|
|
78
|
+
if (isStatic) {
|
|
79
|
+
code +=
|
|
80
|
+
`${this.indent()}(*${className}).set_own_property(${methodName}, ${methodLambda});\n`;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
code +=
|
|
84
|
+
`${this.indent()}(*${className}).get_own_property("prototype").set_own_property(${methodName}, ${methodLambda});\n`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (ts.isGetAccessor(member)) {
|
|
88
|
+
const methodName = visitObjectPropertyName.call(this, member.name, {
|
|
89
|
+
...context,
|
|
90
|
+
isObjectLiteralExpression: true,
|
|
91
|
+
});
|
|
92
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
93
|
+
const lambda = this.generateLambda(member, {
|
|
94
|
+
...classContext,
|
|
95
|
+
isInsideFunction: true,
|
|
96
|
+
});
|
|
97
|
+
if (isStatic) {
|
|
98
|
+
code +=
|
|
99
|
+
`${this.indent()}(*${className}).define_getter(${methodName}, ${lambda});\n`;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
code +=
|
|
103
|
+
`${this.indent()}(*${className}).get_own_property("prototype").define_getter(${methodName}, ${lambda});\n`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (ts.isSetAccessor(member)) {
|
|
107
|
+
const methodName = visitObjectPropertyName.call(this, member.name, {
|
|
108
|
+
...context,
|
|
109
|
+
isObjectLiteralExpression: true,
|
|
110
|
+
});
|
|
111
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
112
|
+
const lambda = this.generateLambda(member, {
|
|
113
|
+
...classContext,
|
|
114
|
+
isInsideFunction: true,
|
|
115
|
+
});
|
|
116
|
+
if (isStatic) {
|
|
117
|
+
code +=
|
|
118
|
+
`${this.indent()}(*${className}).define_setter(${methodName}, ${lambda});\n`;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
code +=
|
|
122
|
+
`${this.indent()}(*${className}).get_own_property("prototype").define_setter(${methodName}, ${lambda});\n`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return code;
|
|
127
|
+
}
|