@ugo-studio/jspp 0.2.5 → 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 +40 -19
- package/dist/ast/symbols.js +9 -8
- 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/core/codegen/control-flow-handlers.js +2 -2
- package/dist/core/codegen/declaration-handlers.js +9 -1
- package/dist/core/codegen/expression-handlers.js +101 -45
- package/dist/core/codegen/function-handlers.js +14 -3
- package/dist/core/codegen/helpers.js +33 -7
- package/dist/core/codegen/index.js +7 -5
- package/dist/core/codegen/statement-handlers.js +109 -37
- package/dist/core/codegen/visitor.js +22 -2
- package/dist/core/constants.js +16 -0
- package/dist/core/error.js +58 -0
- package/dist/index.js +6 -3
- package/package.json +3 -3
- package/src/prelude/scheduler.hpp +144 -144
- package/src/prelude/utils/log_any_value/object.hpp +12 -10
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { COLORS } from "./colors.js";
|
|
2
|
+
export class Spinner {
|
|
3
|
+
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
4
|
+
interval = null;
|
|
5
|
+
frameIndex = 0;
|
|
6
|
+
text;
|
|
7
|
+
constructor(text) {
|
|
8
|
+
this.text = text;
|
|
9
|
+
}
|
|
10
|
+
start() {
|
|
11
|
+
process.stdout.write("\x1b[?25l"); // Hide cursor
|
|
12
|
+
this.frameIndex = 0;
|
|
13
|
+
this.render();
|
|
14
|
+
this.interval = setInterval(() => {
|
|
15
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
16
|
+
this.render();
|
|
17
|
+
}, 80);
|
|
18
|
+
}
|
|
19
|
+
update(text) {
|
|
20
|
+
this.text = text;
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
stop(symbol = "", color = COLORS.reset) {
|
|
24
|
+
if (this.interval) {
|
|
25
|
+
clearInterval(this.interval);
|
|
26
|
+
this.interval = null;
|
|
27
|
+
}
|
|
28
|
+
this.clearLine();
|
|
29
|
+
process.stdout.write(`${color}${symbol} ${COLORS.reset} ${this.text}\n`);
|
|
30
|
+
process.stdout.write("\x1b[?25h"); // Show cursor
|
|
31
|
+
}
|
|
32
|
+
succeed(text) {
|
|
33
|
+
if (text)
|
|
34
|
+
this.text = text;
|
|
35
|
+
this.stop("✔", COLORS.green);
|
|
36
|
+
}
|
|
37
|
+
fail(text) {
|
|
38
|
+
if (text)
|
|
39
|
+
this.text = text;
|
|
40
|
+
this.stop("✖", COLORS.red);
|
|
41
|
+
}
|
|
42
|
+
info(text) {
|
|
43
|
+
if (text)
|
|
44
|
+
this.text = text;
|
|
45
|
+
this.stop("ℹ", COLORS.cyan);
|
|
46
|
+
}
|
|
47
|
+
render() {
|
|
48
|
+
this.clearLine();
|
|
49
|
+
const frame = this.frames[this.frameIndex];
|
|
50
|
+
process.stdout.write(`${COLORS.cyan}${frame} ${COLORS.reset} ${this.text}`);
|
|
51
|
+
}
|
|
52
|
+
clearLine() {
|
|
53
|
+
process.stdout.write("\r\x1b[K");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -44,11 +44,11 @@ export function visitForStatement(node, context) {
|
|
|
44
44
|
: DeclarationType.const;
|
|
45
45
|
conditionContext.localScopeSymbols.add(name, {
|
|
46
46
|
type: declType,
|
|
47
|
-
|
|
47
|
+
checks: { initialized: true },
|
|
48
48
|
});
|
|
49
49
|
statementContext.localScopeSymbols.add(name, {
|
|
50
50
|
type: declType,
|
|
51
|
-
|
|
51
|
+
checks: { initialized: true },
|
|
52
52
|
});
|
|
53
53
|
if (typeInfo.needsHeapAllocation) {
|
|
54
54
|
initializerCode =
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { CompilerError } from "../error.js";
|
|
2
3
|
import { CodeGenerator } from "./index.js";
|
|
3
4
|
export function visitVariableDeclarationList(node, context) {
|
|
4
5
|
return node.declarations
|
|
@@ -69,7 +70,14 @@ export function visitVariableDeclaration(node, context) {
|
|
|
69
70
|
if (isLetOrConst) {
|
|
70
71
|
// If there's no initializer, it should be assigned undefined.
|
|
71
72
|
if (!initializer) {
|
|
72
|
-
|
|
73
|
+
// Constant variables must be initialized
|
|
74
|
+
const isConst = (varDecl.parent.flags & (ts.NodeFlags.Const)) !== 0;
|
|
75
|
+
if (isConst) {
|
|
76
|
+
throw new CompilerError(`The constant "${name}" must be initialized`, varDecl, "SyntaxError");
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
return `${nativeLambdaCode}${assignmentTarget} = jspp::Constants::UNDEFINED`;
|
|
80
|
+
}
|
|
73
81
|
}
|
|
74
82
|
return `${nativeLambdaCode}${assignmentTarget}${initializer}`;
|
|
75
83
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { constants } from "../constants.js";
|
|
3
|
+
import { CompilerError } from "../error.js";
|
|
2
4
|
import { CodeGenerator } from "./index.js";
|
|
3
5
|
export function visitObjectPropertyName(node, context) {
|
|
4
6
|
if (ts.isNumericLiteral(node)) {
|
|
@@ -190,7 +192,7 @@ export function visitPropertyAccessExpression(node, context) {
|
|
|
190
192
|
const propAccess = node;
|
|
191
193
|
if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
|
|
192
194
|
if (!context.superClassVar) {
|
|
193
|
-
throw new
|
|
195
|
+
throw new CompilerError("super.prop accessed but no super class variable found in context", propAccess.expression, "SyntaxError");
|
|
194
196
|
}
|
|
195
197
|
const propName = propAccess.name.getText();
|
|
196
198
|
return `jspp::AnyValue::resolve_property_for_read((${context.superClassVar}).get_own_property("prototype").get_own_property("${propName}"), ${this.globalThisVar}, "${this.escapeString(propName)}")`;
|
|
@@ -258,7 +260,11 @@ export function visitElementAccessExpression(node, context) {
|
|
|
258
260
|
export function visitBinaryExpression(node, context) {
|
|
259
261
|
const binExpr = node;
|
|
260
262
|
const opToken = binExpr.operatorToken;
|
|
261
|
-
|
|
263
|
+
const op = opToken.getText();
|
|
264
|
+
const visitContext = {
|
|
265
|
+
...context,
|
|
266
|
+
supportedNativeLiterals: undefined,
|
|
267
|
+
};
|
|
262
268
|
const assignmentOperators = [
|
|
263
269
|
ts.SyntaxKind.PlusEqualsToken,
|
|
264
270
|
ts.SyntaxKind.MinusEqualsToken,
|
|
@@ -279,8 +285,8 @@ export function visitBinaryExpression(node, context) {
|
|
|
279
285
|
if (assignmentOperators.includes(opToken.kind)) {
|
|
280
286
|
if (opToken.kind ===
|
|
281
287
|
ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken) {
|
|
282
|
-
const leftText = this.visit(binExpr.left,
|
|
283
|
-
const rightText = this.visit(binExpr.right,
|
|
288
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
289
|
+
const rightText = this.visit(binExpr.right, visitContext);
|
|
284
290
|
let target = leftText;
|
|
285
291
|
if (ts.isIdentifier(binExpr.left)) {
|
|
286
292
|
const scope = this.getScopeForNode(binExpr.left);
|
|
@@ -292,8 +298,8 @@ export function visitBinaryExpression(node, context) {
|
|
|
292
298
|
}
|
|
293
299
|
}
|
|
294
300
|
if (opToken.kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken) {
|
|
295
|
-
const leftText = this.visit(binExpr.left,
|
|
296
|
-
const rightText = this.visit(binExpr.right,
|
|
301
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
302
|
+
const rightText = this.visit(binExpr.right, visitContext);
|
|
297
303
|
let target = leftText;
|
|
298
304
|
if (ts.isIdentifier(binExpr.left)) {
|
|
299
305
|
const scope = this.getScopeForNode(binExpr.left);
|
|
@@ -308,8 +314,8 @@ export function visitBinaryExpression(node, context) {
|
|
|
308
314
|
if (opToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
|
|
309
315
|
opToken.kind === ts.SyntaxKind.BarBarEqualsToken ||
|
|
310
316
|
opToken.kind === ts.SyntaxKind.QuestionQuestionEqualsToken) {
|
|
311
|
-
const leftText = this.visit(binExpr.left,
|
|
312
|
-
const rightText = this.visit(binExpr.right,
|
|
317
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
318
|
+
const rightText = this.visit(binExpr.right, visitContext);
|
|
313
319
|
let target = leftText;
|
|
314
320
|
if (ts.isIdentifier(binExpr.left)) {
|
|
315
321
|
const scope = this.getScopeForNode(binExpr.left);
|
|
@@ -328,21 +334,21 @@ export function visitBinaryExpression(node, context) {
|
|
|
328
334
|
}
|
|
329
335
|
}
|
|
330
336
|
}
|
|
331
|
-
const leftText = this.visit(binExpr.left,
|
|
337
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
332
338
|
let rightText = ts.isNumericLiteral(binExpr.right)
|
|
333
339
|
? binExpr.right.getText()
|
|
334
|
-
: this.visit(binExpr.right,
|
|
340
|
+
: this.visit(binExpr.right, visitContext);
|
|
335
341
|
if (ts.isIdentifier(binExpr.right)) {
|
|
336
342
|
const scope = this.getScopeForNode(binExpr.right);
|
|
337
343
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.right.getText(), scope);
|
|
338
|
-
rightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right),
|
|
344
|
+
rightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, typeInfo);
|
|
339
345
|
}
|
|
340
346
|
let target = leftText;
|
|
341
347
|
if (ts.isIdentifier(binExpr.left)) {
|
|
342
348
|
const scope = this.getScopeForNode(binExpr.left);
|
|
343
349
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.getText(), scope);
|
|
344
350
|
if (context.derefBeforeAssignment) {
|
|
345
|
-
target = this.getDerefCode(leftText, leftText,
|
|
351
|
+
target = this.getDerefCode(leftText, leftText, visitContext, typeInfo);
|
|
346
352
|
}
|
|
347
353
|
else if (typeInfo.needsHeapAllocation) {
|
|
348
354
|
target = `*${leftText}`;
|
|
@@ -350,8 +356,9 @@ export function visitBinaryExpression(node, context) {
|
|
|
350
356
|
}
|
|
351
357
|
return `${target} ${op} ${rightText}`;
|
|
352
358
|
}
|
|
359
|
+
// Assignment expression `a = 1`
|
|
353
360
|
if (opToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
354
|
-
let rightText = this.visit(binExpr.right,
|
|
361
|
+
let rightText = this.visit(binExpr.right, visitContext);
|
|
355
362
|
if (ts.isPropertyAccessExpression(binExpr.left)) {
|
|
356
363
|
const propAccess = binExpr.left;
|
|
357
364
|
if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
|
|
@@ -365,13 +372,13 @@ export function visitBinaryExpression(node, context) {
|
|
|
365
372
|
if (rightTypeInfo &&
|
|
366
373
|
!rightTypeInfo.isParameter &&
|
|
367
374
|
!rightTypeInfo.isBuiltin) {
|
|
368
|
-
finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right),
|
|
375
|
+
finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, rightTypeInfo);
|
|
369
376
|
}
|
|
370
377
|
}
|
|
371
378
|
// Approximate super assignment as setting property on 'this'
|
|
372
379
|
return `(${this.globalThisVar}).set_own_property("${propName}", ${finalRightText})`;
|
|
373
380
|
}
|
|
374
|
-
const objExprText = this.visit(propAccess.expression,
|
|
381
|
+
const objExprText = this.visit(propAccess.expression, visitContext);
|
|
375
382
|
const propName = propAccess.name.getText();
|
|
376
383
|
let finalObjExpr = objExprText;
|
|
377
384
|
if (ts.isIdentifier(propAccess.expression)) {
|
|
@@ -396,14 +403,14 @@ export function visitBinaryExpression(node, context) {
|
|
|
396
403
|
}
|
|
397
404
|
else if (ts.isElementAccessExpression(binExpr.left)) {
|
|
398
405
|
const elemAccess = binExpr.left;
|
|
399
|
-
const objExprText = this.visit(elemAccess.expression,
|
|
400
|
-
let argText = visitObjectPropertyName.call(this, elemAccess.argumentExpression, { ...
|
|
406
|
+
const objExprText = this.visit(elemAccess.expression, visitContext);
|
|
407
|
+
let argText = visitObjectPropertyName.call(this, elemAccess.argumentExpression, { ...visitContext, isBracketNotationPropertyAccess: true });
|
|
401
408
|
let finalObjExpr = objExprText;
|
|
402
409
|
if (ts.isIdentifier(elemAccess.expression)) {
|
|
403
410
|
const scope = this.getScopeForNode(elemAccess.expression);
|
|
404
411
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(elemAccess.expression.getText(), scope);
|
|
405
412
|
if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
|
|
406
|
-
finalObjExpr = this.getDerefCode(objExprText, this.getJsVarName(elemAccess.expression),
|
|
413
|
+
finalObjExpr = this.getDerefCode(objExprText, this.getJsVarName(elemAccess.expression), visitContext, typeInfo);
|
|
407
414
|
}
|
|
408
415
|
}
|
|
409
416
|
if (ts.isIdentifier(elemAccess.argumentExpression)) {
|
|
@@ -413,7 +420,7 @@ export function visitBinaryExpression(node, context) {
|
|
|
413
420
|
if (argTypeInfo &&
|
|
414
421
|
!argTypeInfo.isParameter &&
|
|
415
422
|
!argTypeInfo.isBuiltin) {
|
|
416
|
-
argText = this.getDerefCode(argText, this.getJsVarName(elemAccess.argumentExpression),
|
|
423
|
+
argText = this.getDerefCode(argText, this.getJsVarName(elemAccess.argumentExpression), visitContext, argTypeInfo);
|
|
417
424
|
}
|
|
418
425
|
}
|
|
419
426
|
let finalRightText = rightText;
|
|
@@ -424,12 +431,12 @@ export function visitBinaryExpression(node, context) {
|
|
|
424
431
|
if (rightTypeInfo &&
|
|
425
432
|
!rightTypeInfo.isParameter &&
|
|
426
433
|
!rightTypeInfo.isBuiltin) {
|
|
427
|
-
finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right),
|
|
434
|
+
finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, rightTypeInfo);
|
|
428
435
|
}
|
|
429
436
|
}
|
|
430
437
|
return `${finalObjExpr}.set_own_property(${argText}, ${finalRightText})`;
|
|
431
438
|
}
|
|
432
|
-
const leftText = this.visit(binExpr.left,
|
|
439
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
433
440
|
const scope = this.getScopeForNode(binExpr.left);
|
|
434
441
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.text, scope);
|
|
435
442
|
if (!typeInfo && !this.isBuiltinObject(binExpr.left)) {
|
|
@@ -442,7 +449,7 @@ export function visitBinaryExpression(node, context) {
|
|
|
442
449
|
rightText = binExpr.right.getText();
|
|
443
450
|
}
|
|
444
451
|
const target = context.derefBeforeAssignment
|
|
445
|
-
? this.getDerefCode(leftText, leftText,
|
|
452
|
+
? this.getDerefCode(leftText, leftText, visitContext, typeInfo)
|
|
446
453
|
: (typeInfo.needsHeapAllocation ? `*${leftText}` : leftText);
|
|
447
454
|
// Update scope symbols on variable re-assignment
|
|
448
455
|
if (ts.isIdentifier(binExpr.left)) {
|
|
@@ -461,8 +468,9 @@ export function visitBinaryExpression(node, context) {
|
|
|
461
468
|
}
|
|
462
469
|
return `${target} ${op} ${rightText}`;
|
|
463
470
|
}
|
|
464
|
-
const leftText = this.visit(binExpr.left,
|
|
465
|
-
const rightText = this.visit(binExpr.right,
|
|
471
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
472
|
+
const rightText = this.visit(binExpr.right, visitContext);
|
|
473
|
+
// Generate lhs and rhs code
|
|
466
474
|
let finalLeft = leftText;
|
|
467
475
|
if (ts.isIdentifier(binExpr.left)) {
|
|
468
476
|
const scope = this.getScopeForNode(binExpr.left);
|
|
@@ -471,7 +479,7 @@ export function visitBinaryExpression(node, context) {
|
|
|
471
479
|
return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(binExpr.left)})`;
|
|
472
480
|
}
|
|
473
481
|
if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
|
|
474
|
-
finalLeft = this.getDerefCode(leftText, this.getJsVarName(binExpr.left),
|
|
482
|
+
finalLeft = this.getDerefCode(leftText, this.getJsVarName(binExpr.left), visitContext, typeInfo);
|
|
475
483
|
}
|
|
476
484
|
}
|
|
477
485
|
let finalRight = rightText;
|
|
@@ -483,7 +491,7 @@ export function visitBinaryExpression(node, context) {
|
|
|
483
491
|
return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(binExpr.right)})`;
|
|
484
492
|
}
|
|
485
493
|
if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
|
|
486
|
-
finalRight = this.getDerefCode(rightText, this.getJsVarName(binExpr.right),
|
|
494
|
+
finalRight = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, typeInfo);
|
|
487
495
|
}
|
|
488
496
|
}
|
|
489
497
|
if (opToken.kind === ts.SyntaxKind.InKeyword) {
|
|
@@ -501,35 +509,63 @@ export function visitBinaryExpression(node, context) {
|
|
|
501
509
|
if (opToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
502
510
|
return `jspp::nullish_coalesce(${finalLeft}, ${finalRight})`;
|
|
503
511
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
512
|
+
const isLiteral = (n) => ts.isNumericLiteral(n);
|
|
513
|
+
const supportsNativeBoolean = context.supportedNativeLiterals?.includes("boolean") || false;
|
|
514
|
+
// Native values for lhs and rhs
|
|
515
|
+
const literalLeft = isLiteral(binExpr.left)
|
|
516
|
+
? binExpr.left.getText()
|
|
517
|
+
: finalLeft;
|
|
518
|
+
const literalRight = isLiteral(binExpr.right)
|
|
519
|
+
? binExpr.right.getText()
|
|
520
|
+
: finalRight;
|
|
521
|
+
// Operations that returns boolean should return the native boolean if supported
|
|
522
|
+
if (constants.booleanOperators.includes(opToken.kind) &&
|
|
523
|
+
supportsNativeBoolean) {
|
|
524
|
+
if (opToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {
|
|
525
|
+
return `jspp::is_strictly_equal_to_primitive(${literalLeft}, ${literalRight})`;
|
|
526
|
+
}
|
|
527
|
+
if (opToken.kind === ts.SyntaxKind.EqualsEqualsToken) {
|
|
528
|
+
return `jspp::is_equal_to_primitive(${literalLeft}, ${literalRight})`;
|
|
529
|
+
}
|
|
530
|
+
if (opToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
|
|
531
|
+
return `!jspp::is_strictly_equal_to_primitive(${literalLeft}, ${literalRight})`;
|
|
532
|
+
}
|
|
533
|
+
if (opToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
|
|
534
|
+
return `!jspp::is_equal_to_primitive(${literalLeft}, ${literalRight})`;
|
|
535
|
+
}
|
|
536
|
+
if (isLiteral(binExpr.left) && isLiteral(binExpr.right)) {
|
|
537
|
+
return `(${literalLeft} ${op} ${literalRight})`;
|
|
538
|
+
}
|
|
539
|
+
else
|
|
540
|
+
return `(${literalLeft} ${op} ${literalRight}).as_boolean()`;
|
|
541
|
+
}
|
|
542
|
+
// Return boxed value
|
|
511
543
|
if (opToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {
|
|
512
|
-
return `jspp::is_strictly_equal_to(${
|
|
544
|
+
return `jspp::is_strictly_equal_to(${literalLeft}, ${literalRight})`;
|
|
513
545
|
}
|
|
514
546
|
if (opToken.kind === ts.SyntaxKind.EqualsEqualsToken) {
|
|
515
|
-
return `jspp::is_equal_to(${
|
|
547
|
+
return `jspp::is_equal_to(${literalLeft}, ${literalRight})`;
|
|
516
548
|
}
|
|
517
549
|
if (opToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
|
|
518
|
-
return `jspp::not_strictly_equal_to(${
|
|
550
|
+
return `jspp::not_strictly_equal_to(${literalLeft}, ${literalRight})`;
|
|
519
551
|
}
|
|
520
552
|
if (opToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
|
|
521
|
-
return `jspp::not_equal_to(${
|
|
553
|
+
return `jspp::not_equal_to(${literalLeft}, ${literalRight})`;
|
|
522
554
|
}
|
|
523
555
|
if (opToken.kind === ts.SyntaxKind.AsteriskAsteriskToken) {
|
|
524
|
-
return `jspp::pow(${
|
|
556
|
+
return `jspp::pow(${literalLeft}, ${literalRight})`;
|
|
525
557
|
}
|
|
526
558
|
if (opToken.kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken) {
|
|
527
|
-
return `jspp::unsigned_right_shift(${
|
|
559
|
+
return `jspp::unsigned_right_shift(${literalLeft}, ${literalRight})`;
|
|
560
|
+
}
|
|
561
|
+
// Use literal for at most one side
|
|
562
|
+
if (isLiteral(binExpr.left)) {
|
|
563
|
+
finalLeft = binExpr.left.getText();
|
|
528
564
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
return `jspp::AnyValue::make_number(${finalLeft} ${op} ${finalRight})`;
|
|
565
|
+
else if (isLiteral(binExpr.right)) {
|
|
566
|
+
finalRight = binExpr.right.getText();
|
|
532
567
|
}
|
|
568
|
+
// For other arithmetic and bitwise operations, use native operations if possible
|
|
533
569
|
if (op === "+" ||
|
|
534
570
|
op === "-" ||
|
|
535
571
|
op === "*" ||
|
|
@@ -546,7 +582,12 @@ export function visitBinaryExpression(node, context) {
|
|
|
546
582
|
}
|
|
547
583
|
export function visitConditionalExpression(node, context) {
|
|
548
584
|
const condExpr = node;
|
|
549
|
-
const
|
|
585
|
+
const isBinaryExpression = ts.isBinaryExpression(condExpr.condition) &&
|
|
586
|
+
constants.booleanOperators.includes(condExpr.condition.operatorToken.kind);
|
|
587
|
+
const condition = this.visit(condExpr.condition, {
|
|
588
|
+
...context,
|
|
589
|
+
supportedNativeLiterals: isBinaryExpression ? ["boolean"] : undefined,
|
|
590
|
+
});
|
|
550
591
|
const whenTrueStmt = this.visit(condExpr.whenTrue, {
|
|
551
592
|
...context,
|
|
552
593
|
isFunctionBody: false,
|
|
@@ -555,6 +596,9 @@ export function visitConditionalExpression(node, context) {
|
|
|
555
596
|
...context,
|
|
556
597
|
isFunctionBody: false,
|
|
557
598
|
});
|
|
599
|
+
if (isBinaryExpression) {
|
|
600
|
+
return `${condition} ? ${whenTrueStmt} : ${whenFalseStmt}`;
|
|
601
|
+
}
|
|
558
602
|
return `jspp::is_truthy(${condition}) ? ${whenTrueStmt} : ${whenFalseStmt}`;
|
|
559
603
|
}
|
|
560
604
|
export function visitCallExpression(node, context) {
|
|
@@ -562,7 +606,7 @@ export function visitCallExpression(node, context) {
|
|
|
562
606
|
const callee = callExpr.expression;
|
|
563
607
|
if (callee.kind === ts.SyntaxKind.SuperKeyword) {
|
|
564
608
|
if (!context.superClassVar) {
|
|
565
|
-
throw new
|
|
609
|
+
throw new CompilerError("super() called but no super class variable found in context", callee, "SyntaxError");
|
|
566
610
|
}
|
|
567
611
|
const args = callExpr.arguments.map((arg) => this.visit(arg, context))
|
|
568
612
|
.join(", ");
|
|
@@ -593,7 +637,7 @@ export function visitCallExpression(node, context) {
|
|
|
593
637
|
const propAccess = callee;
|
|
594
638
|
if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
|
|
595
639
|
if (!context.superClassVar) {
|
|
596
|
-
throw new
|
|
640
|
+
throw new CompilerError("super.method() called but no super class variable found in context", propAccess.expression, "SyntaxError");
|
|
597
641
|
}
|
|
598
642
|
const propName = propAccess.name.getText();
|
|
599
643
|
return `(${context.superClassVar}).get_own_property("prototype").get_own_property("${propName}").call(${this.globalThisVar}, ${argsSpan}, "${this.escapeString(propName)}")`;
|
|
@@ -827,3 +871,15 @@ export function visitDeleteExpression(node, context) {
|
|
|
827
871
|
}
|
|
828
872
|
return "jspp::Constants::TRUE"; // delete on non-property is true in JS
|
|
829
873
|
}
|
|
874
|
+
export function visitAsExpression(node, context) {
|
|
875
|
+
return this.visit(node.expression, context);
|
|
876
|
+
}
|
|
877
|
+
export function visitTypeAssertionExpression(node, context) {
|
|
878
|
+
return this.visit(node.expression, context);
|
|
879
|
+
}
|
|
880
|
+
export function visitNonNullExpression(node, context) {
|
|
881
|
+
return this.visit(node.expression, context);
|
|
882
|
+
}
|
|
883
|
+
export function visitSatisfiesExpression(node, context) {
|
|
884
|
+
return this.visit(node.expression, context);
|
|
885
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { DeclaredSymbols } from "../../ast/symbols.js";
|
|
2
|
+
import { DeclarationType, DeclaredSymbols } from "../../ast/symbols.js";
|
|
3
|
+
import { CompilerError } from "../error.js";
|
|
3
4
|
import { collectFunctionScopedDeclarations } from "./helpers.js";
|
|
4
5
|
import { CodeGenerator } from "./index.js";
|
|
5
6
|
export function generateLambda(node, context, options) {
|
|
@@ -84,17 +85,27 @@ export function generateLambda(node, context, options) {
|
|
|
84
85
|
// So subsequent code using `argsName` and `visit` (using `globalThisVar`) works correctly.
|
|
85
86
|
const paramExtractor = (parameters) => {
|
|
86
87
|
let code = "";
|
|
87
|
-
parameters.
|
|
88
|
+
parameters.filter((p) => this.isTypescript && p.name.getText() === "this" ? false : true // Ignore "this" parameters
|
|
89
|
+
).forEach((p, i) => {
|
|
88
90
|
const name = p.name.getText();
|
|
89
91
|
const defaultValue = p.initializer
|
|
90
92
|
? this.visit(p.initializer, visitContext)
|
|
91
93
|
: "jspp::Constants::UNDEFINED";
|
|
94
|
+
// Catch invalid parameters
|
|
95
|
+
if (name === "this") {
|
|
96
|
+
throw new CompilerError(`Cannot use '${name}' as a parameter name.`, p, "SyntaxError");
|
|
97
|
+
}
|
|
98
|
+
// Add paramerter to local context
|
|
99
|
+
visitContext.localScopeSymbols.add(name, {
|
|
100
|
+
type: DeclarationType.let,
|
|
101
|
+
checks: { initialized: true },
|
|
102
|
+
});
|
|
92
103
|
const scope = this.getScopeForNode(p);
|
|
93
104
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
|
|
94
105
|
// Handle rest parameters
|
|
95
106
|
if (!!p.dotDotDotToken) {
|
|
96
107
|
if (parameters.length - 1 !== i) {
|
|
97
|
-
throw new
|
|
108
|
+
throw new CompilerError("Rest parameter must be last formal parameter.", p, "SyntaxError");
|
|
98
109
|
}
|
|
99
110
|
if (typeInfo.needsHeapAllocation) {
|
|
100
111
|
code +=
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { BUILTIN_OBJECTS, Scope } from "../../analysis/scope.js";
|
|
3
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
|
}
|
|
@@ -88,7 +93,7 @@ export function getDerefCode(nodeText, varName, context, typeInfo) {
|
|
|
88
93
|
const symbolName = varName.slice(1).slice(0, -1);
|
|
89
94
|
const symbol = context.localScopeSymbols.get(symbolName) ??
|
|
90
95
|
context.globalScopeSymbols.get(symbolName);
|
|
91
|
-
const isInitialized = symbol?.
|
|
96
|
+
const isInitialized = symbol?.checks.initialized ||
|
|
92
97
|
false;
|
|
93
98
|
// Mark the symbol as checked
|
|
94
99
|
this.markSymbolAsInitialized(symbolName, context.globalScopeSymbols, context.localScopeSymbols);
|
|
@@ -109,12 +114,12 @@ export function getDerefCode(nodeText, varName, context, typeInfo) {
|
|
|
109
114
|
export function markSymbolAsInitialized(name, topLevel, local) {
|
|
110
115
|
if (topLevel.has(name)) {
|
|
111
116
|
topLevel.update(name, {
|
|
112
|
-
|
|
117
|
+
checks: { initialized: true },
|
|
113
118
|
});
|
|
114
119
|
}
|
|
115
120
|
else if (local.has(name)) {
|
|
116
121
|
local.update(name, {
|
|
117
|
-
|
|
122
|
+
checks: { initialized: true },
|
|
118
123
|
});
|
|
119
124
|
}
|
|
120
125
|
}
|
|
@@ -138,16 +143,19 @@ export function hoistDeclaration(decl, hoistedSymbols, scopeNode) {
|
|
|
138
143
|
? DeclarationType.function
|
|
139
144
|
: ts.isClassDeclaration(decl)
|
|
140
145
|
? DeclarationType.class
|
|
141
|
-
:
|
|
146
|
+
: ts.isEnumDeclaration(decl)
|
|
147
|
+
? DeclarationType.enum
|
|
148
|
+
: DeclarationType.var;
|
|
142
149
|
if (hoistedSymbols.has(name)) {
|
|
143
150
|
const existingSymbol = hoistedSymbols.get(name);
|
|
144
|
-
// Don't allow multiple declaration of `let`,`const`,`function` or `
|
|
151
|
+
// Don't allow multiple declaration of `let`,`const`,`function`, `class` or `enum` variables
|
|
145
152
|
if (existingSymbol?.type === DeclarationType.let ||
|
|
146
153
|
existingSymbol?.type === DeclarationType.const ||
|
|
147
154
|
existingSymbol?.type === DeclarationType.function ||
|
|
148
155
|
existingSymbol?.type === DeclarationType.class ||
|
|
156
|
+
existingSymbol?.type === DeclarationType.enum ||
|
|
149
157
|
existingSymbol?.type !== declType) {
|
|
150
|
-
throw new
|
|
158
|
+
throw new CompilerError(`Identifier '${name}' has already been declared.`, decl, "SyntaxError");
|
|
151
159
|
}
|
|
152
160
|
// `var` variables can be declared multiple times
|
|
153
161
|
return "";
|
|
@@ -203,10 +211,24 @@ export function prepareScopeSymbolsForVisit(topLevel, local) {
|
|
|
203
211
|
// Join the top and local scopes
|
|
204
212
|
return new DeclaredSymbols(topLevel, local);
|
|
205
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
|
+
}
|
|
206
224
|
export function collectFunctionScopedDeclarations(node) {
|
|
207
225
|
const decls = [];
|
|
208
226
|
function visit(n) {
|
|
209
227
|
if (ts.isVariableStatement(n)) {
|
|
228
|
+
// Ignore Declare modifier
|
|
229
|
+
if (shouldIgnoreStatement(n))
|
|
230
|
+
return;
|
|
231
|
+
// Only collect let/const declarations
|
|
210
232
|
const isLetOrConst = (n.declarationList.flags &
|
|
211
233
|
(ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
212
234
|
if (!isLetOrConst) {
|
|
@@ -257,6 +279,10 @@ export function collectBlockScopedDeclarations(statements) {
|
|
|
257
279
|
const decls = [];
|
|
258
280
|
for (const stmt of statements) {
|
|
259
281
|
if (ts.isVariableStatement(stmt)) {
|
|
282
|
+
// Ignore Declare modifier
|
|
283
|
+
if (shouldIgnoreStatement(stmt))
|
|
284
|
+
continue;
|
|
285
|
+
// Only collect let/const declarations
|
|
260
286
|
const isLetOrConst = (stmt.declarationList.flags &
|
|
261
287
|
(ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
262
288
|
if (isLetOrConst) {
|
|
@@ -2,10 +2,11 @@ import { DeclaredSymbols } from "../../ast/symbols.js";
|
|
|
2
2
|
import { generateFullLambdaExpression, generateLambda, } from "./function-handlers.js";
|
|
3
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
|
|
@@ -34,8 +35,9 @@ export class CodeGenerator {
|
|
|
34
35
|
/**
|
|
35
36
|
* Main entry point for the code generation process.
|
|
36
37
|
*/
|
|
37
|
-
generate(ast, analyzer) {
|
|
38
|
+
generate(ast, analyzer, isTypescript) {
|
|
38
39
|
this.typeAnalyzer = analyzer;
|
|
40
|
+
this.isTypescript = isTypescript;
|
|
39
41
|
this.globalThisVar = this.generateUniqueName("__this_val__", this.getDeclaredSymbols(ast));
|
|
40
42
|
const declarations = `#include "index.hpp"\n\n`;
|
|
41
43
|
let containerCode = `jspp::JsPromise ${MODULE_NAME}() {\n`;
|
|
@@ -68,10 +70,10 @@ export class CodeGenerator {
|
|
|
68
70
|
mainCode += ` jspp::Scheduler::instance().run();\n`;
|
|
69
71
|
mainCode += ` } catch (const std::exception& ex) {\n`;
|
|
70
72
|
mainCode +=
|
|
71
|
-
" 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";
|
|
72
74
|
mainCode +=
|
|
73
|
-
`
|
|
74
|
-
mainCode += `
|
|
75
|
+
` console.call_own_property("error", std::span<const jspp::AnyValue>((const jspp::AnyValue[]){*error}, 1));\n`;
|
|
76
|
+
mainCode += ` return 1;\n`;
|
|
75
77
|
mainCode += ` }\n`;
|
|
76
78
|
mainCode += " return 0;\n}";
|
|
77
79
|
return declarations + containerCode + mainCode;
|