littlewing 0.4.1 → 0.5.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 +278 -40
- package/dist/index.d.ts +139 -8
- package/dist/index.js +621 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,13 +16,23 @@ __export(exports_ast, {
|
|
|
16
16
|
unaryOp: () => unaryOp,
|
|
17
17
|
subtract: () => subtract,
|
|
18
18
|
number: () => number,
|
|
19
|
+
nullishAssign: () => nullishAssign,
|
|
20
|
+
notEquals: () => notEquals,
|
|
19
21
|
negate: () => negate,
|
|
20
22
|
multiply: () => multiply,
|
|
21
23
|
modulo: () => modulo,
|
|
24
|
+
logicalOr: () => logicalOr,
|
|
25
|
+
logicalAnd: () => logicalAnd,
|
|
26
|
+
lessThan: () => lessThan,
|
|
27
|
+
lessEqual: () => lessEqual,
|
|
22
28
|
identifier: () => identifier,
|
|
29
|
+
greaterThan: () => greaterThan,
|
|
30
|
+
greaterEqual: () => greaterEqual,
|
|
23
31
|
functionCall: () => functionCall,
|
|
24
32
|
exponentiate: () => exponentiate,
|
|
33
|
+
equals: () => equals,
|
|
25
34
|
divide: () => divide,
|
|
35
|
+
conditional: () => conditional,
|
|
26
36
|
binaryOp: () => binaryOp,
|
|
27
37
|
assign: () => assign,
|
|
28
38
|
add: () => add
|
|
@@ -68,6 +78,21 @@ function assign(name, value) {
|
|
|
68
78
|
value
|
|
69
79
|
};
|
|
70
80
|
}
|
|
81
|
+
function nullishAssign(name, value) {
|
|
82
|
+
return {
|
|
83
|
+
type: "NullishAssignment",
|
|
84
|
+
name,
|
|
85
|
+
value
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function conditional(condition, consequent, alternate) {
|
|
89
|
+
return {
|
|
90
|
+
type: "ConditionalExpression",
|
|
91
|
+
condition,
|
|
92
|
+
consequent,
|
|
93
|
+
alternate
|
|
94
|
+
};
|
|
95
|
+
}
|
|
71
96
|
function add(left, right) {
|
|
72
97
|
return binaryOp(left, "+", right);
|
|
73
98
|
}
|
|
@@ -89,6 +114,30 @@ function exponentiate(left, right) {
|
|
|
89
114
|
function negate(argument) {
|
|
90
115
|
return unaryOp(argument);
|
|
91
116
|
}
|
|
117
|
+
function equals(left, right) {
|
|
118
|
+
return binaryOp(left, "==", right);
|
|
119
|
+
}
|
|
120
|
+
function notEquals(left, right) {
|
|
121
|
+
return binaryOp(left, "!=", right);
|
|
122
|
+
}
|
|
123
|
+
function lessThan(left, right) {
|
|
124
|
+
return binaryOp(left, "<", right);
|
|
125
|
+
}
|
|
126
|
+
function greaterThan(left, right) {
|
|
127
|
+
return binaryOp(left, ">", right);
|
|
128
|
+
}
|
|
129
|
+
function lessEqual(left, right) {
|
|
130
|
+
return binaryOp(left, "<=", right);
|
|
131
|
+
}
|
|
132
|
+
function greaterEqual(left, right) {
|
|
133
|
+
return binaryOp(left, ">=", right);
|
|
134
|
+
}
|
|
135
|
+
function logicalAnd(left, right) {
|
|
136
|
+
return binaryOp(left, "&&", right);
|
|
137
|
+
}
|
|
138
|
+
function logicalOr(left, right) {
|
|
139
|
+
return binaryOp(left, "||", right);
|
|
140
|
+
}
|
|
92
141
|
// src/types.ts
|
|
93
142
|
var TokenType;
|
|
94
143
|
((TokenType2) => {
|
|
@@ -100,10 +149,21 @@ var TokenType;
|
|
|
100
149
|
TokenType2["SLASH"] = "SLASH";
|
|
101
150
|
TokenType2["PERCENT"] = "PERCENT";
|
|
102
151
|
TokenType2["CARET"] = "CARET";
|
|
152
|
+
TokenType2["DOUBLE_EQUALS"] = "DOUBLE_EQUALS";
|
|
153
|
+
TokenType2["NOT_EQUALS"] = "NOT_EQUALS";
|
|
154
|
+
TokenType2["LESS_THAN"] = "LESS_THAN";
|
|
155
|
+
TokenType2["GREATER_THAN"] = "GREATER_THAN";
|
|
156
|
+
TokenType2["LESS_EQUAL"] = "LESS_EQUAL";
|
|
157
|
+
TokenType2["GREATER_EQUAL"] = "GREATER_EQUAL";
|
|
158
|
+
TokenType2["LOGICAL_AND"] = "LOGICAL_AND";
|
|
159
|
+
TokenType2["LOGICAL_OR"] = "LOGICAL_OR";
|
|
103
160
|
TokenType2["LPAREN"] = "LPAREN";
|
|
104
161
|
TokenType2["RPAREN"] = "RPAREN";
|
|
105
162
|
TokenType2["EQUALS"] = "EQUALS";
|
|
106
163
|
TokenType2["COMMA"] = "COMMA";
|
|
164
|
+
TokenType2["QUESTION"] = "QUESTION";
|
|
165
|
+
TokenType2["COLON"] = "COLON";
|
|
166
|
+
TokenType2["NULLISH_ASSIGN"] = "NULLISH_ASSIGN";
|
|
107
167
|
TokenType2["EOF"] = "EOF";
|
|
108
168
|
})(TokenType ||= {});
|
|
109
169
|
function isNumberLiteral(node) {
|
|
@@ -127,6 +187,12 @@ function isAssignment(node) {
|
|
|
127
187
|
function isProgram(node) {
|
|
128
188
|
return node.type === "Program";
|
|
129
189
|
}
|
|
190
|
+
function isConditionalExpression(node) {
|
|
191
|
+
return node.type === "ConditionalExpression";
|
|
192
|
+
}
|
|
193
|
+
function isNullishAssignment(node) {
|
|
194
|
+
return node.type === "NullishAssignment";
|
|
195
|
+
}
|
|
130
196
|
|
|
131
197
|
// src/codegen.ts
|
|
132
198
|
class CodeGenerator {
|
|
@@ -145,6 +211,10 @@ class CodeGenerator {
|
|
|
145
211
|
return this.generateFunctionCall(node);
|
|
146
212
|
if (isAssignment(node))
|
|
147
213
|
return this.generateAssignment(node);
|
|
214
|
+
if (isNullishAssignment(node))
|
|
215
|
+
return this.generateNullishAssignment(node);
|
|
216
|
+
if (isConditionalExpression(node))
|
|
217
|
+
return this.generateConditionalExpression(node);
|
|
148
218
|
throw new Error(`Unknown node type`);
|
|
149
219
|
}
|
|
150
220
|
generateProgram(node) {
|
|
@@ -179,6 +249,18 @@ class CodeGenerator {
|
|
|
179
249
|
const value = this.generate(node.value);
|
|
180
250
|
return `${node.name} = ${value}`;
|
|
181
251
|
}
|
|
252
|
+
generateNullishAssignment(node) {
|
|
253
|
+
const value = this.generate(node.value);
|
|
254
|
+
return `${node.name} ??= ${value}`;
|
|
255
|
+
}
|
|
256
|
+
generateConditionalExpression(node) {
|
|
257
|
+
const condition = this.generate(node.condition);
|
|
258
|
+
const consequent = this.generate(node.consequent);
|
|
259
|
+
const alternate = this.generate(node.alternate);
|
|
260
|
+
const conditionNeedsParens = isAssignment(node.condition) || isBinaryOp(node.condition) && this.getPrecedence(node.condition.operator) <= 2;
|
|
261
|
+
const conditionCode = conditionNeedsParens ? `(${condition})` : condition;
|
|
262
|
+
return `${conditionCode} ? ${consequent} : ${alternate}`;
|
|
263
|
+
}
|
|
182
264
|
needsParensLeft(node, operator) {
|
|
183
265
|
if (!isBinaryOp(node))
|
|
184
266
|
return false;
|
|
@@ -202,14 +284,25 @@ class CodeGenerator {
|
|
|
202
284
|
getPrecedence(operator) {
|
|
203
285
|
switch (operator) {
|
|
204
286
|
case "^":
|
|
205
|
-
return
|
|
287
|
+
return 8;
|
|
206
288
|
case "*":
|
|
207
289
|
case "/":
|
|
208
290
|
case "%":
|
|
209
|
-
return
|
|
291
|
+
return 7;
|
|
210
292
|
case "+":
|
|
211
293
|
case "-":
|
|
212
|
-
return
|
|
294
|
+
return 6;
|
|
295
|
+
case "==":
|
|
296
|
+
case "!=":
|
|
297
|
+
case "<":
|
|
298
|
+
case ">":
|
|
299
|
+
case "<=":
|
|
300
|
+
case ">=":
|
|
301
|
+
return 5;
|
|
302
|
+
case "&&":
|
|
303
|
+
return 4;
|
|
304
|
+
case "||":
|
|
305
|
+
return 3;
|
|
213
306
|
default:
|
|
214
307
|
return 0;
|
|
215
308
|
}
|
|
@@ -309,14 +402,64 @@ class Lexer {
|
|
|
309
402
|
this.position++;
|
|
310
403
|
return { type: "RPAREN" /* RPAREN */, value: ")", position: start };
|
|
311
404
|
case "=":
|
|
405
|
+
if (this.peek() === "=") {
|
|
406
|
+
this.position += 2;
|
|
407
|
+
return { type: "DOUBLE_EQUALS" /* DOUBLE_EQUALS */, value: "==", position: start };
|
|
408
|
+
}
|
|
312
409
|
this.position++;
|
|
313
410
|
return { type: "EQUALS" /* EQUALS */, value: "=", position: start };
|
|
411
|
+
case "!":
|
|
412
|
+
if (this.peek() === "=") {
|
|
413
|
+
this.position += 2;
|
|
414
|
+
return { type: "NOT_EQUALS" /* NOT_EQUALS */, value: "!=", position: start };
|
|
415
|
+
}
|
|
416
|
+
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
417
|
+
case "<":
|
|
418
|
+
if (this.peek() === "=") {
|
|
419
|
+
this.position += 2;
|
|
420
|
+
return { type: "LESS_EQUAL" /* LESS_EQUAL */, value: "<=", position: start };
|
|
421
|
+
}
|
|
422
|
+
this.position++;
|
|
423
|
+
return { type: "LESS_THAN" /* LESS_THAN */, value: "<", position: start };
|
|
424
|
+
case ">":
|
|
425
|
+
if (this.peek() === "=") {
|
|
426
|
+
this.position += 2;
|
|
427
|
+
return { type: "GREATER_EQUAL" /* GREATER_EQUAL */, value: ">=", position: start };
|
|
428
|
+
}
|
|
429
|
+
this.position++;
|
|
430
|
+
return { type: "GREATER_THAN" /* GREATER_THAN */, value: ">", position: start };
|
|
431
|
+
case "?":
|
|
432
|
+
if (this.peek() === "?" && this.peekAhead(2) === "=") {
|
|
433
|
+
this.position += 3;
|
|
434
|
+
return {
|
|
435
|
+
type: "NULLISH_ASSIGN" /* NULLISH_ASSIGN */,
|
|
436
|
+
value: "??=",
|
|
437
|
+
position: start
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
this.position++;
|
|
441
|
+
return { type: "QUESTION" /* QUESTION */, value: "?", position: start };
|
|
442
|
+
case ":":
|
|
443
|
+
this.position++;
|
|
444
|
+
return { type: "COLON" /* COLON */, value: ":", position: start };
|
|
314
445
|
case ",":
|
|
315
446
|
this.position++;
|
|
316
447
|
return { type: "COMMA" /* COMMA */, value: ",", position: start };
|
|
317
448
|
case ";":
|
|
318
449
|
this.position++;
|
|
319
450
|
return this.nextToken();
|
|
451
|
+
case "&":
|
|
452
|
+
if (this.peek() === "&") {
|
|
453
|
+
this.position += 2;
|
|
454
|
+
return { type: "LOGICAL_AND" /* LOGICAL_AND */, value: "&&", position: start };
|
|
455
|
+
}
|
|
456
|
+
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
457
|
+
case "|":
|
|
458
|
+
if (this.peek() === "|") {
|
|
459
|
+
this.position += 2;
|
|
460
|
+
return { type: "LOGICAL_OR" /* LOGICAL_OR */, value: "||", position: start };
|
|
461
|
+
}
|
|
462
|
+
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
320
463
|
default:
|
|
321
464
|
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
322
465
|
}
|
|
@@ -389,6 +532,9 @@ class Lexer {
|
|
|
389
532
|
peek() {
|
|
390
533
|
return this.getCharAt(this.position + 1);
|
|
391
534
|
}
|
|
535
|
+
peekAhead(n) {
|
|
536
|
+
return this.getCharAt(this.position + n);
|
|
537
|
+
}
|
|
392
538
|
isDigit(char) {
|
|
393
539
|
return char >= "0" && char <= "9";
|
|
394
540
|
}
|
|
@@ -442,12 +588,38 @@ class Parser {
|
|
|
442
588
|
}
|
|
443
589
|
const identName = left.name;
|
|
444
590
|
this.advance();
|
|
445
|
-
const value = this.parseExpression(precedence
|
|
591
|
+
const value = this.parseExpression(precedence);
|
|
446
592
|
left = {
|
|
447
593
|
type: "Assignment",
|
|
448
594
|
name: identName,
|
|
449
595
|
value
|
|
450
596
|
};
|
|
597
|
+
} else if (token.type === "NULLISH_ASSIGN" /* NULLISH_ASSIGN */) {
|
|
598
|
+
if (left.type !== "Identifier") {
|
|
599
|
+
throw new Error("Invalid assignment target");
|
|
600
|
+
}
|
|
601
|
+
const identName = left.name;
|
|
602
|
+
this.advance();
|
|
603
|
+
const value = this.parseExpression(precedence);
|
|
604
|
+
left = {
|
|
605
|
+
type: "NullishAssignment",
|
|
606
|
+
name: identName,
|
|
607
|
+
value
|
|
608
|
+
};
|
|
609
|
+
} else if (token.type === "QUESTION" /* QUESTION */) {
|
|
610
|
+
this.advance();
|
|
611
|
+
const consequent = this.parseExpression(0);
|
|
612
|
+
if (this.peek().type !== "COLON" /* COLON */) {
|
|
613
|
+
throw new Error("Expected : in ternary expression");
|
|
614
|
+
}
|
|
615
|
+
this.advance();
|
|
616
|
+
const alternate = this.parseExpression(precedence);
|
|
617
|
+
left = {
|
|
618
|
+
type: "ConditionalExpression",
|
|
619
|
+
condition: left,
|
|
620
|
+
consequent,
|
|
621
|
+
alternate
|
|
622
|
+
};
|
|
451
623
|
} else if (this.isBinaryOperator(token.type)) {
|
|
452
624
|
const operator = token.value;
|
|
453
625
|
this.advance();
|
|
@@ -529,25 +701,39 @@ class Parser {
|
|
|
529
701
|
getPrecedence(type) {
|
|
530
702
|
switch (type) {
|
|
531
703
|
case "EQUALS" /* EQUALS */:
|
|
704
|
+
case "NULLISH_ASSIGN" /* NULLISH_ASSIGN */:
|
|
532
705
|
return 1;
|
|
706
|
+
case "QUESTION" /* QUESTION */:
|
|
707
|
+
return 2;
|
|
708
|
+
case "LOGICAL_OR" /* LOGICAL_OR */:
|
|
709
|
+
return 3;
|
|
710
|
+
case "LOGICAL_AND" /* LOGICAL_AND */:
|
|
711
|
+
return 4;
|
|
712
|
+
case "DOUBLE_EQUALS" /* DOUBLE_EQUALS */:
|
|
713
|
+
case "NOT_EQUALS" /* NOT_EQUALS */:
|
|
714
|
+
case "LESS_THAN" /* LESS_THAN */:
|
|
715
|
+
case "GREATER_THAN" /* GREATER_THAN */:
|
|
716
|
+
case "LESS_EQUAL" /* LESS_EQUAL */:
|
|
717
|
+
case "GREATER_EQUAL" /* GREATER_EQUAL */:
|
|
718
|
+
return 5;
|
|
533
719
|
case "PLUS" /* PLUS */:
|
|
534
720
|
case "MINUS" /* MINUS */:
|
|
535
|
-
return
|
|
721
|
+
return 6;
|
|
536
722
|
case "STAR" /* STAR */:
|
|
537
723
|
case "SLASH" /* SLASH */:
|
|
538
724
|
case "PERCENT" /* PERCENT */:
|
|
539
|
-
return
|
|
725
|
+
return 7;
|
|
540
726
|
case "CARET" /* CARET */:
|
|
541
|
-
return
|
|
727
|
+
return 8;
|
|
542
728
|
default:
|
|
543
729
|
return 0;
|
|
544
730
|
}
|
|
545
731
|
}
|
|
546
732
|
getUnaryPrecedence() {
|
|
547
|
-
return
|
|
733
|
+
return 6;
|
|
548
734
|
}
|
|
549
735
|
isBinaryOperator(type) {
|
|
550
|
-
return type === "PLUS" /* PLUS */ || type === "MINUS" /* MINUS */ || type === "STAR" /* STAR */ || type === "SLASH" /* SLASH */ || type === "PERCENT" /* PERCENT */ || type === "CARET" /* CARET */;
|
|
736
|
+
return type === "PLUS" /* PLUS */ || type === "MINUS" /* MINUS */ || type === "STAR" /* STAR */ || type === "SLASH" /* SLASH */ || type === "PERCENT" /* PERCENT */ || type === "CARET" /* CARET */ || type === "DOUBLE_EQUALS" /* DOUBLE_EQUALS */ || type === "NOT_EQUALS" /* NOT_EQUALS */ || type === "LESS_THAN" /* LESS_THAN */ || type === "GREATER_THAN" /* GREATER_THAN */ || type === "LESS_EQUAL" /* LESS_EQUAL */ || type === "GREATER_EQUAL" /* GREATER_EQUAL */ || type === "LOGICAL_AND" /* LOGICAL_AND */ || type === "LOGICAL_OR" /* LOGICAL_OR */;
|
|
551
737
|
}
|
|
552
738
|
peek() {
|
|
553
739
|
if (this.current >= this.tokens.length) {
|
|
@@ -593,6 +779,10 @@ class Executor {
|
|
|
593
779
|
return this.executeFunctionCall(node);
|
|
594
780
|
if (isAssignment(node))
|
|
595
781
|
return this.executeAssignment(node);
|
|
782
|
+
if (isNullishAssignment(node))
|
|
783
|
+
return this.executeNullishAssignment(node);
|
|
784
|
+
if (isConditionalExpression(node))
|
|
785
|
+
return this.executeConditionalExpression(node);
|
|
596
786
|
throw new Error(`Unknown node type`);
|
|
597
787
|
}
|
|
598
788
|
executeProgram(node) {
|
|
@@ -634,6 +824,22 @@ class Executor {
|
|
|
634
824
|
return left % right;
|
|
635
825
|
case "^":
|
|
636
826
|
return left ** right;
|
|
827
|
+
case "==":
|
|
828
|
+
return left === right ? 1 : 0;
|
|
829
|
+
case "!=":
|
|
830
|
+
return left !== right ? 1 : 0;
|
|
831
|
+
case "<":
|
|
832
|
+
return left < right ? 1 : 0;
|
|
833
|
+
case ">":
|
|
834
|
+
return left > right ? 1 : 0;
|
|
835
|
+
case "<=":
|
|
836
|
+
return left <= right ? 1 : 0;
|
|
837
|
+
case ">=":
|
|
838
|
+
return left >= right ? 1 : 0;
|
|
839
|
+
case "&&":
|
|
840
|
+
return left !== 0 && right !== 0 ? 1 : 0;
|
|
841
|
+
case "||":
|
|
842
|
+
return left !== 0 || right !== 0 ? 1 : 0;
|
|
637
843
|
default:
|
|
638
844
|
throw new Error(`Unknown operator: ${node.operator}`);
|
|
639
845
|
}
|
|
@@ -661,6 +867,18 @@ class Executor {
|
|
|
661
867
|
this.variables.set(node.name, value);
|
|
662
868
|
return value;
|
|
663
869
|
}
|
|
870
|
+
executeNullishAssignment(node) {
|
|
871
|
+
if (this.variables.has(node.name)) {
|
|
872
|
+
return this.variables.get(node.name);
|
|
873
|
+
}
|
|
874
|
+
const value = this.execute(node.value);
|
|
875
|
+
this.variables.set(node.name, value);
|
|
876
|
+
return value;
|
|
877
|
+
}
|
|
878
|
+
executeConditionalExpression(node) {
|
|
879
|
+
const condition = this.execute(node.condition);
|
|
880
|
+
return condition !== 0 ? this.execute(node.consequent) : this.execute(node.alternate);
|
|
881
|
+
}
|
|
664
882
|
}
|
|
665
883
|
function execute(source, context) {
|
|
666
884
|
const ast = parseSource(source);
|
|
@@ -669,21 +887,371 @@ function execute(source, context) {
|
|
|
669
887
|
}
|
|
670
888
|
// src/optimizer.ts
|
|
671
889
|
function optimize(node) {
|
|
890
|
+
if (!isProgram(node)) {
|
|
891
|
+
return basicOptimize(node);
|
|
892
|
+
}
|
|
893
|
+
const analysis = analyzeProgram(node);
|
|
894
|
+
const { propagated, allConstants } = propagateConstantsOptimal(node, analysis);
|
|
895
|
+
const optimized = eliminateDeadCodeOptimal(propagated, analysis, allConstants);
|
|
896
|
+
return optimized;
|
|
897
|
+
}
|
|
898
|
+
function analyzeProgram(node) {
|
|
899
|
+
if (!isProgram(node)) {
|
|
900
|
+
return {
|
|
901
|
+
constants: new Map,
|
|
902
|
+
tainted: new Set,
|
|
903
|
+
dependencies: new Map,
|
|
904
|
+
liveVariables: new Set,
|
|
905
|
+
assignmentIndices: new Map,
|
|
906
|
+
evaluationOrder: []
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
const constants = new Map;
|
|
910
|
+
const tainted = new Set;
|
|
911
|
+
const dependencies = new Map;
|
|
912
|
+
const assignmentIndices = new Map;
|
|
913
|
+
const assignmentCounts = new Map;
|
|
914
|
+
for (let i = 0;i < node.statements.length; i++) {
|
|
915
|
+
const stmt = node.statements[i];
|
|
916
|
+
if (!stmt)
|
|
917
|
+
continue;
|
|
918
|
+
if (isAssignment(stmt) || isNullishAssignment(stmt)) {
|
|
919
|
+
const varName = stmt.name;
|
|
920
|
+
const count = assignmentCounts.get(varName) || 0;
|
|
921
|
+
assignmentCounts.set(varName, count + 1);
|
|
922
|
+
assignmentIndices.set(varName, i);
|
|
923
|
+
const deps = new Set;
|
|
924
|
+
const hasFunctionCall = collectDependencies(stmt.value, deps);
|
|
925
|
+
dependencies.set(varName, deps);
|
|
926
|
+
if (count === 0 && isNumberLiteral(stmt.value)) {
|
|
927
|
+
constants.set(varName, stmt.value.value);
|
|
928
|
+
}
|
|
929
|
+
if (hasFunctionCall) {
|
|
930
|
+
tainted.add(varName);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
for (const [varName, count] of assignmentCounts) {
|
|
935
|
+
if (count > 1) {
|
|
936
|
+
constants.delete(varName);
|
|
937
|
+
tainted.add(varName);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
let taintChanged = true;
|
|
941
|
+
while (taintChanged) {
|
|
942
|
+
taintChanged = false;
|
|
943
|
+
for (const [varName, deps] of dependencies) {
|
|
944
|
+
if (tainted.has(varName))
|
|
945
|
+
continue;
|
|
946
|
+
for (const dep of deps) {
|
|
947
|
+
if (tainted.has(dep)) {
|
|
948
|
+
tainted.add(varName);
|
|
949
|
+
constants.delete(varName);
|
|
950
|
+
taintChanged = true;
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const liveVariables = new Set;
|
|
957
|
+
const lastStmt = node.statements[node.statements.length - 1];
|
|
958
|
+
if (lastStmt) {
|
|
959
|
+
if (isAssignment(lastStmt)) {
|
|
960
|
+
const deps = new Set;
|
|
961
|
+
collectDependencies(lastStmt.value, deps);
|
|
962
|
+
for (const dep of deps) {
|
|
963
|
+
liveVariables.add(dep);
|
|
964
|
+
}
|
|
965
|
+
} else {
|
|
966
|
+
const deps = new Set;
|
|
967
|
+
collectDependencies(lastStmt, deps);
|
|
968
|
+
for (const dep of deps) {
|
|
969
|
+
liveVariables.add(dep);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
let liveChanged = true;
|
|
974
|
+
while (liveChanged) {
|
|
975
|
+
liveChanged = false;
|
|
976
|
+
for (const [varName, deps] of dependencies) {
|
|
977
|
+
if (liveVariables.has(varName)) {
|
|
978
|
+
for (const dep of deps) {
|
|
979
|
+
if (!liveVariables.has(dep)) {
|
|
980
|
+
liveVariables.add(dep);
|
|
981
|
+
liveChanged = true;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
const evaluationOrder = topologicalSort(dependencies, liveVariables);
|
|
988
|
+
return {
|
|
989
|
+
constants,
|
|
990
|
+
tainted,
|
|
991
|
+
dependencies,
|
|
992
|
+
liveVariables,
|
|
993
|
+
assignmentIndices,
|
|
994
|
+
evaluationOrder
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
function collectDependencies(node, deps) {
|
|
998
|
+
if (isIdentifier(node)) {
|
|
999
|
+
deps.add(node.name);
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
if (isNumberLiteral(node)) {
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
if (isAssignment(node) || isNullishAssignment(node)) {
|
|
1006
|
+
return collectDependencies(node.value, deps);
|
|
1007
|
+
}
|
|
1008
|
+
if (isBinaryOp(node)) {
|
|
1009
|
+
const leftHasCall = collectDependencies(node.left, deps);
|
|
1010
|
+
const rightHasCall = collectDependencies(node.right, deps);
|
|
1011
|
+
return leftHasCall || rightHasCall;
|
|
1012
|
+
}
|
|
1013
|
+
if (isUnaryOp(node)) {
|
|
1014
|
+
return collectDependencies(node.argument, deps);
|
|
1015
|
+
}
|
|
1016
|
+
if (isFunctionCall(node)) {
|
|
1017
|
+
for (const arg of node.arguments) {
|
|
1018
|
+
collectDependencies(arg, deps);
|
|
1019
|
+
}
|
|
1020
|
+
return true;
|
|
1021
|
+
}
|
|
1022
|
+
if (isConditionalExpression(node)) {
|
|
1023
|
+
const condHasCall = collectDependencies(node.condition, deps);
|
|
1024
|
+
const consHasCall = collectDependencies(node.consequent, deps);
|
|
1025
|
+
const altHasCall = collectDependencies(node.alternate, deps);
|
|
1026
|
+
return condHasCall || consHasCall || altHasCall;
|
|
1027
|
+
}
|
|
672
1028
|
if (isProgram(node)) {
|
|
1029
|
+
let hasCall = false;
|
|
1030
|
+
for (const stmt of node.statements) {
|
|
1031
|
+
hasCall = collectDependencies(stmt, deps) || hasCall;
|
|
1032
|
+
}
|
|
1033
|
+
return hasCall;
|
|
1034
|
+
}
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
function topologicalSort(dependencies, liveVariables) {
|
|
1038
|
+
const result = [];
|
|
1039
|
+
const visited = new Set;
|
|
1040
|
+
const visiting = new Set;
|
|
1041
|
+
function visit(varName) {
|
|
1042
|
+
if (visited.has(varName))
|
|
1043
|
+
return;
|
|
1044
|
+
if (visiting.has(varName)) {
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
visiting.add(varName);
|
|
1048
|
+
const deps = dependencies.get(varName);
|
|
1049
|
+
if (deps) {
|
|
1050
|
+
for (const dep of deps) {
|
|
1051
|
+
if (liveVariables.has(dep)) {
|
|
1052
|
+
visit(dep);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
visiting.delete(varName);
|
|
1057
|
+
visited.add(varName);
|
|
1058
|
+
result.push(varName);
|
|
1059
|
+
}
|
|
1060
|
+
for (const varName of liveVariables) {
|
|
1061
|
+
visit(varName);
|
|
1062
|
+
}
|
|
1063
|
+
return result;
|
|
1064
|
+
}
|
|
1065
|
+
function propagateConstantsOptimal(node, analysis) {
|
|
1066
|
+
if (!isProgram(node)) {
|
|
1067
|
+
return { propagated: node, allConstants: new Map };
|
|
1068
|
+
}
|
|
1069
|
+
const allConstants = new Map(analysis.constants);
|
|
1070
|
+
for (const varName of analysis.evaluationOrder) {
|
|
1071
|
+
if (allConstants.has(varName))
|
|
1072
|
+
continue;
|
|
1073
|
+
if (analysis.tainted.has(varName))
|
|
1074
|
+
continue;
|
|
1075
|
+
const deps = analysis.dependencies.get(varName);
|
|
1076
|
+
if (!deps)
|
|
1077
|
+
continue;
|
|
1078
|
+
let allDepsConstant = true;
|
|
1079
|
+
for (const dep of deps) {
|
|
1080
|
+
if (!allConstants.has(dep)) {
|
|
1081
|
+
allDepsConstant = false;
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (allDepsConstant) {
|
|
1086
|
+
const assignmentIdx = analysis.assignmentIndices.get(varName);
|
|
1087
|
+
if (assignmentIdx !== undefined) {
|
|
1088
|
+
const stmt = node.statements[assignmentIdx];
|
|
1089
|
+
if (stmt && isAssignment(stmt)) {
|
|
1090
|
+
const evaluated = evaluateWithConstants(stmt.value, allConstants);
|
|
1091
|
+
if (isNumberLiteral(evaluated)) {
|
|
1092
|
+
allConstants.set(varName, evaluated.value);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
const statements = node.statements.map((stmt) => replaceWithConstants(stmt, allConstants));
|
|
1099
|
+
return {
|
|
1100
|
+
propagated: {
|
|
1101
|
+
type: "Program",
|
|
1102
|
+
statements
|
|
1103
|
+
},
|
|
1104
|
+
allConstants
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
function evaluateWithConstants(node, constants) {
|
|
1108
|
+
if (isIdentifier(node)) {
|
|
1109
|
+
const value = constants.get(node.name);
|
|
1110
|
+
if (value !== undefined) {
|
|
1111
|
+
return number(value);
|
|
1112
|
+
}
|
|
1113
|
+
return node;
|
|
1114
|
+
}
|
|
1115
|
+
if (isNumberLiteral(node)) {
|
|
1116
|
+
return node;
|
|
1117
|
+
}
|
|
1118
|
+
if (isBinaryOp(node)) {
|
|
1119
|
+
const left = evaluateWithConstants(node.left, constants);
|
|
1120
|
+
const right = evaluateWithConstants(node.right, constants);
|
|
1121
|
+
if (isNumberLiteral(left) && isNumberLiteral(right)) {
|
|
1122
|
+
const result = evaluateBinaryOp(node.operator, left.value, right.value);
|
|
1123
|
+
return number(result);
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
...node,
|
|
1127
|
+
left,
|
|
1128
|
+
right
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
if (isUnaryOp(node)) {
|
|
1132
|
+
const argument = evaluateWithConstants(node.argument, constants);
|
|
1133
|
+
if (isNumberLiteral(argument)) {
|
|
1134
|
+
return number(-argument.value);
|
|
1135
|
+
}
|
|
1136
|
+
return {
|
|
1137
|
+
...node,
|
|
1138
|
+
argument
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
if (isFunctionCall(node)) {
|
|
673
1142
|
return {
|
|
674
1143
|
...node,
|
|
675
|
-
|
|
1144
|
+
arguments: node.arguments.map((arg) => evaluateWithConstants(arg, constants))
|
|
676
1145
|
};
|
|
677
1146
|
}
|
|
1147
|
+
if (isConditionalExpression(node)) {
|
|
1148
|
+
const condition = evaluateWithConstants(node.condition, constants);
|
|
1149
|
+
const consequent = evaluateWithConstants(node.consequent, constants);
|
|
1150
|
+
const alternate = evaluateWithConstants(node.alternate, constants);
|
|
1151
|
+
if (isNumberLiteral(condition)) {
|
|
1152
|
+
return condition.value !== 0 ? consequent : alternate;
|
|
1153
|
+
}
|
|
1154
|
+
return {
|
|
1155
|
+
...node,
|
|
1156
|
+
condition,
|
|
1157
|
+
consequent,
|
|
1158
|
+
alternate
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
if (isAssignment(node)) {
|
|
1162
|
+
return {
|
|
1163
|
+
...node,
|
|
1164
|
+
value: evaluateWithConstants(node.value, constants)
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
if (isNullishAssignment(node)) {
|
|
1168
|
+
return {
|
|
1169
|
+
...node,
|
|
1170
|
+
value: evaluateWithConstants(node.value, constants)
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
return node;
|
|
1174
|
+
}
|
|
1175
|
+
function replaceWithConstants(node, constants) {
|
|
1176
|
+
return evaluateWithConstants(node, constants);
|
|
1177
|
+
}
|
|
1178
|
+
function eliminateDeadCodeOptimal(node, analysis, allConstants) {
|
|
1179
|
+
if (!isProgram(node))
|
|
1180
|
+
return node;
|
|
1181
|
+
const lastStmt = node.statements[node.statements.length - 1];
|
|
1182
|
+
if (lastStmt) {
|
|
1183
|
+
const evaluated = evaluateWithConstants(lastStmt, allConstants);
|
|
1184
|
+
if (isNumberLiteral(evaluated)) {
|
|
1185
|
+
return evaluated;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const filteredStatements = [];
|
|
1189
|
+
const variablesInUse = new Set;
|
|
1190
|
+
for (const stmt of node.statements) {
|
|
1191
|
+
collectDependencies(stmt, variablesInUse);
|
|
1192
|
+
}
|
|
1193
|
+
for (let i = 0;i < node.statements.length; i++) {
|
|
1194
|
+
const stmt = node.statements[i];
|
|
1195
|
+
if (!stmt)
|
|
1196
|
+
continue;
|
|
1197
|
+
if (i === node.statements.length - 1) {
|
|
1198
|
+
filteredStatements.push(stmt);
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
if (isAssignment(stmt) || isNullishAssignment(stmt)) {
|
|
1202
|
+
if (variablesInUse.has(stmt.name)) {
|
|
1203
|
+
filteredStatements.push(stmt);
|
|
1204
|
+
}
|
|
1205
|
+
} else {
|
|
1206
|
+
filteredStatements.push(stmt);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
if (filteredStatements.length === 1) {
|
|
1210
|
+
const singleStmt = filteredStatements[0];
|
|
1211
|
+
if (!singleStmt) {
|
|
1212
|
+
return node;
|
|
1213
|
+
}
|
|
1214
|
+
if (isNumberLiteral(singleStmt)) {
|
|
1215
|
+
return singleStmt;
|
|
1216
|
+
}
|
|
1217
|
+
if (isAssignment(singleStmt) && isNumberLiteral(singleStmt.value)) {
|
|
1218
|
+
if (!analysis.liveVariables.has(singleStmt.name)) {
|
|
1219
|
+
return singleStmt.value;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
const evaluated = evaluateWithConstants(singleStmt, allConstants);
|
|
1223
|
+
if (isNumberLiteral(evaluated)) {
|
|
1224
|
+
return evaluated;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
if (filteredStatements.length === 0) {
|
|
1228
|
+
const lastStmt2 = node.statements[node.statements.length - 1];
|
|
1229
|
+
if (lastStmt2 && isAssignment(lastStmt2) && isNumberLiteral(lastStmt2.value)) {
|
|
1230
|
+
return lastStmt2.value;
|
|
1231
|
+
}
|
|
1232
|
+
return node;
|
|
1233
|
+
}
|
|
1234
|
+
return {
|
|
1235
|
+
type: "Program",
|
|
1236
|
+
statements: filteredStatements
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
function basicOptimize(node) {
|
|
678
1240
|
if (isAssignment(node)) {
|
|
679
1241
|
return {
|
|
680
1242
|
...node,
|
|
681
|
-
value:
|
|
1243
|
+
value: basicOptimize(node.value)
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
if (isNullishAssignment(node)) {
|
|
1247
|
+
return {
|
|
1248
|
+
...node,
|
|
1249
|
+
value: basicOptimize(node.value)
|
|
682
1250
|
};
|
|
683
1251
|
}
|
|
684
1252
|
if (isBinaryOp(node)) {
|
|
685
|
-
const left =
|
|
686
|
-
const right =
|
|
1253
|
+
const left = basicOptimize(node.left);
|
|
1254
|
+
const right = basicOptimize(node.right);
|
|
687
1255
|
if (isNumberLiteral(left) && isNumberLiteral(right)) {
|
|
688
1256
|
const result = evaluateBinaryOp(node.operator, left.value, right.value);
|
|
689
1257
|
return number(result);
|
|
@@ -695,7 +1263,7 @@ function optimize(node) {
|
|
|
695
1263
|
};
|
|
696
1264
|
}
|
|
697
1265
|
if (isUnaryOp(node)) {
|
|
698
|
-
const argument =
|
|
1266
|
+
const argument = basicOptimize(node.argument);
|
|
699
1267
|
if (isNumberLiteral(argument)) {
|
|
700
1268
|
return number(-argument.value);
|
|
701
1269
|
}
|
|
@@ -707,7 +1275,27 @@ function optimize(node) {
|
|
|
707
1275
|
if (isFunctionCall(node)) {
|
|
708
1276
|
return {
|
|
709
1277
|
...node,
|
|
710
|
-
arguments: node.arguments.map((arg) =>
|
|
1278
|
+
arguments: node.arguments.map((arg) => basicOptimize(arg))
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
if (isConditionalExpression(node)) {
|
|
1282
|
+
const condition = basicOptimize(node.condition);
|
|
1283
|
+
const consequent = basicOptimize(node.consequent);
|
|
1284
|
+
const alternate = basicOptimize(node.alternate);
|
|
1285
|
+
if (isNumberLiteral(condition)) {
|
|
1286
|
+
return condition.value !== 0 ? consequent : alternate;
|
|
1287
|
+
}
|
|
1288
|
+
return {
|
|
1289
|
+
...node,
|
|
1290
|
+
condition,
|
|
1291
|
+
consequent,
|
|
1292
|
+
alternate
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
if (isProgram(node)) {
|
|
1296
|
+
return {
|
|
1297
|
+
...node,
|
|
1298
|
+
statements: node.statements.map((stmt) => basicOptimize(stmt))
|
|
711
1299
|
};
|
|
712
1300
|
}
|
|
713
1301
|
return node;
|
|
@@ -732,6 +1320,22 @@ function evaluateBinaryOp(operator, left, right) {
|
|
|
732
1320
|
return left % right;
|
|
733
1321
|
case "^":
|
|
734
1322
|
return left ** right;
|
|
1323
|
+
case "==":
|
|
1324
|
+
return left === right ? 1 : 0;
|
|
1325
|
+
case "!=":
|
|
1326
|
+
return left !== right ? 1 : 0;
|
|
1327
|
+
case "<":
|
|
1328
|
+
return left < right ? 1 : 0;
|
|
1329
|
+
case ">":
|
|
1330
|
+
return left > right ? 1 : 0;
|
|
1331
|
+
case "<=":
|
|
1332
|
+
return left <= right ? 1 : 0;
|
|
1333
|
+
case ">=":
|
|
1334
|
+
return left >= right ? 1 : 0;
|
|
1335
|
+
case "&&":
|
|
1336
|
+
return left !== 0 && right !== 0 ? 1 : 0;
|
|
1337
|
+
case "||":
|
|
1338
|
+
return left !== 0 || right !== 0 ? 1 : 0;
|
|
735
1339
|
default:
|
|
736
1340
|
throw new Error(`Unknown operator: ${operator}`);
|
|
737
1341
|
}
|
|
@@ -742,8 +1346,10 @@ export {
|
|
|
742
1346
|
isUnaryOp,
|
|
743
1347
|
isProgram,
|
|
744
1348
|
isNumberLiteral,
|
|
1349
|
+
isNullishAssignment,
|
|
745
1350
|
isIdentifier,
|
|
746
1351
|
isFunctionCall,
|
|
1352
|
+
isConditionalExpression,
|
|
747
1353
|
isBinaryOp,
|
|
748
1354
|
isAssignment,
|
|
749
1355
|
generate,
|