@ugo-studio/jspp 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,6 @@
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";
3
5
  export function visitObjectPropertyName(node, context) {
4
6
  if (ts.isNumericLiteral(node)) {
@@ -28,6 +30,13 @@ export function visitObjectPropertyName(node, context) {
28
30
  export function visitObjectLiteralExpression(node, context) {
29
31
  const obj = node;
30
32
  const objVar = this.generateUniqueName("__obj_", this.getDeclaredSymbols(node));
33
+ if (!obj.properties.some((prop) => ts.isPropertyAssignment(prop) ||
34
+ ts.isShorthandPropertyAssignment(prop) ||
35
+ ts.isMethodDeclaration(prop) || ts.isGetAccessor(prop) ||
36
+ ts.isSetAccessor(prop))) {
37
+ // Empty object
38
+ return `jspp::AnyValue::make_object_with_proto({}, ::Object.get_own_property("prototype"))`;
39
+ }
31
40
  let code = `([&]() {\n`;
32
41
  code +=
33
42
  `${this.indent()} auto ${objVar} = jspp::AnyValue::make_object_with_proto({}, ::Object.get_own_property("prototype"));\n`;
@@ -102,8 +111,6 @@ export function visitObjectLiteralExpression(node, context) {
102
111
  }
103
112
  }
104
113
  this.indentationLevel--;
105
- // code +=
106
- // `${this.indent()} ${returnCmd} ${objVar};\n${this.indent()}} )() ))`;
107
114
  code += `${this.indent()} return ${objVar};\n${this.indent()}})()`;
108
115
  return code;
109
116
  }
@@ -185,7 +192,7 @@ export function visitPropertyAccessExpression(node, context) {
185
192
  const propAccess = node;
186
193
  if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
187
194
  if (!context.superClassVar) {
188
- throw new Error("super.prop accessed but no super class variable found in context");
195
+ throw new CompilerError("super.prop accessed but no super class variable found in context", propAccess.expression, "SyntaxError");
189
196
  }
190
197
  const propName = propAccess.name.getText();
191
198
  return `jspp::AnyValue::resolve_property_for_read((${context.superClassVar}).get_own_property("prototype").get_own_property("${propName}"), ${this.globalThisVar}, "${this.escapeString(propName)}")`;
@@ -253,7 +260,11 @@ export function visitElementAccessExpression(node, context) {
253
260
  export function visitBinaryExpression(node, context) {
254
261
  const binExpr = node;
255
262
  const opToken = binExpr.operatorToken;
256
- let op = opToken.getText();
263
+ const op = opToken.getText();
264
+ const visitContext = {
265
+ ...context,
266
+ supportedNativeLiterals: undefined,
267
+ };
257
268
  const assignmentOperators = [
258
269
  ts.SyntaxKind.PlusEqualsToken,
259
270
  ts.SyntaxKind.MinusEqualsToken,
@@ -274,8 +285,8 @@ export function visitBinaryExpression(node, context) {
274
285
  if (assignmentOperators.includes(opToken.kind)) {
275
286
  if (opToken.kind ===
276
287
  ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken) {
277
- const leftText = this.visit(binExpr.left, context);
278
- const rightText = this.visit(binExpr.right, context);
288
+ const leftText = this.visit(binExpr.left, visitContext);
289
+ const rightText = this.visit(binExpr.right, visitContext);
279
290
  let target = leftText;
280
291
  if (ts.isIdentifier(binExpr.left)) {
281
292
  const scope = this.getScopeForNode(binExpr.left);
@@ -287,8 +298,8 @@ export function visitBinaryExpression(node, context) {
287
298
  }
288
299
  }
289
300
  if (opToken.kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken) {
290
- const leftText = this.visit(binExpr.left, context);
291
- const rightText = this.visit(binExpr.right, context);
301
+ const leftText = this.visit(binExpr.left, visitContext);
302
+ const rightText = this.visit(binExpr.right, visitContext);
292
303
  let target = leftText;
293
304
  if (ts.isIdentifier(binExpr.left)) {
294
305
  const scope = this.getScopeForNode(binExpr.left);
@@ -303,8 +314,8 @@ export function visitBinaryExpression(node, context) {
303
314
  if (opToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
304
315
  opToken.kind === ts.SyntaxKind.BarBarEqualsToken ||
305
316
  opToken.kind === ts.SyntaxKind.QuestionQuestionEqualsToken) {
306
- const leftText = this.visit(binExpr.left, context);
307
- const rightText = this.visit(binExpr.right, context);
317
+ const leftText = this.visit(binExpr.left, visitContext);
318
+ const rightText = this.visit(binExpr.right, visitContext);
308
319
  let target = leftText;
309
320
  if (ts.isIdentifier(binExpr.left)) {
310
321
  const scope = this.getScopeForNode(binExpr.left);
@@ -323,21 +334,21 @@ export function visitBinaryExpression(node, context) {
323
334
  }
324
335
  }
325
336
  }
326
- const leftText = this.visit(binExpr.left, context);
337
+ const leftText = this.visit(binExpr.left, visitContext);
327
338
  let rightText = ts.isNumericLiteral(binExpr.right)
328
339
  ? binExpr.right.getText()
329
- : this.visit(binExpr.right, context);
340
+ : this.visit(binExpr.right, visitContext);
330
341
  if (ts.isIdentifier(binExpr.right)) {
331
342
  const scope = this.getScopeForNode(binExpr.right);
332
343
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.right.getText(), scope);
333
- rightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), context, typeInfo);
344
+ rightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, typeInfo);
334
345
  }
335
346
  let target = leftText;
336
347
  if (ts.isIdentifier(binExpr.left)) {
337
348
  const scope = this.getScopeForNode(binExpr.left);
338
349
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.getText(), scope);
339
350
  if (context.derefBeforeAssignment) {
340
- target = this.getDerefCode(leftText, leftText, context, typeInfo);
351
+ target = this.getDerefCode(leftText, leftText, visitContext, typeInfo);
341
352
  }
342
353
  else if (typeInfo.needsHeapAllocation) {
343
354
  target = `*${leftText}`;
@@ -345,8 +356,9 @@ export function visitBinaryExpression(node, context) {
345
356
  }
346
357
  return `${target} ${op} ${rightText}`;
347
358
  }
359
+ // Assignment expression `a = 1`
348
360
  if (opToken.kind === ts.SyntaxKind.EqualsToken) {
349
- let rightText = this.visit(binExpr.right, context);
361
+ let rightText = this.visit(binExpr.right, visitContext);
350
362
  if (ts.isPropertyAccessExpression(binExpr.left)) {
351
363
  const propAccess = binExpr.left;
352
364
  if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
@@ -360,13 +372,13 @@ export function visitBinaryExpression(node, context) {
360
372
  if (rightTypeInfo &&
361
373
  !rightTypeInfo.isParameter &&
362
374
  !rightTypeInfo.isBuiltin) {
363
- finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), context, rightTypeInfo);
375
+ finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, rightTypeInfo);
364
376
  }
365
377
  }
366
378
  // Approximate super assignment as setting property on 'this'
367
379
  return `(${this.globalThisVar}).set_own_property("${propName}", ${finalRightText})`;
368
380
  }
369
- const objExprText = this.visit(propAccess.expression, context);
381
+ const objExprText = this.visit(propAccess.expression, visitContext);
370
382
  const propName = propAccess.name.getText();
371
383
  let finalObjExpr = objExprText;
372
384
  if (ts.isIdentifier(propAccess.expression)) {
@@ -391,14 +403,14 @@ export function visitBinaryExpression(node, context) {
391
403
  }
392
404
  else if (ts.isElementAccessExpression(binExpr.left)) {
393
405
  const elemAccess = binExpr.left;
394
- const objExprText = this.visit(elemAccess.expression, context);
395
- let argText = visitObjectPropertyName.call(this, elemAccess.argumentExpression, { ...context, isBracketNotationPropertyAccess: true });
406
+ const objExprText = this.visit(elemAccess.expression, visitContext);
407
+ let argText = visitObjectPropertyName.call(this, elemAccess.argumentExpression, { ...visitContext, isBracketNotationPropertyAccess: true });
396
408
  let finalObjExpr = objExprText;
397
409
  if (ts.isIdentifier(elemAccess.expression)) {
398
410
  const scope = this.getScopeForNode(elemAccess.expression);
399
411
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(elemAccess.expression.getText(), scope);
400
412
  if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
401
- finalObjExpr = this.getDerefCode(objExprText, this.getJsVarName(elemAccess.expression), context, typeInfo);
413
+ finalObjExpr = this.getDerefCode(objExprText, this.getJsVarName(elemAccess.expression), visitContext, typeInfo);
402
414
  }
403
415
  }
404
416
  if (ts.isIdentifier(elemAccess.argumentExpression)) {
@@ -408,7 +420,7 @@ export function visitBinaryExpression(node, context) {
408
420
  if (argTypeInfo &&
409
421
  !argTypeInfo.isParameter &&
410
422
  !argTypeInfo.isBuiltin) {
411
- argText = this.getDerefCode(argText, this.getJsVarName(elemAccess.argumentExpression), context, argTypeInfo);
423
+ argText = this.getDerefCode(argText, this.getJsVarName(elemAccess.argumentExpression), visitContext, argTypeInfo);
412
424
  }
413
425
  }
414
426
  let finalRightText = rightText;
@@ -419,12 +431,12 @@ export function visitBinaryExpression(node, context) {
419
431
  if (rightTypeInfo &&
420
432
  !rightTypeInfo.isParameter &&
421
433
  !rightTypeInfo.isBuiltin) {
422
- finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), context, rightTypeInfo);
434
+ finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, rightTypeInfo);
423
435
  }
424
436
  }
425
437
  return `${finalObjExpr}.set_own_property(${argText}, ${finalRightText})`;
426
438
  }
427
- const leftText = this.visit(binExpr.left, context);
439
+ const leftText = this.visit(binExpr.left, visitContext);
428
440
  const scope = this.getScopeForNode(binExpr.left);
429
441
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.text, scope);
430
442
  if (!typeInfo && !this.isBuiltinObject(binExpr.left)) {
@@ -437,12 +449,28 @@ export function visitBinaryExpression(node, context) {
437
449
  rightText = binExpr.right.getText();
438
450
  }
439
451
  const target = context.derefBeforeAssignment
440
- ? this.getDerefCode(leftText, leftText, context, typeInfo)
452
+ ? this.getDerefCode(leftText, leftText, visitContext, typeInfo)
441
453
  : (typeInfo.needsHeapAllocation ? `*${leftText}` : leftText);
454
+ // Update scope symbols on variable re-assignment
455
+ if (ts.isIdentifier(binExpr.left)) {
456
+ if (!ts.isFunctionDeclaration(binExpr.right)) {
457
+ if (context.localScopeSymbols.has(binExpr.left.text)) {
458
+ context.localScopeSymbols.update(binExpr.left.text, {
459
+ func: null,
460
+ });
461
+ }
462
+ else if (context.globalScopeSymbols.has(binExpr.left.text)) {
463
+ context.globalScopeSymbols.update(binExpr.left.text, {
464
+ func: null,
465
+ });
466
+ }
467
+ }
468
+ }
442
469
  return `${target} ${op} ${rightText}`;
443
470
  }
444
- const leftText = this.visit(binExpr.left, context);
445
- const rightText = this.visit(binExpr.right, context);
471
+ const leftText = this.visit(binExpr.left, visitContext);
472
+ const rightText = this.visit(binExpr.right, visitContext);
473
+ // Generate lhs and rhs code
446
474
  let finalLeft = leftText;
447
475
  if (ts.isIdentifier(binExpr.left)) {
448
476
  const scope = this.getScopeForNode(binExpr.left);
@@ -451,7 +479,7 @@ export function visitBinaryExpression(node, context) {
451
479
  return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(binExpr.left)})`;
452
480
  }
453
481
  if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
454
- finalLeft = this.getDerefCode(leftText, this.getJsVarName(binExpr.left), context, typeInfo);
482
+ finalLeft = this.getDerefCode(leftText, this.getJsVarName(binExpr.left), visitContext, typeInfo);
455
483
  }
456
484
  }
457
485
  let finalRight = rightText;
@@ -463,7 +491,7 @@ export function visitBinaryExpression(node, context) {
463
491
  return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(binExpr.right)})`;
464
492
  }
465
493
  if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
466
- finalRight = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), context, typeInfo);
494
+ finalRight = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, typeInfo);
467
495
  }
468
496
  }
469
497
  if (opToken.kind === ts.SyntaxKind.InKeyword) {
@@ -481,35 +509,63 @@ export function visitBinaryExpression(node, context) {
481
509
  if (opToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
482
510
  return `jspp::nullish_coalesce(${finalLeft}, ${finalRight})`;
483
511
  }
484
- // Optimizations to prevent calling make_number multiple times
485
- if (ts.isNumericLiteral(binExpr.left)) {
486
- finalLeft = binExpr.left.getText();
487
- }
488
- if (ts.isNumericLiteral(binExpr.right)) {
489
- finalRight = binExpr.right.getText();
490
- }
512
+ const isLiteral = (n) => ts.isNumericLiteral(n);
513
+ const supportsNativeBoolean = context.supportedNativeLiterals?.includes("boolean") || false;
514
+ // Native values for lhs and rhs
515
+ const literalLeft = isLiteral(binExpr.left)
516
+ ? binExpr.left.getText()
517
+ : finalLeft;
518
+ const literalRight = isLiteral(binExpr.right)
519
+ ? binExpr.right.getText()
520
+ : finalRight;
521
+ // Operations that returns boolean should return the native boolean if supported
522
+ if (constants.booleanOperators.includes(opToken.kind) &&
523
+ supportsNativeBoolean) {
524
+ if (opToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {
525
+ return `jspp::is_strictly_equal_to_primitive(${literalLeft}, ${literalRight})`;
526
+ }
527
+ if (opToken.kind === ts.SyntaxKind.EqualsEqualsToken) {
528
+ return `jspp::is_equal_to_primitive(${literalLeft}, ${literalRight})`;
529
+ }
530
+ if (opToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
531
+ return `!jspp::is_strictly_equal_to_primitive(${literalLeft}, ${literalRight})`;
532
+ }
533
+ if (opToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
534
+ return `!jspp::is_equal_to_primitive(${literalLeft}, ${literalRight})`;
535
+ }
536
+ if (isLiteral(binExpr.left) && isLiteral(binExpr.right)) {
537
+ return `(${literalLeft} ${op} ${literalRight})`;
538
+ }
539
+ else
540
+ return `(${literalLeft} ${op} ${literalRight}).as_boolean()`;
541
+ }
542
+ // Return boxed value
491
543
  if (opToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {
492
- return `jspp::is_strictly_equal_to(${finalLeft}, ${finalRight})`;
544
+ return `jspp::is_strictly_equal_to(${literalLeft}, ${literalRight})`;
493
545
  }
494
546
  if (opToken.kind === ts.SyntaxKind.EqualsEqualsToken) {
495
- return `jspp::is_equal_to(${finalLeft}, ${finalRight})`;
547
+ return `jspp::is_equal_to(${literalLeft}, ${literalRight})`;
496
548
  }
497
549
  if (opToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
498
- return `jspp::not_strictly_equal_to(${finalLeft}, ${finalRight})`;
550
+ return `jspp::not_strictly_equal_to(${literalLeft}, ${literalRight})`;
499
551
  }
500
552
  if (opToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
501
- return `jspp::not_equal_to(${finalLeft}, ${finalRight})`;
553
+ return `jspp::not_equal_to(${literalLeft}, ${literalRight})`;
502
554
  }
503
555
  if (opToken.kind === ts.SyntaxKind.AsteriskAsteriskToken) {
504
- return `jspp::pow(${finalLeft}, ${finalRight})`;
556
+ return `jspp::pow(${literalLeft}, ${literalRight})`;
505
557
  }
506
558
  if (opToken.kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken) {
507
- return `jspp::unsigned_right_shift(${finalLeft}, ${finalRight})`;
559
+ return `jspp::unsigned_right_shift(${literalLeft}, ${literalRight})`;
508
560
  }
509
- // Optimizations to prevent calling make_number multiple times
510
- if (ts.isNumericLiteral(binExpr.left) && ts.isNumericLiteral(binExpr.right)) {
511
- return `jspp::AnyValue::make_number(${finalLeft} ${op} ${finalRight})`;
561
+ // Use literal for at most one side
562
+ if (isLiteral(binExpr.left)) {
563
+ finalLeft = binExpr.left.getText();
512
564
  }
565
+ else if (isLiteral(binExpr.right)) {
566
+ finalRight = binExpr.right.getText();
567
+ }
568
+ // For other arithmetic and bitwise operations, use native operations if possible
513
569
  if (op === "+" ||
514
570
  op === "-" ||
515
571
  op === "*" ||
@@ -526,7 +582,12 @@ export function visitBinaryExpression(node, context) {
526
582
  }
527
583
  export function visitConditionalExpression(node, context) {
528
584
  const condExpr = node;
529
- const condition = this.visit(condExpr.condition, context);
585
+ const isBinaryExpression = ts.isBinaryExpression(condExpr.condition) &&
586
+ constants.booleanOperators.includes(condExpr.condition.operatorToken.kind);
587
+ const condition = this.visit(condExpr.condition, {
588
+ ...context,
589
+ supportedNativeLiterals: isBinaryExpression ? ["boolean"] : undefined,
590
+ });
530
591
  const whenTrueStmt = this.visit(condExpr.whenTrue, {
531
592
  ...context,
532
593
  isFunctionBody: false,
@@ -535,14 +596,17 @@ export function visitConditionalExpression(node, context) {
535
596
  ...context,
536
597
  isFunctionBody: false,
537
598
  });
538
- return `is_truthy(${condition}) ? ${whenTrueStmt} : ${whenFalseStmt}`;
599
+ if (isBinaryExpression) {
600
+ return `${condition} ? ${whenTrueStmt} : ${whenFalseStmt}`;
601
+ }
602
+ return `jspp::is_truthy(${condition}) ? ${whenTrueStmt} : ${whenFalseStmt}`;
539
603
  }
540
604
  export function visitCallExpression(node, context) {
541
605
  const callExpr = node;
542
606
  const callee = callExpr.expression;
543
607
  if (callee.kind === ts.SyntaxKind.SuperKeyword) {
544
608
  if (!context.superClassVar) {
545
- throw new Error("super() called but no super class variable found in context");
609
+ throw new CompilerError("super() called but no super class variable found in context", callee, "SyntaxError");
546
610
  }
547
611
  const args = callExpr.arguments.map((arg) => this.visit(arg, context))
548
612
  .join(", ");
@@ -573,7 +637,7 @@ export function visitCallExpression(node, context) {
573
637
  const propAccess = callee;
574
638
  if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
575
639
  if (!context.superClassVar) {
576
- throw new Error("super.method() called but no super class variable found in context");
640
+ throw new CompilerError("super.method() called but no super class variable found in context", propAccess.expression, "SyntaxError");
577
641
  }
578
642
  const propName = propAccess.name.getText();
579
643
  return `(${context.superClassVar}).get_own_property("prototype").get_own_property("${propName}").call(${this.globalThisVar}, ${argsSpan}, "${this.escapeString(propName)}")`;
@@ -645,6 +709,21 @@ export function visitCallExpression(node, context) {
645
709
  derefCallee = calleeCode;
646
710
  }
647
711
  else if (typeInfo) {
712
+ const name = callee.getText();
713
+ const symbol = context.localScopeSymbols.get(name) ??
714
+ context.globalScopeSymbols.get(name);
715
+ // Optimization: Direct lambda call
716
+ if (symbol && symbol.func?.nativeName) {
717
+ const callExpr = `${symbol.func.nativeName}(jspp::Constants::UNDEFINED, ${argsSpan})`;
718
+ if (symbol.func.isGenerator) {
719
+ if (symbol.func.isAsync) {
720
+ return `jspp::AnyValue::from_async_iterator(${callExpr})`;
721
+ }
722
+ return `jspp::AnyValue::from_iterator(${callExpr})`;
723
+ }
724
+ return callExpr;
725
+ }
726
+ // AnyValue function call
648
727
  derefCallee = this.getDerefCode(calleeCode, this.getJsVarName(callee), context, typeInfo);
649
728
  }
650
729
  }
@@ -792,3 +871,15 @@ export function visitDeleteExpression(node, context) {
792
871
  }
793
872
  return "jspp::Constants::TRUE"; // delete on non-property is true in JS
794
873
  }
874
+ export function visitAsExpression(node, context) {
875
+ return this.visit(node.expression, context);
876
+ }
877
+ export function visitTypeAssertionExpression(node, context) {
878
+ return this.visit(node.expression, context);
879
+ }
880
+ export function visitNonNullExpression(node, context) {
881
+ return this.visit(node.expression, context);
882
+ }
883
+ export function visitSatisfiesExpression(node, context) {
884
+ return this.visit(node.expression, context);
885
+ }
@@ -1,12 +1,13 @@
1
1
  import ts from "typescript";
2
- import { DeclaredSymbols } from "../../ast/symbols.js";
2
+ import { DeclarationType, DeclaredSymbols } from "../../ast/symbols.js";
3
+ import { CompilerError } from "../error.js";
3
4
  import { collectFunctionScopedDeclarations } from "./helpers.js";
4
5
  import { CodeGenerator } from "./index.js";
5
6
  export function generateLambda(node, context, options) {
6
- const isAssignment = options?.isAssignment || false;
7
7
  const capture = options?.capture || "[=]";
8
+ const nativeName = options?.nativeName;
8
9
  const declaredSymbols = this.getDeclaredSymbols(node);
9
- const argsName = this.generateUniqueName("__args_", declaredSymbols);
10
+ const argsName = this.generateUniqueName("__args_", declaredSymbols, context.globalScopeSymbols, context.localScopeSymbols);
10
11
  const isInsideGeneratorFunction = this.isGeneratorFunction(node);
11
12
  const isInsideAsyncFunction = this.isAsyncFunction(node);
12
13
  const returnCmd = this.getReturnCommand({
@@ -23,26 +24,25 @@ export function generateLambda(node, context, options) {
23
24
  // For generators/async, we manually copy them inside the body.
24
25
  const paramThisType = "const jspp::AnyValue&";
25
26
  const paramArgsType = "std::span<const jspp::AnyValue>";
27
+ const selfParamPart = nativeName ? `this auto&& ${nativeName}, ` : "";
28
+ const mutablePart = nativeName ? "" : "mutable ";
26
29
  const thisArgParam = isArrow
27
30
  ? "const jspp::AnyValue&" // Arrow functions use captured 'this' or are never generators in this parser context
28
31
  : `${paramThisType} ${this.globalThisVar}`;
29
- let lambda = `${capture}(${thisArgParam}, ${paramArgsType} ${argsName}) mutable -> ${funcReturnType} `;
30
- const topLevelScopeSymbols = this.prepareScopeSymbolsForVisit(context.topLevelScopeSymbols, context.localScopeSymbols);
32
+ let lambda = `${capture}(${selfParamPart}${thisArgParam}, ${paramArgsType} ${argsName}) ${mutablePart}-> ${funcReturnType} `;
33
+ const globalScopeSymbols = this.prepareScopeSymbolsForVisit(context.globalScopeSymbols, context.localScopeSymbols);
31
34
  const visitContext = {
35
+ currentScopeNode: context.currentScopeNode,
32
36
  isMainContext: false,
33
37
  isInsideFunction: true,
34
38
  isFunctionBody: false,
35
39
  lambdaName: undefined,
36
- topLevelScopeSymbols,
40
+ globalScopeSymbols,
37
41
  localScopeSymbols: new DeclaredSymbols(),
38
42
  superClassVar: context.superClassVar,
39
43
  isInsideGeneratorFunction: isInsideGeneratorFunction,
40
44
  isInsideAsyncFunction: isInsideAsyncFunction,
41
45
  };
42
- // Name of 'this' and 'args' to be used in the body.
43
- // If generator/async, we use the copied versions.
44
- let bodyThisVar = this.globalThisVar;
45
- let bodyArgsVar = argsName;
46
46
  // Preamble to copy arguments for coroutines
47
47
  let preamble = "";
48
48
  if (isInsideGeneratorFunction || isInsideAsyncFunction) {
@@ -51,40 +51,9 @@ export function generateLambda(node, context, options) {
51
51
  if (!isArrow) {
52
52
  preamble +=
53
53
  `${this.indent()}jspp::AnyValue ${thisCopy} = ${this.globalThisVar};\n`;
54
- bodyThisVar = thisCopy;
55
54
  }
56
55
  preamble +=
57
56
  `${this.indent()}std::vector<jspp::AnyValue> ${argsCopy}(${argsName}.begin(), ${argsName}.end());\n`;
58
- bodyArgsVar = argsCopy;
59
- // Update visitContext to use the new 'this' variable if needed?
60
- // CodeGenerator.globalThisVar is a member of the class, so we can't easily change it for the recursive visit
61
- // without changing the class state or passing it down.
62
- // BUT: visitThisKeyword uses `this.globalThisVar`.
63
- // We need to temporarily swap `this.globalThisVar` or handle it.
64
- // Actually, `this.globalThisVar` is set once in `generate`.
65
- // Wait, `visitThisKeyword` uses `this.globalThisVar`.
66
- // If we change the variable name for 'this', we must ensure `visitThisKeyword` generates the correct name.
67
- // `generateLambda` does NOT create a new CodeGenerator instance, so `this.globalThisVar` is the global one.
68
- //
69
- // We need to support shadowing `globalThisVar` for the duration of the visit.
70
- // The current `CodeGenerator` doesn't seem to support changing `globalThisVar` recursively easily
71
- // because it's a property of the class, not `VisitContext`.
72
- //
73
- // However, `visitFunctionDeclaration` etc don't update `globalThisVar`.
74
- // `visitThisKeyword` just returns `this.globalThisVar`.
75
- //
76
- // If we are inside a function, `this` should refer to the function's `this`.
77
- // `this.globalThisVar` is generated in `generate()`: `__this_val__...`.
78
- // And `generateLambda` uses `this.globalThisVar` as the parameter name.
79
- //
80
- // So, if we copy it to `__this_copy`, `visitThisKeyword` will still print `__this_val__...`.
81
- // This is BAD for generators because `__this_val__...` is a reference that dies.
82
- //
83
- // FIX: We must reuse the SAME name for the local copy, and shadow the parameter.
84
- // But we can't declare a variable with the same name as the parameter in the same scope.
85
- //
86
- // We can rename the PARAMETER to something else, and declare the local variable with `this.globalThisVar`.
87
- //
88
57
  }
89
58
  // Adjust parameter names for generator/async to allow shadowing/copying
90
59
  let finalThisParamName = isArrow ? "" : this.globalThisVar;
@@ -100,7 +69,7 @@ export function generateLambda(node, context, options) {
100
69
  : `${paramThisType} ${finalThisParamName}`;
101
70
  // Re-construct lambda header with potentially new param names
102
71
  lambda =
103
- `${capture}(${thisArgParamFinal}, ${paramArgsType} ${finalArgsParamName}) mutable -> ${funcReturnType} `;
72
+ `${capture}(${selfParamPart}${thisArgParamFinal}, ${paramArgsType} ${finalArgsParamName}) ${mutablePart}-> ${funcReturnType} `;
104
73
  // Regenerate preamble
105
74
  preamble = "";
106
75
  if (isInsideGeneratorFunction || isInsideAsyncFunction) {
@@ -116,17 +85,27 @@ export function generateLambda(node, context, options) {
116
85
  // So subsequent code using `argsName` and `visit` (using `globalThisVar`) works correctly.
117
86
  const paramExtractor = (parameters) => {
118
87
  let code = "";
119
- parameters.forEach((p, i) => {
88
+ parameters.filter((p) => this.isTypescript && p.name.getText() === "this" ? false : true // Ignore "this" parameters
89
+ ).forEach((p, i) => {
120
90
  const name = p.name.getText();
121
91
  const defaultValue = p.initializer
122
92
  ? this.visit(p.initializer, visitContext)
123
93
  : "jspp::Constants::UNDEFINED";
94
+ // Catch invalid parameters
95
+ if (name === "this") {
96
+ throw new CompilerError(`Cannot use '${name}' as a parameter name.`, p, "SyntaxError");
97
+ }
98
+ // Add paramerter to local context
99
+ visitContext.localScopeSymbols.add(name, {
100
+ type: DeclarationType.let,
101
+ checks: { initialized: true },
102
+ });
124
103
  const scope = this.getScopeForNode(p);
125
104
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
126
105
  // Handle rest parameters
127
106
  if (!!p.dotDotDotToken) {
128
107
  if (parameters.length - 1 !== i) {
129
- throw new SyntaxError("Rest parameter must be last formal parameter.");
108
+ throw new CompilerError("Rest parameter must be last formal parameter.", p, "SyntaxError");
130
109
  }
131
110
  if (typeInfo.needsHeapAllocation) {
132
111
  code +=
@@ -178,7 +157,7 @@ export function generateLambda(node, context, options) {
178
157
  // Hoist var declarations in the function body
179
158
  const varDecls = collectFunctionScopedDeclarations(node.body);
180
159
  varDecls.forEach((decl) => {
181
- preamble += this.hoistDeclaration(decl, visitContext.localScopeSymbols);
160
+ preamble += this.hoistDeclaration(decl, visitContext.localScopeSymbols, node.body);
182
161
  });
183
162
  this.indentationLevel++;
184
163
  const paramExtraction = paramExtractor(node.parameters);
@@ -215,34 +194,49 @@ export function generateLambda(node, context, options) {
215
194
  else {
216
195
  lambda += `{ ${preamble} ${returnCmd} jspp::Constants::UNDEFINED; }\n`;
217
196
  }
218
- let signature = "";
219
- let callable = "";
197
+ // Return only lambda if required
198
+ if (options?.generateOnlyLambda) {
199
+ return lambda.trimEnd();
200
+ }
201
+ return this.generateFullLambdaExpression(node, context, lambda, options);
202
+ }
203
+ export function generateFullLambdaExpression(node, context, lambda, options) {
204
+ const isAssignment = options?.isAssignment || false;
205
+ const noTypeSignature = options?.noTypeSignature || false;
206
+ const isInsideGeneratorFunction = this.isGeneratorFunction(node);
207
+ const isInsideAsyncFunction = this.isAsyncFunction(node);
208
+ const isArrow = ts.isArrowFunction(node);
209
+ let callable = lambda;
220
210
  let method = "";
221
211
  // Handle generator function
222
212
  if (isInsideGeneratorFunction) {
223
213
  if (isInsideAsyncFunction) {
224
- signature =
225
- "jspp::JsAsyncIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
226
- callable = `std::function<${signature}>(${lambda})`;
214
+ if (!noTypeSignature) {
215
+ const signature = "jspp::JsAsyncIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
216
+ callable = `std::function<${signature}>(${lambda})`;
217
+ }
227
218
  method = `jspp::AnyValue::make_async_generator`;
228
219
  }
229
220
  else {
230
- signature =
231
- "jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
232
- callable = `std::function<${signature}>(${lambda})`;
221
+ if (!noTypeSignature) {
222
+ const signature = "jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
223
+ callable = `std::function<${signature}>(${lambda})`;
224
+ }
233
225
  method = `jspp::AnyValue::make_generator`;
234
226
  }
235
227
  } // Handle async function
236
228
  else if (isInsideAsyncFunction) {
237
- signature =
238
- "jspp::JsPromise(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
239
- callable = `std::function<${signature}>(${lambda})`;
229
+ if (!noTypeSignature) {
230
+ const signature = "jspp::JsPromise(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
231
+ callable = `std::function<${signature}>(${lambda})`;
232
+ }
240
233
  method = `jspp::AnyValue::make_async_function`;
241
234
  } // Handle normal function
242
235
  else {
243
- signature =
244
- `jspp::AnyValue(const jspp::AnyValue&, std::span<const jspp::AnyValue>)`;
245
- callable = `std::function<${signature}>(${lambda})`;
236
+ if (!noTypeSignature) {
237
+ const signature = `jspp::AnyValue(const jspp::AnyValue&, std::span<const jspp::AnyValue>)`;
238
+ callable = `std::function<${signature}>(${lambda})`;
239
+ }
246
240
  if (options?.isClass) {
247
241
  method = `jspp::AnyValue::make_class`;
248
242
  }