@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
package/README.md CHANGED
@@ -3,37 +3,52 @@
3
3
  [![CI](https://github.com/ugo-studio/jspp/actions/workflows/ci.yml/badge.svg)](https://github.com/ugo-studio/jspp/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- **JSPP is a modern, experimental transpiler that converts JavaScript code into high-performance, standard C++23.**
6
+ **JSPP is a modern, experimental transpiler that converts JavaScript and TypeScript code into high-performance, standard C++23.**
7
7
 
8
8
  The primary goal of this project is to achieve a near-perfect translation of JavaScript's dynamic nature and core features into the statically-typed, compiled world of C++, exploring modern C++ capabilities to bridge the gap between these two powerful languages.
9
9
 
10
10
  ## About The Project
11
11
 
12
- JavaScript is flexible and dynamic; C++ is performant and type-safe. JSPP aims to offer the best of both worlds. By transpiling JS to C++, we can potentially run JavaScript logic in environments where C++ is native, with significant performance gains and opportunities for low-level interoperability.
12
+ JavaScript is flexible and dynamic; C++ is performant and type-safe. JSPP aims to offer the best of both worlds. By transpiling JS/TS to C++, we can potentially run JavaScript logic in environments where C++ is native, with significant performance gains and opportunities for low-level interoperability.
13
13
 
14
- This project serves as a deep dive into compiler design, language semantics, and the expressive power of modern C++. The current implementation translates an entire JavaScript file into a single C++ `main` function, cleverly using `std::shared_ptr<std::any>` to replicate JavaScript's dynamic typing, garbage-collection-like memory management, and complex features like closures.
14
+ This project serves as a deep dive into compiler design, language semantics, and the expressive power of modern C++. The current implementation translates an entire JavaScript/TypeScript file into a single C++ `main` function, cleverly using `std::shared_ptr<std::any>` (and custom wrappers) to replicate JavaScript's dynamic typing, garbage-collection-like memory management, and complex features like closures.
15
15
 
16
16
  ## Features
17
17
 
18
- JSPP currently supports a foundational set of JavaScript features:
18
+ JSPP currently supports a comprehensive set of JavaScript features:
19
19
 
20
- - **Dynamic Variables:** Declaration (`let`, `const` and `var`), assignment, and type changes at runtime.
21
- - **Primitive Types:** `undefined`, `null`, `boolean`, `number`, `string`, `object` and `array`.
20
+ - **Languages:** JavaScript (`.js`) and TypeScript (`.ts`) support.
21
+ - **Dynamic Variables:** Declaration (`let`, `const`, `var`), assignment, and type changes at runtime.
22
+ - **Primitive Types:** `undefined`, `null`, `boolean`, `number`, `string`, `symbol`.
22
23
  - **Functions:**
23
- - Function declarations and arrow functions.
24
- - Correct hoisting for both variables and functions.
25
- - Closures with proper lexical scoping and lifetime management.
26
- - **Operators:** All arithmetic (`+`, `-`, `*`) and assignment (`=`) operators.
27
- - **Control Flow:** `void` operator.
28
- - **Built-in APIs:** A `console` object with `log()`, `warn()`, `error()`, and `time()` methods.
24
+ - Function declarations, arrow functions, and function expressions.
25
+ - **Generators:** `function*` and `yield` support.
26
+ - **Async/Await:** `async function` and `await` support (built on C++20 coroutines).
27
+ - Closures with proper lexical scoping.
28
+ - **Object Oriented:**
29
+ - **Classes:** Class declarations, constructors, methods, getters/setters, and inheritance (`extends`).
30
+ - **Prototypes:** Prototype chain traversal and manipulation.
31
+ - **Control Flow:**
32
+ - `if`, `else if`, `else`.
33
+ - Loops: `for`, `for-of`, `for-in`, `while`, `do-while`.
34
+ - `switch` statements.
35
+ - `try`, `catch`, `finally` blocks.
36
+ - **Operators:** Full suite of arithmetic, assignment, comparison, logical, bitwise, and unary operators (including `typeof`, `delete`, `void`, `instanceof`, `in`).
37
+ - **Standard Library:**
38
+ - **Console:** `log`, `warn`, `error`, `time`, `timeEnd`.
39
+ - **Math:** Comprehensive `Math` object implementation.
40
+ - **Timers:** `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval`.
41
+ - **Promise:** Full `Promise` implementation with chaining.
42
+ - **Error:** Standard `Error` class and stack traces.
43
+ - **Arrays & Objects:** Extensive methods support (`map`, `filter`, `reduce`, `push`, `pop`, `Object.keys`, etc.).
29
44
 
30
45
  ## Reserved Keywords
31
46
 
32
47
  JSPP reserves certain keywords to avoid conflicts with the generated C++ code and internal mechanisms. The following keywords are reserved and cannot be used as variable names:
33
48
 
34
- - `std`: Reserved to prevent conflicts with the C++ standard library namespace.
35
- - `jspp`: Reserved for internal use by the JSPP transpiler.
36
- - `co_yield`,`co_return`,`co_await`: Reserved to prevent conflicts with the c++ couroutine keywords.
49
+ - `jspp`: Reserved for internal use by the transpiler.
50
+ - `std`: Reserved to prevent conflicts with the C++ standard library.
51
+ - `co_yield`, `co_return`, `co_await`: Reserved for C++ coroutine mechanics.
37
52
 
38
53
  Using these keywords as variable names will result in a `SyntaxError`.
39
54
 
@@ -51,8 +66,7 @@ To contribute to JSPP or run its test suite, follow these steps:
51
66
 
52
67
  ### Prerequisites
53
68
 
54
- - **Node.js:** This project uses Node.js for package management and script execution.
55
- - [Install Node.js >= v20.0.0](https://nodejs.org/)
69
+ - **Bun:** This project uses [Bun](https://bun.sh/) for package management, script execution, and testing.
56
70
  - **C++ Compiler:** A compiler with support for C++23 is required. This project is tested with `g++`.
57
71
  - `g++` (MinGW on Windows, or available via build-essentials on Linux)
58
72
 
@@ -64,31 +78,31 @@ To contribute to JSPP or run its test suite, follow these steps:
64
78
  ```
65
79
  2. Install dependencies:
66
80
  ```sh
67
- npm install
81
+ bun install
68
82
  ```
69
83
 
70
84
  ## Usage
71
85
 
72
- The primary way to use JSPP is via its command-line interface. This will transpile your `.js` file to C++, compile it, and execute the resulting binary.
86
+ The primary way to use JSPP is via its command-line interface. This will transpile your file to C++, compile it, and execute the resulting binary.
73
87
 
74
88
  ```sh
75
- jspp <path-to-your-js-file>
89
+ jspp <path-to-your-file>
76
90
  ```
77
91
 
78
92
  **Example:**
79
93
 
80
- To run a sample JavaScript file located at `my-code/hello.js`:
94
+ To run a sample TypeScript file located at `my-code/test.ts`:
81
95
 
82
96
  ```sh
83
- jspp my-code/hello.js
97
+ jspp my-code/test.ts
84
98
  ```
85
99
 
86
- The transpiled C++ file and executable will be generated in the same directory as the input file and cleaned up after execution.
100
+ 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).
87
101
 
88
- You can also run the test suite, which will transpile all the JavaScript test cases in `test/cases/`, build the resulting C++ files, and run them.
102
+ You can also run the test suite:
89
103
 
90
104
  ```sh
91
- npm test
105
+ bun test
92
106
  ```
93
107
 
94
108
  ## Roadmap
@@ -101,38 +115,39 @@ This project is ambitious, and there is a long and exciting road ahead. Here is
101
115
 
102
116
  This phase focuses on building a solid foundation that correctly models JavaScript's core runtime behavior.
103
117
 
104
- - [x] Dynamic Variables & Primitives (`let`, `const`, `var`, `undefined`, `null`, `string`, `number`, `boolean`, etc.)
118
+ - [x] Dynamic Variables & Primitives
105
119
  - [x] Function Declarations & Arrow Functions
106
120
  - [x] Correct Hoisting for Variables and Functions
107
121
  - [x] Closures & Lexical Scoping
108
- - [x] `if-elseif-else` conditions
122
+ - [x] Basic Control Flow (`if`, `loops`)
109
123
  - [x] Basic `console` API
110
124
 
111
125
  ---
112
126
 
113
127
  ### **Phase 2: Expanded Language Support**
114
128
 
115
- This phase will broaden the range of supported JavaScript syntax and features.
129
+ This phase broadens the range of supported JavaScript syntax and features.
116
130
 
117
131
  - [x] **Error Handling**: `try`/`catch`/`finally` blocks and `throw`.
118
- - [x] **Objects:** Literals, property access (dot and bracket notation), methods.
119
- - [x] **Arrays:** Literals, indexing, and core methods (`.push`, `.pop`, `.length`, etc.).
132
+ - [x] **Objects & Classes:** Classes, inheritance, literals, property access.
133
+ - [x] **Arrays:** Literals, indexing, and core methods.
120
134
  - [x] **Operators:** Full suite of arithmetic, logical, and comparison operators.
121
- - [x] **Control Flow:** tenary operators, `for` loops, `while` loops, `switch`.
135
+ - [x] **Advanced Control Flow:** `switch`, `for-of`, `for-in`, generators.
136
+ - [x] **TypeScript Support:** Compilation of `.ts` files.
122
137
 
123
- ### **Phase 3: Interoperability & Standard Library**
138
+ ### Phase 3: Interoperability & Standard Library
124
139
 
125
- This phase will focus on building out the standard library and enabling modular code.
140
+ This phase focuses on building out the standard library and enabling modular code.
126
141
 
127
- - [ ] **JS Standard Library:** Implementation of common built-in objects and functions (`Math`, `Date`, `String.prototype.*`, `Array.prototype.*`).
128
- - [x] **Asynchronous Operations:** A C++ implementation of the JavaScript event loop, `Promise`, and `async/await`.
142
+ - [ ] **JS Standard Library:** Implementation of common built-in objects. (Currently supports: `Math`, `Symbol`, `Error`, `String`, `Array`, `Object`, `Timer`). **Pending:** `Date`, `Temporal`, `Map`, `Set`, `JSON`, etc.
143
+ - [x] **Asynchronous Operations:** Event loop, `Promise`, `async/await`, and timers (`setTimeout`).
129
144
  - [ ] **Module System:** Support for `import` and `export` to transpile multi-file projects.
130
145
 
131
146
  ### **Phase 4: Optimization & Advanced Features**
132
147
 
133
148
  With a feature-complete transpiler, the focus will shift to performance and advanced capabilities.
134
149
 
135
- - [ ] **Performance Benchmarking:** Create a suite to compare transpiled C++ performance against V8 and other JS engines.
150
+ - [ ] **Performance Benchmarking:** Create a suite to compare transpiled C++ performance against V8.
136
151
  - [ ] **C++ Interoperability:** Define a clear API for calling C++ functions from the transpiled JavaScript and vice-versa.
137
152
 
138
153
  ## Contributing
@@ -1,4 +1,5 @@
1
1
  import * as ts from "typescript";
2
+ import { CompilerError } from "../core/error.js";
2
3
  export const RESERVED_KEYWORDS = new Set([
3
4
  "jspp",
4
5
  "std",
@@ -86,6 +87,9 @@ export class ScopeManager {
86
87
  define(name, type) {
87
88
  // if (name === "named" || name === "letVal") console.log("Defining", name, "in scope. isBuiltin:", type.isBuiltin, " type:", type.type);
88
89
  if (this.reservedKeywords.has(name) && !type.isBuiltin) {
90
+ if (type.declaration) {
91
+ throw new CompilerError(`Unexpected reserved word "${name}"`, type.declaration, "SyntaxError");
92
+ }
89
93
  throw new Error(`SyntaxError: Unexpected reserved word "${name}"`);
90
94
  }
91
95
  this.currentScope.define(name, type);
@@ -93,6 +97,9 @@ export class ScopeManager {
93
97
  // Defines a `var` variable (hoisted to function or global scope).
94
98
  defineVar(name, type) {
95
99
  if (this.reservedKeywords.has(name) && !type.isBuiltin) {
100
+ if (type.declaration) {
101
+ throw new CompilerError(`Unexpected reserved word "${name}"`, type.declaration, "SyntaxError");
102
+ }
96
103
  throw new Error(`SyntaxError: Unexpected reserved word "${name}"`);
97
104
  }
98
105
  let scope = this.currentScope;
@@ -1,7 +1,43 @@
1
1
  import * as ts from "typescript";
2
- import { isBuiltinObject } from "../core/codegen/helpers.js";
2
+ import { isBuiltinObject, shouldIgnoreStatement, } from "../core/codegen/helpers.js";
3
+ import { CompilerError } from "../core/error.js";
3
4
  import { Traverser } from "../core/traverser.js";
4
5
  import { Scope, ScopeManager } from "./scope.js";
6
+ function getParameterType(node) {
7
+ if (node.type) {
8
+ switch (node.type.kind) {
9
+ case ts.SyntaxKind.StringKeyword:
10
+ return "string";
11
+ case ts.SyntaxKind.NumberKeyword:
12
+ return "number";
13
+ case ts.SyntaxKind.BooleanKeyword:
14
+ return "boolean";
15
+ case ts.SyntaxKind.AnyKeyword:
16
+ return "any";
17
+ case ts.SyntaxKind.VoidKeyword:
18
+ return "void";
19
+ case ts.SyntaxKind.ObjectKeyword:
20
+ return "object";
21
+ case ts.SyntaxKind.SymbolKeyword:
22
+ return "symbol";
23
+ case ts.SyntaxKind.UndefinedKeyword:
24
+ return "undefined";
25
+ case ts.SyntaxKind.UnknownKeyword:
26
+ return "unknown";
27
+ case ts.SyntaxKind.NeverKeyword:
28
+ return "never";
29
+ case ts.SyntaxKind.ArrayType:
30
+ return "array";
31
+ case ts.SyntaxKind.FunctionType:
32
+ return "function";
33
+ }
34
+ if (ts.isTypeReferenceNode(node.type)) {
35
+ return node.type.typeName.getText();
36
+ }
37
+ return node.type.getText();
38
+ }
39
+ return "auto";
40
+ }
5
41
  export class TypeAnalyzer {
6
42
  traverser = new Traverser();
7
43
  scopeManager = new ScopeManager();
@@ -167,12 +203,12 @@ export class TypeAnalyzer {
167
203
  const breakNode = node;
168
204
  if (breakNode.label) {
169
205
  if (!this.labelStack.includes(breakNode.label.text)) {
170
- throw new Error(`SyntaxError: Undefined label '${breakNode.label.text}'`);
206
+ throw new CompilerError(`Undefined label '${breakNode.label.text}'`, breakNode.label, "SyntaxError");
171
207
  }
172
208
  }
173
209
  else {
174
210
  if (this.loopDepth === 0 && this.switchDepth === 0) {
175
- throw new Error("SyntaxError: Unlabeled break must be inside an iteration or switch statement");
211
+ throw new CompilerError("Unlabeled break must be inside an iteration or switch statement", node, "SyntaxError");
176
212
  }
177
213
  }
178
214
  },
@@ -182,14 +218,14 @@ export class TypeAnalyzer {
182
218
  const continueNode = node;
183
219
  if (continueNode.label) {
184
220
  if (!this.labelStack.includes(continueNode.label.text)) {
185
- throw new Error(`SyntaxError: Undefined label '${continueNode.label.text}'`);
221
+ throw new CompilerError(`Undefined label '${continueNode.label.text}'`, continueNode.label, "SyntaxError");
186
222
  }
187
223
  // Also need to check if the label belongs to a loop, but that's harder here.
188
224
  // The TS checker should handle this. We'll assume for now it does.
189
225
  }
190
226
  else {
191
227
  if (this.loopDepth === 0) {
192
- throw new Error("SyntaxError: Unlabeled continue must be inside an iteration statement");
228
+ throw new CompilerError("Unlabeled continue must be inside an iteration statement", node, "SyntaxError");
193
229
  }
194
230
  }
195
231
  },
@@ -205,21 +241,17 @@ export class TypeAnalyzer {
205
241
  this.functionTypeInfo.set(node, funcType);
206
242
  this.scopeManager.enterScope(node);
207
243
  this.nodeToScope.set(node, this.scopeManager.currentScope);
208
- // Catch invalid parameters
244
+ // Define parameters in the new scope
209
245
  node.parameters.forEach((p) => {
210
- if (p.getText() == "this") {
211
- const sourceFile = node.getSourceFile();
212
- const { line, character } = sourceFile
213
- .getLineAndCharacterOfPosition(p.getStart());
214
- throw new SyntaxError(`Cannot use 'this' as a parameter name.\n\n${" ".repeat(6)}at ${sourceFile.fileName}:${line + 1}:${character + 1}\n`);
246
+ if (p.getText() == "this") { // Catch invalid parameters
247
+ throw new CompilerError("Cannot use 'this' as a parameter name.", p, "SyntaxError");
215
248
  }
249
+ this.scopeManager.define(p.name.getText(), {
250
+ type: getParameterType(p),
251
+ isParameter: true,
252
+ declaration: p,
253
+ });
216
254
  });
217
- // Define parameters in the new scope
218
- node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
219
- type: "auto",
220
- isParameter: true,
221
- declaration: p,
222
- }));
223
255
  this.functionStack.push(node);
224
256
  }
225
257
  },
@@ -247,21 +279,17 @@ export class TypeAnalyzer {
247
279
  if (node.name) {
248
280
  this.scopeManager.define(node.name.getText(), funcType);
249
281
  }
250
- // Catch invalid parameters
282
+ // Define parameters in the new scope
251
283
  node.parameters.forEach((p) => {
252
- if (p.getText() == "this") {
253
- const sourceFile = node.getSourceFile();
254
- const { line, character } = sourceFile
255
- .getLineAndCharacterOfPosition(p.getStart());
256
- throw new SyntaxError(`Cannot use 'this' as a parameter name.\n\n${" ".repeat(6)}at ${sourceFile.fileName}:${line + 1}:${character + 1}\n`);
284
+ if (p.getText() == "this") { // Catch invalid parameters
285
+ throw new CompilerError("Cannot use 'this' as a parameter name.", p, "SyntaxError");
257
286
  }
287
+ this.scopeManager.define(p.name.getText(), {
288
+ type: getParameterType(p),
289
+ isParameter: true,
290
+ declaration: p,
291
+ });
258
292
  });
259
- // Define parameters in the new scope
260
- node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
261
- type: "auto",
262
- isParameter: true,
263
- declaration: p,
264
- }));
265
293
  this.functionStack.push(node);
266
294
  }
267
295
  },
@@ -288,23 +316,19 @@ export class TypeAnalyzer {
288
316
  this.scopeManager.define(funcName, funcType);
289
317
  this.functionTypeInfo.set(node, funcType);
290
318
  }
291
- // Catch invalid parameters
292
- node.parameters.forEach((p) => {
293
- if (p.getText() == "this") {
294
- const sourceFile = node.getSourceFile();
295
- const { line, character } = sourceFile
296
- .getLineAndCharacterOfPosition(p.getStart());
297
- throw new SyntaxError(`Cannot use 'this' as a parameter name.\n\n${" ".repeat(6)}at ${sourceFile.fileName}:${line + 1}:${character + 1}\n`);
298
- }
299
- });
300
319
  this.scopeManager.enterScope(node);
301
320
  this.nodeToScope.set(node, this.scopeManager.currentScope);
302
321
  // Define parameters in the new scope
303
- node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
304
- type: "auto",
305
- isParameter: true,
306
- declaration: p,
307
- }));
322
+ node.parameters.forEach((p) => {
323
+ if (p.getText() == "this") { // Catch invalid parameters
324
+ throw new CompilerError("Cannot use 'this' as a parameter name.", p, "SyntaxError");
325
+ }
326
+ this.scopeManager.define(p.name.getText(), {
327
+ type: getParameterType(p),
328
+ isParameter: true,
329
+ declaration: p,
330
+ });
331
+ });
308
332
  this.functionStack.push(node);
309
333
  }
310
334
  },
@@ -336,6 +360,25 @@ export class TypeAnalyzer {
336
360
  this.scopeManager.exitScope();
337
361
  },
338
362
  },
363
+ EnumDeclaration: {
364
+ enter: (node) => {
365
+ const enumNode = node;
366
+ if (enumNode.name) {
367
+ const name = enumNode.name.getText();
368
+ const typeInfo = {
369
+ type: "object", // Enums are objects
370
+ declaration: enumNode,
371
+ needsHeapAllocation: true,
372
+ };
373
+ this.scopeManager.define(name, typeInfo);
374
+ }
375
+ // Enums don't create a new scope for variables, but they are objects.
376
+ // We don't strictly need to enterScope unless we want to track something inside.
377
+ // But standard JS/TS behavior doesn't really have block scope inside enum definition that leaks out or captures differently.
378
+ // However, we map nodes to scopes.
379
+ this.nodeToScope.set(node, this.scopeManager.currentScope);
380
+ },
381
+ },
339
382
  MethodDeclaration: {
340
383
  enter: (node) => {
341
384
  if (ts.isMethodDeclaration(node)) {
@@ -397,6 +440,16 @@ export class TypeAnalyzer {
397
440
  VariableDeclaration: {
398
441
  enter: (node) => {
399
442
  if (ts.isVariableDeclaration(node)) {
443
+ // Check if it is an ambient declaration (declare var/let/const ...)
444
+ let isAmbient = false;
445
+ if (ts.isVariableDeclarationList(node.parent) &&
446
+ ts.isVariableStatement(node.parent.parent)) {
447
+ if (shouldIgnoreStatement(node.parent.parent)) {
448
+ isAmbient = true;
449
+ }
450
+ }
451
+ if (isAmbient)
452
+ return;
400
453
  const name = node.name.getText();
401
454
  const isBlockScoped = (node.parent.flags &
402
455
  (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
@@ -5,35 +5,32 @@ export var DeclarationType;
5
5
  DeclarationType["const"] = "const";
6
6
  DeclarationType["function"] = "function";
7
7
  DeclarationType["class"] = "class";
8
+ DeclarationType["enum"] = "enum";
8
9
  })(DeclarationType || (DeclarationType = {}));
9
10
  export class DeclaredSymbol {
10
11
  type;
11
- checked;
12
- func;
12
+ checks;
13
+ features;
13
14
  constructor(type) {
14
15
  this.type = type;
15
- this.checked = {
16
- initialized: false,
17
- };
18
- this.func = null;
16
+ this.checks = { initialized: false };
17
+ this.features = {};
19
18
  }
20
19
  get isMutable() {
21
20
  return this.type === DeclarationType.let ||
22
21
  this.type === DeclarationType.var;
23
22
  }
24
23
  updateChecked(update) {
25
- this.checked = {
26
- ...this.checked,
24
+ this.checks = {
25
+ ...this.checks,
27
26
  ...update,
28
27
  };
29
28
  }
30
- updateFunc(update) {
31
- this.func = update
32
- ? {
33
- ...this.func,
34
- ...update,
35
- }
36
- : null;
29
+ updateFeatures(update) {
30
+ this.features = {
31
+ ...this.features,
32
+ ...update,
33
+ };
37
34
  }
38
35
  }
39
36
  export class DeclaredSymbols {
@@ -53,21 +50,34 @@ export class DeclaredSymbols {
53
50
  }
54
51
  add(name, value) {
55
52
  const sym = new DeclaredSymbol(value.type);
56
- if (value.checked !== undefined)
57
- sym.updateChecked(value.checked);
58
- if (value.func !== undefined)
59
- sym.updateFunc(value.func);
53
+ if (value.checks)
54
+ sym.updateChecked(value.checks);
55
+ if (value.features)
56
+ sym.updateFeatures(value.features);
60
57
  return this.symbols.set(name, sym);
61
58
  }
62
59
  update(name, update) {
63
60
  const sym = this.get(name);
64
61
  if (sym) {
65
- if (update.type !== undefined)
62
+ if (update.type)
63
+ sym.type = update.type;
64
+ if (update.checks)
65
+ sym.updateChecked(update.checks);
66
+ if (update.features) {
67
+ sym.updateFeatures(update.features);
68
+ }
69
+ return this.symbols.set(name, sym);
70
+ }
71
+ }
72
+ set(name, update) {
73
+ const sym = this.get(name);
74
+ if (sym) {
75
+ if (update.type)
66
76
  sym.type = update.type;
67
- if (update.checked !== undefined)
68
- sym.updateChecked(update.checked);
69
- if (update.func !== undefined)
70
- sym.updateFunc(update.func);
77
+ if (update.checks)
78
+ sym.checks = update.checks;
79
+ if (update.features)
80
+ sym.features = update.features;
71
81
  return this.symbols.set(name, sym);
72
82
  }
73
83
  }
@@ -0,0 +1,59 @@
1
+ import path from "path";
2
+ import pkg from "../../package.json" with { type: "json" };
3
+ import { COLORS } from "./colors.js";
4
+ export function parseArgs(rawArgs) {
5
+ let jsFilePathArg = null;
6
+ let isRelease = false;
7
+ let keepCpp = false;
8
+ let outputExePath = null;
9
+ let scriptArgs = [];
10
+ for (let i = 0; i < rawArgs.length; i++) {
11
+ const arg = rawArgs[i];
12
+ if (!arg)
13
+ continue;
14
+ if (arg === "--") {
15
+ scriptArgs = rawArgs.slice(i + 1);
16
+ break;
17
+ }
18
+ if (arg === "--release") {
19
+ isRelease = true;
20
+ }
21
+ else if (arg === "--keep-cpp") {
22
+ keepCpp = true;
23
+ }
24
+ else if (arg === "-o" || arg === "--output") {
25
+ if (i + 1 < rawArgs.length) {
26
+ outputExePath = rawArgs[i + 1] ?? null;
27
+ i++;
28
+ }
29
+ else {
30
+ console.error(`${COLORS.red}Error: --output requires a file path argument.${COLORS.reset}`);
31
+ process.exit(1);
32
+ }
33
+ }
34
+ else if (arg.startsWith("-")) {
35
+ console.warn(`${COLORS.yellow}Warning: Unknown argument '${arg}'${COLORS.reset}`);
36
+ }
37
+ else {
38
+ if (jsFilePathArg) {
39
+ console.error(`${COLORS.red}Error: Multiple input files specified.${COLORS.reset}`);
40
+ process.exit(1);
41
+ }
42
+ jsFilePathArg = arg;
43
+ }
44
+ }
45
+ if (!jsFilePathArg) {
46
+ console.log(`${COLORS.bold}JSPP Compiler${COLORS.reset} ${COLORS.dim}v${pkg.version}${COLORS.reset}`);
47
+ console.log(`${COLORS.bold}Usage:${COLORS.reset} jspp <path-to-js-file> [--release] [--keep-cpp] [-o <output-path>] [-- <args...>]`);
48
+ process.exit(1);
49
+ }
50
+ return {
51
+ jsFilePath: path.resolve(process.cwd(), jsFilePathArg),
52
+ isRelease,
53
+ keepCpp,
54
+ outputExePath: outputExePath
55
+ ? path.resolve(process.cwd(), outputExePath)
56
+ : null,
57
+ scriptArgs,
58
+ };
59
+ }
@@ -0,0 +1,9 @@
1
+ export const COLORS = {
2
+ reset: "\x1b[0m",
3
+ cyan: "\x1b[36m",
4
+ green: "\x1b[32m",
5
+ yellow: "\x1b[33m",
6
+ red: "\x1b[31m",
7
+ dim: "\x1b[2m",
8
+ bold: "\x1b[1m",
9
+ };
@@ -0,0 +1,20 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ export async function getLatestMtime(dirPath) {
4
+ let maxMtime = 0;
5
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
6
+ for (const entry of entries) {
7
+ const fullPath = path.join(dirPath, entry.name);
8
+ if (entry.isDirectory()) {
9
+ const nestedMtime = await getLatestMtime(fullPath);
10
+ if (nestedMtime > maxMtime)
11
+ maxMtime = nestedMtime;
12
+ }
13
+ else {
14
+ const stats = await fs.stat(fullPath);
15
+ if (stats.mtimeMs > maxMtime)
16
+ maxMtime = stats.mtimeMs;
17
+ }
18
+ }
19
+ return maxMtime;
20
+ }