Skip to content

Commit

Permalink
compress numerical expressions (mishoo#1513)
Browse files Browse the repository at this point in the history
safe operations
- `a === b` => `a == b`
- `a + -b`  => `a - b`
- `-a + b`  => `b - a`
- `a+ +b`   => `+b+a`

associative operations
(bit-wise operations are safe, otherwise `unsafe_math`)
- `a + (b + c)`       => `(a + b) + c`
- `(n + 2) + 3`       => `5 + n`
- `(2 * n) * 3`       => `6 * n`
- `(a | 1) | (2 | d)` => `(3 | a) | b`

fixes mishoo#412
  • Loading branch information
alexlamsl authored Mar 3, 2017
1 parent b5e0e8c commit 18059cc
Show file tree
Hide file tree
Showing 3 changed files with 303 additions and 7 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
comparison are switching. Compression only works if both `comparisons` and
`unsafe_comps` are both set to true.

- `unsafe_math` (default: false) -- optimize numerical expressions like
`2 * x * 3` into `6 * x`, which may give imprecise floating point results.

- `unsafe_proto` (default: false) -- optimize expressions like
`Array.prototype.slice.call(a)` into `[].slice.call(a)`

Expand Down
171 changes: 164 additions & 7 deletions lib/compress.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function Compressor(options, false_by_default) {
drop_debugger : !false_by_default,
unsafe : false,
unsafe_comps : false,
unsafe_math : false,
unsafe_proto : false,
conditionals : !false_by_default,
comparisons : !false_by_default,
Expand Down Expand Up @@ -1043,6 +1044,34 @@ merge(Compressor.prototype, {
node.DEFMETHOD("is_boolean", func);
});

// methods to determine if an expression has a numeric result type
(function (def){
def(AST_Node, return_false);
def(AST_Number, return_true);
var unary = makePredicate("+ - ~ ++ --");
def(AST_Unary, function(){
return unary(this.operator);
});
var binary = makePredicate("- * / % & | ^ << >> >>>");
def(AST_Binary, function(compressor){
return binary(this.operator) || this.operator == "+"
&& this.left.is_number(compressor)
&& this.right.is_number(compressor);
});
var assign = makePredicate("-= *= /= %= &= |= ^= <<= >>= >>>=");
def(AST_Assign, function(compressor){
return assign(this.operator) || this.right.is_number(compressor);
});
def(AST_Seq, function(compressor){
return this.cdr.is_number(compressor);
});
def(AST_Conditional, function(compressor){
return this.consequent.is_number(compressor) && this.alternative.is_number(compressor);
});
})(function(node, func){
node.DEFMETHOD("is_number", func);
});

// methods to determine if an expression has a string result type
(function (def){
def(AST_Node, return_false);
Expand Down Expand Up @@ -2867,8 +2896,14 @@ merge(Compressor.prototype, {
right: rhs[0]
}).optimize(compressor);
}
function reverse(op, force) {
if (force || !(self.left.has_side_effects(compressor) || self.right.has_side_effects(compressor))) {
function reversible() {
return self.left instanceof AST_Constant
|| self.right instanceof AST_Constant
|| !self.left.has_side_effects(compressor)
&& !self.right.has_side_effects(compressor);
}
function reverse(op) {
if (reversible()) {
if (op) self.operator = op;
var tmp = self.left;
self.left = self.right;
Expand All @@ -2884,7 +2919,7 @@ merge(Compressor.prototype, {

if (!(self.left instanceof AST_Binary
&& PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
reverse(null, true);
reverse();
}
}
if (/^[!=]==?$/.test(self.operator)) {
Expand Down Expand Up @@ -2919,6 +2954,7 @@ merge(Compressor.prototype, {
case "===":
case "!==":
if ((self.left.is_string(compressor) && self.right.is_string(compressor)) ||
(self.left.is_number(compressor) && self.right.is_number(compressor)) ||
(self.left.is_boolean() && self.right.is_boolean())) {
self.operator = self.operator.substr(0, 2);
}
Expand Down Expand Up @@ -3056,22 +3092,26 @@ merge(Compressor.prototype, {
}
break;
}
if (self.operator == "+") {
var associative = true;
switch (self.operator) {
case "+":
// "foo" + ("bar" + x) => "foobar" + x
if (self.left instanceof AST_Constant
&& self.right instanceof AST_Binary
&& self.right.operator == "+"
&& self.right.left instanceof AST_Constant
&& self.right.is_string(compressor)) {
self = make_node(AST_Binary, self, {
operator: "+",
left: make_node(AST_String, null, {
left: make_node(AST_String, self.left, {
value: "" + self.left.getValue() + self.right.left.getValue(),
start: self.left.start,
end: self.right.left.end
}),
right: self.right.right
});
}
// (x + "foo") + "bar" => x + "foobar"
if (self.right instanceof AST_Constant
&& self.left instanceof AST_Binary
&& self.left.operator == "+"
Expand All @@ -3080,13 +3120,14 @@ merge(Compressor.prototype, {
self = make_node(AST_Binary, self, {
operator: "+",
left: self.left.left,
right: make_node(AST_String, null, {
right: make_node(AST_String, self.right, {
value: "" + self.left.right.getValue() + self.right.getValue(),
start: self.left.right.start,
end: self.right.end
})
});
}
// (x + "foo") + ("bar" + y) => (x + "foobar") + y
if (self.left instanceof AST_Binary
&& self.left.operator == "+"
&& self.left.is_string(compressor)
Expand All @@ -3100,7 +3141,7 @@ merge(Compressor.prototype, {
left: make_node(AST_Binary, self.left, {
operator: "+",
left: self.left.left,
right: make_node(AST_String, null, {
right: make_node(AST_String, self.left.right, {
value: "" + self.left.right.getValue() + self.right.left.getValue(),
start: self.left.right.start,
end: self.right.left.end
Expand All @@ -3109,6 +3150,122 @@ merge(Compressor.prototype, {
right: self.right.right
});
}
// a + -b => a - b
if (self.right instanceof AST_UnaryPrefix
&& self.right.operator == "-"
&& self.left.is_number(compressor)) {
self = make_node(AST_Binary, self, {
operator: "-",
left: self.left,
right: self.right.expression
});
}
// -a + b => b - a
if (self.left instanceof AST_UnaryPrefix
&& self.left.operator == "-"
&& reversible()
&& self.right.is_number(compressor)) {
self = make_node(AST_Binary, self, {
operator: "-",
left: self.right,
right: self.left.expression
});
}
case "*":
associative = compressor.option("unsafe_math");
case "&":
case "|":
case "^":
// a + +b => +b + a
if (self.left.is_number(compressor)
&& self.right.is_number(compressor)
&& reversible()
&& !(self.left instanceof AST_Binary
&& self.left.operator != self.operator
&& PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
var reversed = make_node(AST_Binary, self, {
operator: self.operator,
left: self.right,
right: self.left
});
if (self.right instanceof AST_Constant
&& !(self.left instanceof AST_Constant)) {
self = best_of(reversed, self);
} else {
self = best_of(self, reversed);
}
}
if (associative && self.is_number(compressor)) {
// a + (b + c) => (a + b) + c
if (self.right instanceof AST_Binary
&& self.right.operator == self.operator) {
self = make_node(AST_Binary, self, {
operator: self.operator,
left: make_node(AST_Binary, self.left, {
operator: self.operator,
left: self.left,
right: self.right.left,
start: self.left.start,
end: self.right.left.end
}),
right: self.right.right
});
}
// (n + 2) + 3 => 5 + n
// (2 * n) * 3 => 6 + n
if (self.right instanceof AST_Constant
&& self.left instanceof AST_Binary
&& self.left.operator == self.operator) {
if (self.left.left instanceof AST_Constant) {
self = make_node(AST_Binary, self, {
operator: self.operator,
left: make_node(AST_Binary, self.left, {
operator: self.operator,
left: self.left.left,
right: self.right,
start: self.left.left.start,
end: self.right.end
}),
right: self.left.right
});
} else if (self.left.right instanceof AST_Constant) {
self = make_node(AST_Binary, self, {
operator: self.operator,
left: make_node(AST_Binary, self.left, {
operator: self.operator,
left: self.left.right,
right: self.right,
start: self.left.right.start,
end: self.right.end
}),
right: self.left.left
});
}
}
// (a | 1) | (2 | d) => (3 | a) | b
if (self.left instanceof AST_Binary
&& self.left.operator == self.operator
&& self.left.right instanceof AST_Constant
&& self.right instanceof AST_Binary
&& self.right.operator == self.operator
&& self.right.left instanceof AST_Constant) {
self = make_node(AST_Binary, self, {
operator: self.operator,
left: make_node(AST_Binary, self.left, {
operator: self.operator,
left: make_node(AST_Binary, self.left.left, {
operator: self.operator,
left: self.left.right,
right: self.right.left,
start: self.left.right.start,
end: self.right.left.end
}),
right: self.left.left
}),
right: self.right.right
});
}
}
}
}
// x && (y && z) ==> x && y && z
Expand Down
Loading

0 comments on commit 18059cc

Please sign in to comment.