@ugo-studio/jspp 0.1.3 → 0.1.5

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