@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
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { CodeGenerator } from "./";
|
|
3
|
+
function visitObjectPropertyName(node, context) {
|
|
4
|
+
if (ts.isStringLiteral(node)) {
|
|
5
|
+
return `"${node.getText().substring(1, node.getText().length - 1) // remove trailing "' from original name
|
|
6
|
+
}"`;
|
|
7
|
+
}
|
|
8
|
+
else if (ts.isComputedPropertyName(node)) {
|
|
9
|
+
let name = ts.isIdentifier(node.expression)
|
|
10
|
+
? `jspp::Access::deref(${node.expression.getText()},${this.getJsVarName(node.expression)})`
|
|
11
|
+
: this.visit(node.expression, context);
|
|
12
|
+
name += ".to_std_string()";
|
|
13
|
+
return name;
|
|
14
|
+
}
|
|
15
|
+
return context.isPropertyNameAccess
|
|
16
|
+
? node.getText()
|
|
17
|
+
: `"${node.getText()}"`;
|
|
18
|
+
}
|
|
19
|
+
export function visitObjectLiteralExpression(node, context) {
|
|
20
|
+
const obj = node;
|
|
21
|
+
let props = "";
|
|
22
|
+
for (const prop of obj.properties) {
|
|
23
|
+
// console.log("Property kind:", ts.SyntaxKind[prop.kind]);
|
|
24
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
25
|
+
const key = visitObjectPropertyName.call(this, prop.name, context);
|
|
26
|
+
const value = ts.isIdentifier(prop.initializer)
|
|
27
|
+
? `jspp::Access::deref(${this.visit(prop.initializer, context)}, ${this.getJsVarName(prop.initializer)})`
|
|
28
|
+
: this.visit(prop.initializer, context);
|
|
29
|
+
props += `{${key}, ${value}},`;
|
|
30
|
+
}
|
|
31
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
32
|
+
const key = visitObjectPropertyName.call(this, prop.name, context);
|
|
33
|
+
const value = `jspp::Access::deref(${this.visit(prop.name, context)}, ${this.getJsVarName(prop.name)})`;
|
|
34
|
+
props += `{${key}, ${value}},`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return `jspp::AnyValue::make_object({${props}})`;
|
|
38
|
+
}
|
|
39
|
+
export function visitArrayLiteralExpression(node, context) {
|
|
40
|
+
const elements = node.elements
|
|
41
|
+
.map((elem) => ts.isIdentifier(elem)
|
|
42
|
+
? `jspp::Access::deref(${this.visit(elem, context)}, ${this.getJsVarName(elem)})`
|
|
43
|
+
: this.visit(elem, context))
|
|
44
|
+
.join(", ");
|
|
45
|
+
return `jspp::AnyValue::make_array({${elements}})`;
|
|
46
|
+
}
|
|
47
|
+
export function visitPrefixUnaryExpression(node, context) {
|
|
48
|
+
const prefixUnaryExpr = node;
|
|
49
|
+
const operand = this.visit(prefixUnaryExpr.operand, context);
|
|
50
|
+
const operator = ts.tokenToString(prefixUnaryExpr.operator);
|
|
51
|
+
if (operator === "++" || operator === "--") {
|
|
52
|
+
return `${operator}(*${operand})`;
|
|
53
|
+
}
|
|
54
|
+
if (operator === "~") {
|
|
55
|
+
return `${operator}(*${operand})`;
|
|
56
|
+
}
|
|
57
|
+
return `${operator}${operand}`;
|
|
58
|
+
}
|
|
59
|
+
export function visitPostfixUnaryExpression(node, context) {
|
|
60
|
+
const postfixUnaryExpr = node;
|
|
61
|
+
const operand = this.visit(postfixUnaryExpr.operand, context);
|
|
62
|
+
const operator = ts.tokenToString(postfixUnaryExpr.operator);
|
|
63
|
+
return `(*${operand})${operator}`;
|
|
64
|
+
}
|
|
65
|
+
export function visitParenthesizedExpression(node, context) {
|
|
66
|
+
const parenExpr = node;
|
|
67
|
+
return `(${this.visit(parenExpr.expression, context)})`;
|
|
68
|
+
}
|
|
69
|
+
export function visitPropertyAccessExpression(node, context) {
|
|
70
|
+
const propAccess = node;
|
|
71
|
+
const exprText = this.visit(propAccess.expression, context);
|
|
72
|
+
const propName = propAccess.name.getText();
|
|
73
|
+
const scope = this.getScopeForNode(propAccess.expression);
|
|
74
|
+
const typeInfo = ts.isIdentifier(propAccess.expression)
|
|
75
|
+
? this.typeAnalyzer.scopeManager.lookupFromScope(propAccess.expression.getText(), scope)
|
|
76
|
+
: null;
|
|
77
|
+
if (ts.isIdentifier(propAccess.expression) && !typeInfo &&
|
|
78
|
+
!this.isBuiltinObject(propAccess.expression)) {
|
|
79
|
+
return `jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(propAccess.expression)})`;
|
|
80
|
+
}
|
|
81
|
+
let finalExpr = "";
|
|
82
|
+
if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
|
|
83
|
+
finalExpr = `jspp::Access::deref(${exprText}, ${this.getJsVarName(propAccess.expression)})`;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
finalExpr = exprText;
|
|
87
|
+
}
|
|
88
|
+
return `${finalExpr}.get_own_property("${propName}")`;
|
|
89
|
+
// return `${finalExpr}["${propName}"]`;
|
|
90
|
+
}
|
|
91
|
+
export function visitElementAccessExpression(node, context) {
|
|
92
|
+
const elemAccess = node;
|
|
93
|
+
const exprText = this.visit(elemAccess.expression, context);
|
|
94
|
+
let argText = visitObjectPropertyName.call(this, elemAccess.argumentExpression, { ...context, isPropertyNameAccess: true });
|
|
95
|
+
// Dereference the expression being accessed
|
|
96
|
+
const exprScope = this.getScopeForNode(elemAccess.expression);
|
|
97
|
+
const exprTypeInfo = ts.isIdentifier(elemAccess.expression)
|
|
98
|
+
? this.typeAnalyzer.scopeManager.lookupFromScope(elemAccess.expression.getText(), exprScope)
|
|
99
|
+
: null;
|
|
100
|
+
if (ts.isIdentifier(elemAccess.expression) && !exprTypeInfo &&
|
|
101
|
+
!this.isBuiltinObject(elemAccess.expression)) {
|
|
102
|
+
return `jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(elemAccess.expression)})`;
|
|
103
|
+
}
|
|
104
|
+
const finalExpr = exprTypeInfo && !exprTypeInfo.isParameter && !exprTypeInfo.isBuiltin
|
|
105
|
+
? `jspp::Access::deref(${exprText}, ${this.getJsVarName(elemAccess.expression)})`
|
|
106
|
+
: exprText;
|
|
107
|
+
// Dereference the argument expression if it's an identifier
|
|
108
|
+
if (ts.isIdentifier(elemAccess.argumentExpression)) {
|
|
109
|
+
const argScope = this.getScopeForNode(elemAccess.argumentExpression);
|
|
110
|
+
const argTypeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(elemAccess.argumentExpression.getText(), argScope);
|
|
111
|
+
if (!argTypeInfo && !this.isBuiltinObject(elemAccess.argumentExpression)) {
|
|
112
|
+
return `jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(elemAccess.argumentExpression)})`;
|
|
113
|
+
}
|
|
114
|
+
if (argTypeInfo && !argTypeInfo.isParameter && !argTypeInfo.isBuiltin) {
|
|
115
|
+
argText = `jspp::Access::deref(${argText}, ${this.getJsVarName(elemAccess.argumentExpression)})`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return `${finalExpr}.get_own_property(${argText})`;
|
|
119
|
+
// return `${finalExpr}[${argText}]`;
|
|
120
|
+
}
|
|
121
|
+
export function visitBinaryExpression(node, context) {
|
|
122
|
+
const binExpr = node;
|
|
123
|
+
const opToken = binExpr.operatorToken;
|
|
124
|
+
let op = opToken.getText();
|
|
125
|
+
if (opToken.kind === ts.SyntaxKind.PlusEqualsToken ||
|
|
126
|
+
opToken.kind === ts.SyntaxKind.MinusEqualsToken ||
|
|
127
|
+
opToken.kind === ts.SyntaxKind.AsteriskEqualsToken ||
|
|
128
|
+
opToken.kind === ts.SyntaxKind.SlashEqualsToken ||
|
|
129
|
+
opToken.kind === ts.SyntaxKind.PercentEqualsToken) {
|
|
130
|
+
const leftText = this.visit(binExpr.left, context);
|
|
131
|
+
const rightText = this.visit(binExpr.right, context);
|
|
132
|
+
return `*${leftText} ${op} ${rightText}`;
|
|
133
|
+
}
|
|
134
|
+
if (opToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
135
|
+
const rightText = this.visit(binExpr.right, context);
|
|
136
|
+
if (ts.isPropertyAccessExpression(binExpr.left)) {
|
|
137
|
+
const propAccess = binExpr.left;
|
|
138
|
+
const objExprText = this.visit(propAccess.expression, context);
|
|
139
|
+
const propName = propAccess.name.getText();
|
|
140
|
+
const scope = this.getScopeForNode(propAccess.expression);
|
|
141
|
+
const typeInfo = ts.isIdentifier(propAccess.expression)
|
|
142
|
+
? this.typeAnalyzer.scopeManager.lookupFromScope(propAccess.expression.getText(), scope)
|
|
143
|
+
: null;
|
|
144
|
+
const finalObjExpr = typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin
|
|
145
|
+
? `jspp::Access::deref(${objExprText}, ${this.getJsVarName(propAccess.expression)})`
|
|
146
|
+
: objExprText;
|
|
147
|
+
let finalRightText = rightText;
|
|
148
|
+
if (ts.isIdentifier(binExpr.right)) {
|
|
149
|
+
const rightScope = this.getScopeForNode(binExpr.right);
|
|
150
|
+
const rightTypeInfo = this.typeAnalyzer.scopeManager
|
|
151
|
+
.lookupFromScope(binExpr.right.getText(), rightScope);
|
|
152
|
+
if (rightTypeInfo &&
|
|
153
|
+
!rightTypeInfo.isParameter &&
|
|
154
|
+
!rightTypeInfo.isBuiltin) {
|
|
155
|
+
finalRightText = `jspp::Access::deref(${rightText}, ${this.getJsVarName(binExpr.right)})`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return `${finalObjExpr}.set_own_property("${propName}", ${finalRightText})`;
|
|
159
|
+
// return `${finalObjExpr}["${propName}"] = ${finalRightText}`;
|
|
160
|
+
}
|
|
161
|
+
else if (ts.isElementAccessExpression(binExpr.left)) {
|
|
162
|
+
const elemAccess = binExpr.left;
|
|
163
|
+
const objExprText = this.visit(elemAccess.expression, context);
|
|
164
|
+
let argText = visitObjectPropertyName.call(this, elemAccess.argumentExpression, { ...context, isPropertyNameAccess: true });
|
|
165
|
+
const scope = this.getScopeForNode(elemAccess.expression);
|
|
166
|
+
const typeInfo = ts.isIdentifier(elemAccess.expression)
|
|
167
|
+
? this.typeAnalyzer.scopeManager.lookupFromScope(elemAccess.expression.getText(), scope)
|
|
168
|
+
: null;
|
|
169
|
+
const finalObjExpr = typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin
|
|
170
|
+
? `jspp::Access::deref(${objExprText}, ${this.getJsVarName(elemAccess.expression)})`
|
|
171
|
+
: objExprText;
|
|
172
|
+
if (ts.isIdentifier(elemAccess.argumentExpression)) {
|
|
173
|
+
const argScope = this.getScopeForNode(elemAccess.argumentExpression);
|
|
174
|
+
const argTypeInfo = this.typeAnalyzer.scopeManager
|
|
175
|
+
.lookupFromScope(elemAccess.argumentExpression.getText(), argScope);
|
|
176
|
+
if (argTypeInfo &&
|
|
177
|
+
!argTypeInfo.isParameter &&
|
|
178
|
+
!argTypeInfo.isBuiltin) {
|
|
179
|
+
argText = `jspp::Access::deref(${argText}, ${this.getJsVarName(elemAccess.argumentExpression)})`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
let finalRightText = rightText;
|
|
183
|
+
if (ts.isIdentifier(binExpr.right)) {
|
|
184
|
+
const rightScope = this.getScopeForNode(binExpr.right);
|
|
185
|
+
const rightTypeInfo = this.typeAnalyzer.scopeManager
|
|
186
|
+
.lookupFromScope(binExpr.right.getText(), rightScope);
|
|
187
|
+
if (rightTypeInfo &&
|
|
188
|
+
!rightTypeInfo.isParameter &&
|
|
189
|
+
!rightTypeInfo.isBuiltin) {
|
|
190
|
+
finalRightText = `jspp::Access::deref(${rightText}, ${this.getJsVarName(binExpr.right)})`;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return `${finalObjExpr}.set_own_property(${argText}, ${finalRightText})`;
|
|
194
|
+
// return `${finalObjExpr}[${argText}] = ${finalRightText}`;
|
|
195
|
+
}
|
|
196
|
+
const leftText = this.visit(binExpr.left, context);
|
|
197
|
+
const scope = this.getScopeForNode(binExpr.left);
|
|
198
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.text, scope);
|
|
199
|
+
if (!typeInfo && !this.isBuiltinObject(binExpr.left)) {
|
|
200
|
+
return `jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(binExpr.left)})`;
|
|
201
|
+
}
|
|
202
|
+
if (typeInfo?.isConst) {
|
|
203
|
+
return `jspp::RuntimeError::throw_immutable_assignment_error()`;
|
|
204
|
+
}
|
|
205
|
+
return `*${leftText} ${op} ${rightText}`;
|
|
206
|
+
}
|
|
207
|
+
const leftText = this.visit(binExpr.left, context);
|
|
208
|
+
const rightText = this.visit(binExpr.right, context);
|
|
209
|
+
const leftIsIdentifier = ts.isIdentifier(binExpr.left);
|
|
210
|
+
const rightIsIdentifier = ts.isIdentifier(binExpr.right);
|
|
211
|
+
const scope = this.getScopeForNode(node);
|
|
212
|
+
const leftTypeInfo = leftIsIdentifier
|
|
213
|
+
? this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.getText(), scope)
|
|
214
|
+
: null;
|
|
215
|
+
const rightTypeInfo = rightIsIdentifier
|
|
216
|
+
? this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.right.getText(), scope)
|
|
217
|
+
: null;
|
|
218
|
+
const finalLeft = leftIsIdentifier && leftTypeInfo && !leftTypeInfo.isParameter &&
|
|
219
|
+
!leftTypeInfo.isBuiltin
|
|
220
|
+
? `jspp::Access::deref(${leftText}, ${this.getJsVarName(binExpr.left)})`
|
|
221
|
+
: leftText;
|
|
222
|
+
const finalRight = rightIsIdentifier && rightTypeInfo && !rightTypeInfo.isParameter &&
|
|
223
|
+
!rightTypeInfo.isBuiltin
|
|
224
|
+
? `jspp::Access::deref(${rightText}, ${this.getJsVarName(binExpr.right)})`
|
|
225
|
+
: rightText;
|
|
226
|
+
if (leftIsIdentifier && !leftTypeInfo &&
|
|
227
|
+
!this.isBuiltinObject(binExpr.left)) {
|
|
228
|
+
return `jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(binExpr.left)})`;
|
|
229
|
+
}
|
|
230
|
+
if (rightIsIdentifier && !rightTypeInfo &&
|
|
231
|
+
!this.isBuiltinObject(binExpr.right)) {
|
|
232
|
+
return `jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(binExpr.right)})`;
|
|
233
|
+
}
|
|
234
|
+
if (opToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {
|
|
235
|
+
return `${finalLeft}.is_strictly_equal_to_primitive(${finalRight})`;
|
|
236
|
+
}
|
|
237
|
+
if (opToken.kind === ts.SyntaxKind.EqualsEqualsToken) {
|
|
238
|
+
return `${finalLeft}.is_equal_to_primitive(${finalRight})`;
|
|
239
|
+
}
|
|
240
|
+
if (opToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
|
|
241
|
+
return `${finalLeft}.not_strictly_equal_to_primitive(${finalRight})`;
|
|
242
|
+
}
|
|
243
|
+
if (opToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
|
|
244
|
+
return `${finalLeft}.not_equal_to_primitive(${finalRight})`;
|
|
245
|
+
}
|
|
246
|
+
if (opToken.kind === ts.SyntaxKind.AsteriskAsteriskToken) {
|
|
247
|
+
return `jspp::pow(${finalLeft}, ${finalRight})`;
|
|
248
|
+
}
|
|
249
|
+
if (op === "+" ||
|
|
250
|
+
op === "-" ||
|
|
251
|
+
op === "*" ||
|
|
252
|
+
op === "/" ||
|
|
253
|
+
op === "%" ||
|
|
254
|
+
op === "^" ||
|
|
255
|
+
op === "&" ||
|
|
256
|
+
op === "|") {
|
|
257
|
+
return `(${finalLeft} ${op} ${finalRight})`;
|
|
258
|
+
}
|
|
259
|
+
return `${finalLeft} ${op} ${finalRight}`;
|
|
260
|
+
}
|
|
261
|
+
export function visitCallExpression(node, context) {
|
|
262
|
+
const callExpr = node;
|
|
263
|
+
const callee = callExpr.expression;
|
|
264
|
+
const calleeName = this.escapeString(callee.getText());
|
|
265
|
+
const args = callExpr.arguments
|
|
266
|
+
.map((arg) => {
|
|
267
|
+
const argText = this.visit(arg, context);
|
|
268
|
+
if (ts.isIdentifier(arg)) {
|
|
269
|
+
const scope = this.getScopeForNode(arg);
|
|
270
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(arg.text, scope);
|
|
271
|
+
if (!typeInfo) {
|
|
272
|
+
return `jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(arg)})`;
|
|
273
|
+
}
|
|
274
|
+
if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
|
|
275
|
+
return `jspp::Access::deref(${argText}, ${this.getJsVarName(arg)})`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return argText;
|
|
279
|
+
})
|
|
280
|
+
.join(", ");
|
|
281
|
+
const calleeCode = this.visit(callee, context);
|
|
282
|
+
let derefCallee;
|
|
283
|
+
if (ts.isIdentifier(callee)) {
|
|
284
|
+
const scope = this.getScopeForNode(callee);
|
|
285
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(callee.text, scope);
|
|
286
|
+
if (!typeInfo && !this.isBuiltinObject(callee)) {
|
|
287
|
+
return `jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(callee)})`;
|
|
288
|
+
}
|
|
289
|
+
if (typeInfo?.isBuiltin) {
|
|
290
|
+
derefCallee = calleeCode;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
derefCallee = `jspp::Access::deref(${calleeCode}, ${this.getJsVarName(callee)})`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
derefCallee = calleeCode;
|
|
298
|
+
}
|
|
299
|
+
return `${derefCallee}.as_function()->call({${args}})`;
|
|
300
|
+
// return `${derefCallee}.as_function("${calleeName}")->call({${args}})`;
|
|
301
|
+
}
|
|
302
|
+
export function visitVoidExpression(node, context) {
|
|
303
|
+
const voidExpr = node;
|
|
304
|
+
const exprText = this.visit(voidExpr.expression, context);
|
|
305
|
+
return `(${exprText}, jspp::AnyValue::make_undefined())`;
|
|
306
|
+
}
|
|
307
|
+
export function visitTemplateExpression(node, context) {
|
|
308
|
+
const templateExpr = node;
|
|
309
|
+
let result = `jspp::AnyValue::make_string("${this.escapeString(templateExpr.head.text)}")`;
|
|
310
|
+
for (const span of templateExpr.templateSpans) {
|
|
311
|
+
const expr = span.expression;
|
|
312
|
+
const exprText = this.visit(expr, context);
|
|
313
|
+
let finalExpr = exprText;
|
|
314
|
+
if (ts.isIdentifier(expr)) {
|
|
315
|
+
const scope = this.getScopeForNode(expr);
|
|
316
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(expr.text, scope);
|
|
317
|
+
if (!typeInfo && !this.isBuiltinObject(expr)) {
|
|
318
|
+
finalExpr =
|
|
319
|
+
`jspp::RuntimeError::throw_unresolved_reference_error(${this.getJsVarName(expr)})`;
|
|
320
|
+
}
|
|
321
|
+
else if (typeInfo &&
|
|
322
|
+
!typeInfo.isParameter &&
|
|
323
|
+
!typeInfo.isBuiltin) {
|
|
324
|
+
finalExpr = `jspp::Access::deref(${exprText}, ${this.getJsVarName(expr)})`;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
result += ` + (${finalExpr})`;
|
|
328
|
+
if (span.literal.text) {
|
|
329
|
+
result += ` + jspp::AnyValue::make_string("${this.escapeString(span.literal.text)}")`;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { CodeGenerator } from "./";
|
|
3
|
+
export function generateLambda(node, isAssignment = false, capture = "[=]") {
|
|
4
|
+
const declaredSymbols = this.getDeclaredSymbols(node);
|
|
5
|
+
const argsName = this.generateUniqueName("__args_", declaredSymbols);
|
|
6
|
+
let lambda = `${capture}(const std::vector<jspp::AnyValue>& ${argsName}) mutable -> jspp::AnyValue `;
|
|
7
|
+
const visitContext = {
|
|
8
|
+
isMainContext: false,
|
|
9
|
+
isInsideFunction: true,
|
|
10
|
+
isFunctionBody: false,
|
|
11
|
+
};
|
|
12
|
+
if (node.body) {
|
|
13
|
+
if (ts.isBlock(node.body)) {
|
|
14
|
+
let paramExtraction = "";
|
|
15
|
+
this.indentationLevel++;
|
|
16
|
+
node.parameters.forEach((p, i) => {
|
|
17
|
+
const name = p.name.getText();
|
|
18
|
+
const defaultValue = p.initializer
|
|
19
|
+
? this.visit(p.initializer, visitContext)
|
|
20
|
+
: "jspp::AnyValue::make_undefined()";
|
|
21
|
+
paramExtraction +=
|
|
22
|
+
`${this.indent()}auto ${name} = ${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue};\n`;
|
|
23
|
+
});
|
|
24
|
+
this.indentationLevel--;
|
|
25
|
+
const blockContent = this.visit(node.body, {
|
|
26
|
+
isMainContext: false,
|
|
27
|
+
isInsideFunction: true,
|
|
28
|
+
isFunctionBody: true,
|
|
29
|
+
});
|
|
30
|
+
// The block visitor already adds braces, so we need to inject the param extraction.
|
|
31
|
+
lambda += "{\n" + paramExtraction + blockContent.substring(2);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
lambda += "{\n";
|
|
35
|
+
this.indentationLevel++;
|
|
36
|
+
node.parameters.forEach((p, i) => {
|
|
37
|
+
const name = p.name.getText();
|
|
38
|
+
const defaultValue = p.initializer
|
|
39
|
+
? this.visit(p.initializer, visitContext)
|
|
40
|
+
: "jspp::AnyValue::make_undefined()";
|
|
41
|
+
lambda +=
|
|
42
|
+
`${this.indent()}auto ${name} = ${argsName}.size() > ${i} ? ${argsName}[${i}] : ${defaultValue};\n`;
|
|
43
|
+
});
|
|
44
|
+
lambda += `${this.indent()}return ${this.visit(node.body, {
|
|
45
|
+
isMainContext: false,
|
|
46
|
+
isInsideFunction: true,
|
|
47
|
+
isFunctionBody: false,
|
|
48
|
+
})};
|
|
49
|
+
`;
|
|
50
|
+
this.indentationLevel--;
|
|
51
|
+
lambda += `${this.indent()}}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
lambda += "{ return jspp::AnyValue::make_undefined(); }\n";
|
|
56
|
+
}
|
|
57
|
+
const signature = `jspp::AnyValue(const std::vector<jspp::AnyValue>&)`;
|
|
58
|
+
const callable = `std::function<${signature}>(${lambda})`;
|
|
59
|
+
const funcName = node.name?.getText();
|
|
60
|
+
const fullExpression = `jspp::AnyValue::make_function(${callable}, "${funcName || ""}")`;
|
|
61
|
+
if (ts.isFunctionDeclaration(node) && !isAssignment && funcName) {
|
|
62
|
+
return `${this.indent()}auto ${funcName} = ${fullExpression};\n`;
|
|
63
|
+
}
|
|
64
|
+
return fullExpression;
|
|
65
|
+
}
|
|
66
|
+
export function visitFunctionDeclaration(node, context) {
|
|
67
|
+
if (context.isInsideFunction) {
|
|
68
|
+
// This will now be handled by the Block visitor for hoisting.
|
|
69
|
+
// However, we still need to generate the lambda for assignment.
|
|
70
|
+
// The block visitor will wrap this in an assignment.
|
|
71
|
+
return this.generateLambda(node);
|
|
72
|
+
}
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
export function visitArrowFunction(node, context) {
|
|
76
|
+
return this.generateLambda(node);
|
|
77
|
+
}
|
|
78
|
+
export function visitFunctionExpression(node, context) {
|
|
79
|
+
const funcExpr = node;
|
|
80
|
+
if (funcExpr.name) {
|
|
81
|
+
const funcName = funcExpr.name.getText();
|
|
82
|
+
let code = "([=]() -> jspp::AnyValue {\n";
|
|
83
|
+
this.indentationLevel++;
|
|
84
|
+
code +=
|
|
85
|
+
`${this.indent()}auto ${funcName} = std::make_shared<jspp::AnyValue>();\n`;
|
|
86
|
+
const lambda = this.generateLambda(funcExpr, false, "[=]");
|
|
87
|
+
code += `${this.indent()}*${funcName} = ${lambda};\n`;
|
|
88
|
+
code += `${this.indent()}return *${funcName};\n`;
|
|
89
|
+
this.indentationLevel--;
|
|
90
|
+
code += `${this.indent()}})()`;
|
|
91
|
+
return code;
|
|
92
|
+
}
|
|
93
|
+
return this.generateLambda(node);
|
|
94
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { Scope } from "../../analysis/scope";
|
|
3
|
+
import { CodeGenerator } from "./";
|
|
4
|
+
const BUILTIN_OBJECTS = new Set([
|
|
5
|
+
"global",
|
|
6
|
+
"globalThis",
|
|
7
|
+
"console",
|
|
8
|
+
"Symbol",
|
|
9
|
+
]);
|
|
10
|
+
export function isBuiltinObject(node) {
|
|
11
|
+
return BUILTIN_OBJECTS.has(node.text);
|
|
12
|
+
}
|
|
13
|
+
export function getDeclaredSymbols(node) {
|
|
14
|
+
const symbols = new Set();
|
|
15
|
+
const visitor = (child) => {
|
|
16
|
+
if (ts.isVariableDeclaration(child)) {
|
|
17
|
+
// Handles let, const, var
|
|
18
|
+
symbols.add(child.name.getText());
|
|
19
|
+
}
|
|
20
|
+
else if (ts.isFunctionDeclaration(child) && child.name) {
|
|
21
|
+
// Handles function declarations
|
|
22
|
+
symbols.add(child.name.getText());
|
|
23
|
+
}
|
|
24
|
+
else if (ts.isParameter(child)) {
|
|
25
|
+
// Handles function parameters
|
|
26
|
+
symbols.add(child.name.getText());
|
|
27
|
+
}
|
|
28
|
+
else if (ts.isCatchClause(child) && child.variableDeclaration) {
|
|
29
|
+
// Handles catch clause variable
|
|
30
|
+
symbols.add(child.variableDeclaration.name.getText());
|
|
31
|
+
}
|
|
32
|
+
ts.forEachChild(child, visitor);
|
|
33
|
+
};
|
|
34
|
+
visitor(node);
|
|
35
|
+
return symbols;
|
|
36
|
+
}
|
|
37
|
+
export function generateUniqueName(prefix, namesToAvoid) {
|
|
38
|
+
let name = `${prefix}${this.exceptionCounter}`;
|
|
39
|
+
while (namesToAvoid.has(name)) {
|
|
40
|
+
this.exceptionCounter++;
|
|
41
|
+
name = `${prefix}${this.exceptionCounter}`;
|
|
42
|
+
}
|
|
43
|
+
this.exceptionCounter++;
|
|
44
|
+
return name;
|
|
45
|
+
}
|
|
46
|
+
export function generateUniqueExceptionName(nameToAvoid) {
|
|
47
|
+
let exceptionName = `__caught_exception_${this.exceptionCounter}`;
|
|
48
|
+
while (exceptionName === nameToAvoid) {
|
|
49
|
+
this.exceptionCounter++;
|
|
50
|
+
exceptionName = `__caught_exception_${this.exceptionCounter}`;
|
|
51
|
+
}
|
|
52
|
+
this.exceptionCounter++;
|
|
53
|
+
return exceptionName;
|
|
54
|
+
}
|
|
55
|
+
export function getScopeForNode(node) {
|
|
56
|
+
let current = node;
|
|
57
|
+
while (current) {
|
|
58
|
+
const scope = this.typeAnalyzer.nodeToScope.get(current);
|
|
59
|
+
if (scope) {
|
|
60
|
+
return scope;
|
|
61
|
+
}
|
|
62
|
+
current = current.parent;
|
|
63
|
+
}
|
|
64
|
+
const rootScope = this.typeAnalyzer.scopeManager.getAllScopes()[0];
|
|
65
|
+
if (!rootScope) {
|
|
66
|
+
throw new Error("Compiler bug: Could not find a root scope.");
|
|
67
|
+
}
|
|
68
|
+
return rootScope;
|
|
69
|
+
}
|
|
70
|
+
export function indent() {
|
|
71
|
+
return " ".repeat(this.indentationLevel);
|
|
72
|
+
}
|
|
73
|
+
export function escapeString(str) {
|
|
74
|
+
return str
|
|
75
|
+
.replace(/\\/g, "\\\\")
|
|
76
|
+
.replace(/"/g, '\\"')
|
|
77
|
+
.replace(/\n/g, "\\n")
|
|
78
|
+
.replace(/\r/g, "\\r")
|
|
79
|
+
.replace(/\t/g, "\\t");
|
|
80
|
+
}
|
|
81
|
+
export function getJsVarName(node) {
|
|
82
|
+
return `"${node.text}"`;
|
|
83
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { TypeAnalyzer } from "../../analysis/typeAnalyzer";
|
|
2
|
+
import { generateLambda } from "./function-handlers";
|
|
3
|
+
import { escapeString, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getJsVarName, getScopeForNode, indent, isBuiltinObject, } from "./helpers";
|
|
4
|
+
import { visit } from "./visitor";
|
|
5
|
+
const CONTAINER_FUNCTION_NAME = "__container__";
|
|
6
|
+
export class CodeGenerator {
|
|
7
|
+
indentationLevel = 0;
|
|
8
|
+
typeAnalyzer;
|
|
9
|
+
exceptionCounter = 0;
|
|
10
|
+
// visitor
|
|
11
|
+
visit = visit;
|
|
12
|
+
// helpers
|
|
13
|
+
getDeclaredSymbols = getDeclaredSymbols;
|
|
14
|
+
generateUniqueName = generateUniqueName;
|
|
15
|
+
generateUniqueExceptionName = generateUniqueExceptionName;
|
|
16
|
+
getScopeForNode = getScopeForNode;
|
|
17
|
+
indent = indent;
|
|
18
|
+
escapeString = escapeString;
|
|
19
|
+
getJsVarName = getJsVarName;
|
|
20
|
+
isBuiltinObject = isBuiltinObject;
|
|
21
|
+
// function handlers
|
|
22
|
+
generateLambda = generateLambda;
|
|
23
|
+
/**
|
|
24
|
+
* Main entry point for the code generation process.
|
|
25
|
+
*/
|
|
26
|
+
generate(ast, analyzer) {
|
|
27
|
+
this.typeAnalyzer = analyzer;
|
|
28
|
+
const declarations = `#include "index.hpp"\n\n`;
|
|
29
|
+
let containerCode = `jspp::AnyValue ${CONTAINER_FUNCTION_NAME}() {\n`;
|
|
30
|
+
this.indentationLevel++;
|
|
31
|
+
containerCode += this.visit(ast, {
|
|
32
|
+
isMainContext: true,
|
|
33
|
+
isInsideFunction: true,
|
|
34
|
+
isFunctionBody: true,
|
|
35
|
+
});
|
|
36
|
+
this.indentationLevel--;
|
|
37
|
+
containerCode += " return jspp::AnyValue::make_undefined();\n";
|
|
38
|
+
containerCode += "}\n\n";
|
|
39
|
+
let mainCode = "int main() {\n";
|
|
40
|
+
mainCode += ` std::ios::sync_with_stdio(false);\n`;
|
|
41
|
+
mainCode += ` std::cin.tie(nullptr);\n`;
|
|
42
|
+
mainCode += ` try {\n`;
|
|
43
|
+
mainCode += ` ${CONTAINER_FUNCTION_NAME}();\n`;
|
|
44
|
+
mainCode += ` } catch (const std::exception& ex) {\n`;
|
|
45
|
+
mainCode +=
|
|
46
|
+
" auto error = std::make_shared<jspp::AnyValue>(jspp::RuntimeError::error_to_value(ex));\n{\n";
|
|
47
|
+
mainCode +=
|
|
48
|
+
` console.get_own_property("error").as_function("console.error")->call({*error});\n`;
|
|
49
|
+
mainCode += ` return 1;\n}\n`;
|
|
50
|
+
mainCode += ` }\n`;
|
|
51
|
+
mainCode += " return 0;\n}";
|
|
52
|
+
return declarations + containerCode + mainCode;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { CodeGenerator } from "./";
|
|
3
|
+
export function visitIdentifier(node) {
|
|
4
|
+
if (node.text === "NaN") {
|
|
5
|
+
return "jspp::AnyValue::make_nan()";
|
|
6
|
+
}
|
|
7
|
+
if (node.text === "undefined") {
|
|
8
|
+
return "jspp::AnyValue::make_undefined()";
|
|
9
|
+
}
|
|
10
|
+
return node.text;
|
|
11
|
+
}
|
|
12
|
+
export function visitNumericLiteral(node) {
|
|
13
|
+
return `jspp::AnyValue::make_number(${this.escapeString(node.text)})`;
|
|
14
|
+
}
|
|
15
|
+
export function visitStringLiteral(node) {
|
|
16
|
+
return `jspp::AnyValue::make_string("${this.escapeString(node.text)}")`;
|
|
17
|
+
}
|
|
18
|
+
export function visitNoSubstitutionTemplateLiteral(node) {
|
|
19
|
+
return `jspp::AnyValue::make_string("${this.escapeString(node.text)}")`;
|
|
20
|
+
}
|
|
21
|
+
export function visitTrueKeyword() {
|
|
22
|
+
return "jspp::AnyValue::make_boolean(true)";
|
|
23
|
+
}
|
|
24
|
+
export function visitFalseKeyword() {
|
|
25
|
+
return "jspp::AnyValue::make_boolean(false)";
|
|
26
|
+
}
|
|
27
|
+
export function visitUndefinedKeyword() {
|
|
28
|
+
return "jspp::AnyValue::make_undefined()";
|
|
29
|
+
}
|
|
30
|
+
export function visitNullKeyword() {
|
|
31
|
+
return "jspp::AnyValue::make_null()";
|
|
32
|
+
}
|