@ugo-studio/jspp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/analysis/scope.js +77 -0
- package/dist/analysis/typeAnalyzer.js +224 -0
- package/dist/ast/types.js +1 -0
- package/dist/cli.js +63 -0
- package/dist/core/codegen/declaration-handlers.js +49 -0
- package/dist/core/codegen/expression-handlers.js +333 -0
- package/dist/core/codegen/function-handlers.js +94 -0
- package/dist/core/codegen/helpers.js +83 -0
- package/dist/core/codegen/index.js +54 -0
- package/dist/core/codegen/literal-handlers.js +32 -0
- package/dist/core/codegen/statement-handlers.js +485 -0
- package/dist/core/codegen/visitor.js +86 -0
- package/dist/core/parser.js +6 -0
- package/dist/core/traverser.js +19 -0
- package/dist/index.js +16 -0
- package/package.json +41 -0
- package/src/prelude/access.hpp +86 -0
- package/src/prelude/any_value.hpp +734 -0
- package/src/prelude/descriptors.hpp +25 -0
- package/src/prelude/error.hpp +31 -0
- package/src/prelude/error_helpers.hpp +59 -0
- package/src/prelude/index.hpp +29 -0
- package/src/prelude/library/console.hpp +111 -0
- package/src/prelude/library/global.hpp +10 -0
- package/src/prelude/library/symbol.hpp +8 -0
- package/src/prelude/log_string.hpp +403 -0
- package/src/prelude/operators.hpp +256 -0
- package/src/prelude/types.hpp +50 -0
- package/src/prelude/values/array.hpp +50 -0
- package/src/prelude/values/function.hpp +19 -0
- package/src/prelude/values/non_values.hpp +20 -0
- package/src/prelude/values/object.hpp +17 -0
- package/src/prelude/values/operators/array.hpp +165 -0
- package/src/prelude/values/operators/function.hpp +34 -0
- package/src/prelude/values/operators/object.hpp +34 -0
- package/src/prelude/values/prototypes/array.hpp +228 -0
- package/src/prelude/values/prototypes/function.hpp +0 -0
- package/src/prelude/values/prototypes/object.hpp +0 -0
- package/src/prelude/values/prototypes/string.hpp +357 -0
- package/src/prelude/well_known_symbols.hpp +10 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { CodeGenerator } from "./";
|
|
3
|
+
export function visitSourceFile(node, context) {
|
|
4
|
+
const sourceFile = node;
|
|
5
|
+
let code = "";
|
|
6
|
+
const varDecls = sourceFile.statements
|
|
7
|
+
.filter(ts.isVariableStatement)
|
|
8
|
+
.flatMap((stmt) => stmt.declarationList.declarations);
|
|
9
|
+
const funcDecls = sourceFile.statements.filter(ts.isFunctionDeclaration);
|
|
10
|
+
const hoistedSymbols = new Set();
|
|
11
|
+
// 1. Hoist function declarations
|
|
12
|
+
funcDecls.forEach((func) => {
|
|
13
|
+
const funcName = func.name?.getText();
|
|
14
|
+
if (funcName && !hoistedSymbols.has(funcName)) {
|
|
15
|
+
hoistedSymbols.add(funcName);
|
|
16
|
+
code +=
|
|
17
|
+
`${this.indent()}auto ${funcName} = std::make_shared<jspp::AnyValue>(jspp::AnyValue::make_undefined());\n`;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
// Hoist variable declarations
|
|
21
|
+
varDecls.forEach((decl) => {
|
|
22
|
+
const name = decl.name.getText();
|
|
23
|
+
if (hoistedSymbols.has(name)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
hoistedSymbols.add(name);
|
|
27
|
+
const isLetOrConst = (decl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
|
|
28
|
+
0;
|
|
29
|
+
const initializer = isLetOrConst
|
|
30
|
+
? "jspp::AnyValue::make_uninitialized()"
|
|
31
|
+
: "jspp::AnyValue::make_undefined()";
|
|
32
|
+
code +=
|
|
33
|
+
`${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initializer});\n`;
|
|
34
|
+
});
|
|
35
|
+
// 2. Assign all hoisted functions first
|
|
36
|
+
funcDecls.forEach((stmt) => {
|
|
37
|
+
const funcName = stmt.name?.getText();
|
|
38
|
+
if (funcName) {
|
|
39
|
+
const lambda = this.generateLambda(stmt, true);
|
|
40
|
+
code += `${this.indent()}*${funcName} = ${lambda};\n`;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// 3. Process other statements
|
|
44
|
+
sourceFile.statements.forEach((stmt) => {
|
|
45
|
+
if (ts.isFunctionDeclaration(stmt)) {
|
|
46
|
+
// Already handled
|
|
47
|
+
}
|
|
48
|
+
else if (ts.isVariableStatement(stmt)) {
|
|
49
|
+
const isLetOrConst = (stmt.declarationList.flags &
|
|
50
|
+
(ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
|
|
51
|
+
0;
|
|
52
|
+
const contextForVisit = {
|
|
53
|
+
...context,
|
|
54
|
+
isAssignmentOnly: !isLetOrConst,
|
|
55
|
+
};
|
|
56
|
+
const assignments = this.visit(stmt.declarationList, contextForVisit);
|
|
57
|
+
if (assignments) {
|
|
58
|
+
code += `${this.indent()}${assignments};\n`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
code += this.visit(stmt, context);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return code;
|
|
66
|
+
}
|
|
67
|
+
export function visitBlock(node, context) {
|
|
68
|
+
let code = "{\n";
|
|
69
|
+
this.indentationLevel++;
|
|
70
|
+
const block = node;
|
|
71
|
+
const varDecls = block.statements
|
|
72
|
+
.filter(ts.isVariableStatement)
|
|
73
|
+
.flatMap((stmt) => stmt.declarationList.declarations);
|
|
74
|
+
const funcDecls = block.statements.filter(ts.isFunctionDeclaration);
|
|
75
|
+
const hoistedSymbols = new Set();
|
|
76
|
+
// 1. Hoist all function declarations
|
|
77
|
+
funcDecls.forEach((func) => {
|
|
78
|
+
const funcName = func.name?.getText();
|
|
79
|
+
if (funcName && !hoistedSymbols.has(funcName)) {
|
|
80
|
+
hoistedSymbols.add(funcName);
|
|
81
|
+
code +=
|
|
82
|
+
`${this.indent()}auto ${funcName} = std::make_shared<jspp::AnyValue>(jspp::AnyValue::make_undefined());\n`;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
// Hoist variable declarations
|
|
86
|
+
varDecls.forEach((decl) => {
|
|
87
|
+
const name = decl.name.getText();
|
|
88
|
+
if (hoistedSymbols.has(name)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
hoistedSymbols.add(name);
|
|
92
|
+
const isLetOrConst = (decl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
|
|
93
|
+
0;
|
|
94
|
+
const initializer = isLetOrConst
|
|
95
|
+
? "jspp::AnyValue::make_uninitialized()"
|
|
96
|
+
: "jspp::AnyValue::make_undefined()";
|
|
97
|
+
code +=
|
|
98
|
+
`${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initializer});\n`;
|
|
99
|
+
});
|
|
100
|
+
// 2. Assign all hoisted functions first
|
|
101
|
+
funcDecls.forEach((stmt) => {
|
|
102
|
+
const funcName = stmt.name?.getText();
|
|
103
|
+
if (funcName) {
|
|
104
|
+
const lambda = this.generateLambda(stmt, true);
|
|
105
|
+
code += `${this.indent()}*${funcName} = ${lambda};\n`;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
// 3. Process other statements
|
|
109
|
+
block.statements.forEach((stmt) => {
|
|
110
|
+
if (ts.isFunctionDeclaration(stmt)) {
|
|
111
|
+
// Do nothing, already handled
|
|
112
|
+
}
|
|
113
|
+
else if (ts.isVariableStatement(stmt)) {
|
|
114
|
+
const isLetOrConst = (stmt.declarationList.flags &
|
|
115
|
+
(ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
|
|
116
|
+
0;
|
|
117
|
+
const contextForVisit = {
|
|
118
|
+
...context,
|
|
119
|
+
isAssignmentOnly: !isLetOrConst,
|
|
120
|
+
};
|
|
121
|
+
const assignments = this.visit(stmt.declarationList, contextForVisit);
|
|
122
|
+
if (assignments) {
|
|
123
|
+
code += `${this.indent()}${assignments};\n`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
code += this.visit(stmt, context);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
if (context.isFunctionBody) {
|
|
131
|
+
const lastStatement = block.statements[block.statements.length - 1];
|
|
132
|
+
if (!lastStatement || !ts.isReturnStatement(lastStatement)) {
|
|
133
|
+
code +=
|
|
134
|
+
`${this.indent()}return jspp::AnyValue::make_undefined();\n`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
this.indentationLevel--;
|
|
138
|
+
code += `${this.indent()}}\n`;
|
|
139
|
+
return code;
|
|
140
|
+
}
|
|
141
|
+
export function visitVariableStatement(node, context) {
|
|
142
|
+
return (this.indent() +
|
|
143
|
+
this.visit(node.declarationList, context) +
|
|
144
|
+
";\n");
|
|
145
|
+
}
|
|
146
|
+
export function visitForStatement(node, context) {
|
|
147
|
+
const forStmt = node;
|
|
148
|
+
let code = "";
|
|
149
|
+
this.indentationLevel++; // Enter a new scope for the for loop
|
|
150
|
+
// Handle initializer
|
|
151
|
+
let initializerCode = "";
|
|
152
|
+
if (forStmt.initializer) {
|
|
153
|
+
if (ts.isVariableDeclarationList(forStmt.initializer)) {
|
|
154
|
+
const varDeclList = forStmt.initializer;
|
|
155
|
+
const isLetOrConst = (varDeclList.flags &
|
|
156
|
+
(ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
|
|
157
|
+
0;
|
|
158
|
+
if (isLetOrConst) {
|
|
159
|
+
// For `let` or `const` in for loop, they are block-scoped to the loop.
|
|
160
|
+
// Declare the variable within the loop's scope.
|
|
161
|
+
// The C++ for loop initializer can contain a declaration.
|
|
162
|
+
const decl = varDeclList.declarations[0]; // Assuming single declaration for simplicity
|
|
163
|
+
if (decl) {
|
|
164
|
+
const name = decl.name.getText();
|
|
165
|
+
const initValue = decl.initializer
|
|
166
|
+
? this.visit(decl.initializer, context)
|
|
167
|
+
: "jspp::AnyValue::make_undefined()";
|
|
168
|
+
initializerCode =
|
|
169
|
+
`auto ${name} = std::make_unique<jspp::AnyValue>(${initValue})`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// For 'var', it's already hoisted, so this is an assignment.
|
|
174
|
+
initializerCode = this.visit(forStmt.initializer, {
|
|
175
|
+
...context,
|
|
176
|
+
isAssignmentOnly: true,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
// If it's an expression (e.g., `i = 0`)
|
|
182
|
+
initializerCode = this.visit(forStmt.initializer, context);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
code += `${this.indent()}for (${initializerCode}; `;
|
|
186
|
+
if (forStmt.condition) {
|
|
187
|
+
code += `(${this.visit(forStmt.condition, context)}).is_truthy()`;
|
|
188
|
+
}
|
|
189
|
+
code += "; ";
|
|
190
|
+
if (forStmt.incrementor) {
|
|
191
|
+
code += this.visit(forStmt.incrementor, context);
|
|
192
|
+
}
|
|
193
|
+
code += ") ";
|
|
194
|
+
code += this.visit(forStmt.statement, {
|
|
195
|
+
...context,
|
|
196
|
+
isFunctionBody: false,
|
|
197
|
+
});
|
|
198
|
+
this.indentationLevel--; // Exit the scope for the for loop
|
|
199
|
+
return code;
|
|
200
|
+
}
|
|
201
|
+
export function visitForInStatement(node, context) {
|
|
202
|
+
const forIn = node;
|
|
203
|
+
let code = `${this.indent()}{\n`;
|
|
204
|
+
this.indentationLevel++; // Enter a new scope for the for-in loop
|
|
205
|
+
let varName = "";
|
|
206
|
+
if (ts.isVariableDeclarationList(forIn.initializer)) {
|
|
207
|
+
const decl = forIn.initializer.declarations[0];
|
|
208
|
+
if (decl) {
|
|
209
|
+
varName = decl.name.getText();
|
|
210
|
+
// Declare the shared_ptr before the loop
|
|
211
|
+
code +=
|
|
212
|
+
`${this.indent()}auto ${varName} = std::make_shared<jspp::AnyValue>(jspp::AnyValue::make_undefined());\n`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else if (ts.isIdentifier(forIn.initializer)) {
|
|
216
|
+
varName = forIn.initializer.getText();
|
|
217
|
+
// Assume it's already declared in an outer scope, just assign to it.
|
|
218
|
+
// No explicit declaration here.
|
|
219
|
+
}
|
|
220
|
+
const expr = forIn.expression;
|
|
221
|
+
const exprText = this.visit(expr, context);
|
|
222
|
+
let derefExpr = exprText;
|
|
223
|
+
if (ts.isIdentifier(expr)) {
|
|
224
|
+
derefExpr = `jspp::Access::deref(${exprText}, ${this.getJsVarName(expr)})`;
|
|
225
|
+
}
|
|
226
|
+
const keysVar = this.generateUniqueName("__keys_", new Set([varName]));
|
|
227
|
+
code +=
|
|
228
|
+
`${this.indent()}std::vector<std::string> ${keysVar} = jspp::Access::get_object_keys(${derefExpr});\n`;
|
|
229
|
+
code += `${this.indent()}for (const auto& ${varName}_str : ${keysVar}) {\n`;
|
|
230
|
+
this.indentationLevel++;
|
|
231
|
+
code +=
|
|
232
|
+
`${this.indent()}*${varName} = jspp::AnyValue::make_string(${varName}_str);\n`;
|
|
233
|
+
code += this.visit(forIn.statement, {
|
|
234
|
+
...context,
|
|
235
|
+
isFunctionBody: false,
|
|
236
|
+
});
|
|
237
|
+
this.indentationLevel--;
|
|
238
|
+
code += `${this.indent()}}}\n`;
|
|
239
|
+
this.indentationLevel--; // Exit the scope for the for-in loop
|
|
240
|
+
return code;
|
|
241
|
+
}
|
|
242
|
+
export function visitForOfStatement(node, context) {
|
|
243
|
+
const forOf = node;
|
|
244
|
+
let code = "";
|
|
245
|
+
this.indentationLevel++; // Enter a new scope for the for-of loop
|
|
246
|
+
let varName = "";
|
|
247
|
+
if (ts.isVariableDeclarationList(forOf.initializer)) {
|
|
248
|
+
const decl = forOf.initializer.declarations[0];
|
|
249
|
+
if (decl) {
|
|
250
|
+
varName = decl.name.getText();
|
|
251
|
+
// Declare the shared_ptr before the loop
|
|
252
|
+
code +=
|
|
253
|
+
`${this.indent()}auto ${varName} = std::make_shared<jspp::AnyValue>(jspp::AnyValue::make_undefined());\n`;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else if (ts.isIdentifier(forOf.initializer)) {
|
|
257
|
+
varName = forOf.initializer.getText();
|
|
258
|
+
// Assume it's already declared in an outer scope, just assign to it.
|
|
259
|
+
// No explicit declaration here.
|
|
260
|
+
}
|
|
261
|
+
const iterableExpr = this.visit(forOf.expression, context);
|
|
262
|
+
const derefIterable = `jspp::Access::deref(${iterableExpr}, ${this.getJsVarName(forOf.expression)})`;
|
|
263
|
+
const arrayPtr = this.generateUniqueName("__array_ptr_", new Set());
|
|
264
|
+
code +=
|
|
265
|
+
`${this.indent()}{ auto ${arrayPtr} = std::any_cast<std::shared_ptr<jspp::JsArray>>(${derefIterable});\n`;
|
|
266
|
+
code +=
|
|
267
|
+
`${this.indent()}for (const auto& ${varName}_val : ${arrayPtr}->items) {\n`;
|
|
268
|
+
this.indentationLevel++;
|
|
269
|
+
code += `${this.indent()}*${varName} = ${varName}_val;\n`;
|
|
270
|
+
code += this.visit(forOf.statement, {
|
|
271
|
+
...context,
|
|
272
|
+
isFunctionBody: false,
|
|
273
|
+
});
|
|
274
|
+
this.indentationLevel--;
|
|
275
|
+
code += `${this.indent()}}}\n`;
|
|
276
|
+
this.indentationLevel--; // Exit the scope for the for-of loop
|
|
277
|
+
return code;
|
|
278
|
+
}
|
|
279
|
+
export function visitIfStatement(node, context) {
|
|
280
|
+
const ifStmt = node;
|
|
281
|
+
const condition = this.visit(ifStmt.expression, context);
|
|
282
|
+
const thenStmt = this.visit(ifStmt.thenStatement, {
|
|
283
|
+
...context,
|
|
284
|
+
isFunctionBody: false,
|
|
285
|
+
});
|
|
286
|
+
let elseStmt = "";
|
|
287
|
+
if (ifStmt.elseStatement) {
|
|
288
|
+
elseStmt = " else " +
|
|
289
|
+
this.visit(ifStmt.elseStatement, {
|
|
290
|
+
...context,
|
|
291
|
+
isFunctionBody: false,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return `${this.indent()}if ((${condition}).is_truthy()) ${thenStmt}${elseStmt}`;
|
|
295
|
+
}
|
|
296
|
+
export function visitExpressionStatement(node, context) {
|
|
297
|
+
return (this.indent() +
|
|
298
|
+
this.visit(node.expression, context) +
|
|
299
|
+
";\n");
|
|
300
|
+
}
|
|
301
|
+
export function visitThrowStatement(node, context) {
|
|
302
|
+
const throwStmt = node;
|
|
303
|
+
const expr = this.visit(throwStmt.expression, context);
|
|
304
|
+
return `${this.indent()}throw jspp::RuntimeError(${expr});
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
export function visitTryStatement(node, context) {
|
|
308
|
+
const tryStmt = node;
|
|
309
|
+
if (tryStmt.finallyBlock) {
|
|
310
|
+
const declaredSymbols = new Set();
|
|
311
|
+
this.getDeclaredSymbols(tryStmt.tryBlock).forEach((s) => declaredSymbols.add(s));
|
|
312
|
+
if (tryStmt.catchClause) {
|
|
313
|
+
this.getDeclaredSymbols(tryStmt.catchClause).forEach((s) => declaredSymbols.add(s));
|
|
314
|
+
}
|
|
315
|
+
this.getDeclaredSymbols(tryStmt.finallyBlock).forEach((s) => declaredSymbols.add(s));
|
|
316
|
+
const finallyLambdaName = this.generateUniqueName("__finally_", declaredSymbols);
|
|
317
|
+
const resultVarName = this.generateUniqueName("__try_result_", declaredSymbols);
|
|
318
|
+
const hasReturnedFlagName = this.generateUniqueName("__try_has_returned_", declaredSymbols);
|
|
319
|
+
let code = `${this.indent()}{\n`;
|
|
320
|
+
this.indentationLevel++;
|
|
321
|
+
code += `${this.indent()}jspp::AnyValue ${resultVarName};\n`;
|
|
322
|
+
code += `${this.indent()}bool ${hasReturnedFlagName} = false;\n`;
|
|
323
|
+
const finallyBlockCode = this.visit(tryStmt.finallyBlock, {
|
|
324
|
+
...context,
|
|
325
|
+
isFunctionBody: false,
|
|
326
|
+
});
|
|
327
|
+
code +=
|
|
328
|
+
`${this.indent()}auto ${finallyLambdaName} = [=]() ${finallyBlockCode.trim()};\n`;
|
|
329
|
+
code += `${this.indent()}try {\n`;
|
|
330
|
+
this.indentationLevel++;
|
|
331
|
+
code +=
|
|
332
|
+
`${this.indent()}${resultVarName} = ([=, &${hasReturnedFlagName}]() -> jspp::AnyValue {\n`;
|
|
333
|
+
this.indentationLevel++;
|
|
334
|
+
const innerContext = {
|
|
335
|
+
...context,
|
|
336
|
+
isFunctionBody: false,
|
|
337
|
+
isInsideTryCatchLambda: true,
|
|
338
|
+
hasReturnedFlag: hasReturnedFlagName,
|
|
339
|
+
};
|
|
340
|
+
code += `${this.indent()}try {\n`;
|
|
341
|
+
this.indentationLevel++;
|
|
342
|
+
code += this.visit(tryStmt.tryBlock, innerContext);
|
|
343
|
+
this.indentationLevel--;
|
|
344
|
+
code += `${this.indent()}}\n`;
|
|
345
|
+
if (tryStmt.catchClause) {
|
|
346
|
+
const exceptionName = this.generateUniqueExceptionName(tryStmt.catchClause.variableDeclaration?.name.getText());
|
|
347
|
+
const catchContext = { ...innerContext, exceptionName };
|
|
348
|
+
code +=
|
|
349
|
+
`${this.indent()}catch (const std::exception& ${exceptionName}) {\n`;
|
|
350
|
+
this.indentationLevel++;
|
|
351
|
+
code += this.visit(tryStmt.catchClause.block, catchContext);
|
|
352
|
+
this.indentationLevel--;
|
|
353
|
+
code += `${this.indent()}}\n`;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
code += `${this.indent()}catch (...) { throw; }\n`;
|
|
357
|
+
}
|
|
358
|
+
code += `${this.indent()}return jspp::AnyValue::make_undefined();\n`;
|
|
359
|
+
this.indentationLevel--;
|
|
360
|
+
code += `${this.indent()}})();\n`;
|
|
361
|
+
this.indentationLevel--;
|
|
362
|
+
code += `${this.indent()}} catch (...) {\n`;
|
|
363
|
+
this.indentationLevel++;
|
|
364
|
+
code += `${this.indent()}${finallyLambdaName}();\n`;
|
|
365
|
+
code += `${this.indent()}throw;\n`;
|
|
366
|
+
this.indentationLevel--;
|
|
367
|
+
code += `${this.indent()}}\n`;
|
|
368
|
+
code += `${this.indent()}${finallyLambdaName}();\n`;
|
|
369
|
+
code += `${this.indent()}if (${hasReturnedFlagName}) {\n`;
|
|
370
|
+
this.indentationLevel++;
|
|
371
|
+
code += `${this.indent()}return ${resultVarName};\n`;
|
|
372
|
+
this.indentationLevel--;
|
|
373
|
+
code += `${this.indent()}}\n`;
|
|
374
|
+
this.indentationLevel--;
|
|
375
|
+
code += `${this.indent()}}\n`;
|
|
376
|
+
return code;
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
const exceptionName = this.generateUniqueExceptionName(tryStmt.catchClause?.variableDeclaration?.name.getText());
|
|
380
|
+
const newContext = {
|
|
381
|
+
...context,
|
|
382
|
+
isFunctionBody: false,
|
|
383
|
+
exceptionName,
|
|
384
|
+
};
|
|
385
|
+
let code = `${this.indent()}try `;
|
|
386
|
+
code += this.visit(tryStmt.tryBlock, newContext);
|
|
387
|
+
if (tryStmt.catchClause) {
|
|
388
|
+
code += ` catch (const std::exception& ${exceptionName}) `;
|
|
389
|
+
code += this.visit(tryStmt.catchClause, newContext);
|
|
390
|
+
}
|
|
391
|
+
return code;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
export function visitCatchClause(node, context) {
|
|
395
|
+
const catchClause = node;
|
|
396
|
+
const exceptionName = context.exceptionName;
|
|
397
|
+
if (!exceptionName) {
|
|
398
|
+
// This should not happen if it's coming from a TryStatement
|
|
399
|
+
throw new Error("Compiler bug: exceptionName not found in context for CatchClause");
|
|
400
|
+
}
|
|
401
|
+
if (catchClause.variableDeclaration) {
|
|
402
|
+
const varName = catchClause.variableDeclaration.name.getText();
|
|
403
|
+
let code = `{\n`;
|
|
404
|
+
this.indentationLevel++;
|
|
405
|
+
code += `${this.indent()}{\n`;
|
|
406
|
+
this.indentationLevel++;
|
|
407
|
+
// Always create the JS exception variable.
|
|
408
|
+
code +=
|
|
409
|
+
`${this.indent()}auto ${varName} = std::make_shared<jspp::AnyValue>(jspp::RuntimeError::error_to_value(${exceptionName}));\n`;
|
|
410
|
+
// Shadow the C++ exception variable *only if* the names don't clash.
|
|
411
|
+
if (varName !== exceptionName) {
|
|
412
|
+
code +=
|
|
413
|
+
`${this.indent()}auto ${exceptionName} = std::make_shared<jspp::AnyValue>(jspp::AnyValue::make_undefined());\n`;
|
|
414
|
+
}
|
|
415
|
+
code += this.visit(catchClause.block, context);
|
|
416
|
+
this.indentationLevel--;
|
|
417
|
+
code += `${this.indent()}}\n`;
|
|
418
|
+
this.indentationLevel--;
|
|
419
|
+
code += `${this.indent()}}\n`;
|
|
420
|
+
return code;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
// No variable in the catch clause, e.g., `catch { ... }`
|
|
424
|
+
let code = `{\n`; // Alway create block scope
|
|
425
|
+
code += this.visit(catchClause.block, context);
|
|
426
|
+
code += `${this.indent()}}\n`;
|
|
427
|
+
return code;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
export function visitReturnStatement(node, context) {
|
|
431
|
+
if (context.isMainContext) {
|
|
432
|
+
return `${this.indent()}jspp::RuntimeError::throw_invalid_return_statement_error();\n`;
|
|
433
|
+
}
|
|
434
|
+
const returnStmt = node;
|
|
435
|
+
if (context.isInsideTryCatchLambda && context.hasReturnedFlag) {
|
|
436
|
+
let returnCode = `${this.indent()}${context.hasReturnedFlag} = true;\n`;
|
|
437
|
+
if (returnStmt.expression) {
|
|
438
|
+
const expr = returnStmt.expression;
|
|
439
|
+
const exprText = this.visit(expr, context);
|
|
440
|
+
if (ts.isIdentifier(expr)) {
|
|
441
|
+
const scope = this.getScopeForNode(expr);
|
|
442
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(expr.text, scope);
|
|
443
|
+
if (!typeInfo) {
|
|
444
|
+
returnCode +=
|
|
445
|
+
`${this.indent()}jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(expr)});\n`; // THROWS, not returns
|
|
446
|
+
}
|
|
447
|
+
if (typeInfo &&
|
|
448
|
+
!typeInfo.isParameter &&
|
|
449
|
+
!typeInfo.isBuiltin) {
|
|
450
|
+
returnCode +=
|
|
451
|
+
`${this.indent()}return jspp::Access::deref(${exprText}, ${this.getJsVarName(expr)});\n`;
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
returnCode += `${this.indent()}return ${exprText};\n`;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
returnCode += `${this.indent()}return ${exprText};\n`;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
returnCode +=
|
|
463
|
+
`${this.indent()}return jspp::AnyValue::make_undefined();\n`;
|
|
464
|
+
}
|
|
465
|
+
return returnCode;
|
|
466
|
+
}
|
|
467
|
+
if (returnStmt.expression) {
|
|
468
|
+
const expr = returnStmt.expression;
|
|
469
|
+
const exprText = this.visit(expr, context);
|
|
470
|
+
if (ts.isIdentifier(expr)) {
|
|
471
|
+
const scope = this.getScopeForNode(expr);
|
|
472
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(expr.text, scope);
|
|
473
|
+
if (!typeInfo) {
|
|
474
|
+
return `${this.indent()}jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(expr)});\n`; // THROWS, not returns
|
|
475
|
+
}
|
|
476
|
+
if (typeInfo &&
|
|
477
|
+
!typeInfo.isParameter &&
|
|
478
|
+
!typeInfo.isBuiltin) {
|
|
479
|
+
return `${this.indent()}return jspp::Access::deref(${exprText}, ${this.getJsVarName(expr)});\n`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return `${this.indent()}return ${exprText};\n`;
|
|
483
|
+
}
|
|
484
|
+
return `${this.indent()}return jspp::AnyValue::make_undefined();\n`;
|
|
485
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { CodeGenerator } from "./";
|
|
3
|
+
import { visitVariableDeclaration, visitVariableDeclarationList, } from "./declaration-handlers";
|
|
4
|
+
import { visitArrayLiteralExpression, visitBinaryExpression, visitCallExpression, visitElementAccessExpression, visitObjectLiteralExpression, visitParenthesizedExpression, visitPostfixUnaryExpression, visitPrefixUnaryExpression, visitPropertyAccessExpression, visitTemplateExpression, visitVoidExpression, } from "./expression-handlers";
|
|
5
|
+
import { visitArrowFunction, visitFunctionDeclaration, visitFunctionExpression, } from "./function-handlers";
|
|
6
|
+
import { visitFalseKeyword, visitIdentifier, visitNoSubstitutionTemplateLiteral, visitNullKeyword, visitNumericLiteral, visitStringLiteral, visitTrueKeyword, visitUndefinedKeyword, } from "./literal-handlers";
|
|
7
|
+
import { visitBlock, visitCatchClause, visitExpressionStatement, visitForInStatement, visitForOfStatement, visitForStatement, visitIfStatement, visitReturnStatement, visitSourceFile, visitThrowStatement, visitTryStatement, visitVariableStatement, } from "./statement-handlers";
|
|
8
|
+
export function visit(node, context) {
|
|
9
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
10
|
+
return visitFunctionDeclaration.call(this, node, context);
|
|
11
|
+
}
|
|
12
|
+
switch (node.kind) {
|
|
13
|
+
case ts.SyntaxKind.ArrowFunction:
|
|
14
|
+
return visitArrowFunction.call(this, node, context);
|
|
15
|
+
case ts.SyntaxKind.FunctionExpression:
|
|
16
|
+
return visitFunctionExpression.call(this, node, context);
|
|
17
|
+
case ts.SyntaxKind.SourceFile:
|
|
18
|
+
return visitSourceFile.call(this, node, context);
|
|
19
|
+
case ts.SyntaxKind.Block:
|
|
20
|
+
return visitBlock.call(this, node, context);
|
|
21
|
+
case ts.SyntaxKind.VariableStatement:
|
|
22
|
+
return visitVariableStatement.call(this, node, context);
|
|
23
|
+
case ts.SyntaxKind.VariableDeclarationList:
|
|
24
|
+
return visitVariableDeclarationList.call(this, node, context);
|
|
25
|
+
case ts.SyntaxKind.VariableDeclaration:
|
|
26
|
+
return visitVariableDeclaration.call(this, node, context);
|
|
27
|
+
case ts.SyntaxKind.ObjectLiteralExpression:
|
|
28
|
+
return visitObjectLiteralExpression.call(this, node, context);
|
|
29
|
+
case ts.SyntaxKind.ArrayLiteralExpression:
|
|
30
|
+
return visitArrayLiteralExpression.call(this, node, context);
|
|
31
|
+
case ts.SyntaxKind.ForStatement:
|
|
32
|
+
return visitForStatement.call(this, node, context);
|
|
33
|
+
case ts.SyntaxKind.ForInStatement:
|
|
34
|
+
return visitForInStatement.call(this, node, context);
|
|
35
|
+
case ts.SyntaxKind.ForOfStatement:
|
|
36
|
+
return visitForOfStatement.call(this, node, context);
|
|
37
|
+
case ts.SyntaxKind.IfStatement:
|
|
38
|
+
return visitIfStatement.call(this, node, context);
|
|
39
|
+
case ts.SyntaxKind.PrefixUnaryExpression:
|
|
40
|
+
return visitPrefixUnaryExpression.call(this, node, context);
|
|
41
|
+
case ts.SyntaxKind.PostfixUnaryExpression:
|
|
42
|
+
return visitPostfixUnaryExpression.call(this, node, context);
|
|
43
|
+
case ts.SyntaxKind.ParenthesizedExpression:
|
|
44
|
+
return visitParenthesizedExpression.call(this, node, context);
|
|
45
|
+
case ts.SyntaxKind.PropertyAccessExpression:
|
|
46
|
+
return visitPropertyAccessExpression.call(this, node, context);
|
|
47
|
+
case ts.SyntaxKind.ElementAccessExpression:
|
|
48
|
+
return visitElementAccessExpression.call(this, node, context);
|
|
49
|
+
case ts.SyntaxKind.ExpressionStatement:
|
|
50
|
+
return visitExpressionStatement.call(this, node, context);
|
|
51
|
+
case ts.SyntaxKind.BinaryExpression:
|
|
52
|
+
return visitBinaryExpression.call(this, node, context);
|
|
53
|
+
case ts.SyntaxKind.ThrowStatement:
|
|
54
|
+
return visitThrowStatement.call(this, node, context);
|
|
55
|
+
case ts.SyntaxKind.TryStatement:
|
|
56
|
+
return visitTryStatement.call(this, node, context);
|
|
57
|
+
case ts.SyntaxKind.CatchClause:
|
|
58
|
+
return visitCatchClause.call(this, node, context);
|
|
59
|
+
case ts.SyntaxKind.CallExpression:
|
|
60
|
+
return visitCallExpression.call(this, node, context);
|
|
61
|
+
case ts.SyntaxKind.ReturnStatement:
|
|
62
|
+
return visitReturnStatement.call(this, node, context);
|
|
63
|
+
case ts.SyntaxKind.Identifier:
|
|
64
|
+
return visitIdentifier.call(this, node);
|
|
65
|
+
case ts.SyntaxKind.NumericLiteral:
|
|
66
|
+
return visitNumericLiteral.call(this, node);
|
|
67
|
+
case ts.SyntaxKind.StringLiteral:
|
|
68
|
+
return visitStringLiteral.call(this, node);
|
|
69
|
+
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
70
|
+
return visitNoSubstitutionTemplateLiteral.call(this, node);
|
|
71
|
+
case ts.SyntaxKind.TemplateExpression:
|
|
72
|
+
return visitTemplateExpression.call(this, node, context);
|
|
73
|
+
case ts.SyntaxKind.TrueKeyword:
|
|
74
|
+
return visitTrueKeyword.call(this);
|
|
75
|
+
case ts.SyntaxKind.FalseKeyword:
|
|
76
|
+
return visitFalseKeyword.call(this);
|
|
77
|
+
case ts.SyntaxKind.VoidExpression:
|
|
78
|
+
return visitVoidExpression.call(this, node, context);
|
|
79
|
+
case ts.SyntaxKind.UndefinedKeyword:
|
|
80
|
+
return visitUndefinedKeyword.call(this);
|
|
81
|
+
case ts.SyntaxKind.NullKeyword:
|
|
82
|
+
return visitNullKeyword.call(this);
|
|
83
|
+
default:
|
|
84
|
+
return `/* Unhandled node: ${ts.SyntaxKind[node.kind]} */`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
export class Traverser {
|
|
3
|
+
traverse(node, visitor) {
|
|
4
|
+
this.traverseNode(node, null, visitor);
|
|
5
|
+
}
|
|
6
|
+
traverseNode(node, parent, visitor) {
|
|
7
|
+
const nodeKind = ts.SyntaxKind[node.kind];
|
|
8
|
+
const visitorActions = visitor[nodeKind];
|
|
9
|
+
if (visitorActions && visitorActions.enter) {
|
|
10
|
+
visitorActions.enter(node, parent);
|
|
11
|
+
}
|
|
12
|
+
ts.forEachChild(node, (childNode) => {
|
|
13
|
+
this.traverseNode(childNode, node, visitor);
|
|
14
|
+
});
|
|
15
|
+
if (visitorActions && visitorActions.exit) {
|
|
16
|
+
visitorActions.exit(node, parent);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { TypeAnalyzer } from "./analysis/typeAnalyzer";
|
|
3
|
+
import { CodeGenerator } from "./core/codegen";
|
|
4
|
+
import { Parser } from "./core/parser";
|
|
5
|
+
export class Interpreter {
|
|
6
|
+
parser = new Parser();
|
|
7
|
+
analyzer = new TypeAnalyzer();
|
|
8
|
+
generator = new CodeGenerator();
|
|
9
|
+
interpret(jsCode) {
|
|
10
|
+
const ast = this.parser.parse(jsCode);
|
|
11
|
+
this.analyzer.analyze(ast);
|
|
12
|
+
const cppCode = this.generator.generate(ast, this.analyzer);
|
|
13
|
+
const preludePath = path.resolve(__dirname, "..", "src", "prelude");
|
|
14
|
+
return { cppCode, preludePath };
|
|
15
|
+
}
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ugo-studio/jspp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A modern, experimental transpiler that converts JavaScript code into high-performance, standard C++23.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "src/index.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"jspp": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src/prelude",
|
|
14
|
+
"prelude-build"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "bun run src/cli.ts",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"precompile": "bun run scripts/precompile-headers.ts",
|
|
20
|
+
"test": "bun run precompile && bun test",
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"prepack": "bun run build",
|
|
23
|
+
"publish-package": "npm publish --access=public"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/bun": "latest"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"typescript": "^5"
|
|
30
|
+
},
|
|
31
|
+
"author": "Ugochukwu David",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"homepage": "https://github.com/ugo-studio/jspp/",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/ugo-studio/jspp.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/ugo-studio/jspp/issues"
|
|
40
|
+
}
|
|
41
|
+
}
|