@ugo-studio/jspp 0.3.1 → 0.3.3

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 (65) hide show
  1. package/dist/cli/args.js +22 -0
  2. package/dist/cli/compiler.js +53 -0
  3. package/dist/cli/index.js +43 -117
  4. package/dist/cli/pch.js +71 -0
  5. package/dist/cli/runner.js +23 -0
  6. package/dist/cli/spinner.js +27 -11
  7. package/dist/cli/transpiler.js +20 -0
  8. package/dist/cli/utils.js +25 -27
  9. package/dist/cli/wasm.js +70 -0
  10. package/dist/index.js +17 -6
  11. package/dist/{analysis → interpreter/analysis}/scope.js +34 -1
  12. package/dist/{analysis → interpreter/analysis}/typeAnalyzer.js +542 -3
  13. package/dist/{core → interpreter/core}/codegen/class-handlers.js +1 -1
  14. package/dist/{core → interpreter/core}/codegen/control-flow-handlers.js +2 -2
  15. package/dist/{core → interpreter/core}/codegen/declaration-handlers.js +21 -9
  16. package/dist/{core → interpreter/core}/codegen/destructuring-handlers.js +3 -3
  17. package/dist/{core → interpreter/core}/codegen/expression-handlers.js +136 -82
  18. package/dist/{core → interpreter/core}/codegen/function-handlers.js +108 -61
  19. package/dist/{core → interpreter/core}/codegen/helpers.js +79 -11
  20. package/dist/interpreter/core/codegen/index.js +156 -0
  21. package/dist/{core → interpreter/core}/codegen/literal-handlers.js +9 -0
  22. package/dist/{core → interpreter/core}/codegen/statement-handlers.js +39 -1
  23. package/package.json +6 -4
  24. package/scripts/precompile-headers.ts +71 -19
  25. package/scripts/setup-emsdk.ts +114 -0
  26. package/src/prelude/any_value.cpp +851 -599
  27. package/src/prelude/any_value.hpp +28 -30
  28. package/src/prelude/jspp.hpp +3 -1
  29. package/src/prelude/library/boolean.cpp +30 -0
  30. package/src/prelude/library/boolean.hpp +14 -0
  31. package/src/prelude/library/error.cpp +2 -2
  32. package/src/prelude/library/global.cpp +2 -0
  33. package/src/prelude/library/math.cpp +46 -43
  34. package/src/prelude/library/math.hpp +2 -0
  35. package/src/prelude/library/object.cpp +1 -1
  36. package/src/prelude/types.hpp +10 -9
  37. package/src/prelude/utils/access.hpp +2 -36
  38. package/src/prelude/utils/assignment_operators.hpp +136 -20
  39. package/src/prelude/utils/log_any_value/log_any_value.hpp +1 -1
  40. package/src/prelude/utils/log_any_value/primitives.hpp +1 -1
  41. package/src/prelude/utils/operators.hpp +123 -88
  42. package/src/prelude/utils/operators_native.hpp +360 -0
  43. package/src/prelude/utils/well_known_symbols.hpp +13 -13
  44. package/src/prelude/values/array.cpp +3 -3
  45. package/src/prelude/values/boolean.cpp +64 -0
  46. package/src/prelude/values/iterator.cpp +262 -210
  47. package/src/prelude/values/number.cpp +137 -92
  48. package/src/prelude/values/object.cpp +163 -122
  49. package/src/prelude/values/prototypes/boolean.hpp +24 -0
  50. package/src/prelude/values/prototypes/number.hpp +8 -1
  51. package/dist/cli/file-utils.js +0 -20
  52. package/dist/cli-utils/args.js +0 -59
  53. package/dist/cli-utils/colors.js +0 -9
  54. package/dist/cli-utils/file-utils.js +0 -20
  55. package/dist/cli-utils/spinner.js +0 -55
  56. package/dist/cli.js +0 -153
  57. package/dist/core/codegen/index.js +0 -88
  58. package/src/prelude/utils/operators_primitive.hpp +0 -337
  59. /package/dist/{ast → interpreter/ast}/symbols.js +0 -0
  60. /package/dist/{ast → interpreter/ast}/types.js +0 -0
  61. /package/dist/{core → interpreter/core}/codegen/visitor.js +0 -0
  62. /package/dist/{core → interpreter/core}/constants.js +0 -0
  63. /package/dist/{core → interpreter/core}/error.js +0 -0
  64. /package/dist/{core → interpreter/core}/parser.js +0 -0
  65. /package/dist/{core → interpreter/core}/traverser.js +0 -0
@@ -5,8 +5,18 @@ import { CodeGenerator } from "./index.js";
5
5
  export function generateLambdaComponents(node, context, options) {
6
6
  const declaredSymbols = this.getDeclaredSymbols(node);
7
7
  const argsName = this.generateUniqueName("__args_", declaredSymbols, context.globalScopeSymbols, context.localScopeSymbols);
8
- const nativeExcessArgsName = this.generateUniqueName("__excess_args", declaredSymbols, context.globalScopeSymbols, context.localScopeSymbols);
9
- const nativeTotalArgsSizeName = this.generateUniqueName("__total_args_size", declaredSymbols, context.globalScopeSymbols, context.localScopeSymbols);
8
+ // const nativeExcessArgsName = this.generateUniqueName(
9
+ // "__excess_args",
10
+ // declaredSymbols,
11
+ // context.globalScopeSymbols,
12
+ // context.localScopeSymbols,
13
+ // );
14
+ // const nativeTotalArgsSizeName = this.generateUniqueName(
15
+ // "__total_args_size",
16
+ // declaredSymbols,
17
+ // context.globalScopeSymbols,
18
+ // context.localScopeSymbols,
19
+ // );
10
20
  const isInsideGeneratorFunction = this.isGeneratorFunction(node);
11
21
  const isInsideAsyncFunction = this.isAsyncFunction(node);
12
22
  const isArrow = ts.isArrowFunction(node);
@@ -14,15 +24,27 @@ export function generateLambdaComponents(node, context, options) {
14
24
  isInsideGeneratorFunction: isInsideGeneratorFunction,
15
25
  isInsideAsyncFunction: isInsideAsyncFunction,
16
26
  });
17
- const funcReturnType = (isInsideGeneratorFunction && isInsideAsyncFunction)
18
- ? "jspp::JsAsyncIterator<jspp::AnyValue>"
19
- : (isInsideGeneratorFunction
20
- ? "jspp::JsIterator<jspp::AnyValue>"
21
- : (isInsideAsyncFunction ? "jspp::JsPromise" : "jspp::AnyValue"));
27
+ const getFuncReturnType = (isInsideNativeLambda) => {
28
+ if (isInsideGeneratorFunction && isInsideAsyncFunction) {
29
+ return "jspp::JsAsyncIterator<jspp::AnyValue>";
30
+ }
31
+ else if (isInsideGeneratorFunction) {
32
+ return "jspp::JsIterator<jspp::AnyValue>";
33
+ }
34
+ else if (isInsideAsyncFunction)
35
+ return "jspp::JsPromise";
36
+ else {
37
+ const inferedReturnType = this.typeAnalyzer.inferFunctionReturnType(node);
38
+ if (isInsideNativeLambda && inferedReturnType === "number") {
39
+ return "double";
40
+ }
41
+ return "jspp::AnyValue";
42
+ }
43
+ };
22
44
  // Lambda arguments: regular functions use std::span for performance.
23
45
  // Generators and async functions use std::vector to ensure they are safely copied into the coroutine frame.
24
46
  const paramThisType = "jspp::AnyValue";
25
- const paramArgsType = (isInsideGeneratorFunction || isInsideAsyncFunction)
47
+ const paramArgsType = isInsideGeneratorFunction || isInsideAsyncFunction
26
48
  ? "std::vector<jspp::AnyValue>"
27
49
  : "std::span<const jspp::AnyValue>";
28
50
  const globalScopeSymbols = this.prepareScopeSymbolsForVisit(context.globalScopeSymbols, context.localScopeSymbols);
@@ -31,7 +53,7 @@ export function generateLambdaComponents(node, context, options) {
31
53
  isMainContext: false,
32
54
  isInsideFunction: true,
33
55
  isFunctionBody: false,
34
- lambdaName: undefined,
56
+ functionName: undefined,
35
57
  globalScopeSymbols,
36
58
  localScopeSymbols: new DeclaredSymbols(),
37
59
  superClassVar: context.superClassVar,
@@ -95,7 +117,8 @@ export function generateLambdaComponents(node, context, options) {
95
117
  `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${signatureName});\n`;
96
118
  }
97
119
  else {
98
- nativeParamsContent += this.generateDestructuring(p.name, signatureName, visitContext) + ";\n";
120
+ nativeParamsContent += this.generateDestructuring(p.name, signatureName, visitContext) +
121
+ ";\n";
99
122
  }
100
123
  }
101
124
  });
@@ -114,8 +137,7 @@ export function generateLambdaComponents(node, context, options) {
114
137
  checks: { initialized: true },
115
138
  });
116
139
  const scope = this.getScopeForNode(p);
117
- const typeInfo = this.typeAnalyzer.scopeManager
118
- .lookupFromScope(name, scope);
140
+ const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
119
141
  // Handle rest parameters
120
142
  if (!!p.dotDotDotToken) {
121
143
  const initValue = `jspp::AnyValue::make_array(${argsName}.subspan(${i}))`;
@@ -148,39 +170,43 @@ export function generateLambdaComponents(node, context, options) {
148
170
  const initValue = `${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue}`;
149
171
  paramsCode +=
150
172
  `${this.indent()}auto ${tempName} = ${initValue};\n`;
151
- paramsCode += this.generateDestructuring(p.name, tempName, visitContext) + ";\n";
173
+ paramsCode +=
174
+ this.generateDestructuring(p.name, tempName, visitContext) +
175
+ ";\n";
152
176
  }
153
177
  });
154
178
  return paramsCode;
155
179
  };
156
180
  // Generate params and function body
157
181
  let paramsContent = "";
158
- let blockContentWithoutOpeningBrace = "";
182
+ let getBlockContentWithoutOpeningBrace = () => "";
159
183
  // Generate `arguments` variable if it used in the function body
160
- if (this.isVariableUsedWithoutDeclaration("arguments", node.body)) {
161
- // Add span parameter for holding the excess arguments from the caller of the native lambda
162
- nativeFuncArgs +=
163
- `, const std::size_t ${nativeTotalArgsSizeName}, std::span<const jspp::AnyValue> ${nativeExcessArgsName} = {}`;
164
- // TODO: compile arguments array from parameters
165
- // this.indentationLevel++;
166
- // nativeParamsContent +=
167
- // `${this.indent()}jspp::AnyValue arguments = jspp::Constants::UNDEFINED;\n`;
168
- // nativeParamsContent += `${this.indent()}{\n`;
169
- // this.indentationLevel++;
170
- // nativeParamsContent +=
171
- // `${this.indent()}std::vector<jspp::AnyValue> ${argsName};\n`;
172
- // this.validateFunctionParams(node.parameters).forEach(
173
- // (p, i) => {
174
- // },
175
- // );
176
- // this.indentationLevel--;
177
- // nativeParamsContent += `${this.indent()}}\n`;
178
- // this.indentationLevel--;
179
- // this.indentationLevel++;
180
- // paramsContent =
181
- // `${this.indent()}jspp::AnyValue arguments = jspp::AnyValue::make_array(${argsName});\n`;
182
- // this.indentationLevel--;
183
- }
184
+ // TODO: compile arguments array from parameters
185
+ // if (
186
+ // this.isVariableUsedWithoutDeclaration("arguments", node.body as ts.Node)
187
+ // ) {
188
+ // // Add span parameter for holding the excess arguments from the caller of the native lambda
189
+ // nativeFuncArgs +=
190
+ // `, const std::size_t ${nativeTotalArgsSizeName}, std::span<const jspp::AnyValue> ${nativeExcessArgsName} = {}`;
191
+ // // this.indentationLevel++;
192
+ // // nativeParamsContent +=
193
+ // // `${this.indent()}jspp::AnyValue arguments = jspp::Constants::UNDEFINED;\n`;
194
+ // // nativeParamsContent += `${this.indent()}{\n`;
195
+ // // this.indentationLevel++;
196
+ // // nativeParamsContent +=
197
+ // // `${this.indent()}std::vector<jspp::AnyValue> ${argsName};\n`;
198
+ // // this.validateFunctionParams(node.parameters).forEach(
199
+ // // (p, i) => {
200
+ // // },
201
+ // // );
202
+ // // this.indentationLevel--;
203
+ // // nativeParamsContent += `${this.indent()}}\n`;
204
+ // // this.indentationLevel--;
205
+ // // this.indentationLevel++;
206
+ // // paramsContent =
207
+ // // `${this.indent()}jspp::AnyValue arguments = jspp::AnyValue::make_array(${argsName});\n`;
208
+ // // this.indentationLevel--;
209
+ // }
184
210
  if (node.body) {
185
211
  if (ts.isBlock(node.body)) {
186
212
  // Hoist var declarations in the function body
@@ -202,34 +228,53 @@ export function generateLambdaComponents(node, context, options) {
202
228
  paramsContent += generateParamsBuilder();
203
229
  this.indentationLevel--;
204
230
  // The block visitor already adds braces, so we need to remove the opening brace to inject the preamble and param extraction.
205
- blockContentWithoutOpeningBrace = this.visit(node.body, {
206
- ...visitContext,
207
- isMainContext: false,
208
- isInsideFunction: true,
209
- isFunctionBody: true,
210
- isInsideGeneratorFunction: isInsideGeneratorFunction,
211
- isInsideAsyncFunction: isInsideAsyncFunction,
212
- }).trimStart().substring(2);
231
+ const properIndentationLevel = this.indentationLevel;
232
+ const nodeBody = node.body;
233
+ getBlockContentWithoutOpeningBrace = (isInsideNativeLambda) => {
234
+ const currentIndentationLevel = this.indentationLevel;
235
+ this.indentationLevel = properIndentationLevel;
236
+ const code = this.visit(nodeBody, {
237
+ ...visitContext,
238
+ isMainContext: false,
239
+ isInsideFunction: true,
240
+ isFunctionBody: true,
241
+ isInsideGeneratorFunction,
242
+ isInsideAsyncFunction,
243
+ isInsideNativeLambda,
244
+ })
245
+ .trim()
246
+ .substring(2);
247
+ this.indentationLevel = currentIndentationLevel;
248
+ return code;
249
+ };
213
250
  }
214
251
  else {
215
252
  this.indentationLevel++;
216
253
  paramsContent += generateParamsBuilder();
217
- blockContentWithoutOpeningBrace =
218
- `${this.indent()}${returnCommand} ${this.visit(node.body, {
254
+ const properIndentationLevel = this.indentationLevel;
255
+ const nodeBody = node.body;
256
+ getBlockContentWithoutOpeningBrace = (isInsideNativeLambda) => {
257
+ const currentIndentationLevel = this.indentationLevel;
258
+ this.indentationLevel = properIndentationLevel;
259
+ let code = `${this.indent()}${returnCommand} ${this.visit(nodeBody, {
219
260
  ...visitContext,
220
261
  isMainContext: false,
221
262
  isInsideFunction: true,
222
263
  isFunctionBody: false,
223
- isInsideGeneratorFunction: isInsideGeneratorFunction,
224
- isInsideAsyncFunction: isInsideAsyncFunction,
264
+ isInsideGeneratorFunction,
265
+ isInsideAsyncFunction,
266
+ isInsideNativeLambda,
225
267
  })};\n`;
268
+ this.indentationLevel--;
269
+ code += `${this.indent()}}`;
270
+ this.indentationLevel = currentIndentationLevel;
271
+ return code;
272
+ };
226
273
  this.indentationLevel--;
227
- blockContentWithoutOpeningBrace += `${this.indent()}}`;
228
274
  }
229
275
  }
230
276
  else {
231
- blockContentWithoutOpeningBrace =
232
- `${returnCommand} jspp::Constants::UNDEFINED; }\n`;
277
+ getBlockContentWithoutOpeningBrace = () => `${returnCommand} jspp::Constants::UNDEFINED; }`;
233
278
  }
234
279
  return {
235
280
  node,
@@ -239,37 +284,39 @@ export function generateLambdaComponents(node, context, options) {
239
284
  thisArgParam,
240
285
  funcArgs,
241
286
  nativeFuncArgs,
242
- funcReturnType,
287
+ getFuncReturnType,
243
288
  preamble,
244
289
  nativePreamble,
245
290
  paramsContent,
246
291
  nativeParamsContent,
247
- blockContentWithoutOpeningBrace,
292
+ getBlockContentWithoutOpeningBrace,
248
293
  isInsideGeneratorFunction,
249
294
  isInsideAsyncFunction,
250
295
  };
251
296
  }
252
297
  export function generateNativeLambda(comps) {
253
- const { options, thisArgParam, nativeFuncArgs, funcReturnType, nativePreamble, nativeParamsContent, blockContentWithoutOpeningBrace, } = comps;
298
+ const { options, thisArgParam, nativeFuncArgs, getFuncReturnType, nativePreamble, nativeParamsContent, getBlockContentWithoutOpeningBrace, } = comps;
254
299
  const capture = options?.capture || "[=]";
255
300
  const nativeName = options?.nativeName;
256
301
  const selfParam = nativeName ? `this auto&& ${nativeName}, ` : "";
257
302
  const mutableLabel = nativeName ? "" : "mutable ";
303
+ const funcReturnType = getFuncReturnType(true);
258
304
  let lambda = `${capture}(${selfParam}${thisArgParam}${nativeFuncArgs}) ${mutableLabel}-> ${funcReturnType} {\n`;
259
305
  lambda += nativePreamble;
260
306
  lambda += nativeParamsContent;
261
- lambda += blockContentWithoutOpeningBrace;
307
+ lambda += getBlockContentWithoutOpeningBrace(true);
262
308
  return lambda;
263
309
  }
264
310
  export function generateWrappedLambda(comps) {
265
- const { node, context, options, thisArgParam, funcArgs, funcReturnType, preamble, paramsContent, blockContentWithoutOpeningBrace, isInsideGeneratorFunction, isInsideAsyncFunction, } = comps;
311
+ const { node, context, options, thisArgParam, funcArgs, getFuncReturnType, preamble, paramsContent, getBlockContentWithoutOpeningBrace, isInsideGeneratorFunction, isInsideAsyncFunction, } = comps;
266
312
  const capture = options?.capture || "[=]";
267
313
  const isAssignment = options?.isAssignment || false;
314
+ const funcReturnType = getFuncReturnType(false);
268
315
  const noTypeSignature = options?.noTypeSignature || false;
269
316
  let lambda = `${capture}(${thisArgParam}, ${funcArgs}) mutable -> ${funcReturnType} {\n`;
270
317
  lambda += preamble;
271
318
  lambda += paramsContent;
272
- lambda += blockContentWithoutOpeningBrace;
319
+ lambda += getBlockContentWithoutOpeningBrace(false);
273
320
  let callable = lambda;
274
321
  let method = "";
275
322
  // Handle generator function
@@ -308,7 +355,7 @@ export function generateWrappedLambda(comps) {
308
355
  method = `jspp::AnyValue::make_function`;
309
356
  }
310
357
  }
311
- const funcName = context?.lambdaName || node.name?.getText();
358
+ const funcName = context?.functionName || node.name?.getText();
312
359
  const hasName = !!funcName && funcName.length > 0;
313
360
  let args = callable;
314
361
  const isArrow = ts.isArrowFunction(node);
@@ -378,7 +425,7 @@ export function visitFunctionExpression(node, context) {
378
425
  `${this.indent()}auto ${funcName} = std::make_shared<jspp::AnyValue>();\n`;
379
426
  const lambda = this.generateWrappedLambda(this.generateLambdaComponents(funcExpr, {
380
427
  ...context,
381
- lambdaName: funcName,
428
+ functionName: funcName,
382
429
  }, {
383
430
  isAssignment: true,
384
431
  capture: "[=]",
@@ -1,6 +1,6 @@
1
1
  import ts from "typescript";
2
2
  import { BUILTIN_OBJECTS, Scope } from "../../analysis/scope.js";
3
- import { DeclarationType, DeclaredSymbols } from "../../ast/symbols.js";
3
+ import { DeclarationType, DeclaredSymbols, } from "../../ast/symbols.js";
4
4
  import { CompilerError } from "../error.js";
5
5
  import { CodeGenerator } from "./index.js";
6
6
  /**
@@ -250,7 +250,7 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
250
250
  : ts.isEnumDeclaration(decl)
251
251
  ? DeclarationType.enum
252
252
  : DeclarationType.var;
253
- const hoistName = (nameNode) => {
253
+ const hoistName = (nameNode, isFromDestructuring = false) => {
254
254
  if (ts.isIdentifier(nameNode)) {
255
255
  const name = nameNode.text;
256
256
  if (hoistedSymbols.has(name)) {
@@ -269,6 +269,10 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
269
269
  }
270
270
  else {
271
271
  // Add the symbol to the hoisted symbols
272
+ hoistedSymbols.add(name, {
273
+ type: declType,
274
+ });
275
+ // Get and update symbol features
272
276
  if (ts.isFunctionDeclaration(decl) ||
273
277
  (ts.isVariableDeclaration(decl) && decl.initializer &&
274
278
  nameNode === decl.name &&
@@ -278,22 +282,23 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
278
282
  const funcExpr = ts.isVariableDeclaration(decl)
279
283
  ? decl.initializer
280
284
  : decl;
281
- const isAsync = this.isAsyncFunction(funcExpr);
282
- const isGenerator = this.isGeneratorFunction(funcExpr);
283
- hoistedSymbols.add(name, {
284
- type: declType,
285
- features: { isAsync, isGenerator },
285
+ // Update features
286
+ hoistedSymbols.update(name, {
287
+ features: {
288
+ isAsync: this.isAsyncFunction(funcExpr),
289
+ isGenerator: this.isGeneratorFunction(funcExpr),
290
+ },
286
291
  });
287
292
  // Don't hoist declarations not used as a variable
288
293
  // They will be called with their native lambda/value
289
294
  if (!this.isDeclarationUsedAsValue(decl, scopeNode) &&
290
295
  !this.isDeclarationUsedBeforeInitialization(name, scopeNode)) {
296
+ hoistedSymbols.update(name, {
297
+ checks: { skippedHoisting: true },
298
+ });
291
299
  return "";
292
300
  }
293
301
  }
294
- else {
295
- hoistedSymbols.add(name, { type: declType });
296
- }
297
302
  }
298
303
  const scope = this.getScopeForNode(decl);
299
304
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
@@ -304,6 +309,13 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
304
309
  return `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initializer});\n`;
305
310
  }
306
311
  else {
312
+ if ((isLet || isConst) &&
313
+ !this.isDeclarationUsedBeforeInitialization(name, scopeNode) && !isFromDestructuring) {
314
+ hoistedSymbols.update(name, {
315
+ checks: { skippedHoisting: true },
316
+ });
317
+ return "";
318
+ }
307
319
  return `${this.indent()}jspp::AnyValue ${name} = ${initializer};\n`;
308
320
  }
309
321
  }
@@ -311,7 +323,7 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
311
323
  let code = "";
312
324
  nameNode.elements.forEach((element) => {
313
325
  if (ts.isBindingElement(element)) {
314
- code += hoistName(element.name);
326
+ code += hoistName(element.name, true);
315
327
  }
316
328
  });
317
329
  return code;
@@ -715,6 +727,33 @@ export function isVariableUsedWithoutDeclaration(name, root) {
715
727
  visit(root);
716
728
  return isUsedIllegally;
717
729
  }
730
+ /**
731
+ * Checks if a node (usually a SourceFile) contains any top-level await expressions.
732
+ * This determines if the module function should be a coroutine.
733
+ */
734
+ export function needsTopLevelAwait(node) {
735
+ let found = false;
736
+ const visitor = (n) => {
737
+ if (found)
738
+ return;
739
+ if (ts.isAwaitExpression(n)) {
740
+ found = true;
741
+ return;
742
+ }
743
+ if (ts.isForOfStatement(n) && n.awaitModifier) {
744
+ found = true;
745
+ return;
746
+ }
747
+ // Nested functions/classes have their own context
748
+ if (ts.isFunctionLike(n) || ts.isClassLike(n) ||
749
+ ts.isModuleDeclaration(n)) {
750
+ return;
751
+ }
752
+ ts.forEachChild(n, visitor);
753
+ };
754
+ ts.forEachChild(node, visitor);
755
+ return found;
756
+ }
718
757
  /**
719
758
  * Validates and filters function parameters, checking for illegal "this"
720
759
  * and correctly positioned rest parameters.
@@ -744,3 +783,32 @@ export function validateFunctionParams(parameters) {
744
783
  return p;
745
784
  }) || [];
746
785
  }
786
+ /**
787
+ * Traverses the AST upwards from the given node to find the nearest enclosing
788
+ * `FunctionDeclaration` node that contains a `ReturnStatement` ancestor.
789
+ *
790
+ * This function is useful for determining the function context in which a return
791
+ * statement appears. It starts from the provided node and walks up the parent
792
+ * chain, tracking whether a return statement has been encountered. Once a
793
+ * function declaration is found after encountering a return statement, it is returned.
794
+ *
795
+ * @param node - The starting TypeScript AST node.
796
+ * @returns The enclosing `ts.FunctionDeclaration` node if found, otherwise `undefined`.
797
+ */
798
+ export function findEnclosingFunctionDeclarationFromReturnStatement(node) {
799
+ let current = node;
800
+ let foundReturn = false;
801
+ while (current) {
802
+ if (ts.isFunctionDeclaration(current)) {
803
+ if (foundReturn) {
804
+ return current;
805
+ }
806
+ break;
807
+ }
808
+ if (ts.isReturnStatement(current)) {
809
+ foundReturn = true;
810
+ }
811
+ current = current.parent;
812
+ }
813
+ return undefined;
814
+ }
@@ -0,0 +1,156 @@
1
+ import ts from "typescript";
2
+ import { DeclaredSymbols } from "../../ast/symbols.js";
3
+ import { generateDestructuring } from "./destructuring-handlers.js";
4
+ import { generateLambdaComponents, generateNativeLambda, generateWrappedLambda, } from "./function-handlers.js";
5
+ import { escapeString, findEnclosingFunctionDeclarationFromReturnStatement, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isDeclarationCalledAsFunction, isDeclarationUsedAsValue, isDeclarationUsedBeforeInitialization, isGeneratorFunction, isVariableUsedWithoutDeclaration, markSymbolAsInitialized, needsTopLevelAwait, prepareScopeSymbolsForVisit, validateFunctionParams, } from "./helpers.js";
6
+ import { visit } from "./visitor.js";
7
+ export class CodeGenerator {
8
+ indentationLevel = 0;
9
+ typeAnalyzer;
10
+ isTypescript = false;
11
+ moduleFunctionName;
12
+ globalThisVar;
13
+ uniqueNameCounter = 0;
14
+ isWasm = false;
15
+ wasmExports = [];
16
+ // visitor
17
+ visit = visit;
18
+ // helpers
19
+ getDeclaredSymbols = getDeclaredSymbols;
20
+ generateUniqueName = generateUniqueName;
21
+ generateUniqueExceptionName = generateUniqueExceptionName;
22
+ hoistDeclaration = hoistDeclaration;
23
+ getScopeForNode = getScopeForNode;
24
+ indent = indent;
25
+ escapeString = escapeString;
26
+ getJsVarName = getJsVarName;
27
+ getDerefCode = getDerefCode;
28
+ getReturnCommand = getReturnCommand;
29
+ isBuiltinObject = isBuiltinObject;
30
+ isGeneratorFunction = isGeneratorFunction;
31
+ isAsyncFunction = isAsyncFunction;
32
+ prepareScopeSymbolsForVisit = prepareScopeSymbolsForVisit;
33
+ markSymbolAsInitialized = markSymbolAsInitialized;
34
+ isDeclarationCalledAsFunction = isDeclarationCalledAsFunction;
35
+ isDeclarationUsedAsValue = isDeclarationUsedAsValue;
36
+ isDeclarationUsedBeforeInitialization = isDeclarationUsedBeforeInitialization;
37
+ isVariableUsedWithoutDeclaration = isVariableUsedWithoutDeclaration;
38
+ validateFunctionParams = validateFunctionParams;
39
+ generateDestructuring = generateDestructuring;
40
+ findEnclosingFunctionDeclarationFromReturnStatement = findEnclosingFunctionDeclarationFromReturnStatement;
41
+ // function handlers
42
+ generateLambdaComponents = generateLambdaComponents;
43
+ generateNativeLambda = generateNativeLambda;
44
+ generateWrappedLambda = generateWrappedLambda;
45
+ /**
46
+ * Main entry point for the code generation process.
47
+ */
48
+ generate(ast, analyzer, isTypescript, isWasm = false) {
49
+ this.typeAnalyzer = analyzer;
50
+ this.isTypescript = isTypescript;
51
+ this.isWasm = isWasm;
52
+ this.wasmExports = [];
53
+ this.moduleFunctionName = this.generateUniqueName("__module_entry_point_", this.getDeclaredSymbols(ast));
54
+ this.globalThisVar = this.generateUniqueName("__this_val__", this.getDeclaredSymbols(ast));
55
+ const isAsyncModule = needsTopLevelAwait(ast);
56
+ const moduleReturnType = isAsyncModule
57
+ ? "jspp::JsPromise"
58
+ : "jspp::AnyValue";
59
+ const moduleReturnCommand = isAsyncModule ? "co_return" : "return";
60
+ let declarations = `#include "jspp.hpp"\n#include "library/global_usings.hpp"\n`;
61
+ if (isWasm) {
62
+ declarations += `#include <emscripten.h>\n`;
63
+ }
64
+ declarations += `\n`;
65
+ // module function code
66
+ let moduleCode = `${moduleReturnType} ${this.moduleFunctionName}() {\n`;
67
+ this.indentationLevel++;
68
+ moduleCode +=
69
+ `${this.indent()}jspp::AnyValue ${this.globalThisVar} = global;\n`;
70
+ const context = {
71
+ currentScopeNode: ast,
72
+ isMainContext: true,
73
+ isInsideFunction: true,
74
+ isFunctionBody: true,
75
+ isInsideAsyncFunction: isAsyncModule,
76
+ globalScopeSymbols: new DeclaredSymbols(),
77
+ localScopeSymbols: new DeclaredSymbols(),
78
+ };
79
+ const generatedBody = this.visit(ast, context);
80
+ moduleCode += generatedBody;
81
+ moduleCode +=
82
+ `${this.indent()}${moduleReturnCommand} jspp::Constants::UNDEFINED;\n`;
83
+ this.indentationLevel--;
84
+ moduleCode += "}\n\n";
85
+ // Wasm Exports
86
+ let wasmGlobalPointers = "";
87
+ let wasmWrappers = "";
88
+ if (isWasm) {
89
+ for (const exp of this.wasmExports) {
90
+ const paramTypes = exp.params.map((_, i) => `jspp::AnyValue`);
91
+ const paramList = paramTypes.length > 0
92
+ ? `, ${paramTypes.join(", ")}`
93
+ : "";
94
+ const pointerName = `__wasm_export_ptr_${exp.jsName}`;
95
+ wasmGlobalPointers +=
96
+ `std::function<jspp::AnyValue(jspp::AnyValue${paramList})> ${pointerName} = nullptr;\n`;
97
+ const wrapperParamList = exp.params.map((_, i) => `double p${i}`).join(", ");
98
+ const callArgsList = exp.params.map((_, i) => `jspp::AnyValue::make_number(p${i})`);
99
+ const callArgs = callArgsList.length > 0
100
+ ? `, ${callArgsList.join(", ")}`
101
+ : "";
102
+ wasmWrappers += `extern "C" EMSCRIPTEN_KEEPALIVE\n`;
103
+ wasmWrappers +=
104
+ `double wasm_export_${exp.jsName}(${wrapperParamList}) {\n`;
105
+ wasmWrappers += ` if (!${pointerName}) return 0;\n`;
106
+ wasmWrappers +=
107
+ ` auto res = ${pointerName}(global${callArgs});\n`;
108
+ wasmWrappers +=
109
+ ` return jspp::Operators_Private::ToNumber(res);\n`;
110
+ wasmWrappers += `}\n\n`;
111
+ }
112
+ }
113
+ // main function code
114
+ let mainCode = "int main(int argc, char** argv) {\n";
115
+ this.indentationLevel++;
116
+ mainCode += `${this.indent()}try {\n`;
117
+ this.indentationLevel++;
118
+ mainCode += `${this.indent()}jspp::initialize_runtime();\n`;
119
+ mainCode += `${this.indent()}jspp::setup_process_argv(argc, argv);\n`;
120
+ if (isAsyncModule) {
121
+ mainCode +=
122
+ `${this.indent()}auto p = ${this.moduleFunctionName}();\n`;
123
+ mainCode +=
124
+ `${this.indent()}p.then(nullptr, [](jspp::AnyValue err) {\n`;
125
+ this.indentationLevel++;
126
+ mainCode +=
127
+ `${this.indent()}auto error = std::make_shared<jspp::AnyValue>(err);\n`;
128
+ this.indentationLevel++;
129
+ mainCode +=
130
+ `${this.indent()}console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
131
+ mainCode += `${this.indent()}std::exit(1);\n`;
132
+ this.indentationLevel--;
133
+ mainCode += `${this.indent()}});\n`;
134
+ }
135
+ else {
136
+ mainCode += `${this.indent()}${this.moduleFunctionName}();\n`;
137
+ }
138
+ mainCode += `${this.indent()}jspp::Scheduler::instance().run();\n`;
139
+ this.indentationLevel--;
140
+ mainCode += `${this.indent()}} catch (const std::exception& ex) {\n`;
141
+ this.indentationLevel++;
142
+ mainCode +=
143
+ `${this.indent()}auto error = std::make_shared<jspp::AnyValue>(jspp::Exception::exception_to_any_value(ex));\n`;
144
+ this.indentationLevel++;
145
+ mainCode +=
146
+ `${this.indent()}console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
147
+ mainCode += `${this.indent()}return 1;\n`;
148
+ this.indentationLevel--;
149
+ mainCode += `${this.indent()}}\n`;
150
+ mainCode += `${this.indent()}return 0;\n`;
151
+ this.indentationLevel--;
152
+ mainCode += `}`;
153
+ return declarations + wasmGlobalPointers + wasmWrappers + moduleCode +
154
+ mainCode;
155
+ }
156
+ }
@@ -11,6 +11,15 @@ export function visitIdentifier(node) {
11
11
  return node.text;
12
12
  }
13
13
  export function visitNumericLiteral(node) {
14
+ const getOuterExpr = (n) => !ts.isParenthesizedExpression(n) &&
15
+ n.kind !== ts.SyntaxKind.FirstLiteralToken
16
+ ? n
17
+ : getOuterExpr(n.parent);
18
+ const outerExpr = getOuterExpr(node);
19
+ if (!ts.isPropertyAccessExpression(outerExpr) &&
20
+ !ts.isElementAccessExpression(outerExpr)) {
21
+ return this.escapeString(node.text);
22
+ }
14
23
  if (node.text === "0" || (node.text.startsWith("0.") &&
15
24
  !node.text.substring(2).split("").some((c) => c !== "0")))
16
25
  return "jspp::Constants::ZERO";