008343a05e
Use size_t instead of uint. Change-Id: I1dc38f61653f9bfc4ddeddcc65b0271aa4ad1256 Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
1575 lines
56 KiB
C++
1575 lines
56 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the qmake application of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qmakeparser.h"
|
|
|
|
#include "qmakevfs.h"
|
|
#include "ioutils.h"
|
|
using namespace QMakeInternal;
|
|
|
|
#include <qfile.h>
|
|
#ifdef PROPARSER_THREAD_SAFE
|
|
# include <qthreadpool.h>
|
|
#endif
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ProFileCache
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
ProFileCache::ProFileCache()
|
|
{
|
|
QMakeVfs::ref();
|
|
}
|
|
|
|
ProFileCache::~ProFileCache()
|
|
{
|
|
for (const Entry &ent : qAsConst(parsed_files))
|
|
if (ent.pro)
|
|
ent.pro->deref();
|
|
QMakeVfs::deref();
|
|
}
|
|
|
|
void ProFileCache::discardFile(const QString &fileName, QMakeVfs *vfs)
|
|
{
|
|
int eid = vfs->idForFileName(fileName, QMakeVfs::VfsExact | QMakeVfs::VfsAccessedOnly);
|
|
if (eid)
|
|
discardFile(eid);
|
|
int cid = vfs->idForFileName(fileName, QMakeVfs::VfsCumulative | QMakeVfs::VfsAccessedOnly);
|
|
if (cid && cid != eid)
|
|
discardFile(cid);
|
|
}
|
|
|
|
void ProFileCache::discardFile(int id)
|
|
{
|
|
#ifdef PROPARSER_THREAD_SAFE
|
|
QMutexLocker lck(&mutex);
|
|
#endif
|
|
auto it = parsed_files.find(id);
|
|
if (it != parsed_files.end()) {
|
|
#ifdef PROPARSER_THREAD_SAFE
|
|
if (it->locker) {
|
|
if (!it->locker->done) {
|
|
++it->locker->waiters;
|
|
it->locker->cond.wait(&mutex);
|
|
if (!--it->locker->waiters) {
|
|
delete it->locker;
|
|
it->locker = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (it->pro)
|
|
it->pro->deref();
|
|
parsed_files.erase(it);
|
|
}
|
|
}
|
|
|
|
void ProFileCache::discardFiles(const QString &prefix, QMakeVfs *vfs)
|
|
{
|
|
#ifdef PROPARSER_THREAD_SAFE
|
|
QMutexLocker lck(&mutex);
|
|
#endif
|
|
auto it = parsed_files.begin(), end = parsed_files.end();
|
|
while (it != end) {
|
|
// Note: this is empty for virtual files from other VFSes.
|
|
QString fn = vfs->fileNameForId(it.key());
|
|
if (fn.startsWith(prefix)) {
|
|
#ifdef PROPARSER_THREAD_SAFE
|
|
if (it->locker) {
|
|
if (!it->locker->done) {
|
|
++it->locker->waiters;
|
|
it->locker->cond.wait(&mutex);
|
|
if (!--it->locker->waiters) {
|
|
delete it->locker;
|
|
it->locker = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (it->pro)
|
|
it->pro->deref();
|
|
it = parsed_files.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////// Parser ///////////
|
|
|
|
#define fL1S(s) QString::fromLatin1(s)
|
|
|
|
namespace { // MSVC2010 doesn't seem to know the semantics of "static" ...
|
|
|
|
static struct {
|
|
QString strelse;
|
|
QString strfor;
|
|
QString strdefineTest;
|
|
QString strdefineReplace;
|
|
QString strbypassNesting;
|
|
QString stroption;
|
|
QString strreturn;
|
|
QString strnext;
|
|
QString strbreak;
|
|
QString strhost_build;
|
|
QString strLINE;
|
|
QString strFILE;
|
|
QString strLITERAL_HASH;
|
|
QString strLITERAL_DOLLAR;
|
|
QString strLITERAL_WHITESPACE;
|
|
} statics;
|
|
|
|
}
|
|
|
|
void QMakeParser::initialize()
|
|
{
|
|
if (!statics.strelse.isNull())
|
|
return;
|
|
|
|
statics.strelse = QLatin1String("else");
|
|
statics.strfor = QLatin1String("for");
|
|
statics.strdefineTest = QLatin1String("defineTest");
|
|
statics.strdefineReplace = QLatin1String("defineReplace");
|
|
statics.strbypassNesting = QLatin1String("bypassNesting");
|
|
statics.stroption = QLatin1String("option");
|
|
statics.strreturn = QLatin1String("return");
|
|
statics.strnext = QLatin1String("next");
|
|
statics.strbreak = QLatin1String("break");
|
|
statics.strhost_build = QLatin1String("host_build");
|
|
statics.strLINE = QLatin1String("_LINE_");
|
|
statics.strFILE = QLatin1String("_FILE_");
|
|
statics.strLITERAL_HASH = QLatin1String("LITERAL_HASH");
|
|
statics.strLITERAL_DOLLAR = QLatin1String("LITERAL_DOLLAR");
|
|
statics.strLITERAL_WHITESPACE = QLatin1String("LITERAL_WHITESPACE");
|
|
}
|
|
|
|
QMakeParser::QMakeParser(ProFileCache *cache, QMakeVfs *vfs, QMakeParserHandler *handler)
|
|
: m_cache(cache)
|
|
, m_handler(handler)
|
|
, m_vfs(vfs)
|
|
{
|
|
// So that single-threaded apps don't have to call initialize() for now.
|
|
initialize();
|
|
}
|
|
|
|
ProFile *QMakeParser::parsedProFile(const QString &fileName, ParseFlags flags)
|
|
{
|
|
ProFile *pro;
|
|
QMakeVfs::VfsFlags vfsFlags = ((flags & ParseCumulative) ? QMakeVfs::VfsCumulative
|
|
: QMakeVfs::VfsExact);
|
|
int id = m_vfs->idForFileName(fileName, vfsFlags);
|
|
if ((flags & ParseUseCache) && m_cache) {
|
|
ProFileCache::Entry *ent;
|
|
#ifdef PROPARSER_THREAD_SAFE
|
|
QMutexLocker locker(&m_cache->mutex);
|
|
#endif
|
|
auto it = m_cache->parsed_files.find(id);
|
|
if (it != m_cache->parsed_files.end()) {
|
|
ent = &*it;
|
|
#ifdef PROPARSER_THREAD_SAFE
|
|
if (ent->locker && !ent->locker->done) {
|
|
++ent->locker->waiters;
|
|
QThreadPool::globalInstance()->releaseThread();
|
|
ent->locker->cond.wait(locker.mutex());
|
|
QThreadPool::globalInstance()->reserveThread();
|
|
if (!--ent->locker->waiters) {
|
|
delete ent->locker;
|
|
ent->locker = 0;
|
|
}
|
|
}
|
|
#endif
|
|
if ((pro = ent->pro))
|
|
pro->ref();
|
|
} else {
|
|
ent = &m_cache->parsed_files[id];
|
|
#ifdef PROPARSER_THREAD_SAFE
|
|
ent->locker = new ProFileCache::Entry::Locker;
|
|
locker.unlock();
|
|
#endif
|
|
QString contents;
|
|
if (readFile(id, flags, &contents)) {
|
|
pro = parsedProBlock(QStringView(contents), id, fileName, 1, FullGrammar);
|
|
pro->itemsRef()->squeeze();
|
|
pro->ref();
|
|
} else {
|
|
pro = nullptr;
|
|
}
|
|
ent->pro = pro;
|
|
#ifdef PROPARSER_THREAD_SAFE
|
|
locker.relock();
|
|
if (ent->locker->waiters) {
|
|
ent->locker->done = true;
|
|
ent->locker->cond.wakeAll();
|
|
} else {
|
|
delete ent->locker;
|
|
ent->locker = 0;
|
|
}
|
|
#endif
|
|
}
|
|
} else {
|
|
QString contents;
|
|
if (readFile(id, flags, &contents))
|
|
pro = parsedProBlock(QStringView(contents), id, fileName, 1, FullGrammar);
|
|
else
|
|
pro = nullptr;
|
|
}
|
|
return pro;
|
|
}
|
|
|
|
ProFile *QMakeParser::parsedProBlock(
|
|
QStringView contents, int id, const QString &name, int line, SubGrammar grammar)
|
|
{
|
|
ProFile *pro = new ProFile(id, name);
|
|
read(pro, contents, line, grammar);
|
|
return pro;
|
|
}
|
|
|
|
void QMakeParser::discardFileFromCache(int id)
|
|
{
|
|
if (m_cache)
|
|
m_cache->discardFile(id);
|
|
}
|
|
|
|
bool QMakeParser::readFile(int id, ParseFlags flags, QString *contents)
|
|
{
|
|
QString errStr;
|
|
QMakeVfs::ReadResult result = m_vfs->readFile(id, contents, &errStr);
|
|
if (result != QMakeVfs::ReadOk) {
|
|
if (m_handler && ((flags & ParseReportMissing) || result != QMakeVfs::ReadNotFound))
|
|
m_handler->message(QMakeParserHandler::ParserIoError,
|
|
fL1S("Cannot read %1: %2").arg(m_vfs->fileNameForId(id), errStr));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void QMakeParser::putTok(ushort *&tokPtr, ushort tok)
|
|
{
|
|
*tokPtr++ = tok;
|
|
}
|
|
|
|
void QMakeParser::putBlockLen(ushort *&tokPtr, uint len)
|
|
{
|
|
*tokPtr++ = (ushort)len;
|
|
*tokPtr++ = (ushort)(len >> 16);
|
|
}
|
|
|
|
void QMakeParser::putBlock(ushort *&tokPtr, const ushort *buf, uint len)
|
|
{
|
|
memcpy(tokPtr, buf, len * 2);
|
|
tokPtr += len;
|
|
}
|
|
|
|
void QMakeParser::putHashStr(ushort *&pTokPtr, const ushort *buf, uint len)
|
|
{
|
|
const size_t hash = ProString::hash((const QChar *)buf, len);
|
|
ushort *tokPtr = pTokPtr;
|
|
*tokPtr++ = (ushort)hash;
|
|
*tokPtr++ = (ushort)(hash >> 16);
|
|
*tokPtr++ = (ushort)len;
|
|
if (len) // buf may be nullptr; don't pass that to memcpy (-> undefined behavior)
|
|
memcpy(tokPtr, buf, len * 2);
|
|
pTokPtr = tokPtr + len;
|
|
}
|
|
|
|
void QMakeParser::finalizeHashStr(ushort *buf, uint len)
|
|
{
|
|
buf[-4] = TokHashLiteral;
|
|
buf[-1] = len;
|
|
const size_t hash = ProString::hash((const QChar *)buf, len);
|
|
buf[-3] = (ushort)hash;
|
|
buf[-2] = (ushort)(hash >> 16);
|
|
}
|
|
|
|
void QMakeParser::read(ProFile *pro, QStringView in, int line, SubGrammar grammar)
|
|
{
|
|
m_proFile = pro;
|
|
m_lineNo = line;
|
|
|
|
// 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) + size_hint(1) +
|
|
// TokValueTerminator(1) == 8 (9)
|
|
// - non-empty assignment "A=B C":5 =>
|
|
// TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokAssign(1) + size_hint(1) +
|
|
// TokLiteral(1) + len(1) + "B"(1) +
|
|
// TokLiteral(1) + len(1) + "C"(1) + TokValueTerminator(1) == 14 (15)
|
|
// - 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
|
|
// - test literal: "X":1 =>
|
|
// TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokCondition(1) = 6 (7)
|
|
// - scope: "X:":2 =>
|
|
// TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokCondition(1) +
|
|
// TokBranch(1) + len(2) + ... + len(2) + ... == 11 (12)
|
|
// - test call: "X():":4 =>
|
|
// TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokTestCall(1) + TokFuncTerminator(1) +
|
|
// TokBranch(1) + len(2) + ... + len(2) + ... == 12 (13)
|
|
// - "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)
|
|
// One extra for possibly missing trailing newline.
|
|
tokBuff.reserve((in.size() + 1) * 7);
|
|
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.clear();
|
|
m_blockstack.resize(1);
|
|
|
|
QStack<ParseCtx> xprStack;
|
|
xprStack.reserve(10);
|
|
|
|
const ushort *cur = (const ushort *)in.data();
|
|
const ushort *inend = cur + in.length();
|
|
m_canElse = false;
|
|
freshLine:
|
|
m_state = StNew;
|
|
m_invert = 0;
|
|
m_operator = NoOperator;
|
|
m_markLine = m_lineNo;
|
|
m_inError = false;
|
|
int parens = 0; // Braces in value context
|
|
int argc = 0;
|
|
int wordCount = 0; // Number of words in currently accumulated expression
|
|
int lastIndent = 0; // Previous line's indentation, to detect accidental continuation abuse
|
|
bool lineMarked = true; // For in-expression markers
|
|
char16_t needSep = TokNewStr; // Met unquoted whitespace
|
|
char16_t quote = 0;
|
|
char16_t term = 0;
|
|
|
|
Context context;
|
|
ushort *ptr;
|
|
if (grammar == ValueGrammar) {
|
|
context = CtxPureValue;
|
|
ptr = tokPtr + 2;
|
|
} else {
|
|
context = CtxTest;
|
|
ptr = buf + 4;
|
|
}
|
|
ushort *xprPtr = ptr;
|
|
|
|
#define FLUSH_LHS_LITERAL() \
|
|
do { \
|
|
if ((tlen = ptr - xprPtr)) { \
|
|
finalizeHashStr(xprPtr, tlen); \
|
|
if (needSep) { \
|
|
wordCount++; \
|
|
needSep = 0; \
|
|
} \
|
|
} else { \
|
|
ptr -= 4; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define FLUSH_RHS_LITERAL() \
|
|
do { \
|
|
if ((tlen = ptr - xprPtr)) { \
|
|
xprPtr[-2] = TokLiteral | needSep; \
|
|
xprPtr[-1] = tlen; \
|
|
if (needSep) { \
|
|
wordCount++; \
|
|
needSep = 0; \
|
|
} \
|
|
} else { \
|
|
ptr -= 2; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define FLUSH_LITERAL() \
|
|
do { \
|
|
if (context == CtxTest) \
|
|
FLUSH_LHS_LITERAL(); \
|
|
else \
|
|
FLUSH_RHS_LITERAL(); \
|
|
} while (0)
|
|
|
|
#define FLUSH_VALUE_LIST() \
|
|
do { \
|
|
if (wordCount > 1) { \
|
|
xprPtr = tokPtr; \
|
|
if (*xprPtr == TokLine) \
|
|
xprPtr += 2; \
|
|
tokPtr[-1] = ((*xprPtr & TokMask) == TokLiteral) ? wordCount : 0; \
|
|
} else { \
|
|
tokPtr[-1] = 0; \
|
|
} \
|
|
tokPtr = ptr; \
|
|
putTok(tokPtr, TokValueTerminator); \
|
|
} while (0)
|
|
|
|
const ushort *end; // End of this line
|
|
const ushort *cptr; // Start of next line
|
|
bool lineCont;
|
|
int indent;
|
|
|
|
if (context == CtxPureValue) {
|
|
end = inend;
|
|
cptr = nullptr;
|
|
lineCont = false;
|
|
indent = 0; // just gcc being stupid
|
|
goto nextChr;
|
|
}
|
|
|
|
forever {
|
|
char16_t c;
|
|
|
|
// First, skip leading whitespace
|
|
for (indent = 0; ; ++cur, ++indent) {
|
|
if (cur == inend) {
|
|
cur = nullptr;
|
|
goto flushLine;
|
|
}
|
|
c = *cur;
|
|
if (c == '\n') {
|
|
++cur;
|
|
goto flushLine;
|
|
}
|
|
if (c != ' ' && c != '\t' && c != '\r')
|
|
break;
|
|
}
|
|
|
|
// Then strip comments. Yep - no escaping is possible.
|
|
for (cptr = cur;; ++cptr) {
|
|
if (cptr == inend) {
|
|
end = cptr;
|
|
break;
|
|
}
|
|
c = *cptr;
|
|
if (c == '#') {
|
|
end = cptr;
|
|
while (++cptr < inend) {
|
|
if (*cptr == '\n') {
|
|
++cptr;
|
|
break;
|
|
}
|
|
}
|
|
if (end == cur) { // Line with only a comment (sans whitespace)
|
|
if (m_markLine == m_lineNo)
|
|
m_markLine++;
|
|
// Qmake bizarreness: such lines do not affect line continuations
|
|
goto ignore;
|
|
}
|
|
break;
|
|
}
|
|
if (c == '\n') {
|
|
end = cptr++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Then look for line continuations. Yep - no escaping here as well.
|
|
forever {
|
|
// We don't have to check for underrun here, as we already determined
|
|
// that the line is non-empty.
|
|
ushort ec = *(end - 1);
|
|
if (ec == '\\') {
|
|
--end;
|
|
lineCont = true;
|
|
break;
|
|
}
|
|
if (ec != ' ' && ec != '\t' && ec != '\r') {
|
|
lineCont = false;
|
|
break;
|
|
}
|
|
--end;
|
|
}
|
|
|
|
// Finally, do the tokenization
|
|
ushort tok, rtok;
|
|
int tlen;
|
|
newWord:
|
|
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++;
|
|
FLUSH_LITERAL();
|
|
if (!lineMarked) {
|
|
lineMarked = true;
|
|
*ptr++ = TokLine;
|
|
*ptr++ = (ushort)m_lineNo;
|
|
}
|
|
term = 0;
|
|
tok = TokVariable;
|
|
c = *cur;
|
|
if (c == '[') {
|
|
ptr += 4;
|
|
tok = TokProperty;
|
|
term = ']';
|
|
c = *++cur;
|
|
} else if (c == '{') {
|
|
ptr += 4;
|
|
term = '}';
|
|
c = *++cur;
|
|
} else if (c == '(') {
|
|
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') || (c == '/' && term)) {
|
|
*ptr++ = c;
|
|
if (++cur == end) {
|
|
c = 0;
|
|
goto notfunc;
|
|
}
|
|
c = *cur;
|
|
}
|
|
if (tok == TokVariable && c == '(')
|
|
tok = TokFuncName;
|
|
notfunc:
|
|
if (ptr == xprPtr)
|
|
languageWarning(fL1S("Missing name in expansion"));
|
|
if (quote)
|
|
tok |= TokQuoted;
|
|
if (needSep) {
|
|
tok |= needSep;
|
|
wordCount++;
|
|
}
|
|
tlen = ptr - xprPtr;
|
|
if (rtok != TokVariable
|
|
|| !resolveVariable(xprPtr, tlen, needSep, &ptr,
|
|
&buf, &xprBuff, &tokPtr, &tokBuff, cur, in)) {
|
|
if (rtok == TokVariable || rtok == TokProperty) {
|
|
xprPtr[-4] = tok;
|
|
const size_t hash = ProString::hash((const QChar *)xprPtr, tlen);
|
|
xprPtr[-3] = (ushort)hash;
|
|
xprPtr[-2] = (ushort)(hash >> 16);
|
|
xprPtr[-1] = tlen;
|
|
} 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;
|
|
top.wordCount = wordCount;
|
|
}
|
|
parens = 0;
|
|
quote = 0;
|
|
term = 0;
|
|
argc = 1;
|
|
context = CtxArgs;
|
|
nextToken:
|
|
wordCount = 0;
|
|
nextWord:
|
|
ptr += (context == CtxTest) ? 4 : 2;
|
|
xprPtr = ptr;
|
|
needSep = TokNewStr;
|
|
goto newWord;
|
|
}
|
|
if (term) {
|
|
checkTerm:
|
|
if (c != term) {
|
|
parseError(fL1S("Missing %1 terminator [found %2]")
|
|
.arg(QChar(term))
|
|
.arg(c ? QString(QChar(c)) : QString::fromLatin1("end-of-line")));
|
|
m_inError = true;
|
|
// Just parse on, as if there was a terminator ...
|
|
} else {
|
|
cur++;
|
|
}
|
|
}
|
|
joinToken:
|
|
ptr += (context == CtxTest) ? 4 : 2;
|
|
xprPtr = ptr;
|
|
needSep = 0;
|
|
goto nextChr;
|
|
}
|
|
} else if (c == '\\') {
|
|
static const char symbols[] = "[]{}()$\\'\"";
|
|
char16_t c2;
|
|
if (cur != end && !((c2 = *cur) & 0xff00) && strchr(symbols, c2)) {
|
|
c = c2;
|
|
cur++;
|
|
} else {
|
|
deprecationWarning(fL1S("Unescaped backslashes are deprecated"));
|
|
}
|
|
} else if (quote) {
|
|
if (c == quote) {
|
|
quote = 0;
|
|
goto nextChr;
|
|
} else if (c == '!' && ptr == xprPtr && context == CtxTest) {
|
|
m_invert++;
|
|
goto nextChr;
|
|
}
|
|
} else if (c == '\'' || c == '"') {
|
|
quote = c;
|
|
goto nextChr;
|
|
} else if (context == CtxArgs) {
|
|
// Function arg context
|
|
if (c == ' ' || c == '\t') {
|
|
FLUSH_RHS_LITERAL();
|
|
goto nextWord;
|
|
} else if (c == '(') {
|
|
++parens;
|
|
} else if (c == ')') {
|
|
if (--parens < 0) {
|
|
FLUSH_RHS_LITERAL();
|
|
*ptr++ = TokFuncTerminator;
|
|
int theargc = argc;
|
|
{
|
|
ParseCtx &top = xprStack.top();
|
|
parens = top.parens;
|
|
quote = top.quote;
|
|
term = top.terminator;
|
|
context = top.context;
|
|
argc = top.argc;
|
|
wordCount = top.wordCount;
|
|
xprStack.resize(xprStack.size() - 1);
|
|
}
|
|
if (term == ':') {
|
|
finalizeCall(tokPtr, buf, ptr, theargc);
|
|
goto nextItem;
|
|
} else if (term == '}') {
|
|
c = (cur == end) ? 0 : *cur;
|
|
goto checkTerm;
|
|
} else {
|
|
Q_ASSERT(!term);
|
|
goto joinToken;
|
|
}
|
|
}
|
|
} else if (!parens && c == ',') {
|
|
FLUSH_RHS_LITERAL();
|
|
*ptr++ = TokArgSeparator;
|
|
argc++;
|
|
goto nextToken;
|
|
}
|
|
} else if (context == CtxTest) {
|
|
// Test or LHS context
|
|
if (c == ' ' || c == '\t') {
|
|
FLUSH_LHS_LITERAL();
|
|
goto nextWord;
|
|
} else if (c == '(') {
|
|
FLUSH_LHS_LITERAL();
|
|
if (wordCount != 1) {
|
|
if (wordCount)
|
|
parseError(fL1S("Extra characters after test expression."));
|
|
else
|
|
parseError(fL1S("Opening parenthesis without prior test name."));
|
|
ptr = buf; // Put empty function name
|
|
}
|
|
*ptr++ = TokTestCall;
|
|
term = ':';
|
|
goto funcCall;
|
|
} else if (c == '!' && ptr == xprPtr) {
|
|
m_invert++;
|
|
goto nextChr;
|
|
} else if (c == ':') {
|
|
FLUSH_LHS_LITERAL();
|
|
finalizeCond(tokPtr, buf, ptr, wordCount);
|
|
warnOperator("in front of AND operator");
|
|
if (m_state == StNew)
|
|
parseError(fL1S("AND operator without prior condition."));
|
|
else
|
|
m_operator = AndOperator;
|
|
nextItem:
|
|
ptr = buf;
|
|
goto nextToken;
|
|
} else if (c == '|') {
|
|
FLUSH_LHS_LITERAL();
|
|
finalizeCond(tokPtr, buf, ptr, wordCount);
|
|
warnOperator("in front of OR operator");
|
|
if (m_state != StCond)
|
|
parseError(fL1S("OR operator without prior condition."));
|
|
else
|
|
m_operator = OrOperator;
|
|
goto nextItem;
|
|
} else if (c == '{') {
|
|
FLUSH_LHS_LITERAL();
|
|
finalizeCond(tokPtr, buf, ptr, wordCount);
|
|
if (m_operator == AndOperator) {
|
|
languageWarning(fL1S("Excess colon in front of opening brace."));
|
|
m_operator = NoOperator;
|
|
}
|
|
failOperator("in front of opening brace");
|
|
flushCond(tokPtr);
|
|
m_state = StNew; // Reset possible StCtrl, so colons get rejected.
|
|
++m_blockstack.top().braceLevel;
|
|
if (grammar == TestGrammar)
|
|
parseError(fL1S("Opening scope not permitted in this context."));
|
|
goto nextItem;
|
|
} else if (c == '}') {
|
|
FLUSH_LHS_LITERAL();
|
|
finalizeCond(tokPtr, buf, ptr, wordCount);
|
|
m_state = StNew; // De-facto newline
|
|
closeScope:
|
|
flushScopes(tokPtr);
|
|
failOperator("in front of closing brace");
|
|
if (!m_blockstack.top().braceLevel) {
|
|
parseError(fL1S("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();
|
|
flushCond(tokPtr);
|
|
acceptColon("in front of assignment");
|
|
putLineMarker(tokPtr);
|
|
if (grammar == TestGrammar) {
|
|
parseError(fL1S("Assignment not permitted in this context."));
|
|
} else if (wordCount != 1) {
|
|
parseError(fL1S("Assignment needs exactly one word on the left hand side."));
|
|
// Put empty variable name.
|
|
} else {
|
|
putBlock(tokPtr, buf, ptr - buf);
|
|
}
|
|
putTok(tokPtr, tok);
|
|
context = CtxValue;
|
|
ptr = ++tokPtr;
|
|
goto nextToken;
|
|
}
|
|
} else if (context == CtxValue) {
|
|
if (c == ' ' || c == '\t') {
|
|
FLUSH_RHS_LITERAL();
|
|
goto nextWord;
|
|
} else if (c == '{') {
|
|
++parens;
|
|
} else if (c == '}') {
|
|
if (!parens) {
|
|
FLUSH_RHS_LITERAL();
|
|
FLUSH_VALUE_LIST();
|
|
context = CtxTest;
|
|
goto closeScope;
|
|
}
|
|
--parens;
|
|
} else if (c == '=') {
|
|
if (indent < lastIndent)
|
|
languageWarning(fL1S("Possible accidental line continuation"));
|
|
}
|
|
}
|
|
*ptr++ = c;
|
|
nextChr:
|
|
if (cur == end)
|
|
goto lineEnd;
|
|
c = *cur++;
|
|
}
|
|
|
|
lineEnd:
|
|
if (lineCont) {
|
|
if (quote) {
|
|
*ptr++ = ' ';
|
|
} else {
|
|
FLUSH_LITERAL();
|
|
needSep = TokNewStr;
|
|
ptr += (context == CtxTest) ? 4 : 2;
|
|
xprPtr = ptr;
|
|
}
|
|
} else {
|
|
cur = cptr;
|
|
flushLine:
|
|
FLUSH_LITERAL();
|
|
if (quote) {
|
|
parseError(fL1S("Missing closing %1 quote").arg(QChar(quote)));
|
|
if (!xprStack.isEmpty()) {
|
|
context = xprStack.at(0).context;
|
|
xprStack.clear();
|
|
}
|
|
goto flErr;
|
|
} else if (!xprStack.isEmpty()) {
|
|
parseError(fL1S("Missing closing parenthesis in function call"));
|
|
context = xprStack.at(0).context;
|
|
xprStack.clear();
|
|
flErr:
|
|
pro->setOk(false);
|
|
if (context == CtxValue) {
|
|
tokPtr[-1] = 0; // sizehint
|
|
putTok(tokPtr, TokValueTerminator);
|
|
} else if (context == CtxPureValue) {
|
|
putTok(tokPtr, TokValueTerminator);
|
|
} else {
|
|
bogusTest(tokPtr, QString());
|
|
}
|
|
} else if (context == CtxValue) {
|
|
FLUSH_VALUE_LIST();
|
|
if (parens)
|
|
languageWarning(fL1S("Possible braces mismatch"));
|
|
} else if (context == CtxPureValue) {
|
|
tokPtr = ptr;
|
|
putTok(tokPtr, TokValueTerminator);
|
|
} else {
|
|
finalizeCond(tokPtr, buf, ptr, wordCount);
|
|
warnOperator("at end of line");
|
|
}
|
|
if (!cur)
|
|
break;
|
|
++m_lineNo;
|
|
goto freshLine;
|
|
}
|
|
|
|
lastIndent = indent;
|
|
lineMarked = false;
|
|
ignore:
|
|
cur = cptr;
|
|
++m_lineNo;
|
|
}
|
|
|
|
flushScopes(tokPtr);
|
|
if (m_blockstack.size() > 1 || m_blockstack.top().braceLevel)
|
|
parseError(fL1S("Missing closing brace(s)."));
|
|
while (m_blockstack.size())
|
|
leaveScope(tokPtr);
|
|
tokBuff.resize(tokPtr - (ushort *)tokBuff.constData()); // Reserved capacity stays
|
|
*pro->itemsRef() = tokBuff;
|
|
|
|
#undef FLUSH_VALUE_LIST
|
|
#undef FLUSH_LITERAL
|
|
#undef FLUSH_LHS_LITERAL
|
|
#undef FLUSH_RHS_LITERAL
|
|
}
|
|
|
|
void QMakeParser::putLineMarker(ushort *&tokPtr)
|
|
{
|
|
if (m_markLine) {
|
|
*tokPtr++ = TokLine;
|
|
*tokPtr++ = (ushort)m_markLine;
|
|
m_markLine = 0;
|
|
}
|
|
}
|
|
|
|
void QMakeParser::enterScope(ushort *&tokPtr, bool special, ScopeState state)
|
|
{
|
|
uchar nest = m_blockstack.top().nest;
|
|
m_blockstack.resize(m_blockstack.size() + 1);
|
|
m_blockstack.top().special = special;
|
|
m_blockstack.top().start = tokPtr;
|
|
m_blockstack.top().nest = nest;
|
|
tokPtr += 2;
|
|
m_state = state;
|
|
m_canElse = false;
|
|
if (special)
|
|
m_markLine = m_lineNo;
|
|
}
|
|
|
|
void QMakeParser::leaveScope(ushort *&tokPtr)
|
|
{
|
|
if (m_blockstack.top().inBranch) {
|
|
// Put empty else block
|
|
putBlockLen(tokPtr, 0);
|
|
}
|
|
if (ushort *start = m_blockstack.top().start) {
|
|
putTok(tokPtr, TokTerminator);
|
|
uint len = tokPtr - start - 2;
|
|
start[0] = (ushort)len;
|
|
start[1] = (ushort)(len >> 16);
|
|
}
|
|
m_blockstack.resize(m_blockstack.size() - 1);
|
|
}
|
|
|
|
// If we are on a fresh line, close all open one-line scopes.
|
|
void QMakeParser::flushScopes(ushort *&tokPtr)
|
|
{
|
|
if (m_state == StNew) {
|
|
while (!m_blockstack.top().braceLevel && m_blockstack.size() > 1)
|
|
leaveScope(tokPtr);
|
|
if (m_blockstack.top().inBranch) {
|
|
m_blockstack.top().inBranch = false;
|
|
// Put empty else block
|
|
putBlockLen(tokPtr, 0);
|
|
}
|
|
m_canElse = false;
|
|
}
|
|
}
|
|
|
|
// If there is a pending conditional, enter a new scope, otherwise flush scopes.
|
|
void QMakeParser::flushCond(ushort *&tokPtr)
|
|
{
|
|
if (m_state == StCond) {
|
|
putTok(tokPtr, TokBranch);
|
|
m_blockstack.top().inBranch = true;
|
|
enterScope(tokPtr, false, StNew);
|
|
} else {
|
|
flushScopes(tokPtr);
|
|
}
|
|
}
|
|
|
|
void QMakeParser::warnOperator(const char *msg)
|
|
{
|
|
if (m_invert) {
|
|
languageWarning(fL1S("Stray NOT operator %1.").arg(fL1S(msg)));
|
|
m_invert = 0;
|
|
}
|
|
if (m_operator == AndOperator) {
|
|
languageWarning(fL1S("Stray AND operator %1.").arg(fL1S(msg)));
|
|
m_operator = NoOperator;
|
|
} else if (m_operator == OrOperator) {
|
|
languageWarning(fL1S("Stray OR operator %1.").arg(fL1S(msg)));
|
|
m_operator = NoOperator;
|
|
}
|
|
}
|
|
|
|
bool QMakeParser::failOperator(const char *msg)
|
|
{
|
|
bool fail = false;
|
|
if (m_invert) {
|
|
parseError(fL1S("Unexpected NOT operator %1.").arg(fL1S(msg)));
|
|
m_invert = 0;
|
|
fail = true;
|
|
}
|
|
if (m_operator == AndOperator) {
|
|
parseError(fL1S("Unexpected AND operator %1.").arg(fL1S(msg)));
|
|
m_operator = NoOperator;
|
|
fail = true;
|
|
} else if (m_operator == OrOperator) {
|
|
parseError(fL1S("Unexpected OR operator %1.").arg(fL1S(msg)));
|
|
m_operator = NoOperator;
|
|
fail = true;
|
|
}
|
|
return fail;
|
|
}
|
|
|
|
bool QMakeParser::acceptColon(const char *msg)
|
|
{
|
|
if (m_operator == AndOperator)
|
|
m_operator = NoOperator;
|
|
return !failOperator(msg);
|
|
}
|
|
|
|
void QMakeParser::putOperator(ushort *&tokPtr)
|
|
{
|
|
if (m_operator== AndOperator) {
|
|
// A colon must be used after else and for() if no brace is used,
|
|
// but in this case it is obviously not a binary operator.
|
|
if (m_state == StCond)
|
|
putTok(tokPtr, TokAnd);
|
|
m_operator = NoOperator;
|
|
} else if (m_operator == OrOperator) {
|
|
putTok(tokPtr, TokOr);
|
|
m_operator = NoOperator;
|
|
}
|
|
}
|
|
|
|
void QMakeParser::finalizeTest(ushort *&tokPtr)
|
|
{
|
|
flushScopes(tokPtr);
|
|
putLineMarker(tokPtr);
|
|
putOperator(tokPtr);
|
|
if (m_invert & 1)
|
|
putTok(tokPtr, TokNot);
|
|
m_invert = 0;
|
|
m_state = StCond;
|
|
m_canElse = true;
|
|
}
|
|
|
|
void QMakeParser::bogusTest(ushort *&tokPtr, const QString &msg)
|
|
{
|
|
if (!msg.isEmpty())
|
|
parseError(msg);
|
|
flushScopes(tokPtr);
|
|
m_operator = NoOperator;
|
|
m_invert = 0;
|
|
m_state = StCond;
|
|
m_canElse = true;
|
|
}
|
|
|
|
void QMakeParser::finalizeCond(ushort *&tokPtr, ushort *uc, ushort *ptr, int wordCount)
|
|
{
|
|
if (wordCount != 1) {
|
|
if (wordCount)
|
|
bogusTest(tokPtr, fL1S("Extra characters after test expression."));
|
|
return;
|
|
}
|
|
|
|
// Check for magic tokens
|
|
if (*uc == TokHashLiteral) {
|
|
uint nlen = uc[3];
|
|
ushort *uce = uc + 4 + nlen;
|
|
if (uce == ptr) {
|
|
m_tmp.setRawData((QChar *)uc + 4, nlen);
|
|
if (!m_tmp.compare(statics.strelse, Qt::CaseInsensitive)) {
|
|
if (failOperator("in front of else"))
|
|
return;
|
|
BlockScope &top = m_blockstack.top();
|
|
if (m_canElse && (!top.special || top.braceLevel)) {
|
|
// A list of tests (the last one likely with side effects),
|
|
// but no assignment, scope, etc.
|
|
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);
|
|
}
|
|
parseError(fL1S("Unexpected 'else'."));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
finalizeTest(tokPtr);
|
|
putBlock(tokPtr, uc, ptr - uc);
|
|
putTok(tokPtr, TokCondition);
|
|
}
|
|
|
|
void QMakeParser::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_tmp.setRawData((QChar *)uc + 4, nlen);
|
|
const QString *defName;
|
|
ushort defType;
|
|
if (m_tmp == statics.strfor) {
|
|
if (!acceptColon("in front of for()")) {
|
|
bogusTest(tokPtr, QString());
|
|
return;
|
|
}
|
|
flushCond(tokPtr);
|
|
putLineMarker(tokPtr);
|
|
--ptr;
|
|
Q_ASSERT(*ptr == TokFuncTerminator);
|
|
if (*uce == (TokLiteral|TokNewStr)) {
|
|
nlen = uce[1];
|
|
uc = uce + 2 + nlen;
|
|
if (uc == ptr) {
|
|
// for(literal) (only "ever" would be legal if qmake was sane)
|
|
putTok(tokPtr, TokForLoop);
|
|
putHashStr(tokPtr, nullptr, (uint)0);
|
|
putBlockLen(tokPtr, 1 + 3 + nlen + 1);
|
|
putTok(tokPtr, TokHashLiteral);
|
|
putHashStr(tokPtr, uce + 2, nlen);
|
|
didFor:
|
|
putTok(tokPtr, TokValueTerminator);
|
|
enterScope(tokPtr, true, StCtrl);
|
|
m_blockstack.top().nest |= NestLoop;
|
|
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, nullptr, (uint)0);
|
|
uc = uce;
|
|
goto doFor;
|
|
}
|
|
parseError(fL1S("Syntax is for(var, list), for(var, forever) or for(ever)."));
|
|
return;
|
|
} else if (m_tmp == statics.strdefineReplace) {
|
|
defName = &statics.strdefineReplace;
|
|
defType = TokReplaceDef;
|
|
goto deffunc;
|
|
} else if (m_tmp == statics.strdefineTest) {
|
|
defName = &statics.strdefineTest;
|
|
defType = TokTestDef;
|
|
deffunc:
|
|
if (m_invert) {
|
|
bogusTest(tokPtr, fL1S("Unexpected NOT operator in front of function definition."));
|
|
return;
|
|
}
|
|
flushScopes(tokPtr);
|
|
putLineMarker(tokPtr);
|
|
if (*uce == (TokLiteral|TokNewStr)) {
|
|
uint nlen = uce[1];
|
|
if (uce[nlen + 2] == TokFuncTerminator) {
|
|
putOperator(tokPtr);
|
|
putTok(tokPtr, defType);
|
|
putHashStr(tokPtr, uce + 2, nlen);
|
|
enterScope(tokPtr, true, StCtrl);
|
|
m_blockstack.top().nest = NestFunction;
|
|
return;
|
|
}
|
|
}
|
|
parseError(fL1S("%1(function) requires one literal argument.").arg(*defName));
|
|
return;
|
|
} else if (m_tmp == statics.strbypassNesting) {
|
|
if (*uce != TokFuncTerminator) {
|
|
bogusTest(tokPtr, fL1S("%1() requires zero arguments.").arg(m_tmp));
|
|
return;
|
|
}
|
|
if (!(m_blockstack.top().nest & NestFunction)) {
|
|
bogusTest(tokPtr, fL1S("Unexpected %1().").arg(m_tmp));
|
|
return;
|
|
}
|
|
if (m_invert) {
|
|
bogusTest(tokPtr, fL1S("Unexpected NOT operator in front of %1().").arg(m_tmp));
|
|
return;
|
|
}
|
|
flushScopes(tokPtr);
|
|
putLineMarker(tokPtr);
|
|
putOperator(tokPtr);
|
|
putTok(tokPtr, TokBypassNesting);
|
|
enterScope(tokPtr, true, StCtrl);
|
|
return;
|
|
} else if (m_tmp == statics.strreturn) {
|
|
if (m_blockstack.top().nest & NestFunction) {
|
|
if (argc > 1) {
|
|
bogusTest(tokPtr, fL1S("return() requires zero or one argument."));
|
|
return;
|
|
}
|
|
} else {
|
|
if (*uce != TokFuncTerminator) {
|
|
bogusTest(tokPtr, fL1S("Top-level return() requires zero arguments."));
|
|
return;
|
|
}
|
|
}
|
|
defType = TokReturn;
|
|
goto ctrlstm2;
|
|
} else if (m_tmp == statics.strnext) {
|
|
defType = TokNext;
|
|
goto ctrlstm;
|
|
} else if (m_tmp == statics.strbreak) {
|
|
defType = TokBreak;
|
|
ctrlstm:
|
|
if (*uce != TokFuncTerminator) {
|
|
bogusTest(tokPtr, fL1S("%1() requires zero arguments.").arg(m_tmp));
|
|
return;
|
|
}
|
|
if (!(m_blockstack.top().nest & NestLoop)) {
|
|
bogusTest(tokPtr, fL1S("Unexpected %1().").arg(m_tmp));
|
|
return;
|
|
}
|
|
ctrlstm2:
|
|
if (m_invert) {
|
|
bogusTest(tokPtr, fL1S("Unexpected NOT operator in front of %1().").arg(m_tmp));
|
|
return;
|
|
}
|
|
finalizeTest(tokPtr);
|
|
putBlock(tokPtr, uce, ptr - uce - 1); // Only for TokReturn
|
|
putTok(tokPtr, defType);
|
|
return;
|
|
} else if (m_tmp == statics.stroption) {
|
|
if (m_state != StNew || m_blockstack.top().braceLevel || m_blockstack.size() > 1
|
|
|| m_invert || m_operator != NoOperator) {
|
|
bogusTest(tokPtr, fL1S("option() must appear outside any control structures."));
|
|
return;
|
|
}
|
|
if (*uce == (TokLiteral|TokNewStr)) {
|
|
uint nlen = uce[1];
|
|
if (uce[nlen + 2] == TokFuncTerminator) {
|
|
m_tmp.setRawData((QChar *)uce + 2, nlen);
|
|
if (m_tmp == statics.strhost_build)
|
|
m_proFile->setHostBuild(true);
|
|
else
|
|
parseError(fL1S("Unknown option() %1.").arg(m_tmp));
|
|
return;
|
|
}
|
|
}
|
|
parseError(fL1S("option() requires one literal argument."));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
finalizeTest(tokPtr);
|
|
putBlock(tokPtr, uc, ptr - uc);
|
|
}
|
|
|
|
bool QMakeParser::resolveVariable(ushort *xprPtr, int tlen, int needSep, ushort **ptr,
|
|
ushort **buf, QString *xprBuff,
|
|
ushort **tokPtr, QString *tokBuff,
|
|
const ushort *cur, QStringView in)
|
|
{
|
|
QString out;
|
|
m_tmp.setRawData((const QChar *)xprPtr, tlen);
|
|
if (m_tmp == statics.strLINE) {
|
|
out.setNum(m_lineNo);
|
|
} else if (m_tmp == statics.strFILE) {
|
|
out = m_proFile->fileName();
|
|
// The string is typically longer than the variable reference, so we need
|
|
// to ensure that there is enough space in the output buffer - as unlikely
|
|
// as an overflow is to actually happen in practice.
|
|
int need = (in.length() - (cur - (const ushort *)in.constData()) + 2) * 5 + out.length();
|
|
int tused = *tokPtr - (ushort *)tokBuff->constData();
|
|
int xused;
|
|
int total;
|
|
bool ptrFinal = xprPtr >= (ushort *)tokBuff->constData()
|
|
&& xprPtr < (ushort *)tokBuff->constData() + tokBuff->capacity();
|
|
if (ptrFinal) {
|
|
xused = xprPtr - (ushort *)tokBuff->constData();
|
|
total = xused + need;
|
|
} else {
|
|
xused = xprPtr - *buf;
|
|
total = tused + xused + need;
|
|
}
|
|
if (tokBuff->capacity() < total) {
|
|
tokBuff->reserve(total);
|
|
*tokPtr = (ushort *)tokBuff->constData() + tused;
|
|
xprBuff->reserve(total);
|
|
*buf = (ushort *)xprBuff->constData();
|
|
xprPtr = (ptrFinal ? (ushort *)tokBuff->constData() : *buf) + xused;
|
|
}
|
|
} else if (m_tmp == statics.strLITERAL_HASH) {
|
|
out = QLatin1String("#");
|
|
} else if (m_tmp == statics.strLITERAL_DOLLAR) {
|
|
out = QLatin1String("$");
|
|
} else if (m_tmp == statics.strLITERAL_WHITESPACE) {
|
|
out = QLatin1String("\t");
|
|
} else {
|
|
return false;
|
|
}
|
|
xprPtr -= 2; // Was set up for variable reference
|
|
xprPtr[-2] = TokLiteral | needSep;
|
|
xprPtr[-1] = out.length();
|
|
memcpy(xprPtr, out.constData(), out.length() * 2);
|
|
*ptr = xprPtr + out.length();
|
|
return true;
|
|
}
|
|
|
|
void QMakeParser::message(int type, const QString &msg) const
|
|
{
|
|
if (!m_inError && m_handler)
|
|
m_handler->message(type, msg, m_proFile->fileName(), m_lineNo);
|
|
}
|
|
|
|
#ifdef PROPARSER_DEBUG
|
|
|
|
#define BOUNDS_CHECK(need) \
|
|
do { \
|
|
int have = limit - offset; \
|
|
if (have < (int)need) { \
|
|
*outStr += fL1S("<out of bounds (need %1, got %2)>").arg(need).arg(have); \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
|
|
static bool getRawUshort(const ushort *tokens, int limit, int &offset, ushort *outVal, QString *outStr)
|
|
{
|
|
BOUNDS_CHECK(1);
|
|
uint val = tokens[offset++];
|
|
*outVal = val;
|
|
return true;
|
|
}
|
|
|
|
static bool getUshort(const ushort *tokens, int limit, int &offset, ushort *outVal, QString *outStr)
|
|
{
|
|
*outStr += fL1S(" << H(");
|
|
if (!getRawUshort(tokens, limit, offset, outVal, outStr))
|
|
return false;
|
|
*outStr += QString::number(*outVal) + QLatin1Char(')');
|
|
return true;
|
|
}
|
|
|
|
static bool getRawUint(const ushort *tokens, int limit, int &offset, uint *outVal, QString *outStr)
|
|
{
|
|
BOUNDS_CHECK(2);
|
|
uint val = tokens[offset++];
|
|
val |= (uint)tokens[offset++] << 16;
|
|
*outVal = val;
|
|
return true;
|
|
}
|
|
|
|
static bool getUint(const ushort *tokens, int limit, int &offset, uint *outVal, QString *outStr)
|
|
{
|
|
*outStr += fL1S(" << I(");
|
|
if (!getRawUint(tokens, limit, offset, outVal, outStr))
|
|
return false;
|
|
*outStr += QString::number(*outVal) + QLatin1Char(')');
|
|
return true;
|
|
}
|
|
|
|
static bool getRawStr(const ushort *tokens, int limit, int &offset, int strLen, QString *outStr)
|
|
{
|
|
BOUNDS_CHECK(strLen);
|
|
*outStr += fL1S("L\"");
|
|
bool attn = false;
|
|
for (int i = 0; i < strLen; i++) {
|
|
ushort val = tokens[offset++];
|
|
switch (val) {
|
|
case '"': *outStr += fL1S("\\\""); break;
|
|
case '\n': *outStr += fL1S("\\n"); break;
|
|
case '\r': *outStr += fL1S("\\r"); break;
|
|
case '\t': *outStr += fL1S("\\t"); break;
|
|
case '\\': *outStr += fL1S("\\\\"); break;
|
|
default:
|
|
if (val < 32 || val > 126) {
|
|
*outStr += (val > 255 ? fL1S("\\u") : fL1S("\\x")) + QString::number(val, 16);
|
|
attn = true;
|
|
continue;
|
|
}
|
|
if (attn && isxdigit(val))
|
|
*outStr += fL1S("\"\"");
|
|
*outStr += QChar(val);
|
|
break;
|
|
}
|
|
attn = false;
|
|
}
|
|
*outStr += QLatin1Char('"');
|
|
return true;
|
|
}
|
|
|
|
static bool getStr(const ushort *tokens, int limit, int &offset, QString *outStr)
|
|
{
|
|
*outStr += fL1S(" << S(");
|
|
ushort len;
|
|
if (!getRawUshort(tokens, limit, offset, &len, outStr))
|
|
return false;
|
|
if (!getRawStr(tokens, limit, offset, len, outStr))
|
|
return false;
|
|
*outStr += QLatin1Char(')');
|
|
return true;
|
|
}
|
|
|
|
static bool getHashStr(const ushort *tokens, int limit, int &offset, QString *outStr)
|
|
{
|
|
*outStr += fL1S(" << HS(");
|
|
uint hash;
|
|
if (!getRawUint(tokens, limit, offset, &hash, outStr))
|
|
return false;
|
|
ushort len;
|
|
if (!getRawUshort(tokens, limit, offset, &len, outStr))
|
|
return false;
|
|
const QChar *chars = (const QChar *)tokens + offset;
|
|
if (!getRawStr(tokens, limit, offset, len, outStr))
|
|
return false;
|
|
uint realhash = ProString::hash(chars, len);
|
|
if (realhash != hash)
|
|
*outStr += fL1S(" /* Bad hash ") + QString::number(hash) + fL1S(" */");
|
|
*outStr += QLatin1Char(')');
|
|
return true;
|
|
}
|
|
|
|
static bool getBlock(const ushort *tokens, int limit, int &offset, QString *outStr, int indent);
|
|
|
|
static bool getSubBlock(const ushort *tokens, int limit, int &offset, QString *outStr, int indent,
|
|
const char *scope)
|
|
{
|
|
*outStr += fL1S("\n /* %1 */ ").arg(offset, 5)
|
|
+ QString(indent * 4, QLatin1Char(' '))
|
|
+ fL1S("/* ") + fL1S(scope) + fL1S(" */");
|
|
uint len;
|
|
if (!getUint(tokens, limit, offset, &len, outStr))
|
|
return false;
|
|
if (len) {
|
|
BOUNDS_CHECK(len);
|
|
int tmpOff = offset;
|
|
offset += len;
|
|
forever {
|
|
if (!getBlock(tokens, offset, tmpOff, outStr, indent + 1))
|
|
break; // Error was already reported, try to continue
|
|
if (tmpOff == offset)
|
|
break;
|
|
*outStr += QLatin1Char('\n') + QString(20 + indent * 4, QLatin1Char(' '))
|
|
+ fL1S("/* Warning: Excess tokens follow. */");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool getBlock(const ushort *tokens, int limit, int &offset, QString *outStr, int indent)
|
|
{
|
|
static const char * const tokNames[] = {
|
|
"TokTerminator",
|
|
"TokLine",
|
|
"TokAssign", "TokAppend", "TokAppendUnique", "TokRemove", "TokReplace",
|
|
"TokValueTerminator",
|
|
"TokLiteral", "TokHashLiteral", "TokVariable", "TokProperty", "TokEnvVar",
|
|
"TokFuncName", "TokArgSeparator", "TokFuncTerminator",
|
|
"TokCondition", "TokTestCall",
|
|
"TokReturn", "TokBreak", "TokNext",
|
|
"TokNot", "TokAnd", "TokOr",
|
|
"TokBranch", "TokForLoop",
|
|
"TokTestDef", "TokReplaceDef", "TokBypassNesting"
|
|
};
|
|
|
|
while (offset != limit) {
|
|
*outStr += fL1S("\n /* %1 */").arg(offset, 5)
|
|
+ QString(indent * 4, QLatin1Char(' '));
|
|
BOUNDS_CHECK(1);
|
|
ushort tok = tokens[offset++];
|
|
ushort maskedTok = tok & TokMask;
|
|
if (maskedTok >= sizeof(tokNames)/sizeof(tokNames[0])
|
|
|| (tok & ~(TokNewStr | TokQuoted | TokMask))) {
|
|
*outStr += fL1S(" << {invalid token %1}").arg(tok);
|
|
return false;
|
|
}
|
|
*outStr += fL1S(" << H(") + fL1S(tokNames[maskedTok]);
|
|
if (tok & TokNewStr)
|
|
*outStr += fL1S(" | TokNewStr");
|
|
if (tok & TokQuoted)
|
|
*outStr += fL1S(" | TokQuoted");
|
|
*outStr += QLatin1Char(')');
|
|
bool ok;
|
|
switch (maskedTok) {
|
|
case TokFuncTerminator: // Recursion, but not a sub-block
|
|
return true;
|
|
case TokArgSeparator:
|
|
case TokValueTerminator: // Not recursion
|
|
case TokTerminator: // Recursion, and limited by (sub-)block length
|
|
case TokCondition:
|
|
case TokReturn:
|
|
case TokBreak:
|
|
case TokNext:
|
|
case TokNot:
|
|
case TokAnd:
|
|
case TokOr:
|
|
ok = true;
|
|
break;
|
|
case TokTestCall:
|
|
ok = getBlock(tokens, limit, offset, outStr, indent + 1);
|
|
break;
|
|
case TokBranch:
|
|
ok = getSubBlock(tokens, limit, offset, outStr, indent, "then branch");
|
|
if (ok)
|
|
ok = getSubBlock(tokens, limit, offset, outStr, indent, "else branch");
|
|
break;
|
|
default:
|
|
switch (maskedTok) {
|
|
case TokAssign:
|
|
case TokAppend:
|
|
case TokAppendUnique:
|
|
case TokRemove:
|
|
case TokReplace:
|
|
// The parameter is the sizehint for the output.
|
|
// fallthrough
|
|
case TokLine: {
|
|
ushort dummy;
|
|
ok = getUshort(tokens, limit, offset, &dummy, outStr);
|
|
break; }
|
|
case TokLiteral:
|
|
case TokEnvVar:
|
|
ok = getStr(tokens, limit, offset, outStr);
|
|
break;
|
|
case TokHashLiteral:
|
|
case TokVariable:
|
|
case TokProperty:
|
|
ok = getHashStr(tokens, limit, offset, outStr);
|
|
break;
|
|
case TokFuncName:
|
|
ok = getHashStr(tokens, limit, offset, outStr);
|
|
if (ok)
|
|
ok = getBlock(tokens, limit, offset, outStr, indent + 1);
|
|
break;
|
|
case TokForLoop:
|
|
ok = getHashStr(tokens, limit, offset, outStr);
|
|
if (ok)
|
|
ok = getSubBlock(tokens, limit, offset, outStr, indent, "iterator");
|
|
if (ok)
|
|
ok = getSubBlock(tokens, limit, offset, outStr, indent, "body");
|
|
break;
|
|
case TokTestDef:
|
|
case TokReplaceDef:
|
|
ok = getHashStr(tokens, limit, offset, outStr);
|
|
if (ok)
|
|
ok = getSubBlock(tokens, limit, offset, outStr, indent, "body");
|
|
break;
|
|
case TokBypassNesting:
|
|
ok = getSubBlock(tokens, limit, offset, outStr, indent, "block");
|
|
break;
|
|
default:
|
|
Q_ASSERT(!"unhandled token");
|
|
}
|
|
}
|
|
if (!ok)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QString QMakeParser::formatProBlock(const QString &block)
|
|
{
|
|
QString outStr;
|
|
outStr += fL1S("\n << TS(");
|
|
int offset = 0;
|
|
getBlock(reinterpret_cast<const ushort *>(block.constData()), block.length(),
|
|
offset, &outStr, 0);
|
|
outStr += QLatin1Char(')');
|
|
return outStr;
|
|
}
|
|
|
|
#endif // PROPARSER_DEBUG
|
|
|
|
QT_END_NAMESPACE
|