started some refactoring (WIP) -- moving squeezer to TreeTransformer
authorMihai Bazon <mihai@bazon.net>
Tue, 25 Sep 2012 12:15:47 +0000 (15:15 +0300)
committerMihai Bazon <mihai@bazon.net>
Tue, 25 Sep 2012 12:15:47 +0000 (15:15 +0300)
lib/compress.js

index 47053c4..ec16dc2 100644 (file)
@@ -146,31 +146,12 @@ function Compressor(options, false_by_default) {
         });
     };
 
-    function do_list(array, compressor, splice_blocks) {
-        return MAP(array, function(node, i){
-            node = node.squeeze(compressor, array, i);
-            if (splice_blocks) {
-                if (node instanceof AST_BlockStatement) {
-                    return MAP.splice(eliminate_spurious_blocks(node.body));
-                }
-                if (node instanceof AST_EmptyStatement)
-                    return MAP.skip;
-            }
-            return node;
+    function do_list(array, compressor) {
+        return MAP(array, function(node){
+            return node.squeeze(compressor);
         });
     };
 
-    function eliminate_spurious_blocks(statements) {
-        return statements.reduce(function(a, stat){
-            if (stat instanceof AST_BlockStatement) {
-                a.push.apply(a, eliminate_spurious_blocks(stat.body));
-            } else if (!(stat instanceof AST_EmptyStatement)) {
-                a.push(stat);
-            }
-            return a;
-        }, []);
-    };
-
     function as_statement_array(thing) {
         if (thing === null) return [];
         if (thing instanceof AST_BlockStatement) return thing.body;
@@ -188,9 +169,9 @@ function Compressor(options, false_by_default) {
 
     function tighten_body(statements, compressor) {
         var CHANGED;
-        statements = do_list(statements, compressor, true);
         do {
             CHANGED = false;
+            statements = eliminate_spurious_blocks(statements);
             if (compressor.option("dead_code")) {
                 statements = eliminate_dead_code(statements, compressor);
             }
@@ -203,10 +184,23 @@ function Compressor(options, false_by_default) {
             if (compressor.option("join_vars")) {
                 statements = join_consecutive_vars(statements, compressor);
             }
-            statements = eliminate_spurious_blocks(statements);
         } while (CHANGED);
         return statements;
 
+        function eliminate_spurious_blocks(statements) {
+            return statements.reduce(function(a, stat){
+                if (stat instanceof AST_BlockStatement) {
+                    CHANGED = true;
+                    a.push.apply(a, eliminate_spurious_blocks(stat.body));
+                } else if (stat instanceof AST_EmptyStatement) {
+                    CHANGED = true;
+                } else {
+                    a.push(stat);
+                }
+                return a;
+            }, []);
+        };
+
         function handle_if_return(statements, compressor) {
             var self = compressor.self();
             var in_lambda = self instanceof AST_Lambda;
@@ -289,7 +283,7 @@ function Compressor(options, false_by_default) {
                     if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
                                || (ab instanceof AST_Continue && self === ab.target()))) {
                         CHANGED = true;
-                        var body = tighten_body(as_statement_array(stat.body).slice(0, -1), compressor);
+                        var body = as_statement_array(stat.body).slice(0, -1);
                         stat = stat.clone();
                         stat.condition = stat.condition.negate(compressor);
                         stat.body = make_node(AST_BlockStatement, stat, {
@@ -308,10 +302,10 @@ function Compressor(options, false_by_default) {
                         CHANGED = true;
                         stat = stat.clone();
                         stat.body = make_node(AST_BlockStatement, stat.body, {
-                            body: tighten_body(as_statement_array(stat.body).concat(ret), compressor)
+                            body: as_statement_array(stat.body).concat(ret)
                         });
                         stat.alternative = make_node(AST_BlockStatement, stat.alternative, {
-                            body: tighten_body(as_statement_array(stat.alternative).slice(0, -1), compressor)
+                            body: as_statement_array(stat.alternative).slice(0, -1)
                         });
                         ret = [ stat.squeeze(compressor) ];
                         continue loop;
@@ -331,6 +325,7 @@ function Compressor(options, false_by_default) {
             var has_quit = false;
             return statements.reduce(function(a, stat){
                 if (has_quit) {
+                    CHANGED = true;
                     extract_declarations_from_unreachable_code(compressor, stat, a);
                 } else {
                     a.push(stat);
@@ -772,7 +767,7 @@ function Compressor(options, false_by_default) {
     });
 
     AST_Directive.DEFMETHOD("optimize", function(compressor){
-        if (this.hoisted || this.scope.has_directive(this.value) !== this.scope) {
+        if (this.scope.has_directive(this.value) !== this.scope) {
             return make_node(AST_EmptyStatement, this);
         }
         return this;
@@ -806,11 +801,12 @@ function Compressor(options, false_by_default) {
 
     SQUEEZE(AST_BlockStatement, function(self, compressor){
         self = self.clone();
-        self.body = tighten_body(self.body, compressor);
+        self.body = do_list(self.body, compressor);
         return self.optimize(compressor);
     });
 
     AST_BlockStatement.DEFMETHOD("optimize", function(compressor){
+        this.body = tighten_body(this.body, compressor);
         switch (this.body.length) {
           case 1: return this.body[0];
           case 0: return make_node(AST_EmptyStatement, this);
@@ -820,15 +816,24 @@ function Compressor(options, false_by_default) {
 
     SQUEEZE(AST_Block, function(self, compressor){
         self = self.clone();
-        self.body = tighten_body(self.body, compressor);
-        return self;
+        self.body = do_list(self.body, compressor);
+        return self.optimize(compressor);
+    });
+
+    AST_Block.DEFMETHOD("optimize", function(compressor){
+        this.body = tighten_body(this.body, compressor);
+        return this;
     });
 
     SQUEEZE(AST_Scope, function(self, compressor){
-        self = self.clone();
-        self.hoist_declarations(compressor);
-        self.body = tighten_body(self.body, compressor);
-        return self;
+        self = self.clone().hoist_declarations(compressor);
+        self.body = do_list(self.body, compressor);
+        return self.optimize(compressor);
+    });
+
+    AST_Scope.DEFMETHOD("optimize", function(compressor){
+        this.body = tighten_body(this.body, compressor);
+        return this;
     });
 
     AST_Scope.DEFMETHOD("drop_unused", function(compressor){
@@ -937,48 +942,70 @@ function Compressor(options, false_by_default) {
     AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){
         var hoist_funs = compressor.option("hoist_funs");
         var hoist_vars = compressor.option("hoist_vars");
-        this.drop_unused(compressor);
+        var self = this;
+        self.drop_unused(compressor);
         if (hoist_funs || hoist_vars) {
-            var self = this;
+            var dirs = [];
             var hoisted = [];
-            var defuns = {};
-            var vars = {}, vars_found = 0, vardecl = [];
-            var tw = new TreeWalker(function(node){
-                if (node !== self) {
-                    if (node instanceof AST_Directive && (hoist_funs || hoist_vars) && !node.hoisted) {
-                        hoisted.unshift(node.clone());
-                        node.hoisted = true;
-                    }
-                    if (node instanceof AST_Defun && hoist_funs && !node.hoisted) {
-                        hoisted.push(node.clone());
-                        node.hoisted = true;
-                        defuns[node.name.name] = true;
-                    }
-                    if (node instanceof AST_Var && hoist_vars && !node.hoisted) {
-                        node.definitions.forEach(function(def){
-                            vars[def.name.name] = def;
-                            ++vars_found;
-                        });
-                        vardecl.push(node);
+            var vars = {}, vars_found = 0, var_decl = 0;
+            // let's count var_decl first, we seem to waste a lot of
+            // space if we hoist `var` when there's only one.
+            self.walk(new TreeWalker(function(node){
+                if (node instanceof AST_Scope && node !== self)
+                    return true;
+                if (node instanceof AST_Var) {
+                    ++var_decl;
+                    return true;
+                }
+            }));
+            hoist_vars = hoist_vars && var_decl > 1;
+            var tt = new TreeTransformer(
+                function before(node) {
+                    if (node !== self) {
+                        if (node instanceof AST_Directive) {
+                            dirs.push(node);
+                            return make_node(AST_EmptyStatement, node);
+                        }
+                        if (node instanceof AST_Defun && hoist_funs) {
+                            hoisted.push(node);
+                            return make_node(AST_EmptyStatement, node);
+                        }
+                        if (node instanceof AST_Var && hoist_vars) {
+                            node.definitions.forEach(function(def){
+                                vars[def.name.name] = def;
+                                ++vars_found;
+                            });
+                            var seq = node.to_assignments();
+                            var p = tt.parent();
+                            if (p instanceof AST_ForIn && p.init === node) {
+                                if (seq == null) return node.definitions[0].name;
+                                return seq;
+                            }
+                            if (p instanceof AST_For && p.init === node) {
+                                return seq;
+                            }
+                            if (!seq) return make_node(AST_EmptyStatement, node);
+                            return make_node(AST_SimpleStatement, node, {
+                                body: seq
+                            });
+                        }
+                        if (node instanceof AST_Scope)
+                            return node; // to avoid descending in nested scopes
                     }
-                    if (node instanceof AST_Scope)
-                        return true;
                 }
-            });
-            self.walk(tw);
-            if (vars_found > 0 && vardecl.length > 1) {
-                vardecl.forEach(function(v){ v.hoisted = true });
-                var node = make_node(AST_Var, self, {
-                    definitions: Object.keys(vars).map(function(name){
-                        var def = vars[name].clone();
-                        def.value = null;
-                        return def;
-                    })
-                });
-                hoisted.unshift(node);
-            }
-            self.body = hoisted.concat(self.body);
+            );
+            self = self.transform(tt);
+
+            if (vars_found > 0) hoisted.unshift(make_node(AST_Var, self, {
+                definitions: Object.keys(vars).map(function(name){
+                    var def = vars[name].clone();
+                    def.value = null;
+                    return def;
+                })
+            }));
+            self.body = dirs.concat(hoisted, self.body);
         }
+        return self;
     });
 
     SQUEEZE(AST_SimpleStatement, function(self, compressor){
@@ -1260,16 +1287,26 @@ function Compressor(options, false_by_default) {
     SQUEEZE(AST_Case, function(self, compressor){
         self = self.clone();
         self.expression = self.expression.squeeze(compressor);
-        self.body = tighten_body(self.body, compressor);
-        return self;
+        self.body = do_list(self.body, compressor);
+        return self.optimize(compressor);
+    });
+
+    AST_Case.DEFMETHOD("optimize", function(compressor){
+        this.body = tighten_body(this.body, compressor);
+        return this;
     });
 
     SQUEEZE(AST_Try, function(self, compressor){
         self = self.clone();
-        self.body = tighten_body(self.body, compressor);
+        self.body = do_list(self.body, compressor);
         if (self.bcatch) self.bcatch = self.bcatch.squeeze(compressor);
         if (self.bfinally) self.bfinally = self.bfinally.squeeze(compressor);
-        return self;
+        return self.optimize(compressor);
+    });
+
+    AST_Try.DEFMETHOD("optimize", function(compressor){
+        this.body = tighten_body(this.body, compressor);
+        return this;
     });
 
     AST_Definitions.DEFMETHOD("remove_initializers", function(){
@@ -1304,25 +1341,15 @@ function Compressor(options, false_by_default) {
     });
 
     SQUEEZE(AST_Definitions, function(self, compressor){
-        if (self.definitions.length == 0)
-            return make_node(AST_EmptyStatement, self);
-        if (self.hoisted) {
-            var seq = self.to_assignments();
-            var p = compressor.parent();
-            if (seq) seq = seq.squeeze(compressor);
-            if (p instanceof AST_ForIn && p.init === self) {
-                if (seq == null) return self.definitions[0].name; //XXX: is this fine?
-                return seq;
-            }
-            if (p instanceof AST_For && p.init === self) {
-                return seq;
-            }
-            if (!seq) return make_node(AST_EmptyStatement, self);
-            return make_node(AST_SimpleStatement, self, { body: seq });
-        }
         self = self.clone();
         self.definitions = do_list(self.definitions, compressor);
-        return self;
+        return self.optimize(compressor);
+    });
+
+    AST_Definitions.DEFMETHOD("optimize", function(compressor){
+        if (this.definitions.length == 0)
+            return make_node(AST_EmptyStatement, this);
+        return this;
     });
 
     SQUEEZE(AST_VarDef, function(self, compressor){
@@ -1332,22 +1359,21 @@ function Compressor(options, false_by_default) {
     });
 
     SQUEEZE(AST_Lambda, function(self, compressor){
-        if (self.hoisted) return make_node(AST_EmptyStatement, self);
-        self = self.clone();
+        self = self.clone().hoist_declarations(compressor);
         if (self.name) self.name = self.name.squeeze(compressor);
         self.argnames = do_list(self.argnames, compressor);
-        self.hoist_declarations(compressor);
-        self.body = tighten_body(self.body, compressor);
+        self.body = do_list(self.body, compressor);
         return self.optimize(compressor);
     });
 
     AST_Function.DEFMETHOD("optimize", function(compressor){
+        var self = AST_Lambda.prototype.optimize.call(this, compressor);
         if (compressor.option("unused")) {
-            if (this.name && this.name.unreferenced()) {
-                this.name = null;
+            if (self.name && self.name.unreferenced()) {
+                self.name = null;
             }
         }
-        return this;
+        return self;
     });
 
     SQUEEZE(AST_Call, function(self, compressor){