cleaned up some mess and started the actual compressor
authorMihai Bazon <mihai@bazon.net>
Tue, 21 Aug 2012 12:45:05 +0000 (15:45 +0300)
committerMihai Bazon <mihai@bazon.net>
Tue, 21 Aug 2012 13:14:43 +0000 (16:14 +0300)
lib/ast.js
lib/compress.js [new file with mode: 0644]
lib/output.js
lib/parse.js
lib/scope.js
tmp/test-node.js
tools/node.js

index bb2a07c..1b75bd6 100644 (file)
@@ -35,6 +35,9 @@ var AST_Token = DEFNODE("Token", "type value line col pos endpos nlb comments_be
 }, null);
 
 var AST_Node = DEFNODE("Node", "start end", {
+    clone: function() {
+        return new this.CTOR(this);
+    },
     $documentation: "Base class of all AST nodes",
     _walk: function(visitor) {
         return visitor._visit(this);
@@ -44,9 +47,10 @@ var AST_Node = DEFNODE("Node", "start end", {
     }
 }, null);
 
-AST_Node.warn_function = noop;
+AST_Node.warn_function = null;
 AST_Node.warn = function(txt, props) {
-    AST_Node.warn_function(string_template(txt, props));
+    if (AST_Node.warn_function)
+        AST_Node.warn_function(string_template(txt, props));
 };
 
 var AST_Debugger = DEFNODE("Debugger", null, {
@@ -86,10 +90,9 @@ var AST_BlockStatement = DEFNODE("BlockStatement", null, {
     $documentation: "A block statement.",
     _walk: function(visitor) {
         return visitor._visit(this, function(){
-            var a = this.body, i = 0, n = a.length;
-            while (i < n) {
-                a[i++]._walk(visitor);
-            }
+            this.body.forEach(function(stat){
+                stat._walk(visitor);
+            });
         });
     }
 }, AST_Statement);
@@ -135,9 +138,8 @@ var AST_ForIn = DEFNODE("ForIn", "init name object", {
     $documentation: "A `for ... in` statement",
     _walk: function(visitor) {
         return visitor._visit(this, function(){
-            if (this.init) this.init._walk(visitor);
-            else if (this.name) this.name._walk(visitor);
-            if (this.object) this.object._walk(visitor);
+            this.init._walk(visitor);
+            this.object._walk(visitor);
             this.body._walk(visitor);
         });
     }
@@ -295,7 +297,7 @@ var AST_Try = DEFNODE("Try", "btry bcatch bfinally", {
 // IE which simply introduces the name in the surrounding scope.  If
 // we ever want to fix this then AST_Catch should inherit from
 // AST_Scope.
-var AST_Catch = DEFNODE("Catch", "argname", {
+var AST_Catch = DEFNODE("Catch", "argname body", {
     $documentation: "A `catch` node; only makes sense as part of a `try` statement",
     _walk: function(visitor) {
         return visitor._visit(this, function(){
@@ -303,11 +305,16 @@ var AST_Catch = DEFNODE("Catch", "argname", {
             this.body._walk(visitor);
         });
     }
-}, AST_BlockStatement);
+});
 
-var AST_Finally = DEFNODE("Finally", null, {
-    $documentation: "A `finally` node; only makes sense as part of a `try` statement"
-}, AST_BlockStatement);
+var AST_Finally = DEFNODE("Finally", "body", {
+    $documentation: "A `finally` node; only makes sense as part of a `try` statement",
+    _walk: function(visitor) {
+        return visitor._visit(this, function(){
+            this.body._walk(visitor);
+        });
+    }
+});
 
 /* -----[ VAR/CONST ]----- */
 
diff --git a/lib/compress.js b/lib/compress.js
new file mode 100644 (file)
index 0000000..025c262
--- /dev/null
@@ -0,0 +1,249 @@
+// The layout of the compressor follows the code generator (see
+// output.js).  Basically each node will have a "squeeze" method
+// that will apply all known compression rules for that node, and
+// return a new node (or the original node if there was no
+// compression).  We can't quite use the TreeWalker for this
+// because it's too simplistic.
+
+// The Compressor object is for storing the options and for
+// maintaining various internal state that might be useful for
+// squeezing nodes.
+
+function Compressor(options) {
+    options = defaults(options, {
+        sequences     : true,
+        dead_code     : true,
+        keep_comps    : true,
+        drop_debugger : true,
+        unsafe        : true
+    });
+    var stack = [];
+    return {
+        option    : function(key) { return options[key] },
+        push_node : function(node) { stack.push(node) },
+        pop_node  : function() { return stack.pop() },
+        stack     : function() { return stack },
+        parent    : function(n) {
+            return stack[stack.length - 2 - (n || 0)];
+        }
+    };
+};
+
+(function(){
+
+    AST_Node.DEFMETHOD("squeeze", function(){
+        return this;
+    });
+
+    AST_Token.DEFMETHOD("squeeze", function(){
+        return this;
+    });
+
+    function SQUEEZE(nodetype, squeeze) {
+        nodetype.DEFMETHOD("squeeze", function(compressor){
+            compressor.push_node(this);
+            var new_node = squeeze(this, compressor);
+            compressor.pop_node();
+            return new_node || this;
+        });
+    };
+
+    function do_list(array, compressor) {
+        return MAP(array, function(node){
+            if (node instanceof Array) {
+                sys.debug(node.map(function(node){
+                    return node.TYPE;
+                }).join("\n"));
+            }
+            return node.squeeze(compressor);
+        });
+    };
+
+    SQUEEZE(AST_Debugger, function(self, compressor){
+        if (compressor.option("drop_debugger"))
+            return new AST_EmptyStatement(self);
+    });
+
+    SQUEEZE(AST_LabeledStatement, function(self, compressor){
+        self = self.clone();
+        self.statement = self.statement.squeeze(compressor);
+        return self.label.references.length == 0 ? self.statement : self;
+    });
+
+    SQUEEZE(AST_Statement, function(self, compressor){
+        self = self.clone();
+        self.body = self.body.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_BlockStatement, function(self, compressor){
+        self = self.clone();
+        self.body = do_list(self.body, compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_EmptyStatement, function(self, compressor){
+        if (compressor.parent() instanceof AST_BlockStatement)
+            return MAP.skip;
+    });
+
+    SQUEEZE(AST_DWLoop, function(self, compressor){
+        self = self.clone();
+        self.condition = self.condition.squeeze(compressor);
+        self.body = self.body.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_For, function(self, compressor){
+        self = self.clone();
+        if (self.init) self.init = self.init.squeeze(compressor);
+        if (self.condition) self.condition = self.condition.squeeze(compressor);
+        if (self.step) self.step = self.step.squeeze(compressor);
+        self.body = self.body.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_ForIn, function(self, compressor){
+        self = self.clone();
+        self.init = self.init.squeeze(compressor);
+        self.object = self.object.squeeze(compressor);
+        self.body = self.body.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_With, function(self, compressor){
+        self = self.clone();
+        self.expression = self.expression.squeeze(compressor);
+        self.body = self.body.squeeze(compressor);
+    });
+
+    SQUEEZE(AST_Exit, function(self, compressor){
+        self = self.clone();
+        if (self.value) self.value = self.value.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_LoopControl, function(self, compressor){
+        self = self.clone();
+        if (self.label) self.label = self.label.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_If, function(self, compressor){
+        self = self.clone();
+        self.condition = self.condition.squeeze(compressor);
+        self.consequent = self.consequent.squeeze(compressor);
+        if (self.alternative)
+            self.alternative = self.alternative.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Switch, function(self, compressor){
+        self = self.clone();
+        self.expression = self.expression.squeeze(compressor);
+        self.body = self.body.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Case, function(self, compressor){
+        self = self.clone();
+        self.expression = self.expression.squeeze(compressor);
+        self.body = do_list(self.body, compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Try, function(self, compressor){
+        self = self.clone();
+        self.btry = self.btry.squeeze(compressor);
+        if (self.bcatch) self.bcatch = self.bcatch.squeeze(compressor);
+        if (self.bfinally) self.bfinally = self.bfinally.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Definitions, function(self, compressor){
+        self = self.clone();
+        self.definitions = do_list(self.definitions, compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_VarDef, function(self, compressor){
+        self = self.clone();
+        if (self.value) self.value = self.value.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Lambda, function(self, compressor){
+        self = self.clone();
+        if (self.name) self.name = self.name.squeeze(compressor);
+        self.argnames = do_list(self.argnames, compressor);
+        self.body = self.body.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Call, function(self, compressor){
+        self = self.clone();
+        self.expression = self.expression.squeeze(compressor);
+        self.args = do_list(self.args, compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Seq, function(self, compressor){
+        self = self.clone();
+        self.first = self.first.squeeze(compressor);
+        self.second = self.second.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Dot, function(self, compressor){
+        self = self.clone();
+        self.expression = self.expression.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Sub, function(self, compressor){
+        self = self.clone();
+        self.expression = self.expression.squeeze(compressor);
+        self.property = self.property.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Unary, function(self, compressor){
+        self = self.clone();
+        self.expression = self.expression.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Binary, function(self, compressor){
+        self = self.clone();
+        self.left = self.left.squeeze(compressor);
+        self.right = self.right.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Conditional, function(self, compressor){
+        self = self.clone();
+        self.condition = self.condition.squeeze(compressor);
+        self.consequent = self.consequent.squeeze(compressor);
+        self.alternative = self.alternative.squeeze(compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Array, function(self, compressor){
+        self = self.clone();
+        self.elements = do_list(self.elements, compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_Object, function(self, compressor){
+        self = self.clone();
+        self.properties = do_list(self.properties, compressor);
+        return self;
+    });
+
+    SQUEEZE(AST_ObjectProperty, function(self, compressor){
+        self = self.clone();
+        self.value = self.value.squeeze(compressor);
+        return self;
+    });
+
+})();
index bd38f9d..6152423 100644 (file)
@@ -444,11 +444,7 @@ function OutputStream(options) {
         output.print("for");
         output.space();
         output.with_parens(function(){
-            if (self.init) {
-                self.init.print(output);
-            } else {
-                self.name.print(output);
-            }
+            self.init.print(output);
             output.space();
             output.print("in");
             output.space();
index 78b8bb7..7f14429 100644 (file)
@@ -746,7 +746,7 @@ function parse($TEXT, exigent_mode) {
           case "punc":
             switch (S.token.value) {
               case "{":
-                return new AST_BlockStatement({ body: block_() });
+                return block_();
               case "[":
               case "(":
                 return simple_statement();
@@ -942,7 +942,7 @@ function parse($TEXT, exigent_mode) {
                 --S.in_function;
                 S.in_loop = loop;
                 S.labels = labels;
-                return new AST_BlockStatement({ body: a });
+                return a;
             })()
         });
     };
@@ -961,19 +961,25 @@ function parse($TEXT, exigent_mode) {
     };
 
     function block_() {
+        var start = S.token;
         expect("{");
         var a = [];
         while (!is("punc", "}")) {
             if (is("eof")) unexpected();
             a.push(statement());
         }
+        var end = S.token;
         next();
-        return a;
+        return new AST_BlockStatement({
+            start : start,
+            body  : a,
+            end   : end
+        });
     };
 
     var switch_block_ = embed_tokens(curry(in_loop, function(){
         expect("{");
-        var a = [], cur = null, branch = null;
+        var a = [], cur = null, branch = null, start = S.token;
         while (!is("punc", "}")) {
             if (is("eof")) unexpected();
             if (is("keyword", "case")) {
@@ -1002,16 +1008,17 @@ function parse($TEXT, exigent_mode) {
             }
         }
         if (branch) branch.end = prev();
+        var end = S.token;
         next();
-        return new AST_SwitchBlock({ body: a });
+        return new AST_SwitchBlock({
+            start : start,
+            body  : a,
+            end   : end
+        });
     }));
 
     function try_() {
-        var body = new AST_BlockStatement({
-            start : S.token,
-            body  : block_(),
-            end   : prev()
-        }), bcatch = null, bfinally = null;
+        var body = block_(), bcatch = null, bfinally = null;
         if (is("keyword", "catch")) {
             var start = S.token;
             next();
@@ -1021,11 +1028,7 @@ function parse($TEXT, exigent_mode) {
             bcatch = new AST_Catch({
                 start   : start,
                 argname : name,
-                body    : new AST_BlockStatement({
-                    start : S.token,
-                    body  : block_(),
-                    end   : prev()
-                }),
+                body    : block_(),
                 end     : prev()
             });
         }
@@ -1034,11 +1037,7 @@ function parse($TEXT, exigent_mode) {
             next();
             bfinally = new AST_Finally({
                 start : start,
-                body  : new AST_BlockStatement({
-                    start : S.token,
-                    body  : block_(),
-                    end   : prev()
-                }),
+                body  : block_(),
                 end   : prev()
             });
         }
index 0ac797e..05585fe 100644 (file)
@@ -138,19 +138,24 @@ AST_Toplevel.DEFMETHOD("scope_warnings", function(options){
                 col: node.start.col
             });
         }
-        if (options.assign_to_global
-            && node instanceof AST_Assign
-            && node.left instanceof AST_SymbolRef
-            && (node.left.undeclared
-                || (node.left.symbol.global
-                    && node.left.scope !== node.left.symbol.scope)))
+        if (options.assign_to_global)
         {
-            AST_Node.warn("{msg}: {name} [{line},{col}]", {
-                msg: node.left.undeclared ? "Accidental global?" : "Assignment to global",
-                name: node.left.name,
-                line: node.start.line,
-                col: node.start.col
-            });
+            var sym = null;
+            if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef)
+                sym = node.left;
+            else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef)
+                sym = node.init;
+            if (sym
+                && (sym.undeclared
+                    || (sym.symbol.global
+                        && sym.scope !== sym.symbol.scope))) {
+                AST_Node.warn("{msg}: {name} [{line},{col}]", {
+                    msg: sym.undeclared ? "Accidental global?" : "Assignment to global",
+                    name: sym.name,
+                    line: sym.start.line,
+                    col: sym.start.col
+                });
+            }
         }
         if (options.eval
             && node instanceof AST_SymbolRef
index 01716ad..8b1251b 100755 (executable)
@@ -18,6 +18,11 @@ time_it("scope", function(){
 time_it("mangle", function(){
     ast.mangle_names();
 });
+time_it("compress", function(){
+    var compressor = new UglifyJS.Compressor({
+    });
+    ast = ast.squeeze(compressor);
+});
 time_it("generate", function(){
     ast.print(stream);
 });
index bb8ed7a..b533e41 100644 (file)
@@ -3,7 +3,9 @@ var vm = require("vm");
 var sys = require("util");
 var path = require("path");
 
-var UglifyJS = vm.createContext({});
+var UglifyJS = vm.createContext({
+    sys: sys
+});
 
 function load_global(file) {
     file = path.resolve(path.dirname(module.filename), file);