@ugo-studio/jspp 0.1.3 → 0.1.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.
Files changed (69) hide show
  1. package/README.md +2 -2
  2. package/dist/analysis/scope.js +16 -4
  3. package/dist/analysis/typeAnalyzer.js +253 -20
  4. package/dist/ast/types.js +6 -0
  5. package/dist/cli.js +1 -2
  6. package/dist/core/codegen/class-handlers.js +127 -0
  7. package/dist/core/codegen/control-flow-handlers.js +464 -0
  8. package/dist/core/codegen/declaration-handlers.js +31 -14
  9. package/dist/core/codegen/expression-handlers.js +429 -117
  10. package/dist/core/codegen/function-handlers.js +91 -15
  11. package/dist/core/codegen/helpers.js +66 -2
  12. package/dist/core/codegen/index.js +17 -6
  13. package/dist/core/codegen/literal-handlers.js +3 -0
  14. package/dist/core/codegen/statement-handlers.js +133 -204
  15. package/dist/core/codegen/visitor.js +29 -3
  16. package/package.json +3 -3
  17. package/src/prelude/any_value.hpp +658 -634
  18. package/src/prelude/any_value_access.hpp +103 -0
  19. package/src/prelude/any_value_defines.hpp +151 -0
  20. package/src/prelude/any_value_helpers.hpp +246 -225
  21. package/src/prelude/exception.hpp +31 -0
  22. package/src/prelude/exception_helpers.hpp +49 -0
  23. package/src/prelude/index.hpp +18 -9
  24. package/src/prelude/library/console.hpp +13 -13
  25. package/src/prelude/library/error.hpp +111 -0
  26. package/src/prelude/library/global.hpp +15 -4
  27. package/src/prelude/library/performance.hpp +2 -2
  28. package/src/prelude/library/promise.hpp +121 -0
  29. package/src/prelude/library/symbol.hpp +3 -4
  30. package/src/prelude/library/timer.hpp +92 -0
  31. package/src/prelude/scheduler.hpp +145 -0
  32. package/src/prelude/types.hpp +10 -1
  33. package/src/prelude/utils/access.hpp +174 -0
  34. package/src/prelude/utils/log_any_value/array.hpp +245 -0
  35. package/src/prelude/utils/log_any_value/config.hpp +32 -0
  36. package/src/prelude/utils/log_any_value/function.hpp +37 -0
  37. package/src/prelude/utils/log_any_value/fwd.hpp +15 -0
  38. package/src/prelude/utils/log_any_value/helpers.hpp +62 -0
  39. package/src/prelude/utils/log_any_value/log_any_value.hpp +94 -0
  40. package/src/prelude/utils/log_any_value/object.hpp +119 -0
  41. package/src/prelude/utils/log_any_value/primitives.hpp +41 -0
  42. package/src/prelude/{operators.hpp → utils/operators.hpp} +29 -10
  43. package/src/prelude/{well_known_symbols.hpp → utils/well_known_symbols.hpp} +0 -1
  44. package/src/prelude/values/array.hpp +3 -2
  45. package/src/prelude/{descriptors.hpp → values/descriptors.hpp} +2 -2
  46. package/src/prelude/values/function.hpp +76 -51
  47. package/src/prelude/values/helpers/array.hpp +20 -11
  48. package/src/prelude/values/helpers/function.hpp +125 -77
  49. package/src/prelude/values/helpers/iterator.hpp +13 -7
  50. package/src/prelude/values/helpers/object.hpp +36 -6
  51. package/src/prelude/values/helpers/promise.hpp +181 -0
  52. package/src/prelude/values/helpers/string.hpp +3 -3
  53. package/src/prelude/values/helpers/symbol.hpp +2 -2
  54. package/src/prelude/values/iterator.hpp +13 -5
  55. package/src/prelude/values/object.hpp +6 -2
  56. package/src/prelude/values/promise.hpp +73 -0
  57. package/src/prelude/values/prototypes/array.hpp +16 -16
  58. package/src/prelude/values/prototypes/function.hpp +4 -4
  59. package/src/prelude/values/prototypes/iterator.hpp +11 -10
  60. package/src/prelude/values/prototypes/object.hpp +26 -0
  61. package/src/prelude/values/prototypes/promise.hpp +124 -0
  62. package/src/prelude/values/prototypes/string.hpp +26 -26
  63. package/src/prelude/values/prototypes/symbol.hpp +5 -3
  64. package/src/prelude/values/string.hpp +1 -1
  65. package/src/prelude/values/symbol.hpp +1 -1
  66. package/src/prelude/access.hpp +0 -91
  67. package/src/prelude/error.hpp +0 -31
  68. package/src/prelude/error_helpers.hpp +0 -59
  69. package/src/prelude/log_string.hpp +0 -407
package/README.md CHANGED
@@ -132,15 +132,15 @@ This phase will broaden the range of supported JavaScript syntax and features.
132
132
  - [x] **Objects:** Literals, property access (dot and bracket notation), methods.
133
133
  - [x] **Arrays:** Literals, indexing, and core methods (`.push`, `.pop`, `.length`, etc.).
134
134
  - [x] **Operators:** Full suite of arithmetic, logical, and comparison operators.
135
- - [ ] **Control Flow:** tenary operators, `for` loops, `while` loops, `switch`.
135
+ - [x] **Control Flow:** tenary operators, `for` loops, `while` loops, `switch`.
136
136
 
137
137
  ### **Phase 3: Interoperability & Standard Library**
138
138
 
139
139
  This phase will focus on building out the standard library and enabling modular code.
140
140
 
141
141
  - [ ] **JS Standard Library:** Implementation of common built-in objects and functions (`Math`, `Date`, `String.prototype.*`, `Array.prototype.*`).
142
+ - [x] **Asynchronous Operations:** A C++ implementation of the JavaScript event loop, `Promise`, and `async/await`.
142
143
  - [ ] **Module System:** Support for `import` and `export` to transpile multi-file projects.
143
- - [ ] **Asynchronous Operations:** A C++ implementation of the JavaScript event loop, `Promise`, and `async/await`.
144
144
 
145
145
  ### **Phase 4: Optimization & Advanced Features**
146
146
 
@@ -1,8 +1,10 @@
1
+ import * as ts from "typescript";
1
2
  export const RESERVED_KEYWORDS = new Set([
2
3
  "jspp",
3
4
  "std",
4
5
  "co_yield",
5
6
  "co_return",
7
+ "co_await",
6
8
  ]);
7
9
  export const BUILTIN_OBJECTS = new Set([
8
10
  { name: "undefined", isConst: true },
@@ -12,13 +14,22 @@ export const BUILTIN_OBJECTS = new Set([
12
14
  { name: "performance", isConst: false },
13
15
  { name: "global", isConst: false },
14
16
  { name: "globalThis", isConst: false },
17
+ { name: "Error", isConst: false },
18
+ { name: "Promise", isConst: false },
19
+ { name: "setTimeout", isConst: false },
20
+ { name: "clearTimeout", isConst: false },
21
+ { name: "setInterval", isConst: false },
22
+ { name: "clearInterval", isConst: false },
15
23
  ]);
16
24
  // Represents a single scope (e.g., a function body or a block statement)
17
25
  export class Scope {
18
26
  parent;
27
+ ownerFunction;
19
28
  symbols = new Map();
20
- constructor(parent) {
29
+ children = [];
30
+ constructor(parent, ownerFunction) {
21
31
  this.parent = parent;
32
+ this.ownerFunction = ownerFunction;
22
33
  }
23
34
  // Defines a variable in this scope.
24
35
  define(name, type) {
@@ -42,7 +53,7 @@ export class ScopeManager {
42
53
  allScopes = []; // Array to store all created scopes
43
54
  reservedKeywords = new Set(RESERVED_KEYWORDS);
44
55
  constructor() {
45
- const rootScope = new Scope(null); // The global scope
56
+ const rootScope = new Scope(null, null); // The global scope
46
57
  this.currentScope = rootScope;
47
58
  this.allScopes.push(rootScope); // Add the root scope to our list
48
59
  for (const { name, isConst } of BUILTIN_OBJECTS) {
@@ -54,8 +65,9 @@ export class ScopeManager {
54
65
  }
55
66
  }
56
67
  // Enters a new, nested scope.
57
- enterScope() {
58
- const newScope = new Scope(this.currentScope);
68
+ enterScope(ownerFunction) {
69
+ const newScope = new Scope(this.currentScope, ownerFunction);
70
+ this.currentScope.children.push(newScope);
59
71
  this.currentScope = newScope;
60
72
  this.allScopes.push(newScope); // Add every new scope to the list
61
73
  }
@@ -8,13 +8,34 @@ export class TypeAnalyzer {
8
8
  functionTypeInfo = new Map();
9
9
  functionStack = [];
10
10
  nodeToScope = new Map();
11
+ labelStack = [];
12
+ loopDepth = 0;
13
+ switchDepth = 0;
11
14
  analyze(ast) {
12
15
  this.nodeToScope.set(ast, this.scopeManager.currentScope);
16
+ const crossScopeModificationVisitor = (node) => {
17
+ if (ts.isIdentifier(node)) {
18
+ const name = node.getText();
19
+ const definingScope = this.scopeManager.currentScope
20
+ .findScopeFor(name);
21
+ if (definingScope) {
22
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
23
+ const definingFunc = definingScope.ownerFunction;
24
+ if (definingFunc !== currentFuncNode) {
25
+ const typeInfo = this.scopeManager.lookup(name);
26
+ if (typeInfo) {
27
+ typeInfo.needsHeapAllocation = true;
28
+ }
29
+ }
30
+ }
31
+ }
32
+ };
13
33
  const visitor = {
14
34
  // Enter new scope for any block-like structure
15
35
  Block: {
16
36
  enter: (node, parent) => {
17
- this.scopeManager.enterScope();
37
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
38
+ this.scopeManager.enterScope(currentFuncNode);
18
39
  this.nodeToScope.set(node, this.scopeManager.currentScope);
19
40
  if (parent && ts.isCatchClause(parent) &&
20
41
  parent.variableDeclaration) {
@@ -35,21 +56,33 @@ export class TypeAnalyzer {
35
56
  },
36
57
  ForStatement: {
37
58
  enter: (node) => {
38
- this.scopeManager.enterScope();
59
+ this.loopDepth++;
60
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
61
+ this.scopeManager.enterScope(currentFuncNode);
39
62
  this.nodeToScope.set(node, this.scopeManager.currentScope);
40
63
  },
41
- exit: () => this.scopeManager.exitScope(),
64
+ exit: () => {
65
+ this.loopDepth--;
66
+ this.scopeManager.exitScope();
67
+ },
42
68
  },
43
69
  ForOfStatement: {
44
70
  enter: (node) => {
45
- this.scopeManager.enterScope();
71
+ this.loopDepth++;
72
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
73
+ this.scopeManager.enterScope(currentFuncNode);
46
74
  this.nodeToScope.set(node, this.scopeManager.currentScope);
47
75
  },
48
- exit: () => this.scopeManager.exitScope(),
76
+ exit: () => {
77
+ this.loopDepth--;
78
+ this.scopeManager.exitScope();
79
+ },
49
80
  },
50
81
  ForInStatement: {
51
82
  enter: (node) => {
52
- this.scopeManager.enterScope();
83
+ this.loopDepth++;
84
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
85
+ this.scopeManager.enterScope(currentFuncNode);
53
86
  this.nodeToScope.set(node, this.scopeManager.currentScope);
54
87
  const forIn = node;
55
88
  if (ts.isVariableDeclarationList(forIn.initializer)) {
@@ -71,7 +104,87 @@ export class TypeAnalyzer {
71
104
  // The generator will handle assigning to it.
72
105
  }
73
106
  },
74
- exit: () => this.scopeManager.exitScope(),
107
+ exit: () => {
108
+ this.loopDepth--;
109
+ this.scopeManager.exitScope();
110
+ },
111
+ },
112
+ WhileStatement: {
113
+ enter: (node) => {
114
+ this.loopDepth++;
115
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
116
+ this.scopeManager.enterScope(currentFuncNode);
117
+ this.nodeToScope.set(node, this.scopeManager.currentScope);
118
+ },
119
+ exit: () => {
120
+ this.loopDepth--;
121
+ this.scopeManager.exitScope();
122
+ },
123
+ },
124
+ DoStatement: {
125
+ enter: (node) => {
126
+ this.loopDepth++;
127
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
128
+ this.scopeManager.enterScope(currentFuncNode);
129
+ this.nodeToScope.set(node, this.scopeManager.currentScope);
130
+ },
131
+ exit: () => {
132
+ this.loopDepth--;
133
+ this.scopeManager.exitScope();
134
+ },
135
+ },
136
+ SwitchStatement: {
137
+ enter: (node) => {
138
+ this.switchDepth++;
139
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
140
+ this.scopeManager.enterScope(currentFuncNode);
141
+ this.nodeToScope.set(node, this.scopeManager.currentScope);
142
+ },
143
+ exit: () => {
144
+ this.switchDepth--;
145
+ this.scopeManager.exitScope();
146
+ },
147
+ },
148
+ LabeledStatement: {
149
+ enter: (node) => {
150
+ this.nodeToScope.set(node, this.scopeManager.currentScope);
151
+ this.labelStack.push(node.label.text);
152
+ },
153
+ exit: () => {
154
+ this.labelStack.pop();
155
+ },
156
+ },
157
+ BreakStatement: {
158
+ enter: (node) => {
159
+ const breakNode = node;
160
+ if (breakNode.label) {
161
+ if (!this.labelStack.includes(breakNode.label.text)) {
162
+ throw new Error(`SyntaxError: Undefined label '${breakNode.label.text}'`);
163
+ }
164
+ }
165
+ else {
166
+ if (this.loopDepth === 0 && this.switchDepth === 0) {
167
+ throw new Error("SyntaxError: Unlabeled break must be inside an iteration or switch statement");
168
+ }
169
+ }
170
+ },
171
+ },
172
+ ContinueStatement: {
173
+ enter: (node) => {
174
+ const continueNode = node;
175
+ if (continueNode.label) {
176
+ if (!this.labelStack.includes(continueNode.label.text)) {
177
+ throw new Error(`SyntaxError: Undefined label '${continueNode.label.text}'`);
178
+ }
179
+ // Also need to check if the label belongs to a loop, but that's harder here.
180
+ // The TS checker should handle this. We'll assume for now it does.
181
+ }
182
+ else {
183
+ if (this.loopDepth === 0) {
184
+ throw new Error("SyntaxError: Unlabeled continue must be inside an iteration statement");
185
+ }
186
+ }
187
+ },
75
188
  },
76
189
  ArrowFunction: {
77
190
  enter: (node) => {
@@ -82,7 +195,7 @@ export class TypeAnalyzer {
82
195
  captures: new Map(),
83
196
  };
84
197
  this.functionTypeInfo.set(node, funcType);
85
- this.scopeManager.enterScope();
198
+ this.scopeManager.enterScope(node);
86
199
  this.nodeToScope.set(node, this.scopeManager.currentScope);
87
200
  // Define parameters in the new scope
88
201
  node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
@@ -108,9 +221,10 @@ export class TypeAnalyzer {
108
221
  isClosure: false,
109
222
  captures: new Map(),
110
223
  declaration: node,
224
+ needsHeapAllocation: true, // Added: Functions are always heap-allocated
111
225
  };
112
226
  this.functionTypeInfo.set(node, funcType);
113
- this.scopeManager.enterScope();
227
+ this.scopeManager.enterScope(node);
114
228
  this.nodeToScope.set(node, this.scopeManager.currentScope);
115
229
  // If the function expression is named, define the name within its own scope for recursion.
116
230
  if (node.name) {
@@ -143,11 +257,12 @@ export class TypeAnalyzer {
143
257
  isClosure: false,
144
258
  captures: new Map(),
145
259
  declaration: node,
260
+ needsHeapAllocation: true,
146
261
  };
147
262
  this.scopeManager.define(funcName, funcType);
148
263
  this.functionTypeInfo.set(node, funcType);
149
264
  }
150
- this.scopeManager.enterScope();
265
+ this.scopeManager.enterScope(node);
151
266
  this.nodeToScope.set(node, this.scopeManager.currentScope);
152
267
  // Define parameters in the new scope
153
268
  node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
@@ -165,21 +280,106 @@ export class TypeAnalyzer {
165
280
  this.scopeManager.exitScope();
166
281
  },
167
282
  },
283
+ ClassDeclaration: {
284
+ enter: (node) => {
285
+ const classNode = node;
286
+ if (classNode.name) {
287
+ const name = classNode.name.getText();
288
+ const typeInfo = {
289
+ type: "function", // Classes are functions
290
+ declaration: classNode,
291
+ needsHeapAllocation: true,
292
+ };
293
+ this.scopeManager.define(name, typeInfo);
294
+ }
295
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
296
+ this.scopeManager.enterScope(currentFuncNode);
297
+ this.nodeToScope.set(node, this.scopeManager.currentScope);
298
+ },
299
+ exit: () => {
300
+ this.scopeManager.exitScope();
301
+ },
302
+ },
303
+ MethodDeclaration: {
304
+ enter: (node) => {
305
+ if (ts.isMethodDeclaration(node)) {
306
+ const funcType = {
307
+ type: "function",
308
+ isClosure: false,
309
+ captures: new Map(),
310
+ declaration: node,
311
+ needsHeapAllocation: true,
312
+ };
313
+ // Methods don't need to be defined in scope by name generally,
314
+ // but we need to track them for captures.
315
+ this.functionTypeInfo.set(node, funcType);
316
+ this.scopeManager.enterScope(node);
317
+ this.nodeToScope.set(node, this.scopeManager.currentScope);
318
+ node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
319
+ type: "auto",
320
+ isParameter: true,
321
+ declaration: p,
322
+ }));
323
+ this.functionStack.push(node);
324
+ }
325
+ },
326
+ exit: (node) => {
327
+ if (ts.isMethodDeclaration(node)) {
328
+ this.functionStack.pop();
329
+ }
330
+ this.scopeManager.exitScope();
331
+ },
332
+ },
333
+ Constructor: {
334
+ enter: (node) => {
335
+ if (ts.isConstructorDeclaration(node)) {
336
+ const funcType = {
337
+ type: "function",
338
+ isClosure: false,
339
+ captures: new Map(),
340
+ declaration: node,
341
+ needsHeapAllocation: true,
342
+ };
343
+ this.functionTypeInfo.set(node, funcType);
344
+ this.scopeManager.enterScope(node);
345
+ this.nodeToScope.set(node, this.scopeManager.currentScope);
346
+ node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
347
+ type: "auto",
348
+ isParameter: true,
349
+ declaration: p,
350
+ }));
351
+ this.functionStack.push(node); // Constructor acts like a function
352
+ }
353
+ },
354
+ exit: (node) => {
355
+ if (ts.isConstructorDeclaration(node)) {
356
+ this.functionStack.pop();
357
+ }
358
+ this.scopeManager.exitScope();
359
+ },
360
+ },
168
361
  VariableDeclaration: {
169
362
  enter: (node) => {
170
363
  if (ts.isVariableDeclaration(node)) {
171
364
  const name = node.name.getText();
172
365
  const isConst = (node.parent.flags & ts.NodeFlags.Const) !== 0;
173
366
  let type = "auto";
367
+ let needsHeap = false;
174
368
  if (node.initializer) {
175
369
  if (ts.isArrayLiteralExpression(node.initializer)) {
176
370
  type = "array";
177
371
  }
372
+ else if (ts.isArrowFunction(node.initializer) ||
373
+ ts.isFunctionExpression(node.initializer)) {
374
+ type = "function";
375
+ needsHeap = true;
376
+ }
178
377
  }
179
378
  const typeInfo = {
180
379
  type,
181
380
  declaration: node,
182
381
  isConst,
382
+ needsHeapAllocation: needsHeap,
183
383
  };
184
384
  this.scopeManager.define(name, typeInfo);
185
385
  }
@@ -190,7 +390,7 @@ export class TypeAnalyzer {
190
390
  if (ts.isIdentifier(node)) {
191
391
  if (isBuiltinObject.call(this, node))
192
392
  return;
193
- const currentFuncNode = this.functionStack[this.functionStack.length - 1];
393
+ const currentFuncNode = this.functionStack[this.functionStack.length - 1] ?? null;
194
394
  if (currentFuncNode &&
195
395
  (ts.isFunctionDeclaration(currentFuncNode) ||
196
396
  ts.isFunctionExpression(currentFuncNode)) &&
@@ -201,17 +401,19 @@ export class TypeAnalyzer {
201
401
  // And see if it belongs to an outer scope
202
402
  const definingScope = this.scopeManager.currentScope
203
403
  .findScopeFor(node.text);
204
- if (definingScope &&
205
- definingScope !== this.scopeManager.currentScope) {
206
- // This is a potential capture!
207
- // Find which function we are currently in and mark the capture.
208
- if (currentFuncNode) {
404
+ if (definingScope) {
405
+ const definingFunc = definingScope.ownerFunction;
406
+ if (definingFunc !== currentFuncNode) {
407
+ // This is a capture!
209
408
  const type = this.scopeManager.lookup(node.text);
210
409
  if (type) {
211
- const info = this.functionTypeInfo.get(currentFuncNode);
212
- if (info) {
213
- info.isClosure = true;
214
- info.captures?.set(node.text, type);
410
+ type.needsHeapAllocation = true;
411
+ if (currentFuncNode) {
412
+ const info = this.functionTypeInfo.get(currentFuncNode);
413
+ if (info) {
414
+ info.isClosure = true;
415
+ info.captures?.set(node.text, type);
416
+ }
215
417
  }
216
418
  }
217
419
  }
@@ -219,6 +421,37 @@ export class TypeAnalyzer {
219
421
  }
220
422
  },
221
423
  },
424
+ BinaryExpression: {
425
+ enter: (node) => {
426
+ if (ts.isBinaryExpression(node)) {
427
+ const isAssignment = node.operatorToken.kind >=
428
+ ts.SyntaxKind.FirstAssignment &&
429
+ node.operatorToken.kind <=
430
+ ts.SyntaxKind.LastAssignment;
431
+ if (isAssignment) {
432
+ crossScopeModificationVisitor(node.left);
433
+ }
434
+ }
435
+ },
436
+ },
437
+ PostfixUnaryExpression: {
438
+ enter: (node) => {
439
+ if (ts.isPostfixUnaryExpression(node)) {
440
+ crossScopeModificationVisitor(node.operand);
441
+ }
442
+ },
443
+ },
444
+ PrefixUnaryExpression: {
445
+ enter: (node) => {
446
+ if (ts.isPrefixUnaryExpression(node)) {
447
+ const op = node.operator;
448
+ if (op === ts.SyntaxKind.PlusPlusToken ||
449
+ op === ts.SyntaxKind.MinusMinusToken) {
450
+ crossScopeModificationVisitor(node.operand);
451
+ }
452
+ }
453
+ },
454
+ },
222
455
  };
223
456
  this.traverser.traverse(ast, visitor);
224
457
  }
package/dist/ast/types.js CHANGED
@@ -1 +1,7 @@
1
1
  import * as ts from "typescript";
2
+ export var DeclaredSymbolType;
3
+ (function (DeclaredSymbolType) {
4
+ DeclaredSymbolType["letOrConst"] = "letOrConst";
5
+ DeclaredSymbolType["function"] = "function";
6
+ DeclaredSymbolType["var"] = "var";
7
+ })(DeclaredSymbolType || (DeclaredSymbolType = {}));
package/dist/cli.js CHANGED
@@ -16,9 +16,8 @@ async function main() {
16
16
  try {
17
17
  const jsCode = await fs.readFile(jsFilePath, "utf-8");
18
18
  const interpreter = new Interpreter();
19
- console.time(`Generated C++ code ${cppFilePath}...`);
20
19
  const { cppCode, preludePath } = interpreter.interpret(jsCode);
21
- console.timeEnd(`Generated C++ code ${cppFilePath}...`);
20
+ console.log(`Generated C++ code ${cppFilePath}...`);
22
21
  await fs.mkdir(outputDir, { recursive: true });
23
22
  await fs.writeFile(cppFilePath, cppCode);
24
23
  console.log(`Compiling ${cppFilePath}...`);
@@ -0,0 +1,127 @@
1
+ import ts from "typescript";
2
+ import { CodeGenerator } from "./";
3
+ import { visitObjectPropertyName } from "./expression-handlers";
4
+ export function visitClassDeclaration(node, context) {
5
+ const className = node.name.getText();
6
+ // Check extends
7
+ let extendsExpr = null;
8
+ if (node.heritageClauses) {
9
+ for (const clause of node.heritageClauses) {
10
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword &&
11
+ clause.types.length > 0) {
12
+ extendsExpr = clause.types[0].expression;
13
+ }
14
+ }
15
+ }
16
+ let parentName = "";
17
+ if (extendsExpr) {
18
+ parentName = this.visit(extendsExpr, context);
19
+ if (ts.isIdentifier(extendsExpr)) {
20
+ const scope = this.getScopeForNode(extendsExpr);
21
+ const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(extendsExpr.getText(), scope);
22
+ if (typeInfo) {
23
+ parentName = this.getDerefCode(parentName, this.getJsVarName(extendsExpr), typeInfo);
24
+ }
25
+ }
26
+ }
27
+ const classContext = {
28
+ ...context,
29
+ superClassVar: parentName,
30
+ };
31
+ // 1. Constructor
32
+ const constructor = node.members.find(ts.isConstructorDeclaration);
33
+ let constructorLambda = "";
34
+ if (constructor) {
35
+ constructorLambda = this.generateLambda(constructor, {
36
+ ...classContext,
37
+ isInsideFunction: true,
38
+ lambdaName: className,
39
+ }, { isClass: true });
40
+ }
41
+ else {
42
+ // Default constructor
43
+ if (parentName) {
44
+ constructorLambda =
45
+ `jspp::AnyValue::make_class([=](const jspp::AnyValue& ${this.globalThisVar}, const std::vector<jspp::AnyValue>& args) mutable -> jspp::AnyValue {
46
+ auto __parent = ${parentName};
47
+ __parent.as_function("super")->call(${this.globalThisVar}, args);
48
+ return jspp::AnyValue::make_undefined();
49
+ }, "${className}")`;
50
+ }
51
+ else {
52
+ constructorLambda =
53
+ `jspp::AnyValue::make_class([=](const jspp::AnyValue& ${this.globalThisVar}, const std::vector<jspp::AnyValue>& args) mutable -> jspp::AnyValue {
54
+ return jspp::AnyValue::make_undefined();
55
+ }, "${className}")`;
56
+ }
57
+ }
58
+ let code = `${this.indent()}*${className} = ${constructorLambda};\n`;
59
+ // Set prototype of class (static inheritance) and prototype object (instance inheritance)
60
+ if (parentName) {
61
+ code +=
62
+ `${this.indent()}(*${className}).set_prototype(${parentName});\n`;
63
+ code +=
64
+ `${this.indent()}(*${className}).get_own_property("prototype").set_prototype(${parentName}.get_own_property("prototype"));\n`;
65
+ }
66
+ // Members
67
+ for (const member of node.members) {
68
+ if (ts.isMethodDeclaration(member)) {
69
+ const methodName = visitObjectPropertyName.call(this, member.name, {
70
+ ...context,
71
+ isObjectLiteralExpression: true, // Reuse this flag to handle computed properties
72
+ });
73
+ const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
74
+ const methodLambda = this.generateLambda(member, {
75
+ ...classContext,
76
+ isInsideFunction: true,
77
+ });
78
+ if (isStatic) {
79
+ code +=
80
+ `${this.indent()}(*${className}).set_own_property(${methodName}, ${methodLambda});\n`;
81
+ }
82
+ else {
83
+ code +=
84
+ `${this.indent()}(*${className}).get_own_property("prototype").set_own_property(${methodName}, ${methodLambda});\n`;
85
+ }
86
+ }
87
+ else if (ts.isGetAccessor(member)) {
88
+ const methodName = visitObjectPropertyName.call(this, member.name, {
89
+ ...context,
90
+ isObjectLiteralExpression: true,
91
+ });
92
+ const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
93
+ const lambda = this.generateLambda(member, {
94
+ ...classContext,
95
+ isInsideFunction: true,
96
+ });
97
+ if (isStatic) {
98
+ code +=
99
+ `${this.indent()}(*${className}).define_getter(${methodName}, ${lambda});\n`;
100
+ }
101
+ else {
102
+ code +=
103
+ `${this.indent()}(*${className}).get_own_property("prototype").define_getter(${methodName}, ${lambda});\n`;
104
+ }
105
+ }
106
+ else if (ts.isSetAccessor(member)) {
107
+ const methodName = visitObjectPropertyName.call(this, member.name, {
108
+ ...context,
109
+ isObjectLiteralExpression: true,
110
+ });
111
+ const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
112
+ const lambda = this.generateLambda(member, {
113
+ ...classContext,
114
+ isInsideFunction: true,
115
+ });
116
+ if (isStatic) {
117
+ code +=
118
+ `${this.indent()}(*${className}).define_setter(${methodName}, ${lambda});\n`;
119
+ }
120
+ else {
121
+ code +=
122
+ `${this.indent()}(*${className}).get_own_property("prototype").define_setter(${methodName}, ${lambda});\n`;
123
+ }
124
+ }
125
+ }
126
+ return code;
127
+ }