don't pretend that break()/next()/return() are functions
authorOswald Buddenhagen <oswald.buddenhagen@nokia.com>
Tue, 11 Sep 2012 17:30:29 +0000 (19:30 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Tue, 21 May 2013 12:43:04 +0000 (14:43 +0200)
it's a pretty braindead thing to implement control flow statements as
(built-in) functions.

as a "side effect", this fixes return() value handling for lists.

(cherry picked from qtcreator/f53ed6c4b3feca59a94d4f0de8b1a7411122e30e)
(cherry picked from qtcreator/f529e22ec38fb9a656d74394e484d2453cf42c69)
Change-Id: I59c8efa0e4d65329327115f7f8ed20719e7f7546
Reviewed-by: Qt Doc Bot <qt_docbot@qt-project.org>
Reviewed-by: Joerg Bornemann <joerg.bornemann@nokia.com>
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@nokia.com>
(cherry picked from qtbase/8400896cfe3fbef7666329a2920bd0dbdd5890af)
Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
src/linguist/shared/proitems.h
src/linguist/shared/qmakebuiltins.cpp
src/linguist/shared/qmakeevaluator.cpp
src/linguist/shared/qmakeevaluator.h
src/linguist/shared/qmakeparser.cpp
src/linguist/shared/qmakeparser.h

index 05f7925..71db004 100644 (file)
@@ -307,6 +307,9 @@ enum ProToken {
     TokTestCall,        // previous literal/expansion is a test function call
                         // - ((nested expansion + TokArgSeparator)* + nested expansion)?
                         // - TokFuncTerminator
+    TokReturn,          // previous literal/expansion is a return value
+    TokBreak,           // break loop
+    TokNext,            // shortcut to next loop iteration
     TokNot,             // '!' operator
     TokAnd,             // ':' operator
     TokOr,              // '|' operator
index dfa55a0..4ef48b5 100644 (file)
@@ -96,7 +96,7 @@ enum ExpandFunc {
 enum TestFunc {
     T_INVALID = 0, T_REQUIRES, 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_DEFINED, T_CONTAINS, T_INFILE,
     T_COUNT, T_ISEMPTY, T_INCLUDE, T_LOAD, T_DEBUG, T_LOG, T_MESSAGE, T_WARNING, T_ERROR, T_IF,
     T_MKPATH, T_WRITE_FILE, T_TOUCH, T_CACHE
 };
@@ -168,9 +168,6 @@ void QMakeEvaluator::initFunctionStatics()
         { "if", T_IF },
         { "isActiveConfig", T_CONFIG },
         { "system", T_SYSTEM },
-        { "return", T_RETURN },
-        { "break", T_BREAK },
-        { "next", T_NEXT },
         { "defined", T_DEFINED },
         { "contains", T_CONTAINS },
         { "infile", T_INFILE },
@@ -1071,17 +1068,6 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional(
         return returnBool(m_functionDefs.replaceFunctions.contains(var)
                           || m_functionDefs.testFunctions.contains(var));
     }
-    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_cumulative)
-            return ReturnTrue;
-        if (m_valuemapStack.size() == 1) {
-            evalError(fL1S("unexpected return()."));
-            return ReturnFalse;
-        }
-        return ReturnReturn;
     case T_EXPORT: {
         if (args.count() != 1) {
             evalError(fL1S("export(variable) requires one argument."));
@@ -1152,20 +1138,6 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional(
             }
             return ret;
         }
-    case T_BREAK:
-        if (m_skipLevel)
-            return ReturnFalse;
-        if (m_loopLevel)
-            return ReturnBreak;
-        evalError(fL1S("Unexpected break()."));
-        return ReturnFalse;
-    case T_NEXT:
-        if (m_skipLevel)
-            return ReturnFalse;
-        if (m_loopLevel)
-            return ReturnNext;
-        evalError(fL1S("Unexpected next()."));
-        return ReturnFalse;
     case T_IF: {
         if (args.count() != 1) {
             evalError(fL1S("if(condition) requires one argument."));
index 5860d62..e9f0f20 100644 (file)
@@ -195,7 +195,6 @@ QMakeEvaluator::QMakeEvaluator(QMakeGlobals *option,
 #ifdef PROEVALUATOR_CUMULATIVE
     m_skipLevel = 0;
 #endif
-    m_loopLevel = 0;
     m_listCount = 0;
     m_valuemapStack.push(ProValueMap());
     m_valuemapInited = false;
@@ -683,6 +682,24 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(
             invert = false;
             curr.clear();
             continue;
+        case TokReturn:
+            m_returnValue = curr;
+            curr.clear();
+            ret = ReturnReturn;
+            goto ctrlstm;
+        case TokBreak:
+            ret = ReturnBreak;
+            goto ctrlstm;
+        case TokNext:
+            ret = ReturnNext;
+          ctrlstm:
+            if (!m_skipLevel && okey != or_op) {
+                traceMsg("flow control statement '%s', aborting block", dbgReturn(ret));
+                return ret;
+            }
+            traceMsg("skipped flow control statement '%s'", dbgReturn(ret));
+            okey = false, or_op = true; // force next evaluation
+            continue;
         default: {
                 const ushort *oTokPtr = --tokPtr;
                 evaluateExpression(tokPtr, &curr, false);
@@ -762,7 +779,6 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProLoop(
     else
         traceMsg("entering loop for %s over %s", dbgKey(variable), dbgStrList(list));
 
-    m_loopLevel++;
     forever {
         if (infinite) {
             if (!variable.isEmpty())
@@ -799,7 +815,6 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProLoop(
         }
     }
   do_break:
-    m_loopLevel--;
 
     traceMsg("done looping");
 
@@ -1589,8 +1604,6 @@ ProStringList QMakeEvaluator::evaluateFunction(
     } 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) {
@@ -1603,7 +1616,6 @@ ProStringList QMakeEvaluator::evaluateFunction(
         ret = m_returnValue;
         m_returnValue.clear();
 
-        m_loopLevel = loopLevel;
         m_current = m_locationStack.pop();
         m_valuemapStack.pop();
     }
index 69f4e96..2eb7623 100644 (file)
@@ -233,7 +233,6 @@ public:
     static void removeEach(ProStringList *varlist, const ProStringList &value);
 
     QMakeEvaluator *m_caller;
-    int m_loopLevel; // To report unexpected break() and next()s
 #ifdef PROEVALUATOR_CUMULATIVE
     bool m_cumulative;
     int m_skipLevel;
index ef0f193..0dbc3ce 100644 (file)
@@ -108,6 +108,9 @@ static struct {
     QString strdefineTest;
     QString strdefineReplace;
     QString stroption;
+    QString strreturn;
+    QString strnext;
+    QString strbreak;
     QString strhost_build;
     QString strLINE;
     QString strFILE;
@@ -128,6 +131,9 @@ void QMakeParser::initialize()
     statics.strdefineTest = QLatin1String("defineTest");
     statics.strdefineReplace = QLatin1String("defineReplace");
     statics.stroption = QLatin1String("option");
+    statics.strreturn = QLatin1String("return");
+    statics.strnext = QLatin1String("next");
+    statics.strbreak = QLatin1String("break");
     statics.strhost_build = QLatin1String("host_build");
     statics.strLINE = QLatin1String("_LINE_");
     statics.strFILE = QLatin1String("_FILE_");
@@ -874,9 +880,11 @@ void QMakeParser::putLineMarker(ushort *&tokPtr)
 
 void QMakeParser::enterScope(ushort *&tokPtr, bool special, ScopeState state)
 {
+    uchar nest = m_blockstack.top().nest;
     m_blockstack.resize(m_blockstack.size() + 1);
     m_blockstack.top().special = special;
     m_blockstack.top().start = tokPtr;
+    m_blockstack.top().nest = nest;
     tokPtr += 2;
     m_state = state;
     m_canElse = false;
@@ -1016,6 +1024,7 @@ void QMakeParser::finalizeCall(ushort *&tokPtr, ushort *uc, ushort *ptr, int arg
             m_tmp.setRawData((QChar *)uc + 4, nlen);
             const QString *defName;
             ushort defType;
+            uchar nest;
             if (m_tmp == statics.strfor) {
                 if (m_invert || m_operator == OrOperator) {
                     // '|' could actually work reasonably, but qmake does nonsense here.
@@ -1038,6 +1047,7 @@ void QMakeParser::finalizeCall(ushort *&tokPtr, ushort *uc, ushort *ptr, int arg
                       didFor:
                         putTok(tokPtr, TokValueTerminator);
                         enterScope(tokPtr, true, StCtrl);
+                        m_blockstack.top().nest |= NestLoop;
                         return;
                     } else if (*uc == TokArgSeparator && argc == 2) {
                         // for(var, something)
@@ -1084,11 +1094,48 @@ void QMakeParser::finalizeCall(ushort *&tokPtr, ushort *uc, ushort *ptr, int arg
                         putTok(tokPtr, defType);
                         putHashStr(tokPtr, uce + 2, nlen);
                         enterScope(tokPtr, true, StCtrl);
+                        m_blockstack.top().nest = NestFunction;
                         return;
                     }
                 }
                 parseError(fL1S("%1(function) requires one literal argument.").arg(*defName));
                 return;
+            } else if (m_tmp == statics.strreturn) {
+                if (argc > 1) {
+                    parseError(fL1S("return() requires zero or one argument."));
+                    bogusTest(tokPtr);
+                    return;
+                }
+                defType = TokReturn;
+                nest = NestFunction;
+                goto ctrlstm2;
+            } else if (m_tmp == statics.strnext) {
+                defType = TokNext;
+                goto ctrlstm;
+            } else if (m_tmp == statics.strbreak) {
+                defType = TokBreak;
+              ctrlstm:
+                if (*uce != TokFuncTerminator) {
+                    parseError(fL1S("%1() requires zero arguments.").arg(m_tmp));
+                    bogusTest(tokPtr);
+                    return;
+                }
+                nest = NestLoop;
+              ctrlstm2:
+                if (m_invert) {
+                    parseError(fL1S("Unexpected NOT operator in front of %1().").arg(m_tmp));
+                    bogusTest(tokPtr);
+                    return;
+                }
+                if (!(m_blockstack.top().nest & nest)) {
+                    parseError(fL1S("Unexpected %1().").arg(m_tmp));
+                    bogusTest(tokPtr);
+                    return;
+                }
+                finalizeTest(tokPtr);
+                putBlock(tokPtr, uce, ptr - uce - 1); // Only for TokReturn
+                putTok(tokPtr, defType);
+                return;
             } else if (m_tmp == statics.stroption) {
                 if (m_state != StNew || m_blockstack.top().braceLevel || m_blockstack.size() > 1
                         || m_invert || m_operator != NoOperator) {
index 4f21d8e..8d5c3ed 100644 (file)
@@ -97,13 +97,20 @@ public:
     void discardFileFromCache(const QString &fileName);
 
 private:
+    enum ScopeNesting {
+        NestNone = 0,
+        NestLoop = 1,
+        NestFunction = 2
+    };
+
     struct BlockScope {
-        BlockScope() : start(0), braceLevel(0), special(false), inBranch(false) {}
+        BlockScope() : start(0), braceLevel(0), special(false), inBranch(false), nest(NestNone) {}
         BlockScope(const BlockScope &other) { *this = other; }
         ushort *start; // Where this block started; store length here
         int braceLevel; // Nesting of braces in scope
         bool special; // Single-line conditionals inside loops, etc. cannot have else branches
         bool inBranch; // The 'else' branch of the previous TokBranch is still open
+        uchar nest; // Into what control structures we are nested
     };
 
     enum ScopeState {