// 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; }); })();