extend support for numeral literals (#4176)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sun, 4 Oct 2020 16:05:03 +0000 (17:05 +0100)
committerGitHub <noreply@github.com>
Sun, 4 Oct 2020 16:05:03 +0000 (00:05 +0800)
lib/parse.js
test/mocha/number-literal.js

index d57bacc..3989ae6 100644 (file)
@@ -60,8 +60,9 @@ KEYWORDS_ATOM = makePredicate(KEYWORDS_ATOM);
 
 var OPERATOR_CHARS = makePredicate(characters("+-*&%=<>!?|~^"));
 
-var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
-var RE_OCT_NUMBER = /^0[0-7]+$/;
+var RE_BIN_NUMBER = /^0b([01]+)$/i;
+var RE_HEX_NUMBER = /^0x([0-9a-f]+)$/i;
+var RE_OCT_NUMBER = /^0o?([0-7]+)$/i;
 
 var OPERATORS = makePredicate([
     "in",
@@ -147,10 +148,6 @@ function is_digit(code) {
     return code >= 48 && code <= 57;
 }
 
-function is_alphanumeric_char(code) {
-    return is_digit(code) || is_letter(code);
-}
-
 function is_unicode_digit(code) {
     return UNICODE.digit.test(String.fromCharCode(code));
 }
@@ -184,14 +181,12 @@ function is_identifier_string(str) {
 }
 
 function parse_js_number(num) {
-    if (RE_HEX_NUMBER.test(num)) {
-        return parseInt(num.substr(2), 16);
-    } else if (RE_OCT_NUMBER.test(num)) {
-        return parseInt(num.substr(1), 8);
-    } else {
-        var val = parseFloat(num);
-        if (val == num) return val;
-    }
+    var match;
+    if (match = RE_BIN_NUMBER.exec(num)) return parseInt(match[1], 2);
+    if (match = RE_HEX_NUMBER.exec(num)) return parseInt(match[1], 16);
+    if (match = RE_OCT_NUMBER.exec(num)) return parseInt(match[1], 8);
+    var val = parseFloat(num);
+    if (val == num) return val;
 }
 
 function JS_Parse_Error(message, filename, line, col, pos) {
@@ -347,11 +342,13 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
               case (after_e = false, 46): // .
                 return (!has_dot && !has_x && !has_e) ? (has_dot = true) : false;
             }
-            return is_alphanumeric_char(code);
+            return is_digit(code) || is_letter(code) || ch == "_";
         });
         if (prefix) num = prefix + num;
-        if (RE_OCT_NUMBER.test(num) && next_token.has_directive("use strict")) {
-            parse_error("Legacy octal literals are not allowed in strict mode");
+        if (/^0[0-7_]+$/.test(num)) {
+            if (next_token.has_directive("use strict")) parse_error("Legacy octal literals are not allowed in strict mode");
+        } else {
+            num = num.replace(has_x ? /([1-9a-f]|.0)_(?=[0-9a-f])/gi : /([1-9]|.0)_(?=[0-9])/gi, "$1");
         }
         var valid = parse_js_number(num);
         if (!isNaN(valid)) return token("num", valid);
index b87c88b..a2b16a9 100644 (file)
@@ -28,4 +28,65 @@ describe("Number literals", function() {
             assert.throws(test(inputs[i]), error, inputs[i]);
         }
     });
+    it("Should parse binary, hexadecimal, octal and underscore correctly", function() {
+        [
+            "42",
+            "4_2",
+            "052",
+            "0o52",
+            "0O52",
+            "0o5_2",
+            "0x2a",
+            "0X2A",
+            "0x2_a",
+            "0b101010",
+            "0B101010",
+            "0b101_010",
+            "0.0000000042e+10",
+            "0.0000000042E+10",
+            "0.0_000000042e+10",
+            "0.0000000042e+1_0",
+            "0.000_000_004_2e+1_0",
+            "0.000_000_004_2e+1_0-0B101_010+0x2_A-0o5_2+4_2",
+        ].forEach(function(code) {
+            var result = UglifyJS.minify(code, {
+                compress: {
+                    expression: true,
+                },
+            });
+            if (result.error) throw result.error;
+            assert.strictEqual(result.code, "42;");
+        });
+    });
+    it("Should reject invalid use of underscore", function() {
+        [
+            "_42",
+            "_+42",
+            "+_42",
+        ].forEach(function(code) {
+            var node = UglifyJS.parse(code, {
+                expression: true,
+            });
+            assert.ok(!node.is_constant(), code);
+            assert.ok(!(node instanceof UglifyJS.AST_Statement), code);
+        });
+        [
+            "42_",
+            "4__2",
+            "0_52",
+            "05_2",
+            "0_o52",
+            "0o_52",
+            "0.0000000042_e10",
+            "0.0000000042e_10",
+            "0.0000000042e_+10",
+            "0.0000000042e+_10",
+        ].forEach(function(code) {
+            assert.throws(function() {
+                UglifyJS.parse(code);
+            }, function(e) {
+                return e instanceof UglifyJS.JS_Parse_Error;
+            }, code);
+        });
+    });
 });