littlewing 0.5.2 → 0.6.0
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 +145 -682
- package/dist/index.d.ts +36 -62
- package/dist/index.js +82 -513
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,8 +15,8 @@ var exports_ast = {};
|
|
|
15
15
|
__export(exports_ast, {
|
|
16
16
|
unaryOp: () => unaryOp,
|
|
17
17
|
subtract: () => subtract,
|
|
18
|
+
program: () => program,
|
|
18
19
|
number: () => number,
|
|
19
|
-
nullishAssign: () => nullishAssign,
|
|
20
20
|
notEquals: () => notEquals,
|
|
21
21
|
negate: () => negate,
|
|
22
22
|
multiply: () => multiply,
|
|
@@ -37,6 +37,12 @@ __export(exports_ast, {
|
|
|
37
37
|
assign: () => assign,
|
|
38
38
|
add: () => add
|
|
39
39
|
});
|
|
40
|
+
function program(statements) {
|
|
41
|
+
return {
|
|
42
|
+
type: "Program",
|
|
43
|
+
statements
|
|
44
|
+
};
|
|
45
|
+
}
|
|
40
46
|
function number(value) {
|
|
41
47
|
return {
|
|
42
48
|
type: "NumberLiteral",
|
|
@@ -78,13 +84,6 @@ function assign(name, value) {
|
|
|
78
84
|
value
|
|
79
85
|
};
|
|
80
86
|
}
|
|
81
|
-
function nullishAssign(name, value) {
|
|
82
|
-
return {
|
|
83
|
-
type: "NullishAssignment",
|
|
84
|
-
name,
|
|
85
|
-
value
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
87
|
function conditional(condition, consequent, alternate) {
|
|
89
88
|
return {
|
|
90
89
|
type: "ConditionalExpression",
|
|
@@ -163,7 +162,6 @@ var TokenType;
|
|
|
163
162
|
TokenType2["COMMA"] = "COMMA";
|
|
164
163
|
TokenType2["QUESTION"] = "QUESTION";
|
|
165
164
|
TokenType2["COLON"] = "COLON";
|
|
166
|
-
TokenType2["NULLISH_ASSIGN"] = "NULLISH_ASSIGN";
|
|
167
165
|
TokenType2["EOF"] = "EOF";
|
|
168
166
|
})(TokenType ||= {});
|
|
169
167
|
function isNumberLiteral(node) {
|
|
@@ -190,9 +188,6 @@ function isProgram(node) {
|
|
|
190
188
|
function isConditionalExpression(node) {
|
|
191
189
|
return node.type === "ConditionalExpression";
|
|
192
190
|
}
|
|
193
|
-
function isNullishAssignment(node) {
|
|
194
|
-
return node.type === "NullishAssignment";
|
|
195
|
-
}
|
|
196
191
|
|
|
197
192
|
// src/utils.ts
|
|
198
193
|
function evaluateBinaryOperation(operator, left, right) {
|
|
@@ -264,7 +259,6 @@ function getOperatorPrecedence(operator) {
|
|
|
264
259
|
function getTokenPrecedence(type) {
|
|
265
260
|
switch (type) {
|
|
266
261
|
case "EQUALS" /* EQUALS */:
|
|
267
|
-
case "NULLISH_ASSIGN" /* NULLISH_ASSIGN */:
|
|
268
262
|
return 1;
|
|
269
263
|
case "QUESTION" /* QUESTION */:
|
|
270
264
|
return 2;
|
|
@@ -310,8 +304,6 @@ class CodeGenerator {
|
|
|
310
304
|
return this.generateFunctionCall(node);
|
|
311
305
|
if (isAssignment(node))
|
|
312
306
|
return this.generateAssignment(node);
|
|
313
|
-
if (isNullishAssignment(node))
|
|
314
|
-
return this.generateNullishAssignment(node);
|
|
315
307
|
if (isConditionalExpression(node))
|
|
316
308
|
return this.generateConditionalExpression(node);
|
|
317
309
|
throw new Error(`Unknown node type`);
|
|
@@ -348,10 +340,6 @@ class CodeGenerator {
|
|
|
348
340
|
const value = this.generate(node.value);
|
|
349
341
|
return `${node.name} = ${value}`;
|
|
350
342
|
}
|
|
351
|
-
generateNullishAssignment(node) {
|
|
352
|
-
const value = this.generate(node.value);
|
|
353
|
-
return `${node.name} ??= ${value}`;
|
|
354
|
-
}
|
|
355
343
|
generateConditionalExpression(node) {
|
|
356
344
|
const condition = this.generate(node.condition);
|
|
357
345
|
const consequent = this.generate(node.consequent);
|
|
@@ -422,8 +410,10 @@ var defaultContext = {
|
|
|
422
410
|
class Lexer {
|
|
423
411
|
source;
|
|
424
412
|
position = 0;
|
|
413
|
+
length;
|
|
425
414
|
constructor(source) {
|
|
426
415
|
this.source = source;
|
|
416
|
+
this.length = source.length;
|
|
427
417
|
}
|
|
428
418
|
tokenize() {
|
|
429
419
|
const tokens = [];
|
|
@@ -438,14 +428,23 @@ class Lexer {
|
|
|
438
428
|
}
|
|
439
429
|
nextToken() {
|
|
440
430
|
this.skipWhitespaceAndComments();
|
|
441
|
-
if (this.position >= this.
|
|
431
|
+
if (this.position >= this.length) {
|
|
432
|
+
return { type: "EOF" /* EOF */, value: "", position: this.position };
|
|
433
|
+
}
|
|
434
|
+
const char = this.source[this.position];
|
|
435
|
+
if (char === undefined) {
|
|
442
436
|
return { type: "EOF" /* EOF */, value: "", position: this.position };
|
|
443
437
|
}
|
|
444
|
-
const char = this.getCharAt(this.position);
|
|
445
438
|
const start = this.position;
|
|
446
439
|
if (this.isDigit(char)) {
|
|
447
440
|
return this.readNumber();
|
|
448
441
|
}
|
|
442
|
+
if (char === ".") {
|
|
443
|
+
const nextChar = this.source[this.position + 1];
|
|
444
|
+
if (nextChar !== undefined && this.isDigit(nextChar)) {
|
|
445
|
+
return this.readNumber();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
449
448
|
if (this.isLetter(char) || char === "_") {
|
|
450
449
|
return this.readIdentifier();
|
|
451
450
|
}
|
|
@@ -475,41 +474,33 @@ class Lexer {
|
|
|
475
474
|
this.position++;
|
|
476
475
|
return { type: "RPAREN" /* RPAREN */, value: ")", position: start };
|
|
477
476
|
case "=":
|
|
478
|
-
if (this.
|
|
477
|
+
if (this.source[this.position + 1] === "=") {
|
|
479
478
|
this.position += 2;
|
|
480
479
|
return { type: "DOUBLE_EQUALS" /* DOUBLE_EQUALS */, value: "==", position: start };
|
|
481
480
|
}
|
|
482
481
|
this.position++;
|
|
483
482
|
return { type: "EQUALS" /* EQUALS */, value: "=", position: start };
|
|
484
483
|
case "!":
|
|
485
|
-
if (this.
|
|
484
|
+
if (this.source[this.position + 1] === "=") {
|
|
486
485
|
this.position += 2;
|
|
487
486
|
return { type: "NOT_EQUALS" /* NOT_EQUALS */, value: "!=", position: start };
|
|
488
487
|
}
|
|
489
488
|
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
490
489
|
case "<":
|
|
491
|
-
if (this.
|
|
490
|
+
if (this.source[this.position + 1] === "=") {
|
|
492
491
|
this.position += 2;
|
|
493
492
|
return { type: "LESS_EQUAL" /* LESS_EQUAL */, value: "<=", position: start };
|
|
494
493
|
}
|
|
495
494
|
this.position++;
|
|
496
495
|
return { type: "LESS_THAN" /* LESS_THAN */, value: "<", position: start };
|
|
497
496
|
case ">":
|
|
498
|
-
if (this.
|
|
497
|
+
if (this.source[this.position + 1] === "=") {
|
|
499
498
|
this.position += 2;
|
|
500
499
|
return { type: "GREATER_EQUAL" /* GREATER_EQUAL */, value: ">=", position: start };
|
|
501
500
|
}
|
|
502
501
|
this.position++;
|
|
503
502
|
return { type: "GREATER_THAN" /* GREATER_THAN */, value: ">", position: start };
|
|
504
503
|
case "?":
|
|
505
|
-
if (this.peek() === "?" && this.peekAhead(2) === "=") {
|
|
506
|
-
this.position += 3;
|
|
507
|
-
return {
|
|
508
|
-
type: "NULLISH_ASSIGN" /* NULLISH_ASSIGN */,
|
|
509
|
-
value: "??=",
|
|
510
|
-
position: start
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
504
|
this.position++;
|
|
514
505
|
return { type: "QUESTION" /* QUESTION */, value: "?", position: start };
|
|
515
506
|
case ":":
|
|
@@ -522,13 +513,13 @@ class Lexer {
|
|
|
522
513
|
this.position++;
|
|
523
514
|
return this.nextToken();
|
|
524
515
|
case "&":
|
|
525
|
-
if (this.
|
|
516
|
+
if (this.source[this.position + 1] === "&") {
|
|
526
517
|
this.position += 2;
|
|
527
518
|
return { type: "LOGICAL_AND" /* LOGICAL_AND */, value: "&&", position: start };
|
|
528
519
|
}
|
|
529
520
|
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
530
521
|
case "|":
|
|
531
|
-
if (this.
|
|
522
|
+
if (this.source[this.position + 1] === "|") {
|
|
532
523
|
this.position += 2;
|
|
533
524
|
return { type: "LOGICAL_OR" /* LOGICAL_OR */, value: "||", position: start };
|
|
534
525
|
}
|
|
@@ -538,14 +529,15 @@ class Lexer {
|
|
|
538
529
|
}
|
|
539
530
|
}
|
|
540
531
|
skipWhitespaceAndComments() {
|
|
541
|
-
while (this.position < this.
|
|
542
|
-
const char = this.
|
|
532
|
+
while (this.position < this.length) {
|
|
533
|
+
const char = this.source[this.position];
|
|
543
534
|
if (this.isWhitespace(char)) {
|
|
544
535
|
this.position++;
|
|
545
536
|
continue;
|
|
546
537
|
}
|
|
547
|
-
if (char === "/" && this.
|
|
548
|
-
|
|
538
|
+
if (char === "/" && this.source[this.position + 1] === "/") {
|
|
539
|
+
this.position += 2;
|
|
540
|
+
while (this.position < this.length && this.source[this.position] !== `
|
|
549
541
|
`) {
|
|
550
542
|
this.position++;
|
|
551
543
|
}
|
|
@@ -558,8 +550,12 @@ class Lexer {
|
|
|
558
550
|
const start = this.position;
|
|
559
551
|
let hasDecimal = false;
|
|
560
552
|
let hasExponent = false;
|
|
561
|
-
|
|
562
|
-
|
|
553
|
+
if (this.source[this.position] === ".") {
|
|
554
|
+
hasDecimal = true;
|
|
555
|
+
this.position++;
|
|
556
|
+
}
|
|
557
|
+
while (this.position < this.length) {
|
|
558
|
+
const char = this.source[this.position];
|
|
563
559
|
if (this.isDigit(char)) {
|
|
564
560
|
this.position++;
|
|
565
561
|
} else if (char === "." && !hasDecimal && !hasExponent) {
|
|
@@ -568,14 +564,15 @@ class Lexer {
|
|
|
568
564
|
} else if ((char === "e" || char === "E") && !hasExponent) {
|
|
569
565
|
hasExponent = true;
|
|
570
566
|
this.position++;
|
|
571
|
-
const nextChar = this.
|
|
567
|
+
const nextChar = this.source[this.position];
|
|
572
568
|
if (nextChar === "+" || nextChar === "-") {
|
|
573
569
|
this.position++;
|
|
574
570
|
}
|
|
575
|
-
|
|
571
|
+
const digitChar = this.source[this.position];
|
|
572
|
+
if (digitChar === undefined || !this.isDigit(digitChar)) {
|
|
576
573
|
throw new Error(`Invalid number: expected digit after exponent at position ${this.position}`);
|
|
577
574
|
}
|
|
578
|
-
while (this.position < this.
|
|
575
|
+
while (this.position < this.length && this.isDigit(this.source[this.position])) {
|
|
579
576
|
this.position++;
|
|
580
577
|
}
|
|
581
578
|
break;
|
|
@@ -588,8 +585,8 @@ class Lexer {
|
|
|
588
585
|
}
|
|
589
586
|
readIdentifier() {
|
|
590
587
|
const start = this.position;
|
|
591
|
-
while (this.position < this.
|
|
592
|
-
const char = this.
|
|
588
|
+
while (this.position < this.length) {
|
|
589
|
+
const char = this.source[this.position];
|
|
593
590
|
if (this.isLetter(char) || this.isDigit(char) || char === "_") {
|
|
594
591
|
this.position++;
|
|
595
592
|
} else {
|
|
@@ -599,24 +596,15 @@ class Lexer {
|
|
|
599
596
|
const name = this.source.slice(start, this.position);
|
|
600
597
|
return { type: "IDENTIFIER" /* IDENTIFIER */, value: name, position: start };
|
|
601
598
|
}
|
|
602
|
-
getCharAt(pos) {
|
|
603
|
-
return pos < this.source.length ? this.source[pos] || "" : "";
|
|
604
|
-
}
|
|
605
|
-
peek() {
|
|
606
|
-
return this.getCharAt(this.position + 1);
|
|
607
|
-
}
|
|
608
|
-
peekAhead(n) {
|
|
609
|
-
return this.getCharAt(this.position + n);
|
|
610
|
-
}
|
|
611
599
|
isDigit(char) {
|
|
612
|
-
return char >= "0" && char <= "9";
|
|
600
|
+
return char !== undefined && char >= "0" && char <= "9";
|
|
613
601
|
}
|
|
614
602
|
isLetter(char) {
|
|
615
|
-
return char >= "a" && char <= "z" || char >= "A" && char <= "Z";
|
|
603
|
+
return char !== undefined && (char >= "a" && char <= "z" || char >= "A" && char <= "Z");
|
|
616
604
|
}
|
|
617
605
|
isWhitespace(char) {
|
|
618
|
-
return char === " " || char === "\t" || char === `
|
|
619
|
-
` || char === "\r";
|
|
606
|
+
return char !== undefined && (char === " " || char === "\t" || char === `
|
|
607
|
+
` || char === "\r");
|
|
620
608
|
}
|
|
621
609
|
}
|
|
622
610
|
|
|
@@ -653,16 +641,13 @@ class Parser {
|
|
|
653
641
|
throw new Error("Empty program");
|
|
654
642
|
}
|
|
655
643
|
if (statements.length === 1) {
|
|
656
|
-
const
|
|
657
|
-
if (
|
|
658
|
-
throw new Error("Unexpected
|
|
644
|
+
const singleStatement = statements[0];
|
|
645
|
+
if (singleStatement === undefined) {
|
|
646
|
+
throw new Error("Unexpected empty statements array");
|
|
659
647
|
}
|
|
660
|
-
return
|
|
648
|
+
return singleStatement;
|
|
661
649
|
}
|
|
662
|
-
return
|
|
663
|
-
type: "Program",
|
|
664
|
-
statements
|
|
665
|
-
};
|
|
650
|
+
return program(statements);
|
|
666
651
|
}
|
|
667
652
|
parseExpression(minPrecedence) {
|
|
668
653
|
let left = this.parsePrefix();
|
|
@@ -679,23 +664,7 @@ class Parser {
|
|
|
679
664
|
const identName = left.name;
|
|
680
665
|
this.advance();
|
|
681
666
|
const value = this.parseExpression(precedence);
|
|
682
|
-
left =
|
|
683
|
-
type: "Assignment",
|
|
684
|
-
name: identName,
|
|
685
|
-
value
|
|
686
|
-
};
|
|
687
|
-
} else if (token.type === "NULLISH_ASSIGN" /* NULLISH_ASSIGN */) {
|
|
688
|
-
if (left.type !== "Identifier") {
|
|
689
|
-
throw new Error("Invalid assignment target");
|
|
690
|
-
}
|
|
691
|
-
const identName = left.name;
|
|
692
|
-
this.advance();
|
|
693
|
-
const value = this.parseExpression(precedence);
|
|
694
|
-
left = {
|
|
695
|
-
type: "NullishAssignment",
|
|
696
|
-
name: identName,
|
|
697
|
-
value
|
|
698
|
-
};
|
|
667
|
+
left = assign(identName, value);
|
|
699
668
|
} else if (token.type === "QUESTION" /* QUESTION */) {
|
|
700
669
|
this.advance();
|
|
701
670
|
const consequent = this.parseExpression(0);
|
|
@@ -704,22 +673,12 @@ class Parser {
|
|
|
704
673
|
}
|
|
705
674
|
this.advance();
|
|
706
675
|
const alternate = this.parseExpression(precedence);
|
|
707
|
-
left =
|
|
708
|
-
type: "ConditionalExpression",
|
|
709
|
-
condition: left,
|
|
710
|
-
consequent,
|
|
711
|
-
alternate
|
|
712
|
-
};
|
|
676
|
+
left = conditional(left, consequent, alternate);
|
|
713
677
|
} else if (this.isBinaryOperator(token.type)) {
|
|
714
678
|
const operator = token.value;
|
|
715
679
|
this.advance();
|
|
716
680
|
const right = this.parseExpression(precedence + 1);
|
|
717
|
-
left =
|
|
718
|
-
type: "BinaryOp",
|
|
719
|
-
left,
|
|
720
|
-
operator,
|
|
721
|
-
right
|
|
722
|
-
};
|
|
681
|
+
left = binaryOp(left, operator, right);
|
|
723
682
|
} else {
|
|
724
683
|
break;
|
|
725
684
|
}
|
|
@@ -731,11 +690,7 @@ class Parser {
|
|
|
731
690
|
if (token.type === "MINUS" /* MINUS */) {
|
|
732
691
|
this.advance();
|
|
733
692
|
const argument = this.parseExpression(this.getUnaryPrecedence());
|
|
734
|
-
return
|
|
735
|
-
type: "UnaryOp",
|
|
736
|
-
operator: "-",
|
|
737
|
-
argument
|
|
738
|
-
};
|
|
693
|
+
return unaryOp(argument);
|
|
739
694
|
}
|
|
740
695
|
if (token.type === "LPAREN" /* LPAREN */) {
|
|
741
696
|
this.advance();
|
|
@@ -748,10 +703,7 @@ class Parser {
|
|
|
748
703
|
}
|
|
749
704
|
if (token.type === "NUMBER" /* NUMBER */) {
|
|
750
705
|
this.advance();
|
|
751
|
-
return
|
|
752
|
-
type: "NumberLiteral",
|
|
753
|
-
value: token.value
|
|
754
|
-
};
|
|
706
|
+
return number(token.value);
|
|
755
707
|
}
|
|
756
708
|
if (token.type === "IDENTIFIER" /* IDENTIFIER */) {
|
|
757
709
|
const name = token.value;
|
|
@@ -763,16 +715,9 @@ class Parser {
|
|
|
763
715
|
throw new Error("Expected closing parenthesis");
|
|
764
716
|
}
|
|
765
717
|
this.advance();
|
|
766
|
-
return
|
|
767
|
-
type: "FunctionCall",
|
|
768
|
-
name,
|
|
769
|
-
arguments: args
|
|
770
|
-
};
|
|
718
|
+
return functionCall(name, args);
|
|
771
719
|
}
|
|
772
|
-
return
|
|
773
|
-
type: "Identifier",
|
|
774
|
-
name
|
|
775
|
-
};
|
|
720
|
+
return identifier(name);
|
|
776
721
|
}
|
|
777
722
|
throw new Error(`Unexpected token: ${token.value}`);
|
|
778
723
|
}
|
|
@@ -819,9 +764,11 @@ function parseSource(source) {
|
|
|
819
764
|
class Executor {
|
|
820
765
|
context;
|
|
821
766
|
variables;
|
|
767
|
+
externalVariables;
|
|
822
768
|
constructor(context = {}) {
|
|
823
769
|
this.context = context;
|
|
824
770
|
this.variables = new Map(Object.entries(context.variables || {}));
|
|
771
|
+
this.externalVariables = new Set(Object.keys(context.variables || {}));
|
|
825
772
|
}
|
|
826
773
|
execute(node) {
|
|
827
774
|
if (isProgram(node))
|
|
@@ -838,8 +785,6 @@ class Executor {
|
|
|
838
785
|
return this.executeFunctionCall(node);
|
|
839
786
|
if (isAssignment(node))
|
|
840
787
|
return this.executeAssignment(node);
|
|
841
|
-
if (isNullishAssignment(node))
|
|
842
|
-
return this.executeNullishAssignment(node);
|
|
843
788
|
if (isConditionalExpression(node))
|
|
844
789
|
return this.executeConditionalExpression(node);
|
|
845
790
|
throw new Error(`Unknown node type`);
|
|
@@ -885,13 +830,11 @@ class Executor {
|
|
|
885
830
|
return fn(...args);
|
|
886
831
|
}
|
|
887
832
|
executeAssignment(node) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
if (this.variables.has(node.name)) {
|
|
894
|
-
return this.variables.get(node.name);
|
|
833
|
+
if (this.externalVariables.has(node.name)) {
|
|
834
|
+
const externalValue = this.variables.get(node.name);
|
|
835
|
+
if (externalValue !== undefined) {
|
|
836
|
+
return externalValue;
|
|
837
|
+
}
|
|
895
838
|
}
|
|
896
839
|
const value = this.execute(node.value);
|
|
897
840
|
this.variables.set(node.name, value);
|
|
@@ -909,420 +852,47 @@ function execute(source, context) {
|
|
|
909
852
|
}
|
|
910
853
|
// src/optimizer.ts
|
|
911
854
|
function optimize(node) {
|
|
912
|
-
if (!isProgram(node)) {
|
|
913
|
-
return basicOptimize(node);
|
|
914
|
-
}
|
|
915
|
-
const analysis = analyzeProgram(node);
|
|
916
|
-
const { propagated, allConstants } = propagateConstantsOptimal(node, analysis);
|
|
917
|
-
const optimized = eliminateDeadCodeOptimal(propagated, analysis, allConstants);
|
|
918
|
-
return optimized;
|
|
919
|
-
}
|
|
920
|
-
function analyzeProgram(node) {
|
|
921
|
-
if (!isProgram(node)) {
|
|
922
|
-
return {
|
|
923
|
-
constants: new Map,
|
|
924
|
-
tainted: new Set,
|
|
925
|
-
dependencies: new Map,
|
|
926
|
-
liveVariables: new Set,
|
|
927
|
-
assignmentIndices: new Map,
|
|
928
|
-
evaluationOrder: []
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
const constants = new Map;
|
|
932
|
-
const tainted = new Set;
|
|
933
|
-
const dependencies = new Map;
|
|
934
|
-
const assignmentIndices = new Map;
|
|
935
|
-
const assignmentCounts = new Map;
|
|
936
|
-
for (let i = 0;i < node.statements.length; i++) {
|
|
937
|
-
const stmt = node.statements[i];
|
|
938
|
-
if (!stmt)
|
|
939
|
-
continue;
|
|
940
|
-
if (isAssignment(stmt) || isNullishAssignment(stmt)) {
|
|
941
|
-
const varName = stmt.name;
|
|
942
|
-
const count = assignmentCounts.get(varName) || 0;
|
|
943
|
-
assignmentCounts.set(varName, count + 1);
|
|
944
|
-
assignmentIndices.set(varName, i);
|
|
945
|
-
const deps = new Set;
|
|
946
|
-
const hasFunctionCall = collectDependencies(stmt.value, deps);
|
|
947
|
-
dependencies.set(varName, deps);
|
|
948
|
-
if (isNullishAssignment(stmt)) {
|
|
949
|
-
tainted.add(varName);
|
|
950
|
-
} else {
|
|
951
|
-
if (count === 0 && isNumberLiteral(stmt.value)) {
|
|
952
|
-
constants.set(varName, stmt.value.value);
|
|
953
|
-
}
|
|
954
|
-
if (hasFunctionCall) {
|
|
955
|
-
tainted.add(varName);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
for (const [varName, count] of assignmentCounts) {
|
|
961
|
-
if (count > 1) {
|
|
962
|
-
constants.delete(varName);
|
|
963
|
-
tainted.add(varName);
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
let taintChanged = true;
|
|
967
|
-
while (taintChanged) {
|
|
968
|
-
taintChanged = false;
|
|
969
|
-
for (const [varName, deps] of dependencies) {
|
|
970
|
-
if (tainted.has(varName))
|
|
971
|
-
continue;
|
|
972
|
-
for (const dep of deps) {
|
|
973
|
-
if (tainted.has(dep)) {
|
|
974
|
-
tainted.add(varName);
|
|
975
|
-
constants.delete(varName);
|
|
976
|
-
taintChanged = true;
|
|
977
|
-
break;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
const liveVariables = new Set;
|
|
983
|
-
const lastStmt = node.statements[node.statements.length - 1];
|
|
984
|
-
if (lastStmt) {
|
|
985
|
-
if (isAssignment(lastStmt)) {
|
|
986
|
-
const deps = new Set;
|
|
987
|
-
collectDependencies(lastStmt.value, deps);
|
|
988
|
-
for (const dep of deps) {
|
|
989
|
-
liveVariables.add(dep);
|
|
990
|
-
}
|
|
991
|
-
} else {
|
|
992
|
-
const deps = new Set;
|
|
993
|
-
collectDependencies(lastStmt, deps);
|
|
994
|
-
for (const dep of deps) {
|
|
995
|
-
liveVariables.add(dep);
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
let liveChanged = true;
|
|
1000
|
-
while (liveChanged) {
|
|
1001
|
-
liveChanged = false;
|
|
1002
|
-
for (const [varName, deps] of dependencies) {
|
|
1003
|
-
if (liveVariables.has(varName)) {
|
|
1004
|
-
for (const dep of deps) {
|
|
1005
|
-
if (!liveVariables.has(dep)) {
|
|
1006
|
-
liveVariables.add(dep);
|
|
1007
|
-
liveChanged = true;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
const evaluationOrder = topologicalSort(dependencies, liveVariables);
|
|
1014
|
-
return {
|
|
1015
|
-
constants,
|
|
1016
|
-
tainted,
|
|
1017
|
-
dependencies,
|
|
1018
|
-
liveVariables,
|
|
1019
|
-
assignmentIndices,
|
|
1020
|
-
evaluationOrder
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
function collectDependencies(node, deps) {
|
|
1024
|
-
if (isIdentifier(node)) {
|
|
1025
|
-
deps.add(node.name);
|
|
1026
|
-
return false;
|
|
1027
|
-
}
|
|
1028
855
|
if (isNumberLiteral(node)) {
|
|
1029
|
-
return false;
|
|
1030
|
-
}
|
|
1031
|
-
if (isAssignment(node) || isNullishAssignment(node)) {
|
|
1032
|
-
return collectDependencies(node.value, deps);
|
|
1033
|
-
}
|
|
1034
|
-
if (isBinaryOp(node)) {
|
|
1035
|
-
const leftHasCall = collectDependencies(node.left, deps);
|
|
1036
|
-
const rightHasCall = collectDependencies(node.right, deps);
|
|
1037
|
-
return leftHasCall || rightHasCall;
|
|
1038
|
-
}
|
|
1039
|
-
if (isUnaryOp(node)) {
|
|
1040
|
-
return collectDependencies(node.argument, deps);
|
|
1041
|
-
}
|
|
1042
|
-
if (isFunctionCall(node)) {
|
|
1043
|
-
for (const arg of node.arguments) {
|
|
1044
|
-
collectDependencies(arg, deps);
|
|
1045
|
-
}
|
|
1046
|
-
return true;
|
|
1047
|
-
}
|
|
1048
|
-
if (isConditionalExpression(node)) {
|
|
1049
|
-
const condHasCall = collectDependencies(node.condition, deps);
|
|
1050
|
-
const consHasCall = collectDependencies(node.consequent, deps);
|
|
1051
|
-
const altHasCall = collectDependencies(node.alternate, deps);
|
|
1052
|
-
return condHasCall || consHasCall || altHasCall;
|
|
1053
|
-
}
|
|
1054
|
-
if (isProgram(node)) {
|
|
1055
|
-
let hasCall = false;
|
|
1056
|
-
for (const stmt of node.statements) {
|
|
1057
|
-
hasCall = collectDependencies(stmt, deps) || hasCall;
|
|
1058
|
-
}
|
|
1059
|
-
return hasCall;
|
|
1060
|
-
}
|
|
1061
|
-
return false;
|
|
1062
|
-
}
|
|
1063
|
-
function topologicalSort(dependencies, liveVariables) {
|
|
1064
|
-
const result = [];
|
|
1065
|
-
const visited = new Set;
|
|
1066
|
-
const visiting = new Set;
|
|
1067
|
-
function visit(varName) {
|
|
1068
|
-
if (visited.has(varName))
|
|
1069
|
-
return;
|
|
1070
|
-
if (visiting.has(varName)) {
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
visiting.add(varName);
|
|
1074
|
-
const deps = dependencies.get(varName);
|
|
1075
|
-
if (deps) {
|
|
1076
|
-
for (const dep of deps) {
|
|
1077
|
-
if (liveVariables.has(dep)) {
|
|
1078
|
-
visit(dep);
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
visiting.delete(varName);
|
|
1083
|
-
visited.add(varName);
|
|
1084
|
-
result.push(varName);
|
|
1085
|
-
}
|
|
1086
|
-
for (const varName of liveVariables) {
|
|
1087
|
-
visit(varName);
|
|
1088
|
-
}
|
|
1089
|
-
return result;
|
|
1090
|
-
}
|
|
1091
|
-
function propagateConstantsOptimal(node, analysis) {
|
|
1092
|
-
if (!isProgram(node)) {
|
|
1093
|
-
return { propagated: node, allConstants: new Map };
|
|
1094
|
-
}
|
|
1095
|
-
const allConstants = new Map(analysis.constants);
|
|
1096
|
-
for (const varName of analysis.evaluationOrder) {
|
|
1097
|
-
if (allConstants.has(varName))
|
|
1098
|
-
continue;
|
|
1099
|
-
if (analysis.tainted.has(varName))
|
|
1100
|
-
continue;
|
|
1101
|
-
const deps = analysis.dependencies.get(varName);
|
|
1102
|
-
if (!deps)
|
|
1103
|
-
continue;
|
|
1104
|
-
let allDepsConstant = true;
|
|
1105
|
-
for (const dep of deps) {
|
|
1106
|
-
if (!allConstants.has(dep)) {
|
|
1107
|
-
allDepsConstant = false;
|
|
1108
|
-
break;
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
if (allDepsConstant) {
|
|
1112
|
-
const assignmentIdx = analysis.assignmentIndices.get(varName);
|
|
1113
|
-
if (assignmentIdx !== undefined) {
|
|
1114
|
-
const stmt = node.statements[assignmentIdx];
|
|
1115
|
-
if (stmt && isAssignment(stmt)) {
|
|
1116
|
-
const evaluated = evaluateWithConstants(stmt.value, allConstants);
|
|
1117
|
-
if (isNumberLiteral(evaluated)) {
|
|
1118
|
-
allConstants.set(varName, evaluated.value);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
const statements = node.statements.map((stmt) => replaceWithConstants(stmt, allConstants));
|
|
1125
|
-
return {
|
|
1126
|
-
propagated: {
|
|
1127
|
-
type: "Program",
|
|
1128
|
-
statements
|
|
1129
|
-
},
|
|
1130
|
-
allConstants
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
function evaluateWithConstants(node, constants) {
|
|
1134
|
-
if (isIdentifier(node)) {
|
|
1135
|
-
const value = constants.get(node.name);
|
|
1136
|
-
if (value !== undefined) {
|
|
1137
|
-
return number(value);
|
|
1138
|
-
}
|
|
1139
856
|
return node;
|
|
1140
857
|
}
|
|
1141
|
-
if (
|
|
858
|
+
if (node.type === "Identifier") {
|
|
1142
859
|
return node;
|
|
1143
860
|
}
|
|
1144
861
|
if (isBinaryOp(node)) {
|
|
1145
|
-
const left =
|
|
1146
|
-
const right =
|
|
862
|
+
const left = optimize(node.left);
|
|
863
|
+
const right = optimize(node.right);
|
|
1147
864
|
if (isNumberLiteral(left) && isNumberLiteral(right)) {
|
|
1148
865
|
const result = evaluateBinaryOperation(node.operator, left.value, right.value);
|
|
1149
866
|
return number(result);
|
|
1150
867
|
}
|
|
1151
|
-
return
|
|
1152
|
-
...node,
|
|
1153
|
-
left,
|
|
1154
|
-
right
|
|
1155
|
-
};
|
|
868
|
+
return binaryOp(left, node.operator, right);
|
|
1156
869
|
}
|
|
1157
870
|
if (isUnaryOp(node)) {
|
|
1158
|
-
const argument =
|
|
871
|
+
const argument = optimize(node.argument);
|
|
1159
872
|
if (isNumberLiteral(argument)) {
|
|
1160
873
|
return number(-argument.value);
|
|
1161
874
|
}
|
|
1162
|
-
return
|
|
1163
|
-
...node,
|
|
1164
|
-
argument
|
|
1165
|
-
};
|
|
875
|
+
return unaryOp(argument);
|
|
1166
876
|
}
|
|
1167
877
|
if (isFunctionCall(node)) {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
arguments: node.arguments.map((arg) => evaluateWithConstants(arg, constants))
|
|
1171
|
-
};
|
|
1172
|
-
}
|
|
1173
|
-
if (isConditionalExpression(node)) {
|
|
1174
|
-
const condition = evaluateWithConstants(node.condition, constants);
|
|
1175
|
-
const consequent = evaluateWithConstants(node.consequent, constants);
|
|
1176
|
-
const alternate = evaluateWithConstants(node.alternate, constants);
|
|
1177
|
-
if (isNumberLiteral(condition)) {
|
|
1178
|
-
return condition.value !== 0 ? consequent : alternate;
|
|
1179
|
-
}
|
|
1180
|
-
return {
|
|
1181
|
-
...node,
|
|
1182
|
-
condition,
|
|
1183
|
-
consequent,
|
|
1184
|
-
alternate
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
|
-
if (isAssignment(node)) {
|
|
1188
|
-
return {
|
|
1189
|
-
...node,
|
|
1190
|
-
value: evaluateWithConstants(node.value, constants)
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
1193
|
-
if (isNullishAssignment(node)) {
|
|
1194
|
-
return {
|
|
1195
|
-
...node,
|
|
1196
|
-
value: evaluateWithConstants(node.value, constants)
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
return node;
|
|
1200
|
-
}
|
|
1201
|
-
function replaceWithConstants(node, constants) {
|
|
1202
|
-
return evaluateWithConstants(node, constants);
|
|
1203
|
-
}
|
|
1204
|
-
function eliminateDeadCodeOptimal(node, analysis, allConstants) {
|
|
1205
|
-
if (!isProgram(node))
|
|
1206
|
-
return node;
|
|
1207
|
-
const lastStmt = node.statements[node.statements.length - 1];
|
|
1208
|
-
if (lastStmt) {
|
|
1209
|
-
const evaluated = evaluateWithConstants(lastStmt, allConstants);
|
|
1210
|
-
if (isNumberLiteral(evaluated)) {
|
|
1211
|
-
return evaluated;
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
const filteredStatements = [];
|
|
1215
|
-
const variablesInUse = new Set;
|
|
1216
|
-
for (const stmt of node.statements) {
|
|
1217
|
-
collectDependencies(stmt, variablesInUse);
|
|
1218
|
-
}
|
|
1219
|
-
for (let i = 0;i < node.statements.length; i++) {
|
|
1220
|
-
const stmt = node.statements[i];
|
|
1221
|
-
if (!stmt)
|
|
1222
|
-
continue;
|
|
1223
|
-
if (i === node.statements.length - 1) {
|
|
1224
|
-
filteredStatements.push(stmt);
|
|
1225
|
-
continue;
|
|
1226
|
-
}
|
|
1227
|
-
if (isAssignment(stmt) || isNullishAssignment(stmt)) {
|
|
1228
|
-
if (variablesInUse.has(stmt.name)) {
|
|
1229
|
-
filteredStatements.push(stmt);
|
|
1230
|
-
}
|
|
1231
|
-
} else {
|
|
1232
|
-
filteredStatements.push(stmt);
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
if (filteredStatements.length === 1) {
|
|
1236
|
-
const singleStmt = filteredStatements[0];
|
|
1237
|
-
if (!singleStmt) {
|
|
1238
|
-
return node;
|
|
1239
|
-
}
|
|
1240
|
-
if (isNumberLiteral(singleStmt)) {
|
|
1241
|
-
return singleStmt;
|
|
1242
|
-
}
|
|
1243
|
-
if (isAssignment(singleStmt) && isNumberLiteral(singleStmt.value)) {
|
|
1244
|
-
if (!analysis.liveVariables.has(singleStmt.name)) {
|
|
1245
|
-
return singleStmt.value;
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
const evaluated = evaluateWithConstants(singleStmt, allConstants);
|
|
1249
|
-
if (isNumberLiteral(evaluated)) {
|
|
1250
|
-
return evaluated;
|
|
1251
|
-
}
|
|
878
|
+
const optimizedArgs = node.arguments.map((arg) => optimize(arg));
|
|
879
|
+
return functionCall(node.name, optimizedArgs);
|
|
1252
880
|
}
|
|
1253
|
-
if (filteredStatements.length === 0) {
|
|
1254
|
-
const lastStmt2 = node.statements[node.statements.length - 1];
|
|
1255
|
-
if (lastStmt2 && isAssignment(lastStmt2) && isNumberLiteral(lastStmt2.value)) {
|
|
1256
|
-
return lastStmt2.value;
|
|
1257
|
-
}
|
|
1258
|
-
return node;
|
|
1259
|
-
}
|
|
1260
|
-
return {
|
|
1261
|
-
type: "Program",
|
|
1262
|
-
statements: filteredStatements
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
function basicOptimize(node) {
|
|
1266
881
|
if (isAssignment(node)) {
|
|
1267
|
-
return
|
|
1268
|
-
...node,
|
|
1269
|
-
value: basicOptimize(node.value)
|
|
1270
|
-
};
|
|
1271
|
-
}
|
|
1272
|
-
if (isNullishAssignment(node)) {
|
|
1273
|
-
return {
|
|
1274
|
-
...node,
|
|
1275
|
-
value: basicOptimize(node.value)
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
if (isBinaryOp(node)) {
|
|
1279
|
-
const left = basicOptimize(node.left);
|
|
1280
|
-
const right = basicOptimize(node.right);
|
|
1281
|
-
if (isNumberLiteral(left) && isNumberLiteral(right)) {
|
|
1282
|
-
const result = evaluateBinaryOperation(node.operator, left.value, right.value);
|
|
1283
|
-
return number(result);
|
|
1284
|
-
}
|
|
1285
|
-
return {
|
|
1286
|
-
...node,
|
|
1287
|
-
left,
|
|
1288
|
-
right
|
|
1289
|
-
};
|
|
1290
|
-
}
|
|
1291
|
-
if (isUnaryOp(node)) {
|
|
1292
|
-
const argument = basicOptimize(node.argument);
|
|
1293
|
-
if (isNumberLiteral(argument)) {
|
|
1294
|
-
return number(-argument.value);
|
|
1295
|
-
}
|
|
1296
|
-
return {
|
|
1297
|
-
...node,
|
|
1298
|
-
argument
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
if (isFunctionCall(node)) {
|
|
1302
|
-
return {
|
|
1303
|
-
...node,
|
|
1304
|
-
arguments: node.arguments.map((arg) => basicOptimize(arg))
|
|
1305
|
-
};
|
|
882
|
+
return assign(node.name, optimize(node.value));
|
|
1306
883
|
}
|
|
1307
884
|
if (isConditionalExpression(node)) {
|
|
1308
|
-
const condition =
|
|
1309
|
-
const consequent = basicOptimize(node.consequent);
|
|
1310
|
-
const alternate = basicOptimize(node.alternate);
|
|
885
|
+
const condition = optimize(node.condition);
|
|
1311
886
|
if (isNumberLiteral(condition)) {
|
|
1312
|
-
return condition.value !== 0 ? consequent : alternate;
|
|
887
|
+
return condition.value !== 0 ? optimize(node.consequent) : optimize(node.alternate);
|
|
1313
888
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
consequent,
|
|
1318
|
-
alternate
|
|
1319
|
-
};
|
|
889
|
+
const consequent = optimize(node.consequent);
|
|
890
|
+
const alternate = optimize(node.alternate);
|
|
891
|
+
return conditional(condition, consequent, alternate);
|
|
1320
892
|
}
|
|
1321
893
|
if (isProgram(node)) {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
statements: node.statements.map((stmt) => basicOptimize(stmt))
|
|
1325
|
-
};
|
|
894
|
+
const optimizedStatements = node.statements.map((stmt) => optimize(stmt));
|
|
895
|
+
return program(optimizedStatements);
|
|
1326
896
|
}
|
|
1327
897
|
return node;
|
|
1328
898
|
}
|
|
@@ -1332,7 +902,6 @@ export {
|
|
|
1332
902
|
isUnaryOp,
|
|
1333
903
|
isProgram,
|
|
1334
904
|
isNumberLiteral,
|
|
1335
|
-
isNullishAssignment,
|
|
1336
905
|
isIdentifier,
|
|
1337
906
|
isFunctionCall,
|
|
1338
907
|
isConditionalExpression,
|