Files
UglifyJS/lib/compress.js
2012-08-22 15:21:58 +03:00

363 lines
12 KiB
JavaScript

// The layout of the compressor follows the code generator (see
// output.js). Basically each node will have a "squeeze" method
// that will apply all known compression rules for that node, and
// return a new node (or the original node if there was no
// compression). We can't quite use the TreeWalker for this
// because it's too simplistic.
// The Compressor object is for storing the options and for
// maintaining various internal state that might be useful for
// squeezing nodes.
function Compressor(options, false_by_default) {
options = defaults(options, {
sequences : !false_by_default,
properties : !false_by_default,
dead_code : !false_by_default,
keep_comps : !false_by_default,
drop_debugger : !false_by_default,
unsafe : !false_by_default,
warnings : true
});
var stack = [];
return {
option : function(key) { return options[key] },
push_node : function(node) { stack.push(node) },
pop_node : function() { return stack.pop() },
stack : function() { return stack },
parent : function(n) {
return stack[stack.length - 2 - (n || 0)];
},
warn : function() {
if (options.warnings)
AST_Node.warn.apply(AST_Node, arguments);
}
};
};
(function(){
AST_Node.DEFMETHOD("squeeze", function(){
return this;
});
function make_node(ctor, orig, props) {
if (!props.start) props.start = orig.start;
if (!props.end) props.end = orig.end;
return new ctor(props);
};
function SQUEEZE(nodetype, squeeze) {
nodetype.DEFMETHOD("squeeze", function(compressor){
compressor.push_node(this);
var new_node = squeeze(this, compressor);
compressor.pop_node();
return new_node || this;
});
};
function do_list(array, compressor) {
return MAP(array, function(node){
return node.squeeze(compressor);
});
};
SQUEEZE(AST_Debugger, function(self, compressor){
if (compressor.option("drop_debugger"))
return new AST_EmptyStatement(self);
});
SQUEEZE(AST_LabeledStatement, function(self, compressor){
self = self.clone();
self.statement = self.statement.squeeze(compressor);
return self.label.references.length == 0 ? self.statement : self;
});
SQUEEZE(AST_Statement, function(self, compressor){
self = self.clone();
self.body = self.body.squeeze(compressor);
return self;
});
function tighten_body(statements, compressor) {
statements = do_list(statements, compressor);
statements = eliminate_spurious_blocks(statements);
if (compressor.option("dead_code")) {
statements = eliminate_dead_code(statements, compressor);
}
if (compressor.option("sequences")) {
statements = sequencesize(statements);
}
return statements;
};
function eliminate_spurious_blocks(statements) {
return statements.reduce(function(a, stat){
if (stat.TYPE == "BlockStatement") {
// XXX: no instanceof here because we would catch
// AST_Lambda-s and other blocks too. perhaps we
// should refine the hierarchy.
a.push.apply(a, stat.body);
} else {
a.push(stat);
}
return a;
}, []);
}
function eliminate_dead_code(statements, compressor) {
var has_quit = false;
return statements.reduce(function(a, stat){
if (has_quit) {
if (stat instanceof AST_Defun) {
a.push(stat);
}
else if (compressor.option("warnings")) {
stat.walk(new TreeWalker(function(node){
if (node instanceof AST_Definitions
|| node instanceof AST_Defun) {
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
if (node instanceof AST_Definitions) {
node = node.clone();
node.remove_initializers();
a.push(node);
}
else if (node instanceof AST_Defun) {
a.push(node);
}
return true;
}
if (node instanceof AST_Scope)
return true;
}))
};
} else {
a.push(stat);
if (stat instanceof AST_Jump) {
has_quit = true;
}
}
return a;
}, []);
}
function sequencesize(statements) {
var prev = null, last = statements.length - 1;
if (last) statements = statements.reduce(function(a, cur, i){
if (prev instanceof AST_SimpleStatement
&& cur instanceof AST_SimpleStatement) {
var seq = make_node(AST_Seq, prev, {
first: prev.body,
second: cur.body
});
prev.body = seq;
}
else if (i == last && cur instanceof AST_Exit
&& cur.value && a.length == 1) {
// it only makes sense to do this transformation
// if the AST gets to a single statement.
var seq = make_node(AST_Seq, prev, {
first: prev.body,
second: cur.value
});
cur.value = seq;
return [ cur ];
}
else {
a.push(cur);
prev = cur;
}
return a;
}, []);
return statements;
}
SQUEEZE(AST_BlockStatement, function(self, compressor){
self = self.clone();
self.body = tighten_body(self.body, compressor);
if (self.body.length == 1 && !self.required)
return self.body[0];
return self;
});
SQUEEZE(AST_EmptyStatement, function(self, compressor){
return self;
});
SQUEEZE(AST_DWLoop, function(self, compressor){
self = self.clone();
self.condition = self.condition.squeeze(compressor);
self.body = self.body.squeeze(compressor);
return self;
});
SQUEEZE(AST_For, function(self, compressor){
self = self.clone();
if (self.init) self.init = self.init.squeeze(compressor);
if (self.condition) self.condition = self.condition.squeeze(compressor);
if (self.step) self.step = self.step.squeeze(compressor);
self.body = self.body.squeeze(compressor);
return self;
});
SQUEEZE(AST_ForIn, function(self, compressor){
self = self.clone();
self.init = self.init.squeeze(compressor);
self.object = self.object.squeeze(compressor);
self.body = self.body.squeeze(compressor);
return self;
});
SQUEEZE(AST_With, function(self, compressor){
self = self.clone();
self.expression = self.expression.squeeze(compressor);
self.body = self.body.squeeze(compressor);
});
SQUEEZE(AST_Exit, function(self, compressor){
self = self.clone();
if (self.value) self.value = self.value.squeeze(compressor);
return self;
});
SQUEEZE(AST_LoopControl, function(self, compressor){
self = self.clone();
if (self.label) self.label = self.label.squeeze(compressor);
return self;
});
SQUEEZE(AST_If, function(self, compressor){
self = self.clone();
self.condition = self.condition.squeeze(compressor);
self.consequent = self.consequent.squeeze(compressor);
if (self.alternative)
self.alternative = self.alternative.squeeze(compressor);
return self;
});
SQUEEZE(AST_Switch, function(self, compressor){
self = self.clone();
self.expression = self.expression.squeeze(compressor);
self.body = self.body.squeeze(compressor);
return self;
});
SQUEEZE(AST_Case, function(self, compressor){
self = self.clone();
self.expression = self.expression.squeeze(compressor);
self.body = tighten_body(self.body, compressor);
return self;
});
SQUEEZE(AST_Try, function(self, compressor){
self = self.clone();
self.btry = self.btry.squeeze(compressor);
if (self.bcatch) self.bcatch = self.bcatch.squeeze(compressor);
if (self.bfinally) self.bfinally = self.bfinally.squeeze(compressor);
return self;
});
AST_Definitions.DEFMETHOD("remove_initializers", function(){
this.definitions = this.definitions.map(function(def){
var def = def.clone();
def.value = null;
return def;
});
});
SQUEEZE(AST_Definitions, function(self, compressor){
self = self.clone();
self.definitions = do_list(self.definitions, compressor);
return self;
});
SQUEEZE(AST_VarDef, function(self, compressor){
self = self.clone();
if (self.value) self.value = self.value.squeeze(compressor);
return self;
});
SQUEEZE(AST_Lambda, function(self, compressor){
self = self.clone();
if (self.name) self.name = self.name.squeeze(compressor);
self.argnames = do_list(self.argnames, compressor);
self.body = self.body.squeeze(compressor);
return self;
});
SQUEEZE(AST_Call, function(self, compressor){
self = self.clone();
self.expression = self.expression.squeeze(compressor);
self.args = do_list(self.args, compressor);
return self;
});
SQUEEZE(AST_Seq, function(self, compressor){
self = self.clone();
self.first = self.first.squeeze(compressor);
self.second = self.second.squeeze(compressor);
return self;
});
SQUEEZE(AST_Dot, function(self, compressor){
self = self.clone();
self.expression = self.expression.squeeze(compressor);
return self;
});
SQUEEZE(AST_Sub, function(self, compressor){
self = self.clone();
self.expression = self.expression.squeeze(compressor);
var prop = self.property = self.property.squeeze(compressor);
if (prop instanceof AST_String && compressor.option("properties")) {
prop = prop.getValue();
if (is_identifier(prop)) {
self = new AST_Dot(self);
self.property = prop;
}
}
return self;
});
SQUEEZE(AST_Unary, function(self, compressor){
self = self.clone();
self.expression = self.expression.squeeze(compressor);
return self;
});
SQUEEZE(AST_Binary, function(self, compressor){
self = self.clone();
self.left = self.left.squeeze(compressor);
self.right = self.right.squeeze(compressor);
return self;
});
SQUEEZE(AST_Conditional, function(self, compressor){
self = self.clone();
self.condition = self.condition.squeeze(compressor);
self.consequent = self.consequent.squeeze(compressor);
self.alternative = self.alternative.squeeze(compressor);
return self;
});
SQUEEZE(AST_Array, function(self, compressor){
self = self.clone();
self.elements = do_list(self.elements, compressor);
return self;
});
SQUEEZE(AST_Object, function(self, compressor){
self = self.clone();
self.properties = do_list(self.properties, compressor);
return self;
});
SQUEEZE(AST_ObjectProperty, function(self, compressor){
self = self.clone();
self.value = self.value.squeeze(compressor);
return self;
});
})();