@ugo-studio/jspp 0.2.8 → 0.3.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/dist/analysis/typeAnalyzer.js +42 -27
- package/dist/core/codegen/class-handlers.js +6 -6
- package/dist/core/codegen/control-flow-handlers.js +4 -4
- package/dist/core/codegen/declaration-handlers.js +21 -3
- package/dist/core/codegen/destructuring-handlers.js +187 -0
- package/dist/core/codegen/expression-handlers.js +7 -0
- package/dist/core/codegen/function-handlers.js +58 -36
- package/dist/core/codegen/helpers.js +288 -52
- package/dist/core/codegen/index.js +7 -4
- package/dist/core/codegen/statement-handlers.js +43 -23
- package/package.json +1 -1
- package/scripts/precompile-headers.ts +13 -5
- package/src/prelude/any_value.hpp +362 -361
- package/src/prelude/any_value_access.hpp +170 -170
- package/src/prelude/any_value_defines.hpp +189 -189
- package/src/prelude/any_value_helpers.hpp +374 -365
- package/src/prelude/library/array.hpp +185 -185
- package/src/prelude/library/console.hpp +111 -111
- package/src/prelude/library/error.hpp +112 -112
- package/src/prelude/library/function.hpp +10 -10
- package/src/prelude/library/math.hpp +307 -307
- package/src/prelude/library/object.hpp +275 -275
- package/src/prelude/library/performance.hpp +1 -1
- package/src/prelude/library/process.hpp +39 -39
- package/src/prelude/library/promise.hpp +123 -123
- package/src/prelude/library/symbol.hpp +52 -52
- package/src/prelude/library/timer.hpp +91 -91
- package/src/prelude/types.hpp +178 -178
- package/src/prelude/utils/access.hpp +411 -393
- package/src/prelude/utils/operators.hpp +336 -329
- package/src/prelude/values/array.hpp +0 -1
- package/src/prelude/values/async_iterator.hpp +83 -81
- package/src/prelude/values/function.hpp +82 -82
- package/src/prelude/values/helpers/array.hpp +198 -208
- package/src/prelude/values/helpers/async_iterator.hpp +275 -271
- package/src/prelude/values/helpers/function.hpp +108 -108
- package/src/prelude/values/helpers/iterator.hpp +144 -107
- package/src/prelude/values/helpers/promise.hpp +253 -253
- package/src/prelude/values/helpers/string.hpp +37 -47
- package/src/prelude/values/iterator.hpp +32 -5
- package/src/prelude/values/promise.hpp +72 -72
- package/src/prelude/values/prototypes/array.hpp +54 -42
- package/src/prelude/values/prototypes/iterator.hpp +201 -74
- package/src/prelude/values/prototypes/promise.hpp +196 -196
- package/src/prelude/values/prototypes/string.hpp +564 -542
- package/src/prelude/values/string.hpp +25 -26
|
@@ -3,15 +3,39 @@ import { BUILTIN_OBJECTS, Scope } from "../../analysis/scope.js";
|
|
|
3
3
|
import { DeclarationType, DeclaredSymbols } from "../../ast/symbols.js";
|
|
4
4
|
import { CompilerError } from "../error.js";
|
|
5
5
|
import { CodeGenerator } from "./index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Checks if an identifier refers to a built-in JavaScript object (e.g., console, Math).
|
|
8
|
+
*
|
|
9
|
+
* @param node The identifier node to check.
|
|
10
|
+
* @returns True if it's a built-in object.
|
|
11
|
+
*/
|
|
6
12
|
export function isBuiltinObject(node) {
|
|
7
13
|
return BUILTIN_OBJECTS.values().some((obj) => obj.name === node.text);
|
|
8
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Collects all symbols declared within a node's subtree.
|
|
17
|
+
*
|
|
18
|
+
* @param node The node to scan for declarations.
|
|
19
|
+
* @returns A set of declared symbol names.
|
|
20
|
+
*/
|
|
9
21
|
export function getDeclaredSymbols(node) {
|
|
10
22
|
const symbols = new Set();
|
|
23
|
+
const collectNames = (name) => {
|
|
24
|
+
if (ts.isIdentifier(name)) {
|
|
25
|
+
symbols.add(name.text);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
name.elements.forEach((element) => {
|
|
29
|
+
if (ts.isBindingElement(element)) {
|
|
30
|
+
collectNames(element.name);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
11
35
|
const visitor = (child) => {
|
|
12
36
|
if (ts.isVariableDeclaration(child)) {
|
|
13
37
|
// Handles let, const, var
|
|
14
|
-
|
|
38
|
+
collectNames(child.name);
|
|
15
39
|
}
|
|
16
40
|
else if (ts.isFunctionDeclaration(child) && child.name) {
|
|
17
41
|
// Handles function declarations
|
|
@@ -27,17 +51,24 @@ export function getDeclaredSymbols(node) {
|
|
|
27
51
|
}
|
|
28
52
|
else if (ts.isParameter(child)) {
|
|
29
53
|
// Handles function parameters
|
|
30
|
-
|
|
54
|
+
collectNames(child.name);
|
|
31
55
|
}
|
|
32
56
|
else if (ts.isCatchClause(child) && child.variableDeclaration) {
|
|
33
57
|
// Handles catch clause variable
|
|
34
|
-
|
|
58
|
+
collectNames(child.variableDeclaration.name);
|
|
35
59
|
}
|
|
36
60
|
ts.forEachChild(child, visitor);
|
|
37
61
|
};
|
|
38
62
|
visitor(node);
|
|
39
63
|
return symbols;
|
|
40
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Generates a unique name by appending a counter to a prefix, avoiding existing symbols.
|
|
67
|
+
*
|
|
68
|
+
* @param prefix The base name for the unique identifier.
|
|
69
|
+
* @param namesToAvoid Sets or maps of names that should not be used.
|
|
70
|
+
* @returns A unique identifier string.
|
|
71
|
+
*/
|
|
41
72
|
export function generateUniqueName(prefix, ...namesToAvoid) {
|
|
42
73
|
let name = `${prefix}${this.uniqueNameCounter}`;
|
|
43
74
|
while (namesToAvoid.some((names) => names.has(name))) {
|
|
@@ -47,6 +78,13 @@ export function generateUniqueName(prefix, ...namesToAvoid) {
|
|
|
47
78
|
this.uniqueNameCounter++;
|
|
48
79
|
return name;
|
|
49
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Generates a unique name for a caught exception variable.
|
|
83
|
+
*
|
|
84
|
+
* @param exceptionNameToAvoid The name of the exception variable to potentially avoid shadowing.
|
|
85
|
+
* @param otherNamesToAvoid Additional names to avoid.
|
|
86
|
+
* @returns A unique identifier string for the exception.
|
|
87
|
+
*/
|
|
50
88
|
export function generateUniqueExceptionName(exceptionNameToAvoid, ...otherNamesToAvoid) {
|
|
51
89
|
let prefix = `__caught_exception_`;
|
|
52
90
|
if (exceptionNameToAvoid) {
|
|
@@ -54,6 +92,15 @@ export function generateUniqueExceptionName(exceptionNameToAvoid, ...otherNamesT
|
|
|
54
92
|
}
|
|
55
93
|
return this.generateUniqueName(prefix, ...otherNamesToAvoid);
|
|
56
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Retrieves the scope associated with a given node.
|
|
97
|
+
*
|
|
98
|
+
* Walks up the parent chain until a node with an associated scope is found.
|
|
99
|
+
*
|
|
100
|
+
* @param node The node to find the scope for.
|
|
101
|
+
* @returns The associated Scope.
|
|
102
|
+
* @throws CompilerError if no scope is found.
|
|
103
|
+
*/
|
|
57
104
|
export function getScopeForNode(node) {
|
|
58
105
|
let current = node;
|
|
59
106
|
while (current) {
|
|
@@ -69,9 +116,20 @@ export function getScopeForNode(node) {
|
|
|
69
116
|
}
|
|
70
117
|
return rootScope;
|
|
71
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Returns a string of spaces representing the current indentation level.
|
|
121
|
+
*
|
|
122
|
+
* @returns An indentation string.
|
|
123
|
+
*/
|
|
72
124
|
export function indent() {
|
|
73
125
|
return " ".repeat(this.indentationLevel);
|
|
74
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Escapes special characters in a string for safe use in C++ string literals.
|
|
129
|
+
*
|
|
130
|
+
* @param str The string to escape.
|
|
131
|
+
* @returns The escaped string.
|
|
132
|
+
*/
|
|
75
133
|
export function escapeString(str) {
|
|
76
134
|
return str
|
|
77
135
|
.replace(/\\/g, "\\\\")
|
|
@@ -81,9 +139,27 @@ export function escapeString(str) {
|
|
|
81
139
|
.replace(/\t/g, "\\t")
|
|
82
140
|
.replace(/\?/g, "\\?");
|
|
83
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Formats a JavaScript variable name as a C++ string literal for error reporting or property access.
|
|
144
|
+
*
|
|
145
|
+
* @param node The identifier node.
|
|
146
|
+
* @returns The variable name wrapped in quotes.
|
|
147
|
+
*/
|
|
84
148
|
export function getJsVarName(node) {
|
|
85
149
|
return `"${node.text}"`;
|
|
86
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Generates C++ code to dereference a variable, handling TDZ and heap allocation.
|
|
153
|
+
*
|
|
154
|
+
* If the symbol is not yet initialized, it generates a call to a deref helper
|
|
155
|
+
* that performs a runtime check.
|
|
156
|
+
*
|
|
157
|
+
* @param nodeText The C++ expression for the variable.
|
|
158
|
+
* @param varName The JavaScript name of the variable.
|
|
159
|
+
* @param context The current visit context.
|
|
160
|
+
* @param typeInfo Type information for the variable.
|
|
161
|
+
* @returns The C++ code for accessing the variable's value.
|
|
162
|
+
*/
|
|
87
163
|
export function getDerefCode(nodeText, varName, context, typeInfo) {
|
|
88
164
|
// Make sure varName is incased in quotes
|
|
89
165
|
if (!varName.startsWith('"'))
|
|
@@ -111,6 +187,13 @@ export function getDerefCode(nodeText, varName, context, typeInfo) {
|
|
|
111
187
|
return `jspp::Access::deref_stack(${nodeText}, ${varName})`;
|
|
112
188
|
}
|
|
113
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Marks a symbol as initialized in the provided symbol tables.
|
|
192
|
+
*
|
|
193
|
+
* @param name The name of the symbol.
|
|
194
|
+
* @param topLevel The global symbol table.
|
|
195
|
+
* @param local The local symbol table.
|
|
196
|
+
*/
|
|
114
197
|
export function markSymbolAsInitialized(name, topLevel, local) {
|
|
115
198
|
if (topLevel.has(name)) {
|
|
116
199
|
topLevel.update(name, {
|
|
@@ -123,18 +206,34 @@ export function markSymbolAsInitialized(name, topLevel, local) {
|
|
|
123
206
|
});
|
|
124
207
|
}
|
|
125
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Determines the appropriate C++ return command (return or co_return) based on context.
|
|
211
|
+
*
|
|
212
|
+
* @param context Partial visit context.
|
|
213
|
+
* @returns "co_return" for generators/async, "return" otherwise.
|
|
214
|
+
*/
|
|
126
215
|
export function getReturnCommand(context) {
|
|
127
216
|
return (context.isInsideGeneratorFunction || context.isInsideAsyncFunction)
|
|
128
217
|
? "co_return"
|
|
129
218
|
: "return";
|
|
130
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Generates C++ code to hoist a declaration (var, let, const, function, class, enum).
|
|
222
|
+
*
|
|
223
|
+
* It registers the symbol and generates the variable declaration, initializing
|
|
224
|
+
* block-scoped variables to UNINITIALIZED for TDZ support.
|
|
225
|
+
*
|
|
226
|
+
* @param decl The declaration node.
|
|
227
|
+
* @param hoistedSymbols The symbol table to register with.
|
|
228
|
+
* @param scopeNode The node representing the current scope.
|
|
229
|
+
* @returns The C++ declaration code.
|
|
230
|
+
* @throws CompilerError if a duplicate declaration is found.
|
|
231
|
+
*/
|
|
131
232
|
export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const isLet = (decl.parent.flags & (ts.NodeFlags.Let)) !== 0;
|
|
137
|
-
const isConst = (decl.parent.flags & (ts.NodeFlags.Const)) !== 0;
|
|
233
|
+
const isLet = ts.isVariableDeclaration(decl) &&
|
|
234
|
+
(decl.parent.flags & (ts.NodeFlags.Let)) !== 0;
|
|
235
|
+
const isConst = ts.isVariableDeclaration(decl) &&
|
|
236
|
+
(decl.parent.flags & (ts.NodeFlags.Const)) !== 0;
|
|
138
237
|
const declType = isLet
|
|
139
238
|
? DeclarationType.let
|
|
140
239
|
: isConst
|
|
@@ -146,52 +245,89 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
|
|
|
146
245
|
: ts.isEnumDeclaration(decl)
|
|
147
246
|
? DeclarationType.enum
|
|
148
247
|
: DeclarationType.var;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// Add the symbol to the hoisted symbols
|
|
165
|
-
if (declType === DeclarationType.function) {
|
|
166
|
-
const isAsync = this.isAsyncFunction(decl);
|
|
167
|
-
const isGenerator = this.isGeneratorFunction(decl);
|
|
168
|
-
hoistedSymbols.add(name, {
|
|
169
|
-
type: declType,
|
|
170
|
-
features: { isAsync, isGenerator },
|
|
171
|
-
});
|
|
172
|
-
// Don't hoist functions not used as a variable
|
|
173
|
-
// they will be called with their native lambdas
|
|
174
|
-
if (!this.isFunctionUsedAsValue(decl, scopeNode) &&
|
|
175
|
-
!this.isFunctionUsedBeforeDeclaration(name, scopeNode)) {
|
|
248
|
+
const hoistName = (nameNode) => {
|
|
249
|
+
if (ts.isIdentifier(nameNode)) {
|
|
250
|
+
const name = nameNode.text;
|
|
251
|
+
if (hoistedSymbols.has(name)) {
|
|
252
|
+
const existingSymbol = hoistedSymbols.get(name);
|
|
253
|
+
// Don't allow multiple declaration of `let`,`const`,`function`, `class` or `enum` variables
|
|
254
|
+
if (existingSymbol?.type === DeclarationType.let ||
|
|
255
|
+
existingSymbol?.type === DeclarationType.const ||
|
|
256
|
+
existingSymbol?.type === DeclarationType.function ||
|
|
257
|
+
existingSymbol?.type === DeclarationType.class ||
|
|
258
|
+
existingSymbol?.type === DeclarationType.enum ||
|
|
259
|
+
existingSymbol?.type !== declType) {
|
|
260
|
+
throw new CompilerError(`Identifier '${name}' has already been declared.`, decl, "SyntaxError");
|
|
261
|
+
}
|
|
262
|
+
// `var` variables can be declared multiple times
|
|
176
263
|
return "";
|
|
177
264
|
}
|
|
265
|
+
else {
|
|
266
|
+
// Add the symbol to the hoisted symbols
|
|
267
|
+
if (ts.isFunctionDeclaration(decl) ||
|
|
268
|
+
(ts.isVariableDeclaration(decl) && decl.initializer &&
|
|
269
|
+
nameNode === decl.name &&
|
|
270
|
+
(ts.isArrowFunction(decl.initializer) ||
|
|
271
|
+
ts.isFunctionExpression(decl.initializer)))) {
|
|
272
|
+
const funcExpr = ts.isVariableDeclaration(decl)
|
|
273
|
+
? decl.initializer
|
|
274
|
+
: decl;
|
|
275
|
+
const isAsync = this.isAsyncFunction(funcExpr);
|
|
276
|
+
const isGenerator = this.isGeneratorFunction(funcExpr);
|
|
277
|
+
hoistedSymbols.add(name, {
|
|
278
|
+
type: declType,
|
|
279
|
+
features: { isAsync, isGenerator },
|
|
280
|
+
});
|
|
281
|
+
// Don't hoist declarations not used as a variable
|
|
282
|
+
// They will be called with their native lambda/value
|
|
283
|
+
if (!this.isDeclarationUsedAsValue(decl, scopeNode) &&
|
|
284
|
+
!this.isDeclarationUsedBeforeInitialization(name, scopeNode)) {
|
|
285
|
+
return "";
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
hoistedSymbols.add(name, { type: declType });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const scope = this.getScopeForNode(decl);
|
|
293
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
|
|
294
|
+
const initializer = isLet || isConst || ts.isClassDeclaration(decl)
|
|
295
|
+
? "jspp::Constants::UNINITIALIZED"
|
|
296
|
+
: "jspp::Constants::UNDEFINED";
|
|
297
|
+
if (typeInfo.needsHeapAllocation) {
|
|
298
|
+
return `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initializer});\n`;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
return `${this.indent()}jspp::AnyValue ${name} = ${initializer};\n`;
|
|
302
|
+
}
|
|
178
303
|
}
|
|
179
304
|
else {
|
|
180
|
-
|
|
305
|
+
let code = "";
|
|
306
|
+
nameNode.elements.forEach((element) => {
|
|
307
|
+
if (ts.isBindingElement(element)) {
|
|
308
|
+
code += hoistName(element.name);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
return code;
|
|
181
312
|
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const initializer = isLet || isConst || ts.isClassDeclaration(decl)
|
|
186
|
-
? "jspp::Constants::UNINITIALIZED"
|
|
187
|
-
: "jspp::Constants::UNDEFINED";
|
|
188
|
-
if (typeInfo.needsHeapAllocation) {
|
|
189
|
-
return `${this.indent()}auto ${name} = std::make_shared<jspp::AnyValue>(${initializer});\n`;
|
|
313
|
+
};
|
|
314
|
+
if (ts.isVariableDeclaration(decl)) {
|
|
315
|
+
return hoistName(decl.name);
|
|
190
316
|
}
|
|
191
317
|
else {
|
|
192
|
-
|
|
318
|
+
const nameNode = decl.name;
|
|
319
|
+
if (!nameNode) {
|
|
320
|
+
return `/* Unknown declaration name: ${ts.SyntaxKind[decl.kind]} */`;
|
|
321
|
+
}
|
|
322
|
+
return hoistName(nameNode);
|
|
193
323
|
}
|
|
194
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Checks if a function node is a generator function.
|
|
327
|
+
*
|
|
328
|
+
* @param node The node to check.
|
|
329
|
+
* @returns True if it's a generator.
|
|
330
|
+
*/
|
|
195
331
|
export function isGeneratorFunction(node) {
|
|
196
332
|
return ((ts.isFunctionDeclaration(node) ||
|
|
197
333
|
ts.isFunctionExpression(node) ||
|
|
@@ -199,6 +335,12 @@ export function isGeneratorFunction(node) {
|
|
|
199
335
|
!!node.asteriskToken // generator indicator
|
|
200
336
|
);
|
|
201
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Checks if a function node is an async function.
|
|
340
|
+
*
|
|
341
|
+
* @param node The node to check.
|
|
342
|
+
* @returns True if it's async.
|
|
343
|
+
*/
|
|
202
344
|
export function isAsyncFunction(node) {
|
|
203
345
|
return ((ts.isFunctionDeclaration(node) ||
|
|
204
346
|
ts.isFunctionExpression(node) ||
|
|
@@ -207,10 +349,23 @@ export function isAsyncFunction(node) {
|
|
|
207
349
|
(ts.getCombinedModifierFlags(node) &
|
|
208
350
|
ts.ModifierFlags.Async) !== 0);
|
|
209
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Combines top-level and local symbol tables for a nested visit.
|
|
354
|
+
*
|
|
355
|
+
* @param topLevel Global/outer symbol table.
|
|
356
|
+
* @param local Local/inner symbol table.
|
|
357
|
+
* @returns A new DeclaredSymbols instance representing the merged scope.
|
|
358
|
+
*/
|
|
210
359
|
export function prepareScopeSymbolsForVisit(topLevel, local) {
|
|
211
360
|
// Join the top and local scopes
|
|
212
361
|
return new DeclaredSymbols(topLevel, local);
|
|
213
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Determines if a statement should be ignored (e.g., ambient declarations).
|
|
365
|
+
*
|
|
366
|
+
* @param stmt The statement to check.
|
|
367
|
+
* @returns True if the statement should be ignored.
|
|
368
|
+
*/
|
|
214
369
|
export function shouldIgnoreStatement(stmt) {
|
|
215
370
|
// Ignore variable statements with 'declare' modifier
|
|
216
371
|
if (ts.isVariableStatement(stmt)) {
|
|
@@ -221,6 +376,13 @@ export function shouldIgnoreStatement(stmt) {
|
|
|
221
376
|
}
|
|
222
377
|
return false;
|
|
223
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Collects all function-scoped (var) declarations within a node,
|
|
381
|
+
* respecting function boundaries.
|
|
382
|
+
*
|
|
383
|
+
* @param node The root node to scan.
|
|
384
|
+
* @returns An array of variable declarations.
|
|
385
|
+
*/
|
|
224
386
|
export function collectFunctionScopedDeclarations(node) {
|
|
225
387
|
const decls = [];
|
|
226
388
|
function visit(n) {
|
|
@@ -275,6 +437,12 @@ export function collectFunctionScopedDeclarations(node) {
|
|
|
275
437
|
ts.forEachChild(node, visit);
|
|
276
438
|
return decls;
|
|
277
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Collects block-scoped (let/const) declarations directly within a list of statements.
|
|
442
|
+
*
|
|
443
|
+
* @param statements The statements to scan.
|
|
444
|
+
* @returns An array of variable declarations.
|
|
445
|
+
*/
|
|
278
446
|
export function collectBlockScopedDeclarations(statements) {
|
|
279
447
|
const decls = [];
|
|
280
448
|
for (const stmt of statements) {
|
|
@@ -292,10 +460,22 @@ export function collectBlockScopedDeclarations(statements) {
|
|
|
292
460
|
}
|
|
293
461
|
return decls;
|
|
294
462
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
463
|
+
/**
|
|
464
|
+
* Checks if a declaration is used as a value within a given node.
|
|
465
|
+
*
|
|
466
|
+
* This is used to determine if a hoisted function declaration needs to be
|
|
467
|
+
* wrapped in an AnyValue and assigned to a variable, or if it can be
|
|
468
|
+
* optimized to only use its native lambda.
|
|
469
|
+
*
|
|
470
|
+
* @param decl The declaration to check.
|
|
471
|
+
* @param root The root node to search for usages within.
|
|
472
|
+
* @returns True if the declaration is used as a value.
|
|
473
|
+
*/
|
|
474
|
+
export function isDeclarationUsedAsValue(decl, root) {
|
|
475
|
+
const nameNode = decl.name;
|
|
476
|
+
if (!nameNode || !ts.isIdentifier(nameNode))
|
|
298
477
|
return false;
|
|
478
|
+
const name = nameNode.text;
|
|
299
479
|
let isUsed = false;
|
|
300
480
|
const visitor = (node) => {
|
|
301
481
|
if (isUsed)
|
|
@@ -336,14 +516,62 @@ export function isFunctionUsedAsValue(decl, root) {
|
|
|
336
516
|
ts.forEachChild(root, visitor);
|
|
337
517
|
return isUsed;
|
|
338
518
|
}
|
|
339
|
-
|
|
519
|
+
/**
|
|
520
|
+
* Checks if a declaration is called as a function (e.g. decl()) within a given node.
|
|
521
|
+
*
|
|
522
|
+
* @param decl The declaration to check.
|
|
523
|
+
* @param root The root node to search for usages within.
|
|
524
|
+
* @returns True if the declaration is called as a function.
|
|
525
|
+
*/
|
|
526
|
+
export function isDeclarationCalledAsFunction(decl, root) {
|
|
527
|
+
const nameNode = decl.name;
|
|
528
|
+
if (!nameNode || !ts.isIdentifier(nameNode))
|
|
529
|
+
return false;
|
|
530
|
+
const name = nameNode.text;
|
|
531
|
+
let isCalled = false;
|
|
532
|
+
const visitor = (node) => {
|
|
533
|
+
if (isCalled)
|
|
534
|
+
return;
|
|
535
|
+
if (ts.isIdentifier(node) && node.text === name) {
|
|
536
|
+
const scope = this.getScopeForNode(node);
|
|
537
|
+
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(node.text, scope);
|
|
538
|
+
if (typeInfo?.declaration === decl) {
|
|
539
|
+
const parent = node.parent;
|
|
540
|
+
if (ts.isCallExpression(parent) && parent.expression === node) {
|
|
541
|
+
isCalled = true;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (!isCalled) {
|
|
546
|
+
ts.forEachChild(node, visitor);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
ts.forEachChild(root, visitor);
|
|
550
|
+
return isCalled;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Checks if a declaration is used before it is initialized within a given node.
|
|
554
|
+
*
|
|
555
|
+
* This helps determine if a hoisted declaration (like a function or class)
|
|
556
|
+
* might be accessed before its assignment logic is executed, necessitating
|
|
557
|
+
* an AnyValue wrapper for TDZ or forward-reference support.
|
|
558
|
+
*
|
|
559
|
+
* @param name The name of the declaration to check.
|
|
560
|
+
* @param root The root node to search for usages within.
|
|
561
|
+
* @returns True if the declaration is used before initialization.
|
|
562
|
+
*/
|
|
563
|
+
export function isDeclarationUsedBeforeInitialization(name, root) {
|
|
340
564
|
let declPos = -1;
|
|
341
565
|
let foundDecl = false;
|
|
342
|
-
// Helper to find the
|
|
566
|
+
// Helper to find the declaration position
|
|
343
567
|
function findDecl(node) {
|
|
344
568
|
if (foundDecl)
|
|
345
569
|
return;
|
|
346
|
-
if (ts.isFunctionDeclaration(node)
|
|
570
|
+
if ((ts.isFunctionDeclaration(node) ||
|
|
571
|
+
ts.isClassDeclaration(node) ||
|
|
572
|
+
ts.isVariableDeclaration(node) ||
|
|
573
|
+
ts.isEnumDeclaration(node)) &&
|
|
574
|
+
node.name && ts.isIdentifier(node.name) && node.name.text === name) {
|
|
347
575
|
declPos = node.getStart();
|
|
348
576
|
foundDecl = true;
|
|
349
577
|
}
|
|
@@ -410,6 +638,14 @@ export function isFunctionUsedBeforeDeclaration(name, root) {
|
|
|
410
638
|
visit(root);
|
|
411
639
|
return isUsedBefore;
|
|
412
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* Validates and filters function parameters, checking for illegal "this"
|
|
643
|
+
* and correctly positioned rest parameters.
|
|
644
|
+
*
|
|
645
|
+
* @param parameters The parameters to validate.
|
|
646
|
+
* @returns A filtered array of valid parameters.
|
|
647
|
+
* @throws CompilerError for invalid parameter usage.
|
|
648
|
+
*/
|
|
413
649
|
export function validateFunctionParams(parameters) {
|
|
414
650
|
return parameters.filter((p) => {
|
|
415
651
|
if (p.name.getText() === "this") {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DeclaredSymbols } from "../../ast/symbols.js";
|
|
2
2
|
import { generateLambdaComponents, generateNativeLambda, generateWrappedLambda, } from "./function-handlers.js";
|
|
3
|
-
import { escapeString, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject,
|
|
3
|
+
import { escapeString, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isDeclarationCalledAsFunction, isDeclarationUsedAsValue, isDeclarationUsedBeforeInitialization, isGeneratorFunction, markSymbolAsInitialized, prepareScopeSymbolsForVisit, validateFunctionParams, } from "./helpers.js";
|
|
4
|
+
import { generateDestructuring } from "./destructuring-handlers.js";
|
|
4
5
|
import { visit } from "./visitor.js";
|
|
5
6
|
const MODULE_NAME = "__entry_point__";
|
|
6
7
|
export class CodeGenerator {
|
|
@@ -27,9 +28,11 @@ export class CodeGenerator {
|
|
|
27
28
|
isAsyncFunction = isAsyncFunction;
|
|
28
29
|
prepareScopeSymbolsForVisit = prepareScopeSymbolsForVisit;
|
|
29
30
|
markSymbolAsInitialized = markSymbolAsInitialized;
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
isDeclarationCalledAsFunction = isDeclarationCalledAsFunction;
|
|
32
|
+
isDeclarationUsedAsValue = isDeclarationUsedAsValue;
|
|
33
|
+
isDeclarationUsedBeforeInitialization = isDeclarationUsedBeforeInitialization;
|
|
32
34
|
validateFunctionParams = validateFunctionParams;
|
|
35
|
+
generateDestructuring = generateDestructuring;
|
|
33
36
|
// function handlers
|
|
34
37
|
generateLambdaComponents = generateLambdaComponents;
|
|
35
38
|
generateNativeLambda = generateNativeLambda;
|
|
@@ -62,7 +65,7 @@ export class CodeGenerator {
|
|
|
62
65
|
mainCode += ` try {\n`;
|
|
63
66
|
mainCode += ` jspp::setup_process_argv(argc, argv);\n`;
|
|
64
67
|
mainCode += ` auto p = ${MODULE_NAME}();\n`;
|
|
65
|
-
mainCode += ` p.then(nullptr, [](
|
|
68
|
+
mainCode += ` p.then(nullptr, [](jspp::AnyValue err) {\n`;
|
|
66
69
|
mainCode +=
|
|
67
70
|
` auto error = std::make_shared<jspp::AnyValue>(err);\n`;
|
|
68
71
|
mainCode +=
|
|
@@ -70,11 +70,13 @@ export function visitSourceFile(node, context) {
|
|
|
70
70
|
noTypeSignature: true,
|
|
71
71
|
});
|
|
72
72
|
// Generate native lambda
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if (this.isDeclarationCalledAsFunction(stmt, node)) {
|
|
74
|
+
const nativeLambda = this.generateNativeLambda(lambdaComps);
|
|
75
|
+
code += `${this.indent()}auto ${nativeName} = ${nativeLambda};\n`;
|
|
76
|
+
}
|
|
75
77
|
// Generate AnyValue wrapped lamda
|
|
76
|
-
if (this.
|
|
77
|
-
this.
|
|
78
|
+
if (this.isDeclarationUsedAsValue(stmt, node) ||
|
|
79
|
+
this.isDeclarationUsedBeforeInitialization(funcName, node)) {
|
|
78
80
|
const wrappedLambda = this.generateWrappedLambda(lambdaComps);
|
|
79
81
|
code += `${this.indent()}*${funcName} = ${wrappedLambda};\n`;
|
|
80
82
|
}
|
|
@@ -165,11 +167,13 @@ export function visitBlock(node, context) {
|
|
|
165
167
|
noTypeSignature: true,
|
|
166
168
|
});
|
|
167
169
|
// Generate native lambda
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
if (this.isDeclarationCalledAsFunction(stmt, node)) {
|
|
171
|
+
const nativeLambda = this.generateNativeLambda(lambdaComps);
|
|
172
|
+
code += `${this.indent()}auto ${nativeName} = ${nativeLambda};\n`;
|
|
173
|
+
}
|
|
170
174
|
// Generate AnyValue wrapped lamda
|
|
171
|
-
if (this.
|
|
172
|
-
this.
|
|
175
|
+
if (this.isDeclarationUsedAsValue(stmt, node) ||
|
|
176
|
+
this.isDeclarationUsedBeforeInitialization(funcName, node)) {
|
|
173
177
|
const wrappedLambda = this.generateWrappedLambda(lambdaComps);
|
|
174
178
|
code += `${this.indent()}*${funcName} = ${wrappedLambda};\n`;
|
|
175
179
|
}
|
|
@@ -362,7 +366,7 @@ export function visitExpressionStatement(node, context) {
|
|
|
362
366
|
export function visitThrowStatement(node, context) {
|
|
363
367
|
const throwStmt = node;
|
|
364
368
|
const expr = this.visit(throwStmt.expression, context);
|
|
365
|
-
return `${this.indent()}throw jspp::Exception(${expr});
|
|
369
|
+
return `${this.indent()}throw jspp::Exception(${expr});
|
|
366
370
|
`;
|
|
367
371
|
}
|
|
368
372
|
export function visitTryStatement(node, context) {
|
|
@@ -436,10 +440,16 @@ export function visitTryStatement(node, context) {
|
|
|
436
440
|
code += `${this.indent()}{\n`; // Block scope
|
|
437
441
|
this.indentationLevel++;
|
|
438
442
|
if (tryStmt.catchClause.variableDeclaration) {
|
|
439
|
-
|
|
440
|
-
.
|
|
441
|
-
|
|
442
|
-
|
|
443
|
+
if (ts.isIdentifier(tryStmt.catchClause.variableDeclaration.name)) {
|
|
444
|
+
const varName = tryStmt.catchClause.variableDeclaration
|
|
445
|
+
.name
|
|
446
|
+
.getText();
|
|
447
|
+
code +=
|
|
448
|
+
`${this.indent()}jspp::AnyValue ${varName} = ${caughtValVar};\n`;
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
code += this.generateDestructuring(tryStmt.catchClause.variableDeclaration.name, caughtValVar, context) + ";\n";
|
|
452
|
+
}
|
|
443
453
|
}
|
|
444
454
|
const catchContext = { ...innerContext, exceptionName };
|
|
445
455
|
code += this.visit(tryStmt.catchClause.block, catchContext);
|
|
@@ -519,10 +529,16 @@ export function visitTryStatement(node, context) {
|
|
|
519
529
|
code += `${this.indent()}{\n`; // Block scope
|
|
520
530
|
this.indentationLevel++;
|
|
521
531
|
if (tryStmt.catchClause.variableDeclaration) {
|
|
522
|
-
|
|
523
|
-
.
|
|
524
|
-
|
|
525
|
-
|
|
532
|
+
if (ts.isIdentifier(tryStmt.catchClause.variableDeclaration.name)) {
|
|
533
|
+
const varName = tryStmt.catchClause.variableDeclaration
|
|
534
|
+
.name
|
|
535
|
+
.getText();
|
|
536
|
+
code +=
|
|
537
|
+
`${this.indent()}jspp::AnyValue ${varName} = ${caughtValVar};\n`;
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
code += this.generateDestructuring(tryStmt.catchClause.variableDeclaration.name, caughtValVar, context) + ";\n";
|
|
541
|
+
}
|
|
526
542
|
}
|
|
527
543
|
code += this.visit(tryStmt.catchClause.block, newContext);
|
|
528
544
|
this.indentationLevel--;
|
|
@@ -635,12 +651,16 @@ export function visitCatchClause(node, context) {
|
|
|
635
651
|
throw new CompilerError("exceptionName not found in context for CatchClause", node, "CompilerBug");
|
|
636
652
|
}
|
|
637
653
|
if (catchClause.variableDeclaration) {
|
|
638
|
-
const varName = catchClause.variableDeclaration.name.getText();
|
|
639
654
|
let code = `{\n`;
|
|
640
655
|
this.indentationLevel++;
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
656
|
+
const exceptionValueCode = `jspp::Exception::exception_to_any_value(${exceptionName})`;
|
|
657
|
+
if (ts.isIdentifier(catchClause.variableDeclaration.name)) {
|
|
658
|
+
const varName = catchClause.variableDeclaration.name.text;
|
|
659
|
+
code += `${this.indent()}jspp::AnyValue ${varName} = ${exceptionValueCode};\n`;
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
code += this.generateDestructuring(catchClause.variableDeclaration.name, exceptionValueCode, context) + ";\n";
|
|
663
|
+
}
|
|
644
664
|
code += this.visit(catchClause.block, context);
|
|
645
665
|
this.indentationLevel--;
|
|
646
666
|
code += `${this.indent()}}\n`;
|
|
@@ -684,11 +704,11 @@ export function visitYieldExpression(node, context) {
|
|
|
684
704
|
code += `${this.indent()}auto ${iterableRef} = ${exprText};\n`;
|
|
685
705
|
if (context.isInsideAsyncFunction) {
|
|
686
706
|
code +=
|
|
687
|
-
`${this.indent()}auto ${iterator} = jspp::Access::
|
|
707
|
+
`${this.indent()}auto ${iterator} = jspp::Access::get_object_async_iterator(${iterableRef}, ${varName});\n`;
|
|
688
708
|
}
|
|
689
709
|
else {
|
|
690
710
|
code +=
|
|
691
|
-
`${this.indent()}auto ${iterator} = jspp::Access::
|
|
711
|
+
`${this.indent()}auto ${iterator} = jspp::Access::get_object_iterator(${iterableRef}, ${varName});\n`;
|
|
692
712
|
}
|
|
693
713
|
code +=
|
|
694
714
|
`${this.indent()}auto ${nextFunc} = ${iterator}.get_own_property("next");\n`;
|
package/package.json
CHANGED