@ugo-studio/jspp 0.3.0 → 0.3.2

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 (127) hide show
  1. package/LICENSE +25 -25
  2. package/README.md +20 -12
  3. package/dist/cli/args.js +22 -0
  4. package/dist/cli/compiler.js +53 -0
  5. package/dist/cli/index.js +43 -107
  6. package/dist/cli/pch.js +71 -0
  7. package/dist/cli/runner.js +23 -0
  8. package/dist/cli/spinner.js +27 -11
  9. package/dist/cli/transpiler.js +20 -0
  10. package/dist/cli/utils.js +59 -0
  11. package/dist/cli/wasm.js +70 -0
  12. package/dist/index.js +17 -6
  13. package/dist/{analysis → interpreter/analysis}/scope.js +38 -3
  14. package/dist/{analysis → interpreter/analysis}/typeAnalyzer.js +563 -28
  15. package/dist/{core → interpreter/core}/codegen/class-handlers.js +1 -1
  16. package/dist/{core → interpreter/core}/codegen/control-flow-handlers.js +12 -11
  17. package/dist/{core → interpreter/core}/codegen/declaration-handlers.js +28 -9
  18. package/dist/{core → interpreter/core}/codegen/destructuring-handlers.js +9 -4
  19. package/dist/{core → interpreter/core}/codegen/expression-handlers.js +82 -88
  20. package/dist/{core → interpreter/core}/codegen/function-handlers.js +159 -46
  21. package/dist/{core → interpreter/core}/codegen/helpers.js +170 -25
  22. package/dist/interpreter/core/codegen/index.js +156 -0
  23. package/dist/{core → interpreter/core}/codegen/literal-handlers.js +9 -0
  24. package/dist/{core → interpreter/core}/codegen/statement-handlers.js +47 -7
  25. package/package.json +6 -4
  26. package/scripts/precompile-headers.ts +293 -50
  27. package/scripts/setup-compiler.ts +63 -63
  28. package/scripts/setup-emsdk.ts +114 -0
  29. package/src/prelude/any_value.cpp +888 -0
  30. package/src/prelude/any_value.hpp +29 -24
  31. package/src/prelude/{exception_helpers.hpp → exception.cpp} +53 -53
  32. package/src/prelude/exception.hpp +27 -27
  33. package/src/prelude/iterator_instantiations.hpp +10 -0
  34. package/src/prelude/{index.hpp → jspp.hpp} +13 -17
  35. package/src/prelude/library/array.cpp +191 -0
  36. package/src/prelude/library/array.hpp +5 -178
  37. package/src/prelude/library/boolean.cpp +30 -0
  38. package/src/prelude/library/boolean.hpp +14 -0
  39. package/src/prelude/library/console.cpp +125 -0
  40. package/src/prelude/library/console.hpp +9 -97
  41. package/src/prelude/library/error.cpp +100 -0
  42. package/src/prelude/library/error.hpp +8 -108
  43. package/src/prelude/library/function.cpp +69 -0
  44. package/src/prelude/library/function.hpp +6 -5
  45. package/src/prelude/library/global.cpp +98 -0
  46. package/src/prelude/library/global.hpp +12 -28
  47. package/src/prelude/library/global_usings.hpp +15 -0
  48. package/src/prelude/library/math.cpp +261 -0
  49. package/src/prelude/library/math.hpp +8 -288
  50. package/src/prelude/library/object.cpp +379 -0
  51. package/src/prelude/library/object.hpp +5 -267
  52. package/src/prelude/library/performance.cpp +21 -0
  53. package/src/prelude/library/performance.hpp +5 -20
  54. package/src/prelude/library/process.cpp +38 -0
  55. package/src/prelude/library/process.hpp +3 -31
  56. package/src/prelude/library/promise.cpp +131 -0
  57. package/src/prelude/library/promise.hpp +5 -116
  58. package/src/prelude/library/symbol.cpp +56 -0
  59. package/src/prelude/library/symbol.hpp +5 -46
  60. package/src/prelude/library/timer.cpp +88 -0
  61. package/src/prelude/library/timer.hpp +11 -87
  62. package/src/prelude/runtime.cpp +19 -0
  63. package/src/prelude/types.hpp +26 -20
  64. package/src/prelude/utils/access.hpp +123 -32
  65. package/src/prelude/utils/assignment_operators.hpp +119 -99
  66. package/src/prelude/utils/log_any_value/array.hpp +61 -40
  67. package/src/prelude/utils/log_any_value/function.hpp +39 -39
  68. package/src/prelude/utils/log_any_value/log_any_value.hpp +1 -1
  69. package/src/prelude/utils/log_any_value/object.hpp +60 -3
  70. package/src/prelude/utils/log_any_value/primitives.hpp +1 -1
  71. package/src/prelude/utils/operators.hpp +109 -94
  72. package/src/prelude/utils/operators_native.hpp +349 -0
  73. package/src/prelude/utils/well_known_symbols.hpp +24 -24
  74. package/src/prelude/values/array.cpp +1399 -0
  75. package/src/prelude/values/array.hpp +4 -0
  76. package/src/prelude/values/async_iterator.cpp +251 -0
  77. package/src/prelude/values/async_iterator.hpp +60 -32
  78. package/src/prelude/values/boolean.cpp +64 -0
  79. package/src/prelude/values/function.cpp +262 -0
  80. package/src/prelude/values/function.hpp +10 -30
  81. package/src/prelude/values/iterator.cpp +309 -0
  82. package/src/prelude/values/iterator.hpp +33 -64
  83. package/src/prelude/values/number.cpp +221 -0
  84. package/src/prelude/values/object.cpp +200 -0
  85. package/src/prelude/values/object.hpp +4 -0
  86. package/src/prelude/values/promise.cpp +479 -0
  87. package/src/prelude/values/promise.hpp +9 -2
  88. package/src/prelude/values/prototypes/array.hpp +46 -1348
  89. package/src/prelude/values/prototypes/async_iterator.hpp +19 -61
  90. package/src/prelude/values/prototypes/boolean.hpp +24 -0
  91. package/src/prelude/values/prototypes/function.hpp +7 -46
  92. package/src/prelude/values/prototypes/iterator.hpp +15 -191
  93. package/src/prelude/values/prototypes/number.hpp +30 -210
  94. package/src/prelude/values/prototypes/object.hpp +7 -23
  95. package/src/prelude/values/prototypes/promise.hpp +8 -186
  96. package/src/prelude/values/prototypes/string.hpp +28 -553
  97. package/src/prelude/values/prototypes/symbol.hpp +9 -70
  98. package/src/prelude/values/shape.hpp +52 -52
  99. package/src/prelude/values/string.cpp +485 -0
  100. package/src/prelude/values/symbol.cpp +89 -0
  101. package/src/prelude/values/symbol.hpp +101 -101
  102. package/dist/cli/file-utils.js +0 -20
  103. package/dist/cli-utils/args.js +0 -59
  104. package/dist/cli-utils/colors.js +0 -9
  105. package/dist/cli-utils/file-utils.js +0 -20
  106. package/dist/cli-utils/spinner.js +0 -55
  107. package/dist/cli.js +0 -153
  108. package/dist/core/codegen/index.js +0 -86
  109. package/src/prelude/any_value_access.hpp +0 -170
  110. package/src/prelude/any_value_defines.hpp +0 -190
  111. package/src/prelude/any_value_helpers.hpp +0 -374
  112. package/src/prelude/utils/operators_primitive.hpp +0 -337
  113. package/src/prelude/values/helpers/array.hpp +0 -199
  114. package/src/prelude/values/helpers/async_iterator.hpp +0 -275
  115. package/src/prelude/values/helpers/function.hpp +0 -109
  116. package/src/prelude/values/helpers/iterator.hpp +0 -145
  117. package/src/prelude/values/helpers/object.hpp +0 -104
  118. package/src/prelude/values/helpers/promise.hpp +0 -254
  119. package/src/prelude/values/helpers/string.hpp +0 -37
  120. package/src/prelude/values/helpers/symbol.hpp +0 -21
  121. /package/dist/{ast → interpreter/ast}/symbols.js +0 -0
  122. /package/dist/{ast → interpreter/ast}/types.js +0 -0
  123. /package/dist/{core → interpreter/core}/codegen/visitor.js +0 -0
  124. /package/dist/{core → interpreter/core}/constants.js +0 -0
  125. /package/dist/{core → interpreter/core}/error.js +0 -0
  126. /package/dist/{core → interpreter/core}/parser.js +0 -0
  127. /package/dist/{core → interpreter/core}/traverser.js +0 -0
@@ -5,6 +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(
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
+ // );
8
20
  const isInsideGeneratorFunction = this.isGeneratorFunction(node);
9
21
  const isInsideAsyncFunction = this.isAsyncFunction(node);
10
22
  const isArrow = ts.isArrowFunction(node);
@@ -12,15 +24,27 @@ export function generateLambdaComponents(node, context, options) {
12
24
  isInsideGeneratorFunction: isInsideGeneratorFunction,
13
25
  isInsideAsyncFunction: isInsideAsyncFunction,
14
26
  });
15
- const funcReturnType = (isInsideGeneratorFunction && isInsideAsyncFunction)
16
- ? "jspp::JsAsyncIterator<jspp::AnyValue>"
17
- : (isInsideGeneratorFunction
18
- ? "jspp::JsIterator<jspp::AnyValue>"
19
- : (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
+ };
20
44
  // Lambda arguments: regular functions use std::span for performance.
21
45
  // Generators and async functions use std::vector to ensure they are safely copied into the coroutine frame.
22
46
  const paramThisType = "jspp::AnyValue";
23
- const paramArgsType = (isInsideGeneratorFunction || isInsideAsyncFunction)
47
+ const paramArgsType = isInsideGeneratorFunction || isInsideAsyncFunction
24
48
  ? "std::vector<jspp::AnyValue>"
25
49
  : "std::span<const jspp::AnyValue>";
26
50
  const globalScopeSymbols = this.prepareScopeSymbolsForVisit(context.globalScopeSymbols, context.localScopeSymbols);
@@ -29,7 +53,7 @@ export function generateLambdaComponents(node, context, options) {
29
53
  isMainContext: false,
30
54
  isInsideFunction: true,
31
55
  isFunctionBody: false,
32
- lambdaName: undefined,
56
+ functionName: undefined,
33
57
  globalScopeSymbols,
34
58
  localScopeSymbols: new DeclaredSymbols(),
35
59
  superClassVar: context.superClassVar,
@@ -59,7 +83,7 @@ export function generateLambdaComponents(node, context, options) {
59
83
  nativePreamble +=
60
84
  `${this.indent()}jspp::AnyValue ${this.globalThisVar} = ${finalThisParamName};\n`;
61
85
  }
62
- // Note: Arguments are now automatically copied into the coroutine frame because
86
+ // Note: Arguments are now automatically copied into the coroutine frame because
63
87
  // the lambda parameter is passed by value (std::vector).
64
88
  // We just need to define argsName as a reference to the parameter.
65
89
  preamble +=
@@ -69,17 +93,33 @@ export function generateLambdaComponents(node, context, options) {
69
93
  let nativeFuncArgs = "";
70
94
  let nativeParamsContent = "";
71
95
  this.validateFunctionParams(node.parameters).forEach((p, i) => {
72
- const name = ts.isIdentifier(p.name)
73
- ? p.name.text
74
- : this.generateUniqueName(`__param_native_${i}_`, visitContext.localScopeSymbols);
96
+ const isIdentifier = ts.isIdentifier(p.name);
97
+ const name = isIdentifier ? p.name.text : this.generateUniqueName(`__param_native_${i}_`, visitContext.localScopeSymbols);
75
98
  const defaultValue = p.initializer
76
99
  ? this.visit(p.initializer, visitContext)
77
100
  : !!p.dotDotDotToken
78
101
  ? "jspp::AnyValue::make_array(std::vector<jspp::AnyValue>{})"
79
102
  : "jspp::Constants::UNDEFINED";
80
- nativeFuncArgs += `, jspp::AnyValue ${name} = ${defaultValue}`;
81
- if (!ts.isIdentifier(p.name)) {
82
- nativeParamsContent += this.generateDestructuring(p.name, name, visitContext) + ";\n";
103
+ let signatureName = name;
104
+ let needsTemp = !isIdentifier;
105
+ if (isIdentifier) {
106
+ const scope = this.getScopeForNode(p);
107
+ const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
108
+ if (typeInfo?.needsHeapAllocation) {
109
+ needsTemp = true;
110
+ signatureName = this.generateUniqueName(`__${name}_param_`, visitContext.localScopeSymbols);
111
+ }
112
+ }
113
+ nativeFuncArgs += `, jspp::AnyValue ${signatureName} = ${defaultValue}`;
114
+ if (needsTemp) {
115
+ if (isIdentifier) {
116
+ nativeParamsContent +=
117
+ `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${signatureName});\n`;
118
+ }
119
+ else {
120
+ nativeParamsContent += this.generateDestructuring(p.name, signatureName, visitContext) +
121
+ ";\n";
122
+ }
83
123
  }
84
124
  });
85
125
  // Extract lambda parameters from arguments span/vector
@@ -97,12 +137,11 @@ export function generateLambdaComponents(node, context, options) {
97
137
  checks: { initialized: true },
98
138
  });
99
139
  const scope = this.getScopeForNode(p);
100
- const typeInfo = this.typeAnalyzer.scopeManager
101
- .lookupFromScope(name, scope);
140
+ const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
102
141
  // Handle rest parameters
103
142
  if (!!p.dotDotDotToken) {
104
143
  const initValue = `jspp::AnyValue::make_array(${argsName}.subspan(${i}))`;
105
- if (typeInfo.needsHeapAllocation) {
144
+ if (typeInfo?.needsHeapAllocation) {
106
145
  paramsCode +=
107
146
  `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initValue});\n`;
108
147
  }
@@ -114,7 +153,7 @@ export function generateLambdaComponents(node, context, options) {
114
153
  }
115
154
  // Normal parameter
116
155
  const initValue = `${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue}`;
117
- if (typeInfo && typeInfo.needsHeapAllocation) {
156
+ if (typeInfo?.needsHeapAllocation) {
118
157
  paramsCode +=
119
158
  `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initValue});\n`;
120
159
  }
@@ -131,14 +170,43 @@ export function generateLambdaComponents(node, context, options) {
131
170
  const initValue = `${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue}`;
132
171
  paramsCode +=
133
172
  `${this.indent()}auto ${tempName} = ${initValue};\n`;
134
- paramsCode += this.generateDestructuring(p.name, tempName, visitContext) + ";\n";
173
+ paramsCode +=
174
+ this.generateDestructuring(p.name, tempName, visitContext) +
175
+ ";\n";
135
176
  }
136
177
  });
137
178
  return paramsCode;
138
179
  };
139
180
  // Generate params and function body
140
181
  let paramsContent = "";
141
- let blockContentWithoutOpeningBrace = "";
182
+ let getBlockContentWithoutOpeningBrace = () => "";
183
+ // Generate `arguments` variable if it used in the function body
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
+ // }
142
210
  if (node.body) {
143
211
  if (ts.isBlock(node.body)) {
144
212
  // Hoist var declarations in the function body
@@ -148,38 +216,65 @@ export function generateLambdaComponents(node, context, options) {
148
216
  preamble += hoistCode;
149
217
  nativePreamble += hoistCode;
150
218
  });
219
+ // Hoist parameters (especially for destructuring)
220
+ node.parameters.forEach((p) => {
221
+ if (!ts.isIdentifier(p.name)) {
222
+ const hoistCode = this.hoistDeclaration(p, visitContext.localScopeSymbols, node.body);
223
+ preamble += hoistCode;
224
+ nativePreamble += hoistCode;
225
+ }
226
+ });
151
227
  this.indentationLevel++;
152
- paramsContent = generateParamsBuilder();
228
+ paramsContent += generateParamsBuilder();
153
229
  this.indentationLevel--;
154
230
  // The block visitor already adds braces, so we need to remove the opening brace to inject the preamble and param extraction.
155
- blockContentWithoutOpeningBrace = this.visit(node.body, {
156
- ...visitContext,
157
- isMainContext: false,
158
- isInsideFunction: true,
159
- isFunctionBody: true,
160
- isInsideGeneratorFunction: isInsideGeneratorFunction,
161
- isInsideAsyncFunction: isInsideAsyncFunction,
162
- }).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
+ };
163
250
  }
164
251
  else {
165
252
  this.indentationLevel++;
166
- paramsContent = generateParamsBuilder();
167
- blockContentWithoutOpeningBrace =
168
- `${this.indent()}${returnCommand} ${this.visit(node.body, {
253
+ paramsContent += generateParamsBuilder();
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, {
169
260
  ...visitContext,
170
261
  isMainContext: false,
171
262
  isInsideFunction: true,
172
263
  isFunctionBody: false,
173
- isInsideGeneratorFunction: isInsideGeneratorFunction,
174
- isInsideAsyncFunction: isInsideAsyncFunction,
264
+ isInsideGeneratorFunction,
265
+ isInsideAsyncFunction,
266
+ isInsideNativeLambda,
175
267
  })};\n`;
268
+ this.indentationLevel--;
269
+ code += `${this.indent()}}`;
270
+ this.indentationLevel = currentIndentationLevel;
271
+ return code;
272
+ };
176
273
  this.indentationLevel--;
177
- blockContentWithoutOpeningBrace += `${this.indent()}}`;
178
274
  }
179
275
  }
180
276
  else {
181
- blockContentWithoutOpeningBrace =
182
- `${returnCommand} jspp::Constants::UNDEFINED; }\n`;
277
+ getBlockContentWithoutOpeningBrace = () => `${returnCommand} jspp::Constants::UNDEFINED; }`;
183
278
  }
184
279
  return {
185
280
  node,
@@ -189,37 +284,39 @@ export function generateLambdaComponents(node, context, options) {
189
284
  thisArgParam,
190
285
  funcArgs,
191
286
  nativeFuncArgs,
192
- funcReturnType,
287
+ getFuncReturnType,
193
288
  preamble,
194
289
  nativePreamble,
195
290
  paramsContent,
196
291
  nativeParamsContent,
197
- blockContentWithoutOpeningBrace,
292
+ getBlockContentWithoutOpeningBrace,
198
293
  isInsideGeneratorFunction,
199
294
  isInsideAsyncFunction,
200
295
  };
201
296
  }
202
297
  export function generateNativeLambda(comps) {
203
- const { options, thisArgParam, nativeFuncArgs, funcReturnType, nativePreamble, nativeParamsContent, blockContentWithoutOpeningBrace, } = comps;
298
+ const { options, thisArgParam, nativeFuncArgs, getFuncReturnType, nativePreamble, nativeParamsContent, getBlockContentWithoutOpeningBrace, } = comps;
204
299
  const capture = options?.capture || "[=]";
205
300
  const nativeName = options?.nativeName;
206
301
  const selfParam = nativeName ? `this auto&& ${nativeName}, ` : "";
207
302
  const mutableLabel = nativeName ? "" : "mutable ";
303
+ const funcReturnType = getFuncReturnType(true);
208
304
  let lambda = `${capture}(${selfParam}${thisArgParam}${nativeFuncArgs}) ${mutableLabel}-> ${funcReturnType} {\n`;
209
305
  lambda += nativePreamble;
210
306
  lambda += nativeParamsContent;
211
- lambda += blockContentWithoutOpeningBrace;
307
+ lambda += getBlockContentWithoutOpeningBrace(true);
212
308
  return lambda;
213
309
  }
214
310
  export function generateWrappedLambda(comps) {
215
- 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;
216
312
  const capture = options?.capture || "[=]";
217
313
  const isAssignment = options?.isAssignment || false;
314
+ const funcReturnType = getFuncReturnType(false);
218
315
  const noTypeSignature = options?.noTypeSignature || false;
219
316
  let lambda = `${capture}(${thisArgParam}, ${funcArgs}) mutable -> ${funcReturnType} {\n`;
220
317
  lambda += preamble;
221
318
  lambda += paramsContent;
222
- lambda += blockContentWithoutOpeningBrace;
319
+ lambda += getBlockContentWithoutOpeningBrace(false);
223
320
  let callable = lambda;
224
321
  let method = "";
225
322
  // Handle generator function
@@ -258,7 +355,7 @@ export function generateWrappedLambda(comps) {
258
355
  method = `jspp::AnyValue::make_function`;
259
356
  }
260
357
  }
261
- const funcName = context?.lambdaName || node.name?.getText();
358
+ const funcName = context?.functionName || node.name?.getText();
262
359
  const hasName = !!funcName && funcName.length > 0;
263
360
  let args = callable;
264
361
  const isArrow = ts.isArrowFunction(node);
@@ -283,7 +380,23 @@ export function generateWrappedLambda(comps) {
283
380
  args += `, "${funcName}"`;
284
381
  }
285
382
  }
286
- const fullExpression = `${method}(${args})`;
383
+ let prototypeExpr = "";
384
+ if (isInsideGeneratorFunction) {
385
+ if (isInsideAsyncFunction) {
386
+ prototypeExpr =
387
+ '::AsyncGeneratorFunction.get_own_property("prototype")';
388
+ }
389
+ else {
390
+ prototypeExpr = '::GeneratorFunction.get_own_property("prototype")';
391
+ }
392
+ }
393
+ else if (isInsideAsyncFunction) {
394
+ prototypeExpr = '::AsyncFunction.get_own_property("prototype")';
395
+ }
396
+ else {
397
+ prototypeExpr = '::Function.get_own_property("prototype")';
398
+ }
399
+ const fullExpression = `${method}(${args}).set_prototype(${prototypeExpr})`;
287
400
  if (ts.isFunctionDeclaration(node) && !isAssignment && node.name) {
288
401
  const funcName = node.name?.getText();
289
402
  return `${this.indent()}auto ${funcName} = ${fullExpression};\n`;
@@ -312,7 +425,7 @@ export function visitFunctionExpression(node, context) {
312
425
  `${this.indent()}auto ${funcName} = std::make_shared<jspp::AnyValue>();\n`;
313
426
  const lambda = this.generateWrappedLambda(this.generateLambdaComponents(funcExpr, {
314
427
  ...context,
315
- lambdaName: funcName,
428
+ functionName: funcName,
316
429
  }, {
317
430
  isAssignment: true,
318
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
  /**
@@ -161,6 +161,9 @@ export function getJsVarName(node) {
161
161
  * @returns The C++ code for accessing the variable's value.
162
162
  */
163
163
  export function getDerefCode(nodeText, varName, context, typeInfo) {
164
+ if (typeInfo?.isBuiltin) {
165
+ return nodeText;
166
+ }
164
167
  // Make sure varName is incased in quotes
165
168
  if (!varName.startsWith('"'))
166
169
  varName = '"' + varName;
@@ -230,22 +233,24 @@ export function getReturnCommand(context) {
230
233
  * @throws CompilerError if a duplicate declaration is found.
231
234
  */
232
235
  export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
233
- const isLet = ts.isVariableDeclaration(decl) &&
234
- (decl.parent.flags & (ts.NodeFlags.Let)) !== 0;
236
+ const isLet = ts.isParameter(decl) || (ts.isVariableDeclaration(decl) &&
237
+ (decl.parent.flags & (ts.NodeFlags.Let)) !== 0);
235
238
  const isConst = ts.isVariableDeclaration(decl) &&
236
239
  (decl.parent.flags & (ts.NodeFlags.Const)) !== 0;
237
- const declType = isLet
240
+ const declType = ts.isParameter(decl)
238
241
  ? DeclarationType.let
239
- : isConst
240
- ? DeclarationType.const
241
- : ts.isFunctionDeclaration(decl)
242
- ? DeclarationType.function
243
- : ts.isClassDeclaration(decl)
244
- ? DeclarationType.class
245
- : ts.isEnumDeclaration(decl)
246
- ? DeclarationType.enum
247
- : DeclarationType.var;
248
- const hoistName = (nameNode) => {
242
+ : isLet
243
+ ? DeclarationType.let
244
+ : isConst
245
+ ? DeclarationType.const
246
+ : ts.isFunctionDeclaration(decl)
247
+ ? DeclarationType.function
248
+ : ts.isClassDeclaration(decl)
249
+ ? DeclarationType.class
250
+ : ts.isEnumDeclaration(decl)
251
+ ? DeclarationType.enum
252
+ : DeclarationType.var;
253
+ const hoistName = (nameNode, isFromDestructuring = false) => {
249
254
  if (ts.isIdentifier(nameNode)) {
250
255
  const name = nameNode.text;
251
256
  if (hoistedSymbols.has(name)) {
@@ -264,40 +269,53 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
264
269
  }
265
270
  else {
266
271
  // Add the symbol to the hoisted symbols
272
+ hoistedSymbols.add(name, {
273
+ type: declType,
274
+ });
275
+ // Get and update symbol features
267
276
  if (ts.isFunctionDeclaration(decl) ||
268
277
  (ts.isVariableDeclaration(decl) && decl.initializer &&
269
278
  nameNode === decl.name &&
270
279
  (ts.isArrowFunction(decl.initializer) ||
271
- ts.isFunctionExpression(decl.initializer)))) {
280
+ (ts.isFunctionExpression(decl.initializer) &&
281
+ !decl.initializer.name)))) {
272
282
  const funcExpr = ts.isVariableDeclaration(decl)
273
283
  ? decl.initializer
274
284
  : decl;
275
- const isAsync = this.isAsyncFunction(funcExpr);
276
- const isGenerator = this.isGeneratorFunction(funcExpr);
277
- hoistedSymbols.add(name, {
278
- type: declType,
279
- features: { isAsync, isGenerator },
285
+ // Update features
286
+ hoistedSymbols.update(name, {
287
+ features: {
288
+ isAsync: this.isAsyncFunction(funcExpr),
289
+ isGenerator: this.isGeneratorFunction(funcExpr),
290
+ },
280
291
  });
281
292
  // Don't hoist declarations not used as a variable
282
293
  // They will be called with their native lambda/value
283
294
  if (!this.isDeclarationUsedAsValue(decl, scopeNode) &&
284
295
  !this.isDeclarationUsedBeforeInitialization(name, scopeNode)) {
296
+ hoistedSymbols.update(name, {
297
+ checks: { skippedHoisting: true },
298
+ });
285
299
  return "";
286
300
  }
287
301
  }
288
- else {
289
- hoistedSymbols.add(name, { type: declType });
290
- }
291
302
  }
292
303
  const scope = this.getScopeForNode(decl);
293
304
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
294
305
  const initializer = isLet || isConst || ts.isClassDeclaration(decl)
295
306
  ? "jspp::Constants::UNINITIALIZED"
296
307
  : "jspp::Constants::UNDEFINED";
297
- if (typeInfo.needsHeapAllocation) {
308
+ if (typeInfo?.needsHeapAllocation) {
298
309
  return `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initializer});\n`;
299
310
  }
300
311
  else {
312
+ if ((isLet || isConst) &&
313
+ !this.isDeclarationUsedBeforeInitialization(name, scopeNode) && !isFromDestructuring) {
314
+ hoistedSymbols.update(name, {
315
+ checks: { skippedHoisting: true },
316
+ });
317
+ return "";
318
+ }
301
319
  return `${this.indent()}jspp::AnyValue ${name} = ${initializer};\n`;
302
320
  }
303
321
  }
@@ -305,7 +323,7 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
305
323
  let code = "";
306
324
  nameNode.elements.forEach((element) => {
307
325
  if (ts.isBindingElement(element)) {
308
- code += hoistName(element.name);
326
+ code += hoistName(element.name, true);
309
327
  }
310
328
  });
311
329
  return code;
@@ -638,6 +656,104 @@ export function isDeclarationUsedBeforeInitialization(name, root) {
638
656
  visit(root);
639
657
  return isUsedBefore;
640
658
  }
659
+ /**
660
+ * Checks if a variable name is used within a node subtree without a preceding declaration.
661
+ * Returns true if:
662
+ * 1. The variable is used but never declared within the provided root node.
663
+ * 2. The variable is used lexically before its declaration within the root node.
664
+ *
665
+ * @param name The variable name to check.
666
+ * @param root The root node to search within.
667
+ * @returns True if used without/before declaration.
668
+ */
669
+ export function isVariableUsedWithoutDeclaration(name, root) {
670
+ let declPos = -1;
671
+ let foundDecl = false;
672
+ // 1. Find the declaration position within this root
673
+ const findDecl = (node) => {
674
+ if (foundDecl)
675
+ return;
676
+ if ((ts.isFunctionDeclaration(node) ||
677
+ ts.isClassDeclaration(node) ||
678
+ ts.isVariableDeclaration(node) ||
679
+ ts.isEnumDeclaration(node) ||
680
+ ts.isParameter(node)) &&
681
+ node.name && ts.isIdentifier(node.name) && node.name.text === name) {
682
+ declPos = node.getStart();
683
+ foundDecl = true;
684
+ }
685
+ else {
686
+ ts.forEachChild(node, findDecl);
687
+ }
688
+ };
689
+ findDecl(root);
690
+ let isUsedIllegally = false;
691
+ // 2. Traverse for usages
692
+ const visit = (node) => {
693
+ if (isUsedIllegally)
694
+ return;
695
+ if (ts.isIdentifier(node) && node.text === name) {
696
+ const parent = node.parent;
697
+ // Skip if this identifier IS the declaration site
698
+ const isDeclarationSite = (ts.isFunctionDeclaration(parent) ||
699
+ ts.isVariableDeclaration(parent) ||
700
+ ts.isClassDeclaration(parent) ||
701
+ ts.isMethodDeclaration(parent) ||
702
+ ts.isEnumDeclaration(parent) ||
703
+ ts.isEnumMember(parent) ||
704
+ ts.isParameter(parent) ||
705
+ ts.isImportSpecifier(parent)) &&
706
+ parent.name === node;
707
+ // Skip if it's a property access (e.g., obj.name)
708
+ const isProperty = (ts.isPropertyAccessExpression(parent) &&
709
+ parent.name === node) ||
710
+ (ts.isPropertyAssignment(parent) && parent.name === node);
711
+ if (!isDeclarationSite && !isProperty) {
712
+ const usagePos = node.getStart();
713
+ // Case 1: Used but not declared in this node
714
+ if (!foundDecl) {
715
+ isUsedIllegally = true;
716
+ return;
717
+ }
718
+ // Case 2: Used lexically before declaration
719
+ if (usagePos < declPos) {
720
+ isUsedIllegally = true;
721
+ return;
722
+ }
723
+ }
724
+ }
725
+ ts.forEachChild(node, visit);
726
+ };
727
+ visit(root);
728
+ return isUsedIllegally;
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
+ }
641
757
  /**
642
758
  * Validates and filters function parameters, checking for illegal "this"
643
759
  * and correctly positioned rest parameters.
@@ -667,3 +783,32 @@ export function validateFunctionParams(parameters) {
667
783
  return p;
668
784
  }) || [];
669
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
+ }