add $$str_member() function

just like $$member(), but operates on a string value rather than a list
variable. it is the swiss army knife of cutting, providing equivalents
of left(), right(), mid() and reverse() all in one.

[ChangeLog][qmake] Added $$str_member() function.

Change-Id: I7c7c6c971db402fff41b428d32a4451f45400728
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
Reviewed-by: Lars Knoll <lars.knoll@theqtcompany.com>
This commit is contained in:
Oswald Buddenhagen 2016-05-18 16:35:01 +02:00
parent ba38926bbf
commit e70330f99e
4 changed files with 153 additions and 37 deletions

View File

@ -2934,6 +2934,7 @@
See also \l{upper(arg1 [, arg2 ..., argn])}{upper()}. See also \l{upper(arg1 [, arg2 ..., argn])}{upper()}.
\target member()
\section2 member(variablename [, start [, end]]) \section2 member(variablename [, start [, end]])
Returns the slice of the list value of \c variablename with the Returns the slice of the list value of \c variablename with the
@ -2960,6 +2961,9 @@
that an empty list will be returned only when an index is invalid that an empty list will be returned only when an index is invalid
(which is implied by the input variable being empty). (which is implied by the input variable being empty).
See also \l{str_member()}.
\target num_add()
\section2 num_add(arg1 [, arg2 ..., argn]) \section2 num_add(arg1 [, arg2 ..., argn])
Takes an arbitrary number of numeric arguments and adds them up, Takes an arbitrary number of numeric arguments and adds them up,
@ -3079,6 +3083,38 @@
\snippet code/doc_src_qmake-manual.pro 168 \snippet code/doc_src_qmake-manual.pro 168
\target str_member()
\section2 str_member(arg [, start [, end]])
This function is identical to \l{member()}, except that it operates
on a string value instead of a list variable, and consequently the
indices refer to character positions.
This function can be used to implement many common string slicing
operations:
\code
# $$left(VAR, len)
left = $$str_member(VAR, 0, $$num_add($$len, -1))
# $$right(VAR, len)
right = $$str_member(VAR, -$$num, -1)
# $$mid(VAR, off, len)
mid = $$str_member(VAR, $$off, $$num_add($$off, $$len, -1))
# $$mid(VAR, off)
mid = $$str_member(VAR, $$off, -1)
# $$reverse(VAR)
reverse = $$str_member(VAR, -1, 0)
\endcode
\note In these implementations, a zero \c len argument needs to be
handled separately.
See also \l{member()}, \l{num_add()}.
\target str_size() \target str_size()
\section2 str_size(arg) \section2 str_size(arg)

View File

@ -80,7 +80,7 @@ QT_BEGIN_NAMESPACE
#define fL1S(s) QString::fromLatin1(s) #define fL1S(s) QString::fromLatin1(s)
enum ExpandFunc { enum ExpandFunc {
E_INVALID = 0, E_MEMBER, E_FIRST, E_TAKE_FIRST, E_LAST, E_TAKE_LAST, E_INVALID = 0, E_MEMBER, E_STR_MEMBER, E_FIRST, E_TAKE_FIRST, E_LAST, E_TAKE_LAST,
E_SIZE, E_STR_SIZE, E_CAT, E_FROMFILE, E_EVAL, E_LIST, E_SPRINTF, E_FORMAT_NUMBER, E_SIZE, E_STR_SIZE, E_CAT, E_FROMFILE, E_EVAL, E_LIST, E_SPRINTF, E_FORMAT_NUMBER,
E_NUM_ADD, E_JOIN, E_SPLIT, E_BASENAME, E_DIRNAME, E_SECTION, E_NUM_ADD, E_JOIN, E_SPLIT, E_BASENAME, E_DIRNAME, E_SECTION,
E_FIND, E_SYSTEM, E_UNIQUE, E_REVERSE, E_QUOTE, E_ESCAPE_EXPAND, E_FIND, E_SYSTEM, E_UNIQUE, E_REVERSE, E_QUOTE, E_ESCAPE_EXPAND,
@ -105,6 +105,7 @@ void QMakeEvaluator::initFunctionStatics()
const ExpandFunc func; const ExpandFunc func;
} expandInits[] = { } expandInits[] = {
{ "member", E_MEMBER }, { "member", E_MEMBER },
{ "str_member", E_STR_MEMBER },
{ "first", E_FIRST }, { "first", E_FIRST },
{ "take_first", E_TAKE_FIRST }, { "take_first", E_TAKE_FIRST },
{ "last", E_LAST }, { "last", E_LAST },
@ -202,6 +203,49 @@ static bool isTrue(const ProString &str)
return !str.compare(statics.strtrue, Qt::CaseInsensitive) || str.toInt(); return !str.compare(statics.strtrue, Qt::CaseInsensitive) || str.toInt();
} }
bool
QMakeEvaluator::getMemberArgs(const ProKey &func, int srclen, const ProStringList &args,
int *start, int *end)
{
*start = 0, *end = 0;
if (args.count() >= 2) {
bool ok = true;
const ProString &start_str = args.at(1);
*start = start_str.toInt(&ok);
if (!ok) {
if (args.count() == 2) {
int dotdot = start_str.indexOf(statics.strDotDot);
if (dotdot != -1) {
*start = start_str.left(dotdot).toInt(&ok);
if (ok)
*end = start_str.mid(dotdot+2).toInt(&ok);
}
}
if (!ok) {
evalError(fL1S("%1() argument 2 (start) '%2' invalid.")
.arg(func.toQString(m_tmp1), start_str.toQString(m_tmp2)));
return false;
}
} else {
*end = *start;
if (args.count() == 3)
*end = args.at(2).toInt(&ok);
if (!ok) {
evalError(fL1S("%1() argument 3 (end) '%2' invalid.")
.arg(func.toQString(m_tmp1), args.at(2).toQString(m_tmp2)));
return false;
}
}
}
if (*start < 0)
*start += srclen;
if (*end < 0)
*end += srclen;
if (*start < 0 || *start >= srclen || *end < 0 || *end >= srclen)
return false;
return true;
}
#if defined(Q_OS_WIN) && defined(PROEVALUATOR_FULL) #if defined(Q_OS_WIN) && defined(PROEVALUATOR_FULL)
static QString windowsErrorCode() static QString windowsErrorCode()
{ {
@ -660,47 +704,37 @@ ProStringList QMakeEvaluator::evaluateBuiltinExpand(
if (args.count() < 1 || args.count() > 3) { if (args.count() < 1 || args.count() > 3) {
evalError(fL1S("member(var, start, end) requires one to three arguments.")); evalError(fL1S("member(var, start, end) requires one to three arguments."));
} else { } else {
bool ok = true; const ProStringList &src = values(map(args.at(0)));
const ProStringList &var = values(map(args.at(0))); int start, end;
int start = 0, end = 0; if (getMemberArgs(func, src.size(), args, &start, &end)) {
if (args.count() >= 2) { ret.reserve(qAbs(end - start) + 1);
const ProString &start_str = args.at(1); if (start < end) {
start = start_str.toInt(&ok); for (int i = start; i <= end && src.size() >= i; i++)
if (!ok) { ret += src.at(i);
if (args.count() == 2) {
int dotdot = start_str.indexOf(statics.strDotDot);
if (dotdot != -1) {
start = start_str.left(dotdot).toInt(&ok);
if (ok)
end = start_str.mid(dotdot+2).toInt(&ok);
}
}
if (!ok)
evalError(fL1S("member() argument 2 (start) '%2' invalid.")
.arg(start_str.toQString(m_tmp1)));
} else { } else {
end = start; for (int i = start; i >= end && src.size() >= i && i >= 0; i--)
if (args.count() == 3) ret += src.at(i);
end = args.at(2).toInt(&ok);
if (!ok)
evalError(fL1S("member() argument 3 (end) '%2' invalid.")
.arg(args.at(2).toQString(m_tmp1)));
} }
} }
if (ok) { }
if (start < 0) break;
start += var.count(); case E_STR_MEMBER:
if (end < 0) if (args.count() < 1 || args.count() > 3) {
end += var.count(); evalError(fL1S("str_member(str, start, end) requires one to three arguments."));
if (start < 0 || start >= var.count() || end < 0 || end >= var.count()) { } else {
//nothing const ProString &src = args.at(0);
} else if (start < end) { int start, end;
for (int i = start; i <= end && var.count() >= i; i++) if (getMemberArgs(func, src.size(), args, &start, &end)) {
ret.append(var[i]); QString res;
res.reserve(qAbs(end - start) + 1);
if (start < end) {
for (int i = start; i <= end && src.size() >= i; i++)
res += src.at(i);
} else { } else {
for (int i = start; i >= end && var.count() >= i && i >= 0; i--) for (int i = start; i >= end && src.size() >= i && i >= 0; i--)
ret += var[i]; res += src.at(i);
} }
ret += ProString(res);
} }
} }
break; break;

View File

@ -232,6 +232,9 @@ public:
QHash<ProKey, QSet<ProKey> > &dependencies, ProValueMap &dependees, QHash<ProKey, QSet<ProKey> > &dependencies, ProValueMap &dependees,
QMultiMap<int, ProString> &rootSet) const; QMultiMap<int, ProString> &rootSet) const;
bool getMemberArgs(const ProKey &name, int srclen, const ProStringList &args,
int *start, int *end);
VisitReturn writeFile(const QString &ctx, const QString &fn, QIODevice::OpenMode mode, VisitReturn writeFile(const QString &ctx, const QString &fn, QIODevice::OpenMode mode,
bool exe, const QString &contents); bool exe, const QString &contents);
#ifndef QT_BOOTSTRAPPED #ifndef QT_BOOTSTRAPPED

View File

@ -718,6 +718,49 @@ void tst_qmakelib::addReplaceFunctions(const QString &qindir)
<< "##:2: member() argument 2 (start) '4..foo' invalid." << "##:2: member() argument 2 (start) '4..foo' invalid."
<< true; << true;
// The argument processing is shared with $$member(), so some tests are skipped.
QTest::newRow("$$str_member(): empty")
<< "VAR = $$str_member()"
<< "VAR ="
<< ""
<< true;
QTest::newRow("$$str_member(): too short")
<< "VAR = $$str_member(string_value, 7, 12)"
<< "VAR =" // this is actually kinda stupid
<< ""
<< true;
QTest::newRow("$$str_member(): ok")
<< "VAR = $$str_member(string_value, 7, 11)"
<< "VAR = value"
<< ""
<< true;
QTest::newRow("$$str_member(): ok (default start)")
<< "VAR = $$str_member(string_value)"
<< "VAR = s"
<< ""
<< true;
QTest::newRow("$$str_member(): ok (default end)")
<< "VAR = $$str_member(string_value, 7)"
<< "VAR = v"
<< ""
<< true;
QTest::newRow("$$str_member(): negative")
<< "VAR = $$str_member(string_value, -5, -3)"
<< "VAR = val"
<< ""
<< true;
QTest::newRow("$$str_member(): inverse")
<< "VAR = $$str_member(string_value, -2, 1)"
<< "VAR = ulav_gnirt"
<< ""
<< true;
QTest::newRow("$$first(): empty") QTest::newRow("$$first(): empty")
<< "IN = \nVAR = $$first(IN)" << "IN = \nVAR = $$first(IN)"
<< "VAR =" << "VAR ="