blob: bb411d6b441b4c35a7a531220a43ebffe56c65f5 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "config.h"
#include "tokenizer.h"
#include <qfile.h>
#include <qhash.h>
#include <qregexp.h>
#include <qstring.h>
#include <qtextcodec.h>
#include <ctype.h>
#include <string.h>
QT_BEGIN_NAMESPACE
#define LANGUAGE_CPP "Cpp"
/* qmake ignore Q_OBJECT */
/*
Keep in sync with tokenizer.h.
*/
static const char *kwords[] = {
"char", "class", "const", "double", "enum", "explicit",
"friend", "inline", "int", "long", "namespace", "operator",
"private", "protected", "public", "short", "signals", "signed",
"slots", "static", "struct", "template", "typedef", "typename",
"union", "unsigned", "using", "virtual", "void", "volatile",
"__int64",
"Q_OBJECT",
"Q_OVERRIDE",
"Q_PROPERTY",
"Q_PRIVATE_PROPERTY",
"Q_DECLARE_SEQUENTIAL_ITERATOR",
"Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR",
"Q_DECLARE_ASSOCIATIVE_ITERATOR",
"Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR",
"Q_DECLARE_FLAGS",
"Q_SIGNALS",
"Q_SLOTS",
"QT_COMPAT",
"QT_COMPAT_CONSTRUCTOR",
"QT_DEPRECATED",
"QT_MOC_COMPAT",
"QT_MODULE",
"QT3_SUPPORT",
"QT3_SUPPORT_CONSTRUCTOR",
"QT3_MOC_SUPPORT",
"QDOC_PROPERTY"
};
static const int KwordHashTableSize = 4096;
static int kwordHashTable[KwordHashTableSize];
static QHash<QByteArray, bool> *ignoredTokensAndDirectives = 0;
static QRegExp *comment = 0;
static QRegExp *versionX = 0;
static QRegExp *definedX = 0;
static QRegExp *defines = 0;
static QRegExp *falsehoods = 0;
static QTextCodec *sourceCodec = 0;
/*
This function is a perfect hash function for the 37 keywords of C99
(with a hash table size of 512). It should perform well on our
Qt-enhanced C++ subset.
*/
static int hashKword(const char *s, int len)
{
return (((uchar) s[0]) + (((uchar) s[2]) << 5) +
(((uchar) s[len - 1]) << 3)) % KwordHashTableSize;
}
static void insertKwordIntoHash(const char *s, int number)
{
int k = hashKword(s, strlen(s));
while (kwordHashTable[k]) {
if (++k == KwordHashTableSize)
k = 0;
}
kwordHashTable[k] = number;
}
Tokenizer::Tokenizer(const Location& loc, QFile &in)
{
init();
yyIn = in.readAll();
yyPos = 0;
start(loc);
}
Tokenizer::Tokenizer(const Location& loc, const QByteArray &in)
: yyIn(in)
{
init();
yyPos = 0;
start(loc);
}
Tokenizer::~Tokenizer()
{
delete[] yyLexBuf1;
delete[] yyLexBuf2;
}
int Tokenizer::getToken()
{
char *t = yyPrevLex;
yyPrevLex = yyLex;
yyLex = t;
while (yyCh != EOF) {
yyTokLoc = yyCurLoc;
yyLexLen = 0;
if (isspace(yyCh)) {
do {
yyCh = getChar();
} while (isspace(yyCh));
}
else if (isalpha(yyCh) || yyCh == '_') {
do {
yyCh = getChar();
} while (isalnum(yyCh) || yyCh == '_');
int k = hashKword(yyLex, yyLexLen);
for (;;) {
int i = kwordHashTable[k];
if (i == 0) {
return Tok_Ident;
}
else if (i == -1) {
if (!parsingMacro && ignoredTokensAndDirectives->contains(yyLex)) {
if (ignoredTokensAndDirectives->value(yyLex)) { // it's a directive
int parenDepth = 0;
while (yyCh != EOF && (yyCh != ')' || parenDepth > 1)) {
if (yyCh == '(')
++parenDepth;
else if (yyCh == ')')
--parenDepth;
yyCh = getChar();
}
if (yyCh == ')')
yyCh = getChar();
}
break;
}
}
else if (strcmp(yyLex, kwords[i - 1]) == 0) {
int ret = (int) Tok_FirstKeyword + i - 1;
if (ret != Tok_explicit && ret != Tok_inline && ret != Tok_typename)
return ret;
break;
}
if (++k == KwordHashTableSize)
k = 0;
}
}
else if (isdigit(yyCh)) {
do {
yyCh = getChar();
} while (isalnum(yyCh) || yyCh == '.' || yyCh == '+' ||
yyCh == '-');
return Tok_Number;
}
else {
switch (yyCh) {
case '!':
case '%':
yyCh = getChar();
if (yyCh == '=')
yyCh = getChar();
return Tok_SomeOperator;
case '"':
yyCh = getChar();
while (yyCh != EOF && yyCh != '"') {
if (yyCh == '\\')
yyCh = getChar();
yyCh = getChar();
}
yyCh = getChar();
if (yyCh == EOF)
yyTokLoc.warning(tr("Unterminated C++ string literal"),
tr("Maybe you forgot '/*!' at the beginning of the file?"));
else
return Tok_String;
break;
case '#':
return getTokenAfterPreprocessor();
case '&':
yyCh = getChar();
if (yyCh == '&' || yyCh == '=') {
yyCh = getChar();
return Tok_SomeOperator;
}
else {
return Tok_Ampersand;
}
case '\'':
yyCh = getChar();
if (yyCh == '\\')
yyCh = getChar();
do {
yyCh = getChar();
} while (yyCh != EOF && yyCh != '\'');
if (yyCh == EOF) {
yyTokLoc.warning(tr("Unterminated C++ character"
" literal"));
}
else {
yyCh = getChar();
return Tok_Number;
}
break;
case '(':
yyCh = getChar();
if (yyNumPreprocessorSkipping == 0)
yyParenDepth++;
if (isspace(yyCh)) {
do {
yyCh = getChar();
} while (isspace(yyCh));
yyLexLen = 1;
yyLex[1] = '\0';
}
if (yyCh == '*') {
yyCh = getChar();
return Tok_LeftParenAster;
}
return Tok_LeftParen;
case ')':
yyCh = getChar();
if (yyNumPreprocessorSkipping == 0)
yyParenDepth--;
return Tok_RightParen;
case '*':
yyCh = getChar();
if (yyCh == '=') {
yyCh = getChar();
return Tok_SomeOperator;
} else {
return Tok_Aster;
}
case '^':
yyCh = getChar();
if (yyCh == '=') {
yyCh = getChar();
return Tok_SomeOperator;
} else {
return Tok_Caret;
}
case '+':
yyCh = getChar();
if (yyCh == '+' || yyCh == '=')
yyCh = getChar();
return Tok_SomeOperator;
case ',':
yyCh = getChar();
return Tok_Comma;
case '-':
yyCh = getChar();
if (yyCh == '-' || yyCh == '=') {
yyCh = getChar();
} else if (yyCh == '>') {
yyCh = getChar();
if (yyCh == '*')
yyCh = getChar();
}
return Tok_SomeOperator;
case '.':
yyCh = getChar();
if (yyCh == '*') {
yyCh = getChar();
} else if (yyCh == '.') {
do {
yyCh = getChar();
} while (yyCh == '.');
return Tok_Ellipsis;
} else if (isdigit(yyCh)) {
do {
yyCh = getChar();
} while (isalnum(yyCh) || yyCh == '.' || yyCh == '+' ||
yyCh == '-');
return Tok_Number;
}
return Tok_SomeOperator;
case '/':
yyCh = getChar();
if (yyCh == '/') {
do {
yyCh = getChar();
} while (yyCh != EOF && yyCh != '\n');
} else if (yyCh == '*') {
bool metDoc = false; // empty doc is no doc
bool metSlashAsterBang = false;
bool metAster = false;
bool metAsterSlash = false;
yyCh = getChar();
if (yyCh == '!')
metSlashAsterBang = true;
while (!metAsterSlash) {
if (yyCh == EOF) {
yyTokLoc.warning(tr("Unterminated C++ comment"));
break;
} else {
if (yyCh == '*') {
metAster = true;
} else if (metAster && yyCh == '/') {
metAsterSlash = true;
} else {
metAster = false;
if (isgraph(yyCh))
metDoc = true;
}
}
yyCh = getChar();
}
if (metSlashAsterBang && metDoc)
return Tok_Doc;
else if (yyParenDepth > 0)
return Tok_Comment;
} else {
if (yyCh == '=')
yyCh = getChar();
return Tok_SomeOperator;
}
break;
case ':':
yyCh = getChar();
if (yyCh == ':') {
yyCh = getChar();
return Tok_Gulbrandsen;
} else {
return Tok_Colon;
}
case ';':
yyCh = getChar();
return Tok_Semicolon;
case '<':
yyCh = getChar();
if (yyCh == '<') {
yyCh = getChar();
if (yyCh == '=')
yyCh = getChar();
return Tok_SomeOperator;
} else if (yyCh == '=') {
yyCh = getChar();
return Tok_SomeOperator;
} else {
return Tok_LeftAngle;
}
case '=':
yyCh = getChar();
if (yyCh == '=') {
yyCh = getChar();
return Tok_SomeOperator;
} else {
return Tok_Equal;
}
case '>':
yyCh = getChar();
if (yyCh == '>') {
yyCh = getChar();
if (yyCh == '=')
yyCh = getChar();
return Tok_SomeOperator;
} else if (yyCh == '=') {
yyCh = getChar();
return Tok_SomeOperator;
} else {
return Tok_RightAngle;
}
case '?':
yyCh = getChar();
return Tok_SomeOperator;
case '[':
yyCh = getChar();
if (yyNumPreprocessorSkipping == 0)
yyBracketDepth++;
return Tok_LeftBracket;
case '\\':
yyCh = getChar();
yyCh = getChar(); // skip one character
break;
case ']':
yyCh = getChar();
if (yyNumPreprocessorSkipping == 0)
yyBracketDepth--;
return Tok_RightBracket;
case '{':
yyCh = getChar();
if (yyNumPreprocessorSkipping == 0)
yyBraceDepth++;
return Tok_LeftBrace;
case '}':
yyCh = getChar();
if (yyNumPreprocessorSkipping == 0)
yyBraceDepth--;
return Tok_RightBrace;
case '|':
yyCh = getChar();
if (yyCh == '|' || yyCh == '=')
yyCh = getChar();
return Tok_SomeOperator;
case '~':
yyCh = getChar();
return Tok_Tilde;
case '@':
yyCh = getChar();
return Tok_At;
default:
// ### We should really prevent qdoc from looking at snippet files rather than
// ### suppress warnings when reading them.
if (yyNumPreprocessorSkipping == 0 && !yyTokLoc.fileName().endsWith(".qdoc")) {
yyTokLoc.warning(tr("Hostile character 0x%1 in C++ source")
.arg((uchar)yyCh, 1, 16));
}
yyCh = getChar();
}
}
}
if (yyPreprocessorSkipping.count() > 1) {
yyTokLoc.warning(tr("Expected #endif before end of file"));
// clear it out or we get an infinite loop!
while (!yyPreprocessorSkipping.isEmpty()) {
popSkipping();
}
}
strcpy(yyLex, "end-of-input");
yyLexLen = strlen(yyLex);
return Tok_Eoi;
}
void Tokenizer::initialize(const Config &config)
{
QString versionSym = config.getString(CONFIG_VERSIONSYM);
QString sourceEncoding = config.getString(CONFIG_SOURCEENCODING);
if (sourceEncoding.isEmpty())
sourceEncoding = QLatin1String("ISO-8859-1");
sourceCodec = QTextCodec::codecForName(sourceEncoding.toLocal8Bit());
comment = new QRegExp("/(?:\\*.*\\*/|/.*\n|/[^\n]*$)");
comment->setMinimal(true);
versionX = new QRegExp("$cannot possibly match^");
if (!versionSym.isEmpty())
versionX->setPattern("[ \t]*(?:" + QRegExp::escape(versionSym)
+ ")[ \t]+\"([^\"]*)\"[ \t]*");
definedX = new QRegExp("defined ?\\(?([A-Z_0-9a-z]+) ?\\)");
QStringList d = config.getStringList(CONFIG_DEFINES);
d += "qdoc";
defines = new QRegExp(d.join("|"));
falsehoods = new QRegExp(config.getStringList(CONFIG_FALSEHOODS).join("|"));
memset(kwordHashTable, 0, sizeof(kwordHashTable));
for (int i = 0; i < Tok_LastKeyword - Tok_FirstKeyword + 1; i++)
insertKwordIntoHash(kwords[i], i + 1);
ignoredTokensAndDirectives = new QHash<QByteArray, bool>;
QStringList tokens = config.getStringList(LANGUAGE_CPP + Config::dot + CONFIG_IGNORETOKENS);
foreach (const QString &t, tokens) {
const QByteArray tb = t.toAscii();
ignoredTokensAndDirectives->insert(tb, false);
insertKwordIntoHash(tb.data(), -1);
}
QStringList directives = config.getStringList(LANGUAGE_CPP + Config::dot
+ CONFIG_IGNOREDIRECTIVES);
foreach (const QString &d, directives) {
const QByteArray db = d.toAscii();
ignoredTokensAndDirectives->insert(db, true);
insertKwordIntoHash(db.data(), -1);
}
}
void Tokenizer::terminate()
{
delete comment;
comment = 0;
delete versionX;
versionX = 0;
delete definedX;
definedX = 0;
delete defines;
defines = 0;
delete falsehoods;
falsehoods = 0;
delete ignoredTokensAndDirectives;
ignoredTokensAndDirectives = 0;
}
void Tokenizer::init()
{
yyLexBuf1 = new char[(int) yyLexBufSize];
yyLexBuf2 = new char[(int) yyLexBufSize];
yyPrevLex = yyLexBuf1;
yyPrevLex[0] = '\0';
yyLex = yyLexBuf2;
yyLex[0] = '\0';
yyLexLen = 0;
yyPreprocessorSkipping.push(false);
yyNumPreprocessorSkipping = 0;
yyBraceDepth = 0;
yyParenDepth = 0;
yyBracketDepth = 0;
yyCh = '\0';
parsingMacro = false;
}
void Tokenizer::start(const Location& loc)
{
yyTokLoc = loc;
yyCurLoc = loc;
yyCurLoc.start();
strcpy(yyPrevLex, "beginning-of-input");
strcpy(yyLex, "beginning-of-input");
yyLexLen = strlen(yyLex);
yyBraceDepth = 0;
yyParenDepth = 0;
yyBracketDepth = 0;
yyCh = '\0';
yyCh = getChar();
}
/*
Returns the next token, if # was met. This function interprets the
preprocessor directive, skips over any #ifdef'd out tokens, and returns the
token after all of that.
*/
int Tokenizer::getTokenAfterPreprocessor()
{
yyCh = getChar();
while (isspace(yyCh) && yyCh != '\n')
yyCh = getChar();
/*
#directive condition
*/
QString directive;
QString condition;
while (isalpha(yyCh)) {
directive += QChar(yyCh);
yyCh = getChar();
}
if (!directive.isEmpty()) {
while (yyCh != EOF && yyCh != '\n') {
if (yyCh == '\\')
yyCh = getChar();
condition += yyCh;
yyCh = getChar();
}
condition.replace(*comment, "");
condition = condition.simplified();
/*
The #if, #ifdef, #ifndef, #elif, #else, and #endif
directives have an effect on the skipping stack. For
instance, if the code processed so far is
#if 1
#if 0
#if 1
// ...
#else
the skipping stack contains, from bottom to top, false true
true (assuming 0 is false and 1 is true). If at least one
entry of the stack is true, the tokens are skipped.
This mechanism is simple yet hard to understand.
*/
if (directive[0] == QChar('i')) {
if (directive == QString("if"))
pushSkipping(!isTrue(condition));
else if (directive == QString("ifdef"))
pushSkipping(!defines->exactMatch(condition));
else if (directive == QString("ifndef"))
pushSkipping(defines->exactMatch(condition));
} else if (directive[0] == QChar('e')) {
if (directive == QString("elif")) {
bool old = popSkipping();
if (old)
pushSkipping(!isTrue(condition));
else
pushSkipping(true);
} else if (directive == QString("else")) {
pushSkipping(!popSkipping());
} else if (directive == QString("endif")) {
popSkipping();
}
} else if (directive == QString("define")) {
if (versionX->exactMatch(condition))
yyVersion = versionX->cap(1);
}
}
int tok;
do {
/*
We set yyLex now, and after getToken() this will be
yyPrevLex. This way, we skip over the preprocessor
directive.
*/
qstrcpy(yyLex, yyPrevLex);
/*
If getToken() meets another #, it will call
getTokenAfterPreprocessor() once again, which could in turn
call getToken() again, etc. Unless there are 10,000 or so
preprocessor directives in a row, this shouldn't overflow
the stack.
*/
tok = getToken();
} while (yyNumPreprocessorSkipping > 0);
return tok;
}
/*
Pushes a new skipping value onto the stack. This corresponds to entering a
new #if block.
*/
void Tokenizer::pushSkipping(bool skip)
{
yyPreprocessorSkipping.push(skip);
if (skip)
yyNumPreprocessorSkipping++;
}
/*
Pops a skipping value from the stack. This corresponds to reaching a #endif.
*/
bool Tokenizer::popSkipping()
{
if (yyPreprocessorSkipping.isEmpty()) {
yyTokLoc.warning(tr("Unexpected #elif, #else or #endif"));
return true;
}
bool skip = yyPreprocessorSkipping.pop();
if (skip)
yyNumPreprocessorSkipping--;
return skip;
}
/*
Returns true if the condition evaluates as true, otherwise false. The
condition is represented by a string. Unsophisticated parsing techniques are
used. The preprocessing method could be named StriNg-Oriented PreProcessing,
as SNOBOL stands for StriNg-Oriented symBOlic Language.
*/
bool Tokenizer::isTrue(const QString &condition)
{
int firstOr = -1;
int firstAnd = -1;
int parenDepth = 0;
/*
Find the first logical operator at top level, but be careful
about precedence. Examples:
X || Y // the or
X || Y || Z // the leftmost or
X || Y && Z // the or
X && Y || Z // the or
(X || Y) && Z // the and
*/
for (int i = 0; i < (int) condition.length() - 1; i++) {
QChar ch = condition[i];
if (ch == QChar('(')) {
parenDepth++;
} else if (ch == QChar(')')) {
parenDepth--;
} else if (parenDepth == 0) {
if (condition[i + 1] == ch) {
if (ch == QChar('|')) {
firstOr = i;
break;
} else if (ch == QChar('&')) {
if (firstAnd == -1)
firstAnd = i;
}
}
}
}
if (firstOr != -1)
return isTrue(condition.left(firstOr)) ||
isTrue(condition.mid(firstOr + 2));
if (firstAnd != -1)
return isTrue(condition.left(firstAnd)) &&
isTrue(condition.mid(firstAnd + 2));
QString t = condition.simplified();
if (t.isEmpty())
return true;
if (t[0] == QChar('!'))
return !isTrue(t.mid(1));
if (t[0] == QChar('(') && t.right(1)[0] == QChar(')'))
return isTrue(t.mid(1, t.length() - 2));
if (definedX->exactMatch(t))
return defines->exactMatch(definedX->cap(1));
else
return !falsehoods->exactMatch(t);
}
QString Tokenizer::lexeme() const
{
return sourceCodec->toUnicode(yyLex);
}
QString Tokenizer::previousLexeme() const
{
return sourceCodec->toUnicode(yyPrevLex);
}
QT_END_NAMESPACE