@ugo-studio/jspp 0.1.4 → 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 (68) hide show
  1. package/dist/analysis/scope.js +17 -0
  2. package/dist/analysis/typeAnalyzer.js +7 -1
  3. package/dist/ast/symbols.js +29 -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 +31 -21
  12. package/dist/core/codegen/declaration-handlers.js +10 -6
  13. package/dist/core/codegen/expression-handlers.js +202 -60
  14. package/dist/core/codegen/function-handlers.js +179 -70
  15. package/dist/core/codegen/helpers.js +107 -17
  16. package/dist/core/codegen/index.js +9 -8
  17. package/dist/core/codegen/literal-handlers.js +15 -6
  18. package/dist/core/codegen/statement-handlers.js +67 -53
  19. package/dist/core/codegen/visitor.js +3 -1
  20. package/package.json +1 -1
  21. package/src/prelude/any_value.hpp +195 -342
  22. package/src/prelude/any_value_access.hpp +78 -30
  23. package/src/prelude/any_value_defines.hpp +74 -35
  24. package/src/prelude/any_value_helpers.hpp +73 -180
  25. package/src/prelude/exception.hpp +1 -0
  26. package/src/prelude/exception_helpers.hpp +4 -4
  27. package/src/prelude/index.hpp +9 -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 +53 -43
  38. package/src/prelude/library/symbol.hpp +45 -57
  39. package/src/prelude/library/timer.hpp +6 -6
  40. package/src/prelude/types.hpp +48 -0
  41. package/src/prelude/utils/access.hpp +182 -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/descriptors.hpp +2 -2
  51. package/src/prelude/values/function.hpp +71 -62
  52. package/src/prelude/values/helpers/array.hpp +64 -28
  53. package/src/prelude/values/helpers/function.hpp +77 -92
  54. package/src/prelude/values/helpers/iterator.hpp +3 -3
  55. package/src/prelude/values/helpers/object.hpp +54 -9
  56. package/src/prelude/values/helpers/promise.hpp +3 -3
  57. package/src/prelude/values/iterator.hpp +1 -1
  58. package/src/prelude/values/object.hpp +10 -3
  59. package/src/prelude/values/promise.hpp +3 -3
  60. package/src/prelude/values/prototypes/array.hpp +851 -12
  61. package/src/prelude/values/prototypes/function.hpp +2 -2
  62. package/src/prelude/values/prototypes/iterator.hpp +5 -5
  63. package/src/prelude/values/prototypes/number.hpp +153 -0
  64. package/src/prelude/values/prototypes/object.hpp +2 -2
  65. package/src/prelude/values/prototypes/promise.hpp +40 -30
  66. package/src/prelude/values/prototypes/string.hpp +28 -28
  67. package/src/prelude/values/prototypes/symbol.hpp +20 -3
  68. 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 || "[=]";
@@ -15,72 +17,162 @@ export function generateLambda(node, context, options) {
15
17
  ? "jspp::JsIterator<jspp::AnyValue>"
16
18
  : (isInsideAsyncFunction ? "jspp::JsPromise" : "jspp::AnyValue");
17
19
  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>&";
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>";
26
24
  const thisArgParam = isArrow
27
- ? "const jspp::AnyValue&" // Arrow functions are never generators in this parser
25
+ ? "const jspp::AnyValue&" // Arrow functions use captured 'this' or are never generators in this parser context
28
26
  : `${paramThisType} ${this.globalThisVar}`;
29
27
  let lambda = `${capture}(${thisArgParam}, ${paramArgsType} ${argsName}) mutable -> ${funcReturnType} `;
30
- const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.currentScopeSymbols);
28
+ const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.localScopeSymbols);
31
29
  const visitContext = {
32
30
  isMainContext: false,
33
31
  isInsideFunction: true,
34
32
  isFunctionBody: false,
35
33
  lambdaName: undefined,
36
34
  topLevelScopeSymbols,
37
- currentScopeSymbols: new Map(),
35
+ localScopeSymbols: new DeclaredSymbols(),
38
36
  superClassVar: context.superClassVar,
39
37
  };
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`;
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`;
78
125
  }
79
126
  else {
80
- paramExtraction +=
81
- `${this.indent()}auto ${name} = ${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue};\n`;
127
+ code +=
128
+ `${this.indent()}jspp::AnyValue ${name} = jspp::Constants::UNDEFINED;\n`;
82
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;
166
+ };
167
+ if (node.body) {
168
+ if (ts.isBlock(node.body)) {
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);
83
173
  });
174
+ this.indentationLevel++;
175
+ const paramExtraction = paramExtractor(node.parameters);
84
176
  this.indentationLevel--;
85
177
  const blockContent = this.visit(node.body, {
86
178
  ...visitContext,
@@ -90,20 +182,14 @@ export function generateLambda(node, context, options) {
90
182
  isInsideGeneratorFunction: isInsideGeneratorFunction,
91
183
  isInsideAsyncFunction: isInsideAsyncFunction,
92
184
  });
93
- // The block visitor already adds braces, so we need to inject the param extraction.
94
- 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);
95
187
  }
96
188
  else {
97
189
  lambda += "{\n";
98
190
  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
- });
191
+ lambda += preamble;
192
+ lambda += paramExtractor(node.parameters);
107
193
  lambda += `${this.indent()}${returnCmd} ${this.visit(node.body, {
108
194
  ...visitContext,
109
195
  isMainContext: false,
@@ -111,14 +197,13 @@ export function generateLambda(node, context, options) {
111
197
  isFunctionBody: false,
112
198
  isInsideGeneratorFunction: isInsideGeneratorFunction,
113
199
  isInsideAsyncFunction: isInsideAsyncFunction,
114
- })};
115
- `;
200
+ })};\n`;
116
201
  this.indentationLevel--;
117
202
  lambda += `${this.indent()}}`;
118
203
  }
119
204
  }
120
205
  else {
121
- lambda += `{ ${returnCmd} jspp::AnyValue::make_undefined(); }\n`;
206
+ lambda += `{ ${preamble} ${returnCmd} jspp::Constants::UNDEFINED; }\n`;
122
207
  }
123
208
  let signature = "";
124
209
  let callable = "";
@@ -126,19 +211,19 @@ export function generateLambda(node, context, options) {
126
211
  // Handle generator function
127
212
  if (isInsideGeneratorFunction) {
128
213
  signature =
129
- "jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, const std::vector<jspp::AnyValue>&)";
214
+ "jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
130
215
  callable = `std::function<${signature}>(${lambda})`;
131
216
  method = `jspp::AnyValue::make_generator`;
132
217
  } // Handle async function
133
218
  else if (isInsideAsyncFunction) {
134
219
  signature =
135
- "jspp::JsPromise(const jspp::AnyValue&, const std::vector<jspp::AnyValue>&)";
220
+ "jspp::JsPromise(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
136
221
  callable = `std::function<${signature}>(${lambda})`;
137
222
  method = `jspp::AnyValue::make_async_function`;
138
223
  } // Handle normal function
139
224
  else {
140
225
  signature =
141
- `jspp::AnyValue(const jspp::AnyValue&, const std::vector<jspp::AnyValue>&)`;
226
+ `jspp::AnyValue(const jspp::AnyValue&, std::span<const jspp::AnyValue>)`;
142
227
  callable = `std::function<${signature}>(${lambda})`;
143
228
  if (options?.isClass) {
144
229
  method = `jspp::AnyValue::make_class`;
@@ -147,9 +232,33 @@ export function generateLambda(node, context, options) {
147
232
  method = `jspp::AnyValue::make_function`;
148
233
  }
149
234
  }
150
- const funcName = node.name?.getText();
151
- const fullExpression = `${method}(${callable}, "${context?.lambdaName || funcName || ""}")`;
152
- if (ts.isFunctionDeclaration(node) && !isAssignment && funcName) {
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
+ }
258
+ }
259
+ const fullExpression = `${method}(${args})`;
260
+ if (ts.isFunctionDeclaration(node) && !isAssignment && node.name) {
261
+ const funcName = node.name?.getText();
153
262
  return `${this.indent()}auto ${funcName} = ${fullExpression};\n`;
154
263
  }
155
264
  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,9 +33,9 @@ export function getDeclaredSymbols(node) {
33
33
  visitor(node);
34
34
  return symbols;
35
35
  }
36
- export function generateUniqueName(prefix, namesToAvoid) {
36
+ export function generateUniqueName(prefix, ...namesToAvoid) {
37
37
  let name = `${prefix}${this.exceptionCounter}`;
38
- while (namesToAvoid.has(name)) {
38
+ while (namesToAvoid.some((names) => names.has(name))) {
39
39
  this.exceptionCounter++;
40
40
  name = `${prefix}${this.exceptionCounter}`;
41
41
  }
@@ -80,19 +80,49 @@ export function escapeString(str) {
80
80
  export function getJsVarName(node) {
81
81
  return `"${node.text}"`;
82
82
  }
83
- export function getDerefCode(nodeText, varName, typeInfo) {
83
+ export function getDerefCode(nodeText, varName, context, typeInfo) {
84
84
  // Make sure varName is incased in quotes
85
85
  if (!varName.startsWith('"'))
86
86
  varName = '"' + varName;
87
87
  if (!varName.endsWith('"'))
88
88
  varName = varName + '"';
89
- if (typeInfo && typeInfo.needsHeapAllocation) {
90
- return `jspp::Access::deref_ptr(${nodeText}, ${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
+ });
91
120
  }
92
- return `jspp::Access::deref_stack(${nodeText}, ${varName})`;
93
121
  }
94
122
  export function getReturnCommand(context) {
95
- return (context.isInsideGeneratorFunction || context.isInsideAsyncFunction) ? "co_return" : "return";
123
+ return (context.isInsideGeneratorFunction || context.isInsideAsyncFunction)
124
+ ? "co_return"
125
+ : "return";
96
126
  }
97
127
  export function hoistDeclaration(decl, hoistedSymbols) {
98
128
  const name = decl.name?.getText();
@@ -101,13 +131,16 @@ export function hoistDeclaration(decl, hoistedSymbols) {
101
131
  }
102
132
  const isLetOrConst = (decl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
103
133
  0;
134
+ // if (name === "letVal") console.log("hoistDeclaration: letVal isLetOrConst=", isLetOrConst, " flags=", decl.parent.flags);
104
135
  const symbolType = isLetOrConst
105
136
  ? DeclaredSymbolType.letOrConst
106
137
  : (ts.isFunctionDeclaration(decl)
107
138
  ? DeclaredSymbolType.function
108
- : (ts.isClassDeclaration(decl) ? DeclaredSymbolType.letOrConst : DeclaredSymbolType.var));
139
+ : (ts.isClassDeclaration(decl)
140
+ ? DeclaredSymbolType.letOrConst
141
+ : DeclaredSymbolType.var));
109
142
  if (hoistedSymbols.has(name)) {
110
- const existingType = hoistedSymbols.get(name);
143
+ const existingType = hoistedSymbols.get(name)?.type;
111
144
  // Don't allow multiple declaration of `letOrConst` or `function` or `class` variables
112
145
  if (existingType === DeclaredSymbolType.letOrConst ||
113
146
  existingType === DeclaredSymbolType.function ||
@@ -117,12 +150,15 @@ export function hoistDeclaration(decl, hoistedSymbols) {
117
150
  // `var` variables can be declared multiple times
118
151
  return "";
119
152
  }
120
- hoistedSymbols.set(name, symbolType);
153
+ hoistedSymbols.set(name, {
154
+ type: symbolType,
155
+ checkedIfUninitialized: false,
156
+ });
121
157
  const scope = this.getScopeForNode(decl);
122
158
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
123
159
  const initializer = isLetOrConst || ts.isClassDeclaration(decl)
124
- ? "jspp::AnyValue::make_uninitialized()"
125
- : "jspp::AnyValue::make_undefined()";
160
+ ? "jspp::Constants::UNINITIALIZED"
161
+ : "jspp::Constants::UNDEFINED";
126
162
  if (typeInfo.needsHeapAllocation) {
127
163
  return `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initializer});\n`;
128
164
  }
@@ -142,10 +178,64 @@ export function isAsyncFunction(node) {
142
178
  ts.isFunctionExpression(node) ||
143
179
  ts.isMethodDeclaration(node) ||
144
180
  ts.isArrowFunction(node)) &&
145
- (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Async) !== 0);
181
+ (ts.getCombinedModifierFlags(node) &
182
+ ts.ModifierFlags.Async) !== 0);
146
183
  }
147
184
  export function prepareScopeSymbolsForVisit(topLevel, local) {
148
- const newTopLevel = new Map(topLevel);
149
- local.forEach((v, k) => newTopLevel.set(k, v));
150
- return newTopLevel;
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;
151
241
  }
@@ -1,6 +1,7 @@
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, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isGeneratorFunction, prepareScopeSymbolsForVisit, } 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 {
@@ -25,6 +26,7 @@ export class CodeGenerator {
25
26
  isGeneratorFunction = isGeneratorFunction;
26
27
  isAsyncFunction = isAsyncFunction;
27
28
  prepareScopeSymbolsForVisit = prepareScopeSymbolsForVisit;
29
+ markSymbolAsChecked = markSymbolAsChecked;
28
30
  // function handlers
29
31
  generateLambda = generateLambda;
30
32
  /**
@@ -42,23 +44,22 @@ export class CodeGenerator {
42
44
  isMainContext: true,
43
45
  isInsideFunction: true,
44
46
  isFunctionBody: true,
45
- topLevelScopeSymbols: new Map(),
46
- currentScopeSymbols: new Map(),
47
+ topLevelScopeSymbols: new DeclaredSymbols(),
48
+ localScopeSymbols: new DeclaredSymbols(),
47
49
  });
48
50
  this.indentationLevel--;
49
- containerCode += " return jspp::AnyValue::make_undefined();\n";
51
+ containerCode += " 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 += ` jspp::setup_process_argv(argc, argv);\n`;
55
56
  mainCode += ` ${CONTAINER_FUNCTION_NAME}();\n`;
56
57
  mainCode += ` jspp::Scheduler::instance().run();\n`;
57
58
  mainCode += ` } catch (const std::exception& ex) {\n`;
58
59
  mainCode +=
59
60
  " auto error = std::make_shared<jspp::AnyValue>(jspp::Exception::exception_to_any_value(ex));\n{\n";
60
61
  mainCode +=
61
- ` ([&](){ auto __obj = console; return __obj.get_own_property("error").as_function("console.error")->call(__obj, {*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`;
62
63
  mainCode += ` return 1;\n}\n`;
63
64
  mainCode += ` }\n`;
64
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,16 +24,20 @@ export function visitNoSubstitutionTemplateLiteral(node) {
19
24
  return `jspp::AnyValue::make_string("${this.escapeString(node.text)}")`;
20
25
  }
21
26
  export function visitTrueKeyword() {
22
- return "jspp::AnyValue::make_boolean(true)";
27
+ // return "jspp::AnyValue::make_boolean(true)";
28
+ return "jspp::Constants::TRUE";
23
29
  }
24
30
  export function visitFalseKeyword() {
25
- return "jspp::AnyValue::make_boolean(false)";
31
+ // return "jspp::AnyValue::make_boolean(false)";
32
+ return "jspp::Constants::FALSE";
26
33
  }
27
34
  export function visitUndefinedKeyword() {
28
- return "jspp::AnyValue::make_undefined()";
35
+ // return "jspp::AnyValue::make_undefined()";
36
+ return "jspp::Constants::UNDEFINED";
29
37
  }
30
38
  export function visitNullKeyword() {
31
- return "jspp::AnyValue::make_null()";
39
+ // return "jspp::AnyValue::make_null()";
40
+ return "jspp::Constants::Null";
32
41
  }
33
42
  export function visitThisKeyword() {
34
43
  return `${this.globalThisVar}`;