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){

View File

@@ -232,6 +232,17 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
var cls = null;
var globals = self.globals = new Dictionary();
var tw = new TreeWalker(function(node, descend){
function isModified(node, level) {
var parent = tw.parent(level);
if (parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--")
|| parent instanceof AST_Assign && parent.left === node
|| parent instanceof AST_Call && parent.expression === node) {
return true;
} else if (parent instanceof AST_PropAccess && parent.expression === node) {
return isModified(parent, level + 1);
}
}
if (node instanceof AST_Lambda) {
var prev_func = func;
func = node;
@@ -252,8 +263,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
}
if (node instanceof AST_SymbolRef) {
var name = node.name;
var parent = tw.parent();
if (name == "eval" && parent instanceof AST_Call) {
if (name == "eval" && tw.parent() instanceof AST_Call) {
for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
s.uses_eval = true;
}
@@ -275,11 +285,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
sym = g;
}
node.thedef = sym;
if (parent instanceof AST_Unary && (parent.operator === '++' || parent.operator === '--')
|| parent instanceof AST_Assign && parent.left === node) {
if (isModified(node, 0)) {
sym.modified = true;
}
node.reference();
node.reference(options);
return true;
}
});
@@ -323,13 +332,18 @@ AST_Lambda.DEFMETHOD("init_scope_vars", function(){
this.variables.set(symbol.name, def);
});
AST_SymbolRef.DEFMETHOD("reference", function() {
AST_SymbolRef.DEFMETHOD("reference", function(options) {
var def = this.definition();
def.references.push(this);
var s = this.scope;
while (s) {
push_uniq(s.enclosed, def);
if (s === def.scope) break;
if (options.keep_fnames) {
s.variables.each(function(d) {
push_uniq(def.scope.enclosed, d);
});
}
s = s.parent_scope;
}
this.frame = this.scope.nesting - def.scope.nesting;
@@ -401,11 +415,6 @@ AST_Function.DEFMETHOD("next_mangled", function(options, def){
}
});
AST_Scope.DEFMETHOD("references", function(sym){
if (sym instanceof AST_Symbol) sym = sym.definition();
return this.enclosed.indexOf(sym) < 0 ? null : sym;
});
AST_Symbol.DEFMETHOD("unmangleable", function(options){
var def = this.definition();
return def && def.unmangleable(options);