more progress on the compressor (WIP)
authorMihai Bazon <mihai@bazon.net>
Mon, 10 Sep 2012 12:54:17 +0000 (15:54 +0300)
committerMihai Bazon <mihai@bazon.net>
Mon, 10 Sep 2012 13:37:05 +0000 (16:37 +0300)
lib/compress.js
lib/output.js
lib/utils.js
test/compress/conditionals.js

index 3ce0629..e615699 100644 (file)
@@ -289,8 +289,8 @@ function Compressor(options, false_by_default) {
     });
 
     function best_of(ast1, ast2) {
-        return ast1.print_to_string({ beautify: false }).length >
-            ast2.print_to_string({ beautify: false }).length
+        return ast1.print_to_string().length >
+            ast2.print_to_string().length
             ? ast2 : ast1;
     };
 
@@ -448,14 +448,12 @@ function Compressor(options, false_by_default) {
                 self.operator = "||";
                 self.left = self.left.negate(compressor);
                 self.right = self.right.negate(compressor);
-                //return best_of(basic_negation(this), self);
-                return self;
+                return best_of(basic_negation(this), self);
               case "||":
                 self.operator = "&&";
                 self.left = self.left.negate(compressor);
                 self.right = self.right.negate(compressor);
-                //return best_of(basic_negation(this), self);
-                return self;
+                return best_of(basic_negation(this), self);
             }
             return basic_negation(this);
         });
@@ -692,18 +690,19 @@ function Compressor(options, false_by_default) {
                 }
             }
         }
-        if (self.condition instanceof AST_UnaryPrefix
-            && self.condition.operator == "!") {
-            self.condition = self.condition.expression;
+        var negated = self.condition.negate(compressor);
+        var negated_is_best = best_of(self.condition, negated) === negated;
+        if (self.alternative && negated_is_best) {
+            self.condition = negated;
             var tmp = self.body;
-            self.body = self.alternative || make_node(AST_EmptyStatement, self);
+            self.body = self.alternative || new AST_EmptyStatement();
             self.alternative = tmp;
         }
         if (self.body instanceof AST_EmptyStatement
             && self.alternative instanceof AST_EmptyStatement) {
             return make_node(AST_SimpleStatement, self.condition, {
                 body: self.condition
-            }).optimize(compressor);
+            });
         }
         if (self.body instanceof AST_SimpleStatement
             && self.alternative instanceof AST_SimpleStatement) {
@@ -718,7 +717,14 @@ function Compressor(options, false_by_default) {
         if ((!self.alternative
              || self.alternative instanceof AST_EmptyStatement)
             && self.body instanceof AST_SimpleStatement) {
-            return make_node(AST_SimpleStatement, self, {
+            if (negated_is_best) return make_node(AST_SimpleStatement, self, {
+                body: make_node(AST_Binary, self, {
+                    operator : "||",
+                    left     : negated,
+                    right    : self.body.body
+                }).optimize(compressor)
+            });
+            else return make_node(AST_SimpleStatement, self, {
                 body: make_node(AST_Binary, self, {
                     operator : "&&",
                     left     : self.condition,
@@ -843,7 +849,61 @@ function Compressor(options, false_by_default) {
         self = self.clone();
         self.expression = self.expression.squeeze(compressor);
         self.args = do_list(self.args, compressor);
-        return self;
+        return self.optimize(compressor);
+    });
+
+    AST_Call.DEFMETHOD("optimize", function(compressor){
+        if (compressor.option("unsafe")) {
+            var exp = this.expression;
+            if (exp instanceof AST_SymbolRef && exp.undeclared) {
+                switch (exp.name) {
+                  case "Array":
+                    if (this.args.length != 1) {
+                        return make_node(AST_Array, this, {
+                            elements: this.args
+                        }).optimize(compressor);
+                    }
+                    break;
+                  case "Object":
+                    if (this.args.length == 0) {
+                        return make_node(AST_Object, this, {
+                            properties: []
+                        }).optimize(compressor);
+                    }
+                    break;
+                  case "String":
+                    return make_node(AST_Binary, this, {
+                        left: this.args[0],
+                        operator: "+",
+                        right: make_node(AST_String, this, { value: "" })
+                    });
+                }
+            }
+            else if (exp instanceof AST_Dot && exp.property == "toString" && this.args.length == 0) {
+                return make_node(AST_Binary, this, {
+                    left: exp.expression,
+                    operator: "+",
+                    right: make_node(AST_String, this, { value: "" })
+                });
+            }
+        }
+        return this;
+    });
+
+    AST_New.DEFMETHOD("optimize", function(compressor){
+        if (compressor.option("unsafe")) {
+            var exp = this.expression;
+            if (exp instanceof AST_SymbolRef && exp.undeclared) {
+                switch (exp.name) {
+                  case "Object":
+                  case "RegExp":
+                  case "Function":
+                  case "Error":
+                  case "Array":
+                    return make_node(AST_Call, this, this).optimize(compressor);
+                }
+            }
+        }
     });
 
     SQUEEZE(AST_Seq, function(self, compressor){
@@ -914,6 +974,15 @@ function Compressor(options, false_by_default) {
     });
 
     AST_Binary.DEFMETHOD("optimize", function(compressor){
+        if (compressor.option("comparations")) switch (this.operator) {
+          case "===":
+          case "!==":
+            if ((this.left.is_string() && this.right.is_string()) ||
+                (this.left.is_boolean() && this.right.is_boolean())) {
+                this.operator = this.operator.substr(0, 2);
+            }
+            break;
+        }
         if (compressor.option("booleans") && compressor.in_boolean_context()) switch (this.operator) {
           case "&&":
             var ll = this.left.evaluate(compressor), left = ll[0];
@@ -984,12 +1053,39 @@ function Compressor(options, false_by_default) {
                 return self.alternative;
             }
         }
-        var rev = self.clone();
-        rev.condition = cond[0].negate(compressor);
-        var tmp = rev.consequent;
-        rev.consequent = rev.alternative;
-        rev.alternative = tmp;
-        return best_of(self, rev);
+        var negated = cond[0].negate(compressor);
+        if (best_of(cond[0], negated) === negated) {
+            self = make_node(AST_Conditional, self, {
+                condition: negated,
+                consequent: self.alternative,
+                alternative: self.consequent
+            });
+        }
+        var consequent = self.consequent;
+        var alternative = self.alternative;
+        if (consequent instanceof AST_Assign
+            && alternative instanceof AST_Assign
+            && consequent.operator == alternative.operator
+            // XXX: this is a rather expensive way to test two node's equivalence:
+            && consequent.left.print_to_string() == alternative.left.print_to_string()
+           ) {
+            /*
+             * Stuff like this:
+             * if (foo) exp = something; else exp = something_else;
+             * ==>
+             * exp = foo ? something : something_else;
+             */
+            self = make_node(AST_Assign, self, {
+                operator: consequent.operator,
+                left: consequent.left,
+                right: make_node(AST_Conditional, self, {
+                    condition: self.condition,
+                    consequent: consequent.right,
+                    alternative: alternative.right
+                })
+            });
+        }
+        return self;
     });
 
     SQUEEZE(AST_Array, function(self, compressor){
index c803fba..35c7ade 100644 (file)
@@ -313,6 +313,9 @@ function OutputStream(options) {
     };
 
     AST_Node.DEFMETHOD("print_to_string", function(options){
+        options = defaults(options, {
+            beautify: false
+        });
         var s = OutputStream(options);
         this.print(s);
         return s.get();
index 519e696..79e612c 100644 (file)
@@ -97,9 +97,9 @@ function repeat_string(str, i) {
 };
 
 function defaults(args, defs) {
-    var ret = {};
     if (args === true)
         args = {};
+    var ret = args || {};
     for (var i in defs) if (HOP(defs, i)) {
         ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
     }
index ba5e674..b9c348a 100644 (file)
@@ -72,3 +72,18 @@ ifs_3_should_warn: {
         var jj; foo();          // 2
     }
 }
+
+ifs_4: {
+    options = {
+        conditionals: true
+    };
+    input: {
+        if (foo && bar) {
+            x(foo)[10].bar.baz = something();
+        } else
+            x(foo)[10].bar.baz = something_else();
+    }
+    expect: {
+        x(foo)[10].bar.baz = (foo && bar) ? something() : something_else();
+    }
+}