littlewing 0.5.3 → 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 +35 -61
- package/dist/index.js +78 -516
- 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,16 +428,22 @@ 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
|
}
|
|
449
|
-
if (char === "."
|
|
450
|
-
|
|
442
|
+
if (char === ".") {
|
|
443
|
+
const nextChar = this.source[this.position + 1];
|
|
444
|
+
if (nextChar !== undefined && this.isDigit(nextChar)) {
|
|
445
|
+
return this.readNumber();
|
|
446
|
+
}
|
|
451
447
|
}
|
|
452
448
|
if (this.isLetter(char) || char === "_") {
|
|
453
449
|
return this.readIdentifier();
|
|
@@ -478,41 +474,33 @@ class Lexer {
|
|
|
478
474
|
this.position++;
|
|
479
475
|
return { type: "RPAREN" /* RPAREN */, value: ")", position: start };
|
|
480
476
|
case "=":
|
|
481
|
-
if (this.
|
|
477
|
+
if (this.source[this.position + 1] === "=") {
|
|
482
478
|
this.position += 2;
|
|
483
479
|
return { type: "DOUBLE_EQUALS" /* DOUBLE_EQUALS */, value: "==", position: start };
|
|
484
480
|
}
|
|
485
481
|
this.position++;
|
|
486
482
|
return { type: "EQUALS" /* EQUALS */, value: "=", position: start };
|
|
487
483
|
case "!":
|
|
488
|
-
if (this.
|
|
484
|
+
if (this.source[this.position + 1] === "=") {
|
|
489
485
|
this.position += 2;
|
|
490
486
|
return { type: "NOT_EQUALS" /* NOT_EQUALS */, value: "!=", position: start };
|
|
491
487
|
}
|
|
492
488
|
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
493
489
|
case "<":
|
|
494
|
-
if (this.
|
|
490
|
+
if (this.source[this.position + 1] === "=") {
|
|
495
491
|
this.position += 2;
|
|
496
492
|
return { type: "LESS_EQUAL" /* LESS_EQUAL */, value: "<=", position: start };
|
|
497
493
|
}
|
|
498
494
|
this.position++;
|
|
499
495
|
return { type: "LESS_THAN" /* LESS_THAN */, value: "<", position: start };
|
|
500
496
|
case ">":
|
|
501
|
-
if (this.
|
|
497
|
+
if (this.source[this.position + 1] === "=") {
|
|
502
498
|
this.position += 2;
|
|
503
499
|
return { type: "GREATER_EQUAL" /* GREATER_EQUAL */, value: ">=", position: start };
|
|
504
500
|
}
|
|
505
501
|
this.position++;
|
|
506
502
|
return { type: "GREATER_THAN" /* GREATER_THAN */, value: ">", position: start };
|
|
507
503
|
case "?":
|
|
508
|
-
if (this.peek() === "?" && this.peekAhead(2) === "=") {
|
|
509
|
-
this.position += 3;
|
|
510
|
-
return {
|
|
511
|
-
type: "NULLISH_ASSIGN" /* NULLISH_ASSIGN */,
|
|
512
|
-
value: "??=",
|
|
513
|
-
position: start
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
504
|
this.position++;
|
|
517
505
|
return { type: "QUESTION" /* QUESTION */, value: "?", position: start };
|
|
518
506
|
case ":":
|
|
@@ -525,13 +513,13 @@ class Lexer {
|
|
|
525
513
|
this.position++;
|
|
526
514
|
return this.nextToken();
|
|
527
515
|
case "&":
|
|
528
|
-
if (this.
|
|
516
|
+
if (this.source[this.position + 1] === "&") {
|
|
529
517
|
this.position += 2;
|
|
530
518
|
return { type: "LOGICAL_AND" /* LOGICAL_AND */, value: "&&", position: start };
|
|
531
519
|
}
|
|
532
520
|
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
533
521
|
case "|":
|
|
534
|
-
if (this.
|
|
522
|
+
if (this.source[this.position + 1] === "|") {
|
|
535
523
|
this.position += 2;
|
|
536
524
|
return { type: "LOGICAL_OR" /* LOGICAL_OR */, value: "||", position: start };
|
|
537
525
|
}
|
|
@@ -541,14 +529,15 @@ class Lexer {
|
|
|
541
529
|
}
|
|
542
530
|
}
|
|
543
531
|
skipWhitespaceAndComments() {
|
|
544
|
-
while (this.position < this.
|
|
545
|
-
const char = this.
|
|
532
|
+
while (this.position < this.length) {
|
|
533
|
+
const char = this.source[this.position];
|
|
546
534
|
if (this.isWhitespace(char)) {
|
|
547
535
|
this.position++;
|
|
548
536
|
continue;
|
|
549
537
|
}
|
|
550
|
-
if (char === "/" && this.
|
|
551
|
-
|
|
538
|
+
if (char === "/" && this.source[this.position + 1] === "/") {
|
|
539
|
+
this.position += 2;
|
|
540
|
+
while (this.position < this.length && this.source[this.position] !== `
|
|
552
541
|
`) {
|
|
553
542
|
this.position++;
|
|
554
543
|
}
|
|
@@ -561,12 +550,12 @@ class Lexer {
|
|
|
561
550
|
const start = this.position;
|
|
562
551
|
let hasDecimal = false;
|
|
563
552
|
let hasExponent = false;
|
|
564
|
-
if (this.
|
|
553
|
+
if (this.source[this.position] === ".") {
|
|
565
554
|
hasDecimal = true;
|
|
566
555
|
this.position++;
|
|
567
556
|
}
|
|
568
|
-
while (this.position < this.
|
|
569
|
-
const char = this.
|
|
557
|
+
while (this.position < this.length) {
|
|
558
|
+
const char = this.source[this.position];
|
|
570
559
|
if (this.isDigit(char)) {
|
|
571
560
|
this.position++;
|
|
572
561
|
} else if (char === "." && !hasDecimal && !hasExponent) {
|
|
@@ -575,14 +564,15 @@ class Lexer {
|
|
|
575
564
|
} else if ((char === "e" || char === "E") && !hasExponent) {
|
|
576
565
|
hasExponent = true;
|
|
577
566
|
this.position++;
|
|
578
|
-
const nextChar = this.
|
|
567
|
+
const nextChar = this.source[this.position];
|
|
579
568
|
if (nextChar === "+" || nextChar === "-") {
|
|
580
569
|
this.position++;
|
|
581
570
|
}
|
|
582
|
-
|
|
571
|
+
const digitChar = this.source[this.position];
|
|
572
|
+
if (digitChar === undefined || !this.isDigit(digitChar)) {
|
|
583
573
|
throw new Error(`Invalid number: expected digit after exponent at position ${this.position}`);
|
|
584
574
|
}
|
|
585
|
-
while (this.position < this.
|
|
575
|
+
while (this.position < this.length && this.isDigit(this.source[this.position])) {
|
|
586
576
|
this.position++;
|
|
587
577
|
}
|
|
588
578
|
break;
|
|
@@ -595,8 +585,8 @@ class Lexer {
|
|
|
595
585
|
}
|
|
596
586
|
readIdentifier() {
|
|
597
587
|
const start = this.position;
|
|
598
|
-
while (this.position < this.
|
|
599
|
-
const char = this.
|
|
588
|
+
while (this.position < this.length) {
|
|
589
|
+
const char = this.source[this.position];
|
|
600
590
|
if (this.isLetter(char) || this.isDigit(char) || char === "_") {
|
|
601
591
|
this.position++;
|
|
602
592
|
} else {
|
|
@@ -606,24 +596,15 @@ class Lexer {
|
|
|
606
596
|
const name = this.source.slice(start, this.position);
|
|
607
597
|
return { type: "IDENTIFIER" /* IDENTIFIER */, value: name, position: start };
|
|
608
598
|
}
|
|
609
|
-
getCharAt(pos) {
|
|
610
|
-
return pos < this.source.length ? this.source[pos] || "" : "";
|
|
611
|
-
}
|
|
612
|
-
peek() {
|
|
613
|
-
return this.getCharAt(this.position + 1);
|
|
614
|
-
}
|
|
615
|
-
peekAhead(n) {
|
|
616
|
-
return this.getCharAt(this.position + n);
|
|
617
|
-
}
|
|
618
599
|
isDigit(char) {
|
|
619
|
-
return char >= "0" && char <= "9";
|
|
600
|
+
return char !== undefined && char >= "0" && char <= "9";
|
|
620
601
|
}
|
|
621
602
|
isLetter(char) {
|
|
622
|
-
return char >= "a" && char <= "z" || char >= "A" && char <= "Z";
|
|
603
|
+
return char !== undefined && (char >= "a" && char <= "z" || char >= "A" && char <= "Z");
|
|
623
604
|
}
|
|
624
605
|
isWhitespace(char) {
|
|
625
|
-
return char === " " || char === "\t" || char === `
|
|
626
|
-
` || char === "\r";
|
|
606
|
+
return char !== undefined && (char === " " || char === "\t" || char === `
|
|
607
|
+
` || char === "\r");
|
|
627
608
|
}
|
|
628
609
|
}
|
|
629
610
|
|
|
@@ -660,16 +641,13 @@ class Parser {
|
|
|
660
641
|
throw new Error("Empty program");
|
|
661
642
|
}
|
|
662
643
|
if (statements.length === 1) {
|
|
663
|
-
const
|
|
664
|
-
if (
|
|
665
|
-
throw new Error("Unexpected
|
|
644
|
+
const singleStatement = statements[0];
|
|
645
|
+
if (singleStatement === undefined) {
|
|
646
|
+
throw new Error("Unexpected empty statements array");
|
|
666
647
|
}
|
|
667
|
-
return
|
|
648
|
+
return singleStatement;
|
|
668
649
|
}
|
|
669
|
-
return
|
|
670
|
-
type: "Program",
|
|
671
|
-
statements
|
|
672
|
-
};
|
|
650
|
+
return program(statements);
|
|
673
651
|
}
|
|
674
652
|
parseExpression(minPrecedence) {
|
|
675
653
|
let left = this.parsePrefix();
|
|
@@ -686,23 +664,7 @@ class Parser {
|
|
|
686
664
|
const identName = left.name;
|
|
687
665
|
this.advance();
|
|
688
666
|
const value = this.parseExpression(precedence);
|
|
689
|
-
left =
|
|
690
|
-
type: "Assignment",
|
|
691
|
-
name: identName,
|
|
692
|
-
value
|
|
693
|
-
};
|
|
694
|
-
} else if (token.type === "NULLISH_ASSIGN" /* NULLISH_ASSIGN */) {
|
|
695
|
-
if (left.type !== "Identifier") {
|
|
696
|
-
throw new Error("Invalid assignment target");
|
|
697
|
-
}
|
|
698
|
-
const identName = left.name;
|
|
699
|
-
this.advance();
|
|
700
|
-
const value = this.parseExpression(precedence);
|
|
701
|
-
left = {
|
|
702
|
-
type: "NullishAssignment",
|
|
703
|
-
name: identName,
|
|
704
|
-
value
|
|
705
|
-
};
|
|
667
|
+
left = assign(identName, value);
|
|
706
668
|
} else if (token.type === "QUESTION" /* QUESTION */) {
|
|
707
669
|
this.advance();
|
|
708
670
|
const consequent = this.parseExpression(0);
|
|
@@ -711,22 +673,12 @@ class Parser {
|
|
|
711
673
|
}
|
|
712
674
|
this.advance();
|
|
713
675
|
const alternate = this.parseExpression(precedence);
|
|
714
|
-
left =
|
|
715
|
-
type: "ConditionalExpression",
|
|
716
|
-
condition: left,
|
|
717
|
-
consequent,
|
|
718
|
-
alternate
|
|
719
|
-
};
|
|
676
|
+
left = conditional(left, consequent, alternate);
|
|
720
677
|
} else if (this.isBinaryOperator(token.type)) {
|
|
721
678
|
const operator = token.value;
|
|
722
679
|
this.advance();
|
|
723
680
|
const right = this.parseExpression(precedence + 1);
|
|
724
|
-
left =
|
|
725
|
-
type: "BinaryOp",
|
|
726
|
-
left,
|
|
727
|
-
operator,
|
|
728
|
-
right
|
|
729
|
-
};
|
|
681
|
+
left = binaryOp(left, operator, right);
|
|
730
682
|
} else {
|
|
731
683
|
break;
|
|
732
684
|
}
|
|
@@ -738,11 +690,7 @@ class Parser {
|
|
|
738
690
|
if (token.type === "MINUS" /* MINUS */) {
|
|
739
691
|
this.advance();
|
|
740
692
|
const argument = this.parseExpression(this.getUnaryPrecedence());
|
|
741
|
-
return
|
|
742
|
-
type: "UnaryOp",
|
|
743
|
-
operator: "-",
|
|
744
|
-
argument
|
|
745
|
-
};
|
|
693
|
+
return unaryOp(argument);
|
|
746
694
|
}
|
|
747
695
|
if (token.type === "LPAREN" /* LPAREN */) {
|
|
748
696
|
this.advance();
|
|
@@ -755,10 +703,7 @@ class Parser {
|
|
|
755
703
|
}
|
|
756
704
|
if (token.type === "NUMBER" /* NUMBER */) {
|
|
757
705
|
this.advance();
|
|
758
|
-
return
|
|
759
|
-
type: "NumberLiteral",
|
|
760
|
-
value: token.value
|
|
761
|
-
};
|
|
706
|
+
return number(token.value);
|
|
762
707
|
}
|
|
763
708
|
if (token.type === "IDENTIFIER" /* IDENTIFIER */) {
|
|
764
709
|
const name = token.value;
|
|
@@ -770,16 +715,9 @@ class Parser {
|
|
|
770
715
|
throw new Error("Expected closing parenthesis");
|
|
771
716
|
}
|
|
772
717
|
this.advance();
|
|
773
|
-
return
|
|
774
|
-
type: "FunctionCall",
|
|
775
|
-
name,
|
|
776
|
-
arguments: args
|
|
777
|
-
};
|
|
718
|
+
return functionCall(name, args);
|
|
778
719
|
}
|
|
779
|
-
return
|
|
780
|
-
type: "Identifier",
|
|
781
|
-
name
|
|
782
|
-
};
|
|
720
|
+
return identifier(name);
|
|
783
721
|
}
|
|
784
722
|
throw new Error(`Unexpected token: ${token.value}`);
|
|
785
723
|
}
|
|
@@ -826,9 +764,11 @@ function parseSource(source) {
|
|
|
826
764
|
class Executor {
|
|
827
765
|
context;
|
|
828
766
|
variables;
|
|
767
|
+
externalVariables;
|
|
829
768
|
constructor(context = {}) {
|
|
830
769
|
this.context = context;
|
|
831
770
|
this.variables = new Map(Object.entries(context.variables || {}));
|
|
771
|
+
this.externalVariables = new Set(Object.keys(context.variables || {}));
|
|
832
772
|
}
|
|
833
773
|
execute(node) {
|
|
834
774
|
if (isProgram(node))
|
|
@@ -845,8 +785,6 @@ class Executor {
|
|
|
845
785
|
return this.executeFunctionCall(node);
|
|
846
786
|
if (isAssignment(node))
|
|
847
787
|
return this.executeAssignment(node);
|
|
848
|
-
if (isNullishAssignment(node))
|
|
849
|
-
return this.executeNullishAssignment(node);
|
|
850
788
|
if (isConditionalExpression(node))
|
|
851
789
|
return this.executeConditionalExpression(node);
|
|
852
790
|
throw new Error(`Unknown node type`);
|
|
@@ -892,13 +830,11 @@ class Executor {
|
|
|
892
830
|
return fn(...args);
|
|
893
831
|
}
|
|
894
832
|
executeAssignment(node) {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
if (this.variables.has(node.name)) {
|
|
901
|
-
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
|
+
}
|
|
902
838
|
}
|
|
903
839
|
const value = this.execute(node.value);
|
|
904
840
|
this.variables.set(node.name, value);
|
|
@@ -916,420 +852,47 @@ function execute(source, context) {
|
|
|
916
852
|
}
|
|
917
853
|
// src/optimizer.ts
|
|
918
854
|
function optimize(node) {
|
|
919
|
-
if (!isProgram(node)) {
|
|
920
|
-
return basicOptimize(node);
|
|
921
|
-
}
|
|
922
|
-
const analysis = analyzeProgram(node);
|
|
923
|
-
const { propagated, allConstants } = propagateConstantsOptimal(node, analysis);
|
|
924
|
-
const optimized = eliminateDeadCodeOptimal(propagated, analysis, allConstants);
|
|
925
|
-
return optimized;
|
|
926
|
-
}
|
|
927
|
-
function analyzeProgram(node) {
|
|
928
|
-
if (!isProgram(node)) {
|
|
929
|
-
return {
|
|
930
|
-
constants: new Map,
|
|
931
|
-
tainted: new Set,
|
|
932
|
-
dependencies: new Map,
|
|
933
|
-
liveVariables: new Set,
|
|
934
|
-
assignmentIndices: new Map,
|
|
935
|
-
evaluationOrder: []
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
const constants = new Map;
|
|
939
|
-
const tainted = new Set;
|
|
940
|
-
const dependencies = new Map;
|
|
941
|
-
const assignmentIndices = new Map;
|
|
942
|
-
const assignmentCounts = new Map;
|
|
943
|
-
for (let i = 0;i < node.statements.length; i++) {
|
|
944
|
-
const stmt = node.statements[i];
|
|
945
|
-
if (!stmt)
|
|
946
|
-
continue;
|
|
947
|
-
if (isAssignment(stmt) || isNullishAssignment(stmt)) {
|
|
948
|
-
const varName = stmt.name;
|
|
949
|
-
const count = assignmentCounts.get(varName) || 0;
|
|
950
|
-
assignmentCounts.set(varName, count + 1);
|
|
951
|
-
assignmentIndices.set(varName, i);
|
|
952
|
-
const deps = new Set;
|
|
953
|
-
const hasFunctionCall = collectDependencies(stmt.value, deps);
|
|
954
|
-
dependencies.set(varName, deps);
|
|
955
|
-
if (isNullishAssignment(stmt)) {
|
|
956
|
-
tainted.add(varName);
|
|
957
|
-
} else {
|
|
958
|
-
if (count === 0 && isNumberLiteral(stmt.value)) {
|
|
959
|
-
constants.set(varName, stmt.value.value);
|
|
960
|
-
}
|
|
961
|
-
if (hasFunctionCall) {
|
|
962
|
-
tainted.add(varName);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
for (const [varName, count] of assignmentCounts) {
|
|
968
|
-
if (count > 1) {
|
|
969
|
-
constants.delete(varName);
|
|
970
|
-
tainted.add(varName);
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
let taintChanged = true;
|
|
974
|
-
while (taintChanged) {
|
|
975
|
-
taintChanged = false;
|
|
976
|
-
for (const [varName, deps] of dependencies) {
|
|
977
|
-
if (tainted.has(varName))
|
|
978
|
-
continue;
|
|
979
|
-
for (const dep of deps) {
|
|
980
|
-
if (tainted.has(dep)) {
|
|
981
|
-
tainted.add(varName);
|
|
982
|
-
constants.delete(varName);
|
|
983
|
-
taintChanged = true;
|
|
984
|
-
break;
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
const liveVariables = new Set;
|
|
990
|
-
const lastStmt = node.statements[node.statements.length - 1];
|
|
991
|
-
if (lastStmt) {
|
|
992
|
-
if (isAssignment(lastStmt)) {
|
|
993
|
-
const deps = new Set;
|
|
994
|
-
collectDependencies(lastStmt.value, deps);
|
|
995
|
-
for (const dep of deps) {
|
|
996
|
-
liveVariables.add(dep);
|
|
997
|
-
}
|
|
998
|
-
} else {
|
|
999
|
-
const deps = new Set;
|
|
1000
|
-
collectDependencies(lastStmt, deps);
|
|
1001
|
-
for (const dep of deps) {
|
|
1002
|
-
liveVariables.add(dep);
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
let liveChanged = true;
|
|
1007
|
-
while (liveChanged) {
|
|
1008
|
-
liveChanged = false;
|
|
1009
|
-
for (const [varName, deps] of dependencies) {
|
|
1010
|
-
if (liveVariables.has(varName)) {
|
|
1011
|
-
for (const dep of deps) {
|
|
1012
|
-
if (!liveVariables.has(dep)) {
|
|
1013
|
-
liveVariables.add(dep);
|
|
1014
|
-
liveChanged = true;
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
const evaluationOrder = topologicalSort(dependencies, liveVariables);
|
|
1021
|
-
return {
|
|
1022
|
-
constants,
|
|
1023
|
-
tainted,
|
|
1024
|
-
dependencies,
|
|
1025
|
-
liveVariables,
|
|
1026
|
-
assignmentIndices,
|
|
1027
|
-
evaluationOrder
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
function collectDependencies(node, deps) {
|
|
1031
|
-
if (isIdentifier(node)) {
|
|
1032
|
-
deps.add(node.name);
|
|
1033
|
-
return false;
|
|
1034
|
-
}
|
|
1035
855
|
if (isNumberLiteral(node)) {
|
|
1036
|
-
return false;
|
|
1037
|
-
}
|
|
1038
|
-
if (isAssignment(node) || isNullishAssignment(node)) {
|
|
1039
|
-
return collectDependencies(node.value, deps);
|
|
1040
|
-
}
|
|
1041
|
-
if (isBinaryOp(node)) {
|
|
1042
|
-
const leftHasCall = collectDependencies(node.left, deps);
|
|
1043
|
-
const rightHasCall = collectDependencies(node.right, deps);
|
|
1044
|
-
return leftHasCall || rightHasCall;
|
|
1045
|
-
}
|
|
1046
|
-
if (isUnaryOp(node)) {
|
|
1047
|
-
return collectDependencies(node.argument, deps);
|
|
1048
|
-
}
|
|
1049
|
-
if (isFunctionCall(node)) {
|
|
1050
|
-
for (const arg of node.arguments) {
|
|
1051
|
-
collectDependencies(arg, deps);
|
|
1052
|
-
}
|
|
1053
|
-
return true;
|
|
1054
|
-
}
|
|
1055
|
-
if (isConditionalExpression(node)) {
|
|
1056
|
-
const condHasCall = collectDependencies(node.condition, deps);
|
|
1057
|
-
const consHasCall = collectDependencies(node.consequent, deps);
|
|
1058
|
-
const altHasCall = collectDependencies(node.alternate, deps);
|
|
1059
|
-
return condHasCall || consHasCall || altHasCall;
|
|
1060
|
-
}
|
|
1061
|
-
if (isProgram(node)) {
|
|
1062
|
-
let hasCall = false;
|
|
1063
|
-
for (const stmt of node.statements) {
|
|
1064
|
-
hasCall = collectDependencies(stmt, deps) || hasCall;
|
|
1065
|
-
}
|
|
1066
|
-
return hasCall;
|
|
1067
|
-
}
|
|
1068
|
-
return false;
|
|
1069
|
-
}
|
|
1070
|
-
function topologicalSort(dependencies, liveVariables) {
|
|
1071
|
-
const result = [];
|
|
1072
|
-
const visited = new Set;
|
|
1073
|
-
const visiting = new Set;
|
|
1074
|
-
function visit(varName) {
|
|
1075
|
-
if (visited.has(varName))
|
|
1076
|
-
return;
|
|
1077
|
-
if (visiting.has(varName)) {
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
visiting.add(varName);
|
|
1081
|
-
const deps = dependencies.get(varName);
|
|
1082
|
-
if (deps) {
|
|
1083
|
-
for (const dep of deps) {
|
|
1084
|
-
if (liveVariables.has(dep)) {
|
|
1085
|
-
visit(dep);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
visiting.delete(varName);
|
|
1090
|
-
visited.add(varName);
|
|
1091
|
-
result.push(varName);
|
|
1092
|
-
}
|
|
1093
|
-
for (const varName of liveVariables) {
|
|
1094
|
-
visit(varName);
|
|
1095
|
-
}
|
|
1096
|
-
return result;
|
|
1097
|
-
}
|
|
1098
|
-
function propagateConstantsOptimal(node, analysis) {
|
|
1099
|
-
if (!isProgram(node)) {
|
|
1100
|
-
return { propagated: node, allConstants: new Map };
|
|
1101
|
-
}
|
|
1102
|
-
const allConstants = new Map(analysis.constants);
|
|
1103
|
-
for (const varName of analysis.evaluationOrder) {
|
|
1104
|
-
if (allConstants.has(varName))
|
|
1105
|
-
continue;
|
|
1106
|
-
if (analysis.tainted.has(varName))
|
|
1107
|
-
continue;
|
|
1108
|
-
const deps = analysis.dependencies.get(varName);
|
|
1109
|
-
if (!deps)
|
|
1110
|
-
continue;
|
|
1111
|
-
let allDepsConstant = true;
|
|
1112
|
-
for (const dep of deps) {
|
|
1113
|
-
if (!allConstants.has(dep)) {
|
|
1114
|
-
allDepsConstant = false;
|
|
1115
|
-
break;
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
if (allDepsConstant) {
|
|
1119
|
-
const assignmentIdx = analysis.assignmentIndices.get(varName);
|
|
1120
|
-
if (assignmentIdx !== undefined) {
|
|
1121
|
-
const stmt = node.statements[assignmentIdx];
|
|
1122
|
-
if (stmt && isAssignment(stmt)) {
|
|
1123
|
-
const evaluated = evaluateWithConstants(stmt.value, allConstants);
|
|
1124
|
-
if (isNumberLiteral(evaluated)) {
|
|
1125
|
-
allConstants.set(varName, evaluated.value);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
const statements = node.statements.map((stmt) => replaceWithConstants(stmt, allConstants));
|
|
1132
|
-
return {
|
|
1133
|
-
propagated: {
|
|
1134
|
-
type: "Program",
|
|
1135
|
-
statements
|
|
1136
|
-
},
|
|
1137
|
-
allConstants
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
function evaluateWithConstants(node, constants) {
|
|
1141
|
-
if (isIdentifier(node)) {
|
|
1142
|
-
const value = constants.get(node.name);
|
|
1143
|
-
if (value !== undefined) {
|
|
1144
|
-
return number(value);
|
|
1145
|
-
}
|
|
1146
856
|
return node;
|
|
1147
857
|
}
|
|
1148
|
-
if (
|
|
858
|
+
if (node.type === "Identifier") {
|
|
1149
859
|
return node;
|
|
1150
860
|
}
|
|
1151
861
|
if (isBinaryOp(node)) {
|
|
1152
|
-
const left =
|
|
1153
|
-
const right =
|
|
862
|
+
const left = optimize(node.left);
|
|
863
|
+
const right = optimize(node.right);
|
|
1154
864
|
if (isNumberLiteral(left) && isNumberLiteral(right)) {
|
|
1155
865
|
const result = evaluateBinaryOperation(node.operator, left.value, right.value);
|
|
1156
866
|
return number(result);
|
|
1157
867
|
}
|
|
1158
|
-
return
|
|
1159
|
-
...node,
|
|
1160
|
-
left,
|
|
1161
|
-
right
|
|
1162
|
-
};
|
|
868
|
+
return binaryOp(left, node.operator, right);
|
|
1163
869
|
}
|
|
1164
870
|
if (isUnaryOp(node)) {
|
|
1165
|
-
const argument =
|
|
871
|
+
const argument = optimize(node.argument);
|
|
1166
872
|
if (isNumberLiteral(argument)) {
|
|
1167
873
|
return number(-argument.value);
|
|
1168
874
|
}
|
|
1169
|
-
return
|
|
1170
|
-
...node,
|
|
1171
|
-
argument
|
|
1172
|
-
};
|
|
875
|
+
return unaryOp(argument);
|
|
1173
876
|
}
|
|
1174
877
|
if (isFunctionCall(node)) {
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
arguments: node.arguments.map((arg) => evaluateWithConstants(arg, constants))
|
|
1178
|
-
};
|
|
1179
|
-
}
|
|
1180
|
-
if (isConditionalExpression(node)) {
|
|
1181
|
-
const condition = evaluateWithConstants(node.condition, constants);
|
|
1182
|
-
const consequent = evaluateWithConstants(node.consequent, constants);
|
|
1183
|
-
const alternate = evaluateWithConstants(node.alternate, constants);
|
|
1184
|
-
if (isNumberLiteral(condition)) {
|
|
1185
|
-
return condition.value !== 0 ? consequent : alternate;
|
|
1186
|
-
}
|
|
1187
|
-
return {
|
|
1188
|
-
...node,
|
|
1189
|
-
condition,
|
|
1190
|
-
consequent,
|
|
1191
|
-
alternate
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
if (isAssignment(node)) {
|
|
1195
|
-
return {
|
|
1196
|
-
...node,
|
|
1197
|
-
value: evaluateWithConstants(node.value, constants)
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
|
-
if (isNullishAssignment(node)) {
|
|
1201
|
-
return {
|
|
1202
|
-
...node,
|
|
1203
|
-
value: evaluateWithConstants(node.value, constants)
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1206
|
-
return node;
|
|
1207
|
-
}
|
|
1208
|
-
function replaceWithConstants(node, constants) {
|
|
1209
|
-
return evaluateWithConstants(node, constants);
|
|
1210
|
-
}
|
|
1211
|
-
function eliminateDeadCodeOptimal(node, analysis, allConstants) {
|
|
1212
|
-
if (!isProgram(node))
|
|
1213
|
-
return node;
|
|
1214
|
-
const lastStmt = node.statements[node.statements.length - 1];
|
|
1215
|
-
if (lastStmt) {
|
|
1216
|
-
const evaluated = evaluateWithConstants(lastStmt, allConstants);
|
|
1217
|
-
if (isNumberLiteral(evaluated)) {
|
|
1218
|
-
return evaluated;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
const filteredStatements = [];
|
|
1222
|
-
const variablesInUse = new Set;
|
|
1223
|
-
for (const stmt of node.statements) {
|
|
1224
|
-
collectDependencies(stmt, variablesInUse);
|
|
1225
|
-
}
|
|
1226
|
-
for (let i = 0;i < node.statements.length; i++) {
|
|
1227
|
-
const stmt = node.statements[i];
|
|
1228
|
-
if (!stmt)
|
|
1229
|
-
continue;
|
|
1230
|
-
if (i === node.statements.length - 1) {
|
|
1231
|
-
filteredStatements.push(stmt);
|
|
1232
|
-
continue;
|
|
1233
|
-
}
|
|
1234
|
-
if (isAssignment(stmt) || isNullishAssignment(stmt)) {
|
|
1235
|
-
if (variablesInUse.has(stmt.name)) {
|
|
1236
|
-
filteredStatements.push(stmt);
|
|
1237
|
-
}
|
|
1238
|
-
} else {
|
|
1239
|
-
filteredStatements.push(stmt);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
if (filteredStatements.length === 1) {
|
|
1243
|
-
const singleStmt = filteredStatements[0];
|
|
1244
|
-
if (!singleStmt) {
|
|
1245
|
-
return node;
|
|
1246
|
-
}
|
|
1247
|
-
if (isNumberLiteral(singleStmt)) {
|
|
1248
|
-
return singleStmt;
|
|
1249
|
-
}
|
|
1250
|
-
if (isAssignment(singleStmt) && isNumberLiteral(singleStmt.value)) {
|
|
1251
|
-
if (!analysis.liveVariables.has(singleStmt.name)) {
|
|
1252
|
-
return singleStmt.value;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
const evaluated = evaluateWithConstants(singleStmt, allConstants);
|
|
1256
|
-
if (isNumberLiteral(evaluated)) {
|
|
1257
|
-
return evaluated;
|
|
1258
|
-
}
|
|
878
|
+
const optimizedArgs = node.arguments.map((arg) => optimize(arg));
|
|
879
|
+
return functionCall(node.name, optimizedArgs);
|
|
1259
880
|
}
|
|
1260
|
-
if (filteredStatements.length === 0) {
|
|
1261
|
-
const lastStmt2 = node.statements[node.statements.length - 1];
|
|
1262
|
-
if (lastStmt2 && isAssignment(lastStmt2) && isNumberLiteral(lastStmt2.value)) {
|
|
1263
|
-
return lastStmt2.value;
|
|
1264
|
-
}
|
|
1265
|
-
return node;
|
|
1266
|
-
}
|
|
1267
|
-
return {
|
|
1268
|
-
type: "Program",
|
|
1269
|
-
statements: filteredStatements
|
|
1270
|
-
};
|
|
1271
|
-
}
|
|
1272
|
-
function basicOptimize(node) {
|
|
1273
881
|
if (isAssignment(node)) {
|
|
1274
|
-
return
|
|
1275
|
-
...node,
|
|
1276
|
-
value: basicOptimize(node.value)
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
if (isNullishAssignment(node)) {
|
|
1280
|
-
return {
|
|
1281
|
-
...node,
|
|
1282
|
-
value: basicOptimize(node.value)
|
|
1283
|
-
};
|
|
1284
|
-
}
|
|
1285
|
-
if (isBinaryOp(node)) {
|
|
1286
|
-
const left = basicOptimize(node.left);
|
|
1287
|
-
const right = basicOptimize(node.right);
|
|
1288
|
-
if (isNumberLiteral(left) && isNumberLiteral(right)) {
|
|
1289
|
-
const result = evaluateBinaryOperation(node.operator, left.value, right.value);
|
|
1290
|
-
return number(result);
|
|
1291
|
-
}
|
|
1292
|
-
return {
|
|
1293
|
-
...node,
|
|
1294
|
-
left,
|
|
1295
|
-
right
|
|
1296
|
-
};
|
|
1297
|
-
}
|
|
1298
|
-
if (isUnaryOp(node)) {
|
|
1299
|
-
const argument = basicOptimize(node.argument);
|
|
1300
|
-
if (isNumberLiteral(argument)) {
|
|
1301
|
-
return number(-argument.value);
|
|
1302
|
-
}
|
|
1303
|
-
return {
|
|
1304
|
-
...node,
|
|
1305
|
-
argument
|
|
1306
|
-
};
|
|
1307
|
-
}
|
|
1308
|
-
if (isFunctionCall(node)) {
|
|
1309
|
-
return {
|
|
1310
|
-
...node,
|
|
1311
|
-
arguments: node.arguments.map((arg) => basicOptimize(arg))
|
|
1312
|
-
};
|
|
882
|
+
return assign(node.name, optimize(node.value));
|
|
1313
883
|
}
|
|
1314
884
|
if (isConditionalExpression(node)) {
|
|
1315
|
-
const condition =
|
|
1316
|
-
const consequent = basicOptimize(node.consequent);
|
|
1317
|
-
const alternate = basicOptimize(node.alternate);
|
|
885
|
+
const condition = optimize(node.condition);
|
|
1318
886
|
if (isNumberLiteral(condition)) {
|
|
1319
|
-
return condition.value !== 0 ? consequent : alternate;
|
|
887
|
+
return condition.value !== 0 ? optimize(node.consequent) : optimize(node.alternate);
|
|
1320
888
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
consequent,
|
|
1325
|
-
alternate
|
|
1326
|
-
};
|
|
889
|
+
const consequent = optimize(node.consequent);
|
|
890
|
+
const alternate = optimize(node.alternate);
|
|
891
|
+
return conditional(condition, consequent, alternate);
|
|
1327
892
|
}
|
|
1328
893
|
if (isProgram(node)) {
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
statements: node.statements.map((stmt) => basicOptimize(stmt))
|
|
1332
|
-
};
|
|
894
|
+
const optimizedStatements = node.statements.map((stmt) => optimize(stmt));
|
|
895
|
+
return program(optimizedStatements);
|
|
1333
896
|
}
|
|
1334
897
|
return node;
|
|
1335
898
|
}
|
|
@@ -1339,7 +902,6 @@ export {
|
|
|
1339
902
|
isUnaryOp,
|
|
1340
903
|
isProgram,
|
|
1341
904
|
isNumberLiteral,
|
|
1342
|
-
isNullishAssignment,
|
|
1343
905
|
isIdentifier,
|
|
1344
906
|
isFunctionCall,
|
|
1345
907
|
isConditionalExpression,
|