From a6ad14165e34b6c740e9d6a25c4443ed953b899b Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 19 May 2009 19:00:06 +0200 Subject: support loops: implement for(), next() & break() cherry-picked 88de3e6a45a41baecb7e56e7cbab7fec30ac0a1c from creator --- tools/linguist/shared/abstractproitemvisitor.h | 3 + tools/linguist/shared/profileevaluator.cpp | 122 ++++++++++++++++++++++++- tools/linguist/shared/proitems.cpp | 25 ++++- tools/linguist/shared/proitems.h | 3 + 4 files changed, 149 insertions(+), 4 deletions(-) diff --git a/tools/linguist/shared/abstractproitemvisitor.h b/tools/linguist/shared/abstractproitemvisitor.h index dd72dfe..43e79e0 100644 --- a/tools/linguist/shared/abstractproitemvisitor.h +++ b/tools/linguist/shared/abstractproitemvisitor.h @@ -53,6 +53,9 @@ struct AbstractProItemVisitor virtual ProItem::ProItemReturn visitBeginProBlock(ProBlock *block) = 0; virtual void visitEndProBlock(ProBlock *block) = 0; + virtual ProItem::ProItemReturn visitProLoopIteration() = 0; + virtual void visitProLoopCleanup() = 0; + virtual void visitBeginProVariable(ProVariable *variable) = 0; virtual void visitEndProVariable(ProVariable *variable) = 0; diff --git a/tools/linguist/shared/profileevaluator.cpp b/tools/linguist/shared/profileevaluator.cpp index daf75c8..d250217 100644 --- a/tools/linguist/shared/profileevaluator.cpp +++ b/tools/linguist/shared/profileevaluator.cpp @@ -173,6 +173,8 @@ public: // implementation of AbstractProItemVisitor ProItem::ProItemReturn visitBeginProBlock(ProBlock *block); void visitEndProBlock(ProBlock *block); + ProItem::ProItemReturn visitProLoopIteration(); + void visitProLoopCleanup(); void visitBeginProVariable(ProVariable *variable); void visitEndProVariable(ProVariable *variable); ProItem::ProItemReturn visitBeginProFile(ProFile *value); @@ -191,6 +193,7 @@ public: bool isActiveConfig(const QString &config, bool regex = false); QStringList expandVariableReferences(const QString &value); + void doVariableReplace(QString *str); QStringList evaluateExpandFunction(const QString &function, const QString &arguments); QString format(const char *format) const; @@ -222,6 +225,14 @@ public: QString m_origfile; QString m_oldPath; // To restore the current path to the path QStack m_profileStack; // To handle 'include(a.pri), so we can track back to 'a.pro' when finished with 'a.pri' + struct ProLoop { + QString variable; + QStringList oldVarVal; + QStringList list; + int index; + bool infinite; + }; + QStack m_loopStack; // we need the following two variables for handling // CONFIG = foo bar $$CONFIG @@ -247,6 +258,7 @@ public: #if !defined(__GNUC__) || __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 3) Q_DECLARE_TYPEINFO(ProFileEvaluator::Private::State, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(ProFileEvaluator::Private::ProLoop, Q_MOVABLE_TYPE); #endif ProFileEvaluator::Private::Private(ProFileEvaluator *q_) @@ -627,6 +639,36 @@ void ProFileEvaluator::Private::visitEndProBlock(ProBlock *block) } } +ProItem::ProItemReturn ProFileEvaluator::Private::visitProLoopIteration() +{ + ProLoop &loop = m_loopStack.top(); + + if (loop.infinite) { + if (!loop.variable.isEmpty()) + m_valuemap[loop.variable] = QStringList(QString::number(loop.index++)); + if (loop.index > 1000) { + q->errorMessage(format("ran into infinite loop (> 1000 iterations).")); + return ProItem::ReturnFalse; + } + } else { + QString val; + do { + if (loop.index >= loop.list.count()) + return ProItem::ReturnFalse; + val = loop.list.at(loop.index++); + } while (val.isEmpty()); // stupid, but qmake is like that + m_valuemap[loop.variable] = QStringList(val); + } + return ProItem::ReturnTrue; +} + +void ProFileEvaluator::Private::visitProLoopCleanup() +{ + ProLoop &loop = m_loopStack.top(); + m_valuemap[loop.variable] = loop.oldVarVal; + m_loopStack.pop_back(); +} + void ProFileEvaluator::Private::visitBeginProVariable(ProVariable *variable) { m_lastVarName = variable->variable(); @@ -1017,6 +1059,11 @@ QString ProFileEvaluator::Private::currentDirectory() const return cur->directoryName(); } +void ProFileEvaluator::Private::doVariableReplace(QString *str) +{ + *str = expandVariableReferences(*str).join(QString(Option::field_sep)); +} + QStringList ProFileEvaluator::Private::expandVariableReferences(const QString &str) { QStringList ret; @@ -1720,7 +1767,7 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction( 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_DEFINE_TEST, T_DEFINE_REPLACE }; + T_FOR, T_DEFINE_TEST, T_DEFINE_REPLACE }; static QHash *functions = 0; if (!functions) { @@ -1753,6 +1800,7 @@ ProItem::ProItemReturn 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("for"), T_FOR); //v functions->insert(QLatin1String("defineTest"), T_DEFINE_TEST); //v functions->insert(QLatin1String("defineReplace"), T_DEFINE_REPLACE); //v } @@ -1817,9 +1865,79 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction( case T_INFILE: case T_REQUIRES: case T_EVAL: +#endif + case T_FOR: { + if (m_cumulative) // This is a no-win situation, so just pretend it's no loop + return ProItem::ReturnTrue; + if (m_skipLevel) + return ProItem::ReturnFalse; + if (args.count() > 2 || args.count() < 1) { + q->logMessage(format("for({var, list|var, forever|ever})" + " requires one or two arguments.")); + return ProItem::ReturnFalse; + } + ProLoop loop; + loop.infinite = false; + loop.index = 0; + QString it_list; + if (args.count() == 1) { + doVariableReplace(&args[0]); + it_list = args[0]; + if (args[0] != QLatin1String("ever")) { + q->logMessage(format("for({var, list|var, forever|ever})" + " requires one or two arguments.")); + return ProItem::ReturnFalse; + } + it_list = QLatin1String("forever"); + } else { + loop.variable = args[0]; + loop.oldVarVal = m_valuemap.value(loop.variable); + doVariableReplace(&args[1]); + it_list = args[1]; + } + loop.list = m_valuemap[it_list]; + if (loop.list.isEmpty()) { + if (it_list == QLatin1String("forever")) { + loop.infinite = true; + } else { + int dotdot = it_list.indexOf(QLatin1String("..")); + if (dotdot != -1) { + bool ok; + int start = it_list.left(dotdot).toInt(&ok); + if (ok) { + int end = it_list.mid(dotdot+2).toInt(&ok); + if (ok) { + if (start < end) { + for (int i = start; i <= end; i++) + loop.list << QString::number(i); + } else { + for (int i = start; i >= end; i--) + loop.list << QString::number(i); + } + } + } + } + } + } + m_loopStack.push(loop); + m_sts.condition = true; + return ProItem::ReturnLoop; + } case T_BREAK: + if (m_skipLevel) + return ProItem::ReturnFalse; + if (!m_loopStack.isEmpty()) + return ProItem::ReturnBreak; + // ### missing: breaking out of multiline blocks + q->logMessage(format("unexpected break().")); + return ProItem::ReturnFalse; case T_NEXT: -#endif + if (m_skipLevel) + return ProItem::ReturnFalse; + if (!m_loopStack.isEmpty()) + return ProItem::ReturnNext; + q->logMessage(format("unexpected next().")); + return ProItem::ReturnFalse; case T_IF: { if (args.count() != 1) { q->logMessage(format("if(condition) requires one argument.")); diff --git a/tools/linguist/shared/proitems.cpp b/tools/linguist/shared/proitems.cpp index 0d9f5c4..905c67e 100644 --- a/tools/linguist/shared/proitems.cpp +++ b/tools/linguist/shared/proitems.cpp @@ -120,9 +120,30 @@ ProItem::ProItemReturn ProBlock::Accept(AbstractProItemVisitor *visitor) if (visitor->visitBeginProBlock(this) == ReturnSkip) return ReturnTrue; ProItemReturn rt = ReturnTrue; - foreach (ProItem *item, m_proitems) - if ((rt = item->Accept(visitor)) != ReturnTrue && rt != ReturnFalse) + for (int i = 0; i < m_proitems.count(); ++i) { + rt = m_proitems.at(i)->Accept(visitor); + if (rt != ReturnTrue && rt != ReturnFalse) { + if (rt == ReturnLoop) { + rt = ReturnTrue; + while (visitor->visitProLoopIteration()) + for (int j = i; ++j < m_proitems.count(); ) { + rt = m_proitems.at(j)->Accept(visitor); + if (rt != ReturnTrue && rt != ReturnFalse) { + if (rt == ReturnNext) { + rt = ReturnTrue; + break; + } + if (rt == ReturnBreak) + rt = ReturnTrue; + goto do_break; + } + } + do_break: + visitor->visitProLoopCleanup(); + } break; + } + } visitor->visitEndProBlock(this); return rt; } diff --git a/tools/linguist/shared/proitems.h b/tools/linguist/shared/proitems.h index be086ac..7833be1 100644 --- a/tools/linguist/shared/proitems.h +++ b/tools/linguist/shared/proitems.h @@ -63,6 +63,9 @@ public: enum ProItemReturn { ReturnFalse, ReturnTrue, + ReturnBreak, + ReturnNext, + ReturnLoop, ReturnSkip, ReturnReturn }; -- cgit v0.12