@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
@@ -1,5 +1,7 @@
1
1
  import ts from "typescript";
2
+ import { DeclaredSymbols } from "../../ast/symbols";
2
3
  import { CodeGenerator } from "./";
4
+ import { collectFunctionScopedDeclarations } from "./helpers";
3
5
  export function generateLambda(node, context, options) {
4
6
  const isAssignment = options?.isAssignment || false;
5
7
  const capture = options?.capture || "[=]";
@@ -11,76 +13,175 @@ export function generateLambda(node, context, options) {
11
13
  isInsideGeneratorFunction: isInsideGeneratorFunction,
12
14
  isInsideAsyncFunction: isInsideAsyncFunction,
13
15
  });
14
- const funcReturnType = isInsideGeneratorFunction
15
- ? "jspp::JsIterator<jspp::AnyValue>"
16
- : (isInsideAsyncFunction ? "jspp::JsPromise" : "jspp::AnyValue");
16
+ const funcReturnType = (isInsideGeneratorFunction && isInsideAsyncFunction)
17
+ ? "jspp::JsAsyncIterator<jspp::AnyValue>"
18
+ : (isInsideGeneratorFunction
19
+ ? "jspp::JsIterator<jspp::AnyValue>"
20
+ : (isInsideAsyncFunction ? "jspp::JsPromise" : "jspp::AnyValue"));
17
21
  const isArrow = ts.isArrowFunction(node);
18
- // For generators and async functions, we MUST copy arguments because the coroutine suspends immediately
19
- // and references to temporary arguments would dangle.
20
- const paramThisType = (isInsideGeneratorFunction || isInsideAsyncFunction)
21
- ? "jspp::AnyValue"
22
- : "const jspp::AnyValue&";
23
- const paramArgsType = (isInsideGeneratorFunction || isInsideAsyncFunction)
24
- ? "std::vector<jspp::AnyValue>"
25
- : "const std::vector<jspp::AnyValue>&";
22
+ // Lambda arguments are ALWAYS const references/spans to avoid copy overhead for normal functions.
23
+ // For generators/async, we manually copy them inside the body.
24
+ const paramThisType = "const jspp::AnyValue&";
25
+ const paramArgsType = "std::span<const jspp::AnyValue>";
26
26
  const thisArgParam = isArrow
27
- ? "const jspp::AnyValue&" // Arrow functions are never generators in this parser
27
+ ? "const jspp::AnyValue&" // Arrow functions use captured 'this' or are never generators in this parser context
28
28
  : `${paramThisType} ${this.globalThisVar}`;
29
29
  let lambda = `${capture}(${thisArgParam}, ${paramArgsType} ${argsName}) mutable -> ${funcReturnType} `;
30
- const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.currentScopeSymbols);
30
+ const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.localScopeSymbols);
31
31
  const visitContext = {
32
32
  isMainContext: false,
33
33
  isInsideFunction: true,
34
34
  isFunctionBody: false,
35
35
  lambdaName: undefined,
36
36
  topLevelScopeSymbols,
37
- currentScopeSymbols: new Map(),
37
+ localScopeSymbols: new DeclaredSymbols(),
38
38
  superClassVar: context.superClassVar,
39
+ isInsideGeneratorFunction: isInsideGeneratorFunction,
40
+ isInsideAsyncFunction: isInsideAsyncFunction,
39
41
  };
40
- if (node.body) {
41
- if (ts.isBlock(node.body)) {
42
- let paramExtraction = "";
43
- this.indentationLevel++;
44
- node.parameters.forEach((p, i) => {
45
- const name = p.name.getText();
46
- const defaultValue = p.initializer
47
- ? this.visit(p.initializer, visitContext)
48
- : "jspp::AnyValue::make_undefined()";
49
- if (!!p.dotDotDotToken) {
50
- if (node.parameters.length - 1 !== i) {
51
- throw new SyntaxError("Rest parameter must be last formal parameter.");
52
- }
53
- const tempName = `temp_${name}`;
54
- paramExtraction +=
55
- `${this.indent()}auto ${name} = jspp::AnyValue::make_undefined();\n`;
56
- paramExtraction += `${this.indent()}{\n`;
57
- this.indentationLevel++;
58
- paramExtraction +=
59
- `${this.indent()}std::vector<std::optional<jspp::AnyValue>> ${tempName};\n`;
60
- paramExtraction +=
61
- `${this.indent()}if (${argsName}.size() > ${i}) {\n`;
62
- this.indentationLevel++;
63
- paramExtraction +=
64
- `${this.indent()}${tempName}.reserve(${argsName}.size() - ${i});\n`;
65
- this.indentationLevel--;
66
- paramExtraction += `${this.indent()}}\n`;
67
- paramExtraction +=
68
- `${this.indent()}for (size_t i = ${i}; i < ${argsName}.size(); i++) {\n`;
69
- this.indentationLevel++;
70
- paramExtraction +=
71
- `${this.indent()}${tempName}.push_back(${argsName}[i]);\n`;
72
- this.indentationLevel--;
73
- paramExtraction += `${this.indent()}}\n`;
74
- paramExtraction +=
75
- `${this.indent()}${name} = jspp::AnyValue::make_array(std::move(${tempName}));\n`;
76
- this.indentationLevel--;
77
- paramExtraction += `${this.indent()}}\n`;
42
+ // Name of 'this' and 'args' to be used in the body.
43
+ // If generator/async, we use the copied versions.
44
+ let bodyThisVar = this.globalThisVar;
45
+ let bodyArgsVar = argsName;
46
+ // Preamble to copy arguments for coroutines
47
+ let preamble = "";
48
+ if (isInsideGeneratorFunction || isInsideAsyncFunction) {
49
+ const thisCopy = this.generateUniqueName("__this_copy", declaredSymbols);
50
+ const argsCopy = this.generateUniqueName("__args_copy", declaredSymbols);
51
+ if (!isArrow) {
52
+ preamble +=
53
+ `${this.indent()}jspp::AnyValue ${thisCopy} = ${this.globalThisVar};\n`;
54
+ bodyThisVar = thisCopy;
55
+ }
56
+ preamble +=
57
+ `${this.indent()}std::vector<jspp::AnyValue> ${argsCopy}(${argsName}.begin(), ${argsName}.end());\n`;
58
+ bodyArgsVar = argsCopy;
59
+ // Update visitContext to use the new 'this' variable if needed?
60
+ // CodeGenerator.globalThisVar is a member of the class, so we can't easily change it for the recursive visit
61
+ // without changing the class state or passing it down.
62
+ // BUT: visitThisKeyword uses `this.globalThisVar`.
63
+ // We need to temporarily swap `this.globalThisVar` or handle it.
64
+ // Actually, `this.globalThisVar` is set once in `generate`.
65
+ // Wait, `visitThisKeyword` uses `this.globalThisVar`.
66
+ // If we change the variable name for 'this', we must ensure `visitThisKeyword` generates the correct name.
67
+ // `generateLambda` does NOT create a new CodeGenerator instance, so `this.globalThisVar` is the global one.
68
+ //
69
+ // We need to support shadowing `globalThisVar` for the duration of the visit.
70
+ // The current `CodeGenerator` doesn't seem to support changing `globalThisVar` recursively easily
71
+ // because it's a property of the class, not `VisitContext`.
72
+ //
73
+ // However, `visitFunctionDeclaration` etc don't update `globalThisVar`.
74
+ // `visitThisKeyword` just returns `this.globalThisVar`.
75
+ //
76
+ // If we are inside a function, `this` should refer to the function's `this`.
77
+ // `this.globalThisVar` is generated in `generate()`: `__this_val__...`.
78
+ // And `generateLambda` uses `this.globalThisVar` as the parameter name.
79
+ //
80
+ // So, if we copy it to `__this_copy`, `visitThisKeyword` will still print `__this_val__...`.
81
+ // This is BAD for generators because `__this_val__...` is a reference that dies.
82
+ //
83
+ // FIX: We must reuse the SAME name for the local copy, and shadow the parameter.
84
+ // But we can't declare a variable with the same name as the parameter in the same scope.
85
+ //
86
+ // We can rename the PARAMETER to something else, and declare the local variable with `this.globalThisVar`.
87
+ //
88
+ }
89
+ // Adjust parameter names for generator/async to allow shadowing/copying
90
+ let finalThisParamName = isArrow ? "" : this.globalThisVar;
91
+ let finalArgsParamName = argsName;
92
+ if (isInsideGeneratorFunction || isInsideAsyncFunction) {
93
+ if (!isArrow) {
94
+ finalThisParamName = this.generateUniqueName("__this_ref", declaredSymbols);
95
+ }
96
+ finalArgsParamName = this.generateUniqueName("__args_ref", declaredSymbols);
97
+ }
98
+ const thisArgParamFinal = isArrow
99
+ ? "const jspp::AnyValue&"
100
+ : `${paramThisType} ${finalThisParamName}`;
101
+ // Re-construct lambda header with potentially new param names
102
+ lambda =
103
+ `${capture}(${thisArgParamFinal}, ${paramArgsType} ${finalArgsParamName}) mutable -> ${funcReturnType} `;
104
+ // Regenerate preamble
105
+ preamble = "";
106
+ if (isInsideGeneratorFunction || isInsideAsyncFunction) {
107
+ if (!isArrow) {
108
+ preamble +=
109
+ `${this.indent()}jspp::AnyValue ${this.globalThisVar} = ${finalThisParamName};\n`;
110
+ }
111
+ preamble +=
112
+ `${this.indent()}std::vector<jspp::AnyValue> ${argsName}(${finalArgsParamName}.begin(), ${finalArgsParamName}.end());\n`;
113
+ }
114
+ // Now 'argsName' refers to the vector (if copied) or the span (if not).
115
+ // And 'this.globalThisVar' refers to the copy (if copied) or the param (if not).
116
+ // So subsequent code using `argsName` and `visit` (using `globalThisVar`) works correctly.
117
+ const paramExtractor = (parameters) => {
118
+ let code = "";
119
+ parameters.forEach((p, i) => {
120
+ const name = p.name.getText();
121
+ const defaultValue = p.initializer
122
+ ? this.visit(p.initializer, visitContext)
123
+ : "jspp::Constants::UNDEFINED";
124
+ const scope = this.getScopeForNode(p);
125
+ const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
126
+ // Handle rest parameters
127
+ if (!!p.dotDotDotToken) {
128
+ if (parameters.length - 1 !== i) {
129
+ throw new SyntaxError("Rest parameter must be last formal parameter.");
130
+ }
131
+ if (typeInfo.needsHeapAllocation) {
132
+ code +=
133
+ `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(jspp::Constants::UNDEFINED);\n`;
78
134
  }
79
135
  else {
80
- paramExtraction +=
81
- `${this.indent()}auto ${name} = ${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue};\n`;
136
+ code +=
137
+ `${this.indent()}jspp::AnyValue ${name} = jspp::Constants::UNDEFINED;\n`;
82
138
  }
139
+ // Extract rest parameters
140
+ const tempName = `temp_${name}`;
141
+ code += `${this.indent()}{\n`;
142
+ this.indentationLevel++;
143
+ code +=
144
+ `${this.indent()}std::vector<std::optional<jspp::AnyValue>> ${tempName};\n`;
145
+ code += `${this.indent()}if (${argsName}.size() > ${i}) {\n`;
146
+ this.indentationLevel++;
147
+ code +=
148
+ `${this.indent()}${tempName}.reserve(${argsName}.size() - ${i});\n`;
149
+ this.indentationLevel--;
150
+ code += `${this.indent()}}\n`;
151
+ code +=
152
+ `${this.indent()}for (size_t j = ${i}; j < ${argsName}.size(); j++) {\n`;
153
+ this.indentationLevel++;
154
+ code +=
155
+ `${this.indent()}${tempName}.push_back(${argsName}[j]);\n`;
156
+ this.indentationLevel--;
157
+ code += `${this.indent()}}\n`;
158
+ code += `${this.indent()}${typeInfo.needsHeapAllocation ? "*" : ""}${name} = jspp::AnyValue::make_array(std::move(${tempName}));\n`;
159
+ this.indentationLevel--;
160
+ code += `${this.indent()}}\n`;
161
+ return;
162
+ }
163
+ // Normal parameter
164
+ const initValue = `${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue}`;
165
+ if (typeInfo && typeInfo.needsHeapAllocation) {
166
+ code +=
167
+ `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initValue});\n`;
168
+ }
169
+ else {
170
+ code +=
171
+ `${this.indent()}jspp::AnyValue ${name} = ${initValue};\n`;
172
+ }
173
+ });
174
+ return code;
175
+ };
176
+ if (node.body) {
177
+ if (ts.isBlock(node.body)) {
178
+ // Hoist var declarations in the function body
179
+ const varDecls = collectFunctionScopedDeclarations(node.body);
180
+ varDecls.forEach((decl) => {
181
+ preamble += this.hoistDeclaration(decl, visitContext.localScopeSymbols);
83
182
  });
183
+ this.indentationLevel++;
184
+ const paramExtraction = paramExtractor(node.parameters);
84
185
  this.indentationLevel--;
85
186
  const blockContent = this.visit(node.body, {
86
187
  ...visitContext,
@@ -90,20 +191,15 @@ export function generateLambda(node, context, options) {
90
191
  isInsideGeneratorFunction: isInsideGeneratorFunction,
91
192
  isInsideAsyncFunction: isInsideAsyncFunction,
92
193
  });
93
- // The block visitor already adds braces, so we need to inject the param extraction.
94
- lambda += "{\n" + paramExtraction + blockContent.substring(2);
194
+ // The block visitor already adds braces, so we need to inject the preamble and param extraction.
195
+ lambda += "{\n" + preamble + paramExtraction +
196
+ blockContent.trimStart().substring(2);
95
197
  }
96
198
  else {
97
199
  lambda += "{\n";
98
200
  this.indentationLevel++;
99
- node.parameters.forEach((p, i) => {
100
- const name = p.name.getText();
101
- const defaultValue = p.initializer
102
- ? this.visit(p.initializer, visitContext)
103
- : "jspp::AnyValue::make_undefined()";
104
- lambda +=
105
- `${this.indent()}auto ${name} = ${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue};\n`;
106
- });
201
+ lambda += preamble;
202
+ lambda += paramExtractor(node.parameters);
107
203
  lambda += `${this.indent()}${returnCmd} ${this.visit(node.body, {
108
204
  ...visitContext,
109
205
  isMainContext: false,
@@ -111,34 +207,41 @@ export function generateLambda(node, context, options) {
111
207
  isFunctionBody: false,
112
208
  isInsideGeneratorFunction: isInsideGeneratorFunction,
113
209
  isInsideAsyncFunction: isInsideAsyncFunction,
114
- })};
115
- `;
210
+ })};\n`;
116
211
  this.indentationLevel--;
117
212
  lambda += `${this.indent()}}`;
118
213
  }
119
214
  }
120
215
  else {
121
- lambda += `{ ${returnCmd} jspp::AnyValue::make_undefined(); }\n`;
216
+ lambda += `{ ${preamble} ${returnCmd} jspp::Constants::UNDEFINED; }\n`;
122
217
  }
123
218
  let signature = "";
124
219
  let callable = "";
125
220
  let method = "";
126
221
  // Handle generator function
127
222
  if (isInsideGeneratorFunction) {
128
- signature =
129
- "jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, const std::vector<jspp::AnyValue>&)";
130
- callable = `std::function<${signature}>(${lambda})`;
131
- method = `jspp::AnyValue::make_generator`;
223
+ if (isInsideAsyncFunction) {
224
+ signature =
225
+ "jspp::JsAsyncIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
226
+ callable = `std::function<${signature}>(${lambda})`;
227
+ method = `jspp::AnyValue::make_async_generator`;
228
+ }
229
+ else {
230
+ signature =
231
+ "jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
232
+ callable = `std::function<${signature}>(${lambda})`;
233
+ method = `jspp::AnyValue::make_generator`;
234
+ }
132
235
  } // Handle async function
133
236
  else if (isInsideAsyncFunction) {
134
237
  signature =
135
- "jspp::JsPromise(const jspp::AnyValue&, const std::vector<jspp::AnyValue>&)";
238
+ "jspp::JsPromise(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
136
239
  callable = `std::function<${signature}>(${lambda})`;
137
240
  method = `jspp::AnyValue::make_async_function`;
138
241
  } // Handle normal function
139
242
  else {
140
243
  signature =
141
- `jspp::AnyValue(const jspp::AnyValue&, const std::vector<jspp::AnyValue>&)`;
244
+ `jspp::AnyValue(const jspp::AnyValue&, std::span<const jspp::AnyValue>)`;
142
245
  callable = `std::function<${signature}>(${lambda})`;
143
246
  if (options?.isClass) {
144
247
  method = `jspp::AnyValue::make_class`;
@@ -147,9 +250,33 @@ export function generateLambda(node, context, options) {
147
250
  method = `jspp::AnyValue::make_function`;
148
251
  }
149
252
  }
150
- const funcName = node.name?.getText();
151
- const fullExpression = `${method}(${callable}, "${context?.lambdaName || funcName || ""}")`;
152
- if (ts.isFunctionDeclaration(node) && !isAssignment && funcName) {
253
+ const funcName = context?.lambdaName || node.name?.getText();
254
+ const hasName = !!funcName && funcName.length > 0;
255
+ let args = callable;
256
+ const isMethod = ts.isMethodDeclaration(node);
257
+ const isAccessor = ts.isGetAccessor(node) || ts.isSetAccessor(node);
258
+ const isConstructor = !isArrow && !isMethod && !isAccessor;
259
+ // make_function(callable, name, is_constructor)
260
+ if (method === `jspp::AnyValue::make_function`) {
261
+ if (hasName) {
262
+ args += `, "${funcName}"`;
263
+ }
264
+ else if (!isConstructor) {
265
+ args += `, std::nullopt`;
266
+ }
267
+ if (!isConstructor) {
268
+ args += `, false`;
269
+ }
270
+ }
271
+ else {
272
+ // make_class, make_generator, make_async_function
273
+ if (hasName) {
274
+ args += `, "${funcName}"`;
275
+ }
276
+ }
277
+ const fullExpression = `${method}(${args})`;
278
+ if (ts.isFunctionDeclaration(node) && !isAssignment && node.name) {
279
+ const funcName = node.name?.getText();
153
280
  return `${this.indent()}auto ${funcName} = ${fullExpression};\n`;
154
281
  }
155
282
  return fullExpression;
@@ -1,6 +1,6 @@
1
1
  import ts from "typescript";
2
2
  import { BUILTIN_OBJECTS, Scope } from "../../analysis/scope";
3
- import { DeclaredSymbolType } from "../../ast/types";
3
+ import { DeclaredSymbols, DeclaredSymbolType } from "../../ast/symbols";
4
4
  import { CodeGenerator } from "./";
5
5
  export function isBuiltinObject(node) {
6
6
  return BUILTIN_OBJECTS.values().some((obj) => obj.name === node.text);
@@ -33,23 +33,21 @@ export function getDeclaredSymbols(node) {
33
33
  visitor(node);
34
34
  return symbols;
35
35
  }
36
- export function generateUniqueName(prefix, namesToAvoid) {
37
- let name = `${prefix}${this.exceptionCounter}`;
38
- while (namesToAvoid.has(name)) {
39
- this.exceptionCounter++;
40
- name = `${prefix}${this.exceptionCounter}`;
36
+ export function generateUniqueName(prefix, ...namesToAvoid) {
37
+ let name = `${prefix}${this.uniqueNameCounter}`;
38
+ while (namesToAvoid.some((names) => names.has(name))) {
39
+ this.uniqueNameCounter++;
40
+ name = `${prefix}${this.uniqueNameCounter}`;
41
41
  }
42
- this.exceptionCounter++;
42
+ this.uniqueNameCounter++;
43
43
  return name;
44
44
  }
45
- export function generateUniqueExceptionName(nameToAvoid) {
46
- let exceptionName = `__caught_exception_${this.exceptionCounter}`;
47
- while (exceptionName === nameToAvoid) {
48
- this.exceptionCounter++;
49
- exceptionName = `__caught_exception_${this.exceptionCounter}`;
45
+ export function generateUniqueExceptionName(exceptionNameToAvoid, ...otherNamesToAvoid) {
46
+ let prefix = `__caught_exception_`;
47
+ if (exceptionNameToAvoid) {
48
+ prefix += exceptionNameToAvoid;
50
49
  }
51
- this.exceptionCounter++;
52
- return exceptionName;
50
+ return this.generateUniqueName(prefix, ...otherNamesToAvoid);
53
51
  }
54
52
  export function getScopeForNode(node) {
55
53
  let current = node;
@@ -80,19 +78,49 @@ export function escapeString(str) {
80
78
  export function getJsVarName(node) {
81
79
  return `"${node.text}"`;
82
80
  }
83
- export function getDerefCode(nodeText, varName, typeInfo) {
81
+ export function getDerefCode(nodeText, varName, context, typeInfo) {
84
82
  // Make sure varName is incased in quotes
85
83
  if (!varName.startsWith('"'))
86
84
  varName = '"' + varName;
87
85
  if (!varName.endsWith('"'))
88
86
  varName = varName + '"';
89
- if (typeInfo && typeInfo.needsHeapAllocation) {
90
- return `jspp::Access::deref_ptr(${nodeText}, ${varName})`;
87
+ const symbolName = varName.slice(1).slice(0, -1);
88
+ const symbol = context.localScopeSymbols.get(symbolName) ??
89
+ context.topLevelScopeSymbols.get(symbolName);
90
+ const checkedIfUninitialized = symbol?.checkedIfUninitialized ||
91
+ false;
92
+ // Mark the symbol as checked
93
+ this.markSymbolAsChecked(symbolName, context.topLevelScopeSymbols, context.localScopeSymbols);
94
+ // Apply deref code
95
+ if (checkedIfUninitialized) {
96
+ if (typeInfo && typeInfo.needsHeapAllocation) {
97
+ return `(*${nodeText})`;
98
+ }
99
+ return `${nodeText}`;
100
+ }
101
+ else {
102
+ if (typeInfo && typeInfo.needsHeapAllocation) {
103
+ return `jspp::Access::deref_ptr(${nodeText}, ${varName})`;
104
+ }
105
+ return `jspp::Access::deref_stack(${nodeText}, ${varName})`;
106
+ }
107
+ }
108
+ export function markSymbolAsChecked(name, topLevel, local) {
109
+ if (topLevel.has(name)) {
110
+ topLevel.update(name, {
111
+ checkedIfUninitialized: true,
112
+ });
113
+ }
114
+ else if (local.has(name)) {
115
+ local.update(name, {
116
+ checkedIfUninitialized: true,
117
+ });
91
118
  }
92
- return `jspp::Access::deref_stack(${nodeText}, ${varName})`;
93
119
  }
94
120
  export function getReturnCommand(context) {
95
- return (context.isInsideGeneratorFunction || context.isInsideAsyncFunction) ? "co_return" : "return";
121
+ return (context.isInsideGeneratorFunction || context.isInsideAsyncFunction)
122
+ ? "co_return"
123
+ : "return";
96
124
  }
97
125
  export function hoistDeclaration(decl, hoistedSymbols) {
98
126
  const name = decl.name?.getText();
@@ -101,13 +129,16 @@ export function hoistDeclaration(decl, hoistedSymbols) {
101
129
  }
102
130
  const isLetOrConst = (decl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
103
131
  0;
132
+ // if (name === "letVal") console.log("hoistDeclaration: letVal isLetOrConst=", isLetOrConst, " flags=", decl.parent.flags);
104
133
  const symbolType = isLetOrConst
105
134
  ? DeclaredSymbolType.letOrConst
106
135
  : (ts.isFunctionDeclaration(decl)
107
136
  ? DeclaredSymbolType.function
108
- : (ts.isClassDeclaration(decl) ? DeclaredSymbolType.letOrConst : DeclaredSymbolType.var));
137
+ : (ts.isClassDeclaration(decl)
138
+ ? DeclaredSymbolType.letOrConst
139
+ : DeclaredSymbolType.var));
109
140
  if (hoistedSymbols.has(name)) {
110
- const existingType = hoistedSymbols.get(name);
141
+ const existingType = hoistedSymbols.get(name)?.type;
111
142
  // Don't allow multiple declaration of `letOrConst` or `function` or `class` variables
112
143
  if (existingType === DeclaredSymbolType.letOrConst ||
113
144
  existingType === DeclaredSymbolType.function ||
@@ -117,12 +148,15 @@ export function hoistDeclaration(decl, hoistedSymbols) {
117
148
  // `var` variables can be declared multiple times
118
149
  return "";
119
150
  }
120
- hoistedSymbols.set(name, symbolType);
151
+ hoistedSymbols.set(name, {
152
+ type: symbolType,
153
+ checkedIfUninitialized: false,
154
+ });
121
155
  const scope = this.getScopeForNode(decl);
122
156
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
123
157
  const initializer = isLetOrConst || ts.isClassDeclaration(decl)
124
- ? "jspp::AnyValue::make_uninitialized()"
125
- : "jspp::AnyValue::make_undefined()";
158
+ ? "jspp::Constants::UNINITIALIZED"
159
+ : "jspp::Constants::UNDEFINED";
126
160
  if (typeInfo.needsHeapAllocation) {
127
161
  return `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initializer});\n`;
128
162
  }
@@ -142,10 +176,73 @@ export function isAsyncFunction(node) {
142
176
  ts.isFunctionExpression(node) ||
143
177
  ts.isMethodDeclaration(node) ||
144
178
  ts.isArrowFunction(node)) &&
145
- (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Async) !== 0);
179
+ (ts.getCombinedModifierFlags(node) &
180
+ ts.ModifierFlags.Async) !== 0);
146
181
  }
147
182
  export function prepareScopeSymbolsForVisit(topLevel, local) {
148
- const newTopLevel = new Map(topLevel);
149
- local.forEach((v, k) => newTopLevel.set(k, v));
150
- return newTopLevel;
183
+ // Join the top and local scopes
184
+ return new DeclaredSymbols(topLevel, local);
185
+ }
186
+ export function collectFunctionScopedDeclarations(node) {
187
+ const decls = [];
188
+ function visit(n) {
189
+ if (ts.isVariableStatement(n)) {
190
+ const isLetOrConst = (n.declarationList.flags &
191
+ (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
192
+ if (!isLetOrConst) {
193
+ decls.push(...n.declarationList.declarations);
194
+ }
195
+ }
196
+ else if (ts.isForStatement(n)) {
197
+ if (n.initializer && ts.isVariableDeclarationList(n.initializer)) {
198
+ const isLetOrConst = (n.initializer.flags &
199
+ (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
200
+ if (!isLetOrConst) {
201
+ decls.push(...n.initializer.declarations);
202
+ }
203
+ }
204
+ }
205
+ else if (ts.isForInStatement(n)) {
206
+ if (ts.isVariableDeclarationList(n.initializer)) {
207
+ const isLetOrConst = (n.initializer.flags &
208
+ (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
209
+ if (!isLetOrConst) {
210
+ decls.push(...n.initializer.declarations);
211
+ }
212
+ }
213
+ }
214
+ else if (ts.isForOfStatement(n)) {
215
+ if (ts.isVariableDeclarationList(n.initializer)) {
216
+ const isLetOrConst = (n.initializer.flags &
217
+ (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
218
+ if (!isLetOrConst) {
219
+ decls.push(...n.initializer.declarations);
220
+ }
221
+ }
222
+ }
223
+ // Stop recursion at function boundaries (but not the root node if it is a function)
224
+ if (n !== node &&
225
+ (ts.isFunctionDeclaration(n) || ts.isFunctionExpression(n) ||
226
+ ts.isArrowFunction(n) || ts.isMethodDeclaration(n) ||
227
+ ts.isGetAccessor(n) || ts.isSetAccessor(n) ||
228
+ ts.isClassDeclaration(n))) {
229
+ return;
230
+ }
231
+ ts.forEachChild(n, visit);
232
+ }
233
+ ts.forEachChild(node, visit);
234
+ return decls;
235
+ }
236
+ export function collectBlockScopedDeclarations(statements) {
237
+ const decls = [];
238
+ for (const stmt of statements) {
239
+ if (ts.isVariableStatement(stmt)) {
240
+ const isLetOrConst = (stmt.declarationList.flags &
241
+ (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
242
+ if (isLetOrConst) {
243
+ decls.push(...stmt.declarationList.declarations);
244
+ }
245
+ }
246
+ }
247
+ return decls;
151
248
  }
@@ -1,13 +1,13 @@
1
- import { TypeAnalyzer } from "../../analysis/typeAnalyzer";
1
+ import { DeclaredSymbols } from "../../ast/symbols";
2
2
  import { generateLambda } from "./function-handlers";
3
- import { escapeString, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isGeneratorFunction, prepareScopeSymbolsForVisit, } from "./helpers";
3
+ import { escapeString, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isGeneratorFunction, markSymbolAsChecked, prepareScopeSymbolsForVisit, } from "./helpers";
4
4
  import { visit } from "./visitor";
5
- const CONTAINER_FUNCTION_NAME = "__container__";
5
+ const MODULE_NAME = "__main_function__";
6
6
  export class CodeGenerator {
7
7
  indentationLevel = 0;
8
8
  typeAnalyzer;
9
9
  globalThisVar;
10
- exceptionCounter = 0;
10
+ uniqueNameCounter = 0;
11
11
  // visitor
12
12
  visit = visit;
13
13
  // helpers
@@ -25,6 +25,7 @@ export class CodeGenerator {
25
25
  isGeneratorFunction = isGeneratorFunction;
26
26
  isAsyncFunction = isAsyncFunction;
27
27
  prepareScopeSymbolsForVisit = prepareScopeSymbolsForVisit;
28
+ markSymbolAsChecked = markSymbolAsChecked;
28
29
  // function handlers
29
30
  generateLambda = generateLambda;
30
31
  /**
@@ -34,7 +35,7 @@ export class CodeGenerator {
34
35
  this.typeAnalyzer = analyzer;
35
36
  this.globalThisVar = this.generateUniqueName("__this_val__", this.getDeclaredSymbols(ast));
36
37
  const declarations = `#include "index.hpp"\n\n`;
37
- let containerCode = `jspp::AnyValue ${CONTAINER_FUNCTION_NAME}() {\n`;
38
+ let containerCode = `jspp::JsPromise ${MODULE_NAME}() {\n`;
38
39
  this.indentationLevel++;
39
40
  containerCode +=
40
41
  `${this.indent()}jspp::AnyValue ${this.globalThisVar} = global;\n`;
@@ -42,24 +43,31 @@ export class CodeGenerator {
42
43
  isMainContext: true,
43
44
  isInsideFunction: true,
44
45
  isFunctionBody: true,
45
- topLevelScopeSymbols: new Map(),
46
- currentScopeSymbols: new Map(),
46
+ isInsideAsyncFunction: true,
47
+ topLevelScopeSymbols: new DeclaredSymbols(),
48
+ localScopeSymbols: new DeclaredSymbols(),
47
49
  });
48
50
  this.indentationLevel--;
49
- containerCode += " return jspp::AnyValue::make_undefined();\n";
51
+ containerCode += " co_return jspp::Constants::UNDEFINED;\n";
50
52
  containerCode += "}\n\n";
51
- let mainCode = "int main() {\n";
52
- // std::ios::sync_with_stdio(false); // Removed to fix console output buffering
53
- // std::cin.tie(nullptr); // Removed to fix console output buffering
53
+ let mainCode = "int main(int argc, char** argv) {\n";
54
54
  mainCode += ` try {\n`;
55
- mainCode += ` ${CONTAINER_FUNCTION_NAME}();\n`;
55
+ mainCode += ` jspp::setup_process_argv(argc, argv);\n`;
56
+ mainCode += ` auto p = ${MODULE_NAME}();\n`;
57
+ mainCode += ` p.then(nullptr, [](const jspp::AnyValue& err) {\n`;
58
+ mainCode +=
59
+ ` auto error = std::make_shared<jspp::AnyValue>(err);\n`;
60
+ mainCode +=
61
+ ` console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
62
+ mainCode += ` std::exit(1);\n`;
63
+ mainCode += ` });\n`;
56
64
  mainCode += ` jspp::Scheduler::instance().run();\n`;
57
65
  mainCode += ` } catch (const std::exception& ex) {\n`;
58
66
  mainCode +=
59
- " auto error = std::make_shared<jspp::AnyValue>(jspp::Exception::exception_to_any_value(ex));\n{\n";
67
+ " auto error = std::make_shared<jspp::AnyValue>(jspp::Exception::exception_to_any_value(ex));\n {\n";
60
68
  mainCode +=
61
- ` ([&](){ auto __obj = console; return __obj.get_own_property("error").as_function("console.error")->call(__obj, {*error}); })();\n`;
62
- mainCode += ` return 1;\n}\n`;
69
+ ` console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
70
+ mainCode += ` return 1;\n }\n`;
63
71
  mainCode += ` }\n`;
64
72
  mainCode += " return 0;\n}";
65
73
  return declarations + containerCode + mainCode;