--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/linguist/lupdate/cpp.cpp Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,2143 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Linguist of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "lupdate.h"
+
+#include <translator.h>
+
+#include <QtCore/QBitArray>
+#include <QtCore/QDebug>
+#include <QtCore/QFileInfo>
+#include <QtCore/QStack>
+#include <QtCore/QString>
+#include <QtCore/QTextCodec>
+#include <QtCore/QTextStream>
+
+#include <ctype.h> // for isXXX()
+
+QT_BEGIN_NAMESPACE
+
+/* qmake ignore Q_OBJECT */
+
+static QString MagicComment(QLatin1String("TRANSLATOR"));
+
+#define STRING(s) static QString str##s(QLatin1String(#s))
+
+//#define DIAGNOSE_RETRANSLATABILITY // FIXME: should make a runtime option of this
+
+class HashString {
+public:
+ HashString() : m_hashed(false) {}
+ explicit HashString(const QString &str) : m_str(str), m_hashed(false) {}
+ void setValue(const QString &str) { m_str = str; m_hashed = false; }
+ const QString &value() const { return m_str; }
+ bool operator==(const HashString &other) const { return m_str == other.m_str; }
+private:
+ QString m_str;
+ mutable uint m_hash;
+ mutable bool m_hashed;
+ friend uint qHash(const HashString &str);
+};
+
+uint qHash(const HashString &str)
+{
+ if (!str.m_hashed) {
+ str.m_hashed = true;
+ str.m_hash = qHash(str.m_str);
+ }
+ return str.m_hash;
+}
+
+class HashStringList {
+public:
+ explicit HashStringList(const QList<HashString> &list) : m_list(list), m_hashed(false) {}
+ const QList<HashString> &value() const { return m_list; }
+ bool operator==(const HashStringList &other) const { return m_list == other.m_list; }
+private:
+ QList<HashString> m_list;
+ mutable uint m_hash;
+ mutable bool m_hashed;
+ friend uint qHash(const HashStringList &list);
+};
+
+uint qHash(const HashStringList &list)
+{
+ if (!list.m_hashed) {
+ list.m_hashed = true;
+ uint hash = 0;
+ foreach (const HashString &qs, list.m_list) {
+ hash ^= qHash(qs) ^ 0xa09df22f;
+ hash = (hash << 13) | (hash >> 19);
+ }
+ list.m_hash = hash;
+ }
+ return list.m_hash;
+}
+
+typedef QList<HashString> NamespaceList;
+
+struct Namespace {
+
+ Namespace() :
+ classDef(this),
+ hasTrFunctions(false), complained(false)
+ {}
+ ~Namespace()
+ {
+ qDeleteAll(children);
+ }
+
+ QHash<HashString, Namespace *> children;
+ QHash<HashString, NamespaceList> aliases;
+ QList<HashStringList> usings;
+
+ // Class declarations set no flags and create no namespaces, so they are ignored.
+ // Class definitions may appear multiple times - but only because we are trying to
+ // "compile" all sources irrespective of build configuration.
+ // Nested classes may be forward-declared inside a definition, and defined in another file.
+ // The latter will detach the class' child list, so clones need a backlink to the original
+ // definition (either one in case of multiple definitions).
+ Namespace *classDef;
+
+ QString trQualification;
+
+ bool hasTrFunctions;
+ bool complained; // ... that tr functions are missing.
+};
+
+static int nextFileId;
+
+class VisitRecorder {
+public:
+ VisitRecorder()
+ {
+ m_ba.resize(nextFileId);
+ }
+ bool tryVisit(int fileId)
+ {
+ if (m_ba.at(fileId))
+ return false;
+ m_ba[fileId] = true;
+ return true;
+ }
+private:
+ QBitArray m_ba;
+};
+
+struct ParseResults {
+ int fileId;
+ Namespace rootNamespace;
+ QSet<const ParseResults *> includes;
+};
+
+typedef QHash<QString, const ParseResults *> ParseResultHash;
+typedef QHash<QString, const Translator *> TranslatorHash;
+
+class CppFiles {
+
+public:
+ static const ParseResults *getResults(const QString &cleanFile);
+ static void setResults(const QString &cleanFile, const ParseResults *results);
+ static const Translator *getTranslator(const QString &cleanFile);
+ static void setTranslator(const QString &cleanFile, const Translator *results);
+ static bool isBlacklisted(const QString &cleanFile);
+ static void setBlacklisted(const QString &cleanFile);
+
+private:
+ static ParseResultHash &parsedFiles();
+ static TranslatorHash &translatedFiles();
+ static QSet<QString> &blacklistedFiles();
+};
+
+class CppParser {
+
+public:
+ CppParser(ParseResults *results = 0);
+ void setInput(const QString &in);
+ void setInput(QTextStream &ts, const QString &fileName);
+ void setTranslator(Translator *_tor) { tor = _tor; }
+ void parse(const QString &initialContext, ConversionData &cd, QSet<QString> &inclusions);
+ void parseInternal(ConversionData &cd, QSet<QString> &inclusions);
+ const ParseResults *recordResults(bool isHeader);
+ void deleteResults() { delete results; }
+
+ struct SavedState {
+ NamespaceList namespaces;
+ QStack<int> namespaceDepths;
+ NamespaceList functionContext;
+ QString functionContextUnresolved;
+ QString pendingContext;
+ };
+
+private:
+ struct IfdefState {
+ IfdefState() {}
+ IfdefState(int _braceDepth, int _parenDepth) :
+ braceDepth(_braceDepth),
+ parenDepth(_parenDepth),
+ elseLine(-1)
+ {}
+
+ SavedState state;
+ int braceDepth, braceDepth1st;
+ int parenDepth, parenDepth1st;
+ int elseLine;
+ };
+
+ uint getChar();
+ uint getToken();
+ bool getMacroArgs();
+ bool match(uint t);
+ bool matchString(QString *s);
+ bool matchEncoding(bool *utf8);
+ bool matchStringOrNull(QString *s);
+ bool matchExpression();
+
+ QString transcode(const QString &str, bool utf8);
+ void recordMessage(
+ int line, const QString &context, const QString &text, const QString &comment,
+ const QString &extracomment, const QString &msgid, const TranslatorMessage::ExtraData &extra,
+ bool utf8, bool plural);
+
+ void processInclude(const QString &file, ConversionData &cd,
+ QSet<QString> &inclusions);
+
+ void saveState(SavedState *state);
+ void loadState(const SavedState *state);
+
+ static QString stringifyNamespace(const NamespaceList &namespaces);
+ static QStringList stringListifyNamespace(const NamespaceList &namespaces);
+ typedef bool (CppParser::*VisitNamespaceCallback)(const Namespace *ns, void *context) const;
+ bool visitNamespace(const NamespaceList &namespaces, int nsCount,
+ VisitNamespaceCallback callback, void *context,
+ VisitRecorder &vr, const ParseResults *rslt) const;
+ bool visitNamespace(const NamespaceList &namespaces, int nsCount,
+ VisitNamespaceCallback callback, void *context) const;
+ static QStringList stringListifySegments(const QList<HashString> &namespaces);
+ bool qualifyOneCallbackOwn(const Namespace *ns, void *context) const;
+ bool qualifyOneCallbackUsing(const Namespace *ns, void *context) const;
+ bool qualifyOne(const NamespaceList &namespaces, int nsCnt, const HashString &segment,
+ NamespaceList *resolved) const;
+ bool fullyQualify(const NamespaceList &namespaces, const QList<HashString> &segments,
+ bool isDeclaration,
+ NamespaceList *resolved, QStringList *unresolved) const;
+ bool fullyQualify(const NamespaceList &namespaces, const QString &segments,
+ bool isDeclaration,
+ NamespaceList *resolved, QStringList *unresolved) const;
+ bool findNamespaceCallback(const Namespace *ns, void *context) const;
+ const Namespace *findNamespace(const NamespaceList &namespaces, int nsCount = -1) const;
+ void enterNamespace(NamespaceList *namespaces, const HashString &name);
+ void truncateNamespaces(NamespaceList *namespaces, int lenght);
+ Namespace *modifyNamespace(NamespaceList *namespaces, bool tryOrigin = true);
+
+ enum {
+ Tok_Eof, Tok_class, Tok_friend, Tok_namespace, Tok_using, Tok_return,
+ Tok_tr = 10, Tok_trUtf8, Tok_translate, Tok_translateUtf8, Tok_trid,
+ Tok_Q_OBJECT = 20, Tok_Q_DECLARE_TR_FUNCTIONS,
+ Tok_Ident, Tok_Comment, Tok_String, Tok_Arrow, Tok_Colon, Tok_ColonColon,
+ Tok_Equals,
+ Tok_LeftBrace = 30, Tok_RightBrace, Tok_LeftParen, Tok_RightParen, Tok_Comma, Tok_Semicolon,
+ Tok_Null = 40, Tok_Integer,
+ Tok_QuotedInclude = 50, Tok_AngledInclude,
+ Tok_Other = 99
+ };
+
+ // Tokenizer state
+ QString yyFileName;
+ int yyCh;
+ bool yyAtNewline;
+ bool yyCodecIsUtf8;
+ bool yyForceUtf8;
+ QString yyWord;
+ qlonglong yyInteger;
+ QStack<IfdefState> yyIfdefStack;
+ int yyBraceDepth;
+ int yyParenDepth;
+ int yyLineNo;
+ int yyCurLineNo;
+ int yyBraceLineNo;
+ int yyParenLineNo;
+
+ // the string to read from and current position in the string
+ QTextCodec *yySourceCodec;
+ bool yySourceIsUnicode;
+ QString yyInStr;
+ const ushort *yyInPtr;
+
+ // Parser state
+ uint yyTok;
+
+ NamespaceList namespaces;
+ QStack<int> namespaceDepths;
+ NamespaceList functionContext;
+ QString functionContextUnresolved;
+ QString prospectiveContext;
+ QString pendingContext;
+ ParseResults *results;
+ Translator *tor;
+ bool directInclude;
+
+ SavedState savedState;
+ int yyMinBraceDepth;
+ bool inDefine;
+};
+
+CppParser::CppParser(ParseResults *_results)
+{
+ tor = 0;
+ if (_results) {
+ results = _results;
+ directInclude = true;
+ } else {
+ results = new ParseResults;
+ directInclude = false;
+ }
+ yyBraceDepth = 0;
+ yyParenDepth = 0;
+ yyCurLineNo = 1;
+ yyBraceLineNo = 1;
+ yyParenLineNo = 1;
+ yyAtNewline = true;
+ yyMinBraceDepth = 0;
+ inDefine = false;
+}
+
+void CppParser::setInput(const QString &in)
+{
+ yyInStr = in;
+ yyFileName = QString();
+ yySourceCodec = 0;
+ yySourceIsUnicode = true;
+ yyForceUtf8 = true;
+}
+
+void CppParser::setInput(QTextStream &ts, const QString &fileName)
+{
+ yyInStr = ts.readAll();
+ yyFileName = fileName;
+ yySourceCodec = ts.codec();
+ yySourceIsUnicode = yySourceCodec->name().startsWith("UTF-");
+ yyForceUtf8 = false;
+}
+
+/*
+ The first part of this source file is the C++ tokenizer. We skip
+ most of C++; the only tokens that interest us are defined here.
+ Thus, the code fragment
+
+ int main()
+ {
+ printf("Hello, world!\n");
+ return 0;
+ }
+
+ is broken down into the following tokens (Tok_ omitted):
+
+ Ident Ident LeftParen RightParen
+ LeftBrace
+ Ident LeftParen String RightParen Semicolon
+ return Semicolon
+ RightBrace.
+
+ The 0 doesn't produce any token.
+*/
+
+uint CppParser::getChar()
+{
+ const ushort *uc = yyInPtr;
+ forever {
+ ushort c = *uc;
+ if (!c) {
+ yyInPtr = uc;
+ return EOF;
+ }
+ ++uc;
+ if (c == '\\') {
+ ushort cc = *uc;
+ if (cc == '\n') {
+ ++yyCurLineNo;
+ ++uc;
+ continue;
+ }
+ if (cc == '\r') {
+ ++yyCurLineNo;
+ ++uc;
+ if (*uc == '\n')
+ ++uc;
+ continue;
+ }
+ }
+ if (c == '\r') {
+ if (*uc == '\n')
+ ++uc;
+ c = '\n';
+ ++yyCurLineNo;
+ yyAtNewline = true;
+ } else if (c == '\n') {
+ ++yyCurLineNo;
+ yyAtNewline = true;
+ } else if (c != ' ' && c != '\t' && c != '#') {
+ yyAtNewline = false;
+ }
+ yyInPtr = uc;
+ return c;
+ }
+}
+
+// This ignores commas, parens and comments.
+// IOW, it understands only a single, simple argument.
+bool CppParser::getMacroArgs()
+{
+ // Failing this assertion would mean losing the preallocated buffer.
+ Q_ASSERT(yyWord.isDetached());
+ yyWord.resize(0);
+
+ while (isspace(yyCh))
+ yyCh = getChar();
+ if (yyCh != '(')
+ return false;
+ do {
+ yyCh = getChar();
+ } while (isspace(yyCh));
+ ushort *ptr = (ushort *)yyWord.unicode();
+ while (yyCh != ')') {
+ if (yyCh == EOF)
+ return false;
+ *ptr++ = yyCh;
+ yyCh = getChar();
+ }
+ yyCh = getChar();
+ for (; ptr != (ushort *)yyWord.unicode() && isspace(*(ptr - 1)); --ptr) ;
+ yyWord.resize(ptr - (ushort *)yyWord.unicode());
+ return true;
+}
+
+STRING(Q_OBJECT);
+STRING(Q_DECLARE_TR_FUNCTIONS);
+STRING(QT_TR_NOOP);
+STRING(QT_TRID_NOOP);
+STRING(QT_TRANSLATE_NOOP);
+STRING(QT_TRANSLATE_NOOP3);
+STRING(QT_TR_NOOP_UTF8);
+STRING(QT_TRANSLATE_NOOP_UTF8);
+STRING(QT_TRANSLATE_NOOP3_UTF8);
+STRING(class);
+// QTranslator::findMessage() has the same parameters as QApplication::translate()
+STRING(findMessage);
+STRING(friend);
+STRING(namespace);
+STRING(qtTrId);
+STRING(return);
+STRING(struct);
+STRING(TR);
+STRING(Tr);
+STRING(tr);
+STRING(trUtf8);
+STRING(translate);
+STRING(using);
+
+uint CppParser::getToken()
+{
+ restart:
+ // Failing this assertion would mean losing the preallocated buffer.
+ Q_ASSERT(yyWord.isDetached());
+ yyWord.resize(0);
+
+ while (yyCh != EOF) {
+ yyLineNo = yyCurLineNo;
+
+ if (yyCh == '#' && yyAtNewline) {
+ /*
+ Early versions of lupdate complained about
+ unbalanced braces in the following code:
+
+ #ifdef ALPHA
+ while (beta) {
+ #else
+ while (gamma) {
+ #endif
+ delta;
+ }
+
+ The code contains, indeed, two opening braces for
+ one closing brace; yet there's no reason to panic.
+
+ The solution is to remember yyBraceDepth as it was
+ when #if, #ifdef or #ifndef was met, and to set
+ yyBraceDepth to that value when meeting #elif or
+ #else.
+ */
+ do {
+ yyCh = getChar();
+ } while (isspace(yyCh) && yyCh != '\n');
+
+ switch (yyCh) {
+ case 'd': // define
+ // Skip over the name of the define to avoid it being interpreted as c++ code
+ do { // Rest of "define"
+ yyCh = getChar();
+ if (yyCh == EOF)
+ return Tok_Eof;
+ if (yyCh == '\n')
+ goto restart;
+ } while (!isspace(yyCh));
+ do { // Space beween "define" and macro name
+ yyCh = getChar();
+ if (yyCh == EOF)
+ return Tok_Eof;
+ if (yyCh == '\n')
+ goto restart;
+ } while (isspace(yyCh));
+ do { // Macro name
+ if (yyCh == '(') {
+ // Argument list. Follows the name without a space, and no
+ // paren nesting is possible.
+ do {
+ yyCh = getChar();
+ if (yyCh == EOF)
+ return Tok_Eof;
+ if (yyCh == '\n')
+ goto restart;
+ } while (yyCh != ')');
+ break;
+ }
+ yyCh = getChar();
+ if (yyCh == EOF)
+ return Tok_Eof;
+ if (yyCh == '\n')
+ goto restart;
+ } while (!isspace(yyCh));
+ do { // Shortcut the immediate newline case if no comments follow.
+ yyCh = getChar();
+ if (yyCh == EOF)
+ return Tok_Eof;
+ if (yyCh == '\n')
+ goto restart;
+ } while (isspace(yyCh));
+
+ saveState(&savedState);
+ yyMinBraceDepth = yyBraceDepth;
+ inDefine = true;
+ goto restart;
+ case 'i':
+ yyCh = getChar();
+ if (yyCh == 'f') {
+ // if, ifdef, ifndef
+ yyIfdefStack.push(IfdefState(yyBraceDepth, yyParenDepth));
+ yyCh = getChar();
+ } else if (yyCh == 'n') {
+ // include
+ do {
+ yyCh = getChar();
+ } while (yyCh != EOF && !isspace(yyCh));
+ do {
+ yyCh = getChar();
+ } while (isspace(yyCh));
+ int tChar;
+ if (yyCh == '"')
+ tChar = '"';
+ else if (yyCh == '<')
+ tChar = '>';
+ else
+ break;
+ ushort *ptr = (ushort *)yyWord.unicode();
+ forever {
+ yyCh = getChar();
+ if (yyCh == EOF || yyCh == '\n')
+ break;
+ if (yyCh == tChar) {
+ yyCh = getChar();
+ break;
+ }
+ *ptr++ = yyCh;
+ }
+ yyWord.resize(ptr - (ushort *)yyWord.unicode());
+ return (tChar == '"') ? Tok_QuotedInclude : Tok_AngledInclude;
+ }
+ break;
+ case 'e':
+ yyCh = getChar();
+ if (yyCh == 'l') {
+ // elif, else
+ if (!yyIfdefStack.isEmpty()) {
+ IfdefState &is = yyIfdefStack.top();
+ if (is.elseLine != -1) {
+ if (yyBraceDepth != is.braceDepth1st || yyParenDepth != is.parenDepth1st)
+ qWarning("%s:%d: Parenthesis/brace mismatch between "
+ "#if and #else branches; using #if branch\n",
+ qPrintable(yyFileName), is.elseLine);
+ } else {
+ is.braceDepth1st = yyBraceDepth;
+ is.parenDepth1st = yyParenDepth;
+ saveState(&is.state);
+ }
+ is.elseLine = yyLineNo;
+ yyBraceDepth = is.braceDepth;
+ yyParenDepth = is.parenDepth;
+ }
+ yyCh = getChar();
+ } else if (yyCh == 'n') {
+ // endif
+ if (!yyIfdefStack.isEmpty()) {
+ IfdefState is = yyIfdefStack.pop();
+ if (is.elseLine != -1) {
+ if (yyBraceDepth != is.braceDepth1st || yyParenDepth != is.parenDepth1st)
+ qWarning("%s:%d: Parenthesis/brace mismatch between "
+ "#if and #else branches; using #if branch\n",
+ qPrintable(yyFileName), is.elseLine);
+ yyBraceDepth = is.braceDepth1st;
+ yyParenDepth = is.parenDepth1st;
+ loadState(&is.state);
+ }
+ }
+ yyCh = getChar();
+ }
+ break;
+ }
+ // Optimization: skip over rest of preprocessor directive
+ do {
+ if (yyCh == '/') {
+ yyCh = getChar();
+ if (yyCh == '/') {
+ do {
+ yyCh = getChar();
+ } while (yyCh != EOF && yyCh != '\n');
+ break;
+ } else if (yyCh == '*') {
+ bool metAster = false;
+
+ forever {
+ yyCh = getChar();
+ if (yyCh == EOF) {
+ qWarning("%s:%d: Unterminated C++ comment\n",
+ qPrintable(yyFileName), yyLineNo);
+ break;
+ }
+
+ if (yyCh == '*') {
+ metAster = true;
+ } else if (metAster && yyCh == '/') {
+ yyCh = getChar();
+ break;
+ } else {
+ metAster = false;
+ }
+ }
+ }
+ } else {
+ yyCh = getChar();
+ }
+ } while (yyCh != '\n' && yyCh != EOF);
+ yyCh = getChar();
+ } else if ((yyCh >= 'A' && yyCh <= 'Z') || (yyCh >= 'a' && yyCh <= 'z') || yyCh == '_') {
+ ushort *ptr = (ushort *)yyWord.unicode();
+ do {
+ *ptr++ = yyCh;
+ yyCh = getChar();
+ } while ((yyCh >= 'A' && yyCh <= 'Z') || (yyCh >= 'a' && yyCh <= 'z')
+ || (yyCh >= '0' && yyCh <= '9') || yyCh == '_');
+ yyWord.resize(ptr - (ushort *)yyWord.unicode());
+
+ //qDebug() << "IDENT: " << yyWord;
+
+ switch (yyWord.unicode()[0].unicode()) {
+ case 'Q':
+ if (yyWord == strQ_OBJECT)
+ return Tok_Q_OBJECT;
+ if (yyWord == strQ_DECLARE_TR_FUNCTIONS)
+ return Tok_Q_DECLARE_TR_FUNCTIONS;
+ if (yyWord == strQT_TR_NOOP)
+ return Tok_tr;
+ if (yyWord == strQT_TRID_NOOP)
+ return Tok_trid;
+ if (yyWord == strQT_TRANSLATE_NOOP)
+ return Tok_translate;
+ if (yyWord == strQT_TRANSLATE_NOOP3)
+ return Tok_translate;
+ if (yyWord == strQT_TR_NOOP_UTF8)
+ return Tok_trUtf8;
+ if (yyWord == strQT_TRANSLATE_NOOP_UTF8)
+ return Tok_translateUtf8;
+ if (yyWord == strQT_TRANSLATE_NOOP3_UTF8)
+ return Tok_translateUtf8;
+ break;
+ case 'T':
+ // TR() for when all else fails
+ if (yyWord == strTR || yyWord == strTr)
+ return Tok_tr;
+ break;
+ case 'c':
+ if (yyWord == strclass)
+ return Tok_class;
+ break;
+ case 'f':
+ /*
+ QTranslator::findMessage() has the same parameters as
+ QApplication::translate().
+ */
+ if (yyWord == strfindMessage)
+ return Tok_translate;
+ if (yyWord == strfriend)
+ return Tok_friend;
+ break;
+ case 'n':
+ if (yyWord == strnamespace)
+ return Tok_namespace;
+ break;
+ case 'q':
+ if (yyWord == strqtTrId)
+ return Tok_trid;
+ break;
+ case 'r':
+ if (yyWord == strreturn)
+ return Tok_return;
+ break;
+ case 's':
+ if (yyWord == strstruct)
+ return Tok_class;
+ break;
+ case 't':
+ if (yyWord == strtr)
+ return Tok_tr;
+ if (yyWord == strtrUtf8)
+ return Tok_trUtf8;
+ if (yyWord == strtranslate)
+ return Tok_translate;
+ break;
+ case 'u':
+ if (yyWord == strusing)
+ return Tok_using;
+ break;
+ }
+ return Tok_Ident;
+ } else {
+ switch (yyCh) {
+ case '\n':
+ if (inDefine) {
+ loadState(&savedState);
+ prospectiveContext.clear();
+ yyBraceDepth = yyMinBraceDepth;
+ yyMinBraceDepth = 0;
+ inDefine = false;
+ }
+ yyCh = getChar();
+ break;
+ case '/':
+ yyCh = getChar();
+ if (yyCh == '/') {
+ ushort *ptr = (ushort *)yyWord.unicode() + yyWord.length();
+ do {
+ yyCh = getChar();
+ if (yyCh == EOF)
+ break;
+ *ptr++ = yyCh;
+ } while (yyCh != '\n');
+ yyWord.resize(ptr - (ushort *)yyWord.unicode());
+ } else if (yyCh == '*') {
+ bool metAster = false;
+ ushort *ptr = (ushort *)yyWord.unicode() + yyWord.length();
+
+ forever {
+ yyCh = getChar();
+ if (yyCh == EOF) {
+ qWarning("%s:%d: Unterminated C++ comment\n",
+ qPrintable(yyFileName), yyLineNo);
+ return Tok_Comment;
+ }
+ *ptr++ = yyCh;
+
+ if (yyCh == '*')
+ metAster = true;
+ else if (metAster && yyCh == '/')
+ break;
+ else
+ metAster = false;
+ }
+ yyWord.resize(ptr - (ushort *)yyWord.unicode() - 2);
+
+ yyCh = getChar();
+ }
+ return Tok_Comment;
+ case '"': {
+ ushort *ptr = (ushort *)yyWord.unicode() + yyWord.length();
+ yyCh = getChar();
+ while (yyCh != EOF && yyCh != '\n' && yyCh != '"') {
+ if (yyCh == '\\') {
+ yyCh = getChar();
+ if (yyCh == EOF || yyCh == '\n')
+ break;
+ *ptr++ = '\\';
+ }
+ *ptr++ = yyCh;
+ yyCh = getChar();
+ }
+ yyWord.resize(ptr - (ushort *)yyWord.unicode());
+
+ if (yyCh != '"')
+ qWarning("%s:%d: Unterminated C++ string\n",
+ qPrintable(yyFileName), yyLineNo);
+ else
+ yyCh = getChar();
+ return Tok_String;
+ }
+ case '-':
+ yyCh = getChar();
+ if (yyCh == '>') {
+ yyCh = getChar();
+ return Tok_Arrow;
+ }
+ break;
+ case ':':
+ yyCh = getChar();
+ if (yyCh == ':') {
+ yyCh = getChar();
+ return Tok_ColonColon;
+ }
+ return Tok_Colon;
+ // Incomplete: '<' might be part of '<=' or of template syntax.
+ // The main intent of not completely ignoring it is to break
+ // parsing of things like std::cout << QObject::tr() as
+ // context std::cout::QObject (see Task 161106)
+ case '=':
+ yyCh = getChar();
+ return Tok_Equals;
+ case '>':
+ case '<':
+ yyCh = getChar();
+ return Tok_Other;
+ case '\'':
+ yyCh = getChar();
+ if (yyCh == '\\')
+ yyCh = getChar();
+
+ forever {
+ if (yyCh == EOF || yyCh == '\n') {
+ qWarning("%s:%d: Unterminated C++ character\n",
+ qPrintable(yyFileName), yyLineNo);
+ break;
+ }
+ yyCh = getChar();
+ if (yyCh == '\'') {
+ yyCh = getChar();
+ break;
+ }
+ }
+ break;
+ case '{':
+ if (yyBraceDepth == 0)
+ yyBraceLineNo = yyCurLineNo;
+ yyBraceDepth++;
+ yyCh = getChar();
+ return Tok_LeftBrace;
+ case '}':
+ if (yyBraceDepth == yyMinBraceDepth) {
+ if (!inDefine)
+ qWarning("%s:%d: Excess closing brace in C++ code"
+ " (or abuse of the C++ preprocessor)\n",
+ qPrintable(yyFileName), yyCurLineNo);
+ // Avoid things getting messed up even more
+ yyCh = getChar();
+ return Tok_Semicolon;
+ }
+ yyBraceDepth--;
+ yyCh = getChar();
+ return Tok_RightBrace;
+ case '(':
+ if (yyParenDepth == 0)
+ yyParenLineNo = yyCurLineNo;
+ yyParenDepth++;
+ yyCh = getChar();
+ return Tok_LeftParen;
+ case ')':
+ if (yyParenDepth == 0)
+ qWarning("%s:%d: Excess closing parenthesis in C++ code"
+ " (or abuse of the C++ preprocessor)\n",
+ qPrintable(yyFileName), yyCurLineNo);
+ else
+ yyParenDepth--;
+ yyCh = getChar();
+ return Tok_RightParen;
+ case ',':
+ yyCh = getChar();
+ return Tok_Comma;
+ case ';':
+ yyCh = getChar();
+ return Tok_Semicolon;
+ case '0':
+ yyCh = getChar();
+ if (yyCh == 'x') {
+ do {
+ yyCh = getChar();
+ } while ((yyCh >= '0' && yyCh <= '9')
+ || (yyCh >= 'a' && yyCh <= 'f') || (yyCh >= 'A' && yyCh <= 'F'));
+ return Tok_Integer;
+ }
+ if (yyCh < '0' || yyCh > '9')
+ return Tok_Null;
+ // Fallthrough
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ do {
+ yyCh = getChar();
+ } while (yyCh >= '0' && yyCh <= '9');
+ return Tok_Integer;
+ default:
+ yyCh = getChar();
+ break;
+ }
+ }
+ }
+ return Tok_Eof;
+}
+
+/*
+ The second part of this source file are namespace/class related
+ utilities for the third part.
+*/
+
+void CppParser::saveState(SavedState *state)
+{
+ state->namespaces = namespaces;
+ state->namespaceDepths = namespaceDepths;
+ state->functionContext = functionContext;
+ state->functionContextUnresolved = functionContextUnresolved;
+ state->pendingContext = pendingContext;
+}
+
+void CppParser::loadState(const SavedState *state)
+{
+ namespaces = state->namespaces;
+ namespaceDepths = state->namespaceDepths;
+ functionContext = state->functionContext;
+ functionContextUnresolved = state->functionContextUnresolved;
+ pendingContext = state->pendingContext;
+}
+
+Namespace *CppParser::modifyNamespace(NamespaceList *namespaces, bool tryOrigin)
+{
+ Namespace *pns, *ns = &results->rootNamespace;
+ for (int i = 1; i < namespaces->count(); ++i) {
+ pns = ns;
+ if (!(ns = pns->children.value(namespaces->at(i)))) {
+ do {
+ ns = new Namespace;
+ if (tryOrigin)
+ if (const Namespace *ons = findNamespace(*namespaces, i + 1))
+ ns->classDef = ons->classDef;
+ pns->children.insert(namespaces->at(i), ns);
+ pns = ns;
+ } while (++i < namespaces->count());
+ break;
+ }
+ }
+ return ns;
+}
+
+QString CppParser::stringifyNamespace(const NamespaceList &namespaces)
+{
+ QString ret;
+ for (int i = 1; i < namespaces.count(); ++i) {
+ if (i > 1)
+ ret += QLatin1String("::");
+ ret += namespaces.at(i).value();
+ }
+ return ret;
+}
+
+QStringList CppParser::stringListifyNamespace(const NamespaceList &namespaces)
+{
+ QStringList ret;
+ for (int i = 1; i < namespaces.count(); ++i)
+ ret << namespaces.at(i).value();
+ return ret;
+}
+
+bool CppParser::visitNamespace(const NamespaceList &namespaces, int nsCount,
+ VisitNamespaceCallback callback, void *context,
+ VisitRecorder &vr, const ParseResults *rslt) const
+{
+ const Namespace *ns = &rslt->rootNamespace;
+ for (int i = 1; i < nsCount; ++i)
+ if (!(ns = ns->children.value(namespaces.at(i))))
+ goto supers;
+ if ((this->*callback)(ns, context))
+ return true;
+supers:
+ foreach (const ParseResults *sup, rslt->includes)
+ if (vr.tryVisit(sup->fileId)
+ && visitNamespace(namespaces, nsCount, callback, context, vr, sup))
+ return true;
+ return false;
+}
+
+bool CppParser::visitNamespace(const NamespaceList &namespaces, int nsCount,
+ VisitNamespaceCallback callback, void *context) const
+{
+ VisitRecorder vr;
+ return visitNamespace(namespaces, nsCount, callback, context, vr, results);
+}
+
+QStringList CppParser::stringListifySegments(const QList<HashString> &segments)
+{
+ QStringList ret;
+ for (int i = 0; i < segments.count(); ++i)
+ ret << segments.at(i).value();
+ return ret;
+}
+
+struct QualifyOneData {
+ QualifyOneData(const NamespaceList &ns, int nsc, const HashString &seg, NamespaceList *rslvd)
+ : namespaces(ns), nsCount(nsc), segment(seg), resolved(rslvd)
+ {}
+
+ const NamespaceList &namespaces;
+ int nsCount;
+ const HashString &segment;
+ NamespaceList *resolved;
+ QSet<HashStringList> visitedUsings;
+};
+
+bool CppParser::qualifyOneCallbackOwn(const Namespace *ns, void *context) const
+{
+ QualifyOneData *data = (QualifyOneData *)context;
+ if (ns->children.contains(data->segment)) {
+ *data->resolved = data->namespaces.mid(0, data->nsCount);
+ *data->resolved << data->segment;
+ return true;
+ }
+ QHash<HashString, NamespaceList>::ConstIterator nsai = ns->aliases.constFind(data->segment);
+ if (nsai != ns->aliases.constEnd()) {
+ *data->resolved = *nsai;
+ return true;
+ }
+ return false;
+}
+
+bool CppParser::qualifyOneCallbackUsing(const Namespace *ns, void *context) const
+{
+ QualifyOneData *data = (QualifyOneData *)context;
+ foreach (const HashStringList &use, ns->usings)
+ if (!data->visitedUsings.contains(use)) {
+ data->visitedUsings.insert(use);
+ if (qualifyOne(use.value(), use.value().count(), data->segment, data->resolved))
+ return true;
+ }
+ return false;
+}
+
+bool CppParser::qualifyOne(const NamespaceList &namespaces, int nsCnt, const HashString &segment,
+ NamespaceList *resolved) const
+{
+ QualifyOneData data(namespaces, nsCnt, segment, resolved);
+
+ if (visitNamespace(namespaces, nsCnt, &CppParser::qualifyOneCallbackOwn, &data))
+ return true;
+
+ return visitNamespace(namespaces, nsCnt, &CppParser::qualifyOneCallbackUsing, &data);
+}
+
+bool CppParser::fullyQualify(const NamespaceList &namespaces, const QList<HashString> &segments,
+ bool isDeclaration,
+ NamespaceList *resolved, QStringList *unresolved) const
+{
+ int nsIdx;
+ int initSegIdx;
+
+ if (segments.first().value().isEmpty()) {
+ // fully qualified
+ if (segments.count() == 1) {
+ resolved->clear();
+ *resolved << HashString(QString());
+ return true;
+ }
+ initSegIdx = 1;
+ nsIdx = 0;
+ } else {
+ initSegIdx = 0;
+ nsIdx = namespaces.count() - 1;
+ }
+
+ do {
+ if (qualifyOne(namespaces, nsIdx + 1, segments[initSegIdx], resolved)) {
+ int segIdx = initSegIdx;
+ while (++segIdx < segments.count()) {
+ if (!qualifyOne(*resolved, resolved->count(), segments[segIdx], resolved)) {
+ if (unresolved)
+ *unresolved = stringListifySegments(segments.mid(segIdx));
+ return false;
+ }
+ }
+ return true;
+ }
+ } while (!isDeclaration && --nsIdx >= 0);
+ resolved->clear();
+ *resolved << HashString(QString());
+ if (unresolved)
+ *unresolved = stringListifySegments(segments.mid(initSegIdx));
+ return false;
+}
+
+bool CppParser::fullyQualify(const NamespaceList &namespaces, const QString &quali,
+ bool isDeclaration,
+ NamespaceList *resolved, QStringList *unresolved) const
+{
+ static QString strColons(QLatin1String("::"));
+
+ QList<HashString> segments;
+ foreach (const QString &str, quali.split(strColons)) // XXX slow, but needs to be fast(?)
+ segments << HashString(str);
+ return fullyQualify(namespaces, segments, isDeclaration, resolved, unresolved);
+}
+
+bool CppParser::findNamespaceCallback(const Namespace *ns, void *context) const
+{
+ *((const Namespace **)context) = ns;
+ return true;
+}
+
+const Namespace *CppParser::findNamespace(const NamespaceList &namespaces, int nsCount) const
+{
+ const Namespace *ns = 0;
+ if (nsCount == -1)
+ nsCount = namespaces.count();
+ visitNamespace(namespaces, nsCount, &CppParser::findNamespaceCallback, &ns);
+ return ns;
+}
+
+void CppParser::enterNamespace(NamespaceList *namespaces, const HashString &name)
+{
+ *namespaces << name;
+ if (!findNamespace(*namespaces))
+ modifyNamespace(namespaces, false);
+}
+
+void CppParser::truncateNamespaces(NamespaceList *namespaces, int length)
+{
+ if (namespaces->count() > length)
+ namespaces->erase(namespaces->begin() + length, namespaces->end());
+}
+
+/*
+ Functions for processing include files.
+*/
+
+ParseResultHash &CppFiles::parsedFiles()
+{
+ static ParseResultHash parsed;
+
+ return parsed;
+}
+
+TranslatorHash &CppFiles::translatedFiles()
+{
+ static TranslatorHash tors;
+
+ return tors;
+}
+
+QSet<QString> &CppFiles::blacklistedFiles()
+{
+ static QSet<QString> blacklisted;
+
+ return blacklisted;
+}
+
+const ParseResults *CppFiles::getResults(const QString &cleanFile)
+{
+ return parsedFiles().value(cleanFile);
+}
+
+void CppFiles::setResults(const QString &cleanFile, const ParseResults *results)
+{
+ parsedFiles().insert(cleanFile, results);
+}
+
+const Translator *CppFiles::getTranslator(const QString &cleanFile)
+{
+ return translatedFiles().value(cleanFile);
+}
+
+void CppFiles::setTranslator(const QString &cleanFile, const Translator *tor)
+{
+ translatedFiles().insert(cleanFile, tor);
+}
+
+bool CppFiles::isBlacklisted(const QString &cleanFile)
+{
+ return blacklistedFiles().contains(cleanFile);
+}
+
+void CppFiles::setBlacklisted(const QString &cleanFile)
+{
+ blacklistedFiles().insert(cleanFile);
+}
+
+static bool isHeader(const QString &name)
+{
+ QString fileExt = QFileInfo(name).suffix();
+ return fileExt.isEmpty() || fileExt.startsWith(QLatin1Char('h'), Qt::CaseInsensitive);
+}
+
+void CppParser::processInclude(const QString &file, ConversionData &cd,
+ QSet<QString> &inclusions)
+{
+ QString cleanFile = QDir::cleanPath(file);
+
+ if (inclusions.contains(cleanFile)) {
+ qWarning("%s:%d: circular inclusion of %s\n",
+ qPrintable(yyFileName), yyLineNo, qPrintable(cleanFile));
+ return;
+ }
+
+ // If the #include is in any kind of namespace, has been blacklisted previously,
+ // or is not a header file (stdc++ extensionless or *.h*), then really include
+ // it. Otherwise it is safe to process it stand-alone and re-use the parsed
+ // namespace data for inclusion into other files.
+ bool isIndirect = false;
+ if (namespaces.count() == 1 && functionContext.count() == 1
+ && functionContextUnresolved.isEmpty() && pendingContext.isEmpty()
+ && !CppFiles::isBlacklisted(cleanFile)
+ && isHeader(cleanFile)) {
+
+ if (const ParseResults *res = CppFiles::getResults(cleanFile)) {
+ results->includes.insert(res);
+ return;
+ }
+
+ isIndirect = true;
+ }
+
+ QFile f(cleanFile);
+ if (!f.open(QIODevice::ReadOnly)) {
+ qWarning("%s:%d: Cannot open %s: %s\n",
+ qPrintable(yyFileName), yyLineNo,
+ qPrintable(cleanFile), qPrintable(f.errorString()));
+ return;
+ }
+
+ QTextStream ts(&f);
+ ts.setCodec(yySourceCodec);
+ ts.setAutoDetectUnicode(true);
+
+ inclusions.insert(cleanFile);
+ if (isIndirect) {
+ CppParser parser;
+ foreach (const QString &projectRoot, cd.m_projectRoots)
+ if (cleanFile.startsWith(projectRoot)) {
+ parser.setTranslator(new Translator);
+ break;
+ }
+ parser.setInput(ts, cleanFile);
+ parser.parse(cd.m_defaultContext, cd, inclusions);
+ results->includes.insert(parser.recordResults(true));
+ } else {
+ CppParser parser(results);
+ parser.namespaces = namespaces;
+ parser.functionContext = functionContext;
+ parser.functionContextUnresolved = functionContextUnresolved;
+ parser.pendingContext = pendingContext;
+ parser.setInput(ts, cleanFile);
+ parser.parseInternal(cd, inclusions);
+ // Avoid that messages obtained by direct scanning are used
+ CppFiles::setBlacklisted(cleanFile);
+ }
+ inclusions.remove(cleanFile);
+}
+
+/*
+ The third part of this source file is the parser. It accomplishes
+ a very easy task: It finds all strings inside a tr() or translate()
+ call, and possibly finds out the context of the call. It supports
+ three cases: (1) the context is specified, as in
+ FunnyDialog::tr("Hello") or translate("FunnyDialog", "Hello");
+ (2) the call appears within an inlined function; (3) the call
+ appears within a function defined outside the class definition.
+*/
+
+bool CppParser::match(uint t)
+{
+ bool matches = (yyTok == t);
+ if (matches)
+ yyTok = getToken();
+ return matches;
+}
+
+bool CppParser::matchString(QString *s)
+{
+ bool matches = false;
+ s->clear();
+ forever {
+ while (yyTok == Tok_Comment)
+ yyTok = getToken();
+ if (yyTok != Tok_String)
+ return matches;
+ matches = true;
+ *s += yyWord;
+ s->detach();
+ yyTok = getToken();
+ }
+}
+
+STRING(QApplication);
+STRING(QCoreApplication);
+STRING(UnicodeUTF8);
+STRING(DefaultCodec);
+STRING(CodecForTr);
+
+bool CppParser::matchEncoding(bool *utf8)
+{
+ if (yyTok != Tok_Ident)
+ return false;
+ if (yyWord == strQApplication || yyWord == strQCoreApplication) {
+ yyTok = getToken();
+ if (yyTok == Tok_ColonColon)
+ yyTok = getToken();
+ }
+ if (yyWord == strUnicodeUTF8) {
+ *utf8 = true;
+ yyTok = getToken();
+ return true;
+ }
+ if (yyWord == strDefaultCodec || yyWord == strCodecForTr) {
+ *utf8 = false;
+ yyTok = getToken();
+ return true;
+ }
+ return false;
+}
+
+bool CppParser::matchStringOrNull(QString *s)
+{
+ return matchString(s) || match(Tok_Null);
+}
+
+/*
+ * match any expression that can return a number, which can be
+ * 1. Literal number (e.g. '11')
+ * 2. simple identifier (e.g. 'm_count')
+ * 3. simple function call (e.g. 'size()' )
+ * 4. function call on an object (e.g. 'list.size()')
+ * 5. function call on an object (e.g. 'list->size()')
+ *
+ * Other cases:
+ * size(2,4)
+ * list().size()
+ * list(a,b).size(2,4)
+ * etc...
+ */
+bool CppParser::matchExpression()
+{
+ if (match(Tok_Null) || match(Tok_Integer))
+ return true;
+
+ int parenlevel = 0;
+ while (match(Tok_Ident) || parenlevel > 0) {
+ if (yyTok == Tok_RightParen) {
+ if (parenlevel == 0) break;
+ --parenlevel;
+ yyTok = getToken();
+ } else if (yyTok == Tok_LeftParen) {
+ yyTok = getToken();
+ if (yyTok == Tok_RightParen) {
+ yyTok = getToken();
+ } else {
+ ++parenlevel;
+ }
+ } else if (yyTok == Tok_Ident) {
+ continue;
+ } else if (yyTok == Tok_Arrow) {
+ yyTok = getToken();
+ } else if (parenlevel == 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+QString CppParser::transcode(const QString &str, bool utf8)
+{
+ static const char tab[] = "abfnrtv";
+ static const char backTab[] = "\a\b\f\n\r\t\v";
+ const QString in = (!utf8 || yySourceIsUnicode)
+ ? str : QString::fromUtf8(yySourceCodec->fromUnicode(str).data());
+ QString out;
+
+ out.reserve(in.length());
+ for (int i = 0; i < in.length();) {
+ ushort c = in[i++].unicode();
+ if (c == '\\') {
+ if (i >= in.length())
+ break;
+ c = in[i++].unicode();
+
+ if (c == '\n')
+ continue;
+
+ if (c == 'x') {
+ QByteArray hex;
+ while (i < in.length() && isxdigit((c = in[i].unicode()))) {
+ hex += c;
+ i++;
+ }
+ out += hex.toUInt(0, 16);
+ } else if (c >= '0' && c < '8') {
+ QByteArray oct;
+ int n = 0;
+ oct += c;
+ while (n < 2 && i < in.length() && (c = in[i].unicode()) >= '0' && c < '8') {
+ i++;
+ n++;
+ oct += c;
+ }
+ out += oct.toUInt(0, 8);
+ } else {
+ const char *p = strchr(tab, c);
+ out += QChar(QLatin1Char(!p ? c : backTab[p - tab]));
+ }
+ } else {
+ out += c;
+ }
+ }
+ return out;
+}
+
+void CppParser::recordMessage(
+ int line, const QString &context, const QString &text, const QString &comment,
+ const QString &extracomment, const QString &msgid, const TranslatorMessage::ExtraData &extra,
+ bool utf8, bool plural)
+{
+ TranslatorMessage msg(
+ transcode(context, utf8), transcode(text, utf8), transcode(comment, utf8), QString(),
+ yyFileName, line, QStringList(),
+ TranslatorMessage::Unfinished, plural);
+ msg.setExtraComment(transcode(extracomment.simplified(), utf8));
+ msg.setId(msgid);
+ msg.setExtras(extra);
+ if ((utf8 || yyForceUtf8) && !yyCodecIsUtf8 && msg.needs8Bit())
+ msg.setUtf8(true);
+ tor->append(msg);
+}
+
+void CppParser::parse(const QString &initialContext, ConversionData &cd,
+ QSet<QString> &inclusions)
+{
+ if (tor)
+ yyCodecIsUtf8 = (tor->codecName() == "UTF-8");
+
+ namespaces << HashString();
+ functionContext = namespaces;
+ functionContextUnresolved = initialContext;
+
+ parseInternal(cd, inclusions);
+}
+
+void CppParser::parseInternal(ConversionData &cd, QSet<QString> &inclusions)
+{
+ static QString strColons(QLatin1String("::"));
+
+ QString context;
+ QString text;
+ QString comment;
+ QString extracomment;
+ QString msgid;
+ QString sourcetext;
+ TranslatorMessage::ExtraData extra;
+ QString prefix;
+#ifdef DIAGNOSE_RETRANSLATABILITY
+ QString functionName;
+#endif
+ int line;
+ bool utf8;
+ bool yyTokColonSeen = false; // Start of c'tor's initializer list
+
+ yyWord.reserve(yyInStr.size()); // Rather insane. That's because we do no length checking.
+ yyInPtr = (const ushort *)yyInStr.unicode();
+ yyCh = getChar();
+ yyTok = getToken();
+ while (yyTok != Tok_Eof) {
+ //qDebug() << "TOKEN: " << yyTok;
+ switch (yyTok) {
+ case Tok_QuotedInclude: {
+ text = QDir(QFileInfo(yyFileName).absolutePath()).absoluteFilePath(yyWord);
+ text.detach();
+ if (QFileInfo(text).isFile()) {
+ processInclude(text, cd, inclusions);
+ yyTok = getToken();
+ break;
+ }
+ }
+ /* fall through */
+ case Tok_AngledInclude: {
+ QStringList cSources = cd.m_allCSources.values(yyWord);
+ if (!cSources.isEmpty()) {
+ foreach (const QString &cSource, cSources)
+ processInclude(cSource, cd, inclusions);
+ goto incOk;
+ }
+ foreach (const QString &incPath, cd.m_includePath) {
+ text = QDir(incPath).absoluteFilePath(yyWord);
+ text.detach();
+ if (QFileInfo(text).isFile()) {
+ processInclude(text, cd, inclusions);
+ goto incOk;
+ }
+ }
+ incOk:
+ yyTok = getToken();
+ break;
+ }
+ case Tok_friend:
+ yyTok = getToken();
+ // These are forward declarations, so ignore them.
+ if (yyTok == Tok_class)
+ yyTok = getToken();
+ break;
+ case Tok_class:
+ yyTokColonSeen = false;
+ /*
+ Partial support for inlined functions.
+ */
+ yyTok = getToken();
+ if (yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0) {
+ QList<HashString> quali;
+ HashString fct;
+ do {
+ /*
+ This code should execute only once, but we play
+ safe with impure definitions such as
+ 'class Q_EXPORT QMessageBox', in which case
+ 'QMessageBox' is the class name, not 'Q_EXPORT'.
+ */
+ text = yyWord;
+ text.detach();
+ fct.setValue(text);
+ yyTok = getToken();
+ } while (yyTok == Tok_Ident);
+ while (yyTok == Tok_ColonColon) {
+ yyTok = getToken();
+ if (yyTok != Tok_Ident)
+ break; // Oops ...
+ quali << fct;
+ text = yyWord;
+ text.detach();
+ fct.setValue(text);
+ yyTok = getToken();
+ }
+ while (yyTok == Tok_Comment)
+ yyTok = getToken();
+ if (yyTok == Tok_Colon) {
+ // Skip any token until '{' since we might do things wrong if we find
+ // a '::' token here.
+ do {
+ yyTok = getToken();
+ } while (yyTok != Tok_LeftBrace && yyTok != Tok_Eof);
+ } else {
+ if (yyTok != Tok_LeftBrace) {
+ // Obviously a forward declaration. We skip those, as they
+ // don't create actually usable namespaces.
+ break;
+ }
+ }
+
+ if (!quali.isEmpty()) {
+ // Forward-declared class definitions can be namespaced.
+ NamespaceList nsl;
+ if (!fullyQualify(namespaces, quali, true, &nsl, 0)) {
+ qWarning("%s:%d: Ignoring definition of undeclared qualified class\n",
+ qPrintable(yyFileName), yyLineNo);
+ break;
+ }
+ namespaceDepths.push(namespaces.count());
+ namespaces = nsl;
+ } else {
+ namespaceDepths.push(namespaces.count());
+ }
+ enterNamespace(&namespaces, fct);
+
+ functionContext = namespaces;
+ functionContextUnresolved.clear(); // Pointless
+ prospectiveContext.clear();
+ pendingContext.clear();
+ }
+ break;
+ case Tok_namespace:
+ yyTokColonSeen = false;
+ yyTok = getToken();
+ if (yyTok == Tok_Ident) {
+ text = yyWord;
+ text.detach();
+ HashString ns = HashString(text);
+ yyTok = getToken();
+ if (yyTok == Tok_LeftBrace) {
+ namespaceDepths.push(namespaces.count());
+ enterNamespace(&namespaces, ns);
+ yyTok = getToken();
+ } else if (yyTok == Tok_Equals) {
+ // e.g. namespace Is = OuterSpace::InnerSpace;
+ QList<HashString> fullName;
+ yyTok = getToken();
+ if (yyTok == Tok_ColonColon)
+ fullName.append(HashString(QString()));
+ while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
+ if (yyTok == Tok_Ident) {
+ text = yyWord;
+ text.detach();
+ fullName.append(HashString(text));
+ }
+ yyTok = getToken();
+ }
+ if (fullName.isEmpty())
+ break;
+ NamespaceList nsl;
+ if (fullyQualify(namespaces, fullName, false, &nsl, 0))
+ modifyNamespace(&namespaces, false)->aliases[ns] = nsl;
+ }
+ } else if (yyTok == Tok_LeftBrace) {
+ // Anonymous namespace
+ namespaceDepths.push(namespaces.count());
+ yyTok = getToken();
+ }
+ break;
+ case Tok_using:
+ yyTok = getToken();
+ // XXX this should affect only the current scope, not the entire current namespace
+ if (yyTok == Tok_namespace) {
+ QList<HashString> fullName;
+ yyTok = getToken();
+ if (yyTok == Tok_ColonColon)
+ fullName.append(HashString(QString()));
+ while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
+ if (yyTok == Tok_Ident) {
+ text = yyWord;
+ text.detach();
+ fullName.append(HashString(text));
+ }
+ yyTok = getToken();
+ }
+ NamespaceList nsl;
+ if (fullyQualify(namespaces, fullName, false, &nsl, 0))
+ modifyNamespace(&namespaces, false)->usings << HashStringList(nsl);
+ } else {
+ QList<HashString> fullName;
+ if (yyTok == Tok_ColonColon)
+ fullName.append(HashString(QString()));
+ while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
+ if (yyTok == Tok_Ident) {
+ text = yyWord;
+ text.detach();
+ fullName.append(HashString(text));
+ }
+ yyTok = getToken();
+ }
+ if (fullName.isEmpty())
+ break;
+ NamespaceList nsl;
+ if (fullyQualify(namespaces, fullName, false, &nsl, 0))
+ modifyNamespace(&namespaces, true)->aliases[nsl.last()] = nsl;
+ }
+ break;
+ case Tok_tr:
+ case Tok_trUtf8:
+ if (!tor)
+ goto case_default;
+ if (!sourcetext.isEmpty())
+ qWarning("%s:%d: //%% cannot be used with tr() / QT_TR_NOOP(). Ignoring\n",
+ qPrintable(yyFileName), yyLineNo);
+ utf8 = (yyTok == Tok_trUtf8);
+ line = yyLineNo;
+ yyTok = getToken();
+ if (match(Tok_LeftParen) && matchString(&text) && !text.isEmpty()) {
+ comment.clear();
+ bool plural = false;
+
+ if (match(Tok_RightParen)) {
+ // no comment
+ } else if (match(Tok_Comma) && matchStringOrNull(&comment)) { //comment
+ if (match(Tok_RightParen)) {
+ // ok,
+ } else if (match(Tok_Comma)) {
+ plural = true;
+ }
+ }
+ if (!pendingContext.isEmpty()) {
+ QStringList unresolved;
+ if (!fullyQualify(namespaces, pendingContext, true, &functionContext, &unresolved)) {
+ functionContextUnresolved = unresolved.join(strColons);
+ qWarning("%s:%d: Qualifying with unknown namespace/class %s::%s\n",
+ qPrintable(yyFileName), yyLineNo,
+ qPrintable(stringifyNamespace(functionContext)),
+ qPrintable(unresolved.first()));
+ }
+ pendingContext.clear();
+ }
+ if (prefix.isEmpty()) {
+ if (functionContextUnresolved.isEmpty()) {
+ int idx = functionContext.length();
+ if (idx < 2) {
+ qWarning("%s:%d: tr() cannot be called without context\n",
+ qPrintable(yyFileName), yyLineNo);
+ break;
+ }
+ Namespace *fctx;
+ while (!(fctx = findNamespace(functionContext, idx)->classDef)->hasTrFunctions) {
+ if (idx == 1) {
+ context = stringifyNamespace(functionContext);
+ fctx = findNamespace(functionContext)->classDef;
+ if (!fctx->complained) {
+ qWarning("%s:%d: Class '%s' lacks Q_OBJECT macro\n",
+ qPrintable(yyFileName), yyLineNo,
+ qPrintable(context));
+ fctx->complained = true;
+ }
+ goto gotctx;
+ }
+ --idx;
+ }
+ if (fctx->trQualification.isEmpty()) {
+ context.clear();
+ for (int i = 1;;) {
+ context += functionContext.at(i).value();
+ if (++i == idx)
+ break;
+ context += strColons;
+ }
+ fctx->trQualification = context;
+ } else {
+ context = fctx->trQualification;
+ }
+ } else {
+ context = (stringListifyNamespace(functionContext)
+ << functionContextUnresolved).join(strColons);
+ }
+ } else {
+#ifdef DIAGNOSE_RETRANSLATABILITY
+ int last = prefix.lastIndexOf(strColons);
+ QString className = prefix.mid(last == -1 ? 0 : last + 2);
+ if (!className.isEmpty() && className == functionName) {
+ qWarning("%s::%d: It is not recommended to call tr() from within a constructor '%s::%s' ",
+ qPrintable(yyFileName), yyLineNo,
+ className.constData(), functionName.constData());
+ }
+#endif
+ prefix.chop(2);
+ NamespaceList nsl;
+ QStringList unresolved;
+ if (fullyQualify(functionContext, prefix, false, &nsl, &unresolved)) {
+ Namespace *fctx = findNamespace(nsl)->classDef;
+ if (fctx->trQualification.isEmpty()) {
+ context = stringifyNamespace(nsl);
+ fctx->trQualification = context;
+ } else {
+ context = fctx->trQualification;
+ }
+ if (!fctx->hasTrFunctions && !fctx->complained) {
+ qWarning("%s:%d: Class '%s' lacks Q_OBJECT macro\n",
+ qPrintable(yyFileName), yyLineNo,
+ qPrintable(context));
+ fctx->complained = true;
+ }
+ } else {
+ context = (stringListifyNamespace(nsl) + unresolved).join(strColons);
+ }
+ prefix.clear();
+ }
+
+ gotctx:
+ recordMessage(line, context, text, comment, extracomment, msgid, extra, utf8, plural);
+ }
+ extracomment.clear();
+ msgid.clear();
+ extra.clear();
+ break;
+ case Tok_translateUtf8:
+ case Tok_translate:
+ if (!tor)
+ goto case_default;
+ if (!sourcetext.isEmpty())
+ qWarning("%s:%d: //%% cannot be used with translate() / QT_TRANSLATE_NOOP(). Ignoring\n",
+ qPrintable(yyFileName), yyLineNo);
+ utf8 = (yyTok == Tok_translateUtf8);
+ line = yyLineNo;
+ yyTok = getToken();
+ if (match(Tok_LeftParen)
+ && matchString(&context)
+ && match(Tok_Comma)
+ && matchString(&text) && !text.isEmpty())
+ {
+ comment.clear();
+ bool plural = false;
+ if (!match(Tok_RightParen)) {
+ // look for comment
+ if (match(Tok_Comma) && matchStringOrNull(&comment)) {
+ if (!match(Tok_RightParen)) {
+ // look for encoding
+ if (match(Tok_Comma)) {
+ if (matchEncoding(&utf8)) {
+ if (!match(Tok_RightParen)) {
+ // look for the plural quantifier,
+ // this can be a number, an identifier or
+ // a function call,
+ // so for simplicity we mark it as plural if
+ // we know we have a comma instead of an
+ // right parentheses.
+ plural = match(Tok_Comma);
+ }
+ } else {
+ // This can be a QTranslator::translate("context",
+ // "source", "comment", n) plural translation
+ if (matchExpression() && match(Tok_RightParen)) {
+ plural = true;
+ } else {
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ recordMessage(line, context, text, comment, extracomment, msgid, extra, utf8, plural);
+ }
+ extracomment.clear();
+ msgid.clear();
+ extra.clear();
+ break;
+ case Tok_trid:
+ if (!tor)
+ goto case_default;
+ if (sourcetext.isEmpty()) {
+ yyTok = getToken();
+ } else {
+ if (!msgid.isEmpty())
+ qWarning("%s:%d: //= cannot be used with qtTrId() / QT_TRID_NOOP(). Ignoring\n",
+ qPrintable(yyFileName), yyLineNo);
+ //utf8 = false; // Maybe use //%% or something like that
+ line = yyLineNo;
+ yyTok = getToken();
+ if (match(Tok_LeftParen) && matchString(&msgid) && !msgid.isEmpty()) {
+ bool plural = match(Tok_Comma);
+ recordMessage(line, QString(), sourcetext, QString(), extracomment,
+ msgid, extra, false, plural);
+ }
+ sourcetext.clear();
+ }
+ extracomment.clear();
+ msgid.clear();
+ extra.clear();
+ break;
+ case Tok_Q_DECLARE_TR_FUNCTIONS:
+ if (getMacroArgs()) {
+ Namespace *ns = modifyNamespace(&namespaces, true);
+ ns->hasTrFunctions = true;
+ ns->trQualification = yyWord;
+ ns->trQualification.detach();
+ }
+ yyTok = getToken();
+ break;
+ case Tok_Q_OBJECT:
+ modifyNamespace(&namespaces, true)->hasTrFunctions = true;
+ yyTok = getToken();
+ break;
+ case Tok_Ident:
+ prefix += yyWord;
+ prefix.detach();
+ yyTok = getToken();
+ if (yyTok != Tok_ColonColon) {
+ prefix.clear();
+ if (yyTok == Tok_Ident && !yyParenDepth)
+ prospectiveContext.clear();
+ }
+ break;
+ case Tok_Comment:
+ if (!tor)
+ goto case_default;
+ if (yyWord.startsWith(QLatin1Char(':'))) {
+ yyWord.remove(0, 1);
+ extracomment += yyWord;
+ extracomment.detach();
+ } else if (yyWord.startsWith(QLatin1Char('='))) {
+ yyWord.remove(0, 1);
+ msgid = yyWord.simplified();
+ msgid.detach();
+ } else if (yyWord.startsWith(QLatin1Char('~'))) {
+ yyWord.remove(0, 1);
+ text = yyWord.trimmed();
+ int k = text.indexOf(QLatin1Char(' '));
+ if (k > -1)
+ extra.insert(text.left(k), text.mid(k + 1).trimmed());
+ text.clear();
+ } else if (yyWord.startsWith(QLatin1Char('%'))) {
+ sourcetext.reserve(sourcetext.length() + yyWord.length());
+ ushort *ptr = (ushort *)sourcetext.data() + sourcetext.length();
+ int p = 1, c;
+ forever {
+ if (p >= yyWord.length())
+ break;
+ c = yyWord.unicode()[p++].unicode();
+ if (isspace(c))
+ continue;
+ if (c != '"') {
+ qWarning("%s:%d: Unexpected character in meta string\n",
+ qPrintable(yyFileName), yyLineNo);
+ break;
+ }
+ forever {
+ if (p >= yyWord.length()) {
+ whoops:
+ qWarning("%s:%d: Unterminated meta string\n",
+ qPrintable(yyFileName), yyLineNo);
+ break;
+ }
+ c = yyWord.unicode()[p++].unicode();
+ if (c == '"')
+ break;
+ if (c == '\\') {
+ if (p >= yyWord.length())
+ goto whoops;
+ c = yyWord.unicode()[p++].unicode();
+ if (c == '\n')
+ goto whoops;
+ *ptr++ = '\\';
+ }
+ *ptr++ = c;
+ }
+ }
+ sourcetext.resize(ptr - (ushort *)sourcetext.data());
+ } else {
+ const ushort *uc = (const ushort *)yyWord.unicode(); // Is zero-terminated
+ int idx = 0;
+ ushort c;
+ while ((c = uc[idx]) == ' ' || c == '\t' || c == '\n')
+ ++idx;
+ if (!memcmp(uc + idx, MagicComment.unicode(), MagicComment.length() * 2)) {
+ idx += MagicComment.length();
+ comment = QString::fromRawData(yyWord.unicode() + idx,
+ yyWord.length() - idx).simplified();
+ int k = comment.indexOf(QLatin1Char(' '));
+ if (k == -1) {
+ context = comment;
+ } else {
+ context = comment.left(k);
+ comment.remove(0, k + 1);
+ recordMessage(yyLineNo, context, QString(), comment, extracomment,
+ QString(), TranslatorMessage::ExtraData(), false, false);
+ extracomment.clear();
+ tor->setExtras(extra);
+ extra.clear();
+ }
+ }
+ }
+ yyTok = getToken();
+ break;
+ case Tok_Arrow:
+ yyTok = getToken();
+ if (yyTok == Tok_tr || yyTok == Tok_trUtf8)
+ qWarning("%s:%d: Cannot invoke tr() like this\n",
+ qPrintable(yyFileName), yyLineNo);
+ break;
+ case Tok_ColonColon:
+ if (yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0 && !yyTokColonSeen)
+ prospectiveContext = prefix;
+ prefix += strColons;
+ yyTok = getToken();
+#ifdef DIAGNOSE_RETRANSLATABILITY
+ if (yyTok == Tok_Ident && yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0) {
+ functionName = yyWord;
+ functionName.detach();
+ }
+#endif
+ break;
+ case Tok_RightBrace:
+ if (yyBraceDepth + 1 == namespaceDepths.count()) // class or namespace
+ truncateNamespaces(&namespaces, namespaceDepths.pop());
+ if (yyBraceDepth == namespaceDepths.count()) {
+ // function, class or namespace
+ if (!yyBraceDepth && !directInclude) {
+ truncateNamespaces(&functionContext, 1);
+ functionContextUnresolved = cd.m_defaultContext;
+ } else {
+ functionContext = namespaces;
+ functionContextUnresolved.clear();
+ }
+ pendingContext.clear();
+ }
+ // fallthrough
+ case Tok_Semicolon:
+ prospectiveContext.clear();
+ prefix.clear();
+ extracomment.clear();
+ msgid.clear();
+ extra.clear();
+ yyTokColonSeen = false;
+ yyTok = getToken();
+ break;
+ case Tok_Colon:
+ if (!prospectiveContext.isEmpty()
+ && yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0)
+ pendingContext = prospectiveContext;
+ yyTokColonSeen = true;
+ yyTok = getToken();
+ break;
+ case Tok_LeftBrace:
+ if (!prospectiveContext.isEmpty()
+ && yyBraceDepth == namespaceDepths.count() + 1 && yyParenDepth == 0)
+ pendingContext = prospectiveContext;
+ // fallthrough
+ case Tok_LeftParen:
+ case Tok_RightParen:
+ yyTokColonSeen = false;
+ yyTok = getToken();
+ break;
+ default:
+ if (!yyParenDepth)
+ prospectiveContext.clear();
+ case_default:
+ yyTok = getToken();
+ break;
+ }
+ }
+
+ if (yyBraceDepth != 0)
+ qWarning("%s:%d: Unbalanced opening brace in C++ code"
+ " (or abuse of the C++ preprocessor)\n",
+ qPrintable(yyFileName), yyBraceLineNo);
+ else if (yyParenDepth != 0)
+ qWarning("%s:%d: Unbalanced opening parenthesis in C++ code"
+ " (or abuse of the C++ preprocessor)\n",
+ qPrintable(yyFileName), yyParenLineNo);
+}
+
+const ParseResults *CppParser::recordResults(bool isHeader)
+{
+ if (tor) {
+ if (tor->messageCount()) {
+ CppFiles::setTranslator(yyFileName, tor);
+ } else {
+ delete tor;
+ tor = 0;
+ }
+ }
+ if (isHeader) {
+ const ParseResults *pr;
+ if (!tor && results->includes.count() == 1
+ && results->rootNamespace.children.isEmpty()
+ && results->rootNamespace.aliases.isEmpty()
+ && results->rootNamespace.usings.isEmpty()) {
+ // This is a forwarding header. Slash it.
+ pr = *results->includes.begin();
+ delete results;
+ } else {
+ results->fileId = nextFileId++;
+ pr = results;
+ }
+ CppFiles::setResults(yyFileName, pr);
+ return pr;
+ } else {
+ delete results;
+ return 0;
+ }
+}
+
+/*
+ Fetches tr() calls in C++ code in UI files (inside "<function>"
+ tag). This mechanism is obsolete.
+*/
+void fetchtrInlinedCpp(const QString &in, Translator &translator, const QString &context)
+{
+ CppParser parser;
+ parser.setInput(in);
+ ConversionData cd;
+ QSet<QString> inclusions;
+ parser.setTranslator(&translator);
+ parser.parse(context, cd, inclusions);
+ parser.deleteResults();
+}
+
+void loadCPP(Translator &translator, const QStringList &filenames, ConversionData &cd)
+{
+ QByteArray codecName = cd.m_codecForSource.isEmpty()
+ ? translator.codecName() : cd.m_codecForSource;
+ QTextCodec *codec = QTextCodec::codecForName(codecName);
+
+ foreach (const QString &filename, filenames) {
+ if (CppFiles::getResults(filename) || CppFiles::isBlacklisted(filename))
+ continue;
+
+ QFile file(filename);
+ if (!file.open(QIODevice::ReadOnly)) {
+ cd.appendError(QString::fromLatin1("Cannot open %1: %2")
+ .arg(filename, file.errorString()));
+ continue;
+ }
+
+ CppParser parser;
+ QTextStream ts(&file);
+ ts.setCodec(codec);
+ ts.setAutoDetectUnicode(true);
+ if (ts.codec()->name() == "UTF-16")
+ translator.setCodecName("System");
+ parser.setInput(ts, filename);
+ Translator *tor = new Translator;
+ tor->setCodecName(translator.codecName());
+ parser.setTranslator(tor);
+ QSet<QString> inclusions;
+ parser.parse(cd.m_defaultContext, cd, inclusions);
+ parser.recordResults(isHeader(filename));
+ }
+
+ foreach (const QString &filename, filenames)
+ if (!CppFiles::isBlacklisted(filename))
+ if (const Translator *tor = CppFiles::getTranslator(filename))
+ foreach (const TranslatorMessage &msg, tor->messages())
+ translator.extend(msg);
+}
+
+QT_END_NAMESPACE