@ugo-studio/jspp 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/analysis/typeAnalyzer.js +42 -27
  2. package/dist/core/codegen/class-handlers.js +2 -2
  3. package/dist/core/codegen/control-flow-handlers.js +4 -4
  4. package/dist/core/codegen/declaration-handlers.js +21 -3
  5. package/dist/core/codegen/destructuring-handlers.js +187 -0
  6. package/dist/core/codegen/expression-handlers.js +7 -0
  7. package/dist/core/codegen/function-handlers.js +58 -36
  8. package/dist/core/codegen/helpers.js +288 -52
  9. package/dist/core/codegen/index.js +7 -4
  10. package/dist/core/codegen/statement-handlers.js +42 -22
  11. package/package.json +1 -1
  12. package/scripts/precompile-headers.ts +13 -5
  13. package/src/prelude/any_value.hpp +26 -25
  14. package/src/prelude/any_value_access.hpp +7 -7
  15. package/src/prelude/any_value_defines.hpp +17 -17
  16. package/src/prelude/any_value_helpers.hpp +16 -7
  17. package/src/prelude/library/array.hpp +7 -7
  18. package/src/prelude/library/console.hpp +5 -5
  19. package/src/prelude/library/error.hpp +3 -3
  20. package/src/prelude/library/function.hpp +1 -1
  21. package/src/prelude/library/math.hpp +38 -38
  22. package/src/prelude/library/object.hpp +16 -16
  23. package/src/prelude/library/performance.hpp +1 -1
  24. package/src/prelude/library/process.hpp +1 -1
  25. package/src/prelude/library/promise.hpp +8 -8
  26. package/src/prelude/library/symbol.hpp +3 -3
  27. package/src/prelude/library/timer.hpp +4 -4
  28. package/src/prelude/types.hpp +4 -4
  29. package/src/prelude/utils/access.hpp +24 -6
  30. package/src/prelude/utils/operators.hpp +7 -0
  31. package/src/prelude/values/async_iterator.hpp +5 -3
  32. package/src/prelude/values/function.hpp +3 -3
  33. package/src/prelude/values/helpers/async_iterator.hpp +7 -3
  34. package/src/prelude/values/helpers/function.hpp +10 -10
  35. package/src/prelude/values/helpers/iterator.hpp +39 -2
  36. package/src/prelude/values/helpers/promise.hpp +9 -9
  37. package/src/prelude/values/helpers/string.hpp +17 -3
  38. package/src/prelude/values/iterator.hpp +32 -5
  39. package/src/prelude/values/promise.hpp +7 -7
  40. package/src/prelude/values/prototypes/array.hpp +41 -41
  41. package/src/prelude/values/prototypes/iterator.hpp +128 -1
@@ -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
- symbols.add(child.name.getText());
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
- symbols.add(child.name.getText());
54
+ collectNames(child.name);
31
55
  }
32
56
  else if (ts.isCatchClause(child) && child.variableDeclaration) {
33
57
  // Handles catch clause variable
34
- symbols.add(child.variableDeclaration.name.getText());
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 name = decl.name?.getText();
133
- if (!name) {
134
- return `/* Unknown declaration name: ${ts.SyntaxKind[decl.kind]} */`;
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
- if (hoistedSymbols.has(name)) {
150
- const existingSymbol = hoistedSymbols.get(name);
151
- // Don't allow multiple declaration of `let`,`const`,`function`, `class` or `enum` variables
152
- if (existingSymbol?.type === DeclarationType.let ||
153
- existingSymbol?.type === DeclarationType.const ||
154
- existingSymbol?.type === DeclarationType.function ||
155
- existingSymbol?.type === DeclarationType.class ||
156
- existingSymbol?.type === DeclarationType.enum ||
157
- existingSymbol?.type !== declType) {
158
- throw new CompilerError(`Identifier '${name}' has already been declared.`, decl, "SyntaxError");
159
- }
160
- // `var` variables can be declared multiple times
161
- return "";
162
- }
163
- else {
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
- hoistedSymbols.add(name, { type: declType });
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
- const scope = this.getScopeForNode(decl);
184
- const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
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
- return `${this.indent()}jspp::AnyValue ${name} = ${initializer};\n`;
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
- export function isFunctionUsedAsValue(decl, root) {
296
- const name = decl.name?.getText();
297
- if (!name)
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
- export function isFunctionUsedBeforeDeclaration(name, root) {
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 function declaration position
566
+ // Helper to find the declaration position
343
567
  function findDecl(node) {
344
568
  if (foundDecl)
345
569
  return;
346
- if (ts.isFunctionDeclaration(node) && node.name?.text === name) {
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, isFunctionUsedAsValue, isFunctionUsedBeforeDeclaration, isGeneratorFunction, markSymbolAsInitialized, prepareScopeSymbolsForVisit, validateFunctionParams, } from "./helpers.js";
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
- isFunctionUsedAsValue = isFunctionUsedAsValue;
31
- isFunctionUsedBeforeDeclaration = isFunctionUsedBeforeDeclaration;
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, [](const jspp::AnyValue& err) {\n`;
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
- const nativeLambda = this.generateNativeLambda(lambdaComps);
74
- code += `${this.indent()}auto ${nativeName} = ${nativeLambda};\n`;
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.isFunctionUsedAsValue(stmt, node) ||
77
- this.isFunctionUsedBeforeDeclaration(funcName, node)) {
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
- const nativeLambda = this.generateNativeLambda(lambdaComps);
169
- code += `${this.indent()}auto ${nativeName} = ${nativeLambda};\n`;
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.isFunctionUsedAsValue(stmt, node) ||
172
- this.isFunctionUsedBeforeDeclaration(funcName, node)) {
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
  }
@@ -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
- const varName = tryStmt.catchClause.variableDeclaration.name
440
- .getText();
441
- code +=
442
- `${this.indent()}jspp::AnyValue ${varName} = ${caughtValVar};\n`;
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
- const varName = tryStmt.catchClause.variableDeclaration.name
523
- .getText();
524
- code +=
525
- `${this.indent()}jspp::AnyValue ${varName} = ${caughtValVar};\n`;
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
- // The JS exception variable is always local to the catch block
642
- code +=
643
- `${this.indent()}jspp::AnyValue ${varName} = jspp::Exception::exception_to_any_value(${exceptionName});\n`;
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::get_async_object_value_iterator(${iterableRef}, ${varName});\n`;
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::get_object_value_iterator(${iterableRef}, ${varName});\n`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ugo-studio/jspp",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "A modern transpiler that converts JavaScript code into high-performance, standard C++23.",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.ts",