@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,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)) {
|
|
@@ -28,6 +30,13 @@ export function visitObjectPropertyName(node, context) {
|
|
|
28
30
|
export function visitObjectLiteralExpression(node, context) {
|
|
29
31
|
const obj = node;
|
|
30
32
|
const objVar = this.generateUniqueName("__obj_", this.getDeclaredSymbols(node));
|
|
33
|
+
if (!obj.properties.some((prop) => ts.isPropertyAssignment(prop) ||
|
|
34
|
+
ts.isShorthandPropertyAssignment(prop) ||
|
|
35
|
+
ts.isMethodDeclaration(prop) || ts.isGetAccessor(prop) ||
|
|
36
|
+
ts.isSetAccessor(prop))) {
|
|
37
|
+
// Empty object
|
|
38
|
+
return `jspp::AnyValue::make_object_with_proto({}, ::Object.get_own_property("prototype"))`;
|
|
39
|
+
}
|
|
31
40
|
let code = `([&]() {\n`;
|
|
32
41
|
code +=
|
|
33
42
|
`${this.indent()} auto ${objVar} = jspp::AnyValue::make_object_with_proto({}, ::Object.get_own_property("prototype"));\n`;
|
|
@@ -102,8 +111,6 @@ export function visitObjectLiteralExpression(node, context) {
|
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
this.indentationLevel--;
|
|
105
|
-
// code +=
|
|
106
|
-
// `${this.indent()} ${returnCmd} ${objVar};\n${this.indent()}} )() ))`;
|
|
107
114
|
code += `${this.indent()} return ${objVar};\n${this.indent()}})()`;
|
|
108
115
|
return code;
|
|
109
116
|
}
|
|
@@ -185,7 +192,7 @@ export function visitPropertyAccessExpression(node, context) {
|
|
|
185
192
|
const propAccess = node;
|
|
186
193
|
if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
|
|
187
194
|
if (!context.superClassVar) {
|
|
188
|
-
throw new
|
|
195
|
+
throw new CompilerError("super.prop accessed but no super class variable found in context", propAccess.expression, "SyntaxError");
|
|
189
196
|
}
|
|
190
197
|
const propName = propAccess.name.getText();
|
|
191
198
|
return `jspp::AnyValue::resolve_property_for_read((${context.superClassVar}).get_own_property("prototype").get_own_property("${propName}"), ${this.globalThisVar}, "${this.escapeString(propName)}")`;
|
|
@@ -253,7 +260,11 @@ export function visitElementAccessExpression(node, context) {
|
|
|
253
260
|
export function visitBinaryExpression(node, context) {
|
|
254
261
|
const binExpr = node;
|
|
255
262
|
const opToken = binExpr.operatorToken;
|
|
256
|
-
|
|
263
|
+
const op = opToken.getText();
|
|
264
|
+
const visitContext = {
|
|
265
|
+
...context,
|
|
266
|
+
supportedNativeLiterals: undefined,
|
|
267
|
+
};
|
|
257
268
|
const assignmentOperators = [
|
|
258
269
|
ts.SyntaxKind.PlusEqualsToken,
|
|
259
270
|
ts.SyntaxKind.MinusEqualsToken,
|
|
@@ -274,8 +285,8 @@ export function visitBinaryExpression(node, context) {
|
|
|
274
285
|
if (assignmentOperators.includes(opToken.kind)) {
|
|
275
286
|
if (opToken.kind ===
|
|
276
287
|
ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken) {
|
|
277
|
-
const leftText = this.visit(binExpr.left,
|
|
278
|
-
const rightText = this.visit(binExpr.right,
|
|
288
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
289
|
+
const rightText = this.visit(binExpr.right, visitContext);
|
|
279
290
|
let target = leftText;
|
|
280
291
|
if (ts.isIdentifier(binExpr.left)) {
|
|
281
292
|
const scope = this.getScopeForNode(binExpr.left);
|
|
@@ -287,8 +298,8 @@ export function visitBinaryExpression(node, context) {
|
|
|
287
298
|
}
|
|
288
299
|
}
|
|
289
300
|
if (opToken.kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken) {
|
|
290
|
-
const leftText = this.visit(binExpr.left,
|
|
291
|
-
const rightText = this.visit(binExpr.right,
|
|
301
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
302
|
+
const rightText = this.visit(binExpr.right, visitContext);
|
|
292
303
|
let target = leftText;
|
|
293
304
|
if (ts.isIdentifier(binExpr.left)) {
|
|
294
305
|
const scope = this.getScopeForNode(binExpr.left);
|
|
@@ -303,8 +314,8 @@ export function visitBinaryExpression(node, context) {
|
|
|
303
314
|
if (opToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
|
|
304
315
|
opToken.kind === ts.SyntaxKind.BarBarEqualsToken ||
|
|
305
316
|
opToken.kind === ts.SyntaxKind.QuestionQuestionEqualsToken) {
|
|
306
|
-
const leftText = this.visit(binExpr.left,
|
|
307
|
-
const rightText = this.visit(binExpr.right,
|
|
317
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
318
|
+
const rightText = this.visit(binExpr.right, visitContext);
|
|
308
319
|
let target = leftText;
|
|
309
320
|
if (ts.isIdentifier(binExpr.left)) {
|
|
310
321
|
const scope = this.getScopeForNode(binExpr.left);
|
|
@@ -323,21 +334,21 @@ export function visitBinaryExpression(node, context) {
|
|
|
323
334
|
}
|
|
324
335
|
}
|
|
325
336
|
}
|
|
326
|
-
const leftText = this.visit(binExpr.left,
|
|
337
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
327
338
|
let rightText = ts.isNumericLiteral(binExpr.right)
|
|
328
339
|
? binExpr.right.getText()
|
|
329
|
-
: this.visit(binExpr.right,
|
|
340
|
+
: this.visit(binExpr.right, visitContext);
|
|
330
341
|
if (ts.isIdentifier(binExpr.right)) {
|
|
331
342
|
const scope = this.getScopeForNode(binExpr.right);
|
|
332
343
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.right.getText(), scope);
|
|
333
|
-
rightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right),
|
|
344
|
+
rightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, typeInfo);
|
|
334
345
|
}
|
|
335
346
|
let target = leftText;
|
|
336
347
|
if (ts.isIdentifier(binExpr.left)) {
|
|
337
348
|
const scope = this.getScopeForNode(binExpr.left);
|
|
338
349
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.getText(), scope);
|
|
339
350
|
if (context.derefBeforeAssignment) {
|
|
340
|
-
target = this.getDerefCode(leftText, leftText,
|
|
351
|
+
target = this.getDerefCode(leftText, leftText, visitContext, typeInfo);
|
|
341
352
|
}
|
|
342
353
|
else if (typeInfo.needsHeapAllocation) {
|
|
343
354
|
target = `*${leftText}`;
|
|
@@ -345,8 +356,9 @@ export function visitBinaryExpression(node, context) {
|
|
|
345
356
|
}
|
|
346
357
|
return `${target} ${op} ${rightText}`;
|
|
347
358
|
}
|
|
359
|
+
// Assignment expression `a = 1`
|
|
348
360
|
if (opToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
349
|
-
let rightText = this.visit(binExpr.right,
|
|
361
|
+
let rightText = this.visit(binExpr.right, visitContext);
|
|
350
362
|
if (ts.isPropertyAccessExpression(binExpr.left)) {
|
|
351
363
|
const propAccess = binExpr.left;
|
|
352
364
|
if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
|
|
@@ -360,13 +372,13 @@ export function visitBinaryExpression(node, context) {
|
|
|
360
372
|
if (rightTypeInfo &&
|
|
361
373
|
!rightTypeInfo.isParameter &&
|
|
362
374
|
!rightTypeInfo.isBuiltin) {
|
|
363
|
-
finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right),
|
|
375
|
+
finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, rightTypeInfo);
|
|
364
376
|
}
|
|
365
377
|
}
|
|
366
378
|
// Approximate super assignment as setting property on 'this'
|
|
367
379
|
return `(${this.globalThisVar}).set_own_property("${propName}", ${finalRightText})`;
|
|
368
380
|
}
|
|
369
|
-
const objExprText = this.visit(propAccess.expression,
|
|
381
|
+
const objExprText = this.visit(propAccess.expression, visitContext);
|
|
370
382
|
const propName = propAccess.name.getText();
|
|
371
383
|
let finalObjExpr = objExprText;
|
|
372
384
|
if (ts.isIdentifier(propAccess.expression)) {
|
|
@@ -391,14 +403,14 @@ export function visitBinaryExpression(node, context) {
|
|
|
391
403
|
}
|
|
392
404
|
else if (ts.isElementAccessExpression(binExpr.left)) {
|
|
393
405
|
const elemAccess = binExpr.left;
|
|
394
|
-
const objExprText = this.visit(elemAccess.expression,
|
|
395
|
-
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 });
|
|
396
408
|
let finalObjExpr = objExprText;
|
|
397
409
|
if (ts.isIdentifier(elemAccess.expression)) {
|
|
398
410
|
const scope = this.getScopeForNode(elemAccess.expression);
|
|
399
411
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(elemAccess.expression.getText(), scope);
|
|
400
412
|
if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
|
|
401
|
-
finalObjExpr = this.getDerefCode(objExprText, this.getJsVarName(elemAccess.expression),
|
|
413
|
+
finalObjExpr = this.getDerefCode(objExprText, this.getJsVarName(elemAccess.expression), visitContext, typeInfo);
|
|
402
414
|
}
|
|
403
415
|
}
|
|
404
416
|
if (ts.isIdentifier(elemAccess.argumentExpression)) {
|
|
@@ -408,7 +420,7 @@ export function visitBinaryExpression(node, context) {
|
|
|
408
420
|
if (argTypeInfo &&
|
|
409
421
|
!argTypeInfo.isParameter &&
|
|
410
422
|
!argTypeInfo.isBuiltin) {
|
|
411
|
-
argText = this.getDerefCode(argText, this.getJsVarName(elemAccess.argumentExpression),
|
|
423
|
+
argText = this.getDerefCode(argText, this.getJsVarName(elemAccess.argumentExpression), visitContext, argTypeInfo);
|
|
412
424
|
}
|
|
413
425
|
}
|
|
414
426
|
let finalRightText = rightText;
|
|
@@ -419,12 +431,12 @@ export function visitBinaryExpression(node, context) {
|
|
|
419
431
|
if (rightTypeInfo &&
|
|
420
432
|
!rightTypeInfo.isParameter &&
|
|
421
433
|
!rightTypeInfo.isBuiltin) {
|
|
422
|
-
finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right),
|
|
434
|
+
finalRightText = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, rightTypeInfo);
|
|
423
435
|
}
|
|
424
436
|
}
|
|
425
437
|
return `${finalObjExpr}.set_own_property(${argText}, ${finalRightText})`;
|
|
426
438
|
}
|
|
427
|
-
const leftText = this.visit(binExpr.left,
|
|
439
|
+
const leftText = this.visit(binExpr.left, visitContext);
|
|
428
440
|
const scope = this.getScopeForNode(binExpr.left);
|
|
429
441
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(binExpr.left.text, scope);
|
|
430
442
|
if (!typeInfo && !this.isBuiltinObject(binExpr.left)) {
|
|
@@ -437,12 +449,28 @@ export function visitBinaryExpression(node, context) {
|
|
|
437
449
|
rightText = binExpr.right.getText();
|
|
438
450
|
}
|
|
439
451
|
const target = context.derefBeforeAssignment
|
|
440
|
-
? this.getDerefCode(leftText, leftText,
|
|
452
|
+
? this.getDerefCode(leftText, leftText, visitContext, typeInfo)
|
|
441
453
|
: (typeInfo.needsHeapAllocation ? `*${leftText}` : leftText);
|
|
454
|
+
// Update scope symbols on variable re-assignment
|
|
455
|
+
if (ts.isIdentifier(binExpr.left)) {
|
|
456
|
+
if (!ts.isFunctionDeclaration(binExpr.right)) {
|
|
457
|
+
if (context.localScopeSymbols.has(binExpr.left.text)) {
|
|
458
|
+
context.localScopeSymbols.update(binExpr.left.text, {
|
|
459
|
+
func: null,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
else if (context.globalScopeSymbols.has(binExpr.left.text)) {
|
|
463
|
+
context.globalScopeSymbols.update(binExpr.left.text, {
|
|
464
|
+
func: null,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
442
469
|
return `${target} ${op} ${rightText}`;
|
|
443
470
|
}
|
|
444
|
-
const leftText = this.visit(binExpr.left,
|
|
445
|
-
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
|
|
446
474
|
let finalLeft = leftText;
|
|
447
475
|
if (ts.isIdentifier(binExpr.left)) {
|
|
448
476
|
const scope = this.getScopeForNode(binExpr.left);
|
|
@@ -451,7 +479,7 @@ export function visitBinaryExpression(node, context) {
|
|
|
451
479
|
return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(binExpr.left)})`;
|
|
452
480
|
}
|
|
453
481
|
if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
|
|
454
|
-
finalLeft = this.getDerefCode(leftText, this.getJsVarName(binExpr.left),
|
|
482
|
+
finalLeft = this.getDerefCode(leftText, this.getJsVarName(binExpr.left), visitContext, typeInfo);
|
|
455
483
|
}
|
|
456
484
|
}
|
|
457
485
|
let finalRight = rightText;
|
|
@@ -463,7 +491,7 @@ export function visitBinaryExpression(node, context) {
|
|
|
463
491
|
return `jspp::Exception::throw_unresolved_reference(${this.getJsVarName(binExpr.right)})`;
|
|
464
492
|
}
|
|
465
493
|
if (typeInfo && !typeInfo.isParameter && !typeInfo.isBuiltin) {
|
|
466
|
-
finalRight = this.getDerefCode(rightText, this.getJsVarName(binExpr.right),
|
|
494
|
+
finalRight = this.getDerefCode(rightText, this.getJsVarName(binExpr.right), visitContext, typeInfo);
|
|
467
495
|
}
|
|
468
496
|
}
|
|
469
497
|
if (opToken.kind === ts.SyntaxKind.InKeyword) {
|
|
@@ -481,35 +509,63 @@ export function visitBinaryExpression(node, context) {
|
|
|
481
509
|
if (opToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
482
510
|
return `jspp::nullish_coalesce(${finalLeft}, ${finalRight})`;
|
|
483
511
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
|
491
543
|
if (opToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {
|
|
492
|
-
return `jspp::is_strictly_equal_to(${
|
|
544
|
+
return `jspp::is_strictly_equal_to(${literalLeft}, ${literalRight})`;
|
|
493
545
|
}
|
|
494
546
|
if (opToken.kind === ts.SyntaxKind.EqualsEqualsToken) {
|
|
495
|
-
return `jspp::is_equal_to(${
|
|
547
|
+
return `jspp::is_equal_to(${literalLeft}, ${literalRight})`;
|
|
496
548
|
}
|
|
497
549
|
if (opToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
|
|
498
|
-
return `jspp::not_strictly_equal_to(${
|
|
550
|
+
return `jspp::not_strictly_equal_to(${literalLeft}, ${literalRight})`;
|
|
499
551
|
}
|
|
500
552
|
if (opToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
|
|
501
|
-
return `jspp::not_equal_to(${
|
|
553
|
+
return `jspp::not_equal_to(${literalLeft}, ${literalRight})`;
|
|
502
554
|
}
|
|
503
555
|
if (opToken.kind === ts.SyntaxKind.AsteriskAsteriskToken) {
|
|
504
|
-
return `jspp::pow(${
|
|
556
|
+
return `jspp::pow(${literalLeft}, ${literalRight})`;
|
|
505
557
|
}
|
|
506
558
|
if (opToken.kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken) {
|
|
507
|
-
return `jspp::unsigned_right_shift(${
|
|
559
|
+
return `jspp::unsigned_right_shift(${literalLeft}, ${literalRight})`;
|
|
508
560
|
}
|
|
509
|
-
//
|
|
510
|
-
if (
|
|
511
|
-
|
|
561
|
+
// Use literal for at most one side
|
|
562
|
+
if (isLiteral(binExpr.left)) {
|
|
563
|
+
finalLeft = binExpr.left.getText();
|
|
512
564
|
}
|
|
565
|
+
else if (isLiteral(binExpr.right)) {
|
|
566
|
+
finalRight = binExpr.right.getText();
|
|
567
|
+
}
|
|
568
|
+
// For other arithmetic and bitwise operations, use native operations if possible
|
|
513
569
|
if (op === "+" ||
|
|
514
570
|
op === "-" ||
|
|
515
571
|
op === "*" ||
|
|
@@ -526,7 +582,12 @@ export function visitBinaryExpression(node, context) {
|
|
|
526
582
|
}
|
|
527
583
|
export function visitConditionalExpression(node, context) {
|
|
528
584
|
const condExpr = node;
|
|
529
|
-
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
|
+
});
|
|
530
591
|
const whenTrueStmt = this.visit(condExpr.whenTrue, {
|
|
531
592
|
...context,
|
|
532
593
|
isFunctionBody: false,
|
|
@@ -535,14 +596,17 @@ export function visitConditionalExpression(node, context) {
|
|
|
535
596
|
...context,
|
|
536
597
|
isFunctionBody: false,
|
|
537
598
|
});
|
|
538
|
-
|
|
599
|
+
if (isBinaryExpression) {
|
|
600
|
+
return `${condition} ? ${whenTrueStmt} : ${whenFalseStmt}`;
|
|
601
|
+
}
|
|
602
|
+
return `jspp::is_truthy(${condition}) ? ${whenTrueStmt} : ${whenFalseStmt}`;
|
|
539
603
|
}
|
|
540
604
|
export function visitCallExpression(node, context) {
|
|
541
605
|
const callExpr = node;
|
|
542
606
|
const callee = callExpr.expression;
|
|
543
607
|
if (callee.kind === ts.SyntaxKind.SuperKeyword) {
|
|
544
608
|
if (!context.superClassVar) {
|
|
545
|
-
throw new
|
|
609
|
+
throw new CompilerError("super() called but no super class variable found in context", callee, "SyntaxError");
|
|
546
610
|
}
|
|
547
611
|
const args = callExpr.arguments.map((arg) => this.visit(arg, context))
|
|
548
612
|
.join(", ");
|
|
@@ -573,7 +637,7 @@ export function visitCallExpression(node, context) {
|
|
|
573
637
|
const propAccess = callee;
|
|
574
638
|
if (propAccess.expression.kind === ts.SyntaxKind.SuperKeyword) {
|
|
575
639
|
if (!context.superClassVar) {
|
|
576
|
-
throw new
|
|
640
|
+
throw new CompilerError("super.method() called but no super class variable found in context", propAccess.expression, "SyntaxError");
|
|
577
641
|
}
|
|
578
642
|
const propName = propAccess.name.getText();
|
|
579
643
|
return `(${context.superClassVar}).get_own_property("prototype").get_own_property("${propName}").call(${this.globalThisVar}, ${argsSpan}, "${this.escapeString(propName)}")`;
|
|
@@ -645,6 +709,21 @@ export function visitCallExpression(node, context) {
|
|
|
645
709
|
derefCallee = calleeCode;
|
|
646
710
|
}
|
|
647
711
|
else if (typeInfo) {
|
|
712
|
+
const name = callee.getText();
|
|
713
|
+
const symbol = context.localScopeSymbols.get(name) ??
|
|
714
|
+
context.globalScopeSymbols.get(name);
|
|
715
|
+
// Optimization: Direct lambda call
|
|
716
|
+
if (symbol && symbol.func?.nativeName) {
|
|
717
|
+
const callExpr = `${symbol.func.nativeName}(jspp::Constants::UNDEFINED, ${argsSpan})`;
|
|
718
|
+
if (symbol.func.isGenerator) {
|
|
719
|
+
if (symbol.func.isAsync) {
|
|
720
|
+
return `jspp::AnyValue::from_async_iterator(${callExpr})`;
|
|
721
|
+
}
|
|
722
|
+
return `jspp::AnyValue::from_iterator(${callExpr})`;
|
|
723
|
+
}
|
|
724
|
+
return callExpr;
|
|
725
|
+
}
|
|
726
|
+
// AnyValue function call
|
|
648
727
|
derefCallee = this.getDerefCode(calleeCode, this.getJsVarName(callee), context, typeInfo);
|
|
649
728
|
}
|
|
650
729
|
}
|
|
@@ -792,3 +871,15 @@ export function visitDeleteExpression(node, context) {
|
|
|
792
871
|
}
|
|
793
872
|
return "jspp::Constants::TRUE"; // delete on non-property is true in JS
|
|
794
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,12 +1,13 @@
|
|
|
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) {
|
|
6
|
-
const isAssignment = options?.isAssignment || false;
|
|
7
7
|
const capture = options?.capture || "[=]";
|
|
8
|
+
const nativeName = options?.nativeName;
|
|
8
9
|
const declaredSymbols = this.getDeclaredSymbols(node);
|
|
9
|
-
const argsName = this.generateUniqueName("__args_", declaredSymbols);
|
|
10
|
+
const argsName = this.generateUniqueName("__args_", declaredSymbols, context.globalScopeSymbols, context.localScopeSymbols);
|
|
10
11
|
const isInsideGeneratorFunction = this.isGeneratorFunction(node);
|
|
11
12
|
const isInsideAsyncFunction = this.isAsyncFunction(node);
|
|
12
13
|
const returnCmd = this.getReturnCommand({
|
|
@@ -23,26 +24,25 @@ export function generateLambda(node, context, options) {
|
|
|
23
24
|
// For generators/async, we manually copy them inside the body.
|
|
24
25
|
const paramThisType = "const jspp::AnyValue&";
|
|
25
26
|
const paramArgsType = "std::span<const jspp::AnyValue>";
|
|
27
|
+
const selfParamPart = nativeName ? `this auto&& ${nativeName}, ` : "";
|
|
28
|
+
const mutablePart = nativeName ? "" : "mutable ";
|
|
26
29
|
const thisArgParam = isArrow
|
|
27
30
|
? "const jspp::AnyValue&" // Arrow functions use captured 'this' or are never generators in this parser context
|
|
28
31
|
: `${paramThisType} ${this.globalThisVar}`;
|
|
29
|
-
let lambda = `${capture}(${thisArgParam}, ${paramArgsType} ${argsName})
|
|
30
|
-
const
|
|
32
|
+
let lambda = `${capture}(${selfParamPart}${thisArgParam}, ${paramArgsType} ${argsName}) ${mutablePart}-> ${funcReturnType} `;
|
|
33
|
+
const globalScopeSymbols = this.prepareScopeSymbolsForVisit(context.globalScopeSymbols, context.localScopeSymbols);
|
|
31
34
|
const visitContext = {
|
|
35
|
+
currentScopeNode: context.currentScopeNode,
|
|
32
36
|
isMainContext: false,
|
|
33
37
|
isInsideFunction: true,
|
|
34
38
|
isFunctionBody: false,
|
|
35
39
|
lambdaName: undefined,
|
|
36
|
-
|
|
40
|
+
globalScopeSymbols,
|
|
37
41
|
localScopeSymbols: new DeclaredSymbols(),
|
|
38
42
|
superClassVar: context.superClassVar,
|
|
39
43
|
isInsideGeneratorFunction: isInsideGeneratorFunction,
|
|
40
44
|
isInsideAsyncFunction: isInsideAsyncFunction,
|
|
41
45
|
};
|
|
42
|
-
// Name of 'this' and 'args' to be used in the body.
|
|
43
|
-
// If generator/async, we use the copied versions.
|
|
44
|
-
let bodyThisVar = this.globalThisVar;
|
|
45
|
-
let bodyArgsVar = argsName;
|
|
46
46
|
// Preamble to copy arguments for coroutines
|
|
47
47
|
let preamble = "";
|
|
48
48
|
if (isInsideGeneratorFunction || isInsideAsyncFunction) {
|
|
@@ -51,40 +51,9 @@ export function generateLambda(node, context, options) {
|
|
|
51
51
|
if (!isArrow) {
|
|
52
52
|
preamble +=
|
|
53
53
|
`${this.indent()}jspp::AnyValue ${thisCopy} = ${this.globalThisVar};\n`;
|
|
54
|
-
bodyThisVar = thisCopy;
|
|
55
54
|
}
|
|
56
55
|
preamble +=
|
|
57
56
|
`${this.indent()}std::vector<jspp::AnyValue> ${argsCopy}(${argsName}.begin(), ${argsName}.end());\n`;
|
|
58
|
-
bodyArgsVar = argsCopy;
|
|
59
|
-
// Update visitContext to use the new 'this' variable if needed?
|
|
60
|
-
// CodeGenerator.globalThisVar is a member of the class, so we can't easily change it for the recursive visit
|
|
61
|
-
// without changing the class state or passing it down.
|
|
62
|
-
// BUT: visitThisKeyword uses `this.globalThisVar`.
|
|
63
|
-
// We need to temporarily swap `this.globalThisVar` or handle it.
|
|
64
|
-
// Actually, `this.globalThisVar` is set once in `generate`.
|
|
65
|
-
// Wait, `visitThisKeyword` uses `this.globalThisVar`.
|
|
66
|
-
// If we change the variable name for 'this', we must ensure `visitThisKeyword` generates the correct name.
|
|
67
|
-
// `generateLambda` does NOT create a new CodeGenerator instance, so `this.globalThisVar` is the global one.
|
|
68
|
-
//
|
|
69
|
-
// We need to support shadowing `globalThisVar` for the duration of the visit.
|
|
70
|
-
// The current `CodeGenerator` doesn't seem to support changing `globalThisVar` recursively easily
|
|
71
|
-
// because it's a property of the class, not `VisitContext`.
|
|
72
|
-
//
|
|
73
|
-
// However, `visitFunctionDeclaration` etc don't update `globalThisVar`.
|
|
74
|
-
// `visitThisKeyword` just returns `this.globalThisVar`.
|
|
75
|
-
//
|
|
76
|
-
// If we are inside a function, `this` should refer to the function's `this`.
|
|
77
|
-
// `this.globalThisVar` is generated in `generate()`: `__this_val__...`.
|
|
78
|
-
// And `generateLambda` uses `this.globalThisVar` as the parameter name.
|
|
79
|
-
//
|
|
80
|
-
// So, if we copy it to `__this_copy`, `visitThisKeyword` will still print `__this_val__...`.
|
|
81
|
-
// This is BAD for generators because `__this_val__...` is a reference that dies.
|
|
82
|
-
//
|
|
83
|
-
// FIX: We must reuse the SAME name for the local copy, and shadow the parameter.
|
|
84
|
-
// But we can't declare a variable with the same name as the parameter in the same scope.
|
|
85
|
-
//
|
|
86
|
-
// We can rename the PARAMETER to something else, and declare the local variable with `this.globalThisVar`.
|
|
87
|
-
//
|
|
88
57
|
}
|
|
89
58
|
// Adjust parameter names for generator/async to allow shadowing/copying
|
|
90
59
|
let finalThisParamName = isArrow ? "" : this.globalThisVar;
|
|
@@ -100,7 +69,7 @@ export function generateLambda(node, context, options) {
|
|
|
100
69
|
: `${paramThisType} ${finalThisParamName}`;
|
|
101
70
|
// Re-construct lambda header with potentially new param names
|
|
102
71
|
lambda =
|
|
103
|
-
`${capture}(${thisArgParamFinal}, ${paramArgsType} ${finalArgsParamName})
|
|
72
|
+
`${capture}(${selfParamPart}${thisArgParamFinal}, ${paramArgsType} ${finalArgsParamName}) ${mutablePart}-> ${funcReturnType} `;
|
|
104
73
|
// Regenerate preamble
|
|
105
74
|
preamble = "";
|
|
106
75
|
if (isInsideGeneratorFunction || isInsideAsyncFunction) {
|
|
@@ -116,17 +85,27 @@ export function generateLambda(node, context, options) {
|
|
|
116
85
|
// So subsequent code using `argsName` and `visit` (using `globalThisVar`) works correctly.
|
|
117
86
|
const paramExtractor = (parameters) => {
|
|
118
87
|
let code = "";
|
|
119
|
-
parameters.
|
|
88
|
+
parameters.filter((p) => this.isTypescript && p.name.getText() === "this" ? false : true // Ignore "this" parameters
|
|
89
|
+
).forEach((p, i) => {
|
|
120
90
|
const name = p.name.getText();
|
|
121
91
|
const defaultValue = p.initializer
|
|
122
92
|
? this.visit(p.initializer, visitContext)
|
|
123
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
|
+
});
|
|
124
103
|
const scope = this.getScopeForNode(p);
|
|
125
104
|
const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
|
|
126
105
|
// Handle rest parameters
|
|
127
106
|
if (!!p.dotDotDotToken) {
|
|
128
107
|
if (parameters.length - 1 !== i) {
|
|
129
|
-
throw new
|
|
108
|
+
throw new CompilerError("Rest parameter must be last formal parameter.", p, "SyntaxError");
|
|
130
109
|
}
|
|
131
110
|
if (typeInfo.needsHeapAllocation) {
|
|
132
111
|
code +=
|
|
@@ -178,7 +157,7 @@ export function generateLambda(node, context, options) {
|
|
|
178
157
|
// Hoist var declarations in the function body
|
|
179
158
|
const varDecls = collectFunctionScopedDeclarations(node.body);
|
|
180
159
|
varDecls.forEach((decl) => {
|
|
181
|
-
preamble += this.hoistDeclaration(decl, visitContext.localScopeSymbols);
|
|
160
|
+
preamble += this.hoistDeclaration(decl, visitContext.localScopeSymbols, node.body);
|
|
182
161
|
});
|
|
183
162
|
this.indentationLevel++;
|
|
184
163
|
const paramExtraction = paramExtractor(node.parameters);
|
|
@@ -215,34 +194,49 @@ export function generateLambda(node, context, options) {
|
|
|
215
194
|
else {
|
|
216
195
|
lambda += `{ ${preamble} ${returnCmd} jspp::Constants::UNDEFINED; }\n`;
|
|
217
196
|
}
|
|
218
|
-
|
|
219
|
-
|
|
197
|
+
// Return only lambda if required
|
|
198
|
+
if (options?.generateOnlyLambda) {
|
|
199
|
+
return lambda.trimEnd();
|
|
200
|
+
}
|
|
201
|
+
return this.generateFullLambdaExpression(node, context, lambda, options);
|
|
202
|
+
}
|
|
203
|
+
export function generateFullLambdaExpression(node, context, lambda, options) {
|
|
204
|
+
const isAssignment = options?.isAssignment || false;
|
|
205
|
+
const noTypeSignature = options?.noTypeSignature || false;
|
|
206
|
+
const isInsideGeneratorFunction = this.isGeneratorFunction(node);
|
|
207
|
+
const isInsideAsyncFunction = this.isAsyncFunction(node);
|
|
208
|
+
const isArrow = ts.isArrowFunction(node);
|
|
209
|
+
let callable = lambda;
|
|
220
210
|
let method = "";
|
|
221
211
|
// Handle generator function
|
|
222
212
|
if (isInsideGeneratorFunction) {
|
|
223
213
|
if (isInsideAsyncFunction) {
|
|
224
|
-
|
|
225
|
-
"jspp::JsAsyncIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
226
|
-
|
|
214
|
+
if (!noTypeSignature) {
|
|
215
|
+
const signature = "jspp::JsAsyncIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
216
|
+
callable = `std::function<${signature}>(${lambda})`;
|
|
217
|
+
}
|
|
227
218
|
method = `jspp::AnyValue::make_async_generator`;
|
|
228
219
|
}
|
|
229
220
|
else {
|
|
230
|
-
|
|
231
|
-
"jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
232
|
-
|
|
221
|
+
if (!noTypeSignature) {
|
|
222
|
+
const signature = "jspp::JsIterator<jspp::AnyValue>(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
223
|
+
callable = `std::function<${signature}>(${lambda})`;
|
|
224
|
+
}
|
|
233
225
|
method = `jspp::AnyValue::make_generator`;
|
|
234
226
|
}
|
|
235
227
|
} // Handle async function
|
|
236
228
|
else if (isInsideAsyncFunction) {
|
|
237
|
-
|
|
238
|
-
"jspp::JsPromise(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
239
|
-
|
|
229
|
+
if (!noTypeSignature) {
|
|
230
|
+
const signature = "jspp::JsPromise(const jspp::AnyValue&, std::span<const jspp::AnyValue>)";
|
|
231
|
+
callable = `std::function<${signature}>(${lambda})`;
|
|
232
|
+
}
|
|
240
233
|
method = `jspp::AnyValue::make_async_function`;
|
|
241
234
|
} // Handle normal function
|
|
242
235
|
else {
|
|
243
|
-
|
|
244
|
-
`jspp::AnyValue(const jspp::AnyValue&, std::span<const jspp::AnyValue>)`;
|
|
245
|
-
|
|
236
|
+
if (!noTypeSignature) {
|
|
237
|
+
const signature = `jspp::AnyValue(const jspp::AnyValue&, std::span<const jspp::AnyValue>)`;
|
|
238
|
+
callable = `std::function<${signature}>(${lambda})`;
|
|
239
|
+
}
|
|
246
240
|
if (options?.isClass) {
|
|
247
241
|
method = `jspp::AnyValue::make_class`;
|
|
248
242
|
}
|