@ugo-studio/jspp 0.2.5 → 0.2.7

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 (54) hide show
  1. package/README.md +51 -36
  2. package/dist/analysis/scope.js +7 -0
  3. package/dist/analysis/typeAnalyzer.js +96 -43
  4. package/dist/ast/symbols.js +34 -24
  5. package/dist/cli/args.js +59 -0
  6. package/dist/cli/colors.js +9 -0
  7. package/dist/cli/file-utils.js +20 -0
  8. package/dist/cli/index.js +160 -0
  9. package/dist/cli/spinner.js +55 -0
  10. package/dist/core/codegen/class-handlers.js +8 -8
  11. package/dist/core/codegen/control-flow-handlers.js +19 -9
  12. package/dist/core/codegen/declaration-handlers.js +30 -10
  13. package/dist/core/codegen/expression-handlers.js +649 -161
  14. package/dist/core/codegen/function-handlers.js +107 -103
  15. package/dist/core/codegen/helpers.js +61 -14
  16. package/dist/core/codegen/index.js +13 -9
  17. package/dist/core/codegen/literal-handlers.js +4 -2
  18. package/dist/core/codegen/statement-handlers.js +147 -55
  19. package/dist/core/codegen/visitor.js +22 -2
  20. package/dist/core/constants.js +16 -0
  21. package/dist/core/error.js +58 -0
  22. package/dist/index.js +6 -3
  23. package/package.json +3 -3
  24. package/src/prelude/any_value.hpp +89 -59
  25. package/src/prelude/any_value_access.hpp +1 -1
  26. package/src/prelude/any_value_helpers.hpp +85 -43
  27. package/src/prelude/index.hpp +1 -0
  28. package/src/prelude/library/array.hpp +3 -2
  29. package/src/prelude/scheduler.hpp +144 -144
  30. package/src/prelude/types.hpp +8 -8
  31. package/src/prelude/utils/access.hpp +62 -6
  32. package/src/prelude/utils/assignment_operators.hpp +14 -14
  33. package/src/prelude/utils/log_any_value/array.hpp +0 -15
  34. package/src/prelude/utils/log_any_value/object.hpp +12 -10
  35. package/src/prelude/utils/log_any_value/primitives.hpp +2 -0
  36. package/src/prelude/utils/operators.hpp +117 -474
  37. package/src/prelude/utils/operators_primitive.hpp +337 -0
  38. package/src/prelude/values/helpers/array.hpp +4 -4
  39. package/src/prelude/values/helpers/async_iterator.hpp +2 -2
  40. package/src/prelude/values/helpers/function.hpp +3 -3
  41. package/src/prelude/values/helpers/iterator.hpp +2 -2
  42. package/src/prelude/values/helpers/object.hpp +3 -3
  43. package/src/prelude/values/helpers/promise.hpp +1 -1
  44. package/src/prelude/values/helpers/string.hpp +1 -1
  45. package/src/prelude/values/helpers/symbol.hpp +1 -1
  46. package/src/prelude/values/prototypes/array.hpp +1125 -853
  47. package/src/prelude/values/prototypes/async_iterator.hpp +32 -14
  48. package/src/prelude/values/prototypes/function.hpp +30 -18
  49. package/src/prelude/values/prototypes/iterator.hpp +40 -17
  50. package/src/prelude/values/prototypes/number.hpp +119 -62
  51. package/src/prelude/values/prototypes/object.hpp +10 -4
  52. package/src/prelude/values/prototypes/promise.hpp +167 -109
  53. package/src/prelude/values/prototypes/string.hpp +407 -231
  54. package/src/prelude/values/prototypes/symbol.hpp +45 -23
@@ -1,5 +1,55 @@
1
1
  import ts from "typescript";
2
+ import { constants } from "../constants.js";
3
+ import { CompilerError } from "../error.js";
2
4
  import { CodeGenerator } from "./index.js";
5
+ /**
6
+ * Helper to recursively flatten static spread elements in arrays or call arguments.
7
+ */
8
+ function flattenArrayElements(elements) {
9
+ const flattened = [];
10
+ for (const elem of elements) {
11
+ if (elem && ts.isSpreadElement(elem)) {
12
+ const expr = elem.expression;
13
+ if (ts.isArrayLiteralExpression(expr)) {
14
+ flattened.push(...flattenArrayElements(expr.elements));
15
+ }
16
+ else if (ts.isStringLiteral(expr) ||
17
+ ts.isNoSubstitutionTemplateLiteral(expr)) {
18
+ for (const char of expr.text) {
19
+ flattened.push(ts.factory.createStringLiteral(char));
20
+ }
21
+ }
22
+ else {
23
+ flattened.push({ dynamicSpread: expr });
24
+ }
25
+ }
26
+ else {
27
+ flattened.push(elem);
28
+ }
29
+ }
30
+ return flattened;
31
+ }
32
+ /**
33
+ * Helper to recursively flatten static spread assignments in object literals.
34
+ */
35
+ function flattenObjectProperties(properties) {
36
+ const flattened = [];
37
+ for (const prop of properties) {
38
+ if (ts.isSpreadAssignment(prop)) {
39
+ const expr = prop.expression;
40
+ if (ts.isObjectLiteralExpression(expr)) {
41
+ flattened.push(...flattenObjectProperties(expr.properties));
42
+ }
43
+ else {
44
+ flattened.push(prop);
45
+ }
46
+ }
47
+ else {
48
+ flattened.push(prop);
49
+ }
50
+ }
51
+ return flattened;
52
+ }
3
53
  export function visitObjectPropertyName(node, context) {
4
54
  if (ts.isNumericLiteral(node)) {
5
55
  return context.isBracketNotationPropertyAccess
@@ -27,11 +77,12 @@ export function visitObjectPropertyName(node, context) {
27
77
  }
28
78
  export function visitObjectLiteralExpression(node, context) {
29
79
  const obj = node;
80
+ const properties = flattenObjectProperties(obj.properties);
30
81
  const objVar = this.generateUniqueName("__obj_", this.getDeclaredSymbols(node));
31
- if (!obj.properties.some((prop) => ts.isPropertyAssignment(prop) ||
82
+ if (!properties.some((prop) => ts.isPropertyAssignment(prop) ||
32
83
  ts.isShorthandPropertyAssignment(prop) ||
33
84
  ts.isMethodDeclaration(prop) || ts.isGetAccessor(prop) ||
34
- ts.isSetAccessor(prop))) {
85
+ ts.isSetAccessor(prop) || ts.isSpreadAssignment(prop))) {
35
86
  // Empty object
36
87
  return `jspp::AnyValue::make_object_with_proto({}, ::Object.get_own_property("prototype"))`;
37
88
  }
@@ -39,7 +90,7 @@ export function visitObjectLiteralExpression(node, context) {
39
90
  code +=
40
91
  `${this.indent()} auto ${objVar} = jspp::AnyValue::make_object_with_proto({}, ::Object.get_own_property("prototype"));\n`;
41
92
  this.indentationLevel++;
42
- for (const prop of obj.properties) {
93
+ for (const prop of properties) {
43
94
  if (ts.isPropertyAssignment(prop)) {
44
95
  const key = visitObjectPropertyName.call(this, prop.name, {
45
96
  ...context,
@@ -76,10 +127,10 @@ export function visitObjectLiteralExpression(node, context) {
76
127
  ...context,
77
128
  isObjectLiteralExpression: true,
78
129
  });
79
- const lambda = this.generateLambda(prop, {
130
+ const lambda = this.generateWrappedLambda(this.generateLambdaComponents(prop, {
80
131
  ...context,
81
132
  isInsideFunction: true,
82
- });
133
+ }));
83
134
  code +=
84
135
  `${this.indent()}${objVar}.define_data_property(${key}, ${lambda});\n`;
85
136
  }
@@ -88,10 +139,10 @@ export function visitObjectLiteralExpression(node, context) {
88
139
  ...context,
89
140
  isObjectLiteralExpression: true,
90
141
  });
91
- const lambda = this.generateLambda(prop, {
142
+ const lambda = this.generateWrappedLambda(this.generateLambdaComponents(prop, {
92
143
  ...context,
93
144
  isInsideFunction: true,
94
- });
145
+ }));
95
146
  code +=
96
147
  `${this.indent()}${objVar}.define_getter(${key}, ${lambda});\n`;
97
148
  }
@@ -100,36 +151,96 @@ export function visitObjectLiteralExpression(node, context) {
100
151
  ...context,
101
152
  isObjectLiteralExpression: true,
102
153
  });
103
- const lambda = this.generateLambda(prop, {
154
+ const lambda = this.generateWrappedLambda(this.generateLambdaComponents(prop, {
104
155
  ...context,
105
156
  isInsideFunction: true,
106
- });
157
+ }));
107
158
  code +=
108
159
  `${this.indent()}${objVar}.define_setter(${key}, ${lambda});\n`;
109
160
  }
161
+ else if (ts.isSpreadAssignment(prop)) {
162
+ let spreadExpr = this.visit(prop.expression, context);
163
+ if (ts.isIdentifier(prop.expression)) {
164
+ const scope = this.getScopeForNode(prop.expression);
165
+ const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(prop.expression.text, scope);
166
+ if (typeInfo && !typeInfo.isBuiltin && !typeInfo.isParameter) {
167
+ spreadExpr = this.getDerefCode(spreadExpr, this.getJsVarName(prop.expression), context, typeInfo);
168
+ }
169
+ }
170
+ code +=
171
+ `${this.indent()}jspp::Access::spread_object(${objVar}, ${spreadExpr});\n`;
172
+ }
110
173
  }
111
174
  this.indentationLevel--;
112
175
  code += `${this.indent()} return ${objVar};\n${this.indent()}})()`;
113
176
  return code;
114
177
  }
115
178
  export function visitArrayLiteralExpression(node, context) {
116
- const elements = node.elements
117
- .map((elem) => {
118
- let elemText = this.visit(elem, context);
119
- if (ts.isIdentifier(elem)) {
120
- const scope = this.getScopeForNode(elem);
121
- const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(elem.text, scope);
122
- if (typeInfo && !typeInfo.isBuiltin && !typeInfo.isParameter) {
123
- elemText = this.getDerefCode(elemText, this.getJsVarName(elem), context, typeInfo);
179
+ const flattened = flattenArrayElements(node.elements);
180
+ const hasSpread = flattened.some((e) => typeof e === "object" && "dynamicSpread" in e);
181
+ if (!hasSpread) {
182
+ const elements = flattened
183
+ .map((elem) => {
184
+ const expr = elem;
185
+ let elemText = this.visit(expr, context);
186
+ if (ts.isIdentifier(expr)) {
187
+ const scope = this.getScopeForNode(expr);
188
+ const typeInfo = this.typeAnalyzer.scopeManager
189
+ .lookupFromScope(expr.text, scope);
190
+ if (typeInfo && !typeInfo.isBuiltin && !typeInfo.isParameter) {
191
+ elemText = this.getDerefCode(elemText, this.getJsVarName(expr), context, typeInfo);
192
+ }
193
+ }
194
+ if (ts.isOmittedExpression(expr)) {
195
+ elemText = "jspp::Constants::UNINITIALIZED";
196
+ }
197
+ return elemText;
198
+ });
199
+ const elementsJoined = elements.join(", ");
200
+ const elementsSpan = elements.length > 0
201
+ ? `std::span<const jspp::AnyValue>((const jspp::AnyValue[]){${elementsJoined}}, ${elements.length})`
202
+ : "std::span<const jspp::AnyValue>{}";
203
+ return `jspp::AnyValue::make_array_with_proto(${elementsSpan}, ::Array.get_own_property("prototype"))`;
204
+ }
205
+ const arrVar = this.generateUniqueName("__arr_", this.getDeclaredSymbols(node));
206
+ let code = `([&]() {\n`;
207
+ code += `${this.indent()} std::vector<jspp::AnyValue> ${arrVar};\n`;
208
+ this.indentationLevel++;
209
+ for (const elem of flattened) {
210
+ if (typeof elem === "object" && "dynamicSpread" in elem) {
211
+ const spreadExprSource = elem.dynamicSpread;
212
+ let spreadExpr = this.visit(spreadExprSource, context);
213
+ if (ts.isIdentifier(spreadExprSource)) {
214
+ const scope = this.getScopeForNode(spreadExprSource);
215
+ const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(spreadExprSource.text, scope);
216
+ if (typeInfo && !typeInfo.isBuiltin && !typeInfo.isParameter) {
217
+ spreadExpr = this.getDerefCode(spreadExpr, this.getJsVarName(spreadExprSource), context, typeInfo);
218
+ }
219
+ }
220
+ code +=
221
+ `${this.indent()}jspp::Access::spread_array(${arrVar}, ${spreadExpr});\n`;
222
+ }
223
+ else {
224
+ const expr = elem;
225
+ let elemText = this.visit(expr, context);
226
+ if (ts.isIdentifier(expr)) {
227
+ const scope = this.getScopeForNode(expr);
228
+ const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(expr.text, scope);
229
+ if (typeInfo && !typeInfo.isBuiltin && !typeInfo.isParameter) {
230
+ elemText = this.getDerefCode(elemText, this.getJsVarName(expr), context, typeInfo);
231
+ }
232
+ }
233
+ if (ts.isOmittedExpression(expr)) {
234
+ elemText = "jspp::Constants::UNINITIALIZED";
124
235
  }
236
+ code += `${this.indent()}${arrVar}.push_back(${elemText});\n`;
125
237
  }
126
- return elemText;
127
- });
128
- const elementsJoined = elements.join(", ");
129
- const elementsSpan = elements.length > 0
130
- ? `std::span<const jspp::AnyValue>((const jspp::AnyValue[]){${elementsJoined}}, ${elements.length})`
131
- : "std::span<const jspp::AnyValue>{}";
132
- return `jspp::AnyValue::make_array_with_proto(${elementsSpan}, ::Array.get_own_property("prototype"))`;
238
+ }
239
+ this.indentationLevel--;
240
+ code +=
241
+ `${this.indent()} return jspp::AnyValue::make_array_with_proto(std::move(${arrVar}), ::Array.get_own_property("prototype"));\n`;
242
+ code += `${this.indent()}})()`;
243
+ return code;
133
244
  }
134
245
  export function visitPrefixUnaryExpression(node, context) {
135
246
  const prefixUnaryExpr = node;
@@ -149,6 +260,15 @@ export function visitPrefixUnaryExpression(node, context) {
149
260
  }
150
261
  return `${operator}(${target})`;
151
262
  }
263
+ if (operator === "+") {
264
+ return `jspp::plus(${operand})`;
265
+ }
266
+ if (operator === "-") {
267
+ return `jspp::negate(${operand})`;
268
+ }
269
+ if (operator === "!") {
270
+ return `jspp::logical_not(${operand})`;
271
+ }
152
272
  if (operator === "~") {
153
273
  let target = operand;
154
274
  if (ts.isIdentifier(prefixUnaryExpr.operand)) {
@@ -161,7 +281,7 @@ export function visitPrefixUnaryExpression(node, context) {
161
281
  target = `*${operand}`;
162
282
  }
163
283
  }
164
- return `${operator}(${target})`;
284
+ return `jspp::bitwise_not(${target})`;
165
285
  }
166
286
  return `${operator}${operand}`;
167
287
  }
@@ -190,7 +310,7 @@ export function visitPropertyAccessExpression(node, context) {
190
310
  const propAccess = node;
191
311
  if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
192
312
  if (!context.superClassVar) {
193
- throw new Error("super.prop accessed but no super class variable found in context");
313
+ throw new CompilerError("super.prop accessed but no super class variable found in context", propAccess.expression, "SyntaxError");
194
314
  }
195
315
  const propName = propAccess.name.getText();
196
316
  return `jspp::AnyValue::resolve_property_for_read((${context.superClassVar}).get_own_property("prototype").get_own_property("${propName}"), ${this.globalThisVar}, "${this.escapeString(propName)}")`;
@@ -213,7 +333,7 @@ export function visitPropertyAccessExpression(node, context) {
213
333
  finalExpr = this.getDerefCode(exprText, this.getJsVarName(propAccess.expression), context, typeInfo);
214
334
  }
215
335
  if (propAccess.questionDotToken) {
216
- return `jspp::Access::optional_get_property(${finalExpr}, "${propName}")`;
336
+ return `jspp::Access::get_optional_property(${finalExpr}, "${propName}")`;
217
337
  }
218
338
  return `${finalExpr}.get_own_property("${propName}")`;
219
339
  }
@@ -251,14 +371,18 @@ export function visitElementAccessExpression(node, context) {
251
371
  }
252
372
  }
253
373
  if (elemAccess.questionDotToken) {
254
- return `jspp::Access::optional_get_element(${finalExpr}, ${argText})`;
374
+ return `jspp::Access::get_optional_element(${finalExpr}, ${argText})`;
255
375
  }
256
376
  return `${finalExpr}.get_own_property(${argText})`;
257
377
  }
258
378
  export function visitBinaryExpression(node, context) {
259
379
  const binExpr = node;
260
380
  const opToken = binExpr.operatorToken;
261
- let op = opToken.getText();
381
+ const op = opToken.getText();
382
+ const visitContext = {
383
+ ...context,
384
+ supportedNativeLiterals: undefined,
385
+ };
262
386
  const assignmentOperators = [
263
387
  ts.SyntaxKind.PlusEqualsToken,
264
388
  ts.SyntaxKind.MinusEqualsToken,
@@ -279,8 +403,8 @@ export function visitBinaryExpression(node, context) {
279
403
  if (assignmentOperators.includes(opToken.kind)) {
280
404
  if (opToken.kind ===
281
405
  ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken) {
282
- const leftText = this.visit(binExpr.left, context);
283
- const rightText = this.visit(binExpr.right, context);
406
+ const leftText = this.visit(binExpr.left, visitContext);
407
+ const rightText = this.visit(binExpr.right, visitContext);
284
408
  let target = leftText;
285
409
  if (ts.isIdentifier(binExpr.left)) {
286
410
  const scope = this.getScopeForNode(binExpr.left);
@@ -292,8 +416,8 @@ export function visitBinaryExpression(node, context) {
292
416
  }
293
417
  }
294
418
  if (opToken.kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken) {
295
- const leftText = this.visit(binExpr.left, context);
296
- const rightText = this.visit(binExpr.right, context);
419
+ const leftText = this.visit(binExpr.left, visitContext);
420
+ const rightText = this.visit(binExpr.right, visitContext);
297
421
  let target = leftText;
298
422
  if (ts.isIdentifier(binExpr.left)) {
299
423
  const scope = this.getScopeForNode(binExpr.left);
@@ -308,8 +432,8 @@ export function visitBinaryExpression(node, context) {
308
432
  if (opToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
309
433
  opToken.kind === ts.SyntaxKind.BarBarEqualsToken ||
310
434
  opToken.kind === ts.SyntaxKind.QuestionQuestionEqualsToken) {
311
- const leftText = this.visit(binExpr.left, context);
312
- const rightText = this.visit(binExpr.right, context);
435
+ const leftText = this.visit(binExpr.left, visitContext);
436
+ const rightText = this.visit(binExpr.right, visitContext);
313
437
  let target = leftText;
314
438
  if (ts.isIdentifier(binExpr.left)) {
315
439
  const scope = this.getScopeForNode(binExpr.left);
@@ -328,21 +452,21 @@ export function visitBinaryExpression(node, context) {
328
452
  }
329
453
  }
330
454
  }
331
- const leftText = this.visit(binExpr.left, context);
455
+ const leftText = this.visit(binExpr.left, visitContext);
332
456
  let rightText = ts.isNumericLiteral(binExpr.right)
333
457
  ? binExpr.right.getText()
334
- : this.visit(binExpr.right, context);
458
+ : this.visit(binExpr.right, visitContext);
335
459
  if (ts.isIdentifier(binExpr.right)) {
336
460
  const scope = this.getScopeForNode(binExpr.right);
337
461
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.right.getText(), scope);
338
- rightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), context, typeInfo);
462
+ rightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, typeInfo);
339
463
  }
340
464
  let target = leftText;
341
465
  if (ts.isIdentifier(binExpr.left)) {
342
466
  const scope = this.getScopeForNode(binExpr.left);
343
467
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.getText(), scope);
344
468
  if (context.derefBeforeAssignment) {
345
- target = this.getDerefCode(leftText, leftText, context, typeInfo);
469
+ target = this.getDerefCode(leftText, leftText, visitContext, typeInfo);
346
470
  }
347
471
  else if (typeInfo.needsHeapAllocation) {
348
472
  target = `*${leftText}`;
@@ -350,8 +474,9 @@ export function visitBinaryExpression(node, context) {
350
474
  }
351
475
  return `${target} ${op} ${rightText}`;
352
476
  }
477
+ // Assignment expression `a = 1`
353
478
  if (opToken.kind === ts.SyntaxKind.EqualsToken) {
354
- let rightText = this.visit(binExpr.right, context);
479
+ let rightText = this.visit(binExpr.right, visitContext);
355
480
  if (ts.isPropertyAccessExpression(binExpr.left)) {
356
481
  const propAccess = binExpr.left;
357
482
  if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
@@ -365,13 +490,13 @@ export function visitBinaryExpression(node, context) {
365
490
  if (rightTypeInfo &&
366
491
  !rightTypeInfo.isParameter &&
367
492
  !rightTypeInfo.isBuiltin) {
368
- finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), context, rightTypeInfo);
493
+ finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, rightTypeInfo);
369
494
  }
370
495
  }
371
496
  // Approximate super assignment as setting property on 'this'
372
497
  return `(${this.globalThisVar}).set_own_property("${propName}", ${finalRightText})`;
373
498
  }
374
- const objExprText = this.visit(propAccess.expression, context);
499
+ const objExprText = this.visit(propAccess.expression, visitContext);
375
500
  const propName = propAccess.name.getText();
376
501
  let finalObjExpr = objExprText;
377
502
  if (ts.isIdentifier(propAccess.expression)) {
@@ -396,14 +521,14 @@ export function visitBinaryExpression(node, context) {
396
521
  }
397
522
  else if (ts.isElementAccessExpression(binExpr.left)) {
398
523
  const elemAccess = binExpr.left;
399
- const objExprText = this.visit(elemAccess.expression, context);
400
- let argText = visitObjectPropertyName.call(this, elemAccess.argumentExpression, { ...context, isBracketNotationPropertyAccess: true });
524
+ const objExprText = this.visit(elemAccess.expression, visitContext);
525
+ let argText = visitObjectPropertyName.call(this, elemAccess.argumentExpression, { ...visitContext, isBracketNotationPropertyAccess: true });
401
526
  let finalObjExpr = objExprText;
402
527
  if (ts.isIdentifier(elemAccess.expression)) {
403
528
  const scope = this.getScopeForNode(elemAccess.expression);
404
529
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(elemAccess.expression.getText(), scope);
405
530
  if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
406
- finalObjExpr = this.getDerefCode(objExprText, this.getJsVarName(elemAccess.expression), context, typeInfo);
531
+ finalObjExpr = this.getDerefCode(objExprText, this.getJsVarName(elemAccess.expression), visitContext, typeInfo);
407
532
  }
408
533
  }
409
534
  if (ts.isIdentifier(elemAccess.argumentExpression)) {
@@ -413,7 +538,7 @@ export function visitBinaryExpression(node, context) {
413
538
  if (argTypeInfo &&
414
539
  !argTypeInfo.isParameter &&
415
540
  !argTypeInfo.isBuiltin) {
416
- argText = this.getDerefCode(argText, this.getJsVarName(elemAccess.argumentExpression), context, argTypeInfo);
541
+ argText = this.getDerefCode(argText, this.getJsVarName(elemAccess.argumentExpression), visitContext, argTypeInfo);
417
542
  }
418
543
  }
419
544
  let finalRightText = rightText;
@@ -424,12 +549,12 @@ export function visitBinaryExpression(node, context) {
424
549
  if (rightTypeInfo &&
425
550
  !rightTypeInfo.isParameter &&
426
551
  !rightTypeInfo.isBuiltin) {
427
- finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), context, rightTypeInfo);
552
+ finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, rightTypeInfo);
428
553
  }
429
554
  }
430
555
  return `${finalObjExpr}.set_own_property(${argText}, ${finalRightText})`;
431
556
  }
432
- const leftText = this.visit(binExpr.left, context);
557
+ const leftText = this.visit(binExpr.left, visitContext);
433
558
  const scope = this.getScopeForNode(binExpr.left);
434
559
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.text, scope);
435
560
  if (!typeInfo && !this.isBuiltinObject(binExpr.left)) {
@@ -442,27 +567,29 @@ export function visitBinaryExpression(node, context) {
442
567
  rightText = binExpr.right.getText();
443
568
  }
444
569
  const target = context.derefBeforeAssignment
445
- ? this.getDerefCode(leftText, leftText, context, typeInfo)
570
+ ? this.getDerefCode(leftText, leftText, visitContext, typeInfo)
446
571
  : (typeInfo.needsHeapAllocation ? `*${leftText}` : leftText);
447
572
  // Update scope symbols on variable re-assignment
573
+ // Reset features
448
574
  if (ts.isIdentifier(binExpr.left)) {
449
575
  if (!ts.isFunctionDeclaration(binExpr.right)) {
450
576
  if (context.localScopeSymbols.has(binExpr.left.text)) {
451
- context.localScopeSymbols.update(binExpr.left.text, {
452
- func: null,
577
+ context.localScopeSymbols.set(binExpr.left.text, {
578
+ features: {},
453
579
  });
454
580
  }
455
581
  else if (context.globalScopeSymbols.has(binExpr.left.text)) {
456
- context.globalScopeSymbols.update(binExpr.left.text, {
457
- func: null,
582
+ context.globalScopeSymbols.set(binExpr.left.text, {
583
+ features: {},
458
584
  });
459
585
  }
460
586
  }
461
587
  }
462
588
  return `${target} ${op} ${rightText}`;
463
589
  }
464
- const leftText = this.visit(binExpr.left, context);
465
- const rightText = this.visit(binExpr.right, context);
590
+ const leftText = this.visit(binExpr.left, visitContext);
591
+ const rightText = this.visit(binExpr.right, visitContext);
592
+ // Generate lhs and rhs code
466
593
  let finalLeft = leftText;
467
594
  if (ts.isIdentifier(binExpr.left)) {
468
595
  const scope = this.getScopeForNode(binExpr.left);
@@ -471,7 +598,11 @@ export function visitBinaryExpression(node, context) {
471
598
  return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(binExpr.left)})`;
472
599
  }
473
600
  if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
474
- finalLeft = this.getDerefCode(leftText, this.getJsVarName(binExpr.left), context, typeInfo);
601
+ finalLeft = this.getDerefCode(leftText, this.getJsVarName(binExpr.left), visitContext, typeInfo);
602
+ }
603
+ // Number optimizations
604
+ if (typeInfo && typeInfo.type === "number") {
605
+ finalLeft = `${finalLeft}.as_double()`;
475
606
  }
476
607
  }
477
608
  let finalRight = rightText;
@@ -483,7 +614,11 @@ export function visitBinaryExpression(node, context) {
483
614
  return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(binExpr.right)})`;
484
615
  }
485
616
  if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
486
- finalRight = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), context, typeInfo);
617
+ finalRight = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, typeInfo);
618
+ }
619
+ // Number optimizations
620
+ if (typeInfo && typeInfo.type === "number") {
621
+ finalRight = `${finalRight}.as_double()`;
487
622
  }
488
623
  }
489
624
  if (opToken.kind === ts.SyntaxKind.InKeyword) {
@@ -501,52 +636,111 @@ export function visitBinaryExpression(node, context) {
501
636
  if (opToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
502
637
  return `jspp::nullish_coalesce(${finalLeft}, ${finalRight})`;
503
638
  }
504
- // Optimizations to prevent calling make_number multiple times
505
- if (ts.isNumericLiteral(binExpr.left)) {
506
- finalLeft = binExpr.left.getText();
507
- }
508
- if (ts.isNumericLiteral(binExpr.right)) {
509
- finalRight = binExpr.right.getText();
639
+ const isLiteral = (n) => ts.isNumericLiteral(n);
640
+ const supportsNativeBoolean = context.supportedNativeLiterals?.includes("boolean") || false;
641
+ // Native values for lhs and rhs
642
+ const literalLeft = isLiteral(binExpr.left)
643
+ ? binExpr.left.getText()
644
+ : finalLeft;
645
+ const literalRight = isLiteral(binExpr.right)
646
+ ? binExpr.right.getText()
647
+ : finalRight;
648
+ // Operations that returns boolean should return the native boolean if supported
649
+ if (constants.booleanOperators.includes(opToken.kind) &&
650
+ supportsNativeBoolean) {
651
+ if (opToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {
652
+ return `jspp::is_strictly_equal_to_primitive(${literalLeft}, ${literalRight})`;
653
+ }
654
+ if (opToken.kind === ts.SyntaxKind.EqualsEqualsToken) {
655
+ return `jspp::is_equal_to_primitive(${literalLeft}, ${literalRight})`;
656
+ }
657
+ if (opToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
658
+ return `!jspp::is_strictly_equal_to_primitive(${literalLeft}, ${literalRight})`;
659
+ }
660
+ if (opToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
661
+ return `!jspp::is_equal_to_primitive(${literalLeft}, ${literalRight})`;
662
+ }
663
+ let funcName = "";
664
+ if (opToken.kind === ts.SyntaxKind.LessThanToken) {
665
+ funcName = "jspp::less_than_primitive";
666
+ }
667
+ if (opToken.kind === ts.SyntaxKind.LessThanEqualsToken) {
668
+ funcName = "jspp::less_than_or_equal_primitive";
669
+ }
670
+ if (opToken.kind === ts.SyntaxKind.GreaterThanToken) {
671
+ funcName = "jspp::greater_than_primitive";
672
+ }
673
+ if (opToken.kind === ts.SyntaxKind.GreaterThanEqualsToken) {
674
+ funcName = "jspp::greater_than_or_equal_primitive";
675
+ }
676
+ // For C++ primitive literals, standard operators are fine if they map directly,
677
+ // but we are safe using our functions (which handle doubles correctly).
678
+ // Actually, for pure numeric literals like "1 < 2", we can leave it as is if we want optimization,
679
+ // but consistency is safer.
680
+ // Let's stick to valid C++ syntax for literals if possible to avoid overhead?
681
+ // jspp::less_than(1, 2) works.
682
+ return `${funcName}(${literalLeft}, ${literalRight})`;
510
683
  }
684
+ // Return boxed value
511
685
  if (opToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {
512
- return `jspp::is_strictly_equal_to(${finalLeft}, ${finalRight})`;
686
+ return `jspp::is_strictly_equal_to(${literalLeft}, ${literalRight})`;
513
687
  }
514
688
  if (opToken.kind === ts.SyntaxKind.EqualsEqualsToken) {
515
- return `jspp::is_equal_to(${finalLeft}, ${finalRight})`;
689
+ return `jspp::is_equal_to(${literalLeft}, ${literalRight})`;
516
690
  }
517
691
  if (opToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
518
- return `jspp::not_strictly_equal_to(${finalLeft}, ${finalRight})`;
692
+ return `jspp::not_strictly_equal_to(${literalLeft}, ${literalRight})`;
519
693
  }
520
694
  if (opToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
521
- return `jspp::not_equal_to(${finalLeft}, ${finalRight})`;
695
+ return `jspp::not_equal_to(${literalLeft}, ${literalRight})`;
522
696
  }
523
697
  if (opToken.kind === ts.SyntaxKind.AsteriskAsteriskToken) {
524
- return `jspp::pow(${finalLeft}, ${finalRight})`;
698
+ return `jspp::pow(${literalLeft}, ${literalRight})`;
525
699
  }
526
700
  if (opToken.kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken) {
527
- return `jspp::unsigned_right_shift(${finalLeft}, ${finalRight})`;
528
- }
529
- // Optimizations to prevent calling make_number multiple times
530
- if (ts.isNumericLiteral(binExpr.left) && ts.isNumericLiteral(binExpr.right)) {
531
- return `jspp::AnyValue::make_number(${finalLeft} ${op} ${finalRight})`;
532
- }
533
- if (op === "+" ||
534
- op === "-" ||
535
- op === "*" ||
536
- op === "/" ||
537
- op === "%" ||
538
- op === "^" ||
539
- op === "&" ||
540
- op === "|" ||
541
- op === "<<" ||
542
- op === ">>") {
543
- return `(${finalLeft} ${op} ${finalRight})`;
544
- }
545
- return `${finalLeft} ${op} ${finalRight}`;
701
+ return `jspp::unsigned_right_shift(${literalLeft}, ${literalRight})`;
702
+ }
703
+ // For other arithmetic and bitwise operations, use native operations if possible
704
+ switch (op) {
705
+ case "+":
706
+ return `jspp::add(${literalLeft}, ${literalRight})`;
707
+ case "-":
708
+ return `jspp::sub(${literalLeft}, ${literalRight})`;
709
+ case "*":
710
+ return `jspp::mul(${literalLeft}, ${literalRight})`;
711
+ case "/":
712
+ return `jspp::div(${literalLeft}, ${literalRight})`;
713
+ case "%":
714
+ return `jspp::mod(${literalLeft}, ${literalRight})`;
715
+ case "^":
716
+ return `jspp::bitwise_xor(${literalLeft}, ${literalRight})`;
717
+ case "&":
718
+ return `jspp::bitwise_and(${literalLeft}, ${literalRight})`;
719
+ case "|":
720
+ return `jspp::bitwise_or(${literalLeft}, ${literalRight})`;
721
+ case "<<":
722
+ return `jspp::left_shift(${literalLeft}, ${literalRight})`;
723
+ case ">>":
724
+ return `jspp::right_shift(${literalLeft}, ${literalRight})`;
725
+ case "<":
726
+ return `jspp::less_than(${literalLeft}, ${literalRight})`;
727
+ case ">":
728
+ return `jspp::greater_than(${literalLeft}, ${literalRight})`;
729
+ case "<=":
730
+ return `jspp::less_than_or_equal(${literalLeft}, ${literalRight})`;
731
+ case ">=":
732
+ return `jspp::greater_than_or_equal(${literalLeft}, ${literalRight})`;
733
+ }
734
+ return `/* Unhandled Operator: ${finalLeft} ${op} ${finalRight} */`; // Default fallback
546
735
  }
547
736
  export function visitConditionalExpression(node, context) {
548
737
  const condExpr = node;
549
- const condition = this.visit(condExpr.condition, context);
738
+ const isBinaryExpression = ts.isBinaryExpression(condExpr.condition) &&
739
+ constants.booleanOperators.includes(condExpr.condition.operatorToken.kind);
740
+ const condition = this.visit(condExpr.condition, {
741
+ ...context,
742
+ supportedNativeLiterals: isBinaryExpression ? ["boolean"] : undefined,
743
+ });
550
744
  const whenTrueStmt = this.visit(condExpr.whenTrue, {
551
745
  ...context,
552
746
  isFunctionBody: false,
@@ -555,53 +749,147 @@ export function visitConditionalExpression(node, context) {
555
749
  ...context,
556
750
  isFunctionBody: false,
557
751
  });
752
+ if (isBinaryExpression) {
753
+ return `${condition} ? ${whenTrueStmt} : ${whenFalseStmt}`;
754
+ }
558
755
  return `jspp::is_truthy(${condition}) ? ${whenTrueStmt} : ${whenFalseStmt}`;
559
756
  }
560
757
  export function visitCallExpression(node, context) {
561
758
  const callExpr = node;
562
759
  const callee = callExpr.expression;
760
+ const flattened = flattenArrayElements(callExpr.arguments);
761
+ const hasSpread = flattened.some((e) => typeof e === "object" && "dynamicSpread" in e);
563
762
  if (callee.kind === ts.SyntaxKind.SuperKeyword) {
564
763
  if (!context.superClassVar) {
565
- throw new Error("super() called but no super class variable found in context");
764
+ throw new CompilerError("super() called but no super class variable found in context", callee, "SyntaxError");
765
+ }
766
+ if (!hasSpread) {
767
+ const args = flattened.map((arg) => this.visit(arg, context))
768
+ .join(", ");
769
+ return `(${context.superClassVar}).call(${this.globalThisVar}, (const jspp::AnyValue[]){${args}}, "super")`;
770
+ }
771
+ else {
772
+ const argsVar = this.generateUniqueName("__args_", this.getDeclaredSymbols(node));
773
+ let code = `([&]() {\n`;
774
+ code +=
775
+ `${this.indent()} std::vector<jspp::AnyValue> ${argsVar};\n`;
776
+ this.indentationLevel++;
777
+ for (const arg of flattened) {
778
+ if (typeof arg === "object" && "dynamicSpread" in arg) {
779
+ const spreadExprSource = arg.dynamicSpread;
780
+ let spreadExpr = this.visit(spreadExprSource, context);
781
+ if (ts.isIdentifier(spreadExprSource)) {
782
+ const scope = this.getScopeForNode(spreadExprSource);
783
+ const typeInfo = this.typeAnalyzer.scopeManager
784
+ .lookupFromScope(spreadExprSource.text, scope);
785
+ spreadExpr = this.getDerefCode(spreadExpr, this.getJsVarName(spreadExprSource), context, typeInfo);
786
+ }
787
+ code +=
788
+ `${this.indent()}jspp::Access::spread_array(${argsVar}, ${spreadExpr});\n`;
789
+ }
790
+ else {
791
+ const expr = arg;
792
+ let argText = this.visit(expr, context);
793
+ if (ts.isIdentifier(expr)) {
794
+ const scope = this.getScopeForNode(expr);
795
+ const typeInfo = this.typeAnalyzer.scopeManager
796
+ .lookupFromScope(expr.text, scope);
797
+ argText = this.getDerefCode(argText, this.getJsVarName(expr), context, typeInfo);
798
+ }
799
+ code +=
800
+ `${this.indent()}${argsVar}.push_back(${argText});\n`;
801
+ }
802
+ }
803
+ code +=
804
+ `${this.indent()} return (${context.superClassVar}).call(${this.globalThisVar}, ${argsVar}, "super");\n`;
805
+ this.indentationLevel--;
806
+ code += `${this.indent()}})()`;
807
+ return code;
566
808
  }
567
- const args = callExpr.arguments.map((arg) => this.visit(arg, context))
568
- .join(", ");
569
- return `(${context.superClassVar}).call(${this.globalThisVar}, (const jspp::AnyValue[]){${args}}, "super")`;
570
809
  }
571
- const argsArray = callExpr.arguments
572
- .map((arg) => {
573
- const argText = this.visit(arg, context);
574
- if (ts.isIdentifier(arg)) {
575
- const scope = this.getScopeForNode(arg);
576
- const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(arg.text, scope);
577
- if (!typeInfo) {
578
- return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(arg)})`;
810
+ const generateArgsSpan = (args) => {
811
+ const argsArray = args
812
+ .map((arg) => {
813
+ const expr = arg;
814
+ const argText = this.visit(expr, context);
815
+ if (ts.isIdentifier(expr)) {
816
+ const scope = this.getScopeForNode(expr);
817
+ const typeInfo = this.typeAnalyzer.scopeManager
818
+ .lookupFromScope(expr.text, scope);
819
+ if (!typeInfo) {
820
+ return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(expr)})`;
821
+ }
822
+ if (typeInfo && !typeInfo.isBuiltin) {
823
+ return this.getDerefCode(argText, this.getJsVarName(expr), context, typeInfo);
824
+ }
825
+ }
826
+ return argText;
827
+ });
828
+ const argsJoined = argsArray.join(", ");
829
+ const argsCount = argsArray.length;
830
+ return argsCount > 0
831
+ ? `std::span<const jspp::AnyValue>((const jspp::AnyValue[]){${argsJoined}}, ${argsCount})`
832
+ : "std::span<const jspp::AnyValue>{}";
833
+ };
834
+ const generateArgsVectorBuilder = (args, argsVar) => {
835
+ let code = `${this.indent()}std::vector<jspp::AnyValue> ${argsVar};\n`;
836
+ for (const arg of args) {
837
+ if (typeof arg === "object" && "dynamicSpread" in arg) {
838
+ const spreadExprSource = arg.dynamicSpread;
839
+ let spreadExpr = this.visit(spreadExprSource, context);
840
+ if (ts.isIdentifier(spreadExprSource)) {
841
+ const scope = this.getScopeForNode(spreadExprSource);
842
+ const typeInfo = this.typeAnalyzer.scopeManager
843
+ .lookupFromScope(spreadExprSource.text, scope);
844
+ if (typeInfo && !typeInfo.isBuiltin && !typeInfo.isParameter) {
845
+ spreadExpr = this.getDerefCode(spreadExpr, this.getJsVarName(spreadExprSource), context, typeInfo);
846
+ }
847
+ }
848
+ code +=
849
+ `${this.indent()}jspp::Access::spread_array(${argsVar}, ${spreadExpr});\n`;
579
850
  }
580
- if (typeInfo && !typeInfo.isBuiltin) {
581
- return this.getDerefCode(argText, this.getJsVarName(arg), context, typeInfo);
851
+ else {
852
+ const expr = arg;
853
+ let argText = this.visit(expr, context);
854
+ if (ts.isIdentifier(expr)) {
855
+ const scope = this.getScopeForNode(expr);
856
+ const typeInfo = this.typeAnalyzer.scopeManager
857
+ .lookupFromScope(expr.text, scope);
858
+ if (typeInfo && !typeInfo.isBuiltin && !typeInfo.isParameter) {
859
+ argText = this.getDerefCode(argText, this.getJsVarName(expr), context, typeInfo);
860
+ }
861
+ }
862
+ code += `${this.indent()}${argsVar}.push_back(${argText});\n`;
582
863
  }
583
864
  }
584
- return argText;
585
- });
586
- const args = argsArray.join(", ");
587
- const argsCount = argsArray.length;
588
- const argsSpan = argsCount > 0
589
- ? `std::span<const jspp::AnyValue>((const jspp::AnyValue[]){${args}}, ${argsCount})`
590
- : "std::span<const jspp::AnyValue>{}";
591
- // Handle obj.method() -> pass obj as 'this'
865
+ return code;
866
+ };
592
867
  if (ts.isPropertyAccessExpression(callee)) {
593
868
  const propAccess = callee;
594
869
  if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
595
870
  if (!context.superClassVar) {
596
- throw new Error("super.method() called but no super class variable found in context");
871
+ throw new CompilerError("super.method() called but no super class variable found in context", propAccess.expression, "SyntaxError");
597
872
  }
598
873
  const propName = propAccess.name.getText();
599
- return `(${context.superClassVar}).get_own_property("prototype").get_own_property("${propName}").call(${this.globalThisVar}, ${argsSpan}, "${this.escapeString(propName)}")`;
874
+ if (!hasSpread) {
875
+ const argsSpan = generateArgsSpan(flattened);
876
+ return `(${context.superClassVar}).get_own_property("prototype").get_own_property("${propName}").call(${this.globalThisVar}, ${argsSpan}, "${this.escapeString(propName)}")`;
877
+ }
878
+ else {
879
+ const argsVar = this.generateUniqueName("__args_", this.getDeclaredSymbols(node));
880
+ let code = `([&]() {\n`;
881
+ this.indentationLevel++;
882
+ code += generateArgsVectorBuilder(flattened, argsVar);
883
+ code +=
884
+ `${this.indent}return (${context.superClassVar}).get_own_property("prototype").get_own_property("${propName}").call(${this.globalThisVar}, ${argsVar}, "${this.escapeString(propName)}");\n`;
885
+ this.indentationLevel--;
886
+ code += `${this.indent()}})()`;
887
+ return code;
888
+ }
600
889
  }
601
890
  const objExpr = propAccess.expression;
602
891
  const propName = propAccess.name.getText();
603
892
  const objCode = this.visit(objExpr, context);
604
- // We need to dereference the object expression if it's a variable
605
893
  let derefObj = objCode;
606
894
  if (ts.isIdentifier(objExpr)) {
607
895
  const scope = this.getScopeForNode(objExpr);
@@ -613,12 +901,37 @@ export function visitCallExpression(node, context) {
613
901
  derefObj = this.getDerefCode(objCode, this.getJsVarName(objExpr), context, typeInfo);
614
902
  }
615
903
  }
616
- if (callExpr.questionDotToken) {
617
- return `jspp::Access::optional_call(${derefObj}.get_own_property("${propName}"), ${derefObj}, ${argsSpan}, "${this.escapeString(propName)}")`;
904
+ if (!hasSpread) {
905
+ const argsSpan = generateArgsSpan(flattened);
906
+ if (propAccess.questionDotToken) {
907
+ const method = callExpr.questionDotToken
908
+ ? "jspp::Access::call_optional_property_with_optional_call"
909
+ : "jspp::Access::call_optional_property";
910
+ return `${method}(${derefObj}, "${propName}", ${argsSpan}, "${this.escapeString(propName)}")`;
911
+ }
912
+ return `${derefObj}.call_own_property("${propName}", ${argsSpan})`;
913
+ }
914
+ else {
915
+ const argsVar = this.generateUniqueName("__args_", this.getDeclaredSymbols(node));
916
+ let code = `([&]() {\n`;
917
+ this.indentationLevel++;
918
+ code += generateArgsVectorBuilder(flattened, argsVar);
919
+ if (propAccess.questionDotToken) {
920
+ const method = callExpr.questionDotToken
921
+ ? "jspp::Access::call_optional_property_with_optional_call"
922
+ : "jspp::Access::call_optional_property";
923
+ code +=
924
+ `${this.indent()}return ${method}(${derefObj}, "${propName}", ${argsVar}, "${this.escapeString(propName)}");\n`;
925
+ }
926
+ else {
927
+ code +=
928
+ `${this.indent()}return ${derefObj}.call_own_property("${propName}", ${argsVar});\n`;
929
+ }
930
+ this.indentationLevel--;
931
+ code += `${this.indent()}})()`;
932
+ return code;
618
933
  }
619
- return `${derefObj}.call_own_property("${propName}", ${argsSpan})`;
620
934
  }
621
- // Handle obj[method]() -> pass obj as 'this'
622
935
  if (ts.isElementAccessExpression(callee)) {
623
936
  const elemAccess = callee;
624
937
  const objExpr = elemAccess.expression;
@@ -635,7 +948,6 @@ export function visitCallExpression(node, context) {
635
948
  }
636
949
  }
637
950
  let argText = visitObjectPropertyName.call(this, elemAccess.argumentExpression, { ...context, isBracketNotationPropertyAccess: true });
638
- // Dereference argument if needed (logic copied from visitElementAccessExpression)
639
951
  if (ts.isIdentifier(elemAccess.argumentExpression)) {
640
952
  const argScope = this.getScopeForNode(elemAccess.argumentExpression);
641
953
  const argTypeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(elemAccess.argumentExpression.getText(), argScope);
@@ -648,39 +960,135 @@ export function visitCallExpression(node, context) {
648
960
  argText = this.getDerefCode(argText, this.getJsVarName(elemAccess.argumentExpression), context, argTypeInfo);
649
961
  }
650
962
  }
651
- if (callExpr.questionDotToken) {
652
- return `jspp::Access::optional_call(${derefObj}.get_own_property(${argText}), ${derefObj}, ${argsSpan})`;
963
+ if (!hasSpread) {
964
+ const argsSpan = generateArgsSpan(flattened);
965
+ if (elemAccess.questionDotToken) {
966
+ const method = callExpr.questionDotToken
967
+ ? "jspp::Access::call_optional_property_with_optional_call"
968
+ : "jspp::Access::call_optional_property";
969
+ return `${method}(${derefObj}, ${argText}, ${argsSpan})`;
970
+ }
971
+ return `${derefObj}.call_own_property(${argText}, ${argsSpan})`;
972
+ }
973
+ else {
974
+ const argsVar = this.generateUniqueName("__args_", this.getDeclaredSymbols(node));
975
+ let code = `([&]() {\n`;
976
+ this.indentationLevel++;
977
+ code += generateArgsVectorBuilder(flattened, argsVar);
978
+ if (elemAccess.questionDotToken) {
979
+ const method = callExpr.questionDotToken
980
+ ? "jspp::Access::call_optional_property_with_optional_call"
981
+ : "jspp::Access::call_optional_property";
982
+ code +=
983
+ `${this.indent()}return ${method}(${derefObj}, ${argText}, ${argsVar});\n`;
984
+ }
985
+ else {
986
+ code +=
987
+ `${this.indent()}return ${derefObj}.call_own_property(${argText}, ${argsVar});\n`;
988
+ }
989
+ this.indentationLevel--;
990
+ code += `${this.indent()}})()`;
991
+ return code;
653
992
  }
654
- return `${derefObj}.call_own_property(${argText}, ${argsSpan})`;
655
993
  }
656
994
  const calleeCode = this.visit(callee, context);
657
995
  let derefCallee = calleeCode;
996
+ let calleeTypeInfo = null;
658
997
  if (ts.isIdentifier(callee)) {
659
998
  const scope = this.getScopeForNode(callee);
660
- const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(callee.text, scope);
661
- if (!typeInfo && !this.isBuiltinObject(callee)) {
999
+ calleeTypeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(callee.text, scope);
1000
+ if (!calleeTypeInfo && !this.isBuiltinObject(callee)) {
662
1001
  return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(callee)})`;
663
1002
  }
664
- if (typeInfo?.isBuiltin) {
1003
+ if (calleeTypeInfo?.isBuiltin) {
665
1004
  derefCallee = calleeCode;
666
1005
  }
667
- else if (typeInfo) {
668
- const name = callee.getText();
669
- const symbol = context.localScopeSymbols.get(name) ??
670
- context.globalScopeSymbols.get(name);
671
- // Optimization: Direct lambda call
672
- if (symbol && symbol.func?.nativeName) {
673
- const callExpr = `${symbol.func.nativeName}(jspp::Constants::UNDEFINED, ${argsSpan})`;
674
- if (symbol.func.isGenerator) {
675
- if (symbol.func.isAsync) {
676
- return `jspp::AnyValue::from_async_iterator(${callExpr})`;
1006
+ else if (calleeTypeInfo) {
1007
+ derefCallee = this.getDerefCode(calleeCode, this.getJsVarName(callee), context, calleeTypeInfo);
1008
+ }
1009
+ }
1010
+ // Direct native lamda if available
1011
+ if (ts.isIdentifier(callee) && calleeTypeInfo) {
1012
+ const name = callee.getText();
1013
+ const symbol = context.localScopeSymbols.get(name) ??
1014
+ context.globalScopeSymbols.get(name);
1015
+ const nativeFeature = symbol?.features?.native;
1016
+ if (nativeFeature && nativeFeature.type === "lambda") {
1017
+ const nativeName = nativeFeature.name;
1018
+ const parameters = nativeFeature.parameters || [];
1019
+ if (!hasSpread) {
1020
+ let argsPart = "";
1021
+ if (parameters) {
1022
+ const argsArray = flattened.map((arg) => {
1023
+ const expr = arg;
1024
+ let argText = this.visit(expr, context);
1025
+ if (ts.isIdentifier(expr)) {
1026
+ const scope = this.getScopeForNode(expr);
1027
+ const typeInfo = this.typeAnalyzer.scopeManager
1028
+ .lookupFromScope(expr.text, scope);
1029
+ argText = this.getDerefCode(argText, this.getJsVarName(expr), context, typeInfo);
1030
+ }
1031
+ return argText;
1032
+ });
1033
+ const argsText = argsArray.slice(0, parameters.length)
1034
+ .filter((_, i) => !parameters[i]?.dotDotDotToken).join(", ");
1035
+ if (argsText)
1036
+ argsPart += `, ${argsText}`;
1037
+ if (argsArray.length > parameters.length &&
1038
+ !!parameters[parameters.length - 1]?.dotDotDotToken) {
1039
+ const restArgsText = `jspp::AnyValue::make_array(std::vector<jspp::AnyValue>{${argsArray.slice(parameters.length - 1).join(", ")}})`;
1040
+ argsPart += `, ${restArgsText}`;
1041
+ }
1042
+ }
1043
+ const callImplementation = `${nativeName}(jspp::Constants::UNDEFINED${argsPart})`;
1044
+ if (symbol.features.isGenerator) {
1045
+ if (symbol.features.isAsync) {
1046
+ return `jspp::AnyValue::from_async_iterator(${callImplementation})`;
677
1047
  }
678
- return `jspp::AnyValue::from_iterator(${callExpr})`;
1048
+ return `jspp::AnyValue::from_iterator(${callImplementation})`;
679
1049
  }
680
- return callExpr;
1050
+ if (symbol.features.isAsync) {
1051
+ return `jspp::AnyValue::from_promise(${callImplementation})`;
1052
+ }
1053
+ return callImplementation;
1054
+ }
1055
+ else {
1056
+ const argsVar = this.generateUniqueName("__args_", this.getDeclaredSymbols(node));
1057
+ let code = `([&]() {\n`;
1058
+ this.indentationLevel++;
1059
+ code += generateArgsVectorBuilder(flattened, argsVar);
1060
+ const callArgs = [];
1061
+ for (let i = 0; i < parameters.length; i++) {
1062
+ const p = parameters[i];
1063
+ if (!p)
1064
+ continue;
1065
+ if (p.dotDotDotToken) {
1066
+ callArgs.push(`jspp::AnyValue::make_array(std::vector<jspp::AnyValue>(${argsVar}.begin() + std::min((size_t)${i}, ${argsVar}.size()), ${argsVar}.end()))`);
1067
+ }
1068
+ else {
1069
+ callArgs.push(`(${argsVar}.size() > ${i} ? ${argsVar}[${i}] : jspp::Constants::UNDEFINED)`);
1070
+ }
1071
+ }
1072
+ let callExprStr = `${nativeName}(jspp::Constants::UNDEFINED${callArgs.length > 0 ? ", " + callArgs.join(", ") : ""})`;
1073
+ if (symbol.features.isGenerator) {
1074
+ if (symbol.features.isAsync) {
1075
+ callExprStr =
1076
+ `jspp::AnyValue::from_async_iterator(${callExprStr})`;
1077
+ }
1078
+ else {
1079
+ callExprStr =
1080
+ `jspp::AnyValue::from_iterator(${callExprStr})`;
1081
+ }
1082
+ }
1083
+ else if (symbol.features.isAsync) {
1084
+ callExprStr =
1085
+ `jspp::AnyValue::from_promise(${callExprStr})`;
1086
+ }
1087
+ code += `${this.indent()}return ${callExprStr};\n`;
1088
+ this.indentationLevel--;
1089
+ code += `${this.indent()}})()`;
1090
+ return code;
681
1091
  }
682
- // AnyValue function call
683
- derefCallee = this.getDerefCode(calleeCode, this.getJsVarName(callee), context, typeInfo);
684
1092
  }
685
1093
  }
686
1094
  let calleeName = "";
@@ -692,14 +1100,33 @@ export function visitCallExpression(node, context) {
692
1100
  const funcExpr = callee.expression;
693
1101
  calleeName = this.escapeString(funcExpr.name?.getText() || "");
694
1102
  }
695
- // Pass undefined as 'this' for normal function calls
696
1103
  const calleeNamePart = calleeName && calleeName.length > 0
697
1104
  ? `, "${calleeName}"`
698
1105
  : "";
699
- if (callExpr.questionDotToken) {
700
- return `jspp::Access::optional_call(${derefCallee}, jspp::Constants::UNDEFINED, ${argsSpan}${calleeNamePart})`;
1106
+ if (!hasSpread) {
1107
+ const argsSpan = generateArgsSpan(flattened);
1108
+ if (callExpr.questionDotToken) {
1109
+ return `${derefCallee}.optional_call(jspp::Constants::UNDEFINED, ${argsSpan}${calleeNamePart})`;
1110
+ }
1111
+ return `${derefCallee}.call(jspp::Constants::UNDEFINED, ${argsSpan}${calleeNamePart})`;
1112
+ }
1113
+ else {
1114
+ const argsVar = this.generateUniqueName("__args_", this.getDeclaredSymbols(node));
1115
+ let code = `([&]() {\n`;
1116
+ this.indentationLevel++;
1117
+ code += generateArgsVectorBuilder(flattened, argsVar);
1118
+ if (callExpr.questionDotToken) {
1119
+ code +=
1120
+ `${this.indent()}return ${derefCallee}.optional_call(jspp::Constants::UNDEFINED, ${argsVar}${calleeNamePart});\n`;
1121
+ }
1122
+ else {
1123
+ code +=
1124
+ `${this.indent()}return ${derefCallee}.call(jspp::Constants::UNDEFINED, ${argsVar}${calleeNamePart});\n`;
1125
+ }
1126
+ this.indentationLevel--;
1127
+ code += `${this.indent()}})()`;
1128
+ return code;
701
1129
  }
702
- return `${derefCallee}.call(jspp::Constants::UNDEFINED, ${argsSpan}${calleeNamePart})`;
703
1130
  }
704
1131
  export function visitVoidExpression(node, context) {
705
1132
  const voidExpr = node;
@@ -725,9 +1152,9 @@ export function visitTemplateExpression(node, context) {
725
1152
  finalExpr = this.getDerefCode(exprText, this.getJsVarName(expr), context, typeInfo);
726
1153
  }
727
1154
  }
728
- result += ` + (${finalExpr})`;
1155
+ result = `jspp::add(${result}, ${finalExpr})`;
729
1156
  if (span.literal.text) {
730
- result += ` + jspp::AnyValue::make_string("${this.escapeString(span.literal.text)}")`;
1157
+ result = `jspp::add(${result}, jspp::AnyValue::make_string("${this.escapeString(span.literal.text)}"))`;
731
1158
  }
732
1159
  }
733
1160
  return result;
@@ -735,6 +1162,8 @@ export function visitTemplateExpression(node, context) {
735
1162
  export function visitNewExpression(node, context) {
736
1163
  const newExpr = node;
737
1164
  const exprText = this.visit(newExpr.expression, context);
1165
+ const flattened = flattenArrayElements(newExpr.arguments || []);
1166
+ const hasSpread = flattened.some((e) => typeof e === "object" && "dynamicSpread" in e);
738
1167
  let derefExpr = exprText;
739
1168
  let name = `"${exprText}"`;
740
1169
  if (ts.isIdentifier(newExpr.expression)) {
@@ -749,30 +1178,77 @@ export function visitNewExpression(node, context) {
749
1178
  derefExpr = this.getDerefCode(exprText, name, context, typeInfo);
750
1179
  }
751
1180
  }
752
- const argsArray = newExpr.arguments
753
- ? newExpr.arguments
1181
+ if (!hasSpread) {
1182
+ const argsArray = flattened
754
1183
  .map((arg) => {
755
- const argText = this.visit(arg, context);
756
- if (ts.isIdentifier(arg)) {
757
- const scope = this.getScopeForNode(arg);
1184
+ const expr = arg;
1185
+ const argText = this.visit(expr, context);
1186
+ if (ts.isIdentifier(expr)) {
1187
+ const scope = this.getScopeForNode(expr);
758
1188
  const typeInfo = this.typeAnalyzer.scopeManager
759
- .lookupFromScope(arg.text, scope);
1189
+ .lookupFromScope(expr.text, scope);
760
1190
  if (!typeInfo) {
761
- return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(arg)})`;
1191
+ return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(expr)})`;
762
1192
  }
763
- if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
764
- return this.getDerefCode(argText, this.getJsVarName(arg), context, typeInfo);
1193
+ if (typeInfo && !typeInfo.isParameter &&
1194
+ !typeInfo.isBuiltin) {
1195
+ return this.getDerefCode(argText, this.getJsVarName(expr), context, typeInfo);
765
1196
  }
766
1197
  }
767
1198
  return argText;
768
- })
769
- : [];
770
- const args = argsArray.join(", ");
771
- const argsCount = argsArray.length;
772
- const argsSpan = argsCount > 0
773
- ? `std::span<const jspp::AnyValue>((const jspp::AnyValue[]){${args}}, ${argsCount})`
774
- : "std::span<const jspp::AnyValue>{}";
775
- return `${derefExpr}.construct(${argsSpan}, ${name})`;
1199
+ });
1200
+ const args = argsArray.join(", ");
1201
+ const argsCount = argsArray.length;
1202
+ const argsSpan = argsCount > 0
1203
+ ? `std::span<const jspp::AnyValue>((const jspp::AnyValue[]){${args}}, ${argsCount})`
1204
+ : "std::span<const jspp::AnyValue>{}";
1205
+ return `${derefExpr}.construct(${argsSpan}, ${name})`;
1206
+ }
1207
+ else {
1208
+ const argsVar = this.generateUniqueName("__args_", this.getDeclaredSymbols(node));
1209
+ let code = `([&]() {\n`;
1210
+ this.indentationLevel++;
1211
+ code += `${this.indent()}std::vector<jspp::AnyValue> ${argsVar};\n`;
1212
+ if (newExpr.arguments) {
1213
+ for (const arg of flattened) {
1214
+ if (typeof arg === "object" && "dynamicSpread" in arg) {
1215
+ const spreadExprSource = arg.dynamicSpread;
1216
+ let spreadExpr = this.visit(spreadExprSource, context);
1217
+ if (ts.isIdentifier(spreadExprSource)) {
1218
+ const scope = this.getScopeForNode(spreadExprSource);
1219
+ const typeInfo = this.typeAnalyzer.scopeManager
1220
+ .lookupFromScope(spreadExprSource.text, scope);
1221
+ if (typeInfo && !typeInfo.isBuiltin &&
1222
+ !typeInfo.isParameter) {
1223
+ spreadExpr = this.getDerefCode(spreadExpr, this.getJsVarName(spreadExprSource), context, typeInfo);
1224
+ }
1225
+ }
1226
+ code +=
1227
+ `${this.indent()}jspp::Access::spread_array(${argsVar}, ${spreadExpr});\n`;
1228
+ }
1229
+ else {
1230
+ const expr = arg;
1231
+ let argText = this.visit(expr, context);
1232
+ if (ts.isIdentifier(expr)) {
1233
+ const scope = this.getScopeForNode(expr);
1234
+ const typeInfo = this.typeAnalyzer.scopeManager
1235
+ .lookupFromScope(expr.text, scope);
1236
+ if (typeInfo && !typeInfo.isBuiltin &&
1237
+ !typeInfo.isParameter) {
1238
+ argText = this.getDerefCode(argText, this.getJsVarName(expr), context, typeInfo);
1239
+ }
1240
+ }
1241
+ code +=
1242
+ `${this.indent()}${argsVar}.push_back(${argText});\n`;
1243
+ }
1244
+ }
1245
+ }
1246
+ code +=
1247
+ `${this.indent()} return ${derefExpr}.construct(${argsVar}, ${name});\n`;
1248
+ this.indentationLevel--;
1249
+ code += `${this.indent()}})()`;
1250
+ return code;
1251
+ }
776
1252
  }
777
1253
  export function visitTypeOfExpression(node, context) {
778
1254
  const typeOfExpr = node;
@@ -827,3 +1303,15 @@ export function visitDeleteExpression(node, context) {
827
1303
  }
828
1304
  return "jspp::Constants::TRUE"; // delete on non-property is true in JS
829
1305
  }
1306
+ export function visitAsExpression(node, context) {
1307
+ return this.visit(node.expression, context);
1308
+ }
1309
+ export function visitTypeAssertionExpression(node, context) {
1310
+ return this.visit(node.expression, context);
1311
+ }
1312
+ export function visitNonNullExpression(node, context) {
1313
+ return this.visit(node.expression, context);
1314
+ }
1315
+ export function visitSatisfiesExpression(node, context) {
1316
+ return this.visit(node.expression, context);
1317
+ }