more optimizations that v1 does and some cleanups
- a = a + x ==> a+=x - joining consecutive var statements (hoisting is not always desirable) - x == false ==> x == 0, x != true ==> x != 1 - x, x ==> x; x = exp(), x ==> x = exp() - discarding useless break-s
This commit is contained in:
@@ -18,18 +18,20 @@ Use a single dash to read input from the standard input.\
|
|||||||
.describe("v", "Verbose")
|
.describe("v", "Verbose")
|
||||||
.describe("b", "Beautify output")
|
.describe("b", "Beautify output")
|
||||||
.describe("m", "Don't mangle names")
|
.describe("m", "Don't mangle names")
|
||||||
.describe("c", "Compressor options")
|
.describe("c", "Pass compressor options")
|
||||||
|
|
||||||
.alias("p", "prefix")
|
.alias("p", "prefix")
|
||||||
.alias("o", "output")
|
.alias("o", "output")
|
||||||
.alias("v", "verbose")
|
.alias("v", "verbose")
|
||||||
.alias("b", "beautify")
|
.alias("b", "beautify")
|
||||||
.alias("c", "options")
|
.alias("c", "options")
|
||||||
|
.alias("m", "no-mangle")
|
||||||
|
|
||||||
.boolean("b")
|
.boolean("b")
|
||||||
.boolean("v")
|
.boolean("v")
|
||||||
.boolean("stats")
|
.boolean("stats")
|
||||||
.boolean("m")
|
.boolean("m")
|
||||||
|
.string("c")
|
||||||
|
|
||||||
.argv
|
.argv
|
||||||
;
|
;
|
||||||
@@ -121,9 +123,9 @@ if (ARGS.stats) {
|
|||||||
/* -----[ functions ]----- */
|
/* -----[ functions ]----- */
|
||||||
|
|
||||||
function do_file_1(file) {
|
function do_file_1(file) {
|
||||||
// if (ARGS.v) {
|
if (ARGS.v) {
|
||||||
// sys.error("Compressing " + file);
|
sys.error("Compressing " + file);
|
||||||
// }
|
}
|
||||||
var code = read_whole_file(file);
|
var code = read_whole_file(file);
|
||||||
var ast;
|
var ast;
|
||||||
time_it("parse", function(){
|
time_it("parse", function(){
|
||||||
|
|||||||
51
lib/ast.js
51
lib/ast.js
@@ -70,8 +70,12 @@ function DEFNODE(type, props, methods, base) {
|
|||||||
ctor.prototype.TYPE = ctor.TYPE = type;
|
ctor.prototype.TYPE = ctor.TYPE = type;
|
||||||
}
|
}
|
||||||
if (methods) for (i in methods) if (HOP(methods, i)) {
|
if (methods) for (i in methods) if (HOP(methods, i)) {
|
||||||
|
if (/^\$/.test(i)) {
|
||||||
|
ctor[i.substr(1)] = methods[i];
|
||||||
|
} else {
|
||||||
ctor.prototype[i] = methods[i];
|
ctor.prototype[i] = methods[i];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ctor.DEFMETHOD = function(name, method) {
|
ctor.DEFMETHOD = function(name, method) {
|
||||||
this.prototype[name] = method;
|
this.prototype[name] = method;
|
||||||
};
|
};
|
||||||
@@ -429,12 +433,45 @@ var AST_New = DEFNODE("New", null, {
|
|||||||
$documentation: "An object instantiation. Derives from a function call since it has exactly the same properties."
|
$documentation: "An object instantiation. Derives from a function call since it has exactly the same properties."
|
||||||
}, AST_Call);
|
}, AST_Call);
|
||||||
|
|
||||||
var AST_Seq = DEFNODE("Seq", "first second", {
|
var AST_Seq = DEFNODE("Seq", "car cdr", {
|
||||||
$documentation: "A sequence expression (two comma-separated expressions)",
|
$documentation: "A sequence expression (two comma-separated expressions)",
|
||||||
|
$cons: function(x, y) {
|
||||||
|
var seq = new AST_Seq(x);
|
||||||
|
seq.car = x;
|
||||||
|
seq.cdr = y;
|
||||||
|
return seq;
|
||||||
|
},
|
||||||
|
$from_array: function(array) {
|
||||||
|
if (array.length == 0) return null;
|
||||||
|
if (array.length == 1) return array[0].clone();
|
||||||
|
var list = null;
|
||||||
|
for (var i = array.length; --i >= 0;) {
|
||||||
|
list = AST_Seq.cons(array[i], list);
|
||||||
|
}
|
||||||
|
var p = list;
|
||||||
|
while (p) {
|
||||||
|
if (p.cdr && !p.cdr.cdr) {
|
||||||
|
p.cdr = p.cdr.car;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p = p.cdr;
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
add: function(node) {
|
||||||
|
var p = this;
|
||||||
|
while (p) {
|
||||||
|
if (!(p.cdr instanceof AST_Seq)) {
|
||||||
|
var cell = AST_Seq.cons(p.cdr, node);
|
||||||
|
return p.cdr = cell;
|
||||||
|
}
|
||||||
|
p = p.cdr;
|
||||||
|
}
|
||||||
|
},
|
||||||
_walk: function(visitor) {
|
_walk: function(visitor) {
|
||||||
return visitor._visit(this, function(){
|
return visitor._visit(this, function(){
|
||||||
this.first._walk(visitor);
|
this.car._walk(visitor);
|
||||||
this.second._walk(visitor);
|
if (this.cdr) this.cdr._walk(visitor);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -629,15 +666,19 @@ var AST_Undefined = DEFNODE("Undefined", null, {
|
|||||||
value: (function(){}())
|
value: (function(){}())
|
||||||
}, AST_Atom);
|
}, AST_Atom);
|
||||||
|
|
||||||
|
var AST_Boolean = DEFNODE("Boolean", null, {
|
||||||
|
$documentation: "Base class for booleans",
|
||||||
|
}, AST_Atom);
|
||||||
|
|
||||||
var AST_False = DEFNODE("False", null, {
|
var AST_False = DEFNODE("False", null, {
|
||||||
$documentation: "The `false` atom",
|
$documentation: "The `false` atom",
|
||||||
value: false
|
value: false
|
||||||
}, AST_Atom);
|
}, AST_Boolean);
|
||||||
|
|
||||||
var AST_True = DEFNODE("True", null, {
|
var AST_True = DEFNODE("True", null, {
|
||||||
$documentation: "The `true` atom",
|
$documentation: "The `true` atom",
|
||||||
value: true
|
value: true
|
||||||
}, AST_Atom);
|
}, AST_Boolean);
|
||||||
|
|
||||||
/* -----[ TreeWalker ]----- */
|
/* -----[ TreeWalker ]----- */
|
||||||
|
|
||||||
|
|||||||
243
lib/compress.js
243
lib/compress.js
@@ -68,6 +68,8 @@ function Compressor(options, false_by_default) {
|
|||||||
hoist_funs : !false_by_default,
|
hoist_funs : !false_by_default,
|
||||||
hoist_vars : !false_by_default,
|
hoist_vars : !false_by_default,
|
||||||
if_return : !false_by_default,
|
if_return : !false_by_default,
|
||||||
|
join_vars : !false_by_default,
|
||||||
|
cascade : !false_by_default,
|
||||||
|
|
||||||
warnings : true
|
warnings : true
|
||||||
});
|
});
|
||||||
@@ -116,6 +118,11 @@ function Compressor(options, false_by_default) {
|
|||||||
return this;
|
return this;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AST_Node.DEFMETHOD("equivalent_to", function(node){
|
||||||
|
// XXX: this is a rather expensive way to test two node's equivalence:
|
||||||
|
return this.print_to_string() == node.print_to_string();
|
||||||
|
});
|
||||||
|
|
||||||
function make_node(ctor, orig, props) {
|
function make_node(ctor, orig, props) {
|
||||||
if (!props) props = {};
|
if (!props) props = {};
|
||||||
if (!props.start) props.start = orig.start;
|
if (!props.start) props.start = orig.start;
|
||||||
@@ -171,6 +178,9 @@ function Compressor(options, false_by_default) {
|
|||||||
if (compressor.option("if_return")) {
|
if (compressor.option("if_return")) {
|
||||||
statements = handle_if_return(statements, compressor);
|
statements = handle_if_return(statements, compressor);
|
||||||
}
|
}
|
||||||
|
if (compressor.option("join_vars")) {
|
||||||
|
statements = join_consecutive_vars(statements, compressor);
|
||||||
|
}
|
||||||
} while (CHANGED);
|
} while (CHANGED);
|
||||||
return statements;
|
return statements;
|
||||||
|
|
||||||
@@ -210,11 +220,11 @@ function Compressor(options, false_by_default) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stat instanceof AST_If
|
if (stat instanceof AST_If
|
||||||
&& stat.body instanceof AST_Return
|
&& stat.body instanceof AST_Exit
|
||||||
&& stat.body.value
|
&& stat.body.value
|
||||||
&& !stat.alternative
|
&& !stat.alternative
|
||||||
&& i < last
|
&& i < last
|
||||||
&& statements[i + 1] instanceof AST_Return
|
&& statements[i + 1].TYPE == stat.body.TYPE
|
||||||
&& statements[i + 1].value) {
|
&& statements[i + 1].value) {
|
||||||
CHANGED = true;
|
CHANGED = true;
|
||||||
return MAP.last(make_node(AST_If, stat, {
|
return MAP.last(make_node(AST_If, stat, {
|
||||||
@@ -223,6 +233,27 @@ function Compressor(options, false_by_default) {
|
|||||||
alternative: statements[i + 1]
|
alternative: statements[i + 1]
|
||||||
}).optimize(compressor));
|
}).optimize(compressor));
|
||||||
}
|
}
|
||||||
|
if (stat instanceof AST_If
|
||||||
|
&& stat.alternative instanceof AST_Exit) {
|
||||||
|
CHANGED = true;
|
||||||
|
return MAP.last(MAP.splice([
|
||||||
|
// 1. negate IF and put the exit in the consequent
|
||||||
|
make_node(AST_If, stat, {
|
||||||
|
condition: stat.condition.negate(compressor),
|
||||||
|
body: stat.alternative
|
||||||
|
}),
|
||||||
|
// 2. the IF body can now go outside the if
|
||||||
|
stat.body
|
||||||
|
// and 3. add the rest of the statements
|
||||||
|
].concat(statements.slice(i + 1))));
|
||||||
|
return MAP.last(make_node(AST_If, stat, {
|
||||||
|
condition: stat.condition.negate(compressor),
|
||||||
|
body: stat.alternative,
|
||||||
|
alternative: make_node(AST_BlockStatement, stat.body, {
|
||||||
|
body: [ stat.body ].concat(statements.slice(i + 1))
|
||||||
|
})
|
||||||
|
}).optimize(compressor));
|
||||||
|
}
|
||||||
return stat;
|
return stat;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -242,46 +273,63 @@ function Compressor(options, false_by_default) {
|
|||||||
}, []);
|
}, []);
|
||||||
};
|
};
|
||||||
|
|
||||||
// XXX: this is destructive -- it modifies tree nodes.
|
function sequencesize(statements, compressor) {
|
||||||
function sequencesize(statements) {
|
if (statements.length < 2) return statements;
|
||||||
var prev = null, last = statements.length - 1;
|
var seq = [], ret = [];
|
||||||
if (last) statements = statements.reduce(function(a, cur, i){
|
function push_seq() {
|
||||||
|
seq = AST_Seq.from_array(seq);
|
||||||
|
if (seq) ret.push(make_node(AST_SimpleStatement, seq, {
|
||||||
|
body: seq
|
||||||
|
}));
|
||||||
|
seq = [];
|
||||||
|
};
|
||||||
|
statements.forEach(function(stat){
|
||||||
|
if (stat instanceof AST_SimpleStatement) seq.push(stat.body);
|
||||||
|
else push_seq(), ret.push(stat);
|
||||||
|
});
|
||||||
|
push_seq();
|
||||||
|
|
||||||
|
// if the last node is return or throw, we can mix in the
|
||||||
|
// previous sequence which might help reducing the list to
|
||||||
|
// a single statement.
|
||||||
|
var exit = ret[ret.length - 1], prev = ret[ret.length - 2];
|
||||||
if (prev instanceof AST_SimpleStatement
|
if (prev instanceof AST_SimpleStatement
|
||||||
&& cur instanceof AST_SimpleStatement) {
|
&& exit instanceof AST_Exit && exit.value) {
|
||||||
CHANGED = true;
|
ret.pop();
|
||||||
var seq = make_node(AST_Seq, prev, {
|
ret.pop();
|
||||||
first: prev.body,
|
if (prev.body instanceof AST_Seq) {
|
||||||
second: cur.body
|
prev.body.add(exit.value);
|
||||||
});
|
prev.body = prev.body.optimize(compressor);
|
||||||
prev.body = seq;
|
exit.value = prev.body;
|
||||||
}
|
|
||||||
else if (i == last
|
|
||||||
&& cur instanceof AST_Exit && cur.value
|
|
||||||
&& a.length > 0
|
|
||||||
&& prev instanceof AST_SimpleStatement) {
|
|
||||||
CHANGED = true;
|
|
||||||
var seq = make_node(AST_Seq, prev, {
|
|
||||||
first: prev.body,
|
|
||||||
second: cur.value
|
|
||||||
});
|
|
||||||
cur.value = seq;
|
|
||||||
a.pop();
|
|
||||||
a.push(cur);
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
a.push(cur);
|
exit.value = AST_Seq.cons(prev.body, exit.value).optimize(compressor);
|
||||||
prev = cur;
|
}
|
||||||
|
ret.push(exit);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHANGED = ret.length != statements.length;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
function join_consecutive_vars(statements, compressor) {
|
||||||
|
var prev = null;
|
||||||
|
return statements.reduce(function(a, stat){
|
||||||
|
if (stat instanceof AST_Definitions && prev && prev.TYPE == stat.TYPE) {
|
||||||
|
prev.definitions = prev.definitions.concat(stat.definitions);
|
||||||
|
CHANGED = true;
|
||||||
|
} else {
|
||||||
|
prev = stat;
|
||||||
|
a.push(stat);
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
}, []);
|
}, []);
|
||||||
return statements;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function extract_declarations_from_unreachable_code(compressor, stat, target) {
|
function extract_declarations_from_unreachable_code(compressor, stat, target) {
|
||||||
warn_dead_code(stat);
|
compressor.warn("Dropping unreachable code [{line},{col}]", stat.start);
|
||||||
stat.walk(new TreeWalker(function(node){
|
stat.walk(new TreeWalker(function(node){
|
||||||
if (node instanceof AST_Definitions) {
|
if (node instanceof AST_Definitions) {
|
||||||
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
|
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
|
||||||
@@ -322,7 +370,7 @@ function Compressor(options, false_by_default) {
|
|||||||
return this.operator == "=" && this.right.is_boolean();
|
return this.operator == "=" && this.right.is_boolean();
|
||||||
});
|
});
|
||||||
def(AST_Seq, function(){
|
def(AST_Seq, function(){
|
||||||
return this.second.is_boolean();
|
return this.cdr.is_boolean();
|
||||||
});
|
});
|
||||||
def(AST_True, function(){ return true });
|
def(AST_True, function(){ return true });
|
||||||
def(AST_False, function(){ return true });
|
def(AST_False, function(){ return true });
|
||||||
@@ -481,7 +529,7 @@ function Compressor(options, false_by_default) {
|
|||||||
});
|
});
|
||||||
def(AST_Seq, function(compressor){
|
def(AST_Seq, function(compressor){
|
||||||
var self = this.clone();
|
var self = this.clone();
|
||||||
self.second = self.second.negate(compressor);
|
self.cdr = self.cdr.negate(compressor);
|
||||||
return self;
|
return self;
|
||||||
});
|
});
|
||||||
def(AST_Conditional, function(compressor){
|
def(AST_Conditional, function(compressor){
|
||||||
@@ -550,6 +598,21 @@ function Compressor(options, false_by_default) {
|
|||||||
|| this.operator == "--";
|
|| this.operator == "--";
|
||||||
});
|
});
|
||||||
def(AST_SymbolRef, function(){ return false });
|
def(AST_SymbolRef, function(){ return false });
|
||||||
|
def(AST_Object, function(){
|
||||||
|
for (var i = this.properties.length; --i >= 0;)
|
||||||
|
if (this.properties[i].has_side_effects())
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
def(AST_ObjectProperty, function(){
|
||||||
|
return this.value.has_side_effects();
|
||||||
|
});
|
||||||
|
def(AST_Array, function(){
|
||||||
|
for (var i = this.elements.length; --i >= 0;)
|
||||||
|
if (this.elements[i].has_side_effects())
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
})(function(node, func){
|
})(function(node, func){
|
||||||
node.DEFMETHOD("has_side_effects", func);
|
node.DEFMETHOD("has_side_effects", func);
|
||||||
});
|
});
|
||||||
@@ -634,7 +697,7 @@ function Compressor(options, false_by_default) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.walk(tw);
|
self.walk(tw);
|
||||||
if (vars_found > 0 && vardecl.length > 1) {
|
if (vars_found > 0 && vardecl.length > 0) {
|
||||||
vardecl.forEach(function(v){ v.hoisted = true });
|
vardecl.forEach(function(v){ v.hoisted = true });
|
||||||
var node = make_node(AST_Var, self, {
|
var node = make_node(AST_Var, self, {
|
||||||
definitions: Object.keys(vars).map(function(name){
|
definitions: Object.keys(vars).map(function(name){
|
||||||
@@ -657,7 +720,7 @@ function Compressor(options, false_by_default) {
|
|||||||
|
|
||||||
AST_SimpleStatement.DEFMETHOD("optimize", function(compressor){
|
AST_SimpleStatement.DEFMETHOD("optimize", function(compressor){
|
||||||
if (!this.body.has_side_effects()) {
|
if (!this.body.has_side_effects()) {
|
||||||
AST_Node.warn("Dropping side-effect-free statement [{line},{col}]", this.start);
|
compressor.warn("Dropping side-effect-free statement [{line},{col}]", this.start);
|
||||||
return make_node(AST_EmptyStatement, this);
|
return make_node(AST_EmptyStatement, this);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
@@ -674,10 +737,6 @@ function Compressor(options, false_by_default) {
|
|||||||
return self.optimize(compressor);
|
return self.optimize(compressor);
|
||||||
});
|
});
|
||||||
|
|
||||||
function warn_dead_code(node) {
|
|
||||||
AST_Node.warn("Dropping unreachable code [{line},{col}]", node.start);
|
|
||||||
};
|
|
||||||
|
|
||||||
AST_DWLoop.DEFMETHOD("optimize", function(compressor){
|
AST_DWLoop.DEFMETHOD("optimize", function(compressor){
|
||||||
var self = this;
|
var self = this;
|
||||||
var cond = self.condition.evaluate(compressor);
|
var cond = self.condition.evaluate(compressor);
|
||||||
@@ -798,7 +857,7 @@ function Compressor(options, false_by_default) {
|
|||||||
self.condition = cond[0];
|
self.condition = cond[0];
|
||||||
if (cond.length == 2) {
|
if (cond.length == 2) {
|
||||||
if (cond[1]) {
|
if (cond[1]) {
|
||||||
AST_Node.warn("Condition always true [{line},{col}]", self.condition.start);
|
compressor.warn("Condition always true [{line},{col}]", self.condition.start);
|
||||||
if (compressor.option("dead_code")) {
|
if (compressor.option("dead_code")) {
|
||||||
var a = [];
|
var a = [];
|
||||||
if (self.alternative) {
|
if (self.alternative) {
|
||||||
@@ -808,7 +867,7 @@ function Compressor(options, false_by_default) {
|
|||||||
return make_node(AST_BlockStatement, self, { body: a });
|
return make_node(AST_BlockStatement, self, { body: a });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AST_Node.warn("Condition always false [{line},{col}]", self.condition.start);
|
compressor.warn("Condition always false [{line},{col}]", self.condition.start);
|
||||||
if (compressor.option("dead_code")) {
|
if (compressor.option("dead_code")) {
|
||||||
var a = [];
|
var a = [];
|
||||||
extract_declarations_from_unreachable_code(compressor, self.body, a);
|
extract_declarations_from_unreachable_code(compressor, self.body, a);
|
||||||
@@ -909,7 +968,17 @@ function Compressor(options, false_by_default) {
|
|||||||
self = self.clone();
|
self = self.clone();
|
||||||
self.expression = self.expression.squeeze(compressor);
|
self.expression = self.expression.squeeze(compressor);
|
||||||
self.body = self.body.squeeze(compressor);
|
self.body = self.body.squeeze(compressor);
|
||||||
return self;
|
return self.optimize(compressor);
|
||||||
|
});
|
||||||
|
|
||||||
|
AST_Switch.DEFMETHOD("optimize", function(compressor){
|
||||||
|
var last_branch = this.body.body[this.body.body.length - 1];
|
||||||
|
if (last_branch) {
|
||||||
|
var stat = last_branch.body[last_branch.body.length - 1]; // last statement
|
||||||
|
if (stat instanceof AST_Break && !stat.label)
|
||||||
|
last_branch.body.pop();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
});
|
});
|
||||||
|
|
||||||
SQUEEZE(AST_Case, function(self, compressor){
|
SQUEEZE(AST_Case, function(self, compressor){
|
||||||
@@ -952,8 +1021,8 @@ function Compressor(options, false_by_default) {
|
|||||||
var first = list[0];
|
var first = list[0];
|
||||||
if (list.length == 1) return first;
|
if (list.length == 1) return first;
|
||||||
return make_node(AST_Seq, first, {
|
return make_node(AST_Seq, first, {
|
||||||
first: first,
|
car: first,
|
||||||
second: seq(list.slice(1))
|
cdr: seq(list.slice(1))
|
||||||
});
|
});
|
||||||
})(assignments);
|
})(assignments);
|
||||||
});
|
});
|
||||||
@@ -1007,7 +1076,7 @@ function Compressor(options, false_by_default) {
|
|||||||
if (compressor.option("unused_func")) {
|
if (compressor.option("unused_func")) {
|
||||||
if (this.name.unreferenced()
|
if (this.name.unreferenced()
|
||||||
&& !(this.parent_scope instanceof AST_Toplevel)) {
|
&& !(this.parent_scope instanceof AST_Toplevel)) {
|
||||||
AST_Node.warn("Dropping unused function {name} [{line},{col}]", {
|
compressor.warn("Dropping unused function {name} [{line},{col}]", {
|
||||||
name: this.name.name,
|
name: this.name.name,
|
||||||
line: this.start.line,
|
line: this.start.line,
|
||||||
col: this.start.col
|
col: this.start.col
|
||||||
@@ -1077,12 +1146,32 @@ function Compressor(options, false_by_default) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
});
|
});
|
||||||
|
|
||||||
SQUEEZE(AST_Seq, function(self, compressor){
|
SQUEEZE(AST_Seq, function(self, compressor){
|
||||||
self = self.clone();
|
self = self.clone();
|
||||||
self.first = self.first.squeeze(compressor);
|
self.car = self.car.squeeze(compressor);
|
||||||
self.second = self.second.squeeze(compressor);
|
self.cdr = self.cdr.squeeze(compressor);
|
||||||
|
return self.optimize(compressor);
|
||||||
|
});
|
||||||
|
|
||||||
|
AST_Seq.DEFMETHOD("optimize", function(compressor){
|
||||||
|
var self = this;
|
||||||
|
if (self.cdr instanceof AST_Seq)
|
||||||
|
self.cdr = self.cdr.optimize(compressor);
|
||||||
|
if (compressor.option("cascade")) {
|
||||||
|
if (self.car instanceof AST_Assign
|
||||||
|
&& !self.car.left.has_side_effects()
|
||||||
|
&& self.car.left.equivalent_to(self.cdr)) {
|
||||||
|
return self.car;
|
||||||
|
}
|
||||||
|
if (!self.car.has_side_effects()
|
||||||
|
&& !self.cdr.has_side_effects()
|
||||||
|
&& self.car.equivalent_to(self.cdr)) {
|
||||||
|
return self.car;
|
||||||
|
}
|
||||||
|
}
|
||||||
return self;
|
return self;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1131,7 +1220,7 @@ function Compressor(options, false_by_default) {
|
|||||||
case "typeof":
|
case "typeof":
|
||||||
// typeof always returns a non-empty string, thus it's
|
// typeof always returns a non-empty string, thus it's
|
||||||
// always true in booleans
|
// always true in booleans
|
||||||
AST_Node.warn("Boolean expression always true [{line},{col}]", this.start);
|
compressor.warn("Boolean expression always true [{line},{col}]", this.start);
|
||||||
return make_node(AST_True, this).optimize(compressor);
|
return make_node(AST_True, this).optimize(compressor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1160,7 +1249,7 @@ function Compressor(options, false_by_default) {
|
|||||||
var ll = this.left.evaluate(compressor), left = ll[0];
|
var ll = this.left.evaluate(compressor), left = ll[0];
|
||||||
var rr = this.right.evaluate(compressor), right = rr[0];
|
var rr = this.right.evaluate(compressor), right = rr[0];
|
||||||
if ((ll.length == 2 && !ll[1]) || (rr.length == 2 && !rr[1])) {
|
if ((ll.length == 2 && !ll[1]) || (rr.length == 2 && !rr[1])) {
|
||||||
AST_Node.warn("Boolean && always false [{line},{col}]", this.start);
|
compressor.warn("Boolean && always false [{line},{col}]", this.start);
|
||||||
return make_node(AST_False, this).optimize(compressor);
|
return make_node(AST_False, this).optimize(compressor);
|
||||||
}
|
}
|
||||||
if (ll.length == 2 && ll[1]) {
|
if (ll.length == 2 && ll[1]) {
|
||||||
@@ -1174,7 +1263,7 @@ function Compressor(options, false_by_default) {
|
|||||||
var ll = this.left.evaluate(compressor), left = ll[0];
|
var ll = this.left.evaluate(compressor), left = ll[0];
|
||||||
var rr = this.right.evaluate(compressor), right = rr[0];
|
var rr = this.right.evaluate(compressor), right = rr[0];
|
||||||
if ((ll.length == 2 && ll[1]) || (rr.length == 2 && rr[1])) {
|
if ((ll.length == 2 && ll[1]) || (rr.length == 2 && rr[1])) {
|
||||||
AST_Node.warn("Boolean || always true [{line},{col}]", this.start);
|
compressor.warn("Boolean || always true [{line},{col}]", this.start);
|
||||||
return make_node(AST_True, this).optimize(compressor);
|
return make_node(AST_True, this).optimize(compressor);
|
||||||
}
|
}
|
||||||
if (ll.length == 2 && !ll[1]) {
|
if (ll.length == 2 && !ll[1]) {
|
||||||
@@ -1189,7 +1278,7 @@ function Compressor(options, false_by_default) {
|
|||||||
var rr = this.right.evaluate(compressor), right = rr[0];
|
var rr = this.right.evaluate(compressor), right = rr[0];
|
||||||
if ((ll.length == 2 && ll[0] instanceof AST_String && ll[1]) ||
|
if ((ll.length == 2 && ll[0] instanceof AST_String && ll[1]) ||
|
||||||
(rr.length == 2 && rr[0] instanceof AST_String && rr[1])) {
|
(rr.length == 2 && rr[0] instanceof AST_String && rr[1])) {
|
||||||
AST_Node.warn("+ in boolean context always true [{line},{col}]", this.start);
|
compressor.warn("+ in boolean context always true [{line},{col}]", this.start);
|
||||||
return make_node(AST_True, this).optimize(compressor);
|
return make_node(AST_True, this).optimize(compressor);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1207,7 +1296,34 @@ function Compressor(options, false_by_default) {
|
|||||||
self.left = self.right;
|
self.left = self.right;
|
||||||
self.right = tmp;
|
self.right = tmp;
|
||||||
};
|
};
|
||||||
if (self instanceof AST_Binary) switch (self.operator) {
|
switch (self.operator) {
|
||||||
|
case "==":
|
||||||
|
case "!=":
|
||||||
|
var ll = self.left.evaluate(compressor);
|
||||||
|
var rr = self.right.evaluate(compressor);
|
||||||
|
if (ll.length == 2 && typeof ll[1] == "boolean") {
|
||||||
|
compressor.warn("Non-strict equality against boolean: {operator} {value} [{line},{col}]", {
|
||||||
|
operator : self.operator,
|
||||||
|
value : ll[1],
|
||||||
|
line : self.start.line,
|
||||||
|
col : self.start.col
|
||||||
|
});
|
||||||
|
self.left = make_node(AST_Number, self.left, {
|
||||||
|
value: +ll[1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (rr.length == 2 && typeof rr[1] == "boolean") {
|
||||||
|
compressor.warn("Non-strict equality against boolean {operator} {value} [{line},{col}]", {
|
||||||
|
operator : self.operator,
|
||||||
|
value : rr[1],
|
||||||
|
line : self.start.line,
|
||||||
|
col : self.start.col
|
||||||
|
});
|
||||||
|
self.right = make_node(AST_Number, self.right, {
|
||||||
|
value: +rr[1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "<": reverse(">"); break;
|
case "<": reverse(">"); break;
|
||||||
case "<=": reverse(">="); break;
|
case "<=": reverse(">="); break;
|
||||||
}
|
}
|
||||||
@@ -1219,7 +1335,21 @@ function Compressor(options, false_by_default) {
|
|||||||
self = self.clone();
|
self = self.clone();
|
||||||
self.left = self.left.squeeze(compressor);
|
self.left = self.left.squeeze(compressor);
|
||||||
self.right = self.right.squeeze(compressor);
|
self.right = self.right.squeeze(compressor);
|
||||||
return self;
|
return self.optimize(compressor);
|
||||||
|
});
|
||||||
|
|
||||||
|
var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
|
||||||
|
AST_Assign.DEFMETHOD("optimize", function(compressor){
|
||||||
|
if (this.operator == "="
|
||||||
|
&& this.left instanceof AST_SymbolRef
|
||||||
|
&& this.right instanceof AST_Binary
|
||||||
|
&& this.right.left instanceof AST_SymbolRef
|
||||||
|
&& this.right.left.name == this.left.name
|
||||||
|
&& member(this.right.operator, ASSIGN_OPS)) {
|
||||||
|
this.operator = this.right.operator + "=";
|
||||||
|
this.right = this.right.right;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
});
|
});
|
||||||
|
|
||||||
SQUEEZE(AST_Conditional, function(self, compressor){
|
SQUEEZE(AST_Conditional, function(self, compressor){
|
||||||
@@ -1236,10 +1366,10 @@ function Compressor(options, false_by_default) {
|
|||||||
var cond = self.condition.evaluate(compressor);
|
var cond = self.condition.evaluate(compressor);
|
||||||
if (cond.length == 2) {
|
if (cond.length == 2) {
|
||||||
if (cond[1]) {
|
if (cond[1]) {
|
||||||
AST_Node.warn("Condition always true [{line},{col}]", self.start);
|
compressor.warn("Condition always true [{line},{col}]", self.start);
|
||||||
return self.consequent;
|
return self.consequent;
|
||||||
} else {
|
} else {
|
||||||
AST_Node.warn("Condition always false [{line},{col}]", self.start);
|
compressor.warn("Condition always false [{line},{col}]", self.start);
|
||||||
return self.alternative;
|
return self.alternative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1256,8 +1386,7 @@ function Compressor(options, false_by_default) {
|
|||||||
if (consequent instanceof AST_Assign
|
if (consequent instanceof AST_Assign
|
||||||
&& alternative instanceof AST_Assign
|
&& alternative instanceof AST_Assign
|
||||||
&& consequent.operator == alternative.operator
|
&& consequent.operator == alternative.operator
|
||||||
// XXX: this is a rather expensive way to test two node's equivalence:
|
&& consequent.left.equivalent_to(alternative.left)
|
||||||
&& consequent.left.print_to_string() == alternative.left.print_to_string()
|
|
||||||
) {
|
) {
|
||||||
/*
|
/*
|
||||||
* Stuff like this:
|
* Stuff like this:
|
||||||
|
|||||||
@@ -779,9 +779,11 @@ function OutputStream(options) {
|
|||||||
AST_Call.prototype.print.call(self, output);
|
AST_Call.prototype.print.call(self, output);
|
||||||
});
|
});
|
||||||
DEFPRINT(AST_Seq, function(self, output){
|
DEFPRINT(AST_Seq, function(self, output){
|
||||||
self.first.print(output);
|
self.car.print(output);
|
||||||
|
if (self.cdr) {
|
||||||
output.comma();
|
output.comma();
|
||||||
self.second.print(output);
|
self.cdr.print(output);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
DEFPRINT(AST_Dot, function(self, output){
|
DEFPRINT(AST_Dot, function(self, output){
|
||||||
var expr = self.expression;
|
var expr = self.expression;
|
||||||
@@ -921,13 +923,12 @@ function OutputStream(options) {
|
|||||||
while (i > 0) {
|
while (i > 0) {
|
||||||
if (p instanceof AST_Statement && p.body === node)
|
if (p instanceof AST_Statement && p.body === node)
|
||||||
return true;
|
return true;
|
||||||
if ((p instanceof AST_Seq && p.first === node ) ||
|
if ((p instanceof AST_Seq && p.car === node ) ||
|
||||||
(p instanceof AST_Call && p.expression === node ) ||
|
(p instanceof AST_Call && p.expression === node ) ||
|
||||||
(p instanceof AST_Dot && p.expression === node ) ||
|
(p instanceof AST_Dot && p.expression === node ) ||
|
||||||
(p instanceof AST_Sub && p.expression === node ) ||
|
(p instanceof AST_Sub && p.expression === node ) ||
|
||||||
(p instanceof AST_Conditional && p.condition === node ) ||
|
(p instanceof AST_Conditional && p.condition === node ) ||
|
||||||
(p instanceof AST_Binary && p.first === node ) ||
|
(p instanceof AST_Binary && p.left === node ) ||
|
||||||
(p instanceof AST_Assign && p.first === node ) ||
|
|
||||||
(p instanceof AST_UnaryPostfix && p.expression === node ))
|
(p instanceof AST_UnaryPostfix && p.expression === node ))
|
||||||
{
|
{
|
||||||
node = p;
|
node = p;
|
||||||
|
|||||||
@@ -1455,8 +1455,8 @@ function parse($TEXT, exigent_mode) {
|
|||||||
next();
|
next();
|
||||||
return new AST_Seq({
|
return new AST_Seq({
|
||||||
start : start,
|
start : start,
|
||||||
first : expr,
|
car : expr,
|
||||||
second : expression(true, no_in),
|
cdr : expression(true, no_in),
|
||||||
end : peek()
|
end : peek()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
10
tmp/todo
10
tmp/todo
@@ -1,11 +1,11 @@
|
|||||||
a = a + x ==> a+=x
|
✓ a = a + x ==> a+=x
|
||||||
|
|
||||||
*******
|
*******
|
||||||
|
|
||||||
join consecutive var statements
|
✓ join consecutive var statements
|
||||||
|
|
||||||
*******
|
*******
|
||||||
|
✓
|
||||||
x == false ==> x == 0
|
x == false ==> x == 0
|
||||||
x == true ==> x == 1
|
x == true ==> x == 1
|
||||||
|
|
||||||
@@ -14,6 +14,8 @@ JS is so sloppy that this could be an indication of a bug.
|
|||||||
|
|
||||||
*******
|
*******
|
||||||
|
|
||||||
|
✓
|
||||||
|
|
||||||
x, x ==> x
|
x, x ==> x
|
||||||
x = foo, x ==> x
|
x = foo, x ==> x
|
||||||
|
|
||||||
@@ -30,6 +32,8 @@ XXX? Not sure if this is worth the trouble.
|
|||||||
|
|
||||||
*******
|
*******
|
||||||
|
|
||||||
|
✓
|
||||||
|
|
||||||
discard spurious break statements
|
discard spurious break statements
|
||||||
|
|
||||||
*******
|
*******
|
||||||
|
|||||||
Reference in New Issue
Block a user