From 6eaeb19a4a5ecbb890fc025591bb24d466d3efe0 Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Sat, 18 Jun 2016 00:25:18 +0200 Subject: [PATCH] Add exponentiation operator --- lib/compress.js | 1 + lib/output.js | 8 +++- lib/parse.js | 13 ++++-- test/compress/evaluate.js | 88 +++++++++++++++++++++++++++++++++++++ test/compress/expression.js | 52 ++++++++++++++++++++++ test/mocha/expression.js | 32 ++++++++++++++ 6 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 test/compress/expression.js create mode 100644 test/mocha/expression.js diff --git a/lib/compress.js b/lib/compress.js index 1c99dad3..0fbed00a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1088,6 +1088,7 @@ merge(Compressor.prototype, { case "^" : return ev(left, c) ^ ev(right, c); case "+" : return ev(left, c) + ev(right, c); case "*" : return ev(left, c) * ev(right, c); + case "**" : return Math.pow(ev(left, c), ev(right, c)); case "/" : return ev(left, c) / ev(right, c); case "%" : return ev(left, c) % ev(right, c); case "-" : return ev(left, c) - ev(right, c); diff --git a/lib/output.js b/lib/output.js index a048ce1a..1fc541a6 100644 --- a/lib/output.js +++ b/lib/output.js @@ -538,7 +538,13 @@ function OutputStream(options) { PARENS([ AST_Unary, AST_Undefined ], function(output){ var p = output.parent(); return p instanceof AST_PropAccess && p.expression === this - || p instanceof AST_New; + || p instanceof AST_New + || p instanceof AST_Binary + && p.operator === "**" + && this instanceof AST_UnaryPrefix + && p.left === this + && this.operator !== "++" + && this.operator !== "--"; }); PARENS(AST_Seq, function(output){ diff --git a/lib/parse.js b/lib/parse.js index 4132340a..f1fbf9e8 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -80,6 +80,7 @@ var OPERATORS = makePredicate([ "|", "^", "*", + "**", "/", "%", ">>", @@ -99,6 +100,7 @@ var OPERATORS = makePredicate([ "-=", "/=", "*=", + "**=", "%=", ">>=", "<<=", @@ -694,7 +696,7 @@ var UNARY_PREFIX = makePredicate([ var UNARY_POSTFIX = makePredicate([ "--", "++" ]); -var ASSIGNMENT = makePredicate([ "=", "+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&=" ]); +var ASSIGNMENT = makePredicate([ "=", "+=", "-=", "/=", "*=", "**=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&=" ]); var PRECEDENCE = (function(a, ret){ for (var i = 0; i < a.length; ++i) { @@ -715,7 +717,8 @@ var PRECEDENCE = (function(a, ret){ ["<", ">", "<=", ">=", "in", "instanceof"], [">>", "<<", ">>>"], ["+", "-"], - ["*", "/", "%"] + ["*", "/", "%"], + ["**"] ], {} ); @@ -2015,8 +2018,12 @@ function parse($TEXT, options) { var expr_op = function(left, min_prec, no_in) { var op = is("operator") ? S.token.value : null; if (op == "in" && no_in) op = null; + if (op == "**" && left instanceof AST_UnaryPrefix + && left.end === S.prev /* unary token in front not allowed, but allowed if prev is for example `)` */ + && left.operator !== "--" && left.operator !== "++") + unexpected(left.start); var prec = op != null ? PRECEDENCE[op] : null; - if (prec != null && prec > min_prec) { + if (prec != null && (prec > min_prec || (op === "**" && min_prec === prec))) { next(); var right = expr_op(maybe_unary(true), prec, no_in); return expr_op(new AST_Binary({ diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index d27582f3..f2658c55 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -37,3 +37,91 @@ positive_zero: { ); } } + +pow: { + options = { evaluate: true } + input: { + var a = 5 ** 3; + } + expect: { + var a = 125; + } +} + +pow_sequence: { + options = { + evaluate: true + } + input: { + var a = 2 ** 3 ** 2; + } + expect: { + var a = 512; + } +} + +pow_mixed: { + options = { + evaluate: true + } + input: { + var a = 5 + 2 ** 3 + 5; + var b = 5 * 3 ** 2; + var c = 5 ** 3 * 2; + var d = 5 ** +3; + } + expect: { + var a = 18; + var b = 45; + var c = 250; + var d = 125; + } +} + +pow_with_right_side_evaluating_to_unary: { + options = { + evaluate: true + } + input: { + var a = (4 - 7) ** foo; + var b = ++bar ** 3; + var c = --baz ** 2; + } + expect_exact: "var a=(-3)**foo;var b=++bar**3;var c=--baz**2;" +} + +pow_with_number_constants: { + options = { + evaluate: true + } + input: { + var a = 5 ** NaN; /* NaN exponent results to NaN */ + var b = 42 ** +0; /* +0 exponent results to NaN */ + var c = 42 ** -0; /* -0 exponent results to NaN */ + var d = NaN ** 1; /* NaN with non-zero exponent is NaN */ + var e = 2 ** Infinity; /* abs(base) > 1 with Infinity as exponent is Infinity */ + var f = 2 ** -Infinity; /* abs(base) > 1 with -Infinity as exponent is +0 */ + var g = (-7) ** (0.5); + var h = 2324334 ** 34343443; + var i = (-2324334) ** 34343443; + var j = 2 ** (-3); + var k = 2.0 ** -3; + var l = 2.0 ** (5 - 7); + var m = 3 ** -10; // Result will be 0.000016935087808430286, which is too long + } + expect: { + var a = NaN; + var b = 1; + var c = 1; + var d = NaN; + var e = Infinity; + var f = 0; + var g = NaN; + var h = Infinity; + var i = -Infinity; + var j = .125; + var k = .125; + var l = .25; + var m = 3 ** -10; + } +} diff --git a/test/compress/expression.js b/test/compress/expression.js new file mode 100644 index 00000000..a3fe1a5a --- /dev/null +++ b/test/compress/expression.js @@ -0,0 +1,52 @@ +pow: { + input: { + var a = 2 ** 7; + var b = 3; + b **= 2; + } + expect: { + var a = 2 ** 7; + var b = 3; + b **= 2; + } +} + +pow_with_number_constants: { + input: { + var a = 5 ** NaN; + var b = 42 ** +0; + var c = 42 ** -0; + var d = NaN ** 1; + var e = 2 ** Infinity; + var f = 2 ** -Infinity; + } + expect: { + var a = 5 ** NaN; + var b = 42 ** +0; + var c = 42 ** -0; + var d = NaN ** 1; + var e = 2 ** (1/0); + var f = 2 ** -(1/0); + } +} + +pow_with_parentheses: { + input: { + var g = (-7) ** (0.5); + var h = 2324334 ** 34343443; + var i = (-2324334) ** 34343443; + var j = 2 ** (-3); + var k = 2.0 ** -3; + var l = 2.0 ** (5 - 7); + } + expect_exact: "var g=(-7)**.5;var h=2324334**34343443;var i=(-2324334)**34343443;var j=2**-3;var k=2**-3;var l=2**(5-7);" +} + +pow_with_unary_between_brackets: { + input: { + var a = (-(+5)) ** 3; + } + expect: { + var a = (-+5)**3; + } +} diff --git a/test/mocha/expression.js b/test/mocha/expression.js new file mode 100644 index 00000000..178cff22 --- /dev/null +++ b/test/mocha/expression.js @@ -0,0 +1,32 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Expression", function() { + it("Should not allow the first exponentiation operator to be prefixed with an unary operator", function() { + var tests = [ + "+5 ** 3", + "-5 ** 3", + "~5 ** 3", + "!5 ** 3", + "void 5 ** 3", + "typeof 5 ** 3", + "delete 5 ** 3", + "var a = -(5) ** 3;" + ]; + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error && + /^SyntaxError: Unexpected token: operator \((?:[!+~-]|void|typeof|delete)\)/.test(e.message); + } + + var exec = function(test) { + return function() { + uglify.parse(test); + } + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(exec(tests[i]), fail, tests[i]); + } + }); +});