@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.
- package/README.md +51 -36
- package/dist/analysis/scope.js +7 -0
- package/dist/analysis/typeAnalyzer.js +58 -5
- package/dist/ast/symbols.js +58 -15
- package/dist/cli/args.js +59 -0
- package/dist/cli/colors.js +9 -0
- package/dist/cli/file-utils.js +20 -0
- package/dist/cli/index.js +160 -0
- package/dist/cli/spinner.js +55 -0
- package/dist/cli.js +1 -1
- package/dist/core/codegen/control-flow-handlers.js +139 -51
- package/dist/core/codegen/declaration-handlers.js +45 -12
- package/dist/core/codegen/expression-handlers.js +139 -48
- package/dist/core/codegen/function-handlers.js +53 -59
- package/dist/core/codegen/helpers.js +196 -32
- package/dist/core/codegen/index.js +15 -9
- package/dist/core/codegen/statement-handlers.js +178 -63
- package/dist/core/codegen/visitor.js +22 -2
- package/dist/core/constants.js +16 -0
- package/dist/core/error.js +58 -0
- package/dist/core/parser.js +2 -2
- package/dist/index.js +6 -3
- package/package.json +3 -3
- package/src/prelude/scheduler.hpp +144 -144
- package/src/prelude/utils/access.hpp +2 -2
- package/src/prelude/utils/log_any_value/object.hpp +12 -10
- package/src/prelude/utils/log_any_value/primitives.hpp +7 -0
- package/src/prelude/utils/operators.hpp +4 -6
- package/src/prelude/values/prototypes/function.hpp +18 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { BUILTIN_OBJECTS, Scope } from "../../analysis/scope.js";
|
|
3
|
-
import {
|
|
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
|
|
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.
|
|
90
|
-
const
|
|
95
|
+
context.globalScopeSymbols.get(symbolName);
|
|
96
|
+
const isInitialized = symbol?.checks.initialized ||
|
|
91
97
|
false;
|
|
92
98
|
// Mark the symbol as checked
|
|
93
|
-
this.
|
|
99
|
+
this.markSymbolAsInitialized(symbolName, context.globalScopeSymbols, context.localScopeSymbols);
|
|
94
100
|
// Apply deref code
|
|
95
|
-
if (
|
|
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
|
|
114
|
+
export function markSymbolAsInitialized(name, topLevel, local) {
|
|
109
115
|
if (topLevel.has(name)) {
|
|
110
116
|
topLevel.update(name, {
|
|
111
|
-
|
|
117
|
+
checks: { initialized: true },
|
|
112
118
|
});
|
|
113
119
|
}
|
|
114
120
|
else if (local.has(name)) {
|
|
115
121
|
local.update(name, {
|
|
116
|
-
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
142
|
-
// Don't allow multiple declaration of `
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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 =
|
|
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,
|
|
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 = "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
73
|
+
" auto error = std::make_shared<jspp::AnyValue>(jspp::Exception::exception_to_any_value(ex));\n";
|
|
68
74
|
mainCode +=
|
|
69
|
-
`
|
|
70
|
-
mainCode += `
|
|
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;
|