You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by an...@apache.org on 2015/05/08 13:36:36 UTC

[11/52] [partial] incubator-ignite git commit: # ignite-843 WIP.

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/0ef79031/modules/webconfig/nodejs/node_modules/jade/node_modules/transformers/node_modules/uglify-js/lib/compress.js
----------------------------------------------------------------------
diff --git a/modules/webconfig/nodejs/node_modules/jade/node_modules/transformers/node_modules/uglify-js/lib/compress.js b/modules/webconfig/nodejs/node_modules/jade/node_modules/transformers/node_modules/uglify-js/lib/compress.js
new file mode 100644
index 0000000..ca23c40
--- /dev/null
+++ b/modules/webconfig/nodejs/node_modules/jade/node_modules/transformers/node_modules/uglify-js/lib/compress.js
@@ -0,0 +1,1968 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mi...@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mi...@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+"use strict";
+
+function Compressor(options, false_by_default) {
+    if (!(this instanceof Compressor))
+        return new Compressor(options, false_by_default);
+    TreeTransformer.call(this, this.before, this.after);
+    this.options = defaults(options, {
+        sequences     : !false_by_default,
+        properties    : !false_by_default,
+        dead_code     : !false_by_default,
+        drop_debugger : !false_by_default,
+        unsafe        : !false_by_default,
+        unsafe_comps  : false,
+        conditionals  : !false_by_default,
+        comparisons   : !false_by_default,
+        evaluate      : !false_by_default,
+        booleans      : !false_by_default,
+        loops         : !false_by_default,
+        unused        : !false_by_default,
+        hoist_funs    : !false_by_default,
+        hoist_vars    : false,
+        if_return     : !false_by_default,
+        join_vars     : !false_by_default,
+        cascade       : !false_by_default,
+        side_effects  : !false_by_default,
+
+        warnings      : true,
+        global_defs   : {}
+    }, true);
+};
+
+Compressor.prototype = new TreeTransformer;
+merge(Compressor.prototype, {
+    option: function(key) { return this.options[key] },
+    warn: function() {
+        if (this.options.warnings)
+            AST_Node.warn.apply(AST_Node, arguments);
+    },
+    before: function(node, descend, in_list) {
+        if (node._squeezed) return node;
+        if (node instanceof AST_Scope) {
+            node.drop_unused(this);
+            node = node.hoist_declarations(this);
+        }
+        descend(node, this);
+        node = node.optimize(this);
+        if (node instanceof AST_Scope) {
+            // dead code removal might leave further unused declarations.
+            // this'll usually save very few bytes, but the performance
+            // hit seems negligible so I'll just drop it here.
+
+            // no point to repeat warnings.
+            var save_warnings = this.options.warnings;
+            this.options.warnings = false;
+            node.drop_unused(this);
+            this.options.warnings = save_warnings;
+        }
+        node._squeezed = true;
+        return node;
+    }
+});
+
+(function(){
+
+    function OPT(node, optimizer) {
+        node.DEFMETHOD("optimize", function(compressor){
+            var self = this;
+            if (self._optimized) return self;
+            var opt = optimizer(self, compressor);
+            opt._optimized = true;
+            if (opt === self) return opt;
+            return opt.transform(compressor);
+        });
+    };
+
+    OPT(AST_Node, function(self, compressor){
+        return self;
+    });
+
+    AST_Node.DEFMETHOD("equivalent_to", function(node){
+        // XXX: this is a rather expensive way to test two node's equivalence:
+        return this.print_to_string() == node.print_to_string();
+    });
+
+    function make_node(ctor, orig, props) {
+        if (!props) props = {};
+        if (orig) {
+            if (!props.start) props.start = orig.start;
+            if (!props.end) props.end = orig.end;
+        }
+        return new ctor(props);
+    };
+
+    function make_node_from_constant(compressor, val, orig) {
+        // XXX: WIP.
+        // if (val instanceof AST_Node) return val.transform(new TreeTransformer(null, function(node){
+        //     if (node instanceof AST_SymbolRef) {
+        //         var scope = compressor.find_parent(AST_Scope);
+        //         var def = scope.find_variable(node);
+        //         node.thedef = def;
+        //         return node;
+        //     }
+        // })).transform(compressor);
+
+        if (val instanceof AST_Node) return val.transform(compressor);
+        switch (typeof val) {
+          case "string":
+            return make_node(AST_String, orig, {
+                value: val
+            }).optimize(compressor);
+          case "number":
+            return make_node(isNaN(val) ? AST_NaN : AST_Number, orig, {
+                value: val
+            }).optimize(compressor);
+          case "boolean":
+            return make_node(val ? AST_True : AST_False, orig);
+          case "undefined":
+            return make_node(AST_Undefined, orig).optimize(compressor);
+          default:
+            if (val === null) {
+                return make_node(AST_Null, orig).optimize(compressor);
+            }
+            if (val instanceof RegExp) {
+                return make_node(AST_RegExp, orig).optimize(compressor);
+            }
+            throw new Error(string_template("Can't handle constant of type: {type}", {
+                type: typeof val
+            }));
+        }
+    };
+
+    function as_statement_array(thing) {
+        if (thing === null) return [];
+        if (thing instanceof AST_BlockStatement) return thing.body;
+        if (thing instanceof AST_EmptyStatement) return [];
+        if (thing instanceof AST_Statement) return [ thing ];
+        throw new Error("Can't convert thing to statement array");
+    };
+
+    function is_empty(thing) {
+        if (thing === null) return true;
+        if (thing instanceof AST_EmptyStatement) return true;
+        if (thing instanceof AST_BlockStatement) return thing.body.length == 0;
+        return false;
+    };
+
+    function loop_body(x) {
+        if (x instanceof AST_Switch) return x;
+        if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) {
+            return (x.body instanceof AST_BlockStatement ? x.body : x);
+        }
+        return x;
+    };
+
+    function tighten_body(statements, compressor) {
+        var CHANGED;
+        do {
+            CHANGED = false;
+            statements = eliminate_spurious_blocks(statements);
+            if (compressor.option("dead_code")) {
+                statements = eliminate_dead_code(statements, compressor);
+            }
+            if (compressor.option("if_return")) {
+                statements = handle_if_return(statements, compressor);
+            }
+            if (compressor.option("sequences")) {
+                statements = sequencesize(statements, compressor);
+            }
+            if (compressor.option("join_vars")) {
+                statements = join_consecutive_vars(statements, compressor);
+            }
+        } while (CHANGED);
+        return statements;
+
+        function eliminate_spurious_blocks(statements) {
+            var seen_dirs = [];
+            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 if (stat instanceof AST_Directive) {
+                    if (seen_dirs.indexOf(stat.value) < 0) {
+                        a.push(stat);
+                        seen_dirs.push(stat.value);
+                    } else {
+                        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;
+            var ret = [];
+            loop: for (var i = statements.length; --i >= 0;) {
+                var stat = statements[i];
+                switch (true) {
+                  case (in_lambda && stat instanceof AST_Return && !stat.value && ret.length == 0):
+                    CHANGED = true;
+                    // note, ret.length is probably always zero
+                    // because we drop unreachable code before this
+                    // step.  nevertheless, it's good to check.
+                    continue loop;
+                  case stat instanceof AST_If:
+                    if (stat.body instanceof AST_Return) {
+                        //---
+                        // pretty silly case, but:
+                        // if (foo()) return; return; ==> foo(); return;
+                        if (((in_lambda && ret.length == 0)
+                             || (ret[0] instanceof AST_Return && !ret[0].value))
+                            && !stat.body.value && !stat.alternative) {
+                            CHANGED = true;
+                            var cond = make_node(AST_SimpleStatement, stat.condition, {
+                                body: stat.condition
+                            });
+                            ret.unshift(cond);
+                            continue loop;
+                        }
+                        //---
+                        // if (foo()) return x; return y; ==> return foo() ? x : y;
+                        if (ret[0] instanceof AST_Return && stat.body.value && ret[0].value && !stat.alternative) {
+                            CHANGED = true;
+                            stat = stat.clone();
+                            stat.alternative = ret[0];
+                            ret[0] = stat.transform(compressor);
+                            continue loop;
+                        }
+                        //---
+                        // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined;
+                        if ((ret.length == 0 || ret[0] instanceof AST_Return) && stat.body.value && !stat.alternative && in_lambda) {
+                            CHANGED = true;
+                            stat = stat.clone();
+                            stat.alternative = ret[0] || make_node(AST_Return, stat, {
+                                value: make_node(AST_Undefined, stat)
+                            });
+                            ret[0] = stat.transform(compressor);
+                            continue loop;
+                        }
+                        //---
+                        // if (foo()) return; [ else x... ]; y... ==> if (!foo()) { x...; y... }
+                        if (!stat.body.value && in_lambda) {
+                            CHANGED = true;
+                            stat = stat.clone();
+                            stat.condition = stat.condition.negate(compressor);
+                            stat.body = make_node(AST_BlockStatement, stat, {
+                                body: as_statement_array(stat.alternative).concat(ret)
+                            });
+                            stat.alternative = null;
+                            ret = [ stat.transform(compressor) ];
+                            continue loop;
+                        }
+                        //---
+                        if (ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement
+                            && (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) {
+                            CHANGED = true;
+                            ret.push(make_node(AST_Return, ret[0], {
+                                value: make_node(AST_Undefined, ret[0])
+                            }).transform(compressor));
+                            ret = as_statement_array(stat.alternative).concat(ret);
+                            ret.unshift(stat);
+                            continue loop;
+                        }
+                    }
+
+                    var ab = aborts(stat.body);
+                    var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
+                    if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
+                               || (ab instanceof AST_Continue && self === loop_body(lct))
+                               || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
+                        if (ab.label) {
+                            remove(ab.label.thedef.references, ab.label);
+                        }
+                        CHANGED = true;
+                        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, {
+                            body: ret
+                        });
+                        stat.alternative = make_node(AST_BlockStatement, stat, {
+                            body: body
+                        });
+                        ret = [ stat.transform(compressor) ];
+                        continue loop;
+                    }
+
+                    var ab = aborts(stat.alternative);
+                    var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
+                    if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
+                               || (ab instanceof AST_Continue && self === loop_body(lct))
+                               || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
+                        if (ab.label) {
+                            remove(ab.label.thedef.references, ab.label);
+                        }
+                        CHANGED = true;
+                        stat = stat.clone();
+                        stat.body = make_node(AST_BlockStatement, stat.body, {
+                            body: as_statement_array(stat.body).concat(ret)
+                        });
+                        stat.alternative = make_node(AST_BlockStatement, stat.alternative, {
+                            body: as_statement_array(stat.alternative).slice(0, -1)
+                        });
+                        ret = [ stat.transform(compressor) ];
+                        continue loop;
+                    }
+
+                    ret.unshift(stat);
+                    break;
+                  default:
+                    ret.unshift(stat);
+                    break;
+                }
+            }
+            return ret;
+        };
+
+        function eliminate_dead_code(statements, compressor) {
+            var has_quit = false;
+            var orig = statements.length;
+            var self = compressor.self();
+            statements = statements.reduce(function(a, stat){
+                if (has_quit) {
+                    extract_declarations_from_unreachable_code(compressor, stat, a);
+                } else {
+                    if (stat instanceof AST_LoopControl) {
+                        var lct = compressor.loopcontrol_target(stat.label);
+                        if ((stat instanceof AST_Break
+                             && lct instanceof AST_BlockStatement
+                             && loop_body(lct) === self) || (stat instanceof AST_Continue
+                                                             && loop_body(lct) === self)) {
+                            if (stat.label) {
+                                remove(stat.label.thedef.references, stat.label);
+                            }
+                        } else {
+                            a.push(stat);
+                        }
+                    } else {
+                        a.push(stat);
+                    }
+                    if (aborts(stat)) has_quit = true;
+                }
+                return a;
+            }, []);
+            CHANGED = statements.length != orig;
+            return statements;
+        };
+
+        function sequencesize(statements, compressor) {
+            if (statements.length < 2) return statements;
+            var seq = [], ret = [];
+            function push_seq() {
+                seq = AST_Seq.from_array(seq);
+                if (seq) ret.push(make_node(AST_SimpleStatement, seq, {
+                    body: seq
+                }));
+                seq = [];
+            };
+            statements.forEach(function(stat){
+                if (stat instanceof AST_SimpleStatement) seq.push(stat.body);
+                else push_seq(), ret.push(stat);
+            });
+            push_seq();
+            ret = sequencesize_2(ret, compressor);
+            CHANGED = ret.length != statements.length;
+            return ret;
+        };
+
+        function sequencesize_2(statements, compressor) {
+            function cons_seq(right) {
+                ret.pop();
+                var left = prev.body;
+                if (left instanceof AST_Seq) {
+                    left.add(right);
+                } else {
+                    left = AST_Seq.cons(left, right);
+                }
+                return left.transform(compressor);
+            };
+            var ret = [], prev = null;
+            statements.forEach(function(stat){
+                if (prev) {
+                    if (stat instanceof AST_For) {
+                        var opera = {};
+                        try {
+                            prev.body.walk(new TreeWalker(function(node){
+                                if (node instanceof AST_Binary && node.operator == "in")
+                                    throw opera;
+                            }));
+                            if (stat.init && !(stat.init instanceof AST_Definitions)) {
+                                stat.init = cons_seq(stat.init);
+                            }
+                            else if (!stat.init) {
+                                stat.init = prev.body;
+                                ret.pop();
+                            }
+                        } catch(ex) {
+                            if (ex !== opera) throw ex;
+                        }
+                    }
+                    else if (stat instanceof AST_If) {
+                        stat.condition = cons_seq(stat.condition);
+                    }
+                    else if (stat instanceof AST_With) {
+                        stat.expression = cons_seq(stat.expression);
+                    }
+                    else if (stat instanceof AST_Exit && stat.value) {
+                        stat.value = cons_seq(stat.value);
+                    }
+                    else if (stat instanceof AST_Exit) {
+                        stat.value = cons_seq(make_node(AST_Undefined, stat));
+                    }
+                    else if (stat instanceof AST_Switch) {
+                        stat.expression = cons_seq(stat.expression);
+                    }
+                }
+                ret.push(stat);
+                prev = stat instanceof AST_SimpleStatement ? stat : null;
+            });
+            return ret;
+        };
+
+        function join_consecutive_vars(statements, compressor) {
+            var prev = null;
+            return statements.reduce(function(a, stat){
+                if (stat instanceof AST_Definitions && prev && prev.TYPE == stat.TYPE) {
+                    prev.definitions = prev.definitions.concat(stat.definitions);
+                    CHANGED = true;
+                }
+                else if (stat instanceof AST_For
+                         && prev instanceof AST_Definitions
+                         && (!stat.init || stat.init.TYPE == prev.TYPE)) {
+                    CHANGED = true;
+                    a.pop();
+                    if (stat.init) {
+                        stat.init.definitions = prev.definitions.concat(stat.init.definitions);
+                    } else {
+                        stat.init = prev;
+                    }
+                    a.push(stat);
+                    prev = stat;
+                }
+                else {
+                    prev = stat;
+                    a.push(stat);
+                }
+                return a;
+            }, []);
+        };
+
+    };
+
+    function extract_declarations_from_unreachable_code(compressor, stat, target) {
+        compressor.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start);
+        stat.walk(new TreeWalker(function(node){
+            if (node instanceof AST_Definitions) {
+                compressor.warn("Declarations in unreachable code! [{file}:{line},{col}]", node.start);
+                node.remove_initializers();
+                target.push(node);
+                return true;
+            }
+            if (node instanceof AST_Defun) {
+                target.push(node);
+                return true;
+            }
+            if (node instanceof AST_Scope) {
+                return true;
+            }
+        }));
+    };
+
+    /* -----[ boolean/negation helpers ]----- */
+
+    // methods to determine whether an expression has a boolean result type
+    (function (def){
+        var unary_bool = [ "!", "delete" ];
+        var binary_bool = [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ];
+        def(AST_Node, function(){ return false });
+        def(AST_UnaryPrefix, function(){
+            return member(this.operator, unary_bool);
+        });
+        def(AST_Binary, function(){
+            return member(this.operator, binary_bool) ||
+                ( (this.operator == "&&" || this.operator == "||") &&
+                  this.left.is_boolean() && this.right.is_boolean() );
+        });
+        def(AST_Conditional, function(){
+            return this.consequent.is_boolean() && this.alternative.is_boolean();
+        });
+        def(AST_Assign, function(){
+            return this.operator == "=" && this.right.is_boolean();
+        });
+        def(AST_Seq, function(){
+            return this.cdr.is_boolean();
+        });
+        def(AST_True, function(){ return true });
+        def(AST_False, function(){ return true });
+    })(function(node, func){
+        node.DEFMETHOD("is_boolean", func);
+    });
+
+    // methods to determine if an expression has a string result type
+    (function (def){
+        def(AST_Node, function(){ return false });
+        def(AST_String, function(){ return true });
+        def(AST_UnaryPrefix, function(){
+            return this.operator == "typeof";
+        });
+        def(AST_Binary, function(compressor){
+            return this.operator == "+" &&
+                (this.left.is_string(compressor) || this.right.is_string(compressor));
+        });
+        def(AST_Assign, function(compressor){
+            return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor);
+        });
+        def(AST_Seq, function(compressor){
+            return this.cdr.is_string(compressor);
+        });
+        def(AST_Conditional, function(compressor){
+            return this.consequent.is_string(compressor) && this.alternative.is_string(compressor);
+        });
+        def(AST_Call, function(compressor){
+            return compressor.option("unsafe")
+                && this.expression instanceof AST_SymbolRef
+                && this.expression.name == "String"
+                && this.expression.undeclared();
+        });
+    })(function(node, func){
+        node.DEFMETHOD("is_string", func);
+    });
+
+    function best_of(ast1, ast2) {
+        return ast1.print_to_string().length >
+            ast2.print_to_string().length
+            ? ast2 : ast1;
+    };
+
+    // methods to evaluate a constant expression
+    (function (def){
+        // The evaluate method returns an array with one or two
+        // elements.  If the node has been successfully reduced to a
+        // constant, then the second element tells us the value;
+        // otherwise the second element is missing.  The first element
+        // of the array is always an AST_Node descendant; when
+        // evaluation was successful it's a node that represents the
+        // constant; otherwise it's the original node.
+        AST_Node.DEFMETHOD("evaluate", function(compressor){
+            if (!compressor.option("evaluate")) return [ this ];
+            try {
+                var val = this._eval(), ast = make_node_from_constant(compressor, val, this);
+                return [ best_of(ast, this), val ];
+            } catch(ex) {
+                if (ex !== def) throw ex;
+                return [ this ];
+            }
+        });
+        def(AST_Statement, function(){
+            throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
+        });
+        def(AST_Function, function(){
+            // XXX: AST_Function inherits from AST_Scope, which itself
+            // inherits from AST_Statement; however, an AST_Function
+            // isn't really a statement.  This could byte in other
+            // places too. :-( Wish JS had multiple inheritance.
+            return [ this ];
+        });
+        function ev(node) {
+            return node._eval();
+        };
+        def(AST_Node, function(){
+            throw def;          // not constant
+        });
+        def(AST_Constant, function(){
+            return this.getValue();
+        });
+        def(AST_UnaryPrefix, function(){
+            var e = this.expression;
+            switch (this.operator) {
+              case "!": return !ev(e);
+              case "typeof": return typeof ev(e);
+              case "void": return void ev(e);
+              case "~": return ~ev(e);
+              case "-":
+                e = ev(e);
+                if (e === 0) throw def;
+                return -e;
+              case "+": return +ev(e);
+            }
+            throw def;
+        });
+        def(AST_Binary, function(){
+            var left = this.left, right = this.right;
+            switch (this.operator) {
+              case "&&"         : return ev(left) &&         ev(right);
+              case "||"         : return ev(left) ||         ev(right);
+              case "|"          : return ev(left) |          ev(right);
+              case "&"          : return ev(left) &          ev(right);
+              case "^"          : return ev(left) ^          ev(right);
+              case "+"          : return ev(left) +          ev(right);
+              case "*"          : return ev(left) *          ev(right);
+              case "/"          : return ev(left) /          ev(right);
+              case "%"          : return ev(left) %          ev(right);
+              case "-"          : return ev(left) -          ev(right);
+              case "<<"         : return ev(left) <<         ev(right);
+              case ">>"         : return ev(left) >>         ev(right);
+              case ">>>"        : return ev(left) >>>        ev(right);
+              case "=="         : return ev(left) ==         ev(right);
+              case "==="        : return ev(left) ===        ev(right);
+              case "!="         : return ev(left) !=         ev(right);
+              case "!=="        : return ev(left) !==        ev(right);
+              case "<"          : return ev(left) <          ev(right);
+              case "<="         : return ev(left) <=         ev(right);
+              case ">"          : return ev(left) >          ev(right);
+              case ">="         : return ev(left) >=         ev(right);
+              case "in"         : return ev(left) in         ev(right);
+              case "instanceof" : return ev(left) instanceof ev(right);
+            }
+            throw def;
+        });
+        def(AST_Conditional, function(){
+            return ev(this.condition)
+                ? ev(this.consequent)
+                : ev(this.alternative);
+        });
+        def(AST_SymbolRef, function(){
+            var d = this.definition();
+            if (d && d.constant && d.init) return ev(d.init);
+            throw def;
+        });
+    })(function(node, func){
+        node.DEFMETHOD("_eval", func);
+    });
+
+    // method to negate an expression
+    (function(def){
+        function basic_negation(exp) {
+            return make_node(AST_UnaryPrefix, exp, {
+                operator: "!",
+                expression: exp
+            });
+        };
+        def(AST_Node, function(){
+            return basic_negation(this);
+        });
+        def(AST_Statement, function(){
+            throw new Error("Cannot negate a statement");
+        });
+        def(AST_Function, function(){
+            return basic_negation(this);
+        });
+        def(AST_UnaryPrefix, function(){
+            if (this.operator == "!")
+                return this.expression;
+            return basic_negation(this);
+        });
+        def(AST_Seq, function(compressor){
+            var self = this.clone();
+            self.cdr = self.cdr.negate(compressor);
+            return self;
+        });
+        def(AST_Conditional, function(compressor){
+            var self = this.clone();
+            self.consequent = self.consequent.negate(compressor);
+            self.alternative = self.alternative.negate(compressor);
+            return best_of(basic_negation(this), self);
+        });
+        def(AST_Binary, function(compressor){
+            var self = this.clone(), op = this.operator;
+            if (compressor.option("unsafe_comps")) {
+                switch (op) {
+                  case "<=" : self.operator = ">"  ; return self;
+                  case "<"  : self.operator = ">=" ; return self;
+                  case ">=" : self.operator = "<"  ; return self;
+                  case ">"  : self.operator = "<=" ; return self;
+                }
+            }
+            switch (op) {
+              case "==" : self.operator = "!="; return self;
+              case "!=" : self.operator = "=="; return self;
+              case "===": self.operator = "!=="; return self;
+              case "!==": self.operator = "==="; return self;
+              case "&&":
+                self.operator = "||";
+                self.left = self.left.negate(compressor);
+                self.right = self.right.negate(compressor);
+                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 basic_negation(this);
+        });
+    })(function(node, func){
+        node.DEFMETHOD("negate", function(compressor){
+            return func.call(this, compressor);
+        });
+    });
+
+    // determine if expression has side effects
+    (function(def){
+        def(AST_Node, function(){ return true });
+
+        def(AST_EmptyStatement, function(){ return false });
+        def(AST_Constant, function(){ return false });
+        def(AST_This, function(){ return false });
+
+        def(AST_Block, function(){
+            for (var i = this.body.length; --i >= 0;) {
+                if (this.body[i].has_side_effects())
+                    return true;
+            }
+            return false;
+        });
+
+        def(AST_SimpleStatement, function(){
+            return this.body.has_side_effects();
+        });
+        def(AST_Defun, function(){ return true });
+        def(AST_Function, function(){ return false });
+        def(AST_Binary, function(){
+            return this.left.has_side_effects()
+                || this.right.has_side_effects();
+        });
+        def(AST_Assign, function(){ return true });
+        def(AST_Conditional, function(){
+            return this.condition.has_side_effects()
+                || this.consequent.has_side_effects()
+                || this.alternative.has_side_effects();
+        });
+        def(AST_Unary, function(){
+            return this.operator == "delete"
+                || this.operator == "++"
+                || this.operator == "--"
+                || this.expression.has_side_effects();
+        });
+        def(AST_SymbolRef, function(){ return false });
+        def(AST_Object, function(){
+            for (var i = this.properties.length; --i >= 0;)
+                if (this.properties[i].has_side_effects())
+                    return true;
+            return false;
+        });
+        def(AST_ObjectProperty, function(){
+            return this.value.has_side_effects();
+        });
+        def(AST_Array, function(){
+            for (var i = this.elements.length; --i >= 0;)
+                if (this.elements[i].has_side_effects())
+                    return true;
+            return false;
+        });
+        // def(AST_Dot, function(){
+        //     return this.expression.has_side_effects();
+        // });
+        // def(AST_Sub, function(){
+        //     return this.expression.has_side_effects()
+        //         || this.property.has_side_effects();
+        // });
+        def(AST_PropAccess, function(){
+            return true;
+        });
+        def(AST_Seq, function(){
+            return this.car.has_side_effects()
+                || this.cdr.has_side_effects();
+        });
+    })(function(node, func){
+        node.DEFMETHOD("has_side_effects", func);
+    });
+
+    // tell me if a statement aborts
+    function aborts(thing) {
+        return thing && thing.aborts();
+    };
+    (function(def){
+        def(AST_Statement, function(){ return null });
+        def(AST_Jump, function(){ return this });
+        function block_aborts(){
+            var n = this.body.length;
+            return n > 0 && aborts(this.body[n - 1]);
+        };
+        def(AST_BlockStatement, block_aborts);
+        def(AST_SwitchBranch, block_aborts);
+        def(AST_If, function(){
+            return this.alternative && aborts(this.body) && aborts(this.alternative);
+        });
+    })(function(node, func){
+        node.DEFMETHOD("aborts", func);
+    });
+
+    /* -----[ optimizers ]----- */
+
+    OPT(AST_Directive, function(self, compressor){
+        if (self.scope.has_directive(self.value) !== self.scope) {
+            return make_node(AST_EmptyStatement, self);
+        }
+        return self;
+    });
+
+    OPT(AST_Debugger, function(self, compressor){
+        if (compressor.option("drop_debugger"))
+            return make_node(AST_EmptyStatement, self);
+        return self;
+    });
+
+    OPT(AST_LabeledStatement, function(self, compressor){
+        if (self.body instanceof AST_Break
+            && compressor.loopcontrol_target(self.body.label) === self.body) {
+            return make_node(AST_EmptyStatement, self);
+        }
+        return self.label.references.length == 0 ? self.body : self;
+    });
+
+    OPT(AST_Block, function(self, compressor){
+        self.body = tighten_body(self.body, compressor);
+        return self;
+    });
+
+    OPT(AST_BlockStatement, function(self, compressor){
+        self.body = tighten_body(self.body, compressor);
+        switch (self.body.length) {
+          case 1: return self.body[0];
+          case 0: return make_node(AST_EmptyStatement, self);
+        }
+        return self;
+    });
+
+    AST_Scope.DEFMETHOD("drop_unused", function(compressor){
+        var self = this;
+        if (compressor.option("unused")
+            && !(self instanceof AST_Toplevel)
+            && !self.uses_eval
+           ) {
+            var in_use = [];
+            var initializations = new Dictionary();
+            // pass 1: find out which symbols are directly used in
+            // this scope (not in nested scopes).
+            var scope = this;
+            var tw = new TreeWalker(function(node, descend){
+                if (node !== self) {
+                    if (node instanceof AST_Defun) {
+                        initializations.add(node.name.name, node);
+                        return true; // don't go in nested scopes
+                    }
+                    if (node instanceof AST_Definitions && scope === self) {
+                        node.definitions.forEach(function(def){
+                            if (def.value) {
+                                initializations.add(def.name.name, def.value);
+                                if (def.value.has_side_effects()) {
+                                    def.value.walk(tw);
+                                }
+                            }
+                        });
+                        return true;
+                    }
+                    if (node instanceof AST_SymbolRef) {
+                        push_uniq(in_use, node.definition());
+                        return true;
+                    }
+                    if (node instanceof AST_Scope) {
+                        var save_scope = scope;
+                        scope = node;
+                        descend();
+                        scope = save_scope;
+                        return true;
+                    }
+                }
+            });
+            self.walk(tw);
+            // pass 2: for every used symbol we need to walk its
+            // initialization code to figure out if it uses other
+            // symbols (that may not be in_use).
+            for (var i = 0; i < in_use.length; ++i) {
+                in_use[i].orig.forEach(function(decl){
+                    // undeclared globals will be instanceof AST_SymbolRef
+                    var init = initializations.get(decl.name);
+                    if (init) init.forEach(function(init){
+                        var tw = new TreeWalker(function(node){
+                            if (node instanceof AST_SymbolRef) {
+                                push_uniq(in_use, node.definition());
+                            }
+                        });
+                        init.walk(tw);
+                    });
+                });
+            }
+            // pass 3: we should drop declarations not in_use
+            var tt = new TreeTransformer(
+                function before(node, descend, in_list) {
+                    if (node instanceof AST_Lambda) {
+                        for (var a = node.argnames, i = a.length; --i >= 0;) {
+                            var sym = a[i];
+                            if (sym.unreferenced()) {
+                                a.pop();
+                                compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
+                                    name : sym.name,
+                                    file : sym.start.file,
+                                    line : sym.start.line,
+                                    col  : sym.start.col
+                                });
+                            }
+                            else break;
+                        }
+                    }
+                    if (node instanceof AST_Defun && node !== self) {
+                        if (!member(node.name.definition(), in_use)) {
+                            compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", {
+                                name : node.name.name,
+                                file : node.name.start.file,
+                                line : node.name.start.line,
+                                col  : node.name.start.col
+                            });
+                            return make_node(AST_EmptyStatement, node);
+                        }
+                        return node;
+                    }
+                    if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
+                        var def = node.definitions.filter(function(def){
+                            if (member(def.name.definition(), in_use)) return true;
+                            var w = {
+                                name : def.name.name,
+                                file : def.name.start.file,
+                                line : def.name.start.line,
+                                col  : def.name.start.col
+                            };
+                            if (def.value && def.value.has_side_effects()) {
+                                def._unused_side_effects = true;
+                                compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w);
+                                return true;
+                            }
+                            compressor.warn("Dropping unused variable {name} [{file}:{line},{col}]", w);
+                            return false;
+                        });
+                        // place uninitialized names at the start
+                        def = mergeSort(def, function(a, b){
+                            if (!a.value && b.value) return -1;
+                            if (!b.value && a.value) return 1;
+                            return 0;
+                        });
+                        // for unused names whose initialization has
+                        // side effects, we can cascade the init. code
+                        // into the next one, or next statement.
+                        var side_effects = [];
+                        for (var i = 0; i < def.length;) {
+                            var x = def[i];
+                            if (x._unused_side_effects) {
+                                side_effects.push(x.value);
+                                def.splice(i, 1);
+                            } else {
+                                if (side_effects.length > 0) {
+                                    side_effects.push(x.value);
+                                    x.value = AST_Seq.from_array(side_effects);
+                                    side_effects = [];
+                                }
+                                ++i;
+                            }
+                        }
+                        if (side_effects.length > 0) {
+                            side_effects = make_node(AST_BlockStatement, node, {
+                                body: [ make_node(AST_SimpleStatement, node, {
+                                    body: AST_Seq.from_array(side_effects)
+                                }) ]
+                            });
+                        } else {
+                            side_effects = null;
+                        }
+                        if (def.length == 0 && !side_effects) {
+                            return make_node(AST_EmptyStatement, node);
+                        }
+                        if (def.length == 0) {
+                            return side_effects;
+                        }
+                        node.definitions = def;
+                        if (side_effects) {
+                            side_effects.body.unshift(node);
+                            node = side_effects;
+                        }
+                        return node;
+                    }
+                    if (node instanceof AST_For && node.init instanceof AST_BlockStatement) {
+                        descend(node, this);
+                        // certain combination of unused name + side effect leads to:
+                        //    https://github.com/mishoo/UglifyJS2/issues/44
+                        // that's an invalid AST.
+                        // We fix it at this stage by moving the `var` outside the `for`.
+                        var body = node.init.body.slice(0, -1);
+                        node.init = node.init.body.slice(-1)[0].body;
+                        body.push(node);
+                        return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
+                            body: body
+                        });
+                    }
+                    if (node instanceof AST_Scope && node !== self)
+                        return node;
+                }
+            );
+            self.transform(tt);
+        }
+    });
+
+    AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){
+        var hoist_funs = compressor.option("hoist_funs");
+        var hoist_vars = compressor.option("hoist_vars");
+        var self = this;
+        if (hoist_funs || hoist_vars) {
+            var dirs = [];
+            var hoisted = [];
+            var vars = new Dictionary(), 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.set(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
+                    }
+                }
+            );
+            self = self.transform(tt);
+            if (vars_found > 0) {
+                // collect only vars which don't show up in self's arguments list
+                var defs = [];
+                vars.each(function(def, name){
+                    if (self instanceof AST_Lambda
+                        && find_if(function(x){ return x.name == def.name.name },
+                                   self.argnames)) {
+                        vars.del(name);
+                    } else {
+                        def = def.clone();
+                        def.value = null;
+                        defs.push(def);
+                        vars.set(name, def);
+                    }
+                });
+                if (defs.length > 0) {
+                    // try to merge in assignments
+                    for (var i = 0; i < self.body.length;) {
+                        if (self.body[i] instanceof AST_SimpleStatement) {
+                            var expr = self.body[i].body, sym, assign;
+                            if (expr instanceof AST_Assign
+                                && expr.operator == "="
+                                && (sym = expr.left) instanceof AST_Symbol
+                                && vars.has(sym.name))
+                            {
+                                var def = vars.get(sym.name);
+                                if (def.value) break;
+                                def.value = expr.right;
+                                remove(defs, def);
+                                defs.push(def);
+                                self.body.splice(i, 1);
+                                continue;
+                            }
+                            if (expr instanceof AST_Seq
+                                && (assign = expr.car) instanceof AST_Assign
+                                && assign.operator == "="
+                                && (sym = assign.left) instanceof AST_Symbol
+                                && vars.has(sym.name))
+                            {
+                                var def = vars.get(sym.name);
+                                if (def.value) break;
+                                def.value = assign.right;
+                                remove(defs, def);
+                                defs.push(def);
+                                self.body[i].body = expr.cdr;
+                                continue;
+                            }
+                        }
+                        if (self.body[i] instanceof AST_EmptyStatement) {
+                            self.body.splice(i, 1);
+                            continue;
+                        }
+                        if (self.body[i] instanceof AST_BlockStatement) {
+                            var tmp = [ i, 1 ].concat(self.body[i].body);
+                            self.body.splice.apply(self.body, tmp);
+                            continue;
+                        }
+                        break;
+                    }
+                    defs = make_node(AST_Var, self, {
+                        definitions: defs
+                    });
+                    hoisted.push(defs);
+                };
+            }
+            self.body = dirs.concat(hoisted, self.body);
+        }
+        return self;
+    });
+
+    OPT(AST_SimpleStatement, function(self, compressor){
+        if (compressor.option("side_effects")) {
+            if (!self.body.has_side_effects()) {
+                compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
+                return make_node(AST_EmptyStatement, self);
+            }
+        }
+        return self;
+    });
+
+    OPT(AST_DWLoop, function(self, compressor){
+        var cond = self.condition.evaluate(compressor);
+        self.condition = cond[0];
+        if (!compressor.option("loops")) return self;
+        if (cond.length > 1) {
+            if (cond[1]) {
+                return make_node(AST_For, self, {
+                    body: self.body
+                });
+            } else if (self instanceof AST_While) {
+                if (compressor.option("dead_code")) {
+                    var a = [];
+                    extract_declarations_from_unreachable_code(compressor, self.body, a);
+                    return make_node(AST_BlockStatement, self, { body: a });
+                }
+            } else {
+                return self.body;
+            }
+        }
+        return self;
+    });
+
+    function if_break_in_loop(self, compressor) {
+        function drop_it(rest) {
+            rest = as_statement_array(rest);
+            if (self.body instanceof AST_BlockStatement) {
+                self.body = self.body.clone();
+                self.body.body = rest.concat(self.body.body.slice(1));
+                self.body = self.body.transform(compressor);
+            } else {
+                self.body = make_node(AST_BlockStatement, self.body, {
+                    body: rest
+                }).transform(compressor);
+            }
+            if_break_in_loop(self, compressor);
+        }
+        var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
+        if (first instanceof AST_If) {
+            if (first.body instanceof AST_Break
+                && compressor.loopcontrol_target(first.body.label) === self) {
+                if (self.condition) {
+                    self.condition = make_node(AST_Binary, self.condition, {
+                        left: self.condition,
+                        operator: "&&",
+                        right: first.condition.negate(compressor),
+                    });
+                } else {
+                    self.condition = first.condition.negate(compressor);
+                }
+                drop_it(first.alternative);
+            }
+            else if (first.alternative instanceof AST_Break
+                     && compressor.loopcontrol_target(first.alternative.label) === self) {
+                if (self.condition) {
+                    self.condition = make_node(AST_Binary, self.condition, {
+                        left: self.condition,
+                        operator: "&&",
+                        right: first.condition,
+                    });
+                } else {
+                    self.condition = first.condition;
+                }
+                drop_it(first.body);
+            }
+        }
+    };
+
+    OPT(AST_While, function(self, compressor) {
+        if (!compressor.option("loops")) return self;
+        self = AST_DWLoop.prototype.optimize.call(self, compressor);
+        if (self instanceof AST_While) {
+            if_break_in_loop(self, compressor);
+            self = make_node(AST_For, self, self).transform(compressor);
+        }
+        return self;
+    });
+
+    OPT(AST_For, function(self, compressor){
+        var cond = self.condition;
+        if (cond) {
+            cond = cond.evaluate(compressor);
+            self.condition = cond[0];
+        }
+        if (!compressor.option("loops")) return self;
+        if (cond) {
+            if (cond.length > 1 && !cond[1]) {
+                if (compressor.option("dead_code")) {
+                    var a = [];
+                    if (self.init instanceof AST_Statement) {
+                        a.push(self.init);
+                    }
+                    else if (self.init) {
+                        a.push(make_node(AST_SimpleStatement, self.init, {
+                            body: self.init
+                        }));
+                    }
+                    extract_declarations_from_unreachable_code(compressor, self.body, a);
+                    return make_node(AST_BlockStatement, self, { body: a });
+                }
+            }
+        }
+        if_break_in_loop(self, compressor);
+        return self;
+    });
+
+    OPT(AST_If, function(self, compressor){
+        if (!compressor.option("conditionals")) return self;
+        // if condition can be statically determined, warn and drop
+        // one of the blocks.  note, statically determined implies
+        // “has no side effects”; also it doesn't work for cases like
+        // `x && true`, though it probably should.
+        var cond = self.condition.evaluate(compressor);
+        self.condition = cond[0];
+        if (cond.length > 1) {
+            if (cond[1]) {
+                compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start);
+                if (compressor.option("dead_code")) {
+                    var a = [];
+                    if (self.alternative) {
+                        extract_declarations_from_unreachable_code(compressor, self.alternative, a);
+                    }
+                    a.push(self.body);
+                    return make_node(AST_BlockStatement, self, { body: a }).transform(compressor);
+                }
+            } else {
+                compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
+                if (compressor.option("dead_code")) {
+                    var a = [];
+                    extract_declarations_from_unreachable_code(compressor, self.body, a);
+                    if (self.alternative) a.push(self.alternative);
+                    return make_node(AST_BlockStatement, self, { body: a }).transform(compressor);
+                }
+            }
+        }
+        if (is_empty(self.alternative)) self.alternative = null;
+        var negated = self.condition.negate(compressor);
+        var negated_is_best = best_of(self.condition, negated) === negated;
+        if (self.alternative && negated_is_best) {
+            negated_is_best = false; // because we already do the switch here.
+            self.condition = negated;
+            var tmp = self.body;
+            self.body = self.alternative || make_node(AST_EmptyStatement);
+            self.alternative = tmp;
+        }
+        if (is_empty(self.body) && is_empty(self.alternative)) {
+            return make_node(AST_SimpleStatement, self.condition, {
+                body: self.condition
+            }).transform(compressor);
+        }
+        if (self.body instanceof AST_SimpleStatement
+            && self.alternative instanceof AST_SimpleStatement) {
+            return make_node(AST_SimpleStatement, self, {
+                body: make_node(AST_Conditional, self, {
+                    condition   : self.condition,
+                    consequent  : self.body.body,
+                    alternative : self.alternative.body
+                })
+            }).transform(compressor);
+        }
+        if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
+            if (negated_is_best) return make_node(AST_SimpleStatement, self, {
+                body: make_node(AST_Binary, self, {
+                    operator : "||",
+                    left     : negated,
+                    right    : self.body.body
+                })
+            }).transform(compressor);
+            return make_node(AST_SimpleStatement, self, {
+                body: make_node(AST_Binary, self, {
+                    operator : "&&",
+                    left     : self.condition,
+                    right    : self.body.body
+                })
+            }).transform(compressor);
+        }
+        if (self.body instanceof AST_EmptyStatement
+            && self.alternative
+            && self.alternative instanceof AST_SimpleStatement) {
+            return make_node(AST_SimpleStatement, self, {
+                body: make_node(AST_Binary, self, {
+                    operator : "||",
+                    left     : self.condition,
+                    right    : self.alternative.body
+                })
+            }).transform(compressor);
+        }
+        if (self.body instanceof AST_Exit
+            && self.alternative instanceof AST_Exit
+            && self.body.TYPE == self.alternative.TYPE) {
+            return make_node(self.body.CTOR, self, {
+                value: make_node(AST_Conditional, self, {
+                    condition   : self.condition,
+                    consequent  : self.body.value || make_node(AST_Undefined, self.body).optimize(compressor),
+                    alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).optimize(compressor)
+                })
+            }).transform(compressor);
+        }
+        if (self.body instanceof AST_If
+            && !self.body.alternative
+            && !self.alternative) {
+            self.condition = make_node(AST_Binary, self.condition, {
+                operator: "&&",
+                left: self.condition,
+                right: self.body.condition
+            }).transform(compressor);
+            self.body = self.body.body;
+        }
+        if (aborts(self.body)) {
+            if (self.alternative) {
+                var alt = self.alternative;
+                self.alternative = null;
+                return make_node(AST_BlockStatement, self, {
+                    body: [ self, alt ]
+                }).transform(compressor);
+            }
+        }
+        if (aborts(self.alternative)) {
+            var body = self.body;
+            self.body = self.alternative;
+            self.condition = negated_is_best ? negated : self.condition.negate(compressor);
+            self.alternative = null;
+            return make_node(AST_BlockStatement, self, {
+                body: [ self, body ]
+            }).transform(compressor);
+        }
+        return self;
+    });
+
+    OPT(AST_Switch, function(self, compressor){
+        if (self.body.length == 0 && compressor.option("conditionals")) {
+            return make_node(AST_SimpleStatement, self, {
+                body: self.expression
+            }).transform(compressor);
+        }
+        var last_branch = self.body[self.body.length - 1];
+        if (last_branch) {
+            var stat = last_branch.body[last_branch.body.length - 1]; // last statement
+            if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self)
+                last_branch.body.pop();
+        }
+        var exp = self.expression.evaluate(compressor);
+        out: if (exp.length == 2) try {
+            // constant expression
+            self.expression = exp[0];
+            if (!compressor.option("dead_code")) break out;
+            var value = exp[1];
+            var in_if = false;
+            var in_block = false;
+            var started = false;
+            var stopped = false;
+            var ruined = false;
+            var tt = new TreeTransformer(function(node, descend, in_list){
+                if (node instanceof AST_Lambda || node instanceof AST_SimpleStatement) {
+                    // no need to descend these node types
+                    return node;
+                }
+                else if (node instanceof AST_Switch && node === self) {
+                    node = node.clone();
+                    descend(node, this);
+                    return ruined ? node : make_node(AST_BlockStatement, node, {
+                        body: node.body.reduce(function(a, branch){
+                            return a.concat(branch.body);
+                        }, [])
+                    }).transform(compressor);
+                }
+                else if (node instanceof AST_If || node instanceof AST_Try) {
+                    var save = in_if;
+                    in_if = !in_block;
+                    descend(node, this);
+                    in_if = save;
+                    return node;
+                }
+                else if (node instanceof AST_StatementWithBody || node instanceof AST_Switch) {
+                    var save = in_block;
+                    in_block = true;
+                    descend(node, this);
+                    in_block = save;
+                    return node;
+                }
+                else if (node instanceof AST_Break && this.loopcontrol_target(node.label) === self) {
+                    if (in_if) {
+                        ruined = true;
+                        return node;
+                    }
+                    if (in_block) return node;
+                    stopped = true;
+                    return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
+                }
+                else if (node instanceof AST_SwitchBranch && this.parent() === self) {
+                    if (stopped) return MAP.skip;
+                    if (node instanceof AST_Case) {
+                        var exp = node.expression.evaluate(compressor);
+                        if (exp.length < 2) {
+                            // got a case with non-constant expression, baling out
+                            throw self;
+                        }
+                        if (exp[1] === value || started) {
+                            started = true;
+                            if (aborts(node)) stopped = true;
+                            descend(node, this);
+                            return node;
+                        }
+                        return MAP.skip;
+                    }
+                    descend(node, this);
+                    return node;
+                }
+            });
+            tt.stack = compressor.stack.slice(); // so that's able to see parent nodes
+            self = self.transform(tt);
+        } catch(ex) {
+            if (ex !== self) throw ex;
+        }
+        return self;
+    });
+
+    OPT(AST_Case, function(self, compressor){
+        self.body = tighten_body(self.body, compressor);
+        return self;
+    });
+
+    OPT(AST_Try, function(self, compressor){
+        self.body = tighten_body(self.body, compressor);
+        return self;
+    });
+
+    AST_Definitions.DEFMETHOD("remove_initializers", function(){
+        this.definitions.forEach(function(def){ def.value = null });
+    });
+
+    AST_Definitions.DEFMETHOD("to_assignments", function(){
+        var assignments = this.definitions.reduce(function(a, def){
+            if (def.value) {
+                var name = make_node(AST_SymbolRef, def.name, def.name);
+                a.push(make_node(AST_Assign, def, {
+                    operator : "=",
+                    left     : name,
+                    right    : def.value
+                }));
+            }
+            return a;
+        }, []);
+        if (assignments.length == 0) return null;
+        return AST_Seq.from_array(assignments);
+    });
+
+    OPT(AST_Definitions, function(self, compressor){
+        if (self.definitions.length == 0)
+            return make_node(AST_EmptyStatement, self);
+        return self;
+    });
+
+    OPT(AST_Function, function(self, compressor){
+        self = AST_Lambda.prototype.optimize.call(self, compressor);
+        if (compressor.option("unused")) {
+            if (self.name && self.name.unreferenced()) {
+                self.name = null;
+            }
+        }
+        return self;
+    });
+
+    OPT(AST_Call, function(self, compressor){
+        if (compressor.option("unsafe")) {
+            var exp = self.expression;
+            if (exp instanceof AST_SymbolRef && exp.undeclared()) {
+                switch (exp.name) {
+                  case "Array":
+                    if (self.args.length != 1) {
+                        return make_node(AST_Array, self, {
+                            elements: self.args
+                        });
+                    }
+                    break;
+                  case "Object":
+                    if (self.args.length == 0) {
+                        return make_node(AST_Object, self, {
+                            properties: []
+                        });
+                    }
+                    break;
+                  case "String":
+                    if (self.args.length == 0) return make_node(AST_String, self, {
+                        value: ""
+                    });
+                    return make_node(AST_Binary, self, {
+                        left: self.args[0],
+                        operator: "+",
+                        right: make_node(AST_String, self, { value: "" })
+                    });
+                }
+            }
+            else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {
+                return make_node(AST_Binary, self, {
+                    left: make_node(AST_String, self, { value: "" }),
+                    operator: "+",
+                    right: exp.expression
+                }).transform(compressor);
+            }
+        }
+        if (compressor.option("side_effects")) {
+            if (self.expression instanceof AST_Function
+                && self.args.length == 0
+                && !AST_Block.prototype.has_side_effects.call(self.expression)) {
+                return make_node(AST_Undefined, self).transform(compressor);
+            }
+        }
+        return self;
+    });
+
+    OPT(AST_New, function(self, compressor){
+        if (compressor.option("unsafe")) {
+            var exp = self.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, self, self).transform(compressor);
+                }
+            }
+        }
+        return self;
+    });
+
+    OPT(AST_Seq, function(self, compressor){
+        if (!compressor.option("side_effects"))
+            return self;
+        if (!self.car.has_side_effects()) {
+            // we shouldn't compress (1,eval)(something) to
+            // eval(something) because that changes the meaning of
+            // eval (becomes lexical instead of global).
+            var p;
+            if (!(self.cdr instanceof AST_SymbolRef
+                  && self.cdr.name == "eval"
+                  && self.cdr.undeclared()
+                  && (p = compressor.parent()) instanceof AST_Call
+                  && p.expression === self)) {
+                return self.cdr;
+            }
+        }
+        if (compressor.option("cascade")) {
+            if (self.car instanceof AST_Assign
+                && !self.car.left.has_side_effects()
+                && self.car.left.equivalent_to(self.cdr)) {
+                return self.car;
+            }
+            if (!self.car.has_side_effects()
+                && !self.cdr.has_side_effects()
+                && self.car.equivalent_to(self.cdr)) {
+                return self.car;
+            }
+        }
+        return self;
+    });
+
+    AST_Unary.DEFMETHOD("lift_sequences", function(compressor){
+        if (compressor.option("sequences")) {
+            if (this.expression instanceof AST_Seq) {
+                var seq = this.expression;
+                var x = seq.to_array();
+                this.expression = x.pop();
+                x.push(this);
+                seq = AST_Seq.from_array(x).transform(compressor);
+                return seq;
+            }
+        }
+        return this;
+    });
+
+    OPT(AST_UnaryPostfix, function(self, compressor){
+        return self.lift_sequences(compressor);
+    });
+
+    OPT(AST_UnaryPrefix, function(self, compressor){
+        self = self.lift_sequences(compressor);
+        var e = self.expression;
+        if (compressor.option("booleans") && compressor.in_boolean_context()) {
+            switch (self.operator) {
+              case "!":
+                if (e instanceof AST_UnaryPrefix && e.operator == "!") {
+                    // !!foo ==> foo, if we're in boolean context
+                    return e.expression;
+                }
+                break;
+              case "typeof":
+                // typeof always returns a non-empty string, thus it's
+                // always true in booleans
+                compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
+                return make_node(AST_True, self);
+            }
+            if (e instanceof AST_Binary && self.operator == "!") {
+                self = best_of(self, e.negate(compressor));
+            }
+        }
+        return self.evaluate(compressor)[0];
+    });
+
+    AST_Binary.DEFMETHOD("lift_sequences", function(compressor){
+        if (compressor.option("sequences")) {
+            if (this.left instanceof AST_Seq) {
+                var seq = this.left;
+                var x = seq.to_array();
+                this.left = x.pop();
+                x.push(this);
+                seq = AST_Seq.from_array(x).transform(compressor);
+                return seq;
+            }
+            if (this.right instanceof AST_Seq
+                && !(this.operator == "||" || this.operator == "&&")
+                && !this.left.has_side_effects()) {
+                var seq = this.right;
+                var x = seq.to_array();
+                this.right = x.pop();
+                x.push(this);
+                seq = AST_Seq.from_array(x).transform(compressor);
+                return seq;
+            }
+        }
+        return this;
+    });
+
+    var commutativeOperators = makePredicate("== === != !== * & | ^");
+
+    OPT(AST_Binary, function(self, compressor){
+        function reverse(op) {
+            if (!(self.left.has_side_effects() && self.right.has_side_effects())) {
+                if (op) self.operator = op;
+                var tmp = self.left;
+                self.left = self.right;
+                self.right = tmp;
+            }
+        };
+        if (commutativeOperators(self.operator)) {
+            if (self.right instanceof AST_Constant
+                && !(self.left instanceof AST_Constant)) {
+                reverse();
+            }
+        }
+        self = self.lift_sequences(compressor);
+        if (compressor.option("comparisons")) switch (self.operator) {
+          case "===":
+          case "!==":
+            if ((self.left.is_string(compressor) && self.right.is_string(compressor)) ||
+                (self.left.is_boolean() && self.right.is_boolean())) {
+                self.operator = self.operator.substr(0, 2);
+            }
+            // XXX: intentionally falling down to the next case
+          case "==":
+          case "!=":
+            if (self.left instanceof AST_String
+                && self.left.value == "undefined"
+                && self.right instanceof AST_UnaryPrefix
+                && self.right.operator == "typeof"
+                && compressor.option("unsafe")) {
+                if (!(self.right.expression instanceof AST_SymbolRef)
+                    || !self.right.expression.undeclared()) {
+                    self.left = self.right.expression;
+                    self.right = make_node(AST_Undefined, self.left).optimize(compressor);
+                    if (self.operator.length == 2) self.operator += "=";
+                }
+            }
+            break;
+        }
+        if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) {
+          case "&&":
+            var ll = self.left.evaluate(compressor);
+            var rr = self.right.evaluate(compressor);
+            if ((ll.length > 1 && !ll[1]) || (rr.length > 1 && !rr[1])) {
+                compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start);
+                return make_node(AST_False, self);
+            }
+            if (ll.length > 1 && ll[1]) {
+                return rr[0];
+            }
+            if (rr.length > 1 && rr[1]) {
+                return ll[0];
+            }
+            break;
+          case "||":
+            var ll = self.left.evaluate(compressor);
+            var rr = self.right.evaluate(compressor);
+            if ((ll.length > 1 && ll[1]) || (rr.length > 1 && rr[1])) {
+                compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start);
+                return make_node(AST_True, self);
+            }
+            if (ll.length > 1 && !ll[1]) {
+                return rr[0];
+            }
+            if (rr.length > 1 && !rr[1]) {
+                return ll[0];
+            }
+            break;
+          case "+":
+            var ll = self.left.evaluate(compressor);
+            var rr = self.right.evaluate(compressor);
+            if ((ll.length > 1 && ll[0] instanceof AST_String && ll[1]) ||
+                (rr.length > 1 && rr[0] instanceof AST_String && rr[1])) {
+                compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start);
+                return make_node(AST_True, self);
+            }
+            break;
+        }
+        var exp = self.evaluate(compressor);
+        if (exp.length > 1) {
+            if (best_of(exp[0], self) !== self)
+                return exp[0];
+        }
+        if (compressor.option("comparisons")) {
+            if (!(compressor.parent() instanceof AST_Binary)
+                || compressor.parent() instanceof AST_Assign) {
+                var negated = make_node(AST_UnaryPrefix, self, {
+                    operator: "!",
+                    expression: self.negate(compressor)
+                });
+                self = best_of(self, negated);
+            }
+            switch (self.operator) {
+              case "<": reverse(">"); break;
+              case "<=": reverse(">="); break;
+            }
+        }
+        if (self.operator == "+" && self.right instanceof AST_String
+            && self.right.getValue() === "" && self.left instanceof AST_Binary
+            && self.left.operator == "+" && self.left.is_string(compressor)) {
+            return self.left;
+        }
+        return self;
+    });
+
+    OPT(AST_SymbolRef, function(self, compressor){
+        if (self.undeclared()) {
+            var defines = compressor.option("global_defs");
+            if (defines && defines.hasOwnProperty(self.name)) {
+                return make_node_from_constant(compressor, defines[self.name], self);
+            }
+            switch (self.name) {
+              case "undefined":
+                return make_node(AST_Undefined, self);
+              case "NaN":
+                return make_node(AST_NaN, self);
+              case "Infinity":
+                return make_node(AST_Infinity, self);
+            }
+        }
+        return self;
+    });
+
+    OPT(AST_Undefined, function(self, compressor){
+        if (compressor.option("unsafe")) {
+            var scope = compressor.find_parent(AST_Scope);
+            var undef = scope.find_variable("undefined");
+            if (undef) {
+                var ref = make_node(AST_SymbolRef, self, {
+                    name   : "undefined",
+                    scope  : scope,
+                    thedef : undef
+                });
+                ref.reference();
+                return ref;
+            }
+        }
+        return self;
+    });
+
+    var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
+    OPT(AST_Assign, function(self, compressor){
+        self = self.lift_sequences(compressor);
+        if (self.operator == "="
+            && self.left instanceof AST_SymbolRef
+            && self.right instanceof AST_Binary
+            && self.right.left instanceof AST_SymbolRef
+            && self.right.left.name == self.left.name
+            && member(self.right.operator, ASSIGN_OPS)) {
+            self.operator = self.right.operator + "=";
+            self.right = self.right.right;
+        }
+        return self;
+    });
+
+    OPT(AST_Conditional, function(self, compressor){
+        if (!compressor.option("conditionals")) return self;
+        if (self.condition instanceof AST_Seq) {
+            var car = self.condition.car;
+            self.condition = self.condition.cdr;
+            return AST_Seq.cons(car, self);
+        }
+        var cond = self.condition.evaluate(compressor);
+        if (cond.length > 1) {
+            if (cond[1]) {
+                compressor.warn("Condition always true [{file}:{line},{col}]", self.start);
+                return self.consequent;
+            } else {
+                compressor.warn("Condition always false [{file}:{line},{col}]", self.start);
+                return self.alternative;
+            }
+        }
+        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
+            && consequent.left.equivalent_to(alternative.left)
+           ) {
+            /*
+             * 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;
+    });
+
+    OPT(AST_Boolean, function(self, compressor){
+        if (compressor.option("booleans")) {
+            var p = compressor.parent();
+            if (p instanceof AST_Binary && (p.operator == "=="
+                                            || p.operator == "!=")) {
+                compressor.warn("Non-strict equality against boolean: {operator} {value} [{file}:{line},{col}]", {
+                    operator : p.operator,
+                    value    : self.value,
+                    file     : p.start.file,
+                    line     : p.start.line,
+                    col      : p.start.col,
+                });
+                return make_node(AST_Number, self, {
+                    value: +self.value
+                });
+            }
+            return make_node(AST_UnaryPrefix, self, {
+                operator: "!",
+                expression: make_node(AST_Number, self, {
+                    value: 1 - self.value
+                })
+            });
+        }
+        return self;
+    });
+
+    OPT(AST_Sub, function(self, compressor){
+        var prop = self.property;
+        if (prop instanceof AST_String && compressor.option("properties")) {
+            prop = prop.getValue();
+            if (is_identifier(prop)) {
+                return make_node(AST_Dot, self, {
+                    expression : self.expression,
+                    property   : prop
+                });
+            }
+        }
+        return self;
+    });
+
+    function literals_in_boolean_context(self, compressor) {
+        if (compressor.option("booleans") && compressor.in_boolean_context()) {
+            return make_node(AST_True, self);
+        }
+        return self;
+    };
+    OPT(AST_Array, literals_in_boolean_context);
+    OPT(AST_Object, literals_in_boolean_context);
+    OPT(AST_RegExp, literals_in_boolean_context);
+
+})();