1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "profileparser.h"
36 using namespace ProFileEvaluatorInternal;
38 #include <QtCore/QFile>
39 #ifdef PROPARSER_THREAD_SAFE
40 # include <QtCore/QThreadPool>
45 ///////////////////////////////////////////////////////////////////////
49 ///////////////////////////////////////////////////////////////////////
51 ProFileCache::~ProFileCache()
53 foreach (const Entry &ent, parsed_files)
58 void ProFileCache::discardFile(const QString &fileName)
60 #ifdef PROPARSER_THREAD_SAFE
61 QMutexLocker lck(&mutex);
63 QHash<QString, Entry>::Iterator it = parsed_files.find(fileName);
64 if (it != parsed_files.end()) {
67 parsed_files.erase(it);
71 void ProFileCache::discardFiles(const QString &prefix)
73 #ifdef PROPARSER_THREAD_SAFE
74 QMutexLocker lck(&mutex);
76 QHash<QString, Entry>::Iterator
77 it = parsed_files.begin(),
78 end = parsed_files.end();
80 if (it.key().startsWith(prefix)) {
83 it = parsed_files.erase(it);
90 ////////// Parser ///////////
92 #define fL1S(s) QString::fromLatin1(s)
94 namespace { // MSVC2010 doesn't seem to know the semantics of "static" ...
99 QString strdefineTest;
100 QString strdefineReplace;
105 void ProFileParser::initialize()
107 if (!statics.strelse.isNull())
110 statics.strelse = QLatin1String("else");
111 statics.strfor = QLatin1String("for");
112 statics.strdefineTest = QLatin1String("defineTest");
113 statics.strdefineReplace = QLatin1String("defineReplace");
116 ProFileParser::ProFileParser(ProFileCache *cache, ProFileParserHandler *handler)
120 // So that single-threaded apps don't have to call initialize() for now.
124 ProFile *ProFileParser::parsedProFile(const QString &fileName, bool cache, const QString *contents)
127 if (cache && m_cache) {
128 ProFileCache::Entry *ent;
129 #ifdef PROPARSER_THREAD_SAFE
130 QMutexLocker locker(&m_cache->mutex);
132 QHash<QString, ProFileCache::Entry>::Iterator it = m_cache->parsed_files.find(fileName);
133 if (it != m_cache->parsed_files.end()) {
135 #ifdef PROPARSER_THREAD_SAFE
136 if (ent->locker && !ent->locker->done) {
137 ++ent->locker->waiters;
138 QThreadPool::globalInstance()->releaseThread();
139 ent->locker->cond.wait(locker.mutex());
140 QThreadPool::globalInstance()->reserveThread();
141 if (!--ent->locker->waiters) {
147 if ((pro = ent->pro))
150 ent = &m_cache->parsed_files[fileName];
151 #ifdef PROPARSER_THREAD_SAFE
152 ent->locker = new ProFileCache::Entry::Locker;
155 pro = new ProFile(fileName);
156 if (!(!contents ? read(pro) : read(pro, *contents))) {
163 #ifdef PROPARSER_THREAD_SAFE
165 if (ent->locker->waiters) {
166 ent->locker->done = true;
167 ent->locker->cond.wakeAll();
175 pro = new ProFile(fileName);
176 if (!(!contents ? read(pro) : read(pro, *contents))) {
184 bool ProFileParser::read(ProFile *pro)
186 QFile file(pro->fileName());
187 if (!file.open(QIODevice::ReadOnly)) {
188 if (m_handler && IoUtils::exists(pro->fileName()))
189 m_handler->parseError(QString(), 0, fL1S("%1 not readable.").arg(pro->fileName()));
193 QString content(QString::fromLocal8Bit(file.readAll()));
195 return read(pro, content);
198 void ProFileParser::putTok(ushort *&tokPtr, ushort tok)
203 void ProFileParser::putBlockLen(ushort *&tokPtr, uint len)
205 *tokPtr++ = (ushort)len;
206 *tokPtr++ = (ushort)(len >> 16);
209 void ProFileParser::putBlock(ushort *&tokPtr, const ushort *buf, uint len)
211 memcpy(tokPtr, buf, len * 2);
215 void ProFileParser::putHashStr(ushort *&pTokPtr, const ushort *buf, uint len)
217 uint hash = ProString::hash((const QChar *)buf, len);
218 ushort *tokPtr = pTokPtr;
219 *tokPtr++ = (ushort)hash;
220 *tokPtr++ = (ushort)(hash >> 16);
221 *tokPtr++ = (ushort)len;
222 memcpy(tokPtr, buf, len * 2);
223 pTokPtr = tokPtr + len;
226 void ProFileParser::finalizeHashStr(ushort *buf, uint len)
228 buf[-4] = TokHashLiteral;
230 uint hash = ProString::hash((const QChar *)buf, len);
231 buf[-3] = (ushort)hash;
232 buf[-2] = (ushort)(hash >> 16);
235 bool ProFileParser::read(ProFile *pro, const QString &in)
240 // Final precompiled token stream buffer
242 // Worst-case size calculations:
243 // - line marker adds 1 (2-nl) to 1st token of each line
244 // - empty assignment "A=":2 =>
245 // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokAssign(1) +
246 // TokValueTerminator(1) == 7 (8)
247 // - non-empty assignment "A=B C":5 =>
248 // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokAssign(1) +
249 // TokLiteral(1) + len(1) + "B"(1) +
250 // TokLiteral(1) + len(1) + "C"(1) + TokValueTerminator(1) == 13 (14)
251 // - variable expansion: "$$f":3 =>
252 // TokVariable(1) + hash(2) + len(1) + "f"(1) = 5
253 // - function expansion: "$$f()":5 =>
254 // TokFuncName(1) + hash(2) + len(1) + "f"(1) + TokFuncTerminator(1) = 6
255 // - scope: "X:":2 =>
256 // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokCondition(1) +
257 // TokBranch(1) + len(2) + ... + len(2) + ... == 10
258 // - test: "X():":4 =>
259 // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokTestCall(1) + TokFuncTerminator(1) +
260 // TokBranch(1) + len(2) + ... + len(2) + ... == 11
261 // - "for(A,B):":9 =>
262 // TokForLoop(1) + hash(2) + len(1) + "A"(1) +
263 // len(2) + TokLiteral(1) + len(1) + "B"(1) + TokValueTerminator(1) +
264 // len(2) + ... + TokTerminator(1) == 14 (15)
265 tokBuff.reserve((in.size() + 1) * 5);
266 ushort *tokPtr = (ushort *)tokBuff.constData(); // Current writing position
268 // Expression precompiler buffer.
270 xprBuff.reserve(tokBuff.capacity()); // Excessive, but simple
271 ushort * const buf = (ushort *)xprBuff.constData();
274 m_blockstack.clear();
275 m_blockstack.resize(1);
277 QStack<ParseCtx> xprStack;
278 xprStack.reserve(10);
280 // We rely on QStrings being null-terminated, so don't maintain a global end pointer.
281 const ushort *cur = (const ushort *)in.unicode();
286 m_operator = NoOperator;
287 m_markLine = m_lineNo;
289 Context context = CtxTest;
290 int parens = 0; // Braces in value context
292 int wordCount = 0; // Number of words in currently accumulated expression
293 bool putSpace = false; // Only ever true inside quoted string
294 bool lineMarked = true; // For in-expression markers
295 ushort needSep = TokNewStr; // Complementary to putSpace: separator outside quotes
301 ushort *xprPtr = ptr;
303 #define FLUSH_LHS_LITERAL() \
305 if ((tlen = ptr - xprPtr)) { \
306 finalizeHashStr(xprPtr, tlen); \
316 #define FLUSH_RHS_LITERAL() \
318 if ((tlen = ptr - xprPtr)) { \
319 xprPtr[-2] = TokLiteral | needSep; \
330 #define FLUSH_LITERAL() \
332 if (context == CtxTest) \
333 FLUSH_LHS_LITERAL(); \
335 FLUSH_RHS_LITERAL(); \
338 #define FLUSH_VALUE_LIST() \
340 if (wordCount > 1) { \
342 if (*xprPtr == TokLine) \
344 tokPtr[-1] = ((*xprPtr & TokMask) == TokLiteral) ? wordCount : 0; \
349 putTok(tokPtr, TokValueTerminator); \
355 // First, skip leading whitespace
363 } else if (c != ' ' && c != '\t' && c != '\r') {
368 // Then strip comments. Yep - no escaping is possible.
369 const ushort *end; // End of this line
370 const ushort *cptr; // Start of next line
371 for (cptr = cur;; ++cptr) {
374 for (end = cptr; (c = *++cptr);) {
380 if (end == cur) { // Line with only a comment (sans whitespace)
381 if (m_markLine == m_lineNo)
383 // Qmake bizarreness: such lines do not affect line continuations
398 // Then look for line continuations. Yep - no escaping here as well.
401 // We don't have to check for underrun here, as we already determined
402 // that the line is non-empty.
403 ushort ec = *(end - 1);
409 if (ec != ' ' && ec != '\t' && ec != '\r') {
416 // Finally, do the tokenization
424 } while (c == ' ' || c == '\t');
427 if (*cur == '$') { // may be EOF, EOL, WS, '#' or '\\' if past end
437 *ptr++ = (ushort)m_lineNo;
447 } else if (c == '{') {
451 } else if (c == '(') {
452 // FIXME: could/should expand this immediately
462 while ((c & 0xFF00) || c == '.' || c == '_' ||
463 (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
464 (c >= '0' && c <= '9')) {
472 if (tok == TokVariable && c == '(')
482 if (rtok == TokVariable) {
484 uint hash = ProString::hash((const QChar *)xprPtr, tlen);
485 xprPtr[-3] = (ushort)hash;
486 xprPtr[-2] = (ushort)(hash >> 16);
491 if ((tok & TokMask) == TokFuncName) {
495 xprStack.resize(xprStack.size() + 1);
496 ParseCtx &top = xprStack.top();
499 top.terminator = term;
500 top.context = context;
502 top.wordCount = wordCount;
512 ptr += (context == CtxTest) ? 4 : 2;
521 parseError(fL1S("Missing %1 terminator [found %2]")
523 .arg(c ? QString(c) : QString::fromLatin1("end-of-line")));
528 // Just parse on, as if there was a terminator ...
532 ptr += (context == CtxTest) ? 4 : 2;
537 } else if (c == '\\' && cur != end) {
538 static const char symbols[] = "[]{}()$\\'\"";
540 if (!(c2 & 0xff00) && strchr(symbols, c2)) {
552 } else if (c == ' ' || c == '\t') {
555 } else if (c == '!' && ptr == xprPtr && context == CtxTest) {
559 } else if (c == '\'' || c == '"') {
562 } else if (c == ' ' || c == '\t') {
565 } else if (context == CtxArgs) {
566 // Function arg context
569 } else if (c == ')') {
572 *ptr++ = TokFuncTerminator;
575 ParseCtx &top = xprStack.top();
578 term = top.terminator;
579 context = top.context;
581 wordCount = top.wordCount;
582 xprStack.resize(xprStack.size() - 1);
585 finalizeCall(tokPtr, buf, ptr, theargc);
587 } else if (term == '}') {
588 c = (cur == end) ? 0 : *cur++;
595 } else if (!parens && c == ',') {
597 *ptr++ = TokArgSeparator;
601 } else if (context == CtxTest) {
602 // Test or LHS context
605 if (wordCount != 1) {
607 parseError(fL1S("Extra characters after test expression."));
609 parseError(fL1S("Opening parenthesis without prior test name."));
611 ptr = buf; // Put empty function name
613 *ptr++ = TokTestCall;
616 } else if (c == '!' && ptr == xprPtr) {
619 } else if (c == ':') {
621 finalizeCond(tokPtr, buf, ptr, wordCount);
622 if (m_state == StNew)
623 parseError(fL1S("And operator without prior condition."));
625 m_operator = AndOperator;
629 } else if (c == '|') {
631 finalizeCond(tokPtr, buf, ptr, wordCount);
632 if (m_state != StCond)
633 parseError(fL1S("Or operator without prior condition."));
635 m_operator = OrOperator;
637 } else if (c == '{') {
639 finalizeCond(tokPtr, buf, ptr, wordCount);
641 ++m_blockstack.top().braceLevel;
643 } else if (c == '}') {
645 finalizeCond(tokPtr, buf, ptr, wordCount);
648 if (!m_blockstack.top().braceLevel) {
649 parseError(fL1S("Excess closing brace."));
650 } else if (!--m_blockstack.top().braceLevel
651 && m_blockstack.count() != 1) {
655 m_markLine = m_lineNo;
658 } else if (c == '+') {
661 } else if (c == '-') {
664 } else if (c == '*') {
665 tok = TokAppendUnique;
667 } else if (c == '~') {
674 } else if (c == '=') {
679 putLineMarker(tokPtr);
680 if (wordCount != 1) {
681 parseError(fL1S("Assignment needs exactly one word on the left hand side."));
683 // Put empty variable name.
685 putBlock(tokPtr, buf, ptr - buf);
692 } else { // context == CtxValue
695 } else if (c == '}') {
723 ptr += (context == CtxTest) ? 4 : 2;
732 parseError(fL1S("Missing closing %1 quote").arg(QChar(quote)));
733 if (!xprStack.isEmpty()) {
734 context = xprStack.at(0).context;
738 } else if (!xprStack.isEmpty()) {
739 parseError(fL1S("Missing closing parenthesis in function call"));
740 context = xprStack.at(0).context;
744 if (context == CtxValue) {
745 tokPtr[-1] = 0; // sizehint
746 putTok(tokPtr, TokValueTerminator);
750 } else if (context == CtxValue) {
753 finalizeCond(tokPtr, buf, ptr, wordCount);
773 if (m_blockstack.size() > 1) {
774 parseError(fL1S("Missing closing brace(s)."));
777 while (m_blockstack.size())
780 *pro->itemsRef() = QString(tokBuff.constData(), tokPtr - (ushort *)tokBuff.constData());
783 #undef FLUSH_VALUE_LIST
785 #undef FLUSH_LHS_LITERAL
786 #undef FLUSH_RHS_LITERAL
789 void ProFileParser::putLineMarker(ushort *&tokPtr)
793 *tokPtr++ = (ushort)m_markLine;
798 void ProFileParser::enterScope(ushort *&tokPtr, bool special, ScopeState state)
800 m_blockstack.resize(m_blockstack.size() + 1);
801 m_blockstack.top().special = special;
802 m_blockstack.top().start = tokPtr;
807 m_markLine = m_lineNo;
810 void ProFileParser::leaveScope(ushort *&tokPtr)
812 if (m_blockstack.top().inBranch) {
813 // Put empty else block
814 putBlockLen(tokPtr, 0);
816 if (ushort *start = m_blockstack.top().start) {
817 putTok(tokPtr, TokTerminator);
818 uint len = tokPtr - start - 2;
819 start[0] = (ushort)len;
820 start[1] = (ushort)(len >> 16);
822 m_blockstack.resize(m_blockstack.size() - 1);
825 // If we are on a fresh line, close all open one-line scopes.
826 void ProFileParser::flushScopes(ushort *&tokPtr)
828 if (m_state == StNew) {
829 while (!m_blockstack.top().braceLevel && m_blockstack.size() > 1)
831 if (m_blockstack.top().inBranch) {
832 m_blockstack.top().inBranch = false;
833 // Put empty else block
834 putBlockLen(tokPtr, 0);
840 // If there is a pending conditional, enter a new scope, otherwise flush scopes.
841 void ProFileParser::flushCond(ushort *&tokPtr)
843 if (m_state == StCond) {
844 putTok(tokPtr, TokBranch);
845 m_blockstack.top().inBranch = true;
846 enterScope(tokPtr, false, StNew);
852 void ProFileParser::finalizeTest(ushort *&tokPtr)
855 putLineMarker(tokPtr);
856 if (m_operator != NoOperator) {
857 putTok(tokPtr, (m_operator == AndOperator) ? TokAnd : TokOr);
858 m_operator = NoOperator;
861 putTok(tokPtr, TokNot);
868 void ProFileParser::bogusTest(ushort *&tokPtr)
871 m_operator = NoOperator;
875 m_proFile->setOk(false);
878 void ProFileParser::finalizeCond(ushort *&tokPtr, ushort *uc, ushort *ptr, int wordCount)
880 if (wordCount != 1) {
882 parseError(fL1S("Extra characters after test expression."));
888 // Check for magic tokens
889 if (*uc == TokHashLiteral) {
891 ushort *uce = uc + 4 + nlen;
893 m_tmp.setRawData((QChar *)uc + 4, nlen);
894 if (!m_tmp.compare(statics.strelse, Qt::CaseInsensitive)) {
895 if (m_invert || m_operator != NoOperator) {
896 parseError(fL1S("Unexpected operator in front of else."));
899 BlockScope &top = m_blockstack.top();
900 if (m_canElse && (!top.special || top.braceLevel)) {
901 // A list of tests (the last one likely with side effects),
902 // but no assignment, scope, etc.
903 putTok(tokPtr, TokBranch);
904 // Put empty then block
905 putBlockLen(tokPtr, 0);
906 enterScope(tokPtr, false, StCtrl);
910 BlockScope &top = m_blockstack.top();
911 if (top.inBranch && (!top.special || top.braceLevel)) {
912 top.inBranch = false;
913 enterScope(tokPtr, false, StCtrl);
916 if (top.braceLevel || m_blockstack.size() == 1)
920 parseError(fL1S("Unexpected 'else'."));
926 finalizeTest(tokPtr);
927 putBlock(tokPtr, uc, ptr - uc);
928 putTok(tokPtr, TokCondition);
931 void ProFileParser::finalizeCall(ushort *&tokPtr, ushort *uc, ushort *ptr, int argc)
933 // Check for magic tokens
934 if (*uc == TokHashLiteral) {
936 ushort *uce = uc + 4 + nlen;
937 if (*uce == TokTestCall) {
939 m_tmp.setRawData((QChar *)uc + 4, nlen);
940 const QString *defName;
942 if (m_tmp == statics.strfor) {
944 putLineMarker(tokPtr);
945 if (m_invert || m_operator == OrOperator) {
946 // '|' could actually work reasonably, but qmake does nonsense here.
947 parseError(fL1S("Unexpected operator in front of for()."));
950 if (*uce == (TokLiteral|TokNewStr)) {
953 if (*uc == TokFuncTerminator) {
954 // for(literal) (only "ever" would be legal if qmake was sane)
955 putTok(tokPtr, TokForLoop);
956 putHashStr(tokPtr, (ushort *)0, (uint)0);
957 putBlockLen(tokPtr, 1 + 3 + nlen + 1);
958 putTok(tokPtr, TokHashLiteral);
959 putHashStr(tokPtr, uce + 2, nlen);
961 putTok(tokPtr, TokValueTerminator);
962 enterScope(tokPtr, true, StCtrl);
964 } else if (*uc == TokArgSeparator && argc == 2) {
965 // for(var, something)
967 putTok(tokPtr, TokForLoop);
968 putHashStr(tokPtr, uce + 2, nlen);
971 putBlockLen(tokPtr, nlen + 1);
972 putBlock(tokPtr, uc, nlen);
975 } else if (argc == 1) {
976 // for(non-literal) (this wouldn't be here if qmake was sane)
977 putTok(tokPtr, TokForLoop);
978 putHashStr(tokPtr, (ushort *)0, (uint)0);
982 parseError(fL1S("Syntax is for(var, list), for(var, forever) or for(ever)."));
984 } else if (m_tmp == statics.strdefineReplace) {
985 defName = &statics.strdefineReplace;
986 defType = TokReplaceDef;
988 } else if (m_tmp == statics.strdefineTest) {
989 defName = &statics.strdefineTest;
990 defType = TokTestDef;
993 putLineMarker(tokPtr);
995 parseError(fL1S("Unexpected operator in front of function definition."));
998 if (*uce == (TokLiteral|TokNewStr)) {
1000 if (uce[nlen + 2] == TokFuncTerminator) {
1001 if (m_operator != NoOperator) {
1002 putTok(tokPtr, (m_operator == AndOperator) ? TokAnd : TokOr);
1003 m_operator = NoOperator;
1005 putTok(tokPtr, defType);
1006 putHashStr(tokPtr, uce + 2, nlen);
1007 uc = uce + 2 + nlen + 1;
1008 enterScope(tokPtr, true, StCtrl);
1012 parseError(fL1S("%1(function) requires one literal argument.").arg(*defName));
1018 finalizeTest(tokPtr);
1019 putBlock(tokPtr, uc, ptr - uc);
1022 void ProFileParser::parseError(const QString &msg) const
1024 if (!m_inError && m_handler)
1025 m_handler->parseError(m_proFile->fileName(), m_lineNo, msg);