aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2017-03-03 18:04:32 +0800
committerGitHub <noreply@github.com>2017-03-03 18:04:32 +0800
commit18059cc94fdc037e296a1cb1b08143d5e3aae570 (patch)
treed8b787fba1df5fe5c5052354008084c9f74e58d3
parentb5e0e8c2038c7c0ea13771891eb84f6e6f7bcbc3 (diff)
downloadtracifyjs-18059cc94fdc037e296a1cb1b08143d5e3aae570.tar.gz
tracifyjs-18059cc94fdc037e296a1cb1b08143d5e3aae570.zip
compress numerical expressions (#1513)
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 #412
-rw-r--r--README.md3
-rw-r--r--lib/compress.js171
-rw-r--r--test/compress/numbers.js136
3 files changed, 303 insertions, 7 deletions
diff --git a/README.md b/README.md
index 79064d79..628bcdec 100644
--- a/README.md
+++ b/README.md
@@ -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)`
diff --git a/lib/compress.js b/lib/compress.js
index 38ebbf40..ec1e7174 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -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,
@@ -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);
@@ -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;
@@ -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)) {
@@ -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);
}
@@ -3056,7 +3092,10 @@ 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 == "+"
@@ -3064,7 +3103,7 @@ merge(Compressor.prototype, {
&& 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
@@ -3072,6 +3111,7 @@ merge(Compressor.prototype, {
right: self.right.right
});
}
+ // (x + "foo") + "bar" => x + "foobar"
if (self.right instanceof AST_Constant
&& self.left instanceof AST_Binary
&& self.left.operator == "+"
@@ -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)
@@ -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
@@ -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
diff --git a/test/compress/numbers.js b/test/compress/numbers.js
index 8e32ad02..0b40bb9c 100644
--- a/test/compress/numbers.js
+++ b/test/compress/numbers.js
@@ -17,3 +17,139 @@ hex_numbers_in_parentheses_for_prototype_functions: {
}
expect_exact: "-2;(-2).toFixed(0);2;2..toFixed(0);.2;.2.toFixed(0);2e-8;2e-8.toFixed(0);0xde0b6b3a7640080;(0xde0b6b3a7640080).toFixed(0);"
}
+
+comparisons: {
+ options = {
+ comparisons: true,
+ }
+ input: {
+ console.log(
+ ~x === 42,
+ x % n === 42
+ );
+ }
+ expect: {
+ console.log(
+ 42 == ~x,
+ x % n == 42
+ );
+ }
+}
+
+evaluate_1: {
+ options = {
+ evaluate: true,
+ unsafe_math: false,
+ }
+ input: {
+ console.log(
+ x + 1 + 2,
+ x * 1 * 2,
+ +x + 1 + 2,
+ 1 + x + 2 + 3,
+ 1 | x | 2 | 3,
+ 1 + x-- + 2 + 3,
+ 1 + (x*y + 2) + 3,
+ 1 + (2 + x + 3),
+ 1 + (2 + ~x + 3),
+ -y + (2 + ~x + 3),
+ 1 & (2 & x & 3),
+ 1 + (2 + (x |= 0) + 3)
+ );
+ }
+ expect: {
+ console.log(
+ x + 1 + 2,
+ 1 * x * 2,
+ +x + 1 + 2,
+ 1 + x + 2 + 3,
+ 3 | x,
+ 1 + x-- + 2 + 3,
+ x*y + 2 + 1 + 3,
+ 1 + (2 + x + 3),
+ 2 + ~x + 3 + 1,
+ -y + (2 + ~x + 3),
+ 0 & x,
+ 2 + (x |= 0) + 3 + 1
+ );
+ }
+}
+
+evaluate_2: {
+ options = {
+ evaluate: true,
+ unsafe_math: true,
+ }
+ input: {
+ console.log(
+ x + 1 + 2,
+ x * 1 * 2,
+ +x + 1 + 2,
+ 1 + x + 2 + 3,
+ 1 | x | 2 | 3,
+ 1 + x-- + 2 + 3,
+ 1 + (x*y + 2) + 3,
+ 1 + (2 + x + 3),
+ 1 & (2 & x & 3),
+ 1 + (2 + (x |= 0) + 3)
+ );
+ }
+ expect: {
+ console.log(
+ x + 1 + 2,
+ 2 * x,
+ 3 + +x,
+ 1 + x + 2 + 3,
+ 3 | x,
+ 6 + x--,
+ 6 + x*y,
+ 1 + (2 + x + 3),
+ 0 & x,
+ 6 + (x |= 0)
+ );
+ }
+}
+
+evaluate_3: {
+ options = {
+ evaluate: true,
+ unsafe: true,
+ unsafe_math: true,
+ }
+ input: {
+ console.log(1 + Number(x) + 2);
+ }
+ expect: {
+ console.log(3 + +x);
+ }
+}
+
+evaluate_4: {
+ options = {
+ evaluate: true,
+ }
+ input: {
+ console.log(
+ 1+ +a,
+ +a+1,
+ 1+-a,
+ -a+1,
+ +a+ +b,
+ +a+-b,
+ -a+ +b,
+ -a+-b
+ );
+ }
+ expect: {
+ console.log(
+ +a+1,
+ +a+1,
+ 1-a,
+ 1-a,
+ +a+ +b,
+ +a-b,
+ -a+ +b,
+ -a-b
+ );
+ }
+}