resolve constant expressions
authorMihai Bazon <mihai@bazon.net>
Mon, 3 Sep 2012 12:47:15 +0000 (15:47 +0300)
committerMihai Bazon <mihai@bazon.net>
Mon, 3 Sep 2012 12:47:15 +0000 (15:47 +0300)
lib/compress.js

index c290a02..139d54a 100644 (file)
@@ -60,6 +60,9 @@ function Compressor(options, false_by_default) {
         keep_comps    : !false_by_default,
         drop_debugger : !false_by_default,
         unsafe        : !false_by_default,
+        ifs           : !false_by_default,
+        comparations  : !false_by_default,
+        evaluate      : !false_by_default,
 
         warnings      : true
     });
@@ -86,6 +89,7 @@ function Compressor(options, false_by_default) {
     });
 
     function make_node(ctor, orig, props) {
+        if (!props) props = {};
         if (!props.start) props.start = orig.start;
         if (!props.end) props.end = orig.end;
         return new ctor(props);
@@ -106,23 +110,6 @@ function Compressor(options, false_by_default) {
         });
     };
 
-    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.body = self.body.squeeze(compressor);
-        return self.label.references.length == 0 ? self.body : self;
-    });
-
-    SQUEEZE(AST_Statement, function(self, compressor){
-        self = self.clone();
-        self.body = self.body.squeeze(compressor);
-        return self;
-    });
-
     function tighten_body(statements, compressor) {
         statements = do_list(statements, compressor);
         statements = eliminate_spurious_blocks(statements);
@@ -219,6 +206,238 @@ function Compressor(options, false_by_default) {
         return statements;
     }
 
+    /* -----[ 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.second.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(){
+            return this.operator == "+" &&
+                (this.left.is_string() || this.right.is_string());
+        });
+        def(AST_Assign, function(){
+            return this.operator == "=" && this.right.is_string();
+        });
+    })(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){
+        AST_Node.DEFMETHOD("evaluate", function(compressor, constant, not_constant){
+            if (!compressor.option("evaluate")) return this;
+            try {
+                var val = this._eval(), ast;
+                switch (typeof val) {
+                  case "string":
+                    ast = make_node(AST_String, this, {
+                        value: val
+                    });
+                    break;
+                  case "number":
+                    ast = make_node(AST_Number, this, {
+                        value: val
+                    });
+                    break;
+                  case "boolean":
+                    ast = make_node(val ? AST_True : AST_False, this);
+                    break;
+                  case "undefined":
+                    ast = make_node(AST_Undefined, this);
+                    break;
+                  default:
+                    if (val === null) {
+                        ast = make_node(AST_Null, this);
+                        break;
+                    }
+                    throw new Error(string_template("Can't handle constant of type: {type}", {
+                        type: typeof val
+                    }));
+                }
+                if (constant) return constant(ast, val);
+                return ast;
+            } catch(ex) {
+                if (ex !== def) throw ex;
+                if (not_constant) return not_constant(this);
+                return this;
+            }
+        });
+        function evaluate(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 !evaluate(e);
+              case "typeof": return typeof evaluate(e);
+              case "~": return ~evaluate(e);
+              case "-": return -evaluate(e);
+              case "+": return +evaluate(e);
+            }
+            throw def;
+        });
+        def(AST_Binary, function(){
+            var left = this.left, right = this.right;
+            switch (this.operator) {
+              case "&&"         : return evaluate(left) &&         evaluate(right);
+              case "||"         : return evaluate(left) ||         evaluate(right);
+              case "|"          : return evaluate(left) |          evaluate(right);
+              case "&"          : return evaluate(left) &          evaluate(right);
+              case "^"          : return evaluate(left) ^          evaluate(right);
+              case "+"          : return evaluate(left) +          evaluate(right);
+              case "*"          : return evaluate(left) *          evaluate(right);
+              case "/"          : return evaluate(left) /          evaluate(right);
+              case "%"          : return evaluate(left) %          evaluate(right);
+              case "-"          : return evaluate(left) -          evaluate(right);
+              case "<<"         : return evaluate(left) <<         evaluate(right);
+              case ">>"         : return evaluate(left) >>         evaluate(right);
+              case ">>>"        : return evaluate(left) >>>        evaluate(right);
+              case "=="         : return evaluate(left) ==         evaluate(right);
+              case "==="        : return evaluate(left) ===        evaluate(right);
+              case "!="         : return evaluate(left) !=         evaluate(right);
+              case "!=="        : return evaluate(left) !==        evaluate(right);
+              case "<"          : return evaluate(left) <          evaluate(right);
+              case "<="         : return evaluate(left) <=         evaluate(right);
+              case ">"          : return evaluate(left) >          evaluate(right);
+              case ">="         : return evaluate(left) >=         evaluate(right);
+              case "in"         : return evaluate(left) in         evaluate(right);
+              case "instanceof" : return evaluate(left) instanceof evaluate(right);
+            }
+            throw def;
+        });
+        def(AST_Conditional, function(){
+            return evaluate(this.condition)
+                ? evaluate(this.consequent)
+                : evaluate(this.alternative);
+        });
+    })(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 evaluate a statement");
+        });
+        def(AST_UnaryPrefix, function(){
+            if (this.operator == "!" && this.expression.is_boolean())
+                return this.expression();
+            return basic_negation(this);
+        });
+        def(AST_Seq, function(compressor){
+            var self = this.clone();
+            self.second = self.second.negate(compressor);
+            return self;
+        });
+        def(AST_Conditional, function(){
+            var self = this.clone();
+            self.consequent = self.consequent.negate(compressor);
+            self.alternative = self.alternative.negate(compressor);
+            //return best_of(basic_negation(this), self);
+            return self;
+        });
+        def(AST_Binary, function(compressor){
+            var self = this.clone(), op = this.operator;
+            if (compressor.option("comparations")) 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);
+                return self;
+              case "||":
+                self.operator = "&&";
+                self.left = self.left.negate(compressor);
+                self.right = self.right.negate(compressor);
+                //return best_of(basic_negation(this), self);
+                return self;
+            }
+            return basic_negation(this);
+        });
+    })(function(node, func){
+        node.DEFMETHOD("negate", func);
+    });
+
+    /* -----[ node squeezers ]----- */
+
+    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.body = self.body.squeeze(compressor);
+        return self.label.references.length == 0 ? self.body : 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 = tighten_body(self.body, compressor);
@@ -279,9 +498,14 @@ function Compressor(options, false_by_default) {
         self.body = self.body.squeeze(compressor);
         if (self.alternative)
             self.alternative = self.alternative.squeeze(compressor);
+        if (!compressor.option("ifs")) return self;
         return self;
     });
 
+    AST_If.DEFMETHOD("switch_branches", function(){
+
+    });
+
     SQUEEZE(AST_Switch, function(self, compressor){
         self = self.clone();
         self.expression = self.expression.squeeze(compressor);
@@ -372,7 +596,20 @@ function Compressor(options, false_by_default) {
         return self;
     });
 
+    SQUEEZE(AST_UnaryPrefix, function(self, compressor){
+        self = self.clone();
+        self.expression = self.expression.squeeze(compressor);
+        return self.evaluate(compressor);
+    });
+
     SQUEEZE(AST_Binary, function(self, compressor){
+        self = self.clone();
+        self.left = self.left.squeeze(compressor);
+        self.right = self.right.squeeze(compressor);
+        return self.evaluate(compressor);
+    });
+
+    SQUEEZE(AST_Assign, function(self, compressor){
         self = self.clone();
         self.left = self.left.squeeze(compressor);
         self.right = self.right.squeeze(compressor);