Commit 88de3e6a authored by Oswald Buddenhagen's avatar Oswald Buddenhagen
Browse files

support loops: implement for(), next() & break()

parent d89338aa
......@@ -41,6 +41,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;
......
......@@ -159,6 +159,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);
......@@ -180,6 +182,7 @@ public:
void expandPatternHelper(const QString &relName, const QString &absName,
QStringList &sources_out);
QStringList expandVariableReferences(const QString &value);
void doVariableReplace(QString *str);
QStringList evaluateExpandFunction(const QString &function, const QString &arguments);
QString format(const char *format) const;
......@@ -211,6 +214,14 @@ public:
QString m_origfile;
QString m_oldPath; // To restore the current path to the path
QStack<ProFile*> 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<ProLoop> m_loopStack;
// we need the following two variables for handling
// CONFIG = foo bar $$CONFIG
......@@ -238,6 +249,7 @@ public:
};
Q_DECLARE_TYPEINFO(ProFileEvaluator::Private::State, Q_PRIMITIVE_TYPE);
Q_DECLARE_TYPEINFO(ProFileEvaluator::Private::ProLoop, Q_MOVABLE_TYPE);
ProFileEvaluator::Private::Private(ProFileEvaluator *q_)
: q(q_)
......@@ -618,6 +630,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();
......@@ -1037,6 +1079,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;
......@@ -1738,7 +1785,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<QString, int> *functions = 0;
if (!functions) {
......@@ -1771,6 +1818,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
}
......@@ -1835,9 +1883,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."));
......
......@@ -108,9 +108,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;
}
......
......@@ -52,6 +52,9 @@ public:
enum ProItemReturn {
ReturnFalse,
ReturnTrue,
ReturnBreak,
ReturnNext,
ReturnLoop,
ReturnSkip,
ReturnReturn
};
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment