[Pkg-javascript-commits] [uglifyjs] 69/228: compress numerical expressions (#1513)

Jonas Smedegaard dr at jones.dk
Sat Apr 15 14:25:18 UTC 2017


This is an automated email from the git hooks/post-receive script.

js pushed a commit to branch master
in repository uglifyjs.

commit 18059cc94fdc037e296a1cb1b08143d5e3aae570
Author: Alex Lam S.L <alexlamsl at gmail.com>
Date:   Fri Mar 3 18:04:32 2017 +0800

    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
---
 README.md                |   3 +
 lib/compress.js          | 171 +++++++++++++++++++++++++++++++++++++++++++++--
 test/compress/numbers.js | 136 +++++++++++++++++++++++++++++++++++++
 3 files changed, 303 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index 79064d7..628bcde 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 38ebbf4..ec1e717 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 8e32ad0..0b40bb9 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
+        );
+    }
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/uglifyjs.git



More information about the Pkg-javascript-commits mailing list