@ugo-studio/jspp 0.3.2 → 0.3.4

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.
package/README.md CHANGED
@@ -61,7 +61,7 @@ Using these keywords as variable names will result in a `SyntaxError`.
61
61
  To use JSPP as a command-line tool, install it globally via npm:
62
62
 
63
63
  ```sh
64
- npm i @ugo-studio/jspp -g
64
+ npm i @ugo-studio/jspp@latest -g
65
65
  ```
66
66
 
67
67
  ## For Developers
@@ -86,7 +86,7 @@ To contribute to JSPP or run its test suite, follow these steps:
86
86
  ```sh
87
87
  bun install
88
88
  ```
89
- *Note: The `postinstall` script will automatically check for your C++ compiler and precompile the runtime headers and library.*
89
+ *Note: The `postinstall` script will automatically check for your C++ compiler and setup emsdk for wasm support.*
90
90
 
91
91
  ## Usage
92
92
 
@@ -106,10 +106,6 @@ jspp my-code/test.ts
106
106
 
107
107
  The transpiled C++ file and executable will be generated in the same directory as the input file and cleaned up after execution (unless `--keep-cpp` is used).
108
108
 
109
- ### Timing and Reports
110
-
111
- In debug mode (default), JSPP provides a compilation time report using GCC's `-ftime-report`. This helps track the performance of the transpilation and compilation phases.
112
-
113
109
  ## Roadmap
114
110
 
115
111
  This project is ambitious, and there is a long and exciting road ahead. Here is a high-level overview of the planned features and the project's current standing.
package/dist/cli/args.js CHANGED
@@ -5,6 +5,7 @@ export function parseArgs(rawArgs) {
5
5
  let jsFilePathArg = null;
6
6
  let isRelease = false;
7
7
  let keepCpp = false;
8
+ let shouldRunOutput = false;
8
9
  let outputExePath = null;
9
10
  let scriptArgs = [];
10
11
  let target = "native";
@@ -22,6 +23,9 @@ export function parseArgs(rawArgs) {
22
23
  else if (arg === "--keep-cpp") {
23
24
  keepCpp = true;
24
25
  }
26
+ else if (arg === "--run-output") {
27
+ shouldRunOutput = true;
28
+ }
25
29
  else if (arg === "-t" || arg === "--target") {
26
30
  if (i + 1 < rawArgs.length) {
27
31
  const targetValue = rawArgs[i + 1]?.toLowerCase();
@@ -72,6 +76,7 @@ export function parseArgs(rawArgs) {
72
76
  jsFilePath: path.resolve(process.cwd(), jsFilePathArg),
73
77
  isRelease,
74
78
  keepCpp,
79
+ shouldRunOutput,
75
80
  outputExePath: outputExePath
76
81
  ? path.resolve(process.cwd(), outputExePath)
77
82
  : null,
@@ -49,5 +49,5 @@ export async function compileCpp(cppFilePath, exeFilePath, pchDir, preludePath,
49
49
  }
50
50
  const compileEndTime = performance.now();
51
51
  const compileTime = msToHumanReadable(compileEndTime - compileStartTime);
52
- spinner.succeed(`Compiled to ${COLORS.green}${COLORS.bold}${path.basename(exeFilePath)}${COLORS.reset} ${COLORS.dim}[${compileTime}]${COLORS.reset}`);
52
+ spinner.succeed(`Compiled to ${COLORS.green}${COLORS.bold}${exeFilePath}${COLORS.reset} ${COLORS.dim}[${compileTime}]${COLORS.reset}`);
53
53
  }
package/dist/cli/index.js CHANGED
@@ -17,7 +17,7 @@ const emsdkEnv = {
17
17
  PATH: `${path.join(pkgDir, ".emsdk")}${path.delimiter}${path.join(pkgDir, ".emsdk", "upstream", "emscripten")}${path.delimiter}${process.env.PATH}`,
18
18
  };
19
19
  async function main() {
20
- const { jsFilePath, isRelease, keepCpp, outputExePath, scriptArgs, target, } = parseArgs(process.argv.slice(2));
20
+ const { jsFilePath, isRelease, keepCpp, shouldRunOutput, outputExePath, scriptArgs, target, } = parseArgs(process.argv.slice(2));
21
21
  const isWasm = target === "wasm";
22
22
  const ext = path.extname(jsFilePath);
23
23
  const jsFileName = path.basename(jsFilePath, ext);
@@ -80,7 +80,9 @@ async function main() {
80
80
  }
81
81
  }
82
82
  // 4. Execution Phase
83
- await runOutput(exeFilePath, scriptArgs, isWasm);
83
+ if (shouldRunOutput) {
84
+ await runOutput(exeFilePath, scriptArgs, isWasm);
85
+ }
84
86
  }
85
87
  catch (error) {
86
88
  if (error instanceof CompilerError) {
@@ -568,7 +568,7 @@ export class TypeAnalyzer {
568
568
  };
569
569
  const visitor = {
570
570
  // Enter new scope for any block-like structure
571
- Block: {
571
+ [ts.SyntaxKind.Block]: {
572
572
  enter: (node, parent) => {
573
573
  const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
574
574
  null;
@@ -591,7 +591,7 @@ export class TypeAnalyzer {
591
591
  },
592
592
  exit: () => this.scopeManager.exitScope(),
593
593
  },
594
- ForStatement: {
594
+ [ts.SyntaxKind.ForStatement]: {
595
595
  enter: (node) => {
596
596
  this.loopDepth++;
597
597
  const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
@@ -604,7 +604,7 @@ export class TypeAnalyzer {
604
604
  this.scopeManager.exitScope();
605
605
  },
606
606
  },
607
- ForOfStatement: {
607
+ [ts.SyntaxKind.ForOfStatement]: {
608
608
  enter: (node) => {
609
609
  this.loopDepth++;
610
610
  const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
@@ -617,7 +617,7 @@ export class TypeAnalyzer {
617
617
  this.scopeManager.exitScope();
618
618
  },
619
619
  },
620
- ForInStatement: {
620
+ [ts.SyntaxKind.ForInStatement]: {
621
621
  enter: (node) => {
622
622
  this.loopDepth++;
623
623
  const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
@@ -649,7 +649,7 @@ export class TypeAnalyzer {
649
649
  this.scopeManager.exitScope();
650
650
  },
651
651
  },
652
- WhileStatement: {
652
+ [ts.SyntaxKind.WhileStatement]: {
653
653
  enter: (node) => {
654
654
  this.loopDepth++;
655
655
  const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
@@ -662,7 +662,7 @@ export class TypeAnalyzer {
662
662
  this.scopeManager.exitScope();
663
663
  },
664
664
  },
665
- DoStatement: {
665
+ [ts.SyntaxKind.DoStatement]: {
666
666
  enter: (node) => {
667
667
  this.loopDepth++;
668
668
  const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
@@ -675,7 +675,7 @@ export class TypeAnalyzer {
675
675
  this.scopeManager.exitScope();
676
676
  },
677
677
  },
678
- SwitchStatement: {
678
+ [ts.SyntaxKind.SwitchStatement]: {
679
679
  enter: (node) => {
680
680
  this.switchDepth++;
681
681
  const currentFuncNode = this.functionStack[this.functionStack.length - 1] ??
@@ -688,7 +688,7 @@ export class TypeAnalyzer {
688
688
  this.scopeManager.exitScope();
689
689
  },
690
690
  },
691
- LabeledStatement: {
691
+ [ts.SyntaxKind.LabeledStatement]: {
692
692
  enter: (node) => {
693
693
  this.nodeToScope.set(node, this.scopeManager.currentScope);
694
694
  this.labelStack.push(node.label.text);
@@ -697,7 +697,7 @@ export class TypeAnalyzer {
697
697
  this.labelStack.pop();
698
698
  },
699
699
  },
700
- BreakStatement: {
700
+ [ts.SyntaxKind.BreakStatement]: {
701
701
  enter: (node) => {
702
702
  const breakNode = node;
703
703
  if (breakNode.label) {
@@ -712,7 +712,7 @@ export class TypeAnalyzer {
712
712
  }
713
713
  },
714
714
  },
715
- ContinueStatement: {
715
+ [ts.SyntaxKind.ContinueStatement]: {
716
716
  enter: (node) => {
717
717
  const continueNode = node;
718
718
  if (continueNode.label) {
@@ -729,7 +729,7 @@ export class TypeAnalyzer {
729
729
  }
730
730
  },
731
731
  },
732
- ArrowFunction: {
732
+ [ts.SyntaxKind.ArrowFunction]: {
733
733
  enter: (node) => {
734
734
  if (ts.isArrowFunction(node)) {
735
735
  const funcType = {
@@ -760,7 +760,7 @@ export class TypeAnalyzer {
760
760
  this.scopeManager.exitScope();
761
761
  },
762
762
  },
763
- FunctionExpression: {
763
+ [ts.SyntaxKind.FunctionExpression]: {
764
764
  enter: (node) => {
765
765
  if (ts.isFunctionExpression(node)) {
766
766
  const funcType = {
@@ -795,7 +795,7 @@ export class TypeAnalyzer {
795
795
  this.scopeManager.exitScope();
796
796
  },
797
797
  },
798
- FunctionDeclaration: {
798
+ [ts.SyntaxKind.FunctionDeclaration]: {
799
799
  enter: (node) => {
800
800
  if (ts.isFunctionDeclaration(node)) {
801
801
  // Define the function in the current scope.
@@ -831,7 +831,44 @@ export class TypeAnalyzer {
831
831
  this.scopeManager.exitScope();
832
832
  },
833
833
  },
834
- ClassDeclaration: {
834
+ [ts.SyntaxKind.GetAccessor]: {
835
+ enter: (node) => {
836
+ if (ts.isGetAccessorDeclaration(node)) {
837
+ this.scopeManager.enterScope(node);
838
+ this.nodeToScope.set(node, this.scopeManager.currentScope);
839
+ this.functionStack.push(node);
840
+ }
841
+ },
842
+ exit: (node) => {
843
+ if (ts.isGetAccessorDeclaration(node)) {
844
+ this.functionStack.pop();
845
+ }
846
+ this.scopeManager.exitScope();
847
+ },
848
+ },
849
+ [ts.SyntaxKind.SetAccessor]: {
850
+ enter: (node) => {
851
+ if (ts.isSetAccessorDeclaration(node)) {
852
+ this.scopeManager.enterScope(node);
853
+ this.nodeToScope.set(node, this.scopeManager.currentScope);
854
+ // Define parameters in the new scope
855
+ node.parameters.forEach((p) => {
856
+ if (p.getText() == "this") { // Catch invalid parameters
857
+ throw new CompilerError("Cannot use 'this' as a parameter name.", p, "SyntaxError");
858
+ }
859
+ this.defineParameter(p.name, p);
860
+ });
861
+ this.functionStack.push(node);
862
+ }
863
+ },
864
+ exit: (node) => {
865
+ if (ts.isSetAccessorDeclaration(node)) {
866
+ this.functionStack.pop();
867
+ }
868
+ this.scopeManager.exitScope();
869
+ },
870
+ },
871
+ [ts.SyntaxKind.ClassDeclaration]: {
835
872
  enter: (node) => {
836
873
  const classNode = node;
837
874
  if (classNode.name) {
@@ -853,7 +890,7 @@ export class TypeAnalyzer {
853
890
  this.scopeManager.exitScope();
854
891
  },
855
892
  },
856
- EnumDeclaration: {
893
+ [ts.SyntaxKind.EnumDeclaration]: {
857
894
  enter: (node) => {
858
895
  const enumNode = node;
859
896
  if (enumNode.name) {
@@ -872,7 +909,7 @@ export class TypeAnalyzer {
872
909
  this.nodeToScope.set(node, this.scopeManager.currentScope);
873
910
  },
874
911
  },
875
- MethodDeclaration: {
912
+ [ts.SyntaxKind.MethodDeclaration]: {
876
913
  enter: (node) => {
877
914
  if (ts.isMethodDeclaration(node)) {
878
915
  const funcType = {
@@ -898,7 +935,7 @@ export class TypeAnalyzer {
898
935
  this.scopeManager.exitScope();
899
936
  },
900
937
  },
901
- Constructor: {
938
+ [ts.SyntaxKind.Constructor]: {
902
939
  enter: (node) => {
903
940
  if (ts.isConstructorDeclaration(node)) {
904
941
  const funcType = {
@@ -923,7 +960,7 @@ export class TypeAnalyzer {
923
960
  this.scopeManager.exitScope();
924
961
  },
925
962
  },
926
- VariableDeclaration: {
963
+ [ts.SyntaxKind.VariableDeclaration]: {
927
964
  enter: (node) => {
928
965
  if (ts.isVariableDeclaration(node)) {
929
966
  // Check if it is an ambient declaration (declare var/let/const ...)
@@ -990,8 +1027,8 @@ export class TypeAnalyzer {
990
1027
  }
991
1028
  },
992
1029
  },
993
- Identifier: {
994
- enter: (node, parent) => {
1030
+ [ts.SyntaxKind.Identifier]: {
1031
+ enter: (node) => {
995
1032
  if (ts.isIdentifier(node)) {
996
1033
  if (isBuiltinObject.call(this, node))
997
1034
  return;
@@ -1027,7 +1064,7 @@ export class TypeAnalyzer {
1027
1064
  }
1028
1065
  },
1029
1066
  },
1030
- BinaryExpression: {
1067
+ [ts.SyntaxKind.BinaryExpression]: {
1031
1068
  enter: (node) => {
1032
1069
  if (ts.isBinaryExpression(node)) {
1033
1070
  const isAssignment = node.operatorToken.kind >=
@@ -1050,7 +1087,7 @@ export class TypeAnalyzer {
1050
1087
  }
1051
1088
  },
1052
1089
  },
1053
- CallExpression: {
1090
+ [ts.SyntaxKind.CallExpression]: {
1054
1091
  enter: (node) => {
1055
1092
  if (ts.isCallExpression(node)) {
1056
1093
  const callee = node.expression;
@@ -1070,7 +1107,7 @@ export class TypeAnalyzer {
1070
1107
  }
1071
1108
  },
1072
1109
  },
1073
- ReturnStatement: {
1110
+ [ts.SyntaxKind.ReturnStatement]: {
1074
1111
  enter: (node) => {
1075
1112
  if (ts.isReturnStatement(node) && node.expression) {
1076
1113
  const currentFuncNode = this.functionStack[this.functionStack.length - 1];
@@ -1086,14 +1123,14 @@ export class TypeAnalyzer {
1086
1123
  }
1087
1124
  },
1088
1125
  },
1089
- PostfixUnaryExpression: {
1126
+ [ts.SyntaxKind.PostfixUnaryExpression]: {
1090
1127
  enter: (node) => {
1091
1128
  if (ts.isPostfixUnaryExpression(node)) {
1092
1129
  crossScopeModificationVisitor(node.operand);
1093
1130
  }
1094
1131
  },
1095
1132
  },
1096
- PrefixUnaryExpression: {
1133
+ [ts.SyntaxKind.PrefixUnaryExpression]: {
1097
1134
  enter: (node) => {
1098
1135
  if (ts.isPrefixUnaryExpression(node)) {
1099
1136
  const op = node.operator;
@@ -153,16 +153,19 @@ export function visitForInStatement(node, context) {
153
153
  derefExpr = this.getDerefCode(exprText, this.getJsVarName(expr), context, typeInfo);
154
154
  }
155
155
  const keysVar = this.generateUniqueName("__keys_", new Set([varName]));
156
+ const visitContext = {
157
+ ...context,
158
+ globalScopeSymbols: this.prepareScopeSymbolsForVisit(context.globalScopeSymbols, context.localScopeSymbols),
159
+ localScopeSymbols: new DeclaredSymbols(),
160
+ currentLabel: undefined,
161
+ isFunctionBody: false,
162
+ };
156
163
  code +=
157
164
  `${this.indent()}std::vector<jspp::AnyValue> ${keysVar} = jspp::Access::get_object_keys(${derefExpr});\n`;
158
165
  code += `${this.indent()}for (const auto& ${varName}_val : ${keysVar}) {\n`;
159
166
  this.indentationLevel++;
160
167
  code += `${this.indent()}${assignmentTarget} = ${varName}_val;\n`;
161
- code += this.visit(forIn.statement, {
162
- ...context,
163
- currentLabel: undefined,
164
- isFunctionBody: false,
165
- });
168
+ code += this.visit(forIn.statement, visitContext);
166
169
  this.indentationLevel--;
167
170
  if (context.currentLabel) {
168
171
  code += `${this.indent()}${context.currentLabel}_continue:;\n`;
@@ -188,6 +191,7 @@ export function visitForOfStatement(node, context) {
188
191
  this.indentationLevel++; // Enter a new scope for the for-of loop
189
192
  let elemName = "";
190
193
  let assignmentTarget = "";
194
+ let declarationType = DeclarationType.const;
191
195
  code += `${this.indent()}{\n`;
192
196
  if (ts.isVariableDeclarationList(forOf.initializer)) {
193
197
  const decl = forOf.initializer.declarations[0];
@@ -205,6 +209,11 @@ export function visitForOfStatement(node, context) {
205
209
  `${this.indent()}jspp::AnyValue ${elemName} = jspp::Constants::UNDEFINED;\n`;
206
210
  assignmentTarget = elemName;
207
211
  }
212
+ declarationType = (decl.parent.flags & (ts.NodeFlags.Let)) !== 0
213
+ ? DeclarationType.let
214
+ : (decl.parent.flags & (ts.NodeFlags.Const)) !== 0
215
+ ? DeclarationType.const
216
+ : DeclarationType.var;
208
217
  }
209
218
  }
210
219
  else if (ts.isIdentifier(forOf.initializer)) {
@@ -214,6 +223,12 @@ export function visitForOfStatement(node, context) {
214
223
  assignmentTarget = typeInfo?.needsHeapAllocation
215
224
  ? `*${elemName}`
216
225
  : elemName;
226
+ declarationType =
227
+ (forOf.initializer.parent.flags & (ts.NodeFlags.Let)) !== 0
228
+ ? DeclarationType.let
229
+ : (forOf.initializer.parent.flags & (ts.NodeFlags.Const)) !== 0
230
+ ? DeclarationType.const
231
+ : DeclarationType.var;
217
232
  }
218
233
  const iterableExpr = this.visit(forOf.expression, context);
219
234
  let derefIterable = iterableExpr;
@@ -230,6 +245,19 @@ export function visitForOfStatement(node, context) {
230
245
  const nextRes = this.generateUniqueName("__next_res", declaredSymbols);
231
246
  const isAwait = forOf.awaitModifier !== undefined;
232
247
  const varName = this.getJsVarName(forOf.expression);
248
+ const visitContext = {
249
+ ...context,
250
+ globalScopeSymbols: this.prepareScopeSymbolsForVisit(context.globalScopeSymbols, context.localScopeSymbols),
251
+ localScopeSymbols: new DeclaredSymbols(),
252
+ currentLabel: undefined,
253
+ isFunctionBody: false,
254
+ };
255
+ if (elemName) {
256
+ visitContext.localScopeSymbols.add(elemName, {
257
+ type: declarationType,
258
+ checks: { initialized: true },
259
+ });
260
+ }
233
261
  code += `${this.indent()}auto ${iterableRef} = ${derefIterable};\n`;
234
262
  if (isAwait) {
235
263
  code +=
@@ -254,11 +282,7 @@ export function visitForOfStatement(node, context) {
254
282
  this.indentationLevel++;
255
283
  code +=
256
284
  `${this.indent()}${assignmentTarget} = ${nextRes}.get_own_property("value");\n`;
257
- code += this.visit(forOf.statement, {
258
- ...context,
259
- currentLabel: undefined,
260
- isFunctionBody: false,
261
- });
285
+ code += this.visit(forOf.statement, visitContext);
262
286
  if (context.currentLabel) {
263
287
  code += `${this.indent()}${context.currentLabel}_continue:;\n`;
264
288
  }
@@ -247,6 +247,17 @@ export function visitPrefixUnaryExpression(node, context) {
247
247
  const operand = this.visit(prefixUnaryExpr.operand, context);
248
248
  const operator = ts.tokenToString(prefixUnaryExpr.operator);
249
249
  if (operator === "++" || operator === "--") {
250
+ const opFunc = operator === "++" ? "add" : "sub";
251
+ if (ts.isPropertyAccessExpression(prefixUnaryExpr.operand)) {
252
+ const obj = this.visit(prefixUnaryExpr.operand.expression, context);
253
+ const prop = visitObjectPropertyName.call(this, prefixUnaryExpr.operand.name, context);
254
+ return `${obj}.set_own_property(${prop}, jspp::${opFunc}(${obj}.get_own_property(${prop}), 1.0))`;
255
+ }
256
+ else if (ts.isElementAccessExpression(prefixUnaryExpr.operand)) {
257
+ const obj = this.visit(prefixUnaryExpr.operand.expression, context);
258
+ const prop = visitObjectPropertyName.call(this, prefixUnaryExpr.operand.argumentExpression, { ...context, isBracketNotationPropertyAccess: true });
259
+ return `${obj}.set_own_property(${prop}, jspp::${opFunc}(${obj}.get_own_property(${prop}), 1.0))`;
260
+ }
250
261
  let target = operand;
251
262
  if (ts.isIdentifier(prefixUnaryExpr.operand)) {
252
263
  const scope = this.getScopeForNode(prefixUnaryExpr.operand);
@@ -289,6 +300,20 @@ export function visitPostfixUnaryExpression(node, context) {
289
300
  const postfixUnaryExpr = node;
290
301
  const operand = this.visit(postfixUnaryExpr.operand, context);
291
302
  const operator = ts.tokenToString(postfixUnaryExpr.operator);
303
+ if (ts.isPropertyAccessExpression(postfixUnaryExpr.operand)) {
304
+ const obj = this.visit(postfixUnaryExpr.operand.expression, context);
305
+ const prop = visitObjectPropertyName.call(this, postfixUnaryExpr.operand.name, context);
306
+ const opFunc = operator === "++" ? "add" : "sub";
307
+ // Postfix needs IILE to return old value
308
+ return `([&]() { auto oldVal = ${obj}.get_own_property(${prop}); ${obj}.set_own_property(${prop}, jspp::${opFunc}(oldVal, 1.0)); return oldVal; })()`;
309
+ }
310
+ else if (ts.isElementAccessExpression(postfixUnaryExpr.operand)) {
311
+ const obj = this.visit(postfixUnaryExpr.operand.expression, context);
312
+ const prop = visitObjectPropertyName.call(this, postfixUnaryExpr.operand.argumentExpression, { ...context, isBracketNotationPropertyAccess: true });
313
+ const opFunc = operator === "++" ? "add" : "sub";
314
+ // Postfix needs IILE to return old value
315
+ return `([&]() { auto oldVal = ${obj}.get_own_property(${prop}); ${obj}.set_own_property(${prop}, jspp::${opFunc}(oldVal, 1.0)); return oldVal; })()`;
316
+ }
292
317
  let target = operand;
293
318
  if (ts.isIdentifier(postfixUnaryExpr.operand)) {
294
319
  const scope = this.getScopeForNode(postfixUnaryExpr.operand);
@@ -402,42 +427,36 @@ export function visitBinaryExpression(node, context) {
402
427
  if (assignmentOperators.includes(opToken.kind)) {
403
428
  if (opToken.kind ===
404
429
  ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken) {
405
- const leftText = this.visit(binExpr.left, visitContext);
406
430
  const rightText = this.visit(binExpr.right, visitContext);
407
- let target = leftText;
408
431
  if (ts.isIdentifier(binExpr.left)) {
432
+ const leftText = this.visit(binExpr.left, visitContext);
409
433
  const scope = this.getScopeForNode(binExpr.left);
410
434
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.text, scope);
411
- target = typeInfo?.needsHeapAllocation
435
+ const target = typeInfo?.needsHeapAllocation
412
436
  ? `*${leftText}`
413
437
  : leftText;
414
- return `${target} = jspp::unsigned_right_shift(${target}, ${rightText})`;
438
+ return `jspp::unsigned_right_shift_assign(${target}, ${rightText})`;
415
439
  }
416
- }
417
- if (opToken.kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken) {
418
- const leftText = this.visit(binExpr.left, visitContext);
419
- const rightText = this.visit(binExpr.right, visitContext);
420
- let target = leftText;
421
- if (ts.isIdentifier(binExpr.left)) {
422
- const scope = this.getScopeForNode(binExpr.left);
423
- const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.text, scope);
424
- target = typeInfo?.needsHeapAllocation
425
- ? `*${leftText}`
426
- : leftText;
427
- return `${target} = jspp::pow(${target}, ${rightText})`;
440
+ else if (ts.isPropertyAccessExpression(binExpr.left)) {
441
+ const obj = this.visit(binExpr.left.expression, context);
442
+ const prop = visitObjectPropertyName.call(this, binExpr.left.name, context);
443
+ return `${obj}.set_own_property(${prop}, jspp::unsigned_right_shift(${obj}.get_own_property(${prop}), ${rightText}))`;
444
+ }
445
+ else if (ts.isElementAccessExpression(binExpr.left)) {
446
+ const obj = this.visit(binExpr.left.expression, context);
447
+ const prop = visitObjectPropertyName.call(this, binExpr.left.argumentExpression, { ...context, isBracketNotationPropertyAccess: true });
448
+ return `${obj}.set_own_property(${prop}, jspp::unsigned_right_shift(${obj}.get_own_property(${prop}), ${rightText}))`;
428
449
  }
429
- // For complex LHS, we need a different approach, but this is a start.
430
450
  }
431
451
  if (opToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
432
452
  opToken.kind === ts.SyntaxKind.BarBarEqualsToken ||
433
453
  opToken.kind === ts.SyntaxKind.QuestionQuestionEqualsToken) {
434
- const leftText = this.visit(binExpr.left, visitContext);
435
454
  const rightText = this.visit(binExpr.right, visitContext);
436
- let target = leftText;
437
455
  if (ts.isIdentifier(binExpr.left)) {
456
+ const leftText = this.visit(binExpr.left, visitContext);
438
457
  const scope = this.getScopeForNode(binExpr.left);
439
458
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.text, scope);
440
- target = typeInfo?.needsHeapAllocation
459
+ const target = typeInfo?.needsHeapAllocation
441
460
  ? `*${leftText}`
442
461
  : leftText;
443
462
  if (opToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken) {
@@ -450,6 +469,29 @@ export function visitBinaryExpression(node, context) {
450
469
  return `jspp::nullish_coalesce_assign(${target}, ${rightText})`;
451
470
  }
452
471
  }
472
+ else if (ts.isPropertyAccessExpression(binExpr.left)) {
473
+ const obj = this.visit(binExpr.left.expression, context);
474
+ const prop = visitObjectPropertyName.call(this, binExpr.left.name, context);
475
+ const func = opToken.kind ===
476
+ ts.SyntaxKind.AmpersandAmpersandEqualsToken
477
+ ? "logical_and"
478
+ : (opToken.kind === ts.SyntaxKind.BarBarEqualsToken
479
+ ? "logical_or"
480
+ : "nullish_coalesce");
481
+ // Logical assignments return newVal, set_own_property returns newVal.
482
+ return `${obj}.set_own_property(${prop}, jspp::${func}(${obj}.get_own_property(${prop}), ${rightText}))`;
483
+ }
484
+ else if (ts.isElementAccessExpression(binExpr.left)) {
485
+ const obj = this.visit(binExpr.left.expression, context);
486
+ const prop = visitObjectPropertyName.call(this, binExpr.left.argumentExpression, { ...context, isBracketNotationPropertyAccess: true });
487
+ const func = opToken.kind ===
488
+ ts.SyntaxKind.AmpersandAmpersandEqualsToken
489
+ ? "logical_and"
490
+ : (opToken.kind === ts.SyntaxKind.BarBarEqualsToken
491
+ ? "logical_or"
492
+ : "nullish_coalesce");
493
+ return `${obj}.set_own_property(${prop}, jspp::${func}(${obj}.get_own_property(${prop}), ${rightText}))`;
494
+ }
453
495
  }
454
496
  const leftText = this.visit(binExpr.left, visitContext);
455
497
  let rightText = ts.isNumericLiteral(binExpr.right)
@@ -460,6 +502,35 @@ export function visitBinaryExpression(node, context) {
460
502
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.right.getText(), scope);
461
503
  rightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, typeInfo);
462
504
  }
505
+ if (ts.isPropertyAccessExpression(binExpr.left)) {
506
+ const obj = this.visit(binExpr.left.expression, context);
507
+ const prop = visitObjectPropertyName.call(this, binExpr.left.name, context);
508
+ const opBase = op.slice(0, -1); // "+=", "-=" -> "+", "-"
509
+ const opFunc = opBase === "+" ? "add" : (opBase === "-" ? "sub" : (opBase === "*" ? "mul" : (opBase === "/" ? "div" : (opBase === "%" ? "mod" : (opBase === "&" ? "bitwise_and" : (opBase === "|" ? "bitwise_or" : (opBase === "^" ? "bitwise_xor" : (opBase === "<<" ? "left_shift" : (opBase === ">>"
510
+ ? "right_shift"
511
+ : "")))))))));
512
+ if (opFunc) {
513
+ return `${obj}.set_own_property(${prop}, jspp::${opFunc}(${obj}.get_own_property(${prop}), ${rightText}))`;
514
+ }
515
+ else {
516
+ // Fallback to IILE if we don't have an opFunc mapping
517
+ return `([&]() { auto val = ${obj}.get_own_property(${prop}); val ${op} ${rightText}; ${obj}.set_own_property(${prop}, val); return val; })()`;
518
+ }
519
+ }
520
+ else if (ts.isElementAccessExpression(binExpr.left)) {
521
+ const obj = this.visit(binExpr.left.expression, context);
522
+ const prop = visitObjectPropertyName.call(this, binExpr.left.argumentExpression, { ...context, isBracketNotationPropertyAccess: true });
523
+ const opBase = op.slice(0, -1);
524
+ const opFunc = opBase === "+" ? "add" : (opBase === "-" ? "sub" : (opBase === "*" ? "mul" : (opBase === "/" ? "div" : (opBase === "%" ? "mod" : (opBase === "&" ? "bitwise_and" : (opBase === "|" ? "bitwise_or" : (opBase === "^" ? "bitwise_xor" : (opBase === "<<" ? "left_shift" : (opBase === ">>"
525
+ ? "right_shift"
526
+ : "")))))))));
527
+ if (opFunc) {
528
+ return `${obj}.set_own_property(${prop}, jspp::${opFunc}(${obj}.get_own_property(${prop}), ${rightText}))`;
529
+ }
530
+ else {
531
+ return `([&]() { auto val = ${obj}.get_own_property(${prop}); val ${op} ${rightText}; ${obj}.set_own_property(${prop}, val); return val; })()`;
532
+ }
533
+ }
463
534
  let target = leftText;
464
535
  if (ts.isIdentifier(binExpr.left)) {
465
536
  const scope = this.getScopeForNode(binExpr.left);
@@ -1266,7 +1337,7 @@ export function visitTypeOfExpression(node, context) {
1266
1337
  derefExpr = this.getDerefCode(exprText, this.getJsVarName(typeOfExpr.expression), context, typeInfo);
1267
1338
  }
1268
1339
  }
1269
- return `jspp::Access::type_of(${derefExpr})`;
1340
+ return `jspp::type_of(${derefExpr})`;
1270
1341
  }
1271
1342
  export function visitAwaitExpression(node, context) {
1272
1343
  const awaitExpr = node;
@@ -123,7 +123,7 @@ export function generateLambdaComponents(node, context, options) {
123
123
  }
124
124
  });
125
125
  // Extract lambda parameters from arguments span/vector
126
- const generateParamsBuilder = () => {
126
+ const wrappedFuncParamsBuilder = () => {
127
127
  let paramsCode = "";
128
128
  this.validateFunctionParams(node.parameters).forEach((p, i) => {
129
129
  if (ts.isIdentifier(p.name)) {
@@ -152,14 +152,19 @@ export function generateLambdaComponents(node, context, options) {
152
152
  return;
153
153
  }
154
154
  // Normal parameter
155
- const initValue = `${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue}`;
156
- if (typeInfo?.needsHeapAllocation) {
157
- paramsCode +=
158
- `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initValue});\n`;
159
- }
160
- else {
161
- paramsCode +=
162
- `${this.indent()}jspp::AnyValue ${name} = ${initValue};\n`;
155
+ const isUsedInFuncBody = !!p.initializer ||
156
+ this.isDeclarationUsedAsValue(p, node) ||
157
+ this.isDeclarationCalledAsFunction(p, node);
158
+ if (isUsedInFuncBody) {
159
+ const initValue = `${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue}`;
160
+ if (typeInfo?.needsHeapAllocation) {
161
+ paramsCode +=
162
+ `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initValue});\n`;
163
+ }
164
+ else {
165
+ paramsCode +=
166
+ `${this.indent()}jspp::AnyValue ${name} = ${initValue};\n`;
167
+ }
163
168
  }
164
169
  }
165
170
  else {
@@ -225,7 +230,7 @@ export function generateLambdaComponents(node, context, options) {
225
230
  }
226
231
  });
227
232
  this.indentationLevel++;
228
- paramsContent += generateParamsBuilder();
233
+ paramsContent += wrappedFuncParamsBuilder();
229
234
  this.indentationLevel--;
230
235
  // The block visitor already adds braces, so we need to remove the opening brace to inject the preamble and param extraction.
231
236
  const properIndentationLevel = this.indentationLevel;
@@ -250,7 +255,7 @@ export function generateLambdaComponents(node, context, options) {
250
255
  }
251
256
  else {
252
257
  this.indentationLevel++;
253
- paramsContent += generateParamsBuilder();
258
+ paramsContent += wrappedFuncParamsBuilder();
254
259
  const properIndentationLevel = this.indentationLevel;
255
260
  const nodeBody = node.body;
256
261
  getBlockContentWithoutOpeningBrace = (isInsideNativeLambda) => {
@@ -799,7 +799,11 @@ export function findEnclosingFunctionDeclarationFromReturnStatement(node) {
799
799
  let current = node;
800
800
  let foundReturn = false;
801
801
  while (current) {
802
- if (ts.isFunctionDeclaration(current)) {
802
+ if (ts.isFunctionDeclaration(current) ||
803
+ ts.isFunctionExpression(current) ||
804
+ ts.isArrowFunction(current) ||
805
+ ts.isMethodDeclaration(current) ||
806
+ ts.isConstructorDeclaration(current)) {
803
807
  if (foundReturn) {
804
808
  return current;
805
809
  }