@ugo-studio/jspp 0.1.0
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/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/analysis/scope.js +77 -0
- package/dist/analysis/typeAnalyzer.js +224 -0
- package/dist/ast/types.js +1 -0
- package/dist/cli.js +63 -0
- package/dist/core/codegen/declaration-handlers.js +49 -0
- package/dist/core/codegen/expression-handlers.js +333 -0
- package/dist/core/codegen/function-handlers.js +94 -0
- package/dist/core/codegen/helpers.js +83 -0
- package/dist/core/codegen/index.js +54 -0
- package/dist/core/codegen/literal-handlers.js +32 -0
- package/dist/core/codegen/statement-handlers.js +485 -0
- package/dist/core/codegen/visitor.js +86 -0
- package/dist/core/parser.js +6 -0
- package/dist/core/traverser.js +19 -0
- package/dist/index.js +16 -0
- package/package.json +41 -0
- package/src/prelude/access.hpp +86 -0
- package/src/prelude/any_value.hpp +734 -0
- package/src/prelude/descriptors.hpp +25 -0
- package/src/prelude/error.hpp +31 -0
- package/src/prelude/error_helpers.hpp +59 -0
- package/src/prelude/index.hpp +29 -0
- package/src/prelude/library/console.hpp +111 -0
- package/src/prelude/library/global.hpp +10 -0
- package/src/prelude/library/symbol.hpp +8 -0
- package/src/prelude/log_string.hpp +403 -0
- package/src/prelude/operators.hpp +256 -0
- package/src/prelude/types.hpp +50 -0
- package/src/prelude/values/array.hpp +50 -0
- package/src/prelude/values/function.hpp +19 -0
- package/src/prelude/values/non_values.hpp +20 -0
- package/src/prelude/values/object.hpp +17 -0
- package/src/prelude/values/operators/array.hpp +165 -0
- package/src/prelude/values/operators/function.hpp +34 -0
- package/src/prelude/values/operators/object.hpp +34 -0
- package/src/prelude/values/prototypes/array.hpp +228 -0
- package/src/prelude/values/prototypes/function.hpp +0 -0
- package/src/prelude/values/prototypes/object.hpp +0 -0
- package/src/prelude/values/prototypes/string.hpp +357 -0
- package/src/prelude/well_known_symbols.hpp +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Emmanuel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the “Software”), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# JSPP (JavaScript++)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ugo-studio/jspp)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
**JSPP is a modern, experimental transpiler that converts JavaScript code into high-performance, standard C++23.**
|
|
7
|
+
|
|
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
|
+
|
|
10
|
+
## About The Project
|
|
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.
|
|
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.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
JSPP currently supports a foundational set of JavaScript features:
|
|
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`.
|
|
22
|
+
- **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.
|
|
29
|
+
|
|
30
|
+
## Reserved Keywords
|
|
31
|
+
|
|
32
|
+
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
|
+
|
|
34
|
+
- `std`: Reserved to prevent conflicts with the C++ standard library namespace.
|
|
35
|
+
- `jspp`: Reserved for internal use by the JSPP transpiler.
|
|
36
|
+
|
|
37
|
+
Using these keywords as variable names will result in a `SyntaxError`.
|
|
38
|
+
|
|
39
|
+
## How It Works
|
|
40
|
+
|
|
41
|
+
The transpilation process is a classic three-stage pipeline:
|
|
42
|
+
|
|
43
|
+
1. **Parsing:** The incoming JavaScript code is parsed into an Abstract Syntax Tree (AST) using the powerful TypeScript compiler API. This gives us a structured, traversable representation of the code.
|
|
44
|
+
|
|
45
|
+
2. **Analysis:** The `TypeAnalyzer` traverses the AST. While it doesn't perform traditional static type checking, it plays a crucial role in understanding the code's structure. It identifies scopes (global, function, block) and detects when variables are "captured" by closures.
|
|
46
|
+
|
|
47
|
+
3. **Code Generation:** The `CodeGenerator` performs a final traversal of the AST. It translates each node into its C++ equivalent.
|
|
48
|
+
- All variables are declared as `std::shared_ptr<AnyValue>` (where `AnyValue` uses a [Tagged Union](https://en.wikipedia.org/wiki/Tagged_union) system for managing dynamic types like JavaScript). This approach elegantly mimics JavaScript's dynamic types and reference-based memory model.
|
|
49
|
+
- Closures are implemented as C++ lambdas that capture `shared_ptr`s by value, ensuring variable lifetimes are correctly extended beyond their original scope.
|
|
50
|
+
- The entire script is wrapped into a single `main` function, with hoisting logic carefully replicated to ensure correct execution order.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
To use JSPP as a command-line tool, install it globally via npm:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
npm install -g @ugo-studio/jspp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## For Developers
|
|
61
|
+
|
|
62
|
+
To contribute to JSPP or run its test suite, follow these steps:
|
|
63
|
+
|
|
64
|
+
### Prerequisites
|
|
65
|
+
|
|
66
|
+
- **Bun:** This project uses Bun for package management and script execution.
|
|
67
|
+
- [Install Bun](https://bun.sh/docs/installation)
|
|
68
|
+
- **C++ Compiler:** A compiler with support for C++23 is required. This project is tested with `g++`.
|
|
69
|
+
- `g++` (MinGW on Windows, or available via build-essentials on Linux)
|
|
70
|
+
|
|
71
|
+
### Setup
|
|
72
|
+
|
|
73
|
+
1. Clone the repo:
|
|
74
|
+
```sh
|
|
75
|
+
git clone https://github.com/ugo-studio/jspp.git
|
|
76
|
+
```
|
|
77
|
+
2. Install dependencies:
|
|
78
|
+
```sh
|
|
79
|
+
bun install
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
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.
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
jspp <path-to-your-js-file>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Example:**
|
|
91
|
+
|
|
92
|
+
To run a sample JavaScript file located at `my-code/hello.js`:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
jspp my-code/hello.js
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The transpiled C++ file and executable will be generated in the same directory as the input file and cleaned up after execution.
|
|
99
|
+
|
|
100
|
+
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.
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
bun run test
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Roadmap
|
|
107
|
+
|
|
108
|
+
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.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### **Phase 1: Core Language Features**
|
|
113
|
+
|
|
114
|
+
This phase focuses on building a solid foundation that correctly models JavaScript's core runtime behavior.
|
|
115
|
+
|
|
116
|
+
- [x] Dynamic Variables & Primitives (`let`, `const`, `var`, `undefined`, `null`, `string`, `number`, `boolean`, etc.)
|
|
117
|
+
- [x] Function Declarations & Arrow Functions
|
|
118
|
+
- [x] Correct Hoisting for Variables and Functions
|
|
119
|
+
- [x] Closures & Lexical Scoping (via `std::shared_ptr` capture)
|
|
120
|
+
- [x] `if-elseif-else` conditions
|
|
121
|
+
- [x] Basic `console` API
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### **Phase 2: Expanded Language Support**
|
|
126
|
+
|
|
127
|
+
This phase will broaden the range of supported JavaScript syntax and features.
|
|
128
|
+
|
|
129
|
+
- [x] **Error Handling**: `try`/`catch`/`finally` blocks and `throw`.
|
|
130
|
+
- [x] **Objects:** Literals, property access (dot and bracket notation), methods.
|
|
131
|
+
- [x] **Arrays:** Literals, indexing, and core methods (`.push`, `.pop`, `.length`, etc.).
|
|
132
|
+
- [x] **Operators:** Full suite of arithmetic, logical, and comparison operators.
|
|
133
|
+
- [ ] **Control Flow:** tenary operators, `for` loops, `while` loops, `switch`.
|
|
134
|
+
|
|
135
|
+
### **Phase 3: Interoperability & Standard Library**
|
|
136
|
+
|
|
137
|
+
This phase will focus on building out the standard library and enabling modular code.
|
|
138
|
+
|
|
139
|
+
- [ ] **JS Standard Library:** Implementation of common built-in objects and functions (`Math`, `Date`, `String.prototype.*`, `Array.prototype.*`).
|
|
140
|
+
- [ ] **Module System:** Support for `import` and `export` to transpile multi-file projects.
|
|
141
|
+
- [ ] **Asynchronous Operations:** A C++ implementation of the JavaScript event loop, `Promise`, and `async/await`.
|
|
142
|
+
|
|
143
|
+
### **Phase 4: Optimization & Advanced Features**
|
|
144
|
+
|
|
145
|
+
With a feature-complete transpiler, the focus will shift to performance and advanced capabilities.
|
|
146
|
+
|
|
147
|
+
- [ ] **Performance Benchmarking:** Create a suite to compare transpiled C++ performance against V8 and other JS engines.
|
|
148
|
+
- [ ] **C++ Interoperability:** Define a clear API for calling C++ functions from the transpiled JavaScript and vice-versa.
|
|
149
|
+
|
|
150
|
+
## Contributing
|
|
151
|
+
|
|
152
|
+
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
|
153
|
+
|
|
154
|
+
1. Fork the Project
|
|
155
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
156
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
|
157
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
|
158
|
+
5. Open a Pull Request
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const RESERVED_KEYWORDS = ["std", "jspp"];
|
|
2
|
+
// Represents a single scope (e.g., a function body or a block statement)
|
|
3
|
+
export class Scope {
|
|
4
|
+
parent;
|
|
5
|
+
symbols = new Map();
|
|
6
|
+
constructor(parent) {
|
|
7
|
+
this.parent = parent;
|
|
8
|
+
}
|
|
9
|
+
// Defines a variable in this scope.
|
|
10
|
+
define(name, type) {
|
|
11
|
+
if (this.symbols.has(name)) {
|
|
12
|
+
return false; // Already defined in this scope
|
|
13
|
+
}
|
|
14
|
+
this.symbols.set(name, type);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
// Finds the scope where a variable is defined, walking up the chain.
|
|
18
|
+
findScopeFor(name) {
|
|
19
|
+
if (this.symbols.has(name)) {
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
return this.parent ? this.parent.findScopeFor(name) : null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Manages the hierarchy of scopes during analysis.
|
|
26
|
+
export class ScopeManager {
|
|
27
|
+
currentScope;
|
|
28
|
+
allScopes = []; // Array to store all created scopes
|
|
29
|
+
reservedKeywords = new Set(RESERVED_KEYWORDS);
|
|
30
|
+
constructor() {
|
|
31
|
+
const rootScope = new Scope(null); // The global scope
|
|
32
|
+
this.currentScope = rootScope;
|
|
33
|
+
this.allScopes.push(rootScope); // Add the root scope to our list
|
|
34
|
+
this.define("undefined", {
|
|
35
|
+
type: "undefined",
|
|
36
|
+
isConst: true,
|
|
37
|
+
isBuiltin: true,
|
|
38
|
+
});
|
|
39
|
+
this.define("null", {
|
|
40
|
+
type: "null",
|
|
41
|
+
isConst: true,
|
|
42
|
+
isBuiltin: true,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Enters a new, nested scope.
|
|
46
|
+
enterScope() {
|
|
47
|
+
const newScope = new Scope(this.currentScope);
|
|
48
|
+
this.currentScope = newScope;
|
|
49
|
+
this.allScopes.push(newScope); // Add every new scope to the list
|
|
50
|
+
}
|
|
51
|
+
// Exits the current scope, returning to the parent.
|
|
52
|
+
exitScope() {
|
|
53
|
+
if (this.currentScope.parent) {
|
|
54
|
+
this.currentScope = this.currentScope.parent;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Defines a variable in the current scope.
|
|
58
|
+
define(name, type) {
|
|
59
|
+
if (this.reservedKeywords.has(name) && !type.isBuiltin) {
|
|
60
|
+
throw new Error(`SyntaxError: Unexpected reserved word "${name}"`);
|
|
61
|
+
}
|
|
62
|
+
this.currentScope.define(name, type);
|
|
63
|
+
}
|
|
64
|
+
// Looks up a variable's type information from the current scope upwards.
|
|
65
|
+
lookup(name) {
|
|
66
|
+
const scope = this.currentScope.findScopeFor(name);
|
|
67
|
+
return scope ? scope.symbols.get(name) ?? null : null;
|
|
68
|
+
}
|
|
69
|
+
lookupFromScope(name, scope) {
|
|
70
|
+
const definingScope = scope.findScopeFor(name);
|
|
71
|
+
return definingScope ? definingScope.symbols.get(name) ?? null : null;
|
|
72
|
+
}
|
|
73
|
+
// The missing method to retrieve all scopes for the generator.
|
|
74
|
+
getAllScopes() {
|
|
75
|
+
return this.allScopes;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import { Traverser } from "../core/traverser";
|
|
3
|
+
import { Scope, ScopeManager } from "./scope";
|
|
4
|
+
export class TypeAnalyzer {
|
|
5
|
+
traverser = new Traverser();
|
|
6
|
+
scopeManager = new ScopeManager();
|
|
7
|
+
functionTypeInfo = new Map();
|
|
8
|
+
functionStack = [];
|
|
9
|
+
nodeToScope = new Map();
|
|
10
|
+
analyze(ast) {
|
|
11
|
+
this.nodeToScope.set(ast, this.scopeManager.currentScope);
|
|
12
|
+
const visitor = {
|
|
13
|
+
// Enter new scope for any block-like structure
|
|
14
|
+
Block: {
|
|
15
|
+
enter: (node, parent) => {
|
|
16
|
+
this.scopeManager.enterScope();
|
|
17
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
18
|
+
if (parent && ts.isCatchClause(parent) &&
|
|
19
|
+
parent.variableDeclaration) {
|
|
20
|
+
if (ts.isIdentifier(parent.variableDeclaration.name)) {
|
|
21
|
+
const name = parent.variableDeclaration.name
|
|
22
|
+
.getText();
|
|
23
|
+
// The type is basically 'any' or 'string' from the .what()
|
|
24
|
+
const typeInfo = {
|
|
25
|
+
type: "string",
|
|
26
|
+
declaration: parent.variableDeclaration,
|
|
27
|
+
};
|
|
28
|
+
this.scopeManager.define(name, typeInfo);
|
|
29
|
+
}
|
|
30
|
+
// TODO: handle binding patterns in catch clause
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
exit: () => this.scopeManager.exitScope(),
|
|
34
|
+
},
|
|
35
|
+
ForStatement: {
|
|
36
|
+
enter: (node) => {
|
|
37
|
+
this.scopeManager.enterScope();
|
|
38
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
39
|
+
},
|
|
40
|
+
exit: () => this.scopeManager.exitScope(),
|
|
41
|
+
},
|
|
42
|
+
ForOfStatement: {
|
|
43
|
+
enter: (node) => {
|
|
44
|
+
this.scopeManager.enterScope();
|
|
45
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
46
|
+
},
|
|
47
|
+
exit: () => this.scopeManager.exitScope(),
|
|
48
|
+
},
|
|
49
|
+
ForInStatement: {
|
|
50
|
+
enter: (node) => {
|
|
51
|
+
this.scopeManager.enterScope();
|
|
52
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
53
|
+
const forIn = node;
|
|
54
|
+
if (ts.isVariableDeclarationList(forIn.initializer)) {
|
|
55
|
+
const varDecl = forIn.initializer.declarations[0];
|
|
56
|
+
if (varDecl) {
|
|
57
|
+
const name = varDecl.name.getText();
|
|
58
|
+
const isConst = (varDecl.parent.flags & ts.NodeFlags.Const) !== 0;
|
|
59
|
+
const typeInfo = {
|
|
60
|
+
type: "string", // Keys are always strings
|
|
61
|
+
declaration: varDecl,
|
|
62
|
+
isConst,
|
|
63
|
+
};
|
|
64
|
+
this.scopeManager.define(name, typeInfo);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (ts.isIdentifier(forIn.initializer)) {
|
|
68
|
+
// If it's an existing identifier, we don't redefine it, but ensure it's in scope.
|
|
69
|
+
// The generator will handle assigning to it.
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
exit: () => this.scopeManager.exitScope(),
|
|
73
|
+
},
|
|
74
|
+
ArrowFunction: {
|
|
75
|
+
enter: (node) => {
|
|
76
|
+
if (ts.isArrowFunction(node)) {
|
|
77
|
+
const funcType = {
|
|
78
|
+
type: "function",
|
|
79
|
+
isClosure: false,
|
|
80
|
+
captures: new Map(),
|
|
81
|
+
};
|
|
82
|
+
this.functionTypeInfo.set(node, funcType);
|
|
83
|
+
this.scopeManager.enterScope();
|
|
84
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
85
|
+
// Define parameters in the new scope
|
|
86
|
+
node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
|
|
87
|
+
type: "auto",
|
|
88
|
+
isParameter: true,
|
|
89
|
+
declaration: p,
|
|
90
|
+
}));
|
|
91
|
+
this.functionStack.push(node);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
exit: (node) => {
|
|
95
|
+
if (ts.isArrowFunction(node)) {
|
|
96
|
+
this.functionStack.pop();
|
|
97
|
+
}
|
|
98
|
+
this.scopeManager.exitScope();
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
FunctionExpression: {
|
|
102
|
+
enter: (node) => {
|
|
103
|
+
if (ts.isFunctionExpression(node)) {
|
|
104
|
+
const funcType = {
|
|
105
|
+
type: "function",
|
|
106
|
+
isClosure: false,
|
|
107
|
+
captures: new Map(),
|
|
108
|
+
declaration: node,
|
|
109
|
+
};
|
|
110
|
+
this.functionTypeInfo.set(node, funcType);
|
|
111
|
+
this.scopeManager.enterScope();
|
|
112
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
113
|
+
// If the function expression is named, define the name within its own scope for recursion.
|
|
114
|
+
if (node.name) {
|
|
115
|
+
this.scopeManager.define(node.name.getText(), funcType);
|
|
116
|
+
}
|
|
117
|
+
// Define parameters in the new scope
|
|
118
|
+
node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
|
|
119
|
+
type: "auto",
|
|
120
|
+
isParameter: true,
|
|
121
|
+
declaration: p,
|
|
122
|
+
}));
|
|
123
|
+
this.functionStack.push(node);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
exit: (node) => {
|
|
127
|
+
if (ts.isFunctionExpression(node)) {
|
|
128
|
+
this.functionStack.pop();
|
|
129
|
+
}
|
|
130
|
+
this.scopeManager.exitScope();
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
FunctionDeclaration: {
|
|
134
|
+
enter: (node) => {
|
|
135
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
136
|
+
// Define the function in the current scope.
|
|
137
|
+
if (node.name) {
|
|
138
|
+
const funcName = node.name.getText();
|
|
139
|
+
const funcType = {
|
|
140
|
+
type: "function",
|
|
141
|
+
isClosure: false,
|
|
142
|
+
captures: new Map(),
|
|
143
|
+
declaration: node,
|
|
144
|
+
};
|
|
145
|
+
this.scopeManager.define(funcName, funcType);
|
|
146
|
+
this.functionTypeInfo.set(node, funcType);
|
|
147
|
+
}
|
|
148
|
+
this.scopeManager.enterScope();
|
|
149
|
+
this.nodeToScope.set(node, this.scopeManager.currentScope);
|
|
150
|
+
// Define parameters in the new scope
|
|
151
|
+
node.parameters.forEach((p) => this.scopeManager.define(p.name.getText(), {
|
|
152
|
+
type: "auto",
|
|
153
|
+
isParameter: true,
|
|
154
|
+
declaration: p,
|
|
155
|
+
}));
|
|
156
|
+
this.functionStack.push(node);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
exit: (node) => {
|
|
160
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
161
|
+
this.functionStack.pop();
|
|
162
|
+
}
|
|
163
|
+
this.scopeManager.exitScope();
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
VariableDeclaration: {
|
|
167
|
+
enter: (node) => {
|
|
168
|
+
if (ts.isVariableDeclaration(node)) {
|
|
169
|
+
const name = node.name.getText();
|
|
170
|
+
const isConst = (node.parent.flags & ts.NodeFlags.Const) !== 0;
|
|
171
|
+
let type = "auto";
|
|
172
|
+
if (node.initializer) {
|
|
173
|
+
if (ts.isArrayLiteralExpression(node.initializer)) {
|
|
174
|
+
type = "array";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const typeInfo = {
|
|
178
|
+
type,
|
|
179
|
+
declaration: node,
|
|
180
|
+
isConst,
|
|
181
|
+
};
|
|
182
|
+
this.scopeManager.define(name, typeInfo);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
Identifier: {
|
|
187
|
+
enter: (node, parent) => {
|
|
188
|
+
if (ts.isIdentifier(node)) {
|
|
189
|
+
if (node.text === "console") {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const currentFuncNode = this.functionStack[this.functionStack.length - 1];
|
|
193
|
+
if (currentFuncNode &&
|
|
194
|
+
(ts.isFunctionDeclaration(currentFuncNode) ||
|
|
195
|
+
ts.isFunctionExpression(currentFuncNode)) &&
|
|
196
|
+
node.text === currentFuncNode.name?.getText()) {
|
|
197
|
+
return; // Don't treat recursive call as capture
|
|
198
|
+
}
|
|
199
|
+
// Check if this identifier is being used as a variable (not a function name, etc.)
|
|
200
|
+
// And see if it belongs to an outer scope
|
|
201
|
+
const definingScope = this.scopeManager.currentScope
|
|
202
|
+
.findScopeFor(node.text);
|
|
203
|
+
if (definingScope &&
|
|
204
|
+
definingScope !== this.scopeManager.currentScope) {
|
|
205
|
+
// This is a potential capture!
|
|
206
|
+
// Find which function we are currently in and mark the capture.
|
|
207
|
+
if (currentFuncNode) {
|
|
208
|
+
const type = this.scopeManager.lookup(node.text);
|
|
209
|
+
if (type) {
|
|
210
|
+
const info = this.functionTypeInfo.get(currentFuncNode);
|
|
211
|
+
if (info) {
|
|
212
|
+
info.isClosure = true;
|
|
213
|
+
info.captures?.set(node.text, type);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
this.traverser.traverse(ast, visitor);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { Interpreter } from "./index";
|
|
5
|
+
async function main() {
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
if (args.length === 0) {
|
|
8
|
+
console.error("Usage: jspp <path-to-js-file>");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const jsFilePath = path.resolve(process.cwd(), args[0]);
|
|
12
|
+
const jsFileName = path.basename(jsFilePath, ".js");
|
|
13
|
+
const outputDir = path.dirname(jsFilePath);
|
|
14
|
+
const cppFilePath = path.join(outputDir, `${jsFileName}.cpp`);
|
|
15
|
+
const exeFilePath = path.join(outputDir, `${jsFileName}.exe`);
|
|
16
|
+
try {
|
|
17
|
+
const jsCode = await fs.readFile(jsFilePath, "utf-8");
|
|
18
|
+
const interpreter = new Interpreter();
|
|
19
|
+
const { cppCode, preludePath } = interpreter.interpret(jsCode);
|
|
20
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
21
|
+
await fs.writeFile(cppFilePath, cppCode);
|
|
22
|
+
console.log(`Compiling ${cppFilePath}...`);
|
|
23
|
+
const compile = Bun.spawnSync({
|
|
24
|
+
cmd: [
|
|
25
|
+
"g++",
|
|
26
|
+
"-std=c++20",
|
|
27
|
+
cppFilePath,
|
|
28
|
+
"-o",
|
|
29
|
+
exeFilePath,
|
|
30
|
+
"-I",
|
|
31
|
+
preludePath,
|
|
32
|
+
"-O2",
|
|
33
|
+
"-DNDEBUG",
|
|
34
|
+
// "-include",
|
|
35
|
+
// path.join(process.cwd(), "prelude-build", "index.hpp"),
|
|
36
|
+
],
|
|
37
|
+
stdout: "inherit",
|
|
38
|
+
stderr: "inherit",
|
|
39
|
+
});
|
|
40
|
+
if (compile.exitCode !== 0) {
|
|
41
|
+
console.error(`Compilation failed for ${jsFileName}.js`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
console.log(`Running ${exeFilePath}...`);
|
|
45
|
+
console.log("\x1b[32m\n------start------\x1b[0m");
|
|
46
|
+
const run = Bun.spawnSync({
|
|
47
|
+
cmd: [exeFilePath],
|
|
48
|
+
stdout: "inherit",
|
|
49
|
+
stderr: "inherit",
|
|
50
|
+
});
|
|
51
|
+
console.log("\x1b[32m\n------end--------\x1b[0m");
|
|
52
|
+
if (run.exitCode !== 0) {
|
|
53
|
+
console.error(`Execution failed for ${jsFileName}.js`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.log(`Successfully ran ${jsFileName}.js`);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error(`Error: ${error.message}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
main();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { CodeGenerator } from "./";
|
|
3
|
+
export function visitVariableDeclarationList(node, context) {
|
|
4
|
+
return node.declarations
|
|
5
|
+
.map((d) => this.visit(d, context))
|
|
6
|
+
.filter(Boolean)
|
|
7
|
+
.join(", ");
|
|
8
|
+
}
|
|
9
|
+
export function visitVariableDeclaration(node, context) {
|
|
10
|
+
const varDecl = node;
|
|
11
|
+
const name = varDecl.name.getText();
|
|
12
|
+
let initializer = "";
|
|
13
|
+
if (varDecl.initializer) {
|
|
14
|
+
const initExpr = varDecl.initializer;
|
|
15
|
+
let initText = this.visit(initExpr, context);
|
|
16
|
+
if (ts.isIdentifier(initExpr)) {
|
|
17
|
+
const scope = this.getScopeForNode(initExpr);
|
|
18
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(initExpr.text, scope);
|
|
19
|
+
const varName = this.getJsVarName(initExpr);
|
|
20
|
+
if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
|
|
21
|
+
initText = `jspp::Access::deref(${initText}, ${varName})`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
initializer = " = " + initText;
|
|
25
|
+
}
|
|
26
|
+
const isLetOrConst = (varDecl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
27
|
+
if (isLetOrConst) {
|
|
28
|
+
// If there's no initializer, it should be assigned undefined.
|
|
29
|
+
if (!initializer)
|
|
30
|
+
return `*${name} = jspp::AnyValue::make_undefined()`;
|
|
31
|
+
return `*${name}${initializer}`;
|
|
32
|
+
}
|
|
33
|
+
// For 'var', it's a bit more complex.
|
|
34
|
+
// If we are in a non-function-body block, 'var' is hoisted, so it's an assignment.
|
|
35
|
+
// If we are at the top level or in a function body, it's a declaration if not already hoisted.
|
|
36
|
+
// The current logic hoists at the function level, so we need to decide if this is the *hoisting* declaration or a later assignment.
|
|
37
|
+
// The `isAssignmentOnly` flag helps here.
|
|
38
|
+
if (context.isAssignmentOnly) {
|
|
39
|
+
if (!initializer)
|
|
40
|
+
return "";
|
|
41
|
+
return `*${name}${initializer}`;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const initValue = initializer
|
|
45
|
+
? initializer.substring(3)
|
|
46
|
+
: "jspp::AnyValue::make_undefined()";
|
|
47
|
+
return `auto ${name} = std::make_shared<jspp::AnyValue>(${initValue})`;
|
|
48
|
+
}
|
|
49
|
+
}
|