@ugo-studio/jspp 0.1.4 → 0.1.6

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.
Files changed (71) hide show
  1. package/dist/analysis/scope.js +17 -0
  2. package/dist/analysis/typeAnalyzer.js +7 -1
  3. package/dist/ast/symbols.js +32 -0
  4. package/dist/ast/types.js +0 -6
  5. package/dist/cli-utils/args.js +57 -0
  6. package/dist/cli-utils/colors.js +9 -0
  7. package/dist/cli-utils/file-utils.js +20 -0
  8. package/dist/cli-utils/spinner.js +55 -0
  9. package/dist/cli.js +105 -30
  10. package/dist/core/codegen/class-handlers.js +10 -6
  11. package/dist/core/codegen/control-flow-handlers.js +57 -28
  12. package/dist/core/codegen/declaration-handlers.js +10 -6
  13. package/dist/core/codegen/expression-handlers.js +206 -61
  14. package/dist/core/codegen/function-handlers.js +203 -76
  15. package/dist/core/codegen/helpers.js +125 -28
  16. package/dist/core/codegen/index.js +23 -15
  17. package/dist/core/codegen/literal-handlers.js +15 -6
  18. package/dist/core/codegen/statement-handlers.js +282 -84
  19. package/dist/core/codegen/visitor.js +3 -1
  20. package/package.json +1 -1
  21. package/src/prelude/any_value.hpp +221 -342
  22. package/src/prelude/any_value_access.hpp +168 -81
  23. package/src/prelude/any_value_defines.hpp +74 -35
  24. package/src/prelude/any_value_helpers.hpp +75 -180
  25. package/src/prelude/exception.hpp +1 -0
  26. package/src/prelude/exception_helpers.hpp +4 -4
  27. package/src/prelude/index.hpp +12 -2
  28. package/src/prelude/library/array.hpp +190 -0
  29. package/src/prelude/library/console.hpp +6 -5
  30. package/src/prelude/library/error.hpp +10 -8
  31. package/src/prelude/library/function.hpp +10 -0
  32. package/src/prelude/library/global.hpp +20 -0
  33. package/src/prelude/library/math.hpp +308 -0
  34. package/src/prelude/library/object.hpp +288 -0
  35. package/src/prelude/library/performance.hpp +1 -1
  36. package/src/prelude/library/process.hpp +39 -0
  37. package/src/prelude/library/promise.hpp +57 -55
  38. package/src/prelude/library/symbol.hpp +45 -57
  39. package/src/prelude/library/timer.hpp +6 -6
  40. package/src/prelude/types.hpp +54 -0
  41. package/src/prelude/utils/access.hpp +215 -11
  42. package/src/prelude/utils/assignment_operators.hpp +99 -0
  43. package/src/prelude/utils/log_any_value/array.hpp +8 -8
  44. package/src/prelude/utils/log_any_value/function.hpp +6 -4
  45. package/src/prelude/utils/log_any_value/object.hpp +41 -24
  46. package/src/prelude/utils/log_any_value/primitives.hpp +3 -1
  47. package/src/prelude/utils/operators.hpp +750 -274
  48. package/src/prelude/utils/well_known_symbols.hpp +12 -0
  49. package/src/prelude/values/array.hpp +8 -6
  50. package/src/prelude/values/async_iterator.hpp +79 -0
  51. package/src/prelude/values/descriptors.hpp +2 -2
  52. package/src/prelude/values/function.hpp +72 -62
  53. package/src/prelude/values/helpers/array.hpp +64 -28
  54. package/src/prelude/values/helpers/async_iterator.hpp +275 -0
  55. package/src/prelude/values/helpers/function.hpp +81 -92
  56. package/src/prelude/values/helpers/iterator.hpp +3 -3
  57. package/src/prelude/values/helpers/object.hpp +54 -9
  58. package/src/prelude/values/helpers/promise.hpp +13 -6
  59. package/src/prelude/values/iterator.hpp +1 -1
  60. package/src/prelude/values/object.hpp +10 -3
  61. package/src/prelude/values/promise.hpp +7 -11
  62. package/src/prelude/values/prototypes/array.hpp +851 -12
  63. package/src/prelude/values/prototypes/async_iterator.hpp +50 -0
  64. package/src/prelude/values/prototypes/function.hpp +2 -2
  65. package/src/prelude/values/prototypes/iterator.hpp +5 -5
  66. package/src/prelude/values/prototypes/number.hpp +153 -0
  67. package/src/prelude/values/prototypes/object.hpp +2 -2
  68. package/src/prelude/values/prototypes/promise.hpp +40 -30
  69. package/src/prelude/values/prototypes/string.hpp +28 -28
  70. package/src/prelude/values/prototypes/symbol.hpp +20 -3
  71. package/src/prelude/values/shape.hpp +52 -0
@@ -2,14 +2,19 @@ import ts from "typescript";
2
2
  import { CodeGenerator } from "./";
3
3
  export function visitIdentifier(node) {
4
4
  if (node.text === "NaN") {
5
- return "jspp::AnyValue::make_nan()";
5
+ // return "jspp::AnyValue::make_nan()";
6
+ return "jspp::Constants::NaN";
6
7
  }
7
8
  if (node.text === "undefined") {
8
- return "jspp::AnyValue::make_undefined()";
9
+ return visitUndefinedKeyword();
9
10
  }
10
11
  return node.text;
11
12
  }
12
13
  export function visitNumericLiteral(node) {
14
+ if (node.text === "0")
15
+ return "jspp::Constants::ZERO";
16
+ if (node.text === "1")
17
+ return "jspp::Constants::ONE";
13
18
  return `jspp::AnyValue::make_number(${this.escapeString(node.text)})`;
14
19
  }
15
20
  export function visitStringLiteral(node) {
@@ -19,16 +24,20 @@ export function visitNoSubstitutionTemplateLiteral(node) {
19
24
  return `jspp::AnyValue::make_string("${this.escapeString(node.text)}")`;
20
25
  }
21
26
  export function visitTrueKeyword() {
22
- return "jspp::AnyValue::make_boolean(true)";
27
+ // return "jspp::AnyValue::make_boolean(true)";
28
+ return "jspp::Constants::TRUE";
23
29
  }
24
30
  export function visitFalseKeyword() {
25
- return "jspp::AnyValue::make_boolean(false)";
31
+ // return "jspp::AnyValue::make_boolean(false)";
32
+ return "jspp::Constants::FALSE";
26
33
  }
27
34
  export function visitUndefinedKeyword() {
28
- return "jspp::AnyValue::make_undefined()";
35
+ // return "jspp::AnyValue::make_undefined()";
36
+ return "jspp::Constants::UNDEFINED";
29
37
  }
30
38
  export function visitNullKeyword() {
31
- return "jspp::AnyValue::make_null()";
39
+ // return "jspp::AnyValue::make_null()";
40
+ return "jspp::Constants::Null";
32
41
  }
33
42
  export function visitThisKeyword() {
34
43
  return `${this.globalThisVar}`;
@@ -1,15 +1,17 @@
1
1
  import ts from "typescript";
2
+ import { DeclaredSymbols } from "../../ast/symbols";
2
3
  import { CodeGenerator } from "./";
4
+ import { collectBlockScopedDeclarations, collectFunctionScopedDeclarations, } from "./helpers";
3
5
  export function visitSourceFile(node, context) {
4
6
  const sourceFile = node;
5
7
  let code = "";
6
- const varDecls = sourceFile.statements
7
- .filter(ts.isVariableStatement)
8
- .flatMap((stmt) => stmt.declarationList.declarations);
8
+ // 1. Collect all var declarations (recursively) + top-level let/const
9
+ const varDecls = collectFunctionScopedDeclarations(sourceFile);
10
+ const topLevelLetConst = collectBlockScopedDeclarations(sourceFile.statements);
9
11
  const funcDecls = sourceFile.statements.filter(ts.isFunctionDeclaration);
10
12
  const classDecls = sourceFile.statements.filter(ts.isClassDeclaration);
11
- const hoistedSymbols = new Map();
12
- // 1. Hoist function declarations
13
+ const hoistedSymbols = new DeclaredSymbols();
14
+ // Hoist function declarations
13
15
  funcDecls.forEach((func) => {
14
16
  code += this.hoistDeclaration(func, hoistedSymbols);
15
17
  });
@@ -17,19 +19,27 @@ export function visitSourceFile(node, context) {
17
19
  classDecls.forEach((cls) => {
18
20
  code += this.hoistDeclaration(cls, hoistedSymbols);
19
21
  });
20
- // Hoist variable declarations
22
+ // Hoist variable declarations (var)
21
23
  varDecls.forEach((decl) => {
22
24
  code += this.hoistDeclaration(decl, hoistedSymbols);
23
25
  });
26
+ // Hoist top-level let/const
27
+ topLevelLetConst.forEach((decl) => {
28
+ code += this.hoistDeclaration(decl, hoistedSymbols);
29
+ });
30
+ // Compile symbols for other statements (excluding function)
31
+ const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.localScopeSymbols);
32
+ const localScopeSymbols = new DeclaredSymbols(hoistedSymbols); // hoistedSymbols becomes new local
24
33
  // 2. Assign all hoisted functions first
25
34
  const contextForFunctions = {
26
35
  ...context,
27
- currentScopeSymbols: new Map(context.currentScopeSymbols),
36
+ localScopeSymbols: new DeclaredSymbols(context.localScopeSymbols, hoistedSymbols),
28
37
  };
29
- hoistedSymbols.forEach((v, k) => contextForFunctions.currentScopeSymbols.set(k, v));
30
38
  funcDecls.forEach((stmt) => {
31
39
  const funcName = stmt.name?.getText();
32
40
  if (funcName) {
41
+ this.markSymbolAsChecked(funcName, contextForFunctions.topLevelScopeSymbols, contextForFunctions.localScopeSymbols);
42
+ this.markSymbolAsChecked(funcName, topLevelScopeSymbols, localScopeSymbols);
33
43
  const lambda = this.generateLambda(stmt, contextForFunctions, {
34
44
  isAssignment: true,
35
45
  });
@@ -38,8 +48,6 @@ export function visitSourceFile(node, context) {
38
48
  });
39
49
  // 3. Process other statements
40
50
  sourceFile.statements.forEach((stmt) => {
41
- const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.currentScopeSymbols);
42
- const currentScopeSymbols = hoistedSymbols; // hoistedSymbols becomes new local
43
51
  if (ts.isFunctionDeclaration(stmt)) {
44
52
  // Already handled
45
53
  }
@@ -50,7 +58,7 @@ export function visitSourceFile(node, context) {
50
58
  const contextForVisit = {
51
59
  ...context,
52
60
  topLevelScopeSymbols,
53
- currentScopeSymbols,
61
+ localScopeSymbols,
54
62
  isAssignmentOnly: !isLetOrConst,
55
63
  };
56
64
  const assignments = this.visit(stmt.declarationList, contextForVisit);
@@ -63,24 +71,21 @@ export function visitSourceFile(node, context) {
63
71
  ...context,
64
72
  isFunctionBody: false,
65
73
  topLevelScopeSymbols,
66
- currentScopeSymbols,
67
- // currentScopeSymbols: undefined, // clear the currentScopeSymbols for nested visit
68
- // topLevelScopeSymbols: undefined, // clear the topLevelScopeSymbols for nested visit
74
+ localScopeSymbols,
69
75
  });
70
76
  }
71
77
  });
72
78
  return code;
73
79
  }
74
80
  export function visitBlock(node, context) {
75
- let code = "{\n";
81
+ let code = `${this.indent()}{\n`;
76
82
  this.indentationLevel++;
77
83
  const block = node;
78
- const varDecls = block.statements
79
- .filter(ts.isVariableStatement)
80
- .flatMap((stmt) => stmt.declarationList.declarations);
84
+ // Collect ONLY block-scoped declarations (let/const)
85
+ const blockScopedDecls = collectBlockScopedDeclarations(block.statements);
81
86
  const funcDecls = block.statements.filter(ts.isFunctionDeclaration);
82
87
  const classDecls = block.statements.filter(ts.isClassDeclaration);
83
- const hoistedSymbols = new Map();
88
+ const hoistedSymbols = new DeclaredSymbols();
84
89
  // 1. Hoist all function declarations
85
90
  funcDecls.forEach((func) => {
86
91
  code += this.hoistDeclaration(func, hoistedSymbols);
@@ -89,19 +94,23 @@ export function visitBlock(node, context) {
89
94
  classDecls.forEach((cls) => {
90
95
  code += this.hoistDeclaration(cls, hoistedSymbols);
91
96
  });
92
- // Hoist variable declarations
93
- varDecls.forEach((decl) => {
97
+ // Hoist variable declarations (let/const only)
98
+ blockScopedDecls.forEach((decl) => {
94
99
  code += this.hoistDeclaration(decl, hoistedSymbols);
95
100
  });
101
+ // Compile symbols for other statements (excluding function)
102
+ const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.localScopeSymbols);
103
+ const localScopeSymbols = new DeclaredSymbols(hoistedSymbols); // hoistedSymbols becomes new local
96
104
  // 2. Assign all hoisted functions first
97
105
  const contextForFunctions = {
98
106
  ...context,
99
- currentScopeSymbols: new Map(context.currentScopeSymbols),
107
+ localScopeSymbols: new DeclaredSymbols(context.localScopeSymbols, hoistedSymbols),
100
108
  };
101
- hoistedSymbols.forEach((v, k) => contextForFunctions.currentScopeSymbols.set(k, v));
102
109
  funcDecls.forEach((stmt) => {
103
110
  const funcName = stmt.name?.getText();
104
111
  if (funcName) {
112
+ this.markSymbolAsChecked(funcName, contextForFunctions.topLevelScopeSymbols, contextForFunctions.localScopeSymbols);
113
+ this.markSymbolAsChecked(funcName, topLevelScopeSymbols, localScopeSymbols);
105
114
  const lambda = this.generateLambda(stmt, contextForFunctions, {
106
115
  isAssignment: true,
107
116
  });
@@ -110,8 +119,6 @@ export function visitBlock(node, context) {
110
119
  });
111
120
  // 3. Process other statements
112
121
  block.statements.forEach((stmt) => {
113
- const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.currentScopeSymbols);
114
- const currentScopeSymbols = hoistedSymbols; // hoistedSymbols becomes new local
115
122
  if (ts.isFunctionDeclaration(stmt)) {
116
123
  // Do nothing, already handled
117
124
  }
@@ -122,7 +129,7 @@ export function visitBlock(node, context) {
122
129
  const contextForVisit = {
123
130
  ...context,
124
131
  topLevelScopeSymbols,
125
- currentScopeSymbols,
132
+ localScopeSymbols,
126
133
  isAssignmentOnly: !isLetOrConst,
127
134
  };
128
135
  const assignments = this.visit(stmt.declarationList, contextForVisit);
@@ -135,16 +142,14 @@ export function visitBlock(node, context) {
135
142
  ...context,
136
143
  isFunctionBody: false,
137
144
  topLevelScopeSymbols,
138
- currentScopeSymbols,
139
- // currentScopeSymbols: undefined, // clear the currentScopeSymbols for nested visit
140
- // topLevelScopeSymbols: undefined, // clear the topLevelScopeSymbols for nested visit
145
+ localScopeSymbols,
141
146
  });
142
147
  }
143
148
  });
144
149
  if (context.isFunctionBody) {
145
150
  const lastStatement = block.statements[block.statements.length - 1];
146
151
  if (!lastStatement || !ts.isReturnStatement(lastStatement)) {
147
- code += `${this.indent()}${this.getReturnCommand(context)} jspp::AnyValue::make_undefined();\n`;
152
+ code += `${this.indent()}${this.getReturnCommand(context)} jspp::Constants::UNDEFINED;\n`;
148
153
  }
149
154
  }
150
155
  this.indentationLevel--;
@@ -212,7 +217,7 @@ export function visitIfStatement(node, context) {
212
217
  isFunctionBody: false,
213
218
  });
214
219
  }
215
- return `${this.indent()}if ((${condition}).is_truthy()) ${thenStmt}${elseStmt}`;
220
+ return `${this.indent()}if (is_truthy(${condition})) ${thenStmt}${elseStmt}`;
216
221
  }
217
222
  export function visitExpressionStatement(node, context) {
218
223
  return (this.indent() +
@@ -222,35 +227,206 @@ export function visitExpressionStatement(node, context) {
222
227
  export function visitThrowStatement(node, context) {
223
228
  const throwStmt = node;
224
229
  const expr = this.visit(throwStmt.expression, context);
225
- return `${this.indent()}throw jspp::Exception(${expr});
230
+ return `${this.indent()}throw jspp::Exception(${expr});
226
231
  `;
227
232
  }
228
233
  export function visitTryStatement(node, context) {
229
234
  const tryStmt = node;
235
+ if (context.isInsideAsyncFunction) {
236
+ if (tryStmt.finallyBlock) {
237
+ const declaredSymbols = new Set(context.topLevelScopeSymbols.toSet());
238
+ this.getDeclaredSymbols(tryStmt.tryBlock).forEach((s) => declaredSymbols.add(s));
239
+ if (tryStmt.catchClause) {
240
+ this.getDeclaredSymbols(tryStmt.catchClause).forEach((s) => declaredSymbols.add(s));
241
+ }
242
+ this.getDeclaredSymbols(tryStmt.finallyBlock).forEach((s) => declaredSymbols.add(s));
243
+ const resultVarName = this.generateUniqueName("__try_result_", declaredSymbols);
244
+ const hasReturnedFlagName = this.generateUniqueName("__try_has_returned_", declaredSymbols);
245
+ const catchAllExPtrName = this.generateUniqueName("__catch_all_exptr", declaredSymbols);
246
+ let code = `${this.indent()}{\n`;
247
+ this.indentationLevel++;
248
+ code += `${this.indent()}jspp::AnyValue ${resultVarName};\n`;
249
+ code +=
250
+ `${this.indent()}std::exception_ptr ${catchAllExPtrName} = nullptr;\n`;
251
+ code += `${this.indent()}bool ${hasReturnedFlagName} = false;\n`;
252
+ const returnType = "jspp::JsPromise";
253
+ const returnCmd = "co_return";
254
+ const callPrefix = "co_await ";
255
+ code += `${this.indent()}try {\n`;
256
+ this.indentationLevel++;
257
+ code +=
258
+ `${this.indent()}${resultVarName} = ${callPrefix}([=, &${hasReturnedFlagName}]() -> ${returnType} {\n`;
259
+ this.indentationLevel++;
260
+ const innerContext = {
261
+ ...context,
262
+ isFunctionBody: false,
263
+ isInsideTryCatchLambda: true,
264
+ hasReturnedFlag: hasReturnedFlagName,
265
+ };
266
+ const exPtr = this.generateUniqueName("__ex_ptr");
267
+ code += `${this.indent()}std::exception_ptr ${exPtr} = nullptr;\n`;
268
+ code += `${this.indent()}try {\n`;
269
+ this.indentationLevel++;
270
+ code += this.visit(tryStmt.tryBlock, innerContext);
271
+ this.indentationLevel--;
272
+ code +=
273
+ `${this.indent()}} catch (...) { ${exPtr} = std::current_exception(); }\n`;
274
+ if (tryStmt.catchClause) {
275
+ const exceptionName = this.generateUniqueExceptionName(tryStmt.catchClause.variableDeclaration?.name.getText(), declaredSymbols);
276
+ const caughtValVar = this.generateUniqueName("__caught_val");
277
+ const caughtFlagVar = this.generateUniqueName("__caught_flag");
278
+ code +=
279
+ `${this.indent()}jspp::AnyValue ${caughtValVar} = jspp::Constants::UNDEFINED;\n`;
280
+ code += `${this.indent()}bool ${caughtFlagVar} = false;\n`;
281
+ code += `${this.indent()}if (${exPtr}) {\n`;
282
+ this.indentationLevel++;
283
+ code +=
284
+ `${this.indent()}try { std::rethrow_exception(${exPtr}); } catch (const std::exception& ${exceptionName}) {\n`;
285
+ this.indentationLevel++;
286
+ code +=
287
+ `${this.indent()}${caughtValVar} = jspp::Exception::exception_to_any_value(${exceptionName});\n`;
288
+ code += `${this.indent()}${caughtFlagVar} = true;\n`;
289
+ this.indentationLevel--;
290
+ code += `${this.indent()}} catch (...) {\n`;
291
+ this.indentationLevel++;
292
+ code +=
293
+ `${this.indent()}${caughtValVar} = jspp::AnyValue::make_string("Unknown native exception");\n`;
294
+ code += `${this.indent()}${caughtFlagVar} = true;\n`;
295
+ this.indentationLevel--;
296
+ code += `${this.indent()}}\n`;
297
+ this.indentationLevel--;
298
+ code += `${this.indent()}}\n`;
299
+ code += `${this.indent()}if (${caughtFlagVar}) {\n`;
300
+ this.indentationLevel++;
301
+ code += `${this.indent()}{\n`; // Block scope
302
+ this.indentationLevel++;
303
+ if (tryStmt.catchClause.variableDeclaration) {
304
+ const varName = tryStmt.catchClause.variableDeclaration.name
305
+ .getText();
306
+ code +=
307
+ `${this.indent()}jspp::AnyValue ${varName} = ${caughtValVar};\n`;
308
+ }
309
+ const catchContext = { ...innerContext, exceptionName };
310
+ code += this.visit(tryStmt.catchClause.block, catchContext);
311
+ this.indentationLevel--;
312
+ code += `${this.indent()}}\n`;
313
+ this.indentationLevel--;
314
+ code += `${this.indent()}}\n`;
315
+ }
316
+ else {
317
+ code +=
318
+ `${this.indent()}if (${exPtr}) { std::rethrow_exception(${exPtr}); }\n`;
319
+ }
320
+ code +=
321
+ `${this.indent()}${returnCmd} jspp::Constants::UNDEFINED;\n`;
322
+ this.indentationLevel--;
323
+ code += `${this.indent()}})();\n`;
324
+ this.indentationLevel--;
325
+ code +=
326
+ `${this.indent()}} catch (...) { ${catchAllExPtrName} = std::current_exception(); }\n`;
327
+ code += `${this.indent()}// finally block\n`;
328
+ code += this.visit(tryStmt.finallyBlock, {
329
+ ...context,
330
+ isFunctionBody: false,
331
+ });
332
+ code += `${this.indent()}// re-throw or return\n`;
333
+ code +=
334
+ `${this.indent()}if (${catchAllExPtrName}) { std::rethrow_exception(${catchAllExPtrName}); }\n`;
335
+ code +=
336
+ `${this.indent()}if (${hasReturnedFlagName}) { ${returnCmd} ${resultVarName}; }\n`;
337
+ this.indentationLevel--;
338
+ code += `${this.indent()}}\n`;
339
+ return code;
340
+ }
341
+ else {
342
+ const exceptionName = this.generateUniqueExceptionName(tryStmt.catchClause?.variableDeclaration?.name.getText(), context.topLevelScopeSymbols, context.localScopeSymbols);
343
+ const newContext = {
344
+ ...context,
345
+ isFunctionBody: false,
346
+ exceptionName,
347
+ };
348
+ const exPtr = this.generateUniqueName("__ex_ptr");
349
+ let code = `${this.indent()}{\n`;
350
+ this.indentationLevel++;
351
+ code += `${this.indent()}std::exception_ptr ${exPtr} = nullptr;\n`;
352
+ code += `${this.indent()}try {\n`;
353
+ this.indentationLevel++;
354
+ code += this.visit(tryStmt.tryBlock, newContext);
355
+ this.indentationLevel--;
356
+ code +=
357
+ `${this.indent()}} catch (...) { ${exPtr} = std::current_exception(); }\n`;
358
+ if (tryStmt.catchClause) {
359
+ const caughtValVar = this.generateUniqueName("__caught_val");
360
+ const caughtFlagVar = this.generateUniqueName("__caught_flag");
361
+ code +=
362
+ `${this.indent()}jspp::AnyValue ${caughtValVar} = jspp::Constants::UNDEFINED;\n`;
363
+ code += `${this.indent()}bool ${caughtFlagVar} = false;\n`;
364
+ code += `${this.indent()}if (${exPtr}) {\n`;
365
+ this.indentationLevel++;
366
+ code +=
367
+ `${this.indent()}try { std::rethrow_exception(${exPtr}); } catch (const std::exception& ${exceptionName}) {\n`;
368
+ this.indentationLevel++;
369
+ code +=
370
+ `${this.indent()}${caughtValVar} = jspp::Exception::exception_to_any_value(${exceptionName});\n`;
371
+ code += `${this.indent()}${caughtFlagVar} = true;\n`;
372
+ this.indentationLevel--;
373
+ code += `${this.indent()}} catch (...) {\n`;
374
+ this.indentationLevel++;
375
+ code +=
376
+ `${this.indent()}${caughtValVar} = jspp::AnyValue::make_string("Unknown native exception");\n`;
377
+ code += `${this.indent()}${caughtFlagVar} = true;\n`;
378
+ this.indentationLevel--;
379
+ code += `${this.indent()}}\n`;
380
+ this.indentationLevel--;
381
+ code += `${this.indent()}}\n`;
382
+ code += `${this.indent()}if (${caughtFlagVar}) {\n`;
383
+ this.indentationLevel++;
384
+ code += `${this.indent()}{\n`; // Block scope
385
+ this.indentationLevel++;
386
+ if (tryStmt.catchClause.variableDeclaration) {
387
+ const varName = tryStmt.catchClause.variableDeclaration.name
388
+ .getText();
389
+ code +=
390
+ `${this.indent()}jspp::AnyValue ${varName} = ${caughtValVar};\n`;
391
+ }
392
+ code += this.visit(tryStmt.catchClause.block, newContext);
393
+ this.indentationLevel--;
394
+ code += `${this.indent()}}\n`;
395
+ this.indentationLevel--;
396
+ code += `${this.indent()}}\n`;
397
+ }
398
+ else {
399
+ code +=
400
+ `${this.indent()}if (${exPtr}) { std::rethrow_exception(${exPtr}); }\n`;
401
+ }
402
+ this.indentationLevel--;
403
+ code += `${this.indent()}}\n`;
404
+ return code;
405
+ }
406
+ }
230
407
  if (tryStmt.finallyBlock) {
231
- const declaredSymbols = new Set();
408
+ const declaredSymbols = new Set(context.topLevelScopeSymbols.toSet());
232
409
  this.getDeclaredSymbols(tryStmt.tryBlock).forEach((s) => declaredSymbols.add(s));
233
410
  if (tryStmt.catchClause) {
234
411
  this.getDeclaredSymbols(tryStmt.catchClause).forEach((s) => declaredSymbols.add(s));
235
412
  }
236
413
  this.getDeclaredSymbols(tryStmt.finallyBlock).forEach((s) => declaredSymbols.add(s));
237
- const finallyLambdaName = this.generateUniqueName("__finally_", declaredSymbols);
238
414
  const resultVarName = this.generateUniqueName("__try_result_", declaredSymbols);
239
415
  const hasReturnedFlagName = this.generateUniqueName("__try_has_returned_", declaredSymbols);
416
+ const catchAllExPtrName = this.generateUniqueName("__catch_all_exptr", declaredSymbols);
240
417
  let code = `${this.indent()}{\n`;
241
418
  this.indentationLevel++;
242
419
  code += `${this.indent()}jspp::AnyValue ${resultVarName};\n`;
243
- code += `${this.indent()}bool ${hasReturnedFlagName} = false;\n`;
244
- const finallyBlockCode = this.visit(tryStmt.finallyBlock, {
245
- ...context,
246
- isFunctionBody: false,
247
- });
248
420
  code +=
249
- `${this.indent()}auto ${finallyLambdaName} = [=]() ${finallyBlockCode.trim()};\n`;
421
+ `${this.indent()}std::exception_ptr ${catchAllExPtrName} = nullptr;\n`;
422
+ code += `${this.indent()}bool ${hasReturnedFlagName} = false;\n`;
423
+ const returnType = "jspp::AnyValue";
424
+ const returnCmd = "return";
425
+ const callPrefix = "";
250
426
  code += `${this.indent()}try {\n`;
251
427
  this.indentationLevel++;
252
428
  code +=
253
- `${this.indent()}${resultVarName} = ([=, &${hasReturnedFlagName}]() -> jspp::AnyValue {\n`;
429
+ `${this.indent()}${resultVarName} = ${callPrefix}([=, &${hasReturnedFlagName}]() -> ${returnType} {\n`;
254
430
  this.indentationLevel++;
255
431
  const innerContext = {
256
432
  ...context,
@@ -264,11 +440,14 @@ export function visitTryStatement(node, context) {
264
440
  this.indentationLevel--;
265
441
  code += `${this.indent()}}\n`;
266
442
  if (tryStmt.catchClause) {
267
- const exceptionName = this.generateUniqueExceptionName(tryStmt.catchClause.variableDeclaration?.name.getText());
443
+ const exceptionName = this.generateUniqueExceptionName(tryStmt.catchClause.variableDeclaration?.name.getText(), context.topLevelScopeSymbols, context.localScopeSymbols);
268
444
  const catchContext = { ...innerContext, exceptionName };
269
445
  code +=
270
446
  `${this.indent()}catch (const std::exception& ${exceptionName}) {\n`;
271
447
  this.indentationLevel++;
448
+ // We cannot co_await here. For now, let's just visit the catch block.
449
+ // If the catch block contains await, it will fail to compile.
450
+ // TODO: properly handle async catch by moving it out of native C++ catch.
272
451
  code += this.visit(tryStmt.catchClause.block, catchContext);
273
452
  this.indentationLevel--;
274
453
  code += `${this.indent()}}\n`;
@@ -276,28 +455,28 @@ export function visitTryStatement(node, context) {
276
455
  else {
277
456
  code += `${this.indent()}catch (...) { throw; }\n`;
278
457
  }
279
- code += `${this.indent()}${this.getReturnCommand(context)} jspp::AnyValue::make_undefined();\n`;
458
+ code += `${this.indent()}${returnCmd} jspp::Constants::UNDEFINED;\n`;
280
459
  this.indentationLevel--;
281
460
  code += `${this.indent()}})();\n`;
282
461
  this.indentationLevel--;
283
- code += `${this.indent()}} catch (...) {\n`;
284
- this.indentationLevel++;
285
- code += `${this.indent()}${finallyLambdaName}();\n`;
286
- code += `${this.indent()}throw;\n`;
287
- this.indentationLevel--;
288
- code += `${this.indent()}}\n`;
289
- code += `${this.indent()}${finallyLambdaName}();\n`;
290
- code += `${this.indent()}if (${hasReturnedFlagName}) {\n`;
291
- this.indentationLevel++;
292
- code += `${this.indent()}return ${resultVarName};\n`;
293
- this.indentationLevel--;
294
- code += `${this.indent()}}\n`;
462
+ code +=
463
+ `${this.indent()}} catch (...) { ${catchAllExPtrName} = std::current_exception(); }\n`;
464
+ code += `${this.indent()}// finally block\n`;
465
+ code += this.visit(tryStmt.finallyBlock, {
466
+ ...context,
467
+ isFunctionBody: false,
468
+ });
469
+ code += `${this.indent()}// re-throw or return\n`;
470
+ code +=
471
+ `${this.indent()}if (${catchAllExPtrName}) { std::rethrow_exception(${catchAllExPtrName}); }\n`;
472
+ code +=
473
+ `${this.indent()}if (${hasReturnedFlagName}) { ${returnCmd} ${resultVarName}; }\n`;
295
474
  this.indentationLevel--;
296
475
  code += `${this.indent()}}\n`;
297
476
  return code;
298
477
  }
299
478
  else {
300
- const exceptionName = this.generateUniqueExceptionName(tryStmt.catchClause?.variableDeclaration?.name.getText());
479
+ const exceptionName = this.generateUniqueExceptionName(tryStmt.catchClause?.variableDeclaration?.name.getText(), context.topLevelScopeSymbols, context.localScopeSymbols);
301
480
  const newContext = {
302
481
  ...context,
303
482
  isFunctionBody: false,
@@ -306,7 +485,8 @@ export function visitTryStatement(node, context) {
306
485
  let code = `${this.indent()}try `;
307
486
  code += this.visit(tryStmt.tryBlock, newContext);
308
487
  if (tryStmt.catchClause) {
309
- code += ` catch (const std::exception& ${exceptionName}) `;
488
+ code +=
489
+ `${this.indent()}catch (const std::exception& ${exceptionName}) `;
310
490
  code += this.visit(tryStmt.catchClause, newContext);
311
491
  }
312
492
  return code;
@@ -323,21 +503,12 @@ export function visitCatchClause(node, context) {
323
503
  const varName = catchClause.variableDeclaration.name.getText();
324
504
  let code = `{\n`;
325
505
  this.indentationLevel++;
326
- code += `${this.indent()}{\n`;
327
- this.indentationLevel++;
328
506
  // The JS exception variable is always local to the catch block
329
507
  code +=
330
508
  `${this.indent()}jspp::AnyValue ${varName} = jspp::Exception::exception_to_any_value(${exceptionName});\n`;
331
- // Shadow the C++ exception variable *only if* the names don't clash.
332
- if (varName !== exceptionName) {
333
- code +=
334
- `${this.indent()}auto ${exceptionName} = std::make_shared<jspp::AnyValue>(jspp::AnyValue::make_undefined());\n`;
335
- }
336
509
  code += this.visit(catchClause.block, context);
337
510
  this.indentationLevel--;
338
511
  code += `${this.indent()}}\n`;
339
- this.indentationLevel--;
340
- code += `${this.indent()}}\n`;
341
512
  return code;
342
513
  }
343
514
  else {
@@ -361,7 +532,7 @@ export function visitYieldExpression(node, context) {
361
532
  if (typeInfo &&
362
533
  !typeInfo.isParameter &&
363
534
  !typeInfo.isBuiltin) {
364
- exprText = this.getDerefCode(exprText, this.getJsVarName(expr), typeInfo);
535
+ exprText = this.getDerefCode(exprText, this.getJsVarName(expr), context, typeInfo);
365
536
  }
366
537
  }
367
538
  // Handle `yield*` expression
@@ -369,32 +540,59 @@ export function visitYieldExpression(node, context) {
369
540
  let code = `${this.indent()}{\n`;
370
541
  this.indentationLevel++;
371
542
  const declaredSymbols = this.getDeclaredSymbols(expr);
543
+ context.topLevelScopeSymbols.toSet().forEach((s) => declaredSymbols.add(s));
372
544
  const iterableRef = this.generateUniqueName("__iter_ref", declaredSymbols);
373
545
  const iterator = this.generateUniqueName("__iter", declaredSymbols);
374
546
  const nextFunc = this.generateUniqueName("__next_func", declaredSymbols);
375
547
  const nextRes = this.generateUniqueName("__next_res", declaredSymbols);
376
548
  const varName = this.getJsVarName(expr);
377
549
  code += `${this.indent()}auto ${iterableRef} = ${exprText};\n`;
550
+ if (context.isInsideAsyncFunction) {
551
+ code +=
552
+ `${this.indent()}auto ${iterator} = jspp::Access::get_async_object_value_iterator(${iterableRef}, ${varName});\n`;
553
+ }
554
+ else {
555
+ code +=
556
+ `${this.indent()}auto ${iterator} = jspp::Access::get_object_value_iterator(${iterableRef}, ${varName});\n`;
557
+ }
378
558
  code +=
379
- `${this.indent()}auto ${iterator} = jspp::Access::get_object_value_iterator(${iterableRef}, ${varName});\n`;
380
- code +=
381
- `${this.indent()}auto ${nextFunc} = ${iterator}.get_own_property("next").as_function();\n`;
382
- code +=
383
- `${this.indent()}auto ${nextRes} = ${nextFunc}->call(${iterator}, {});\n`;
559
+ `${this.indent()}auto ${nextFunc} = ${iterator}.get_own_property("next");\n`;
560
+ if (context.isInsideAsyncFunction) {
561
+ code +=
562
+ `${this.indent()}auto ${nextRes} = co_await ${nextFunc}.call(${iterator}, {}, "next");\n`;
563
+ }
564
+ else {
565
+ code +=
566
+ `${this.indent()}auto ${nextRes} = ${nextFunc}.call(${iterator}, {}, "next");\n`;
567
+ }
384
568
  code +=
385
- `${this.indent()}while (!${nextRes}.get_own_property("done").is_truthy()) {\n`;
569
+ `${this.indent()}while (!is_truthy(${nextRes}.get_own_property("done"))) {\n`;
386
570
  this.indentationLevel++;
387
- code +=
388
- `${this.indent()}co_yield ${nextRes}.get_own_property("value");\n`;
389
- code +=
390
- `${this.indent()}${nextRes} = ${nextFunc}->call(${iterator}, {});\n`;
571
+ if (context.isInsideAsyncFunction) {
572
+ code +=
573
+ `${this.indent()}co_yield co_await ${nextRes}.get_own_property("value");\n`;
574
+ }
575
+ else {
576
+ code +=
577
+ `${this.indent()}co_yield ${nextRes}.get_own_property("value");\n`;
578
+ }
579
+ if (context.isInsideAsyncFunction) {
580
+ code +=
581
+ `${this.indent()}${nextRes} = co_await ${nextFunc}.call(${iterator}, {}, "next");\n`;
582
+ }
583
+ else {
584
+ code +=
585
+ `${this.indent()}${nextRes} = ${nextFunc}.call(${iterator}, {}, "next");\n`;
586
+ }
391
587
  this.indentationLevel--;
392
- code += `${this.indent()}}}\n`;
588
+ code += `${this.indent()}}\n`;
393
589
  return code;
394
590
  }
395
- return `${this.indent()}co_yield ${exprText}`;
591
+ const awaitPart = context.isInsideAsyncFunction ? "co_await " : "";
592
+ return `${this.indent()}co_yield ${awaitPart}${exprText}`;
396
593
  }
397
- return `${this.indent()}co_yield jspp::AnyValue::make_undefined()`;
594
+ const awaitPart = context.isInsideAsyncFunction ? "co_await " : "";
595
+ return `${this.indent()}co_yield ${awaitPart}jspp::Constants::UNDEFINED`;
398
596
  }
399
597
  export function visitReturnStatement(node, context) {
400
598
  if (context.isMainContext) {
@@ -418,14 +616,14 @@ export function visitReturnStatement(node, context) {
418
616
  else if (typeInfo &&
419
617
  !typeInfo.isParameter &&
420
618
  !typeInfo.isBuiltin) {
421
- finalExpr = this.getDerefCode(exprText, this.getJsVarName(expr), typeInfo);
619
+ finalExpr = this.getDerefCode(exprText, this.getJsVarName(expr), context, typeInfo);
422
620
  }
423
621
  }
424
622
  returnCode += `${this.indent()}${returnCmd} ${finalExpr};\n`;
425
623
  }
426
624
  else {
427
625
  returnCode +=
428
- `${this.indent()}${returnCmd} jspp::AnyValue::make_undefined();\n`;
626
+ `${this.indent()}${returnCmd} jspp::Constants::UNDEFINED;\n`;
429
627
  }
430
628
  return returnCode;
431
629
  }
@@ -442,10 +640,10 @@ export function visitReturnStatement(node, context) {
442
640
  if (typeInfo &&
443
641
  !typeInfo.isParameter &&
444
642
  !typeInfo.isBuiltin) {
445
- finalExpr = this.getDerefCode(exprText, this.getJsVarName(expr), typeInfo);
643
+ finalExpr = this.getDerefCode(exprText, this.getJsVarName(expr), context, typeInfo);
446
644
  }
447
645
  }
448
646
  return `${this.indent()}${returnCmd} ${finalExpr};\n`;
449
647
  }
450
- return `${this.indent()}${returnCmd} jspp::AnyValue::make_undefined();\n`;
648
+ return `${this.indent()}${returnCmd} jspp::Constants::UNDEFINED;\n`;
451
649
  }
@@ -3,7 +3,7 @@ import { CodeGenerator } from "./";
3
3
  import { visitClassDeclaration } from "./class-handlers";
4
4
  import { visitCaseClause, visitDefaultClause, visitDoStatement, visitForInStatement, visitForOfStatement, visitForStatement, visitSwitchStatement, visitWhileStatement, } from "./control-flow-handlers";
5
5
  import { visitVariableDeclaration, visitVariableDeclarationList, } from "./declaration-handlers";
6
- import { visitArrayLiteralExpression, visitAwaitExpression, visitBinaryExpression, visitCallExpression, visitConditionalExpression, visitElementAccessExpression, visitNewExpression, visitObjectLiteralExpression, visitParenthesizedExpression, visitPostfixUnaryExpression, visitPrefixUnaryExpression, visitPropertyAccessExpression, visitTemplateExpression, visitTypeOfExpression, visitVoidExpression, } from "./expression-handlers";
6
+ import { visitArrayLiteralExpression, visitAwaitExpression, visitBinaryExpression, visitCallExpression, visitConditionalExpression, visitDeleteExpression, visitElementAccessExpression, visitNewExpression, visitObjectLiteralExpression, visitParenthesizedExpression, visitPostfixUnaryExpression, visitPrefixUnaryExpression, visitPropertyAccessExpression, visitTemplateExpression, visitTypeOfExpression, visitVoidExpression, } from "./expression-handlers";
7
7
  import { visitArrowFunction, visitFunctionDeclaration, visitFunctionExpression, } from "./function-handlers";
8
8
  import { visitFalseKeyword, visitIdentifier, visitNoSubstitutionTemplateLiteral, visitNullKeyword, visitNumericLiteral, visitStringLiteral, visitThisKeyword, visitTrueKeyword, visitUndefinedKeyword, } from "./literal-handlers";
9
9
  import { visitBlock, visitBreakStatement, visitCatchClause, visitContinueStatement, visitExpressionStatement, visitIfStatement, visitLabeledStatement, visitReturnStatement, visitSourceFile, visitThrowStatement, visitTryStatement, visitVariableStatement, visitYieldExpression, } from "./statement-handlers";
@@ -72,6 +72,8 @@ export function visit(node, context) {
72
72
  return visitBinaryExpression.call(this, node, context);
73
73
  case ts.SyntaxKind.ConditionalExpression:
74
74
  return visitConditionalExpression.call(this, node, context);
75
+ case ts.SyntaxKind.DeleteExpression:
76
+ return visitDeleteExpression.call(this, node, context);
75
77
  case ts.SyntaxKind.ThrowStatement:
76
78
  return visitThrowStatement.call(this, node, context);
77
79
  case ts.SyntaxKind.TryStatement: