From f03138daa805c01293a2b60f96231989906aca59 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 3 Sep 2012 15:47:15 +0300 Subject: [PATCH] resolve constant expressions --- lib/compress.js | 271 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 254 insertions(+), 17 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index c290a02e..139d54a8 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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); -- 2.34.1