From d077ba29c34782d1699693b6e3f07c2037eecdba Mon Sep 17 00:00:00 2001
From: Oswald Buddenhagen <oswald.buddenhagen@nokia.com>
Date: Mon, 18 May 2009 17:46:30 +0200
Subject: [PATCH] support custom functions: implement defineTest(),
 defineReplace(), defined(), return() & export()

---
 src/shared/proparser/abstractproitemvisitor.h |   9 +-
 src/shared/proparser/profileevaluator.cpp     | 295 ++++++++++++++----
 src/shared/proparser/proitems.cpp             |  61 ++--
 src/shared/proparser/proitems.h               |  30 +-
 4 files changed, 284 insertions(+), 111 deletions(-)

diff --git a/src/shared/proparser/abstractproitemvisitor.h b/src/shared/proparser/abstractproitemvisitor.h
index d3904fd7029..9e8e0582b28 100644
--- a/src/shared/proparser/abstractproitemvisitor.h
+++ b/src/shared/proparser/abstractproitemvisitor.h
@@ -37,17 +37,18 @@ QT_BEGIN_NAMESPACE
 struct AbstractProItemVisitor
 {
     virtual ~AbstractProItemVisitor() {}
-    virtual void visitBeginProBlock(ProBlock *block) = 0;
+
+    virtual ProItem::ProItemReturn visitBeginProBlock(ProBlock *block) = 0;
     virtual void visitEndProBlock(ProBlock *block) = 0;
 
     virtual void visitBeginProVariable(ProVariable *variable) = 0;
     virtual void visitEndProVariable(ProVariable *variable) = 0;
 
-    virtual bool visitBeginProFile(ProFile *value) = 0;
-    virtual bool visitEndProFile(ProFile *value) = 0;
+    virtual ProItem::ProItemReturn visitBeginProFile(ProFile *value) = 0;
+    virtual ProItem::ProItemReturn visitEndProFile(ProFile *value) = 0;
 
     virtual void visitProValue(ProValue *value) = 0;
-    virtual void visitProFunction(ProFunction *function) = 0;
+    virtual ProItem::ProItemReturn visitProFunction(ProFunction *function) = 0;
     virtual void visitProOperator(ProOperator *function) = 0;
     virtual void visitProCondition(ProCondition *function) = 0;
 };
diff --git a/src/shared/proparser/profileevaluator.cpp b/src/shared/proparser/profileevaluator.cpp
index 6947f3e81ff..40cf6e5cef5 100644
--- a/src/shared/proparser/profileevaluator.cpp
+++ b/src/shared/proparser/profileevaluator.cpp
@@ -157,14 +157,14 @@ public:
     /////////////// Evaluating pro file contents
 
     // implementation of AbstractProItemVisitor
-    void visitBeginProBlock(ProBlock *block);
+    ProItem::ProItemReturn visitBeginProBlock(ProBlock *block);
     void visitEndProBlock(ProBlock *block);
     void visitBeginProVariable(ProVariable *variable);
     void visitEndProVariable(ProVariable *variable);
-    bool visitBeginProFile(ProFile *value);
-    bool visitEndProFile(ProFile *value);
+    ProItem::ProItemReturn visitBeginProFile(ProFile *value);
+    ProItem::ProItemReturn visitEndProFile(ProFile *value);
     void visitProValue(ProValue *value);
-    void visitProFunction(ProFunction *function);
+    ProItem::ProItemReturn visitProFunction(ProFunction *function);
     void visitProOperator(ProOperator *oper);
     void visitProCondition(ProCondition *condition);
 
@@ -187,10 +187,15 @@ public:
     QString currentDirectory() const;
     ProFile *currentProFile() const;
 
-    bool evaluateConditionalFunction(const QString &function, const QString &arguments);
+    ProItem::ProItemReturn evaluateConditionalFunction(const QString &function, const QString &arguments);
     bool evaluateFile(const QString &fileName);
     bool evaluateFeatureFile(const QString &fileName);
 
+    static inline ProItem::ProItemReturn returnBool(bool b)
+        { return b ? ProItem::ReturnTrue : ProItem::ReturnFalse; }
+
+    QStringList evaluateFunction(ProBlock *funcPtr, const QStringList &argumentsList, bool *ok);
+
     QStringList qmakeFeaturePaths();
 
     struct State {
@@ -217,6 +222,14 @@ public:
     QHash<QString, QString> m_properties;
     QString m_outputDir;
 
+    bool m_definingTest;
+    QString m_definingFunc;
+    QHash<QString, ProBlock *> m_testFunctions;
+    QHash<QString, ProBlock *> m_replaceFunctions;
+    QStringList m_returnValue;
+    QStack<QHash<QString, QStringList> > m_valuemapStack;
+    QStack<QHash<const ProFile*, QHash<QString, QStringList> > > m_filevaluemapStack;
+
     int m_prevLineNo;                               // Checking whether we're assigning the same TARGET
     ProFile *m_prevProFile;                         // See m_prevLineNo
     QStringList m_addUserConfigCmdArgs;
@@ -236,6 +249,7 @@ ProFileEvaluator::Private::Private(ProFileEvaluator *q_)
     // Configuration, more or less
     m_verbose = true;
     m_cumulative = true;
+    m_parsePreAndPostFiles = true;
 
     // Evaluator state
     m_sts.condition = false;
@@ -243,7 +257,7 @@ ProFileEvaluator::Private::Private(ProFileEvaluator *q_)
     m_invertNext = false;
     m_skipLevel = 0;
     m_isFirstVariableValue = true;
-    m_parsePreAndPostFiles = true;
+    m_definingFunc.clear();
 }
 
 bool ProFileEvaluator::Private::read(ProFile *pro)
@@ -555,13 +569,27 @@ void ProFileEvaluator::Private::updateItem()
 }
 
 
-void ProFileEvaluator::Private::visitBeginProBlock(ProBlock *block)
+ProItem::ProItemReturn ProFileEvaluator::Private::visitBeginProBlock(ProBlock *block)
 {
     if (block->blockKind() & ProBlock::ScopeContentsKind) {
-        if (!m_sts.condition)
-            ++m_skipLevel;
-        else
-            Q_ASSERT(!m_skipLevel);
+        if (!m_definingFunc.isEmpty()) {
+            if (!m_skipLevel || m_cumulative) {
+                QHash<QString, ProBlock *> *hash =
+                        (m_definingTest ? &m_testFunctions : &m_replaceFunctions);
+                if (ProBlock *def = hash->value(m_definingFunc))
+                    def->deref();
+                hash->insert(m_definingFunc, block);
+                block->ref();
+                block->setBlockKind(block->blockKind() | ProBlock::FunctionBodyKind);
+            }
+            m_definingFunc.clear();
+            return ProItem::ReturnSkip;
+        } else if (!(block->blockKind() & ProBlock::FunctionBodyKind)) {
+            if (!m_sts.condition)
+                ++m_skipLevel;
+            else
+                Q_ASSERT(!m_skipLevel);
+        }
     } else {
         if (!m_skipLevel) {
             if (m_sts.condition) {
@@ -572,11 +600,13 @@ void ProFileEvaluator::Private::visitBeginProBlock(ProBlock *block)
             Q_ASSERT(!m_sts.condition);
         }
     }
+    return ProItem::ReturnTrue;
 }
 
 void ProFileEvaluator::Private::visitEndProBlock(ProBlock *block)
 {
-    if (block->blockKind() & ProBlock::ScopeContentsKind) {
+    if ((block->blockKind() & ProBlock::ScopeContentsKind)
+        && !(block->blockKind() & ProBlock::FunctionBodyKind)) {
         if (m_skipLevel) {
             Q_ASSERT(!m_sts.condition);
             --m_skipLevel;
@@ -624,10 +654,9 @@ void ProFileEvaluator::Private::visitProCondition(ProCondition *cond)
     m_invertNext = false;
 }
 
-bool ProFileEvaluator::Private::visitBeginProFile(ProFile * pro)
+ProItem::ProItemReturn ProFileEvaluator::Private::visitBeginProFile(ProFile * pro)
 {
     PRE(pro);
-    bool ok = true;
     m_lineNo = pro->lineNumber();
     if (m_origfile.isEmpty())
         m_origfile = pro->fileName();
@@ -655,16 +684,15 @@ bool ProFileEvaluator::Private::visitBeginProFile(ProFile * pro)
             m_cumulative = cumulative;
         }
 
-        ok = QDir::setCurrent(pro->directoryName());
+        return returnBool(QDir::setCurrent(pro->directoryName()));
     }
 
-    return ok;
+    return ProItem::ReturnTrue;
 }
 
-bool ProFileEvaluator::Private::visitEndProFile(ProFile * pro)
+ProItem::ProItemReturn ProFileEvaluator::Private::visitEndProFile(ProFile * pro)
 {
     PRE(pro);
-    bool ok = true;
     m_lineNo = pro->lineNumber();
     if (m_profileStack.count() == 1 && !m_oldPath.isEmpty()) {
         const QString &mkspecDirectory = propertyValue(QLatin1String("QMAKE_MKSPECS"));
@@ -693,13 +721,21 @@ bool ProFileEvaluator::Private::visitEndProFile(ProFile * pro)
                     break;
             }
 
+            foreach (ProBlock *itm, m_replaceFunctions)
+                itm->deref();
+            m_replaceFunctions.clear();
+            foreach (ProBlock *itm, m_testFunctions)
+                itm->deref();
+            m_testFunctions.clear();
+
             m_cumulative = cumulative;
         }
 
         m_profileStack.pop();
-        ok = QDir::setCurrent(m_oldPath);
+        return returnBool(QDir::setCurrent(m_oldPath));
     }
-    return ok;
+
+    return ProItem::ReturnTrue;
 }
 
 static void replaceInList(QStringList *varlist,
@@ -854,7 +890,7 @@ void ProFileEvaluator::Private::visitProValue(ProValue *value)
     m_isFirstVariableValue = false;
 }
 
-void ProFileEvaluator::Private::visitProFunction(ProFunction *func)
+ProItem::ProItemReturn ProFileEvaluator::Private::visitProFunction(ProFunction *func)
 {
     // Make sure that called subblocks don't inherit & destroy the state
     bool invertThis = m_invertNext;
@@ -869,10 +905,13 @@ void ProFileEvaluator::Private::visitProFunction(ProFunction *func)
         QString arguments = text.mid(lparen + 1, rparen - lparen - 1);
         QString funcName = text.left(lparen);
         m_lineNo = func->lineNumber();
-        bool result = evaluateConditionalFunction(funcName.trimmed(), arguments);
-        if (!m_skipLevel && (result ^ invertThis))
+        ProItem::ProItemReturn result = evaluateConditionalFunction(funcName.trimmed(), arguments);
+        if (result != ProItem::ReturnFalse && result != ProItem::ReturnTrue)
+            return result;
+        if (!m_skipLevel && ((result == ProItem::ReturnTrue) ^ invertThis))
             m_sts.condition = true;
     }
+    return ProItem::ReturnTrue;
 }
 
 
@@ -1212,10 +1251,49 @@ bool ProFileEvaluator::Private::isActiveConfig(const QString &config, bool regex
     return false;
 }
 
+QStringList ProFileEvaluator::Private::evaluateFunction(
+        ProBlock *funcPtr, const QStringList &argumentsList, bool *ok)
+{
+    bool oki;
+    QStringList ret;
+
+    if (m_valuemapStack.count() >= 100) {
+        q->errorMessage(format("ran into infinite recursion (depth > 100)."));
+        ok = false;
+    } else {
+        State sts = m_sts;
+        m_valuemapStack.push(m_valuemap);
+        m_filevaluemapStack.push(m_filevaluemap);
+
+        QStringList args;
+        for (int i = 0; i < argumentsList.count(); ++i) {
+            QStringList theArgs = expandVariableReferences(argumentsList[i]);
+            args += theArgs;
+            m_valuemap[QString::number(i+1)] = theArgs;
+        }
+        m_valuemap[QLatin1String("ARGS")] = args;
+        oki = (funcPtr->Accept(this) != ProItem::ReturnFalse); // True || Return
+        ret = m_returnValue;
+        m_returnValue.clear();
+
+        m_valuemap = m_valuemapStack.pop();
+        m_filevaluemap = m_filevaluemapStack.pop();
+        m_sts = sts;
+    }
+    if (ok)
+        *ok = oki;
+    if (oki)
+        return ret;
+    return QStringList();
+}
+
 QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &func, const QString &arguments)
 {
     QStringList argumentsList = split_arg_list(arguments);
 
+    if (ProBlock *funcPtr = m_replaceFunctions.value(func, 0))
+        return evaluateFunction(funcPtr, argumentsList, 0);
+
     QStringList args;
     for (int i = 0; i < argumentsList.count(); ++i)
         args += expandVariableReferences(argumentsList[i]).join(Option::field_sep);
@@ -1618,13 +1696,40 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun
     return ret;
 }
 
-bool ProFileEvaluator::Private::evaluateConditionalFunction(
+ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction(
         const QString &function, const QString &arguments)
 {
     QStringList argumentsList = split_arg_list(arguments);
+
+    if (ProBlock *funcPtr = m_testFunctions.value(function, 0)) {
+        bool ok;
+        QStringList ret = evaluateFunction(funcPtr, argumentsList, &ok);
+        if (ok) {
+            if (ret.isEmpty()) {
+                return ProItem::ReturnTrue;
+            } else {
+                if (ret.first() != QLatin1String("false")) {
+                    if (ret.first() == QLatin1String("true")) {
+                        return ProItem::ReturnTrue;
+                    } else {
+                        bool ok;
+                        int val = ret.first().toInt(&ok);
+                        if (ok) {
+                            if (val)
+                                return ProItem::ReturnTrue;
+                        } else {
+                            q->logMessage(format("Unexpected return value from test '%1': %2")
+                                          .arg(function).arg(ret.join(QLatin1String(" :: "))));
+                        }
+                    }
+                }
+            }
+        }
+        return ProItem::ReturnFalse;
+    }
+
     QString sep;
     sep.append(Option::field_sep);
-
     QStringList args;
     for (int i = 0; i < argumentsList.count(); ++i)
         args += expandVariableReferences(argumentsList[i]).join(sep);
@@ -1632,7 +1737,8 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(
     enum TestFunc { T_REQUIRES=1, T_GREATERTHAN, T_LESSTHAN, T_EQUALS,
                     T_EXISTS, T_EXPORT, T_CLEAR, T_UNSET, T_EVAL, T_CONFIG, T_SYSTEM,
                     T_RETURN, T_BREAK, T_NEXT, T_DEFINED, T_CONTAINS, T_INFILE,
-                    T_COUNT, T_ISEMPTY, T_INCLUDE, T_LOAD, T_DEBUG, T_MESSAGE, T_IF };
+                    T_COUNT, T_ISEMPTY, T_INCLUDE, T_LOAD, T_DEBUG, T_MESSAGE, T_IF,
+                    T_DEFINE_TEST, T_DEFINE_REPLACE };
 
     static QHash<QString, int> *functions = 0;
     if (!functions) {
@@ -1665,35 +1771,87 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(
         functions->insert(QLatin1String("message"), T_MESSAGE);   //v
         functions->insert(QLatin1String("warning"), T_MESSAGE);   //v
         functions->insert(QLatin1String("error"), T_MESSAGE);     //v
+        functions->insert(QLatin1String("defineTest"), T_DEFINE_TEST);        //v
+        functions->insert(QLatin1String("defineReplace"), T_DEFINE_REPLACE);  //v
     }
 
     TestFunc func_t = (TestFunc)functions->value(function);
 
     switch (func_t) {
+        case T_DEFINE_TEST:
+            m_definingTest = true;
+            goto defineFunc;
+        case T_DEFINE_REPLACE:
+            m_definingTest = false;
+          defineFunc:
+            if (args.count() != 1) {
+                q->logMessage(format("%s(function) requires one argument.").arg(function));
+                return ProItem::ReturnFalse;
+            }
+            m_definingFunc = args.first();
+            return ProItem::ReturnTrue;
+        case T_DEFINED:
+            if (args.count() < 1 || args.count() > 2) {
+                q->logMessage(format("defined(function, [\"test\"|\"replace\"])"
+                                     " requires one or two arguments."));
+                return ProItem::ReturnFalse;
+            }
+            if (args.count() > 1) {
+                if (args[1] == QLatin1String("test"))
+                    return returnBool(m_testFunctions.contains(args[0]));
+                else if (args[1] == QLatin1String("replace"))
+                    return returnBool(m_replaceFunctions.contains(args[0]));
+                q->logMessage(format("defined(function, type):"
+                                     " unexpected type [%1].\n").arg(args[1]));
+                return ProItem::ReturnFalse;
+            }
+            return returnBool(m_replaceFunctions.contains(args[0])
+                              || m_testFunctions.contains(args[0]));
+        case T_RETURN:
+            m_returnValue = args;
+            // It is "safe" to ignore returns - due to qmake brokeness
+            // they cannot be used to terminate loops anyway.
+            if (m_skipLevel || m_cumulative)
+                return ProItem::ReturnTrue;
+            if (m_valuemapStack.isEmpty()) {
+                q->logMessage(format("unexpected return()."));
+                return ProItem::ReturnFalse;
+            }
+            return ProItem::ReturnReturn;
+        case T_EXPORT:
+            if (m_skipLevel && !m_cumulative)
+                return ProItem::ReturnTrue;
+            if (args.count() != 1) {
+                q->logMessage(format("export(variable) requires one argument."));
+                return ProItem::ReturnFalse;
+            }
+            for (int i = 0; i < m_valuemapStack.size(); ++i) {
+                m_valuemapStack[i][args[0]] = m_valuemap[args[0]];
+                m_filevaluemapStack[i][currentProFile()][args[0]] =
+                        m_filevaluemap[currentProFile()][args[0]];
+            }
+            return ProItem::ReturnTrue;
 #if 0
         case T_INFILE:
         case T_REQUIRES:
         case T_GREATERTHAN:
         case T_LESSTHAN:
         case T_EQUALS:
-        case T_EXPORT:
         case T_CLEAR:
         case T_UNSET:
         case T_EVAL:
         case T_IF:
-        case T_RETURN:
         case T_BREAK:
         case T_NEXT:
-        case T_DEFINED:
 #endif
         case T_CONFIG: {
             if (args.count() < 1 || args.count() > 2) {
                 q->logMessage(format("CONFIG(config) requires one or two arguments."));
-                return false;
+                return ProItem::ReturnFalse;
             }
             if (args.count() == 1) {
                 //cond = isActiveConfig(args.first()); XXX
-                return false;
+                return ProItem::ReturnFalse;
             }
             const QStringList mutuals = args[1].split(QLatin1Char('|'));
             const QStringList &configs = valuesDirect(QLatin1String("CONFIG"));
@@ -1701,16 +1859,16 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(
             for (int i = configs.size() - 1; i >= 0; i--) {
                 for (int mut = 0; mut < mutuals.count(); mut++) {
                     if (configs[i] == mutuals[mut].trimmed()) {
-                        return (configs[i] == args[0]);
+                        return returnBool(configs[i] == args[0]);
                     }
                 }
             }
-            return false;
+            return ProItem::ReturnFalse;
         }
         case T_CONTAINS: {
             if (args.count() < 2 || args.count() > 3) {
                 q->logMessage(format("contains(var, val) requires two or three arguments."));
-                return false;
+                return ProItem::ReturnFalse;
             }
 
             QRegExp regx(args[1]);
@@ -1719,7 +1877,7 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(
                 for (int i = 0; i < l.size(); ++i) {
                     const QString val = l[i];
                     if (regx.exactMatch(val) || val == args[1]) {
-                        return true;
+                        return ProItem::ReturnTrue;
                     }
                 }
             } else {
@@ -1728,47 +1886,47 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(
                     const QString val = l[i];
                     for (int mut = 0; mut < mutuals.count(); mut++) {
                         if (val == mutuals[mut].trimmed()) {
-                            return (regx.exactMatch(val) || val == args[1]);
+                            return returnBool(regx.exactMatch(val) || val == args[1]);
                         }
                     }
                 }
             }
-            return false;
+            return ProItem::ReturnFalse;
         }
         case T_COUNT: {
             if (args.count() != 2 && args.count() != 3) {
                 q->logMessage(format("count(var, count, op=\"equals\") requires two or three arguments."));
-                return false;
+                return ProItem::ReturnFalse;
             }
             if (args.count() == 3) {
                 QString comp = args[2];
                 if (comp == QLatin1String(">") || comp == QLatin1String("greaterThan")) {
-                    return (values(args.first()).count() > args[1].toInt());
+                    return returnBool(values(args.first()).count() > args[1].toInt());
                 } else if (comp == QLatin1String(">=")) {
-                    return (values(args.first()).count() >= args[1].toInt());
+                    return returnBool(values(args.first()).count() >= args[1].toInt());
                 } else if (comp == QLatin1String("<") || comp == QLatin1String("lessThan")) {
-                    return (values(args.first()).count() < args[1].toInt());
+                    return returnBool(values(args.first()).count() < args[1].toInt());
                 } else if (comp == QLatin1String("<=")) {
-                    return (values(args.first()).count() <= args[1].toInt());
+                    return returnBool(values(args.first()).count() <= args[1].toInt());
                 } else if (comp == QLatin1String("equals") || comp == QLatin1String("isEqual")
                            || comp == QLatin1String("=") || comp == QLatin1String("==")) {
-                    return (values(args.first()).count() == args[1].toInt());
+                    return returnBool(values(args.first()).count() == args[1].toInt());
                 } else {
                     q->logMessage(format("unexpected modifier to count(%2)").arg(comp));
-                    return false;
+                    return ProItem::ReturnFalse;
                 }
             }
-            return (values(args.first()).count() == args[1].toInt());
+            return returnBool(values(args.first()).count() == args[1].toInt());
         }
         case T_INCLUDE: {
             if (m_skipLevel && !m_cumulative)
-                return false;
+                return ProItem::ReturnFalse;
             QString parseInto;
             if (args.count() == 2) {
                 parseInto = args[1];
             } else if (args.count() != 1) {
                 q->logMessage(format("include(file) requires one or two arguments."));
-                return false;
+                return ProItem::ReturnFalse;
             }
             QString fileName = args.first();
             // ### this breaks if we have include(c:/reallystupid.pri) but IMHO that's really bad style.
@@ -1777,11 +1935,11 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(
             State sts = m_sts;
             bool ok = evaluateFile(fileName);
             m_sts = sts;
-            return ok;
+            return returnBool(ok);
         }
         case T_LOAD: {
             if (m_skipLevel && !m_cumulative)
-                return false;
+                return ProItem::ReturnFalse;
             QString parseInto;
             bool ignore_error = false;
             if (args.count() == 2) {
@@ -1789,18 +1947,18 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(
                 ignore_error = (sarg.toLower() == QLatin1String("true") || sarg.toInt());
             } else if (args.count() != 1) {
                 q->logMessage(format("load(feature) requires one or two arguments."));
-                return false;
+                return ProItem::ReturnFalse;
             }
             // XXX ignore_error unused
-            return evaluateFeatureFile(args.first());
+            return returnBool(evaluateFeatureFile(args.first()));
         }
         case T_DEBUG:
             // Yup - do nothing. Nothing is going to enable debug output anyway.
-            return false;
+            return ProItem::ReturnFalse;
         case T_MESSAGE: {
             if (args.count() != 1) {
                 q->logMessage(format("%1(message) requires one argument.").arg(function));
-                return false;
+                return ProItem::ReturnFalse;
             }
             QString msg = fixEnvVariables(args.first());
             if (function == QLatin1String("error")) {
@@ -1817,42 +1975,42 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(
             } else {
                 q->fileMessage(format("Project MESSAGE: %1").arg(msg));
             }
-            return false;
+            return ProItem::ReturnFalse;
         }
 #if 0 // Way too dangerous to enable.
         case T_SYSTEM: {
             if (args.count() != 1) {
                 q->logMessage(format("system(exec) requires one argument."));
-                false;
+                ProItem::ReturnFalse;
             }
-            return (system(args.first().toLatin1().constData()) == 0);
+            return returnBool(system(args.first().toLatin1().constData()) == 0);
         }
 #endif
         case T_ISEMPTY: {
             if (args.count() != 1) {
                 q->logMessage(format("isEmpty(var) requires one argument."));
-                return false;
+                return ProItem::ReturnFalse;
             }
             QStringList sl = values(args.first());
             if (sl.count() == 0) {
-                return true;
+                return ProItem::ReturnTrue;
             } else if (sl.count() > 0) {
                 QString var = sl.first();
                 if (var.isEmpty())
-                    return true;
+                    return ProItem::ReturnTrue;
             }
-            return false;
+            return ProItem::ReturnFalse;
         }
         case T_EXISTS: {
             if (args.count() != 1) {
                 q->logMessage(format("exists(file) requires one argument."));
-                return false;
+                return ProItem::ReturnFalse;
             }
             QString file = args.first();
             file = Option::fixPathToLocalOS(file);
 
             if (QFile::exists(file)) {
-                return true;
+                return ProItem::ReturnTrue;
             }
             //regular expression I guess
             QString dirstr = currentDirectory();
@@ -1863,17 +2021,16 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(
             }
             if (file.contains(QLatin1Char('*')) || file.contains(QLatin1Char('?')))
                 if (!QDir(dirstr).entryList(QStringList(file)).isEmpty())
-                    return true;
+                    return ProItem::ReturnTrue;
 
-            return false;
+            return ProItem::ReturnFalse;
         }
         case 0:
-            // This is too chatty currently (missing defineTest and defineReplace)
-            //q->logMessage(format("'%1' is not a recognized test function").arg(function));
-            return false;
+            q->logMessage(format("'%1' is not a recognized test function").arg(function));
+            return ProItem::ReturnFalse;
         default:
             q->logMessage(format("Function '%1' is not implemented").arg(function));
-            return false;
+            return ProItem::ReturnFalse;
     }
 }
 
@@ -2032,7 +2189,7 @@ bool ProFileEvaluator::Private::evaluateFile(const QString &fileName)
     ProFile *pro = q->parsedProFile(fileName);
     if (pro) {
         m_profileStack.push(pro);
-        bool ok = pro->Accept(this);
+        bool ok = (pro->Accept(this) == ProItem::ReturnTrue);
         m_profileStack.pop();
         q->releaseParsedProFile(pro);
         return ok;
diff --git a/src/shared/proparser/proitems.cpp b/src/shared/proparser/proitems.cpp
index 11b7a9eb3d9..3b52a64a42f 100644
--- a/src/shared/proparser/proitems.cpp
+++ b/src/shared/proparser/proitems.cpp
@@ -46,15 +46,21 @@ QString ProItem::comment() const
 }
 
 // --------------- ProBlock ----------------
+
 ProBlock::ProBlock(ProBlock *parent)
 {
     m_blockKind = 0;
     m_parent = parent;
+    m_refCount = 1;
 }
 
 ProBlock::~ProBlock()
 {
-    qDeleteAll(m_proitems);
+    foreach (ProItem *itm, m_proitems)
+        if (itm->kind() == BlockKind)
+            static_cast<ProBlock *>(itm)->deref();
+        else
+            delete itm;
 }
 
 void ProBlock::appendItem(ProItem *proitem)
@@ -97,15 +103,16 @@ ProItem::ProItemKind ProBlock::kind() const
     return ProItem::BlockKind;
 }
 
-bool ProBlock::Accept(AbstractProItemVisitor *visitor)
+ProItem::ProItemReturn ProBlock::Accept(AbstractProItemVisitor *visitor)
 {
-    visitor->visitBeginProBlock(this);
-    foreach (ProItem *item, m_proitems) {
-        if (!item->Accept(visitor))
-            return false;
-    }
+    if (visitor->visitBeginProBlock(this) == ReturnSkip)
+        return ReturnTrue;
+    ProItemReturn rt = ReturnTrue;
+    foreach (ProItem *item, m_proitems)
+        if ((rt = item->Accept(visitor)) != ReturnTrue && rt != ReturnFalse)
+            break;
     visitor->visitEndProBlock(this);
-    return true;
+    return rt;
 }
 
 // --------------- ProVariable ----------------
@@ -137,15 +144,13 @@ QString ProVariable::variable() const
     return m_variable;
 }
 
-bool ProVariable::Accept(AbstractProItemVisitor *visitor)
+ProItem::ProItemReturn ProVariable::Accept(AbstractProItemVisitor *visitor)
 {
     visitor->visitBeginProVariable(this);
-    foreach (ProItem *item, m_proitems) {
-        if (!item->Accept(visitor))
-            return false;
-    }
+    foreach (ProItem *item, m_proitems)
+        item->Accept(visitor); // cannot fail
     visitor->visitEndProVariable(this);
-    return true;
+    return ReturnTrue;
 }
 
 // --------------- ProValue ----------------
@@ -180,10 +185,10 @@ ProItem::ProItemKind ProValue::kind() const
     return ProItem::ValueKind;
 }
 
-bool ProValue::Accept(AbstractProItemVisitor *visitor)
+ProItem::ProItemReturn ProValue::Accept(AbstractProItemVisitor *visitor)
 {
     visitor->visitProValue(this);
-    return true;
+    return ReturnTrue;
 }
 
 // --------------- ProFunction ----------------
@@ -207,10 +212,9 @@ ProItem::ProItemKind ProFunction::kind() const
     return ProItem::FunctionKind;
 }
 
-bool ProFunction::Accept(AbstractProItemVisitor *visitor)
+ProItem::ProItemReturn ProFunction::Accept(AbstractProItemVisitor *visitor)
 {
-    visitor->visitProFunction(this);
-    return true;
+    return visitor->visitProFunction(this);
 }
 
 // --------------- ProCondition ----------------
@@ -234,10 +238,10 @@ ProItem::ProItemKind ProCondition::kind() const
     return ProItem::ConditionKind;
 }
 
-bool ProCondition::Accept(AbstractProItemVisitor *visitor)
+ProItem::ProItemReturn ProCondition::Accept(AbstractProItemVisitor *visitor)
 {
     visitor->visitProCondition(this);
-    return true;
+    return ReturnTrue;
 }
 
 // --------------- ProOperator ----------------
@@ -261,10 +265,10 @@ ProItem::ProItemKind ProOperator::kind() const
     return ProItem::OperatorKind;
 }
 
-bool ProOperator::Accept(AbstractProItemVisitor *visitor)
+ProItem::ProItemReturn ProOperator::Accept(AbstractProItemVisitor *visitor)
 {
     visitor->visitProOperator(this);
-    return true;
+    return ReturnTrue;
 }
 
 // --------------- ProFile ----------------
@@ -309,13 +313,12 @@ bool ProFile::isModified() const
     return m_modified;
 }
 
-bool ProFile::Accept(AbstractProItemVisitor *visitor)
+ProItem::ProItemReturn ProFile::Accept(AbstractProItemVisitor *visitor)
 {
-    visitor->visitBeginProFile(this);
-    foreach (ProItem *item, m_proitems) {
-        if (!item->Accept(visitor))
-            return false;
-    }
+    ProItemReturn rt;
+    if ((rt = visitor->visitBeginProFile(this)) != ReturnTrue)
+        return rt;
+    ProBlock::Accept(visitor); // cannot fail
     return visitor->visitEndProFile(this);
 }
 
diff --git a/src/shared/proparser/proitems.h b/src/shared/proparser/proitems.h
index 845711d228a..b0aceef8179 100644
--- a/src/shared/proparser/proitems.h
+++ b/src/shared/proparser/proitems.h
@@ -49,6 +49,13 @@ public:
         BlockKind
     };
 
+    enum ProItemReturn {
+        ReturnFalse,
+        ReturnTrue,
+        ReturnSkip,
+        ReturnReturn
+   };
+
     ProItem() : m_lineNumber(0) {}
     virtual ~ProItem() {}
 
@@ -57,7 +64,7 @@ public:
     void setComment(const QString &comment);
     QString comment() const;
 
-    virtual bool Accept(AbstractProItemVisitor *visitor) = 0;
+    virtual ProItemReturn Accept(AbstractProItemVisitor *visitor) = 0;
     int lineNumber() const { return m_lineNumber; }
     void setLineNumber(int lineNumber) { m_lineNumber = lineNumber; }
 
@@ -75,7 +82,8 @@ public:
         ScopeContentsKind   = 0x02,
         VariableKind        = 0x04,
         ProFileKind         = 0x08,
-        SingleLine          = 0x10
+        FunctionBodyKind    = 0x10,
+        SingleLine          = 0x80
     };
 
     ProBlock(ProBlock *parent);
@@ -91,14 +99,18 @@ public:
     void setParent(ProBlock *parent);
     ProBlock *parent() const;
 
+    void ref() { ++m_refCount; }
+    void deref() { if (!--m_refCount) delete this; }
+
     ProItem::ProItemKind kind() const;
 
-    virtual bool Accept(AbstractProItemVisitor *visitor);
+    virtual ProItemReturn Accept(AbstractProItemVisitor *visitor);
 protected:
     QList<ProItem *> m_proitems;
 private:
     ProBlock *m_parent;
     int m_blockKind;
+    int m_refCount;
 };
 
 class ProVariable : public ProBlock
@@ -120,7 +132,7 @@ public:
     void setVariable(const QString &name);
     QString variable() const;
 
-    virtual bool Accept(AbstractProItemVisitor *visitor);
+    virtual ProItemReturn Accept(AbstractProItemVisitor *visitor);
 private:
     VariableOperator m_variableKind;
     QString m_variable;
@@ -139,7 +151,7 @@ public:
 
     ProItem::ProItemKind kind() const;
 
-    virtual bool Accept(AbstractProItemVisitor *visitor);
+    virtual ProItemReturn Accept(AbstractProItemVisitor *visitor);
 private:
     QString m_value;
     ProVariable *m_variable;
@@ -155,7 +167,7 @@ public:
 
     ProItem::ProItemKind kind() const;
 
-    virtual bool Accept(AbstractProItemVisitor *visitor);
+    virtual ProItemReturn Accept(AbstractProItemVisitor *visitor);
 private:
     QString m_text;
 };
@@ -170,7 +182,7 @@ public:
 
     ProItem::ProItemKind kind() const;
 
-    virtual bool Accept(AbstractProItemVisitor *visitor);
+    virtual ProItemReturn Accept(AbstractProItemVisitor *visitor);
 private:
     QString m_text;
 };
@@ -190,7 +202,7 @@ public:
 
     ProItem::ProItemKind kind() const;
 
-    virtual bool Accept(AbstractProItemVisitor *visitor);
+    virtual ProItemReturn Accept(AbstractProItemVisitor *visitor);
 private:
     OperatorKind m_operatorKind;
 };
@@ -210,7 +222,7 @@ public:
     void setModified(bool modified);
     bool isModified() const;
 
-    virtual bool Accept(AbstractProItemVisitor *visitor);
+    virtual ProItemReturn Accept(AbstractProItemVisitor *visitor);
 
 private:
     QString m_fileName;
-- 
GitLab