@ugo-studio/jspp 0.1.3 → 0.1.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 +2 -2
- package/dist/analysis/scope.js +33 -4
- package/dist/analysis/typeAnalyzer.js +260 -21
- package/dist/ast/symbols.js +29 -0
- package/dist/cli-utils/args.js +57 -0
- package/dist/cli-utils/colors.js +9 -0
- package/dist/cli-utils/file-utils.js +20 -0
- package/dist/cli-utils/spinner.js +55 -0
- package/dist/cli.js +105 -31
- package/dist/core/codegen/class-handlers.js +131 -0
- package/dist/core/codegen/control-flow-handlers.js +474 -0
- package/dist/core/codegen/declaration-handlers.js +36 -15
- package/dist/core/codegen/expression-handlers.js +579 -125
- package/dist/core/codegen/function-handlers.js +222 -37
- package/dist/core/codegen/helpers.js +158 -4
- package/dist/core/codegen/index.js +20 -8
- package/dist/core/codegen/literal-handlers.js +18 -6
- package/dist/core/codegen/statement-handlers.js +171 -228
- package/dist/core/codegen/visitor.js +31 -3
- package/package.json +3 -3
- package/src/prelude/any_value.hpp +510 -633
- package/src/prelude/any_value_access.hpp +151 -0
- package/src/prelude/any_value_defines.hpp +190 -0
- package/src/prelude/any_value_helpers.hpp +139 -225
- package/src/prelude/exception.hpp +32 -0
- package/src/prelude/exception_helpers.hpp +49 -0
- package/src/prelude/index.hpp +25 -9
- package/src/prelude/library/array.hpp +190 -0
- package/src/prelude/library/console.hpp +14 -13
- package/src/prelude/library/error.hpp +113 -0
- package/src/prelude/library/function.hpp +10 -0
- package/src/prelude/library/global.hpp +35 -4
- package/src/prelude/library/math.hpp +308 -0
- package/src/prelude/library/object.hpp +288 -0
- package/src/prelude/library/performance.hpp +2 -2
- package/src/prelude/library/process.hpp +39 -0
- package/src/prelude/library/promise.hpp +131 -0
- package/src/prelude/library/symbol.hpp +46 -59
- package/src/prelude/library/timer.hpp +92 -0
- package/src/prelude/scheduler.hpp +145 -0
- package/src/prelude/types.hpp +58 -1
- package/src/prelude/utils/access.hpp +345 -0
- package/src/prelude/utils/assignment_operators.hpp +99 -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 +39 -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 +136 -0
- package/src/prelude/utils/log_any_value/primitives.hpp +43 -0
- package/src/prelude/utils/operators.hpp +751 -0
- package/src/prelude/utils/well_known_symbols.hpp +25 -0
- package/src/prelude/values/array.hpp +10 -7
- package/src/prelude/{descriptors.hpp → values/descriptors.hpp} +2 -2
- package/src/prelude/values/function.hpp +85 -51
- package/src/prelude/values/helpers/array.hpp +80 -35
- package/src/prelude/values/helpers/function.hpp +110 -77
- package/src/prelude/values/helpers/iterator.hpp +16 -10
- package/src/prelude/values/helpers/object.hpp +85 -10
- package/src/prelude/values/helpers/promise.hpp +181 -0
- package/src/prelude/values/helpers/string.hpp +3 -3
- package/src/prelude/values/helpers/symbol.hpp +2 -2
- package/src/prelude/values/iterator.hpp +14 -6
- package/src/prelude/values/object.hpp +14 -3
- package/src/prelude/values/promise.hpp +73 -0
- package/src/prelude/values/prototypes/array.hpp +855 -16
- package/src/prelude/values/prototypes/function.hpp +4 -4
- package/src/prelude/values/prototypes/iterator.hpp +11 -10
- package/src/prelude/values/prototypes/number.hpp +153 -0
- package/src/prelude/values/prototypes/object.hpp +26 -0
- package/src/prelude/values/prototypes/promise.hpp +134 -0
- package/src/prelude/values/prototypes/string.hpp +29 -29
- package/src/prelude/values/prototypes/symbol.hpp +22 -3
- package/src/prelude/values/shape.hpp +52 -0
- package/src/prelude/values/string.hpp +1 -1
- package/src/prelude/values/symbol.hpp +1 -1
- package/src/prelude/access.hpp +0 -91
- package/src/prelude/error.hpp +0 -31
- package/src/prelude/error_helpers.hpp +0 -59
- package/src/prelude/log_string.hpp +0 -407
- package/src/prelude/operators.hpp +0 -256
- package/src/prelude/well_known_symbols.hpp +0 -14
package/README.md
CHANGED
|
@@ -132,15 +132,15 @@ This phase will broaden the range of supported JavaScript syntax and features.
|
|
|
132
132
|
- [x] **Objects:** Literals, property access (dot and bracket notation), methods.
|
|
133
133
|
- [x] **Arrays:** Literals, indexing, and core methods (`.push`, `.pop`, `.length`, etc.).
|
|
134
134
|
- [x] **Operators:** Full suite of arithmetic, logical, and comparison operators.
|
|
135
|
-
- [
|
|
135
|
+
- [x] **Control Flow:** tenary operators, `for` loops, `while` loops, `switch`.
|
|
136
136
|
|
|
137
137
|
### **Phase 3: Interoperability & Standard Library**
|
|
138
138
|
|
|
139
139
|
This phase will focus on building out the standard library and enabling modular code.
|
|
140
140
|
|
|
141
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`.
|
|
142
143
|
- [ ] **Module System:** Support for `import` and `export` to transpile multi-file projects.
|
|
143
|
-
- [ ] **Asynchronous Operations:** A C++ implementation of the JavaScript event loop, `Promise`, and `async/await`.
|
|
144
144
|
|
|
145
145
|
### **Phase 4: Optimization & Advanced Features**
|
|
146
146
|
|
package/dist/analysis/scope.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
1
2
|
export const RESERVED_KEYWORDS = new Set([
|
|
2
3
|
"jspp",
|
|
3
4
|
"std",
|
|
4
5
|
"co_yield",
|
|
5
6
|
"co_return",
|
|
7
|
+
"co_await",
|
|
6
8
|
]);
|
|
7
9
|
export const BUILTIN_OBJECTS = new Set([
|
|
8
10
|
{ name: "undefined", isConst: true },
|
|
@@ -12,13 +14,27 @@ export const BUILTIN_OBJECTS = new Set([
|
|
|
12
14
|
{ name: "performance", isConst: false },
|
|
13
15
|
{ name: "global", isConst: false },
|
|
14
16
|
{ name: "globalThis", isConst: false },
|
|
17
|
+
{ name: "process", isConst: false },
|
|
18
|
+
{ name: "Error", isConst: false },
|
|
19
|
+
{ name: "Promise", isConst: false },
|
|
20
|
+
{ name: "Function", isConst: false },
|
|
21
|
+
{ name: "setTimeout", isConst: false },
|
|
22
|
+
{ name: "clearTimeout", isConst: false },
|
|
23
|
+
{ name: "setInterval", isConst: false },
|
|
24
|
+
{ name: "clearInterval", isConst: false },
|
|
25
|
+
{ name: "Array", isConst: false },
|
|
26
|
+
{ name: "Object", isConst: false },
|
|
27
|
+
{ name: "Math", isConst: false },
|
|
15
28
|
]);
|
|
16
29
|
// Represents a single scope (e.g., a function body or a block statement)
|
|
17
30
|
export class Scope {
|
|
18
31
|
parent;
|
|
32
|
+
ownerFunction;
|
|
19
33
|
symbols = new Map();
|
|
20
|
-
|
|
34
|
+
children = [];
|
|
35
|
+
constructor(parent, ownerFunction) {
|
|
21
36
|
this.parent = parent;
|
|
37
|
+
this.ownerFunction = ownerFunction;
|
|
22
38
|
}
|
|
23
39
|
// Defines a variable in this scope.
|
|
24
40
|
define(name, type) {
|
|
@@ -42,7 +58,7 @@ export class ScopeManager {
|
|
|
42
58
|
allScopes = []; // Array to store all created scopes
|
|
43
59
|
reservedKeywords = new Set(RESERVED_KEYWORDS);
|
|
44
60
|
constructor() {
|
|
45
|
-
const rootScope = new Scope(null); // The global scope
|
|
61
|
+
const rootScope = new Scope(null, null); // The global scope
|
|
46
62
|
this.currentScope = rootScope;
|
|
47
63
|
this.allScopes.push(rootScope); // Add the root scope to our list
|
|
48
64
|
for (const { name, isConst } of BUILTIN_OBJECTS) {
|
|
@@ -54,8 +70,9 @@ export class ScopeManager {
|
|
|
54
70
|
}
|
|
55
71
|
}
|
|
56
72
|
// Enters a new, nested scope.
|
|
57
|
-
enterScope() {
|
|
58
|
-
const newScope = new Scope(this.currentScope);
|
|
73
|
+
enterScope(ownerFunction) {
|
|
74
|
+
const newScope = new Scope(this.currentScope, ownerFunction);
|
|
75
|
+
this.currentScope.children.push(newScope);
|
|
59
76
|
this.currentScope = newScope;
|
|
60
77
|
this.allScopes.push(newScope); // Add every new scope to the list
|
|
61
78
|
}
|
|
@@ -67,11 +84,23 @@ export class ScopeManager {
|
|
|
67
84
|
}
|
|
68
85
|
// Defines a variable in the current scope.
|
|
69
86
|
define(name, type) {
|
|
87
|
+
// if (name === "named" || name === "letVal") console.log("Defining", name, "in scope. isBuiltin:", type.isBuiltin, " type:", type.type);
|
|
70
88
|
if (this.reservedKeywords.has(name) && !type.isBuiltin) {
|
|
71
89
|
throw new Error(`SyntaxError: Unexpected reserved word "${name}"`);
|
|
72
90
|
}
|
|
73
91
|
this.currentScope.define(name, type);
|
|
74
92
|
}
|
|
93
|
+
// Defines a `var` variable (hoisted to function or global scope).
|
|
94
|
+
defineVar(name, type) {
|
|
95
|
+
if (this.reservedKeywords.has(name) && !type.isBuiltin) {
|
|
96
|
+
throw new Error(`SyntaxError: Unexpected reserved word "${name}"`);
|
|
97
|
+
}
|
|
98
|
+
let scope = this.currentScope;
|
|
99
|
+
while (scope.parent && scope.ownerFunction === scope.parent.ownerFunction) {
|
|
100
|
+
scope = scope.parent;
|
|
101
|
+
}
|
|
102
|
+
scope.define(name, type);
|
|
103
|
+
}
|
|
75
104
|
// Looks up a variable's type information from the current scope upwards.
|
|
76
105
|
lookup(name) {
|
|
77
106
|
const scope = this.currentScope.findScopeFor(name);
|
|
@@ -8,13 +8,34 @@ export class TypeAnalyzer {
|
|
|
8
8
|
functionTypeInfo = new Map();
|
|
9
9
|
functionStack = [];
|
|
10
10
|
nodeToScope = new Map();
|
|
11
|
+
labelStack = [];
|
|
12
|
+
loopDepth = 0;
|
|
13
|
+
switchDepth = 0;
|
|
11
14
|
analyze(ast) {
|
|
12
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
|
+
};
|
|
13
33
|
const visitor = {
|
|
14
34
|
// Enter new scope for any block-like structure
|
|
15
35
|
Block: {
|
|
16
36
|
enter: (node, parent) => {
|
|
17
|
-
this.
|
|
37
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
38
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
18
39
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
19
40
|
if (parent && ts.isCatchClause(parent) &&
|
|
20
41
|
parent.variableDeclaration) {
|
|
@@ -35,21 +56,33 @@ export class TypeAnalyzer {
|
|
|
35
56
|
},
|
|
36
57
|
ForStatement: {
|
|
37
58
|
enter: (node) => {
|
|
38
|
-
this.
|
|
59
|
+
this.loopDepth++;
|
|
60
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
61
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
39
62
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
40
63
|
},
|
|
41
|
-
exit: () =>
|
|
64
|
+
exit: () => {
|
|
65
|
+
this.loopDepth--;
|
|
66
|
+
this.scopeManager.exitScope();
|
|
67
|
+
},
|
|
42
68
|
},
|
|
43
69
|
ForOfStatement: {
|
|
44
70
|
enter: (node) => {
|
|
45
|
-
this.
|
|
71
|
+
this.loopDepth++;
|
|
72
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
73
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
46
74
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
47
75
|
},
|
|
48
|
-
exit: () =>
|
|
76
|
+
exit: () => {
|
|
77
|
+
this.loopDepth--;
|
|
78
|
+
this.scopeManager.exitScope();
|
|
79
|
+
},
|
|
49
80
|
},
|
|
50
81
|
ForInStatement: {
|
|
51
82
|
enter: (node) => {
|
|
52
|
-
this.
|
|
83
|
+
this.loopDepth++;
|
|
84
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
85
|
+
this.scopeManager.enterScope(currentFuncNode);
|
|
53
86
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
54
87
|
const forIn = node;
|
|
55
88
|
if (ts.isVariableDeclarationList(forIn.initializer)) {
|
|
@@ -71,7 +104,87 @@ export class TypeAnalyzer {
|
|
|
71
104
|
// The generator will handle assigning to it.
|
|
72
105
|
}
|
|
73
106
|
},
|
|
74
|
-
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
|
+
},
|
|
75
188
|
},
|
|
76
189
|
ArrowFunction: {
|
|
77
190
|
enter: (node) => {
|
|
@@ -82,7 +195,7 @@ export class TypeAnalyzer {
|
|
|
82
195
|
captures: new Map(),
|
|
83
196
|
};
|
|
84
197
|
this.functionTypeInfo.set(node, funcType);
|
|
85
|
-
this.scopeManager.enterScope();
|
|
198
|
+
this.scopeManager.enterScope(node);
|
|
86
199
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
87
200
|
// Define parameters in the new scope
|
|
88
201
|
node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
|
|
@@ -108,9 +221,10 @@ export class TypeAnalyzer {
|
|
|
108
221
|
isClosure: false,
|
|
109
222
|
captures: new Map(),
|
|
110
223
|
declaration: node,
|
|
224
|
+
needsHeapAllocation: true, // Added: Functions are always heap-allocated
|
|
111
225
|
};
|
|
112
226
|
this.functionTypeInfo.set(node, funcType);
|
|
113
|
-
this.scopeManager.enterScope();
|
|
227
|
+
this.scopeManager.enterScope(node);
|
|
114
228
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
115
229
|
// If the function expression is named, define the name within its own scope for recursion.
|
|
116
230
|
if (node.name) {
|
|
@@ -143,11 +257,12 @@ export class TypeAnalyzer {
|
|
|
143
257
|
isClosure: false,
|
|
144
258
|
captures: new Map(),
|
|
145
259
|
declaration: node,
|
|
260
|
+
needsHeapAllocation: true,
|
|
146
261
|
};
|
|
147
262
|
this.scopeManager.define(funcName, funcType);
|
|
148
263
|
this.functionTypeInfo.set(node, funcType);
|
|
149
264
|
}
|
|
150
|
-
this.scopeManager.enterScope();
|
|
265
|
+
this.scopeManager.enterScope(node);
|
|
151
266
|
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
152
267
|
// Define parameters in the new scope
|
|
153
268
|
node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
|
|
@@ -165,23 +280,114 @@ export class TypeAnalyzer {
|
|
|
165
280
|
this.scopeManager.exitScope();
|
|
166
281
|
},
|
|
167
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
|
+
},
|
|
168
361
|
VariableDeclaration: {
|
|
169
362
|
enter: (node) => {
|
|
170
363
|
if (ts.isVariableDeclaration(node)) {
|
|
171
364
|
const name = node.name.getText();
|
|
365
|
+
const isBlockScoped = (node.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
172
366
|
const isConst = (node.parent.flags & ts.NodeFlags.Const) !== 0;
|
|
173
367
|
let type = "auto";
|
|
368
|
+
let needsHeap = false;
|
|
174
369
|
if (node.initializer) {
|
|
175
370
|
if (ts.isArrayLiteralExpression(node.initializer)) {
|
|
176
371
|
type = "array";
|
|
177
372
|
}
|
|
373
|
+
else if (ts.isArrowFunction(node.initializer) ||
|
|
374
|
+
ts.isFunctionExpression(node.initializer)) {
|
|
375
|
+
type = "function";
|
|
376
|
+
needsHeap = true;
|
|
377
|
+
}
|
|
178
378
|
}
|
|
179
379
|
const typeInfo = {
|
|
180
380
|
type,
|
|
181
381
|
declaration: node,
|
|
182
382
|
isConst,
|
|
383
|
+
needsHeapAllocation: needsHeap,
|
|
183
384
|
};
|
|
184
|
-
|
|
385
|
+
if (isBlockScoped) {
|
|
386
|
+
this.scopeManager.define(name, typeInfo);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
this.scopeManager.defineVar(name, typeInfo);
|
|
390
|
+
}
|
|
185
391
|
}
|
|
186
392
|
},
|
|
187
393
|
},
|
|
@@ -190,7 +396,7 @@ export class TypeAnalyzer {
|
|
|
190
396
|
if (ts.isIdentifier(node)) {
|
|
191
397
|
if (isBuiltinObject.call(this, node))
|
|
192
398
|
return;
|
|
193
|
-
const currentFuncNode = this.functionStack[this.functionStack.length - 1];
|
|
399
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
|
|
194
400
|
if (currentFuncNode &&
|
|
195
401
|
(ts.isFunctionDeclaration(currentFuncNode) ||
|
|
196
402
|
ts.isFunctionExpression(currentFuncNode)) &&
|
|
@@ -201,17 +407,19 @@ export class TypeAnalyzer {
|
|
|
201
407
|
// And see if it belongs to an outer scope
|
|
202
408
|
const definingScope = this.scopeManager.currentScope
|
|
203
409
|
.findScopeFor(node.text);
|
|
204
|
-
if (definingScope
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (currentFuncNode) {
|
|
410
|
+
if (definingScope) {
|
|
411
|
+
const definingFunc = definingScope.ownerFunction;
|
|
412
|
+
if (definingFunc !== currentFuncNode) {
|
|
413
|
+
// This is a capture!
|
|
209
414
|
const type = this.scopeManager.lookup(node.text);
|
|
210
415
|
if (type) {
|
|
211
|
-
|
|
212
|
-
if (
|
|
213
|
-
info
|
|
214
|
-
info
|
|
416
|
+
type.needsHeapAllocation = true;
|
|
417
|
+
if (currentFuncNode) {
|
|
418
|
+
const info = this.functionTypeInfo.get(currentFuncNode);
|
|
419
|
+
if (info) {
|
|
420
|
+
info.isClosure = true;
|
|
421
|
+
info.captures?.set(node.text, type);
|
|
422
|
+
}
|
|
215
423
|
}
|
|
216
424
|
}
|
|
217
425
|
}
|
|
@@ -219,6 +427,37 @@ export class TypeAnalyzer {
|
|
|
219
427
|
}
|
|
220
428
|
},
|
|
221
429
|
},
|
|
430
|
+
BinaryExpression: {
|
|
431
|
+
enter: (node) => {
|
|
432
|
+
if (ts.isBinaryExpression(node)) {
|
|
433
|
+
const isAssignment = node.operatorToken.kind >=
|
|
434
|
+
ts.SyntaxKind.FirstAssignment &&
|
|
435
|
+
node.operatorToken.kind <=
|
|
436
|
+
ts.SyntaxKind.LastAssignment;
|
|
437
|
+
if (isAssignment) {
|
|
438
|
+
crossScopeModificationVisitor(node.left);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
PostfixUnaryExpression: {
|
|
444
|
+
enter: (node) => {
|
|
445
|
+
if (ts.isPostfixUnaryExpression(node)) {
|
|
446
|
+
crossScopeModificationVisitor(node.operand);
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
PrefixUnaryExpression: {
|
|
451
|
+
enter: (node) => {
|
|
452
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
453
|
+
const op = node.operator;
|
|
454
|
+
if (op === ts.SyntaxKind.PlusPlusToken ||
|
|
455
|
+
op === ts.SyntaxKind.MinusMinusToken) {
|
|
456
|
+
crossScopeModificationVisitor(node.operand);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
},
|
|
222
461
|
};
|
|
223
462
|
this.traverser.traverse(ast, visitor);
|
|
224
463
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export var DeclaredSymbolType;
|
|
2
|
+
(function (DeclaredSymbolType) {
|
|
3
|
+
DeclaredSymbolType["letOrConst"] = "letOrConst";
|
|
4
|
+
DeclaredSymbolType["function"] = "function";
|
|
5
|
+
DeclaredSymbolType["var"] = "var";
|
|
6
|
+
})(DeclaredSymbolType || (DeclaredSymbolType = {}));
|
|
7
|
+
export class DeclaredSymbols {
|
|
8
|
+
symbols;
|
|
9
|
+
constructor(...m) {
|
|
10
|
+
this.symbols = new Map();
|
|
11
|
+
m.forEach((ds) => ds.symbols.forEach((v, k) => this.symbols.set(k, v)));
|
|
12
|
+
}
|
|
13
|
+
has(name) {
|
|
14
|
+
return this.symbols.has(name);
|
|
15
|
+
}
|
|
16
|
+
get(name) {
|
|
17
|
+
return this.symbols.get(name);
|
|
18
|
+
}
|
|
19
|
+
set(name, value) {
|
|
20
|
+
return this.symbols.set(name, value);
|
|
21
|
+
}
|
|
22
|
+
update(name, update) {
|
|
23
|
+
const oldValue = this.get(name);
|
|
24
|
+
if (oldValue) {
|
|
25
|
+
const newValue = { ...oldValue, ...update };
|
|
26
|
+
return this.symbols.set(name, newValue);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { COLORS } from "./colors";
|
|
3
|
+
export function parseArgs(rawArgs) {
|
|
4
|
+
let jsFilePathArg = null;
|
|
5
|
+
let isRelease = false;
|
|
6
|
+
let keepCpp = false;
|
|
7
|
+
let outputExePath = null;
|
|
8
|
+
let scriptArgs = [];
|
|
9
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
10
|
+
const arg = rawArgs[i];
|
|
11
|
+
if (!arg)
|
|
12
|
+
continue;
|
|
13
|
+
if (arg === "--") {
|
|
14
|
+
scriptArgs = rawArgs.slice(i + 1);
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
if (arg === "--release") {
|
|
18
|
+
isRelease = true;
|
|
19
|
+
}
|
|
20
|
+
else if (arg === "--keep-cpp") {
|
|
21
|
+
keepCpp = true;
|
|
22
|
+
}
|
|
23
|
+
else if (arg === "-o" || arg === "--output") {
|
|
24
|
+
if (i + 1 < rawArgs.length) {
|
|
25
|
+
outputExePath = rawArgs[i + 1] ?? null;
|
|
26
|
+
i++;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.error(`${COLORS.red}Error: --output requires a file path argument.${COLORS.reset}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (arg.startsWith("-")) {
|
|
34
|
+
console.warn(`${COLORS.yellow}Warning: Unknown argument '${arg}'${COLORS.reset}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
if (jsFilePathArg) {
|
|
38
|
+
console.error(`${COLORS.red}Error: Multiple input files specified.${COLORS.reset}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
jsFilePathArg = arg;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!jsFilePathArg) {
|
|
45
|
+
console.log(`${COLORS.bold}Usage:${COLORS.reset} jspp <path-to-js-file> [--release] [--keep-cpp] [-o <output-path>] [-- <args...>]`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
jsFilePath: path.resolve(process.cwd(), jsFilePathArg),
|
|
50
|
+
isRelease,
|
|
51
|
+
keepCpp,
|
|
52
|
+
outputExePath: outputExePath
|
|
53
|
+
? path.resolve(process.cwd(), outputExePath)
|
|
54
|
+
: null,
|
|
55
|
+
scriptArgs,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export async function getLatestMtime(dirPath) {
|
|
4
|
+
let maxMtime = 0;
|
|
5
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
6
|
+
for (const entry of entries) {
|
|
7
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
8
|
+
if (entry.isDirectory()) {
|
|
9
|
+
const nestedMtime = await getLatestMtime(fullPath);
|
|
10
|
+
if (nestedMtime > maxMtime)
|
|
11
|
+
maxMtime = nestedMtime;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
const stats = await fs.stat(fullPath);
|
|
15
|
+
if (stats.mtimeMs > maxMtime)
|
|
16
|
+
maxMtime = stats.mtimeMs;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return maxMtime;
|
|
20
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { COLORS } from "./colors";
|
|
2
|
+
export class Spinner {
|
|
3
|
+
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
4
|
+
interval = null;
|
|
5
|
+
frameIndex = 0;
|
|
6
|
+
text;
|
|
7
|
+
constructor(text) {
|
|
8
|
+
this.text = text;
|
|
9
|
+
}
|
|
10
|
+
start() {
|
|
11
|
+
process.stdout.write("\x1b[?25l"); // Hide cursor
|
|
12
|
+
this.frameIndex = 0;
|
|
13
|
+
this.render();
|
|
14
|
+
this.interval = setInterval(() => {
|
|
15
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
16
|
+
this.render();
|
|
17
|
+
}, 80);
|
|
18
|
+
}
|
|
19
|
+
update(text) {
|
|
20
|
+
this.text = text;
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
stop(symbol = "", color = COLORS.reset) {
|
|
24
|
+
if (this.interval) {
|
|
25
|
+
clearInterval(this.interval);
|
|
26
|
+
this.interval = null;
|
|
27
|
+
}
|
|
28
|
+
this.clearLine();
|
|
29
|
+
process.stdout.write(`${color}${symbol} ${COLORS.reset} ${this.text}\n`);
|
|
30
|
+
process.stdout.write("\x1b[?25h"); // Show cursor
|
|
31
|
+
}
|
|
32
|
+
succeed(text) {
|
|
33
|
+
if (text)
|
|
34
|
+
this.text = text;
|
|
35
|
+
this.stop("✔", COLORS.green);
|
|
36
|
+
}
|
|
37
|
+
fail(text) {
|
|
38
|
+
if (text)
|
|
39
|
+
this.text = text;
|
|
40
|
+
this.stop("✖", COLORS.red);
|
|
41
|
+
}
|
|
42
|
+
info(text) {
|
|
43
|
+
if (text)
|
|
44
|
+
this.text = text;
|
|
45
|
+
this.stop("ℹ", COLORS.cyan);
|
|
46
|
+
}
|
|
47
|
+
render() {
|
|
48
|
+
this.clearLine();
|
|
49
|
+
const frame = this.frames[this.frameIndex];
|
|
50
|
+
process.stdout.write(`${COLORS.cyan}${frame} ${COLORS.reset} ${this.text}`);
|
|
51
|
+
}
|
|
52
|
+
clearLine() {
|
|
53
|
+
process.stdout.write("\r\x1b[K");
|
|
54
|
+
}
|
|
55
|
+
}
|