diff --git a/src/shared/proparser/profileevaluator.cpp b/src/shared/proparser/profileevaluator.cpp index b1cacc5bb6a7679675e35f574eec966c15225a9e..282c3aff4920d5c58383c829f765ae1300e31830 100644 --- a/src/shared/proparser/profileevaluator.cpp +++ b/src/shared/proparser/profileevaluator.cpp @@ -195,20 +195,29 @@ public: StCond // Conditionals met on current line }; + enum Context { CtxTest, CtxValue, CtxArgs }; + struct ParseCtx { + int parens; // Nesting of non-functional parentheses + int argc; // Number of arguments in current function call + Context context; + ushort quote; // Enclosing quote type + ushort terminator; // '}' if replace function call is braced, ':' if test function + }; + bool read(ProFile *pro); bool read(ProFile *pro, const QString &content); bool read(QString *out, const QString &content); - bool readInternal(QString *out, const QString &content, ushort *buf); + bool readInternal(QString *out, const QString &content); ALWAYS_INLINE void putTok(ushort *&tokPtr, ushort tok); ALWAYS_INLINE void putBlockLen(ushort *&tokPtr, uint len); ALWAYS_INLINE void putBlock(ushort *&tokPtr, const ushort *buf, uint len); void putHashStr(ushort *&pTokPtr, const ushort *buf, uint len); - ALWAYS_INLINE void putHashStr(ushort *&pTokPtr, const ushort *buf, const ushort *ptr); - ALWAYS_INLINE void putHashStr(ushort *&pTokPtr, const QString &str); + void finalizeHashStr(ushort *buf, uint len); void putLineMarker(ushort *&tokPtr); - void updateItem(ushort *&tokPtr, ushort *uc, ushort *ptr); - bool startVariable(ushort *&tokPtr, ushort *uc, ushort *ptr); + void finalizeCond(ushort *&tokPtr, ushort *uc, ushort *ptr); + void finalizeCall(ushort *&tokPtr, ushort *uc, ushort *ptr, int argc); + void finalizeTest(ushort *&tokPtr); void enterScope(ushort *&tokPtr, bool special, ScopeState state); void leaveScope(ushort *&tokPtr); void flushCond(ushort *&tokPtr); @@ -216,7 +225,7 @@ public: QStack<BlockScope> m_blockstack; ScopeState m_state; - bool m_lineMarked; // Current line already got a marker + int m_markLine; // Put marker for this line bool m_canElse; // Conditionals met on previous line, but no scope was opened bool m_invert; // Pending conditional is negated enum { NoOperator, AndOperator, OrOperator } m_operator; // Pending conditional is ORed/ANDed @@ -234,18 +243,17 @@ public: static ALWAYS_INLINE uint getBlockLen(const ushort *&tokPtr); ProString getStr(const ushort *&tokPtr); ProString getHashStr(const ushort *&tokPtr); - ProString getLongStr(const ushort *&tokPtr); + void evaluateExpression(const ushort *&tokPtr, ProStringList *ret, bool joined); static ALWAYS_INLINE void skipStr(const ushort *&tokPtr); static ALWAYS_INLINE void skipHashStr(const ushort *&tokPtr); - inline void skipLongStr(const ushort *&tokPtr); + void skipExpression(const ushort *&tokPtr); VisitReturn visitProFile(ProFile *pro); VisitReturn visitProBlock(const ushort *tokPtr); - VisitReturn visitProLoop(const ProString &variable, const ProString &expression, + VisitReturn visitProLoop(const ProString &variable, const ushort *exprPtr, const ushort *tokPtr); void visitProFunctionDef(ushort tok, const ProString &name, const ushort *tokPtr); - void visitProVariable(ushort tok, const ProString &varName, const ProString &value); - VisitReturn visitProCondition(const ProString &text); + void visitProVariable(ushort tok, const ProStringList &curr, const ushort *&tokPtr); static inline ProString map(const ProString &var); QHash<ProString, ProStringList> *findValues(const ProString &variableName, @@ -258,7 +266,10 @@ public: static ProStringList split_value_list(const QString &vals); bool isActiveConfig(const QString &config, bool regex = false); ProStringList expandVariableReferences(const ProString &value, int *pos = 0, bool joined = false); + ProStringList expandVariableReferences(const ushort *&tokPtr, bool joined = false); ProStringList evaluateExpandFunction(const ProString &function, const ProString &arguments); + ProStringList evaluateExpandFunction(const ProString &function, const ushort *&tokPtr); + ProStringList evaluateExpandFunction(const ProString &function, const ProStringList &args); QString format(const char *format) const; void logMessage(const QString &msg) const; void errorMessage(const QString &msg) const; @@ -271,6 +282,8 @@ public: { return IoUtils::resolvePath(currentDirectory(), fileName); } VisitReturn evaluateConditionalFunction(const ProString &function, const ProString &arguments); + VisitReturn evaluateConditionalFunction(const ProString &function, const ushort *&tokPtr); + VisitReturn evaluateConditionalFunction(const ProString &function, const ProStringList &args); ProFile *parsedProFile(const QString &fileName, bool cache, const QString &contents = QString()); bool evaluateFile(const QString &fileName); @@ -282,6 +295,7 @@ public: static ALWAYS_INLINE VisitReturn returnBool(bool b) { return b ? ReturnTrue : ReturnFalse; } + QList<ProStringList> prepareFunctionArgs(const ushort *&tokPtr); QList<ProStringList> prepareFunctionArgs(const ProString &arguments); ProStringList evaluateFunction(const FunctionDef &func, const QList<ProStringList> &argumentsList, bool *ok); VisitReturn evaluateBoolFunction(const FunctionDef &func, const QList<ProStringList> &argumentsList, @@ -339,6 +353,7 @@ public: #if !defined(__GNUC__) || __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 3) Q_DECLARE_TYPEINFO(ProFileEvaluator::Private::BlockScope, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(ProFileEvaluator::Private::Context, Q_PRIMITIVE_TYPE); #endif static struct { @@ -555,28 +570,24 @@ bool ProFileEvaluator::Private::read(ProFile *pro) file.close(); m_lineNo = 1; m_profileStack.push(pro); - bool ret = readInternal(pro->itemsRef(), content, (ushort*)content.data()); + bool ret = readInternal(pro->itemsRef(), content); m_profileStack.pop(); return ret; } bool ProFileEvaluator::Private::read(ProFile *pro, const QString &content) { - QString buf; - buf.reserve(content.size()); m_lineNo = 1; m_profileStack.push(pro); - bool ret = readInternal(pro->itemsRef(), content, (ushort*)buf.data()); + bool ret = readInternal(pro->itemsRef(), content); m_profileStack.pop(); return ret; } bool ProFileEvaluator::Private::read(QString *out, const QString &content) { - QString buf; - buf.reserve(content.size()); m_lineNo = 0; - return readInternal(out, content, (ushort*)buf.data()); + return readInternal(out, content); } void ProFileEvaluator::Private::putTok(ushort *&tokPtr, ushort tok) @@ -590,13 +601,10 @@ void ProFileEvaluator::Private::putBlockLen(ushort *&tokPtr, uint len) *tokPtr++ = (ushort)(len >> 16); } -static void putStr(ushort *&pTokPtr, const QString &str) +void ProFileEvaluator::Private::putBlock(ushort *&tokPtr, const ushort *buf, uint len) { - ushort *tokPtr = pTokPtr; - uint len = str.length(); - *tokPtr++ = (ushort)len; - memcpy(tokPtr, str.constData(), len * 2); - pTokPtr = tokPtr + len; + memcpy(tokPtr, buf, len * 2); + tokPtr += len; } void ProFileEvaluator::Private::putHashStr(ushort *&pTokPtr, const ushort *buf, uint len) @@ -610,47 +618,58 @@ void ProFileEvaluator::Private::putHashStr(ushort *&pTokPtr, const ushort *buf, pTokPtr = tokPtr + len; } -void ProFileEvaluator::Private::putHashStr(ushort *&pTokPtr, const ushort *buf, const ushort *ptr) -{ - putHashStr(pTokPtr, buf, ptr - buf); -} - -void ProFileEvaluator::Private::putHashStr(ushort *&pTokPtr, const QString &str) -{ - putHashStr(pTokPtr, (const ushort *)str.constData(), str.length()); -} - -static void putLongStr(ushort *&pTokPtr, const ushort *buf, uint len) -{ - ushort *tokPtr = pTokPtr; - *tokPtr++ = (ushort)len; - *tokPtr++ = (ushort)(len >> 16); - memcpy(tokPtr, buf, len * 2); - pTokPtr = tokPtr + len; -} - -static void putLongStr(ushort *&pTokPtr, const ushort *buf, const ushort *ptr) +void ProFileEvaluator::Private::finalizeHashStr(ushort *buf, uint len) { - putLongStr(pTokPtr, buf, ptr - buf); -} - -static void putLongStr(ushort *&pTokPtr, const QString &str) -{ - putLongStr(pTokPtr, (const ushort *)str.constData(), str.length()); + buf[-4] = TokHashLiteral; + buf[-1] = len; + uint hash = ProString::hash((const QChar *)buf, len); + buf[-3] = (ushort)hash; + buf[-2] = (ushort)(hash >> 16); } // We know that the buffer cannot grow larger than the input string, // and the read() functions rely on it. -bool ProFileEvaluator::Private::readInternal(QString *out, const QString &in, ushort *buf) +bool ProFileEvaluator::Private::readInternal(QString *out, const QString &in) { - // Expression precompiler buffer + // Final precompiled token stream buffer QString tokBuff; + // Worst-case size calculations: + // - line marker adds 1 (2-nl) to 1st token of each line + // - empty assignment "A=":2 => + // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokAssign(1) + + // TokValueTerminator(1) == 7 (8) + // - non-empty assignment "A=B C":5 => + // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokAssign(1) + + // TokLiteral(1) + len(1) + "B"(1) + + // TokLiteral(1) + len(1) + "C"(1) + TokValueTerminator(1) == 13 (14) + // - variable expansion: "$$f":3 => + // TokVariable(1) + hash(2) + len(1) + "f"(1) = 5 + // - function expansion: "$$f()":5 => + // TokFuncName(1) + hash(2) + len(1) + "f"(1) + TokFuncTerminator(1) = 6 + // - scope: "X:":2 => + // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokCondition(1) + + // TokBranch(1) + len(2) + ... + len(2) + ... == 10 + // - test: "X():":4 => + // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokTestCall(1) + TokFuncTerminator(1) + + // TokBranch(1) + len(2) + ... + len(2) + ... == 11 + // - "for(A,B):":9 => + // TokForLoop(1) + hash(2) + len(1) + "A"(1) + + // len(2) + TokLiteral(1) + len(1) + "B"(1) + TokValueTerminator(1) + + // len(2) + ... + TokTerminator(1) == 14 (15) tokBuff.reserve((in.size() + 1) * 5); ushort *tokPtr = (ushort *)tokBuff.constData(); // Current writing position + // Expression precompiler buffer. + QString xprBuff; + xprBuff.reserve(tokBuff.capacity()); // Excessive, but simple + ushort *buf = (ushort *)xprBuff.constData(); + // Parser state m_blockstack.resize(m_blockstack.size() + 1); + QStack<ParseCtx> xprStack; + xprStack.reserve(10); + // We rely on QStrings being null-terminated, so don't maintain a global end pointer. const ushort *cur = (const ushort *)in.unicode(); m_canElse = false; @@ -658,41 +677,72 @@ bool ProFileEvaluator::Private::readInternal(QString *out, const QString &in, us m_state = StNew; m_invert = false; m_operator = NoOperator; - m_lineMarked = false; - bool inAssignment = false; - ushort *ptr = buf; + m_markLine = m_lineNo; + Context context = CtxTest; int parens = 0; + int argc = 0; bool inError = false; - bool putSpace = false; + bool putSpace = false; // Only ever true inside quoted string + bool lineMarked = true; // For in-expression markers + ushort needSep = 0; // Complementary to putSpace: separator outside quotes ushort quote = 0; + ushort term = 0; + + ushort *ptr = buf; + ptr += 4; + ushort *xprPtr = ptr; + +#define FLUSH_LHS_LITERAL(setSep) \ + do { \ + if ((tlen = ptr - xprPtr)) { \ + if (needSep) \ + goto extraChars; \ + finalizeHashStr(xprPtr, tlen); \ + if (setSep) \ + needSep = TokNewStr; \ + } else { \ + ptr -= 4; \ + if (setSep && ptr != buf) \ + needSep = TokNewStr; \ + } \ + } while (0) + +#define FLUSH_RHS_LITERAL(setSep) \ + do { \ + if ((tlen = ptr - xprPtr)) { \ + xprPtr[-2] = TokLiteral | needSep; \ + xprPtr[-1] = tlen; \ + if (setSep) \ + needSep = TokNewStr; \ + } else { \ + ptr -= 2; \ + if (setSep && ptr != ((context == CtxValue) ? tokPtr : buf)) \ + needSep = TokNewStr; \ + } \ + } while (0) + +#define FLUSH_LITERAL(setSep) \ + do { \ + if (context == CtxTest) \ + FLUSH_LHS_LITERAL(setSep); \ + else \ + FLUSH_RHS_LITERAL(setSep); \ + } while (0) + forever { ushort c; // First, skip leading whitespace for (;; ++cur) { c = *cur; - if (c == '\n' || !c) { // Entirely empty line (sans whitespace) - if (inAssignment) { - putLongStr(tokPtr, buf, ptr); - inAssignment = false; - } else { - updateItem(tokPtr, buf, ptr); - } - if (c) { - ++cur; - ++m_lineNo; - goto freshLine; - } - flushScopes(tokPtr); - if (m_blockstack.size() > 1) - logMessage(format("Missing closing brace(s).")); - while (m_blockstack.size()) - leaveScope(tokPtr); - *out = QString(tokBuff.constData(), tokPtr - (ushort *)tokBuff.constData()); - return true; - } - if (c != ' ' && c != '\t' && c != '\r') + if (c == '\n') { + ++cur; + goto flushLine; + } else if (!c) { + goto flushLine; + } else if (c != ' ' && c != '\t' && c != '\r') { break; + } } // Then strip comments. Yep - no escaping is possible. @@ -743,127 +793,339 @@ bool ProFileEvaluator::Private::readInternal(QString *out, const QString &in, us if (!inError) { // Finally, do the tokenization - if (!inAssignment) { - newItem: - do { - if (cur == end) - goto lineEnd; - c = *cur++; - } while (c == ' ' || c == '\t'); - forever { - if (c == '"') { - quote = '"' - quote; - } else if (c == '!' && ptr == buf) { - m_invert ^= true; - goto nextItem; - } else if (!quote) { - if (c == '(') { - ++parens; - } else if (c == ')') { - --parens; - } else if (!parens) { - if (c == ':') { - updateItem(tokPtr, buf, ptr); - if (m_state == StNew) - logMessage(format("And operator without prior condition.")); - else - m_operator = AndOperator; - nextItem: - ptr = buf; - putSpace = false; - goto newItem; - } - if (c == '|') { - updateItem(tokPtr, buf, ptr); - if (m_state != StCond) - logMessage(format("Or operator without prior condition.")); - else - m_operator = OrOperator; - goto nextItem; + ushort tok, rtok; + int tlen; + newToken: + do { + if (cur == end) + goto lineEnd; + c = *cur++; + } while (c == ' ' || c == '\t'); + forever { + if (c == '$') { + if (*cur == '$') { // may be EOF, EOL, WS or '#' if past end + cur++; + if (putSpace) { + putSpace = false; + *ptr++ = ' '; + } + tlen = ptr - xprPtr; + if (context == CtxTest) { + if (needSep) + goto extraChars; + if (tlen) + finalizeHashStr(xprPtr, tlen); + else + ptr -= 4; + } else { + if (tlen) { + xprPtr[-2] = TokLiteral | needSep; + xprPtr[-1] = tlen; + needSep = 0; + } else { + ptr -= 2; } - if (c == '{') { - updateItem(tokPtr, buf, ptr); - flushCond(tokPtr); - ++m_blockstack.top().braceLevel; - goto nextItem; + } + if (!lineMarked) { + lineMarked = true; + *ptr++ = TokLine; + *ptr++ = (ushort)m_lineNo; + } + term = 0; + tok = TokVariable; + c = *cur; + if (c == '[') { + ptr += 2; + tok = TokProperty; + term = ']'; + c = *++cur; + } else if (c == '{') { + ptr += 4; + term = '}'; + c = *++cur; + } else if (c == '(') { + // FIXME: could/should expand this immediately + ptr += 2; + tok = TokEnvVar; + term = ')'; + c = *++cur; + } else { + ptr += 4; + } + xprPtr = ptr; + rtok = tok; + while ((c & 0xFF00) || c == '.' || c == '_' || + (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9')) { + *ptr++ = c; + if (++cur == end) { + c = 0; + goto notfunc; } - if (c == '}') { - updateItem(tokPtr, buf, ptr); - flushScopes(tokPtr); - if (!m_blockstack.top().braceLevel) - errorMessage(format("Excess closing brace.")); - else if (!--m_blockstack.top().braceLevel - && m_blockstack.count() != 1) { - leaveScope(tokPtr); - m_state = StNew; - m_canElse = false; - m_lineMarked = false; - } - goto nextItem; + c = *cur; + } + if (tok == TokVariable && c == '(') + tok = TokFuncName; + notfunc: + if (quote) + tok |= TokQuoted; + tok |= needSep; + needSep = 0; + tlen = ptr - xprPtr; + if (rtok == TokVariable) { + xprPtr[-4] = tok; + uint hash = ProString::hash((const QChar *)xprPtr, tlen); + xprPtr[-3] = (ushort)hash; + xprPtr[-2] = (ushort)(hash >> 16); + } else { + xprPtr[-2] = tok; + } + xprPtr[-1] = tlen; + if ((tok & TokMask) == TokFuncName) { + cur++; + funcCall: + { + xprStack.resize(xprStack.size() + 1); + ParseCtx &top = xprStack.top(); + top.parens = parens; + top.quote = quote; + top.terminator = term; + top.context = context; + top.argc = argc; } - if (c == '=') { - if ((inAssignment = startVariable(tokPtr, buf, ptr))) { - ptr = buf; - putSpace = false; - break; - } - ptr = buf; - inError = true; - goto skip; + parens = 0; + quote = 0; + term = 0; + argc = 1; + context = CtxArgs; + nextToken: + ptr += (context == CtxTest) ? 4 : 2; + xprPtr = ptr; + goto newToken; + } + if (term) { + cur++; + checkTerm: + if (c != term) { + logMessage(format("Missing %1 terminator [found %2]") + .arg(QChar(term)) + .arg(c ? QString(c) : QString::fromLatin1("end-of-line"))); + return false; } } + joinToken: + ptr += (context == CtxTest) ? 4 : 2; + xprPtr = ptr; + goto nextChr; } - - if (putSpace) { - putSpace = false; - *ptr++ = ' '; + } else if (c == '\\') { + static const char symbols[] = "[]{}()$\\'\""; + ushort c2 = *cur; + if (!(c2 & 0xff00) && strchr(symbols, c2)) { + c = c2; + cur++; } - *ptr++ = c; - - forever { - if (cur == end) - goto lineEnd; - c = *cur++; - if (c != ' ' && c != '\t') - break; + } else if (quote) { + if (c == quote) { + quote = 0; + if (putSpace) { + putSpace = false; + *ptr++ = ' '; + } + goto nextChr; + } else if (c == ' ' || c == '\t') { putSpace = true; + goto nextChr; + } else if (c == '!' && ptr == xprPtr && context == CtxTest) { + m_invert ^= true; + goto nextChr; + } + } else if (c == '\'' || c == '"') { + quote = c; + goto nextChr; + } else if (c == ' ' || c == '\t') { + FLUSH_LITERAL(true); + goto nextToken; + } else if (context == CtxArgs) { + // Function arg context + if (c == '(') { + ++parens; + } else if (c == ')') { + if (--parens < 0) { + FLUSH_RHS_LITERAL(false); + *ptr++ = TokFuncTerminator; + int theargc = argc; + { + ParseCtx &top = xprStack.top(); + parens = top.parens; + quote = top.quote; + term = top.terminator; + context = top.context; + argc = top.argc; + xprStack.resize(xprStack.size() - 1); + } + if (term == ':') { + finalizeCall(tokPtr, buf, ptr, theargc); + needSep = TokNewStr; + goto nextItem; + } else if (term == '}') { + c = (cur == end) ? 0 : *cur++; + needSep = 0; + goto checkTerm; + } else { + Q_ASSERT(!term); + needSep = 0; + goto joinToken; + } + } + } else if (!parens && c == ',') { + FLUSH_RHS_LITERAL(false); + *ptr++ = TokArgSeparator; + argc++; + needSep = 0; + goto nextToken; + } + } else if (context == CtxTest) { + // Test or LHS context + if (c == '(') { + FLUSH_LHS_LITERAL(false); + if (ptr == buf) { + logMessage(format("Opening parenthesis without prior test name.")); + inError = true; + goto skip; + } + *ptr++ = TokTestCall; + term = ':'; + needSep = 0; + goto funcCall; + } else if (c == '!' && ptr == xprPtr) { + m_invert ^= true; + goto nextChr; + } else if (c == ':') { + FLUSH_LHS_LITERAL(false); + finalizeCond(tokPtr, buf, ptr); + if (m_state == StNew) + logMessage(format("And operator without prior condition.")); + else + m_operator = AndOperator; + nextItem: + ptr = buf; + needSep = 0; + goto nextToken; + } else if (c == '|') { + FLUSH_LHS_LITERAL(false); + finalizeCond(tokPtr, buf, ptr); + if (m_state != StCond) + logMessage(format("Or operator without prior condition.")); + else + m_operator = OrOperator; + goto nextItem; + } else if (c == '{') { + FLUSH_LHS_LITERAL(false); + finalizeCond(tokPtr, buf, ptr); + flushCond(tokPtr); + ++m_blockstack.top().braceLevel; + goto nextItem; + } else if (c == '}') { + FLUSH_LHS_LITERAL(false); + finalizeCond(tokPtr, buf, ptr); + flushScopes(tokPtr); + if (!m_blockstack.top().braceLevel) { + logMessage(format("Excess closing brace.")); + } else if (!--m_blockstack.top().braceLevel + && m_blockstack.count() != 1) { + leaveScope(tokPtr); + m_state = StNew; + m_canElse = false; + m_markLine = m_lineNo; + } + goto nextItem; + } else if (c == '+') { + tok = TokAppend; + goto do2Op; + } else if (c == '-') { + tok = TokRemove; + goto do2Op; + } else if (c == '*') { + tok = TokAppendUnique; + goto do2Op; + } else if (c == '~') { + tok = TokReplace; + do2Op: + if (*cur == '=') { + cur++; + goto doOp; + } + } else if (c == '=') { + tok = TokAssign; + doOp: + FLUSH_LHS_LITERAL(false); + flushCond(tokPtr); + putLineMarker(tokPtr); + if (!(tlen = ptr - buf)) { + logMessage(format("Assignment operator without prior variable name.")); + inError = true; + goto skip; + } + putBlock(tokPtr, buf, tlen); + putTok(tokPtr, tok); + context = CtxValue; + ptr = tokPtr; + needSep = 0; + goto nextToken; } } - } // !inAssignment - - do { - if (cur == end) - goto lineEnd; - c = *cur++; - } while (c == ' ' || c == '\t'); - forever { if (putSpace) { putSpace = false; *ptr++ = ' '; } *ptr++ = c; - - forever { - if (cur == end) - goto lineEnd; - c = *cur++; - if (c != ' ' && c != '\t') - break; - putSpace = true; - } + nextChr: + if (cur == end) + goto lineEnd; + c = *cur++; } + lineEnd: if (lineCont) { - putSpace = (ptr != buf); + if (quote) { + putSpace = true; + } else { + FLUSH_LITERAL(true); + ptr += (context == CtxTest) ? 4 : 2; + xprPtr = ptr; + } } else { - if (inAssignment) { - putLongStr(tokPtr, buf, ptr); - inAssignment = false; + c = '\n'; + cur = cptr; + flushLine: + FLUSH_LITERAL(false); + if (quote) { + logMessage(format("Missing closing %1 quote").arg(QChar(quote))); + return false; + } + if (!xprStack.isEmpty()) { + logMessage(format("Missing closing parenthesis in function call")); + return false; + } + if (context == CtxValue) { + tokPtr = ptr; + putTok(tokPtr, TokValueTerminator); } else { - updateItem(tokPtr, buf, ptr); + finalizeCond(tokPtr, buf, ptr); } - ptr = buf; - putSpace = false; + if (!c) { + flushScopes(tokPtr); + if (m_blockstack.size() > 1) + logMessage(format("Missing closing brace(s).")); + while (m_blockstack.size()) + leaveScope(tokPtr); + xprBuff.clear(); + *out = QString(tokBuff.constData(), tokPtr - (ushort *)tokBuff.constData()); + return true; + } + ++m_lineNo; + goto freshLine; } } // !inError skip: @@ -872,61 +1134,27 @@ bool ProFileEvaluator::Private::readInternal(QString *out, const QString &in, us ++m_lineNo; goto freshLine; } + lineMarked = false; ignore: cur = cptr; ++m_lineNo; } -} - -bool ProFileEvaluator::Private::startVariable(ushort *&tokPtr, ushort *uc, ushort *ptr) -{ - flushCond(tokPtr); - - if (ptr == uc) // Line starting with '=', like a conflict marker - return false; - - putLineMarker(tokPtr); - - ProToken opkind; - switch (*(ptr - 1)) { - case '+': - --ptr; - opkind = TokAppend; - break; - case '-': - --ptr; - opkind = TokRemove; - break; - case '*': - --ptr; - opkind = TokAppendUnique; - break; - case '~': - --ptr; - opkind = TokReplace; - break; - default: - opkind = TokAssign; - goto skipTrunc; - } - if (ptr == uc) // Line starting with manipulation operator - return false; - if (*(ptr - 1) == ' ') - --ptr; +#undef FLUSH_LITERAL +#undef FLUSH_LHS_LITERAL +#undef FLUSH_RHS_LITERAL - skipTrunc: - putTok(tokPtr, opkind); - putHashStr(tokPtr, uc, ptr); - return true; + extraChars: + logMessage(format("Extra characters after test expression.")); + return false; } void ProFileEvaluator::Private::putLineMarker(ushort *&tokPtr) { - if (!m_lineMarked) { - m_lineMarked = true; + if (m_markLine) { *tokPtr++ = TokLine; - *tokPtr++ = (ushort)m_lineNo; + *tokPtr++ = (ushort)m_markLine; + m_markLine = 0; } } @@ -938,6 +1166,8 @@ void ProFileEvaluator::Private::enterScope(ushort *&tokPtr, bool special, ScopeS tokPtr += 2; m_state = state; m_canElse = false; + if (special) + m_markLine = m_lineNo; } void ProFileEvaluator::Private::leaveScope(ushort *&tokPtr) @@ -980,161 +1210,157 @@ void ProFileEvaluator::Private::flushCond(ushort *&tokPtr) } } -static bool get_next_arg(const QString ¶ms, int *pos, QString *out) +void ProFileEvaluator::Private::finalizeTest(ushort *&tokPtr) { - int quote = 0; - int parens = 0; - const ushort *uc = (const ushort *)params.constData(); - const ushort *params_start = uc + *pos; - if (*params_start == ' ') - ++params_start; - const ushort *params_data = params_start; - const ushort *params_next = 0; - for (;; params_data++) { - ushort unicode = *params_data; - if (!unicode) { // Huh? - params_next = params_data; - break; - } else if (unicode == '(') { - ++parens; - } else if (unicode == ')') { - if (--parens < 0) { - params_next = params_data; - break; - } - } else if (quote && unicode == quote) { - quote = 0; - } else if (!quote && (unicode == '\'' || unicode == '"')) { - quote = unicode; - } - if (!parens && !quote && unicode == ',') { - params_next = params_data + 1; - break; - } + flushScopes(tokPtr); + putLineMarker(tokPtr); + if (m_operator != NoOperator) { + putTok(tokPtr, (m_operator == AndOperator) ? TokAnd : TokOr); + m_operator = NoOperator; } - if (params_data == params_start) - return false; - if (out) { - if (params_data[-1] == ' ') - --params_data; - *out = params.mid(params_start - uc, params_data - params_start); + if (m_invert) { + putTok(tokPtr, TokNot); + m_invert = false; } - *pos = params_next - uc; - return true; + m_state = StCond; + m_canElse = true; } -static bool isKeyFunc(const QString &str, const QString &key, int *pos) -{ - if (!str.startsWith(key)) - return false; - const ushort *uc = (const ushort *)str.constData() + key.length(); - if (*uc == ' ') - uc++; - if (*uc != '(') - return false; - *pos = uc - (const ushort *)str.constData() + 1; - return true; -} - -void ProFileEvaluator::Private::updateItem(ushort *&tokPtr, ushort *uc, ushort *ptr) +void ProFileEvaluator::Private::finalizeCond(ushort *&tokPtr, ushort *uc, ushort *ptr) { if (ptr == uc) return; - QString str((QChar*)uc, ptr - uc); - int pos; - const QString *defName; - ushort defType; - if (!str.compare(statics.strelse, Qt::CaseInsensitive)) { - if (m_invert || m_operator != NoOperator) { - logMessage(format("Unexpected operator in front of else.")); - return; - } - BlockScope &top = m_blockstack.top(); - if (m_canElse && (!top.special || top.braceLevel)) { - putTok(tokPtr, TokBranch); - // Put empty then block - *tokPtr++ = 0; - *tokPtr++ = 0; - enterScope(tokPtr, false, StCtrl); - return; - } - forever { - BlockScope &top = m_blockstack.top(); - if (top.inBranch && (!top.special || top.braceLevel)) { - top.inBranch = false; - enterScope(tokPtr, false, StCtrl); + // Check for magic tokens + if (*uc == TokHashLiteral) { + uint nlen = uc[3]; + ushort *uce = uc + 4 + nlen; + if (uce == ptr) { + m_tmp1.setRawData((QChar *)uc + 4, nlen); + if (!m_tmp1.compare(statics.strelse, Qt::CaseInsensitive)) { + if (m_invert || m_operator != NoOperator) { + logMessage(format("Unexpected operator in front of else.")); + return; + } + BlockScope &top = m_blockstack.top(); + if (m_canElse && (!top.special || top.braceLevel)) { + putTok(tokPtr, TokBranch); + // Put empty then block + putBlockLen(tokPtr, 0); + enterScope(tokPtr, false, StCtrl); + return; + } + forever { + BlockScope &top = m_blockstack.top(); + if (top.inBranch && (!top.special || top.braceLevel)) { + top.inBranch = false; + enterScope(tokPtr, false, StCtrl); + return; + } + if (top.braceLevel || m_blockstack.size() == 1) + break; + leaveScope(tokPtr); + } + errorMessage(format("Unexpected 'else'.")); return; } - if (top.braceLevel || m_blockstack.size() == 1) - break; - leaveScope(tokPtr); - } - errorMessage(format("Unexpected 'else'.")); - } else if (isKeyFunc(str, statics.strfor, &pos)) { - flushCond(tokPtr); - putLineMarker(tokPtr); - if (m_invert || m_operator == OrOperator) { - // '|' could actually work reasonably, but qmake does nonsense here. - logMessage(format("Unexpected operator in front of for().")); - return; } - QString var, expr; - if (!get_next_arg(str, &pos, &var) - || (get_next_arg(str, &pos, &expr) && get_next_arg(str, &pos, 0))) { - logMessage(format("Syntax is for(var, list), for(var, forever) or for(ever).")); - return; - } - if (expr.isEmpty()) { - expr = var; - var.clear(); - } - putTok(tokPtr, TokForLoop); - putHashStr(tokPtr, var); - putLongStr(tokPtr, expr); - enterScope(tokPtr, true, StCtrl); - } else if (isKeyFunc(str, statics.strdefineReplace, &pos)) { - defName = &statics.strdefineReplace; - defType = TokReplaceDef; - goto deffunc; - } else if (isKeyFunc(str, statics.strdefineTest, &pos)) { - defName = &statics.strdefineTest; - defType = TokTestDef; - deffunc: - flushScopes(tokPtr); - putLineMarker(tokPtr); - if (m_invert) { - logMessage(format("Unexpected operator in front of function definition.")); - return; - } - QString func; - if (!get_next_arg(str, &pos, &func) || get_next_arg(str, &pos, 0)) { - logMessage(format("%s(function) requires one argument.").arg(*defName)); - return; - } - if (m_operator != NoOperator) { - putTok(tokPtr, (m_operator == AndOperator) ? TokAnd : TokOr); - m_operator = NoOperator; + } + + finalizeTest(tokPtr); + putBlock(tokPtr, uc, ptr - uc); + putTok(tokPtr, TokCondition); +} + +void ProFileEvaluator::Private::finalizeCall(ushort *&tokPtr, ushort *uc, ushort *ptr, int argc) +{ + // Check for magic tokens + if (*uc == TokHashLiteral) { + uint nlen = uc[3]; + ushort *uce = uc + 4 + nlen; + if (*uce == TokTestCall) { + uce++; + m_tmp1.setRawData((QChar *)uc + 4, nlen); + const QString *defName; + ushort defType; + if (m_tmp1 == statics.strfor) { + flushCond(tokPtr); + putLineMarker(tokPtr); + if (m_invert || m_operator == OrOperator) { + // '|' could actually work reasonably, but qmake does nonsense here. + logMessage(format("Unexpected operator in front of for().")); + return; + } + if (*uce == TokLiteral) { + nlen = uce[1]; + uc = uce + 2 + nlen; + if (*uc == TokFuncTerminator) { + // for(literal) (only "ever" would be legal if qmake was sane) + putTok(tokPtr, TokForLoop); + putHashStr(tokPtr, (ushort *)0, (uint)0); + putBlockLen(tokPtr, 1 + 3 + nlen + 1); + putTok(tokPtr, TokHashLiteral); + putHashStr(tokPtr, uce + 2, nlen); + didFor: + putTok(tokPtr, TokValueTerminator); + enterScope(tokPtr, true, StCtrl); + return; + } else if (*uc == TokArgSeparator && argc == 2) { + // for(var, something) + uc++; + putTok(tokPtr, TokForLoop); + putHashStr(tokPtr, uce + 2, nlen); + doFor: + nlen = ptr - uc; + putBlockLen(tokPtr, nlen + 1); + putBlock(tokPtr, uc, nlen); + goto didFor; + } + } else if (argc == 1) { + // for(non-literal) (this wouldn't be here if qmake was sane) + putTok(tokPtr, TokForLoop); + putHashStr(tokPtr, (ushort *)0, (uint)0); + uc = uce; + goto doFor; + } + logMessage(format("Syntax is for(var, list), for(var, forever) or for(ever).")); + return; + } else if (m_tmp1 == statics.strdefineReplace) { + defName = &statics.strdefineReplace; + defType = TokReplaceDef; + goto deffunc; + } else if (m_tmp1 == statics.strdefineTest) { + defName = &statics.strdefineTest; + defType = TokTestDef; + deffunc: + flushScopes(tokPtr); + putLineMarker(tokPtr); + if (m_invert) { + logMessage(format("Unexpected operator in front of function definition.")); + return; + } + if (*uce == TokLiteral) { + uint nlen = uce[1]; + if (uce[nlen + 2] == TokFuncTerminator) { + if (m_operator != NoOperator) { + putTok(tokPtr, (m_operator == AndOperator) ? TokAnd : TokOr); + m_operator = NoOperator; + } + putTok(tokPtr, defType); + putHashStr(tokPtr, uce + 2, nlen); + uc = uce + 2 + nlen + 1; + enterScope(tokPtr, true, StCtrl); + return; + } + } + logMessage(format("%1(function) requires one literal argument.").arg(*defName)); + return; + } } - putTok(tokPtr, defType); - putHashStr(tokPtr, func); - enterScope(tokPtr, true, StCtrl); - } else { - flushScopes(tokPtr); - putLineMarker(tokPtr); - if (m_operator != NoOperator) { - putTok(tokPtr, (m_operator == AndOperator) ? TokAnd : TokOr); - m_operator = NoOperator; - } - if (m_invert) { - putTok(tokPtr, TokNot); - m_invert = false; - } - putTok(tokPtr, TokCondition); - putStr(tokPtr, str); - m_state = StCond; - m_canElse = true; } + + finalizeTest(tokPtr); + putBlock(tokPtr, uc, ptr - uc); } //////// Evaluator tools ///////// @@ -1165,15 +1391,6 @@ ProString ProFileEvaluator::Private::getHashStr(const ushort *&tokPtr) return ret; } -ProString ProFileEvaluator::Private::getLongStr(const ushort *&tokPtr) -{ - uint len = getBlockLen(tokPtr); - const QString &str(m_stringStack.top()); - ProString ret(str, tokPtr - (const ushort *)str.constData(), len, NoHash); - tokPtr += len; - return ret; -} - void ProFileEvaluator::Private::skipStr(const ushort *&tokPtr) { uint len = *tokPtr++; @@ -1187,12 +1404,6 @@ void ProFileEvaluator::Private::skipHashStr(const ushort *&tokPtr) tokPtr += len; } -void ProFileEvaluator::Private::skipLongStr(const ushort *&tokPtr) -{ - uint len = getBlockLen(tokPtr); - tokPtr += len; -} - // FIXME: this should not build new strings for direct sections. // Note that the E_SPRINTF and E_LIST implementations rely on the deep copy. ProStringList ProFileEvaluator::Private::split_value_list(const QString &vals) @@ -1323,9 +1534,142 @@ static bool isTrue(const ProString &_str, QString &tmp) //////// Evaluator ///////// +static ALWAYS_INLINE void addStr( + const ProString &str, ProStringList *ret, bool &pending, bool joined) +{ + if (joined) { + ret->last().append(str, &pending); + } else { + if (!pending) { + pending = true; + *ret << str; + } else { + ret->last().append(str); + } + } +} + +static ALWAYS_INLINE void addStrList( + const ProStringList &list, ushort tok, ProStringList *ret, bool &pending, bool joined) +{ + if (!list.isEmpty()) { + if (joined) { + ret->last().append(list, &pending, !(tok & TokQuoted)); + } else { + if (tok & TokQuoted) { + if (!pending) { + pending = true; + *ret << ProString(); + } + ret->last().append(list); + } else { + if (!pending) { + pending = true; + // Another qmake bizzarity: if nothing is pending and the + // first element is empty, it will be eaten + if (!list.at(0).isEmpty()) { + // The common case + *ret += list; + return; + } + } else { + ret->last().append(list.at(0)); + } + // This is somewhat slow, but a corner case + for (int j = 1; j < list.size(); ++j) + *ret << list.at(j); + } + } + } +} + +void ProFileEvaluator::Private::evaluateExpression( + const ushort *&tokPtr, ProStringList *ret, bool joined) +{ + if (joined) + *ret << ProString(); + bool pending = false; + forever { + ushort tok = *tokPtr++; + if (tok & TokNewStr) + pending = false; + ushort maskedTok = tok & TokMask; + switch (maskedTok) { + case TokLine: + m_lineNo = *tokPtr++; + break; + case TokLiteral: + addStr(getStr(tokPtr), ret, pending, joined); + break; + case TokHashLiteral: + addStr(getHashStr(tokPtr), ret, pending, joined); + break; + case TokVariable: + addStrList(values(map(getHashStr(tokPtr))), tok, ret, pending, joined); + break; + case TokProperty: + addStr(ProString(propertyValue( + getStr(tokPtr).toQString(m_tmp1)), NoHash), ret, pending, joined); + break; + case TokEnvVar: + addStrList(split_value_list(QString::fromLocal8Bit(qgetenv( + getStr(tokPtr).toQString(m_tmp1).toLatin1().constData()))), tok, ret, pending, joined); + break; + case TokFuncName: { + ProString func = getHashStr(tokPtr); + addStrList(evaluateExpandFunction(func, tokPtr), tok, ret, pending, joined); + break; } + default: + tokPtr--; + return; + } + } +} + +void ProFileEvaluator::Private::skipExpression(const ushort *&pTokPtr) +{ + const ushort *tokPtr = pTokPtr; + forever { + ushort tok = *tokPtr++; + switch (tok) { + case TokLine: + m_lineNo = *tokPtr++; + break; + case TokValueTerminator: + case TokFuncTerminator: + pTokPtr = tokPtr; + return; + case TokArgSeparator: + break; + default: + switch (tok & TokMask) { + case TokLiteral: + case TokProperty: + case TokEnvVar: + skipStr(tokPtr); + break; + case TokHashLiteral: + case TokVariable: + skipHashStr(tokPtr); + break; + case TokFuncName: + skipHashStr(tokPtr); + pTokPtr = tokPtr; + skipExpression(pTokPtr); + tokPtr = pTokPtr; + break; + default: + Q_ASSERT_X(false, "skipExpression", "Unrecognized token"); + break; + } + } + } +} + ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProBlock( const ushort *tokPtr) { + ProStringList curr; bool okey = true, or_op = false, invert = false; uint blockLen; VisitReturn ret = ReturnTrue; @@ -1338,11 +1682,10 @@ ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProBlock( case TokAppend: case TokAppendUnique: case TokRemove: - case TokReplace: { - const ProString &varName = getHashStr(tokPtr); - const ProString &varVal = getLongStr(tokPtr); - visitProVariable(tok, varName, varVal); - continue; } + case TokReplace: + visitProVariable(tok, curr, tokPtr); + curr.clear(); + continue; case TokBranch: blockLen = getBlockLen(tokPtr); if (m_cumulative) { @@ -1373,17 +1716,21 @@ ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProBlock( case TokForLoop: if (m_cumulative) { // This is a no-win situation, so just pretend it's no loop skipHashStr(tokPtr); - skipLongStr(tokPtr); + uint exprLen = getBlockLen(tokPtr); + tokPtr += exprLen; blockLen = getBlockLen(tokPtr); ret = visitProBlock(tokPtr); } else if (okey != or_op) { const ProString &variable = getHashStr(tokPtr); - const ProString &expression = getLongStr(tokPtr); + uint exprLen = getBlockLen(tokPtr); + const ushort *exprPtr = tokPtr; + tokPtr += exprLen; blockLen = getBlockLen(tokPtr); - ret = visitProLoop(variable, expression, tokPtr); + ret = visitProLoop(variable, exprPtr, tokPtr); } else { skipHashStr(tokPtr); - skipLongStr(tokPtr); + uint exprLen = getBlockLen(tokPtr); + tokPtr += exprLen; blockLen = getBlockLen(tokPtr); ret = ReturnTrue; } @@ -1414,24 +1761,53 @@ ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProBlock( continue; case TokCondition: if (!m_skipLevel && okey != or_op) { - ret = visitProCondition(getStr(tokPtr)); - switch (ret) { - case ReturnTrue: okey = true; break; - case ReturnFalse: okey = false; break; - default: return ret; + if (curr.size() != 1) { + logMessage(format("Conditional must expand to exactly one word.")); + okey = false; + } else { + okey = isActiveConfig(curr.at(0).toQString(m_tmp2), true) ^ invert; + } + } + or_op = !okey; // tentatively force next evaluation + invert = false; + curr.clear(); + continue; + case TokTestCall: + if (!m_skipLevel && okey != or_op) { + if (curr.size() != 1) { + logMessage(format("Test name must expand to exactly one word.")); + skipExpression(tokPtr); + okey = false; + } else { + ret = evaluateConditionalFunction(curr.at(0), tokPtr); + switch (ret) { + case ReturnTrue: okey = true; break; + case ReturnFalse: okey = false; break; + default: return ret; + } + okey ^= invert; } - okey ^= invert; } else if (m_cumulative) { m_skipLevel++; - visitProCondition(getStr(tokPtr)); + if (curr.size() != 1) + skipExpression(tokPtr); + else + evaluateConditionalFunction(curr.at(0), tokPtr); m_skipLevel--; } else { - skipStr(tokPtr); + skipExpression(tokPtr); } or_op = !okey; // tentatively force next evaluation invert = false; + curr.clear(); continue; - default: Q_ASSERT_X(false, "visitProBlock", "unexpected item type"); + default: { + const ushort *oTokPtr = --tokPtr; + evaluateExpression(tokPtr, &curr, false); + if (tokPtr != oTokPtr) + continue; + } + Q_ASSERT_X(false, "visitProBlock", "unexpected item type"); } if (ret != ReturnTrue && ret != ReturnFalse) break; @@ -1454,14 +1830,14 @@ void ProFileEvaluator::Private::visitProFunctionDef( } ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProLoop( - const ProString &_variable, const ProString &expression, const ushort *tokPtr) + const ProString &_variable, const ushort *exprPtr, const ushort *tokPtr) { VisitReturn ret = ReturnTrue; bool infinite = false; int index = 0; ProString variable; ProStringList oldVarVal; - ProString it_list = expandVariableReferences(expression, 0, true).first(); + ProString it_list = expandVariableReferences(exprPtr, true).at(0); if (_variable.isEmpty()) { if (it_list != statics.strever) { logMessage(format("Invalid loop expression.")); @@ -1541,12 +1917,19 @@ ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProLoop( } void ProFileEvaluator::Private::visitProVariable( - ushort tok, const ProString &varName, const ProString &value) + ushort tok, const ProStringList &curr, const ushort *&tokPtr) { + if (curr.size() != 1) { + skipExpression(tokPtr); + logMessage(format("Left hand side of assignment must expand to exactly one word.")); + return; + } + const ProString &varName = map(curr.first()); + if (tok == TokReplace) { // ~= // DEFINES ~= s/a/b/?[gqi] - const ProStringList &varVal = expandVariableReferences(value, 0, true); + const ProStringList &varVal = expandVariableReferences(tokPtr, true); const QString &val = varVal.at(0).toQString(m_tmp1); if (val.length() < 4 || val.at(0) != QLatin1Char('s')) { logMessage(format("the ~= operator can handle only the s/// function.")); @@ -1579,7 +1962,7 @@ void ProFileEvaluator::Private::visitProVariable( replaceInList(&m_filevaluemap[currentProFile()][varName], regexp, replace, global, m_tmp2); } } else { - ProStringList varVal = expandVariableReferences(value); + ProStringList varVal = expandVariableReferences(tokPtr); switch (tok) { default: // whatever - cannot happen case TokAssign: // = @@ -1623,24 +2006,6 @@ void ProFileEvaluator::Private::visitProVariable( } } -ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProCondition( - const ProString &text) -{ - const QString &txt = text.toQString(m_tmp1); - if (txt.endsWith(QLatin1Char(')'))) { - if (!m_cumulative && m_skipLevel) - return ReturnTrue; - int lparen = txt.indexOf(QLatin1Char('(')); - ProString arguments = text.mid(lparen + 1, text.size() - lparen - 2); - ProString funcName = text.left(lparen).trimmed(); - return evaluateConditionalFunction(funcName, arguments); - } else { - if (m_skipLevel) - return ReturnTrue; - return returnBool(isActiveConfig(txt, true)); - } -} - ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::visitProFile(ProFile *pro) { m_lineNo = 0; @@ -2275,6 +2640,47 @@ bool ProFileEvaluator::Private::isActiveConfig(const QString &config, bool regex return false; } +ProStringList ProFileEvaluator::Private::expandVariableReferences( + const ushort *&tokPtr, bool joined) +{ + ProStringList ret; + forever { + evaluateExpression(tokPtr, &ret, joined); + switch (*tokPtr) { + case TokValueTerminator: + case TokFuncTerminator: + tokPtr++; + return ret; + case TokArgSeparator: + if (joined) { + tokPtr++; + continue; + } + // fallthrough + default: + Q_ASSERT_X(false, "expandVariableReferences", "Unrecognized token"); + break; + } + } +} + +QList<ProStringList> ProFileEvaluator::Private::prepareFunctionArgs(const ushort *&tokPtr) +{ + QList<ProStringList> args_list; + if (*tokPtr != TokFuncTerminator) { + for (;; tokPtr++) { + ProStringList arg; + evaluateExpression(tokPtr, &arg, false); + args_list << arg; + if (*tokPtr == TokFuncTerminator) + break; + Q_ASSERT(*tokPtr == TokArgSeparator); + } + } + tokPtr++; + return args_list; +} + QList<ProStringList> ProFileEvaluator::Private::prepareFunctionArgs(const ProString &arguments) { QList<ProStringList> args_list; @@ -2346,6 +2752,18 @@ ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::evaluateBoolFu return ReturnFalse; } +ProStringList ProFileEvaluator::Private::evaluateExpandFunction( + const ProString &func, const ushort *&tokPtr) +{ + QHash<ProString, FunctionDef>::ConstIterator it = + m_functionDefs.replaceFunctions.constFind(func); + if (it != m_functionDefs.replaceFunctions.constEnd()) + return evaluateFunction(*it, prepareFunctionArgs(tokPtr), 0); + + //why don't the builtin functions just use args_list? --Sam + return evaluateExpandFunction(func, expandVariableReferences(tokPtr, true)); +} + ProStringList ProFileEvaluator::Private::evaluateExpandFunction( const ProString &func, const ProString &arguments) { @@ -2356,8 +2774,12 @@ ProStringList ProFileEvaluator::Private::evaluateExpandFunction( //why don't the builtin functions just use args_list? --Sam int pos = 0; - ProStringList args = expandVariableReferences(arguments, &pos, true); + return evaluateExpandFunction(func, expandVariableReferences(arguments, &pos, true)); +} +ProStringList ProFileEvaluator::Private::evaluateExpandFunction( + const ProString &func, const ProStringList &args) +{ ExpandFunc func_t = ExpandFunc(statics.expands.value(func.toQString(m_tmp1).toLower())); ProStringList ret; @@ -2745,8 +3167,24 @@ ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::evaluateCondit //why don't the builtin functions just use args_list? --Sam int pos = 0; - ProStringList args = expandVariableReferences(arguments, &pos, true); + return evaluateConditionalFunction(function, expandVariableReferences(arguments, &pos, true)); +} + +ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::evaluateConditionalFunction( + const ProString &function, const ushort *&tokPtr) +{ + QHash<ProString, FunctionDef>::ConstIterator it = + m_functionDefs.testFunctions.constFind(function); + if (it != m_functionDefs.testFunctions.constEnd()) + return evaluateBoolFunction(*it, prepareFunctionArgs(tokPtr), function); + //why don't the builtin functions just use args_list? --Sam + return evaluateConditionalFunction(function, expandVariableReferences(tokPtr, true)); +} + +ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::evaluateConditionalFunction( + const ProString &function, const ProStringList &args) +{ TestFunc func_t = (TestFunc)statics.functions.value(function); switch (func_t) { @@ -2832,8 +3270,7 @@ ProFileEvaluator::Private::VisitReturn ProFileEvaluator::Private::evaluateCondit #endif case T_EVAL: { QString pro; - QString buf = args.join(QLatin1String(" ")); - if (!readInternal(&pro, buf, (ushort*)buf.data())) + if (!readInternal(&pro, args.join(statics.field_sep))) return ReturnFalse; m_stringStack.push(pro); VisitReturn ret = visitProBlock((const ushort *)pro.constData()); diff --git a/src/shared/proparser/proitems.cpp b/src/shared/proparser/proitems.cpp index 9abb554cc29d55e2c09c78ebda55810c14655d80..c4005aeacaab80f51e42c4c71603cbd9958fbb3d 100644 --- a/src/shared/proparser/proitems.cpp +++ b/src/shared/proparser/proitems.cpp @@ -157,6 +157,89 @@ bool ProString::operator==(const QLatin1String &other) const return (uc == e); } +QChar *ProString::prepareAppend(int extraLen) +{ + if (m_string.isDetached() && m_length + extraLen <= m_string.capacity()) { + m_string.reserve(0); // Prevent the resize() below from reallocating + QChar *ptr = (QChar *)m_string.constData(); + if (m_offset) + memmove(ptr, ptr + m_offset, m_length * 2); + ptr += m_length; + m_offset = 0; + m_length += extraLen; + m_string.resize(m_length); + m_hash = 0x80000000; + return ptr; + } else { + QString neu(m_length + extraLen, Qt::Uninitialized); + QChar *ptr = (QChar *)neu.constData(); + memcpy(ptr, m_string.constData() + m_offset, m_length * 2); + ptr += m_length; + *this = ProString(neu, NoHash); + return ptr; + } +} + +// If pending != 0, prefix with space if appending to non-empty non-pending +ProString &ProString::append(const ProString &other, bool *pending) +{ + if (other.m_length) { + if (!m_length) { + *this = other; + } else { + QChar *ptr; + if (pending && !*pending) { + ptr = prepareAppend(1 + other.m_length); + *ptr++ = 32; + } else { + ptr = prepareAppend(other.m_length); + } + memcpy(ptr, other.m_string.constData() + other.m_offset, other.m_length * 2); + } + if (pending) + *pending = true; + } + return *this; +} + +ProString &ProString::append(const ProStringList &other, bool *pending, bool skipEmpty1st) +{ + if (const int sz = other.size()) { + int startIdx = 0; + if (pending && !*pending && skipEmpty1st && other.at(0).isEmpty()) { + if (sz == 1) + return *this; + startIdx = 1; + } + if (!m_length && sz == startIdx + 1) { + *this = other.at(startIdx); + } else { + int totalLength = sz - startIdx; + for (int i = startIdx; i < sz; ++i) + totalLength += other.at(i).size(); + bool putSpace = false; + if (pending && !*pending && m_length) + putSpace = true; + else + totalLength--; + + QChar *ptr = prepareAppend(totalLength); + for (int i = startIdx; i < sz; ++i) { + if (putSpace) + *ptr++ = 32; + else + putSpace = true; + const ProString &str = other.at(i); + memcpy(ptr, str.m_string.constData() + str.m_offset, str.m_length * 2); + ptr += str.m_length; + } + } + if (pending) + *pending = true; + } + return *this; +} + QString operator+(const ProString &one, const ProString &two) { if (two.m_length) { diff --git a/src/shared/proparser/proitems.h b/src/shared/proparser/proitems.h index 6da0d80f14c65a98a0687eec1c6f5f7a7a2f80a2..c991713ac99c887e46082371881749f193f23a0b 100644 --- a/src/shared/proparser/proitems.h +++ b/src/shared/proparser/proitems.h @@ -53,6 +53,8 @@ namespace ProStringConstants { enum OmitPreHashing { NoHash }; } +class ProStringList; + class ProString { public: ProString(); @@ -67,6 +69,9 @@ public: ProString(const QString &str, int offset, int length, ProStringConstants::OmitPreHashing); QString toQString() const; QString &toQString(QString &tmp) const; + ProString &operator+=(const ProString &other); + ProString &append(const ProString &other, bool *pending = 0); + ProString &append(const ProStringList &other, bool *pending = 0, bool skipEmpty1st = false); bool operator==(const ProString &other) const; bool operator==(const QString &other) const; bool operator==(const QLatin1String &other) const; @@ -88,6 +93,7 @@ private: QString m_string; int m_offset, m_length; mutable uint m_hash; + QChar *prepareAppend(int extraLen); uint updatedHash() const; friend uint qHash(const ProString &str); friend QString operator+(const ProString &one, const ProString &two); @@ -112,35 +118,66 @@ public: // These token definitions affect both ProFileEvaluator and ProWriter enum ProToken { TokTerminator = 0, // end of stream (possibly not included in length; must be zero) - TokLine, // line marker: // +1 (2-nl) to 1st token of each line + TokLine, // line marker: // - line (1) - TokAssign, // variable = // "A=":2 => 1+4+2=7 (8) - TokAppend, // variable += // "A+=":3 => 1+4+2=7 (8) - TokAppendUnique, // variable *= // "A*=":3 => 1+4+2=7 (8) - TokRemove, // variable -= // "A-=":3 => 1+4+2=7 (8) - TokReplace, // variable ~= // "A~=":3 => 1+4+2=7 (8) - // - variable name: hash (2), length (1), chars (length) - // - expression: length (2), chars (length) - TokCondition, // CONFIG test: // "A":1 => 1+2=3 (4) - // - test name: lenght (1), chars (length) + TokAssign, // variable = + TokAppend, // variable += + TokAppendUnique, // variable *= + TokRemove, // variable -= + TokReplace, // variable ~= + // previous literal/expansion is a variable manipulation + // - value expression + TokValueTerminator + TokValueTerminator, // assignment value terminator + TokLiteral, // literal string (fully dequoted) + // - length (1) + // - string data (length; unterminated) + TokHashLiteral, // literal string with hash (fully dequoted) + // - hash (2) + // - length (1) + // - string data (length; unterminated) + TokVariable, // qmake variable expansion + // - hash (2) + // - name length (1) + // - name (name length; unterminated) + TokProperty, // qmake property expansion + // - name length (1) + // - name (name length; unterminated) + TokEnvVar, // environment variable expansion + // - name length (1) + // - name (name length; unterminated) + TokFuncName, // replace function expansion + // - hash (2) + // - name length (1) + // - name (name length; unterminated) + // - ((nested expansion + TokArgSeparator)* + nested expansion)? + // - TokFuncTerminator + TokArgSeparator, // function argument separator + TokFuncTerminator, // function argument list terminator + TokCondition, // previous literal/expansion is a conditional + TokTestCall, // previous literal/expansion is a test function call + // - ((nested expansion + TokArgSeparator)* + nested expansion)? + // - TokFuncTerminator TokNot, // '!' operator TokAnd, // ':' operator TokOr, // '|' operator - TokBranch, // branch point: // "X:A=":4 => [5]+1+4+1+1+[7]=19 (20) + TokBranch, // branch point: // - then block length (2) // - then block + TokTerminator (then block length) // - else block length (2) // - else block + TokTerminator (else block length) - TokForLoop, // for loop: // "for(A,B)":8 => 1+4+3+2+1=11 (12) + TokForLoop, // for loop: // - variable name: hash (2), length (1), chars (length) - // - expression: length (2), chars (length) + // - expression: length (2), bytes + TokValueTerminator (length) // - body length (2) // - body + TokTerminator (body length) - TokTestDef, // test function definition: // "defineTest(A):":14 => 1+4+2+1=8 (9) - TokReplaceDef, // replace function definition: // "defineReplace(A):":17 => 1+4+2+1=8 (9) + TokTestDef, // test function definition: + TokReplaceDef, // replace function definition: // - function name: hash (2), length (1), chars (length) // - body length (2) // - body + TokTerminator (body length) + TokMask = 0xff, + TokQuoted = 0x100, // The expression is quoted => join expanded stringlist + TokNewStr = 0x200 // Next stringlist element }; class ProFile diff --git a/src/shared/proparser/prowriter.cpp b/src/shared/proparser/prowriter.cpp index 3a238b6b60b13fb54427588b892ab93e3e9c15ed..46ceca51232139ec31c0c98e0d8d84031d6cb6b0 100644 --- a/src/shared/proparser/prowriter.cpp +++ b/src/shared/proparser/prowriter.cpp @@ -42,13 +42,29 @@ static uint getBlockLen(const ushort *&tokPtr) return len; } -static QString &getHashStr(const ushort *&tokPtr, QString &tmp) +static bool getLiteral(const ushort *tokPtr, const ushort *tokEnd, QString &tmp) { - tokPtr += 2; // ignore hash - uint len = *tokPtr++; - tmp.setRawData((const QChar *)tokPtr, len); - tokPtr += len; - return tmp; + int count = 0; + while (tokPtr != tokEnd) { + ushort tok = *tokPtr++; + switch (tok & TokMask) { + case TokLine: + tokPtr++; + break; + case TokHashLiteral: + tokPtr += 2; + // fallthrough + case TokLiteral: { + uint len = *tokPtr++; + tmp.setRawData((const QChar *)tokPtr, len); + count++; + tokPtr += len; + break; } + default: + return false; + } + } + return count == 1; } static void skipStr(const ushort *&tokPtr) @@ -64,19 +80,53 @@ static void skipHashStr(const ushort *&tokPtr) tokPtr += len; } -static void skipLongStr(const ushort *&tokPtr) +static void skipBlock(const ushort *&tokPtr) { uint len = getBlockLen(tokPtr); tokPtr += len; } -static void skipBlock(const ushort *&tokPtr) +static void skipExpression(const ushort *&pTokPtr, int &lineNo) { - uint len = getBlockLen(tokPtr); - tokPtr += len; + const ushort *tokPtr = pTokPtr; + forever { + ushort tok = *tokPtr++; + switch (tok) { + case TokLine: + lineNo = *tokPtr++; + break; + case TokValueTerminator: + case TokFuncTerminator: + pTokPtr = tokPtr; + return; + case TokArgSeparator: + break; + default: + switch (tok & TokMask) { + case TokLiteral: + case TokProperty: + case TokEnvVar: + skipStr(tokPtr); + break; + case TokHashLiteral: + case TokVariable: + skipHashStr(tokPtr); + break; + case TokFuncName: + skipHashStr(tokPtr); + pTokPtr = tokPtr; + skipExpression(pTokPtr, lineNo); + tokPtr = pTokPtr; + break; + default: + pTokPtr = tokPtr - 1; + return; + } + } + } } -static void skipToken(ushort tok, const ushort *&tokPtr, int &lineNo) +static const ushort *skipToken(ushort tok, const ushort *&tokPtr, int &lineNo) { switch (tok) { case TokLine: @@ -87,16 +137,14 @@ static void skipToken(ushort tok, const ushort *&tokPtr, int &lineNo) case TokAppendUnique: case TokRemove: case TokReplace: - skipHashStr(tokPtr); - skipLongStr(tokPtr); - break; - case TokBranch: - skipBlock(tokPtr); - skipBlock(tokPtr); + case TokTestCall: + skipExpression(tokPtr, lineNo); break; case TokForLoop: skipHashStr(tokPtr); - skipLongStr(tokPtr); + // fallthrough + case TokBranch: + skipBlock(tokPtr); skipBlock(tokPtr); break; case TokTestDef: @@ -107,12 +155,17 @@ static void skipToken(ushort tok, const ushort *&tokPtr, int &lineNo) case TokNot: case TokAnd: case TokOr: - break; case TokCondition: - skipStr(tokPtr); break; - default: Q_ASSERT_X(false, "skipToken", "unexpected item type"); + default: { + const ushort *oTokPtr = --tokPtr; + skipExpression(tokPtr, lineNo); + if (tokPtr != oTokPtr) + return oTokPtr; + } + Q_ASSERT_X(false, "skipToken", "unexpected item type"); } + return 0; } void ProWriter::addFiles(ProFile *profile, QStringList *lines, @@ -123,9 +176,10 @@ void ProWriter::addFiles(ProFile *profile, QStringList *lines, const ushort *tokPtr = (const ushort *)profile->items().constData(); int lineNo = 0; QString tmp; + const ushort *lastXpr = 0; while (ushort tok = *tokPtr++) { if (tok == TokAssign || tok == TokAppend || tok == TokAppendUnique) { - if (var == getHashStr(tokPtr, tmp)) { + if (getLiteral(lastXpr, tokPtr - 1, tmp) && var == tmp) { for (--lineNo; lineNo < lines->count(); lineNo++) { QString line = lines->at(lineNo); int idx = line.indexOf(QLatin1Char('#')); @@ -152,9 +206,9 @@ void ProWriter::addFiles(ProFile *profile, QStringList *lines, lines->insert(lineNo, added); return; } - skipLongStr(tokPtr); + skipExpression(tokPtr, lineNo); } else { - skipToken(tok, tokPtr, lineNo); + lastXpr = skipToken(tok, tokPtr, lineNo); } } @@ -170,6 +224,7 @@ static void findProVariables(const ushort *tokPtr, const QStringList &vars, { int lineNo = 0; QString tmp; + const ushort *lastXpr = 0; while (ushort tok = *tokPtr++) { if (tok == TokBranch) { uint blockLen = getBlockLen(tokPtr); @@ -179,11 +234,11 @@ static void findProVariables(const ushort *tokPtr, const QStringList &vars, findProVariables(tokPtr, vars, proVars); tokPtr += blockLen; } else if (tok == TokAssign || tok == TokAppend || tok == TokAppendUnique) { - if (vars.contains(getHashStr(tokPtr, tmp))) + if (getLiteral(lastXpr, tokPtr - 1, tmp) && vars.contains(tmp)) *proVars << lineNo; - skipLongStr(tokPtr); + skipExpression(tokPtr, lineNo); } else { - skipToken(tok, tokPtr, lineNo); + lastXpr = skipToken(tok, tokPtr, lineNo); } } }