@ugo-studio/jspp 0.2.4 → 0.2.6

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.
@@ -1,6 +1,7 @@
1
1
  import ts from "typescript";
2
2
  import { BUILTIN_OBJECTS, Scope } from "../../analysis/scope.js";
3
- import { DeclaredSymbols, DeclaredSymbolType } from "../../ast/symbols.js";
3
+ import { DeclarationType, DeclaredSymbols } from "../../ast/symbols.js";
4
+ import { CompilerError } from "../error.js";
4
5
  import { CodeGenerator } from "./index.js";
5
6
  export function isBuiltinObject(node) {
6
7
  return BUILTIN_OBJECTS.values().some((obj) => obj.name === node.text);
@@ -20,6 +21,10 @@ export function getDeclaredSymbols(node) {
20
21
  // Handles class declarations
21
22
  symbols.add(child.name.getText());
22
23
  }
24
+ else if (ts.isEnumDeclaration(child) && child.name) {
25
+ // Handles enum declarations
26
+ symbols.add(child.name.getText());
27
+ }
23
28
  else if (ts.isParameter(child)) {
24
29
  // Handles function parameters
25
30
  symbols.add(child.name.getText());
@@ -60,7 +65,7 @@ export function getScopeForNode(node) {
60
65
  }
61
66
  const rootScope = this.typeAnalyzer.scopeManager.getAllScopes()[0];
62
67
  if (!rootScope) {
63
- throw new Error("Compiler bug: Could not find a root scope.");
68
+ throw new CompilerError("Could not find a root scope.", node, "CompilerBug");
64
69
  }
65
70
  return rootScope;
66
71
  }
@@ -73,7 +78,8 @@ export function escapeString(str) {
73
78
  .replace(/"/g, '\\"')
74
79
  .replace(/\n/g, "\\n")
75
80
  .replace(/\r/g, "\\r")
76
- .replace(/\t/g, "\\t");
81
+ .replace(/\t/g, "\\t")
82
+ .replace(/\?/g, "\\?");
77
83
  }
78
84
  export function getJsVarName(node) {
79
85
  return `"${node.text}"`;
@@ -86,13 +92,13 @@ export function getDerefCode(nodeText, varName, context, typeInfo) {
86
92
  varName = varName + '"';
87
93
  const symbolName = varName.slice(1).slice(0, -1);
88
94
  const symbol = context.localScopeSymbols.get(symbolName) ??
89
- context.topLevelScopeSymbols.get(symbolName);
90
- const checkedIfUninitialized = symbol?.checkedIfUninitialized ||
95
+ context.globalScopeSymbols.get(symbolName);
96
+ const isInitialized = symbol?.checks.initialized ||
91
97
  false;
92
98
  // Mark the symbol as checked
93
- this.markSymbolAsChecked(symbolName, context.topLevelScopeSymbols, context.localScopeSymbols);
99
+ this.markSymbolAsInitialized(symbolName, context.globalScopeSymbols, context.localScopeSymbols);
94
100
  // Apply deref code
95
- if (checkedIfUninitialized) {
101
+ if (isInitialized) {
96
102
  if (typeInfo && typeInfo.needsHeapAllocation) {
97
103
  return `(*${nodeText})`;
98
104
  }
@@ -105,15 +111,15 @@ export function getDerefCode(nodeText, varName, context, typeInfo) {
105
111
  return `jspp::Access::deref_stack(${nodeText}, ${varName})`;
106
112
  }
107
113
  }
108
- export function markSymbolAsChecked(name, topLevel, local) {
114
+ export function markSymbolAsInitialized(name, topLevel, local) {
109
115
  if (topLevel.has(name)) {
110
116
  topLevel.update(name, {
111
- checkedIfUninitialized: true,
117
+ checks: { initialized: true },
112
118
  });
113
119
  }
114
120
  else if (local.has(name)) {
115
121
  local.update(name, {
116
- checkedIfUninitialized: true,
122
+ checks: { initialized: true },
117
123
  });
118
124
  }
119
125
  }
@@ -122,39 +128,61 @@ export function getReturnCommand(context) {
122
128
  ? "co_return"
123
129
  : "return";
124
130
  }
125
- export function hoistDeclaration(decl, hoistedSymbols) {
131
+ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
126
132
  const name = decl.name?.getText();
127
133
  if (!name) {
128
134
  return `/* Unknown declaration name: ${ts.SyntaxKind[decl.kind]} */`;
129
135
  }
130
- const isLetOrConst = (decl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
131
- 0;
132
- // if (name === "letVal") console.log("hoistDeclaration: letVal isLetOrConst=", isLetOrConst, " flags=", decl.parent.flags);
133
- const symbolType = isLetOrConst
134
- ? DeclaredSymbolType.letOrConst
135
- : (ts.isFunctionDeclaration(decl)
136
- ? DeclaredSymbolType.function
137
- : (ts.isClassDeclaration(decl)
138
- ? DeclaredSymbolType.letOrConst
139
- : DeclaredSymbolType.var));
136
+ const isLet = (decl.parent.flags & (ts.NodeFlags.Let)) !== 0;
137
+ const isConst = (decl.parent.flags & (ts.NodeFlags.Const)) !== 0;
138
+ const declType = isLet
139
+ ? DeclarationType.let
140
+ : isConst
141
+ ? DeclarationType.const
142
+ : ts.isFunctionDeclaration(decl)
143
+ ? DeclarationType.function
144
+ : ts.isClassDeclaration(decl)
145
+ ? DeclarationType.class
146
+ : ts.isEnumDeclaration(decl)
147
+ ? DeclarationType.enum
148
+ : DeclarationType.var;
140
149
  if (hoistedSymbols.has(name)) {
141
- const existingType = hoistedSymbols.get(name)?.type;
142
- // Don't allow multiple declaration of `letOrConst` or `function` or `class` variables
143
- if (existingType === DeclaredSymbolType.letOrConst ||
144
- existingType === DeclaredSymbolType.function ||
145
- existingType !== symbolType) {
146
- throw new SyntaxError(`Identifier '${name}' has already been declared`);
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");
147
159
  }
148
160
  // `var` variables can be declared multiple times
149
161
  return "";
150
162
  }
151
- hoistedSymbols.set(name, {
152
- type: symbolType,
153
- checkedIfUninitialized: false,
154
- });
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
+ func: { 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)) {
176
+ return "";
177
+ }
178
+ }
179
+ else {
180
+ hoistedSymbols.add(name, { type: declType });
181
+ }
182
+ }
155
183
  const scope = this.getScopeForNode(decl);
156
184
  const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
157
- const initializer = isLetOrConst || ts.isClassDeclaration(decl)
185
+ const initializer = isLet || isConst || ts.isClassDeclaration(decl)
158
186
  ? "jspp::Constants::UNINITIALIZED"
159
187
  : "jspp::Constants::UNDEFINED";
160
188
  if (typeInfo.needsHeapAllocation) {
@@ -183,10 +211,24 @@ export function prepareScopeSymbolsForVisit(topLevel, local) {
183
211
  // Join the top and local scopes
184
212
  return new DeclaredSymbols(topLevel, local);
185
213
  }
214
+ export function shouldIgnoreStatement(stmt) {
215
+ // Ignore variable statements with 'declare' modifier
216
+ if (ts.isVariableStatement(stmt)) {
217
+ if (stmt.modifiers &&
218
+ stmt.modifiers.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword)) {
219
+ return true;
220
+ }
221
+ }
222
+ return false;
223
+ }
186
224
  export function collectFunctionScopedDeclarations(node) {
187
225
  const decls = [];
188
226
  function visit(n) {
189
227
  if (ts.isVariableStatement(n)) {
228
+ // Ignore Declare modifier
229
+ if (shouldIgnoreStatement(n))
230
+ return;
231
+ // Only collect let/const declarations
190
232
  const isLetOrConst = (n.declarationList.flags &
191
233
  (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
192
234
  if (!isLetOrConst) {
@@ -237,6 +279,10 @@ export function collectBlockScopedDeclarations(statements) {
237
279
  const decls = [];
238
280
  for (const stmt of statements) {
239
281
  if (ts.isVariableStatement(stmt)) {
282
+ // Ignore Declare modifier
283
+ if (shouldIgnoreStatement(stmt))
284
+ continue;
285
+ // Only collect let/const declarations
240
286
  const isLetOrConst = (stmt.declarationList.flags &
241
287
  (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
242
288
  if (isLetOrConst) {
@@ -246,3 +292,121 @@ export function collectBlockScopedDeclarations(statements) {
246
292
  }
247
293
  return decls;
248
294
  }
295
+ export function isFunctionUsedAsValue(decl, root) {
296
+ const funcName = decl.name?.getText();
297
+ if (!funcName)
298
+ return false;
299
+ let isUsed = false;
300
+ const visitor = (node) => {
301
+ if (isUsed)
302
+ return;
303
+ if (ts.isIdentifier(node) && node.text === funcName) {
304
+ const scope = this.getScopeForNode(node);
305
+ const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(node.text, scope);
306
+ if (typeInfo?.declaration === decl) {
307
+ const parent = node.parent;
308
+ // Ignore declarations where the identifier is the name being declared
309
+ if ((ts.isFunctionDeclaration(parent) ||
310
+ ts.isVariableDeclaration(parent) ||
311
+ ts.isClassDeclaration(parent) ||
312
+ ts.isMethodDeclaration(parent) ||
313
+ ts.isParameter(parent) ||
314
+ ts.isImportSpecifier(parent)) &&
315
+ parent.name === node) {
316
+ // Declaration, do nothing
317
+ } // Ignore property names (e.g. obj.funcName)
318
+ else if ((ts.isPropertyAccessExpression(parent) &&
319
+ parent.name === node) ||
320
+ (ts.isPropertyAssignment(parent) && parent.name === node)) {
321
+ // Property name, do nothing
322
+ } // Ignore direct calls (e.g. funcName())
323
+ else if (ts.isCallExpression(parent) && parent.expression === node) {
324
+ // Call, do nothing
325
+ }
326
+ else {
327
+ // Used as a value
328
+ isUsed = true;
329
+ }
330
+ }
331
+ }
332
+ if (!isUsed) {
333
+ ts.forEachChild(node, visitor);
334
+ }
335
+ };
336
+ ts.forEachChild(root, visitor);
337
+ return isUsed;
338
+ }
339
+ export function isFunctionUsedBeforeDeclaration(funcName, root) {
340
+ let declPos = -1;
341
+ let foundDecl = false;
342
+ // Helper to find the function declaration position
343
+ function findDecl(node) {
344
+ if (foundDecl)
345
+ return;
346
+ if (ts.isFunctionDeclaration(node) && node.name?.text === funcName) {
347
+ declPos = node.getStart();
348
+ foundDecl = true;
349
+ }
350
+ else {
351
+ ts.forEachChild(node, findDecl);
352
+ }
353
+ }
354
+ findDecl(root);
355
+ // If not declared in this scope (or at least not found), assume it's not a local forward-ref issue
356
+ if (!foundDecl)
357
+ return false;
358
+ let isUsedBefore = false;
359
+ function visit(node) {
360
+ if (isUsedBefore)
361
+ return;
362
+ if (ts.isIdentifier(node) && node.text === funcName) {
363
+ const parent = node.parent;
364
+ // Ignore declarations where the identifier is the name being declared
365
+ if ((ts.isFunctionDeclaration(parent) ||
366
+ ts.isVariableDeclaration(parent) ||
367
+ ts.isClassDeclaration(parent) ||
368
+ ts.isMethodDeclaration(parent) ||
369
+ ts.isParameter(parent) ||
370
+ ts.isImportSpecifier(parent)) &&
371
+ parent.name === node) {
372
+ // Declaration (or shadowing), do nothing
373
+ } // Ignore property names (e.g. obj.funcName)
374
+ else if ((ts.isPropertyAccessExpression(parent) &&
375
+ parent.name === node) ||
376
+ (ts.isPropertyAssignment(parent) && parent.name === node)) {
377
+ // Property name, do nothing
378
+ }
379
+ else {
380
+ // It is a usage (call or value usage)
381
+ const usagePos = node.getStart();
382
+ // Check 1: Lexically before
383
+ if (usagePos < declPos) {
384
+ isUsedBefore = true;
385
+ return;
386
+ }
387
+ // Check 2: Inside a function declared before
388
+ let current = parent;
389
+ while (current && current !== root) {
390
+ if (ts.isFunctionDeclaration(current) ||
391
+ ts.isMethodDeclaration(current) ||
392
+ ts.isArrowFunction(current) ||
393
+ ts.isFunctionExpression(current) ||
394
+ ts.isGetAccessor(current) ||
395
+ ts.isSetAccessor(current) ||
396
+ ts.isConstructorDeclaration(current)) {
397
+ if (current.getStart() < declPos) {
398
+ isUsedBefore = true;
399
+ return;
400
+ }
401
+ // Once we hit a function boundary, we stop bubbling up for this usage.
402
+ break;
403
+ }
404
+ current = current.parent;
405
+ }
406
+ }
407
+ }
408
+ ts.forEachChild(node, visit);
409
+ }
410
+ visit(root);
411
+ return isUsedBefore;
412
+ }
@@ -1,11 +1,12 @@
1
1
  import { DeclaredSymbols } from "../../ast/symbols.js";
2
- import { generateLambda } from "./function-handlers.js";
3
- import { escapeString, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isGeneratorFunction, markSymbolAsChecked, prepareScopeSymbolsForVisit, } from "./helpers.js";
2
+ import { generateFullLambdaExpression, generateLambda, } from "./function-handlers.js";
3
+ import { escapeString, generateUniqueExceptionName, generateUniqueName, getDeclaredSymbols, getDerefCode, getJsVarName, getReturnCommand, getScopeForNode, hoistDeclaration, indent, isAsyncFunction, isBuiltinObject, isFunctionUsedAsValue, isFunctionUsedBeforeDeclaration, isGeneratorFunction, markSymbolAsInitialized, prepareScopeSymbolsForVisit, } from "./helpers.js";
4
4
  import { visit } from "./visitor.js";
5
- const MODULE_NAME = "__main_function__";
5
+ const MODULE_NAME = "__entry_point__";
6
6
  export class CodeGenerator {
7
7
  indentationLevel = 0;
8
8
  typeAnalyzer;
9
+ isTypescript = false;
9
10
  globalThisVar;
10
11
  uniqueNameCounter = 0;
11
12
  // visitor
@@ -25,14 +26,18 @@ export class CodeGenerator {
25
26
  isGeneratorFunction = isGeneratorFunction;
26
27
  isAsyncFunction = isAsyncFunction;
27
28
  prepareScopeSymbolsForVisit = prepareScopeSymbolsForVisit;
28
- markSymbolAsChecked = markSymbolAsChecked;
29
+ markSymbolAsInitialized = markSymbolAsInitialized;
30
+ isFunctionUsedAsValue = isFunctionUsedAsValue;
31
+ isFunctionUsedBeforeDeclaration = isFunctionUsedBeforeDeclaration;
29
32
  // function handlers
30
33
  generateLambda = generateLambda;
34
+ generateFullLambdaExpression = generateFullLambdaExpression;
31
35
  /**
32
36
  * Main entry point for the code generation process.
33
37
  */
34
- generate(ast, analyzer) {
38
+ generate(ast, analyzer, isTypescript) {
35
39
  this.typeAnalyzer = analyzer;
40
+ this.isTypescript = isTypescript;
36
41
  this.globalThisVar = this.generateUniqueName("__this_val__", this.getDeclaredSymbols(ast));
37
42
  const declarations = `#include "index.hpp"\n\n`;
38
43
  let containerCode = `jspp::JsPromise ${MODULE_NAME}() {\n`;
@@ -40,11 +45,12 @@ export class CodeGenerator {
40
45
  containerCode +=
41
46
  `${this.indent()}jspp::AnyValue ${this.globalThisVar} = global;\n`;
42
47
  containerCode += this.visit(ast, {
48
+ currentScopeNode: ast,
43
49
  isMainContext: true,
44
50
  isInsideFunction: true,
45
51
  isFunctionBody: true,
46
52
  isInsideAsyncFunction: true,
47
- topLevelScopeSymbols: new DeclaredSymbols(),
53
+ globalScopeSymbols: new DeclaredSymbols(),
48
54
  localScopeSymbols: new DeclaredSymbols(),
49
55
  });
50
56
  this.indentationLevel--;
@@ -64,10 +70,10 @@ export class CodeGenerator {
64
70
  mainCode += ` jspp::Scheduler::instance().run();\n`;
65
71
  mainCode += ` } catch (const std::exception& ex) {\n`;
66
72
  mainCode +=
67
- " auto error = std::make_shared<jspp::AnyValue>(jspp::Exception::exception_to_any_value(ex));\n {\n";
73
+ " auto error = std::make_shared<jspp::AnyValue>(jspp::Exception::exception_to_any_value(ex));\n";
68
74
  mainCode +=
69
- ` console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
70
- mainCode += ` return 1;\n }\n`;
75
+ ` console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
76
+ mainCode += ` return 1;\n`;
71
77
  mainCode += ` }\n`;
72
78
  mainCode += " return 0;\n}";
73
79
  return declarations + containerCode + mainCode;