From 9d23ba0a22fe3d1561b7860dc3aefadef72a4514 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 24 Jan 2021 21:48:51 +0000 Subject: [PATCH] support exponentiation operator (#4593) --- README.md | 6 ++++ lib/compress.js | 3 ++ lib/output.js | 13 ++++++-- lib/parse.js | 6 ++-- test/compress/exponentiation.js | 58 +++++++++++++++++++++++++++++++++ test/ufuzz/index.js | 20 +++++++----- 6 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 test/compress/exponentiation.js diff --git a/README.md b/README.md index 9cecfcf4..30fc1238 100644 --- a/README.md +++ b/README.md @@ -1254,3 +1254,9 @@ To allow for better optimizations, the compiler makes various assumptions: }()) => b)()); ``` UglifyJS may modify the input which in turn may suppress those errors. +- Some arithmetic operations with `BigInt` may throw `TypeError`: + ```javascript + 1n + 1; + // TypeError: can't convert BigInt to number + ``` + UglifyJS may modify the input which in turn may suppress those errors. diff --git a/lib/compress.js b/lib/compress.js index d79c1cce..ac380cf3 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -4168,6 +4168,9 @@ merge(Compressor.prototype, { case "<=" : result = left <= right; break; case ">" : result = left > right; break; case ">=" : result = left >= right; break; + case "**": + result = Math.pow(left, right); + break; case "in": if (right && typeof right == "object" && HOP(right, left)) { result = true; diff --git a/lib/output.js b/lib/output.js index f2748e9e..8521d7ef 100644 --- a/lib/output.js +++ b/lib/output.js @@ -684,6 +684,10 @@ function OutputStream(options) { PARENS(AST_Unary, function(output) { var p = output.parent(); + // (-x) ** y + if (p instanceof AST_Binary) return p.operator == "**" && p.left === this; + // (x++).toString(3) + // (typeof x).length return (p instanceof AST_Call || p instanceof AST_PropAccess) && p.expression === this; }); @@ -722,11 +726,14 @@ function OutputStream(options) { var p = output.parent(); // await (foo && bar) if (p instanceof AST_Await) return true; - // this deals with precedence: 3 * (2 + 1) + // this deals with precedence: + // 3 * (2 + 1) + // 3 - (2 - 1) + // (1 ** 2) ** 3 if (p instanceof AST_Binary) { var po = p.operator, pp = PRECEDENCE[po]; var so = this.operator, sp = PRECEDENCE[so]; - return pp > sp || (pp == sp && this === p.right); + return pp > sp || (pp == sp && this === p[po == "**" ? "left" : "right"]); } // (foo && bar)() if (p instanceof AST_Call) return p.expression === this; @@ -818,6 +825,8 @@ function OutputStream(options) { PARENS(AST_Await, function(output) { var p = output.parent(); + // (await x) ** y + if (p instanceof AST_Binary) return p.operator == "**" && p.left === this; // new (await foo) // (await foo)(bar) if (p instanceof AST_Call) return p.expression === this; diff --git a/lib/parse.js b/lib/parse.js index 64307a95..38c56d8f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -81,6 +81,7 @@ var OPERATORS = makePredicate([ "*", "/", "%", + "**", ">>", "<<", ">>>", @@ -630,7 +631,8 @@ var PRECEDENCE = function(a, ret) { ["<", ">", "<=", ">=", "in", "instanceof"], [">>", "<<", ">>>"], ["+", "-"], - ["*", "/", "%"] + ["*", "/", "%"], + ["**"], ], {}); var ATOMIC_START_TOKEN = makePredicate("atom bigint num regexp string"); @@ -1860,7 +1862,7 @@ function parse($TEXT, options) { var prec = op != null ? PRECEDENCE[op] : null; if (prec != null && prec > min_prec) { next(); - var right = expr_op(maybe_await(), prec, no_in); + var right = expr_op(maybe_await(), op == "**" ? prec - 1 : prec, no_in); return expr_op(new AST_Binary({ start : left.start, left : left, diff --git a/test/compress/exponentiation.js b/test/compress/exponentiation.js new file mode 100644 index 00000000..008d5871 --- /dev/null +++ b/test/compress/exponentiation.js @@ -0,0 +1,58 @@ +precedence_1: { + input: { + console.log(-4 ** 3 ** 2); + } + expect_exact: "console.log((-4)**3**2);" + expect_stdout: "-262144" + node_version: ">=8" +} + +precedence_2: { + input: { + console.log(-4 ** (3 ** 2)); + } + expect_exact: "console.log((-4)**3**2);" + expect_stdout: "-262144" + node_version: ">=8" +} + +precedence_3: { + input: { + console.log(-(4 ** 3) ** 2); + } + expect_exact: "console.log((-(4**3))**2);" + expect_stdout: "4096" + node_version: ">=8" +} + +precedence_4: { + input: { + console.log((-4 ** 3) ** 2); + } + expect_exact: "console.log(((-4)**3)**2);" + expect_stdout: "4096" + node_version: ">=8" +} + +await: { + input: { + (async a => a * await a ** ++a % a)(2).then(console.log); + } + expect_exact: "(async a=>a*(await a)**++a%a)(2).then(console.log);" + expect_stdout: "1" + node_version: ">=8" +} + +evaluate: { + options = { + evaluate: true, + } + input: { + console.log(1 + 2 ** 3 - 4); + } + expect: { + console.log(5); + } + expect_stdout: "5" + node_version: ">=8" +} diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index f8d648bc..bee4c399 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -140,6 +140,7 @@ var SUPPORT = function(matrix) { const_block: "var a; { const a = 0; }", default_value: "[ a = 0 ] = [];", destructuring: "[] = [];", + exponentiation: "0 ** 0", let: "let a;", rest: "var [...a] = [];", rest_object: "var {...a} = {};", @@ -168,7 +169,7 @@ var VALUES = [ "4", "5", "22", - "-0", // 0/-0 !== 0 + "(-0)", // 0/-0 !== 0 "23..toString()", "24 .toString()", "25. ", @@ -190,7 +191,7 @@ var VALUES = [ "this", ]; if (SUPPORT.bigint) VALUES = VALUES.concat([ - "!0o644n", + "(!0o644n)", "([3n][0] > 2)", "(-42n).toString()", "Number(0XDEADn << 16n | 0xbeefn)", @@ -224,6 +225,7 @@ var BINARY_OPS = [ BINARY_OPS = BINARY_OPS.concat(BINARY_OPS); BINARY_OPS = BINARY_OPS.concat(BINARY_OPS); BINARY_OPS = BINARY_OPS.concat(BINARY_OPS); +if (SUPPORT.exponentiation) BINARY_OPS.push("**"); BINARY_OPS = BINARY_OPS.concat(BINARY_OPS); BINARY_OPS = BINARY_OPS.concat(BINARY_OPS); BINARY_OPS.push(" in "); @@ -439,17 +441,19 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was return pairs; function mapShuffled(values, fn) { - var ordered = []; - var shuffled = []; + var declare_only = []; + var side_effects = []; values.forEach(function(value, index) { value = fn(value, index); - if (/]:/.test(value) ? canThrow && rng(10) == 0 : rng(5)) { - shuffled.splice(rng(shuffled.length + 1), 0, value); + if (/]:|=/.test(value) ? canThrow && rng(10) == 0 : rng(5)) { + declare_only.splice(rng(declare_only.length + 1), 0, value); + } else if (canThrow && rng(5) == 0) { + side_effects.splice(rng(side_effects.length + 1), 0, value); } else { - ordered.push(value); + side_effects.push(value); } }); - return shuffled.concat(ordered); + return declare_only.concat(side_effects); } function convertToRest(names) { -- 2.34.1