// Copyright 2013 the V8 project authors. All rights reserved.
// Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1.  Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
// 2.  Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in the
//     documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

description(
"This test checks that parentheses are preserved when significant, and not added where inappropriate. " +
"We need this test because the JavaScriptCore parser removes all parentheses and the serializer then adds them back."
);

function compileAndSerialize(expression)
{
    var f = eval("(function () { return " + expression + "; })");
    var serializedString = f.toString();
    serializedString = serializedString.replace(/[ \t\r\n]+/g, " ");
    serializedString = serializedString.replace("function () { return ", "");
    serializedString = serializedString.replace("; }", "");
    return serializedString;
}

function compileAndSerializeLeftmostTest(expression)
{
    var f = eval("(function () { " + expression + "; })");
    var serializedString = f.toString();
    serializedString = serializedString.replace(/[ \t\r\n]+/g, " ");
    serializedString = serializedString.replace("function () { ", "");
    serializedString = serializedString.replace("; }", "");
    return serializedString;
}

var removesExtraParentheses = compileAndSerialize("(a + b) + c") == "a + b + c";

function testKeepParentheses(expression)
{
  shouldBe("compileAndSerialize('" + expression + "')",
           "'" + expression + "'");
}

function testOptionalParentheses(expression)
{
  stripped_expression = removesExtraParentheses
        ? expression.replace(/\(/g, '').replace(/\)/g, '')
        : expression;
  shouldBe("compileAndSerialize('" + expression + "')",
           "'" + stripped_expression + "'");
}

function testLeftAssociativeSame(opA, opB)
{
    testKeepParentheses("a " + opA + " b " + opB + " c");
    testOptionalParentheses("(a " + opA + " b) " + opB + " c");
    testKeepParentheses("a " + opA + " (b " + opB + " c)");
}

function testRightAssociativeSame(opA, opB)
{
    testKeepParentheses("a " + opA + " b " + opB + " c");
    testKeepParentheses("(a " + opA + " b) " + opB + " c");
    testOptionalParentheses("a " + opA + " (b " + opB + " c)");
}

function testHigherFirst(opHigher, opLower)
{
    testKeepParentheses("a " + opHigher + " b " + opLower + " c");
    testOptionalParentheses("(a " + opHigher + " b) " + opLower + " c");
    testKeepParentheses("a " + opHigher + " (b " + opLower + " c)");
}

function testLowerFirst(opLower, opHigher)
{
    testKeepParentheses("a " + opLower + " b " + opHigher + " c");
    testKeepParentheses("(a " + opLower + " b) " + opHigher + " c");
    testOptionalParentheses("a " + opLower + " (b " + opHigher + " c)");
}

var binaryOperators = [
    [ "*", "/", "%" ], [ "+", "-" ],
    [ "<<", ">>", ">>>" ],
    [ "<", ">", "<=", ">=", "instanceof", "in" ],
    [ "==", "!=", "===", "!==" ],
    [ "&" ], [ "^" ], [ "|" ],
    [ "&&" ], [ "||" ]
];

for (i = 0; i < binaryOperators.length; ++i) {
    var ops = binaryOperators[i];
    for (j = 0; j < ops.length; ++j) {
        var op = ops[j];
        testLeftAssociativeSame(op, op);
        if (j != 0)
            testLeftAssociativeSame(ops[0], op);
        if (i < binaryOperators.length - 1) {
            var nextOps = binaryOperators[i + 1];
            if (j == 0)
                for (k = 0; k < nextOps.length; ++k)
                    testHigherFirst(op, nextOps[k]);
            else
                testHigherFirst(op, nextOps[0]);
        }
    }
}

var assignmentOperators = [ "=", "*=", "/=" , "%=", "+=", "-=", "<<=", ">>=", ">>>=", "&=", "^=", "|=" ];

for (i = 0; i < assignmentOperators.length; ++i) {
    var op = assignmentOperators[i];
    testRightAssociativeSame(op, op);
    if (i != 0)
        testRightAssociativeSame("=", op);
    testLowerFirst(op, "+");
    shouldThrow("compileAndSerialize('a + b " + op + " c')");
    testKeepParentheses("(a + b) " + op + " c");
    testKeepParentheses("a + (b " + op + " c)");
}

var prefixOperators = [ "delete", "void", "typeof", "++", "--", "+", "-", "~", "!" ];
var prefixOperatorSpace = [ " ", " ", " ", "", "", " ", " ", "", "" ];

for (i = 0; i < prefixOperators.length; ++i) {
    var op = prefixOperators[i] + prefixOperatorSpace[i];
    testKeepParentheses("" + op + "a + b");
    testOptionalParentheses("(" + op + "a) + b");
    testKeepParentheses("" + op + "(a + b)");
    testKeepParentheses("!" + op + "a");
    testOptionalParentheses("!(" + op + "a)");
}


testKeepParentheses("!a++");
testOptionalParentheses("!(a++)");
testKeepParentheses("(!a)++");

testKeepParentheses("!a--");
testOptionalParentheses("!(a--)");
testKeepParentheses("(!a)--");

testKeepParentheses("(-1)[a]");
testKeepParentheses("(-1)[a] = b");
testKeepParentheses("(-1)[a] += b");
testKeepParentheses("(-1)[a]++");
testKeepParentheses("++(-1)[a]");
testKeepParentheses("(-1)[a]()");

testKeepParentheses("new (-1)()");

testKeepParentheses("(-1).a");
testKeepParentheses("(-1).a = b");
testKeepParentheses("(-1).a += b");
testKeepParentheses("(-1).a++");
testKeepParentheses("++(-1).a");
testKeepParentheses("(-1).a()");

testKeepParentheses("(- 0)[a]");
testKeepParentheses("(- 0)[a] = b");
testKeepParentheses("(- 0)[a] += b");
testKeepParentheses("(- 0)[a]++");
testKeepParentheses("++(- 0)[a]");
testKeepParentheses("(- 0)[a]()");

testKeepParentheses("new (- 0)()");

testKeepParentheses("(- 0).a");
testKeepParentheses("(- 0).a = b");
testKeepParentheses("(- 0).a += b");
testKeepParentheses("(- 0).a++");
testKeepParentheses("++(- 0).a");
testKeepParentheses("(- 0).a()");

testOptionalParentheses("(1)[a]");
testOptionalParentheses("(1)[a] = b");
testOptionalParentheses("(1)[a] += b");
testOptionalParentheses("(1)[a]++");
testOptionalParentheses("++(1)[a]");

shouldBe("compileAndSerialize('(1)[a]()')",
    removesExtraParentheses ? "'1[a]()'" : "'(1)[a]()'");

shouldBe("compileAndSerialize('new (1)()')",
    removesExtraParentheses ? "'new 1()'" : "'new (1)()'");

testKeepParentheses("(1).a");
testKeepParentheses("(1).a = b");
testKeepParentheses("(1).a += b");
testKeepParentheses("(1).a++");
testKeepParentheses("++(1).a");
testKeepParentheses("(1).a()");

for (i = 0; i < assignmentOperators.length; ++i) {
    var op = assignmentOperators[i];
    testKeepParentheses("(-1) " + op + " a");
    testKeepParentheses("(- 0) " + op + " a");
    testKeepParentheses("1 " + op + " a");
}

shouldBe("compileAndSerializeLeftmostTest('({ }).x')", "'({ }).x'");
shouldBe("compileAndSerializeLeftmostTest('x = { }')", "'x = { }'");
shouldBe("compileAndSerializeLeftmostTest('(function () { })()')", "'(function () { })()'");
shouldBe("compileAndSerializeLeftmostTest('x = function () { }')", "'x = function () { }'");

shouldBe("compileAndSerializeLeftmostTest('var a')", "'var a'");
shouldBe("compileAndSerializeLeftmostTest('var a = 1')", "'var a = 1'");
shouldBe("compileAndSerializeLeftmostTest('var a, b')", "'var a, b'");
shouldBe("compileAndSerializeLeftmostTest('var a = 1, b = 2')", "'var a = 1, b = 2'");
shouldBe("compileAndSerializeLeftmostTest('var a, b, c')", "'var a, b, c'");
shouldBe("compileAndSerializeLeftmostTest('var a = 1, b = 2, c = 3')", "'var a = 1, b = 2, c = 3'");

shouldBe("compileAndSerializeLeftmostTest('const a = 1')", "'const a = 1'");
shouldBe("compileAndSerializeLeftmostTest('const a = (1, 2)')", "'const a = (1, 2)'");
shouldBe("compileAndSerializeLeftmostTest('const a, b = 1')", "'const a, b = 1'");
shouldBe("compileAndSerializeLeftmostTest('const a = 1, b')", "'const a = 1, b'");
shouldBe("compileAndSerializeLeftmostTest('const a = 1, b = 1')", "'const a = 1, b = 1'");
shouldBe("compileAndSerializeLeftmostTest('const a = (1, 2), b = 1')", "'const a = (1, 2), b = 1'");
shouldBe("compileAndSerializeLeftmostTest('const a = 1, b = (1, 2)')", "'const a = 1, b = (1, 2)'");
shouldBe("compileAndSerializeLeftmostTest('const a = (1, 2), b = (1, 2)')", "'const a = (1, 2), b = (1, 2)'");

shouldBe("compileAndSerialize('(function () { new (a.b()).c })')", "'(function () { new (a.b()).c })'");