/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Linguist of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qmakeevaluator.h"

#include "qmakeglobals.h"
#include "qmakeparser.h"
#include "qmakeevaluator_p.h"
#include "ioutils.h"

#include <qbytearray.h>
#include <qdatetime.h>
#include <qdebug.h>
#include <qdir.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qlist.h>
#include <qregexp.h>
#include <qset.h>
#include <qstack.h>
#include <qstring.h>
#include <qstringlist.h>
#ifdef PROEVALUATOR_THREAD_SAFE
# include <qthreadpool.h>
#endif

#ifdef Q_OS_UNIX
#include <unistd.h>
#include <sys/utsname.h>
#else
#include <windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>

using namespace QMakeInternal;

QT_BEGIN_NAMESPACE

#define fL1S(s) QString::fromLatin1(s)


QMakeBaseKey::QMakeBaseKey(const QString &_root, bool _hostBuild)
    : root(_root), hostBuild(_hostBuild)
{
}

uint qHash(const QMakeBaseKey &key)
{
    return qHash(key.root) ^ (uint)key.hostBuild;
}

bool operator==(const QMakeBaseKey &one, const QMakeBaseKey &two)
{
    return one.root == two.root && one.hostBuild == two.hostBuild;
}

QMakeBaseEnv::QMakeBaseEnv()
    : evaluator(0)
{
#ifdef PROEVALUATOR_THREAD_SAFE
    inProgress = false;
#endif
}

QMakeBaseEnv::~QMakeBaseEnv()
{
    delete evaluator;
}

namespace QMakeInternal {
QMakeStatics statics;
}

void QMakeEvaluator::initStatics()
{
    if (!statics.field_sep.isNull())
        return;

    statics.field_sep = QLatin1String(" ");
    statics.strtrue = QLatin1String("true");
    statics.strfalse = QLatin1String("false");
    statics.strCONFIG = ProKey("CONFIG");
    statics.strARGS = ProKey("ARGS");
    statics.strDot = QLatin1String(".");
    statics.strDotDot = QLatin1String("..");
    statics.strever = QLatin1String("ever");
    statics.strforever = QLatin1String("forever");
    statics.strhost_build = QLatin1String("host_build");
    statics.strTEMPLATE = ProKey("TEMPLATE");
#ifdef PROEVALUATOR_FULL
    statics.strREQUIRES = ProKey("REQUIRES");
#endif

    statics.fakeValue = ProStringList(ProString("_FAKE_")); // It has to have a unique begin() value

    initFunctionStatics();

    static const struct {
        const char * const oldname, * const newname;
    } mapInits[] = {
        { "INTERFACES", "FORMS" },
        { "QMAKE_POST_BUILD", "QMAKE_POST_LINK" },
        { "TARGETDEPS", "POST_TARGETDEPS" },
        { "LIBPATH", "QMAKE_LIBDIR" },
        { "QMAKE_EXT_MOC", "QMAKE_EXT_CPP_MOC" },
        { "QMAKE_MOD_MOC", "QMAKE_H_MOD_MOC" },
        { "QMAKE_LFLAGS_SHAPP", "QMAKE_LFLAGS_APP" },
        { "PRECOMPH", "PRECOMPILED_HEADER" },
        { "PRECOMPCPP", "PRECOMPILED_SOURCE" },
        { "INCPATH", "INCLUDEPATH" },
        { "QMAKE_EXTRA_WIN_COMPILERS", "QMAKE_EXTRA_COMPILERS" },
        { "QMAKE_EXTRA_UNIX_COMPILERS", "QMAKE_EXTRA_COMPILERS" },
        { "QMAKE_EXTRA_WIN_TARGETS", "QMAKE_EXTRA_TARGETS" },
        { "QMAKE_EXTRA_UNIX_TARGETS", "QMAKE_EXTRA_TARGETS" },
        { "QMAKE_EXTRA_UNIX_INCLUDES", "QMAKE_EXTRA_INCLUDES" },
        { "QMAKE_EXTRA_UNIX_VARIABLES", "QMAKE_EXTRA_VARIABLES" },
        { "QMAKE_RPATH", "QMAKE_LFLAGS_RPATH" },
        { "QMAKE_FRAMEWORKDIR", "QMAKE_FRAMEWORKPATH" },
        { "QMAKE_FRAMEWORKDIR_FLAGS", "QMAKE_FRAMEWORKPATH_FLAGS" },
        { "IN_PWD", "PWD" }
    };
    for (unsigned i = 0; i < sizeof(mapInits)/sizeof(mapInits[0]); ++i)
        statics.varMap.insert(ProKey(mapInits[i].oldname), ProKey(mapInits[i].newname));
}

const ProKey &QMakeEvaluator::map(const ProKey &var)
{
    QHash<ProKey, ProKey>::ConstIterator it = statics.varMap.constFind(var);
    if (it == statics.varMap.constEnd())
        return var;
    deprecationWarning(fL1S("Variable %1 is deprecated; use %2 instead.")
                       .arg(var.toQString(), it.value().toQString()));
    return it.value();
}


QMakeEvaluator::QMakeEvaluator(QMakeGlobals *option,
                               QMakeParser *parser, QMakeHandler *handler)
  :
#ifdef PROEVALUATOR_DEBUG
    m_debugLevel(option->debugLevel),
#endif
    m_option(option), m_parser(parser), m_handler(handler)
{
    // So that single-threaded apps don't have to call initialize() for now.
    initStatics();

    // Configuration, more or less
    m_caller = 0;
#ifdef PROEVALUATOR_CUMULATIVE
    m_cumulative = false;
#endif
    m_hostBuild = false;

    // Evaluator state
#ifdef PROEVALUATOR_CUMULATIVE
    m_skipLevel = 0;
#endif
    m_loopLevel = 0;
    m_listCount = 0;
    m_valuemapStack.push(ProValueMap());
    m_valuemapInited = false;
}

QMakeEvaluator::~QMakeEvaluator()
{
}

void QMakeEvaluator::initFrom(const QMakeEvaluator &other)
{
    Q_ASSERT_X(&other, "QMakeEvaluator::visitProFile", "Project not prepared");
    m_functionDefs = other.m_functionDefs;
    m_valuemapStack = other.m_valuemapStack;
    m_valuemapInited = true;
    m_qmakespec = other.m_qmakespec;
    m_qmakespecName = other.m_qmakespecName;
    m_mkspecPaths = other.m_mkspecPaths;
    m_featureRoots = other.m_featureRoots;
    m_dirSep = other.m_dirSep;
}

//////// Evaluator tools /////////

uint QMakeEvaluator::getBlockLen(const ushort *&tokPtr)
{
    uint len = *tokPtr++;
    len |= (uint)*tokPtr++ << 16;
    return len;
}

ProString QMakeEvaluator::getStr(const ushort *&tokPtr)
{
    uint len = *tokPtr++;
    ProString ret(m_current.pro->items(), tokPtr - m_current.pro->tokPtr(), len);
    ret.setSource(m_current.pro);
    tokPtr += len;
    return ret;
}

ProKey QMakeEvaluator::getHashStr(const ushort *&tokPtr)
{
    uint hash = getBlockLen(tokPtr);
    uint len = *tokPtr++;
    ProKey ret(m_current.pro->items(), tokPtr - m_current.pro->tokPtr(), len, hash);
    tokPtr += len;
    return ret;
}

void QMakeEvaluator::skipStr(const ushort *&tokPtr)
{
    uint len = *tokPtr++;
    tokPtr += len;
}

void QMakeEvaluator::skipHashStr(const ushort *&tokPtr)
{
    tokPtr += 2;
    uint len = *tokPtr++;
    tokPtr += len;
}

// FIXME: this should not build new strings for direct sections.
// Note that the E_SPRINTF and E_LIST implementations rely on the deep copy.
ProStringList QMakeEvaluator::split_value_list(const QString &vals, const ProFile *source)
{
    QString build;
    ProStringList ret;
    QStack<char> quote;

    const ushort SPACE = ' ';
    const ushort LPAREN = '(';
    const ushort RPAREN = ')';
    const ushort SINGLEQUOTE = '\'';
    const ushort DOUBLEQUOTE = '"';
    const ushort BACKSLASH = '\\';

    if (!source)
        source = currentProFile();

    ushort unicode;
    const QChar *vals_data = vals.data();
    const int vals_len = vals.length();
    int parens = 0;
    for (int x = 0; x < vals_len; x++) {
        unicode = vals_data[x].unicode();
        if (x != (int)vals_len-1 && unicode == BACKSLASH &&
            (vals_data[x+1].unicode() == SINGLEQUOTE || vals_data[x+1].unicode() == DOUBLEQUOTE)) {
            build += vals_data[x++]; //get that 'escape'
        } else if (!quote.isEmpty() && unicode == quote.top()) {
            quote.pop();
        } else if (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE) {
            quote.push(unicode);
        } else if (unicode == RPAREN) {
            --parens;
        } else if (unicode == LPAREN) {
            ++parens;
        }

        if (!parens && quote.isEmpty() && vals_data[x] == SPACE) {
            ret << ProString(build).setSource(source);
            build.clear();
        } else {
            build += vals_data[x];
        }
    }
    if (!build.isEmpty())
        ret << ProString(build).setSource(source);
    if (parens)
        deprecationWarning(fL1S("Unmatched parentheses are deprecated."));
    return ret;
}

static void zipEmpty(ProStringList *value)
{
    for (int i = value->size(); --i >= 0;)
        if (value->at(i).isEmpty())
            value->remove(i);
}

static void insertUnique(ProStringList *varlist, const ProStringList &value)
{
    foreach (const ProString &str, value)
        if (!str.isEmpty() && !varlist->contains(str))
            varlist->append(str);
}

static void removeAll(ProStringList *varlist, const ProString &value)
{
    for (int i = varlist->size(); --i >= 0; )
        if (varlist->at(i) == value)
            varlist->remove(i);
}

void QMakeEvaluator::removeEach(ProStringList *varlist, const ProStringList &value)
{
    foreach (const ProString &str, value)
        if (!str.isEmpty())
            removeAll(varlist, str);
}

static void replaceInList(ProStringList *varlist,
        const QRegExp &regexp, const QString &replace, bool global, QString &tmp)
{
    for (ProStringList::Iterator varit = varlist->begin(); varit != varlist->end(); ) {
        QString val = varit->toQString(tmp);
        QString copy = val; // Force detach and have a reference value
        val.replace(regexp, replace);
        if (!val.isSharedWith(copy) && val != copy) {
            if (val.isEmpty()) {
                varit = varlist->erase(varit);
            } else {
                (*varit).setValue(val);
                ++varit;
            }
            if (!global)
                break;
        } else {
            ++varit;
        }
    }
}

//////// Evaluator /////////

static ALWAYS_INLINE void addStr(
        const ProString &str, ProStringList *ret, bool &pending, bool joined)
{
    if (joined) {
        ret->last().append(str, &pending);
    } else {
        if (!pending) {
            pending = true;
            *ret << str;
        } else {
            ret->last().append(str);
        }
    }
}

static ALWAYS_INLINE void addStrList(
        const ProStringList &list, ushort tok, ProStringList *ret, bool &pending, bool joined)
{
    if (!list.isEmpty()) {
        if (joined) {
            ret->last().append(list, &pending, !(tok & TokQuoted));
        } else {
            if (tok & TokQuoted) {
                if (!pending) {
                    pending = true;
                    *ret << ProString();
                }
                ret->last().append(list);
            } else {
                if (!pending) {
                    // Another qmake bizzarity: if nothing is pending and the
                    // first element is empty, it will be eaten
                    if (!list.at(0).isEmpty()) {
                        // The common case
                        pending = true;
                        *ret += list;
                        return;
                    }
                } else {
                    ret->last().append(list.at(0));
                }
                // This is somewhat slow, but a corner case
                for (int j = 1; j < list.size(); ++j) {
                    pending = true;
                    *ret << list.at(j);
                }
            }
        }
    }
}

void QMakeEvaluator::evaluateExpression(
        const ushort *&tokPtr, ProStringList *ret, bool joined)
{
    debugMsg(2, joined ? "evaluating joined expression" : "evaluating expression");
    if (joined)
        *ret << ProString();
    bool pending = false;
    forever {
        ushort tok = *tokPtr++;
        if (tok & TokNewStr) {
            debugMsg(2, "new string");
            pending = false;
        }
        ushort maskedTok = tok & TokMask;
        switch (maskedTok) {
        case TokLine:
            m_current.line = *tokPtr++;
            break;
        case TokLiteral: {
            const ProString &val = getStr(tokPtr);
            debugMsg(2, "literal %s", dbgStr(val));
            addStr(val, ret, pending, joined);
            break; }
        case TokHashLiteral: {
            const ProKey &val = getHashStr(tokPtr);
            debugMsg(2, "hashed literal %s", dbgStr(val.toString()));
            addStr(val, ret, pending, joined);
            break; }
        case TokVariable: {
            const ProKey &var = getHashStr(tokPtr);
            const ProStringList &val = values(map(var));
            debugMsg(2, "variable %s => %s", dbgKey(var), dbgStrList(val));
            addStrList(val, tok, ret, pending, joined);
            break; }
        case TokProperty: {
            const ProKey &var = getHashStr(tokPtr);
            const ProString &val = propertyValue(var);
            debugMsg(2, "property %s => %s", dbgKey(var), dbgStr(val));
            addStr(val, ret, pending, joined);
            break; }
        case TokEnvVar: {
            const ProString &var = getStr(tokPtr);
            const ProStringList &val = split_value_list(m_option->getEnv(var.toQString(m_tmp1)));
            debugMsg(2, "env var %s => %s", dbgStr(var), dbgStrList(val));
            addStrList(val, tok, ret, pending, joined);
            break; }
        case TokFuncName: {
            const ProKey &func = getHashStr(tokPtr);
            debugMsg(2, "function %s", dbgKey(func));
            addStrList(evaluateExpandFunction(func, tokPtr), tok, ret, pending, joined);
            break; }
        default:
            debugMsg(2, "evaluated expression => %s", dbgStrList(*ret));
            tokPtr--;
            return;
        }
    }
}

void QMakeEvaluator::skipExpression(const ushort *&pTokPtr)
{
    const ushort *tokPtr = pTokPtr;
    forever {
        ushort tok = *tokPtr++;
        switch (tok) {
        case TokLine:
            m_current.line = *tokPtr++;
            break;
        case TokValueTerminator:
        case TokFuncTerminator:
            pTokPtr = tokPtr;
            return;
        case TokArgSeparator:
            break;
        default:
            switch (tok & TokMask) {
            case TokLiteral:
            case TokEnvVar:
                skipStr(tokPtr);
                break;
            case TokHashLiteral:
            case TokVariable:
            case TokProperty:
                skipHashStr(tokPtr);
                break;
            case TokFuncName:
                skipHashStr(tokPtr);
                pTokPtr = tokPtr;
                skipExpression(pTokPtr);
                tokPtr = pTokPtr;
                break;
            default:
                Q_ASSERT_X(false, "skipExpression", "Unrecognized token");
                break;
            }
        }
    }
}

QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(
        ProFile *pro, const ushort *tokPtr)
{
    m_current.pro = pro;
    m_current.line = 0;
    return visitProBlock(tokPtr);
}

QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(
        const ushort *tokPtr)
{
    traceMsg("entering block");
    ProStringList curr;
    bool okey = true, or_op = false, invert = false;
    uint blockLen;
    while (ushort tok = *tokPtr++) {
        VisitReturn ret;
        switch (tok) {
        case TokLine:
            m_current.line = *tokPtr++;
            continue;
        case TokAssign:
        case TokAppend:
        case TokAppendUnique:
        case TokRemove:
        case TokReplace:
            visitProVariable(tok, curr, tokPtr);
            curr.clear();
            continue;
        case TokBranch:
            blockLen = getBlockLen(tokPtr);
            if (m_cumulative) {
#ifdef PROEVALUATOR_CUMULATIVE
                if (!okey)
                    m_skipLevel++;
                ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue;
                tokPtr += blockLen;
                blockLen = getBlockLen(tokPtr);
                if (!okey)
                    m_skipLevel--;
                else
                    m_skipLevel++;
                if ((ret == ReturnTrue || ret == ReturnFalse) && blockLen)
                    ret = visitProBlock(tokPtr);
                if (okey)
                    m_skipLevel--;
#endif
            } else {
                if (okey) {
                    traceMsg("taking 'then' branch");
                    ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue;
                    traceMsg("finished 'then' branch");
                }
                tokPtr += blockLen;
                blockLen = getBlockLen(tokPtr);
                if (!okey) {
                    traceMsg("taking 'else' branch");
                    ret = blockLen ? visitProBlock(tokPtr) : ReturnTrue;
                    traceMsg("finished 'else' branch");
                }
            }
            tokPtr += blockLen;
            okey = true, or_op = false; // force next evaluation
            break;
        case TokForLoop:
            if (m_cumulative) { // This is a no-win situation, so just pretend it's no loop
                skipHashStr(tokPtr);
                uint exprLen = getBlockLen(tokPtr);
                tokPtr += exprLen;
                blockLen = getBlockLen(tokPtr);
                ret = visitProBlock(tokPtr);
            } else if (okey != or_op) {
                const ProKey &variable = getHashStr(tokPtr);
                uint exprLen = getBlockLen(tokPtr);
                const ushort *exprPtr = tokPtr;
                tokPtr += exprLen;
                blockLen = getBlockLen(tokPtr);
                ret = visitProLoop(variable, exprPtr, tokPtr);
            } else {
                skipHashStr(tokPtr);
                uint exprLen = getBlockLen(tokPtr);
                tokPtr += exprLen;
                blockLen = getBlockLen(tokPtr);
                traceMsg("skipped loop");
                ret = ReturnTrue;
            }
            tokPtr += blockLen;
            okey = true, or_op = false; // force next evaluation
            break;
        case TokTestDef:
        case TokReplaceDef:
            if (m_cumulative || okey != or_op) {
                const ProKey &name = getHashStr(tokPtr);
                blockLen = getBlockLen(tokPtr);
                visitProFunctionDef(tok, name, tokPtr);
                traceMsg("defined %s function %s",
                      tok == TokTestDef ? "test" : "replace", dbgKey(name));
            } else {
                traceMsg("skipped function definition");
                skipHashStr(tokPtr);
                blockLen = getBlockLen(tokPtr);
            }
            tokPtr += blockLen;
            okey = true, or_op = false; // force next evaluation
            continue;
        case TokNot:
            traceMsg("NOT");
            invert ^= true;
            continue;
        case TokAnd:
            traceMsg("AND");
            or_op = false;
            continue;
        case TokOr:
            traceMsg("OR");
            or_op = true;
            continue;
        case TokCondition:
            if (!m_skipLevel && okey != or_op) {
                if (curr.size() != 1) {
                    if (!m_cumulative || !curr.isEmpty())
                        evalError(fL1S("Conditional must expand to exactly one word."));
                    okey = false;
                } else {
                    okey = isActiveConfig(curr.at(0).toQString(m_tmp2), true);
                    traceMsg("condition %s is %s", dbgStr(curr.at(0)), dbgBool(okey));
                    okey ^= invert;
                }
            } else {
                traceMsg("skipped condition %s", curr.size() == 1 ? dbgStr(curr.at(0)) : "<invalid>");
            }
            or_op = !okey; // tentatively force next evaluation
            invert = false;
            curr.clear();
            continue;
        case TokTestCall:
            if (!m_skipLevel && okey != or_op) {
                if (curr.size() != 1) {
                    if (!m_cumulative || !curr.isEmpty())
                        evalError(fL1S("Test name must expand to exactly one word."));
                    skipExpression(tokPtr);
                    okey = false;
                } else {
                    traceMsg("evaluating test function %s", dbgStr(curr.at(0)));
                    ret = evaluateConditionalFunction(curr.at(0).toKey(), tokPtr);
                    switch (ret) {
                    case ReturnTrue: okey = true; break;
                    case ReturnFalse: okey = false; break;
                    default:
                        traceMsg("aborting block, function status: %s", dbgReturn(ret));
                        return ret;
                    }
                    traceMsg("test function returned %s", dbgBool(okey));
                    okey ^= invert;
                }
            } else if (m_cumulative) {
#ifdef PROEVALUATOR_CUMULATIVE
                m_skipLevel++;
                if (curr.size() != 1)
                    skipExpression(tokPtr);
                else
                    evaluateConditionalFunction(curr.at(0).toKey(), tokPtr);
                m_skipLevel--;
#endif
            } else {
                skipExpression(tokPtr);
                traceMsg("skipped test function %s", curr.size() == 1 ? dbgStr(curr.at(0)) : "<invalid>");
            }
            or_op = !okey; // tentatively force next evaluation
            invert = false;
            curr.clear();
            continue;
        default: {
                const ushort *oTokPtr = --tokPtr;
                evaluateExpression(tokPtr, &curr, false);
                if (tokPtr != oTokPtr)
                    continue;
            }
            Q_ASSERT_X(false, "visitProBlock", "unexpected item type");
            continue;
        }
        if (ret != ReturnTrue && ret != ReturnFalse) {
            traceMsg("aborting block, status: %s", dbgReturn(ret));
            return ret;
        }
    }
    traceMsg("leaving block, okey=%s", dbgBool(okey));
    return returnBool(okey);
}


void QMakeEvaluator::visitProFunctionDef(
        ushort tok, const ProKey &name, const ushort *tokPtr)
{
    QHash<ProKey, ProFunctionDef> *hash =
            (tok == TokTestDef
             ? &m_functionDefs.testFunctions
             : &m_functionDefs.replaceFunctions);
    hash->insert(name, ProFunctionDef(m_current.pro, tokPtr - m_current.pro->tokPtr()));
}

QMakeEvaluator::VisitReturn QMakeEvaluator::visitProLoop(
        const ProKey &_variable, const ushort *exprPtr, const ushort *tokPtr)
{
    VisitReturn ret = ReturnTrue;
    bool infinite = false;
    int index = 0;
    ProKey variable;
    ProStringList oldVarVal;
    ProString it_list = expandVariableReferences(exprPtr, 0, true).at(0);
    if (_variable.isEmpty()) {
        if (it_list != statics.strever) {
            evalError(fL1S("Invalid loop expression."));
            return ReturnFalse;
        }
        it_list = ProString(statics.strforever);
    } else {
        variable = map(_variable);
        oldVarVal = values(variable);
    }
    ProStringList list = values(it_list.toKey());
    if (list.isEmpty()) {
        if (it_list == statics.strforever) {
            infinite = true;
        } else {
            const QString &itl = it_list.toQString(m_tmp1);
            int dotdot = itl.indexOf(statics.strDotDot);
            if (dotdot != -1) {
                bool ok;
                int start = itl.left(dotdot).toInt(&ok);
                if (ok) {
                    int end = itl.mid(dotdot+2).toInt(&ok);
                    if (ok) {
                        if (start < end) {
                            for (int i = start; i <= end; i++)
                                list << ProString(QString::number(i));
                        } else {
                            for (int i = start; i >= end; i--)
                                list << ProString(QString::number(i));
                        }
                    }
                }
            }
        }
    }

    if (infinite)
        traceMsg("entering infinite loop for %s", dbgKey(variable));
    else
        traceMsg("entering loop for %s over %s", dbgKey(variable), dbgStrList(list));

    m_loopLevel++;
    forever {
        if (infinite) {
            if (!variable.isEmpty())
                m_valuemapStack.top()[variable] = ProStringList(ProString(QString::number(index++)));
            if (index > 1000) {
                evalError(fL1S("Ran into infinite loop (> 1000 iterations)."));
                break;
            }
            traceMsg("loop iteration %d", index);
        } else {
            ProString val;
            do {
                if (index >= list.count())
                    goto do_break;
                val = list.at(index++);
            } while (val.isEmpty()); // stupid, but qmake is like that
            traceMsg("loop iteration %s", dbgStr(val));
            m_valuemapStack.top()[variable] = ProStringList(val);
        }

        ret = visitProBlock(tokPtr);
        switch (ret) {
        case ReturnTrue:
        case ReturnFalse:
            break;
        case ReturnNext:
            ret = ReturnTrue;
            break;
        case ReturnBreak:
            ret = ReturnTrue;
            goto do_break;
        default:
            goto do_break;
        }
    }
  do_break:
    m_loopLevel--;

    traceMsg("done looping");

    if (!variable.isEmpty())
        m_valuemapStack.top()[variable] = oldVarVal;
    return ret;
}

void QMakeEvaluator::visitProVariable(
        ushort tok, const ProStringList &curr, const ushort *&tokPtr)
{
    int sizeHint = *tokPtr++;

    if (curr.size() != 1) {
        skipExpression(tokPtr);
        if (!m_cumulative || !curr.isEmpty())
            evalError(fL1S("Left hand side of assignment must expand to exactly one word."));
        return;
    }
    const ProKey &varName = map(curr.first());

    if (tok == TokReplace) {      // ~=
        // DEFINES ~= s/a/b/?[gqi]

        const ProStringList &varVal = expandVariableReferences(tokPtr, sizeHint, true);
        const QString &val = varVal.at(0).toQString(m_tmp1);
        if (val.length() < 4 || val.at(0) != QLatin1Char('s')) {
            evalError(fL1S("The ~= operator can handle only the s/// function."));
            return;
        }
        QChar sep = val.at(1);
        QStringList func = val.split(sep);
        if (func.count() < 3 || func.count() > 4) {
            evalError(fL1S("The s/// function expects 3 or 4 arguments."));
            return;
        }

        bool global = false, quote = false, case_sense = false;
        if (func.count() == 4) {
            global = func[3].indexOf(QLatin1Char('g')) != -1;
            case_sense = func[3].indexOf(QLatin1Char('i')) == -1;
            quote = func[3].indexOf(QLatin1Char('q')) != -1;
        }
        QString pattern = func[1];
        QString replace = func[2];
        if (quote)
            pattern = QRegExp::escape(pattern);

        QRegExp regexp(pattern, case_sense ? Qt::CaseSensitive : Qt::CaseInsensitive);

        // We could make a union of modified and unmodified values,
        // but this will break just as much as it fixes, so leave it as is.
        replaceInList(&valuesRef(varName), regexp, replace, global, m_tmp2);
        debugMsg(2, "replaced %s with %s", dbgQStr(pattern), dbgQStr(replace));
    } else {
        ProStringList varVal = expandVariableReferences(tokPtr, sizeHint);
        switch (tok) {
        default: // whatever - cannot happen
        case TokAssign:          // =
            zipEmpty(&varVal);
            if (!m_cumulative) {
                // FIXME: add check+warning about accidental value removal.
                // This may be a bit too noisy, though.
                m_valuemapStack.top()[varName] = varVal;
            } else {
                if (!varVal.isEmpty()) {
                    // We are greedy for values. But avoid exponential growth.
                    ProStringList &v = valuesRef(varName);
                    if (v.isEmpty()) {
                        v = varVal;
                    } else {
                        ProStringList old = v;
                        v = varVal;
                        QSet<ProString> has;
                        has.reserve(v.size());
                        foreach (const ProString &s, v)
                            has.insert(s);
                        v.reserve(v.size() + old.size());
                        foreach (const ProString &s, old)
                            if (!has.contains(s))
                                v << s;
                    }
                }
            }
            debugMsg(2, "assigning");
            break;
        case TokAppendUnique:    // *=
            insertUnique(&valuesRef(varName), varVal);
            debugMsg(2, "appending unique");
            break;
        case TokAppend:          // +=
            zipEmpty(&varVal);
            valuesRef(varName) += varVal;
            debugMsg(2, "appending");
            break;
        case TokRemove:       // -=
            if (!m_cumulative) {
                removeEach(&valuesRef(varName), varVal);
            } else {
                // We are stingy with our values, too.
            }
            debugMsg(2, "removing");
            break;
        }
    }
    traceMsg("%s := %s", dbgKey(varName), dbgStrList(values(varName)));

    if (varName == statics.strTEMPLATE)
        setTemplate();
#ifdef PROEVALUATOR_FULL
    else if (varName == statics.strREQUIRES)
        checkRequirements(values(varName));
#endif
}

void QMakeEvaluator::setTemplate()
{
    ProStringList &values = valuesRef(statics.strTEMPLATE);
    if (!m_option->user_template.isEmpty()) {
        // Don't allow override
        values = ProStringList(ProString(m_option->user_template));
    } else {
        if (values.isEmpty())
            values.append(ProString("app"));
        else
            values.erase(values.begin() + 1, values.end());
    }
    if (!m_option->user_template_prefix.isEmpty()) {
        QString val = values.first().toQString(m_tmp1);
        if (!val.startsWith(m_option->user_template_prefix)) {
            val.prepend(m_option->user_template_prefix);
            values = ProStringList(ProString(val));
        }
    }
}

void QMakeEvaluator::loadDefaults()
{
    ProValueMap &vars = m_valuemapStack.top();

    vars[ProKey("DIR_SEPARATOR")] << ProString(m_option->dir_sep);
    vars[ProKey("DIRLIST_SEPARATOR")] << ProString(m_option->dirlist_sep);
    vars[ProKey("_DATE_")] << ProString(QDateTime::currentDateTime().toString());
    if (!m_option->qmake_abslocation.isEmpty())
        vars[ProKey("QMAKE_QMAKE")] << ProString(m_option->qmake_abslocation);
#if defined(Q_OS_WIN32)
    vars[ProKey("QMAKE_HOST.os")] << ProString("Windows");

    DWORD name_length = 1024;
    wchar_t name[1024];
    if (GetComputerName(name, &name_length))
        vars[ProKey("QMAKE_HOST.name")] << ProString(QString::fromWCharArray(name));

    QSysInfo::WinVersion ver = QSysInfo::WindowsVersion;
    vars[ProKey("QMAKE_HOST.version")] << ProString(QString::number(ver));
    ProString verStr;
    switch (ver) {
    case QSysInfo::WV_Me: verStr = ProString("WinMe"); break;
    case QSysInfo::WV_95: verStr = ProString("Win95"); break;
    case QSysInfo::WV_98: verStr = ProString("Win98"); break;
    case QSysInfo::WV_NT: verStr = ProString("WinNT"); break;
    case QSysInfo::WV_2000: verStr = ProString("Win2000"); break;
    case QSysInfo::WV_2003: verStr = ProString("Win2003"); break;
    case QSysInfo::WV_XP: verStr = ProString("WinXP"); break;
    case QSysInfo::WV_VISTA: verStr = ProString("WinVista"); break;
    default: verStr = ProString("Unknown"); break;
    }
    vars[ProKey("QMAKE_HOST.version_string")] << verStr;

    SYSTEM_INFO info;
    GetSystemInfo(&info);
    ProString archStr;
    switch (info.wProcessorArchitecture) {
# ifdef PROCESSOR_ARCHITECTURE_AMD64
    case PROCESSOR_ARCHITECTURE_AMD64:
        archStr = ProString("x86_64");
        break;
# endif
    case PROCESSOR_ARCHITECTURE_INTEL:
        archStr = ProString("x86");
        break;
    case PROCESSOR_ARCHITECTURE_IA64:
# ifdef PROCESSOR_ARCHITECTURE_IA32_ON_WIN64
    case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64:
# endif
        archStr = ProString("IA64");
        break;
    default:
        archStr = ProString("Unknown");
        break;
    }
    vars[ProKey("QMAKE_HOST.arch")] << archStr;

# if defined(Q_CC_MSVC) // ### bogus condition, but nobody x-builds for msvc with a different qmake
    QLatin1Char backslash('\\');
    QString paths = m_option->getEnv(QLatin1String("PATH"));
    QString vcBin64 = m_option->getEnv(QLatin1String("VCINSTALLDIR"));
    if (!vcBin64.endsWith(backslash))
        vcBin64.append(backslash);
    vcBin64.append(QLatin1String("bin\\amd64"));
    QString vcBinX86_64 = m_option->getEnv(QLatin1String("VCINSTALLDIR"));
    if (!vcBinX86_64.endsWith(backslash))
        vcBinX86_64.append(backslash);
    vcBinX86_64.append(QLatin1String("bin\\x86_amd64"));
    if (paths.contains(vcBin64, Qt::CaseInsensitive)
            || paths.contains(vcBinX86_64, Qt::CaseInsensitive))
        vars[ProKey("QMAKE_TARGET.arch")] << ProString("x86_64");
    else
        vars[ProKey("QMAKE_TARGET.arch")] << ProString("x86");
# endif
#elif defined(Q_OS_UNIX)
    struct utsname name;
    if (!uname(&name)) {
        vars[ProKey("QMAKE_HOST.os")] << ProString(name.sysname);
        vars[ProKey("QMAKE_HOST.name")] << ProString(QString::fromLocal8Bit(name.nodename));
        vars[ProKey("QMAKE_HOST.version")] << ProString(name.release);
        vars[ProKey("QMAKE_HOST.version_string")] << ProString(name.version);
        vars[ProKey("QMAKE_HOST.arch")] << ProString(name.machine);
    }
#endif

    m_valuemapInited = true;
}

bool QMakeEvaluator::prepareProject(const QString &inDir)
{
    QString superdir;
    if (m_option->do_cache) {
        QString conffile;
        QString cachefile = m_option->cachefile;
        if (cachefile.isEmpty())  { //find it as it has not been specified
            if (m_outputDir.isEmpty())
                goto no_cache;
            superdir = m_outputDir;
            forever {
                QString superfile = superdir + QLatin1String("/.qmake.super");
                if (IoUtils::exists(superfile)) {
                    m_superfile = superfile;
                    break;
                }
                QFileInfo qdfi(superdir);
                if (qdfi.isRoot()) {
                    superdir.clear();
                    break;
                }
                superdir = qdfi.path();
            }
            QString sdir = inDir;
            QString dir = m_outputDir;
            forever {
                conffile = sdir + QLatin1String("/.qmake.conf");
                if (!IoUtils::exists(conffile))
                    conffile.clear();
                cachefile = dir + QLatin1String("/.qmake.cache");
                if (!IoUtils::exists(cachefile))
                    cachefile.clear();
                if (!conffile.isEmpty() || !cachefile.isEmpty()) {
                    if (dir != sdir)
                        m_sourceRoot = sdir;
                    m_buildRoot = dir;
                    break;
                }
                if (dir == superdir)
                    goto no_cache;
                QFileInfo qsdfi(sdir);
                QFileInfo qdfi(dir);
                if (qsdfi.isRoot() || qdfi.isRoot())
                    goto no_cache;
                sdir = qsdfi.path();
                dir = qdfi.path();
            }
        } else {
            m_buildRoot = QFileInfo(cachefile).path();
        }
        m_conffile = conffile;
        m_cachefile = cachefile;
    }
  no_cache:

    // Look for mkspecs/ in source and build. First to win determines the root.
    QString sdir = inDir;
    QString dir = m_outputDir;
    while (dir != m_buildRoot) {
        if ((dir != sdir && QFileInfo(sdir, QLatin1String("mkspecs")).isDir())
                || QFileInfo(dir, QLatin1String("mkspecs")).isDir()) {
            if (dir != sdir)
                m_sourceRoot = sdir;
            m_buildRoot = dir;
            break;
        }
        if (dir == superdir)
            break;
        QFileInfo qsdfi(sdir);
        QFileInfo qdfi(dir);
        if (qsdfi.isRoot() || qdfi.isRoot())
            break;
        sdir = qsdfi.path();
        dir = qdfi.path();
    }

    return true;
}

bool QMakeEvaluator::loadSpecInternal()
{
    if (!evaluateFeatureFile(QLatin1String("spec_pre.prf")))
        return false;
    QString spec = m_qmakespec + QLatin1String("/qmake.conf");
    if (!evaluateFile(spec, QMakeHandler::EvalConfigFile, LoadProOnly)) {
        evalError(fL1S("Could not read qmake configuration file %1.").arg(spec));
        return false;
    }
#ifndef QT_BUILD_QMAKE
    // Legacy support for Qt4 default specs
#  ifdef Q_OS_UNIX
    if (m_qmakespec.endsWith(QLatin1String("/default-host"))
        || m_qmakespec.endsWith(QLatin1String("/default"))) {
        QString rspec = QFileInfo(m_qmakespec).readLink();
        if (!rspec.isEmpty())
            m_qmakespec = QDir::cleanPath(QDir(m_qmakespec).absoluteFilePath(rspec));
    }
#  else
    // We can't resolve symlinks as they do on Unix, so configure.exe puts
    // the source of the qmake.conf at the end of the default/qmake.conf in
    // the QMAKESPEC_ORIGINAL variable.
    const ProString &orig_spec = first(ProKey("QMAKESPEC_ORIGINAL"));
    if (!orig_spec.isEmpty())
        m_qmakespec = orig_spec.toQString();
#  endif
#endif
    valuesRef(ProKey("QMAKESPEC")) << ProString(m_qmakespec);
    m_qmakespecName = IoUtils::fileName(m_qmakespec).toString();
    if (!evaluateFeatureFile(QLatin1String("spec_post.prf")))
        return false;
    // The MinGW and x-build specs may change the separator; $$shell_{path,quote}() need it
    m_dirSep = first(ProKey("QMAKE_DIR_SEP"));
    return true;
}

bool QMakeEvaluator::loadSpec()
{
    QString qmakespec = m_option->expandEnvVars(
                m_hostBuild ? m_option->qmakespec : m_option->xqmakespec);

    {
        QMakeEvaluator evaluator(m_option, m_parser, m_handler);
        if (!m_superfile.isEmpty()) {
            valuesRef(ProKey("_QMAKE_SUPER_CACHE_")) << ProString(m_superfile);
            if (!evaluator.evaluateFile(m_superfile, QMakeHandler::EvalConfigFile, LoadProOnly))
                return false;
        }
        if (!m_conffile.isEmpty()) {
            valuesRef(ProKey("_QMAKE_CONF_")) << ProString(m_conffile);
            if (!evaluator.evaluateFile(m_conffile, QMakeHandler::EvalConfigFile, LoadProOnly))
                return false;
        }
        if (!m_cachefile.isEmpty()) {
            valuesRef(ProKey("_QMAKE_CACHE_")) << ProString(m_cachefile);
            if (!evaluator.evaluateFile(m_cachefile, QMakeHandler::EvalConfigFile, LoadProOnly))
                return false;
        }
        if (qmakespec.isEmpty()) {
            if (!m_hostBuild)
                qmakespec = evaluator.first(ProKey("XQMAKESPEC")).toQString();
            if (qmakespec.isEmpty())
                qmakespec = evaluator.first(ProKey("QMAKESPEC")).toQString();
        }
        m_qmakepath = evaluator.values(ProKey("QMAKEPATH")).toQStringList();
        m_qmakefeatures = evaluator.values(ProKey("QMAKEFEATURES")).toQStringList();
    }

    updateMkspecPaths();
    if (qmakespec.isEmpty())
        qmakespec = propertyValue(ProKey(m_hostBuild ? "QMAKE_SPEC" : "QMAKE_XSPEC")).toQString();
#ifndef QT_BUILD_QMAKE
    // Legacy support for Qt4 qmake in Qt Creator, etc.
    if (qmakespec.isEmpty())
        qmakespec = m_hostBuild ? QLatin1String("default-host") : QLatin1String("default");
#endif
    if (IoUtils::isRelativePath(qmakespec)) {
        foreach (const QString &root, m_mkspecPaths) {
            QString mkspec = root + QLatin1Char('/') + qmakespec;
            if (IoUtils::exists(mkspec)) {
                qmakespec = mkspec;
                goto cool;
            }
        }
        evalError(fL1S("Could not find qmake configuration file %1.").arg(qmakespec));
        return false;
    }
  cool:
    m_qmakespec = QDir::cleanPath(qmakespec);

    if (!m_superfile.isEmpty()
        && !evaluateFile(m_superfile, QMakeHandler::EvalConfigFile, LoadProOnly)) {
        return false;
    }
    if (!loadSpecInternal())
        return false;
    updateFeaturePaths(); // The spec extends the feature search path, so rebuild the cache.
    if (!m_conffile.isEmpty()
        && !evaluateFile(m_conffile, QMakeHandler::EvalConfigFile, LoadProOnly)) {
        return false;
    }
    if (!m_cachefile.isEmpty()
        && !evaluateFile(m_cachefile, QMakeHandler::EvalConfigFile, LoadProOnly)) {
        return false;
    }
    return true;
}

void QMakeEvaluator::setupProject()
{
    setTemplate();
    ProValueMap &vars = m_valuemapStack.top();
    vars[ProKey("TARGET")] << ProString(QFileInfo(currentFileName()).baseName());
    vars[ProKey("_PRO_FILE_")] << ProString(currentFileName());
    vars[ProKey("_PRO_FILE_PWD_")] << ProString(currentDirectory());
    vars[ProKey("OUT_PWD")] << ProString(m_outputDir);
}

void QMakeEvaluator::evaluateCommand(const QString &cmds, const QString &where)
{
    if (!cmds.isEmpty()) {
        if (ProFile *pro = m_parser->parsedProBlock(cmds, where, -1)) {
            if (pro->isOk()) {
                m_locationStack.push(m_current);
                visitProBlock(pro, pro->tokPtr());
                m_current = m_locationStack.pop();
            }
            pro->deref();
        }
    }
}

void QMakeEvaluator::evaluateConfigFeatures()
{
    QSet<QString> processed;
    forever {
        bool finished = true;
        ProStringList configs = values(statics.strCONFIG);
        for (int i = configs.size() - 1; i >= 0; --i) {
            QString config = configs.at(i).toQString(m_tmp1).toLower();
            if (!processed.contains(config)) {
                config.detach();
                processed.insert(config);
                if (evaluateFeatureFile(config, true)) {
                    finished = false;
                    break;
                }
            }
        }
        if (finished)
            break;
    }
}

QMakeEvaluator::VisitReturn QMakeEvaluator::visitProFile(
        ProFile *pro, QMakeHandler::EvalFileType type, LoadFlags flags)
{
    if (!m_cumulative && !pro->isOk())
        return ReturnFalse;

    if (flags & LoadPreFiles) {
        if (!prepareProject(pro->directoryName()))
            return ReturnFalse;

        m_hostBuild = pro->isHostBuild();

#ifdef PROEVALUATOR_THREAD_SAFE
        m_option->mutex.lock();
#endif
        QMakeBaseEnv **baseEnvPtr = &m_option->baseEnvs[QMakeBaseKey(m_buildRoot, m_hostBuild)];
        if (!*baseEnvPtr)
            *baseEnvPtr = new QMakeBaseEnv;
        QMakeBaseEnv *baseEnv = *baseEnvPtr;

#ifdef PROEVALUATOR_THREAD_SAFE
        {
            QMutexLocker locker(&baseEnv->mutex);
            m_option->mutex.unlock();
            if (baseEnv->inProgress) {
                QThreadPool::globalInstance()->releaseThread();
                baseEnv->cond.wait(&baseEnv->mutex);
                QThreadPool::globalInstance()->reserveThread();
                if (!baseEnv->isOk)
                    return ReturnFalse;
            } else
#endif
            if (!baseEnv->evaluator) {
#ifdef PROEVALUATOR_THREAD_SAFE
                baseEnv->inProgress = true;
                locker.unlock();
#endif

                QMakeEvaluator *baseEval = new QMakeEvaluator(m_option, m_parser, m_handler);
                baseEnv->evaluator = baseEval;
                baseEval->m_superfile = m_superfile;
                baseEval->m_conffile = m_conffile;
                baseEval->m_cachefile = m_cachefile;
                baseEval->m_sourceRoot = m_sourceRoot;
                baseEval->m_buildRoot = m_buildRoot;
                baseEval->m_hostBuild = m_hostBuild;
                bool ok = baseEval->loadSpec();

#ifdef PROEVALUATOR_THREAD_SAFE
                locker.relock();
                baseEnv->isOk = ok;
                baseEnv->inProgress = false;
                baseEnv->cond.wakeAll();
#endif

                if (!ok)
                    return ReturnFalse;
            }
#ifdef PROEVALUATOR_THREAD_SAFE
        }
#endif

        initFrom(*baseEnv->evaluator);
    } else {
        if (!m_valuemapInited)
            loadDefaults();
    }

#ifdef QT_BUILD_QMAKE
    for (ProValueMap::ConstIterator it = m_extraVars.constBegin();
         it != m_extraVars.constEnd(); ++it)
        m_valuemapStack.first().insert(it.key(), it.value());
#endif

    m_handler->aboutToEval(currentProFile(), pro, type);
    m_profileStack.push(pro);
    valuesRef(ProKey("PWD")) = ProStringList(ProString(currentDirectory()));
    if (flags & LoadPreFiles) {
        setupProject();

        evaluateFeatureFile(QLatin1String("default_pre.prf"));

        evaluateCommand(m_option->precmds, fL1S("(command line)"));

#ifdef QT_BUILD_QMAKE
        // After user configs, to override them
        if (!m_extraConfigs.isEmpty())
            evaluateCommand("CONFIG += " + m_extraConfigs.join(' '), fL1S("(extra configs)"));
#endif
    }

    debugMsg(1, "visiting file %s", qPrintable(pro->fileName()));
    visitProBlock(pro, pro->tokPtr());
    debugMsg(1, "done visiting file %s", qPrintable(pro->fileName()));

    if (flags & LoadPostFiles) {
        evaluateCommand(m_option->postcmds, fL1S("(command line -after)"));

#ifdef QT_BUILD_QMAKE
        // Again, to ensure the project does not mess with us.
        // Specifically, do not allow a project to override debug/release within a
        // debug_and_release build pass - it's too late for that at this point anyway.
        if (!m_extraConfigs.isEmpty())
            evaluateCommand("CONFIG += " + m_extraConfigs.join(' '), fL1S("(extra configs)"));
#endif

        evaluateFeatureFile(QLatin1String("default_post.prf"));

        evaluateConfigFeatures();
    }
    m_profileStack.pop();
    valuesRef(ProKey("PWD")) = ProStringList(ProString(currentDirectory()));
    m_handler->doneWithEval(currentProFile());

    return ReturnTrue;
}


void QMakeEvaluator::updateMkspecPaths()
{
    QStringList ret;
    const QString concat = QLatin1String("/mkspecs");

    foreach (const QString &it, m_option->getPathListEnv(QLatin1String("QMAKEPATH")))
        ret << it + concat;

    foreach (const QString &it, m_qmakepath)
        ret << it + concat;

    if (!m_buildRoot.isEmpty())
        ret << m_buildRoot + concat;
    if (!m_sourceRoot.isEmpty())
        ret << m_sourceRoot + concat;

    ret << m_option->propertyValue(ProKey("QT_HOST_DATA/get")) + concat;

    ret.removeDuplicates();
    m_mkspecPaths = ret;
}

void QMakeEvaluator::updateFeaturePaths()
{
    QString mkspecs_concat = QLatin1String("/mkspecs");
    QString features_concat = QLatin1String("/features/");

    QStringList feature_roots;

    foreach (const QString &f, m_option->getPathListEnv(QLatin1String("QMAKEFEATURES")))
        feature_roots += f;

    feature_roots += m_qmakefeatures;

    feature_roots += m_option->propertyValue(ProKey("QMAKEFEATURES")).toQString(m_mtmp).split(
            m_option->dirlist_sep, QString::SkipEmptyParts);

    QStringList feature_bases;
    if (!m_buildRoot.isEmpty())
        feature_bases << m_buildRoot;
    if (!m_sourceRoot.isEmpty())
        feature_bases << m_sourceRoot;

    foreach (const QString &item, m_option->getPathListEnv(QLatin1String("QMAKEPATH")))
        feature_bases << (item + mkspecs_concat);

    foreach (const QString &item, m_qmakepath)
        feature_bases << (item + mkspecs_concat);

    if (!m_qmakespec.isEmpty()) {
        // The spec is already platform-dependent, so no subdirs here.
        feature_roots << (m_qmakespec + features_concat);

        // Also check directly under the root directory of the mkspecs collection
        QDir specdir(m_qmakespec);
        while (!specdir.isRoot() && specdir.cdUp()) {
            const QString specpath = specdir.path();
            if (specpath.endsWith(mkspecs_concat)) {
                if (IoUtils::exists(specpath + features_concat))
                    feature_bases << specpath;
                break;
            }
        }
    }

    feature_bases << (m_option->propertyValue(ProKey("QT_HOST_DATA/get")).toQString(m_mtmp)
                      + mkspecs_concat);

    foreach (const QString &fb, feature_bases) {
        foreach (const ProString &sfx, values(ProKey("QMAKE_PLATFORM")))
            feature_roots << (fb + features_concat + sfx + QLatin1Char('/'));
        feature_roots << (fb + features_concat);
    }

    for (int i = 0; i < feature_roots.count(); ++i)
        if (!feature_roots.at(i).endsWith((ushort)'/'))
            feature_roots[i].append((ushort)'/');

    feature_roots.removeDuplicates();

    QStringList ret;
    foreach (const QString &root, feature_roots)
        if (IoUtils::exists(root))
            ret << root;
    m_featureRoots = ret;
}

ProString QMakeEvaluator::propertyValue(const ProKey &name) const
{
    if (name == QLatin1String("QMAKE_MKSPECS"))
        return ProString(m_mkspecPaths.join(m_option->dirlist_sep));
    ProString ret = m_option->propertyValue(name);
//    if (ret.isNull())
//        evalError(fL1S("Querying unknown property %1").arg(name.toQString(m_mtmp)));
    return ret;
}

ProFile *QMakeEvaluator::currentProFile() const
{
    if (m_profileStack.count() > 0)
        return m_profileStack.top();
    return 0;
}

QString QMakeEvaluator::currentFileName() const
{
    ProFile *pro = currentProFile();
    if (pro)
        return pro->fileName();
    return QString();
}

QString QMakeEvaluator::currentDirectory() const
{
    ProFile *pro = currentProFile();
    if (pro)
        return pro->directoryName();
    return QString();
}

bool QMakeEvaluator::isActiveConfig(const QString &config, bool regex)
{
    // magic types for easy flipping
    if (config == statics.strtrue)
        return true;
    if (config == statics.strfalse)
        return false;

    if (config == statics.strhost_build)
        return m_hostBuild;

    if (regex && (config.contains(QLatin1Char('*')) || config.contains(QLatin1Char('?')))) {
        QString cfg = config;
        cfg.detach(); // Keep m_tmp out of QRegExp's cache
        QRegExp re(cfg, Qt::CaseSensitive, QRegExp::Wildcard);

        // mkspecs
        if (re.exactMatch(m_qmakespecName))
            return true;

        // CONFIG variable
        int t = 0;
        foreach (const ProString &configValue, values(statics.strCONFIG)) {
            if (re.exactMatch(configValue.toQString(m_tmp[t])))
                return true;
            t ^= 1;
        }
    } else {
        // mkspecs
        if (m_qmakespecName == config)
            return true;

        // CONFIG variable
        if (values(statics.strCONFIG).contains(ProString(config)))
            return true;
    }

    return false;
}

ProStringList QMakeEvaluator::expandVariableReferences(
        const ushort *&tokPtr, int sizeHint, bool joined)
{
    ProStringList ret;
    ret.reserve(sizeHint);
    forever {
        evaluateExpression(tokPtr, &ret, joined);
        switch (*tokPtr) {
        case TokValueTerminator:
        case TokFuncTerminator:
            tokPtr++;
            return ret;
        case TokArgSeparator:
            if (joined) {
                tokPtr++;
                continue;
            }
            // fallthrough
        default:
            Q_ASSERT_X(false, "expandVariableReferences", "Unrecognized token");
            break;
        }
    }
}

QList<ProStringList> QMakeEvaluator::prepareFunctionArgs(const ushort *&tokPtr)
{
    QList<ProStringList> args_list;
    if (*tokPtr != TokFuncTerminator) {
        for (;; tokPtr++) {
            ProStringList arg;
            evaluateExpression(tokPtr, &arg, false);
            args_list << arg;
            if (*tokPtr == TokFuncTerminator)
                break;
            Q_ASSERT(*tokPtr == TokArgSeparator);
        }
    }
    tokPtr++;
    return args_list;
}

ProStringList QMakeEvaluator::evaluateFunction(
        const ProFunctionDef &func, const QList<ProStringList> &argumentsList, bool *ok)
{
    bool oki;
    ProStringList ret;

    if (m_valuemapStack.count() >= 100) {
        evalError(fL1S("Ran into infinite recursion (depth > 100)."));
        oki = false;
    } else {
        m_valuemapStack.push(ProValueMap());
        m_locationStack.push(m_current);
        int loopLevel = m_loopLevel;
        m_loopLevel = 0;

        ProStringList args;
        for (int i = 0; i < argumentsList.count(); ++i) {
            args += argumentsList[i];
            m_valuemapStack.top()[ProKey(QString::number(i+1))] = argumentsList[i];
        }
        m_valuemapStack.top()[statics.strARGS] = args;
        VisitReturn vr = visitProBlock(func.pro(), func.tokPtr());
        oki = (vr != ReturnFalse && vr != ReturnError); // True || Return
        ret = m_returnValue;
        m_returnValue.clear();

        m_loopLevel = loopLevel;
        m_current = m_locationStack.pop();
        m_valuemapStack.pop();
    }
    if (ok)
        *ok = oki;
    if (oki)
        return ret;
    return ProStringList();
}

QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBoolFunction(
        const ProFunctionDef &func, const QList<ProStringList> &argumentsList,
        const ProString &function)
{
    bool ok;
    ProStringList ret = evaluateFunction(func, argumentsList, &ok);
    if (ok) {
        if (ret.isEmpty())
            return ReturnTrue;
        if (ret.at(0) != statics.strfalse) {
            if (ret.at(0) == statics.strtrue)
                return ReturnTrue;
            int val = ret.at(0).toQString(m_tmp1).toInt(&ok);
            if (ok) {
                if (val)
                    return ReturnTrue;
            } else {
                evalError(fL1S("Unexpected return value from test '%1': %2.")
                          .arg(function.toQString(m_tmp1))
                          .arg(ret.join(QLatin1String(" :: "))));
            }
        }
    }
    return ReturnFalse;
}

QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditionalFunction(
        const ProKey &func, const ushort *&tokPtr)
{
    if (int func_t = statics.functions.value(func)) {
        //why don't the builtin functions just use args_list? --Sam
        return evaluateBuiltinConditional(func_t, func, expandVariableReferences(tokPtr, 5, true));
    }

    QHash<ProKey, ProFunctionDef>::ConstIterator it =
            m_functionDefs.testFunctions.constFind(func);
    if (it != m_functionDefs.testFunctions.constEnd()) {
        const QList<ProStringList> args = prepareFunctionArgs(tokPtr);
        traceMsg("calling %s(%s)", dbgKey(func), dbgStrListList(args));
        return evaluateBoolFunction(*it, args, func);
    }

    skipExpression(tokPtr);
    evalError(fL1S("'%1' is not a recognized test function.").arg(func.toQString(m_tmp1)));
    return ReturnFalse;
}

ProStringList QMakeEvaluator::evaluateExpandFunction(
        const ProKey &func, const ushort *&tokPtr)
{
    if (int func_t = statics.expands.value(func)) {
        //why don't the builtin functions just use args_list? --Sam
        return evaluateBuiltinExpand(func_t, func, expandVariableReferences(tokPtr, 5, true));
    }

    QHash<ProKey, ProFunctionDef>::ConstIterator it =
            m_functionDefs.replaceFunctions.constFind(func);
    if (it != m_functionDefs.replaceFunctions.constEnd()) {
        const QList<ProStringList> args = prepareFunctionArgs(tokPtr);
        traceMsg("calling $$%s(%s)", dbgKey(func), dbgStrListList(args));
        return evaluateFunction(*it, args, 0);
    }

    skipExpression(tokPtr);
    evalError(fL1S("'%1' is not a recognized replace function.").arg(func.toQString(m_tmp1)));
    return ProStringList();
}

bool QMakeEvaluator::evaluateConditional(const QString &cond, const QString &where, int line)
{
    bool ret = false;
    ProFile *pro = m_parser->parsedProBlock(cond, where, line, QMakeParser::TestGrammar);
    if (pro) {
        if (pro->isOk()) {
            m_locationStack.push(m_current);
            ret = visitProBlock(pro, pro->tokPtr()) == ReturnTrue;
            m_current = m_locationStack.pop();
        }
        pro->deref();
    }
    return ret;
}

#ifdef PROEVALUATOR_FULL
void QMakeEvaluator::checkRequirements(const ProStringList &deps)
{
    ProStringList &failed = valuesRef(ProKey("QMAKE_FAILED_REQUIREMENTS"));
    foreach (const ProString &dep, deps)
        if (!evaluateConditional(dep.toQString(), m_current.pro->fileName(), m_current.line))
            failed << dep;
}
#endif

ProValueMap *QMakeEvaluator::findValues(const ProKey &variableName, ProValueMap::Iterator *rit)
{
    ProValueMapStack::Iterator vmi = m_valuemapStack.end();
    do {
        --vmi;
        ProValueMap::Iterator it = (*vmi).find(variableName);
        if (it != (*vmi).end()) {
            if (it->constBegin() == statics.fakeValue.constBegin())
                return 0;
            *rit = it;
            return &(*vmi);
        }
    } while (vmi != m_valuemapStack.begin());
    return 0;
}

ProStringList &QMakeEvaluator::valuesRef(const ProKey &variableName)
{
    ProValueMap::Iterator it = m_valuemapStack.top().find(variableName);
    if (it != m_valuemapStack.top().end()) {
        if (it->constBegin() == statics.fakeValue.constBegin())
            it->clear();
        return *it;
    }
    ProValueMapStack::Iterator vmi = m_valuemapStack.end();
    if (--vmi != m_valuemapStack.begin()) {
        do {
            --vmi;
            ProValueMap::ConstIterator it = (*vmi).constFind(variableName);
            if (it != (*vmi).constEnd()) {
                ProStringList &ret = m_valuemapStack.top()[variableName];
                if (it->constBegin() != statics.fakeValue.constBegin())
                    ret = *it;
                return ret;
            }
        } while (vmi != m_valuemapStack.begin());
    }
    return m_valuemapStack.top()[variableName];
}

ProStringList QMakeEvaluator::values(const ProKey &variableName) const
{
    ProValueMapStack::ConstIterator vmi = m_valuemapStack.constEnd();
    do {
        --vmi;
        ProValueMap::ConstIterator it = (*vmi).constFind(variableName);
        if (it != (*vmi).constEnd()) {
            if (it->constBegin() == statics.fakeValue.constBegin())
                break;
            return *it;
        }
    } while (vmi != m_valuemapStack.constBegin());
    return ProStringList();
}

ProString QMakeEvaluator::first(const ProKey &variableName) const
{
    const ProStringList &vals = values(variableName);
    if (!vals.isEmpty())
        return vals.first();
    return ProString();
}

bool QMakeEvaluator::evaluateFile(
        const QString &fileName, QMakeHandler::EvalFileType type, LoadFlags flags)
{
    if (ProFile *pro = m_parser->parsedProFile(fileName, true)) {
        m_locationStack.push(m_current);
        bool ok = (visitProFile(pro, type, flags) == ReturnTrue);
        m_current = m_locationStack.pop();
        pro->deref();
#ifdef PROEVALUATOR_FULL
        if (ok) {
            ProStringList &iif = m_valuemapStack.first()[ProKey("QMAKE_INTERNAL_INCLUDED_FILES")];
            ProString ifn(fileName);
            if (!iif.contains(ifn))
                iif << ifn;
        }
#endif
        return ok;
    } else {
        if (!(flags & LoadSilent) && !IoUtils::exists(fileName))
            evalError(fL1S("WARNING: Include file %1 not found").arg(fileName));
        return false;
    }
}

bool QMakeEvaluator::evaluateFileChecked(
        const QString &fileName, QMakeHandler::EvalFileType type, LoadFlags flags)
{
    if (fileName.isEmpty())
        return false;
    QMakeEvaluator *ref = this;
    do {
        foreach (const ProFile *pf, ref->m_profileStack)
            if (pf->fileName() == fileName) {
                evalError(fL1S("Circular inclusion of %1.").arg(fileName));
                return false;
            }
    } while ((ref = ref->m_caller));
    return evaluateFile(fileName, type, flags);
}

bool QMakeEvaluator::evaluateFeatureFile(const QString &fileName, bool silent)
{
    QString fn = fileName;
    if (!fn.endsWith(QLatin1String(".prf")))
        fn += QLatin1String(".prf");

    if (m_featureRoots.isEmpty())
        updateFeaturePaths();
    int start_root = 0;
    QString currFn = currentFileName();
    if (IoUtils::fileName(currFn) == IoUtils::fileName(fn)) {
        for (int root = 0; root < m_featureRoots.size(); ++root)
            if (currFn == m_featureRoots.at(root) + fn) {
                start_root = root + 1;
                break;
            }
    }
    for (int root = start_root; root < m_featureRoots.size(); ++root) {
        QString fname = m_featureRoots.at(root) + fn;
        if (IoUtils::exists(fname)) {
            fn = fname;
            goto cool;
        }
    }
#ifdef QMAKE_BUILTIN_PRFS
    fn.prepend(QLatin1String(":/qmake/features/"));
    if (QFileInfo(fn).exists())
        goto cool;
#endif
    if (!silent)
        evalError(fL1S("Cannot find feature %1").arg(fileName));
    return false;

  cool:
    ProStringList &already = valuesRef(ProKey("QMAKE_INTERNAL_INCLUDED_FEATURES"));
    ProString afn(fn);
    if (already.contains(afn)) {
        if (!silent)
            languageWarning(fL1S("Feature %1 already included").arg(fileName));
        return true;
    }
    already.append(afn);

#ifdef PROEVALUATOR_CUMULATIVE
    bool cumulative = m_cumulative;
    m_cumulative = false;
#endif

    // The path is fully normalized already.
    bool ok = evaluateFile(fn, QMakeHandler::EvalFeatureFile, LoadProOnly);

#ifdef PROEVALUATOR_CUMULATIVE
    m_cumulative = cumulative;
#endif
    return ok;
}

bool QMakeEvaluator::evaluateFileInto(const QString &fileName, ProValueMap *values, LoadFlags flags)
{
    QMakeEvaluator visitor(m_option, m_parser, m_handler);
    visitor.m_caller = this;
    visitor.m_outputDir = m_outputDir;
    visitor.m_featureRoots = m_featureRoots;
    if (!visitor.evaluateFileChecked(fileName, QMakeHandler::EvalAuxFile, flags))
        return false;
    *values = visitor.m_valuemapStack.top();
#ifdef PROEVALUATOR_FULL
    ProKey qiif("QMAKE_INTERNAL_INCLUDED_FILES");
    ProStringList &iif = m_valuemapStack.first()[qiif];
    foreach (const ProString &ifn, values->value(qiif))
        if (!iif.contains(ifn))
            iif << ifn;
#endif
    return true;
}

void QMakeEvaluator::message(int type, const QString &msg) const
{
    if (!m_skipLevel)
        m_handler->message(type, msg,
                m_current.line ? m_current.pro->fileName() : QString(),
                m_current.line != 0xffff ? m_current.line : -1);
}

#ifdef PROEVALUATOR_DEBUG
void QMakeEvaluator::debugMsgInternal(int level, const char *fmt, ...) const
{
    va_list ap;

    if (level <= m_debugLevel) {
        fprintf(stderr, "DEBUG %d: ", level);
        va_start(ap, fmt);
        vfprintf(stderr, fmt, ap);
        va_end(ap);
        fputc('\n', stderr);
    }
}

void QMakeEvaluator::traceMsgInternal(const char *fmt, ...) const
{
    va_list ap;

    if (!m_current.pro)
        fprintf(stderr, "DEBUG 1: ");
    else if (m_current.line <= 0)
        fprintf(stderr, "DEBUG 1: %s: ", qPrintable(m_current.pro->fileName()));
    else
        fprintf(stderr, "DEBUG 1: %s:%d: ", qPrintable(m_current.pro->fileName()), m_current.line);
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fputc('\n', stderr);
}

QString QMakeEvaluator::formatValue(const ProString &val, bool forceQuote)
{
    QString ret;
    ret.reserve(val.size() + 2);
    const QChar *chars = val.constData();
    bool quote = forceQuote || val.isEmpty();
    for (int i = 0, l = val.size(); i < l; i++) {
        QChar c = chars[i];
        ushort uc = c.unicode();
        if (uc < 32) {
            switch (uc) {
            case '\r':
                ret += QLatin1String("\\r");
                break;
            case '\n':
                ret += QLatin1String("\\n");
                break;
            case '\t':
                ret += QLatin1String("\\t");
                break;
            default:
                ret += QString::fromLatin1("\\x%1").arg(uc, 2, 16, QLatin1Char('0'));
                break;
            }
        } else {
            switch (uc) {
            case '\\':
                ret += QLatin1String("\\\\");
                break;
            case '"':
                ret += QLatin1String("\\\"");
                break;
            case '\'':
                ret += QLatin1String("\\'");
                break;
            case 32:
                quote = true;
                // fallthrough
            default:
                ret += c;
                break;
            }
        }
    }
    if (quote) {
        ret.prepend(QLatin1Char('"'));
        ret.append(QLatin1Char('"'));
    }
    return ret;
}

QString QMakeEvaluator::formatValueList(const ProStringList &vals, bool commas)
{
    QString ret;

    foreach (const ProString &str, vals) {
        if (!ret.isEmpty()) {
            if (commas)
                ret += QLatin1Char(',');
            ret += QLatin1Char(' ');
        }
        ret += formatValue(str);
    }
    return ret;
}

QString QMakeEvaluator::formatValueListList(const QList<ProStringList> &lists)
{
    QString ret;

    foreach (const ProStringList &list, lists) {
        if (!ret.isEmpty())
            ret += QLatin1String(", ");
        ret += formatValueList(list);
    }
    return ret;
}
#endif

QT_END_NAMESPACE
