aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMihai Bazon <mihai@bazon.net>2012-09-03 15:47:15 +0300
committerMihai Bazon <mihai@bazon.net>2012-09-03 15:47:15 +0300
commitf03138daa805c01293a2b60f96231989906aca59 (patch)
treee42ef4f38b21101c1f112ffa42d21b64fbf4bb30
parentf70226461732321780a04f5c5dd02ed753ede0cf (diff)
downloadtracifyjs-f03138daa805c01293a2b60f96231989906aca59.tar.gz
tracifyjs-f03138daa805c01293a2b60f96231989906aca59.zip
resolve constant expressions
-rw-r--r--lib/compress.js271
1 files 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,10 +596,23 @@ 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);
return self;
});