/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "tst_qmakelib.h"

#include <proitems.h>
#include <qmakevfs.h>
#include <qmakeparser.h>

class TokenStream
{
public:
    TokenStream() {}
    QString toString() const { return ts; }

    TokenStream &operator<<(ushort n) { ts += QChar(n); return *this; }
    TokenStream &operator<<(uint n) { ts += QChar(n & 0xffff); ts += QChar(n >> 16); return *this; }
    TokenStream &operator<<(const QStringRef &s) { ts += s; return *this; }
    TokenStream &operator<<(const ProString &s) { return *this << ushort(s.length()) << s.toQStringRef(); }
    TokenStream &operator<<(const ProKey &s) { return *this << s.hash() << s.toString(); }

private:
    QString ts;
};

#define TS(s) (TokenStream() s).toString()
#define H(n) ushort(n)
#define I(n) uint(n)
#define S(s) ProString(QString::fromWCharArray(s))
#define HS(s) ProKey(QString::fromWCharArray(s))

QT_WARNING_PUSH
QT_WARNING_DISABLE_MSVC(4003)  // "not enough actual parameters for macro TS()"

void tst_qmakelib::addParseOperators()
{
    QTest::newRow("assign none")
            << "VAR ="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokAssign) << H(0)
    /*    11 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("append none")
            << "VAR +="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokAppend) << H(0)
    /*    11 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("unique append none")
            << "VAR *="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokAppendUnique) << H(0)
    /*    11 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("remove none")
            << "VAR -="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokRemove) << H(0)
    /*    11 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("replace empty")
            << "VAR ~="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokReplace) << H(0)
    /*    11 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("assignment without variable")
            << "="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokAssign) << H(0)
    /*     4 */ << H(TokValueTerminator))
            << "in:1: Assignment needs exactly one word on the left hand side."
            << false;

    QTest::newRow("assignment with multiple variables")
            << "VAR VAR ="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokAssign) << H(0)
    /*     4 */ << H(TokValueTerminator))
            << "in:1: Assignment needs exactly one word on the left hand side."
            << false;
}

void tst_qmakelib::addParseValues()
{
#define ASSIGN_VAR(h) \
        H(TokLine) << H(1) \
        << H(TokHashLiteral) << HS(L"VAR") \
        << H(TokAssign) << H(h)

    QTest::newRow("one literal")
            << "VAR = val"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"val")
    /*    16 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("one literal (squeezed)")
            << "VAR=val"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"val")
    /*    16 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("many literals")
            << "VAR = foo barbaz bak hello"
            << TS(
    /*     0 */ << ASSIGN_VAR(4)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    16 */ << H(TokLiteral | TokNewStr) << S(L"barbaz")
    /*    24 */ << H(TokLiteral | TokNewStr) << S(L"bak")
    /*    29 */ << H(TokLiteral | TokNewStr) << S(L"hello")
    /*    36 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("many literals (tab-separated")
            << "VAR\t=\tfoo\tbarbaz\tbak\thello"
            << TS(
    /*     0 */ << ASSIGN_VAR(4)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    16 */ << H(TokLiteral | TokNewStr) << S(L"barbaz")
    /*    24 */ << H(TokLiteral | TokNewStr) << S(L"bak")
    /*    29 */ << H(TokLiteral | TokNewStr) << S(L"hello")
    /*    36 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("one quoted literal")
            << "VAR = \"val ue\""
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"val ue")
    /*    19 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("quoted literal with missing quote")
            << "VAR = val \"ue"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokAssign) << H(0)
    /*    11 */ << H(TokValueTerminator))
            << "in:1: Missing closing \" quote"
            << false;

    QTest::newRow("many quoted literals")
            << "VAR = \"foo\" barbaz    'bak hello' \"\""
            << TS(
    /*     0 */ << ASSIGN_VAR(3)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    16 */ << H(TokLiteral | TokNewStr) << S(L"barbaz")
    /*    24 */ << H(TokLiteral | TokNewStr) << S(L"bak hello")
    /*    35 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("many quoted literals (with tabs)")
            << "VAR\t=\t\"foo\"\tbarbaz\t'bak\thello'"
            << TS(
    /*     0 */ << ASSIGN_VAR(3)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    16 */ << H(TokLiteral | TokNewStr) << S(L"barbaz")
    /*    24 */ << H(TokLiteral | TokNewStr) << S(L"bak\thello")
    /*    35 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("quoted and unquoted spaces")
            << "  VAR = \"val ue   \"   "
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"val ue   ")
    /*    22 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("funny literals")
            << "VAR = foo:bar|!baz(blam!, ${foo})"
            << TS(
    /*     0 */ << ASSIGN_VAR(2)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"foo:bar|!baz(blam!,")
    /*    32 */ << H(TokLiteral | TokNewStr) << S(L"${foo})")
    /*    41 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("literals with escapes")
            << "VAR = \\{hi\\} \\[ho\\] \\)uh\\( \"\\\\oh\\$\"\\' \\$\\${FOO}"
            << TS(
    /*     0 */ << ASSIGN_VAR(5)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"{hi}")
    /*    17 */ << H(TokLiteral | TokNewStr) << S(L"[ho]")
    /*    23 */ << H(TokLiteral | TokNewStr) << S(L")uh(")
    /*    29 */ << H(TokLiteral | TokNewStr) << S(L"\\oh$'")
    /*    36 */ << H(TokLiteral | TokNewStr) << S(L"$${FOO}")
    /*    45 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("magic variables")
            << "VAR = $$LITERAL_HASH $$LITERAL_DOLLAR $$LITERAL_WHITESPACE $$_FILE_ $$_LINE_"
            << TS(
    /*     0 */ << ASSIGN_VAR(5)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"#")
    /*    14 */ << H(TokLiteral | TokNewStr) << S(L"$")
    /*    17 */ << H(TokLiteral | TokNewStr) << S(L"\t")
    /*    20 */ << H(TokLiteral | TokNewStr) << S(L"in")
    /*    24 */ << H(TokLiteral | TokNewStr) << S(L"1")
    /*    27 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("continuations and comments")
            << "VAR = foo \\\n  bar\n  \n"
               "GAR = foo \\ # comment\n  bar \\\n   # comment\n baz \\\n"
                    "\"quoted \\ #comment\n    escape\" \\\n    right\\\n      after \\\n    gorilla!\n \n\n"
               "MOO = \\\n  kuh # comment\nLOO =\n\n"
               "FOO = bar \\\n# comment\n   baz \\\n    \n# comment\n"
               "GAZ="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokAssign) << H(2)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    16 */ << H(TokLiteral | TokNewStr) << S(L"bar")
    /*    21 */ << H(TokValueTerminator)
    /*    22 */ << H(TokLine) << H(4)
    /*    24 */ << H(TokHashLiteral) << HS(L"GAR")
    /*    31 */ << H(TokAssign) << H(7)
    /*    33 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    38 */ << H(TokLiteral | TokNewStr) << S(L"bar")
    /*    43 */ << H(TokLiteral | TokNewStr) << S(L"baz")
    /*    48 */ << H(TokLiteral | TokNewStr) << S(L"quoted  escape")
    /*    64 */ << H(TokLiteral | TokNewStr) << S(L"right")
    /*    71 */ << H(TokLiteral | TokNewStr) << S(L"after")
    /*    78 */ << H(TokLiteral | TokNewStr) << S(L"gorilla!")
    /*    88 */ << H(TokValueTerminator)
    /*    89 */ << H(TokLine) << H(15)
    /*    91 */ << H(TokHashLiteral) << HS(L"MOO")
    /*    98 */ << H(TokAssign) << H(0)
    /*   100 */ << H(TokLiteral | TokNewStr) << S(L"kuh")
    /*   105 */ << H(TokValueTerminator)
    /*   106 */ << H(TokLine) << H(17)
    /*   108 */ << H(TokHashLiteral) << HS(L"LOO")
    /*   115 */ << H(TokAssign) << H(0)
    /*   117 */ << H(TokValueTerminator)
    /*   118 */ << H(TokLine) << H(19)
    /*   120 */ << H(TokHashLiteral) << HS(L"FOO")
    /*   127 */ << H(TokAssign) << H(2)
    /*   129 */ << H(TokLiteral | TokNewStr) << S(L"bar")
    /*   134 */ << H(TokLiteral | TokNewStr) << S(L"baz")
    /*   139 */ << H(TokValueTerminator)
    /*   140 */ << H(TokLine) << H(24)
    /*   142 */ << H(TokHashLiteral) << HS(L"GAZ")
    /*   149 */ << H(TokAssign) << H(0)
    /*   151 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("accidental continuation")
            << "VAR0 = \\\n    this \\\n    is \\\n    ok\n"
               "VAR1 = \\\n    this \\\n    is=still \\\n    ok\n"
               "VAR2 = \\\n    this \\\n    is \\\n"
               "VAR3 = \\\n    not ok\n"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR0")
    /*    10 */ << H(TokAssign) << H(3)
    /*    12 */ << H(TokLiteral | TokNewStr) << S(L"this")
    /*    18 */ << H(TokLiteral | TokNewStr) << S(L"is")
    /*    22 */ << H(TokLiteral | TokNewStr) << S(L"ok")
    /*    26 */ << H(TokValueTerminator)
    /*    27 */ << H(TokLine) << H(5)
    /*    29 */ << H(TokHashLiteral) << HS(L"VAR1")
    /*    37 */ << H(TokAssign) << H(3)
    /*    39 */ << H(TokLiteral | TokNewStr) << S(L"this")
    /*    45 */ << H(TokLiteral | TokNewStr) << S(L"is=still")
    /*    55 */ << H(TokLiteral | TokNewStr) << S(L"ok")
    /*    59 */ << H(TokValueTerminator)
    /*    60 */ << H(TokLine) << H(9)
    /*    62 */ << H(TokHashLiteral) << HS(L"VAR2")
    /*    70 */ << H(TokAssign) << H(6)
    /*    72 */ << H(TokLiteral | TokNewStr) << S(L"this")
    /*    78 */ << H(TokLiteral | TokNewStr) << S(L"is")
    /*    82 */ << H(TokLiteral | TokNewStr) << S(L"VAR3")
    /*    88 */ << H(TokLiteral | TokNewStr) << S(L"=")
    /*    91 */ << H(TokLiteral | TokNewStr) << S(L"not")
    /*    96 */ << H(TokLiteral | TokNewStr) << S(L"ok")
    /*   100 */ << H(TokValueTerminator))
            << "WARNING: in:12: Possible accidental line continuation"
            << true;

    QTest::newRow("plain variable expansion")
            << "VAR = $$bar"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokVariable | TokNewStr) << HS(L"bar")
    /*    18 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("braced variable expansion")
            << "VAR = $${foo/bar}"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokVariable | TokNewStr) << HS(L"foo/bar")
    /*    22 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("bogus variable expansion")
            << "VAR = $$  "
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokVariable | TokNewStr) << HS(L"")
    /*    15 */ << H(TokValueTerminator))
            << "WARNING: in:1: Missing name in expansion"
            << true;

    QTest::newRow("bogus braced variable expansion")
            << "VAR = $${}"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokVariable | TokNewStr) << HS(L"")
    /*    15 */ << H(TokValueTerminator))
            << "WARNING: in:1: Missing name in expansion"
            << true;

    QTest::newRow("unterminated braced variable expansion")
            << "VAR = $${FOO"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokAssign) << H(0)
    /*    11 */ << H(TokVariable | TokNewStr) << HS(L"FOO")
    /*    18 */ << H(TokValueTerminator))
            << "in:1: Missing } terminator [found end-of-line]"
            << false;

    QTest::newRow("invalid identifier in braced variable expansion")
            << "VAR = $${FOO/BAR+BAZ}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokAssign) << H(0)
    /*    11 */ << H(TokVariable | TokNewStr) << HS(L"FOO/BAR")
    /*    22 */ << H(TokLiteral) << S(L"+BAZ")
    /*    28 */ << H(TokValueTerminator))
            << "in:1: Missing } terminator [found +]"
            << false;

    QTest::newRow("property expansion")
            << "VAR = $$[bar]"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokProperty | TokNewStr) << HS(L"bar")
    /*    18 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("environment expansion")
            << "VAR = $$(bar)"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokEnvVar | TokNewStr) << S(L"bar")
    /*    16 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("plain function call")
            << "VAR = $$bar()"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokFuncName | TokNewStr) << HS(L"bar")
    /*    18 */     << H(TokFuncTerminator)
    /*    19 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("braced function call")
            << "VAR = $${bar()}"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokFuncName | TokNewStr) << HS(L"bar")
    /*    18 */     << H(TokFuncTerminator)
    /*    19 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("function call with one argument")
            << "VAR = $$bar(blubb)"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokFuncName | TokNewStr) << HS(L"bar")
    /*    18 */     << H(TokLiteral | TokNewStr) << S(L"blubb")
    /*    25 */     << H(TokFuncTerminator)
    /*    26 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("function call with multiple arguments")
            << "VAR = $$bar(  blubb blubb, hey  ,$$you)"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokFuncName | TokNewStr) << HS(L"bar")
    /*    18 */     << H(TokLiteral | TokNewStr) << S(L"blubb")
    /*    25 */     << H(TokLiteral | TokNewStr) << S(L"blubb")
    /*    32 */     << H(TokArgSeparator)
    /*    33 */     << H(TokLiteral | TokNewStr) << S(L"hey")
    /*    38 */     << H(TokArgSeparator)
    /*    39 */     << H(TokVariable | TokNewStr) << HS(L"you")
    /*    46 */     << H(TokFuncTerminator)
    /*    47 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("nested function call")
            << "VAR = $$foo(yo, $$bar(blubb))"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokFuncName | TokNewStr) << HS(L"foo")
    /*    18 */     << H(TokLiteral | TokNewStr) << S(L"yo")
    /*    22 */     << H(TokArgSeparator)
    /*    23 */     << H(TokFuncName | TokNewStr) << HS(L"bar")
    /*    30 */         << H(TokLiteral | TokNewStr) << S(L"blubb")
    /*    37 */         << H(TokFuncTerminator)
    /*    38 */     << H(TokFuncTerminator)
    /*    39 */ << H(TokValueTerminator))
            << ""
            << true;

    // This is a rather questionable "feature"
    QTest::newRow("function call with parenthesized argument")
            << "VAR = $$bar(blubb (yo, man) blabb, nope)"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokFuncName | TokNewStr) << HS(L"bar")
    /*    18 */     << H(TokLiteral | TokNewStr) << S(L"blubb")
    /*    25 */     << H(TokLiteral | TokNewStr) << S(L"(yo,")
    /*    31 */     << H(TokLiteral | TokNewStr) << S(L"man)")
    /*    37 */     << H(TokLiteral | TokNewStr) << S(L"blabb")
    /*    44 */     << H(TokArgSeparator)
    /*    45 */     << H(TokLiteral | TokNewStr) << S(L"nope")
    /*    51 */     << H(TokFuncTerminator)
    /*    52 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("separate literal and expansion")
            << "VAR = foo $$bar"
            << TS(
    /*     0 */ << ASSIGN_VAR(2)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    16 */ << H(TokVariable | TokNewStr) << HS(L"bar")
    /*    23 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("separate expansion and literal")
            << "VAR = $$bar foo"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokVariable | TokNewStr) << HS(L"bar")
    /*    18 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    23 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("joined literal and expansion")
            << "VAR = foo$$bar"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    16 */ << H(TokVariable) << HS(L"bar")
    /*    23 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("joined expansion and literal")
            << "VAR = $${bar}foo"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokVariable | TokNewStr) << HS(L"bar")
    /*    18 */ << H(TokLiteral) << S(L"foo")
    /*    23 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("plain variable expansion with funny name and literal")
            << "VAR = $$az_AZ_09.dot/nix"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokVariable | TokNewStr) << HS(L"az_AZ_09.dot")
    /*    27 */ << H(TokLiteral) << S(L"/nix")
    /*    33 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("braced variable expansion with funny name")
            << "VAR = $${az_AZ_09.dot/nix}"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokVariable | TokNewStr) << HS(L"az_AZ_09.dot/nix")
    /*    31 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("quoted joined literal and expansion")
            << "VAR = 'foo$$bar'"
            << TS(
    /*     0 */ << ASSIGN_VAR(0)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    16 */ << H(TokVariable | TokQuoted) << HS(L"bar")
    /*    23 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("assignment with expansion in variable name")
            << "VAR$$EXTRA ="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokVariable) << HS(L"EXTRA")
    /*    18 */ << H(TokAssign) << H(0)
    /*    20 */ << H(TokValueTerminator))
            << ""
            << true;
}

void tst_qmakelib::addParseConditions()
{
    QTest::newRow("one test")
            << "foo"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("wildcard-test")
            << "foo-*"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo-*")
    /*    11 */ << H(TokCondition))
            << ""
            << true;

    // This is a rather questionable "feature"
    QTest::newRow("one quoted test")
            << "\"foo\""
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("two tests")
            << "foo\nbar"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokLine) << H(2)
    /*    12 */ << H(TokHashLiteral) << HS(L"bar")
    /*    19 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("bogus two tests")
            << "foo bar\nbaz"
            << TS()
            << "in:1: Extra characters after test expression."
            << false;

    QTest::newRow("test-AND-test")
            << "foo:bar"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokAnd)
    /*    11 */ << H(TokHashLiteral) << HS(L"bar")
    /*    18 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("test-OR-test")
            << "  foo  | bar "
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokOr)
    /*    11 */ << H(TokHashLiteral) << HS(L"bar")
    /*    18 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("NOT-test")
            << "!foo"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokNot)
    /*     3 */ << H(TokHashLiteral) << HS(L"foo")
    /*    10 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("NOT-NOT-test")
            << "!!foo"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition))
            << ""
            << true;

    // This is a rather questionable "feature"
    QTest::newRow("quoted-NOT-test")
            << "\"!foo\""
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokNot)
    /*     3 */ << H(TokHashLiteral) << HS(L"foo")
    /*    10 */ << H(TokCondition))
            << ""
            << true;

    // This is a rather questionable "feature"
    QTest::newRow("NOT-quoted-test")
            << "!\"foo\""
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokNot)
    /*     3 */ << H(TokHashLiteral) << HS(L"foo")
    /*    10 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("test-AND-NOT-test")
            << "foo:!bar"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokAnd)
    /*    11 */ << H(TokNot)
    /*    12 */ << H(TokHashLiteral) << HS(L"bar")
    /*    19 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("test-assignment")
            << "foo\nVAR="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokLine) << H(2)
    /*    12 */ << H(TokHashLiteral) << HS(L"VAR")
    /*    19 */ << H(TokAssign) << H(0)
    /*    21 */ << H(TokValueTerminator))
            << ""
            << true;

    QTest::newRow("test-AND-assignment")
            << "foo: VAR ="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokBranch)
    /*    11 */ /* then branch */ << I(11)
    /*    13 */     << H(TokHashLiteral) << HS(L"VAR")
    /*    20 */     << H(TokAssign) << H(0)
    /*    22 */     << H(TokValueTerminator)
    /*    23 */     << H(TokTerminator)
    /*    24 */ /* else branch */ << I(0))
            << ""
            << true;

    QTest::newRow("test-else-test")
            << "foo\nelse: bar"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokBranch)
    /*    11 */ /* then branch */ << I(0)
    /*    13 */ /* else branch */ << I(11)
    /*    15 */     << H(TokLine) << H(2)
    /*    17 */     << H(TokHashLiteral) << HS(L"bar")
    /*    24 */     << H(TokCondition)
    /*    25 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("function-else-test")
            << "foo()\nelse: bar"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokTestCall)
    /*    10 */     << H(TokFuncTerminator)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(0)
    /*    14 */ /* else branch */ << I(11)
    /*    16 */     << H(TokLine) << H(2)
    /*    18 */     << H(TokHashLiteral) << HS(L"bar")
    /*    25 */     << H(TokCondition)
    /*    26 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("test-AND-test-else-test")
            << "foo:bar\nelse: baz"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokAnd)
    /*    11 */ << H(TokHashLiteral) << HS(L"bar")
    /*    18 */ << H(TokCondition)
    /*    19 */ << H(TokBranch)
    /*    20 */ /* then branch */ << I(0)
    /*    22 */ /* else branch */ << I(11)
    /*    24 */     << H(TokLine) << H(2)
    /*    26 */     << H(TokHashLiteral) << HS(L"baz")
    /*    33 */     << H(TokCondition)
    /*    34 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("test-AND-test-else-test-else-test-function")
            << "foo:bar\nelse: baz\nelse: bak\nbuzz()"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokAnd)
    /*    11 */ << H(TokHashLiteral) << HS(L"bar")
    /*    18 */ << H(TokCondition)
    /*    19 */ << H(TokBranch)
    /*    20 */ /* then branch */ << I(0)
    /*    22 */ /* else branch */ << I(27)
    /*    24 */     << H(TokLine) << H(2)
    /*    26 */     << H(TokHashLiteral) << HS(L"baz")
    /*    33 */     << H(TokCondition)
    /*    34 */     << H(TokBranch)
    /*    35 */     /* then branch */ << I(0)
    /*    37 */     /* else branch */ << I(11)
    /*    39 */         << H(TokLine) << H(3)
    /*    41 */         << H(TokHashLiteral) << HS(L"bak")
    /*    48 */         << H(TokCondition)
    /*    49 */         << H(TokTerminator)
    /*    50 */     << H(TokTerminator)
    /*    51 */ << H(TokLine) << H(4)
    /*    53 */ << H(TokHashLiteral) << HS(L"buzz")
    /*    61 */ << H(TokTestCall)
    /*    62 */     << H(TokFuncTerminator))
            << ""
            << true;

    QTest::newRow("test-assignment-else-assignment")
            << "foo: VAR =\nelse: VAR="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokBranch)
    /*    11 */ /* then branch */ << I(11)
    /*    13 */     << H(TokHashLiteral) << HS(L"VAR")
    /*    20 */     << H(TokAssign) << H(0)
    /*    22 */     << H(TokValueTerminator)
    /*    23 */     << H(TokTerminator)
    /*    24 */ /* else branch */ << I(13)
    /*    26 */     << H(TokLine) << H(2)
    /*    28 */     << H(TokHashLiteral) << HS(L"VAR")
    /*    35 */     << H(TokAssign) << H(0)
    /*    37 */     << H(TokValueTerminator)
    /*    38 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("test-else-test-assignment")
            << "foo\nelse: bar: VAR ="
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokBranch)
    /*    11 */ /* then branch */ << I(0)
    /*    13 */ /* else branch */ << I(27)
    /*    15 */     << H(TokLine) << H(2)
    /*    17 */     << H(TokHashLiteral) << HS(L"bar")
    /*    24 */     << H(TokCondition)
    /*    25 */     << H(TokBranch)
    /*    26 */     /* then branch */ << I(11)
    /*    28 */         << H(TokHashLiteral) << HS(L"VAR")
    /*    35 */         << H(TokAssign) << H(0)
    /*    37 */         << H(TokValueTerminator)
    /*    38 */         << H(TokTerminator)
    /*    39 */     /* else branch */ << I(0)
    /*    41 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("one function")
            << "foo()"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokTestCall)
    /*    10 */     << H(TokFuncTerminator))
            << ""
            << true;

    QTest::newRow("one function (with spaces)")
            << " foo(  ) "
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokTestCall)
    /*    10 */     << H(TokFuncTerminator))
            << ""
            << true;

    QTest::newRow("unterminated function call")
            << "foo(\nfoo"
            << TS()
            << "in:1: Missing closing parenthesis in function call"
            << false;

    QTest::newRow("function with arguments")
            << "foo(blah, hi ho)"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokTestCall)
    /*    10 */     << H(TokLiteral | TokNewStr) << S(L"blah")
    /*    16 */     << H(TokArgSeparator)
    /*    17 */     << H(TokLiteral | TokNewStr) << S(L"hi")
    /*    21 */     << H(TokLiteral | TokNewStr) << S(L"ho")
    /*    25 */     << H(TokFuncTerminator))
            << ""
            << true;

    QTest::newRow("function with empty arguments")
            << "foo(,)"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokTestCall)
    /*    10 */     << H(TokArgSeparator)
    /*    11 */     << H(TokFuncTerminator))
            << ""
            << true;

    QTest::newRow("function with funny arguments")
            << "foo(blah\\, \"hi ,  \\ho\" ,uh\\  ,\\oh  ,,   )"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokTestCall)
    /*    10 */     << H(TokLiteral | TokNewStr) << S(L"blah\\")
    /*    17 */     << H(TokArgSeparator)
    /*    18 */     << H(TokLiteral | TokNewStr) << S(L"hi ,  \\ho")
    /*    29 */     << H(TokArgSeparator)
    /*    30 */     << H(TokLiteral | TokNewStr) << S(L"uh\\")
    /*    35 */     << H(TokArgSeparator)
    /*    36 */     << H(TokLiteral | TokNewStr) << S(L"\\oh")
    /*    41 */     << H(TokArgSeparator)
    /*    42 */     << H(TokArgSeparator)
    /*    43 */     << H(TokFuncTerminator))
            << "WARNING: in:1: Unescaped backslashes are deprecated\n"
               "WARNING: in:1: Unescaped backslashes are deprecated\n"
               "WARNING: in:1: Unescaped backslashes are deprecated\n"
               "WARNING: in:1: Unescaped backslashes are deprecated"
            << true;

    QTest::newRow("function with nested call")
            << "foo($$blah(hi ho))"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokTestCall)
    /*    10 */     << H(TokFuncName | TokNewStr) << HS(L"blah")
    /*    18 */         << H(TokLiteral | TokNewStr) << S(L"hi")
    /*    22 */         << H(TokLiteral | TokNewStr) << S(L"ho")
    /*    26 */         << H(TokFuncTerminator)
    /*    27 */     << H(TokFuncTerminator))
            << ""
            << true;

    QTest::newRow("stand-alone parentheses")
            << "()"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestCall)
    /*     3 */     << H(TokFuncTerminator))
            << "in:1: Opening parenthesis without prior test name."
            << false;

    QTest::newRow("bogus test and function")
            << "foo bar()"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestCall)
    /*     3 */     << H(TokFuncTerminator))
            << "in:1: Extra characters after test expression."
            << false;

    // This is a rather questionable "feature"
    QTest::newRow("two functions")
            << "foo() bar()"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokTestCall)
    /*    10 */     << H(TokFuncTerminator)
    /*    11 */ << H(TokHashLiteral) << HS(L"bar")
    /*    18 */ << H(TokTestCall)
    /*    19 */     << H(TokFuncTerminator))
            << ""
            << true;

    QTest::newRow("function-AND-test")
            << "foo():bar"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokTestCall)
    /*    10 */     << H(TokFuncTerminator)
    /*    11 */ << H(TokAnd)
    /*    12 */ << H(TokHashLiteral) << HS(L"bar")
    /*    19 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("test-AND-function")
            << "foo:bar()"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokAnd)
    /*    11 */ << H(TokHashLiteral) << HS(L"bar")
    /*    18 */ << H(TokTestCall)
    /*    19 */     << H(TokFuncTerminator))
            << ""
            << true;

    QTest::newRow("NOT-function-AND-test")
            << "!foo():bar"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokNot)
    /*     3 */ << H(TokHashLiteral) << HS(L"foo")
    /*    10 */ << H(TokTestCall)
    /*    11 */     << H(TokFuncTerminator)
    /*    12 */ << H(TokAnd)
    /*    13 */ << H(TokHashLiteral) << HS(L"bar")
    /*    20 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("test-AND-NOT-function")
            << "foo:!bar()"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"foo")
    /*     9 */ << H(TokCondition)
    /*    10 */ << H(TokAnd)
    /*    11 */ << H(TokNot)
    /*    12 */ << H(TokHashLiteral) << HS(L"bar")
    /*    19 */ << H(TokTestCall)
    /*    20 */     << H(TokFuncTerminator))
            << ""
            << true;
}

void tst_qmakelib::addParseControlStatements()
{
    QTest::newRow("for(VAR, LIST) loop")
            << "for(VAR, LIST)"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokForLoop) << HS(L"VAR")
    /*     9 */ /* iterator */ << I(7)
    /*    11 */     << H(TokLiteral | TokNewStr) << S(L"LIST")
    /*    17 */     << H(TokValueTerminator)
    /*    18 */ /* body */ << I(1)
    /*    20 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("for(ever) loop")
            << "for(ever)"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokForLoop) << HS(L"")
    /*     6 */ /* iterator */ << I(9)
    /*     8 */     << H(TokHashLiteral) << HS(L"ever")
    /*    16 */     << H(TokValueTerminator)
    /*    17 */ /* body */ << I(1)
    /*    19 */     << H(TokTerminator))
            << ""
            << true;

    // This is a rather questionable "feature"
    QTest::newRow("for($$blub) loop")
            << "for($$blub)"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokForLoop) << HS(L"")
    /*     6 */ /* iterator */ << I(9)
    /*     8 */     << H(TokVariable | TokNewStr) << HS(L"blub")
    /*    16 */     << H(TokValueTerminator)
    /*    17 */ /* body */ << I(1)
    /*    19 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("test-for-test-else-test")
            << "true:for(VAR, LIST): true\nelse: true"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(31)
    /*    14 */     << H(TokForLoop) << HS(L"VAR")
    /*    21 */     /* iterator */ << I(7)
    /*    23 */         << H(TokLiteral | TokNewStr) << S(L"LIST")
    /*    29 */         << H(TokValueTerminator)
    /*    30 */     /* body */ << I(12)
    /*    32 */         << H(TokLine) << H(1)
    /*    34 */         << H(TokHashLiteral) << HS(L"true")
    /*    42 */         << H(TokCondition)
    /*    43 */         << H(TokTerminator)
    /*    44 */     << H(TokTerminator)
    /*    45 */ /* else branch */ << I(12)
    /*    47 */     << H(TokLine) << H(2)
    /*    49 */     << H(TokHashLiteral) << HS(L"true")
    /*    57 */     << H(TokCondition)
    /*    58 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("next()")
            << "for(ever): next()"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokForLoop) << HS(L"")
    /*     6 */ /* iterator */ << I(9)
    /*     8 */     << H(TokHashLiteral) << HS(L"ever")
    /*    16 */     << H(TokValueTerminator)
    /*    17 */ /* body */ << I(4)
    /*    19 */     << H(TokLine) << H(1)
    /*    21 */     << H(TokNext)
    /*    22 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("break()")
            << "for(ever): break()"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokForLoop) << HS(L"")
    /*     6 */ /* iterator */ << I(9)
    /*     8 */     << H(TokHashLiteral) << HS(L"ever")
    /*    16 */     << H(TokValueTerminator)
    /*    17 */ /* body */ << I(4)
    /*    19 */     << H(TokLine) << H(1)
    /*    21 */     << H(TokBreak)
    /*    22 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("top-level return()")
            << "return()"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokReturn))
            << ""
            << true;

    QTest::newRow("else")
            << "else"
            << TS()
            << "in:1: Unexpected 'else'."
            << false;

    QTest::newRow("test-{else}")
            << "test { else }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(1)
    /*    14 */     << H(TokTerminator)
    /*    15 */ /* else branch */ << I(0))
            << "in:1: Unexpected 'else'."
            << false;

    QTest::newRow("defineTest-{else}")
            << "defineTest(fn) { else }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestDef) << HS(L"fn")
    /*     8 */ /* body */ << I(1)
    /*    10 */     << H(TokTerminator))
            << "in:1: Unexpected 'else'."
            << false;

    QTest::newRow("for-else")
            << "for(ever) { else }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokForLoop) << HS(L"")
    /*     6 */ /* iterator */ << I(9)
    /*     8 */     << H(TokHashLiteral) << HS(L"ever")
    /*    16 */     << H(TokValueTerminator)
    /*    17 */ /* body */ << I(1)
    /*    19 */     << H(TokTerminator))
            << "in:1: Unexpected 'else'."
            << false;

    QTest::newRow("double-test-else")
            << "foo bar\nelse"
            << TS(
    /*     0 */ << H(TokBranch)
    /*     1 */ /* then branch */ << I(0)
    /*     3 */ /* else branch */ << I(1)  // This seems weird
    /*     5 */     << H(TokTerminator))
            << "in:1: Extra characters after test expression."
            << false;

    QTest::newRow("test-function-else")
            << "foo bar()\nelse"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestCall)  // This seems pointless
    /*     3 */     << H(TokFuncTerminator)
    /*     4 */ << H(TokBranch)
    /*     5 */ /* then branch */ << I(0)
    /*     7 */ /* else branch */ << I(1)  // This seems weird
    /*     9 */     << H(TokTerminator))
            << "in:1: Extra characters after test expression."
            << false;
}

void tst_qmakelib::addParseBraces()
{
    QTest::newRow("{}")
            << "{ }"
            << TS()
            << ""
            << true;

    QTest::newRow("{}-newlines")
            << "\n\n{ }\n\n"
            << TS()
            << ""
            << true;

    QTest::newRow("{")
            << "{"
            << TS()
            << "in:2: Missing closing brace(s)."
            << false;

    QTest::newRow("test {")
            << "test {"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(1)
    /*    14 */     << H(TokTerminator)
    /*    15 */ /* else branch */ << I(0))
            << "in:2: Missing closing brace(s)."
            << false;

    QTest::newRow("}")
            << "}"
            << TS()
            << "in:1: Excess closing brace."
            << false;

    QTest::newRow("{test}")
            << "{ true }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("{test-newlines}")
            << "{\ntrue\n}"
            << TS(
    /*     0 */ << H(TokLine) << H(2)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("{assignment-test}-test")
            << "{ VAR = { foo } bar } true"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokAssign) << H(4)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"{")
    /*    14 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    19 */ << H(TokLiteral | TokNewStr) << S(L"}")
    /*    22 */ << H(TokLiteral | TokNewStr) << S(L"bar")
    /*    27 */ << H(TokValueTerminator)
    /*    28 */ << H(TokHashLiteral) << HS(L"true")
    /*    36 */ << H(TokCondition))
            << ""
            << true;

    QTest::newRow("assignment with excess opening brace")
            << "VAR = { { foo }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"VAR")
    /*     9 */ << H(TokAssign) << H(4)
    /*    11 */ << H(TokLiteral | TokNewStr) << S(L"{")
    /*    14 */ << H(TokLiteral | TokNewStr) << S(L"{")
    /*    17 */ << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    22 */ << H(TokLiteral | TokNewStr) << S(L"}")
    /*    25 */ << H(TokValueTerminator))
            << "WARNING: in:1: Possible braces mismatch"
            << true;

    QTest::newRow("test-{}")
            << "true {}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(1)
    /*    14 */     << H(TokTerminator)
    /*    15 */ /* else branch */ << I(0))
            << ""
            << true;

    QTest::newRow("test-{newlines}")
            << "true {\n}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(1)
    /*    14 */     << H(TokTerminator)
    /*    15 */ /* else branch */ << I(0))
            << ""
            << true;

    QTest::newRow("test-{test}")
            << "true { true }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(10)
    /*    14 */     << H(TokHashLiteral) << HS(L"true")
    /*    22 */     << H(TokCondition)
    /*    23 */     << H(TokTerminator)
    /*    24 */ /* else branch */ << I(0))
            << ""
            << true;

    QTest::newRow("test:-{test}")
            << "true: { true }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(10)
    /*    14 */     << H(TokHashLiteral) << HS(L"true")
    /*    22 */     << H(TokCondition)
    /*    23 */     << H(TokTerminator)
    /*    24 */ /* else branch */ << I(0))
            << "WARNING: in:1: Excess colon in front of opening brace."
            << true;

    QTest::newRow("test-{test-newlines}")
            << "true {\ntrue\n}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(12)
    /*    14 */     << H(TokLine) << H(2)
    /*    16 */     << H(TokHashLiteral) << HS(L"true")
    /*    24 */     << H(TokCondition)
    /*    25 */     << H(TokTerminator)
    /*    26 */ /* else branch */ << I(0))
            << ""
            << true;

    QTest::newRow("test:-{test-newlines}")
            << "true: {\ntrue\n}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(12)
    /*    14 */     << H(TokLine) << H(2)
    /*    16 */     << H(TokHashLiteral) << HS(L"true")
    /*    24 */     << H(TokCondition)
    /*    25 */     << H(TokTerminator)
    /*    26 */ /* else branch */ << I(0))
            << "WARNING: in:1: Excess colon in front of opening brace."
            << true;

    QTest::newRow("test-{assignment}")
            << "true { VAR = {foo} }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(18)
    /*    14 */     << H(TokHashLiteral) << HS(L"VAR")
    /*    21 */     << H(TokAssign) << H(0)
    /*    23 */     << H(TokLiteral | TokNewStr) << S(L"{foo}")
    /*    30 */     << H(TokValueTerminator)
    /*    31 */     << H(TokTerminator)
    /*    32 */ /* else branch */ << I(0))
            << ""
            << true;

    QTest::newRow("test-{test-assignment}")
            << "true { true: VAR = {foo} }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(33)
    /*    14 */     << H(TokHashLiteral) << HS(L"true")
    /*    22 */     << H(TokCondition)
    /*    23 */     << H(TokBranch)
    /*    24 */     /* then branch */ << I(18)
    /*    26 */         << H(TokHashLiteral) << HS(L"VAR")
    /*    33 */         << H(TokAssign) << H(0)
    /*    35 */         << H(TokLiteral | TokNewStr) << S(L"{foo}")
    /*    42 */         << H(TokValueTerminator)
    /*    43 */         << H(TokTerminator)
    /*    44 */     /* else branch */ << I(0)
    /*    46 */     << H(TokTerminator)
    /*    47 */ /* else branch */ << I(0))
            << ""
            << true;

    QTest::newRow("test-{assignment-newlines}")
            << "true {\nVAR = {foo}\n}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(20)
    /*    14 */     << H(TokLine) << H(2)
    /*    16 */     << H(TokHashLiteral) << HS(L"VAR")
    /*    23 */     << H(TokAssign) << H(0)
    /*    25 */     << H(TokLiteral | TokNewStr) << S(L"{foo}")
    /*    32 */     << H(TokValueTerminator)
    /*    33 */     << H(TokTerminator)
    /*    34 */ /* else branch */ << I(0))
            << ""
            << true;

    QTest::newRow("test-{}-else-test-{}")
            << "true {} else: true {}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(1)
    /*    14 */     << H(TokTerminator)
    /*    15 */ /* else branch */ << I(18)
    /*    17 */     << H(TokLine) << H(1)
    /*    19 */     << H(TokHashLiteral) << HS(L"true")
    /*    27 */     << H(TokCondition)
    /*    28 */     << H(TokBranch)
    /*    29 */     /* then branch */ << I(1)
    /*    31 */         << H(TokTerminator)
    /*    32 */     /* else branch */ << I(0)
    /*    34 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("test-{}-else-test-{}-newlines")
            << "true {\n}\nelse: true {\n}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(1)
    /*    14 */     << H(TokTerminator)
    /*    15 */ /* else branch */ << I(18)
    /*    17 */     << H(TokLine) << H(3)
    /*    19 */     << H(TokHashLiteral) << HS(L"true")
    /*    27 */     << H(TokCondition)
    /*    28 */     << H(TokBranch)
    /*    29 */     /* then branch */ << I(1)
    /*    31 */         << H(TokTerminator)
    /*    32 */     /* else branch */ << I(0)
    /*    34 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("test-{test}-else-test-{}-newlines")
            << "true {\ntrue\n}\nelse: true {\n}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(12)
    /*    14 */     << H(TokLine) << H(2)
    /*    16 */     << H(TokHashLiteral) << HS(L"true")
    /*    24 */     << H(TokCondition)
    /*    25 */     << H(TokTerminator)
    /*    26 */ /* else branch */ << I(18)
    /*    28 */     << H(TokLine) << H(4)
    /*    30 */     << H(TokHashLiteral) << HS(L"true")
    /*    38 */     << H(TokCondition)
    /*    39 */     << H(TokBranch)
    /*    40 */     /* then branch */ << I(1)
    /*    42 */         << H(TokTerminator)
    /*    43 */     /* else branch */ << I(0)
    /*    45 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("for-{next}")
            << "for(ever) { next() }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokForLoop) << HS(L"")
    /*     6 */ /* iterator */ << I(9)
    /*     8 */     << H(TokHashLiteral) << HS(L"ever")
    /*    16 */     << H(TokValueTerminator)
    /*    17 */ /* body */ << I(4)
    /*    19 */     << H(TokLine) << H(1)
    /*    21 */     << H(TokNext)
    /*    22 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("for:-{next}")
            << "for(ever): { next() }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokForLoop) << HS(L"")
    /*     6 */ /* iterator */ << I(9)
    /*     8 */     << H(TokHashLiteral) << HS(L"ever")
    /*    16 */     << H(TokValueTerminator)
    /*    17 */ /* body */ << I(4)
    /*    19 */     << H(TokLine) << H(1)
    /*    21 */     << H(TokNext)
    /*    22 */     << H(TokTerminator))
            << "WARNING: in:1: Excess colon in front of opening brace."
            << true;

    QTest::newRow("test-for-{test-else-test-newlines}")
            << "true:for(VAR, LIST) {\ntrue\nelse: true\n}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(48)
    /*    14 */     << H(TokForLoop) << HS(L"VAR")
    /*    21 */     /* iterator */ << I(7)
    /*    23 */         << H(TokLiteral | TokNewStr) << S(L"LIST")
    /*    29 */         << H(TokValueTerminator)
    /*    30 */     /* body */ << I(29)
    /*    32 */         << H(TokLine) << H(2)
    /*    34 */         << H(TokHashLiteral) << HS(L"true")
    /*    42 */         << H(TokCondition)
    /*    43 */         << H(TokBranch)
    /*    44 */         /* then branch */ << I(0)
    /*    46 */         /* else branch */ << I(12)
    /*    48 */             << H(TokLine) << H(3)
    /*    50 */             << H(TokHashLiteral) << HS(L"true")
    /*    58 */             << H(TokCondition)
    /*    59 */             << H(TokTerminator)
    /*    60 */         << H(TokTerminator)
    /*    61 */     << H(TokTerminator)
    /*    62 */ /* else branch */ << I(0))
            << ""
            << true;

    QTest::newRow("test-for-{test-else-test}")
            << "true:for(VAR, LIST) { true\nelse: true }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"true")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(48)
    /*    14 */     << H(TokForLoop) << HS(L"VAR")
    /*    21 */     /* iterator */ << I(7)
    /*    23 */         << H(TokLiteral | TokNewStr) << S(L"LIST")
    /*    29 */         << H(TokValueTerminator)
    /*    30 */     /* body */ << I(29)
    /*    32 */         << H(TokLine) << H(1)
    /*    34 */         << H(TokHashLiteral) << HS(L"true")
    /*    42 */         << H(TokCondition)
    /*    43 */         << H(TokBranch)
    /*    44 */         /* then branch */ << I(0)
    /*    46 */         /* else branch */ << I(12)
    /*    48 */             << H(TokLine) << H(2)
    /*    50 */             << H(TokHashLiteral) << HS(L"true")
    /*    58 */             << H(TokCondition)
    /*    59 */             << H(TokTerminator)
    /*    60 */         << H(TokTerminator)
    /*    61 */     << H(TokTerminator)
    /*    62 */ /* else branch */ << I(0))
            << ""
            << true;
}

void tst_qmakelib::addParseCustomFunctions()
{
    QTest::newRow("defineTest-{newlines}")
            << "defineTest(test) {\n}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestDef) << HS(L"test")
    /*    10 */ /* body */ << I(1)
    /*    12 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("defineTest:-test")
            << "defineTest(test): test"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestDef) << HS(L"test")
    /*    10 */ /* body */ << I(12)
    /*    12 */     << H(TokLine) << H(1)
    /*    14 */     << H(TokHashLiteral) << HS(L"test")
    /*    22 */     << H(TokCondition)
    /*    23 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("defineTest-{test}")
            << "defineTest(test) { test }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestDef) << HS(L"test")
    /*    10 */ /* body */ << I(12)
    /*    12 */     << H(TokLine) << H(1)
    /*    14 */     << H(TokHashLiteral) << HS(L"test")
    /*    22 */     << H(TokCondition)
    /*    23 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("defineTest-{return}")
            << "defineTest(test) { return() }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestDef) << HS(L"test")
    /*    10 */ /* body */ << I(4)
    /*    12 */     << H(TokLine) << H(1)
    /*    14 */     << H(TokReturn)
    /*    15 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("defineReplace-{return-stuff}")
            << "defineReplace(stuff) { return(foo bar) }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokReplaceDef) << HS(L"stuff")
    /*    11 */ /* body */ << I(14)
    /*    13 */     << H(TokLine) << H(1)
    /*    15 */     << H(TokLiteral | TokNewStr) << S(L"foo")
    /*    20 */     << H(TokLiteral | TokNewStr) << S(L"bar")
    /*    25 */     << H(TokReturn)
    /*    26 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("test-AND-defineTest-{}")
            << "test: defineTest(test) {}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokAnd)
    /*    12 */ << H(TokTestDef) << HS(L"test")
    /*    20 */ /* body */ << I(1)
    /*    22 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("test-OR-defineTest-{}")
            << "test| defineTest(test) {}"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokOr)
    /*    12 */ << H(TokTestDef) << HS(L"test")
    /*    20 */ /* body */ << I(1)
    /*    22 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("bypassNesting()-{return}")
            << "defineTest(test) { bypassNesting() { return(true) } }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestDef) << HS(L"test")
    /*    10 */ /* body */ << I(16)
    /*    12 */     << H(TokLine) << H(1)
    /*    14 */     << H(TokBypassNesting)
    /*    15 */     /* block */ << I(10)
    /*    17 */         << H(TokLine) << H(1)
    /*    19 */         << H(TokLiteral | TokNewStr) << S(L"true")
    /*    25 */         << H(TokReturn)
    /*    26 */         << H(TokTerminator)
    /*    27 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("test-AND-bypassNesting()-{}")
            << "defineTest(test) { test: bypassNesting() {} }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestDef) << HS(L"test")
    /*    10 */ /* body */ << I(17)
    /*    12 */     << H(TokLine) << H(1)
    /*    14 */     << H(TokHashLiteral) << HS(L"test")
    /*    22 */     << H(TokCondition)
    /*    23 */     << H(TokAnd)
    /*    24 */     << H(TokBypassNesting)
    /*    25 */     /* block */ << I(1)
    /*    27 */         << H(TokTerminator)
    /*    28 */     << H(TokTerminator))
            << ""
            << true;

    QTest::newRow("test-OR-bypassNesting()-{}")
            << "defineTest(test) { test| bypassNesting() {} }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokTestDef) << HS(L"test")
    /*    10 */ /* body */ << I(17)
    /*    12 */     << H(TokLine) << H(1)
    /*    14 */     << H(TokHashLiteral) << HS(L"test")
    /*    22 */     << H(TokCondition)
    /*    23 */     << H(TokOr)
    /*    24 */     << H(TokBypassNesting)
    /*    25 */     /* block */ << I(1)
    /*    27 */         << H(TokTerminator)
    /*    28 */     << H(TokTerminator))
            << ""
            << true;
}

void tst_qmakelib::addParseAbuse()
{
    QTest::newRow("!")
            << ""
            << TS()
            << ""
            << true;

    QTest::newRow("|")
            << ""
            << TS()
            << ""
            << true;

    QTest::newRow(":")
            << ""
            << TS()
            << ""
            << true;

    QTest::newRow("NOT-assignment")
            << "!VAR ="
            << TS()
            << "in:1: Unexpected NOT operator in front of assignment."
            << false;

    QTest::newRow("NOT-{}")
            << "!{}"
            << TS()
            << "in:1: Unexpected NOT operator in front of opening brace."
            << false;

    QTest::newRow("NOT-else")
            << "test\n!else {}"
            << TS()
            << "in:2: Unexpected NOT operator in front of else."
            << false;

    QTest::newRow("NOT-for-{}")
            << "!for(ever) {}"
            << TS()
            << "in:1: Unexpected NOT operator in front of for()."
            << false;

    QTest::newRow("NOT-defineTest-{}")
            << "!defineTest(test) {}"
            << TS()
            << "in:1: Unexpected NOT operator in front of function definition."
            << false;

    QTest::newRow("outer-bypassNesting()-{}")
            << "bypassNesting() {}"
            << TS()
            << "in:1: Unexpected bypassNesting()."
            << false;

    QTest::newRow("bypassNesting(arg)-{}")
            << "defineTest(test) { bypassNesting(arg) {} }"
            << TS()
            << "in:1: bypassNesting() requires zero arguments."
            << false;

    QTest::newRow("NOT-bypassNesting()-{}")
            << "defineTest(test) { !bypassNesting() {} }"
            << TS()
            << "in:1: Unexpected NOT operator in front of bypassNesting()."
            << false;

    QTest::newRow("AND-test")
            << ":test"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition))
            << "in:1: AND operator without prior condition."
            << false;

    QTest::newRow("test-AND-else")
            << "test:else"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition))
            << "in:1: Unexpected AND operator in front of else."
            << false;

    QTest::newRow("test-AND-AND-test")
            << "test::test"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokAnd)
    /*    12 */ << H(TokHashLiteral) << HS(L"test")
    /*    20 */ << H(TokCondition))
            << "WARNING: in:1: Stray AND operator in front of AND operator."
            << true;

    QTest::newRow("test-AND-OR-test")
            << "test:|test"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokOr)
    /*    12 */ << H(TokHashLiteral) << HS(L"test")
    /*    20 */ << H(TokCondition))
            << "WARNING: in:1: Stray AND operator in front of OR operator."
            << true;

    QTest::newRow("test-{AND-test}")
            << "test { :test }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(10)
    /*    14 */     << H(TokHashLiteral) << HS(L"test")
    /*    22 */     << H(TokCondition)
    /*    23 */     << H(TokTerminator)
    /*    24 */ /* else branch */ << I(0))
            << "in:1: AND operator without prior condition."
            << false;

    QTest::newRow("test-OR-assignment")
            << "foo| VAR ="
            << TS()
            << "in:1: Unexpected OR operator in front of assignment."
            << false;

    QTest::newRow("test-OR-{}")
            << "foo|{}"
            << TS()
            << "in:1: Unexpected OR operator in front of opening brace."
            << false;

    QTest::newRow("test-OR-for")
            << "foo|for(ever) {}"
            << TS()
            << "in:1: Unexpected OR operator in front of for()."
            << false;

    QTest::newRow("OR-test")
            << "|test"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition))
            << "in:1: OR operator without prior condition."
            << false;

    QTest::newRow("test-OR-else")
            << "test|else"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition))
            << "in:1: Unexpected OR operator in front of else."
            << false;

    QTest::newRow("test-OR-OR-test")
            << "test||test"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokOr)
    /*    12 */ << H(TokHashLiteral) << HS(L"test")
    /*    20 */ << H(TokCondition))
            << "WARNING: in:1: Stray OR operator in front of OR operator."
            << true;

    QTest::newRow("test-OR-AND-test")
            << "test|:test"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokAnd)
    /*    12 */ << H(TokHashLiteral) << HS(L"test")
    /*    20 */ << H(TokCondition))
            << "WARNING: in:1: Stray OR operator in front of AND operator."
            << true;

    QTest::newRow("test-{OR-test}")
            << "test { |test }"
            << TS(
    /*     0 */ << H(TokLine) << H(1)
    /*     2 */ << H(TokHashLiteral) << HS(L"test")
    /*    10 */ << H(TokCondition)
    /*    11 */ << H(TokBranch)
    /*    12 */ /* then branch */ << I(10)
    /*    14 */     << H(TokHashLiteral) << HS(L"test")
    /*    22 */     << H(TokCondition)
    /*    23 */     << H(TokTerminator)
    /*    24 */ /* else branch */ << I(0))
            << "in:1: OR operator without prior condition."
            << false;

    // Token buffer overflow. Verify with Valgrind or asan.
    QTest::newRow("QTCREATORBUG-16508")
            << "a{b{c{d{"
            << TS()
            << "in:2: Missing closing brace(s)."
            << false;
}

void tst_qmakelib::proParser_data()
{
    QTest::addColumn<QString>("in");
    QTest::addColumn<QString>("out");
    QTest::addColumn<QString>("msgs");
    QTest::addColumn<bool>("ok");

    QTest::newRow("empty")
            << ""
            << TS()
            << ""
            << true;

    QTest::newRow("empty (whitespace)")
            << "  \t   \t"
            << TS()
            << ""
            << true;

    addParseOperators();  // Variable operators

    addParseValues();

    addParseConditions();  // "Tests"

    addParseControlStatements();

    addParseBraces();

    addParseCustomFunctions();

    addParseAbuse();  // Mostly operator abuse

    // option() (these produce no tokens)

    QTest::newRow("option(host_build)")
            << "option(host_build)"
            << TS()
            << ""
            << true;

    QTest::newRow("option()")
            << "option()"
            << TS()
            << "in:1: option() requires one literal argument."
            << false;

    QTest::newRow("option(host_build magic)")
            << "option(host_build magic)"
            << TS()
            << "in:1: option() requires one literal argument."
            << false;

    QTest::newRow("option(host_build, magic)")
            << "option(host_build, magic)"
            << TS()
            << "in:1: option() requires one literal argument."
            << false;

    QTest::newRow("option($$OPTION)")
            << "option($$OPTION)"
            << TS()
            << "in:1: option() requires one literal argument."
            << false;

    QTest::newRow("{option(host_build)}")
            << "{option(host_build)}"
            << TS()
            << "in:1: option() must appear outside any control structures."
            << false;
}

QT_WARNING_POP

void tst_qmakelib::proParser()
{
    QFETCH(QString, in);
    QFETCH(QString, out);
    QFETCH(QString, msgs);
    QFETCH(bool, ok);

    bool verified = true;
    QMakeTestHandler handler;
    handler.setExpectedMessages(msgs.split('\n', QString::SkipEmptyParts));
    QMakeVfs vfs;
    QMakeParser parser(0, &vfs, &handler);
    ProFile *pro = parser.parsedProBlock(QStringRef(&in), "in", 1, QMakeParser::FullGrammar);
    if (handler.printedMessages()) {
        qWarning("Got unexpected message(s)");
        verified = false;
    }
    QStringList missingMsgs = handler.expectedMessages();
    if (!missingMsgs.isEmpty()) {
        foreach (const QString &msg, missingMsgs)
            qWarning("Missing message: %s", qPrintable(msg));
        verified = false;
    }
    if (pro->isOk() != ok) {
        static const char * const lbl[] = { "failure", "success" };
        qWarning("Expected %s, got %s", lbl[int(ok)], lbl[1 - int(ok)]);
        verified = false;
    }
    if (pro->items() != out && (ok || !out.isEmpty())) {
        qWarning("Bytecode mismatch.\nActual:%s\nExpected:%s",
                 qPrintable(QMakeParser::formatProBlock(pro->items())),
                 qPrintable(QMakeParser::formatProBlock(out)));
        verified = false;
    }
    pro->deref();
    QVERIFY(verified);
}
