more optimizations for ifs/conditionals
(XXX: should add tests before anything else)
This commit is contained in:
167
lib/compress.js
167
lib/compress.js
@@ -60,7 +60,7 @@ function Compressor(options, false_by_default) {
|
||||
keep_comps : !false_by_default,
|
||||
drop_debugger : !false_by_default,
|
||||
unsafe : !false_by_default,
|
||||
ifs : !false_by_default,
|
||||
conditionals : !false_by_default,
|
||||
comparations : !false_by_default,
|
||||
evaluate : !false_by_default,
|
||||
|
||||
@@ -88,6 +88,10 @@ function Compressor(options, false_by_default) {
|
||||
return this;
|
||||
});
|
||||
|
||||
AST_Node.DEFMETHOD("optimize", function(){
|
||||
return this;
|
||||
});
|
||||
|
||||
function make_node(ctor, orig, props) {
|
||||
if (!props) props = {};
|
||||
if (!props.start) props.start = orig.start;
|
||||
@@ -143,7 +147,7 @@ function Compressor(options, false_by_default) {
|
||||
if (stat instanceof AST_Defun) {
|
||||
a.push(stat);
|
||||
}
|
||||
else if (compressor.option("warnings")) {
|
||||
else {
|
||||
stat.walk(new TreeWalker(function(node){
|
||||
if (node instanceof AST_Definitions || node instanceof AST_Defun) {
|
||||
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
|
||||
@@ -254,13 +258,22 @@ function Compressor(options, false_by_default) {
|
||||
node.DEFMETHOD("is_string", func);
|
||||
});
|
||||
|
||||
// function best_of(ast1, ast2) {
|
||||
// return ast1.print_to_string().length > ast2.print_to_string().length ? ast2 : ast1;
|
||||
// };
|
||||
function best_of(ast1, ast2) {
|
||||
return ast1.print_to_string({ beautify: false }).length >
|
||||
ast2.print_to_string({ beautify: false }).length
|
||||
? ast2 : ast1;
|
||||
};
|
||||
|
||||
// methods to evaluate a constant expression
|
||||
(function (def){
|
||||
AST_Node.DEFMETHOD("evaluate", function(compressor, constant, not_constant){
|
||||
// The evaluate method returns an array with one or two
|
||||
// elements. If the node has been successfully reduced to a
|
||||
// constant, then the second element tells us the value;
|
||||
// otherwise the second element is missing. The first element
|
||||
// of the array is always an AST_Node descendant; when
|
||||
// evaluation was successful it's a node that represents the
|
||||
// constant; otherwise it's the original node.
|
||||
AST_Node.DEFMETHOD("evaluate", function(compressor){
|
||||
if (!compressor.option("evaluate")) return this;
|
||||
try {
|
||||
var val = this._eval(), ast;
|
||||
@@ -290,15 +303,13 @@ function Compressor(options, false_by_default) {
|
||||
type: typeof val
|
||||
}));
|
||||
}
|
||||
if (constant) return constant(ast, val);
|
||||
return ast;
|
||||
return [ ast, val ];
|
||||
} catch(ex) {
|
||||
if (ex !== def) throw ex;
|
||||
if (not_constant) return not_constant(this);
|
||||
return this;
|
||||
return [ this ];
|
||||
}
|
||||
});
|
||||
function evaluate(node) {
|
||||
function ev(node) {
|
||||
return node._eval();
|
||||
};
|
||||
def(AST_Node, function(){
|
||||
@@ -310,47 +321,47 @@ function Compressor(options, false_by_default) {
|
||||
def(AST_UnaryPrefix, function(){
|
||||
var e = this.expression;
|
||||
switch (this.operator) {
|
||||
case "!": return !evaluate(e);
|
||||
case "typeof": return typeof evaluate(e);
|
||||
case "~": return ~evaluate(e);
|
||||
case "-": return -evaluate(e);
|
||||
case "+": return +evaluate(e);
|
||||
case "!": return !ev(e);
|
||||
case "typeof": return typeof ev(e);
|
||||
case "~": return ~ev(e);
|
||||
case "-": return -ev(e);
|
||||
case "+": return +ev(e);
|
||||
}
|
||||
throw def;
|
||||
});
|
||||
def(AST_Binary, function(){
|
||||
var left = this.left, right = this.right;
|
||||
switch (this.operator) {
|
||||
case "&&" : return evaluate(left) && evaluate(right);
|
||||
case "||" : return evaluate(left) || evaluate(right);
|
||||
case "|" : return evaluate(left) | evaluate(right);
|
||||
case "&" : return evaluate(left) & evaluate(right);
|
||||
case "^" : return evaluate(left) ^ evaluate(right);
|
||||
case "+" : return evaluate(left) + evaluate(right);
|
||||
case "*" : return evaluate(left) * evaluate(right);
|
||||
case "/" : return evaluate(left) / evaluate(right);
|
||||
case "%" : return evaluate(left) % evaluate(right);
|
||||
case "-" : return evaluate(left) - evaluate(right);
|
||||
case "<<" : return evaluate(left) << evaluate(right);
|
||||
case ">>" : return evaluate(left) >> evaluate(right);
|
||||
case ">>>" : return evaluate(left) >>> evaluate(right);
|
||||
case "==" : return evaluate(left) == evaluate(right);
|
||||
case "===" : return evaluate(left) === evaluate(right);
|
||||
case "!=" : return evaluate(left) != evaluate(right);
|
||||
case "!==" : return evaluate(left) !== evaluate(right);
|
||||
case "<" : return evaluate(left) < evaluate(right);
|
||||
case "<=" : return evaluate(left) <= evaluate(right);
|
||||
case ">" : return evaluate(left) > evaluate(right);
|
||||
case ">=" : return evaluate(left) >= evaluate(right);
|
||||
case "in" : return evaluate(left) in evaluate(right);
|
||||
case "instanceof" : return evaluate(left) instanceof evaluate(right);
|
||||
case "&&" : return ev(left) && ev(right);
|
||||
case "||" : return ev(left) || ev(right);
|
||||
case "|" : return ev(left) | ev(right);
|
||||
case "&" : return ev(left) & ev(right);
|
||||
case "^" : return ev(left) ^ ev(right);
|
||||
case "+" : return ev(left) + ev(right);
|
||||
case "*" : return ev(left) * ev(right);
|
||||
case "/" : return ev(left) / ev(right);
|
||||
case "%" : return ev(left) % ev(right);
|
||||
case "-" : return ev(left) - ev(right);
|
||||
case "<<" : return ev(left) << ev(right);
|
||||
case ">>" : return ev(left) >> ev(right);
|
||||
case ">>>" : return ev(left) >>> ev(right);
|
||||
case "==" : return ev(left) == ev(right);
|
||||
case "===" : return ev(left) === ev(right);
|
||||
case "!=" : return ev(left) != ev(right);
|
||||
case "!==" : return ev(left) !== ev(right);
|
||||
case "<" : return ev(left) < ev(right);
|
||||
case "<=" : return ev(left) <= ev(right);
|
||||
case ">" : return ev(left) > ev(right);
|
||||
case ">=" : return ev(left) >= ev(right);
|
||||
case "in" : return ev(left) in ev(right);
|
||||
case "instanceof" : return ev(left) instanceof ev(right);
|
||||
}
|
||||
throw def;
|
||||
});
|
||||
def(AST_Conditional, function(){
|
||||
return evaluate(this.condition)
|
||||
? evaluate(this.consequent)
|
||||
: evaluate(this.alternative);
|
||||
return ev(this.condition)
|
||||
? ev(this.consequent)
|
||||
: ev(this.alternative);
|
||||
});
|
||||
})(function(node, func){
|
||||
node.DEFMETHOD("_eval", func);
|
||||
@@ -371,8 +382,8 @@ function Compressor(options, false_by_default) {
|
||||
throw new Error("Cannot evaluate a statement");
|
||||
});
|
||||
def(AST_UnaryPrefix, function(){
|
||||
if (this.operator == "!" && this.expression.is_boolean())
|
||||
return this.expression();
|
||||
if (this.operator == "!")
|
||||
return this.expression;
|
||||
return basic_negation(this);
|
||||
});
|
||||
def(AST_Seq, function(compressor){
|
||||
@@ -498,12 +509,56 @@ function Compressor(options, false_by_default) {
|
||||
self.body = self.body.squeeze(compressor);
|
||||
if (self.alternative)
|
||||
self.alternative = self.alternative.squeeze(compressor);
|
||||
if (!compressor.option("ifs")) return self;
|
||||
return self;
|
||||
return compressor.option("conditionals") ? self.optimize(compressor) : self;
|
||||
});
|
||||
|
||||
AST_If.DEFMETHOD("switch_branches", function(){
|
||||
|
||||
AST_If.DEFMETHOD("optimize", function(compressor){
|
||||
// if condition can be statically determined, warn and drop
|
||||
// one of the blocks. note, statically determined implies
|
||||
// “has no side effects”; also it doesn't work for cases like
|
||||
// `x && true`, though it probably should.
|
||||
var self = this;
|
||||
var cond = self.condition.evaluate(compressor);
|
||||
if (cond.length == 2) {
|
||||
if (cond[1]) {
|
||||
AST_Node.warn("Condition always true [{line},{col}]", self.condition.start);
|
||||
return self.body;
|
||||
} else {
|
||||
AST_Node.warn("Condition always false [{line},{col}]", self.condition.start);
|
||||
return self.alternative || new AST_EmptyStatement(self);
|
||||
}
|
||||
}
|
||||
if (self.body instanceof AST_SimpleStatement
|
||||
&& self.alternative instanceof AST_SimpleStatement) {
|
||||
return make_node(AST_SimpleStatement, self, {
|
||||
body: make_node(AST_Conditional, self, {
|
||||
condition : self.condition,
|
||||
consequent : self.body.body,
|
||||
alternative : self.alternative.body
|
||||
}).optimize(compressor)
|
||||
});
|
||||
}
|
||||
if (!self.alternative && self.body instanceof AST_SimpleStatement) {
|
||||
return make_node(AST_SimpleStatement, self, {
|
||||
body: make_node(AST_Binary, self, {
|
||||
operator: "&&",
|
||||
left: self.condition,
|
||||
right: self.body.body
|
||||
}).optimize(compressor)
|
||||
});
|
||||
}
|
||||
if (self.body instanceof AST_EmptyStatement
|
||||
&& self.alternative
|
||||
&& !(self.alternative instanceof AST_EmptyStatement)) {
|
||||
return make_node(AST_SimpleStatement, self, {
|
||||
body: make_node(AST_Binary, self, {
|
||||
operator: "||",
|
||||
left: self.condition,
|
||||
right: self.alternative.body
|
||||
}).optimize(compressor)
|
||||
});
|
||||
}
|
||||
return self;
|
||||
});
|
||||
|
||||
SQUEEZE(AST_Switch, function(self, compressor){
|
||||
@@ -599,14 +654,14 @@ function Compressor(options, false_by_default) {
|
||||
SQUEEZE(AST_UnaryPrefix, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.expression = self.expression.squeeze(compressor);
|
||||
return self.evaluate(compressor);
|
||||
return self.evaluate(compressor)[0];
|
||||
});
|
||||
|
||||
SQUEEZE(AST_Binary, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.left = self.left.squeeze(compressor);
|
||||
self.right = self.right.squeeze(compressor);
|
||||
return self.evaluate(compressor);
|
||||
return self.evaluate(compressor)[0];
|
||||
});
|
||||
|
||||
SQUEEZE(AST_Assign, function(self, compressor){
|
||||
@@ -621,7 +676,17 @@ function Compressor(options, false_by_default) {
|
||||
self.condition = self.condition.squeeze(compressor);
|
||||
self.consequent = self.consequent.squeeze(compressor);
|
||||
self.alternative = self.alternative.squeeze(compressor);
|
||||
return self;
|
||||
return compressor.option("conditionals") ? self.optimize(compressor) : self;
|
||||
});
|
||||
|
||||
AST_Conditional.DEFMETHOD("optimize", function(compressor){
|
||||
var self = this;
|
||||
var rev = self.clone();
|
||||
rev.condition = rev.condition.negate(compressor);
|
||||
var tmp = rev.consequent;
|
||||
rev.consequent = rev.alternative;
|
||||
rev.alternative = tmp;
|
||||
return best_of(self, rev);
|
||||
});
|
||||
|
||||
SQUEEZE(AST_Array, function(self, compressor){
|
||||
|
||||
Reference in New Issue
Block a user