Merge branch 'master' into harmony

This commit is contained in:
Richard van Velzen
2017-01-26 13:02:22 +01:00
16 changed files with 1268 additions and 277 deletions

View File

@@ -152,6 +152,12 @@ merge(Compressor.prototype, {
AST_Node.DEFMETHOD("clear_opt_flags", function(){
this.walk(new TreeWalker(function(node){
if (node instanceof AST_SymbolRef) {
var d = node.definition();
if (d && d.init) {
delete d.init._evaluated;
}
}
if (!(node instanceof AST_Directive || node instanceof AST_Constant)) {
node._squeezed = false;
node._optimized = false;
@@ -349,7 +355,7 @@ merge(Compressor.prototype, {
if (ref.scope.uses_eval || ref.scope.uses_with) break;
// Constant single use vars can be replaced in any scope.
if (!(var_decl.value instanceof AST_RegExp) && var_decl.value.is_constant(compressor)) {
if (var_decl.value.is_constant()) {
var ctt = new TreeTransformer(function(node) {
if (node === ref)
return replace_var(node, ctt.parent(), true);
@@ -989,6 +995,11 @@ merge(Compressor.prototype, {
node.DEFMETHOD("is_string", func);
});
function isLHS(node, parent) {
return parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--")
|| parent instanceof AST_Assign && parent.left === node;
}
function best_of(ast1, ast2) {
return ast1.print_to_string().length >
ast2.print_to_string().length
@@ -1006,39 +1017,61 @@ merge(Compressor.prototype, {
// constant; otherwise it's the original or a replacement node.
AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return [ this ];
var val;
try {
var val = this._eval(compressor);
return [ best_of(make_node_from_constant(compressor, val, this), this), val ];
val = this._eval(compressor);
} catch(ex) {
if (ex !== def) throw ex;
return [ this ];
}
var node;
try {
node = make_node_from_constant(compressor, val, this);
} catch(ex) {
return [ this ];
}
return [ best_of(node, this), val ];
});
AST_Node.DEFMETHOD("is_constant", function(compressor){
var unaryPrefix = makePredicate("! ~ - +");
AST_Node.DEFMETHOD("is_constant", function(){
// Accomodate when compress option evaluate=false
// as well as the common constant expressions !0 and !1
return this instanceof AST_Constant
|| (this instanceof AST_UnaryPrefix && this.operator == "!"
&& this.expression instanceof AST_Constant)
|| this.evaluate(compressor).length > 1;
// as well as the common constant expressions !0 and -1
if (this instanceof AST_Constant) {
return !(this instanceof AST_RegExp);
} else {
return this instanceof AST_UnaryPrefix
&& this.expression instanceof AST_Constant
&& unaryPrefix(this.operator);
}
});
// Obtain the constant value of an expression already known to be constant.
// Result only valid iff this.is_constant(compressor) is true.
// Result only valid iff this.is_constant() is true.
AST_Node.DEFMETHOD("constant_value", function(compressor){
// Accomodate when option evaluate=false.
if (this instanceof AST_Constant) return this.value;
// Accomodate the common constant expressions !0 and !1 when option evaluate=false.
if (this instanceof AST_UnaryPrefix
&& this.operator == "!"
&& this.expression instanceof AST_Constant) {
return !this.expression.value;
if (this instanceof AST_Constant && !(this instanceof AST_RegExp)) {
return this.value;
}
var result = this.evaluate(compressor)
// Accomodate the common constant expressions !0 and -1 when option evaluate=false.
if (this instanceof AST_UnaryPrefix
&& this.expression instanceof AST_Constant) switch (this.operator) {
case "!":
return !this.expression.value;
case "~":
return ~this.expression.value;
case "-":
return -this.expression.value;
case "+":
return +this.expression.value;
default:
throw new Error(string_template("Cannot evaluate unary expression {value}", {
value: this.print_to_string()
}));
}
var result = this.evaluate(compressor);
if (result.length > 1) {
return result[1];
}
// should never be reached
return undefined;
throw new Error(string_template("Cannot evaluate constant [{file}:{line},{col}]", this.start));
});
def(AST_Statement, function(){
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
@@ -1071,6 +1104,32 @@ merge(Compressor.prototype, {
if (this.segments.length !== 1) throw def;
return this.segments[0].value;
});
def(AST_Array, function(compressor){
if (compressor.option("unsafe")) {
return this.elements.map(function(element) {
return ev(element, compressor);
});
}
throw def;
});
def(AST_Object, function(compressor){
if (compressor.option("unsafe")) {
var val = {};
for (var i = 0, len = this.properties.length; i < len; i++) {
var prop = this.properties[i];
var key = prop.key;
if (key instanceof AST_Node) {
key = ev(key, compressor);
}
if (typeof Object.prototype[key] === 'function') {
throw def;
}
val[key] = ev(prop.value, compressor);
}
return val;
}
throw def;
});
def(AST_UnaryPrefix, function(compressor){
var e = this.expression;
switch (this.operator) {
@@ -1140,6 +1199,12 @@ merge(Compressor.prototype, {
try {
var d = this.definition();
if (d && (d.constant || compressor.option("reduce_vars") && !d.modified) && d.init) {
if (compressor.option("unsafe")) {
if (!HOP(d.init, '_evaluated')) {
d.init._evaluated = ev(d.init, compressor);
}
return d.init._evaluated;
}
return ev(d.init, compressor);
}
} finally {
@@ -1147,11 +1212,16 @@ merge(Compressor.prototype, {
}
throw def;
});
def(AST_Dot, function(compressor){
if (compressor.option("unsafe") && this.property == "length") {
var str = ev(this.expression, compressor);
if (typeof str == "string")
return str.length;
def(AST_PropAccess, function(compressor){
if (compressor.option("unsafe")) {
var key = this.property;
if (key instanceof AST_Node) {
key = ev(key, compressor);
}
var val = ev(this.expression, compressor);
if (val && HOP(val, key)) {
return val[key];
}
}
throw def;
});
@@ -2494,6 +2564,16 @@ merge(Compressor.prototype, {
var commutativeOperators = makePredicate("== === != !== * & | ^");
OPT(AST_Binary, function(self, compressor){
var lhs = self.left.evaluate(compressor);
var rhs = self.right.evaluate(compressor);
if (lhs.length > 1 && lhs[0].is_constant() !== self.left.is_constant()
|| rhs.length > 1 && rhs[0].is_constant() !== self.right.is_constant()) {
return make_node(AST_Binary, self, {
operator: self.operator,
left: lhs[0],
right: rhs[0]
}).optimize(compressor);
}
function reverse(op, force) {
if (force || !(self.left.has_side_effects(compressor) || self.right.has_side_effects(compressor))) {
if (op) self.operator = op;
@@ -2566,32 +2646,6 @@ merge(Compressor.prototype, {
}
break;
}
if (compressor.option("conditionals")) {
if (self.operator == "&&") {
var ll = self.left.evaluate(compressor);
if (ll.length > 1) {
if (ll[1]) {
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
} else {
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, ll[0]);
}
}
}
else if (self.operator == "||") {
var ll = self.left.evaluate(compressor);
if (ll.length > 1) {
if (ll[1]) {
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, ll[0]);
} else {
compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
}
}
}
}
if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) {
case "&&":
var ll = self.left.evaluate(compressor);
@@ -2665,6 +2719,30 @@ merge(Compressor.prototype, {
return self.left;
}
if (compressor.option("evaluate")) {
switch (self.operator) {
case "&&":
if (self.left.is_constant()) {
if (self.left.constant_value(compressor)) {
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.right);
} else {
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.left);
}
}
break;
case "||":
if (self.left.is_constant()) {
if (self.left.constant_value(compressor)) {
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.left);
} else {
compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.right);
}
}
break;
}
if (self.operator == "+") {
if (self.left instanceof AST_Constant
&& self.right instanceof AST_Binary
@@ -2738,14 +2816,6 @@ merge(Compressor.prototype, {
});
OPT(AST_SymbolRef, function(self, compressor){
function isLHS(symbol, parent) {
return (
parent instanceof AST_Binary &&
parent.operator === '=' &&
parent.left === symbol
);
}
if (self.undeclared() && !isLHS(self, compressor.parent())) {
var defines = compressor.option("global_defs");
if (defines && HOP(defines, self.name)) {
@@ -2899,14 +2969,14 @@ merge(Compressor.prototype, {
});
}
// y?1:1 --> 1
if (consequent.is_constant(compressor)
&& alternative.is_constant(compressor)
if (consequent.is_constant()
&& alternative.is_constant()
&& consequent.equivalent_to(alternative)) {
var consequent_value = consequent.constant_value(compressor);
var consequent_value = consequent.evaluate(compressor)[0];
if (self.condition.has_side_effects(compressor)) {
return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent_value, self)]);
return AST_Seq.from_array([self.condition, consequent_value]);
} else {
return make_node_from_constant(compressor, consequent_value, self);
return consequent_value;
}
}
@@ -3023,7 +3093,7 @@ merge(Compressor.prototype, {
});
}
}
return self;
return self.evaluate(compressor)[0];
});
OPT(AST_Dot, function(self, compressor){